Compare commits

..

40 Commits

Author SHA1 Message Date
github-actions[bot] e6093f959f 🆙 Bump version to 4.1.14 [skip ci] 2026-05-06 10:51:57 +00:00
DuckieTM c854770561 Merge pull request #100 from duckietm/dev
Dev
2026-05-06 12:51:02 +02:00
DuckieTM a0b59134ee Merge pull request #99 from Lorenzune/merge-duckie-main-2026-05-06
Merge live secure runtime updates into dev
2026-05-06 07:08:37 +02:00
Lorenzune 67924289ac Complete secure config example 2026-05-06 06:27:49 +02:00
Lorenzune 26326bcc0e Merge remote-tracking branch 'duckie/main' into merge-duckie-main-2026-05-06
# Conflicts:
#	Database Updates/016_custom_prefixes_setup.sql
#	Database Updates/custom_nick_icons_setup.sql
#	Database Updates/remember_login_tokens.sql
#	Database Updates/wired_message_length_512.sql
#	Emulator/src/main/java/com/eu/habbo/habbohotel/GameEnvironment.java
#	Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUserDataComposer.java
#	Emulator/src/main/java/com/eu/habbo/messages/outgoing/users/UserProfileComposer.java
#	Emulator/src/main/java/com/eu/habbo/networking/gameserver/WebSocketChannelInitializer.java
#	Emulator/src/main/java/com/eu/habbo/networking/gameserver/auth/AuthHttpHandler.java
2026-05-06 04:23:14 +02:00
duckietm ee0613a480 🆙 Update 2026-05-05 12:50:28 +02:00
DuckieTM 37d7885663 🆙 update 2026-05-05 12:09:05 +02:00
github-actions[bot] fdf0e5d806 🆙 Bump version to 4.1.13 [skip ci] 2026-05-04 13:38:38 +00:00
DuckieTM c64d3b7b8d Merge pull request #98 from duckietm/dev
Dev
2026-05-04 15:37:38 +02:00
duckietm c2b85c0c8c 🆙 Redone Background profiles 2026-05-04 15:15:41 +02:00
duckietm f8a651b059 🆙 Security update Info stand background 2026-05-04 13:18:06 +02:00
github-actions[bot] 00f9feab14 🆙 Bump version to 4.1.12 [skip ci] 2026-05-04 08:54:02 +00:00
DuckieTM 0b37705b65 Merge pull request #97 from duckietm/dev
Dev
2026-05-04 10:53:08 +02:00
duckietm 9b77ca1016 🆙 Cleanup 2026-05-04 10:52:48 +02:00
duckietm 39941cd496 🆕 Added extra packet for the pets 2026-05-04 10:08:37 +02:00
duckietm 7095dfad43 🆙 Fix Pickall 2026-05-04 08:20:58 +02:00
DuckieTM 750b172304 Merge pull request #96 from simoleo89/feat/full-box-background
Feat/full box background
2026-05-04 08:03:55 +02:00
simoleo89 5afa1f274c feat(profile): add background_card_id for full-box card backgrounds
Introduces a 4th profile-style id (cardBg) alongside the existing
background/stand/overlay triplet. The new id is meant to render a
background that fills the entire user info card on the client.

- HabboInfo: new InfostandCardBg field, loaded/saved with the
  existing background ids; users.background_card_id column added
  via sqlupdates/add_users_background_card_id.sql.
- ChangeInfostandBgEvent: reads a 4th int with bytesAvailable
  guard to remain compatible with older clients.
- RoomUserDataComposer, RoomUsersComposer, UserProfileComposer:
  append the cardBg int after the existing trio. Bot sections in
  RoomUsersComposer pad an extra zero to keep field count consistent.
2026-05-03 22:09:53 +02:00
duckietm 8f59eb652f 🆙 As NAcho wants it, add effect on disconnected user & small security update 2026-05-01 16:59:34 +02:00
duckietm 8a8cd1121e 🆕 Create Custom Bage & Security update 2026-05-01 15:58:48 +02:00
github-actions[bot] 60e5ba3a6a 🆙 Bump version to 4.1.11 [skip ci] 2026-05-01 05:49:08 +00:00
DuckieTM 9fa3fad70c Merge pull request #95 from duckietm/dev
🆕 News API
2026-05-01 07:48:08 +02:00
duckietm 860f61f765 🆕 News API 2026-04-30 17:21:33 +02:00
github-actions[bot] c5137bf3dc 🆙 Bump version to 4.1.10 [skip ci] 2026-04-29 15:11:05 +00:00
DuckieTM 5150418796 Merge pull request #94 from duckietm/dev
Dev
2026-04-29 17:10:02 +02:00
duckietm 5c71b318fb 🆙 Latest compiled version 2026-04-29 17:09:43 +02:00
duckietm 1cac407c45 🆕 Effect selection in user dropdown 2026-04-29 13:20:53 +02:00
github-actions[bot] d85eecd624 🆙 Bump version to 4.1.9 [skip ci] 2026-04-28 11:52:58 +00:00
DuckieTM c50098a945 Merge pull request #93 from duckietm/dev
🆕 Added Staffchat to the Emu
2026-04-28 13:52:02 +02:00
duckietm 0224f3f416 🆕 Added Staffchat to the Emu
!!! Do not run the Staffchat plugin anymore !!!!

- execute the sql:

