Compare commits

..

51 Commits

Author SHA1 Message Date
github-actions[bot] 8709a72b6e 🆙 Bump version to 4.1.15 [skip ci] 2026-05-12 08:55:48 +00:00
DuckieTM c331da9fbe Merge pull request #101 from duckietm/dev
Dev
2026-05-12 10:54:51 +02:00
duckietm f9a079da02 🆙 comibe SQLs 2026-05-12 09:18:22 +02:00
duckietm 89eb989c26 🆙 Refactor AuthHttpHandler for the API and Websocket 2026-05-12 09:11:43 +02:00
duckietm 47be392d8e 🆕 Added Reset password / Email and chenge username in user settings 2026-05-11 18:06:34 +02:00
duckietm d9465a0a65 🆙 Update Some security updates for guilds 2026-05-08 15:38:14 +02:00
duckietm 90314d00fe 🆙 Fix Guilds removal 2026-05-08 15:19:00 +02:00
duckietm 56c73b9d98 🆙 Small fix for the websocket, some CF users have problems with the max frame size 2026-05-08 08:03:51 +02:00
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
github-actions[bot] 03d37650a0 🆙 Bump version to 4.1.8 [skip ci] 2026-04-28 09:32:12 +00:00
DuckieTM f4e5449443 Merge pull request #92 from duckietm/dev
🆙 Added Ban to the API
2026-04-28 11:31:15 +02:00
duckietm 1ebc8314a8 🆙 Added Ban to the API 2026-04-28 11:30:54 +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
123 changed files with 7883 additions and 1499 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 FOREIGN_KEY_CHECKS = 0;
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`;
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 SQL_MODE = @OLD_SQL_MODE;
+2 -3
View File
@@ -91,7 +91,7 @@ ALTER TABLE `catalog_pages`
'builders_club_addons',
'builders_club_loyalty'
) NOT NULL DEFAULT 'default_3x3';
ALTER TABLE `catalog_pages`
ADD COLUMN IF NOT EXISTS `catalog_mode` ENUM('NORMAL','BUILDER','BOTH') NOT NULL DEFAULT 'NORMAL'
AFTER `club_only`;
@@ -878,7 +878,7 @@ ON DUPLICATE KEY UPDATE
`permission` = VALUES(`permission`),
`overridable` = VALUES(`overridable`),
`triggers_talking_furniture` = VALUES(`triggers_talking_furniture`);
ALTER TABLE `catalog_club_offers`
MODIFY COLUMN `type` ENUM('HC', 'VIP', 'BUILDERS_CLUB', 'BUILDERS_CLUB_ADDON') NOT NULL DEFAULT 'HC';
@@ -987,4 +987,3 @@ ALTER TABLE `catalog_pages_bc`
'builders_club_addons',
'builders_club_loyalty'
) 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,5 @@
ALTER TABLE users
ADD COLUMN `last_username_change` INT(11) NOT NULL;
INSERT INTO emulator_settings (`key`, `value`, `comment`)
VALUES ('rename.cooldown_days', '30', 'Days between username changes');
@@ -0,0 +1,51 @@
-- ============================================================
-- 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`;
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`);
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>
<artifactId>Habbo</artifactId>
<version>4.1.7</version>
<version>4.1.15</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -163,7 +163,7 @@
<version>2.13.0</version>
</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 -->
<dependency>
<groupId>org.mindrot</groupId>
@@ -171,7 +171,7 @@
<version>0.4</version>
</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 -->
<dependency>
<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.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().getX());
@@ -21,7 +21,10 @@ import com.eu.habbo.habbohotel.pets.PetManager;
import com.eu.habbo.habbohotel.polls.PollManager;
import com.eu.habbo.habbohotel.rooms.RoomChatBubbleManager;
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.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.SubscriptionScheduler;
import org.slf4j.Logger;
@@ -58,6 +61,9 @@ public class GameEnvironment {
private SubscriptionManager subscriptionManager;
private CalendarManager calendarManager;
private RoomChatBubbleManager roomChatBubbleManager;
private GoogleTranslateManager googleTranslateManager;
private CustomBadgeManager customBadgeManager;
private InfostandBackgroundManager infostandBackgroundManager;
public void load() throws Exception {
LOGGER.info("GameEnvironment -> Loading...");
@@ -84,6 +90,9 @@ public class GameEnvironment {
this.pollManager = new PollManager();
this.calendarManager = new CalendarManager();
this.roomChatBubbleManager = new RoomChatBubbleManager();
this.googleTranslateManager = new GoogleTranslateManager();
this.customBadgeManager = new CustomBadgeManager();
this.infostandBackgroundManager = new InfostandBackgroundManager();
this.roomManager.loadPublicRooms();
this.navigatorManager.loadNavigator();
@@ -121,6 +130,9 @@ public class GameEnvironment {
this.hotelViewManager.dispose();
this.subscriptionManager.dispose();
this.calendarManager.dispose();
if (this.googleTranslateManager != null) {
this.googleTranslateManager.clearCache();
}
LOGGER.info("GameEnvironment -> Disposed!");
}
@@ -219,4 +231,16 @@ public class GameEnvironment {
public RoomChatBubbleManager getRoomChatBubbleManager() {
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;
import com.eu.habbo.Emulator;
import com.eu.habbo.database.SqlQueries;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboBadge;
@@ -50,12 +49,16 @@ public class AchievementManager {
if (habbo != null) {
progressAchievement(habbo, achievement, amount);
} else {
try {
SqlQueries.update(
"INSERT INTO users_achievements_queue (user_id, achievement_id, amount) VALUES (?, ?, ?) "
+ "ON DUPLICATE KEY UPDATE amount = amount + ?",
habboId, achievement.id, amount, amount);
} catch (SqlQueries.DataAccessException e) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("" +
"INSERT INTO users_achievements_queue (user_id, achievement_id, amount) VALUES (?, ?, ?) " +
"ON DUPLICATE KEY UPDATE amount = amount + ?")) {
statement.setInt(1, habboId);
statement.setInt(2, achievement.id);
statement.setInt(3, amount);
statement.setInt(4, amount);
statement.execute();
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
}
@@ -200,41 +203,48 @@ public class AchievementManager {
}
public static void createUserEntry(Habbo habbo, Achievement achievement) {
try {
SqlQueries.update(
"INSERT INTO users_achievements (user_id, achievement_name, progress) VALUES (?, ?, ?)",
habbo.getHabboInfo().getId(), achievement.name, 1);
} catch (SqlQueries.DataAccessException e) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO users_achievements (user_id, achievement_name, progress) VALUES (?, ?, ?)")) {
statement.setInt(1, habbo.getHabboInfo().getId());
statement.setString(2, achievement.name);
statement.setInt(3, 1);
statement.execute();
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
}
public static void saveAchievements(Habbo habbo) {
int userId = habbo.getHabboInfo().getId();
try {
SqlQueries.batchUpdate(
"UPDATE users_achievements SET progress = ? WHERE achievement_name = ? AND user_id = ? LIMIT 1",
habbo.getHabboStats().getAchievementProgress().entrySet(),
(ps, entry) -> {
ps.setInt(1, entry.getValue());
ps.setString(2, entry.getKey().name);
ps.setInt(3, userId);
});
} catch (SqlQueries.DataAccessException e) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE users_achievements SET progress = ? WHERE achievement_name = ? AND user_id = ? LIMIT 1")) {
statement.setInt(3, habbo.getHabboInfo().getId());
for (Map.Entry<Achievement, Integer> map : habbo.getHabboStats().getAchievementProgress().entrySet()) {
statement.setInt(1, map.getValue());
statement.setString(2, map.getKey().name);
statement.addBatch();
}
statement.executeBatch();
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
}
public static int getAchievementProgressForHabbo(int userId, Achievement achievement) {
try {
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);
if (achievement == null) {
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() {
@@ -71,13 +71,23 @@ public class BotManager {
}
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;
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)) {
statement.setString(1, data.get("name"));
statement.setString(2, data.get("motto"));
statement.setString(3, data.get("figure"));
statement.setString(4, data.get("gender").toUpperCase());
statement.setString(5, type);
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.setInt(1, ownerId);
statement.setString(2, data.get("name"));
statement.setString(3, data.get("motto"));
statement.setString(4, data.get("figure"));
statement.setString(5, data.get("gender").toUpperCase());
statement.setString(6, type);
statement.execute();
try (ResultSet set = statement.getGeneratedKeys()) {
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) {
bot.setOwnerId(habbo.getClient().getHabbo().getHabboInfo().getId());
@@ -1,18 +0,0 @@
package com.eu.habbo.habbohotel.commands;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.messages.outgoing.users.UserDataComposer;
public class ChangeNameCommand extends Command {
public ChangeNameCommand() {
super("cmd_changename", Emulator.getTexts().getValue("commands.keys.cmd_changename").split(";"));
}
@Override
public boolean handle(GameClient gameClient, String[] params) throws Exception {
gameClient.getHabbo().getHabboStats().allowNameChange = !gameClient.getHabbo().getHabboStats().allowNameChange;
gameClient.sendResponse(new UserDataComposer(gameClient.getHabbo()));
return true;
}
}
@@ -184,7 +184,6 @@ public class CommandHandler {
addCommand(new BlockAlertCommand());
addCommand(new BotsCommand());
addCommand(new CalendarCommand());
addCommand(new ChangeNameCommand());
addCommand(new ChatTypeCommand());
addCommand(new CommandsCommand());
addCommand(new ControlCommand());
@@ -1,23 +1,16 @@
package com.eu.habbo.habbohotel.gameclients;
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.messages.outgoing.rooms.users.RoomUserEffectComposer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ConcurrentHashMap;
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 {
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);
}
/**
* Park a disconnected Habbo in ghost mode. Their room presence is
* 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 int getPausedEffectId() {
return Emulator.getConfig().getInt("session.reconnect.effect.id", 170);
}
public boolean parkHabbo(Habbo habbo, String ssoTicket) {
int graceSeconds = getGracePeriodSeconds();
if (graceSeconds <= 0) {
@@ -51,7 +42,6 @@ public class SessionResumeManager {
int userId = habbo.getHabboInfo().getId();
// Cancel any existing ghost session for this user
GhostSession existing = ghostSessions.remove(userId);
if (existing != null && existing.disposeFuture != null) {
existing.disposeFuture.cancel(false);
@@ -60,12 +50,18 @@ public class SessionResumeManager {
LOGGER.info("[SessionResume] Parking {} (id={}) for {}s grace period",
habbo.getHabboInfo().getUsername(), userId, graceSeconds);
// Restore the SSO ticket so the client can reconnect with the same ticket
if (ssoTicket != null && !ssoTicket.isEmpty()) {
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(() -> {
GhostSession ghost = ghostSessions.remove(userId);
if (ghost != null) {
@@ -75,22 +71,19 @@ public class SessionResumeManager {
}
}, graceSeconds * 1000);
ghostSessions.put(userId, new GhostSession(habbo, ssoTicket, future));
ghostSessions.put(userId, new GhostSession(habbo, ssoTicket, future, previousEffectId, previousEffectEnd));
applyPausedEffect(habbo);
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) {
GhostSession ghost = ghostSessions.remove(userId);
if (ghost == null) {
return null;
}
// Cancel the scheduled dispose
if (ghost.disposeFuture != null) {
ghost.disposeFuture.cancel(false);
}
@@ -98,19 +91,15 @@ public class SessionResumeManager {
LOGGER.info("[SessionResume] Resuming session for {} (id={})",
ghost.habbo.getHabboInfo().getUsername(), userId);
restorePausedEffect(ghost);
return ghost.habbo;
}
/**
* Check if a user has a ghost session (is in grace period).
*/
public boolean hasGhostSession(int userId) {
return ghostSessions.containsKey(userId);
}
/**
* Immediately expire all ghost sessions (e.g. on emulator shutdown).
*/
public void disposeAll() {
for (GhostSession ghost : ghostSessions.values()) {
if (ghost.disposeFuture != null) {
@@ -121,9 +110,6 @@ public class SessionResumeManager {
ghostSessions.clear();
}
/**
* Perform the actual full disconnect that normally happens in Habbo.disconnect().
*/
private void performFullDisconnect(Habbo habbo) {
try {
habbo.getHabboInfo().setOnline(false);
@@ -132,7 +118,6 @@ public class SessionResumeManager {
LOGGER.error("[SessionResume] Error during deferred disconnect", e);
}
// Clear the SSO ticket now that the grace period is truly over
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) {
try (var connection = Emulator.getDatabase().getDataSource().getConnection();
var statement = connection.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) {
@@ -163,11 +180,16 @@ public class SessionResumeManager {
final Habbo habbo;
final String ssoTicket;
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.ssoTicket = ssoTicket;
this.disposeFuture = disposeFuture;
this.previousEffectId = previousEffectId;
this.previousEffectEnd = previousEffectEnd;
}
}
}
@@ -2,11 +2,13 @@ package com.eu.habbo.habbohotel.guilds;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.guilds.forums.ForumThread;
import com.eu.habbo.habbohotel.guilds.forums.ForumView;
import com.eu.habbo.habbohotel.items.interactions.InteractionGuildFurni;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.messages.outgoing.guilds.GuildJoinErrorComposer;
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumDataComposer;
import gnu.trove.TCollections;
import gnu.trove.iterator.TIntObjectIterator;
import gnu.trove.map.TIntObjectMap;
@@ -142,12 +144,36 @@ public class GuildManager {
deleteFavourite.execute();
}
try (PreparedStatement statement = connection.prepareStatement("DELETE FROM guild_forum_views WHERE guild_id = ?")) {
statement.setInt(1, guild.getId());
statement.execute();
}
try (PreparedStatement statement = connection.prepareStatement("DELETE c FROM guilds_forums_comments c INNER JOIN guilds_forums_threads t ON c.thread_id = t.id WHERE t.guild_id = ?")) {
statement.setInt(1, guild.getId());
statement.execute();
}
try (PreparedStatement statement = connection.prepareStatement("DELETE FROM guilds_forums_threads WHERE guild_id = ?")) {
statement.setInt(1, guild.getId());
statement.execute();
}
try (PreparedStatement statement = connection.prepareStatement("DELETE FROM guilds_members WHERE guild_id = ?")) {
statement.setInt(1, guild.getId());
statement.execute();
}
try (PreparedStatement statement = connection.prepareStatement("UPDATE rooms SET guild_id = 0 WHERE guild_id = ?")) {
statement.setInt(1, guild.getId());
statement.execute();
}
try (PreparedStatement statement = connection.prepareStatement("UPDATE items SET guild_id = 0 WHERE guild_id = ?")) {
statement.setInt(1, guild.getId());
statement.execute();
}
try (PreparedStatement statement = connection.prepareStatement("DELETE FROM guilds WHERE id = ?")) {
statement.setInt(1, guild.getId());
statement.execute();
@@ -161,6 +187,10 @@ public class GuildManager {
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
this.guilds.remove(guild.getId());
ForumThread.clearCacheForGuild(guild.getId());
GuildForumDataComposer.invalidateUnreadCache(guild.getId());
}
@@ -1,5 +1,6 @@
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.interactions.InteractionWiredCondition;
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.users.HabboItem;
import com.eu.habbo.messages.ServerMessage;
import gnu.trove.set.hash.THashSet;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.stream.Collectors;
public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
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_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;
private final THashSet<HabboItem> items;
private int comparison = COMPARISON_EQUAL;
private int quantity = 0;
private int sourceGroup = SOURCE_GROUP_USERS;
@@ -33,10 +43,12 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
public WiredConditionSelectionQuantity(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
this.items = new THashSet<>();
}
public WiredConditionSelectionQuantity(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
this.items = new THashSet<>();
}
@Override
@@ -46,9 +58,18 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
@Override
public void serializeWiredData(ServerMessage message, Room room) {
message.appendBoolean(false);
message.appendInt(5);
message.appendInt(0);
this.refresh(room);
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.getId());
message.appendString("");
@@ -69,8 +90,36 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
this.comparison = (params.length > 0) ? this.normalizeComparison(params[0]) : COMPARISON_EQUAL;
this.quantity = (params.length > 1) ? this.normalizeQuantity(params[1]) : 0;
this.sourceGroup = (params.length > 2) ? this.normalizeSourceGroup(params[2]) : SOURCE_GROUP_USERS;
this.sourceType = (params.length > 3) ? this.normalizeSourceType(this.sourceGroup, params[3]) : WiredSourceUtil.SOURCE_TRIGGER;
this.items.clear();
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;
}
@@ -97,11 +146,14 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
@Override
public String getWiredData() {
this.refresh(Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()));
return WiredManager.getGson().toJson(new JsonData(
this.comparison,
this.quantity,
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.sourceGroup = this.normalizeSourceGroup(data.sourceGroup);
this.sourceType = this.normalizeSourceType(this.sourceGroup, data.sourceType);
this.loadSelectedItems(data.itemIds, room);
return;
}
@@ -150,6 +203,7 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
@Override
public void onPickUp() {
this.items.clear();
this.comparison = COMPARISON_EQUAL;
this.quantity = 0;
this.sourceGroup = SOURCE_GROUP_USERS;
@@ -158,7 +212,7 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
private int resolveCount(WiredContext ctx) {
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();
}
@@ -188,10 +242,18 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
private int normalizeSourceType(int group, int value) {
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) {
case WiredSourceUtil.SOURCE_SELECTED:
case WiredSourceUtil.SOURCE_SELECTOR:
case WiredSourceUtil.SOURCE_SIGNAL:
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 {
int comparison;
int quantity;
int sourceGroup;
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.quantity = quantity;
this.sourceGroup = sourceGroup;
this.sourceType = sourceType;
this.itemIds = itemIds;
}
}
}
@@ -82,7 +82,7 @@ public class WiredEffectBotTalk extends InteractionWiredEffect {
this.setDelay(delay);
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;
return true;
@@ -105,7 +105,7 @@ public class WiredEffectBotTalkToHabbo extends InteractionWiredEffect {
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.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.setDelay(delay);
@@ -4,6 +4,7 @@ import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
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.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.Room;
@@ -60,29 +61,74 @@ public class WiredEffectControlClock extends InteractionWiredEffect {
}
for (HabboItem item : effectiveItems) {
if (!(item instanceof InteractionGameUpCounter)) {
if (!(item instanceof InteractionGameTimer)) {
continue;
}
InteractionGameUpCounter counter = (InteractionGameUpCounter) item;
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;
if (item instanceof InteractionGameUpCounter) {
this.controlUpCounter((InteractionGameUpCounter) item, room);
continue;
}
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));
}
if (!(item instanceof InteractionGameUpCounter)) {
if (!(item instanceof InteractionGameTimer)) {
throw new WiredSaveException("wiredfurni.error.require_counter_furni");
}
@@ -53,26 +53,37 @@ public class WiredEffectFurniToFurni extends InteractionWiredEffect {
return;
}
HabboItem moveItem = this.resolveLastMoveItem(ctx);
HabboItem targetItem = this.resolveLastTargetItem(ctx);
List<HabboItem> moveItems = this.resolveMoveItems(ctx);
List<HabboItem> targetItems = this.resolveTargetItems(ctx);
if (moveItem == null || targetItem == null || moveItem.getId() == targetItem.getId()) {
if (moveItems.isEmpty() || targetItems.isEmpty()) {
return;
}
RoomTile targetTile = room.getLayout().getTile(targetItem.getX(), targetItem.getY());
if (targetTile == null) {
return;
}
int targetIndex = 0;
for (HabboItem moveItem : moveItems) {
if (moveItem == null) {
continue;
}
FurnitureMovementError error = WiredMoveCarryHelper.moveFurni(room, this, moveItem, targetTile, moveItem.getRotation(), null, false, ctx);
if (error == FurnitureMovementError.NONE) {
return;
}
HabboItem targetItem = targetItems.get(targetIndex % targetItems.size());
targetIndex++;
error = WiredMoveCarryHelper.moveFurni(room, this, moveItem, targetTile, moveItem.getRotation(), targetItem.getZ(), null, false, ctx);
if (error == FurnitureMovementError.NONE) {
return;
if (targetItem == null || moveItem.getId() == targetItem.getId()) {
continue;
}
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;
}
private HabboItem resolveLastMoveItem(WiredContext ctx) {
return this.resolveLastItem(ctx, this.moveSource, this.moveItems);
private List<HabboItem> resolveMoveItems(WiredContext ctx) {
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;
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) {
this.validateItems(items);
}
List<HabboItem> resolvedItems = WiredSourceUtil.resolveItems(ctx, source, items);
if (resolvedItems.isEmpty()) {
return null;
}
for (int index = resolvedItems.size() - 1; index >= 0; index--) {
HabboItem item = resolvedItems.get(index);
if (item != null) {
return item;
}
}
return null;
return WiredSourceUtil.resolveItems(ctx, source, items).stream()
.filter(item -> item != null && ctx.room().getHabboItem(item.getId()) != null)
.collect(Collectors.toList());
}
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;
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_TRIGGER = 1;
@@ -166,7 +166,7 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
.signalChannel(signalChannel)
.signalUserCount(signalUserCount)
.signalFurniCount(sourceItem != null ? 1 : 0)
.contextVariableScope(ctx.contextVariables())
.contextVariableScope(ctx.contextVariables().copy())
.triggeredByEffect(true);
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();
if (delay > Emulator.getConfig().getInt("hotel.wired.max_delay", 20)) {
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 int DELIVERY_DEDUP_CLEANUP_THRESHOLD = 512;
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 int userSource = WiredSourceUtil.SOURCE_TRIGGER;
@@ -96,9 +98,12 @@ public class WiredEffectWhisper extends InteractionWiredEffect {
if(gameClient.getHabbo() == null || !gameClient.getHabbo().hasPermission(Permission.ACC_SUPERWIRED)) {
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();
if(delay > Emulator.getConfig().getInt("hotel.wired.max_delay", 20))
@@ -109,6 +114,35 @@ public class WiredEffectWhisper extends InteractionWiredEffect {
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) {
return WiredSourceUtil.resolveUsers(ctx, this.userSource);
}
@@ -212,7 +246,9 @@ public class WiredEffectWhisper extends InteractionWiredEffect {
}
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()) {
habbo.getRoomUnit().getRoom().unIdle(habbo);
@@ -170,13 +170,15 @@ public class WiredExtraTextInputVariable extends InteractionWiredExtra {
}
public Integer resolveCapturedValue(Room room, String rawValue) {
String normalizedValue = rawValue != null ? rawValue.trim() : "";
if (normalizedValue.isEmpty()) {
return null;
}
String capturedValue = rawValue != null ? rawValue : "";
String normalizedValue = capturedValue.trim();
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 {
@@ -22,6 +22,7 @@ public class WiredExtraVariableTextConnector extends InteractionWiredExtra {
public static final int CODE = 79;
public static final int MAX_MAPPING_LENGTH = 1000;
public static final int MAX_MAPPING_LINES = 30;
private static final String PRESERVED_SPACE = "\u00A0";
private String mappingsText = "";
private LinkedHashMap<Integer, String> mappings = new LinkedHashMap<>();
@@ -123,8 +124,12 @@ public class WiredExtraVariableTextConnector extends InteractionWiredExtra {
return "";
}
String mappedValue = this.mappings.get(value);
return mappedValue != null ? mappedValue : String.valueOf(value);
if (this.mappings.containsKey(value)) {
String mappedValue = this.mappings.get(value);
return mappedValue != null ? preserveSpaces(mappedValue) : "";
}
return String.valueOf(value);
}
public Integer resolveValue(String text) {
@@ -132,17 +137,16 @@ public class WiredExtraVariableTextConnector extends InteractionWiredExtra {
return null;
}
String normalizedText = text.trim();
if (normalizedText.isEmpty()) {
return null;
}
String normalizedText = normalizePreservedSpaces(text);
for (Map.Entry<Integer, String> entry : this.mappings.entrySet()) {
if (entry == null || entry.getKey() == null || entry.getValue() == null) {
continue;
}
if (entry.getValue().trim().equalsIgnoreCase(normalizedText)) {
String normalizedMappingValue = normalizePreservedSpaces(entry.getValue());
if (normalizedMappingValue.equalsIgnoreCase(normalizedText)) {
return entry.getKey();
}
}
@@ -195,8 +199,8 @@ public class WiredExtraVariableTextConnector extends InteractionWiredExtra {
continue;
}
String line = rawLine.trim();
if (line.isEmpty()) {
String line = rawLine;
if (line.trim().isEmpty()) {
continue;
}
@@ -210,7 +214,7 @@ public class WiredExtraVariableTextConnector extends InteractionWiredExtra {
}
String keyPart = line.substring(0, separatorIndex).trim();
String valuePart = line.substring(separatorIndex + 1).trim();
String valuePart = line.substring(separatorIndex + 1);
try {
result.put(Integer.parseInt(keyPart), valuePart);
@@ -221,6 +225,14 @@ public class WiredExtraVariableTextConnector extends InteractionWiredExtra {
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 {
String mappingsText;
@@ -95,6 +95,11 @@ public class WiredEffectFurniAltitude extends InteractionWiredEffect {
return true;
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(
@@ -100,6 +100,11 @@ public class WiredEffectFurniArea extends InteractionWiredEffect {
return true;
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(rootX, rootY, areaWidth, areaHeight, filterExisting, invert, getDelay()));
@@ -155,6 +155,11 @@ public class WiredEffectFurniByType extends InteractionWiredEffect {
return true;
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
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_TILE_OFFSETS = 64;
private static final int GRID_RANGE = 4;
private int sourceType = SOURCE_USER_TRIGGER;
private boolean filterExisting = false;
@@ -69,8 +70,20 @@ public class WiredEffectFurniNeighborhood extends InteractionWiredEffect {
int totalRaw = 0;
int wiredSkipped = 0;
Set<HabboItem> result = new LinkedHashSet<>();
Set<HabboItem> neighborhoodItems = new LinkedHashSet<>();
for (int[] src : sourcePositions) {
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) {
int tx = src[0] + (offset[0] - this.targetOffsetX);
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());
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.
// 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());
}
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) {
switch (sourceType) {
case SOURCE_USER_TRIGGER: {
if (ctx.tile().isPresent()) {
return Collections.singletonList(new int[]{ ctx.tile().get().x, ctx.tile().get().y });
Optional<RoomUnit> actor = ctx.actor();
if (actor.isPresent()) {
return Collections.singletonList(new int[]{ actor.get().getX(), actor.get().getY() });
}
return ctx.actor()
.map(actor -> Collections.singletonList(new int[]{ actor.getX(), actor.getY() }))
return ctx.tile()
.map(tile -> Collections.singletonList(new int[]{ tile.x, tile.y }))
.orElse(Collections.emptyList());
}
case SOURCE_USER_SIGNAL: {
@@ -260,6 +309,16 @@ public class WiredEffectFurniNeighborhood extends InteractionWiredEffect {
return true;
}
@Override
public boolean hasRequiredSelectorTargets(WiredContext ctx) {
return ctx != null && ctx.targets().hasItems();
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(
@@ -128,6 +128,11 @@ public class WiredEffectFurniOnFurni extends InteractionWiredEffect {
return true;
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
this.refresh(Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()));
@@ -86,6 +86,11 @@ public class WiredEffectFurniPicks extends InteractionWiredEffect {
return true;
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(
@@ -77,6 +77,11 @@ public class WiredEffectFurniSignal extends InteractionWiredEffect {
return true;
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(this.filterExisting, this.invert, this.getDelay()));
@@ -86,6 +86,11 @@ public class WiredEffectUsersArea extends InteractionWiredEffect {
return true;
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(rootX, rootY, areaWidth, areaHeight, filterExisting, invert, getDelay()));
@@ -92,6 +92,11 @@ public class WiredEffectUsersByAction extends InteractionWiredEffect {
return true;
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(
@@ -90,6 +90,11 @@ public class WiredEffectUsersByName extends InteractionWiredEffect {
return true;
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
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;
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
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;
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
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;
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
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_TILE_OFFSETS = 64;
private static final int GRID_RANGE = 4;
private int sourceType = SOURCE_USER_TRIGGER;
private boolean filterExisting = false;
@@ -87,11 +88,25 @@ public class WiredEffectUsersNeighborhood extends InteractionWiredEffect {
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> neighborhoodUsers = new ArrayList<>();
for (RoomUnit unit : room.getRoomUnits()) {
String pos = unit.getX() + "," + unit.getY();
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);
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());
@@ -110,15 +125,51 @@ public class WiredEffectUsersNeighborhood extends InteractionWiredEffect {
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) {
switch (sourceType) {
case SOURCE_USER_TRIGGER: {
if (ctx.tile().isPresent()) {
return Collections.singletonList(new int[]{ ctx.tile().get().x, ctx.tile().get().y });
Optional<RoomUnit> actor = ctx.actor();
if (actor.isPresent()) {
return Collections.singletonList(new int[]{ actor.get().getX(), actor.get().getY() });
}
return ctx.actor()
.map(actor -> Collections.singletonList(new int[]{ actor.getX(), actor.getY() }))
return ctx.tile()
.map(tile -> Collections.singletonList(new int[]{ tile.x, tile.y }))
.orElse(Collections.emptyList());
}
case SOURCE_USER_SIGNAL: {
@@ -262,6 +313,16 @@ public class WiredEffectUsersNeighborhood extends InteractionWiredEffect {
return true;
}
@Override
public boolean hasRequiredSelectorTargets(WiredContext ctx) {
return ctx != null && ctx.targets().hasUsers();
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(
@@ -115,6 +115,11 @@ public class WiredEffectUsersOnFurni extends InteractionWiredEffect {
return true;
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
this.refresh(Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()));
@@ -71,6 +71,11 @@ public class WiredEffectUsersSignal extends InteractionWiredEffect {
return true;
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(this.filterExisting, this.invert, this.getDelay()));
@@ -76,6 +76,11 @@ public class WiredEffectUsersTeam extends InteractionWiredEffect {
return true;
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
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;
}
@Override
public boolean usesExistingSelectorTargets() {
return this.filterExisting;
}
@Override
public String getWiredData() {
this.refreshReferenceItems();
@@ -219,6 +219,10 @@ public class Messenger {
} catch (SQLException 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) {
@@ -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 {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) {
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.setString(2, getFallbackPetName(type));
statement.setInt(3, getFallbackOffspringType(type));
statement.setString(4, "");
statement.setString(5, "");
statement.setString(6, "");
statement.setString(7, "0");
statement.execute();
}
@@ -411,6 +417,42 @@ public class PetManager {
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) {
int type = Integer.parseInt(item.getName().toLowerCase().replace("a0 pet", ""));
@@ -540,4 +582,4 @@ public class PetManager {
return false;
}
}
}
@@ -1177,7 +1177,7 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
if (this.needsUpdate) {
try (Connection connection = Emulator.getDatabase().getDataSource()
.getConnection(); PreparedStatement statement = connection.prepareStatement(
"UPDATE rooms SET name = ?, description = ?, password = ?, state = ?, users_max = ?, category = ?, score = ?, paper_floor = ?, paper_wall = ?, paper_landscape = ?, thickness_wall = ?, wall_height = ?, thickness_floor = ?, moodlight_data = ?, tags = ?, allow_other_pets = ?, allow_other_pets_eat = ?, allow_walkthrough = ?, allow_hidewall = ?, chat_mode = ?, chat_weight = ?, chat_speed = ?, chat_hearing_distance = ?, chat_protection =?, who_can_mute = ?, who_can_kick = ?, who_can_ban = ?, poll_id = ?, guild_id = ?, roller_speed = ?, override_model = ?, is_staff_picked = ?, promoted = ?, trade_mode = ?, move_diagonally = ?, owner_id = ?, owner_name = ?, jukebox_active = ?, hidewired = ?, 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(2, this.description);
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(40, this.allowUnderpass ? "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();
this.needsUpdate = false;
} catch (SQLException e) {
@@ -4,6 +4,7 @@ import com.eu.habbo.Emulator;
import com.eu.habbo.core.DatabaseLoggable;
import com.eu.habbo.habbohotel.permissions.Permission;
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.ServerMessage;
import com.eu.habbo.messages.incoming.Incoming;
@@ -204,23 +205,14 @@ public class RoomChatMessage implements Runnable, ISerialize, DatabaseLoggable {
message.appendInt(this.getMessage().length());
// Custom prefix data
String prefixText = "";
String prefixColor = "";
String prefixIcon = "";
String prefixEffect = "";
if (this.habbo != null && this.habbo.getInventory() != null && this.habbo.getInventory().getPrefixesComponent() != null) {
com.eu.habbo.habbohotel.users.UserPrefix activePrefix = this.habbo.getInventory().getPrefixesComponent().getActivePrefix();
if (activePrefix != null) {
prefixText = activePrefix.getText();
prefixColor = activePrefix.getColor();
prefixIcon = activePrefix.getIcon();
prefixEffect = activePrefix.getEffect();
}
}
message.appendString(prefixText);
message.appendString(prefixColor);
message.appendString(prefixIcon);
message.appendString(prefixEffect);
UserCustomizationData customizationData = (this.habbo != null) ? UserCustomizationData.fromHabbo(this.habbo) : UserCustomizationData.empty();
message.appendString(customizationData.prefixText);
message.appendString(customizationData.prefixColor);
message.appendString(customizationData.prefixIcon);
message.appendString(customizationData.prefixEffect);
message.appendString(customizationData.prefixFont);
message.appendString(customizationData.nickIcon);
message.appendString(customizationData.displayOrder);
} catch (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.pets.*;
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.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.extra.*;
import com.eu.habbo.habbohotel.items.interactions.wired.triggers.WiredTriggerReceiveSignal;
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.HabboItem;
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.WiredMovementPhysics;
import com.eu.habbo.habbohotel.wired.tick.WiredTickable;
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.plugin.Event;
import com.eu.habbo.plugin.events.furniture.*;
@@ -94,7 +88,7 @@ public class RoomItemManager {
}
try (PreparedStatement statement = connection.prepareStatement(
"SELECT * FROM items WHERE room_id = ?")) {
"SELECT * FROM items WHERE room_id = ?")) {
statement.setInt(1, this.room.getId());
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
@@ -106,8 +100,8 @@ public class RoomItemManager {
}
if (this.itemCount() > Room.MAXIMUM_FURNI) {
LOGGER.error("Room ID: {} has exceeded the furniture limit ({} > {}).",
this.room.getId(), this.itemCount(), Room.MAXIMUM_FURNI);
LOGGER.error("Room ID: {} has exceeded the furniture limit ({} > {}).",
this.room.getId(), this.itemCount(), Room.MAXIMUM_FURNI);
}
}
@@ -116,7 +110,7 @@ public class RoomItemManager {
*/
public void loadWiredData(Connection connection) {
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());
try (ResultSet set = statement.executeQuery()) {
@@ -274,7 +268,7 @@ public class RoomItemManager {
}
if (iterator.value().getBaseItem().getInteractionType().getType()
== InteractionPostIt.class) {
== InteractionPostIt.class) {
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()
&& tile.y <= item.getY() + length - 1)) {
&& tile.y <= item.getY() + length - 1)) {
continue;
}
@@ -447,7 +441,7 @@ public class RoomItemManager {
}
if (highestItem != null && highestItem.getZ() + Item.getCurrentHeight(highestItem)
> item.getZ() + Item.getCurrentHeight(item)) {
> item.getZ() + Item.getCurrentHeight(item)) {
continue;
}
@@ -516,7 +510,7 @@ public class RoomItemManager {
}
if (highestItem != null && highestItem.getZ() + Item.getCurrentHeight(highestItem)
> item.getZ() + Item.getCurrentHeight(item)) {
> item.getZ() + Item.getCurrentHeight(item)) {
continue;
}
@@ -598,7 +592,7 @@ public class RoomItemManager {
}
if (lowestChair != null && lowestChair.getZ() + Item.getCurrentHeight(lowestChair)
> item.getZ() + Item.getCurrentHeight(item)) {
> item.getZ() + Item.getCurrentHeight(item)) {
continue;
}
@@ -647,7 +641,7 @@ public class RoomItemManager {
this.furniOwnerNames.put(item.getUserId(), habbo.getUsername());
} else {
LOGGER.error("Failed to find username for item (ID: {}, UserID: {})",
item.getId(), item.getUserId());
item.getId(), item.getUserId());
}
}
}
@@ -665,7 +659,7 @@ public class RoomItemManager {
if (specialTypes == null) {
return;
}
boolean isWiredItem = false;
synchronized (specialTypes) {
@@ -714,29 +708,29 @@ public class RoomItemManager {
} else if (item instanceof InteractionPetTree) {
specialTypes.addPetTree((InteractionPetTree) item);
} else if (item instanceof InteractionMoodLight ||
item instanceof InteractionPyramid ||
item instanceof InteractionMusicDisc ||
item instanceof InteractionBattleBanzaiSphere ||
item instanceof InteractionTalkingFurniture ||
item instanceof InteractionWater ||
item instanceof InteractionWaterItem ||
item instanceof InteractionMuteArea ||
item instanceof InteractionBuildArea ||
item instanceof InteractionTagPole ||
item instanceof InteractionTagField ||
item instanceof InteractionJukeBox ||
item instanceof InteractionPetBreedingNest ||
item instanceof InteractionBlackHole ||
item instanceof InteractionWiredHighscore ||
item instanceof InteractionStickyPole ||
item instanceof WiredBlob ||
item instanceof InteractionTent ||
item instanceof InteractionSnowboardSlope ||
item instanceof InteractionFireworks) {
item instanceof InteractionPyramid ||
item instanceof InteractionMusicDisc ||
item instanceof InteractionBattleBanzaiSphere ||
item instanceof InteractionTalkingFurniture ||
item instanceof InteractionWater ||
item instanceof InteractionWaterItem ||
item instanceof InteractionMuteArea ||
item instanceof InteractionBuildArea ||
item instanceof InteractionTagPole ||
item instanceof InteractionTagField ||
item instanceof InteractionJukeBox ||
item instanceof InteractionPetBreedingNest ||
item instanceof InteractionBlackHole ||
item instanceof InteractionWiredHighscore ||
item instanceof InteractionStickyPole ||
item instanceof WiredBlob ||
item instanceof InteractionTent ||
item instanceof InteractionSnowboardSlope ||
item instanceof InteractionFireworks) {
specialTypes.addUndefined(item);
}
}
// Invalidate wired cache when wired items are added
if (isWiredItem) {
WiredManager.invalidateRoom(this.room);
@@ -810,7 +804,7 @@ public class RoomItemManager {
}
this.room.getFurniVariableManager().removeAssignmentsForFurni(item.getId());
boolean isWiredItem = false;
// Unregister from tick service for time-based wired triggers (new 50ms tick system)
@@ -822,53 +816,53 @@ public class RoomItemManager {
specialTypes.removeCycleTask((ICycleable) item);
}
if (item instanceof InteractionBattleBanzaiTeleporter) {
specialTypes.removeBanzaiTeleporter((InteractionBattleBanzaiTeleporter) item);
} else if (item instanceof InteractionWiredTrigger) {
specialTypes.removeTrigger((InteractionWiredTrigger) item);
isWiredItem = true;
} else if (item instanceof InteractionWiredEffect) {
specialTypes.removeEffect((InteractionWiredEffect) item);
isWiredItem = true;
} else if (item instanceof InteractionWiredCondition) {
specialTypes.removeCondition((InteractionWiredCondition) item);
isWiredItem = true;
} else if (item instanceof InteractionWiredExtra) {
boolean removedContextDefinition = false;
boolean removedVariableTextConnector = false;
if (item instanceof WiredExtraUserVariable) {
this.room.getUserVariableManager().removeDefinition(item.getId());
} else if (item instanceof WiredExtraFurniVariable) {
this.room.getFurniVariableManager().removeDefinition(item.getId());
} else if (item instanceof WiredExtraRoomVariable) {
this.room.getRoomVariableManager().removeDefinition(item.getId());
} else if (item instanceof WiredExtraContextVariable) {
removedContextDefinition = true;
} else if (item instanceof WiredExtraVariableTextConnector) {
removedVariableTextConnector = true;
} else if (item instanceof WiredExtraVariableReference) {
if (((WiredExtraVariableReference) item).isRoomReference()) {
this.room.getRoomVariableManager().removeDefinition(item.getId());
} else {
this.room.getUserVariableManager().removeDefinition(item.getId());
}
} else if (item instanceof WiredExtraVariableEcho) {
WiredExtraVariableEcho echo = (WiredExtraVariableEcho) item;
if (item instanceof InteractionBattleBanzaiTeleporter) {
specialTypes.removeBanzaiTeleporter((InteractionBattleBanzaiTeleporter) item);
} else if (item instanceof InteractionWiredTrigger) {
specialTypes.removeTrigger((InteractionWiredTrigger) item);
isWiredItem = true;
} else if (item instanceof InteractionWiredEffect) {
specialTypes.removeEffect((InteractionWiredEffect) item);
isWiredItem = true;
} else if (item instanceof InteractionWiredCondition) {
specialTypes.removeCondition((InteractionWiredCondition) item);
isWiredItem = true;
} else if (item instanceof InteractionWiredExtra) {
boolean removedContextDefinition = false;
boolean removedVariableTextConnector = false;
if (item instanceof WiredExtraUserVariable) {
this.room.getUserVariableManager().removeDefinition(item.getId());
} else if (item instanceof WiredExtraFurniVariable) {
this.room.getFurniVariableManager().removeDefinition(item.getId());
} else if (item instanceof WiredExtraRoomVariable) {
this.room.getRoomVariableManager().removeDefinition(item.getId());
} else if (item instanceof WiredExtraContextVariable) {
removedContextDefinition = true;
} else if (item instanceof WiredExtraVariableTextConnector) {
removedVariableTextConnector = true;
} else if (item instanceof WiredExtraVariableReference) {
if (((WiredExtraVariableReference) item).isRoomReference()) {
this.room.getRoomVariableManager().removeDefinition(item.getId());
} else {
this.room.getUserVariableManager().removeDefinition(item.getId());
}
} else if (item instanceof WiredExtraVariableEcho) {
WiredExtraVariableEcho echo = (WiredExtraVariableEcho) item;
if (echo.isRoomEcho()) {
this.room.getRoomVariableManager().removeDefinition(item.getId());
} else if (echo.isFurniEcho()) {
this.room.getFurniVariableManager().removeDefinition(item.getId());
} else {
this.room.getUserVariableManager().removeDefinition(item.getId());
}
}
specialTypes.removeExtra((InteractionWiredExtra) item);
if (removedContextDefinition || removedVariableTextConnector) {
WiredContextVariableSupport.broadcastDefinitions(this.room);
}
isWiredItem = true;
} else if (item instanceof InteractionRoller) {
if (echo.isRoomEcho()) {
this.room.getRoomVariableManager().removeDefinition(item.getId());
} else if (echo.isFurniEcho()) {
this.room.getFurniVariableManager().removeDefinition(item.getId());
} else {
this.room.getUserVariableManager().removeDefinition(item.getId());
}
}
specialTypes.removeExtra((InteractionWiredExtra) item);
if (removedContextDefinition || removedVariableTextConnector) {
WiredContextVariableSupport.broadcastDefinitions(this.room);
}
isWiredItem = true;
} else if (item instanceof InteractionRoller) {
specialTypes.removeRoller((InteractionRoller) item);
} else if (item instanceof InteractionGameScoreboard) {
specialTypes.removeScoreboard((InteractionGameScoreboard) item);
@@ -889,26 +883,26 @@ public class RoomItemManager {
} else if (item instanceof InteractionPetTree) {
specialTypes.removePetTree((InteractionPetTree) item);
} else if (item instanceof InteractionMoodLight ||
item instanceof InteractionPyramid ||
item instanceof InteractionMusicDisc ||
item instanceof InteractionBattleBanzaiSphere ||
item instanceof InteractionTalkingFurniture ||
item instanceof InteractionWaterItem ||
item instanceof InteractionWater ||
item instanceof InteractionMuteArea ||
item instanceof InteractionTagPole ||
item instanceof InteractionTagField ||
item instanceof InteractionJukeBox ||
item instanceof InteractionPetBreedingNest ||
item instanceof InteractionBlackHole ||
item instanceof InteractionWiredHighscore ||
item instanceof InteractionStickyPole ||
item instanceof WiredBlob ||
item instanceof InteractionTent ||
item instanceof InteractionSnowboardSlope) {
item instanceof InteractionPyramid ||
item instanceof InteractionMusicDisc ||
item instanceof InteractionBattleBanzaiSphere ||
item instanceof InteractionTalkingFurniture ||
item instanceof InteractionWaterItem ||
item instanceof InteractionWater ||
item instanceof InteractionMuteArea ||
item instanceof InteractionTagPole ||
item instanceof InteractionTagField ||
item instanceof InteractionJukeBox ||
item instanceof InteractionPetBreedingNest ||
item instanceof InteractionBlackHole ||
item instanceof InteractionWiredHighscore ||
item instanceof InteractionStickyPole ||
item instanceof WiredBlob ||
item instanceof InteractionTent ||
item instanceof InteractionSnowboardSlope) {
specialTypes.removeUndefined(item);
}
// Invalidate wired cache when wired items are removed
if (isWiredItem || cleanedSignalAntennaReferences) {
WiredManager.invalidateRoom(this.room);
@@ -936,9 +930,9 @@ public class RoomItemManager {
if (item.getBaseItem().getType() == FurnitureType.FLOOR) {
this.room.sendComposer(new FloorItemUpdateComposer(item).compose());
this.room.updateTiles(this.room.getLayout()
.getTilesAt(this.room.getLayout().getTile(item.getX(), item.getY()),
item.getBaseItem().getWidth(), item.getBaseItem().getLength(),
item.getRotation()));
.getTilesAt(this.room.getLayout().getTile(item.getX(), item.getY()),
item.getBaseItem().getWidth(), item.getBaseItem().getLength(),
item.getRotation()));
} else if (item.getBaseItem().getType() == FurnitureType.WALL) {
this.room.sendComposer(new WallItemUpdateComposer(item).compose());
}
@@ -963,9 +957,9 @@ public class RoomItemManager {
}
this.room.updateTiles(this.room.getLayout()
.getTilesAt(this.room.getLayout().getTile(item.getX(), item.getY()),
item.getBaseItem().getWidth(), item.getBaseItem().getLength(),
item.getRotation()));
.getTilesAt(this.room.getLayout().getTile(item.getX(), item.getY()),
item.getBaseItem().getWidth(), item.getBaseItem().getLength(),
item.getRotation()));
if (item instanceof InteractionMultiHeight) {
((InteractionMultiHeight) item).updateUnitsOnItem(this.room);
@@ -1032,7 +1026,7 @@ public class RoomItemManager {
if (Emulator.getPluginManager().isRegistered(FurniturePickedUpEvent.class, true)) {
FurniturePickedUpEvent event = Emulator.getPluginManager()
.fireEvent(new FurniturePickedUpEvent(item, picker));
.fireEvent(new FurniturePickedUpEvent(item, picker));
if (event.isCancelled()) {
return;
@@ -1060,10 +1054,10 @@ public class RoomItemManager {
}
THashSet<RoomTile> updatedTiles = this.room.getLayout().getTilesAt(
this.room.getLayout().getTile(item.getX(), item.getY()),
item.getBaseItem().getWidth(),
item.getBaseItem().getLength(),
item.getRotation());
this.room.getLayout().getTile(item.getX(), item.getY()),
item.getBaseItem().getWidth(),
item.getBaseItem().getLength(),
item.getRotation());
this.room.updateTiles(updatedTiles);
for (RoomTile tile : updatedTiles) {
@@ -1114,6 +1108,7 @@ public class RoomItemManager {
if (habbo != null && !inventoryItems.isEmpty()) {
habbo.getInventory().getItemsComponent().addItems(inventoryItems);
habbo.getClient().sendResponse(new AddHabboItemComposer(inventoryItems));
habbo.getClient().sendResponse(new InventoryRefreshComposer());
}
for (HabboItem i : items) {
@@ -1160,7 +1155,7 @@ public class RoomItemManager {
}
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()) {
user.getInventory().getItemsComponent().addItems(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 x = 0; x < item.getBaseItem().getWidth(); x++) {
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) {
lockedTiles.add(tile);
@@ -1233,7 +1229,7 @@ public class RoomItemManager {
for (short y = 0; y < item.getBaseItem().getWidth(); y++) {
for (short x = 0; x < item.getBaseItem().getLength(); x++) {
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) {
lockedTiles.add(tile);
@@ -1324,8 +1320,8 @@ public class RoomItemManager {
rotation %= 8;
if (this.room.hasRights(habbo) || this.room.getGuildRightLevel(habbo)
.isEqualOrGreaterThan(RoomRightLevels.GUILD_RIGHTS) || habbo.hasPermission(
Permission.ACC_MOVEROTATE) || BuildersClubRoomSupport.canPlaceInRoom(habbo, this.room)) {
.isEqualOrGreaterThan(RoomRightLevels.GUILD_RIGHTS) || habbo.hasPermission(
Permission.ACC_MOVEROTATE) || BuildersClubRoomSupport.canPlaceInRoom(habbo, this.room)) {
return FurnitureMovementError.NONE;
}
@@ -1334,10 +1330,10 @@ public class RoomItemManager {
if (rentSpace != null) {
if (!RoomLayout.squareInSquare(RoomLayout.getRectangle(rentSpace.getX(), rentSpace.getY(),
rentSpace.getBaseItem().getWidth(), rentSpace.getBaseItem().getLength(),
rentSpace.getRotation()),
RoomLayout.getRectangle(tile.x, tile.y, item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), rotation))) {
rentSpace.getBaseItem().getWidth(), rentSpace.getBaseItem().getLength(),
rentSpace.getRotation()),
RoomLayout.getRectangle(tile.x, tile.y, item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), rotation))) {
return FurnitureMovementError.NO_RIGHTS;
} else {
return FurnitureMovementError.NONE;
@@ -1347,7 +1343,7 @@ public class RoomItemManager {
for (HabboItem area : this.room.getRoomSpecialTypes().getItemsOfType(InteractionBuildArea.class)) {
if (((InteractionBuildArea) area).inSquare(tile) && ((InteractionBuildArea) area).isBuilder(
habbo.getHabboInfo().getUsername())) {
habbo.getHabboInfo().getUsername())) {
return FurnitureMovementError.NONE;
}
}
@@ -1438,14 +1434,14 @@ public class RoomItemManager {
}
THashSet<RoomTile> occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), rotation);
item.getBaseItem().getLength(), rotation);
for (RoomTile t : occupiedTiles) {
if (t.state == RoomTileState.INVALID) {
return FurnitureMovementError.INVALID_MOVE;
}
if (!Emulator.getConfig().getBoolean("wired.place.under", false) || (
Emulator.getConfig().getBoolean("wired.place.under", false) && !item.isWalkable()
&& !item.getBaseItem().allowSit() && !item.getBaseItem().allowLay())) {
Emulator.getConfig().getBoolean("wired.place.under", false) && !item.isWalkable()
&& !item.getBaseItem().allowSit() && !item.getBaseItem().allowLay())) {
if (checkForUnits && this.room.hasHabbosAt(t.x, t.y)) {
return FurnitureMovementError.TILE_HAS_HABBOS;
}
@@ -1490,7 +1486,7 @@ public class RoomItemManager {
}
THashSet<RoomTile> occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), rotation);
item.getBaseItem().getLength(), rotation);
for (RoomTile t : occupiedTiles) {
if (t.state == RoomTileState.INVALID) {
return FurnitureMovementError.INVALID_MOVE;
@@ -1542,7 +1538,7 @@ public class RoomItemManager {
boolean pluginHelper = false;
if (Emulator.getPluginManager().isRegistered(FurniturePlacedEvent.class, true)) {
FurniturePlacedEvent event = Emulator.getPluginManager()
.fireEvent(new FurniturePlacedEvent(item, owner, tile));
.fireEvent(new FurniturePlacedEvent(item, owner, tile));
if (event.isCancelled()) {
return FurnitureMovementError.CANCEL_PLUGIN_PLACE;
@@ -1553,7 +1549,7 @@ public class RoomItemManager {
RoomLayout layout = this.room.getLayout();
THashSet<RoomTile> occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), rotation);
item.getBaseItem().getLength(), rotation);
FurnitureMovementError fits = furnitureFitsAt(tile, item, rotation);
@@ -1572,7 +1568,7 @@ public class RoomItemManager {
if (Emulator.getPluginManager().isRegistered(FurnitureBuildheightEvent.class, true)) {
FurnitureBuildheightEvent event = Emulator.getPluginManager()
.fireEvent(new FurnitureBuildheightEvent(item, owner, 0.00, height));
.fireEvent(new FurnitureBuildheightEvent(item, owner, 0.00, height));
if (event.hasChangedHeight()) {
height = layout.getHeightAtSquare(tile.x, tile.y) + event.getUpdatedHeight();
}
@@ -1592,7 +1588,7 @@ public class RoomItemManager {
item.onPlace(this.room);
this.room.updateTiles(occupiedTiles);
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)) {
RoomConfInvisSupport.sendState(this.room);
@@ -1620,7 +1616,7 @@ public class RoomItemManager {
*/
public FurnitureMovementError placeWallFurniAt(HabboItem item, String wallPosition, Habbo 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;
}
@@ -1638,7 +1634,7 @@ public class RoomItemManager {
this.furniOwnerNames.put(item.getUserId(), this.resolveOwnerName(item, owner));
}
this.room.sendComposer(
new AddWallItemComposer(item, this.getFurniOwnerName(item.getUserId())).compose());
new AddWallItemComposer(item, this.getFurniOwnerName(item.getUserId())).compose());
item.needsUpdate(true);
this.addHabboItem(item);
item.setRoomId(this.room.getId());
@@ -1989,7 +1985,7 @@ public class RoomItemManager {
boolean pluginHelper = false;
if (Emulator.getPluginManager().isRegistered(FurnitureMovedEvent.class, true)) {
FurnitureMovedEvent event = Emulator.getPluginManager()
.fireEvent(new FurnitureMovedEvent(item, actor, oldLocation, tile));
.fireEvent(new FurnitureMovedEvent(item, actor, oldLocation, tile));
if (event.isCancelled()) {
return FurnitureMovementError.CANCEL_PLUGIN_MOVE;
}
@@ -2002,9 +1998,9 @@ public class RoomItemManager {
// Check if can be placed at new position
THashSet<RoomTile> occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), rotation);
item.getBaseItem().getLength(), rotation);
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);
@@ -2013,15 +2009,15 @@ public class RoomItemManager {
for (RoomTile t : occupiedTiles) {
HabboItem tileTopItem = this.getTopItemAt(t.x, t.y);
if (!magicTile && ((tileTopItem != null && tileTopItem != item ? (
t.state.equals(RoomTileState.INVALID) || !t.getAllowStack()
t.state.equals(RoomTileState.INVALID) || !t.getAllowStack()
|| !tileTopItem.getBaseItem().allowStack())
: this.room.calculateTileState(t, item).equals(RoomTileState.INVALID)))) {
: this.room.calculateTileState(t, item).equals(RoomTileState.INVALID)))) {
return FurnitureMovementError.CANT_STACK;
}
if (!Emulator.getConfig().getBoolean("wired.place.under", false) || (
Emulator.getConfig().getBoolean("wired.place.under", false) && !item.isWalkable()
&& !item.getBaseItem().allowSit() && !item.getBaseItem().allowLay())) {
Emulator.getConfig().getBoolean("wired.place.under", false) && !item.isWalkable()
&& !item.getBaseItem().allowSit() && !item.getBaseItem().allowLay())) {
if (checkForUnits) {
if (!magicTile && this.room.hasHabbosAt(t.x, t.y)) {
return FurnitureMovementError.TILE_HAS_HABBOS;
@@ -2048,8 +2044,8 @@ public class RoomItemManager {
}
THashSet<RoomTile> oldOccupiedTiles = layout.getTilesAt(
layout.getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), item.getRotation());
layout.getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), item.getRotation());
int oldRotation = item.getRotation();
@@ -2066,9 +2062,9 @@ public class RoomItemManager {
}
if ((stackHelper == null && topItem != null && topItem != item && !topItem.getBaseItem()
.allowStack()) || (topItem != null && topItem != item
&& topItem.getZ() + Item.getCurrentHeight(topItem) + Item.getCurrentHeight(item)
> Room.MAXIMUM_FURNI_HEIGHT)) {
.allowStack()) || (topItem != null && topItem != item
&& topItem.getZ() + Item.getCurrentHeight(topItem) + Item.getCurrentHeight(item)
> Room.MAXIMUM_FURNI_HEIGHT)) {
item.setRotation(oldRotation);
return FurnitureMovementError.CANT_STACK;
}
@@ -2117,7 +2113,7 @@ public class RoomItemManager {
if (Emulator.getPluginManager().isRegistered(FurnitureBuildheightEvent.class, true)) {
FurnitureBuildheightEvent event = Emulator.getPluginManager()
.fireEvent(new FurnitureBuildheightEvent(item, actor, 0.00, height));
.fireEvent(new FurnitureBuildheightEvent(item, actor, 0.00, height));
if (event.hasChangedHeight()) {
height = layout.getHeightAtSquare(tile.x, tile.y) + event.getUpdatedHeight();
pluginHeight = true;
@@ -2138,7 +2134,7 @@ public class RoomItemManager {
if (item.getZ() > Room.MAXIMUM_FURNI_HEIGHT) {
item.setZ(Room.MAXIMUM_FURNI_HEIGHT);
}
// Update wired spatial index and invalidate cache when wired items are moved
if (item instanceof InteractionWiredTrigger) {
this.room.getRoomSpecialTypes().updateTriggerLocation((InteractionWiredTrigger) item, oldLocation.x, oldLocation.y);
@@ -2198,7 +2194,7 @@ public class RoomItemManager {
boolean pluginHelper = false;
if (Emulator.getPluginManager().isRegistered(FurnitureMovedEvent.class, true)) {
FurnitureMovedEvent event = Emulator.getPluginManager()
.fireEvent(new FurnitureMovedEvent(item, actor, oldLocation, tile));
.fireEvent(new FurnitureMovedEvent(item, actor, oldLocation, tile));
if (event.isCancelled()) {
return FurnitureMovementError.CANCEL_PLUGIN_MOVE;
}
@@ -2210,9 +2206,9 @@ public class RoomItemManager {
HabboItem stackHelper = this.findStackHeightHelperAt(tile, item);
THashSet<RoomTile> occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), rotation);
item.getBaseItem().getLength(), rotation);
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);
@@ -2221,9 +2217,9 @@ public class RoomItemManager {
for (RoomTile t : occupiedTiles) {
HabboItem tileTopItem = this.getTopPhysicsItemAt(t.x, t.y, item, physics);
if (!magicTile && ((tileTopItem != null && tileTopItem != item ? (
t.state.equals(RoomTileState.INVALID) || !t.getAllowStack()
t.state.equals(RoomTileState.INVALID) || !t.getAllowStack()
|| !tileTopItem.getBaseItem().allowStack())
: this.room.calculateTileState(t, item).equals(RoomTileState.INVALID)))) {
: this.room.calculateTileState(t, item).equals(RoomTileState.INVALID)))) {
return FurnitureMovementError.CANT_STACK;
}
@@ -2251,8 +2247,8 @@ public class RoomItemManager {
}
THashSet<RoomTile> oldOccupiedTiles = layout.getTilesAt(
layout.getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), item.getRotation());
layout.getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(),
item.getBaseItem().getLength(), item.getRotation());
int oldRotation = item.getRotation();
@@ -2269,9 +2265,9 @@ public class RoomItemManager {
}
if ((stackHelper == null && topItem != null && topItem != item && !topItem.getBaseItem()
.allowStack()) || (topItem != null && topItem != item
&& topItem.getZ() + Item.getCurrentHeight(topItem) + Item.getCurrentHeight(item)
> Room.MAXIMUM_FURNI_HEIGHT)) {
.allowStack()) || (topItem != null && topItem != item
&& topItem.getZ() + Item.getCurrentHeight(topItem) + Item.getCurrentHeight(item)
> Room.MAXIMUM_FURNI_HEIGHT)) {
item.setRotation(oldRotation);
return FurnitureMovementError.CANT_STACK;
}
@@ -2319,7 +2315,7 @@ public class RoomItemManager {
if (Emulator.getPluginManager().isRegistered(FurnitureBuildheightEvent.class, true)) {
FurnitureBuildheightEvent event = Emulator.getPluginManager()
.fireEvent(new FurnitureBuildheightEvent(item, actor, 0.00, height));
.fireEvent(new FurnitureBuildheightEvent(item, actor, 0.00, height));
if (event.hasChangedHeight()) {
height = layout.getHeightAtSquare(tile.x, tile.y) + event.getUpdatedHeight();
pluginHeight = true;
@@ -2391,10 +2387,10 @@ public class RoomItemManager {
boolean magicTile = this.isStackPlacementBypassItem(item);
RoomLayout layout = this.room.getLayout();
// Check if can be placed at new position
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<>();
for (RoomTile t : occupiedTiles) {
@@ -2438,8 +2434,8 @@ public class RoomItemManager {
}
return !item.isWalkable()
&& !item.getBaseItem().allowSit()
&& !item.getBaseItem().allowLay();
&& !item.getBaseItem().allowSit()
&& !item.getBaseItem().allowLay();
}
private FurnitureMovementError getPhysicsUnitCollision(RoomTile tile, WiredMovementPhysics physics) {
@@ -2515,7 +2511,7 @@ public class RoomItemManager {
for (HabboItem item : this.getPhysicsItemsAt(tile, exclude, physics)) {
if (highestItem != null && highestItem.getZ() + Item.getCurrentHeight(highestItem)
> item.getZ() + Item.getCurrentHeight(item)) {
> item.getZ() + Item.getCurrentHeight(item)) {
continue;
}
@@ -2539,7 +2535,7 @@ public class RoomItemManager {
}
if (highestItem != null && highestItem.getZ() + Item.getCurrentHeight(highestItem)
> topItem.getZ() + Item.getCurrentHeight(topItem)) {
> topItem.getZ() + Item.getCurrentHeight(topItem)) {
continue;
}
@@ -343,18 +343,16 @@ public class RoomSpecialTypes {
* Adds a wired trigger to the room.
* @param trigger The trigger to add
*/
public static final int MAX_SIGNAL_SENDERS_PER_ROOM = 25;
public static final int MAX_SIGNAL_RECEIVERS_PER_ROOM = 5;
public static final int MAX_SENDERS_PER_RECEIVER = 5;
public static final int MAX_SIGNAL_SENDERS_PER_ROOM = 0;
public static final int MAX_SIGNAL_RECEIVERS_PER_ROOM = 0;
public static final int MAX_SENDERS_PER_RECEIVER = 0;
public boolean isSignalSenderLimitReached() {
Set<InteractionWiredEffect> existing = this.getSignalSenders();
return existing != null && existing.size() >= MAX_SIGNAL_SENDERS_PER_ROOM;
return false;
}
public boolean isSignalReceiverLimitReached() {
Set<InteractionWiredTrigger> existing = this.wiredTriggers.get(WiredTriggerType.RECEIVE_SIGNAL);
return existing != null && existing.size() >= MAX_SIGNAL_RECEIVERS_PER_ROOM;
return false;
}
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 InfostandStand;
private int InfostandOverlay;
private int InfostandCardBg;
private int loadingRoom;
private Room currentRoom;
private String roomEntryMethod = "door";
@@ -91,6 +92,7 @@ public class HabboInfo implements Runnable {
this.InfostandBg = set.getInt("background_id");
this.InfostandStand = set.getInt("background_stand_id");
this.InfostandOverlay = set.getInt("background_overlay_id");
this.InfostandCardBg = set.getInt("background_card_id");
this.currentRoom = null;
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
@@ -290,6 +292,14 @@ public class HabboInfo implements Runnable {
public void setInfostandOverlay(int infostandOverlay) {
InfostandOverlay = infostandOverlay;
}
public int getInfostandCardBg() {
return InfostandCardBg;
}
public void setInfostandCardBg(int infostandCardBg) {
InfostandCardBg = infostandCardBg;
}
public Rank getRank() {
return this.rank;
}
@@ -577,7 +587,7 @@ public class HabboInfo implements Runnable {
try {
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.online ? "1" : "0",
this.look,
@@ -593,6 +603,7 @@ public class HabboInfo implements Runnable {
this.InfostandBg,
this.InfostandStand,
this.InfostandOverlay,
this.InfostandCardBg,
this.id);
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e);
@@ -23,6 +23,8 @@ public class HabboInventory {
private ItemsComponent itemsComponent;
private PetsComponent petsComponent;
private PrefixesComponent prefixesComponent;
private NickIconsComponent nickIconsComponent;
private UserVisualSettingsComponent userVisualSettingsComponent;
public HabboInventory(Habbo habbo) {
this.habbo = habbo;
@@ -68,6 +70,18 @@ public class HabboInventory {
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);
}
@@ -127,6 +141,22 @@ public class HabboInventory {
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() {
this.badgesComponent.dispose();
this.botsComponent.dispose();
@@ -135,6 +165,8 @@ public class HabboInventory {
this.petsComponent.dispose();
this.wardrobeComponent.dispose();
this.prefixesComponent.dispose();
this.nickIconsComponent.dispose();
this.userVisualSettingsComponent.dispose();
this.badgesComponent = null;
this.botsComponent = null;
@@ -143,6 +175,8 @@ public class HabboInventory {
this.petsComponent = null;
this.wardrobeComponent = null;
this.prefixesComponent = null;
this.nickIconsComponent = null;
this.userVisualSettingsComponent = null;
}
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 icon;
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 needsInsert;
private boolean needsUpdate;
@@ -29,6 +35,12 @@ public class UserPrefix implements Runnable {
if (this.icon == null) this.icon = "";
this.effect = set.getString("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.needsInsert = 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) {
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.userId = userId;
this.text = text;
this.color = color;
this.icon = icon != null ? icon : "";
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.needsInsert = true;
this.needsUpdate = false;
@@ -54,14 +76,20 @@ public class UserPrefix implements Runnable {
if (this.needsInsert) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
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.setInt(1, this.userId);
statement.setString(2, this.text);
statement.setString(3, this.color);
statement.setString(4, this.icon);
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();
try (ResultSet set = statement.getGeneratedKeys()) {
if (set.next()) {
@@ -82,14 +110,20 @@ public class UserPrefix implements Runnable {
} else if (this.needsUpdate) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
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(2, this.color);
statement.setString(3, this.icon);
statement.setString(4, this.effect);
statement.setBoolean(5, this.active);
statement.setInt(6, this.id);
statement.setInt(7, this.userId);
statement.setString(5, this.font);
statement.setBoolean(6, this.active);
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();
}
this.needsUpdate = false;
@@ -109,6 +143,13 @@ public class UserPrefix implements Runnable {
public void setIcon(String icon) { this.icon = icon != null ? icon : ""; }
public String getEffect() { return this.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 void setActive(boolean active) {
@@ -119,4 +160,29 @@ public class UserPrefix implements Runnable {
public void needsUpdate(boolean needsUpdate) { this.needsUpdate = needsUpdate; }
public void needsInsert(boolean needsInsert) { this.needsInsert = needsInsert; }
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;
import com.eu.habbo.Emulator;
import com.eu.habbo.database.SqlQueries;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.users.Habbo;
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.InventoryItemsAddedEvent;
import gnu.trove.TCollections;
import gnu.trove.iterator.TIntObjectIterator;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.THashMap;
import gnu.trove.map.hash.TIntObjectHashMap;
@@ -18,9 +18,11 @@ import gnu.trove.set.hash.THashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
public class ItemsComponent {
private static final Logger LOGGER = LoggerFactory.getLogger(ItemsComponent.class);
@@ -37,23 +39,25 @@ public class ItemsComponent {
public static THashMap<Integer, HabboItem> loadItems(Habbo habbo) {
THashMap<Integer, HabboItem> itemsList = new THashMap<>();
try {
SqlQueries.forEach(
"SELECT * FROM items WHERE room_id = ? AND user_id = ?",
rs -> {
try {
HabboItem item = Emulator.getGameEnvironment().getItemManager().loadHabboItem(rs);
if (item != null) {
itemsList.put(rs.getInt("id"), item);
} else {
LOGGER.error("Failed to load HabboItem: {}", rs.getInt("id"));
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
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")) {
statement.setInt(1, 0);
statement.setInt(2, habbo.getHabboInfo().getId());
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
try {
HabboItem item = Emulator.getGameEnvironment().getItemManager().loadHabboItem(set);
if (item != null) {
itemsList.put(set.getInt("id"), item);
} else {
LOGGER.error("Failed to load HabboItem: {}", set.getInt("id"));
}
},
0, habbo.getHabboInfo().getId());
} catch (SqlQueries.DataAccessException e) {
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
@@ -147,45 +151,70 @@ public class ItemsComponent {
public void dispose() {
synchronized (this.items) {
if (!this.items.isEmpty()) {
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);
}
}
TIntObjectIterator<HabboItem> items = this.items.iterator();
try {
if (!deletes.isEmpty()) {
SqlQueries.batchUpdate(
"DELETE FROM items WHERE id = ?",
deletes,
(ps, item) -> ps.setInt(1, item.getId()));
if (items == null) {
LOGGER.error("Items is NULL!");
return;
}
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()) {
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) {
} catch (SQLException 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;
}
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) {
synchronized (this.prefixes) {
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;
}
/**
* 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.
* <p>
@@ -112,7 +112,7 @@ public final class WiredContext {
this.state = state;
this.legacySettings = legacySettings;
this.contextVariables = (event.getContextVariableScope() != null)
? event.getContextVariableScope()
? event.getContextVariableScope().copy()
: new WiredContextVariableScope();
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.outgoing.generic.alerts.BubbleAlertComposer;
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.WiredStackTriggeredEvent;
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 */
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.
*
@@ -151,6 +155,7 @@ public final class WiredEngine {
this.bannedRooms = new ConcurrentHashMap<>();
this.roomDiagnostics = new ConcurrentHashMap<>();
this.sourceStacksByTriggerKey = new ConcurrentHashMap<>();
this.filteredSelectorAnimationTokens = new ConcurrentHashMap<>();
}
/**
@@ -426,6 +431,10 @@ public final class WiredEngine {
applySelectionFilterExtras(stack, ctx, executedSelectors);
}
if (!selectorsHaveRequiredTargets(executedSelectors, ctx)) {
return false;
}
boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, negateConditions);
List<IWiredEffect> executableEffects = getExecutableEffectsForCurrentExecution(stack, conditionsPassedForExecution);
boolean hasSpecialOutcome = conditionsPassedForExecution && hasSpecialTriggerOutcome(stack, event);
@@ -541,6 +550,10 @@ public final class WiredEngine {
applySelectionFilterExtras(stack, ctx, executedSelectors);
}
if (!selectorsHaveRequiredTargets(executedSelectors, ctx)) {
return false;
}
boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, negateConditions);
List<IWiredEffect> executableEffects = getExecutableEffectsForCurrentExecution(stack, conditionsPassedForExecution);
boolean hasSpecialOutcome = conditionsPassedForExecution && hasSpecialTriggerOutcome(stack, event);
@@ -627,6 +640,10 @@ public final class WiredEngine {
applySelectionFilterExtras(stack, ctx, executedSelectors);
}
if (!selectorsHaveRequiredTargets(executedSelectors, ctx)) {
return false;
}
boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, negateConditions);
List<IWiredEffect> executableEffects = getExecutableEffectsForCurrentExecution(stack, conditionsPassedForExecution);
return !executableEffects.isEmpty();
@@ -660,6 +677,10 @@ public final class WiredEngine {
applySelectionFilterExtras(stack, ctx, executedSelectors);
}
if (!selectorsHaveRequiredTargets(executedSelectors, ctx)) {
return false;
}
boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, false);
if (!conditionsPassedForExecution) {
return false;
@@ -1011,9 +1032,27 @@ public final class WiredEngine {
if (effects.isEmpty()) return Collections.emptyList();
List<InteractionWiredEffect> executedSelectors = new ArrayList<>();
List<IWiredEffect> immediateSelectors = new ArrayList<>();
List<IWiredEffect> deferredSelectors = new ArrayList<>();
for (IWiredEffect effect : effects) {
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()) {
continue;
}
@@ -1022,14 +1061,17 @@ public final class WiredEngine {
try {
effect.execute(ctx);
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) {
LOGGER.warn("Error executing selector: {}", e.getMessage());
}
}
return executedSelectors;
}
private void finalizeSelectors(List<InteractionWiredEffect> executedSelectors, WiredContext ctx, long currentTime) {
@@ -1042,7 +1084,56 @@ public final class WiredEngine {
for (InteractionWiredEffect wiredEffect : executedSelectors) {
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);
}
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.
*/
@@ -151,8 +151,20 @@ public final class WiredSourceUtil {
selectorCtx.setIncludeWiredSelectorItems(originalCtx.includeWiredSelectorItems());
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) {
if (effect == null || effect.usesExistingSelectorTargets() != deferred) {
continue;
}
if (effect.requiresActor() && !selectorCtx.hasActor()) {
continue;
}
@@ -163,10 +175,6 @@ public final class WiredSourceUtil {
} catch (Exception ignored) {
}
}
applySelectionFilterExtras(room, triggerItem, selectorCtx);
return selectorCtx;
}
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();
}
MatchResult matchResult = matchTemplate(trigger, text, capturersByName);
MatchResult matchResult = matchTemplate(trigger, text, capturersByName, room);
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();
}
@@ -78,12 +87,28 @@ public final class WiredTextInputCaptureSupport {
Integer resolvedValue = capturer.resolveCapturedValue(room, capture.getValue());
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();
}
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);
}
@@ -108,12 +133,13 @@ public final class WiredTextInputCaptureSupport {
return capturers;
}
private static MatchResult matchTemplate(WiredTriggerHabboSaysKeyword trigger, String rawText, Map<String, WiredExtraTextInputVariable> capturersByName) {
String text = rawText != null ? rawText.trim() : "";
private static MatchResult matchTemplate(WiredTriggerHabboSaysKeyword trigger, String rawText, Map<String, WiredExtraTextInputVariable> capturersByName, Room room) {
String text = rawText != null ? rawText : "";
String normalizedText = text.trim();
String template = trigger.getKey() != null ? trigger.getKey().trim() : "";
if (trigger.getMatchMode() == MATCH_ALL_WORDS && template.isEmpty()) {
if (capturersByName.size() != 1 || text.isEmpty()) {
if (capturersByName.size() != 1 || normalizedText.isEmpty()) {
return MatchResult.noMatch();
}
@@ -123,12 +149,24 @@ public final class WiredTextInputCaptureSupport {
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);
if (pattern == null) {
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();
if (!matches) {
return MatchResult.noMatch();
@@ -142,12 +180,136 @@ public final class WiredTextInputCaptureSupport {
}
String capturedValue = matcher.group(index + 1);
captures.put(placeholderName, capturedValue != null ? capturedValue.trim() : "");
captures.put(placeholderName, normalizeCapturedValue(capturedValue));
}
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) {
if (template == null || template.isEmpty()) {
return null;
@@ -160,7 +322,7 @@ public final class WiredTextInputCaptureSupport {
while (matcher.find()) {
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() : "";
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);
}
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) {
if (ctx == null || room == null || captureResult == null || !captureResult.matches || captureResult.capturedValues.isEmpty()) {
return;
@@ -32,6 +32,8 @@ import java.util.List;
import java.util.Locale;
public final class WiredTextPlaceholderUtil {
private static final char PRESERVED_SPACE = '\u00A0';
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) {
@@ -275,7 +311,7 @@ public final class WiredTextPlaceholderUtil {
}
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) {
@@ -284,7 +320,7 @@ public final class WiredTextPlaceholderUtil {
}
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) {
@@ -11,6 +11,8 @@ import java.util.List;
import java.util.Map;
public final class WiredVariableTextConnectorSupport {
private static final String PRESERVED_SPACE = "\u00A0";
private WiredVariableTextConnectorSupport() {
}
@@ -71,7 +73,7 @@ public final class WiredVariableTextConnectorSupport {
Map<Integer, String> mappings = connector.getMappings();
if (mappings.containsKey(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;
}
String normalizedText = text.trim();
if (normalizedText.isEmpty()) {
return null;
}
String normalizedText = normalizePreservedSpaces(text);
for (WiredExtraVariableTextConnector connector : getConnectors(room, definitionItemId)) {
Integer mappedValue = connector.resolveValue(normalizedText);
@@ -97,4 +96,12 @@ public final class WiredVariableTextConnectorSupport {
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.hotelview.*;
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.modtool.*;
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.users.*;
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.UnknownEvent1;
import com.eu.habbo.messages.incoming.users.*;
@@ -117,6 +120,7 @@ public class PacketManager {
this.registerGuilds();
this.registerPets();
this.registerWired();
this.registerTranslation();
this.registerAchievements();
this.registerFloorPlanEditor();
this.registerAmbassadors();
@@ -409,6 +413,13 @@ public class PacketManager {
this.registerHandler(Incoming.SetActivePrefixEvent, SetActivePrefixEvent.class);
this.registerHandler(Incoming.DeletePrefixEvent, DeletePrefixEvent.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 {
@@ -635,6 +646,11 @@ public class PacketManager {
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 {
this.registerHandler(Incoming.RequestResolutionEvent, RequestResolutionEvent.class);
this.registerHandler(Incoming.RequestTalenTrackEvent, RequestTalentTrackEvent.class);
@@ -419,6 +419,8 @@ public class Incoming {
public static final int WiredUserVariableUpdateEvent = 10025;
public static final int WiredUserVariableManageEvent = 10026;
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 RequestInventoryBadgeDelete = 10031;
@@ -448,6 +450,11 @@ public class Incoming {
public static final int SetActivePrefixEvent = 7012;
public static final int DeletePrefixEvent = 7013;
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
public static final int YouTubeRoomPlayEvent = 8001;
@@ -29,7 +29,9 @@ public class GuildAcceptMembershipEvent extends MessageHandler {
if (guild != null) {
GuildMember groupMember = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guild, this.client.getHabbo());
if (userId == this.client.getHabbo().getHabboInfo().getId() || guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || groupMember.getRank().equals(GuildRank.ADMIN) || groupMember.getRank().equals(GuildRank.OWNER) || this.client.getHabbo().hasPermission(Permission.ACC_GUILD_ADMIN)) {
if (guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId()
|| this.client.getHabbo().hasPermission(Permission.ACC_GUILD_ADMIN)
|| (groupMember != null && (groupMember.getRank().equals(GuildRank.ADMIN) || groupMember.getRank().equals(GuildRank.OWNER)))) {
if (habbo != null) {
if (habbo.getHabboStats().hasGuild(guild.getId())) {
this.client.sendResponse(new GuildAcceptMemberErrorComposer(guild.getId(), GuildAcceptMemberErrorComposer.ALREADY_ACCEPTED));
@@ -11,6 +11,11 @@ import com.eu.habbo.messages.outgoing.guilds.GuildMemberUpdateComposer;
import com.eu.habbo.plugin.events.guilds.GuildGivenAdminEvent;
public class GuildSetAdminEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 500;
}
@Override
public void handle() throws Exception {
int guildId = this.packet.readInt();
@@ -48,7 +48,7 @@ public class GuildForumPostThreadEvent extends MessageHandler {
if (threadId == 0) {
if (!((guild.canPostThreads().state == 0)
|| (guild.canPostThreads().state == 1 && member != null)
|| (guild.canPostThreads().state == 1 && member != null && member.getRank().type <= GuildRank.MEMBER.type)
|| (guild.canPostThreads().state == 2 && member != null && (member.getRank().type < GuildRank.MEMBER.type))
|| (guild.canPostThreads().state == 3 && guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId())
|| isStaff)) {
@@ -87,7 +87,7 @@ public class GuildForumPostThreadEvent extends MessageHandler {
}
if (!((guild.canPostMessages().state == 0)
|| (guild.canPostMessages().state == 1 && member != null)
|| (guild.canPostMessages().state == 1 && member != null && member.getRank().type <= GuildRank.MEMBER.type)
|| (guild.canPostMessages().state == 2 && member != null && (member.getRank().type < GuildRank.MEMBER.type))
|| (guild.canPostMessages().state == 3 && guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId())
|| isStaff)) {
@@ -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.outgoing.generic.alerts.BubbleAlertKeys;
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.users.UserCreditsComposer;
import com.eu.habbo.messages.outgoing.users.UserCurrencyComposer;
@@ -19,6 +20,7 @@ import java.sql.SQLException;
public class PurchasePrefixEvent extends MessageHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(PurchasePrefixEvent.class);
private static final String[] ALLOWED_FONTS = { "", "pixel", "cherry", "vampiro" };
@Override
public int getRatelimit() {
@@ -31,6 +33,7 @@ public class PurchasePrefixEvent extends MessageHandler {
String color = this.packet.readString();
String icon = this.packet.readString();
String effect = this.packet.readString();
String font = this.packet.readString();
Habbo habbo = this.client.getHabbo();
@@ -42,6 +45,9 @@ public class PurchasePrefixEvent extends MessageHandler {
int priceCredits = getSettingInt("price_credits", 5);
int pricePoints = getSettingInt("price_points", 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
text = text.trim();
@@ -72,43 +78,67 @@ public class PurchasePrefixEvent extends MessageHandler {
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
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."));
return;
}
int totalPricePointsSameType = pricePoints + ((fontPricePoints > 0 && fontPointsType == pointsType && !font.isEmpty()) ? fontPricePoints : 0);
// 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."));
return;
}
// Deduct currency
if (priceCredits > 0) {
habbo.getHabboInfo().addCredits(-priceCredits);
if (totalPriceCredits > 0) {
habbo.getHabboInfo().addCredits(-totalPriceCredits);
this.client.sendResponse(new UserCreditsComposer(habbo));
}
if (pricePoints > 0) {
habbo.getHabboInfo().addCurrencyAmount(pointsType, -pricePoints);
if (totalPricePointsSameType > 0) {
habbo.getHabboInfo().addCurrencyAmount(pointsType, -totalPricePointsSameType);
this.client.sendResponse(new UserCurrencyComposer(habbo));
}
// Validate icon (allow empty or known icon names)
if (icon == null) icon = "";
icon = icon.trim();
// Validate effect
if (effect == null) effect = "";
effect = effect.trim();
if (!font.isEmpty() && fontPricePoints > 0 && fontPointsType != pointsType) {
habbo.getHabboInfo().addCurrencyAmount(fontPointsType, -fontPricePoints);
this.client.sendResponse(new UserCurrencyComposer(habbo));
}
// 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
habbo.getInventory().getPrefixesComponent().addPrefix(prefix);
this.client.sendResponse(new PrefixReceivedComposer(prefix));
this.client.sendResponse(new UserNickIconsComposer(habbo));
}
private int getSettingInt(String key, int defaultValue) {
@@ -142,4 +172,14 @@ public class PurchasePrefixEvent extends MessageHandler {
}
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.messages.incoming.MessageHandler;
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 {
@Override
@@ -12,6 +14,11 @@ public class SetActivePrefixEvent extends MessageHandler {
if (prefixId == 0) {
this.client.getHabbo().getInventory().getPrefixesComponent().deactivateAll();
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;
}
@@ -21,5 +28,10 @@ public class SetActivePrefixEvent extends MessageHandler {
this.client.getHabbo().getInventory().getPrefixesComponent().setActive(prefixId);
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;
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;
public class ActivateEffectEvent extends MessageHandler {
@Override
public void handle() throws Exception {
int effectId = this.packet.readInt();
Habbo habbo = this.client.getHabbo();
if (habbo == null) return;
if (this.client.getHabbo().getInventory().getEffectsComponent().ownsEffect(effectId)) {
this.client.getHabbo().getInventory().getEffectsComponent().activateEffect(effectId);
if (habbo.getInventory().getEffectsComponent().ownsEffect(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;
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.outgoing.rooms.users.RoomUserDataComposer;
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
public void handle() throws Exception {
int backgroundImage = this.packet.readInt();
int backgroundStand = this.packet.readInt();
int backgroundOverlay = this.packet.readInt();
Habbo habbo = this.client.getHabbo();
if (habbo == null) return;
this.client.getHabbo().getHabboInfo().setInfostandBg(backgroundImage);
this.client.getHabbo().getHabboInfo().setInfostandStand(backgroundStand);
this.client.getHabbo().getHabboInfo().setInfostandOverlay(backgroundOverlay);
this.client.getHabbo().getHabboInfo().run();
HabboInfo info = habbo.getHabboInfo();
if (info == null) return;
if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != null) {
this.client.getHabbo().getHabboInfo().getCurrentRoom().sendComposer(new RoomUserDataComposer(this.client.getHabbo()).compose());
HabboStats stats = habbo.getHabboStats();
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 {
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.messages.incoming.MessageHandler;
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;
public class WiredEffectSaveDataEvent extends MessageHandler {
@@ -39,6 +40,16 @@ public class WiredEffectSaveDataEvent extends MessageHandler {
if (saved) {
this.client.sendResponse(new WiredSavedComposer());
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);
Emulator.getThreading().run(effect);
} else {
@@ -124,6 +124,8 @@ public class Outgoing {
public final static int WiredRoomSettingsDataComposer = 5102; // CUSTOM
public final static int WiredUserVariablesDataComposer = 5103; // 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 RoomPaintComposer = 2454; // 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 PrefixReceivedComposer = 7002;
public static final int ActivePrefixUpdatedComposer = 7003;
public static final int UserNickIconsComposer = 7004;
public static final int AvailableCommandsComposer = 4050;
// 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.getIcon());
this.response.appendString(this.prefix.getEffect());
this.response.appendString(this.prefix.getFont());
} else {
this.response.appendInt(0);
this.response.appendString("");
this.response.appendString("");
this.response.appendString("");
this.response.appendString("");
this.response.appendString("");
}
return this.response;
@@ -20,6 +20,7 @@ public class PrefixReceivedComposer extends MessageComposer {
this.response.appendString(this.prefix.getColor());
this.response.appendString(this.prefix.getIcon());
this.response.appendString(this.prefix.getEffect());
this.response.appendString(this.prefix.getFont());
return this.response;
}
}
@@ -30,6 +30,7 @@ public class UserPrefixesComposer extends MessageComposer {
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);
}
@@ -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);
if (pet instanceof IPetLook) {
this.response.appendString(((IPetLook) pet).getLook());
} else {

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