Merge branch 'duckietm:main' into Dev

This commit is contained in:
Medievalshell
2026-04-18 12:47:56 +02:00
committed by GitHub
557 changed files with 54865 additions and 7147 deletions
+1
View File
@@ -14,3 +14,4 @@ config.ini
.DS_Store
/Emulator/plugins
/Emulator/.idea
-38
View File
@@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="ccf1bd3c-cd48-45b2-9a9d-461474e2ab14" name="Changes" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectId" id="2djKOc5iV19iK1uWSJto4t3tYDy" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true"
}
}]]></component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="ccf1bd3c-cd48-45b2-9a9d-461474e2ab14" name="Changes" comment="" />
<created>1710516016645</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1710516016645</updated>
</task>
<servers />
</component>
</project>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,519 @@
-- =============================================================================
-- 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;
SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';
-- =============================================================================
-- From: UpdateDatabase_Allow_diagonale.sql
-- =============================================================================
INSERT IGNORE INTO `emulator_settings` (`key`, `value`)
VALUES ('pathfinder.diagonal.enabled', '1');
-- =============================================================================
-- From: UpdateDatabase_BOT.sql
-- =============================================================================
INSERT IGNORE INTO `emulator_settings` (`key`, `value`)
VALUES ('hotel.bot.limit.walking.distance', '1');
INSERT IGNORE INTO `emulator_settings` (`key`, `value`)
VALUES ('hotel.bot.limit.walking.distance.radius', '5');
-- =============================================================================
-- From: UpdateDatabase_Banners.sql
-- =============================================================================
ALTER TABLE `users`
ADD COLUMN IF NOT EXISTS `background_id` INT(11) NOT NULL DEFAULT 0 AFTER `machine_id`,
ADD COLUMN IF NOT EXISTS `background_stand_id` INT(11) NOT NULL DEFAULT 0 AFTER `background_id`,
ADD COLUMN IF NOT EXISTS `background_overlay_id` INT(11) NOT NULL DEFAULT 0 AFTER `background_stand_id`;
-- =============================================================================
-- From: UpdateDatabase_DanceCMD.sql
-- =============================================================================
ALTER TABLE `permissions`
ADD COLUMN IF NOT EXISTS `cms_dance` ENUM('0','1') NULL DEFAULT '0' AFTER `cmd_credits`;
INSERT IGNORE INTO `emulator_texts` (`key`, `value`)
VALUES ('commands.description.cmd_dance', 'dance around the world ! use 1 t/m 4 and 0 to stop');
INSERT IGNORE INTO `emulator_texts` (`key`, `value`)
VALUES ('commands.keys.cmd_dance', 'dance');
-- =============================================================================
-- From: UpdateDatabase_Happiness.sql
-- =============================================================================
-- Rename key if the old one exists
UPDATE `emulator_texts`
SET `key` = 'generic.pet.happiness', `value` = 'Happiness'
WHERE `key` = 'generic.pet.happyness';
-- Rename columns (IF EXISTS prevents error if already renamed)
ALTER TABLE `pet_commands_data`
CHANGE COLUMN IF EXISTS `cost_happyness` `cost_happiness` int(11) NOT NULL DEFAULT '0';
ALTER TABLE `users_pets`
CHANGE COLUMN IF EXISTS `happyness` `happiness` int(11) NOT NULL DEFAULT '100';
-- =============================================================================
-- From: UpdateDatabase_Websocket.sql
-- =============================================================================
INSERT IGNORE INTO `emulator_settings` (`key`, `value`)
VALUES ('websockets.whitelist', 'localhost');
INSERT IGNORE INTO `emulator_settings` (`key`, `value`)
VALUES ('ws.nitro.host', '0.0.0.0');
INSERT IGNORE INTO `emulator_settings` (`key`, `value`)
VALUES ('ws.nitro.ip.header', '');
INSERT IGNORE INTO `emulator_settings` (`key`, `value`)
VALUES ('ws.nitro.port', '2096');
-- =============================================================================
-- From: UpdateDatabase_unignorable.sql
-- =============================================================================
ALTER TABLE `permissions`
ADD COLUMN IF NOT EXISTS `acc_unignorable` ENUM('0','1') NOT NULL DEFAULT '0';
-- =============================================================================
-- From: Default_Camera.sql
-- =============================================================================
CREATE TABLE IF NOT EXISTS `camera_web` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`room_id` INT(11) NOT NULL DEFAULT 0,
`timestamp` INT(11) NOT NULL DEFAULT 0,
`url` VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
INDEX `idx_camera_web_user_id` (`user_id`),
INDEX `idx_camera_web_timestamp` (`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Camera emulator settings
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
('camera.url', 'http://localhost/camera/');
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
('imager.location.output.camera', '/path/to/www/camera/');
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
('imager.location.output.thumbnail', '/path/to/www/thumbnails/');
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
('camera.item_id', '0');
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
('camera.price.credits', '2');
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
('camera.price.points', '0');
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
('camera.price.points.publish', '1');
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
('camera.extradata', '{"t":"%timestamp%","u":"%id%","m":"","s":"%room_id%","w":"%url%"}');
-- Camera emulator texts
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
('camera.permission', 'You do not have permission to use the camera.');
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
('camera.wait', 'Please wait %seconds% more seconds before taking another photo.');
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
('camera.error.creation', 'An error occurred while processing your photo. Please try again.');
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
('camera.daily.limit', 'You have reached the daily photo limit. Try again tomorrow.');
-- =============================================================================
-- From: 07012026_UpdateDatabase_to_4-0-1.sql
-- =============================================================================
-- Wired abuse protection settings
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
('wired.abuse.max.recursion.depth', '10')
ON DUPLICATE KEY UPDATE `key` = `key`;
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
('wired.abuse.max.events.per.window', '100')
ON DUPLICATE KEY UPDATE `key` = `key`;
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
('wired.abuse.rate.limit.window.ms', '10000')
ON DUPLICATE KEY UPDATE `key` = `key`;
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
('wired.abuse.ban.duration.ms', '600000')
ON DUPLICATE KEY UPDATE `key` = `key`;
-- Wired abuse texts
INSERT INTO `emulator_texts` (`key`, `value`) VALUES
('wired.abuse.room.alert', 'Wired execution has been temporarily disabled in this room due to abuse detection. It will resume in %minutes% minutes.')
ON DUPLICATE KEY UPDATE `key` = `key`;
INSERT INTO `emulator_texts` (`key`, `value`) VALUES
('wired.abuse.staff.title', 'Wired Abuse Detected')
ON DUPLICATE KEY UPDATE `key` = `key`;
INSERT INTO `emulator_texts` (`key`, `value`) VALUES
('wired.abuse.staff.message', 'Room: %roomname%\nOwner: %owner%\nBanned for %minutes% minutes.')
ON DUPLICATE KEY UPDATE `key` = `key`;
INSERT INTO `emulator_texts` (`key`, `value`) VALUES
('wired.abuse.staff.link', 'Go to Room')
ON DUPLICATE KEY UPDATE `key` = `key`;
-- Wired tick resolution
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
('wired.tick.resolution', '100')
ON DUPLICATE KEY UPDATE `key` = `key`;
-- Wired engine configuration
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
('wired.engine.enabled', '1'),
('wired.engine.exclusive', '1'),
('wired.engine.maxStepsPerStack', '100'),
('wired.engine.debug', '0')
ON DUPLICATE KEY UPDATE `key` = `key`;
-- Wired tick system configuration
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
('wired.tick.interval.ms', '50'),
('wired.tick.debug', '0'),
('wired.tick.thread.priority', '6')
ON DUPLICATE KEY UPDATE `key` = `key`;
-- Pathfinder settings
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
('pathfinder.click.delay', '0')
ON DUPLICATE KEY UPDATE `key` = `key`;
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
('pathfinder.retro-style.diagonals', '0')
ON DUPLICATE KEY UPDATE `key` = `key`;
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
('pathfinder.step.allow.falling', '1')
ON DUPLICATE KEY UPDATE `key` = `key`;
-- =============================================================================
-- From: 09012026_UpdateDatabase_to_4-0-2.sql + 12012026_Battle Banzai.sql
-- (These two files are identical - deduplicated here)
-- =============================================================================
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
('hotel.banzai.fill.max_queue', '50'),
('hotel.banzai.fill.cooldown_ms', '100')
ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);
-- =============================================================================
-- From: 12012026_Breeding Fixes.sql
-- =============================================================================
-- Recreate pet_breeding with correct structure
CREATE TABLE IF NOT EXISTS `pet_breeding` (
`pet_id` int(11) NOT NULL COMMENT 'Parent pet type',
`offspring_id` int(11) NOT NULL COMMENT 'Baby pet type',
PRIMARY KEY (`pet_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
TRUNCATE TABLE `pet_breeding`;
INSERT INTO `pet_breeding` (`pet_id`, `offspring_id`) VALUES
(0, 29), -- Dog -> Baby Dog
(1, 28), -- Cat -> Baby Cat
(3, 25), -- Terrier -> Baby Terrier
(4, 24), -- Bear -> Baby Bear
(5, 30); -- Pig -> Baby Pig
-- Recreate pet_breeding_races with correct structure
CREATE TABLE IF NOT EXISTS `pet_breeding_races` (
`pet_type` int(11) NOT NULL COMMENT 'Baby pet type (offspring)',
`rarity_level` int(11) NOT NULL COMMENT '1=Common, 2=Uncommon, 3=Rare, 4=Epic',
`breed` int(11) NOT NULL COMMENT 'Visual breed/color variant',
PRIMARY KEY (`pet_type`, `rarity_level`, `breed`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
TRUNCATE TABLE `pet_breeding_races`;
-- Baby Dog (29) - Offspring of Dog (0)
INSERT INTO `pet_breeding_races` (`pet_type`, `rarity_level`, `breed`) VALUES
(29, 1, 0), (29, 1, 1), (29, 1, 2), (29, 1, 3),
(29, 1, 4), (29, 1, 5), (29, 1, 6), (29, 1, 7),
(29, 2, 8), (29, 2, 9), (29, 2, 10), (29, 2, 11), (29, 2, 12),
(29, 3, 13), (29, 3, 14), (29, 3, 15), (29, 3, 16),
(29, 4, 17), (29, 4, 18), (29, 4, 19);
-- Baby Cat (28) - Offspring of Cat (1)
INSERT INTO `pet_breeding_races` (`pet_type`, `rarity_level`, `breed`) VALUES
(28, 1, 0), (28, 1, 1), (28, 1, 2), (28, 1, 3),
(28, 1, 4), (28, 1, 5), (28, 1, 6), (28, 1, 7),
(28, 2, 8), (28, 2, 9), (28, 2, 10), (28, 2, 11), (28, 2, 12),
(28, 3, 13), (28, 3, 14), (28, 3, 15), (28, 3, 16),
(28, 4, 17), (28, 4, 18), (28, 4, 19);
-- Baby Terrier (25) - Offspring of Terrier (3)
INSERT INTO `pet_breeding_races` (`pet_type`, `rarity_level`, `breed`) VALUES
(25, 1, 0), (25, 1, 1), (25, 1, 2), (25, 1, 3),
(25, 1, 4), (25, 1, 5), (25, 1, 6), (25, 1, 7),
(25, 2, 8), (25, 2, 9), (25, 2, 10), (25, 2, 11), (25, 2, 12),
(25, 3, 13), (25, 3, 14), (25, 3, 15), (25, 3, 16),
(25, 4, 17), (25, 4, 18), (25, 4, 19);
-- Baby Bear (24) - Offspring of Bear (4)
INSERT INTO `pet_breeding_races` (`pet_type`, `rarity_level`, `breed`) VALUES
(24, 1, 0), (24, 1, 1), (24, 1, 2), (24, 1, 3),
(24, 1, 4), (24, 1, 5), (24, 1, 6), (24, 1, 7),
(24, 2, 8), (24, 2, 9), (24, 2, 10), (24, 2, 11), (24, 2, 12),
(24, 3, 13), (24, 3, 14), (24, 3, 15), (24, 3, 16),
(24, 4, 17), (24, 4, 18), (24, 4, 19);
-- Baby Pig (30) - Offspring of Pig (5)
INSERT INTO `pet_breeding_races` (`pet_type`, `rarity_level`, `breed`) VALUES
(30, 1, 0), (30, 1, 1), (30, 1, 2), (30, 1, 3),
(30, 1, 4), (30, 1, 5), (30, 1, 6), (30, 1, 7),
(30, 2, 8), (30, 2, 9), (30, 2, 10), (30, 2, 11), (30, 2, 12),
(30, 3, 13), (30, 3, 14), (30, 3, 15), (30, 3, 16),
(30, 4, 17), (30, 4, 18), (30, 4, 19);
-- Fix pet_actions offspring_type values
UPDATE `pet_actions` SET `offspring_type` = 29 WHERE `pet_type` = 0;
UPDATE `pet_actions` SET `offspring_type` = 28 WHERE `pet_type` = 1;
UPDATE `pet_actions` SET `offspring_type` = 25 WHERE `pet_type` = 3;
UPDATE `pet_actions` SET `offspring_type` = 24 WHERE `pet_type` = 4;
UPDATE `pet_actions` SET `offspring_type` = 30 WHERE `pet_type` = 5;
UPDATE `pet_actions` SET `offspring_type` = -1 WHERE `pet_type` NOT IN (0, 1, 3, 4, 5);
-- Fix items_base whitespace in interaction_type
UPDATE `items_base` SET `interaction_type` = TRIM(`interaction_type`);
-- Ensure breeding nest items have correct interaction_type
UPDATE `items_base` SET `interaction_type` = 'breeding_nest'
WHERE `item_name` LIKE 'pet_breeding_%' AND `interaction_type` != 'breeding_nest';
-- =============================================================================
-- From: 12012026_ChatBubbles.sql
-- =============================================================================
ALTER TABLE `permissions`
ADD COLUMN IF NOT EXISTS `cmd_update_chat_bubbles` ENUM('0','1') NOT NULL DEFAULT '0';
CREATE TABLE IF NOT EXISTS `chat_bubbles` (
`type` INT(11) NOT NULL AUTO_INCREMENT COMMENT 'Only 46 and higher will work',
`name` VARCHAR(255) NOT NULL DEFAULT '',
`permission` VARCHAR(255) NOT NULL DEFAULT '',
`overridable` TINYINT(1) NOT NULL DEFAULT 1,
`triggers_talking_furniture` TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
('commands.keys.cmd_update_chat_bubbles', 'update_chat_bubbles');
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
('commands.success.cmd_update_chat_bubbles', 'Successfully updated chat bubbles');
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
('commands.description.cmd_update_chat_bubbles', ':update_chat_bubbles');
-- =============================================================================
-- From: 16032026_updateall_command.sql
-- =============================================================================
ALTER TABLE `permissions`
ADD COLUMN IF NOT EXISTS `cmd_update_all` ENUM('0','1') NOT NULL DEFAULT '0' AFTER `cmd_update_achievements`;
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
('commands.keys.cmd_update_all', 'update_all');
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
('commands.description.cmd_update_all', ':update_all');
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
('commands.succes.cmd_update_all', 'Successfully updated everything!');
-- =============================================================================
-- From: 17032026_allow_underpass.sql
-- =============================================================================
ALTER TABLE `rooms`
ADD COLUMN IF NOT EXISTS `allow_underpass` ENUM('0','1') NOT NULL DEFAULT '0' AFTER `move_diagonally`;
-- =============================================================================
-- From: 19032026_hotel_timezone.sql
-- =============================================================================
INSERT IGNORE INTO `emulator_settings` (`key`, `value`)
VALUES ('hotel.timezone', 'Europe/Rome');
-- =============================================================================
-- From: 21022026_user_prefixes.sql
-- =============================================================================
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 COLLATE=utf8mb4_unicode_ci;
-- =============================================================================
-- From: 06042026_builders_club_catalog_offers.sql
-- =============================================================================
ALTER TABLE `catalog_club_offers`
MODIFY COLUMN `type` ENUM('HC','VIP','BUILDERS_CLUB','BUILDERS_CLUB_ADDON') NOT NULL DEFAULT 'HC';
ALTER TABLE `catalog_pages`
MODIFY COLUMN `page_layout` ENUM(
'default_3x3',
'club_buy',
'club_gift',
'frontpage',
'spaces',
'recycler',
'recycler_info',
'recycler_prizes',
'trophies',
'plasto',
'marketplace',
'marketplace_own_items',
'spaces_new',
'soundmachine',
'guilds',
'guild_furni',
'info_duckets',
'info_rentables',
'info_pets',
'roomads',
'single_bundle',
'sold_ltd_items',
'badge_display',
'bots',
'pets',
'pets2',
'pets3',
'productpage1',
'room_bundle',
'recent_purchases',
'default_3x3_color_grouping',
'guild_forum',
'vip_buy',
'info_loyalty',
'loyalty_vip_buy',
'collectibles',
'petcustomization',
'frontpage_featured',
'builders_club_frontpage',
'builders_club_addons',
'builders_club_loyalty'
) NOT NULL DEFAULT 'default_3x3';
ALTER TABLE `catalog_pages`
ADD COLUMN `catalog_mode` ENUM('NORMAL','BUILDER','BOTH') NOT NULL DEFAULT 'NORMAL' AFTER `club_only`;
ALTER TABLE `catalog_pages_bc`
MODIFY COLUMN `page_layout` ENUM(
'default_3x3',
'club_buy',
'club_gift',
'frontpage',
'spaces',
'recycler',
'recycler_info',
'recycler_prizes',
'trophies',
'plasto',
'marketplace',
'marketplace_own_items',
'spaces_new',
'soundmachine',
'guilds',
'guild_furni',
'info_duckets',
'info_rentables',
'info_pets',
'roomads',
'single_bundle',
'sold_ltd_items',
'badge_display',
'bots',
'pets',
'pets2',
'pets3',
'productpage1',
'room_bundle',
'recent_purchases',
'default_3x3_color_grouping',
'guild_forum',
'vip_buy',
'info_loyalty',
'loyalty_vip_buy',
'collectibles',
'petcustomization',
'frontpage_featured',
'builders_club_frontpage',
'builders_club_addons',
'builders_club_loyalty'
) NOT NULL DEFAULT 'default_3x3';
ALTER TABLE `users_settings`
ADD COLUMN IF NOT EXISTS `builders_club_bonus_furni` INT(11) NOT NULL DEFAULT 0 AFTER `hc_gifts_claimed`;
-- =============================================================================
-- Done
-- =============================================================================
SET FOREIGN_KEY_CHECKS = 1;
SET SQL_MODE = @OLD_SQL_MODE;
@@ -0,0 +1,97 @@
SET FOREIGN_KEY_CHECKS = 0;
SET @OLD_SQL_MODE = @@SQL_MODE;
SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';
ALTER TABLE IF EXISTS `achievements` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `items` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `rooms` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `users_achievements` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `catalog_items` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `catalog_pages` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `navigator_flatcats` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `wordfilter` ENGINE = InnoDB, ROW_FORMAT = DYNAMIC;
ALTER TABLE IF EXISTS `logs_shop_purchases` MODIFY `user_id` int(11) DEFAULT NULL;
ALTER TABLE IF EXISTS `users_subscriptions` MODIFY `user_id` int(11) DEFAULT NULL;
ALTER TABLE IF EXISTS `items` MODIFY `item_id` int(11) unsigned DEFAULT 0;
DELIMITER //
DROP PROCEDURE IF EXISTS `_add_id_pk_if_missing`//
CREATE PROCEDURE `_add_id_pk_if_missing`(IN tbl VARCHAR(64))
BEGIN
DECLARE col_exists INT DEFAULT 0;
SELECT COUNT(*) INTO col_exists
FROM `information_schema`.`COLUMNS`
WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = tbl AND `COLUMN_NAME` = 'id';
IF col_exists = 0 THEN
SET @sql = CONCAT('ALTER TABLE `', tbl, '` ADD COLUMN `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END//
DELIMITER ;
CALL `_add_id_pk_if_missing`('bot_serves');
CALL `_add_id_pk_if_missing`('chatlogs_room');
CALL `_add_id_pk_if_missing`('commandlogs');
CALL `_add_id_pk_if_missing`('room_enter_log');
DELIMITER //
DROP PROCEDURE IF EXISTS `_add_index_if_missing`//
CREATE PROCEDURE `_add_index_if_missing`(IN tbl VARCHAR(64), IN idx VARCHAR(64), IN cols VARCHAR(255))
BEGIN
DECLARE tbl_exists INT DEFAULT 0;
DECLARE idx_exists INT DEFAULT 0;
SELECT COUNT(*) INTO tbl_exists FROM `information_schema`.`TABLES` WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = tbl;
IF tbl_exists > 0 THEN
SELECT COUNT(*) INTO idx_exists FROM `information_schema`.`STATISTICS` WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = tbl AND `INDEX_NAME` = idx;
IF idx_exists = 0 THEN
SET @sql = CONCAT('ALTER TABLE `', tbl, '` ADD INDEX `', idx, '` (', cols, ')');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
END IF;
END IF;
END//
DELIMITER ;
CALL `_add_index_if_missing`('bans', 'idx_bans_user_id', '`user_id`');
CALL `_add_index_if_missing`('guilds', 'idx_guilds_user_id', '`user_id`');
CALL `_add_index_if_missing`('room_bans', 'idx_room_bans_room_id', '`room_id`');
DELIMITER //
DROP PROCEDURE IF EXISTS `_add_fk_if_missing`//
CREATE PROCEDURE `_add_fk_if_missing`(IN tbl VARCHAR(64), IN fk_name VARCHAR(64), IN col VARCHAR(64), IN ref_tbl VARCHAR(64), IN ref_col VARCHAR(64), IN on_del VARCHAR(20))
BEGIN
DECLARE tbl_exists INT DEFAULT 0;
DECLARE ref_exists INT DEFAULT 0;
DECLARE fk_exists INT DEFAULT 0;
SELECT COUNT(*) INTO tbl_exists FROM `information_schema`.`TABLES` WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = tbl;
SELECT COUNT(*) INTO ref_exists FROM `information_schema`.`TABLES` WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = ref_tbl;
IF tbl_exists > 0 AND ref_exists > 0 THEN
SELECT COUNT(*) INTO fk_exists FROM `information_schema`.`TABLE_CONSTRAINTS` WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = tbl AND `CONSTRAINT_NAME` = fk_name;
IF fk_exists = 0 THEN
SET @sql = CONCAT('ALTER TABLE `', tbl, '` ADD CONSTRAINT `', fk_name, '` FOREIGN KEY (`', col, '`) REFERENCES `', ref_tbl, '` (`', ref_col, '`) ON DELETE ', on_del);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
END IF;
END IF;
END//
DELIMITER ;
CALL `_add_fk_if_missing`('rooms', 'fk_rooms_owner', 'owner_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('items', 'fk_items_user', 'user_id', 'users', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('catalog_items', 'fk_catitems_page', 'page_id', 'catalog_pages', 'id', 'CASCADE');
CALL `_add_fk_if_missing`('guilds', 'fk_guilds_user', 'user_id', 'users', 'id', 'CASCADE');
DROP PROCEDURE IF EXISTS `_add_id_pk_if_missing`;
DROP PROCEDURE IF EXISTS `_add_index_if_missing`;
DROP PROCEDURE IF EXISTS `_add_fk_if_missing`;
# Make sure thenb System account exists
INSERT INTO `users` (`id`, `username`, `password`, `ip_register`, `ip_current`, `motto`, `look`, `rank`, `credits`)
SELECT 0, '[SYSTEM]', '!', '127.0.0.1', '127.0.0.1', 'System sentinel - do not delete', '', 1, 0
WHERE NOT EXISTS (SELECT 1 FROM `users` WHERE `id` = 0);
SET FOREIGN_KEY_CHECKS = 1;
SET SQL_MODE = @OLD_SQL_MODE;
+1
View File
@@ -0,0 +1 @@
ALTER TABLE `guild_forum_views` ADD UNIQUE KEY `user_guild` (`user_id`, `guild_id`);
@@ -0,0 +1,98 @@
CREATE TABLE IF NOT EXISTS `wired_emulator_settings` (
`key` varchar(191) NOT NULL,
`value` text NOT NULL,
`comment` text NOT NULL,
PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
INSERT INTO `wired_emulator_settings` (`key`, `value`, `comment`)
SELECT 'wired.engine.enabled', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.engine.enabled' LIMIT 1), '1'), 'Compatibility flag kept for older configs. The runtime now always uses the new wired engine.'
UNION ALL
SELECT 'wired.engine.exclusive', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.engine.exclusive' LIMIT 1), '1'), 'Compatibility flag kept for older configs. The runtime now always uses the new wired engine.'
UNION ALL
SELECT 'wired.engine.maxStepsPerStack', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.engine.maxStepsPerStack' LIMIT 1), '100'), 'Maximum amount of internal processing steps allowed for a single wired stack execution.'
UNION ALL
SELECT 'wired.engine.debug', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.engine.debug' LIMIT 1), '0'), 'Enable verbose debug logging for the new wired engine.'
UNION ALL
SELECT 'wired.custom.enabled', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.custom.enabled' LIMIT 1), '0'), 'Enable custom legacy wired behaviour such as user-based cooldown exceptions and compatibility logic.'
UNION ALL
SELECT 'hotel.wired.furni.selection.count', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'hotel.wired.furni.selection.count' LIMIT 1), '5'), 'Maximum number of furni that a wired box can store or select.'
UNION ALL
SELECT 'hotel.wired.max_delay', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'hotel.wired.max_delay' LIMIT 1), '20'), 'Maximum delay value accepted by wired effects that support delayed execution.'
UNION ALL
SELECT 'hotel.wired.message.max_length', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'hotel.wired.message.max_length' LIMIT 1), '100'), 'Maximum length of text fields used by wired messages and bot text effects.'
UNION ALL
SELECT 'wired.effect.teleport.delay', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.effect.teleport.delay' LIMIT 1), '500'), 'Delay in milliseconds used by wired teleport movement.'
UNION ALL
SELECT 'wired.place.under', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.place.under' LIMIT 1), '0'), 'Allow placing wired furniture underneath other items when room rules permit it.'
UNION ALL
SELECT 'wired.tick.interval.ms', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.tick.interval.ms' LIMIT 1), '50'), 'Global wired tick interval in milliseconds used by repeaters and other tick-driven wired items.'
UNION ALL
SELECT 'wired.tick.resolution', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.tick.resolution' LIMIT 1), '100'), 'Legacy wired tick resolution value kept for compatibility with older wired timing setups.'
UNION ALL
SELECT 'wired.tick.debug', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.tick.debug' LIMIT 1), '0'), 'Enable verbose logging for the wired tick service.'
UNION ALL
SELECT 'wired.tick.thread.priority', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.tick.thread.priority' LIMIT 1), '6'), 'Java thread priority used by the wired tick service.'
UNION ALL
SELECT 'wired.highscores.displaycount', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.highscores.displaycount' LIMIT 1), '25'), 'Maximum number of wired highscore entries shown to users when a highscore is displayed.'
UNION ALL
SELECT 'wired.abuse.max.recursion.depth', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.abuse.max.recursion.depth' LIMIT 1), '10'), 'Maximum recursive wired depth allowed before execution is stopped.'
UNION ALL
SELECT 'wired.abuse.max.events.per.window', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.abuse.max.events.per.window' LIMIT 1), '100'), 'Maximum amount of identical wired events allowed inside the abuse rate-limit window before a room ban is applied.'
UNION ALL
SELECT 'wired.abuse.rate.limit.window.ms', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.abuse.rate.limit.window.ms' LIMIT 1), '10000'), 'Time window in milliseconds used by the wired abuse rate limiter.'
UNION ALL
SELECT 'wired.abuse.ban.duration.ms', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.abuse.ban.duration.ms' LIMIT 1), '600000'), 'Duration in milliseconds of the temporary wired ban after abuse detection.'
UNION ALL
SELECT 'wired.monitor.usage.window.ms', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.usage.window.ms' LIMIT 1), '1000'), 'Rolling window size in milliseconds used to calculate wired usage in the :wired monitor.'
UNION ALL
SELECT 'wired.monitor.usage.limit', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.usage.limit' LIMIT 1), '1000'), 'Maximum wired usage budget allowed in one monitor window before EXECUTION_CAP is raised.'
UNION ALL
SELECT 'wired.monitor.delayed.events.limit', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.delayed.events.limit' LIMIT 1), '100'), 'Maximum number of delayed wired events that can be queued in one room at the same time.'
UNION ALL
SELECT 'wired.monitor.overload.average.ms', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.overload.average.ms' LIMIT 1), '50'), 'Average execution time threshold in milliseconds that starts overload tracking.'
UNION ALL
SELECT 'wired.monitor.overload.peak.ms', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.overload.peak.ms' LIMIT 1), '150'), 'Peak single execution time threshold in milliseconds that starts overload tracking.'
UNION ALL
SELECT 'wired.monitor.overload.consecutive.windows', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.overload.consecutive.windows' LIMIT 1), '2'), 'Number of consecutive overloaded monitor windows required before logging EXECUTOR_OVERLOAD.'
UNION ALL
SELECT 'wired.monitor.heavy.usage.percent', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.heavy.usage.percent' LIMIT 1), '70'), 'Usage percentage threshold that contributes to marking a room as heavy in the :wired monitor.'
UNION ALL
SELECT 'wired.monitor.heavy.consecutive.windows', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.heavy.consecutive.windows' LIMIT 1), '5'), 'Number of consecutive windows above the heavy usage threshold required before the room is marked as heavy.'
UNION ALL
SELECT 'wired.monitor.heavy.delayed.percent', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.heavy.delayed.percent' LIMIT 1), '60'), 'Delayed queue percentage threshold that also contributes to the heavy-room calculation.'
ON DUPLICATE KEY UPDATE
`value` = VALUES(`value`),
`comment` = VALUES(`comment`);
DELETE FROM `emulator_settings`
WHERE `key` IN (
'wired.engine.enabled',
'wired.engine.exclusive',
'wired.engine.maxStepsPerStack',
'wired.engine.debug',
'wired.custom.enabled',
'hotel.wired.furni.selection.count',
'hotel.wired.max_delay',
'hotel.wired.message.max_length',
'wired.effect.teleport.delay',
'wired.place.under',
'wired.tick.interval.ms',
'wired.tick.resolution',
'wired.tick.debug',
'wired.tick.thread.priority',
'wired.highscores.displaycount',
'wired.abuse.max.recursion.depth',
'wired.abuse.max.events.per.window',
'wired.abuse.rate.limit.window.ms',
'wired.abuse.ban.duration.ms',
'wired.monitor.usage.window.ms',
'wired.monitor.usage.limit',
'wired.monitor.delayed.events.limit',
'wired.monitor.overload.average.ms',
'wired.monitor.overload.peak.ms',
'wired.monitor.overload.consecutive.windows',
'wired.monitor.heavy.usage.percent',
'wired.monitor.heavy.consecutive.windows',
'wired.monitor.heavy.delayed.percent'
);
@@ -0,0 +1,332 @@
ALTER TABLE `emulator_settings`
ADD COLUMN IF NOT EXISTS `comment` text NOT NULL AFTER `value`;
UPDATE `emulator_settings` SET `comment` = 'Characters allowed when users choose or change a username.' WHERE `key` = 'allowed.username.characters';
UPDATE `emulator_settings` SET `comment` = 'Cooldown in milliseconds used by the Apollyon-specific behaviour or command flow.' WHERE `key` = 'apollyon.cooldown.amount';
UPDATE `emulator_settings` SET `comment` = 'Asset URL used by the BaseJump or FastFood game client.' WHERE `key` = 'basejump.assets.url';
UPDATE `emulator_settings` SET `comment` = 'SWF URL used to launch the BaseJump or FastFood game client.' WHERE `key` = 'basejump.url';
UPDATE `emulator_settings` SET `comment` = 'Date format used by visitor bots when they print timestamps.' WHERE `key` = 'bots.visitor.dateformat';
UPDATE `emulator_settings` SET `comment` = 'Master switch for bubble alert notifications.' WHERE `key` = 'bubblealerts.enabled';
UPDATE `emulator_settings` SET `comment` = 'Enable bubble alerts when friends come online.' WHERE `key` = 'bubblealerts.notif_friendonline.enabled';
UPDATE `emulator_settings` SET `comment` = 'Image template used when showing friend-online bubble alerts.' WHERE `key` = 'bubblealerts.notif_friendonline.image';
UPDATE `emulator_settings` SET `comment` = 'Use the configured figure image inside friend-online bubble alerts.' WHERE `key` = 'bubblealerts.notif_friendonline.useimage';
UPDATE `emulator_settings` SET `comment` = 'Show bubble alerts for marketplace notifications.' WHERE `key` = 'bubblealerts.notif_marketplace.enabled';
UPDATE `emulator_settings` SET `comment` = 'Show bubble alerts for limited-item purchases.' WHERE `key` = 'bubblealerts.notif_purchase.limited';
UPDATE `emulator_settings` SET `comment` = 'Allow bots to be included in room bundles or package rewards.' WHERE `key` = 'bundle.bots.enabled';
UPDATE `emulator_settings` SET `comment` = 'Allow pets to be included in room bundles or package rewards.' WHERE `key` = 'bundle.pets.enabled';
UPDATE `emulator_settings` SET `comment` = 'Enable the GET callback used to report version to external services.' WHERE `key` = 'callback.get.version';
UPDATE `emulator_settings` SET `comment` = 'Enable the POST callback used to report errors to external services.' WHERE `key` = 'callback.post.errors';
UPDATE `emulator_settings` SET `comment` = 'Enable the POST callback used to report statistics to external services.' WHERE `key` = 'callback.post.statistics';
UPDATE `emulator_settings` SET `comment` = 'Enable the in-room camera feature.' WHERE `key` = 'camera.enabled';
UPDATE `emulator_settings` SET `comment` = 'Extradata template written into camera photo items when they are created.' WHERE `key` = 'camera.extradata';
UPDATE `emulator_settings` SET `comment` = 'Base item ID used by the generated camera photo furniture.' WHERE `key` = 'camera.item_id';
UPDATE `emulator_settings` SET `comment` = 'Credit price charged when taking a camera photo.' WHERE `key` = 'camera.price.credits';
UPDATE `emulator_settings` SET `comment` = 'Amount of activity points charged when taking a camera photo.' WHERE `key` = 'camera.price.points';
UPDATE `emulator_settings` SET `comment` = 'Amount of activity points charged when publishing a camera photo.' WHERE `key` = 'camera.price.points.publish';
UPDATE `emulator_settings` SET `comment` = 'Activity point type used for the camera publish cost.' WHERE `key` = 'camera.price.points.publish.type';
UPDATE `emulator_settings` SET `comment` = 'Activity point type used for the camera capture cost.' WHERE `key` = 'camera.price.points.type';
UPDATE `emulator_settings` SET `comment` = 'Delay in seconds before a published camera photo becomes available.' WHERE `key` = 'camera.publish.delay';
UPDATE `emulator_settings` SET `comment` = 'Base URL where camera images are published.' WHERE `key` = 'camera.url';
UPDATE `emulator_settings` SET `comment` = 'Force HTTPS when generating camera image URLs.' WHERE `key` = 'camera.use.https';
UPDATE `emulator_settings` SET `comment` = 'Require HC or VIP status before users can create a guild.' WHERE `key` = 'catalog.guild.hc_required';
UPDATE `emulator_settings` SET `comment` = 'Credit cost required to create a guild.' WHERE `key` = 'catalog.guild.price';
UPDATE `emulator_settings` SET `comment` = 'Layout or image ID used when a limited page is sold out.' WHERE `key` = 'catalog.ltd.page.soldout';
UPDATE `emulator_settings` SET `comment` = 'Randomize the order or selection of limited catalog items.' WHERE `key` = 'catalog.ltd.random';
UPDATE `emulator_settings` SET `comment` = 'Catalog page ID used for VIP gift redemption.' WHERE `key` = 'catalog.page.vipgifts';
UPDATE `emulator_settings` SET `comment` = 'Semicolon-separated list of chat color IDs blocked for the chatcolor command.' WHERE `key` = 'commands.cmd_chatcolor.banned_numbers';
UPDATE `emulator_settings` SET `comment` = 'Minimum permission rank required to use the staffonline command.' WHERE `key` = 'commands.cmd_staffonline.min_rank';
UPDATE `emulator_settings` SET `comment` = 'Use the legacy command plugin loading style.' WHERE `key` = 'commands.plugins.oldstyle';
UPDATE `emulator_settings` SET `comment` = 'Controls the emulator console mode or console output style.' WHERE `key` = 'console.mode';
UPDATE `emulator_settings` SET `comment` = 'Enable custom item stacking behaviour outside the default stacking rules.' WHERE `key` = 'custom.stacking.enabled';
UPDATE `emulator_settings` SET `comment` = 'Maximum batch or partition size used by partitioned database operations.' WHERE `key` = 'db.max.partition.size';
UPDATE `emulator_settings` SET `comment` = 'Minimum batch or partition size used by partitioned database operations.' WHERE `key` = 'db.min.partition.size';
UPDATE `emulator_settings` SET `comment` = 'Maximum size of the database connection pool.' WHERE `key` = 'db.pool.maxsize';
UPDATE `emulator_settings` SET `comment` = 'Minimum number of open connections kept in the database pool.' WHERE `key` = 'db.pool.minsize';
UPDATE `emulator_settings` SET `comment` = 'Enable general emulator debug mode.' WHERE `key` = 'debug.mode';
UPDATE `emulator_settings` SET `comment` = 'Show internal debug error messages.' WHERE `key` = 'debug.show.errors';
UPDATE `emulator_settings` SET `comment` = 'Show packet headers in debug logs.' WHERE `key` = 'debug.show.headers';
UPDATE `emulator_settings` SET `comment` = 'Print packet-level debug output.' WHERE `key` = 'debug.show.packets';
UPDATE `emulator_settings` SET `comment` = 'Print debug output for undefined incoming or outgoing packets.' WHERE `key` = 'debug.show.packets.undefined';
UPDATE `emulator_settings` SET `comment` = 'Log SQL exceptions to the console.' WHERE `key` = 'debug.show.sql.exception';
UPDATE `emulator_settings` SET `comment` = 'Show user-related debug messages.' WHERE `key` = 'debug.show.users';
UPDATE `emulator_settings` SET `comment` = 'Semicolon-separated discount thresholds used for extra batch bonuses.' WHERE `key` = 'discount.additional.thresholds';
UPDATE `emulator_settings` SET `comment` = 'Number of free items granted inside one discount batch.' WHERE `key` = 'discount.batch.free.items';
UPDATE `emulator_settings` SET `comment` = 'Number of items required for one discount batch.' WHERE `key` = 'discount.batch.size';
UPDATE `emulator_settings` SET `comment` = 'Minimum number of discount batches required before the bonus logic applies.' WHERE `key` = 'discount.bonus.min.discounts';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of catalog items that can participate in one discount batch.' WHERE `key` = 'discount.max.allowed.items';
UPDATE `emulator_settings` SET `comment` = 'Enable or disable the feature controlled by `easter_eggs.enabled`.' WHERE `key` = 'easter_eggs.enabled';
UPDATE `emulator_settings` SET `comment` = 'RSA private exponent used by the encryption layer.' WHERE `key` = 'enc.d';
UPDATE `emulator_settings` SET `comment` = 'RSA public exponent used by the encryption layer.' WHERE `key` = 'enc.e';
UPDATE `emulator_settings` SET `comment` = 'Enable RSA encryption support for the socket handshake.' WHERE `key` = 'enc.enabled';
UPDATE `emulator_settings` SET `comment` = 'RSA modulus used by the encryption layer.' WHERE `key` = 'enc.n';
UPDATE `emulator_settings` SET `comment` = 'Semicolon-separated effect IDs used by the kill command for the killer.' WHERE `key` = 'essentials.cmd_kill.effect.killer';
UPDATE `emulator_settings` SET `comment` = 'Semicolon-separated effect IDs used by the kill command for the victim.' WHERE `key` = 'essentials.cmd_kill.effect.victim';
UPDATE `emulator_settings` SET `comment` = 'Allow users with room rights to bypass the normal flood protection.' WHERE `key` = 'flood.with.rights';
UPDATE `emulator_settings` SET `comment` = 'Enable FTP uploads for generated assets.' WHERE `key` = 'ftp.enabled';
UPDATE `emulator_settings` SET `comment` = 'FTP host used for asset uploads.' WHERE `key` = 'ftp.host';
UPDATE `emulator_settings` SET `comment` = 'FTP password used for asset uploads.' WHERE `key` = 'ftp.password';
UPDATE `emulator_settings` SET `comment` = 'FTP username used for asset uploads.' WHERE `key` = 'ftp.user';
UPDATE `emulator_settings` SET `comment` = 'Maximum tile distance at which talking furniture can react to nearby speech.' WHERE `key` = 'furniture.talking.range';
UPDATE `emulator_settings` SET `comment` = 'API key used by the FastFood or BaseJump integration.' WHERE `key` = 'gamecenter.fastfood.apiKey';
UPDATE `emulator_settings` SET `comment` = 'Asset base URL used by the FastFood or BaseJump game client.' WHERE `key` = 'gamecenter.fastfood.assets';
UPDATE `emulator_settings` SET `comment` = 'Background color used by the FastFood launcher UI.' WHERE `key` = 'gamecenter.fastfood.background.color';
UPDATE `emulator_settings` SET `comment` = 'Enable the FastFood or BaseJump gamecenter integration.' WHERE `key` = 'gamecenter.fastfood.enabled';
UPDATE `emulator_settings` SET `comment` = 'Text color used by the FastFood launcher UI.' WHERE `key` = 'gamecenter.fastfood.text.color';
UPDATE `emulator_settings` SET `comment` = 'Theme name used by the FastFood launcher.' WHERE `key` = 'gamecenter.fastfood.theme';
UPDATE `emulator_settings` SET `comment` = 'Background image used for the SnowWar Arctic map.' WHERE `key` = 'gamecenter.snowwar.artic.bg';
UPDATE `emulator_settings` SET `comment` = 'Asset base URL used by the SnowWar game client.' WHERE `key` = 'gamecenter.snowwar.assets';
UPDATE `emulator_settings` SET `comment` = 'Background image used for the SnowWar Dragon Cave map.' WHERE `key` = 'gamecenter.snowwar.dragoncave.bg';
UPDATE `emulator_settings` SET `comment` = 'Enable the SnowWar gamecenter integration.' WHERE `key` = 'gamecenter.snowwar.enabled';
UPDATE `emulator_settings` SET `comment` = 'Background image used for the SnowWar Fight Night map.' WHERE `key` = 'gamecenter.snowwar.fightnight.bg';
UPDATE `emulator_settings` SET `comment` = 'Background color used by the SnowWar launcher UI.' WHERE `key` = 'gamecenter.snowwar.game.background.color';
UPDATE `emulator_settings` SET `comment` = 'Countdown in seconds before a SnowWar round starts.' WHERE `key` = 'gamecenter.snowwar.game.start.time';
UPDATE `emulator_settings` SET `comment` = 'Text color used by the SnowWar launcher UI.' WHERE `key` = 'gamecenter.snowwar.game.text.color';
UPDATE `emulator_settings` SET `comment` = 'Minimum number of players required to start SnowWar.' WHERE `key` = 'gamecenter.snowwar.players.min';
UPDATE `emulator_settings` SET `comment` = 'Room ID used as the SnowWar lobby or host room.' WHERE `key` = 'gamecenter.snowwar.room.id';
UPDATE `emulator_settings` SET `comment` = 'Remote figuredata URL used when the hotel loads avatar figure definitions.' WHERE `key` = 'gamedata.figuredata.url';
UPDATE `emulator_settings` SET `comment` = 'Time in seconds that guardians have to accept a case.' WHERE `key` = 'guardians.accept.timer';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of guardians that can be assigned to one case.' WHERE `key` = 'guardians.maximum.guardians.total';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of times an unanswered guardian case can be resent.' WHERE `key` = 'guardians.maximum.resends';
UPDATE `emulator_settings` SET `comment` = 'Minimum number of guardian votes required to resolve a case.' WHERE `key` = 'guardians.minimum.votes';
UPDATE `emulator_settings` SET `comment` = 'Cooldown in seconds before the same user can open a new guardian report.' WHERE `key` = 'guardians.reporting.cooldown';
UPDATE `emulator_settings` SET `comment` = 'Use the legacy generic alert window style.' WHERE `key` = 'hotel.alert.oldstyle';
UPDATE `emulator_settings` SET `comment` = 'Allow users to ignore staff accounts.' WHERE `key` = 'hotel.allow.ignore.staffs';
UPDATE `emulator_settings` SET `comment` = 'Amount of credits granted on each automatic payout.' WHERE `key` = 'hotel.auto.credits.amount';
UPDATE `emulator_settings` SET `comment` = 'Enable automatic credits payouts.' WHERE `key` = 'hotel.auto.credits.enabled';
UPDATE `emulator_settings` SET `comment` = 'Multiplier applied to automatic credits payouts for HC users.' WHERE `key` = 'hotel.auto.credits.hc_modifier';
UPDATE `emulator_settings` SET `comment` = 'Skip users staying in hotel view when giving automatic credits payouts.' WHERE `key` = 'hotel.auto.credits.ignore.hotelview';
UPDATE `emulator_settings` SET `comment` = 'Skip idle users when giving automatic credits payouts.' WHERE `key` = 'hotel.auto.credits.ignore.idled';
UPDATE `emulator_settings` SET `comment` = 'Interval in seconds between automatic credits payouts.' WHERE `key` = 'hotel.auto.credits.interval';
UPDATE `emulator_settings` SET `comment` = 'Enable automatic gotwpoints payouts.' WHERE `key` = 'hotel.auto.gotwpoints.enabled';
UPDATE `emulator_settings` SET `comment` = 'Multiplier applied to automatic gotwpoints payouts for HC users.' WHERE `key` = 'hotel.auto.gotwpoints.hc_modifier';
UPDATE `emulator_settings` SET `comment` = 'Skip users staying in hotel view when giving automatic gotwpoints payouts.' WHERE `key` = 'hotel.auto.gotwpoints.ignore.hotelview';
UPDATE `emulator_settings` SET `comment` = 'Skip idle users when giving automatic gotwpoints payouts.' WHERE `key` = 'hotel.auto.gotwpoints.ignore.idled';
UPDATE `emulator_settings` SET `comment` = 'Interval in seconds between automatic gotwpoints payouts.' WHERE `key` = 'hotel.auto.gotwpoints.interval';
UPDATE `emulator_settings` SET `comment` = 'Internal currency name used by the automatic gotwpoints payout.' WHERE `key` = 'hotel.auto.gotwpoints.name';
UPDATE `emulator_settings` SET `comment` = 'Currency type ID used by the automatic gotwpoints payout.' WHERE `key` = 'hotel.auto.gotwpoints.type';
UPDATE `emulator_settings` SET `comment` = 'Amount of pixels granted on each automatic payout.' WHERE `key` = 'hotel.auto.pixels.amount';
UPDATE `emulator_settings` SET `comment` = 'Enable automatic pixels payouts.' WHERE `key` = 'hotel.auto.pixels.enabled';
UPDATE `emulator_settings` SET `comment` = 'Multiplier applied to automatic pixels payouts for HC users.' WHERE `key` = 'hotel.auto.pixels.hc_modifier';
UPDATE `emulator_settings` SET `comment` = 'Skip users staying in hotel view when giving automatic pixels payouts.' WHERE `key` = 'hotel.auto.pixels.ignore.hotelview';
UPDATE `emulator_settings` SET `comment` = 'Skip idle users when giving automatic pixels payouts.' WHERE `key` = 'hotel.auto.pixels.ignore.idled';
UPDATE `emulator_settings` SET `comment` = 'Interval in seconds between automatic pixels payouts.' WHERE `key` = 'hotel.auto.pixels.interval';
UPDATE `emulator_settings` SET `comment` = 'Amount of points granted on each automatic payout.' WHERE `key` = 'hotel.auto.points.amount';
UPDATE `emulator_settings` SET `comment` = 'Enable automatic points payouts.' WHERE `key` = 'hotel.auto.points.enabled';
UPDATE `emulator_settings` SET `comment` = 'Multiplier applied to automatic points payouts for HC users.' WHERE `key` = 'hotel.auto.points.hc_modifier';
UPDATE `emulator_settings` SET `comment` = 'Skip users staying in hotel view when giving automatic points payouts.' WHERE `key` = 'hotel.auto.points.ignore.hotelview';
UPDATE `emulator_settings` SET `comment` = 'Skip idle users when giving automatic points payouts.' WHERE `key` = 'hotel.auto.points.ignore.idled';
UPDATE `emulator_settings` SET `comment` = 'Interval in seconds between automatic points payouts.' WHERE `key` = 'hotel.auto.points.interval';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.banzai.points.tile.fill`.' WHERE `key` = 'hotel.banzai.points.tile.fill';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.banzai.points.tile.lock`.' WHERE `key` = 'hotel.banzai.points.tile.lock';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.banzai.points.tile.steal`.' WHERE `key` = 'hotel.banzai.points.tile.steal';
UPDATE `emulator_settings` SET `comment` = 'Maximum tile distance from which a butler bot accepts commands.' WHERE `key` = 'hotel.bot.butler.commanddistance';
UPDATE `emulator_settings` SET `comment` = 'Maximum tile distance from which a butler bot can serve requests.' WHERE `key` = 'hotel.bot.butler.servedistance';
UPDATE `emulator_settings` SET `comment` = 'Minimum number of seconds between bot chat lines.' WHERE `key` = 'hotel.bot.chat.minimum.interval';
UPDATE `emulator_settings` SET `comment` = 'Maximum bot chat delay allowed when configuring scripted speech.' WHERE `key` = 'hotel.bot.max.chatdelay';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of characters allowed in bot chat lines.' WHERE `key` = 'hotel.bot.max.chatlength';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of characters allowed in bot names.' WHERE `key` = 'hotel.bot.max.namelength';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of bots allowed in one inventory.' WHERE `key` = 'hotel.bots.max.inventory';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of bots allowed in one room.' WHERE `key` = 'hotel.bots.max.room';
UPDATE `emulator_settings` SET `comment` = 'Default calendar campaign name or identifier.' WHERE `key` = 'hotel.calendar.default';
UPDATE `emulator_settings` SET `comment` = 'Enable the hotel calendar feature.' WHERE `key` = 'hotel.calendar.enabled';
UPDATE `emulator_settings` SET `comment` = 'Multiplier applied to calendar pixel rewards for HC users.' WHERE `key` = 'hotel.calendar.pixels.hc_modifier';
UPDATE `emulator_settings` SET `comment` = 'Unix timestamp used as the calendar start date.' WHERE `key` = 'hotel.calendar.starttimestamp';
UPDATE `emulator_settings` SET `comment` = 'Number of discount slots or discount batches shown by the catalog.' WHERE `key` = 'hotel.catalog.discounts.amount';
UPDATE `emulator_settings` SET `comment` = 'Respect catalog item order numbers when rendering pages.' WHERE `key` = 'hotel.catalog.items.display.ordernum';
UPDATE `emulator_settings` SET `comment` = 'Enable daily purchase limits for limited catalog items.' WHERE `key` = 'hotel.catalog.ltd.limit.enabled';
UPDATE `emulator_settings` SET `comment` = 'Cooldown in seconds between catalog purchases.' WHERE `key` = 'hotel.catalog.purchase.cooldown';
UPDATE `emulator_settings` SET `comment` = 'Enable the catalog recycler feature.' WHERE `key` = 'hotel.catalog.recycler.enabled';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of characters allowed in one public chat message.' WHERE `key` = 'hotel.chat.max.length';
UPDATE `emulator_settings` SET `comment` = 'Daily amount of respect points available for users.' WHERE `key` = 'hotel.daily.respect';
UPDATE `emulator_settings` SET `comment` = 'Daily amount of pet respect points available for users.' WHERE `key` = 'hotel.daily.respect.pets';
UPDATE `emulator_settings` SET `comment` = 'Enable or disable the feature controlled by `hotel.ecotron.enabled`.' WHERE `key` = 'hotel.ecotron.enabled';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.ecotron.rarity.chance.1`.' WHERE `key` = 'hotel.ecotron.rarity.chance.1';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.ecotron.rarity.chance.2`.' WHERE `key` = 'hotel.ecotron.rarity.chance.2';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.ecotron.rarity.chance.3`.' WHERE `key` = 'hotel.ecotron.rarity.chance.3';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.ecotron.rarity.chance.4`.' WHERE `key` = 'hotel.ecotron.rarity.chance.4';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.ecotron.rarity.chance.5`.' WHERE `key` = 'hotel.ecotron.rarity.chance.5';
UPDATE `emulator_settings` SET `comment` = 'Mute duration in seconds applied by the hotel flood protection.' WHERE `key` = 'hotel.flood.mute.time';
UPDATE `emulator_settings` SET `comment` = 'Maximum total floorplan area allowed for custom rooms.' WHERE `key` = 'hotel.floorplan.max.totalarea';
UPDATE `emulator_settings` SET `comment` = 'Maximum floorplan width or length allowed for custom rooms.' WHERE `key` = 'hotel.floorplan.max.widthlength';
UPDATE `emulator_settings` SET `comment` = 'Number of explosion boosts lost when a player gets frozen.' WHERE `key` = 'hotel.freeze.onfreeze.loose.explosionboost';
UPDATE `emulator_settings` SET `comment` = 'Number of snowballs lost when a player gets frozen.' WHERE `key` = 'hotel.freeze.onfreeze.loose.snowballs';
UPDATE `emulator_settings` SET `comment` = 'Time in seconds a player remains frozen.' WHERE `key` = 'hotel.freeze.onfreeze.time.frozen';
UPDATE `emulator_settings` SET `comment` = 'Score awarded for blocking tiles in Freeze.' WHERE `key` = 'hotel.freeze.points.block';
UPDATE `emulator_settings` SET `comment` = 'Score awarded for using Freeze effects or power-up actions.' WHERE `key` = 'hotel.freeze.points.effect';
UPDATE `emulator_settings` SET `comment` = 'Score awarded for freezing another player in Freeze.' WHERE `key` = 'hotel.freeze.points.freeze';
UPDATE `emulator_settings` SET `comment` = 'Chance for Freeze power-ups to spawn.' WHERE `key` = 'hotel.freeze.powerup.chance';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of extra lives granted by a Freeze power-up.' WHERE `key` = 'hotel.freeze.powerup.max.lives';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of extra snowballs granted by a Freeze power-up.' WHERE `key` = 'hotel.freeze.powerup.max.snowballs';
UPDATE `emulator_settings` SET `comment` = 'Allow Freeze protection power-ups to stack.' WHERE `key` = 'hotel.freeze.powerup.protection.stack';
UPDATE `emulator_settings` SET `comment` = 'Protection time in seconds after receiving a Freeze protection power-up.' WHERE `key` = 'hotel.freeze.powerup.protection.time';
UPDATE `emulator_settings` SET `comment` = 'Default friend category ID assigned to new friends.' WHERE `key` = 'hotel.friendcategory';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.furni.gym.achievement.olympics_c16_crosstrainer`.' WHERE `key` = 'hotel.furni.gym.achievement.olympics_c16_crosstrainer';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.furni.gym.achievement.olympics_c16_trampoline`.' WHERE `key` = 'hotel.furni.gym.achievement.olympics_c16_trampoline';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.furni.gym.achievement.olympics_c16_treadmill`.' WHERE `key` = 'hotel.furni.gym.achievement.olympics_c16_treadmill';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.furni.gym.forcerot.olympics_c16_crosstrainer`.' WHERE `key` = 'hotel.furni.gym.forcerot.olympics_c16_crosstrainer';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.furni.gym.forcerot.olympics_c16_trampoline`.' WHERE `key` = 'hotel.furni.gym.forcerot.olympics_c16_trampoline';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.furni.gym.forcerot.olympics_c16_treadmill`.' WHERE `key` = 'hotel.furni.gym.forcerot.olympics_c16_treadmill';
UPDATE `emulator_settings` SET `comment` = 'Comma-separated list of gift box type IDs allowed in the catalog.' WHERE `key` = 'hotel.gifts.box_types';
UPDATE `emulator_settings` SET `comment` = 'Maximum message length allowed on gift notes.' WHERE `key` = 'hotel.gifts.length.max';
UPDATE `emulator_settings` SET `comment` = 'Comma-separated list of ribbon type IDs allowed in the catalog.' WHERE `key` = 'hotel.gifts.ribbon_types';
UPDATE `emulator_settings` SET `comment` = 'Credit price used by special gift boxes.' WHERE `key` = 'hotel.gifts.special.price';
UPDATE `emulator_settings` SET `comment` = 'Room ID used as the default home room for new users.' WHERE `key` = 'hotel.home.room';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of items allowed in one inventory.' WHERE `key` = 'hotel.inventory.max.items';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.item.trap.hween14_rare2`.' WHERE `key` = 'hotel.item.trap.hween14_rare2';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.item.trap.hween_c17_handstrap`.' WHERE `key` = 'hotel.item.trap.hween_c17_handstrap';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.item.trap.hween_c17_spiketrap`.' WHERE `key` = 'hotel.item.trap.hween_c17_spiketrap';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `hotel.item.trap.pirate_sandtrap`.' WHERE `key` = 'hotel.item.trap.pirate_sandtrap';
UPDATE `emulator_settings` SET `comment` = 'Track limit used by large jukebox furniture.' WHERE `key` = 'hotel.jukebox.limit.large';
UPDATE `emulator_settings` SET `comment` = 'Track limit used by normal jukebox furniture.' WHERE `key` = 'hotel.jukebox.limit.normal';
UPDATE `emulator_settings` SET `comment` = 'Enable logging for chat.' WHERE `key` = 'hotel.log.chat';
UPDATE `emulator_settings` SET `comment` = 'Enable logging for chat private.' WHERE `key` = 'hotel.log.chat.private';
UPDATE `emulator_settings` SET `comment` = 'Enable logging for room enter.' WHERE `key` = 'hotel.log.room.enter';
UPDATE `emulator_settings` SET `comment` = 'Enable logging for trades.' WHERE `key` = 'hotel.log.trades';
UPDATE `emulator_settings` SET `comment` = 'Currency type used for marketplace prices and taxes.' WHERE `key` = 'hotel.marketplace.currency';
UPDATE `emulator_settings` SET `comment` = 'Enable or disable the feature controlled by `hotel.marketplace.enabled`.' WHERE `key` = 'hotel.marketplace.enabled';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of bots allowed in one room.' WHERE `key` = 'hotel.max.bots.room';
UPDATE `emulator_settings` SET `comment` = 'Maximum amount of duckets a user can hold.' WHERE `key` = 'hotel.max.duckets';
UPDATE `emulator_settings` SET `comment` = 'Enable or disable the feature controlled by `hotel.messenger.offline.messaging.enabled`.' WHERE `key` = 'hotel.messenger.offline.messaging.enabled';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of results returned by messenger user searches.' WHERE `key` = 'hotel.messenger.search.maxresults';
UPDATE `emulator_settings` SET `comment` = 'Public hotel name shown across the client and outgoing messages.' WHERE `key` = 'hotel.name';
UPDATE `emulator_settings` SET `comment` = 'Enable navigator room previews or camera mode.' WHERE `key` = 'hotel.navigator.camera';
UPDATE `emulator_settings` SET `comment` = 'Default owner name displayed by the navigator.' WHERE `key` = 'hotel.navigator.owner';
UPDATE `emulator_settings` SET `comment` = 'Number of rooms shown in the popular rooms list.' WHERE `key` = 'hotel.navigator.popular.amount';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of rooms shown per popular category.' WHERE `key` = 'hotel.navigator.popular.category.maxresults';
UPDATE `emulator_settings` SET `comment` = 'List type used for the popular rooms tab.' WHERE `key` = 'hotel.navigator.popular.listtype';
UPDATE `emulator_settings` SET `comment` = 'Include public rooms inside the popular rooms tab.' WHERE `key` = 'hotel.navigator.populartab.publics';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of results returned by navigator searches.' WHERE `key` = 'hotel.navigator.search.maxresults';
UPDATE `emulator_settings` SET `comment` = 'Respect order numbers when sorting navigator results.' WHERE `key` = 'hotel.navigator.sort.ordernum';
UPDATE `emulator_settings` SET `comment` = 'Category ID used for the staff picks tab.' WHERE `key` = 'hotel.navigator.staffpicks.categoryid';
UPDATE `emulator_settings` SET `comment` = 'Enable the NUX gift flow for new users.' WHERE `key` = 'hotel.nux.gifts.enabled';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of pets allowed in one inventory.' WHERE `key` = 'hotel.pets.max.inventory';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of pets allowed in one room.' WHERE `key` = 'hotel.pets.max.room';
UPDATE `emulator_settings` SET `comment` = 'Maximum pet name length.' WHERE `key` = 'hotel.pets.name.length.max';
UPDATE `emulator_settings` SET `comment` = 'Minimum pet name length.' WHERE `key` = 'hotel.pets.name.length.min';
UPDATE `emulator_settings` SET `comment` = 'Generic player label used by text templates and client messages.' WHERE `key` = 'hotel.player.name';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of the same limited item a user can buy per day.' WHERE `key` = 'hotel.purchase.ltd.limit.daily.item';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of limited items a user can buy per day across all limited sales.' WHERE `key` = 'hotel.purchase.ltd.limit.daily.total';
UPDATE `emulator_settings` SET `comment` = 'Cooldown in seconds before daily counters such as respect are refilled.' WHERE `key` = 'hotel.refill.daily';
UPDATE `emulator_settings` SET `comment` = 'Maximum roller delay or speed value accepted by roller furniture.' WHERE `key` = 'hotel.rollers.speed.maximum';
UPDATE `emulator_settings` SET `comment` = 'Enable room-entry logs.' WHERE `key` = 'hotel.room.enter.logs';
UPDATE `emulator_settings` SET `comment` = 'Validate custom floorplans before rooms are saved.' WHERE `key` = 'hotel.room.floorplan.check.enabled';
UPDATE `emulator_settings` SET `comment` = 'Maximum amount of furniture allowed in one room.' WHERE `key` = 'hotel.room.furni.max';
UPDATE `emulator_settings` SET `comment` = 'Room ID used as the newbie lobby.' WHERE `key` = 'hotel.room.nooblobby';
UPDATE `emulator_settings` SET `comment` = 'Kick users who stand on public room door tiles.' WHERE `key` = 'hotel.room.public.doortile.kick';
UPDATE `emulator_settings` SET `comment` = 'Allow rollers to ignore normal placement rules.' WHERE `key` = 'hotel.room.rollers.norules';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of avatars that rollers can move at once.' WHERE `key` = 'hotel.room.rollers.roll_avatars.max';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of sticky notes allowed in one room.' WHERE `key` = 'hotel.room.stickies.max';
UPDATE `emulator_settings` SET `comment` = 'Prefix template written by sticky pole furniture.' WHERE `key` = 'hotel.room.stickypole.prefix';
UPDATE `emulator_settings` SET `comment` = 'Semicolon-separated staff room tags.' WHERE `key` = 'hotel.room.tags.staff';
UPDATE `emulator_settings` SET `comment` = 'Allow empty rooms to switch into the idle state automatically.' WHERE `key` = 'hotel.rooms.auto.idle';
UPDATE `emulator_settings` SET `comment` = 'Enable decoration-hosting features for rooms.' WHERE `key` = 'hotel.rooms.deco_hosting';
UPDATE `emulator_settings` SET `comment` = 'Time in seconds before temporary hand items are cleared.' WHERE `key` = 'hotel.rooms.handitem.time';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of favorite rooms allowed per user.' WHERE `key` = 'hotel.rooms.max.favorite';
UPDATE `emulator_settings` SET `comment` = 'Idle cycle count before a room user is marked idle.' WHERE `key` = 'hotel.roomuser.idle.cycles';
UPDATE `emulator_settings` SET `comment` = 'Idle cycle count before a room user is kicked for idling.' WHERE `key` = 'hotel.roomuser.idle.cycles.kick';
UPDATE `emulator_settings` SET `comment` = 'Ignore the wired idle status when checking the room idle rule.' WHERE `key` = 'hotel.roomuser.idle.not_dancing.ignore.wired_idle';
UPDATE `emulator_settings` SET `comment` = 'Enable the sanctions system.' WHERE `key` = 'hotel.sanctions.enabled';
UPDATE `emulator_settings` SET `comment` = 'Modifier used by the shop discount calculation.' WHERE `key` = 'hotel.shop.discount.modifier';
UPDATE `emulator_settings` SET `comment` = 'Enable the talent track feature.' WHERE `key` = 'hotel.talenttrack.enabled';
UPDATE `emulator_settings` SET `comment` = 'Offer ID requested when the client asks for a targeted offer.' WHERE `key` = 'hotel.targetoffer.id';
UPDATE `emulator_settings` SET `comment` = 'Allow users to use teleports inside locked rooms when they otherwise qualify.' WHERE `key` = 'hotel.teleport.locked.allowed';
UPDATE `emulator_settings` SET `comment` = 'Enable room trading.' WHERE `key` = 'hotel.trading.enabled';
UPDATE `emulator_settings` SET `comment` = 'Require the trading perk before users may trade.' WHERE `key` = 'hotel.trading.requires.perk';
UPDATE `emulator_settings` SET `comment` = 'Maximum value used by `hotel.trophies.length.max`.' WHERE `key` = 'hotel.trophies.length.max';
UPDATE `emulator_settings` SET `comment` = 'Run clothing validation when the related action occurs: onchangelooks.' WHERE `key` = 'hotel.users.clothingvalidation.onchangelooks';
UPDATE `emulator_settings` SET `comment` = 'Run clothing validation when the related action occurs: onfballgate.' WHERE `key` = 'hotel.users.clothingvalidation.onfballgate';
UPDATE `emulator_settings` SET `comment` = 'Run clothing validation when the related action occurs: onhcexpired.' WHERE `key` = 'hotel.users.clothingvalidation.onhcexpired';
UPDATE `emulator_settings` SET `comment` = 'Run clothing validation when the related action occurs: onlogin.' WHERE `key` = 'hotel.users.clothingvalidation.onlogin';
UPDATE `emulator_settings` SET `comment` = 'Run clothing validation when the related action occurs: onmannequin.' WHERE `key` = 'hotel.users.clothingvalidation.onmannequin';
UPDATE `emulator_settings` SET `comment` = 'Run clothing validation when the related action occurs: onmimic.' WHERE `key` = 'hotel.users.clothingvalidation.onmimic';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of friends allowed for normal users.' WHERE `key` = 'hotel.users.max.friends';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of friends allowed for HC users.' WHERE `key` = 'hotel.users.max.friends.hc';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of rooms allowed for normal users.' WHERE `key` = 'hotel.users.max.rooms';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of rooms allowed for HC users.' WHERE `key` = 'hotel.users.max.rooms.hc';
UPDATE `emulator_settings` SET `comment` = 'Enable the limited-countdown hotel-view widget.' WHERE `key` = 'hotel.view.ltdcountdown.enabled';
UPDATE `emulator_settings` SET `comment` = 'Item ID shown by the limited-countdown widget.' WHERE `key` = 'hotel.view.ltdcountdown.itemid';
UPDATE `emulator_settings` SET `comment` = 'Item name shown by the limited-countdown widget.' WHERE `key` = 'hotel.view.ltdcountdown.itemname';
UPDATE `emulator_settings` SET `comment` = 'Catalog page ID linked by the limited-countdown widget.' WHERE `key` = 'hotel.view.ltdcountdown.pageid';
UPDATE `emulator_settings` SET `comment` = 'Unix timestamp used by the limited-countdown widget.' WHERE `key` = 'hotel.view.ltdcountdown.timestamp';
UPDATE `emulator_settings` SET `comment` = 'Delay in milliseconds before the welcome alert is shown.' WHERE `key` = 'hotel.welcome.alert.delay';
UPDATE `emulator_settings` SET `comment` = 'Enable the welcome alert shown after login.' WHERE `key` = 'hotel.welcome.alert.enabled';
UPDATE `emulator_settings` SET `comment` = 'Message template used by the welcome alert.' WHERE `key` = 'hotel.welcome.alert.message';
UPDATE `emulator_settings` SET `comment` = 'Use the legacy welcome alert window style.' WHERE `key` = 'hotel.welcome.alert.oldstyle';
UPDATE `emulator_settings` SET `comment` = 'Mute duration in minutes applied when word-filter automute is triggered.' WHERE `key` = 'hotel.wordfilter.automute';
UPDATE `emulator_settings` SET `comment` = 'Enable the word filter system.' WHERE `key` = 'hotel.wordfilter.enabled';
UPDATE `emulator_settings` SET `comment` = 'Apply the word filter to messenger messages.' WHERE `key` = 'hotel.wordfilter.messenger';
UPDATE `emulator_settings` SET `comment` = 'Normalise text before checking it against the word filter.' WHERE `key` = 'hotel.wordfilter.normalise';
UPDATE `emulator_settings` SET `comment` = 'Replacement word used when text is censored.' WHERE `key` = 'hotel.wordfilter.replacement';
UPDATE `emulator_settings` SET `comment` = 'Apply the word filter to room chat.' WHERE `key` = 'hotel.wordfilter.rooms';
UPDATE `emulator_settings` SET `comment` = 'SQL query used to populate the hotel-view hall of fame panel.' WHERE `key` = 'hotelview.halloffame.query';
UPDATE `emulator_settings` SET `comment` = 'Amount of activity points awarded by the hotel-view promotion.' WHERE `key` = 'hotelview.promotional.points';
UPDATE `emulator_settings` SET `comment` = 'Activity point type used by the hotel-view promotional reward.' WHERE `key` = 'hotelview.promotional.points.type';
UPDATE `emulator_settings` SET `comment` = 'Base item ID used by the hotel-view promotional reward.' WHERE `key` = 'hotelview.promotional.reward.id';
UPDATE `emulator_settings` SET `comment` = 'Public item name used by the hotel-view promotional reward.' WHERE `key` = 'hotelview.promotional.reward.name';
UPDATE `emulator_settings` SET `comment` = 'Generate images locally instead of relying on an external imager service.' WHERE `key` = 'imager.internal.enabled';
UPDATE `emulator_settings` SET `comment` = 'Filesystem path where badge part assets are stored.' WHERE `key` = 'imager.location.badgeparts';
UPDATE `emulator_settings` SET `comment` = 'Filesystem output path for generated badges.' WHERE `key` = 'imager.location.output.badges';
UPDATE `emulator_settings` SET `comment` = 'Filesystem output path for saved camera photos.' WHERE `key` = 'imager.location.output.camera';
UPDATE `emulator_settings` SET `comment` = 'Filesystem output path for generated camera thumbnails.' WHERE `key` = 'imager.location.output.thumbnail';
UPDATE `emulator_settings` SET `comment` = 'Template URL used to fetch YouTube thumbnails.' WHERE `key` = 'imager.url.youtube';
UPDATE `emulator_settings` SET `comment` = 'Client asset path used for the basejump gamecenter images.' WHERE `key` = 'images.gamecenter.basejump';
UPDATE `emulator_settings` SET `comment` = 'Client asset path used for the snowwar gamecenter images.' WHERE `key` = 'images.gamecenter.snowwar';
UPDATE `emulator_settings` SET `comment` = 'Show the hotel information panel or startup information message.' WHERE `key` = 'info.shown';
UPDATE `emulator_settings` SET `comment` = 'Prevent invisible users from speaking in rooms.' WHERE `key` = 'invisible.prevent.chat';
UPDATE `emulator_settings` SET `comment` = 'Number of Netty boss-group threads used by the socket server.' WHERE `key` = 'io.bossgroup.threads';
UPDATE `emulator_settings` SET `comment` = 'Handle incoming client packets with a multi-threaded pipeline.' WHERE `key` = 'io.client.multithreaded.handler';
UPDATE `emulator_settings` SET `comment` = 'Number of Netty worker-group threads used by the socket server.' WHERE `key` = 'io.workergroup.threads';
UPDATE `emulator_settings` SET `comment` = 'Enable extra debug logging in the emulator logger.' WHERE `key` = 'logging.debug';
UPDATE `emulator_settings` SET `comment` = 'Log packet parsing errors.' WHERE `key` = 'logging.errors.packets';
UPDATE `emulator_settings` SET `comment` = 'Log runtime exceptions.' WHERE `key` = 'logging.errors.runtime';
UPDATE `emulator_settings` SET `comment` = 'Log SQL errors.' WHERE `key` = 'logging.errors.sql';
UPDATE `emulator_settings` SET `comment` = 'Log packet traffic in the standard logger.' WHERE `key` = 'logging.packets';
UPDATE `emulator_settings` SET `comment` = 'Log undefined packets in the standard logger.' WHERE `key` = 'logging.packets.undefined';
UPDATE `emulator_settings` SET `comment` = 'Global switch for the marketplace subsystem.' WHERE `key` = 'marketplace.enabled';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `monsterplant.seed.item_id`.' WHERE `key` = 'monsterplant.seed.item_id';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `monsterplant.seed_rare.item_id`.' WHERE `key` = 'monsterplant.seed_rare.item_id';
UPDATE `emulator_settings` SET `comment` = 'Validate moodlight color values before applying them.' WHERE `key` = 'moodlight.color_check.enabled';
UPDATE `emulator_settings` SET `comment` = 'Semicolon-separated navigator event category definitions shown in the events tab.' WHERE `key` = 'navigator.eventcategories';
UPDATE `emulator_settings` SET `comment` = 'Enable TCP proxy-aware networking behaviour.' WHERE `key` = 'networking.tcp.proxy';
UPDATE `emulator_settings` SET `comment` = 'Automatically notify staff when a chat report is created.' WHERE `key` = 'notify.staff.chat.auto.report';
UPDATE `emulator_settings` SET `comment` = 'Base path used by the client to load furniture icon assets.' WHERE `key` = 'path.furniture.icons';
UPDATE `emulator_settings` SET `comment` = 'Maximum pathfinder execution time in milliseconds before aborting.' WHERE `key` = 'pathfinder.execution_time.milli';
UPDATE `emulator_settings` SET `comment` = 'Enforce the pathfinder execution time limit.' WHERE `key` = 'pathfinder.max_execution_time.enabled';
UPDATE `emulator_settings` SET `comment` = 'Allow the pathfinder to walk down falling steps.' WHERE `key` = 'pathfinder.step.allow.falling';
UPDATE `emulator_settings` SET `comment` = 'Maximum height difference the pathfinder may step onto.' WHERE `key` = 'pathfinder.step.maximum.height';
UPDATE `emulator_settings` SET `comment` = 'Chat bubble style ID used by the pirate parrot.' WHERE `key` = 'pirate_parrot.message.bubble';
UPDATE `emulator_settings` SET `comment` = 'Number of predefined messages available to the pirate parrot.' WHERE `key` = 'pirate_parrot.message.count';
UPDATE `emulator_settings` SET `comment` = 'Maximum number of characters allowed on post-it notes.' WHERE `key` = 'postit.charlimit';
UPDATE `emulator_settings` SET `comment` = 'Maximum delay allowed in the Pyramids minigame or puzzle timing.' WHERE `key` = 'pyramids.max.delay';
UPDATE `emulator_settings` SET `comment` = 'Use retro-style home room behaviour in the navigator or onboarding flow.' WHERE `key` = 'retro.style.homeroom';
UPDATE `emulator_settings` SET `comment` = 'Extra room chat delay applied before users can speak again.' WHERE `key` = 'room.chat.delay';
UPDATE `emulator_settings` SET `comment` = 'Allow whispering while a user stands inside a mute area.' WHERE `key` = 'room.chat.mutearea.allow_whisper';
UPDATE `emulator_settings` SET `comment` = 'HTML or text format used for room chat prefixes.' WHERE `key` = 'room.chat.prefix.format';
UPDATE `emulator_settings` SET `comment` = 'Badge code displayed on promoted rooms.' WHERE `key` = 'room.promotion.badge';
UPDATE `emulator_settings` SET `comment` = 'Image used by Rosie bubble notifications.' WHERE `key` = 'rosie.bubble.image.url';
UPDATE `emulator_settings` SET `comment` = 'Currency type used by Rosie when buying a room or room package.' WHERE `key` = 'rosie.buyroom.currency.type';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `runtime.threads`.' WHERE `key` = 'runtime.threads';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `save.private.chats`.' WHERE `key` = 'save.private.chats';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `save.room.chats`.' WHERE `key` = 'save.room.chats';
UPDATE `emulator_settings` SET `comment` = 'Expose moderation tickets to the scripter or automation tooling.' WHERE `key` = 'scripter.modtool.tickets';
UPDATE `emulator_settings` SET `comment` = 'Currency type ID used for diamonds.' WHERE `key` = 'seasonal.currency.diamond';
UPDATE `emulator_settings` SET `comment` = 'Currency type ID used for duckets.' WHERE `key` = 'seasonal.currency.ducket';
UPDATE `emulator_settings` SET `comment` = 'Semicolon-separated display names for seasonal currency types.' WHERE `key` = 'seasonal.currency.names';
UPDATE `emulator_settings` SET `comment` = 'Currency type ID used for pixels.' WHERE `key` = 'seasonal.currency.pixel';
UPDATE `emulator_settings` SET `comment` = 'Currency type ID used for shells.' WHERE `key` = 'seasonal.currency.shell';
UPDATE `emulator_settings` SET `comment` = 'Primary seasonal currency type ID.' WHERE `key` = 'seasonal.primary.type';
UPDATE `emulator_settings` SET `comment` = 'Semicolon-separated list of currency type IDs treated as seasonal currencies.' WHERE `key` = 'seasonal.types';
UPDATE `emulator_settings` SET `comment` = 'Achievement code granted for the HC subscription tier.' WHERE `key` = 'subscriptions.hc.achievement';
UPDATE `emulator_settings` SET `comment` = 'Number of days before expiry when HC discount offers become available.' WHERE `key` = 'subscriptions.hc.discount.days_before_end';
UPDATE `emulator_settings` SET `comment` = 'Enable discounted HC renewal offers.' WHERE `key` = 'subscriptions.hc.discount.enabled';
UPDATE `emulator_settings` SET `comment` = 'Reset tracked credits spent when the HC subscription expires.' WHERE `key` = 'subscriptions.hc.payday.creditsspent_reset_on_expire';
UPDATE `emulator_settings` SET `comment` = 'Currency rewarded by the HC payday system.' WHERE `key` = 'subscriptions.hc.payday.currency';
UPDATE `emulator_settings` SET `comment` = 'Enable the HC payday reward system.' WHERE `key` = 'subscriptions.hc.payday.enabled';
UPDATE `emulator_settings` SET `comment` = 'Date interval used between HC payday reward runs.' WHERE `key` = 'subscriptions.hc.payday.interval';
UPDATE `emulator_settings` SET `comment` = 'Next scheduled execution date for HC payday rewards.' WHERE `key` = 'subscriptions.hc.payday.next_date';
UPDATE `emulator_settings` SET `comment` = 'Percentage of eligible spending returned by HC payday.' WHERE `key` = 'subscriptions.hc.payday.percentage';
UPDATE `emulator_settings` SET `comment` = 'Semicolon-separated streak thresholds and rewards for HC payday.' WHERE `key` = 'subscriptions.hc.payday.streak';
UPDATE `emulator_settings` SET `comment` = 'Enable the subscription background scheduler.' WHERE `key` = 'subscriptions.scheduler.enabled';
UPDATE `emulator_settings` SET `comment` = 'Interval in minutes between subscription scheduler runs.' WHERE `key` = 'subscriptions.scheduler.interval';
UPDATE `emulator_settings` SET `comment` = 'Compatibility marker used by the custom team wired implementation. Do not remove.' WHERE `key` = 'team.wired.update.rc-1';
UPDATE `emulator_settings` SET `comment` = 'API key used by the YouTube integration.' WHERE `key` = 'youtube.apikey';
+6
View File
@@ -0,0 +1,6 @@
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
('furni.editor.renderer.config.path', '/var/www/Gamedata/config/renderer-config.json'),
('furni.editor.asset.base.path', '/var/www/Gamedata/furniture/nitro-assets/');
ALTER TABLE permissions
ADD COLUMN `acc_catalogfurni` ENUM('0','1') NOT NULL DEFAULT '0' AFTER `acc_catalog_ids`;
@@ -0,0 +1 @@
DELETE FROM emulator_settings WHERE (`key` = 'youtube.apikey');
@@ -0,0 +1,499 @@
-- Normalizes the legacy `permissions` table into:
-- 1. `permission_ranks` -> one row per rank with rank metadata.
-- 2. `permission_definitions` -> one row per permission key with comments and one `rank_<id>` column per rank.
--
-- This migration keeps the old `permissions` table untouched so the emulator can safely fall back to it.
-- It also cleans up the older experimental normalized objects if they were already created.
DROP VIEW IF EXISTS `permissions_matrix_view`;
DROP PROCEDURE IF EXISTS `refresh_permissions_matrix_view`;
DROP TABLE IF EXISTS `permission_rank_values`;
DROP TABLE IF EXISTS `permission_nodes`;
CREATE TABLE IF NOT EXISTS `permission_ranks` (
`id` int(11) NOT NULL,
`rank_name` varchar(25) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`hidden_rank` tinyint(1) NOT NULL DEFAULT 0,
`badge` varchar(12) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '',
`job_description` varchar(255) NOT NULL DEFAULT 'Here to help',
`staff_color` varchar(8) NOT NULL DEFAULT '#327fa8',
`staff_background` varchar(255) NOT NULL DEFAULT 'staff-bg.png',
`level` int(11) NOT NULL DEFAULT 1,
`room_effect` int(11) NOT NULL DEFAULT 0,
`log_commands` enum('0','1') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '0',
`prefix` varchar(5) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '',
`prefix_color` varchar(7) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '',
`auto_credits_amount` int(11) DEFAULT 0,
`auto_pixels_amount` int(11) DEFAULT 0,
`auto_gotw_amount` int(11) DEFAULT 0,
`auto_points_amount` int(11) DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_uca1400_ai_ci ROW_FORMAT=DYNAMIC;
CREATE TABLE IF NOT EXISTS `permission_definitions` (
`permission_key` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`max_value` tinyint(3) unsigned NOT NULL DEFAULT 1,
`comment` text NOT NULL,
PRIMARY KEY (`permission_key`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_uca1400_ai_ci ROW_FORMAT=DYNAMIC;
ALTER TABLE `permission_definitions`
DROP COLUMN IF EXISTS `category`,
DROP COLUMN IF EXISTS `value_type`,
DROP COLUMN IF EXISTS `sort_order`;
INSERT INTO `permission_ranks` (
`id`,
`rank_name`,
`hidden_rank`,
`badge`,
`job_description`,
`staff_color`,
`staff_background`,
`level`,
`room_effect`,
`log_commands`,
`prefix`,
`prefix_color`,
`auto_credits_amount`,
`auto_pixels_amount`,
`auto_gotw_amount`,
`auto_points_amount`
)
SELECT
`id`,
`rank_name`,
`hidden_rank`,
`badge`,
`job_description`,
`staff_color`,
`staff_background`,
`level`,
`room_effect`,
`log_commands`,
`prefix`,
`prefix_color`,
`auto_credits_amount`,
`auto_pixels_amount`,
`auto_gotw_amount`,
`auto_points_amount`
FROM `permissions`
ON DUPLICATE KEY UPDATE
`rank_name` = VALUES(`rank_name`),
`hidden_rank` = VALUES(`hidden_rank`),
`badge` = VALUES(`badge`),
`job_description` = VALUES(`job_description`),
`staff_color` = VALUES(`staff_color`),
`staff_background` = VALUES(`staff_background`),
`level` = VALUES(`level`),
`room_effect` = VALUES(`room_effect`),
`log_commands` = VALUES(`log_commands`),
`prefix` = VALUES(`prefix`),
`prefix_color` = VALUES(`prefix_color`),
`auto_credits_amount` = VALUES(`auto_credits_amount`),
`auto_pixels_amount` = VALUES(`auto_pixels_amount`),
`auto_gotw_amount` = VALUES(`auto_gotw_amount`),
`auto_points_amount` = VALUES(`auto_points_amount`);
DROP PROCEDURE IF EXISTS `refresh_permission_definition_rank_columns`;
DELIMITER $$
CREATE PROCEDURE `refresh_permission_definition_rank_columns`()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE current_rank_id INT;
DECLARE current_column_name VARCHAR(32);
DECLARE column_exists INT DEFAULT 0;
DECLARE rank_cursor CURSOR FOR SELECT `id` FROM `permission_ranks` ORDER BY `id` ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN rank_cursor;
rank_loop: LOOP
FETCH rank_cursor INTO current_rank_id;
IF done = 1 THEN
LEAVE rank_loop;
END IF;
SET current_column_name = CONCAT('rank_', current_rank_id);
SELECT COUNT(*)
INTO column_exists
FROM `information_schema`.`columns`
WHERE `table_schema` = DATABASE()
AND `table_name` = 'permission_definitions'
AND `column_name` = current_column_name;
IF column_exists = 0 THEN
SET @alter_permissions_column_sql = CONCAT(
'ALTER TABLE `permission_definitions` ADD COLUMN `',
current_column_name,
'` tinyint(3) unsigned NOT NULL DEFAULT 0'
);
PREPARE alter_permissions_column_stmt FROM @alter_permissions_column_sql;
EXECUTE alter_permissions_column_stmt;
DEALLOCATE PREPARE alter_permissions_column_stmt;
END IF;
END LOOP;
CLOSE rank_cursor;
END$$
DELIMITER ;
CALL `refresh_permission_definition_rank_columns`();
INSERT INTO `permission_definitions` (
`permission_key`,
`max_value`,
`comment`
)
SELECT
`column_name` AS `permission_key`,
CASE
WHEN `column_type` LIKE '%''2''%' THEN 2
ELSE 1
END AS `max_value`,
CASE
WHEN COALESCE(`column_comment`, '') <> '' THEN `column_comment`
WHEN `column_name` LIKE 'cmd\_%' AND `column_type` LIKE '%''2''%' THEN CONCAT(
'Controls access to the :',
REPLACE(SUBSTRING(`column_name`, 5), '_', ' '),
' command. Values: 0 = disabled, 1 = allowed, 2 = allowed only when room-owner rights may be used.'
)
WHEN `column_name` LIKE 'cmd\_%' THEN CONCAT(
'Controls access to the :',
REPLACE(SUBSTRING(`column_name`, 5), '_', ' '),
' command. Values: 0 = disabled, 1 = allowed.'
)
WHEN `column_name` LIKE 'acc\_%' AND `column_type` LIKE '%''2''%' THEN CONCAT(
'Controls the ',
REPLACE(SUBSTRING(`column_name`, 5), '_', ' '),
' capability for this rank. Values: 0 = disabled, 1 = enabled, 2 = enabled only when room-owner rights may be used.'
)
WHEN `column_name` LIKE 'acc\_%' THEN CONCAT(
'Controls the ',
REPLACE(SUBSTRING(`column_name`, 5), '_', ' '),
' capability for this rank. Values: 0 = disabled, 1 = enabled.'
)
ELSE CONCAT(
'Legacy permission-related value migrated from the old permissions table for ',
`column_name`,
'.'
)
END AS `comment`
FROM `information_schema`.`columns`
WHERE `table_schema` = DATABASE()
AND `table_name` = 'permissions'
AND `column_name` NOT IN (
'id',
'rank_name',
'hidden_rank',
'badge',
'job_description',
'staff_color',
'staff_background',
'level',
'room_effect',
'log_commands',
'prefix',
'prefix_color',
'auto_credits_amount',
'auto_pixels_amount',
'auto_gotw_amount',
'auto_points_amount'
)
ON DUPLICATE KEY UPDATE
`max_value` = VALUES(`max_value`),
`comment` = VALUES(`comment`);
DROP TEMPORARY TABLE IF EXISTS `tmp_permission_comments`;
CREATE TEMPORARY TABLE `tmp_permission_comments` (
`permission_key` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`comment` text NOT NULL,
PRIMARY KEY (`permission_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_uca1400_ai_ci;
INSERT INTO `tmp_permission_comments` (`permission_key`, `comment`) VALUES
('cmd_about', 'Allows using :about to display emulator, revision, or hotel information exposed by the command.'),
('cmd_alert', 'Allows using :alert to send a hotel alert popup to a specific user.'),
('cmd_allow_trading', 'Allows using the trading-toggle command to enable or disable trading for a target user.'),
('cmd_badge', 'Allows granting a badge code to a target user through a command.'),
('cmd_ban', 'Allows banning users from the hotel.'),
('cmd_blockalert', 'Allows sending the block-alert style moderation message.'),
('cmd_bots', 'Allows using :bots to list the bots currently placed in the room.'),
('cmd_bundle', 'Allows using :bundle / :roombundle to create a catalog room-bundle offer for the current room.'),
('cmd_calendar', 'Allows using the hotel calendar command and any calendar actions wired to that command entry.'),
('cmd_changename', 'Allows forcing a user-name change through the change-name command flow.'),
('cmd_chatcolor', 'Allows changing the active chat bubble color through the chat-color command.'),
('cmd_commands', 'Allows using :commands to list the command keys available to the current user.'),
('cmd_connect_camera', 'Allows using the command that links the in-room camera feature to the current room session.'),
('cmd_control', 'Allows using :control to take over another in-room user and stop controlling them later.'),
('cmd_coords', 'Allows using :coords to inspect room coordinates for tiles, users, or furniture.'),
('cmd_credits', 'Allows giving or removing credits from a user through the staff currency command.'),
('cmd_subscription', 'Allows granting or editing subscription time through the subscription command.'),
('cmd_danceall', 'Allows forcing every Habbo currently in the room to dance.'),
('cmd_diagonal', 'Allows toggling diagonal walking for the current room.'),
('cmd_disconnect', 'Allows disconnecting a user from the hotel immediately.'),
('cmd_duckets', 'Allows giving or removing duckets from a user through the staff currency command.'),
('cmd_ejectall', 'Allows ejecting all users from the current room.'),
('cmd_empty', 'Allows clearing the current user furniture inventory through the empty-inventory command.'),
('cmd_empty_bots', 'Allows clearing the current user bot inventory through the empty-bots command.'),
('cmd_empty_pets', 'Allows clearing the current user pet inventory through the empty-pets command.'),
('cmd_enable', 'Allows applying an avatar effect to yourself, or to another user when acc_enable_others is also granted.'),
('cmd_event', 'Allows marking the current room as an event room through the event command.'),
('cmd_faceless', 'Allows toggling the faceless avatar visual state on the executing room unit.'),
('cmd_fastwalk', 'Allows toggling fast-walk mode for yourself or another in-room user.'),
('cmd_filterword', 'Allows adding or removing entries from the configured word filter through command usage.'),
('cmd_freeze', 'Allows freezing a target user in place.'),
('cmd_freeze_bots', 'Allows freezing bots that are placed in the room.'),
('cmd_gift', 'Allows sending a gift to a target user through the gift command.'),
('cmd_give_rank', 'Allows setting another user rank through the give-rank command.'),
('cmd_ha', 'Allows sending a hotel-wide alert.'),
('acc_can_stalk', 'Allows following users even when they have disabled stalking.'),
('cmd_hal', 'Allows sending a hotel-wide alert with a clickable link or extended content.'),
('cmd_invisible', 'Allows toggling invisible staff mode.'),
('cmd_ip_ban', 'Allows banning a user by IP address.'),
('cmd_machine_ban', 'Allows banning a user by machine identifier.'),
('cmd_hand_item', 'Allows spawning or changing the hand item currently held by a user.'),
('cmd_happyhour', 'Allows starting or stopping the happy-hour event flow exposed by the happyhour command.'),
('cmd_hidewired', 'Allows toggling whether wired furniture is visually hidden in the current room.'),
('cmd_kickall', 'Allows kicking every user from the current room.'),
('cmd_softkick', 'Allows soft-kicking a user back to the hotel view without a full sanction.'),
('cmd_massbadge', 'Allows giving the same badge to many users at once.'),
('cmd_roombadge', 'Allows setting or overriding the room badge shown to users.'),
('cmd_masscredits', 'Allows giving credits to many users at once through the mass-credits command.'),
('cmd_massduckets', 'Allows giving duckets to many users at once through the mass-duckets command.'),
('cmd_massgift', 'Allows sending the same gift to many users at once.'),
('cmd_masspoints', 'Allows giving activity points to many users at once through the mass-points command.'),
('cmd_moonwalk', 'Allows toggling the moonwalk avatar effect for yourself while you are inside a room.'),
('cmd_mimic', 'Allows copying another user appearance or presence state through the mimic command.'),
('cmd_multi', 'Allows executing multiple chat commands from the special sticky/post-it scripting payload.'),
('cmd_mute', 'Allows muting a target user.'),
('cmd_pet_info', 'Allows opening the detailed pet-information view for a pet.'),
('cmd_pickall', 'Allows picking up every furniture item from the current room.'),
('cmd_plugins', 'Legacy key for the :plugins command, which currently lists loaded plugins without enforcing this dedicated permission node in code.'),
('cmd_points', 'Allows giving or removing activity points from a user through the points command.'),
('cmd_promote_offer', 'Allows using :promoteoffer to list active target offers or switch the globally promoted target offer.'),
('cmd_pull', 'Allows pulling a nearby user onto the tile directly in front of you.'),
('cmd_push', 'Allows pushing the user standing in front of you one tile farther in the direction you are facing.'),
('cmd_redeem', 'Allows redeeming redeemable inventory items through the redeem command flow.'),
('cmd_reload_room', 'Allows unloading and reloading the current room, then forwarding the occupants back into the fresh room instance.'),
('cmd_roomalert', 'Allows sending the same alert message to everyone in the current room.'),
('cmd_roomcredits', 'Allows giving credits to every Habbo currently in the room.'),
('cmd_roomeffect', 'Allows applying the same avatar effect id to every Habbo currently in the room.'),
('cmd_roomgift', 'Allows sending the same gift to every Habbo currently in the room.'),
('cmd_roomitem', 'Allows setting the same hand-item id for every Habbo in the room; using 0 clears the hand item.'),
('cmd_roommute', 'Allows muting every Habbo currently in the room.'),
('cmd_roompixels', 'Allows giving duckets or pixels to every Habbo currently in the room.'),
('cmd_roompoints', 'Allows giving activity points to every Habbo currently in the room.'),
('cmd_say', 'Allows forcing another online user to say a custom message in their current room.'),
('cmd_say_all', 'Allows making everyone in the room say a message.'),
('cmd_setmax', 'Allows using :setmax to change the maximum user capacity of the current room.'),
('cmd_set_poll', 'Allows using :setpoll to attach or remove a poll on the current room.'),
('cmd_setpublic', 'Allows using :setpublic to change the room public/private visibility state.'),
('cmd_setspeed', 'Allows using :setspeed to change the room walking speed setting.'),
('cmd_shout', 'Allows forcing another online user to shout a custom message in their current room.'),
('cmd_shout_all', 'Allows making everyone in the room shout a message.'),
('cmd_shutdown', 'Allows using the shutdown command to stop the emulator process.'),
('cmd_sitdown', 'Allows forcing users to sit down through the sitdown command.'),
('cmd_staffalert', 'Allows sending an alert that is visible only to staff members.'),
('cmd_staffonline', 'Allows viewing the current list of online staff members.'),
('cmd_summon', 'Allows summoning a target user into the room where the staff member currently is.'),
('cmd_summonrank', 'Allows summoning all online users of a given rank into the current room.'),
('cmd_super_ban', 'Allows issuing the strongest ban command variant exposed by the super-ban command.'),
('cmd_stalk', 'Allows following another user to their room.'),
('cmd_superpull', 'Allows pulling a user to the tile in front of you without the short-range reach check used by :pull.'),
('cmd_take_badge', 'Allows removing a badge code from a target user.'),
('cmd_talk', 'Allows using the legacy :talk command to make another user speak a command-provided message.'),
('cmd_teleport', 'Allows toggling the room-unit teleport mode used by the :teleport command.'),
('cmd_trash', 'Allows deleting or trashing furniture/items through the trash command flow.'),
('cmd_transform', 'Allows transforming your room unit into a chosen pet type, race, and color.'),
('cmd_unban', 'Allows removing active bans.'),
('cmd_unload', 'Allows disposing the current room instance immediately through :unload / :crash.'),
('cmd_unmute', 'Allows removing an active mute from a target user.'),
('cmd_update_achievements', 'Allows using :update_achievements to reload achievements configuration.'),
('cmd_update_bots', 'Allows using :update_bots to reload bot data and bot configuration.'),
('cmd_update_catalogue', 'Allows using :update_catalogue to reload catalogue pages and offers.'),
('cmd_update_config', 'Allows using :update_config to reload emulator configuration settings.'),
('cmd_update_guildparts', 'Allows using :update_guildparts to reload guild badge parts and guild configuration.'),
('cmd_update_hotel_view', 'Allows using :update_hotel_view to reload hotel-view assets or settings.'),
('cmd_update_items', 'Allows using :update_items to reload item data and furniture definitions.'),
('cmd_update_navigator', 'Allows using :update_navigator to reload navigator configuration and listings.'),
('cmd_update_permissions', 'Allows using :update_permissions to reload ranks and permissions from the database.'),
('cmd_update_pet_data', 'Allows using :update_pet_data to reload pet types and pet races.'),
('cmd_update_plugins', 'Allows using :update_plugins to reload plugin data or plugin metadata.'),
('cmd_update_polls', 'Allows using :update_polls to reload poll and questionnaire data.'),
('cmd_update_texts', 'Allows using :update_texts to reload external texts and localizations.'),
('cmd_update_wordfilter', 'Allows using :update_wordfilter to reload the word-filter list.'),
('cmd_userinfo', 'Allows opening the detailed user-information view used by staff tools.'),
('cmd_word_quiz', 'Allows starting a room word-quiz event with a custom question and optional duration.'),
('cmd_warp', 'Allows instantly warping your room unit to a target tile.'),
('acc_anychatcolor', 'Allows selecting any chat bubble color, including normally restricted colors.'),
('acc_anyroomowner', 'Treats the rank as room owner for owner-only checks such as room settings, wired saving, rights management, floorplan editing, and similar room-owner gates.'),
('acc_empty_others', 'Allows :empty, :empty_bots, and :empty_pets to target another user inventory instead of only your own.'),
('acc_enable_others', 'Allows :enable to apply avatar effects to another user instead of only to yourself.'),
('acc_see_whispers', 'Allows seeing whispers sent between other users in the room.'),
('acc_see_tentchat', 'Allows seeing tent chat or similar hidden chat channels that are normally not visible to everyone.'),
('acc_superwired', 'Allows saving advanced wired data without the normal wordfilter and reward payload restrictions applied to regular users.'),
('acc_supporttool', 'Allows opening and using the support/moderation tool interface.'),
('acc_unkickable', 'Prevents the user from being kicked by normal moderation or room commands.'),
('acc_guildgate', 'Allows bypassing guild gate access restrictions.'),
('acc_moverotate', 'Allows moving, rotating, and saving wired furniture without the usual room-owner restriction checks.'),
('acc_placefurni', 'Allows placing furniture, opening :wired, and passing room-right checks that normally require owner or controller rights.'),
('acc_unlimited_bots', 'Removes both the bot inventory cap and the per-room bot placement cap for this rank.'),
('acc_unlimited_pets', 'Removes both the pet inventory cap and the per-room pet placement cap for this rank.'),
('acc_hide_ip', 'Hides the user IP address in staff tools and other staff-facing views.'),
('acc_hide_mail', 'Hides the user email address in moderation tools and staff views.'),
('acc_not_mimiced', 'Prevents other users from mimicking this account.'),
('acc_chat_no_flood', 'Exempts the user from flood protection limits.'),
('acc_staff_chat', 'Allows accessing staff-only chat channels and staff broadcasts.'),
('acc_staff_pick', 'Allows using staff item pick-up actions that bypass normal room ownership restrictions.'),
('acc_enteranyroom', 'Allows entering rooms regardless of door mode, bans, or normal access restrictions.'),
('acc_fullrooms', 'Allows entering rooms even when they are at maximum user capacity.'),
('acc_infinite_credits', 'Prevents credits from being consumed when a command or purchase checks credit balance.'),
('acc_infinite_pixels', 'Prevents duckets or pixels from being consumed when the balance is checked.'),
('acc_infinite_points', 'Prevents activity points from being consumed when the balance is checked.'),
('acc_ambassador', 'Marks the rank as an ambassador for ambassador-only tools and visuals.'),
('acc_debug', 'Allows using debug-only features, commands, or internal tooling.'),
('acc_chat_no_limit', 'Lets the user hear and be heard regardless of room hearing distance limits.'),
('acc_chat_no_filter', 'Bypasses the word filter for chat and staff-generated messages.'),
('acc_nomute', 'Prevents the user from being muted by normal mute checks.'),
('acc_guild_admin', 'Allows bypassing guild admin restrictions when managing guilds.'),
('acc_catalog_ids', 'Allows seeing internal catalogue page ids, offer ids, or related technical catalogue identifiers.'),
('acc_modtool_ticket_q', 'Allows seeing and handling the moderation ticket queue.'),
('acc_modtool_user_logs', 'Allows reading user chat logs in the moderation tool.'),
('acc_modtool_user_alert', 'Allows sending moderation alerts or cautions to users.'),
('acc_modtool_user_kick', 'Allows kicking users from the moderation tool.'),
('acc_modtool_user_ban', 'Allows banning users from the moderation tool.'),
('acc_modtool_room_info', 'Allows viewing room information in the moderation tool.'),
('acc_modtool_room_logs', 'Allows viewing room chat logs in the moderation tool.'),
('acc_trade_anywhere', 'Allows starting trades outside the normal trade-enabled areas.'),
('acc_update_notifications', 'Allows receiving update notifications emitted by the emulator.'),
('acc_helper_use_guide_tool', 'Allows opening the helper guide tool.'),
('acc_helper_give_guide_tours', 'Allows accepting and handling guide tour requests.'),
('acc_helper_judge_chat_reviews', 'Allows reviewing helper or chat review tickets.'),
('acc_floorplan_editor', 'Allows opening and saving the floorplan editor.'),
('acc_camera', 'Allows using the in-room camera feature and related camera UI actions.'),
('acc_ads_background', 'Allows editing room advertisement backgrounds.'),
('cmd_wordquiz', 'Legacy alias of cmd_word_quiz for starting a room word-quiz event.'),
('acc_room_staff_tags', 'Shows staff tags or markers above the user while inside rooms.'),
('acc_infinite_friends', 'Removes the normal friend-list size limit.'),
('acc_mimic_unredeemed', 'Allows mimicking looks even when they contain unreleased or restricted clothing.'),
('cmd_update_youtube_playlists', 'Allows reloading YouTube playlist configuration for furniture integrations.'),
('cmd_add_youtube_playlist', 'Allows adding a new YouTube playlist entry.'),
('acc_mention', 'Allows using mention-related chat features beyond the normal rank restriction.'),
('cmd_setstate', 'Legacy room-editor permission for :setstate / :ss, used to change the selected furni state or extradata value.'),
('cmd_buildheight', 'Legacy room-editor permission for :buildheight / :bh, used to change the room build-height override.'),
('cmd_setrotation', 'Legacy room-editor permission for :setrotation / :rot, used to change the rotation of the selected furni.'),
('cmd_sellroom', 'Allows putting the current room up for sale through the sell-room command.'),
('cmd_buyroom', 'Allows purchasing a room that is marked as for sale through the buy-room command.'),
('cmd_pay', 'Allows transferring currency to another user through the pay command.'),
('cmd_kill', 'Allows using the kill command effect exposed by the current command set.'),
('cmd_hoverboard', 'Allows toggling the hoverboard effect or hoverboard movement mode.'),
('cmd_kiss', 'Allows using the kiss interaction command on another user.'),
('cmd_hug', 'Allows using the hug interaction command on another user.'),
('cmd_welcome', 'Allows triggering the welcome command behavior defined by the current command set.'),
('cmd_disable_effects', 'Allows disabling active avatar effects through the disable-effects command.'),
('cmd_brb', 'Allows toggling the be-right-back status command.'),
('cmd_nuke', 'Allows using the nuke command exposed by the current command set.'),
('cmd_slime', 'Allows applying the slime command/effect exposed by the current command set.'),
('cmd_explain', 'Allows using the explain command to send the predefined explanation/help flow to users.'),
('cmd_closedice', 'Legacy essentials permission for :closedice, used to close dice items in the room or all dice at once.'),
('acc_closedice_room', 'Legacy companion permission used by older closed-dice room checks.'),
('cmd_set', 'Legacy essentials permission for :set / :changefurni, the generic furni editing command documented by :set info.'),
('cmd_furnidata', 'Allows viewing technical furnidata information in-game for selected furniture.'),
('kiss_cmd', 'Legacy alias used for the kiss command permission.'),
('acc_calendar_force', 'Allows claiming calendar rewards even when the normal day-difference timing check would block the claim.'),
('cmd_update_calendar', 'Allows using :update_calendar to reload calendar definitions and rewards.'),
('cmd_update_all', 'Allows using :update_all to reload all supported runtime data sets in one command.'),
('cms_dance', 'Legacy CMS-side permission kept for website integrations; no direct in-emulator command handler was found in the current tree.'),
('acc_catalogfurni', 'Allows using catalogue administration features related to furniture pages and offers.'),
('acc_unignorable', 'Prevents the account from being ignored by other users through the ignore system.'),
('cmd_update_chat_bubbles', 'Allows using :update_chat_bubbles to reload chat-bubble definitions and assets.'),
('cmd_calendar_staff', 'Allows the staff-only actions exposed by the calendar command flow.');
UPDATE `permission_definitions` pd
INNER JOIN `tmp_permission_comments` tc ON tc.`permission_key` = pd.`permission_key`
SET pd.`comment` = tc.`comment`;
DROP TEMPORARY TABLE IF EXISTS `tmp_permission_comments`;
DROP PROCEDURE IF EXISTS `refresh_permission_definition_values`;
DELIMITER $$
CREATE PROCEDURE `refresh_permission_definition_values`()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE current_rank_id INT;
DECLARE current_column_name VARCHAR(32);
DECLARE rank_cursor CURSOR FOR SELECT `id` FROM `permission_ranks` ORDER BY `id` ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN rank_cursor;
rank_loop: LOOP
FETCH rank_cursor INTO current_rank_id;
IF done = 1 THEN
LEAVE rank_loop;
END IF;
SET current_column_name = CONCAT('rank_', current_rank_id);
SELECT GROUP_CONCAT(
CONCAT(
'SELECT ''',
REPLACE(`column_name`, '''', ''''''),
''' AS permission_key, CAST(COALESCE(`',
REPLACE(`column_name`, '`', '``'),
'`, ''0'') AS UNSIGNED) AS permission_value FROM `permissions` WHERE `id` = ',
current_rank_id
)
ORDER BY `ordinal_position`
SEPARATOR ' UNION ALL '
) INTO @permission_rank_source_sql
FROM `information_schema`.`columns`
WHERE `table_schema` = DATABASE()
AND `table_name` = 'permissions'
AND `column_name` NOT IN (
'id',
'rank_name',
'hidden_rank',
'badge',
'job_description',
'staff_color',
'staff_background',
'level',
'room_effect',
'log_commands',
'prefix',
'prefix_color',
'auto_credits_amount',
'auto_pixels_amount',
'auto_gotw_amount',
'auto_points_amount'
);
SET @permission_rank_update_sql = CONCAT(
'UPDATE `permission_definitions` pd ',
'INNER JOIN (',
@permission_rank_source_sql,
') src ON src.permission_key = pd.permission_key ',
'SET pd.`',
current_column_name,
'` = src.permission_value'
);
PREPARE permission_rank_update_stmt FROM @permission_rank_update_sql;
EXECUTE permission_rank_update_stmt;
DEALLOCATE PREPARE permission_rank_update_stmt;
END LOOP;
CLOSE rank_cursor;
END$$
DELIMITER ;
CALL `refresh_permission_definition_values`();
@@ -0,0 +1 @@
INSERT INTO emulator_settings (`key`, `value`) VALUES ('wired.tick.workers', '6');
@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS `room_wired_settings` (
`room_id` int(11) NOT NULL,
`inspect_mask` int(11) NOT NULL DEFAULT 0 COMMENT 'Bitmask for who can open and inspect Wired in the room. 1=everyone, 2=users with rights, 4=group members, 8=group admins.',
`modify_mask` int(11) NOT NULL DEFAULT 0 COMMENT 'Bitmask for who can modify Wired in the room. 2=users with rights, 4=group members, 8=group admins.',
PRIMARY KEY (`room_id`),
CONSTRAINT `fk_room_wired_settings_room_id` FOREIGN KEY (`room_id`) REFERENCES `rooms` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -0,0 +1,3 @@
INSERT INTO emulator_settings (`key`, `value`) VALUES ('packet.global.rate.limit', '50');
ALTER TABLE `rooms` ADD COLUMN IF NOT EXISTS `youtube_enabled` TINYINT(1) NOT NULL DEFAULT 0;
+990
View File
@@ -0,0 +1,990 @@
UPDATE emulator_settings SET `value` = '1' WHERE (`key` = 'wired.engine.enabled');
UPDATE emulator_settings SET `value` = '1' WHERE (`key` = 'wired.engine.exclusive');
ALTER TABLE emulator_settings
ADD COLUMN IF NOT EXISTS `comment` VARCHAR(255) NOT NULL AFTER `value`;
CREATE TABLE IF NOT EXISTS `catalog_items_bc` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`item_ids` varchar(666) NOT NULL,
`page_id` int(11) NOT NULL,
`catalog_name` varchar(100) NOT NULL,
`order_number` int(11) NOT NULL DEFAULT 1,
`extradata` varchar(500) NOT NULL DEFAULT '',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci ROW_FORMAT=DYNAMIC;
CREATE TABLE IF NOT EXISTS `catalog_pages_bc` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent_id` int(11) NOT NULL DEFAULT -1,
`caption` varchar(128) NOT NULL,
`page_layout` enum(
'default_3x3','club_buy','club_gift','frontpage','spaces','recycler',
'recycler_info','recycler_prizes','trophies','plasto','marketplace',
'marketplace_own_items','spaces_new','soundmachine','guilds','guild_furni',
'info_duckets','info_rentables','info_pets','roomads','single_bundle',
'sold_ltd_items','badge_display','bots','pets','pets2','pets3',
'productpage1','room_bundle','recent_purchases',
'default_3x3_color_grouping','guild_forum','vip_buy','info_loyalty',
'loyalty_vip_buy','collectibles','petcustomization','frontpage_featured'
) NOT NULL DEFAULT 'default_3x3',
`icon_color` int(11) NOT NULL DEFAULT 1,
`icon_image` int(11) NOT NULL DEFAULT 1,
`order_num` int(11) NOT NULL DEFAULT 1,
`visible` enum('0','1') NOT NULL DEFAULT '1',
`enabled` enum('0','1') NOT NULL DEFAULT '1',
`page_headline` varchar(1024) NOT NULL DEFAULT '',
`page_teaser` varchar(64) NOT NULL DEFAULT '',
`page_special` varchar(2048) DEFAULT '' COMMENT 'Gold Bubble: catalog_special_txtbg1 // Speech Bubble: catalog_special_txtbg2 // Place normal text in page_text_teaser',
`page_text1` text DEFAULT NULL,
`page_text2` text DEFAULT NULL,
`page_text_details` text DEFAULT NULL,
`page_text_teaser` text DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci ROW_FORMAT=DYNAMIC;
ALTER TABLE `catalog_club_offers`
MODIFY COLUMN `type` ENUM('HC','VIP','BUILDERS_CLUB','BUILDERS_CLUB_ADDON') NOT NULL DEFAULT 'HC';
ALTER TABLE `catalog_pages`
MODIFY COLUMN `page_layout` ENUM(
'default_3x3',
'club_buy',
'club_gift',
'frontpage',
'spaces',
'recycler',
'recycler_info',
'recycler_prizes',
'trophies',
'plasto',
'marketplace',
'marketplace_own_items',
'spaces_new',
'soundmachine',
'guilds',
'guild_furni',
'info_duckets',
'info_rentables',
'info_pets',
'roomads',
'single_bundle',
'sold_ltd_items',
'badge_display',
'bots',
'pets',
'pets2',
'pets3',
'productpage1',
'room_bundle',
'recent_purchases',
'default_3x3_color_grouping',
'guild_forum',
'vip_buy',
'info_loyalty',
'loyalty_vip_buy',
'collectibles',
'petcustomization',
'frontpage_featured',
'builders_club_frontpage',
'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`;
ALTER TABLE `catalog_pages_bc`
MODIFY COLUMN `page_layout` ENUM(
'default_3x3',
'club_buy',
'club_gift',
'frontpage',
'spaces',
'recycler',
'recycler_info',
'recycler_prizes',
'trophies',
'plasto',
'marketplace',
'marketplace_own_items',
'spaces_new',
'soundmachine',
'guilds',
'guild_furni',
'info_duckets',
'info_rentables',
'info_pets',
'roomads',
'single_bundle',
'sold_ltd_items',
'badge_display',
'bots',
'pets',
'pets2',
'pets3',
'productpage1',
'room_bundle',
'recent_purchases',
'default_3x3_color_grouping',
'guild_forum',
'vip_buy',
'info_loyalty',
'loyalty_vip_buy',
'collectibles',
'petcustomization',
'frontpage_featured',
'builders_club_frontpage',
'builders_club_addons',
'builders_club_loyalty'
) NOT NULL DEFAULT 'default_3x3';
SET @col_exists := (
SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'users_settings'
AND COLUMN_NAME = 'builders_club_bonus_furni'
);
SET @sql := IF(@col_exists = 0,
'ALTER TABLE `users_settings` ADD COLUMN `builders_club_bonus_furni` INT NOT NULL DEFAULT 0;',
'SELECT "exists";'
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
CREATE TABLE IF NOT EXISTS `wired_emulator_settings` (
`key` varchar(191) NOT NULL,
`value` text NOT NULL,
`comment` text NOT NULL,
PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
INSERT INTO `wired_emulator_settings` (`key`, `value`, `comment`)
SELECT 'wired.engine.enabled', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.engine.enabled' LIMIT 1), '1'), 'Compatibility flag kept for older configs. The runtime now always uses the new wired engine.'
UNION ALL
SELECT 'wired.engine.exclusive', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.engine.exclusive' LIMIT 1), '1'), 'Compatibility flag kept for older configs. The runtime now always uses the new wired engine.'
UNION ALL
SELECT 'wired.engine.maxStepsPerStack', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.engine.maxStepsPerStack' LIMIT 1), '100'), 'Maximum amount of internal processing steps allowed for a single wired stack execution.'
UNION ALL
SELECT 'wired.engine.debug', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.engine.debug' LIMIT 1), '0'), 'Enable verbose debug logging for the new wired engine.'
UNION ALL
SELECT 'wired.custom.enabled', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.custom.enabled' LIMIT 1), '0'), 'Enable custom legacy wired behaviour such as user-based cooldown exceptions and compatibility logic.'
UNION ALL
SELECT 'hotel.wired.furni.selection.count', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'hotel.wired.furni.selection.count' LIMIT 1), '5'), 'Maximum number of furni that a wired box can store or select.'
UNION ALL
SELECT 'hotel.wired.max_delay', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'hotel.wired.max_delay' LIMIT 1), '20'), 'Maximum delay value accepted by wired effects that support delayed execution.'
UNION ALL
SELECT 'hotel.wired.message.max_length', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'hotel.wired.message.max_length' LIMIT 1), '100'), 'Maximum length of text fields used by wired messages and bot text effects.'
UNION ALL
SELECT 'wired.effect.teleport.delay', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.effect.teleport.delay' LIMIT 1), '500'), 'Delay in milliseconds used by wired teleport movement.'
UNION ALL
SELECT 'wired.place.under', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.place.under' LIMIT 1), '0'), 'Allow placing wired furniture underneath other items when room rules permit it.'
UNION ALL
SELECT 'wired.tick.interval.ms', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.tick.interval.ms' LIMIT 1), '50'), 'Global wired tick interval in milliseconds used by repeaters and other tick-driven wired items.'
UNION ALL
SELECT 'wired.tick.resolution', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.tick.resolution' LIMIT 1), '100'), 'Legacy wired tick resolution value kept for compatibility with older wired timing setups.'
UNION ALL
SELECT 'wired.tick.debug', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.tick.debug' LIMIT 1), '0'), 'Enable verbose logging for the wired tick service.'
UNION ALL
SELECT 'wired.tick.thread.priority', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.tick.thread.priority' LIMIT 1), '6'), 'Java thread priority used by the wired tick service.'
UNION ALL
SELECT 'wired.highscores.displaycount', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.highscores.displaycount' LIMIT 1), '25'), 'Maximum number of wired highscore entries shown to users when a highscore is displayed.'
UNION ALL
SELECT 'wired.abuse.max.recursion.depth', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.abuse.max.recursion.depth' LIMIT 1), '10'), 'Maximum recursive wired depth allowed before execution is stopped.'
UNION ALL
SELECT 'wired.abuse.max.events.per.window', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.abuse.max.events.per.window' LIMIT 1), '100'), 'Maximum amount of identical wired events allowed inside the abuse rate-limit window before a room ban is applied.'
UNION ALL
SELECT 'wired.abuse.rate.limit.window.ms', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.abuse.rate.limit.window.ms' LIMIT 1), '10000'), 'Time window in milliseconds used by the wired abuse rate limiter.'
UNION ALL
SELECT 'wired.abuse.ban.duration.ms', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.abuse.ban.duration.ms' LIMIT 1), '600000'), 'Duration in milliseconds of the temporary wired ban after abuse detection.'
UNION ALL
SELECT 'wired.monitor.usage.window.ms', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.usage.window.ms' LIMIT 1), '1000'), 'Rolling window size in milliseconds used to calculate wired usage in the :wired monitor.'
UNION ALL
SELECT 'wired.monitor.usage.limit', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.usage.limit' LIMIT 1), '1000'), 'Maximum wired usage budget allowed in one monitor window before EXECUTION_CAP is raised.'
UNION ALL
SELECT 'wired.monitor.delayed.events.limit', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.delayed.events.limit' LIMIT 1), '100'), 'Maximum number of delayed wired events that can be queued in one room at the same time.'
UNION ALL
SELECT 'wired.monitor.overload.average.ms', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.overload.average.ms' LIMIT 1), '50'), 'Average execution time threshold in milliseconds that starts overload tracking.'
UNION ALL
SELECT 'wired.monitor.overload.peak.ms', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.overload.peak.ms' LIMIT 1), '150'), 'Peak single execution time threshold in milliseconds that starts overload tracking.'
UNION ALL
SELECT 'wired.monitor.overload.consecutive.windows', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.overload.consecutive.windows' LIMIT 1), '2'), 'Number of consecutive overloaded monitor windows required before logging EXECUTOR_OVERLOAD.'
UNION ALL
SELECT 'wired.monitor.heavy.usage.percent', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.heavy.usage.percent' LIMIT 1), '70'), 'Usage percentage threshold that contributes to marking a room as heavy in the :wired monitor.'
UNION ALL
SELECT 'wired.monitor.heavy.consecutive.windows', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.heavy.consecutive.windows' LIMIT 1), '5'), 'Number of consecutive windows above the heavy usage threshold required before the room is marked as heavy.'
UNION ALL
SELECT 'wired.monitor.heavy.delayed.percent', COALESCE((SELECT `value` FROM `emulator_settings` WHERE `key` = 'wired.monitor.heavy.delayed.percent' LIMIT 1), '60'), 'Delayed queue percentage threshold that also contributes to the heavy-room calculation.'
ON DUPLICATE KEY UPDATE
`value` = VALUES(`value`),
`comment` = VALUES(`comment`);
DELETE FROM `emulator_settings`
WHERE `key` IN (
'wired.engine.enabled',
'wired.engine.exclusive',
'wired.engine.maxStepsPerStack',
'wired.engine.debug',
'wired.custom.enabled',
'hotel.wired.furni.selection.count',
'hotel.wired.max_delay',
'hotel.wired.message.max_length',
'wired.effect.teleport.delay',
'wired.place.under',
'wired.tick.interval.ms',
'wired.tick.resolution',
'wired.tick.debug',
'wired.tick.thread.priority',
'wired.highscores.displaycount',
'wired.abuse.max.recursion.depth',
'wired.abuse.max.events.per.window',
'wired.abuse.rate.limit.window.ms',
'wired.abuse.ban.duration.ms',
'wired.monitor.usage.window.ms',
'wired.monitor.usage.limit',
'wired.monitor.delayed.events.limit',
'wired.monitor.overload.average.ms',
'wired.monitor.overload.peak.ms',
'wired.monitor.overload.consecutive.windows',
'wired.monitor.heavy.usage.percent',
'wired.monitor.heavy.consecutive.windows',
'wired.monitor.heavy.delayed.percent'
);
UPDATE `emulator_settings` SET `comment` = 'Allow whispering while a user stands inside a mute area.' WHERE `key` = 'room.chat.mutearea.allow_whisper';
UPDATE `emulator_settings` SET `comment` = 'HTML or text format used for room chat prefixes.' WHERE `key` = 'room.chat.prefix.format';
UPDATE `emulator_settings` SET `comment` = 'Badge code displayed on promoted rooms.' WHERE `key` = 'room.promotion.badge';
UPDATE `emulator_settings` SET `comment` = 'Image used by Rosie bubble notifications.' WHERE `key` = 'rosie.bubble.image.url';
UPDATE `emulator_settings` SET `comment` = 'Currency type used by Rosie when buying a room or room package.' WHERE `key` = 'rosie.buyroom.currency.type';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `runtime.threads`.' WHERE `key` = 'runtime.threads';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `save.private.chats`.' WHERE `key` = 'save.private.chats';
UPDATE `emulator_settings` SET `comment` = 'Configuration value used by `save.room.chats`.' WHERE `key` = 'save.room.chats';
UPDATE `emulator_settings` SET `comment` = 'Expose moderation tickets to the scripter or automation tooling.' WHERE `key` = 'scripter.modtool.tickets';
UPDATE `emulator_settings` SET `comment` = 'Currency type ID used for diamonds.' WHERE `key` = 'seasonal.currency.diamond';
UPDATE `emulator_settings` SET `comment` = 'Currency type ID used for duckets.' WHERE `key` = 'seasonal.currency.ducket';
UPDATE `emulator_settings` SET `comment` = 'Semicolon-separated display names for seasonal currency types.' WHERE `key` = 'seasonal.currency.names';
UPDATE `emulator_settings` SET `comment` = 'Currency type ID used for pixels.' WHERE `key` = 'seasonal.currency.pixel';
UPDATE `emulator_settings` SET `comment` = 'Currency type ID used for shells.' WHERE `key` = 'seasonal.currency.shell';
UPDATE `emulator_settings` SET `comment` = 'Primary seasonal currency type ID.' WHERE `key` = 'seasonal.primary.type';
UPDATE `emulator_settings` SET `comment` = 'Semicolon-separated list of currency type IDs treated as seasonal currencies.' WHERE `key` = 'seasonal.types';
UPDATE `emulator_settings` SET `comment` = 'Achievement code granted for the HC subscription tier.' WHERE `key` = 'subscriptions.hc.achievement';
UPDATE `emulator_settings` SET `comment` = 'Number of days before expiry when HC discount offers become available.' WHERE `key` = 'subscriptions.hc.discount.days_before_end';
UPDATE `emulator_settings` SET `comment` = 'Enable discounted HC renewal offers.' WHERE `key` = 'subscriptions.hc.discount.enabled';
UPDATE `emulator_settings` SET `comment` = 'Reset tracked credits spent when the HC subscription expires.' WHERE `key` = 'subscriptions.hc.payday.creditsspent_reset_on_expire';
UPDATE `emulator_settings` SET `comment` = 'Currency rewarded by the HC payday system.' WHERE `key` = 'subscriptions.hc.payday.currency';
UPDATE `emulator_settings` SET `comment` = 'Enable the HC payday reward system.' WHERE `key` = 'subscriptions.hc.payday.enabled';
UPDATE `emulator_settings` SET `comment` = 'Date interval used between HC payday reward runs.' WHERE `key` = 'subscriptions.hc.payday.interval';
UPDATE `emulator_settings` SET `comment` = 'Next scheduled execution date for HC payday rewards.' WHERE `key` = 'subscriptions.hc.payday.next_date';
UPDATE `emulator_settings` SET `comment` = 'Percentage of eligible spending returned by HC payday.' WHERE `key` = 'subscriptions.hc.payday.percentage';
UPDATE `emulator_settings` SET `comment` = 'Semicolon-separated streak thresholds and rewards for HC payday.' WHERE `key` = 'subscriptions.hc.payday.streak';
UPDATE `emulator_settings` SET `comment` = 'Enable the subscription background scheduler.' WHERE `key` = 'subscriptions.scheduler.enabled';
UPDATE `emulator_settings` SET `comment` = 'Interval in minutes between subscription scheduler runs.' WHERE `key` = 'subscriptions.scheduler.interval';
UPDATE `emulator_settings` SET `comment` = 'Compatibility marker used by the custom team wired implementation. Do not remove.' WHERE `key` = 'team.wired.update.rc-1';
UPDATE `emulator_settings` SET `comment` = 'API key used by the YouTube integration.' WHERE `key` = 'youtube.apikey';
DROP VIEW IF EXISTS `permissions_matrix_view`;
DROP PROCEDURE IF EXISTS `refresh_permissions_matrix_view`;
DROP TABLE IF EXISTS `permission_rank_values`;
DROP TABLE IF EXISTS `permission_nodes`;
CREATE TABLE IF NOT EXISTS `permission_ranks` (
`id` int(11) NOT NULL,
`rank_name` varchar(25) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`hidden_rank` tinyint(1) NOT NULL DEFAULT 0,
`badge` varchar(12) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '',
`job_description` varchar(255) NOT NULL DEFAULT 'Here to help',
`staff_color` varchar(8) NOT NULL DEFAULT '#327fa8',
`staff_background` varchar(255) NOT NULL DEFAULT 'staff-bg.png',
`level` int(11) NOT NULL DEFAULT 1,
`room_effect` int(11) NOT NULL DEFAULT 0,
`log_commands` enum('0','1') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '0',
`prefix` varchar(5) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '',
`prefix_color` varchar(7) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '',
`auto_credits_amount` int(11) DEFAULT 0,
`auto_pixels_amount` int(11) DEFAULT 0,
`auto_gotw_amount` int(11) DEFAULT 0,
`auto_points_amount` int(11) DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_uca1400_ai_ci ROW_FORMAT=DYNAMIC;
CREATE TABLE IF NOT EXISTS `permission_definitions` (
`permission_key` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`max_value` tinyint(3) unsigned NOT NULL DEFAULT 1,
`comment` text NOT NULL,
PRIMARY KEY (`permission_key`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_uca1400_ai_ci ROW_FORMAT=DYNAMIC;
ALTER TABLE `permission_definitions`
DROP COLUMN IF EXISTS `category`,
DROP COLUMN IF EXISTS `value_type`,
DROP COLUMN IF EXISTS `sort_order`;
INSERT INTO `permission_ranks` (
`id`,
`rank_name`,
`hidden_rank`,
`badge`,
`job_description`,
`staff_color`,
`staff_background`,
`level`,
`room_effect`,
`log_commands`,
`prefix`,
`prefix_color`,
`auto_credits_amount`,
`auto_pixels_amount`,
`auto_gotw_amount`,
`auto_points_amount`
)
SELECT
`id`,
`rank_name`,
`hidden_rank`,
`badge`,
`job_description`,
`staff_color`,
`staff_background`,
`level`,
`room_effect`,
`log_commands`,
`prefix`,
`prefix_color`,
`auto_credits_amount`,
`auto_pixels_amount`,
`auto_gotw_amount`,
`auto_points_amount`
FROM `permissions`
ON DUPLICATE KEY UPDATE
`rank_name` = VALUES(`rank_name`),
`hidden_rank` = VALUES(`hidden_rank`),
`badge` = VALUES(`badge`),
`job_description` = VALUES(`job_description`),
`staff_color` = VALUES(`staff_color`),
`staff_background` = VALUES(`staff_background`),
`level` = VALUES(`level`),
`room_effect` = VALUES(`room_effect`),
`log_commands` = VALUES(`log_commands`),
`prefix` = VALUES(`prefix`),
`prefix_color` = VALUES(`prefix_color`),
`auto_credits_amount` = VALUES(`auto_credits_amount`),
`auto_pixels_amount` = VALUES(`auto_pixels_amount`),
`auto_gotw_amount` = VALUES(`auto_gotw_amount`),
`auto_points_amount` = VALUES(`auto_points_amount`);
DROP PROCEDURE IF EXISTS `refresh_permission_definition_rank_columns`;
DELIMITER $$
CREATE PROCEDURE `refresh_permission_definition_rank_columns`()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE current_rank_id INT;
DECLARE current_column_name VARCHAR(32);
DECLARE column_exists INT DEFAULT 0;
DECLARE rank_cursor CURSOR FOR SELECT `id` FROM `permission_ranks` ORDER BY `id` ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN rank_cursor;
rank_loop: LOOP
FETCH rank_cursor INTO current_rank_id;
IF done = 1 THEN
LEAVE rank_loop;
END IF;
SET current_column_name = CONCAT('rank_', current_rank_id);
SELECT COUNT(*)
INTO column_exists
FROM `information_schema`.`columns`
WHERE `table_schema` = DATABASE()
AND `table_name` = 'permission_definitions'
AND `column_name` = current_column_name;
IF column_exists = 0 THEN
SET @alter_permissions_column_sql = CONCAT(
'ALTER TABLE `permission_definitions` ADD COLUMN `',
current_column_name,
'` tinyint(3) unsigned NOT NULL DEFAULT 0'
);
PREPARE alter_permissions_column_stmt FROM @alter_permissions_column_sql;
EXECUTE alter_permissions_column_stmt;
DEALLOCATE PREPARE alter_permissions_column_stmt;
END IF;
END LOOP;
CLOSE rank_cursor;
END$$
DELIMITER ;
CALL `refresh_permission_definition_rank_columns`();
INSERT INTO `permission_definitions` (
`permission_key`,
`max_value`,
`comment`
)
SELECT
`column_name` AS `permission_key`,
CASE
WHEN `column_type` LIKE '%''2''%' THEN 2
ELSE 1
END AS `max_value`,
CASE
WHEN COALESCE(`column_comment`, '') <> '' THEN `column_comment`
WHEN `column_name` LIKE 'cmd\_%' AND `column_type` LIKE '%''2''%' THEN CONCAT(
'Controls access to the :',
REPLACE(SUBSTRING(`column_name`, 5), '_', ' '),
' command. Values: 0 = disabled, 1 = allowed, 2 = allowed only when room-owner rights may be used.'
)
WHEN `column_name` LIKE 'cmd\_%' THEN CONCAT(
'Controls access to the :',
REPLACE(SUBSTRING(`column_name`, 5), '_', ' '),
' command. Values: 0 = disabled, 1 = allowed.'
)
WHEN `column_name` LIKE 'acc\_%' AND `column_type` LIKE '%''2''%' THEN CONCAT(
'Controls the ',
REPLACE(SUBSTRING(`column_name`, 5), '_', ' '),
' capability for this rank. Values: 0 = disabled, 1 = enabled, 2 = enabled only when room-owner rights may be used.'
)
WHEN `column_name` LIKE 'acc\_%' THEN CONCAT(
'Controls the ',
REPLACE(SUBSTRING(`column_name`, 5), '_', ' '),
' capability for this rank. Values: 0 = disabled, 1 = enabled.'
)
ELSE CONCAT(
'Legacy permission-related value migrated from the old permissions table for ',
`column_name`,
'.'
)
END AS `comment`
FROM `information_schema`.`columns`
WHERE `table_schema` = DATABASE()
AND `table_name` = 'permissions'
AND `column_name` NOT IN (
'id',
'rank_name',
'hidden_rank',
'badge',
'job_description',
'staff_color',
'staff_background',
'level',
'room_effect',
'log_commands',
'prefix',
'prefix_color',
'auto_credits_amount',
'auto_pixels_amount',
'auto_gotw_amount',
'auto_points_amount'
)
ON DUPLICATE KEY UPDATE
`max_value` = VALUES(`max_value`),
`comment` = VALUES(`comment`);
DROP TEMPORARY TABLE IF EXISTS `tmp_permission_comments`;
CREATE TEMPORARY TABLE `tmp_permission_comments` (
`permission_key` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`comment` text NOT NULL,
PRIMARY KEY (`permission_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_uca1400_ai_ci;
INSERT INTO `tmp_permission_comments` (`permission_key`, `comment`) VALUES
('cmd_about', 'Allows using :about to display emulator, revision, or hotel information exposed by the command.'),
('cmd_alert', 'Allows using :alert to send a hotel alert popup to a specific user.'),
('cmd_allow_trading', 'Allows using the trading-toggle command to enable or disable trading for a target user.'),
('cmd_badge', 'Allows granting a badge code to a target user through a command.'),
('cmd_ban', 'Allows banning users from the hotel.'),
('cmd_blockalert', 'Allows sending the block-alert style moderation message.'),
('cmd_bots', 'Allows using :bots to list the bots currently placed in the room.'),
('cmd_bundle', 'Allows using :bundle / :roombundle to create a catalog room-bundle offer for the current room.'),
('cmd_calendar', 'Allows using the hotel calendar command and any calendar actions wired to that command entry.'),
('cmd_changename', 'Allows forcing a user-name change through the change-name command flow.'),
('cmd_chatcolor', 'Allows changing the active chat bubble color through the chat-color command.'),
('cmd_commands', 'Allows using :commands to list the command keys available to the current user.'),
('cmd_connect_camera', 'Allows using the command that links the in-room camera feature to the current room session.'),
('cmd_control', 'Allows using :control to take over another in-room user and stop controlling them later.'),
('cmd_coords', 'Allows using :coords to inspect room coordinates for tiles, users, or furniture.'),
('cmd_credits', 'Allows giving or removing credits from a user through the staff currency command.'),
('cmd_subscription', 'Allows granting or editing subscription time through the subscription command.'),
('cmd_danceall', 'Allows forcing every Habbo currently in the room to dance.'),
('cmd_diagonal', 'Allows toggling diagonal walking for the current room.'),
('cmd_disconnect', 'Allows disconnecting a user from the hotel immediately.'),
('cmd_duckets', 'Allows giving or removing duckets from a user through the staff currency command.'),
('cmd_ejectall', 'Allows ejecting all users from the current room.'),
('cmd_empty', 'Allows clearing the current user furniture inventory through the empty-inventory command.'),
('cmd_empty_bots', 'Allows clearing the current user bot inventory through the empty-bots command.'),
('cmd_empty_pets', 'Allows clearing the current user pet inventory through the empty-pets command.'),
('cmd_enable', 'Allows applying an avatar effect to yourself, or to another user when acc_enable_others is also granted.'),
('cmd_event', 'Allows marking the current room as an event room through the event command.'),
('cmd_faceless', 'Allows toggling the faceless avatar visual state on the executing room unit.'),
('cmd_fastwalk', 'Allows toggling fast-walk mode for yourself or another in-room user.'),
('cmd_filterword', 'Allows adding or removing entries from the configured word filter through command usage.'),
('cmd_freeze', 'Allows freezing a target user in place.'),
('cmd_freeze_bots', 'Allows freezing bots that are placed in the room.'),
('cmd_gift', 'Allows sending a gift to a target user through the gift command.'),
('cmd_give_rank', 'Allows setting another user rank through the give-rank command.'),
('cmd_ha', 'Allows sending a hotel-wide alert.'),
('acc_can_stalk', 'Allows following users even when they have disabled stalking.'),
('cmd_hal', 'Allows sending a hotel-wide alert with a clickable link or extended content.'),
('cmd_invisible', 'Allows toggling invisible staff mode.'),
('cmd_ip_ban', 'Allows banning a user by IP address.'),
('cmd_machine_ban', 'Allows banning a user by machine identifier.'),
('cmd_hand_item', 'Allows spawning or changing the hand item currently held by a user.'),
('cmd_happyhour', 'Allows starting or stopping the happy-hour event flow exposed by the happyhour command.'),
('cmd_hidewired', 'Allows toggling whether wired furniture is visually hidden in the current room.'),
('cmd_kickall', 'Allows kicking every user from the current room.'),
('cmd_softkick', 'Allows soft-kicking a user back to the hotel view without a full sanction.'),
('cmd_massbadge', 'Allows giving the same badge to many users at once.'),
('cmd_roombadge', 'Allows setting or overriding the room badge shown to users.'),
('cmd_masscredits', 'Allows giving credits to many users at once through the mass-credits command.'),
('cmd_massduckets', 'Allows giving duckets to many users at once through the mass-duckets command.'),
('cmd_massgift', 'Allows sending the same gift to many users at once.'),
('cmd_masspoints', 'Allows giving activity points to many users at once through the mass-points command.'),
('cmd_moonwalk', 'Allows toggling the moonwalk avatar effect for yourself while you are inside a room.'),
('cmd_mimic', 'Allows copying another user appearance or presence state through the mimic command.'),
('cmd_multi', 'Allows executing multiple chat commands from the special sticky/post-it scripting payload.'),
('cmd_mute', 'Allows muting a target user.'),
('cmd_pet_info', 'Allows opening the detailed pet-information view for a pet.'),
('cmd_pickall', 'Allows picking up every furniture item from the current room.'),
('cmd_plugins', 'Legacy key for the :plugins command, which currently lists loaded plugins without enforcing this dedicated permission node in code.'),
('cmd_points', 'Allows giving or removing activity points from a user through the points command.'),
('cmd_promote_offer', 'Allows using :promoteoffer to list active target offers or switch the globally promoted target offer.'),
('cmd_pull', 'Allows pulling a nearby user onto the tile directly in front of you.'),
('cmd_push', 'Allows pushing the user standing in front of you one tile farther in the direction you are facing.'),
('cmd_redeem', 'Allows redeeming redeemable inventory items through the redeem command flow.'),
('cmd_reload_room', 'Allows unloading and reloading the current room, then forwarding the occupants back into the fresh room instance.'),
('cmd_roomalert', 'Allows sending the same alert message to everyone in the current room.'),
('cmd_roomcredits', 'Allows giving credits to every Habbo currently in the room.'),
('cmd_roomeffect', 'Allows applying the same avatar effect id to every Habbo currently in the room.'),
('cmd_roomgift', 'Allows sending the same gift to every Habbo currently in the room.'),
('cmd_roomitem', 'Allows setting the same hand-item id for every Habbo in the room; using 0 clears the hand item.'),
('cmd_roommute', 'Allows muting every Habbo currently in the room.'),
('cmd_roompixels', 'Allows giving duckets or pixels to every Habbo currently in the room.'),
('cmd_roompoints', 'Allows giving activity points to every Habbo currently in the room.'),
('cmd_say', 'Allows forcing another online user to say a custom message in their current room.'),
('cmd_say_all', 'Allows making everyone in the room say a message.'),
('cmd_setmax', 'Allows using :setmax to change the maximum user capacity of the current room.'),
('cmd_set_poll', 'Allows using :setpoll to attach or remove a poll on the current room.'),
('cmd_setpublic', 'Allows using :setpublic to change the room public/private visibility state.'),
('cmd_setspeed', 'Allows using :setspeed to change the room walking speed setting.'),
('cmd_shout', 'Allows forcing another online user to shout a custom message in their current room.'),
('cmd_shout_all', 'Allows making everyone in the room shout a message.'),
('cmd_shutdown', 'Allows using the shutdown command to stop the emulator process.'),
('cmd_sitdown', 'Allows forcing users to sit down through the sitdown command.'),
('cmd_staffalert', 'Allows sending an alert that is visible only to staff members.'),
('cmd_staffonline', 'Allows viewing the current list of online staff members.'),
('cmd_summon', 'Allows summoning a target user into the room where the staff member currently is.'),
('cmd_summonrank', 'Allows summoning all online users of a given rank into the current room.'),
('cmd_super_ban', 'Allows issuing the strongest ban command variant exposed by the super-ban command.'),
('cmd_stalk', 'Allows following another user to their room.'),
('cmd_superpull', 'Allows pulling a user to the tile in front of you without the short-range reach check used by :pull.'),
('cmd_take_badge', 'Allows removing a badge code from a target user.'),
('cmd_talk', 'Allows using the legacy :talk command to make another user speak a command-provided message.'),
('cmd_teleport', 'Allows toggling the room-unit teleport mode used by the :teleport command.'),
('cmd_trash', 'Allows deleting or trashing furniture/items through the trash command flow.'),
('cmd_transform', 'Allows transforming your room unit into a chosen pet type, race, and color.'),
('cmd_unban', 'Allows removing active bans.'),
('cmd_unload', 'Allows disposing the current room instance immediately through :unload / :crash.'),
('cmd_unmute', 'Allows removing an active mute from a target user.'),
('cmd_update_achievements', 'Allows using :update_achievements to reload achievements configuration.'),
('cmd_update_bots', 'Allows using :update_bots to reload bot data and bot configuration.'),
('cmd_update_catalogue', 'Allows using :update_catalogue to reload catalogue pages and offers.'),
('cmd_update_config', 'Allows using :update_config to reload emulator configuration settings.'),
('cmd_update_guildparts', 'Allows using :update_guildparts to reload guild badge parts and guild configuration.'),
('cmd_update_hotel_view', 'Allows using :update_hotel_view to reload hotel-view assets or settings.'),
('cmd_update_items', 'Allows using :update_items to reload item data and furniture definitions.'),
('cmd_update_navigator', 'Allows using :update_navigator to reload navigator configuration and listings.'),
('cmd_update_permissions', 'Allows using :update_permissions to reload ranks and permissions from the database.'),
('cmd_update_pet_data', 'Allows using :update_pet_data to reload pet types and pet races.'),
('cmd_update_plugins', 'Allows using :update_plugins to reload plugin data or plugin metadata.'),
('cmd_update_polls', 'Allows using :update_polls to reload poll and questionnaire data.'),
('cmd_update_texts', 'Allows using :update_texts to reload external texts and localizations.'),
('cmd_update_wordfilter', 'Allows using :update_wordfilter to reload the word-filter list.'),
('cmd_userinfo', 'Allows opening the detailed user-information view used by staff tools.'),
('cmd_word_quiz', 'Allows starting a room word-quiz event with a custom question and optional duration.'),
('cmd_warp', 'Allows instantly warping your room unit to a target tile.'),
('acc_anychatcolor', 'Allows selecting any chat bubble color, including normally restricted colors.'),
('acc_anyroomowner', 'Treats the rank as room owner for owner-only checks such as room settings, wired saving, rights management, floorplan editing, and similar room-owner gates.'),
('acc_empty_others', 'Allows :empty, :empty_bots, and :empty_pets to target another user inventory instead of only your own.'),
('acc_enable_others', 'Allows :enable to apply avatar effects to another user instead of only to yourself.'),
('acc_see_whispers', 'Allows seeing whispers sent between other users in the room.'),
('acc_see_tentchat', 'Allows seeing tent chat or similar hidden chat channels that are normally not visible to everyone.'),
('acc_superwired', 'Allows saving advanced wired data without the normal wordfilter and reward payload restrictions applied to regular users.'),
('acc_supporttool', 'Allows opening and using the support/moderation tool interface.'),
('acc_unkickable', 'Prevents the user from being kicked by normal moderation or room commands.'),
('acc_guildgate', 'Allows bypassing guild gate access restrictions.'),
('acc_moverotate', 'Allows moving, rotating, and saving wired furniture without the usual room-owner restriction checks.'),
('acc_placefurni', 'Allows placing furniture, opening :wired, and passing room-right checks that normally require owner or controller rights.'),
('acc_unlimited_bots', 'Removes both the bot inventory cap and the per-room bot placement cap for this rank.'),
('acc_unlimited_pets', 'Removes both the pet inventory cap and the per-room pet placement cap for this rank.'),
('acc_hide_ip', 'Hides the user IP address in staff tools and other staff-facing views.'),
('acc_hide_mail', 'Hides the user email address in moderation tools and staff views.'),
('acc_not_mimiced', 'Prevents other users from mimicking this account.'),
('acc_chat_no_flood', 'Exempts the user from flood protection limits.'),
('acc_staff_chat', 'Allows accessing staff-only chat channels and staff broadcasts.'),
('acc_staff_pick', 'Allows using staff item pick-up actions that bypass normal room ownership restrictions.'),
('acc_enteranyroom', 'Allows entering rooms regardless of door mode, bans, or normal access restrictions.'),
('acc_fullrooms', 'Allows entering rooms even when they are at maximum user capacity.'),
('acc_infinite_credits', 'Prevents credits from being consumed when a command or purchase checks credit balance.'),
('acc_infinite_pixels', 'Prevents duckets or pixels from being consumed when the balance is checked.'),
('acc_infinite_points', 'Prevents activity points from being consumed when the balance is checked.'),
('acc_ambassador', 'Marks the rank as an ambassador for ambassador-only tools and visuals.'),
('acc_debug', 'Allows using debug-only features, commands, or internal tooling.'),
('acc_chat_no_limit', 'Lets the user hear and be heard regardless of room hearing distance limits.'),
('acc_chat_no_filter', 'Bypasses the word filter for chat and staff-generated messages.'),
('acc_nomute', 'Prevents the user from being muted by normal mute checks.'),
('acc_guild_admin', 'Allows bypassing guild admin restrictions when managing guilds.'),
('acc_catalog_ids', 'Allows seeing internal catalogue page ids, offer ids, or related technical catalogue identifiers.'),
('acc_modtool_ticket_q', 'Allows seeing and handling the moderation ticket queue.'),
('acc_modtool_user_logs', 'Allows reading user chat logs in the moderation tool.'),
('acc_modtool_user_alert', 'Allows sending moderation alerts or cautions to users.'),
('acc_modtool_user_kick', 'Allows kicking users from the moderation tool.'),
('acc_modtool_user_ban', 'Allows banning users from the moderation tool.'),
('acc_modtool_room_info', 'Allows viewing room information in the moderation tool.'),
('acc_modtool_room_logs', 'Allows viewing room chat logs in the moderation tool.'),
('acc_trade_anywhere', 'Allows starting trades outside the normal trade-enabled areas.'),
('acc_update_notifications', 'Allows receiving update notifications emitted by the emulator.'),
('acc_helper_use_guide_tool', 'Allows opening the helper guide tool.'),
('acc_helper_give_guide_tours', 'Allows accepting and handling guide tour requests.'),
('acc_helper_judge_chat_reviews', 'Allows reviewing helper or chat review tickets.'),
('acc_floorplan_editor', 'Allows opening and saving the floorplan editor.'),
('acc_camera', 'Allows using the in-room camera feature and related camera UI actions.'),
('acc_ads_background', 'Allows editing room advertisement backgrounds.'),
('cmd_wordquiz', 'Legacy alias of cmd_word_quiz for starting a room word-quiz event.'),
('acc_room_staff_tags', 'Shows staff tags or markers above the user while inside rooms.'),
('acc_infinite_friends', 'Removes the normal friend-list size limit.'),
('acc_mimic_unredeemed', 'Allows mimicking looks even when they contain unreleased or restricted clothing.'),
('cmd_update_youtube_playlists', 'Allows reloading YouTube playlist configuration for furniture integrations.'),
('cmd_add_youtube_playlist', 'Allows adding a new YouTube playlist entry.'),
('acc_mention', 'Allows using mention-related chat features beyond the normal rank restriction.'),
('cmd_setstate', 'Legacy room-editor permission for :setstate / :ss, used to change the selected furni state or extradata value.'),
('cmd_buildheight', 'Legacy room-editor permission for :buildheight / :bh, used to change the room build-height override.'),
('cmd_setrotation', 'Legacy room-editor permission for :setrotation / :rot, used to change the rotation of the selected furni.'),
('cmd_sellroom', 'Allows putting the current room up for sale through the sell-room command.'),
('cmd_buyroom', 'Allows purchasing a room that is marked as for sale through the buy-room command.'),
('cmd_pay', 'Allows transferring currency to another user through the pay command.'),
('cmd_kill', 'Allows using the kill command effect exposed by the current command set.'),
('cmd_hoverboard', 'Allows toggling the hoverboard effect or hoverboard movement mode.'),
('cmd_kiss', 'Allows using the kiss interaction command on another user.'),
('cmd_hug', 'Allows using the hug interaction command on another user.'),
('cmd_welcome', 'Allows triggering the welcome command behavior defined by the current command set.'),
('cmd_disable_effects', 'Allows disabling active avatar effects through the disable-effects command.'),
('cmd_brb', 'Allows toggling the be-right-back status command.'),
('cmd_nuke', 'Allows using the nuke command exposed by the current command set.'),
('cmd_slime', 'Allows applying the slime command/effect exposed by the current command set.'),
('cmd_explain', 'Allows using the explain command to send the predefined explanation/help flow to users.'),
('cmd_closedice', 'Legacy essentials permission for :closedice, used to close dice items in the room or all dice at once.'),
('acc_closedice_room', 'Legacy companion permission used by older closed-dice room checks.'),
('cmd_set', 'Legacy essentials permission for :set / :changefurni, the generic furni editing command documented by :set info.'),
('cmd_furnidata', 'Allows viewing technical furnidata information in-game for selected furniture.'),
('kiss_cmd', 'Legacy alias used for the kiss command permission.'),
('acc_calendar_force', 'Allows claiming calendar rewards even when the normal day-difference timing check would block the claim.'),
('cmd_update_calendar', 'Allows using :update_calendar to reload calendar definitions and rewards.'),
('cmd_update_all', 'Allows using :update_all to reload all supported runtime data sets in one command.'),
('cms_dance', 'Legacy CMS-side permission kept for website integrations; no direct in-emulator command handler was found in the current tree.'),
('acc_catalogfurni', 'Allows using catalogue administration features related to furniture pages and offers.'),
('acc_unignorable', 'Prevents the account from being ignored by other users through the ignore system.'),
('cmd_update_chat_bubbles', 'Allows using :update_chat_bubbles to reload chat-bubble definitions and assets.'),
('cmd_calendar_staff', 'Allows the staff-only actions exposed by the calendar command flow.');
UPDATE `permission_definitions` pd
INNER JOIN `tmp_permission_comments` tc ON tc.`permission_key` = pd.`permission_key`
SET pd.`comment` = tc.`comment`;
DROP TEMPORARY TABLE IF EXISTS `tmp_permission_comments`;
DROP PROCEDURE IF EXISTS `refresh_permission_definition_values`;
DELIMITER $$
CREATE PROCEDURE `refresh_permission_definition_values`()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE current_rank_id INT;
DECLARE current_column_name VARCHAR(32);
DECLARE rank_cursor CURSOR FOR SELECT `id` FROM `permission_ranks` ORDER BY `id` ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN rank_cursor;
rank_loop: LOOP
FETCH rank_cursor INTO current_rank_id;
IF done = 1 THEN
LEAVE rank_loop;
END IF;
SET current_column_name = CONCAT('rank_', current_rank_id);
SELECT GROUP_CONCAT(
CONCAT(
'SELECT ''',
REPLACE(`column_name`, '''', ''''''),
''' AS permission_key, CAST(COALESCE(`',
REPLACE(`column_name`, '`', '``'),
'`, ''0'') AS UNSIGNED) AS permission_value FROM `permissions` WHERE `id` = ',
current_rank_id
)
ORDER BY `ordinal_position`
SEPARATOR ' UNION ALL '
) INTO @permission_rank_source_sql
FROM `information_schema`.`columns`
WHERE `table_schema` = DATABASE()
AND `table_name` = 'permissions'
AND `column_name` NOT IN (
'id',
'rank_name',
'hidden_rank',
'badge',
'job_description',
'staff_color',
'staff_background',
'level',
'room_effect',
'log_commands',
'prefix',
'prefix_color',
'auto_credits_amount',
'auto_pixels_amount',
'auto_gotw_amount',
'auto_points_amount'
);
SET @permission_rank_update_sql = CONCAT(
'UPDATE `permission_definitions` pd ',
'INNER JOIN (',
@permission_rank_source_sql,
') src ON src.permission_key = pd.permission_key ',
'SET pd.`',
current_column_name,
'` = src.permission_value'
);
PREPARE permission_rank_update_stmt FROM @permission_rank_update_sql;
EXECUTE permission_rank_update_stmt;
DEALLOCATE PREPARE permission_rank_update_stmt;
END LOOP;
CLOSE rank_cursor;
END$$
DELIMITER ;
CALL `refresh_permission_definition_values`();
CREATE TABLE IF NOT EXISTS `room_wired_settings` (
`room_id` int(11) NOT NULL,
`inspect_mask` int(11) NOT NULL DEFAULT 0 COMMENT 'Bitmask for who can open and inspect Wired in the room. 1=everyone, 2=users with rights, 4=group members, 8=group admins.',
`modify_mask` int(11) NOT NULL DEFAULT 0 COMMENT 'Bitmask for who can modify Wired in the room. 2=users with rights, 4=group members, 8=group admins.',
PRIMARY KEY (`room_id`),
CONSTRAINT `fk_room_wired_settings_room_id` FOREIGN KEY (`room_id`) REFERENCES `rooms` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `room_user_wired_variables` (
`room_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`variable_item_id` int(11) NOT NULL,
`value` int(11) DEFAULT NULL,
`created_at` int(11) NOT NULL DEFAULT 0,
`updated_at` int(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`room_id`, `user_id`, `variable_item_id`),
KEY `idx_room_user_wired_variables_room_item` (`room_id`, `variable_item_id`),
KEY `idx_room_user_wired_variables_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `room_furni_wired_variables` (
`room_id` int(11) NOT NULL,
`furni_id` int(11) NOT NULL,
`variable_item_id` int(11) NOT NULL,
`value` int(11) DEFAULT NULL,
`created_at` int(11) NOT NULL DEFAULT 0,
`updated_at` int(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`room_id`, `furni_id`, `variable_item_id`),
KEY `idx_room_furni_wired_variables_room_item` (`room_id`, `variable_item_id`),
KEY `idx_room_furni_wired_variables_furni` (`furni_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `room_wired_variables` (
`room_id` int(11) NOT NULL,
`variable_item_id` int(11) NOT NULL,
`value` int(11) NOT NULL DEFAULT 0,
`created_at` int(11) NOT NULL DEFAULT 0,
`updated_at` int(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`room_id`, `variable_item_id`),
KEY `idx_room_wired_variables_room_item` (`room_id`, `variable_item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
ALTER TABLE `room_user_wired_variables`
ADD COLUMN IF NOT EXISTS `created_at` int(11) NOT NULL DEFAULT 0 AFTER `value`;
ALTER TABLE `room_user_wired_variables`
ADD COLUMN IF NOT EXISTS `updated_at` int(11) NOT NULL DEFAULT 0 AFTER `created_at`;
UPDATE `room_user_wired_variables`
SET
`created_at` = IF(`created_at` > 0, `created_at`, UNIX_TIMESTAMP()),
`updated_at` = IF(`updated_at` > 0, `updated_at`, IF(`created_at` > 0, `created_at`, UNIX_TIMESTAMP()));
ALTER TABLE `room_furni_wired_variables`
ADD COLUMN IF NOT EXISTS `created_at` int(11) NOT NULL DEFAULT 0 AFTER `value`;
ALTER TABLE `room_furni_wired_variables`
ADD COLUMN IF NOT EXISTS `updated_at` int(11) NOT NULL DEFAULT 0 AFTER `created_at`;
UPDATE `room_furni_wired_variables`
SET
`created_at` = IF(`created_at` > 0, `created_at`, UNIX_TIMESTAMP()),
`updated_at` = IF(`updated_at` > 0, `updated_at`, IF(`created_at` > 0, `created_at`, UNIX_TIMESTAMP()));
ALTER TABLE `room_wired_variables`
ADD COLUMN IF NOT EXISTS `created_at` int(11) NOT NULL DEFAULT 0 AFTER `value`;
ALTER TABLE `room_wired_variables`
ADD COLUMN IF NOT EXISTS `updated_at` int(11) NOT NULL DEFAULT 0 AFTER `created_at`;
UPDATE `room_wired_variables`
SET
`created_at` = 0,
`updated_at` = IF(`updated_at` > 0, `updated_at`, UNIX_TIMESTAMP());
INSERT INTO `chat_bubbles` (`type`, `name`, `permission`, `overridable`, `triggers_talking_furniture`) VALUES
(200, 'SHOW_MESSAGE_RED', '', 1, 0),
(201, 'SHOW_MESSAGE_GREEN', '', 1, 0),
(202, 'SHOW_MESSAGE_BLUE', '', 1, 0),
(210, 'SHOW_MESSAGE_ALERT', '', 1, 0),
(211, 'SHOW_MESSAGE_INFO', '', 1, 0),
(212, 'SHOW_MESSAGE_WARNING', '', 1, 0),
(220, 'SHOW_MESSAGE_WRONG', '', 1, 0),
(221, 'SHOW_MESSAGE_WRONG_CIRCLED', '', 1, 0),
(222, 'SHOW_MESSAGE_CORRECT', '', 1, 0),
(223, 'SHOW_MESSAGE_CORRECT_CIRCLED', '', 1, 0),
(224, 'SHOW_MESSAGE_QUESTION', '', 1, 0),
(225, 'SHOW_MESSAGE_QUESTION_CIRCLED', '', 1, 0),
(226, 'SHOW_MESSAGE_ARROW_UP', '', 1, 0),
(227, 'SHOW_MESSAGE_ARROW_UP_CIRCLED', '', 1, 0),
(228, 'SHOW_MESSAGE_ARROW_DOWN', '', 1, 0),
(229, 'SHOW_MESSAGE_ARROW_DOWN_CIRCLED', '', 1, 0),
(250, 'SHOW_MESSAGE_SKULL', '', 1, 0),
(251, 'SHOW_MESSAGE_SKULL_ALT', '', 1, 0),
(252, 'SHOW_MESSAGE_MAGNIFIER', '', 1, 0)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`permission` = VALUES(`permission`),
`overridable` = VALUES(`overridable`),
`triggers_talking_furniture` = VALUES(`triggers_talking_furniture`);
ALTER TABLE `catalog_club_offers`
MODIFY COLUMN `type` ENUM('HC', 'VIP', 'BUILDERS_CLUB', 'BUILDERS_CLUB_ADDON') NOT NULL DEFAULT 'HC';
ALTER TABLE `catalog_pages`
MODIFY COLUMN `page_layout` ENUM(
'default_3x3',
'club_buy',
'club_gift',
'frontpage',
'spaces',
'recycler',
'recycler_info',
'recycler_prizes',
'trophies',
'plasto',
'marketplace',
'marketplace_own_items',
'spaces_new',
'soundmachine',
'guilds',
'guild_furni',
'info_duckets',
'info_rentables',
'info_pets',
'roomads',
'single_bundle',
'sold_ltd_items',
'badge_display',
'bots',
'pets',
'pets2',
'pets3',
'productpage1',
'room_bundle',
'recent_purchases',
'default_3x3_color_grouping',
'guild_forum',
'vip_buy',
'info_loyalty',
'loyalty_vip_buy',
'collectibles',
'petcustomization',
'frontpage_featured',
'builders_club_frontpage',
'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`;
ALTER TABLE `rooms`
ADD COLUMN IF NOT EXISTS `builders_club_trial_locked` TINYINT(1) NOT NULL DEFAULT 0 AFTER `allow_underpass`,
ADD COLUMN IF NOT EXISTS `builders_club_original_state` VARCHAR(16) NOT NULL DEFAULT 'open' AFTER `builders_club_trial_locked`;
CREATE TABLE IF NOT EXISTS `builders_club_items` (
`item_id` INT(11) NOT NULL,
`user_id` INT(11) NOT NULL,
`room_id` INT(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`item_id`),
KEY `idx_builders_club_items_user_id` (`user_id`),
KEY `idx_builders_club_items_room_id` (`room_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
ALTER TABLE `catalog_pages_bc`
MODIFY COLUMN `page_layout` ENUM(
'default_3x3',
'club_buy',
'club_gift',
'frontpage',
'spaces',
'recycler',
'recycler_info',
'recycler_prizes',
'trophies',
'plasto',
'marketplace',
'marketplace_own_items',
'spaces_new',
'soundmachine',
'guilds',
'guild_furni',
'info_duckets',
'info_rentables',
'info_pets',
'roomads',
'single_bundle',
'sold_ltd_items',
'badge_display',
'bots',
'pets',
'pets2',
'pets3',
'productpage1',
'room_bundle',
'recent_purchases',
'default_3x3_color_grouping',
'guild_forum',
'vip_buy',
'info_loyalty',
'loyalty_vip_buy',
'collectibles',
'petcustomization',
'frontpage_featured',
'builders_club_frontpage',
'builders_club_addons',
'builders_club_loyalty'
) NOT NULL DEFAULT 'default_3x3';
@@ -1,152 +0,0 @@
-- Wired Abuse Protection Settings
-- These settings control the wired abuse detection and rate limiting system
-- Maximum recursion depth to prevent infinite loops (e.g., collision + chase triggering each other)
INSERT INTO `emulator_settings` (`key`, `value`) VALUES ('wired.abuse.max.recursion.depth', '10');
-- Maximum events of same type per room within the rate limit window before triggering a ban
-- Set higher for rooms with many users and complex wired setups
INSERT INTO `emulator_settings` (`key`, `value`) VALUES ('wired.abuse.max.events.per.window', '100');
-- Time window in milliseconds for counting rapid events
-- Events are counted within this window to detect abuse patterns
INSERT INTO `emulator_settings` (`key`, `value`) VALUES ('wired.abuse.rate.limit.window.ms', '10000');
-- Duration in milliseconds to ban wired execution in a room after abuse is detected
-- Default: 600000 (10 minutes)
INSERT INTO `emulator_settings` (`key`, `value`) VALUES ('wired.abuse.ban.duration.ms', '600000');
-- Wired Abuse Alert Texts
-- Alert shown to all users in the room when wired is temporarily disabled
INSERT INTO `emulator_texts` (`key`, `value`) VALUES ('wired.abuse.room.alert', 'Wired execution has been temporarily disabled in this room due to abuse detection. It will resume in %minutes% minutes.');
-- Title for the staff bubble alert
INSERT INTO `emulator_texts` (`key`, `value`) VALUES ('wired.abuse.staff.title', 'Wired Abuse Detected');
-- Message for the staff bubble alert
-- Available placeholders: %roomname%, %owner%, %minutes%
INSERT INTO `emulator_texts` (`key`, `value`) VALUES ('wired.abuse.staff.message', 'Room: %roomname%\nOwner: %owner%\nBanned for %minutes% minutes.');
-- Link text for the staff bubble alert (navigates to the room)
INSERT INTO `emulator_texts` (`key`, `value`) VALUES ('wired.abuse.staff.link', 'Go to Room');
-- Default tick resolution for wired timer triggers (in milliseconds)
INSERT INTO `emulator_settings` (`key`, `value`) VALUES ('wired.tick.resolution', '100');
-- =====================================================
-- Wired Engine Rewrite - Configuration Settings
-- =====================================================
-- This SQL script adds the configuration options for the new
-- context-driven wired engine architecture.
--
-- Run this script after upgrading to enable the new wired system.
-- =====================================================
-- Insert new wired engine configuration settings
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
('wired.engine.enabled', '0'),
('wired.engine.exclusive', '0'),
('wired.engine.maxStepsPerStack', '100'),
('wired.engine.debug', '0')
ON DUPLICATE KEY UPDATE `key` = `key`;
-- =====================================================
-- Configuration Options Explained:
-- =====================================================
--
-- wired.engine.enabled
-- Enable the new wired engine. When set to 1, the new engine
-- runs alongside the legacy WiredHandler for parallel testing.
-- Default: 0 (disabled)
--
-- wired.engine.exclusive
-- When set to 1, disables the legacy WiredHandler completely.
-- Only enable this after thorough testing with parallel mode.
-- Default: 0 (legacy handler still active)
--
-- wired.engine.maxStepsPerStack
-- Maximum number of steps (trigger checks + condition evaluations
-- + effect executions) allowed per wired stack execution.
-- Prevents infinite loops from misconfigured wired setups.
-- Default: 100
--
-- wired.engine.debug
-- Enable verbose debug logging for wired execution.
-- Useful for troubleshooting wired stack behavior.
-- Default: 0 (disabled)
--
-- =====================================================
-- Migration Path:
-- =====================================================
--
-- Phase 1: Parallel Testing
-- UPDATE emulator_settings SET value = '1' WHERE `key` = 'wired.engine.enabled';
-- -- Test all wired functionality, compare behavior between old and new
--
-- Phase 2: Switch to New Engine
-- UPDATE emulator_settings SET value = '1' WHERE `key` = 'wired.engine.exclusive';
-- -- Legacy handler disabled, new engine handles all wired events
--
-- Phase 3: Cleanup (after confirming stability)
-- -- Remove legacy WiredHandler calls from codebase
--
-- =====================================================
-- =====================================================
-- Wired Tick System - Configuration Settings
-- =====================================================
-- This SQL script adds the configuration options for the new
-- high-resolution wired tick system (50ms default).
--
-- Run this script to configure the wired timer triggers.
-- =====================================================
-- Insert new wired tick system configuration settings
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
('wired.tick.interval.ms', '50'),
('wired.tick.debug', '0'),
('wired.tick.thread.priority', '6')
ON DUPLICATE KEY UPDATE `key` = `key`;
-- =====================================================
-- Configuration Options Explained:
-- =====================================================
--
-- wired.tick.interval.ms
-- The tick interval in milliseconds for wired timer triggers.
-- Lower values = more precise timing but higher CPU usage.
-- Recommended: 50 (default), Range: 10-500
-- Default: 50
--
-- wired.tick.debug
-- Enable verbose debug logging for wired tick operations.
-- Logs each tick cycle and trigger execution.
-- Warning: Very verbose, only enable for troubleshooting.
-- Default: 0 (disabled)
--
-- wired.tick.thread.priority
-- Thread priority for the wired tick service (1-10).
-- Higher priority = better timing accuracy under load.
-- Java Thread priorities: MIN=1, NORM=5, MAX=10
-- Default: 6 (slightly above normal)
--
-- =====================================================
-- Usage Examples:
-- =====================================================
--
-- Increase tick resolution for competitive mini-games:
-- UPDATE emulator_settings SET value = '25' WHERE `key` = 'wired.tick.interval.ms';
--
-- Reduce CPU usage on low-end servers:
-- UPDATE emulator_settings SET value = '100' WHERE `key` = 'wired.tick.interval.ms';
--
-- Enable debug logging for troubleshooting:
-- UPDATE emulator_settings SET value = '1' WHERE `key` = 'wired.tick.debug';
--
-- =====================================================
INSERT INTO `emulator_settings` (`key`, `value`) VALUES ('pathfinder.click.delay', '0');
INSERT INTO `emulator_settings` (`key`, `value`) VALUES ('pathfinder.retro-style.diagonals', '0');
INSERT INTO `emulator_settings` (`key`, `value`) VALUES ('pathfinder.step.allow.falling', '1');
@@ -1,672 +0,0 @@
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- =====================================================
-- SECTION 1: Pet System Emulator Settings
-- =====================================================
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
-- Core pet limits
('hotel.pets.max.room', '15'),
('hotel.pets.max.inventory', '25'),
('hotel.pets.name.length.min', '1'),
('hotel.pets.name.length.max', '15'),
('hotel.daily.respect.pets', '3'),
-- Command cooldown and spam prevention
('pet.command.cooldown_ms', '2000'),
('pet.command.max_same_spam', '3'),
('pet.command.spam_reset_ms', '10000'),
('pet.command.min_energy', '15'),
('pet.command.min_happiness', '10'),
('pet.command.base_obey_chance', '70'),
-- Pet behavior settings
('pet.behavior.autonomous_action_delay', '5000'),
('pet.behavior.idle_wander_min_ms', '10000'),
('pet.behavior.idle_wander_max_ms', '30000'),
-- Pet stats decay/recovery rates (per cycle)
('pet.stats.hunger_decay', '1'),
('pet.stats.thirst_decay', '1'),
('pet.stats.energy_decay', '1'),
('pet.stats.happiness_decay', '1'),
('pet.stats.energy_recovery', '5'),
('pet.stats.happiness_recovery', '1'),
-- Pet thresholds (below this = needs attention)
('pet.threshold.hungry', '50'),
('pet.threshold.thirsty', '50'),
('pet.threshold.tired', '30'),
('pet.threshold.sad', '30'),
-- Pet breeding
('pet.breeding.timeout_seconds', '120')
ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);
-- =====================================================
-- SECTION 2: Pet Actions (Pet Type Definitions)
-- =====================================================
DROP TABLE IF EXISTS `pet_actions`;
CREATE TABLE `pet_actions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`pet_type` int(11) NOT NULL,
`pet_name` varchar(32) NOT NULL DEFAULT '',
`offspring_type` int(11) NOT NULL DEFAULT -1,
`happy_actions` varchar(100) NOT NULL DEFAULT 'sml',
`tired_actions` varchar(100) NOT NULL DEFAULT 'trd',
`random_actions` varchar(100) NOT NULL DEFAULT 'lov',
`can_swim` enum('0','1') NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `pet_type` (`pet_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `pet_actions` (`pet_type`, `pet_name`, `offspring_type`, `happy_actions`, `tired_actions`, `random_actions`, `can_swim`) VALUES
(0, 'Dog', 29, 'sml,wav,joy', 'trd,yng', 'lov,snf', '0'),
(1, 'Cat', 28, 'sml,pur', 'trd,yng', 'lov,lck', '0'),
(2, 'Crocodile', -1, 'sml', 'trd', 'lov,snp', '1'),
(3, 'Terrier', 25, 'sml,wav,joy', 'trd,yng', 'lov,snf', '0'),
(4, 'Bear', 24, 'sml,grw', 'trd,yng', 'lov', '0'),
(5, 'Pig', 30, 'sml,oink', 'trd,yng', 'lov,rol', '0'),
(6, 'Lion', -1, 'sml,ror', 'trd,yng', 'lov', '0'),
(7, 'Rhino', -1, 'sml', 'trd,yng', 'lov', '0'),
(8, 'Tarantula', -1, 'sml', 'trd', 'lov,crw', '0'),
(9, 'Turtle', -1, 'sml', 'trd', 'lov', '1'),
(10, 'Chick', -1, 'sml,chp', 'trd', 'lov,pck', '0'),
(11, 'Frog', -1, 'sml,crk', 'trd', 'lov,jmp', '1'),
(12, 'Dragon', -1, 'sml,flm', 'trd,smk', 'lov,fly', '0'),
(13, 'Monster', -1, 'sml', 'trd', 'lov', '0'),
(14, 'Monkey', -1, 'sml,ook', 'trd,yng', 'lov,swg', '0'),
(15, 'Horse', -1, 'sml,nei', 'trd,yng', 'lov', '0'),
(16, 'Monsterplant', -1, 'sml', 'trd', 'lov', '0'),
(17, 'Bunny', -1, 'sml,hop', 'trd,yng', 'lov', '0'),
(18, 'Evil Bunny', -1, 'sml', 'trd', 'lov', '0'),
(19, 'Bored Bunny', -1, 'sml', 'trd', 'lov', '0'),
(20, 'Cute Bunny', -1, 'sml,hop', 'trd', 'lov', '0'),
(21, 'Wise Pigeon', -1, 'sml,coo', 'trd', 'lov,pck', '0'),
(22, 'Evil Pigeon', -1, 'sml', 'trd', 'lov,pck', '0'),
(23, 'Evil Monkey', -1, 'sml', 'trd', 'lov,swg', '0'),
(24, 'Baby Bear', -1, 'sml', 'trd,yng', 'lov', '0'),
(25, 'Baby Terrier', -1, 'sml', 'trd,yng', 'lov', '0'),
(26, 'Gnome', -1, 'sml,grn', 'trd', 'lov', '0'),
(27, 'Leprechaun', -1, 'sml,grn', 'trd', 'lov,jig', '0'),
(28, 'Baby Cat', -1, 'sml', 'trd,yng', 'lov', '0'),
(29, 'Baby Dog', -1, 'sml', 'trd,yng', 'lov', '0'),
(30, 'Baby Pig', -1, 'sml', 'trd,yng', 'lov', '0'),
(31, 'Haloompa', -1, 'sml', 'trd', 'lov', '0'),
(32, 'Fools Pet', -1, 'sml', 'trd', 'lov', '0'),
(33, 'Pterodactyl', -1, 'sml,sqk', 'trd', 'lov,fly', '0'),
(34, 'Velociraptor', -1, 'sml,hss', 'trd', 'lov,clw', '0'),
(35, 'Cow', -1, 'sml,moo', 'trd,yng', 'lov,chw', '0');
-- =====================================================
-- SECTION 3: Pet Commands Data (English Command Names)
-- =====================================================
-- Command IDs mapped to PetManager.petActions:
-- 0=Free, 1=Sit, 2=Down, 3=Here, 4=Beg, 5=PlayDead, 6=Stay, 7=Follow
-- 8=Stand, 9=Jump, 10=Speak, 11=Play, 12=Silent, 13=Nest, 14=Drink
-- 15=FollowLeft, 16=FollowRight, 17=PlayFootball, 18=Teleport, 19=Bounce
-- 20=Flatten, 21=Dance, 22=Spin, 23=Switch, 24=MoveForward
-- 25=TurnLeft, 26=TurnRight, 27=Relax, 28=Croak, 29=Dip, 30=Wave
-- 31=Mambo, 32=HighJump, 33=ChickenDance, 34=TripleJump
-- 35=Wings, 36=BreatheFire, 37=Hang, 38=Torch, 40=Swing, 41=Roll
-- 42=RingOfFire, 43=Eat, 44=WagTail, 45=Count, 46=Breed
DROP TABLE IF EXISTS `pet_commands_data`;
CREATE TABLE `pet_commands_data` (
`command_id` int(11) NOT NULL,
`text` varchar(25) NOT NULL,
`required_level` int(11) NOT NULL DEFAULT 1,
`reward_xp` int(11) NOT NULL DEFAULT 5,
`cost_happiness` int(11) NOT NULL DEFAULT 0,
`cost_energy` int(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`command_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `pet_commands_data` (`command_id`, `text`, `required_level`, `reward_xp`, `cost_happiness`, `cost_energy`) VALUES
(0, 'free', 1, 5, 0, 0),
(1, 'sit', 1, 5, 2, 2),
(2, 'down', 2, 10, 3, 3),
(3, 'come here', 2, 10, 2, 5),
(4, 'beg', 2, 10, 3, 4),
(5, 'play dead', 3, 15, 4, 5),
(6, 'stay', 4, 10, 2, 3),
(7, 'follow', 5, 15, 3, 8),
(8, 'stand', 6, 15, 2, 3),
(9, 'jump', 6, 15, 4, 8),
(10, 'speak', 7, 10, 3, 3),
(11, 'play', 8, 5, 5, 10),
(12, 'silent', 8, 5, 2, 1),
(13, 'nest', 5, 5, 0, 0),
(14, 'drink', 1, 5, 0, 0),
(15, 'follow left', 15, 15, 4, 10),
(16, 'follow right', 15, 15, 4, 10),
(17, 'play football', 10, 5, 5, 12),
(18, 'teleport', 9, 5, 3, 5),
(19, 'bounce', 9, 5, 5, 10),
(20, 'flatten', 11, 5, 3, 4),
(21, 'dance', 12, 10, 6, 12),
(22, 'spin', 10, 5, 4, 8),
(23, 'switch', 12, 5, 3, 3),
(24, 'move forward', 17, 5, 2, 2),
(25, 'turn left', 18, 5, 2, 2),
(26, 'turn right', 18, 5, 2, 2),
(27, 'relax', 13, 5, 0, 0),
(28, 'croak', 14, 5, 3, 3),
(29, 'dip', 14, 5, 5, 10),
(30, 'wave', 5, 5, 2, 3),
(31, 'mambo', 18, 5, 6, 12),
(32, 'high jump', 18, 5, 5, 12),
(33, 'chicken dance', 7, 5, 5, 10),
(34, 'triple jump', 9, 5, 6, 15),
(35, 'spread wings', 8, 5, 4, 6),
(36, 'breathe fire', 10, 5, 5, 8),
(37, 'hang', 12, 5, 4, 6),
(38, 'torch', 6, 5, 3, 5),
(40, 'swing', 13, 5, 4, 8),
(41, 'roll', 10, 5, 5, 10),
(42, 'ring of fire', 20, 10, 8, 15),
(43, 'eat', 1, 5, 0, 0),
(44, 'wag tail', 4, 5, 3, 4),
(45, 'count', 6, 5, 4, 5),
(46, 'breed', 1, 5, 10, 20);
-- =====================================================
-- SECTION 4: Pet Commands (Pet Type -> Command Mapping)
-- =====================================================
DROP TABLE IF EXISTS `pet_commands`;
CREATE TABLE `pet_commands` (
`pet_id` int(11) NOT NULL,
`command_id` int(11) NOT NULL,
PRIMARY KEY (`pet_id`, `command_id`),
KEY `pet_id` (`pet_id`),
KEY `command_id` (`command_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Dog (0) - Full standard pet commands
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (0, 9),
(0, 10), (0, 11), (0, 12), (0, 13), (0, 14), (0, 15), (0, 16), (0, 17), (0, 24),
(0, 25), (0, 26), (0, 43), (0, 44), (0, 46);
-- Cat (1) - Full standard pet commands + breed
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9),
(1, 10), (1, 11), (1, 12), (1, 13), (1, 14), (1, 15), (1, 16), (1, 17), (1, 24),
(1, 25), (1, 26), (1, 43), (1, 46);
-- Crocodile (2) - Standard commands (can swim)
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(2, 0), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (2, 6), (2, 7), (2, 8), (2, 9),
(2, 10), (2, 11), (2, 12), (2, 13), (2, 14), (2, 15), (2, 16), (2, 17), (2, 24),
(2, 25), (2, 26), (2, 29), (2, 43);
-- Terrier (3) - Standard commands + breed
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(3, 0), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5), (3, 6), (3, 7), (3, 8), (3, 9),
(3, 10), (3, 11), (3, 12), (3, 13), (3, 14), (3, 15), (3, 16), (3, 17), (3, 24),
(3, 25), (3, 26), (3, 43), (3, 46);
-- Bear (4) - Standard commands + breed
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(4, 0), (4, 1), (4, 2), (4, 3), (4, 4), (4, 5), (4, 6), (4, 7), (4, 8), (4, 9),
(4, 10), (4, 11), (4, 12), (4, 13), (4, 14), (4, 15), (4, 16), (4, 17), (4, 24),
(4, 25), (4, 26), (4, 43), (4, 46);
-- Pig (5) - Standard commands + breed
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(5, 0), (5, 1), (5, 2), (5, 3), (5, 4), (5, 5), (5, 6), (5, 7), (5, 8), (5, 9),
(5, 10), (5, 11), (5, 12), (5, 13), (5, 14), (5, 15), (5, 16), (5, 17), (5, 24),
(5, 25), (5, 26), (5, 43), (5, 46);
-- Lion (6) - Standard commands
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(6, 0), (6, 1), (6, 2), (6, 3), (6, 4), (6, 5), (6, 6), (6, 7), (6, 8), (6, 9),
(6, 10), (6, 11), (6, 12), (6, 13), (6, 14), (6, 15), (6, 16), (6, 17), (6, 24),
(6, 25), (6, 26), (6, 43);
-- Rhino (7) - Standard commands
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(7, 0), (7, 1), (7, 2), (7, 3), (7, 4), (7, 5), (7, 6), (7, 7), (7, 8), (7, 9),
(7, 10), (7, 11), (7, 12), (7, 13), (7, 14), (7, 15), (7, 16), (7, 17), (7, 24),
(7, 25), (7, 26), (7, 43);
-- Tarantula (8) - Spider commands (bounce, flatten, spin, etc.)
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(8, 0), (8, 2), (8, 3), (8, 5), (8, 6), (8, 7), (8, 9), (8, 10), (8, 11), (8, 13),
(8, 14), (8, 15), (8, 16), (8, 17), (8, 19), (8, 20), (8, 21), (8, 22), (8, 23),
(8, 24), (8, 25), (8, 26), (8, 43);
-- Turtle (9) - Aquatic commands
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(9, 0), (9, 1), (9, 2), (9, 3), (9, 6), (9, 7), (9, 8), (9, 10), (9, 11), (9, 13),
(9, 14), (9, 15), (9, 16), (9, 24), (9, 25), (9, 26), (9, 29), (9, 41), (9, 43);
-- Chick (10) - Bird commands
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(10, 0), (10, 2), (10, 3), (10, 6), (10, 7), (10, 11), (10, 13), (10, 15), (10, 16),
(10, 17), (10, 33);
-- Frog (11) - Amphibian commands (croak, dip, wave, mambo)
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(11, 0), (11, 1), (11, 2), (11, 3), (11, 4), (11, 5), (11, 6), (11, 7), (11, 9),
(11, 13), (11, 14), (11, 15), (11, 16), (11, 17), (11, 27), (11, 28), (11, 29),
(11, 30), (11, 31), (11, 43);
-- Dragon (12) - Dragon special commands (fire, hang, swing, ring of fire)
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(12, 0), (12, 2), (12, 3), (12, 5), (12, 6), (12, 7), (12, 8), (12, 9), (12, 10),
(12, 11), (12, 12), (12, 13), (12, 14), (12, 15), (12, 16), (12, 22), (12, 35),
(12, 36), (12, 37), (12, 38), (12, 40), (12, 41), (12, 42), (12, 43);
-- Monster (13) - Basic commands
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(13, 0), (13, 2), (13, 3), (13, 6), (13, 7), (13, 13);
-- Monkey (14) - Monkey commands (wave, hang, swing)
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(14, 0), (14, 1), (14, 2), (14, 3), (14, 4), (14, 5), (14, 6), (14, 7), (14, 9),
(14, 13), (14, 14), (14, 15), (14, 16), (14, 17), (14, 27), (14, 29), (14, 30),
(14, 31), (14, 37), (14, 40), (14, 43);
-- Horse (15) - Rideable pet commands + wag tail, count
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(15, 0), (15, 2), (15, 3), (15, 6), (15, 7), (15, 10), (15, 11), (15, 12), (15, 13),
(15, 14), (15, 15), (15, 16), (15, 24), (15, 25), (15, 26), (15, 43), (15, 44), (15, 45);
-- Monsterplant (16) - Minimal commands
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(16, 0), (16, 14), (16, 43);
-- Bunnies (17-20) - Bunny commands
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(17, 0), (17, 2), (17, 3), (17, 6), (17, 7), (17, 11), (17, 13), (17, 15), (17, 16), (17, 17),
(18, 0), (18, 2), (18, 3), (18, 6), (18, 7), (18, 11), (18, 13), (18, 15), (18, 16), (18, 17),
(19, 0), (19, 2), (19, 3), (19, 6), (19, 7), (19, 11), (19, 13), (19, 15), (19, 16), (19, 17),
(20, 0), (20, 2), (20, 3), (20, 6), (20, 7), (20, 11), (20, 13), (20, 15), (20, 16), (20, 17);
-- Pigeons (21-22)
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(21, 0), (21, 2), (21, 3), (21, 6), (21, 7), (21, 11), (21, 13), (21, 15), (21, 16), (21, 17),
(22, 0), (22, 2), (22, 3), (22, 6), (22, 7), (22, 11), (22, 13), (22, 15), (22, 16), (22, 17);
-- Evil Monkey (23) - Monkey commands
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(23, 0), (23, 1), (23, 2), (23, 3), (23, 4), (23, 5), (23, 6), (23, 7), (23, 9),
(23, 13), (23, 14), (23, 15), (23, 16), (23, 17), (23, 25), (23, 26), (23, 27),
(23, 29), (23, 30), (23, 31), (23, 37), (23, 40), (23, 43);
-- Baby Bear (24)
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(24, 0), (24, 1), (24, 2), (24, 3), (24, 4), (24, 6), (24, 7), (24, 8), (24, 10),
(24, 11), (24, 12), (24, 13), (24, 14), (24, 15), (24, 16), (24, 17), (24, 24),
(24, 25), (24, 26), (24, 43);
-- Baby Terrier (25)
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(25, 0), (25, 1), (25, 2), (25, 3), (25, 4), (25, 6), (25, 7), (25, 8), (25, 10),
(25, 11), (25, 12), (25, 13), (25, 14), (25, 15), (25, 16), (25, 17), (25, 24),
(25, 25), (25, 26), (25, 43);
-- Gnome (26)
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(26, 0), (26, 1), (26, 2), (26, 3), (26, 4), (26, 6), (26, 7), (26, 8), (26, 13),
(26, 14), (26, 15), (26, 16), (26, 17), (26, 25), (26, 26), (26, 43);
-- Leprechaun (27)
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(27, 0), (27, 1), (27, 2), (27, 3), (27, 4), (27, 6), (27, 7), (27, 8), (27, 13),
(27, 14), (27, 15), (27, 16), (27, 17), (27, 25), (27, 26), (27, 43);
-- Baby Cat (28)
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(28, 0), (28, 1), (28, 2), (28, 3), (28, 4), (28, 6), (28, 7), (28, 8), (28, 10),
(28, 11), (28, 12), (28, 13), (28, 14), (28, 15), (28, 16), (28, 17), (28, 24),
(28, 25), (28, 26), (28, 43);
-- Baby Dog (29)
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(29, 0), (29, 1), (29, 2), (29, 3), (29, 4), (29, 6), (29, 7), (29, 8), (29, 10),
(29, 11), (29, 12), (29, 13), (29, 14), (29, 15), (29, 16), (29, 17), (29, 24),
(29, 25), (29, 26), (29, 43);
-- Baby Pig (30)
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(30, 0), (30, 1), (30, 2), (30, 3), (30, 4), (30, 6), (30, 7), (30, 8), (30, 10),
(30, 11), (30, 12), (30, 13), (30, 14), (30, 15), (30, 16), (30, 17), (30, 24),
(30, 25), (30, 26), (30, 43);
-- Haloompa (31)
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(31, 0), (31, 1), (31, 2), (31, 3), (31, 4), (31, 6), (31, 7), (31, 8), (31, 13),
(31, 14), (31, 15), (31, 16), (31, 17), (31, 25), (31, 26), (31, 43);
-- Fools Pet (32) - Full dance/trick set
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(32, 0), (32, 1), (32, 2), (32, 3), (32, 4), (32, 5), (32, 6), (32, 7), (32, 8),
(32, 9), (32, 13), (32, 14), (32, 15), (32, 16), (32, 17), (32, 21), (32, 25),
(32, 26), (32, 43);
-- Pterodactyl (33)
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(33, 0), (33, 2), (33, 3), (33, 4), (33, 6), (33, 7), (33, 11), (33, 13), (33, 14),
(33, 15), (33, 16), (33, 25), (33, 26), (33, 43);
-- Velociraptor (34)
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(34, 0), (34, 1), (34, 2), (34, 3), (34, 6), (34, 7), (34, 8), (34, 10), (34, 12),
(34, 13), (34, 14), (34, 15), (34, 16), (34, 17), (34, 21), (34, 26), (34, 43);
-- Cow (35)
INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES
(35, 0), (35, 2), (35, 3), (35, 4), (35, 6), (35, 7), (35, 13), (35, 14), (35, 15),
(35, 16), (35, 17), (35, 25), (35, 26), (35, 30), (35, 43);
-- =====================================================
-- SECTION 5: Pet Vocals (Pet Speech Messages)
-- =====================================================
-- pet_id = -1 means general vocals for all pets
-- pet_id >= 0 means specific to that pet type
CREATE TABLE IF NOT EXISTS `pet_vocals` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`pet_id` int(11) NOT NULL DEFAULT -1,
`type` varchar(20) NOT NULL,
`message` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `pet_id` (`pet_id`),
KEY `type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Clear existing vocals
DELETE FROM `pet_vocals`;
-- =====================================================
-- GENERAL VOCALS (pet_id = -1, used by all pets)
-- =====================================================
-- GREET_OWNER - When owner enters room
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(-1, 'GREET_OWNER', '*perks up excitedly*'),
(-1, 'GREET_OWNER', 'You''re back!'),
(-1, 'GREET_OWNER', '*bounces with joy*'),
(-1, 'GREET_OWNER', 'I missed you!'),
(-1, 'GREET_OWNER', '*runs in circles happily*'),
(-1, 'GREET_OWNER', 'Yay! My favorite person!'),
(-1, 'GREET_OWNER', '*jumps up and down*'),
(-1, 'GREET_OWNER', 'Finally! You''re here!'),
(-1, 'GREET_OWNER', '*tail wagging intensifies*');
-- LEVEL_UP - When pet gains a level
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(-1, 'LEVEL_UP', '*jumps with joy!*'),
(-1, 'LEVEL_UP', 'I leveled up!'),
(-1, 'LEVEL_UP', 'I feel stronger!'),
(-1, 'LEVEL_UP', 'Woohoo! New level!'),
(-1, 'LEVEL_UP', '*celebrates*'),
(-1, 'LEVEL_UP', 'I''m getting better!'),
(-1, 'LEVEL_UP', 'Level up! Yeah!');
-- MUTED - When told to be silent
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(-1, 'MUTED', '*stays quiet*'),
(-1, 'MUTED', '...'),
(-1, 'MUTED', '*zips lips*'),
(-1, 'MUTED', '*nods silently*');
-- UNKNOWN_COMMAND - When pet doesn't understand
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(-1, 'UNKNOWN_COMMAND', '*tilts head confused*'),
(-1, 'UNKNOWN_COMMAND', 'Huh?'),
(-1, 'UNKNOWN_COMMAND', 'I don''t understand...'),
(-1, 'UNKNOWN_COMMAND', '*looks puzzled*'),
(-1, 'UNKNOWN_COMMAND', 'What do you mean?'),
(-1, 'UNKNOWN_COMMAND', '*scratches head*');
-- DISOBEY - When pet refuses command
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(-1, 'DISOBEY', '*ignores command*'),
(-1, 'DISOBEY', 'Maybe later...'),
(-1, 'DISOBEY', 'I don''t feel like it'),
(-1, 'DISOBEY', '*pretends not to hear*'),
(-1, 'DISOBEY', 'Nah...'),
(-1, 'DISOBEY', '*turns away*'),
(-1, 'DISOBEY', 'Not right now'),
(-1, 'DISOBEY', '*yawns dismissively*'),
(-1, 'DISOBEY', 'Too tired for that'),
(-1, 'DISOBEY', 'Ask me again later'),
(-1, 'DISOBEY', '*looks the other way*'),
(-1, 'DISOBEY', 'I''d rather not'),
(-1, 'DISOBEY', '*shakes head*'),
(-1, 'DISOBEY', 'No thanks'),
(-1, 'DISOBEY', '*walks away slowly*'),
(-1, 'DISOBEY', 'Can''t be bothered'),
(-1, 'DISOBEY', '*pretends to be asleep*'),
(-1, 'DISOBEY', 'You can''t make me!'),
(-1, 'DISOBEY', '*stubbornly sits down*'),
(-1, 'DISOBEY', 'I refuse!');
-- DRINKING - When drinking water
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(-1, 'DRINKING', '*laps up water*'),
(-1, 'DRINKING', 'Refreshing!'),
(-1, 'DRINKING', '*gulp gulp*'),
(-1, 'DRINKING', 'Ah, that''s good!'),
(-1, 'DRINKING', '*slurp*');
-- EATING - When eating food
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(-1, 'EATING', '*munches happily*'),
(-1, 'EATING', 'Yum!'),
(-1, 'EATING', 'Delicious!'),
(-1, 'EATING', '*nom nom nom*'),
(-1, 'EATING', 'This is tasty!'),
(-1, 'EATING', '*chomps*'),
(-1, 'EATING', 'More please!');
-- PLAYFUL - When in playful mood
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(-1, 'PLAYFUL', '*bounces excitedly*'),
(-1, 'PLAYFUL', 'Let''s play!'),
(-1, 'PLAYFUL', '*runs around happily*'),
(-1, 'PLAYFUL', 'Play with me!'),
(-1, 'PLAYFUL', '*jumps around*'),
(-1, 'PLAYFUL', 'I wanna play!'),
(-1, 'PLAYFUL', '*brings a toy*'),
(-1, 'PLAYFUL', 'Wheee!'),
(-1, 'PLAYFUL', '*zooms around the room*'),
(-1, 'PLAYFUL', 'Catch me if you can!');
-- SLEEPING - When sleeping
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(-1, 'SLEEPING', '*snores softly*'),
(-1, 'SLEEPING', 'Zzz...'),
(-1, 'SLEEPING', '*mumbles in sleep*'),
(-1, 'SLEEPING', 'ZzZzZz...'),
(-1, 'SLEEPING', '*dreams peacefully*'),
(-1, 'SLEEPING', '*twitches while dreaming*'),
(-1, 'SLEEPING', '*snoozes*'),
(-1, 'SLEEPING', '*breathes slowly*');
-- TIRED - When tired/low energy
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(-1, 'TIRED', '*yawns*'),
(-1, 'TIRED', 'So sleepy...'),
(-1, 'TIRED', '*eyes drooping*'),
(-1, 'TIRED', 'I need rest...');
-- THIRSTY - When thirsty
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(-1, 'THIRSTY', '*pants*'),
(-1, 'THIRSTY', 'Water please!'),
(-1, 'THIRSTY', '*looks at water bowl*'),
(-1, 'THIRSTY', 'So thirsty...'),
(-1, 'THIRSTY', '*dry tongue*'),
(-1, 'THIRSTY', 'Need a drink!'),
(-1, 'THIRSTY', '*licks lips*'),
(-1, 'THIRSTY', 'I''m parched!');
-- HUNGRY - When hungry
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(-1, 'HUNGRY', '*stomach growls*'),
(-1, 'HUNGRY', 'I need food!'),
(-1, 'HUNGRY', '*looks at food bowl*'),
(-1, 'HUNGRY', 'Feed me!'),
(-1, 'HUNGRY', 'So hungry...'),
(-1, 'HUNGRY', '*tummy rumbles*'),
(-1, 'HUNGRY', 'Food please!'),
(-1, 'HUNGRY', '*drools at thought of food*'),
(-1, 'HUNGRY', 'Is it dinner time?'),
(-1, 'HUNGRY', '*sniffs around for food*'),
(-1, 'HUNGRY', 'I could eat a horse!'),
(-1, 'HUNGRY', '*begs for food*'),
(-1, 'HUNGRY', 'Starving over here!');
-- GENERIC_NEUTRAL - Random idle chat
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(-1, 'GENERIC_NEUTRAL', '*looks around*'),
(-1, 'GENERIC_NEUTRAL', '*sniffs the air*'),
(-1, 'GENERIC_NEUTRAL', '*stretches*'),
(-1, 'GENERIC_NEUTRAL', '*scratches ear*'),
(-1, 'GENERIC_NEUTRAL', '*observes surroundings*'),
(-1, 'GENERIC_NEUTRAL', '*sits quietly*'),
(-1, 'GENERIC_NEUTRAL', '*watches curiously*');
-- GENERIC_SAD - When sad/low happiness
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(-1, 'GENERIC_SAD', '*whimpers*'),
(-1, 'GENERIC_SAD', '*looks sad*'),
(-1, 'GENERIC_SAD', '*sighs*'),
(-1, 'GENERIC_SAD', '*droops head*'),
(-1, 'GENERIC_SAD', 'I''m lonely...'),
(-1, 'GENERIC_SAD', '*mopes around*'),
(-1, 'GENERIC_SAD', '*looks dejected*'),
(-1, 'GENERIC_SAD', 'Nobody loves me...'),
(-1, 'GENERIC_SAD', '*sulks in corner*');
-- GENERIC_HAPPY - When happy
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(-1, 'GENERIC_HAPPY', '*wags tail happily*'),
(-1, 'GENERIC_HAPPY', '*jumps with joy*'),
(-1, 'GENERIC_HAPPY', ':)'),
(-1, 'GENERIC_HAPPY', 'Life is good!'),
(-1, 'GENERIC_HAPPY', '*prances around*'),
(-1, 'GENERIC_HAPPY', '*does a happy dance*'),
(-1, 'GENERIC_HAPPY', 'I''m so happy!'),
(-1, 'GENERIC_HAPPY', '*beams with joy*'),
(-1, 'GENERIC_HAPPY', 'What a great day!'),
(-1, 'GENERIC_HAPPY', '*grins*'),
(-1, 'GENERIC_HAPPY', '*radiates happiness*'),
(-1, 'GENERIC_HAPPY', 'Yippee!'),
(-1, 'GENERIC_HAPPY', '*spins around happily*'),
(-1, 'GENERIC_HAPPY', 'This is the best!');
-- =====================================================
-- PET-SPECIFIC VOCALS
-- =====================================================
-- Dog (0) specific vocals
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(0, 'GENERIC_HAPPY', 'Woof woof!'),
(0, 'GENERIC_HAPPY', '*wags tail furiously*'),
(0, 'GREET_OWNER', '*barks excitedly*'),
(0, 'GREET_OWNER', 'Woof! You''re home!'),
(0, 'PLAYFUL', '*drops ball at your feet*'),
(0, 'PLAYFUL', 'Throw the ball!'),
(0, 'HUNGRY', '*stares at food bowl*'),
(0, 'DISOBEY', '*chases own tail instead*');
-- Cat (1) specific vocals
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(1, 'GENERIC_HAPPY', '*purrs*'),
(1, 'GENERIC_HAPPY', 'Meow!'),
(1, 'GENERIC_NEUTRAL', '*grooms self*'),
(1, 'GREET_OWNER', '*rubs against leg*'),
(1, 'DISOBEY', '*looks away disdainfully*'),
(1, 'DISOBEY', '*yawns dismissively*'),
(1, 'PLAYFUL', '*pounces on shadow*'),
(1, 'SLEEPING', '*purrs while sleeping*');
-- Dragon (12) specific vocals
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(12, 'GENERIC_HAPPY', '*breathes small flames happily*'),
(12, 'GENERIC_HAPPY', '*roars softly*'),
(12, 'GENERIC_SAD', '*smoke puffs from nostrils*'),
(12, 'DISOBEY', '*snorts flames*'),
(12, 'DISOBEY', 'I am a DRAGON, not a servant!'),
(12, 'HUNGRY', '*eyes the nearest villager*'),
(12, 'HUNGRY', 'I require sustenance!'),
(12, 'THIRSTY', '*smoke rises as throat dries*'),
(12, 'PLAYFUL', '*chases own tail, breathing fire*'),
(12, 'GREET_OWNER', '*bows majestic head*'),
(12, 'SLEEPING', '*snores, causing small fires*'),
(12, 'LEVEL_UP', '*ROARS triumphantly!*');
-- Horse (15) specific vocals
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(15, 'GENERIC_HAPPY', '*neighs happily*'),
(15, 'GENERIC_NEUTRAL', '*swishes tail*'),
(15, 'GREET_OWNER', '*whinnies in greeting*'),
(15, 'DISOBEY', 'Nay. (Geddit?)'),
(15, 'HUNGRY', '*looks at hay expectantly*'),
(15, 'PLAYFUL', 'Let''s go for a ride!'),
(15, 'TIRED', '*stamps hoof wearily*');
-- Tarantula (8) specific vocals
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(8, 'GREET_OWNER', 'You look more edible every time!'),
(8, 'DISOBEY', '*hisses*'),
(8, 'DISOBEY', 'I do not obey mammals'),
(8, 'HUNGRY', 'Bring me fresh meat!'),
(8, 'PLAYFUL', '*dances on eight legs*'),
(8, 'GENERIC_HAPPY', '*clicks mandibles happily*');
-- Frog (11) specific vocals
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(11, 'GENERIC_HAPPY', 'Ribbit!'),
(11, 'GENERIC_NEUTRAL', '*croaks*'),
(11, 'GREET_OWNER', '*hops excitedly*'),
(11, 'PLAYFUL', '*catches fly with tongue*'),
(11, 'THIRSTY', '*seeks water*');
-- Cow (35) specific vocals
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(35, 'GENERIC_HAPPY', 'Moooo!'),
(35, 'GREET_OWNER', 'Greetings. Did you bring kale?'),
(35, 'EATING', '*chews grass thoughtfully*'),
(35, 'DISOBEY', 'I''d rather meditate'),
(35, 'LEVEL_UP', '*DING* I''m on the up!');
-- Lion (6) specific vocals
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(6, 'GENERIC_HAPPY', '*roars majestically*'),
(6, 'GREET_OWNER', '*nods regally*'),
(6, 'DISOBEY', 'I am the king!'),
(6, 'HUNGRY', '*eyes prey*'),
(6, 'PLAYFUL', '*pounces playfully*');
-- Bear (4) specific vocals
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(4, 'GENERIC_HAPPY', '*growls contentedly*'),
(4, 'HUNGRY', '*sniffs for honey*'),
(4, 'SLEEPING', '*hibernates*'),
(4, 'GREET_OWNER', '*bear hug incoming*');
-- Monkey (14) specific vocals
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(14, 'GENERIC_HAPPY', 'Ook ook!'),
(14, 'PLAYFUL', '*swings from furniture*'),
(14, 'GREET_OWNER', '*does a backflip*'),
(14, 'DISOBEY', '*throws something*'),
(14, 'HUNGRY', '*looks for bananas*');
-- Bunny (17) specific vocals
INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES
(17, 'GENERIC_HAPPY', '*hops happily*'),
(17, 'GREET_OWNER', '*twitches nose excitedly*'),
(17, 'PLAYFUL', '*binkies around*'),
(17, 'HUNGRY', '*nibbles on carrot*');
SET FOREIGN_KEY_CHECKS = 1;
@@ -1,18 +0,0 @@
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- =====================================================
-- Update 4.0.2-beta to 4.0.3-beta
-- =====================================================
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
-- Maximum pending flood-fill tasks in the executor queue
-- Prevents memory leaks from rapid tile locking
('hotel.banzai.fill.max_queue', '50'),
-- Minimum interval (ms) between flood-fill calculations per game
-- Prevents errors via rapid wired triggering
('hotel.banzai.fill.cooldown_ms', '100')
ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);
SET FOREIGN_KEY_CHECKS = 1;
@@ -1,216 +0,0 @@
-- =====================================================
-- Pet Breeding Complete Setup
-- =====================================================
-- This file sets up all breeding-related data:
-- 1. pet_breeding - Maps parent pet types to offspring types
-- 2. pet_breeding_races - Defines possible breeds/colors for offspring by rarity
-- =====================================================
-- =====================================================
-- SECTION 1: Pet Breeding (Parent -> Offspring Mapping)
-- =====================================================
-- This table maps which pet type produces which baby type
CREATE TABLE IF NOT EXISTS `pet_breeding` (
`pet_id` int(11) NOT NULL COMMENT 'Parent pet type',
`offspring_id` int(11) NOT NULL COMMENT 'Baby pet type',
PRIMARY KEY (`pet_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Clear existing data
TRUNCATE TABLE `pet_breeding`;
-- Insert breeding mappings
INSERT INTO `pet_breeding` (`pet_id`, `offspring_id`) VALUES
(0, 29), -- Dog -> Baby Dog
(1, 28), -- Cat -> Baby Cat
(3, 25), -- Terrier -> Baby Terrier
(4, 24), -- Bear -> Baby Bear
(5, 30); -- Pig -> Baby Pig
-- =====================================================
-- SECTION 2: Pet Breeding Races (Offspring Breeds by Rarity)
-- =====================================================
-- rarity_level: 1=Common, 2=Uncommon, 3=Rare, 4=Epic
-- breed: The visual breed/color variant of the baby pet
--
-- Higher rarity = harder to get, more special colors
-- Each baby pet type should have breeds at all 4 rarity levels
CREATE TABLE IF NOT EXISTS `pet_breeding_races` (
`pet_id` int(11) NOT NULL COMMENT 'Baby pet type (offspring)',
`rarity_level` int(11) NOT NULL COMMENT '1=Common, 2=Uncommon, 3=Rare, 4=Epic',
`breed` int(11) NOT NULL COMMENT 'Visual breed/color variant',
PRIMARY KEY (`pet_id`, `rarity_level`, `breed`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Clear existing data
TRUNCATE TABLE `pet_breeding_races`;
-- =====================================================
-- Baby Dog (29) - Offspring of Dog (0) - 20 breeds
-- =====================================================
INSERT INTO `pet_breeding_races` (`pet_type`, `rarity_level`, `breed`) VALUES
-- Common breeds (rarity 1) - Most likely to get
(29, 1, 0),
(29, 1, 1),
(29, 1, 2),
(29, 1, 3),
(29, 1, 4),
(29, 1, 5),
(29, 1, 6),
(29, 1, 7),
-- Uncommon breeds (rarity 2)
(29, 2, 8),
(29, 2, 9),
(29, 2, 10),
(29, 2, 11),
(29, 2, 12),
-- Rare breeds (rarity 3)
(29, 3, 13),
(29, 3, 14),
(29, 3, 15),
(29, 3, 16),
-- Epic breeds (rarity 4) - Hardest to get
(29, 4, 17),
(29, 4, 18),
(29, 4, 19);
-- =====================================================
-- Baby Cat (28) - Offspring of Cat (1) - 20 breeds
-- =====================================================
INSERT INTO `pet_breeding_races` (`pet_type`, `rarity_level`, `breed`) VALUES
-- Common breeds (rarity 1)
(28, 1, 0),
(28, 1, 1),
(28, 1, 2),
(28, 1, 3),
(28, 1, 4),
(28, 1, 5),
(28, 1, 6),
(28, 1, 7),
-- Uncommon breeds (rarity 2)
(28, 2, 8),
(28, 2, 9),
(28, 2, 10),
(28, 2, 11),
(28, 2, 12),
-- Rare breeds (rarity 3)
(28, 3, 13),
(28, 3, 14),
(28, 3, 15),
(28, 3, 16),
-- Epic breeds (rarity 4)
(28, 4, 17),
(28, 4, 18),
(28, 4, 19);
-- =====================================================
-- Baby Terrier (25) - Offspring of Terrier (3) - 20 breeds
-- =====================================================
INSERT INTO `pet_breeding_races` (`pet_type`, `rarity_level`, `breed`) VALUES
-- Common breeds (rarity 1)
(25, 1, 0),
(25, 1, 1),
(25, 1, 2),
(25, 1, 3),
(25, 1, 4),
(25, 1, 5),
(25, 1, 6),
(25, 1, 7),
-- Uncommon breeds (rarity 2)
(25, 2, 8),
(25, 2, 9),
(25, 2, 10),
(25, 2, 11),
(25, 2, 12),
-- Rare breeds (rarity 3)
(25, 3, 13),
(25, 3, 14),
(25, 3, 15),
(25, 3, 16),
-- Epic breeds (rarity 4)
(25, 4, 17),
(25, 4, 18),
(25, 4, 19);
-- =====================================================
-- Baby Bear (24) - Offspring of Bear (4) - 20 breeds
-- =====================================================
INSERT INTO `pet_breeding_races` (`pet_type`, `rarity_level`, `breed`) VALUES
-- Common breeds (rarity 1)
(24, 1, 0),
(24, 1, 1),
(24, 1, 2),
(24, 1, 3),
(24, 1, 4),
(24, 1, 5),
(24, 1, 6),
(24, 1, 7),
-- Uncommon breeds (rarity 2)
(24, 2, 8),
(24, 2, 9),
(24, 2, 10),
(24, 2, 11),
(24, 2, 12),
-- Rare breeds (rarity 3)
(24, 3, 13),
(24, 3, 14),
(24, 3, 15),
(24, 3, 16),
-- Epic breeds (rarity 4)
(24, 4, 17),
(24, 4, 18),
(24, 4, 19);
-- =====================================================
-- Baby Pig (30) - Offspring of Pig (5) - 20 breeds
-- =====================================================
INSERT INTO `pet_breeding_races` (`pet_type`, `rarity_level`, `breed`) VALUES
-- Common breeds (rarity 1)
(30, 1, 0),
(30, 1, 1),
(30, 1, 2),
(30, 1, 3),
(30, 1, 4),
(30, 1, 5),
(30, 1, 6),
(30, 1, 7),
-- Uncommon breeds (rarity 2)
(30, 2, 8),
(30, 2, 9),
(30, 2, 10),
(30, 2, 11),
(30, 2, 12),
-- Rare breeds (rarity 3)
(30, 3, 13),
(30, 3, 14),
(30, 3, 15),
(30, 3, 16),
-- Epic breeds (rarity 4)
(30, 4, 17),
(30, 4, 18),
(30, 4, 19);
-- =====================================================
-- Also ensure pet_actions has correct offspring_type values
-- =====================================================
UPDATE `pet_actions` SET `offspring_type` = 29 WHERE `pet_type` = 0; -- Dog -> Baby Dog
UPDATE `pet_actions` SET `offspring_type` = 28 WHERE `pet_type` = 1; -- Cat -> Baby Cat
UPDATE `pet_actions` SET `offspring_type` = 25 WHERE `pet_type` = 3; -- Terrier -> Baby Terrier
UPDATE `pet_actions` SET `offspring_type` = 24 WHERE `pet_type` = 4; -- Bear -> Baby Bear
UPDATE `pet_actions` SET `offspring_type` = 30 WHERE `pet_type` = 5; -- Pig -> Baby Pig
-- Set non-breedable pets to -1
UPDATE `pet_actions` SET `offspring_type` = -1 WHERE `pet_type` NOT IN (0, 1, 3, 4, 5);
-- =====================================================
-- Fix any items_base with leading/trailing spaces in interaction_type
-- =====================================================
UPDATE `items_base` SET `interaction_type` = TRIM(`interaction_type`);
-- =====================================================
-- Ensure breeding nest items have correct interaction_type
-- =====================================================
UPDATE `items_base` SET `interaction_type` = 'breeding_nest'
WHERE `item_name` LIKE 'pet_breeding_%' AND `interaction_type` != 'breeding_nest';
-15
View File
@@ -1,15 +0,0 @@
ALTER TABLE `permissions` ADD COLUMN `cmd_update_chat_bubbles` ENUM('0','1') NOT NULL DEFAULT '0';
--New table for custom chat bubbles
CREATE TABLE chat_bubbles (
type INT(11) PRIMARY KEY AUTO_INCREMENT COMMENT "Only 46 and higher will work",
name VARCHAR(255) NOT NULL DEFAULT '',
permission VARCHAR(255) NOT NULL DEFAULT '',
overridable BOOLEAN NOT NULL DEFAULT TRUE,
triggers_talking_furniture BOOLEAN NOT NULL DEFAULT FALSE
);
--New texts for update chat bubbles command
INSERT INTO `emulator_texts` (`key`, `value`) VALUES ('commands.keys.cmd_update_chat_bubbles', 'update_chat_bubbles');
INSERT INTO `emulator_texts` (`key`, `value`) VALUES ('commands.success.cmd_update_chat_bubbles', 'Successfully updated chat bubbles');
INSERT INTO `emulator_texts` (`key`, `value`) VALUES ('commands.description.cmd_update_chat_bubbles', ':update_chat_bubbles');
@@ -1,6 +0,0 @@
ALTER TABLE `permissions` ADD `cmd_update_all` ENUM('0','1') NOT NULL DEFAULT '0' AFTER `cmd_update_achievements`;
INSERT INTO `emulator_texts` (`key`, `value`) VALUES
('commands.keys.cmd_update_all', 'update_all'),
('commands.description.cmd_update_all', ':update_all'),
('commands.succes.cmd_update_all', 'Successfully updated everything!');
@@ -1 +0,0 @@
ALTER TABLE `rooms` ADD COLUMN `allow_underpass` ENUM('0','1') NOT NULL DEFAULT '0' AFTER `move_diagonally`;
-71
View File
@@ -1,71 +0,0 @@
-- ============================================================
-- Camera - Database Setup
-- Run this SQL manually before using the camera feature.
-- ============================================================
-- -----------------------------------------
-- Table: camera_web (stores published photos)
-- -----------------------------------------
CREATE TABLE IF NOT EXISTS `camera_web` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`room_id` INT(11) NOT NULL DEFAULT 0,
`timestamp` INT(11) NOT NULL DEFAULT 0,
`url` VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
INDEX `idx_camera_web_user_id` (`user_id`),
INDEX `idx_camera_web_timestamp` (`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- -----------------------------------------
-- Emulator Settings for Camera
-- -----------------------------------------
-- Uses INSERT IGNORE so existing values are not overwritten.
-- Base URL where camera photos are served (include trailing slash)
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
('camera.url', 'http://localhost/camera/');
-- Filesystem path where full-size camera photos are saved (include trailing slash)
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
('imager.location.output.camera', '/path/to/www/camera/');
-- Filesystem path where room thumbnail images are saved (include trailing slash)
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
('imager.location.output.thumbnail', '/path/to/www/thumbnails/');
-- Item ID for the wall photo item (must exist in items_base with interaction type "external_image")
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
('camera.item_id', '0');
-- Price in credits to purchase a photo as a wall item
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
('camera.price.credits', '2');
-- Price in seasonal points to purchase a photo as a wall item
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
('camera.price.points', '0');
-- Price in seasonal points to publish a photo to the web
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
('camera.price.points.publish', '1');
-- JSON template for photo item extradata
-- Available placeholders: %timestamp%, %room_id%, %url%, %id%
INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
('camera.extradata', '{"t":"%timestamp%","u":"%id%","m":"","s":"%room_id%","w":"%url%"}');
-- -----------------------------------------
-- Emulator Texts for Camera
-- -----------------------------------------
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
('camera.permission', 'You do not have permission to use the camera.');
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
('camera.wait', 'Please wait %seconds% more seconds before taking another photo.');
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
('camera.error.creation', 'An error occurred while processing your photo. Please try again.');
INSERT IGNORE INTO `emulator_texts` (`key`, `value`) VALUES
('camera.daily.limit', 'You have reached the daily photo limit. Try again tomorrow.');
@@ -0,0 +1,161 @@
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_time_more_than' WHERE `public_name` = 'wf_cnd_time_more_than';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_time_less_than' WHERE `public_name` = 'wf_cnd_time_less_than';
UPDATE `items_base` SET `interaction_type` = 'wf_act_give_reward' WHERE `public_name` = 'wf_act_give_reward';
UPDATE `items_base` SET `interaction_type` = 'wf_act_call_stacks' WHERE `public_name` = 'wf_act_call_stacks';
UPDATE `items_base` SET `interaction_type` = 'wf_act_neg_call_stack' WHERE `public_name` = 'wf_act_neg_call_stack';
UPDATE `items_base` SET `interaction_type` = 'wf_act_neg_call_stacks' WHERE `public_name` = 'wf_act_neg_call_stacks';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_maze';
UPDATE `items_base` SET `interaction_type` = 'wf_act_give_score_tm' WHERE `public_name` = 'wf_act_give_score_tm';
UPDATE `items_base` SET `interaction_type` = 'wf_act_move_to_dir' WHERE `public_name` = 'wf_act_move_to_dir';
UPDATE `items_base` SET `interaction_type` = 'wf_act_leave_team' WHERE `public_name` = 'wf_act_leave_team';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_actor_in_team' WHERE `public_name` = 'wf_cnd_actor_in_team';
UPDATE `items_base` SET `interaction_type` = 'wf_act_flee' WHERE `public_name` = 'wf_act_flee';
UPDATE `items_base` SET `interaction_type` = 'wf_act_join_team' WHERE `public_name` = 'wf_act_join_team';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_not_in_team' WHERE `public_name` = 'wf_cnd_not_in_team';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_not_furni_on' WHERE `public_name` = 'wf_cnd_not_furni_on';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_stuff_is' WHERE `public_name` = 'wf_cnd_stuff_is';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_not_stuff_is' WHERE `public_name` = 'wf_cnd_not_stuff_is';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_date_rng_active' WHERE `public_name` = 'wf_cnd_date_rng_active';
UPDATE `items_base` SET `interaction_type` = 'wf_act_bot_clothes' WHERE `public_name` = 'wf_act_bot_clothes';
UPDATE `items_base` SET `interaction_type` = 'wf_act_bot_teleport' WHERE `public_name` = 'wf_act_bot_teleport';
UPDATE `items_base` SET `interaction_type` = 'wf_act_bot_follow_avatar' WHERE `public_name` = 'wf_act_bot_follow_avatar';
UPDATE `items_base` SET `interaction_type` = 'wf_act_bot_give_handitem' WHERE `public_name` = 'wf_act_bot_give_handitem';
UPDATE `items_base` SET `interaction_type` = 'wf_act_bot_move' WHERE `public_name` = 'wf_act_bot_move';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_has_handitem' WHERE `public_name` = 'wf_cnd_has_handitem';
UPDATE `items_base` SET `interaction_type` = 'wf_act_bot_talk_to_avatar' WHERE `public_name` = 'wf_act_bot_talk_to_avatar';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_bot_reached_avtr' WHERE `public_name` = 'wf_trg_bot_reached_avtr';
UPDATE `items_base` SET `interaction_type` = 'wf_act_bot_talk' WHERE `public_name` = 'wf_act_bot_talk';
UPDATE `items_base` SET `interaction_type` = 'wf_act_move_rotate' WHERE `public_name` = 'wf_act_move_rotate';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_wire2';
UPDATE `items_base` SET `interaction_type` = 'switch' WHERE `public_name` = 'wf_floor_switch2';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_state_changed' WHERE `public_name` = 'wf_trg_state_changed';
UPDATE `items_base` SET `interaction_type` = 'wf_xtra_random' WHERE `public_name` = 'wf_xtra_random';
UPDATE `items_base` SET `interaction_type` = 'wf_xtra_unseen' WHERE `public_name` = 'wf_xtra_unseen';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_periodically' WHERE `public_name` = 'wf_trg_periodically';
UPDATE `items_base` SET `interaction_type` = 'pyramid' WHERE `public_name` = 'wf_pyramid';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_score_achieved' WHERE `public_name` = 'wf_trg_score_achieved';
UPDATE `items_base` SET `interaction_type` = 'wf_act_teleport_to' WHERE `public_name` = 'wf_act_teleport_to';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_says_something' WHERE `public_name` = 'wf_trg_says_something';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_wire4';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_walks_off_furni' WHERE `public_name` = 'wf_trg_walks_off_furni';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_at_given_time' WHERE `public_name` = 'wf_trg_at_given_time';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_game_ends' WHERE `public_name` = 'wf_trg_game_ends';
UPDATE `items_base` SET `interaction_type` = 'wf_act_show_message' WHERE `public_name` = 'wf_act_show_message';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_collision' WHERE `public_name` = 'wf_trg_collision';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_enter_room' WHERE `public_name` = 'wf_trg_enter_room';
UPDATE `items_base` SET `interaction_type` = 'wf_act_toggle_state' WHERE `public_name` = 'wf_act_toggle_state';
UPDATE `items_base` SET `interaction_type` = 'gate' WHERE `public_name` = 'wf_firegate';
UPDATE `items_base` SET `interaction_type` = 'pressureplate' WHERE `public_name` = 'wf_ringplate';
UPDATE `items_base` SET `interaction_type` = 'pressureplate' WHERE `public_name` = 'wf_pressureplate';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_glowball';
UPDATE `items_base` SET `interaction_type` = 'wf_act_reset_timers' WHERE `public_name` = 'wf_act_reset_timers';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_furnis_hv_avtrs' WHERE `public_name` = 'wf_cnd_furnis_hv_avtrs';
UPDATE `items_base` SET `interaction_type` = 'pressureplate' WHERE `public_name` = 'wf_arrowplate';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_trggrer_on_frn' WHERE `public_name` = 'wf_cnd_trggrer_on_frn';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_wire1';
UPDATE `items_base` SET `interaction_type` = 'wf_act_give_score' WHERE `public_name` = 'wf_act_give_score';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_wire3';
UPDATE `items_base` SET `interaction_type` = 'gate' WHERE `public_name` = 'wf_glassdoor';
UPDATE `items_base` SET `interaction_type` = 'wf_act_match_to_sshot' WHERE `public_name` = 'wf_act_match_to_sshot';
UPDATE `items_base` SET `interaction_type` = 'switch' WHERE `public_name` = 'wf_floor_switch1';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_game_starts' WHERE `public_name` = 'wf_trg_game_starts';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_walks_on_furni' WHERE `public_name` = 'wf_trg_walks_on_furni';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_actor_in_group' WHERE `public_name` = 'wf_cnd_actor_in_group';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_not_in_group' WHERE `public_name` = 'wf_cnd_not_in_group';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_not_trggrer_on' WHERE `public_name` = 'wf_cnd_not_trggrer_on';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_not_hv_avtrs' WHERE `public_name` = 'wf_cnd_not_hv_avtrs';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_user_count_in' WHERE `public_name` = 'wf_cnd_user_count_in';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_not_user_count' WHERE `public_name` = 'wf_cnd_not_user_count';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_wearing_effect' WHERE `public_name` = 'wf_cnd_wearing_effect';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_not_wearing_fx' WHERE `public_name` = 'wf_cnd_not_wearing_fx';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_wearing_badge' WHERE `public_name` = 'wf_cnd_wearing_badge';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_not_wearing_b' WHERE `public_name` = 'wf_cnd_not_wearing_b';
UPDATE `items_base` SET `interaction_type` = 'wf_act_kick_user' WHERE `public_name` = 'wf_act_kick_user';
UPDATE `items_base` SET `interaction_type` = 'wf_act_mute_triggerer' WHERE `public_name` = 'wf_act_mute_triggerer';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_match_snapshot' WHERE `public_name` = 'wf_cnd_match_snapshot';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_not_match_snap' WHERE `public_name` = 'wf_cnd_not_match_snap';
UPDATE `items_base` SET `interaction_type` = 'wf_blob' WHERE `public_name` = 'wf_blob';
UPDATE `items_base` SET `interaction_type` = 'wf_blob' WHERE `public_name` = 'wf_blob2';
UPDATE `items_base` SET `interaction_type` = 'puzzle_box' WHERE `public_name` = 'wf_box';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_has_furni_on' WHERE `public_name` = 'wf_cnd_has_furni_on';
UPDATE `items_base` SET `interaction_type` = 'wf_act_super_wired' WHERE `public_name` = 'wf_act_super_wired';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_super_wired' WHERE `public_name` = 'wf_cnd_super_wired';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_period_long' WHERE `public_name` = 'wf_trg_period_long';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_bot_reached_stf' WHERE `public_name` = 'wf_trg_bot_reached_stf';
UPDATE `items_base` SET `interaction_type` = 'wf_act_chase' WHERE `public_name` = 'wf_act_chase';
UPDATE `items_base` SET `interaction_type` = 'wf_act_move_furni_to' WHERE `public_name` = 'wf_act_move_furni_to';
UPDATE `items_base` SET `interaction_type` = 'wf_act_toggle_to_rnd' WHERE `public_name` = 'wf_act_toggle_to_rnd';
UPDATE `items_base` SET `interaction_type` = 'wf_blob' WHERE `public_name` = 'wf_blob2_vis';
UPDATE `items_base` SET `interaction_type` = 'wf_blob' WHERE `public_name` = 'wf_blob_invis';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_at_time_long' WHERE `public_name` = 'wf_trg_at_time_long';
UPDATE `items_base` SET `interaction_type` = 'wf_act_control_clock' WHERE `public_name` = 'wf_act_control_clock';
UPDATE `items_base` SET `interaction_type` = 'game_upcounter' WHERE `public_name` = 'wf_game_upcounter1';
UPDATE `items_base` SET `interaction_type` = 'game_upcounter' WHERE `public_name` = 'wf_game_upcounter2';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_clock_counter' WHERE `public_name` = 'wf_trg_clock_counter';
UPDATE `items_base` SET `interaction_type` = 'wf_xtra_or_eval' WHERE `public_name` = 'wf_xtra_or_eval';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_test_act';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_test_cnd';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_test_trg';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_test_xtra';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_counter_time_matches' WHERE `public_name` = 'wf_cnd_counter_time_matches';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_match_date' WHERE `public_name` = 'wf_cnd_match_date';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_match_time' WHERE `public_name` = 'wf_cnd_match_time';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_not_has_handitem' WHERE `public_name` = 'wf_cnd_not_has_handitem';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_not_triggerer_match' WHERE `public_name` = 'wf_cnd_not_triggerer_match';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_not_user_performs_action' WHERE `public_name` = 'wf_cnd_not_user_performs_action';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_team_has_rank' WHERE `public_name` = 'wf_cnd_team_has_rank';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_team_has_score' WHERE `public_name` = 'wf_cnd_team_has_score';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_triggerer_match' WHERE `public_name` = 'wf_cnd_triggerer_match';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_user_performs_action' WHERE `public_name` = 'wf_cnd_user_performs_action';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_user_performs_action' WHERE `public_name` = 'wf_trg_user_performs_action';
UPDATE `items_base` SET `interaction_type` = 'wf_act_freeze' WHERE `public_name` = 'wf_act_freeze';
UPDATE `items_base` SET `interaction_type` = 'wf_act_furni_to_furni' WHERE `public_name` = 'wf_act_furni_to_furni';
UPDATE `items_base` SET `interaction_type` = 'wf_act_furni_to_user' WHERE `public_name` = 'wf_act_furni_to_user';
UPDATE `items_base` SET `interaction_type` = 'wf_act_rel_mov' WHERE `public_name` = 'wf_act_rel_mov';
UPDATE `items_base` SET `interaction_type` = 'wf_act_send_signal' WHERE `public_name` = 'wf_act_send_signal';
UPDATE `items_base` SET `interaction_type` = 'wf_act_neg_send_signal' WHERE `public_name` = 'wf_act_neg_send_signal';
UPDATE `items_base` SET `interaction_type` = 'wf_act_set_altitude' WHERE `public_name` = 'wf_act_set_altitude';
UPDATE `items_base` SET `interaction_type` = 'wf_act_unfreeze' WHERE `public_name` = 'wf_act_unfreeze';
UPDATE `items_base` SET `interaction_type` = 'antenna' WHERE `public_name` = 'wf_antenna1';
UPDATE `items_base` SET `interaction_type` = 'antenna' WHERE `public_name` = 'wf_antenna2';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_actor_dir' WHERE `public_name` = 'wf_cnd_actor_dir';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_has_altitude' WHERE `public_name` = 'wf_cnd_has_altitude';
UPDATE `items_base` SET `interaction_type` = 'wf_cnd_slc_quantity' WHERE `public_name` = 'wf_cnd_slc_quantity';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_numbertile1';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_numbertile2';
UPDATE `items_base` SET `interaction_type` = 'wf_slc_furni_altitude' WHERE `public_name` = 'wf_slc_furni_altitude';
UPDATE `items_base` SET `interaction_type` = 'wf_slc_furni_area' WHERE `public_name` = 'wf_slc_furni_area';
UPDATE `items_base` SET `interaction_type` = 'wf_slc_furni_bytype' WHERE `public_name` = 'wf_slc_furni_bytype';
UPDATE `items_base` SET `interaction_type` = 'wf_slc_furni_neighborhood' WHERE `public_name` = 'wf_slc_furni_neighborhood';
UPDATE `items_base` SET `interaction_type` = 'wf_slc_furni_onfurni' WHERE `public_name` = 'wf_slc_furni_onfurni';
UPDATE `items_base` SET `interaction_type` = 'wf_slc_furni_picks' WHERE `public_name` = 'wf_slc_furni_picks';
UPDATE `items_base` SET `interaction_type` = 'wf_slc_furni_signal' WHERE `public_name` = 'wf_slc_furni_signal';
UPDATE `items_base` SET `interaction_type` = 'wf_slc_users_area' WHERE `public_name` = 'wf_slc_users_area';
UPDATE `items_base` SET `interaction_type` = 'wf_slc_users_byaction' WHERE `public_name` = 'wf_slc_users_byaction';
UPDATE `items_base` SET `interaction_type` = 'wf_slc_users_byname' WHERE `public_name` = 'wf_slc_users_byname';
UPDATE `items_base` SET `interaction_type` = 'wf_slc_users_bytype' WHERE `public_name` = 'wf_slc_users_bytype';
UPDATE `items_base` SET `interaction_type` = 'wf_slc_users_group' WHERE `public_name` = 'wf_slc_users_group';
UPDATE `items_base` SET `interaction_type` = 'wf_slc_users_handitem' WHERE `public_name` = 'wf_slc_users_handitem';
UPDATE `items_base` SET `interaction_type` = 'wf_slc_users_neighborhood' WHERE `public_name` = 'wf_slc_users_neighborhood';
UPDATE `items_base` SET `interaction_type` = 'wf_slc_users_onfurni' WHERE `public_name` = 'wf_slc_users_onfurni';
UPDATE `items_base` SET `interaction_type` = 'wf_slc_users_signal' WHERE `public_name` = 'wf_slc_users_signal';
UPDATE `items_base` SET `interaction_type` = 'wf_slc_users_team' WHERE `public_name` = 'wf_slc_users_team';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_test_slc';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_tile1';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_tile2';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_click_furni' WHERE `public_name` = 'wf_trg_click_furni';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_period_short' WHERE `public_name` = 'wf_trg_period_short';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_recv_signal' WHERE `public_name` = 'wf_trg_recv_signal';
UPDATE `items_base` SET `interaction_type` = 'wf_trg_stuff_state' WHERE `public_name` = 'wf_trg_stuff_state';
UPDATE `items_base` SET `interaction_type` = 'wf_xtra_anim_time' WHERE `public_name` = 'wf_xtra_anim_time';
UPDATE `items_base` SET `interaction_type` = 'wf_xtra_execution_limit' WHERE `public_name` = 'wf_xtra_execution_limit';
UPDATE `items_base` SET `interaction_type` = 'wf_xtra_filter_furni' WHERE `public_name` = 'wf_xtra_filter_furni';
UPDATE `items_base` SET `interaction_type` = 'wf_xtra_filter_users' WHERE `public_name` = 'wf_xtra_filter_users';
UPDATE `items_base` SET `interaction_type` = 'wf_xtra_mov_carry_users' WHERE `public_name` = 'wf_xtra_mov_carry_users';
UPDATE `items_base` SET `interaction_type` = 'wf_xtra_mov_no_animation' WHERE `public_name` = 'wf_xtra_mov_no_animation';
UPDATE `items_base` SET `interaction_type` = 'wf_xtra_mov_physics' WHERE `public_name` = 'wf_xtra_mov_physics';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_act_log';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_act_neg_log';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_var_echo';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_xtra_var_lvlup_system';
UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_xtra_var_time_util';
@@ -0,0 +1,11 @@
UPDATE items_base SET customparams = 'is_invisible' WHERE public_name like 'tile_stackmagic%';
UPDATE items_base SET customparams = 'is_invisible' WHERE public_name like 'tile_walkmagic%';
UPDATE items_base SET customparams = 'is_invisible' WHERE public_name like 'room_invisible_block%';
UPDATE items_base SET customparams = 'is_invisible' WHERE public_name = 'room_invisible_sit_tile';
UPDATE items_base SET customparams = 'is_invisible' WHERE public_name = 'room_invisible_click_tile';
UPDATE `items_base` SET `interaction_type` = 'wf_conf_invis_control' WHERE `public_name` = 'conf_invis_control';
UPDATE `items_base` SET `interaction_type` = 'wf_conf_handitem_block' WHERE `public_name` = 'conf_handitem_block';
UPDATE `items_base` SET `interaction_type` = 'wf_conf_wired_disable' WHERE `public_name` = 'conf_wired_disable';
UPDATE `items_base` SET `interaction_type` = 'wf_conf_queue_speed' WHERE `public_name` = 'conf_queue_speed';
UPDATE `items_base` SET `interaction_type` = 'wf_conf_area_hide' WHERE `public_name` = 'conf_area_hide';
@@ -1 +0,0 @@
INSERT INTO emulator_settings (`key`, `value`) VALUES ('pathfinder.diagonal.enabled', '1');
-4
View File
@@ -1,4 +0,0 @@
### New bot walking settings
INSERT INTO `emulator_settings` (`key`, `value`) VALUES ('hotel.bot.limit.walking.distance', '1');
INSERT INTO `emulator_settings` (`key`, `value`) VALUES ('hotel.bot.limit.walking.distance.radius', '5');
@@ -1,4 +0,0 @@
ALTER TABLE `users`
ADD COLUMN `background_id` INT(11) NOT NULL DEFAULT 0 AFTER `machine_id`,
ADD COLUMN `background_stand_id` INT(11) NOT NULL DEFAULT 0 AFTER `background_id`,
ADD COLUMN `background_overlay_id` INT(11) NOT NULL DEFAULT 0 AFTER `background_stand_id`;
@@ -1,5 +0,0 @@
ALTER TABLE `camwijs`.`permissions`
ADD COLUMN `cms_dance` ENUM('0', '1') NULL DEFAULT '0' AFTER `cmd_credits`;
INSERT INTO emulator_texts (`key`, `value`) VALUES ('commands.description.cmd_dance', 'dance around the world ! use 1 t/m 4 and 0 to stop');
INSERT INTO emulator_texts (`key`, `value`) VALUES ('commands.keys.cmd_dance', 'dance');
@@ -1,4 +0,0 @@
UPDATE `emulator_texts` SET `key` = 'generic.pet.happiness', `value` = 'Happiness' WHERE `key` = 'generic.pet.happyness';
ALTER TABLE `pet_commands_data` CHANGE `cost_happyness` `cost_happiness` int(11) NOT NULL DEFAULT '0';
ALTER TABLE `users_pets` CHANGE `happyness` `happiness` int(11) NOT NULL DEFAULT '100';
@@ -1,6 +0,0 @@
### WEBSOCKET SETTINGS
INSERT INTO `emulator_settings` (`key`, `value`) VALUES ('websockets.whitelist', 'localhost'); # Change this to the url of the websocket Expl. ws.mydomain.com
INSERT INTO `emulator_settings` (`key`, `value`) VALUES ('ws.nitro.host', '0.0.0.0'); # Best is this to leave it at 0.0.0.0
INSERT INTO `emulator_settings` (`key`, `value`) VALUES ('ws.nitro.ip.header', ''); # When useing a proxy change the header : X-Forwarded-For when using a proxy server or CF-Connecting-IP if behind Cloudflare.
INSERT INTO `emulator_settings` (`key`, `value`) VALUES ('ws.nitro.port', '2096'); # set the port of the websocket, cloudflare ports : 443 / 2053 / 2083 / 2087 / 2096 / 8443
@@ -1,2 +0,0 @@
--New permission
ALTER TABLE `permissions` ADD COLUMN `acc_unignorable` ENUM('0','1') NOT NULL DEFAULT '0';
+51 -3
View File
@@ -1530,7 +1530,7 @@ CREATE TABLE `catalog_club_offers` (
`credits` int(0) NOT NULL DEFAULT 10,
`points` int(0) NOT NULL DEFAULT 0,
`points_type` int(0) NOT NULL DEFAULT 0,
`type` enum('HC','VIP') CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT 'HC',
`type` enum('HC','VIP','BUILDERS_CLUB','BUILDERS_CLUB_ADDON') CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT 'HC',
`deal` enum('0','1') CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT '0',
`giftable` enum('1','0') CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT '0',
PRIMARY KEY (`id`) USING BTREE
@@ -13499,7 +13499,7 @@ CREATE TABLE `catalog_pages_bc` (
`id` int(0) NOT NULL AUTO_INCREMENT,
`parent_id` int(0) NOT NULL DEFAULT -1,
`caption` varchar(128) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
`page_layout` enum('default_3x3','club_buy','club_gift','frontpage','spaces','recycler','recycler_info','recycler_prizes','trophies','plasto','marketplace','marketplace_own_items','spaces_new','soundmachine','guilds','guild_furni','info_duckets','info_rentables','info_pets','roomads','single_bundle','sold_ltd_items','badge_display','bots','pets','pets2','pets3','productpage1','room_bundle','recent_purchases','default_3x3_color_grouping','guild_forum','vip_buy','info_loyalty','loyalty_vip_buy','collectibles','petcustomization','frontpage_featured') CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT 'default_3x3',
`page_layout` enum('default_3x3','club_buy','club_gift','frontpage','spaces','recycler','recycler_info','recycler_prizes','trophies','plasto','marketplace','marketplace_own_items','spaces_new','soundmachine','guilds','guild_furni','info_duckets','info_rentables','info_pets','roomads','single_bundle','sold_ltd_items','badge_display','bots','pets','pets2','pets3','productpage1','room_bundle','recent_purchases','default_3x3_color_grouping','guild_forum','vip_buy','info_loyalty','loyalty_vip_buy','collectibles','petcustomization','frontpage_featured','builders_club_frontpage','builders_club_addons','builders_club_loyalty') CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT 'default_3x3',
`icon_color` int(0) NOT NULL DEFAULT 1,
`icon_image` int(0) NOT NULL DEFAULT 1,
`order_num` int(0) NOT NULL DEFAULT 1,
@@ -30209,6 +30209,7 @@ CREATE TABLE `users_settings` (
`ui_flags` int(0) NOT NULL DEFAULT 1,
`has_gotten_default_saved_searches` tinyint(1) NOT NULL DEFAULT 0,
`hc_gifts_claimed` int(0) NULL DEFAULT 0,
`builders_club_bonus_furni` int(0) NOT NULL DEFAULT 0,
`last_hc_payday` int(0) NULL DEFAULT 0,
`max_rooms` int(0) NULL DEFAULT 50,
`max_friends` int(0) NULL DEFAULT 300,
@@ -30223,7 +30224,7 @@ CREATE TABLE `users_settings` (
-- ----------------------------
-- Records of users_settings
-- ----------------------------
INSERT INTO `users_settings` VALUES (1, 1, 0, 0, 3, 3, 0, 0, 0, '0', '1', '0', 0, 0, 0, 0, 0, 0, 0, '0', '0', '0', 100, 100, 100, '0', '0', 0, 0, 0, 'Arcturus Emulator;', 0, 0, 0, 0, 0, '0', -1, -1, '0', '0', '0', 0, '0', '0', 0, 1, 1, 0, 0, 50, 300);
INSERT INTO `users_settings` VALUES (1, 1, 0, 0, 3, 3, 0, 0, 0, '0', '1', '0', 0, 0, 0, 0, 0, 0, 0, '0', '0', '0', 100, 100, 100, '0', '0', 0, 0, 0, 'Arcturus Emulator;', 0, 0, 0, 0, 0, '0', -1, -1, '0', '0', '0', 0, '0', '0', 0, 1, 1, 0, 0, 0, 50, 300);
-- ----------------------------
-- Table structure for users_subscriptions
@@ -30449,3 +30450,50 @@ INSERT INTO `youtube_playlists` VALUES (6587, 'PL4YfV2mXS8WXOkxFly7YsGL8cKtqp873
INSERT INTO `youtube_playlists` VALUES (6587, 'PL80F08DAE1B614BA9', 0);
SET FOREIGN_KEY_CHECKS = 1;
ALTER TABLE `catalog_pages`
MODIFY COLUMN `page_layout` ENUM(
'default_3x3',
'club_buy',
'club_gift',
'frontpage',
'spaces',
'recycler',
'recycler_info',
'recycler_prizes',
'trophies',
'plasto',
'marketplace',
'marketplace_own_items',
'spaces_new',
'soundmachine',
'guilds',
'guild_furni',
'info_duckets',
'info_rentables',
'info_pets',
'roomads',
'single_bundle',
'sold_ltd_items',
'badge_display',
'bots',
'pets',
'pets2',
'pets3',
'productpage1',
'room_bundle',
'recent_purchases',
'default_3x3_color_grouping',
'guild_forum',
'vip_buy',
'info_loyalty',
'loyalty_vip_buy',
'collectibles',
'petcustomization',
'frontpage_featured',
'builders_club_frontpage',
'builders_club_addons',
'builders_club_loyalty'
) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT 'default_3x3';
ALTER TABLE `catalog_pages`
ADD COLUMN `catalog_mode` ENUM('NORMAL','BUILDER','BOTH') CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT 'NORMAL' AFTER `club_only`;
-3
View File
@@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
-13
View File
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="Habbo" />
</profile>
</annotationProcessing>
</component>
</project>
-7
View File
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>
-25
View File
@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="central" />
<option name="url" value="https://repo1.maven.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="mvnrepo" />
<option name="name" value="mvnrepo" />
<option name="url" value="https://mvnrepository.com/" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>
-12
View File
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="openjdk-23" project-jdk-type="JavaSDK" />
</project>
Binary file not shown.
+8 -8
View File
@@ -6,12 +6,12 @@
<groupId>com.eu.habbo</groupId>
<artifactId>Habbo</artifactId>
<version>4.0.5</version>
<version>4.1.2</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
</properties>
<build>
@@ -19,7 +19,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<version>3.15.0</version>
<configuration>
<source>19</source>
<target>19</target>
@@ -86,11 +86,11 @@
<version>2.11.0</version>
</dependency>
<!-- MySQL Connector -->
<!-- MariaDB Connector/J (native driver for MariaDB) -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>9.1.0</version>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.5.1</version>
<scope>runtime</scope>
</dependency>
@@ -0,0 +1,17 @@
-- ============================================================
-- 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
@@ -0,0 +1,115 @@
-- ============================================================
-- 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');
@@ -7,6 +7,7 @@ import com.eu.habbo.core.*;
import com.eu.habbo.core.consolecommands.ConsoleCommand;
import com.eu.habbo.database.Database;
import com.eu.habbo.habbohotel.GameEnvironment;
import com.eu.habbo.habbohotel.gameclients.SessionResumeManager;
import com.eu.habbo.networking.gameserver.GameServer;
import com.eu.habbo.networking.rconserver.RCONServer;
import com.eu.habbo.plugin.PluginManager;
@@ -20,6 +21,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.JarURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.sql.Timestamp;
@@ -36,8 +39,8 @@ public final class Emulator {
private static final String CLASS_PATH = (System.getProperty("java.class.path") != null ? System.getProperty("java.class.path") : "Unknown");
public final static int MAJOR = 4;
public final static int MINOR = 0;
public final static int BUILD = 5;
public final static int MINOR = 1;
public final static int BUILD = 0;
public final static String PREVIEW = "";
public static final String version = "Arcturus Morningstar" + " " + MAJOR + "." + MINOR + "." + BUILD + " " + PREVIEW;
@@ -52,6 +55,9 @@ public final class Emulator {
"Still Rocking in 2026.\n";
public static String build = "";
public static long buildTimestamp = -1L;
public static boolean isReady = false;
public static boolean isShuttingDown = false;
public static boolean stopped = false;
@@ -103,13 +109,6 @@ public final class Emulator {
System.out.println(logo);
System.out.println();
LOGGER.info("https://github.com/duckietm/Arcturus-Morningstar-Extended, ");
System.out.println();
LOGGER.info("This project is for educational purposes only. This Emulator is an open-source fork of Arcturus created by TheGeneral.");
LOGGER.info("Version: {}", version);
LOGGER.info("Build: {}", build);
long startTime = System.nanoTime();
Emulator.runtime = Runtime.getRuntime();
@@ -141,6 +140,15 @@ public final class Emulator {
Emulator.config.register("camera.price.points", "0");
Emulator.config.register("camera.price.points.type", "5");
Emulator.config.register("camera.render.delay", "5");
Emulator.config.register("hotel.timezone", java.time.ZoneId.systemDefault().getId());
String hotelTimezoneId = Emulator.getConfig().getValue("hotel.timezone", java.time.ZoneId.systemDefault().getId());
System.out.println();
LOGGER.info("https://github.com/duckietm/Arcturus-Morningstar-Extended, ");
System.out.println();
LOGGER.info("This project is for educational purposes only. This Emulator is an open-source fork of Arcturus created by TheGeneral.");
LOGGER.info("Version: {}", version);
LOGGER.info("Build: {}", build);
LOGGER.info("Build Timestamp: {} [{}]", formatBuildTimestamp(buildTimestamp, hotelTimezoneId), hotelTimezoneId);
Emulator.texts.register("camera.permission", "You don't have permission to use the camera!");
Emulator.texts.register("camera.wait", "Please wait %seconds% seconds before making another picture.");
Emulator.texts.register("camera.error.creation", "Failed to create your picture. *sadpanda*");
@@ -216,12 +224,21 @@ public final class Emulator {
private static void setBuild() {
if (Emulator.class.getProtectionDomain().getCodeSource() == null) {
build = "UNKNOWN";
buildTimestamp = -1L;
return;
}
StringBuilder sb = new StringBuilder();
try {
String filepath = new File(Emulator.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getAbsolutePath();
File buildFile = new File(Emulator.class.getProtectionDomain().getCodeSource().getLocation().toURI());
buildTimestamp = resolveBuildTimestamp(buildFile);
if (!buildFile.isFile()) {
build = "DEV";
return;
}
String filepath = buildFile.getAbsolutePath();
MessageDigest md = MessageDigest.getInstance("MD5");
try (FileInputStream fis = new FileInputStream(filepath)) {
byte[] dataBytes = new byte[1024];
@@ -234,14 +251,69 @@ public final class Emulator {
}
} catch (Exception e) {
build = "UNKNOWN";
buildTimestamp = -1L;
return;
}
build = sb.toString();
}
private static long resolveBuildTimestamp(File buildFile) {
if (buildFile != null && buildFile.exists() && buildFile.isFile()) {
return buildFile.lastModified();
}
try {
URL classUrl = Emulator.class.getResource("Emulator.class");
if (classUrl != null) {
if ("file".equalsIgnoreCase(classUrl.getProtocol())) {
File classFile = new File(classUrl.toURI());
if (classFile.exists()) {
return classFile.lastModified();
}
}
if ("jar".equalsIgnoreCase(classUrl.getProtocol())) {
JarURLConnection connection = (JarURLConnection) classUrl.openConnection();
File jarFile = new File(connection.getJarFileURL().toURI());
if (jarFile.exists()) {
return jarFile.lastModified();
}
}
}
} catch (Exception ignored) {
}
if (buildFile != null && buildFile.exists()) {
return buildFile.lastModified();
}
return -1L;
}
private static String formatBuildTimestamp(long buildTimestamp, String timezoneId) {
if (buildTimestamp <= 0) {
return "UNKNOWN";
}
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
format.setTimeZone(TimeZone.getTimeZone(java.time.ZoneId.of(timezoneId)));
} catch (Exception ignored) {
format.setTimeZone(TimeZone.getDefault());
}
return format.format(new Timestamp(buildTimestamp));
}
private static void dispose() {
Emulator.getThreading().setCanAdd(false);
if (Emulator.threading != null) {
Emulator.threading.setCanAdd(false);
}
Emulator.isShuttingDown = true;
Emulator.isReady = false;
@@ -250,6 +322,7 @@ public final class Emulator {
if (Emulator.pluginManager != null)
tryShutdown(() -> Emulator.pluginManager.fireEvent(new EmulatorStartShutdownEvent()));
if (Emulator.rconServer != null) tryShutdown(() -> Emulator.rconServer.stop());
tryShutdown(() -> SessionResumeManager.getInstance().disposeAll());
if (Emulator.gameEnvironment != null) tryShutdown(() -> Emulator.gameEnvironment.dispose());
if (Emulator.pluginManager != null)
tryShutdown(() -> Emulator.pluginManager.fireEvent(new EmulatorStoppedEvent()));
@@ -4,7 +4,6 @@ import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.guilds.forums.ForumThread;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.messages.incoming.friends.SearchUserEvent;
import com.eu.habbo.messages.incoming.navigator.SearchRoomsEvent;
import com.eu.habbo.messages.outgoing.users.UserDataComposer;
import com.eu.habbo.threading.runnables.AchievementUpdater;
import org.slf4j.Logger;
@@ -101,8 +100,7 @@ public class CleanerThread implements Runnable {
LAST_HABBO_CACHE_CLEARED = time;
}
SearchRoomsEvent.cachedResults.clear();
SearchUserEvent.cachedResults.clear();
SearchUserEvent.cleanExpiredCache();
}
@@ -17,14 +17,18 @@ import java.util.Properties;
public class ConfigurationManager {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationManager.class);
private static final String EMULATOR_SETTINGS_TABLE = "emulator_settings";
private static final String WIRED_SETTINGS_TABLE = "wired_emulator_settings";
private final Properties properties;
private final Properties wiredProperties;
private final String configurationPath;
public boolean loaded = false;
public boolean isLoading = false;
public ConfigurationManager(String configurationPath) {
this.properties = new Properties();
this.wiredProperties = new Properties();
this.configurationPath = configurationPath;
this.reload();
}
@@ -32,6 +36,7 @@ public class ConfigurationManager {
public void reload() {
this.isLoading = true;
this.properties.clear();
this.wiredProperties.clear();
InputStream input = null;
@@ -87,6 +92,7 @@ public class ConfigurationManager {
// Runtime
envMapping.put("runtime.threads", "RT_THREADS");
envMapping.put("logging.errors.runtime", "RT_LOG_ERRORS");
envMapping.put("hotel.timezone", "HOTEL_TIMEZONE");
for (Map.Entry<String, String> entry : envMapping.entrySet()) {
String envValue = System.getenv(entry.getValue());
@@ -115,31 +121,15 @@ public class ConfigurationManager {
LOGGER.info("Loading configuration from database...");
long millis = System.currentTimeMillis();
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); Statement statement = connection.createStatement()) {
if (statement.execute("SELECT * FROM emulator_settings")) {
try (ResultSet set = statement.getResultSet()) {
while (set.next()) {
this.properties.put(set.getString("key"), set.getString("value"));
}
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
this.loadSettingsTable(EMULATOR_SETTINGS_TABLE, this.properties, false);
this.loadSettingsTable(WIRED_SETTINGS_TABLE, this.wiredProperties, true);
LOGGER.info("Configuration -> loaded! ({} MS)", System.currentTimeMillis() - millis);
}
public void saveToDatabase() {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE emulator_settings SET `value` = ? WHERE `key` = ? LIMIT 1")) {
for (Map.Entry<Object, Object> entry : this.properties.entrySet()) {
statement.setString(1, entry.getValue().toString());
statement.setString(2, entry.getKey().toString());
statement.executeUpdate();
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
this.saveSettingsTable(EMULATOR_SETTINGS_TABLE, this.properties);
this.saveSettingsTable(WIRED_SETTINGS_TABLE, this.wiredProperties);
}
@@ -152,10 +142,21 @@ public class ConfigurationManager {
if (this.isLoading)
return defaultValue;
if (!this.properties.containsKey(key)) {
Properties targetProperties = this.resolveProperties(key);
if (targetProperties.containsKey(key)) {
return targetProperties.getProperty(key, defaultValue);
}
if (this.isWiredSettingKey(key) && this.properties.containsKey(key)) {
return this.properties.getProperty(key, defaultValue);
}
if (!targetProperties.containsKey(key)) {
LOGGER.error("Config key not found {}", key);
}
return this.properties.getProperty(key, defaultValue);
return defaultValue;
}
public boolean getBoolean(String key) {
@@ -208,21 +209,91 @@ public class ConfigurationManager {
}
public void update(String key, String value) {
this.properties.setProperty(key, value);
this.resolveProperties(key).setProperty(key, value);
}
public void register(String key, String value) {
if (this.properties.getProperty(key, null) != null)
this.register(key, value, "");
}
public void register(String key, String value, String comment) {
Properties targetProperties = this.resolveProperties(key);
if (targetProperties.getProperty(key, null) != null)
return;
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO emulator_settings VALUES (?, ?)")) {
statement.setString(1, key);
statement.setString(2, value);
statement.execute();
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
this.insertSetting(key, value, comment);
this.update(key, value);
}
private void loadSettingsTable(String tableName, Properties targetProperties, boolean optional) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
Statement statement = connection.createStatement()) {
if (statement.execute("SELECT * FROM " + tableName)) {
try (ResultSet set = statement.getResultSet()) {
while (set.next()) {
targetProperties.put(set.getString("key"), set.getString("value"));
}
}
}
} catch (SQLException e) {
if (optional) {
LOGGER.warn("Skipping optional config table {}: {}", tableName, e.getMessage());
} else {
LOGGER.error("Caught SQL exception", e);
}
}
}
private void saveSettingsTable(String tableName, Properties sourceProperties) {
String sql = "UPDATE " + tableName + " SET `value` = ? WHERE `key` = ? LIMIT 1";
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(sql)) {
for (Map.Entry<Object, Object> entry : sourceProperties.entrySet()) {
statement.setString(1, entry.getValue().toString());
statement.setString(2, entry.getKey().toString());
statement.executeUpdate();
}
} catch (SQLException e) {
if (WIRED_SETTINGS_TABLE.equals(tableName)) {
LOGGER.warn("Skipping wired config save for table {}: {}", tableName, e.getMessage());
} else {
LOGGER.error("Caught SQL exception", e);
}
}
}
private void insertSetting(String key, String value, String comment) {
String tableName = this.isWiredSettingKey(key) ? WIRED_SETTINGS_TABLE : EMULATOR_SETTINGS_TABLE;
String sql = this.isWiredSettingKey(key)
? "INSERT INTO " + tableName + " (`key`, `value`, `comment`) VALUES (?, ?, ?)"
: "INSERT INTO " + tableName + " (`key`, `value`) VALUES (?, ?)";
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, key);
statement.setString(2, value);
if (this.isWiredSettingKey(key)) {
statement.setString(3, comment == null ? "" : comment);
}
statement.execute();
} catch (SQLException e) {
if (this.isWiredSettingKey(key)) {
LOGGER.warn("Unable to insert wired setting {} into {}: {}", key, tableName, e.getMessage());
} else {
LOGGER.error("Caught SQL exception", e);
}
}
}
private Properties resolveProperties(String key) {
return this.isWiredSettingKey(key) ? this.wiredProperties : this.properties;
}
private boolean isWiredSettingKey(String key) {
return key != null && (key.startsWith("wired.") || key.startsWith("hotel.wired."));
}
}
@@ -7,6 +7,10 @@ import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;
public class DatabaseLogger {
@@ -24,19 +28,41 @@ public class DatabaseLogger {
return;
}
if (this.loggables.isEmpty()) {
// Drain the queue into a local snapshot so new loggables that arrive
// during this save cycle roll into the next flush instead of extending
// the current one indefinitely.
List<DatabaseLoggable> snapshot = new ArrayList<>();
DatabaseLoggable next;
while ((next = this.loggables.poll()) != null) {
snapshot.add(next);
}
if (snapshot.isEmpty()) {
return;
}
// Group by SQL query so each distinct statement only prepares and
// executeBatches once. LinkedHashMap preserves first-seen order so
// auto-increment ids on chat/log tables correlate with the time the
// events actually happened.
Map<String, List<DatabaseLoggable>> byQuery = new LinkedHashMap<>();
for (DatabaseLoggable loggable : snapshot) {
byQuery.computeIfAbsent(loggable.getQuery(), k -> new ArrayList<>()).add(loggable);
}
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) {
while (!this.loggables.isEmpty()) {
DatabaseLoggable loggable = this.loggables.remove();
try (PreparedStatement statement = connection.prepareStatement(loggable.getQuery())) {
loggable.log(statement);
for (Map.Entry<String, List<DatabaseLoggable>> group : byQuery.entrySet()) {
List<DatabaseLoggable> entries = group.getValue();
try (PreparedStatement statement = connection.prepareStatement(group.getKey())) {
for (DatabaseLoggable loggable : entries) {
loggable.log(statement);
}
statement.executeBatch();
} catch (SQLException e) {
// One bad group shouldn't prevent other groups from flushing.
LOGGER.error("Exception caught while saving loggable group of size {}: {}",
entries.size(), group.getKey(), e);
}
}
} catch (SQLException e) {
LOGGER.error("Exception caught while saving loggables to database.", e);
@@ -47,6 +47,8 @@ public class RoomUserPetComposer extends MessageComposer {
this.response.appendBoolean(true); //Can breed
this.response.appendInt(0);
this.response.appendString("");
this.response.appendString("unknown");
this.response.appendInt(0);
return this.response;
}
@@ -4,16 +4,15 @@ import com.eu.habbo.Emulator;
import com.eu.habbo.core.ConfigurationManager;
import com.zaxxer.hikari.HikariDataSource;
import gnu.trove.map.hash.THashMap;
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.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Database {
@@ -48,11 +47,9 @@ public class Database {
}
public void dispose() {
if (this.databasePool != null) {
this.databasePool.getDatabase().close();
if (this.dataSource != null && !this.dataSource.isClosed()) {
this.dataSource.close();
}
this.dataSource.close();
}
public HikariDataSource getDataSource() {
@@ -63,51 +60,77 @@ public class Database {
return this.databasePool;
}
public static PreparedStatement preparedStatementWithParams(Connection connection, String query, THashMap<String, Object> queryParams) throws SQLException {
THashMap<Integer, Object> params = new THashMap<Integer, Object>();
THashSet<String> quotedParams = new THashSet<>();
public static PreparedStatement preparedStatementWithParams(Connection connection,
String query,
Map<String, Object> queryParams) throws SQLException {
StringBuilder positional = new StringBuilder(query.length());
List<Object> bindValues = new ArrayList<>();
for(String key : queryParams.keySet()) {
quotedParams.add(Pattern.quote(key));
}
int i = 0;
int n = query.length();
String regex = "(" + String.join("|", quotedParams) + ")";
while (i < n) {
char c = query.charAt(i);
Matcher m = Pattern.compile(regex).matcher(query);
int i = 1;
while (m.find()) {
try {
params.put(i, queryParams.get(m.group(1)));
if (c == '\'') {
positional.append(c);
i++;
while (i < n) {
char inner = query.charAt(i);
positional.append(inner);
i++;
if (inner == '\'') {
if (i < n && query.charAt(i) == '\'') {
positional.append('\'');
i++;
} else {
break;
}
}
}
continue;
}
catch (Exception ignored) { }
if (c == '@' && i + 1 < n && isNameStart(query.charAt(i + 1))) {
int start = i;
int j = i + 1;
while (j < n && isNamePart(query.charAt(j))) {
j++;
}
String name = query.substring(start, j);
if (!queryParams.containsKey(name)) {
throw new IllegalArgumentException(
"SQL template references parameter '" + name + "' but no value was provided");
}
positional.append('?');
bindValues.add(queryParams.get(name));
i = j;
continue;
}
positional.append(c);
i++;
}
PreparedStatement statement = connection.prepareStatement(query.replaceAll(regex, "?"));
for(Map.Entry<Integer, Object> set : params.entrySet()) {
if(set.getValue().getClass() == String.class) {
statement.setString(set.getKey(), (String)set.getValue());
}
else if(set.getValue().getClass() == Integer.class) {
statement.setInt(set.getKey(), (Integer)set.getValue());
}
else if(set.getValue().getClass() == Double.class) {
statement.setDouble(set.getKey(), (Double)set.getValue());
}
else if(set.getValue().getClass() == Float.class) {
statement.setFloat(set.getKey(), (Float)set.getValue());
}
else if(set.getValue().getClass() == Long.class) {
statement.setLong(set.getKey(), (Long)set.getValue());
}
else {
statement.setObject(set.getKey(), set.getValue());
}
PreparedStatement statement = connection.prepareStatement(positional.toString());
for (int k = 0; k < bindValues.size(); k++) {
statement.setObject(k + 1, bindValues.get(k));
}
return statement;
}
@Deprecated
public static PreparedStatement preparedStatementWithParams(Connection connection,
String query,
THashMap<String, Object> queryParams) throws SQLException {
return preparedStatementWithParams(connection, query, (Map<String, Object>) queryParams);
}
private static boolean isNameStart(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
}
private static boolean isNamePart(char c) {
return isNameStart(c) || (c >= '0' && c <= '9');
}
}
@@ -8,41 +8,85 @@ import org.slf4j.LoggerFactory;
class DatabasePool {
private final Logger log = LoggerFactory.getLogger(DatabasePool.class);
// Connection settings
private static final String DB_HOSTNAME_KEY = "db.hostname";
private static final String DB_PORT_KEY = "db.port";
private static final String DB_PASSWORD_KEY = "db.password";
private static final String DB_NAME_KEY = "db.database";
private static final String DB_USER_KEY = "db.username";
private static final String DB_PARAMS_KEY = "db.params";
// Pool sizing
private static final String DB_POOL_MAX_SIZE = "db.pool.maxsize";
private static final String DB_POOL_MIN_SIZE = "db.pool.minsize";
private static final String DB_HOSTNAME_KEY = "db.hostname";
private static final String DB_PORT_KEY = "db.port";
private static final String DB_PASSWORD_KEY = "db.password";
private static final String DB_NAME_KEY = "db.database";
private static final String DB_USER_KEY = "db.username";
private static final String DB_PARAMS_KEY = "db.params";
// Pool tuning (all overridable via config.ini; sensible MariaDB defaults apply otherwise)
private static final String DB_POOL_CONNECTION_TIMEOUT = "db.pool.connection_timeout_ms";
private static final String DB_POOL_IDLE_TIMEOUT = "db.pool.idle_timeout_ms";
private static final String DB_POOL_MAX_LIFETIME = "db.pool.max_lifetime_ms";
private static final String DB_POOL_LEAK_THRESHOLD = "db.pool.leak_detection_ms";
private static final String DB_POOL_VALIDATION_TIMEOUT = "db.pool.validation_timeout_ms";
private HikariDataSource database;
private static DatabasePool instance;
DatabasePool() {
}
public static synchronized DatabasePool getInstance() {
if (instance == null) {
instance = new DatabasePool();
}
return instance;
}
public boolean getStoragePooling(ConfigurationManager config) {
try {
HikariConfig databaseConfiguration = new HikariConfig();
// Pool sizing
databaseConfiguration.setMaximumPoolSize(config.getInt(DB_POOL_MAX_SIZE, 50));
databaseConfiguration.setMinimumIdle(config.getInt(DB_POOL_MIN_SIZE, 10));
databaseConfiguration.setJdbcUrl("jdbc:mysql://" + config.getValue(DB_HOSTNAME_KEY, "localhost") + ":" + config.getValue(DB_PORT_KEY, "3306") + "/" + config.getValue(DB_NAME_KEY) + config.getValue(DB_PARAMS_KEY));
databaseConfiguration.addDataSourceProperty("serverName", config.getValue(DB_HOSTNAME_KEY, "localhost"));
databaseConfiguration.addDataSourceProperty("port", config.getValue(DB_PORT_KEY, "3306"));
databaseConfiguration.addDataSourceProperty("databaseName", config.getValue(DB_NAME_KEY));
databaseConfiguration.addDataSourceProperty("user", config.getValue(DB_USER_KEY));
databaseConfiguration.addDataSourceProperty("password", config.getValue(DB_PASSWORD_KEY));
// Pool timeouts (milliseconds)
databaseConfiguration.setConnectionTimeout(config.getInt(DB_POOL_CONNECTION_TIMEOUT, 10_000));
databaseConfiguration.setIdleTimeout(config.getInt(DB_POOL_IDLE_TIMEOUT, 600_000));
databaseConfiguration.setMaxLifetime(config.getInt(DB_POOL_MAX_LIFETIME, 1_800_000));
databaseConfiguration.setValidationTimeout(config.getInt(DB_POOL_VALIDATION_TIMEOUT, 5_000));
// Leak detection: 0 disables it. Default 20s helps locate connections
// that weren't closed in a try-with-resources block.
int leakThreshold = config.getInt(DB_POOL_LEAK_THRESHOLD, 20_000);
if (leakThreshold > 0) {
databaseConfiguration.setLeakDetectionThreshold(leakThreshold);
}
// Use the MariaDB Connector/J native protocol instead of the Oracle MySQL driver.
databaseConfiguration.setJdbcUrl("jdbc:mariadb://"
+ config.getValue(DB_HOSTNAME_KEY, "localhost") + ":"
+ config.getValue(DB_PORT_KEY, "3306") + "/"
+ config.getValue(DB_NAME_KEY)
+ config.getValue(DB_PARAMS_KEY));
databaseConfiguration.setUsername(config.getValue(DB_USER_KEY));
databaseConfiguration.setPassword(config.getValue(DB_PASSWORD_KEY));
// Prepared-statement caching. Without these, Hikari's cache is off entirely
// and every prepareStatement() call re-parses on the server side.
databaseConfiguration.addDataSourceProperty("cachePrepStmts", "true");
databaseConfiguration.addDataSourceProperty("prepStmtCacheSize", "500");
databaseConfiguration.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
databaseConfiguration.addDataSourceProperty("useServerPrepStmts", "true");
// Bulk write throughput: rewrites batched INSERTs into a single multi-value
// INSERT statement. Huge win for item/room/inventory persistence paths.
databaseConfiguration.addDataSourceProperty("rewriteBatchedStatements", "true");
// Cut per-connection round-trips.
databaseConfiguration.addDataSourceProperty("cacheServerConfiguration", "true");
databaseConfiguration.addDataSourceProperty("useLocalSessionState", "true");
databaseConfiguration.addDataSourceProperty("cacheResultSetMetadata", "true");
databaseConfiguration.addDataSourceProperty("elideSetAutoCommits", "true");
databaseConfiguration.addDataSourceProperty("maintainTimeStats", "false");
databaseConfiguration.setPoolName("HabboHikariPool");
log.info("INITIALIZING DATABASE SERVER: " + config.getValue(DB_HOSTNAME_KEY));
log.info("ON PORT: " + config.getValue(DB_PORT_KEY));
log.info("HABBO DATABASE: " + config.getValue(DB_NAME_KEY));
log.info("USING DRIVER: MariaDB Connector/J");
this.database = new HikariDataSource(databaseConfiguration);
} catch (Exception e) {
@@ -58,4 +102,4 @@ class DatabasePool {
}
return database;
}
}
}
@@ -0,0 +1,113 @@
package com.eu.habbo.database;
import com.eu.habbo.Emulator;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
public final class SqlQueries {
private SqlQueries() {
}
@FunctionalInterface
public interface RowMapper<T> {
T map(ResultSet rs) throws SQLException;
}
@FunctionalInterface
public interface RowConsumer {
void accept(ResultSet rs) throws SQLException;
}
@FunctionalInterface
public interface ParameterBinder<P> {
void bind(PreparedStatement ps, P value) throws SQLException;
}
public static class DataAccessException extends RuntimeException {
public DataAccessException(String message, Throwable cause) {
super(message, cause);
}
}
public static <T> List<T> query(String sql, RowMapper<T> mapper, Object... params) {
try (Connection c = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
bindAll(ps, params);
try (ResultSet rs = ps.executeQuery()) {
List<T> out = new ArrayList<>();
while (rs.next()) {
out.add(mapper.map(rs));
}
return out;
}
} catch (SQLException e) {
throw new DataAccessException("query failed: " + sql, e);
}
}
public static <T> Optional<T> queryOne(String sql, RowMapper<T> mapper, Object... params) {
try (Connection c = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
bindAll(ps, params);
try (ResultSet rs = ps.executeQuery()) {
return rs.next() ? Optional.ofNullable(mapper.map(rs)) : Optional.empty();
}
} catch (SQLException e) {
throw new DataAccessException("queryOne failed: " + sql, e);
}
}
public static void forEach(String sql, RowConsumer consumer, Object... params) {
try (Connection c = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
bindAll(ps, params);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
consumer.accept(rs);
}
}
} catch (SQLException e) {
throw new DataAccessException("forEach failed: " + sql, e);
}
}
public static int update(String sql, Object... params) {
try (Connection c = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
bindAll(ps, params);
return ps.executeUpdate();
} catch (SQLException e) {
throw new DataAccessException("update failed: " + sql, e);
}
}
public static <P> int[] batchUpdate(String sql, Collection<? extends P> items, ParameterBinder<P> binder) {
try (Connection c = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
for (P item : items) {
binder.bind(ps, item);
ps.addBatch();
}
return ps.executeBatch();
} catch (SQLException e) {
throw new DataAccessException("batchUpdate failed: " + sql, e);
}
}
private static void bindAll(PreparedStatement ps, Object[] params) throws SQLException {
if (params == null) {
return;
}
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
}
}
@@ -1,6 +1,7 @@
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;
@@ -49,16 +50,12 @@ public class AchievementManager {
if (habbo != null) {
progressAchievement(habbo, achievement, amount);
} else {
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) {
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) {
LOGGER.error("Caught SQL exception", e);
}
}
@@ -203,44 +200,41 @@ public class AchievementManager {
}
public static void createUserEntry(Habbo habbo, Achievement achievement) {
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) {
try {
SqlQueries.update(
"INSERT INTO users_achievements (user_id, achievement_name, progress) VALUES (?, ?, ?)",
habbo.getHabboInfo().getId(), achievement.name, 1);
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e);
}
}
public static void saveAchievements(Habbo habbo) {
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) {
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) {
LOGGER.error("Caught SQL exception", e);
}
}
public static int getAchievementProgressForHabbo(int userId, Achievement achievement) {
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) {
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);
return 0;
}
return 0;
}
public void reload() {
@@ -393,4 +387,4 @@ public class AchievementManager {
public TalentTrackLevel getTalentTrackLevel(TalentTrackType type, int level) {
return this.talentTrackLevels.get(type).get(level);
}
}
}
@@ -1,6 +1,7 @@
package com.eu.habbo.habbohotel.campaign.calendar;
import com.eu.habbo.Emulator;
import com.eu.habbo.database.SqlQueries;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.messages.outgoing.events.calendar.AdventCalendarProductComposer;
import com.eu.habbo.plugin.events.users.calendar.UserClaimRewardEvent;
@@ -33,27 +34,27 @@ public class CalendarManager {
public boolean reload() {
this.dispose();
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM calendar_campaigns WHERE enabled = 1")) {
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
calendarCampaigns.put(set.getInt("id"), new CalendarCampaign(set));
}
}
} catch (SQLException e) {
try {
SqlQueries.query(
"SELECT * FROM calendar_campaigns WHERE enabled = 1",
CalendarCampaign::new)
.forEach(c -> calendarCampaigns.put(c.getId(), c));
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e);
return false;
}
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM calendar_rewards")) {
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
CalendarCampaign campaign = calendarCampaigns.get(set.getInt("campaign_id"));
if(campaign != null){
campaign.addReward(new CalendarRewardObject(set));
}
}
}
} catch (SQLException e) {
try {
SqlQueries.query(
"SELECT * FROM calendar_rewards",
rs -> Map.entry(rs.getInt("campaign_id"), new CalendarRewardObject(rs)))
.forEach(entry -> {
CalendarCampaign campaign = calendarCampaigns.get(entry.getKey());
if (campaign != null) {
campaign.addReward(entry.getValue());
}
});
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e);
return false;
}
@@ -94,14 +95,12 @@ public class CalendarManager {
public boolean deleteCampaign(CalendarCampaign campaign) {
calendarCampaigns.remove(campaign.getId());
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("DELETE FROM calendar_campaigns WHERE id = ? LIMIT 1")) {
statement.setInt(1, campaign.getId());
return statement.execute();
} catch (SQLException e) {
try {
return SqlQueries.update("DELETE FROM calendar_campaigns WHERE id = ? LIMIT 1", campaign.getId()) > 0;
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e);
return false;
}
return false;
}
public CalendarCampaign getCalendarCampaign(String campaignName) {
@@ -136,14 +135,15 @@ public class CalendarManager {
habbo.getHabboStats().calendarRewardsClaimed.add(new CalendarRewardClaimed(habbo.getHabboInfo().getId(), campaign.getId(), day, object.getId(), new Timestamp(System.currentTimeMillis())));
habbo.getClient().sendResponse(new AdventCalendarProductComposer(true, object, habbo));
object.give(habbo);
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO calendar_rewards_claimed (user_id, campaign_id, day, reward_id, timestamp) VALUES (?, ?, ?, ?, ?)")) {
statement.setInt(1, habbo.getHabboInfo().getId());
statement.setInt(2, campaign.getId());
statement.setInt(3, day);
statement.setInt(4, object.getId());
statement.setInt(5, Emulator.getIntUnixTimestamp());
statement.execute();
} catch (SQLException e) {
try {
SqlQueries.update(
"INSERT INTO calendar_rewards_claimed (user_id, campaign_id, day, reward_id, timestamp) VALUES (?, ?, ?, ?, ?)",
habbo.getHabboInfo().getId(),
campaign.getId(),
day,
object.getId(),
Emulator.getIntUnixTimestamp());
} catch (SqlQueries.DataAccessException e) {
LOGGER.error("Caught SQL exception", e);
}
}
@@ -338,6 +338,8 @@ public class CatalogItem implements ISerialize, Runnable, Comparable<CatalogItem
message.appendBoolean(haveOffer(this));
message.appendBoolean(false); //unknown
message.appendString(this.name + ".png");
message.appendString(this.itemId == null ? "" : this.itemId);
message.appendBoolean(this.haveOffer);
}
@Override
@@ -173,6 +173,9 @@ public class CatalogManager {
case mad_money:
this.put(layout.name().toLowerCase(), MadMoneyLayout.class);
break;
case custom_prefix:
this.put(layout.name().toLowerCase(), CustomPrefixLayout.class);
break;
case default_3x3:
default:
this.put("default_3x3", Default_3x3Layout.class);
@@ -185,6 +188,7 @@ public class CatalogManager {
public static int PURCHASE_COOLDOWN = 1;
public static boolean SORT_USING_ORDERNUM = false;
public final TIntObjectMap<CatalogPage> catalogPages;
public final TIntObjectMap<CatalogPage> buildersClubCatalogPages;
public final TIntObjectMap<CatalogFeaturedPage> catalogFeaturedPages;
public final THashMap<Integer, THashSet<Item>> prizes;
public final THashMap<Integer, Integer> giftWrappers;
@@ -194,6 +198,7 @@ public class CatalogManager {
public final THashMap<Integer, TargetOffer> targetOffers;
public final THashMap<Integer, ClothItem> clothing;
public final TIntIntHashMap offerDefs;
public final TIntIntHashMap buildersClubOfferDefs;
public final Item ecotronItem;
public final THashMap<Integer, CatalogLimitedConfiguration> limitedNumbers;
private final List<Voucher> vouchers;
@@ -201,6 +206,7 @@ public class CatalogManager {
public CatalogManager() {
long millis = System.currentTimeMillis();
this.catalogPages = TCollections.synchronizedMap(new TIntObjectHashMap<>());
this.buildersClubCatalogPages = TCollections.synchronizedMap(new TIntObjectHashMap<>());
this.catalogFeaturedPages = new TIntObjectHashMap<>();
this.prizes = new THashMap<>();
this.giftWrappers = new THashMap<>();
@@ -210,6 +216,7 @@ public class CatalogManager {
this.targetOffers = new THashMap<>();
this.clothing = new THashMap<>();
this.offerDefs = new TIntIntHashMap();
this.buildersClubOfferDefs = new TIntIntHashMap();
this.vouchers = new ArrayList<>();
this.limitedNumbers = new THashMap<>();
@@ -226,8 +233,10 @@ public class CatalogManager {
this.loadLimitedNumbers();
this.loadCatalogPages();
this.loadBuildersClubCatalogPages();
this.loadCatalogFeaturedPages();
this.loadCatalogItems();
this.loadBuildersClubCatalogItems();
this.loadClubOffers();
this.loadTargetOffers();
this.loadVouchers();
@@ -312,6 +321,57 @@ public class CatalogManager {
LOGGER.info("Loaded {} Catalog Pages!", this.catalogPages.size());
}
private synchronized void loadBuildersClubCatalogPages() {
this.buildersClubCatalogPages.clear();
final THashMap<Integer, CatalogPage> pages = new THashMap<>();
pages.put(-1, new CatalogRootLayout());
String query = "SELECT id, parent_id, caption, caption AS caption_save, page_layout, icon_color, icon_image, 1 AS min_rank, order_num, visible, enabled, '0' AS club_only, 'BUILDERS_CLUB' AS catalog_mode, page_headline, page_teaser, page_special, page_text1, page_text2, page_text_details, page_text_teaser, '' AS includes FROM catalog_pages_bc ORDER BY parent_id, id";
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(query)) {
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
Class<? extends CatalogPage> pageClazz = pageDefinitions.get(set.getString("page_layout"));
if (pageClazz == null) {
LOGGER.info("Unknown Builders Club Page Layout: {}", set.getString("page_layout"));
continue;
}
try {
CatalogPage page = pageClazz.getConstructor(ResultSet.class).newInstance(set);
pages.put(page.getId(), page);
} catch (Exception e) {
LOGGER.error("Failed to load Builders Club layout: {}", set.getString("page_layout"));
}
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
pages.forEachValue((object) -> {
CatalogPage page = pages.get(object.parentId);
if (page != null) {
if (page.id != object.id) {
page.addChildPage(object);
}
} else {
if (object.parentId != -2) {
LOGGER.info("Builders Club parent page not found for {} (ID: {}, parent_id: {})", object.getPageName(), object.id, object.parentId);
}
}
return true;
});
this.buildersClubCatalogPages.putAll(pages);
LOGGER.info("Loaded {} Builders Club Catalog Pages!", this.buildersClubCatalogPages.size());
}
private synchronized void loadCatalogFeaturedPages() {
this.catalogFeaturedPages.clear();
@@ -388,6 +448,53 @@ public class CatalogManager {
}
}
private synchronized void loadBuildersClubCatalogItems() {
this.buildersClubOfferDefs.clear();
String query = "SELECT id, item_ids, page_id, catalog_name, 0 AS cost_credits, 0 AS cost_points, 0 AS points_type, 1 AS amount, 0 AS limited_stack, 0 AS limited_sells, extradata, '0' AS club_only, '1' AS have_offer, id AS offer_id, order_number FROM catalog_items_bc";
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
Statement statement = connection.createStatement();
ResultSet set = statement.executeQuery(query)) {
CatalogItem item;
while (set.next()) {
if (set.getString("item_ids").equals("0")) {
continue;
}
CatalogPage page = this.buildersClubCatalogPages.get(set.getInt("page_id"));
if (page == null) {
continue;
}
item = page.getCatalogItem(set.getInt("id"));
if (item == null) {
item = new CatalogItem(set);
page.addItem(item);
page.addOfferId(item.getOfferId());
this.buildersClubOfferDefs.put(item.getOfferId(), item.getId());
} else {
item.update(set);
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
for (CatalogPage page : this.buildersClubCatalogPages.valueCollection()) {
for (Integer id : page.getIncluded()) {
CatalogPage includedPage = this.buildersClubCatalogPages.get(id);
if (includedPage != null) {
page.getCatalogItems().putAll(includedPage.getCatalogItems());
}
}
}
}
private void loadClubOffers() {
this.clubOffers.clear();
@@ -582,6 +689,10 @@ public class CatalogManager {
return this.catalogPages.get(pageId);
}
public CatalogPage getCatalogPage(int pageId, CatalogPageType pageType) {
return this.getCatalogPagesMap(pageType).get(pageId);
}
public CatalogPage getCatalogPage(String captionSafe) {
return this.catalogPages.valueCollection().stream()
.filter(p -> p != null && p.getPageName() != null && p.getPageName().equalsIgnoreCase(captionSafe))
@@ -600,9 +711,15 @@ public class CatalogManager {
}
public CatalogItem getCatalogItem(int id) {
return this.getCatalogItem(id, CatalogPageType.NORMAL);
}
public CatalogItem getCatalogItem(int id, CatalogPageType pageType) {
final CatalogItem[] item = {null};
synchronized (this.catalogPages) {
this.catalogPages.forEachValue(new TObjectProcedure<CatalogPage>() {
final TIntObjectMap<CatalogPage> pagesMap = this.getCatalogPagesMap(pageType);
synchronized (pagesMap) {
pagesMap.forEachValue(new TObjectProcedure<CatalogPage>() {
@Override
public boolean execute(CatalogPage object) {
item[0] = object.getCatalogItem(id);
@@ -617,17 +734,28 @@ public class CatalogManager {
public List<CatalogPage> getCatalogPages(int parentId, final Habbo habbo) {
final List<CatalogPage> pages = new ArrayList<>();
return this.getCatalogPages(parentId, habbo, CatalogPageType.NORMAL);
}
this.catalogPages.get(parentId).childPages.forEachValue(new TObjectProcedure<CatalogPage>() {
public List<CatalogPage> getCatalogPages(int parentId, final Habbo habbo, final CatalogPageType pageType) {
final List<CatalogPage> pages = new ArrayList<>();
final TIntObjectMap<CatalogPage> pagesMap = this.getCatalogPagesMap(pageType);
CatalogPage parentPage = pagesMap.get(parentId);
if (parentPage == null) {
return pages;
}
parentPage.childPages.forEachValue(new TObjectProcedure<CatalogPage>() {
@Override
public boolean execute(CatalogPage object) {
boolean isVisiblePage = object.visible;
boolean hasRightRank = object.getRank() <= habbo.getHabboInfo().getRank().getId();
boolean clubRightsOkay = !object.isClubOnly() || habbo.getHabboInfo().getHabboStats().hasActiveClub();
boolean pageTypeMatches = (pageType == CatalogPageType.BUILDER) || object.getCatalogPageType().matches(pageType);
if (isVisiblePage && hasRightRank && clubRightsOkay) {
if (isVisiblePage && hasRightRank && clubRightsOkay && pageTypeMatches) {
pages.add(object);
}
return true;
@@ -701,22 +829,42 @@ public class CatalogManager {
}
public CatalogPage createCatalogPage(String caption, String captionSave, int roomId, int icon, CatalogPageLayouts layout, int minRank, int parentId) {
public CatalogPage createCatalogPage(String caption, String captionSave, int roomId, int icon, CatalogPageLayouts layout, int minRank, int parentId, CatalogPageType pageType, CatalogPageType catalogMode) {
CatalogPage catalogPage = null;
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO catalog_pages (parent_id, caption, caption_save, icon_image, visible, enabled, min_rank, page_layout, room_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS)) {
boolean buildersClubPage = (pageType == CatalogPageType.BUILDER);
String insertQuery = buildersClubPage
? "INSERT INTO catalog_pages_bc (parent_id, caption, page_layout, icon_color, icon_image, order_num, visible, enabled) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
: "INSERT INTO catalog_pages (parent_id, caption, caption_save, icon_image, visible, enabled, min_rank, page_layout, room_id, catalog_mode) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
String selectQuery = buildersClubPage
? "SELECT id, parent_id, caption, caption AS caption_save, page_layout, icon_color, icon_image, 1 AS min_rank, order_num, visible, enabled, '0' AS club_only, 'BUILDERS_CLUB' AS catalog_mode, page_headline, page_teaser, page_special, page_text1, page_text2, page_text_details, page_text_teaser, '' AS includes FROM catalog_pages_bc WHERE id = ?"
: "SELECT * FROM catalog_pages WHERE id = ?";
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(insertQuery, Statement.RETURN_GENERATED_KEYS)) {
statement.setInt(1, parentId);
statement.setString(2, caption);
statement.setString(3, captionSave);
statement.setInt(4, icon);
statement.setString(5, "1");
statement.setString(6, "1");
statement.setInt(7, minRank);
statement.setString(8, layout.name());
statement.setInt(9, roomId);
if (buildersClubPage) {
statement.setString(3, layout.name());
statement.setInt(4, 1);
statement.setInt(5, icon);
statement.setInt(6, 1);
statement.setString(7, "1");
statement.setString(8, "1");
} else {
statement.setString(3, captionSave);
statement.setInt(4, icon);
statement.setString(5, "1");
statement.setString(6, "1");
statement.setInt(7, minRank);
statement.setString(8, layout.name());
statement.setInt(9, roomId);
statement.setString(10, catalogMode.name());
}
statement.execute();
try (ResultSet set = statement.getGeneratedKeys()) {
if (set.next()) {
try (PreparedStatement stmt = connection.prepareStatement("SELECT * FROM catalog_pages WHERE id = ?")) {
try (PreparedStatement stmt = connection.prepareStatement(selectQuery)) {
stmt.setInt(1, set.getInt(1));
try (ResultSet page = stmt.executeQuery()) {
if (page.next()) {
@@ -741,7 +889,7 @@ public class CatalogManager {
}
if (catalogPage != null) {
this.catalogPages.put(catalogPage.getId(), catalogPage);
this.getCatalogPagesMap(pageType).put(catalogPage.getId(), catalogPage);
}
return catalogPage;
@@ -988,7 +1136,7 @@ public class CatalogManager {
if (extradata.length() > Emulator.getConfig().getInt("hotel.trophies.length.max", 300)) {
extradata = extradata.substring(0, Emulator.getConfig().getInt("hotel.trophies.length.max", 300));
}
extradata = habbo.getClient().getHabbo().getHabboInfo().getUsername() + (char) 9 + Calendar.getInstance().get(Calendar.DAY_OF_MONTH) + "-" + (Calendar.getInstance().get(Calendar.MONTH) + 1) + "-" + Calendar.getInstance().get(Calendar.YEAR) + (char) 9 + Emulator.getGameEnvironment().getWordFilter().filter(extradata.replace(((char) 9) + "", ""), habbo);
}
@@ -1057,7 +1205,7 @@ public class CatalogManager {
if (badgeFound && item.getBaseItems().size() == 1) {
habbo.getClient().sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.ALREADY_HAVE_BADGE));
return;
return;
}
UserCatalogItemPurchasedEvent purchasedEvent = new UserCatalogItemPurchasedEvent(habbo, item, itemsList, totalCredits, totalPoints, badges);
@@ -1141,14 +1289,23 @@ public class CatalogManager {
}
public List<ClubOffer> getClubOffers() {
return this.getClubOffers(ClubOffer.WINDOW_HABBO_CLUB);
}
public TIntObjectMap<CatalogPage> getCatalogPagesMap(CatalogPageType pageType) {
return (pageType == CatalogPageType.BUILDER) ? this.buildersClubCatalogPages : this.catalogPages;
}
public List<ClubOffer> getClubOffers(int windowId) {
List<ClubOffer> offers = new ArrayList<>();
for (Map.Entry<Integer, ClubOffer> entry : this.clubOffers.entrySet()) {
if (!entry.getValue().isDeal()) {
if (!entry.getValue().isDeal() && entry.getValue().belongsToWindow(windowId)) {
offers.add(entry.getValue());
}
}
offers.sort(Comparator.comparingInt(ClubOffer::getId));
return offers;
}
@@ -32,6 +32,7 @@ public abstract class CatalogPage implements Comparable<CatalogPage>, ISerialize
protected boolean visible;
protected boolean enabled;
protected boolean clubOnly;
protected CatalogPageType catalogPageType = CatalogPageType.NORMAL;
protected String layout;
protected String headerImage;
protected String teaserImage;
@@ -59,6 +60,11 @@ public abstract class CatalogPage implements Comparable<CatalogPage>, ISerialize
this.visible = set.getBoolean("visible");
this.enabled = set.getBoolean("enabled");
this.clubOnly = set.getBoolean("club_only");
try {
this.catalogPageType = CatalogPageType.fromString(set.getString("catalog_mode"));
} catch (SQLException ignored) {
this.catalogPageType = CatalogPageType.NORMAL;
}
this.layout = set.getString("page_layout");
this.headerImage = set.getString("page_headline");
this.teaserImage = set.getString("page_teaser");
@@ -68,8 +74,9 @@ public abstract class CatalogPage implements Comparable<CatalogPage>, ISerialize
this.textDetails = set.getString("page_text_details");
this.textTeaser = set.getString("page_text_teaser");
if (!set.getString("includes").isEmpty()) {
for (String id : set.getString("includes").split(";")) {
String includes = set.getString("includes");
if (includes != null && !includes.isEmpty()) {
for (String id : includes.split(";")) {
try {
this.included.add(Integer.valueOf(id));
} catch (Exception e) {
@@ -128,6 +135,10 @@ public abstract class CatalogPage implements Comparable<CatalogPage>, ISerialize
return this.clubOnly;
}
public CatalogPageType getCatalogPageType() {
return this.catalogPageType;
}
public String getLayout() {
return this.layout;
}
@@ -43,5 +43,6 @@ public enum CatalogPageLayouts {
builders_club_loyalty,
monkey,
niko,
mad_money
mad_money,
custom_prefix
}
@@ -4,6 +4,33 @@ public enum CatalogPageType {
NORMAL,
BUILDER,
BUILDER
BOTH;
public static CatalogPageType fromString(String value) {
if (value == null || value.isEmpty()) {
return NORMAL;
}
switch (value.trim().toUpperCase()) {
case "BUILDERS_CLUB":
case "BUILDER":
case "BC":
return BUILDER;
case "BOTH":
return BOTH;
case "NORMAL":
default:
return NORMAL;
}
}
public boolean matches(CatalogPageType requestedType) {
if (this == BOTH || requestedType == BOTH) {
return true;
}
return this == requestedType;
}
}
@@ -10,6 +10,30 @@ import java.util.Calendar;
import java.util.TimeZone;
public class ClubOffer implements ISerialize {
public static final int WINDOW_HABBO_CLUB = 1;
public static final int WINDOW_BUILDERS_CLUB = 2;
public static final int WINDOW_BUILDERS_CLUB_ADDONS = 3;
public enum OfferType {
HC,
VIP,
BUILDERS_CLUB,
BUILDERS_CLUB_ADDON;
public static OfferType fromDatabase(String value) {
if (value == null) {
return HC;
}
for (OfferType type : OfferType.values()) {
if (type.name().equalsIgnoreCase(value)) {
return type;
}
}
return HC;
}
}
private final int id;
@@ -28,12 +52,16 @@ public class ClubOffer implements ISerialize {
private final int pointsType;
private final OfferType type;
private final boolean vip;
private final boolean deal;
private final boolean giftable;
public ClubOffer(ResultSet set) throws SQLException {
this.id = set.getInt("id");
this.name = set.getString("name");
@@ -41,8 +69,10 @@ public class ClubOffer implements ISerialize {
this.credits = set.getInt("credits");
this.points = set.getInt("points");
this.pointsType = set.getInt("points_type");
this.vip = set.getString("type").equalsIgnoreCase("vip");
this.type = OfferType.fromDatabase(set.getString("type"));
this.vip = this.type == OfferType.VIP;
this.deal = set.getString("deal").equals("1");
this.giftable = set.getString("giftable").equals("1");
}
public int getId() {
@@ -69,6 +99,10 @@ public class ClubOffer implements ISerialize {
return this.pointsType;
}
public OfferType getType() {
return this.type;
}
public boolean isVip() {
return this.vip;
}
@@ -77,13 +111,49 @@ public class ClubOffer implements ISerialize {
return this.deal;
}
public boolean isGiftable() {
return this.giftable;
}
public boolean isBuildersClubSubscription() {
return this.type == OfferType.BUILDERS_CLUB;
}
public boolean isBuildersClubAddon() {
return this.type == OfferType.BUILDERS_CLUB_ADDON;
}
public boolean isHabboClubOffer() {
return this.type == OfferType.HC || this.type == OfferType.VIP;
}
public boolean isSubscriptionOffer() {
return !this.isBuildersClubAddon();
}
public int getWindowId() {
if (this.isBuildersClubAddon()) {
return WINDOW_BUILDERS_CLUB_ADDONS;
}
if (this.isBuildersClubSubscription()) {
return WINDOW_BUILDERS_CLUB;
}
return WINDOW_HABBO_CLUB;
}
public boolean belongsToWindow(int windowId) {
return this.getWindowId() == windowId;
}
@Override
public void serialize(ServerMessage message) {
serialize(message, Emulator.getIntUnixTimestamp());
}
public void serialize(ServerMessage message, int hcExpireTimestamp) {
hcExpireTimestamp = Math.max(Emulator.getIntUnixTimestamp(), hcExpireTimestamp);
public void serialize(ServerMessage message, int expireTimestamp) {
expireTimestamp = Math.max(Emulator.getIntUnixTimestamp(), expireTimestamp);
message.appendInt(this.id);
message.appendString(this.name);
message.appendBoolean(false); //unused
@@ -96,27 +166,29 @@ public class ClubOffer implements ISerialize {
long secondsTotal = seconds;
int totalYears = (int) Math.floor((int) seconds / (86400.0 * 31 * 12));
int totalYears = (int) Math.floor(seconds / (86400.0 * 31 * 12));
seconds -= totalYears * (86400 * 31 * 12);
int totalMonths = (int) Math.floor((int) seconds / (86400.0 * 31));
int totalMonths = (int) Math.floor(seconds / (86400.0 * 31));
seconds -= totalMonths * (86400 * 31);
int totalDays = (int) Math.floor((int) seconds / 86400.0);
int totalDays = (int) Math.floor(seconds / 86400.0);
seconds -= totalDays * 86400L;
message.appendInt((int) secondsTotal / 86400 / 31);
message.appendInt((int) seconds);
message.appendBoolean(false); //giftable
message.appendInt((int) seconds);
message.appendInt(totalMonths);
message.appendInt(totalDays);
message.appendBoolean(this.giftable);
message.appendInt(totalDays);
hcExpireTimestamp += secondsTotal;
if (this.isSubscriptionOffer()) {
expireTimestamp += secondsTotal;
}
Calendar cal = Calendar.getInstance();
cal.setTimeZone(TimeZone.getTimeZone("UTC"));
cal.setTimeInMillis(hcExpireTimestamp * 1000L);
cal.setTimeInMillis(expireTimestamp * 1000L);
message.appendInt(cal.get(Calendar.YEAR));
message.appendInt(cal.get(Calendar.MONTH) + 1);
message.appendInt(cal.get(Calendar.DAY_OF_MONTH));
}
}
}
@@ -0,0 +1,27 @@
package com.eu.habbo.habbohotel.catalog.layouts;
import com.eu.habbo.habbohotel.catalog.CatalogPage;
import com.eu.habbo.messages.ServerMessage;
import java.sql.ResultSet;
import java.sql.SQLException;
public class CustomPrefixLayout extends CatalogPage {
public CustomPrefixLayout(ResultSet set) throws SQLException {
super(set);
}
@Override
public void serialize(ServerMessage message) {
message.appendString("custom_prefix");
message.appendInt(3);
message.appendString(super.getHeaderImage());
message.appendString(super.getTeaserImage());
message.appendString(super.getSpecialImage());
message.appendInt(3);
message.appendString(super.getTextOne());
message.appendString(super.getTextDetails());
message.appendString(super.getTextTeaser());
}
}
@@ -75,6 +75,12 @@ public class MarketPlace {
return;
}
ownerSet.first();
if (ownerSet.getInt("user_id") != habbo.getHabboInfo().getId()) {
LOGGER.warn("User {} attempted to take back marketplace offer {} owned by user {}", habbo.getHabboInfo().getId(), offer.getOfferId(), ownerSet.getInt("user_id"));
return;
}
try (PreparedStatement statement = connection.prepareStatement("DELETE FROM marketplace_items WHERE id = ? AND state != 2")) {
statement.setInt(1, offer.getOfferId());
int count = statement.executeUpdate();
@@ -115,10 +121,10 @@ public class MarketPlace {
List<MarketPlaceOffer> offers = new ArrayList<>(10);
String query = "SELECT B.* FROM marketplace_items a INNER JOIN (SELECT b.item_id AS base_item_id, b.limited_data AS ltd_data, marketplace_items.*, AVG(price) as avg, MIN(marketplace_items.price) as minPrice, MAX(marketplace_items.price) as maxPrice, COUNT(*) as number, (SELECT COUNT(*) FROM marketplace_items c INNER JOIN items as items_b ON c.item_id = items_b.id WHERE state = 2 AND items_b.item_id = base_item_id AND DATE(from_unixtime(sold_timestamp)) = CURDATE()) as sold_count_today FROM marketplace_items INNER JOIN items b ON marketplace_items.item_id = b.id INNER JOIN items_base bi ON b.item_id = bi.id INNER JOIN catalog_items ci ON bi.id = ci.item_ids WHERE price = (SELECT MIN(e.price) FROM marketplace_items e, items d WHERE e.item_id = d.id AND d.item_id = b.item_id AND e.state = 1 AND e.timestamp > ? GROUP BY d.item_id) AND state = 1 AND timestamp > ?";
if (minPrice > 0) {
query += " AND CEIL(price + (price / 100)) >= " + minPrice;
query += " AND CEIL(price + (price / 100)) >= ?";
}
if (maxPrice > 0 && maxPrice > minPrice) {
query += " AND CEIL(price + (price / 100)) <= " + maxPrice;
query += " AND CEIL(price + (price / 100)) <= ?";
}
if (!search.isEmpty()) {
query += " AND ( bi.public_name LIKE ? OR ci.catalog_name LIKE ? ) ";
@@ -155,11 +161,18 @@ public class MarketPlace {
query += " LIMIT 250";
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement(query)) {
statement.setInt(1, Emulator.getIntUnixTimestamp() - 172800);
statement.setInt(2, Emulator.getIntUnixTimestamp() - 172800);
int paramIndex = 1;
statement.setInt(paramIndex++, Emulator.getIntUnixTimestamp() - 172800);
statement.setInt(paramIndex++, Emulator.getIntUnixTimestamp() - 172800);
if (minPrice > 0) {
statement.setInt(paramIndex++, minPrice);
}
if (maxPrice > 0 && maxPrice > minPrice) {
statement.setInt(paramIndex++, maxPrice);
}
if (!search.isEmpty()) {
statement.setString(3, "%" + search + "%");
statement.setString(4, "%" + search + "%");
statement.setString(paramIndex++, "%" + search + "%");
statement.setString(paramIndex++, "%" + search + "%");
}
try (ResultSet set = statement.executeQuery()) {
@@ -15,7 +15,7 @@ public class AboutCommand extends Command {
}
public static String credits = "Arcturus Morningstar is an opensource project based on Arcturus By TheGeneral \n" +
"The Following people have all contributed to this emulator:\n" +
"TheGeneral\n Beny\n Alejandro\n Capheus\n Skeletor\n Harmonic\n Mike\n Remco\n zGrav \n Quadral \n Harmony\n Swirny\n ArpyAge\n Mikkel\n Rodolfo\n Rasmus\n Kitt Mustang\n Snaiker\n nttzx\n necmi\n Dome\n Jose Flores\n Cam\n Oliver\n Narzo\n Tenshie\n MartenM\n Ridge\n SenpaiDipper\n Snaiker\n Thijmen\n DuckieTM\n simoleo89\n Medievalshell\n Lorenzune";
"TheGeneral\n Beny\n Alejandro\n Capheus\n Skeletor\n Harmonic\n Mike\n Remco\n zGrav \n Quadral \n Harmony\n Swirny\n ArpyAge\n Mikkel\n Rodolfo\n Rasmus\n Kitt Mustang\n Snaiker\n nttzx\n necmi\n Dome\n Jose Flores\n Cam\n Oliver\n Narzo\n Tenshie\n MartenM\n Ridge\n SenpaiDipper\n Snaiker\n Thijmen\n DuckieTM\n simoleo89\n Medievalshell\n Lorenzo (the wired master)";
@Override
public boolean handle(GameClient gameClient, String[] params) {
@@ -36,7 +36,8 @@ public class BadgeCommand extends Command {
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(params[1]);
if (habbo != null) {
if (habbo.addBadge(params[2])) {
String senderName = gameClient.getHabbo().getHabboInfo().getUsername();
if (habbo.addBadge(params[2], senderName)) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.succes.cmd_badge.given").replace("%user%", params[1]).replace("%badge%", params[2]), RoomChatMessageBubbles.ALERT);
} else {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_badge.already_owned").replace("%user%", params[1]).replace("%badge%", params[2]), RoomChatMessageBubbles.ALERT);
@@ -297,7 +297,11 @@ public class CommandHandler {
addCommand(new SoftKickCommand());
addCommand(new SubscriptionCommand());
addCommand(new UpdateChatBubblesCommand());
addCommand(new GivePrefixCommand());
addCommand(new ListPrefixesCommand());
addCommand(new RemovePrefixCommand());
addCommand(new PrefixBlacklistCommand());
addCommand(new WiredCommand());
addCommand(new TestCommand());
}
@@ -0,0 +1,66 @@
package com.eu.habbo.habbohotel.commands;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.UserPrefix;
import com.eu.habbo.messages.outgoing.inventory.prefixes.PrefixReceivedComposer;
import com.eu.habbo.messages.outgoing.inventory.prefixes.UserPrefixesComposer;
public class GivePrefixCommand extends Command {
public GivePrefixCommand() {
super("cmd_give_prefix", Emulator.getTexts().getValue("commands.keys.cmd_give_prefix").split(";"));
}
@Override
public boolean handle(GameClient gameClient, String[] params) throws Exception {
if (params.length < 4) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_give_prefix.usage"), RoomChatMessageBubbles.ALERT);
return true;
}
String targetName = params[1];
String text = params[2];
String color = params[3];
String icon = params.length > 4 ? params[4] : "";
String effect = params.length > 5 ? params[5] : "";
// Validate color
String[] colorParts = color.split(",");
for (String part : colorParts) {
if (!part.matches("^#[0-9A-Fa-f]{6}$")) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_give_prefix.invalid_color"), RoomChatMessageBubbles.ALERT);
return true;
}
}
if (text.length() > 15) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_give_prefix.too_long"), RoomChatMessageBubbles.ALERT);
return true;
}
Habbo target = Emulator.getGameEnvironment().getHabboManager().getHabbo(targetName);
if (target == null) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_give_prefix.user_not_found"), RoomChatMessageBubbles.ALERT);
return true;
}
UserPrefix prefix = new UserPrefix(target.getHabboInfo().getId(), text, color, icon, effect);
prefix.run();
target.getInventory().getPrefixesComponent().addPrefix(prefix);
target.getClient().sendResponse(new PrefixReceivedComposer(prefix));
target.getClient().sendResponse(new UserPrefixesComposer(target));
gameClient.getHabbo().whisper(
Emulator.getTexts().getValue("commands.succes.cmd_give_prefix")
.replace("%user%", targetName)
.replace("%prefix%", text),
RoomChatMessageBubbles.ALERT
);
return true;
}
}
@@ -0,0 +1,59 @@
package com.eu.habbo.habbohotel.commands;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.UserPrefix;
import java.util.List;
public class ListPrefixesCommand extends Command {
public ListPrefixesCommand() {
super("cmd_list_prefixes", Emulator.getTexts().getValue("commands.keys.cmd_list_prefixes").split(";"));
}
@Override
public boolean handle(GameClient gameClient, String[] params) throws Exception {
if (params.length < 2) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_list_prefixes.usage"), RoomChatMessageBubbles.ALERT);
return true;
}
String targetName = params[1];
Habbo target = Emulator.getGameEnvironment().getHabboManager().getHabbo(targetName);
if (target == null) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_list_prefixes.user_not_found"), RoomChatMessageBubbles.ALERT);
return true;
}
List<UserPrefix> prefixes = target.getInventory().getPrefixesComponent().getPrefixes();
if (prefixes.isEmpty()) {
gameClient.getHabbo().whisper(
Emulator.getTexts().getValue("commands.succes.cmd_list_prefixes.empty").replace("%user%", targetName),
RoomChatMessageBubbles.ALERT
);
return true;
}
StringBuilder sb = new StringBuilder();
sb.append(Emulator.getTexts().getValue("commands.succes.cmd_list_prefixes.header").replace("%user%", targetName)).append("\r");
for (UserPrefix prefix : prefixes) {
sb.append("ID: ").append(prefix.getId())
.append(" | {").append(prefix.getText()).append("}")
.append(" | Color: ").append(prefix.getColor())
.append(prefix.getIcon().isEmpty() ? "" : " | Icon: " + prefix.getIcon())
.append(prefix.getEffect().isEmpty() ? "" : " | Effect: " + prefix.getEffect())
.append(prefix.isActive() ? " [ACTIVE]" : "")
.append("\r");
}
gameClient.getHabbo().whisper(sb.toString(), RoomChatMessageBubbles.ALERT);
return true;
}
}
@@ -0,0 +1,98 @@
package com.eu.habbo.habbohotel.commands;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class PrefixBlacklistCommand extends Command {
private static final Logger LOGGER = LoggerFactory.getLogger(PrefixBlacklistCommand.class);
public PrefixBlacklistCommand() {
super("cmd_prefix_blacklist", Emulator.getTexts().getValue("commands.keys.cmd_prefix_blacklist").split(";"));
}
@Override
public boolean handle(GameClient gameClient, String[] params) throws Exception {
if (params.length < 2) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.usage"), RoomChatMessageBubbles.ALERT);
return true;
}
String action = params[1].toLowerCase();
if (action.equals("list")) {
StringBuilder sb = new StringBuilder();
sb.append(Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.header")).append("\r");
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT word FROM custom_prefix_blacklist ORDER BY word")) {
try (ResultSet set = statement.executeQuery()) {
int count = 0;
while (set.next()) {
sb.append("- ").append(set.getString("word")).append("\r");
count++;
}
if (count == 0) {
sb.append(Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.empty"));
}
}
} catch (SQLException e) {
LOGGER.error("Error listing prefix blacklist", e);
}
gameClient.getHabbo().whisper(sb.toString(), RoomChatMessageBubbles.ALERT);
return true;
}
if (params.length < 3) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.usage"), RoomChatMessageBubbles.ALERT);
return true;
}
String word = params[2].toLowerCase().trim();
if (word.isEmpty()) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.empty_word"), RoomChatMessageBubbles.ALERT);
return true;
}
if (action.equals("add")) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("INSERT INTO custom_prefix_blacklist (word) VALUES (?)")) {
statement.setString(1, word);
statement.execute();
} catch (SQLException e) {
LOGGER.error("Error adding prefix blacklist word", e);
}
gameClient.getHabbo().whisper(
Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.added").replace("%word%", word),
RoomChatMessageBubbles.ALERT
);
} else if (action.equals("remove")) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("DELETE FROM custom_prefix_blacklist WHERE word = ?")) {
statement.setString(1, word);
statement.execute();
} catch (SQLException e) {
LOGGER.error("Error removing prefix blacklist word", e);
}
gameClient.getHabbo().whisper(
Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.removed").replace("%word%", word),
RoomChatMessageBubbles.ALERT
);
} else {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.usage"), RoomChatMessageBubbles.ALERT);
}
return true;
}
}
@@ -0,0 +1,81 @@
package com.eu.habbo.habbohotel.commands;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.UserPrefix;
import com.eu.habbo.messages.outgoing.inventory.prefixes.UserPrefixesComposer;
import java.util.List;
public class RemovePrefixCommand extends Command {
public RemovePrefixCommand() {
super("cmd_remove_prefix", Emulator.getTexts().getValue("commands.keys.cmd_remove_prefix").split(";"));
}
@Override
public boolean handle(GameClient gameClient, String[] params) throws Exception {
if (params.length < 3) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_remove_prefix.usage"), RoomChatMessageBubbles.ALERT);
return true;
}
String targetName = params[1];
String prefixIdStr = params[2];
Habbo target = Emulator.getGameEnvironment().getHabboManager().getHabbo(targetName);
if (target == null) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_remove_prefix.user_not_found"), RoomChatMessageBubbles.ALERT);
return true;
}
if (prefixIdStr.equalsIgnoreCase("all")) {
List<UserPrefix> prefixes = target.getInventory().getPrefixesComponent().getPrefixes();
for (UserPrefix prefix : prefixes) {
prefix.needsDelete(true);
Emulator.getThreading().run(prefix);
}
// Clear in-memory
for (UserPrefix prefix : prefixes) {
target.getInventory().getPrefixesComponent().removePrefix(prefix);
}
target.getClient().sendResponse(new UserPrefixesComposer(target));
gameClient.getHabbo().whisper(
Emulator.getTexts().getValue("commands.succes.cmd_remove_prefix.all").replace("%user%", targetName),
RoomChatMessageBubbles.ALERT
);
} else {
int prefixId;
try {
prefixId = Integer.parseInt(prefixIdStr);
} catch (NumberFormatException e) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_remove_prefix.invalid_id"), RoomChatMessageBubbles.ALERT);
return true;
}
UserPrefix prefix = target.getInventory().getPrefixesComponent().getPrefix(prefixId);
if (prefix == null) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_remove_prefix.not_found"), RoomChatMessageBubbles.ALERT);
return true;
}
target.getInventory().getPrefixesComponent().removePrefix(prefix);
prefix.needsDelete(true);
Emulator.getThreading().run(prefix);
target.getClient().sendResponse(new UserPrefixesComposer(target));
gameClient.getHabbo().whisper(
Emulator.getTexts().getValue("commands.succes.cmd_remove_prefix").replace("%user%", targetName).replace("%id%", String.valueOf(prefixId)),
RoomChatMessageBubbles.ALERT
);
}
return true;
}
}
@@ -4,6 +4,7 @@ import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.catalog.CatalogItem;
import com.eu.habbo.habbohotel.catalog.CatalogPage;
import com.eu.habbo.habbohotel.catalog.CatalogPageLayouts;
import com.eu.habbo.habbohotel.catalog.CatalogPageType;
import com.eu.habbo.habbohotel.catalog.layouts.RoomBundleLayout;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles;
@@ -41,7 +42,7 @@ public class RoomBundleCommand extends Command {
points = Integer.parseInt(params[3]);
pointsType = Integer.parseInt(params[4]);
CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().createCatalogPage("Room Bundle: " + gameClient.getHabbo().getHabboInfo().getCurrentRoom().getName(), "room_bundle_" + gameClient.getHabbo().getHabboInfo().getCurrentRoom().getId(), gameClient.getHabbo().getHabboInfo().getCurrentRoom().getId(), 0, CatalogPageLayouts.room_bundle, gameClient.getHabbo().getHabboInfo().getRank().getId(), parentId);
CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().createCatalogPage("Room Bundle: " + gameClient.getHabbo().getHabboInfo().getCurrentRoom().getName(), "room_bundle_" + gameClient.getHabbo().getHabboInfo().getCurrentRoom().getId(), gameClient.getHabbo().getHabboInfo().getCurrentRoom().getId(), 0, CatalogPageLayouts.room_bundle, gameClient.getHabbo().getHabboInfo().getRank().getId(), parentId, CatalogPageType.NORMAL, CatalogPageType.NORMAL);
if (page instanceof RoomBundleLayout) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO catalog_items (page_id, item_ids, catalog_name, cost_credits, cost_points, points_type ) VALUES (?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS)) {
@@ -29,7 +29,7 @@ public class UnmuteCommand extends Command {
}
if (habbo.getHabboInfo().getCurrentRoom() != null && habbo.getHabboInfo().getCurrentRoom().isMuted(habbo)) {
habbo.getHabboInfo().getCurrentRoom().muteHabbo(habbo, 1);
habbo.getHabboInfo().getCurrentRoom().unmuteHabbo(habbo);
}
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.succes.cmd_unmute").replace("%user%", params[1]), RoomChatMessageBubbles.ALERT);
@@ -41,4 +41,4 @@ public class UnmuteCommand extends Command {
return true;
}
}
}
@@ -84,7 +84,7 @@ public class UserInfoCommand extends Command {
if (onlineHabbo != null) {
message.append("\r" + "<b>Other accounts (");
ArrayList<HabboInfo> users = Emulator.getGameEnvironment().getHabboManager().getCloneAccounts(onlineHabbo, 10);
List<HabboInfo> users = Emulator.getGameEnvironment().getHabboManager().getCloneAccounts(onlineHabbo, 10);
users.sort(new Comparator<HabboInfo>() {
@Override
public int compare(HabboInfo o1, HabboInfo o2) {
@@ -0,0 +1,30 @@
package com.eu.habbo.habbohotel.commands;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles;
import com.eu.habbo.messages.outgoing.users.InClientLinkComposer;
public class WiredCommand extends Command {
public WiredCommand() {
super(null, new String[]{"wired"});
}
@Override
public boolean handle(GameClient gameClient, String[] params) throws Exception {
Room room = gameClient.getHabbo().getHabboInfo().getCurrentRoom();
if (room == null) {
gameClient.getHabbo().whisper("You need to be inside a room to use :wired.", RoomChatMessageBubbles.ALERT);
return true;
}
if (!room.canInspectWired(gameClient.getHabbo())) {
gameClient.sendResponse(new InClientLinkComposer("wired-tools/invalid"));
return true;
}
gameClient.sendResponse(new InClientLinkComposer("wired-tools/show"));
return true;
}
}
@@ -26,6 +26,7 @@ public class GameClient {
private Habbo habbo;
private boolean handshakeFinished;
private String machineId = "";
private String ssoTicket = "";
public final ConcurrentHashMap<Integer, Integer> incomingPacketCounter = new ConcurrentHashMap<>(25);
public final ConcurrentHashMap<Class<? extends MessageHandler>, Long> messageTimestamps = new ConcurrentHashMap<>();
@@ -82,6 +83,14 @@ public class GameClient {
this.machineId = machineId;
}
public String getSsoTicket() {
return this.ssoTicket;
}
public void setSsoTicket(String ssoTicket) {
this.ssoTicket = ssoTicket != null ? ssoTicket : "";
}
public void sendResponse(MessageComposer composer) {
this.sendResponse(composer.compose());
}
@@ -145,8 +154,15 @@ public class GameClient {
if (this.habbo != null) {
if (this.habbo.isOnline()) {
this.habbo.getHabboInfo().setOnline(false);
this.habbo.disconnect();
// Try to park the habbo in the grace period instead of immediate disconnect
boolean parked = SessionResumeManager.getInstance().parkHabbo(this.habbo, this.ssoTicket);
if (!parked) {
// No grace period configured immediate disconnect as before
this.habbo.getHabboInfo().setOnline(false);
this.habbo.disconnect();
}
// If parked, do NOT call disconnect() the habbo stays in the room
}
this.habbo = null;
@@ -116,6 +116,22 @@ public class GameClientManager {
}
/**
* Find an existing GameClient that authenticated with the given SSO ticket.
* Used to detect reconnections where the old connection hasn't been closed yet.
*/
public GameClient findClientBySsoTicket(String ssoTicket) {
if (ssoTicket == null || ssoTicket.isEmpty()) return null;
for (GameClient client : this.clients.values()) {
if (ssoTicket.equals(client.getSsoTicket()) && client.getHabbo() != null) {
return client;
}
}
return null;
}
public List<Habbo> getHabbosWithMachineId(String machineId) {
List<Habbo> habbos = new ArrayList<>();
@@ -0,0 +1,173 @@
package com.eu.habbo.habbohotel.gameclients;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.users.Habbo;
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);
private static SessionResumeManager instance;
private final ConcurrentHashMap<Integer, GhostSession> ghostSessions = new ConcurrentHashMap<>();
public static SessionResumeManager getInstance() {
if (instance == null) {
instance = new SessionResumeManager();
}
return instance;
}
public int getGracePeriodSeconds() {
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 boolean parkHabbo(Habbo habbo, String ssoTicket) {
int graceSeconds = getGracePeriodSeconds();
if (graceSeconds <= 0) {
return false;
}
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);
}
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
ScheduledFuture<?> future = Emulator.getThreading().run(() -> {
GhostSession ghost = ghostSessions.remove(userId);
if (ghost != null) {
LOGGER.info("[SessionResume] Grace period expired for {} (id={}) - performing full disconnect",
ghost.habbo.getHabboInfo().getUsername(), userId);
performFullDisconnect(ghost.habbo);
}
}, graceSeconds * 1000);
ghostSessions.put(userId, new GhostSession(habbo, ssoTicket, future));
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);
}
LOGGER.info("[SessionResume] Resuming session for {} (id={})",
ghost.habbo.getHabboInfo().getUsername(), userId);
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) {
ghost.disposeFuture.cancel(false);
}
performFullDisconnect(ghost.habbo);
}
ghostSessions.clear();
}
/**
* Perform the actual full disconnect that normally happens in Habbo.disconnect().
*/
private void performFullDisconnect(Habbo habbo) {
try {
habbo.getHabboInfo().setOnline(false);
habbo.disconnect();
} catch (Exception e) {
LOGGER.error("[SessionResume] Error during deferred disconnect", e);
}
// Clear the SSO ticket now that the grace period is truly over
clearSsoTicket(habbo.getHabboInfo().getId());
}
private void restoreSsoTicket(int userId, String ssoTicket) {
try (var connection = Emulator.getDatabase().getDataSource().getConnection();
var statement = connection.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) {
statement.setString(1, ssoTicket);
statement.setInt(2, userId);
statement.execute();
LOGGER.info("[SessionResume] Restored SSO ticket for user {} during grace period", userId);
} catch (Exception e) {
LOGGER.error("[SessionResume] Failed to restore SSO ticket for user " + userId, 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")) {
statement.setString(1, "");
statement.setInt(2, userId);
statement.execute();
} catch (Exception e) {
LOGGER.error("[SessionResume] Failed to clear SSO ticket for user " + userId, e);
}
}
private static class GhostSession {
final Habbo habbo;
final String ssoTicket;
final ScheduledFuture<?> disposeFuture;
GhostSession(Habbo habbo, String ssoTicket, ScheduledFuture<?> disposeFuture) {
this.habbo = habbo;
this.ssoTicket = ssoTicket;
this.disposeFuture = disposeFuture;
}
}
}
@@ -36,8 +36,16 @@ public class GamePlayer {
if (this.score < 0) this.score = 0;
if(isWired && this.score > 0) {
if(isWired) {
this.wiredScore += amount;
if (this.wiredScore < 0) {
this.wiredScore = 0;
}
if (this.wiredScore > this.score) {
this.wiredScore = this.score;
}
}
WiredManager.triggerScoreAchieved(this.habbo.getHabboInfo().getCurrentRoom(), this.habbo.getRoomUnit(), this.score, amount);
@@ -6,6 +6,10 @@ import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.users.Habbo;
public class WiredGame extends Game {
public static final int RED_EFFECT_ID = 223;
public static final int BLUE_EFFECT_ID = 224;
public static final int YELLOW_EFFECT_ID = 225;
public static final int GREEN_EFFECT_ID = 226;
public GameState state = GameState.RUNNING;
public WiredGame(Room room) {
@@ -28,7 +32,7 @@ public class WiredGame extends Game {
@Override
public boolean addHabbo(Habbo habbo, GameTeamColors teamColor) {
this.room.giveEffect(habbo, FreezeGame.effectId + teamColor.type, -1);
this.room.giveEffect(habbo, this.getEffectId(teamColor), -1);
return super.addHabbo(habbo, teamColor);
}
@@ -47,4 +51,19 @@ public class WiredGame extends Game {
public GameState getState() {
return GameState.RUNNING;
}
}
private int getEffectId(GameTeamColors teamColor) {
switch (teamColor) {
case RED:
return RED_EFFECT_ID;
case BLUE:
return BLUE_EFFECT_ID;
case YELLOW:
return YELLOW_EFFECT_ID;
case GREEN:
return GREEN_EFFECT_ID;
default:
return FreezeGame.effectId + teamColor.type;
}
}
}
@@ -135,16 +135,10 @@ public class ForumThread implements Runnable, ISerialize {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT A.*, B.`id` AS `last_comment_id` " +
"FROM guilds_forums_threads A " +
"JOIN (" +
"SELECT * " +
"LEFT JOIN (" +
"SELECT `thread_id`, MAX(`id`) AS `id`, MAX(`created_at`) AS `created_at` " +
"FROM `guilds_forums_comments` " +
"WHERE `id` IN (" +
"SELECT MAX(id) " +
"FROM `guilds_forums_comments` B " +
"GROUP BY `thread_id` AND B.`id` " +
"ORDER BY B.`id` " +
") " +
"ORDER BY `id` DESC " +
"GROUP BY `thread_id`" +
") B ON A.`id` = B.`thread_id` " +
"WHERE A.`guild_id` = ? " +
"ORDER BY A.`pinned` DESC, B.`created_at` DESC "
@@ -176,16 +170,10 @@ public class ForumThread implements Runnable, ISerialize {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement(
"SELECT A.*, B.`id` AS `last_comment_id` " +
"FROM guilds_forums_threads A " +
"JOIN (" +
"SELECT * " +
"LEFT JOIN (" +
"SELECT `thread_id`, MAX(`id`) AS `id`, MAX(`created_at`) AS `created_at` " +
"FROM `guilds_forums_comments` " +
"WHERE `id` IN (" +
"SELECT MAX(id) " +
"FROM `guilds_forums_comments` B " +
"GROUP BY `thread_id` AND b.`id`" +
"ORDER BY B.`id` " +
") " +
"ORDER BY `id` DESC " +
"GROUP BY `thread_id`" +
") B ON A.`id` = B.`thread_id` " +
"WHERE A.`id` = ? " +
"ORDER BY A.`pinned` DESC, B.`created_at` DESC " +
@@ -222,6 +210,19 @@ public class ForumThread implements Runnable, ISerialize {
guildThreads.add(thread);
}
public static void clearCacheForGuild(int guildId) {
synchronized (guildThreadsCache) {
THashSet<ForumThread> threads = guildThreadsCache.remove(guildId);
if (threads != null) {
synchronized (forumThreadsCache) {
for (ForumThread thread : threads) {
forumThreadsCache.remove(thread.threadId);
}
}
}
}
}
public static void clearCache() {
for (THashSet<ForumThread> threads : guildThreadsCache.values()) {
for (ForumThread thread : threads) {
@@ -91,7 +91,23 @@ public class Item implements ISerialize {
this.allowGift = set.getBoolean("allow_gift");
this.allowInventoryStack = set.getBoolean("allow_inventory_stack");
this.interactionType = Emulator.getGameEnvironment().getItemManager().getItemInteraction(set.getString("interaction_type").toLowerCase());
String interactionTypeName = set.getString("interaction_type");
if (interactionTypeName == null) {
interactionTypeName = "default";
}
this.interactionType = Emulator.getGameEnvironment().getItemManager().getItemInteraction(interactionTypeName.toLowerCase());
if ((this.interactionType != null)
&& "default".equalsIgnoreCase(this.interactionType.getName())
&& (this.fullName != null)
&& this.fullName.toLowerCase().startsWith("wf_")) {
ItemInteraction fallbackInteraction = Emulator.getGameEnvironment().getItemManager().getItemInteraction(this.fullName.toLowerCase());
if ((fallbackInteraction != null) && !"default".equalsIgnoreCase(fallbackInteraction.getName())) {
this.interactionType = fallbackInteraction;
}
}
this.stateCount = set.getShort("interaction_modes_count");
this.effectM = set.getShort("effect_id_male");
@@ -3,6 +3,7 @@ package com.eu.habbo.habbohotel.items;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.items.interactions.*;
import com.eu.habbo.habbohotel.items.interactions.games.InteractionGameTimer;
import com.eu.habbo.habbohotel.items.interactions.games.InteractionGameUpCounter;
import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.InteractionBattleBanzaiPuck;
import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.InteractionBattleBanzaiSphere;
import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.InteractionBattleBanzaiTeleporter;
@@ -48,15 +49,33 @@ import com.eu.habbo.habbohotel.items.interactions.totems.InteractionTotemLegs;
import com.eu.habbo.habbohotel.items.interactions.totems.InteractionTotemPlanet;
import com.eu.habbo.habbohotel.items.interactions.wired.conditions.*;
import com.eu.habbo.habbohotel.items.interactions.wired.effects.*;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectFurniArea;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectUsersArea;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectUsersNeighborhood;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectFurniNeighborhood;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectFurniByType;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredBlob;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraAnimationTime;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterFurni;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterFurniByVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterUser;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterUsersByVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFurniVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraMoveCarryUsers;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraExecuteInOrder;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraExecutionLimit;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraMovePhysics;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraMoveNoAnimation;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraOrEval;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRandom;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRoomVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraTextOutputFurniName;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraTextInputVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraTextOutputUsername;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraTextOutputVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraContextVariable;
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.WiredExtraUnseen;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableReference;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableLevelUpSystem;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableTextConnector;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.*;
import com.eu.habbo.habbohotel.items.interactions.wired.triggers.*;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
@@ -150,6 +169,7 @@ public class ItemManager {
this.interactionsList.add(new ItemInteraction("monsterplant_seed", InteractionMonsterPlantSeed.class));
this.interactionsList.add(new ItemInteraction("gift", InteractionGift.class));
this.interactionsList.add(new ItemInteraction("stack_helper", InteractionStackHelper.class));
this.interactionsList.add(new ItemInteraction("stack_walk_helper", InteractionStackWalkHelper.class));
this.interactionsList.add(new ItemInteraction("puzzle_box", InteractionPuzzleBox.class));
this.interactionsList.add(new ItemInteraction("hopper", InteractionHopper.class));
this.interactionsList.add(new ItemInteraction("costume_hopper", InteractionCostumeHopper.class));
@@ -179,6 +199,13 @@ public class ItemManager {
this.interactionsList.add(new ItemInteraction("youtube", InteractionYoutubeTV.class));
this.interactionsList.add(new ItemInteraction("jukebox", InteractionJukeBox.class));
this.interactionsList.add(new ItemInteraction("switch", InteractionSwitch.class));
this.interactionsList.add(new ItemInteraction("conf_invis_control", InteractionConfInvisControl.class));
this.interactionsList.add(new ItemInteraction("wf_conf_invis_control", InteractionConfInvisControl.class));
this.interactionsList.add(new ItemInteraction("wf_conf_area_hide", InteractionAreaHideControl.class));
this.interactionsList.add(new ItemInteraction("conf_area_hide", InteractionAreaHideControl.class));
this.interactionsList.add(new ItemInteraction("wf_conf_handitem_block", InteractionHanditemBlockControl.class));
this.interactionsList.add(new ItemInteraction("wf_conf_queue_speed", InteractionQueueSpeedControl.class));
this.interactionsList.add(new ItemInteraction("wf_conf_wired_disable", InteractionWiredDisableControl.class));
this.interactionsList.add(new ItemInteraction("switch_remote_control", InteractionSwitchRemoteControl.class));
this.interactionsList.add(new ItemInteraction("fx_box", InteractionFXBox.class));
this.interactionsList.add(new ItemInteraction("blackhole", InteractionBlackHole.class));
@@ -202,16 +229,28 @@ public class ItemManager {
this.interactionsList.add(new ItemInteraction("random_state", InteractionRandomState.class));
this.interactionsList.add(new ItemInteraction("vendingmachine_no_sides", InteractionNoSidesVendingMachine.class));
this.interactionsList.add(new ItemInteraction("tile_walkmagic", InteractionTileWalkMagic.class));
this.interactionsList.add(new ItemInteraction("antenna", InteractionDefault.class));
this.interactionsList.add(new ItemInteraction("room_invisible_click_tile", InteractionDefault.class));
this.interactionsList.add(new ItemInteraction("game_timer", InteractionGameTimer.class));
this.interactionsList.add(new ItemInteraction("game_upcounter", InteractionGameUpCounter.class));
this.interactionsList.add(new ItemInteraction("wf_trg_walks_on_furni", WiredTriggerHabboWalkOnFurni.class));
this.interactionsList.add(new ItemInteraction("wf_trg_walks_off_furni", WiredTriggerHabboWalkOffFurni.class));
this.interactionsList.add(new ItemInteraction("wf_trg_click_furni", WiredTriggerHabboClicksFurni.class));
this.interactionsList.add(new ItemInteraction("wf_trg_click_tile", WiredTriggerHabboClicksTile.class));
this.interactionsList.add(new ItemInteraction("wf_trg_click_user", WiredTriggerHabboClicksUser.class));
this.interactionsList.add(new ItemInteraction("wf_trg_user_performs_action", WiredTriggerHabboPerformsAction.class));
this.interactionsList.add(new ItemInteraction("wf_trg_enter_room", WiredTriggerHabboEntersRoom.class));
this.interactionsList.add(new ItemInteraction("wf_trg_leave_room", WiredTriggerHabboLeavesRoom.class));
this.interactionsList.add(new ItemInteraction("wf_trg_says_something", WiredTriggerHabboSaysKeyword.class));
this.interactionsList.add(new ItemInteraction("wf_trg_clock_counter", WiredTriggerClockCounter.class));
this.interactionsList.add(new ItemInteraction("wf_trg_var_changed", WiredTriggerVariableChanged.class));
this.interactionsList.add(new ItemInteraction("wf_trg_periodically", WiredTriggerRepeater.class));
this.interactionsList.add(new ItemInteraction("wf_trg_period_short", WiredTriggerRepeaterShort.class));
this.interactionsList.add(new ItemInteraction("wf_trg_period_long", WiredTriggerRepeaterLong.class));
this.interactionsList.add(new ItemInteraction("wf_trg_state_changed", WiredTriggerFurniStateToggled.class));
this.interactionsList.add(new ItemInteraction("wf_trg_stuff_state", WiredTriggerFurniStateToggled.class));
this.interactionsList.add(new ItemInteraction("wf_trg_at_given_time", WiredTriggerAtSetTime.class));
this.interactionsList.add(new ItemInteraction("wf_trg_at_time_long", WiredTriggerAtTimeLong.class));
this.interactionsList.add(new ItemInteraction("wf_trg_collision", WiredTriggerCollision.class));
@@ -242,6 +281,8 @@ public class ItemManager {
this.interactionsList.add(new ItemInteraction("wf_act_move_furni_to", WiredEffectMoveFurniTo.class));
this.interactionsList.add(new ItemInteraction("wf_act_give_reward", WiredEffectGiveReward.class));
this.interactionsList.add(new ItemInteraction("wf_act_call_stacks", WiredEffectTriggerStacks.class));
this.interactionsList.add(new ItemInteraction("wf_act_neg_call_stack", WiredEffectNegativeTriggerStacks.class));
this.interactionsList.add(new ItemInteraction("wf_act_neg_call_stacks", WiredEffectNegativeTriggerStacks.class));
this.interactionsList.add(new ItemInteraction("wf_act_kick_user", WiredEffectKickHabbo.class));
this.interactionsList.add(new ItemInteraction("wf_act_mute_triggerer", WiredEffectMuteHabbo.class));
this.interactionsList.add(new ItemInteraction("wf_act_bot_teleport", WiredEffectBotTeleport.class));
@@ -255,12 +296,40 @@ public class ItemManager {
this.interactionsList.add(new ItemInteraction("wf_act_alert", WiredEffectAlert.class));
this.interactionsList.add(new ItemInteraction("wf_act_give_handitem", WiredEffectGiveHandItem.class));
this.interactionsList.add(new ItemInteraction("wf_act_give_effect", WiredEffectGiveEffect.class));
this.interactionsList.add(new ItemInteraction("wf_act_freeze", WiredEffectFreeze.class));
this.interactionsList.add(new ItemInteraction("wf_act_unfreeze", WiredEffectUnfreeze.class));
this.interactionsList.add(new ItemInteraction("wf_act_furni_to_user", WiredEffectFurniToUser.class));
this.interactionsList.add(new ItemInteraction("wf_act_user_to_furni", WiredEffectUserToFurni.class));
this.interactionsList.add(new ItemInteraction("wf_act_furni_to_furni", WiredEffectFurniToFurni.class));
this.interactionsList.add(new ItemInteraction("wf_act_set_altitude", WiredEffectSetAltitude.class));
this.interactionsList.add(new ItemInteraction("wf_act_rel_mov", WiredEffectRelativeMove.class));
this.interactionsList.add(new ItemInteraction("wf_act_control_clock", WiredEffectControlClock.class));
this.interactionsList.add(new ItemInteraction("wf_act_adjust_clock", WiredEffectAdjustClock.class));
this.interactionsList.add(new ItemInteraction("wf_act_move_rotate_user", WiredEffectMoveRotateUser.class));
this.interactionsList.add(new ItemInteraction("wf_slc_furni_area", WiredEffectFurniArea.class));
this.interactionsList.add(new ItemInteraction("wf_slc_furni_neighborhood", WiredEffectFurniNeighborhood.class));
this.interactionsList.add(new ItemInteraction("wf_slc_furni_bytype", WiredEffectFurniByType.class));
this.interactionsList.add(new ItemInteraction("wf_slc_furni_altitude", WiredEffectFurniAltitude.class));
this.interactionsList.add(new ItemInteraction("wf_slc_furni_onfurni", WiredEffectFurniOnFurni.class));
this.interactionsList.add(new ItemInteraction("wf_slc_furni_picks", WiredEffectFurniPicks.class));
this.interactionsList.add(new ItemInteraction("wf_slc_furni_signal", WiredEffectFurniSignal.class));
this.interactionsList.add(new ItemInteraction("wf_slc_users_area", WiredEffectUsersArea.class));
this.interactionsList.add(new ItemInteraction("wf_slc_users_neighborhood", WiredEffectUsersNeighborhood.class));
this.interactionsList.add(new ItemInteraction("wf_slc_users_signal", WiredEffectUsersSignal.class));
this.interactionsList.add(new ItemInteraction("wf_slc_users_bytype", WiredEffectUsersByType.class));
this.interactionsList.add(new ItemInteraction("wf_slc_users_team", WiredEffectUsersTeam.class));
this.interactionsList.add(new ItemInteraction("wf_slc_users_byaction", WiredEffectUsersByAction.class));
this.interactionsList.add(new ItemInteraction("wf_slc_users_byname", WiredEffectUsersByName.class));
this.interactionsList.add(new ItemInteraction("wf_slc_users_handitem", WiredEffectUsersHandItem.class));
this.interactionsList.add(new ItemInteraction("wf_slc_users_onfurni", WiredEffectUsersOnFurni.class));
this.interactionsList.add(new ItemInteraction("wf_slc_users_group", WiredEffectUsersGroup.class));
this.interactionsList.add(new ItemInteraction("wf_slc_furni_with_var", WiredEffectFurniWithVariable.class));
this.interactionsList.add(new ItemInteraction("wf_slc_users_with_var", WiredEffectUsersWithVariable.class));
this.interactionsList.add(new ItemInteraction("wf_act_send_signal", WiredEffectSendSignal.class));
this.interactionsList.add(new ItemInteraction("wf_act_neg_send_signal", WiredEffectNegativeSendSignal.class));
this.interactionsList.add(new ItemInteraction("wf_act_give_var", WiredEffectGiveVariable.class));
this.interactionsList.add(new ItemInteraction("wf_act_remove_var", WiredEffectRemoveVariable.class));
this.interactionsList.add(new ItemInteraction("wf_act_change_var_val", WiredEffectChangeVariableValue.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_has_furni_on", WiredConditionFurniHaveFurni.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_furnis_hv_avtrs", WiredConditionFurniHaveHabbo.class));
@@ -285,14 +354,54 @@ public class ItemManager {
this.interactionsList.add(new ItemInteraction("wf_cnd_actor_in_team", WiredConditionTeamMember.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_trggrer_on_frn", WiredConditionTriggerOnFurni.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_has_handitem", WiredConditionHabboHasHandItem.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_not_has_handitem", WiredConditionNotHabboHasHandItem.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_date_rng_active", WiredConditionDateRangeActive.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_valid_moves", WiredConditionMovementValidation.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_counter_time_matches", WiredConditionCounterTimeMatches.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_match_time", WiredConditionMatchTime.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_match_date", WiredConditionMatchDate.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_actor_dir", WiredConditionActorDir.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_slc_quantity", WiredConditionSelectionQuantity.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_user_performs_action", WiredConditionUserPerformsAction.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_not_user_performs_action", WiredConditionNotUserPerformsAction.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_has_altitude", WiredConditionHasAltitude.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_triggerer_match", WiredConditionTriggererMatch.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_not_triggerer_match", WiredConditionNotTriggererMatch.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_team_has_score", WiredConditionTeamHasScore.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_team_has_rank", WiredConditionTeamHasRank.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_has_var", WiredConditionHasVariable.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_neg_has_var", WiredConditionNotHasVariable.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_var_val_match", WiredConditionVariableValueMatch.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_var_age_match", WiredConditionVariableAgeMatch.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_random", WiredExtraRandom.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_unseen", WiredExtraUnseen.class));
this.interactionsList.add(new ItemInteraction("wf_blob", WiredBlob.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_or_eval", WiredExtraOrEval.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_filter_furni", WiredExtraFilterFurni.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_filter_user", WiredExtraFilterUser.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_filter_users", WiredExtraFilterUser.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_filter_furni_by_var", WiredExtraFilterFurniByVariable.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_filter_users_by_var", WiredExtraFilterUsersByVariable.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_mov_carry_users", WiredExtraMoveCarryUsers.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_mov_no_animation", WiredExtraMoveNoAnimation.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_anim_time", WiredExtraAnimationTime.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_mov_physics", WiredExtraMovePhysics.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_exec_in_order", WiredExtraExecuteInOrder.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_execution_limit", WiredExtraExecutionLimit.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_text_output_username", WiredExtraTextOutputUsername.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_text_output_furni_name", WiredExtraTextOutputFurniName.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_text_output_variable", WiredExtraTextOutputVariable.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_text_input_variable", WiredExtraTextInputVariable.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_var_text_connector", WiredExtraVariableTextConnector.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_var_lvlup_system", WiredExtraVariableLevelUpSystem.class));
this.interactionsList.add(new ItemInteraction("wf_var_user", WiredExtraUserVariable.class));
this.interactionsList.add(new ItemInteraction("wf_var_furni", WiredExtraFurniVariable.class));
this.interactionsList.add(new ItemInteraction("wf_var_room", WiredExtraRoomVariable.class));
this.interactionsList.add(new ItemInteraction("wf_var_context", WiredExtraContextVariable.class));
this.interactionsList.add(new ItemInteraction("wf_var_reference", WiredExtraVariableReference.class));
this.interactionsList.add(new ItemInteraction("wf_var_echo", WiredExtraVariableEcho.class));
this.interactionsList.add(new ItemInteraction("wf_highscore", InteractionWiredHighscore.class));
@@ -574,7 +683,7 @@ public class ItemManager {
statement.execute();
try (ResultSet set = statement.getGeneratedKeys()) {
try (PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO items_presents VALUES (?, ?)")) {
try (PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO items_presents (item_id, base_item_reward) VALUES (?, ?)")) {
while (set.next() && item == null) {
preparedStatement.setInt(1, set.getInt(1));
preparedStatement.setInt(2, Integer.parseInt(itemId));
@@ -635,7 +744,7 @@ public class ItemManager {
}
public void insertTeleportPair(int itemOneId, int itemTwoId) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO items_teleports VALUES (?, ?)")) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO items_teleports (teleport_one_id, teleport_two_id) VALUES (?, ?)")) {
statement.setInt(1, itemOneId);
statement.setInt(2, itemTwoId);
statement.execute();
@@ -655,20 +764,28 @@ public class ItemManager {
}
public int[] getTargetTeleportRoomId(HabboItem item) {
int[] a = new int[]{};
int[] target = new int[]{};
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT items.id, items.room_id FROM items_teleports INNER JOIN items ON items_teleports.teleport_one_id = items.id OR items_teleports.teleport_two_id = items.id WHERE items.id != ? AND items.room_id > 0 LIMIT 1")) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT items_teleports.*, A.room_id as a_room_id, A.id as a_id, B.room_id as b_room_id, B.id as b_id FROM items_teleports INNER JOIN items AS A ON items_teleports.teleport_one_id = A.id INNER JOIN items AS B ON items_teleports.teleport_two_id = B.id WHERE (teleport_one_id = ? OR teleport_two_id = ?) LIMIT 1")) {
statement.setInt(1, item.getId());
statement.setInt(2, item.getId());
try (ResultSet set = statement.executeQuery()) {
if (set.next()) {
a = new int[]{set.getInt("room_id"), set.getInt("id")};
final boolean useA = (set.getInt("a_id") != item.getId());
final int targetRoomId = useA ? set.getInt("a_room_id") : set.getInt("b_room_id");
final int targetItemId = useA ? set.getInt("a_id") : set.getInt("b_id");
if (targetRoomId > 0 && targetItemId > 0) {
target = new int[]{targetRoomId, targetItemId};
}
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
return a;
return target;
}
public HabboItem loadHabboItem(int itemId) {
@@ -76,7 +76,11 @@ public class YoutubeManager {
private final THashMap<Integer, ArrayList<YoutubePlaylist>> playlists = new THashMap<>();
private final THashMap<String, YoutubePlaylist> playlistCache = new THashMap<>();
private final String apiKey = Emulator.getConfig().getValue("youtube.apikey");
private String getApiKey() {
String key = Emulator.getConfig().getValue("youtube.apikey");
return key != null ? key : "";
}
public void load() {
this.playlists.clear();
@@ -89,11 +93,19 @@ public class YoutubeManager {
LOGGER.info("YouTube Manager -> Loading...");
if (getApiKey().isEmpty()) {
LOGGER.warn("YouTube Manager -> No API key configured (youtube.apikey). YouTube TVs will not work!");
}
int dbEntryCount = 0;
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM youtube_playlists")) {
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
final int itemId = set.getInt("item_id");
final String playlistId = set.getString("playlist_id");
dbEntryCount++;
LOGGER.info("YouTube Manager -> Loading playlist {} for base item #{}", playlistId, itemId);
youtubeDataLoaderPool.submit(() -> {
YoutubePlaylist playlist;
@@ -101,6 +113,9 @@ public class YoutubeManager {
playlist = this.getPlaylistDataById(playlistId);
if (playlist != null) {
this.addPlaylistToItem(itemId, playlist);
LOGGER.info("YouTube Manager -> Successfully loaded playlist {} for base item #{}", playlistId, itemId);
} else {
LOGGER.error("YouTube Manager -> Failed to load playlist {} for base item #{} (returned null - check API key and playlist ID)", playlistId, itemId);
}
} catch (IOException e) {
LOGGER.error("Failed to load YouTube playlist {} ERROR: {}", playlistId, e);
@@ -112,6 +127,10 @@ public class YoutubeManager {
LOGGER.error("Caught SQL exception", e);
}
if (dbEntryCount == 0) {
LOGGER.warn("YouTube Manager -> No entries found in youtube_playlists table!");
}
youtubeDataLoaderPool.shutdown();
try {
youtubeDataLoaderPool.awaitTermination(60, TimeUnit.SECONDS);
@@ -125,7 +144,12 @@ public class YoutubeManager {
public YoutubePlaylist getPlaylistDataById(String playlistId) throws IOException {
if (this.playlistCache.containsKey(playlistId)) return this.playlistCache.get(playlistId);
if(apiKey.isEmpty()) return null;
String apiKey = getApiKey();
if(apiKey.isEmpty()) {
LOGGER.error("YouTube API key is not configured! Set 'youtube.apikey' in emulator_settings to enable YouTube TV.");
return null;
}
YoutubePlaylist playlist;
URL playlistInfo = URI.create("https://youtube.googleapis.com/youtube/v3/playlists?part=snippet&id=" + playlistId + "&maxResults=1&key=" + apiKey).toURL();
@@ -0,0 +1,172 @@
package com.eu.habbo.habbohotel.items.interactions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomAreaHideSupport;
import com.eu.habbo.habbohotel.rooms.RoomLayout;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.habbohotel.wired.WiredEffectType;
import com.eu.habbo.messages.ServerMessage;
import gnu.trove.map.hash.THashMap;
import java.sql.ResultSet;
import java.sql.SQLException;
public class InteractionAreaHideControl extends InteractionCustomValues {
public static final THashMap<String, String> defaultValues = new THashMap<String, String>() {
{
this.put("state", "0");
}
{
this.put("rootX", "0");
}
{
this.put("rootY", "0");
}
{
this.put("width", "0");
}
{
this.put("length", "0");
}
{
this.put("invisibility", "0");
}
{
this.put("wallItems", "0");
}
{
this.put("invert", "0");
}
};
public InteractionAreaHideControl(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem, defaultValues);
this.normalizeValues();
}
public InteractionAreaHideControl(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells, defaultValues);
this.normalizeValues();
}
@Override
public void serializeExtradata(ServerMessage serverMessage) {
this.normalizeValues();
serverMessage.appendInt(5 + (this.isLimited() ? 256 : 0));
serverMessage.appendInt(8);
serverMessage.appendInt(RoomAreaHideSupport.getState(this));
serverMessage.appendInt(RoomAreaHideSupport.getRootX(this));
serverMessage.appendInt(RoomAreaHideSupport.getRootY(this));
serverMessage.appendInt(RoomAreaHideSupport.getWidth(this));
serverMessage.appendInt(RoomAreaHideSupport.getLength(this));
serverMessage.appendInt(RoomAreaHideSupport.isInvisibilityEnabled(this) ? 1 : 0);
serverMessage.appendInt(RoomAreaHideSupport.includesWallItems(this) ? 1 : 0);
serverMessage.appendInt(RoomAreaHideSupport.isInverted(this) ? 1 : 0);
if (this.isLimited()) {
serverMessage.appendInt(this.getLimitedSells());
serverMessage.appendInt(this.getLimitedStack());
}
}
@Override
public boolean isUsable() {
return true;
}
@Override
public boolean allowWiredResetState() {
return true;
}
@Override
public void onClick(GameClient client, Room room, Object[] objects) throws Exception {
if (room == null) {
return;
}
boolean wiredToggle = objects != null
&& objects.length >= 2
&& objects[1] instanceof WiredEffectType;
if (!wiredToggle) {
if (client == null || !this.canToggle(client.getHabbo(), room)) {
return;
}
}
this.values.put("state", (RoomAreaHideSupport.getState(this) == 1) ? "0" : "1");
this.normalizeValues();
this.needsUpdate(true);
Emulator.getThreading().run(this);
room.updateItem(this);
}
@Override
public void onCustomValuesSaved(Room room, GameClient client, THashMap<String, String> oldValues) {
this.normalizeValues();
}
public boolean canToggle(Habbo habbo, Room room) {
if (habbo == null || room == null) {
return false;
}
if (room.hasRights(habbo)) {
return true;
}
if (!habbo.getHabboStats().isRentingSpace()) {
return false;
}
HabboItem rentedItem = room.getHabboItem(habbo.getHabboStats().rentedItemId);
return room.getLayout() != null
&& rentedItem != null
&& RoomLayout.squareInSquare(
RoomLayout.getRectangle(
rentedItem.getX(),
rentedItem.getY(),
rentedItem.getBaseItem().getWidth(),
rentedItem.getBaseItem().getLength(),
rentedItem.getRotation()
),
RoomLayout.getRectangle(
this.getX(),
this.getY(),
this.getBaseItem().getWidth(),
this.getBaseItem().getLength(),
this.getRotation()
)
);
}
private void normalizeValues() {
this.values.put("state", booleanFlag(this.values.get("state")));
this.values.put("rootX", Integer.toString(nonNegative(this.values.get("rootX"))));
this.values.put("rootY", Integer.toString(nonNegative(this.values.get("rootY"))));
this.values.put("width", Integer.toString(nonNegative(this.values.get("width"))));
this.values.put("length", Integer.toString(nonNegative(this.values.get("length"))));
this.values.put("invisibility", booleanFlag(this.values.get("invisibility")));
this.values.put("wallItems", booleanFlag(this.values.get("wallItems")));
this.values.put("invert", booleanFlag(this.values.get("invert")));
}
private static int nonNegative(String value) {
try {
return Math.max(0, Integer.parseInt(value));
} catch (Exception ignored) {
return 0;
}
}
private static String booleanFlag(String value) {
return ("1".equals(value) || "true".equalsIgnoreCase(value)) ? "1" : "0";
}
}
@@ -0,0 +1,16 @@
package com.eu.habbo.habbohotel.items.interactions;
import com.eu.habbo.habbohotel.items.Item;
import java.sql.ResultSet;
import java.sql.SQLException;
public class InteractionConfInvisControl extends InteractionRemoteSwitchControl {
public InteractionConfInvisControl(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public InteractionConfInvisControl(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
}
@@ -0,0 +1,16 @@
package com.eu.habbo.habbohotel.items.interactions;
import com.eu.habbo.habbohotel.items.Item;
import java.sql.ResultSet;
import java.sql.SQLException;
public class InteractionHanditemBlockControl extends InteractionRemoteSwitchControl {
public InteractionHanditemBlockControl(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public InteractionHanditemBlockControl(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
}
@@ -0,0 +1,186 @@
package com.eu.habbo.habbohotel.items.interactions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.rooms.Room;
import java.sql.ResultSet;
import java.sql.SQLException;
public class InteractionQueueSpeedControl extends InteractionRemoteSwitchControl {
private static final int[] MODE_STATES = new int[]{0, 3, 6, 9};
private static final int MODE_FRAME_COUNT = 3;
private static final int BASE_FRAME_DURATION_MS = 500;
private transient volatile int animationRevision = 0;
private transient volatile int animationRoomId = 0;
private transient volatile int animationModeState = Integer.MIN_VALUE;
public InteractionQueueSpeedControl(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public InteractionQueueSpeedControl(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
public static int toModeState(String extradata) {
int state = 0;
try {
state = Integer.parseInt(extradata);
} catch (NumberFormatException ignored) {
}
if (state >= 9) {
return 9;
}
if (state >= 6) {
return 6;
}
if (state >= 3) {
return 3;
}
return 0;
}
public static int toRollerSpeed(String extradata) {
int modeState = toModeState(extradata);
if (modeState >= 9) {
return 3;
}
if (modeState >= 6) {
return 2;
}
if (modeState >= 3) {
return 1;
}
return 0;
}
public static int toRollerIntervalMs(String extradata) {
return BASE_FRAME_DURATION_MS * (toRollerSpeed(extradata) + 1);
}
@Override
public void onClick(GameClient client, Room room, Object[] objects) throws Exception {
if (room == null) {
return;
}
boolean wiredToggle = objects != null
&& objects.length >= 2
&& objects[1] instanceof com.eu.habbo.habbohotel.wired.WiredEffectType;
if (!wiredToggle) {
if (client == null) {
return;
}
if (!this.canToggle(client.getHabbo(), room)) {
super.onClick(client, room, new Object[]{"QUEUE_SPEED_USE"});
return;
}
}
int nextModeState = getNextModeState(this.getExtradata());
applyModeState(room, nextModeState, true);
if (client != null) {
super.onClick(client, room, new Object[]{"TOGGLE_OVERRIDE"});
}
}
@Override
public void onPlace(Room room) {
super.onPlace(room);
this.ensureAnimationLoop(room);
}
@Override
public void onPickUp(Room room) {
this.animationRevision++;
this.animationRoomId = 0;
this.animationModeState = Integer.MIN_VALUE;
super.onPickUp(room);
}
public void ensureAnimationLoop(Room room) {
if (room == null || !room.isLoaded() || this.getRoomId() != room.getId()) {
return;
}
int modeState = toModeState(this.getExtradata());
if (this.animationRoomId == room.getId() && this.animationModeState == modeState) {
return;
}
applyModeState(room, modeState, false);
}
private void applyModeState(Room room, int modeState, boolean persistSelection) {
if (room == null) {
return;
}
this.animationRevision++;
this.animationRoomId = room.getId();
this.animationModeState = modeState;
this.setExtradata(Integer.toString(modeState));
if (persistSelection) {
this.needsUpdate(true);
}
room.updateItemState(this);
int revision = this.animationRevision;
int nextFrame = modeState + 1;
long delay = toRollerIntervalMs(Integer.toString(modeState));
Emulator.getThreading().run(() -> this.animateNextFrame(room, modeState, nextFrame, revision), delay);
}
private void animateNextFrame(Room room, int modeState, int nextFrame, int revision) {
if (room == null || !room.isLoaded() || this.getRoomId() != room.getId()) {
return;
}
if (revision != this.animationRevision || modeState != this.animationModeState) {
return;
}
int maxFrame = modeState + (MODE_FRAME_COUNT - 1);
int frame = (nextFrame > maxFrame) ? modeState : nextFrame;
this.setExtradata(Integer.toString(frame));
room.updateItemState(this);
int followingFrame = (frame >= maxFrame) ? modeState : (frame + 1);
long delay = toRollerIntervalMs(Integer.toString(modeState));
Emulator.getThreading().run(() -> this.animateNextFrame(room, modeState, followingFrame, revision), delay);
}
private static int getNextModeState(String extradata) {
int currentModeState = toModeState(extradata);
for (int index = 0; index < MODE_STATES.length; index++) {
if (MODE_STATES[index] != currentModeState) {
continue;
}
return MODE_STATES[(index + 1) % MODE_STATES.length];
}
return MODE_STATES[0];
}
}
@@ -0,0 +1,21 @@
package com.eu.habbo.habbohotel.items.interactions;
import com.eu.habbo.habbohotel.items.Item;
import java.sql.ResultSet;
import java.sql.SQLException;
public class InteractionRemoteSwitchControl extends InteractionDefault {
public InteractionRemoteSwitchControl(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public InteractionRemoteSwitchControl(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
public boolean isUsable() {
return true;
}
}
@@ -0,0 +1,48 @@
package com.eu.habbo.habbohotel.items.interactions;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.messages.ServerMessage;
import java.sql.ResultSet;
import java.sql.SQLException;
public class InteractionStackWalkHelper extends HabboItem {
public InteractionStackWalkHelper(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public InteractionStackWalkHelper(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
public boolean canWalkOn(RoomUnit roomUnit, Room room, Object[] objects) {
return false;
}
@Override
public boolean isWalkable() {
return false;
}
@Override
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) throws Exception {
}
@Override
public void serializeExtradata(ServerMessage serverMessage) {
serverMessage.appendInt((this.isLimited() ? 256 : 0));
serverMessage.appendString(this.getExtradata());
super.serializeExtradata(serverMessage);
}
@Override
public boolean isUsable() {
return true;
}
}
@@ -43,7 +43,7 @@ public abstract class InteractionWiredCondition extends InteractionWired impleme
@Override
public void onClick(GameClient client, Room room, Object[] objects) throws Exception {
if (client != null) {
if (room.hasRights(client.getHabbo())) {
if (room.canInspectWired(client.getHabbo())) {
client.sendResponse(new WiredConditionDataComposer(this, room));
this.activateBox(room);
}
@@ -0,0 +1,16 @@
package com.eu.habbo.habbohotel.items.interactions;
import com.eu.habbo.habbohotel.items.Item;
import java.sql.ResultSet;
import java.sql.SQLException;
public class InteractionWiredDisableControl extends InteractionRemoteSwitchControl {
public InteractionWiredDisableControl(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public InteractionWiredDisableControl(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
}
@@ -16,6 +16,8 @@ import com.eu.habbo.messages.outgoing.wired.WiredEffectDataComposer;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Predicate;
/**
@@ -78,7 +80,7 @@ public abstract class InteractionWiredEffect extends InteractionWired implements
@Override
public void onClick(GameClient client, Room room, Object[] objects) throws Exception {
if (client != null) {
if (room.hasRights(client.getHabbo())) {
if (room.canInspectWired(client.getHabbo())) {
client.sendResponse(new WiredEffectDataComposer(this, room));
this.activateBox(room);
}
@@ -198,4 +200,66 @@ public abstract class InteractionWiredEffect extends InteractionWired implements
|| additionalRemoveCondition.test(item));
return sizeBefore - items.size();
}
protected <T> LinkedHashSet<T> applySelectorModifiers(Iterable<T> matchedTargets,
Iterable<T> availableTargets,
Iterable<T> existingTargets,
boolean filterExisting,
boolean invert) {
LinkedHashSet<T> matched = toLinkedHashSet(matchedTargets);
LinkedHashSet<T> base = filterExisting
? toLinkedHashSet(existingTargets)
: toLinkedHashSet(availableTargets);
if (invert) {
base.removeAll(matched);
return base;
}
if (filterExisting) {
matched.retainAll(base);
}
return matched;
}
protected LinkedHashSet<HabboItem> getSelectableFloorItems(Room room) {
return this.getSelectableFloorItems(room, null);
}
protected LinkedHashSet<HabboItem> getSelectableFloorItems(Room room, WiredContext ctx) {
LinkedHashSet<HabboItem> result = new LinkedHashSet<>();
if (room == null) {
return result;
}
boolean includeWiredItems = this.includeWiredTargets(ctx);
room.getFloorItems().forEach(item -> {
if (item != null && (includeWiredItems || !(item instanceof InteractionWired))) {
result.add(item);
}
});
return result;
}
protected boolean includeWiredTargets(WiredContext ctx) {
return ctx != null && ctx.includeWiredSelectorItems();
}
protected <T> LinkedHashSet<T> toLinkedHashSet(Iterable<T> values) {
LinkedHashSet<T> result = new LinkedHashSet<>();
if (values == null) {
return result;
}
for (T value : values) {
if (value != null) {
result.add(value);
}
}
return result;
}
}
@@ -2,8 +2,11 @@ package com.eu.habbo.habbohotel.items.interactions;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
import com.eu.habbo.messages.outgoing.wired.WiredExtraDataComposer;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -20,7 +23,10 @@ public abstract class InteractionWiredExtra extends InteractionWired {
@Override
public void onClick(GameClient client, Room room, Object[] objects) throws Exception {
if (client != null) {
if (room.hasRights(client.getHabbo())) {
if (room.canInspectWired(client.getHabbo())) {
if (this.hasConfiguration()) {
client.sendResponse(new WiredExtraDataComposer(this, room));
}
this.activateBox(room);
}
}
@@ -35,4 +41,12 @@ public abstract class InteractionWiredExtra extends InteractionWired {
public boolean isWalkable() {
return true;
}
}
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
return true;
}
public boolean hasConfiguration() {
return false;
}
}
@@ -90,6 +90,7 @@ public class InteractionWiredHighscore extends HabboItem {
try {
int state = Integer.parseInt(this.getExtradata());
this.setExtradata(Math.abs(state - 1) + "");
this.needsUpdate(true);
room.updateItem(this);
} catch (Exception e) {
LOGGER.error("Caught exception", e);
@@ -150,4 +151,4 @@ public class InteractionWiredHighscore extends HabboItem {
public void reloadData() {
this.data = Emulator.getGameEnvironment().getItemManager().getHighscoreManager().getHighscoreRowsForItem(this.getId(), this.clearType, this.scoreType);
}
}
}

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