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
Compare commits
122 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 155b2202c7 | |||
| 10c291eb9f | |||
| 349a8c727e | |||
| 68f2b71d14 | |||
| 69a6c0d060 | |||
| 885bdca0c4 | |||
| db035294a7 | |||
| 3216ba1df6 | |||
| 8d6b969d75 | |||
| b9723e0298 | |||
| c4aae676b2 | |||
| 585f4dd3aa | |||
| afa114d511 | |||
| 0aadd01493 | |||
| 9d98fbf9ee | |||
| b38274e134 | |||
| 02ab30180c | |||
| da63439d53 | |||
| bf1a29a6e8 | |||
| 6391d721ff | |||
| dfea6bcf83 | |||
| a7f207bb76 | |||
| b7915884b6 | |||
| 478f7bdba0 | |||
| c255f1e1b4 | |||
| 9c831a9da4 | |||
| 08d1ae97a7 | |||
| f8fe1e3e22 | |||
| be77cdf4aa | |||
| 1ba2e43d4d | |||
| 8dd5155562 | |||
| 4f4f581371 | |||
| 9705b3e42a | |||
| e626a7fc50 | |||
| d6ebb632e6 | |||
| 014ca9ca48 | |||
| d189d66f9e | |||
| c272a36cc5 | |||
| 1d6e05ee57 | |||
| ea44771d69 | |||
| 1da783aff9 | |||
| e772686c4b | |||
| a00f7b01f5 | |||
| 6b4089cace | |||
| 9ea7acf05c | |||
| bab43af41e | |||
| 10a2b2b872 | |||
| 458b37dbed | |||
| 55b38e7b85 | |||
| 4a96c5baaf | |||
| 539c5b5b96 | |||
| 7b7154e68f | |||
| 4aabb738a3 | |||
| 691dc42627 | |||
| 226873c1fb | |||
| a06a204b39 | |||
| e213609609 | |||
| 44d38b8661 | |||
| ccadb81970 | |||
| 0a3a940946 | |||
| 4613fbe80c | |||
| 9328f4a355 | |||
| da8b947ddf | |||
| b9658d0407 | |||
| 68d3731393 | |||
| 4ef4ed1a96 | |||
| c20e273a2c | |||
| 83d418e712 | |||
| 38d05e8a06 | |||
| c83a3bad8e | |||
| 0bd23ad244 | |||
| d8f74b2477 | |||
| dac09e92d1 | |||
| fbf979419e | |||
| 6126c35779 | |||
| a1749c9eda | |||
| 525c124fa5 | |||
| 57087a31f2 | |||
| c4b3295a45 | |||
| 418c753e6c | |||
| 8419f11883 | |||
| 1a0d783ff7 | |||
| 655e039df7 | |||
| 7726691cde | |||
| 67503aeb2a | |||
| b206b32748 | |||
| ad60861a3f | |||
| b77290f5e7 | |||
| b14730d37f | |||
| 9126396973 | |||
| d321ff3b85 | |||
| 7f38a25eef | |||
| 4820ab15f3 | |||
| 8d989e7a19 | |||
| 1f7ec96e1c | |||
| 969f177108 | |||
| e485c2747c | |||
| d99a51899b | |||
| 29677a19be | |||
| 21ee36e089 | |||
| 4e47dbee16 | |||
| e7ba4d0926 | |||
| 67d2f52f64 | |||
| 69d770b65e | |||
| 2492569e16 | |||
| 9c215bea6b | |||
| 7dc3581f8f | |||
| f38eb32eee | |||
| 222e356ff0 | |||
| c8022ccc45 | |||
| 9579833775 | |||
| 87ad289a54 | |||
| fd28af5f69 | |||
| 99c938b98f | |||
| 82d90418cd | |||
| 8b51be4940 | |||
| 54259f89bd | |||
| 272a9b9f42 | |||
| 9c94402f78 | |||
| 7271506262 | |||
| 09710fc5d6 | |||
| d958fbc0ab |
@@ -34,3 +34,13 @@ SET @ddl = IF(@col_exists = 0,
|
|||||||
PREPARE stmt FROM @ddl;
|
PREPARE stmt FROM @ddl;
|
||||||
EXECUTE stmt;
|
EXECUTE stmt;
|
||||||
DEALLOCATE PREPARE stmt;
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
|
||||||
|
UPDATE emulator_settings SET `key`='ws.whitelist' WHERE `key`='websockets.whitelist';
|
||||||
|
UPDATE emulator_settings SET `key`='ws.host' WHERE `key`='ws.nitro.host';
|
||||||
|
UPDATE emulator_settings SET `key`='ws.port' WHERE `key`='ws.nitro.port';
|
||||||
|
INSERT IGNORE INTO emulator_settings (`key`, `value`)
|
||||||
|
VALUES ('ws.ip.header', 'X-Forwarded-For');
|
||||||
|
|
||||||
|
INSERT IGNORE INTO emulator_settings (`key`, `value`)
|
||||||
|
VALUES ('ws.enabled', 'true');
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN `background_border_id` INT(11) NOT NULL DEFAULT 0 AFTER `background_id`;
|
||||||
|
|
||||||
|
ALTER TABLE infostand_backgrounds
|
||||||
|
CHANGE COLUMN `category` `category` ENUM('background', 'stand', 'overlay', 'card', 'border') NOT NULL ;
|
||||||
|
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `infostand_backgrounds` (`id`, `category`, `min_rank`, `is_hc_only`, `is_ambassador_only`) VALUES
|
||||||
|
(1, 'border', 1, 0, 0),
|
||||||
|
(2, 'border', 1, 0, 0),
|
||||||
|
(3, 'border', 1, 0, 0),
|
||||||
|
(4, 'border', 1, 0, 0),
|
||||||
|
(5, 'border', 1, 0, 0),
|
||||||
|
(6, 'border', 1, 0, 0),
|
||||||
|
(7, 'border', 1, 0, 0),
|
||||||
|
(8, 'border', 1, 0, 0),
|
||||||
|
(9, 'border', 1, 0, 0),
|
||||||
|
(10, 'border', 1, 0, 0),
|
||||||
|
(11, 'border', 1, 0, 0),
|
||||||
|
(12, 'border', 1, 0, 0),
|
||||||
|
(13, 'border', 1, 0, 0),
|
||||||
|
(14, 'border', 1, 0, 0),
|
||||||
|
(15, 'border', 1, 0, 0),
|
||||||
|
(16, 'border', 1, 0, 0),
|
||||||
|
(17, 'border', 1, 0, 0),
|
||||||
|
(18, 'border', 1, 0, 0),
|
||||||
|
(19, 'border', 1, 0, 0),
|
||||||
|
(20, 'border', 1, 0, 0),
|
||||||
|
(21, 'border', 1, 0, 0),
|
||||||
|
(22, 'border', 1, 0, 0),
|
||||||
|
(23, 'border', 1, 0, 0),
|
||||||
|
(24, 'border', 1, 0, 0),
|
||||||
|
(25, 'border', 1, 0, 0);
|
||||||
@@ -0,0 +1,474 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- Live required schema
|
||||||
|
-- ============================================================
|
||||||
|
-- Consolidated schema for the currently used Nitro/Arcturus live
|
||||||
|
-- additions. This file intentionally excludes old/unused migration
|
||||||
|
-- artifacts and dump-only data.
|
||||||
|
--
|
||||||
|
-- Scope:
|
||||||
|
-- - tables/columns currently referenced by Java code
|
||||||
|
-- - runtime settings required by secure assets/API, login, wired, and UI
|
||||||
|
-- - safe CREATE IF NOT EXISTS / ADD COLUMN IF NOT EXISTS statements
|
||||||
|
--
|
||||||
|
-- Assumes the base Arcturus database already exists.
|
||||||
|
-- Tested for MariaDB-style syntax used by this project.
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- Core settings support
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
|
||||||
|
ALTER TABLE `emulator_settings`
|
||||||
|
ADD COLUMN IF NOT EXISTS `comment` TEXT NULL DEFAULT '' AFTER `value`;
|
||||||
|
|
||||||
|
ALTER TABLE catalog_pages
|
||||||
|
ADD COLUMN IF NOT EXISTS `catalog_mode` ENUM('NORMAL', 'BUILDER', 'BOTH') NOT NULL DEFAULT 'NORMAL' AFTER `includes`;
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `wired_emulator_settings` (
|
||||||
|
`key` VARCHAR(255) NOT NULL,
|
||||||
|
`value` TEXT NOT NULL,
|
||||||
|
`comment` TEXT NULL DEFAULT '',
|
||||||
|
PRIMARY KEY (`key`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
|
||||||
|
('crypto.ws.enabled', '0'),
|
||||||
|
('crypto.ws.signing.enabled', '0'),
|
||||||
|
('crypto.ws.signing.public_key', ''),
|
||||||
|
('crypto.ws.signing.private_key', ''),
|
||||||
|
('login.access.jwt.secret', ''),
|
||||||
|
('login.remember.duration.days', '30'),
|
||||||
|
('login.remember.rotate.interval.minutes', '15'),
|
||||||
|
('login.remember.jwt.secret', ''),
|
||||||
|
('login.turnstile.enabled', '0'),
|
||||||
|
('login.turnstile.sitekey', ''),
|
||||||
|
('login.turnstile.secretkey', ''),
|
||||||
|
('login.ratelimit.enabled', '1'),
|
||||||
|
('login.ratelimit.max_attempts', '5'),
|
||||||
|
('login.ratelimit.window_sec', '60'),
|
||||||
|
('login.ratelimit.lockout_sec', '120'),
|
||||||
|
('login.register.enabled', '1'),
|
||||||
|
('register.max_per_ip', '5'),
|
||||||
|
('register.default.look', 'hr-100-7.hd-180-1.ch-210-66.lg-270-82.sh-290-80'),
|
||||||
|
('register.default.motto', 'I love Habbo!'),
|
||||||
|
('password.reset.url', 'http://localhost/reset-password'),
|
||||||
|
('smtp.provider', 'own'),
|
||||||
|
('smtp.host', 'localhost'),
|
||||||
|
('smtp.port', '587'),
|
||||||
|
('smtp.username', ''),
|
||||||
|
('smtp.password', ''),
|
||||||
|
('smtp.from_address', 'no-reply@example.com'),
|
||||||
|
('smtp.from_name', 'Habbo Hotel'),
|
||||||
|
('smtp.use_tls', '1'),
|
||||||
|
('smtp.use_ssl', '0'),
|
||||||
|
('new_user_credits', '0'),
|
||||||
|
('new_user_duckets', '0'),
|
||||||
|
('new_user_diamonds', '0')
|
||||||
|
ON DUPLICATE KEY UPDATE `value` = `value`;
|
||||||
|
|
||||||
|
INSERT INTO `wired_emulator_settings` (`key`, `value`, `comment`) VALUES
|
||||||
|
('wired.engine.enabled', '1', 'Compatibility flag. The runtime uses the new wired engine.'),
|
||||||
|
('wired.engine.exclusive', '1', 'Compatibility flag. The runtime uses exclusive wired engine execution.'),
|
||||||
|
('wired.engine.maxStepsPerStack', '100', 'Maximum internal processing steps allowed for a single wired stack execution.'),
|
||||||
|
('wired.engine.debug', '0', 'Enable verbose debug logging for the wired engine.'),
|
||||||
|
('wired.custom.enabled', '0', 'Enable custom legacy wired compatibility behavior.'),
|
||||||
|
('hotel.wired.furni.selection.count', '5', 'Maximum number of furni that a wired box can store or select.'),
|
||||||
|
('hotel.wired.max_delay', '20', 'Maximum delay value accepted by wired effects that support delayed execution.'),
|
||||||
|
('hotel.wired.message.max_length', '512', 'Maximum length of wired message text fields.'),
|
||||||
|
('wired.effect.teleport.delay', '500', 'Delay in milliseconds used by wired teleport movement.'),
|
||||||
|
('wired.place.under', '0', 'Allow placing wired furniture underneath other items when room rules permit it.'),
|
||||||
|
('wired.tick.interval.ms', '50', 'Global wired tick interval in milliseconds.'),
|
||||||
|
('wired.tick.resolution', '100', 'Legacy wired tick resolution value.'),
|
||||||
|
('wired.tick.debug', '0', 'Enable verbose logging for the wired tick service.'),
|
||||||
|
('wired.tick.thread.priority', '6', 'Java thread priority used by the wired tick service.'),
|
||||||
|
('wired.highscores.displaycount', '25', 'Maximum number of wired highscore entries shown to users.'),
|
||||||
|
('wired.abuse.max.recursion.depth', '10', 'Maximum recursive wired depth before execution is stopped.'),
|
||||||
|
('wired.abuse.max.events.per.window', '100', 'Maximum identical wired events allowed inside the abuse rate-limit window.'),
|
||||||
|
('wired.abuse.rate.limit.window.ms', '10000', 'Wired abuse rate-limit window in milliseconds.'),
|
||||||
|
('wired.abuse.ban.duration.ms', '600000', 'Temporary wired ban duration after abuse detection.'),
|
||||||
|
('wired.monitor.usage.window.ms', '1000', 'Rolling window size for wired usage monitoring.'),
|
||||||
|
('wired.monitor.usage.limit', '1000', 'Maximum wired usage budget in one monitor window.'),
|
||||||
|
('wired.monitor.delayed.events.limit', '100', 'Maximum delayed wired events queued in one room.'),
|
||||||
|
('wired.monitor.overload.average.ms', '50', 'Average execution time threshold for overload tracking.'),
|
||||||
|
('wired.monitor.overload.peak.ms', '150', 'Peak execution time threshold for overload tracking.'),
|
||||||
|
('wired.monitor.overload.consecutive.windows', '2', 'Consecutive overloaded windows required before logging overload.'),
|
||||||
|
('wired.monitor.heavy.usage.percent', '70', 'Usage percentage threshold for heavy-room tracking.'),
|
||||||
|
('wired.monitor.heavy.consecutive.windows', '5', 'Consecutive windows above heavy usage threshold.'),
|
||||||
|
('wired.monitor.heavy.delayed.percent', '60', 'Delayed queue percentage threshold for heavy-room tracking.')
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
`value` = VALUES(`value`),
|
||||||
|
`comment` = VALUES(`comment`);
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- Login API, room templates, remember-me, and news
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `password_resets` (
|
||||||
|
`user_id` INT(11) NOT NULL,
|
||||||
|
`token` VARCHAR(128) NOT NULL,
|
||||||
|
`expires_at` TIMESTAMP NOT NULL,
|
||||||
|
`created_ip` VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
PRIMARY KEY (`user_id`),
|
||||||
|
UNIQUE KEY `idx_password_resets_token` (`token`),
|
||||||
|
CONSTRAINT `fk_password_resets_user`
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `users_remember_families` (
|
||||||
|
`family_id` CHAR(36) NOT NULL,
|
||||||
|
`user_id` INT(11) NOT NULL,
|
||||||
|
`current_version` INT(11) NOT NULL DEFAULT 1,
|
||||||
|
`created_at` INT(11) NOT NULL,
|
||||||
|
`expires_at` INT(11) NOT NULL,
|
||||||
|
`revoked` TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
`last_ip` VARCHAR(45) NOT NULL DEFAULT '',
|
||||||
|
PRIMARY KEY (`family_id`),
|
||||||
|
KEY `idx_users_remember_families_user_id` (`user_id`),
|
||||||
|
KEY `idx_users_remember_families_expires_at` (`expires_at`),
|
||||||
|
CONSTRAINT `fk_users_remember_families_user`
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `room_templates` (
|
||||||
|
`template_id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`title` VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
`description` VARCHAR(256) NOT NULL DEFAULT '',
|
||||||
|
`thumbnail` VARCHAR(512) NOT NULL DEFAULT '',
|
||||||
|
`sort_order` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`enabled` ENUM('0','1') NOT NULL DEFAULT '1',
|
||||||
|
`name` VARCHAR(50) NOT NULL DEFAULT '',
|
||||||
|
`room_description` VARCHAR(250) NOT NULL DEFAULT '',
|
||||||
|
`model` VARCHAR(100) NOT NULL,
|
||||||
|
`password` VARCHAR(50) NOT NULL DEFAULT '',
|
||||||
|
`state` ENUM('open','locked','password','invisible') NOT NULL DEFAULT 'open',
|
||||||
|
`users_max` INT(11) NOT NULL DEFAULT 25,
|
||||||
|
`category` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`paper_floor` VARCHAR(50) NOT NULL DEFAULT '0.0',
|
||||||
|
`paper_wall` VARCHAR(50) NOT NULL DEFAULT '0.0',
|
||||||
|
`paper_landscape` VARCHAR(50) NOT NULL DEFAULT '0.0',
|
||||||
|
`thickness_wall` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`thickness_floor` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`moodlight_data` VARCHAR(2048) NOT NULL DEFAULT '',
|
||||||
|
`override_model` ENUM('0','1') NOT NULL DEFAULT '0',
|
||||||
|
`trade_mode` INT(2) NOT NULL DEFAULT 2,
|
||||||
|
`heightmap` MEDIUMTEXT NOT NULL,
|
||||||
|
`door_x` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`door_y` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`door_dir` INT(4) NOT NULL DEFAULT 2,
|
||||||
|
PRIMARY KEY (`template_id`),
|
||||||
|
KEY `idx_room_templates_enabled_sort` (`enabled`, `sort_order`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `room_templates_items` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`template_id` INT(11) NOT NULL,
|
||||||
|
`item_id` INT(11) UNSIGNED NOT NULL,
|
||||||
|
`wall_pos` VARCHAR(20) NOT NULL DEFAULT '',
|
||||||
|
`x` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`y` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`z` DOUBLE(10,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`rot` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`extra_data` VARCHAR(2096) NOT NULL DEFAULT '',
|
||||||
|
`wired_data` VARCHAR(4096) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_room_templates_items_template_id` (`template_id`),
|
||||||
|
KEY `idx_room_templates_items_item_id` (`item_id`),
|
||||||
|
CONSTRAINT `fk_room_templates_items_template`
|
||||||
|
FOREIGN KEY (`template_id`) REFERENCES `room_templates` (`template_id`) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT `fk_room_templates_items_item_base`
|
||||||
|
FOREIGN KEY (`item_id`) REFERENCES `items_base` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `ui_news` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`title` VARCHAR(150) NOT NULL,
|
||||||
|
`body` TEXT NOT NULL,
|
||||||
|
`image` MEDIUMTEXT DEFAULT NULL,
|
||||||
|
`link_text` VARCHAR(80) NOT NULL DEFAULT '',
|
||||||
|
`link_url` VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
`sort_order` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_ui_news_enabled_sort` (`enabled`, `sort_order`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
INSERT INTO `ui_news` (`title`, `body`, `image`, `link_text`, `link_url`, `enabled`, `sort_order`)
|
||||||
|
SELECT 'Welcome to the Hotel!', 'Catch up on the latest events, updates and competitions happening right now in the hotel.', '', '', '', 1, 0
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM `ui_news`);
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- Wired runtime data
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `room_wired_settings` (
|
||||||
|
`room_id` INT(11) NOT NULL,
|
||||||
|
`inspect_mask` INT(11) NOT NULL DEFAULT 0 COMMENT 'Bitmask for who can open and inspect Wired in the room. 1=everyone, 2=users with rights, 4=group members, 8=group admins.',
|
||||||
|
`modify_mask` INT(11) NOT NULL DEFAULT 0 COMMENT 'Bitmask for who can modify Wired in the room. 2=users with rights, 4=group members, 8=group admins.',
|
||||||
|
PRIMARY KEY (`room_id`),
|
||||||
|
CONSTRAINT `fk_room_wired_settings_room`
|
||||||
|
FOREIGN KEY (`room_id`) REFERENCES `rooms` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `room_wired_variables` (
|
||||||
|
`room_id` INT(11) NOT NULL,
|
||||||
|
`variable_item_id` INT(11) NOT NULL,
|
||||||
|
`value` INT(11) DEFAULT NULL,
|
||||||
|
`created_at` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`updated_at` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`room_id`, `variable_item_id`),
|
||||||
|
KEY `idx_room_wired_variables_room_item` (`room_id`, `variable_item_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `room_user_wired_variables` (
|
||||||
|
`room_id` INT(11) NOT NULL,
|
||||||
|
`user_id` INT(11) NOT NULL,
|
||||||
|
`variable_item_id` INT(11) NOT NULL,
|
||||||
|
`value` INT(11) DEFAULT NULL,
|
||||||
|
`created_at` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`updated_at` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`room_id`, `user_id`, `variable_item_id`),
|
||||||
|
KEY `idx_room_user_wired_variables_room_item` (`room_id`, `variable_item_id`),
|
||||||
|
KEY `idx_room_user_wired_variables_user` (`user_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `room_furni_wired_variables` (
|
||||||
|
`room_id` INT(11) NOT NULL,
|
||||||
|
`furni_id` INT(11) NOT NULL,
|
||||||
|
`variable_item_id` INT(11) NOT NULL,
|
||||||
|
`value` INT(11) DEFAULT NULL,
|
||||||
|
`created_at` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`updated_at` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`room_id`, `furni_id`, `variable_item_id`),
|
||||||
|
KEY `idx_room_furni_wired_variables_room_item` (`room_id`, `variable_item_id`),
|
||||||
|
KEY `idx_room_furni_wired_variables_furni` (`furni_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- User customization: prefixes, nick icons, profile backgrounds
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
|
||||||
|
ALTER TABLE `users`
|
||||||
|
ADD COLUMN IF NOT EXISTS `background_id` INT(11) NOT NULL DEFAULT 0 AFTER `machine_id`,
|
||||||
|
ADD COLUMN IF NOT EXISTS `background_stand_id` INT(11) NOT NULL DEFAULT 0 AFTER `background_id`,
|
||||||
|
ADD COLUMN IF NOT EXISTS `background_overlay_id` INT(11) NOT NULL DEFAULT 0 AFTER `background_stand_id`,
|
||||||
|
ADD COLUMN IF NOT EXISTS `background_card_id` INT(11) NOT NULL DEFAULT 0 AFTER `background_overlay_id`;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `infostand_backgrounds` (
|
||||||
|
`id` INT(11) NOT NULL,
|
||||||
|
`category` ENUM('background','stand','overlay','card') NOT NULL,
|
||||||
|
`min_rank` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`is_hc_only` TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
`is_ambassador_only` TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`, `category`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `infostand_backgrounds` (`id`, `category`, `min_rank`, `is_hc_only`, `is_ambassador_only`) VALUES
|
||||||
|
(0, 'background', 0, 0, 0),
|
||||||
|
(0, 'stand', 0, 0, 0),
|
||||||
|
(0, 'overlay', 0, 0, 0),
|
||||||
|
(0, 'card', 0, 0, 0);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `user_prefixes` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` INT(11) NOT NULL,
|
||||||
|
`text` VARCHAR(50) NOT NULL,
|
||||||
|
`color` VARCHAR(255) NOT NULL DEFAULT '#FFFFFF',
|
||||||
|
`icon` VARCHAR(50) NOT NULL DEFAULT '',
|
||||||
|
`effect` VARCHAR(50) NOT NULL DEFAULT '',
|
||||||
|
`font` VARCHAR(50) NOT NULL DEFAULT '',
|
||||||
|
`catalog_prefix_id` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`display_name` VARCHAR(100) NOT NULL DEFAULT '',
|
||||||
|
`points` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`points_type` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`is_custom` TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
`active` TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_user_prefixes_user_id` (`user_id`),
|
||||||
|
KEY `idx_user_prefixes_user_active` (`user_id`, `active`),
|
||||||
|
CONSTRAINT `fk_user_prefixes_user`
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `custom_prefixes_catalog` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`display_name` VARCHAR(100) NOT NULL DEFAULT '',
|
||||||
|
`text` VARCHAR(50) NOT NULL,
|
||||||
|
`color` VARCHAR(255) NOT NULL DEFAULT '#FFFFFF',
|
||||||
|
`icon` VARCHAR(50) NOT NULL DEFAULT '',
|
||||||
|
`effect` VARCHAR(50) NOT NULL DEFAULT '',
|
||||||
|
`font` VARCHAR(50) NOT NULL DEFAULT '',
|
||||||
|
`points` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`points_type` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
`sort_order` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `user_visual_settings` (
|
||||||
|
`user_id` INT(11) NOT NULL,
|
||||||
|
`display_order` VARCHAR(50) NOT NULL DEFAULT 'icon-prefix-name',
|
||||||
|
PRIMARY KEY (`user_id`),
|
||||||
|
CONSTRAINT `fk_user_visual_settings_user`
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `custom_prefix_settings` (
|
||||||
|
`key_name` VARCHAR(100) NOT NULL,
|
||||||
|
`value` VARCHAR(255) NOT NULL,
|
||||||
|
PRIMARY KEY (`key_name`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `custom_prefix_settings` (`key_name`, `value`) VALUES
|
||||||
|
('max_length', '15'),
|
||||||
|
('min_rank_to_buy', '1'),
|
||||||
|
('price_credits', '5'),
|
||||||
|
('price_points', '0'),
|
||||||
|
('points_type', '0'),
|
||||||
|
('font_price_credits', '10'),
|
||||||
|
('font_price_points', '0'),
|
||||||
|
('font_points_type', '0');
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `custom_prefixes_catalog`
|
||||||
|
(`id`, `display_name`, `text`, `color`, `icon`, `effect`, `font`, `points`, `points_type`, `enabled`, `sort_order`)
|
||||||
|
VALUES
|
||||||
|
(1, 'VIP', 'VIP', '#FFD700', '', 'glow', '', 10, 0, 1, 1),
|
||||||
|
(2, 'Legend', 'Legend', '#8B5CF6', '', 'discord-neon', '', 15, 0, 1, 2),
|
||||||
|
(3, 'Staff Pick', 'Staff', '#3B82F6', '*', 'cartoon', '', 20, 0, 1, 3);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `custom_nick_icons_catalog` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`icon_key` VARCHAR(50) NOT NULL,
|
||||||
|
`display_name` VARCHAR(100) NOT NULL DEFAULT '',
|
||||||
|
`points` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`points_type` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
`sort_order` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_custom_nick_icons_catalog_icon_key` (`icon_key`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `user_nick_icons` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` INT(11) NOT NULL,
|
||||||
|
`icon_key` VARCHAR(50) NOT NULL,
|
||||||
|
`active` TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_user_nick_icons_user_icon` (`user_id`, `icon_key`),
|
||||||
|
KEY `idx_user_nick_icons_user_id` (`user_id`),
|
||||||
|
KEY `idx_user_nick_icons_user_active` (`user_id`, `active`),
|
||||||
|
CONSTRAINT `fk_user_nick_icons_user`
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `custom_nick_icons_catalog` (`icon_key`, `display_name`, `points`, `points_type`, `enabled`, `sort_order`) VALUES
|
||||||
|
('1', 'Icon 1', 10, 0, 1, 1),
|
||||||
|
('2', 'Icon 2', 10, 0, 1, 2),
|
||||||
|
('3', 'Icon 3', 10, 0, 1, 3),
|
||||||
|
('4', 'Icon 4', 10, 0, 1, 4),
|
||||||
|
('5', 'Icon 5', 10, 0, 1, 5),
|
||||||
|
('6', 'Icon 6', 10, 0, 1, 6);
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- Custom badge maker
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `users_custom_badge_settings` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`badge_path` VARCHAR(255) NOT NULL DEFAULT '/var/www/gamedata/c_images/album1584',
|
||||||
|
`badge_url` VARCHAR(255) NOT NULL DEFAULT '/gamedata/c_images/album1584',
|
||||||
|
`price_badge` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`currency_type` INT(11) NOT NULL DEFAULT -1,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
INSERT INTO `users_custom_badge_settings` (`id`, `badge_path`, `badge_url`, `price_badge`, `currency_type`)
|
||||||
|
SELECT 1, '/var/www/gamedata/c_images/album1584', '/gamedata/c_images/album1584', 50, 5
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM `users_custom_badge_settings` WHERE `id` = 1);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `user_custom_badge` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` INT(11) NOT NULL,
|
||||||
|
`badge_id` VARCHAR(64) NOT NULL,
|
||||||
|
`badge_name` VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
`badge_description` VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
`date_created` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`date_edit` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_user_custom_badge_badge_id` (`badge_id`),
|
||||||
|
KEY `idx_user_custom_badge_user_id` (`user_id`),
|
||||||
|
CONSTRAINT `fk_user_custom_badge_user`
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- UI/catalog compatibility values used by the current client
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
|
||||||
|
INSERT INTO `chat_bubbles` (`type`, `name`, `permission`, `overridable`, `triggers_talking_furniture`) VALUES
|
||||||
|
(200, 'SHOW_MESSAGE_RED', '', 1, 0),
|
||||||
|
(201, 'SHOW_MESSAGE_GREEN', '', 1, 0),
|
||||||
|
(202, 'SHOW_MESSAGE_BLUE', '', 1, 0),
|
||||||
|
(210, 'SHOW_MESSAGE_ALERT', '', 1, 0),
|
||||||
|
(211, 'SHOW_MESSAGE_INFO', '', 1, 0),
|
||||||
|
(212, 'SHOW_MESSAGE_WARNING', '', 1, 0),
|
||||||
|
(220, 'SHOW_MESSAGE_WRONG', '', 1, 0),
|
||||||
|
(221, 'SHOW_MESSAGE_WRONG_CIRCLED', '', 1, 0),
|
||||||
|
(222, 'SHOW_MESSAGE_CORRECT', '', 1, 0),
|
||||||
|
(223, 'SHOW_MESSAGE_CORRECT_CIRCLED', '', 1, 0),
|
||||||
|
(224, 'SHOW_MESSAGE_QUESTION', '', 1, 0),
|
||||||
|
(225, 'SHOW_MESSAGE_QUESTION_CIRCLED', '', 1, 0),
|
||||||
|
(226, 'SHOW_MESSAGE_ARROW_UP', '', 1, 0),
|
||||||
|
(227, 'SHOW_MESSAGE_ARROW_UP_CIRCLED', '', 1, 0),
|
||||||
|
(228, 'SHOW_MESSAGE_ARROW_DOWN', '', 1, 0),
|
||||||
|
(229, 'SHOW_MESSAGE_ARROW_DOWN_CIRCLED', '', 1, 0),
|
||||||
|
(250, 'SHOW_MESSAGE_SKULL', '', 1, 0),
|
||||||
|
(251, 'SHOW_MESSAGE_SKULL_ALT', '', 1, 0),
|
||||||
|
(252, 'SHOW_MESSAGE_MAGNIFIER', '', 1, 0)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
`name` = VALUES(`name`),
|
||||||
|
`permission` = VALUES(`permission`),
|
||||||
|
`overridable` = VALUES(`overridable`),
|
||||||
|
`triggers_talking_furniture` = VALUES(`triggers_talking_furniture`);
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
|
||||||
|
('commands.keys.cmd_setroom_template', 'setroom_template;set_room_template'),
|
||||||
|
('commands.succes.cmd_setroom_template.verify', 'Copy the current room "%roomname%" to room_templates? Type :setroom_template %generic.yes% to confirm.'),
|
||||||
|
('commands.succes.cmd_setroom_template', 'Room saved as template id %id% with %items% items (%skipped% skipped - item_id not in items_base).'),
|
||||||
|
('commands.error.cmd_setroom_template', 'Could not save room as template. Check the server log for details.'),
|
||||||
|
('commands.error.cmd_setroom_template.no_room', 'You must be inside a room to use this command.'),
|
||||||
|
('commands.keys.cmd_give_prefix', 'giveprefix'),
|
||||||
|
('commands.keys.cmd_list_prefixes', 'listprefixes'),
|
||||||
|
('commands.keys.cmd_remove_prefix', 'removeprefix'),
|
||||||
|
('commands.keys.cmd_prefix_blacklist', 'prefixblacklist'),
|
||||||
|
('wiredfurni.badgereceived.body', 'You have just received a new Badge! Check your Inventory!'),
|
||||||
|
('wiredfurni.badgereceived.title', 'Badge received!');
|
||||||
|
|
||||||
|
-- Optional permission metadata for normalized permission schemas.
|
||||||
|
-- Actual rank values still belong in the permissions/permission_ranks setup.
|
||||||
|
CREATE TABLE IF NOT EXISTS `permission_definitions` (
|
||||||
|
`permission_key` VARCHAR(64) NOT NULL,
|
||||||
|
`max_value` TINYINT(3) UNSIGNED NOT NULL DEFAULT 1,
|
||||||
|
`comment` TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (`permission_key`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
INSERT INTO `permission_definitions` (`permission_key`, `max_value`, `comment`) VALUES
|
||||||
|
('cmd_setroom_template', 1, 'Allows using :setroom_template to copy a room into the login room-template table.'),
|
||||||
|
('cmd_give_prefix', 1, 'Allows granting custom prefixes to users.'),
|
||||||
|
('cmd_list_prefixes', 1, 'Allows listing custom prefixes assigned to users.'),
|
||||||
|
('cmd_remove_prefix', 1, 'Allows removing custom prefixes from users.'),
|
||||||
|
('cmd_prefix_blacklist', 1, 'Allows managing the custom prefix blacklist.')
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
`max_value` = VALUES(`max_value`),
|
||||||
|
`comment` = VALUES(`comment`);
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- Explicitly obsolete table from older remember-me attempts.
|
||||||
|
-- The current Java uses users_remember_families only.
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `users_remember_tokens`;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- Fix: acc_supporttool wrongly granted to VIP / wrongly denied to Super Mod
|
||||||
|
-- ============================================================
|
||||||
|
-- The default permission_definitions seed shipped acc_supporttool
|
||||||
|
-- with rank pattern (0, 1, 1, 1, 1, 0, 1) — i.e. rank_2 (VIP) and
|
||||||
|
-- rank_3 (X, junior helper) had ALLOWED, while rank_6 (Super Mod)
|
||||||
|
-- did NOT. That's two bugs:
|
||||||
|
--
|
||||||
|
-- * VIP users see the ModTools button on the toolbar and can
|
||||||
|
-- open Room/User info windows. The actual sanction endpoints
|
||||||
|
-- still gate on ACC_SUPPORTTOOL server-side so they can't
|
||||||
|
-- actually moderate, but the UI exposure is wrong and lets a
|
||||||
|
-- VIP request user info / room info / chatlogs they have no
|
||||||
|
-- business reading.
|
||||||
|
-- * Super Mod is denied the tool entirely, which is obviously
|
||||||
|
-- unintended given the rank name.
|
||||||
|
--
|
||||||
|
-- Intended pattern: only Support (4) and up — (0, 0, 0, 1, 1, 1, 1).
|
||||||
|
--
|
||||||
|
-- Run on existing deployments to align with the corrected default
|
||||||
|
-- seed in `Default Database/FullDatabase.sql`. Idempotent.
|
||||||
|
|
||||||
|
UPDATE `permission_definitions`
|
||||||
|
SET `rank_1` = 0,
|
||||||
|
`rank_2` = 0,
|
||||||
|
`rank_3` = 0,
|
||||||
|
`rank_4` = 1,
|
||||||
|
`rank_5` = 1,
|
||||||
|
`rank_6` = 1,
|
||||||
|
`rank_7` = 1
|
||||||
|
WHERE `permission_key` = 'acc_supporttool';
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
ALTER TABLE catalog_club_offers
|
||||||
|
ADD COLUMN IF NOT EXISTS giftable ENUM('0','1') NOT NULL DEFAULT '0';
|
||||||
|
|
||||||
|
INSERT INTO emulator_texts (`key`, `value`)
|
||||||
|
VALUES ('prereg.reward.you.received', 'You have recived:'),
|
||||||
|
('generic.days', 'days');
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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_housekeeping', '1', 'Allow housekeeping in the client', '0', '0', '0', '0', '0', '0', '1');
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `housekeeping_log` (
|
||||||
|
`id` INT NOT NULL AUTO_INCREMENT,
|
||||||
|
`timestamp` INT NOT NULL,
|
||||||
|
`actor_id` INT NOT NULL,
|
||||||
|
`actor_name` VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
`target_type` VARCHAR(16) NOT NULL DEFAULT 'user',
|
||||||
|
`target_id` INT NOT NULL DEFAULT 0,
|
||||||
|
`target_label` VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
`action` VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
`detail` VARCHAR(500) NOT NULL DEFAULT '',
|
||||||
|
`success` TINYINT NOT NULL DEFAULT 1,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `timestamp` (`timestamp`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
ALTER TABLE `bots`
|
||||||
|
MODIFY COLUMN `type` ENUM('generic','visitor_log','bartender','weapons_dealer','frank')
|
||||||
|
NOT NULL DEFAULT 'generic';
|
||||||
|
|
||||||
|
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_bot_frank', 1, 'Required to purchase the Frank mascot bot from the catalog.',
|
||||||
|
0, 0, 0, 0, 0, 0, 1)
|
||||||
|
ON DUPLICATE KEY UPDATE `comment` = VALUES(`comment`);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `bot_chat_responses` (
|
||||||
|
`id` INT NOT NULL AUTO_INCREMENT,
|
||||||
|
`bot_type` VARCHAR(32) NOT NULL,
|
||||||
|
`keys` VARCHAR(255) NOT NULL COMMENT 'semicolon-separated trigger words',
|
||||||
|
`responses` TEXT NOT NULL COMMENT 'newline-separated replies; bot picks one at random',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `bot_type` (`bot_type`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
INSERT INTO `bot_chat_responses` (`bot_type`, `keys`, `responses`) VALUES
|
||||||
|
('frank', '__door_triggers', 'show me the door\nkick me\ni want to leave\nlet me out'),
|
||||||
|
('frank', '__door_lines', 'Right this way - mind the step!\nAnd out you go. Come back soon!\nAllow me to escort you to the exit.\nThere''s the door. Farewell, true believer!'),
|
||||||
|
('frank', '__busy_whisper', 'Sorry, I am currently busy. Please wait until I am available.'),
|
||||||
|
('frank', 'frank', 'Hello, I''m Frank! Welcome to Habbo.'),
|
||||||
|
('frank', 'help', 'What do you need help with?'),
|
||||||
|
('frank', 'thanks;thank you', 'Just doing my job, true believer!'),
|
||||||
|
('frank', 'new', 'Welcome to Habbo! I hope you have a great time here.'),
|
||||||
|
('frank', 'rooms', 'Looking for somewhere fun? Try the Navigator - thousands of rooms to explore!'),
|
||||||
|
('frank', 'sulake', 'Sulake is the company behind Habbo. Take a look: https://www.sulake.com'),
|
||||||
|
('frank', 'vip;hc', 'VIP gets you more outfits, more furni, more everything. Worth it!'),
|
||||||
|
('frank', 'music', 'Snoop Dogg, Frank Sinatra and a little Beethoven on Sundays.'),
|
||||||
|
('frank', 'movie', 'I''m a Casablanca man. Black and white films are an underrated art.'),
|
||||||
|
('frank', 'game', 'Battleship. Always Battleship.'),
|
||||||
|
('frank', 'snowstorm', 'Honestly? I''m terrible at Snowstorm. Don''t tell anyone.'),
|
||||||
|
('frank', 'furni', 'Best furniture maker in town - hands down, the folks at Sulake.'),
|
||||||
|
('frank', 'animal;cat;pet','I have a cat called Mr. Whiskers. He runs the place, really.'),
|
||||||
|
('frank', 'miranda', 'Miranda. The love of my life. Don''t get me started.'),
|
||||||
|
('frank', 'frank black', 'Named after the man himself. Frank Black is a hero of mine.'),
|
||||||
|
('frank', 'life', 'Life is like a bowl of popcorn - warm, salty and buttery.'),
|
||||||
|
('frank', 'job;work', 'I''m sure you can find work in one of the guest rooms!'),
|
||||||
|
('frank', 'snouthill', 'Snouthill... so many memories.'),
|
||||||
|
('frank', 'wife', 'I had a wife once. She broke my stereo.'),
|
||||||
|
('frank', 'baseball', 'Oh, I used to love to go down to the old ball park and watch Christy Mathewson and Honus Wagner at bat.'),
|
||||||
|
('frank', 'mark', 'I don''t trust Mark.'),
|
||||||
|
('frank', 'vietnam', 'Vietnam? Don''t ask. Worst trip of my life.'),
|
||||||
|
('frank', 'pills;drugs', 'Drugs are bad, mmkay?');
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `bot_serves` (`keys`, `item`) VALUES
|
||||||
|
('sunflower', 1002),
|
||||||
|
('cola;habbo cola', 19),
|
||||||
|
('rose', 1000),
|
||||||
|
('book', 1003),
|
||||||
|
('tea', 27),
|
||||||
|
('coffee', 8),
|
||||||
|
('migraine;headache;pills', 1015),
|
||||||
|
('radioactive liquid;radioactive', 30),
|
||||||
|
('turkey;can of turkey', 70);
|
||||||
|
|
||||||
|
-- VERY IMPORTANT !!!!
|
||||||
|
-- First check if the items_base ID and catalog_items ID is not in use !
|
||||||
|
-- After the SQL please go to the catalog_items table and change the page_id to where your BOTS are located
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `items_base` (`id`, `sprite_id`, `item_name`, `public_name`, `width`, `length`, `stack_height`, `allow_stack`, `allow_sit`, `allow_lay`, `allow_walk`, `allow_gift`, `allow_trade`, `allow_recycle`, `allow_marketplace_sell`, `allow_inventory_stack`, `type`, `interaction_type`, `interaction_modes_count`, `vending_ids`, `multiheight`, `customparams`)
|
||||||
|
VALUES (19001, 0, 'bot_frank', 'Frank', 1, 1, 0.00, '0', '0', '0', '1', '0', '0', '0', '0', '0', 'r', 'default', 1, '0', '0', '0');
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `catalog_items` (`item_ids`, `page_id`, `offer_id`, `catalog_name`, `cost_credits`, `cost_points`, `points_type`, `amount`, `extradata`)
|
||||||
|
VALUES ('19001', 8, 19001, 'Frank', 0, 0, 0, 1, 'name:Frank;motto:Welcome to Habbo!;figure:hr-3499-33.sh-290-90.ch-3971-72-73.lg-270-73.hd-205-1-1.fa-1206-67.ha-3409-73-72;gender:m');
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
ALTER TABLE `rooms`
|
||||||
|
ADD COLUMN IF NOT EXISTS `soundboard_enabled` TINYINT(1) NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `soundboard_sounds` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`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)
|
||||||
|
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
`sort_order` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
-- Fortune Wheel — tables
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
CREATE TABLE IF NOT EXISTS `wheel_prizes` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`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
|
||||||
|
`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)
|
||||||
|
`weight` INT(11) NOT NULL DEFAULT 1, -- relative probability
|
||||||
|
`label` VARCHAR(64) NOT NULL DEFAULT '', -- slice label override (optional)
|
||||||
|
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
`sort_order` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `wheel_user_state` (
|
||||||
|
`user_id` INT(11) NOT NULL,
|
||||||
|
`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
|
||||||
|
`last_reset` INT(11) NOT NULL DEFAULT 0, -- day index of last daily reset (unix / 86400)
|
||||||
|
PRIMARY KEY (`user_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `wheel_recent_wins` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` INT(11) NOT NULL,
|
||||||
|
`username` VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
`look` VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
`prize_label` VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
`won_at` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
INSERT INTO `emulator_settings` (`key`, `value`, `comment`) VALUES
|
||||||
|
('wheel.free_spins_per_day', '1', 'Fortune wheel: free spins granted each day.'),
|
||||||
|
('wheel.spin_cost', '50', 'Fortune wheel: cost of one extra spin.'),
|
||||||
|
('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`)
|
||||||
|
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;
|
||||||
@@ -63,15 +63,6 @@ CREATE TABLE IF NOT EXISTS `custom_prefix_settings` (
|
|||||||
PRIMARY KEY (`key_name`)
|
PRIMARY KEY (`key_name`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
-- ------------------------------------------------------------
|
|
||||||
-- 5. Blacklist table
|
|
||||||
-- ------------------------------------------------------------
|
|
||||||
CREATE TABLE IF NOT EXISTS `custom_prefix_blacklist` (
|
|
||||||
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
|
||||||
`word` VARCHAR(100) NOT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `uk_word` (`word`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
||||||
|
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- Schema upgrades for existing installations
|
-- Schema upgrades for existing installations
|
||||||
@@ -296,14 +287,6 @@ INSERT IGNORE INTO `custom_prefixes_catalog`
|
|||||||
(2, 'Legend', 'Legend', '#8B5CF6', '', 'discord-neon', '', 15, 0, 1, 2),
|
(2, 'Legend', 'Legend', '#8B5CF6', '', 'discord-neon', '', 15, 0, 1, 2),
|
||||||
(3, 'Staff Pick', 'Staff', '#3B82F6', '*', 'cartoon', '', 20, 0, 1, 3);
|
(3, 'Staff Pick', 'Staff', '#3B82F6', '*', 'cartoon', '', 20, 0, 1, 3);
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Example blacklist entries
|
|
||||||
-- ============================================================
|
|
||||||
INSERT IGNORE INTO `custom_prefix_blacklist` (`word`) VALUES
|
|
||||||
('admin'),
|
|
||||||
('staff'),
|
|
||||||
('mod'),
|
|
||||||
('owner');
|
|
||||||
|
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- Notes
|
-- Notes
|
||||||
|
|||||||
@@ -28598,7 +28598,7 @@ INSERT INTO `permission_definitions` (`permission_key`, `max_value`, `comment`,
|
|||||||
('acc_staff_chat', 1, 'Grants access to the in-game Staff Chat group buddy: receives broadcasts from other staff and can broadcast to anyone holding this permission.', 0, 0, 0, 0, 0, 0, 1),
|
('acc_staff_chat', 1, 'Grants access to the in-game Staff Chat group buddy: receives broadcasts from other staff and can broadcast to anyone holding this permission.', 0, 0, 0, 0, 0, 0, 1),
|
||||||
('acc_staff_pick', 1, 'Allows using staff item pick-up actions that bypass normal room ownership restrictions.', 0, 0, 0, 0, 0, 0, 1),
|
('acc_staff_pick', 1, 'Allows using staff item pick-up actions that bypass normal room ownership restrictions.', 0, 0, 0, 0, 0, 0, 1),
|
||||||
('acc_superwired', 1, 'Allows saving advanced wired data without the normal wordfilter and reward payload restrictions applied to regular users.', 0, 0, 0, 0, 0, 0, 1),
|
('acc_superwired', 1, 'Allows saving advanced wired data without the normal wordfilter and reward payload restrictions applied to regular users.', 0, 0, 0, 0, 0, 0, 1),
|
||||||
('acc_supporttool', 1, 'Allows opening and using the support/moderation tool interface.', 0, 1, 1, 1, 1, 0, 1),
|
('acc_supporttool', 1, 'Allows opening and using the support/moderation tool interface.', 0, 0, 0, 1, 1, 1, 1),
|
||||||
('acc_trade_anywhere', 1, 'Allows starting trades outside the normal trade-enabled areas.', 0, 0, 0, 0, 0, 0, 1),
|
('acc_trade_anywhere', 1, 'Allows starting trades outside the normal trade-enabled areas.', 0, 0, 0, 0, 0, 0, 1),
|
||||||
('acc_unignorable', 1, 'Prevents the account from being ignored by other users through the ignore system.', 0, 0, 0, 0, 0, 0, 0),
|
('acc_unignorable', 1, 'Prevents the account from being ignored by other users through the ignore system.', 0, 0, 0, 0, 0, 0, 0),
|
||||||
('acc_unkickable', 1, 'Prevents the user from being kicked by normal moderation or room commands.', 0, 0, 0, 0, 0, 0, 1),
|
('acc_unkickable', 1, 'Prevents the user from being kicked by normal moderation or room commands.', 0, 0, 0, 0, 0, 0, 1),
|
||||||
|
|||||||
+2
-1
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>com.eu.habbo</groupId>
|
<groupId>com.eu.habbo</groupId>
|
||||||
<artifactId>Habbo</artifactId>
|
<artifactId>Habbo</artifactId>
|
||||||
<version>4.2.9</version>
|
<version>4.2.30</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
<archive>
|
<archive>
|
||||||
<manifest>
|
<manifest>
|
||||||
<mainClass>com.eu.habbo.Emulator</mainClass>
|
<mainClass>com.eu.habbo.Emulator</mainClass>
|
||||||
|
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||||
</manifest>
|
</manifest>
|
||||||
</archive>
|
</archive>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import ch.qos.logback.core.ConsoleAppender;
|
|||||||
import com.eu.habbo.core.*;
|
import com.eu.habbo.core.*;
|
||||||
import com.eu.habbo.core.consolecommands.ConsoleCommand;
|
import com.eu.habbo.core.consolecommands.ConsoleCommand;
|
||||||
import com.eu.habbo.database.Database;
|
import com.eu.habbo.database.Database;
|
||||||
|
import com.eu.habbo.gui.EmulatorDashboard;
|
||||||
import com.eu.habbo.habbohotel.GameEnvironment;
|
import com.eu.habbo.habbohotel.GameEnvironment;
|
||||||
import com.eu.habbo.habbohotel.gameclients.SessionResumeManager;
|
import com.eu.habbo.habbohotel.gameclients.SessionResumeManager;
|
||||||
import com.eu.habbo.networking.gameserver.GameServer;
|
import com.eu.habbo.networking.gameserver.GameServer;
|
||||||
@@ -38,12 +39,23 @@ public final class Emulator {
|
|||||||
private static final String OS_NAME = (System.getProperty("os.name") != null ? System.getProperty("os.name") : "Unknown");
|
private static final String OS_NAME = (System.getProperty("os.name") != null ? System.getProperty("os.name") : "Unknown");
|
||||||
private static final String CLASS_PATH = (System.getProperty("java.class.path") != null ? System.getProperty("java.class.path") : "Unknown");
|
private static final String CLASS_PATH = (System.getProperty("java.class.path") != null ? System.getProperty("java.class.path") : "Unknown");
|
||||||
|
|
||||||
|
// Fallback version, only used when running outside a packaged jar (e.g. from
|
||||||
|
// the IDE). In production the version comes from the jar manifest below.
|
||||||
public final static int MAJOR = 4;
|
public final static int MAJOR = 4;
|
||||||
public final static int MINOR = 1;
|
public final static int MINOR = 1;
|
||||||
public final static int BUILD = 0;
|
public final static int BUILD = 0;
|
||||||
public final static String PREVIEW = "";
|
public final static String PREVIEW = "";
|
||||||
|
|
||||||
public static final String version = "Arcturus Morningstar" + " " + MAJOR + "." + MINOR + "." + BUILD + " " + PREVIEW;
|
// Tied to the Maven project version: read from the jar manifest
|
||||||
|
// (Implementation-Version = ${project.version}, see pom assembly plugin).
|
||||||
|
private static String resolveVersionNumber() {
|
||||||
|
String implementation = Emulator.class.getPackage().getImplementationVersion();
|
||||||
|
if (implementation != null && !implementation.isEmpty()) return implementation;
|
||||||
|
String fallback = MAJOR + "." + MINOR + "." + BUILD;
|
||||||
|
return PREVIEW.isEmpty() ? fallback : fallback + " " + PREVIEW;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String version = "Arcturus Morningstar Extended " + resolveVersionNumber();
|
||||||
private static final String logo =
|
private static final String logo =
|
||||||
"\n" +
|
"\n" +
|
||||||
"███╗ ███╗ ██████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗ ███████╗████████╗ █████╗ ██████╗ \n" +
|
"███╗ ███╗ ██████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗ ███████╗████████╗ █████╗ ██████╗ \n" +
|
||||||
@@ -186,6 +198,10 @@ public final class Emulator {
|
|||||||
Emulator.isReady = true;
|
Emulator.isReady = true;
|
||||||
Emulator.timeStarted = getIntUnixTimestamp();
|
Emulator.timeStarted = getIntUnixTimestamp();
|
||||||
|
|
||||||
|
if (Emulator.getConfig().getBoolean("gui.enabled", true)) {
|
||||||
|
EmulatorDashboard.launch();
|
||||||
|
}
|
||||||
|
|
||||||
if (Emulator.getConfig().getInt("runtime.threads") < (Runtime.getRuntime().availableProcessors() * 2)) {
|
if (Emulator.getConfig().getInt("runtime.threads") < (Runtime.getRuntime().availableProcessors() * 2)) {
|
||||||
LOGGER.warn("Emulator settings runtime.threads ({}) can be increased to ({}) to possibly increase performance.",
|
LOGGER.warn("Emulator settings runtime.threads ({}) can be increased to ({}) to possibly increase performance.",
|
||||||
Emulator.getConfig().getInt("runtime.threads"),
|
Emulator.getConfig().getInt("runtime.threads"),
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ public class RoomUserPetComposer extends MessageComposer {
|
|||||||
this.response.appendString("");
|
this.response.appendString("");
|
||||||
this.response.appendString("unknown");
|
this.response.appendString("unknown");
|
||||||
this.response.appendInt(0);
|
this.response.appendInt(0);
|
||||||
|
this.response.appendInt(0);
|
||||||
return this.response;
|
return this.response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,631 @@
|
|||||||
|
package com.eu.habbo.gui;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.monitoring.EmulatorStatsService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.CompoundBorder;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import javax.swing.border.MatteBorder;
|
||||||
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
|
import javax.swing.table.DefaultTableModel;
|
||||||
|
import javax.swing.table.JTableHeader;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.ComponentAdapter;
|
||||||
|
import java.awt.event.ComponentEvent;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.geom.Path2D;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class EmulatorDashboard extends JFrame {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(EmulatorDashboard.class);
|
||||||
|
|
||||||
|
// Modern Dark Theme Colors
|
||||||
|
private static final Color COLOR_BG = new Color(18, 18, 18);
|
||||||
|
private static final Color COLOR_SURFACE = new Color(30, 30, 30);
|
||||||
|
private static final Color COLOR_SURFACE_HOVER = new Color(45, 45, 45);
|
||||||
|
private static final Color COLOR_PRIMARY = new Color(99, 102, 241);
|
||||||
|
private static final Color COLOR_PRIMARY_SOFT = new Color(99, 102, 241, 45);
|
||||||
|
private static final Color COLOR_SUCCESS = new Color(34, 197, 94);
|
||||||
|
private static final Color COLOR_WARNING = new Color(245, 158, 11);
|
||||||
|
private static final Color COLOR_TEXT = new Color(240, 240, 240);
|
||||||
|
private static final Color COLOR_TEXT_MUTED = new Color(150, 150, 150);
|
||||||
|
private static final Color COLOR_TEXT_SUBTLE = new Color(110, 110, 110);
|
||||||
|
private static final Color COLOR_BORDER = new Color(50, 50, 50);
|
||||||
|
private static final Font FONT_TITLE = new Font("Segoe UI", Font.BOLD, 26);
|
||||||
|
private static final Font FONT_SECTION = new Font("Segoe UI", Font.BOLD, 16);
|
||||||
|
private static final Font FONT_SMALL = new Font("Segoe UI", Font.PLAIN, 12);
|
||||||
|
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||||
|
|
||||||
|
private static EmulatorDashboard instance;
|
||||||
|
|
||||||
|
// Overview Tab
|
||||||
|
private final JLabel memLabel = createMetricLabel();
|
||||||
|
private final JLabel cpuLabel = createMetricLabel();
|
||||||
|
private final JLabel threadLabel = createMetricLabel();
|
||||||
|
private final JLabel usersLabel = createMetricLabel();
|
||||||
|
private final JLabel roomsLabel = createMetricLabel();
|
||||||
|
private final JLabel wiredLabel = createMetricLabel();
|
||||||
|
private final JLabel uptimeLabel = createStatusValueLabel();
|
||||||
|
private final JLabel lastUpdatedLabel = createStatusValueLabel();
|
||||||
|
private final JLabel footerStatusLabel = createStatusValueLabel();
|
||||||
|
private final MemoryGraphPanel memoryGraph = new MemoryGraphPanel();
|
||||||
|
|
||||||
|
// Tables
|
||||||
|
private final DefaultTableModel usersTableModel;
|
||||||
|
private final DefaultTableModel roomsTableModel;
|
||||||
|
private final DefaultTableModel wiredTableModel;
|
||||||
|
private final JTable usersTable;
|
||||||
|
private final JTable roomsTable;
|
||||||
|
private final JTable wiredTable;
|
||||||
|
private final JLabel usersCountLabel = createCountLabel();
|
||||||
|
private final JLabel roomsCountLabel = createCountLabel();
|
||||||
|
private final JLabel wiredCountLabel = createCountLabel();
|
||||||
|
|
||||||
|
// UI Components
|
||||||
|
private final JPanel cardsPanel;
|
||||||
|
private final CardLayout cardLayout;
|
||||||
|
private final Map<String, JPanel> navButtons = new HashMap<>();
|
||||||
|
private String selectedCardName = "Overview";
|
||||||
|
|
||||||
|
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||||
|
Thread t = new Thread(r, "Dashboard-Updater");
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
|
private EmulatorDashboard() {
|
||||||
|
setTitle("Arcturus Morningstar - System Dashboard");
|
||||||
|
setSize(1100, 700);
|
||||||
|
setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
|
||||||
|
setLocationRelativeTo(null);
|
||||||
|
getContentPane().setBackground(COLOR_BG);
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
|
||||||
|
// Setup custom Look & Feel basics to remove weird Swing borders
|
||||||
|
UIManager.put("ScrollBar.background", COLOR_BG);
|
||||||
|
UIManager.put("ScrollBar.thumb", COLOR_SURFACE_HOVER);
|
||||||
|
|
||||||
|
// Sidebar
|
||||||
|
JPanel sidebar = new JPanel();
|
||||||
|
sidebar.setLayout(new BoxLayout(sidebar, BoxLayout.Y_AXIS));
|
||||||
|
sidebar.setBackground(COLOR_SURFACE);
|
||||||
|
sidebar.setPreferredSize(new Dimension(220, 0));
|
||||||
|
sidebar.setBorder(new MatteBorder(0, 0, 0, 1, COLOR_BORDER));
|
||||||
|
|
||||||
|
// Sidebar Header
|
||||||
|
JPanel brandPanel = new JPanel(new BorderLayout());
|
||||||
|
brandPanel.setBackground(COLOR_SURFACE);
|
||||||
|
brandPanel.setBorder(new EmptyBorder(20, 20, 30, 20));
|
||||||
|
|
||||||
|
JLabel brandTitle = new JLabel("Arcturus");
|
||||||
|
brandTitle.setFont(new Font("Segoe UI", Font.BOLD, 22));
|
||||||
|
brandTitle.setForeground(COLOR_TEXT);
|
||||||
|
|
||||||
|
JLabel brandSub = new JLabel("v" + Emulator.version);
|
||||||
|
brandSub.setFont(new Font("Segoe UI", Font.PLAIN, 12));
|
||||||
|
brandSub.setForeground(COLOR_PRIMARY);
|
||||||
|
|
||||||
|
brandPanel.add(brandTitle, BorderLayout.NORTH);
|
||||||
|
brandPanel.add(brandSub, BorderLayout.SOUTH);
|
||||||
|
sidebar.add(brandPanel);
|
||||||
|
|
||||||
|
// Main Cards
|
||||||
|
cardLayout = new CardLayout();
|
||||||
|
cardsPanel = new JPanel(cardLayout);
|
||||||
|
cardsPanel.setBackground(COLOR_BG);
|
||||||
|
|
||||||
|
// Setup Tabs
|
||||||
|
usersTableModel = createTableModel(new String[]{"ID", "Username", "Rank", "Credits", "Room ID"});
|
||||||
|
roomsTableModel = createTableModel(new String[]{"Room ID", "Name", "Players", "Items", "Tickables", "CPU (ms)", "Est. RAM (KB)", "Thread"});
|
||||||
|
wiredTableModel = createTableModel(new String[]{"Room ID", "Avg Tick", "Peak Tick", "Usage %", "Delayed", "Overloaded?", "Heavy?"});
|
||||||
|
usersTable = createDashboardTable(usersTableModel);
|
||||||
|
roomsTable = createDashboardTable(roomsTableModel);
|
||||||
|
wiredTable = createDashboardTable(wiredTableModel);
|
||||||
|
|
||||||
|
cardsPanel.add(createOverviewTab(), "Overview");
|
||||||
|
cardsPanel.add(createTableTab("Online Users", "Players currently connected to the emulator.", usersTable, usersCountLabel), "Online Users");
|
||||||
|
cardsPanel.add(createTableTab("Active Rooms", "Loaded rooms with lightweight performance indicators.", roomsTable, roomsCountLabel), "Active Rooms");
|
||||||
|
cardsPanel.add(createTableTab("Wired Diagnostics", "Rooms currently using wired timing, delay and execution budget.", wiredTable, wiredCountLabel), "Wired Diagnostics");
|
||||||
|
|
||||||
|
// Sidebar Navigation
|
||||||
|
sidebar.add(createNavButton("Overview", "Overview"));
|
||||||
|
sidebar.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||||
|
sidebar.add(createNavButton("Online Users", "Online Users"));
|
||||||
|
sidebar.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||||
|
sidebar.add(createNavButton("Active Rooms", "Active Rooms"));
|
||||||
|
sidebar.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||||
|
sidebar.add(createNavButton("Wired Diagnostics", "Wired Diagnostics"));
|
||||||
|
sidebar.add(Box.createVerticalGlue());
|
||||||
|
|
||||||
|
add(sidebar, BorderLayout.WEST);
|
||||||
|
add(cardsPanel, BorderLayout.CENTER);
|
||||||
|
add(createStatusBar(), BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
addComponentListener(new ComponentAdapter() {
|
||||||
|
@Override
|
||||||
|
public void componentShown(ComponentEvent e) {
|
||||||
|
setActiveCard("Overview");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start updates
|
||||||
|
scheduler.scheduleAtFixedRate(this::updateMetrics, 0, 1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DefaultTableModel createTableModel(String[] cols) {
|
||||||
|
return new DefaultTableModel(cols, 0) {
|
||||||
|
@Override public boolean isCellEditable(int row, int column) { return false; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel createNavButton(String text, String cardName) {
|
||||||
|
JPanel btn = new JPanel(new BorderLayout());
|
||||||
|
btn.setBackground(COLOR_SURFACE);
|
||||||
|
btn.setMaximumSize(new Dimension(220, 45));
|
||||||
|
btn.setBorder(new EmptyBorder(0, 18, 0, 0));
|
||||||
|
btn.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||||
|
navButtons.put(cardName, btn);
|
||||||
|
|
||||||
|
JLabel lbl = new JLabel(text);
|
||||||
|
lbl.setFont(new Font("Segoe UI", Font.BOLD, 14));
|
||||||
|
lbl.setForeground(COLOR_TEXT_MUTED);
|
||||||
|
btn.add(lbl, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
btn.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseEntered(MouseEvent e) {
|
||||||
|
btn.setBackground(COLOR_SURFACE_HOVER);
|
||||||
|
lbl.setForeground(COLOR_TEXT);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void mouseExited(MouseEvent e) {
|
||||||
|
updateNavButtonStyle(cardName, btn, lbl);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
setActiveCard(cardName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateNavButtonStyle(cardName, btn, lbl);
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel createOverviewTab() {
|
||||||
|
JPanel wrapper = new JPanel(new BorderLayout());
|
||||||
|
wrapper.setBackground(COLOR_BG);
|
||||||
|
wrapper.setBorder(new EmptyBorder(30, 30, 30, 30));
|
||||||
|
|
||||||
|
JPanel header = new JPanel(new BorderLayout(0, 14));
|
||||||
|
header.setOpaque(false);
|
||||||
|
|
||||||
|
JLabel title = new JLabel("Dashboard Overview");
|
||||||
|
title.setFont(FONT_TITLE);
|
||||||
|
title.setForeground(COLOR_TEXT);
|
||||||
|
|
||||||
|
JLabel subtitle = new JLabel("Operational view of emulator health, activity and wired performance.");
|
||||||
|
subtitle.setFont(new Font("Segoe UI", Font.PLAIN, 13));
|
||||||
|
subtitle.setForeground(COLOR_TEXT_MUTED);
|
||||||
|
|
||||||
|
JPanel titleBlock = new JPanel();
|
||||||
|
titleBlock.setOpaque(false);
|
||||||
|
titleBlock.setLayout(new BoxLayout(titleBlock, BoxLayout.Y_AXIS));
|
||||||
|
titleBlock.add(title);
|
||||||
|
titleBlock.add(Box.createRigidArea(new Dimension(0, 4)));
|
||||||
|
titleBlock.add(subtitle);
|
||||||
|
|
||||||
|
header.add(titleBlock, BorderLayout.NORTH);
|
||||||
|
header.add(createOverviewMetaPanel(), BorderLayout.SOUTH);
|
||||||
|
wrapper.add(header, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
JPanel content = new JPanel(new GridLayout(1, 2, 20, 20));
|
||||||
|
content.setOpaque(false);
|
||||||
|
content.setBorder(new EmptyBorder(20, 0, 0, 0));
|
||||||
|
|
||||||
|
// Left Stats
|
||||||
|
JPanel statsPanel = new JPanel(new GridLayout(3, 2, 12, 12));
|
||||||
|
statsPanel.setOpaque(false);
|
||||||
|
statsPanel.add(createMetricCard("Memory Allocation", memLabel));
|
||||||
|
statsPanel.add(createMetricCard("CPU Load", cpuLabel));
|
||||||
|
statsPanel.add(createMetricCard("Active OS Threads", threadLabel));
|
||||||
|
statsPanel.add(createMetricCard("Connected Players", usersLabel));
|
||||||
|
statsPanel.add(createMetricCard("Loaded Rooms", roomsLabel));
|
||||||
|
statsPanel.add(createMetricCard("Wired Tickables", wiredLabel));
|
||||||
|
content.add(statsPanel);
|
||||||
|
|
||||||
|
// Right Graph
|
||||||
|
JPanel graphContainer = new JPanel(new BorderLayout());
|
||||||
|
graphContainer.setBackground(COLOR_SURFACE);
|
||||||
|
graphContainer.setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
new MatteBorder(1, 1, 1, 1, COLOR_BORDER),
|
||||||
|
new EmptyBorder(15, 15, 15, 15)
|
||||||
|
));
|
||||||
|
|
||||||
|
JLabel gTitle = new JLabel("Realtime Memory Usage");
|
||||||
|
gTitle.setFont(FONT_SECTION);
|
||||||
|
gTitle.setForeground(COLOR_TEXT_MUTED);
|
||||||
|
gTitle.setBorder(new EmptyBorder(0, 0, 15, 0));
|
||||||
|
graphContainer.add(gTitle, BorderLayout.NORTH);
|
||||||
|
graphContainer.add(memoryGraph, BorderLayout.CENTER);
|
||||||
|
content.add(graphContainer);
|
||||||
|
|
||||||
|
wrapper.add(content, BorderLayout.CENTER);
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel createMetricCard(String title, JLabel valueLabel) {
|
||||||
|
JPanel card = new JPanel(new BorderLayout());
|
||||||
|
card.setBackground(COLOR_SURFACE);
|
||||||
|
card.setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
new MatteBorder(1, 1, 1, 1, COLOR_BORDER),
|
||||||
|
new EmptyBorder(15, 20, 15, 20)
|
||||||
|
));
|
||||||
|
|
||||||
|
JLabel tLabel = new JLabel(title);
|
||||||
|
tLabel.setFont(new Font("Segoe UI", Font.BOLD, 13));
|
||||||
|
tLabel.setForeground(COLOR_TEXT_MUTED);
|
||||||
|
|
||||||
|
card.add(tLabel, BorderLayout.NORTH);
|
||||||
|
card.add(valueLabel, BorderLayout.SOUTH);
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JLabel createMetricLabel() {
|
||||||
|
JLabel label = new JLabel("-");
|
||||||
|
label.setFont(new Font("Segoe UI", Font.BOLD, 28));
|
||||||
|
label.setForeground(COLOR_TEXT);
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel createOverviewMetaPanel() {
|
||||||
|
JPanel panel = new JPanel(new GridLayout(1, 3, 12, 12));
|
||||||
|
panel.setOpaque(false);
|
||||||
|
panel.add(createStatusCard("Uptime", uptimeLabel, COLOR_PRIMARY));
|
||||||
|
panel.add(createStatusCard("Last Refresh", lastUpdatedLabel, COLOR_SUCCESS));
|
||||||
|
panel.add(createStatusCard("GUI Status", footerStatusLabel, COLOR_WARNING));
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel createStatusCard(String title, JLabel valueLabel, Color accent) {
|
||||||
|
JPanel panel = new JPanel(new BorderLayout());
|
||||||
|
panel.setBackground(COLOR_SURFACE);
|
||||||
|
panel.setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
new MatteBorder(1, 1, 1, 1, COLOR_BORDER),
|
||||||
|
new EmptyBorder(12, 14, 12, 14)
|
||||||
|
));
|
||||||
|
|
||||||
|
JPanel accentBar = new JPanel();
|
||||||
|
accentBar.setBackground(accent);
|
||||||
|
accentBar.setPreferredSize(new Dimension(6, 0));
|
||||||
|
|
||||||
|
JPanel content = new JPanel();
|
||||||
|
content.setOpaque(false);
|
||||||
|
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
|
||||||
|
|
||||||
|
JLabel label = new JLabel(title);
|
||||||
|
label.setFont(FONT_SMALL);
|
||||||
|
label.setForeground(COLOR_TEXT_MUTED);
|
||||||
|
|
||||||
|
content.add(label);
|
||||||
|
content.add(Box.createRigidArea(new Dimension(0, 6)));
|
||||||
|
content.add(valueLabel);
|
||||||
|
|
||||||
|
panel.add(accentBar, BorderLayout.WEST);
|
||||||
|
panel.add(content, BorderLayout.CENTER);
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JLabel createStatusValueLabel() {
|
||||||
|
JLabel label = new JLabel("-");
|
||||||
|
label.setFont(new Font("Segoe UI", Font.BOLD, 16));
|
||||||
|
label.setForeground(COLOR_TEXT);
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JLabel createCountLabel() {
|
||||||
|
JLabel label = new JLabel("0 rows");
|
||||||
|
label.setFont(FONT_SMALL);
|
||||||
|
label.setForeground(COLOR_TEXT_MUTED);
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JTable createDashboardTable(DefaultTableModel model) {
|
||||||
|
JTable table = new JTable(model);
|
||||||
|
table.setBackground(COLOR_SURFACE);
|
||||||
|
table.setForeground(COLOR_TEXT);
|
||||||
|
table.setGridColor(COLOR_BORDER);
|
||||||
|
table.setRowHeight(34);
|
||||||
|
table.setFont(new Font("Segoe UI", Font.PLAIN, 13));
|
||||||
|
table.setFillsViewportHeight(true);
|
||||||
|
table.setSelectionBackground(COLOR_PRIMARY);
|
||||||
|
table.setSelectionForeground(Color.WHITE);
|
||||||
|
table.setShowVerticalLines(false);
|
||||||
|
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
|
table.setAutoCreateRowSorter(true);
|
||||||
|
|
||||||
|
JTableHeader header = table.getTableHeader();
|
||||||
|
header.setBackground(new Color(22, 22, 22));
|
||||||
|
header.setForeground(COLOR_TEXT_MUTED);
|
||||||
|
header.setFont(new Font("Segoe UI", Font.BOLD, 13));
|
||||||
|
header.setPreferredSize(new Dimension(0, 38));
|
||||||
|
header.setBorder(BorderFactory.createMatteBorder(1, 0, 1, 0, COLOR_BORDER));
|
||||||
|
((DefaultTableCellRenderer) header.getDefaultRenderer()).setHorizontalAlignment(JLabel.LEFT);
|
||||||
|
((DefaultTableCellRenderer) header.getDefaultRenderer()).setBorder(new EmptyBorder(0, 10, 0, 0));
|
||||||
|
|
||||||
|
table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||||
|
JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||||
|
label.setBorder(new EmptyBorder(0, 10, 0, 10));
|
||||||
|
label.setForeground(isSelected ? Color.WHITE : COLOR_TEXT);
|
||||||
|
label.setBackground(isSelected ? COLOR_PRIMARY : ((row % 2 == 0) ? COLOR_SURFACE : new Color(35, 35, 35)));
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel createTableTab(String title, String subtitle, JTable table, JLabel countLabel) {
|
||||||
|
JPanel wrapper = new JPanel(new BorderLayout());
|
||||||
|
wrapper.setBackground(COLOR_BG);
|
||||||
|
wrapper.setBorder(new EmptyBorder(30, 30, 30, 30));
|
||||||
|
|
||||||
|
JPanel titlePanel = new JPanel(new BorderLayout());
|
||||||
|
titlePanel.setOpaque(false);
|
||||||
|
|
||||||
|
JLabel titleLbl = new JLabel(title);
|
||||||
|
titleLbl.setFont(FONT_TITLE);
|
||||||
|
titleLbl.setForeground(COLOR_TEXT);
|
||||||
|
|
||||||
|
JLabel subtitleLbl = new JLabel(subtitle);
|
||||||
|
subtitleLbl.setFont(new Font("Segoe UI", Font.PLAIN, 13));
|
||||||
|
subtitleLbl.setForeground(COLOR_TEXT_MUTED);
|
||||||
|
subtitleLbl.setBorder(new EmptyBorder(6, 0, 0, 0));
|
||||||
|
|
||||||
|
JPanel textPanel = new JPanel();
|
||||||
|
textPanel.setOpaque(false);
|
||||||
|
textPanel.setLayout(new BoxLayout(textPanel, BoxLayout.Y_AXIS));
|
||||||
|
textPanel.add(titleLbl);
|
||||||
|
textPanel.add(subtitleLbl);
|
||||||
|
|
||||||
|
titlePanel.add(textPanel, BorderLayout.WEST);
|
||||||
|
titlePanel.add(countLabel, BorderLayout.EAST);
|
||||||
|
wrapper.add(titlePanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
JScrollPane scrollPane = new JScrollPane(table);
|
||||||
|
scrollPane.getViewport().setBackground(COLOR_SURFACE);
|
||||||
|
scrollPane.setBorder(new MatteBorder(1, 1, 1, 1, COLOR_BORDER));
|
||||||
|
scrollPane.setBorder(new CompoundBorder(
|
||||||
|
new EmptyBorder(20, 0, 0, 0),
|
||||||
|
new MatteBorder(1, 1, 1, 1, COLOR_BORDER)
|
||||||
|
));
|
||||||
|
wrapper.add(scrollPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel createStatusBar() {
|
||||||
|
JPanel statusBar = new JPanel(new BorderLayout());
|
||||||
|
statusBar.setBackground(COLOR_SURFACE);
|
||||||
|
statusBar.setBorder(new CompoundBorder(
|
||||||
|
new MatteBorder(1, 0, 0, 0, COLOR_BORDER),
|
||||||
|
new EmptyBorder(8, 14, 8, 14)
|
||||||
|
));
|
||||||
|
|
||||||
|
JLabel left = new JLabel("Dashboard running locally");
|
||||||
|
left.setFont(FONT_SMALL);
|
||||||
|
left.setForeground(COLOR_TEXT_SUBTLE);
|
||||||
|
|
||||||
|
JLabel right = new JLabel("Tip: table columns are sortable");
|
||||||
|
right.setFont(FONT_SMALL);
|
||||||
|
right.setForeground(COLOR_TEXT_SUBTLE);
|
||||||
|
|
||||||
|
statusBar.add(left, BorderLayout.WEST);
|
||||||
|
statusBar.add(right, BorderLayout.EAST);
|
||||||
|
return statusBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setActiveCard(String cardName) {
|
||||||
|
selectedCardName = cardName;
|
||||||
|
cardLayout.show(cardsPanel, cardName);
|
||||||
|
navButtons.forEach((name, button) -> {
|
||||||
|
JLabel label = (JLabel) button.getComponent(0);
|
||||||
|
updateNavButtonStyle(name, button, label);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateNavButtonStyle(String cardName, JPanel button, JLabel label) {
|
||||||
|
boolean active = cardName.equals(selectedCardName);
|
||||||
|
button.setBackground(active ? COLOR_PRIMARY_SOFT : COLOR_SURFACE);
|
||||||
|
label.setForeground(active ? COLOR_TEXT : COLOR_TEXT_MUTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMetrics() {
|
||||||
|
try {
|
||||||
|
EmulatorStatsService.Snapshot snapshot = EmulatorStatsService.collectSnapshot();
|
||||||
|
EmulatorStatsService.Overview overview = snapshot.overview;
|
||||||
|
|
||||||
|
Object[][] usersData = new Object[snapshot.users.size()][5];
|
||||||
|
for (int i = 0; i < snapshot.users.size(); i++) {
|
||||||
|
EmulatorStatsService.OnlineUserRow user = snapshot.users.get(i);
|
||||||
|
usersData[i] = new Object[]{user.id, user.username, user.rank, user.credits, user.roomId};
|
||||||
|
}
|
||||||
|
|
||||||
|
Object[][] roomsData = new Object[snapshot.rooms.size()][8];
|
||||||
|
for (int i = 0; i < snapshot.rooms.size(); i++) {
|
||||||
|
EmulatorStatsService.ActiveRoomRow room = snapshot.rooms.get(i);
|
||||||
|
roomsData[i] = new Object[]{
|
||||||
|
room.roomId,
|
||||||
|
room.name,
|
||||||
|
room.players,
|
||||||
|
room.items,
|
||||||
|
room.tickables,
|
||||||
|
String.format("%.2f", room.cpuMs),
|
||||||
|
room.estimatedRamKb,
|
||||||
|
room.thread
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Object[][] wiredData = new Object[snapshot.wired.size()][7];
|
||||||
|
for (int i = 0; i < snapshot.wired.size(); i++) {
|
||||||
|
EmulatorStatsService.WiredRoomRow wiredRoom = snapshot.wired.get(i);
|
||||||
|
wiredData[i] = new Object[]{
|
||||||
|
wiredRoom.roomId,
|
||||||
|
wiredRoom.averageTickMs + " ms",
|
||||||
|
wiredRoom.peakTickMs + " ms",
|
||||||
|
wiredRoom.usagePercent + "%",
|
||||||
|
wiredRoom.delayedEventsPending,
|
||||||
|
wiredRoom.overloaded ? "YES" : "NO",
|
||||||
|
wiredRoom.heavy ? "YES" : "NO"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
memLabel.setText(String.format("%d MB / %d MB", overview.memoryUsedMb, overview.memoryMaxMb));
|
||||||
|
cpuLabel.setText(String.format("%.1f %%", overview.cpuLoadPercent));
|
||||||
|
threadLabel.setText(String.valueOf(overview.activeOsThreads));
|
||||||
|
usersLabel.setText(String.valueOf(overview.connectedPlayers));
|
||||||
|
roomsLabel.setText(String.valueOf(overview.loadedRooms));
|
||||||
|
wiredLabel.setText(String.valueOf(overview.wiredTickables));
|
||||||
|
uptimeLabel.setText(EmulatorStatsService.formatDuration(overview.uptimeSeconds));
|
||||||
|
lastUpdatedLabel.setText(LocalDateTime.now().format(TIME_FORMAT));
|
||||||
|
footerStatusLabel.setText(overview.guiStatus);
|
||||||
|
memoryGraph.addValue((long) overview.memoryUsedMb * 1024L * 1024L, (long) overview.memoryMaxMb * 1024L * 1024L);
|
||||||
|
|
||||||
|
usersTableModel.setDataVector(usersData, new String[]{"ID", "Username", "Rank", "Credits", "Room ID"});
|
||||||
|
roomsTableModel.setDataVector(roomsData, new String[]{"Room ID", "Name", "Players", "Items", "Tickables", "CPU (ms)", "Est. RAM (KB)", "Thread"});
|
||||||
|
wiredTableModel.setDataVector(wiredData, new String[]{"Room ID", "Avg Tick", "Peak Tick", "Usage %", "Delayed", "Overloaded?", "Heavy?"});
|
||||||
|
usersCountLabel.setText(snapshot.users.size() + " rows");
|
||||||
|
roomsCountLabel.setText(snapshot.rooms.size() + " rows");
|
||||||
|
wiredCountLabel.setText(snapshot.wired.size() + " rows");
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Error updating dashboard metrics", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void launch() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new EmulatorDashboard();
|
||||||
|
}
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
instance.setVisible(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MemoryGraphPanel extends JPanel {
|
||||||
|
private final LinkedList<Double> history = new LinkedList<>();
|
||||||
|
private static final int MAX_POINTS = 100;
|
||||||
|
|
||||||
|
public MemoryGraphPanel() {
|
||||||
|
setOpaque(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addValue(long used, long max) {
|
||||||
|
double percent = (double) used / (double) max;
|
||||||
|
history.addLast(percent);
|
||||||
|
if (history.size() > MAX_POINTS) {
|
||||||
|
history.removeFirst();
|
||||||
|
}
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void paintComponent(Graphics g) {
|
||||||
|
super.paintComponent(g);
|
||||||
|
Graphics2D g2 = (Graphics2D) g;
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
|
||||||
|
int width = getWidth();
|
||||||
|
int height = getHeight();
|
||||||
|
|
||||||
|
// Background grid and labels
|
||||||
|
g2.setFont(new Font("Segoe UI", Font.PLAIN, 10));
|
||||||
|
long maxMemRaw = Runtime.getRuntime().maxMemory();
|
||||||
|
|
||||||
|
for(int i = 0; i <= 4; i++) {
|
||||||
|
int y = i == 0 ? 15 : height * i / 4;
|
||||||
|
if (i == 4) y = height - 5;
|
||||||
|
|
||||||
|
g2.setColor(COLOR_BORDER);
|
||||||
|
g2.drawLine(0, y, width, y);
|
||||||
|
|
||||||
|
// Draw Y-axis numbers
|
||||||
|
g2.setColor(COLOR_TEXT_MUTED);
|
||||||
|
long labelVal = (long) (maxMemRaw * (1.0 - (double)i / 4.0)) / 1024 / 1024;
|
||||||
|
g2.drawString(labelVal + " MB", 5, y - 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (history.size() < 2) return;
|
||||||
|
|
||||||
|
double dx = (double) width / (MAX_POINTS - 1);
|
||||||
|
Path2D path = new Path2D.Double();
|
||||||
|
path.moveTo(0, height);
|
||||||
|
|
||||||
|
int i = MAX_POINTS - history.size();
|
||||||
|
for (Double val : history) {
|
||||||
|
double x = i * dx;
|
||||||
|
double y = height - (val * height);
|
||||||
|
if (i == MAX_POINTS - history.size()) {
|
||||||
|
path.moveTo(x, y);
|
||||||
|
} else {
|
||||||
|
path.lineTo(x, y);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw line
|
||||||
|
g2.setStroke(new BasicStroke(3.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
|
||||||
|
g2.setColor(COLOR_PRIMARY);
|
||||||
|
g2.draw(path);
|
||||||
|
|
||||||
|
// Fill area
|
||||||
|
path.lineTo(width, height);
|
||||||
|
path.lineTo((MAX_POINTS - history.size()) * dx, height);
|
||||||
|
path.closePath();
|
||||||
|
|
||||||
|
GradientPaint fillPaint = new GradientPaint(
|
||||||
|
0, 0, new Color(COLOR_PRIMARY.getRed(), COLOR_PRIMARY.getGreen(), COLOR_PRIMARY.getBlue(), 120),
|
||||||
|
0, height, new Color(COLOR_PRIMARY.getRed(), COLOR_PRIMARY.getGreen(), COLOR_PRIMARY.getBlue(), 10)
|
||||||
|
);
|
||||||
|
g2.setPaint(fillPaint);
|
||||||
|
g2.fill(path);
|
||||||
|
|
||||||
|
Double lastValue = history.peekLast();
|
||||||
|
if (lastValue != null) {
|
||||||
|
String usageLabel = String.format("Usage %.1f%%", lastValue * 100.0);
|
||||||
|
g2.setFont(new Font("Segoe UI", Font.BOLD, 12));
|
||||||
|
FontMetrics metrics = g2.getFontMetrics();
|
||||||
|
int labelWidth = metrics.stringWidth(usageLabel) + 16;
|
||||||
|
int labelHeight = 24;
|
||||||
|
int labelX = Math.max(8, width - labelWidth - 8);
|
||||||
|
int labelY = 8;
|
||||||
|
|
||||||
|
g2.setColor(new Color(0, 0, 0, 130));
|
||||||
|
g2.fillRoundRect(labelX, labelY, labelWidth, labelHeight, 12, 12);
|
||||||
|
g2.setColor(COLOR_TEXT);
|
||||||
|
g2.drawString(usageLabel, labelX + 8, labelY + 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatDuration(long millis) {
|
||||||
|
long totalSeconds = Math.max(0L, millis / 1000L);
|
||||||
|
long hours = totalSeconds / 3600L;
|
||||||
|
long minutes = (totalSeconds % 3600L) / 60L;
|
||||||
|
long seconds = totalSeconds % 60L;
|
||||||
|
return String.format("%02dh %02dm %02ds", hours, minutes, seconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ import com.eu.habbo.habbohotel.achievements.AchievementManager;
|
|||||||
import com.eu.habbo.habbohotel.bots.BotManager;
|
import com.eu.habbo.habbohotel.bots.BotManager;
|
||||||
import com.eu.habbo.habbohotel.campaign.calendar.CalendarManager;
|
import com.eu.habbo.habbohotel.campaign.calendar.CalendarManager;
|
||||||
import com.eu.habbo.habbohotel.catalog.CatalogManager;
|
import com.eu.habbo.habbohotel.catalog.CatalogManager;
|
||||||
|
import com.eu.habbo.habbohotel.wheel.WheelManager;
|
||||||
|
import com.eu.habbo.habbohotel.soundboard.SoundboardManager;
|
||||||
import com.eu.habbo.habbohotel.commands.CommandHandler;
|
import com.eu.habbo.habbohotel.commands.CommandHandler;
|
||||||
import com.eu.habbo.habbohotel.crafting.CraftingManager;
|
import com.eu.habbo.habbohotel.crafting.CraftingManager;
|
||||||
import com.eu.habbo.habbohotel.guides.GuideManager;
|
import com.eu.habbo.habbohotel.guides.GuideManager;
|
||||||
@@ -64,6 +66,8 @@ public class GameEnvironment {
|
|||||||
private GoogleTranslateManager googleTranslateManager;
|
private GoogleTranslateManager googleTranslateManager;
|
||||||
private CustomBadgeManager customBadgeManager;
|
private CustomBadgeManager customBadgeManager;
|
||||||
private InfostandBackgroundManager infostandBackgroundManager;
|
private InfostandBackgroundManager infostandBackgroundManager;
|
||||||
|
private WheelManager wheelManager;
|
||||||
|
private SoundboardManager soundboardManager;
|
||||||
|
|
||||||
public void load() throws Exception {
|
public void load() throws Exception {
|
||||||
LOGGER.info("GameEnvironment -> Loading...");
|
LOGGER.info("GameEnvironment -> Loading...");
|
||||||
@@ -93,6 +97,8 @@ public class GameEnvironment {
|
|||||||
this.googleTranslateManager = new GoogleTranslateManager();
|
this.googleTranslateManager = new GoogleTranslateManager();
|
||||||
this.customBadgeManager = new CustomBadgeManager();
|
this.customBadgeManager = new CustomBadgeManager();
|
||||||
this.infostandBackgroundManager = new InfostandBackgroundManager();
|
this.infostandBackgroundManager = new InfostandBackgroundManager();
|
||||||
|
this.wheelManager = new WheelManager();
|
||||||
|
this.soundboardManager = new SoundboardManager();
|
||||||
|
|
||||||
this.roomManager.loadPublicRooms();
|
this.roomManager.loadPublicRooms();
|
||||||
this.navigatorManager.loadNavigator();
|
this.navigatorManager.loadNavigator();
|
||||||
@@ -156,6 +162,14 @@ public class GameEnvironment {
|
|||||||
return this.catalogManager;
|
return this.catalogManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public WheelManager getWheelManager() {
|
||||||
|
return this.wheelManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SoundboardManager getSoundboardManager() {
|
||||||
|
return this.soundboardManager;
|
||||||
|
}
|
||||||
|
|
||||||
public HotelViewManager getHotelViewManager() {
|
public HotelViewManager getHotelViewManager() {
|
||||||
return this.hotelViewManager;
|
return this.hotelViewManager;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,18 +138,20 @@ public class Bot implements Runnable {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (this.needsUpdate) {
|
if (this.needsUpdate) {
|
||||||
|
Room localRoom = this.room;
|
||||||
|
RoomUnit localRoomUnit = this.roomUnit;
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE bots SET name = ?, motto = ?, figure = ?, gender = ?, user_id = ?, room_id = ?, x = ?, y = ?, z = ?, rot = ?, dance = ?, freeroam = ?, chat_lines = ?, chat_auto = ?, chat_random = ?, chat_delay = ?, effect = ?, bubble_id = ? WHERE id = ?")) {
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE bots SET name = ?, motto = ?, figure = ?, gender = ?, user_id = ?, room_id = ?, x = ?, y = ?, z = ?, rot = ?, dance = ?, freeroam = ?, chat_lines = ?, chat_auto = ?, chat_random = ?, chat_delay = ?, effect = ?, bubble_id = ? WHERE id = ?")) {
|
||||||
statement.setString(1, this.name);
|
statement.setString(1, this.name);
|
||||||
statement.setString(2, this.motto);
|
statement.setString(2, this.motto);
|
||||||
statement.setString(3, this.figure);
|
statement.setString(3, this.figure);
|
||||||
statement.setString(4, this.gender.toString());
|
statement.setString(4, this.gender.toString());
|
||||||
statement.setInt(5, this.ownerId);
|
statement.setInt(5, this.ownerId);
|
||||||
statement.setInt(6, this.room == null ? 0 : this.room.getId());
|
statement.setInt(6, localRoom == null ? 0 : localRoom.getId());
|
||||||
statement.setInt(7, this.roomUnit == null ? 0 : this.roomUnit.getX());
|
statement.setInt(7, localRoomUnit == null ? 0 : localRoomUnit.getX());
|
||||||
statement.setInt(8, this.roomUnit == null ? 0 : this.roomUnit.getY());
|
statement.setInt(8, localRoomUnit == null ? 0 : localRoomUnit.getY());
|
||||||
statement.setDouble(9, this.roomUnit == null ? 0 : this.roomUnit.getZ());
|
statement.setDouble(9, localRoomUnit == null ? 0 : localRoomUnit.getZ());
|
||||||
statement.setInt(10, this.roomUnit == null ? 0 : this.roomUnit.getBodyRotation().getValue());
|
statement.setInt(10, localRoomUnit == null ? 0 : localRoomUnit.getBodyRotation().getValue());
|
||||||
statement.setInt(11, this.roomUnit == null ? 0 : this.roomUnit.getDanceType().getType());
|
statement.setInt(11, localRoomUnit == null ? 0 : localRoomUnit.getDanceType().getType());
|
||||||
statement.setString(12, this.canWalk ? "1" : "0");
|
statement.setString(12, this.canWalk ? "1" : "0");
|
||||||
StringBuilder text = new StringBuilder();
|
StringBuilder text = new StringBuilder();
|
||||||
for (String s : this.chatLines) {
|
for (String s : this.chatLines) {
|
||||||
@@ -187,11 +189,7 @@ public class Bot implements Runnable {
|
|||||||
int timeOut = Emulator.getRandom().nextInt(20) * 2;
|
int timeOut = Emulator.getRandom().nextInt(20) * 2;
|
||||||
this.roomUnit.setWalkTimeOut((timeOut < 10 ? 5 : timeOut) + Emulator.getIntUnixTimestamp());
|
this.roomUnit.setWalkTimeOut((timeOut < 10 ? 5 : timeOut) + Emulator.getIntUnixTimestamp());
|
||||||
}
|
}
|
||||||
}/* else {
|
}
|
||||||
for (RoomTile t : this.room.getLayout().getTilesAround(this.room.getLayout().getTile(this.getRoomUnit().getX(), this.getRoomUnit().getY()))) {
|
|
||||||
WiredManager.handle(WiredTriggerType.BOT_REACHED_STF, this.roomUnit, this.room, this.room.getItemsAt(t).toArray());
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.chatLines.isEmpty() && this.chatTimeOut <= Emulator.getIntUnixTimestamp() && this.chatAuto) {
|
if (!this.chatLines.isEmpty() && this.chatTimeOut <= Emulator.getIntUnixTimestamp() && this.chatAuto) {
|
||||||
@@ -216,7 +214,7 @@ public class Bot implements Runnable {
|
|||||||
} else {
|
} else {
|
||||||
this.lastChatIndex++;
|
this.lastChatIndex++;
|
||||||
if (this.lastChatIndex >= this.chatLines.size()) {
|
if (this.lastChatIndex >= this.chatLines.size()) {
|
||||||
this.lastChatIndex = 0; // start from scratch :-3
|
this.lastChatIndex = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +280,7 @@ public class Bot implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onPickUp(Habbo habbo, Room room) {
|
public void onPickUp(Habbo habbo, Room room) {
|
||||||
|
this.stopFollowingHabbo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onUserSay(final RoomChatMessage message) {
|
public void onUserSay(final RoomChatMessage message) {
|
||||||
@@ -308,9 +306,6 @@ public class Bot implements Runnable {
|
|||||||
public void setName(String name) {
|
public void setName(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.needsUpdate = true;
|
this.needsUpdate = true;
|
||||||
|
|
||||||
//if(this.room != null)
|
|
||||||
//this.room.sendComposer(new ChangeNameUpdatedComposer(this.getRoomUnit(), this.getName()).compose());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMotto() {
|
public String getMotto() {
|
||||||
@@ -537,5 +532,28 @@ public class Bot implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final short[] DEFAULT_OWNER_ACTION_IDS = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11};
|
||||||
|
|
||||||
|
public static final int ACTION_ROTATE = 11;
|
||||||
|
|
||||||
|
private static final long MIN_OWNER_ACTION_INTERVAL_MS = 200L;
|
||||||
|
|
||||||
|
private volatile long lastOwnerActionAt;
|
||||||
|
|
||||||
|
public short[] getOwnerActionIds() {
|
||||||
|
return DEFAULT_OWNER_ACTION_IDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean tryAcquireOwnerActionSlot() {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (now - this.lastOwnerActionAt < MIN_OWNER_ACTION_INTERVAL_MS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.lastOwnerActionAt = now;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPostOwnerAction(int actionId) {
|
||||||
|
// no-op default
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public class BotManager {
|
|||||||
addBotDefinition("generic", Bot.class);
|
addBotDefinition("generic", Bot.class);
|
||||||
addBotDefinition("bartender", ButlerBot.class);
|
addBotDefinition("bartender", ButlerBot.class);
|
||||||
addBotDefinition("visitor_log", VisitorBot.class);
|
addBotDefinition("visitor_log", VisitorBot.class);
|
||||||
|
addBotDefinition(FrankBot.BOT_TYPE, FrankBot.class);
|
||||||
|
|
||||||
this.reload();
|
this.reload();
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,13 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class ButlerBot extends Bot {
|
public class ButlerBot extends Bot {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ButlerBot.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(ButlerBot.class);
|
||||||
public static THashMap<THashSet<String>, Integer> serveItems = new THashMap<>();
|
public static THashMap<THashSet<String>, Integer> serveItems = new THashMap<>();
|
||||||
|
private static final ConcurrentHashMap<Pattern, Integer> serveItemsCompiled = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public ButlerBot(ResultSet set) throws SQLException {
|
public ButlerBot(ResultSet set) throws SQLException {
|
||||||
super(set);
|
super(set);
|
||||||
@@ -38,6 +41,7 @@ public class ButlerBot extends Bot {
|
|||||||
serveItems = new THashMap<>();
|
serveItems = new THashMap<>();
|
||||||
|
|
||||||
serveItems.clear();
|
serveItems.clear();
|
||||||
|
serveItemsCompiled.clear();
|
||||||
|
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); Statement statement = connection.createStatement(); ResultSet set = statement.executeQuery("SELECT * FROM bot_serves")) {
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); Statement statement = connection.createStatement(); ResultSet set = statement.executeQuery("SELECT * FROM bot_serves")) {
|
||||||
while (set.next()) {
|
while (set.next()) {
|
||||||
@@ -45,6 +49,17 @@ public class ButlerBot extends Bot {
|
|||||||
THashSet<String> ks = new THashSet<>();
|
THashSet<String> ks = new THashSet<>();
|
||||||
Collections.addAll(ks, keys);
|
Collections.addAll(ks, keys);
|
||||||
serveItems.put(ks, set.getInt("item"));
|
serveItems.put(ks, set.getInt("item"));
|
||||||
|
|
||||||
|
for (String key : keys) {
|
||||||
|
if (key != null && !key.trim().isEmpty()) {
|
||||||
|
try {
|
||||||
|
Pattern pattern = Pattern.compile("\\b" + Pattern.quote(key.toLowerCase()) + "\\b");
|
||||||
|
serveItemsCompiled.put(pattern, set.getInt("item"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Failed to compile butler bot keyword pattern: {}", key, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
LOGGER.error("Caught SQL exception", e);
|
LOGGER.error("Caught SQL exception", e);
|
||||||
@@ -53,6 +68,7 @@ public class ButlerBot extends Bot {
|
|||||||
|
|
||||||
public static void dispose() {
|
public static void dispose() {
|
||||||
serveItems.clear();
|
serveItems.clear();
|
||||||
|
serveItemsCompiled.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -66,74 +82,73 @@ public class ButlerBot extends Bot {
|
|||||||
if (distanceBetweenBotAndHabbo <= Emulator.getConfig().getInt("hotel.bot.butler.commanddistance")) {
|
if (distanceBetweenBotAndHabbo <= Emulator.getConfig().getInt("hotel.bot.butler.commanddistance")) {
|
||||||
|
|
||||||
if (message.getUnfilteredMessage() != null) {
|
if (message.getUnfilteredMessage() != null) {
|
||||||
for (Map.Entry<THashSet<String>, Integer> set : serveItems.entrySet()) {
|
String unfilteredLower = message.getUnfilteredMessage().toLowerCase();
|
||||||
for (String keyword : set.getKey()) {
|
for (Map.Entry<Pattern, Integer> entry : serveItemsCompiled.entrySet()) {
|
||||||
|
Pattern pattern = entry.getKey();
|
||||||
|
if (pattern.matcher(unfilteredLower).matches()) {
|
||||||
|
int itemId = entry.getValue();
|
||||||
|
String keyword = pattern.pattern().replace("\\b", "").replace("\\Q", "").replace("\\E", "");
|
||||||
|
|
||||||
// Check if the string contains a certain keyword using a regex.
|
// Enable plugins to cancel this event
|
||||||
// If keyword = tea, teapot wouldn't trigger it.
|
BotServerItemEvent serveEvent = new BotServerItemEvent(this, message.getHabbo(), itemId);
|
||||||
if (message.getUnfilteredMessage().toLowerCase().matches("\\b" + keyword + "\\b")) {
|
if (Emulator.getPluginManager().fireEvent(serveEvent).isCancelled()) {
|
||||||
|
|
||||||
// Enable plugins to cancel this event
|
|
||||||
BotServerItemEvent serveEvent = new BotServerItemEvent(this, message.getHabbo(), set.getValue());
|
|
||||||
if (Emulator.getPluginManager().fireEvent(serveEvent).isCancelled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start give handitem process
|
|
||||||
if (this.getRoomUnit().canWalk()) {
|
|
||||||
final String key = keyword;
|
|
||||||
final Bot bot = this;
|
|
||||||
|
|
||||||
// Step 1: Look at Habbo
|
|
||||||
bot.lookAt(serveEvent.habbo);
|
|
||||||
|
|
||||||
// Step 2: Prepare tasks for when the Bot (carrying the handitem) reaches the Habbo
|
|
||||||
final List<Runnable> tasks = new ArrayList<>();
|
|
||||||
tasks.add(new RoomUnitGiveHanditem(serveEvent.habbo.getRoomUnit(), serveEvent.habbo.getHabboInfo().getCurrentRoom(), serveEvent.itemId));
|
|
||||||
tasks.add(new RoomUnitGiveHanditem(this.getRoomUnit(), serveEvent.habbo.getHabboInfo().getCurrentRoom(), 0));
|
|
||||||
|
|
||||||
tasks.add(() -> {
|
|
||||||
if(this.getRoom() != null) {
|
|
||||||
String botMessage = Emulator.getTexts()
|
|
||||||
.getValue("bots.butler.given")
|
|
||||||
.replace("%key%", key)
|
|
||||||
.replace("%username%", serveEvent.habbo.getHabboInfo().getUsername());
|
|
||||||
|
|
||||||
if (!WiredManager.triggerUserSays(this.getRoom(), this.getRoomUnit(), botMessage)) {
|
|
||||||
bot.talk(botMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
List<Runnable> failedReached = new ArrayList<>();
|
|
||||||
failedReached.add(() -> {
|
|
||||||
if (distanceBetweenBotAndHabbo <= Emulator.getConfig().getInt("hotel.bot.butler.servedistance", 8)) {
|
|
||||||
for (Runnable task : tasks) {
|
|
||||||
task.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Give bot the handitem that it's going to give the Habbo
|
|
||||||
Emulator.getThreading().run(new RoomUnitGiveHanditem(this.getRoomUnit(), serveEvent.habbo.getHabboInfo().getCurrentRoom(), serveEvent.itemId));
|
|
||||||
|
|
||||||
if (distanceBetweenBotAndHabbo > Emulator.getConfig().getInt("hotel.bot.butler.reachdistance", 3)) {
|
|
||||||
Emulator.getThreading().run(new RoomUnitWalkToRoomUnit(this.getRoomUnit(), serveEvent.habbo.getRoomUnit(), serveEvent.habbo.getHabboInfo().getCurrentRoom(), tasks, failedReached, Emulator.getConfig().getInt("hotel.bot.butler.reachdistance", 3)));
|
|
||||||
} else {
|
|
||||||
Emulator.getThreading().run(failedReached.get(0), 1000);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(this.getRoom() != null) {
|
|
||||||
this.getRoom().giveHandItem(serveEvent.habbo, serveEvent.itemId);
|
|
||||||
|
|
||||||
String msg = Emulator.getTexts().getValue("bots.butler.given").replace("%key%", keyword).replace("%username%", serveEvent.habbo.getHabboInfo().getUsername());
|
|
||||||
if (!WiredManager.triggerUserSays(this.getRoom(), this.getRoomUnit(), msg)) {
|
|
||||||
this.talk(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start give handitem process
|
||||||
|
if (this.getRoomUnit().canWalk()) {
|
||||||
|
final String key = keyword;
|
||||||
|
final Bot bot = this;
|
||||||
|
|
||||||
|
// Step 1: Look at Habbo
|
||||||
|
bot.lookAt(serveEvent.habbo);
|
||||||
|
|
||||||
|
// Step 2: Prepare tasks for when the Bot (carrying the handitem) reaches the Habbo
|
||||||
|
final List<Runnable> tasks = new ArrayList<>();
|
||||||
|
tasks.add(new RoomUnitGiveHanditem(serveEvent.habbo.getRoomUnit(), serveEvent.habbo.getHabboInfo().getCurrentRoom(), serveEvent.itemId));
|
||||||
|
tasks.add(new RoomUnitGiveHanditem(this.getRoomUnit(), serveEvent.habbo.getHabboInfo().getCurrentRoom(), 0));
|
||||||
|
|
||||||
|
tasks.add(() -> {
|
||||||
|
if(this.getRoom() != null) {
|
||||||
|
String botMessage = Emulator.getTexts()
|
||||||
|
.getValue("bots.butler.given")
|
||||||
|
.replace("%key%", key)
|
||||||
|
.replace("%username%", serveEvent.habbo.getHabboInfo().getUsername());
|
||||||
|
|
||||||
|
if (!WiredManager.triggerUserSays(this.getRoom(), this.getRoomUnit(), botMessage)) {
|
||||||
|
bot.talk(botMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
List<Runnable> failedReached = new ArrayList<>();
|
||||||
|
failedReached.add(() -> {
|
||||||
|
if (distanceBetweenBotAndHabbo <= Emulator.getConfig().getInt("hotel.bot.butler.servedistance", 8)) {
|
||||||
|
for (Runnable task : tasks) {
|
||||||
|
task.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Give bot the handitem that it's going to give the Habbo
|
||||||
|
Emulator.getThreading().run(new RoomUnitGiveHanditem(this.getRoomUnit(), serveEvent.habbo.getHabboInfo().getCurrentRoom(), serveEvent.itemId));
|
||||||
|
|
||||||
|
if (distanceBetweenBotAndHabbo > Emulator.getConfig().getInt("hotel.bot.butler.reachdistance", 3)) {
|
||||||
|
Emulator.getThreading().run(new RoomUnitWalkToRoomUnit(this.getRoomUnit(), serveEvent.habbo.getRoomUnit(), serveEvent.habbo.getHabboInfo().getCurrentRoom(), tasks, failedReached, Emulator.getConfig().getInt("hotel.bot.butler.reachdistance", 3)));
|
||||||
|
} else {
|
||||||
|
Emulator.getThreading().run(failedReached.get(0), 1000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(this.getRoom() != null) {
|
||||||
|
this.getRoom().giveHandItem(serveEvent.habbo, serveEvent.itemId);
|
||||||
|
|
||||||
|
String msg = Emulator.getTexts().getValue("bots.butler.given").replace("%key%", keyword).replace("%username%", serveEvent.habbo.getHabboInfo().getUsername());
|
||||||
|
if (!WiredManager.triggerUserSays(this.getRoom(), this.getRoomUnit(), msg)) {
|
||||||
|
this.talk(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,455 @@
|
|||||||
|
package com.eu.habbo.habbohotel.bots;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.rooms.*;
|
||||||
|
import com.eu.habbo.habbohotel.users.Habbo;
|
||||||
|
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer;
|
||||||
|
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserWhisperComposer;
|
||||||
|
import com.eu.habbo.threading.runnables.RoomUnitWalkToLocation;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class FrankBot extends ButlerBot {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(FrankBot.class);
|
||||||
|
public static final String BOT_TYPE = "frank";
|
||||||
|
public static final String PERMISSION_USE = "acc_bot_frank";
|
||||||
|
private static final String KEY_DOOR_LINES = "__door_lines";
|
||||||
|
private static final String KEY_BUSY_WHISPER = "__busy_whisper";
|
||||||
|
private static final String KEY_DOOR_TRIGGERS = "__door_triggers";
|
||||||
|
private static final List<String> DEFAULT_DOOR_LINES = List.of(
|
||||||
|
"Right this way - mind the step!",
|
||||||
|
"And out you go. Come back soon!",
|
||||||
|
"Allow me to escort you to the exit.",
|
||||||
|
"There's the door. Farewell, true believer!"
|
||||||
|
);
|
||||||
|
private static final String DEFAULT_BUSY_WHISPER =
|
||||||
|
"Sorry, I am currently busy. Please wait until I am available.";
|
||||||
|
private static final Pattern DEFAULT_DOOR_PATTERN = Pattern.compile(
|
||||||
|
"\\b(show me the door|kick me|i want to leave|let me out)\\b");
|
||||||
|
|
||||||
|
private static final ConcurrentHashMap<Pattern, List<String>> chatResponses = new ConcurrentHashMap<>();
|
||||||
|
private static volatile List<String> doorLines = DEFAULT_DOOR_LINES;
|
||||||
|
private static volatile String busyWhisper = DEFAULT_BUSY_WHISPER;
|
||||||
|
private static volatile Pattern doorTriggerPattern = DEFAULT_DOOR_PATTERN;
|
||||||
|
|
||||||
|
private static final Random RANDOM = new Random();
|
||||||
|
|
||||||
|
private static final int MAX_CHAT_KEYWORDS = 256;
|
||||||
|
private static final int MAX_DOOR_TRIGGERS = 32;
|
||||||
|
private static final int MAX_MESSAGE_LEN = 256;
|
||||||
|
private static final long BUSY_WHISPER_COOLDOWN_MS = 5000L;
|
||||||
|
|
||||||
|
private volatile RoomTile homeTile;
|
||||||
|
private volatile RoomUserRotation homeRotation;
|
||||||
|
private final AtomicBoolean busy = new AtomicBoolean(false);
|
||||||
|
private final AtomicBoolean returnScheduled = new AtomicBoolean(false);
|
||||||
|
private final ConcurrentHashMap<Integer, Long> lastBusyWhisperAt = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public FrankBot(ResultSet set) throws SQLException {
|
||||||
|
super(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrankBot(Bot bot) {
|
||||||
|
super(bot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlace(Habbo habbo, Room room) {
|
||||||
|
super.onPlace(habbo, room);
|
||||||
|
if (this.getRoomUnit() != null) {
|
||||||
|
this.homeTile = this.getRoomUnit().getCurrentLocation();
|
||||||
|
this.homeRotation = this.getRoomUnit().getBodyRotation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final short[] FRANK_OWNER_ACTIONS = { (short) Bot.ACTION_ROTATE };
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short[] getOwnerActionIds() {
|
||||||
|
return FRANK_OWNER_ACTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPostOwnerAction(int actionId) {
|
||||||
|
if (actionId == ACTION_ROTATE && this.getRoomUnit() != null) {
|
||||||
|
this.homeRotation = this.getRoomUnit().getBodyRotation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void initialise() {
|
||||||
|
chatResponses.clear();
|
||||||
|
doorLines = DEFAULT_DOOR_LINES;
|
||||||
|
busyWhisper = DEFAULT_BUSY_WHISPER;
|
||||||
|
doorTriggerPattern = DEFAULT_DOOR_PATTERN;
|
||||||
|
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
Statement statement = connection.createStatement();
|
||||||
|
ResultSet set = statement.executeQuery("SELECT `keys`, `responses` FROM bot_chat_responses WHERE bot_type = '" + BOT_TYPE + "'")) {
|
||||||
|
while (set.next()) {
|
||||||
|
String keysRaw = set.getString("keys");
|
||||||
|
String responsesRaw = set.getString("responses");
|
||||||
|
|
||||||
|
if (keysRaw == null || responsesRaw == null) continue;
|
||||||
|
|
||||||
|
List<String> responses = new ArrayList<>();
|
||||||
|
for (String line : responsesRaw.split("\n")) {
|
||||||
|
String trimmed = line.trim();
|
||||||
|
if (!trimmed.isEmpty()) responses.add(trimmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responses.isEmpty()) continue;
|
||||||
|
|
||||||
|
String firstKey = keysRaw.split(";", 2)[0].trim();
|
||||||
|
if (firstKey.startsWith("__")) {
|
||||||
|
switch (firstKey) {
|
||||||
|
case KEY_DOOR_LINES:
|
||||||
|
doorLines = new CopyOnWriteArrayList<>(responses);
|
||||||
|
break;
|
||||||
|
case KEY_BUSY_WHISPER:
|
||||||
|
busyWhisper = responses.get(0);
|
||||||
|
break;
|
||||||
|
case KEY_DOOR_TRIGGERS:
|
||||||
|
doorTriggerPattern = buildDoorTriggerPattern(responses);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOGGER.warn("FrankBot: unknown system key '{}', ignored", firstKey);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> shared = new CopyOnWriteArrayList<>(responses);
|
||||||
|
|
||||||
|
for (String key : keysRaw.split(";")) {
|
||||||
|
if (chatResponses.size() >= MAX_CHAT_KEYWORDS) {
|
||||||
|
LOGGER.warn("FrankBot: chat keyword cap ({}) reached, remaining rows ignored",
|
||||||
|
MAX_CHAT_KEYWORDS);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
String k = key == null ? "" : key.trim().toLowerCase();
|
||||||
|
if (k.isEmpty()) continue;
|
||||||
|
try {
|
||||||
|
Pattern pattern = Pattern.compile("\\b" + Pattern.quote(k) + "\\b");
|
||||||
|
chatResponses.put(pattern, shared);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Failed to compile Frank chat keyword pattern: {}", k, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.warn("FrankBot: could not load bot_chat_responses ({}). Frank will still serve items.", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
ButlerBot.initialise();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void dispose() {
|
||||||
|
chatResponses.clear();
|
||||||
|
doorLines = DEFAULT_DOOR_LINES;
|
||||||
|
busyWhisper = DEFAULT_BUSY_WHISPER;
|
||||||
|
doorTriggerPattern = DEFAULT_DOOR_PATTERN;
|
||||||
|
ButlerBot.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Pattern buildDoorTriggerPattern(List<String> triggers) {
|
||||||
|
StringBuilder sb = new StringBuilder("\\b(");
|
||||||
|
boolean first = true;
|
||||||
|
int count = 0;
|
||||||
|
for (String trigger : triggers) {
|
||||||
|
if (count >= MAX_DOOR_TRIGGERS) {
|
||||||
|
LOGGER.warn("FrankBot: door trigger cap ({}) reached, extra entries ignored",
|
||||||
|
MAX_DOOR_TRIGGERS);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
String t = trigger == null ? "" : trigger.trim().toLowerCase();
|
||||||
|
if (t.isEmpty()) continue;
|
||||||
|
if (!first) sb.append('|');
|
||||||
|
sb.append(Pattern.quote(t));
|
||||||
|
first = false;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
sb.append(")\\b");
|
||||||
|
|
||||||
|
if (first) return DEFAULT_DOOR_PATTERN;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Pattern.compile(sb.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("FrankBot: failed to compile door trigger pattern from {}, falling back to default", triggers, e);
|
||||||
|
return DEFAULT_DOOR_PATTERN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUserSay(final RoomChatMessage message) {
|
||||||
|
Room currentRoom = this.getRoom();
|
||||||
|
if (currentRoom == null) return;
|
||||||
|
|
||||||
|
Habbo asker = message.getHabbo();
|
||||||
|
if (asker == null || asker.getClient() == null) return;
|
||||||
|
|
||||||
|
if (this.getRoomUnit() == null) return;
|
||||||
|
|
||||||
|
String raw = message.getUnfilteredMessage();
|
||||||
|
if (raw != null && raw.length() > MAX_MESSAGE_LEN) return;
|
||||||
|
|
||||||
|
if (this.homeTile == null) {
|
||||||
|
this.homeTile = this.getRoomUnit().getCurrentLocation();
|
||||||
|
this.homeRotation = this.getRoomUnit().getBodyRotation();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.busy.get() || this.getRoomUnit().hasStatus(RoomUnitStatus.MOVE)) {
|
||||||
|
this.whisperThrottled(asker, busyWhisper);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (raw != null) {
|
||||||
|
double distance = this.getRoomUnit().getCurrentLocation().distance(asker.getRoomUnit().getCurrentLocation());
|
||||||
|
int commandDistance = Emulator.getConfig().getInt("hotel.bot.butler.commanddistance");
|
||||||
|
|
||||||
|
if (distance <= commandDistance) {
|
||||||
|
String lower = raw.toLowerCase();
|
||||||
|
|
||||||
|
if (doorTriggerPattern.matcher(lower).find()) {
|
||||||
|
if (!this.busy.compareAndSet(false, true)) {
|
||||||
|
this.whisperThrottled(asker, busyWhisper);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.showToTheDoor(asker);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (java.util.Map.Entry<Pattern, List<String>> entry : chatResponses.entrySet()) {
|
||||||
|
if (entry.getKey().matcher(lower).find()) {
|
||||||
|
List<String> options = entry.getValue();
|
||||||
|
if (options.isEmpty()) continue;
|
||||||
|
|
||||||
|
String reply = options.get(RANDOM.nextInt(options.size()));
|
||||||
|
this.talk(reply);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.busy.compareAndSet(false, true)) {
|
||||||
|
this.whisperThrottled(asker, busyWhisper);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.onUserSay(message);
|
||||||
|
this.schedulePostServeReturn(currentRoom.getId(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void whisperThrottled(Habbo target, String text) {
|
||||||
|
if (target == null || text == null || text.isEmpty() || this.getRoomUnit() == null) return;
|
||||||
|
int userId = target.getHabboInfo().getId();
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
Long last = lastBusyWhisperAt.get(userId);
|
||||||
|
if (last != null && (now - last) < BUSY_WHISPER_COOLDOWN_MS) return;
|
||||||
|
lastBusyWhisperAt.put(userId, now);
|
||||||
|
RoomChatMessage msg = new RoomChatMessage(text, this.getRoomUnit(), RoomChatMessageBubbles.BOT);
|
||||||
|
target.getClient().sendResponse(new RoomUserWhisperComposer(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showToTheDoor(final Habbo target) {
|
||||||
|
final Room room = this.getRoom();
|
||||||
|
if (room == null || room.getLayout() == null || target == null) {
|
||||||
|
this.busy.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final RoomTile doorTile = room.getLayout().getDoorTile();
|
||||||
|
if (doorTile == null) {
|
||||||
|
this.busy.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lookAt(target);
|
||||||
|
List<String> lines = doorLines;
|
||||||
|
String line = lines.isEmpty() ? DEFAULT_DOOR_LINES.get(RANDOM.nextInt(DEFAULT_DOOR_LINES.size()))
|
||||||
|
: lines.get(RANDOM.nextInt(lines.size()));
|
||||||
|
this.talk(line);
|
||||||
|
|
||||||
|
final int targetId = target.getHabboInfo().getId();
|
||||||
|
final int roomId = room.getId();
|
||||||
|
final AtomicBoolean fired = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
final Runnable kickThenReturn = () -> {
|
||||||
|
if (!fired.compareAndSet(false, true)) return;
|
||||||
|
Room currentRoom = this.getRoom();
|
||||||
|
if (currentRoom == null || currentRoom.getId() != roomId) {
|
||||||
|
this.busy.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Habbo stillHere = currentRoom.getHabbo(targetId);
|
||||||
|
if (stillHere != null) {
|
||||||
|
currentRoom.kickHabbo(stillHere, false);
|
||||||
|
}
|
||||||
|
this.scheduleReturnHome(targetId, roomId, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.getRoomUnit().canWalk() && !this.getRoomUnit().getCurrentLocation().equals(doorTile)) {
|
||||||
|
List<Runnable> onArrive = new ArrayList<>();
|
||||||
|
onArrive.add(kickThenReturn);
|
||||||
|
|
||||||
|
List<Runnable> onFail = new ArrayList<>();
|
||||||
|
onFail.add(() -> Emulator.getThreading().run(kickThenReturn, 1500));
|
||||||
|
|
||||||
|
this.getRoomUnit().setGoalLocation(doorTile);
|
||||||
|
Emulator.getThreading().run(
|
||||||
|
new RoomUnitWalkToLocation(this.getRoomUnit(), doorTile, room, onArrive, onFail));
|
||||||
|
} else {
|
||||||
|
Emulator.getThreading().run(kickThenReturn, 1500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int RETURN_HOME_POLL_MS = 500;
|
||||||
|
private static final int RETURN_HOME_MAX_WAIT_MS = 8000;
|
||||||
|
private static final int POST_SERVE_POLL_MS = 750;
|
||||||
|
private static final int POST_SERVE_MAX_WAIT_MS = 30000;
|
||||||
|
|
||||||
|
private void schedulePostServeReturn(final int roomId, final int waitedMs) {
|
||||||
|
if (waitedMs == 0 && !this.returnScheduled.compareAndSet(false, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (waitedMs >= POST_SERVE_MAX_WAIT_MS) {
|
||||||
|
this.returnScheduled.set(false);
|
||||||
|
this.busy.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.homeTile == null) {
|
||||||
|
this.returnScheduled.set(false);
|
||||||
|
this.busy.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Emulator.getThreading().run(() -> {
|
||||||
|
Room r = this.getRoom();
|
||||||
|
if (r == null || r.getId() != roomId || this.getRoomUnit() == null || this.homeTile == null) {
|
||||||
|
this.returnScheduled.set(false);
|
||||||
|
this.busy.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.getRoomUnit().getCurrentLocation().equals(this.homeTile)) {
|
||||||
|
if (this.homeRotation != null && this.getRoomUnit().getBodyRotation() != this.homeRotation) {
|
||||||
|
this.getRoomUnit().setRotation(this.homeRotation);
|
||||||
|
r.sendComposer(new RoomUserStatusComposer(this.getRoomUnit()).compose());
|
||||||
|
this.persistPosition();
|
||||||
|
} else {
|
||||||
|
this.busy.set(false);
|
||||||
|
}
|
||||||
|
this.returnScheduled.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean stillWalking = this.getRoomUnit().hasStatus(RoomUnitStatus.MOVE)
|
||||||
|
|| (this.getRoomUnit().getPath() != null && !this.getRoomUnit().getPath().isEmpty());
|
||||||
|
|
||||||
|
if (stillWalking) {
|
||||||
|
this.schedulePostServeReturn(roomId, waitedMs + POST_SERVE_POLL_MS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.returnScheduled.set(false);
|
||||||
|
this.returnHome(-1, false);
|
||||||
|
}, POST_SERVE_POLL_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleReturnHome(final int kickedHabboId, final int roomId, final int waitedMs) {
|
||||||
|
Room currentRoom = this.getRoom();
|
||||||
|
if (currentRoom == null || currentRoom.getId() != roomId) return;
|
||||||
|
|
||||||
|
boolean stillEscorting = currentRoom.getHabbo(kickedHabboId) != null;
|
||||||
|
|
||||||
|
if (!stillEscorting || waitedMs >= RETURN_HOME_MAX_WAIT_MS) {
|
||||||
|
this.returnHome(kickedHabboId, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Emulator.getThreading().run(
|
||||||
|
() -> this.scheduleReturnHome(kickedHabboId, roomId, waitedMs + RETURN_HOME_POLL_MS),
|
||||||
|
RETURN_HOME_POLL_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void returnHome(int kickedHabboId, boolean alwaysTeleport) {
|
||||||
|
final Room room = this.getRoom();
|
||||||
|
if (room == null || this.homeTile == null || this.getRoomUnit() == null) {
|
||||||
|
this.busy.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Runnable teleportHome = () -> {
|
||||||
|
Room r = this.getRoom();
|
||||||
|
if (r == null || this.getRoomUnit() == null) return;
|
||||||
|
|
||||||
|
double homeZ = r.getTopHeightAt(this.homeTile.x, this.homeTile.y);
|
||||||
|
|
||||||
|
this.getRoomUnit().stopWalking();
|
||||||
|
this.getRoomUnit().setZ(homeZ);
|
||||||
|
this.getRoomUnit().setLocation(this.homeTile);
|
||||||
|
this.getRoomUnit().setPreviousLocationZ(homeZ);
|
||||||
|
if (this.homeRotation != null) {
|
||||||
|
this.getRoomUnit().setRotation(this.homeRotation);
|
||||||
|
}
|
||||||
|
this.getRoomUnit().statusUpdate(true);
|
||||||
|
r.sendComposer(new RoomUserStatusComposer(this.getRoomUnit()).compose());
|
||||||
|
this.persistPosition();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.getRoomUnit().getCurrentLocation().equals(this.homeTile)) {
|
||||||
|
if (this.homeRotation != null) {
|
||||||
|
this.getRoomUnit().setRotation(this.homeRotation);
|
||||||
|
room.sendComposer(new RoomUserStatusComposer(this.getRoomUnit()).compose());
|
||||||
|
}
|
||||||
|
this.persistPosition();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasOtherWatchers = false;
|
||||||
|
for (Habbo h : room.getCurrentHabbos().values()) {
|
||||||
|
if (h.getHabboInfo().getId() != kickedHabboId) {
|
||||||
|
hasOtherWatchers = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alwaysTeleport || !hasOtherWatchers || !this.getRoomUnit().canWalk()) {
|
||||||
|
teleportHome.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Runnable> onArrive = new ArrayList<>();
|
||||||
|
onArrive.add(() -> {
|
||||||
|
if (this.homeRotation != null && this.getRoom() != null) {
|
||||||
|
this.getRoomUnit().setRotation(this.homeRotation);
|
||||||
|
this.getRoom().sendComposer(new RoomUserStatusComposer(this.getRoomUnit()).compose());
|
||||||
|
}
|
||||||
|
this.persistPosition();
|
||||||
|
});
|
||||||
|
|
||||||
|
List<Runnable> onFail = new ArrayList<>();
|
||||||
|
onFail.add(teleportHome);
|
||||||
|
|
||||||
|
this.getRoomUnit().setGoalLocation(this.homeTile);
|
||||||
|
Emulator.getThreading().run(
|
||||||
|
new RoomUnitWalkToLocation(this.getRoomUnit(), this.homeTile, room, onArrive, onFail));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void persistPosition() {
|
||||||
|
this.needsUpdate(true);
|
||||||
|
this.run();
|
||||||
|
this.busy.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -202,6 +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;
|
||||||
|
public final TIntObjectMap<int[]> furnitureValues;
|
||||||
|
private volatile byte[] rareValuesPayloadCache;
|
||||||
|
|
||||||
public CatalogManager() {
|
public CatalogManager() {
|
||||||
long millis = System.currentTimeMillis();
|
long millis = System.currentTimeMillis();
|
||||||
@@ -219,6 +221,7 @@ public class CatalogManager {
|
|||||||
this.buildersClubOfferDefs = new TIntIntHashMap();
|
this.buildersClubOfferDefs = new TIntIntHashMap();
|
||||||
this.vouchers = new ArrayList<>();
|
this.vouchers = new ArrayList<>();
|
||||||
this.limitedNumbers = new THashMap<>();
|
this.limitedNumbers = new THashMap<>();
|
||||||
|
this.furnitureValues = new TIntObjectHashMap<>();
|
||||||
|
|
||||||
this.initialize();
|
this.initialize();
|
||||||
|
|
||||||
@@ -243,6 +246,76 @@ public class CatalogManager {
|
|||||||
this.loadClothing();
|
this.loadClothing();
|
||||||
this.loadRecycler();
|
this.loadRecycler();
|
||||||
this.loadGiftWrappers();
|
this.loadGiftWrappers();
|
||||||
|
this.loadFurnitureValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void loadFurnitureValues() {
|
||||||
|
this.furnitureValues.clear();
|
||||||
|
final int diamondType = Emulator.getConfig().getInt("seasonal.currency.diamond", 5);
|
||||||
|
|
||||||
|
for (CatalogPage page : this.catalogPages.valueCollection()) {
|
||||||
|
for (CatalogItem catalogItem : page.getCatalogItems().valueCollection()) {
|
||||||
|
if (catalogItem.getAmount() != 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int credits = catalogItem.getCredits();
|
||||||
|
int points = catalogItem.getPoints();
|
||||||
|
int pointsType = catalogItem.getPointsType();
|
||||||
|
|
||||||
|
if (points <= 0 || pointsType != diamondType)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
THashSet<Item> baseItems = catalogItem.getBaseItems();
|
||||||
|
|
||||||
|
if (baseItems.size() != 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (Item item : baseItems) {
|
||||||
|
FurnitureType type = item.getType();
|
||||||
|
|
||||||
|
if (type != FurnitureType.FLOOR && type != FurnitureType.WALL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int spriteId = item.getSpriteId();
|
||||||
|
|
||||||
|
if (spriteId > 0 && !this.furnitureValues.containsKey(spriteId)) {
|
||||||
|
this.furnitureValues.put(spriteId, new int[]{credits, points, pointsType});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rebuildRareValuesPayloadCache();
|
||||||
|
|
||||||
|
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() {
|
||||||
|
return this.furnitureValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getRareValuesPayloadSnapshot() {
|
||||||
|
return this.rareValuesPayloadCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void loadLimitedNumbers() {
|
private synchronized void loadLimitedNumbers() {
|
||||||
@@ -1046,10 +1119,19 @@ public class CatalogManager {
|
|||||||
for (Item baseItem : item.getBaseItems()) {
|
for (Item baseItem : item.getBaseItems()) {
|
||||||
for (int k = 0; k < item.getItemAmount(baseItem.getId()); k++) {
|
for (int k = 0; k < item.getItemAmount(baseItem.getId()); k++) {
|
||||||
if (baseItem.getName().startsWith("rentable_bot_") || baseItem.getName().startsWith("bot_")) {
|
if (baseItem.getName().startsWith("rentable_bot_") || baseItem.getName().startsWith("bot_")) {
|
||||||
|
String baseName = baseItem.getName();
|
||||||
String type = item.getName().replace("rentable_bot_", "");
|
String type = item.getName().replace("rentable_bot_", "");
|
||||||
type = type.replace("bot_", "");
|
type = type.replace("bot_", "");
|
||||||
type = type.replace("visitor_logger", "visitor_log");
|
type = type.replace("visitor_logger", "visitor_log");
|
||||||
|
|
||||||
|
if (("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)) {
|
||||||
|
habbo.getClient().sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
THashMap<String, String> data = new THashMap<>();
|
THashMap<String, String> data = new THashMap<>();
|
||||||
|
|
||||||
for (String s : item.getExtradata().split(";")) {
|
for (String s : item.getExtradata().split(";")) {
|
||||||
|
|||||||
@@ -72,7 +72,11 @@ public class ClubOffer implements ISerialize {
|
|||||||
this.type = OfferType.fromDatabase(set.getString("type"));
|
this.type = OfferType.fromDatabase(set.getString("type"));
|
||||||
this.vip = this.type == OfferType.VIP;
|
this.vip = this.type == OfferType.VIP;
|
||||||
this.deal = set.getString("deal").equals("1");
|
this.deal = set.getString("deal").equals("1");
|
||||||
this.giftable = set.getString("giftable").equals("1");
|
boolean giftable = false;
|
||||||
|
try {
|
||||||
|
giftable = "1".equals(set.getString("giftable"));
|
||||||
|
} catch (SQLException ignored) {}
|
||||||
|
this.giftable = giftable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getId() {
|
public int getId() {
|
||||||
|
|||||||
+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) {
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ public class CommandHandler {
|
|||||||
addCommand(new EmptyInventoryCommand());
|
addCommand(new EmptyInventoryCommand());
|
||||||
addCommand(new EmptyBotsInventoryCommand());
|
addCommand(new EmptyBotsInventoryCommand());
|
||||||
addCommand(new EmptyPetsInventoryCommand());
|
addCommand(new EmptyPetsInventoryCommand());
|
||||||
|
addCommand(new EmuStatsCommand());
|
||||||
addCommand(new EnableCommand());
|
addCommand(new EnableCommand());
|
||||||
addCommand(new EventCommand());
|
addCommand(new EventCommand());
|
||||||
addCommand(new FacelessCommand());
|
addCommand(new FacelessCommand());
|
||||||
@@ -300,7 +301,6 @@ public class CommandHandler {
|
|||||||
addCommand(new GivePrefixCommand());
|
addCommand(new GivePrefixCommand());
|
||||||
addCommand(new ListPrefixesCommand());
|
addCommand(new ListPrefixesCommand());
|
||||||
addCommand(new RemovePrefixCommand());
|
addCommand(new RemovePrefixCommand());
|
||||||
addCommand(new PrefixBlacklistCommand());
|
|
||||||
addCommand(new WiredCommand());
|
addCommand(new WiredCommand());
|
||||||
addCommand(new TestCommand());
|
addCommand(new TestCommand());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.eu.habbo.habbohotel.commands;
|
||||||
|
|
||||||
|
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
|
|
||||||
|
public class EmuStatsCommand extends Command {
|
||||||
|
public EmuStatsCommand() {
|
||||||
|
super(Permission.ACC_MODTOOL_ROOM_INFO, new String[]{"emustats"});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(GameClient gameClient, String[] params) {
|
||||||
|
gameClient.getHabbo().whisper("Emulator stats are available in the Nitro stats window.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
package com.eu.habbo.habbohotel.commands;
|
|
||||||
|
|
||||||
import com.eu.habbo.Emulator;
|
|
||||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
|
||||||
import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.sql.Connection;
|
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
|
|
||||||
public class PrefixBlacklistCommand extends Command {
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(PrefixBlacklistCommand.class);
|
|
||||||
|
|
||||||
public PrefixBlacklistCommand() {
|
|
||||||
super("cmd_prefix_blacklist", Emulator.getTexts().getValue("commands.keys.cmd_prefix_blacklist").split(";"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean handle(GameClient gameClient, String[] params) throws Exception {
|
|
||||||
if (params.length < 2) {
|
|
||||||
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.usage"), RoomChatMessageBubbles.ALERT);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
String action = params[1].toLowerCase();
|
|
||||||
|
|
||||||
if (action.equals("list")) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.header")).append("\r");
|
|
||||||
|
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
|
||||||
PreparedStatement statement = connection.prepareStatement("SELECT word FROM custom_prefix_blacklist ORDER BY word")) {
|
|
||||||
try (ResultSet set = statement.executeQuery()) {
|
|
||||||
int count = 0;
|
|
||||||
while (set.next()) {
|
|
||||||
sb.append("- ").append(set.getString("word")).append("\r");
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
if (count == 0) {
|
|
||||||
sb.append(Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.empty"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (SQLException e) {
|
|
||||||
LOGGER.error("Error listing prefix blacklist", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
gameClient.getHabbo().whisper(sb.toString(), RoomChatMessageBubbles.ALERT);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.length < 3) {
|
|
||||||
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.usage"), RoomChatMessageBubbles.ALERT);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
String word = params[2].toLowerCase().trim();
|
|
||||||
|
|
||||||
if (word.isEmpty()) {
|
|
||||||
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.empty_word"), RoomChatMessageBubbles.ALERT);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.equals("add")) {
|
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
|
||||||
PreparedStatement statement = connection.prepareStatement("INSERT INTO custom_prefix_blacklist (word) VALUES (?)")) {
|
|
||||||
statement.setString(1, word);
|
|
||||||
statement.execute();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
LOGGER.error("Error adding prefix blacklist word", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
gameClient.getHabbo().whisper(
|
|
||||||
Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.added").replace("%word%", word),
|
|
||||||
RoomChatMessageBubbles.ALERT
|
|
||||||
);
|
|
||||||
} else if (action.equals("remove")) {
|
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
|
||||||
PreparedStatement statement = connection.prepareStatement("DELETE FROM custom_prefix_blacklist WHERE word = ?")) {
|
|
||||||
statement.setString(1, word);
|
|
||||||
statement.execute();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
LOGGER.error("Error removing prefix blacklist word", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
gameClient.getHabbo().whisper(
|
|
||||||
Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.removed").replace("%word%", word),
|
|
||||||
RoomChatMessageBubbles.ALERT
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.usage"), RoomChatMessageBubbles.ALERT);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+40
-1
@@ -2,7 +2,12 @@ package com.eu.habbo.habbohotel.commands;
|
|||||||
|
|
||||||
import com.eu.habbo.Emulator;
|
import com.eu.habbo.Emulator;
|
||||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.PermissionsManager;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.Rank;
|
||||||
import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles;
|
import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles;
|
||||||
|
import com.eu.habbo.habbohotel.users.Habbo;
|
||||||
|
import com.eu.habbo.habbohotel.users.HabboManager;
|
||||||
|
import com.eu.habbo.messages.outgoing.users.UserPermissionsComposer;
|
||||||
|
|
||||||
public class UpdatePermissionsCommand extends Command {
|
public class UpdatePermissionsCommand extends Command {
|
||||||
public UpdatePermissionsCommand() {
|
public UpdatePermissionsCommand() {
|
||||||
@@ -13,7 +18,41 @@ public class UpdatePermissionsCommand extends Command {
|
|||||||
public boolean handle(GameClient gameClient, String[] params) throws Exception {
|
public boolean handle(GameClient gameClient, String[] params) throws Exception {
|
||||||
Emulator.getGameEnvironment().getPermissionsManager().reload();
|
Emulator.getGameEnvironment().getPermissionsManager().reload();
|
||||||
|
|
||||||
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.succes.cmd_update_permissions"), RoomChatMessageBubbles.ALERT);
|
// PermissionsManager.reload() rebuilt the rank table — each online
|
||||||
|
// Habbo's HabboInfo still references the OLD Rank object, so
|
||||||
|
// server-side hasPermission() / wire composers would keep
|
||||||
|
// reporting stale data until relogin. Re-bind every connected
|
||||||
|
// user to the freshly-loaded Rank by id, then ship the new
|
||||||
|
// UserPermissionsComposer (which carries clubLevel,
|
||||||
|
// securityLevel, isAmbassador, rank metadata and the resolved
|
||||||
|
// permission_definitions map) so Nitro clients' React-side
|
||||||
|
// useHasPermission(key) / useUserRank() / useUserPermissions()
|
||||||
|
// consumers re-render against the updated tables without an F5.
|
||||||
|
HabboManager habboManager = Emulator.getGameEnvironment().getHabboManager();
|
||||||
|
PermissionsManager permissions = Emulator.getGameEnvironment().getPermissionsManager();
|
||||||
|
|
||||||
|
int refreshed = 0;
|
||||||
|
|
||||||
|
for (Habbo habbo : habboManager.getOnlineHabbos().values()) {
|
||||||
|
if (habbo == null || habbo.getHabboInfo() == null || habbo.getClient() == null) continue;
|
||||||
|
|
||||||
|
int currentRankId = habbo.getHabboInfo().getRank().getId();
|
||||||
|
// Defensive fallback: if the admin deleted the rank from the
|
||||||
|
// permission_ranks table between sessions, fall back to rank 1
|
||||||
|
// (Member) so the user isn't stranded with a null Rank.
|
||||||
|
Rank freshRank = permissions.rankExists(currentRankId)
|
||||||
|
? permissions.getRank(currentRankId)
|
||||||
|
: permissions.getRank(1);
|
||||||
|
|
||||||
|
habbo.getHabboInfo().setRank(freshRank);
|
||||||
|
habbo.getClient().sendResponse(new UserPermissionsComposer(habbo));
|
||||||
|
refreshed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameClient.getHabbo().whisper(
|
||||||
|
Emulator.getTexts().getValue("commands.succes.cmd_update_permissions") + " (" + refreshed + " online refreshed)",
|
||||||
|
RoomChatMessageBubbles.ALERT
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,10 +208,10 @@ public abstract class Game implements Runnable {
|
|||||||
this.state = GameState.IDLE;
|
this.state = GameState.IDLE;
|
||||||
|
|
||||||
boolean gamesActive = false;
|
boolean gamesActive = false;
|
||||||
for (HabboItem timer : room.getFloorItems()) {
|
for (InteractionGameTimer timer : room.getRoomSpecialTypes().getGameTimers().values()) {
|
||||||
if (timer instanceof InteractionGameTimer) {
|
if (timer.isRunning()) {
|
||||||
if (((InteractionGameTimer) timer).isRunning())
|
gamesActive = true;
|
||||||
gamesActive = true;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,49 +6,55 @@ import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
|||||||
public class GamePlayer {
|
public class GamePlayer {
|
||||||
|
|
||||||
private final Habbo habbo;
|
private final Habbo habbo;
|
||||||
|
|
||||||
|
|
||||||
private GameTeamColors teamColor;
|
private GameTeamColors teamColor;
|
||||||
|
|
||||||
|
|
||||||
private int score;
|
private int score;
|
||||||
private int wiredScore;
|
private int wiredScore;
|
||||||
|
|
||||||
|
|
||||||
public GamePlayer(Habbo habbo, GameTeamColors teamColor) {
|
public GamePlayer(Habbo habbo, GameTeamColors teamColor) {
|
||||||
this.habbo = habbo;
|
this.habbo = habbo;
|
||||||
this.teamColor = teamColor;
|
this.teamColor = teamColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
this.score = 0;
|
this.score = 0;
|
||||||
this.wiredScore = 0;
|
this.wiredScore = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void addScore(int amount) {
|
public void addScore(int amount) {
|
||||||
addScore(amount, false);
|
addScore(amount, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void addScore(int amount, boolean isWired) {
|
public void addScore(int amount, boolean isWired) {
|
||||||
if (habbo.getHabboInfo().getGamePlayer() != null && this.habbo.getHabboInfo().getCurrentGame() != null && this.habbo.getHabboInfo().getCurrentRoom().getGame(this.habbo.getHabboInfo().getCurrentGame()).getTeamForHabbo(this.habbo) != null) {
|
com.eu.habbo.habbohotel.rooms.Room roomToTrigger = null;
|
||||||
this.score += amount;
|
com.eu.habbo.habbohotel.rooms.RoomUnit roomUnitToTrigger = null;
|
||||||
|
int currentScore = 0;
|
||||||
|
|
||||||
if (this.score < 0) this.score = 0;
|
synchronized (this) {
|
||||||
|
if (this.habbo.getHabboInfo().getGamePlayer() != null && this.habbo.getHabboInfo().getCurrentGame() != null && this.habbo.getHabboInfo().getCurrentRoom().getGame(this.habbo.getHabboInfo().getCurrentGame()).getTeamForHabbo(this.habbo) != null) {
|
||||||
|
this.score += amount;
|
||||||
|
|
||||||
if(isWired) {
|
if (this.score < 0) this.score = 0;
|
||||||
this.wiredScore += amount;
|
|
||||||
|
|
||||||
if (this.wiredScore < 0) {
|
if (isWired) {
|
||||||
this.wiredScore = 0;
|
this.wiredScore += amount;
|
||||||
|
|
||||||
|
if (this.wiredScore < 0) {
|
||||||
|
this.wiredScore = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.wiredScore > this.score) {
|
||||||
|
this.wiredScore = this.score;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.wiredScore > this.score) {
|
roomToTrigger = this.habbo.getHabboInfo().getCurrentRoom();
|
||||||
this.wiredScore = this.score;
|
roomUnitToTrigger = this.habbo.getRoomUnit();
|
||||||
}
|
currentScore = this.score;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WiredManager.triggerScoreAchieved(this.habbo.getHabboInfo().getCurrentRoom(), this.habbo.getRoomUnit(), this.score, amount);
|
if (roomToTrigger != null && roomUnitToTrigger != null) {
|
||||||
|
WiredManager.triggerScoreAchieved(roomToTrigger, roomUnitToTrigger, currentScore, amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,12 +62,10 @@ public class GamePlayer {
|
|||||||
return this.habbo;
|
return this.habbo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public GameTeamColors getTeamColor() {
|
public GameTeamColors getTeamColor() {
|
||||||
return this.teamColor;
|
return this.teamColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public int getScore() {
|
public int getScore() {
|
||||||
return this.score;
|
return this.score;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,6 +252,25 @@ public class Guild implements Runnable {
|
|||||||
return this.readForum;
|
return this.readForum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean canHabboReadForum(int habboId, GuildMember member, boolean staff) {
|
||||||
|
if (staff || this.getOwnerId() == habboId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this.readForum) {
|
||||||
|
case EVERYONE:
|
||||||
|
return true;
|
||||||
|
case MEMBERS:
|
||||||
|
return member != null && member.getRank().type <= GuildRank.MEMBER.type;
|
||||||
|
case ADMINS:
|
||||||
|
return member != null && member.getRank().type < GuildRank.MEMBER.type;
|
||||||
|
case OWNER:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setReadForum(SettingsState readForum) {
|
public void setReadForum(SettingsState readForum) {
|
||||||
this.readForum = readForum;
|
this.readForum = readForum;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,12 @@ public class Item implements ISerialize {
|
|||||||
return item.getName().toLowerCase().startsWith("a0 pet");
|
return item.getName().toLowerCase().startsWith("a0 pet");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isBot(Item item) {
|
||||||
|
if (item == null) return false;
|
||||||
|
String name = item.getName();
|
||||||
|
return name != null && (name.startsWith("bot_") || name.startsWith("rentable_bot_"));
|
||||||
|
}
|
||||||
|
|
||||||
public static double getCurrentHeight(HabboItem item) {
|
public static double getCurrentHeight(HabboItem item) {
|
||||||
if (item instanceof InteractionMultiHeight && item.getBaseItem().getMultiHeights().length > 0) {
|
if (item instanceof InteractionMultiHeight && item.getBaseItem().getMultiHeights().length > 0) {
|
||||||
if (item.getExtradata().isEmpty()) {
|
if (item.getExtradata().isEmpty()) {
|
||||||
|
|||||||
+3
-1
@@ -89,7 +89,9 @@ public class InteractionOneWayGate extends HabboItem {
|
|||||||
Emulator.getThreading().run(new RoomUnitWalkToLocation(unit, tile, room, onFail, onFail));
|
Emulator.getThreading().run(new RoomUnitWalkToLocation(unit, tile, room, onFail, onFail));
|
||||||
|
|
||||||
Emulator.getThreading().run(() -> {
|
Emulator.getThreading().run(() -> {
|
||||||
WiredManager.triggerUserWalksOn(room, unit, this);
|
if (room.isLoaded()) {
|
||||||
|
WiredManager.triggerUserWalksOn(room, unit, this);
|
||||||
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+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 {
|
||||||
|
|||||||
+23
-15
@@ -93,23 +93,24 @@ public abstract class InteractionWired extends InteractionDefault {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (this.needsUpdate()) {
|
if (this.needsUpdate()) {
|
||||||
String wiredData = this.getWiredData();
|
String wiredDataRaw = this.getWiredData();
|
||||||
|
final String wiredData = (wiredDataRaw == null) ? "" : wiredDataRaw;
|
||||||
|
final int currentRoomId = this.getRoomId();
|
||||||
|
final int currentId = this.getId();
|
||||||
|
|
||||||
if (wiredData == null) {
|
Emulator.getThreading().run(() -> {
|
||||||
wiredData = "";
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE items SET wired_data = ? WHERE id = ?")) {
|
||||||
}
|
if (currentRoomId != 0) {
|
||||||
|
statement.setString(1, wiredData);
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE items SET wired_data = ? WHERE id = ?")) {
|
} else {
|
||||||
if (this.getRoomId() != 0) {
|
statement.setString(1, "");
|
||||||
statement.setString(1, wiredData);
|
}
|
||||||
} else {
|
statement.setInt(2, currentId);
|
||||||
statement.setString(1, "");
|
statement.execute();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Caught SQL exception", e);
|
||||||
}
|
}
|
||||||
statement.setInt(2, this.getId());
|
});
|
||||||
statement.execute();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
LOGGER.error("Caught SQL exception", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
super.run();
|
super.run();
|
||||||
}
|
}
|
||||||
@@ -216,6 +217,9 @@ public abstract class InteractionWired extends InteractionDefault {
|
|||||||
public static WiredSettings readSettings(ClientMessage packet, boolean isEffect)
|
public static WiredSettings readSettings(ClientMessage packet, boolean isEffect)
|
||||||
{
|
{
|
||||||
int intParamCount = packet.readInt();
|
int intParamCount = packet.readInt();
|
||||||
|
if (intParamCount < 0 || intParamCount > 100) {
|
||||||
|
throw new IllegalArgumentException("Invalid intParamCount: " + intParamCount);
|
||||||
|
}
|
||||||
int[] intParams = new int[intParamCount];
|
int[] intParams = new int[intParamCount];
|
||||||
|
|
||||||
for(int i = 0; i < intParamCount; i++)
|
for(int i = 0; i < intParamCount; i++)
|
||||||
@@ -226,6 +230,10 @@ public abstract class InteractionWired extends InteractionDefault {
|
|||||||
String stringParam = packet.readString();
|
String stringParam = packet.readString();
|
||||||
|
|
||||||
int itemCount = packet.readInt();
|
int itemCount = packet.readInt();
|
||||||
|
int selectionLimit = Emulator.getConfig() != null ? Emulator.getConfig().getInt("hotel.wired.furni.selection.count", 5) : 5;
|
||||||
|
if (itemCount < 0 || itemCount > selectionLimit * 20) {
|
||||||
|
throw new IllegalArgumentException("Invalid itemCount: " + itemCount + " exceeds maximum allowed limit");
|
||||||
|
}
|
||||||
int[] itemIds = new int[itemCount];
|
int[] itemIds = new int[itemCount];
|
||||||
|
|
||||||
for(int i = 0; i < itemCount; i++)
|
for(int i = 0; i < itemCount; i++)
|
||||||
|
|||||||
+20
-11
@@ -154,6 +154,7 @@ public class InteractionGameTimer extends HabboItem {
|
|||||||
@Override
|
@Override
|
||||||
public void onPickUp(Room room) {
|
public void onPickUp(Room room) {
|
||||||
this.endGame(room);
|
this.endGame(room);
|
||||||
|
this.threadActive = false;
|
||||||
|
|
||||||
this.timeNow = this.getInitialTimeValue();
|
this.timeNow = this.getInitialTimeValue();
|
||||||
this.setExtradata(this.timeNow + "\t" + this.baseTime);
|
this.setExtradata(this.timeNow + "\t" + this.baseTime);
|
||||||
@@ -220,8 +221,7 @@ public class InteractionGameTimer extends HabboItem {
|
|||||||
room.updateItem(this);
|
room.updateItem(this);
|
||||||
WiredManager.triggerGameStarts(room);
|
WiredManager.triggerGameStarts(room);
|
||||||
|
|
||||||
if (!this.threadActive) {
|
if (this.tryActivateTimerThread()) {
|
||||||
this.threadActive = true;
|
|
||||||
this.scheduleTimerRunnable(this.getTimerStartDelayMs());
|
this.scheduleTimerRunnable(this.getTimerStartDelayMs());
|
||||||
}
|
}
|
||||||
} else if (client != null) {
|
} else if (client != null) {
|
||||||
@@ -243,8 +243,7 @@ public class InteractionGameTimer extends HabboItem {
|
|||||||
} else {
|
} else {
|
||||||
this.unpause(room);
|
this.unpause(room);
|
||||||
|
|
||||||
if (!this.threadActive) {
|
if (this.tryActivateTimerThread()) {
|
||||||
this.threadActive = true;
|
|
||||||
this.scheduleTimerRunnable(this.getTimerResumeDelayMs());
|
this.scheduleTimerRunnable(this.getTimerResumeDelayMs());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,8 +256,7 @@ public class InteractionGameTimer extends HabboItem {
|
|||||||
this.createNewGame(room);
|
this.createNewGame(room);
|
||||||
WiredManager.triggerGameStarts(room);
|
WiredManager.triggerGameStarts(room);
|
||||||
|
|
||||||
if (!this.threadActive) {
|
if (this.tryActivateTimerThread()) {
|
||||||
this.threadActive = true;
|
|
||||||
this.scheduleTimerRunnable(this.getTimerStartDelayMs());
|
this.scheduleTimerRunnable(this.getTimerStartDelayMs());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -297,8 +295,7 @@ public class InteractionGameTimer extends HabboItem {
|
|||||||
}
|
}
|
||||||
this.createNewGame(room);
|
this.createNewGame(room);
|
||||||
WiredManager.triggerGameStarts(room);
|
WiredManager.triggerGameStarts(room);
|
||||||
if (!threadActive) {
|
if (this.tryActivateTimerThread()) {
|
||||||
threadActive = true;
|
|
||||||
this.scheduleTimerRunnable(this.getTimerStartDelayMs());
|
this.scheduleTimerRunnable(this.getTimerStartDelayMs());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -321,8 +318,7 @@ public class InteractionGameTimer extends HabboItem {
|
|||||||
this.isPaused = false;
|
this.isPaused = false;
|
||||||
this.unpause(room);
|
this.unpause(room);
|
||||||
|
|
||||||
if (!this.threadActive) {
|
if (this.tryActivateTimerThread()) {
|
||||||
this.threadActive = true;
|
|
||||||
this.scheduleTimerRunnable(this.getTimerResumeDelayMs());
|
this.scheduleTimerRunnable(this.getTimerResumeDelayMs());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -406,7 +402,9 @@ public class InteractionGameTimer extends HabboItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setThreadActive(boolean threadActive) {
|
public void setThreadActive(boolean threadActive) {
|
||||||
this.threadActive = threadActive;
|
synchronized (this) {
|
||||||
|
this.threadActive = threadActive;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPaused() {
|
public boolean isPaused() {
|
||||||
@@ -428,4 +426,15 @@ public class InteractionGameTimer extends HabboItem {
|
|||||||
public int getBaseTime() {
|
public int getBaseTime() {
|
||||||
return this.baseTime;
|
return this.baseTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean tryActivateTimerThread() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (this.threadActive) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.threadActive = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-14
@@ -20,12 +20,13 @@ import com.eu.habbo.messages.ServerMessage;
|
|||||||
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
|
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
|
||||||
import com.eu.habbo.messages.outgoing.generic.alerts.UpdateFailedComposer;
|
import com.eu.habbo.messages.outgoing.generic.alerts.UpdateFailedComposer;
|
||||||
import gnu.trove.procedure.TObjectProcedure;
|
import gnu.trove.procedure.TObjectProcedure;
|
||||||
import gnu.trove.set.hash.THashSet;
|
|
||||||
|
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
public class WiredEffectGiveReward extends InteractionWiredEffect {
|
public class WiredEffectGiveReward extends InteractionWiredEffect {
|
||||||
public static final int LIMIT_ONCE = 0;
|
public static final int LIMIT_ONCE = 0;
|
||||||
@@ -37,10 +38,10 @@ public class WiredEffectGiveReward extends InteractionWiredEffect {
|
|||||||
|
|
||||||
public int limit;
|
public int limit;
|
||||||
public int limitationInterval;
|
public int limitationInterval;
|
||||||
public int given;
|
public AtomicInteger given = new AtomicInteger(0);
|
||||||
public int rewardTime;
|
public int rewardTime;
|
||||||
public boolean uniqueRewards;
|
public boolean uniqueRewards;
|
||||||
public THashSet<WiredGiveRewardItem> rewardItems = new THashSet<>();
|
public List<WiredGiveRewardItem> rewardItems = new CopyOnWriteArrayList<>();
|
||||||
public int userSource = WiredSourceUtil.SOURCE_TRIGGER;
|
public int userSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||||
|
|
||||||
public WiredEffectGiveReward(ResultSet set, Item baseItem) throws SQLException {
|
public WiredEffectGiveReward(ResultSet set, Item baseItem) throws SQLException {
|
||||||
@@ -71,9 +72,8 @@ public class WiredEffectGiveReward extends InteractionWiredEffect {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getWiredData() {
|
public String getWiredData() {
|
||||||
|
|
||||||
ArrayList<WiredGiveRewardItem> rewards = new ArrayList<>(this.rewardItems);
|
ArrayList<WiredGiveRewardItem> rewards = new ArrayList<>(this.rewardItems);
|
||||||
return WiredManager.getGson().toJson(new JsonData(this.limit, this.given, this.rewardTime, this.uniqueRewards, this.limitationInterval, rewards, this.getDelay(), this.userSource));
|
return WiredManager.getGson().toJson(new JsonData(this.limit, this.given.get(), this.rewardTime, this.uniqueRewards, this.limitationInterval, rewards, this.getDelay(), this.userSource));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -84,7 +84,7 @@ public class WiredEffectGiveReward extends InteractionWiredEffect {
|
|||||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||||
this.setDelay(data.delay);
|
this.setDelay(data.delay);
|
||||||
this.limit = data.limit;
|
this.limit = data.limit;
|
||||||
this.given = data.given;
|
this.given.set(data.given);
|
||||||
this.rewardTime = data.reward_time;
|
this.rewardTime = data.reward_time;
|
||||||
this.uniqueRewards = data.unique_rewards;
|
this.uniqueRewards = data.unique_rewards;
|
||||||
this.limitationInterval = data.limit_interval;
|
this.limitationInterval = data.limit_interval;
|
||||||
@@ -96,7 +96,7 @@ public class WiredEffectGiveReward extends InteractionWiredEffect {
|
|||||||
String[] data = wiredData.split(":");
|
String[] data = wiredData.split(":");
|
||||||
if (data.length > 0) {
|
if (data.length > 0) {
|
||||||
this.limit = Integer.parseInt(data[0]);
|
this.limit = Integer.parseInt(data[0]);
|
||||||
this.given = Integer.parseInt(data[1]);
|
this.given.set(Integer.parseInt(data[1]));
|
||||||
this.rewardTime = Integer.parseInt(data[2]);
|
this.rewardTime = Integer.parseInt(data[2]);
|
||||||
this.uniqueRewards = data[3].equals("1");
|
this.uniqueRewards = data[3].equals("1");
|
||||||
this.limitationInterval = Integer.parseInt(data[4]);
|
this.limitationInterval = Integer.parseInt(data[4]);
|
||||||
@@ -127,7 +127,7 @@ public class WiredEffectGiveReward extends InteractionWiredEffect {
|
|||||||
public void onPickUp() {
|
public void onPickUp() {
|
||||||
this.limit = 0;
|
this.limit = 0;
|
||||||
this.limitationInterval = 0;
|
this.limitationInterval = 0;
|
||||||
this.given = 0;
|
this.given.set(0);
|
||||||
this.rewardTime = 0;
|
this.rewardTime = 0;
|
||||||
this.uniqueRewards = false;
|
this.uniqueRewards = false;
|
||||||
this.rewardItems.clear();
|
this.rewardItems.clear();
|
||||||
@@ -192,7 +192,7 @@ public class WiredEffectGiveReward extends InteractionWiredEffect {
|
|||||||
this.limit = settings.getIntParams()[2];
|
this.limit = settings.getIntParams()[2];
|
||||||
this.limitationInterval = settings.getIntParams()[3];
|
this.limitationInterval = settings.getIntParams()[3];
|
||||||
this.userSource = settings.getIntParams()[4];
|
this.userSource = settings.getIntParams()[4];
|
||||||
this.given = 0;
|
this.given.set(0);
|
||||||
|
|
||||||
String data = settings.getStringParam();
|
String data = settings.getStringParam();
|
||||||
|
|
||||||
@@ -276,15 +276,15 @@ public class WiredEffectGiveReward extends InteractionWiredEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getGiven() {
|
public int getGiven() {
|
||||||
return this.given;
|
return this.given.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGiven(int given) {
|
public void setGiven(int given) {
|
||||||
this.given = given;
|
this.given.set(given);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void incrementGiven() {
|
public void incrementGiven() {
|
||||||
this.given++;
|
this.given.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRewardTime() {
|
public int getRewardTime() {
|
||||||
@@ -303,11 +303,11 @@ public class WiredEffectGiveReward extends InteractionWiredEffect {
|
|||||||
this.uniqueRewards = uniqueRewards;
|
this.uniqueRewards = uniqueRewards;
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashSet<WiredGiveRewardItem> getRewardItems() {
|
public List<WiredGiveRewardItem> getRewardItems() {
|
||||||
return this.rewardItems;
|
return this.rewardItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRewardItems(THashSet<WiredGiveRewardItem> rewardItems) {
|
public void setRewardItems(List<WiredGiveRewardItem> rewardItems) {
|
||||||
this.rewardItems = rewardItems;
|
this.rewardItems = rewardItems;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-44
@@ -384,61 +384,69 @@ public final class WiredVariableReferenceSupport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void upsertSharedUserAssignment(int sourceRoomId, int sourceVariableItemId, int userId, SharedUserAssignment assignment) {
|
private static void upsertSharedUserAssignment(int sourceRoomId, int sourceVariableItemId, int userId, SharedUserAssignment assignment) {
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
Emulator.getThreading().run(() -> {
|
||||||
PreparedStatement statement = connection.prepareStatement("INSERT INTO room_user_wired_variables (room_id, user_id, variable_item_id, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = VALUES(updated_at)")) {
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
statement.setInt(1, sourceRoomId);
|
PreparedStatement statement = connection.prepareStatement("INSERT INTO room_user_wired_variables (room_id, user_id, variable_item_id, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = VALUES(updated_at)")) {
|
||||||
statement.setInt(2, userId);
|
statement.setInt(1, sourceRoomId);
|
||||||
statement.setInt(3, sourceVariableItemId);
|
statement.setInt(2, userId);
|
||||||
|
statement.setInt(3, sourceVariableItemId);
|
||||||
|
|
||||||
if (assignment.getValue() == null) {
|
if (assignment.getValue() == null) {
|
||||||
statement.setNull(4, java.sql.Types.INTEGER);
|
statement.setNull(4, java.sql.Types.INTEGER);
|
||||||
} else {
|
} else {
|
||||||
statement.setInt(4, assignment.getValue());
|
statement.setInt(4, assignment.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
statement.setInt(5, assignment.getCreatedAt());
|
||||||
|
statement.setInt(6, assignment.getUpdatedAt());
|
||||||
|
statement.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Failed to store shared wired user variable {} for room {} user {}", sourceVariableItemId, sourceRoomId, userId, e);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
statement.setInt(5, assignment.getCreatedAt());
|
|
||||||
statement.setInt(6, assignment.getUpdatedAt());
|
|
||||||
statement.executeUpdate();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
LOGGER.error("Failed to store shared wired user variable {} for room {} user {}", sourceVariableItemId, sourceRoomId, userId, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void deleteSharedUserAssignment(int sourceRoomId, int sourceVariableItemId, int userId) {
|
private static void deleteSharedUserAssignment(int sourceRoomId, int sourceVariableItemId, int userId) {
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
Emulator.getThreading().run(() -> {
|
||||||
PreparedStatement statement = connection.prepareStatement("DELETE FROM room_user_wired_variables WHERE room_id = ? AND user_id = ? AND variable_item_id = ?")) {
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
statement.setInt(1, sourceRoomId);
|
PreparedStatement statement = connection.prepareStatement("DELETE FROM room_user_wired_variables WHERE room_id = ? AND user_id = ? AND variable_item_id = ?")) {
|
||||||
statement.setInt(2, userId);
|
statement.setInt(1, sourceRoomId);
|
||||||
statement.setInt(3, sourceVariableItemId);
|
statement.setInt(2, userId);
|
||||||
statement.executeUpdate();
|
statement.setInt(3, sourceVariableItemId);
|
||||||
} catch (SQLException e) {
|
statement.executeUpdate();
|
||||||
LOGGER.error("Failed to delete shared wired user variable {} for room {} user {}", sourceVariableItemId, sourceRoomId, userId, e);
|
} catch (SQLException e) {
|
||||||
}
|
LOGGER.error("Failed to delete shared wired user variable {} for room {} user {}", sourceVariableItemId, sourceRoomId, userId, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void upsertSharedRoomAssignment(int sourceRoomId, int sourceVariableItemId, SharedRoomAssignment assignment) {
|
private static void upsertSharedRoomAssignment(int sourceRoomId, int sourceVariableItemId, SharedRoomAssignment assignment) {
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
Emulator.getThreading().run(() -> {
|
||||||
PreparedStatement statement = connection.prepareStatement("INSERT INTO room_wired_variables (room_id, variable_item_id, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = VALUES(updated_at)")) {
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
statement.setInt(1, sourceRoomId);
|
PreparedStatement statement = connection.prepareStatement("INSERT INTO room_wired_variables (room_id, variable_item_id, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = VALUES(updated_at)")) {
|
||||||
statement.setInt(2, sourceVariableItemId);
|
statement.setInt(1, sourceRoomId);
|
||||||
statement.setInt(3, assignment.getValue());
|
statement.setInt(2, sourceVariableItemId);
|
||||||
statement.setInt(4, 0);
|
statement.setInt(3, assignment.getValue());
|
||||||
statement.setInt(5, assignment.getUpdatedAt());
|
statement.setInt(4, 0);
|
||||||
statement.executeUpdate();
|
statement.setInt(5, assignment.getUpdatedAt());
|
||||||
} catch (SQLException e) {
|
statement.executeUpdate();
|
||||||
LOGGER.error("Failed to store shared wired room variable {} for room {}", sourceVariableItemId, sourceRoomId, e);
|
} catch (SQLException e) {
|
||||||
}
|
LOGGER.error("Failed to store shared wired room variable {} for room {}", sourceVariableItemId, sourceRoomId, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void deleteSharedRoomAssignment(int sourceRoomId, int sourceVariableItemId) {
|
private static void deleteSharedRoomAssignment(int sourceRoomId, int sourceVariableItemId) {
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
Emulator.getThreading().run(() -> {
|
||||||
PreparedStatement statement = connection.prepareStatement("DELETE FROM room_wired_variables WHERE room_id = ? AND variable_item_id = ?")) {
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
statement.setInt(1, sourceRoomId);
|
PreparedStatement statement = connection.prepareStatement("DELETE FROM room_wired_variables WHERE room_id = ? AND variable_item_id = ?")) {
|
||||||
statement.setInt(2, sourceVariableItemId);
|
statement.setInt(1, sourceRoomId);
|
||||||
statement.executeUpdate();
|
statement.setInt(2, sourceVariableItemId);
|
||||||
} catch (SQLException e) {
|
statement.executeUpdate();
|
||||||
LOGGER.error("Failed to delete shared wired room variable {} for room {}", sourceVariableItemId, sourceRoomId, e);
|
} catch (SQLException e) {
|
||||||
}
|
LOGGER.error("Failed to delete shared wired room variable {} for room {}", sourceVariableItemId, sourceRoomId, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String createDefinitionPrefix(int sourceRoomId, int sourceVariableItemId) {
|
private static String createDefinitionPrefix(int sourceRoomId, int sourceVariableItemId) {
|
||||||
|
|||||||
+5
-1
@@ -123,7 +123,11 @@ public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements
|
|||||||
@Override
|
@Override
|
||||||
public boolean saveData(WiredSettings settings) {
|
public boolean saveData(WiredSettings settings) {
|
||||||
if (settings.getIntParams().length < 1) return false;
|
if (settings.getIntParams().length < 1) return false;
|
||||||
this.repeatTime = settings.getIntParams()[0] * 5000;
|
int interval = settings.getIntParams()[0];
|
||||||
|
if (interval < 1) {
|
||||||
|
interval = 1;
|
||||||
|
}
|
||||||
|
this.repeatTime = interval * 5000;
|
||||||
// No accumulated time reset needed - using global tick count
|
// No accumulated time reset needed - using global tick count
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -651,6 +651,10 @@ public class ModToolManager {
|
|||||||
sender.getClient().sendResponse(new ModToolIssueHandledComposer(ModToolIssueHandledComposer.ABUSIVE));
|
sender.getClient().sendResponse(new ModToolIssueHandledComposer(ModToolIssueHandledComposer.ABUSIVE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reporter (the user who opened the CFH) gets their abusive
|
||||||
|
// counter bumped — the legacy stat shown in the User Info table.
|
||||||
|
bumpUserSettingCounter(issue.senderId, "cfh_abusive");
|
||||||
|
|
||||||
this.updateTicketToMods(issue);
|
this.updateTicketToMods(issue);
|
||||||
|
|
||||||
this.removeTicket(issue);
|
this.removeTicket(issue);
|
||||||
@@ -737,4 +741,38 @@ public class ModToolManager {
|
|||||||
|
|
||||||
return issues;
|
return issues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments a single integer counter on `users_settings` for the
|
||||||
|
* given user. Used by the moderation sanction handlers to bump the
|
||||||
|
* legacy counters that `ModToolUserInfoComposer` surfaces (cfh_warnings,
|
||||||
|
* cfh_bans, cfh_abusive, tradelock_amount) — historically these were
|
||||||
|
* only ever incremented by the CFH submission path, so a user could
|
||||||
|
* accumulate any number of bans/mutes without the User Info table
|
||||||
|
* reflecting it.
|
||||||
|
*
|
||||||
|
* Restricted to a whitelisted column name to keep the dynamic SQL
|
||||||
|
* safe; the caller passes a Permission-style constant.
|
||||||
|
*/
|
||||||
|
public static void bumpUserSettingCounter(int userId, String column) {
|
||||||
|
switch (column) {
|
||||||
|
case "cfh_warnings":
|
||||||
|
case "cfh_bans":
|
||||||
|
case "cfh_abusive":
|
||||||
|
case "tradelock_amount":
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOGGER.warn("Refusing to bump unrecognized user_settings column: {}", column);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement(
|
||||||
|
"UPDATE users_settings SET " + column + " = " + column + " + 1 WHERE user_id = ?")) {
|
||||||
|
statement.setInt(1, userId);
|
||||||
|
statement.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Caught SQL exception bumping {} for user {}", column, userId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ public class Permission {
|
|||||||
public static String ACC_SEE_WHISPERS = "acc_see_whispers";
|
public static String ACC_SEE_WHISPERS = "acc_see_whispers";
|
||||||
public static String ACC_SEE_TENTCHAT = "acc_see_tentchat";
|
public static String ACC_SEE_TENTCHAT = "acc_see_tentchat";
|
||||||
public static String ACC_SUPERWIRED = "acc_superwired";
|
public static String ACC_SUPERWIRED = "acc_superwired";
|
||||||
|
public static String ACC_HOUSEKEEPING = "acc_housekeeping";
|
||||||
public static String ACC_SUPPORTTOOL = "acc_supporttool";
|
public static String ACC_SUPPORTTOOL = "acc_supporttool";
|
||||||
public static String ACC_UNKICKABLE = "acc_unkickable";
|
public static String ACC_UNKICKABLE = "acc_unkickable";
|
||||||
public static String ACC_GUILDGATE = "acc_guildgate";
|
public static String ACC_GUILDGATE = "acc_guildgate";
|
||||||
|
|||||||
@@ -115,19 +115,21 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final Object roomUnitLock = new Object();
|
public final Object roomUnitLock = new Object();
|
||||||
public final ConcurrentHashMap<RoomTile, THashSet<HabboItem>> tileCache = new ConcurrentHashMap<>();
|
|
||||||
public final List<Integer> userVotes;
|
public final List<Integer> userVotes;
|
||||||
private final TIntArrayList rights;
|
private final TIntArrayList rights;
|
||||||
private final TIntIntHashMap mutedHabbos;
|
private final TIntIntHashMap mutedHabbos;
|
||||||
private final TIntObjectHashMap<RoomBan> bannedHabbos;
|
private final TIntObjectHashMap<RoomBan> bannedHabbos;
|
||||||
private final Set<Game> games;
|
private final Set<Game> games;
|
||||||
private final TIntObjectMap<RoomMoodlightData> moodlightData;
|
private final TIntObjectMap<RoomMoodlightData> moodlightData;
|
||||||
|
public volatile double lastCycleCpuMs = 0.0;
|
||||||
|
public volatile String lastCycleThread = "N/A";
|
||||||
|
|
||||||
private final Object loadLock = new Object();
|
private final Object loadLock = new Object();
|
||||||
//Use appropriately. Could potentially cause memory leaks when used incorrectly.
|
//Use appropriately. Could potentially cause memory leaks when used incorrectly.
|
||||||
public volatile boolean preventUnloading = false;
|
public volatile boolean preventUnloading = false;
|
||||||
public volatile boolean preventUncaching = false;
|
public volatile boolean preventUncaching = false;
|
||||||
public Set<ServerMessage> scheduledComposers = ConcurrentHashMap.newKeySet();
|
public Set<ServerMessage> scheduledComposers = ConcurrentHashMap.newKeySet();
|
||||||
public Set<Runnable> scheduledTasks = ConcurrentHashMap.newKeySet();
|
public final java.util.concurrent.ConcurrentLinkedQueue<Runnable> scheduledTasks = new java.util.concurrent.ConcurrentLinkedQueue<>();
|
||||||
public String wordQuiz = "";
|
public String wordQuiz = "";
|
||||||
public int noVotes = 0;
|
public int noVotes = 0;
|
||||||
public int yesVotes = 0;
|
public int yesVotes = 0;
|
||||||
@@ -195,6 +197,7 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
|||||||
private int wiredInspectMask = WIRED_ACCESS_DEFAULT_INSPECT_MASK;
|
private int wiredInspectMask = WIRED_ACCESS_DEFAULT_INSPECT_MASK;
|
||||||
private int wiredModifyMask = WIRED_ACCESS_DEFAULT_MODIFY_MASK;
|
private int wiredModifyMask = WIRED_ACCESS_DEFAULT_MODIFY_MASK;
|
||||||
private boolean youtubeEnabled = false;
|
private boolean youtubeEnabled = false;
|
||||||
|
private boolean soundboardEnabled = false;
|
||||||
private String youtubeCurrentVideo = "";
|
private String youtubeCurrentVideo = "";
|
||||||
private String youtubeSenderName = "";
|
private String youtubeSenderName = "";
|
||||||
private final java.util.List<String> youtubePlaylist = new java.util.concurrent.CopyOnWriteArrayList<>();
|
private final java.util.List<String> youtubePlaylist = new java.util.concurrent.CopyOnWriteArrayList<>();
|
||||||
@@ -202,6 +205,8 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
|||||||
|
|
||||||
public boolean isYoutubeEnabled() { return this.youtubeEnabled; }
|
public boolean isYoutubeEnabled() { return this.youtubeEnabled; }
|
||||||
public void setYoutubeEnabled(boolean enabled) { this.youtubeEnabled = enabled; }
|
public void setYoutubeEnabled(boolean enabled) { this.youtubeEnabled = enabled; }
|
||||||
|
public boolean isSoundboardEnabled() { return this.soundboardEnabled; }
|
||||||
|
public void setSoundboardEnabled(boolean enabled) { this.soundboardEnabled = enabled; }
|
||||||
public String getYoutubeCurrentVideo() { return this.youtubeCurrentVideo; }
|
public String getYoutubeCurrentVideo() { return this.youtubeCurrentVideo; }
|
||||||
public String getYoutubeSenderName() { return this.youtubeSenderName; }
|
public String getYoutubeSenderName() { return this.youtubeSenderName; }
|
||||||
public java.util.List<String> getYoutubePlaylist() { return this.youtubePlaylist; }
|
public java.util.List<String> getYoutubePlaylist() { return this.youtubePlaylist; }
|
||||||
@@ -248,6 +253,7 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
|||||||
this.allowWalkthrough = set.getBoolean("allow_walkthrough");
|
this.allowWalkthrough = set.getBoolean("allow_walkthrough");
|
||||||
this.hideWall = set.getBoolean("allow_hidewall");
|
this.hideWall = set.getBoolean("allow_hidewall");
|
||||||
try { this.youtubeEnabled = set.getBoolean("youtube_enabled"); } catch (Exception e) { this.youtubeEnabled = false; }
|
try { this.youtubeEnabled = set.getBoolean("youtube_enabled"); } catch (Exception e) { this.youtubeEnabled = false; }
|
||||||
|
try { this.soundboardEnabled = set.getBoolean("soundboard_enabled"); } catch (Exception e) { this.soundboardEnabled = false; }
|
||||||
this.chatMode = set.getInt("chat_mode");
|
this.chatMode = set.getInt("chat_mode");
|
||||||
this.chatWeight = set.getInt("chat_weight");
|
this.chatWeight = set.getInt("chat_weight");
|
||||||
this.chatSpeed = set.getInt("chat_speed");
|
this.chatSpeed = set.getInt("chat_speed");
|
||||||
@@ -981,8 +987,6 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
|||||||
this.scheduledTasks.clear();
|
this.scheduledTasks.clear();
|
||||||
this.scheduledComposers.clear();
|
this.scheduledComposers.clear();
|
||||||
|
|
||||||
this.tileCache.clear();
|
|
||||||
|
|
||||||
synchronized (this.mutedHabbos) {
|
synchronized (this.mutedHabbos) {
|
||||||
this.mutedHabbos.clear();
|
this.mutedHabbos.clear();
|
||||||
}
|
}
|
||||||
@@ -1160,10 +1164,13 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
|||||||
synchronized (this.loadLock) {
|
synchronized (this.loadLock) {
|
||||||
if (this.loaded) {
|
if (this.loaded) {
|
||||||
try {
|
try {
|
||||||
|
long startTime = System.nanoTime();
|
||||||
|
this.lastCycleThread = Thread.currentThread().getName();
|
||||||
// Run cycle directly instead of scheduling on thread pool
|
// Run cycle directly instead of scheduling on thread pool
|
||||||
// This ensures all cycle tasks in the same tick execute synchronously
|
// This ensures all cycle tasks in the same tick execute synchronously
|
||||||
// preventing wired desync issues
|
// preventing wired desync issues
|
||||||
this.cycle();
|
this.cycle();
|
||||||
|
this.lastCycleCpuMs = (System.nanoTime() - startTime) / 1000000.0;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.error("Caught exception", e);
|
LOGGER.error("Caught exception", e);
|
||||||
}
|
}
|
||||||
@@ -2320,27 +2327,37 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
|||||||
sanitizedInspectMask |= sanitizedModifyMask;
|
sanitizedInspectMask |= sanitizedModifyMask;
|
||||||
|
|
||||||
synchronized (this.wiredSettingsLock) {
|
synchronized (this.wiredSettingsLock) {
|
||||||
int previousInspectMask = this.wiredInspectMask;
|
final int finalInspectMask = sanitizedInspectMask;
|
||||||
int previousModifyMask = this.wiredModifyMask;
|
final int finalModifyMask = sanitizedModifyMask;
|
||||||
|
final int finalId = this.id;
|
||||||
|
final int previousInspectMask = this.wiredInspectMask;
|
||||||
|
final int previousModifyMask = this.wiredModifyMask;
|
||||||
|
|
||||||
this.wiredInspectMask = sanitizedInspectMask;
|
this.wiredInspectMask = sanitizedInspectMask;
|
||||||
this.wiredModifyMask = sanitizedModifyMask;
|
this.wiredModifyMask = sanitizedModifyMask;
|
||||||
this.wiredSettingsLoaded = true;
|
this.wiredSettingsLoaded = true;
|
||||||
|
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
Emulator.getThreading().run(() -> {
|
||||||
PreparedStatement statement = connection.prepareStatement(
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
"INSERT INTO room_wired_settings (room_id, inspect_mask, modify_mask) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE inspect_mask = VALUES(inspect_mask), modify_mask = VALUES(modify_mask)")) {
|
PreparedStatement statement = connection.prepareStatement(
|
||||||
statement.setInt(1, this.id);
|
"INSERT INTO room_wired_settings (room_id, inspect_mask, modify_mask) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE inspect_mask = VALUES(inspect_mask), modify_mask = VALUES(modify_mask)")) {
|
||||||
statement.setInt(2, sanitizedInspectMask);
|
statement.setInt(1, finalId);
|
||||||
statement.setInt(3, sanitizedModifyMask);
|
statement.setInt(2, finalInspectMask);
|
||||||
statement.executeUpdate();
|
statement.setInt(3, finalModifyMask);
|
||||||
this.pushWiredSettingsToCurrentHabbos();
|
statement.executeUpdate();
|
||||||
return true;
|
} catch (SQLException e) {
|
||||||
} catch (SQLException e) {
|
synchronized (this.wiredSettingsLock) {
|
||||||
this.wiredInspectMask = previousInspectMask;
|
if (this.wiredInspectMask == finalInspectMask && this.wiredModifyMask == finalModifyMask) {
|
||||||
this.wiredModifyMask = previousModifyMask;
|
this.wiredInspectMask = previousInspectMask;
|
||||||
LOGGER.error("Caught SQL exception while saving wired room settings", e);
|
this.wiredModifyMask = previousModifyMask;
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
LOGGER.error("Caught SQL exception while saving wired room settings", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.pushWiredSettingsToCurrentHabbos();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2878,4 +2895,20 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
|||||||
public Collection<RoomUnit> getRoomUnitsAt(RoomTile tile) {
|
public Collection<RoomUnit> getRoomUnitsAt(RoomTile tile) {
|
||||||
return this.unitManager.getRoomUnitsAt(tile);
|
return this.unitManager.getRoomUnitsAt(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getEstimatedMemoryUsage() {
|
||||||
|
long bytes = 1024 * 10; // Base footprint
|
||||||
|
if (this.itemManager != null) {
|
||||||
|
bytes += this.itemManager.itemCount() * 512L;
|
||||||
|
}
|
||||||
|
bytes += this.getUserCount() * 2048L;
|
||||||
|
if (this.layout != null) {
|
||||||
|
bytes += this.layout.getMapSize() * 128L;
|
||||||
|
}
|
||||||
|
com.eu.habbo.habbohotel.wired.tick.WiredTickService wired = com.eu.habbo.habbohotel.wired.tick.WiredTickService.getInstance();
|
||||||
|
if (wired != null) {
|
||||||
|
bytes += wired.getTickableCount(this.getId()) * 256L;
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -313,27 +313,6 @@ public class RoomChatManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String wiredSayMessage = roomChatMessage.getMessage();
|
|
||||||
|
|
||||||
// Handle commands and wired
|
|
||||||
boolean suppressSaysOutput = false;
|
|
||||||
if (chatType != RoomChatType.WHISPER) {
|
|
||||||
if (CommandHandler.handleCommand(habbo.getClient(), roomChatMessage.getUnfilteredMessage())) {
|
|
||||||
WiredManager.triggerUserSays(habbo.getHabboInfo().getCurrentRoom(), habbo.getRoomUnit(), wiredSayMessage);
|
|
||||||
roomChatMessage.isCommand = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ignoreWired) {
|
|
||||||
suppressSaysOutput = WiredManager.shouldSuppressUserSaysOutput(
|
|
||||||
habbo.getHabboInfo().getCurrentRoom(),
|
|
||||||
habbo.getRoomUnit(),
|
|
||||||
wiredSayMessage,
|
|
||||||
chatType.ordinal(),
|
|
||||||
roomChatMessage.getBubble() != null ? roomChatMessage.getBubble().getType() : -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flood protection
|
// Flood protection
|
||||||
if (!habbo.hasPermission(Permission.ACC_CHAT_NO_FLOOD)) {
|
if (!habbo.hasPermission(Permission.ACC_CHAT_NO_FLOOD)) {
|
||||||
final int chatCounter = habbo.getHabboStats().chatCounter.addAndGet(1);
|
final int chatCounter = habbo.getHabboStats().chatCounter.addAndGet(1);
|
||||||
@@ -357,6 +336,27 @@ public class RoomChatManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String wiredSayMessage = roomChatMessage.getMessage();
|
||||||
|
|
||||||
|
// Handle commands and wired
|
||||||
|
boolean suppressSaysOutput = false;
|
||||||
|
if (chatType != RoomChatType.WHISPER) {
|
||||||
|
if (CommandHandler.handleCommand(habbo.getClient(), roomChatMessage.getUnfilteredMessage())) {
|
||||||
|
WiredManager.triggerUserSays(habbo.getHabboInfo().getCurrentRoom(), habbo.getRoomUnit(), wiredSayMessage);
|
||||||
|
roomChatMessage.isCommand = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ignoreWired) {
|
||||||
|
suppressSaysOutput = WiredManager.shouldSuppressUserSaysOutput(
|
||||||
|
habbo.getHabboInfo().getCurrentRoom(),
|
||||||
|
habbo.getRoomUnit(),
|
||||||
|
wiredSayMessage,
|
||||||
|
chatType.ordinal(),
|
||||||
|
roomChatMessage.getBubble() != null ? roomChatMessage.getBubble().getType() : -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build prefix messages
|
// Build prefix messages
|
||||||
ServerMessage prefixMessage = null;
|
ServerMessage prefixMessage = null;
|
||||||
|
|
||||||
@@ -615,6 +615,9 @@ public class RoomChatManager {
|
|||||||
InteractionTalkingFurniture.class);
|
InteractionTalkingFurniture.class);
|
||||||
|
|
||||||
for (HabboItem item : items) {
|
for (HabboItem item : items) {
|
||||||
|
if (item.getExtradata().equals("1")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (this.room.getLayout().getTile(item.getX(), item.getY())
|
if (this.room.getLayout().getTile(item.getX(), item.getY())
|
||||||
.distance(habbo.getRoomUnit().getCurrentLocation()) <= Emulator.getConfig()
|
.distance(habbo.getRoomUnit().getCurrentLocation()) <= Emulator.getConfig()
|
||||||
.getInt("furniture.talking.range")) {
|
.getInt("furniture.talking.range")) {
|
||||||
|
|||||||
@@ -134,7 +134,18 @@ public class RoomChatMessageBubbles {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static RoomChatMessageBubbles getBubble(int id) {
|
public static RoomChatMessageBubbles getBubble(int id) {
|
||||||
return BUBBLES.getOrDefault(id, NORMAL);
|
RoomChatMessageBubbles bubble = BUBBLES.get(id);
|
||||||
|
if (bubble != null) return bubble;
|
||||||
|
|
||||||
|
// Custom chat bubbles (client-side only, e.g. ids 253+) are not registered
|
||||||
|
// above. Instead of falling back to NORMAL (which made them render as the
|
||||||
|
// default bubble), pass the id through so the server relays it as-is and
|
||||||
|
// the client renders its own .bubble-<id> style. Capped to avoid abuse.
|
||||||
|
if (id > 0 && id <= 1000) {
|
||||||
|
return new RoomChatMessageBubbles(id, "CUSTOM_" + id, "", true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NORMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void registerBubble(RoomChatMessageBubbles bubble) {
|
private static void registerBubble(RoomChatMessageBubbles bubble) {
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ public class RoomCycleManager {
|
|||||||
final boolean[] foundRightHolder = {false};
|
final boolean[] foundRightHolder = {false};
|
||||||
|
|
||||||
boolean loaded = this.room.isLoaded();
|
boolean loaded = this.room.isLoaded();
|
||||||
this.room.tileCache.clear();
|
|
||||||
|
|
||||||
if (loaded) {
|
if (loaded) {
|
||||||
processScheduledTasks();
|
processScheduledTasks();
|
||||||
@@ -164,13 +163,9 @@ public class RoomCycleManager {
|
|||||||
* Processes scheduled tasks.
|
* Processes scheduled tasks.
|
||||||
*/
|
*/
|
||||||
private void processScheduledTasks() {
|
private void processScheduledTasks() {
|
||||||
if (!this.room.scheduledTasks.isEmpty()) {
|
Runnable task;
|
||||||
Set<Runnable> tasks = this.room.scheduledTasks;
|
while ((task = this.room.scheduledTasks.poll()) != null) {
|
||||||
this.room.scheduledTasks = ConcurrentHashMap.newKeySet();
|
Emulator.getThreading().run(task);
|
||||||
|
|
||||||
for (Runnable runnable : tasks) {
|
|
||||||
Emulator.getThreading().run(runnable);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,7 +481,7 @@ public class RoomCycleManager {
|
|||||||
if (!unit.hasStatus(RoomUnitStatus.LAY)) {
|
if (!unit.hasStatus(RoomUnitStatus.LAY)) {
|
||||||
BedProfile bedProfile = new BedProfile(topItem);
|
BedProfile bedProfile = new BedProfile(topItem);
|
||||||
double layHeight = Item.getCurrentHeight(topItem) * 1.0D + bedProfile.getLayZOffset();
|
double layHeight = Item.getCurrentHeight(topItem) * 1.0D + bedProfile.getLayZOffset();
|
||||||
LOGGER.info("[BedProfile] item={} stackHeight={} isFlat={} isDouble={} X={} Y={} Z={}",
|
LOGGER.debug("[BedProfile] item={} stackHeight={} isFlat={} isDouble={} X={} Y={} Z={}",
|
||||||
topItem.getBaseItem().getName(), topItem.getBaseItem().getHeight(),
|
topItem.getBaseItem().getName(), topItem.getBaseItem().getHeight(),
|
||||||
bedProfile.isFlat(), bedProfile.isDouble(),
|
bedProfile.isFlat(), bedProfile.isDouble(),
|
||||||
bedProfile.getLayXOffset(), bedProfile.getLayYOffset(), bedProfile.getLayZOffset());
|
bedProfile.getLayXOffset(), bedProfile.getLayYOffset(), bedProfile.getLayZOffset());
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public class RoomFurniVariableManager {
|
|||||||
private final Room room;
|
private final Room room;
|
||||||
private final ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, VariableAssignment>> activeAssignmentsByFurniId;
|
private final ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, VariableAssignment>> activeAssignmentsByFurniId;
|
||||||
private volatile boolean permanentAssignmentsLoaded;
|
private volatile boolean permanentAssignmentsLoaded;
|
||||||
|
private final java.util.concurrent.atomic.AtomicBoolean broadcastRequested = new java.util.concurrent.atomic.AtomicBoolean(false);
|
||||||
|
|
||||||
public RoomFurniVariableManager(Room room) {
|
public RoomFurniVariableManager(Room room) {
|
||||||
this.room = room;
|
this.room = room;
|
||||||
@@ -591,7 +592,22 @@ public class RoomFurniVariableManager {
|
|||||||
habbo.getClient().sendResponse(new WiredUserVariablesDataComposer(this.room.getUserVariableManager().createSnapshot(), this.createSnapshot(), this.room.getRoomVariableManager().createSnapshot()));
|
habbo.getClient().sendResponse(new WiredUserVariablesDataComposer(this.room.getUserVariableManager().createSnapshot(), this.createSnapshot(), this.room.getRoomVariableManager().createSnapshot()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void requestBroadcast() {
|
||||||
|
if (this.broadcastRequested.compareAndSet(false, true)) {
|
||||||
|
Emulator.getThreading().run(() -> {
|
||||||
|
this.broadcastRequested.set(false);
|
||||||
|
if (this.room.isLoaded()) {
|
||||||
|
this.broadcastSnapshotRaw();
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void broadcastSnapshot() {
|
public void broadcastSnapshot() {
|
||||||
|
this.requestBroadcast();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void broadcastSnapshotRaw() {
|
||||||
RoomUserVariableManager.Snapshot userSnapshot = this.room.getUserVariableManager().createSnapshot();
|
RoomUserVariableManager.Snapshot userSnapshot = this.room.getUserVariableManager().createSnapshot();
|
||||||
Snapshot furniSnapshot = this.createSnapshot();
|
Snapshot furniSnapshot = this.createSnapshot();
|
||||||
RoomVariableManager.Snapshot roomSnapshot = this.room.getRoomVariableManager().createSnapshot();
|
RoomVariableManager.Snapshot roomSnapshot = this.room.getRoomVariableManager().createSnapshot();
|
||||||
|
|||||||
@@ -148,55 +148,8 @@ public class RoomItemManager {
|
|||||||
item = this.roomItems.get(id);
|
item = this.roomItems.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check special types if not found in main storage
|
|
||||||
RoomSpecialTypes specialTypes = this.room.getRoomSpecialTypes();
|
|
||||||
|
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
item = specialTypes.getBanzaiTeleporter(id);
|
item = this.room.getRoomSpecialTypes().getSpecialItem(id);
|
||||||
}
|
|
||||||
|
|
||||||
if (item == null) {
|
|
||||||
item = specialTypes.getTrigger(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item == null) {
|
|
||||||
item = specialTypes.getEffect(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item == null) {
|
|
||||||
item = specialTypes.getCondition(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item == null) {
|
|
||||||
item = specialTypes.getGameGate(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item == null) {
|
|
||||||
item = specialTypes.getGameScorebord(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item == null) {
|
|
||||||
item = specialTypes.getGameTimer(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item == null) {
|
|
||||||
item = specialTypes.getFreezeExitTiles().get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item == null) {
|
|
||||||
item = specialTypes.getRoller(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item == null) {
|
|
||||||
item = specialTypes.getNest(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item == null) {
|
|
||||||
item = specialTypes.getPetDrink(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item == null) {
|
|
||||||
item = specialTypes.getPetFood(id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
@@ -726,7 +679,7 @@ public class RoomItemManager {
|
|||||||
item instanceof WiredBlob ||
|
item instanceof WiredBlob ||
|
||||||
item instanceof InteractionTent ||
|
item instanceof InteractionTent ||
|
||||||
item instanceof InteractionSnowboardSlope ||
|
item instanceof InteractionSnowboardSlope ||
|
||||||
item instanceof InteractionFireworks) {
|
item instanceof InteractionFireworks || item instanceof InteractionVoteCounter) {
|
||||||
specialTypes.addUndefined(item);
|
specialTypes.addUndefined(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -899,7 +852,7 @@ public class RoomItemManager {
|
|||||||
item instanceof InteractionStickyPole ||
|
item instanceof InteractionStickyPole ||
|
||||||
item instanceof WiredBlob ||
|
item instanceof WiredBlob ||
|
||||||
item instanceof InteractionTent ||
|
item instanceof InteractionTent ||
|
||||||
item instanceof InteractionSnowboardSlope) {
|
item instanceof InteractionSnowboardSlope || item instanceof InteractionVoteCounter) {
|
||||||
specialTypes.removeUndefined(item);
|
specialTypes.removeUndefined(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1020,6 +1020,10 @@ public class RoomManager {
|
|||||||
room.getYoutubeWatchers()).compose());
|
room.getYoutubeWatchers()).compose());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
habbo.getClient().sendResponse(new com.eu.habbo.messages.outgoing.soundboard.SoundboardSettingsComposer(
|
||||||
|
room.isSoundboardEnabled(),
|
||||||
|
Emulator.getGameEnvironment().getSoundboardManager().getSounds()).compose());
|
||||||
|
|
||||||
WiredManager.triggerUserEntersRoom(room, habbo.getRoomUnit());
|
WiredManager.triggerUserEntersRoom(room, habbo.getRoomUnit());
|
||||||
room.habboEntered(habbo);
|
room.habboEntered(habbo);
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ public class RoomSpecialTypes {
|
|||||||
private final THashMap<Integer, InteractionFreezeExitTile> freezeExitTile;
|
private final THashMap<Integer, InteractionFreezeExitTile> freezeExitTile;
|
||||||
private final THashMap<Integer, HabboItem> undefined;
|
private final THashMap<Integer, HabboItem> undefined;
|
||||||
private final Set<ICycleable> cycleTasks;
|
private final Set<ICycleable> cycleTasks;
|
||||||
|
private final ConcurrentHashMap<Integer, HabboItem> specialItemsById = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public RoomSpecialTypes() {
|
public RoomSpecialTypes() {
|
||||||
this.banzaiTeleporters = new THashMap<>(0);
|
this.banzaiTeleporters = new THashMap<>(0);
|
||||||
@@ -115,11 +116,11 @@ public class RoomSpecialTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addBanzaiTeleporter(InteractionBattleBanzaiTeleporter item) {
|
public void addBanzaiTeleporter(InteractionBattleBanzaiTeleporter item) {
|
||||||
this.banzaiTeleporters.put(item.getId(), item);
|
this.banzaiTeleporters.put(item.getId(), item); this.specialItemsById.put(item.getId(), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeBanzaiTeleporter(InteractionBattleBanzaiTeleporter item) {
|
public void removeBanzaiTeleporter(InteractionBattleBanzaiTeleporter item) {
|
||||||
this.banzaiTeleporters.remove(item.getId());
|
this.banzaiTeleporters.remove(item.getId()); this.specialItemsById.remove(item.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashSet<InteractionBattleBanzaiTeleporter> getBanzaiTeleporters() {
|
public THashSet<InteractionBattleBanzaiTeleporter> getBanzaiTeleporters() {
|
||||||
@@ -155,11 +156,11 @@ public class RoomSpecialTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addNest(InteractionNest item) {
|
public void addNest(InteractionNest item) {
|
||||||
this.nests.put(item.getId(), item);
|
this.nests.put(item.getId(), item); this.specialItemsById.put(item.getId(), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeNest(InteractionNest item) {
|
public void removeNest(InteractionNest item) {
|
||||||
this.nests.remove(item.getId());
|
this.nests.remove(item.getId()); this.specialItemsById.remove(item.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashSet<InteractionNest> getNests() {
|
public THashSet<InteractionNest> getNests() {
|
||||||
@@ -177,11 +178,11 @@ public class RoomSpecialTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addPetDrink(InteractionPetDrink item) {
|
public void addPetDrink(InteractionPetDrink item) {
|
||||||
this.petDrinks.put(item.getId(), item);
|
this.petDrinks.put(item.getId(), item); this.specialItemsById.put(item.getId(), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePetDrink(InteractionPetDrink item) {
|
public void removePetDrink(InteractionPetDrink item) {
|
||||||
this.petDrinks.remove(item.getId());
|
this.petDrinks.remove(item.getId()); this.specialItemsById.remove(item.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashSet<InteractionPetDrink> getPetDrinks() {
|
public THashSet<InteractionPetDrink> getPetDrinks() {
|
||||||
@@ -199,11 +200,11 @@ public class RoomSpecialTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addPetFood(InteractionPetFood item) {
|
public void addPetFood(InteractionPetFood item) {
|
||||||
this.petFoods.put(item.getId(), item);
|
this.petFoods.put(item.getId(), item); this.specialItemsById.put(item.getId(), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePetFood(InteractionPetFood petFood) {
|
public void removePetFood(InteractionPetFood petFood) {
|
||||||
this.petFoods.remove(petFood.getId());
|
this.petFoods.remove(petFood.getId()); this.specialItemsById.remove(petFood.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashSet<InteractionPetFood> getPetFoods() {
|
public THashSet<InteractionPetFood> getPetFoods() {
|
||||||
@@ -221,11 +222,11 @@ public class RoomSpecialTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addPetToy(InteractionPetToy item) {
|
public void addPetToy(InteractionPetToy item) {
|
||||||
this.petToys.put(item.getId(), item);
|
this.petToys.put(item.getId(), item); this.specialItemsById.put(item.getId(), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePetToy(InteractionPetToy petToy) {
|
public void removePetToy(InteractionPetToy petToy) {
|
||||||
this.petToys.remove(petToy.getId());
|
this.petToys.remove(petToy.getId()); this.specialItemsById.remove(petToy.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashSet<InteractionPetToy> getPetToys() {
|
public THashSet<InteractionPetToy> getPetToys() {
|
||||||
@@ -243,11 +244,11 @@ public class RoomSpecialTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addPetTree(InteractionPetTree item) {
|
public void addPetTree(InteractionPetTree item) {
|
||||||
this.petTrees.put(item.getId(), item);
|
this.petTrees.put(item.getId(), item); this.specialItemsById.put(item.getId(), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePetTree(InteractionPetTree petTree) {
|
public void removePetTree(InteractionPetTree petTree) {
|
||||||
this.petTrees.remove(petTree.getId());
|
this.petTrees.remove(petTree.getId()); this.specialItemsById.remove(petTree.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashSet<InteractionPetTree> getPetTrees() {
|
public THashSet<InteractionPetTree> getPetTrees() {
|
||||||
@@ -270,12 +271,14 @@ public class RoomSpecialTypes {
|
|||||||
synchronized (this.rollers) {
|
synchronized (this.rollers) {
|
||||||
this.rollers.put(item.getId(), item);
|
this.rollers.put(item.getId(), item);
|
||||||
}
|
}
|
||||||
|
this.specialItemsById.put(item.getId(), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeRoller(InteractionRoller roller) {
|
public void removeRoller(InteractionRoller roller) {
|
||||||
synchronized (this.rollers) {
|
synchronized (this.rollers) {
|
||||||
this.rollers.remove(roller.getId());
|
this.rollers.remove(roller.getId());
|
||||||
}
|
}
|
||||||
|
this.specialItemsById.remove(roller.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashMap<Integer, InteractionRoller> getRollers() {
|
public THashMap<Integer, InteractionRoller> getRollers() {
|
||||||
@@ -469,11 +472,11 @@ public class RoomSpecialTypes {
|
|||||||
// Add to type-based index
|
// Add to type-based index
|
||||||
this.wiredTriggers.computeIfAbsent(trigger.getType(), k -> ConcurrentHashMap.newKeySet())
|
this.wiredTriggers.computeIfAbsent(trigger.getType(), k -> ConcurrentHashMap.newKeySet())
|
||||||
.add(trigger);
|
.add(trigger);
|
||||||
|
|
||||||
// Add to spatial index
|
// Add to spatial index
|
||||||
long key = coordinateKey(trigger.getX(), trigger.getY());
|
long key = coordinateKey(trigger.getX(), trigger.getY());
|
||||||
this.wiredTriggersByLocation.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet())
|
this.wiredTriggersByLocation.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet())
|
||||||
.add(trigger);
|
.add(trigger);
|
||||||
|
this.specialItemsById.put(trigger.getId(), trigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -489,7 +492,6 @@ public class RoomSpecialTypes {
|
|||||||
this.wiredTriggers.remove(trigger.getType());
|
this.wiredTriggers.remove(trigger.getType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove from spatial index
|
// Remove from spatial index
|
||||||
long key = coordinateKey(trigger.getX(), trigger.getY());
|
long key = coordinateKey(trigger.getX(), trigger.getY());
|
||||||
Set<InteractionWiredTrigger> locationTriggers = this.wiredTriggersByLocation.get(key);
|
Set<InteractionWiredTrigger> locationTriggers = this.wiredTriggersByLocation.get(key);
|
||||||
@@ -499,6 +501,7 @@ public class RoomSpecialTypes {
|
|||||||
this.wiredTriggersByLocation.remove(key);
|
this.wiredTriggersByLocation.remove(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.specialItemsById.remove(trigger.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -589,11 +592,11 @@ public class RoomSpecialTypes {
|
|||||||
// Add to type-based index
|
// Add to type-based index
|
||||||
this.wiredEffects.computeIfAbsent(effect.getType(), k -> ConcurrentHashMap.newKeySet())
|
this.wiredEffects.computeIfAbsent(effect.getType(), k -> ConcurrentHashMap.newKeySet())
|
||||||
.add(effect);
|
.add(effect);
|
||||||
|
|
||||||
// Add to spatial index
|
// Add to spatial index
|
||||||
long key = coordinateKey(effect.getX(), effect.getY());
|
long key = coordinateKey(effect.getX(), effect.getY());
|
||||||
this.wiredEffectsByLocation.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet())
|
this.wiredEffectsByLocation.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet())
|
||||||
.add(effect);
|
.add(effect);
|
||||||
|
this.specialItemsById.put(effect.getId(), effect);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -609,7 +612,6 @@ public class RoomSpecialTypes {
|
|||||||
this.wiredEffects.remove(effect.getType());
|
this.wiredEffects.remove(effect.getType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove from spatial index
|
// Remove from spatial index
|
||||||
long key = coordinateKey(effect.getX(), effect.getY());
|
long key = coordinateKey(effect.getX(), effect.getY());
|
||||||
Set<InteractionWiredEffect> locationEffects = this.wiredEffectsByLocation.get(key);
|
Set<InteractionWiredEffect> locationEffects = this.wiredEffectsByLocation.get(key);
|
||||||
@@ -619,6 +621,7 @@ public class RoomSpecialTypes {
|
|||||||
this.wiredEffectsByLocation.remove(key);
|
this.wiredEffectsByLocation.remove(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.specialItemsById.remove(effect.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -709,11 +712,11 @@ public class RoomSpecialTypes {
|
|||||||
// Add to type-based index
|
// Add to type-based index
|
||||||
this.wiredConditions.computeIfAbsent(condition.getType(), k -> ConcurrentHashMap.newKeySet())
|
this.wiredConditions.computeIfAbsent(condition.getType(), k -> ConcurrentHashMap.newKeySet())
|
||||||
.add(condition);
|
.add(condition);
|
||||||
|
|
||||||
// Add to spatial index
|
// Add to spatial index
|
||||||
long key = coordinateKey(condition.getX(), condition.getY());
|
long key = coordinateKey(condition.getX(), condition.getY());
|
||||||
this.wiredConditionsByLocation.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet())
|
this.wiredConditionsByLocation.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet())
|
||||||
.add(condition);
|
.add(condition);
|
||||||
|
this.specialItemsById.put(condition.getId(), condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -729,7 +732,6 @@ public class RoomSpecialTypes {
|
|||||||
this.wiredConditions.remove(condition.getType());
|
this.wiredConditions.remove(condition.getType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove from spatial index
|
// Remove from spatial index
|
||||||
long key = coordinateKey(condition.getX(), condition.getY());
|
long key = coordinateKey(condition.getX(), condition.getY());
|
||||||
Set<InteractionWiredCondition> locationConditions = this.wiredConditionsByLocation.get(key);
|
Set<InteractionWiredCondition> locationConditions = this.wiredConditionsByLocation.get(key);
|
||||||
@@ -739,6 +741,7 @@ public class RoomSpecialTypes {
|
|||||||
this.wiredConditionsByLocation.remove(key);
|
this.wiredConditionsByLocation.remove(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.specialItemsById.remove(condition.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -805,11 +808,11 @@ public class RoomSpecialTypes {
|
|||||||
*/
|
*/
|
||||||
public void addExtra(InteractionWiredExtra extra) {
|
public void addExtra(InteractionWiredExtra extra) {
|
||||||
this.wiredExtras.put(extra.getId(), extra);
|
this.wiredExtras.put(extra.getId(), extra);
|
||||||
|
|
||||||
// Add to spatial index
|
// Add to spatial index
|
||||||
long key = coordinateKey(extra.getX(), extra.getY());
|
long key = coordinateKey(extra.getX(), extra.getY());
|
||||||
this.wiredExtrasByLocation.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet())
|
this.wiredExtrasByLocation.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet())
|
||||||
.add(extra);
|
.add(extra);
|
||||||
|
this.specialItemsById.put(extra.getId(), extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -818,7 +821,6 @@ public class RoomSpecialTypes {
|
|||||||
*/
|
*/
|
||||||
public void removeExtra(InteractionWiredExtra extra) {
|
public void removeExtra(InteractionWiredExtra extra) {
|
||||||
this.wiredExtras.remove(extra.getId());
|
this.wiredExtras.remove(extra.getId());
|
||||||
|
|
||||||
// Remove from spatial index
|
// Remove from spatial index
|
||||||
long key = coordinateKey(extra.getX(), extra.getY());
|
long key = coordinateKey(extra.getX(), extra.getY());
|
||||||
Set<InteractionWiredExtra> locationExtras = this.wiredExtrasByLocation.get(key);
|
Set<InteractionWiredExtra> locationExtras = this.wiredExtrasByLocation.get(key);
|
||||||
@@ -828,6 +830,7 @@ public class RoomSpecialTypes {
|
|||||||
this.wiredExtrasByLocation.remove(key);
|
this.wiredExtrasByLocation.remove(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.specialItemsById.remove(extra.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -880,11 +883,11 @@ public class RoomSpecialTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addGameScoreboard(InteractionGameScoreboard scoreboard) {
|
public void addGameScoreboard(InteractionGameScoreboard scoreboard) {
|
||||||
this.gameScoreboards.put(scoreboard.getId(), scoreboard);
|
this.gameScoreboards.put(scoreboard.getId(), scoreboard); this.specialItemsById.put(scoreboard.getId(), scoreboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeScoreboard(InteractionGameScoreboard scoreboard) {
|
public void removeScoreboard(InteractionGameScoreboard scoreboard) {
|
||||||
this.gameScoreboards.remove(scoreboard.getId());
|
this.gameScoreboards.remove(scoreboard.getId()); this.specialItemsById.remove(scoreboard.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashMap<Integer, InteractionFreezeScoreboard> getFreezeScoreboards() {
|
public THashMap<Integer, InteractionFreezeScoreboard> getFreezeScoreboards() {
|
||||||
@@ -980,11 +983,11 @@ public class RoomSpecialTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addGameGate(InteractionGameGate gameGate) {
|
public void addGameGate(InteractionGameGate gameGate) {
|
||||||
this.gameGates.put(gameGate.getId(), gameGate);
|
this.gameGates.put(gameGate.getId(), gameGate); this.specialItemsById.put(gameGate.getId(), gameGate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeGameGate(InteractionGameGate gameGate) {
|
public void removeGameGate(InteractionGameGate gameGate) {
|
||||||
this.gameGates.remove(gameGate.getId());
|
this.gameGates.remove(gameGate.getId()); this.specialItemsById.remove(gameGate.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashMap<Integer, InteractionFreezeGate> getFreezeGates() {
|
public THashMap<Integer, InteractionFreezeGate> getFreezeGates() {
|
||||||
@@ -1021,11 +1024,11 @@ public class RoomSpecialTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addGameTimer(InteractionGameTimer gameTimer) {
|
public void addGameTimer(InteractionGameTimer gameTimer) {
|
||||||
this.gameTimers.put(gameTimer.getId(), gameTimer);
|
this.gameTimers.put(gameTimer.getId(), gameTimer); this.specialItemsById.put(gameTimer.getId(), gameTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeGameTimer(InteractionGameTimer gameTimer) {
|
public void removeGameTimer(InteractionGameTimer gameTimer) {
|
||||||
this.gameTimers.remove(gameTimer.getId());
|
this.gameTimers.remove(gameTimer.getId()); this.specialItemsById.remove(gameTimer.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashMap<Integer, InteractionGameTimer> getGameTimers() {
|
public THashMap<Integer, InteractionGameTimer> getGameTimers() {
|
||||||
@@ -1043,7 +1046,7 @@ public class RoomSpecialTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addFreezeExitTile(InteractionFreezeExitTile freezeExitTile) {
|
public void addFreezeExitTile(InteractionFreezeExitTile freezeExitTile) {
|
||||||
this.freezeExitTile.put(freezeExitTile.getId(), freezeExitTile);
|
this.freezeExitTile.put(freezeExitTile.getId(), freezeExitTile); this.specialItemsById.put(freezeExitTile.getId(), freezeExitTile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashMap<Integer, InteractionFreezeExitTile> getFreezeExitTiles() {
|
public THashMap<Integer, InteractionFreezeExitTile> getFreezeExitTiles() {
|
||||||
@@ -1051,7 +1054,7 @@ public class RoomSpecialTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void removeFreezeExitTile(InteractionFreezeExitTile freezeExitTile) {
|
public void removeFreezeExitTile(InteractionFreezeExitTile freezeExitTile) {
|
||||||
this.freezeExitTile.remove(freezeExitTile.getId());
|
this.freezeExitTile.remove(freezeExitTile.getId()); this.specialItemsById.remove(freezeExitTile.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasFreezeExitTile() {
|
public boolean hasFreezeExitTile() {
|
||||||
@@ -1062,12 +1065,14 @@ public class RoomSpecialTypes {
|
|||||||
synchronized (this.undefined) {
|
synchronized (this.undefined) {
|
||||||
this.undefined.put(item.getId(), item);
|
this.undefined.put(item.getId(), item);
|
||||||
}
|
}
|
||||||
|
this.specialItemsById.put(item.getId(), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeUndefined(HabboItem item) {
|
public void removeUndefined(HabboItem item) {
|
||||||
synchronized (this.undefined) {
|
synchronized (this.undefined) {
|
||||||
this.undefined.remove(item.getId());
|
this.undefined.remove(item.getId());
|
||||||
}
|
}
|
||||||
|
this.specialItemsById.remove(item.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashSet<HabboItem> getItemsOfType(Class<? extends HabboItem> type) {
|
public THashSet<HabboItem> getItemsOfType(Class<? extends HabboItem> type) {
|
||||||
@@ -1130,6 +1135,10 @@ public class RoomSpecialTypes {
|
|||||||
this.cycleTasks.remove(task);
|
this.cycleTasks.remove(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HabboItem getSpecialItem(int itemId) {
|
||||||
|
return this.specialItemsById.get(itemId);
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized void dispose() {
|
public synchronized void dispose() {
|
||||||
this.banzaiTeleporters.clear();
|
this.banzaiTeleporters.clear();
|
||||||
this.nests.clear();
|
this.nests.clear();
|
||||||
@@ -1142,6 +1151,7 @@ public class RoomSpecialTypes {
|
|||||||
this.wiredTriggers.clear();
|
this.wiredTriggers.clear();
|
||||||
this.wiredEffects.clear();
|
this.wiredEffects.clear();
|
||||||
this.wiredConditions.clear();
|
this.wiredConditions.clear();
|
||||||
|
this.wiredExtras.clear();
|
||||||
|
|
||||||
this.gameScoreboards.clear();
|
this.gameScoreboards.clear();
|
||||||
this.gameGates.clear();
|
this.gameGates.clear();
|
||||||
@@ -1150,6 +1160,7 @@ public class RoomSpecialTypes {
|
|||||||
this.freezeExitTile.clear();
|
this.freezeExitTile.clear();
|
||||||
this.undefined.clear();
|
this.undefined.clear();
|
||||||
this.cycleTasks.clear();
|
this.cycleTasks.clear();
|
||||||
|
this.specialItemsById.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rectangle tentAt(RoomTile location) {
|
public Rectangle tentAt(RoomTile location) {
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ public class RoomTileManager {
|
|||||||
*/
|
*/
|
||||||
public void updateTile(RoomTile tile) {
|
public void updateTile(RoomTile tile) {
|
||||||
if (tile != null) {
|
if (tile != null) {
|
||||||
this.room.tileCache.remove(tile);
|
|
||||||
this.room.getItemManager().tileCache.remove(tile);
|
this.room.getItemManager().tileCache.remove(tile);
|
||||||
tile.setStackHeight(this.getStackHeight(tile.x, tile.y, false));
|
tile.setStackHeight(this.getStackHeight(tile.x, tile.y, false));
|
||||||
tile.setState(this.calculateTileState(tile));
|
tile.setState(this.calculateTileState(tile));
|
||||||
@@ -41,7 +40,6 @@ public class RoomTileManager {
|
|||||||
*/
|
*/
|
||||||
public void updateTiles(THashSet<RoomTile> tiles) {
|
public void updateTiles(THashSet<RoomTile> tiles) {
|
||||||
for (RoomTile tile : tiles) {
|
for (RoomTile tile : tiles) {
|
||||||
this.room.tileCache.remove(tile);
|
|
||||||
this.room.getItemManager().tileCache.remove(tile);
|
this.room.getItemManager().tileCache.remove(tile);
|
||||||
tile.setStackHeight(this.getStackHeight(tile.x, tile.y, false));
|
tile.setStackHeight(this.getStackHeight(tile.x, tile.y, false));
|
||||||
tile.setState(this.calculateTileState(tile));
|
tile.setState(this.calculateTileState(tile));
|
||||||
|
|||||||
@@ -71,6 +71,24 @@ public class RoomUnitManager {
|
|||||||
*/
|
*/
|
||||||
public void clear() {
|
public void clear() {
|
||||||
synchronized (this.room.roomUnitLock) {
|
synchronized (this.room.roomUnitLock) {
|
||||||
|
for (Habbo habbo : this.currentHabbos.values()) {
|
||||||
|
if (habbo.getRoomUnit() != null) {
|
||||||
|
WiredMoveCarryHelper.cleanupRoomUnit(habbo.getRoomUnit());
|
||||||
|
WiredUserMovementHelper.cleanupRoomUnit(habbo.getRoomUnit());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Bot bot : this.currentBots.valueCollection()) {
|
||||||
|
if (bot.getRoomUnit() != null) {
|
||||||
|
WiredMoveCarryHelper.cleanupRoomUnit(bot.getRoomUnit());
|
||||||
|
WiredUserMovementHelper.cleanupRoomUnit(bot.getRoomUnit());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Pet pet : this.currentPets.valueCollection()) {
|
||||||
|
if (pet.getRoomUnit() != null) {
|
||||||
|
WiredMoveCarryHelper.cleanupRoomUnit(pet.getRoomUnit());
|
||||||
|
WiredUserMovementHelper.cleanupRoomUnit(pet.getRoomUnit());
|
||||||
|
}
|
||||||
|
}
|
||||||
this.unitCounter = 0;
|
this.unitCounter = 0;
|
||||||
this.currentHabbos.clear();
|
this.currentHabbos.clear();
|
||||||
this.currentPets.clear();
|
this.currentPets.clear();
|
||||||
@@ -222,6 +240,8 @@ public class RoomUnitManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (habbo.getRoomUnit() != null) {
|
if (habbo.getRoomUnit() != null) {
|
||||||
|
WiredMoveCarryHelper.cleanupRoomUnit(habbo.getRoomUnit());
|
||||||
|
WiredUserMovementHelper.cleanupRoomUnit(habbo.getRoomUnit());
|
||||||
WiredManager.triggerUserLeavesRoom(this.room, habbo.getRoomUnit());
|
WiredManager.triggerUserLeavesRoom(this.room, habbo.getRoomUnit());
|
||||||
if (WiredFreezeUtil.isFrozen(habbo.getRoomUnit())) {
|
if (WiredFreezeUtil.isFrozen(habbo.getRoomUnit())) {
|
||||||
WiredFreezeUtil.unfreeze(this.room, habbo.getRoomUnit());
|
WiredFreezeUtil.unfreeze(this.room, habbo.getRoomUnit());
|
||||||
@@ -646,14 +666,22 @@ public class RoomUnitManager {
|
|||||||
public boolean removeBot(Bot bot) {
|
public boolean removeBot(Bot bot) {
|
||||||
synchronized (this.currentBots) {
|
synchronized (this.currentBots) {
|
||||||
if (this.currentBots.containsKey(bot.getId())) {
|
if (this.currentBots.containsKey(bot.getId())) {
|
||||||
if (bot.getRoomUnit() != null && bot.getRoomUnit().getCurrentLocation() != null) {
|
if (bot.getRoomUnit() != null) {
|
||||||
bot.getRoomUnit().getCurrentLocation().removeUnit(bot.getRoomUnit());
|
WiredMoveCarryHelper.cleanupRoomUnit(bot.getRoomUnit());
|
||||||
|
WiredUserMovementHelper.cleanupRoomUnit(bot.getRoomUnit());
|
||||||
|
if (bot.getRoomUnit().getCurrentLocation() != null) {
|
||||||
|
bot.getRoomUnit().getCurrentLocation().removeUnit(bot.getRoomUnit());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentBots.remove(bot.getId());
|
this.currentBots.remove(bot.getId());
|
||||||
bot.getRoomUnit().setInRoom(false);
|
if (bot.getRoomUnit() != null) {
|
||||||
|
bot.getRoomUnit().setInRoom(false);
|
||||||
|
}
|
||||||
bot.setRoom(null);
|
bot.setRoom(null);
|
||||||
this.room.sendComposer(new RoomUserRemoveComposer(bot.getRoomUnit()).compose());
|
if (bot.getRoomUnit() != null) {
|
||||||
|
this.room.sendComposer(new RoomUserRemoveComposer(bot.getRoomUnit()).compose());
|
||||||
|
}
|
||||||
bot.setRoomUnit(null);
|
bot.setRoomUnit(null);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -876,7 +904,12 @@ public class RoomUnitManager {
|
|||||||
* Removes a Pet from the room.
|
* Removes a Pet from the room.
|
||||||
*/
|
*/
|
||||||
public Pet removePet(int petId) {
|
public Pet removePet(int petId) {
|
||||||
return this.currentPets.remove(petId);
|
Pet pet = this.currentPets.remove(petId);
|
||||||
|
if (pet != null && pet.getRoomUnit() != null) {
|
||||||
|
WiredMoveCarryHelper.cleanupRoomUnit(pet.getRoomUnit());
|
||||||
|
WiredUserMovementHelper.cleanupRoomUnit(pet.getRoomUnit());
|
||||||
|
}
|
||||||
|
return pet;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1454,6 +1487,24 @@ public class RoomUnitManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
|
for (Habbo habbo : this.currentHabbos.values()) {
|
||||||
|
if (habbo.getRoomUnit() != null) {
|
||||||
|
WiredMoveCarryHelper.cleanupRoomUnit(habbo.getRoomUnit());
|
||||||
|
WiredUserMovementHelper.cleanupRoomUnit(habbo.getRoomUnit());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Bot bot : this.currentBots.valueCollection()) {
|
||||||
|
if (bot.getRoomUnit() != null) {
|
||||||
|
WiredMoveCarryHelper.cleanupRoomUnit(bot.getRoomUnit());
|
||||||
|
WiredUserMovementHelper.cleanupRoomUnit(bot.getRoomUnit());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Pet pet : this.currentPets.valueCollection()) {
|
||||||
|
if (pet.getRoomUnit() != null) {
|
||||||
|
WiredMoveCarryHelper.cleanupRoomUnit(pet.getRoomUnit());
|
||||||
|
WiredUserMovementHelper.cleanupRoomUnit(pet.getRoomUnit());
|
||||||
|
}
|
||||||
|
}
|
||||||
this.currentHabbos.clear();
|
this.currentHabbos.clear();
|
||||||
this.currentBots.clear();
|
this.currentBots.clear();
|
||||||
this.currentPets.clear();
|
this.currentPets.clear();
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public class RoomUserVariableManager {
|
|||||||
|
|
||||||
private final Room room;
|
private final Room room;
|
||||||
private final ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, VariableAssignment>> activeAssignmentsByUserId;
|
private final ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, VariableAssignment>> activeAssignmentsByUserId;
|
||||||
|
private final java.util.concurrent.atomic.AtomicBoolean broadcastRequested = new java.util.concurrent.atomic.AtomicBoolean(false);
|
||||||
|
|
||||||
public RoomUserVariableManager(Room room) {
|
public RoomUserVariableManager(Room room) {
|
||||||
this.room = room;
|
this.room = room;
|
||||||
@@ -660,7 +661,22 @@ public class RoomUserVariableManager {
|
|||||||
habbo.getClient().sendResponse(new WiredUserVariablesDataComposer(this.createSnapshot(), this.room.getFurniVariableManager().createSnapshot(), this.room.getRoomVariableManager().createSnapshot()));
|
habbo.getClient().sendResponse(new WiredUserVariablesDataComposer(this.createSnapshot(), this.room.getFurniVariableManager().createSnapshot(), this.room.getRoomVariableManager().createSnapshot()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void requestBroadcast() {
|
||||||
|
if (this.broadcastRequested.compareAndSet(false, true)) {
|
||||||
|
Emulator.getThreading().run(() -> {
|
||||||
|
this.broadcastRequested.set(false);
|
||||||
|
if (this.room.isLoaded()) {
|
||||||
|
this.broadcastSnapshotRaw();
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void broadcastSnapshot() {
|
public void broadcastSnapshot() {
|
||||||
|
this.requestBroadcast();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void broadcastSnapshotRaw() {
|
||||||
Snapshot userSnapshot = this.createSnapshot();
|
Snapshot userSnapshot = this.createSnapshot();
|
||||||
RoomFurniVariableManager.Snapshot furniSnapshot = this.room.getFurniVariableManager().createSnapshot();
|
RoomFurniVariableManager.Snapshot furniSnapshot = this.room.getFurniVariableManager().createSnapshot();
|
||||||
RoomVariableManager.Snapshot roomSnapshot = this.room.getRoomVariableManager().createSnapshot();
|
RoomVariableManager.Snapshot roomSnapshot = this.room.getRoomVariableManager().createSnapshot();
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public class RoomVariableManager {
|
|||||||
private final Room room;
|
private final Room room;
|
||||||
private final ConcurrentHashMap<Integer, VariableAssignment> activeAssignmentsByDefinitionId;
|
private final ConcurrentHashMap<Integer, VariableAssignment> activeAssignmentsByDefinitionId;
|
||||||
private volatile boolean persistentValuesLoaded;
|
private volatile boolean persistentValuesLoaded;
|
||||||
|
private final java.util.concurrent.atomic.AtomicBoolean broadcastRequested = new java.util.concurrent.atomic.AtomicBoolean(false);
|
||||||
|
|
||||||
public RoomVariableManager(Room room) {
|
public RoomVariableManager(Room room) {
|
||||||
this.room = room;
|
this.room = room;
|
||||||
@@ -433,7 +434,22 @@ public class RoomVariableManager {
|
|||||||
habbo.getClient().sendResponse(new WiredUserVariablesDataComposer(this.room.getUserVariableManager().createSnapshot(), this.room.getFurniVariableManager().createSnapshot(), this.createSnapshot()));
|
habbo.getClient().sendResponse(new WiredUserVariablesDataComposer(this.room.getUserVariableManager().createSnapshot(), this.room.getFurniVariableManager().createSnapshot(), this.createSnapshot()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void requestBroadcast() {
|
||||||
|
if (this.broadcastRequested.compareAndSet(false, true)) {
|
||||||
|
Emulator.getThreading().run(() -> {
|
||||||
|
this.broadcastRequested.set(false);
|
||||||
|
if (this.room.isLoaded()) {
|
||||||
|
this.broadcastSnapshotRaw();
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void broadcastSnapshot() {
|
public void broadcastSnapshot() {
|
||||||
|
this.requestBroadcast();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void broadcastSnapshotRaw() {
|
||||||
RoomUserVariableManager.Snapshot userSnapshot = this.room.getUserVariableManager().createSnapshot();
|
RoomUserVariableManager.Snapshot userSnapshot = this.room.getUserVariableManager().createSnapshot();
|
||||||
RoomFurniVariableManager.Snapshot furniSnapshot = this.room.getFurniVariableManager().createSnapshot();
|
RoomFurniVariableManager.Snapshot furniSnapshot = this.room.getFurniVariableManager().createSnapshot();
|
||||||
Snapshot roomSnapshot = this.createSnapshot();
|
Snapshot roomSnapshot = this.createSnapshot();
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.eu.habbo.habbohotel.soundboard;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SoundboardManager {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(SoundboardManager.class);
|
||||||
|
|
||||||
|
private final List<SoundboardSound> sounds = new ArrayList<>();
|
||||||
|
|
||||||
|
public SoundboardManager() {
|
||||||
|
long millis = System.currentTimeMillis();
|
||||||
|
this.bootstrap();
|
||||||
|
this.reload();
|
||||||
|
LOGGER.info("Soundboard Manager -> Loaded! ({} MS, {} sounds)", System.currentTimeMillis() - millis, this.sounds.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Self-bootstrap: room flag column + sounds table, so the feature works even
|
||||||
|
// before the manual migration is applied.
|
||||||
|
private void bootstrap() {
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
Statement statement = connection.createStatement()) {
|
||||||
|
statement.execute("ALTER TABLE `rooms` ADD COLUMN IF NOT EXISTS `soundboard_enabled` TINYINT(1) NOT NULL DEFAULT 0");
|
||||||
|
statement.execute("CREATE TABLE IF NOT EXISTS `soundboard_sounds` (" +
|
||||||
|
"`id` INT(11) NOT NULL AUTO_INCREMENT, `name` VARCHAR(64) NOT NULL DEFAULT '', " +
|
||||||
|
"`url` VARCHAR(255) NOT NULL DEFAULT '', `enabled` TINYINT(1) NOT NULL DEFAULT 1, " +
|
||||||
|
"`sort_order` INT(11) NOT NULL DEFAULT 0, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Failed to bootstrap soundboard schema", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
this.sounds.clear();
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement("SELECT id, name, url FROM soundboard_sounds WHERE enabled = 1 ORDER BY sort_order ASC, id ASC");
|
||||||
|
ResultSet set = statement.executeQuery()) {
|
||||||
|
while (set.next()) {
|
||||||
|
this.sounds.add(new SoundboardSound(set));
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Failed to load soundboard sounds", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SoundboardSound> getSounds() {
|
||||||
|
return this.sounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SoundboardSound getSound(int id) {
|
||||||
|
for (SoundboardSound sound : this.sounds) {
|
||||||
|
if (sound.id == id) return sound;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Owner toggle — persists the room flag with a dedicated UPDATE (kept out of
|
||||||
|
// the big room-settings save to avoid touching that statement).
|
||||||
|
public void setRoomEnabled(int roomId, boolean enabled) {
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement("UPDATE rooms SET soundboard_enabled = ? WHERE id = ? LIMIT 1")) {
|
||||||
|
statement.setString(1, enabled ? "1" : "0");
|
||||||
|
statement.setInt(2, roomId);
|
||||||
|
statement.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Failed to set soundboard_enabled for room {}", roomId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.eu.habbo.habbohotel.soundboard;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
// One soundboard pad: a named audio clip served from a URL (uploaded via the CMS).
|
||||||
|
public class SoundboardSound {
|
||||||
|
public final int id;
|
||||||
|
public final String name;
|
||||||
|
public final String url;
|
||||||
|
|
||||||
|
public SoundboardSound(ResultSet set) throws SQLException {
|
||||||
|
this.id = set.getInt("id");
|
||||||
|
this.name = set.getString("name");
|
||||||
|
this.url = set.getString("url");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,6 +46,7 @@ public class HabboInfo implements Runnable {
|
|||||||
private int InfostandStand;
|
private int InfostandStand;
|
||||||
private int InfostandOverlay;
|
private int InfostandOverlay;
|
||||||
private int InfostandCardBg;
|
private int InfostandCardBg;
|
||||||
|
private int InfostandBorder;
|
||||||
private int loadingRoom;
|
private int loadingRoom;
|
||||||
private Room currentRoom;
|
private Room currentRoom;
|
||||||
private String roomEntryMethod = "door";
|
private String roomEntryMethod = "door";
|
||||||
@@ -93,6 +94,11 @@ public class HabboInfo implements Runnable {
|
|||||||
this.InfostandStand = set.getInt("background_stand_id");
|
this.InfostandStand = set.getInt("background_stand_id");
|
||||||
this.InfostandOverlay = set.getInt("background_overlay_id");
|
this.InfostandOverlay = set.getInt("background_overlay_id");
|
||||||
this.InfostandCardBg = set.getInt("background_card_id");
|
this.InfostandCardBg = set.getInt("background_card_id");
|
||||||
|
try {
|
||||||
|
this.InfostandBorder = set.getInt("background_border_id");
|
||||||
|
} catch (SQLException ignored) {
|
||||||
|
this.InfostandBorder = 0;
|
||||||
|
}
|
||||||
this.currentRoom = null;
|
this.currentRoom = null;
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
LOGGER.error("Caught SQL exception", e);
|
LOGGER.error("Caught SQL exception", e);
|
||||||
@@ -300,6 +306,15 @@ public class HabboInfo implements Runnable {
|
|||||||
public void setInfostandCardBg(int infostandCardBg) {
|
public void setInfostandCardBg(int infostandCardBg) {
|
||||||
InfostandCardBg = infostandCardBg;
|
InfostandCardBg = infostandCardBg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getInfostandBorder() {
|
||||||
|
return InfostandBorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInfostandBorder(int infostandBorder) {
|
||||||
|
InfostandBorder = infostandBorder;
|
||||||
|
}
|
||||||
|
|
||||||
public Rank getRank() {
|
public Rank getRank() {
|
||||||
return this.rank;
|
return this.rank;
|
||||||
}
|
}
|
||||||
@@ -587,7 +602,7 @@ public class HabboInfo implements Runnable {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
SqlQueries.update(
|
SqlQueries.update(
|
||||||
"UPDATE users SET motto = ?, online = ?, look = ?, gender = ?, credits = ?, last_login = ?, last_online = ?, home_room = ?, ip_current = ?, `rank` = ?, machine_id = ?, username = ?, background_id = ?, background_stand_id = ?, background_overlay_id = ?, background_card_id = ? WHERE id = ?",
|
"UPDATE users SET motto = ?, online = ?, look = ?, gender = ?, credits = ?, last_login = ?, last_online = ?, home_room = ?, ip_current = ?, `rank` = ?, machine_id = ?, username = ?, background_id = ?, background_stand_id = ?, background_overlay_id = ?, background_card_id = ?, background_border_id = ? WHERE id = ?",
|
||||||
this.motto,
|
this.motto,
|
||||||
this.online ? "1" : "0",
|
this.online ? "1" : "0",
|
||||||
this.look,
|
this.look,
|
||||||
@@ -604,6 +619,7 @@ public class HabboInfo implements Runnable {
|
|||||||
this.InfostandStand,
|
this.InfostandStand,
|
||||||
this.InfostandOverlay,
|
this.InfostandOverlay,
|
||||||
this.InfostandCardBg,
|
this.InfostandCardBg,
|
||||||
|
this.InfostandBorder,
|
||||||
this.id);
|
this.id);
|
||||||
} catch (SqlQueries.DataAccessException e) {
|
} catch (SqlQueries.DataAccessException e) {
|
||||||
LOGGER.error("Caught SQL exception", e);
|
LOGGER.error("Caught SQL exception", e);
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ public class HabboManager {
|
|||||||
|
|
||||||
private final ConcurrentHashMap<Integer, Habbo> onlineHabbos;
|
private final ConcurrentHashMap<Integer, Habbo> onlineHabbos;
|
||||||
private final ConcurrentHashMap<String, Habbo> onlineHabbosByName;
|
private final ConcurrentHashMap<String, Habbo> onlineHabbosByName;
|
||||||
|
private final ConcurrentHashMap<Integer, String> usernameCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public HabboManager() {
|
public HabboManager() {
|
||||||
long millis = System.currentTimeMillis();
|
long millis = System.currentTimeMillis();
|
||||||
@@ -131,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) {
|
||||||
@@ -158,6 +156,26 @@ public class HabboManager {
|
|||||||
return this.getHabbo(id).getHabboInfo();
|
return this.getHabbo(id).getHabboInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCachedUsername(int id) {
|
||||||
|
String cached = this.usernameCache.get(id);
|
||||||
|
if (cached != null) return cached;
|
||||||
|
|
||||||
|
Habbo online = this.getHabbo(id);
|
||||||
|
if (online != null) {
|
||||||
|
String name = online.getHabboInfo().getUsername();
|
||||||
|
this.usernameCache.put(id, name);
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
HabboInfo offline = getOfflineHabboInfo(id);
|
||||||
|
if (offline != null) {
|
||||||
|
String name = offline.getUsername();
|
||||||
|
this.usernameCache.put(id, name);
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
public int getOnlineCount() {
|
public int getOnlineCount() {
|
||||||
return this.onlineHabbos.size();
|
return this.onlineHabbos.size();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ public class HabboStats implements Runnable {
|
|||||||
public long lastTradeTimestamp = Emulator.getIntUnixTimestamp();
|
public long lastTradeTimestamp = Emulator.getIntUnixTimestamp();
|
||||||
public long lastGiftTimestamp = Emulator.getIntUnixTimestamp();
|
public long lastGiftTimestamp = Emulator.getIntUnixTimestamp();
|
||||||
public long lastPurchaseTimestamp = Emulator.getIntUnixTimestamp();
|
public long lastPurchaseTimestamp = Emulator.getIntUnixTimestamp();
|
||||||
|
public long lastFloorplanSaveTimestamp = 0;
|
||||||
public int uiFlags;
|
public int uiFlags;
|
||||||
public boolean hasGottenDefaultSavedSearches;
|
public boolean hasGottenDefaultSavedSearches;
|
||||||
private HabboInfo habboInfo;
|
private HabboInfo habboInfo;
|
||||||
|
|||||||
+5
-3
@@ -24,7 +24,8 @@ public class InfostandBackgroundManager {
|
|||||||
BACKGROUND("background"),
|
BACKGROUND("background"),
|
||||||
STAND("stand"),
|
STAND("stand"),
|
||||||
OVERLAY("overlay"),
|
OVERLAY("overlay"),
|
||||||
CARD("card");
|
CARD("card"),
|
||||||
|
BORDER("border");
|
||||||
|
|
||||||
public final String dbValue;
|
public final String dbValue;
|
||||||
|
|
||||||
@@ -89,11 +90,12 @@ public class InfostandBackgroundManager {
|
|||||||
this.enforce = loaded > 0;
|
this.enforce = loaded > 0;
|
||||||
|
|
||||||
if (this.enforce) {
|
if (this.enforce) {
|
||||||
LOGGER.info("InfostandBackgroundManager -> Loaded {} backgrounds, {} stands, {} overlays, {} cards from infostand_backgrounds.",
|
LOGGER.info("InfostandBackgroundManager -> Loaded {} backgrounds, {} stands, {} overlays, {} cards, {} borders from infostand_backgrounds.",
|
||||||
this.entries.get(Category.BACKGROUND).size(),
|
this.entries.get(Category.BACKGROUND).size(),
|
||||||
this.entries.get(Category.STAND).size(),
|
this.entries.get(Category.STAND).size(),
|
||||||
this.entries.get(Category.OVERLAY).size(),
|
this.entries.get(Category.OVERLAY).size(),
|
||||||
this.entries.get(Category.CARD).size());
|
this.entries.get(Category.CARD).size(),
|
||||||
|
this.entries.get(Category.BORDER).size());
|
||||||
} else {
|
} else {
|
||||||
LOGGER.info("InfostandBackgroundManager -> infostand_backgrounds is empty, server-side validation disabled (only range clamp will apply).");
|
LOGGER.info("InfostandBackgroundManager -> infostand_backgrounds is empty, server-side validation disabled (only range clamp will apply).");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,430 @@
|
|||||||
|
package com.eu.habbo.habbohotel.wheel;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.items.Item;
|
||||||
|
import com.eu.habbo.habbohotel.users.Habbo;
|
||||||
|
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||||
|
import gnu.trove.set.hash.THashSet;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
public class WheelManager {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(WheelManager.class);
|
||||||
|
private static final int RECENT_KEEP = 50;
|
||||||
|
private static final int SECONDS_PER_DAY = 86400;
|
||||||
|
|
||||||
|
public static final Set<String> VALID_PRIZE_TYPES = Set.of(
|
||||||
|
"credits", "points", "spin", "item", "badge", "nothing");
|
||||||
|
public static final int MAX_PRIZES_PER_SAVE = 64;
|
||||||
|
public static final int MAX_STRING_LEN = 64;
|
||||||
|
public static final int MAX_PRIZE_AMOUNT = 1_000_000;
|
||||||
|
public static final int MAX_ITEM_QUANTITY = 100;
|
||||||
|
public static final int MAX_WEIGHT = 1_000_000;
|
||||||
|
public static final int MAX_EXTRA_SPINS = 10_000;
|
||||||
|
private static final long MIN_SPIN_INTERVAL_MS = 1500L;
|
||||||
|
|
||||||
|
private final List<WheelPrize> prizes = new ArrayList<>();
|
||||||
|
private int totalWeight = 0;
|
||||||
|
private int freeSpinsPerDay = 1;
|
||||||
|
private int spinCost = 50;
|
||||||
|
private int spinCostType = 5;
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<Integer, Long> lastSpinAt = new ConcurrentHashMap<>();
|
||||||
|
private final ConcurrentHashMap<Integer, WheelUserState> userStateCache = new ConcurrentHashMap<>();
|
||||||
|
private final java.util.concurrent.CopyOnWriteArrayList<WheelRecentWin> recentWinsCache = new java.util.concurrent.CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
public WheelManager() {
|
||||||
|
long millis = System.currentTimeMillis();
|
||||||
|
this.reload();
|
||||||
|
LOGGER.info("Wheel Manager -> Loaded! ({} MS)", System.currentTimeMillis() - millis);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
this.loadSettings();
|
||||||
|
this.loadPrizes();
|
||||||
|
this.loadRecentWins();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadSettings() {
|
||||||
|
this.freeSpinsPerDay = Emulator.getConfig().getInt("wheel.free_spins_per_day", 1);
|
||||||
|
this.spinCost = Emulator.getConfig().getInt("wheel.spin_cost", 50);
|
||||||
|
this.spinCostType = Emulator.getConfig().getInt("wheel.spin_cost_type", 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadPrizes() {
|
||||||
|
this.prizes.clear();
|
||||||
|
this.totalWeight = 0;
|
||||||
|
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement("SELECT * FROM wheel_prizes WHERE enabled = 1 ORDER BY sort_order ASC, id ASC");
|
||||||
|
ResultSet set = statement.executeQuery()) {
|
||||||
|
while (set.next()) {
|
||||||
|
WheelPrize prize = new WheelPrize(set);
|
||||||
|
this.prizes.add(prize);
|
||||||
|
this.totalWeight += prize.weight;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Failed to load fortune wheel prizes", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<WheelPrize> getPrizes() {
|
||||||
|
return this.prizes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSpinCost() {
|
||||||
|
return this.spinCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSpinCostType() {
|
||||||
|
return this.spinCostType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int today() {
|
||||||
|
return Emulator.getIntUnixTimestamp() / SECONDS_PER_DAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized WheelUserState getUserState(int userId) {
|
||||||
|
int today = this.today();
|
||||||
|
WheelUserState cached = this.userStateCache.get(userId);
|
||||||
|
|
||||||
|
if (cached != null) {
|
||||||
|
if (cached.lastReset != today) {
|
||||||
|
cached.freeSpins = this.freeSpinsPerDay;
|
||||||
|
cached.lastReset = today;
|
||||||
|
this.persistUserState(userId, cached);
|
||||||
|
}
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
WheelUserState state = new WheelUserState();
|
||||||
|
boolean exists = false;
|
||||||
|
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement("SELECT free_spins, extra_spins, last_reset FROM wheel_user_state WHERE user_id = ?")) {
|
||||||
|
statement.setInt(1, userId);
|
||||||
|
try (ResultSet set = statement.executeQuery()) {
|
||||||
|
if (set.next()) {
|
||||||
|
state.freeSpins = set.getInt("free_spins");
|
||||||
|
state.extraSpins = set.getInt("extra_spins");
|
||||||
|
state.lastReset = set.getInt("last_reset");
|
||||||
|
exists = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Failed to read wheel state for user {}", userId, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
state.freeSpins = this.freeSpinsPerDay;
|
||||||
|
state.extraSpins = 0;
|
||||||
|
state.lastReset = today;
|
||||||
|
this.persistUserState(userId, state);
|
||||||
|
} else if (state.lastReset != today) {
|
||||||
|
state.freeSpins = this.freeSpinsPerDay;
|
||||||
|
state.lastReset = today;
|
||||||
|
this.persistUserState(userId, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.userStateCache.put(userId, state);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void persistUserState(int userId, WheelUserState state) {
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement(
|
||||||
|
"INSERT INTO wheel_user_state (user_id, free_spins, extra_spins, last_reset) VALUES (?, ?, ?, ?) " +
|
||||||
|
"ON DUPLICATE KEY UPDATE free_spins = VALUES(free_spins), extra_spins = VALUES(extra_spins), last_reset = VALUES(last_reset)")) {
|
||||||
|
statement.setInt(1, userId);
|
||||||
|
statement.setInt(2, state.freeSpins);
|
||||||
|
statement.setInt(3, state.extraSpins);
|
||||||
|
statement.setInt(4, state.lastReset);
|
||||||
|
statement.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Failed to persist wheel state for user {}", userId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized WheelPrize spin(Habbo habbo) {
|
||||||
|
int userId = habbo.getHabboInfo().getId();
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
Long last = this.lastSpinAt.get(userId);
|
||||||
|
if (last != null && (now - last) < MIN_SPIN_INTERVAL_MS) return null;
|
||||||
|
this.lastSpinAt.put(userId, now);
|
||||||
|
|
||||||
|
WheelUserState state = this.getUserState(userId);
|
||||||
|
|
||||||
|
boolean usedFree;
|
||||||
|
if (state.freeSpins > 0) {
|
||||||
|
state.freeSpins--;
|
||||||
|
usedFree = true;
|
||||||
|
} else if (state.extraSpins > 0) {
|
||||||
|
state.extraSpins--;
|
||||||
|
usedFree = false;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
WheelPrize prize = this.pickWeighted();
|
||||||
|
if (prize == null) {
|
||||||
|
if (usedFree) state.freeSpins++; else state.extraSpins++;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.giveReward(habbo, prize, state);
|
||||||
|
this.persistUserState(userId, state);
|
||||||
|
this.recordWin(habbo, prize);
|
||||||
|
|
||||||
|
return prize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WheelPrize pickWeighted() {
|
||||||
|
if (this.prizes.isEmpty() || this.totalWeight <= 0) return null;
|
||||||
|
|
||||||
|
int roll = ThreadLocalRandom.current().nextInt(this.totalWeight);
|
||||||
|
int acc = 0;
|
||||||
|
for (WheelPrize prize : this.prizes) {
|
||||||
|
acc += prize.weight;
|
||||||
|
if (roll < acc) return prize;
|
||||||
|
}
|
||||||
|
return this.prizes.get(this.prizes.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void giveReward(Habbo habbo, WheelPrize prize, WheelUserState state) {
|
||||||
|
int amount = Math.max(0, Math.min(prize.amount, MAX_PRIZE_AMOUNT));
|
||||||
|
|
||||||
|
switch (prize.type) {
|
||||||
|
case "credits":
|
||||||
|
if (amount > 0) habbo.giveCredits(amount);
|
||||||
|
break;
|
||||||
|
case "points":
|
||||||
|
if (amount > 0) habbo.givePoints(prize.pointsType, amount);
|
||||||
|
break;
|
||||||
|
case "spin":
|
||||||
|
int room = Math.max(0, MAX_EXTRA_SPINS - state.extraSpins);
|
||||||
|
state.extraSpins += Math.min(amount, room);
|
||||||
|
break;
|
||||||
|
case "item":
|
||||||
|
this.giveItem(habbo, prize, Math.min(amount, MAX_ITEM_QUANTITY));
|
||||||
|
break;
|
||||||
|
case "badge":
|
||||||
|
if (prize.value != null && !prize.value.isEmpty()) {
|
||||||
|
habbo.addBadge(prize.value, "Fortune Wheel");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "nothing":
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void giveItem(Habbo habbo, WheelPrize prize, int quantity) {
|
||||||
|
if (quantity <= 0 || prize.value == null) return;
|
||||||
|
|
||||||
|
int baseId;
|
||||||
|
try {
|
||||||
|
baseId = Integer.parseInt(prize.value.trim());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Item base = Emulator.getGameEnvironment().getItemManager().getItem(baseId);
|
||||||
|
if (base == null) return;
|
||||||
|
|
||||||
|
THashSet<HabboItem> items = new THashSet<>();
|
||||||
|
for (int i = 0; i < quantity; i++) {
|
||||||
|
HabboItem item = Emulator.getGameEnvironment().getItemManager().createItem(habbo.getHabboInfo().getId(), base, 0, 0, "");
|
||||||
|
if (item != null) items.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!items.isEmpty()) {
|
||||||
|
habbo.addFurniture(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recordWin(Habbo habbo, WheelPrize prize) {
|
||||||
|
WheelRecentWin win = new WheelRecentWin(
|
||||||
|
habbo.getHabboInfo().getUsername(),
|
||||||
|
habbo.getHabboInfo().getLook(),
|
||||||
|
prize.label);
|
||||||
|
|
||||||
|
this.recentWinsCache.add(0, win);
|
||||||
|
while (this.recentWinsCache.size() > RECENT_KEEP) {
|
||||||
|
this.recentWinsCache.remove(this.recentWinsCache.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) {
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement(
|
||||||
|
"INSERT INTO wheel_recent_wins (user_id, username, look, prize_label, won_at) VALUES (?, ?, ?, ?, ?)")) {
|
||||||
|
statement.setInt(1, habbo.getHabboInfo().getId());
|
||||||
|
statement.setString(2, habbo.getHabboInfo().getUsername());
|
||||||
|
statement.setString(3, habbo.getHabboInfo().getLook());
|
||||||
|
statement.setString(4, prize.label);
|
||||||
|
statement.setInt(5, Emulator.getIntUnixTimestamp());
|
||||||
|
statement.executeUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
try (PreparedStatement trim = connection.prepareStatement(
|
||||||
|
"DELETE FROM wheel_recent_wins WHERE id < (SELECT id FROM (SELECT id FROM wheel_recent_wins ORDER BY id DESC LIMIT 1 OFFSET ?) t)")) {
|
||||||
|
trim.setInt(1, RECENT_KEEP - 1);
|
||||||
|
trim.executeUpdate();
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Failed to record wheel win", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<WheelRecentWin> getRecentWins(int limit) {
|
||||||
|
if (limit <= 0) return new ArrayList<>();
|
||||||
|
int size = this.recentWinsCache.size();
|
||||||
|
if (size == 0) return new ArrayList<>();
|
||||||
|
int take = Math.min(limit, size);
|
||||||
|
return new ArrayList<>(this.recentWinsCache.subList(0, take));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadRecentWins() {
|
||||||
|
this.recentWinsCache.clear();
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement("SELECT username, look, prize_label FROM wheel_recent_wins ORDER BY id DESC LIMIT ?")) {
|
||||||
|
statement.setInt(1, RECENT_KEEP);
|
||||||
|
try (ResultSet set = statement.executeQuery()) {
|
||||||
|
while (set.next()) {
|
||||||
|
this.recentWinsCache.add(new WheelRecentWin(
|
||||||
|
set.getString("username"),
|
||||||
|
set.getString("look"),
|
||||||
|
set.getString("prize_label")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Failed to load wheel recent wins", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean buySpin(Habbo habbo) {
|
||||||
|
if (this.spinCost <= 0) return false;
|
||||||
|
|
||||||
|
int userId = habbo.getHabboInfo().getId();
|
||||||
|
WheelUserState state = this.getUserState(userId);
|
||||||
|
if (state.extraSpins >= MAX_EXTRA_SPINS) return false;
|
||||||
|
|
||||||
|
if (this.spinCostType == -1) {
|
||||||
|
if (habbo.getHabboInfo().getCredits() < this.spinCost) return false;
|
||||||
|
habbo.giveCredits(-this.spinCost);
|
||||||
|
} else {
|
||||||
|
if (habbo.getHabboInfo().getCurrencyAmount(this.spinCostType) < this.spinCost) return false;
|
||||||
|
habbo.givePoints(this.spinCostType, -this.spinCost);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.extraSpins++;
|
||||||
|
this.persistUserState(userId, state);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists a single prize. An {@code id <= 0} inserts a brand-new prize and
|
||||||
|
* returns its generated id; a positive id updates the existing row (and
|
||||||
|
* re-enables it, so a previously soft-deleted prize can be brought back).
|
||||||
|
* {@code sortOrder} reflects the prize's position in the editor so the
|
||||||
|
* wheel layout matches what the admin sees. Returns the effective row id,
|
||||||
|
* or {@code 0} if the write failed.
|
||||||
|
*/
|
||||||
|
public int savePrize(int id, String type, String value, int amount, int pointsType, int weight, String label, int sortOrder) {
|
||||||
|
String safeType = (type != null && VALID_PRIZE_TYPES.contains(type)) ? type : "nothing";
|
||||||
|
String safeValue = truncate(value, MAX_STRING_LEN);
|
||||||
|
String safeLabel = truncate(label, MAX_STRING_LEN);
|
||||||
|
int safeAmount = clamp(amount, 0, MAX_PRIZE_AMOUNT);
|
||||||
|
int safeWeight = clamp(weight, 0, MAX_WEIGHT);
|
||||||
|
int safeSort = clamp(sortOrder, 0, MAX_PRIZES_PER_SAVE);
|
||||||
|
|
||||||
|
if (id > 0) {
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement(
|
||||||
|
"UPDATE wheel_prizes SET type = ?, value = ?, amount = ?, points_type = ?, weight = ?, label = ?, sort_order = ?, enabled = 1 WHERE id = ?")) {
|
||||||
|
statement.setString(1, safeType);
|
||||||
|
statement.setString(2, safeValue);
|
||||||
|
statement.setInt(3, safeAmount);
|
||||||
|
statement.setInt(4, pointsType);
|
||||||
|
statement.setInt(5, safeWeight);
|
||||||
|
statement.setString(6, safeLabel);
|
||||||
|
statement.setInt(7, safeSort);
|
||||||
|
statement.setInt(8, id);
|
||||||
|
statement.executeUpdate();
|
||||||
|
return id;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Failed to save wheel prize {}", id, e);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement(
|
||||||
|
"INSERT INTO wheel_prizes (type, value, amount, points_type, weight, label, enabled, sort_order) VALUES (?, ?, ?, ?, ?, ?, 1, ?)",
|
||||||
|
Statement.RETURN_GENERATED_KEYS)) {
|
||||||
|
statement.setString(1, safeType);
|
||||||
|
statement.setString(2, safeValue);
|
||||||
|
statement.setInt(3, safeAmount);
|
||||||
|
statement.setInt(4, pointsType);
|
||||||
|
statement.setInt(5, safeWeight);
|
||||||
|
statement.setString(6, safeLabel);
|
||||||
|
statement.setInt(7, safeSort);
|
||||||
|
statement.executeUpdate();
|
||||||
|
|
||||||
|
try (ResultSet keys = statement.getGeneratedKeys()) {
|
||||||
|
if (keys.next()) return keys.getInt(1);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Failed to insert wheel prize", e);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Soft-deletes every enabled prize whose id is not in {@code keptIds} by
|
||||||
|
* setting {@code enabled = 0}. This is intentionally non-destructive: rows
|
||||||
|
* stay in the table (so historical references and re-enabling remain
|
||||||
|
* possible) but {@link #loadPrizes()} only ever loads {@code enabled = 1}.
|
||||||
|
* An empty set disables all prizes.
|
||||||
|
*/
|
||||||
|
public void disablePrizesNotIn(Set<Integer> keptIds) {
|
||||||
|
if (keptIds == null) return;
|
||||||
|
|
||||||
|
StringBuilder sql = new StringBuilder("UPDATE wheel_prizes SET enabled = 0 WHERE enabled = 1");
|
||||||
|
if (!keptIds.isEmpty()) {
|
||||||
|
StringJoiner ids = new StringJoiner(",", " AND id NOT IN (", ")");
|
||||||
|
for (Integer keptId : keptIds) {
|
||||||
|
ids.add(Integer.toString(keptId));
|
||||||
|
}
|
||||||
|
sql.append(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement(sql.toString())) {
|
||||||
|
statement.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Failed to disable removed wheel prizes", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String truncate(String s, int max) {
|
||||||
|
if (s == null) return "";
|
||||||
|
return s.length() <= max ? s : s.substring(0, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int clamp(int value, int min, int max) {
|
||||||
|
if (value < min) return min;
|
||||||
|
if (value > max) return max;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.eu.habbo.habbohotel.wheel;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.items.Item;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
// One slice of the wheel. type = item | badge | credits | points | spin | nothing.
|
||||||
|
public class WheelPrize {
|
||||||
|
public final int id;
|
||||||
|
public final String type;
|
||||||
|
public final String value; // item: base item id ; badge: badge code ; others: unused
|
||||||
|
public final int amount; // item qty / credits / points / extra spins
|
||||||
|
public final int pointsType; // for type=points
|
||||||
|
public final int weight;
|
||||||
|
public final String label;
|
||||||
|
public final int spriteId; // resolved for item prizes so the client can render the furni icon
|
||||||
|
|
||||||
|
public WheelPrize(ResultSet set) throws SQLException {
|
||||||
|
this.id = set.getInt("id");
|
||||||
|
this.type = set.getString("type");
|
||||||
|
this.value = set.getString("value");
|
||||||
|
this.amount = set.getInt("amount");
|
||||||
|
this.pointsType = set.getInt("points_type");
|
||||||
|
this.weight = Math.max(0, set.getInt("weight"));
|
||||||
|
this.label = set.getString("label");
|
||||||
|
this.spriteId = resolveSpriteId(this.type, this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int resolveSpriteId(String type, String value) {
|
||||||
|
if (!"item".equals(type) || value == null) return 0;
|
||||||
|
try {
|
||||||
|
Item item = Emulator.getGameEnvironment().getItemManager().getItem(Integer.parseInt(value.trim()));
|
||||||
|
return item != null ? item.getSpriteId() : 0;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String badgeCode() {
|
||||||
|
return "badge".equals(this.type) && this.value != null ? this.value : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.eu.habbo.habbohotel.wheel;
|
||||||
|
|
||||||
|
// A row in the "latest winners" panel. Denormalized (username/look stored at win time).
|
||||||
|
public class WheelRecentWin {
|
||||||
|
public final String username;
|
||||||
|
public final String look;
|
||||||
|
public final String prizeLabel;
|
||||||
|
|
||||||
|
public WheelRecentWin(String username, String look, String prizeLabel) {
|
||||||
|
this.username = username != null ? username : "";
|
||||||
|
this.look = look != null ? look : "";
|
||||||
|
this.prizeLabel = prizeLabel != null ? prizeLabel : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.eu.habbo.habbohotel.wheel;
|
||||||
|
|
||||||
|
// Per-user spin balance. freeSpins resets daily (lazy, on access); extraSpins persist.
|
||||||
|
public class WheelUserState {
|
||||||
|
public int freeSpins;
|
||||||
|
public int extraSpins;
|
||||||
|
public int lastReset; // day index (unix / 86400) of the last daily reset
|
||||||
|
|
||||||
|
public int totalSpins() {
|
||||||
|
return this.freeSpins + this.extraSpins;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,6 +43,7 @@ import java.sql.ResultSet;
|
|||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -89,11 +90,11 @@ public class WiredHandler {
|
|||||||
long millis = System.currentTimeMillis();
|
long millis = System.currentTimeMillis();
|
||||||
List<LegacyExecutionPlan> executionPlans = new ArrayList<>();
|
List<LegacyExecutionPlan> executionPlans = new ArrayList<>();
|
||||||
|
|
||||||
List<RoomTile> triggeredTiles = new ArrayList<>();
|
LinkedHashSet<Long> triggeredTiles = new LinkedHashSet<>();
|
||||||
for (InteractionWiredTrigger trigger : triggers) {
|
for (InteractionWiredTrigger trigger : triggers) {
|
||||||
RoomTile tile = room.getLayout().getTile(trigger.getX(), trigger.getY());
|
long coordinateKey = toTileCoordinateKey(trigger.getX(), trigger.getY());
|
||||||
|
|
||||||
if (triggeredTiles.contains(tile))
|
if (!triggeredTiles.add(coordinateKey))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
LegacyExecutionPlan executionPlan = new LegacyExecutionPlan();
|
LegacyExecutionPlan executionPlan = new LegacyExecutionPlan();
|
||||||
@@ -103,8 +104,6 @@ public class WiredHandler {
|
|||||||
|
|
||||||
if (triggerType.equals(WiredTriggerType.SAY_SOMETHING))
|
if (triggerType.equals(WiredTriggerType.SAY_SOMETHING))
|
||||||
talked = true;
|
talked = true;
|
||||||
|
|
||||||
triggeredTiles.add(tile);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,20 +138,19 @@ public class WiredHandler {
|
|||||||
long millis = System.currentTimeMillis();
|
long millis = System.currentTimeMillis();
|
||||||
List<LegacyExecutionPlan> executionPlans = new ArrayList<>();
|
List<LegacyExecutionPlan> executionPlans = new ArrayList<>();
|
||||||
|
|
||||||
List<RoomTile> triggeredTiles = new ArrayList<>();
|
LinkedHashSet<Long> triggeredTiles = new LinkedHashSet<>();
|
||||||
for (InteractionWiredTrigger trigger : triggers) {
|
for (InteractionWiredTrigger trigger : triggers) {
|
||||||
if (trigger.getClass() != triggerType) continue;
|
if (trigger.getClass() != triggerType) continue;
|
||||||
|
|
||||||
RoomTile tile = room.getLayout().getTile(trigger.getX(), trigger.getY());
|
long coordinateKey = toTileCoordinateKey(trigger.getX(), trigger.getY());
|
||||||
|
|
||||||
if (triggeredTiles.contains(tile))
|
if (!triggeredTiles.add(coordinateKey))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
LegacyExecutionPlan executionPlan = new LegacyExecutionPlan();
|
LegacyExecutionPlan executionPlan = new LegacyExecutionPlan();
|
||||||
|
|
||||||
if (handle(trigger, roomUnit, room, stuff, executionPlan)) {
|
if (handle(trigger, roomUnit, room, stuff, executionPlan)) {
|
||||||
executionPlans.add(executionPlan);
|
executionPlans.add(executionPlan);
|
||||||
triggeredTiles.add(tile);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +185,11 @@ public class WiredHandler {
|
|||||||
WiredExtraExecutionLimit executionLimitExtra = null;
|
WiredExtraExecutionLimit executionLimitExtra = null;
|
||||||
WiredExtraRandom randomExtra = null;
|
WiredExtraRandom randomExtra = null;
|
||||||
|
|
||||||
|
int conditionEvaluationMode = WiredExtraOrEval.MODE_ALL;
|
||||||
|
int conditionEvaluationValue = 1;
|
||||||
|
boolean hasExtraUnseen = false;
|
||||||
|
boolean hasExtraExecuteInOrder = false;
|
||||||
|
|
||||||
for (InteractionWiredExtra extra : extras) {
|
for (InteractionWiredExtra extra : extras) {
|
||||||
if (executionLimitExtra == null && extra instanceof WiredExtraExecutionLimit) {
|
if (executionLimitExtra == null && extra instanceof WiredExtraExecutionLimit) {
|
||||||
executionLimitExtra = (WiredExtraExecutionLimit) extra;
|
executionLimitExtra = (WiredExtraExecutionLimit) extra;
|
||||||
@@ -195,18 +198,22 @@ public class WiredHandler {
|
|||||||
if (randomExtra == null && extra instanceof WiredExtraRandom) {
|
if (randomExtra == null && extra instanceof WiredExtraRandom) {
|
||||||
randomExtra = (WiredExtraRandom) extra;
|
randomExtra = (WiredExtraRandom) extra;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasExtraUnseen && extra instanceof WiredExtraUnseen) {
|
||||||
|
hasExtraUnseen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasExtraExecuteInOrder && extra instanceof WiredExtraExecuteInOrder) {
|
||||||
|
hasExtraExecuteInOrder = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extra instanceof WiredExtraOrEval) {
|
||||||
|
conditionEvaluationMode = ((WiredExtraOrEval) extra).getEvaluationMode();
|
||||||
|
conditionEvaluationValue = ((WiredExtraOrEval) extra).getCompareValue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!conditions.isEmpty()) {
|
if (!conditions.isEmpty()) {
|
||||||
int conditionEvaluationMode = WiredExtraOrEval.MODE_ALL;
|
|
||||||
int conditionEvaluationValue = 1;
|
|
||||||
for (InteractionWiredExtra extra : extras) {
|
|
||||||
if (extra instanceof WiredExtraOrEval) {
|
|
||||||
conditionEvaluationMode = ((WiredExtraOrEval) extra).getEvaluationMode();
|
|
||||||
conditionEvaluationValue = ((WiredExtraOrEval) extra).getCompareValue();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!evaluateConditions(conditions, roomUnit, room, stuff, conditionEvaluationMode, conditionEvaluationValue)) {
|
if (!evaluateConditions(conditions, roomUnit, room, stuff, conditionEvaluationMode, conditionEvaluationValue)) {
|
||||||
for (InteractionWiredCondition condition : conditions) {
|
for (InteractionWiredCondition condition : conditions) {
|
||||||
@@ -230,9 +237,6 @@ public class WiredHandler {
|
|||||||
|
|
||||||
trigger.setCooldown(millis);
|
trigger.setCooldown(millis);
|
||||||
|
|
||||||
boolean hasExtraUnseen = room.getRoomSpecialTypes().hasExtraType(trigger.getX(), trigger.getY(), WiredExtraUnseen.class);
|
|
||||||
boolean hasExtraExecuteInOrder = room.getRoomSpecialTypes().hasExtraType(trigger.getX(), trigger.getY(), WiredExtraExecuteInOrder.class);
|
|
||||||
|
|
||||||
for (InteractionWiredExtra extra : extras) {
|
for (InteractionWiredExtra extra : extras) {
|
||||||
extra.activateBox(room, roomUnit, millis);
|
extra.activateBox(room, roomUnit, millis);
|
||||||
}
|
}
|
||||||
@@ -244,7 +248,7 @@ public class WiredHandler {
|
|||||||
executionPlan.executeInOrder = hasExtraExecuteInOrder;
|
executionPlan.executeInOrder = hasExtraExecuteInOrder;
|
||||||
|
|
||||||
if (hasExtraUnseen) {
|
if (hasExtraUnseen) {
|
||||||
for (InteractionWiredExtra extra : room.getRoomSpecialTypes().getExtras(trigger.getX(), trigger.getY())) {
|
for (InteractionWiredExtra extra : extras) {
|
||||||
if (extra instanceof WiredExtraUnseen) {
|
if (extra instanceof WiredExtraUnseen) {
|
||||||
extra.setExtradata(extra.getExtradata().equals("1") ? "0" : "1");
|
extra.setExtradata(extra.getExtradata().equals("1") ? "0" : "1");
|
||||||
InteractionWiredEffect effect = ((WiredExtraUnseen) extra).getUnseenEffect(effectList);
|
InteractionWiredEffect effect = ((WiredExtraUnseen) extra).getUnseenEffect(effectList);
|
||||||
@@ -357,20 +361,14 @@ public class WiredHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LinkedHashSet<Integer> delays = new LinkedHashSet<>();
|
Map<Integer, List<InteractionWiredEffect>> delayBatches = new LinkedHashMap<>();
|
||||||
for (InteractionWiredEffect effect : queueableEffects) {
|
for (InteractionWiredEffect effect : queueableEffects) {
|
||||||
delays.add(effect.getDelay());
|
delayBatches.computeIfAbsent(effect.getDelay(), ignored -> new ArrayList<>()).add(effect);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Integer delay : delays) {
|
for (Map.Entry<Integer, List<InteractionWiredEffect>> entry : delayBatches.entrySet()) {
|
||||||
List<InteractionWiredEffect> delayBatch = new ArrayList<>();
|
Integer delay = entry.getKey();
|
||||||
|
List<InteractionWiredEffect> delayBatch = entry.getValue();
|
||||||
for (InteractionWiredEffect effect : queueableEffects) {
|
|
||||||
if (effect.getDelay() == delay) {
|
|
||||||
delayBatch.add(effect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (delayBatch.isEmpty()) {
|
if (delayBatch.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -424,11 +422,19 @@ public class WiredHandler {
|
|||||||
|
|
||||||
public static GsonBuilder getGsonBuilder() {
|
public static GsonBuilder getGsonBuilder() {
|
||||||
if(gsonBuilder == null) {
|
if(gsonBuilder == null) {
|
||||||
gsonBuilder = new GsonBuilder();
|
synchronized (WiredHandler.class) {
|
||||||
|
if (gsonBuilder == null) {
|
||||||
|
gsonBuilder = new GsonBuilder();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return gsonBuilder;
|
return gsonBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static long toTileCoordinateKey(int x, int y) {
|
||||||
|
return (((long) x) << 32) | (y & 0xffffffffL);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean executeEffectsAtTiles(THashSet<RoomTile> tiles, final RoomUnit roomUnit, final Room room, final Object[] stuff) {
|
public static boolean executeEffectsAtTiles(THashSet<RoomTile> tiles, final RoomUnit roomUnit, final Room room, final Object[] stuff) {
|
||||||
for (RoomTile tile : tiles) {
|
for (RoomTile tile : tiles) {
|
||||||
if (room != null) {
|
if (room != null) {
|
||||||
@@ -470,7 +476,7 @@ public class WiredHandler {
|
|||||||
|
|
||||||
private static void completeReward(Habbo habbo, WiredEffectGiveReward wiredBox, WiredGiveRewardItem reward, int successCode) {
|
private static void completeReward(Habbo habbo, WiredEffectGiveReward wiredBox, WiredGiveRewardItem reward, int successCode) {
|
||||||
if (wiredBox.limit > 0)
|
if (wiredBox.limit > 0)
|
||||||
wiredBox.given++;
|
wiredBox.incrementGiven();
|
||||||
|
|
||||||
persistReward(wiredBox.getId(), habbo.getHabboInfo().getId(), reward.id, Emulator.getIntUnixTimestamp());
|
persistReward(wiredBox.getId(), habbo.getHabboInfo().getId(), reward.id, Emulator.getIntUnixTimestamp());
|
||||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(successCode));
|
habbo.getClient().sendResponse(new WiredRewardAlertComposer(successCode));
|
||||||
@@ -569,93 +575,124 @@ public class WiredHandler {
|
|||||||
|
|
||||||
public static boolean getReward(Habbo habbo, WiredEffectGiveReward wiredBox) {
|
public static boolean getReward(Habbo habbo, WiredEffectGiveReward wiredBox) {
|
||||||
if (wiredBox.limit > 0) {
|
if (wiredBox.limit > 0) {
|
||||||
if (wiredBox.limit - wiredBox.given == 0) {
|
if (wiredBox.limit - wiredBox.getGiven() == 0) {
|
||||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.LIMITED_NO_MORE_AVAILABLE));
|
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.LIMITED_NO_MORE_AVAILABLE));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT COUNT(*) as row_count, wired_rewards_given.* FROM wired_rewards_given WHERE user_id = ? AND wired_item = ? ORDER BY timestamp DESC LIMIT ?", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) {
|
WiredGiveRewardItem rewardToGive = null;
|
||||||
|
int failureCode = -1;
|
||||||
|
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM wired_rewards_given WHERE user_id = ? AND wired_item = ? ORDER BY timestamp DESC LIMIT ?", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) {
|
||||||
statement.setInt(1, habbo.getHabboInfo().getId());
|
statement.setInt(1, habbo.getHabboInfo().getId());
|
||||||
statement.setInt(2, wiredBox.getId());
|
statement.setInt(2, wiredBox.getId());
|
||||||
statement.setInt(3, wiredBox.rewardItems.size());
|
statement.setInt(3, wiredBox.rewardItems.size());
|
||||||
|
|
||||||
try (ResultSet set = statement.executeQuery()) {
|
try (ResultSet set = statement.executeQuery()) {
|
||||||
if (set.first()) {
|
if (set.first()) {
|
||||||
if (set.getInt("row_count") >= 1) {
|
set.last();
|
||||||
|
int rowCount = set.getRow();
|
||||||
|
set.first();
|
||||||
|
|
||||||
|
if (rowCount >= 1) {
|
||||||
if (wiredBox.rewardTime == WiredEffectGiveReward.LIMIT_ONCE) {
|
if (wiredBox.rewardTime == WiredEffectGiveReward.LIMIT_ONCE) {
|
||||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED));
|
failureCode = WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set.beforeFirst();
|
if (failureCode == -1) {
|
||||||
if (set.next()) {
|
|
||||||
if (wiredBox.rewardTime == WiredEffectGiveReward.LIMIT_N_MINUTES) {
|
if (wiredBox.rewardTime == WiredEffectGiveReward.LIMIT_N_MINUTES) {
|
||||||
if (Emulator.getIntUnixTimestamp() - set.getInt("timestamp") <= 60) {
|
if (Emulator.getIntUnixTimestamp() - set.getInt("timestamp") <= 60) {
|
||||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_MINUTE));
|
failureCode = WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_MINUTE;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wiredBox.uniqueRewards) {
|
if (failureCode == -1 && wiredBox.uniqueRewards) {
|
||||||
if (set.getInt("row_count") == wiredBox.rewardItems.size()) {
|
if (rowCount == wiredBox.rewardItems.size()) {
|
||||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALL_COLLECTED));
|
failureCode = WiredRewardAlertComposer.REWARD_ALL_COLLECTED;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wiredBox.rewardTime == WiredEffectGiveReward.LIMIT_N_HOURS) {
|
if (failureCode == -1 && wiredBox.rewardTime == WiredEffectGiveReward.LIMIT_N_HOURS) {
|
||||||
if (!(Emulator.getIntUnixTimestamp() - set.getInt("timestamp") >= (3600 * wiredBox.limitationInterval))) {
|
if (!(Emulator.getIntUnixTimestamp() - set.getInt("timestamp") >= (3600 * wiredBox.limitationInterval))) {
|
||||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_HOUR));
|
failureCode = WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_HOUR;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wiredBox.rewardTime == WiredEffectGiveReward.LIMIT_N_DAY) {
|
if (failureCode == -1 && wiredBox.rewardTime == WiredEffectGiveReward.LIMIT_N_DAY) {
|
||||||
if (!(Emulator.getIntUnixTimestamp() - set.getInt("timestamp") >= (86400 * wiredBox.limitationInterval))) {
|
if (!(Emulator.getIntUnixTimestamp() - set.getInt("timestamp") >= (86400 * wiredBox.limitationInterval))) {
|
||||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_TODAY));
|
failureCode = WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_TODAY;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wiredBox.uniqueRewards) {
|
if (failureCode == -1) {
|
||||||
for (WiredGiveRewardItem item : wiredBox.rewardItems) {
|
if (wiredBox.uniqueRewards) {
|
||||||
set.beforeFirst();
|
for (WiredGiveRewardItem item : wiredBox.rewardItems) {
|
||||||
boolean found = false;
|
set.beforeFirst();
|
||||||
|
boolean found = false;
|
||||||
|
|
||||||
while (set.next()) {
|
while (set.next()) {
|
||||||
if (set.getInt("reward_id") == item.id)
|
if (set.getInt("reward_id") == item.id)
|
||||||
found = true;
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
rewardToGive = item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!found) {
|
if (rewardToGive == null) {
|
||||||
return giveReward(habbo, wiredBox, item);
|
failureCode = WiredRewardAlertComposer.REWARD_ALL_COLLECTED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALL_COLLECTED));
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
int randomNumber = Emulator.getRandom().nextInt(101);
|
|
||||||
|
|
||||||
int count = 0;
|
|
||||||
for (WiredGiveRewardItem item : wiredBox.rewardItems) {
|
|
||||||
if (randomNumber >= count && randomNumber <= (count + item.probability)) {
|
|
||||||
return giveReward(habbo, wiredBox, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
count += item.probability;
|
|
||||||
}
|
|
||||||
|
|
||||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.UNLUCKY_NO_REWARD));
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
LOGGER.error("Caught SQL exception", e);
|
LOGGER.error("Caught SQL exception", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failureCode != -1) {
|
||||||
|
habbo.getClient().sendResponse(new WiredRewardAlertComposer(failureCode));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no unique reward was determined and there are no failures, pick a random reward or the first unique one
|
||||||
|
if (rewardToGive == null) {
|
||||||
|
if (wiredBox.uniqueRewards) {
|
||||||
|
if (!wiredBox.rewardItems.isEmpty()) {
|
||||||
|
rewardToGive = wiredBox.rewardItems.get(0);
|
||||||
|
} else {
|
||||||
|
failureCode = WiredRewardAlertComposer.REWARD_ALL_COLLECTED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int randomNumber = Emulator.getRandom().nextInt(101);
|
||||||
|
int count = 0;
|
||||||
|
for (WiredGiveRewardItem item : wiredBox.rewardItems) {
|
||||||
|
if (randomNumber >= count && randomNumber <= (count + item.probability)) {
|
||||||
|
rewardToGive = item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
count += item.probability;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rewardToGive == null) {
|
||||||
|
failureCode = WiredRewardAlertComposer.UNLUCKY_NO_REWARD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failureCode != -1) {
|
||||||
|
habbo.getClient().sendResponse(new WiredRewardAlertComposer(failureCode));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rewardToGive != null) {
|
||||||
|
return giveReward(habbo, wiredBox, rewardToGive);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
+19
-23
@@ -71,27 +71,12 @@ public final class RoomWiredStackIndex implements WiredStackIndex {
|
|||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check cache first
|
|
||||||
if (useCache) {
|
if (useCache) {
|
||||||
Map<WiredEvent.Type, List<WiredStack>> roomCache = cache.get(room.getId());
|
return cache.computeIfAbsent(room.getId(), k -> new ConcurrentHashMap<>())
|
||||||
if (roomCache != null) {
|
.computeIfAbsent(type, t -> buildStacks(room, t));
|
||||||
List<WiredStack> cached = roomCache.get(type);
|
} else {
|
||||||
if (cached != null) {
|
return buildStacks(room, type);
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build stacks for this event type
|
|
||||||
List<WiredStack> stacks = buildStacks(room, type);
|
|
||||||
|
|
||||||
// Cache the result
|
|
||||||
if (useCache) {
|
|
||||||
cache.computeIfAbsent(room.getId(), k -> new ConcurrentHashMap<>())
|
|
||||||
.put(type, stacks);
|
|
||||||
}
|
|
||||||
|
|
||||||
return stacks;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -206,16 +191,27 @@ public final class RoomWiredStackIndex implements WiredStackIndex {
|
|||||||
THashSet<InteractionWiredExtra> extras = specialTypes.getExtras(x, y);
|
THashSet<InteractionWiredExtra> extras = specialTypes.getExtras(x, y);
|
||||||
int conditionEvaluationMode = WiredExtraOrEval.MODE_ALL;
|
int conditionEvaluationMode = WiredExtraOrEval.MODE_ALL;
|
||||||
int conditionEvaluationValue = 1;
|
int conditionEvaluationValue = 1;
|
||||||
boolean useRandom = specialTypes.hasExtraType(x, y, WiredExtraRandom.class);
|
boolean useRandom = false;
|
||||||
boolean useUnseen = specialTypes.hasExtraType(x, y, WiredExtraUnseen.class);
|
boolean useUnseen = false;
|
||||||
boolean executeInOrder = specialTypes.hasExtraType(x, y, WiredExtraExecuteInOrder.class);
|
boolean executeInOrder = false;
|
||||||
|
|
||||||
if (extras != null) {
|
if (extras != null) {
|
||||||
for (InteractionWiredExtra extra : extras) {
|
for (InteractionWiredExtra extra : extras) {
|
||||||
|
if (!useRandom && extra instanceof WiredExtraRandom) {
|
||||||
|
useRandom = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!useUnseen && extra instanceof WiredExtraUnseen) {
|
||||||
|
useUnseen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!executeInOrder && extra instanceof WiredExtraExecuteInOrder) {
|
||||||
|
executeInOrder = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (extra instanceof WiredExtraOrEval) {
|
if (extra instanceof WiredExtraOrEval) {
|
||||||
conditionEvaluationMode = ((WiredExtraOrEval) extra).getEvaluationMode();
|
conditionEvaluationMode = ((WiredExtraOrEval) extra).getEvaluationMode();
|
||||||
conditionEvaluationValue = ((WiredExtraOrEval) extra).getCompareValue();
|
conditionEvaluationValue = ((WiredExtraOrEval) extra).getCompareValue();
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,10 +86,10 @@ public final class WiredEngine {
|
|||||||
public static int MONITOR_USAGE_WINDOW_MS = 1000;
|
public static int MONITOR_USAGE_WINDOW_MS = 1000;
|
||||||
|
|
||||||
/** Monitor execution cap per room window */
|
/** Monitor execution cap per room window */
|
||||||
public static int MONITOR_USAGE_LIMIT = 1000;
|
public static int MONITOR_USAGE_LIMIT = 50000;
|
||||||
|
|
||||||
/** Maximum delayed events allowed per room at the same time */
|
/** Maximum delayed events allowed per room at the same time */
|
||||||
public static int MONITOR_DELAYED_EVENTS_LIMIT = 100;
|
public static int MONITOR_DELAYED_EVENTS_LIMIT = 50000;
|
||||||
|
|
||||||
/** Average execution threshold that marks overload */
|
/** Average execution threshold that marks overload */
|
||||||
public static int MONITOR_OVERLOAD_AVERAGE_MS = 50;
|
public static int MONITOR_OVERLOAD_AVERAGE_MS = 50;
|
||||||
@@ -180,14 +180,19 @@ public final class WiredEngine {
|
|||||||
|
|
||||||
int roomId = room.getId();
|
int roomId = room.getId();
|
||||||
|
|
||||||
|
if (this.isRoomBanned(roomId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Soft rate limiting to prevent rapid-fire event spam without banning whole rooms
|
// Soft rate limiting to prevent rapid-fire event spam without banning whole rooms
|
||||||
if (isRateLimited(roomId, room, event.getType())) {
|
if (isRateLimited(roomId, room, event.getType())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check and increment recursion depth to prevent infinite loops
|
// Check and increment recursion depth to prevent infinite loops
|
||||||
int currentDepth = roomRecursionDepth.getOrDefault(roomId, 0);
|
int currentDepth = roomRecursionDepth.merge(roomId, 1, Integer::sum);
|
||||||
if (currentDepth >= MAX_RECURSION_DEPTH) {
|
if (currentDepth > MAX_RECURSION_DEPTH) {
|
||||||
|
roomRecursionDepth.merge(roomId, -1, Integer::sum);
|
||||||
getDiagnostics(roomId).recordRecursionTimeout(
|
getDiagnostics(roomId).recordRecursionTimeout(
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
String.format("Recursion depth %d/%d while handling %s", currentDepth, MAX_RECURSION_DEPTH, event.getType().name()),
|
String.format("Recursion depth %d/%d while handling %s", currentDepth, MAX_RECURSION_DEPTH, event.getType().name()),
|
||||||
@@ -199,18 +204,12 @@ public final class WiredEngine {
|
|||||||
debug(room, "RECURSION LIMIT REACHED - aborting to prevent crash");
|
debug(room, "RECURSION LIMIT REACHED - aborting to prevent crash");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
roomRecursionDepth.put(roomId, currentDepth + 1);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return handleEventInternal(event, room, negateConditions);
|
return handleEventInternal(event, room, negateConditions);
|
||||||
} finally {
|
} finally {
|
||||||
// Decrement recursion depth
|
// Decrement recursion depth
|
||||||
int newDepth = roomRecursionDepth.getOrDefault(roomId, 1) - 1;
|
roomRecursionDepth.compute(roomId, (k, v) -> (v == null || v <= 1) ? null : v - 1);
|
||||||
if (newDepth <= 0) {
|
|
||||||
roomRecursionDepth.remove(roomId);
|
|
||||||
} else {
|
|
||||||
roomRecursionDepth.put(roomId, newDepth);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,28 +233,27 @@ public final class WiredEngine {
|
|||||||
|
|
||||||
int roomId = room.getId();
|
int roomId = room.getId();
|
||||||
|
|
||||||
|
if (this.isRoomBanned(roomId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (isRateLimited(roomId, room, event.getType())) {
|
if (isRateLimited(roomId, room, event.getType())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int currentDepth = roomRecursionDepth.getOrDefault(roomId, 0);
|
int currentDepth = roomRecursionDepth.merge(roomId, 1, Integer::sum);
|
||||||
if (currentDepth >= MAX_RECURSION_DEPTH) {
|
if (currentDepth > MAX_RECURSION_DEPTH) {
|
||||||
|
roomRecursionDepth.merge(roomId, -1, Integer::sum);
|
||||||
LOGGER.warn("Wired recursion limit reached in room {} (depth: {}). " +
|
LOGGER.warn("Wired recursion limit reached in room {} (depth: {}). " +
|
||||||
"Possible infinite loop detected (source item execution). Aborting.", roomId, currentDepth);
|
"Possible infinite loop detected (source item execution). Aborting.", roomId, currentDepth);
|
||||||
debug(room, "RECURSION LIMIT REACHED - aborting source-item execution");
|
debug(room, "RECURSION LIMIT REACHED - aborting source-item execution");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
roomRecursionDepth.put(roomId, currentDepth + 1);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return handleEventForSourceItemInternal(event, room, sourceItemId);
|
return handleEventForSourceItemInternal(event, room, sourceItemId);
|
||||||
} finally {
|
} finally {
|
||||||
int newDepth = roomRecursionDepth.getOrDefault(roomId, 1) - 1;
|
roomRecursionDepth.compute(roomId, (k, v) -> (v == null || v <= 1) ? null : v - 1);
|
||||||
if (newDepth <= 0) {
|
|
||||||
roomRecursionDepth.remove(roomId);
|
|
||||||
} else {
|
|
||||||
roomRecursionDepth.put(roomId, newDepth);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1094,7 +1092,13 @@ public final class WiredEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void animateFilteredSelectorBox(Room room, InteractionWiredEffect wiredEffect) {
|
private void animateFilteredSelectorBox(Room room, InteractionWiredEffect wiredEffect) {
|
||||||
if (room == null || wiredEffect == null || room.isHideWired()) {
|
if (room == null || wiredEffect == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If wired is hidden, skip animation but ensure any stale token is cleaned up
|
||||||
|
if (room.isHideWired()) {
|
||||||
|
this.filteredSelectorAnimationTokens.remove(wiredEffect.getId());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1364,11 +1368,10 @@ public final class WiredEngine {
|
|||||||
? String.valueOf(stack.triggerItem().getId())
|
? String.valueOf(stack.triggerItem().getId())
|
||||||
: "default";
|
: "default";
|
||||||
|
|
||||||
int current = unseenIndices.getOrDefault(key, -1);
|
return unseenIndices.compute(key, (k, current) -> {
|
||||||
int next = (current + 1) % effectCount;
|
if (current == null) current = -1;
|
||||||
unseenIndices.put(key, next);
|
return (current + 1) % effectCount;
|
||||||
|
});
|
||||||
return next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1622,6 +1625,8 @@ public final class WiredEngine {
|
|||||||
clearRoomRecursionDepth(roomId);
|
clearRoomRecursionDepth(roomId);
|
||||||
clearRoomRateLimiters(roomId);
|
clearRoomRateLimiters(roomId);
|
||||||
clearRoomSourceStackCache(roomId);
|
clearRoomSourceStackCache(roomId);
|
||||||
|
clearRoomDiagnostics(roomId);
|
||||||
|
clearRoomBan(roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1684,38 +1689,46 @@ public final class WiredEngine {
|
|||||||
* @param room the room object
|
* @param room the room object
|
||||||
*/
|
*/
|
||||||
private void banRoom(int roomId, Room room, WiredEvent.Type eventType, int eventCount) {
|
private void banRoom(int roomId, Room room, WiredEvent.Type eventType, int eventCount) {
|
||||||
long banExpiry = System.currentTimeMillis() + WIRED_BAN_DURATION_MS;
|
|
||||||
bannedRooms.put(roomId, banExpiry);
|
|
||||||
getDiagnostics(roomId).recordKilled(
|
getDiagnostics(roomId).recordKilled(
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
String.format("Rate limit exceeded for %s with %d event(s) in %dms", eventType.name(), eventCount, RATE_LIMIT_WINDOW_MS),
|
String.format("Rate limit exceeded for %s with %d event(s) in %dms", eventType.name(), eventCount, RATE_LIMIT_WINDOW_MS),
|
||||||
eventType.name(),
|
eventType.name(),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
|
||||||
long banMinutes = WIRED_BAN_DURATION_MS / 60000;
|
// Only actually ban the room if ban duration is configured (> 0)
|
||||||
|
if (WIRED_BAN_DURATION_MS > 0) {
|
||||||
// Send alert to all users in the room
|
long banExpiry = System.currentTimeMillis() + WIRED_BAN_DURATION_MS;
|
||||||
String roomAlertMessage = Emulator.getTexts().getValue("wired.abuse.room.alert")
|
bannedRooms.put(roomId, banExpiry);
|
||||||
.replace("%minutes%", String.valueOf(banMinutes));
|
|
||||||
room.sendComposer(new GenericAlertComposer(roomAlertMessage).compose());
|
long banMinutes = WIRED_BAN_DURATION_MS / 60000;
|
||||||
|
|
||||||
// Send scripter bubble alert to staff with room link
|
// Send alert to all users in the room
|
||||||
THashMap<String, String> keys = new THashMap<>();
|
String roomAlertMessage = Emulator.getTexts().getValue("wired.abuse.room.alert")
|
||||||
keys.put("title", Emulator.getTexts().getValue("wired.abuse.staff.title"));
|
.replace("%minutes%", String.valueOf(banMinutes));
|
||||||
keys.put("message", Emulator.getTexts().getValue("wired.abuse.staff.message")
|
room.sendComposer(new GenericAlertComposer(roomAlertMessage).compose());
|
||||||
.replace("%roomname%", room.getName())
|
|
||||||
.replace("%owner%", room.getOwnerName())
|
// Send scripter bubble alert to staff with room link
|
||||||
.replace("%minutes%", String.valueOf(banMinutes)));
|
THashMap<String, String> keys = new THashMap<>();
|
||||||
keys.put("linkUrl", "event:navigator/goto/" + roomId);
|
keys.put("title", Emulator.getTexts().getValue("wired.abuse.staff.title"));
|
||||||
keys.put("linkTitle", Emulator.getTexts().getValue("wired.abuse.staff.link"));
|
keys.put("message", Emulator.getTexts().getValue("wired.abuse.staff.message")
|
||||||
Emulator.getGameEnvironment().getHabboManager().sendPacketToHabbosWithPermission(
|
.replace("%roomname%", room.getName())
|
||||||
new BubbleAlertComposer("admin.staffalert", keys).compose(),
|
.replace("%owner%", room.getOwnerName())
|
||||||
"acc_modtool_room_info"
|
.replace("%minutes%", String.valueOf(banMinutes)));
|
||||||
);
|
keys.put("linkUrl", "event:navigator/goto/" + roomId);
|
||||||
|
keys.put("linkTitle", Emulator.getTexts().getValue("wired.abuse.staff.link"));
|
||||||
LOGGER.warn("Wired abuse detected in room {} ({}). Owner: {}. Wired banned for {} minutes.",
|
Emulator.getGameEnvironment().getHabboManager().sendPacketToHabbosWithPermission(
|
||||||
roomId, room.getName(), room.getOwnerName(), banMinutes);
|
new BubbleAlertComposer("admin.staffalert", keys).compose(),
|
||||||
|
"acc_modtool_room_info"
|
||||||
|
);
|
||||||
|
|
||||||
|
LOGGER.warn("Wired abuse detected in room {} ({}). Owner: {}. Wired banned for {} minutes.",
|
||||||
|
roomId, room.getName(), room.getOwnerName(), banMinutes);
|
||||||
|
} else {
|
||||||
|
// Ban duration is 0 - only log, do not spam alerts or put a ban entry
|
||||||
|
LOGGER.warn("Wired rate limit exceeded in room {} ({}) for event {} ({} events). Ban disabled (wired.abuse.ban.duration.ms=0).",
|
||||||
|
roomId, room.getName(), eventType.name(), eventCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -913,10 +913,7 @@ public final class WiredManager {
|
|||||||
if (room != null) {
|
if (room != null) {
|
||||||
room.getFurniVariableManager().clearTransientAssignments();
|
room.getFurniVariableManager().clearTransientAssignments();
|
||||||
room.getRoomVariableManager().clearTransientAssignments();
|
room.getRoomVariableManager().clearTransientAssignments();
|
||||||
}
|
invalidateRoom(room);
|
||||||
|
|
||||||
if (engine != null && room != null) {
|
|
||||||
engine.clearRoomExecutionCaches(room.getId());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1112,18 +1109,16 @@ public final class WiredManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void persistReward(int wiredId, int habboId, int rewardId, int timestamp) {
|
private static void persistReward(int wiredId, int habboId, int rewardId, int timestamp) {
|
||||||
Emulator.getThreading().run(() -> {
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
PreparedStatement statement = connection.prepareStatement("INSERT INTO wired_rewards_given (wired_item, user_id, reward_id, timestamp) VALUES (?, ?, ?, ?)")) {
|
||||||
PreparedStatement statement = connection.prepareStatement("INSERT INTO wired_rewards_given (wired_item, user_id, reward_id, timestamp) VALUES ( ?, ?, ?, ?)")) {
|
statement.setInt(1, wiredId);
|
||||||
statement.setInt(1, wiredId);
|
statement.setInt(2, habboId);
|
||||||
statement.setInt(2, habboId);
|
statement.setInt(3, rewardId);
|
||||||
statement.setInt(3, rewardId);
|
statement.setInt(4, timestamp);
|
||||||
statement.setInt(4, timestamp);
|
statement.execute();
|
||||||
statement.execute();
|
} catch (SQLException e) {
|
||||||
} catch (SQLException e) {
|
LOGGER.error("Caught SQL exception", e);
|
||||||
LOGGER.error("Caught SQL exception", e);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void completeReward(Habbo habbo, WiredEffectGiveReward wiredBox, WiredGiveRewardItem reward, int successCode) {
|
private static void completeReward(Habbo habbo, WiredEffectGiveReward wiredBox, WiredGiveRewardItem reward, int successCode) {
|
||||||
@@ -1246,96 +1241,128 @@ public final class WiredManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean getReward(Habbo habbo, WiredEffectGiveReward wiredBox) {
|
public static boolean getReward(Habbo habbo, WiredEffectGiveReward wiredBox) {
|
||||||
if (wiredBox.getLimit() > 0) {
|
synchronized (wiredBox) {
|
||||||
if (wiredBox.getLimit() - wiredBox.getGiven() == 0) {
|
if (wiredBox.getLimit() > 0) {
|
||||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.LIMITED_NO_MORE_AVAILABLE));
|
if (wiredBox.getLimit() - wiredBox.getGiven() == 0) {
|
||||||
|
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.LIMITED_NO_MORE_AVAILABLE));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WiredGiveRewardItem rewardToGive = null;
|
||||||
|
int failureCode = -1;
|
||||||
|
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM wired_rewards_given WHERE user_id = ? AND wired_item = ? ORDER BY timestamp DESC LIMIT ?", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) {
|
||||||
|
statement.setInt(1, habbo.getHabboInfo().getId());
|
||||||
|
statement.setInt(2, wiredBox.getId());
|
||||||
|
statement.setInt(3, wiredBox.getRewardItems().size());
|
||||||
|
|
||||||
|
try (ResultSet set = statement.executeQuery()) {
|
||||||
|
if (set.first()) {
|
||||||
|
set.last();
|
||||||
|
int rowCount = set.getRow();
|
||||||
|
set.first();
|
||||||
|
|
||||||
|
if (rowCount >= 1) {
|
||||||
|
if (wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_ONCE) {
|
||||||
|
failureCode = WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failureCode == -1) {
|
||||||
|
if (wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_N_MINUTES) {
|
||||||
|
if (Emulator.getIntUnixTimestamp() - set.getInt("timestamp") <= 60) {
|
||||||
|
failureCode = WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_MINUTE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failureCode == -1 && wiredBox.isUniqueRewards()) {
|
||||||
|
if (rowCount == wiredBox.getRewardItems().size()) {
|
||||||
|
failureCode = WiredRewardAlertComposer.REWARD_ALL_COLLECTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failureCode == -1 && wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_N_HOURS) {
|
||||||
|
if (!(Emulator.getIntUnixTimestamp() - set.getInt("timestamp") >= (3600 * wiredBox.getLimitationInterval()))) {
|
||||||
|
failureCode = WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_HOUR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failureCode == -1 && wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_N_DAY) {
|
||||||
|
if (!(Emulator.getIntUnixTimestamp() - set.getInt("timestamp") >= (86400 * wiredBox.getLimitationInterval()))) {
|
||||||
|
failureCode = WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_TODAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failureCode == -1) {
|
||||||
|
if (wiredBox.isUniqueRewards()) {
|
||||||
|
for (WiredGiveRewardItem item : wiredBox.getRewardItems()) {
|
||||||
|
set.beforeFirst();
|
||||||
|
boolean found = false;
|
||||||
|
|
||||||
|
while (set.next()) {
|
||||||
|
if (set.getInt("reward_id") == item.id)
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
rewardToGive = item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rewardToGive == null) {
|
||||||
|
failureCode = WiredRewardAlertComposer.REWARD_ALL_COLLECTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Caught SQL exception", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT COUNT(*) as row_count, wired_rewards_given.* FROM wired_rewards_given WHERE user_id = ? AND wired_item = ? ORDER BY timestamp DESC LIMIT ?", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) {
|
if (failureCode != -1) {
|
||||||
statement.setInt(1, habbo.getHabboInfo().getId());
|
habbo.getClient().sendResponse(new WiredRewardAlertComposer(failureCode));
|
||||||
statement.setInt(2, wiredBox.getId());
|
return false;
|
||||||
statement.setInt(3, wiredBox.getRewardItems().size());
|
}
|
||||||
|
|
||||||
try (ResultSet set = statement.executeQuery()) {
|
if (rewardToGive == null) {
|
||||||
if (set.first()) {
|
if (wiredBox.isUniqueRewards()) {
|
||||||
if (set.getInt("row_count") >= 1) {
|
if (!wiredBox.getRewardItems().isEmpty()) {
|
||||||
if (wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_ONCE) {
|
rewardToGive = wiredBox.getRewardItems().get(0);
|
||||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set.beforeFirst();
|
|
||||||
if (set.next()) {
|
|
||||||
if (wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_N_MINUTES) {
|
|
||||||
if (Emulator.getIntUnixTimestamp() - set.getInt("timestamp") <= 60) {
|
|
||||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_MINUTE));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wiredBox.isUniqueRewards()) {
|
|
||||||
if (set.getInt("row_count") == wiredBox.getRewardItems().size()) {
|
|
||||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALL_COLLECTED));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_N_HOURS) {
|
|
||||||
if (!(Emulator.getIntUnixTimestamp() - set.getInt("timestamp") >= (3600 * wiredBox.getLimitationInterval()))) {
|
|
||||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_HOUR));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_N_DAY) {
|
|
||||||
if (!(Emulator.getIntUnixTimestamp() - set.getInt("timestamp") >= (86400 * wiredBox.getLimitationInterval()))) {
|
|
||||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_TODAY));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wiredBox.isUniqueRewards()) {
|
|
||||||
for (WiredGiveRewardItem item : wiredBox.getRewardItems()) {
|
|
||||||
set.beforeFirst();
|
|
||||||
boolean found = false;
|
|
||||||
|
|
||||||
while (set.next()) {
|
|
||||||
if (set.getInt("reward_id") == item.id)
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
return giveReward(habbo, wiredBox, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALL_COLLECTED));
|
|
||||||
return false;
|
|
||||||
} else {
|
} else {
|
||||||
int randomNumber = Emulator.getRandom().nextInt(101);
|
failureCode = WiredRewardAlertComposer.REWARD_ALL_COLLECTED;
|
||||||
|
}
|
||||||
int count = 0;
|
} else {
|
||||||
for (WiredGiveRewardItem item : wiredBox.getRewardItems()) {
|
int randomNumber = Emulator.getRandom().nextInt(101);
|
||||||
if (randomNumber >= count && randomNumber <= (count + item.probability)) {
|
int count = 0;
|
||||||
return giveReward(habbo, wiredBox, item);
|
for (WiredGiveRewardItem item : wiredBox.getRewardItems()) {
|
||||||
}
|
if (randomNumber >= count && randomNumber <= (count + item.probability)) {
|
||||||
|
rewardToGive = item;
|
||||||
count += item.probability;
|
break;
|
||||||
}
|
}
|
||||||
|
count += item.probability;
|
||||||
|
}
|
||||||
|
|
||||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.UNLUCKY_NO_REWARD));
|
if (rewardToGive == null) {
|
||||||
return false;
|
failureCode = WiredRewardAlertComposer.UNLUCKY_NO_REWARD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
|
||||||
LOGGER.error("Caught SQL exception", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
if (failureCode != -1) {
|
||||||
|
habbo.getClient().sendResponse(new WiredRewardAlertComposer(failureCode));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rewardToGive != null) {
|
||||||
|
return giveReward(habbo, wiredBox, rewardToGive);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1105,4 +1105,11 @@ public final class WiredMoveCarryHelper {
|
|||||||
this.expiresAt = System.currentTimeMillis() + USER_FOLLOWER_TTL_MS;
|
this.expiresAt = System.currentTimeMillis() + USER_FOLLOWER_TTL_MS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void cleanupRoomUnit(RoomUnit roomUnit) {
|
||||||
|
if (roomUnit != null) {
|
||||||
|
SUPPRESSED_STATUS_COMPOSER_UNTIL.remove(roomUnit.getId());
|
||||||
|
ACTIVE_USER_FOLLOWERS.remove(roomUnit.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-65
@@ -266,19 +266,19 @@ public final class WiredRoomDiagnostics {
|
|||||||
private final ArrayDeque<HistoryEntry> history;
|
private final ArrayDeque<HistoryEntry> history;
|
||||||
private final int maxHistoryEntries;
|
private final int maxHistoryEntries;
|
||||||
|
|
||||||
private long windowStartedAt;
|
private final java.util.concurrent.atomic.AtomicLong windowStartedAt = new java.util.concurrent.atomic.AtomicLong();
|
||||||
private int usageCurrentWindow;
|
private final java.util.concurrent.atomic.AtomicInteger usageCurrentWindow = new java.util.concurrent.atomic.AtomicInteger();
|
||||||
private int delayedEventsPending;
|
private final java.util.concurrent.atomic.AtomicInteger delayedEventsPending = new java.util.concurrent.atomic.AtomicInteger();
|
||||||
private long totalExecutionMsCurrentWindow;
|
private final java.util.concurrent.atomic.AtomicLong totalExecutionMsCurrentWindow = new java.util.concurrent.atomic.AtomicLong();
|
||||||
private int executionSamplesCurrentWindow;
|
private final java.util.concurrent.atomic.AtomicInteger executionSamplesCurrentWindow = new java.util.concurrent.atomic.AtomicInteger();
|
||||||
private int averageExecutionMs;
|
private volatile int averageExecutionMs;
|
||||||
private int peakExecutionMs;
|
private volatile int peakExecutionMs;
|
||||||
private int consecutiveHeavyWindows;
|
private volatile int consecutiveHeavyWindows;
|
||||||
private int consecutiveOverloadWindows;
|
private volatile int consecutiveOverloadWindows;
|
||||||
private boolean heavy;
|
private volatile boolean heavy;
|
||||||
private String peakExecutionSourceLabel;
|
private volatile String peakExecutionSourceLabel;
|
||||||
private int peakExecutionSourceId;
|
private volatile int peakExecutionSourceId;
|
||||||
private String peakExecutionReason;
|
private volatile String peakExecutionReason;
|
||||||
|
|
||||||
public WiredRoomDiagnostics(int usageWindowMs, int usageLimitPerWindow, int delayedEventsLimit,
|
public WiredRoomDiagnostics(int usageWindowMs, int usageLimitPerWindow, int delayedEventsLimit,
|
||||||
int overloadAverageThresholdMs, int overloadPeakThresholdMs,
|
int overloadAverageThresholdMs, int overloadPeakThresholdMs,
|
||||||
@@ -310,67 +310,69 @@ public final class WiredRoomDiagnostics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean tryConsumeExecutionBudget(int estimatedCost, long now, String sourceLabel, int sourceId, String reason) {
|
public boolean tryConsumeExecutionBudget(int estimatedCost, long now, String sourceLabel, int sourceId, String reason) {
|
||||||
rollWindow(now);
|
rollWindowIfNeeded(now);
|
||||||
|
|
||||||
int normalizedCost = Math.max(0, estimatedCost);
|
int normalizedCost = Math.max(0, estimatedCost);
|
||||||
if ((this.usageCurrentWindow + normalizedCost) > this.usageLimitPerWindow) {
|
int currentUsage = this.usageCurrentWindow.addAndGet(normalizedCost);
|
||||||
|
if (currentUsage > this.usageLimitPerWindow) {
|
||||||
record(Type.EXECUTION_CAP, now,
|
record(Type.EXECUTION_CAP, now,
|
||||||
buildExecutionCapReason(normalizedCost, reason),
|
buildExecutionCapReason(normalizedCost, reason, currentUsage),
|
||||||
sourceLabel,
|
sourceLabel,
|
||||||
sourceId);
|
sourceId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.usageCurrentWindow += normalizedCost;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean tryScheduleDelayedEvent(long now, String sourceLabel, int sourceId, String reason) {
|
public boolean tryScheduleDelayedEvent(long now, String sourceLabel, int sourceId, String reason) {
|
||||||
rollWindow(now);
|
rollWindowIfNeeded(now);
|
||||||
|
|
||||||
if ((this.delayedEventsPending + 1) > this.delayedEventsLimit) {
|
int currentPending = this.delayedEventsPending.incrementAndGet();
|
||||||
|
if (currentPending > this.delayedEventsLimit) {
|
||||||
record(Type.DELAYED_EVENTS_CAP, now,
|
record(Type.DELAYED_EVENTS_CAP, now,
|
||||||
buildDelayedCapReason(reason),
|
buildDelayedCapReason(reason, currentPending),
|
||||||
sourceLabel,
|
sourceLabel,
|
||||||
sourceId);
|
sourceId);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.delayedEventsPending++;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void completeDelayedEvent() {
|
public void completeDelayedEvent() {
|
||||||
if (this.delayedEventsPending > 0) {
|
this.delayedEventsPending.updateAndGet(v -> v > 0 ? v - 1 : 0);
|
||||||
this.delayedEventsPending--;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void recordExecution(long elapsedMs, long now, String sourceLabel, int sourceId, String reason) {
|
public void recordExecution(long elapsedMs, long now, String sourceLabel, int sourceId, String reason) {
|
||||||
rollWindow(now);
|
rollWindowIfNeeded(now);
|
||||||
|
|
||||||
int normalizedElapsed = (int) Math.max(0L, elapsedMs);
|
int normalizedElapsed = (int) Math.max(0L, elapsedMs);
|
||||||
|
|
||||||
this.totalExecutionMsCurrentWindow += normalizedElapsed;
|
long total = this.totalExecutionMsCurrentWindow.addAndGet(normalizedElapsed);
|
||||||
this.executionSamplesCurrentWindow++;
|
int samples = this.executionSamplesCurrentWindow.incrementAndGet();
|
||||||
this.averageExecutionMs = (int) Math.round(this.totalExecutionMsCurrentWindow / (double) this.executionSamplesCurrentWindow);
|
this.averageExecutionMs = (int) Math.round(total / (double) samples);
|
||||||
|
|
||||||
if (normalizedElapsed >= this.peakExecutionMs) {
|
if (normalizedElapsed >= this.peakExecutionMs) {
|
||||||
this.peakExecutionMs = normalizedElapsed;
|
synchronized (this) {
|
||||||
this.peakExecutionSourceLabel = sanitizeSourceLabel(sourceLabel);
|
if (normalizedElapsed >= this.peakExecutionMs) {
|
||||||
this.peakExecutionSourceId = Math.max(0, sourceId);
|
this.peakExecutionMs = normalizedElapsed;
|
||||||
this.peakExecutionReason = sanitizeReason(reason);
|
this.peakExecutionSourceLabel = sanitizeSourceLabel(sourceLabel);
|
||||||
|
this.peakExecutionSourceId = Math.max(0, sourceId);
|
||||||
|
this.peakExecutionReason = sanitizeReason(reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void recordKilled(long now, String reason, String sourceLabel, int sourceId) {
|
public void recordKilled(long now, String reason, String sourceLabel, int sourceId) {
|
||||||
rollWindow(now);
|
rollWindowIfNeeded(now);
|
||||||
record(Type.KILLED, now, reason, sourceLabel, sourceId);
|
record(Type.KILLED, now, reason, sourceLabel, sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void recordRecursionTimeout(long now, String reason, String sourceLabel, int sourceId) {
|
public void recordRecursionTimeout(long now, String reason, String sourceLabel, int sourceId) {
|
||||||
rollWindow(now);
|
rollWindowIfNeeded(now);
|
||||||
record(Type.RECURSION_TIMEOUT, now, reason, sourceLabel, sourceId);
|
record(Type.RECURSION_TIMEOUT, now, reason, sourceLabel, sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +396,7 @@ public final class WiredRoomDiagnostics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Snapshot snapshot(int recursionDepthCurrent, int recursionDepthLimit, long killedUntilMs, long now) {
|
public synchronized Snapshot snapshot(int recursionDepthCurrent, int recursionDepthLimit, long killedUntilMs, long now) {
|
||||||
rollWindow(now);
|
rollWindowIfNeeded(now);
|
||||||
|
|
||||||
List<LogEntry> logEntries = new ArrayList<>(Type.values().length);
|
List<LogEntry> logEntries = new ArrayList<>(Type.values().length);
|
||||||
List<HistoryEntry> historyEntries = new ArrayList<>(this.history.size());
|
List<HistoryEntry> historyEntries = new ArrayList<>(this.history.size());
|
||||||
@@ -422,10 +424,10 @@ public final class WiredRoomDiagnostics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Snapshot(
|
return new Snapshot(
|
||||||
this.usageCurrentWindow,
|
this.usageCurrentWindow.get(),
|
||||||
this.usageLimitPerWindow,
|
this.usageLimitPerWindow,
|
||||||
this.heavy,
|
this.heavy,
|
||||||
this.delayedEventsPending,
|
this.delayedEventsPending.get(),
|
||||||
this.delayedEventsLimit,
|
this.delayedEventsLimit,
|
||||||
this.averageExecutionMs,
|
this.averageExecutionMs,
|
||||||
this.peakExecutionMs,
|
this.peakExecutionMs,
|
||||||
@@ -444,30 +446,40 @@ public final class WiredRoomDiagnostics {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rollWindow(long now) {
|
private void rollWindowIfNeeded(long now) {
|
||||||
if (this.windowStartedAt <= 0L) {
|
long startedAt = this.windowStartedAt.get();
|
||||||
this.windowStartedAt = now;
|
if (startedAt <= 0L) {
|
||||||
|
this.windowStartedAt.compareAndSet(startedAt, now);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while ((now - this.windowStartedAt) >= this.usageWindowMs) {
|
if ((now - startedAt) >= this.usageWindowMs) {
|
||||||
evaluateWindow(this.windowStartedAt + this.usageWindowMs);
|
synchronized (this) {
|
||||||
this.windowStartedAt += this.usageWindowMs;
|
startedAt = this.windowStartedAt.get();
|
||||||
this.usageCurrentWindow = 0;
|
if ((now - startedAt) >= this.usageWindowMs) {
|
||||||
this.totalExecutionMsCurrentWindow = 0L;
|
while ((now - startedAt) >= this.usageWindowMs) {
|
||||||
this.executionSamplesCurrentWindow = 0;
|
evaluateWindow(startedAt + this.usageWindowMs);
|
||||||
this.averageExecutionMs = 0;
|
startedAt += this.usageWindowMs;
|
||||||
this.peakExecutionMs = 0;
|
|
||||||
this.peakExecutionSourceLabel = null;
|
this.usageCurrentWindow.set(0);
|
||||||
this.peakExecutionSourceId = 0;
|
this.totalExecutionMsCurrentWindow.set(0L);
|
||||||
this.peakExecutionReason = null;
|
this.executionSamplesCurrentWindow.set(0);
|
||||||
|
this.averageExecutionMs = 0;
|
||||||
|
this.peakExecutionMs = 0;
|
||||||
|
this.peakExecutionSourceLabel = null;
|
||||||
|
this.peakExecutionSourceId = 0;
|
||||||
|
this.peakExecutionReason = null;
|
||||||
|
}
|
||||||
|
this.windowStartedAt.set(startedAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void evaluateWindow(long now) {
|
private void evaluateWindow(long now) {
|
||||||
int usagePercent = (int) Math.round((this.usageCurrentWindow * 100D) / this.usageLimitPerWindow);
|
int usagePercent = (int) Math.round((this.usageCurrentWindow.get() * 100D) / this.usageLimitPerWindow);
|
||||||
int delayedPercent = (int) Math.round((this.delayedEventsPending * 100D) / this.delayedEventsLimit);
|
int delayedPercent = (int) Math.round((this.delayedEventsPending.get() * 100D) / this.delayedEventsLimit);
|
||||||
boolean overloadWindow = (this.executionSamplesCurrentWindow > 0)
|
boolean overloadWindow = (this.executionSamplesCurrentWindow.get() > 0)
|
||||||
&& ((this.averageExecutionMs >= this.overloadAverageThresholdMs) || (this.peakExecutionMs >= this.overloadPeakThresholdMs));
|
&& ((this.averageExecutionMs >= this.overloadAverageThresholdMs) || (this.peakExecutionMs >= this.overloadPeakThresholdMs));
|
||||||
boolean heavyWindow = (usagePercent >= this.heavyUsageThresholdPercent)
|
boolean heavyWindow = (usagePercent >= this.heavyUsageThresholdPercent)
|
||||||
|| (delayedPercent >= this.heavyDelayedThresholdPercent)
|
|| (delayedPercent >= this.heavyDelayedThresholdPercent)
|
||||||
@@ -516,22 +528,22 @@ public final class WiredRoomDiagnostics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildExecutionCapReason(int normalizedCost, String reason) {
|
private String buildExecutionCapReason(int normalizedCost, String reason, int currentUsage) {
|
||||||
return joinReason(
|
return joinReason(
|
||||||
reason,
|
reason,
|
||||||
String.format("Estimated stack cost %d would exceed usage budget %d/%d in %dms window",
|
String.format("Estimated stack cost %d would exceed usage budget %d/%d in %dms window",
|
||||||
normalizedCost,
|
normalizedCost,
|
||||||
this.usageCurrentWindow,
|
currentUsage,
|
||||||
this.usageLimitPerWindow,
|
this.usageLimitPerWindow,
|
||||||
this.usageWindowMs)
|
this.usageWindowMs)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildDelayedCapReason(String reason) {
|
private String buildDelayedCapReason(String reason, int currentPending) {
|
||||||
return joinReason(
|
return joinReason(
|
||||||
reason,
|
reason,
|
||||||
String.format("Pending delayed events would exceed queue %d/%d",
|
String.format("Pending delayed events would exceed queue %d/%d",
|
||||||
this.delayedEventsPending,
|
currentPending,
|
||||||
this.delayedEventsLimit)
|
this.delayedEventsLimit)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -544,7 +556,7 @@ public final class WiredRoomDiagnostics {
|
|||||||
this.overloadAverageThresholdMs,
|
this.overloadAverageThresholdMs,
|
||||||
this.peakExecutionMs,
|
this.peakExecutionMs,
|
||||||
this.overloadPeakThresholdMs,
|
this.overloadPeakThresholdMs,
|
||||||
this.executionSamplesCurrentWindow,
|
this.executionSamplesCurrentWindow.get(),
|
||||||
this.usageWindowMs)
|
this.usageWindowMs)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-3
@@ -33,6 +33,8 @@ import java.util.Locale;
|
|||||||
|
|
||||||
public final class WiredTextPlaceholderUtil {
|
public final class WiredTextPlaceholderUtil {
|
||||||
private static final char PRESERVED_SPACE = '\u00A0';
|
private static final char PRESERVED_SPACE = '\u00A0';
|
||||||
|
private static final int MAX_PLACEHOLDER_EXPANSION_LENGTH = 16384;
|
||||||
|
private static final int MAX_PLACEHOLDER_REPLACEMENTS = 512;
|
||||||
|
|
||||||
private WiredTextPlaceholderUtil() {
|
private WiredTextPlaceholderUtil() {
|
||||||
}
|
}
|
||||||
@@ -56,13 +58,20 @@ public final class WiredTextPlaceholderUtil {
|
|||||||
|
|
||||||
String resolvedText = text;
|
String resolvedText = text;
|
||||||
|
|
||||||
|
int replacementCount = 0;
|
||||||
|
|
||||||
for (InteractionWiredExtra extra : WiredExecutionOrderUtil.sort(extras)) {
|
for (InteractionWiredExtra extra : WiredExecutionOrderUtil.sort(extras)) {
|
||||||
if (extra instanceof WiredExtraTextOutputUsername) {
|
if (extra instanceof WiredExtraTextOutputUsername) {
|
||||||
WiredExtraTextOutputUsername usernameExtra = (WiredExtraTextOutputUsername) extra;
|
WiredExtraTextOutputUsername usernameExtra = (WiredExtraTextOutputUsername) extra;
|
||||||
String placeholderToken = usernameExtra.getPlaceholderToken();
|
String placeholderToken = usernameExtra.getPlaceholderToken();
|
||||||
|
|
||||||
if (!placeholderToken.isEmpty() && resolvedText.contains(placeholderToken)) {
|
if (!placeholderToken.isEmpty() && resolvedText.contains(placeholderToken)) {
|
||||||
resolvedText = resolvedText.replace(placeholderToken, buildUsernameReplacement(ctx, usernameExtra));
|
resolvedText = replaceWithBudget(resolvedText, placeholderToken, buildUsernameReplacement(ctx, usernameExtra));
|
||||||
|
replacementCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldStopPlaceholderExpansion(resolvedText, replacementCount)) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -73,7 +82,12 @@ public final class WiredTextPlaceholderUtil {
|
|||||||
String placeholderToken = furniExtra.getPlaceholderToken();
|
String placeholderToken = furniExtra.getPlaceholderToken();
|
||||||
|
|
||||||
if (!placeholderToken.isEmpty() && resolvedText.contains(placeholderToken)) {
|
if (!placeholderToken.isEmpty() && resolvedText.contains(placeholderToken)) {
|
||||||
resolvedText = resolvedText.replace(placeholderToken, buildFurniNameReplacement(ctx, furniExtra));
|
resolvedText = replaceWithBudget(resolvedText, placeholderToken, buildFurniNameReplacement(ctx, furniExtra));
|
||||||
|
replacementCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldStopPlaceholderExpansion(resolvedText, replacementCount)) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -84,7 +98,12 @@ public final class WiredTextPlaceholderUtil {
|
|||||||
String placeholderToken = variableExtra.getPlaceholderToken();
|
String placeholderToken = variableExtra.getPlaceholderToken();
|
||||||
|
|
||||||
if (!placeholderToken.isEmpty() && resolvedText.contains(placeholderToken)) {
|
if (!placeholderToken.isEmpty() && resolvedText.contains(placeholderToken)) {
|
||||||
resolvedText = resolvedText.replace(placeholderToken, buildVariableReplacement(ctx, variableExtra));
|
resolvedText = replaceWithBudget(resolvedText, placeholderToken, buildVariableReplacement(ctx, variableExtra));
|
||||||
|
replacementCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldStopPlaceholderExpansion(resolvedText, replacementCount)) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,6 +111,61 @@ public final class WiredTextPlaceholderUtil {
|
|||||||
return preserveRepeatedSpaces(resolvedText);
|
return preserveRepeatedSpaces(resolvedText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean shouldStopPlaceholderExpansion(String resolvedText, int replacementCount) {
|
||||||
|
return replacementCount >= MAX_PLACEHOLDER_REPLACEMENTS
|
||||||
|
|| (resolvedText != null && resolvedText.length() >= MAX_PLACEHOLDER_EXPANSION_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String replaceWithBudget(String input, String placeholderToken, String replacement) {
|
||||||
|
if (input == null || input.isEmpty() || placeholderToken == null || placeholderToken.isEmpty()) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replacement == null) {
|
||||||
|
replacement = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
int matchIndex = input.indexOf(placeholderToken);
|
||||||
|
if (matchIndex < 0) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder(Math.min(MAX_PLACEHOLDER_EXPANSION_LENGTH, input.length()));
|
||||||
|
int searchIndex = 0;
|
||||||
|
|
||||||
|
while (matchIndex >= 0) {
|
||||||
|
builder.append(input, searchIndex, matchIndex);
|
||||||
|
|
||||||
|
int remainingCapacity = MAX_PLACEHOLDER_EXPANSION_LENGTH - builder.length();
|
||||||
|
if (remainingCapacity <= 0) {
|
||||||
|
return builder.substring(0, MAX_PLACEHOLDER_EXPANSION_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replacement.length() <= remainingCapacity) {
|
||||||
|
builder.append(replacement);
|
||||||
|
} else {
|
||||||
|
builder.append(replacement, 0, remainingCapacity);
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
searchIndex = matchIndex + placeholderToken.length();
|
||||||
|
if (builder.length() >= MAX_PLACEHOLDER_EXPANSION_LENGTH) {
|
||||||
|
return builder.substring(0, MAX_PLACEHOLDER_EXPANSION_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
matchIndex = input.indexOf(placeholderToken, searchIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
int remainingCapacity = MAX_PLACEHOLDER_EXPANSION_LENGTH - builder.length();
|
||||||
|
if (remainingCapacity <= 0) {
|
||||||
|
return builder.substring(0, MAX_PLACEHOLDER_EXPANSION_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
int tailLength = Math.min(input.length() - searchIndex, remainingCapacity);
|
||||||
|
builder.append(input, searchIndex, searchIndex + tailLength);
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private static String preserveRepeatedSpaces(String text) {
|
private static String preserveRepeatedSpaces(String text) {
|
||||||
if (text == null || text.length() < 2) {
|
if (text == null || text.length() < 2) {
|
||||||
return text;
|
return text;
|
||||||
|
|||||||
@@ -587,4 +587,10 @@ public final class WiredUserMovementHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void cleanupRoomUnit(RoomUnit roomUnit) {
|
||||||
|
if (roomUnit != null) {
|
||||||
|
SUPPRESSED_STATUS_COMPOSER_UNTIL.remove(roomUnit.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-25
@@ -16,6 +16,7 @@ import java.time.LocalTime;
|
|||||||
import java.time.temporal.TemporalAdjusters;
|
import java.time.temporal.TemporalAdjusters;
|
||||||
import java.time.temporal.WeekFields;
|
import java.time.temporal.WeekFields;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@@ -23,7 +24,7 @@ import java.util.stream.Stream;
|
|||||||
public class WiredHighscoreManager {
|
public class WiredHighscoreManager {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(WiredHighscoreManager.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(WiredHighscoreManager.class);
|
||||||
|
|
||||||
private final HashMap<Integer, List<WiredHighscoreDataEntry>> data = new HashMap<>();
|
private final ConcurrentHashMap<Integer, List<WiredHighscoreDataEntry>> data = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private final static String locale = (System.getProperty("user.language") != null ? System.getProperty("user.language") : "en");
|
private final static String locale = (System.getProperty("user.language") != null ? System.getProperty("user.language") : "en");
|
||||||
private final static String country = (System.getProperty("user.country") != null ? System.getProperty("user.country") : "US");
|
private final static String country = (System.getProperty("user.country") != null ? System.getProperty("user.country") : "US");
|
||||||
@@ -60,15 +61,12 @@ public class WiredHighscoreManager {
|
|||||||
|
|
||||||
private void loadHighscoreData() {
|
private void loadHighscoreData() {
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM items_highscore_data")) {
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM items_highscore_data")) {
|
||||||
|
statement.setFetchSize(1000);
|
||||||
try (ResultSet set = statement.executeQuery()) {
|
try (ResultSet set = statement.executeQuery()) {
|
||||||
while (set.next()) {
|
while (set.next()) {
|
||||||
WiredHighscoreDataEntry entry = new WiredHighscoreDataEntry(set);
|
WiredHighscoreDataEntry entry = new WiredHighscoreDataEntry(set);
|
||||||
|
|
||||||
if (!this.data.containsKey(entry.getItemId())) {
|
this.data.computeIfAbsent(entry.getItemId(), k -> Collections.synchronizedList(new ArrayList<>())).add(entry);
|
||||||
this.data.put(entry.getItemId(), new ArrayList<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.data.get(entry.getItemId()).add(entry);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
@@ -77,33 +75,39 @@ public class WiredHighscoreManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addHighscoreData(WiredHighscoreDataEntry entry) {
|
public void addHighscoreData(WiredHighscoreDataEntry entry) {
|
||||||
if (!this.data.containsKey(entry.getItemId())) {
|
this.data.computeIfAbsent(entry.getItemId(), k -> Collections.synchronizedList(new ArrayList<>())).add(entry);
|
||||||
this.data.put(entry.getItemId(), new ArrayList<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.data.get(entry.getItemId()).add(entry);
|
Emulator.getThreading().run(() -> {
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO `items_highscore_data` (`item_id`, `user_ids`, `score`, `is_win`, `timestamp`) VALUES (?, ?, ?, ?, ?)")) {
|
||||||
|
statement.setInt(1, entry.getItemId());
|
||||||
|
statement.setString(2, String.join(",", entry.getUserIds().stream().map(Object::toString).collect(Collectors.toList())));
|
||||||
|
statement.setInt(3, entry.getScore());
|
||||||
|
statement.setInt(4, entry.isWin() ? 1 : 0);
|
||||||
|
statement.setInt(5, entry.getTimestamp());
|
||||||
|
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO `items_highscore_data` (`item_id`, `user_ids`, `score`, `is_win`, `timestamp`) VALUES (?, ?, ?, ?, ?)")) {
|
statement.execute();
|
||||||
statement.setInt(1, entry.getItemId());
|
} catch (SQLException e) {
|
||||||
statement.setString(2, String.join(",", entry.getUserIds().stream().map(Object::toString).collect(Collectors.toList())));
|
LOGGER.error("Caught SQL exception", e);
|
||||||
statement.setInt(3, entry.getScore());
|
}
|
||||||
statement.setInt(4, entry.isWin() ? 1 : 0);
|
});
|
||||||
statement.setInt(5, entry.getTimestamp());
|
|
||||||
|
|
||||||
statement.execute();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
LOGGER.error("Caught SQL exception", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<WiredHighscoreRow> getHighscoreRowsForItem(int itemId, WiredHighscoreClearType clearType, WiredHighscoreScoreType scoreType) {
|
public List<WiredHighscoreRow> getHighscoreRowsForItem(int itemId, WiredHighscoreClearType clearType, WiredHighscoreScoreType scoreType) {
|
||||||
if (!this.data.containsKey(itemId)) return null;
|
if (!this.data.containsKey(itemId)) return null;
|
||||||
|
|
||||||
Stream<WiredHighscoreRow> highscores = new ArrayList<>(this.data.get(itemId)).stream()
|
List<WiredHighscoreDataEntry> list = this.data.get(itemId);
|
||||||
|
if (list == null) return null;
|
||||||
|
|
||||||
|
List<WiredHighscoreDataEntry> copy;
|
||||||
|
synchronized (list) {
|
||||||
|
copy = new ArrayList<>(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<WiredHighscoreRow> highscores = copy.stream()
|
||||||
.filter(entry -> this.timeMatchesEntry(entry, clearType) && (scoreType != WiredHighscoreScoreType.MOSTWIN || entry.isWin()))
|
.filter(entry -> this.timeMatchesEntry(entry, clearType) && (scoreType != WiredHighscoreScoreType.MOSTWIN || entry.isWin()))
|
||||||
.map(entry -> new WiredHighscoreRow(
|
.map(entry -> new WiredHighscoreRow(
|
||||||
entry.getUserIds().stream()
|
entry.getUserIds().stream()
|
||||||
.map(id -> Emulator.getGameEnvironment().getHabboManager().getHabboInfo(id).getUsername())
|
.map(id -> Emulator.getGameEnvironment().getHabboManager().getCachedUsername(id))
|
||||||
.collect(Collectors.toList()),
|
.collect(Collectors.toList()),
|
||||||
entry.getScore()
|
entry.getScore()
|
||||||
));
|
));
|
||||||
@@ -167,7 +171,7 @@ public class WiredHighscoreManager {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HashMap<Integer, List<WiredHighscoreDataEntry>> getData() {
|
public Map<Integer, List<WiredHighscoreDataEntry>> getData() {
|
||||||
return this.data;
|
return this.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +180,7 @@ public class WiredHighscoreManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setEntriesForItemId(int itemId, List<WiredHighscoreDataEntry> entries) {
|
public void setEntriesForItemId(int itemId, List<WiredHighscoreDataEntry> entries) {
|
||||||
this.data.put(itemId, entries);
|
this.data.put(itemId, Collections.synchronizedList(entries));
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getTodayStartTimestamp() {
|
private long getTodayStartTimestamp() {
|
||||||
|
|||||||
@@ -60,11 +60,15 @@ public final class WiredTickService {
|
|||||||
/** Whether a shard worker loop is currently scheduled/running. */
|
/** Whether a shard worker loop is currently scheduled/running. */
|
||||||
private AtomicBoolean[] shardScheduled;
|
private AtomicBoolean[] shardScheduled;
|
||||||
|
|
||||||
private final ConcurrentHashMap<Integer, Set<WiredTickable>> roomTickables;
|
private final ConcurrentHashMap<Integer, Set<WiredTickable>>[] shardRoomTickables;
|
||||||
private final AtomicBoolean running;
|
private final AtomicBoolean running;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
private WiredTickService() {
|
private WiredTickService() {
|
||||||
this.roomTickables = new ConcurrentHashMap<>();
|
this.shardRoomTickables = new ConcurrentHashMap[MAX_WORKER_COUNT];
|
||||||
|
for (int i = 0; i < MAX_WORKER_COUNT; i++) {
|
||||||
|
this.shardRoomTickables[i] = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
this.running = new AtomicBoolean(false);
|
this.running = new AtomicBoolean(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +236,9 @@ public final class WiredTickService {
|
|||||||
shardProcessedTicks = null;
|
shardProcessedTicks = null;
|
||||||
shardScheduled = null;
|
shardScheduled = null;
|
||||||
|
|
||||||
roomTickables.clear();
|
for (int i = 0; i < MAX_WORKER_COUNT; i++) {
|
||||||
|
shardRoomTickables[i].clear();
|
||||||
|
}
|
||||||
LOGGER.info("WiredTickService stopped");
|
LOGGER.info("WiredTickService stopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +252,8 @@ public final class WiredTickService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int roomId = room.getId();
|
int roomId = room.getId();
|
||||||
Set<WiredTickable> tickables = roomTickables.computeIfAbsent(roomId, k -> ConcurrentHashMap.newKeySet());
|
int shardIndex = getShardIndex(roomId);
|
||||||
|
Set<WiredTickable> tickables = shardRoomTickables[shardIndex].computeIfAbsent(roomId, k -> ConcurrentHashMap.newKeySet());
|
||||||
|
|
||||||
if (tickables.add(tickable)) {
|
if (tickables.add(tickable)) {
|
||||||
tickable.onRegistered(room, System.currentTimeMillis());
|
tickable.onRegistered(room, System.currentTimeMillis());
|
||||||
@@ -259,7 +266,8 @@ public final class WiredTickService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int roomId = room.getId();
|
int roomId = room.getId();
|
||||||
Set<WiredTickable> tickables = roomTickables.get(roomId);
|
int shardIndex = getShardIndex(roomId);
|
||||||
|
Set<WiredTickable> tickables = shardRoomTickables[shardIndex].get(roomId);
|
||||||
|
|
||||||
if (tickables != null) {
|
if (tickables != null) {
|
||||||
if (tickables.remove(tickable)) {
|
if (tickables.remove(tickable)) {
|
||||||
@@ -267,13 +275,14 @@ public final class WiredTickService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tickables.isEmpty()) {
|
if (tickables.isEmpty()) {
|
||||||
roomTickables.remove(roomId);
|
shardRoomTickables[shardIndex].remove(roomId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unregister(int roomId, int tickableId) {
|
public void unregister(int roomId, int tickableId) {
|
||||||
Set<WiredTickable> tickables = roomTickables.get(roomId);
|
int shardIndex = getShardIndex(roomId);
|
||||||
|
Set<WiredTickable> tickables = shardRoomTickables[shardIndex].get(roomId);
|
||||||
|
|
||||||
if (tickables != null) {
|
if (tickables != null) {
|
||||||
tickables.removeIf(t -> {
|
tickables.removeIf(t -> {
|
||||||
@@ -288,7 +297,7 @@ public final class WiredTickService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (tickables.isEmpty()) {
|
if (tickables.isEmpty()) {
|
||||||
roomTickables.remove(roomId);
|
shardRoomTickables[shardIndex].remove(roomId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,11 +307,12 @@ public final class WiredTickService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<WiredTickable> tickables = roomTickables.remove(room.getId());
|
int roomId = room.getId();
|
||||||
|
int shardIndex = getShardIndex(roomId);
|
||||||
|
Set<WiredTickable> tickables = shardRoomTickables[shardIndex].remove(roomId);
|
||||||
|
|
||||||
if (tickables != null) {
|
if (tickables != null) {
|
||||||
WiredTickable[] snapshot = tickables.toArray(new WiredTickable[0]);
|
for (WiredTickable tickable : tickables) {
|
||||||
for (WiredTickable tickable : snapshot) {
|
|
||||||
try {
|
try {
|
||||||
if (tickable != null) {
|
if (tickable != null) {
|
||||||
tickable.onUnregistered(room);
|
tickable.onUnregistered(room);
|
||||||
@@ -316,7 +326,7 @@ public final class WiredTickService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOGGER.debug("Unregistered {} tickables from room {}", snapshot.length, room.getId());
|
LOGGER.debug("Unregistered {} tickables from room {}", tickables.size(), room.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,11 +335,12 @@ public final class WiredTickService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<WiredTickable> tickables = roomTickables.get(room.getId());
|
int roomId = room.getId();
|
||||||
|
int shardIndex = getShardIndex(roomId);
|
||||||
|
Set<WiredTickable> tickables = shardRoomTickables[shardIndex].get(roomId);
|
||||||
|
|
||||||
if (tickables != null) {
|
if (tickables != null) {
|
||||||
WiredTickable[] snapshot = tickables.toArray(new WiredTickable[0]);
|
for (WiredTickable tickable : tickables) {
|
||||||
for (WiredTickable tickable : snapshot) {
|
|
||||||
try {
|
try {
|
||||||
if (tickable != null) {
|
if (tickable != null) {
|
||||||
tickable.resetTimer();
|
tickable.resetTimer();
|
||||||
@@ -347,16 +358,25 @@ public final class WiredTickService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getTickableCount(int roomId) {
|
public int getTickableCount(int roomId) {
|
||||||
Set<WiredTickable> tickables = roomTickables.get(roomId);
|
int shardIndex = getShardIndex(roomId);
|
||||||
|
Set<WiredTickable> tickables = shardRoomTickables[shardIndex].get(roomId);
|
||||||
return tickables != null ? tickables.size() : 0;
|
return tickables != null ? tickables.size() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTotalTickableCount() {
|
public int getTotalTickableCount() {
|
||||||
return roomTickables.values().stream().mapToInt(Set::size).sum();
|
int count = 0;
|
||||||
|
for (int i = 0; i < MAX_WORKER_COUNT; i++) {
|
||||||
|
count += shardRoomTickables[i].values().stream().mapToInt(Set::size).sum();
|
||||||
|
}
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getActiveRoomCount() {
|
public int getActiveRoomCount() {
|
||||||
return roomTickables.size();
|
int count = 0;
|
||||||
|
for (int i = 0; i < MAX_WORKER_COUNT; i++) {
|
||||||
|
count += shardRoomTickables[i].size();
|
||||||
|
}
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTickCount() {
|
public long getTickCount() {
|
||||||
@@ -396,6 +416,12 @@ public final class WiredTickService {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If lagging by more than 5 ticks (250ms), skip intermediate ticks to avoid CPU starvation
|
||||||
|
if (requestedTick - nextTick > 5) {
|
||||||
|
nextTick = requestedTick - 5;
|
||||||
|
shardProcessedTicks[shardIndex].set(nextTick);
|
||||||
|
}
|
||||||
|
|
||||||
processShardTick(shardIndex, nextTick);
|
processShardTick(shardIndex, nextTick);
|
||||||
shardProcessedTicks[shardIndex].set(nextTick);
|
shardProcessedTicks[shardIndex].set(nextTick);
|
||||||
}
|
}
|
||||||
@@ -414,12 +440,8 @@ public final class WiredTickService {
|
|||||||
int processedTickables = 0;
|
int processedTickables = 0;
|
||||||
int processedRooms = 0;
|
int processedRooms = 0;
|
||||||
|
|
||||||
for (Map.Entry<Integer, Set<WiredTickable>> entry : roomTickables.entrySet()) {
|
for (Map.Entry<Integer, Set<WiredTickable>> entry : shardRoomTickables[shardIndex].entrySet()) {
|
||||||
int roomId = entry.getKey();
|
int roomId = entry.getKey();
|
||||||
if (getShardIndex(roomId) != shardIndex) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<WiredTickable> tickables = entry.getValue();
|
Set<WiredTickable> tickables = entry.getValue();
|
||||||
if (tickables == null || tickables.isEmpty()) {
|
if (tickables == null || tickables.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -435,14 +457,9 @@ public final class WiredTickService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long roomStart = System.currentTimeMillis();
|
long roomStart = System.currentTimeMillis();
|
||||||
WiredTickable[] snapshot = tickables.toArray(new WiredTickable[0]);
|
|
||||||
if (snapshot.length == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
processedRooms++;
|
processedRooms++;
|
||||||
|
|
||||||
for (WiredTickable tickable : snapshot) {
|
for (WiredTickable tickable : tickables) {
|
||||||
long tickableStart = System.currentTimeMillis();
|
long tickableStart = System.currentTimeMillis();
|
||||||
|
|
||||||
if (tickable == null) {
|
if (tickable == null) {
|
||||||
@@ -489,7 +506,7 @@ public final class WiredTickService {
|
|||||||
shardIndex,
|
shardIndex,
|
||||||
roomId,
|
roomId,
|
||||||
currentTick,
|
currentTick,
|
||||||
snapshot.length,
|
tickables.size(),
|
||||||
roomDuration
|
roomDuration
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.eu.habbo.messages;
|
|||||||
|
|
||||||
import com.eu.habbo.Emulator;
|
import com.eu.habbo.Emulator;
|
||||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||||
|
import com.eu.habbo.monitoring.EmulatorNetworkStats;
|
||||||
import com.eu.habbo.messages.incoming.Incoming;
|
import com.eu.habbo.messages.incoming.Incoming;
|
||||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
import com.eu.habbo.messages.incoming.achievements.RequestAchievementConfigurationEvent;
|
import com.eu.habbo.messages.incoming.achievements.RequestAchievementConfigurationEvent;
|
||||||
@@ -180,6 +181,8 @@ public class PacketManager {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
EmulatorNetworkStats.recordIncoming(packet.bytesAvailable() + 6);
|
||||||
|
|
||||||
if (this.isRegistered(packet.getMessageId())) {
|
if (this.isRegistered(packet.getMessageId())) {
|
||||||
Class<? extends MessageHandler> handlerClass = this.incoming.get(packet.getMessageId());
|
Class<? extends MessageHandler> handlerClass = this.incoming.get(packet.getMessageId());
|
||||||
|
|
||||||
@@ -716,5 +719,42 @@ public class PacketManager {
|
|||||||
this.registerHandler(Incoming.YouTubeRoomPlayEvent, com.eu.habbo.messages.incoming.rooms.youtube.YouTubeRoomPlayEvent.class);
|
this.registerHandler(Incoming.YouTubeRoomPlayEvent, com.eu.habbo.messages.incoming.rooms.youtube.YouTubeRoomPlayEvent.class);
|
||||||
this.registerHandler(Incoming.YouTubeRoomWatchingEvent, com.eu.habbo.messages.incoming.rooms.youtube.YouTubeRoomWatchingEvent.class);
|
this.registerHandler(Incoming.YouTubeRoomWatchingEvent, com.eu.habbo.messages.incoming.rooms.youtube.YouTubeRoomWatchingEvent.class);
|
||||||
this.registerHandler(Incoming.YouTubeRoomSettingsEvent, com.eu.habbo.messages.incoming.rooms.youtube.YouTubeRoomSettingsEvent.class);
|
this.registerHandler(Incoming.YouTubeRoomSettingsEvent, com.eu.habbo.messages.incoming.rooms.youtube.YouTubeRoomSettingsEvent.class);
|
||||||
|
|
||||||
|
// Housekeeping (in-client admin panel)
|
||||||
|
this.registerHandler(Incoming.HousekeepingFindUserByNameEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingFindUserByNameEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingFindUserByIdEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingFindUserByIdEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingBanUserEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingBanUserEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingUnbanUserEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingUnbanUserEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingMuteUserEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingMuteUserEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingKickUserEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingKickUserEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingForceDisconnectUserEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingForceDisconnectUserEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingSetUserRankEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingSetUserRankEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingTradeLockUserEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingTradeLockUserEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingResetUserPasswordEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingResetUserPasswordEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingFindRoomByIdEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingFindRoomByIdEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingSearchRoomsEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingSearchRoomsEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingRoomStateEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingRoomStateEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingMuteRoomEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingMuteRoomEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingKickAllFromRoomEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingKickAllFromRoomEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingTransferRoomOwnershipEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingTransferRoomOwnershipEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingDeleteRoomEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingDeleteRoomEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingGiveCreditsEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingGiveCreditsEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingGiveCurrencyEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingGiveCurrencyEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingGrantItemEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingGrantItemEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingSetHcSubscriptionEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingSetHcSubscriptionEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingSendHotelAlertEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingSendHotelAlertEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingGetDashboardEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingGetDashboardEvent.class);
|
||||||
|
this.registerHandler(Incoming.HousekeepingListActionLogEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingListActionLogEvent.class);
|
||||||
|
|
||||||
|
this.registerHandler(Incoming.RequestRareValuesEvent, com.eu.habbo.messages.incoming.rarevalues.RequestRareValuesEvent.class);
|
||||||
|
|
||||||
|
this.registerHandler(Incoming.WheelOpenEvent, com.eu.habbo.messages.incoming.wheel.WheelOpenEvent.class);
|
||||||
|
this.registerHandler(Incoming.WheelSpinEvent, com.eu.habbo.messages.incoming.wheel.WheelSpinEvent.class);
|
||||||
|
this.registerHandler(Incoming.WheelBuySpinEvent, com.eu.habbo.messages.incoming.wheel.WheelBuySpinEvent.class);
|
||||||
|
this.registerHandler(Incoming.WheelAdminGetPrizesEvent, com.eu.habbo.messages.incoming.wheel.WheelAdminGetPrizesEvent.class);
|
||||||
|
this.registerHandler(Incoming.WheelAdminSavePrizesEvent, com.eu.habbo.messages.incoming.wheel.WheelAdminSavePrizesEvent.class);
|
||||||
|
|
||||||
|
this.registerHandler(Incoming.SoundboardPlayEvent, com.eu.habbo.messages.incoming.soundboard.SoundboardPlayEvent.class);
|
||||||
|
this.registerHandler(Incoming.SoundboardSetEnabledEvent, com.eu.habbo.messages.incoming.soundboard.SoundboardSetEnabledEvent.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -460,4 +460,40 @@ public class Incoming {
|
|||||||
public static final int YouTubeRoomPlayEvent = 8001;
|
public static final int YouTubeRoomPlayEvent = 8001;
|
||||||
public static final int YouTubeRoomWatchingEvent = 8002;
|
public static final int YouTubeRoomWatchingEvent = 8002;
|
||||||
public static final int YouTubeRoomSettingsEvent = 8003;
|
public static final int YouTubeRoomSettingsEvent = 8003;
|
||||||
|
|
||||||
|
// Housekeeping (in-client admin panel) — IDs 9100..9199 reserved
|
||||||
|
public static final int HousekeepingFindUserByNameEvent = 9100;
|
||||||
|
public static final int HousekeepingFindUserByIdEvent = 9101;
|
||||||
|
public static final int HousekeepingBanUserEvent = 9102;
|
||||||
|
public static final int HousekeepingUnbanUserEvent = 9103;
|
||||||
|
public static final int HousekeepingMuteUserEvent = 9104;
|
||||||
|
public static final int HousekeepingKickUserEvent = 9105;
|
||||||
|
public static final int HousekeepingForceDisconnectUserEvent = 9106;
|
||||||
|
public static final int HousekeepingSetUserRankEvent = 9107;
|
||||||
|
public static final int HousekeepingTradeLockUserEvent = 9108;
|
||||||
|
public static final int HousekeepingResetUserPasswordEvent = 9109;
|
||||||
|
public static final int HousekeepingFindRoomByIdEvent = 9110;
|
||||||
|
public static final int HousekeepingSearchRoomsEvent = 9111;
|
||||||
|
public static final int HousekeepingRoomStateEvent = 9112;
|
||||||
|
public static final int HousekeepingMuteRoomEvent = 9113;
|
||||||
|
public static final int HousekeepingKickAllFromRoomEvent = 9114;
|
||||||
|
public static final int HousekeepingTransferRoomOwnershipEvent = 9115;
|
||||||
|
public static final int HousekeepingDeleteRoomEvent = 9116;
|
||||||
|
public static final int HousekeepingGiveCreditsEvent = 9117;
|
||||||
|
public static final int HousekeepingGiveCurrencyEvent = 9118;
|
||||||
|
public static final int HousekeepingGrantItemEvent = 9119;
|
||||||
|
public static final int HousekeepingSetHcSubscriptionEvent = 9120;
|
||||||
|
public static final int HousekeepingSendHotelAlertEvent = 9121;
|
||||||
|
public static final int HousekeepingGetDashboardEvent = 9122;
|
||||||
|
public static final int HousekeepingListActionLogEvent = 9123;
|
||||||
|
|
||||||
|
// Custom features — IDs 9300+ reserved
|
||||||
|
public static final int RequestRareValuesEvent = 9300;
|
||||||
|
public static final int WheelOpenEvent = 9301;
|
||||||
|
public static final int WheelSpinEvent = 9302;
|
||||||
|
public static final int WheelBuySpinEvent = 9303;
|
||||||
|
public static final int WheelAdminGetPrizesEvent = 9304;
|
||||||
|
public static final int WheelAdminSavePrizesEvent = 9305;
|
||||||
|
public static final int SoundboardPlayEvent = 9306;
|
||||||
|
public static final int SoundboardSetEnabledEvent = 9307;
|
||||||
}
|
}
|
||||||
|
|||||||
+282
-86
@@ -2,10 +2,8 @@ package com.eu.habbo.messages.incoming.catalog;
|
|||||||
|
|
||||||
import com.eu.habbo.Emulator;
|
import com.eu.habbo.Emulator;
|
||||||
import com.eu.habbo.habbohotel.achievements.AchievementManager;
|
import com.eu.habbo.habbohotel.achievements.AchievementManager;
|
||||||
import com.eu.habbo.habbohotel.catalog.CatalogItem;
|
import com.eu.habbo.habbohotel.catalog.*;
|
||||||
import com.eu.habbo.habbohotel.catalog.CatalogLimitedConfiguration;
|
import com.eu.habbo.habbohotel.catalog.layouts.*;
|
||||||
import com.eu.habbo.habbohotel.catalog.CatalogManager;
|
|
||||||
import com.eu.habbo.habbohotel.catalog.CatalogPage;
|
|
||||||
import com.eu.habbo.habbohotel.items.FurnitureType;
|
import com.eu.habbo.habbohotel.items.FurnitureType;
|
||||||
import com.eu.habbo.habbohotel.items.Item;
|
import com.eu.habbo.habbohotel.items.Item;
|
||||||
import com.eu.habbo.habbohotel.items.interactions.*;
|
import com.eu.habbo.habbohotel.items.interactions.*;
|
||||||
@@ -14,6 +12,7 @@ import com.eu.habbo.habbohotel.permissions.Permission;
|
|||||||
import com.eu.habbo.habbohotel.users.Habbo;
|
import com.eu.habbo.habbohotel.users.Habbo;
|
||||||
import com.eu.habbo.habbohotel.users.HabboBadge;
|
import com.eu.habbo.habbohotel.users.HabboBadge;
|
||||||
import com.eu.habbo.habbohotel.users.HabboItem;
|
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||||
|
import com.eu.habbo.habbohotel.users.subscriptions.Subscription;
|
||||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
import com.eu.habbo.messages.outgoing.catalog.*;
|
import com.eu.habbo.messages.outgoing.catalog.*;
|
||||||
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
|
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
|
||||||
@@ -22,21 +21,22 @@ import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer;
|
|||||||
import com.eu.habbo.messages.outgoing.generic.alerts.HotelWillCloseInMinutesComposer;
|
import com.eu.habbo.messages.outgoing.generic.alerts.HotelWillCloseInMinutesComposer;
|
||||||
import com.eu.habbo.messages.outgoing.inventory.AddHabboItemComposer;
|
import com.eu.habbo.messages.outgoing.inventory.AddHabboItemComposer;
|
||||||
import com.eu.habbo.messages.outgoing.inventory.InventoryRefreshComposer;
|
import com.eu.habbo.messages.outgoing.inventory.InventoryRefreshComposer;
|
||||||
|
import com.eu.habbo.messages.outgoing.users.UserClubComposer;
|
||||||
import com.eu.habbo.threading.runnables.ShutdownEmulator;
|
import com.eu.habbo.threading.runnables.ShutdownEmulator;
|
||||||
import gnu.trove.map.hash.THashMap;
|
import gnu.trove.map.hash.THashMap;
|
||||||
import gnu.trove.set.hash.THashSet;
|
import gnu.trove.set.hash.THashSet;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.*;
|
||||||
import java.sql.PreparedStatement;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
|
||||||
public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(CatalogBuyItemAsGiftEvent.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(CatalogBuyItemAsGiftEvent.class);
|
||||||
|
|
||||||
|
private static final int USERNAME_MAX = 32;
|
||||||
|
private static final int EXTRADATA_MAX = 256;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRatelimit() {
|
public int getRatelimit() {
|
||||||
return 500;
|
return 500;
|
||||||
@@ -44,61 +44,66 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle() throws Exception {
|
public void handle() throws Exception {
|
||||||
LOGGER.error("DEBUG GIFT: entered CatalogBuyItemAsGiftEvent.handle()");
|
|
||||||
|
|
||||||
if (Emulator.getIntUnixTimestamp() - this.client.getHabbo().getHabboStats().lastGiftTimestamp >= CatalogManager.PURCHASE_COOLDOWN) {
|
if (Emulator.getIntUnixTimestamp() - this.client.getHabbo().getHabboStats().lastGiftTimestamp >= CatalogManager.PURCHASE_COOLDOWN) {
|
||||||
this.client.getHabbo().getHabboStats().lastGiftTimestamp = Emulator.getIntUnixTimestamp();
|
|
||||||
|
|
||||||
if (ShutdownEmulator.timestamp > 0) {
|
if (ShutdownEmulator.timestamp > 0) {
|
||||||
LOGGER.error("DEBUG GIFT: emulator closing");
|
LOGGER.debug("emulator closing");
|
||||||
this.client.sendResponse(new HotelWillCloseInMinutesComposer((ShutdownEmulator.timestamp - Emulator.getIntUnixTimestamp()) / 60));
|
this.client.sendResponse(new HotelWillCloseInMinutesComposer((ShutdownEmulator.timestamp - Emulator.getIntUnixTimestamp()) / 60));
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.client.getHabbo().getHabboStats().isPurchasingFurniture) {
|
synchronized (this.client.getHabbo().getHabboStats()) {
|
||||||
LOGGER.error("DEBUG GIFT: isPurchasingFurniture already true");
|
if (this.client.getHabbo().getHabboStats().isPurchasingFurniture) {
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
LOGGER.debug("isPurchasingFurniture already true");
|
||||||
return;
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
this.client.getHabbo().getHabboStats().isPurchasingFurniture = true;
|
this.client.getHabbo().getHabboStats().isPurchasingFurniture = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int paidCredits = 0;
|
||||||
|
int paidPoints = 0;
|
||||||
|
int paidPointsType = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
int pageId = this.packet.readInt();
|
int pageId = this.packet.readInt();
|
||||||
int itemId = this.packet.readInt();
|
int itemId = this.packet.readInt();
|
||||||
String extraData = this.packet.readString();
|
String extraData = this.packet.readString();
|
||||||
|
if (extraData.length() > EXTRADATA_MAX) extraData = extraData.substring(0, EXTRADATA_MAX);
|
||||||
String username = this.packet.readString();
|
String username = this.packet.readString();
|
||||||
String message = Emulator.getGameEnvironment().getWordFilter().filter(this.packet.readString(), this.client.getHabbo());
|
if (username.length() > USERNAME_MAX) username = username.substring(0, USERNAME_MAX);
|
||||||
|
int messageMax = Emulator.getConfig().getInt("hotel.gifts.length.max", 300);
|
||||||
|
String rawMessage = this.packet.readString();
|
||||||
|
if (rawMessage.length() > messageMax) rawMessage = rawMessage.substring(0, messageMax);
|
||||||
|
String message = Emulator.getGameEnvironment().getWordFilter().filter(rawMessage, this.client.getHabbo());
|
||||||
int spriteId = this.packet.readInt();
|
int spriteId = this.packet.readInt();
|
||||||
int color = this.packet.readInt();
|
int color = this.packet.readInt();
|
||||||
int ribbonId = this.packet.readInt();
|
int ribbonId = this.packet.readInt();
|
||||||
boolean showName = this.packet.readBoolean();
|
boolean showName = this.packet.readBoolean();
|
||||||
|
|
||||||
LOGGER.error(
|
LOGGER.debug("Gift request: pageId={}, itemId={}, spriteId={}, color={}, ribbonId={}", pageId, itemId, spriteId, color, ribbonId);
|
||||||
"DEBUG GIFT: pageId={}, itemId={}, extraData={}, username={}, spriteId={}, color={}, ribbonId={}, showName={}, message={}",
|
|
||||||
pageId, itemId, extraData, username, spriteId, color, ribbonId, showName, message
|
|
||||||
);
|
|
||||||
|
|
||||||
int userId = 0;
|
int userId = 0;
|
||||||
|
|
||||||
|
CatalogPage clubGiftPage = Emulator.getGameEnvironment().getCatalogManager().catalogPages.get(pageId);
|
||||||
|
if (this.isClubOfferPage(clubGiftPage)) {
|
||||||
|
this.handleClubOfferGift(clubGiftPage, itemId, username);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!Emulator.getGameEnvironment().getCatalogManager().giftWrappers.containsKey(spriteId)
|
if (!Emulator.getGameEnvironment().getCatalogManager().giftWrappers.containsKey(spriteId)
|
||||||
&& !Emulator.getGameEnvironment().getCatalogManager().giftFurnis.containsKey(spriteId)) {
|
&& !Emulator.getGameEnvironment().getCatalogManager().giftFurnis.containsKey(spriteId)) {
|
||||||
LOGGER.error("DEBUG GIFT: invalid spriteId for gift wrapper/furni -> {}", spriteId);
|
LOGGER.debug("invalid spriteId for gift wrapper/furni -> {}", spriteId);
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!GiftConfigurationComposer.BOX_TYPES.contains(color) || !GiftConfigurationComposer.RIBBON_TYPES.contains(ribbonId)) {
|
if (!GiftConfigurationComposer.BOX_TYPES.contains(color) || !GiftConfigurationComposer.RIBBON_TYPES.contains(ribbonId)) {
|
||||||
LOGGER.error("DEBUG GIFT: invalid color/ribbon -> color={}, ribbonId={}", color, ribbonId);
|
LOGGER.debug("invalid color/ribbon -> color={}, ribbonId={}", color, ribbonId);
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.length() > Emulator.getConfig().getInt("hotel.gifts.length.max", 300)) {
|
|
||||||
message = message.substring(0, Emulator.getConfig().getInt("hotel.gifts.length.max", 300));
|
|
||||||
}
|
|
||||||
|
|
||||||
Integer iItemId = Emulator.getGameEnvironment().getCatalogManager().giftWrappers.get(spriteId);
|
Integer iItemId = Emulator.getGameEnvironment().getCatalogManager().giftWrappers.get(spriteId);
|
||||||
|
|
||||||
if (iItemId == null) {
|
if (iItemId == null) {
|
||||||
@@ -106,7 +111,7 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (iItemId == null) {
|
if (iItemId == null) {
|
||||||
LOGGER.error("DEBUG GIFT: iItemId null for spriteId={}", spriteId);
|
LOGGER.debug("iItemId null for spriteId={}", spriteId);
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -114,7 +119,7 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
Item giftItem = Emulator.getGameEnvironment().getItemManager().getItem(iItemId);
|
Item giftItem = Emulator.getGameEnvironment().getItemManager().getItem(iItemId);
|
||||||
|
|
||||||
if (giftItem == null) {
|
if (giftItem == null) {
|
||||||
LOGGER.error("DEBUG GIFT: direct giftItem null, trying random fallback. iItemId={}", iItemId);
|
LOGGER.debug("direct giftItem null, trying random fallback. iItemId={}", iItemId);
|
||||||
giftItem = Emulator.getGameEnvironment().getItemManager().getItem(
|
giftItem = Emulator.getGameEnvironment().getItemManager().getItem(
|
||||||
(Integer) Emulator.getGameEnvironment().getCatalogManager().giftFurnis.values().toArray()[
|
(Integer) Emulator.getGameEnvironment().getCatalogManager().giftFurnis.values().toArray()[
|
||||||
Emulator.getRandom().nextInt(Emulator.getGameEnvironment().getCatalogManager().giftFurnis.size())
|
Emulator.getRandom().nextInt(Emulator.getGameEnvironment().getCatalogManager().giftFurnis.size())
|
||||||
@@ -122,7 +127,7 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (giftItem == null) {
|
if (giftItem == null) {
|
||||||
LOGGER.error("DEBUG GIFT: fallback giftItem also null");
|
LOGGER.debug("fallback giftItem also null");
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -132,7 +137,7 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(username);
|
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(username);
|
||||||
|
|
||||||
if (habbo == null) {
|
if (habbo == null) {
|
||||||
LOGGER.error("DEBUG GIFT: target user not online, checking DB -> {}", username);
|
LOGGER.debug("target user not online, checking DB -> {}", username);
|
||||||
try (PreparedStatement statement = connection.prepareStatement("SELECT id FROM users WHERE username = ?")) {
|
try (PreparedStatement statement = connection.prepareStatement("SELECT id FROM users WHERE username = ?")) {
|
||||||
statement.setString(1, username);
|
statement.setString(1, username);
|
||||||
|
|
||||||
@@ -149,7 +154,7 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (userId == 0) {
|
if (userId == 0) {
|
||||||
LOGGER.error("DEBUG GIFT: receiver not found -> {}", username);
|
LOGGER.debug("receiver not found -> {}", username);
|
||||||
this.client.sendResponse(new GiftReceiverNotFoundComposer());
|
this.client.sendResponse(new GiftReceiverNotFoundComposer());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -157,38 +162,47 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().catalogPages.get(pageId);
|
CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().catalogPages.get(pageId);
|
||||||
|
|
||||||
if (page == null) {
|
if (page == null) {
|
||||||
LOGGER.error("DEBUG GIFT: page null -> {}", pageId);
|
LOGGER.debug("page null -> {}", pageId);
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (page.getRank() > this.client.getHabbo().getHabboInfo().getRank().getId() || !page.isEnabled() || !page.isVisible()) {
|
if (page.getRank() > this.client.getHabbo().getHabboInfo().getRank().getId() || !page.isEnabled() || !page.isVisible()) {
|
||||||
LOGGER.error("DEBUG GIFT: page access denied. pageRank={}, userRank={}, enabled={}, visible={}",
|
LOGGER.debug("page access denied. pageRank={}, userRank={}, enabled={}, visible={}", page.getRank(), this.client.getHabbo().getHabboInfo().getRank().getId(), page.isEnabled(), page.isVisible());
|
||||||
page.getRank(),
|
|
||||||
this.client.getHabbo().getHabboInfo().getRank().getId(),
|
|
||||||
page.isEnabled(),
|
|
||||||
page.isVisible());
|
|
||||||
this.client.sendResponse(new AlertPurchaseUnavailableComposer(AlertPurchaseUnavailableComposer.ILLEGAL));
|
this.client.sendResponse(new AlertPurchaseUnavailableComposer(AlertPurchaseUnavailableComposer.ILLEGAL));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CatalogItem item = page.getCatalogItem(itemId);
|
CatalogItem item = page.getCatalogItem(itemId);
|
||||||
|
|
||||||
|
// Search-results gift sends the catalog offer_id as
|
||||||
|
// itemId, not catalog_items.id - see the same fix in
|
||||||
|
// CatalogBuyItemEvent. Fall back to scanning the
|
||||||
|
// page for the matching offer_id.
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
LOGGER.error("DEBUG GIFT: catalog item null -> {}", itemId);
|
for (CatalogItem candidate : page.getCatalogItems().valueCollection()) {
|
||||||
|
if (candidate != null && candidate.getOfferId() == itemId) {
|
||||||
|
item = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item == null) {
|
||||||
|
LOGGER.debug("catalog item null -> {}", itemId);
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.isClubOnly() && !this.client.getHabbo().getHabboStats().hasActiveClub()) {
|
if (item.isClubOnly() && !this.client.getHabbo().getHabboStats().hasActiveClub()) {
|
||||||
LOGGER.error("DEBUG GIFT: item requires club -> itemId={}", itemId);
|
LOGGER.debug("item requires club -> itemId={}", itemId);
|
||||||
this.client.sendResponse(new AlertPurchaseUnavailableComposer(AlertPurchaseUnavailableComposer.REQUIRES_CLUB));
|
this.client.sendResponse(new AlertPurchaseUnavailableComposer(AlertPurchaseUnavailableComposer.REQUIRES_CLUB));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Item baseItem : item.getBaseItems()) {
|
for (Item baseItem : item.getBaseItems()) {
|
||||||
if (!baseItem.allowGift()) {
|
if (!baseItem.allowGift()) {
|
||||||
LOGGER.error("DEBUG GIFT: base item not giftable -> baseItemId={}, name={}", baseItem.getId(), baseItem.getName());
|
LOGGER.debug("base item not giftable -> baseItemId={}, name={}", baseItem.getId(), baseItem.getName());
|
||||||
this.client.sendResponse(new AlertPurchaseUnavailableComposer(AlertPurchaseUnavailableComposer.ILLEGAL));
|
this.client.sendResponse(new AlertPurchaseUnavailableComposer(AlertPurchaseUnavailableComposer.ILLEGAL));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -196,7 +210,7 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
|
|
||||||
if (item.isLimited()) {
|
if (item.isLimited()) {
|
||||||
if (item.getLimitedStack() == item.getLimitedSells()) {
|
if (item.getLimitedStack() == item.getLimitedSells()) {
|
||||||
LOGGER.error("DEBUG GIFT: LTD sold out -> itemId={}", itemId);
|
LOGGER.debug("LTD sold out -> itemId={}", itemId);
|
||||||
this.client.sendResponse(new AlertLimitedSoldOutComposer());
|
this.client.sendResponse(new AlertLimitedSoldOutComposer());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -205,14 +219,14 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
int totalCredits = item.getCredits();
|
int totalCredits = item.getCredits();
|
||||||
int totalPoints = item.getPoints();
|
int totalPoints = item.getPoints();
|
||||||
|
|
||||||
|
// Paid wrapping (giftWrappers) costs hotel.gifts.special.price; default furni wrap is free.
|
||||||
|
boolean isPaidWrap = Emulator.getGameEnvironment().getCatalogManager().giftWrappers.containsKey(spriteId);
|
||||||
|
int wrapFee = isPaidWrap ? Emulator.getConfig().getInt("hotel.gifts.special.price", 0) : 0;
|
||||||
|
totalCredits += wrapFee;
|
||||||
|
|
||||||
if (totalCredits > this.client.getHabbo().getHabboInfo().getCredits()
|
if (totalCredits > this.client.getHabbo().getHabboInfo().getCredits()
|
||||||
|| totalPoints > this.client.getHabbo().getHabboInfo().getCurrencyAmount(item.getPointsType())) {
|
|| totalPoints > this.client.getHabbo().getHabboInfo().getCurrencyAmount(item.getPointsType())) {
|
||||||
LOGGER.error("DEBUG GIFT: not enough currency. creditsNeeded={}, creditsHave={}, pointsNeeded={}, pointsHave={}, pointsType={}",
|
LOGGER.debug("not enough currency. creditsNeeded={}, pointsNeeded={}, pointsType={}", totalCredits, totalPoints, item.getPointsType());
|
||||||
totalCredits,
|
|
||||||
this.client.getHabbo().getHabboInfo().getCredits(),
|
|
||||||
totalPoints,
|
|
||||||
this.client.getHabbo().getHabboInfo().getCurrencyAmount(item.getPointsType()),
|
|
||||||
item.getPointsType());
|
|
||||||
this.client.sendResponse(new AlertPurchaseUnavailableComposer(AlertPurchaseUnavailableComposer.ILLEGAL));
|
this.client.sendResponse(new AlertPurchaseUnavailableComposer(AlertPurchaseUnavailableComposer.ILLEGAL));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -223,7 +237,7 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
|
|
||||||
if (item.isLimited()) {
|
if (item.isLimited()) {
|
||||||
if (Emulator.getGameEnvironment().getCatalogManager().getLimitedConfig(item).available() == 0) {
|
if (Emulator.getGameEnvironment().getCatalogManager().getLimitedConfig(item).available() == 0) {
|
||||||
LOGGER.error("DEBUG GIFT: LTD available=0 -> itemId={}", itemId);
|
LOGGER.debug("LTD available=0 -> itemId={}", itemId);
|
||||||
this.client.sendResponse(new AlertLimitedSoldOutComposer());
|
this.client.sendResponse(new AlertLimitedSoldOutComposer());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -231,7 +245,7 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
if (Emulator.getConfig().getBoolean("hotel.catalog.ltd.limit.enabled")) {
|
if (Emulator.getConfig().getBoolean("hotel.catalog.ltd.limit.enabled")) {
|
||||||
int ltdLimit = Emulator.getConfig().getInt("hotel.purchase.ltd.limit.daily.total");
|
int ltdLimit = Emulator.getConfig().getInt("hotel.purchase.ltd.limit.daily.total");
|
||||||
if (this.client.getHabbo().getHabboStats().totalLtds() >= ltdLimit) {
|
if (this.client.getHabbo().getHabboStats().totalLtds() >= ltdLimit) {
|
||||||
LOGGER.error("DEBUG GIFT: sender reached daily total LTD limit");
|
LOGGER.debug("sender reached daily total LTD limit");
|
||||||
this.client.getHabbo().alert(
|
this.client.getHabbo().alert(
|
||||||
Emulator.getTexts().getValue("error.catalog.buy.limited.daily.total")
|
Emulator.getTexts().getValue("error.catalog.buy.limited.daily.total")
|
||||||
.replace("%itemname%", item.getBaseItems().iterator().next().getFullName())
|
.replace("%itemname%", item.getBaseItems().iterator().next().getFullName())
|
||||||
@@ -242,7 +256,7 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
|
|
||||||
ltdLimit = Emulator.getConfig().getInt("hotel.purchase.ltd.limit.daily.item");
|
ltdLimit = Emulator.getConfig().getInt("hotel.purchase.ltd.limit.daily.item");
|
||||||
if (this.client.getHabbo().getHabboStats().totalLtds(item.getId()) >= ltdLimit) {
|
if (this.client.getHabbo().getHabboStats().totalLtds(item.getId()) >= ltdLimit) {
|
||||||
LOGGER.error("DEBUG GIFT: sender reached daily LTD item limit");
|
LOGGER.debug("sender reached daily LTD item limit");
|
||||||
this.client.getHabbo().alert(
|
this.client.getHabbo().alert(
|
||||||
Emulator.getTexts().getValue("error.catalog.buy.limited.daily.item")
|
Emulator.getTexts().getValue("error.catalog.buy.limited.daily.item")
|
||||||
.replace("%itemname%", item.getBaseItems().iterator().next().getFullName())
|
.replace("%itemname%", item.getBaseItems().iterator().next().getFullName())
|
||||||
@@ -293,20 +307,20 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (badgeFound) {
|
if (badgeFound) {
|
||||||
LOGGER.error("DEBUG GIFT: receiver already has badge");
|
LOGGER.debug("receiver already has badge");
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.ALREADY_HAVE_BADGE));
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.ALREADY_HAVE_BADGE));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.getAmount() > 1 || item.getBaseItems().size() > 1) {
|
if (item.getAmount() > 1 || item.getBaseItems().size() > 1) {
|
||||||
LOGGER.error("DEBUG GIFT: unsupported multi amount/baseItems. amount={}, baseItems={}", item.getAmount(), item.getBaseItems().size());
|
LOGGER.debug("unsupported multi amount/baseItems. amount={}, baseItems={}", item.getAmount(), item.getBaseItems().size());
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Item baseItem : item.getBaseItems()) {
|
for (Item baseItem : item.getBaseItems()) {
|
||||||
if (item.getItemAmount(baseItem.getId()) > 1) {
|
if (item.getItemAmount(baseItem.getId()) > 1) {
|
||||||
LOGGER.error("DEBUG GIFT: unsupported item amount > 1 for baseItemId={}", baseItem.getId());
|
LOGGER.debug("unsupported item amount > 1 for baseItemId={}", baseItem.getId());
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -330,11 +344,11 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
badgeFound = true;
|
badgeFound = true;
|
||||||
}
|
}
|
||||||
} else if (item.getName().startsWith("rentable_bot_")) {
|
} else if (item.getName().startsWith("rentable_bot_")) {
|
||||||
LOGGER.error("DEBUG GIFT: rentable bot gifts not supported");
|
LOGGER.debug("rentable bot gifts not supported");
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
||||||
return;
|
return;
|
||||||
} else if (Item.isPet(baseItem)) {
|
} else if (Item.isPet(baseItem)) {
|
||||||
LOGGER.error("DEBUG GIFT: pet gifts not supported");
|
LOGGER.debug("pet gifts not supported");
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -370,8 +384,7 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
HabboItem teleportTwo = Emulator.getGameEnvironment().getItemManager().createItem(userId, baseItem, limitedStack, limitedNumber, extraData);
|
HabboItem teleportTwo = Emulator.getGameEnvironment().getItemManager().createItem(userId, baseItem, limitedStack, limitedNumber, extraData);
|
||||||
|
|
||||||
if (teleportOne == null || teleportTwo == null) {
|
if (teleportOne == null || teleportTwo == null) {
|
||||||
LOGGER.error("DEBUG GIFT: teleport creation failed. baseItemId={}, teleportOneNull={}, teleportTwoNull={}",
|
LOGGER.debug("teleport creation failed. baseItemId={}, teleportOneNull={}, teleportTwoNull={}", baseItem.getId(), teleportOne == null, teleportTwo == null);
|
||||||
baseItem.getId(), teleportOne == null, teleportTwo == null);
|
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -384,7 +397,7 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
HabboItem habboItem = Emulator.getGameEnvironment().getItemManager().createItem(userId, baseItem, limitedNumber, limitedNumber, extraData);
|
HabboItem habboItem = Emulator.getGameEnvironment().getItemManager().createItem(userId, baseItem, limitedNumber, limitedNumber, extraData);
|
||||||
|
|
||||||
if (habboItem == null) {
|
if (habboItem == null) {
|
||||||
LOGGER.error("DEBUG GIFT: hopper creation failed. baseItemId={}", baseItem.getId());
|
LOGGER.debug("hopper creation failed. baseItemId={}", baseItem.getId());
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -397,13 +410,13 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
HabboItem createdItem = Emulator.getGameEnvironment().getItemManager().createItem(userId, baseItem, limitedStack, limitedNumber, extraData);
|
HabboItem createdItem = Emulator.getGameEnvironment().getItemManager().createItem(userId, baseItem, limitedStack, limitedNumber, extraData);
|
||||||
|
|
||||||
if (createdItem == null) {
|
if (createdItem == null) {
|
||||||
LOGGER.error("DEBUG GIFT: guild item creation failed. baseItemId={}", baseItem.getId());
|
LOGGER.debug("guild item creation failed. baseItemId={}", baseItem.getId());
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(createdItem instanceof InteractionGuildFurni)) {
|
if (!(createdItem instanceof InteractionGuildFurni)) {
|
||||||
LOGGER.error("DEBUG GIFT: created guild item has wrong class -> {}", createdItem.getClass().getName());
|
LOGGER.debug("created guild item has wrong class -> {}", createdItem.getClass().getName());
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -428,7 +441,7 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
HabboItem habboItem = Emulator.getGameEnvironment().getItemManager().createItem(userId, baseItem, limitedStack, limitedNumber, extraData);
|
HabboItem habboItem = Emulator.getGameEnvironment().getItemManager().createItem(userId, baseItem, limitedStack, limitedNumber, extraData);
|
||||||
|
|
||||||
if (habboItem == null) {
|
if (habboItem == null) {
|
||||||
LOGGER.error("DEBUG GIFT: normal item creation failed. baseItemId={}, baseItemName={}", baseItem.getId(), baseItem.getName());
|
LOGGER.debug("normal item creation failed. baseItemId={}, baseItemName={}", baseItem.getId(), baseItem.getName());
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -437,7 +450,7 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOGGER.error("DEBUG GIFT: avatar_effect not supported");
|
LOGGER.debug("avatar_effect not supported");
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
||||||
this.client.sendResponse(new GenericAlertComposer(Emulator.getTexts().getValue("error.catalog.buy.not_yet")));
|
this.client.sendResponse(new GenericAlertComposer(Emulator.getTexts().getValue("error.catalog.buy.not_yet")));
|
||||||
return;
|
return;
|
||||||
@@ -446,7 +459,7 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (itemsList.isEmpty()) {
|
if (itemsList.isEmpty()) {
|
||||||
LOGGER.error("DEBUG GIFT: itemsList empty before giftData");
|
LOGGER.debug("itemsList empty before giftData");
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -455,7 +468,7 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
|
|
||||||
for (HabboItem i : itemsList) {
|
for (HabboItem i : itemsList) {
|
||||||
if (i == null) {
|
if (i == null) {
|
||||||
LOGGER.error("DEBUG GIFT: null HabboItem detected inside itemsList");
|
LOGGER.debug("null HabboItem detected inside itemsList");
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -475,10 +488,37 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
.append("\t")
|
.append("\t")
|
||||||
.append(this.client.getHabbo().getHabboInfo().getLook());
|
.append(this.client.getHabbo().getHabboInfo().getLook());
|
||||||
|
|
||||||
|
// Deduct currency before createGift so a failure here leaves the sender unpaid rather than gifted.
|
||||||
|
if (!this.client.getHabbo().hasPermission(Permission.ACC_INFINITE_CREDITS) && totalCredits > 0) {
|
||||||
|
this.client.getHabbo().giveCredits(-totalCredits);
|
||||||
|
paidCredits = totalCredits;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalPoints > 0) {
|
||||||
|
if (item.getPointsType() == 0 && !this.client.getHabbo().hasPermission(Permission.ACC_INFINITE_PIXELS)) {
|
||||||
|
this.client.getHabbo().givePixels(-totalPoints);
|
||||||
|
paidPoints = totalPoints;
|
||||||
|
paidPointsType = 0;
|
||||||
|
} else if (!this.client.getHabbo().hasPermission(Permission.ACC_INFINITE_POINTS)) {
|
||||||
|
this.client.getHabbo().givePoints(item.getPointsType(), -totalPoints);
|
||||||
|
paidPoints = totalPoints;
|
||||||
|
paidPointsType = item.getPointsType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HabboItem gift = Emulator.getGameEnvironment().getItemManager().createGift(username, giftItem, giftData.toString(), 0, 0);
|
HabboItem gift = Emulator.getGameEnvironment().getItemManager().createGift(username, giftItem, giftData.toString(), 0, 0);
|
||||||
|
|
||||||
if (gift == null) {
|
if (gift == null) {
|
||||||
LOGGER.error("DEBUG GIFT: createGift returned null");
|
LOGGER.debug("createGift returned null");
|
||||||
|
if (paidCredits > 0) {
|
||||||
|
this.client.getHabbo().giveCredits(paidCredits);
|
||||||
|
paidCredits = 0;
|
||||||
|
}
|
||||||
|
if (paidPoints > 0) {
|
||||||
|
if (paidPointsType == 0) this.client.getHabbo().givePixels(paidPoints);
|
||||||
|
else this.client.getHabbo().givePoints(paidPointsType, paidPoints);
|
||||||
|
paidPoints = 0;
|
||||||
|
}
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -486,9 +526,8 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
if (limitedConfiguration != null) {
|
if (limitedConfiguration != null) {
|
||||||
for (HabboItem itm : itemsList) {
|
for (HabboItem itm : itemsList) {
|
||||||
if (itm == null) {
|
if (itm == null) {
|
||||||
LOGGER.error("DEBUG GIFT: null item before limitedSold()");
|
// Trip the catch path so the deduction is refunded.
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
throw new IllegalStateException("null item before limitedSold()");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
limitedConfiguration.limitedSold(item.getId(), this.client.getHabbo(), itm);
|
limitedConfiguration.limitedSold(item.getId(), this.client.getHabbo(), itm);
|
||||||
}
|
}
|
||||||
@@ -526,32 +565,189 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_INFINITE_CREDITS)) {
|
// Gift fully delivered; commit cooldown and clear refund tracking so the catch block can't double-refund.
|
||||||
if (totalCredits > 0) {
|
this.client.getHabbo().getHabboStats().lastGiftTimestamp = Emulator.getIntUnixTimestamp();
|
||||||
this.client.getHabbo().giveCredits(-totalCredits);
|
paidCredits = 0;
|
||||||
}
|
paidPoints = 0;
|
||||||
}
|
|
||||||
|
|
||||||
if (totalPoints > 0) {
|
|
||||||
if (item.getPointsType() == 0 && !this.client.getHabbo().hasPermission(Permission.ACC_INFINITE_PIXELS)) {
|
|
||||||
this.client.getHabbo().givePixels(-totalPoints);
|
|
||||||
} else if (!this.client.getHabbo().hasPermission(Permission.ACC_INFINITE_POINTS)) {
|
|
||||||
this.client.getHabbo().givePoints(item.getPointsType(), -totalPoints);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.error("DEBUG GIFT: success sending PurchaseOKComposer");
|
|
||||||
this.client.sendResponse(new PurchaseOKComposer(item));
|
this.client.sendResponse(new PurchaseOKComposer(item));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.error("Exception caught", e);
|
LOGGER.error("Exception caught", e);
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
||||||
|
if (paidCredits > 0) this.client.getHabbo().giveCredits(paidCredits);
|
||||||
|
if (paidPoints > 0) {
|
||||||
|
if (paidPointsType == 0) this.client.getHabbo().givePixels(paidPoints);
|
||||||
|
else this.client.getHabbo().givePoints(paidPointsType, paidPoints);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.client.getHabbo().getHabboStats().isPurchasingFurniture = false;
|
this.client.getHabbo().getHabboStats().isPurchasingFurniture = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LOGGER.error("DEBUG GIFT: cooldown blocked purchase");
|
LOGGER.debug("cooldown blocked purchase");
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isClubOfferPage(CatalogPage page) {
|
||||||
|
return page instanceof ClubBuyLayout
|
||||||
|
|| page instanceof VipBuyLayout
|
||||||
|
|| page instanceof BuildersClubFrontPageLayout
|
||||||
|
|| page instanceof BuildersClubAddonsLayout
|
||||||
|
|| page instanceof BuildersClubLoyaltyLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getClubOfferWindowId(CatalogPage page) {
|
||||||
|
if (page instanceof BuildersClubAddonsLayout) {
|
||||||
|
return ClubOffer.WINDOW_BUILDERS_CLUB_ADDONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page instanceof BuildersClubFrontPageLayout || page instanceof BuildersClubLoyaltyLayout) {
|
||||||
|
return ClubOffer.WINDOW_BUILDERS_CLUB;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ClubOffer.WINDOW_HABBO_CLUB;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleClubOfferGift(CatalogPage page, int offerId, String username) {
|
||||||
|
ClubOffer offer = Emulator.getGameEnvironment().getCatalogManager().clubOffers.get(offerId);
|
||||||
|
|
||||||
|
if (offer == null || !offer.belongsToWindow(this.getClubOfferWindowId(page))) {
|
||||||
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!offer.isGiftable()) {
|
||||||
|
this.client.sendResponse(new AlertPurchaseUnavailableComposer(AlertPurchaseUnavailableComposer.ILLEGAL));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offer.isBuildersClubAddon()) {
|
||||||
|
this.client.sendResponse(new AlertPurchaseUnavailableComposer(AlertPurchaseUnavailableComposer.ILLEGAL));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalCredits = offer.getCredits();
|
||||||
|
int totalPoints = offer.getPoints();
|
||||||
|
|
||||||
|
if (totalCredits > this.client.getHabbo().getHabboInfo().getCredits()
|
||||||
|
|| totalPoints > this.client.getHabbo().getHabboInfo().getCurrencyAmount(offer.getPointsType())) {
|
||||||
|
this.client.sendResponse(new AlertPurchaseUnavailableComposer(AlertPurchaseUnavailableComposer.ILLEGAL));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Habbo recipient = Emulator.getGameEnvironment().getHabboManager().getHabbo(username);
|
||||||
|
int recipientId = 0;
|
||||||
|
|
||||||
|
if (recipient != null) {
|
||||||
|
recipientId = recipient.getHabboInfo().getId();
|
||||||
|
} else {
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement("SELECT id FROM users WHERE username = ?")) {
|
||||||
|
statement.setString(1, username);
|
||||||
|
try (ResultSet set = statement.executeQuery()) {
|
||||||
|
if (set.next()) recipientId = set.getInt(1);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Caught SQL exception while resolving club gift recipient", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipientId == 0) {
|
||||||
|
this.client.sendResponse(new GiftReceiverNotFoundComposer());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipientId == this.client.getHabbo().getHabboInfo().getId()) {
|
||||||
|
this.client.sendResponse(new AlertPurchaseUnavailableComposer(AlertPurchaseUnavailableComposer.ILLEGAL));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String subscriptionType = offer.isBuildersClubSubscription() ? Subscription.BUILDERS_CLUB : Subscription.HABBO_CLUB;
|
||||||
|
int duration = offer.getDays() * 86400;
|
||||||
|
|
||||||
|
boolean extended;
|
||||||
|
if (recipient != null) {
|
||||||
|
extended = (recipient.getHabboStats().createSubscription(subscriptionType, duration) != null);
|
||||||
|
} else {
|
||||||
|
extended = this.extendOfflineSubscription(recipientId, subscriptionType, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!extended) {
|
||||||
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalCredits > 0 && !this.client.getHabbo().hasPermission(Permission.ACC_INFINITE_CREDITS)) {
|
||||||
|
this.client.getHabbo().giveCredits(-totalCredits);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalPoints > 0) {
|
||||||
|
if (offer.getPointsType() == 0 && !this.client.getHabbo().hasPermission(Permission.ACC_INFINITE_PIXELS)) {
|
||||||
|
this.client.getHabbo().givePixels(-totalPoints);
|
||||||
|
} else if (!this.client.getHabbo().hasPermission(Permission.ACC_INFINITE_POINTS)) {
|
||||||
|
this.client.getHabbo().givePoints(offer.getPointsType(), -totalPoints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipient != null) {
|
||||||
|
recipient.getClient().sendResponse(new UserClubComposer(recipient, subscriptionType, UserClubComposer.RESPONSE_TYPE_NORMAL));
|
||||||
|
|
||||||
|
String prefix = Emulator.getTexts().getValue("prereg.reward.you.received", "You have received:");
|
||||||
|
String daysWord = Emulator.getTexts().getValue("generic.days", "days");
|
||||||
|
String clubLabel = offer.isBuildersClubSubscription() ? "Builders Club" : "HC";
|
||||||
|
String giftDescription = clubLabel + " (" + offer.getDays() + " " + daysWord + ")";
|
||||||
|
THashMap<String, String> keys = new THashMap<>();
|
||||||
|
keys.put("display", "BUBBLE");
|
||||||
|
keys.put("image", "${image.library.url}notifications/gift.gif");
|
||||||
|
keys.put("message", prefix + " " + giftDescription);
|
||||||
|
recipient.getClient().sendResponse(new BubbleAlertComposer(BubbleAlertKeys.RECEIVED_GIFT.key, keys));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.client.getHabbo().getHabboInfo().getId() != recipientId) {
|
||||||
|
AchievementManager.progressAchievement(
|
||||||
|
this.client.getHabbo(),
|
||||||
|
Emulator.getGameEnvironment().getAchievementManager().getAchievement("GiftGiver")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client.sendResponse(new PurchaseOKComposer(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean extendOfflineSubscription(int userId, String subscriptionType, int duration) {
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) {
|
||||||
|
try (PreparedStatement select = connection.prepareStatement(
|
||||||
|
"SELECT id, duration FROM users_subscriptions WHERE user_id = ? AND subscription_type = ? AND active = 1 ORDER BY id DESC LIMIT 1")) {
|
||||||
|
select.setInt(1, userId);
|
||||||
|
select.setString(2, subscriptionType);
|
||||||
|
try (ResultSet set = select.executeQuery()) {
|
||||||
|
if (set.next()) {
|
||||||
|
int subId = set.getInt("id");
|
||||||
|
int existing = set.getInt("duration");
|
||||||
|
try (PreparedStatement update = connection.prepareStatement(
|
||||||
|
"UPDATE users_subscriptions SET duration = ? WHERE id = ?")) {
|
||||||
|
update.setInt(1, existing + duration);
|
||||||
|
update.setInt(2, subId);
|
||||||
|
update.executeUpdate();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try (PreparedStatement insert = connection.prepareStatement(
|
||||||
|
"INSERT INTO users_subscriptions (user_id, subscription_type, timestamp_start, duration, active) VALUES (?, ?, ?, ?, 1)",
|
||||||
|
Statement.RETURN_GENERATED_KEYS)) {
|
||||||
|
insert.setInt(1, userId);
|
||||||
|
insert.setString(2, subscriptionType);
|
||||||
|
insert.setInt(3, Emulator.getIntUnixTimestamp());
|
||||||
|
insert.setInt(4, duration);
|
||||||
|
insert.executeUpdate();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.error("Caught SQL exception while extending offline subscription", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+41
-7
@@ -5,6 +5,7 @@ import com.eu.habbo.habbohotel.bots.BotManager;
|
|||||||
import com.eu.habbo.habbohotel.catalog.*;
|
import com.eu.habbo.habbohotel.catalog.*;
|
||||||
import com.eu.habbo.habbohotel.catalog.layouts.*;
|
import com.eu.habbo.habbohotel.catalog.layouts.*;
|
||||||
import com.eu.habbo.habbohotel.items.FurnitureType;
|
import com.eu.habbo.habbohotel.items.FurnitureType;
|
||||||
|
import com.eu.habbo.habbohotel.items.Item;
|
||||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
import com.eu.habbo.habbohotel.pets.PetManager;
|
import com.eu.habbo.habbohotel.pets.PetManager;
|
||||||
import com.eu.habbo.habbohotel.rooms.BuildersClubRoomSupport;
|
import com.eu.habbo.habbohotel.rooms.BuildersClubRoomSupport;
|
||||||
@@ -201,15 +202,48 @@ public class CatalogBuyItemEvent extends MessageHandler {
|
|||||||
|
|
||||||
else
|
else
|
||||||
item = page.getCatalogItem(itemId);
|
item = page.getCatalogItem(itemId);
|
||||||
// temp patch, can a dev with better knowledge than me look into this asap pls.
|
|
||||||
if (page instanceof BotsLayout) {
|
// Search-results buy sends the catalog offer_id as itemId
|
||||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_UNLIMITED_BOTS) && this.client.getHabbo().getInventory().getBotsComponent().getBots().size() >= BotManager.MAXIMUM_BOT_INVENTORY_SIZE) {
|
// (FurnitureOffer.offerId is derived from furnidata's
|
||||||
this.client.getHabbo().alert(Emulator.getTexts().getValue("error.bots.max.inventory").replace("%amount%", BotManager.MAXIMUM_BOT_INVENTORY_SIZE + ""));
|
// purchaseOfferId, which matches `catalog_items.offer_id`),
|
||||||
return;
|
// not the `catalog_items.id` primary key that getCatalogItem
|
||||||
|
// expects. Fall back to scanning the page for the matching
|
||||||
|
// offer_id so the search → buy flow works.
|
||||||
|
if (item == null && !(page instanceof RecentPurchasesLayout)) {
|
||||||
|
for (CatalogItem candidate : page.getCatalogItems().valueCollection()) {
|
||||||
|
if (candidate != null && candidate.getOfferId() == itemId) {
|
||||||
|
item = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (page instanceof PetsLayout) {
|
// Inventory cap check based on the actual base items the
|
||||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_UNLIMITED_PETS) && this.client.getHabbo().getInventory().getPetsComponent().getPets().size() >= PetManager.MAXIMUM_PET_INVENTORY_SIZE) {
|
// purchase will create, not the page layout - bots/pets
|
||||||
|
// can legitimately live on bundle pages, search results,
|
||||||
|
// recent-purchases, etc., and the layout-instanceof check
|
||||||
|
// missed all those paths. Mirrors the bot/pet branches
|
||||||
|
// inside CatalogManager.purchaseItem (Item.isBot / isPet
|
||||||
|
// and the same prefix check) so detection stays in sync.
|
||||||
|
boolean itemHasBot = false;
|
||||||
|
boolean itemHasPet = false;
|
||||||
|
|
||||||
|
if (item != null) {
|
||||||
|
for (Item baseItem : item.getBaseItems()) {
|
||||||
|
if (baseItem == null) continue;
|
||||||
|
if (Item.isBot(baseItem)) itemHasBot = true;
|
||||||
|
if (Item.isPet(baseItem)) itemHasPet = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemHasBot && !this.client.getHabbo().hasPermission(Permission.ACC_UNLIMITED_BOTS)
|
||||||
|
&& this.client.getHabbo().getInventory().getBotsComponent().getBots().size() >= BotManager.MAXIMUM_BOT_INVENTORY_SIZE) {
|
||||||
|
this.client.getHabbo().alert(Emulator.getTexts().getValue("error.bots.max.inventory").replace("%amount%", BotManager.MAXIMUM_BOT_INVENTORY_SIZE + ""));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemHasPet) {
|
||||||
|
if (!this.client.getHabbo().hasPermission(Permission.ACC_UNLIMITED_PETS)
|
||||||
|
&& this.client.getHabbo().getInventory().getPetsComponent().getPets().size() >= PetManager.MAXIMUM_PET_INVENTORY_SIZE) {
|
||||||
this.client.getHabbo().alert(Emulator.getTexts().getValue("error.pets.max.inventory").replace("%amount%", PetManager.MAXIMUM_PET_INVENTORY_SIZE + ""));
|
this.client.getHabbo().alert(Emulator.getTexts().getValue("error.pets.max.inventory").replace("%amount%", PetManager.MAXIMUM_PET_INVENTORY_SIZE + ""));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-1
@@ -36,8 +36,21 @@ public class CatalogAdminCreatePageEvent extends MessageHandler {
|
|||||||
pageLayout = CatalogPageLayouts.default_3x3;
|
pageLayout = CatalogPageLayouts.default_3x3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parentId != -1 && Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(parentId) == null) {
|
||||||
|
this.client.sendResponse(new CatalogAdminResultComposer(false, "Parent page not found: " + parentId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iconType < 0) iconType = 0;
|
||||||
|
if (minRank < 1) minRank = 1;
|
||||||
|
if (orderNum < 0) orderNum = 0;
|
||||||
|
if (caption == null) caption = "";
|
||||||
|
if (caption2 == null) caption2 = "";
|
||||||
|
if (caption.length() > 128) caption = caption.substring(0, 128);
|
||||||
|
if (caption2.length() > 25) caption2 = caption2.substring(0, 25);
|
||||||
|
|
||||||
CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().createCatalogPage(
|
CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().createCatalogPage(
|
||||||
caption, caption2, 0, iconType, pageLayout, minRank, parentId, pageType, catalogMode
|
caption, caption2, 0, iconType, pageLayout, minRank, parentId, pageType, catalogMode
|
||||||
);
|
);
|
||||||
|
|
||||||
if (page == null) {
|
if (page == null) {
|
||||||
|
|||||||
+43
-7
@@ -1,6 +1,7 @@
|
|||||||
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
|
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
|
||||||
|
|
||||||
import com.eu.habbo.Emulator;
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.catalog.CatalogPage;
|
||||||
import com.eu.habbo.habbohotel.catalog.CatalogPageType;
|
import com.eu.habbo.habbohotel.catalog.CatalogPageType;
|
||||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
@@ -11,6 +12,9 @@ import java.sql.PreparedStatement;
|
|||||||
|
|
||||||
public class CatalogAdminMovePageEvent extends MessageHandler {
|
public class CatalogAdminMovePageEvent extends MessageHandler {
|
||||||
|
|
||||||
|
private static final int MAX_PARENT_WALK = 64;
|
||||||
|
private static final int ROOT_PARENT_ID = -1;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle() throws Exception {
|
public void handle() throws Exception {
|
||||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
|
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
|
||||||
@@ -24,12 +28,10 @@ public class CatalogAdminMovePageEvent extends MessageHandler {
|
|||||||
CatalogPageType pageType = CatalogPageType.fromString(this.packet.readString());
|
CatalogPageType pageType = CatalogPageType.fromString(this.packet.readString());
|
||||||
String tableName = (pageType == CatalogPageType.BUILDER) ? "catalog_pages_bc" : "catalog_pages";
|
String tableName = (pageType == CatalogPageType.BUILDER) ? "catalog_pages_bc" : "catalog_pages";
|
||||||
|
|
||||||
// Special values: -1 = toggle enabled, -2 = toggle visible
|
|
||||||
if (newParentId == -1) {
|
if (newParentId == -1) {
|
||||||
// Toggle enabled
|
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
PreparedStatement statement = connection.prepareStatement(
|
PreparedStatement statement = connection.prepareStatement(
|
||||||
"UPDATE " + tableName + " SET enabled = IF(enabled = '1', '0', '1') WHERE id = ?")) {
|
"UPDATE " + tableName + " SET enabled = IF(enabled = '1', '0', '1') WHERE id = ?")) {
|
||||||
statement.setInt(1, pageId);
|
statement.setInt(1, pageId);
|
||||||
statement.execute();
|
statement.execute();
|
||||||
}
|
}
|
||||||
@@ -38,21 +40,43 @@ public class CatalogAdminMovePageEvent extends MessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (newParentId == -2) {
|
if (newParentId == -2) {
|
||||||
// Toggle visible
|
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
PreparedStatement statement = connection.prepareStatement(
|
PreparedStatement statement = connection.prepareStatement(
|
||||||
"UPDATE " + tableName + " SET visible = IF(visible = '1', '0', '1') WHERE id = ?")) {
|
"UPDATE " + tableName + " SET visible = IF(visible = '1', '0', '1') WHERE id = ?")) {
|
||||||
statement.setInt(1, pageId);
|
statement.setInt(1, pageId);
|
||||||
statement.execute();
|
statement.execute();
|
||||||
}
|
}
|
||||||
this.client.sendResponse(new CatalogAdminResultComposer(true, "Visibility toggled"));
|
this.client.sendResponse(new CatalogAdminResultComposer(true, "Visibility toggled"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(pageId, pageType);
|
||||||
|
if (page == null) {
|
||||||
|
this.client.sendResponse(new CatalogAdminResultComposer(false, "Page not found: " + pageId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newParentId == pageId) {
|
||||||
|
this.client.sendResponse(new CatalogAdminResultComposer(false, "A page cannot be its own parent"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CatalogPage parent = Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(newParentId);
|
||||||
|
if (parent == null) {
|
||||||
|
this.client.sendResponse(new CatalogAdminResultComposer(false, "Parent page not found: " + newParentId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.wouldCreateCycle(pageId, newParentId)) {
|
||||||
|
this.client.sendResponse(new CatalogAdminResultComposer(false, "Refusing to move: that would create a cycle"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newIndex < 0) newIndex = 0;
|
||||||
|
|
||||||
// Normal move: update parent and order
|
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
PreparedStatement statement = connection.prepareStatement(
|
PreparedStatement statement = connection.prepareStatement(
|
||||||
"UPDATE " + tableName + " SET parent_id = ?, order_num = ? WHERE id = ?")) {
|
"UPDATE " + tableName + " SET parent_id = ?, order_num = ? WHERE id = ?")) {
|
||||||
statement.setInt(1, newParentId);
|
statement.setInt(1, newParentId);
|
||||||
statement.setInt(2, newIndex);
|
statement.setInt(2, newIndex);
|
||||||
statement.setInt(3, pageId);
|
statement.setInt(3, pageId);
|
||||||
@@ -61,4 +85,16 @@ public class CatalogAdminMovePageEvent extends MessageHandler {
|
|||||||
|
|
||||||
this.client.sendResponse(new CatalogAdminResultComposer(true, "Page moved"));
|
this.client.sendResponse(new CatalogAdminResultComposer(true, "Page moved"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean wouldCreateCycle(int pageId, int parentId) {
|
||||||
|
int current = parentId;
|
||||||
|
for (int hops = 0; hops < MAX_PARENT_WALK; hops++) {
|
||||||
|
if (current == ROOT_PARENT_ID) return false;
|
||||||
|
if (current == pageId) return true;
|
||||||
|
CatalogPage parent = Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(current);
|
||||||
|
if (parent == null) return false;
|
||||||
|
current = parent.getParentId();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+97
-6
@@ -2,16 +2,35 @@ package com.eu.habbo.messages.incoming.catalog.catalogadmin;
|
|||||||
|
|
||||||
import com.eu.habbo.Emulator;
|
import com.eu.habbo.Emulator;
|
||||||
import com.eu.habbo.habbohotel.catalog.CatalogPage;
|
import com.eu.habbo.habbohotel.catalog.CatalogPage;
|
||||||
|
import com.eu.habbo.habbohotel.catalog.CatalogPageLayouts;
|
||||||
import com.eu.habbo.habbohotel.catalog.CatalogPageType;
|
import com.eu.habbo.habbohotel.catalog.CatalogPageType;
|
||||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
import com.eu.habbo.messages.outgoing.catalog.catalogadmin.CatalogAdminResultComposer;
|
import com.eu.habbo.messages.outgoing.catalog.catalogadmin.CatalogAdminResultComposer;
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.safety.Safelist;
|
||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
|
|
||||||
public class CatalogAdminSavePageEvent extends MessageHandler {
|
public class CatalogAdminSavePageEvent extends MessageHandler {
|
||||||
|
|
||||||
|
private static final int MAX_CAPTION_LENGTH = 128;
|
||||||
|
private static final int MAX_CAPTION_SAVE_LENGTH = 25;
|
||||||
|
private static final int MAX_HEADLINE_LENGTH = 1024;
|
||||||
|
private static final int MAX_TEASER_LENGTH = 64;
|
||||||
|
private static final int MAX_TEXT_LENGTH = 8192;
|
||||||
|
private static final int MAX_PARENT_WALK = 64;
|
||||||
|
private static final int ROOT_PARENT_ID = -1;
|
||||||
|
|
||||||
|
private static final Safelist PAGE_HTML_SAFELIST = new Safelist()
|
||||||
|
.addTags("b", "i", "u", "br", "span", "div", "p", "a", "strong", "em", "img")
|
||||||
|
.addAttributes("a", "href", "target", "class", "style")
|
||||||
|
.addAttributes("img", "src", "alt", "class", "style")
|
||||||
|
.addAttributes(":all", "class", "style")
|
||||||
|
.addProtocols("a", "href", "http", "https", "mailto", "#")
|
||||||
|
.addProtocols("img", "src", "http", "https", "data");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle() throws Exception {
|
public void handle() throws Exception {
|
||||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
|
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
|
||||||
@@ -34,7 +53,7 @@ public class CatalogAdminSavePageEvent extends MessageHandler {
|
|||||||
String textDetails = this.packet.readString();
|
String textDetails = this.packet.readString();
|
||||||
CatalogPageType pageType = CatalogPageType.fromString(this.packet.readString());
|
CatalogPageType pageType = CatalogPageType.fromString(this.packet.readString());
|
||||||
CatalogPageType catalogMode = CatalogPageType.fromString(this.packet.readString());
|
CatalogPageType catalogMode = CatalogPageType.fromString(this.packet.readString());
|
||||||
|
String text1 = this.packet.bytesAvailable() > 0 ? this.packet.readString() : "";
|
||||||
CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(pageId, pageType);
|
CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(pageId, pageType);
|
||||||
|
|
||||||
if (page == null) {
|
if (page == null) {
|
||||||
@@ -42,9 +61,55 @@ public class CatalogAdminSavePageEvent extends MessageHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
CatalogPageLayouts.valueOf(layout);
|
||||||
|
} catch (IllegalArgumentException | NullPointerException e) {
|
||||||
|
this.client.sendResponse(new CatalogAdminResultComposer(false, "Invalid layout: " + layout));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentId != ROOT_PARENT_ID) {
|
||||||
|
if (parentId == pageId) {
|
||||||
|
this.client.sendResponse(new CatalogAdminResultComposer(false, "A page cannot be its own parent"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CatalogPage parent = Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(parentId);
|
||||||
|
if (parent == null) {
|
||||||
|
this.client.sendResponse(new CatalogAdminResultComposer(false, "Parent page not found: " + parentId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.wouldCreateCycle(pageId, parentId)) {
|
||||||
|
this.client.sendResponse(new CatalogAdminResultComposer(false, "Refusing to re-parent: that would create a cycle"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iconType < 0) iconType = 0;
|
||||||
|
if (minRank < 1) minRank = 1;
|
||||||
|
if (orderNum < 0) orderNum = 0;
|
||||||
|
|
||||||
|
headline = this.sanitizeHtml(headline);
|
||||||
|
teaser = this.sanitizeHtml(teaser);
|
||||||
|
textDetails = this.sanitizeHtml(textDetails);
|
||||||
|
text1 = this.sanitizeHtml(text1);
|
||||||
|
|
||||||
|
caption = this.clampLength(caption, MAX_CAPTION_LENGTH);
|
||||||
|
caption2 = this.clampLength(caption2, MAX_CAPTION_SAVE_LENGTH);
|
||||||
|
headline = this.clampLength(headline, MAX_HEADLINE_LENGTH);
|
||||||
|
teaser = this.clampLength(teaser, MAX_TEASER_LENGTH);
|
||||||
|
textDetails = this.clampLength(textDetails, MAX_TEXT_LENGTH);
|
||||||
|
text1 = this.clampLength(text1, MAX_TEXT_LENGTH);
|
||||||
|
|
||||||
|
if (headline.isEmpty() && page.getHeaderImage() != null) headline = page.getHeaderImage();
|
||||||
|
if (teaser.isEmpty() && page.getTeaserImage() != null) teaser = page.getTeaserImage();
|
||||||
|
if (textDetails.isEmpty() && page.getTextDetails() != null) textDetails = page.getTextDetails();
|
||||||
|
if (text1.isEmpty() && page.getTextOne() != null) text1 = page.getTextOne();
|
||||||
|
|
||||||
String query = (pageType == CatalogPageType.BUILDER)
|
String query = (pageType == CatalogPageType.BUILDER)
|
||||||
? "UPDATE catalog_pages_bc SET caption = ?, page_layout = ?, icon_image = ?, visible = ?, enabled = ?, order_num = ?, parent_id = ?, page_headline = ?, page_teaser = ?, page_text_details = ? WHERE id = ?"
|
? "UPDATE catalog_pages_bc SET caption = ?, page_layout = ?, icon_image = ?, visible = ?, enabled = ?, order_num = ?, parent_id = ?, page_headline = ?, page_teaser = ?, page_text_details = ?, page_text1 = ? WHERE id = ?"
|
||||||
: "UPDATE catalog_pages SET caption = ?, caption_save = ?, page_layout = ?, icon_image = ?, min_rank = ?, visible = ?, enabled = ?, order_num = ?, parent_id = ?, page_headline = ?, page_teaser = ?, page_text_details = ?, catalog_mode = ? WHERE id = ?";
|
: "UPDATE catalog_pages SET caption = ?, caption_save = ?, page_layout = ?, icon_image = ?, min_rank = ?, visible = ?, enabled = ?, order_num = ?, parent_id = ?, page_headline = ?, page_teaser = ?, page_text_details = ?, page_text1 = ?, catalog_mode = ? WHERE id = ?";
|
||||||
|
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
PreparedStatement statement = connection.prepareStatement(query)) {
|
PreparedStatement statement = connection.prepareStatement(query)) {
|
||||||
@@ -60,7 +125,8 @@ public class CatalogAdminSavePageEvent extends MessageHandler {
|
|||||||
statement.setString(8, headline);
|
statement.setString(8, headline);
|
||||||
statement.setString(9, teaser);
|
statement.setString(9, teaser);
|
||||||
statement.setString(10, textDetails);
|
statement.setString(10, textDetails);
|
||||||
statement.setInt(11, pageId);
|
statement.setString(11, text1);
|
||||||
|
statement.setInt(12, pageId);
|
||||||
} else {
|
} else {
|
||||||
statement.setString(2, caption2);
|
statement.setString(2, caption2);
|
||||||
statement.setString(3, layout);
|
statement.setString(3, layout);
|
||||||
@@ -73,8 +139,9 @@ public class CatalogAdminSavePageEvent extends MessageHandler {
|
|||||||
statement.setString(10, headline);
|
statement.setString(10, headline);
|
||||||
statement.setString(11, teaser);
|
statement.setString(11, teaser);
|
||||||
statement.setString(12, textDetails);
|
statement.setString(12, textDetails);
|
||||||
statement.setString(13, catalogMode.name());
|
statement.setString(13, text1);
|
||||||
statement.setInt(14, pageId);
|
statement.setString(14, catalogMode.name());
|
||||||
|
statement.setInt(15, pageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
statement.execute();
|
statement.execute();
|
||||||
@@ -82,4 +149,28 @@ public class CatalogAdminSavePageEvent extends MessageHandler {
|
|||||||
|
|
||||||
this.client.sendResponse(new CatalogAdminResultComposer(true, "Page saved"));
|
this.client.sendResponse(new CatalogAdminResultComposer(true, "Page saved"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean wouldCreateCycle(int pageId, int parentId) {
|
||||||
|
int current = parentId;
|
||||||
|
for (int hops = 0; hops < MAX_PARENT_WALK; hops++) {
|
||||||
|
if (current == ROOT_PARENT_ID) return false;
|
||||||
|
if (current == pageId) return true;
|
||||||
|
CatalogPage parent = Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(current);
|
||||||
|
if (parent == null) return false;
|
||||||
|
current = parent.getParentId();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String clampLength(String value, int max) {
|
||||||
|
if (value == null) return "";
|
||||||
|
if (value.length() <= max) return value;
|
||||||
|
return value.substring(0, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String sanitizeHtml(String value) {
|
||||||
|
if (value == null || value.isEmpty()) return "";
|
||||||
|
return Jsoup.clean(value, PAGE_HTML_SAFELIST);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+227
-123
@@ -4,23 +4,36 @@ import com.eu.habbo.Emulator;
|
|||||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
import com.eu.habbo.habbohotel.rooms.*;
|
import com.eu.habbo.habbohotel.rooms.*;
|
||||||
import com.eu.habbo.habbohotel.users.Habbo;
|
import com.eu.habbo.habbohotel.users.Habbo;
|
||||||
|
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||||
import com.eu.habbo.messages.ServerMessage;
|
import com.eu.habbo.messages.ServerMessage;
|
||||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
|
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
|
||||||
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys;
|
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys;
|
||||||
import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer;
|
import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer;
|
||||||
|
import com.eu.habbo.messages.outgoing.inventory.AddHabboItemComposer;
|
||||||
|
import com.eu.habbo.messages.outgoing.inventory.InventoryRefreshComposer;
|
||||||
import com.eu.habbo.messages.outgoing.rooms.ForwardToRoomComposer;
|
import com.eu.habbo.messages.outgoing.rooms.ForwardToRoomComposer;
|
||||||
import gnu.trove.set.hash.THashSet;
|
import gnu.trove.set.hash.THashSet;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.StringJoiner;
|
import java.util.StringJoiner;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class FloorPlanEditorSaveEvent extends MessageHandler {
|
public class FloorPlanEditorSaveEvent extends MessageHandler {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(FloorPlanEditorSaveEvent.class);
|
||||||
|
|
||||||
public static int MAXIMUM_FLOORPLAN_WIDTH_LENGTH = 64;
|
public static int MAXIMUM_FLOORPLAN_WIDTH_LENGTH = 64;
|
||||||
public static int MAXIMUM_FLOORPLAN_SIZE = 64 * 64;
|
public static int MAXIMUM_FLOORPLAN_SIZE = 64 * 64;
|
||||||
|
|
||||||
|
private static final int SAVE_COOLDOWN_SECONDS = 3;
|
||||||
|
private static final int MAX_AUTO_PICKUP_ITEMS = 500;
|
||||||
|
private static final Pattern ALLOWED_MAP_CHARS = Pattern.compile("[a-zA-Z0-9\r]+");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRatelimit() {
|
public int getRatelimit() {
|
||||||
return 500;
|
return 500;
|
||||||
@@ -38,153 +51,244 @@ public class FloorPlanEditorSaveEvent extends MessageHandler {
|
|||||||
if (room == null)
|
if (room == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (room.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || this.client.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER)) {
|
if (!(room.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || this.client.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER))) {
|
||||||
StringJoiner errors = new StringJoiner("<br />");
|
return;
|
||||||
String map = this.packet.readString();
|
}
|
||||||
map = map.replace("X", "x");
|
|
||||||
|
|
||||||
String[] mapRows = map.split("\r");
|
long now = Emulator.getIntUnixTimestamp();
|
||||||
|
if (now - this.client.getHabbo().getHabboStats().lastFloorplanSaveTimestamp < SAVE_COOLDOWN_SECONDS) {
|
||||||
|
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FLOORPLAN_EDITOR_ERROR.key, "Please wait a few seconds before saving again."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int firstRowSize = mapRows[0].length();
|
StringJoiner errors = new StringJoiner("<br />");
|
||||||
|
String map = this.packet.readString();
|
||||||
|
|
||||||
if (Emulator.getConfig().getBoolean("hotel.room.floorplan.check.enabled")) {
|
if (map == null || map.length() > MAXIMUM_FLOORPLAN_SIZE) {
|
||||||
if (!map.matches("[a-zA-Z0-9\r]+")) errors.add("${notification.floorplan_editor.error.title}");
|
LOGGER.warn("Floorplan save rejected (oversize): user={} room={} mapLen={}",
|
||||||
|
this.client.getHabbo().getHabboInfo().getId(), room.getId(), map == null ? 0 : map.length());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Arrays.stream(mapRows)
|
if (!ALLOWED_MAP_CHARS.matcher(map).matches()) {
|
||||||
.filter(line -> line.length() != firstRowSize)
|
LOGGER.warn("Floorplan save rejected (illegal chars): user={} room={}",
|
||||||
.findAny()
|
this.client.getHabbo().getHabboInfo().getId(), room.getId());
|
||||||
.ifPresent(s -> errors.add("(General): Line " + (Arrays.asList(mapRows).indexOf(s) + 1) + " is of different length than line 1"));
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (map.isEmpty() || map.replace("x", "").replace("\r", "").isEmpty()) {
|
map = map.replace("X", "x");
|
||||||
errors.add("${notification.floorplan_editor.error.message.effective_height_is_0}");
|
|
||||||
|
String[] mapRows = map.split("\r");
|
||||||
|
|
||||||
|
if (mapRows.length == 0 || mapRows.length > MAXIMUM_FLOORPLAN_WIDTH_LENGTH) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int firstRowSize = mapRows[0].length();
|
||||||
|
|
||||||
|
if (firstRowSize == 0 || firstRowSize > MAXIMUM_FLOORPLAN_WIDTH_LENGTH) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String row : mapRows) {
|
||||||
|
if (row.length() != firstRowSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Emulator.getConfig().getBoolean("hotel.room.floorplan.check.enabled")) {
|
||||||
|
if (map.replace("x", "").replace("\r", "").isEmpty()) {
|
||||||
|
errors.add("${notification.floorplan_editor.error.message.effective_height_is_0}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int doorX = this.packet.readInt();
|
||||||
|
int doorY = this.packet.readInt();
|
||||||
|
|
||||||
|
if (doorX < 0 || doorX >= firstRowSize || doorY < 0 || doorY >= mapRows.length) {
|
||||||
|
errors.add("${notification.floorplan_editor.error.message.entry_tile_outside_map}");
|
||||||
|
} else if (mapRows[doorY].charAt(doorX) == 'x') {
|
||||||
|
errors.add("${notification.floorplan_editor.error.message.entry_not_on_tile}");
|
||||||
|
}
|
||||||
|
|
||||||
|
int doorRotation = this.packet.readInt();
|
||||||
|
if (doorRotation < 0 || doorRotation > 7) {
|
||||||
|
errors.add("${notification.floorplan_editor.error.message.invalid_entry_tile_direction}");
|
||||||
|
}
|
||||||
|
|
||||||
|
int wallSize = this.packet.readInt();
|
||||||
|
if (wallSize < -2 || wallSize > 1) {
|
||||||
|
errors.add("${notification.floorplan_editor.error.message.invalid_wall_thickness}");
|
||||||
|
}
|
||||||
|
int floorSize = this.packet.readInt();
|
||||||
|
if (floorSize < -2 || floorSize > 1) {
|
||||||
|
errors.add("${notification.floorplan_editor.error.message.invalid_floor_thickness}");
|
||||||
|
}
|
||||||
|
|
||||||
|
int wallHeight = -1;
|
||||||
|
if (this.packet.bytesAvailable() >= 4)
|
||||||
|
wallHeight = this.packet.readInt();
|
||||||
|
|
||||||
|
if (wallHeight < -1 || wallHeight > 15) {
|
||||||
|
errors.add("${notification.floorplan_editor.error.message.invalid_walls_fixed_height}");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean autoPickup = false;
|
||||||
|
if (this.packet.bytesAvailable() >= 1) {
|
||||||
|
autoPickup = this.packet.readBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length() > 0) {
|
||||||
|
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FLOORPLAN_EDITOR_ERROR.key, errors.toString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
THashSet<RoomTile> locked_tileList = room.getLockedTiles();
|
||||||
|
THashSet<RoomTile> new_tileList = new THashSet<>();
|
||||||
|
THashSet<HabboItem> itemsToPickup = new THashSet<>();
|
||||||
|
int blockedX = -1;
|
||||||
|
int blockedY = -1;
|
||||||
|
blockingRoomItemScan:
|
||||||
|
for (int y = 0; y < mapRows.length; y++) {
|
||||||
|
for (int x = 0; x < firstRowSize; x++) {
|
||||||
|
|
||||||
|
RoomTile tile = room.getLayout().getTile((short) x, (short) y);
|
||||||
|
new_tileList.add(tile);
|
||||||
|
String square = String.valueOf(mapRows[y].charAt(x));
|
||||||
|
short height;
|
||||||
|
|
||||||
|
if (square.equalsIgnoreCase("x") && room.getTopItemAt(x, y) != null) {
|
||||||
|
if (autoPickup) {
|
||||||
|
THashSet<HabboItem> here = room.getItemsAt(x, y);
|
||||||
|
if (here != null) itemsToPickup.addAll(here);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
blockedX = x;
|
||||||
|
blockedY = y;
|
||||||
|
break blockingRoomItemScan;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (map.length() > MAXIMUM_FLOORPLAN_SIZE) {
|
try {
|
||||||
errors.add("${notification.floorplan_editor.error.message.too_large_area}");
|
if (square.isEmpty()) {
|
||||||
}
|
height = 0;
|
||||||
|
} else if (Emulator.isNumeric(square)) {
|
||||||
if (mapRows.length > MAXIMUM_FLOORPLAN_WIDTH_LENGTH) errors.add("${notification.floorplan_editor.error.message.too_large_height}");
|
height = Short.parseShort(square);
|
||||||
else if (Arrays.stream(mapRows).anyMatch(l -> l.length() > MAXIMUM_FLOORPLAN_WIDTH_LENGTH || l.isEmpty())) errors.add("${notification.floorplan_editor.error.message.too_large_width}");
|
} else {
|
||||||
|
int idx = "abcdefghijklmnopqrstuvwxyz".indexOf(square.toLowerCase());
|
||||||
if (errors.length() > 0) {
|
if (idx < 0) {
|
||||||
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FLOORPLAN_EDITOR_ERROR.key, errors.toString()));
|
return;
|
||||||
|
}
|
||||||
|
height = (short) Math.min(26, 10 + idx);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int doorX = this.packet.readInt();
|
if (tile != null && tile.state != RoomTileState.INVALID && height != tile.z && room.getTopItemAt(x, y) != null) {
|
||||||
int doorY = this.packet.readInt();
|
if (autoPickup) {
|
||||||
|
THashSet<HabboItem> here = room.getItemsAt(x, y);
|
||||||
if (doorX < 0 || doorX > firstRowSize || doorY < 0 || doorY >= mapRows.length) {
|
if (here != null) itemsToPickup.addAll(here);
|
||||||
errors.add("${notification.floorplan_editor.error.message.entry_tile_outside_map}");
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
if (doorY < mapRows.length && doorX < mapRows[doorY].length() && mapRows[doorY].charAt(doorX) == 'x') {
|
|
||||||
errors.add("${notification.floorplan_editor.error.message.entry_not_on_tile}");
|
|
||||||
}
|
|
||||||
|
|
||||||
int doorRotation = this.packet.readInt();
|
|
||||||
if (doorRotation < 0 || doorRotation > 7) {
|
|
||||||
errors.add("${notification.floorplan_editor.error.message.invalid_entry_tile_direction}");
|
|
||||||
}
|
|
||||||
|
|
||||||
int wallSize = this.packet.readInt();
|
|
||||||
if (wallSize < -2 || wallSize > 1) {
|
|
||||||
errors.add("${notification.floorplan_editor.error.message.invalid_wall_thickness}");
|
|
||||||
}
|
|
||||||
int floorSize = this.packet.readInt();
|
|
||||||
if (floorSize < -2 || floorSize > 1) {
|
|
||||||
errors.add("${notification.floorplan_editor.error.message.invalid_floor_thickness}");
|
|
||||||
}
|
|
||||||
|
|
||||||
int wallHeight = -1;
|
|
||||||
if (this.packet.bytesAvailable() >= 4)
|
|
||||||
wallHeight = this.packet.readInt();
|
|
||||||
|
|
||||||
if (wallHeight < -1 || wallHeight > 15) {
|
|
||||||
errors.add("${notification.floorplan_editor.error.message.invalid_walls_fixed_height}");
|
|
||||||
}
|
|
||||||
|
|
||||||
THashSet<RoomTile> locked_tileList = room.getLockedTiles();
|
|
||||||
THashSet<RoomTile> new_tileList = new THashSet<>();
|
|
||||||
blockingRoomItemScan:
|
|
||||||
for (int y = 0; y < mapRows.length; y++) {
|
|
||||||
for (int x = 0; x < firstRowSize; x++) {
|
|
||||||
|
|
||||||
RoomTile tile = room.getLayout().getTile((short) x, (short) y);
|
|
||||||
new_tileList.add(tile);
|
|
||||||
String square = String.valueOf(mapRows[y].charAt(x));
|
|
||||||
short height;
|
|
||||||
|
|
||||||
if (square.equalsIgnoreCase("x") && room.getTopItemAt(x, y) != null) {
|
|
||||||
errors.add("${notification.floorplan_editor.error.message.change_blocked_by_room_item}");
|
|
||||||
break blockingRoomItemScan;
|
|
||||||
} else {
|
|
||||||
if (square.isEmpty()) {
|
|
||||||
height = 0;
|
|
||||||
} else if (Emulator.isNumeric(square)) {
|
|
||||||
height = Short.parseShort(square);
|
|
||||||
} else {
|
|
||||||
height = (short) (10 + "ABCDEFGHIJKLMNOPQRSTUVWXYZ".indexOf(square.toUpperCase()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tile != null && tile.state != RoomTileState.INVALID && height != tile.z && room.getTopItemAt(x, y) != null) {
|
|
||||||
errors.add("${notification.floorplan_editor.error.message.change_blocked_by_room_item}");
|
|
||||||
break blockingRoomItemScan;
|
|
||||||
}
|
}
|
||||||
|
blockedX = x;
|
||||||
|
blockedY = y;
|
||||||
|
break blockingRoomItemScan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockedX < 0) {
|
||||||
locked_tileList.removeAll(new_tileList);
|
locked_tileList.removeAll(new_tileList);
|
||||||
if (!locked_tileList.isEmpty()) {
|
if (!locked_tileList.isEmpty()) {
|
||||||
errors.add("${notification.floorplan_editor.error.message.change_blocked_by_room_item}");
|
if (autoPickup) {
|
||||||
|
for (RoomTile lt : locked_tileList) {
|
||||||
|
THashSet<HabboItem> here = room.getItemsAt(lt.x, lt.y);
|
||||||
|
if (here != null) itemsToPickup.addAll(here);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RoomTile first = locked_tileList.iterator().next();
|
||||||
|
blockedX = first.x;
|
||||||
|
blockedY = first.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockedX >= 0) {
|
||||||
|
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FLOORPLAN_EDITOR_ERROR.key,
|
||||||
|
"${notification.floorplan_editor.error.message.change_blocked_by_room_item} (" + blockedX + ", " + blockedY + ")"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (autoPickup && !itemsToPickup.isEmpty()) {
|
||||||
if (errors.length() > 0) {
|
if (itemsToPickup.size() > MAX_AUTO_PICKUP_ITEMS) {
|
||||||
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FLOORPLAN_EDITOR_ERROR.key, errors.toString()));
|
LOGGER.warn("Floorplan auto-pickup rejected (over cap): user={} room={} itemCount={} cap={}",
|
||||||
|
this.client.getHabbo().getHabboInfo().getId(), room.getId(), itemsToPickup.size(), MAX_AUTO_PICKUP_ITEMS);
|
||||||
|
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FLOORPLAN_EDITOR_ERROR.key,
|
||||||
|
"Too many items would be picked up (" + itemsToPickup.size() + " > " + MAX_AUTO_PICKUP_ITEMS + "). Remove some furniture manually and save again."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RoomLayout layout = room.getLayout();
|
Map<Integer, ArrayList<HabboItem>> byOwner = new HashMap<>();
|
||||||
|
for (HabboItem itm : itemsToPickup) {
|
||||||
if (layout instanceof CustomRoomLayout) {
|
if (itm == null) continue;
|
||||||
layout.setDoorX((short) doorX);
|
byOwner.computeIfAbsent(itm.getUserId(), k -> new ArrayList<>()).add(itm);
|
||||||
layout.setDoorY((short) doorY);
|
room.pickUpItem(itm, null);
|
||||||
layout.setDoorDirection(doorRotation);
|
|
||||||
layout.setHeightmap(map);
|
|
||||||
layout.parse();
|
|
||||||
|
|
||||||
if (layout.getDoorTile() == null) {
|
|
||||||
this.client.getHabbo().alert("Error");
|
|
||||||
((CustomRoomLayout) layout).needsUpdate(false);
|
|
||||||
Emulator.getGameEnvironment().getRoomManager().unloadRoom(room);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
((CustomRoomLayout) layout).needsUpdate(true);
|
|
||||||
Emulator.getThreading().run((CustomRoomLayout) layout);
|
|
||||||
} else {
|
|
||||||
layout = Emulator.getGameEnvironment().getRoomManager().insertCustomLayout(room, map, doorX, doorY, doorRotation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (layout != null) {
|
for (Map.Entry<Integer, ArrayList<HabboItem>> entry : byOwner.entrySet()) {
|
||||||
room.setHasCustomLayout(true);
|
Habbo owner = Emulator.getGameEnvironment().getHabboManager().getHabbo(entry.getKey());
|
||||||
room.setNeedsUpdate(true);
|
if (owner == null) continue;
|
||||||
room.setLayout(layout);
|
for (HabboItem itm : entry.getValue()) {
|
||||||
room.setWallSize(wallSize);
|
owner.getClient().sendResponse(new AddHabboItemComposer(itm));
|
||||||
room.setFloorSize(floorSize);
|
|
||||||
room.setWallHeight(wallHeight);
|
|
||||||
room.save();
|
|
||||||
Collection<Habbo> habbos = new ArrayList<>(room.getUserCount());
|
|
||||||
habbos.addAll(room.getHabbos());
|
|
||||||
Emulator.getGameEnvironment().getRoomManager().unloadRoom(room);
|
|
||||||
room = Emulator.getGameEnvironment().getRoomManager().loadRoom(room.getId());
|
|
||||||
ServerMessage message = new ForwardToRoomComposer(room.getId()).compose();
|
|
||||||
for (Habbo habbo : habbos) {
|
|
||||||
habbo.getClient().sendResponse(message);
|
|
||||||
}
|
}
|
||||||
|
owner.getClient().sendResponse(new InventoryRefreshComposer());
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Floorplan auto-pickup: user={} room={} itemCount={} owners={}",
|
||||||
|
this.client.getHabbo().getHabboInfo().getId(), room.getId(), itemsToPickup.size(), byOwner.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
RoomLayout layout = room.getLayout();
|
||||||
|
|
||||||
|
if (layout instanceof CustomRoomLayout) {
|
||||||
|
layout.setDoorX((short) doorX);
|
||||||
|
layout.setDoorY((short) doorY);
|
||||||
|
layout.setDoorDirection(doorRotation);
|
||||||
|
layout.setHeightmap(map);
|
||||||
|
layout.parse();
|
||||||
|
|
||||||
|
if (layout.getDoorTile() == null) {
|
||||||
|
this.client.getHabbo().alert("Error");
|
||||||
|
((CustomRoomLayout) layout).needsUpdate(false);
|
||||||
|
Emulator.getGameEnvironment().getRoomManager().unloadRoom(room);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
((CustomRoomLayout) layout).needsUpdate(true);
|
||||||
|
Emulator.getThreading().run((CustomRoomLayout) layout);
|
||||||
|
} else {
|
||||||
|
layout = Emulator.getGameEnvironment().getRoomManager().insertCustomLayout(room, map, doorX, doorY, doorRotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layout != null) {
|
||||||
|
room.setHasCustomLayout(true);
|
||||||
|
room.setNeedsUpdate(true);
|
||||||
|
room.setLayout(layout);
|
||||||
|
room.setWallSize(wallSize);
|
||||||
|
room.setFloorSize(floorSize);
|
||||||
|
room.setWallHeight(wallHeight);
|
||||||
|
room.save();
|
||||||
|
|
||||||
|
this.client.getHabbo().getHabboStats().lastFloorplanSaveTimestamp = now;
|
||||||
|
LOGGER.info("Floorplan saved: user={} room={} mapLen={} rows={} cols={}",
|
||||||
|
this.client.getHabbo().getHabboInfo().getId(), room.getId(), map.length(), mapRows.length, firstRowSize);
|
||||||
|
|
||||||
|
Collection<Habbo> habbos = new ArrayList<>(room.getUserCount());
|
||||||
|
habbos.addAll(room.getHabbos());
|
||||||
|
Emulator.getGameEnvironment().getRoomManager().unloadRoom(room);
|
||||||
|
room = Emulator.getGameEnvironment().getRoomManager().loadRoom(room.getId());
|
||||||
|
ServerMessage message = new ForwardToRoomComposer(room.getId()).compose();
|
||||||
|
for (Habbo habbo : habbos) {
|
||||||
|
habbo.getClient().sendResponse(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-37
@@ -25,45 +25,55 @@ public class GuildAcceptMembershipEvent extends MessageHandler {
|
|||||||
int userId = this.packet.readInt();
|
int userId = this.packet.readInt();
|
||||||
|
|
||||||
Guild guild = Emulator.getGameEnvironment().getGuildManager().getGuild(guildId);
|
Guild guild = Emulator.getGameEnvironment().getGuildManager().getGuild(guildId);
|
||||||
|
|
||||||
|
if (guild == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GuildMember actorMember = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guild, this.client.getHabbo());
|
||||||
|
boolean canAccept = guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId()
|
||||||
|
|| this.client.getHabbo().hasPermission(Permission.ACC_GUILD_ADMIN)
|
||||||
|
|| (actorMember != null && (actorMember.getRank().equals(GuildRank.ADMIN) || actorMember.getRank().equals(GuildRank.OWNER)));
|
||||||
|
|
||||||
|
if (!canAccept) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GuildMember targetMember = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, userId);
|
||||||
|
|
||||||
|
if (targetMember == null) {
|
||||||
|
this.client.sendResponse(new GuildAcceptMemberErrorComposer(guild.getId(), GuildAcceptMemberErrorComposer.NO_LONGER_MEMBER));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetMember.getRank().type != GuildRank.REQUESTED.type) {
|
||||||
|
this.client.sendResponse(new GuildAcceptMemberErrorComposer(guild.getId(), GuildAcceptMemberErrorComposer.ALREADY_ACCEPTED));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
|
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
|
||||||
|
|
||||||
if (guild != null) {
|
GuildAcceptedMembershipEvent event = new GuildAcceptedMembershipEvent(guild, userId, habbo);
|
||||||
GuildMember groupMember = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guild, this.client.getHabbo());
|
Emulator.getPluginManager().fireEvent(event);
|
||||||
if (guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId()
|
|
||||||
|| this.client.getHabbo().hasPermission(Permission.ACC_GUILD_ADMIN)
|
if (event.isCancelled()) {
|
||||||
|| (groupMember != null && (groupMember.getRank().equals(GuildRank.ADMIN) || groupMember.getRank().equals(GuildRank.OWNER)))) {
|
return;
|
||||||
if (habbo != null) {
|
}
|
||||||
if (habbo.getHabboStats().hasGuild(guild.getId())) {
|
|
||||||
this.client.sendResponse(new GuildAcceptMemberErrorComposer(guild.getId(), GuildAcceptMemberErrorComposer.ALREADY_ACCEPTED));
|
if (habbo != null) {
|
||||||
return;
|
habbo.getHabboStats().addGuild(guild.getId());
|
||||||
} else {
|
}
|
||||||
//Check the user has requested
|
|
||||||
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guild, habbo);
|
Emulator.getGameEnvironment().getGuildManager().joinGuild(guild, this.client, userId, true);
|
||||||
if (member == null || member.getRank().type != GuildRank.REQUESTED.type) {
|
guild.decreaseRequestCount();
|
||||||
this.client.sendResponse(new GuildAcceptMemberErrorComposer(guild.getId(), GuildAcceptMemberErrorComposer.NO_LONGER_MEMBER));
|
guild.increaseMemberCount();
|
||||||
return;
|
this.client.sendResponse(new GuildRefreshMembersListComposer(guild));
|
||||||
} else {
|
|
||||||
GuildAcceptedMembershipEvent event = new GuildAcceptedMembershipEvent(guild, userId, habbo);
|
if (habbo != null) {
|
||||||
Emulator.getPluginManager().fireEvent(event);
|
Room room = habbo.getHabboInfo().getCurrentRoom();
|
||||||
if (!event.isCancelled()) {
|
if (room != null && room.getGuildId() == guildId) {
|
||||||
habbo.getHabboStats().addGuild(guild.getId());
|
habbo.getClient().sendResponse(new GuildInfoComposer(guild, habbo.getClient(), false, Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, userId)));
|
||||||
Emulator.getGameEnvironment().getGuildManager().joinGuild(guild, this.client, habbo.getHabboInfo().getId(), true);
|
room.refreshRightsForHabbo(habbo);
|
||||||
guild.decreaseRequestCount();
|
|
||||||
guild.increaseMemberCount();
|
|
||||||
this.client.sendResponse(new GuildRefreshMembersListComposer(guild));
|
|
||||||
Room room = habbo.getHabboInfo().getCurrentRoom();
|
|
||||||
if (room != null) {
|
|
||||||
if (room.getGuildId() == guildId) {
|
|
||||||
habbo.getClient().sendResponse(new GuildInfoComposer(guild, habbo.getClient(), false, Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, userId)));
|
|
||||||
room.refreshRightsForHabbo(habbo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Emulator.getGameEnvironment().getGuildManager().joinGuild(guild, this.client, userId, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -29,6 +29,11 @@ public class GuildDeclineMembershipEvent extends MessageHandler {
|
|||||||
if (guild != null) {
|
if (guild != null) {
|
||||||
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guild, this.client.getHabbo());
|
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guild, this.client.getHabbo());
|
||||||
if (userId == this.client.getHabbo().getHabboInfo().getId() || guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || (member != null && (member.getRank().equals(GuildRank.ADMIN) || member.getRank().equals(GuildRank.OWNER))) || this.client.getHabbo().hasPermission(Permission.ACC_GUILD_ADMIN)) {
|
if (userId == this.client.getHabbo().getHabboInfo().getId() || guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || (member != null && (member.getRank().equals(GuildRank.ADMIN) || member.getRank().equals(GuildRank.OWNER))) || this.client.getHabbo().hasPermission(Permission.ACC_GUILD_ADMIN)) {
|
||||||
|
GuildMember target = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, userId);
|
||||||
|
if (target == null || target.getRank().type != GuildRank.REQUESTED.type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
guild.decreaseRequestCount();
|
guild.decreaseRequestCount();
|
||||||
Emulator.getGameEnvironment().getGuildManager().removeMember(guild, userId);
|
Emulator.getGameEnvironment().getGuildManager().removeMember(guild, userId);
|
||||||
this.client.sendResponse(new GuildMembersComposer(guild, Emulator.getGameEnvironment().getGuildManager().getGuildMembers(guild, 0, 0, ""), this.client.getHabbo(), 0, 0, "", true, Emulator.getGameEnvironment().getGuildManager().getGuildMembersCount(guild, 0, 0, "")));
|
this.client.sendResponse(new GuildMembersComposer(guild, Emulator.getGameEnvironment().getGuildManager().getGuildMembers(guild, 0, 0, ""), this.client.getHabbo(), 0, 0, "", true, Emulator.getGameEnvironment().getGuildManager().getGuildMembersCount(guild, 0, 0, "")));
|
||||||
|
|||||||
+74
-69
@@ -30,20 +30,69 @@ public class RequestGuildBuyEvent extends MessageHandler {
|
|||||||
final String name = Emulator.getGameEnvironment().getWordFilter().filter(this.packet.readString(), this.client.getHabbo());
|
final String name = Emulator.getGameEnvironment().getWordFilter().filter(this.packet.readString(), this.client.getHabbo());
|
||||||
final String description = Emulator.getGameEnvironment().getWordFilter().filter(this.packet.readString(), this.client.getHabbo());
|
final String description = Emulator.getGameEnvironment().getWordFilter().filter(this.packet.readString(), this.client.getHabbo());
|
||||||
|
|
||||||
if(name.length() > 29){
|
if (name.length() == 0 || name.length() > 29) {
|
||||||
this.client.sendResponse(new GuildEditFailComposer(GuildEditFailComposer.INVALID_GUILD_NAME));
|
this.client.sendResponse(new GuildEditFailComposer(GuildEditFailComposer.INVALID_GUILD_NAME));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(description.length() > 254){
|
if (description.length() > 254) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (Emulator.getConfig().getBoolean("catalog.guild.hc_required", true) && !this.client.getHabbo().getHabboStats().hasActiveClub()) {
|
if (Emulator.getConfig().getBoolean("catalog.guild.hc_required", true) && !this.client.getHabbo().getHabboStats().hasActiveClub()) {
|
||||||
this.client.sendResponse(new GuildEditFailComposer(GuildEditFailComposer.HC_REQUIRED));
|
this.client.sendResponse(new GuildEditFailComposer(GuildEditFailComposer.HC_REQUIRED));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int roomId = this.packet.readInt();
|
||||||
|
|
||||||
|
Room r = Emulator.getGameEnvironment().getRoomManager().getRoom(roomId);
|
||||||
|
|
||||||
|
if (r == null) {
|
||||||
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.hasGuild() || r.getGuildId() != 0) {
|
||||||
|
this.client.sendResponse(new GuildEditFailComposer(GuildEditFailComposer.ROOM_ALREADY_IN_USE));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.getOwnerId() != this.client.getHabbo().getHabboInfo().getId()) {
|
||||||
|
String message = Emulator.getTexts().getValue("scripter.warning.guild.buy.owner").replace("%username%", this.client.getHabbo().getHabboInfo().getUsername()).replace("%roomname%", r.getName().replace("%owner%", r.getOwnerName()));
|
||||||
|
ScripterManager.scripterDetected(this.client, message);
|
||||||
|
LOGGER.info(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int colorOne = this.packet.readInt();
|
||||||
|
int colorTwo = this.packet.readInt();
|
||||||
|
|
||||||
|
int count = this.packet.readInt();
|
||||||
|
|
||||||
|
StringBuilder badge = new StringBuilder();
|
||||||
|
|
||||||
|
byte base = 1;
|
||||||
|
|
||||||
|
while (base < count) {
|
||||||
|
int id = this.packet.readInt();
|
||||||
|
int color = this.packet.readInt();
|
||||||
|
int pos = this.packet.readInt();
|
||||||
|
|
||||||
|
if (base == 1) {
|
||||||
|
badge.append("b");
|
||||||
|
} else {
|
||||||
|
badge.append("s");
|
||||||
|
}
|
||||||
|
|
||||||
|
badge.append(id < 100 ? "0" : "").append(id < 10 ? "0" : "").append(id).append(color < 10 ? "0" : "").append(color).append(pos);
|
||||||
|
|
||||||
|
base += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only charge the player once every step has been validated. Previously the
|
||||||
|
// credits were deducted before the room was checked, so a purchase that
|
||||||
|
// failed afterwards (missing room, room already used by a guild, not the
|
||||||
|
// owner) still took the credits without ever creating the group.
|
||||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_INFINITE_CREDITS)) {
|
if (!this.client.getHabbo().hasPermission(Permission.ACC_INFINITE_CREDITS)) {
|
||||||
int guildPrice = Emulator.getConfig().getInt("catalog.guild.price");
|
int guildPrice = Emulator.getConfig().getInt("catalog.guild.price");
|
||||||
if (this.client.getHabbo().getHabboInfo().getCredits() >= guildPrice) {
|
if (this.client.getHabbo().getHabboInfo().getCredits() >= guildPrice) {
|
||||||
@@ -54,78 +103,34 @@ public class RequestGuildBuyEvent extends MessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int roomId = this.packet.readInt();
|
Guild guild = Emulator.getGameEnvironment().getGuildManager().createGuild(this.client.getHabbo(), roomId, r.getName(), name, description, badge.toString(), colorOne, colorTwo);
|
||||||
|
|
||||||
Room r = Emulator.getGameEnvironment().getRoomManager().getRoom(roomId);
|
r.setGuild(guild.getId());
|
||||||
|
r.removeAllRights();
|
||||||
|
r.setNeedsUpdate(true);
|
||||||
|
|
||||||
if (r != null) {
|
Emulator.getGameEnvironment().getGuildManager().addGuild(guild);
|
||||||
if (r.hasGuild()) {
|
|
||||||
this.client.sendResponse(new GuildEditFailComposer(GuildEditFailComposer.ROOM_ALREADY_IN_USE));
|
if (Emulator.getConfig().getBoolean("imager.internal.enabled")) {
|
||||||
return;
|
Emulator.getBadgeImager().generate(guild);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client.sendResponse(new PurchaseOKComposer());
|
||||||
|
this.client.sendResponse(new GuildBoughtComposer(guild));
|
||||||
|
r.refreshGuild(guild);
|
||||||
|
|
||||||
|
for (Habbo habbo : r.getHabbos()) {
|
||||||
|
if (habbo.getClient() == null) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r.getOwnerId() == this.client.getHabbo().getHabboInfo().getId()) {
|
habbo.getClient().sendResponse(new GuildInfoComposer(guild, habbo.getClient(), false, null));
|
||||||
if (r.getGuildId() == 0) {
|
|
||||||
int colorOne = this.packet.readInt();
|
|
||||||
int colorTwo = this.packet.readInt();
|
|
||||||
|
|
||||||
int count = this.packet.readInt();
|
if (habbo.getHabboInfo().getId() != this.client.getHabbo().getHabboInfo().getId()) {
|
||||||
|
habbo.getClient().sendResponse(new RoomDataComposer(r, habbo, true, false));
|
||||||
StringBuilder badge = new StringBuilder();
|
|
||||||
|
|
||||||
byte base = 1;
|
|
||||||
|
|
||||||
while (base < count) {
|
|
||||||
int id = this.packet.readInt();
|
|
||||||
int color = this.packet.readInt();
|
|
||||||
int pos = this.packet.readInt();
|
|
||||||
|
|
||||||
if (base == 1) {
|
|
||||||
badge.append("b");
|
|
||||||
} else {
|
|
||||||
badge.append("s");
|
|
||||||
}
|
|
||||||
|
|
||||||
badge.append(id < 100 ? "0" : "").append(id < 10 ? "0" : "").append(id).append(color < 10 ? "0" : "").append(color).append(pos);
|
|
||||||
|
|
||||||
base += 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
Guild guild = Emulator.getGameEnvironment().getGuildManager().createGuild(this.client.getHabbo(), roomId, r.getName(), name, description, badge.toString(), colorOne, colorTwo);
|
|
||||||
|
|
||||||
r.setGuild(guild.getId());
|
|
||||||
r.removeAllRights();
|
|
||||||
r.setNeedsUpdate(true);
|
|
||||||
|
|
||||||
Emulator.getGameEnvironment().getGuildManager().addGuild(guild);
|
|
||||||
|
|
||||||
if (Emulator.getConfig().getBoolean("imager.internal.enabled")) {
|
|
||||||
Emulator.getBadgeImager().generate(guild);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.client.sendResponse(new PurchaseOKComposer());
|
|
||||||
this.client.sendResponse(new GuildBoughtComposer(guild));
|
|
||||||
r.refreshGuild(guild);
|
|
||||||
|
|
||||||
for (Habbo habbo : r.getHabbos()) {
|
|
||||||
if (habbo.getClient() == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
habbo.getClient().sendResponse(new GuildInfoComposer(guild, habbo.getClient(), false, null));
|
|
||||||
|
|
||||||
if (habbo.getHabboInfo().getId() != this.client.getHabbo().getHabboInfo().getId()) {
|
|
||||||
habbo.getClient().sendResponse(new RoomDataComposer(r, habbo, true, false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Emulator.getPluginManager().fireEvent(new GuildPurchasedEvent(guild, this.client.getHabbo()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String message = Emulator.getTexts().getValue("scripter.warning.guild.buy.owner").replace("%username%", this.client.getHabbo().getHabboInfo().getUsername()).replace("%roomname%", r.getName().replace("%owner%", r.getOwnerName()));
|
|
||||||
ScripterManager.scripterDetected(this.client, message);
|
|
||||||
LOGGER.info(message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Emulator.getPluginManager().fireEvent(new GuildPurchasedEvent(guild, this.client.getHabbo()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-1
@@ -2,7 +2,11 @@ package com.eu.habbo.messages.incoming.guilds.forums;
|
|||||||
|
|
||||||
import com.eu.habbo.Emulator;
|
import com.eu.habbo.Emulator;
|
||||||
import com.eu.habbo.habbohotel.guilds.Guild;
|
import com.eu.habbo.habbohotel.guilds.Guild;
|
||||||
|
import com.eu.habbo.habbohotel.guilds.GuildMember;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
|
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
|
||||||
|
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys;
|
||||||
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumDataComposer;
|
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumDataComposer;
|
||||||
|
|
||||||
public class GuildForumDataEvent extends MessageHandler {
|
public class GuildForumDataEvent extends MessageHandler {
|
||||||
@@ -20,10 +24,18 @@ public class GuildForumDataEvent extends MessageHandler {
|
|||||||
if (guild == null) return;
|
if (guild == null) return;
|
||||||
if (!guild.hasForum()) return;
|
if (!guild.hasForum()) return;
|
||||||
|
|
||||||
|
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, this.client.getHabbo().getHabboInfo().getId());
|
||||||
|
boolean staff = this.client.getHabbo().hasPermission(Permission.ACC_MODTOOL_TICKET_Q);
|
||||||
|
|
||||||
|
if (!guild.canHabboReadForum(this.client.getHabbo().getHabboInfo().getId(), member, staff)) {
|
||||||
|
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FORUMS_ACCESS_DENIED.key).compose());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.client.sendResponse(new GuildForumDataComposer(guild, this.client.getHabbo()));
|
this.client.sendResponse(new GuildForumDataComposer(guild, this.client.getHabbo()));
|
||||||
|
|
||||||
if (!Emulator.getGameEnvironment().getGuildManager().hasViewedForum(this.client.getHabbo().getHabboInfo().getId(), guildId)) {
|
if (!Emulator.getGameEnvironment().getGuildManager().hasViewedForum(this.client.getHabbo().getHabboInfo().getId(), guildId)) {
|
||||||
Emulator.getGameEnvironment().getGuildManager().addView(this.client.getHabbo().getHabboInfo().getId(), guildId);
|
Emulator.getGameEnvironment().getGuildManager().addView(this.client.getHabbo().getHabboInfo().getId(), guildId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-1
@@ -2,7 +2,11 @@ package com.eu.habbo.messages.incoming.guilds.forums;
|
|||||||
|
|
||||||
import com.eu.habbo.Emulator;
|
import com.eu.habbo.Emulator;
|
||||||
import com.eu.habbo.habbohotel.guilds.Guild;
|
import com.eu.habbo.habbohotel.guilds.Guild;
|
||||||
|
import com.eu.habbo.habbohotel.guilds.GuildMember;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
|
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
|
||||||
|
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys;
|
||||||
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumDataComposer;
|
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumDataComposer;
|
||||||
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumThreadsComposer;
|
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumThreadsComposer;
|
||||||
import com.eu.habbo.messages.outgoing.handshake.ConnectionErrorComposer;
|
import com.eu.habbo.messages.outgoing.handshake.ConnectionErrorComposer;
|
||||||
@@ -24,8 +28,15 @@ public class GuildForumThreadsEvent extends MessageHandler {
|
|||||||
this.client.sendResponse(new ConnectionErrorComposer(404));
|
this.client.sendResponse(new ConnectionErrorComposer(404));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, this.client.getHabbo().getHabboInfo().getId());
|
||||||
|
boolean staff = this.client.getHabbo().hasPermission(Permission.ACC_MODTOOL_TICKET_Q);
|
||||||
|
|
||||||
|
if (!guild.canHabboReadForum(this.client.getHabbo().getHabboInfo().getId(), member, staff)) {
|
||||||
|
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FORUMS_ACCESS_DENIED.key).compose());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.client.sendResponse(new GuildForumDataComposer(guild, this.client.getHabbo()));
|
this.client.sendResponse(new GuildForumDataComposer(guild, this.client.getHabbo()));
|
||||||
this.client.sendResponse(new GuildForumThreadsComposer(guild, index));
|
this.client.sendResponse(new GuildForumThreadsComposer(guild, index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -38,7 +38,6 @@ public class GuildForumThreadsMessagesEvent extends MessageHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify thread belongs to the requested guild
|
|
||||||
if (thread.getGuildId() != guildId) {
|
if (thread.getGuildId() != guildId) {
|
||||||
this.client.sendResponse(new ConnectionErrorComposer(403));
|
this.client.sendResponse(new ConnectionErrorComposer(403));
|
||||||
return;
|
return;
|
||||||
@@ -47,6 +46,11 @@ public class GuildForumThreadsMessagesEvent extends MessageHandler {
|
|||||||
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, this.client.getHabbo().getHabboInfo().getId());
|
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, this.client.getHabbo().getHabboInfo().getId());
|
||||||
boolean isGuildAdministrator = (guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || (member != null && member.getRank().equals(GuildRank.ADMIN)));
|
boolean isGuildAdministrator = (guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || (member != null && member.getRank().equals(GuildRank.ADMIN)));
|
||||||
|
|
||||||
|
if (!guild.canHabboReadForum(this.client.getHabbo().getHabboInfo().getId(), member, hasStaffPermissions)) {
|
||||||
|
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FORUMS_ACCESS_DENIED.key).compose());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (thread.getState() != ForumThreadState.HIDDEN_BY_GUILD_ADMIN || hasStaffPermissions || isGuildAdministrator) {
|
if (thread.getState() != ForumThreadState.HIDDEN_BY_GUILD_ADMIN || hasStaffPermissions || isGuildAdministrator) {
|
||||||
this.client.sendResponse(new GuildForumCommentsComposer(guildId, threadId, index, thread.getComments(limit, index)));
|
this.client.sendResponse(new GuildForumCommentsComposer(guildId, threadId, index, thread.getComments(limit, index)));
|
||||||
this.client.sendResponse(new GuildForumDataComposer(guild, this.client.getHabbo()));
|
this.client.sendResponse(new GuildForumDataComposer(guild, this.client.getHabbo()));
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.eu.habbo.messages.incoming.handshake;
|
package com.eu.habbo.messages.incoming.handshake;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
import com.eu.habbo.messages.NoAuthMessage;
|
import com.eu.habbo.messages.NoAuthMessage;
|
||||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -24,6 +25,15 @@ public class MachineIDEvent extends MessageHandler {
|
|||||||
|
|
||||||
this.client.setMachineId(storedMachineId);
|
this.client.setMachineId(storedMachineId);
|
||||||
|
|
||||||
|
// Persist the machine fingerprint onto the user so machine/super bans can
|
||||||
|
// target it (createOfflineUserBan copies users.machine_id). The Nitro client
|
||||||
|
// sends this UniqueID packet right after the SSO ticket, so the Habbo is
|
||||||
|
// normally already loaded by the time we get here.
|
||||||
|
if (!storedMachineId.isEmpty() && this.client.getHabbo() != null && this.client.getHabbo().getHabboInfo() != null) {
|
||||||
|
this.client.getHabbo().getHabboInfo().setMachineID(storedMachineId);
|
||||||
|
Emulator.getThreading().run(this.client.getHabbo());
|
||||||
|
}
|
||||||
|
|
||||||
LOGGER.debug("Setting client MachineId to {}", storedMachineId);
|
LOGGER.debug("Setting client MachineId to {}", storedMachineId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+10
-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);
|
||||||
@@ -168,6 +161,12 @@ public class SecureLoginEvent extends MessageHandler {
|
|||||||
throw new NullPointerException(habbo.getHabboInfo().getUsername() + " has a NON EXISTING RANK!");
|
throw new NullPointerException(habbo.getHabboInfo().getUsername() + " has a NON EXISTING RANK!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the machine fingerprint already arrived (UniqueID before login),
|
||||||
|
// persist it so machine/super bans can target this user.
|
||||||
|
if (this.client.getMachineId() != null && !this.client.getMachineId().isEmpty()) {
|
||||||
|
this.client.getHabbo().getHabboInfo().setMachineID(this.client.getMachineId());
|
||||||
|
}
|
||||||
|
|
||||||
Emulator.getThreading().run(habbo);
|
Emulator.getThreading().run(habbo);
|
||||||
Emulator.getGameEnvironment().getHabboManager().addHabbo(habbo);
|
Emulator.getGameEnvironment().getHabboManager().addHabbo(habbo);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
package com.eu.habbo.messages.incoming.housekeeping;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.modtool.ModToolBan;
|
||||||
|
import com.eu.habbo.habbohotel.modtool.ModToolBanType;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
|
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply an arbitrary-duration account ban. Duration is taken in hours
|
||||||
|
* from the wire and converted to seconds for ModToolManager.ban —
|
||||||
|
* unlike ModToolSanctionBanEvent which only accepts the four fixed
|
||||||
|
* Habbo-protocol banType buckets.
|
||||||
|
*/
|
||||||
|
public class HousekeepingBanUserEvent extends MessageHandler {
|
||||||
|
private static final String ACTION_KEY = "user.ban";
|
||||||
|
private static final int SECONDS_IN_HOUR = 3600;
|
||||||
|
// 100-year ceiling, matches ModToolSanctionBanEvent's permanent ban.
|
||||||
|
private static final int MAX_DURATION_SECONDS = 100 * 365 * 24 * 3600;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRatelimit() {
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle() throws Exception {
|
||||||
|
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int userId = this.packet.readInt();
|
||||||
|
String reason = this.packet.readString();
|
||||||
|
int hours = this.packet.readInt();
|
||||||
|
|
||||||
|
if (userId <= 0 || hours <= 0) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long durationLong = (long) hours * SECONDS_IN_HOUR;
|
||||||
|
int duration = durationLong > MAX_DURATION_SECONDS ? MAX_DURATION_SECONDS : (int) durationLong;
|
||||||
|
|
||||||
|
List<ModToolBan> bans = Emulator.getGameEnvironment().getModToolManager()
|
||||||
|
.ban(userId, this.client.getHabbo(), reason != null ? reason : "", duration, ModToolBanType.ACCOUNT, 0);
|
||||||
|
|
||||||
|
if (bans == null || bans.isEmpty()) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.ban_failed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModToolBan doesn't expose the `bans` table autoinc id on the
|
||||||
|
// object, so we return the target user id as the actionId — it's
|
||||||
|
// the only stable handle the client can use until a dedicated
|
||||||
|
// housekeeping_log row id supersedes it.
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
package com.eu.habbo.messages.incoming.housekeeping;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
|
import com.eu.habbo.habbohotel.rooms.Room;
|
||||||
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
|
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permanently delete a room. Mirrors the minimum-viable subset of
|
||||||
|
* RequestDeleteRoomEvent: eject all users from the live room, dispose
|
||||||
|
* + uncache, then DELETE FROM rooms. Pets/guild/custom-layout cleanup
|
||||||
|
* is intentionally skipped on this slice — leftover rows in those
|
||||||
|
* tables become orphans but don't crash the emulator; a follow-up
|
||||||
|
* pass can cascade once we have a HK audit-log row to attach the
|
||||||
|
* orphan-cleanup to.
|
||||||
|
*/
|
||||||
|
public class HousekeepingDeleteRoomEvent extends MessageHandler {
|
||||||
|
private static final String ACTION_KEY = "room.delete";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRatelimit() {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle() throws Exception {
|
||||||
|
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int roomId = this.packet.readInt();
|
||||||
|
|
||||||
|
if (roomId <= 0) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId, false);
|
||||||
|
|
||||||
|
if (room != null) {
|
||||||
|
room.ejectAll();
|
||||||
|
room.preventUnloading = false;
|
||||||
|
room.dispose();
|
||||||
|
Emulator.getGameEnvironment().getRoomManager().uncacheRoom(room);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement("DELETE FROM rooms WHERE id = ? LIMIT 1")) {
|
||||||
|
statement.setInt(1, roomId);
|
||||||
|
int rows = statement.executeUpdate();
|
||||||
|
|
||||||
|
if (rows == 0) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.room_not_found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.db_failed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, roomId, ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
package com.eu.habbo.messages.incoming.housekeeping;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
|
import com.eu.habbo.habbohotel.rooms.Room;
|
||||||
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
|
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingRoomDetailComposer;
|
||||||
|
|
||||||
|
public class HousekeepingFindRoomByIdEvent extends MessageHandler {
|
||||||
|
@Override
|
||||||
|
public int getRatelimit() {
|
||||||
|
return 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle() throws Exception {
|
||||||
|
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int roomId = this.packet.readInt();
|
||||||
|
|
||||||
|
if (roomId <= 0) {
|
||||||
|
this.client.sendResponse(new HousekeepingRoomDetailComposer(null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadRoom covers both the in-memory cache (already-loaded rooms)
|
||||||
|
// and the offline path (SELECT * FROM rooms WHERE id=?). Pass
|
||||||
|
// false for loadData so we don't pull furni/bots/pets just to
|
||||||
|
// render an HK panel summary — getOwnerName / getUserCount work
|
||||||
|
// on the pre-loaded skeleton.
|
||||||
|
Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId, false);
|
||||||
|
|
||||||
|
this.client.sendResponse(new HousekeepingRoomDetailComposer(room));
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package com.eu.habbo.messages.incoming.housekeeping;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
|
import com.eu.habbo.habbohotel.users.HabboInfo;
|
||||||
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
|
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingUserDetailComposer;
|
||||||
|
|
||||||
|
public class HousekeepingFindUserByIdEvent extends MessageHandler {
|
||||||
|
@Override
|
||||||
|
public int getRatelimit() {
|
||||||
|
return 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle() throws Exception {
|
||||||
|
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int userId = this.packet.readInt();
|
||||||
|
|
||||||
|
if (userId <= 0) {
|
||||||
|
this.client.sendResponse(new HousekeepingUserDetailComposer(null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HabboManager.getHabboInfo(int) returns the in-memory record for
|
||||||
|
// online users and falls through to the offline SQL lookup
|
||||||
|
// otherwise, so a single call covers both branches.
|
||||||
|
HabboInfo info = Emulator.getGameEnvironment().getHabboManager().getHabboInfo(userId);
|
||||||
|
|
||||||
|
this.client.sendResponse(new HousekeepingUserDetailComposer(info));
|
||||||
|
}
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package com.eu.habbo.messages.incoming.housekeeping;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
|
import com.eu.habbo.habbohotel.users.Habbo;
|
||||||
|
import com.eu.habbo.habbohotel.users.HabboInfo;
|
||||||
|
import com.eu.habbo.habbohotel.users.HabboManager;
|
||||||
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
|
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingUserDetailComposer;
|
||||||
|
|
||||||
|
public class HousekeepingFindUserByNameEvent extends MessageHandler {
|
||||||
|
@Override
|
||||||
|
public int getRatelimit() {
|
||||||
|
return 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle() throws Exception {
|
||||||
|
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String username = this.packet.readString();
|
||||||
|
|
||||||
|
if (username == null || username.isEmpty()) {
|
||||||
|
this.client.sendResponse(new HousekeepingUserDetailComposer(null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Habbo online = Emulator.getGameEnvironment().getHabboManager().getHabbo(username);
|
||||||
|
HabboInfo info = online != null ? online.getHabboInfo() : HabboManager.getOfflineHabboInfo(username);
|
||||||
|
|
||||||
|
this.client.sendResponse(new HousekeepingUserDetailComposer(info));
|
||||||
|
}
|
||||||
|
}
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
package com.eu.habbo.messages.incoming.housekeeping;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
|
import com.eu.habbo.habbohotel.users.Habbo;
|
||||||
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
|
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force-close the session of an online user. Unlike kick (which only
|
||||||
|
* removes them from the current room), this drops their socket. Equivalent
|
||||||
|
* to /disconnect in command form but issued through the HK panel.
|
||||||
|
*/
|
||||||
|
public class HousekeepingForceDisconnectUserEvent extends MessageHandler {
|
||||||
|
private static final String ACTION_KEY = "user.disconnect";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRatelimit() {
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle() throws Exception {
|
||||||
|
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int userId = this.packet.readInt();
|
||||||
|
String reason = this.packet.readString();
|
||||||
|
|
||||||
|
if (userId <= 0) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Habbo target = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
|
||||||
|
|
||||||
|
if (target == null) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.user_offline"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reason != null && !reason.isEmpty()) {
|
||||||
|
target.alert(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACK first so the action result lands before the target's socket
|
||||||
|
// closes (otherwise an alerted user on the same emulator thread may
|
||||||
|
// already be torn down when we try to write).
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
|
||||||
|
|
||||||
|
target.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
package com.eu.habbo.messages.incoming.housekeeping;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
|
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingDashboardComposer;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public class HousekeepingGetDashboardEvent extends MessageHandler {
|
||||||
|
@Override
|
||||||
|
public int getRatelimit() {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle() throws Exception {
|
||||||
|
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int onlineUsers = Emulator.getGameEnvironment().getHabboManager().getOnlineHabbos().size();
|
||||||
|
int activeRooms = 0;
|
||||||
|
int totalUsers = 0;
|
||||||
|
int totalRooms = 0;
|
||||||
|
int pendingTickets = 0;
|
||||||
|
int sanctionsLast24h = 0;
|
||||||
|
int now = Emulator.getIntUnixTimestamp();
|
||||||
|
|
||||||
|
// activeRooms = loaded rooms with at least one user
|
||||||
|
try {
|
||||||
|
for (var room : Emulator.getGameEnvironment().getRoomManager().getActiveRooms()) {
|
||||||
|
if (room != null && room.getUserCount() > 0) activeRooms++;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// fall through with 0
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) {
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement("SELECT COUNT(*) FROM users");
|
||||||
|
ResultSet rs = statement.executeQuery()) {
|
||||||
|
if (rs.next()) totalUsers = rs.getInt(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement("SELECT COUNT(*) FROM rooms");
|
||||||
|
ResultSet rs = statement.executeQuery()) {
|
||||||
|
if (rs.next()) totalRooms = rs.getInt(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement("SELECT COUNT(*) FROM support_tickets WHERE state = 0");
|
||||||
|
ResultSet rs = statement.executeQuery()) {
|
||||||
|
if (rs.next()) pendingTickets = rs.getInt(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement("SELECT COUNT(*) FROM bans WHERE timestamp > ?")) {
|
||||||
|
statement.setInt(1, now - (24 * 3600));
|
||||||
|
try (ResultSet rs = statement.executeQuery()) {
|
||||||
|
if (rs.next()) sanctionsLast24h = rs.getInt(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException ignored) {
|
||||||
|
// Surface 0s rather than failing the whole dashboard on a missing
|
||||||
|
// optional table — the HK panel can render partial data.
|
||||||
|
}
|
||||||
|
|
||||||
|
int uptime = (int) ((System.currentTimeMillis() - HOUSEKEEPING_BOOT_MILLIS) / 1000);
|
||||||
|
String version = "Arcturus-Morningstar-Extended";
|
||||||
|
|
||||||
|
this.client.sendResponse(new HousekeepingDashboardComposer(
|
||||||
|
onlineUsers,
|
||||||
|
totalUsers,
|
||||||
|
activeRooms,
|
||||||
|
totalRooms,
|
||||||
|
onlineUsers, // peakOnlineToday — not tracked, use current as best-effort
|
||||||
|
onlineUsers, // peakOnlineAllTime — same
|
||||||
|
pendingTickets,
|
||||||
|
sanctionsLast24h,
|
||||||
|
Math.max(uptime, 0),
|
||||||
|
version
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approximate uptime — captured at class-load time rather than emu startup
|
||||||
|
// (Emulator.java doesn't expose a public startup timestamp). For HK panel
|
||||||
|
// headline metrics this is close enough; if tighter accuracy is needed
|
||||||
|
// later, plumb Emulator.startup through and read it here.
|
||||||
|
private static final long HOUSEKEEPING_BOOT_MILLIS = System.currentTimeMillis();
|
||||||
|
}
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
package com.eu.habbo.messages.incoming.housekeeping;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
|
import com.eu.habbo.habbohotel.users.Habbo;
|
||||||
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
|
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public class HousekeepingGiveCreditsEvent extends MessageHandler {
|
||||||
|
private static final String ACTION_KEY = "user.give_credits";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRatelimit() {
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle() throws Exception {
|
||||||
|
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int userId = this.packet.readInt();
|
||||||
|
int amount = this.packet.readInt();
|
||||||
|
|
||||||
|
if (userId <= 0 || amount == 0) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Habbo online = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
|
||||||
|
|
||||||
|
if (online != null) {
|
||||||
|
// giveCredits already pushes UserCreditsComposer and persists via the
|
||||||
|
// standard HabboInfo write path; nothing extra needed for the online branch.
|
||||||
|
online.giveCredits(amount);
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement("UPDATE users SET credits = credits + ? WHERE id = ? LIMIT 1")) {
|
||||||
|
statement.setInt(1, amount);
|
||||||
|
statement.setInt(2, userId);
|
||||||
|
int rows = statement.executeUpdate();
|
||||||
|
|
||||||
|
if (rows == 0) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.user_not_found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.db_failed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
package com.eu.habbo.messages.incoming.housekeeping;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
|
import com.eu.habbo.habbohotel.users.Habbo;
|
||||||
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
|
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic non-credits currency grant. Wire field `currencyType`:
|
||||||
|
* 0 => duckets / pixels, 5 => diamonds, 101 => seasonal-primary.
|
||||||
|
* Online users go through Habbo.givePoints / givePixels which dispatches
|
||||||
|
* a UserCurrencyComposer; offline goes straight to `users_currency`.
|
||||||
|
*/
|
||||||
|
public class HousekeepingGiveCurrencyEvent extends MessageHandler {
|
||||||
|
private static final int CURRENCY_DUCKETS = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRatelimit() {
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle() throws Exception {
|
||||||
|
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int userId = this.packet.readInt();
|
||||||
|
int currencyType = this.packet.readInt();
|
||||||
|
int amount = this.packet.readInt();
|
||||||
|
|
||||||
|
String actionKey = "user.give_currency_" + currencyType;
|
||||||
|
|
||||||
|
if (userId <= 0 || amount == 0) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, false, 0, "housekeeping.error.invalid_input"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Habbo online = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
|
||||||
|
|
||||||
|
if (online != null) {
|
||||||
|
// givePixels writes users_currency type=0 and ships UserCurrency;
|
||||||
|
// givePoints(type, amount) is the generalised path for everything else.
|
||||||
|
if (currencyType == CURRENCY_DUCKETS) {
|
||||||
|
online.givePixels(amount);
|
||||||
|
} else {
|
||||||
|
online.givePoints(currencyType, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, true, userId, ""));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement(
|
||||||
|
"INSERT INTO users_currency (user_id, type, amount) VALUES (?, ?, ?) " +
|
||||||
|
"ON DUPLICATE KEY UPDATE amount = amount + VALUES(amount)")) {
|
||||||
|
statement.setInt(1, userId);
|
||||||
|
statement.setInt(2, currencyType);
|
||||||
|
statement.setInt(3, amount);
|
||||||
|
statement.executeUpdate();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, false, 0, "housekeeping.error.db_failed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, true, userId, ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
package com.eu.habbo.messages.incoming.housekeeping;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
|
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grant a furni item (by items_base id) `quantity` times. Each row in
|
||||||
|
* the `items` table is one furni unit; quantity > 1 just batches the
|
||||||
|
* insert. The online user's HabboInventory isn't proactively refreshed
|
||||||
|
* — they'll see the new items next time they open the hand inventory
|
||||||
|
* (or after a relog).
|
||||||
|
*/
|
||||||
|
public class HousekeepingGrantItemEvent extends MessageHandler {
|
||||||
|
private static final String ACTION_KEY = "user.grant_item";
|
||||||
|
private static final int MAX_QUANTITY_PER_CALL = 100;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRatelimit() {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle() throws Exception {
|
||||||
|
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int userId = this.packet.readInt();
|
||||||
|
int itemId = this.packet.readInt();
|
||||||
|
int quantity = this.packet.readInt();
|
||||||
|
|
||||||
|
if (userId <= 0 || itemId <= 0 || quantity <= 0) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quantity > MAX_QUANTITY_PER_CALL) {
|
||||||
|
quantity = MAX_QUANTITY_PER_CALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement("INSERT INTO items (user_id, item_id, extra_data) VALUES (?, ?, '')")) {
|
||||||
|
for (int i = 0; i < quantity; i++) {
|
||||||
|
statement.setInt(1, userId);
|
||||||
|
statement.setInt(2, itemId);
|
||||||
|
statement.addBatch();
|
||||||
|
}
|
||||||
|
statement.executeBatch();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.economy_failed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package com.eu.habbo.messages.incoming.housekeeping;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
|
import com.eu.habbo.habbohotel.rooms.Room;
|
||||||
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
|
import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer;
|
||||||
|
|
||||||
|
public class HousekeepingKickAllFromRoomEvent extends MessageHandler {
|
||||||
|
private static final String ACTION_KEY = "room.kick_all";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRatelimit() {
|
||||||
|
return 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle() throws Exception {
|
||||||
|
if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int roomId = this.packet.readInt();
|
||||||
|
|
||||||
|
if (roomId <= 0) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId, false);
|
||||||
|
|
||||||
|
if (room == null) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.room_not_found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
room.ejectAll();
|
||||||
|
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, roomId, ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user