INSERT INTO `permission_definitions` (`permission_key`, `max_value`, `comment`)
VALUES ( '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.' )
ON DUPLICATE KEY UPDATE `max_value` = VALUES(`max_value`), `comment`   = VALUES(`comment`);
2026-04-28 13:51:04 +02:00
Lorenzune 59ce829fe0 Merge duckie main into live merge branch 2026-04-25 13:52:04 +02:00
Lorenzune 9bad1eb3f6 Update secure configuration example paths 2026-04-25 13:30:00 +02:00
Lorenzune f51617d092 Add secure mode config toggles 2026-04-24 15:55:39 +02:00
Lorenzune 585af846c4 Add secure assets and remember login support 2026-04-23 16:27:01 +02:00
Lorenzune dde2c4143c checkpoint: secure config gdm and api baseline 2026-04-23 07:01:09 +02:00
Lorenzune 26999c254b Merge remote-tracking branch 'duckie/main' into duckie-live-merge-2026-04-21 2026-04-22 09:43:43 +02:00
Lorenzune dd96523496 Merge latest duckie main with UI login 2026-04-21 11:44:19 +02:00
Lorenzune 02f3ded44e Merge remote-tracking branch 'duckie-temp/main' into duckie-merge-2026-04-21
# Conflicts:
#	Emulator/src/main/java/com/eu/habbo/habbohotel/achievements/AchievementManager.java
#	Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java
#	Emulator/src/main/java/com/eu/habbo/habbohotel/users/inventory/ItemsComponent.java
#	Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java
2026-04-21 11:20:06 +02:00
Lorenzune 8bbe8640b0 WIP preserve local changes before duckie merge 2026-04-21 11:13:32 +02:00
Lorenzune 078fb3db60 Fix wired text capture and showmessage behavior 2026-04-21 08:54:02 +02:00
111 changed files with 6187 additions and 664 deletions
+6 -35
View File
@@ -1,37 +1,3 @@
-- =============================================================================
-- Consolidated Database Updates - All-in-One
-- =============================================================================
-- This file combines ALL individual update scripts from SQL/Database Updates/
-- into a single idempotent migration. Every statement is safe to re-run:
-- - ALTER TABLE ADD COLUMN IF NOT EXISTS (MariaDB 10.0+)
-- - ALTER TABLE CHANGE/MODIFY COLUMN IF EXISTS
-- - CREATE TABLE IF NOT EXISTS
-- - INSERT IGNORE / ON DUPLICATE KEY UPDATE for settings
-- - TRUNCATE + re-insert for reference data (breeding)
--
-- Run order: This file FIRST, then 001_optimize_gameserver.sql
--
-- Source files (in applied order):
-- 1. UpdateDatabase_Allow_diagonale.sql
-- 2. UpdateDatabase_BOT.sql
-- 3. UpdateDatabase_Banners.sql
-- 4. UpdateDatabase_DanceCMD.sql
-- 5. UpdateDatabase_Happiness.sql
-- 6. UpdateDatabase_Websocket.sql
-- 7. UpdateDatabase_unignorable.sql
-- 8. Default_Camera.sql
-- 9. 07012026_UpdateDatabase_to_4-0-1.sql
-- 10. 09012026_UpdateDatabase_to_4-0-2.sql
-- 11. 12012026_Battle Banzai.sql (same as #10, deduplicated)
-- 12. 12012026_Breeding Fixes.sql
-- 13. 12012026_ChatBubbles.sql
-- 14. 16032026_updateall_command.sql
-- 15. 17032026_allow_underpass.sql
-- 16. 19032026_hotel_timezone.sql
-- 17. 21022026_user_prefixes.sql
-- 18. 06042026_builders_club_catalog_offers.sql
-- =============================================================================
SET NAMES utf8mb4; SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0; SET FOREIGN_KEY_CHECKS = 0;
SET @OLD_SQL_MODE = @@SQL_MODE; SET @OLD_SQL_MODE = @@SQL_MODE;
@@ -512,8 +478,13 @@ ALTER TABLE `users_settings`
ADD COLUMN IF NOT EXISTS `builders_club_bonus_furni` INT(11) NOT NULL DEFAULT 0 AFTER `hc_gifts_claimed`; ADD COLUMN IF NOT EXISTS `builders_club_bonus_furni` INT(11) NOT NULL DEFAULT 0 AFTER `hc_gifts_claimed`;
INSERT INTO `permission_definitions` (`permission_key`, `max_value`, `comment`)
VALUES ( '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.' )
ON DUPLICATE KEY UPDATE `max_value` = VALUES(`max_value`), `comment` = VALUES(`comment`);
-- ============================================================================= -- =============================================================================
-- Done -- Done.
-- ============================================================================= -- =============================================================================
SET FOREIGN_KEY_CHECKS = 1; SET FOREIGN_KEY_CHECKS = 1;
SET SQL_MODE = @OLD_SQL_MODE; SET SQL_MODE = @OLD_SQL_MODE;
-1
View File
@@ -987,4 +987,3 @@ ALTER TABLE `catalog_pages_bc`
'builders_club_addons', 'builders_club_addons',
'builders_club_loyalty' 'builders_club_loyalty'
) NOT NULL DEFAULT 'default_3x3'; ) NOT NULL DEFAULT 'default_3x3';
File diff suppressed because one or more lines are too long
@@ -0,0 +1,28 @@
-- Make sure that the emulator has write access to the badge_path folder !!!!!
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 `badge_id` (`badge_id`),
KEY `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;
@@ -0,0 +1 @@
ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `background_card_id` INT(11) NOT NULL DEFAULT 0 AFTER `background_overlay_id`;
@@ -0,0 +1,356 @@
-- ============================================================
-- Custom Prefix System - Complete Setup (safe upgrade version)
-- ============================================================
-- Questo script è pensato per essere rieseguito senza errori
-- anche se le tabelle esistono già con una struttura parziale.
-- ------------------------------------------------------------
-- 1. Main user prefixes table
-- ------------------------------------------------------------
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`),
INDEX `idx_user_id` (`user_id`),
INDEX `idx_user_active` (`user_id`, `active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ------------------------------------------------------------
-- 2. Catalog table
-- ------------------------------------------------------------
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;
-- ------------------------------------------------------------
-- 3. User visual settings
-- ------------------------------------------------------------
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`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ------------------------------------------------------------
-- 4. Prefix settings table
-- ------------------------------------------------------------
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;
-- ------------------------------------------------------------
-- 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
-- ============================================================
-- ------------------------------------------------------------
-- user_prefixes: add missing columns safely
-- ------------------------------------------------------------
SET @dbname = DATABASE();
SET @sql = (
SELECT IF(
EXISTS(
SELECT 1
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @dbname
AND TABLE_NAME = 'user_prefixes'
AND COLUMN_NAME = 'font'
),
'SELECT 1',
'ALTER TABLE `user_prefixes` ADD COLUMN `font` VARCHAR(50) NOT NULL DEFAULT '''' AFTER `effect`'
)
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql = (
SELECT IF(
EXISTS(
SELECT 1
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @dbname
AND TABLE_NAME = 'user_prefixes'
AND COLUMN_NAME = 'catalog_prefix_id'
),
'SELECT 1',
'ALTER TABLE `user_prefixes` ADD COLUMN `catalog_prefix_id` INT(11) NOT NULL DEFAULT 0 AFTER `font`'
)
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql = (
SELECT IF(
EXISTS(
SELECT 1
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @dbname
AND TABLE_NAME = 'user_prefixes'
AND COLUMN_NAME = 'display_name'
),
'SELECT 1',
'ALTER TABLE `user_prefixes` ADD COLUMN `display_name` VARCHAR(100) NOT NULL DEFAULT '''' AFTER `catalog_prefix_id`'
)
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql = (
SELECT IF(
EXISTS(
SELECT 1
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @dbname
AND TABLE_NAME = 'user_prefixes'
AND COLUMN_NAME = 'points'
),
'SELECT 1',
'ALTER TABLE `user_prefixes` ADD COLUMN `points` INT(11) NOT NULL DEFAULT 0 AFTER `display_name`'
)
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql = (
SELECT IF(
EXISTS(
SELECT 1
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @dbname
AND TABLE_NAME = 'user_prefixes'
AND COLUMN_NAME = 'points_type'
),
'SELECT 1',
'ALTER TABLE `user_prefixes` ADD COLUMN `points_type` INT(11) NOT NULL DEFAULT 0 AFTER `points`'
)
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql = (
SELECT IF(
EXISTS(
SELECT 1
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @dbname
AND TABLE_NAME = 'user_prefixes'
AND COLUMN_NAME = 'is_custom'
),
'SELECT 1',
'ALTER TABLE `user_prefixes` ADD COLUMN `is_custom` TINYINT(1) NOT NULL DEFAULT 1 AFTER `points_type`'
)
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ------------------------------------------------------------
-- custom_prefixes_catalog: add missing columns safely
-- ------------------------------------------------------------
SET @sql = (
SELECT IF(
EXISTS(
SELECT 1
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @dbname
AND TABLE_NAME = 'custom_prefixes_catalog'
AND COLUMN_NAME = 'font'
),
'SELECT 1',
'ALTER TABLE `custom_prefixes_catalog` ADD COLUMN `font` VARCHAR(50) NOT NULL DEFAULT '''' AFTER `effect`'
)
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql = (
SELECT IF(
EXISTS(
SELECT 1
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @dbname
AND TABLE_NAME = 'custom_prefixes_catalog'
AND COLUMN_NAME = 'points'
),
'SELECT 1',
'ALTER TABLE `custom_prefixes_catalog` ADD COLUMN `points` INT(11) NOT NULL DEFAULT 0 AFTER `font`'
)
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql = (
SELECT IF(
EXISTS(
SELECT 1
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @dbname
AND TABLE_NAME = 'custom_prefixes_catalog'
AND COLUMN_NAME = 'points_type'
),
'SELECT 1',
'ALTER TABLE `custom_prefixes_catalog` ADD COLUMN `points_type` INT(11) NOT NULL DEFAULT 0 AFTER `points`'
)
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql = (
SELECT IF(
EXISTS(
SELECT 1
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @dbname
AND TABLE_NAME = 'custom_prefixes_catalog'
AND COLUMN_NAME = 'enabled'
),
'SELECT 1',
'ALTER TABLE `custom_prefixes_catalog` ADD COLUMN `enabled` TINYINT(1) NOT NULL DEFAULT 1 AFTER `points_type`'
)
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @sql = (
SELECT IF(
EXISTS(
SELECT 1
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @dbname
AND TABLE_NAME = 'custom_prefixes_catalog'
AND COLUMN_NAME = 'sort_order'
),
'SELECT 1',
'ALTER TABLE `custom_prefixes_catalog` ADD COLUMN `sort_order` INT(11) NOT NULL DEFAULT 0 AFTER `enabled`'
)
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- ============================================================
-- Default settings
-- ============================================================
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');
-- ============================================================
-- Default catalog entries
-- ============================================================
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);
-- ============================================================
-- Example blacklist entries
-- ============================================================
INSERT IGNORE INTO `custom_prefix_blacklist` (`word`) VALUES
('admin'),
('staff'),
('mod'),
('owner');
-- ============================================================
-- Notes
-- ============================================================
-- Preset prefixes for `:customize` are loaded directly by
-- UserNickIconsComposer and displayed inside the `:customize` panel.
--
-- This setup does not require rows in `catalog_pages`.
--
-- Command texts / permission inserts are intentionally omitted
-- for compatibility with both legacy and normalized permission schemas.
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
-- GivePrefix command
('commands.keys.cmd_give_prefix', 'giveprefix'),
('commands.error.cmd_give_prefix.usage', 'Usage: :giveprefix <username> <text> <color> [icon] [effect]'),
('commands.error.cmd_give_prefix.invalid_color', 'Invalid color format. Use hex format (#FF0000).'),
('commands.error.cmd_give_prefix.too_long', 'Prefix text is too long (max 15 characters).'),
('commands.error.cmd_give_prefix.user_not_found', 'User not found or not online.'),
('commands.succes.cmd_give_prefix', 'Prefix {%prefix%} successfully given to %user%!'),
-- ListPrefixes command
('commands.keys.cmd_list_prefixes', 'listprefixes'),
('commands.error.cmd_list_prefixes.usage', 'Usage: :listprefixes <username>'),
('commands.error.cmd_list_prefixes.user_not_found', 'User not found or not online.'),
('commands.succes.cmd_list_prefixes.header', 'Prefixes of %user%:'),
('commands.succes.cmd_list_prefixes.empty', '%user% has no prefixes.'),
-- RemovePrefix command
('commands.keys.cmd_remove_prefix', 'removeprefix'),
('commands.error.cmd_remove_prefix.usage', 'Usage: :removeprefix <username> <id|all>'),
('commands.error.cmd_remove_prefix.user_not_found', 'User not found or not online.'),
('commands.error.cmd_remove_prefix.invalid_id', 'Invalid prefix ID. Must be a number or "all".'),
('commands.error.cmd_remove_prefix.not_found', 'Prefix not found for this user.'),
('commands.succes.cmd_remove_prefix', 'Prefix #%id% removed from %user%.'),
('commands.succes.cmd_remove_prefix.all', 'All prefixes removed from %user%.'),
-- PrefixBlacklist command
('commands.keys.cmd_prefix_blacklist', 'prefixblacklist'),
('commands.error.cmd_prefix_blacklist.usage', 'Usage: :prefixblacklist <add|remove|list> [word]'),
('commands.error.cmd_prefix_blacklist.empty_word', 'Word cannot be empty.'),
('commands.succes.cmd_prefix_blacklist.header', 'Blacklisted prefix words:'),
('commands.succes.cmd_prefix_blacklist.empty', 'No blacklisted words.'),
('commands.succes.cmd_prefix_blacklist.added', 'Word "%word%" added to prefix blacklist.'),
('commands.succes.cmd_prefix_blacklist.removed', 'Word "%word%" removed from prefix blacklist.');
INSERT IGNORE INTO permission_definitions
(permission_key, max_value, rank_1, rank_2, rank_3, rank_4, rank_5, rank_6, rank_7)
VALUES
('cmd_give_prefix', '1', '0', '0', '0', '0', '0', '0', '1'),
('cmd_list_prefixes', '1', '0', '0', '0', '0', '0', '0', '1'),
('cmd_remove_prefix', '1', '0', '0', '0', '0', '0', '0', '1'),
('cmd_prefix_blacklist', '1', '0', '0', '0', '0', '0', '0', '1');
@@ -0,0 +1,244 @@
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=utf8mb3 COLLATE=utf8mb3_unicode_ci;
INSERT INTO `infostand_backgrounds` (`id`, `category`, `min_rank`, `is_hc_only`, `is_ambassador_only`) VALUES
(0, 'background', 0, 0, 0),
(1, 'background', 0, 0, 0),
(2, 'background', 0, 0, 0),
(3, 'background', 0, 0, 0),
(4, 'background', 0, 0, 0),
(5, 'background', 0, 0, 0),
(6, 'background', 0, 0, 0),
(7, 'background', 0, 0, 0),
(8, 'background', 0, 0, 0),
(9, 'background', 0, 0, 0),
(10, 'background', 0, 0, 0),
(11, 'background', 0, 0, 0),
(12, 'background', 0, 0, 0),
(13, 'background', 0, 0, 0),
(14, 'background', 0, 0, 0),
(15, 'background', 0, 0, 0),
(16, 'background', 0, 0, 0),
(17, 'background', 0, 0, 0),
(18, 'background', 0, 0, 0),
(19, 'background', 0, 0, 0),
(20, 'background', 0, 0, 0),
(21, 'background', 0, 0, 0),
(22, 'background', 0, 0, 0),
(23, 'background', 0, 0, 0),
(24, 'background', 0, 0, 0),
(25, 'background', 0, 0, 0),
(26, 'background', 0, 0, 0),
(27, 'background', 0, 0, 0),
(28, 'background', 0, 0, 0),
(29, 'background', 0, 0, 0),
(30, 'background', 0, 0, 0),
(31, 'background', 0, 0, 0),
(32, 'background', 0, 0, 0),
(33, 'background', 0, 0, 0),
(34, 'background', 0, 0, 0),
(35, 'background', 0, 0, 0),
(36, 'background', 0, 0, 0),
(37, 'background', 0, 0, 0),
(38, 'background', 0, 0, 0),
(39, 'background', 0, 0, 0),
(40, 'background', 0, 0, 0),
(41, 'background', 0, 0, 0),
(42, 'background', 0, 1, 0),
(43, 'background', 0, 1, 0),
(44, 'background', 0, 1, 0),
(45, 'background', 0, 1, 0),
(46, 'background', 0, 1, 0),
(47, 'background', 0, 1, 0),
(48, 'background', 0, 1, 0),
(49, 'background', 0, 1, 0),
(50, 'background', 0, 1, 0),
(51, 'background', 0, 1, 0),
(52, 'background', 0, 1, 0),
(53, 'background', 0, 1, 0),
(54, 'background', 0, 1, 0),
(55, 'background', 0, 1, 0),
(56, 'background', 0, 1, 0),
(57, 'background', 0, 1, 0),
(58, 'background', 0, 1, 0),
(59, 'background', 0, 1, 0),
(60, 'background', 0, 1, 0),
(61, 'background', 0, 1, 0),
(62, 'background', 0, 1, 0),
(63, 'background', 0, 1, 0),
(64, 'background', 0, 1, 0),
(65, 'background', 0, 1, 0),
(66, 'background', 0, 1, 0),
(67, 'background', 0, 1, 0),
(68, 'background', 0, 1, 0),
(69, 'background', 0, 1, 0),
(70, 'background', 0, 1, 0),
(71, 'background', 0, 1, 0),
(72, 'background', 0, 1, 0),
(73, 'background', 0, 1, 0),
(74, 'background', 0, 1, 0),
(75, 'background', 0, 1, 0),
(76, 'background', 0, 1, 0),
(77, 'background', 0, 1, 0),
(78, 'background', 0, 1, 0),
(79, 'background', 0, 1, 0),
(80, 'background', 0, 1, 0),
(81, 'background', 0, 1, 0),
(82, 'background', 0, 1, 0),
(83, 'background', 0, 1, 0),
(84, 'background', 0, 1, 0),
(85, 'background', 0, 1, 0),
(86, 'background', 0, 1, 0),
(87, 'background', 0, 1, 0),
(88, 'background', 0, 1, 0),
(89, 'background', 0, 1, 0),
(90, 'background', 0, 1, 0),
(91, 'background', 0, 1, 0),
(92, 'background', 0, 1, 0),
(93, 'background', 0, 1, 0),
(94, 'background', 0, 1, 0),
(95, 'background', 0, 1, 0),
(96, 'background', 0, 1, 0),
(97, 'background', 0, 1, 0),
(98, 'background', 0, 1, 0),
(99, 'background', 0, 1, 0),
(100, 'background', 0, 1, 0),
(101, 'background', 2, 0, 0),
(102, 'background', 0, 1, 0),
(103, 'background', 0, 1, 0),
(104, 'background', 0, 1, 0),
(105, 'background', 0, 1, 0),
(106, 'background', 0, 1, 0),
(107, 'background', 0, 1, 0),
(108, 'background', 0, 1, 0),
(109, 'background', 0, 1, 0),
(110, 'background', 0, 1, 0),
(111, 'background', 0, 1, 0),
(112, 'background', 0, 1, 0),
(113, 'background', 0, 1, 0),
(114, 'background', 0, 1, 0),
(115, 'background', 0, 1, 0),
(116, 'background', 0, 1, 0),
(117, 'background', 0, 1, 0),
(118, 'background', 0, 1, 0),
(119, 'background', 0, 1, 0),
(120, 'background', 0, 1, 0),
(121, 'background', 0, 1, 0),
(122, 'background', 0, 1, 0),
(123, 'background', 0, 1, 0),
(124, 'background', 0, 1, 0),
(125, 'background', 0, 1, 0),
(126, 'background', 0, 1, 0),
(127, 'background', 0, 1, 0),
(128, 'background', 0, 1, 0),
(129, 'background', 0, 1, 0),
(130, 'background', 0, 1, 0),
(131, 'background', 0, 1, 0),
(132, 'background', 0, 1, 0),
(133, 'background', 0, 1, 0),
(134, 'background', 0, 1, 0),
(135, 'background', 0, 1, 0),
(136, 'background', 0, 1, 0),
(137, 'background', 0, 1, 0),
(138, 'background', 0, 1, 0),
(139, 'background', 0, 1, 0),
(140, 'background', 0, 1, 0),
(141, 'background', 0, 1, 0),
(142, 'background', 0, 1, 0),
(143, 'background', 0, 1, 0),
(144, 'background', 0, 1, 0),
(145, 'background', 0, 1, 0),
(146, 'background', 0, 1, 0),
(147, 'background', 0, 1, 0),
(148, 'background', 0, 1, 0),
(149, 'background', 0, 1, 0),
(150, 'background', 0, 1, 0),
(151, 'background', 0, 1, 0),
(152, 'background', 0, 1, 0),
(153, 'background', 0, 1, 0),
(154, 'background', 0, 1, 0),
(155, 'background', 0, 1, 0),
(156, 'background', 0, 1, 0),
(157, 'background', 0, 1, 0),
(158, 'background', 0, 1, 0),
(159, 'background', 0, 1, 0),
(160, 'background', 0, 1, 0),
(161, 'background', 0, 1, 0),
(162, 'background', 0, 1, 0),
(163, 'background', 0, 1, 0),
(164, 'background', 0, 1, 0),
(165, 'background', 0, 1, 0),
(166, 'background', 0, 1, 0),
(167, 'background', 0, 1, 0),
(168, 'background', 0, 1, 0),
(169, 'background', 0, 1, 0),
(170, 'background', 0, 1, 0),
(171, 'background', 0, 1, 0),
(172, 'background', 0, 1, 0),
(173, 'background', 0, 1, 0),
(174, 'background', 0, 1, 0),
(175, 'background', 0, 1, 0),
(176, 'background', 0, 1, 0),
(177, 'background', 0, 1, 0),
(178, 'background', 0, 1, 0),
(179, 'background', 0, 1, 0),
(180, 'background', 0, 1, 0),
(181, 'background', 0, 1, 0),
(182, 'background', 0, 1, 0),
(183, 'background', 0, 1, 0),
(184, 'background', 0, 1, 0),
(185, 'background', 0, 1, 0),
(186, 'background', 0, 1, 0),
(187, 'background', 0, 1, 0),
(0, 'stand', 0, 0, 0),
(1, 'stand', 0, 0, 0),
(2, 'stand', 0, 0, 0),
(3, 'stand', 0, 0, 0),
(4, 'stand', 0, 0, 0),
(5, 'stand', 0, 0, 0),
(6, 'stand', 0, 0, 0),
(7, 'stand', 0, 0, 0),
(8, 'stand', 0, 0, 0),
(9, 'stand', 0, 0, 0),
(10, 'stand', 0, 0, 0),
(11, 'stand', 0, 0, 0),
(12, 'stand', 0, 0, 0),
(13, 'stand', 0, 0, 0),
(14, 'stand', 0, 0, 0),
(15, 'stand', 0, 0, 0),
(16, 'stand', 0, 1, 0),
(17, 'stand', 0, 1, 0),
(18, 'stand', 0, 1, 0),
(19, 'stand', 0, 1, 0),
(20, 'stand', 0, 1, 0),
(21, 'stand', 0, 1, 0),
(0, 'overlay', 0, 0, 0),
(1, 'overlay', 0, 0, 0),
(2, 'overlay', 0, 1, 0),
(3, 'overlay', 0, 1, 0),
(4, 'overlay', 0, 1, 0),
(5, 'overlay', 0, 1, 0),
(6, 'overlay', 0, 1, 0),
(7, 'overlay', 0, 1, 0),
(8, 'overlay', 0, 1, 0),
(1, 'card', 0, 0, 0),
(2, 'card', 0, 0, 0),
(3, 'card', 0, 0, 0),
(4, 'card', 0, 0, 0),
(5, 'card', 0, 0, 0),
(6, 'card', 0, 0, 0),
(7, 'card', 0, 0, 0),
(8, 'card', 0, 0, 0),
(9, 'card', 0, 0, 0),
(10, 'card', 0, 0, 0),
(11, 'card', 0, 0, 0),
(12, 'card', 0, 0, 0),
(13, 'card', 0, 0, 0),
(14, 'card', 0, 0, 0),
(15, 'card', 0, 0, 0);
@@ -0,0 +1,36 @@
-- ============================================================
-- Nick Icon Customization Setup
-- ============================================================
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_icon_key` (`icon_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
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_icon` (`user_id`, `icon_key`),
KEY `idx_user_id` (`user_id`),
KEY `idx_user_active` (`user_id`, `active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
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);
ALTER TABLE `custom_nick_icons_catalog`
ADD COLUMN IF NOT EXISTS `display_name` VARCHAR(100) NOT NULL DEFAULT '' AFTER `icon_key`;
@@ -0,0 +1,8 @@
ALTER TABLE `users`
ADD COLUMN IF NOT EXISTS `remember_token_hash` VARCHAR(64) NOT NULL DEFAULT '' AFTER `auth_ticket`;
ALTER TABLE `users`
ADD COLUMN IF NOT EXISTS `remember_token_expires_at` INT(11) UNSIGNED NOT NULL DEFAULT 0 AFTER `remember_token_hash`;
ALTER TABLE `users`
ADD INDEX IF NOT EXISTS `idx_users_remember_token_hash` (`remember_token_hash`);
@@ -0,0 +1,3 @@
INSERT INTO `wired_emulator_settings` (`key`, `value`, `comment`)
VALUES ('hotel.wired.message.max_length', '512', 'Maximum length of text fields used by wired messages and bot text effects.')
ON DUPLICATE KEY UPDATE `value` = '512';
+3 -3
View File
@@ -6,7 +6,7 @@
<groupId>com.eu.habbo</groupId> <groupId>com.eu.habbo</groupId>
<artifactId>Habbo</artifactId> <artifactId>Habbo</artifactId>
<version>4.1.8</version> <version>4.1.14</version>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -163,7 +163,7 @@
<version>2.13.0</version> <version>2.13.0</version>
</dependency> </dependency>
<!-- jBCrypt used by the built-in /api/auth/* HTTP login handler <!-- jBCrypt used by the built-in /api/auth/* HTTP login handler
to verify Laravel-style $2y$ BCrypt hashes from users.password --> to verify Laravel-style $2y$ BCrypt hashes from users.password -->
<dependency> <dependency>
<groupId>org.mindrot</groupId> <groupId>org.mindrot</groupId>
@@ -171,7 +171,7 @@
<version>0.4</version> <version>0.4</version>
</dependency> </dependency>
<!-- Jakarta Mail used by the built-in forgot-password endpoint <!-- Jakarta Mail used by the built-in forgot-password endpoint
when smtp.* keys are configured in emulator_settings --> when smtp.* keys are configured in emulator_settings -->
<dependency> <dependency>
<groupId>org.eclipse.angus</groupId> <groupId>org.eclipse.angus</groupId>
@@ -1,17 +0,0 @@
-- ============================================================
-- Catalog & Furni Admin Permission
-- Adds acc_catalogfurni permission to the permissions table
-- Required by: CatalogAdmin packet handlers (10050-10059)
-- ============================================================
-- 1. Add the column to the permissions table
ALTER TABLE `permissions`
ADD COLUMN `acc_catalogfurni` ENUM('0','1') NOT NULL DEFAULT '0'
AFTER `acc_catalog_ids`;
-- 2. Enable for Administrator (rank 7) by default
UPDATE `permissions` SET `acc_catalogfurni` = '1' WHERE `id` = 7;
-- Optional: enable for other ranks as needed
-- UPDATE `permissions` SET `acc_catalogfurni` = '1' WHERE `id` = 6; -- Super Mod
-- UPDATE `permissions` SET `acc_catalogfurni` = '1' WHERE `id` = 5; -- Moderator
@@ -1,115 +0,0 @@
-- ============================================================
-- Custom Prefix System - Complete Setup
-- ============================================================
-- 1. Main user prefixes table
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 '',
`active` TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
INDEX `idx_user_id` (`user_id`),
INDEX `idx_user_active` (`user_id`, `active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 2. Prefix settings table
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;
-- Default settings
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');
-- 3. Blacklisted words 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;
-- Example blacklist entries (customize as needed)
INSERT IGNORE INTO `custom_prefix_blacklist` (`word`) VALUES
('admin'),
('staff'),
('mod'),
('owner');
-- 4. Add effect column (if table already exists without it)
-- ALTER TABLE `user_prefixes` ADD COLUMN IF NOT EXISTS `effect` VARCHAR(50) NOT NULL DEFAULT '' AFTER `icon`;
-- ============================================================
-- Catalog page for custom prefixes
-- ============================================================
-- NOTE: Adjust parent_id to match your catalog parent category ID.
-- Example: parent_id = -1 for root, or the ID of your "Extra" / "Specials" category
INSERT INTO `catalog_pages` (
`parent_id`, `caption`, `caption_save`, `icon_image`, `visible`, `enabled`,
`min_rank`, `page_layout`, `page_strings_1`, `page_strings_2`
) VALUES (
-1,
'Custom Prefix',
'custom_prefix',
1,
1,
1,
1,
'custom_prefix',
'Create your own custom prefix!\rChoose text, colors, icon and effects to stand out in chat.',
''
);
-- ============================================================
-- Command texts (insert into emulator_texts if not present)
-- ============================================================
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
-- GivePrefix command
('commands.keys.cmd_give_prefix', 'giveprefix'),
('commands.error.cmd_give_prefix.usage', 'Usage: :giveprefix <username> <text> <color> [icon] [effect]'),
('commands.error.cmd_give_prefix.invalid_color', 'Invalid color format. Use hex format (#FF0000).'),
('commands.error.cmd_give_prefix.too_long', 'Prefix text is too long (max 15 characters).'),
('commands.error.cmd_give_prefix.user_not_found', 'User not found or not online.'),
('commands.succes.cmd_give_prefix', 'Prefix {%prefix%} successfully given to %user%!'),
-- ListPrefixes command
('commands.keys.cmd_list_prefixes', 'listprefixes'),
('commands.error.cmd_list_prefixes.usage', 'Usage: :listprefixes <username>'),
('commands.error.cmd_list_prefixes.user_not_found', 'User not found or not online.'),
('commands.succes.cmd_list_prefixes.header', 'Prefixes of %user%:'),
('commands.succes.cmd_list_prefixes.empty', '%user% has no prefixes.'),
-- RemovePrefix command
('commands.keys.cmd_remove_prefix', 'removeprefix'),
('commands.error.cmd_remove_prefix.usage', 'Usage: :removeprefix <username> <id|all>'),
('commands.error.cmd_remove_prefix.user_not_found', 'User not found or not online.'),
('commands.error.cmd_remove_prefix.invalid_id', 'Invalid prefix ID. Must be a number or "all".'),
('commands.error.cmd_remove_prefix.not_found', 'Prefix not found for this user.'),
('commands.succes.cmd_remove_prefix', 'Prefix #%id% removed from %user%.'),
('commands.succes.cmd_remove_prefix.all', 'All prefixes removed from %user%.'),
-- PrefixBlacklist command
('commands.keys.cmd_prefix_blacklist', 'prefixblacklist'),
('commands.error.cmd_prefix_blacklist.usage', 'Usage: :prefixblacklist <add|remove|list> [word]'),
('commands.error.cmd_prefix_blacklist.empty_word', 'Word cannot be empty.'),
('commands.succes.cmd_prefix_blacklist.header', 'Blacklisted prefix words:'),
('commands.succes.cmd_prefix_blacklist.empty', 'No blacklisted words.'),
('commands.succes.cmd_prefix_blacklist.added', 'Word "%word%" added to prefix blacklist.'),
('commands.succes.cmd_prefix_blacklist.removed', 'Word "%word%" removed from prefix blacklist.');
-- ============================================================
-- Permissions for prefix commands (add to permissions table)
-- ============================================================
INSERT IGNORE INTO `permissions` (`id`, `rank_id`, `permission_name`, `setting_type`) VALUES
(NULL, 7, 'cmd_give_prefix', '1'),
(NULL, 7, 'cmd_list_prefixes', '1'),
(NULL, 7, 'cmd_remove_prefix', '1'),
(NULL, 7, 'cmd_prefix_blacklist', '1');
@@ -28,6 +28,7 @@ public class RoomUserPetComposer extends MessageComposer {
this.response.appendInt(0); this.response.appendInt(0);
this.response.appendInt(0); this.response.appendInt(0);
this.response.appendInt(0); this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendString(this.petType + " " + this.race + " " + this.color + " 2 2 -1 0 3 -1 0"); this.response.appendString(this.petType + " " + this.race + " " + this.color + " 2 2 -1 0 3 -1 0");
this.response.appendInt(this.habbo.getRoomUnit().getId()); this.response.appendInt(this.habbo.getRoomUnit().getId());
this.response.appendInt(this.habbo.getRoomUnit().getX()); this.response.appendInt(this.habbo.getRoomUnit().getX());
@@ -21,7 +21,10 @@ import com.eu.habbo.habbohotel.pets.PetManager;
import com.eu.habbo.habbohotel.polls.PollManager; import com.eu.habbo.habbohotel.polls.PollManager;
import com.eu.habbo.habbohotel.rooms.RoomChatBubbleManager; import com.eu.habbo.habbohotel.rooms.RoomChatBubbleManager;
import com.eu.habbo.habbohotel.rooms.RoomManager; import com.eu.habbo.habbohotel.rooms.RoomManager;
import com.eu.habbo.habbohotel.translations.GoogleTranslateManager;
import com.eu.habbo.habbohotel.users.HabboManager; import com.eu.habbo.habbohotel.users.HabboManager;
import com.eu.habbo.habbohotel.users.custombadge.CustomBadgeManager;
import com.eu.habbo.habbohotel.users.infostand.InfostandBackgroundManager;
import com.eu.habbo.habbohotel.users.subscriptions.SubscriptionManager; import com.eu.habbo.habbohotel.users.subscriptions.SubscriptionManager;
import com.eu.habbo.habbohotel.users.subscriptions.SubscriptionScheduler; import com.eu.habbo.habbohotel.users.subscriptions.SubscriptionScheduler;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -58,6 +61,9 @@ public class GameEnvironment {
private SubscriptionManager subscriptionManager; private SubscriptionManager subscriptionManager;
private CalendarManager calendarManager; private CalendarManager calendarManager;
private RoomChatBubbleManager roomChatBubbleManager; private RoomChatBubbleManager roomChatBubbleManager;
private GoogleTranslateManager googleTranslateManager;
private CustomBadgeManager customBadgeManager;
private InfostandBackgroundManager infostandBackgroundManager;
public void load() throws Exception { public void load() throws Exception {
LOGGER.info("GameEnvironment -> Loading..."); LOGGER.info("GameEnvironment -> Loading...");
@@ -84,6 +90,9 @@ public class GameEnvironment {
this.pollManager = new PollManager(); this.pollManager = new PollManager();
this.calendarManager = new CalendarManager(); this.calendarManager = new CalendarManager();
this.roomChatBubbleManager = new RoomChatBubbleManager(); this.roomChatBubbleManager = new RoomChatBubbleManager();
this.googleTranslateManager = new GoogleTranslateManager();
this.customBadgeManager = new CustomBadgeManager();
this.infostandBackgroundManager = new InfostandBackgroundManager();
this.roomManager.loadPublicRooms(); this.roomManager.loadPublicRooms();
this.navigatorManager.loadNavigator(); this.navigatorManager.loadNavigator();
@@ -121,6 +130,9 @@ public class GameEnvironment {
this.hotelViewManager.dispose(); this.hotelViewManager.dispose();
this.subscriptionManager.dispose(); this.subscriptionManager.dispose();
this.calendarManager.dispose(); this.calendarManager.dispose();
if (this.googleTranslateManager != null) {
this.googleTranslateManager.clearCache();
}
LOGGER.info("GameEnvironment -> Disposed!"); LOGGER.info("GameEnvironment -> Disposed!");
} }
@@ -219,4 +231,16 @@ public class GameEnvironment {
public RoomChatBubbleManager getRoomChatBubbleManager() { public RoomChatBubbleManager getRoomChatBubbleManager() {
return roomChatBubbleManager; return roomChatBubbleManager;
} }
public GoogleTranslateManager getGoogleTranslateManager() {
return this.googleTranslateManager;
}
public CustomBadgeManager getCustomBadgeManager() {
return this.customBadgeManager;
}
public InfostandBackgroundManager getInfostandBackgroundManager() {
return this.infostandBackgroundManager;
}
} }
@@ -1,7 +1,6 @@
package com.eu.habbo.habbohotel.achievements; package com.eu.habbo.habbohotel.achievements;
import com.eu.habbo.Emulator; import com.eu.habbo.Emulator;
import com.eu.habbo.database.SqlQueries;
import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.items.Item;
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;
@@ -50,12 +49,16 @@ public class AchievementManager {
if (habbo != null) { if (habbo != null) {
progressAchievement(habbo, achievement, amount); progressAchievement(habbo, achievement, amount);
} else { } else {
try { try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
SqlQueries.update( PreparedStatement statement = connection.prepareStatement("" +
"INSERT INTO users_achievements_queue (user_id, achievement_id, amount) VALUES (?, ?, ?) " "INSERT INTO users_achievements_queue (user_id, achievement_id, amount) VALUES (?, ?, ?) " +
+ "ON DUPLICATE KEY UPDATE amount = amount + ?", "ON DUPLICATE KEY UPDATE amount = amount + ?")) {
habboId, achievement.id, amount, amount); statement.setInt(1, habboId);
} catch (SqlQueries.DataAccessException e) { statement.setInt(2, achievement.id);
statement.setInt(3, amount);
statement.setInt(4, amount);
statement.execute();
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e); LOGGER.error("Caught SQL exception", e);
} }
} }
@@ -200,41 +203,48 @@ public class AchievementManager {
} }
public static void createUserEntry(Habbo habbo, Achievement achievement) { public static void createUserEntry(Habbo habbo, Achievement achievement) {
try { try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO users_achievements (user_id, achievement_name, progress) VALUES (?, ?, ?)")) {
SqlQueries.update( statement.setInt(1, habbo.getHabboInfo().getId());
"INSERT INTO users_achievements (user_id, achievement_name, progress) VALUES (?, ?, ?)", statement.setString(2, achievement.name);
habbo.getHabboInfo().getId(), achievement.name, 1); statement.setInt(3, 1);
} catch (SqlQueries.DataAccessException e) { statement.execute();
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e); LOGGER.error("Caught SQL exception", e);
} }
} }
public static void saveAchievements(Habbo habbo) { public static void saveAchievements(Habbo habbo) {
int userId = habbo.getHabboInfo().getId(); try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE users_achievements SET progress = ? WHERE achievement_name = ? AND user_id = ? LIMIT 1")) {
try { statement.setInt(3, habbo.getHabboInfo().getId());
SqlQueries.batchUpdate( for (Map.Entry<Achievement, Integer> map : habbo.getHabboStats().getAchievementProgress().entrySet()) {
"UPDATE users_achievements SET progress = ? WHERE achievement_name = ? AND user_id = ? LIMIT 1", statement.setInt(1, map.getValue());
habbo.getHabboStats().getAchievementProgress().entrySet(), statement.setString(2, map.getKey().name);
(ps, entry) -> { statement.addBatch();
ps.setInt(1, entry.getValue()); }
ps.setString(2, entry.getKey().name); statement.executeBatch();
ps.setInt(3, userId); } catch (SQLException e) {
});
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e); LOGGER.error("Caught SQL exception", e);
} }
} }
public static int getAchievementProgressForHabbo(int userId, Achievement achievement) { public static int getAchievementProgressForHabbo(int userId, Achievement achievement) {
try { if (achievement == null) {
return SqlQueries.queryOne(
"SELECT progress FROM users_achievements WHERE user_id = ? AND achievement_name = ? LIMIT 1",
rs -> rs.getInt("progress"),
userId, achievement.name).orElse(0);
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e);
return 0; return 0;
} }
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT progress FROM users_achievements WHERE user_id = ? AND achievement_name = ? LIMIT 1")) {
statement.setInt(1, userId);
statement.setString(2, achievement.name);
try (ResultSet set = statement.executeQuery()) {
if (set.next()) {
return set.getInt("progress");
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
return 0;
} }
public void reload() { public void reload() {
@@ -71,13 +71,23 @@ public class BotManager {
} }
public Bot createBot(THashMap<String, String> data, String type) { public Bot createBot(THashMap<String, String> data, String type) {
return this.createBot(data, type, 0);
}
public Bot createBot(THashMap<String, String> data, String type, int ownerId) {
if (ownerId <= 0) {
LOGGER.error("Cannot create bot of type '{}' without a valid owner user id.", type);
return null;
}
Bot bot = null; Bot bot = null;
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO bots (user_id, room_id, name, motto, figure, gender, type) VALUES (0, 0, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS)) { try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO bots (user_id, room_id, name, motto, figure, gender, type) VALUES (?, 0, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS)) {
statement.setString(1, data.get("name")); statement.setInt(1, ownerId);
statement.setString(2, data.get("motto")); statement.setString(2, data.get("name"));
statement.setString(3, data.get("figure")); statement.setString(3, data.get("motto"));
statement.setString(4, data.get("gender").toUpperCase()); statement.setString(4, data.get("figure"));
statement.setString(5, type); statement.setString(5, data.get("gender").toUpperCase());
statement.setString(6, type);
statement.execute(); statement.execute();
try (ResultSet set = statement.getGeneratedKeys()) { try (ResultSet set = statement.getGeneratedKeys()) {
if (set.next()) { if (set.next()) {
@@ -1058,7 +1058,7 @@ public class CatalogManager {
} }
} }
Bot bot = Emulator.getGameEnvironment().getBotManager().createBot(data, type); Bot bot = Emulator.getGameEnvironment().getBotManager().createBot(data, type, habbo.getHabboInfo().getId());
if (bot != null) { if (bot != null) {
bot.setOwnerId(habbo.getClient().getHabbo().getHabboInfo().getId()); bot.setOwnerId(habbo.getClient().getHabbo().getHabboInfo().getId());
@@ -1,23 +1,16 @@
package com.eu.habbo.habbohotel.gameclients; package com.eu.habbo.habbohotel.gameclients;
import com.eu.habbo.Emulator; import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserEffectComposer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
/**
* Manages a grace period for disconnected users. Instead of immediately
* disposing a Habbo when their WebSocket drops, the Habbo is held in
* a "ghost" state for a configurable number of seconds. If the same
* user reconnects (via SSO ticket) within the grace window, their
* existing Habbo object is resumed on the new connection keeping
* them in their room, preserving inventory state, etc.
*
* Config key: session.reconnect.grace.seconds (default: 30)
*/
public class SessionResumeManager { public class SessionResumeManager {
private static final Logger LOGGER = LoggerFactory.getLogger(SessionResumeManager.class); private static final Logger LOGGER = LoggerFactory.getLogger(SessionResumeManager.class);
@@ -37,12 +30,10 @@ public class SessionResumeManager {
return Emulator.getConfig().getInt("session.reconnect.grace.seconds", 30); return Emulator.getConfig().getInt("session.reconnect.grace.seconds", 30);
} }
/** public int getPausedEffectId() {
* Park a disconnected Habbo in ghost mode. Their room presence is return Emulator.getConfig().getInt("session.reconnect.effect.id", 170);
* preserved, but the old GameClient channel is closed. }
*
* @return true if the habbo was parked (grace period > 0), false if immediate dispose should happen
*/
public boolean parkHabbo(Habbo habbo, String ssoTicket) { public boolean parkHabbo(Habbo habbo, String ssoTicket) {
int graceSeconds = getGracePeriodSeconds(); int graceSeconds = getGracePeriodSeconds();
if (graceSeconds <= 0) { if (graceSeconds <= 0) {
@@ -51,7 +42,6 @@ public class SessionResumeManager {
int userId = habbo.getHabboInfo().getId(); int userId = habbo.getHabboInfo().getId();
// Cancel any existing ghost session for this user
GhostSession existing = ghostSessions.remove(userId); GhostSession existing = ghostSessions.remove(userId);
if (existing != null && existing.disposeFuture != null) { if (existing != null && existing.disposeFuture != null) {
existing.disposeFuture.cancel(false); existing.disposeFuture.cancel(false);
@@ -60,12 +50,18 @@ public class SessionResumeManager {
LOGGER.info("[SessionResume] Parking {} (id={}) for {}s grace period", LOGGER.info("[SessionResume] Parking {} (id={}) for {}s grace period",
habbo.getHabboInfo().getUsername(), userId, graceSeconds); habbo.getHabboInfo().getUsername(), userId, graceSeconds);
// Restore the SSO ticket so the client can reconnect with the same ticket
if (ssoTicket != null && !ssoTicket.isEmpty()) { if (ssoTicket != null && !ssoTicket.isEmpty()) {
restoreSsoTicket(userId, ssoTicket); restoreSsoTicket(userId, ssoTicket);
} }
// Schedule the final disconnect after the grace period int previousEffectId = 0;
int previousEffectEnd = 0;
RoomUnit unit = habbo.getRoomUnit();
if (unit != null) {
previousEffectId = unit.getEffectId();
previousEffectEnd = unit.getEffectEndTimestamp();
}
ScheduledFuture<?> future = Emulator.getThreading().run(() -> { ScheduledFuture<?> future = Emulator.getThreading().run(() -> {
GhostSession ghost = ghostSessions.remove(userId); GhostSession ghost = ghostSessions.remove(userId);
if (ghost != null) { if (ghost != null) {
@@ -75,22 +71,19 @@ public class SessionResumeManager {
} }
}, graceSeconds * 1000); }, graceSeconds * 1000);
ghostSessions.put(userId, new GhostSession(habbo, ssoTicket, future)); ghostSessions.put(userId, new GhostSession(habbo, ssoTicket, future, previousEffectId, previousEffectEnd));
applyPausedEffect(habbo);
return true; return true;
} }
/**
* Try to resume a ghost session for the given user ID.
*
* @return the parked Habbo if found within grace period, null otherwise
*/
public Habbo resumeSession(int userId) { public Habbo resumeSession(int userId) {
GhostSession ghost = ghostSessions.remove(userId); GhostSession ghost = ghostSessions.remove(userId);
if (ghost == null) { if (ghost == null) {
return null; return null;
} }
// Cancel the scheduled dispose
if (ghost.disposeFuture != null) { if (ghost.disposeFuture != null) {
ghost.disposeFuture.cancel(false); ghost.disposeFuture.cancel(false);
} }
@@ -98,19 +91,15 @@ public class SessionResumeManager {
LOGGER.info("[SessionResume] Resuming session for {} (id={})", LOGGER.info("[SessionResume] Resuming session for {} (id={})",
ghost.habbo.getHabboInfo().getUsername(), userId); ghost.habbo.getHabboInfo().getUsername(), userId);
restorePausedEffect(ghost);
return ghost.habbo; return ghost.habbo;
} }
/**
* Check if a user has a ghost session (is in grace period).
*/
public boolean hasGhostSession(int userId) { public boolean hasGhostSession(int userId) {
return ghostSessions.containsKey(userId); return ghostSessions.containsKey(userId);
} }
/**
* Immediately expire all ghost sessions (e.g. on emulator shutdown).
*/
public void disposeAll() { public void disposeAll() {
for (GhostSession ghost : ghostSessions.values()) { for (GhostSession ghost : ghostSessions.values()) {
if (ghost.disposeFuture != null) { if (ghost.disposeFuture != null) {
@@ -121,9 +110,6 @@ public class SessionResumeManager {
ghostSessions.clear(); ghostSessions.clear();
} }
/**
* Perform the actual full disconnect that normally happens in Habbo.disconnect().
*/
private void performFullDisconnect(Habbo habbo) { private void performFullDisconnect(Habbo habbo) {
try { try {
habbo.getHabboInfo().setOnline(false); habbo.getHabboInfo().setOnline(false);
@@ -132,7 +118,6 @@ public class SessionResumeManager {
LOGGER.error("[SessionResume] Error during deferred disconnect", e); LOGGER.error("[SessionResume] Error during deferred disconnect", e);
} }
// Clear the SSO ticket now that the grace period is truly over
clearSsoTicket(habbo.getHabboInfo().getId()); clearSsoTicket(habbo.getHabboInfo().getId());
} }
@@ -148,6 +133,38 @@ public class SessionResumeManager {
} }
} }
private void applyPausedEffect(Habbo habbo) {
int effectId = getPausedEffectId();
if (effectId <= 0) return;
try {
RoomUnit unit = habbo.getRoomUnit();
Room room = habbo.getHabboInfo() == null ? null : habbo.getHabboInfo().getCurrentRoom();
if (unit == null || room == null) return;
int endTimestamp = Emulator.getIntUnixTimestamp() + getGracePeriodSeconds() + 10;
unit.setEffectId(effectId, endTimestamp);
room.sendComposer(new RoomUserEffectComposer(unit).compose());
} catch (Exception e) {
LOGGER.error("[SessionResume] Failed to apply paused effect", e);
}
}
private void restorePausedEffect(GhostSession ghost) {
try {
Habbo habbo = ghost.habbo;
RoomUnit unit = habbo.getRoomUnit();
Room room = habbo.getHabboInfo() == null ? null : habbo.getHabboInfo().getCurrentRoom();
if (unit == null || room == null) return;
int pausedEffectId = getPausedEffectId();
if (unit.getEffectId() == pausedEffectId) {
unit.setEffectId(ghost.previousEffectId, ghost.previousEffectEnd);
room.sendComposer(new RoomUserEffectComposer(unit).compose());
}
} catch (Exception e) {
LOGGER.error("[SessionResume] Failed to restore previous effect", e);
}
}
private void clearSsoTicket(int userId) { private void clearSsoTicket(int userId) {
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 = ? LIMIT 1")) {
@@ -163,11 +180,16 @@ public class SessionResumeManager {
final Habbo habbo; final Habbo habbo;
final String ssoTicket; final String ssoTicket;
final ScheduledFuture<?> disposeFuture; final ScheduledFuture<?> disposeFuture;
final int previousEffectId;
final int previousEffectEnd;
GhostSession(Habbo habbo, String ssoTicket, ScheduledFuture<?> disposeFuture) { GhostSession(Habbo habbo, String ssoTicket, ScheduledFuture<?> disposeFuture,
int previousEffectId, int previousEffectEnd) {
this.habbo = habbo; this.habbo = habbo;
this.ssoTicket = ssoTicket; this.ssoTicket = ssoTicket;
this.disposeFuture = disposeFuture; this.disposeFuture = disposeFuture;
this.previousEffectId = previousEffectId;
this.previousEffectEnd = previousEffectEnd;
} }
} }
} }
@@ -1,5 +1,6 @@
package com.eu.habbo.habbohotel.items.interactions.wired.conditions; package com.eu.habbo.habbohotel.items.interactions.wired.conditions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredCondition; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredCondition;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
@@ -11,10 +12,12 @@ import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil; import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.ServerMessage;
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.List; import java.util.List;
import java.util.stream.Collectors;
public class WiredConditionSelectionQuantity extends InteractionWiredCondition { public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
private static final int COMPARISON_LESS_THAN = 0; private static final int COMPARISON_LESS_THAN = 0;
@@ -23,9 +26,16 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
private static final int SOURCE_GROUP_USERS = 0; private static final int SOURCE_GROUP_USERS = 0;
private static final int SOURCE_GROUP_FURNI = 1; private static final int SOURCE_GROUP_FURNI = 1;
private static final int SOURCE_USER_TRIGGER = 0;
private static final int SOURCE_USER_SIGNAL = 1;
private static final int SOURCE_USER_CLICKED = 2;
private static final int SOURCE_FURNI_TRIGGER = 3;
private static final int SOURCE_FURNI_PICKED = 4;
private static final int SOURCE_FURNI_SIGNAL = 5;
public static final WiredConditionType type = WiredConditionType.SLC_QUANTITY; public static final WiredConditionType type = WiredConditionType.SLC_QUANTITY;
private final THashSet<HabboItem> items;
private int comparison = COMPARISON_EQUAL; private int comparison = COMPARISON_EQUAL;
private int quantity = 0; private int quantity = 0;
private int sourceGroup = SOURCE_GROUP_USERS; private int sourceGroup = SOURCE_GROUP_USERS;
@@ -33,10 +43,12 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
public WiredConditionSelectionQuantity(ResultSet set, Item baseItem) throws SQLException { public WiredConditionSelectionQuantity(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem); super(set, baseItem);
this.items = new THashSet<>();
} }
public WiredConditionSelectionQuantity(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { public WiredConditionSelectionQuantity(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells); super(id, userId, item, extradata, limitedStack, limitedSells);
this.items = new THashSet<>();
} }
@Override @Override
@@ -46,9 +58,18 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
@Override @Override
public void serializeWiredData(ServerMessage message, Room room) { public void serializeWiredData(ServerMessage message, Room room) {
message.appendBoolean(false); this.refresh(room);
message.appendInt(5);
message.appendInt(0); boolean pickMode = this.sourceGroup == SOURCE_GROUP_FURNI && this.sourceType == WiredSourceUtil.SOURCE_SELECTED;
message.appendBoolean(pickMode);
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
message.appendInt(pickMode ? this.items.size() : 0);
if (pickMode) {
for (HabboItem item : this.items) {
message.appendInt(item.getId());
}
}
message.appendInt(this.getBaseItem().getSpriteId()); message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId()); message.appendInt(this.getId());
message.appendString(""); message.appendString("");
@@ -69,8 +90,36 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
this.comparison = (params.length > 0) ? this.normalizeComparison(params[0]) : COMPARISON_EQUAL; this.comparison = (params.length > 0) ? this.normalizeComparison(params[0]) : COMPARISON_EQUAL;
this.quantity = (params.length > 1) ? this.normalizeQuantity(params[1]) : 0; this.quantity = (params.length > 1) ? this.normalizeQuantity(params[1]) : 0;
this.sourceGroup = (params.length > 2) ? this.normalizeSourceGroup(params[2]) : SOURCE_GROUP_USERS; this.items.clear();
this.sourceType = (params.length > 3) ? this.normalizeSourceType(this.sourceGroup, params[3]) : WiredSourceUtil.SOURCE_TRIGGER;
if (params.length > 3) {
this.sourceGroup = this.normalizeSourceGroup(params[2]);
this.sourceType = this.normalizeSourceType(this.sourceGroup, params[3]);
} else {
this.setSourceSelection((params.length > 2) ? params[2] : SOURCE_USER_TRIGGER);
}
if (this.sourceGroup != SOURCE_GROUP_FURNI || this.sourceType != WiredSourceUtil.SOURCE_SELECTED) {
return true;
}
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
if (room == null) {
return false;
}
int count = settings.getFurniIds().length;
if (count > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) {
return false;
}
for (int itemId : settings.getFurniIds()) {
HabboItem item = room.getHabboItem(itemId);
if (item != null) {
this.items.add(item);
}
}
return true; return true;
} }
@@ -97,11 +146,14 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
@Override @Override
public String getWiredData() { public String getWiredData() {
this.refresh(Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()));
return WiredManager.getGson().toJson(new JsonData( return WiredManager.getGson().toJson(new JsonData(
this.comparison, this.comparison,
this.quantity, this.quantity,
this.sourceGroup, this.sourceGroup,
this.sourceType this.sourceType,
this.items.stream().map(HabboItem::getId).collect(Collectors.toList())
)); ));
} }
@@ -125,6 +177,7 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
this.quantity = this.normalizeQuantity(data.quantity); this.quantity = this.normalizeQuantity(data.quantity);
this.sourceGroup = this.normalizeSourceGroup(data.sourceGroup); this.sourceGroup = this.normalizeSourceGroup(data.sourceGroup);
this.sourceType = this.normalizeSourceType(this.sourceGroup, data.sourceType); this.sourceType = this.normalizeSourceType(this.sourceGroup, data.sourceType);
this.loadSelectedItems(data.itemIds, room);
return; return;
} }
@@ -150,6 +203,7 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
@Override @Override
public void onPickUp() { public void onPickUp() {
this.items.clear();
this.comparison = COMPARISON_EQUAL; this.comparison = COMPARISON_EQUAL;
this.quantity = 0; this.quantity = 0;
this.sourceGroup = SOURCE_GROUP_USERS; this.sourceGroup = SOURCE_GROUP_USERS;
@@ -158,7 +212,7 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
private int resolveCount(WiredContext ctx) { private int resolveCount(WiredContext ctx) {
if (this.sourceGroup == SOURCE_GROUP_FURNI) { if (this.sourceGroup == SOURCE_GROUP_FURNI) {
List<HabboItem> items = WiredSourceUtil.resolveItems(ctx, this.sourceType, null); List<HabboItem> items = WiredSourceUtil.resolveItems(ctx, this.sourceType, this.items);
return items.size(); return items.size();
} }
@@ -188,10 +242,18 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
private int normalizeSourceType(int group, int value) { private int normalizeSourceType(int group, int value) {
if (group == SOURCE_GROUP_USERS) { if (group == SOURCE_GROUP_USERS) {
return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER; switch (value) {
case WiredSourceUtil.SOURCE_CLICKED_USER:
case WiredSourceUtil.SOURCE_SIGNAL:
case WiredSourceUtil.SOURCE_SELECTOR:
return value;
default:
return WiredSourceUtil.SOURCE_TRIGGER;
}
} }
switch (value) { switch (value) {
case WiredSourceUtil.SOURCE_SELECTED:
case WiredSourceUtil.SOURCE_SELECTOR: case WiredSourceUtil.SOURCE_SELECTOR:
case WiredSourceUtil.SOURCE_SIGNAL: case WiredSourceUtil.SOURCE_SIGNAL:
case WiredSourceUtil.SOURCE_TRIGGER: case WiredSourceUtil.SOURCE_TRIGGER:
@@ -201,17 +263,104 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
} }
} }
private int getSourceSelection() {
if (this.sourceGroup == SOURCE_GROUP_FURNI) {
switch (this.sourceType) {
case WiredSourceUtil.SOURCE_SELECTED:
return SOURCE_FURNI_PICKED;
case WiredSourceUtil.SOURCE_SIGNAL:
return SOURCE_FURNI_SIGNAL;
default:
return SOURCE_FURNI_TRIGGER;
}
}
switch (this.sourceType) {
case WiredSourceUtil.SOURCE_CLICKED_USER:
return SOURCE_USER_CLICKED;
case WiredSourceUtil.SOURCE_SIGNAL:
return SOURCE_USER_SIGNAL;
default:
return SOURCE_USER_TRIGGER;
}
}
private void setSourceSelection(int value) {
switch (value) {
case SOURCE_USER_SIGNAL:
this.sourceGroup = SOURCE_GROUP_USERS;
this.sourceType = WiredSourceUtil.SOURCE_SIGNAL;
break;
case SOURCE_USER_CLICKED:
this.sourceGroup = SOURCE_GROUP_USERS;
this.sourceType = WiredSourceUtil.SOURCE_CLICKED_USER;
break;
case SOURCE_FURNI_TRIGGER:
this.sourceGroup = SOURCE_GROUP_FURNI;
this.sourceType = WiredSourceUtil.SOURCE_TRIGGER;
break;
case SOURCE_FURNI_PICKED:
this.sourceGroup = SOURCE_GROUP_FURNI;
this.sourceType = WiredSourceUtil.SOURCE_SELECTED;
break;
case SOURCE_FURNI_SIGNAL:
this.sourceGroup = SOURCE_GROUP_FURNI;
this.sourceType = WiredSourceUtil.SOURCE_SIGNAL;
break;
default:
this.sourceGroup = SOURCE_GROUP_USERS;
this.sourceType = WiredSourceUtil.SOURCE_TRIGGER;
break;
}
}
private void loadSelectedItems(List<Integer> itemIds, Room room) {
this.items.clear();
if (itemIds == null || room == null) {
return;
}
for (Integer itemId : itemIds) {
HabboItem item = room.getHabboItem(itemId);
if (item != null) {
this.items.add(item);
}
}
}
private void refresh(Room room) {
if (room == null || this.items.isEmpty()) {
return;
}
THashSet<HabboItem> itemsToRemove = new THashSet<>();
for (HabboItem item : this.items) {
if (item == null || room.getHabboItem(item.getId()) == null) {
itemsToRemove.add(item);
}
}
for (HabboItem item : itemsToRemove) {
this.items.remove(item);
}
}
static class JsonData { static class JsonData {
int comparison; int comparison;
int quantity; int quantity;
int sourceGroup; int sourceGroup;
int sourceType; int sourceType;
List<Integer> itemIds;
public JsonData(int comparison, int quantity, int sourceGroup, int sourceType) { public JsonData(int comparison, int quantity, int sourceGroup, int sourceType, List<Integer> itemIds) {
this.comparison = comparison; this.comparison = comparison;
this.quantity = quantity; this.quantity = quantity;
this.sourceGroup = sourceGroup; this.sourceGroup = sourceGroup;
this.sourceType = sourceType; this.sourceType = sourceType;
this.itemIds = itemIds;
} }
} }
} }
@@ -82,7 +82,7 @@ public class WiredEffectBotTalk extends InteractionWiredEffect {
this.setDelay(delay); this.setDelay(delay);
this.botName = data[0].substring(0, Math.min(data[0].length(), Emulator.getConfig().getInt("hotel.wired.message.max_length", 100))); this.botName = data[0].substring(0, Math.min(data[0].length(), Emulator.getConfig().getInt("hotel.wired.message.max_length", 100)));
this.message = data[1].substring(0, Math.min(data[1].length(), Emulator.getConfig().getInt("hotel.wired.message.max_length", 100))); this.message = data[1].substring(0, Math.min(data[1].length(), Emulator.getConfig().getInt("hotel.wired.bot.message.max_length", 100)));
this.mode = mode; this.mode = mode;
return true; return true;
@@ -105,7 +105,7 @@ public class WiredEffectBotTalkToHabbo extends InteractionWiredEffect {
throw new WiredSaveException("Delay too long"); throw new WiredSaveException("Delay too long");
this.botName = data[0].substring(0, Math.min(data[0].length(), Emulator.getConfig().getInt("hotel.wired.message.max_length", 100))); this.botName = data[0].substring(0, Math.min(data[0].length(), Emulator.getConfig().getInt("hotel.wired.message.max_length", 100)));
this.message = data[1].substring(0, Math.min(data[1].length(), Emulator.getConfig().getInt("hotel.wired.message.max_length", 100))); this.message = data[1].substring(0, Math.min(data[1].length(), Emulator.getConfig().getInt("hotel.wired.bot.message.max_length", 100)));
this.mode = mode; this.mode = mode;
this.setDelay(delay); this.setDelay(delay);
@@ -4,6 +4,7 @@ import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient; import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
import com.eu.habbo.habbohotel.items.interactions.games.InteractionGameTimer;
import com.eu.habbo.habbohotel.items.interactions.games.InteractionGameUpCounter; import com.eu.habbo.habbohotel.items.interactions.games.InteractionGameUpCounter;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.Room;
@@ -60,29 +61,74 @@ public class WiredEffectControlClock extends InteractionWiredEffect {
} }
for (HabboItem item : effectiveItems) { for (HabboItem item : effectiveItems) {
if (!(item instanceof InteractionGameUpCounter)) { if (!(item instanceof InteractionGameTimer)) {
continue; continue;
} }
InteractionGameUpCounter counter = (InteractionGameUpCounter) item; if (item instanceof InteractionGameUpCounter) {
this.controlUpCounter((InteractionGameUpCounter) item, room);
switch (this.action) { continue;
case ACTION_START:
counter.restartFromZero(room);
break;
case ACTION_STOP:
counter.stopCounter(room);
break;
case ACTION_RESET:
counter.resetCounter(room);
break;
case ACTION_PAUSE:
counter.pauseCounter(room);
break;
case ACTION_RESUME:
counter.resumeCounter(room);
break;
} }
this.controlGameTimer((InteractionGameTimer) item, room);
}
}
private void controlUpCounter(InteractionGameUpCounter counter, Room room) {
switch (this.action) {
case ACTION_START:
counter.restartFromZero(room);
break;
case ACTION_STOP:
counter.stopCounter(room);
break;
case ACTION_RESET:
counter.resetCounter(room);
break;
case ACTION_PAUSE:
counter.pauseCounter(room);
break;
case ACTION_RESUME:
counter.resumeCounter(room);
break;
}
}
private void controlGameTimer(InteractionGameTimer timer, Room room) {
switch (this.action) {
case ACTION_START:
timer.startTimer(room);
break;
case ACTION_STOP:
this.stopGameTimer(timer, room, false);
break;
case ACTION_RESET:
this.stopGameTimer(timer, room, true);
break;
case ACTION_PAUSE:
timer.pauseTimer(room);
break;
case ACTION_RESUME:
timer.resumeTimer(room);
break;
}
}
private void stopGameTimer(InteractionGameTimer timer, Room room, boolean resetTime) {
boolean wasActive = timer.isRunning() || timer.isPaused();
timer.endGame(room);
if (resetTime) {
timer.setTimeNow(timer.getBaseTime());
timer.setExtradata(timer.getTimeNow() + "\t" + timer.getBaseTime());
}
room.updateItem(timer);
timer.needsUpdate(true);
if (wasActive) {
WiredManager.triggerGameEnds(room);
} }
} }
@@ -206,7 +252,7 @@ public class WiredEffectControlClock extends InteractionWiredEffect {
throw new WiredSaveException(String.format("Item %s not found", itemId)); throw new WiredSaveException(String.format("Item %s not found", itemId));
} }
if (!(item instanceof InteractionGameUpCounter)) { if (!(item instanceof InteractionGameTimer)) {
throw new WiredSaveException("wiredfurni.error.require_counter_furni"); throw new WiredSaveException("wiredfurni.error.require_counter_furni");
} }
@@ -53,26 +53,37 @@ public class WiredEffectFurniToFurni extends InteractionWiredEffect {
return; return;
} }
HabboItem moveItem = this.resolveLastMoveItem(ctx); List<HabboItem> moveItems = this.resolveMoveItems(ctx);
HabboItem targetItem = this.resolveLastTargetItem(ctx); List<HabboItem> targetItems = this.resolveTargetItems(ctx);
if (moveItem == null || targetItem == null || moveItem.getId() == targetItem.getId()) { if (moveItems.isEmpty() || targetItems.isEmpty()) {
return; return;
} }
RoomTile targetTile = room.getLayout().getTile(targetItem.getX(), targetItem.getY()); int targetIndex = 0;
if (targetTile == null) { for (HabboItem moveItem : moveItems) {
return; if (moveItem == null) {
} continue;
}
FurnitureMovementError error = WiredMoveCarryHelper.moveFurni(room, this, moveItem, targetTile, moveItem.getRotation(), null, false, ctx); HabboItem targetItem = targetItems.get(targetIndex % targetItems.size());
if (error == FurnitureMovementError.NONE) { targetIndex++;
return;
}
error = WiredMoveCarryHelper.moveFurni(room, this, moveItem, targetTile, moveItem.getRotation(), targetItem.getZ(), null, false, ctx); if (targetItem == null || moveItem.getId() == targetItem.getId()) {
if (error == FurnitureMovementError.NONE) { continue;
return; }
RoomTile targetTile = room.getLayout().getTile(targetItem.getX(), targetItem.getY());
if (targetTile == null) {
continue;
}
FurnitureMovementError error = WiredMoveCarryHelper.moveFurni(room, this, moveItem, targetTile, moveItem.getRotation(), null, false, ctx);
if (error == FurnitureMovementError.NONE) {
continue;
}
WiredMoveCarryHelper.moveFurni(room, this, moveItem, targetTile, moveItem.getRotation(), targetItem.getZ(), null, false, ctx);
} }
} }
@@ -233,35 +244,23 @@ public class WiredEffectFurniToFurni extends InteractionWiredEffect {
return COOLDOWN_MOVEMENT; return COOLDOWN_MOVEMENT;
} }
private HabboItem resolveLastMoveItem(WiredContext ctx) { private List<HabboItem> resolveMoveItems(WiredContext ctx) {
return this.resolveLastItem(ctx, this.moveSource, this.moveItems); return this.resolveItems(ctx, this.moveSource, this.moveItems);
} }
private HabboItem resolveLastTargetItem(WiredContext ctx) { private List<HabboItem> resolveTargetItems(WiredContext ctx) {
int source = (this.targetSource == SOURCE_SECONDARY_SELECTED) ? WiredSourceUtil.SOURCE_SELECTED : this.targetSource; int source = (this.targetSource == SOURCE_SECONDARY_SELECTED) ? WiredSourceUtil.SOURCE_SELECTED : this.targetSource;
return this.resolveLastItem(ctx, source, this.targetItems); return this.resolveItems(ctx, source, this.targetItems);
} }
private HabboItem resolveLastItem(WiredContext ctx, int source, List<HabboItem> items) { private List<HabboItem> resolveItems(WiredContext ctx, int source, List<HabboItem> items) {
if (source == WiredSourceUtil.SOURCE_SELECTED) { if (source == WiredSourceUtil.SOURCE_SELECTED) {
this.validateItems(items); this.validateItems(items);
} }
List<HabboItem> resolvedItems = WiredSourceUtil.resolveItems(ctx, source, items); return WiredSourceUtil.resolveItems(ctx, source, items).stream()
.filter(item -> item != null && ctx.room().getHabboItem(item.getId()) != null)
if (resolvedItems.isEmpty()) { .collect(Collectors.toList());
return null;
}
for (int index = resolvedItems.size() - 1; index >= 0; index--) {
HabboItem item = resolvedItems.get(index);
if (item != null) {
return item;
}
}
return null;
} }
private List<HabboItem> parseItems(String data, Room room) throws WiredSaveException { private List<HabboItem> parseItems(String data, Room room) throws WiredSaveException {
@@ -33,7 +33,7 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
public static final WiredEffectType type = WiredEffectType.SEND_SIGNAL; public static final WiredEffectType type = WiredEffectType.SEND_SIGNAL;
private static final int MAX_SIGNAL_DEPTH = 10; public static int MAX_SIGNAL_DEPTH = 100;
private static final int ANTENNA_PICKED = 0; private static final int ANTENNA_PICKED = 0;
private static final int ANTENNA_TRIGGER = 1; private static final int ANTENNA_TRIGGER = 1;
@@ -166,7 +166,7 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
.signalChannel(signalChannel) .signalChannel(signalChannel)
.signalUserCount(signalUserCount) .signalUserCount(signalUserCount)
.signalFurniCount(sourceItem != null ? 1 : 0) .signalFurniCount(sourceItem != null ? 1 : 0)
.contextVariableScope(ctx.contextVariables()) .contextVariableScope(ctx.contextVariables().copy())
.triggeredByEffect(true); .triggeredByEffect(true);
if (actor != null) builder.actor(actor); if (actor != null) builder.actor(actor);
@@ -286,15 +286,6 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
} }
} }
if (room != null && room.getRoomSpecialTypes() != null) {
for (HabboItem receiver : newItems) {
int count = room.getRoomSpecialTypes().countSendersTargetingReceiver(receiver.getId(), this);
if (count >= RoomSpecialTypes.MAX_SENDERS_PER_RECEIVER) {
throw new WiredSaveException("Maximum of " + RoomSpecialTypes.MAX_SENDERS_PER_RECEIVER + " senders per receiver reached");
}
}
}
int delay = settings.getDelay(); int delay = settings.getDelay();
if (delay > Emulator.getConfig().getInt("hotel.wired.max_delay", 20)) { if (delay > Emulator.getConfig().getInt("hotel.wired.max_delay", 20)) {
throw new WiredSaveException("Delay too long"); throw new WiredSaveException("Delay too long");
@@ -34,6 +34,8 @@ public class WiredEffectWhisper extends InteractionWiredEffect {
private static final long DELIVERY_DEDUP_TTL_MS = 60_000L; private static final long DELIVERY_DEDUP_TTL_MS = 60_000L;
private static final int DELIVERY_DEDUP_CLEANUP_THRESHOLD = 512; private static final int DELIVERY_DEDUP_CLEANUP_THRESHOLD = 512;
private static final ConcurrentHashMap<String, Long> DELIVERY_DEDUP = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, Long> DELIVERY_DEDUP = new ConcurrentHashMap<>();
private static final int DEFAULT_SHOW_MESSAGE_MAX_LENGTH = 200;
private static final int DEFAULT_SHOW_MESSAGE_MAX_LINES = 8;
protected String message = ""; protected String message = "";
protected int userSource = WiredSourceUtil.SOURCE_TRIGGER; protected int userSource = WiredSourceUtil.SOURCE_TRIGGER;
@@ -96,9 +98,12 @@ public class WiredEffectWhisper extends InteractionWiredEffect {
if(gameClient.getHabbo() == null || !gameClient.getHabbo().hasPermission(Permission.ACC_SUPERWIRED)) { if(gameClient.getHabbo() == null || !gameClient.getHabbo().hasPermission(Permission.ACC_SUPERWIRED)) {
message = Emulator.getGameEnvironment().getWordFilter().filter(message, null); message = Emulator.getGameEnvironment().getWordFilter().filter(message, null);
message = message.substring(0, Math.min(message.length(), Emulator.getConfig().getInt("hotel.wired.message.max_length", 100)));
} }
int maxLength = Emulator.getConfig().getInt("hotel.wired.show_message.max_length", DEFAULT_SHOW_MESSAGE_MAX_LENGTH);
int maxLines = Emulator.getConfig().getInt("hotel.wired.show_message.max_lines", DEFAULT_SHOW_MESSAGE_MAX_LINES);
message = clampMessage(message, maxLength, maxLines);
int delay = settings.getDelay(); int delay = settings.getDelay();
if(delay > Emulator.getConfig().getInt("hotel.wired.max_delay", 20)) if(delay > Emulator.getConfig().getInt("hotel.wired.max_delay", 20))
@@ -109,6 +114,35 @@ public class WiredEffectWhisper extends InteractionWiredEffect {
return true; return true;
} }
private static String clampMessage(String value, int maxLength, int maxLines) {
if (value == null || value.isEmpty()) {
return "";
}
int safeMaxLength = Math.max(1, maxLength);
int safeMaxLines = Math.max(1, maxLines);
String normalized = value.replace("\r\n", "\n").replace('\r', '\n');
String[] lines = normalized.split("\n", -1);
StringBuilder builder = new StringBuilder();
int linesToWrite = Math.min(lines.length, safeMaxLines);
for (int index = 0; index < linesToWrite; index++) {
if (builder.length() > 0) {
builder.append('\n');
}
builder.append(lines[index]);
}
if (builder.length() > safeMaxLength) {
builder.setLength(safeMaxLength);
}
return builder.toString();
}
protected List<RoomUnit> resolveUsers(WiredContext ctx) { protected List<RoomUnit> resolveUsers(WiredContext ctx) {
return WiredSourceUtil.resolveUsers(ctx, this.userSource); return WiredSourceUtil.resolveUsers(ctx, this.userSource);
} }
@@ -212,7 +246,9 @@ public class WiredEffectWhisper extends InteractionWiredEffect {
} }
String msg = buildMessage(ctx, (sharedSourceHabbo != null) ? sharedSourceHabbo : habbo); String msg = buildMessage(ctx, (sharedSourceHabbo != null) ? sharedSourceHabbo : habbo);
habbo.getClient().sendResponse(new RoomUserWhisperComposer(new RoomChatMessage(msg, habbo, habbo, RoomChatMessageBubbles.getBubble(this.bubbleStyle)))); habbo.getClient().sendResponse(new RoomUserWhisperComposer(
new RoomChatMessage(msg, habbo.getRoomUnit(), RoomChatMessageBubbles.getBubble(this.bubbleStyle))
));
if (habbo.getRoomUnit().isIdle()) { if (habbo.getRoomUnit().isIdle()) {
habbo.getRoomUnit().getRoom().unIdle(habbo); habbo.getRoomUnit().getRoom().unIdle(habbo);
@@ -170,13 +170,15 @@ public class WiredExtraTextInputVariable extends InteractionWiredExtra {
} }
public Integer resolveCapturedValue(Room room, String rawValue) { public Integer resolveCapturedValue(Room room, String rawValue) {
String normalizedValue = rawValue != null ? rawValue.trim() : ""; String capturedValue = rawValue != null ? rawValue : "";
if (normalizedValue.isEmpty()) { String normalizedValue = capturedValue.trim();
return null;
}
if (this.getDisplayType(room) == DISPLAY_TEXTUAL) { if (this.getDisplayType(room) == DISPLAY_TEXTUAL) {
return WiredVariableTextConnectorSupport.toValue(room, this.variableItemId, normalizedValue); return WiredVariableTextConnectorSupport.toValue(room, this.variableItemId, capturedValue);
}
if (normalizedValue.isEmpty()) {
return null;
} }
try { try {
@@ -22,6 +22,7 @@ public class WiredExtraVariableTextConnector extends InteractionWiredExtra {
public static final int CODE = 79; public static final int CODE = 79;
public static final int MAX_MAPPING_LENGTH = 1000; public static final int MAX_MAPPING_LENGTH = 1000;
public static final int MAX_MAPPING_LINES = 30; public static final int MAX_MAPPING_LINES = 30;
private static final String PRESERVED_SPACE = "\u00A0";
private String mappingsText = ""; private String mappingsText = "";
private LinkedHashMap<Integer, String> mappings = new LinkedHashMap<>(); private LinkedHashMap<Integer, String> mappings = new LinkedHashMap<>();
@@ -123,8 +124,12 @@ public class WiredExtraVariableTextConnector extends InteractionWiredExtra {
return ""; return "";
} }
String mappedValue = this.mappings.get(value); if (this.mappings.containsKey(value)) {
return mappedValue != null ? mappedValue : String.valueOf(value); String mappedValue = this.mappings.get(value);
return mappedValue != null ? preserveSpaces(mappedValue) : "";
}
return String.valueOf(value);
} }
public Integer resolveValue(String text) { public Integer resolveValue(String text) {
@@ -132,17 +137,16 @@ public class WiredExtraVariableTextConnector extends InteractionWiredExtra {
return null; return null;
} }
String normalizedText = text.trim(); String normalizedText = normalizePreservedSpaces(text);
if (normalizedText.isEmpty()) {
return null;
}
for (Map.Entry<Integer, String> entry : this.mappings.entrySet()) { for (Map.Entry<Integer, String> entry : this.mappings.entrySet()) {
if (entry == null || entry.getKey() == null || entry.getValue() == null) { if (entry == null || entry.getKey() == null || entry.getValue() == null) {
continue; continue;
} }
if (entry.getValue().trim().equalsIgnoreCase(normalizedText)) { String normalizedMappingValue = normalizePreservedSpaces(entry.getValue());
if (normalizedMappingValue.equalsIgnoreCase(normalizedText)) {
return entry.getKey(); return entry.getKey();
} }
} }
@@ -195,8 +199,8 @@ public class WiredExtraVariableTextConnector extends InteractionWiredExtra {
continue; continue;
} }
String line = rawLine.trim(); String line = rawLine;
if (line.isEmpty()) { if (line.trim().isEmpty()) {
continue; continue;
} }
@@ -210,7 +214,7 @@ public class WiredExtraVariableTextConnector extends InteractionWiredExtra {
} }
String keyPart = line.substring(0, separatorIndex).trim(); String keyPart = line.substring(0, separatorIndex).trim();
String valuePart = line.substring(separatorIndex + 1).trim(); String valuePart = line.substring(separatorIndex + 1);
try { try {
result.put(Integer.parseInt(keyPart), valuePart); result.put(Integer.parseInt(keyPart), valuePart);
@@ -221,6 +225,14 @@ public class WiredExtraVariableTextConnector extends InteractionWiredExtra {
return result; return result;
} }
private static String preserveSpaces(String value) {
return value.replace(" ", PRESERVED_SPACE);
}
private static String normalizePreservedSpaces(String value) {
return value.replace(PRESERVED_SPACE, " ");
}
static class JsonData { static class JsonData {
String mappingsText; String mappingsText;
@@ -95,6 +95,11 @@ public class WiredEffectFurniAltitude extends InteractionWiredEffect {
return true; return true;
} }
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData( return WiredManager.getGson().toJson(new JsonData(
@@ -100,6 +100,11 @@ public class WiredEffectFurniArea extends InteractionWiredEffect {
return true; return true;
} }
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(rootX, rootY, areaWidth, areaHeight, filterExisting, invert, getDelay())); return WiredManager.getGson().toJson(new JsonData(rootX, rootY, areaWidth, areaHeight, filterExisting, invert, getDelay()));
@@ -155,6 +155,11 @@ public class WiredEffectFurniByType extends InteractionWiredEffect {
return true; return true;
} }
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
return WiredManager.getGson().toJson( return WiredManager.getGson().toJson(
@@ -38,6 +38,7 @@ public class WiredEffectFurniNeighborhood extends InteractionWiredEffect {
private static final int MAX_PICKED_FURNI = 20; private static final int MAX_PICKED_FURNI = 20;
private static final int MAX_TILE_OFFSETS = 64; private static final int MAX_TILE_OFFSETS = 64;
private static final int GRID_RANGE = 4;
private int sourceType = SOURCE_USER_TRIGGER; private int sourceType = SOURCE_USER_TRIGGER;
private boolean filterExisting = false; private boolean filterExisting = false;
@@ -69,8 +70,20 @@ public class WiredEffectFurniNeighborhood extends InteractionWiredEffect {
int totalRaw = 0; int totalRaw = 0;
int wiredSkipped = 0; int wiredSkipped = 0;
Set<HabboItem> result = new LinkedHashSet<>(); Set<HabboItem> result = new LinkedHashSet<>();
Set<HabboItem> neighborhoodItems = new LinkedHashSet<>();
for (int[] src : sourcePositions) { for (int[] src : sourcePositions) {
LOGGER.info("[FurniNeighborhood] Source: ({},{}), offsets: {}", src[0], src[1], tileOffsets.size()); LOGGER.info("[FurniNeighborhood] Source: ({},{}), offsets: {}", src[0], src[1], tileOffsets.size());
for (int[] offset : getFullGridOffsets()) {
int tx = src[0] + (offset[0] - this.targetOffsetX);
int ty = src[1] + (offset[1] - this.targetOffsetY);
for (HabboItem item : room.getItemsAt(tx, ty)) {
if (item != null && (includeWiredItems || !(item instanceof InteractionWired))) {
neighborhoodItems.add(item);
}
}
}
for (int[] offset : tileOffsets) { for (int[] offset : tileOffsets) {
int tx = src[0] + (offset[0] - this.targetOffsetX); int tx = src[0] + (offset[0] - this.targetOffsetX);
int ty = src[1] + (offset[1] - this.targetOffsetY); int ty = src[1] + (offset[1] - this.targetOffsetY);
@@ -91,7 +104,7 @@ public class WiredEffectFurniNeighborhood extends InteractionWiredEffect {
} }
LOGGER.info("[FurniNeighborhood] Raw={}, wiredSkipped={}, kept={}", totalRaw, wiredSkipped, result.size()); LOGGER.info("[FurniNeighborhood] Raw={}, wiredSkipped={}, kept={}", totalRaw, wiredSkipped, result.size());
result = this.applySelectorModifiers(result, this.getSelectableFloorItems(room, ctx), ctx.targets().items(), filterExisting, invert); result = this.applyNeighborhoodModifiers(result, neighborhoodItems, ctx.targets().items());
// Always set the selector result even if empty. // Always set the selector result even if empty.
// An empty result means no items matched the neighborhood, so downstream // An empty result means no items matched the neighborhood, so downstream
@@ -100,15 +113,51 @@ public class WiredEffectFurniNeighborhood extends InteractionWiredEffect {
LOGGER.info("[FurniNeighborhood] Set {} items as targets", result.size()); LOGGER.info("[FurniNeighborhood] Set {} items as targets", result.size());
} }
private List<int[]> getFullGridOffsets() {
List<int[]> offsets = new ArrayList<>();
for (int y = -GRID_RANGE; y <= GRID_RANGE; y++) {
for (int x = -GRID_RANGE; x <= GRID_RANGE; x++) {
offsets.add(new int[]{ x, y });
}
}
return offsets;
}
private LinkedHashSet<HabboItem> applyNeighborhoodModifiers(Set<HabboItem> matchedTargets,
Set<HabboItem> neighborhoodTargets,
Collection<HabboItem> existingTargets) {
LinkedHashSet<HabboItem> matched = new LinkedHashSet<>(matchedTargets);
if (this.invert) {
LinkedHashSet<HabboItem> base = new LinkedHashSet<>(neighborhoodTargets);
base.removeAll(matched);
if (this.filterExisting) {
base.retainAll(this.toLinkedHashSet(existingTargets));
}
return base;
}
if (this.filterExisting) {
matched.retainAll(this.toLinkedHashSet(existingTargets));
}
return matched;
}
private List<int[]> resolveSourcePositions(WiredContext ctx, Room room) { private List<int[]> resolveSourcePositions(WiredContext ctx, Room room) {
switch (sourceType) { switch (sourceType) {
case SOURCE_USER_TRIGGER: { case SOURCE_USER_TRIGGER: {
if (ctx.tile().isPresent()) { Optional<RoomUnit> actor = ctx.actor();
return Collections.singletonList(new int[]{ ctx.tile().get().x, ctx.tile().get().y }); if (actor.isPresent()) {
return Collections.singletonList(new int[]{ actor.get().getX(), actor.get().getY() });
} }
return ctx.actor() return ctx.tile()
.map(actor -> Collections.singletonList(new int[]{ actor.getX(), actor.getY() })) .map(tile -> Collections.singletonList(new int[]{ tile.x, tile.y }))
.orElse(Collections.emptyList()); .orElse(Collections.emptyList());
} }
case SOURCE_USER_SIGNAL: { case SOURCE_USER_SIGNAL: {
@@ -260,6 +309,16 @@ public class WiredEffectFurniNeighborhood extends InteractionWiredEffect {
return true; return true;
} }
@Override
public boolean hasRequiredSelectorTargets(WiredContext ctx) {
return ctx != null && ctx.targets().hasItems();
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
return WiredManager.getGson().toJson( return WiredManager.getGson().toJson(
@@ -128,6 +128,11 @@ public class WiredEffectFurniOnFurni extends InteractionWiredEffect {
return true; return true;
} }
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
this.refresh(Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId())); this.refresh(Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()));
@@ -86,6 +86,11 @@ public class WiredEffectFurniPicks extends InteractionWiredEffect {
return true; return true;
} }
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData( return WiredManager.getGson().toJson(new JsonData(
@@ -77,6 +77,11 @@ public class WiredEffectFurniSignal extends InteractionWiredEffect {
return true; return true;
} }
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(this.filterExisting, this.invert, this.getDelay())); return WiredManager.getGson().toJson(new JsonData(this.filterExisting, this.invert, this.getDelay()));
@@ -86,6 +86,11 @@ public class WiredEffectUsersArea extends InteractionWiredEffect {
return true; return true;
} }
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(rootX, rootY, areaWidth, areaHeight, filterExisting, invert, getDelay())); return WiredManager.getGson().toJson(new JsonData(rootX, rootY, areaWidth, areaHeight, filterExisting, invert, getDelay()));
@@ -92,6 +92,11 @@ public class WiredEffectUsersByAction extends InteractionWiredEffect {
return true; return true;
} }
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData( return WiredManager.getGson().toJson(new JsonData(
@@ -90,6 +90,11 @@ public class WiredEffectUsersByName extends InteractionWiredEffect {
return true; return true;
} }
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(this.namesText, this.filterExisting, this.invert, this.getDelay())); return WiredManager.getGson().toJson(new JsonData(this.namesText, this.filterExisting, this.invert, this.getDelay()));
@@ -76,6 +76,11 @@ public class WiredEffectUsersByType extends InteractionWiredEffect {
return true; return true;
} }
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(this.entityType, this.filterExisting, this.invert, this.getDelay())); return WiredManager.getGson().toJson(new JsonData(this.entityType, this.filterExisting, this.invert, this.getDelay()));
@@ -90,6 +90,11 @@ public class WiredEffectUsersGroup extends InteractionWiredEffect {
return true; return true;
} }
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(this.groupType, this.selectedGroupId, this.filterExisting, this.invert, this.getDelay())); return WiredManager.getGson().toJson(new JsonData(this.groupType, this.selectedGroupId, this.filterExisting, this.invert, this.getDelay()));
@@ -73,6 +73,11 @@ public class WiredEffectUsersHandItem extends InteractionWiredEffect {
return true; return true;
} }
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(this.handItemId, this.filterExisting, this.invert, this.getDelay())); return WiredManager.getGson().toJson(new JsonData(this.handItemId, this.filterExisting, this.invert, this.getDelay()));
@@ -38,6 +38,7 @@ public class WiredEffectUsersNeighborhood extends InteractionWiredEffect {
private static final int MAX_PICKED_FURNI = 20; private static final int MAX_PICKED_FURNI = 20;
private static final int MAX_TILE_OFFSETS = 64; private static final int MAX_TILE_OFFSETS = 64;
private static final int GRID_RANGE = 4;
private int sourceType = SOURCE_USER_TRIGGER; private int sourceType = SOURCE_USER_TRIGGER;
private boolean filterExisting = false; private boolean filterExisting = false;
@@ -87,11 +88,25 @@ public class WiredEffectUsersNeighborhood extends InteractionWiredEffect {
LOGGER.debug("[Neighborhood] Target tiles: {}", targetTiles); LOGGER.debug("[Neighborhood] Target tiles: {}", targetTiles);
Set<String> neighborhoodTiles = new HashSet<>();
for (int[] src : sourcePositions) {
for (int[] offset : getFullGridOffsets()) {
int tx = src[0] + (offset[0] - this.targetOffsetX);
int ty = src[1] + (offset[1] - this.targetOffsetY);
neighborhoodTiles.add(tx + "," + ty);
}
}
List<RoomUnit> result = new ArrayList<>(); List<RoomUnit> result = new ArrayList<>();
List<RoomUnit> neighborhoodUsers = new ArrayList<>();
for (RoomUnit unit : room.getRoomUnits()) { for (RoomUnit unit : room.getRoomUnits()) {
String pos = unit.getX() + "," + unit.getY(); String pos = unit.getX() + "," + unit.getY();
boolean onTile = targetTiles.contains(pos); boolean onTile = targetTiles.contains(pos);
if (neighborhoodTiles.contains(pos)) {
neighborhoodUsers.add(unit);
}
LOGGER.debug("[Neighborhood] Unit id={} type={} pos={} onTile={}", unit.getId(), unit.getRoomUnitType(), pos, onTile); LOGGER.debug("[Neighborhood] Unit id={} type={} pos={} onTile={}", unit.getId(), unit.getRoomUnitType(), pos, onTile);
if (onTile) { if (onTile) {
@@ -99,7 +114,7 @@ public class WiredEffectUsersNeighborhood extends InteractionWiredEffect {
} }
} }
result = new ArrayList<>(this.applySelectorModifiers(result, room.getRoomUnits(), ctx.targets().users(), filterExisting, invert)); result = new ArrayList<>(this.applyNeighborhoodModifiers(result, neighborhoodUsers, ctx.targets().users()));
LOGGER.debug("[Neighborhood] Result: {} users selected", result.size()); LOGGER.debug("[Neighborhood] Result: {} users selected", result.size());
@@ -110,15 +125,51 @@ public class WiredEffectUsersNeighborhood extends InteractionWiredEffect {
ctx.targets().setUsers(result); ctx.targets().setUsers(result);
} }
private List<int[]> getFullGridOffsets() {
List<int[]> offsets = new ArrayList<>();
for (int y = -GRID_RANGE; y <= GRID_RANGE; y++) {
for (int x = -GRID_RANGE; x <= GRID_RANGE; x++) {
offsets.add(new int[]{ x, y });
}
}
return offsets;
}
private LinkedHashSet<RoomUnit> applyNeighborhoodModifiers(Collection<RoomUnit> matchedTargets,
Collection<RoomUnit> neighborhoodTargets,
Collection<RoomUnit> existingTargets) {
LinkedHashSet<RoomUnit> matched = new LinkedHashSet<>(matchedTargets);
if (this.invert) {
LinkedHashSet<RoomUnit> base = new LinkedHashSet<>(neighborhoodTargets);
base.removeAll(matched);
if (this.filterExisting) {
base.retainAll(this.toLinkedHashSet(existingTargets));
}
return base;
}
if (this.filterExisting) {
matched.retainAll(this.toLinkedHashSet(existingTargets));
}
return matched;
}
private List<int[]> resolveSourcePositions(WiredContext ctx, Room room) { private List<int[]> resolveSourcePositions(WiredContext ctx, Room room) {
switch (sourceType) { switch (sourceType) {
case SOURCE_USER_TRIGGER: { case SOURCE_USER_TRIGGER: {
if (ctx.tile().isPresent()) { Optional<RoomUnit> actor = ctx.actor();
return Collections.singletonList(new int[]{ ctx.tile().get().x, ctx.tile().get().y }); if (actor.isPresent()) {
return Collections.singletonList(new int[]{ actor.get().getX(), actor.get().getY() });
} }
return ctx.actor() return ctx.tile()
.map(actor -> Collections.singletonList(new int[]{ actor.getX(), actor.getY() })) .map(tile -> Collections.singletonList(new int[]{ tile.x, tile.y }))
.orElse(Collections.emptyList()); .orElse(Collections.emptyList());
} }
case SOURCE_USER_SIGNAL: { case SOURCE_USER_SIGNAL: {
@@ -262,6 +313,16 @@ public class WiredEffectUsersNeighborhood extends InteractionWiredEffect {
return true; return true;
} }
@Override
public boolean hasRequiredSelectorTargets(WiredContext ctx) {
return ctx != null && ctx.targets().hasUsers();
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
return WiredManager.getGson().toJson( return WiredManager.getGson().toJson(
@@ -115,6 +115,11 @@ public class WiredEffectUsersOnFurni extends InteractionWiredEffect {
return true; return true;
} }
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
this.refresh(Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId())); this.refresh(Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()));
@@ -71,6 +71,11 @@ public class WiredEffectUsersSignal extends InteractionWiredEffect {
return true; return true;
} }
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(this.filterExisting, this.invert, this.getDelay())); return WiredManager.getGson().toJson(new JsonData(this.filterExisting, this.invert, this.getDelay()));
@@ -76,6 +76,11 @@ public class WiredEffectUsersTeam extends InteractionWiredEffect {
return true; return true;
} }
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(this.teamType, this.filterExisting, this.invert, this.getDelay())); return WiredManager.getGson().toJson(new JsonData(this.teamType, this.filterExisting, this.invert, this.getDelay()));
@@ -187,6 +187,11 @@ public abstract class WiredEffectVariableSelectorBase extends InteractionWiredEf
return true; return true;
} }
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override @Override
public String getWiredData() { public String getWiredData() {
this.refreshReferenceItems(); this.refreshReferenceItems();
@@ -219,6 +219,10 @@ public class Messenger {
} catch (SQLException e) { } catch (SQLException e) {
LOGGER.error("Caught SQL exception", e); LOGGER.error("Caught SQL exception", e);
} }
if (habbo.hasPermission(StaffChatBuddy.PERMISSION_KEY)) {
this.friends.putIfAbsent(StaffChatBuddy.BUDDY_ID, new StaffChatBuddy(habbo.getHabboInfo().getId()));
}
} }
public MessengerBuddy loadFriend(Habbo habbo, int userId) { public MessengerBuddy loadFriend(Habbo habbo, int userId) {
@@ -0,0 +1,57 @@
package com.eu.habbo.habbohotel.messenger;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.commands.CommandHandler;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboGender;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.friends.FriendChatMessageComposer;
public class StaffChatBuddy extends MessengerBuddy {
public static final int BUDDY_ID = -1;
public static final String PERMISSION_KEY = "acc_staff_chat";
public static final String DISPLAY_NAME = "Staff Chat";
public static final String DEFAULT_LOOK = "ADM";
public StaffChatBuddy(int userOne) {
super(BUDDY_ID, DISPLAY_NAME, DEFAULT_LOOK, (short) 0, userOne);
this.setOnline(true);
}
@Override
public void onMessageReceived(Habbo from, String message) {
if (from == null || message == null || message.isEmpty()) return;
// Re-check permission so a staff member who was demoted mid-session
// can no longer broadcast to the staff channel.
if (!from.hasPermission(PERMISSION_KEY)) return;
if (message.charAt(0) == ':') {
CommandHandler.handleCommand(from.getClient(), message);
return;
}
Message chatMessage = new Message(from.getHabboInfo().getId(), BUDDY_ID, message);
Emulator.getGameServer().getGameClientManager().sendBroadcastResponse(
new FriendChatMessageComposer(chatMessage, BUDDY_ID, from.getHabboInfo().getId()).compose(),
PERMISSION_KEY,
from.getClient());
}
@Override
public void serialize(ServerMessage message) {
message.appendInt(this.getId());
message.appendString(this.getUsername());
message.appendInt(this.getGender().equals(HabboGender.M) ? 0 : 1);
message.appendBoolean(true); // online
message.appendBoolean(false); // not in room
message.appendString(this.getLook());
message.appendInt(0); // category
message.appendString(""); // motto
message.appendString(""); // last seen
message.appendString(""); // realname
message.appendBoolean(true); // offline messaging supported
message.appendBoolean(false);
message.appendBoolean(false);
message.appendShort(0); // relation
}
}
@@ -370,8 +370,14 @@ public class PetManager {
} else { } else {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) { try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) {
LOGGER.error("Missing petdata for type {}. Adding this to the database...", type); LOGGER.error("Missing petdata for type {}. Adding this to the database...", type);
try (PreparedStatement statement = connection.prepareStatement("INSERT INTO pet_actions (pet_type) VALUES (?)")) { try (PreparedStatement statement = connection.prepareStatement("INSERT INTO pet_actions (pet_type, pet_name, offspring_type, happy_actions, tired_actions, random_actions, can_swim) VALUES (?, ?, ?, ?, ?, ?, ?)")) {
statement.setInt(1, type); statement.setInt(1, type);
statement.setString(2, getFallbackPetName(type));
statement.setInt(3, getFallbackOffspringType(type));
statement.setString(4, "");
statement.setString(5, "");
statement.setString(6, "");
statement.setString(7, "0");
statement.execute(); statement.execute();
} }
@@ -411,6 +417,42 @@ public class PetManager {
return this.petData.values(); return this.petData.values();
} }
private static String getFallbackPetName(int type) {
switch (type) {
case 0:
return "Dog";
case 1:
return "Cat";
case 2:
return "Crocodile";
case 3:
return "Terrier";
case 4:
return "Bear";
case 5:
return "Pig";
default:
return "pet_type_" + type;
}
}
private static int getFallbackOffspringType(int type) {
switch (type) {
case 0:
return 29;
case 1:
return 28;
case 3:
return 25;
case 4:
return 24;
case 5:
return 30;
default:
return -1;
}
}
public Pet createPet(Item item, String name, String race, String color, GameClient client) { public Pet createPet(Item item, String name, String race, String color, GameClient client) {
int type = Integer.parseInt(item.getName().toLowerCase().replace("a0 pet", "")); int type = Integer.parseInt(item.getName().toLowerCase().replace("a0 pet", ""));
@@ -1177,7 +1177,7 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
if (this.needsUpdate) { if (this.needsUpdate) {
try (Connection connection = Emulator.getDatabase().getDataSource() try (Connection connection = Emulator.getDatabase().getDataSource()
.getConnection(); PreparedStatement statement = connection.prepareStatement( .getConnection(); PreparedStatement statement = connection.prepareStatement(
"UPDATE rooms SET name = ?, description = ?, password = ?, state = ?, users_max = ?, category = ?, score = ?, paper_floor = ?, paper_wall = ?, paper_landscape = ?, thickness_wall = ?, wall_height = ?, thickness_floor = ?, moodlight_data = ?, tags = ?, allow_other_pets = ?, allow_other_pets_eat = ?, allow_walkthrough = ?, allow_hidewall = ?, chat_mode = ?, chat_weight = ?, chat_speed = ?, chat_hearing_distance = ?, chat_protection =?, who_can_mute = ?, who_can_kick = ?, who_can_ban = ?, poll_id = ?, guild_id = ?, roller_speed = ?, override_model = ?, is_staff_picked = ?, promoted = ?, trade_mode = ?, move_diagonally = ?, owner_id = ?, owner_name = ?, jukebox_active = ?, hidewired = ?, allow_underpass = ?, youtube_enabled = ? WHERE id = ?")) { "UPDATE rooms SET name = ?, description = ?, password = ?, state = ?, users_max = ?, category = ?, score = ?, paper_floor = ?, paper_wall = ?, paper_landscape = ?, thickness_wall = ?, wall_height = ?, thickness_floor = ?, moodlight_data = ?, tags = ?, allow_other_pets = ?, allow_other_pets_eat = ?, allow_walkthrough = ?, allow_hidewall = ?, chat_mode = ?, chat_weight = ?, chat_speed = ?, chat_hearing_distance = ?, chat_protection =?, who_can_mute = ?, who_can_kick = ?, who_can_ban = ?, poll_id = ?, guild_id = ?, roller_speed = ?, override_model = ?, is_staff_picked = ?, promoted = ?, trade_mode = ?, move_diagonally = ?, owner_id = ?, owner_name = ?, jukebox_active = ?, hidewired = ?, allow_underpass = ?, youtube_enabled = ?, builders_club_trial_locked = ?, builders_club_original_state = ? WHERE id = ?")) {
statement.setString(1, this.name); statement.setString(1, this.name);
statement.setString(2, this.description); statement.setString(2, this.description);
statement.setString(3, this.password); statement.setString(3, this.password);
@@ -1228,7 +1228,9 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
statement.setString(39, this.hideWired ? "1" : "0"); statement.setString(39, this.hideWired ? "1" : "0");
statement.setString(40, this.allowUnderpass ? "1" : "0"); statement.setString(40, this.allowUnderpass ? "1" : "0");
statement.setString(41, this.youtubeEnabled ? "1" : "0"); statement.setString(41, this.youtubeEnabled ? "1" : "0");
statement.setInt(42, this.id); statement.setString(42, this.buildersClubTrialLocked ? "1" : "0");
statement.setString(43, (this.buildersClubOriginalState != null ? this.buildersClubOriginalState : RoomState.OPEN).name().toLowerCase());
statement.setInt(44, this.id);
statement.executeUpdate(); statement.executeUpdate();
this.needsUpdate = false; this.needsUpdate = false;
} catch (SQLException e) { } catch (SQLException e) {
@@ -4,6 +4,7 @@ import com.eu.habbo.Emulator;
import com.eu.habbo.core.DatabaseLoggable; import com.eu.habbo.core.DatabaseLoggable;
import com.eu.habbo.habbohotel.permissions.Permission; 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.UserCustomizationData;
import com.eu.habbo.messages.ISerialize; import com.eu.habbo.messages.ISerialize;
import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.Incoming; import com.eu.habbo.messages.incoming.Incoming;
@@ -204,23 +205,14 @@ public class RoomChatMessage implements Runnable, ISerialize, DatabaseLoggable {
message.appendInt(this.getMessage().length()); message.appendInt(this.getMessage().length());
// Custom prefix data // Custom prefix data
String prefixText = ""; UserCustomizationData customizationData = (this.habbo != null) ? UserCustomizationData.fromHabbo(this.habbo) : UserCustomizationData.empty();
String prefixColor = ""; message.appendString(customizationData.prefixText);
String prefixIcon = ""; message.appendString(customizationData.prefixColor);
String prefixEffect = ""; message.appendString(customizationData.prefixIcon);
if (this.habbo != null && this.habbo.getInventory() != null && this.habbo.getInventory().getPrefixesComponent() != null) { message.appendString(customizationData.prefixEffect);
com.eu.habbo.habbohotel.users.UserPrefix activePrefix = this.habbo.getInventory().getPrefixesComponent().getActivePrefix(); message.appendString(customizationData.prefixFont);
if (activePrefix != null) { message.appendString(customizationData.nickIcon);
prefixText = activePrefix.getText(); message.appendString(customizationData.displayOrder);
prefixColor = activePrefix.getColor();
prefixIcon = activePrefix.getIcon();
prefixEffect = activePrefix.getEffect();
}
}
message.appendString(prefixText);
message.appendString(prefixColor);
message.appendString(prefixIcon);
message.appendString(prefixEffect);
} catch (Exception e) { } catch (Exception e) {
LOGGER.error("Caught exception", e); LOGGER.error("Caught exception", e);
} }
@@ -15,25 +15,19 @@ import com.eu.habbo.habbohotel.items.interactions.games.tag.InteractionTagField;
import com.eu.habbo.habbohotel.items.interactions.games.tag.InteractionTagPole; import com.eu.habbo.habbohotel.items.interactions.games.tag.InteractionTagPole;
import com.eu.habbo.habbohotel.items.interactions.pets.*; import com.eu.habbo.habbohotel.items.interactions.pets.*;
import com.eu.habbo.habbohotel.items.interactions.wired.effects.WiredEffectSendSignal; import com.eu.habbo.habbohotel.items.interactions.wired.effects.WiredEffectSendSignal;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredBlob; import com.eu.habbo.habbohotel.items.interactions.wired.extra.*;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFurniVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRoomVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraUserVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableEcho;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableReference;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableTextConnector;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraContextVariable;
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
import com.eu.habbo.habbohotel.items.interactions.wired.triggers.WiredTriggerReceiveSignal; import com.eu.habbo.habbohotel.items.interactions.wired.triggers.WiredTriggerReceiveSignal;
import com.eu.habbo.habbohotel.permissions.Permission; 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.HabboInfo; import com.eu.habbo.habbohotel.users.HabboInfo;
import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.habbohotel.users.HabboManager; import com.eu.habbo.habbohotel.users.HabboManager;
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.habbohotel.wired.core.WiredMovementPhysics; import com.eu.habbo.habbohotel.wired.core.WiredMovementPhysics;
import com.eu.habbo.habbohotel.wired.tick.WiredTickable; import com.eu.habbo.habbohotel.wired.tick.WiredTickable;
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.rooms.items.*; import com.eu.habbo.messages.outgoing.rooms.items.*;
import com.eu.habbo.plugin.Event; import com.eu.habbo.plugin.Event;
import com.eu.habbo.plugin.events.furniture.*; import com.eu.habbo.plugin.events.furniture.*;
@@ -94,7 +88,7 @@ public class RoomItemManager {
} }
try (PreparedStatement statement = connection.prepareStatement( try (PreparedStatement statement = connection.prepareStatement(
"SELECT * FROM items WHERE room_id = ?")) { "SELECT * FROM items WHERE room_id = ?")) {
statement.setInt(1, this.room.getId()); statement.setInt(1, this.room.getId());
try (ResultSet set = statement.executeQuery()) { try (ResultSet set = statement.executeQuery()) {
while (set.next()) { while (set.next()) {
@@ -107,7 +101,7 @@ public class RoomItemManager {
if (this.itemCount() > Room.MAXIMUM_FURNI) { if (this.itemCount() > Room.MAXIMUM_FURNI) {
LOGGER.error("Room ID: {} has exceeded the furniture limit ({} > {}).", LOGGER.error("Room ID: {} has exceeded the furniture limit ({} > {}).",
this.room.getId(), this.itemCount(), Room.MAXIMUM_FURNI); this.room.getId(), this.itemCount(), Room.MAXIMUM_FURNI);
} }
} }
@@ -116,7 +110,7 @@ public class RoomItemManager {
*/ */
public void loadWiredData(Connection connection) { public void loadWiredData(Connection connection) {
try (PreparedStatement statement = connection.prepareStatement( try (PreparedStatement statement = connection.prepareStatement(
"SELECT id, wired_data FROM items WHERE room_id = ? AND wired_data<>''")) { "SELECT id, wired_data FROM items WHERE room_id = ? AND wired_data<>''")) {
statement.setInt(1, this.room.getId()); statement.setInt(1, this.room.getId());
try (ResultSet set = statement.executeQuery()) { try (ResultSet set = statement.executeQuery()) {
@@ -274,7 +268,7 @@ public class RoomItemManager {
} }
if (iterator.value().getBaseItem().getInteractionType().getType() if (iterator.value().getBaseItem().getInteractionType().getType()
== InteractionPostIt.class) { == InteractionPostIt.class) {
items.add(iterator.value()); items.add(iterator.value());
} }
} }
@@ -359,7 +353,7 @@ public class RoomItemManager {
} }
if (!(tile.x >= item.getX() && tile.x <= item.getX() + width - 1 && tile.y >= item.getY() if (!(tile.x >= item.getX() && tile.x <= item.getX() + width - 1 && tile.y >= item.getY()
&& tile.y <= item.getY() + length - 1)) { && tile.y <= item.getY() + length - 1)) {
continue; continue;
} }
@@ -447,7 +441,7 @@ public class RoomItemManager {
} }
if (highestItem != null && highestItem.getZ() + Item.getCurrentHeight(highestItem) if (highestItem != null && highestItem.getZ() + Item.getCurrentHeight(highestItem)
> item.getZ() + Item.getCurrentHeight(item)) { > item.getZ() + Item.getCurrentHeight(item)) {
continue; continue;
} }
@@ -516,7 +510,7 @@ public class RoomItemManager {
} }
if (highestItem != null && highestItem.getZ() + Item.getCurrentHeight(highestItem) if (highestItem != null && highestItem.getZ() + Item.getCurrentHeight(highestItem)
> item.getZ() + Item.getCurrentHeight(item)) { > item.getZ() + Item.getCurrentHeight(item)) {
continue; continue;
} }
@@ -598,7 +592,7 @@ public class RoomItemManager {
} }
if (lowestChair != null && lowestChair.getZ() + Item.getCurrentHeight(lowestChair) if (lowestChair != null && lowestChair.getZ() + Item.getCurrentHeight(lowestChair)
> item.getZ() + Item.getCurrentHeight(item)) { > item.getZ() + Item.getCurrentHeight(item)) {
continue; continue;
} }
@@ -647,7 +641,7 @@ public class RoomItemManager {
this.furniOwnerNames.put(item.getUserId(), habbo.getUsername()); this.furniOwnerNames.put(item.getUserId(), habbo.getUsername());
} else { } else {
LOGGER.error("Failed to find username for item (ID: {}, UserID: {})", LOGGER.error("Failed to find username for item (ID: {}, UserID: {})",
item.getId(), item.getUserId()); item.getId(), item.getUserId());
} }
} }
} }
@@ -714,25 +708,25 @@ public class RoomItemManager {
} else if (item instanceof InteractionPetTree) { } else if (item instanceof InteractionPetTree) {
specialTypes.addPetTree((InteractionPetTree) item); specialTypes.addPetTree((InteractionPetTree) item);
} else if (item instanceof InteractionMoodLight || } else if (item instanceof InteractionMoodLight ||
item instanceof InteractionPyramid || item instanceof InteractionPyramid ||
item instanceof InteractionMusicDisc || item instanceof InteractionMusicDisc ||
item instanceof InteractionBattleBanzaiSphere || item instanceof InteractionBattleBanzaiSphere ||
item instanceof InteractionTalkingFurniture || item instanceof InteractionTalkingFurniture ||
item instanceof InteractionWater || item instanceof InteractionWater ||
item instanceof InteractionWaterItem || item instanceof InteractionWaterItem ||
item instanceof InteractionMuteArea || item instanceof InteractionMuteArea ||
item instanceof InteractionBuildArea || item instanceof InteractionBuildArea ||
item instanceof InteractionTagPole || item instanceof InteractionTagPole ||
item instanceof InteractionTagField || item instanceof InteractionTagField ||
item instanceof InteractionJukeBox || item instanceof InteractionJukeBox ||
item instanceof InteractionPetBreedingNest || item instanceof InteractionPetBreedingNest ||
item instanceof InteractionBlackHole || item instanceof InteractionBlackHole ||
item instanceof InteractionWiredHighscore || item instanceof InteractionWiredHighscore ||
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 InteractionFireworks) { item instanceof InteractionFireworks) {
specialTypes.addUndefined(item); specialTypes.addUndefined(item);
} }
} }
@@ -822,53 +816,53 @@ public class RoomItemManager {
specialTypes.removeCycleTask((ICycleable) item); specialTypes.removeCycleTask((ICycleable) item);
} }
if (item instanceof InteractionBattleBanzaiTeleporter) { if (item instanceof InteractionBattleBanzaiTeleporter) {
specialTypes.removeBanzaiTeleporter((InteractionBattleBanzaiTeleporter) item); specialTypes.removeBanzaiTeleporter((InteractionBattleBanzaiTeleporter) item);
} else if (item instanceof InteractionWiredTrigger) { } else if (item instanceof InteractionWiredTrigger) {
specialTypes.removeTrigger((InteractionWiredTrigger) item); specialTypes.removeTrigger((InteractionWiredTrigger) item);
isWiredItem = true; isWiredItem = true;
} else if (item instanceof InteractionWiredEffect) { } else if (item instanceof InteractionWiredEffect) {
specialTypes.removeEffect((InteractionWiredEffect) item); specialTypes.removeEffect((InteractionWiredEffect) item);
isWiredItem = true; isWiredItem = true;
} else if (item instanceof InteractionWiredCondition) { } else if (item instanceof InteractionWiredCondition) {
specialTypes.removeCondition((InteractionWiredCondition) item); specialTypes.removeCondition((InteractionWiredCondition) item);
isWiredItem = true; isWiredItem = true;
} else if (item instanceof InteractionWiredExtra) { } else if (item instanceof InteractionWiredExtra) {
boolean removedContextDefinition = false; boolean removedContextDefinition = false;
boolean removedVariableTextConnector = false; boolean removedVariableTextConnector = false;
if (item instanceof WiredExtraUserVariable) { if (item instanceof WiredExtraUserVariable) {
this.room.getUserVariableManager().removeDefinition(item.getId()); this.room.getUserVariableManager().removeDefinition(item.getId());
} else if (item instanceof WiredExtraFurniVariable) { } else if (item instanceof WiredExtraFurniVariable) {
this.room.getFurniVariableManager().removeDefinition(item.getId()); this.room.getFurniVariableManager().removeDefinition(item.getId());
} else if (item instanceof WiredExtraRoomVariable) { } else if (item instanceof WiredExtraRoomVariable) {
this.room.getRoomVariableManager().removeDefinition(item.getId()); this.room.getRoomVariableManager().removeDefinition(item.getId());
} else if (item instanceof WiredExtraContextVariable) { } else if (item instanceof WiredExtraContextVariable) {
removedContextDefinition = true; removedContextDefinition = true;
} else if (item instanceof WiredExtraVariableTextConnector) { } else if (item instanceof WiredExtraVariableTextConnector) {
removedVariableTextConnector = true; removedVariableTextConnector = true;
} else if (item instanceof WiredExtraVariableReference) { } else if (item instanceof WiredExtraVariableReference) {
if (((WiredExtraVariableReference) item).isRoomReference()) { if (((WiredExtraVariableReference) item).isRoomReference()) {
this.room.getRoomVariableManager().removeDefinition(item.getId()); this.room.getRoomVariableManager().removeDefinition(item.getId());
} else { } else {
this.room.getUserVariableManager().removeDefinition(item.getId()); this.room.getUserVariableManager().removeDefinition(item.getId());
} }
} else if (item instanceof WiredExtraVariableEcho) { } else if (item instanceof WiredExtraVariableEcho) {
WiredExtraVariableEcho echo = (WiredExtraVariableEcho) item; WiredExtraVariableEcho echo = (WiredExtraVariableEcho) item;
if (echo.isRoomEcho()) { if (echo.isRoomEcho()) {
this.room.getRoomVariableManager().removeDefinition(item.getId()); this.room.getRoomVariableManager().removeDefinition(item.getId());
} else if (echo.isFurniEcho()) { } else if (echo.isFurniEcho()) {
this.room.getFurniVariableManager().removeDefinition(item.getId()); this.room.getFurniVariableManager().removeDefinition(item.getId());
} else { } else {
this.room.getUserVariableManager().removeDefinition(item.getId()); this.room.getUserVariableManager().removeDefinition(item.getId());
} }
} }
specialTypes.removeExtra((InteractionWiredExtra) item); specialTypes.removeExtra((InteractionWiredExtra) item);
if (removedContextDefinition || removedVariableTextConnector) { if (removedContextDefinition || removedVariableTextConnector) {
WiredContextVariableSupport.broadcastDefinitions(this.room); WiredContextVariableSupport.broadcastDefinitions(this.room);
} }
isWiredItem = true; isWiredItem = true;
} else if (item instanceof InteractionRoller) { } else if (item instanceof InteractionRoller) {
specialTypes.removeRoller((InteractionRoller) item); specialTypes.removeRoller((InteractionRoller) item);
} else if (item instanceof InteractionGameScoreboard) { } else if (item instanceof InteractionGameScoreboard) {
specialTypes.removeScoreboard((InteractionGameScoreboard) item); specialTypes.removeScoreboard((InteractionGameScoreboard) item);
@@ -889,23 +883,23 @@ public class RoomItemManager {
} else if (item instanceof InteractionPetTree) { } else if (item instanceof InteractionPetTree) {
specialTypes.removePetTree((InteractionPetTree) item); specialTypes.removePetTree((InteractionPetTree) item);
} else if (item instanceof InteractionMoodLight || } else if (item instanceof InteractionMoodLight ||
item instanceof InteractionPyramid || item instanceof InteractionPyramid ||
item instanceof InteractionMusicDisc || item instanceof InteractionMusicDisc ||
item instanceof InteractionBattleBanzaiSphere || item instanceof InteractionBattleBanzaiSphere ||
item instanceof InteractionTalkingFurniture || item instanceof InteractionTalkingFurniture ||
item instanceof InteractionWaterItem || item instanceof InteractionWaterItem ||
item instanceof InteractionWater || item instanceof InteractionWater ||
item instanceof InteractionMuteArea || item instanceof InteractionMuteArea ||
item instanceof InteractionTagPole || item instanceof InteractionTagPole ||
item instanceof InteractionTagField || item instanceof InteractionTagField ||
item instanceof InteractionJukeBox || item instanceof InteractionJukeBox ||
item instanceof InteractionPetBreedingNest || item instanceof InteractionPetBreedingNest ||
item instanceof InteractionBlackHole || item instanceof InteractionBlackHole ||
item instanceof InteractionWiredHighscore || item instanceof InteractionWiredHighscore ||
item instanceof InteractionStickyPole || item instanceof InteractionStickyPole ||
item instanceof WiredBlob || item instanceof WiredBlob ||
item instanceof InteractionTent || item instanceof InteractionTent ||
item instanceof InteractionSnowboardSlope) { item instanceof InteractionSnowboardSlope) {
specialTypes.removeUndefined(item); specialTypes.removeUndefined(item);
} }
@@ -936,9 +930,9 @@ public class RoomItemManager {
if (item.getBaseItem().getType() == FurnitureType.FLOOR) { if (item.getBaseItem().getType() == FurnitureType.FLOOR) {
this.room.sendComposer(new FloorItemUpdateComposer(item).compose()); this.room.sendComposer(new FloorItemUpdateComposer(item).compose());
this.room.updateTiles(this.room.getLayout() this.room.updateTiles(this.room.getLayout()
.getTilesAt(this.room.getLayout().getTile(item.getX(), item.getY()), .getTilesAt(this.room.getLayout().getTile(item.getX(), item.getY()),
item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getBaseItem().getWidth(), item.getBaseItem().getLength(),
item.getRotation())); item.getRotation()));
} else if (item.getBaseItem().getType() == FurnitureType.WALL) { } else if (item.getBaseItem().getType() == FurnitureType.WALL) {
this.room.sendComposer(new WallItemUpdateComposer(item).compose()); this.room.sendComposer(new WallItemUpdateComposer(item).compose());
} }
@@ -963,9 +957,9 @@ public class RoomItemManager {
} }
this.room.updateTiles(this.room.getLayout() this.room.updateTiles(this.room.getLayout()
.getTilesAt(this.room.getLayout().getTile(item.getX(), item.getY()), .getTilesAt(this.room.getLayout().getTile(item.getX(), item.getY()),
item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getBaseItem().getWidth(), item.getBaseItem().getLength(),
item.getRotation())); item.getRotation()));
if (item instanceof InteractionMultiHeight) { if (item instanceof InteractionMultiHeight) {
((InteractionMultiHeight) item).updateUnitsOnItem(this.room); ((InteractionMultiHeight) item).updateUnitsOnItem(this.room);
@@ -1032,7 +1026,7 @@ public class RoomItemManager {
if (Emulator.getPluginManager().isRegistered(FurniturePickedUpEvent.class, true)) { if (Emulator.getPluginManager().isRegistered(FurniturePickedUpEvent.class, true)) {
FurniturePickedUpEvent event = Emulator.getPluginManager() FurniturePickedUpEvent event = Emulator.getPluginManager()
.fireEvent(new FurniturePickedUpEvent(item, picker)); .fireEvent(new FurniturePickedUpEvent(item, picker));
if (event.isCancelled()) { if (event.isCancelled()) {
return; return;
@@ -1060,10 +1054,10 @@ public class RoomItemManager {
} }
THashSet<RoomTile> updatedTiles = this.room.getLayout().getTilesAt( THashSet<RoomTile> updatedTiles = this.room.getLayout().getTilesAt(
this.room.getLayout().getTile(item.getX(), item.getY()), this.room.getLayout().getTile(item.getX(), item.getY()),
item.getBaseItem().getWidth(), item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), item.getBaseItem().getLength(),
item.getRotation()); item.getRotation());
this.room.updateTiles(updatedTiles); this.room.updateTiles(updatedTiles);
for (RoomTile tile : updatedTiles) { for (RoomTile tile : updatedTiles) {
@@ -1114,6 +1108,7 @@ public class RoomItemManager {
if (habbo != null && !inventoryItems.isEmpty()) { if (habbo != null && !inventoryItems.isEmpty()) {
habbo.getInventory().getItemsComponent().addItems(inventoryItems); habbo.getInventory().getItemsComponent().addItems(inventoryItems);
habbo.getClient().sendResponse(new AddHabboItemComposer(inventoryItems)); habbo.getClient().sendResponse(new AddHabboItemComposer(inventoryItems));
habbo.getClient().sendResponse(new InventoryRefreshComposer());
} }
for (HabboItem i : items) { for (HabboItem i : items) {
@@ -1160,7 +1155,7 @@ public class RoomItemManager {
} }
userItemsMap.computeIfAbsent(iterator.value().getUserId(), k -> new THashSet<>()) userItemsMap.computeIfAbsent(iterator.value().getUserId(), k -> new THashSet<>())
.add(iterator.value()); .add(iterator.value());
} }
} }
@@ -1182,6 +1177,7 @@ public class RoomItemManager {
if (user != null && !inventoryItems.isEmpty()) { if (user != null && !inventoryItems.isEmpty()) {
user.getInventory().getItemsComponent().addItems(inventoryItems); user.getInventory().getItemsComponent().addItems(inventoryItems);
user.getClient().sendResponse(new AddHabboItemComposer(inventoryItems)); user.getClient().sendResponse(new AddHabboItemComposer(inventoryItems));
user.getClient().sendResponse(new InventoryRefreshComposer());
} }
} }
} }
@@ -1222,7 +1218,7 @@ public class RoomItemManager {
for (short y = 0; y < item.getBaseItem().getLength(); y++) { for (short y = 0; y < item.getBaseItem().getLength(); y++) {
for (short x = 0; x < item.getBaseItem().getWidth(); x++) { for (short x = 0; x < item.getBaseItem().getWidth(); x++) {
RoomTile tile = this.room.getLayout().getTile( RoomTile tile = this.room.getLayout().getTile(
(short) (item.getX() + x), (short) (item.getY() + y)); (short) (item.getX() + x), (short) (item.getY() + y));
if (tile != null) { if (tile != null) {
lockedTiles.add(tile); lockedTiles.add(tile);
@@ -1233,7 +1229,7 @@ public class RoomItemManager {
for (short y = 0; y < item.getBaseItem().getWidth(); y++) { for (short y = 0; y < item.getBaseItem().getWidth(); y++) {
for (short x = 0; x < item.getBaseItem().getLength(); x++) { for (short x = 0; x < item.getBaseItem().getLength(); x++) {
RoomTile tile = this.room.getLayout().getTile( RoomTile tile = this.room.getLayout().getTile(
(short) (item.getX() + x), (short) (item.getY() + y)); (short) (item.getX() + x), (short) (item.getY() + y));
if (tile != null) { if (tile != null) {
lockedTiles.add(tile); lockedTiles.add(tile);
@@ -1324,8 +1320,8 @@ public class RoomItemManager {
rotation %= 8; rotation %= 8;
if (this.room.hasRights(habbo) || this.room.getGuildRightLevel(habbo) if (this.room.hasRights(habbo) || this.room.getGuildRightLevel(habbo)
.isEqualOrGreaterThan(RoomRightLevels.GUILD_RIGHTS) || habbo.hasPermission( .isEqualOrGreaterThan(RoomRightLevels.GUILD_RIGHTS) || habbo.hasPermission(
Permission.ACC_MOVEROTATE) || BuildersClubRoomSupport.canPlaceInRoom(habbo, this.room)) { Permission.ACC_MOVEROTATE) || BuildersClubRoomSupport.canPlaceInRoom(habbo, this.room)) {
return FurnitureMovementError.NONE; return FurnitureMovementError.NONE;
} }
@@ -1334,10 +1330,10 @@ public class RoomItemManager {
if (rentSpace != null) { if (rentSpace != null) {
if (!RoomLayout.squareInSquare(RoomLayout.getRectangle(rentSpace.getX(), rentSpace.getY(), if (!RoomLayout.squareInSquare(RoomLayout.getRectangle(rentSpace.getX(), rentSpace.getY(),
rentSpace.getBaseItem().getWidth(), rentSpace.getBaseItem().getLength(), rentSpace.getBaseItem().getWidth(), rentSpace.getBaseItem().getLength(),
rentSpace.getRotation()), rentSpace.getRotation()),
RoomLayout.getRectangle(tile.x, tile.y, item.getBaseItem().getWidth(), RoomLayout.getRectangle(tile.x, tile.y, item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), rotation))) { item.getBaseItem().getLength(), rotation))) {
return FurnitureMovementError.NO_RIGHTS; return FurnitureMovementError.NO_RIGHTS;
} else { } else {
return FurnitureMovementError.NONE; return FurnitureMovementError.NONE;
@@ -1347,7 +1343,7 @@ public class RoomItemManager {
for (HabboItem area : this.room.getRoomSpecialTypes().getItemsOfType(InteractionBuildArea.class)) { for (HabboItem area : this.room.getRoomSpecialTypes().getItemsOfType(InteractionBuildArea.class)) {
if (((InteractionBuildArea) area).inSquare(tile) && ((InteractionBuildArea) area).isBuilder( if (((InteractionBuildArea) area).inSquare(tile) && ((InteractionBuildArea) area).isBuilder(
habbo.getHabboInfo().getUsername())) { habbo.getHabboInfo().getUsername())) {
return FurnitureMovementError.NONE; return FurnitureMovementError.NONE;
} }
} }
@@ -1438,14 +1434,14 @@ public class RoomItemManager {
} }
THashSet<RoomTile> occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(), THashSet<RoomTile> occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), rotation); item.getBaseItem().getLength(), rotation);
for (RoomTile t : occupiedTiles) { for (RoomTile t : occupiedTiles) {
if (t.state == RoomTileState.INVALID) { if (t.state == RoomTileState.INVALID) {
return FurnitureMovementError.INVALID_MOVE; return FurnitureMovementError.INVALID_MOVE;
} }
if (!Emulator.getConfig().getBoolean("wired.place.under", false) || ( if (!Emulator.getConfig().getBoolean("wired.place.under", false) || (
Emulator.getConfig().getBoolean("wired.place.under", false) && !item.isWalkable() Emulator.getConfig().getBoolean("wired.place.under", false) && !item.isWalkable()
&& !item.getBaseItem().allowSit() && !item.getBaseItem().allowLay())) { && !item.getBaseItem().allowSit() && !item.getBaseItem().allowLay())) {
if (checkForUnits && this.room.hasHabbosAt(t.x, t.y)) { if (checkForUnits && this.room.hasHabbosAt(t.x, t.y)) {
return FurnitureMovementError.TILE_HAS_HABBOS; return FurnitureMovementError.TILE_HAS_HABBOS;
} }
@@ -1490,7 +1486,7 @@ public class RoomItemManager {
} }
THashSet<RoomTile> occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(), THashSet<RoomTile> occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), rotation); item.getBaseItem().getLength(), rotation);
for (RoomTile t : occupiedTiles) { for (RoomTile t : occupiedTiles) {
if (t.state == RoomTileState.INVALID) { if (t.state == RoomTileState.INVALID) {
return FurnitureMovementError.INVALID_MOVE; return FurnitureMovementError.INVALID_MOVE;
@@ -1542,7 +1538,7 @@ public class RoomItemManager {
boolean pluginHelper = false; boolean pluginHelper = false;
if (Emulator.getPluginManager().isRegistered(FurniturePlacedEvent.class, true)) { if (Emulator.getPluginManager().isRegistered(FurniturePlacedEvent.class, true)) {
FurniturePlacedEvent event = Emulator.getPluginManager() FurniturePlacedEvent event = Emulator.getPluginManager()
.fireEvent(new FurniturePlacedEvent(item, owner, tile)); .fireEvent(new FurniturePlacedEvent(item, owner, tile));
if (event.isCancelled()) { if (event.isCancelled()) {
return FurnitureMovementError.CANCEL_PLUGIN_PLACE; return FurnitureMovementError.CANCEL_PLUGIN_PLACE;
@@ -1553,7 +1549,7 @@ public class RoomItemManager {
RoomLayout layout = this.room.getLayout(); RoomLayout layout = this.room.getLayout();
THashSet<RoomTile> occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(), THashSet<RoomTile> occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), rotation); item.getBaseItem().getLength(), rotation);
FurnitureMovementError fits = furnitureFitsAt(tile, item, rotation); FurnitureMovementError fits = furnitureFitsAt(tile, item, rotation);
@@ -1572,7 +1568,7 @@ public class RoomItemManager {
if (Emulator.getPluginManager().isRegistered(FurnitureBuildheightEvent.class, true)) { if (Emulator.getPluginManager().isRegistered(FurnitureBuildheightEvent.class, true)) {
FurnitureBuildheightEvent event = Emulator.getPluginManager() FurnitureBuildheightEvent event = Emulator.getPluginManager()
.fireEvent(new FurnitureBuildheightEvent(item, owner, 0.00, height)); .fireEvent(new FurnitureBuildheightEvent(item, owner, 0.00, height));
if (event.hasChangedHeight()) { if (event.hasChangedHeight()) {
height = layout.getHeightAtSquare(tile.x, tile.y) + event.getUpdatedHeight(); height = layout.getHeightAtSquare(tile.x, tile.y) + event.getUpdatedHeight();
} }
@@ -1592,7 +1588,7 @@ public class RoomItemManager {
item.onPlace(this.room); item.onPlace(this.room);
this.room.updateTiles(occupiedTiles); this.room.updateTiles(occupiedTiles);
this.room.sendComposer( this.room.sendComposer(
new AddFloorItemComposer(item, this.getFurniOwnerName(item.getUserId())).compose()); new AddFloorItemComposer(item, this.getFurniOwnerName(item.getUserId())).compose());
if (RoomConfInvisSupport.isControllerItem(item) || RoomConfInvisSupport.isTarget(item)) { if (RoomConfInvisSupport.isControllerItem(item) || RoomConfInvisSupport.isTarget(item)) {
RoomConfInvisSupport.sendState(this.room); RoomConfInvisSupport.sendState(this.room);
@@ -1620,7 +1616,7 @@ public class RoomItemManager {
*/ */
public FurnitureMovementError placeWallFurniAt(HabboItem item, String wallPosition, Habbo owner) { public FurnitureMovementError placeWallFurniAt(HabboItem item, String wallPosition, Habbo owner) {
if (!(this.room.hasRights(owner) || this.room.getGuildRightLevel(owner) if (!(this.room.hasRights(owner) || this.room.getGuildRightLevel(owner)
.isEqualOrGreaterThan(RoomRightLevels.GUILD_RIGHTS) || BuildersClubRoomSupport.canPlaceInRoom(owner, this.room))) { .isEqualOrGreaterThan(RoomRightLevels.GUILD_RIGHTS) || BuildersClubRoomSupport.canPlaceInRoom(owner, this.room))) {
return FurnitureMovementError.NO_RIGHTS; return FurnitureMovementError.NO_RIGHTS;
} }
@@ -1638,7 +1634,7 @@ public class RoomItemManager {
this.furniOwnerNames.put(item.getUserId(), this.resolveOwnerName(item, owner)); this.furniOwnerNames.put(item.getUserId(), this.resolveOwnerName(item, owner));
} }
this.room.sendComposer( this.room.sendComposer(
new AddWallItemComposer(item, this.getFurniOwnerName(item.getUserId())).compose()); new AddWallItemComposer(item, this.getFurniOwnerName(item.getUserId())).compose());
item.needsUpdate(true); item.needsUpdate(true);
this.addHabboItem(item); this.addHabboItem(item);
item.setRoomId(this.room.getId()); item.setRoomId(this.room.getId());
@@ -1989,7 +1985,7 @@ public class RoomItemManager {
boolean pluginHelper = false; boolean pluginHelper = false;
if (Emulator.getPluginManager().isRegistered(FurnitureMovedEvent.class, true)) { if (Emulator.getPluginManager().isRegistered(FurnitureMovedEvent.class, true)) {
FurnitureMovedEvent event = Emulator.getPluginManager() FurnitureMovedEvent event = Emulator.getPluginManager()
.fireEvent(new FurnitureMovedEvent(item, actor, oldLocation, tile)); .fireEvent(new FurnitureMovedEvent(item, actor, oldLocation, tile));
if (event.isCancelled()) { if (event.isCancelled()) {
return FurnitureMovementError.CANCEL_PLUGIN_MOVE; return FurnitureMovementError.CANCEL_PLUGIN_MOVE;
} }
@@ -2002,9 +1998,9 @@ public class RoomItemManager {
// Check if can be placed at new position // Check if can be placed at new position
THashSet<RoomTile> occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(), THashSet<RoomTile> occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), rotation); item.getBaseItem().getLength(), rotation);
THashSet<RoomTile> newOccupiedTiles = layout.getTilesAt(tile, THashSet<RoomTile> newOccupiedTiles = layout.getTilesAt(tile,
item.getBaseItem().getWidth(), item.getBaseItem().getLength(), rotation); item.getBaseItem().getWidth(), item.getBaseItem().getLength(), rotation);
HabboItem topItem = this.getTopItemAt(occupiedTiles, null); HabboItem topItem = this.getTopItemAt(occupiedTiles, null);
@@ -2013,15 +2009,15 @@ public class RoomItemManager {
for (RoomTile t : occupiedTiles) { for (RoomTile t : occupiedTiles) {
HabboItem tileTopItem = this.getTopItemAt(t.x, t.y); HabboItem tileTopItem = this.getTopItemAt(t.x, t.y);
if (!magicTile && ((tileTopItem != null && tileTopItem != item ? ( if (!magicTile && ((tileTopItem != null && tileTopItem != item ? (
t.state.equals(RoomTileState.INVALID) || !t.getAllowStack() t.state.equals(RoomTileState.INVALID) || !t.getAllowStack()
|| !tileTopItem.getBaseItem().allowStack()) || !tileTopItem.getBaseItem().allowStack())
: this.room.calculateTileState(t, item).equals(RoomTileState.INVALID)))) { : this.room.calculateTileState(t, item).equals(RoomTileState.INVALID)))) {
return FurnitureMovementError.CANT_STACK; return FurnitureMovementError.CANT_STACK;
} }
if (!Emulator.getConfig().getBoolean("wired.place.under", false) || ( if (!Emulator.getConfig().getBoolean("wired.place.under", false) || (
Emulator.getConfig().getBoolean("wired.place.under", false) && !item.isWalkable() Emulator.getConfig().getBoolean("wired.place.under", false) && !item.isWalkable()
&& !item.getBaseItem().allowSit() && !item.getBaseItem().allowLay())) { && !item.getBaseItem().allowSit() && !item.getBaseItem().allowLay())) {
if (checkForUnits) { if (checkForUnits) {
if (!magicTile && this.room.hasHabbosAt(t.x, t.y)) { if (!magicTile && this.room.hasHabbosAt(t.x, t.y)) {
return FurnitureMovementError.TILE_HAS_HABBOS; return FurnitureMovementError.TILE_HAS_HABBOS;
@@ -2048,8 +2044,8 @@ public class RoomItemManager {
} }
THashSet<RoomTile> oldOccupiedTiles = layout.getTilesAt( THashSet<RoomTile> oldOccupiedTiles = layout.getTilesAt(
layout.getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(), layout.getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), item.getRotation()); item.getBaseItem().getLength(), item.getRotation());
int oldRotation = item.getRotation(); int oldRotation = item.getRotation();
@@ -2066,9 +2062,9 @@ public class RoomItemManager {
} }
if ((stackHelper == null && topItem != null && topItem != item && !topItem.getBaseItem() if ((stackHelper == null && topItem != null && topItem != item && !topItem.getBaseItem()
.allowStack()) || (topItem != null && topItem != item .allowStack()) || (topItem != null && topItem != item
&& topItem.getZ() + Item.getCurrentHeight(topItem) + Item.getCurrentHeight(item) && topItem.getZ() + Item.getCurrentHeight(topItem) + Item.getCurrentHeight(item)
> Room.MAXIMUM_FURNI_HEIGHT)) { > Room.MAXIMUM_FURNI_HEIGHT)) {
item.setRotation(oldRotation); item.setRotation(oldRotation);
return FurnitureMovementError.CANT_STACK; return FurnitureMovementError.CANT_STACK;
} }
@@ -2117,7 +2113,7 @@ public class RoomItemManager {
if (Emulator.getPluginManager().isRegistered(FurnitureBuildheightEvent.class, true)) { if (Emulator.getPluginManager().isRegistered(FurnitureBuildheightEvent.class, true)) {
FurnitureBuildheightEvent event = Emulator.getPluginManager() FurnitureBuildheightEvent event = Emulator.getPluginManager()
.fireEvent(new FurnitureBuildheightEvent(item, actor, 0.00, height)); .fireEvent(new FurnitureBuildheightEvent(item, actor, 0.00, height));
if (event.hasChangedHeight()) { if (event.hasChangedHeight()) {
height = layout.getHeightAtSquare(tile.x, tile.y) + event.getUpdatedHeight(); height = layout.getHeightAtSquare(tile.x, tile.y) + event.getUpdatedHeight();
pluginHeight = true; pluginHeight = true;
@@ -2198,7 +2194,7 @@ public class RoomItemManager {
boolean pluginHelper = false; boolean pluginHelper = false;
if (Emulator.getPluginManager().isRegistered(FurnitureMovedEvent.class, true)) { if (Emulator.getPluginManager().isRegistered(FurnitureMovedEvent.class, true)) {
FurnitureMovedEvent event = Emulator.getPluginManager() FurnitureMovedEvent event = Emulator.getPluginManager()
.fireEvent(new FurnitureMovedEvent(item, actor, oldLocation, tile)); .fireEvent(new FurnitureMovedEvent(item, actor, oldLocation, tile));
if (event.isCancelled()) { if (event.isCancelled()) {
return FurnitureMovementError.CANCEL_PLUGIN_MOVE; return FurnitureMovementError.CANCEL_PLUGIN_MOVE;
} }
@@ -2210,9 +2206,9 @@ public class RoomItemManager {
HabboItem stackHelper = this.findStackHeightHelperAt(tile, item); HabboItem stackHelper = this.findStackHeightHelperAt(tile, item);
THashSet<RoomTile> occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(), THashSet<RoomTile> occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), rotation); item.getBaseItem().getLength(), rotation);
THashSet<RoomTile> newOccupiedTiles = layout.getTilesAt(tile, THashSet<RoomTile> newOccupiedTiles = layout.getTilesAt(tile,
item.getBaseItem().getWidth(), item.getBaseItem().getLength(), rotation); item.getBaseItem().getWidth(), item.getBaseItem().getLength(), rotation);
HabboItem topItem = this.getTopPhysicsItemAt(occupiedTiles, null, physics); HabboItem topItem = this.getTopPhysicsItemAt(occupiedTiles, null, physics);
@@ -2221,9 +2217,9 @@ public class RoomItemManager {
for (RoomTile t : occupiedTiles) { for (RoomTile t : occupiedTiles) {
HabboItem tileTopItem = this.getTopPhysicsItemAt(t.x, t.y, item, physics); HabboItem tileTopItem = this.getTopPhysicsItemAt(t.x, t.y, item, physics);
if (!magicTile && ((tileTopItem != null && tileTopItem != item ? ( if (!magicTile && ((tileTopItem != null && tileTopItem != item ? (
t.state.equals(RoomTileState.INVALID) || !t.getAllowStack() t.state.equals(RoomTileState.INVALID) || !t.getAllowStack()
|| !tileTopItem.getBaseItem().allowStack()) || !tileTopItem.getBaseItem().allowStack())
: this.room.calculateTileState(t, item).equals(RoomTileState.INVALID)))) { : this.room.calculateTileState(t, item).equals(RoomTileState.INVALID)))) {
return FurnitureMovementError.CANT_STACK; return FurnitureMovementError.CANT_STACK;
} }
@@ -2251,8 +2247,8 @@ public class RoomItemManager {
} }
THashSet<RoomTile> oldOccupiedTiles = layout.getTilesAt( THashSet<RoomTile> oldOccupiedTiles = layout.getTilesAt(
layout.getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(), layout.getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), item.getRotation()); item.getBaseItem().getLength(), item.getRotation());
int oldRotation = item.getRotation(); int oldRotation = item.getRotation();
@@ -2269,9 +2265,9 @@ public class RoomItemManager {
} }
if ((stackHelper == null && topItem != null && topItem != item && !topItem.getBaseItem() if ((stackHelper == null && topItem != null && topItem != item && !topItem.getBaseItem()
.allowStack()) || (topItem != null && topItem != item .allowStack()) || (topItem != null && topItem != item
&& topItem.getZ() + Item.getCurrentHeight(topItem) + Item.getCurrentHeight(item) && topItem.getZ() + Item.getCurrentHeight(topItem) + Item.getCurrentHeight(item)
> Room.MAXIMUM_FURNI_HEIGHT)) { > Room.MAXIMUM_FURNI_HEIGHT)) {
item.setRotation(oldRotation); item.setRotation(oldRotation);
return FurnitureMovementError.CANT_STACK; return FurnitureMovementError.CANT_STACK;
} }
@@ -2319,7 +2315,7 @@ public class RoomItemManager {
if (Emulator.getPluginManager().isRegistered(FurnitureBuildheightEvent.class, true)) { if (Emulator.getPluginManager().isRegistered(FurnitureBuildheightEvent.class, true)) {
FurnitureBuildheightEvent event = Emulator.getPluginManager() FurnitureBuildheightEvent event = Emulator.getPluginManager()
.fireEvent(new FurnitureBuildheightEvent(item, actor, 0.00, height)); .fireEvent(new FurnitureBuildheightEvent(item, actor, 0.00, height));
if (event.hasChangedHeight()) { if (event.hasChangedHeight()) {
height = layout.getHeightAtSquare(tile.x, tile.y) + event.getUpdatedHeight(); height = layout.getHeightAtSquare(tile.x, tile.y) + event.getUpdatedHeight();
pluginHeight = true; pluginHeight = true;
@@ -2394,7 +2390,7 @@ public class RoomItemManager {
// Check if can be placed at new position // Check if can be placed at new position
THashSet<RoomTile> occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(), THashSet<RoomTile> occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), rotation); item.getBaseItem().getLength(), rotation);
java.util.List<Pair<RoomTile, THashSet<HabboItem>>> tileFurniList = new java.util.ArrayList<>(); java.util.List<Pair<RoomTile, THashSet<HabboItem>>> tileFurniList = new java.util.ArrayList<>();
for (RoomTile t : occupiedTiles) { for (RoomTile t : occupiedTiles) {
@@ -2438,8 +2434,8 @@ public class RoomItemManager {
} }
return !item.isWalkable() return !item.isWalkable()
&& !item.getBaseItem().allowSit() && !item.getBaseItem().allowSit()
&& !item.getBaseItem().allowLay(); && !item.getBaseItem().allowLay();
} }
private FurnitureMovementError getPhysicsUnitCollision(RoomTile tile, WiredMovementPhysics physics) { private FurnitureMovementError getPhysicsUnitCollision(RoomTile tile, WiredMovementPhysics physics) {
@@ -2515,7 +2511,7 @@ public class RoomItemManager {
for (HabboItem item : this.getPhysicsItemsAt(tile, exclude, physics)) { for (HabboItem item : this.getPhysicsItemsAt(tile, exclude, physics)) {
if (highestItem != null && highestItem.getZ() + Item.getCurrentHeight(highestItem) if (highestItem != null && highestItem.getZ() + Item.getCurrentHeight(highestItem)
> item.getZ() + Item.getCurrentHeight(item)) { > item.getZ() + Item.getCurrentHeight(item)) {
continue; continue;
} }
@@ -2539,7 +2535,7 @@ public class RoomItemManager {
} }
if (highestItem != null && highestItem.getZ() + Item.getCurrentHeight(highestItem) if (highestItem != null && highestItem.getZ() + Item.getCurrentHeight(highestItem)
> topItem.getZ() + Item.getCurrentHeight(topItem)) { > topItem.getZ() + Item.getCurrentHeight(topItem)) {
continue; continue;
} }
@@ -343,18 +343,16 @@ public class RoomSpecialTypes {
* Adds a wired trigger to the room. * Adds a wired trigger to the room.
* @param trigger The trigger to add * @param trigger The trigger to add
*/ */
public static final int MAX_SIGNAL_SENDERS_PER_ROOM = 25; public static final int MAX_SIGNAL_SENDERS_PER_ROOM = 0;
public static final int MAX_SIGNAL_RECEIVERS_PER_ROOM = 5; public static final int MAX_SIGNAL_RECEIVERS_PER_ROOM = 0;
public static final int MAX_SENDERS_PER_RECEIVER = 5; public static final int MAX_SENDERS_PER_RECEIVER = 0;
public boolean isSignalSenderLimitReached() { public boolean isSignalSenderLimitReached() {
Set<InteractionWiredEffect> existing = this.getSignalSenders(); return false;
return existing != null && existing.size() >= MAX_SIGNAL_SENDERS_PER_ROOM;
} }
public boolean isSignalReceiverLimitReached() { public boolean isSignalReceiverLimitReached() {
Set<InteractionWiredTrigger> existing = this.wiredTriggers.get(WiredTriggerType.RECEIVE_SIGNAL); return false;
return existing != null && existing.size() >= MAX_SIGNAL_RECEIVERS_PER_ROOM;
} }
public int countSendersTargetingReceiver(int receiverItemId, InteractionWiredEffect excludeSender) { public int countSendersTargetingReceiver(int receiverItemId, InteractionWiredEffect excludeSender) {
@@ -0,0 +1,469 @@
package com.eu.habbo.habbohotel.translations;
import com.eu.habbo.Emulator;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class GoogleTranslateManager {
private static final Logger LOGGER = LoggerFactory.getLogger(GoogleTranslateManager.class);
private static final int DEFAULT_TIMEOUT_MS = 5000;
private static final long CACHE_TTL_MS = 1000L * 60L * 60L * 6L;
private static final int MAX_TRANSLATION_CACHE_SIZE = 2048;
private static final int MAX_LANGUAGE_CACHE_SIZE = 32;
private static final String FREE_TRANSLATE_ENDPOINT = "https://translate.googleapis.com/translate_a/single";
private static final List<SupportedLanguage> FREE_SUPPORTED_LANGUAGES = buildFreeSupportedLanguages();
private final Map<String, CachedTranslation> translationCache = Collections.synchronizedMap(
new LinkedHashMap<String, CachedTranslation>(128, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, CachedTranslation> eldest) {
return this.size() > MAX_TRANSLATION_CACHE_SIZE;
}
});
private final Map<String, CachedLanguages> languagesCache = Collections.synchronizedMap(
new LinkedHashMap<String, CachedLanguages>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, CachedLanguages> eldest) {
return this.size() > MAX_LANGUAGE_CACHE_SIZE;
}
});
public SupportedLanguagesResponse getSupportedLanguages(String displayLanguage) {
String normalizedDisplayLanguage = normalizeLanguageCode(displayLanguage, "en");
CachedLanguages cachedLanguages = this.languagesCache.get(normalizedDisplayLanguage);
if ((cachedLanguages != null) && !cachedLanguages.isExpired()) {
return SupportedLanguagesResponse.success(new ArrayList<>(cachedLanguages.languages));
}
ArrayList<SupportedLanguage> supportedLanguages = new ArrayList<>(FREE_SUPPORTED_LANGUAGES);
this.languagesCache.put(normalizedDisplayLanguage, new CachedLanguages(supportedLanguages));
return SupportedLanguagesResponse.success(supportedLanguages);
}
public TranslationResponse translate(String text, String targetLanguage) {
String safeText = text == null ? "" : text;
String normalizedTargetLanguage = normalizeLanguageCode(targetLanguage, "en");
if (safeText.trim().isEmpty()) {
return TranslationResponse.success(safeText, safeText, "", normalizedTargetLanguage);
}
String cacheKey = normalizedTargetLanguage + '\u0000' + safeText;
CachedTranslation cachedTranslation = this.translationCache.get(cacheKey);
if ((cachedTranslation != null) && !cachedTranslation.isExpired()) {
return cachedTranslation.response;
}
try {
String requestUrl = FREE_TRANSLATE_ENDPOINT
+ "?client=gtx"
+ "&sl=auto"
+ "&tl=" + encode(normalizedTargetLanguage)
+ "&dt=t"
+ "&q=" + encode(safeText);
HttpsURLConnection connection = this.openGet(requestUrl);
int statusCode = connection.getResponseCode();
if (statusCode != 200) {
return TranslationResponse.failure(safeText, normalizedTargetLanguage, this.readErrorMessage(connection));
}
JsonArray response = this.readJsonArray(connection.getInputStream());
JsonArray translatedParts = response.size() > 0 && response.get(0).isJsonArray()
? response.get(0).getAsJsonArray()
: new JsonArray();
StringBuilder translatedText = new StringBuilder();
for (int index = 0; index < translatedParts.size(); index++) {
if (!translatedParts.get(index).isJsonArray()) {
continue;
}
JsonArray translatedPart = translatedParts.get(index).getAsJsonArray();
if (translatedPart.size() > 0 && !translatedPart.get(0).isJsonNull()) {
translatedText.append(translatedPart.get(0).getAsString());
}
}
String detectedLanguage = "";
if (response.size() > 2 && !response.get(2).isJsonNull()) {
detectedLanguage = response.get(2).getAsString();
}
String resolvedTranslation = translatedText.length() > 0 ? translatedText.toString() : safeText;
TranslationResponse translationResponse = TranslationResponse.success(safeText, resolvedTranslation, detectedLanguage, normalizedTargetLanguage);
this.translationCache.put(cacheKey, new CachedTranslation(translationResponse));
return translationResponse;
} catch (Exception e) {
LOGGER.error("Failed to translate text with Google Translate", e);
return TranslationResponse.failure(safeText, normalizedTargetLanguage, "Failed to translate text with Google Translate.");
}
}
public void clearCache() {
this.translationCache.clear();
this.languagesCache.clear();
}
private int getTimeoutMs() {
return Math.max(1000, Emulator.getConfig().getInt("translate.google.timeout.ms", DEFAULT_TIMEOUT_MS));
}
private HttpsURLConnection openGet(String requestUrl) throws IOException {
HttpsURLConnection connection = (HttpsURLConnection) URI.create(requestUrl).toURL().openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(this.getTimeoutMs());
connection.setReadTimeout(this.getTimeoutMs());
connection.setRequestProperty("Accept", "application/json");
return connection;
}
private JsonObject readJson(InputStream inputStream) throws IOException {
try (InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
return JsonParser.parseReader(bufferedReader).getAsJsonObject();
}
}
private JsonArray readJsonArray(InputStream inputStream) throws IOException {
try (InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
return JsonParser.parseReader(bufferedReader).getAsJsonArray();
}
}
private String readErrorMessage(HttpsURLConnection connection) {
try {
InputStream errorStream = connection.getErrorStream();
if (errorStream == null) {
return "Google Translate request failed with HTTP " + connection.getResponseCode() + '.';
}
try {
JsonObject errorResponse = this.readJson(errorStream);
if (errorResponse.has("error") && errorResponse.get("error").isJsonObject()) {
JsonObject errorObject = errorResponse.getAsJsonObject("error");
if (errorObject.has("message")) {
return errorObject.get("message").getAsString();
}
}
} catch (Exception ignored) {
try (InputStreamReader inputStreamReader = new InputStreamReader(errorStream, StandardCharsets.UTF_8);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
StringBuilder responseText = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
responseText.append(line);
}
if (responseText.length() > 0) {
return responseText.toString();
}
}
}
} catch (Exception e) {
LOGGER.warn("Failed to parse Google Translate error response", e);
}
try {
return "Google Translate request failed with HTTP " + connection.getResponseCode() + '.';
} catch (IOException e) {
return "Google Translate request failed.";
}
}
private static String normalizeLanguageCode(String languageCode, String fallback) {
if (languageCode == null || languageCode.trim().isEmpty()) {
return fallback;
}
String normalized = languageCode.trim().replace('_', '-');
String[] split = normalized.split("-");
if (split.length <= 1) {
return normalized;
}
return split[0] + '-' + split[1].toUpperCase();
}
private static String encode(String value) {
return URLEncoder.encode(value == null ? "" : value, StandardCharsets.UTF_8);
}
private static List<SupportedLanguage> buildFreeSupportedLanguages() {
ArrayList<SupportedLanguage> languages = new ArrayList<>();
addLanguage(languages, "af", "Afrikaans");
addLanguage(languages, "sq", "Albanian");
addLanguage(languages, "am", "Amharic");
addLanguage(languages, "ar", "Arabic");
addLanguage(languages, "hy", "Armenian");
addLanguage(languages, "az", "Azerbaijani");
addLanguage(languages, "eu", "Basque");
addLanguage(languages, "be", "Belarusian");
addLanguage(languages, "bn", "Bengali");
addLanguage(languages, "bs", "Bosnian");
addLanguage(languages, "bg", "Bulgarian");
addLanguage(languages, "ca", "Catalan");
addLanguage(languages, "ceb", "Cebuano");
addLanguage(languages, "ny", "Chichewa");
addLanguage(languages, "zh-CN", "Chinese (Simplified)");
addLanguage(languages, "zh-TW", "Chinese (Traditional)");
addLanguage(languages, "co", "Corsican");
addLanguage(languages, "hr", "Croatian");
addLanguage(languages, "cs", "Czech");
addLanguage(languages, "da", "Danish");
addLanguage(languages, "nl", "Dutch");
addLanguage(languages, "en", "English");
addLanguage(languages, "eo", "Esperanto");
addLanguage(languages, "et", "Estonian");
addLanguage(languages, "tl", "Filipino");
addLanguage(languages, "fi", "Finnish");
addLanguage(languages, "fr", "French");
addLanguage(languages, "fy", "Frisian");
addLanguage(languages, "gl", "Galician");
addLanguage(languages, "ka", "Georgian");
addLanguage(languages, "de", "German");
addLanguage(languages, "el", "Greek");
addLanguage(languages, "gu", "Gujarati");
addLanguage(languages, "ht", "Haitian Creole");
addLanguage(languages, "ha", "Hausa");
addLanguage(languages, "haw", "Hawaiian");
addLanguage(languages, "iw", "Hebrew");
addLanguage(languages, "hi", "Hindi");
addLanguage(languages, "hmn", "Hmong");
addLanguage(languages, "hu", "Hungarian");
addLanguage(languages, "is", "Icelandic");
addLanguage(languages, "ig", "Igbo");
addLanguage(languages, "id", "Indonesian");
addLanguage(languages, "ga", "Irish");
addLanguage(languages, "it", "Italian");
addLanguage(languages, "ja", "Japanese");
addLanguage(languages, "jw", "Javanese");
addLanguage(languages, "kn", "Kannada");
addLanguage(languages, "kk", "Kazakh");
addLanguage(languages, "km", "Khmer");
addLanguage(languages, "rw", "Kinyarwanda");
addLanguage(languages, "ko", "Korean");
addLanguage(languages, "ku", "Kurdish");
addLanguage(languages, "ky", "Kyrgyz");
addLanguage(languages, "lo", "Lao");
addLanguage(languages, "la", "Latin");
addLanguage(languages, "lv", "Latvian");
addLanguage(languages, "lt", "Lithuanian");
addLanguage(languages, "lb", "Luxembourgish");
addLanguage(languages, "mk", "Macedonian");
addLanguage(languages, "mg", "Malagasy");
addLanguage(languages, "ms", "Malay");
addLanguage(languages, "ml", "Malayalam");
addLanguage(languages, "mt", "Maltese");
addLanguage(languages, "mi", "Maori");
addLanguage(languages, "mr", "Marathi");
addLanguage(languages, "mn", "Mongolian");
addLanguage(languages, "my", "Myanmar");
addLanguage(languages, "ne", "Nepali");
addLanguage(languages, "no", "Norwegian");
addLanguage(languages, "or", "Odia");
addLanguage(languages, "ps", "Pashto");
addLanguage(languages, "fa", "Persian");
addLanguage(languages, "pl", "Polish");
addLanguage(languages, "pt", "Portuguese");
addLanguage(languages, "pa", "Punjabi");
addLanguage(languages, "ro", "Romanian");
addLanguage(languages, "ru", "Russian");
addLanguage(languages, "sm", "Samoan");
addLanguage(languages, "gd", "Scots");
addLanguage(languages, "sr", "Serbian");
addLanguage(languages, "st", "Sesotho");
addLanguage(languages, "sn", "Shona");
addLanguage(languages, "sd", "Sindhi");
addLanguage(languages, "si", "Sinhala");
addLanguage(languages, "sk", "Slovak");
addLanguage(languages, "sl", "Slovenian");
addLanguage(languages, "so", "Somali");
addLanguage(languages, "es", "Spanish");
addLanguage(languages, "su", "Sundanese");
addLanguage(languages, "sw", "Swahili");
addLanguage(languages, "sv", "Swedish");
addLanguage(languages, "tg", "Tajik");
addLanguage(languages, "ta", "Tamil");
addLanguage(languages, "tt", "Tatar");
addLanguage(languages, "te", "Telugu");
addLanguage(languages, "th", "Thai");
addLanguage(languages, "tr", "Turkish");
addLanguage(languages, "tk", "Turkmen");
addLanguage(languages, "uk", "Ukrainian");
addLanguage(languages, "ur", "Urdu");
addLanguage(languages, "ug", "Uyghur");
addLanguage(languages, "uz", "Uzbek");
addLanguage(languages, "vi", "Vietnamese");
addLanguage(languages, "cy", "Welsh");
addLanguage(languages, "xh", "Xhosa");
addLanguage(languages, "yi", "Yiddish");
addLanguage(languages, "yo", "Yoruba");
addLanguage(languages, "zu", "Zulu");
languages.sort(Comparator.comparing(SupportedLanguage::getName, String.CASE_INSENSITIVE_ORDER));
return Collections.unmodifiableList(languages);
}
private static void addLanguage(List<SupportedLanguage> languages, String code, String name) {
languages.add(new SupportedLanguage(code, name));
}
public static class SupportedLanguage {
private final String code;
private final String name;
public SupportedLanguage(String code, String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return this.code;
}
public String getName() {
return this.name;
}
}
public static class SupportedLanguagesResponse {
private final boolean success;
private final String errorMessage;
private final List<SupportedLanguage> languages;
private SupportedLanguagesResponse(boolean success, String errorMessage, List<SupportedLanguage> languages) {
this.success = success;
this.errorMessage = errorMessage == null ? "" : errorMessage;
this.languages = languages == null ? Collections.emptyList() : languages;
}
public static SupportedLanguagesResponse success(List<SupportedLanguage> languages) {
return new SupportedLanguagesResponse(true, "", languages);
}
public static SupportedLanguagesResponse failure(String errorMessage) {
return new SupportedLanguagesResponse(false, errorMessage, Collections.emptyList());
}
public boolean isSuccess() {
return this.success;
}
public String getErrorMessage() {
return this.errorMessage;
}
public List<SupportedLanguage> getLanguages() {
return this.languages;
}
}
public static class TranslationResponse {
private final boolean success;
private final String errorMessage;
private final String originalText;
private final String translatedText;
private final String detectedLanguage;
private final String targetLanguage;
private TranslationResponse(boolean success, String errorMessage, String originalText, String translatedText, String detectedLanguage, String targetLanguage) {
this.success = success;
this.errorMessage = errorMessage == null ? "" : errorMessage;
this.originalText = originalText == null ? "" : originalText;
this.translatedText = translatedText == null ? "" : translatedText;
this.detectedLanguage = detectedLanguage == null ? "" : detectedLanguage;
this.targetLanguage = targetLanguage == null ? "" : targetLanguage;
}
public static TranslationResponse success(String originalText, String translatedText, String detectedLanguage, String targetLanguage) {
return new TranslationResponse(true, "", originalText, translatedText, detectedLanguage, targetLanguage);
}
public static TranslationResponse failure(String originalText, String targetLanguage, String errorMessage) {
return new TranslationResponse(false, errorMessage, originalText, originalText, "", targetLanguage);
}
public boolean isSuccess() {
return this.success;
}
public String getErrorMessage() {
return this.errorMessage;
}
public String getOriginalText() {
return this.originalText;
}
public String getTranslatedText() {
return this.translatedText;
}
public String getDetectedLanguage() {
return this.detectedLanguage;
}
public String getTargetLanguage() {
return this.targetLanguage;
}
}
private static class CachedTranslation {
private final long createdAt;
private final TranslationResponse response;
private CachedTranslation(TranslationResponse response) {
this.createdAt = System.currentTimeMillis();
this.response = response;
}
private boolean isExpired() {
return (System.currentTimeMillis() - this.createdAt) > CACHE_TTL_MS;
}
}
private static class CachedLanguages {
private final long createdAt;
private final List<SupportedLanguage> languages;
private CachedLanguages(List<SupportedLanguage> languages) {
this.createdAt = System.currentTimeMillis();
this.languages = languages;
}
private boolean isExpired() {
return (System.currentTimeMillis() - this.createdAt) > CACHE_TTL_MS;
}
}
}
@@ -45,6 +45,7 @@ public class HabboInfo implements Runnable {
private int InfostandBg; private int InfostandBg;
private int InfostandStand; private int InfostandStand;
private int InfostandOverlay; private int InfostandOverlay;
private int InfostandCardBg;
private int loadingRoom; private int loadingRoom;
private Room currentRoom; private Room currentRoom;
private String roomEntryMethod = "door"; private String roomEntryMethod = "door";
@@ -91,6 +92,7 @@ public class HabboInfo implements Runnable {
this.InfostandBg = set.getInt("background_id"); this.InfostandBg = set.getInt("background_id");
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.currentRoom = null; this.currentRoom = null;
} catch (SQLException e) { } catch (SQLException e) {
LOGGER.error("Caught SQL exception", e); LOGGER.error("Caught SQL exception", e);
@@ -290,6 +292,14 @@ public class HabboInfo implements Runnable {
public void setInfostandOverlay(int infostandOverlay) { public void setInfostandOverlay(int infostandOverlay) {
InfostandOverlay = infostandOverlay; InfostandOverlay = infostandOverlay;
} }
public int getInfostandCardBg() {
return InfostandCardBg;
}
public void setInfostandCardBg(int infostandCardBg) {
InfostandCardBg = infostandCardBg;
}
public Rank getRank() { public Rank getRank() {
return this.rank; return this.rank;
} }
@@ -577,7 +587,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 = ? 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 = ? WHERE id = ?",
this.motto, this.motto,
this.online ? "1" : "0", this.online ? "1" : "0",
this.look, this.look,
@@ -593,6 +603,7 @@ public class HabboInfo implements Runnable {
this.InfostandBg, this.InfostandBg,
this.InfostandStand, this.InfostandStand,
this.InfostandOverlay, this.InfostandOverlay,
this.InfostandCardBg,
this.id); this.id);
} catch (SqlQueries.DataAccessException e) { } catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e); LOGGER.error("Caught SQL exception", e);
@@ -23,6 +23,8 @@ public class HabboInventory {
private ItemsComponent itemsComponent; private ItemsComponent itemsComponent;
private PetsComponent petsComponent; private PetsComponent petsComponent;
private PrefixesComponent prefixesComponent; private PrefixesComponent prefixesComponent;
private NickIconsComponent nickIconsComponent;
private UserVisualSettingsComponent userVisualSettingsComponent;
public HabboInventory(Habbo habbo) { public HabboInventory(Habbo habbo) {
this.habbo = habbo; this.habbo = habbo;
@@ -68,6 +70,18 @@ public class HabboInventory {
LOGGER.error("Caught exception", e); LOGGER.error("Caught exception", e);
} }
try {
this.nickIconsComponent = new NickIconsComponent(this.habbo);
} catch (Exception e) {
LOGGER.error("Caught exception", e);
}
try {
this.userVisualSettingsComponent = new UserVisualSettingsComponent(this.habbo);
} catch (Exception e) {
LOGGER.error("Caught exception", e);
}
this.items = MarketPlace.getOwnOffers(this.habbo); this.items = MarketPlace.getOwnOffers(this.habbo);
} }
@@ -127,6 +141,22 @@ public class HabboInventory {
this.prefixesComponent = prefixesComponent; this.prefixesComponent = prefixesComponent;
} }
public NickIconsComponent getNickIconsComponent() {
return this.nickIconsComponent;
}
public void setNickIconsComponent(NickIconsComponent nickIconsComponent) {
this.nickIconsComponent = nickIconsComponent;
}
public UserVisualSettingsComponent getUserVisualSettingsComponent() {
return this.userVisualSettingsComponent;
}
public void setUserVisualSettingsComponent(UserVisualSettingsComponent userVisualSettingsComponent) {
this.userVisualSettingsComponent = userVisualSettingsComponent;
}
public void dispose() { public void dispose() {
this.badgesComponent.dispose(); this.badgesComponent.dispose();
this.botsComponent.dispose(); this.botsComponent.dispose();
@@ -135,6 +165,8 @@ public class HabboInventory {
this.petsComponent.dispose(); this.petsComponent.dispose();
this.wardrobeComponent.dispose(); this.wardrobeComponent.dispose();
this.prefixesComponent.dispose(); this.prefixesComponent.dispose();
this.nickIconsComponent.dispose();
this.userVisualSettingsComponent.dispose();
this.badgesComponent = null; this.badgesComponent = null;
this.botsComponent = null; this.botsComponent = null;
@@ -143,6 +175,8 @@ public class HabboInventory {
this.petsComponent = null; this.petsComponent = null;
this.wardrobeComponent = null; this.wardrobeComponent = null;
this.prefixesComponent = null; this.prefixesComponent = null;
this.nickIconsComponent = null;
this.userVisualSettingsComponent = null;
} }
public void addMarketplaceOffer(MarketPlaceOffer marketPlaceOffer) { public void addMarketplaceOffer(MarketPlaceOffer marketPlaceOffer) {
@@ -0,0 +1,121 @@
package com.eu.habbo.habbohotel.users;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.users.inventory.UserVisualSettingsComponent;
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 UserCustomizationData {
private static final Logger LOGGER = LoggerFactory.getLogger(UserCustomizationData.class);
public final String nickIcon;
public final String displayOrder;
public final String prefixText;
public final String prefixColor;
public final String prefixIcon;
public final String prefixEffect;
public final String prefixFont;
private UserCustomizationData(String nickIcon, String displayOrder, String prefixText, String prefixColor, String prefixIcon, String prefixEffect, String prefixFont) {
this.nickIcon = nickIcon != null ? nickIcon : "";
this.displayOrder = UserVisualSettingsComponent.sanitizeDisplayOrder(displayOrder);
this.prefixText = prefixText != null ? prefixText : "";
this.prefixColor = prefixColor != null ? prefixColor : "";
this.prefixIcon = prefixIcon != null ? prefixIcon : "";
this.prefixEffect = prefixEffect != null ? prefixEffect : "";
this.prefixFont = prefixFont != null ? prefixFont : "";
}
public static UserCustomizationData fromHabbo(Habbo habbo) {
if (habbo == null) {
return empty();
}
String nickIcon = "";
String displayOrder = UserVisualSettingsComponent.DEFAULT_DISPLAY_ORDER;
String prefixText = "";
String prefixColor = "";
String prefixIcon = "";
String prefixEffect = "";
String prefixFont = "";
if (habbo.getInventory() != null) {
if (habbo.getInventory().getNickIconsComponent() != null) {
UserNickIcon activeNickIcon = habbo.getInventory().getNickIconsComponent().getActiveNickIcon();
if (activeNickIcon != null && activeNickIcon.getIconKey() != null) {
nickIcon = activeNickIcon.getIconKey();
}
}
if (habbo.getInventory().getPrefixesComponent() != null) {
UserPrefix activePrefix = habbo.getInventory().getPrefixesComponent().getActivePrefix();
if (activePrefix != null) {
prefixText = activePrefix.getText();
prefixColor = activePrefix.getColor();
prefixIcon = activePrefix.getIcon();
prefixEffect = activePrefix.getEffect();
prefixFont = activePrefix.getFont();
}
}
if (habbo.getInventory().getUserVisualSettingsComponent() != null) {
displayOrder = habbo.getInventory().getUserVisualSettingsComponent().getDisplayOrder();
}
}
return new UserCustomizationData(nickIcon, displayOrder, prefixText, prefixColor, prefixIcon, prefixEffect, prefixFont);
}
public static UserCustomizationData fromUserId(int userId) {
String nickIcon = "";
String prefixText = "";
String prefixColor = "";
String prefixIcon = "";
String prefixEffect = "";
String prefixFont = "";
String displayOrder = UserVisualSettingsComponent.loadDisplayOrder(userId);
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) {
try (PreparedStatement nickStatement = connection.prepareStatement(
"SELECT icon_key FROM user_nick_icons WHERE user_id = ? AND active = 1 LIMIT 1")) {
nickStatement.setInt(1, userId);
try (ResultSet set = nickStatement.executeQuery()) {
if (set.next()) {
nickIcon = set.getString("icon_key");
}
}
}
try (PreparedStatement prefixStatement = connection.prepareStatement(
"SELECT text, color, icon, effect, font FROM user_prefixes WHERE user_id = ? AND active = 1 LIMIT 1")) {
prefixStatement.setInt(1, userId);
try (ResultSet set = prefixStatement.executeQuery()) {
if (set.next()) {
prefixText = set.getString("text");
prefixColor = set.getString("color");
prefixIcon = set.getString("icon");
prefixEffect = set.getString("effect");
prefixFont = set.getString("font");
}
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception while loading user customization data", e);
}
return new UserCustomizationData(nickIcon, displayOrder, prefixText, prefixColor, prefixIcon, prefixEffect, prefixFont);
}
public static UserCustomizationData empty() {
return new UserCustomizationData("", UserVisualSettingsComponent.DEFAULT_DISPLAY_ORDER, "", "", "", "", "");
}
}
@@ -0,0 +1,118 @@
package com.eu.habbo.habbohotel.users;
import com.eu.habbo.Emulator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.*;
public class UserNickIcon implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(UserNickIcon.class);
private int id;
private final int userId;
private String iconKey;
private boolean active;
private boolean needsInsert;
private boolean needsUpdate;
private boolean needsDelete;
public UserNickIcon(ResultSet set) throws SQLException {
this.id = set.getInt("id");
this.userId = set.getInt("user_id");
this.iconKey = set.getString("icon_key");
this.active = set.getBoolean("active");
this.needsInsert = false;
this.needsUpdate = false;
this.needsDelete = false;
}
public UserNickIcon(int userId, String iconKey) {
this.id = 0;
this.userId = userId;
this.iconKey = iconKey;
this.active = false;
this.needsInsert = true;
this.needsUpdate = false;
this.needsDelete = false;
}
@Override
public void run() {
try {
if (this.needsInsert) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"INSERT INTO user_nick_icons (user_id, icon_key, active) VALUES (?, ?, ?)",
Statement.RETURN_GENERATED_KEYS)) {
statement.setInt(1, this.userId);
statement.setString(2, this.iconKey);
statement.setBoolean(3, this.active);
statement.execute();
try (ResultSet set = statement.getGeneratedKeys()) {
if (set.next()) {
this.id = set.getInt(1);
}
}
}
this.needsInsert = false;
} else if (this.needsDelete) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"DELETE FROM user_nick_icons WHERE id = ? AND user_id = ?")) {
statement.setInt(1, this.id);
statement.setInt(2, this.userId);
statement.execute();
}
this.needsDelete = false;
} else if (this.needsUpdate) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"UPDATE user_nick_icons SET icon_key = ?, active = ? WHERE id = ? AND user_id = ?")) {
statement.setString(1, this.iconKey);
statement.setBoolean(2, this.active);
statement.setInt(3, this.id);
statement.setInt(4, this.userId);
statement.execute();
}
this.needsUpdate = false;
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
}
public int getId() {
return this.id;
}
public int getUserId() {
return this.userId;
}
public String getIconKey() {
return this.iconKey;
}
public void setIconKey(String iconKey) {
this.iconKey = iconKey;
this.needsUpdate = true;
}
public boolean isActive() {
return this.active;
}
public void setActive(boolean active) {
this.active = active;
this.needsUpdate = true;
}
public void needsDelete(boolean needsDelete) {
this.needsDelete = needsDelete;
}
}
@@ -15,6 +15,12 @@ public class UserPrefix implements Runnable {
private String color; private String color;
private String icon; private String icon;
private String effect; private String effect;
private String font;
private int catalogPrefixId;
private String displayName;
private int points;
private int pointsType;
private boolean custom;
private boolean active; private boolean active;
private boolean needsInsert; private boolean needsInsert;
private boolean needsUpdate; private boolean needsUpdate;
@@ -29,6 +35,12 @@ public class UserPrefix implements Runnable {
if (this.icon == null) this.icon = ""; if (this.icon == null) this.icon = "";
this.effect = set.getString("effect"); this.effect = set.getString("effect");
if (this.effect == null) this.effect = ""; if (this.effect == null) this.effect = "";
this.font = readString(set, "font", "");
this.catalogPrefixId = readInt(set, "catalog_prefix_id", 0);
this.displayName = readString(set, "display_name", this.text);
this.points = readInt(set, "points", 0);
this.pointsType = readInt(set, "points_type", 0);
this.custom = readBoolean(set, "is_custom", true);
this.active = set.getBoolean("active"); this.active = set.getBoolean("active");
this.needsInsert = false; this.needsInsert = false;
this.needsUpdate = false; this.needsUpdate = false;
@@ -36,12 +48,22 @@ public class UserPrefix implements Runnable {
} }
public UserPrefix(int userId, String text, String color, String icon, String effect) { public UserPrefix(int userId, String text, String color, String icon, String effect) {
this(userId, text, color, icon, effect, "", 0, text, 0, 0, true);
}
public UserPrefix(int userId, String text, String color, String icon, String effect, String font, int catalogPrefixId, String displayName, int points, int pointsType, boolean custom) {
this.id = 0; this.id = 0;
this.userId = userId; this.userId = userId;
this.text = text; this.text = text;
this.color = color; this.color = color;
this.icon = icon != null ? icon : ""; this.icon = icon != null ? icon : "";
this.effect = effect != null ? effect : ""; this.effect = effect != null ? effect : "";
this.font = font != null ? font : "";
this.catalogPrefixId = catalogPrefixId;
this.displayName = (displayName != null && !displayName.isEmpty()) ? displayName : text;
this.points = points;
this.pointsType = pointsType;
this.custom = custom;
this.active = false; this.active = false;
this.needsInsert = true; this.needsInsert = true;
this.needsUpdate = false; this.needsUpdate = false;
@@ -54,14 +76,20 @@ public class UserPrefix implements Runnable {
if (this.needsInsert) { if (this.needsInsert) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement( PreparedStatement statement = connection.prepareStatement(
"INSERT INTO user_prefixes (user_id, text, color, icon, effect, active) VALUES (?, ?, ?, ?, ?, ?)", "INSERT INTO user_prefixes (user_id, text, color, icon, effect, font, active, catalog_prefix_id, display_name, points, points_type, is_custom) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
Statement.RETURN_GENERATED_KEYS)) { Statement.RETURN_GENERATED_KEYS)) {
statement.setInt(1, this.userId); statement.setInt(1, this.userId);
statement.setString(2, this.text); statement.setString(2, this.text);
statement.setString(3, this.color); statement.setString(3, this.color);
statement.setString(4, this.icon); statement.setString(4, this.icon);
statement.setString(5, this.effect); statement.setString(5, this.effect);
statement.setBoolean(6, this.active); statement.setString(6, this.font);
statement.setBoolean(7, this.active);
statement.setInt(8, this.catalogPrefixId);
statement.setString(9, this.displayName);
statement.setInt(10, this.points);
statement.setInt(11, this.pointsType);
statement.setBoolean(12, this.custom);
statement.execute(); statement.execute();
try (ResultSet set = statement.getGeneratedKeys()) { try (ResultSet set = statement.getGeneratedKeys()) {
if (set.next()) { if (set.next()) {
@@ -82,14 +110,20 @@ public class UserPrefix implements Runnable {
} else if (this.needsUpdate) { } else if (this.needsUpdate) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement( PreparedStatement statement = connection.prepareStatement(
"UPDATE user_prefixes SET text = ?, color = ?, icon = ?, effect = ?, active = ? WHERE id = ? AND user_id = ?")) { "UPDATE user_prefixes SET text = ?, color = ?, icon = ?, effect = ?, font = ?, active = ?, catalog_prefix_id = ?, display_name = ?, points = ?, points_type = ?, is_custom = ? WHERE id = ? AND user_id = ?")) {
statement.setString(1, this.text); statement.setString(1, this.text);
statement.setString(2, this.color); statement.setString(2, this.color);
statement.setString(3, this.icon); statement.setString(3, this.icon);
statement.setString(4, this.effect); statement.setString(4, this.effect);
statement.setBoolean(5, this.active); statement.setString(5, this.font);
statement.setInt(6, this.id); statement.setBoolean(6, this.active);
statement.setInt(7, this.userId); statement.setInt(7, this.catalogPrefixId);
statement.setString(8, this.displayName);
statement.setInt(9, this.points);
statement.setInt(10, this.pointsType);
statement.setBoolean(11, this.custom);
statement.setInt(12, this.id);
statement.setInt(13, this.userId);
statement.execute(); statement.execute();
} }
this.needsUpdate = false; this.needsUpdate = false;
@@ -109,6 +143,13 @@ public class UserPrefix implements Runnable {
public void setIcon(String icon) { this.icon = icon != null ? icon : ""; } public void setIcon(String icon) { this.icon = icon != null ? icon : ""; }
public String getEffect() { return this.effect; } public String getEffect() { return this.effect; }
public void setEffect(String effect) { this.effect = effect != null ? effect : ""; } public void setEffect(String effect) { this.effect = effect != null ? effect : ""; }
public String getFont() { return this.font; }
public void setFont(String font) { this.font = font != null ? font : ""; }
public int getCatalogPrefixId() { return this.catalogPrefixId; }
public String getDisplayName() { return this.displayName; }
public int getPoints() { return this.points; }
public int getPointsType() { return this.pointsType; }
public boolean isCustom() { return this.custom; }
public boolean isActive() { return this.active; } public boolean isActive() { return this.active; }
public void setActive(boolean active) { public void setActive(boolean active) {
@@ -119,4 +160,29 @@ public class UserPrefix implements Runnable {
public void needsUpdate(boolean needsUpdate) { this.needsUpdate = needsUpdate; } public void needsUpdate(boolean needsUpdate) { this.needsUpdate = needsUpdate; }
public void needsInsert(boolean needsInsert) { this.needsInsert = needsInsert; } public void needsInsert(boolean needsInsert) { this.needsInsert = needsInsert; }
public void needsDelete(boolean needsDelete) { this.needsDelete = needsDelete; } public void needsDelete(boolean needsDelete) { this.needsDelete = needsDelete; }
private static int readInt(ResultSet set, String columnName, int defaultValue) {
try {
return set.getInt(columnName);
} catch (SQLException e) {
return defaultValue;
}
}
private static String readString(ResultSet set, String columnName, String defaultValue) {
try {
String value = set.getString(columnName);
return value != null ? value : defaultValue;
} catch (SQLException e) {
return defaultValue;
}
}
private static boolean readBoolean(ResultSet set, String columnName, boolean defaultValue) {
try {
return set.getBoolean(columnName);
} catch (SQLException e) {
return defaultValue;
}
}
} }
@@ -0,0 +1,75 @@
package com.eu.habbo.habbohotel.users.custombadge;
import java.sql.ResultSet;
import java.sql.SQLException;
public class CustomBadge {
private final int id;
private final int userId;
private final String badgeId;
private String badgeName;
private String badgeDescription;
private final int dateCreated;
private int dateEdit;
public CustomBadge(ResultSet set) throws SQLException {
this.id = set.getInt("id");
this.userId = set.getInt("user_id");
this.badgeId = set.getString("badge_id");
this.badgeName = set.getString("badge_name");
this.badgeDescription = set.getString("badge_description");
this.dateCreated = set.getInt("date_created");
this.dateEdit = set.getInt("date_edit");
}
public CustomBadge(int id, int userId, String badgeId, String badgeName, String badgeDescription, int dateCreated, int dateEdit) {
this.id = id;
this.userId = userId;
this.badgeId = badgeId;
this.badgeName = badgeName;
this.badgeDescription = badgeDescription;
this.dateCreated = dateCreated;
this.dateEdit = dateEdit;
}
public int getId() {
return this.id;
}
public int getUserId() {
return this.userId;
}
public String getBadgeId() {
return this.badgeId;
}
public String getBadgeName() {
return this.badgeName;
}
public String getBadgeDescription() {
return this.badgeDescription;
}
public int getDateCreated() {
return this.dateCreated;
}
public int getDateEdit() {
return this.dateEdit;
}
public void setBadgeName(String badgeName) {
this.badgeName = badgeName;
}
public void setBadgeDescription(String badgeDescription) {
this.badgeDescription = badgeDescription;
}
public void setDateEdit(int dateEdit) {
this.dateEdit = dateEdit;
}
}
@@ -0,0 +1,15 @@
package com.eu.habbo.habbohotel.users.custombadge;
public class CustomBadgeException extends Exception {
private final String code;
public CustomBadgeException(String code, String message) {
super(message);
this.code = code;
}
public String getCode() {
return this.code;
}
}
@@ -0,0 +1,588 @@
package com.eu.habbo.habbohotel.users.custombadge;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboBadge;
import com.eu.habbo.habbohotel.users.inventory.BadgesComponent;
import com.eu.habbo.messages.outgoing.inventory.InventoryBadgesComposer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.SecureRandom;
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.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
public class CustomBadgeManager {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomBadgeManager.class);
public static final int MAX_PER_USER = 5;
public static final int BADGE_WIDTH = 40;
public static final int BADGE_HEIGHT = 40;
public static final int MAX_BADGE_SIZE_BYTES = 40960;
private static final int RANDOM_SUFFIX_LENGTH = 5;
private static final char[] RANDOM_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
private static final Pattern BADGE_ID_PATTERN = Pattern.compile("^CUST[A-Z0-9]{" + RANDOM_SUFFIX_LENGTH + "}-\\d+$");
private static final byte[] PNG_MAGIC = { (byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
private static final int RATE_LIMIT_OPS = 5;
private static final long RATE_LIMIT_WINDOW_MS = 60_000L;
private final SecureRandom random = new SecureRandom();
private final Map<Integer, long[]> rateBuckets = new ConcurrentHashMap<>();
private final Map<String, BadgeText> textCache = new ConcurrentHashMap<>();
private final java.util.concurrent.atomic.AtomicLong textCacheVersion = new java.util.concurrent.atomic.AtomicLong();
private volatile CustomBadgeSettings settings;
public CustomBadgeManager() {
this.reload();
}
public static final class BadgeText {
public final String name;
public final String description;
public BadgeText(String name, String description) {
this.name = name == null ? "" : name;
this.description = description == null ? "" : description;
}
}
public Map<String, BadgeText> getTextCache() {
return java.util.Collections.unmodifiableMap(this.textCache);
}
public long getTextCacheVersion() {
return this.textCacheVersion.get();
}
private void loadTextCache() {
Map<String, BadgeText> next = new java.util.HashMap<>();
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"SELECT `badge_id`, `badge_name`, `badge_description` FROM `user_custom_badge`")) {
try (ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
next.put(resultSet.getString("badge_id"),
new BadgeText(
resultSet.getString("badge_name"),
resultSet.getString("badge_description")));
}
}
} catch (SQLException e) {
LOGGER.error("CustomBadgeManager -> Failed to load badge text cache.", e);
return;
}
this.textCache.clear();
this.textCache.putAll(next);
this.textCacheVersion.incrementAndGet();
LOGGER.info("CustomBadgeManager -> loaded {} custom badge texts into memory.", next.size());
}
public void reload() {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"SELECT `badge_path`, `badge_url`, `price_badge`, `currency_type` FROM `users_custom_badge_settings` ORDER BY `id` ASC LIMIT 1")) {
try (ResultSet resultSet = statement.executeQuery()) {
if (resultSet.next()) {
this.settings = new CustomBadgeSettings(
resultSet.getString("badge_path"),
resultSet.getString("badge_url"),
resultSet.getInt("price_badge"),
resultSet.getInt("currency_type"));
} else {
this.settings = new CustomBadgeSettings(
"/var/www/gamedata/c_images/album1584",
"/gamedata/c_images/album1584",
0, -1);
LOGGER.warn("CustomBadgeManager -> No row found in users_custom_badge_settings, falling back to defaults.");
}
}
} catch (SQLException e) {
LOGGER.error("CustomBadgeManager -> Failed to load settings.", e);
}
loadTextCache();
}
public CustomBadgeSettings getSettings() {
return this.settings;
}
public List<CustomBadge> listForUser(int userId) {
List<CustomBadge> result = new ArrayList<>();
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"SELECT * FROM `user_custom_badge` WHERE `user_id` = ? ORDER BY `date_created` ASC")) {
statement.setInt(1, userId);
try (ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
result.add(new CustomBadge(resultSet));
}
}
} catch (SQLException e) {
LOGGER.error("CustomBadgeManager -> Failed to list badges for user " + userId, e);
}
return result;
}
public CustomBadge getByBadgeId(String badgeId) {
if (badgeId == null || badgeId.isEmpty()) return null;
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"SELECT * FROM `user_custom_badge` WHERE `badge_id` = ? LIMIT 1")) {
statement.setString(1, badgeId);
try (ResultSet resultSet = statement.executeQuery()) {
if (resultSet.next()) {
return new CustomBadge(resultSet);
}
}
} catch (SQLException e) {
LOGGER.error("CustomBadgeManager -> Failed to load badge " + badgeId, e);
}
return null;
}
public int countForUser(int userId) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"SELECT COUNT(*) FROM `user_custom_badge` WHERE `user_id` = ?")) {
statement.setInt(1, userId);
try (ResultSet resultSet = statement.executeQuery()) {
if (resultSet.next()) {
return resultSet.getInt(1);
}
}
} catch (SQLException e) {
LOGGER.error("CustomBadgeManager -> Failed to count badges for user " + userId, e);
}
return 0;
}
public CustomBadge create(int userId, String name, String description, byte[] pngBytes) throws CustomBadgeException {
enforceRateLimit(userId);
if (this.countForUser(userId) >= MAX_PER_USER) {
throw new CustomBadgeException("limit_reached", "Maximum of " + MAX_PER_USER + " custom badges reached.");
}
BufferedImage image = validatePng(pngBytes);
chargeForCreate(userId);
String badgeId = generateBadgeId();
int now = (int) (System.currentTimeMillis() / 1000L);
try {
writeBadgeFile(badgeId, image);
} catch (CustomBadgeException e) {
refundForCreate(userId);
throw e;
}
String safeName = sanitize(name, 64);
String safeDesc = sanitize(description, 255);
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"INSERT INTO `user_custom_badge` (`user_id`, `badge_id`, `badge_name`, `badge_description`, `date_created`, `date_edit`) VALUES (?, ?, ?, ?, ?, ?)",
Statement.RETURN_GENERATED_KEYS)) {
statement.setInt(1, userId);
statement.setString(2, badgeId);
statement.setString(3, safeName);
statement.setString(4, safeDesc);
statement.setInt(5, now);
statement.setInt(6, now);
statement.executeUpdate();
int generatedId = 0;
try (ResultSet keys = statement.getGeneratedKeys()) {
if (keys.next()) generatedId = keys.getInt(1);
}
this.textCache.put(badgeId, new BadgeText(safeName, safeDesc));
this.textCacheVersion.incrementAndGet();
issueBadgeToInventory(userId, badgeId);
return new CustomBadge(generatedId, userId, badgeId, safeName, safeDesc, now, now);
} catch (SQLException e) {
deleteBadgeFileQuietly(badgeId);
refundForCreate(userId);
LOGGER.error("CustomBadgeManager -> Failed to insert badge for user " + userId, e);
throw new CustomBadgeException("db_error", "Could not save the badge.");
}
}
public CustomBadge update(int userId, String oldBadgeId, String name, String description, byte[] pngBytes) throws CustomBadgeException {
enforceRateLimit(userId);
CustomBadge existing = getByBadgeId(oldBadgeId);
if (existing == null || existing.getUserId() != userId) {
throw new CustomBadgeException("not_found", "Badge not found.");
}
BufferedImage image = validatePng(pngBytes);
String newBadgeId = generateBadgeId();
int now = (int) (System.currentTimeMillis() / 1000L);
writeBadgeFile(newBadgeId, image);
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"UPDATE `user_custom_badge` SET `badge_id` = ?, `badge_name` = ?, `badge_description` = ?, `date_edit` = ? WHERE `id` = ?")) {
statement.setString(1, newBadgeId);
statement.setString(2, sanitize(name, 64));
statement.setString(3, sanitize(description, 255));
statement.setInt(4, now);
statement.setInt(5, existing.getId());
statement.executeUpdate();
} catch (SQLException e) {
deleteBadgeFileQuietly(newBadgeId);
LOGGER.error("CustomBadgeManager -> Failed to update badge " + oldBadgeId, e);
throw new CustomBadgeException("db_error", "Could not update the badge.");
}
String safeName = sanitize(name, 64);
String safeDesc = sanitize(description, 255);
this.textCache.remove(oldBadgeId);
this.textCache.put(newBadgeId, new BadgeText(safeName, safeDesc));
this.textCacheVersion.incrementAndGet();
renameBadgeInInventory(userId, oldBadgeId, newBadgeId);
deleteBadgeFileQuietly(oldBadgeId);
return new CustomBadge(existing.getId(), userId, newBadgeId, safeName, safeDesc, existing.getDateCreated(), now);
}
public void delete(int userId, String badgeId) throws CustomBadgeException {
enforceRateLimit(userId);
CustomBadge existing = getByBadgeId(badgeId);
if (existing == null || existing.getUserId() != userId) {
throw new CustomBadgeException("not_found", "Badge not found.");
}
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"DELETE FROM `user_custom_badge` WHERE `id` = ?")) {
statement.setInt(1, existing.getId());
statement.executeUpdate();
} catch (SQLException e) {
LOGGER.error("CustomBadgeManager -> Failed to delete badge " + badgeId, e);
throw new CustomBadgeException("db_error", "Could not delete the badge.");
}
this.textCache.remove(badgeId);
this.textCacheVersion.incrementAndGet();
revokeBadgeFromInventory(userId, badgeId);
deleteBadgeFileQuietly(badgeId);
}
public boolean isCustomBadgeId(String badgeId) {
return badgeId != null && BADGE_ID_PATTERN.matcher(badgeId).matches();
}
public String generateBadgeId() {
long timestamp = System.currentTimeMillis() / 1000L;
for (int attempt = 0; attempt < 8; attempt++) {
StringBuilder suffix = new StringBuilder(RANDOM_SUFFIX_LENGTH);
for (int i = 0; i < RANDOM_SUFFIX_LENGTH; i++) {
suffix.append(RANDOM_ALPHABET[this.random.nextInt(RANDOM_ALPHABET.length)]);
}
String candidate = "CUST" + suffix + "-" + timestamp;
if (getByBadgeId(candidate) == null) return candidate;
timestamp++;
}
throw new IllegalStateException("Could not allocate a unique custom badge id after 8 attempts.");
}
public String publicUrlFor(String badgeId) {
CustomBadgeSettings current = this.settings;
if (current == null) return "";
String base = current.getBadgeUrl();
if (base == null || base.isEmpty()) return "";
if (base.endsWith("/")) return base + badgeId + ".gif";
return base + "/" + badgeId + ".gif";
}
private void chargeForCreate(int userId) throws CustomBadgeException {
CustomBadgeSettings current = this.settings;
if (current == null) return;
int price = current.getPriceBadge();
if (price <= 0) return;
Habbo habbo = Emulator.getGameServer().getGameClientManager().getHabbo(userId);
if (habbo == null) {
throw new CustomBadgeException("must_be_online",
"You must be online in the hotel to create a paid badge.");
}
int currencyType = current.getCurrencyType();
if (currencyType == -1) {
if (habbo.getHabboInfo().getCredits() < price) {
throw new CustomBadgeException("insufficient_funds",
"You don't have enough credits (need " + price + ").");
}
habbo.giveCredits(-price);
} else {
if (habbo.getHabboInfo().getCurrencyAmount(currencyType) < price) {
throw new CustomBadgeException("insufficient_funds",
"You don't have enough of that currency (need " + price + ").");
}
habbo.givePoints(currencyType, -price);
}
}
private void issueBadgeToInventory(int userId, String badgeId) {
Habbo online = Emulator.getGameServer().getGameClientManager().getHabbo(userId);
if (online != null) {
BadgesComponent.createBadge(badgeId, online);
if (online.getClient() != null) {
online.getClient().sendResponse(new InventoryBadgesComposer(online));
}
return;
}
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"INSERT INTO `users_badges` (`user_id`, `slot_id`, `badge_code`) VALUES (?, 0, ?)")) {
statement.setInt(1, userId);
statement.setString(2, badgeId);
statement.executeUpdate();
} catch (SQLException e) {
LOGGER.error("CustomBadgeManager -> Failed to issue offline badge " + badgeId + " to user " + userId, e);
}
}
private void renameBadgeInInventory(int userId, String oldBadgeId, String newBadgeId) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"UPDATE `users_badges` SET `badge_code` = ? WHERE `user_id` = ? AND `badge_code` = ?")) {
statement.setString(1, newBadgeId);
statement.setInt(2, userId);
statement.setString(3, oldBadgeId);
statement.executeUpdate();
} catch (SQLException e) {
LOGGER.error("CustomBadgeManager -> Failed to rename badge in users_badges " + oldBadgeId + " -> " + newBadgeId, e);
}
Habbo online = Emulator.getGameServer().getGameClientManager().getHabbo(userId);
if (online == null) return;
HabboBadge existing = online.getInventory().getBadgesComponent().getBadge(oldBadgeId);
if (existing != null) existing.setCode(newBadgeId);
if (online.getClient() != null) {
online.getClient().sendResponse(new InventoryBadgesComposer(online));
}
}
private void revokeBadgeFromInventory(int userId, String badgeId) {
BadgesComponent.deleteBadge(userId, badgeId);
Habbo online = Emulator.getGameServer().getGameClientManager().getHabbo(userId);
if (online == null) return;
online.getInventory().getBadgesComponent().removeBadge(badgeId);
if (online.getClient() != null) {
online.getClient().sendResponse(new InventoryBadgesComposer(online));
}
}
private BufferedImage validatePng(byte[] data) throws CustomBadgeException {
if (data == null || data.length == 0) {
throw new CustomBadgeException("empty", "Badge image is empty.");
}
if (data.length > MAX_BADGE_SIZE_BYTES) {
throw new CustomBadgeException("too_large", "Badge image exceeds " + MAX_BADGE_SIZE_BYTES + " bytes.");
}
if (data.length < PNG_MAGIC.length) {
throw new CustomBadgeException("invalid_image", "Badge image must be a PNG.");
}
for (int i = 0; i < PNG_MAGIC.length; i++) {
if (data[i] != PNG_MAGIC[i]) {
throw new CustomBadgeException("invalid_image", "Badge image must be a PNG.");
}
}
try (ImageInputStream peek = ImageIO.createImageInputStream(new ByteArrayInputStream(data))) {
if (peek == null) throw new IOException("no input stream");
Iterator<ImageReader> readers = ImageIO.getImageReaders(peek);
if (!readers.hasNext()) {
throw new CustomBadgeException("invalid_image", "Badge image format not recognised.");
}
ImageReader reader = readers.next();
try {
reader.setInput(peek, true, true);
int w = reader.getWidth(0);
int h = reader.getHeight(0);
if (w != BADGE_WIDTH || h != BADGE_HEIGHT) {
throw new CustomBadgeException("wrong_dimensions",
"Badge image must be " + BADGE_WIDTH + "x" + BADGE_HEIGHT + " pixels.");
}
} finally {
reader.dispose();
}
} catch (IOException e) {
throw new CustomBadgeException("invalid_image", "Badge image header could not be read.");
}
BufferedImage image;
try {
image = ImageIO.read(new ByteArrayInputStream(data));
} catch (IOException e) {
throw new CustomBadgeException("invalid_image", "Badge image could not be decoded.");
}
if (image == null
|| image.getWidth() != BADGE_WIDTH
|| image.getHeight() != BADGE_HEIGHT) {
throw new CustomBadgeException("invalid_image", "Badge image could not be decoded.");
}
return image;
}
private void enforceRateLimit(int userId) throws CustomBadgeException {
long now = System.currentTimeMillis();
long[] bucket = this.rateBuckets.computeIfAbsent(userId, id -> new long[RATE_LIMIT_OPS]);
synchronized (bucket) {
long oldest = Long.MAX_VALUE;
int oldestIdx = 0;
for (int i = 0; i < bucket.length; i++) {
if (bucket[i] < oldest) { oldest = bucket[i]; oldestIdx = i; }
}
if (oldest > now - RATE_LIMIT_WINDOW_MS) {
throw new CustomBadgeException("rate_limited",
"Too many badge operations. Try again in a moment.");
}
bucket[oldestIdx] = now;
}
}
private void refundForCreate(int userId) {
CustomBadgeSettings current = this.settings;
if (current == null) return;
int price = current.getPriceBadge();
if (price <= 0) return;
Habbo habbo = Emulator.getGameServer().getGameClientManager().getHabbo(userId);
if (habbo == null) {
LOGGER.warn("CustomBadgeManager -> Could not refund {} (price {}): user offline", userId, price);
return;
}
int currencyType = current.getCurrencyType();
if (currencyType == -1) habbo.giveCredits(price);
else habbo.givePoints(currencyType, price);
}
private void writeBadgeFile(String badgeId, BufferedImage source) throws CustomBadgeException {
CustomBadgeSettings current = this.settings;
if (current == null || current.getBadgePath() == null || current.getBadgePath().isEmpty()) {
throw new CustomBadgeException("not_configured", "Custom badge storage path is not configured.");
}
try {
Path dir = Paths.get(current.getBadgePath()).toAbsolutePath();
Files.createDirectories(dir);
Path target = dir.resolve(badgeId + ".gif");
BufferedImage indexed = toIndexedGifImage(source);
if (!ImageIO.write(indexed, "gif", target.toFile())) {
throw new IOException("No GIF ImageWriter available.");
}
LOGGER.info("CustomBadgeManager -> wrote badge {} ({} bytes) to {}",
badgeId, Files.size(target), target);
} catch (IOException e) {
LOGGER.error("CustomBadgeManager -> Failed to write badge " + badgeId
+ " to " + current.getBadgePath(), e);
throw new CustomBadgeException("write_failed", "Could not save the badge file.");
}
}
private static BufferedImage toIndexedGifImage(BufferedImage source) {
int w = source.getWidth();
int h = source.getHeight();
int[] pixels = source.getRGB(0, 0, w, h, null, 0, w);
Map<Integer, Integer> indexByColor = new LinkedHashMap<>();
indexByColor.put(0, 0);
for (int p : pixels) {
int alpha = (p >>> 24) & 0xff;
int key = (alpha < 128) ? 0 : (p | 0xFF000000);
if (key == 0) continue;
if (indexByColor.size() >= 256) break;
indexByColor.computeIfAbsent(key, k -> indexByColor.size());
}
int n = indexByColor.size();
byte[] r = new byte[n];
byte[] g = new byte[n];
byte[] b = new byte[n];
int i = 0;
for (Integer color : indexByColor.keySet()) {
r[i] = (byte) ((color >>> 16) & 0xff);
g[i] = (byte) ((color >>> 8) & 0xff);
b[i] = (byte) (color & 0xff);
i++;
}
IndexColorModel colorModel = new IndexColorModel(8, n, r, g, b, 0);
BufferedImage out = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_INDEXED, colorModel);
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
int p = pixels[y * w + x];
int alpha = (p >>> 24) & 0xff;
int key = (alpha < 128) ? 0 : (p | 0xFF000000);
Integer idx = indexByColor.get(key);
out.getRaster().setSample(x, y, 0, idx == null ? 0 : idx);
}
}
return out;
}
private void deleteBadgeFileQuietly(String badgeId) {
CustomBadgeSettings current = this.settings;
if (current == null || current.getBadgePath() == null) return;
File file = new File(current.getBadgePath(), badgeId + ".gif");
if (file.exists() && !file.delete()) {
LOGGER.warn("CustomBadgeManager -> Could not delete stale badge file: {}", file.getAbsolutePath());
}
}
private static String sanitize(String value, int maxLength) {
if (value == null) return "";
StringBuilder out = new StringBuilder(Math.min(value.length(), maxLength));
for (int i = 0; i < value.length() && out.length() < maxLength; i++) {
char c = value.charAt(i);
if (c < 0x20 || c == 0x7F) continue;
out.append(c);
}
return out.toString().trim();
}
}
@@ -0,0 +1,32 @@
package com.eu.habbo.habbohotel.users.custombadge;
public class CustomBadgeSettings {
private final String badgePath;
private final String badgeUrl;
private final int priceBadge;
private final int currencyType;
public CustomBadgeSettings(String badgePath, String badgeUrl, int priceBadge, int currencyType) {
this.badgePath = badgePath;
this.badgeUrl = badgeUrl;
this.priceBadge = priceBadge;
this.currencyType = currencyType;
}
public String getBadgePath() {
return this.badgePath;
}
public String getBadgeUrl() {
return this.badgeUrl;
}
public int getPriceBadge() {
return this.priceBadge;
}
public int getCurrencyType() {
return this.currencyType;
}
}
@@ -0,0 +1,136 @@
package com.eu.habbo.habbohotel.users.infostand;
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.HabboStats;
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.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
public class InfostandBackgroundManager {
private static final Logger LOGGER = LoggerFactory.getLogger(InfostandBackgroundManager.class);
public enum Category {
BACKGROUND("background"),
STAND("stand"),
OVERLAY("overlay"),
CARD("card");
public final String dbValue;
Category(String dbValue) {
this.dbValue = dbValue;
}
public static Category fromDbValue(String value) {
for (Category category : values()) {
if (category.dbValue.equalsIgnoreCase(value)) return category;
}
return null;
}
}
private final Map<Category, Map<Integer, Entry>> entries = new EnumMap<>(Category.class);
private boolean enforce = false;
public InfostandBackgroundManager() {
for (Category category : Category.values()) {
this.entries.put(category, Collections.emptyMap());
}
this.reload();
}
public void reload() {
Map<Category, Map<Integer, Entry>> next = new EnumMap<>(Category.class);
for (Category category : Category.values()) {
next.put(category, new HashMap<>());
}
int loaded = 0;
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT id, category, min_rank, is_hc_only, is_ambassador_only FROM infostand_backgrounds");
ResultSet set = statement.executeQuery()) {
while (set.next()) {
Category category = Category.fromDbValue(set.getString("category"));
if (category == null) continue;
int id = set.getInt("id");
int minRank = set.getInt("min_rank");
boolean isHcOnly = set.getBoolean("is_hc_only");
boolean isAmbassadorOnly = set.getBoolean("is_ambassador_only");
next.get(category).put(id, new Entry(minRank, isHcOnly, isAmbassadorOnly));
loaded++;
}
} catch (SQLException e) {
this.enforce = false;
for (Category category : Category.values()) {
this.entries.put(category, Collections.emptyMap());
}
LOGGER.error("InfostandBackgroundManager -> Failed to load infostand_backgrounds, server-side validation disabled.", e);
return;
}
for (Category category : Category.values()) {
this.entries.put(category, next.get(category));
}
this.enforce = loaded > 0;
if (this.enforce) {
LOGGER.info("InfostandBackgroundManager -> Loaded {} backgrounds, {} stands, {} overlays, {} cards from infostand_backgrounds.",
this.entries.get(Category.BACKGROUND).size(),
this.entries.get(Category.STAND).size(),
this.entries.get(Category.OVERLAY).size(),
this.entries.get(Category.CARD).size());
} else {
LOGGER.info("InfostandBackgroundManager -> infostand_backgrounds is empty, server-side validation disabled (only range clamp will apply).");
}
}
public boolean canUse(Habbo habbo, Category category, int id) {
if (id == 0) return true;
if (!this.enforce) return true;
if (habbo == null) return false;
Map<Integer, Entry> categoryEntries = this.entries.get(category);
if (categoryEntries == null) return false;
Entry entry = categoryEntries.get(id);
if (entry == null) return false;
HabboInfo info = habbo.getHabboInfo();
int rankId = (info != null && info.getRank() != null) ? info.getRank().getId() : 0;
HabboStats stats = habbo.getHabboStats();
boolean hasClub = stats != null && stats.hasActiveClub();
if (entry.isHcOnly && !hasClub) return false;
if (entry.isAmbassadorOnly && !habbo.hasPermission(Permission.ACC_AMBASSADOR)) return false;
if (rankId < entry.minRank) return false;
return true;
}
public static final class Entry {
public final int minRank;
public final boolean isHcOnly;
public final boolean isAmbassadorOnly;
public Entry(int minRank, boolean isHcOnly, boolean isAmbassadorOnly) {
this.minRank = minRank;
this.isHcOnly = isHcOnly;
this.isAmbassadorOnly = isAmbassadorOnly;
}
}
}
@@ -1,7 +1,6 @@
package com.eu.habbo.habbohotel.users.inventory; package com.eu.habbo.habbohotel.users.inventory;
import com.eu.habbo.Emulator; import com.eu.habbo.Emulator;
import com.eu.habbo.database.SqlQueries;
import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboInventory; import com.eu.habbo.habbohotel.users.HabboInventory;
@@ -10,6 +9,7 @@ import com.eu.habbo.plugin.events.inventory.InventoryItemAddedEvent;
import com.eu.habbo.plugin.events.inventory.InventoryItemRemovedEvent; import com.eu.habbo.plugin.events.inventory.InventoryItemRemovedEvent;
import com.eu.habbo.plugin.events.inventory.InventoryItemsAddedEvent; import com.eu.habbo.plugin.events.inventory.InventoryItemsAddedEvent;
import gnu.trove.TCollections; import gnu.trove.TCollections;
import gnu.trove.iterator.TIntObjectIterator;
import gnu.trove.map.TIntObjectMap; import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.THashMap; import gnu.trove.map.hash.THashMap;
import gnu.trove.map.hash.TIntObjectHashMap; import gnu.trove.map.hash.TIntObjectHashMap;
@@ -18,9 +18,11 @@ 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.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.NoSuchElementException;
import java.util.List;
public class ItemsComponent { public class ItemsComponent {
private static final Logger LOGGER = LoggerFactory.getLogger(ItemsComponent.class); private static final Logger LOGGER = LoggerFactory.getLogger(ItemsComponent.class);
@@ -37,23 +39,25 @@ public class ItemsComponent {
public static THashMap<Integer, HabboItem> loadItems(Habbo habbo) { public static THashMap<Integer, HabboItem> loadItems(Habbo habbo) {
THashMap<Integer, HabboItem> itemsList = new THashMap<>(); THashMap<Integer, HabboItem> itemsList = new THashMap<>();
try { try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT items.* FROM items LEFT JOIN builders_club_items ON builders_club_items.item_id = items.id WHERE items.room_id = ? AND items.user_id = ? AND builders_club_items.item_id IS NULL")) {
SqlQueries.forEach( statement.setInt(1, 0);
"SELECT * FROM items WHERE room_id = ? AND user_id = ?", statement.setInt(2, habbo.getHabboInfo().getId());
rs -> { try (ResultSet set = statement.executeQuery()) {
try { while (set.next()) {
HabboItem item = Emulator.getGameEnvironment().getItemManager().loadHabboItem(rs); try {
if (item != null) { HabboItem item = Emulator.getGameEnvironment().getItemManager().loadHabboItem(set);
itemsList.put(rs.getInt("id"), item);
} else { if (item != null) {
LOGGER.error("Failed to load HabboItem: {}", rs.getInt("id")); itemsList.put(set.getInt("id"), item);
} } else {
} catch (SQLException e) { LOGGER.error("Failed to load HabboItem: {}", set.getInt("id"));
LOGGER.error("Caught SQL exception", e);
} }
}, } catch (SQLException e) {
0, habbo.getHabboInfo().getId()); LOGGER.error("Caught SQL exception", e);
} catch (SqlQueries.DataAccessException e) { }
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e); LOGGER.error("Caught SQL exception", e);
} }
@@ -147,45 +151,70 @@ public class ItemsComponent {
public void dispose() { public void dispose() {
synchronized (this.items) { synchronized (this.items) {
if (!this.items.isEmpty()) { TIntObjectIterator<HabboItem> items = this.items.iterator();
List<HabboItem> updates = new ArrayList<>();
List<HabboItem> deletes = new ArrayList<>();
for (HabboItem item : this.items.valueCollection()) {
if (item.needsDelete()) {
deletes.add(item);
item.needsUpdate(false);
item.needsDelete(false);
} else if (item.needsUpdate()) {
updates.add(item);
item.needsUpdate(false);
}
}
try { if (items == null) {
if (!deletes.isEmpty()) { LOGGER.error("Items is NULL!");
SqlQueries.batchUpdate( return;
"DELETE FROM items WHERE id = ?", }
deletes,
(ps, item) -> ps.setInt(1, item.getId())); if (!this.items.isEmpty()) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) {
try (PreparedStatement updateStmt = connection.prepareStatement(
"UPDATE items SET user_id = ?, room_id = ?, wall_pos = ?, x = ?, y = ?, z = ?, rot = ?, extra_data = ?, limited_data = ? WHERE id = ?")) {
try (PreparedStatement deleteStmt = connection.prepareStatement(
"DELETE FROM items WHERE id = ?")) {
int updateCount = 0;
int deleteCount = 0;
for (int i = this.items.size(); i-- > 0; ) {
try {
items.advance();
} catch (NoSuchElementException e) {
break;
}
HabboItem item = items.value();
if (item.needsDelete()) {
deleteStmt.setInt(1, item.getId());
deleteStmt.addBatch();
deleteCount++;
item.needsUpdate(false);
item.needsDelete(false);
} else if (item.needsUpdate()) {
updateStmt.setInt(1, item.getUserId());
updateStmt.setInt(2, item.getRoomId());
updateStmt.setString(3, item.getWallPosition());
updateStmt.setInt(4, item.getX());
updateStmt.setInt(5, item.getY());
updateStmt.setDouble(6, item.getZ());
updateStmt.setInt(7, item.getRotation());
updateStmt.setString(8, item.getExtradata());
updateStmt.setString(9, item.getLimitedStack() + ":" + item.getLimitedSells());
updateStmt.setInt(10, item.getId());
updateStmt.addBatch();
updateCount++;
item.needsUpdate(false);
}
if (updateCount > 0 && updateCount % 100 == 0) {
updateStmt.executeBatch();
}
if (deleteCount > 0 && deleteCount % 100 == 0) {
deleteStmt.executeBatch();
}
}
if (deleteCount % 100 != 0) {
deleteStmt.executeBatch();
}
if (updateCount % 100 != 0) {
updateStmt.executeBatch();
}
}
} }
if (!updates.isEmpty()) { } catch (SQLException e) {
SqlQueries.batchUpdate(
"UPDATE items SET user_id = ?, room_id = ?, wall_pos = ?, x = ?, y = ?, z = ?, rot = ?, extra_data = ?, limited_data = ? WHERE id = ?",
updates,
(ps, item) -> {
ps.setInt(1, item.getUserId());
ps.setInt(2, item.getRoomId());
ps.setString(3, item.getWallPosition());
ps.setInt(4, item.getX());
ps.setInt(5, item.getY());
ps.setDouble(6, item.getZ());
ps.setInt(7, item.getRotation());
ps.setString(8, item.getExtradata());
ps.setString(9, item.getLimitedStack() + ":" + item.getLimitedSells());
ps.setInt(10, item.getId());
});
}
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception during batch item save", e); LOGGER.error("Caught SQL exception during batch item save", e);
} }
} }
@@ -0,0 +1,119 @@
package com.eu.habbo.habbohotel.users.inventory;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.UserNickIcon;
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.util.ArrayList;
import java.util.List;
public class NickIconsComponent {
private static final Logger LOGGER = LoggerFactory.getLogger(NickIconsComponent.class);
private final List<UserNickIcon> nickIcons = new ArrayList<>();
private final Habbo habbo;
public NickIconsComponent(Habbo habbo) {
this.habbo = habbo;
this.loadNickIcons();
}
private void loadNickIcons() {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT * FROM user_nick_icons WHERE user_id = ?")) {
statement.setInt(1, this.habbo.getHabboInfo().getId());
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
this.nickIcons.add(new UserNickIcon(set));
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
}
public List<UserNickIcon> getNickIcons() {
synchronized (this.nickIcons) {
return new ArrayList<>(this.nickIcons);
}
}
public UserNickIcon getActiveNickIcon() {
synchronized (this.nickIcons) {
for (UserNickIcon nickIcon : this.nickIcons) {
if (nickIcon.isActive()) {
return nickIcon;
}
}
}
return null;
}
public UserNickIcon getNickIcon(int id) {
synchronized (this.nickIcons) {
for (UserNickIcon nickIcon : this.nickIcons) {
if (nickIcon.getId() == id) {
return nickIcon;
}
}
}
return null;
}
public UserNickIcon getNickIconByKey(String iconKey) {
synchronized (this.nickIcons) {
for (UserNickIcon nickIcon : this.nickIcons) {
if (nickIcon.getIconKey().equalsIgnoreCase(iconKey)) {
return nickIcon;
}
}
}
return null;
}
public void addNickIcon(UserNickIcon nickIcon) {
synchronized (this.nickIcons) {
this.nickIcons.add(nickIcon);
}
}
public void setActive(int nickIconId) {
synchronized (this.nickIcons) {
for (UserNickIcon nickIcon : this.nickIcons) {
boolean shouldBeActive = (nickIcon.getId() == nickIconId);
if (nickIcon.isActive() != shouldBeActive) {
nickIcon.setActive(shouldBeActive);
Emulator.getThreading().run(nickIcon);
}
}
}
}
public void deactivateAll() {
synchronized (this.nickIcons) {
for (UserNickIcon nickIcon : this.nickIcons) {
if (nickIcon.isActive()) {
nickIcon.setActive(false);
Emulator.getThreading().run(nickIcon);
}
}
}
}
public void dispose() {
synchronized (this.nickIcons) {
this.nickIcons.clear();
}
}
}
@@ -62,6 +62,15 @@ public class PrefixesComponent {
return null; return null;
} }
public UserPrefix getPrefixByCatalogId(int catalogPrefixId) {
synchronized (this.prefixes) {
for (UserPrefix prefix : this.prefixes) {
if (prefix.getCatalogPrefixId() == catalogPrefixId) return prefix;
}
}
return null;
}
public void addPrefix(UserPrefix prefix) { public void addPrefix(UserPrefix prefix) {
synchronized (this.prefixes) { synchronized (this.prefixes) {
this.prefixes.add(prefix); this.prefixes.add(prefix);
@@ -0,0 +1,94 @@
package com.eu.habbo.habbohotel.users.inventory;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.users.Habbo;
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.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class UserVisualSettingsComponent {
private static final Logger LOGGER = LoggerFactory.getLogger(UserVisualSettingsComponent.class);
public static final String DEFAULT_DISPLAY_ORDER = "icon-prefix-name";
private static final Set<String> ALLOWED_PARTS = new HashSet<>(Arrays.asList("icon", "prefix", "name"));
private final Habbo habbo;
private String displayOrder = DEFAULT_DISPLAY_ORDER;
public UserVisualSettingsComponent(Habbo habbo) {
this.habbo = habbo;
this.loadSettings();
}
private void loadSettings() {
this.displayOrder = loadDisplayOrder(this.habbo.getHabboInfo().getId());
}
public String getDisplayOrder() {
return sanitizeDisplayOrder(this.displayOrder);
}
public void setDisplayOrder(String displayOrder) {
this.displayOrder = sanitizeDisplayOrder(displayOrder);
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"INSERT INTO user_visual_settings (user_id, display_order) VALUES (?, ?) ON DUPLICATE KEY UPDATE display_order = VALUES(display_order)")) {
statement.setInt(1, this.habbo.getHabboInfo().getId());
statement.setString(2, this.displayOrder);
statement.executeUpdate();
} catch (SQLException e) {
LOGGER.error("Caught SQL exception while saving user visual settings", e);
}
}
public static String loadDisplayOrder(int userId) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"SELECT display_order FROM user_visual_settings WHERE user_id = ? LIMIT 1")) {
statement.setInt(1, userId);
try (ResultSet set = statement.executeQuery()) {
if (set.next()) {
return sanitizeDisplayOrder(set.getString("display_order"));
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception while loading user visual settings", e);
}
return DEFAULT_DISPLAY_ORDER;
}
public static String sanitizeDisplayOrder(String displayOrder) {
if (displayOrder == null || displayOrder.trim().isEmpty()) {
return DEFAULT_DISPLAY_ORDER;
}
String[] parts = displayOrder.trim().toLowerCase().split("-");
if (parts.length != 3) {
return DEFAULT_DISPLAY_ORDER;
}
Set<String> uniqueParts = new HashSet<>();
for (String part : parts) {
if (!ALLOWED_PARTS.contains(part) || !uniqueParts.add(part)) {
return DEFAULT_DISPLAY_ORDER;
}
}
return String.join("-", parts);
}
public void dispose() {
this.displayOrder = DEFAULT_DISPLAY_ORDER;
}
}
@@ -77,6 +77,22 @@ public interface IWiredEffect {
return false; return false;
} }
/**
* Selectors can use this to gate stack execution after their target list has
* been resolved. Returning false stops the stack before conditions/effects.
*/
default boolean hasRequiredSelectorTargets(WiredContext ctx) {
return true;
}
/**
* Selectors that filter the current selector result should run after
* selectors that create/replace that result.
*/
default boolean usesExistingSelectorTargets() {
return false;
}
/** /**
* Simulate this effect's execution and record intended state changes. * Simulate this effect's execution and record intended state changes.
* <p> * <p>
@@ -112,7 +112,7 @@ public final class WiredContext {
this.state = state; this.state = state;
this.legacySettings = legacySettings; this.legacySettings = legacySettings;
this.contextVariables = (event.getContextVariableScope() != null) this.contextVariables = (event.getContextVariableScope() != null)
? event.getContextVariableScope() ? event.getContextVariableScope().copy()
: new WiredContextVariableScope(); : new WiredContextVariableScope();
this.targets = new WiredTargets(); this.targets = new WiredTargets();
@@ -26,6 +26,7 @@ import com.eu.habbo.habbohotel.wired.api.WiredStack;
import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.ServerMessage;
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.GenericAlertComposer; import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer;
import com.eu.habbo.messages.outgoing.rooms.items.ItemStateComposer;
import com.eu.habbo.plugin.events.furniture.wired.WiredStackExecutedEvent; import com.eu.habbo.plugin.events.furniture.wired.WiredStackExecutedEvent;
import com.eu.habbo.plugin.events.furniture.wired.WiredStackTriggeredEvent; import com.eu.habbo.plugin.events.furniture.wired.WiredStackTriggeredEvent;
import gnu.trove.map.hash.THashMap; import gnu.trove.map.hash.THashMap;
@@ -130,6 +131,9 @@ public final class WiredEngine {
/** Cache room+eventType+sourceItemId -> matching stacks for source-triggered timer events */ /** Cache room+eventType+sourceItemId -> matching stacks for source-triggered timer events */
private final ConcurrentHashMap<String, List<WiredStack>> sourceStacksByTriggerKey; private final ConcurrentHashMap<String, List<WiredStack>> sourceStacksByTriggerKey;
/** Track filter-selector animation tokens so rapid executions do not reset newer animations */
private final ConcurrentHashMap<Integer, Long> filteredSelectorAnimationTokens;
/** /**
* Create a new wired engine. * Create a new wired engine.
* *
@@ -151,6 +155,7 @@ public final class WiredEngine {
this.bannedRooms = new ConcurrentHashMap<>(); this.bannedRooms = new ConcurrentHashMap<>();
this.roomDiagnostics = new ConcurrentHashMap<>(); this.roomDiagnostics = new ConcurrentHashMap<>();
this.sourceStacksByTriggerKey = new ConcurrentHashMap<>(); this.sourceStacksByTriggerKey = new ConcurrentHashMap<>();
this.filteredSelectorAnimationTokens = new ConcurrentHashMap<>();
} }
/** /**
@@ -426,6 +431,10 @@ public final class WiredEngine {
applySelectionFilterExtras(stack, ctx, executedSelectors); applySelectionFilterExtras(stack, ctx, executedSelectors);
} }
if (!selectorsHaveRequiredTargets(executedSelectors, ctx)) {
return false;
}
boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, negateConditions); boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, negateConditions);
List<IWiredEffect> executableEffects = getExecutableEffectsForCurrentExecution(stack, conditionsPassedForExecution); List<IWiredEffect> executableEffects = getExecutableEffectsForCurrentExecution(stack, conditionsPassedForExecution);
boolean hasSpecialOutcome = conditionsPassedForExecution && hasSpecialTriggerOutcome(stack, event); boolean hasSpecialOutcome = conditionsPassedForExecution && hasSpecialTriggerOutcome(stack, event);
@@ -541,6 +550,10 @@ public final class WiredEngine {
applySelectionFilterExtras(stack, ctx, executedSelectors); applySelectionFilterExtras(stack, ctx, executedSelectors);
} }
if (!selectorsHaveRequiredTargets(executedSelectors, ctx)) {
return false;
}
boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, negateConditions); boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, negateConditions);
List<IWiredEffect> executableEffects = getExecutableEffectsForCurrentExecution(stack, conditionsPassedForExecution); List<IWiredEffect> executableEffects = getExecutableEffectsForCurrentExecution(stack, conditionsPassedForExecution);
boolean hasSpecialOutcome = conditionsPassedForExecution && hasSpecialTriggerOutcome(stack, event); boolean hasSpecialOutcome = conditionsPassedForExecution && hasSpecialTriggerOutcome(stack, event);
@@ -627,6 +640,10 @@ public final class WiredEngine {
applySelectionFilterExtras(stack, ctx, executedSelectors); applySelectionFilterExtras(stack, ctx, executedSelectors);
} }
if (!selectorsHaveRequiredTargets(executedSelectors, ctx)) {
return false;
}
boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, negateConditions); boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, negateConditions);
List<IWiredEffect> executableEffects = getExecutableEffectsForCurrentExecution(stack, conditionsPassedForExecution); List<IWiredEffect> executableEffects = getExecutableEffectsForCurrentExecution(stack, conditionsPassedForExecution);
return !executableEffects.isEmpty(); return !executableEffects.isEmpty();
@@ -660,6 +677,10 @@ public final class WiredEngine {
applySelectionFilterExtras(stack, ctx, executedSelectors); applySelectionFilterExtras(stack, ctx, executedSelectors);
} }
if (!selectorsHaveRequiredTargets(executedSelectors, ctx)) {
return false;
}
boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, false); boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, false);
if (!conditionsPassedForExecution) { if (!conditionsPassedForExecution) {
return false; return false;
@@ -1011,9 +1032,27 @@ public final class WiredEngine {
if (effects.isEmpty()) return Collections.emptyList(); if (effects.isEmpty()) return Collections.emptyList();
List<InteractionWiredEffect> executedSelectors = new ArrayList<>(); List<InteractionWiredEffect> executedSelectors = new ArrayList<>();
List<IWiredEffect> immediateSelectors = new ArrayList<>();
List<IWiredEffect> deferredSelectors = new ArrayList<>();
for (IWiredEffect effect : effects) { for (IWiredEffect effect : effects) {
if (!effect.isSelector()) continue; if (!effect.isSelector()) continue;
if (effect.usesExistingSelectorTargets()) {
deferredSelectors.add(effect);
} else {
immediateSelectors.add(effect);
}
}
executeSelectorList(immediateSelectors, ctx, executedSelectors);
executeSelectorList(deferredSelectors, ctx, executedSelectors);
return executedSelectors;
}
private void executeSelectorList(List<IWiredEffect> selectors, WiredContext ctx, List<InteractionWiredEffect> executedSelectors) {
for (IWiredEffect effect : selectors) {
if (effect.requiresActor() && !ctx.hasActor()) { if (effect.requiresActor() && !ctx.hasActor()) {
continue; continue;
} }
@@ -1022,14 +1061,17 @@ public final class WiredEngine {
try { try {
effect.execute(ctx); effect.execute(ctx);
if (effect instanceof InteractionWiredEffect) { if (effect instanceof InteractionWiredEffect) {
executedSelectors.add((InteractionWiredEffect) effect); InteractionWiredEffect wiredEffect = (InteractionWiredEffect) effect;
executedSelectors.add(wiredEffect);
if (wiredEffect.usesExistingSelectorTargets()) {
setFilteredSelectorState(ctx.room(), wiredEffect, "3");
}
} }
} catch (Exception e) { } catch (Exception e) {
LOGGER.warn("Error executing selector: {}", e.getMessage()); LOGGER.warn("Error executing selector: {}", e.getMessage());
} }
} }
return executedSelectors;
} }
private void finalizeSelectors(List<InteractionWiredEffect> executedSelectors, WiredContext ctx, long currentTime) { private void finalizeSelectors(List<InteractionWiredEffect> executedSelectors, WiredContext ctx, long currentTime) {
@@ -1042,7 +1084,56 @@ public final class WiredEngine {
for (InteractionWiredEffect wiredEffect : executedSelectors) { for (InteractionWiredEffect wiredEffect : executedSelectors) {
wiredEffect.setCooldown(currentTime); wiredEffect.setCooldown(currentTime);
wiredEffect.activateBox(room, actor, currentTime);
if (wiredEffect.usesExistingSelectorTargets()) {
animateFilteredSelectorBox(room, wiredEffect);
} else {
wiredEffect.activateBox(room, actor, currentTime);
}
}
}
private void animateFilteredSelectorBox(Room room, InteractionWiredEffect wiredEffect) {
if (room == null || wiredEffect == null || room.isHideWired()) {
return;
}
long animationToken = System.nanoTime();
this.filteredSelectorAnimationTokens.put(wiredEffect.getId(), animationToken);
setFilteredSelectorState(room, wiredEffect, "3", animationToken, false);
scheduleFilteredSelectorState(room, wiredEffect, "4", animationToken, 80L, false);
scheduleFilteredSelectorState(room, wiredEffect, "5", animationToken, 160L, false);
scheduleFilteredSelectorState(room, wiredEffect, "3", animationToken, 240L, true);
}
private void scheduleFilteredSelectorState(Room room, InteractionWiredEffect wiredEffect, String state, long animationToken, long delay, boolean clearToken) {
Emulator.getThreading().run(() -> setFilteredSelectorState(room, wiredEffect, state, animationToken, clearToken), delay);
}
private void setFilteredSelectorState(Room room, InteractionWiredEffect wiredEffect, String state) {
setFilteredSelectorState(room, wiredEffect, state, 0L, false);
}
private void setFilteredSelectorState(Room room, InteractionWiredEffect wiredEffect, String state, long animationToken, boolean clearToken) {
if (room == null || wiredEffect == null || room.isHideWired()) {
return;
}
if (animationToken != 0L) {
Long currentToken = this.filteredSelectorAnimationTokens.get(wiredEffect.getId());
if (currentToken == null || currentToken != animationToken) {
return;
}
}
if (!state.equals(wiredEffect.getExtradata())) {
wiredEffect.setExtradata(state);
room.sendComposer(new ItemStateComposer(wiredEffect).compose());
}
if (clearToken) {
this.filteredSelectorAnimationTokens.remove(wiredEffect.getId(), animationToken);
} }
} }
@@ -1059,6 +1150,20 @@ public final class WiredEngine {
WiredSelectionFilterSupport.applySelectorFilters(room, stack.triggerItem(), ctx); WiredSelectionFilterSupport.applySelectorFilters(room, stack.triggerItem(), ctx);
} }
private boolean selectorsHaveRequiredTargets(List<InteractionWiredEffect> executedSelectors, WiredContext ctx) {
if (executedSelectors == null || executedSelectors.isEmpty()) {
return true;
}
for (InteractionWiredEffect selector : executedSelectors) {
if (!selector.hasRequiredSelectorTargets(ctx)) {
return false;
}
}
return true;
}
/** /**
* Schedule a delayed effect execution. * Schedule a delayed effect execution.
*/ */
@@ -151,8 +151,20 @@ public final class WiredSourceUtil {
selectorCtx.setIncludeWiredSelectorItems(originalCtx.includeWiredSelectorItems()); selectorCtx.setIncludeWiredSelectorItems(originalCtx.includeWiredSelectorItems());
List<InteractionWiredEffect> selectorEffects = getOrderedSelectorEffects(originalCtx, room, triggerItem); List<InteractionWiredEffect> selectorEffects = getOrderedSelectorEffects(originalCtx, room, triggerItem);
executeSelectorEffects(selectorCtx, selectorEffects, false);
executeSelectorEffects(selectorCtx, selectorEffects, true);
applySelectionFilterExtras(room, triggerItem, selectorCtx);
return selectorCtx;
}
private static void executeSelectorEffects(WiredContext selectorCtx, List<InteractionWiredEffect> selectorEffects, boolean deferred) {
for (InteractionWiredEffect effect : selectorEffects) { for (InteractionWiredEffect effect : selectorEffects) {
if (effect == null || effect.usesExistingSelectorTargets() != deferred) {
continue;
}
if (effect.requiresActor() && !selectorCtx.hasActor()) { if (effect.requiresActor() && !selectorCtx.hasActor()) {
continue; continue;
} }
@@ -163,10 +175,6 @@ public final class WiredSourceUtil {
} catch (Exception ignored) { } catch (Exception ignored) {
} }
} }
applySelectionFilterExtras(room, triggerItem, selectorCtx);
return selectorCtx;
} }
private static WiredContext cloneSelectorContext(WiredContext originalCtx, boolean includeWiredItems) { private static WiredContext cloneSelectorContext(WiredContext originalCtx, boolean includeWiredItems) {
@@ -64,8 +64,17 @@ public final class WiredTextInputCaptureSupport {
return trigger.matches(stack.triggerItem(), event) ? CaptureResult.matched(new LinkedHashMap<>()) : CaptureResult.noMatch(); return trigger.matches(stack.triggerItem(), event) ? CaptureResult.matched(new LinkedHashMap<>()) : CaptureResult.noMatch();
} }
MatchResult matchResult = matchTemplate(trigger, text, capturersByName); MatchResult matchResult = matchTemplate(trigger, text, capturersByName, room);
if (!matchResult.matches) { if (!matchResult.matches) {
if (WiredManager.isDebugEnabled()) {
WiredManager.debug("[TextCapture] NO_MATCH room={} triggerId={} mode={} key='{}' text='{}' len={}",
room.getId(),
stack.triggerItem().getId(),
trigger.getMatchMode(),
safeForLog(trigger.getKey()),
safeForLog(text),
(text != null ? text.length() : 0));
}
return CaptureResult.noMatch(); return CaptureResult.noMatch();
} }
@@ -78,12 +87,28 @@ public final class WiredTextInputCaptureSupport {
Integer resolvedValue = capturer.resolveCapturedValue(room, capture.getValue()); Integer resolvedValue = capturer.resolveCapturedValue(room, capture.getValue());
if (resolvedValue == null) { if (resolvedValue == null) {
if (WiredManager.isDebugEnabled()) {
WiredManager.debug("[TextCapture] RESOLVE_FAIL room={} triggerId={} capturer='{}' raw='{}' rawLen={}",
room.getId(),
stack.triggerItem().getId(),
capture.getKey(),
safeForLog(capture.getValue()),
(capture.getValue() != null ? capture.getValue().length() : 0));
}
return CaptureResult.noMatch(); return CaptureResult.noMatch();
} }
capturedValues.put(capturer.getVariableItemId(), resolvedValue); capturedValues.put(capturer.getVariableItemId(), resolvedValue);
} }
if (WiredManager.isDebugEnabled()) {
WiredManager.debug("[TextCapture] MATCH_OK room={} triggerId={} captures={} textLen={}",
room.getId(),
stack.triggerItem().getId(),
capturedValues.size(),
(text != null ? text.length() : 0));
}
return CaptureResult.matched(capturedValues); return CaptureResult.matched(capturedValues);
} }
@@ -108,12 +133,13 @@ public final class WiredTextInputCaptureSupport {
return capturers; return capturers;
} }
private static MatchResult matchTemplate(WiredTriggerHabboSaysKeyword trigger, String rawText, Map<String, WiredExtraTextInputVariable> capturersByName) { private static MatchResult matchTemplate(WiredTriggerHabboSaysKeyword trigger, String rawText, Map<String, WiredExtraTextInputVariable> capturersByName, Room room) {
String text = rawText != null ? rawText.trim() : ""; String text = rawText != null ? rawText : "";
String normalizedText = text.trim();
String template = trigger.getKey() != null ? trigger.getKey().trim() : ""; String template = trigger.getKey() != null ? trigger.getKey().trim() : "";
if (trigger.getMatchMode() == MATCH_ALL_WORDS && template.isEmpty()) { if (trigger.getMatchMode() == MATCH_ALL_WORDS && template.isEmpty()) {
if (capturersByName.size() != 1 || text.isEmpty()) { if (capturersByName.size() != 1 || normalizedText.isEmpty()) {
return MatchResult.noMatch(); return MatchResult.noMatch();
} }
@@ -123,12 +149,24 @@ public final class WiredTextInputCaptureSupport {
return MatchResult.matched(captures); return MatchResult.matched(captures);
} }
MatchResult adjacentCaptureResult = matchAdjacentCapturers(template, rawText, capturersByName, room, trigger.getMatchMode());
if (adjacentCaptureResult != null) {
if (WiredManager.isDebugEnabled()) {
WiredManager.debug("[TextCapture] ADJACENT mode used key='{}' textLen={} matched={}",
safeForLog(template),
(rawText != null ? rawText.length() : 0),
adjacentCaptureResult.matches);
}
return adjacentCaptureResult;
}
TemplatePattern pattern = buildPattern(template); TemplatePattern pattern = buildPattern(template);
if (pattern == null) { if (pattern == null) {
return MatchResult.noMatch(); return MatchResult.noMatch();
} }
Matcher matcher = pattern.pattern.matcher(text); String matchText = pattern.placeholderNames.isEmpty() ? normalizedText : text;
Matcher matcher = pattern.pattern.matcher(matchText);
boolean matches = (trigger.getMatchMode() == MATCH_CONTAINS) ? matcher.find() : matcher.matches(); boolean matches = (trigger.getMatchMode() == MATCH_CONTAINS) ? matcher.find() : matcher.matches();
if (!matches) { if (!matches) {
return MatchResult.noMatch(); return MatchResult.noMatch();
@@ -142,12 +180,136 @@ public final class WiredTextInputCaptureSupport {
} }
String capturedValue = matcher.group(index + 1); String capturedValue = matcher.group(index + 1);
captures.put(placeholderName, capturedValue != null ? capturedValue.trim() : ""); captures.put(placeholderName, normalizeCapturedValue(capturedValue));
} }
return MatchResult.matched(captures); return MatchResult.matched(captures);
} }
private static MatchResult matchAdjacentCapturers(String template, String rawText, Map<String, WiredExtraTextInputVariable> capturersByName, Room room, int matchMode) {
if (template == null || template.isEmpty() || rawText == null || capturersByName == null || capturersByName.isEmpty() || room == null) {
return null;
}
Matcher matcher = PLACEHOLDER_PATTERN.matcher(template);
List<String> placeholderNames = new ArrayList<>();
int cursor = 0;
while (matcher.find()) {
if (matcher.start() != cursor) {
return null;
}
String placeholderName = matcher.group(1) != null ? matcher.group(1).trim().toLowerCase() : "";
if (placeholderName.isEmpty() || !capturersByName.containsKey(placeholderName)) {
return null;
}
placeholderNames.add(placeholderName);
cursor = matcher.end();
}
if (placeholderNames.isEmpty() || cursor != template.length()) {
return null;
}
int placeholderCount = placeholderNames.size();
int textLength = rawText.length();
boolean[][] reachable = new boolean[placeholderCount + 1][textLength + 1];
int[][] previousIndex = new int[placeholderCount + 1][textLength + 1];
String[][] capturedValues = new String[placeholderCount + 1][textLength + 1];
for (int placeholderIndex = 0; placeholderIndex <= placeholderCount; placeholderIndex++) {
for (int textIndex = 0; textIndex <= textLength; textIndex++) {
previousIndex[placeholderIndex][textIndex] = -1;
}
}
reachable[0][0] = true;
for (int placeholderIndex = 0; placeholderIndex < placeholderCount; placeholderIndex++) {
String placeholderName = placeholderNames.get(placeholderIndex);
WiredExtraTextInputVariable capturer = capturersByName.get(placeholderName);
if (capturer == null) {
return MatchResult.noMatch();
}
for (int textIndex = 0; textIndex <= textLength; textIndex++) {
if (!reachable[placeholderIndex][textIndex]) {
continue;
}
int minEndIndex = (textIndex < textLength) ? (textIndex + 1) : textIndex;
for (int endIndex = minEndIndex; endIndex <= textLength; endIndex++) {
if (reachable[placeholderIndex + 1][endIndex]) {
continue;
}
String candidate = rawText.substring(textIndex, endIndex);
if (capturer.resolveCapturedValue(room, candidate) == null) {
continue;
}
reachable[placeholderIndex + 1][endIndex] = true;
previousIndex[placeholderIndex + 1][endIndex] = textIndex;
capturedValues[placeholderIndex + 1][endIndex] = candidate;
}
}
}
int resultEndIndex = -1;
if (matchMode == MATCH_CONTAINS) {
for (int endIndex = textLength; endIndex >= 0; endIndex--) {
if (reachable[placeholderCount][endIndex]) {
resultEndIndex = endIndex;
break;
}
}
} else if (reachable[placeholderCount][textLength]) {
resultEndIndex = textLength;
}
if (resultEndIndex < 0) {
return MatchResult.noMatch();
}
LinkedHashMap<String, String> captures = new LinkedHashMap<>();
int backtrackTextIndex = resultEndIndex;
for (int placeholderIndex = placeholderCount; placeholderIndex > 0; placeholderIndex--) {
String placeholderName = placeholderNames.get(placeholderIndex - 1);
String capturedValue = capturedValues[placeholderIndex][backtrackTextIndex];
captures.put(placeholderName, capturedValue != null ? capturedValue : "");
backtrackTextIndex = previousIndex[placeholderIndex][backtrackTextIndex];
if (backtrackTextIndex < 0) {
return MatchResult.noMatch();
}
}
return MatchResult.matched(captures);
}
private static String normalizeCapturedValue(String value) {
return value != null ? value : "";
}
private static String safeForLog(String value) {
if (value == null) {
return "";
}
String normalized = value
.replace("\r", "\\r")
.replace("\n", "\\n")
.replace("\u00A0", "");
if (normalized.length() > 180) {
return normalized.substring(0, 180) + "...(" + normalized.length() + ")";
}
return normalized;
}
private static TemplatePattern buildPattern(String template) { private static TemplatePattern buildPattern(String template) {
if (template == null || template.isEmpty()) { if (template == null || template.isEmpty()) {
return null; return null;
@@ -160,7 +322,7 @@ public final class WiredTextInputCaptureSupport {
while (matcher.find()) { while (matcher.find()) {
regex.append(Pattern.quote(template.substring(cursor, matcher.start()))); regex.append(Pattern.quote(template.substring(cursor, matcher.start())));
regex.append("(.+?)"); regex.append(hasPlaceholderAfter(template, matcher.end()) ? "(.+?)" : "(.+)");
String placeholderName = matcher.group(1) != null ? matcher.group(1).trim().toLowerCase() : ""; String placeholderName = matcher.group(1) != null ? matcher.group(1).trim().toLowerCase() : "";
placeholderNames.add(placeholderName); placeholderNames.add(placeholderName);
@@ -176,6 +338,10 @@ public final class WiredTextInputCaptureSupport {
return new TemplatePattern(Pattern.compile(regex.toString(), Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE), placeholderNames); return new TemplatePattern(Pattern.compile(regex.toString(), Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE), placeholderNames);
} }
private static boolean hasPlaceholderAfter(String template, int cursor) {
return PLACEHOLDER_PATTERN.matcher(template.substring(cursor)).find();
}
public static void applyToContext(WiredContext ctx, Room room, CaptureResult captureResult) { public static void applyToContext(WiredContext ctx, Room room, CaptureResult captureResult) {
if (ctx == null || room == null || captureResult == null || !captureResult.matches || captureResult.capturedValues.isEmpty()) { if (ctx == null || room == null || captureResult == null || !captureResult.matches || captureResult.capturedValues.isEmpty()) {
return; return;
@@ -32,6 +32,8 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
public final class WiredTextPlaceholderUtil { public final class WiredTextPlaceholderUtil {
private static final char PRESERVED_SPACE = '\u00A0';
private WiredTextPlaceholderUtil() { private WiredTextPlaceholderUtil() {
} }
@@ -87,7 +89,41 @@ public final class WiredTextPlaceholderUtil {
} }
} }
return resolvedText; return preserveRepeatedSpaces(resolvedText);
}
private static String preserveRepeatedSpaces(String text) {
if (text == null || text.length() < 2) {
return text;
}
StringBuilder result = new StringBuilder(text.length());
int index = 0;
while (index < text.length()) {
char currentChar = text.charAt(index);
if (currentChar != ' ') {
result.append(currentChar);
index++;
continue;
}
int startIndex = index;
while (index < text.length() && text.charAt(index) == ' ') {
index++;
}
int spaceCount = index - startIndex;
if (spaceCount == 1) {
result.append(' ');
continue;
}
for (int spaceIndex = 0; spaceIndex < spaceCount; spaceIndex++) {
result.append(PRESERVED_SPACE);
}
}
return result.toString();
} }
public static boolean requiresActor(Room room, HabboItem stackItem) { public static boolean requiresActor(Room room, HabboItem stackItem) {
@@ -275,7 +311,7 @@ public final class WiredTextPlaceholderUtil {
} }
String value = resolveRoomVariableValue(room, extra); String value = resolveRoomVariableValue(room, extra);
return (value == null || value.isEmpty()) ? List.of() : List.of(value); return value == null ? List.of() : List.of(value);
} }
private static List<String> collectContextVariableValues(WiredContext ctx, WiredExtraTextOutputVariable extra) { private static List<String> collectContextVariableValues(WiredContext ctx, WiredExtraTextOutputVariable extra) {
@@ -284,7 +320,7 @@ public final class WiredTextPlaceholderUtil {
} }
String value = resolveContextVariableValue(ctx, extra); String value = resolveContextVariableValue(ctx, extra);
return (value == null || value.isEmpty()) ? List.of() : List.of(value); return value == null ? List.of() : List.of(value);
} }
private static String resolveUserVariableValue(Room room, RoomUnit roomUnit, WiredExtraTextOutputVariable extra) { private static String resolveUserVariableValue(Room room, RoomUnit roomUnit, WiredExtraTextOutputVariable extra) {
@@ -11,6 +11,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
public final class WiredVariableTextConnectorSupport { public final class WiredVariableTextConnectorSupport {
private static final String PRESERVED_SPACE = "\u00A0";
private WiredVariableTextConnectorSupport() { private WiredVariableTextConnectorSupport() {
} }
@@ -71,7 +73,7 @@ public final class WiredVariableTextConnectorSupport {
Map<Integer, String> mappings = connector.getMappings(); Map<Integer, String> mappings = connector.getMappings();
if (mappings.containsKey(value)) { if (mappings.containsKey(value)) {
String mappedValue = mappings.get(value); String mappedValue = mappings.get(value);
return mappedValue != null ? mappedValue : String.valueOf(value); return mappedValue != null ? preserveSpaces(mappedValue) : "";
} }
} }
@@ -83,10 +85,7 @@ public final class WiredVariableTextConnectorSupport {
return null; return null;
} }
String normalizedText = text.trim(); String normalizedText = normalizePreservedSpaces(text);
if (normalizedText.isEmpty()) {
return null;
}
for (WiredExtraVariableTextConnector connector : getConnectors(room, definitionItemId)) { for (WiredExtraVariableTextConnector connector : getConnectors(room, definitionItemId)) {
Integer mappedValue = connector.resolveValue(normalizedText); Integer mappedValue = connector.resolveValue(normalizedText);
@@ -97,4 +96,12 @@ public final class WiredVariableTextConnectorSupport {
return null; return null;
} }
private static String preserveSpaces(String value) {
return value.replace(" ", PRESERVED_SPACE);
}
private static String normalizePreservedSpaces(String value) {
return value.replace(PRESERVED_SPACE, " ");
}
} }
@@ -36,6 +36,7 @@ import com.eu.habbo.messages.incoming.helper.MySanctionStatusEvent;
import com.eu.habbo.messages.incoming.helper.RequestTalentTrackEvent; import com.eu.habbo.messages.incoming.helper.RequestTalentTrackEvent;
import com.eu.habbo.messages.incoming.hotelview.*; import com.eu.habbo.messages.incoming.hotelview.*;
import com.eu.habbo.messages.incoming.inventory.*; import com.eu.habbo.messages.incoming.inventory.*;
import com.eu.habbo.messages.incoming.inventory.nickicons.*;
import com.eu.habbo.messages.incoming.inventory.prefixes.*; import com.eu.habbo.messages.incoming.inventory.prefixes.*;
import com.eu.habbo.messages.incoming.modtool.*; import com.eu.habbo.messages.incoming.modtool.*;
import com.eu.habbo.messages.incoming.navigator.*; import com.eu.habbo.messages.incoming.navigator.*;
@@ -61,6 +62,8 @@ import com.eu.habbo.messages.incoming.rooms.promotions.RequestPromotionRoomsEven
import com.eu.habbo.messages.incoming.rooms.promotions.UpdateRoomPromotionEvent; import com.eu.habbo.messages.incoming.rooms.promotions.UpdateRoomPromotionEvent;
import com.eu.habbo.messages.incoming.rooms.users.*; import com.eu.habbo.messages.incoming.rooms.users.*;
import com.eu.habbo.messages.incoming.trading.*; import com.eu.habbo.messages.incoming.trading.*;
import com.eu.habbo.messages.incoming.translation.TranslationLanguagesRequestEvent;
import com.eu.habbo.messages.incoming.translation.TranslationTextRequestEvent;
import com.eu.habbo.messages.incoming.unknown.RequestResolutionEvent; import com.eu.habbo.messages.incoming.unknown.RequestResolutionEvent;
import com.eu.habbo.messages.incoming.unknown.UnknownEvent1; import com.eu.habbo.messages.incoming.unknown.UnknownEvent1;
import com.eu.habbo.messages.incoming.users.*; import com.eu.habbo.messages.incoming.users.*;
@@ -117,6 +120,7 @@ public class PacketManager {
this.registerGuilds(); this.registerGuilds();
this.registerPets(); this.registerPets();
this.registerWired(); this.registerWired();
this.registerTranslation();
this.registerAchievements(); this.registerAchievements();
this.registerFloorPlanEditor(); this.registerFloorPlanEditor();
this.registerAmbassadors(); this.registerAmbassadors();
@@ -409,6 +413,13 @@ public class PacketManager {
this.registerHandler(Incoming.SetActivePrefixEvent, SetActivePrefixEvent.class); this.registerHandler(Incoming.SetActivePrefixEvent, SetActivePrefixEvent.class);
this.registerHandler(Incoming.DeletePrefixEvent, DeletePrefixEvent.class); this.registerHandler(Incoming.DeletePrefixEvent, DeletePrefixEvent.class);
this.registerHandler(Incoming.PurchasePrefixEvent, PurchasePrefixEvent.class); this.registerHandler(Incoming.PurchasePrefixEvent, PurchasePrefixEvent.class);
this.registerHandler(Incoming.PurchaseCatalogPrefixEvent, PurchaseCatalogPrefixEvent.class);
this.registerHandler(Incoming.SetDisplayOrderEvent, SetDisplayOrderEvent.class);
// Nick Icons
this.registerHandler(Incoming.RequestUserNickIconsEvent, RequestUserNickIconsEvent.class);
this.registerHandler(Incoming.PurchaseNickIconEvent, PurchaseNickIconEvent.class);
this.registerHandler(Incoming.SetActiveNickIconEvent, SetActiveNickIconEvent.class);
} }
void registerRooms() throws Exception { void registerRooms() throws Exception {
@@ -635,6 +646,11 @@ public class PacketManager {
this.registerHandler(Incoming.WiredUserInspectMoveEvent, WiredUserInspectMoveEvent.class); this.registerHandler(Incoming.WiredUserInspectMoveEvent, WiredUserInspectMoveEvent.class);
} }
void registerTranslation() throws Exception {
this.registerHandler(Incoming.TranslationLanguagesRequestEvent, TranslationLanguagesRequestEvent.class);
this.registerHandler(Incoming.TranslationTextRequestEvent, TranslationTextRequestEvent.class);
}
void registerUnknown() throws Exception { void registerUnknown() throws Exception {
this.registerHandler(Incoming.RequestResolutionEvent, RequestResolutionEvent.class); this.registerHandler(Incoming.RequestResolutionEvent, RequestResolutionEvent.class);
this.registerHandler(Incoming.RequestTalenTrackEvent, RequestTalentTrackEvent.class); this.registerHandler(Incoming.RequestTalenTrackEvent, RequestTalentTrackEvent.class);
@@ -419,6 +419,8 @@ public class Incoming {
public static final int WiredUserVariableUpdateEvent = 10025; public static final int WiredUserVariableUpdateEvent = 10025;
public static final int WiredUserVariableManageEvent = 10026; public static final int WiredUserVariableManageEvent = 10026;
public static final int WiredUserInspectMoveEvent = 10027; public static final int WiredUserInspectMoveEvent = 10027;
public static final int TranslationLanguagesRequestEvent = 10032;
public static final int TranslationTextRequestEvent = 10033;
public static final int RequestInventoryPetDelete = 10030; public static final int RequestInventoryPetDelete = 10030;
public static final int RequestInventoryBadgeDelete = 10031; public static final int RequestInventoryBadgeDelete = 10031;
@@ -448,6 +450,11 @@ public class Incoming {
public static final int SetActivePrefixEvent = 7012; public static final int SetActivePrefixEvent = 7012;
public static final int DeletePrefixEvent = 7013; public static final int DeletePrefixEvent = 7013;
public static final int PurchasePrefixEvent = 7014; public static final int PurchasePrefixEvent = 7014;
public static final int RequestUserNickIconsEvent = 7015;
public static final int PurchaseNickIconEvent = 7016;
public static final int SetActiveNickIconEvent = 7017;
public static final int PurchaseCatalogPrefixEvent = 7018;
public static final int SetDisplayOrderEvent = 7019;
// YouTube Room Broadcast // YouTube Room Broadcast
public static final int YouTubeRoomPlayEvent = 8001; public static final int YouTubeRoomPlayEvent = 8001;
@@ -0,0 +1,95 @@
package com.eu.habbo.messages.incoming.inventory.nickicons;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.UserNickIcon;
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.inventory.nickicons.UserNickIconsComposer;
import com.eu.habbo.messages.outgoing.users.UserCurrencyComposer;
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 PurchaseNickIconEvent extends MessageHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(PurchaseNickIconEvent.class);
@Override
public int getRatelimit() {
return 500;
}
@Override
public void handle() throws Exception {
Habbo habbo = this.client.getHabbo();
if (habbo == null) {
return;
}
String requestedIconKey = normalizeIconKey(this.packet.readString());
if (requestedIconKey.isEmpty()) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Invalid nick icon selected."));
return;
}
if (habbo.getInventory().getNickIconsComponent().getNickIconByKey(requestedIconKey) != null) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "You already own this nick icon."));
return;
}
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT points, points_type, enabled FROM custom_nick_icons_catalog WHERE icon_key = ? LIMIT 1")) {
statement.setString(1, requestedIconKey);
try (ResultSet set = statement.executeQuery()) {
if (!set.next() || !set.getBoolean("enabled")) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "This nick icon is not available."));
return;
}
int points = set.getInt("points");
int pointsType = set.getInt("points_type");
if (points > 0 && habbo.getHabboInfo().getCurrencyAmount(pointsType) < points) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Not enough points."));
return;
}
if (points > 0) {
habbo.getHabboInfo().addCurrencyAmount(pointsType, -points);
this.client.sendResponse(new UserCurrencyComposer(habbo));
}
UserNickIcon nickIcon = new UserNickIcon(habbo.getHabboInfo().getId(), requestedIconKey);
nickIcon.run();
habbo.getInventory().getNickIconsComponent().addNickIcon(nickIcon);
this.client.sendResponse(new UserNickIconsComposer(habbo));
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Unable to purchase this nick icon right now."));
}
}
private String normalizeIconKey(String iconKey) {
if (iconKey == null) {
return "";
}
String normalized = iconKey.trim().toLowerCase();
if (normalized.endsWith(".gif")) {
normalized = normalized.substring(0, normalized.length() - 4);
}
return normalized.matches("^[a-z0-9_-]+$") ? normalized : "";
}
}
@@ -0,0 +1,11 @@
package com.eu.habbo.messages.incoming.inventory.nickicons;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.inventory.nickicons.UserNickIconsComposer;
public class RequestUserNickIconsEvent extends MessageHandler {
@Override
public void handle() throws Exception {
this.client.sendResponse(new UserNickIconsComposer(this.client.getHabbo()));
}
}
@@ -0,0 +1,34 @@
package com.eu.habbo.messages.incoming.inventory.nickicons;
import com.eu.habbo.habbohotel.users.UserNickIcon;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.inventory.nickicons.UserNickIconsComposer;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDataComposer;
public class SetActiveNickIconEvent extends MessageHandler {
@Override
public void handle() throws Exception {
int nickIconId = this.packet.readInt();
if (nickIconId == 0) {
this.client.getHabbo().getInventory().getNickIconsComponent().deactivateAll();
this.client.sendResponse(new UserNickIconsComposer(this.client.getHabbo()));
if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != null) {
this.client.getHabbo().getHabboInfo().getCurrentRoom().sendComposer(new RoomUserDataComposer(this.client.getHabbo()).compose());
}
return;
}
UserNickIcon nickIcon = this.client.getHabbo().getInventory().getNickIconsComponent().getNickIcon(nickIconId);
if (nickIcon == null) {
return;
}
this.client.getHabbo().getInventory().getNickIconsComponent().setActive(nickIconId);
this.client.sendResponse(new UserNickIconsComposer(this.client.getHabbo()));
if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != null) {
this.client.getHabbo().getHabboInfo().getCurrentRoom().sendComposer(new RoomUserDataComposer(this.client.getHabbo()).compose());
}
}
}
@@ -0,0 +1,84 @@
package com.eu.habbo.messages.incoming.inventory.prefixes;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.UserPrefix;
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.inventory.nickicons.UserNickIconsComposer;
import com.eu.habbo.messages.outgoing.users.UserCurrencyComposer;
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 PurchaseCatalogPrefixEvent extends MessageHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(PurchaseCatalogPrefixEvent.class);
@Override
public int getRatelimit() {
return 500;
}
@Override
public void handle() throws Exception {
int catalogPrefixId = this.packet.readInt();
Habbo habbo = this.client.getHabbo();
if (habbo == null || catalogPrefixId <= 0) {
return;
}
if (habbo.getInventory().getPrefixesComponent().getPrefixByCatalogId(catalogPrefixId) != null) {
this.client.sendResponse(new UserNickIconsComposer(habbo));
return;
}
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"SELECT display_name, text, color, icon, effect, font, points, points_type FROM custom_prefixes_catalog WHERE id = ? AND enabled = 1 LIMIT 1")) {
statement.setInt(1, catalogPrefixId);
try (ResultSet set = statement.executeQuery()) {
if (!set.next()) {
return;
}
int points = set.getInt("points");
int pointsType = set.getInt("points_type");
if (points > 0 && habbo.getHabboInfo().getCurrencyAmount(pointsType) < points) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Not enough points."));
return;
}
if (points > 0) {
habbo.getHabboInfo().addCurrencyAmount(pointsType, -points);
this.client.sendResponse(new UserCurrencyComposer(habbo));
}
UserPrefix prefix = new UserPrefix(
habbo.getHabboInfo().getId(),
set.getString("text"),
set.getString("color"),
set.getString("icon"),
set.getString("effect"),
set.getString("font"),
catalogPrefixId,
set.getString("display_name"),
points,
pointsType,
false);
prefix.run();
habbo.getInventory().getPrefixesComponent().addPrefix(prefix);
this.client.sendResponse(new UserNickIconsComposer(habbo));
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception while purchasing catalog prefix", e);
}
}
}
@@ -6,6 +6,7 @@ import com.eu.habbo.habbohotel.users.UserPrefix;
import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.incoming.MessageHandler;
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.BubbleAlertComposer; import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
import com.eu.habbo.messages.outgoing.inventory.nickicons.UserNickIconsComposer;
import com.eu.habbo.messages.outgoing.inventory.prefixes.PrefixReceivedComposer; import com.eu.habbo.messages.outgoing.inventory.prefixes.PrefixReceivedComposer;
import com.eu.habbo.messages.outgoing.users.UserCreditsComposer; import com.eu.habbo.messages.outgoing.users.UserCreditsComposer;
import com.eu.habbo.messages.outgoing.users.UserCurrencyComposer; import com.eu.habbo.messages.outgoing.users.UserCurrencyComposer;
@@ -19,6 +20,7 @@ import java.sql.SQLException;
public class PurchasePrefixEvent extends MessageHandler { public class PurchasePrefixEvent extends MessageHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(PurchasePrefixEvent.class); private static final Logger LOGGER = LoggerFactory.getLogger(PurchasePrefixEvent.class);
private static final String[] ALLOWED_FONTS = { "", "pixel", "cherry", "vampiro" };
@Override @Override
public int getRatelimit() { public int getRatelimit() {
@@ -31,6 +33,7 @@ public class PurchasePrefixEvent extends MessageHandler {
String color = this.packet.readString(); String color = this.packet.readString();
String icon = this.packet.readString(); String icon = this.packet.readString();
String effect = this.packet.readString(); String effect = this.packet.readString();
String font = this.packet.readString();
Habbo habbo = this.client.getHabbo(); Habbo habbo = this.client.getHabbo();
@@ -42,6 +45,9 @@ public class PurchasePrefixEvent extends MessageHandler {
int priceCredits = getSettingInt("price_credits", 5); int priceCredits = getSettingInt("price_credits", 5);
int pricePoints = getSettingInt("price_points", 0); int pricePoints = getSettingInt("price_points", 0);
int pointsType = getSettingInt("points_type", 0); int pointsType = getSettingInt("points_type", 0);
int fontPriceCredits = getSettingInt("font_price_credits", 10);
int fontPricePoints = getSettingInt("font_price_points", 0);
int fontPointsType = getSettingInt("font_points_type", pointsType);
// Validate text // Validate text
text = text.trim(); text = text.trim();
@@ -72,43 +78,67 @@ public class PurchasePrefixEvent extends MessageHandler {
return; return;
} }
if (icon == null) icon = "";
icon = icon.trim();
if (effect == null) effect = "";
effect = effect.trim();
if (font == null) font = "";
font = font.trim().toLowerCase();
if (!isAllowedFont(font)) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Invalid font format."));
return;
}
int totalPriceCredits = priceCredits + (!font.isEmpty() ? fontPriceCredits : 0);
// Check credits // Check credits
if (priceCredits > 0 && habbo.getHabboInfo().getCredits() < priceCredits) { if (totalPriceCredits > 0 && habbo.getHabboInfo().getCredits() < totalPriceCredits) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Not enough credits.")); this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Not enough credits."));
return; return;
} }
int totalPricePointsSameType = pricePoints + ((fontPricePoints > 0 && fontPointsType == pointsType && !font.isEmpty()) ? fontPricePoints : 0);
// Check points // Check points
if (pricePoints > 0 && habbo.getHabboInfo().getCurrencyAmount(pointsType) < pricePoints) { if (totalPricePointsSameType > 0 && habbo.getHabboInfo().getCurrencyAmount(pointsType) < totalPricePointsSameType) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Not enough points."));
return;
}
if (!font.isEmpty() && fontPricePoints > 0 && fontPointsType != pointsType && habbo.getHabboInfo().getCurrencyAmount(fontPointsType) < fontPricePoints) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Not enough points.")); this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Not enough points."));
return; return;
} }
// Deduct currency // Deduct currency
if (priceCredits > 0) { if (totalPriceCredits > 0) {
habbo.getHabboInfo().addCredits(-priceCredits); habbo.getHabboInfo().addCredits(-totalPriceCredits);
this.client.sendResponse(new UserCreditsComposer(habbo)); this.client.sendResponse(new UserCreditsComposer(habbo));
} }
if (pricePoints > 0) { if (totalPricePointsSameType > 0) {
habbo.getHabboInfo().addCurrencyAmount(pointsType, -pricePoints); habbo.getHabboInfo().addCurrencyAmount(pointsType, -totalPricePointsSameType);
this.client.sendResponse(new UserCurrencyComposer(habbo)); this.client.sendResponse(new UserCurrencyComposer(habbo));
} }
// Validate icon (allow empty or known icon names) if (!font.isEmpty() && fontPricePoints > 0 && fontPointsType != pointsType) {
if (icon == null) icon = ""; habbo.getHabboInfo().addCurrencyAmount(fontPointsType, -fontPricePoints);
icon = icon.trim(); this.client.sendResponse(new UserCurrencyComposer(habbo));
}
// Validate effect
if (effect == null) effect = "";
effect = effect.trim();
// Create prefix // Create prefix
UserPrefix prefix = new UserPrefix(habbo.getHabboInfo().getId(), text, color, icon, effect); int storedPoints = totalPricePointsSameType;
int storedPointsType = (storedPoints > 0) ? pointsType : ((!font.isEmpty() && fontPricePoints > 0) ? fontPointsType : pointsType);
UserPrefix prefix = new UserPrefix(habbo.getHabboInfo().getId(), text, color, icon, effect, font, 0, text, storedPoints, storedPointsType, true);
prefix.run(); // Insert into DB synchronously to get the ID prefix.run(); // Insert into DB synchronously to get the ID
habbo.getInventory().getPrefixesComponent().addPrefix(prefix); habbo.getInventory().getPrefixesComponent().addPrefix(prefix);
this.client.sendResponse(new PrefixReceivedComposer(prefix)); this.client.sendResponse(new PrefixReceivedComposer(prefix));
this.client.sendResponse(new UserNickIconsComposer(habbo));
} }
private int getSettingInt(String key, int defaultValue) { private int getSettingInt(String key, int defaultValue) {
@@ -142,4 +172,14 @@ public class PurchasePrefixEvent extends MessageHandler {
} }
return false; return false;
} }
private boolean isAllowedFont(String font) {
for (String allowedFont : ALLOWED_FONTS) {
if (allowedFont.equals(font)) {
return true;
}
}
return false;
}
} }
@@ -3,6 +3,8 @@ package com.eu.habbo.messages.incoming.inventory.prefixes;
import com.eu.habbo.habbohotel.users.UserPrefix; import com.eu.habbo.habbohotel.users.UserPrefix;
import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.inventory.prefixes.ActivePrefixUpdatedComposer; import com.eu.habbo.messages.outgoing.inventory.prefixes.ActivePrefixUpdatedComposer;
import com.eu.habbo.messages.outgoing.inventory.nickicons.UserNickIconsComposer;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDataComposer;
public class SetActivePrefixEvent extends MessageHandler { public class SetActivePrefixEvent extends MessageHandler {
@Override @Override
@@ -12,6 +14,11 @@ public class SetActivePrefixEvent extends MessageHandler {
if (prefixId == 0) { if (prefixId == 0) {
this.client.getHabbo().getInventory().getPrefixesComponent().deactivateAll(); this.client.getHabbo().getInventory().getPrefixesComponent().deactivateAll();
this.client.sendResponse(new ActivePrefixUpdatedComposer(null)); this.client.sendResponse(new ActivePrefixUpdatedComposer(null));
this.client.sendResponse(new UserNickIconsComposer(this.client.getHabbo()));
if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != null) {
this.client.getHabbo().getHabboInfo().getCurrentRoom().sendComposer(new RoomUserDataComposer(this.client.getHabbo()).compose());
}
return; return;
} }
@@ -21,5 +28,10 @@ public class SetActivePrefixEvent extends MessageHandler {
this.client.getHabbo().getInventory().getPrefixesComponent().setActive(prefixId); this.client.getHabbo().getInventory().getPrefixesComponent().setActive(prefixId);
this.client.sendResponse(new ActivePrefixUpdatedComposer(prefix)); this.client.sendResponse(new ActivePrefixUpdatedComposer(prefix));
this.client.sendResponse(new UserNickIconsComposer(this.client.getHabbo()));
if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != null) {
this.client.getHabbo().getHabboInfo().getCurrentRoom().sendComposer(new RoomUserDataComposer(this.client.getHabbo()).compose());
}
} }
} }
@@ -0,0 +1,26 @@
package com.eu.habbo.messages.incoming.inventory.prefixes;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.inventory.UserVisualSettingsComponent;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.inventory.nickicons.UserNickIconsComposer;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDataComposer;
public class SetDisplayOrderEvent extends MessageHandler {
@Override
public void handle() throws Exception {
Habbo habbo = this.client.getHabbo();
if (habbo == null || habbo.getInventory() == null || habbo.getInventory().getUserVisualSettingsComponent() == null) {
return;
}
String displayOrder = UserVisualSettingsComponent.sanitizeDisplayOrder(this.packet.readString());
habbo.getInventory().getUserVisualSettingsComponent().setDisplayOrder(displayOrder);
this.client.sendResponse(new UserNickIconsComposer(habbo));
if (habbo.getHabboInfo().getCurrentRoom() != null) {
habbo.getHabboInfo().getCurrentRoom().sendComposer(new RoomUserDataComposer(habbo).compose());
}
}
}
@@ -0,0 +1,28 @@
package com.eu.habbo.messages.incoming.translation;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.translations.GoogleTranslateManager;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.translation.TranslationLanguagesComposer;
public class TranslationLanguagesRequestEvent extends MessageHandler {
@Override
public void handle() {
final GameClient client = this.client;
final String displayLanguage = this.packet.readString();
Emulator.getThreading().run(() -> {
GoogleTranslateManager.SupportedLanguagesResponse response = Emulator.getGameEnvironment()
.getGoogleTranslateManager()
.getSupportedLanguages(displayLanguage);
client.sendResponse(new TranslationLanguagesComposer(response).compose());
});
}
@Override
public int getRatelimit() {
return 250;
}
}
@@ -0,0 +1,25 @@
package com.eu.habbo.messages.incoming.translation;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.translations.GoogleTranslateManager;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.translation.TranslationResultComposer;
public class TranslationTextRequestEvent extends MessageHandler {
@Override
public void handle() {
final GameClient client = this.client;
final int requestId = this.packet.readInt();
final String text = this.packet.readString();
final String targetLanguage = this.packet.readString();
Emulator.getThreading().run(() -> {
GoogleTranslateManager.TranslationResponse response = Emulator.getGameEnvironment()
.getGoogleTranslateManager()
.translate(text, targetLanguage);
client.sendResponse(new TranslationResultComposer(requestId, response).compose());
});
}
}
@@ -1,14 +1,30 @@
package com.eu.habbo.messages.incoming.users; package com.eu.habbo.messages.incoming.users;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.incoming.MessageHandler;
public class ActivateEffectEvent extends MessageHandler { public class ActivateEffectEvent extends MessageHandler {
@Override @Override
public void handle() throws Exception { public void handle() throws Exception {
int effectId = this.packet.readInt(); int effectId = this.packet.readInt();
Habbo habbo = this.client.getHabbo();
if (habbo == null) return;
if (this.client.getHabbo().getInventory().getEffectsComponent().ownsEffect(effectId)) { if (habbo.getInventory().getEffectsComponent().ownsEffect(effectId)) {
this.client.getHabbo().getInventory().getEffectsComponent().activateEffect(effectId); habbo.getInventory().getEffectsComponent().activateEffect(effectId);
return;
} }
int rankId = habbo.getHabboInfo().getRank().getId();
if (Emulator.getGameEnvironment().getPermissionsManager().isEffectBlocked(effectId, rankId)) {
return;
}
Room room = habbo.getHabboInfo().getCurrentRoom();
if (room == null || habbo.getHabboInfo().getRiding() != null) return;
room.giveEffect(habbo, effectId, -1);
} }
} }
@@ -1,24 +1,77 @@
package com.eu.habbo.messages.incoming.users; package com.eu.habbo.messages.incoming.users;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboInfo;
import com.eu.habbo.habbohotel.users.HabboStats;
import com.eu.habbo.habbohotel.users.infostand.InfostandBackgroundManager;
import com.eu.habbo.habbohotel.users.infostand.InfostandBackgroundManager.Category;
import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDataComposer; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDataComposer;
public class ChangeInfostandBgEvent extends MessageHandler { public class ChangeInfostandBgEvent extends MessageHandler {
private static final String COOLDOWN_KEY = "infostand_bg_cooldown";
private static final long COOLDOWN_MS = 500L;
private static final int MIN_ID = 0;
private static final int MAX_ID = 9999;
@Override @Override
public void handle() throws Exception { public void handle() throws Exception {
int backgroundImage = this.packet.readInt(); Habbo habbo = this.client.getHabbo();
int backgroundStand = this.packet.readInt(); if (habbo == null) return;
int backgroundOverlay = this.packet.readInt();
this.client.getHabbo().getHabboInfo().setInfostandBg(backgroundImage); HabboInfo info = habbo.getHabboInfo();
this.client.getHabbo().getHabboInfo().setInfostandStand(backgroundStand); if (info == null) return;
this.client.getHabbo().getHabboInfo().setInfostandOverlay(backgroundOverlay);
this.client.getHabbo().getHabboInfo().run();
if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != null) { HabboStats stats = habbo.getHabboStats();
this.client.getHabbo().getHabboInfo().getCurrentRoom().sendComposer(new RoomUserDataComposer(this.client.getHabbo()).compose()); if (stats != null) {
long now = System.currentTimeMillis();
Object last = stats.cache.get(COOLDOWN_KEY);
if (last instanceof Long && (now - (Long) last) < COOLDOWN_MS) {
return;
}
stats.cache.put(COOLDOWN_KEY, now);
}
int requestedBg = sanitize(this.packet.readInt());
int requestedStand = sanitize(this.packet.readInt());
int requestedOverlay = sanitize(this.packet.readInt());
int requestedCard = this.packet.bytesAvailable() >= 4 ? sanitize(this.packet.readInt()) : 0;
InfostandBackgroundManager manager = Emulator.getGameEnvironment() != null ? Emulator.getGameEnvironment().getInfostandBackgroundManager() : null;
int backgroundImage = resolve(manager, habbo, Category.BACKGROUND, requestedBg, info.getInfostandBg());
int backgroundStand = resolve(manager, habbo, Category.STAND, requestedStand, info.getInfostandStand());
int backgroundOverlay = resolve(manager, habbo, Category.OVERLAY, requestedOverlay, info.getInfostandOverlay());
int backgroundCard = resolve(manager, habbo, Category.CARD, requestedCard, info.getInfostandCardBg());
if (info.getInfostandBg() == backgroundImage
&& info.getInfostandStand() == backgroundStand
&& info.getInfostandOverlay() == backgroundOverlay
&& info.getInfostandCardBg() == backgroundCard) {
return;
}
info.setInfostandBg(backgroundImage);
info.setInfostandStand(backgroundStand);
info.setInfostandOverlay(backgroundOverlay);
info.setInfostandCardBg(backgroundCard);
info.run();
if (info.getCurrentRoom() != null) {
info.getCurrentRoom().sendComposer(new RoomUserDataComposer(habbo).compose());
} else { } else {
this.client.sendResponse(new RoomUserDataComposer(this.client.getHabbo())); this.client.sendResponse(new RoomUserDataComposer(habbo));
} }
} }
private static int sanitize(int value) {
if (value < MIN_ID || value > MAX_ID) return 0;
return value;
}
private static int resolve(InfostandBackgroundManager manager, Habbo habbo, Category category, int requested, int current) {
if (manager == null) return requested;
return manager.canUse(habbo, category, requested) ? requested : current;
}
} }
@@ -9,6 +9,7 @@ import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.generic.alerts.UpdateFailedComposer; import com.eu.habbo.messages.outgoing.generic.alerts.UpdateFailedComposer;
import com.eu.habbo.messages.outgoing.rooms.items.ItemStateComposer;
import com.eu.habbo.messages.outgoing.wired.WiredSavedComposer; import com.eu.habbo.messages.outgoing.wired.WiredSavedComposer;
public class WiredEffectSaveDataEvent extends MessageHandler { public class WiredEffectSaveDataEvent extends MessageHandler {
@@ -39,6 +40,16 @@ public class WiredEffectSaveDataEvent extends MessageHandler {
if (saved) { if (saved) {
this.client.sendResponse(new WiredSavedComposer()); this.client.sendResponse(new WiredSavedComposer());
if (effect != null) { if (effect != null) {
if (effect.isSelector()) {
if (effect.usesExistingSelectorTargets()) {
effect.setExtradata("3");
room.sendComposer(new ItemStateComposer(effect).compose());
} else if ("3".equals(effect.getExtradata()) || "4".equals(effect.getExtradata()) || "5".equals(effect.getExtradata())) {
effect.setExtradata("0");
room.sendComposer(new ItemStateComposer(effect).compose());
}
}
effect.needsUpdate(true); effect.needsUpdate(true);
Emulator.getThreading().run(effect); Emulator.getThreading().run(effect);
} else { } else {
@@ -124,6 +124,8 @@ public class Outgoing {
public final static int WiredRoomSettingsDataComposer = 5102; // CUSTOM public final static int WiredRoomSettingsDataComposer = 5102; // CUSTOM
public final static int WiredUserVariablesDataComposer = 5103; // CUSTOM public final static int WiredUserVariablesDataComposer = 5103; // CUSTOM
public final static int ConfInvisStateComposer = 5104; // CUSTOM public final static int ConfInvisStateComposer = 5104; // CUSTOM
public final static int TranslationLanguagesComposer = 5106; // CUSTOM
public final static int TranslationResultComposer = 5107; // CUSTOM
public final static int AreaHideComposer = 6001; // CUSTOM public final static int AreaHideComposer = 6001; // CUSTOM
public final static int RoomPaintComposer = 2454; // PRODUCTION-201611291003-338511768 public final static int RoomPaintComposer = 2454; // PRODUCTION-201611291003-338511768
public final static int MarketplaceConfigComposer = 1823; // PRODUCTION-201611291003-338511768 public final static int MarketplaceConfigComposer = 1823; // PRODUCTION-201611291003-338511768
@@ -576,6 +578,7 @@ public class Outgoing {
public static final int UserPrefixesComposer = 7001; public static final int UserPrefixesComposer = 7001;
public static final int PrefixReceivedComposer = 7002; public static final int PrefixReceivedComposer = 7002;
public static final int ActivePrefixUpdatedComposer = 7003; public static final int ActivePrefixUpdatedComposer = 7003;
public static final int UserNickIconsComposer = 7004;
public static final int AvailableCommandsComposer = 4050; public static final int AvailableCommandsComposer = 4050;
// YouTube Room Broadcast // YouTube Room Broadcast
@@ -0,0 +1,217 @@
package com.eu.habbo.messages.outgoing.inventory.nickicons;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.UserCustomizationData;
import com.eu.habbo.habbohotel.users.UserNickIcon;
import com.eu.habbo.habbohotel.users.UserPrefix;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing;
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.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class UserNickIconsComposer extends MessageComposer {
private static final Logger LOGGER = LoggerFactory.getLogger(UserNickIconsComposer.class);
private final Habbo habbo;
public UserNickIconsComposer(Habbo habbo) {
this.habbo = habbo;
}
@Override
protected ServerMessage composeInternal() {
this.response.init(Outgoing.UserNickIconsComposer);
if (this.habbo == null || this.habbo.getInventory() == null || this.habbo.getInventory().getNickIconsComponent() == null) {
this.response.appendInt(0);
return this.response;
}
Map<String, UserNickIcon> ownedByKey = new HashMap<>();
List<UserNickIcon> ownedNickIcons = this.habbo.getInventory().getNickIconsComponent().getNickIcons();
for (UserNickIcon nickIcon : ownedNickIcons) {
ownedByKey.put(nickIcon.getIconKey().toLowerCase(), nickIcon);
}
Map<Integer, UserPrefix> ownedPrefixesByCatalogId = new HashMap<>();
List<UserPrefix> ownedPrefixes = this.habbo.getInventory().getPrefixesComponent().getPrefixes();
for (UserPrefix prefix : ownedPrefixes) {
if (prefix.getCatalogPrefixId() > 0) {
ownedPrefixesByCatalogId.put(prefix.getCatalogPrefixId(), prefix);
}
}
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"SELECT icon_key, display_name, points, points_type FROM custom_nick_icons_catalog WHERE enabled = 1 ORDER BY sort_order ASC, id ASC")) {
try (ResultSet set = statement.executeQuery()) {
List<CatalogNickIcon> catalogNickIcons = new ArrayList<>();
while (set.next()) {
catalogNickIcons.add(new CatalogNickIcon(
set.getString("icon_key"),
set.getString("display_name"),
set.getInt("points"),
set.getInt("points_type")));
}
this.response.appendInt(catalogNickIcons.size());
for (CatalogNickIcon catalogNickIcon : catalogNickIcons) {
UserNickIcon ownedNickIcon = ownedByKey.get(catalogNickIcon.iconKey.toLowerCase());
this.response.appendString(catalogNickIcon.iconKey);
this.response.appendString(catalogNickIcon.displayName != null ? catalogNickIcon.displayName : "");
this.response.appendInt(catalogNickIcon.points);
this.response.appendInt(catalogNickIcon.pointsType);
this.response.appendInt(ownedNickIcon != null ? 1 : 0);
this.response.appendInt((ownedNickIcon != null && ownedNickIcon.isActive()) ? 1 : 0);
this.response.appendInt(ownedNickIcon != null ? ownedNickIcon.getId() : 0);
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
this.response.appendInt(0);
}
UserCustomizationData customizationData = UserCustomizationData.fromHabbo(this.habbo);
this.response.appendString(customizationData.displayOrder);
this.response.appendInt(this.getSettingInt("max_length", 15));
this.response.appendInt(this.getSettingInt("price_credits", 5));
this.response.appendInt(this.getSettingInt("price_points", 0));
this.response.appendInt(this.getSettingInt("points_type", 0));
this.response.appendInt(this.getSettingInt("font_price_credits", 10));
this.response.appendInt(this.getSettingInt("font_price_points", 0));
this.response.appendInt(this.getSettingInt("font_points_type", 0));
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"SELECT id, display_name, text, color, icon, effect, font, points, points_type FROM custom_prefixes_catalog WHERE enabled = 1 ORDER BY sort_order ASC, id ASC")) {
try (ResultSet set = statement.executeQuery()) {
List<CatalogPrefix> catalogPrefixes = new ArrayList<>();
while (set.next()) {
catalogPrefixes.add(new CatalogPrefix(
set.getInt("id"),
set.getString("display_name"),
set.getString("text"),
set.getString("color"),
set.getString("icon"),
set.getString("effect"),
set.getString("font"),
set.getInt("points"),
set.getInt("points_type")));
}
this.response.appendInt(catalogPrefixes.size());
for (CatalogPrefix catalogPrefix : catalogPrefixes) {
UserPrefix ownedPrefix = ownedPrefixesByCatalogId.get(catalogPrefix.id);
this.response.appendInt(catalogPrefix.id);
this.response.appendString(catalogPrefix.displayName != null ? catalogPrefix.displayName : catalogPrefix.text);
this.response.appendString(catalogPrefix.text != null ? catalogPrefix.text : "");
this.response.appendString(catalogPrefix.color != null ? catalogPrefix.color : "");
this.response.appendString(catalogPrefix.icon != null ? catalogPrefix.icon : "");
this.response.appendString(catalogPrefix.effect != null ? catalogPrefix.effect : "");
this.response.appendString(catalogPrefix.font != null ? catalogPrefix.font : "");
this.response.appendInt(catalogPrefix.points);
this.response.appendInt(catalogPrefix.pointsType);
this.response.appendInt(ownedPrefix != null ? 1 : 0);
this.response.appendInt((ownedPrefix != null && ownedPrefix.isActive()) ? 1 : 0);
this.response.appendInt(ownedPrefix != null ? ownedPrefix.getId() : 0);
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception loading prefix catalog", e);
this.response.appendInt(0);
}
this.response.appendInt(ownedPrefixes.size());
for (UserPrefix prefix : ownedPrefixes) {
this.response.appendInt(prefix.getId());
this.response.appendString(prefix.getDisplayName() != null ? prefix.getDisplayName() : prefix.getText());
this.response.appendString(prefix.getText());
this.response.appendString(prefix.getColor());
this.response.appendString(prefix.getIcon());
this.response.appendString(prefix.getEffect());
this.response.appendString(prefix.getFont());
this.response.appendInt(prefix.isActive() ? 1 : 0);
this.response.appendInt(prefix.isCustom() ? 1 : 0);
this.response.appendInt(prefix.getPoints());
this.response.appendInt(prefix.getPointsType());
this.response.appendInt(prefix.getCatalogPrefixId());
}
return this.response;
}
private int getSettingInt(String key, int defaultValue) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT `value` FROM custom_prefix_settings WHERE key_name = ? LIMIT 1")) {
statement.setString(1, key);
try (ResultSet set = statement.executeQuery()) {
if (set.next()) {
return Integer.parseInt(set.getString("value"));
}
}
} catch (SQLException | NumberFormatException e) {
LOGGER.error("Caught exception while resolving prefix setting {}", key, e);
}
return defaultValue;
}
private static class CatalogNickIcon {
private final String iconKey;
private final String displayName;
private final int points;
private final int pointsType;
private CatalogNickIcon(String iconKey, String displayName, int points, int pointsType) {
this.iconKey = iconKey;
this.displayName = displayName;
this.points = points;
this.pointsType = pointsType;
}
}
private static class CatalogPrefix {
private final int id;
private final String displayName;
private final String text;
private final String color;
private final String icon;
private final String effect;
private final String font;
private final int points;
private final int pointsType;
private CatalogPrefix(int id, String displayName, String text, String color, String icon, String effect, String font, int points, int pointsType) {
this.id = id;
this.displayName = displayName;
this.text = text;
this.color = color;
this.icon = icon;
this.effect = effect;
this.font = font;
this.points = points;
this.pointsType = pointsType;
}
}
}
@@ -22,12 +22,14 @@ public class ActivePrefixUpdatedComposer extends MessageComposer {
this.response.appendString(this.prefix.getColor()); this.response.appendString(this.prefix.getColor());
this.response.appendString(this.prefix.getIcon()); this.response.appendString(this.prefix.getIcon());
this.response.appendString(this.prefix.getEffect()); this.response.appendString(this.prefix.getEffect());
this.response.appendString(this.prefix.getFont());
} else { } else {
this.response.appendInt(0); this.response.appendInt(0);
this.response.appendString(""); this.response.appendString("");
this.response.appendString(""); this.response.appendString("");
this.response.appendString(""); this.response.appendString("");
this.response.appendString(""); this.response.appendString("");
this.response.appendString("");
} }
return this.response; return this.response;
@@ -20,6 +20,7 @@ public class PrefixReceivedComposer extends MessageComposer {
this.response.appendString(this.prefix.getColor()); this.response.appendString(this.prefix.getColor());
this.response.appendString(this.prefix.getIcon()); this.response.appendString(this.prefix.getIcon());
this.response.appendString(this.prefix.getEffect()); this.response.appendString(this.prefix.getEffect());
this.response.appendString(this.prefix.getFont());
return this.response; return this.response;
} }
} }
@@ -30,6 +30,7 @@ public class UserPrefixesComposer extends MessageComposer {
this.response.appendString(prefix.getColor()); this.response.appendString(prefix.getColor());
this.response.appendString(prefix.getIcon()); this.response.appendString(prefix.getIcon());
this.response.appendString(prefix.getEffect()); this.response.appendString(prefix.getEffect());
this.response.appendString(prefix.getFont());
this.response.appendInt(prefix.isActive() ? 1 : 0); this.response.appendInt(prefix.isActive() ? 1 : 0);
} }
@@ -36,6 +36,7 @@ public class RoomPetComposer extends MessageComposer implements TIntObjectProced
this.response.appendInt(0); this.response.appendInt(0);
this.response.appendInt(0); this.response.appendInt(0);
this.response.appendInt(0); this.response.appendInt(0);
this.response.appendInt(0);
if (pet instanceof IPetLook) { if (pet instanceof IPetLook) {
this.response.appendString(((IPetLook) pet).getLook()); this.response.appendString(((IPetLook) pet).getLook());
} else { } else {
@@ -1,6 +1,7 @@
package com.eu.habbo.messages.outgoing.rooms.users; package com.eu.habbo.messages.outgoing.rooms.users;
import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.UserCustomizationData;
import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer; import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing; import com.eu.habbo.messages.outgoing.Outgoing;
@@ -23,6 +24,15 @@ public class RoomUserDataComposer extends MessageComposer {
this.response.appendInt(this.habbo.getHabboInfo().getInfostandBg()); this.response.appendInt(this.habbo.getHabboInfo().getInfostandBg());
this.response.appendInt(this.habbo.getHabboInfo().getInfostandStand()); this.response.appendInt(this.habbo.getHabboInfo().getInfostandStand());
this.response.appendInt(this.habbo.getHabboInfo().getInfostandOverlay()); this.response.appendInt(this.habbo.getHabboInfo().getInfostandOverlay());
this.response.appendInt(this.habbo.getHabboInfo().getInfostandCardBg());
UserCustomizationData customizationData = UserCustomizationData.fromHabbo(this.habbo);
this.response.appendString(customizationData.nickIcon);
this.response.appendString(customizationData.prefixText);
this.response.appendString(customizationData.prefixColor);
this.response.appendString(customizationData.prefixIcon);
this.response.appendString(customizationData.prefixEffect);
this.response.appendString(customizationData.prefixFont);
this.response.appendString(customizationData.displayOrder);
return this.response; return this.response;
} }
@@ -4,6 +4,7 @@ import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.bots.Bot; import com.eu.habbo.habbohotel.bots.Bot;
import com.eu.habbo.habbohotel.guilds.Guild; import com.eu.habbo.habbohotel.guilds.Guild;
import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.UserCustomizationData;
import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer; import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing; import com.eu.habbo.messages.outgoing.Outgoing;
@@ -43,6 +44,7 @@ public class RoomUsersComposer extends MessageComposer {
this.response.appendInt(this.habbo.getHabboInfo().getInfostandBg()); this.response.appendInt(this.habbo.getHabboInfo().getInfostandBg());
this.response.appendInt(this.habbo.getHabboInfo().getInfostandStand()); this.response.appendInt(this.habbo.getHabboInfo().getInfostandStand());
this.response.appendInt(this.habbo.getHabboInfo().getInfostandOverlay()); this.response.appendInt(this.habbo.getHabboInfo().getInfostandOverlay());
this.response.appendInt(this.habbo.getHabboInfo().getInfostandCardBg());
this.response.appendString(this.habbo.getHabboInfo().getLook()); this.response.appendString(this.habbo.getHabboInfo().getLook());
this.response.appendInt(this.habbo.getRoomUnit().getId()); //Room Unit ID this.response.appendInt(this.habbo.getRoomUnit().getId()); //Room Unit ID
this.response.appendInt(this.habbo.getRoomUnit().getX()); this.response.appendInt(this.habbo.getRoomUnit().getX());
@@ -66,6 +68,14 @@ public class RoomUsersComposer extends MessageComposer {
this.response.appendString(""); this.response.appendString("");
this.response.appendInt(this.habbo.getHabboStats().getAchievementScore()); this.response.appendInt(this.habbo.getHabboStats().getAchievementScore());
this.response.appendBoolean(true); this.response.appendBoolean(true);
UserCustomizationData customizationData = UserCustomizationData.fromHabbo(this.habbo);
this.response.appendString(customizationData.nickIcon);
this.response.appendString(customizationData.prefixText);
this.response.appendString(customizationData.prefixColor);
this.response.appendString(customizationData.prefixIcon);
this.response.appendString(customizationData.prefixEffect);
this.response.appendString(customizationData.prefixFont);
this.response.appendString(customizationData.displayOrder);
this.response.appendString(this.habbo.getHabboInfo().getRoomEntryMethod()); this.response.appendString(this.habbo.getHabboInfo().getRoomEntryMethod());
this.response.appendInt(this.habbo.getHabboInfo().getRoomEntryTeleportId()); this.response.appendInt(this.habbo.getHabboInfo().getRoomEntryTeleportId());
} else if (this.habbos != null) { } else if (this.habbos != null) {
@@ -78,6 +88,7 @@ public class RoomUsersComposer extends MessageComposer {
this.response.appendInt(habbo.getHabboInfo().getInfostandBg()); this.response.appendInt(habbo.getHabboInfo().getInfostandBg());
this.response.appendInt(habbo.getHabboInfo().getInfostandStand()); this.response.appendInt(habbo.getHabboInfo().getInfostandStand());
this.response.appendInt(habbo.getHabboInfo().getInfostandOverlay()); this.response.appendInt(habbo.getHabboInfo().getInfostandOverlay());
this.response.appendInt(habbo.getHabboInfo().getInfostandCardBg());
this.response.appendString(habbo.getHabboInfo().getLook()); this.response.appendString(habbo.getHabboInfo().getLook());
this.response.appendInt(habbo.getRoomUnit().getId()); //Room Unit ID this.response.appendInt(habbo.getRoomUnit().getId()); //Room Unit ID
this.response.appendInt(habbo.getRoomUnit().getX()); this.response.appendInt(habbo.getRoomUnit().getX());
@@ -99,6 +110,14 @@ public class RoomUsersComposer extends MessageComposer {
this.response.appendString(""); this.response.appendString("");
this.response.appendInt(habbo.getHabboStats().getAchievementScore()); this.response.appendInt(habbo.getHabboStats().getAchievementScore());
this.response.appendBoolean(true); this.response.appendBoolean(true);
UserCustomizationData customizationData = UserCustomizationData.fromHabbo(habbo);
this.response.appendString(customizationData.nickIcon);
this.response.appendString(customizationData.prefixText);
this.response.appendString(customizationData.prefixColor);
this.response.appendString(customizationData.prefixIcon);
this.response.appendString(customizationData.prefixEffect);
this.response.appendString(customizationData.prefixFont);
this.response.appendString(customizationData.displayOrder);
this.response.appendString(habbo.getHabboInfo().getRoomEntryMethod()); this.response.appendString(habbo.getHabboInfo().getRoomEntryMethod());
this.response.appendInt(habbo.getHabboInfo().getRoomEntryTeleportId()); this.response.appendInt(habbo.getHabboInfo().getRoomEntryTeleportId());
} }
@@ -111,6 +130,7 @@ public class RoomUsersComposer extends MessageComposer {
this.response.appendInt(0); this.response.appendInt(0);
this.response.appendInt(0); this.response.appendInt(0);
this.response.appendInt(0); this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendString(this.bot.getFigure()); this.response.appendString(this.bot.getFigure());
this.response.appendInt(this.bot.getRoomUnit().getId()); this.response.appendInt(this.bot.getRoomUnit().getId());
this.response.appendInt(this.bot.getRoomUnit().getX()); this.response.appendInt(this.bot.getRoomUnit().getX());
@@ -143,6 +163,7 @@ public class RoomUsersComposer extends MessageComposer {
this.response.appendInt(0); this.response.appendInt(0);
this.response.appendInt(0); this.response.appendInt(0);
this.response.appendInt(0); this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendString(bot.getFigure()); this.response.appendString(bot.getFigure());
this.response.appendInt(bot.getRoomUnit().getId()); this.response.appendInt(bot.getRoomUnit().getId());
this.response.appendInt(bot.getRoomUnit().getX()); this.response.appendInt(bot.getRoomUnit().getX());
@@ -0,0 +1,33 @@
package com.eu.habbo.messages.outgoing.translation;
import com.eu.habbo.habbohotel.translations.GoogleTranslateManager;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing;
public class TranslationLanguagesComposer extends MessageComposer {
private final GoogleTranslateManager.SupportedLanguagesResponse responseData;
public TranslationLanguagesComposer(GoogleTranslateManager.SupportedLanguagesResponse responseData) {
this.responseData = responseData;
}
@Override
protected ServerMessage composeInternal() {
this.response.init(Outgoing.TranslationLanguagesComposer);
this.response.appendBoolean(this.responseData != null && this.responseData.isSuccess());
this.response.appendString(this.responseData != null ? this.responseData.getErrorMessage() : "Unknown error");
int count = (this.responseData != null) ? this.responseData.getLanguages().size() : 0;
this.response.appendInt(count);
if (this.responseData != null) {
for (GoogleTranslateManager.SupportedLanguage language : this.responseData.getLanguages()) {
this.response.appendString(language.getCode());
this.response.appendString(language.getName());
}
}
return this.response;
}
}
@@ -0,0 +1,29 @@
package com.eu.habbo.messages.outgoing.translation;
import com.eu.habbo.habbohotel.translations.GoogleTranslateManager;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing;
public class TranslationResultComposer extends MessageComposer {
private final int requestId;
private final GoogleTranslateManager.TranslationResponse responseData;
public TranslationResultComposer(int requestId, GoogleTranslateManager.TranslationResponse responseData) {
this.requestId = requestId;
this.responseData = responseData;
}
@Override
protected ServerMessage composeInternal() {
this.response.init(Outgoing.TranslationResultComposer);
this.response.appendInt(this.requestId);
this.response.appendBoolean(this.responseData != null && this.responseData.isSuccess());
this.response.appendString(this.responseData != null ? this.responseData.getErrorMessage() : "Unknown error");
this.response.appendString(this.responseData != null ? this.responseData.getOriginalText() : "");
this.response.appendString(this.responseData != null ? this.responseData.getTranslatedText() : "");
this.response.appendString(this.responseData != null ? this.responseData.getDetectedLanguage() : "");
this.response.appendString(this.responseData != null ? this.responseData.getTargetLanguage() : "");
return this.response;
}
}
@@ -6,6 +6,7 @@ import com.eu.habbo.habbohotel.guilds.Guild;
import com.eu.habbo.habbohotel.messenger.Messenger; import com.eu.habbo.habbohotel.messenger.Messenger;
import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboInfo; import com.eu.habbo.habbohotel.users.HabboInfo;
import com.eu.habbo.habbohotel.users.UserCustomizationData;
import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer; import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing; import com.eu.habbo.messages.outgoing.Outgoing;
@@ -115,6 +116,15 @@ public class UserProfileComposer extends MessageComposer {
this.response.appendInt(this.habboInfo.getInfostandBg()); this.response.appendInt(this.habboInfo.getInfostandBg());
this.response.appendInt(this.habboInfo.getInfostandStand()); this.response.appendInt(this.habboInfo.getInfostandStand());
this.response.appendInt(this.habboInfo.getInfostandOverlay()); this.response.appendInt(this.habboInfo.getInfostandOverlay());
this.response.appendInt(this.habboInfo.getInfostandCardBg());
UserCustomizationData customizationData = (this.habbo != null) ? UserCustomizationData.fromHabbo(this.habbo) : UserCustomizationData.fromUserId(this.habboInfo.getId());
this.response.appendString(customizationData.nickIcon);
this.response.appendString(customizationData.prefixText);
this.response.appendString(customizationData.prefixColor);
this.response.appendString(customizationData.prefixIcon);
this.response.appendString(customizationData.prefixEffect);
this.response.appendString(customizationData.prefixFont);
this.response.appendString(customizationData.displayOrder);
return this.response; return this.response;
} }

Some files were not shown because too many files have changed in this diff Show More