You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-19 06:56:19 +00:00
feat: add advanced wired variable system and tooling
This commit is contained in:
@@ -213,8 +213,8 @@ ON DUPLICATE KEY UPDATE `key` = `key`;
|
||||
|
||||
-- Wired engine configuration
|
||||
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
|
||||
('wired.engine.enabled', '0'),
|
||||
('wired.engine.exclusive', '0'),
|
||||
('wired.engine.enabled', '1'),
|
||||
('wired.engine.exclusive', '1'),
|
||||
('wired.engine.maxStepsPerStack', '100'),
|
||||
('wired.engine.debug', '0')
|
||||
ON DUPLICATE KEY UPDATE `key` = `key`;
|
||||
|
||||
@@ -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';
|
||||
@@ -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,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,33 @@
|
||||
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;
|
||||
@@ -0,0 +1,32 @@
|
||||
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());
|
||||
@@ -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;
|
||||
|
||||
@@ -116,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -153,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) {
|
||||
@@ -209,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."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package com.eu.habbo.habbohotel.commands;
|
||||
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
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(Permission.ACC_PLACEFURNI, new String[]{"wired"});
|
||||
super(null, new String[]{"wired"});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -20,12 +19,8 @@ public class WiredCommand extends Command {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean hasRights = room.hasRights(gameClient.getHabbo())
|
||||
|| room.isOwner(gameClient.getHabbo())
|
||||
|| gameClient.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER);
|
||||
|
||||
if (!hasRights) {
|
||||
gameClient.getHabbo().whisper("You need room rights to open the Wired Creator Tools.", RoomChatMessageBubbles.ALERT);
|
||||
if (!room.canInspectWired(gameClient.getHabbo())) {
|
||||
gameClient.sendResponse(new InClientLinkComposer("wired-tools/invalid"));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,10 @@ import com.eu.habbo.habbohotel.items.interactions.wired.effects.*;
|
||||
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;
|
||||
@@ -60,9 +63,18 @@ import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraMovePhys
|
||||
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;
|
||||
@@ -225,6 +237,7 @@ public class ItemManager {
|
||||
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));
|
||||
@@ -300,7 +313,12 @@ public class ItemManager {
|
||||
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_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));
|
||||
@@ -340,6 +358,10 @@ public class ItemManager {
|
||||
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));
|
||||
@@ -349,6 +371,8 @@ public class ItemManager {
|
||||
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));
|
||||
@@ -357,6 +381,16 @@ public class ItemManager {
|
||||
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));
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
|
||||
+1
-1
@@ -80,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);
|
||||
}
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ 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));
|
||||
}
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ public abstract class InteractionWiredTrigger extends InteractionWired implement
|
||||
@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 WiredTriggerDataComposer(this, room));
|
||||
this.activateBox(room);
|
||||
}
|
||||
|
||||
+506
@@ -0,0 +1,506 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.conditions;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.bots.Bot;
|
||||
import com.eu.habbo.habbohotel.items.FurnitureType;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredCondition;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
|
||||
import com.eu.habbo.habbohotel.pets.Pet;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomRightLevels;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
|
||||
import com.eu.habbo.habbohotel.users.DanceType;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||
import com.eu.habbo.habbohotel.wired.WiredConditionType;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContext;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredInternalVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import gnu.trove.set.hash.THashSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class WiredConditionHasVariable extends InteractionWiredCondition {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(WiredConditionHasVariable.class);
|
||||
|
||||
protected static final int TARGET_USER = 0;
|
||||
protected static final int TARGET_FURNI = 1;
|
||||
protected static final int TARGET_CONTEXT = 2;
|
||||
protected static final int TARGET_ROOM = 3;
|
||||
protected static final int QUANTIFIER_ALL = 0;
|
||||
protected static final int QUANTIFIER_ANY = 1;
|
||||
private static final String CUSTOM_TOKEN_PREFIX = "custom:";
|
||||
private static final String INTERNAL_TOKEN_PREFIX = "internal:";
|
||||
|
||||
public static final WiredConditionType type = WiredConditionType.HAS_VAR;
|
||||
|
||||
protected final THashSet<HabboItem> selectedItems = new THashSet<>();
|
||||
protected int targetType = TARGET_USER;
|
||||
protected int userSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
protected int furniSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
protected int quantifier = QUANTIFIER_ALL;
|
||||
protected String variableToken = "";
|
||||
protected int variableItemId = 0;
|
||||
|
||||
public WiredConditionHasVariable(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredConditionHasVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WiredConditionType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
this.refresh();
|
||||
|
||||
List<HabboItem> serializedItems = new ArrayList<>();
|
||||
if (this.targetType == TARGET_FURNI && this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
serializedItems.addAll(this.selectedItems);
|
||||
}
|
||||
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
|
||||
message.appendInt(serializedItems.size());
|
||||
|
||||
for (HabboItem item : serializedItems) {
|
||||
message.appendInt(item.getId());
|
||||
}
|
||||
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(this.variableToken == null ? "" : this.variableToken);
|
||||
message.appendInt(4);
|
||||
message.appendInt(this.targetType);
|
||||
message.appendInt(this.userSource);
|
||||
message.appendInt(this.furniSource);
|
||||
message.appendInt(this.quantifier);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getType().code);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings) {
|
||||
int[] params = settings.getIntParams();
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
|
||||
this.targetType = (params.length > 0) ? normalizeTargetType(params[0]) : TARGET_USER;
|
||||
this.userSource = (params.length > 1) ? normalizeUserSource(params[1]) : WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.furniSource = (params.length > 2) ? normalizeFurniSource(params[2]) : WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.quantifier = (params.length > 3) ? normalizeQuantifier(params[3]) : QUANTIFIER_ALL;
|
||||
this.setVariableToken(normalizeVariableToken(settings.getStringParam()));
|
||||
|
||||
if (this.variableToken.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.selectedItems.clear();
|
||||
|
||||
if (this.targetType == TARGET_FURNI && this.furniSource == WiredSourceUtil.SOURCE_SELECTED && room != null) {
|
||||
int[] furniIds = settings.getFurniIds();
|
||||
if (furniIds.length > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int furniId : furniIds) {
|
||||
HabboItem item = room.getHabboItem(furniId);
|
||||
|
||||
if (item != null) {
|
||||
this.selectedItems.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evaluate(WiredContext ctx) {
|
||||
return this.evaluateWithNegation(ctx, false);
|
||||
}
|
||||
|
||||
protected boolean evaluateWithNegation(WiredContext ctx, boolean negative) {
|
||||
Room room = ctx.room();
|
||||
|
||||
if (room == null || this.variableToken == null || this.variableToken.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return switch (this.targetType) {
|
||||
case TARGET_FURNI -> this.evaluateFurniTargets(ctx, room, negative);
|
||||
case TARGET_CONTEXT -> {
|
||||
boolean contextMatch = this.matchesContext(ctx, room);
|
||||
yield negative ? !contextMatch : contextMatch;
|
||||
}
|
||||
case TARGET_ROOM -> {
|
||||
boolean roomMatch = this.matchesRoom(room);
|
||||
yield negative ? !roomMatch : roomMatch;
|
||||
}
|
||||
default -> this.evaluateUserTargets(ctx, room, negative);
|
||||
};
|
||||
}
|
||||
|
||||
private boolean evaluateUserTargets(WiredContext ctx, Room room, boolean negative) {
|
||||
List<RoomUnit> targets = WiredSourceUtil.resolveUsers(ctx, this.userSource);
|
||||
if (targets.isEmpty()) return false;
|
||||
|
||||
boolean match = (this.quantifier == QUANTIFIER_ANY)
|
||||
? this.matchesAnyUser(room, targets)
|
||||
: this.matchesAllUsers(room, targets);
|
||||
|
||||
return negative ? !match : match;
|
||||
}
|
||||
|
||||
private boolean evaluateFurniTargets(WiredContext ctx, Room room, boolean negative) {
|
||||
this.refresh();
|
||||
|
||||
List<HabboItem> targets = WiredSourceUtil.resolveItems(ctx, this.furniSource, this.selectedItems);
|
||||
if (targets.isEmpty()) return false;
|
||||
|
||||
boolean match = (this.quantifier == QUANTIFIER_ANY)
|
||||
? this.matchesAnyFurni(room, targets)
|
||||
: this.matchesAllFurni(room, targets);
|
||||
|
||||
return negative ? !match : match;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
this.refresh();
|
||||
|
||||
List<Integer> itemIds = new ArrayList<>();
|
||||
for (HabboItem item : this.selectedItems) {
|
||||
if (item != null) itemIds.add(item.getId());
|
||||
}
|
||||
|
||||
return WiredManager.getGson().toJson(new JsonData(
|
||||
itemIds,
|
||||
this.targetType,
|
||||
this.variableToken,
|
||||
this.variableItemId,
|
||||
this.userSource,
|
||||
this.furniSource,
|
||||
this.quantifier
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty()) return;
|
||||
|
||||
try {
|
||||
if (wiredData.startsWith("{")) {
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
|
||||
if (data == null) return;
|
||||
|
||||
this.targetType = normalizeTargetType(data.targetType);
|
||||
this.userSource = normalizeUserSource(data.userSource);
|
||||
this.furniSource = normalizeFurniSource(data.furniSource);
|
||||
this.quantifier = normalizeQuantifier(data.quantifier);
|
||||
this.setVariableToken(normalizeVariableToken((data.variableToken != null) ? data.variableToken : ((data.variableItemId > 0) ? String.valueOf(data.variableItemId) : "")));
|
||||
|
||||
if (room != null && data.itemIds != null) {
|
||||
for (Integer itemId : data.itemIds) {
|
||||
if (itemId == null || itemId <= 0) continue;
|
||||
|
||||
HabboItem item = room.getHabboItem(itemId);
|
||||
if (item != null) this.selectedItems.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.setVariableToken(normalizeVariableToken(wiredData));
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to load wired variable condition data for item {}", this.getId(), e);
|
||||
this.onPickUp();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
this.targetType = TARGET_USER;
|
||||
this.userSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.furniSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.quantifier = QUANTIFIER_ALL;
|
||||
this.selectedItems.clear();
|
||||
this.setVariableToken("");
|
||||
}
|
||||
|
||||
protected boolean matchesAnyUser(Room room, List<RoomUnit> targets) {
|
||||
for (RoomUnit roomUnit : targets) {
|
||||
if (this.matchesUser(room, roomUnit)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean matchesAllUsers(Room room, List<RoomUnit> targets) {
|
||||
for (RoomUnit roomUnit : targets) {
|
||||
if (!this.matchesUser(room, roomUnit)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean matchesAnyFurni(Room room, List<HabboItem> targets) {
|
||||
for (HabboItem item : targets) {
|
||||
if (this.matchesFurni(room, item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean matchesAllFurni(Room room, List<HabboItem> targets) {
|
||||
for (HabboItem item : targets) {
|
||||
if (!this.matchesFurni(room, item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean matchesUser(Room room, RoomUnit roomUnit) {
|
||||
if (room == null || roomUnit == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isCustomVariableToken(this.variableToken)) {
|
||||
Habbo habbo = room.getHabbo(roomUnit);
|
||||
|
||||
return habbo != null && room.getUserVariableManager().hasVariable(habbo.getHabboInfo().getId(), this.variableItemId);
|
||||
}
|
||||
|
||||
if (isInternalVariableToken(this.variableToken)) {
|
||||
return this.hasUserInternalVariable(room, roomUnit, getInternalVariableKey(this.variableToken));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean matchesFurni(Room room, HabboItem item) {
|
||||
if (room == null || item == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isCustomVariableToken(this.variableToken)) {
|
||||
return room.getFurniVariableManager().hasVariable(item.getId(), this.variableItemId);
|
||||
}
|
||||
|
||||
if (isInternalVariableToken(this.variableToken)) {
|
||||
return this.hasFurniInternalVariable(item, getInternalVariableKey(this.variableToken));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean matchesContext(WiredContext ctx, Room room) {
|
||||
if (ctx == null || room == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isCustomVariableToken(this.variableToken)) {
|
||||
return WiredContextVariableSupport.hasVariable(ctx, this.variableItemId);
|
||||
}
|
||||
|
||||
if (isInternalVariableToken(this.variableToken)) {
|
||||
return WiredInternalVariableSupport.readContextValue(ctx, getInternalVariableKey(this.variableToken)) != null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean matchesRoom(Room room) {
|
||||
if (room == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isCustomVariableToken(this.variableToken)) {
|
||||
return room.getRoomVariableManager().hasVariable(this.variableItemId);
|
||||
}
|
||||
|
||||
if (isInternalVariableToken(this.variableToken)) {
|
||||
return this.hasRoomInternalVariable(getInternalVariableKey(this.variableToken));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean hasUserInternalVariable(Room room, RoomUnit roomUnit, String key) {
|
||||
return WiredInternalVariableSupport.hasUserValue(room, roomUnit, key);
|
||||
}
|
||||
|
||||
protected boolean hasFurniInternalVariable(HabboItem item, String key) {
|
||||
return WiredInternalVariableSupport.hasFurniValue(item, key);
|
||||
}
|
||||
|
||||
protected boolean hasRoomInternalVariable(String key) {
|
||||
return WiredInternalVariableSupport.hasRoomValue(Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()), key);
|
||||
}
|
||||
|
||||
protected void refresh() {
|
||||
THashSet<HabboItem> staleItems = new THashSet<>();
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
|
||||
if (room == null) {
|
||||
staleItems.addAll(this.selectedItems);
|
||||
} else {
|
||||
for (HabboItem item : this.selectedItems) {
|
||||
if (item == null || item.getRoomId() != room.getId()) {
|
||||
staleItems.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.selectedItems.removeAll(staleItems);
|
||||
}
|
||||
|
||||
protected void setVariableToken(String token) {
|
||||
this.variableToken = normalizeVariableToken(token);
|
||||
this.variableItemId = getCustomItemId(this.variableToken);
|
||||
}
|
||||
|
||||
protected boolean hasRoomEntryMethod(Habbo habbo) {
|
||||
if (habbo == null) return false;
|
||||
|
||||
String roomEntryMethod = habbo.getHabboInfo().getRoomEntryMethod();
|
||||
|
||||
return roomEntryMethod != null && !roomEntryMethod.trim().isEmpty() && !"unknown".equalsIgnoreCase(roomEntryMethod);
|
||||
}
|
||||
|
||||
protected TeamEffectData getTeamEffectData(int effectValue) {
|
||||
if (effectValue <= 0) return null;
|
||||
|
||||
if (effectValue >= 223 && effectValue <= 226) return new TeamEffectData(effectValue - 222, 0);
|
||||
if (effectValue >= 33 && effectValue <= 36) return new TeamEffectData(effectValue - 32, 1);
|
||||
if (effectValue >= 40 && effectValue <= 43) return new TeamEffectData(effectValue - 39, 2);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected static int normalizeTargetType(int value) {
|
||||
return switch (value) {
|
||||
case TARGET_FURNI, TARGET_CONTEXT, TARGET_ROOM -> value;
|
||||
default -> TARGET_USER;
|
||||
};
|
||||
}
|
||||
|
||||
protected static int normalizeQuantifier(int value) {
|
||||
return (value == QUANTIFIER_ANY) ? QUANTIFIER_ANY : QUANTIFIER_ALL;
|
||||
}
|
||||
|
||||
protected static int normalizeUserSource(int value) {
|
||||
return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
|
||||
}
|
||||
|
||||
protected static int normalizeFurniSource(int value) {
|
||||
return switch (value) {
|
||||
case WiredSourceUtil.SOURCE_SELECTED, WiredSourceUtil.SOURCE_SELECTOR, WiredSourceUtil.SOURCE_SIGNAL -> value;
|
||||
default -> WiredSourceUtil.SOURCE_TRIGGER;
|
||||
};
|
||||
}
|
||||
|
||||
protected static boolean isCustomVariableToken(String token) {
|
||||
return token != null && token.startsWith(CUSTOM_TOKEN_PREFIX);
|
||||
}
|
||||
|
||||
protected static boolean isInternalVariableToken(String token) {
|
||||
return token != null && token.startsWith(INTERNAL_TOKEN_PREFIX);
|
||||
}
|
||||
|
||||
protected static int getCustomItemId(String token) {
|
||||
if (!isCustomVariableToken(token)) return 0;
|
||||
|
||||
try {
|
||||
return Integer.parseInt(token.substring(CUSTOM_TOKEN_PREFIX.length()));
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected static String getInternalVariableKey(String token) {
|
||||
return isInternalVariableToken(token) ? WiredInternalVariableSupport.normalizeKey(token.substring(INTERNAL_TOKEN_PREFIX.length())) : "";
|
||||
}
|
||||
|
||||
protected static String normalizeVariableToken(String token) {
|
||||
if (token == null) return "";
|
||||
|
||||
String normalized = token.trim();
|
||||
if (normalized.isEmpty()) return "";
|
||||
if (isCustomVariableToken(normalized)) return normalized;
|
||||
if (isInternalVariableToken(normalized)) return INTERNAL_TOKEN_PREFIX + WiredInternalVariableSupport.normalizeKey(normalized.substring(INTERNAL_TOKEN_PREFIX.length()));
|
||||
|
||||
try {
|
||||
int parsed = Integer.parseInt(normalized);
|
||||
return (parsed > 0) ? (CUSTOM_TOKEN_PREFIX + parsed) : "";
|
||||
} catch (NumberFormatException e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
protected static class JsonData {
|
||||
List<Integer> itemIds;
|
||||
int targetType;
|
||||
String variableToken;
|
||||
int variableItemId;
|
||||
int userSource;
|
||||
int furniSource;
|
||||
int quantifier;
|
||||
|
||||
public JsonData(List<Integer> itemIds, int targetType, String variableToken, int variableItemId, int userSource, int furniSource, int quantifier) {
|
||||
this.itemIds = itemIds;
|
||||
this.targetType = targetType;
|
||||
this.variableToken = variableToken;
|
||||
this.variableItemId = variableItemId;
|
||||
this.userSource = userSource;
|
||||
this.furniSource = furniSource;
|
||||
this.quantifier = quantifier;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class TeamEffectData {
|
||||
final int colorId;
|
||||
final int typeId;
|
||||
|
||||
protected TeamEffectData(int colorId, int typeId) {
|
||||
this.colorId = colorId;
|
||||
this.typeId = typeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.conditions;
|
||||
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.wired.WiredConditionType;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContext;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class WiredConditionNotHasVariable extends WiredConditionHasVariable {
|
||||
public static final WiredConditionType type = WiredConditionType.NOT_HAS_VAR;
|
||||
|
||||
public WiredConditionNotHasVariable(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredConditionNotHasVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WiredConditionType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evaluate(WiredContext ctx) {
|
||||
return this.evaluateWithNegation(ctx, true);
|
||||
}
|
||||
}
|
||||
+420
@@ -0,0 +1,420 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.conditions;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||
import com.eu.habbo.habbohotel.wired.WiredConditionType;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContext;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class WiredConditionVariableAgeMatch extends WiredConditionHasVariable {
|
||||
public static final WiredConditionType type = WiredConditionType.VAR_AGE_MATCH;
|
||||
|
||||
private static final int TARGET_CONTEXT = 2;
|
||||
private static final int COMPARE_VALUE_CREATED = 0;
|
||||
private static final int COMPARE_VALUE_UPDATED = 1;
|
||||
private static final int COMPARISON_LOWER_THAN = 0;
|
||||
private static final int COMPARISON_HIGHER_THAN = 2;
|
||||
private static final int DURATION_UNIT_MILLISECONDS = 0;
|
||||
private static final int DURATION_UNIT_SECONDS = 1;
|
||||
private static final int DURATION_UNIT_MINUTES = 2;
|
||||
private static final int DURATION_UNIT_HOURS = 3;
|
||||
private static final int DURATION_UNIT_DAYS = 4;
|
||||
private static final int DURATION_UNIT_WEEKS = 5;
|
||||
private static final int DURATION_UNIT_MONTHS = 6;
|
||||
private static final int DURATION_UNIT_YEARS = 7;
|
||||
|
||||
protected int compareValue = COMPARE_VALUE_CREATED;
|
||||
protected int comparison = COMPARISON_LOWER_THAN;
|
||||
protected int durationAmount = 0;
|
||||
protected int durationUnit = DURATION_UNIT_SECONDS;
|
||||
|
||||
public WiredConditionVariableAgeMatch(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredConditionVariableAgeMatch(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WiredConditionType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
this.refresh();
|
||||
|
||||
List<HabboItem> serializedItems = new ArrayList<>();
|
||||
if (this.targetType == TARGET_FURNI && this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
serializedItems.addAll(this.selectedItems);
|
||||
}
|
||||
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
|
||||
message.appendInt(serializedItems.size());
|
||||
|
||||
for (HabboItem item : serializedItems) {
|
||||
message.appendInt(item.getId());
|
||||
}
|
||||
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(this.variableToken == null ? "" : this.variableToken);
|
||||
message.appendInt(8);
|
||||
message.appendInt(this.targetType);
|
||||
message.appendInt(this.compareValue);
|
||||
message.appendInt(this.comparison);
|
||||
message.appendInt(this.durationAmount);
|
||||
message.appendInt(this.durationUnit);
|
||||
message.appendInt(this.userSource);
|
||||
message.appendInt(this.furniSource);
|
||||
message.appendInt(this.quantifier);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getType().code);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings) {
|
||||
int[] params = settings.getIntParams();
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
|
||||
this.targetType = (params.length > 0) ? normalizeTargetTypeExtended(params[0]) : TARGET_USER;
|
||||
this.compareValue = (params.length > 1) ? normalizeCompareValue(params[1]) : COMPARE_VALUE_CREATED;
|
||||
this.comparison = (params.length > 2) ? normalizeComparison(params[2]) : COMPARISON_LOWER_THAN;
|
||||
this.durationAmount = Math.max(0, (params.length > 3) ? params[3] : 0);
|
||||
this.durationUnit = (params.length > 4) ? normalizeDurationUnit(params[4]) : DURATION_UNIT_SECONDS;
|
||||
this.userSource = (params.length > 5) ? normalizeUserSource(params[5]) : WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.furniSource = (params.length > 6) ? normalizeFurniSource(params[6]) : WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.quantifier = (params.length > 7) ? normalizeQuantifier(params[7]) : QUANTIFIER_ALL;
|
||||
this.setVariableToken(normalizeVariableToken(settings.getStringParam()));
|
||||
|
||||
if (!this.isValidSource(room)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.selectedItems.clear();
|
||||
|
||||
if (this.targetType == TARGET_FURNI && this.furniSource == WiredSourceUtil.SOURCE_SELECTED && room != null) {
|
||||
int[] furniIds = settings.getFurniIds();
|
||||
if (furniIds.length > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int furniId : furniIds) {
|
||||
HabboItem item = room.getHabboItem(furniId);
|
||||
|
||||
if (item != null) {
|
||||
this.selectedItems.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evaluate(WiredContext ctx) {
|
||||
Room room = ctx.room();
|
||||
|
||||
if (room == null || this.variableToken == null || this.variableToken.isEmpty() || !isCustomVariableToken(this.variableToken)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long thresholdMs = durationToMillis(this.durationAmount, this.durationUnit);
|
||||
|
||||
return switch (this.targetType) {
|
||||
case TARGET_FURNI -> this.evaluateFurniTargets(ctx, room, thresholdMs);
|
||||
case TARGET_ROOM -> this.evaluateRoomTarget(room, thresholdMs);
|
||||
case TARGET_CONTEXT -> this.evaluateContextTarget(ctx, room, thresholdMs);
|
||||
default -> this.evaluateUserTargets(ctx, room, thresholdMs);
|
||||
};
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
this.refresh();
|
||||
|
||||
List<Integer> itemIds = new ArrayList<>();
|
||||
for (HabboItem item : this.selectedItems) {
|
||||
if (item != null) itemIds.add(item.getId());
|
||||
}
|
||||
|
||||
return WiredManager.getGson().toJson(new JsonData(
|
||||
itemIds,
|
||||
this.targetType,
|
||||
this.variableToken,
|
||||
this.variableItemId,
|
||||
this.compareValue,
|
||||
this.comparison,
|
||||
this.durationAmount,
|
||||
this.durationUnit,
|
||||
this.userSource,
|
||||
this.furniSource,
|
||||
this.quantifier
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty()) return;
|
||||
|
||||
try {
|
||||
if (wiredData.startsWith("{")) {
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
|
||||
if (data == null) return;
|
||||
|
||||
this.targetType = normalizeTargetTypeExtended(data.targetType);
|
||||
this.compareValue = normalizeCompareValue(data.compareValue);
|
||||
this.comparison = normalizeComparison(data.comparison);
|
||||
this.durationAmount = Math.max(0, data.durationAmount);
|
||||
this.durationUnit = normalizeDurationUnit(data.durationUnit);
|
||||
this.userSource = normalizeUserSource(data.userSource);
|
||||
this.furniSource = normalizeFurniSource(data.furniSource);
|
||||
this.quantifier = normalizeQuantifier(data.quantifier);
|
||||
this.setVariableToken(normalizeVariableToken((data.variableToken != null) ? data.variableToken : ((data.variableItemId > 0) ? String.valueOf(data.variableItemId) : "")));
|
||||
|
||||
if (room != null && data.itemIds != null) {
|
||||
for (Integer itemId : data.itemIds) {
|
||||
if (itemId == null || itemId <= 0) continue;
|
||||
|
||||
HabboItem item = room.getHabboItem(itemId);
|
||||
if (item != null) this.selectedItems.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.setVariableToken(normalizeVariableToken(wiredData));
|
||||
} catch (Exception e) {
|
||||
this.onPickUp();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
super.onPickUp();
|
||||
this.compareValue = COMPARE_VALUE_CREATED;
|
||||
this.comparison = COMPARISON_LOWER_THAN;
|
||||
this.durationAmount = 0;
|
||||
this.durationUnit = DURATION_UNIT_SECONDS;
|
||||
}
|
||||
|
||||
private boolean evaluateUserTargets(WiredContext ctx, Room room, long thresholdMs) {
|
||||
List<RoomUnit> targets = WiredSourceUtil.resolveUsers(ctx, this.userSource);
|
||||
if (targets.isEmpty()) return false;
|
||||
|
||||
if (this.quantifier == QUANTIFIER_ANY) {
|
||||
for (RoomUnit roomUnit : targets) {
|
||||
if (this.matchesAge(this.readUserAgeMs(room, roomUnit), thresholdMs)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
for (RoomUnit roomUnit : targets) {
|
||||
if (!this.matchesAge(this.readUserAgeMs(room, roomUnit), thresholdMs)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean evaluateFurniTargets(WiredContext ctx, Room room, long thresholdMs) {
|
||||
this.refresh();
|
||||
|
||||
List<HabboItem> targets = WiredSourceUtil.resolveItems(ctx, this.furniSource, this.selectedItems);
|
||||
if (targets.isEmpty()) return false;
|
||||
|
||||
if (this.quantifier == QUANTIFIER_ANY) {
|
||||
for (HabboItem item : targets) {
|
||||
if (this.matchesAge(this.readFurniAgeMs(room, item), thresholdMs)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
for (HabboItem item : targets) {
|
||||
if (!this.matchesAge(this.readFurniAgeMs(room, item), thresholdMs)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean evaluateRoomTarget(Room room, long thresholdMs) {
|
||||
return this.matchesAge(this.readRoomAgeMs(room), thresholdMs);
|
||||
}
|
||||
|
||||
private boolean evaluateContextTarget(WiredContext ctx, Room room, long thresholdMs) {
|
||||
return this.matchesAge(this.readContextAgeMs(ctx, room), thresholdMs);
|
||||
}
|
||||
|
||||
private Long readUserAgeMs(Room room, RoomUnit roomUnit) {
|
||||
if (room == null || roomUnit == null) return null;
|
||||
|
||||
Habbo habbo = room.getHabbo(roomUnit);
|
||||
if (habbo == null || !room.getUserVariableManager().hasVariable(habbo.getHabboInfo().getId(), this.variableItemId)) return null;
|
||||
|
||||
int timestamp = (this.compareValue == COMPARE_VALUE_UPDATED)
|
||||
? room.getUserVariableManager().getUpdatedAt(habbo.getHabboInfo().getId(), this.variableItemId)
|
||||
: room.getUserVariableManager().getCreatedAt(habbo.getHabboInfo().getId(), this.variableItemId);
|
||||
|
||||
return timestampToAgeMs(timestamp);
|
||||
}
|
||||
|
||||
private Long readFurniAgeMs(Room room, HabboItem item) {
|
||||
if (room == null || item == null || !room.getFurniVariableManager().hasVariable(item.getId(), this.variableItemId)) return null;
|
||||
|
||||
int timestamp = (this.compareValue == COMPARE_VALUE_UPDATED)
|
||||
? room.getFurniVariableManager().getUpdatedAt(item.getId(), this.variableItemId)
|
||||
: room.getFurniVariableManager().getCreatedAt(item.getId(), this.variableItemId);
|
||||
|
||||
return timestampToAgeMs(timestamp);
|
||||
}
|
||||
|
||||
private Long readRoomAgeMs(Room room) {
|
||||
if (room == null) return null;
|
||||
if (this.compareValue == COMPARE_VALUE_CREATED) return null;
|
||||
|
||||
int timestamp = room.getRoomVariableManager().getUpdatedAt(this.variableItemId);
|
||||
return timestampToAgeMs(timestamp);
|
||||
}
|
||||
|
||||
private Long readContextAgeMs(WiredContext ctx, Room room) {
|
||||
if (ctx == null || room == null || !WiredContextVariableSupport.hasVariable(ctx, this.variableItemId)) return null;
|
||||
|
||||
int timestamp = (this.compareValue == COMPARE_VALUE_UPDATED)
|
||||
? WiredContextVariableSupport.getUpdatedAt(ctx, this.variableItemId)
|
||||
: WiredContextVariableSupport.getCreatedAt(ctx, this.variableItemId);
|
||||
|
||||
return timestampToAgeMs(timestamp);
|
||||
}
|
||||
|
||||
private boolean matchesAge(Long ageMs, long thresholdMs) {
|
||||
if (ageMs == null) return false;
|
||||
|
||||
return switch (this.comparison) {
|
||||
case COMPARISON_HIGHER_THAN -> ageMs > thresholdMs;
|
||||
default -> ageMs < thresholdMs;
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isValidSource(Room room) {
|
||||
if (room == null || !isCustomVariableToken(this.variableToken)) return false;
|
||||
|
||||
return switch (this.targetType) {
|
||||
case TARGET_FURNI -> room.getFurniVariableManager().getDefinitionInfo(this.variableItemId) != null;
|
||||
case TARGET_CONTEXT -> WiredContextVariableSupport.getDefinitionInfo(room, this.variableItemId) != null;
|
||||
case TARGET_ROOM -> {
|
||||
WiredVariableDefinitionInfo definition = room.getRoomVariableManager().getDefinitionInfo(this.variableItemId);
|
||||
yield this.compareValue == COMPARE_VALUE_UPDATED && definition != null;
|
||||
}
|
||||
default -> room.getUserVariableManager().getDefinitionInfo(this.variableItemId) != null;
|
||||
};
|
||||
}
|
||||
|
||||
private static Long timestampToAgeMs(int timestampSeconds) {
|
||||
if (timestampSeconds <= 0) return null;
|
||||
|
||||
long timestampMs = (timestampSeconds * 1000L);
|
||||
return Math.max(0L, System.currentTimeMillis() - timestampMs);
|
||||
}
|
||||
|
||||
private static long durationToMillis(int amount, int unit) {
|
||||
long normalizedAmount = Math.max(0L, amount);
|
||||
|
||||
return switch (unit) {
|
||||
case DURATION_UNIT_MILLISECONDS -> normalizedAmount;
|
||||
case DURATION_UNIT_MINUTES -> safeMultiply(normalizedAmount, 60_000L);
|
||||
case DURATION_UNIT_HOURS -> safeMultiply(normalizedAmount, 3_600_000L);
|
||||
case DURATION_UNIT_DAYS -> safeMultiply(normalizedAmount, 86_400_000L);
|
||||
case DURATION_UNIT_WEEKS -> safeMultiply(normalizedAmount, 604_800_000L);
|
||||
case DURATION_UNIT_MONTHS -> safeMultiply(normalizedAmount, 2_592_000_000L);
|
||||
case DURATION_UNIT_YEARS -> safeMultiply(normalizedAmount, 31_536_000_000L);
|
||||
default -> safeMultiply(normalizedAmount, 1_000L);
|
||||
};
|
||||
}
|
||||
|
||||
private static long safeMultiply(long left, long right) {
|
||||
if (left <= 0 || right <= 0) return 0L;
|
||||
if (left > (Long.MAX_VALUE / right)) return Long.MAX_VALUE;
|
||||
|
||||
return left * right;
|
||||
}
|
||||
|
||||
private static int normalizeTargetTypeExtended(int value) {
|
||||
return switch (value) {
|
||||
case TARGET_FURNI, TARGET_CONTEXT, TARGET_ROOM -> value;
|
||||
default -> TARGET_USER;
|
||||
};
|
||||
}
|
||||
|
||||
private static int normalizeCompareValue(int value) {
|
||||
return (value == COMPARE_VALUE_UPDATED) ? COMPARE_VALUE_UPDATED : COMPARE_VALUE_CREATED;
|
||||
}
|
||||
|
||||
private static int normalizeComparison(int value) {
|
||||
return (value == COMPARISON_HIGHER_THAN) ? COMPARISON_HIGHER_THAN : COMPARISON_LOWER_THAN;
|
||||
}
|
||||
|
||||
private static int normalizeDurationUnit(int value) {
|
||||
return switch (value) {
|
||||
case DURATION_UNIT_MILLISECONDS, DURATION_UNIT_SECONDS, DURATION_UNIT_MINUTES, DURATION_UNIT_HOURS,
|
||||
DURATION_UNIT_DAYS, DURATION_UNIT_WEEKS, DURATION_UNIT_MONTHS, DURATION_UNIT_YEARS -> value;
|
||||
default -> DURATION_UNIT_SECONDS;
|
||||
};
|
||||
}
|
||||
|
||||
protected static class JsonData {
|
||||
List<Integer> itemIds;
|
||||
int targetType;
|
||||
String variableToken;
|
||||
int variableItemId;
|
||||
int compareValue;
|
||||
int comparison;
|
||||
int durationAmount;
|
||||
int durationUnit;
|
||||
int userSource;
|
||||
int furniSource;
|
||||
int quantifier;
|
||||
|
||||
JsonData(List<Integer> itemIds, int targetType, String variableToken, int variableItemId, int compareValue, int comparison, int durationAmount, int durationUnit, int userSource, int furniSource, int quantifier) {
|
||||
this.itemIds = itemIds;
|
||||
this.targetType = targetType;
|
||||
this.variableToken = variableToken;
|
||||
this.variableItemId = variableItemId;
|
||||
this.compareValue = compareValue;
|
||||
this.comparison = comparison;
|
||||
this.durationAmount = durationAmount;
|
||||
this.durationUnit = durationUnit;
|
||||
this.userSource = userSource;
|
||||
this.furniSource = furniSource;
|
||||
this.quantifier = quantifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
+814
@@ -0,0 +1,814 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.conditions;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.bots.Bot;
|
||||
import com.eu.habbo.habbohotel.games.Game;
|
||||
import com.eu.habbo.habbohotel.games.GamePlayer;
|
||||
import com.eu.habbo.habbohotel.games.GameTeam;
|
||||
import com.eu.habbo.habbohotel.games.GameTeamColors;
|
||||
import com.eu.habbo.habbohotel.games.battlebanzai.BattleBanzaiGame;
|
||||
import com.eu.habbo.habbohotel.games.freeze.FreezeGame;
|
||||
import com.eu.habbo.habbohotel.games.wired.WiredGame;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
|
||||
import com.eu.habbo.habbohotel.pets.Pet;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||
import com.eu.habbo.habbohotel.wired.WiredConditionType;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContext;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredInternalVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.util.HotelDateTimeUtil;
|
||||
import gnu.trove.set.hash.THashSet;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class WiredConditionVariableValueMatch extends WiredConditionHasVariable {
|
||||
public static final WiredConditionType type = WiredConditionType.VAR_VAL_MATCH;
|
||||
|
||||
private static final int TARGET_CONTEXT = 2;
|
||||
private static final int SOURCE_SECONDARY_SELECTED = 101;
|
||||
private static final int REFERENCE_CONSTANT = 0;
|
||||
private static final int REFERENCE_VARIABLE = 1;
|
||||
private static final int COMPARISON_GREATER_THAN = 0;
|
||||
private static final int COMPARISON_GREATER_THAN_OR_EQUAL = 1;
|
||||
private static final int COMPARISON_EQUAL = 2;
|
||||
private static final int COMPARISON_LESS_THAN_OR_EQUAL = 3;
|
||||
private static final int COMPARISON_LESS_THAN = 4;
|
||||
private static final int COMPARISON_NOT_EQUAL = 5;
|
||||
private static final String DELIM = "\t";
|
||||
private static final String FURNI_DELIM = ";";
|
||||
|
||||
protected int comparison = COMPARISON_EQUAL;
|
||||
protected int referenceMode = REFERENCE_CONSTANT;
|
||||
protected int referenceConstantValue = 0;
|
||||
protected int referenceTargetType = TARGET_USER;
|
||||
protected int referenceUserSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
protected int referenceFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
protected String referenceVariableToken = "";
|
||||
protected int referenceVariableItemId = 0;
|
||||
protected final THashSet<HabboItem> referenceSelectedItems = new THashSet<>();
|
||||
|
||||
public WiredConditionVariableValueMatch(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredConditionVariableValueMatch(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WiredConditionType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
this.refresh();
|
||||
this.refreshReferenceItems();
|
||||
|
||||
List<HabboItem> serializedItems = new ArrayList<>();
|
||||
if (this.targetType == TARGET_FURNI && this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
serializedItems.addAll(this.selectedItems);
|
||||
}
|
||||
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
|
||||
message.appendInt(serializedItems.size());
|
||||
|
||||
for (HabboItem item : serializedItems) {
|
||||
message.appendInt(item.getId());
|
||||
}
|
||||
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(this.serializeStringData());
|
||||
message.appendInt(10);
|
||||
message.appendInt(this.targetType);
|
||||
message.appendInt(this.comparison);
|
||||
message.appendInt(this.referenceMode);
|
||||
message.appendInt(this.referenceConstantValue);
|
||||
message.appendInt(this.referenceTargetType);
|
||||
message.appendInt(this.userSource);
|
||||
message.appendInt(this.furniSource);
|
||||
message.appendInt(this.referenceUserSource);
|
||||
message.appendInt(this.referenceFurniSource);
|
||||
message.appendInt(this.quantifier);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getType().code);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings) {
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
if (room == null) return false;
|
||||
|
||||
int[] params = settings.getIntParams();
|
||||
String[] stringParts = this.parseStringData(settings.getStringParam());
|
||||
int nextTargetType = normalizeTargetTypeExtended(param(params, 0, TARGET_USER));
|
||||
int nextComparison = normalizeComparison(param(params, 1, COMPARISON_EQUAL));
|
||||
int nextReferenceMode = normalizeReferenceMode(param(params, 2, REFERENCE_CONSTANT));
|
||||
int nextReferenceConstantValue = param(params, 3, 0);
|
||||
int nextReferenceTargetType = normalizeTargetTypeExtended(param(params, 4, TARGET_USER));
|
||||
int nextUserSource = normalizeUserSource(param(params, 5, WiredSourceUtil.SOURCE_TRIGGER));
|
||||
int nextFurniSource = normalizeFurniSource(param(params, 6, WiredSourceUtil.SOURCE_TRIGGER));
|
||||
int nextReferenceUserSource = normalizeUserSource(param(params, 7, WiredSourceUtil.SOURCE_TRIGGER));
|
||||
int nextReferenceFurniSource = normalizeReferenceFurniSource(param(params, 8, WiredSourceUtil.SOURCE_TRIGGER));
|
||||
int nextQuantifier = normalizeQuantifier(param(params, 9, QUANTIFIER_ALL));
|
||||
String nextVariableToken = normalizeVariableToken((stringParts.length > 0) ? stringParts[0] : settings.getStringParam());
|
||||
String nextReferenceVariableToken = normalizeVariableToken((stringParts.length > 1) ? stringParts[1] : "");
|
||||
|
||||
if (!this.isValidSource(room, nextTargetType, nextVariableToken)) return false;
|
||||
if (nextReferenceMode == REFERENCE_VARIABLE && !this.isValidReference(room, nextReferenceTargetType, nextReferenceVariableToken)) return false;
|
||||
|
||||
int selectionLimit = Emulator.getConfig().getInt("hotel.wired.furni.selection.count");
|
||||
List<HabboItem> nextSelectedItems = (nextTargetType == TARGET_FURNI && nextFurniSource == WiredSourceUtil.SOURCE_SELECTED)
|
||||
? this.parseItems(settings.getFurniIds(), room)
|
||||
: new ArrayList<>();
|
||||
List<HabboItem> nextReferenceItems = (nextReferenceMode == REFERENCE_VARIABLE && nextReferenceTargetType == TARGET_FURNI && nextReferenceFurniSource == SOURCE_SECONDARY_SELECTED)
|
||||
? this.parseItems((stringParts.length > 2) ? stringParts[2] : "", room)
|
||||
: new ArrayList<>();
|
||||
|
||||
if (nextSelectedItems.size() > selectionLimit || nextReferenceItems.size() > selectionLimit) return false;
|
||||
|
||||
this.selectedItems.clear();
|
||||
this.selectedItems.addAll(nextSelectedItems);
|
||||
this.referenceSelectedItems.clear();
|
||||
this.referenceSelectedItems.addAll(nextReferenceItems);
|
||||
this.targetType = nextTargetType;
|
||||
this.comparison = nextComparison;
|
||||
this.referenceMode = nextReferenceMode;
|
||||
this.referenceConstantValue = nextReferenceConstantValue;
|
||||
this.referenceTargetType = nextReferenceTargetType;
|
||||
this.userSource = nextUserSource;
|
||||
this.furniSource = nextFurniSource;
|
||||
this.referenceUserSource = nextReferenceUserSource;
|
||||
this.referenceFurniSource = nextReferenceFurniSource;
|
||||
this.quantifier = nextQuantifier;
|
||||
this.setVariableToken(nextVariableToken);
|
||||
this.setReferenceVariableToken(nextReferenceVariableToken);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evaluate(WiredContext ctx) {
|
||||
Room room = ctx.room();
|
||||
|
||||
if (room == null || this.variableToken == null || this.variableToken.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return switch (this.targetType) {
|
||||
case TARGET_FURNI -> this.evaluateFurniTargets(ctx, room);
|
||||
case TARGET_ROOM -> this.evaluateRoomTarget(ctx, room);
|
||||
case TARGET_CONTEXT -> this.evaluateContextTarget(ctx, room);
|
||||
default -> this.evaluateUserTargets(ctx, room);
|
||||
};
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
this.refresh();
|
||||
this.refreshReferenceItems();
|
||||
|
||||
return WiredManager.getGson().toJson(new JsonData(
|
||||
this.targetType,
|
||||
this.variableToken,
|
||||
this.variableItemId,
|
||||
this.comparison,
|
||||
this.referenceMode,
|
||||
this.referenceConstantValue,
|
||||
this.referenceTargetType,
|
||||
this.referenceVariableToken,
|
||||
this.referenceVariableItemId,
|
||||
this.userSource,
|
||||
this.furniSource,
|
||||
this.referenceUserSource,
|
||||
this.referenceFurniSource,
|
||||
this.quantifier,
|
||||
this.toIds(this.selectedItems),
|
||||
this.toIds(this.referenceSelectedItems)
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty() || !wiredData.startsWith("{")) return;
|
||||
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
if (data == null) return;
|
||||
|
||||
this.targetType = normalizeTargetTypeExtended(data.targetType);
|
||||
this.setVariableToken(normalizeVariableToken((data.variableToken != null) ? data.variableToken : ((data.variableItemId > 0) ? String.valueOf(data.variableItemId) : "")));
|
||||
this.comparison = normalizeComparison(data.comparison);
|
||||
this.referenceMode = normalizeReferenceMode(data.referenceMode);
|
||||
this.referenceConstantValue = data.referenceConstantValue;
|
||||
this.referenceTargetType = normalizeTargetTypeExtended(data.referenceTargetType);
|
||||
this.setReferenceVariableToken(normalizeVariableToken((data.referenceVariableToken != null) ? data.referenceVariableToken : ((data.referenceVariableItemId > 0) ? String.valueOf(data.referenceVariableItemId) : "")));
|
||||
this.userSource = normalizeUserSource(data.userSource);
|
||||
this.furniSource = normalizeFurniSource(data.furniSource);
|
||||
this.referenceUserSource = normalizeUserSource(data.referenceUserSource);
|
||||
this.referenceFurniSource = normalizeReferenceFurniSource(data.referenceFurniSource);
|
||||
this.quantifier = normalizeQuantifier(data.quantifier);
|
||||
|
||||
if (room == null) return;
|
||||
|
||||
this.selectedItems.addAll(this.parseItems(data.selectedItemIds, room));
|
||||
this.referenceSelectedItems.addAll(this.parseItems(data.referenceSelectedItemIds, room));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
super.onPickUp();
|
||||
this.comparison = COMPARISON_EQUAL;
|
||||
this.referenceMode = REFERENCE_CONSTANT;
|
||||
this.referenceConstantValue = 0;
|
||||
this.referenceTargetType = TARGET_USER;
|
||||
this.referenceUserSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.referenceFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.referenceSelectedItems.clear();
|
||||
this.setReferenceVariableToken("");
|
||||
}
|
||||
|
||||
public boolean requiresTriggeringUser() {
|
||||
return (this.targetType == TARGET_USER && this.userSource == WiredSourceUtil.SOURCE_TRIGGER)
|
||||
|| (this.referenceMode == REFERENCE_VARIABLE && this.referenceTargetType == TARGET_USER && this.referenceUserSource == WiredSourceUtil.SOURCE_TRIGGER);
|
||||
}
|
||||
|
||||
private boolean evaluateUserTargets(WiredContext ctx, Room room) {
|
||||
List<RoomUnit> targets = WiredSourceUtil.resolveUsers(ctx, this.userSource);
|
||||
if (targets.isEmpty()) return false;
|
||||
|
||||
ReferenceSnapshot references = this.resolveReferences(ctx, room);
|
||||
|
||||
if (this.quantifier == QUANTIFIER_ANY) {
|
||||
int index = 0;
|
||||
for (RoomUnit roomUnit : targets) {
|
||||
Integer currentValue = this.readUserValue(room, roomUnit);
|
||||
Integer referenceValue = this.referenceFor(references, roomUnit != null ? roomUnit.getId() : 0, TARGET_USER, index++);
|
||||
|
||||
if (this.matchesComparison(currentValue, referenceValue)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
for (RoomUnit roomUnit : targets) {
|
||||
Integer currentValue = this.readUserValue(room, roomUnit);
|
||||
Integer referenceValue = this.referenceFor(references, roomUnit != null ? roomUnit.getId() : 0, TARGET_USER, index++);
|
||||
|
||||
if (!this.matchesComparison(currentValue, referenceValue)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean evaluateFurniTargets(WiredContext ctx, Room room) {
|
||||
this.refresh();
|
||||
|
||||
List<HabboItem> targets = WiredSourceUtil.resolveItems(ctx, this.furniSource, this.selectedItems);
|
||||
if (targets.isEmpty()) return false;
|
||||
|
||||
ReferenceSnapshot references = this.resolveReferences(ctx, room);
|
||||
|
||||
if (this.quantifier == QUANTIFIER_ANY) {
|
||||
int index = 0;
|
||||
for (HabboItem item : targets) {
|
||||
Integer currentValue = this.readFurniValue(room, item);
|
||||
Integer referenceValue = this.referenceFor(references, item != null ? item.getId() : 0, TARGET_FURNI, index++);
|
||||
|
||||
if (this.matchesComparison(currentValue, referenceValue)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
for (HabboItem item : targets) {
|
||||
Integer currentValue = this.readFurniValue(room, item);
|
||||
Integer referenceValue = this.referenceFor(references, item != null ? item.getId() : 0, TARGET_FURNI, index++);
|
||||
|
||||
if (!this.matchesComparison(currentValue, referenceValue)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean evaluateRoomTarget(WiredContext ctx, Room room) {
|
||||
Integer currentValue = this.readRoomValue(room);
|
||||
Integer referenceValue = this.referenceFor(this.resolveReferences(ctx, room), room.getId(), TARGET_ROOM, 0);
|
||||
|
||||
return this.matchesComparison(currentValue, referenceValue);
|
||||
}
|
||||
|
||||
private boolean evaluateContextTarget(WiredContext ctx, Room room) {
|
||||
Integer currentValue = this.readContextTargetValue(ctx, room);
|
||||
Integer referenceValue = this.referenceFor(this.resolveReferences(ctx, room), this.variableItemId, TARGET_CONTEXT, 0);
|
||||
|
||||
return this.matchesComparison(currentValue, referenceValue);
|
||||
}
|
||||
|
||||
private ReferenceSnapshot resolveReferences(WiredContext ctx, Room room) {
|
||||
if (this.referenceMode != REFERENCE_VARIABLE) return null;
|
||||
|
||||
return switch (this.referenceTargetType) {
|
||||
case TARGET_USER -> this.userReferences(ctx, room);
|
||||
case TARGET_FURNI -> this.furniReferences(ctx, room);
|
||||
case TARGET_CONTEXT -> this.contextReferences(ctx, room);
|
||||
case TARGET_ROOM -> this.roomReferences(room);
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private ReferenceSnapshot userReferences(WiredContext ctx, Room room) {
|
||||
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_USER);
|
||||
|
||||
if (isInternalVariableToken(this.referenceVariableToken)) {
|
||||
String key = getInternalVariableKey(this.referenceVariableToken);
|
||||
if (!canUseUserInternalReference(key)) return null;
|
||||
|
||||
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.referenceUserSource)) {
|
||||
Integer value = this.readUserInternalValue(room, roomUnit, key);
|
||||
if (value != null && roomUnit != null) snapshot.add(roomUnit.getId(), value);
|
||||
}
|
||||
|
||||
return snapshot.isEmpty() ? null : snapshot;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.referenceVariableItemId);
|
||||
if (definition == null || !definition.hasValue()) return null;
|
||||
|
||||
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.referenceUserSource)) {
|
||||
if (roomUnit == null) continue;
|
||||
|
||||
Habbo habbo = room.getHabbo(roomUnit);
|
||||
if (habbo != null) snapshot.add(roomUnit.getId(), room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), this.referenceVariableItemId));
|
||||
}
|
||||
|
||||
return snapshot.isEmpty() ? null : snapshot;
|
||||
}
|
||||
|
||||
private ReferenceSnapshot furniReferences(WiredContext ctx, Room room) {
|
||||
int source = (this.referenceFurniSource == SOURCE_SECONDARY_SELECTED) ? WiredSourceUtil.SOURCE_SELECTED : this.referenceFurniSource;
|
||||
if (source == WiredSourceUtil.SOURCE_SELECTED) this.refreshReferenceItems();
|
||||
|
||||
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_FURNI);
|
||||
|
||||
if (isInternalVariableToken(this.referenceVariableToken)) {
|
||||
String key = getInternalVariableKey(this.referenceVariableToken);
|
||||
if (!canUseFurniInternalReference(key)) return null;
|
||||
|
||||
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, source, this.referenceSelectedItems)) {
|
||||
Integer value = this.readFurniInternalValue(room, item, key);
|
||||
if (value != null && item != null) snapshot.add(item.getId(), value);
|
||||
}
|
||||
|
||||
return snapshot.isEmpty() ? null : snapshot;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.referenceVariableItemId);
|
||||
if (definition == null || !definition.hasValue()) return null;
|
||||
|
||||
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, source, this.referenceSelectedItems)) {
|
||||
if (item != null) snapshot.add(item.getId(), room.getFurniVariableManager().getCurrentValue(item.getId(), this.referenceVariableItemId));
|
||||
}
|
||||
|
||||
return snapshot.isEmpty() ? null : snapshot;
|
||||
}
|
||||
|
||||
private ReferenceSnapshot roomReferences(Room room) {
|
||||
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_ROOM);
|
||||
|
||||
if (isInternalVariableToken(this.referenceVariableToken)) {
|
||||
String key = getInternalVariableKey(this.referenceVariableToken);
|
||||
if (!canUseRoomInternalReference(key)) return null;
|
||||
|
||||
Integer value = this.readRoomInternalValue(room, key);
|
||||
if (value == null) return null;
|
||||
|
||||
snapshot.add(room.getId(), value);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getRoomVariableManager().getDefinitionInfo(this.referenceVariableItemId);
|
||||
if (definition == null || !definition.hasValue()) return null;
|
||||
|
||||
snapshot.add(room.getId(), room.getRoomVariableManager().getCurrentValue(this.referenceVariableItemId));
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private ReferenceSnapshot contextReferences(WiredContext ctx, Room room) {
|
||||
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_CONTEXT);
|
||||
|
||||
if (isInternalVariableToken(this.referenceVariableToken)) {
|
||||
String key = getInternalVariableKey(this.referenceVariableToken);
|
||||
if (!canUseContextInternalReference(key)) return null;
|
||||
|
||||
Integer value = WiredInternalVariableSupport.readContextValue(ctx, key);
|
||||
if (value == null) return null;
|
||||
|
||||
snapshot.add(this.referenceVariableItemId > 0 ? this.referenceVariableItemId : (room != null ? room.getId() : 0), value);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, this.referenceVariableItemId);
|
||||
if (definition == null || !definition.hasValue() || !WiredContextVariableSupport.hasVariable(ctx, this.referenceVariableItemId)) return null;
|
||||
|
||||
Integer value = WiredContextVariableSupport.getCurrentValue(ctx, this.referenceVariableItemId);
|
||||
if (value == null) return null;
|
||||
|
||||
snapshot.add(this.referenceVariableItemId, value);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private Integer readUserValue(Room room, RoomUnit roomUnit) {
|
||||
if (room == null || roomUnit == null) return null;
|
||||
|
||||
if (isInternalVariableToken(this.variableToken)) {
|
||||
String key = getInternalVariableKey(this.variableToken);
|
||||
return canUseUserInternalReference(key) ? this.readUserInternalValue(room, roomUnit, key) : null;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.variableItemId);
|
||||
if (definition == null || !definition.hasValue()) return null;
|
||||
|
||||
Habbo habbo = room.getHabbo(roomUnit);
|
||||
return (habbo != null) ? room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), this.variableItemId) : null;
|
||||
}
|
||||
|
||||
private Integer readFurniValue(Room room, HabboItem item) {
|
||||
if (room == null || item == null) return null;
|
||||
|
||||
if (isInternalVariableToken(this.variableToken)) {
|
||||
String key = getInternalVariableKey(this.variableToken);
|
||||
return canUseFurniInternalReference(key) ? this.readFurniInternalValue(room, item, key) : null;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.variableItemId);
|
||||
return (definition != null && definition.hasValue()) ? room.getFurniVariableManager().getCurrentValue(item.getId(), this.variableItemId) : null;
|
||||
}
|
||||
|
||||
private Integer readRoomValue(Room room) {
|
||||
if (room == null) return null;
|
||||
|
||||
if (isInternalVariableToken(this.variableToken)) {
|
||||
String key = getInternalVariableKey(this.variableToken);
|
||||
return canUseRoomInternalReference(key) ? this.readRoomInternalValue(room, key) : null;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getRoomVariableManager().getDefinitionInfo(this.variableItemId);
|
||||
return (definition != null && definition.hasValue()) ? room.getRoomVariableManager().getCurrentValue(this.variableItemId) : null;
|
||||
}
|
||||
|
||||
private Integer readContextTargetValue(WiredContext ctx, Room room) {
|
||||
if (ctx == null || room == null) return null;
|
||||
|
||||
if (isInternalVariableToken(this.variableToken)) {
|
||||
String key = getInternalVariableKey(this.variableToken);
|
||||
return canUseContextInternalReference(key) ? WiredInternalVariableSupport.readContextValue(ctx, key) : null;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, this.variableItemId);
|
||||
if (definition == null || !definition.hasValue() || !WiredContextVariableSupport.hasVariable(ctx, this.variableItemId)) return null;
|
||||
|
||||
return WiredContextVariableSupport.getCurrentValue(ctx, this.variableItemId);
|
||||
}
|
||||
|
||||
private Integer referenceFor(ReferenceSnapshot snapshot, int destinationEntityId, int destinationTarget, int destinationIndex) {
|
||||
if (this.referenceMode != REFERENCE_VARIABLE) return this.referenceConstantValue;
|
||||
if (snapshot == null || snapshot.isEmpty()) return null;
|
||||
if (snapshot.targetType == destinationTarget && snapshot.values.containsKey(destinationEntityId)) return snapshot.values.get(destinationEntityId);
|
||||
if (destinationIndex >= 0 && destinationIndex < snapshot.values.size()) return new ArrayList<>(snapshot.values.values()).get(destinationIndex);
|
||||
return new ArrayList<>(snapshot.values.values()).get(0);
|
||||
}
|
||||
|
||||
private boolean matchesComparison(Integer currentValue, Integer referenceValue) {
|
||||
if (currentValue == null || referenceValue == null) return false;
|
||||
|
||||
return switch (this.comparison) {
|
||||
case COMPARISON_GREATER_THAN -> currentValue > referenceValue;
|
||||
case COMPARISON_GREATER_THAN_OR_EQUAL -> currentValue >= referenceValue;
|
||||
case COMPARISON_LESS_THAN_OR_EQUAL -> currentValue <= referenceValue;
|
||||
case COMPARISON_LESS_THAN -> currentValue < referenceValue;
|
||||
case COMPARISON_NOT_EQUAL -> !currentValue.equals(referenceValue);
|
||||
default -> currentValue.equals(referenceValue);
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isValidSource(Room room, int targetType, String variableToken) {
|
||||
if (variableToken == null || variableToken.isEmpty()) return false;
|
||||
|
||||
return switch (targetType) {
|
||||
case TARGET_USER -> isInternalVariableToken(variableToken)
|
||||
? canUseUserInternalReference(getInternalVariableKey(variableToken))
|
||||
: this.isValidUserCustomValue(room, getCustomItemId(variableToken));
|
||||
case TARGET_FURNI -> isInternalVariableToken(variableToken)
|
||||
? canUseFurniInternalReference(getInternalVariableKey(variableToken))
|
||||
: this.isValidFurniCustomValue(room, getCustomItemId(variableToken));
|
||||
case TARGET_CONTEXT -> isInternalVariableToken(variableToken)
|
||||
? canUseContextInternalReference(getInternalVariableKey(variableToken))
|
||||
: this.isValidContextCustomValue(room, getCustomItemId(variableToken));
|
||||
case TARGET_ROOM -> isInternalVariableToken(variableToken)
|
||||
? canUseRoomInternalReference(getInternalVariableKey(variableToken))
|
||||
: this.isValidRoomCustomValue(room, getCustomItemId(variableToken));
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isValidReference(Room room, int targetType, String variableToken) {
|
||||
return this.isValidSource(room, targetType, variableToken);
|
||||
}
|
||||
|
||||
private boolean isValidUserCustomValue(Room room, int variableItemId) {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getUserVariableManager().getDefinitionInfo(variableItemId) : null;
|
||||
return definition != null && definition.hasValue();
|
||||
}
|
||||
|
||||
private boolean isValidFurniCustomValue(Room room, int variableItemId) {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getFurniVariableManager().getDefinitionInfo(variableItemId) : null;
|
||||
return definition != null && definition.hasValue();
|
||||
}
|
||||
|
||||
private boolean isValidRoomCustomValue(Room room, int variableItemId) {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getRoomVariableManager().getDefinitionInfo(variableItemId) : null;
|
||||
return definition != null && definition.hasValue();
|
||||
}
|
||||
|
||||
private boolean isValidContextCustomValue(Room room, int variableItemId) {
|
||||
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, variableItemId);
|
||||
return definition != null && definition.hasValue();
|
||||
}
|
||||
|
||||
private Integer readUserInternalValue(Room room, RoomUnit roomUnit, String key) {
|
||||
return WiredInternalVariableSupport.readUserValue(room, roomUnit, key);
|
||||
}
|
||||
|
||||
private Integer readFurniInternalValue(Room room, HabboItem item, String key) {
|
||||
return WiredInternalVariableSupport.readFurniValue(room, item, key);
|
||||
}
|
||||
|
||||
private Integer readRoomInternalValue(Room room, String key) {
|
||||
return WiredInternalVariableSupport.readRoomValue(room, key);
|
||||
}
|
||||
|
||||
private Integer getUserTeamScore(Room room, Habbo habbo) {
|
||||
if (room == null || habbo == null || habbo.getHabboInfo().getGamePlayer() == null) return null;
|
||||
|
||||
Game game = this.resolveTeamGame(room, habbo);
|
||||
GamePlayer gamePlayer = habbo.getHabboInfo().getGamePlayer();
|
||||
|
||||
if (game == null || gamePlayer.getTeamColor() == null) return gamePlayer.getScore();
|
||||
|
||||
GameTeam team = game.getTeam(gamePlayer.getTeamColor());
|
||||
return (team != null) ? team.getTotalScore() : gamePlayer.getScore();
|
||||
}
|
||||
|
||||
private Integer getTeamColorId(int effectId) {
|
||||
TeamEffectData data = this.getTeamEffectData(effectId);
|
||||
return data == null ? null : data.colorId;
|
||||
}
|
||||
|
||||
private Integer getTeamTypeId(int effectId) {
|
||||
TeamEffectData data = this.getTeamEffectData(effectId);
|
||||
return data == null ? null : data.typeId;
|
||||
}
|
||||
|
||||
private int getTeamMetric(Room room, GameTeamColors color, boolean score) {
|
||||
Game game = this.resolveTeamGame(room, null);
|
||||
if (game == null || color == null) return 0;
|
||||
|
||||
GameTeam team = game.getTeam(color);
|
||||
if (team == null) return 0;
|
||||
|
||||
return score ? team.getTotalScore() : team.getMembers().size();
|
||||
}
|
||||
|
||||
private Game resolveTeamGame(Room room, Habbo habbo) {
|
||||
if (room == null) return null;
|
||||
|
||||
if (habbo != null && habbo.getHabboInfo() != null && habbo.getHabboInfo().getCurrentGame() != null) {
|
||||
Game game = room.getGame(habbo.getHabboInfo().getCurrentGame());
|
||||
if (game != null) return game;
|
||||
}
|
||||
|
||||
Game wiredGame = room.getGame(WiredGame.class);
|
||||
if (wiredGame != null) return wiredGame;
|
||||
|
||||
Game freezeGame = room.getGame(FreezeGame.class);
|
||||
if (freezeGame != null) return freezeGame;
|
||||
|
||||
return room.getGame(BattleBanzaiGame.class);
|
||||
}
|
||||
|
||||
private List<HabboItem> parseItems(int[] ids, Room room) {
|
||||
List<HabboItem> items = new ArrayList<>();
|
||||
if (ids == null || room == null) return items;
|
||||
|
||||
for (int id : ids) {
|
||||
HabboItem item = room.getHabboItem(id);
|
||||
if (item != null) items.add(item);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private List<HabboItem> parseItems(List<Integer> ids, Room room) {
|
||||
List<HabboItem> items = new ArrayList<>();
|
||||
if (ids == null || room == null) return items;
|
||||
|
||||
for (Integer id : ids) {
|
||||
if (id == null || id <= 0) continue;
|
||||
|
||||
HabboItem item = room.getHabboItem(id);
|
||||
if (item != null) items.add(item);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private List<HabboItem> parseItems(String ids, Room room) {
|
||||
List<HabboItem> items = new ArrayList<>();
|
||||
if (ids == null || ids.trim().isEmpty() || room == null) return items;
|
||||
|
||||
for (String part : ids.split("[;,\\t]")) {
|
||||
int id = parseInteger(part);
|
||||
if (id <= 0) continue;
|
||||
|
||||
HabboItem item = room.getHabboItem(id);
|
||||
if (item != null) items.add(item);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private void refreshReferenceItems() {
|
||||
THashSet<HabboItem> staleItems = new THashSet<>();
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
|
||||
if (room == null) {
|
||||
staleItems.addAll(this.referenceSelectedItems);
|
||||
} else {
|
||||
for (HabboItem item : this.referenceSelectedItems) {
|
||||
if (item == null || item.getRoomId() != room.getId() || room.getHabboItem(item.getId()) == null) {
|
||||
staleItems.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.referenceSelectedItems.removeAll(staleItems);
|
||||
}
|
||||
|
||||
private String serializeStringData() {
|
||||
return (this.variableToken == null ? "" : this.variableToken) + DELIM + (this.referenceVariableToken == null ? "" : this.referenceVariableToken) + DELIM + this.serializeIds(this.referenceSelectedItems);
|
||||
}
|
||||
|
||||
private String[] parseStringData(String value) {
|
||||
return (value == null || value.isEmpty()) ? new String[0] : value.split("\\t", -1);
|
||||
}
|
||||
|
||||
private List<Integer> toIds(THashSet<HabboItem> items) {
|
||||
List<Integer> ids = new ArrayList<>();
|
||||
for (HabboItem item : items) {
|
||||
if (item != null) ids.add(item.getId());
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
private String serializeIds(THashSet<HabboItem> items) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (HabboItem item : items) {
|
||||
if (item == null) continue;
|
||||
if (builder.length() > 0) builder.append(FURNI_DELIM);
|
||||
builder.append(item.getId());
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void setReferenceVariableToken(String token) {
|
||||
this.referenceVariableToken = normalizeVariableToken(token);
|
||||
this.referenceVariableItemId = getCustomItemId(this.referenceVariableToken);
|
||||
}
|
||||
|
||||
private static boolean canUseUserInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseUserReference(key);
|
||||
}
|
||||
|
||||
private static boolean canUseFurniInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseFurniReference(key);
|
||||
}
|
||||
|
||||
private static boolean canUseRoomInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseRoomReference(key);
|
||||
}
|
||||
|
||||
private static boolean canUseContextInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseContextReference(key);
|
||||
}
|
||||
|
||||
private static int param(int[] params, int index, int fallback) {
|
||||
return (params.length > index) ? params[index] : fallback;
|
||||
}
|
||||
|
||||
private static int normalizeTargetTypeExtended(int value) {
|
||||
return switch (value) {
|
||||
case TARGET_FURNI, TARGET_CONTEXT, TARGET_ROOM -> value;
|
||||
default -> TARGET_USER;
|
||||
};
|
||||
}
|
||||
|
||||
private static int normalizeReferenceMode(int value) {
|
||||
return (value == REFERENCE_VARIABLE) ? REFERENCE_VARIABLE : REFERENCE_CONSTANT;
|
||||
}
|
||||
|
||||
private static int normalizeReferenceFurniSource(int value) {
|
||||
return switch (value) {
|
||||
case SOURCE_SECONDARY_SELECTED, WiredSourceUtil.SOURCE_SELECTOR, WiredSourceUtil.SOURCE_SIGNAL -> value;
|
||||
default -> WiredSourceUtil.SOURCE_TRIGGER;
|
||||
};
|
||||
}
|
||||
|
||||
private static int normalizeComparison(int value) {
|
||||
return switch (value) {
|
||||
case COMPARISON_GREATER_THAN, COMPARISON_GREATER_THAN_OR_EQUAL, COMPARISON_LESS_THAN_OR_EQUAL, COMPARISON_LESS_THAN, COMPARISON_NOT_EQUAL -> value;
|
||||
default -> COMPARISON_EQUAL;
|
||||
};
|
||||
}
|
||||
|
||||
private static int parseInteger(String value) {
|
||||
try {
|
||||
return (value == null || value.trim().isEmpty()) ? 0 : Integer.parseInt(value.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static class JsonData {
|
||||
int targetType, variableItemId, comparison, referenceMode, referenceConstantValue, referenceTargetType, referenceVariableItemId, userSource, furniSource, referenceUserSource, referenceFurniSource, quantifier;
|
||||
String variableToken, referenceVariableToken;
|
||||
List<Integer> selectedItemIds, referenceSelectedItemIds;
|
||||
|
||||
JsonData(int targetType, String variableToken, int variableItemId, int comparison, int referenceMode, int referenceConstantValue, int referenceTargetType, String referenceVariableToken, int referenceVariableItemId, int userSource, int furniSource, int referenceUserSource, int referenceFurniSource, int quantifier, List<Integer> selectedItemIds, List<Integer> referenceSelectedItemIds) {
|
||||
this.targetType = targetType;
|
||||
this.variableToken = variableToken;
|
||||
this.variableItemId = variableItemId;
|
||||
this.comparison = comparison;
|
||||
this.referenceMode = referenceMode;
|
||||
this.referenceConstantValue = referenceConstantValue;
|
||||
this.referenceTargetType = referenceTargetType;
|
||||
this.referenceVariableToken = referenceVariableToken;
|
||||
this.referenceVariableItemId = referenceVariableItemId;
|
||||
this.userSource = userSource;
|
||||
this.furniSource = furniSource;
|
||||
this.referenceUserSource = referenceUserSource;
|
||||
this.referenceFurniSource = referenceFurniSource;
|
||||
this.quantifier = quantifier;
|
||||
this.selectedItemIds = selectedItemIds;
|
||||
this.referenceSelectedItemIds = referenceSelectedItemIds;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReferenceSnapshot {
|
||||
final int targetType;
|
||||
final LinkedHashMap<Integer, Integer> values = new LinkedHashMap<>();
|
||||
|
||||
ReferenceSnapshot(int targetType) {
|
||||
this.targetType = targetType;
|
||||
}
|
||||
|
||||
void add(int entityId, int value) {
|
||||
this.values.put(entityId, value);
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return this.values.isEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
+932
@@ -0,0 +1,932 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.effects;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.bots.Bot;
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.games.Game;
|
||||
import com.eu.habbo.habbohotel.games.GamePlayer;
|
||||
import com.eu.habbo.habbohotel.games.GameTeam;
|
||||
import com.eu.habbo.habbohotel.games.GameTeamColors;
|
||||
import com.eu.habbo.habbohotel.games.battlebanzai.BattleBanzaiGame;
|
||||
import com.eu.habbo.habbohotel.games.freeze.FreezeGame;
|
||||
import com.eu.habbo.habbohotel.games.wired.WiredGame;
|
||||
import com.eu.habbo.habbohotel.items.FurnitureType;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
|
||||
import com.eu.habbo.habbohotel.pets.Pet;
|
||||
import com.eu.habbo.habbohotel.rooms.FurnitureMovementError;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomTile;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomTileState;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUserRotation;
|
||||
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
|
||||
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.habbohotel.wired.core.WiredContext;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredInternalVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredUserMovementHelper;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
|
||||
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer;
|
||||
import com.eu.habbo.util.HotelDateTimeUtil;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class WiredEffectChangeVariableValue extends InteractionWiredEffect {
|
||||
public static final WiredEffectType type = WiredEffectType.CHANGE_VAR_VAL;
|
||||
public static final int TARGET_USER = 0, TARGET_FURNI = 1, TARGET_CONTEXT = 2, TARGET_ROOM = 3;
|
||||
public static final int REF_CONSTANT = 0, REF_VARIABLE = 1;
|
||||
public static final int OP_ASSIGN = 0, OP_ADD = 1, OP_SUB = 2, OP_MUL = 3, OP_DIV = 4, OP_POW = 5, OP_MOD = 6, OP_MIN = 40, OP_MAX = 41, OP_RANDOM = 50, OP_ABS = 60, OP_AND = 100, OP_OR = 101, OP_XOR = 102, OP_NOT = 103, OP_LSHIFT = 104, OP_RSHIFT = 105;
|
||||
|
||||
private static final int SOURCE_SECONDARY_SELECTED = 101;
|
||||
private static final String DELIM = "\t", FURNI_DELIM = ";";
|
||||
private static final String CUSTOM_TOKEN_PREFIX = "custom:";
|
||||
private static final String INTERNAL_TOKEN_PREFIX = "internal:";
|
||||
|
||||
private int destinationTargetType = TARGET_USER, destinationVariableItemId = 0, operation = OP_ASSIGN, referenceMode = REF_CONSTANT, referenceConstantValue = 0, referenceTargetType = TARGET_USER, referenceVariableItemId = 0, destinationUserSource = WiredSourceUtil.SOURCE_TRIGGER, destinationFurniSource = WiredSourceUtil.SOURCE_TRIGGER, referenceUserSource = WiredSourceUtil.SOURCE_TRIGGER, referenceFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
private String destinationVariableToken = "", referenceVariableToken = "";
|
||||
private final List<HabboItem> destinationSelectedFurni = new ArrayList<>();
|
||||
private final List<HabboItem> referenceSelectedFurni = new ArrayList<>();
|
||||
|
||||
public WiredEffectChangeVariableValue(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredEffectChangeVariableValue(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WiredContext ctx) {
|
||||
Room room = ctx.room();
|
||||
if (room == null) return;
|
||||
|
||||
switch (this.destinationTargetType) {
|
||||
case TARGET_USER -> this.executeUsers(ctx, room);
|
||||
case TARGET_FURNI -> this.executeFurni(ctx, room);
|
||||
case TARGET_CONTEXT -> this.executeContext(ctx, room);
|
||||
case TARGET_ROOM -> this.executeRoom(ctx, room);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeUsers(WiredContext ctx, Room room) {
|
||||
if (isInternalVariableToken(this.destinationVariableToken)) {
|
||||
this.executeUsersInternal(ctx, room, getInternalVariableKey(this.destinationVariableToken));
|
||||
return;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.destinationVariableItemId);
|
||||
if (definition == null || !definition.hasValue() || definition.isReadOnly()) return;
|
||||
|
||||
ReferenceSnapshot references = this.resolveReferences(ctx, room);
|
||||
int index = 0;
|
||||
|
||||
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.destinationUserSource)) {
|
||||
if (roomUnit == null) continue;
|
||||
|
||||
Habbo habbo = room.getHabbo(roomUnit);
|
||||
if (habbo == null) continue;
|
||||
|
||||
Integer referenceValue = this.referenceFor(references, roomUnit.getId(), TARGET_USER, index++);
|
||||
if (!this.isUnaryOperation() && referenceValue == null) continue;
|
||||
|
||||
int currentValue = room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), this.destinationVariableItemId);
|
||||
room.getUserVariableManager().updateVariableValue(habbo.getHabboInfo().getId(), this.destinationVariableItemId, this.applyOperation(currentValue, referenceValue));
|
||||
}
|
||||
}
|
||||
|
||||
private void executeUsersInternal(WiredContext ctx, Room room, String key) {
|
||||
if (!canUseUserInternalDestination(key)) return;
|
||||
|
||||
ReferenceSnapshot references = this.resolveReferences(ctx, room);
|
||||
int index = 0;
|
||||
|
||||
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.destinationUserSource)) {
|
||||
if (roomUnit == null) continue;
|
||||
|
||||
Integer currentValue = this.readUserInternalValue(room, roomUnit, key);
|
||||
if (currentValue == null) continue;
|
||||
|
||||
Integer referenceValue = this.referenceFor(references, roomUnit.getId(), TARGET_USER, index++);
|
||||
if (!this.isUnaryOperation() && referenceValue == null) continue;
|
||||
|
||||
this.writeUserInternalValue(room, roomUnit, key, this.applyOperation(currentValue, referenceValue));
|
||||
}
|
||||
}
|
||||
|
||||
private void executeFurni(WiredContext ctx, Room room) {
|
||||
if (isInternalVariableToken(this.destinationVariableToken)) {
|
||||
this.executeFurniInternal(ctx, room, getInternalVariableKey(this.destinationVariableToken));
|
||||
return;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.destinationVariableItemId);
|
||||
if (definition == null || !definition.hasValue() || definition.isReadOnly()) return;
|
||||
if (this.destinationFurniSource == WiredSourceUtil.SOURCE_SELECTED) this.validateItems(this.destinationSelectedFurni);
|
||||
|
||||
ReferenceSnapshot references = this.resolveReferences(ctx, room);
|
||||
int index = 0;
|
||||
|
||||
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, this.destinationFurniSource, this.destinationSelectedFurni)) {
|
||||
if (item == null) continue;
|
||||
|
||||
Integer referenceValue = this.referenceFor(references, item.getId(), TARGET_FURNI, index++);
|
||||
if (!this.isUnaryOperation() && referenceValue == null) continue;
|
||||
|
||||
int currentValue = room.getFurniVariableManager().getCurrentValue(item.getId(), this.destinationVariableItemId);
|
||||
room.getFurniVariableManager().updateVariableValue(item.getId(), this.destinationVariableItemId, this.applyOperation(currentValue, referenceValue));
|
||||
}
|
||||
}
|
||||
|
||||
private void executeFurniInternal(WiredContext ctx, Room room, String key) {
|
||||
if (!canUseFurniInternalDestination(key)) return;
|
||||
if (this.destinationFurniSource == WiredSourceUtil.SOURCE_SELECTED) this.validateItems(this.destinationSelectedFurni);
|
||||
|
||||
ReferenceSnapshot references = this.resolveReferences(ctx, room);
|
||||
int index = 0;
|
||||
|
||||
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, this.destinationFurniSource, this.destinationSelectedFurni)) {
|
||||
if (item == null) continue;
|
||||
|
||||
Integer currentValue = this.readFurniInternalValue(room, item, key);
|
||||
if (currentValue == null) continue;
|
||||
|
||||
Integer referenceValue = this.referenceFor(references, item.getId(), TARGET_FURNI, index++);
|
||||
if (!this.isUnaryOperation() && referenceValue == null) continue;
|
||||
|
||||
this.writeFurniInternalValue(room, item, key, this.applyOperation(currentValue, referenceValue));
|
||||
}
|
||||
}
|
||||
|
||||
private void executeRoom(WiredContext ctx, Room room) {
|
||||
if (isInternalVariableToken(this.destinationVariableToken)) return;
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getRoomVariableManager().getDefinitionInfo(this.destinationVariableItemId);
|
||||
if (definition == null || !definition.hasValue() || definition.isReadOnly()) return;
|
||||
|
||||
ReferenceSnapshot references = this.resolveReferences(ctx, room);
|
||||
Integer referenceValue = this.referenceFor(references, room.getId(), TARGET_ROOM, 0);
|
||||
if (!this.isUnaryOperation() && referenceValue == null) return;
|
||||
|
||||
int currentValue = room.getRoomVariableManager().getCurrentValue(this.destinationVariableItemId);
|
||||
room.getRoomVariableManager().updateVariableValue(this.destinationVariableItemId, this.applyOperation(currentValue, referenceValue));
|
||||
}
|
||||
|
||||
private void executeContext(WiredContext ctx, Room room) {
|
||||
if (isInternalVariableToken(this.destinationVariableToken)) return;
|
||||
|
||||
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, this.destinationVariableItemId);
|
||||
if (definition == null || !definition.hasValue() || definition.isReadOnly()) return;
|
||||
|
||||
ReferenceSnapshot references = this.resolveReferences(ctx, room);
|
||||
Integer referenceValue = this.referenceFor(references, this.destinationVariableItemId, TARGET_CONTEXT, 0);
|
||||
if (!this.isUnaryOperation() && referenceValue == null) return;
|
||||
if (!WiredContextVariableSupport.hasVariable(ctx, this.destinationVariableItemId)) return;
|
||||
|
||||
Integer currentValue = WiredContextVariableSupport.getCurrentValue(ctx, this.destinationVariableItemId);
|
||||
int nextValue = this.applyOperation(currentValue != null ? currentValue : 0, referenceValue);
|
||||
WiredContextVariableSupport.updateVariableValue(ctx, room, this.destinationVariableItemId, nextValue);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
this.validateItems(this.destinationSelectedFurni);
|
||||
this.validateItems(this.referenceSelectedFurni);
|
||||
|
||||
List<HabboItem> selectedItems = new ArrayList<>();
|
||||
if (this.destinationTargetType == TARGET_FURNI && this.destinationFurniSource == WiredSourceUtil.SOURCE_SELECTED) selectedItems.addAll(this.destinationSelectedFurni);
|
||||
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
|
||||
message.appendInt(selectedItems.size());
|
||||
for (HabboItem item : selectedItems) message.appendInt(item.getId());
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(this.serializeStringData());
|
||||
message.appendInt(9);
|
||||
message.appendInt(this.destinationTargetType);
|
||||
message.appendInt(this.operation);
|
||||
message.appendInt(this.referenceMode);
|
||||
message.appendInt(this.referenceConstantValue);
|
||||
message.appendInt(this.referenceTargetType);
|
||||
message.appendInt(this.destinationUserSource);
|
||||
message.appendInt(this.destinationFurniSource);
|
||||
message.appendInt(this.referenceUserSource);
|
||||
message.appendInt(this.referenceFurniSource);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getType().code);
|
||||
message.appendInt(this.getDelay());
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
|
||||
Room room = this.getRoom();
|
||||
if (room == null) throw new WiredSaveException("Room not found");
|
||||
|
||||
int[] params = settings.getIntParams();
|
||||
String[] stringParts = this.parseStringData(settings.getStringParam());
|
||||
int nextDestinationTargetType = normalizeTargetType(param(params, 0, TARGET_USER));
|
||||
int nextOperation = normalizeOperation(param(params, 1, OP_ASSIGN));
|
||||
int nextReferenceMode = normalizeReferenceMode(param(params, 2, REF_CONSTANT));
|
||||
int nextReferenceConstantValue = param(params, 3, 0);
|
||||
int nextReferenceTargetType = normalizeTargetType(param(params, 4, TARGET_USER));
|
||||
int nextDestinationUserSource = normalizeUserSource(param(params, 5, WiredSourceUtil.SOURCE_TRIGGER));
|
||||
int nextDestinationFurniSource = normalizeDestinationFurniSource(param(params, 6, WiredSourceUtil.SOURCE_TRIGGER));
|
||||
int nextReferenceUserSource = normalizeUserSource(param(params, 7, WiredSourceUtil.SOURCE_TRIGGER));
|
||||
int nextReferenceFurniSource = normalizeReferenceFurniSource(param(params, 8, WiredSourceUtil.SOURCE_TRIGGER));
|
||||
String nextDestinationVariableToken = normalizeVariableToken((stringParts.length > 0) ? stringParts[0] : "");
|
||||
String nextReferenceVariableToken = normalizeVariableToken((stringParts.length > 1) ? stringParts[1] : "");
|
||||
|
||||
this.validateDestination(room, nextDestinationTargetType, nextDestinationVariableToken);
|
||||
if (nextReferenceMode == REF_VARIABLE) this.validateReference(room, nextReferenceTargetType, nextReferenceVariableToken);
|
||||
|
||||
int maxDelay = Emulator.getConfig().getInt("hotel.wired.max_delay", 20);
|
||||
if (settings.getDelay() > maxDelay) throw new WiredSaveException("Delay too long");
|
||||
|
||||
List<HabboItem> nextDestinationItems = (nextDestinationTargetType == TARGET_FURNI && nextDestinationFurniSource == WiredSourceUtil.SOURCE_SELECTED) ? this.parseItems(settings.getFurniIds(), room) : new ArrayList<>();
|
||||
List<HabboItem> nextReferenceItems = (nextReferenceMode == REF_VARIABLE && nextReferenceTargetType == TARGET_FURNI && nextReferenceFurniSource == SOURCE_SECONDARY_SELECTED) ? this.parseItems((stringParts.length > 2) ? stringParts[2] : "", room) : new ArrayList<>();
|
||||
int selectionLimit = Emulator.getConfig().getInt("hotel.wired.furni.selection.count");
|
||||
if (nextDestinationItems.size() > selectionLimit || nextReferenceItems.size() > selectionLimit) throw new WiredSaveException("Too many furni selected");
|
||||
|
||||
this.destinationSelectedFurni.clear();
|
||||
this.destinationSelectedFurni.addAll(nextDestinationItems);
|
||||
this.referenceSelectedFurni.clear();
|
||||
this.referenceSelectedFurni.addAll(nextReferenceItems);
|
||||
this.destinationTargetType = nextDestinationTargetType;
|
||||
this.setDestinationVariableToken(nextDestinationVariableToken);
|
||||
this.operation = nextOperation;
|
||||
this.referenceMode = nextReferenceMode;
|
||||
this.referenceConstantValue = nextReferenceConstantValue;
|
||||
this.referenceTargetType = nextReferenceTargetType;
|
||||
this.setReferenceVariableToken(nextReferenceVariableToken);
|
||||
this.destinationUserSource = nextDestinationUserSource;
|
||||
this.destinationFurniSource = nextDestinationFurniSource;
|
||||
this.referenceUserSource = nextReferenceUserSource;
|
||||
this.referenceFurniSource = nextReferenceFurniSource;
|
||||
this.setDelay(settings.getDelay());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
return WiredManager.getGson().toJson(new JsonData(this.destinationTargetType, this.destinationVariableToken, this.destinationVariableItemId, this.operation, this.referenceMode, this.referenceConstantValue, this.referenceTargetType, this.referenceVariableToken, this.referenceVariableItemId, this.destinationUserSource, this.destinationFurniSource, this.referenceUserSource, this.referenceFurniSource, this.getDelay(), this.toIds(this.destinationSelectedFurni), this.toIds(this.referenceSelectedFurni)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty() || !wiredData.startsWith("{")) return;
|
||||
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
if (data == null) return;
|
||||
|
||||
this.destinationTargetType = normalizeTargetType(data.destinationTargetType);
|
||||
this.setDestinationVariableToken(normalizeVariableToken((data.destinationVariableToken != null) ? data.destinationVariableToken : ((data.destinationVariableItemId > 0) ? String.valueOf(data.destinationVariableItemId) : "")));
|
||||
this.operation = normalizeOperation(data.operation);
|
||||
this.referenceMode = normalizeReferenceMode(data.referenceMode);
|
||||
this.referenceConstantValue = data.referenceConstantValue;
|
||||
this.referenceTargetType = normalizeTargetType(data.referenceTargetType);
|
||||
this.setReferenceVariableToken(normalizeVariableToken((data.referenceVariableToken != null) ? data.referenceVariableToken : ((data.referenceVariableItemId > 0) ? String.valueOf(data.referenceVariableItemId) : "")));
|
||||
this.destinationUserSource = normalizeUserSource(data.destinationUserSource);
|
||||
this.destinationFurniSource = normalizeDestinationFurniSource(data.destinationFurniSource);
|
||||
this.referenceUserSource = normalizeUserSource(data.referenceUserSource);
|
||||
this.referenceFurniSource = normalizeReferenceFurniSource(data.referenceFurniSource);
|
||||
this.setDelay(Math.max(0, data.delay));
|
||||
|
||||
if (room != null) {
|
||||
try {
|
||||
this.destinationSelectedFurni.addAll(this.parseItems(data.destinationSelectedFurniIds, room));
|
||||
this.referenceSelectedFurni.addAll(this.parseItems(data.referenceSelectedFurniIds, room));
|
||||
} catch (WiredSaveException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
this.destinationTargetType = TARGET_USER;
|
||||
this.setDestinationVariableToken("");
|
||||
this.operation = OP_ASSIGN;
|
||||
this.referenceMode = REF_CONSTANT;
|
||||
this.referenceConstantValue = 0;
|
||||
this.referenceTargetType = TARGET_USER;
|
||||
this.setReferenceVariableToken("");
|
||||
this.destinationUserSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.destinationFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.referenceUserSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.referenceFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.destinationSelectedFurni.clear();
|
||||
this.referenceSelectedFurni.clear();
|
||||
this.setDelay(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public WiredEffectType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresTriggeringUser() {
|
||||
return (this.destinationTargetType == TARGET_USER && this.destinationUserSource == WiredSourceUtil.SOURCE_TRIGGER)
|
||||
|| (this.referenceMode == REF_VARIABLE && this.referenceTargetType == TARGET_USER && this.referenceUserSource == WiredSourceUtil.SOURCE_TRIGGER);
|
||||
}
|
||||
|
||||
private ReferenceSnapshot resolveReferences(WiredContext ctx, Room room) {
|
||||
if (this.referenceMode != REF_VARIABLE) return null;
|
||||
|
||||
return switch (this.referenceTargetType) {
|
||||
case TARGET_USER -> this.userReferences(ctx, room);
|
||||
case TARGET_FURNI -> this.furniReferences(ctx, room);
|
||||
case TARGET_CONTEXT -> this.contextReferences(ctx, room);
|
||||
case TARGET_ROOM -> this.roomReferences(room);
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private ReferenceSnapshot userReferences(WiredContext ctx, Room room) {
|
||||
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_USER);
|
||||
|
||||
if (isInternalVariableToken(this.referenceVariableToken)) {
|
||||
String key = getInternalVariableKey(this.referenceVariableToken);
|
||||
if (!canUseUserInternalReference(key)) return null;
|
||||
|
||||
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.referenceUserSource)) {
|
||||
if (roomUnit == null) continue;
|
||||
|
||||
Integer value = this.readUserInternalValue(room, roomUnit, key);
|
||||
if (value != null) snapshot.add(roomUnit.getId(), value);
|
||||
}
|
||||
|
||||
return snapshot.isEmpty() ? null : snapshot;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.referenceVariableItemId);
|
||||
if (definition == null || !definition.hasValue()) return null;
|
||||
|
||||
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.referenceUserSource)) {
|
||||
if (roomUnit == null) continue;
|
||||
|
||||
Habbo habbo = room.getHabbo(roomUnit);
|
||||
if (habbo != null) snapshot.add(roomUnit.getId(), room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), this.referenceVariableItemId));
|
||||
}
|
||||
|
||||
return snapshot.isEmpty() ? null : snapshot;
|
||||
}
|
||||
|
||||
private ReferenceSnapshot furniReferences(WiredContext ctx, Room room) {
|
||||
int source = (this.referenceFurniSource == SOURCE_SECONDARY_SELECTED) ? WiredSourceUtil.SOURCE_SELECTED : this.referenceFurniSource;
|
||||
if (source == WiredSourceUtil.SOURCE_SELECTED) this.validateItems(this.referenceSelectedFurni);
|
||||
|
||||
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_FURNI);
|
||||
|
||||
if (isInternalVariableToken(this.referenceVariableToken)) {
|
||||
String key = getInternalVariableKey(this.referenceVariableToken);
|
||||
if (!canUseFurniInternalReference(key)) return null;
|
||||
|
||||
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, source, this.referenceSelectedFurni)) {
|
||||
if (item == null) continue;
|
||||
|
||||
Integer value = this.readFurniInternalValue(room, item, key);
|
||||
if (value != null) snapshot.add(item.getId(), value);
|
||||
}
|
||||
|
||||
return snapshot.isEmpty() ? null : snapshot;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.referenceVariableItemId);
|
||||
if (definition == null || !definition.hasValue()) return null;
|
||||
|
||||
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, source, this.referenceSelectedFurni)) {
|
||||
if (item != null) snapshot.add(item.getId(), room.getFurniVariableManager().getCurrentValue(item.getId(), this.referenceVariableItemId));
|
||||
}
|
||||
|
||||
return snapshot.isEmpty() ? null : snapshot;
|
||||
}
|
||||
|
||||
private ReferenceSnapshot roomReferences(Room room) {
|
||||
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_ROOM);
|
||||
|
||||
if (isInternalVariableToken(this.referenceVariableToken)) {
|
||||
String key = getInternalVariableKey(this.referenceVariableToken);
|
||||
if (!canUseRoomInternalReference(key)) return null;
|
||||
|
||||
Integer value = this.readRoomInternalValue(room, key);
|
||||
if (value == null) return null;
|
||||
|
||||
snapshot.add(room.getId(), value);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getRoomVariableManager().getDefinitionInfo(this.referenceVariableItemId);
|
||||
if (definition == null || !definition.hasValue()) return null;
|
||||
|
||||
snapshot.add(room.getId(), room.getRoomVariableManager().getCurrentValue(this.referenceVariableItemId));
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private ReferenceSnapshot contextReferences(WiredContext ctx, Room room) {
|
||||
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_CONTEXT);
|
||||
|
||||
if (isInternalVariableToken(this.referenceVariableToken)) {
|
||||
String key = getInternalVariableKey(this.referenceVariableToken);
|
||||
if (!canUseContextInternalReference(key)) return null;
|
||||
|
||||
Integer value = WiredInternalVariableSupport.readContextValue(ctx, key);
|
||||
if (value == null) return null;
|
||||
|
||||
snapshot.add(this.referenceVariableItemId > 0 ? this.referenceVariableItemId : room.getId(), value);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, this.referenceVariableItemId);
|
||||
if (definition == null || !definition.hasValue() || !WiredContextVariableSupport.hasVariable(ctx, this.referenceVariableItemId)) return null;
|
||||
|
||||
Integer value = WiredContextVariableSupport.getCurrentValue(ctx, this.referenceVariableItemId);
|
||||
if (value == null) return null;
|
||||
|
||||
snapshot.add(this.referenceVariableItemId, value);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private Integer referenceFor(ReferenceSnapshot snapshot, int destinationEntityId, int destinationTarget, int destinationIndex) {
|
||||
if (this.referenceMode != REF_VARIABLE) return this.referenceConstantValue;
|
||||
if (this.isUnaryOperation()) return 0;
|
||||
if (snapshot == null || snapshot.isEmpty()) return null;
|
||||
if (snapshot.targetType == destinationTarget && snapshot.values.containsKey(destinationEntityId)) return snapshot.values.get(destinationEntityId);
|
||||
if (destinationIndex >= 0 && destinationIndex < snapshot.values.size()) return new ArrayList<>(snapshot.values.values()).get(destinationIndex);
|
||||
return new ArrayList<>(snapshot.values.values()).get(0);
|
||||
}
|
||||
|
||||
private int applyOperation(int currentValue, Integer referenceValue) {
|
||||
return switch (this.operation) {
|
||||
case OP_ASSIGN -> (referenceValue != null) ? referenceValue : currentValue;
|
||||
case OP_ADD -> clamp((long) currentValue + referenceValue);
|
||||
case OP_SUB -> clamp((long) currentValue - referenceValue);
|
||||
case OP_MUL -> clamp((long) currentValue * referenceValue);
|
||||
case OP_DIV -> (referenceValue == null || referenceValue == 0) ? currentValue : (currentValue / referenceValue);
|
||||
case OP_POW -> (referenceValue == null || referenceValue < 0) ? 0 : clamp(Math.round(Math.pow(currentValue, referenceValue)));
|
||||
case OP_MOD -> (referenceValue == null || referenceValue == 0) ? currentValue : (currentValue % referenceValue);
|
||||
case OP_MIN -> (referenceValue != null) ? Math.min(currentValue, referenceValue) : currentValue;
|
||||
case OP_MAX -> (referenceValue != null) ? Math.max(currentValue, referenceValue) : currentValue;
|
||||
case OP_RANDOM -> (referenceValue == null || referenceValue <= 0) ? 0 : Emulator.getRandom().nextInt(referenceValue + 1);
|
||||
case OP_ABS -> (currentValue == Integer.MIN_VALUE) ? Integer.MAX_VALUE : Math.abs(currentValue);
|
||||
case OP_AND -> (referenceValue != null) ? (currentValue & referenceValue) : currentValue;
|
||||
case OP_OR -> (referenceValue != null) ? (currentValue | referenceValue) : currentValue;
|
||||
case OP_XOR -> (referenceValue != null) ? (currentValue ^ referenceValue) : currentValue;
|
||||
case OP_NOT -> ~currentValue;
|
||||
case OP_LSHIFT -> currentValue << shift(referenceValue);
|
||||
case OP_RSHIFT -> currentValue >> shift(referenceValue);
|
||||
default -> currentValue;
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isUnaryOperation() {
|
||||
return this.operation == OP_ABS || this.operation == OP_NOT;
|
||||
}
|
||||
|
||||
private void validateDestination(Room room, int targetType, String variableToken) throws WiredSaveException {
|
||||
if (variableToken == null || variableToken.isEmpty()) throw new WiredSaveException("wiredfurni.params.variables.validation.missing_variable");
|
||||
|
||||
boolean valid = switch (targetType) {
|
||||
case TARGET_USER -> isInternalVariableToken(variableToken)
|
||||
? canUseUserInternalDestination(getInternalVariableKey(variableToken))
|
||||
: this.isValidUserCustomDestination(room, getCustomItemId(variableToken));
|
||||
case TARGET_FURNI -> isInternalVariableToken(variableToken)
|
||||
? canUseFurniInternalDestination(getInternalVariableKey(variableToken))
|
||||
: this.isValidFurniCustomDestination(room, getCustomItemId(variableToken));
|
||||
case TARGET_CONTEXT -> !isInternalVariableToken(variableToken)
|
||||
&& this.isValidContextCustomDestination(room, getCustomItemId(variableToken));
|
||||
case TARGET_ROOM -> !isInternalVariableToken(variableToken) && this.isValidRoomCustomDestination(room, getCustomItemId(variableToken));
|
||||
default -> false;
|
||||
};
|
||||
|
||||
if (!valid) throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
|
||||
private void validateReference(Room room, int targetType, String variableToken) throws WiredSaveException {
|
||||
if (variableToken == null || variableToken.isEmpty()) throw new WiredSaveException("wiredfurni.params.variables.validation.missing_variable");
|
||||
|
||||
boolean valid = switch (targetType) {
|
||||
case TARGET_USER -> isInternalVariableToken(variableToken)
|
||||
? canUseUserInternalReference(getInternalVariableKey(variableToken))
|
||||
: this.isValidUserCustomReference(room, getCustomItemId(variableToken));
|
||||
case TARGET_FURNI -> isInternalVariableToken(variableToken)
|
||||
? canUseFurniInternalReference(getInternalVariableKey(variableToken))
|
||||
: this.isValidFurniCustomDestination(room, getCustomItemId(variableToken));
|
||||
case TARGET_CONTEXT -> isInternalVariableToken(variableToken)
|
||||
? canUseContextInternalReference(getInternalVariableKey(variableToken))
|
||||
: this.isValidContextCustomReference(room, getCustomItemId(variableToken));
|
||||
case TARGET_ROOM -> isInternalVariableToken(variableToken)
|
||||
? canUseRoomInternalReference(getInternalVariableKey(variableToken))
|
||||
: this.isValidRoomCustomReference(room, getCustomItemId(variableToken));
|
||||
default -> false;
|
||||
};
|
||||
|
||||
if (!valid) throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
|
||||
private boolean isValidUserCustomDestination(Room room, int variableItemId) {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getUserVariableManager().getDefinitionInfo(variableItemId) : null;
|
||||
return definition != null && definition.hasValue() && !definition.isReadOnly();
|
||||
}
|
||||
|
||||
private boolean isValidFurniCustomDestination(Room room, int variableItemId) {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getFurniVariableManager().getDefinitionInfo(variableItemId) : null;
|
||||
return definition != null && definition.hasValue() && !definition.isReadOnly();
|
||||
}
|
||||
|
||||
private boolean isValidRoomCustomDestination(Room room, int variableItemId) {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getRoomVariableManager().getDefinitionInfo(variableItemId) : null;
|
||||
return definition != null && definition.hasValue() && !definition.isReadOnly();
|
||||
}
|
||||
|
||||
private boolean isValidContextCustomDestination(Room room, int variableItemId) {
|
||||
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, variableItemId);
|
||||
return definition != null && definition.hasValue() && !definition.isReadOnly();
|
||||
}
|
||||
|
||||
private boolean isValidUserCustomReference(Room room, int variableItemId) {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getUserVariableManager().getDefinitionInfo(variableItemId) : null;
|
||||
return definition != null && definition.hasValue();
|
||||
}
|
||||
|
||||
private boolean isValidRoomCustomReference(Room room, int variableItemId) {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getRoomVariableManager().getDefinitionInfo(variableItemId) : null;
|
||||
return definition != null && definition.hasValue();
|
||||
}
|
||||
|
||||
private boolean isValidContextCustomReference(Room room, int variableItemId) {
|
||||
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, variableItemId);
|
||||
return definition != null && definition.hasValue();
|
||||
}
|
||||
|
||||
private boolean isValidFurniCustomReference(Room room, int variableItemId) {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getFurniVariableManager().getDefinitionInfo(variableItemId) : null;
|
||||
return definition != null && definition.hasValue();
|
||||
}
|
||||
|
||||
private Integer readUserInternalValue(Room room, RoomUnit roomUnit, String key) {
|
||||
return WiredInternalVariableSupport.readUserValue(room, roomUnit, key);
|
||||
}
|
||||
|
||||
private boolean writeUserInternalValue(Room room, RoomUnit roomUnit, String key, int value) {
|
||||
return WiredInternalVariableSupport.writeUserValue(room, roomUnit, key, value);
|
||||
}
|
||||
|
||||
private Integer readFurniInternalValue(Room room, HabboItem item, String key) {
|
||||
return WiredInternalVariableSupport.readFurniValue(room, item, key);
|
||||
}
|
||||
|
||||
private boolean writeFurniInternalValue(Room room, HabboItem item, String key, int value) {
|
||||
return WiredInternalVariableSupport.writeFurniValue(room, item, key, value);
|
||||
}
|
||||
|
||||
private Integer readRoomInternalValue(Room room, String key) {
|
||||
return WiredInternalVariableSupport.readRoomValue(room, key);
|
||||
}
|
||||
|
||||
private Integer getUserTeamScore(Room room, Habbo habbo) {
|
||||
if (room == null || habbo == null || habbo.getHabboInfo().getGamePlayer() == null) return null;
|
||||
|
||||
Game game = this.resolveTeamGame(room, habbo);
|
||||
GamePlayer gamePlayer = habbo.getHabboInfo().getGamePlayer();
|
||||
|
||||
if (game == null || gamePlayer.getTeamColor() == null) return gamePlayer.getScore();
|
||||
|
||||
GameTeam team = game.getTeam(gamePlayer.getTeamColor());
|
||||
return (team != null) ? team.getTotalScore() : gamePlayer.getScore();
|
||||
}
|
||||
|
||||
private Integer getTeamColorId(int effectId) {
|
||||
TeamEffectData data = this.getTeamEffectData(effectId);
|
||||
return data == null ? null : data.colorId;
|
||||
}
|
||||
|
||||
private Integer getTeamTypeId(int effectId) {
|
||||
TeamEffectData data = this.getTeamEffectData(effectId);
|
||||
return data == null ? null : data.typeId;
|
||||
}
|
||||
|
||||
private int getTeamMetric(Room room, GameTeamColors color, boolean score) {
|
||||
Game game = this.resolveTeamGame(room, null);
|
||||
if (game == null || color == null) return 0;
|
||||
|
||||
GameTeam team = game.getTeam(color);
|
||||
if (team == null) return 0;
|
||||
|
||||
return score ? team.getTotalScore() : team.getMembers().size();
|
||||
}
|
||||
|
||||
private Game resolveTeamGame(Room room, Habbo habbo) {
|
||||
if (room == null) return null;
|
||||
|
||||
if (habbo != null && habbo.getHabboInfo() != null && habbo.getHabboInfo().getCurrentGame() != null) {
|
||||
Game game = room.getGame(habbo.getHabboInfo().getCurrentGame());
|
||||
if (game != null) return game;
|
||||
}
|
||||
|
||||
Game wiredGame = room.getGame(WiredGame.class);
|
||||
if (wiredGame != null) return wiredGame;
|
||||
|
||||
Game freezeGame = room.getGame(FreezeGame.class);
|
||||
if (freezeGame != null) return freezeGame;
|
||||
|
||||
return room.getGame(BattleBanzaiGame.class);
|
||||
}
|
||||
|
||||
private TeamEffectData getTeamEffectData(int effectValue) {
|
||||
if (effectValue <= 0) return null;
|
||||
|
||||
if (effectValue >= 223 && effectValue <= 226) return new TeamEffectData(effectValue - 222, 0);
|
||||
if (effectValue >= 33 && effectValue <= 36) return new TeamEffectData(effectValue - 32, 1);
|
||||
if (effectValue >= 40 && effectValue <= 43) return new TeamEffectData(effectValue - 39, 2);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean moveUserTo(Room room, RoomUnit roomUnit, int x, int y) {
|
||||
if (room == null || roomUnit == null || room.getLayout() == null) return false;
|
||||
|
||||
RoomTile targetTile = room.getLayout().getTile((short) x, (short) y);
|
||||
if (targetTile == null || targetTile.state == RoomTileState.INVALID) return false;
|
||||
|
||||
double targetZ = targetTile.getStackHeight() + ((targetTile.state == RoomTileState.SIT) ? -0.5 : 0);
|
||||
return WiredUserMovementHelper.moveUser(room, roomUnit, targetTile, targetZ, roomUnit.getBodyRotation(), roomUnit.getHeadRotation(), 0, true);
|
||||
}
|
||||
|
||||
private boolean moveFurniTo(Room room, HabboItem item, int x, int y, int rotation, double z) {
|
||||
if (room == null || item == null || room.getLayout() == null) return false;
|
||||
|
||||
RoomTile targetTile = room.getLayout().getTile((short) x, (short) y);
|
||||
if (targetTile == null || targetTile.state == RoomTileState.INVALID) return false;
|
||||
|
||||
FurnitureMovementError error = room.moveFurniTo(item, targetTile, rotation, z, null, true, true);
|
||||
return error == FurnitureMovementError.NONE;
|
||||
}
|
||||
|
||||
private List<HabboItem> parseItems(int[] ids, Room room) throws WiredSaveException {
|
||||
List<HabboItem> items = new ArrayList<>();
|
||||
if (ids == null || room == null) return items;
|
||||
|
||||
for (int id : ids) {
|
||||
HabboItem item = room.getHabboItem(id);
|
||||
if (item == null) throw new WiredSaveException(String.format("Item %s not found", id));
|
||||
items.add(item);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private List<HabboItem> parseItems(List<Integer> ids, Room room) throws WiredSaveException {
|
||||
List<HabboItem> items = new ArrayList<>();
|
||||
if (ids == null || room == null) return items;
|
||||
|
||||
for (Integer id : ids) {
|
||||
if (id == null || id <= 0) continue;
|
||||
|
||||
HabboItem item = room.getHabboItem(id);
|
||||
if (item == null) throw new WiredSaveException(String.format("Item %s not found", id));
|
||||
items.add(item);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private List<HabboItem> parseItems(String ids, Room room) throws WiredSaveException {
|
||||
List<HabboItem> items = new ArrayList<>();
|
||||
if (ids == null || ids.trim().isEmpty() || room == null) return items;
|
||||
|
||||
for (String part : ids.split("[;,\\t]")) {
|
||||
int id = parseInteger(part);
|
||||
if (id <= 0) continue;
|
||||
|
||||
HabboItem item = room.getHabboItem(id);
|
||||
if (item == null) throw new WiredSaveException(String.format("Item %s not found", id));
|
||||
items.add(item);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private String serializeStringData() {
|
||||
return (this.destinationVariableToken == null ? "" : this.destinationVariableToken) + DELIM + (this.referenceVariableToken == null ? "" : this.referenceVariableToken) + DELIM + this.serializeIds(this.referenceSelectedFurni);
|
||||
}
|
||||
|
||||
private String[] parseStringData(String value) {
|
||||
return (value == null || value.isEmpty()) ? new String[0] : value.split("\\t", -1);
|
||||
}
|
||||
|
||||
private List<Integer> toIds(List<HabboItem> items) {
|
||||
List<Integer> ids = new ArrayList<>();
|
||||
for (HabboItem item : items) if (item != null) ids.add(item.getId());
|
||||
return ids;
|
||||
}
|
||||
|
||||
private String serializeIds(List<HabboItem> items) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (HabboItem item : items) {
|
||||
if (item == null) continue;
|
||||
if (builder.length() > 0) builder.append(FURNI_DELIM);
|
||||
builder.append(item.getId());
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void setDestinationVariableToken(String token) {
|
||||
this.destinationVariableToken = normalizeVariableToken(token);
|
||||
this.destinationVariableItemId = getCustomItemId(this.destinationVariableToken);
|
||||
}
|
||||
|
||||
private void setReferenceVariableToken(String token) {
|
||||
this.referenceVariableToken = normalizeVariableToken(token);
|
||||
this.referenceVariableItemId = getCustomItemId(this.referenceVariableToken);
|
||||
}
|
||||
|
||||
private static boolean isCustomVariableToken(String token) {
|
||||
return token != null && token.startsWith(CUSTOM_TOKEN_PREFIX);
|
||||
}
|
||||
|
||||
private static boolean isInternalVariableToken(String token) {
|
||||
return token != null && token.startsWith(INTERNAL_TOKEN_PREFIX);
|
||||
}
|
||||
|
||||
private static int getCustomItemId(String token) {
|
||||
if (!isCustomVariableToken(token)) return 0;
|
||||
return parseInteger(token.substring(CUSTOM_TOKEN_PREFIX.length()));
|
||||
}
|
||||
|
||||
private static String getInternalVariableKey(String token) {
|
||||
return isInternalVariableToken(token) ? WiredInternalVariableSupport.normalizeKey(token.substring(INTERNAL_TOKEN_PREFIX.length())) : "";
|
||||
}
|
||||
|
||||
private static String normalizeVariableToken(String token) {
|
||||
if (token == null) return "";
|
||||
|
||||
String normalized = token.trim();
|
||||
if (normalized.isEmpty()) return "";
|
||||
if (isCustomVariableToken(normalized)) return normalized;
|
||||
if (isInternalVariableToken(normalized)) return INTERNAL_TOKEN_PREFIX + WiredInternalVariableSupport.normalizeKey(normalized.substring(INTERNAL_TOKEN_PREFIX.length()));
|
||||
|
||||
int parsedValue = parseInteger(normalized);
|
||||
return parsedValue > 0 ? CUSTOM_TOKEN_PREFIX + parsedValue : "";
|
||||
}
|
||||
|
||||
private static boolean canUseUserInternalDestination(String key) {
|
||||
return WiredInternalVariableSupport.canUseUserDestination(key);
|
||||
}
|
||||
|
||||
private static boolean canUseFurniInternalDestination(String key) {
|
||||
return WiredInternalVariableSupport.canUseFurniDestination(key);
|
||||
}
|
||||
|
||||
private static boolean canUseUserInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseUserReference(key);
|
||||
}
|
||||
|
||||
private static boolean canUseFurniInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseFurniReference(key);
|
||||
}
|
||||
|
||||
private static boolean canUseRoomInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseRoomReference(key);
|
||||
}
|
||||
|
||||
private static boolean canUseContextInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseContextReference(key);
|
||||
}
|
||||
|
||||
private static int param(int[] params, int index, int fallback) {
|
||||
return (params.length > index) ? params[index] : fallback;
|
||||
}
|
||||
|
||||
private static int normalizeTargetType(int value) {
|
||||
return switch (value) {
|
||||
case TARGET_FURNI, TARGET_CONTEXT, TARGET_ROOM -> value;
|
||||
default -> TARGET_USER;
|
||||
};
|
||||
}
|
||||
|
||||
private static int normalizeReferenceMode(int value) {
|
||||
return (value == REF_VARIABLE) ? REF_VARIABLE : REF_CONSTANT;
|
||||
}
|
||||
|
||||
private static int normalizeUserSource(int value) {
|
||||
return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
|
||||
}
|
||||
|
||||
private static int normalizeDestinationFurniSource(int value) {
|
||||
return switch (value) {
|
||||
case WiredSourceUtil.SOURCE_SELECTED, WiredSourceUtil.SOURCE_SELECTOR, WiredSourceUtil.SOURCE_SIGNAL -> value;
|
||||
default -> WiredSourceUtil.SOURCE_TRIGGER;
|
||||
};
|
||||
}
|
||||
|
||||
private static int normalizeReferenceFurniSource(int value) {
|
||||
return switch (value) {
|
||||
case SOURCE_SECONDARY_SELECTED, WiredSourceUtil.SOURCE_SELECTOR, WiredSourceUtil.SOURCE_SIGNAL -> value;
|
||||
default -> WiredSourceUtil.SOURCE_TRIGGER;
|
||||
};
|
||||
}
|
||||
|
||||
private static int normalizeOperation(int value) {
|
||||
return switch (value) {
|
||||
case OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_POW, OP_MOD, OP_MIN, OP_MAX, OP_RANDOM, OP_ABS, OP_AND, OP_OR, OP_XOR, OP_NOT, OP_LSHIFT, OP_RSHIFT -> value;
|
||||
default -> OP_ASSIGN;
|
||||
};
|
||||
}
|
||||
|
||||
private static int parseInteger(String value) {
|
||||
try {
|
||||
return (value == null || value.trim().isEmpty()) ? 0 : Integer.parseInt(value.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static int shift(Integer value) {
|
||||
return (value == null) ? 0 : Math.max(0, Math.min(31, value));
|
||||
}
|
||||
|
||||
private static int clamp(long value) {
|
||||
return (value > Integer.MAX_VALUE) ? Integer.MAX_VALUE : ((value < Integer.MIN_VALUE) ? Integer.MIN_VALUE : (int) value);
|
||||
}
|
||||
|
||||
static class JsonData {
|
||||
int destinationTargetType, destinationVariableItemId, operation, referenceMode, referenceConstantValue, referenceTargetType, referenceVariableItemId, destinationUserSource, destinationFurniSource, referenceUserSource, referenceFurniSource, delay;
|
||||
String destinationVariableToken, referenceVariableToken;
|
||||
List<Integer> destinationSelectedFurniIds, referenceSelectedFurniIds;
|
||||
|
||||
JsonData(int destinationTargetType, String destinationVariableToken, int destinationVariableItemId, int operation, int referenceMode, int referenceConstantValue, int referenceTargetType, String referenceVariableToken, int referenceVariableItemId, int destinationUserSource, int destinationFurniSource, int referenceUserSource, int referenceFurniSource, int delay, List<Integer> destinationSelectedFurniIds, List<Integer> referenceSelectedFurniIds) {
|
||||
this.destinationTargetType = destinationTargetType;
|
||||
this.destinationVariableToken = destinationVariableToken;
|
||||
this.destinationVariableItemId = destinationVariableItemId;
|
||||
this.operation = operation;
|
||||
this.referenceMode = referenceMode;
|
||||
this.referenceConstantValue = referenceConstantValue;
|
||||
this.referenceTargetType = referenceTargetType;
|
||||
this.referenceVariableToken = referenceVariableToken;
|
||||
this.referenceVariableItemId = referenceVariableItemId;
|
||||
this.destinationUserSource = destinationUserSource;
|
||||
this.destinationFurniSource = destinationFurniSource;
|
||||
this.referenceUserSource = referenceUserSource;
|
||||
this.referenceFurniSource = referenceFurniSource;
|
||||
this.delay = delay;
|
||||
this.destinationSelectedFurniIds = destinationSelectedFurniIds;
|
||||
this.referenceSelectedFurniIds = referenceSelectedFurniIds;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReferenceSnapshot {
|
||||
final int targetType;
|
||||
final LinkedHashMap<Integer, Integer> values = new LinkedHashMap<>();
|
||||
|
||||
ReferenceSnapshot(int targetType) {
|
||||
this.targetType = targetType;
|
||||
}
|
||||
|
||||
void add(int entityId, int value) {
|
||||
this.values.put(entityId, value);
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return this.values.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TeamEffectData {
|
||||
final int colorId;
|
||||
final int typeId;
|
||||
|
||||
TeamEffectData(int colorId, int typeId) {
|
||||
this.colorId = colorId;
|
||||
this.typeId = typeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
+434
@@ -0,0 +1,434 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.effects;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
|
||||
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.habbohotel.wired.core.WiredContext;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
|
||||
import gnu.trove.set.hash.THashSet;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class WiredEffectGiveVariable extends InteractionWiredEffect {
|
||||
public static final WiredEffectType type = WiredEffectType.GIVE_VAR;
|
||||
|
||||
public static final int TARGET_USER = 0;
|
||||
public static final int TARGET_FURNI = 1;
|
||||
public static final int TARGET_CONTEXT = 2;
|
||||
|
||||
private int variableItemId = 0;
|
||||
private int targetType = TARGET_USER;
|
||||
private boolean overrideExisting = false;
|
||||
private int initialValue = 0;
|
||||
private int userSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
private int furniSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
private final THashSet<HabboItem> selectedFurni;
|
||||
|
||||
public WiredEffectGiveVariable(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
this.selectedFurni = new THashSet<>();
|
||||
}
|
||||
|
||||
public WiredEffectGiveVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
this.selectedFurni = new THashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WiredContext ctx) {
|
||||
Room room = ctx.room();
|
||||
|
||||
if (room == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this.targetType) {
|
||||
case TARGET_USER:
|
||||
this.executeUserVariables(ctx, room);
|
||||
return;
|
||||
case TARGET_FURNI:
|
||||
this.executeFurniVariables(ctx, room);
|
||||
return;
|
||||
case TARGET_CONTEXT:
|
||||
this.executeContextVariables(ctx, room);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void executeContextVariables(WiredContext ctx, Room room) {
|
||||
WiredVariableDefinitionInfo definitionInfo = WiredContextVariableSupport.getDefinitionInfo(room, this.variableItemId);
|
||||
if (definitionInfo == null || definitionInfo.isReadOnly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Integer value = definitionInfo.hasValue() ? this.initialValue : null;
|
||||
WiredContextVariableSupport.assignVariable(ctx, room, this.variableItemId, value, this.overrideExisting);
|
||||
}
|
||||
|
||||
private void executeUserVariables(WiredContext ctx, Room room) {
|
||||
WiredVariableDefinitionInfo definitionInfo = room.getUserVariableManager().getDefinitionInfo(this.variableItemId);
|
||||
|
||||
if (definitionInfo == null || definitionInfo.isReadOnly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<RoomUnit> users = WiredSourceUtil.resolveUsers(ctx, this.userSource);
|
||||
|
||||
if (users.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Integer value = definitionInfo.hasValue() ? this.initialValue : null;
|
||||
|
||||
for (RoomUnit roomUnit : users) {
|
||||
if (roomUnit == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Habbo habbo = room.getHabbo(roomUnit);
|
||||
|
||||
if (habbo == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
room.getUserVariableManager().assignVariable(habbo, this.variableItemId, value, this.overrideExisting);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeFurniVariables(WiredContext ctx, Room room) {
|
||||
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.variableItemId);
|
||||
|
||||
if (definition == null || definition.isReadOnly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
this.validateItems(this.selectedFurni);
|
||||
}
|
||||
|
||||
List<HabboItem> furni = WiredSourceUtil.resolveItems(ctx, this.furniSource, this.selectedFurni);
|
||||
|
||||
if (furni.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Integer value = definition.hasValue() ? this.initialValue : null;
|
||||
|
||||
for (HabboItem item : furni) {
|
||||
if (item == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
room.getFurniVariableManager().assignVariable(item, this.variableItemId, value, this.overrideExisting);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
List<HabboItem> selectedItems = new ArrayList<>();
|
||||
|
||||
if (this.targetType == TARGET_FURNI && this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
for (HabboItem item : this.selectedFurni) {
|
||||
if (item != null && room != null && room.getHabboItem(item.getId()) != null) {
|
||||
selectedItems.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
|
||||
message.appendInt(selectedItems.size());
|
||||
|
||||
for (HabboItem item : selectedItems) {
|
||||
message.appendInt(item.getId());
|
||||
}
|
||||
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(String.valueOf(this.variableItemId));
|
||||
message.appendInt(5);
|
||||
message.appendInt(this.targetType);
|
||||
message.appendInt(this.overrideExisting ? 1 : 0);
|
||||
message.appendInt(this.initialValue);
|
||||
message.appendInt(this.userSource);
|
||||
message.appendInt(this.furniSource);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getType().code);
|
||||
message.appendInt(this.getDelay());
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
|
||||
if (room == null) {
|
||||
throw new WiredSaveException("Room not found");
|
||||
}
|
||||
|
||||
int[] intParams = settings.getIntParams();
|
||||
int nextTargetType = normalizeTargetType((intParams.length > 0) ? intParams[0] : TARGET_USER);
|
||||
boolean nextOverrideExisting = (intParams.length > 1) && (intParams[1] == 1);
|
||||
int nextInitialValue = (intParams.length > 2) ? intParams[2] : 0;
|
||||
int nextUserSource = normalizeUserSource((intParams.length > 3) ? intParams[3] : WiredSourceUtil.SOURCE_TRIGGER);
|
||||
int nextFurniSource = normalizeFurniSource((intParams.length > 4) ? intParams[4] : WiredSourceUtil.SOURCE_TRIGGER);
|
||||
int nextVariableItemId = parseVariableItemId(settings.getStringParam());
|
||||
|
||||
if (nextVariableItemId <= 0 && settings.getFurniIds() != null && settings.getFurniIds().length > 0) {
|
||||
int legacyItemId = settings.getFurniIds()[0];
|
||||
|
||||
if (room.getUserVariableManager().hasDefinition(legacyItemId)) {
|
||||
nextVariableItemId = legacyItemId;
|
||||
nextTargetType = TARGET_USER;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextVariableItemId <= 0) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.missing_variable");
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo userDefinitionInfo = (nextTargetType == TARGET_USER) ? room.getUserVariableManager().getDefinitionInfo(nextVariableItemId) : null;
|
||||
|
||||
if (nextTargetType == TARGET_USER && (userDefinitionInfo == null || userDefinitionInfo.isReadOnly())) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
|
||||
if (nextTargetType == TARGET_FURNI) {
|
||||
WiredVariableDefinitionInfo furniDefinitionInfo = room.getFurniVariableManager().getDefinitionInfo(nextVariableItemId);
|
||||
|
||||
if (furniDefinitionInfo == null || furniDefinitionInfo.isReadOnly()) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
}
|
||||
|
||||
if (nextTargetType == TARGET_CONTEXT) {
|
||||
WiredVariableDefinitionInfo contextDefinitionInfo = WiredContextVariableSupport.getDefinitionInfo(room, nextVariableItemId);
|
||||
|
||||
if (contextDefinitionInfo == null || contextDefinitionInfo.isReadOnly()) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
}
|
||||
|
||||
this.selectedFurni.clear();
|
||||
|
||||
if (nextTargetType == TARGET_FURNI && nextFurniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
int[] furniIds = settings.getFurniIds();
|
||||
int itemsCount = (furniIds != null) ? furniIds.length : 0;
|
||||
|
||||
if (itemsCount > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) {
|
||||
throw new WiredSaveException("Too many furni selected");
|
||||
}
|
||||
|
||||
for (int i = 0; i < itemsCount; i++) {
|
||||
int itemId = furniIds[i];
|
||||
HabboItem item = room.getHabboItem(itemId);
|
||||
|
||||
if (item == null) {
|
||||
throw new WiredSaveException(String.format("Item %s not found", itemId));
|
||||
}
|
||||
|
||||
this.selectedFurni.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
this.variableItemId = nextVariableItemId;
|
||||
this.targetType = nextTargetType;
|
||||
this.overrideExisting = nextOverrideExisting;
|
||||
this.initialValue = nextInitialValue;
|
||||
this.userSource = nextUserSource;
|
||||
this.furniSource = nextFurniSource;
|
||||
this.setDelay(settings.getDelay());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
List<Integer> selectedItemIds = new ArrayList<>();
|
||||
|
||||
for (HabboItem item : this.selectedFurni) {
|
||||
if (item != null) {
|
||||
selectedItemIds.add(item.getId());
|
||||
}
|
||||
}
|
||||
|
||||
return WiredManager.getGson().toJson(new JsonData(this.variableItemId, this.targetType, this.overrideExisting, this.initialValue, this.userSource, this.furniSource, this.getDelay(), selectedItemIds));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wiredData.startsWith("{")) {
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
|
||||
if (data != null) {
|
||||
this.variableItemId = Math.max(0, data.variableItemId);
|
||||
this.targetType = normalizeTargetType(data.targetType);
|
||||
this.overrideExisting = data.overrideExisting;
|
||||
this.initialValue = data.initialValue;
|
||||
this.userSource = normalizeUserSource(data.userSource);
|
||||
this.furniSource = normalizeFurniSource(data.furniSource);
|
||||
this.setDelay(Math.max(0, data.delay));
|
||||
|
||||
if (room != null && data.selectedFurniIds != null) {
|
||||
for (Integer itemId : data.selectedFurniIds) {
|
||||
if (itemId == null || itemId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
HabboItem item = room.getHabboItem(itemId);
|
||||
|
||||
if (item != null) {
|
||||
this.selectedFurni.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.variableItemId = Math.max(0, Integer.parseInt(wiredData.trim()));
|
||||
this.targetType = TARGET_USER;
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
this.variableItemId = 0;
|
||||
this.targetType = TARGET_USER;
|
||||
this.overrideExisting = false;
|
||||
this.initialValue = 0;
|
||||
this.userSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.furniSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.selectedFurni.clear();
|
||||
this.setDelay(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public WiredEffectType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public int getVariableItemId() {
|
||||
return this.variableItemId;
|
||||
}
|
||||
|
||||
public int getTargetType() {
|
||||
return this.targetType;
|
||||
}
|
||||
|
||||
public boolean isOverrideExisting() {
|
||||
return this.overrideExisting;
|
||||
}
|
||||
|
||||
public int getInitialValue() {
|
||||
return this.initialValue;
|
||||
}
|
||||
|
||||
public int getUserSource() {
|
||||
return this.userSource;
|
||||
}
|
||||
|
||||
public int getFurniSource() {
|
||||
return this.furniSource;
|
||||
}
|
||||
|
||||
public THashSet<HabboItem> getSelectedFurni() {
|
||||
return this.selectedFurni;
|
||||
}
|
||||
|
||||
private static int normalizeTargetType(int value) {
|
||||
switch (value) {
|
||||
case TARGET_FURNI:
|
||||
case TARGET_CONTEXT:
|
||||
return value;
|
||||
default:
|
||||
return TARGET_USER;
|
||||
}
|
||||
}
|
||||
|
||||
private static int normalizeUserSource(int value) {
|
||||
return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
|
||||
}
|
||||
|
||||
private static int normalizeFurniSource(int value) {
|
||||
switch (value) {
|
||||
case WiredSourceUtil.SOURCE_SELECTED:
|
||||
case WiredSourceUtil.SOURCE_SELECTOR:
|
||||
case WiredSourceUtil.SOURCE_SIGNAL:
|
||||
return value;
|
||||
default:
|
||||
return WiredSourceUtil.SOURCE_TRIGGER;
|
||||
}
|
||||
}
|
||||
|
||||
private static int parseVariableItemId(String value) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
return Math.max(0, Integer.parseInt(value.trim()));
|
||||
} catch (NumberFormatException ignored) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static class JsonData {
|
||||
int variableItemId;
|
||||
int targetType;
|
||||
boolean overrideExisting;
|
||||
int initialValue;
|
||||
int userSource;
|
||||
int furniSource;
|
||||
int delay;
|
||||
List<Integer> selectedFurniIds;
|
||||
|
||||
JsonData(int variableItemId, int targetType, boolean overrideExisting, int initialValue, int userSource, int furniSource, int delay, List<Integer> selectedFurniIds) {
|
||||
this.variableItemId = variableItemId;
|
||||
this.targetType = targetType;
|
||||
this.overrideExisting = overrideExisting;
|
||||
this.initialValue = initialValue;
|
||||
this.userSource = userSource;
|
||||
this.furniSource = furniSource;
|
||||
this.delay = delay;
|
||||
this.selectedFurniIds = selectedFurniIds;
|
||||
}
|
||||
}
|
||||
}
|
||||
+370
@@ -0,0 +1,370 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.effects;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
|
||||
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.habbohotel.wired.core.WiredContext;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
|
||||
import gnu.trove.set.hash.THashSet;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class WiredEffectRemoveVariable extends InteractionWiredEffect {
|
||||
public static final WiredEffectType type = WiredEffectType.REMOVE_VAR;
|
||||
|
||||
public static final int TARGET_USER = 0;
|
||||
public static final int TARGET_FURNI = 1;
|
||||
public static final int TARGET_CONTEXT = 2;
|
||||
|
||||
private int variableItemId = 0;
|
||||
private int targetType = TARGET_USER;
|
||||
private int userSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
private int furniSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
private final THashSet<HabboItem> selectedFurni;
|
||||
|
||||
public WiredEffectRemoveVariable(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
this.selectedFurni = new THashSet<>();
|
||||
}
|
||||
|
||||
public WiredEffectRemoveVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
this.selectedFurni = new THashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(WiredContext ctx) {
|
||||
Room room = ctx.room();
|
||||
|
||||
if (room == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this.targetType) {
|
||||
case TARGET_USER:
|
||||
this.executeUserVariables(ctx, room);
|
||||
return;
|
||||
case TARGET_FURNI:
|
||||
this.executeFurniVariables(ctx, room);
|
||||
return;
|
||||
case TARGET_CONTEXT:
|
||||
this.executeContextVariables(ctx, room);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void executeUserVariables(WiredContext ctx, Room room) {
|
||||
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.variableItemId);
|
||||
|
||||
if (definition == null || definition.isReadOnly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<RoomUnit> users = WiredSourceUtil.resolveUsers(ctx, this.userSource);
|
||||
|
||||
for (RoomUnit roomUnit : users) {
|
||||
if (roomUnit == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Habbo habbo = room.getHabbo(roomUnit);
|
||||
|
||||
if (habbo == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
room.getUserVariableManager().removeVariable(habbo.getHabboInfo().getId(), this.variableItemId);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeFurniVariables(WiredContext ctx, Room room) {
|
||||
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.variableItemId);
|
||||
|
||||
if (definition == null || definition.isReadOnly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
this.validateItems(this.selectedFurni);
|
||||
}
|
||||
|
||||
List<HabboItem> furni = WiredSourceUtil.resolveItems(ctx, this.furniSource, this.selectedFurni);
|
||||
|
||||
for (HabboItem item : furni) {
|
||||
if (item == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
room.getFurniVariableManager().removeVariable(item.getId(), this.variableItemId);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeContextVariables(WiredContext ctx, Room room) {
|
||||
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, this.variableItemId);
|
||||
|
||||
if (definition == null || definition.isReadOnly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
WiredContextVariableSupport.removeVariable(ctx, room, this.variableItemId);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
List<HabboItem> selectedItems = new ArrayList<>();
|
||||
|
||||
if (this.targetType == TARGET_FURNI && this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
for (HabboItem item : this.selectedFurni) {
|
||||
if (item != null && room != null && room.getHabboItem(item.getId()) != null) {
|
||||
selectedItems.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
|
||||
message.appendInt(selectedItems.size());
|
||||
|
||||
for (HabboItem item : selectedItems) {
|
||||
message.appendInt(item.getId());
|
||||
}
|
||||
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(String.valueOf(this.variableItemId));
|
||||
message.appendInt(3);
|
||||
message.appendInt(this.targetType);
|
||||
message.appendInt(this.userSource);
|
||||
message.appendInt(this.furniSource);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getType().code);
|
||||
message.appendInt(this.getDelay());
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
|
||||
if (room == null) {
|
||||
throw new WiredSaveException("Room not found");
|
||||
}
|
||||
|
||||
int[] intParams = settings.getIntParams();
|
||||
int nextTargetType = normalizeTargetType((intParams.length > 0) ? intParams[0] : TARGET_USER);
|
||||
int nextUserSource = normalizeUserSource((intParams.length > 1) ? intParams[1] : WiredSourceUtil.SOURCE_TRIGGER);
|
||||
int nextFurniSource = normalizeFurniSource((intParams.length > 2) ? intParams[2] : WiredSourceUtil.SOURCE_TRIGGER);
|
||||
int nextVariableItemId = parseVariableItemId(settings.getStringParam());
|
||||
|
||||
if (nextVariableItemId <= 0) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.missing_variable");
|
||||
}
|
||||
|
||||
switch (nextTargetType) {
|
||||
case TARGET_USER:
|
||||
WiredVariableDefinitionInfo userDefinition = room.getUserVariableManager().getDefinitionInfo(nextVariableItemId);
|
||||
if (userDefinition == null || userDefinition.isReadOnly()) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
break;
|
||||
case TARGET_FURNI:
|
||||
WiredVariableDefinitionInfo furniDefinition = room.getFurniVariableManager().getDefinitionInfo(nextVariableItemId);
|
||||
if (furniDefinition == null || furniDefinition.isReadOnly()) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
break;
|
||||
case TARGET_CONTEXT:
|
||||
WiredVariableDefinitionInfo contextDefinition = WiredContextVariableSupport.getDefinitionInfo(room, nextVariableItemId);
|
||||
if (contextDefinition == null || contextDefinition.isReadOnly()) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
|
||||
this.selectedFurni.clear();
|
||||
|
||||
if (nextTargetType == TARGET_FURNI && nextFurniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
int[] furniIds = settings.getFurniIds();
|
||||
int itemsCount = (furniIds != null) ? furniIds.length : 0;
|
||||
|
||||
if (itemsCount > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) {
|
||||
throw new WiredSaveException("Too many furni selected");
|
||||
}
|
||||
|
||||
for (int i = 0; i < itemsCount; i++) {
|
||||
int itemId = furniIds[i];
|
||||
HabboItem item = room.getHabboItem(itemId);
|
||||
|
||||
if (item == null) {
|
||||
throw new WiredSaveException(String.format("Item %s not found", itemId));
|
||||
}
|
||||
|
||||
this.selectedFurni.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
this.variableItemId = nextVariableItemId;
|
||||
this.targetType = nextTargetType;
|
||||
this.userSource = nextUserSource;
|
||||
this.furniSource = nextFurniSource;
|
||||
this.setDelay(settings.getDelay());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
List<Integer> selectedItemIds = new ArrayList<>();
|
||||
|
||||
for (HabboItem item : this.selectedFurni) {
|
||||
if (item != null) {
|
||||
selectedItemIds.add(item.getId());
|
||||
}
|
||||
}
|
||||
|
||||
return WiredManager.getGson().toJson(new JsonData(this.variableItemId, this.targetType, this.userSource, this.furniSource, this.getDelay(), selectedItemIds));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wiredData.startsWith("{")) {
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
|
||||
if (data != null) {
|
||||
this.variableItemId = Math.max(0, data.variableItemId);
|
||||
this.targetType = normalizeTargetType(data.targetType);
|
||||
this.userSource = normalizeUserSource(data.userSource);
|
||||
this.furniSource = normalizeFurniSource(data.furniSource);
|
||||
this.setDelay(Math.max(0, data.delay));
|
||||
|
||||
if (room != null && data.selectedFurniIds != null) {
|
||||
for (Integer itemId : data.selectedFurniIds) {
|
||||
if (itemId == null || itemId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
HabboItem item = room.getHabboItem(itemId);
|
||||
|
||||
if (item != null) {
|
||||
this.selectedFurni.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.variableItemId = Math.max(0, Integer.parseInt(wiredData.trim()));
|
||||
this.targetType = TARGET_USER;
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
this.variableItemId = 0;
|
||||
this.targetType = TARGET_USER;
|
||||
this.userSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.furniSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.selectedFurni.clear();
|
||||
this.setDelay(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public WiredEffectType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
private static int normalizeTargetType(int value) {
|
||||
switch (value) {
|
||||
case TARGET_FURNI:
|
||||
case TARGET_CONTEXT:
|
||||
return value;
|
||||
default:
|
||||
return TARGET_USER;
|
||||
}
|
||||
}
|
||||
|
||||
private static int normalizeUserSource(int value) {
|
||||
return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
|
||||
}
|
||||
|
||||
private static int normalizeFurniSource(int value) {
|
||||
switch (value) {
|
||||
case WiredSourceUtil.SOURCE_SELECTED:
|
||||
case WiredSourceUtil.SOURCE_SELECTOR:
|
||||
case WiredSourceUtil.SOURCE_SIGNAL:
|
||||
return value;
|
||||
default:
|
||||
return WiredSourceUtil.SOURCE_TRIGGER;
|
||||
}
|
||||
}
|
||||
|
||||
private static int parseVariableItemId(String value) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
return Math.max(0, Integer.parseInt(value.trim()));
|
||||
} catch (NumberFormatException ignored) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static class JsonData {
|
||||
int variableItemId;
|
||||
int targetType;
|
||||
int userSource;
|
||||
int furniSource;
|
||||
int delay;
|
||||
List<Integer> selectedFurniIds;
|
||||
|
||||
JsonData(int variableItemId, int targetType, int userSource, int furniSource, int delay, List<Integer> selectedFurniIds) {
|
||||
this.variableItemId = variableItemId;
|
||||
this.targetType = targetType;
|
||||
this.userSource = userSource;
|
||||
this.furniSource = furniSource;
|
||||
this.delay = delay;
|
||||
this.selectedFurniIds = selectedFurniIds;
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-2
@@ -122,13 +122,13 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
|
||||
for (RoomUnit user : usersToSend) {
|
||||
for (HabboItem sourceItem : furniToSend) {
|
||||
for (HabboItem antenna : resolvedAntennas) {
|
||||
fireSignalAtAntenna(room, antenna, user, sourceItem, nextDepth);
|
||||
fireSignalAtAntenna(ctx, room, antenna, user, sourceItem, nextDepth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fireSignalAtAntenna(Room room, HabboItem antenna, RoomUnit actor, HabboItem sourceItem, int depth) {
|
||||
private void fireSignalAtAntenna(WiredContext ctx, Room room, HabboItem antenna, RoomUnit actor, HabboItem sourceItem, int depth) {
|
||||
if (antenna == null) return;
|
||||
RoomTile tile = room.getLayout().getTile(antenna.getX(), antenna.getY());
|
||||
if (tile == null) return;
|
||||
@@ -144,6 +144,9 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
|
||||
.tile(tile)
|
||||
.callStackDepth(depth)
|
||||
.signalChannel(signalChannel)
|
||||
.signalUserCount(actor != null ? 1 : 0)
|
||||
.signalFurniCount(sourceItem != null ? 1 : 0)
|
||||
.contextVariableScope(ctx.contextVariables())
|
||||
.triggeredByEffect(true);
|
||||
|
||||
if (actor != null) builder.actor(actor);
|
||||
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
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.habbohotel.wired.core.WiredContextVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class WiredExtraContextVariable extends InteractionWiredExtra {
|
||||
public static final int CODE = 84;
|
||||
|
||||
private String variableName = "";
|
||||
private boolean hasValue = false;
|
||||
|
||||
public WiredExtraContextVariable(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredExtraContextVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
if (room == null) {
|
||||
throw new WiredSaveException("Room not found");
|
||||
}
|
||||
|
||||
int[] intParams = settings.getIntParams();
|
||||
String normalizedName = WiredVariableNameValidator.normalizeForSave(settings.getStringParam());
|
||||
|
||||
WiredVariableNameValidator.validateDefinitionName(room, this.getId(), normalizedName);
|
||||
|
||||
this.variableName = normalizedName;
|
||||
this.hasValue = (intParams.length > 0) && (intParams[0] == 1);
|
||||
|
||||
WiredContextVariableSupport.broadcastDefinitions(room);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
return WiredManager.getGson().toJson(new JsonData(this.variableName, this.hasValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(this.variableName);
|
||||
message.appendInt(1);
|
||||
message.appendInt(this.hasValue ? 1 : 0);
|
||||
message.appendInt(0);
|
||||
message.appendInt(CODE);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wiredData.startsWith("{")) {
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
|
||||
if (data != null) {
|
||||
this.variableName = WiredVariableNameValidator.normalizeLegacy(data.variableName);
|
||||
this.hasValue = data.hasValue;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.variableName = WiredVariableNameValidator.normalizeLegacy(wiredData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
this.variableName = "";
|
||||
this.hasValue = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConfiguration() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getVariableName() {
|
||||
return this.variableName;
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return this.hasValue;
|
||||
}
|
||||
|
||||
static class JsonData {
|
||||
String variableName;
|
||||
boolean hasValue;
|
||||
|
||||
JsonData(String variableName, boolean hasValue) {
|
||||
this.variableName = variableName;
|
||||
this.hasValue = hasValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
|
||||
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class WiredExtraFilterFurniByVariable extends WiredExtraVariableFilterBase {
|
||||
public static final int CODE = 78;
|
||||
|
||||
public WiredExtraFilterFurniByVariable(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredExtraFilterFurniByVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getVariableTargetType() {
|
||||
return TARGET_FURNI;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getCode() {
|
||||
return CODE;
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
|
||||
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class WiredExtraFilterUsersByVariable extends WiredExtraVariableFilterBase {
|
||||
public static final int CODE = 77;
|
||||
|
||||
public WiredExtraFilterUsersByVariable(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredExtraFilterUsersByVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getVariableTargetType() {
|
||||
return TARGET_USER;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getCode() {
|
||||
return CODE;
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
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.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class WiredExtraFurniVariable extends InteractionWiredExtra {
|
||||
public static final int CODE = 71;
|
||||
public static final int AVAILABILITY_ROOM_ACTIVE = 1;
|
||||
public static final int AVAILABILITY_PERMANENT = 10;
|
||||
|
||||
private String variableName = "";
|
||||
private boolean hasValue = false;
|
||||
private int availability = AVAILABILITY_ROOM_ACTIVE;
|
||||
|
||||
public WiredExtraFurniVariable(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredExtraFurniVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
|
||||
int[] intParams = settings.getIntParams();
|
||||
String normalizedName = WiredVariableNameValidator.normalizeForSave(settings.getStringParam());
|
||||
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
|
||||
if (room == null) {
|
||||
throw new WiredSaveException("Room not found");
|
||||
}
|
||||
|
||||
WiredVariableNameValidator.validateDefinitionName(room, this.getId(), normalizedName);
|
||||
|
||||
this.variableName = normalizedName;
|
||||
this.hasValue = (intParams.length > 0) && (intParams[0] == 1);
|
||||
this.availability = normalizeAvailability((intParams.length > 1) ? intParams[1] : AVAILABILITY_ROOM_ACTIVE);
|
||||
|
||||
room.getFurniVariableManager().handleDefinitionUpdated(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
return WiredManager.getGson().toJson(new JsonData(this.variableName, this.hasValue, this.availability));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(this.variableName);
|
||||
message.appendInt(2);
|
||||
message.appendInt(this.hasValue ? 1 : 0);
|
||||
message.appendInt(this.availability);
|
||||
message.appendInt(0);
|
||||
message.appendInt(CODE);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wiredData.startsWith("{")) {
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
|
||||
if (data != null) {
|
||||
this.variableName = WiredVariableNameValidator.normalizeLegacy(data.variableName);
|
||||
this.hasValue = data.hasValue;
|
||||
this.availability = normalizeAvailability(data.availability);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.variableName = WiredVariableNameValidator.normalizeLegacy(wiredData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
this.variableName = "";
|
||||
this.hasValue = false;
|
||||
this.availability = AVAILABILITY_ROOM_ACTIVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConfiguration() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getVariableName() {
|
||||
return this.variableName;
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return this.hasValue;
|
||||
}
|
||||
|
||||
public int getAvailability() {
|
||||
return this.availability;
|
||||
}
|
||||
|
||||
public boolean isPermanentAvailability() {
|
||||
return this.availability == AVAILABILITY_PERMANENT;
|
||||
}
|
||||
|
||||
private static int normalizeAvailability(int value) {
|
||||
if (value == AVAILABILITY_PERMANENT) {
|
||||
return AVAILABILITY_PERMANENT;
|
||||
}
|
||||
|
||||
return AVAILABILITY_ROOM_ACTIVE;
|
||||
}
|
||||
|
||||
static class JsonData {
|
||||
String variableName;
|
||||
boolean hasValue;
|
||||
int availability;
|
||||
|
||||
JsonData(String variableName, boolean hasValue, int availability) {
|
||||
this.variableName = variableName;
|
||||
this.hasValue = hasValue;
|
||||
this.availability = availability;
|
||||
}
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
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.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class WiredExtraRoomVariable extends InteractionWiredExtra {
|
||||
public static final int CODE = 72;
|
||||
public static final int AVAILABILITY_ROOM_ACTIVE = 1;
|
||||
public static final int AVAILABILITY_PERMANENT = 10;
|
||||
public static final int AVAILABILITY_SHARED = 11;
|
||||
|
||||
private String variableName = "";
|
||||
private int availability = AVAILABILITY_ROOM_ACTIVE;
|
||||
|
||||
public WiredExtraRoomVariable(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredExtraRoomVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
|
||||
String normalizedName = WiredVariableNameValidator.normalizeForSave(settings.getStringParam());
|
||||
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
|
||||
if (room == null) {
|
||||
throw new WiredSaveException("Room not found");
|
||||
}
|
||||
|
||||
WiredVariableNameValidator.validateDefinitionName(room, this.getId(), normalizedName);
|
||||
|
||||
int[] intParams = settings.getIntParams();
|
||||
|
||||
this.variableName = normalizedName;
|
||||
this.availability = normalizeAvailability((intParams.length > 0) ? intParams[0] : AVAILABILITY_ROOM_ACTIVE);
|
||||
|
||||
room.getRoomVariableManager().handleDefinitionUpdated(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
return WiredManager.getGson().toJson(new JsonData(this.variableName, this.availability));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
int currentValue = (room != null) ? room.getRoomVariableManager().getCurrentValue(this.getId()) : 0;
|
||||
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(this.variableName);
|
||||
message.appendInt(2);
|
||||
message.appendInt(this.availability);
|
||||
message.appendInt(currentValue);
|
||||
message.appendInt(0);
|
||||
message.appendInt(CODE);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wiredData.startsWith("{")) {
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
|
||||
if (data != null) {
|
||||
this.variableName = WiredVariableNameValidator.normalizeLegacy(data.variableName);
|
||||
this.availability = normalizeAvailability(data.availability);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.variableName = WiredVariableNameValidator.normalizeLegacy(wiredData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
this.variableName = "";
|
||||
this.availability = AVAILABILITY_ROOM_ACTIVE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConfiguration() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getVariableName() {
|
||||
return this.variableName;
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getAvailability() {
|
||||
return this.availability;
|
||||
}
|
||||
|
||||
public boolean isPermanentAvailability() {
|
||||
return this.availability == AVAILABILITY_PERMANENT || this.availability == AVAILABILITY_SHARED;
|
||||
}
|
||||
|
||||
public boolean isSharedAvailability() {
|
||||
return this.availability == AVAILABILITY_SHARED;
|
||||
}
|
||||
|
||||
private static int normalizeAvailability(int value) {
|
||||
if (value == AVAILABILITY_PERMANENT || value == AVAILABILITY_SHARED) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return AVAILABILITY_ROOM_ACTIVE;
|
||||
}
|
||||
|
||||
static class JsonData {
|
||||
String variableName;
|
||||
int availability;
|
||||
|
||||
JsonData(String variableName, int availability) {
|
||||
this.variableName = variableName;
|
||||
this.availability = availability;
|
||||
}
|
||||
}
|
||||
}
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
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.habbohotel.rooms.WiredVariableDefinitionInfo;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredVariableTextConnectorSupport;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class WiredExtraTextInputVariable extends InteractionWiredExtra {
|
||||
public static final int CODE = 85;
|
||||
public static final int DISPLAY_NUMERIC = 1;
|
||||
public static final int DISPLAY_TEXTUAL = 2;
|
||||
public static final String DEFAULT_CAPTURER_NAME = "";
|
||||
public static final int MAX_CAPTURER_NAME_LENGTH = 32;
|
||||
|
||||
private static final String CUSTOM_TOKEN_PREFIX = "custom:";
|
||||
private static final Pattern WRAPPED_PLACEHOLDER_PATTERN = Pattern.compile("^#(.*)#$");
|
||||
|
||||
private int variableItemId = 0;
|
||||
private String variableToken = "";
|
||||
private String capturerName = DEFAULT_CAPTURER_NAME;
|
||||
private int displayType = DISPLAY_NUMERIC;
|
||||
|
||||
public WiredExtraTextInputVariable(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredExtraTextInputVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
if (room == null) {
|
||||
throw new WiredSaveException("Room not found");
|
||||
}
|
||||
|
||||
int[] intParams = settings.getIntParams();
|
||||
String[] stringData = splitStringData(settings.getStringParam());
|
||||
String nextVariableToken = normalizeVariableToken(stringData[0]);
|
||||
int nextVariableItemId = getCustomItemId(nextVariableToken);
|
||||
|
||||
if (nextVariableItemId <= 0) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.missing_variable");
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definitionInfo = WiredContextVariableSupport.getDefinitionInfo(room, nextVariableItemId);
|
||||
if (definitionInfo == null || !definitionInfo.hasValue()) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
|
||||
this.variableItemId = nextVariableItemId;
|
||||
this.variableToken = nextVariableToken;
|
||||
this.capturerName = normalizeCapturerName(stringData[1]);
|
||||
this.displayType = normalizeDisplayType((intParams.length > 0) ? intParams[0] : DISPLAY_NUMERIC);
|
||||
|
||||
if (!canUseTextualDisplay(room, this.variableItemId)) {
|
||||
this.displayType = DISPLAY_NUMERIC;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
return WiredManager.getGson().toJson(new JsonData(this.variableToken, this.variableItemId, this.capturerName, this.displayType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(this.variableToken + "\t" + this.capturerName);
|
||||
message.appendInt(1);
|
||||
message.appendInt(this.getDisplayType(room));
|
||||
message.appendInt(0);
|
||||
message.appendInt(CODE);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wiredData.startsWith("{")) {
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
|
||||
if (data != null) {
|
||||
this.variableToken = normalizeVariableToken((data.variableToken != null)
|
||||
? data.variableToken
|
||||
: ((data.variableItemId > 0) ? String.valueOf(data.variableItemId) : ""));
|
||||
this.variableItemId = getCustomItemId(this.variableToken);
|
||||
this.capturerName = normalizeCapturerName(data.capturerName);
|
||||
this.displayType = normalizeDisplayType(data.displayType);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
String[] legacyData = splitStringData(wiredData);
|
||||
this.variableToken = normalizeVariableToken(legacyData[0]);
|
||||
this.variableItemId = getCustomItemId(this.variableToken);
|
||||
this.capturerName = normalizeCapturerName(legacyData[1]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
this.variableItemId = 0;
|
||||
this.variableToken = "";
|
||||
this.capturerName = DEFAULT_CAPTURER_NAME;
|
||||
this.displayType = DISPLAY_NUMERIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConfiguration() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getVariableItemId() {
|
||||
return this.variableItemId;
|
||||
}
|
||||
|
||||
public String getVariableToken() {
|
||||
return this.variableToken;
|
||||
}
|
||||
|
||||
public String getCapturerName() {
|
||||
return this.capturerName;
|
||||
}
|
||||
|
||||
public String getPlaceholderToken() {
|
||||
return this.capturerName.isEmpty() ? "" : "#" + this.capturerName + "#";
|
||||
}
|
||||
|
||||
public int getDisplayType(Room room) {
|
||||
return (this.displayType == DISPLAY_TEXTUAL && canUseTextualDisplay(room, this.variableItemId))
|
||||
? DISPLAY_TEXTUAL
|
||||
: DISPLAY_NUMERIC;
|
||||
}
|
||||
|
||||
public Integer resolveCapturedValue(Room room, String rawValue) {
|
||||
String normalizedValue = rawValue != null ? rawValue.trim() : "";
|
||||
if (normalizedValue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.getDisplayType(room) == DISPLAY_TEXTUAL) {
|
||||
return WiredVariableTextConnectorSupport.toValue(room, this.variableItemId, normalizedValue);
|
||||
}
|
||||
|
||||
try {
|
||||
return Integer.parseInt(normalizedValue);
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean canUseTextualDisplay(Room room, int definitionItemId) {
|
||||
WiredVariableDefinitionInfo definitionInfo = WiredContextVariableSupport.getDefinitionInfo(room, definitionItemId);
|
||||
return definitionInfo != null && definitionInfo.hasValue() && definitionInfo.isTextConnected();
|
||||
}
|
||||
|
||||
private static String[] splitStringData(String value) {
|
||||
if (value == null) {
|
||||
return new String[]{ "", DEFAULT_CAPTURER_NAME };
|
||||
}
|
||||
|
||||
String[] parts = value.split("\t", -1);
|
||||
if (parts.length == 1) {
|
||||
return new String[]{ parts[0], DEFAULT_CAPTURER_NAME };
|
||||
}
|
||||
|
||||
return new String[]{ parts[0], parts[1] };
|
||||
}
|
||||
|
||||
private static int normalizeDisplayType(int value) {
|
||||
return (value == DISPLAY_TEXTUAL) ? DISPLAY_TEXTUAL : DISPLAY_NUMERIC;
|
||||
}
|
||||
|
||||
private static String normalizeCapturerName(String value) {
|
||||
if (value == null) {
|
||||
return DEFAULT_CAPTURER_NAME;
|
||||
}
|
||||
|
||||
String normalized = value.trim().replace("\t", "").replace("\r", "").replace("\n", "");
|
||||
if (WRAPPED_PLACEHOLDER_PATTERN.matcher(normalized).matches()) {
|
||||
normalized = normalized.substring(1, normalized.length() - 1).trim();
|
||||
}
|
||||
|
||||
if (normalized.length() > MAX_CAPTURER_NAME_LENGTH) {
|
||||
normalized = normalized.substring(0, MAX_CAPTURER_NAME_LENGTH);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static String normalizeVariableToken(String value) {
|
||||
String normalized = value == null ? "" : value.trim();
|
||||
if (normalized.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (normalized.startsWith(CUSTOM_TOKEN_PREFIX)) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
try {
|
||||
int parsedValue = Integer.parseInt(normalized);
|
||||
return parsedValue > 0 ? (CUSTOM_TOKEN_PREFIX + parsedValue) : "";
|
||||
} catch (NumberFormatException ignored) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static int getCustomItemId(String token) {
|
||||
if (token == null || !token.startsWith(CUSTOM_TOKEN_PREFIX)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
return Integer.parseInt(token.substring(CUSTOM_TOKEN_PREFIX.length()));
|
||||
} catch (NumberFormatException ignored) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static class JsonData {
|
||||
String variableToken;
|
||||
int variableItemId;
|
||||
String capturerName;
|
||||
int displayType;
|
||||
|
||||
JsonData(String variableToken, int variableItemId, String capturerName, int displayType) {
|
||||
this.variableToken = variableToken;
|
||||
this.variableItemId = variableItemId;
|
||||
this.capturerName = capturerName;
|
||||
this.displayType = displayType;
|
||||
}
|
||||
}
|
||||
}
|
||||
+544
@@ -0,0 +1,544 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
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.habbohotel.rooms.WiredVariableDefinitionInfo;
|
||||
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredInternalVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
|
||||
import gnu.trove.set.hash.THashSet;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class WiredExtraTextOutputVariable extends InteractionWiredExtra {
|
||||
public static final int CODE = 80;
|
||||
public static final int TARGET_USER = 0;
|
||||
public static final int TARGET_FURNI = 1;
|
||||
public static final int TARGET_CONTEXT = 2;
|
||||
public static final int TARGET_ROOM = 3;
|
||||
public static final int DISPLAY_NUMERIC = 1;
|
||||
public static final int DISPLAY_TEXTUAL = 2;
|
||||
public static final int TYPE_SINGLE = 1;
|
||||
public static final int TYPE_MULTIPLE = 2;
|
||||
public static final String DEFAULT_VARIABLE_TOKEN = "";
|
||||
public static final String DEFAULT_PLACEHOLDER_NAME = "";
|
||||
public static final String DEFAULT_DELIMITER = ", ";
|
||||
public static final int MAX_PLACEHOLDER_NAME_LENGTH = 32;
|
||||
public static final int MAX_DELIMITER_LENGTH = 16;
|
||||
|
||||
private static final String CUSTOM_TOKEN_PREFIX = "custom:";
|
||||
private static final String INTERNAL_TOKEN_PREFIX = "internal:";
|
||||
private static final Pattern WRAPPED_PLACEHOLDER_PATTERN = Pattern.compile("^\\$\\((.*)\\)$");
|
||||
|
||||
private final THashSet<HabboItem> items;
|
||||
private int targetType = TARGET_USER;
|
||||
private int displayType = DISPLAY_NUMERIC;
|
||||
private int placeholderType = TYPE_SINGLE;
|
||||
private int userSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
private int furniSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
private int variableItemId = 0;
|
||||
private String variableToken = DEFAULT_VARIABLE_TOKEN;
|
||||
private String placeholderName = DEFAULT_PLACEHOLDER_NAME;
|
||||
private String delimiter = DEFAULT_DELIMITER;
|
||||
|
||||
public WiredExtraTextOutputVariable(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
this.items = new THashSet<>();
|
||||
}
|
||||
|
||||
public WiredExtraTextOutputVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
this.items = new THashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
if (room == null) {
|
||||
throw new WiredSaveException("Room not found");
|
||||
}
|
||||
|
||||
int[] intParams = settings.getIntParams();
|
||||
String[] stringData = splitStringData(settings.getStringParam());
|
||||
int nextTargetType = normalizeTargetType((intParams.length > 0) ? intParams[0] : TARGET_USER);
|
||||
String nextVariableToken = normalizeVariableToken(stringData[0]);
|
||||
|
||||
if (nextVariableToken.isEmpty()) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.missing_variable");
|
||||
}
|
||||
|
||||
if (!isValidVariable(room, nextTargetType, nextVariableToken)) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
|
||||
int nextFurniSource = normalizeFurniSource((intParams.length > 4) ? intParams[4] : WiredSourceUtil.SOURCE_TRIGGER);
|
||||
this.items.clear();
|
||||
|
||||
if (nextTargetType == TARGET_FURNI && nextFurniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
if (settings.getFurniIds().length > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
|
||||
for (int itemId : settings.getFurniIds()) {
|
||||
HabboItem item = room.getHabboItem(itemId);
|
||||
|
||||
if (item != null) {
|
||||
this.items.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.targetType = nextTargetType;
|
||||
this.setVariableToken(nextVariableToken);
|
||||
this.displayType = normalizeDisplayType((intParams.length > 1) ? intParams[1] : DISPLAY_NUMERIC);
|
||||
this.placeholderType = normalizePlaceholderType((intParams.length > 2) ? intParams[2] : TYPE_SINGLE);
|
||||
this.userSource = normalizeUserSource((intParams.length > 3) ? intParams[3] : WiredSourceUtil.SOURCE_TRIGGER);
|
||||
this.furniSource = nextFurniSource;
|
||||
this.placeholderName = normalizePlaceholderName(stringData[1]);
|
||||
this.delimiter = normalizeDelimiter(stringData[2]);
|
||||
|
||||
if (!canUseTextualDisplay(room, this.targetType, this.variableToken)) {
|
||||
this.displayType = DISPLAY_NUMERIC;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
return WiredManager.getGson().toJson(new JsonData(
|
||||
this.targetType,
|
||||
this.variableToken,
|
||||
this.variableItemId,
|
||||
this.displayType,
|
||||
this.placeholderType,
|
||||
this.userSource,
|
||||
this.furniSource,
|
||||
this.placeholderName,
|
||||
this.delimiter,
|
||||
this.items.stream().map(HabboItem::getId).collect(Collectors.toList())
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
this.refresh(room);
|
||||
|
||||
List<HabboItem> selectedItems = new ArrayList<>();
|
||||
if (this.targetType == TARGET_FURNI && this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
|
||||
selectedItems.addAll(this.items);
|
||||
}
|
||||
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
|
||||
message.appendInt(selectedItems.size());
|
||||
|
||||
for (HabboItem item : selectedItems) {
|
||||
message.appendInt(item.getId());
|
||||
}
|
||||
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(this.variableToken + "\t" + this.placeholderName + "\t" + this.delimiter);
|
||||
message.appendInt(5);
|
||||
message.appendInt(this.targetType);
|
||||
message.appendInt(this.displayType);
|
||||
message.appendInt(this.placeholderType);
|
||||
message.appendInt(this.userSource);
|
||||
message.appendInt(this.furniSource);
|
||||
message.appendInt(0);
|
||||
message.appendInt(CODE);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wiredData.startsWith("{")) {
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
|
||||
if (data != null) {
|
||||
this.targetType = normalizeTargetType(data.targetType);
|
||||
this.setVariableToken(normalizeVariableToken((data.variableToken != null) ? data.variableToken : ((data.variableItemId > 0) ? String.valueOf(data.variableItemId) : "")));
|
||||
this.displayType = normalizeDisplayType(data.displayType);
|
||||
this.placeholderType = normalizePlaceholderType(data.placeholderType);
|
||||
this.userSource = normalizeUserSource(data.userSource);
|
||||
this.furniSource = normalizeFurniSource(data.furniSource);
|
||||
this.placeholderName = normalizePlaceholderName(data.placeholderName);
|
||||
this.delimiter = normalizeDelimiter(data.delimiter);
|
||||
|
||||
if (room != null && data.itemIds != null) {
|
||||
for (Integer itemId : data.itemIds) {
|
||||
if (itemId == null || itemId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
HabboItem item = room.getHabboItem(itemId);
|
||||
if (item != null) {
|
||||
this.items.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (room == null || !canUseTextualDisplay(room, this.targetType, this.variableToken)) {
|
||||
this.displayType = DISPLAY_NUMERIC;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
String[] legacyData = splitStringData(wiredData);
|
||||
this.setVariableToken(normalizeVariableToken(legacyData[0]));
|
||||
this.placeholderName = normalizePlaceholderName(legacyData[1]);
|
||||
this.delimiter = normalizeDelimiter(legacyData[2]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
this.items.clear();
|
||||
this.targetType = TARGET_USER;
|
||||
this.setVariableToken(DEFAULT_VARIABLE_TOKEN);
|
||||
this.displayType = DISPLAY_NUMERIC;
|
||||
this.placeholderType = TYPE_SINGLE;
|
||||
this.userSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.furniSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.placeholderName = DEFAULT_PLACEHOLDER_NAME;
|
||||
this.delimiter = DEFAULT_DELIMITER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConfiguration() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getTargetType() {
|
||||
return this.targetType;
|
||||
}
|
||||
|
||||
public String getVariableToken() {
|
||||
return this.variableToken;
|
||||
}
|
||||
|
||||
public int getVariableItemId() {
|
||||
return this.variableItemId;
|
||||
}
|
||||
|
||||
public int getDisplayType(Room room) {
|
||||
return (this.displayType == DISPLAY_TEXTUAL && canUseTextualDisplay(room, this.targetType, this.variableToken))
|
||||
? DISPLAY_TEXTUAL
|
||||
: DISPLAY_NUMERIC;
|
||||
}
|
||||
|
||||
public int getPlaceholderType() {
|
||||
return this.placeholderType;
|
||||
}
|
||||
|
||||
public String getPlaceholderName() {
|
||||
return this.placeholderName;
|
||||
}
|
||||
|
||||
public String getPlaceholderToken() {
|
||||
return this.placeholderName.isEmpty() ? "" : "$(" + this.placeholderName + ")";
|
||||
}
|
||||
|
||||
public String getDelimiter() {
|
||||
return this.delimiter;
|
||||
}
|
||||
|
||||
public int getUserSource() {
|
||||
return this.userSource;
|
||||
}
|
||||
|
||||
public int getFurniSource() {
|
||||
return this.furniSource;
|
||||
}
|
||||
|
||||
public THashSet<HabboItem> getItems() {
|
||||
return this.items;
|
||||
}
|
||||
|
||||
public boolean requiresActor() {
|
||||
return this.targetType == TARGET_USER
|
||||
&& (this.userSource == WiredSourceUtil.SOURCE_TRIGGER || this.userSource == WiredSourceUtil.SOURCE_CLICKED_USER);
|
||||
}
|
||||
|
||||
public void refresh(Room room) {
|
||||
THashSet<HabboItem> remove = new THashSet<>();
|
||||
|
||||
for (HabboItem item : this.items) {
|
||||
if (room == null || room.getHabboItem(item.getId()) == null) {
|
||||
remove.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
for (HabboItem item : remove) {
|
||||
this.items.remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isCustomVariableToken(String token) {
|
||||
return token != null && token.startsWith(CUSTOM_TOKEN_PREFIX);
|
||||
}
|
||||
|
||||
public static boolean isInternalVariableToken(String token) {
|
||||
return token != null && token.startsWith(INTERNAL_TOKEN_PREFIX);
|
||||
}
|
||||
|
||||
public static String getInternalVariableKey(String token) {
|
||||
return isInternalVariableToken(token) ? WiredInternalVariableSupport.normalizeKey(token.substring(INTERNAL_TOKEN_PREFIX.length())) : "";
|
||||
}
|
||||
|
||||
public static int getCustomItemId(String token) {
|
||||
if (!isCustomVariableToken(token)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
return Integer.parseInt(token.substring(CUSTOM_TOKEN_PREFIX.length()));
|
||||
} catch (NumberFormatException ignored) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] splitStringData(String value) {
|
||||
if (value == null) {
|
||||
return new String[]{ DEFAULT_VARIABLE_TOKEN, DEFAULT_PLACEHOLDER_NAME, DEFAULT_DELIMITER };
|
||||
}
|
||||
|
||||
String[] parts = value.split("\t", -1);
|
||||
if (parts.length == 1) {
|
||||
return new String[]{ value, DEFAULT_PLACEHOLDER_NAME, DEFAULT_DELIMITER };
|
||||
}
|
||||
|
||||
if (parts.length == 2) {
|
||||
return new String[]{ parts[0], parts[1], DEFAULT_DELIMITER };
|
||||
}
|
||||
|
||||
return new String[]{ parts[0], parts[1], parts[2] };
|
||||
}
|
||||
|
||||
private static int normalizeTargetType(int value) {
|
||||
return switch (value) {
|
||||
case TARGET_FURNI, TARGET_CONTEXT, TARGET_ROOM -> value;
|
||||
default -> TARGET_USER;
|
||||
};
|
||||
}
|
||||
|
||||
private static int normalizeDisplayType(int value) {
|
||||
return (value == DISPLAY_TEXTUAL) ? DISPLAY_TEXTUAL : DISPLAY_NUMERIC;
|
||||
}
|
||||
|
||||
private static int normalizePlaceholderType(int value) {
|
||||
return (value == TYPE_MULTIPLE) ? TYPE_MULTIPLE : TYPE_SINGLE;
|
||||
}
|
||||
|
||||
private static int normalizeUserSource(int value) {
|
||||
return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
|
||||
}
|
||||
|
||||
private static int normalizeFurniSource(int value) {
|
||||
return switch (value) {
|
||||
case WiredSourceUtil.SOURCE_SELECTED, WiredSourceUtil.SOURCE_SELECTOR, WiredSourceUtil.SOURCE_SIGNAL -> value;
|
||||
default -> WiredSourceUtil.SOURCE_TRIGGER;
|
||||
};
|
||||
}
|
||||
|
||||
private static String normalizePlaceholderName(String value) {
|
||||
if (value == null) {
|
||||
return DEFAULT_PLACEHOLDER_NAME;
|
||||
}
|
||||
|
||||
String normalized = value.trim().replace("\t", "").replace("\r", "").replace("\n", "");
|
||||
if (WRAPPED_PLACEHOLDER_PATTERN.matcher(normalized).matches()) {
|
||||
normalized = normalized.substring(2, normalized.length() - 1).trim();
|
||||
}
|
||||
|
||||
if (normalized.length() > MAX_PLACEHOLDER_NAME_LENGTH) {
|
||||
normalized = normalized.substring(0, MAX_PLACEHOLDER_NAME_LENGTH);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static String normalizeDelimiter(String value) {
|
||||
if (value == null) {
|
||||
return DEFAULT_DELIMITER;
|
||||
}
|
||||
|
||||
String normalized = value.replace("\t", "").replace("\r", "").replace("\n", "");
|
||||
if (normalized.length() > MAX_DELIMITER_LENGTH) {
|
||||
normalized = normalized.substring(0, MAX_DELIMITER_LENGTH);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static String normalizeVariableToken(String value) {
|
||||
String normalized = (value == null) ? "" : value.trim();
|
||||
if (normalized.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (isCustomVariableToken(normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
if (isInternalVariableToken(normalized)) {
|
||||
return INTERNAL_TOKEN_PREFIX + WiredInternalVariableSupport.normalizeKey(normalized.substring(INTERNAL_TOKEN_PREFIX.length()));
|
||||
}
|
||||
|
||||
try {
|
||||
int parsedValue = Integer.parseInt(normalized);
|
||||
return parsedValue > 0 ? (CUSTOM_TOKEN_PREFIX + parsedValue) : "";
|
||||
} catch (NumberFormatException ignored) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private void setVariableToken(String token) {
|
||||
this.variableToken = normalizeVariableToken(token);
|
||||
this.variableItemId = getCustomItemId(this.variableToken);
|
||||
}
|
||||
|
||||
private static boolean canUseTextualDisplay(Room room, int targetType, String variableToken) {
|
||||
if (room == null || !isCustomVariableToken(variableToken)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int itemId = getCustomItemId(variableToken);
|
||||
if (itemId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return switch (targetType) {
|
||||
case TARGET_USER -> {
|
||||
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(itemId);
|
||||
yield definition != null && definition.hasValue() && definition.isTextConnected();
|
||||
}
|
||||
case TARGET_FURNI -> {
|
||||
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(itemId);
|
||||
yield definition != null && definition.hasValue() && definition.isTextConnected();
|
||||
}
|
||||
case TARGET_CONTEXT -> {
|
||||
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, itemId);
|
||||
yield definition != null && definition.hasValue() && definition.isTextConnected();
|
||||
}
|
||||
case TARGET_ROOM -> {
|
||||
WiredVariableDefinitionInfo definition = room.getRoomVariableManager().getDefinitionInfo(itemId);
|
||||
yield definition != null && definition.hasValue() && definition.isTextConnected();
|
||||
}
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean isValidVariable(Room room, int targetType, String variableToken) {
|
||||
if (room == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return switch (targetType) {
|
||||
case TARGET_USER -> isInternalVariableToken(variableToken)
|
||||
? canUseUserInternalReference(getInternalVariableKey(variableToken))
|
||||
: isUserCustomValue(room, getCustomItemId(variableToken));
|
||||
case TARGET_FURNI -> isInternalVariableToken(variableToken)
|
||||
? canUseFurniInternalReference(getInternalVariableKey(variableToken))
|
||||
: isFurniCustomValue(room, getCustomItemId(variableToken));
|
||||
case TARGET_CONTEXT -> isInternalVariableToken(variableToken)
|
||||
? WiredInternalVariableSupport.canUseContextReference(getInternalVariableKey(variableToken))
|
||||
: isContextCustomValue(room, getCustomItemId(variableToken));
|
||||
case TARGET_ROOM -> isInternalVariableToken(variableToken)
|
||||
? canUseRoomInternalReference(getInternalVariableKey(variableToken))
|
||||
: isRoomCustomValue(room, getCustomItemId(variableToken));
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean isUserCustomValue(Room room, int itemId) {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getUserVariableManager().getDefinitionInfo(itemId) : null;
|
||||
return definition != null && definition.hasValue();
|
||||
}
|
||||
|
||||
private static boolean isFurniCustomValue(Room room, int itemId) {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getFurniVariableManager().getDefinitionInfo(itemId) : null;
|
||||
return definition != null && definition.hasValue();
|
||||
}
|
||||
|
||||
private static boolean isRoomCustomValue(Room room, int itemId) {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getRoomVariableManager().getDefinitionInfo(itemId) : null;
|
||||
return definition != null && definition.hasValue();
|
||||
}
|
||||
|
||||
private static boolean isContextCustomValue(Room room, int itemId) {
|
||||
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, itemId);
|
||||
return definition != null && definition.hasValue();
|
||||
}
|
||||
|
||||
private static boolean canUseUserInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseUserReference(key);
|
||||
}
|
||||
|
||||
private static boolean canUseFurniInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseFurniReference(key);
|
||||
}
|
||||
|
||||
private static boolean canUseRoomInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseRoomReference(key);
|
||||
}
|
||||
|
||||
static class JsonData {
|
||||
int targetType;
|
||||
String variableToken;
|
||||
int variableItemId;
|
||||
int displayType;
|
||||
int placeholderType;
|
||||
int userSource;
|
||||
int furniSource;
|
||||
String placeholderName;
|
||||
String delimiter;
|
||||
List<Integer> itemIds;
|
||||
|
||||
JsonData(int targetType, String variableToken, int variableItemId, int displayType, int placeholderType, int userSource, int furniSource, String placeholderName, String delimiter, List<Integer> itemIds) {
|
||||
this.targetType = targetType;
|
||||
this.variableToken = variableToken;
|
||||
this.variableItemId = variableItemId;
|
||||
this.displayType = displayType;
|
||||
this.placeholderType = placeholderType;
|
||||
this.userSource = userSource;
|
||||
this.furniSource = furniSource;
|
||||
this.placeholderName = placeholderName;
|
||||
this.delimiter = delimiter;
|
||||
this.itemIds = itemIds;
|
||||
}
|
||||
}
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
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.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class WiredExtraUserVariable extends InteractionWiredExtra {
|
||||
public static final int CODE = 70;
|
||||
public static final int AVAILABILITY_ROOM = 0;
|
||||
public static final int AVAILABILITY_PERMANENT = 10;
|
||||
public static final int AVAILABILITY_SHARED = 11;
|
||||
|
||||
private String variableName = "";
|
||||
private boolean hasValue = false;
|
||||
private int availability = AVAILABILITY_ROOM;
|
||||
|
||||
public WiredExtraUserVariable(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredExtraUserVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
|
||||
int[] intParams = settings.getIntParams();
|
||||
String normalizedName = WiredVariableNameValidator.normalizeForSave(settings.getStringParam());
|
||||
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
|
||||
if (room == null) {
|
||||
throw new WiredSaveException("Room not found");
|
||||
}
|
||||
|
||||
WiredVariableNameValidator.validateDefinitionName(room, this.getId(), normalizedName);
|
||||
|
||||
this.variableName = normalizedName;
|
||||
this.hasValue = (intParams.length > 0) && (intParams[0] == 1);
|
||||
this.availability = normalizeAvailability((intParams.length > 1) ? intParams[1] : AVAILABILITY_ROOM);
|
||||
|
||||
room.getUserVariableManager().handleDefinitionUpdated(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
return WiredManager.getGson().toJson(new JsonData(this.variableName, this.hasValue, this.availability));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(this.variableName);
|
||||
message.appendInt(2);
|
||||
message.appendInt(this.hasValue ? 1 : 0);
|
||||
message.appendInt(this.availability);
|
||||
message.appendInt(0);
|
||||
message.appendInt(CODE);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wiredData.startsWith("{")) {
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
|
||||
if (data != null) {
|
||||
this.variableName = WiredVariableNameValidator.normalizeLegacy(data.variableName);
|
||||
this.hasValue = data.hasValue;
|
||||
this.availability = normalizeAvailability(data.availability);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.variableName = WiredVariableNameValidator.normalizeLegacy(wiredData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
this.variableName = "";
|
||||
this.hasValue = false;
|
||||
this.availability = AVAILABILITY_ROOM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConfiguration() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getVariableName() {
|
||||
return variableName;
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return hasValue;
|
||||
}
|
||||
|
||||
public int getAvailability() {
|
||||
return availability;
|
||||
}
|
||||
|
||||
public boolean isPermanentAvailability() {
|
||||
return this.availability == AVAILABILITY_PERMANENT || this.availability == AVAILABILITY_SHARED;
|
||||
}
|
||||
|
||||
public boolean isSharedAvailability() {
|
||||
return this.availability == AVAILABILITY_SHARED;
|
||||
}
|
||||
|
||||
private static int normalizeAvailability(int value) {
|
||||
if (value == AVAILABILITY_PERMANENT || value == AVAILABILITY_SHARED) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return AVAILABILITY_ROOM;
|
||||
}
|
||||
|
||||
static class JsonData {
|
||||
String variableName;
|
||||
boolean hasValue;
|
||||
int availability;
|
||||
|
||||
JsonData(String variableName, boolean hasValue, int availability) {
|
||||
this.variableName = variableName;
|
||||
this.hasValue = hasValue;
|
||||
this.availability = availability;
|
||||
}
|
||||
}
|
||||
}
|
||||
+800
@@ -0,0 +1,800 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.bots.Bot;
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.games.Game;
|
||||
import com.eu.habbo.habbohotel.games.GamePlayer;
|
||||
import com.eu.habbo.habbohotel.games.GameTeam;
|
||||
import com.eu.habbo.habbohotel.games.GameTeamColors;
|
||||
import com.eu.habbo.habbohotel.items.FurnitureType;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
|
||||
import com.eu.habbo.habbohotel.pets.Pet;
|
||||
import com.eu.habbo.habbohotel.rooms.FurnitureMovementError;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomTile;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomTileState;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomRightLevels;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUserRotation;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
|
||||
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
|
||||
import com.eu.habbo.habbohotel.users.DanceType;
|
||||
import com.eu.habbo.habbohotel.users.HabboGender;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredInternalVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredUserMovementHelper;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredVariableLevelSystemSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredVariableTextConnectorSupport;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
|
||||
import com.eu.habbo.util.HotelDateTimeUtil;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.Locale;
|
||||
|
||||
public class WiredExtraVariableEcho extends InteractionWiredExtra {
|
||||
public static final int CODE = 83;
|
||||
public static final int TARGET_USER = 0;
|
||||
public static final int TARGET_FURNI = 1;
|
||||
public static final int TARGET_ROOM = 3;
|
||||
|
||||
private static final String CUSTOM_TOKEN_PREFIX = "custom:";
|
||||
private static final String INTERNAL_TOKEN_PREFIX = "internal:";
|
||||
private static final int DEFAULT_USER_AVAILABILITY = WiredExtraUserVariable.AVAILABILITY_ROOM;
|
||||
private static final int DEFAULT_FURNI_AVAILABILITY = WiredExtraFurniVariable.AVAILABILITY_ROOM_ACTIVE;
|
||||
private static final int DEFAULT_ROOM_AVAILABILITY = WiredExtraRoomVariable.AVAILABILITY_ROOM_ACTIVE;
|
||||
|
||||
private String variableName = "";
|
||||
private int sourceTargetType = TARGET_USER;
|
||||
private String sourceVariableToken = "";
|
||||
private int sourceVariableItemId = 0;
|
||||
private String sourceVariableName = "";
|
||||
|
||||
public WiredExtraVariableEcho(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredExtraVariableEcho(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
|
||||
if (room == null) {
|
||||
throw new WiredSaveException("Room not found");
|
||||
}
|
||||
|
||||
ConfigData config = parseConfigData(settings.getStringParam());
|
||||
int normalizedTargetType = normalizeTargetType(config.sourceTargetType);
|
||||
String normalizedToken = normalizeVariableToken(config.sourceVariableToken, config.sourceVariableItemId);
|
||||
int normalizedItemId = getCustomVariableItemId(normalizedToken);
|
||||
SourceState sourceState = this.resolveSourceState(room, normalizedTargetType, normalizedToken, normalizedItemId);
|
||||
|
||||
if (normalizedToken.isEmpty()) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.missing_variable");
|
||||
}
|
||||
|
||||
if (sourceState == null || !sourceState.hasValue()) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
|
||||
if (!isAllowedEchoSource(sourceState, normalizedToken)) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
|
||||
if (!WiredVariableTextConnectorSupport.isTextConnected(room, this)) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
|
||||
if (createsCycle(room, this.getId(), normalizedTargetType, normalizedToken, normalizedItemId)) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
|
||||
String normalizedName = deriveVariableName(config.variableName, sourceState.getName());
|
||||
WiredVariableNameValidator.validateDefinitionName(room, this.getId(), normalizedName);
|
||||
|
||||
this.variableName = normalizedName;
|
||||
this.sourceTargetType = normalizedTargetType;
|
||||
this.sourceVariableToken = normalizedToken;
|
||||
this.sourceVariableItemId = normalizedItemId;
|
||||
this.sourceVariableName = sourceState.getName();
|
||||
|
||||
room.getUserVariableManager().broadcastSnapshot();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
return WiredManager.getGson().toJson(new JsonData(this.variableName, this.sourceTargetType, this.sourceVariableToken, this.sourceVariableItemId, this.sourceVariableName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(WiredManager.getGson().toJson(new EditorPayload(this.variableName, this.sourceTargetType, this.sourceVariableToken, this.sourceVariableItemId, this.getResolvedSourceName(room))));
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
message.appendInt(CODE);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty() || !wiredData.startsWith("{")) {
|
||||
return;
|
||||
}
|
||||
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.variableName = WiredVariableNameValidator.normalizeLegacy(data.variableName);
|
||||
this.sourceTargetType = normalizeTargetType(data.sourceTargetType);
|
||||
this.sourceVariableToken = normalizeVariableToken(data.sourceVariableToken, data.sourceVariableItemId);
|
||||
this.sourceVariableItemId = getCustomVariableItemId(this.sourceVariableToken);
|
||||
this.sourceVariableName = normalizeSourceName(data.sourceVariableName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
this.variableName = "";
|
||||
this.sourceTargetType = TARGET_USER;
|
||||
this.sourceVariableToken = "";
|
||||
this.sourceVariableItemId = 0;
|
||||
this.sourceVariableName = "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConfiguration() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getVariableName() {
|
||||
return this.variableName;
|
||||
}
|
||||
|
||||
public int getSourceTargetType() {
|
||||
return this.sourceTargetType;
|
||||
}
|
||||
|
||||
public String getSourceVariableToken() {
|
||||
return this.sourceVariableToken;
|
||||
}
|
||||
|
||||
public int getSourceVariableItemId() {
|
||||
return this.sourceVariableItemId;
|
||||
}
|
||||
|
||||
public String getSourceVariableName() {
|
||||
return this.sourceVariableName;
|
||||
}
|
||||
|
||||
public boolean isUserEcho() {
|
||||
return this.sourceTargetType == TARGET_USER;
|
||||
}
|
||||
|
||||
public boolean isFurniEcho() {
|
||||
return this.sourceTargetType == TARGET_FURNI;
|
||||
}
|
||||
|
||||
public boolean isRoomEcho() {
|
||||
return this.sourceTargetType == TARGET_ROOM;
|
||||
}
|
||||
|
||||
public WiredVariableDefinitionInfo createDefinitionInfo(Room room) {
|
||||
SourceState sourceState = this.resolveSourceState(room, this.sourceTargetType, this.sourceVariableToken, this.sourceVariableItemId);
|
||||
int availability = (sourceState != null) ? sourceState.getAvailability() : defaultAvailability(this.sourceTargetType);
|
||||
boolean hasValue = (sourceState == null) || sourceState.hasValue();
|
||||
boolean readOnly = sourceState == null || sourceState.isReadOnly();
|
||||
|
||||
return new WiredVariableDefinitionInfo(
|
||||
this.getId(),
|
||||
this.variableName,
|
||||
hasValue,
|
||||
availability,
|
||||
WiredVariableTextConnectorSupport.isTextConnected(room, this),
|
||||
readOnly
|
||||
);
|
||||
}
|
||||
|
||||
public boolean hasVariable(Room room, int entityId) {
|
||||
if (room == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isCustomVariableToken(this.sourceVariableToken)) {
|
||||
return switch (this.sourceTargetType) {
|
||||
case TARGET_FURNI -> room.getFurniVariableManager().hasVariable(entityId, this.sourceVariableItemId);
|
||||
case TARGET_ROOM -> room.getRoomVariableManager().hasVariable(this.sourceVariableItemId);
|
||||
default -> room.getUserVariableManager().hasVariable(entityId, this.sourceVariableItemId);
|
||||
};
|
||||
}
|
||||
|
||||
return this.readCurrentValue(room, entityId) != null;
|
||||
}
|
||||
|
||||
public int getCurrentValue(Room room, int entityId) {
|
||||
Integer value = this.readCurrentValue(room, entityId);
|
||||
return (value != null) ? value : 0;
|
||||
}
|
||||
|
||||
public int getCreatedAt(Room room, int entityId) {
|
||||
if (room == null || !isCustomVariableToken(this.sourceVariableToken)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return switch (this.sourceTargetType) {
|
||||
case TARGET_FURNI -> room.getFurniVariableManager().getCreatedAt(entityId, this.sourceVariableItemId);
|
||||
case TARGET_ROOM -> room.getRoomVariableManager().getCreatedAt(this.sourceVariableItemId);
|
||||
default -> room.getUserVariableManager().getCreatedAt(entityId, this.sourceVariableItemId);
|
||||
};
|
||||
}
|
||||
|
||||
public int getUpdatedAt(Room room, int entityId) {
|
||||
if (room == null || !isCustomVariableToken(this.sourceVariableToken)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return switch (this.sourceTargetType) {
|
||||
case TARGET_FURNI -> room.getFurniVariableManager().getUpdatedAt(entityId, this.sourceVariableItemId);
|
||||
case TARGET_ROOM -> room.getRoomVariableManager().getUpdatedAt(this.sourceVariableItemId);
|
||||
default -> room.getUserVariableManager().getUpdatedAt(entityId, this.sourceVariableItemId);
|
||||
};
|
||||
}
|
||||
|
||||
public boolean assignValue(Room room, int entityId, Integer value, boolean overrideExisting) {
|
||||
if (room == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SourceState sourceState = this.resolveSourceState(room, this.sourceTargetType, this.sourceVariableToken, this.sourceVariableItemId);
|
||||
if (sourceState == null || sourceState.isReadOnly()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isCustomVariableToken(this.sourceVariableToken)) {
|
||||
return switch (this.sourceTargetType) {
|
||||
case TARGET_FURNI -> room.getFurniVariableManager().assignVariable(room.getHabboItem(entityId), this.sourceVariableItemId, value, overrideExisting);
|
||||
case TARGET_ROOM -> room.getRoomVariableManager().updateVariableValue(this.sourceVariableItemId, (value != null) ? value : 0);
|
||||
default -> room.getUserVariableManager().assignVariable(room.getHabbo(entityId), this.sourceVariableItemId, value, overrideExisting);
|
||||
};
|
||||
}
|
||||
|
||||
return value != null && this.writeCurrentValue(room, entityId, value);
|
||||
}
|
||||
|
||||
public boolean updateValue(Room room, int entityId, Integer value) {
|
||||
if (room == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SourceState sourceState = this.resolveSourceState(room, this.sourceTargetType, this.sourceVariableToken, this.sourceVariableItemId);
|
||||
if (sourceState == null || sourceState.isReadOnly() || !sourceState.hasValue()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isCustomVariableToken(this.sourceVariableToken)) {
|
||||
return switch (this.sourceTargetType) {
|
||||
case TARGET_FURNI -> room.getFurniVariableManager().updateVariableValue(entityId, this.sourceVariableItemId, value);
|
||||
case TARGET_ROOM -> room.getRoomVariableManager().updateVariableValue(this.sourceVariableItemId, (value != null) ? value : 0);
|
||||
default -> room.getUserVariableManager().updateVariableValue(entityId, this.sourceVariableItemId, value);
|
||||
};
|
||||
}
|
||||
|
||||
return value != null && this.writeCurrentValue(room, entityId, value);
|
||||
}
|
||||
|
||||
public boolean removeValue(Room room, int entityId) {
|
||||
if (room == null || !isCustomVariableToken(this.sourceVariableToken)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SourceState sourceState = this.resolveSourceState(room, this.sourceTargetType, this.sourceVariableToken, this.sourceVariableItemId);
|
||||
if (sourceState == null || sourceState.isReadOnly()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return switch (this.sourceTargetType) {
|
||||
case TARGET_FURNI -> room.getFurniVariableManager().removeVariable(entityId, this.sourceVariableItemId);
|
||||
case TARGET_ROOM -> room.getRoomVariableManager().removeVariable(this.sourceVariableItemId);
|
||||
default -> room.getUserVariableManager().removeVariable(entityId, this.sourceVariableItemId);
|
||||
};
|
||||
}
|
||||
|
||||
private Integer readCurrentValue(Room room, int entityId) {
|
||||
if (room == null || this.sourceVariableToken == null || this.sourceVariableToken.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isCustomVariableToken(this.sourceVariableToken)) {
|
||||
return switch (this.sourceTargetType) {
|
||||
case TARGET_FURNI -> room.getFurniVariableManager().hasVariable(entityId, this.sourceVariableItemId)
|
||||
? room.getFurniVariableManager().getCurrentValue(entityId, this.sourceVariableItemId)
|
||||
: null;
|
||||
case TARGET_ROOM -> room.getRoomVariableManager().getCurrentValue(this.sourceVariableItemId);
|
||||
default -> room.getUserVariableManager().hasVariable(entityId, this.sourceVariableItemId)
|
||||
? room.getUserVariableManager().getCurrentValue(entityId, this.sourceVariableItemId)
|
||||
: null;
|
||||
};
|
||||
}
|
||||
|
||||
String key = getInternalVariableKey(this.sourceVariableToken);
|
||||
if (key.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return switch (this.sourceTargetType) {
|
||||
case TARGET_FURNI -> this.readFurniInternalValue(room, room.getHabboItem(entityId), key);
|
||||
case TARGET_ROOM -> this.readRoomInternalValue(room, key);
|
||||
default -> {
|
||||
Habbo habbo = room.getHabbo(entityId);
|
||||
yield this.readUserInternalValue(room, (habbo != null) ? habbo.getRoomUnit() : null, key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private boolean writeCurrentValue(Room room, int entityId, int value) {
|
||||
if (room == null || !isInternalVariableToken(this.sourceVariableToken)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String key = getInternalVariableKey(this.sourceVariableToken);
|
||||
if (key.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return switch (this.sourceTargetType) {
|
||||
case TARGET_FURNI -> this.writeFurniInternalValue(room, room.getHabboItem(entityId), key, value);
|
||||
case TARGET_ROOM -> false;
|
||||
default -> {
|
||||
Habbo habbo = room.getHabbo(entityId);
|
||||
yield this.writeUserInternalValue(room, (habbo != null) ? habbo.getRoomUnit() : null, key, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private SourceState resolveSourceState(Room room, int targetType, String token, int variableItemId) {
|
||||
if (room == null || token == null || token.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isCustomVariableToken(token)) {
|
||||
WiredVariableDefinitionInfo definitionInfo = switch (targetType) {
|
||||
case TARGET_FURNI -> room.getFurniVariableManager().getDefinitionInfo(variableItemId);
|
||||
case TARGET_ROOM -> room.getRoomVariableManager().getDefinitionInfo(variableItemId);
|
||||
default -> room.getUserVariableManager().getDefinitionInfo(variableItemId);
|
||||
};
|
||||
|
||||
if (definitionInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SourceState(definitionInfo.getName(), definitionInfo.hasValue(), definitionInfo.getAvailability(), definitionInfo.isReadOnly());
|
||||
}
|
||||
|
||||
String key = getInternalVariableKey(token);
|
||||
if (key.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return switch (targetType) {
|
||||
case TARGET_FURNI -> canUseFurniInternalReference(key)
|
||||
? new SourceState(key, true, DEFAULT_FURNI_AVAILABILITY, !canUseFurniInternalDestination(key))
|
||||
: null;
|
||||
case TARGET_ROOM -> canUseRoomInternalReference(key)
|
||||
? new SourceState(key, true, DEFAULT_ROOM_AVAILABILITY, true)
|
||||
: null;
|
||||
default -> canUseUserInternalReference(key)
|
||||
? new SourceState(key, true, DEFAULT_USER_AVAILABILITY, !canUseUserInternalDestination(key))
|
||||
: null;
|
||||
};
|
||||
}
|
||||
|
||||
private String getResolvedSourceName(Room room) {
|
||||
SourceState sourceState = this.resolveSourceState(room, this.sourceTargetType, this.sourceVariableToken, this.sourceVariableItemId);
|
||||
return (sourceState != null) ? sourceState.getName() : this.sourceVariableName;
|
||||
}
|
||||
|
||||
private static boolean createsCycle(Room room, int currentItemId, int targetType, String token, int variableItemId) {
|
||||
if (room == null || currentItemId <= 0 || !isCustomVariableToken(token) || variableItemId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (variableItemId == currentItemId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
WiredVariableLevelSystemSupport.DerivedDefinition derivedDefinition = WiredVariableLevelSystemSupport.resolveDerivedDefinition(room, targetType, variableItemId);
|
||||
if (derivedDefinition != null) {
|
||||
return createsCycle(room, currentItemId, targetType, createCustomVariableToken(derivedDefinition.getBaseDefinitionItemId()), derivedDefinition.getBaseDefinitionItemId());
|
||||
}
|
||||
|
||||
if (room.getRoomSpecialTypes() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
InteractionWiredExtra extra = room.getRoomSpecialTypes().getExtra(variableItemId);
|
||||
if (!(extra instanceof WiredExtraVariableEcho)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WiredExtraVariableEcho echo = (WiredExtraVariableEcho) extra;
|
||||
return createsCycle(room, currentItemId, echo.getSourceTargetType(), echo.getSourceVariableToken(), echo.getSourceVariableItemId());
|
||||
}
|
||||
|
||||
private static String deriveVariableName(String requestedName, String sourceName) {
|
||||
String normalizedRequestedName = WiredVariableNameValidator.normalizeForSave(requestedName);
|
||||
if (!normalizedRequestedName.isEmpty()) {
|
||||
return normalizedRequestedName;
|
||||
}
|
||||
|
||||
String fallbackValue = normalizeSourceName(sourceName)
|
||||
.replaceAll("^[~@]+", "")
|
||||
.replaceAll("[^A-Za-z0-9_]+", "_")
|
||||
.replaceAll("_+", "_")
|
||||
.replaceAll("^_+", "")
|
||||
.replaceAll("_+$", "");
|
||||
|
||||
if (fallbackValue.length() > WiredVariableNameValidator.MAX_NAME_LENGTH) {
|
||||
fallbackValue = fallbackValue.substring(0, WiredVariableNameValidator.MAX_NAME_LENGTH);
|
||||
}
|
||||
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
private static boolean isAllowedEchoSource(SourceState sourceState, String token) {
|
||||
if (sourceState == null || token == null || token.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isInternalVariableToken(token)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isCustomVariableToken(token) && sourceState.getName() != null && sourceState.getName().contains(".");
|
||||
}
|
||||
|
||||
private static ConfigData parseConfigData(String value) {
|
||||
if (value == null || value.isEmpty() || !value.startsWith("{")) {
|
||||
return new ConfigData();
|
||||
}
|
||||
|
||||
ConfigData config = WiredManager.getGson().fromJson(value, ConfigData.class);
|
||||
return (config != null) ? config : new ConfigData();
|
||||
}
|
||||
|
||||
private static String normalizeSourceName(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return value.trim().replace("\t", "").replace("\r", "").replace("\n", "");
|
||||
}
|
||||
|
||||
private static int normalizeTargetType(int value) {
|
||||
if (value == TARGET_FURNI || value == TARGET_ROOM) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return TARGET_USER;
|
||||
}
|
||||
|
||||
private static int defaultAvailability(int targetType) {
|
||||
return switch (targetType) {
|
||||
case TARGET_FURNI -> DEFAULT_FURNI_AVAILABILITY;
|
||||
case TARGET_ROOM -> DEFAULT_ROOM_AVAILABILITY;
|
||||
default -> DEFAULT_USER_AVAILABILITY;
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean isCustomVariableToken(String token) {
|
||||
return token != null && token.startsWith(CUSTOM_TOKEN_PREFIX);
|
||||
}
|
||||
|
||||
private static boolean isInternalVariableToken(String token) {
|
||||
return token != null && token.startsWith(INTERNAL_TOKEN_PREFIX);
|
||||
}
|
||||
|
||||
private static String getInternalVariableKey(String token) {
|
||||
return isInternalVariableToken(token) ? WiredInternalVariableSupport.normalizeKey(token.substring(INTERNAL_TOKEN_PREFIX.length())) : "";
|
||||
}
|
||||
|
||||
private static int getCustomVariableItemId(String token) {
|
||||
if (!isCustomVariableToken(token)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
return Integer.parseInt(token.substring(CUSTOM_TOKEN_PREFIX.length()));
|
||||
} catch (NumberFormatException ignored) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static String createCustomVariableToken(int itemId) {
|
||||
return itemId > 0 ? CUSTOM_TOKEN_PREFIX + itemId : "";
|
||||
}
|
||||
|
||||
private static String normalizeVariableToken(String token, int fallbackItemId) {
|
||||
String normalizedToken = (token != null) ? token.trim() : "";
|
||||
|
||||
if (isCustomVariableToken(normalizedToken)) {
|
||||
return normalizedToken;
|
||||
}
|
||||
|
||||
if (isInternalVariableToken(normalizedToken)) {
|
||||
return INTERNAL_TOKEN_PREFIX + WiredInternalVariableSupport.normalizeKey(normalizedToken.substring(INTERNAL_TOKEN_PREFIX.length()));
|
||||
}
|
||||
|
||||
if (fallbackItemId > 0) {
|
||||
return createCustomVariableToken(fallbackItemId);
|
||||
}
|
||||
|
||||
if (normalizedToken.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
return createCustomVariableToken(Integer.parseInt(normalizedToken));
|
||||
} catch (NumberFormatException ignored) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private Integer readUserInternalValue(Room room, RoomUnit roomUnit, String key) {
|
||||
return WiredInternalVariableSupport.readUserValue(room, roomUnit, key);
|
||||
}
|
||||
|
||||
private Integer getUserTypeValue(Habbo habbo, Bot bot, Pet pet) {
|
||||
if (habbo != null) return 1;
|
||||
if (bot != null) return 2;
|
||||
if (pet != null) return 3;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Integer getGenderValue(Habbo habbo, Bot bot) {
|
||||
HabboGender gender = null;
|
||||
|
||||
if (habbo != null && habbo.getHabboInfo() != null) {
|
||||
gender = habbo.getHabboInfo().getGender();
|
||||
} else if (bot != null) {
|
||||
gender = bot.getGender();
|
||||
}
|
||||
|
||||
if (gender == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return gender == HabboGender.F ? 1 : 2;
|
||||
}
|
||||
|
||||
private boolean writeUserInternalValue(Room room, RoomUnit roomUnit, String key, int value) {
|
||||
return WiredInternalVariableSupport.writeUserValue(room, roomUnit, key, value);
|
||||
}
|
||||
|
||||
private Integer readFurniInternalValue(Room room, HabboItem item, String key) {
|
||||
return WiredInternalVariableSupport.readFurniValue(room, item, key);
|
||||
}
|
||||
|
||||
private boolean writeFurniInternalValue(Room room, HabboItem item, String key, int value) {
|
||||
return WiredInternalVariableSupport.writeFurniValue(room, item, key, value);
|
||||
}
|
||||
|
||||
private Integer readRoomInternalValue(Room room, String key) {
|
||||
return WiredInternalVariableSupport.readRoomValue(room, key);
|
||||
}
|
||||
|
||||
private Integer getUserTeamScore(Room room, Habbo habbo) {
|
||||
if (room == null || habbo == null || habbo.getHabboInfo() == null || habbo.getHabboInfo().getGamePlayer() == null) return null;
|
||||
|
||||
Game game = this.resolveTeamGame(room, habbo);
|
||||
GamePlayer gamePlayer = habbo.getHabboInfo().getGamePlayer();
|
||||
|
||||
if (game == null || gamePlayer.getTeamColor() == null) return gamePlayer.getScore();
|
||||
|
||||
GameTeam team = game.getTeam(gamePlayer.getTeamColor());
|
||||
return (team != null) ? team.getTotalScore() : gamePlayer.getScore();
|
||||
}
|
||||
|
||||
private int getTeamMetric(Room room, GameTeamColors color, boolean score) {
|
||||
Game game = this.resolveTeamGame(room, null);
|
||||
if (game == null || color == null) return 0;
|
||||
|
||||
GameTeam team = game.getTeam(color);
|
||||
if (team == null) return 0;
|
||||
|
||||
return score ? team.getTotalScore() : team.getMembers().size();
|
||||
}
|
||||
|
||||
private int getTeamColorId(int effectId) {
|
||||
if (effectId >= 33 && effectId <= 36) return effectId - 32;
|
||||
if (effectId >= 40 && effectId <= 43) return effectId - 39;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int getTeamTypeId(int effectId) {
|
||||
if (effectId >= 33 && effectId <= 36) return 1;
|
||||
if (effectId >= 40 && effectId <= 43) return 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private Game resolveTeamGame(Room room, Habbo habbo) {
|
||||
if (room == null) return null;
|
||||
|
||||
if (habbo != null && habbo.getHabboInfo() != null && habbo.getHabboInfo().getCurrentGame() != null) {
|
||||
Game game = room.getGame(habbo.getHabboInfo().getCurrentGame());
|
||||
if (game != null) return game;
|
||||
}
|
||||
|
||||
Game game = room.getGame(com.eu.habbo.habbohotel.games.battlebanzai.BattleBanzaiGame.class);
|
||||
if (game != null) return game;
|
||||
|
||||
game = room.getGame(com.eu.habbo.habbohotel.games.freeze.FreezeGame.class);
|
||||
if (game != null) return game;
|
||||
|
||||
return room.getGame(com.eu.habbo.habbohotel.games.wired.WiredGame.class);
|
||||
}
|
||||
|
||||
private boolean moveUserTo(Room room, RoomUnit roomUnit, int x, int y) {
|
||||
if (room == null || roomUnit == null || room.getLayout() == null) return false;
|
||||
|
||||
RoomTile targetTile = room.getLayout().getTile((short) x, (short) y);
|
||||
if (targetTile == null || targetTile.state == RoomTileState.INVALID) return false;
|
||||
|
||||
double targetZ = targetTile.getStackHeight() + ((targetTile.state == RoomTileState.SIT) ? -0.5 : 0);
|
||||
return WiredUserMovementHelper.moveUser(room, roomUnit, targetTile, targetZ, roomUnit.getBodyRotation(), roomUnit.getHeadRotation(), 0, true);
|
||||
}
|
||||
|
||||
private boolean moveFurniTo(Room room, HabboItem item, int x, int y, int rotation, double z) {
|
||||
if (room == null || item == null || room.getLayout() == null) return false;
|
||||
|
||||
RoomTile targetTile = room.getLayout().getTile((short) x, (short) y);
|
||||
if (targetTile == null || targetTile.state == RoomTileState.INVALID) return false;
|
||||
|
||||
FurnitureMovementError error = room.moveFurniTo(item, targetTile, rotation, z, null, true, true);
|
||||
return error == FurnitureMovementError.NONE;
|
||||
}
|
||||
|
||||
private static Integer parseInteger(String value) {
|
||||
if (value == null || value.isEmpty()) return 0;
|
||||
|
||||
try {
|
||||
return Integer.parseInt(value.trim());
|
||||
} catch (NumberFormatException ignored) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean canUseUserInternalDestination(String key) {
|
||||
return WiredInternalVariableSupport.canUseUserDestination(key);
|
||||
}
|
||||
|
||||
private static boolean canUseFurniInternalDestination(String key) {
|
||||
return WiredInternalVariableSupport.canUseFurniDestination(key);
|
||||
}
|
||||
|
||||
private static boolean canUseUserInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseUserReference(key);
|
||||
}
|
||||
|
||||
private static boolean canUseFurniInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseFurniReference(key);
|
||||
}
|
||||
|
||||
private static boolean canUseRoomInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseRoomReference(key);
|
||||
}
|
||||
|
||||
private Integer getRoomEntryMethodValue(Habbo habbo) {
|
||||
if (habbo == null || habbo.getHabboInfo() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String roomEntryMethod = habbo.getHabboInfo().getRoomEntryMethod();
|
||||
|
||||
if (roomEntryMethod == null || roomEntryMethod.trim().isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return switch (roomEntryMethod.trim().toLowerCase(Locale.ROOT)) {
|
||||
case "door" -> 1;
|
||||
case "teleport" -> 2;
|
||||
default -> 3;
|
||||
};
|
||||
}
|
||||
|
||||
static class JsonData {
|
||||
String variableName;
|
||||
int sourceTargetType;
|
||||
String sourceVariableToken;
|
||||
int sourceVariableItemId;
|
||||
String sourceVariableName;
|
||||
|
||||
JsonData(String variableName, int sourceTargetType, String sourceVariableToken, int sourceVariableItemId, String sourceVariableName) {
|
||||
this.variableName = variableName;
|
||||
this.sourceTargetType = sourceTargetType;
|
||||
this.sourceVariableToken = sourceVariableToken;
|
||||
this.sourceVariableItemId = sourceVariableItemId;
|
||||
this.sourceVariableName = sourceVariableName;
|
||||
}
|
||||
}
|
||||
|
||||
static class ConfigData {
|
||||
String variableName = "";
|
||||
int sourceTargetType = TARGET_USER;
|
||||
String sourceVariableToken = "";
|
||||
int sourceVariableItemId = 0;
|
||||
}
|
||||
|
||||
static class EditorPayload extends ConfigData {
|
||||
String sourceVariableName;
|
||||
|
||||
EditorPayload(String variableName, int sourceTargetType, String sourceVariableToken, int sourceVariableItemId, String sourceVariableName) {
|
||||
this.variableName = variableName;
|
||||
this.sourceTargetType = sourceTargetType;
|
||||
this.sourceVariableToken = sourceVariableToken;
|
||||
this.sourceVariableItemId = sourceVariableItemId;
|
||||
this.sourceVariableName = sourceVariableName;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SourceState {
|
||||
private final String name;
|
||||
private final boolean hasValue;
|
||||
private final int availability;
|
||||
private final boolean readOnly;
|
||||
|
||||
private SourceState(String name, boolean hasValue, int availability, boolean readOnly) {
|
||||
this.name = name;
|
||||
this.hasValue = hasValue;
|
||||
this.availability = availability;
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return this.hasValue;
|
||||
}
|
||||
|
||||
public int getAvailability() {
|
||||
return this.availability;
|
||||
}
|
||||
|
||||
public boolean isReadOnly() {
|
||||
return this.readOnly;
|
||||
}
|
||||
}
|
||||
}
|
||||
+757
@@ -0,0 +1,757 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.bots.Bot;
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.games.Game;
|
||||
import com.eu.habbo.habbohotel.games.GamePlayer;
|
||||
import com.eu.habbo.habbohotel.games.GameTeam;
|
||||
import com.eu.habbo.habbohotel.games.GameTeamColors;
|
||||
import com.eu.habbo.habbohotel.games.battlebanzai.BattleBanzaiGame;
|
||||
import com.eu.habbo.habbohotel.games.freeze.FreezeGame;
|
||||
import com.eu.habbo.habbohotel.games.wired.WiredGame;
|
||||
import com.eu.habbo.habbohotel.items.FurnitureType;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
|
||||
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
|
||||
import com.eu.habbo.habbohotel.pets.Pet;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||
import com.eu.habbo.habbohotel.users.DanceType;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContext;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredInternalVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
|
||||
import com.eu.habbo.util.HotelDateTimeUtil;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public abstract class WiredExtraVariableFilterBase extends InteractionWiredExtra {
|
||||
protected static final int TARGET_USER = 0;
|
||||
protected static final int TARGET_FURNI = 1;
|
||||
protected static final int TARGET_CONTEXT = 2;
|
||||
protected static final int TARGET_ROOM = 3;
|
||||
|
||||
protected static final int AMOUNT_CONSTANT = 0;
|
||||
protected static final int AMOUNT_VARIABLE = 1;
|
||||
protected static final int SOURCE_SECONDARY_SELECTED = 101;
|
||||
|
||||
protected static final int SORT_VALUE_HIGHEST = 0;
|
||||
protected static final int SORT_VALUE_LOWEST = 1;
|
||||
protected static final int SORT_CREATION_OLDEST = 2;
|
||||
protected static final int SORT_CREATION_LATEST = 3;
|
||||
protected static final int SORT_UPDATE_OLDEST = 4;
|
||||
protected static final int SORT_UPDATE_LATEST = 5;
|
||||
|
||||
private static final int MAX_FILTER_AMOUNT = 10000;
|
||||
private static final String CUSTOM_TOKEN_PREFIX = "custom:";
|
||||
private static final String INTERNAL_TOKEN_PREFIX = "internal:";
|
||||
private static final String DELIM = "\t";
|
||||
|
||||
protected int sortBy = SORT_VALUE_HIGHEST;
|
||||
protected int amountMode = AMOUNT_CONSTANT;
|
||||
protected int amountConstantValue = 1;
|
||||
protected int referenceTargetType = TARGET_USER;
|
||||
protected int referenceUserSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
protected int referenceFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
protected String variableToken = "";
|
||||
protected int variableItemId = 0;
|
||||
protected String referenceVariableToken = "";
|
||||
protected int referenceVariableItemId = 0;
|
||||
protected final List<HabboItem> referenceSelectedItems = new ArrayList<>();
|
||||
|
||||
protected WiredExtraVariableFilterBase(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
protected WiredExtraVariableFilterBase(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
protected abstract int getVariableTargetType();
|
||||
|
||||
protected abstract int getCode();
|
||||
|
||||
public List<RoomUnit> filterUsers(Room room, WiredContext ctx, Iterable<RoomUnit> values) {
|
||||
if (room == null || ctx == null || this.getVariableTargetType() != TARGET_USER || this.variableToken.isEmpty()) {
|
||||
return toUserList(values);
|
||||
}
|
||||
|
||||
int amount = this.resolveAmount(ctx, room);
|
||||
if (amount <= 0) return new ArrayList<>();
|
||||
|
||||
List<SortableEntry<RoomUnit>> matches = new ArrayList<>();
|
||||
|
||||
for (RoomUnit roomUnit : values) {
|
||||
if (roomUnit == null) continue;
|
||||
|
||||
MetricSnapshot metric = this.resolveUserMetric(room, roomUnit);
|
||||
if (metric == null) continue;
|
||||
|
||||
matches.add(new SortableEntry<>(roomUnit, metric));
|
||||
}
|
||||
|
||||
matches.sort(this.metricComparator());
|
||||
return trimUsers(matches, amount);
|
||||
}
|
||||
|
||||
public List<HabboItem> filterItems(Room room, WiredContext ctx, Iterable<HabboItem> values) {
|
||||
if (room == null || ctx == null || this.getVariableTargetType() != TARGET_FURNI || this.variableToken.isEmpty()) {
|
||||
return toItemList(values);
|
||||
}
|
||||
|
||||
int amount = this.resolveAmount(ctx, room);
|
||||
if (amount <= 0) return new ArrayList<>();
|
||||
|
||||
List<SortableEntry<HabboItem>> matches = new ArrayList<>();
|
||||
|
||||
for (HabboItem item : values) {
|
||||
if (item == null) continue;
|
||||
|
||||
MetricSnapshot metric = this.resolveFurniMetric(room, item);
|
||||
if (metric == null) continue;
|
||||
|
||||
matches.add(new SortableEntry<>(item, metric));
|
||||
}
|
||||
|
||||
matches.sort(this.metricComparator());
|
||||
return trimItems(matches, amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
if (room == null) throw new WiredSaveException("Room not found");
|
||||
|
||||
int[] params = settings.getIntParams();
|
||||
String[] stringParts = parseStringData(settings.getStringParam());
|
||||
String nextVariableToken = normalizeVariableToken((stringParts.length > 0) ? stringParts[0] : "");
|
||||
String nextReferenceVariableToken = normalizeVariableToken((stringParts.length > 1) ? stringParts[1] : "");
|
||||
int nextSortBy = normalizeSortBy(param(params, 0, SORT_VALUE_HIGHEST));
|
||||
int nextAmountMode = normalizeAmountMode(param(params, 1, AMOUNT_CONSTANT));
|
||||
int nextAmountConstantValue = normalizeAmount(param(params, 2, 1));
|
||||
int nextReferenceTargetType = normalizeReferenceTargetType(param(params, 3, TARGET_USER));
|
||||
int nextReferenceUserSource = normalizeUserSource(param(params, 4, WiredSourceUtil.SOURCE_TRIGGER));
|
||||
int nextReferenceFurniSource = normalizeReferenceFurniSource(param(params, 5, WiredSourceUtil.SOURCE_TRIGGER));
|
||||
|
||||
if (!this.isValidMainVariable(room, nextVariableToken)) throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
if (nextAmountMode == AMOUNT_VARIABLE && !this.isValidReference(room, nextReferenceTargetType, nextReferenceVariableToken)) throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
|
||||
List<HabboItem> nextReferenceItems = new ArrayList<>();
|
||||
if (nextAmountMode == AMOUNT_VARIABLE && nextReferenceTargetType == TARGET_FURNI && nextReferenceFurniSource == SOURCE_SECONDARY_SELECTED) {
|
||||
int selectionLimit = Emulator.getConfig().getInt("hotel.wired.furni.selection.count");
|
||||
if (settings.getFurniIds().length > selectionLimit) throw new WiredSaveException("Too many furni selected");
|
||||
|
||||
for (int furniId : settings.getFurniIds()) {
|
||||
HabboItem item = room.getHabboItem(furniId);
|
||||
if (item != null) nextReferenceItems.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
this.sortBy = nextSortBy;
|
||||
this.amountMode = nextAmountMode;
|
||||
this.amountConstantValue = nextAmountConstantValue;
|
||||
this.referenceTargetType = nextReferenceTargetType;
|
||||
this.referenceUserSource = nextReferenceUserSource;
|
||||
this.referenceFurniSource = nextReferenceFurniSource;
|
||||
this.setVariableToken(nextVariableToken);
|
||||
this.setReferenceVariableToken(nextReferenceVariableToken);
|
||||
this.referenceSelectedItems.clear();
|
||||
this.referenceSelectedItems.addAll(nextReferenceItems);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
this.refreshReferenceItems();
|
||||
return WiredManager.getGson().toJson(new JsonData(this.sortBy, this.amountMode, this.amountConstantValue, this.referenceTargetType, this.referenceUserSource, this.referenceFurniSource, this.variableToken, this.variableItemId, this.referenceVariableToken, this.referenceVariableItemId, this.toIds(this.referenceSelectedItems)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
this.refreshReferenceItems();
|
||||
List<HabboItem> selectedItems = new ArrayList<>();
|
||||
if (this.amountMode == AMOUNT_VARIABLE && this.referenceTargetType == TARGET_FURNI && this.referenceFurniSource == SOURCE_SECONDARY_SELECTED) {
|
||||
selectedItems.addAll(this.referenceSelectedItems);
|
||||
}
|
||||
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
|
||||
message.appendInt(selectedItems.size());
|
||||
for (HabboItem item : selectedItems) message.appendInt(item.getId());
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(this.serializeStringData());
|
||||
message.appendInt(6);
|
||||
message.appendInt(this.sortBy);
|
||||
message.appendInt(this.amountMode);
|
||||
message.appendInt(this.amountConstantValue);
|
||||
message.appendInt(this.referenceTargetType);
|
||||
message.appendInt(this.referenceUserSource);
|
||||
message.appendInt(this.referenceFurniSource);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getCode());
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
}
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty() || !wiredData.startsWith("{")) return;
|
||||
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
if (data == null) return;
|
||||
|
||||
this.sortBy = normalizeSortBy(data.sortBy);
|
||||
this.amountMode = normalizeAmountMode(data.amountMode);
|
||||
this.amountConstantValue = normalizeAmount(data.amountConstantValue);
|
||||
this.referenceTargetType = normalizeReferenceTargetType(data.referenceTargetType);
|
||||
this.referenceUserSource = normalizeUserSource(data.referenceUserSource);
|
||||
this.referenceFurniSource = normalizeReferenceFurniSource(data.referenceFurniSource);
|
||||
this.setVariableToken(normalizeVariableToken((data.variableToken != null) ? data.variableToken : ((data.variableItemId > 0) ? String.valueOf(data.variableItemId) : "")));
|
||||
this.setReferenceVariableToken(normalizeVariableToken((data.referenceVariableToken != null) ? data.referenceVariableToken : ((data.referenceVariableItemId > 0) ? String.valueOf(data.referenceVariableItemId) : "")));
|
||||
|
||||
if (room == null || data.selectedItemIds == null) return;
|
||||
|
||||
for (Integer itemId : data.selectedItemIds) {
|
||||
if (itemId == null || itemId <= 0) continue;
|
||||
|
||||
HabboItem item = room.getHabboItem(itemId);
|
||||
if (item != null) this.referenceSelectedItems.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
this.sortBy = SORT_VALUE_HIGHEST;
|
||||
this.amountMode = AMOUNT_CONSTANT;
|
||||
this.amountConstantValue = 1;
|
||||
this.referenceTargetType = TARGET_USER;
|
||||
this.referenceUserSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.referenceFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.referenceSelectedItems.clear();
|
||||
this.setVariableToken("");
|
||||
this.setReferenceVariableToken("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConfiguration() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private int resolveAmount(WiredContext ctx, Room room) {
|
||||
if (this.amountMode != AMOUNT_VARIABLE) return normalizeAmount(this.amountConstantValue);
|
||||
|
||||
Integer value = this.resolveReferenceValue(ctx, room);
|
||||
return value == null ? 0 : normalizeAmount(value);
|
||||
}
|
||||
|
||||
private Integer resolveReferenceValue(WiredContext ctx, Room room) {
|
||||
if (room == null) return null;
|
||||
|
||||
if (this.referenceTargetType == TARGET_FURNI) {
|
||||
int source = (this.referenceFurniSource == SOURCE_SECONDARY_SELECTED) ? WiredSourceUtil.SOURCE_SELECTED : this.referenceFurniSource;
|
||||
if (source == WiredSourceUtil.SOURCE_SELECTED) this.refreshReferenceItems();
|
||||
|
||||
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, source, this.referenceSelectedItems)) {
|
||||
Integer value = this.readFurniReferenceValue(room, item);
|
||||
if (value != null) return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.referenceTargetType == TARGET_CONTEXT) {
|
||||
return this.readContextReferenceValue(ctx, room);
|
||||
}
|
||||
|
||||
if (this.referenceTargetType == TARGET_ROOM) {
|
||||
return this.readRoomReferenceValue(room);
|
||||
}
|
||||
|
||||
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.referenceUserSource)) {
|
||||
Integer value = this.readUserReferenceValue(room, roomUnit);
|
||||
if (value != null) return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Integer readUserReferenceValue(Room room, RoomUnit roomUnit) {
|
||||
if (room == null || roomUnit == null) return null;
|
||||
|
||||
if (isInternalVariableToken(this.referenceVariableToken)) {
|
||||
String key = getInternalVariableKey(this.referenceVariableToken);
|
||||
return canUseUserInternalReference(key) ? this.readUserInternalValue(room, roomUnit, key) : null;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.referenceVariableItemId);
|
||||
if (definition == null || !definition.hasValue()) return null;
|
||||
|
||||
Habbo habbo = room.getHabbo(roomUnit);
|
||||
return (habbo != null) ? room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), this.referenceVariableItemId) : null;
|
||||
}
|
||||
|
||||
private Integer readFurniReferenceValue(Room room, HabboItem item) {
|
||||
if (room == null || item == null) return null;
|
||||
|
||||
if (isInternalVariableToken(this.referenceVariableToken)) {
|
||||
String key = getInternalVariableKey(this.referenceVariableToken);
|
||||
return canUseFurniInternalReference(key) ? this.readFurniInternalValue(room, item, key) : null;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.referenceVariableItemId);
|
||||
return (definition != null && definition.hasValue()) ? room.getFurniVariableManager().getCurrentValue(item.getId(), this.referenceVariableItemId) : null;
|
||||
}
|
||||
|
||||
private Integer readRoomReferenceValue(Room room) {
|
||||
if (room == null) return null;
|
||||
|
||||
if (isInternalVariableToken(this.referenceVariableToken)) {
|
||||
String key = getInternalVariableKey(this.referenceVariableToken);
|
||||
return canUseRoomInternalReference(key) ? this.readRoomInternalValue(room, key) : null;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getRoomVariableManager().getDefinitionInfo(this.referenceVariableItemId);
|
||||
return (definition != null && definition.hasValue()) ? room.getRoomVariableManager().getCurrentValue(this.referenceVariableItemId) : null;
|
||||
}
|
||||
|
||||
private Integer readContextReferenceValue(WiredContext ctx, Room room) {
|
||||
if (ctx == null) return null;
|
||||
|
||||
if (isInternalVariableToken(this.referenceVariableToken)) {
|
||||
String key = getInternalVariableKey(this.referenceVariableToken);
|
||||
return canUseContextInternalReference(key) ? WiredInternalVariableSupport.readContextValue(ctx, key) : null;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, this.referenceVariableItemId);
|
||||
if (definition == null || !definition.hasValue() || !WiredContextVariableSupport.hasVariable(ctx, this.referenceVariableItemId)) return null;
|
||||
|
||||
return WiredContextVariableSupport.getCurrentValue(ctx, this.referenceVariableItemId);
|
||||
}
|
||||
|
||||
private MetricSnapshot resolveUserMetric(Room room, RoomUnit roomUnit) {
|
||||
if (room == null || roomUnit == null) return null;
|
||||
|
||||
if (isInternalVariableToken(this.variableToken)) {
|
||||
String key = getInternalVariableKey(this.variableToken);
|
||||
Integer value = canUseUserInternalReference(key) ? this.readUserInternalValue(room, roomUnit, key) : null;
|
||||
return (value != null) ? new MetricSnapshot(roomUnit.getId(), value, 0, 0) : null;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.variableItemId);
|
||||
Habbo habbo = room.getHabbo(roomUnit);
|
||||
if (definition == null || habbo == null) return null;
|
||||
if (!room.getUserVariableManager().hasVariable(habbo.getHabboInfo().getId(), this.variableItemId)) return null;
|
||||
|
||||
return new MetricSnapshot(
|
||||
roomUnit.getId(),
|
||||
definition.hasValue() ? room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), this.variableItemId) : 0,
|
||||
room.getUserVariableManager().getCreatedAt(habbo.getHabboInfo().getId(), this.variableItemId),
|
||||
room.getUserVariableManager().getUpdatedAt(habbo.getHabboInfo().getId(), this.variableItemId));
|
||||
}
|
||||
|
||||
private MetricSnapshot resolveFurniMetric(Room room, HabboItem item) {
|
||||
if (room == null || item == null) return null;
|
||||
|
||||
if (isInternalVariableToken(this.variableToken)) {
|
||||
String key = getInternalVariableKey(this.variableToken);
|
||||
Integer value = canUseFurniInternalReference(key) ? this.readFurniInternalValue(room, item, key) : null;
|
||||
return (value != null) ? new MetricSnapshot(item.getId(), value, 0, 0) : null;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.variableItemId);
|
||||
if (definition == null) return null;
|
||||
if (!room.getFurniVariableManager().hasVariable(item.getId(), this.variableItemId)) return null;
|
||||
|
||||
return new MetricSnapshot(
|
||||
item.getId(),
|
||||
definition.hasValue() ? room.getFurniVariableManager().getCurrentValue(item.getId(), this.variableItemId) : 0,
|
||||
room.getFurniVariableManager().getCreatedAt(item.getId(), this.variableItemId),
|
||||
room.getFurniVariableManager().getUpdatedAt(item.getId(), this.variableItemId));
|
||||
}
|
||||
|
||||
private Comparator<SortableEntry<?>> metricComparator() {
|
||||
return switch (this.sortBy) {
|
||||
case SORT_VALUE_LOWEST -> Comparator.comparingInt((SortableEntry<?> entry) -> entry.metric.value).thenComparingInt(entry -> entry.metric.entityId);
|
||||
case SORT_CREATION_OLDEST -> Comparator.comparingInt((SortableEntry<?> entry) -> entry.metric.createdAt).thenComparingInt(entry -> entry.metric.entityId);
|
||||
case SORT_CREATION_LATEST -> Comparator.<SortableEntry<?>, Integer>comparing(entry -> entry.metric.createdAt).reversed().thenComparingInt(entry -> entry.metric.entityId);
|
||||
case SORT_UPDATE_OLDEST -> Comparator.comparingInt((SortableEntry<?> entry) -> entry.metric.updatedAt).thenComparingInt(entry -> entry.metric.entityId);
|
||||
case SORT_UPDATE_LATEST -> Comparator.<SortableEntry<?>, Integer>comparing(entry -> entry.metric.updatedAt).reversed().thenComparingInt(entry -> entry.metric.entityId);
|
||||
default -> Comparator.<SortableEntry<?>, Integer>comparing(entry -> entry.metric.value).reversed().thenComparingInt(entry -> entry.metric.entityId);
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isValidMainVariable(Room room, String token) {
|
||||
if (token == null || token.isEmpty()) return false;
|
||||
|
||||
if (isInternalVariableToken(token)) {
|
||||
String key = getInternalVariableKey(token);
|
||||
return this.getVariableTargetType() == TARGET_FURNI ? canUseFurniInternalReference(key) : canUseUserInternalReference(key);
|
||||
}
|
||||
|
||||
if (this.getVariableTargetType() == TARGET_FURNI) {
|
||||
return room != null && room.getFurniVariableManager().getDefinitionInfo(getCustomItemId(token)) != null;
|
||||
}
|
||||
|
||||
return room != null && room.getUserVariableManager().getDefinitionInfo(getCustomItemId(token)) != null;
|
||||
}
|
||||
|
||||
private boolean isValidReference(Room room, int targetType, String token) {
|
||||
if (token == null || token.isEmpty()) return false;
|
||||
|
||||
if (isInternalVariableToken(token)) {
|
||||
String key = getInternalVariableKey(token);
|
||||
return switch (targetType) {
|
||||
case TARGET_FURNI -> canUseFurniInternalReference(key);
|
||||
case TARGET_CONTEXT -> canUseContextInternalReference(key);
|
||||
case TARGET_ROOM -> canUseRoomInternalReference(key);
|
||||
default -> canUseUserInternalReference(key);
|
||||
};
|
||||
}
|
||||
|
||||
return switch (targetType) {
|
||||
case TARGET_FURNI -> {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getFurniVariableManager().getDefinitionInfo(getCustomItemId(token)) : null;
|
||||
yield definition != null && definition.hasValue();
|
||||
}
|
||||
case TARGET_CONTEXT -> this.isValidContextCustomReference(room, getCustomItemId(token));
|
||||
case TARGET_ROOM -> {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getRoomVariableManager().getDefinitionInfo(getCustomItemId(token)) : null;
|
||||
yield definition != null && definition.hasValue();
|
||||
}
|
||||
default -> {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getUserVariableManager().getDefinitionInfo(getCustomItemId(token)) : null;
|
||||
yield definition != null && definition.hasValue();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isValidContextCustomReference(Room room, int variableItemId) {
|
||||
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, variableItemId);
|
||||
return definition != null && definition.hasValue();
|
||||
}
|
||||
private static List<RoomUnit> trimUsers(List<SortableEntry<RoomUnit>> matches, int amount) {
|
||||
List<RoomUnit> result = new ArrayList<>();
|
||||
for (SortableEntry<RoomUnit> match : matches) {
|
||||
if (result.size() >= amount) break;
|
||||
result.add(match.entity);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<HabboItem> trimItems(List<SortableEntry<HabboItem>> matches, int amount) {
|
||||
List<HabboItem> result = new ArrayList<>();
|
||||
for (SortableEntry<HabboItem> match : matches) {
|
||||
if (result.size() >= amount) break;
|
||||
result.add(match.entity);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<RoomUnit> toUserList(Iterable<RoomUnit> values) {
|
||||
List<RoomUnit> result = new ArrayList<>();
|
||||
if (values == null) return result;
|
||||
for (RoomUnit value : values) if (value != null) result.add(value);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<HabboItem> toItemList(Iterable<HabboItem> values) {
|
||||
List<HabboItem> result = new ArrayList<>();
|
||||
if (values == null) return result;
|
||||
for (HabboItem value : values) if (value != null) result.add(value);
|
||||
return result;
|
||||
}
|
||||
|
||||
private String serializeStringData() {
|
||||
return (this.variableToken == null ? "" : this.variableToken) + DELIM + (this.referenceVariableToken == null ? "" : this.referenceVariableToken);
|
||||
}
|
||||
|
||||
private void refreshReferenceItems() {
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
if (room == null) {
|
||||
this.referenceSelectedItems.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
this.referenceSelectedItems.removeIf(item -> item == null || item.getRoomId() != room.getId() || room.getHabboItem(item.getId()) == null);
|
||||
}
|
||||
|
||||
private void setVariableToken(String token) {
|
||||
this.variableToken = normalizeVariableToken(token);
|
||||
this.variableItemId = getCustomItemId(this.variableToken);
|
||||
}
|
||||
|
||||
private void setReferenceVariableToken(String token) {
|
||||
this.referenceVariableToken = normalizeVariableToken(token);
|
||||
this.referenceVariableItemId = getCustomItemId(this.referenceVariableToken);
|
||||
}
|
||||
|
||||
private List<Integer> toIds(List<HabboItem> items) {
|
||||
List<Integer> ids = new ArrayList<>();
|
||||
for (HabboItem item : items) if (item != null) ids.add(item.getId());
|
||||
return ids;
|
||||
}
|
||||
|
||||
private Integer readUserInternalValue(Room room, RoomUnit roomUnit, String key) {
|
||||
return WiredInternalVariableSupport.readUserValue(room, roomUnit, key);
|
||||
}
|
||||
|
||||
private Integer readFurniInternalValue(Room room, HabboItem item, String key) {
|
||||
return WiredInternalVariableSupport.readFurniValue(room, item, key);
|
||||
}
|
||||
|
||||
private Integer readRoomInternalValue(Room room, String key) {
|
||||
return WiredInternalVariableSupport.readRoomValue(room, key);
|
||||
}
|
||||
private int getUserTeamScore(Room room, Habbo habbo) {
|
||||
if (room == null || habbo == null || habbo.getHabboInfo() == null || habbo.getHabboInfo().getGamePlayer() == null) return 0;
|
||||
|
||||
Game game = this.resolveTeamGame(room, habbo);
|
||||
if (game == null) return 0;
|
||||
|
||||
GamePlayer player = habbo.getHabboInfo().getGamePlayer();
|
||||
return player.getScore();
|
||||
}
|
||||
|
||||
private int getTeamColorId(int effectValue) {
|
||||
TeamEffectData effectData = this.getTeamEffectData(effectValue);
|
||||
return (effectData != null) ? effectData.colorId : 0;
|
||||
}
|
||||
|
||||
private int getTeamTypeId(int effectValue) {
|
||||
TeamEffectData effectData = this.getTeamEffectData(effectValue);
|
||||
return (effectData != null) ? effectData.typeId : 0;
|
||||
}
|
||||
|
||||
private int getTeamMetric(Room room, GameTeamColors color, boolean score) {
|
||||
Game game = room.getGame(WiredGame.class);
|
||||
if (game == null) game = room.getGame(FreezeGame.class);
|
||||
if (game == null) game = room.getGame(BattleBanzaiGame.class);
|
||||
if (game == null) return 0;
|
||||
|
||||
GameTeam team = game.getTeam(color);
|
||||
if (team == null) return 0;
|
||||
|
||||
return score ? team.getTotalScore() : team.getMembers().size();
|
||||
}
|
||||
|
||||
private Game resolveTeamGame(Room room, Habbo habbo) {
|
||||
if (room == null) return null;
|
||||
|
||||
if (habbo != null && habbo.getHabboInfo() != null && habbo.getHabboInfo().getCurrentGame() != null) {
|
||||
Game game = room.getGame(habbo.getHabboInfo().getCurrentGame());
|
||||
if (game != null) return game;
|
||||
}
|
||||
|
||||
Game wiredGame = room.getGame(WiredGame.class);
|
||||
if (wiredGame != null) return wiredGame;
|
||||
|
||||
Game freezeGame = room.getGame(FreezeGame.class);
|
||||
if (freezeGame != null) return freezeGame;
|
||||
|
||||
return room.getGame(BattleBanzaiGame.class);
|
||||
}
|
||||
|
||||
private TeamEffectData getTeamEffectData(int effectValue) {
|
||||
if (effectValue <= 0) return null;
|
||||
|
||||
if (effectValue >= 223 && effectValue <= 226) return new TeamEffectData(effectValue - 222, 0);
|
||||
if (effectValue >= 33 && effectValue <= 36) return new TeamEffectData(effectValue - 32, 1);
|
||||
if (effectValue >= 40 && effectValue <= 43) return new TeamEffectData(effectValue - 39, 2);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int normalizeSortBy(int value) {
|
||||
return switch (value) {
|
||||
case SORT_VALUE_LOWEST, SORT_CREATION_OLDEST, SORT_CREATION_LATEST, SORT_UPDATE_OLDEST, SORT_UPDATE_LATEST -> value;
|
||||
default -> SORT_VALUE_HIGHEST;
|
||||
};
|
||||
}
|
||||
|
||||
private static int normalizeAmountMode(int value) {
|
||||
return (value == AMOUNT_VARIABLE) ? AMOUNT_VARIABLE : AMOUNT_CONSTANT;
|
||||
}
|
||||
|
||||
private static int normalizeReferenceTargetType(int value) {
|
||||
return switch (value) {
|
||||
case TARGET_FURNI, TARGET_CONTEXT, TARGET_ROOM -> value;
|
||||
default -> TARGET_USER;
|
||||
};
|
||||
}
|
||||
|
||||
private static int normalizeUserSource(int value) {
|
||||
return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
|
||||
}
|
||||
|
||||
private static int normalizeReferenceFurniSource(int value) {
|
||||
return switch (value) {
|
||||
case SOURCE_SECONDARY_SELECTED, WiredSourceUtil.SOURCE_SELECTOR, WiredSourceUtil.SOURCE_SIGNAL -> value;
|
||||
default -> WiredSourceUtil.SOURCE_TRIGGER;
|
||||
};
|
||||
}
|
||||
|
||||
protected static String normalizeVariableToken(String token) {
|
||||
if (token == null) return "";
|
||||
|
||||
String normalized = token.trim();
|
||||
if (normalized.isEmpty()) return "";
|
||||
if (normalized.startsWith(INTERNAL_TOKEN_PREFIX)) {
|
||||
return INTERNAL_TOKEN_PREFIX + WiredInternalVariableSupport.normalizeKey(normalized.substring(INTERNAL_TOKEN_PREFIX.length()));
|
||||
}
|
||||
if (isCustomVariableToken(normalized) || isInternalVariableToken(normalized)) return normalized;
|
||||
|
||||
try {
|
||||
int parsed = Integer.parseInt(normalized);
|
||||
return (parsed > 0) ? (CUSTOM_TOKEN_PREFIX + parsed) : "";
|
||||
} catch (NumberFormatException e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
protected static boolean isCustomVariableToken(String token) {
|
||||
return token != null && token.startsWith(CUSTOM_TOKEN_PREFIX);
|
||||
}
|
||||
|
||||
protected static boolean isInternalVariableToken(String token) {
|
||||
return token != null && token.startsWith(INTERNAL_TOKEN_PREFIX);
|
||||
}
|
||||
|
||||
protected static int getCustomItemId(String token) {
|
||||
if (!isCustomVariableToken(token)) return 0;
|
||||
|
||||
try {
|
||||
return Integer.parseInt(token.substring(CUSTOM_TOKEN_PREFIX.length()));
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected static String getInternalVariableKey(String token) {
|
||||
return isInternalVariableToken(token) ? WiredInternalVariableSupport.normalizeKey(token.substring(INTERNAL_TOKEN_PREFIX.length())) : "";
|
||||
}
|
||||
|
||||
private static boolean canUseUserInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseUserReference(key);
|
||||
}
|
||||
|
||||
private static boolean canUseFurniInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseFurniReference(key);
|
||||
}
|
||||
|
||||
private static boolean canUseRoomInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseRoomReference(key);
|
||||
}
|
||||
|
||||
private static boolean canUseContextInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseContextReference(key);
|
||||
}
|
||||
|
||||
private static int param(int[] params, int index, int fallback) {
|
||||
return (params != null && params.length > index) ? params[index] : fallback;
|
||||
}
|
||||
|
||||
private static String[] parseStringData(String value) {
|
||||
return (value == null || value.isEmpty()) ? new String[0] : value.split("\\t", -1);
|
||||
}
|
||||
|
||||
private static int parseInteger(String value) {
|
||||
try {
|
||||
return (value == null || value.trim().isEmpty()) ? 0 : Integer.parseInt(value.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static int normalizeAmount(int value) {
|
||||
return Math.max(0, Math.min(MAX_FILTER_AMOUNT, value));
|
||||
}
|
||||
|
||||
protected static class JsonData {
|
||||
int sortBy;
|
||||
int amountMode;
|
||||
int amountConstantValue;
|
||||
int referenceTargetType;
|
||||
int referenceUserSource;
|
||||
int referenceFurniSource;
|
||||
String variableToken;
|
||||
int variableItemId;
|
||||
String referenceVariableToken;
|
||||
int referenceVariableItemId;
|
||||
List<Integer> selectedItemIds;
|
||||
|
||||
JsonData(int sortBy, int amountMode, int amountConstantValue, int referenceTargetType, int referenceUserSource, int referenceFurniSource, String variableToken, int variableItemId, String referenceVariableToken, int referenceVariableItemId, List<Integer> selectedItemIds) {
|
||||
this.sortBy = sortBy;
|
||||
this.amountMode = amountMode;
|
||||
this.amountConstantValue = amountConstantValue;
|
||||
this.referenceTargetType = referenceTargetType;
|
||||
this.referenceUserSource = referenceUserSource;
|
||||
this.referenceFurniSource = referenceFurniSource;
|
||||
this.variableToken = variableToken;
|
||||
this.variableItemId = variableItemId;
|
||||
this.referenceVariableToken = referenceVariableToken;
|
||||
this.referenceVariableItemId = referenceVariableItemId;
|
||||
this.selectedItemIds = selectedItemIds;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SortableEntry<T> {
|
||||
final T entity;
|
||||
final MetricSnapshot metric;
|
||||
|
||||
SortableEntry(T entity, MetricSnapshot metric) {
|
||||
this.entity = entity;
|
||||
this.metric = metric;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MetricSnapshot {
|
||||
final int entityId;
|
||||
final int value;
|
||||
final int createdAt;
|
||||
final int updatedAt;
|
||||
|
||||
MetricSnapshot(int entityId, int value, int createdAt, int updatedAt) {
|
||||
this.entityId = entityId;
|
||||
this.value = value;
|
||||
this.createdAt = createdAt;
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TeamEffectData {
|
||||
final int colorId;
|
||||
final int typeId;
|
||||
|
||||
TeamEffectData(int colorId, int typeId) {
|
||||
this.colorId = colorId;
|
||||
this.typeId = typeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
|
||||
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
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.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class WiredExtraVariableLevelUpSystem extends InteractionWiredExtra {
|
||||
public static final int CODE = 82;
|
||||
|
||||
public static final int MODE_LINEAR = 1;
|
||||
public static final int MODE_EXPONENTIAL = 2;
|
||||
public static final int MODE_MANUAL = 3;
|
||||
|
||||
public static final int SUB_CURRENT_LEVEL = 0;
|
||||
public static final int SUB_CURRENT_XP = 1;
|
||||
public static final int SUB_LEVEL_PROGRESS = 2;
|
||||
public static final int SUB_LEVEL_PROGRESS_PERCENT = 3;
|
||||
public static final int SUB_TOTAL_XP_REQUIRED = 4;
|
||||
public static final int SUB_XP_REMAINING = 5;
|
||||
public static final int SUB_IS_AT_MAX = 6;
|
||||
public static final int SUB_MAX_LEVEL = 7;
|
||||
public static final int SUBVARIABLE_COUNT = 8;
|
||||
|
||||
private static final int DEFAULT_STEP_SIZE = 100;
|
||||
private static final int DEFAULT_MAX_LEVEL = 10;
|
||||
private static final int DEFAULT_FIRST_LEVEL_XP = 100;
|
||||
private static final int DEFAULT_INCREASE_FACTOR = 100;
|
||||
private static final int MAX_MANUAL_TEXT_LENGTH = 4096;
|
||||
|
||||
private int mode = MODE_LINEAR;
|
||||
private int stepSize = DEFAULT_STEP_SIZE;
|
||||
private int maxLevel = DEFAULT_MAX_LEVEL;
|
||||
private int firstLevelXp = DEFAULT_FIRST_LEVEL_XP;
|
||||
private int increaseFactor = DEFAULT_INCREASE_FACTOR;
|
||||
private String interpolationText = "";
|
||||
private int subvariableMask = (1 << SUB_CURRENT_LEVEL) | (1 << SUB_CURRENT_XP);
|
||||
|
||||
public WiredExtraVariableLevelUpSystem(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredExtraVariableLevelUpSystem(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings, GameClient gameClient) {
|
||||
this.applyConfig(parseJsonData(settings.getStringParam()));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
return WiredManager.getGson().toJson(new JsonData(
|
||||
this.mode,
|
||||
this.stepSize,
|
||||
this.maxLevel,
|
||||
this.firstLevelXp,
|
||||
this.increaseFactor,
|
||||
this.interpolationText,
|
||||
this.getSelectedSubvariables()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(this.getWiredData());
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
message.appendInt(CODE);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.applyConfig(parseJsonData(wiredData));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
this.mode = MODE_LINEAR;
|
||||
this.stepSize = DEFAULT_STEP_SIZE;
|
||||
this.maxLevel = DEFAULT_MAX_LEVEL;
|
||||
this.firstLevelXp = DEFAULT_FIRST_LEVEL_XP;
|
||||
this.increaseFactor = DEFAULT_INCREASE_FACTOR;
|
||||
this.interpolationText = "";
|
||||
this.subvariableMask = (1 << SUB_CURRENT_LEVEL) | (1 << SUB_CURRENT_XP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConfiguration() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
public int getStepSize() {
|
||||
return this.stepSize;
|
||||
}
|
||||
|
||||
public int getMaxLevel() {
|
||||
return this.maxLevel;
|
||||
}
|
||||
|
||||
public int getFirstLevelXp() {
|
||||
return this.firstLevelXp;
|
||||
}
|
||||
|
||||
public int getIncreaseFactor() {
|
||||
return this.increaseFactor;
|
||||
}
|
||||
|
||||
public String getInterpolationText() {
|
||||
return this.interpolationText;
|
||||
}
|
||||
|
||||
public boolean hasSubvariable(int subvariableType) {
|
||||
return subvariableType >= 0
|
||||
&& subvariableType < SUBVARIABLE_COUNT
|
||||
&& ((this.subvariableMask & (1 << subvariableType)) != 0);
|
||||
}
|
||||
|
||||
public List<Integer> getSelectedSubvariables() {
|
||||
List<Integer> result = new ArrayList<>();
|
||||
|
||||
for (int index = 0; index < SUBVARIABLE_COUNT; index++) {
|
||||
if (this.hasSubvariable(index)) {
|
||||
result.add(index);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void applyConfig(JsonData data) {
|
||||
if (data == null) {
|
||||
this.onPickUp();
|
||||
return;
|
||||
}
|
||||
|
||||
this.mode = normalizeMode(data.mode);
|
||||
this.stepSize = normalizeNonNegative(data.stepSize, DEFAULT_STEP_SIZE);
|
||||
this.maxLevel = normalizeMaxLevel(data.maxLevel);
|
||||
this.firstLevelXp = normalizeNonNegative(data.firstLevelXp, DEFAULT_FIRST_LEVEL_XP);
|
||||
this.increaseFactor = normalizeNonNegative(data.increaseFactor, DEFAULT_INCREASE_FACTOR);
|
||||
this.interpolationText = normalizeInterpolationText(data.interpolationText);
|
||||
this.subvariableMask = normalizeSubvariableMask(data.subvariables);
|
||||
}
|
||||
|
||||
private static JsonData parseJsonData(String value) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
return new JsonData();
|
||||
}
|
||||
|
||||
try {
|
||||
if (value.trim().startsWith("{")) {
|
||||
JsonData data = WiredManager.getGson().fromJson(value, JsonData.class);
|
||||
return (data != null) ? data : new JsonData();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
JsonData fallback = new JsonData();
|
||||
fallback.interpolationText = normalizeInterpolationText(value);
|
||||
fallback.mode = MODE_MANUAL;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
private static int normalizeMode(int value) {
|
||||
return switch (value) {
|
||||
case MODE_EXPONENTIAL, MODE_MANUAL -> value;
|
||||
default -> MODE_LINEAR;
|
||||
};
|
||||
}
|
||||
|
||||
private static int normalizeNonNegative(int value, int fallback) {
|
||||
return Math.max(0, (value > 0) ? value : fallback);
|
||||
}
|
||||
|
||||
private static int normalizeMaxLevel(int value) {
|
||||
return Math.max(1, (value > 0) ? value : DEFAULT_MAX_LEVEL);
|
||||
}
|
||||
|
||||
private static String normalizeInterpolationText(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String normalized = value.replace("\r", "");
|
||||
if (normalized.length() > MAX_MANUAL_TEXT_LENGTH) {
|
||||
normalized = normalized.substring(0, MAX_MANUAL_TEXT_LENGTH);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static int normalizeSubvariableMask(List<Integer> subvariables) {
|
||||
if (subvariables == null) {
|
||||
return (1 << SUB_CURRENT_LEVEL) | (1 << SUB_CURRENT_XP);
|
||||
}
|
||||
|
||||
int mask = 0;
|
||||
for (Integer subvariable : subvariables) {
|
||||
if (subvariable == null || subvariable < 0 || subvariable >= SUBVARIABLE_COUNT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
mask |= (1 << subvariable);
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
static class JsonData {
|
||||
int mode = MODE_LINEAR;
|
||||
int stepSize = DEFAULT_STEP_SIZE;
|
||||
int maxLevel = DEFAULT_MAX_LEVEL;
|
||||
int firstLevelXp = DEFAULT_FIRST_LEVEL_XP;
|
||||
int increaseFactor = DEFAULT_INCREASE_FACTOR;
|
||||
String interpolationText = "";
|
||||
List<Integer> subvariables = null;
|
||||
|
||||
JsonData() {
|
||||
}
|
||||
|
||||
JsonData(int mode, int stepSize, int maxLevel, int firstLevelXp, int increaseFactor, String interpolationText, List<Integer> subvariables) {
|
||||
this.mode = mode;
|
||||
this.stepSize = stepSize;
|
||||
this.maxLevel = maxLevel;
|
||||
this.firstLevelXp = firstLevelXp;
|
||||
this.increaseFactor = increaseFactor;
|
||||
this.interpolationText = interpolationText;
|
||||
this.subvariables = subvariables;
|
||||
}
|
||||
}
|
||||
}
|
||||
+321
@@ -0,0 +1,321 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
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.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class WiredExtraVariableReference extends InteractionWiredExtra {
|
||||
public static final int CODE = 81;
|
||||
|
||||
private String variableName = "";
|
||||
private int sourceRoomId = 0;
|
||||
private String sourceRoomName = "";
|
||||
private int sourceVariableItemId = 0;
|
||||
private String sourceVariableName = "";
|
||||
private int sourceTargetType = WiredVariableReferenceSupport.TARGET_USER;
|
||||
private boolean hasValue = false;
|
||||
private boolean readOnly = true;
|
||||
|
||||
public WiredExtraVariableReference(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredExtraVariableReference(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
|
||||
if (room == null) {
|
||||
throw new WiredSaveException("Room not found");
|
||||
}
|
||||
|
||||
ConfigData config = parseConfigData(settings.getStringParam());
|
||||
String normalizedName = WiredVariableNameValidator.normalizeForSave(config.variableName);
|
||||
|
||||
WiredVariableNameValidator.validateDefinitionName(room, this.getId(), normalizedName);
|
||||
|
||||
if (config.sourceRoomId <= 0 || config.sourceVariableItemId <= 0) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.missing_variable");
|
||||
}
|
||||
|
||||
WiredVariableReferenceSupport.SharedDefinitionOption definition = WiredVariableReferenceSupport.findSharedDefinition(
|
||||
room,
|
||||
config.sourceRoomId,
|
||||
config.sourceVariableItemId,
|
||||
config.sourceTargetType
|
||||
);
|
||||
|
||||
if (definition == null) {
|
||||
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
|
||||
this.variableName = normalizedName;
|
||||
this.sourceRoomId = definition.getRoomId();
|
||||
this.sourceRoomName = sanitizeLabel(definition.getRoomName());
|
||||
this.sourceVariableItemId = definition.getItemId();
|
||||
this.sourceVariableName = definition.getName();
|
||||
this.sourceTargetType = definition.getTargetType();
|
||||
this.hasValue = definition.hasValue();
|
||||
this.readOnly = config.readOnly;
|
||||
|
||||
room.getUserVariableManager().broadcastSnapshot();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
return WiredManager.getGson().toJson(new JsonData(
|
||||
this.variableName,
|
||||
this.sourceRoomId,
|
||||
this.sourceRoomName,
|
||||
this.sourceVariableItemId,
|
||||
this.sourceVariableName,
|
||||
this.sourceTargetType,
|
||||
this.hasValue,
|
||||
this.readOnly
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(buildEditorPayload(room));
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
message.appendInt(CODE);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty() || !wiredData.startsWith("{")) {
|
||||
return;
|
||||
}
|
||||
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.variableName = WiredVariableNameValidator.normalizeLegacy(data.variableName);
|
||||
this.sourceRoomId = Math.max(0, data.sourceRoomId);
|
||||
this.sourceRoomName = sanitizeLabel(data.sourceRoomName);
|
||||
this.sourceVariableItemId = Math.max(0, data.sourceVariableItemId);
|
||||
this.sourceVariableName = WiredVariableNameValidator.normalizeLegacy(data.sourceVariableName);
|
||||
this.sourceTargetType = normalizeTargetType(data.sourceTargetType);
|
||||
this.hasValue = data.hasValue;
|
||||
this.readOnly = data.readOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
this.variableName = "";
|
||||
this.sourceRoomId = 0;
|
||||
this.sourceRoomName = "";
|
||||
this.sourceVariableItemId = 0;
|
||||
this.sourceVariableName = "";
|
||||
this.sourceTargetType = WiredVariableReferenceSupport.TARGET_USER;
|
||||
this.hasValue = false;
|
||||
this.readOnly = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConfiguration() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getVariableName() {
|
||||
return this.variableName;
|
||||
}
|
||||
|
||||
public int getSourceRoomId() {
|
||||
return this.sourceRoomId;
|
||||
}
|
||||
|
||||
public String getSourceRoomName() {
|
||||
return this.sourceRoomName;
|
||||
}
|
||||
|
||||
public int getSourceVariableItemId() {
|
||||
return this.sourceVariableItemId;
|
||||
}
|
||||
|
||||
public String getSourceVariableName() {
|
||||
return this.sourceVariableName;
|
||||
}
|
||||
|
||||
public int getSourceTargetType() {
|
||||
return this.sourceTargetType;
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return this.hasValue;
|
||||
}
|
||||
|
||||
public boolean isReadOnly() {
|
||||
return this.readOnly;
|
||||
}
|
||||
|
||||
public int getAvailability() {
|
||||
return WiredVariableReferenceSupport.SHARED_AVAILABILITY;
|
||||
}
|
||||
|
||||
public boolean isUserReference() {
|
||||
return this.sourceTargetType == WiredVariableReferenceSupport.TARGET_USER;
|
||||
}
|
||||
|
||||
public boolean isRoomReference() {
|
||||
return this.sourceTargetType == WiredVariableReferenceSupport.TARGET_ROOM;
|
||||
}
|
||||
|
||||
private String buildEditorPayload(Room room) {
|
||||
List<RoomEditorData> roomOptions = new ArrayList<>();
|
||||
|
||||
for (WiredVariableReferenceSupport.RoomOption option : WiredVariableReferenceSupport.loadRoomOptions(room)) {
|
||||
List<VariableEditorData> variables = new ArrayList<>();
|
||||
|
||||
for (WiredVariableReferenceSupport.SharedDefinitionOption definition : option.getVariables()) {
|
||||
variables.add(new VariableEditorData(definition.getItemId(), definition.getName(), definition.getTargetType(), definition.hasValue()));
|
||||
}
|
||||
|
||||
roomOptions.add(new RoomEditorData(option.getRoomId(), option.getRoomName(), variables));
|
||||
}
|
||||
|
||||
return WiredManager.getGson().toJson(new EditorPayload(
|
||||
this.variableName,
|
||||
this.sourceRoomId,
|
||||
this.sourceRoomName,
|
||||
this.sourceVariableItemId,
|
||||
this.sourceVariableName,
|
||||
this.sourceTargetType,
|
||||
this.readOnly,
|
||||
roomOptions
|
||||
));
|
||||
}
|
||||
|
||||
private static ConfigData parseConfigData(String value) {
|
||||
if (value == null || value.isEmpty() || !value.startsWith("{")) {
|
||||
return new ConfigData();
|
||||
}
|
||||
|
||||
ConfigData config = WiredManager.getGson().fromJson(value, ConfigData.class);
|
||||
return (config != null) ? config : new ConfigData();
|
||||
}
|
||||
|
||||
private static int normalizeTargetType(int value) {
|
||||
return (value == WiredVariableReferenceSupport.TARGET_ROOM) ? WiredVariableReferenceSupport.TARGET_ROOM : WiredVariableReferenceSupport.TARGET_USER;
|
||||
}
|
||||
|
||||
private static String sanitizeLabel(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return value.trim().replace("\t", "").replace("\r", "").replace("\n", "");
|
||||
}
|
||||
|
||||
static class JsonData {
|
||||
String variableName;
|
||||
int sourceRoomId;
|
||||
String sourceRoomName;
|
||||
int sourceVariableItemId;
|
||||
String sourceVariableName;
|
||||
int sourceTargetType;
|
||||
boolean hasValue;
|
||||
boolean readOnly;
|
||||
|
||||
JsonData(String variableName, int sourceRoomId, String sourceRoomName, int sourceVariableItemId, String sourceVariableName, int sourceTargetType, boolean hasValue, boolean readOnly) {
|
||||
this.variableName = variableName;
|
||||
this.sourceRoomId = sourceRoomId;
|
||||
this.sourceRoomName = sourceRoomName;
|
||||
this.sourceVariableItemId = sourceVariableItemId;
|
||||
this.sourceVariableName = sourceVariableName;
|
||||
this.sourceTargetType = sourceTargetType;
|
||||
this.hasValue = hasValue;
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
}
|
||||
|
||||
static class ConfigData {
|
||||
String variableName = "";
|
||||
int sourceRoomId = 0;
|
||||
int sourceVariableItemId = 0;
|
||||
int sourceTargetType = WiredVariableReferenceSupport.TARGET_USER;
|
||||
boolean readOnly = true;
|
||||
}
|
||||
|
||||
static class EditorPayload extends ConfigData {
|
||||
String sourceRoomName;
|
||||
String sourceVariableName;
|
||||
List<RoomEditorData> rooms;
|
||||
|
||||
EditorPayload(String variableName, int sourceRoomId, String sourceRoomName, int sourceVariableItemId, String sourceVariableName, int sourceTargetType, boolean readOnly, List<RoomEditorData> rooms) {
|
||||
this.variableName = variableName;
|
||||
this.sourceRoomId = sourceRoomId;
|
||||
this.sourceRoomName = sourceRoomName;
|
||||
this.sourceVariableItemId = sourceVariableItemId;
|
||||
this.sourceVariableName = sourceVariableName;
|
||||
this.sourceTargetType = sourceTargetType;
|
||||
this.readOnly = readOnly;
|
||||
this.rooms = rooms;
|
||||
}
|
||||
}
|
||||
|
||||
static class RoomEditorData {
|
||||
int roomId;
|
||||
String roomName;
|
||||
List<VariableEditorData> variables;
|
||||
|
||||
RoomEditorData(int roomId, String roomName, List<VariableEditorData> variables) {
|
||||
this.roomId = roomId;
|
||||
this.roomName = roomName;
|
||||
this.variables = variables;
|
||||
}
|
||||
}
|
||||
|
||||
static class VariableEditorData {
|
||||
int itemId;
|
||||
String name;
|
||||
int targetType;
|
||||
boolean hasValue;
|
||||
|
||||
VariableEditorData(int itemId, String name, int targetType, boolean hasValue) {
|
||||
this.itemId = itemId;
|
||||
this.name = name;
|
||||
this.targetType = targetType;
|
||||
this.hasValue = hasValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
|
||||
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
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.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class WiredExtraVariableTextConnector extends InteractionWiredExtra {
|
||||
public static final int CODE = 79;
|
||||
private static final int MAX_MAPPING_LENGTH = 4096;
|
||||
|
||||
private String mappingsText = "";
|
||||
private LinkedHashMap<Integer, String> mappings = new LinkedHashMap<>();
|
||||
|
||||
public WiredExtraVariableTextConnector(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredExtraVariableTextConnector(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings, GameClient gameClient) {
|
||||
this.setMappingsText(settings.getStringParam());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
return WiredManager.getGson().toJson(new JsonData(this.mappingsText));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(this.mappingsText);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
message.appendInt(CODE);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wiredData.startsWith("{")) {
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
|
||||
if (data != null) {
|
||||
this.setMappingsText(data.mappingsText);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.setMappingsText(wiredData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
this.mappingsText = "";
|
||||
this.mappings = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConfiguration() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getMappingsText() {
|
||||
return this.mappingsText;
|
||||
}
|
||||
|
||||
public Map<Integer, String> getMappings() {
|
||||
return Collections.unmodifiableMap(this.mappings);
|
||||
}
|
||||
|
||||
public String resolveText(Integer value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String mappedValue = this.mappings.get(value);
|
||||
return mappedValue != null ? mappedValue : String.valueOf(value);
|
||||
}
|
||||
|
||||
public Integer resolveValue(String text) {
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String normalizedText = text.trim();
|
||||
if (normalizedText.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (Map.Entry<Integer, String> entry : this.mappings.entrySet()) {
|
||||
if (entry == null || entry.getKey() == null || entry.getValue() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.getValue().trim().equalsIgnoreCase(normalizedText)) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void setMappingsText(String value) {
|
||||
this.mappingsText = normalizeMappingsText(value);
|
||||
this.mappings = parseMappings(this.mappingsText);
|
||||
}
|
||||
|
||||
private static String normalizeMappingsText(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String normalized = value.replace("\r", "");
|
||||
|
||||
if (normalized.length() > MAX_MAPPING_LENGTH) {
|
||||
normalized = normalized.substring(0, MAX_MAPPING_LENGTH);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static LinkedHashMap<Integer, String> parseMappings(String value) {
|
||||
LinkedHashMap<Integer, String> result = new LinkedHashMap<>();
|
||||
if (value == null || value.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (String rawLine : value.split("\n")) {
|
||||
if (rawLine == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String line = rawLine.trim();
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int separatorIndex = line.indexOf('=');
|
||||
if (separatorIndex < 0) {
|
||||
separatorIndex = line.indexOf(',');
|
||||
}
|
||||
|
||||
if (separatorIndex <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String keyPart = line.substring(0, separatorIndex).trim();
|
||||
String valuePart = line.substring(separatorIndex + 1).trim();
|
||||
|
||||
try {
|
||||
result.put(Integer.parseInt(keyPart), valuePart);
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static class JsonData {
|
||||
String mappingsText;
|
||||
|
||||
JsonData(String mappingsText) {
|
||||
this.mappingsText = mappingsText;
|
||||
}
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
|
||||
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final class WiredVariableNameValidator {
|
||||
static final int MIN_NAME_LENGTH = 1;
|
||||
static final int MAX_NAME_LENGTH = 40;
|
||||
|
||||
private static final Pattern VALID_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$");
|
||||
|
||||
private WiredVariableNameValidator() {
|
||||
}
|
||||
|
||||
static String normalizeForSave(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return value.trim()
|
||||
.replace("\t", "")
|
||||
.replace("\r", "")
|
||||
.replace("\n", "");
|
||||
}
|
||||
|
||||
static String normalizeLegacy(String value) {
|
||||
String normalized = normalizeForSave(value);
|
||||
|
||||
if (normalized.contains("=")) {
|
||||
normalized = normalized.substring(0, normalized.indexOf('=')).trim();
|
||||
}
|
||||
|
||||
while (normalized.startsWith("@") || normalized.startsWith("~")) {
|
||||
normalized = normalized.substring(1).trim();
|
||||
}
|
||||
|
||||
if (normalized.length() > MAX_NAME_LENGTH) {
|
||||
normalized = normalized.substring(0, MAX_NAME_LENGTH);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
static void validateDefinitionName(Room room, int currentItemId, String variableName) throws WiredSaveException {
|
||||
String normalized = normalizeForSave(variableName);
|
||||
|
||||
if (normalized.length() < MIN_NAME_LENGTH || normalized.length() > MAX_NAME_LENGTH) {
|
||||
throw new WiredSaveException("wiredfurni.error.variables.name_length");
|
||||
}
|
||||
|
||||
if (!VALID_NAME_PATTERN.matcher(normalized).matches()) {
|
||||
throw new WiredSaveException("wiredfurni.error.variables.name_syntax");
|
||||
}
|
||||
|
||||
if (isNameInUse(room, currentItemId, normalized)) {
|
||||
throw new WiredSaveException("wiredfurni.error.variables.name_uniq");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isNameInUse(Room room, int currentItemId, String variableName) {
|
||||
if (room == null || room.getRoomSpecialTypes() == null || variableName == null || variableName.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (InteractionWiredExtra extra : room.getRoomSpecialTypes().getExtras()) {
|
||||
if (extra == null || extra.getId() == currentItemId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String existingName = getDefinitionName(extra);
|
||||
|
||||
if (existingName != null && existingName.equalsIgnoreCase(variableName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String getDefinitionName(InteractionWiredExtra extra) {
|
||||
if (extra instanceof WiredExtraUserVariable) {
|
||||
return ((WiredExtraUserVariable) extra).getVariableName();
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraFurniVariable) {
|
||||
return ((WiredExtraFurniVariable) extra).getVariableName();
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraRoomVariable) {
|
||||
return ((WiredExtraRoomVariable) extra).getVariableName();
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraContextVariable) {
|
||||
return ((WiredExtraContextVariable) extra).getVariableName();
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraVariableReference) {
|
||||
return ((WiredExtraVariableReference) extra).getVariableName();
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraVariableEcho) {
|
||||
return ((WiredExtraVariableEcho) extra).getVariableName();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+629
@@ -0,0 +1,629 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class WiredVariableReferenceSupport {
|
||||
public static final int TARGET_USER = 0;
|
||||
public static final int TARGET_ROOM = 3;
|
||||
public static final int SHARED_AVAILABILITY = 11;
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(WiredVariableReferenceSupport.class);
|
||||
|
||||
private static final ConcurrentHashMap<String, CachedUserAssignment> USER_ASSIGNMENT_CACHE = new ConcurrentHashMap<>();
|
||||
private static final ConcurrentHashMap<String, CachedRoomAssignment> ROOM_ASSIGNMENT_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
private WiredVariableReferenceSupport() {
|
||||
}
|
||||
|
||||
public static boolean isSharedAvailability(int availability) {
|
||||
return availability == SHARED_AVAILABILITY;
|
||||
}
|
||||
|
||||
public static SharedDefinitionOption findSharedDefinition(Room room, int sourceRoomId, int sourceVariableItemId, int sourceTargetType) {
|
||||
if (room == null || sourceRoomId <= 0 || sourceVariableItemId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (RoomOption roomOption : loadRoomOptions(room)) {
|
||||
if (roomOption.getRoomId() != sourceRoomId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (SharedDefinitionOption definition : roomOption.getVariables()) {
|
||||
if (definition.getItemId() == sourceVariableItemId && definition.getTargetType() == sourceTargetType) {
|
||||
return definition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<RoomOption> loadRoomOptions(Room room) {
|
||||
if (room == null || room.getOwnerId() <= 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Map<Integer, RoomOption> optionsByRoomId = new LinkedHashMap<>();
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
"SELECT rooms.id AS room_id, rooms.name AS room_name, items.id AS item_id, items.wired_data, items_base.interaction_type " +
|
||||
"FROM rooms " +
|
||||
"INNER JOIN items ON rooms.id = items.room_id " +
|
||||
"INNER JOIN items_base ON items.item_id = items_base.id " +
|
||||
"WHERE rooms.owner_id = ? AND rooms.id <> ? AND items_base.interaction_type IN ('wf_var_user', 'wf_var_room') " +
|
||||
"ORDER BY rooms.name ASC, items.id ASC")) {
|
||||
statement.setInt(1, room.getOwnerId());
|
||||
statement.setInt(2, room.getId());
|
||||
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
while (set.next()) {
|
||||
SharedDefinitionOption definition = parseSharedDefinition(
|
||||
set.getString("interaction_type"),
|
||||
set.getInt("item_id"),
|
||||
set.getString("wired_data"),
|
||||
set.getInt("room_id"),
|
||||
set.getString("room_name")
|
||||
);
|
||||
|
||||
if (definition == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RoomOption roomOption = optionsByRoomId.computeIfAbsent(
|
||||
definition.getRoomId(),
|
||||
key -> new RoomOption(definition.getRoomId(), definition.getRoomName(), new ArrayList<>())
|
||||
);
|
||||
|
||||
roomOption.getVariables().add(definition);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to load shared variable reference options for room {}", room.getId(), e);
|
||||
}
|
||||
|
||||
List<RoomOption> result = new ArrayList<>(optionsByRoomId.values());
|
||||
|
||||
for (RoomOption option : result) {
|
||||
option.getVariables().sort(Comparator.comparing(SharedDefinitionOption::getName, String.CASE_INSENSITIVE_ORDER).thenComparingInt(SharedDefinitionOption::getItemId));
|
||||
}
|
||||
|
||||
result.sort(Comparator.comparing(RoomOption::getRoomName, String.CASE_INSENSITIVE_ORDER).thenComparingInt(RoomOption::getRoomId));
|
||||
return result;
|
||||
}
|
||||
|
||||
public static SharedUserAssignment getSharedUserAssignment(WiredExtraVariableReference reference, int userId) {
|
||||
if (reference == null || !reference.isUserReference() || userId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String cacheKey = createUserCacheKey(reference.getSourceRoomId(), reference.getSourceVariableItemId(), userId);
|
||||
CachedUserAssignment cachedValue = USER_ASSIGNMENT_CACHE.get(cacheKey);
|
||||
|
||||
if (cachedValue != null) {
|
||||
return cachedValue.present ? cachedValue.toAssignment() : null;
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("SELECT value, created_at, updated_at FROM room_user_wired_variables WHERE room_id = ? AND user_id = ? AND variable_item_id = ? LIMIT 1")) {
|
||||
statement.setInt(1, reference.getSourceRoomId());
|
||||
statement.setInt(2, userId);
|
||||
statement.setInt(3, reference.getSourceVariableItemId());
|
||||
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
if (!set.next()) {
|
||||
USER_ASSIGNMENT_CACHE.put(cacheKey, CachedUserAssignment.missing());
|
||||
return null;
|
||||
}
|
||||
|
||||
Integer value = null;
|
||||
int rawValue = set.getInt("value");
|
||||
if (!set.wasNull()) {
|
||||
value = rawValue;
|
||||
}
|
||||
|
||||
int createdAt = normalizeTimestamp(set.getInt("created_at"), 0);
|
||||
SharedUserAssignment assignment = new SharedUserAssignment(
|
||||
value,
|
||||
createdAt,
|
||||
normalizeTimestamp(set.getInt("updated_at"), createdAt)
|
||||
);
|
||||
|
||||
USER_ASSIGNMENT_CACHE.put(cacheKey, CachedUserAssignment.present(assignment));
|
||||
return assignment;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to load shared wired user variable {} for room {} user {}", reference.getSourceVariableItemId(), reference.getSourceRoomId(), userId, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean assignSharedUserVariable(WiredExtraVariableReference reference, int userId, Integer value, boolean overrideExisting) {
|
||||
if (reference == null || !reference.isUserReference() || reference.isReadOnly() || userId <= 0 || !isSharedSourceStillAvailable(reference)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Integer normalizedValue = reference.hasValue() ? value : null;
|
||||
SharedUserAssignment existingAssignment = getSharedUserAssignment(reference, userId);
|
||||
|
||||
if (existingAssignment != null && !overrideExisting) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int now = Emulator.getIntUnixTimestamp();
|
||||
SharedUserAssignment nextAssignment = (existingAssignment == null)
|
||||
? new SharedUserAssignment(normalizedValue, now, now)
|
||||
: new SharedUserAssignment(normalizedValue, existingAssignment.getCreatedAt(), Objects.equals(existingAssignment.getValue(), normalizedValue) ? existingAssignment.getUpdatedAt() : now);
|
||||
|
||||
if (existingAssignment != null && Objects.equals(existingAssignment.getValue(), normalizedValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
upsertSharedUserAssignment(reference.getSourceRoomId(), reference.getSourceVariableItemId(), userId, nextAssignment);
|
||||
USER_ASSIGNMENT_CACHE.put(createUserCacheKey(reference.getSourceRoomId(), reference.getSourceVariableItemId(), userId), CachedUserAssignment.present(nextAssignment));
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean updateSharedUserVariable(WiredExtraVariableReference reference, int userId, Integer value) {
|
||||
if (reference == null || !reference.isUserReference() || reference.isReadOnly() || userId <= 0 || !reference.hasValue() || !isSharedSourceStillAvailable(reference)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SharedUserAssignment existingAssignment = getSharedUserAssignment(reference, userId);
|
||||
if (existingAssignment == null || Objects.equals(existingAssignment.getValue(), value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SharedUserAssignment nextAssignment = new SharedUserAssignment(value, existingAssignment.getCreatedAt(), Emulator.getIntUnixTimestamp());
|
||||
upsertSharedUserAssignment(reference.getSourceRoomId(), reference.getSourceVariableItemId(), userId, nextAssignment);
|
||||
USER_ASSIGNMENT_CACHE.put(createUserCacheKey(reference.getSourceRoomId(), reference.getSourceVariableItemId(), userId), CachedUserAssignment.present(nextAssignment));
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean removeSharedUserVariable(WiredExtraVariableReference reference, int userId) {
|
||||
if (reference == null || !reference.isUserReference() || reference.isReadOnly() || userId <= 0 || !isSharedSourceStillAvailable(reference)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SharedUserAssignment existingAssignment = getSharedUserAssignment(reference, userId);
|
||||
if (existingAssignment == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
deleteSharedUserAssignment(reference.getSourceRoomId(), reference.getSourceVariableItemId(), userId);
|
||||
USER_ASSIGNMENT_CACHE.put(createUserCacheKey(reference.getSourceRoomId(), reference.getSourceVariableItemId(), userId), CachedUserAssignment.missing());
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void cacheSharedUserAssignment(int sourceRoomId, int sourceVariableItemId, int userId, Integer value, int createdAt, int updatedAt) {
|
||||
USER_ASSIGNMENT_CACHE.put(createUserCacheKey(sourceRoomId, sourceVariableItemId, userId), CachedUserAssignment.present(new SharedUserAssignment(value, createdAt, updatedAt)));
|
||||
}
|
||||
|
||||
public static void clearSharedUserAssignment(int sourceRoomId, int sourceVariableItemId, int userId) {
|
||||
USER_ASSIGNMENT_CACHE.put(createUserCacheKey(sourceRoomId, sourceVariableItemId, userId), CachedUserAssignment.missing());
|
||||
}
|
||||
|
||||
public static void clearSharedUserDefinition(int sourceRoomId, int sourceVariableItemId) {
|
||||
String prefix = createDefinitionPrefix(sourceRoomId, sourceVariableItemId) + ":";
|
||||
USER_ASSIGNMENT_CACHE.entrySet().removeIf(entry -> entry.getKey().startsWith(prefix));
|
||||
}
|
||||
|
||||
public static SharedRoomAssignment getSharedRoomAssignment(WiredExtraVariableReference reference) {
|
||||
if (reference == null || !reference.isRoomReference()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String cacheKey = createRoomCacheKey(reference.getSourceRoomId(), reference.getSourceVariableItemId());
|
||||
CachedRoomAssignment cachedValue = ROOM_ASSIGNMENT_CACHE.get(cacheKey);
|
||||
|
||||
if (cachedValue != null) {
|
||||
return cachedValue.present ? cachedValue.toAssignment() : null;
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("SELECT value, updated_at FROM room_wired_variables WHERE room_id = ? AND variable_item_id = ? LIMIT 1")) {
|
||||
statement.setInt(1, reference.getSourceRoomId());
|
||||
statement.setInt(2, reference.getSourceVariableItemId());
|
||||
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
if (!set.next()) {
|
||||
ROOM_ASSIGNMENT_CACHE.put(cacheKey, CachedRoomAssignment.missing());
|
||||
return null;
|
||||
}
|
||||
|
||||
SharedRoomAssignment assignment = new SharedRoomAssignment(set.getInt("value"), normalizeTimestamp(set.getInt("updated_at"), 0));
|
||||
ROOM_ASSIGNMENT_CACHE.put(cacheKey, CachedRoomAssignment.present(assignment));
|
||||
return assignment;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to load shared wired room variable {} for room {}", reference.getSourceVariableItemId(), reference.getSourceRoomId(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean updateSharedRoomVariable(WiredExtraVariableReference reference, int value) {
|
||||
if (reference == null || !reference.isRoomReference() || reference.isReadOnly() || !isSharedSourceStillAvailable(reference)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SharedRoomAssignment existingAssignment = getSharedRoomAssignment(reference);
|
||||
if (existingAssignment != null && existingAssignment.getValue() == value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SharedRoomAssignment nextAssignment = new SharedRoomAssignment(value, Emulator.getIntUnixTimestamp());
|
||||
upsertSharedRoomAssignment(reference.getSourceRoomId(), reference.getSourceVariableItemId(), nextAssignment);
|
||||
ROOM_ASSIGNMENT_CACHE.put(createRoomCacheKey(reference.getSourceRoomId(), reference.getSourceVariableItemId()), CachedRoomAssignment.present(nextAssignment));
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean removeSharedRoomVariable(WiredExtraVariableReference reference) {
|
||||
if (reference == null || !reference.isRoomReference() || reference.isReadOnly() || !isSharedSourceStillAvailable(reference)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SharedRoomAssignment existingAssignment = getSharedRoomAssignment(reference);
|
||||
if (existingAssignment == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
deleteSharedRoomAssignment(reference.getSourceRoomId(), reference.getSourceVariableItemId());
|
||||
ROOM_ASSIGNMENT_CACHE.put(createRoomCacheKey(reference.getSourceRoomId(), reference.getSourceVariableItemId()), CachedRoomAssignment.missing());
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void cacheSharedRoomAssignment(int sourceRoomId, int sourceVariableItemId, int value, int updatedAt) {
|
||||
ROOM_ASSIGNMENT_CACHE.put(createRoomCacheKey(sourceRoomId, sourceVariableItemId), CachedRoomAssignment.present(new SharedRoomAssignment(value, updatedAt)));
|
||||
}
|
||||
|
||||
public static void clearSharedRoomDefinition(int sourceRoomId, int sourceVariableItemId) {
|
||||
ROOM_ASSIGNMENT_CACHE.put(createRoomCacheKey(sourceRoomId, sourceVariableItemId), CachedRoomAssignment.missing());
|
||||
}
|
||||
|
||||
private static SharedDefinitionOption parseSharedDefinition(String interactionType, int itemId, String wiredData, int roomId, String roomName) {
|
||||
if ("wf_var_user".equals(interactionType)) {
|
||||
UserDefinitionData data = parseUserDefinitionData(wiredData);
|
||||
if (data == null || !isSharedAvailability(data.availability) || data.variableName.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SharedDefinitionOption(roomId, roomName, itemId, data.variableName, TARGET_USER, data.hasValue);
|
||||
}
|
||||
|
||||
if ("wf_var_room".equals(interactionType)) {
|
||||
RoomDefinitionData data = parseRoomDefinitionData(wiredData);
|
||||
if (data == null || !isSharedAvailability(data.availability) || data.variableName.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SharedDefinitionOption(roomId, roomName, itemId, data.variableName, TARGET_ROOM, true);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static UserDefinitionData parseUserDefinitionData(String wiredData) {
|
||||
if (wiredData == null || wiredData.isEmpty() || !wiredData.startsWith("{")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
UserDefinitionData data = WiredManager.getGson().fromJson(wiredData, UserDefinitionData.class);
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
data.variableName = WiredVariableNameValidator.normalizeLegacy(data.variableName);
|
||||
return data;
|
||||
}
|
||||
|
||||
private static RoomDefinitionData parseRoomDefinitionData(String wiredData) {
|
||||
if (wiredData == null || wiredData.isEmpty() || !wiredData.startsWith("{")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
RoomDefinitionData data = WiredManager.getGson().fromJson(wiredData, RoomDefinitionData.class);
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
data.variableName = WiredVariableNameValidator.normalizeLegacy(data.variableName);
|
||||
return data;
|
||||
}
|
||||
|
||||
private static boolean isSharedSourceStillAvailable(WiredExtraVariableReference reference) {
|
||||
if (reference == null || reference.getSourceRoomId() <= 0 || reference.getSourceVariableItemId() <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
"SELECT items.wired_data, items_base.interaction_type " +
|
||||
"FROM items INNER JOIN items_base ON items.item_id = items_base.id " +
|
||||
"WHERE items.id = ? AND items.room_id = ? LIMIT 1")) {
|
||||
statement.setInt(1, reference.getSourceVariableItemId());
|
||||
statement.setInt(2, reference.getSourceRoomId());
|
||||
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
if (!set.next()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SharedDefinitionOption definition = parseSharedDefinition(
|
||||
set.getString("interaction_type"),
|
||||
reference.getSourceVariableItemId(),
|
||||
set.getString("wired_data"),
|
||||
reference.getSourceRoomId(),
|
||||
""
|
||||
);
|
||||
|
||||
return definition != null && definition.getTargetType() == reference.getSourceTargetType();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to validate shared wired variable source {} in room {}", reference.getSourceVariableItemId(), reference.getSourceRoomId(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void upsertSharedUserAssignment(int sourceRoomId, int sourceVariableItemId, int userId, SharedUserAssignment assignment) {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("INSERT INTO room_user_wired_variables (room_id, user_id, variable_item_id, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = VALUES(updated_at)")) {
|
||||
statement.setInt(1, sourceRoomId);
|
||||
statement.setInt(2, userId);
|
||||
statement.setInt(3, sourceVariableItemId);
|
||||
|
||||
if (assignment.getValue() == null) {
|
||||
statement.setNull(4, java.sql.Types.INTEGER);
|
||||
} else {
|
||||
statement.setInt(4, assignment.getValue());
|
||||
}
|
||||
|
||||
statement.setInt(5, assignment.getCreatedAt());
|
||||
statement.setInt(6, assignment.getUpdatedAt());
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to store shared wired user variable {} for room {} user {}", sourceVariableItemId, sourceRoomId, userId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteSharedUserAssignment(int sourceRoomId, int sourceVariableItemId, int userId) {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("DELETE FROM room_user_wired_variables WHERE room_id = ? AND user_id = ? AND variable_item_id = ?")) {
|
||||
statement.setInt(1, sourceRoomId);
|
||||
statement.setInt(2, userId);
|
||||
statement.setInt(3, sourceVariableItemId);
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to delete shared wired user variable {} for room {} user {}", sourceVariableItemId, sourceRoomId, userId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void upsertSharedRoomAssignment(int sourceRoomId, int sourceVariableItemId, SharedRoomAssignment assignment) {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("INSERT INTO room_wired_variables (room_id, variable_item_id, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = VALUES(updated_at)")) {
|
||||
statement.setInt(1, sourceRoomId);
|
||||
statement.setInt(2, sourceVariableItemId);
|
||||
statement.setInt(3, assignment.getValue());
|
||||
statement.setInt(4, 0);
|
||||
statement.setInt(5, assignment.getUpdatedAt());
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to store shared wired room variable {} for room {}", sourceVariableItemId, sourceRoomId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteSharedRoomAssignment(int sourceRoomId, int sourceVariableItemId) {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("DELETE FROM room_wired_variables WHERE room_id = ? AND variable_item_id = ?")) {
|
||||
statement.setInt(1, sourceRoomId);
|
||||
statement.setInt(2, sourceVariableItemId);
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to delete shared wired room variable {} for room {}", sourceVariableItemId, sourceRoomId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String createDefinitionPrefix(int sourceRoomId, int sourceVariableItemId) {
|
||||
return sourceRoomId + ":" + sourceVariableItemId;
|
||||
}
|
||||
|
||||
private static String createUserCacheKey(int sourceRoomId, int sourceVariableItemId, int userId) {
|
||||
return createDefinitionPrefix(sourceRoomId, sourceVariableItemId) + ":" + userId;
|
||||
}
|
||||
|
||||
private static String createRoomCacheKey(int sourceRoomId, int sourceVariableItemId) {
|
||||
return createDefinitionPrefix(sourceRoomId, sourceVariableItemId);
|
||||
}
|
||||
|
||||
private static int normalizeTimestamp(int value, int fallback) {
|
||||
if (value > 0) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (fallback > 0) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return Emulator.getIntUnixTimestamp();
|
||||
}
|
||||
|
||||
public static class RoomOption {
|
||||
private final int roomId;
|
||||
private final String roomName;
|
||||
private final List<SharedDefinitionOption> variables;
|
||||
|
||||
public RoomOption(int roomId, String roomName, List<SharedDefinitionOption> variables) {
|
||||
this.roomId = roomId;
|
||||
this.roomName = roomName;
|
||||
this.variables = variables;
|
||||
}
|
||||
|
||||
public int getRoomId() {
|
||||
return this.roomId;
|
||||
}
|
||||
|
||||
public String getRoomName() {
|
||||
return this.roomName;
|
||||
}
|
||||
|
||||
public List<SharedDefinitionOption> getVariables() {
|
||||
return this.variables;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SharedDefinitionOption {
|
||||
private final int roomId;
|
||||
private final String roomName;
|
||||
private final int itemId;
|
||||
private final String name;
|
||||
private final int targetType;
|
||||
private final boolean hasValue;
|
||||
|
||||
public SharedDefinitionOption(int roomId, String roomName, int itemId, String name, int targetType, boolean hasValue) {
|
||||
this.roomId = roomId;
|
||||
this.roomName = roomName;
|
||||
this.itemId = itemId;
|
||||
this.name = name;
|
||||
this.targetType = targetType;
|
||||
this.hasValue = hasValue;
|
||||
}
|
||||
|
||||
public int getRoomId() {
|
||||
return this.roomId;
|
||||
}
|
||||
|
||||
public String getRoomName() {
|
||||
return this.roomName;
|
||||
}
|
||||
|
||||
public int getItemId() {
|
||||
return this.itemId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public int getTargetType() {
|
||||
return this.targetType;
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return this.hasValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SharedUserAssignment {
|
||||
private final Integer value;
|
||||
private final int createdAt;
|
||||
private final int updatedAt;
|
||||
|
||||
public SharedUserAssignment(Integer value, int createdAt, int updatedAt) {
|
||||
this.value = value;
|
||||
this.createdAt = createdAt;
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public Integer getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public int getCreatedAt() {
|
||||
return this.createdAt;
|
||||
}
|
||||
|
||||
public int getUpdatedAt() {
|
||||
return this.updatedAt;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SharedRoomAssignment {
|
||||
private final int value;
|
||||
private final int updatedAt;
|
||||
|
||||
public SharedRoomAssignment(int value, int updatedAt) {
|
||||
this.value = value;
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public int getUpdatedAt() {
|
||||
return this.updatedAt;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CachedUserAssignment {
|
||||
private final boolean present;
|
||||
private final SharedUserAssignment assignment;
|
||||
|
||||
private CachedUserAssignment(boolean present, SharedUserAssignment assignment) {
|
||||
this.present = present;
|
||||
this.assignment = assignment;
|
||||
}
|
||||
|
||||
private static CachedUserAssignment present(SharedUserAssignment assignment) {
|
||||
return new CachedUserAssignment(true, assignment);
|
||||
}
|
||||
|
||||
private static CachedUserAssignment missing() {
|
||||
return new CachedUserAssignment(false, null);
|
||||
}
|
||||
|
||||
private SharedUserAssignment toAssignment() {
|
||||
return this.assignment;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CachedRoomAssignment {
|
||||
private final boolean present;
|
||||
private final SharedRoomAssignment assignment;
|
||||
|
||||
private CachedRoomAssignment(boolean present, SharedRoomAssignment assignment) {
|
||||
this.present = present;
|
||||
this.assignment = assignment;
|
||||
}
|
||||
|
||||
private static CachedRoomAssignment present(SharedRoomAssignment assignment) {
|
||||
return new CachedRoomAssignment(true, assignment);
|
||||
}
|
||||
|
||||
private static CachedRoomAssignment missing() {
|
||||
return new CachedRoomAssignment(false, null);
|
||||
}
|
||||
|
||||
private SharedRoomAssignment toAssignment() {
|
||||
return this.assignment;
|
||||
}
|
||||
}
|
||||
|
||||
private static class UserDefinitionData {
|
||||
String variableName;
|
||||
boolean hasValue;
|
||||
int availability;
|
||||
}
|
||||
|
||||
private static class RoomDefinitionData {
|
||||
String variableName;
|
||||
int availability;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.selector;
|
||||
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.wired.WiredEffectType;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class WiredEffectFurniWithVariable extends WiredEffectVariableSelectorBase {
|
||||
public static final WiredEffectType type = WiredEffectType.FURNI_WITH_VAR_SELECTOR;
|
||||
|
||||
public WiredEffectFurniWithVariable(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredEffectFurniWithVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getVariableTargetType() {
|
||||
return TARGET_FURNI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WiredEffectType getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.selector;
|
||||
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.wired.WiredEffectType;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class WiredEffectUsersWithVariable extends WiredEffectVariableSelectorBase {
|
||||
public static final WiredEffectType type = WiredEffectType.USERS_WITH_VAR_SELECTOR;
|
||||
|
||||
public WiredEffectUsersWithVariable(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredEffectUsersWithVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getVariableTargetType() {
|
||||
return TARGET_USER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WiredEffectType getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
+862
@@ -0,0 +1,862 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.selector;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.bots.Bot;
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.games.Game;
|
||||
import com.eu.habbo.habbohotel.games.GamePlayer;
|
||||
import com.eu.habbo.habbohotel.games.GameTeam;
|
||||
import com.eu.habbo.habbohotel.games.GameTeamColors;
|
||||
import com.eu.habbo.habbohotel.games.battlebanzai.BattleBanzaiGame;
|
||||
import com.eu.habbo.habbohotel.games.freeze.FreezeGame;
|
||||
import com.eu.habbo.habbohotel.games.wired.WiredGame;
|
||||
import com.eu.habbo.habbohotel.items.FurnitureType;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWired;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
|
||||
import com.eu.habbo.habbohotel.pets.Pet;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomRightLevels;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
|
||||
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
|
||||
import com.eu.habbo.habbohotel.users.DanceType;
|
||||
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.habbohotel.wired.core.WiredContext;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredInternalVariableSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
|
||||
import com.eu.habbo.util.HotelDateTimeUtil;
|
||||
import gnu.trove.set.hash.THashSet;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public abstract class WiredEffectVariableSelectorBase extends InteractionWiredEffect {
|
||||
protected static final int TARGET_USER = 0;
|
||||
protected static final int TARGET_FURNI = 1;
|
||||
protected static final int TARGET_CONTEXT = 2;
|
||||
protected static final int TARGET_ROOM = 3;
|
||||
|
||||
protected static final int REFERENCE_CONSTANT = 0;
|
||||
protected static final int REFERENCE_VARIABLE = 1;
|
||||
|
||||
protected static final int SOURCE_SECONDARY_SELECTED = 101;
|
||||
|
||||
protected static final int COMPARISON_GREATER_THAN = 0;
|
||||
protected static final int COMPARISON_GREATER_THAN_OR_EQUAL = 1;
|
||||
protected static final int COMPARISON_EQUAL = 2;
|
||||
protected static final int COMPARISON_LESS_THAN_OR_EQUAL = 3;
|
||||
protected static final int COMPARISON_LESS_THAN = 4;
|
||||
protected static final int COMPARISON_NOT_EQUAL = 5;
|
||||
|
||||
private static final String CUSTOM_TOKEN_PREFIX = "custom:";
|
||||
private static final String INTERNAL_TOKEN_PREFIX = "internal:";
|
||||
private static final String DELIM = "\t";
|
||||
|
||||
protected boolean selectByValue = false;
|
||||
protected int comparison = COMPARISON_EQUAL;
|
||||
protected int referenceMode = REFERENCE_CONSTANT;
|
||||
protected int referenceConstantValue = 0;
|
||||
protected int referenceTargetType = TARGET_USER;
|
||||
protected int referenceUserSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
protected int referenceFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
protected boolean filterExisting = false;
|
||||
protected boolean invert = false;
|
||||
protected String variableToken = "";
|
||||
protected int variableItemId = 0;
|
||||
protected String referenceVariableToken = "";
|
||||
protected int referenceVariableItemId = 0;
|
||||
protected final THashSet<HabboItem> referenceSelectedItems = new THashSet<>();
|
||||
|
||||
protected WiredEffectVariableSelectorBase(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
protected WiredEffectVariableSelectorBase(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
protected abstract int getVariableTargetType();
|
||||
|
||||
@Override
|
||||
public void execute(WiredContext ctx) {
|
||||
Room room = ctx.room();
|
||||
|
||||
if (room == null || this.variableToken == null || this.variableToken.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.getVariableTargetType() == TARGET_FURNI) {
|
||||
LinkedHashSet<HabboItem> matchedItems = new LinkedHashSet<>();
|
||||
|
||||
for (HabboItem item : this.getSelectableFloorItems(room, ctx)) {
|
||||
if (item == null) continue;
|
||||
if (!this.matchesFurni(room, item, ctx)) continue;
|
||||
|
||||
matchedItems.add(item);
|
||||
}
|
||||
|
||||
LinkedHashSet<HabboItem> result = this.applySelectorModifiers(matchedItems, this.getSelectableFloorItems(room, ctx), ctx.targets().items(), this.filterExisting, this.invert);
|
||||
ctx.targets().setItems(result);
|
||||
return;
|
||||
}
|
||||
|
||||
LinkedHashSet<RoomUnit> matchedUsers = new LinkedHashSet<>();
|
||||
|
||||
for (RoomUnit roomUnit : room.getRoomUnits()) {
|
||||
if (roomUnit == null) continue;
|
||||
if (!this.matchesUser(room, roomUnit, ctx)) continue;
|
||||
|
||||
matchedUsers.add(roomUnit);
|
||||
}
|
||||
|
||||
LinkedHashSet<RoomUnit> result = this.applySelectorModifiers(matchedUsers, room.getRoomUnits(), ctx.targets().users(), this.filterExisting, this.invert);
|
||||
ctx.targets().setUsers(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
|
||||
Room room = this.getRoom();
|
||||
if (room == null) return false;
|
||||
|
||||
int[] params = settings.getIntParams();
|
||||
String[] stringParts = parseStringData(settings.getStringParam());
|
||||
|
||||
boolean nextSelectByValue = param(params, 0, 0) == 1;
|
||||
int nextComparison = normalizeComparison(param(params, 1, COMPARISON_EQUAL));
|
||||
int nextReferenceMode = normalizeReferenceMode(param(params, 2, REFERENCE_CONSTANT));
|
||||
int nextReferenceConstantValue = param(params, 3, 0);
|
||||
int nextReferenceTargetType = normalizeReferenceTargetType(param(params, 4, TARGET_USER));
|
||||
int nextReferenceUserSource = normalizeUserSource(param(params, 5, WiredSourceUtil.SOURCE_TRIGGER));
|
||||
int nextReferenceFurniSource = normalizeReferenceFurniSource(param(params, 6, WiredSourceUtil.SOURCE_TRIGGER));
|
||||
boolean nextFilterExisting = param(params, 7, 0) == 1;
|
||||
boolean nextInvert = param(params, 8, 0) == 1;
|
||||
String nextVariableToken = normalizeVariableToken((stringParts.length > 0) ? stringParts[0] : settings.getStringParam());
|
||||
String nextReferenceVariableToken = normalizeVariableToken((stringParts.length > 1) ? stringParts[1] : "");
|
||||
|
||||
if (!this.isValidMainVariable(room, nextVariableToken, nextSelectByValue)) return false;
|
||||
if (nextSelectByValue && nextReferenceMode == REFERENCE_VARIABLE && !this.isValidReference(room, nextReferenceTargetType, nextReferenceVariableToken)) return false;
|
||||
|
||||
List<HabboItem> nextReferenceItems = new ArrayList<>();
|
||||
|
||||
if (nextSelectByValue && nextReferenceMode == REFERENCE_VARIABLE && nextReferenceTargetType == TARGET_FURNI && nextReferenceFurniSource == SOURCE_SECONDARY_SELECTED) {
|
||||
int[] furniIds = settings.getFurniIds();
|
||||
if (furniIds.length > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) return false;
|
||||
|
||||
for (int furniId : furniIds) {
|
||||
HabboItem item = room.getHabboItem(furniId);
|
||||
if (item != null) nextReferenceItems.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
this.referenceSelectedItems.clear();
|
||||
this.referenceSelectedItems.addAll(nextReferenceItems);
|
||||
this.selectByValue = nextSelectByValue;
|
||||
this.comparison = nextComparison;
|
||||
this.referenceMode = nextReferenceMode;
|
||||
this.referenceConstantValue = nextReferenceConstantValue;
|
||||
this.referenceTargetType = nextReferenceTargetType;
|
||||
this.referenceUserSource = nextReferenceUserSource;
|
||||
this.referenceFurniSource = nextReferenceFurniSource;
|
||||
this.filterExisting = nextFilterExisting;
|
||||
this.invert = nextInvert;
|
||||
this.setVariableToken(nextVariableToken);
|
||||
this.setReferenceVariableToken(nextReferenceVariableToken);
|
||||
this.setDelay(settings.getDelay());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelector() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
this.refreshReferenceItems();
|
||||
|
||||
return WiredManager.getGson().toJson(new JsonData(
|
||||
this.selectByValue,
|
||||
this.comparison,
|
||||
this.referenceMode,
|
||||
this.referenceConstantValue,
|
||||
this.referenceTargetType,
|
||||
this.referenceUserSource,
|
||||
this.referenceFurniSource,
|
||||
this.filterExisting,
|
||||
this.invert,
|
||||
this.variableToken,
|
||||
this.variableItemId,
|
||||
this.referenceVariableToken,
|
||||
this.referenceVariableItemId,
|
||||
this.toIds(this.referenceSelectedItems),
|
||||
this.getDelay()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty() || !wiredData.startsWith("{")) return;
|
||||
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
if (data == null) return;
|
||||
|
||||
this.selectByValue = data.selectByValue;
|
||||
this.comparison = normalizeComparison(data.comparison);
|
||||
this.referenceMode = normalizeReferenceMode(data.referenceMode);
|
||||
this.referenceConstantValue = data.referenceConstantValue;
|
||||
this.referenceTargetType = normalizeReferenceTargetType(data.referenceTargetType);
|
||||
this.referenceUserSource = normalizeUserSource(data.referenceUserSource);
|
||||
this.referenceFurniSource = normalizeReferenceFurniSource(data.referenceFurniSource);
|
||||
this.filterExisting = data.filterExisting;
|
||||
this.invert = data.invert;
|
||||
this.setVariableToken(normalizeVariableToken((data.variableToken != null) ? data.variableToken : ((data.variableItemId > 0) ? String.valueOf(data.variableItemId) : "")));
|
||||
this.setReferenceVariableToken(normalizeVariableToken((data.referenceVariableToken != null) ? data.referenceVariableToken : ((data.referenceVariableItemId > 0) ? String.valueOf(data.referenceVariableItemId) : "")));
|
||||
this.setDelay(data.delay);
|
||||
|
||||
if (room == null || data.selectedItemIds == null) return;
|
||||
|
||||
for (Integer itemId : data.selectedItemIds) {
|
||||
if (itemId == null || itemId <= 0) continue;
|
||||
|
||||
HabboItem item = room.getHabboItem(itemId);
|
||||
if (item != null) this.referenceSelectedItems.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
this.selectByValue = false;
|
||||
this.comparison = COMPARISON_EQUAL;
|
||||
this.referenceMode = REFERENCE_CONSTANT;
|
||||
this.referenceConstantValue = 0;
|
||||
this.referenceTargetType = TARGET_USER;
|
||||
this.referenceUserSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.referenceFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
this.filterExisting = false;
|
||||
this.invert = false;
|
||||
this.referenceSelectedItems.clear();
|
||||
this.setVariableToken("");
|
||||
this.setReferenceVariableToken("");
|
||||
this.setDelay(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
this.refreshReferenceItems();
|
||||
|
||||
List<HabboItem> serializedItems = new ArrayList<>();
|
||||
if (this.selectByValue && this.referenceMode == REFERENCE_VARIABLE && this.referenceTargetType == TARGET_FURNI && this.referenceFurniSource == SOURCE_SECONDARY_SELECTED) {
|
||||
serializedItems.addAll(this.referenceSelectedItems);
|
||||
}
|
||||
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
|
||||
message.appendInt(serializedItems.size());
|
||||
|
||||
for (HabboItem item : serializedItems) {
|
||||
message.appendInt(item.getId());
|
||||
}
|
||||
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(this.serializeStringData());
|
||||
message.appendInt(9);
|
||||
message.appendInt(this.selectByValue ? 1 : 0);
|
||||
message.appendInt(this.comparison);
|
||||
message.appendInt(this.referenceMode);
|
||||
message.appendInt(this.referenceConstantValue);
|
||||
message.appendInt(this.referenceTargetType);
|
||||
message.appendInt(this.referenceUserSource);
|
||||
message.appendInt(this.referenceFurniSource);
|
||||
message.appendInt(this.filterExisting ? 1 : 0);
|
||||
message.appendInt(this.invert ? 1 : 0);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getType().code);
|
||||
message.appendInt(this.getDelay());
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresTriggeringUser() {
|
||||
return this.selectByValue && this.referenceMode == REFERENCE_VARIABLE && this.referenceTargetType == TARGET_USER && this.referenceUserSource == WiredSourceUtil.SOURCE_TRIGGER;
|
||||
}
|
||||
|
||||
private boolean matchesUser(Room room, RoomUnit roomUnit, WiredContext ctx) {
|
||||
if (!this.selectByValue) return this.hasUserVariable(room, roomUnit);
|
||||
|
||||
Integer currentValue = this.readUserValue(room, roomUnit);
|
||||
Integer referenceValue = this.resolveReferenceValue(ctx, room, roomUnit != null ? roomUnit.getId() : 0, TARGET_USER, -1);
|
||||
|
||||
return this.matchesComparison(currentValue, referenceValue);
|
||||
}
|
||||
|
||||
private boolean matchesFurni(Room room, HabboItem item, WiredContext ctx) {
|
||||
if (!this.selectByValue) return this.hasFurniVariable(room, item);
|
||||
|
||||
Integer currentValue = this.readFurniValue(room, item);
|
||||
Integer referenceValue = this.resolveReferenceValue(ctx, room, item != null ? item.getId() : 0, TARGET_FURNI, -1);
|
||||
|
||||
return this.matchesComparison(currentValue, referenceValue);
|
||||
}
|
||||
|
||||
private boolean hasUserVariable(Room room, RoomUnit roomUnit) {
|
||||
if (room == null || roomUnit == null) return false;
|
||||
|
||||
if (isCustomVariableToken(this.variableToken)) {
|
||||
Habbo habbo = room.getHabbo(roomUnit);
|
||||
return habbo != null && room.getUserVariableManager().hasVariable(habbo.getHabboInfo().getId(), this.variableItemId);
|
||||
}
|
||||
|
||||
return isInternalVariableToken(this.variableToken) && this.hasUserInternalVariable(room, roomUnit, getInternalVariableKey(this.variableToken));
|
||||
}
|
||||
|
||||
private boolean hasFurniVariable(Room room, HabboItem item) {
|
||||
if (room == null || item == null) return false;
|
||||
|
||||
if (isCustomVariableToken(this.variableToken)) {
|
||||
return room.getFurniVariableManager().hasVariable(item.getId(), this.variableItemId);
|
||||
}
|
||||
|
||||
return isInternalVariableToken(this.variableToken) && this.hasFurniInternalVariable(item, getInternalVariableKey(this.variableToken));
|
||||
}
|
||||
|
||||
private Integer resolveReferenceValue(WiredContext ctx, Room room, int destinationEntityId, int destinationTargetType, int destinationIndex) {
|
||||
if (!this.selectByValue || this.referenceMode != REFERENCE_VARIABLE) return this.referenceConstantValue;
|
||||
|
||||
ReferenceSnapshot snapshot = this.resolveReferences(ctx, room);
|
||||
if (snapshot == null || snapshot.isEmpty()) return null;
|
||||
if (snapshot.targetType == destinationTargetType && snapshot.values.containsKey(destinationEntityId)) return snapshot.values.get(destinationEntityId);
|
||||
if (destinationIndex >= 0 && destinationIndex < snapshot.values.size()) return new ArrayList<>(snapshot.values.values()).get(destinationIndex);
|
||||
|
||||
return new ArrayList<>(snapshot.values.values()).get(0);
|
||||
}
|
||||
|
||||
private ReferenceSnapshot resolveReferences(WiredContext ctx, Room room) {
|
||||
return switch (this.referenceTargetType) {
|
||||
case TARGET_FURNI -> this.furniReferences(ctx, room);
|
||||
case TARGET_CONTEXT -> this.contextReferences(ctx, room);
|
||||
case TARGET_ROOM -> this.roomReferences(room);
|
||||
default -> this.userReferences(ctx, room);
|
||||
};
|
||||
}
|
||||
|
||||
private ReferenceSnapshot userReferences(WiredContext ctx, Room room) {
|
||||
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_USER);
|
||||
|
||||
if (isInternalVariableToken(this.referenceVariableToken)) {
|
||||
String key = getInternalVariableKey(this.referenceVariableToken);
|
||||
if (!canUseUserInternalReference(key)) return null;
|
||||
|
||||
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.referenceUserSource)) {
|
||||
Integer value = this.readUserInternalValue(room, roomUnit, key);
|
||||
if (value != null && roomUnit != null) snapshot.add(roomUnit.getId(), value);
|
||||
}
|
||||
|
||||
return snapshot.isEmpty() ? null : snapshot;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.referenceVariableItemId);
|
||||
if (definition == null || !definition.hasValue()) return null;
|
||||
|
||||
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.referenceUserSource)) {
|
||||
if (roomUnit == null) continue;
|
||||
|
||||
Habbo habbo = room.getHabbo(roomUnit);
|
||||
if (habbo != null) snapshot.add(roomUnit.getId(), room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), this.referenceVariableItemId));
|
||||
}
|
||||
|
||||
return snapshot.isEmpty() ? null : snapshot;
|
||||
}
|
||||
|
||||
private ReferenceSnapshot furniReferences(WiredContext ctx, Room room) {
|
||||
int source = (this.referenceFurniSource == SOURCE_SECONDARY_SELECTED) ? WiredSourceUtil.SOURCE_SELECTED : this.referenceFurniSource;
|
||||
if (source == WiredSourceUtil.SOURCE_SELECTED) this.refreshReferenceItems();
|
||||
|
||||
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_FURNI);
|
||||
|
||||
if (isInternalVariableToken(this.referenceVariableToken)) {
|
||||
String key = getInternalVariableKey(this.referenceVariableToken);
|
||||
if (!canUseFurniInternalReference(key)) return null;
|
||||
|
||||
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, source, this.referenceSelectedItems)) {
|
||||
Integer value = this.readFurniInternalValue(room, item, key);
|
||||
if (value != null && item != null) snapshot.add(item.getId(), value);
|
||||
}
|
||||
|
||||
return snapshot.isEmpty() ? null : snapshot;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.referenceVariableItemId);
|
||||
if (definition == null || !definition.hasValue()) return null;
|
||||
|
||||
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, source, this.referenceSelectedItems)) {
|
||||
if (item != null) snapshot.add(item.getId(), room.getFurniVariableManager().getCurrentValue(item.getId(), this.referenceVariableItemId));
|
||||
}
|
||||
|
||||
return snapshot.isEmpty() ? null : snapshot;
|
||||
}
|
||||
|
||||
private ReferenceSnapshot roomReferences(Room room) {
|
||||
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_ROOM);
|
||||
|
||||
if (isInternalVariableToken(this.referenceVariableToken)) {
|
||||
String key = getInternalVariableKey(this.referenceVariableToken);
|
||||
if (!canUseRoomInternalReference(key)) return null;
|
||||
|
||||
Integer value = this.readRoomInternalValue(room, key);
|
||||
if (value == null) return null;
|
||||
|
||||
snapshot.add(room.getId(), value);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getRoomVariableManager().getDefinitionInfo(this.referenceVariableItemId);
|
||||
if (definition == null || !definition.hasValue()) return null;
|
||||
|
||||
snapshot.add(room.getId(), room.getRoomVariableManager().getCurrentValue(this.referenceVariableItemId));
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private ReferenceSnapshot contextReferences(WiredContext ctx, Room room) {
|
||||
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_CONTEXT);
|
||||
|
||||
if (isInternalVariableToken(this.referenceVariableToken)) {
|
||||
String key = getInternalVariableKey(this.referenceVariableToken);
|
||||
if (!canUseContextInternalReference(key)) return null;
|
||||
|
||||
Integer value = WiredInternalVariableSupport.readContextValue(ctx, key);
|
||||
if (value == null) return null;
|
||||
|
||||
snapshot.add(this.referenceVariableItemId > 0 ? this.referenceVariableItemId : (room != null ? room.getId() : 0), value);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, this.referenceVariableItemId);
|
||||
if (definition == null || !definition.hasValue() || !WiredContextVariableSupport.hasVariable(ctx, this.referenceVariableItemId)) return null;
|
||||
|
||||
Integer value = WiredContextVariableSupport.getCurrentValue(ctx, this.referenceVariableItemId);
|
||||
if (value == null) return null;
|
||||
|
||||
snapshot.add(this.referenceVariableItemId, value);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
private boolean isValidMainVariable(Room room, String token, boolean requireValue) {
|
||||
if (token == null || token.isEmpty()) return false;
|
||||
|
||||
int targetType = this.getVariableTargetType();
|
||||
|
||||
if (isInternalVariableToken(token)) {
|
||||
String key = getInternalVariableKey(token);
|
||||
return targetType == TARGET_FURNI
|
||||
? (requireValue ? canUseFurniInternalReference(key) : this.hasFurniInternalKey(key))
|
||||
: (requireValue ? canUseUserInternalReference(key) : this.hasUserInternalKey(key));
|
||||
}
|
||||
|
||||
if (targetType == TARGET_FURNI) {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getFurniVariableManager().getDefinitionInfo(getCustomItemId(token)) : null;
|
||||
return definition != null && (!requireValue || definition.hasValue());
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getUserVariableManager().getDefinitionInfo(getCustomItemId(token)) : null;
|
||||
return definition != null && (!requireValue || definition.hasValue());
|
||||
}
|
||||
|
||||
private boolean isValidReference(Room room, int targetType, String token) {
|
||||
if (token == null || token.isEmpty()) return false;
|
||||
|
||||
if (isInternalVariableToken(token)) {
|
||||
String key = getInternalVariableKey(token);
|
||||
return switch (targetType) {
|
||||
case TARGET_FURNI -> canUseFurniInternalReference(key);
|
||||
case TARGET_CONTEXT -> canUseContextInternalReference(key);
|
||||
case TARGET_ROOM -> canUseRoomInternalReference(key);
|
||||
default -> canUseUserInternalReference(key);
|
||||
};
|
||||
}
|
||||
|
||||
return switch (targetType) {
|
||||
case TARGET_FURNI -> {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getFurniVariableManager().getDefinitionInfo(getCustomItemId(token)) : null;
|
||||
yield definition != null && definition.hasValue();
|
||||
}
|
||||
case TARGET_CONTEXT -> this.isValidContextCustomReference(room, getCustomItemId(token));
|
||||
case TARGET_ROOM -> {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getRoomVariableManager().getDefinitionInfo(getCustomItemId(token)) : null;
|
||||
yield definition != null && definition.hasValue();
|
||||
}
|
||||
default -> {
|
||||
WiredVariableDefinitionInfo definition = (room != null) ? room.getUserVariableManager().getDefinitionInfo(getCustomItemId(token)) : null;
|
||||
yield definition != null && definition.hasValue();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isValidContextCustomReference(Room room, int variableItemId) {
|
||||
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, variableItemId);
|
||||
return definition != null && definition.hasValue();
|
||||
}
|
||||
|
||||
private boolean matchesComparison(Integer currentValue, Integer referenceValue) {
|
||||
if (currentValue == null || referenceValue == null) return false;
|
||||
|
||||
return switch (this.comparison) {
|
||||
case COMPARISON_GREATER_THAN -> currentValue > referenceValue;
|
||||
case COMPARISON_GREATER_THAN_OR_EQUAL -> currentValue >= referenceValue;
|
||||
case COMPARISON_LESS_THAN_OR_EQUAL -> currentValue <= referenceValue;
|
||||
case COMPARISON_LESS_THAN -> currentValue < referenceValue;
|
||||
case COMPARISON_NOT_EQUAL -> !currentValue.equals(referenceValue);
|
||||
default -> currentValue.equals(referenceValue);
|
||||
};
|
||||
}
|
||||
|
||||
private Integer readUserValue(Room room, RoomUnit roomUnit) {
|
||||
if (room == null || roomUnit == null) return null;
|
||||
|
||||
if (isInternalVariableToken(this.variableToken)) {
|
||||
String key = getInternalVariableKey(this.variableToken);
|
||||
return canUseUserInternalReference(key) ? this.readUserInternalValue(room, roomUnit, key) : null;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.variableItemId);
|
||||
if (definition == null || !definition.hasValue()) return null;
|
||||
|
||||
Habbo habbo = room.getHabbo(roomUnit);
|
||||
return (habbo != null) ? room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), this.variableItemId) : null;
|
||||
}
|
||||
|
||||
private Integer readFurniValue(Room room, HabboItem item) {
|
||||
if (room == null || item == null) return null;
|
||||
|
||||
if (isInternalVariableToken(this.variableToken)) {
|
||||
String key = getInternalVariableKey(this.variableToken);
|
||||
return canUseFurniInternalReference(key) ? this.readFurniInternalValue(room, item, key) : null;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.variableItemId);
|
||||
return (definition != null && definition.hasValue()) ? room.getFurniVariableManager().getCurrentValue(item.getId(), this.variableItemId) : null;
|
||||
}
|
||||
|
||||
private boolean hasUserInternalVariable(Room room, RoomUnit roomUnit, String key) {
|
||||
return WiredInternalVariableSupport.hasUserValue(room, roomUnit, key);
|
||||
}
|
||||
|
||||
private boolean hasFurniInternalVariable(HabboItem item, String key) {
|
||||
return WiredInternalVariableSupport.hasFurniValue(item, key);
|
||||
}
|
||||
|
||||
private boolean hasUserInternalKey(String key) {
|
||||
return WiredInternalVariableSupport.canUseUserReference(key);
|
||||
}
|
||||
|
||||
private boolean hasFurniInternalKey(String key) {
|
||||
return WiredInternalVariableSupport.canUseFurniReference(key) || "@wallitem_offset".equals(WiredInternalVariableSupport.normalizeKey(key));
|
||||
}
|
||||
|
||||
private boolean hasRoomEntryMethod(Habbo habbo) {
|
||||
if (habbo == null) return false;
|
||||
|
||||
String roomEntryMethod = habbo.getHabboInfo().getRoomEntryMethod();
|
||||
return roomEntryMethod != null && !roomEntryMethod.trim().isEmpty() && !"unknown".equalsIgnoreCase(roomEntryMethod);
|
||||
}
|
||||
|
||||
private Integer readUserInternalValue(Room room, RoomUnit roomUnit, String key) {
|
||||
return WiredInternalVariableSupport.readUserValue(room, roomUnit, key);
|
||||
}
|
||||
|
||||
private Integer readFurniInternalValue(Room room, HabboItem item, String key) {
|
||||
return WiredInternalVariableSupport.readFurniValue(room, item, key);
|
||||
}
|
||||
|
||||
private Integer readRoomInternalValue(Room room, String key) {
|
||||
return WiredInternalVariableSupport.readRoomValue(room, key);
|
||||
}
|
||||
|
||||
private Integer getUserTeamScore(Room room, Habbo habbo) {
|
||||
if (room == null || habbo == null || habbo.getHabboInfo().getGamePlayer() == null) return null;
|
||||
|
||||
Game game = this.resolveTeamGame(room, habbo);
|
||||
GamePlayer gamePlayer = habbo.getHabboInfo().getGamePlayer();
|
||||
|
||||
if (game == null || gamePlayer.getTeamColor() == null) return gamePlayer.getScore();
|
||||
|
||||
GameTeam team = game.getTeam(gamePlayer.getTeamColor());
|
||||
return (team != null) ? team.getTotalScore() : gamePlayer.getScore();
|
||||
}
|
||||
|
||||
private Integer getTeamColorId(int effectId) {
|
||||
TeamEffectData data = this.getTeamEffectData(effectId);
|
||||
return data == null ? null : data.colorId;
|
||||
}
|
||||
|
||||
private Integer getTeamTypeId(int effectId) {
|
||||
TeamEffectData data = this.getTeamEffectData(effectId);
|
||||
return data == null ? null : data.typeId;
|
||||
}
|
||||
|
||||
private int getTeamMetric(Room room, GameTeamColors color, boolean score) {
|
||||
Game game = this.resolveTeamGame(room, null);
|
||||
if (game == null || color == null) return 0;
|
||||
|
||||
GameTeam team = game.getTeam(color);
|
||||
if (team == null) return 0;
|
||||
|
||||
return score ? team.getTotalScore() : team.getMembers().size();
|
||||
}
|
||||
|
||||
private Game resolveTeamGame(Room room, Habbo habbo) {
|
||||
if (room == null) return null;
|
||||
|
||||
if (habbo != null && habbo.getHabboInfo() != null && habbo.getHabboInfo().getCurrentGame() != null) {
|
||||
Game game = room.getGame(habbo.getHabboInfo().getCurrentGame());
|
||||
if (game != null) return game;
|
||||
}
|
||||
|
||||
Game wiredGame = room.getGame(WiredGame.class);
|
||||
if (wiredGame != null) return wiredGame;
|
||||
|
||||
Game freezeGame = room.getGame(FreezeGame.class);
|
||||
if (freezeGame != null) return freezeGame;
|
||||
|
||||
return room.getGame(BattleBanzaiGame.class);
|
||||
}
|
||||
|
||||
private TeamEffectData getTeamEffectData(int effectValue) {
|
||||
if (effectValue <= 0) return null;
|
||||
|
||||
if (effectValue >= 223 && effectValue <= 226) return new TeamEffectData(effectValue - 222, 0);
|
||||
if (effectValue >= 33 && effectValue <= 36) return new TeamEffectData(effectValue - 32, 1);
|
||||
if (effectValue >= 40 && effectValue <= 43) return new TeamEffectData(effectValue - 39, 2);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void refreshReferenceItems() {
|
||||
THashSet<HabboItem> staleItems = new THashSet<>();
|
||||
Room room = this.getRoom();
|
||||
|
||||
if (room == null) {
|
||||
staleItems.addAll(this.referenceSelectedItems);
|
||||
} else {
|
||||
for (HabboItem item : this.referenceSelectedItems) {
|
||||
if (item == null || item.getRoomId() != room.getId() || room.getHabboItem(item.getId()) == null) {
|
||||
staleItems.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.referenceSelectedItems.removeAll(staleItems);
|
||||
}
|
||||
|
||||
private String serializeStringData() {
|
||||
return (this.variableToken == null ? "" : this.variableToken) + DELIM + (this.referenceVariableToken == null ? "" : this.referenceVariableToken);
|
||||
}
|
||||
|
||||
private void setVariableToken(String token) {
|
||||
this.variableToken = normalizeVariableToken(token);
|
||||
this.variableItemId = getCustomItemId(this.variableToken);
|
||||
}
|
||||
|
||||
private void setReferenceVariableToken(String token) {
|
||||
this.referenceVariableToken = normalizeVariableToken(token);
|
||||
this.referenceVariableItemId = getCustomItemId(this.referenceVariableToken);
|
||||
}
|
||||
|
||||
private List<Integer> toIds(THashSet<HabboItem> items) {
|
||||
List<Integer> ids = new ArrayList<>();
|
||||
|
||||
for (HabboItem item : items) {
|
||||
if (item != null) ids.add(item.getId());
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
private static int normalizeReferenceMode(int value) {
|
||||
return (value == REFERENCE_VARIABLE) ? REFERENCE_VARIABLE : REFERENCE_CONSTANT;
|
||||
}
|
||||
|
||||
private static int normalizeReferenceTargetType(int value) {
|
||||
return switch (value) {
|
||||
case TARGET_FURNI, TARGET_CONTEXT, TARGET_ROOM -> value;
|
||||
default -> TARGET_USER;
|
||||
};
|
||||
}
|
||||
|
||||
private static int normalizeReferenceFurniSource(int value) {
|
||||
return switch (value) {
|
||||
case SOURCE_SECONDARY_SELECTED, WiredSourceUtil.SOURCE_SELECTOR, WiredSourceUtil.SOURCE_SIGNAL -> value;
|
||||
default -> WiredSourceUtil.SOURCE_TRIGGER;
|
||||
};
|
||||
}
|
||||
|
||||
private static int normalizeComparison(int value) {
|
||||
return switch (value) {
|
||||
case COMPARISON_GREATER_THAN, COMPARISON_GREATER_THAN_OR_EQUAL, COMPARISON_LESS_THAN_OR_EQUAL, COMPARISON_LESS_THAN, COMPARISON_NOT_EQUAL -> value;
|
||||
default -> COMPARISON_EQUAL;
|
||||
};
|
||||
}
|
||||
|
||||
private static int normalizeUserSource(int value) {
|
||||
return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
|
||||
}
|
||||
|
||||
protected static boolean isCustomVariableToken(String token) {
|
||||
return token != null && token.startsWith(CUSTOM_TOKEN_PREFIX);
|
||||
}
|
||||
|
||||
protected static boolean isInternalVariableToken(String token) {
|
||||
return token != null && token.startsWith(INTERNAL_TOKEN_PREFIX);
|
||||
}
|
||||
|
||||
private static int getCustomItemId(String token) {
|
||||
if (!isCustomVariableToken(token)) return 0;
|
||||
|
||||
try {
|
||||
return Integer.parseInt(token.substring(CUSTOM_TOKEN_PREFIX.length()));
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getInternalVariableKey(String token) {
|
||||
return isInternalVariableToken(token) ? WiredInternalVariableSupport.normalizeKey(token.substring(INTERNAL_TOKEN_PREFIX.length())) : "";
|
||||
}
|
||||
|
||||
protected static String normalizeVariableToken(String token) {
|
||||
if (token == null) return "";
|
||||
|
||||
String normalized = token.trim();
|
||||
if (normalized.isEmpty()) return "";
|
||||
if (isCustomVariableToken(normalized)) return normalized;
|
||||
if (isInternalVariableToken(normalized)) return INTERNAL_TOKEN_PREFIX + WiredInternalVariableSupport.normalizeKey(normalized.substring(INTERNAL_TOKEN_PREFIX.length()));
|
||||
|
||||
try {
|
||||
int parsed = Integer.parseInt(normalized);
|
||||
return (parsed > 0) ? (CUSTOM_TOKEN_PREFIX + parsed) : "";
|
||||
} catch (NumberFormatException e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean canUseUserInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseUserReference(key);
|
||||
}
|
||||
|
||||
private static boolean canUseFurniInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseFurniReference(key);
|
||||
}
|
||||
|
||||
private static boolean canUseRoomInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseRoomReference(key);
|
||||
}
|
||||
|
||||
private static boolean canUseContextInternalReference(String key) {
|
||||
return WiredInternalVariableSupport.canUseContextReference(key);
|
||||
}
|
||||
|
||||
private static int param(int[] params, int index, int fallback) {
|
||||
return (params != null && params.length > index) ? params[index] : fallback;
|
||||
}
|
||||
|
||||
private static String[] parseStringData(String value) {
|
||||
return (value == null || value.isEmpty()) ? new String[0] : value.split("\\t", -1);
|
||||
}
|
||||
|
||||
private static int parseInteger(String value) {
|
||||
try {
|
||||
return (value == null || value.trim().isEmpty()) ? 0 : Integer.parseInt(value.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class JsonData {
|
||||
boolean selectByValue;
|
||||
int comparison;
|
||||
int referenceMode;
|
||||
int referenceConstantValue;
|
||||
int referenceTargetType;
|
||||
int referenceUserSource;
|
||||
int referenceFurniSource;
|
||||
boolean filterExisting;
|
||||
boolean invert;
|
||||
String variableToken;
|
||||
int variableItemId;
|
||||
String referenceVariableToken;
|
||||
int referenceVariableItemId;
|
||||
List<Integer> selectedItemIds;
|
||||
int delay;
|
||||
|
||||
JsonData(boolean selectByValue, int comparison, int referenceMode, int referenceConstantValue, int referenceTargetType, int referenceUserSource, int referenceFurniSource, boolean filterExisting, boolean invert, String variableToken, int variableItemId, String referenceVariableToken, int referenceVariableItemId, List<Integer> selectedItemIds, int delay) {
|
||||
this.selectByValue = selectByValue;
|
||||
this.comparison = comparison;
|
||||
this.referenceMode = referenceMode;
|
||||
this.referenceConstantValue = referenceConstantValue;
|
||||
this.referenceTargetType = referenceTargetType;
|
||||
this.referenceUserSource = referenceUserSource;
|
||||
this.referenceFurniSource = referenceFurniSource;
|
||||
this.filterExisting = filterExisting;
|
||||
this.invert = invert;
|
||||
this.variableToken = variableToken;
|
||||
this.variableItemId = variableItemId;
|
||||
this.referenceVariableToken = referenceVariableToken;
|
||||
this.referenceVariableItemId = referenceVariableItemId;
|
||||
this.selectedItemIds = selectedItemIds;
|
||||
this.delay = delay;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ReferenceSnapshot {
|
||||
final int targetType;
|
||||
final LinkedHashMap<Integer, Integer> values = new LinkedHashMap<>();
|
||||
|
||||
ReferenceSnapshot(int targetType) {
|
||||
this.targetType = targetType;
|
||||
}
|
||||
|
||||
void add(int entityId, int value) {
|
||||
this.values.put(entityId, value);
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return this.values.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TeamEffectData {
|
||||
final int colorId;
|
||||
final int typeId;
|
||||
|
||||
TeamEffectData(int colorId, int typeId) {
|
||||
this.colorId = colorId;
|
||||
this.typeId = typeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -141,6 +141,18 @@ public class WiredTriggerHabboSaysKeyword extends InteractionWiredTrigger {
|
||||
return this.hideMessage;
|
||||
}
|
||||
|
||||
public boolean isOwnerOnly() {
|
||||
return this.ownerOnly;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
public int getMatchMode() {
|
||||
return this.matchMode;
|
||||
}
|
||||
|
||||
private boolean matchesText(String text) {
|
||||
String normalizedText = text.toLowerCase().trim();
|
||||
String normalizedKey = this.key.toLowerCase().trim();
|
||||
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
package com.eu.habbo.habbohotel.items.interactions.wired.triggers;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger;
|
||||
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.habbohotel.rooms.WiredVariableDefinitionInfo;
|
||||
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||
import com.eu.habbo.habbohotel.wired.WiredTriggerType;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredEvent;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredTriggerSaveException;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class WiredTriggerVariableChanged extends InteractionWiredTrigger {
|
||||
public static final WiredTriggerType type = WiredTriggerType.VARIABLE_CHANGED;
|
||||
|
||||
public static final int TARGET_USER = 0;
|
||||
public static final int TARGET_FURNI = 1;
|
||||
public static final int TARGET_ROOM = 3;
|
||||
|
||||
private static final String CUSTOM_TOKEN_PREFIX = "custom:";
|
||||
|
||||
private String variableToken = "";
|
||||
private int variableItemId = 0;
|
||||
private int targetType = TARGET_USER;
|
||||
private boolean createdEnabled = true;
|
||||
private boolean valueChangedEnabled = true;
|
||||
private boolean increasedEnabled = true;
|
||||
private boolean decreasedEnabled = true;
|
||||
private boolean unchangedEnabled = true;
|
||||
private boolean deletedEnabled = true;
|
||||
|
||||
public WiredTriggerVariableChanged(ResultSet set, Item baseItem) throws SQLException {
|
||||
super(set, baseItem);
|
||||
}
|
||||
|
||||
public WiredTriggerVariableChanged(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
|
||||
super(id, userId, item, extradata, limitedStack, limitedSells);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(HabboItem triggerItem, WiredEvent event) {
|
||||
if (event == null || event.getType() != WiredEvent.Type.VARIABLE_CHANGED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.getVariableTargetType() != this.targetType || event.getVariableDefinitionItemId() != this.variableItemId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.createdEnabled && event.isVariableCreated()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.deletedEnabled && event.isVariableDeleted()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.valueChangedEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return switch (event.getVariableChangeKind()) {
|
||||
case INCREASED -> this.increasedEnabled;
|
||||
case DECREASED -> this.decreasedEnabled;
|
||||
case UNCHANGED -> this.unchangedEnabled;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WiredTriggerType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeWiredData(ServerMessage message, Room room) {
|
||||
message.appendBoolean(false);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getBaseItem().getSpriteId());
|
||||
message.appendInt(this.getId());
|
||||
message.appendString(this.variableToken == null ? "" : this.variableToken);
|
||||
message.appendInt(7);
|
||||
message.appendInt(this.targetType);
|
||||
message.appendInt(this.createdEnabled ? 1 : 0);
|
||||
message.appendInt(this.valueChangedEnabled ? 1 : 0);
|
||||
message.appendInt(this.increasedEnabled ? 1 : 0);
|
||||
message.appendInt(this.decreasedEnabled ? 1 : 0);
|
||||
message.appendInt(this.unchangedEnabled ? 1 : 0);
|
||||
message.appendInt(this.deletedEnabled ? 1 : 0);
|
||||
message.appendInt(0);
|
||||
message.appendInt(this.getType().code);
|
||||
message.appendInt(0);
|
||||
message.appendInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings) {
|
||||
return this.saveData(settings, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings, GameClient gameClient) {
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
|
||||
int[] params = settings.getIntParams();
|
||||
|
||||
this.targetType = normalizeTargetType((params.length > 0) ? params[0] : TARGET_USER);
|
||||
this.createdEnabled = (params.length <= 1) || (params[1] == 1);
|
||||
this.valueChangedEnabled = (params.length <= 2) || (params[2] == 1);
|
||||
this.increasedEnabled = (params.length <= 3) || (params[3] == 1);
|
||||
this.decreasedEnabled = (params.length <= 4) || (params[4] == 1);
|
||||
this.unchangedEnabled = (params.length <= 5) || (params[5] == 1);
|
||||
this.deletedEnabled = (params.length <= 6) || (params[6] == 1);
|
||||
this.setVariableToken(normalizeVariableToken(settings.getStringParam()));
|
||||
this.normalizeOptions();
|
||||
|
||||
if (this.variableItemId <= 0) {
|
||||
throw new WiredTriggerSaveException("wiredfurni.params.variables.validation.missing_variable");
|
||||
}
|
||||
|
||||
if (!this.hasAnyEnabledOption()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (room == null || !this.isValidDefinition(room)) {
|
||||
throw new WiredTriggerSaveException("wiredfurni.params.variables.validation.invalid_variable");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
return WiredManager.getGson().toJson(new JsonData(
|
||||
this.variableToken,
|
||||
this.variableItemId,
|
||||
this.targetType,
|
||||
this.createdEnabled,
|
||||
this.valueChangedEnabled,
|
||||
this.increasedEnabled,
|
||||
this.decreasedEnabled,
|
||||
this.unchangedEnabled,
|
||||
this.deletedEnabled
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadWiredData(ResultSet set, Room room) throws SQLException {
|
||||
this.onPickUp();
|
||||
|
||||
String wiredData = set.getString("wired_data");
|
||||
if (wiredData == null || wiredData.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wiredData.startsWith("{")) {
|
||||
this.setVariableToken(normalizeVariableToken(wiredData));
|
||||
return;
|
||||
}
|
||||
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.targetType = normalizeTargetType(data.targetType);
|
||||
this.createdEnabled = data.createdEnabled;
|
||||
this.valueChangedEnabled = data.valueChangedEnabled;
|
||||
this.increasedEnabled = data.increasedEnabled;
|
||||
this.decreasedEnabled = data.decreasedEnabled;
|
||||
this.unchangedEnabled = data.unchangedEnabled;
|
||||
this.deletedEnabled = data.deletedEnabled;
|
||||
this.setVariableToken(normalizeVariableToken((data.variableToken != null) ? data.variableToken : ((data.variableItemId > 0) ? String.valueOf(data.variableItemId) : "")));
|
||||
this.normalizeOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPickUp() {
|
||||
this.variableToken = "";
|
||||
this.variableItemId = 0;
|
||||
this.targetType = TARGET_USER;
|
||||
this.createdEnabled = true;
|
||||
this.valueChangedEnabled = true;
|
||||
this.increasedEnabled = true;
|
||||
this.decreasedEnabled = true;
|
||||
this.unchangedEnabled = true;
|
||||
this.deletedEnabled = true;
|
||||
}
|
||||
|
||||
private void setVariableToken(String token) {
|
||||
this.variableToken = normalizeVariableToken(token);
|
||||
this.variableItemId = getCustomItemId(this.variableToken);
|
||||
}
|
||||
|
||||
private void normalizeOptions() {
|
||||
if (!this.valueChangedEnabled) {
|
||||
this.increasedEnabled = false;
|
||||
this.decreasedEnabled = false;
|
||||
this.unchangedEnabled = false;
|
||||
}
|
||||
|
||||
if (this.targetType == TARGET_ROOM) {
|
||||
this.createdEnabled = false;
|
||||
this.deletedEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasAnyEnabledOption() {
|
||||
return this.createdEnabled
|
||||
|| this.deletedEnabled
|
||||
|| (this.valueChangedEnabled && (this.increasedEnabled || this.decreasedEnabled || this.unchangedEnabled));
|
||||
}
|
||||
|
||||
private boolean isValidDefinition(Room room) {
|
||||
WiredVariableDefinitionInfo definitionInfo = switch (this.targetType) {
|
||||
case TARGET_FURNI -> room.getFurniVariableManager().getDefinitionInfo(this.variableItemId);
|
||||
case TARGET_ROOM -> room.getRoomVariableManager().getDefinitionInfo(this.variableItemId);
|
||||
default -> room.getUserVariableManager().getDefinitionInfo(this.variableItemId);
|
||||
};
|
||||
|
||||
return definitionInfo != null;
|
||||
}
|
||||
|
||||
private static int normalizeTargetType(int value) {
|
||||
return switch (value) {
|
||||
case TARGET_FURNI, TARGET_ROOM -> value;
|
||||
default -> TARGET_USER;
|
||||
};
|
||||
}
|
||||
|
||||
private static String normalizeVariableToken(String token) {
|
||||
if (token == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String normalized = token.trim();
|
||||
if (normalized.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (normalized.startsWith(CUSTOM_TOKEN_PREFIX)) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
try {
|
||||
int itemId = Integer.parseInt(normalized);
|
||||
return (itemId > 0) ? (CUSTOM_TOKEN_PREFIX + itemId) : "";
|
||||
} catch (NumberFormatException ignored) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static int getCustomItemId(String token) {
|
||||
if (token == null || !token.startsWith(CUSTOM_TOKEN_PREFIX)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
return Integer.parseInt(token.substring(CUSTOM_TOKEN_PREFIX.length()));
|
||||
} catch (NumberFormatException ignored) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static class JsonData {
|
||||
String variableToken;
|
||||
int variableItemId;
|
||||
int targetType;
|
||||
boolean createdEnabled;
|
||||
boolean valueChangedEnabled;
|
||||
boolean increasedEnabled;
|
||||
boolean decreasedEnabled;
|
||||
boolean unchangedEnabled;
|
||||
boolean deletedEnabled;
|
||||
|
||||
JsonData(String variableToken, int variableItemId, int targetType, boolean createdEnabled, boolean valueChangedEnabled, boolean increasedEnabled, boolean decreasedEnabled, boolean unchangedEnabled, boolean deletedEnabled) {
|
||||
this.variableToken = variableToken;
|
||||
this.variableItemId = variableItemId;
|
||||
this.targetType = targetType;
|
||||
this.createdEnabled = createdEnabled;
|
||||
this.valueChangedEnabled = valueChangedEnabled;
|
||||
this.increasedEnabled = increasedEnabled;
|
||||
this.decreasedEnabled = decreasedEnabled;
|
||||
this.unchangedEnabled = unchangedEnabled;
|
||||
this.deletedEnabled = deletedEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,11 +54,23 @@ public class ModToolManager {
|
||||
if (userId <= 0)
|
||||
return;
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT users.*, users_settings.*, permissions.rank_name, permissions.acc_hide_mail AS hide_mail, permissions.id AS rank_id FROM users INNER JOIN users_settings ON users.id = users_settings.user_id INNER JOIN permissions ON permissions.id = users.rank WHERE users.id = ? LIMIT 1")) {
|
||||
String query = Emulator.getGameEnvironment().getPermissionsManager().isNormalizedSchemaEnabled()
|
||||
? "SELECT users.*, users_settings.*, permission_ranks.rank_name, permission_ranks.id AS rank_id " +
|
||||
"FROM users " +
|
||||
"INNER JOIN users_settings ON users.id = users_settings.user_id " +
|
||||
"INNER JOIN permission_ranks ON permission_ranks.id = users.rank " +
|
||||
"WHERE users.id = ? LIMIT 1"
|
||||
: "SELECT users.*, users_settings.*, permissions.rank_name, permissions.acc_hide_mail AS hide_mail, permissions.id AS rank_id FROM users INNER JOIN users_settings ON users.id = users_settings.user_id INNER JOIN permissions ON permissions.id = users.rank WHERE users.id = ? LIMIT 1";
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement(query)) {
|
||||
statement.setInt(1, userId);
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
while (set.next()) {
|
||||
client.sendResponse(new ModToolUserInfoComposer(set));
|
||||
boolean hideMail = Emulator.getGameEnvironment().getPermissionsManager().isNormalizedSchemaEnabled()
|
||||
? Emulator.getGameEnvironment().getPermissionsManager().getRank(set.getInt("rank_id")).hasPermission("acc_hide_mail", false)
|
||||
: set.getBoolean("hide_mail");
|
||||
|
||||
client.sendResponse(new ModToolUserInfoComposer(set, hideMail));
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
|
||||
+156
-8
@@ -11,6 +11,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -20,6 +21,7 @@ public class PermissionsManager {
|
||||
private final TIntObjectHashMap<Rank> ranks;
|
||||
private final TIntIntHashMap enables;
|
||||
private final THashMap<String, List<Rank>> badges;
|
||||
private volatile boolean normalizedSchemaEnabled;
|
||||
|
||||
public PermissionsManager() {
|
||||
long millis = System.currentTimeMillis();
|
||||
@@ -40,7 +42,30 @@ public class PermissionsManager {
|
||||
private void loadPermissions() {
|
||||
this.badges.clear();
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); Statement statement = connection.createStatement(); ResultSet set = statement.executeQuery("SELECT * FROM permissions ORDER BY id ASC")) {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) {
|
||||
if (this.hasNormalizedPermissionsSchema(connection)) {
|
||||
try {
|
||||
if (this.loadPermissionsNormalized(connection)) {
|
||||
this.normalizedSchemaEnabled = true;
|
||||
LOGGER.info("Permissions Manager -> Using normalized permissions schema.");
|
||||
return;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOGGER.warn("Permissions Manager -> Failed to load normalized permissions schema, falling back to legacy permissions table.", e);
|
||||
}
|
||||
}
|
||||
|
||||
this.normalizedSchemaEnabled = false;
|
||||
this.badges.clear();
|
||||
LOGGER.info("Permissions Manager -> Using legacy permissions schema.");
|
||||
this.loadPermissionsLegacy(connection);
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Caught SQL exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadPermissionsLegacy(Connection connection) throws SQLException {
|
||||
try (Statement statement = connection.createStatement(); ResultSet set = statement.executeQuery("SELECT * FROM permissions ORDER BY id ASC")) {
|
||||
while (set.next()) {
|
||||
Rank rank = null;
|
||||
if (!this.ranks.containsKey(set.getInt("id"))) {
|
||||
@@ -51,16 +76,135 @@ public class PermissionsManager {
|
||||
rank.load(set);
|
||||
}
|
||||
|
||||
if (rank != null && !rank.getBadge().isEmpty()) {
|
||||
if (!this.badges.containsKey(rank.getBadge())) {
|
||||
this.badges.put(rank.getBadge(), new ArrayList<Rank>());
|
||||
}
|
||||
this.addBadgeMapping(rank);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.badges.get(rank.getBadge()).add(rank);
|
||||
private boolean loadPermissionsNormalized(Connection connection) throws SQLException {
|
||||
boolean hasRanks = false;
|
||||
List<Rank> loadedRanks = new ArrayList<>();
|
||||
|
||||
try (Statement statement = connection.createStatement(); ResultSet set = statement.executeQuery("SELECT * FROM permission_ranks ORDER BY id ASC")) {
|
||||
while (set.next()) {
|
||||
hasRanks = true;
|
||||
|
||||
Rank rank = this.ranks.get(set.getInt("id"));
|
||||
|
||||
if (rank == null) {
|
||||
rank = new Rank(set.getInt("id"));
|
||||
this.ranks.put(set.getInt("id"), rank);
|
||||
}
|
||||
|
||||
rank.loadNormalizedMetadata(set);
|
||||
this.addBadgeMapping(rank);
|
||||
loadedRanks.add(rank);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasRanks) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.ensureNormalizedRankColumns(connection, loadedRanks);
|
||||
|
||||
boolean hasDefinitions = false;
|
||||
|
||||
try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM permission_definitions ORDER BY permission_key ASC");
|
||||
ResultSet set = statement.executeQuery()) {
|
||||
ResultSetMetaData meta = set.getMetaData();
|
||||
Set<String> availableColumns = new HashSet<>();
|
||||
|
||||
for (int i = 1; i <= meta.getColumnCount(); i++) {
|
||||
availableColumns.add(meta.getColumnName(i).toLowerCase());
|
||||
}
|
||||
|
||||
for (Rank rank : loadedRanks) {
|
||||
if (!availableColumns.contains(("rank_" + rank.getId()).toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Caught SQL exception", e);
|
||||
|
||||
while (set.next()) {
|
||||
hasDefinitions = true;
|
||||
String permissionKey = set.getString("permission_key");
|
||||
|
||||
for (Rank rank : loadedRanks) {
|
||||
String rankColumn = "rank_" + rank.getId();
|
||||
|
||||
if (!availableColumns.contains(rankColumn.toLowerCase())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rank.setPermission(permissionKey, PermissionSetting.fromString(Integer.toString(set.getInt(rankColumn))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hasDefinitions;
|
||||
}
|
||||
|
||||
private void ensureNormalizedRankColumns(Connection connection, List<Rank> loadedRanks) throws SQLException {
|
||||
Set<String> availableColumns = new HashSet<>();
|
||||
|
||||
try (PreparedStatement statement = connection.prepareStatement("SELECT column_name FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'permission_definitions'");
|
||||
ResultSet set = statement.executeQuery()) {
|
||||
while (set.next()) {
|
||||
availableColumns.add(set.getString("column_name").toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
for (Rank rank : loadedRanks) {
|
||||
String rankColumn = "rank_" + rank.getId();
|
||||
|
||||
if (availableColumns.contains(rankColumn.toLowerCase())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
statement.execute("ALTER TABLE permission_definitions ADD COLUMN `" + rankColumn + "` tinyint(3) unsigned NOT NULL DEFAULT 0");
|
||||
}
|
||||
|
||||
availableColumns.add(rankColumn.toLowerCase());
|
||||
LOGGER.info("Permissions Manager -> Added missing normalized permission column {}.", rankColumn);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasNormalizedPermissionsSchema(Connection connection) throws SQLException {
|
||||
if (!this.tableExists(connection, "permission_ranks") || !this.tableExists(connection, "permission_definitions")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.tableHasRows(connection, "permission_ranks")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.tableHasRows(connection, "permission_definitions");
|
||||
}
|
||||
|
||||
private boolean tableExists(Connection connection, String tableName) throws SQLException {
|
||||
try (PreparedStatement statement = connection.prepareStatement("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?")) {
|
||||
statement.setString(1, tableName);
|
||||
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
return set.next() && set.getInt(1) > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean tableHasRows(Connection connection, String tableName) throws SQLException {
|
||||
try (Statement statement = connection.createStatement(); ResultSet set = statement.executeQuery("SELECT COUNT(*) FROM " + tableName)) {
|
||||
return set.next() && set.getInt(1) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void addBadgeMapping(Rank rank) {
|
||||
if (rank != null && !rank.getBadge().isEmpty()) {
|
||||
if (!this.badges.containsKey(rank.getBadge())) {
|
||||
this.badges.put(rank.getBadge(), new ArrayList<Rank>());
|
||||
}
|
||||
|
||||
this.badges.get(rank.getBadge()).add(rank);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,4 +283,8 @@ public class PermissionsManager {
|
||||
public List<Rank> getAllRanks() {
|
||||
return new ArrayList<>(this.ranks.valueCollection());
|
||||
}
|
||||
|
||||
public boolean isNormalizedSchemaEnabled() {
|
||||
return this.normalizedSchemaEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,32 +35,29 @@ public class Rank {
|
||||
private int gotwTimerAmount;
|
||||
|
||||
public Rank(ResultSet set) throws SQLException {
|
||||
this(set.getInt("id"));
|
||||
this.load(set);
|
||||
}
|
||||
|
||||
public Rank(int id) {
|
||||
this.permissions = new THashMap<>();
|
||||
this.variables = new THashMap<>();
|
||||
this.id = set.getInt("id");
|
||||
this.level = set.getInt("level");
|
||||
this.id = id;
|
||||
this.level = 1;
|
||||
this.diamondsTimerAmount = 1;
|
||||
this.creditsTimerAmount = 1;
|
||||
this.pixelsTimerAmount = 1;
|
||||
this.gotwTimerAmount = 1;
|
||||
|
||||
this.load(set);
|
||||
}
|
||||
|
||||
public void load(ResultSet set) throws SQLException {
|
||||
this.permissions.clear();
|
||||
this.variables.clear();
|
||||
|
||||
this.loadMetadata(set);
|
||||
|
||||
ResultSetMetaData meta = set.getMetaData();
|
||||
this.name = set.getString("rank_name");
|
||||
this.badge = set.getString("badge");
|
||||
this.roomEffect = set.getInt("room_effect");
|
||||
this.logCommands = set.getString("log_commands").equals("1");
|
||||
this.prefix = set.getString("prefix");
|
||||
this.prefixColor = set.getString("prefix_color");
|
||||
this.level = set.getInt("level");
|
||||
this.diamondsTimerAmount = set.getInt("auto_points_amount");
|
||||
this.creditsTimerAmount = set.getInt("auto_credits_amount");
|
||||
this.pixelsTimerAmount = set.getInt("auto_pixels_amount");
|
||||
this.gotwTimerAmount = set.getInt("auto_gotw_amount");
|
||||
this.hasPrefix = !this.prefix.isEmpty();
|
||||
|
||||
for (int i = 1; i < meta.getColumnCount() + 1; i++) {
|
||||
String columnName = meta.getColumnName(i);
|
||||
if (columnName.startsWith("cmd_") || columnName.startsWith("acc_")) {
|
||||
@@ -71,6 +68,51 @@ public class Rank {
|
||||
}
|
||||
}
|
||||
|
||||
public void loadNormalizedMetadata(ResultSet set) throws SQLException {
|
||||
this.permissions.clear();
|
||||
this.variables.clear();
|
||||
this.loadMetadata(set);
|
||||
this.storeMetadataVariables();
|
||||
}
|
||||
|
||||
public void setPermission(String key, PermissionSetting setting) {
|
||||
this.permissions.put(key, new Permission(key, setting));
|
||||
}
|
||||
|
||||
private void loadMetadata(ResultSet set) throws SQLException {
|
||||
this.name = this.safeString(set.getString("rank_name"));
|
||||
this.badge = this.safeString(set.getString("badge"));
|
||||
this.roomEffect = set.getInt("room_effect");
|
||||
this.logCommands = "1".equals(this.safeString(set.getString("log_commands")));
|
||||
this.prefix = this.safeString(set.getString("prefix"));
|
||||
this.prefixColor = this.safeString(set.getString("prefix_color"));
|
||||
this.level = set.getInt("level");
|
||||
this.diamondsTimerAmount = set.getInt("auto_points_amount");
|
||||
this.creditsTimerAmount = set.getInt("auto_credits_amount");
|
||||
this.pixelsTimerAmount = set.getInt("auto_pixels_amount");
|
||||
this.gotwTimerAmount = set.getInt("auto_gotw_amount");
|
||||
this.hasPrefix = !this.prefix.isEmpty();
|
||||
}
|
||||
|
||||
private void storeMetadataVariables() {
|
||||
this.variables.put("id", Integer.toString(this.id));
|
||||
this.variables.put("rank_name", this.name);
|
||||
this.variables.put("badge", this.badge);
|
||||
this.variables.put("room_effect", Integer.toString(this.roomEffect));
|
||||
this.variables.put("log_commands", this.logCommands ? "1" : "0");
|
||||
this.variables.put("prefix", this.prefix);
|
||||
this.variables.put("prefix_color", this.prefixColor);
|
||||
this.variables.put("level", Integer.toString(this.level));
|
||||
this.variables.put("auto_points_amount", Integer.toString(this.diamondsTimerAmount));
|
||||
this.variables.put("auto_credits_amount", Integer.toString(this.creditsTimerAmount));
|
||||
this.variables.put("auto_pixels_amount", Integer.toString(this.pixelsTimerAmount));
|
||||
this.variables.put("auto_gotw_amount", Integer.toString(this.gotwTimerAmount));
|
||||
}
|
||||
|
||||
private String safeString(String value) {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
|
||||
public boolean hasPermission(String key, boolean isRoomOwner) {
|
||||
if (this.permissions.containsKey(key)) {
|
||||
Permission permission = this.permissions.get(key);
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.eu.habbo.habbohotel.bots.Bot;
|
||||
import com.eu.habbo.habbohotel.games.Game;
|
||||
import com.eu.habbo.habbohotel.guilds.Guild;
|
||||
import com.eu.habbo.habbohotel.guilds.GuildMember;
|
||||
import com.eu.habbo.habbohotel.guilds.GuildRank;
|
||||
import com.eu.habbo.habbohotel.items.FurnitureType;
|
||||
import com.eu.habbo.habbohotel.items.Item;
|
||||
import com.eu.habbo.habbohotel.items.interactions.*;
|
||||
@@ -30,6 +31,7 @@ import com.eu.habbo.messages.outgoing.rooms.UpdateStackHeightComposer;
|
||||
import com.eu.habbo.messages.outgoing.rooms.items.*;
|
||||
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserIgnoredComposer;
|
||||
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer;
|
||||
import com.eu.habbo.messages.outgoing.wired.WiredRoomSettingsDataComposer;
|
||||
import com.eu.habbo.plugin.Event;
|
||||
import com.eu.habbo.plugin.events.furniture.FurniturePickedUpEvent;
|
||||
import com.eu.habbo.plugin.events.rooms.RoomLoadedEvent;
|
||||
@@ -75,6 +77,9 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
private RoomRollerManager rollerManager;
|
||||
private RoomMessagingManager messagingManager;
|
||||
private RoomCycleManager cycleManager;
|
||||
private RoomUserVariableManager userVariableManager;
|
||||
private RoomFurniVariableManager furniVariableManager;
|
||||
private RoomVariableManager roomVariableManager;
|
||||
|
||||
public static final Comparator<Room> SORT_SCORE = (o1, o2) -> o2.getScore() - o1.getScore();
|
||||
public static final Comparator<Room> SORT_ID = (o1, o2) -> o2.getId() - o1.getId();
|
||||
@@ -92,6 +97,14 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
public static int ROLLERS_MAXIMUM_ROLL_AVATARS = 1;
|
||||
public static boolean MUTEAREA_CAN_WHISPER = false;
|
||||
public static double MAXIMUM_FURNI_HEIGHT = 40d;
|
||||
public static final int WIRED_ACCESS_EVERYONE = 1;
|
||||
public static final int WIRED_ACCESS_USERS_WITH_RIGHTS = 2;
|
||||
public static final int WIRED_ACCESS_GROUP_MEMBERS = 4;
|
||||
public static final int WIRED_ACCESS_GROUP_ADMINS = 8;
|
||||
public static final int WIRED_ACCESS_ALLOWED_INSPECT_MASK = WIRED_ACCESS_EVERYONE | WIRED_ACCESS_USERS_WITH_RIGHTS | WIRED_ACCESS_GROUP_MEMBERS | WIRED_ACCESS_GROUP_ADMINS;
|
||||
public static final int WIRED_ACCESS_ALLOWED_MODIFY_MASK = WIRED_ACCESS_USERS_WITH_RIGHTS | WIRED_ACCESS_GROUP_MEMBERS | WIRED_ACCESS_GROUP_ADMINS;
|
||||
public static final int WIRED_ACCESS_DEFAULT_INSPECT_MASK = 0;
|
||||
public static final int WIRED_ACCESS_DEFAULT_MODIFY_MASK = 0;
|
||||
|
||||
static {
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
@@ -175,6 +188,10 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
private volatile boolean muted;
|
||||
private RoomSpecialTypes roomSpecialTypes;
|
||||
private TraxManager traxManager;
|
||||
private final Object wiredSettingsLock = new Object();
|
||||
private volatile boolean wiredSettingsLoaded;
|
||||
private int wiredInspectMask = WIRED_ACCESS_DEFAULT_INSPECT_MASK;
|
||||
private int wiredModifyMask = WIRED_ACCESS_DEFAULT_MODIFY_MASK;
|
||||
|
||||
public final THashMap<String, Object> cache;
|
||||
|
||||
@@ -269,6 +286,9 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
this.rollerManager = new RoomRollerManager(this);
|
||||
this.messagingManager = new RoomMessagingManager(this);
|
||||
this.cycleManager = new RoomCycleManager(this);
|
||||
this.userVariableManager = new RoomUserVariableManager(this);
|
||||
this.furniVariableManager = new RoomFurniVariableManager(this);
|
||||
this.roomVariableManager = new RoomVariableManager(this);
|
||||
}
|
||||
|
||||
// ==================== MANAGER GETTERS ====================
|
||||
@@ -350,6 +370,18 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
return this.cycleManager;
|
||||
}
|
||||
|
||||
public RoomUserVariableManager getUserVariableManager() {
|
||||
return this.userVariableManager;
|
||||
}
|
||||
|
||||
public RoomFurniVariableManager getFurniVariableManager() {
|
||||
return this.furniVariableManager;
|
||||
}
|
||||
|
||||
public RoomVariableManager getRoomVariableManager() {
|
||||
return this.roomVariableManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the roller manager for this room.
|
||||
*/
|
||||
@@ -924,13 +956,13 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
|
||||
this.itemManager.saveAllPendingItems();
|
||||
|
||||
// Unregister all wired tickables for this room from the tick service
|
||||
com.eu.habbo.habbohotel.wired.core.WiredManager.unregisterRoomTickables(this);
|
||||
|
||||
if (this.roomSpecialTypes != null) {
|
||||
this.roomSpecialTypes.dispose();
|
||||
}
|
||||
|
||||
// Unregister all wired tickables for this room from the tick service
|
||||
com.eu.habbo.habbohotel.wired.core.WiredManager.unregisterRoomTickables(this);
|
||||
|
||||
// Clear wired engine caches for this room
|
||||
if (com.eu.habbo.habbohotel.wired.core.WiredManager.getStackIndex() != null) {
|
||||
com.eu.habbo.habbohotel.wired.core.WiredManager.getStackIndex().invalidateAll(this);
|
||||
@@ -938,6 +970,8 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
if (com.eu.habbo.habbohotel.wired.core.WiredManager.getEngine() != null) {
|
||||
com.eu.habbo.habbohotel.wired.core.WiredManager.getEngine().clearRoomRecursionDepth(this.id);
|
||||
com.eu.habbo.habbohotel.wired.core.WiredManager.getEngine().clearRoomRateLimiters(this.id);
|
||||
com.eu.habbo.habbohotel.wired.core.WiredManager.getEngine().clearRoomBan(this.id);
|
||||
com.eu.habbo.habbohotel.wired.core.WiredManager.getEngine().clearRoomDiagnostics(this.id);
|
||||
}
|
||||
|
||||
this.itemManager.clear();
|
||||
@@ -2127,20 +2161,108 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
return this.rightsManager.hasRights(habbo);
|
||||
}
|
||||
|
||||
public boolean hasExplicitRights(Habbo habbo) {
|
||||
return habbo != null && this.rights.contains(habbo.getHabboInfo().getId());
|
||||
}
|
||||
|
||||
public int getWiredInspectMask() {
|
||||
this.ensureWiredSettingsLoaded();
|
||||
return this.wiredInspectMask;
|
||||
}
|
||||
|
||||
public int getWiredModifyMask() {
|
||||
this.ensureWiredSettingsLoaded();
|
||||
return this.wiredModifyMask;
|
||||
}
|
||||
|
||||
public boolean canInspectWired(Habbo habbo) {
|
||||
if (habbo == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.canManageWiredSettings(habbo)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.ensureWiredSettingsLoaded();
|
||||
return this.matchesWiredAccessMask(habbo, this.wiredInspectMask, true);
|
||||
}
|
||||
|
||||
public boolean canModifyWired(Habbo habbo) {
|
||||
if (habbo == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.canManageWiredSettings(habbo)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.ensureWiredSettingsLoaded();
|
||||
return this.matchesWiredAccessMask(habbo, this.wiredModifyMask, false);
|
||||
}
|
||||
|
||||
public boolean canManageWiredSettings(Habbo habbo) {
|
||||
return habbo != null && this.isOwner(habbo);
|
||||
}
|
||||
|
||||
public boolean saveWiredSettings(int inspectMask, int modifyMask) {
|
||||
int sanitizedInspectMask = sanitizeWiredInspectMask(inspectMask);
|
||||
int sanitizedModifyMask = sanitizeWiredModifyMask(modifyMask);
|
||||
sanitizedInspectMask |= sanitizedModifyMask;
|
||||
|
||||
synchronized (this.wiredSettingsLock) {
|
||||
int previousInspectMask = this.wiredInspectMask;
|
||||
int previousModifyMask = this.wiredModifyMask;
|
||||
this.wiredInspectMask = sanitizedInspectMask;
|
||||
this.wiredModifyMask = sanitizedModifyMask;
|
||||
this.wiredSettingsLoaded = true;
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
"INSERT INTO room_wired_settings (room_id, inspect_mask, modify_mask) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE inspect_mask = VALUES(inspect_mask), modify_mask = VALUES(modify_mask)")) {
|
||||
statement.setInt(1, this.id);
|
||||
statement.setInt(2, sanitizedInspectMask);
|
||||
statement.setInt(3, sanitizedModifyMask);
|
||||
statement.executeUpdate();
|
||||
this.pushWiredSettingsToCurrentHabbos();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
this.wiredInspectMask = previousInspectMask;
|
||||
this.wiredModifyMask = previousModifyMask;
|
||||
LOGGER.error("Caught SQL exception while saving wired room settings", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void giveRights(Habbo habbo) {
|
||||
this.rightsManager.giveRights(habbo);
|
||||
if (habbo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.giveRights(habbo.getHabboInfo().getId());
|
||||
}
|
||||
|
||||
public void giveRights(int userId) {
|
||||
this.rightsManager.giveRights(userId);
|
||||
|
||||
if (!this.rights.contains(userId)) {
|
||||
this.rights.add(userId);
|
||||
}
|
||||
|
||||
this.pushWiredSettingsToCurrentHabbos();
|
||||
}
|
||||
|
||||
public void removeRights(int userId) {
|
||||
this.rightsManager.removeRights(userId);
|
||||
this.rights.remove(userId);
|
||||
this.pushWiredSettingsToCurrentHabbos();
|
||||
}
|
||||
|
||||
public void removeAllRights() {
|
||||
this.rightsManager.removeAllRights();
|
||||
this.rights.clear();
|
||||
this.pushWiredSettingsToCurrentHabbos();
|
||||
}
|
||||
|
||||
void refreshRightsInRoom() {
|
||||
@@ -2167,6 +2289,111 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
return this.bannedHabbos;
|
||||
}
|
||||
|
||||
private void ensureWiredSettingsLoaded() {
|
||||
if (this.wiredSettingsLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (this.wiredSettingsLock) {
|
||||
if (this.wiredSettingsLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.wiredInspectMask = WIRED_ACCESS_DEFAULT_INSPECT_MASK;
|
||||
this.wiredModifyMask = WIRED_ACCESS_DEFAULT_MODIFY_MASK;
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
"SELECT inspect_mask, modify_mask FROM room_wired_settings WHERE room_id = ? LIMIT 1")) {
|
||||
statement.setInt(1, this.id);
|
||||
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
if (set.next()) {
|
||||
this.wiredInspectMask = sanitizeWiredInspectMask(set.getInt("inspect_mask"));
|
||||
this.wiredModifyMask = sanitizeWiredModifyMask(set.getInt("modify_mask"));
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Caught SQL exception while loading wired room settings", e);
|
||||
}
|
||||
|
||||
this.wiredSettingsLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean matchesWiredAccessMask(Habbo habbo, int mask, boolean allowEveryone) {
|
||||
if (habbo == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (allowEveryone && hasWiredAccess(mask, WIRED_ACCESS_EVERYONE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasWiredAccess(mask, WIRED_ACCESS_USERS_WITH_RIGHTS) && this.hasExplicitRights(habbo)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasWiredAccess(mask, WIRED_ACCESS_GROUP_ADMINS) && this.isRoomGroupAdmin(habbo)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return hasWiredAccess(mask, WIRED_ACCESS_GROUP_MEMBERS) && this.isRoomGroupMember(habbo);
|
||||
}
|
||||
|
||||
private boolean isRoomGroupMember(Habbo habbo) {
|
||||
return habbo != null && this.guild > 0 && habbo.getHabboStats().hasGuild(this.guild);
|
||||
}
|
||||
|
||||
private boolean isRoomGroupAdmin(Habbo habbo) {
|
||||
if (!this.isRoomGroupMember(habbo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(this.guild, habbo.getHabboInfo().getId());
|
||||
|
||||
if (member == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GuildRank rank = member.getRank();
|
||||
return rank == GuildRank.OWNER || rank == GuildRank.ADMIN;
|
||||
}
|
||||
|
||||
private static boolean hasWiredAccess(int mask, int permissionMask) {
|
||||
return (mask & permissionMask) != 0;
|
||||
}
|
||||
|
||||
private static int sanitizeWiredInspectMask(int mask) {
|
||||
int sanitizedMask = mask & WIRED_ACCESS_ALLOWED_INSPECT_MASK;
|
||||
|
||||
if (hasWiredAccess(sanitizedMask, WIRED_ACCESS_GROUP_MEMBERS)) {
|
||||
sanitizedMask |= WIRED_ACCESS_GROUP_ADMINS;
|
||||
}
|
||||
|
||||
return sanitizedMask;
|
||||
}
|
||||
|
||||
private static int sanitizeWiredModifyMask(int mask) {
|
||||
int sanitizedMask = mask & WIRED_ACCESS_ALLOWED_MODIFY_MASK;
|
||||
|
||||
if (hasWiredAccess(sanitizedMask, WIRED_ACCESS_GROUP_MEMBERS)) {
|
||||
sanitizedMask |= WIRED_ACCESS_GROUP_ADMINS;
|
||||
}
|
||||
|
||||
return sanitizedMask;
|
||||
}
|
||||
|
||||
private void pushWiredSettingsToCurrentHabbos() {
|
||||
for (Habbo currentHabbo : this.getCurrentHabbos().values()) {
|
||||
if (currentHabbo == null || currentHabbo.getClient() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
currentHabbo.getClient().sendResponse(new WiredRoomSettingsDataComposer(this, currentHabbo));
|
||||
}
|
||||
}
|
||||
|
||||
public void addRoomBan(RoomBan roomBan) {
|
||||
this.rightsManager.addRoomBan(roomBan);
|
||||
}
|
||||
|
||||
@@ -328,7 +328,9 @@ public class RoomChatManager {
|
||||
suppressSaysOutput = WiredManager.shouldSuppressUserSaysOutput(
|
||||
habbo.getHabboInfo().getCurrentRoom(),
|
||||
habbo.getRoomUnit(),
|
||||
wiredSayMessage);
|
||||
wiredSayMessage,
|
||||
chatType.ordinal(),
|
||||
roomChatMessage.getBubble() != null ? roomChatMessage.getBubble().getType() : -1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,7 +409,12 @@ public class RoomChatManager {
|
||||
}
|
||||
|
||||
if (chatType != RoomChatType.WHISPER && !ignoreWired && !roomChatMessage.isCommand) {
|
||||
WiredManager.triggerUserSays(habbo.getHabboInfo().getCurrentRoom(), habbo.getRoomUnit(), wiredSayMessage);
|
||||
WiredManager.triggerUserSays(
|
||||
habbo.getHabboInfo().getCurrentRoom(),
|
||||
habbo.getRoomUnit(),
|
||||
wiredSayMessage,
|
||||
chatType.ordinal(),
|
||||
roomChatMessage.getBubble() != null ? roomChatMessage.getBubble().getType() : -1);
|
||||
}
|
||||
|
||||
// Notify bots and talking furniture
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,13 @@ import com.eu.habbo.habbohotel.items.interactions.games.tag.InteractionTagPole;
|
||||
import com.eu.habbo.habbohotel.items.interactions.pets.*;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.effects.WiredEffectSendSignal;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredBlob;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFurniVariable;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRoomVariable;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraUserVariable;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableEcho;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableReference;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraContextVariable;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.triggers.WiredTriggerReceiveSignal;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
@@ -773,6 +780,8 @@ public class RoomItemManager {
|
||||
if (specialTypes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.room.getFurniVariableManager().removeAssignmentsForFurni(item.getId());
|
||||
|
||||
boolean isWiredItem = false;
|
||||
|
||||
@@ -785,21 +794,50 @@ public class RoomItemManager {
|
||||
specialTypes.removeCycleTask((ICycleable) item);
|
||||
}
|
||||
|
||||
if (item instanceof InteractionBattleBanzaiTeleporter) {
|
||||
specialTypes.removeBanzaiTeleporter((InteractionBattleBanzaiTeleporter) item);
|
||||
} else if (item instanceof InteractionWiredTrigger) {
|
||||
specialTypes.removeTrigger((InteractionWiredTrigger) item);
|
||||
isWiredItem = true;
|
||||
} else if (item instanceof InteractionWiredEffect) {
|
||||
specialTypes.removeEffect((InteractionWiredEffect) item);
|
||||
isWiredItem = true;
|
||||
} else if (item instanceof InteractionWiredCondition) {
|
||||
specialTypes.removeCondition((InteractionWiredCondition) item);
|
||||
isWiredItem = true;
|
||||
} else if (item instanceof InteractionWiredExtra) {
|
||||
specialTypes.removeExtra((InteractionWiredExtra) item);
|
||||
isWiredItem = true;
|
||||
} else if (item instanceof InteractionRoller) {
|
||||
if (item instanceof InteractionBattleBanzaiTeleporter) {
|
||||
specialTypes.removeBanzaiTeleporter((InteractionBattleBanzaiTeleporter) item);
|
||||
} else if (item instanceof InteractionWiredTrigger) {
|
||||
specialTypes.removeTrigger((InteractionWiredTrigger) item);
|
||||
isWiredItem = true;
|
||||
} else if (item instanceof InteractionWiredEffect) {
|
||||
specialTypes.removeEffect((InteractionWiredEffect) item);
|
||||
isWiredItem = true;
|
||||
} else if (item instanceof InteractionWiredCondition) {
|
||||
specialTypes.removeCondition((InteractionWiredCondition) item);
|
||||
isWiredItem = true;
|
||||
} else if (item instanceof InteractionWiredExtra) {
|
||||
boolean removedContextDefinition = false;
|
||||
if (item instanceof WiredExtraUserVariable) {
|
||||
this.room.getUserVariableManager().removeDefinition(item.getId());
|
||||
} else if (item instanceof WiredExtraFurniVariable) {
|
||||
this.room.getFurniVariableManager().removeDefinition(item.getId());
|
||||
} else if (item instanceof WiredExtraRoomVariable) {
|
||||
this.room.getRoomVariableManager().removeDefinition(item.getId());
|
||||
} else if (item instanceof WiredExtraContextVariable) {
|
||||
removedContextDefinition = true;
|
||||
} else if (item instanceof WiredExtraVariableReference) {
|
||||
if (((WiredExtraVariableReference) item).isRoomReference()) {
|
||||
this.room.getRoomVariableManager().removeDefinition(item.getId());
|
||||
} else {
|
||||
this.room.getUserVariableManager().removeDefinition(item.getId());
|
||||
}
|
||||
} else if (item instanceof WiredExtraVariableEcho) {
|
||||
WiredExtraVariableEcho echo = (WiredExtraVariableEcho) item;
|
||||
|
||||
if (echo.isRoomEcho()) {
|
||||
this.room.getRoomVariableManager().removeDefinition(item.getId());
|
||||
} else if (echo.isFurniEcho()) {
|
||||
this.room.getFurniVariableManager().removeDefinition(item.getId());
|
||||
} else {
|
||||
this.room.getUserVariableManager().removeDefinition(item.getId());
|
||||
}
|
||||
}
|
||||
specialTypes.removeExtra((InteractionWiredExtra) item);
|
||||
if (removedContextDefinition) {
|
||||
WiredContextVariableSupport.broadcastDefinitions(this.room);
|
||||
}
|
||||
isWiredItem = true;
|
||||
} else if (item instanceof InteractionRoller) {
|
||||
specialTypes.removeRoller((InteractionRoller) item);
|
||||
} else if (item instanceof InteractionGameScoreboard) {
|
||||
specialTypes.removeScoreboard((InteractionGameScoreboard) item);
|
||||
|
||||
@@ -780,6 +780,7 @@ public class RoomManager {
|
||||
|
||||
habbo.getRoomUnit().setInvisible(false);
|
||||
room.addHabbo(habbo);
|
||||
room.getUserVariableManager().restorePermanentAssignments(habbo);
|
||||
|
||||
// Pre-send own wearing badges so the client cache is populated before the user clicks themselves
|
||||
habbo.getClient().sendResponse(new UserBadgesComposer(habbo.getInventory().getBadgesComponent().getWearingBadges(), habbo.getHabboInfo().getId()));
|
||||
|
||||
@@ -232,12 +232,14 @@ public class RoomUnitManager {
|
||||
habbo.getRoomUnit().getCurrentLocation().removeUnit(habbo.getRoomUnit());
|
||||
}
|
||||
|
||||
synchronized (this.room.roomUnitLock) {
|
||||
this.currentHabbos.remove(habbo.getHabboInfo().getId());
|
||||
}
|
||||
synchronized (this.room.roomUnitLock) {
|
||||
this.currentHabbos.remove(habbo.getHabboInfo().getId());
|
||||
}
|
||||
|
||||
if (sendRemovePacket && habbo.getRoomUnit() != null && !habbo.getRoomUnit().isTeleporting) {
|
||||
this.room.sendComposer(new RoomUserRemoveComposer(habbo.getRoomUnit()).compose());
|
||||
this.room.getUserVariableManager().clearAssignmentsForUser(habbo.getHabboInfo().getId());
|
||||
|
||||
if (sendRemovePacket && habbo.getRoomUnit() != null && !habbo.getRoomUnit().isTeleporting) {
|
||||
this.room.sendComposer(new RoomUserRemoveComposer(habbo.getRoomUnit()).compose());
|
||||
}
|
||||
|
||||
if (habbo.getRoomUnit().getCurrentLocation() != null) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,827 @@
|
||||
package com.eu.habbo.habbohotel.rooms;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRoomVariable;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableEcho;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableReference;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredVariableReferenceSupport;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredEvent;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredVariableLevelSystemSupport;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredVariableTextConnectorSupport;
|
||||
import com.eu.habbo.messages.outgoing.wired.WiredUserVariablesDataComposer;
|
||||
import gnu.trove.set.hash.THashSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class RoomVariableManager {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RoomVariableManager.class);
|
||||
|
||||
private final Room room;
|
||||
private final ConcurrentHashMap<Integer, VariableAssignment> activeAssignmentsByDefinitionId;
|
||||
private volatile boolean persistentValuesLoaded;
|
||||
|
||||
public RoomVariableManager(Room room) {
|
||||
this.room = room;
|
||||
this.activeAssignmentsByDefinitionId = new ConcurrentHashMap<>();
|
||||
this.persistentValuesLoaded = false;
|
||||
}
|
||||
|
||||
public void ensurePersistentValuesLoaded() {
|
||||
if (this.persistentValuesLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
if (this.persistentValuesLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<Integer> staleDefinitionIds = new ArrayList<>();
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("SELECT variable_item_id, value, created_at, updated_at FROM room_wired_variables WHERE room_id = ?")) {
|
||||
statement.setInt(1, this.room.getId());
|
||||
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
while (set.next()) {
|
||||
int definitionItemId = set.getInt("variable_item_id");
|
||||
WiredExtraRoomVariable definition = this.getDefinition(definitionItemId);
|
||||
|
||||
if (definition == null || !definition.isPermanentAvailability()) {
|
||||
staleDefinitionIds.add(definitionItemId);
|
||||
continue;
|
||||
}
|
||||
|
||||
int updatedAt = normalizeTimestamp(set.getInt("updated_at"), 0);
|
||||
|
||||
this.activeAssignmentsByDefinitionId.put(definitionItemId, new VariableAssignment(set.getInt("value"), 0, updatedAt));
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to restore wired room variables for room {}", this.room.getId(), e);
|
||||
}
|
||||
|
||||
for (Integer definitionItemId : staleDefinitionIds) {
|
||||
this.deletePersistentAssignment(definitionItemId);
|
||||
}
|
||||
|
||||
this.persistentValuesLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
public int getCurrentValue(int definitionItemId) {
|
||||
this.ensurePersistentValuesLoaded();
|
||||
|
||||
WiredVariableLevelSystemSupport.DerivedDefinition derivedDefinition = WiredVariableLevelSystemSupport.resolveDerivedDefinition(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, definitionItemId);
|
||||
if (derivedDefinition != null) {
|
||||
Integer baseValue = this.getRawValue(derivedDefinition.getBaseDefinitionItemId());
|
||||
Integer derivedValue = WiredVariableLevelSystemSupport.getDerivedValue(derivedDefinition.getLevelSystem(), derivedDefinition.getSubvariableType(), baseValue);
|
||||
return (derivedValue != null) ? derivedValue : 0;
|
||||
}
|
||||
|
||||
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
|
||||
if (extra instanceof WiredExtraVariableEcho) {
|
||||
return ((WiredExtraVariableEcho) extra).getCurrentValue(this.room, this.room.getId());
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraVariableReference) {
|
||||
WiredVariableReferenceSupport.SharedRoomAssignment assignment = WiredVariableReferenceSupport.getSharedRoomAssignment((WiredExtraVariableReference) extra);
|
||||
return assignment != null ? assignment.getValue() : 0;
|
||||
}
|
||||
|
||||
VariableAssignment assignment = this.activeAssignmentsByDefinitionId.get(definitionItemId);
|
||||
|
||||
return (assignment != null) ? assignment.getValue() : 0;
|
||||
}
|
||||
|
||||
public int getCreatedAt(int definitionItemId) {
|
||||
this.ensurePersistentValuesLoaded();
|
||||
|
||||
WiredVariableLevelSystemSupport.DerivedDefinition derivedDefinition = WiredVariableLevelSystemSupport.resolveDerivedDefinition(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, definitionItemId);
|
||||
if (derivedDefinition != null) {
|
||||
VariableAssignment assignment = this.getRawAssignment(derivedDefinition.getBaseDefinitionItemId());
|
||||
return (assignment != null) ? assignment.getCreatedAt() : 0;
|
||||
}
|
||||
|
||||
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
|
||||
if (extra instanceof WiredExtraVariableEcho) {
|
||||
return ((WiredExtraVariableEcho) extra).getCreatedAt(this.room, this.room.getId());
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraVariableReference) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
VariableAssignment assignment = this.activeAssignmentsByDefinitionId.get(definitionItemId);
|
||||
return (assignment != null) ? assignment.getCreatedAt() : 0;
|
||||
}
|
||||
|
||||
public int getUpdatedAt(int definitionItemId) {
|
||||
this.ensurePersistentValuesLoaded();
|
||||
|
||||
WiredVariableLevelSystemSupport.DerivedDefinition derivedDefinition = WiredVariableLevelSystemSupport.resolveDerivedDefinition(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, definitionItemId);
|
||||
if (derivedDefinition != null) {
|
||||
VariableAssignment assignment = this.getRawAssignment(derivedDefinition.getBaseDefinitionItemId());
|
||||
return (assignment != null) ? assignment.getUpdatedAt() : 0;
|
||||
}
|
||||
|
||||
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
|
||||
if (extra instanceof WiredExtraVariableEcho) {
|
||||
return ((WiredExtraVariableEcho) extra).getUpdatedAt(this.room, this.room.getId());
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraVariableReference) {
|
||||
WiredVariableReferenceSupport.SharedRoomAssignment assignment = WiredVariableReferenceSupport.getSharedRoomAssignment((WiredExtraVariableReference) extra);
|
||||
return assignment != null ? assignment.getUpdatedAt() : 0;
|
||||
}
|
||||
|
||||
VariableAssignment assignment = this.activeAssignmentsByDefinitionId.get(definitionItemId);
|
||||
return (assignment != null) ? assignment.getUpdatedAt() : 0;
|
||||
}
|
||||
|
||||
public boolean hasVariable(int definitionItemId) {
|
||||
if (definitionItemId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WiredVariableLevelSystemSupport.DerivedDefinition derivedDefinition = WiredVariableLevelSystemSupport.resolveDerivedDefinition(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, definitionItemId);
|
||||
if (derivedDefinition != null) {
|
||||
return this.getRawAssignment(derivedDefinition.getBaseDefinitionItemId()) != null;
|
||||
}
|
||||
|
||||
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
|
||||
if (extra instanceof WiredExtraVariableEcho) {
|
||||
return ((WiredExtraVariableEcho) extra).hasVariable(this.room, this.room.getId());
|
||||
}
|
||||
|
||||
return this.getDefinitionInfo(definitionItemId) != null;
|
||||
}
|
||||
|
||||
public boolean updateVariableValue(int definitionItemId, int value) {
|
||||
this.ensurePersistentValuesLoaded();
|
||||
|
||||
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
|
||||
WiredVariableDefinitionInfo definitionInfo = this.getDefinitionInfo(definitionItemId);
|
||||
|
||||
if (definitionInfo == null || definitionInfo.isReadOnly()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Integer previousValue = definitionInfo.hasValue() ? this.getCurrentValue(definitionItemId) : null;
|
||||
|
||||
if (extra instanceof WiredExtraVariableEcho) {
|
||||
boolean changed = ((WiredExtraVariableEcho) extra).updateValue(this.room, this.room.getId(), value);
|
||||
boolean shouldEmit = changed || (definitionInfo.hasValue() && previousValue != null && previousValue == value);
|
||||
|
||||
if (shouldEmit) {
|
||||
Integer currentValue = definitionInfo.hasValue() ? this.getCurrentValue(definitionItemId) : null;
|
||||
this.emitVariableChangedEvents(extra, definitionInfo, previousValue, currentValue);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
this.broadcastSnapshot();
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraVariableReference) {
|
||||
boolean changed = WiredVariableReferenceSupport.updateSharedRoomVariable((WiredExtraVariableReference) extra, value);
|
||||
boolean shouldEmit = changed || (definitionInfo.hasValue() && previousValue != null && previousValue == value);
|
||||
|
||||
if (shouldEmit) {
|
||||
Integer currentValue = definitionInfo.hasValue() ? this.getCurrentValue(definitionItemId) : null;
|
||||
this.emitVariableChangedEvents(extra, definitionInfo, previousValue, currentValue);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
this.broadcastSnapshot();
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
VariableAssignment assignment = this.activeAssignmentsByDefinitionId.get(definitionItemId);
|
||||
|
||||
if (assignment == null) {
|
||||
assignment = new VariableAssignment(value, 0, Emulator.getIntUnixTimestamp());
|
||||
this.activeAssignmentsByDefinitionId.put(definitionItemId, assignment);
|
||||
} else if (assignment.getValue() == value) {
|
||||
this.emitVariableChangedEvents(extra, definitionInfo, previousValue, assignment.getValue());
|
||||
return false;
|
||||
} else {
|
||||
assignment.setValue(value, Emulator.getIntUnixTimestamp());
|
||||
}
|
||||
|
||||
WiredExtraRoomVariable definition = (WiredExtraRoomVariable) extra;
|
||||
|
||||
if (definition.isPermanentAvailability()) {
|
||||
this.upsertPersistentAssignment(definitionItemId, assignment);
|
||||
}
|
||||
|
||||
if (definition.isSharedAvailability()) {
|
||||
WiredVariableReferenceSupport.cacheSharedRoomAssignment(this.room.getId(), definitionItemId, assignment.getValue(), assignment.getUpdatedAt());
|
||||
} else {
|
||||
WiredVariableReferenceSupport.clearSharedRoomDefinition(this.room.getId(), definitionItemId);
|
||||
}
|
||||
|
||||
this.emitVariableChangedEvents(extra, definitionInfo, previousValue, assignment.getValue());
|
||||
this.broadcastSnapshot();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean removeVariable(int definitionItemId) {
|
||||
this.ensurePersistentValuesLoaded();
|
||||
|
||||
if (definitionItemId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo definitionInfo = this.getDefinitionInfo(definitionItemId);
|
||||
if (definitionInfo == null || definitionInfo.isReadOnly()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Integer previousValue = definitionInfo.hasValue() ? this.getCurrentValue(definitionItemId) : null;
|
||||
|
||||
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
|
||||
if (extra instanceof WiredExtraVariableEcho) {
|
||||
boolean changed = ((WiredExtraVariableEcho) extra).removeValue(this.room, this.room.getId());
|
||||
|
||||
if (changed) {
|
||||
Integer currentValue = definitionInfo.hasValue() ? this.getCurrentValue(definitionItemId) : null;
|
||||
this.emitVariableChangedEvents(extra, definitionInfo, previousValue, currentValue);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
this.broadcastSnapshot();
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraVariableReference) {
|
||||
boolean changed = WiredVariableReferenceSupport.removeSharedRoomVariable((WiredExtraVariableReference) extra);
|
||||
|
||||
if (changed) {
|
||||
Integer currentValue = definitionInfo.hasValue() ? this.getCurrentValue(definitionItemId) : null;
|
||||
this.emitVariableChangedEvents(extra, definitionInfo, previousValue, currentValue);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
this.broadcastSnapshot();
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
VariableAssignment removed = this.activeAssignmentsByDefinitionId.remove(definitionItemId);
|
||||
this.deletePersistentAssignment(definitionItemId);
|
||||
|
||||
WiredExtraRoomVariable definition = this.getDefinition(definitionItemId);
|
||||
if (definition != null && definition.isSharedAvailability()) {
|
||||
WiredVariableReferenceSupport.clearSharedRoomDefinition(this.room.getId(), definitionItemId);
|
||||
}
|
||||
|
||||
if (removed != null) {
|
||||
Integer currentValue = definitionInfo.hasValue() ? this.getCurrentValue(definitionItemId) : null;
|
||||
this.emitVariableChangedEvents(extra, definitionInfo, previousValue, currentValue);
|
||||
this.broadcastSnapshot();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void clearTransientAssignments() {
|
||||
this.ensurePersistentValuesLoaded();
|
||||
|
||||
boolean changed = false;
|
||||
|
||||
for (Integer definitionItemId : new ArrayList<>(this.activeAssignmentsByDefinitionId.keySet())) {
|
||||
WiredExtraRoomVariable definition = this.getDefinition(definitionItemId);
|
||||
|
||||
if (definition != null && definition.isPermanentAvailability()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.activeAssignmentsByDefinitionId.remove(definitionItemId) != null) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
this.broadcastSnapshot();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeDefinition(int definitionItemId) {
|
||||
this.ensurePersistentValuesLoaded();
|
||||
this.activeAssignmentsByDefinitionId.remove(definitionItemId);
|
||||
this.deletePersistentAssignment(definitionItemId);
|
||||
WiredExtraRoomVariable definition = this.getDefinition(definitionItemId);
|
||||
if (definition != null && definition.isSharedAvailability()) {
|
||||
WiredVariableReferenceSupport.clearSharedRoomDefinition(this.room.getId(), definitionItemId);
|
||||
}
|
||||
this.broadcastSnapshot();
|
||||
}
|
||||
|
||||
public void handleDefinitionUpdated(WiredExtraRoomVariable definition) {
|
||||
if (definition == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ensurePersistentValuesLoaded();
|
||||
|
||||
if (!definition.isPermanentAvailability()) {
|
||||
this.deletePersistentAssignment(definition.getId());
|
||||
} else {
|
||||
VariableAssignment assignment = this.activeAssignmentsByDefinitionId.get(definition.getId());
|
||||
|
||||
if (assignment == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.upsertPersistentAssignment(definition.getId(), assignment);
|
||||
}
|
||||
|
||||
if (definition.isSharedAvailability()) {
|
||||
VariableAssignment assignment = this.activeAssignmentsByDefinitionId.get(definition.getId());
|
||||
|
||||
if (assignment != null) {
|
||||
WiredVariableReferenceSupport.cacheSharedRoomAssignment(this.room.getId(), definition.getId(), assignment.getValue(), assignment.getUpdatedAt());
|
||||
}
|
||||
} else {
|
||||
WiredVariableReferenceSupport.clearSharedRoomDefinition(this.room.getId(), definition.getId());
|
||||
}
|
||||
|
||||
this.broadcastSnapshot();
|
||||
}
|
||||
|
||||
public Snapshot createSnapshot() {
|
||||
this.ensurePersistentValuesLoaded();
|
||||
|
||||
List<DefinitionEntry> definitions = new ArrayList<>();
|
||||
List<AssignmentEntry> assignments = new ArrayList<>();
|
||||
List<Integer> derivedDefinitionIds = new ArrayList<>();
|
||||
List<WiredExtraVariableEcho> roomEchoes = this.getRoomEchoes();
|
||||
|
||||
for (WiredVariableDefinitionInfo definition : this.getAllDefinitionInfos()) {
|
||||
definitions.add(new DefinitionEntry(definition.getItemId(), definition.getName(), definition.hasValue(), definition.getAvailability(), definition.isTextConnected(), definition.isReadOnly()));
|
||||
|
||||
if (WiredVariableLevelSystemSupport.resolveDerivedDefinition(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, definition.getItemId()) != null) {
|
||||
derivedDefinitionIds.add(definition.getItemId());
|
||||
}
|
||||
|
||||
if (this.isReferenceDefinition(definition.getItemId())) {
|
||||
WiredExtraVariableReference reference = (WiredExtraVariableReference) this.getDefinitionExtra(definition.getItemId());
|
||||
WiredVariableReferenceSupport.SharedRoomAssignment assignment = WiredVariableReferenceSupport.getSharedRoomAssignment(reference);
|
||||
assignments.add(new AssignmentEntry(definition.getItemId(), (assignment != null) ? assignment.getValue() : 0, 0, (assignment != null) ? assignment.getUpdatedAt() : 0));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (derivedDefinitionIds.contains(definition.getItemId())) {
|
||||
assignments.add(new AssignmentEntry(
|
||||
definition.getItemId(),
|
||||
this.getCurrentValue(definition.getItemId()),
|
||||
this.getCreatedAt(definition.getItemId()),
|
||||
this.getUpdatedAt(definition.getItemId())
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (roomEchoes.stream().anyMatch(echo -> echo.getId() == definition.getItemId())) {
|
||||
assignments.add(new AssignmentEntry(
|
||||
definition.getItemId(),
|
||||
this.getCurrentValue(definition.getItemId()),
|
||||
this.getCreatedAt(definition.getItemId()),
|
||||
this.getUpdatedAt(definition.getItemId())
|
||||
));
|
||||
continue;
|
||||
}
|
||||
|
||||
VariableAssignment assignment = this.activeAssignmentsByDefinitionId.get(definition.getItemId());
|
||||
assignments.add(new AssignmentEntry(definition.getItemId(), (assignment != null) ? assignment.getValue() : 0, 0, (assignment != null) ? assignment.getUpdatedAt() : 0));
|
||||
}
|
||||
|
||||
assignments.sort(Comparator.comparingInt(AssignmentEntry::getVariableItemId));
|
||||
|
||||
return new Snapshot(this.room.getId(), definitions, assignments);
|
||||
}
|
||||
|
||||
public void sendSnapshot(Habbo habbo) {
|
||||
if (habbo == null || habbo.getClient() == null || !this.room.canInspectWired(habbo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
habbo.getClient().sendResponse(new WiredUserVariablesDataComposer(this.room.getUserVariableManager().createSnapshot(), this.room.getFurniVariableManager().createSnapshot(), this.createSnapshot()));
|
||||
}
|
||||
|
||||
public void broadcastSnapshot() {
|
||||
RoomUserVariableManager.Snapshot userSnapshot = this.room.getUserVariableManager().createSnapshot();
|
||||
RoomFurniVariableManager.Snapshot furniSnapshot = this.room.getFurniVariableManager().createSnapshot();
|
||||
Snapshot roomSnapshot = this.createSnapshot();
|
||||
|
||||
for (Habbo habbo : this.room.getCurrentHabbos().values()) {
|
||||
if (habbo == null || habbo.getClient() == null || !this.room.canInspectWired(habbo)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
habbo.getClient().sendResponse(new WiredUserVariablesDataComposer(userSnapshot, furniSnapshot, roomSnapshot));
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<WiredExtraRoomVariable> getDefinitions() {
|
||||
if (this.room.getRoomSpecialTypes() == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
THashSet<InteractionWiredExtra> extras = this.room.getRoomSpecialTypes().getExtras();
|
||||
List<WiredExtraRoomVariable> result = new ArrayList<>();
|
||||
|
||||
for (InteractionWiredExtra extra : extras) {
|
||||
if (extra instanceof WiredExtraRoomVariable) {
|
||||
result.add((WiredExtraRoomVariable) extra);
|
||||
}
|
||||
}
|
||||
|
||||
result.sort(Comparator.comparing(WiredExtraRoomVariable::getVariableName, String.CASE_INSENSITIVE_ORDER).thenComparingInt(WiredExtraRoomVariable::getId));
|
||||
return result;
|
||||
}
|
||||
|
||||
public Collection<WiredVariableDefinitionInfo> getAllDefinitionInfos() {
|
||||
List<WiredVariableDefinitionInfo> result = new ArrayList<>();
|
||||
List<WiredVariableDefinitionInfo> baseDefinitions = new ArrayList<>();
|
||||
|
||||
for (WiredExtraRoomVariable definition : this.getDefinitions()) {
|
||||
baseDefinitions.add(new WiredVariableDefinitionInfo(
|
||||
definition.getId(),
|
||||
definition.getVariableName(),
|
||||
definition.hasValue(),
|
||||
definition.getAvailability(),
|
||||
WiredVariableTextConnectorSupport.isTextConnected(this.room, definition),
|
||||
false
|
||||
));
|
||||
}
|
||||
|
||||
for (WiredExtraVariableReference reference : this.getRoomReferences()) {
|
||||
baseDefinitions.add(new WiredVariableDefinitionInfo(reference.getId(), reference.getVariableName(), reference.hasValue(), reference.getAvailability(), false, reference.isReadOnly()));
|
||||
}
|
||||
|
||||
for (WiredExtraVariableEcho echo : this.getRoomEchoes()) {
|
||||
baseDefinitions.add(echo.createDefinitionInfo(this.room));
|
||||
}
|
||||
|
||||
result.addAll(baseDefinitions);
|
||||
|
||||
for (WiredVariableDefinitionInfo definition : baseDefinitions) {
|
||||
result.addAll(WiredVariableLevelSystemSupport.getDerivedDefinitions(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, this.getDefinitionExtra(definition.getItemId()), definition));
|
||||
}
|
||||
|
||||
result.sort(Comparator.comparing(WiredVariableDefinitionInfo::getName, String.CASE_INSENSITIVE_ORDER).thenComparingInt(WiredVariableDefinitionInfo::getItemId));
|
||||
return result;
|
||||
}
|
||||
|
||||
public WiredVariableDefinitionInfo getDefinitionInfo(int definitionItemId) {
|
||||
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
|
||||
|
||||
if (extra instanceof WiredExtraRoomVariable) {
|
||||
WiredExtraRoomVariable definition = (WiredExtraRoomVariable) extra;
|
||||
return new WiredVariableDefinitionInfo(
|
||||
definition.getId(),
|
||||
definition.getVariableName(),
|
||||
definition.hasValue(),
|
||||
definition.getAvailability(),
|
||||
WiredVariableTextConnectorSupport.isTextConnected(this.room, definition),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isRoomReference()) {
|
||||
WiredExtraVariableReference reference = (WiredExtraVariableReference) extra;
|
||||
return new WiredVariableDefinitionInfo(reference.getId(), reference.getVariableName(), reference.hasValue(), reference.getAvailability(), false, reference.isReadOnly());
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraVariableEcho && ((WiredExtraVariableEcho) extra).isRoomEcho()) {
|
||||
return ((WiredExtraVariableEcho) extra).createDefinitionInfo(this.room);
|
||||
}
|
||||
|
||||
return WiredVariableLevelSystemSupport.getDerivedDefinitionInfo(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, definitionItemId);
|
||||
}
|
||||
|
||||
private WiredExtraRoomVariable getDefinition(int definitionItemId) {
|
||||
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
|
||||
|
||||
if (!(extra instanceof WiredExtraRoomVariable)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (WiredExtraRoomVariable) extra;
|
||||
}
|
||||
|
||||
private InteractionWiredExtra getDefinitionExtra(int definitionItemId) {
|
||||
if (this.room.getRoomSpecialTypes() == null || definitionItemId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.room.getRoomSpecialTypes().getExtra(definitionItemId);
|
||||
}
|
||||
|
||||
private boolean isReferenceDefinition(int definitionItemId) {
|
||||
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
|
||||
return extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isRoomReference();
|
||||
}
|
||||
|
||||
private List<WiredExtraVariableReference> getRoomReferences() {
|
||||
if (this.room.getRoomSpecialTypes() == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<WiredExtraVariableReference> result = new ArrayList<>();
|
||||
|
||||
for (InteractionWiredExtra extra : this.room.getRoomSpecialTypes().getExtras()) {
|
||||
if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isRoomReference()) {
|
||||
result.add((WiredExtraVariableReference) extra);
|
||||
}
|
||||
}
|
||||
|
||||
result.sort(Comparator.comparing(WiredExtraVariableReference::getVariableName, String.CASE_INSENSITIVE_ORDER).thenComparingInt(WiredExtraVariableReference::getId));
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<WiredExtraVariableEcho> getRoomEchoes() {
|
||||
if (this.room.getRoomSpecialTypes() == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<WiredExtraVariableEcho> result = new ArrayList<>();
|
||||
|
||||
for (InteractionWiredExtra extra : this.room.getRoomSpecialTypes().getExtras()) {
|
||||
if (extra instanceof WiredExtraVariableEcho && ((WiredExtraVariableEcho) extra).isRoomEcho()) {
|
||||
result.add((WiredExtraVariableEcho) extra);
|
||||
}
|
||||
}
|
||||
|
||||
result.sort(Comparator.comparing(WiredExtraVariableEcho::getVariableName, String.CASE_INSENSITIVE_ORDER).thenComparingInt(WiredExtraVariableEcho::getId));
|
||||
return result;
|
||||
}
|
||||
|
||||
private VariableAssignment getRawAssignment(int definitionItemId) {
|
||||
if (definitionItemId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
|
||||
if (extra instanceof WiredExtraVariableReference) {
|
||||
WiredVariableReferenceSupport.SharedRoomAssignment assignment = WiredVariableReferenceSupport.getSharedRoomAssignment((WiredExtraVariableReference) extra);
|
||||
return (assignment != null) ? new VariableAssignment(assignment.getValue(), 0, assignment.getUpdatedAt()) : null;
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraVariableEcho) {
|
||||
WiredExtraVariableEcho echo = (WiredExtraVariableEcho) extra;
|
||||
if (!echo.hasVariable(this.room, this.room.getId())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new VariableAssignment(echo.getCurrentValue(this.room, this.room.getId()), echo.getCreatedAt(this.room, this.room.getId()), echo.getUpdatedAt(this.room, this.room.getId()));
|
||||
}
|
||||
|
||||
return this.activeAssignmentsByDefinitionId.get(definitionItemId);
|
||||
}
|
||||
|
||||
private Integer getRawValue(int definitionItemId) {
|
||||
VariableAssignment assignment = this.getRawAssignment(definitionItemId);
|
||||
return (assignment != null) ? assignment.getValue() : null;
|
||||
}
|
||||
|
||||
private void emitVariableChangedEvents(InteractionWiredExtra definitionExtra, WiredVariableDefinitionInfo definitionInfo, Integer previousValue, Integer currentValue) {
|
||||
if (definitionInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emitVariableChangedEvent(definitionInfo.getItemId(), definitionInfo.hasValue(), previousValue, currentValue);
|
||||
|
||||
for (WiredVariableDefinitionInfo derivedDefinition : WiredVariableLevelSystemSupport.getDerivedDefinitions(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, definitionExtra, definitionInfo)) {
|
||||
WiredVariableLevelSystemSupport.DerivedDefinition resolvedDefinition = WiredVariableLevelSystemSupport.resolveDerivedDefinition(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, derivedDefinition.getItemId());
|
||||
|
||||
if (resolvedDefinition == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Integer derivedPreviousValue = WiredVariableLevelSystemSupport.getDerivedValue(resolvedDefinition.getLevelSystem(), resolvedDefinition.getSubvariableType(), previousValue);
|
||||
Integer derivedCurrentValue = WiredVariableLevelSystemSupport.getDerivedValue(resolvedDefinition.getLevelSystem(), resolvedDefinition.getSubvariableType(), currentValue);
|
||||
|
||||
this.emitVariableChangedEvent(derivedDefinition.getItemId(), true, derivedPreviousValue, derivedCurrentValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void emitVariableChangedEvent(int definitionItemId, boolean hasValue, Integer previousValue, Integer currentValue) {
|
||||
WiredEvent.VariableChangeKind changeKind = resolveVariableChangeKind(hasValue, previousValue, currentValue);
|
||||
|
||||
if (changeKind == WiredEvent.VariableChangeKind.NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
WiredManager.triggerRoomVariableChanged(this.room, definitionItemId, changeKind);
|
||||
}
|
||||
|
||||
private static WiredEvent.VariableChangeKind resolveVariableChangeKind(boolean hasValue, Integer previousValue, Integer currentValue) {
|
||||
if (!hasValue) {
|
||||
return WiredEvent.VariableChangeKind.NONE;
|
||||
}
|
||||
|
||||
if (Objects.equals(previousValue, currentValue)) {
|
||||
return WiredEvent.VariableChangeKind.UNCHANGED;
|
||||
}
|
||||
|
||||
int previousNumericValue = (previousValue != null) ? previousValue : 0;
|
||||
int currentNumericValue = (currentValue != null) ? currentValue : 0;
|
||||
|
||||
return (currentNumericValue > previousNumericValue)
|
||||
? WiredEvent.VariableChangeKind.INCREASED
|
||||
: WiredEvent.VariableChangeKind.DECREASED;
|
||||
}
|
||||
|
||||
private void upsertPersistentAssignment(int definitionItemId, VariableAssignment assignment) {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("INSERT INTO room_wired_variables (room_id, variable_item_id, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = VALUES(updated_at)")) {
|
||||
statement.setInt(1, this.room.getId());
|
||||
statement.setInt(2, definitionItemId);
|
||||
statement.setInt(3, (assignment != null) ? assignment.getValue() : 0);
|
||||
|
||||
int now = Emulator.getIntUnixTimestamp();
|
||||
statement.setInt(4, 0);
|
||||
statement.setInt(5, (assignment != null) ? assignment.getUpdatedAt() : now);
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to store permanent wired room variable for room {} and item {}", this.room.getId(), definitionItemId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void deletePersistentAssignment(int definitionItemId) {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("DELETE FROM room_wired_variables WHERE room_id = ? AND variable_item_id = ?")) {
|
||||
statement.setInt(1, this.room.getId());
|
||||
statement.setInt(2, definitionItemId);
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to delete permanent wired room variable for room {} and item {}", this.room.getId(), definitionItemId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static int normalizeTimestamp(int value, int fallback) {
|
||||
if (value > 0) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (fallback > 0) {
|
||||
return fallback;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static class Snapshot {
|
||||
private final int roomId;
|
||||
private final List<DefinitionEntry> definitions;
|
||||
private final List<AssignmentEntry> assignments;
|
||||
|
||||
public Snapshot(int roomId, List<DefinitionEntry> definitions, List<AssignmentEntry> assignments) {
|
||||
this.roomId = roomId;
|
||||
this.definitions = definitions;
|
||||
this.assignments = assignments;
|
||||
}
|
||||
|
||||
public int getRoomId() {
|
||||
return this.roomId;
|
||||
}
|
||||
|
||||
public List<DefinitionEntry> getDefinitions() {
|
||||
return this.definitions;
|
||||
}
|
||||
|
||||
public List<AssignmentEntry> getAssignments() {
|
||||
return this.assignments;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DefinitionEntry {
|
||||
private final int itemId;
|
||||
private final String name;
|
||||
private final boolean hasValue;
|
||||
private final int availability;
|
||||
private final boolean textConnected;
|
||||
private final boolean readOnly;
|
||||
|
||||
public DefinitionEntry(int itemId, String name, boolean hasValue, int availability, boolean textConnected, boolean readOnly) {
|
||||
this.itemId = itemId;
|
||||
this.name = name;
|
||||
this.hasValue = hasValue;
|
||||
this.availability = availability;
|
||||
this.textConnected = textConnected;
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
public int getItemId() {
|
||||
return this.itemId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return this.hasValue;
|
||||
}
|
||||
|
||||
public int getAvailability() {
|
||||
return this.availability;
|
||||
}
|
||||
|
||||
public boolean isTextConnected() {
|
||||
return this.textConnected;
|
||||
}
|
||||
|
||||
public boolean isReadOnly() {
|
||||
return this.readOnly;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AssignmentEntry {
|
||||
private final int variableItemId;
|
||||
private final Integer value;
|
||||
private final int createdAt;
|
||||
private final int updatedAt;
|
||||
|
||||
public AssignmentEntry(int variableItemId, Integer value, int createdAt, int updatedAt) {
|
||||
this.variableItemId = variableItemId;
|
||||
this.value = value;
|
||||
this.createdAt = createdAt;
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public int getVariableItemId() {
|
||||
return this.variableItemId;
|
||||
}
|
||||
|
||||
public Integer getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return this.value != null;
|
||||
}
|
||||
|
||||
public int getCreatedAt() {
|
||||
return this.createdAt;
|
||||
}
|
||||
|
||||
public int getUpdatedAt() {
|
||||
return this.updatedAt;
|
||||
}
|
||||
}
|
||||
|
||||
private static class VariableAssignment {
|
||||
private int value;
|
||||
private final int createdAt;
|
||||
private int updatedAt;
|
||||
|
||||
public VariableAssignment(int value, int createdAt, int updatedAt) {
|
||||
this.value = value;
|
||||
this.createdAt = createdAt;
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void setValue(int value, int updatedAt) {
|
||||
this.value = value;
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public int getCreatedAt() {
|
||||
return this.createdAt;
|
||||
}
|
||||
|
||||
public int getUpdatedAt() {
|
||||
return this.updatedAt;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.eu.habbo.habbohotel.rooms;
|
||||
|
||||
public class WiredVariableDefinitionInfo {
|
||||
private final int itemId;
|
||||
private final String name;
|
||||
private final boolean hasValue;
|
||||
private final int availability;
|
||||
private final boolean textConnected;
|
||||
private final boolean readOnly;
|
||||
|
||||
public WiredVariableDefinitionInfo(int itemId, String name, boolean hasValue, int availability, boolean textConnected, boolean readOnly) {
|
||||
this.itemId = itemId;
|
||||
this.name = name;
|
||||
this.hasValue = hasValue;
|
||||
this.availability = availability;
|
||||
this.textConnected = textConnected;
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
public int getItemId() {
|
||||
return this.itemId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public boolean hasValue() {
|
||||
return this.hasValue;
|
||||
}
|
||||
|
||||
public int getAvailability() {
|
||||
return this.availability;
|
||||
}
|
||||
|
||||
public boolean isTextConnected() {
|
||||
return this.textConnected;
|
||||
}
|
||||
|
||||
public boolean isReadOnly() {
|
||||
return this.readOnly;
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,11 @@ public enum WiredConditionType {
|
||||
MATCH_TIME(36),
|
||||
MATCH_DATE(37),
|
||||
ACTOR_DIR(38),
|
||||
SLC_QUANTITY(39);
|
||||
SLC_QUANTITY(39),
|
||||
HAS_VAR(40),
|
||||
NOT_HAS_VAR(41),
|
||||
VAR_VAL_MATCH(42),
|
||||
VAR_AGE_MATCH(43);
|
||||
|
||||
public final int code;
|
||||
|
||||
|
||||
@@ -54,7 +54,12 @@ public enum WiredEffectType {
|
||||
USERS_BY_NAME_SELECTOR(52),
|
||||
USERS_ON_FURNI_SELECTOR(53),
|
||||
USERS_GROUP_SELECTOR(54),
|
||||
USERS_HANDITEM_SELECTOR(55);
|
||||
USERS_HANDITEM_SELECTOR(55),
|
||||
GIVE_VAR(69),
|
||||
REMOVE_VAR(73),
|
||||
CHANGE_VAR_VAL(74),
|
||||
FURNI_WITH_VAR_SELECTOR(75),
|
||||
USERS_WITH_VAR_SELECTOR(76);
|
||||
|
||||
public final int code;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ public enum WiredTriggerType {
|
||||
CLICKS_USER(20),
|
||||
USER_PERFORMS_ACTION(21),
|
||||
CLOCK_COUNTER(22),
|
||||
VARIABLE_CHANGED(23),
|
||||
SAY_COMMAND(0),
|
||||
IDLES(11),
|
||||
UNIDLES(11),
|
||||
|
||||
@@ -59,6 +59,9 @@ public final class WiredContext {
|
||||
/** Extra settings from the trigger item (for legacy compatibility) */
|
||||
private final Object[] legacySettings;
|
||||
|
||||
/** Runtime-local context variables shared through the current execution chain. */
|
||||
private WiredContextVariableScope contextVariables;
|
||||
|
||||
/** Whether selector item resolution should include wired furniture too. */
|
||||
private boolean includeWiredSelectorItems = false;
|
||||
|
||||
@@ -108,6 +111,9 @@ public final class WiredContext {
|
||||
this.services = services;
|
||||
this.state = state;
|
||||
this.legacySettings = legacySettings;
|
||||
this.contextVariables = (event.getContextVariableScope() != null)
|
||||
? event.getContextVariableScope()
|
||||
: new WiredContextVariableScope();
|
||||
this.targets = new WiredTargets();
|
||||
|
||||
// Default targets: include actor and trigger item for backwards compatibility
|
||||
@@ -259,6 +265,14 @@ public final class WiredContext {
|
||||
return legacySettings != null ? legacySettings : new Object[0];
|
||||
}
|
||||
|
||||
public WiredContextVariableScope contextVariables() {
|
||||
return this.contextVariables;
|
||||
}
|
||||
|
||||
public void forkContextVariables() {
|
||||
this.contextVariables = this.contextVariables.copy();
|
||||
}
|
||||
|
||||
public boolean includeWiredSelectorItems() {
|
||||
return this.includeWiredSelectorItems;
|
||||
}
|
||||
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package com.eu.habbo.habbohotel.wired.core;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class WiredContextVariableScope {
|
||||
private final LinkedHashMap<Integer, VariableAssignment> assignments;
|
||||
|
||||
public WiredContextVariableScope() {
|
||||
this.assignments = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
private WiredContextVariableScope(Map<Integer, VariableAssignment> source) {
|
||||
this.assignments = new LinkedHashMap<>();
|
||||
|
||||
if (source == null || source.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Map.Entry<Integer, VariableAssignment> entry : source.entrySet()) {
|
||||
if (entry == null || entry.getKey() == null || entry.getKey() <= 0 || entry.getValue() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.assignments.put(entry.getKey(), entry.getValue().copy());
|
||||
}
|
||||
}
|
||||
|
||||
public WiredContextVariableScope copy() {
|
||||
return new WiredContextVariableScope(this.assignments);
|
||||
}
|
||||
|
||||
public boolean hasVariable(int definitionItemId) {
|
||||
return definitionItemId > 0 && this.assignments.containsKey(definitionItemId);
|
||||
}
|
||||
|
||||
public Integer getValue(int definitionItemId) {
|
||||
VariableAssignment assignment = this.assignments.get(definitionItemId);
|
||||
return assignment != null ? assignment.getValue() : null;
|
||||
}
|
||||
|
||||
public int getCreatedAt(int definitionItemId) {
|
||||
VariableAssignment assignment = this.assignments.get(definitionItemId);
|
||||
return assignment != null ? assignment.getCreatedAt() : 0;
|
||||
}
|
||||
|
||||
public int getUpdatedAt(int definitionItemId) {
|
||||
VariableAssignment assignment = this.assignments.get(definitionItemId);
|
||||
return assignment != null ? assignment.getUpdatedAt() : 0;
|
||||
}
|
||||
|
||||
public boolean assignValue(int definitionItemId, Integer value, boolean overrideExisting) {
|
||||
if (definitionItemId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VariableAssignment existingAssignment = this.assignments.get(definitionItemId);
|
||||
|
||||
if (existingAssignment != null && !overrideExisting) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int now = Emulator.getIntUnixTimestamp();
|
||||
|
||||
if (existingAssignment == null || overrideExisting) {
|
||||
this.assignments.put(definitionItemId, new VariableAssignment(value, now, now));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean updateValue(int definitionItemId, Integer value) {
|
||||
if (definitionItemId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
VariableAssignment assignment = this.assignments.get(definitionItemId);
|
||||
if (assignment == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((assignment.getValue() == null && value == null)
|
||||
|| (assignment.getValue() != null && assignment.getValue().equals(value))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assignment.setValue(value, Emulator.getIntUnixTimestamp());
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean removeValue(int definitionItemId) {
|
||||
if (definitionItemId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.assignments.remove(definitionItemId) != null;
|
||||
}
|
||||
|
||||
public static final class VariableAssignment {
|
||||
private Integer value;
|
||||
private final int createdAt;
|
||||
private int updatedAt;
|
||||
|
||||
public VariableAssignment(Integer value, int createdAt, int updatedAt) {
|
||||
this.value = value;
|
||||
this.createdAt = createdAt;
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public Integer getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public int getCreatedAt() {
|
||||
return this.createdAt;
|
||||
}
|
||||
|
||||
public int getUpdatedAt() {
|
||||
return this.updatedAt;
|
||||
}
|
||||
|
||||
public void setValue(Integer value, int updatedAt) {
|
||||
this.value = value;
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
private VariableAssignment copy() {
|
||||
return new VariableAssignment(this.value, this.createdAt, this.updatedAt);
|
||||
}
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
package com.eu.habbo.habbohotel.wired.core;
|
||||
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraContextVariable;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
|
||||
import com.eu.habbo.messages.outgoing.wired.WiredUserVariablesDataComposer;
|
||||
import gnu.trove.set.hash.THashSet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public final class WiredContextVariableSupport {
|
||||
private WiredContextVariableSupport() {
|
||||
}
|
||||
|
||||
public static List<WiredExtraContextVariable> getDefinitions(Room room) {
|
||||
List<WiredExtraContextVariable> definitions = new ArrayList<>();
|
||||
|
||||
if (room == null || room.getRoomSpecialTypes() == null) {
|
||||
return definitions;
|
||||
}
|
||||
|
||||
THashSet<InteractionWiredExtra> extras = room.getRoomSpecialTypes().getExtras();
|
||||
if (extras == null || extras.isEmpty()) {
|
||||
return definitions;
|
||||
}
|
||||
|
||||
for (InteractionWiredExtra extra : extras) {
|
||||
if (extra instanceof WiredExtraContextVariable) {
|
||||
definitions.add((WiredExtraContextVariable) extra);
|
||||
}
|
||||
}
|
||||
|
||||
definitions.sort(Comparator
|
||||
.comparing(WiredExtraContextVariable::getVariableName, String.CASE_INSENSITIVE_ORDER)
|
||||
.thenComparingInt(WiredExtraContextVariable::getId));
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
public static List<WiredVariableDefinitionInfo> createDefinitionInfos(Room room) {
|
||||
List<WiredVariableDefinitionInfo> definitions = new ArrayList<>();
|
||||
|
||||
for (WiredExtraContextVariable definition : getDefinitions(room)) {
|
||||
if (definition == null || definition.getVariableName() == null || definition.getVariableName().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
definitions.add(new WiredVariableDefinitionInfo(
|
||||
definition.getId(),
|
||||
definition.getVariableName(),
|
||||
definition.hasValue(),
|
||||
0,
|
||||
WiredVariableTextConnectorSupport.isTextConnected(room, definition.getId()),
|
||||
false));
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
public static WiredExtraContextVariable getDefinition(Room room, int definitionItemId) {
|
||||
if (room == null || room.getRoomSpecialTypes() == null || definitionItemId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
InteractionWiredExtra extra = room.getRoomSpecialTypes().getExtra(definitionItemId);
|
||||
return (extra instanceof WiredExtraContextVariable) ? (WiredExtraContextVariable) extra : null;
|
||||
}
|
||||
|
||||
public static WiredVariableDefinitionInfo getDefinitionInfo(Room room, int definitionItemId) {
|
||||
WiredExtraContextVariable definition = getDefinition(room, definitionItemId);
|
||||
if (definition == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new WiredVariableDefinitionInfo(
|
||||
definition.getId(),
|
||||
definition.getVariableName(),
|
||||
definition.hasValue(),
|
||||
0,
|
||||
WiredVariableTextConnectorSupport.isTextConnected(room, definition.getId()),
|
||||
false);
|
||||
}
|
||||
|
||||
public static boolean hasDefinition(Room room, int definitionItemId) {
|
||||
return getDefinition(room, definitionItemId) != null;
|
||||
}
|
||||
|
||||
public static boolean assignVariable(WiredContext ctx, Room room, int definitionItemId, Integer value, boolean overrideExisting) {
|
||||
WiredExtraContextVariable definition = getDefinition(room, definitionItemId);
|
||||
if (ctx == null || definition == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (overrideExisting && ctx.contextVariables().hasVariable(definitionItemId)) {
|
||||
ctx.forkContextVariables();
|
||||
}
|
||||
|
||||
return ctx.contextVariables().assignValue(definitionItemId, definition.hasValue() ? value : null, overrideExisting);
|
||||
}
|
||||
|
||||
public static boolean updateVariableValue(WiredContext ctx, Room room, int definitionItemId, Integer value) {
|
||||
WiredExtraContextVariable definition = getDefinition(room, definitionItemId);
|
||||
if (ctx == null || definition == null || !definition.hasValue()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ctx.contextVariables().updateValue(definitionItemId, value);
|
||||
}
|
||||
|
||||
public static boolean removeVariable(WiredContext ctx, Room room, int definitionItemId) {
|
||||
return ctx != null && getDefinition(room, definitionItemId) != null && ctx.contextVariables().removeValue(definitionItemId);
|
||||
}
|
||||
|
||||
public static boolean hasVariable(WiredContext ctx, int definitionItemId) {
|
||||
return ctx != null && ctx.contextVariables().hasVariable(definitionItemId);
|
||||
}
|
||||
|
||||
public static Integer getCurrentValue(WiredContext ctx, int definitionItemId) {
|
||||
return ctx != null ? ctx.contextVariables().getValue(definitionItemId) : null;
|
||||
}
|
||||
|
||||
public static int getCreatedAt(WiredContext ctx, int definitionItemId) {
|
||||
return ctx != null ? ctx.contextVariables().getCreatedAt(definitionItemId) : 0;
|
||||
}
|
||||
|
||||
public static int getUpdatedAt(WiredContext ctx, int definitionItemId) {
|
||||
return ctx != null ? ctx.contextVariables().getUpdatedAt(definitionItemId) : 0;
|
||||
}
|
||||
|
||||
public static void broadcastDefinitions(Room room) {
|
||||
if (room == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
WiredUserVariablesDataComposer composer = new WiredUserVariablesDataComposer(
|
||||
room.getUserVariableManager().createSnapshot(),
|
||||
room.getFurniVariableManager().createSnapshot(),
|
||||
room.getRoomVariableManager().createSnapshot());
|
||||
|
||||
room.getHabbos().forEach(habbo ->
|
||||
{
|
||||
if (habbo == null || habbo.getClient() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
habbo.getClient().sendResponse(composer);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,9 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWiredCondition;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
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.WiredExtraExecutionLimit;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraOrEval;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRandom;
|
||||
@@ -20,6 +22,7 @@ import com.eu.habbo.habbohotel.wired.WiredConditionOperator;
|
||||
import com.eu.habbo.habbohotel.wired.api.IWiredCondition;
|
||||
import com.eu.habbo.habbohotel.wired.api.IWiredEffect;
|
||||
import com.eu.habbo.habbohotel.wired.api.WiredStack;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
|
||||
import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer;
|
||||
import com.eu.habbo.plugin.events.furniture.wired.WiredStackExecutedEvent;
|
||||
@@ -77,6 +80,33 @@ public final class WiredEngine {
|
||||
/** Duration to ban wired execution in a room after abuse detected (milliseconds) */
|
||||
public static long WIRED_BAN_DURATION_MS = 600000;
|
||||
|
||||
/** Monitor usage window in milliseconds */
|
||||
public static int MONITOR_USAGE_WINDOW_MS = 1000;
|
||||
|
||||
/** Monitor execution cap per room window */
|
||||
public static int MONITOR_USAGE_LIMIT = 1000;
|
||||
|
||||
/** Maximum delayed events allowed per room at the same time */
|
||||
public static int MONITOR_DELAYED_EVENTS_LIMIT = 100;
|
||||
|
||||
/** Average execution threshold that marks overload */
|
||||
public static int MONITOR_OVERLOAD_AVERAGE_MS = 50;
|
||||
|
||||
/** Peak execution threshold that marks overload */
|
||||
public static int MONITOR_OVERLOAD_PEAK_MS = 150;
|
||||
|
||||
/** Consecutive overloaded windows required before recording overload */
|
||||
public static int MONITOR_OVERLOAD_CONSECUTIVE_WINDOWS = 2;
|
||||
|
||||
/** Usage percentage threshold that marks a room as heavy */
|
||||
public static int MONITOR_HEAVY_USAGE_PERCENT = 70;
|
||||
|
||||
/** Consecutive windows above threshold before marking heavy */
|
||||
public static int MONITOR_HEAVY_CONSECUTIVE_WINDOWS = 5;
|
||||
|
||||
/** Delayed queue percentage threshold that contributes to heavy state */
|
||||
public static int MONITOR_HEAVY_DELAYED_PERCENT = 60;
|
||||
|
||||
private final WiredServices services;
|
||||
private final WiredStackIndex index;
|
||||
private final int maxStepsPerStack;
|
||||
@@ -93,6 +123,9 @@ public final class WiredEngine {
|
||||
/** Track rooms that are banned from wired execution: roomId -> ban expiry timestamp */
|
||||
private final ConcurrentHashMap<Integer, Long> bannedRooms;
|
||||
|
||||
/** Track monitor diagnostics per room */
|
||||
private final ConcurrentHashMap<Integer, WiredRoomDiagnostics> roomDiagnostics;
|
||||
|
||||
/**
|
||||
* Create a new wired engine.
|
||||
*
|
||||
@@ -112,6 +145,7 @@ public final class WiredEngine {
|
||||
this.roomRecursionDepth = new ConcurrentHashMap<>();
|
||||
this.eventRateLimiters = new ConcurrentHashMap<>();
|
||||
this.bannedRooms = new ConcurrentHashMap<>();
|
||||
this.roomDiagnostics = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,6 +180,12 @@ public final class WiredEngine {
|
||||
// Check and increment recursion depth to prevent infinite loops
|
||||
int currentDepth = roomRecursionDepth.getOrDefault(roomId, 0);
|
||||
if (currentDepth >= MAX_RECURSION_DEPTH) {
|
||||
getDiagnostics(roomId).recordRecursionTimeout(
|
||||
System.currentTimeMillis(),
|
||||
String.format("Recursion depth %d/%d while handling %s", currentDepth, MAX_RECURSION_DEPTH, event.getType().name()),
|
||||
event.getType().name(),
|
||||
0
|
||||
);
|
||||
LOGGER.warn("Wired recursion limit reached in room {} (depth: {}). " +
|
||||
"Possible infinite loop detected (e.g., collision + chase). Aborting.", roomId, currentDepth);
|
||||
debug(room, "RECURSION LIMIT REACHED - aborting to prevent crash");
|
||||
@@ -215,9 +255,10 @@ public final class WiredEngine {
|
||||
*/
|
||||
private boolean processStack(WiredStack stack, WiredEvent event, long currentTime) {
|
||||
Room room = event.getRoom();
|
||||
WiredTextInputCaptureSupport.CaptureResult captureResult = resolveTextInputCapture(stack, event);
|
||||
|
||||
// Check if trigger matches
|
||||
if (!stack.trigger().matches(stack.triggerItem(), event)) {
|
||||
if (!captureResult.matches()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -226,13 +267,23 @@ public final class WiredEngine {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stackHasExecutableOutcome(stack, event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create execution context with stack reference
|
||||
WiredState state = new WiredState(maxStepsPerStack);
|
||||
WiredContext ctx = new WiredContext(event, stack.triggerItem(), stack, services, state, null);
|
||||
WiredTextInputCaptureSupport.applyToContext(ctx, room, captureResult);
|
||||
WiredRoomDiagnostics diagnostics = getDiagnostics(room.getId());
|
||||
|
||||
// Initial step for trigger
|
||||
state.step();
|
||||
|
||||
|
||||
int stackCost = estimateStackCost(stack, roomRecursionDepth.getOrDefault(room.getId(), 0));
|
||||
String monitorSourceLabel = getMonitorSourceLabel(stack.triggerItem(), event);
|
||||
int monitorSourceId = getMonitorSourceId(stack.triggerItem());
|
||||
|
||||
debug(room, "Trigger matched: {} at item {} (conditions: {}, effects: {})",
|
||||
event.getType(),
|
||||
stack.triggerItem() != null ? stack.triggerItem().getId() : "null",
|
||||
@@ -274,6 +325,16 @@ public final class WiredEngine {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!diagnostics.tryConsumeExecutionBudget(
|
||||
stackCost,
|
||||
currentTime,
|
||||
monitorSourceLabel,
|
||||
monitorSourceId,
|
||||
buildStackMonitorReason(stack, event, stackCost))) {
|
||||
debug(room, "Execution cap blocked stack {}", stack.triggerItem() != null ? stack.triggerItem().getId() : "null");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((event.getType() == WiredEvent.Type.USER_CLICKS_USER)
|
||||
&& (stack.triggerItem() instanceof WiredTriggerHabboClicksUser)
|
||||
&& event.getActor().isPresent()) {
|
||||
@@ -302,14 +363,22 @@ public final class WiredEngine {
|
||||
|
||||
// Fire executed event
|
||||
fireExecutedEvent(stack, event);
|
||||
diagnostics.recordExecution(
|
||||
state.elapsedMs(),
|
||||
System.currentTimeMillis(),
|
||||
monitorSourceLabel,
|
||||
monitorSourceId,
|
||||
buildExecutionMonitorReason(stack, state.elapsedMs())
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean wouldTriggerStack(WiredStack stack, WiredEvent event, long currentTime) {
|
||||
Room room = event.getRoom();
|
||||
WiredTextInputCaptureSupport.CaptureResult captureResult = resolveTextInputCapture(stack, event);
|
||||
|
||||
if (!stack.trigger().matches(stack.triggerItem(), event)) {
|
||||
if (!captureResult.matches()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -317,8 +386,13 @@ public final class WiredEngine {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stackHasExecutableOutcome(stack, event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WiredState state = new WiredState(maxStepsPerStack);
|
||||
WiredContext ctx = new WiredContext(event, stack.triggerItem(), stack, services, state, null);
|
||||
WiredTextInputCaptureSupport.applyToContext(ctx, room, captureResult);
|
||||
|
||||
state.step();
|
||||
|
||||
@@ -336,6 +410,43 @@ public final class WiredEngine {
|
||||
return executionLimitExtra == null || executionLimitExtra.canExecuteAt(currentTime);
|
||||
}
|
||||
|
||||
private boolean stackHasExecutableOutcome(WiredStack stack, WiredEvent event) {
|
||||
if (stack == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stack.hasEffects()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (stack.triggerItem() instanceof WiredTriggerHabboSaysKeyword) {
|
||||
return ((WiredTriggerHabboSaysKeyword) stack.triggerItem()).isHideMessage();
|
||||
}
|
||||
|
||||
if ((event != null)
|
||||
&& (event.getType() == WiredEvent.Type.USER_CLICKS_USER)
|
||||
&& (stack.triggerItem() instanceof WiredTriggerHabboClicksUser)) {
|
||||
WiredTriggerHabboClicksUser trigger = (WiredTriggerHabboClicksUser) stack.triggerItem();
|
||||
return trigger.isBlockMenuOpen() || trigger.isDoNotRotate();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private WiredTextInputCaptureSupport.CaptureResult resolveTextInputCapture(WiredStack stack, WiredEvent event) {
|
||||
if (stack == null || event == null) {
|
||||
return WiredTextInputCaptureSupport.CaptureResult.noMatch();
|
||||
}
|
||||
|
||||
if (event.getType() != WiredEvent.Type.USER_SAYS || !(stack.triggerItem() instanceof WiredTriggerHabboSaysKeyword)) {
|
||||
return stack.trigger().matches(stack.triggerItem(), event)
|
||||
? WiredTextInputCaptureSupport.CaptureResult.matched(new LinkedHashMap<>())
|
||||
: WiredTextInputCaptureSupport.CaptureResult.noMatch();
|
||||
}
|
||||
|
||||
return WiredTextInputCaptureSupport.resolve(stack, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate all conditions in a stack.
|
||||
*/
|
||||
@@ -462,38 +573,48 @@ public final class WiredEngine {
|
||||
executeOrderedEffects(regulars, ctx, currentTime);
|
||||
return;
|
||||
} else {
|
||||
// Normal mode: regular effects in random order
|
||||
// Normal mode: preserve the physical stack order.
|
||||
// This matches the legacy handler behavior and avoids visual/state races
|
||||
// for combinations such as Move/Rotate + Match To Snapshot in the same stack.
|
||||
toExecute = new ArrayList<>(regulars);
|
||||
Collections.shuffle(toExecute);
|
||||
}
|
||||
|
||||
// Execute selected effects
|
||||
for (IWiredEffect effect : toExecute) {
|
||||
// Check if effect requires actor
|
||||
if (effect.requiresActor() && !ctx.hasActor()) {
|
||||
continue;
|
||||
}
|
||||
WiredMoveCarryHelper.beginMovementCollection();
|
||||
|
||||
// Handle delay
|
||||
int delay = effect.getDelay();
|
||||
if (delay > 0) {
|
||||
// Schedule delayed execution
|
||||
scheduleDelayedEffect(effect, ctx, delay, currentTime);
|
||||
} else {
|
||||
// Execute immediately
|
||||
ctx.state().step();
|
||||
try {
|
||||
effect.execute(ctx);
|
||||
|
||||
// Activate box animation after execution
|
||||
if (effect instanceof InteractionWiredEffect) {
|
||||
InteractionWiredEffect wiredEffect = (InteractionWiredEffect) effect;
|
||||
wiredEffect.setCooldown(currentTime);
|
||||
wiredEffect.activateBox(ctx.room(), ctx.actor().orElse(null), currentTime);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Error executing effect: {}", e.getMessage());
|
||||
try {
|
||||
// Execute selected effects
|
||||
for (IWiredEffect effect : toExecute) {
|
||||
// Check if effect requires actor
|
||||
if (effect.requiresActor() && !ctx.hasActor()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle delay
|
||||
int delay = effect.getDelay();
|
||||
if (delay > 0) {
|
||||
// Schedule delayed execution
|
||||
scheduleDelayedEffect(effect, ctx, delay, currentTime);
|
||||
} else {
|
||||
// Execute immediately
|
||||
ctx.state().step();
|
||||
try {
|
||||
effect.execute(ctx);
|
||||
|
||||
// Activate box animation after execution
|
||||
if (effect instanceof InteractionWiredEffect) {
|
||||
InteractionWiredEffect wiredEffect = (InteractionWiredEffect) effect;
|
||||
wiredEffect.setCooldown(currentTime);
|
||||
wiredEffect.activateBox(ctx.room(), ctx.actor().orElse(null), currentTime);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Error executing effect: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
ServerMessage movementComposer = WiredMoveCarryHelper.finishMovementCollection();
|
||||
if (movementComposer != null) {
|
||||
ctx.room().sendComposer(movementComposer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -561,21 +682,50 @@ public final class WiredEngine {
|
||||
|
||||
int furniLimit = Integer.MAX_VALUE;
|
||||
int userLimit = Integer.MAX_VALUE;
|
||||
List<WiredExtraFilterFurniByVariable> furniVariableFilters = new ArrayList<>();
|
||||
List<WiredExtraFilterUsersByVariable> userVariableFilters = new ArrayList<>();
|
||||
|
||||
for (InteractionWiredExtra extra : extras) {
|
||||
if (extra instanceof WiredExtraFilterFurni) {
|
||||
furniLimit = Math.min(furniLimit, ((WiredExtraFilterFurni) extra).getAmount());
|
||||
} else if (extra instanceof WiredExtraFilterUser) {
|
||||
userLimit = Math.min(userLimit, ((WiredExtraFilterUser) extra).getAmount());
|
||||
} else if (extra instanceof WiredExtraFilterFurniByVariable) {
|
||||
furniVariableFilters.add((WiredExtraFilterFurniByVariable) extra);
|
||||
} else if (extra instanceof WiredExtraFilterUsersByVariable) {
|
||||
userVariableFilters.add((WiredExtraFilterUsersByVariable) extra);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.targets().isItemsModifiedBySelector() && furniLimit != Integer.MAX_VALUE) {
|
||||
ctx.targets().setItems(limitIterable(ctx.targets().items(), furniLimit));
|
||||
furniVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId()));
|
||||
userVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId()));
|
||||
|
||||
if (ctx.targets().isItemsModifiedBySelector()) {
|
||||
Iterable<HabboItem> filteredItems = ctx.targets().items();
|
||||
|
||||
for (WiredExtraFilterFurniByVariable extra : furniVariableFilters) {
|
||||
filteredItems = extra.filterItems(room, ctx, filteredItems);
|
||||
}
|
||||
|
||||
if (furniLimit != Integer.MAX_VALUE) {
|
||||
filteredItems = limitIterable(filteredItems, furniLimit);
|
||||
}
|
||||
|
||||
ctx.targets().setItems(filteredItems);
|
||||
}
|
||||
|
||||
if (ctx.targets().isUsersModifiedBySelector() && userLimit != Integer.MAX_VALUE) {
|
||||
ctx.targets().setUsers(limitIterable(ctx.targets().users(), userLimit));
|
||||
if (ctx.targets().isUsersModifiedBySelector()) {
|
||||
Iterable<RoomUnit> filteredUsers = ctx.targets().users();
|
||||
|
||||
for (WiredExtraFilterUsersByVariable extra : userVariableFilters) {
|
||||
filteredUsers = extra.filterUsers(room, ctx, filteredUsers);
|
||||
}
|
||||
|
||||
if (userLimit != Integer.MAX_VALUE) {
|
||||
filteredUsers = limitIterable(filteredUsers, userLimit);
|
||||
}
|
||||
|
||||
ctx.targets().setUsers(filteredUsers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,6 +754,19 @@ public final class WiredEngine {
|
||||
* Schedule a delayed effect execution.
|
||||
*/
|
||||
private void scheduleDelayedEffect(IWiredEffect effect, WiredContext ctx, int delay, long triggerTime) {
|
||||
WiredRoomDiagnostics diagnostics = getDiagnostics(ctx.room().getId());
|
||||
String sourceLabel = getMonitorSourceLabel(ctx.triggerItem(), ctx.event());
|
||||
int sourceId = getMonitorSourceId(ctx.triggerItem());
|
||||
|
||||
if (!diagnostics.tryScheduleDelayedEvent(
|
||||
System.currentTimeMillis(),
|
||||
sourceLabel,
|
||||
sourceId,
|
||||
String.format("Scheduling delayed effect %s with delay %d tick(s)", effect.getClass().getSimpleName(), delay))) {
|
||||
debug(ctx.room(), "Delayed events cap blocked effect {}", effect.getClass().getSimpleName());
|
||||
return;
|
||||
}
|
||||
|
||||
// Delay is in 500ms ticks
|
||||
long delayMs = delay * 500L;
|
||||
long elapsedSinceTrigger = Math.max(0L, System.currentTimeMillis() - triggerTime);
|
||||
@@ -613,6 +776,7 @@ public final class WiredEngine {
|
||||
|
||||
Emulator.getThreading().run(() -> {
|
||||
if (!room.isLoaded() || room.getHabbos().isEmpty()) {
|
||||
diagnostics.completeDelayedEvent();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -627,6 +791,8 @@ public final class WiredEngine {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Error executing delayed effect: {}", e.getMessage());
|
||||
} finally {
|
||||
diagnostics.completeDelayedEvent();
|
||||
}
|
||||
}, remainingDelayMs);
|
||||
}
|
||||
@@ -712,6 +878,19 @@ public final class WiredEngine {
|
||||
}
|
||||
|
||||
private void scheduleOrderedEffectBatch(List<IWiredEffect> batch, WiredContext ctx, int delay, long triggerTime) {
|
||||
WiredRoomDiagnostics diagnostics = getDiagnostics(ctx.room().getId());
|
||||
String sourceLabel = getMonitorSourceLabel(ctx.triggerItem(), ctx.event());
|
||||
int sourceId = getMonitorSourceId(ctx.triggerItem());
|
||||
|
||||
if (!diagnostics.tryScheduleDelayedEvent(
|
||||
System.currentTimeMillis(),
|
||||
sourceLabel,
|
||||
sourceId,
|
||||
String.format("Scheduling ordered batch with %d effect(s) and delay %d tick(s)", batch.size(), delay))) {
|
||||
debug(ctx.room(), "Delayed events cap blocked ordered batch with {} effect(s)", batch.size());
|
||||
return;
|
||||
}
|
||||
|
||||
long delayMs = delay * 500L;
|
||||
long elapsedSinceTrigger = Math.max(0L, System.currentTimeMillis() - triggerTime);
|
||||
long remainingDelayMs = Math.max(0L, delayMs - elapsedSinceTrigger);
|
||||
@@ -719,10 +898,15 @@ public final class WiredEngine {
|
||||
|
||||
Emulator.getThreading().run(() -> {
|
||||
if (!room.isLoaded() || room.getHabbos().isEmpty()) {
|
||||
diagnostics.completeDelayedEvent();
|
||||
return;
|
||||
}
|
||||
|
||||
executeOrderedEffectBatch(batch, ctx, System.currentTimeMillis(), true);
|
||||
try {
|
||||
executeOrderedEffectBatch(batch, ctx, System.currentTimeMillis(), true);
|
||||
} finally {
|
||||
diagnostics.completeDelayedEvent();
|
||||
}
|
||||
}, remainingDelayMs);
|
||||
}
|
||||
|
||||
@@ -730,21 +914,30 @@ public final class WiredEngine {
|
||||
Room room = ctx.room();
|
||||
RoomUnit actor = ctx.actor().orElse(null);
|
||||
|
||||
for (IWiredEffect effect : batch) {
|
||||
try {
|
||||
if (!useExecutionTimeForCooldown) {
|
||||
ctx.state().step();
|
||||
}
|
||||
WiredMoveCarryHelper.beginMovementCollection();
|
||||
|
||||
effect.execute(ctx);
|
||||
try {
|
||||
for (IWiredEffect effect : batch) {
|
||||
try {
|
||||
if (!useExecutionTimeForCooldown) {
|
||||
ctx.state().step();
|
||||
}
|
||||
|
||||
if (effect instanceof InteractionWiredEffect) {
|
||||
InteractionWiredEffect wiredEffect = (InteractionWiredEffect) effect;
|
||||
wiredEffect.setCooldown(executionTime);
|
||||
wiredEffect.activateBox(room, actor, executionTime);
|
||||
effect.execute(ctx);
|
||||
|
||||
if (effect instanceof InteractionWiredEffect) {
|
||||
InteractionWiredEffect wiredEffect = (InteractionWiredEffect) effect;
|
||||
wiredEffect.setCooldown(executionTime);
|
||||
wiredEffect.activateBox(room, actor, executionTime);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Error executing ordered effect batch item: {}", e.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Error executing ordered effect batch item: {}", e.getMessage());
|
||||
}
|
||||
} finally {
|
||||
ServerMessage movementComposer = WiredMoveCarryHelper.finishMovementCollection();
|
||||
if (movementComposer != null) {
|
||||
room.sendComposer(movementComposer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -961,6 +1154,29 @@ public final class WiredEngine {
|
||||
String prefix = roomId + ":";
|
||||
eventRateLimiters.keySet().removeIf(key -> key.startsWith(prefix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear monitor diagnostics for a specific room.
|
||||
* @param roomId the room ID
|
||||
*/
|
||||
public void clearRoomDiagnostics(int roomId) {
|
||||
roomDiagnostics.remove(roomId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all monitor diagnostics.
|
||||
*/
|
||||
public void clearAllDiagnostics() {
|
||||
roomDiagnostics.clear();
|
||||
}
|
||||
|
||||
public void clearRoomDiagnosticsLogs(int roomId) {
|
||||
WiredRoomDiagnostics diagnostics = roomDiagnostics.get(roomId);
|
||||
|
||||
if (diagnostics != null) {
|
||||
diagnostics.clearLogs();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear room ban for a specific room.
|
||||
@@ -970,6 +1186,23 @@ public final class WiredEngine {
|
||||
public void clearRoomBan(int roomId) {
|
||||
bannedRooms.remove(roomId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a monitor snapshot for a room.
|
||||
* @param roomId the room ID
|
||||
* @return the diagnostics snapshot
|
||||
*/
|
||||
public WiredRoomDiagnostics.Snapshot getDiagnosticsSnapshot(int roomId) {
|
||||
long now = System.currentTimeMillis();
|
||||
long killedUntil = bannedRooms.getOrDefault(roomId, 0L);
|
||||
|
||||
return getDiagnostics(roomId).snapshot(
|
||||
getRecursionDepth(roomId),
|
||||
MAX_RECURSION_DEPTH,
|
||||
killedUntil,
|
||||
now
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a room is currently banned from wired execution.
|
||||
@@ -997,9 +1230,15 @@ public final class WiredEngine {
|
||||
* @param roomId the room ID
|
||||
* @param room the room object (for sending alerts)
|
||||
*/
|
||||
private void banRoom(int roomId, Room room) {
|
||||
private void banRoom(int roomId, Room room, WiredEvent.Type eventType, int eventCount) {
|
||||
long banExpiry = System.currentTimeMillis() + WIRED_BAN_DURATION_MS;
|
||||
bannedRooms.put(roomId, banExpiry);
|
||||
getDiagnostics(roomId).recordKilled(
|
||||
System.currentTimeMillis(),
|
||||
String.format("Rate limit exceeded for %s with %d event(s) in %dms", eventType.name(), eventCount, RATE_LIMIT_WINDOW_MS),
|
||||
eventType.name(),
|
||||
0
|
||||
);
|
||||
|
||||
long banMinutes = WIRED_BAN_DURATION_MS / 60000;
|
||||
|
||||
@@ -1049,10 +1288,109 @@ public final class WiredEngine {
|
||||
boolean limited = tracker.isRateLimited(now);
|
||||
if (limited && tracker.shouldBan(now)) {
|
||||
// First time hitting limit in this suppression window - ban the room
|
||||
banRoom(roomId, room);
|
||||
banRoom(roomId, room, eventType, tracker.getEventCount());
|
||||
}
|
||||
return limited;
|
||||
}
|
||||
|
||||
private WiredRoomDiagnostics getDiagnostics(int roomId) {
|
||||
return roomDiagnostics.computeIfAbsent(roomId, ignored -> new WiredRoomDiagnostics(
|
||||
MONITOR_USAGE_WINDOW_MS,
|
||||
MONITOR_USAGE_LIMIT,
|
||||
MONITOR_DELAYED_EVENTS_LIMIT,
|
||||
MONITOR_OVERLOAD_AVERAGE_MS,
|
||||
MONITOR_OVERLOAD_PEAK_MS,
|
||||
MONITOR_HEAVY_USAGE_PERCENT,
|
||||
MONITOR_HEAVY_CONSECUTIVE_WINDOWS,
|
||||
MONITOR_OVERLOAD_CONSECUTIVE_WINDOWS,
|
||||
MONITOR_HEAVY_DELAYED_PERCENT,
|
||||
200
|
||||
));
|
||||
}
|
||||
|
||||
private int estimateStackCost(WiredStack stack, int recursionDepth) {
|
||||
int cost = 1;
|
||||
|
||||
if (stack == null) {
|
||||
return cost;
|
||||
}
|
||||
|
||||
cost += Math.max(0, stack.conditions().size());
|
||||
|
||||
for (IWiredEffect effect : stack.effects()) {
|
||||
if (effect == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cost += effect.isSelector() ? 2 : 3;
|
||||
|
||||
if (effect.getDelay() > 0) {
|
||||
cost += 4;
|
||||
}
|
||||
}
|
||||
|
||||
cost += Math.max(0, recursionDepth) * 2;
|
||||
|
||||
return Math.max(1, cost);
|
||||
}
|
||||
|
||||
private String getMonitorSourceLabel(HabboItem triggerItem, WiredEvent event) {
|
||||
if (triggerItem != null && triggerItem.getBaseItem() != null && triggerItem.getBaseItem().getInteractionType() != null) {
|
||||
return triggerItem.getBaseItem().getInteractionType().getName();
|
||||
}
|
||||
|
||||
return (event != null && event.getType() != null) ? event.getType().name() : "room";
|
||||
}
|
||||
|
||||
private int getMonitorSourceId(HabboItem triggerItem) {
|
||||
return triggerItem != null ? triggerItem.getId() : 0;
|
||||
}
|
||||
|
||||
private String buildStackMonitorReason(WiredStack stack, WiredEvent event, int stackCost) {
|
||||
if (stack == null) {
|
||||
return String.format("Processing %s with estimated cost %d", event.getType().name(), stackCost);
|
||||
}
|
||||
|
||||
int selectors = 0;
|
||||
int delayedEffects = 0;
|
||||
|
||||
for (IWiredEffect effect : stack.effects()) {
|
||||
if (effect == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (effect.isSelector()) {
|
||||
selectors++;
|
||||
}
|
||||
|
||||
if (effect.getDelay() > 0) {
|
||||
delayedEffects++;
|
||||
}
|
||||
}
|
||||
|
||||
return String.format(
|
||||
"Trigger %s with %d condition(s), %d effect(s), %d selector(s), %d delayed effect(s) and estimated cost %d",
|
||||
event.getType().name(),
|
||||
stack.conditions().size(),
|
||||
stack.effects().size(),
|
||||
selectors,
|
||||
delayedEffects,
|
||||
stackCost
|
||||
);
|
||||
}
|
||||
|
||||
private String buildExecutionMonitorReason(WiredStack stack, long elapsedMs) {
|
||||
if (stack == null) {
|
||||
return String.format("Execution completed in %dms", elapsedMs);
|
||||
}
|
||||
|
||||
return String.format(
|
||||
"Stack with %d condition(s) and %d effect(s) completed in %dms",
|
||||
stack.conditions().size(),
|
||||
stack.effects().size(),
|
||||
elapsedMs
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks event rate for a specific room + event type combination.
|
||||
@@ -1094,5 +1432,9 @@ public final class WiredEngine {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
synchronized int getEventCount() {
|
||||
return eventCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,9 @@ public final class WiredEvent {
|
||||
|
||||
/** Up-counter reaches a configured elapsed time */
|
||||
CLOCK_COUNTER_REACHED(WiredTriggerType.CLOCK_COUNTER),
|
||||
|
||||
/** A user, furni or global variable changed */
|
||||
VARIABLE_CHANGED(WiredTriggerType.VARIABLE_CHANGED),
|
||||
|
||||
/** Long timer repeat */
|
||||
TIMER_REPEAT_LONG(WiredTriggerType.PERIODICALLY_LONG),
|
||||
@@ -150,6 +153,13 @@ public final class WiredEvent {
|
||||
}
|
||||
}
|
||||
|
||||
public enum VariableChangeKind {
|
||||
NONE,
|
||||
INCREASED,
|
||||
DECREASED,
|
||||
UNCHANGED
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
private final Room room;
|
||||
private final RoomUnit actor; // nullable - the user/bot that caused the event
|
||||
@@ -164,6 +174,16 @@ public final class WiredEvent {
|
||||
private final int signalChannel; // channel for signal routing (0-based)
|
||||
private final int actionId; // user action id for USER_PERFORMS_ACTION
|
||||
private final int actionParameter; // sign/dance parameter when relevant
|
||||
private final int chatType; // RoomChatType metadata for USER_SAYS
|
||||
private final int chatStyle; // bubble style for USER_SAYS
|
||||
private final int signalUserCount; // forwarded users in SIGNAL_RECEIVED
|
||||
private final int signalFurniCount; // forwarded furni in SIGNAL_RECEIVED
|
||||
private final int variableTargetType;
|
||||
private final int variableDefinitionItemId;
|
||||
private final boolean variableCreated;
|
||||
private final boolean variableDeleted;
|
||||
private final VariableChangeKind variableChangeKind;
|
||||
private final WiredContextVariableScope contextVariableScope;
|
||||
private final long createdAtMs;
|
||||
|
||||
private WiredEvent(Builder builder) {
|
||||
@@ -181,6 +201,16 @@ public final class WiredEvent {
|
||||
this.signalChannel = builder.signalChannel;
|
||||
this.actionId = builder.actionId;
|
||||
this.actionParameter = builder.actionParameter;
|
||||
this.chatType = builder.chatType;
|
||||
this.chatStyle = builder.chatStyle;
|
||||
this.signalUserCount = builder.signalUserCount;
|
||||
this.signalFurniCount = builder.signalFurniCount;
|
||||
this.variableTargetType = builder.variableTargetType;
|
||||
this.variableDefinitionItemId = builder.variableDefinitionItemId;
|
||||
this.variableCreated = builder.variableCreated;
|
||||
this.variableDeleted = builder.variableDeleted;
|
||||
this.variableChangeKind = builder.variableChangeKind;
|
||||
this.contextVariableScope = builder.contextVariableScope;
|
||||
this.createdAtMs = builder.createdAtMs;
|
||||
}
|
||||
|
||||
@@ -291,6 +321,46 @@ public final class WiredEvent {
|
||||
return actionParameter;
|
||||
}
|
||||
|
||||
public int getChatType() {
|
||||
return chatType;
|
||||
}
|
||||
|
||||
public int getChatStyle() {
|
||||
return chatStyle;
|
||||
}
|
||||
|
||||
public int getSignalUserCount() {
|
||||
return signalUserCount;
|
||||
}
|
||||
|
||||
public int getSignalFurniCount() {
|
||||
return signalFurniCount;
|
||||
}
|
||||
|
||||
public int getVariableTargetType() {
|
||||
return variableTargetType;
|
||||
}
|
||||
|
||||
public int getVariableDefinitionItemId() {
|
||||
return variableDefinitionItemId;
|
||||
}
|
||||
|
||||
public boolean isVariableCreated() {
|
||||
return variableCreated;
|
||||
}
|
||||
|
||||
public boolean isVariableDeleted() {
|
||||
return variableDeleted;
|
||||
}
|
||||
|
||||
public VariableChangeKind getVariableChangeKind() {
|
||||
return variableChangeKind;
|
||||
}
|
||||
|
||||
public WiredContextVariableScope getContextVariableScope() {
|
||||
return contextVariableScope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp when this event was created.
|
||||
* @return milliseconds since epoch
|
||||
@@ -348,6 +418,16 @@ public final class WiredEvent {
|
||||
private int signalChannel;
|
||||
private int actionId;
|
||||
private int actionParameter = -1;
|
||||
private int chatType = -1;
|
||||
private int chatStyle = -1;
|
||||
private int signalUserCount;
|
||||
private int signalFurniCount;
|
||||
private int variableTargetType = -1;
|
||||
private int variableDefinitionItemId;
|
||||
private boolean variableCreated;
|
||||
private boolean variableDeleted;
|
||||
private VariableChangeKind variableChangeKind = VariableChangeKind.NONE;
|
||||
private WiredContextVariableScope contextVariableScope;
|
||||
private long createdAtMs = System.currentTimeMillis();
|
||||
|
||||
private Builder(Type type, Room room) {
|
||||
@@ -462,6 +542,56 @@ public final class WiredEvent {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder chatType(int chatType) {
|
||||
this.chatType = chatType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder chatStyle(int chatStyle) {
|
||||
this.chatStyle = chatStyle;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder signalUserCount(int signalUserCount) {
|
||||
this.signalUserCount = Math.max(0, signalUserCount);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder signalFurniCount(int signalFurniCount) {
|
||||
this.signalFurniCount = Math.max(0, signalFurniCount);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder variableTargetType(int variableTargetType) {
|
||||
this.variableTargetType = variableTargetType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder variableDefinitionItemId(int variableDefinitionItemId) {
|
||||
this.variableDefinitionItemId = Math.max(0, variableDefinitionItemId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder variableCreated(boolean variableCreated) {
|
||||
this.variableCreated = variableCreated;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder variableDeleted(boolean variableDeleted) {
|
||||
this.variableDeleted = variableDeleted;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder variableChangeKind(VariableChangeKind variableChangeKind) {
|
||||
this.variableChangeKind = (variableChangeKind != null) ? variableChangeKind : VariableChangeKind.NONE;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder contextVariableScope(WiredContextVariableScope contextVariableScope) {
|
||||
this.contextVariableScope = contextVariableScope;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom creation timestamp.
|
||||
* @param createdAtMs milliseconds since epoch
|
||||
|
||||
+554
@@ -0,0 +1,554 @@
|
||||
package com.eu.habbo.habbohotel.wired.core;
|
||||
|
||||
import com.eu.habbo.habbohotel.bots.Bot;
|
||||
import com.eu.habbo.habbohotel.games.Game;
|
||||
import com.eu.habbo.habbohotel.games.GamePlayer;
|
||||
import com.eu.habbo.habbohotel.games.GameTeam;
|
||||
import com.eu.habbo.habbohotel.games.GameTeamColors;
|
||||
import com.eu.habbo.habbohotel.items.FurnitureType;
|
||||
import com.eu.habbo.habbohotel.pets.Pet;
|
||||
import com.eu.habbo.habbohotel.rooms.FurnitureMovementError;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomRightLevels;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomTile;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomTileState;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUserRotation;
|
||||
import com.eu.habbo.habbohotel.users.DanceType;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.users.HabboGender;
|
||||
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||
import com.eu.habbo.util.HotelDateTimeUtil;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class WiredInternalVariableSupport {
|
||||
private WiredInternalVariableSupport() {
|
||||
}
|
||||
|
||||
public static String normalizeKey(String key) {
|
||||
if (key == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String normalized = key.trim();
|
||||
|
||||
return switch (normalized) {
|
||||
case "@position.x" -> "@position_x";
|
||||
case "@position.y" -> "@position_y";
|
||||
case "@effect" -> "@effect_id";
|
||||
case "@handitems" -> "@handitem_id";
|
||||
case "@is_mute" -> "@is_muted";
|
||||
case "@teams.red.score" -> "@team_red_score";
|
||||
case "@teams.green.score" -> "@team_green_score";
|
||||
case "@teams.blue.score" -> "@team_blue_score";
|
||||
case "@teams.yellow.score" -> "@team_yellow_score";
|
||||
case "@teams.red.size" -> "@team_red_size";
|
||||
case "@teams.green.size" -> "@team_green_size";
|
||||
case "@teams.blue.size" -> "@team_blue_size";
|
||||
case "@teams.yellow.size" -> "@team_yellow_size";
|
||||
default -> normalized;
|
||||
};
|
||||
}
|
||||
|
||||
public static boolean canUseUserDestination(String key) {
|
||||
String normalized = normalizeKey(key);
|
||||
return "@position_x".equals(normalized) || "@position_y".equals(normalized) || "@direction".equals(normalized);
|
||||
}
|
||||
|
||||
public static boolean canUseFurniDestination(String key) {
|
||||
String normalized = normalizeKey(key);
|
||||
return "@state".equals(normalized) || "@position_x".equals(normalized) || "@position_y".equals(normalized)
|
||||
|| "@rotation".equals(normalized) || "@altitude".equals(normalized);
|
||||
}
|
||||
|
||||
public static boolean canUseUserReference(String key) {
|
||||
String normalized = normalizeKey(key);
|
||||
|
||||
return "@index".equals(normalized) || "@type".equals(normalized) || "@gender".equals(normalized)
|
||||
|| "@level".equals(normalized) || "@achievement_score".equals(normalized) || "@is_hc".equals(normalized)
|
||||
|| "@has_rights".equals(normalized) || "@is_group_admin".equals(normalized) || "@is_owner".equals(normalized)
|
||||
|| "@is_muted".equals(normalized) || "@is_trading".equals(normalized) || "@is_frozen".equals(normalized)
|
||||
|| "@effect_id".equals(normalized) || "@team_score".equals(normalized) || "@team_color".equals(normalized)
|
||||
|| "@team_type".equals(normalized) || "@sign".equals(normalized) || "@dance".equals(normalized)
|
||||
|| "@is_idle".equals(normalized) || "@handitem_id".equals(normalized) || "@position_x".equals(normalized)
|
||||
|| "@position_y".equals(normalized) || "@direction".equals(normalized) || "@altitude".equals(normalized)
|
||||
|| "@favourite_group_id".equals(normalized) || "@room_entry.method".equals(normalized)
|
||||
|| "@room_entry.teleport_id".equals(normalized) || "@user_id".equals(normalized)
|
||||
|| "@bot_id".equals(normalized) || "@pet_id".equals(normalized) || "@pet_owner_id".equals(normalized);
|
||||
}
|
||||
|
||||
public static boolean canUseFurniReference(String key) {
|
||||
String normalized = normalizeKey(key);
|
||||
|
||||
return "~teleport.target_id".equals(normalized) || "@id".equals(normalized) || "@class_id".equals(normalized)
|
||||
|| "@height".equals(normalized) || "@state".equals(normalized) || "@position_x".equals(normalized)
|
||||
|| "@position_y".equals(normalized) || "@rotation".equals(normalized) || "@altitude".equals(normalized)
|
||||
|| "@is_invisible".equals(normalized) || "@type".equals(normalized) || "@is_stackable".equals(normalized)
|
||||
|| "@can_stand_on".equals(normalized) || "@can_sit_on".equals(normalized) || "@can_lay_on".equals(normalized)
|
||||
|| "@owner_id".equals(normalized) || "@wallitem_offset".equals(normalized)
|
||||
|| "@dimensions.x".equals(normalized) || "@dimensions.y".equals(normalized);
|
||||
}
|
||||
|
||||
public static boolean canUseRoomReference(String key) {
|
||||
String normalized = normalizeKey(key);
|
||||
|
||||
return "@furni_count".equals(normalized) || "@user_count".equals(normalized) || "@wired_timer".equals(normalized)
|
||||
|| "@team_red_score".equals(normalized) || "@team_green_score".equals(normalized) || "@team_blue_score".equals(normalized)
|
||||
|| "@team_yellow_score".equals(normalized) || "@team_red_size".equals(normalized) || "@team_green_size".equals(normalized)
|
||||
|| "@team_blue_size".equals(normalized) || "@team_yellow_size".equals(normalized) || "@room_id".equals(normalized)
|
||||
|| "@group_id".equals(normalized) || "@timezone_server".equals(normalized) || "@timezone_client".equals(normalized)
|
||||
|| "@current_time".equals(normalized) || "@current_time.millisecond_of_second".equals(normalized)
|
||||
|| "@current_time.seconds_of_minute".equals(normalized) || "@current_time.minute_of_hour".equals(normalized)
|
||||
|| "@current_time.hour_of_day".equals(normalized) || "@current_time.day_of_week".equals(normalized)
|
||||
|| "@current_time.day_of_month".equals(normalized) || "@current_time.day_of_year".equals(normalized)
|
||||
|| "@current_time.week_of_year".equals(normalized) || "@current_time.month_of_year".equals(normalized)
|
||||
|| "@current_time.year".equals(normalized);
|
||||
}
|
||||
|
||||
public static boolean canUseContextReference(String key) {
|
||||
String normalized = normalizeKey(key);
|
||||
|
||||
return "@selector_furni_count".equals(normalized) || "@selector_user_count".equals(normalized)
|
||||
|| "@signal_furni_count".equals(normalized) || "@signal_user_count".equals(normalized)
|
||||
|| "@antenna_id".equals(normalized) || "@chat_type".equals(normalized) || "@chat_style".equals(normalized);
|
||||
}
|
||||
|
||||
public static boolean hasUserValue(Room room, RoomUnit roomUnit, String key) {
|
||||
if (room == null || roomUnit == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Habbo habbo = room.getHabbo(roomUnit);
|
||||
Bot bot = room.getBot(roomUnit);
|
||||
Pet pet = room.getPet(roomUnit);
|
||||
String normalized = normalizeKey(key);
|
||||
|
||||
return switch (normalized) {
|
||||
case "@index", "@type", "@level", "@achievement_score", "@position_x", "@position_y", "@direction", "@altitude" -> true;
|
||||
case "@gender" -> habbo != null || bot != null;
|
||||
case "@is_hc" -> habbo != null && habbo.getHabboStats().hasActiveClub();
|
||||
case "@has_rights" -> habbo != null && (room.hasRights(habbo) || room.getGuildRightLevel(habbo).isEqualOrGreaterThan(RoomRightLevels.GUILD_RIGHTS));
|
||||
case "@is_group_admin" -> habbo != null && room.getGuildRightLevel(habbo).isEqualOrGreaterThan(RoomRightLevels.GUILD_ADMIN);
|
||||
case "@is_owner" -> habbo != null && room.isOwner(habbo);
|
||||
case "@is_muted" -> (habbo != null && room.isMuted(habbo)) || (pet != null && pet.isMuted());
|
||||
case "@is_trading" -> habbo != null && room.getActiveTradeForHabbo(habbo) != null;
|
||||
case "@is_frozen" -> WiredFreezeUtil.isFrozen(roomUnit);
|
||||
case "@effect_id" -> roomUnit.getEffectId() > 0;
|
||||
case "@team_score", "@team_color", "@team_type" -> getTeamEffectData(roomUnit.getEffectId()) != null;
|
||||
case "@sign" -> roomUnit.hasStatus(RoomUnitStatus.SIGN);
|
||||
case "@dance" -> roomUnit.getDanceType() != null && roomUnit.getDanceType() != DanceType.NONE;
|
||||
case "@is_idle" -> roomUnit.isIdle();
|
||||
case "@handitem_id" -> roomUnit.getHandItem() > 0;
|
||||
case "@favourite_group_id" -> habbo != null && habbo.getHabboStats().guild > 0;
|
||||
case "@room_entry.method" -> habbo != null && hasRoomEntryMethod(habbo);
|
||||
case "@room_entry.teleport_id" -> habbo != null && habbo.getHabboInfo().getRoomEntryTeleportId() > 0;
|
||||
case "@user_id" -> habbo != null;
|
||||
case "@bot_id" -> bot != null;
|
||||
case "@pet_id" -> pet != null;
|
||||
case "@pet_owner_id" -> pet != null && pet.getUserId() > 0;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
public static boolean hasFurniValue(HabboItem item, String key) {
|
||||
if (item == null || item.getBaseItem() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String normalized = normalizeKey(key);
|
||||
|
||||
return switch (normalized) {
|
||||
case "@id", "@class_id", "@height", "@state", "@position_x", "@position_y", "@rotation", "@altitude",
|
||||
"@is_invisible", "@type", "@owner_id", "@dimensions.x", "@dimensions.y" -> true;
|
||||
case "~teleport.target_id" -> item.getTeleportTargetId() > 0;
|
||||
case "@wallitem_offset" -> item.getBaseItem().getType() == FurnitureType.WALL;
|
||||
case "@is_stackable" -> item.getBaseItem().allowStack();
|
||||
case "@can_stand_on" -> item.getBaseItem().allowWalk();
|
||||
case "@can_sit_on" -> item.getBaseItem().allowSit();
|
||||
case "@can_lay_on" -> item.getBaseItem().allowLay();
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
public static boolean hasRoomValue(Room room, String key) {
|
||||
return room != null && canUseRoomReference(key);
|
||||
}
|
||||
|
||||
public static Integer readUserValue(Room room, RoomUnit roomUnit, String key) {
|
||||
if (room == null || roomUnit == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Habbo habbo = room.getHabbo(roomUnit);
|
||||
Bot bot = room.getBot(roomUnit);
|
||||
Pet pet = room.getPet(roomUnit);
|
||||
String normalized = normalizeKey(key);
|
||||
|
||||
return switch (normalized) {
|
||||
case "@index" -> roomUnit.getId();
|
||||
case "@type" -> getUserTypeValue(habbo, bot, pet);
|
||||
case "@gender" -> getGenderValue(habbo, bot);
|
||||
case "@level" -> (roomUnit.getRightsLevel() != null) ? roomUnit.getRightsLevel().level : 0;
|
||||
case "@achievement_score" -> (habbo != null) ? habbo.getHabboStats().getAchievementScore() : null;
|
||||
case "@is_hc" -> (habbo != null && habbo.getHabboStats().hasActiveClub()) ? 1 : 0;
|
||||
case "@has_rights" -> (habbo != null && (room.hasRights(habbo) || room.getGuildRightLevel(habbo).isEqualOrGreaterThan(RoomRightLevels.GUILD_RIGHTS))) ? 1 : 0;
|
||||
case "@is_group_admin" -> (habbo != null && room.getGuildRightLevel(habbo).isEqualOrGreaterThan(RoomRightLevels.GUILD_ADMIN)) ? 1 : 0;
|
||||
case "@is_owner" -> (habbo != null && room.isOwner(habbo)) ? 1 : 0;
|
||||
case "@is_muted" -> ((habbo != null && room.isMuted(habbo)) || (pet != null && pet.isMuted())) ? 1 : 0;
|
||||
case "@is_trading" -> (habbo != null && room.getActiveTradeForHabbo(habbo) != null) ? 1 : 0;
|
||||
case "@is_frozen" -> WiredFreezeUtil.isFrozen(roomUnit) ? 1 : 0;
|
||||
case "@effect_id" -> roomUnit.getEffectId();
|
||||
case "@team_score" -> getUserTeamScore(room, habbo);
|
||||
case "@team_color" -> getTeamColorId(roomUnit.getEffectId());
|
||||
case "@team_type" -> getTeamTypeId(roomUnit.getEffectId());
|
||||
case "@sign" -> parseStatusInteger(roomUnit, RoomUnitStatus.SIGN);
|
||||
case "@dance" -> (roomUnit.getDanceType() != null) ? roomUnit.getDanceType().getType() : 0;
|
||||
case "@is_idle" -> roomUnit.isIdle() ? 1 : 0;
|
||||
case "@handitem_id" -> roomUnit.getHandItem();
|
||||
case "@position_x" -> (int) roomUnit.getX();
|
||||
case "@position_y" -> (int) roomUnit.getY();
|
||||
case "@direction" -> (roomUnit.getBodyRotation() != null) ? (int) roomUnit.getBodyRotation().getValue() : 0;
|
||||
case "@altitude" -> (int) Math.round(roomUnit.getZ() * 100);
|
||||
case "@favourite_group_id" -> (habbo != null) ? habbo.getHabboStats().guild : null;
|
||||
case "@room_entry.method" -> getRoomEntryMethodValue(habbo);
|
||||
case "@room_entry.teleport_id" -> (habbo != null) ? habbo.getHabboInfo().getRoomEntryTeleportId() : null;
|
||||
case "@user_id" -> (habbo != null) ? habbo.getHabboInfo().getId() : null;
|
||||
case "@bot_id" -> (bot != null) ? bot.getId() : null;
|
||||
case "@pet_id" -> (pet != null) ? pet.getId() : null;
|
||||
case "@pet_owner_id" -> (pet != null) ? pet.getUserId() : null;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
public static boolean writeUserValue(Room room, RoomUnit roomUnit, String key, int value) {
|
||||
if (room == null || roomUnit == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String normalized = normalizeKey(key);
|
||||
|
||||
return switch (normalized) {
|
||||
case "@position_x" -> moveUserTo(room, roomUnit, value, roomUnit.getY());
|
||||
case "@position_y" -> moveUserTo(room, roomUnit, roomUnit.getX(), value);
|
||||
case "@direction" -> {
|
||||
RoomUserRotation rotation = RoomUserRotation.fromValue(value);
|
||||
yield WiredUserMovementHelper.updateUserDirection(room, roomUnit, rotation, rotation);
|
||||
}
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
public static Integer readFurniValue(Room room, HabboItem item, String key) {
|
||||
if (room == null || item == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String normalized = normalizeKey(key);
|
||||
|
||||
return switch (normalized) {
|
||||
case "~teleport.target_id" -> item.getTeleportTargetId();
|
||||
case "@id" -> item.getId();
|
||||
case "@class_id" -> (item.getBaseItem() != null) ? item.getBaseItem().getId() : null;
|
||||
case "@height" -> (item.getBaseItem() != null) ? (int) Math.round(item.getBaseItem().getHeight() * 100) : null;
|
||||
case "@state" -> parseInteger(item.getExtradata());
|
||||
case "@position_x" -> (int) item.getX();
|
||||
case "@position_y" -> (int) item.getY();
|
||||
case "@rotation" -> item.getRotation();
|
||||
case "@altitude" -> (int) Math.round(item.getZ() * 100);
|
||||
case "@is_invisible" -> 0;
|
||||
case "@type" -> 0;
|
||||
case "@is_stackable" -> (item.getBaseItem() != null && item.getBaseItem().allowStack()) ? 1 : 0;
|
||||
case "@can_stand_on" -> (item.getBaseItem() != null && item.getBaseItem().allowWalk()) ? 1 : 0;
|
||||
case "@can_sit_on" -> (item.getBaseItem() != null && item.getBaseItem().allowSit()) ? 1 : 0;
|
||||
case "@can_lay_on" -> (item.getBaseItem() != null && item.getBaseItem().allowLay()) ? 1 : 0;
|
||||
case "@wallitem_offset" -> ((item.getBaseItem() != null) && item.getBaseItem().getType() == FurnitureType.WALL && item.getWallPosition() != null && !item.getWallPosition().trim().isEmpty()) ? 1 : 0;
|
||||
case "@dimensions.x" -> (item.getBaseItem() != null) ? (int) item.getBaseItem().getWidth() : null;
|
||||
case "@dimensions.y" -> (item.getBaseItem() != null) ? (int) item.getBaseItem().getLength() : null;
|
||||
case "@owner_id" -> item.getUserId();
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
public static boolean writeFurniValue(Room room, HabboItem item, String key, int value) {
|
||||
if (room == null || item == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String normalized = normalizeKey(key);
|
||||
|
||||
if ("@state".equals(normalized)) {
|
||||
item.setExtradata(String.valueOf(value));
|
||||
room.updateItemState(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getBaseItem() == null || item.getBaseItem().getType() != FurnitureType.FLOOR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return switch (normalized) {
|
||||
case "@position_x" -> moveFurniTo(room, item, value, item.getY(), item.getRotation(), item.getZ());
|
||||
case "@position_y" -> moveFurniTo(room, item, item.getX(), value, item.getRotation(), item.getZ());
|
||||
case "@rotation" -> moveFurniTo(room, item, item.getX(), item.getY(), value, item.getZ());
|
||||
case "@altitude" -> moveFurniTo(room, item, item.getX(), item.getY(), item.getRotation(), value / 100.0);
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
public static Integer readRoomValue(Room room, String key) {
|
||||
if (room == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ZonedDateTime now = HotelDateTimeUtil.now();
|
||||
String normalized = normalizeKey(key);
|
||||
|
||||
return switch (normalized) {
|
||||
case "@furni_count" -> room.getFloorItems().size() + room.getWallItems().size();
|
||||
case "@user_count" -> room.getUserCount();
|
||||
case "@wired_timer" -> (int) (WiredManager.getTickService().getTickCount() / 10L);
|
||||
case "@team_red_score" -> getTeamMetric(room, GameTeamColors.RED, true);
|
||||
case "@team_green_score" -> getTeamMetric(room, GameTeamColors.GREEN, true);
|
||||
case "@team_blue_score" -> getTeamMetric(room, GameTeamColors.BLUE, true);
|
||||
case "@team_yellow_score" -> getTeamMetric(room, GameTeamColors.YELLOW, true);
|
||||
case "@team_red_size" -> getTeamMetric(room, GameTeamColors.RED, false);
|
||||
case "@team_green_size" -> getTeamMetric(room, GameTeamColors.GREEN, false);
|
||||
case "@team_blue_size" -> getTeamMetric(room, GameTeamColors.BLUE, false);
|
||||
case "@team_yellow_size" -> getTeamMetric(room, GameTeamColors.YELLOW, false);
|
||||
case "@room_id" -> room.getId();
|
||||
case "@group_id" -> room.getGuildId();
|
||||
case "@timezone_server" -> now.getOffset().getTotalSeconds() / 60;
|
||||
case "@timezone_client" -> 0;
|
||||
case "@current_time" -> (int) now.toEpochSecond();
|
||||
case "@current_time.millisecond_of_second" -> now.getNano() / 1_000_000;
|
||||
case "@current_time.seconds_of_minute" -> now.getSecond();
|
||||
case "@current_time.minute_of_hour" -> now.getMinute();
|
||||
case "@current_time.hour_of_day" -> now.getHour();
|
||||
case "@current_time.day_of_week" -> now.getDayOfWeek().getValue();
|
||||
case "@current_time.day_of_month" -> now.getDayOfMonth();
|
||||
case "@current_time.day_of_year" -> now.getDayOfYear();
|
||||
case "@current_time.week_of_year" -> now.get(WeekFields.of(Locale.ITALY).weekOfWeekBasedYear());
|
||||
case "@current_time.month_of_year" -> now.getMonthValue();
|
||||
case "@current_time.year" -> now.getYear();
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
public static Integer readContextValue(WiredContext ctx, String key) {
|
||||
if (ctx == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String normalized = normalizeKey(key);
|
||||
|
||||
return switch (normalized) {
|
||||
case "@selector_furni_count" -> countIterable(ctx.targets() != null ? ctx.targets().items() : null);
|
||||
case "@selector_user_count" -> countIterable(ctx.targets() != null ? ctx.targets().users() : null);
|
||||
case "@signal_furni_count" -> ctx.event().getSignalFurniCount();
|
||||
case "@signal_user_count" -> ctx.event().getSignalUserCount();
|
||||
case "@antenna_id" -> ctx.event().getSignalChannel();
|
||||
case "@chat_type" -> ctx.event().getChatType();
|
||||
case "@chat_style" -> ctx.event().getChatStyle();
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private static Integer getUserTypeValue(Habbo habbo, Bot bot, Pet pet) {
|
||||
if (habbo != null) return 1;
|
||||
if (pet != null) return 2;
|
||||
if (bot != null) return 4;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Integer getGenderValue(Habbo habbo, Bot bot) {
|
||||
HabboGender gender = null;
|
||||
|
||||
if (habbo != null && habbo.getHabboInfo() != null) {
|
||||
gender = habbo.getHabboInfo().getGender();
|
||||
} else if (bot != null) {
|
||||
gender = bot.getGender();
|
||||
}
|
||||
|
||||
if (gender == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (gender == HabboGender.F) ? 1 : 0;
|
||||
}
|
||||
|
||||
private static Integer getUserTeamScore(Room room, Habbo habbo) {
|
||||
if (room == null || habbo == null || habbo.getHabboInfo() == null || habbo.getHabboInfo().getGamePlayer() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Game game = resolveTeamGame(room, habbo);
|
||||
GamePlayer gamePlayer = habbo.getHabboInfo().getGamePlayer();
|
||||
|
||||
if (game == null || gamePlayer.getTeamColor() == null) {
|
||||
return gamePlayer.getScore();
|
||||
}
|
||||
|
||||
GameTeam team = game.getTeam(gamePlayer.getTeamColor());
|
||||
return (team != null) ? team.getTotalScore() : gamePlayer.getScore();
|
||||
}
|
||||
|
||||
private static Integer getTeamColorId(int effectId) {
|
||||
TeamEffectData data = getTeamEffectData(effectId);
|
||||
return (data != null) ? data.colorId : null;
|
||||
}
|
||||
|
||||
private static Integer getTeamTypeId(int effectId) {
|
||||
TeamEffectData data = getTeamEffectData(effectId);
|
||||
return (data != null) ? data.typeId : null;
|
||||
}
|
||||
|
||||
private static TeamEffectData getTeamEffectData(int effectId) {
|
||||
if (effectId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (effectId >= 223 && effectId <= 226) return new TeamEffectData(effectId - 222, 0);
|
||||
if (effectId >= 33 && effectId <= 36) return new TeamEffectData(effectId - 32, 1);
|
||||
if (effectId >= 40 && effectId <= 43) return new TeamEffectData(effectId - 39, 2);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int getTeamMetric(Room room, GameTeamColors color, boolean score) {
|
||||
Game game = resolveTeamGame(room, null);
|
||||
if (game == null || color == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
GameTeam team = game.getTeam(color);
|
||||
if (team == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return score ? team.getTotalScore() : team.getMembers().size();
|
||||
}
|
||||
|
||||
private static Game resolveTeamGame(Room room, Habbo habbo) {
|
||||
if (room == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (habbo != null && habbo.getHabboInfo() != null && habbo.getHabboInfo().getCurrentGame() != null) {
|
||||
Game game = room.getGame(habbo.getHabboInfo().getCurrentGame());
|
||||
if (game != null) {
|
||||
return game;
|
||||
}
|
||||
}
|
||||
|
||||
Game game = room.getGame(com.eu.habbo.habbohotel.games.battlebanzai.BattleBanzaiGame.class);
|
||||
if (game != null) {
|
||||
return game;
|
||||
}
|
||||
|
||||
game = room.getGame(com.eu.habbo.habbohotel.games.freeze.FreezeGame.class);
|
||||
if (game != null) {
|
||||
return game;
|
||||
}
|
||||
|
||||
return room.getGame(com.eu.habbo.habbohotel.games.wired.WiredGame.class);
|
||||
}
|
||||
|
||||
private static boolean hasRoomEntryMethod(Habbo habbo) {
|
||||
if (habbo == null || habbo.getHabboInfo() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String roomEntryMethod = habbo.getHabboInfo().getRoomEntryMethod();
|
||||
return roomEntryMethod != null && !roomEntryMethod.trim().isEmpty() && !"unknown".equalsIgnoreCase(roomEntryMethod);
|
||||
}
|
||||
|
||||
private static Integer getRoomEntryMethodValue(Habbo habbo) {
|
||||
if (habbo == null || habbo.getHabboInfo() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String roomEntryMethod = habbo.getHabboInfo().getRoomEntryMethod();
|
||||
|
||||
if (roomEntryMethod == null || roomEntryMethod.trim().isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return switch (roomEntryMethod.trim().toLowerCase(Locale.ROOT)) {
|
||||
case "door" -> 1;
|
||||
case "teleport" -> 2;
|
||||
default -> 3;
|
||||
};
|
||||
}
|
||||
|
||||
private static int parseStatusInteger(RoomUnit roomUnit, RoomUnitStatus status) {
|
||||
if (roomUnit == null || status == null || !roomUnit.hasStatus(status)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return parseInteger(roomUnit.getStatus(status));
|
||||
}
|
||||
|
||||
private static boolean moveUserTo(Room room, RoomUnit roomUnit, int x, int y) {
|
||||
if (room == null || roomUnit == null || room.getLayout() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RoomTile targetTile = room.getLayout().getTile((short) x, (short) y);
|
||||
if (targetTile == null || targetTile.state == RoomTileState.INVALID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
double targetZ = targetTile.getStackHeight() + ((targetTile.state == RoomTileState.SIT) ? -0.5 : 0);
|
||||
return WiredUserMovementHelper.moveUser(room, roomUnit, targetTile, targetZ, roomUnit.getBodyRotation(), roomUnit.getHeadRotation(), 0, true);
|
||||
}
|
||||
|
||||
private static boolean moveFurniTo(Room room, HabboItem item, int x, int y, int rotation, double z) {
|
||||
if (room == null || item == null || room.getLayout() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RoomTile targetTile = room.getLayout().getTile((short) x, (short) y);
|
||||
if (targetTile == null || targetTile.state == RoomTileState.INVALID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FurnitureMovementError error = room.moveFurniTo(item, targetTile, rotation, z, null, true, true);
|
||||
return error == FurnitureMovementError.NONE;
|
||||
}
|
||||
|
||||
private static int parseInteger(String value) {
|
||||
try {
|
||||
return (value == null || value.trim().isEmpty()) ? 0 : Integer.parseInt(value.trim());
|
||||
} catch (NumberFormatException exception) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static int countIterable(Iterable<?> values) {
|
||||
if (values == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (Object ignored : values) {
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static class TeamEffectData {
|
||||
final int colorId;
|
||||
final int typeId;
|
||||
|
||||
TeamEffectData(int colorId, int typeId) {
|
||||
this.colorId = colorId;
|
||||
this.typeId = typeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,29 +40,21 @@ import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Manager class for the new wired engine system.
|
||||
* Manager class for the wired runtime.
|
||||
* <p>
|
||||
* This class serves as the integration point between the emulator and the new
|
||||
* wired engine. It provides static methods for triggering events and manages
|
||||
* the lifecycle of the engine.
|
||||
* WiredManager is now the sole runtime entrypoint for wired execution. Legacy
|
||||
* configuration keys are still read for backwards compatibility with existing
|
||||
* databases, but they no longer switch execution back to {@code WiredHandler}.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* <h3>Configuration Options:</h3>
|
||||
* <ul>
|
||||
* <li>{@code wired.engine.enabled} - Enable new engine (parallel mode)</li>
|
||||
* <li>{@code wired.engine.exclusive} - Disable legacy handler when true</li>
|
||||
* <li>{@code wired.engine.enabled} - Compatibility flag kept for old configs</li>
|
||||
* <li>{@code wired.engine.exclusive} - Compatibility flag kept for old configs</li>
|
||||
* <li>{@code wired.engine.maxStepsPerStack} - Loop protection limit</li>
|
||||
* <li>{@code wired.engine.debug} - Verbose logging</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h3>Migration Strategy:</h3>
|
||||
* <ol>
|
||||
* <li>Set {@code wired.engine.enabled=true} to run both engines in parallel</li>
|
||||
* <li>Test thoroughly to ensure identical behavior</li>
|
||||
* <li>Set {@code wired.engine.exclusive=true} to disable legacy engine</li>
|
||||
* <li>Full migration complete - WiredManager is now the only wired engine</li>
|
||||
* </ol>
|
||||
*
|
||||
*
|
||||
* @see WiredEngine
|
||||
* @see WiredEvents
|
||||
*/
|
||||
@@ -80,8 +72,8 @@ public final class WiredManager {
|
||||
public static final String CONFIG_DEBUG = "wired.engine.debug";
|
||||
|
||||
// Defaults
|
||||
private static final boolean DEFAULT_ENABLED = false;
|
||||
private static final boolean DEFAULT_EXCLUSIVE = false;
|
||||
private static final boolean DEFAULT_ENABLED = true;
|
||||
private static final boolean DEFAULT_EXCLUSIVE = true;
|
||||
private static final int DEFAULT_MAX_STEPS = 100;
|
||||
|
||||
/** The singleton engine instance */
|
||||
@@ -117,6 +109,7 @@ public final class WiredManager {
|
||||
|
||||
// Load configuration
|
||||
boolean enabled = Emulator.getConfig().getBoolean(CONFIG_ENABLED, DEFAULT_ENABLED);
|
||||
boolean exclusive = Emulator.getConfig().getBoolean(CONFIG_EXCLUSIVE, DEFAULT_EXCLUSIVE);
|
||||
int maxSteps = Emulator.getConfig().getInt(CONFIG_MAX_STEPS, DEFAULT_MAX_STEPS);
|
||||
boolean debug = Emulator.getConfig().getBoolean(CONFIG_DEBUG, false);
|
||||
|
||||
@@ -138,9 +131,13 @@ public final class WiredManager {
|
||||
WiredTickService.getInstance().start();
|
||||
|
||||
initialized = true;
|
||||
|
||||
LOGGER.info("Wired Manager initialized - enabled: {}, maxSteps: {}, debug: {}",
|
||||
enabled, maxSteps, debug);
|
||||
|
||||
if (!enabled || !exclusive) {
|
||||
LOGGER.warn("wired.engine.enabled / wired.engine.exclusive are now compatibility-only flags. WiredManager runs as the exclusive engine runtime.");
|
||||
}
|
||||
|
||||
LOGGER.info("Wired Manager initialized - exclusive runtime active, maxSteps: {}, debug: {}",
|
||||
maxSteps, debug);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,6 +160,7 @@ public final class WiredManager {
|
||||
|
||||
if (engine != null) {
|
||||
engine.clearUnseenCache();
|
||||
engine.clearAllDiagnostics();
|
||||
}
|
||||
|
||||
initialized = false;
|
||||
@@ -174,7 +172,7 @@ public final class WiredManager {
|
||||
* @return true if enabled
|
||||
*/
|
||||
public static boolean isEnabled() {
|
||||
return Emulator.getConfig().getBoolean(CONFIG_ENABLED, DEFAULT_ENABLED);
|
||||
return initialized && engine != null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,7 +180,7 @@ public final class WiredManager {
|
||||
* @return true if exclusive mode
|
||||
*/
|
||||
public static boolean isExclusive() {
|
||||
return Emulator.getConfig().getBoolean(CONFIG_EXCLUSIVE, DEFAULT_EXCLUSIVE);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,6 +199,27 @@ public final class WiredManager {
|
||||
return stackIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current monitor snapshot for a room.
|
||||
* @param roomId the room ID
|
||||
* @return the diagnostics snapshot, or null if the engine is unavailable
|
||||
*/
|
||||
public static WiredRoomDiagnostics.Snapshot getDiagnosticsSnapshot(int roomId) {
|
||||
if (engine == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return engine.getDiagnosticsSnapshot(roomId);
|
||||
}
|
||||
|
||||
public static void clearDiagnosticsLogs(int roomId) {
|
||||
if (engine == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
engine.clearRoomDiagnosticsLogs(roomId);
|
||||
}
|
||||
|
||||
// ========== Event Triggering Methods ==========
|
||||
|
||||
/**
|
||||
@@ -308,20 +327,28 @@ public final class WiredManager {
|
||||
* Trigger when a user says something.
|
||||
*/
|
||||
public static boolean triggerUserSays(Room room, RoomUnit user, String message) {
|
||||
return triggerUserSays(room, user, message, -1, -1);
|
||||
}
|
||||
|
||||
public static boolean triggerUserSays(Room room, RoomUnit user, String message, int chatType, int chatStyle) {
|
||||
if (!isEnabled() || room == null || user == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WiredEvent event = WiredEvents.userSays(room, user, message);
|
||||
|
||||
WiredEvent event = WiredEvents.userSays(room, user, message, chatType, chatStyle);
|
||||
return handleEvent(event);
|
||||
}
|
||||
|
||||
public static boolean shouldSuppressUserSaysOutput(Room room, RoomUnit user, String message) {
|
||||
return shouldSuppressUserSaysOutput(room, user, message, -1, -1);
|
||||
}
|
||||
|
||||
public static boolean shouldSuppressUserSaysOutput(Room room, RoomUnit user, String message, int chatType, int chatStyle) {
|
||||
if (!isEnabled() || engine == null || room == null || user == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WiredEvent event = WiredEvents.userSays(room, user, message);
|
||||
WiredEvent event = WiredEvents.userSays(room, user, message, chatType, chatStyle);
|
||||
return engine.shouldSuppressUserSaysOutput(event);
|
||||
}
|
||||
|
||||
@@ -361,6 +388,36 @@ public final class WiredManager {
|
||||
return handleEvent(event);
|
||||
}
|
||||
|
||||
public static boolean triggerUserVariableChanged(Room room, int userId, int definitionItemId, boolean created, boolean deleted, WiredEvent.VariableChangeKind changeKind) {
|
||||
if (!isEnabled() || room == null || definitionItemId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Habbo habbo = room.getHabbo(userId);
|
||||
RoomUnit roomUnit = (habbo != null) ? habbo.getRoomUnit() : null;
|
||||
WiredEvent event = WiredEvents.userVariableChanged(room, roomUnit, definitionItemId, created, deleted, changeKind);
|
||||
return handleEvent(event);
|
||||
}
|
||||
|
||||
public static boolean triggerFurniVariableChanged(Room room, int furniId, int definitionItemId, boolean created, boolean deleted, WiredEvent.VariableChangeKind changeKind) {
|
||||
if (!isEnabled() || room == null || furniId <= 0 || definitionItemId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HabboItem item = room.getHabboItem(furniId);
|
||||
WiredEvent event = WiredEvents.furniVariableChanged(room, item, definitionItemId, created, deleted, changeKind);
|
||||
return handleEvent(event);
|
||||
}
|
||||
|
||||
public static boolean triggerRoomVariableChanged(Room room, int definitionItemId, WiredEvent.VariableChangeKind changeKind) {
|
||||
if (!isEnabled() || room == null || definitionItemId <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WiredEvent event = WiredEvents.roomVariableChanged(room, definitionItemId, changeKind);
|
||||
return handleEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a timer tick.
|
||||
*/
|
||||
@@ -567,8 +624,8 @@ public final class WiredManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger from legacy system for parallel running.
|
||||
* This allows the new engine to run alongside the old one during migration.
|
||||
* Compatibility bridge for code paths that still describe themselves as
|
||||
* legacy-triggered. Execution still goes through the new engine only.
|
||||
*/
|
||||
public static boolean triggerFromLegacy(WiredTriggerType triggerType, RoomUnit roomUnit, Room room, Object[] stuff) {
|
||||
if (!isEnabled() || room == null) {
|
||||
@@ -725,6 +782,11 @@ public final class WiredManager {
|
||||
*/
|
||||
public static void unregisterRoomTickables(Room room) {
|
||||
WiredTickService.getInstance().unregisterRoom(room);
|
||||
|
||||
if (room != null) {
|
||||
room.getFurniVariableManager().clearTransientAssignments();
|
||||
room.getRoomVariableManager().clearTransientAssignments();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+19
-1
@@ -176,7 +176,25 @@ public final class WiredMoveCarryHelper {
|
||||
&& !sendUpdates
|
||||
&& oldLocation != null
|
||||
&& (oldLocation.x != targetTile.x || oldLocation.y != targetTile.y || Double.compare(oldZ, movingItem.getZ()) != 0)) {
|
||||
room.sendComposer(new FloorItemOnRollerComposer(movingItem, null, oldLocation, oldZ, targetTile, movingItem.getZ(), 0, room).compose());
|
||||
List<WiredMovementsComposer.MovementData> collectedMovements = COLLECTED_MOVEMENTS.get();
|
||||
|
||||
if (collectedMovements != null) {
|
||||
collectedMovements.add(WiredMovementsComposer.furniMovement(
|
||||
movingItem.getId(),
|
||||
oldLocation.x,
|
||||
oldLocation.y,
|
||||
targetTile.x,
|
||||
targetTile.y,
|
||||
oldZ,
|
||||
movingItem.getZ(),
|
||||
movingItem.getRotation(),
|
||||
WiredMovementsComposer.DEFAULT_DURATION,
|
||||
0,
|
||||
WiredMovementsComposer.FURNI_ANCHOR_NONE,
|
||||
0));
|
||||
} else {
|
||||
room.sendComposer(new FloorItemOnRollerComposer(movingItem, null, oldLocation, oldZ, targetTile, movingItem.getZ(), 0, room).compose());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -0,0 +1,586 @@
|
||||
package com.eu.habbo.habbohotel.wired.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Tracks wired monitor data for a single room.
|
||||
*/
|
||||
public final class WiredRoomDiagnostics {
|
||||
|
||||
public enum Type {
|
||||
EXECUTION_CAP,
|
||||
DELAYED_EVENTS_CAP,
|
||||
EXECUTOR_OVERLOAD,
|
||||
MARKED_AS_HEAVY,
|
||||
KILLED,
|
||||
RECURSION_TIMEOUT
|
||||
}
|
||||
|
||||
public enum Severity {
|
||||
WARNING,
|
||||
ERROR
|
||||
}
|
||||
|
||||
public static final class LogEntry {
|
||||
private final Type type;
|
||||
private final Severity severity;
|
||||
private int count;
|
||||
private long firstOccurredAtMs;
|
||||
private long lastOccurredAtMs;
|
||||
private String latestReason;
|
||||
private String latestSourceLabel;
|
||||
private int latestSourceId;
|
||||
|
||||
private LogEntry(Type type, Severity severity) {
|
||||
this.type = type;
|
||||
this.severity = severity;
|
||||
}
|
||||
|
||||
private void record(long now, String reason, String sourceLabel, int sourceId) {
|
||||
if (this.count <= 0) {
|
||||
this.firstOccurredAtMs = now;
|
||||
}
|
||||
|
||||
this.count++;
|
||||
this.lastOccurredAtMs = now;
|
||||
this.latestReason = sanitizeReason(reason);
|
||||
this.latestSourceLabel = sanitizeSourceLabel(sourceLabel);
|
||||
this.latestSourceId = Math.max(0, sourceId);
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Severity getSeverity() {
|
||||
return severity;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public long getFirstOccurredAtMs() {
|
||||
return firstOccurredAtMs;
|
||||
}
|
||||
|
||||
public long getLastOccurredAtMs() {
|
||||
return lastOccurredAtMs;
|
||||
}
|
||||
|
||||
public String getLatestReason() {
|
||||
return latestReason;
|
||||
}
|
||||
|
||||
public String getLatestSourceLabel() {
|
||||
return latestSourceLabel;
|
||||
}
|
||||
|
||||
public int getLatestSourceId() {
|
||||
return latestSourceId;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Snapshot {
|
||||
private final int usageCurrentWindow;
|
||||
private final int usageLimitPerWindow;
|
||||
private final boolean heavy;
|
||||
private final int delayedEventsPending;
|
||||
private final int delayedEventsLimit;
|
||||
private final int averageExecutionMs;
|
||||
private final int peakExecutionMs;
|
||||
private final int recursionDepthCurrent;
|
||||
private final int recursionDepthLimit;
|
||||
private final int killedRemainingSeconds;
|
||||
private final int usageWindowMs;
|
||||
private final int overloadAverageThresholdMs;
|
||||
private final int overloadPeakThresholdMs;
|
||||
private final int heavyUsageThresholdPercent;
|
||||
private final int heavyConsecutiveWindowsThreshold;
|
||||
private final int overloadConsecutiveWindowsThreshold;
|
||||
private final int heavyDelayedThresholdPercent;
|
||||
private final List<LogEntry> logs;
|
||||
private final List<HistoryEntry> history;
|
||||
|
||||
public Snapshot(int usageCurrentWindow, int usageLimitPerWindow, boolean heavy, int delayedEventsPending,
|
||||
int delayedEventsLimit, int averageExecutionMs, int peakExecutionMs,
|
||||
int recursionDepthCurrent, int recursionDepthLimit, int killedRemainingSeconds,
|
||||
int usageWindowMs, int overloadAverageThresholdMs, int overloadPeakThresholdMs,
|
||||
int heavyUsageThresholdPercent, int heavyConsecutiveWindowsThreshold,
|
||||
int overloadConsecutiveWindowsThreshold, int heavyDelayedThresholdPercent,
|
||||
List<LogEntry> logs, List<HistoryEntry> history) {
|
||||
this.usageCurrentWindow = usageCurrentWindow;
|
||||
this.usageLimitPerWindow = usageLimitPerWindow;
|
||||
this.heavy = heavy;
|
||||
this.delayedEventsPending = delayedEventsPending;
|
||||
this.delayedEventsLimit = delayedEventsLimit;
|
||||
this.averageExecutionMs = averageExecutionMs;
|
||||
this.peakExecutionMs = peakExecutionMs;
|
||||
this.recursionDepthCurrent = recursionDepthCurrent;
|
||||
this.recursionDepthLimit = recursionDepthLimit;
|
||||
this.killedRemainingSeconds = killedRemainingSeconds;
|
||||
this.usageWindowMs = usageWindowMs;
|
||||
this.overloadAverageThresholdMs = overloadAverageThresholdMs;
|
||||
this.overloadPeakThresholdMs = overloadPeakThresholdMs;
|
||||
this.heavyUsageThresholdPercent = heavyUsageThresholdPercent;
|
||||
this.heavyConsecutiveWindowsThreshold = heavyConsecutiveWindowsThreshold;
|
||||
this.overloadConsecutiveWindowsThreshold = overloadConsecutiveWindowsThreshold;
|
||||
this.heavyDelayedThresholdPercent = heavyDelayedThresholdPercent;
|
||||
this.logs = Collections.unmodifiableList(logs);
|
||||
this.history = Collections.unmodifiableList(history);
|
||||
}
|
||||
|
||||
public int getUsageCurrentWindow() {
|
||||
return usageCurrentWindow;
|
||||
}
|
||||
|
||||
public int getUsageLimitPerWindow() {
|
||||
return usageLimitPerWindow;
|
||||
}
|
||||
|
||||
public boolean isHeavy() {
|
||||
return heavy;
|
||||
}
|
||||
|
||||
public int getDelayedEventsPending() {
|
||||
return delayedEventsPending;
|
||||
}
|
||||
|
||||
public int getDelayedEventsLimit() {
|
||||
return delayedEventsLimit;
|
||||
}
|
||||
|
||||
public int getAverageExecutionMs() {
|
||||
return averageExecutionMs;
|
||||
}
|
||||
|
||||
public int getPeakExecutionMs() {
|
||||
return peakExecutionMs;
|
||||
}
|
||||
|
||||
public int getRecursionDepthCurrent() {
|
||||
return recursionDepthCurrent;
|
||||
}
|
||||
|
||||
public int getRecursionDepthLimit() {
|
||||
return recursionDepthLimit;
|
||||
}
|
||||
|
||||
public int getKilledRemainingSeconds() {
|
||||
return killedRemainingSeconds;
|
||||
}
|
||||
|
||||
public int getUsageWindowMs() {
|
||||
return usageWindowMs;
|
||||
}
|
||||
|
||||
public int getOverloadAverageThresholdMs() {
|
||||
return overloadAverageThresholdMs;
|
||||
}
|
||||
|
||||
public int getOverloadPeakThresholdMs() {
|
||||
return overloadPeakThresholdMs;
|
||||
}
|
||||
|
||||
public int getHeavyUsageThresholdPercent() {
|
||||
return heavyUsageThresholdPercent;
|
||||
}
|
||||
|
||||
public int getHeavyConsecutiveWindowsThreshold() {
|
||||
return heavyConsecutiveWindowsThreshold;
|
||||
}
|
||||
|
||||
public int getOverloadConsecutiveWindowsThreshold() {
|
||||
return overloadConsecutiveWindowsThreshold;
|
||||
}
|
||||
|
||||
public int getHeavyDelayedThresholdPercent() {
|
||||
return heavyDelayedThresholdPercent;
|
||||
}
|
||||
|
||||
public List<LogEntry> getLogs() {
|
||||
return logs;
|
||||
}
|
||||
|
||||
public List<HistoryEntry> getHistory() {
|
||||
return history;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class HistoryEntry {
|
||||
private final Type type;
|
||||
private final Severity severity;
|
||||
private final long occurredAtMs;
|
||||
private final String reason;
|
||||
private final String sourceLabel;
|
||||
private final int sourceId;
|
||||
|
||||
public HistoryEntry(Type type, Severity severity, long occurredAtMs, String reason, String sourceLabel, int sourceId) {
|
||||
this.type = type;
|
||||
this.severity = severity;
|
||||
this.occurredAtMs = occurredAtMs;
|
||||
this.reason = sanitizeReason(reason);
|
||||
this.sourceLabel = sanitizeSourceLabel(sourceLabel);
|
||||
this.sourceId = Math.max(0, sourceId);
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Severity getSeverity() {
|
||||
return severity;
|
||||
}
|
||||
|
||||
public long getOccurredAtMs() {
|
||||
return occurredAtMs;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public String getSourceLabel() {
|
||||
return sourceLabel;
|
||||
}
|
||||
|
||||
public int getSourceId() {
|
||||
return sourceId;
|
||||
}
|
||||
}
|
||||
|
||||
private final int usageWindowMs;
|
||||
private final int usageLimitPerWindow;
|
||||
private final int delayedEventsLimit;
|
||||
private final int overloadAverageThresholdMs;
|
||||
private final int overloadPeakThresholdMs;
|
||||
private final int heavyUsageThresholdPercent;
|
||||
private final int heavyConsecutiveWindowsThreshold;
|
||||
private final int overloadConsecutiveWindowsThreshold;
|
||||
private final int heavyDelayedThresholdPercent;
|
||||
private final EnumMap<Type, LogEntry> logs;
|
||||
private final ArrayDeque<HistoryEntry> history;
|
||||
private final int maxHistoryEntries;
|
||||
|
||||
private long windowStartedAt;
|
||||
private int usageCurrentWindow;
|
||||
private int delayedEventsPending;
|
||||
private long totalExecutionMsCurrentWindow;
|
||||
private int executionSamplesCurrentWindow;
|
||||
private int averageExecutionMs;
|
||||
private int peakExecutionMs;
|
||||
private int consecutiveHeavyWindows;
|
||||
private int consecutiveOverloadWindows;
|
||||
private boolean heavy;
|
||||
private String peakExecutionSourceLabel;
|
||||
private int peakExecutionSourceId;
|
||||
private String peakExecutionReason;
|
||||
|
||||
public WiredRoomDiagnostics(int usageWindowMs, int usageLimitPerWindow, int delayedEventsLimit,
|
||||
int overloadAverageThresholdMs, int overloadPeakThresholdMs,
|
||||
int heavyUsageThresholdPercent, int heavyConsecutiveWindowsThreshold) {
|
||||
this(usageWindowMs, usageLimitPerWindow, delayedEventsLimit, overloadAverageThresholdMs, overloadPeakThresholdMs,
|
||||
heavyUsageThresholdPercent, heavyConsecutiveWindowsThreshold, 2, 60, 200);
|
||||
}
|
||||
|
||||
public WiredRoomDiagnostics(int usageWindowMs, int usageLimitPerWindow, int delayedEventsLimit,
|
||||
int overloadAverageThresholdMs, int overloadPeakThresholdMs,
|
||||
int heavyUsageThresholdPercent, int heavyConsecutiveWindowsThreshold,
|
||||
int overloadConsecutiveWindowsThreshold, int heavyDelayedThresholdPercent,
|
||||
int maxHistoryEntries) {
|
||||
this.usageWindowMs = Math.max(250, usageWindowMs);
|
||||
this.usageLimitPerWindow = Math.max(1, usageLimitPerWindow);
|
||||
this.delayedEventsLimit = Math.max(1, delayedEventsLimit);
|
||||
this.overloadAverageThresholdMs = Math.max(1, overloadAverageThresholdMs);
|
||||
this.overloadPeakThresholdMs = Math.max(this.overloadAverageThresholdMs, overloadPeakThresholdMs);
|
||||
this.heavyUsageThresholdPercent = Math.max(1, Math.min(100, heavyUsageThresholdPercent));
|
||||
this.heavyConsecutiveWindowsThreshold = Math.max(1, heavyConsecutiveWindowsThreshold);
|
||||
this.overloadConsecutiveWindowsThreshold = Math.max(1, overloadConsecutiveWindowsThreshold);
|
||||
this.heavyDelayedThresholdPercent = Math.max(1, Math.min(100, heavyDelayedThresholdPercent));
|
||||
this.maxHistoryEntries = Math.max(10, maxHistoryEntries);
|
||||
this.logs = new EnumMap<>(Type.class);
|
||||
this.history = new ArrayDeque<>(this.maxHistoryEntries);
|
||||
|
||||
for (Type type : Type.values()) {
|
||||
this.logs.put(type, new LogEntry(type, defaultSeverity(type)));
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean tryConsumeExecutionBudget(int estimatedCost, long now, String sourceLabel, int sourceId, String reason) {
|
||||
rollWindow(now);
|
||||
|
||||
int normalizedCost = Math.max(0, estimatedCost);
|
||||
if ((this.usageCurrentWindow + normalizedCost) > this.usageLimitPerWindow) {
|
||||
record(Type.EXECUTION_CAP, now,
|
||||
buildExecutionCapReason(normalizedCost, reason),
|
||||
sourceLabel,
|
||||
sourceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.usageCurrentWindow += normalizedCost;
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized boolean tryScheduleDelayedEvent(long now, String sourceLabel, int sourceId, String reason) {
|
||||
rollWindow(now);
|
||||
|
||||
if ((this.delayedEventsPending + 1) > this.delayedEventsLimit) {
|
||||
record(Type.DELAYED_EVENTS_CAP, now,
|
||||
buildDelayedCapReason(reason),
|
||||
sourceLabel,
|
||||
sourceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.delayedEventsPending++;
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized void completeDelayedEvent() {
|
||||
if (this.delayedEventsPending > 0) {
|
||||
this.delayedEventsPending--;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void recordExecution(long elapsedMs, long now, String sourceLabel, int sourceId, String reason) {
|
||||
rollWindow(now);
|
||||
|
||||
int normalizedElapsed = (int) Math.max(0L, elapsedMs);
|
||||
|
||||
this.totalExecutionMsCurrentWindow += normalizedElapsed;
|
||||
this.executionSamplesCurrentWindow++;
|
||||
this.averageExecutionMs = (int) Math.round(this.totalExecutionMsCurrentWindow / (double) this.executionSamplesCurrentWindow);
|
||||
|
||||
if (normalizedElapsed >= this.peakExecutionMs) {
|
||||
this.peakExecutionMs = normalizedElapsed;
|
||||
this.peakExecutionSourceLabel = sanitizeSourceLabel(sourceLabel);
|
||||
this.peakExecutionSourceId = Math.max(0, sourceId);
|
||||
this.peakExecutionReason = sanitizeReason(reason);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void recordKilled(long now, String reason, String sourceLabel, int sourceId) {
|
||||
rollWindow(now);
|
||||
record(Type.KILLED, now, reason, sourceLabel, sourceId);
|
||||
}
|
||||
|
||||
public synchronized void recordRecursionTimeout(long now, String reason, String sourceLabel, int sourceId) {
|
||||
rollWindow(now);
|
||||
record(Type.RECURSION_TIMEOUT, now, reason, sourceLabel, sourceId);
|
||||
}
|
||||
|
||||
public synchronized void clearLogs() {
|
||||
for (Type type : Type.values()) {
|
||||
LogEntry entry = this.logs.get(type);
|
||||
|
||||
if (entry == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
entry.count = 0;
|
||||
entry.firstOccurredAtMs = 0L;
|
||||
entry.lastOccurredAtMs = 0L;
|
||||
entry.latestReason = "";
|
||||
entry.latestSourceLabel = "";
|
||||
entry.latestSourceId = 0;
|
||||
}
|
||||
|
||||
this.history.clear();
|
||||
}
|
||||
|
||||
public synchronized Snapshot snapshot(int recursionDepthCurrent, int recursionDepthLimit, long killedUntilMs, long now) {
|
||||
rollWindow(now);
|
||||
|
||||
List<LogEntry> logEntries = new ArrayList<>(Type.values().length);
|
||||
List<HistoryEntry> historyEntries = new ArrayList<>(this.history.size());
|
||||
|
||||
for (Type type : Type.values()) {
|
||||
LogEntry source = this.logs.get(type);
|
||||
LogEntry copy = new LogEntry(source.getType(), source.getSeverity());
|
||||
|
||||
copy.count = source.getCount();
|
||||
copy.firstOccurredAtMs = source.getFirstOccurredAtMs();
|
||||
copy.lastOccurredAtMs = source.getLastOccurredAtMs();
|
||||
copy.latestReason = source.getLatestReason();
|
||||
copy.latestSourceLabel = source.getLatestSourceLabel();
|
||||
copy.latestSourceId = source.getLatestSourceId();
|
||||
|
||||
logEntries.add(copy);
|
||||
}
|
||||
|
||||
historyEntries.addAll(this.history);
|
||||
|
||||
int killedRemainingSeconds = 0;
|
||||
|
||||
if (killedUntilMs > now) {
|
||||
killedRemainingSeconds = (int) Math.max(0L, Math.ceil((killedUntilMs - now) / 1000D));
|
||||
}
|
||||
|
||||
return new Snapshot(
|
||||
this.usageCurrentWindow,
|
||||
this.usageLimitPerWindow,
|
||||
this.heavy,
|
||||
this.delayedEventsPending,
|
||||
this.delayedEventsLimit,
|
||||
this.averageExecutionMs,
|
||||
this.peakExecutionMs,
|
||||
recursionDepthCurrent,
|
||||
recursionDepthLimit,
|
||||
killedRemainingSeconds,
|
||||
this.usageWindowMs,
|
||||
this.overloadAverageThresholdMs,
|
||||
this.overloadPeakThresholdMs,
|
||||
this.heavyUsageThresholdPercent,
|
||||
this.heavyConsecutiveWindowsThreshold,
|
||||
this.overloadConsecutiveWindowsThreshold,
|
||||
this.heavyDelayedThresholdPercent,
|
||||
logEntries,
|
||||
historyEntries
|
||||
);
|
||||
}
|
||||
|
||||
private void rollWindow(long now) {
|
||||
if (this.windowStartedAt <= 0L) {
|
||||
this.windowStartedAt = now;
|
||||
return;
|
||||
}
|
||||
|
||||
while ((now - this.windowStartedAt) >= this.usageWindowMs) {
|
||||
evaluateWindow(this.windowStartedAt + this.usageWindowMs);
|
||||
this.windowStartedAt += this.usageWindowMs;
|
||||
this.usageCurrentWindow = 0;
|
||||
this.totalExecutionMsCurrentWindow = 0L;
|
||||
this.executionSamplesCurrentWindow = 0;
|
||||
this.averageExecutionMs = 0;
|
||||
this.peakExecutionMs = 0;
|
||||
this.peakExecutionSourceLabel = null;
|
||||
this.peakExecutionSourceId = 0;
|
||||
this.peakExecutionReason = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void evaluateWindow(long now) {
|
||||
int usagePercent = (int) Math.round((this.usageCurrentWindow * 100D) / this.usageLimitPerWindow);
|
||||
int delayedPercent = (int) Math.round((this.delayedEventsPending * 100D) / this.delayedEventsLimit);
|
||||
boolean overloadWindow = (this.executionSamplesCurrentWindow > 0)
|
||||
&& ((this.averageExecutionMs >= this.overloadAverageThresholdMs) || (this.peakExecutionMs >= this.overloadPeakThresholdMs));
|
||||
boolean heavyWindow = (usagePercent >= this.heavyUsageThresholdPercent)
|
||||
|| (delayedPercent >= this.heavyDelayedThresholdPercent)
|
||||
|| overloadWindow;
|
||||
|
||||
if (overloadWindow) {
|
||||
this.consecutiveOverloadWindows++;
|
||||
|
||||
if (this.consecutiveOverloadWindows >= this.overloadConsecutiveWindowsThreshold) {
|
||||
record(Type.EXECUTOR_OVERLOAD, now,
|
||||
buildExecutorOverloadReason(),
|
||||
this.peakExecutionSourceLabel,
|
||||
this.peakExecutionSourceId);
|
||||
}
|
||||
} else {
|
||||
this.consecutiveOverloadWindows = 0;
|
||||
}
|
||||
|
||||
if (heavyWindow) {
|
||||
this.consecutiveHeavyWindows++;
|
||||
|
||||
if (!this.heavy && (this.consecutiveHeavyWindows >= this.heavyConsecutiveWindowsThreshold)) {
|
||||
this.heavy = true;
|
||||
record(Type.MARKED_AS_HEAVY, now,
|
||||
buildHeavyReason(usagePercent, delayedPercent, overloadWindow),
|
||||
overloadWindow ? this.peakExecutionSourceLabel : null,
|
||||
overloadWindow ? this.peakExecutionSourceId : 0);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.consecutiveHeavyWindows = 0;
|
||||
this.heavy = false;
|
||||
}
|
||||
|
||||
private void record(Type type, long now, String reason, String sourceLabel, int sourceId) {
|
||||
LogEntry entry = this.logs.get(type);
|
||||
if (entry != null) {
|
||||
entry.record(now, reason, sourceLabel, sourceId);
|
||||
this.history.addFirst(new HistoryEntry(type, entry.getSeverity(), now, reason, sourceLabel, sourceId));
|
||||
|
||||
while (this.history.size() > this.maxHistoryEntries) {
|
||||
this.history.removeLast();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String buildExecutionCapReason(int normalizedCost, String reason) {
|
||||
return joinReason(
|
||||
reason,
|
||||
String.format("Estimated stack cost %d would exceed usage budget %d/%d in %dms window",
|
||||
normalizedCost,
|
||||
this.usageCurrentWindow,
|
||||
this.usageLimitPerWindow,
|
||||
this.usageWindowMs)
|
||||
);
|
||||
}
|
||||
|
||||
private String buildDelayedCapReason(String reason) {
|
||||
return joinReason(
|
||||
reason,
|
||||
String.format("Pending delayed events would exceed queue %d/%d",
|
||||
this.delayedEventsPending,
|
||||
this.delayedEventsLimit)
|
||||
);
|
||||
}
|
||||
|
||||
private String buildExecutorOverloadReason() {
|
||||
return joinReason(
|
||||
this.peakExecutionReason,
|
||||
String.format("Average execution %dms (limit %dms), peak %dms (limit %dms) across %d execution(s) in %dms window",
|
||||
this.averageExecutionMs,
|
||||
this.overloadAverageThresholdMs,
|
||||
this.peakExecutionMs,
|
||||
this.overloadPeakThresholdMs,
|
||||
this.executionSamplesCurrentWindow,
|
||||
this.usageWindowMs)
|
||||
);
|
||||
}
|
||||
|
||||
private String buildHeavyReason(int usagePercent, int delayedPercent, boolean overloadWindow) {
|
||||
return String.format(
|
||||
"Room stayed above heavy thresholds for %d consecutive window(s): usage %d%%/%d%%, delayed %d%%/%d%%, overload %s",
|
||||
this.consecutiveHeavyWindows,
|
||||
usagePercent,
|
||||
this.heavyUsageThresholdPercent,
|
||||
delayedPercent,
|
||||
this.heavyDelayedThresholdPercent,
|
||||
overloadWindow ? "yes" : "no"
|
||||
);
|
||||
}
|
||||
|
||||
private static String joinReason(String primary, String fallback) {
|
||||
String cleanPrimary = sanitizeReason(primary);
|
||||
String cleanFallback = sanitizeReason(fallback);
|
||||
|
||||
if (cleanPrimary.isEmpty()) return cleanFallback;
|
||||
if (cleanFallback.isEmpty()) return cleanPrimary;
|
||||
if (cleanPrimary.equals(cleanFallback)) return cleanPrimary;
|
||||
|
||||
return cleanPrimary + ". " + cleanFallback;
|
||||
}
|
||||
|
||||
private static String sanitizeReason(String reason) {
|
||||
return (reason == null) ? "" : reason.trim();
|
||||
}
|
||||
|
||||
private static String sanitizeSourceLabel(String sourceLabel) {
|
||||
return (sourceLabel == null) ? "" : sourceLabel.trim();
|
||||
}
|
||||
|
||||
private Severity defaultSeverity(Type type) {
|
||||
return (type == Type.MARKED_AS_HEAVY) ? Severity.WARNING : Severity.ERROR;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,9 @@ import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
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.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||
@@ -233,21 +235,50 @@ public final class WiredSourceUtil {
|
||||
|
||||
int furniLimit = Integer.MAX_VALUE;
|
||||
int userLimit = Integer.MAX_VALUE;
|
||||
List<WiredExtraFilterFurniByVariable> furniVariableFilters = new ArrayList<>();
|
||||
List<WiredExtraFilterUsersByVariable> userVariableFilters = new ArrayList<>();
|
||||
|
||||
for (InteractionWiredExtra extra : extras) {
|
||||
if (extra instanceof WiredExtraFilterFurni) {
|
||||
furniLimit = Math.min(furniLimit, ((WiredExtraFilterFurni) extra).getAmount());
|
||||
} else if (extra instanceof WiredExtraFilterUser) {
|
||||
userLimit = Math.min(userLimit, ((WiredExtraFilterUser) extra).getAmount());
|
||||
} else if (extra instanceof WiredExtraFilterFurniByVariable) {
|
||||
furniVariableFilters.add((WiredExtraFilterFurniByVariable) extra);
|
||||
} else if (extra instanceof WiredExtraFilterUsersByVariable) {
|
||||
userVariableFilters.add((WiredExtraFilterUsersByVariable) extra);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectorCtx.targets().isItemsModifiedBySelector() && furniLimit != Integer.MAX_VALUE) {
|
||||
selectorCtx.targets().setItems(limitIterable(selectorCtx.targets().items(), furniLimit));
|
||||
furniVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId()));
|
||||
userVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId()));
|
||||
|
||||
if (selectorCtx.targets().isItemsModifiedBySelector()) {
|
||||
Iterable<HabboItem> filteredItems = selectorCtx.targets().items();
|
||||
|
||||
for (WiredExtraFilterFurniByVariable extra : furniVariableFilters) {
|
||||
filteredItems = extra.filterItems(room, selectorCtx, filteredItems);
|
||||
}
|
||||
|
||||
if (furniLimit != Integer.MAX_VALUE) {
|
||||
filteredItems = limitIterable(filteredItems, furniLimit);
|
||||
}
|
||||
|
||||
selectorCtx.targets().setItems(filteredItems);
|
||||
}
|
||||
|
||||
if (selectorCtx.targets().isUsersModifiedBySelector() && userLimit != Integer.MAX_VALUE) {
|
||||
selectorCtx.targets().setUsers(limitIterable(selectorCtx.targets().users(), userLimit));
|
||||
if (selectorCtx.targets().isUsersModifiedBySelector()) {
|
||||
Iterable<RoomUnit> filteredUsers = selectorCtx.targets().users();
|
||||
|
||||
for (WiredExtraFilterUsersByVariable extra : userVariableFilters) {
|
||||
filteredUsers = extra.filterUsers(room, selectorCtx, filteredUsers);
|
||||
}
|
||||
|
||||
if (userLimit != Integer.MAX_VALUE) {
|
||||
filteredUsers = limitIterable(filteredUsers, userLimit);
|
||||
}
|
||||
|
||||
selectorCtx.targets().setUsers(filteredUsers);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
package com.eu.habbo.habbohotel.wired.core;
|
||||
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraTextInputVariable;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.triggers.WiredTriggerHabboSaysKeyword;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.wired.api.WiredStack;
|
||||
import gnu.trove.set.hash.THashSet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class WiredTextInputCaptureSupport {
|
||||
private static final int MATCH_CONTAINS = 0;
|
||||
private static final int MATCH_EXACT = 1;
|
||||
private static final int MATCH_ALL_WORDS = 2;
|
||||
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("#([^#]+)#");
|
||||
|
||||
private WiredTextInputCaptureSupport() {
|
||||
}
|
||||
|
||||
public static CaptureResult resolve(WiredStack stack, WiredEvent event) {
|
||||
if (stack == null || event == null || !(stack.triggerItem() instanceof WiredTriggerHabboSaysKeyword)) {
|
||||
return CaptureResult.noMatch();
|
||||
}
|
||||
|
||||
WiredTriggerHabboSaysKeyword trigger = (WiredTriggerHabboSaysKeyword) stack.triggerItem();
|
||||
Room room = event.getRoom();
|
||||
RoomUnit actor = event.getActor().orElse(null);
|
||||
String text = event.getText().orElse(null);
|
||||
|
||||
if (room == null || actor == null || text == null) {
|
||||
return CaptureResult.noMatch();
|
||||
}
|
||||
|
||||
List<WiredExtraTextInputVariable> capturers = getCapturers(room, trigger);
|
||||
if (capturers.isEmpty()) {
|
||||
return trigger.matches(stack.triggerItem(), event) ? CaptureResult.matched(new LinkedHashMap<>()) : CaptureResult.noMatch();
|
||||
}
|
||||
|
||||
if (trigger.isOwnerOnly()) {
|
||||
Habbo habbo = room.getHabbo(actor);
|
||||
if (habbo == null || room.getOwnerId() != habbo.getHabboInfo().getId()) {
|
||||
return CaptureResult.noMatch();
|
||||
}
|
||||
}
|
||||
|
||||
LinkedHashMap<String, WiredExtraTextInputVariable> capturersByName = new LinkedHashMap<>();
|
||||
for (WiredExtraTextInputVariable capturer : capturers) {
|
||||
if (capturer == null || capturer.getCapturerName() == null || capturer.getCapturerName().trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
capturersByName.put(capturer.getCapturerName().trim().toLowerCase(), capturer);
|
||||
}
|
||||
|
||||
if (capturersByName.isEmpty()) {
|
||||
return trigger.matches(stack.triggerItem(), event) ? CaptureResult.matched(new LinkedHashMap<>()) : CaptureResult.noMatch();
|
||||
}
|
||||
|
||||
MatchResult matchResult = matchTemplate(trigger, text, capturersByName);
|
||||
if (!matchResult.matches) {
|
||||
return CaptureResult.noMatch();
|
||||
}
|
||||
|
||||
LinkedHashMap<Integer, Integer> capturedValues = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, String> capture : matchResult.captures.entrySet()) {
|
||||
WiredExtraTextInputVariable capturer = capturersByName.get(capture.getKey());
|
||||
if (capturer == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Integer resolvedValue = capturer.resolveCapturedValue(room, capture.getValue());
|
||||
if (resolvedValue == null) {
|
||||
return CaptureResult.noMatch();
|
||||
}
|
||||
|
||||
capturedValues.put(capturer.getVariableItemId(), resolvedValue);
|
||||
}
|
||||
|
||||
return CaptureResult.matched(capturedValues);
|
||||
}
|
||||
|
||||
private static List<WiredExtraTextInputVariable> getCapturers(Room room, WiredTriggerHabboSaysKeyword trigger) {
|
||||
List<WiredExtraTextInputVariable> capturers = new ArrayList<>();
|
||||
|
||||
if (room == null || trigger == null || room.getRoomSpecialTypes() == null) {
|
||||
return capturers;
|
||||
}
|
||||
|
||||
THashSet<InteractionWiredExtra> extras = room.getRoomSpecialTypes().getExtras(trigger.getX(), trigger.getY());
|
||||
if (extras == null || extras.isEmpty()) {
|
||||
return capturers;
|
||||
}
|
||||
|
||||
for (InteractionWiredExtra extra : WiredExecutionOrderUtil.sort(extras)) {
|
||||
if (extra instanceof WiredExtraTextInputVariable) {
|
||||
capturers.add((WiredExtraTextInputVariable) extra);
|
||||
}
|
||||
}
|
||||
|
||||
return capturers;
|
||||
}
|
||||
|
||||
private static MatchResult matchTemplate(WiredTriggerHabboSaysKeyword trigger, String rawText, Map<String, WiredExtraTextInputVariable> capturersByName) {
|
||||
String text = rawText != null ? rawText.trim() : "";
|
||||
String template = trigger.getKey() != null ? trigger.getKey().trim() : "";
|
||||
|
||||
if (trigger.getMatchMode() == MATCH_ALL_WORDS && template.isEmpty()) {
|
||||
if (capturersByName.size() != 1 || text.isEmpty()) {
|
||||
return MatchResult.noMatch();
|
||||
}
|
||||
|
||||
String placeholderName = capturersByName.keySet().iterator().next();
|
||||
LinkedHashMap<String, String> captures = new LinkedHashMap<>();
|
||||
captures.put(placeholderName, text);
|
||||
return MatchResult.matched(captures);
|
||||
}
|
||||
|
||||
TemplatePattern pattern = buildPattern(template);
|
||||
if (pattern == null) {
|
||||
return MatchResult.noMatch();
|
||||
}
|
||||
|
||||
Matcher matcher = pattern.pattern.matcher(text);
|
||||
boolean matches = (trigger.getMatchMode() == MATCH_CONTAINS) ? matcher.find() : matcher.matches();
|
||||
if (!matches) {
|
||||
return MatchResult.noMatch();
|
||||
}
|
||||
|
||||
LinkedHashMap<String, String> captures = new LinkedHashMap<>();
|
||||
for (int index = 0; index < pattern.placeholderNames.size(); index++) {
|
||||
String placeholderName = pattern.placeholderNames.get(index);
|
||||
if (!capturersByName.containsKey(placeholderName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String capturedValue = matcher.group(index + 1);
|
||||
captures.put(placeholderName, capturedValue != null ? capturedValue.trim() : "");
|
||||
}
|
||||
|
||||
return MatchResult.matched(captures);
|
||||
}
|
||||
|
||||
private static TemplatePattern buildPattern(String template) {
|
||||
if (template == null || template.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Matcher matcher = PLACEHOLDER_PATTERN.matcher(template);
|
||||
StringBuilder regex = new StringBuilder();
|
||||
List<String> placeholderNames = new ArrayList<>();
|
||||
int cursor = 0;
|
||||
|
||||
while (matcher.find()) {
|
||||
regex.append(Pattern.quote(template.substring(cursor, matcher.start())));
|
||||
regex.append("(.+?)");
|
||||
|
||||
String placeholderName = matcher.group(1) != null ? matcher.group(1).trim().toLowerCase() : "";
|
||||
placeholderNames.add(placeholderName);
|
||||
cursor = matcher.end();
|
||||
}
|
||||
|
||||
regex.append(Pattern.quote(template.substring(cursor)));
|
||||
|
||||
if (placeholderNames.isEmpty()) {
|
||||
regex = new StringBuilder(Pattern.quote(template));
|
||||
}
|
||||
|
||||
return new TemplatePattern(Pattern.compile(regex.toString(), Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE), placeholderNames);
|
||||
}
|
||||
|
||||
public static void applyToContext(WiredContext ctx, Room room, CaptureResult captureResult) {
|
||||
if (ctx == null || room == null || captureResult == null || !captureResult.matches || captureResult.capturedValues.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.forkContextVariables();
|
||||
|
||||
for (Map.Entry<Integer, Integer> entry : captureResult.capturedValues.entrySet()) {
|
||||
if (entry == null || entry.getKey() == null || entry.getKey() <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!WiredContextVariableSupport.updateVariableValue(ctx, room, entry.getKey(), entry.getValue())) {
|
||||
WiredContextVariableSupport.assignVariable(ctx, room, entry.getKey(), entry.getValue(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final class CaptureResult {
|
||||
private final boolean matches;
|
||||
private final LinkedHashMap<Integer, Integer> capturedValues;
|
||||
|
||||
private CaptureResult(boolean matches, LinkedHashMap<Integer, Integer> capturedValues) {
|
||||
this.matches = matches;
|
||||
this.capturedValues = capturedValues;
|
||||
}
|
||||
|
||||
public boolean matches() {
|
||||
return this.matches;
|
||||
}
|
||||
|
||||
public static CaptureResult matched(LinkedHashMap<Integer, Integer> capturedValues) {
|
||||
return new CaptureResult(true, capturedValues);
|
||||
}
|
||||
|
||||
public static CaptureResult noMatch() {
|
||||
return new CaptureResult(false, new LinkedHashMap<>());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MatchResult {
|
||||
private final boolean matches;
|
||||
private final LinkedHashMap<String, String> captures;
|
||||
|
||||
private MatchResult(boolean matches, LinkedHashMap<String, String> captures) {
|
||||
this.matches = matches;
|
||||
this.captures = captures;
|
||||
}
|
||||
|
||||
private static MatchResult matched(LinkedHashMap<String, String> captures) {
|
||||
return new MatchResult(true, captures);
|
||||
}
|
||||
|
||||
private static MatchResult noMatch() {
|
||||
return new MatchResult(false, new LinkedHashMap<>());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TemplatePattern {
|
||||
private final Pattern pattern;
|
||||
private final List<String> placeholderNames;
|
||||
|
||||
private TemplatePattern(Pattern pattern, List<String> placeholderNames) {
|
||||
this.pattern = pattern;
|
||||
this.placeholderNames = placeholderNames;
|
||||
}
|
||||
}
|
||||
}
|
||||
+331
-4
@@ -1,20 +1,35 @@
|
||||
package com.eu.habbo.habbohotel.wired.core;
|
||||
|
||||
import com.eu.habbo.habbohotel.bots.Bot;
|
||||
import com.eu.habbo.habbohotel.games.Game;
|
||||
import com.eu.habbo.habbohotel.games.GamePlayer;
|
||||
import com.eu.habbo.habbohotel.games.GameTeam;
|
||||
import com.eu.habbo.habbohotel.games.GameTeamColors;
|
||||
import com.eu.habbo.habbohotel.games.battlebanzai.BattleBanzaiGame;
|
||||
import com.eu.habbo.habbohotel.games.freeze.FreezeGame;
|
||||
import com.eu.habbo.habbohotel.games.wired.WiredGame;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraTextOutputFurniName;
|
||||
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.WiredExtraUserVariable;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFurniVariable;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRoomVariable;
|
||||
import com.eu.habbo.habbohotel.pets.Pet;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnitType;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||
import com.eu.habbo.util.HotelDateTimeUtil;
|
||||
import gnu.trove.set.hash.THashSet;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class WiredTextPlaceholderUtil {
|
||||
private WiredTextPlaceholderUtil() {
|
||||
@@ -58,6 +73,17 @@ public final class WiredTextPlaceholderUtil {
|
||||
if (!placeholderToken.isEmpty() && resolvedText.contains(placeholderToken)) {
|
||||
resolvedText = resolvedText.replace(placeholderToken, buildFurniNameReplacement(ctx, furniExtra));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraTextOutputVariable) {
|
||||
WiredExtraTextOutputVariable variableExtra = (WiredExtraTextOutputVariable) extra;
|
||||
String placeholderToken = variableExtra.getPlaceholderToken();
|
||||
|
||||
if (!placeholderToken.isEmpty() && resolvedText.contains(placeholderToken)) {
|
||||
resolvedText = resolvedText.replace(placeholderToken, buildVariableReplacement(ctx, variableExtra));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,12 +101,14 @@ public final class WiredTextPlaceholderUtil {
|
||||
}
|
||||
|
||||
for (InteractionWiredExtra extra : extras) {
|
||||
if (!(extra instanceof WiredExtraTextOutputUsername)) {
|
||||
continue;
|
||||
if (extra instanceof WiredExtraTextOutputUsername) {
|
||||
int userSource = ((WiredExtraTextOutputUsername) extra).getUserSource();
|
||||
if (userSource == WiredSourceUtil.SOURCE_TRIGGER || userSource == WiredSourceUtil.SOURCE_CLICKED_USER) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int userSource = ((WiredExtraTextOutputUsername) extra).getUserSource();
|
||||
if ((userSource == WiredSourceUtil.SOURCE_TRIGGER) || (userSource == WiredSourceUtil.SOURCE_CLICKED_USER)) {
|
||||
if (extra instanceof WiredExtraTextOutputVariable && ((WiredExtraTextOutputVariable) extra).requiresActor()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -164,6 +192,186 @@ public final class WiredTextPlaceholderUtil {
|
||||
return furniNames.get(0);
|
||||
}
|
||||
|
||||
private static String buildVariableReplacement(WiredContext ctx, WiredExtraTextOutputVariable extra) {
|
||||
List<String> values = switch (extra.getTargetType()) {
|
||||
case WiredExtraTextOutputVariable.TARGET_FURNI -> collectFurniVariableValues(ctx, extra);
|
||||
case WiredExtraTextOutputVariable.TARGET_CONTEXT -> collectContextVariableValues(ctx, extra);
|
||||
case WiredExtraTextOutputVariable.TARGET_ROOM -> collectRoomVariableValues(ctx, extra);
|
||||
default -> collectUserVariableValues(ctx, extra);
|
||||
};
|
||||
|
||||
if (values.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (extra.getPlaceholderType() == WiredExtraTextOutputVariable.TYPE_MULTIPLE) {
|
||||
return String.join(extra.getDelimiter(), values);
|
||||
}
|
||||
|
||||
return values.get(0);
|
||||
}
|
||||
|
||||
private static List<String> collectUserVariableValues(WiredContext ctx, WiredExtraTextOutputVariable extra) {
|
||||
Room room = ctx.room();
|
||||
List<RoomUnit> users = WiredSourceUtil.resolveUsers(ctx, extra.getUserSource());
|
||||
if (room == null || users.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
LinkedHashSet<Integer> seenUserIds = new LinkedHashSet<>();
|
||||
List<String> values = new ArrayList<>();
|
||||
|
||||
for (RoomUnit roomUnit : users) {
|
||||
if (roomUnit == null || !seenUserIds.add(roomUnit.getId())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String value = resolveUserVariableValue(room, roomUnit, extra);
|
||||
if (value != null && !value.isEmpty()) {
|
||||
values.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
private static List<String> collectFurniVariableValues(WiredContext ctx, WiredExtraTextOutputVariable extra) {
|
||||
Room room = ctx.room();
|
||||
if (room == null) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
extra.refresh(room);
|
||||
|
||||
List<HabboItem> items = (extra.getFurniSource() == WiredSourceUtil.SOURCE_SELECTOR)
|
||||
? WiredSourceUtil.resolveSelectorItems(ctx, true)
|
||||
: WiredSourceUtil.resolveItems(ctx, extra.getFurniSource(), extra.getItems());
|
||||
|
||||
if (items.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
LinkedHashSet<Integer> seenItemIds = new LinkedHashSet<>();
|
||||
List<String> values = new ArrayList<>();
|
||||
|
||||
for (HabboItem item : items) {
|
||||
if (item == null || !seenItemIds.add(item.getId())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String value = resolveFurniVariableValue(room, item, extra);
|
||||
if (value != null && !value.isEmpty()) {
|
||||
values.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
private static List<String> collectRoomVariableValues(WiredContext ctx, WiredExtraTextOutputVariable extra) {
|
||||
Room room = ctx.room();
|
||||
if (room == null) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
String value = resolveRoomVariableValue(room, extra);
|
||||
return (value == null || value.isEmpty()) ? List.of() : List.of(value);
|
||||
}
|
||||
|
||||
private static List<String> collectContextVariableValues(WiredContext ctx, WiredExtraTextOutputVariable extra) {
|
||||
if (ctx == null) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
String value = resolveContextVariableValue(ctx, extra);
|
||||
return (value == null || value.isEmpty()) ? List.of() : List.of(value);
|
||||
}
|
||||
|
||||
private static String resolveUserVariableValue(Room room, RoomUnit roomUnit, WiredExtraTextOutputVariable extra) {
|
||||
if (room == null || roomUnit == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (WiredExtraTextOutputVariable.isInternalVariableToken(extra.getVariableToken())) {
|
||||
Integer value = readUserInternalValue(room, roomUnit, WiredExtraTextOutputVariable.getInternalVariableKey(extra.getVariableToken()));
|
||||
return value != null ? String.valueOf(value) : null;
|
||||
}
|
||||
|
||||
Habbo habbo = room.getHabbo(roomUnit);
|
||||
if (habbo == null || !room.getUserVariableManager().hasVariable(habbo.getHabboInfo().getId(), extra.getVariableItemId())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Integer value = room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), extra.getVariableItemId());
|
||||
if (extra.getDisplayType(room) == WiredExtraTextOutputVariable.DISPLAY_TEXTUAL) {
|
||||
return WiredVariableTextConnectorSupport.toText(room, extra.getVariableItemId(), value);
|
||||
}
|
||||
|
||||
return value != null ? String.valueOf(value) : null;
|
||||
}
|
||||
|
||||
private static String resolveFurniVariableValue(Room room, HabboItem item, WiredExtraTextOutputVariable extra) {
|
||||
if (room == null || item == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (WiredExtraTextOutputVariable.isInternalVariableToken(extra.getVariableToken())) {
|
||||
Integer value = readFurniInternalValue(room, item, WiredExtraTextOutputVariable.getInternalVariableKey(extra.getVariableToken()));
|
||||
return value != null ? String.valueOf(value) : null;
|
||||
}
|
||||
|
||||
if (!room.getFurniVariableManager().hasVariable(item.getId(), extra.getVariableItemId())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Integer value = room.getFurniVariableManager().getCurrentValue(item.getId(), extra.getVariableItemId());
|
||||
if (extra.getDisplayType(room) == WiredExtraTextOutputVariable.DISPLAY_TEXTUAL) {
|
||||
return WiredVariableTextConnectorSupport.toText(room, extra.getVariableItemId(), value);
|
||||
}
|
||||
|
||||
return value != null ? String.valueOf(value) : null;
|
||||
}
|
||||
|
||||
private static String resolveRoomVariableValue(Room room, WiredExtraTextOutputVariable extra) {
|
||||
if (room == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (WiredExtraTextOutputVariable.isInternalVariableToken(extra.getVariableToken())) {
|
||||
Integer value = readRoomInternalValue(room, WiredExtraTextOutputVariable.getInternalVariableKey(extra.getVariableToken()));
|
||||
return value != null ? String.valueOf(value) : null;
|
||||
}
|
||||
|
||||
Integer value = room.getRoomVariableManager().getCurrentValue(extra.getVariableItemId());
|
||||
if (extra.getDisplayType(room) == WiredExtraTextOutputVariable.DISPLAY_TEXTUAL) {
|
||||
return WiredVariableTextConnectorSupport.toText(room, extra.getVariableItemId(), value);
|
||||
}
|
||||
|
||||
return value != null ? String.valueOf(value) : null;
|
||||
}
|
||||
|
||||
private static String resolveContextVariableValue(WiredContext ctx, WiredExtraTextOutputVariable extra) {
|
||||
if (ctx == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (WiredExtraTextOutputVariable.isInternalVariableToken(extra.getVariableToken())) {
|
||||
Integer value = WiredInternalVariableSupport.readContextValue(ctx, WiredExtraTextOutputVariable.getInternalVariableKey(extra.getVariableToken()));
|
||||
return value != null ? String.valueOf(value) : null;
|
||||
}
|
||||
|
||||
if (!WiredContextVariableSupport.hasVariable(ctx, extra.getVariableItemId())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Integer value = WiredContextVariableSupport.getCurrentValue(ctx, extra.getVariableItemId());
|
||||
if (extra.getDisplayType(ctx.room()) == WiredExtraTextOutputVariable.DISPLAY_TEXTUAL) {
|
||||
return WiredVariableTextConnectorSupport.toText(ctx.room(), extra.getVariableItemId(), value);
|
||||
}
|
||||
|
||||
return value != null ? String.valueOf(value) : null;
|
||||
}
|
||||
|
||||
private static String getRoomUnitName(Room room, RoomUnit roomUnit) {
|
||||
if (room == null || roomUnit == null) {
|
||||
return "";
|
||||
@@ -186,4 +394,123 @@ public final class WiredTextPlaceholderUtil {
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
private static Integer readUserInternalValue(Room room, RoomUnit roomUnit, String key) {
|
||||
return WiredInternalVariableSupport.readUserValue(room, roomUnit, key);
|
||||
}
|
||||
|
||||
private static Integer readFurniInternalValue(Room room, HabboItem item, String key) {
|
||||
return WiredInternalVariableSupport.readFurniValue(room, item, key);
|
||||
}
|
||||
|
||||
private static Integer readRoomInternalValue(Room room, String key) {
|
||||
return WiredInternalVariableSupport.readRoomValue(room, key);
|
||||
}
|
||||
|
||||
private static Integer getUserTeamScore(Room room, Habbo habbo) {
|
||||
if (room == null || habbo == null || habbo.getHabboInfo().getGamePlayer() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Game game = resolveTeamGame(room, habbo);
|
||||
GamePlayer gamePlayer = habbo.getHabboInfo().getGamePlayer();
|
||||
|
||||
if (game == null || gamePlayer.getTeamColor() == null) {
|
||||
return gamePlayer.getScore();
|
||||
}
|
||||
|
||||
GameTeam team = game.getTeam(gamePlayer.getTeamColor());
|
||||
return (team != null) ? team.getTotalScore() : gamePlayer.getScore();
|
||||
}
|
||||
|
||||
private static Integer getTeamColorId(int effectId) {
|
||||
TeamEffectData data = getTeamEffectData(effectId);
|
||||
return data == null ? null : data.colorId;
|
||||
}
|
||||
|
||||
private static Integer getTeamTypeId(int effectId) {
|
||||
TeamEffectData data = getTeamEffectData(effectId);
|
||||
return data == null ? null : data.typeId;
|
||||
}
|
||||
|
||||
private static int getTeamMetric(Room room, GameTeamColors color, boolean score) {
|
||||
Game game = resolveTeamGame(room, null);
|
||||
if (game == null || color == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
GameTeam team = game.getTeam(color);
|
||||
if (team == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return score ? team.getTotalScore() : team.getMembers().size();
|
||||
}
|
||||
|
||||
private static Game resolveTeamGame(Room room, Habbo habbo) {
|
||||
if (room == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (habbo != null && habbo.getHabboInfo() != null && habbo.getHabboInfo().getCurrentGame() != null) {
|
||||
Game game = room.getGame(habbo.getHabboInfo().getCurrentGame());
|
||||
if (game != null) {
|
||||
return game;
|
||||
}
|
||||
}
|
||||
|
||||
Game wiredGame = room.getGame(WiredGame.class);
|
||||
if (wiredGame != null) {
|
||||
return wiredGame;
|
||||
}
|
||||
|
||||
Game freezeGame = room.getGame(FreezeGame.class);
|
||||
if (freezeGame != null) {
|
||||
return freezeGame;
|
||||
}
|
||||
|
||||
return room.getGame(BattleBanzaiGame.class);
|
||||
}
|
||||
|
||||
private static TeamEffectData getTeamEffectData(int effectValue) {
|
||||
if (effectValue <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (effectValue >= 223 && effectValue <= 226) {
|
||||
return new TeamEffectData(effectValue - 222, 0);
|
||||
}
|
||||
|
||||
if (effectValue >= 33 && effectValue <= 36) {
|
||||
return new TeamEffectData(effectValue - 32, 1);
|
||||
}
|
||||
|
||||
if (effectValue >= 40 && effectValue <= 43) {
|
||||
return new TeamEffectData(effectValue - 39, 2);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Integer parseInteger(String value) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return Integer.parseInt(value.trim());
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TeamEffectData {
|
||||
final int colorId;
|
||||
final int typeId;
|
||||
|
||||
TeamEffectData(int colorId, int typeId) {
|
||||
this.colorId = colorId;
|
||||
this.typeId = typeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+35
-4
@@ -4,7 +4,9 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger;
|
||||
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.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||
@@ -172,21 +174,50 @@ public final class WiredTriggerSourceUtil {
|
||||
|
||||
int furniLimit = Integer.MAX_VALUE;
|
||||
int userLimit = Integer.MAX_VALUE;
|
||||
List<WiredExtraFilterFurniByVariable> furniVariableFilters = new ArrayList<>();
|
||||
List<WiredExtraFilterUsersByVariable> userVariableFilters = new ArrayList<>();
|
||||
|
||||
for (InteractionWiredExtra extra : extras) {
|
||||
if (extra instanceof WiredExtraFilterFurni) {
|
||||
furniLimit = Math.min(furniLimit, ((WiredExtraFilterFurni) extra).getAmount());
|
||||
} else if (extra instanceof WiredExtraFilterUser) {
|
||||
userLimit = Math.min(userLimit, ((WiredExtraFilterUser) extra).getAmount());
|
||||
} else if (extra instanceof WiredExtraFilterFurniByVariable) {
|
||||
furniVariableFilters.add((WiredExtraFilterFurniByVariable) extra);
|
||||
} else if (extra instanceof WiredExtraFilterUsersByVariable) {
|
||||
userVariableFilters.add((WiredExtraFilterUsersByVariable) extra);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectorCtx.targets().isItemsModifiedBySelector() && furniLimit != Integer.MAX_VALUE) {
|
||||
selectorCtx.targets().setItems(limitIterable(selectorCtx.targets().items(), furniLimit));
|
||||
furniVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId()));
|
||||
userVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId()));
|
||||
|
||||
if (selectorCtx.targets().isItemsModifiedBySelector()) {
|
||||
Iterable<HabboItem> filteredItems = selectorCtx.targets().items();
|
||||
|
||||
for (WiredExtraFilterFurniByVariable extra : furniVariableFilters) {
|
||||
filteredItems = extra.filterItems(room, selectorCtx, filteredItems);
|
||||
}
|
||||
|
||||
if (furniLimit != Integer.MAX_VALUE) {
|
||||
filteredItems = limitIterable(filteredItems, furniLimit);
|
||||
}
|
||||
|
||||
selectorCtx.targets().setItems(filteredItems);
|
||||
}
|
||||
|
||||
if (selectorCtx.targets().isUsersModifiedBySelector() && userLimit != Integer.MAX_VALUE) {
|
||||
selectorCtx.targets().setUsers(limitIterable(selectorCtx.targets().users(), userLimit));
|
||||
if (selectorCtx.targets().isUsersModifiedBySelector()) {
|
||||
Iterable<RoomUnit> filteredUsers = selectorCtx.targets().users();
|
||||
|
||||
for (WiredExtraFilterUsersByVariable extra : userVariableFilters) {
|
||||
filteredUsers = extra.filterUsers(room, selectorCtx, filteredUsers);
|
||||
}
|
||||
|
||||
if (userLimit != Integer.MAX_VALUE) {
|
||||
filteredUsers = limitIterable(filteredUsers, userLimit);
|
||||
}
|
||||
|
||||
selectorCtx.targets().setUsers(filteredUsers);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+564
@@ -0,0 +1,564 @@
|
||||
package com.eu.habbo.habbohotel.wired.core;
|
||||
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFurniVariable;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRoomVariable;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraUserVariable;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableLevelUpSystem;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableReference;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
|
||||
import gnu.trove.set.hash.THashSet;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class WiredVariableLevelSystemSupport {
|
||||
public static final int TARGET_USER = 0;
|
||||
public static final int TARGET_FURNI = 1;
|
||||
public static final int TARGET_ROOM = 3;
|
||||
|
||||
private static final int SYNTHETIC_USER_OFFSET = 700_000_000;
|
||||
private static final int SYNTHETIC_FURNI_OFFSET = 800_000_000;
|
||||
private static final int SYNTHETIC_ROOM_OFFSET = 900_000_000;
|
||||
private static final int SYNTHETIC_STRIDE = 16;
|
||||
|
||||
private WiredVariableLevelSystemSupport() {
|
||||
}
|
||||
|
||||
public static WiredExtraVariableLevelUpSystem getLevelSystem(Room room, InteractionWiredExtra definition) {
|
||||
if (room == null || definition == null || room.getRoomSpecialTypes() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
THashSet<InteractionWiredExtra> extras = room.getRoomSpecialTypes().getExtras(definition.getX(), definition.getY());
|
||||
if (extras == null || extras.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (InteractionWiredExtra extra : WiredExecutionOrderUtil.sort(extras)) {
|
||||
if (extra instanceof WiredExtraVariableLevelUpSystem) {
|
||||
return (WiredExtraVariableLevelUpSystem) extra;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<WiredVariableDefinitionInfo> getDerivedDefinitions(Room room, int targetType, InteractionWiredExtra definitionExtra, WiredVariableDefinitionInfo baseDefinition) {
|
||||
if (room == null || definitionExtra == null || baseDefinition == null || !baseDefinition.hasValue()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
WiredExtraVariableLevelUpSystem levelSystem = getLevelSystem(room, definitionExtra);
|
||||
if (levelSystem == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<WiredVariableDefinitionInfo> result = new ArrayList<>();
|
||||
|
||||
for (int subvariableType : levelSystem.getSelectedSubvariables()) {
|
||||
result.add(new WiredVariableDefinitionInfo(
|
||||
createSyntheticItemId(targetType, baseDefinition.getItemId(), subvariableType),
|
||||
baseDefinition.getName() + "." + getSubvariableKey(subvariableType),
|
||||
true,
|
||||
baseDefinition.getAvailability(),
|
||||
false,
|
||||
true
|
||||
));
|
||||
}
|
||||
|
||||
result.sort(Comparator.comparing(WiredVariableDefinitionInfo::getName, String.CASE_INSENSITIVE_ORDER).thenComparingInt(WiredVariableDefinitionInfo::getItemId));
|
||||
return result;
|
||||
}
|
||||
|
||||
public static WiredVariableDefinitionInfo getDerivedDefinitionInfo(Room room, int targetType, int syntheticItemId) {
|
||||
DerivedDefinition derived = resolveDerivedDefinition(room, targetType, syntheticItemId);
|
||||
|
||||
if (derived == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new WiredVariableDefinitionInfo(
|
||||
derived.syntheticItemId,
|
||||
derived.variableName,
|
||||
true,
|
||||
derived.baseDefinition.getAvailability(),
|
||||
false,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public static DerivedDefinition resolveDerivedDefinition(Room room, int targetType, int syntheticItemId) {
|
||||
DecodedSyntheticId decoded = decodeSyntheticId(syntheticItemId);
|
||||
if (decoded == null || decoded.targetType != targetType || room == null || room.getRoomSpecialTypes() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
InteractionWiredExtra baseExtra = room.getRoomSpecialTypes().getExtra(decoded.baseDefinitionItemId);
|
||||
if (!matchesTarget(baseExtra, targetType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
WiredVariableDefinitionInfo baseDefinition = createBaseDefinitionInfo(room, baseExtra, targetType);
|
||||
if (baseDefinition == null || !baseDefinition.hasValue()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
WiredExtraVariableLevelUpSystem levelSystem = getLevelSystem(room, baseExtra);
|
||||
if (levelSystem == null || !levelSystem.hasSubvariable(decoded.subvariableType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DerivedDefinition(
|
||||
syntheticItemId,
|
||||
decoded.baseDefinitionItemId,
|
||||
decoded.subvariableType,
|
||||
baseDefinition.getName() + "." + getSubvariableKey(decoded.subvariableType),
|
||||
baseDefinition,
|
||||
levelSystem
|
||||
);
|
||||
}
|
||||
|
||||
public static Integer getDerivedValue(WiredExtraVariableLevelUpSystem levelSystem, int subvariableType, Integer baseValue) {
|
||||
if (levelSystem == null || baseValue == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LevelProgress progress = calculateProgress(levelSystem, baseValue);
|
||||
return switch (subvariableType) {
|
||||
case WiredExtraVariableLevelUpSystem.SUB_CURRENT_LEVEL -> progress.currentLevel;
|
||||
case WiredExtraVariableLevelUpSystem.SUB_CURRENT_XP -> progress.currentXp;
|
||||
case WiredExtraVariableLevelUpSystem.SUB_LEVEL_PROGRESS -> progress.progressXp;
|
||||
case WiredExtraVariableLevelUpSystem.SUB_LEVEL_PROGRESS_PERCENT -> progress.progressPercent;
|
||||
case WiredExtraVariableLevelUpSystem.SUB_TOTAL_XP_REQUIRED -> progress.totalXpRequired;
|
||||
case WiredExtraVariableLevelUpSystem.SUB_XP_REMAINING -> progress.xpRemaining;
|
||||
case WiredExtraVariableLevelUpSystem.SUB_IS_AT_MAX -> progress.isAtMax ? 1 : 0;
|
||||
case WiredExtraVariableLevelUpSystem.SUB_MAX_LEVEL -> progress.maxLevel;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
public static List<LevelEntry> buildPreviewEntries(WiredExtraVariableLevelUpSystem levelSystem) {
|
||||
if (levelSystem == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return buildThresholdEntries(levelSystem);
|
||||
}
|
||||
|
||||
private static boolean matchesTarget(InteractionWiredExtra extra, int targetType) {
|
||||
if (extra == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return switch (targetType) {
|
||||
case TARGET_FURNI -> extra instanceof WiredExtraFurniVariable;
|
||||
case TARGET_ROOM -> (extra instanceof WiredExtraRoomVariable)
|
||||
|| (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isRoomReference());
|
||||
default -> (extra instanceof WiredExtraUserVariable)
|
||||
|| (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isUserReference());
|
||||
};
|
||||
}
|
||||
|
||||
private static WiredVariableDefinitionInfo createBaseDefinitionInfo(Room room, InteractionWiredExtra extra, int targetType) {
|
||||
if (room == null || extra == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (targetType == TARGET_FURNI && extra instanceof WiredExtraFurniVariable) {
|
||||
WiredExtraFurniVariable definition = (WiredExtraFurniVariable) extra;
|
||||
return new WiredVariableDefinitionInfo(
|
||||
definition.getId(),
|
||||
definition.getVariableName(),
|
||||
definition.hasValue(),
|
||||
definition.getAvailability(),
|
||||
WiredVariableTextConnectorSupport.isTextConnected(room, definition),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (targetType == TARGET_USER) {
|
||||
if (extra instanceof WiredExtraUserVariable) {
|
||||
WiredExtraUserVariable definition = (WiredExtraUserVariable) extra;
|
||||
return new WiredVariableDefinitionInfo(
|
||||
definition.getId(),
|
||||
definition.getVariableName(),
|
||||
definition.hasValue(),
|
||||
definition.getAvailability(),
|
||||
WiredVariableTextConnectorSupport.isTextConnected(room, definition),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isUserReference()) {
|
||||
WiredExtraVariableReference reference = (WiredExtraVariableReference) extra;
|
||||
return new WiredVariableDefinitionInfo(reference.getId(), reference.getVariableName(), reference.hasValue(), reference.getAvailability(), false, reference.isReadOnly());
|
||||
}
|
||||
}
|
||||
|
||||
if (targetType == TARGET_ROOM) {
|
||||
if (extra instanceof WiredExtraRoomVariable) {
|
||||
WiredExtraRoomVariable definition = (WiredExtraRoomVariable) extra;
|
||||
return new WiredVariableDefinitionInfo(
|
||||
definition.getId(),
|
||||
definition.getVariableName(),
|
||||
definition.hasValue(),
|
||||
definition.getAvailability(),
|
||||
WiredVariableTextConnectorSupport.isTextConnected(room, definition),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isRoomReference()) {
|
||||
WiredExtraVariableReference reference = (WiredExtraVariableReference) extra;
|
||||
return new WiredVariableDefinitionInfo(reference.getId(), reference.getVariableName(), reference.hasValue(), reference.getAvailability(), false, reference.isReadOnly());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int createSyntheticItemId(int targetType, int baseDefinitionItemId, int subvariableType) {
|
||||
int offset = switch (targetType) {
|
||||
case TARGET_FURNI -> SYNTHETIC_FURNI_OFFSET;
|
||||
case TARGET_ROOM -> SYNTHETIC_ROOM_OFFSET;
|
||||
default -> SYNTHETIC_USER_OFFSET;
|
||||
};
|
||||
|
||||
return offset + (baseDefinitionItemId * SYNTHETIC_STRIDE) + (subvariableType + 1);
|
||||
}
|
||||
|
||||
private static DecodedSyntheticId decodeSyntheticId(int syntheticItemId) {
|
||||
if (syntheticItemId >= SYNTHETIC_ROOM_OFFSET) {
|
||||
return decodeSyntheticId(syntheticItemId, TARGET_ROOM, SYNTHETIC_ROOM_OFFSET);
|
||||
}
|
||||
|
||||
if (syntheticItemId >= SYNTHETIC_FURNI_OFFSET) {
|
||||
return decodeSyntheticId(syntheticItemId, TARGET_FURNI, SYNTHETIC_FURNI_OFFSET);
|
||||
}
|
||||
|
||||
if (syntheticItemId >= SYNTHETIC_USER_OFFSET) {
|
||||
return decodeSyntheticId(syntheticItemId, TARGET_USER, SYNTHETIC_USER_OFFSET);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static DecodedSyntheticId decodeSyntheticId(int syntheticItemId, int targetType, int offset) {
|
||||
int localValue = syntheticItemId - offset;
|
||||
if (localValue < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int encodedSubvariable = localValue % SYNTHETIC_STRIDE;
|
||||
int baseDefinitionItemId = localValue / SYNTHETIC_STRIDE;
|
||||
int subvariableType = encodedSubvariable - 1;
|
||||
|
||||
if (baseDefinitionItemId <= 0 || subvariableType < 0 || subvariableType >= WiredExtraVariableLevelUpSystem.SUBVARIABLE_COUNT) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DecodedSyntheticId(targetType, baseDefinitionItemId, subvariableType);
|
||||
}
|
||||
|
||||
private static String getSubvariableKey(int subvariableType) {
|
||||
return switch (subvariableType) {
|
||||
case WiredExtraVariableLevelUpSystem.SUB_CURRENT_LEVEL -> "current_level";
|
||||
case WiredExtraVariableLevelUpSystem.SUB_CURRENT_XP -> "current_xp";
|
||||
case WiredExtraVariableLevelUpSystem.SUB_LEVEL_PROGRESS -> "level_progress";
|
||||
case WiredExtraVariableLevelUpSystem.SUB_LEVEL_PROGRESS_PERCENT -> "level_progress_percent";
|
||||
case WiredExtraVariableLevelUpSystem.SUB_TOTAL_XP_REQUIRED -> "total_xp_required";
|
||||
case WiredExtraVariableLevelUpSystem.SUB_XP_REMAINING -> "xp_remaining";
|
||||
case WiredExtraVariableLevelUpSystem.SUB_IS_AT_MAX -> "is_at_max";
|
||||
case WiredExtraVariableLevelUpSystem.SUB_MAX_LEVEL -> "max_level";
|
||||
default -> "value";
|
||||
};
|
||||
}
|
||||
|
||||
private static LevelProgress calculateProgress(WiredExtraVariableLevelUpSystem levelSystem, int rawBaseValue) {
|
||||
int currentXp = Math.max(0, rawBaseValue);
|
||||
List<LevelEntry> entries = buildThresholdEntries(levelSystem);
|
||||
|
||||
if (entries.isEmpty()) {
|
||||
entries = new ArrayList<>();
|
||||
entries.add(new LevelEntry(1, 0));
|
||||
}
|
||||
|
||||
int maxLevel = entries.get(entries.size() - 1).level;
|
||||
int currentLevel = 1;
|
||||
int currentThreshold = 0;
|
||||
int nextThreshold = 0;
|
||||
|
||||
for (int index = 0; index < entries.size(); index++) {
|
||||
LevelEntry entry = entries.get(index);
|
||||
|
||||
if (currentXp >= entry.requiredXp) {
|
||||
currentLevel = entry.level;
|
||||
currentThreshold = entry.requiredXp;
|
||||
nextThreshold = (index + 1 < entries.size()) ? entries.get(index + 1).requiredXp : entry.requiredXp;
|
||||
continue;
|
||||
}
|
||||
|
||||
nextThreshold = entry.requiredXp;
|
||||
break;
|
||||
}
|
||||
|
||||
boolean isAtMax = currentLevel >= maxLevel;
|
||||
|
||||
if (isAtMax) {
|
||||
nextThreshold = currentThreshold;
|
||||
}
|
||||
|
||||
int progressXp = Math.max(0, currentXp - currentThreshold);
|
||||
int progressPercent;
|
||||
|
||||
if (isAtMax) {
|
||||
progressPercent = 100;
|
||||
} else {
|
||||
int delta = Math.max(0, nextThreshold - currentThreshold);
|
||||
progressPercent = (delta <= 0) ? 100 : Math.max(0, Math.min(100, (int) Math.floor((progressXp * 100D) / delta)));
|
||||
}
|
||||
|
||||
int totalXpRequired = isAtMax ? currentThreshold : nextThreshold;
|
||||
int xpRemaining = Math.max(0, totalXpRequired - currentXp);
|
||||
|
||||
return new LevelProgress(currentLevel, currentXp, progressXp, progressPercent, totalXpRequired, xpRemaining, isAtMax, maxLevel);
|
||||
}
|
||||
|
||||
private static List<LevelEntry> buildThresholdEntries(WiredExtraVariableLevelUpSystem levelSystem) {
|
||||
return switch (levelSystem.getMode()) {
|
||||
case WiredExtraVariableLevelUpSystem.MODE_EXPONENTIAL -> buildExponentialEntries(levelSystem);
|
||||
case WiredExtraVariableLevelUpSystem.MODE_MANUAL -> buildManualEntries(levelSystem);
|
||||
default -> buildLinearEntries(levelSystem);
|
||||
};
|
||||
}
|
||||
|
||||
private static List<LevelEntry> buildLinearEntries(WiredExtraVariableLevelUpSystem levelSystem) {
|
||||
List<LevelEntry> entries = new ArrayList<>();
|
||||
int maxLevel = Math.max(1, levelSystem.getMaxLevel());
|
||||
int stepSize = Math.max(0, levelSystem.getStepSize());
|
||||
|
||||
for (int level = 1; level <= maxLevel; level++) {
|
||||
entries.add(new LevelEntry(level, clamp((long) (level - 1) * stepSize)));
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static List<LevelEntry> buildExponentialEntries(WiredExtraVariableLevelUpSystem levelSystem) {
|
||||
List<LevelEntry> entries = new ArrayList<>();
|
||||
int maxLevel = Math.max(1, levelSystem.getMaxLevel());
|
||||
int currentIncrement = Math.max(0, levelSystem.getFirstLevelXp());
|
||||
int factor = Math.max(0, levelSystem.getIncreaseFactor());
|
||||
long threshold = 0L;
|
||||
|
||||
entries.add(new LevelEntry(1, 0));
|
||||
|
||||
for (int level = 2; level <= maxLevel; level++) {
|
||||
threshold += currentIncrement;
|
||||
entries.add(new LevelEntry(level, clamp(threshold)));
|
||||
currentIncrement = clamp(Math.round(currentIncrement * (100D + factor) / 100D));
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static List<LevelEntry> buildManualEntries(WiredExtraVariableLevelUpSystem levelSystem) {
|
||||
LinkedHashMap<Integer, Integer> anchors = parseAnchors(levelSystem.getInterpolationText());
|
||||
if (!anchors.containsKey(1)) {
|
||||
anchors.put(1, 0);
|
||||
}
|
||||
|
||||
List<Map.Entry<Integer, Integer>> sortedAnchors = new ArrayList<>(anchors.entrySet());
|
||||
sortedAnchors.sort(Map.Entry.comparingByKey());
|
||||
|
||||
if (sortedAnchors.isEmpty()) {
|
||||
return Collections.singletonList(new LevelEntry(1, 0));
|
||||
}
|
||||
|
||||
LinkedHashMap<Integer, Integer> result = new LinkedHashMap<>();
|
||||
|
||||
for (int index = 0; index < sortedAnchors.size(); index++) {
|
||||
Map.Entry<Integer, Integer> current = sortedAnchors.get(index);
|
||||
int currentLevel = Math.max(1, current.getKey());
|
||||
int currentXp = Math.max(0, current.getValue());
|
||||
|
||||
result.put(currentLevel, currentXp);
|
||||
|
||||
if (index + 1 >= sortedAnchors.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Map.Entry<Integer, Integer> next = sortedAnchors.get(index + 1);
|
||||
int nextLevel = Math.max(currentLevel, next.getKey());
|
||||
int nextXp = Math.max(0, next.getValue());
|
||||
|
||||
if (nextLevel <= currentLevel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int level = currentLevel + 1; level < nextLevel; level++) {
|
||||
double ratio = (double) (level - currentLevel) / (double) (nextLevel - currentLevel);
|
||||
int interpolatedXp = clamp(Math.round(currentXp + ((nextXp - currentXp) * ratio)));
|
||||
result.put(level, interpolatedXp);
|
||||
}
|
||||
}
|
||||
|
||||
List<LevelEntry> entries = new ArrayList<>();
|
||||
for (Map.Entry<Integer, Integer> entry : result.entrySet()) {
|
||||
entries.add(new LevelEntry(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
|
||||
entries.sort(Comparator.comparingInt(levelEntry -> levelEntry.level));
|
||||
return entries;
|
||||
}
|
||||
|
||||
private static LinkedHashMap<Integer, Integer> parseAnchors(String interpolationText) {
|
||||
LinkedHashMap<Integer, Integer> result = new LinkedHashMap<>();
|
||||
if (interpolationText == null || interpolationText.trim().isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (String rawLine : interpolationText.split("\n")) {
|
||||
if (rawLine == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String line = rawLine.trim();
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int separatorIndex = line.indexOf('=');
|
||||
if (separatorIndex < 0) {
|
||||
separatorIndex = line.indexOf(',');
|
||||
}
|
||||
|
||||
if (separatorIndex <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Integer level = parseInteger(line.substring(0, separatorIndex));
|
||||
Integer xp = parseInteger(line.substring(separatorIndex + 1));
|
||||
|
||||
if (level == null || xp == null || level <= 0 || xp < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.put(level, xp);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Integer parseInteger(String value) {
|
||||
try {
|
||||
return (value == null || value.trim().isEmpty()) ? null : Integer.parseInt(value.trim());
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static int clamp(long value) {
|
||||
if (value > Integer.MAX_VALUE) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
if (value < Integer.MIN_VALUE) {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
return (int) value;
|
||||
}
|
||||
|
||||
public static class DerivedDefinition {
|
||||
private final int syntheticItemId;
|
||||
private final int baseDefinitionItemId;
|
||||
private final int subvariableType;
|
||||
private final String variableName;
|
||||
private final WiredVariableDefinitionInfo baseDefinition;
|
||||
private final WiredExtraVariableLevelUpSystem levelSystem;
|
||||
|
||||
public DerivedDefinition(int syntheticItemId, int baseDefinitionItemId, int subvariableType, String variableName, WiredVariableDefinitionInfo baseDefinition, WiredExtraVariableLevelUpSystem levelSystem) {
|
||||
this.syntheticItemId = syntheticItemId;
|
||||
this.baseDefinitionItemId = baseDefinitionItemId;
|
||||
this.subvariableType = subvariableType;
|
||||
this.variableName = variableName;
|
||||
this.baseDefinition = baseDefinition;
|
||||
this.levelSystem = levelSystem;
|
||||
}
|
||||
|
||||
public int getBaseDefinitionItemId() {
|
||||
return this.baseDefinitionItemId;
|
||||
}
|
||||
|
||||
public int getSubvariableType() {
|
||||
return this.subvariableType;
|
||||
}
|
||||
|
||||
public WiredVariableDefinitionInfo getBaseDefinition() {
|
||||
return this.baseDefinition;
|
||||
}
|
||||
|
||||
public WiredExtraVariableLevelUpSystem getLevelSystem() {
|
||||
return this.levelSystem;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LevelEntry {
|
||||
private final int level;
|
||||
private final int requiredXp;
|
||||
|
||||
public LevelEntry(int level, int requiredXp) {
|
||||
this.level = level;
|
||||
this.requiredXp = requiredXp;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return this.level;
|
||||
}
|
||||
|
||||
public int getRequiredXp() {
|
||||
return this.requiredXp;
|
||||
}
|
||||
}
|
||||
|
||||
private static class LevelProgress {
|
||||
private final int currentLevel;
|
||||
private final int currentXp;
|
||||
private final int progressXp;
|
||||
private final int progressPercent;
|
||||
private final int totalXpRequired;
|
||||
private final int xpRemaining;
|
||||
private final boolean isAtMax;
|
||||
private final int maxLevel;
|
||||
|
||||
private LevelProgress(int currentLevel, int currentXp, int progressXp, int progressPercent, int totalXpRequired, int xpRemaining, boolean isAtMax, int maxLevel) {
|
||||
this.currentLevel = currentLevel;
|
||||
this.currentXp = currentXp;
|
||||
this.progressXp = progressXp;
|
||||
this.progressPercent = progressPercent;
|
||||
this.totalXpRequired = totalXpRequired;
|
||||
this.xpRemaining = xpRemaining;
|
||||
this.isAtMax = isAtMax;
|
||||
this.maxLevel = maxLevel;
|
||||
}
|
||||
}
|
||||
|
||||
private static class DecodedSyntheticId {
|
||||
private final int targetType;
|
||||
private final int baseDefinitionItemId;
|
||||
private final int subvariableType;
|
||||
|
||||
private DecodedSyntheticId(int targetType, int baseDefinitionItemId, int subvariableType) {
|
||||
this.targetType = targetType;
|
||||
this.baseDefinitionItemId = baseDefinitionItemId;
|
||||
this.subvariableType = subvariableType;
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.eu.habbo.habbohotel.wired.core;
|
||||
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableTextConnector;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import gnu.trove.set.hash.THashSet;
|
||||
|
||||
public final class WiredVariableTextConnectorSupport {
|
||||
private WiredVariableTextConnectorSupport() {
|
||||
}
|
||||
|
||||
public static boolean isTextConnected(Room room, InteractionWiredExtra definition) {
|
||||
return getConnector(room, definition) != null;
|
||||
}
|
||||
|
||||
public static boolean isTextConnected(Room room, int definitionItemId) {
|
||||
return getConnector(room, definitionItemId) != null;
|
||||
}
|
||||
|
||||
public static WiredExtraVariableTextConnector getConnector(Room room, int definitionItemId) {
|
||||
if (room == null || room.getRoomSpecialTypes() == null || definitionItemId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
InteractionWiredExtra extra = room.getRoomSpecialTypes().getExtra(definitionItemId);
|
||||
return getConnector(room, extra);
|
||||
}
|
||||
|
||||
public static WiredExtraVariableTextConnector getConnector(Room room, InteractionWiredExtra definition) {
|
||||
if (room == null || definition == null || room.getRoomSpecialTypes() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
THashSet<InteractionWiredExtra> extras = room.getRoomSpecialTypes().getExtras(definition.getX(), definition.getY());
|
||||
if (extras == null || extras.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (InteractionWiredExtra extra : WiredExecutionOrderUtil.sort(extras)) {
|
||||
if (extra instanceof WiredExtraVariableTextConnector) {
|
||||
return (WiredExtraVariableTextConnector) extra;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String toText(Room room, int definitionItemId, Integer value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
WiredExtraVariableTextConnector connector = getConnector(room, definitionItemId);
|
||||
return connector != null ? connector.resolveText(value) : String.valueOf(value);
|
||||
}
|
||||
|
||||
public static Integer toValue(Room room, int definitionItemId, String text) {
|
||||
WiredExtraVariableTextConnector connector = getConnector(room, definitionItemId);
|
||||
return connector != null ? connector.resolveValue(text) : null;
|
||||
}
|
||||
}
|
||||
@@ -167,9 +167,15 @@ public final class WiredEvents {
|
||||
* @return the event
|
||||
*/
|
||||
public static WiredEvent userSays(Room room, RoomUnit user, String message) {
|
||||
return userSays(room, user, message, -1, -1);
|
||||
}
|
||||
|
||||
public static WiredEvent userSays(Room room, RoomUnit user, String message, int chatType, int chatStyle) {
|
||||
return WiredEvent.builder(WiredEvent.Type.USER_SAYS, room)
|
||||
.actor(user)
|
||||
.text(message)
|
||||
.chatType(chatType)
|
||||
.chatStyle(chatStyle)
|
||||
.tile(user.getCurrentLocation())
|
||||
.build();
|
||||
}
|
||||
@@ -192,6 +198,42 @@ public final class WiredEvents {
|
||||
.build();
|
||||
}
|
||||
|
||||
public static WiredEvent userVariableChanged(Room room, RoomUnit user, int definitionItemId, boolean created, boolean deleted, WiredEvent.VariableChangeKind changeKind) {
|
||||
return WiredEvent.builder(WiredEvent.Type.VARIABLE_CHANGED, room)
|
||||
.actor(user)
|
||||
.tile((user != null) ? user.getCurrentLocation() : null)
|
||||
.variableTargetType(0)
|
||||
.variableDefinitionItemId(definitionItemId)
|
||||
.variableCreated(created)
|
||||
.variableDeleted(deleted)
|
||||
.variableChangeKind(changeKind)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static WiredEvent furniVariableChanged(Room room, HabboItem item, int definitionItemId, boolean created, boolean deleted, WiredEvent.VariableChangeKind changeKind) {
|
||||
RoomTile tile = (item != null) ? room.getLayout().getTile(item.getX(), item.getY()) : null;
|
||||
|
||||
return WiredEvent.builder(WiredEvent.Type.VARIABLE_CHANGED, room)
|
||||
.sourceItem(item)
|
||||
.tile(tile)
|
||||
.variableTargetType(1)
|
||||
.variableDefinitionItemId(definitionItemId)
|
||||
.variableCreated(created)
|
||||
.variableDeleted(deleted)
|
||||
.variableChangeKind(changeKind)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static WiredEvent roomVariableChanged(Room room, int definitionItemId, WiredEvent.VariableChangeKind changeKind) {
|
||||
return WiredEvent.builder(WiredEvent.Type.VARIABLE_CHANGED, room)
|
||||
.variableTargetType(3)
|
||||
.variableDefinitionItemId(definitionItemId)
|
||||
.variableCreated(false)
|
||||
.variableDeleted(false)
|
||||
.variableChangeKind(changeKind)
|
||||
.build();
|
||||
}
|
||||
|
||||
// ========== Timer Events ==========
|
||||
|
||||
/**
|
||||
|
||||
@@ -67,6 +67,12 @@ import com.eu.habbo.messages.incoming.users.*;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredApplySetConditionsEvent;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredConditionSaveDataEvent;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredEffectSaveDataEvent;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredMonitorRequestEvent;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredRoomSettingsRequestEvent;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredRoomSettingsSaveEvent;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredUserVariableManageEvent;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredUserVariableUpdateEvent;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredUserVariablesRequestEvent;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredTriggerSaveDataEvent;
|
||||
import com.eu.habbo.plugin.EventHandler;
|
||||
import com.eu.habbo.plugin.events.emulator.EmulatorConfigUpdatedEvent;
|
||||
@@ -615,6 +621,12 @@ public class PacketManager {
|
||||
this.registerHandler(Incoming.WiredEffectSaveDataEvent, WiredEffectSaveDataEvent.class);
|
||||
this.registerHandler(Incoming.WiredConditionSaveDataEvent, WiredConditionSaveDataEvent.class);
|
||||
this.registerHandler(Incoming.WiredApplySetConditionsEvent, WiredApplySetConditionsEvent.class);
|
||||
this.registerHandler(Incoming.WiredMonitorRequestEvent, WiredMonitorRequestEvent.class);
|
||||
this.registerHandler(Incoming.WiredRoomSettingsRequestEvent, WiredRoomSettingsRequestEvent.class);
|
||||
this.registerHandler(Incoming.WiredRoomSettingsSaveEvent, WiredRoomSettingsSaveEvent.class);
|
||||
this.registerHandler(Incoming.WiredUserVariablesRequestEvent, WiredUserVariablesRequestEvent.class);
|
||||
this.registerHandler(Incoming.WiredUserVariableUpdateEvent, WiredUserVariableUpdateEvent.class);
|
||||
this.registerHandler(Incoming.WiredUserVariableManageEvent, WiredUserVariableManageEvent.class);
|
||||
}
|
||||
|
||||
void registerUnknown() throws Exception {
|
||||
|
||||
@@ -409,6 +409,12 @@ public class Incoming {
|
||||
// CUSTOM
|
||||
public static final int UpdateFurniturePositionEvent = 10019;
|
||||
public static final int ClickUserEvent = 10020;
|
||||
public static final int WiredMonitorRequestEvent = 10021;
|
||||
public static final int WiredRoomSettingsRequestEvent = 10022;
|
||||
public static final int WiredRoomSettingsSaveEvent = 10023;
|
||||
public static final int WiredUserVariablesRequestEvent = 10024;
|
||||
public static final int WiredUserVariableUpdateEvent = 10025;
|
||||
public static final int WiredUserVariableManageEvent = 10026;
|
||||
public static final int RequestInventoryPetDelete = 10030;
|
||||
public static final int RequestInventoryBadgeDelete = 10031;
|
||||
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ public class WiredApplySetConditionsEvent extends MessageHandler {
|
||||
if (room != null) {
|
||||
|
||||
// Executing Habbo should be able to edit wireds
|
||||
if (room.hasRights(this.client.getHabbo()) || room.isOwner(this.client.getHabbo())) {
|
||||
if (room.canModifyWired(this.client.getHabbo())) {
|
||||
|
||||
List<HabboItem> wireds = new ArrayList<>();
|
||||
wireds.addAll(room.getRoomSpecialTypes().getConditions());
|
||||
|
||||
+1
-2
@@ -4,7 +4,6 @@ import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWired;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredCondition;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
@@ -23,7 +22,7 @@ public class WiredConditionSaveDataEvent extends MessageHandler {
|
||||
Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom();
|
||||
|
||||
if (room != null) {
|
||||
if (room.hasRights(this.client.getHabbo()) || room.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || this.client.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER) || this.client.getHabbo().hasPermission(Permission.ACC_MOVEROTATE)) {
|
||||
if (room.canModifyWired(this.client.getHabbo())) {
|
||||
InteractionWiredCondition condition = room.getRoomSpecialTypes().getCondition(itemId);
|
||||
|
||||
if (condition != null) {
|
||||
|
||||
+1
-2
@@ -5,7 +5,6 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWired;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
@@ -20,7 +19,7 @@ public class WiredEffectSaveDataEvent extends MessageHandler {
|
||||
Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom();
|
||||
|
||||
if (room != null) {
|
||||
if (room.hasRights(this.client.getHabbo()) || room.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || this.client.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER) || this.client.getHabbo().hasPermission(Permission.ACC_MOVEROTATE)) {
|
||||
if (room.canModifyWired(this.client.getHabbo())) {
|
||||
InteractionWiredEffect effect = room.getRoomSpecialTypes().getEffect(itemId);
|
||||
InteractionWiredExtra extra = room.getRoomSpecialTypes().getExtra(itemId);
|
||||
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.eu.habbo.messages.incoming.wired;
|
||||
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.wired.WiredMonitorDataComposer;
|
||||
|
||||
public class WiredMonitorRequestEvent extends MessageHandler {
|
||||
private static final int ACTION_FETCH = 0;
|
||||
private static final int ACTION_CLEAR_LOGS = 1;
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
Room room = currentRoom();
|
||||
|
||||
if (room == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!room.canInspectWired(this.client.getHabbo())) {
|
||||
return;
|
||||
}
|
||||
|
||||
int action = ACTION_FETCH;
|
||||
|
||||
if (this.packet.bytesAvailable() >= 4) {
|
||||
action = this.packet.readInt();
|
||||
}
|
||||
|
||||
if ((action == ACTION_CLEAR_LOGS) && room.canModifyWired(this.client.getHabbo())) {
|
||||
WiredManager.clearDiagnosticsLogs(room.getId());
|
||||
}
|
||||
|
||||
this.client.sendResponse(new WiredMonitorDataComposer(WiredManager.getDiagnosticsSnapshot(room.getId())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 50;
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.eu.habbo.messages.incoming.wired;
|
||||
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.wired.WiredRoomSettingsDataComposer;
|
||||
|
||||
public class WiredRoomSettingsRequestEvent extends MessageHandler {
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
Room room = currentRoom();
|
||||
|
||||
if (room == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.client.sendResponse(new WiredRoomSettingsDataComposer(room, this.client.getHabbo()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 250;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.eu.habbo.messages.incoming.wired;
|
||||
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.wired.WiredRoomSettingsDataComposer;
|
||||
|
||||
public class WiredRoomSettingsSaveEvent extends MessageHandler {
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
Room room = currentRoom();
|
||||
|
||||
if (room == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.packet.bytesAvailable() < 8) {
|
||||
this.client.sendResponse(new WiredRoomSettingsDataComposer(room, this.client.getHabbo()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!room.canManageWiredSettings(this.client.getHabbo())) {
|
||||
this.client.sendResponse(new WiredRoomSettingsDataComposer(room, this.client.getHabbo()));
|
||||
return;
|
||||
}
|
||||
|
||||
int inspectMask = this.packet.readInt();
|
||||
int modifyMask = this.packet.readInt();
|
||||
|
||||
room.saveWiredSettings(inspectMask, modifyMask);
|
||||
|
||||
this.client.sendResponse(new WiredRoomSettingsDataComposer(room, this.client.getHabbo()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 250;
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -4,7 +4,6 @@ import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWired;
|
||||
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
@@ -19,7 +18,7 @@ public class WiredTriggerSaveDataEvent extends MessageHandler {
|
||||
Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom();
|
||||
|
||||
if (room != null) {
|
||||
if (room.hasRights(this.client.getHabbo()) || room.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || this.client.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER) || this.client.getHabbo().hasPermission(Permission.ACC_MOVEROTATE)) {
|
||||
if (room.canModifyWired(this.client.getHabbo())) {
|
||||
InteractionWiredTrigger trigger = room.getRoomSpecialTypes().getTrigger(itemId);
|
||||
|
||||
if (trigger != null) {
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package com.eu.habbo.messages.incoming.wired;
|
||||
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
|
||||
public class WiredUserVariableManageEvent extends MessageHandler {
|
||||
private static final int ACTION_ASSIGN = 0;
|
||||
private static final int ACTION_REMOVE = 1;
|
||||
private static final int TARGET_ROOM = 3;
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
Room room = currentRoom();
|
||||
|
||||
if (room == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!room.canModifyWired(this.client.getHabbo())) {
|
||||
room.getRoomVariableManager().sendSnapshot(this.client.getHabbo());
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.packet.bytesAvailable() < 20) {
|
||||
room.getRoomVariableManager().sendSnapshot(this.client.getHabbo());
|
||||
return;
|
||||
}
|
||||
|
||||
int action = this.packet.readInt();
|
||||
int targetType = this.packet.readInt();
|
||||
int targetId = this.packet.readInt();
|
||||
int definitionItemId = this.packet.readInt();
|
||||
int value = this.packet.readInt();
|
||||
|
||||
switch (targetType) {
|
||||
case com.eu.habbo.habbohotel.items.interactions.wired.effects.WiredEffectGiveVariable.TARGET_FURNI:
|
||||
if (action == ACTION_REMOVE) {
|
||||
room.getFurniVariableManager().removeVariable(targetId, definitionItemId);
|
||||
} else {
|
||||
HabboItem furni = room.getHabboItem(targetId);
|
||||
if (furni != null) {
|
||||
room.getFurniVariableManager().assignVariable(furni, definitionItemId, value, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TARGET_ROOM:
|
||||
if (action == ACTION_REMOVE) {
|
||||
room.getRoomVariableManager().removeVariable(definitionItemId);
|
||||
} else {
|
||||
room.getRoomVariableManager().updateVariableValue(definitionItemId, value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (action == ACTION_REMOVE) {
|
||||
room.getUserVariableManager().removeVariable(targetId, definitionItemId);
|
||||
} else {
|
||||
Habbo habbo = room.getHabbo(targetId);
|
||||
if (habbo != null) {
|
||||
room.getUserVariableManager().assignVariable(habbo, definitionItemId, value, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
room.getRoomVariableManager().sendSnapshot(this.client.getHabbo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 150;
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package com.eu.habbo.messages.incoming.wired;
|
||||
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.items.interactions.wired.effects.WiredEffectGiveVariable;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
|
||||
public class WiredUserVariableUpdateEvent extends MessageHandler {
|
||||
private static final int TARGET_ROOM = 3;
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
Room room = currentRoom();
|
||||
|
||||
if (room == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!room.canModifyWired(this.client.getHabbo())) {
|
||||
room.getUserVariableManager().sendSnapshot(this.client.getHabbo());
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.packet.bytesAvailable() < 16) {
|
||||
room.getUserVariableManager().sendSnapshot(this.client.getHabbo());
|
||||
return;
|
||||
}
|
||||
|
||||
int targetType = this.packet.readInt();
|
||||
int targetId = this.packet.readInt();
|
||||
int definitionItemId = this.packet.readInt();
|
||||
int value = this.packet.readInt();
|
||||
|
||||
if (targetType == WiredEffectGiveVariable.TARGET_FURNI) {
|
||||
room.getFurniVariableManager().updateVariableValue(targetId, definitionItemId, value);
|
||||
room.getFurniVariableManager().sendSnapshot(this.client.getHabbo());
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetType == TARGET_ROOM) {
|
||||
room.getRoomVariableManager().updateVariableValue(definitionItemId, value);
|
||||
room.getRoomVariableManager().sendSnapshot(this.client.getHabbo());
|
||||
return;
|
||||
}
|
||||
|
||||
room.getUserVariableManager().updateVariableValue(targetId, definitionItemId, value);
|
||||
room.getUserVariableManager().sendSnapshot(this.client.getHabbo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 150;
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.eu.habbo.messages.incoming.wired;
|
||||
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
|
||||
public class WiredUserVariablesRequestEvent extends MessageHandler {
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
Room room = currentRoom();
|
||||
|
||||
if (room == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
room.getUserVariableManager().sendSnapshot(this.client.getHabbo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 50;
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,9 @@ public class Outgoing {
|
||||
public final static int EnableNotificationsComposer = 3284; // PRODUCTION-201611291003-338511768
|
||||
public final static int HallOfFameComposer = 3005; // PRODUCTION-201611291003-338511768
|
||||
public final static int WiredSavedComposer = 1155; // PRODUCTION-201611291003-338511768
|
||||
public final static int WiredMonitorDataComposer = 5101; // CUSTOM
|
||||
public final static int WiredRoomSettingsDataComposer = 5102; // CUSTOM
|
||||
public final static int WiredUserVariablesDataComposer = 5103; // CUSTOM
|
||||
public final static int RoomPaintComposer = 2454; // PRODUCTION-201611291003-338511768
|
||||
public final static int MarketplaceConfigComposer = 1823; // PRODUCTION-201611291003-338511768
|
||||
public final static int AddBotComposer = 1352; // PRODUCTION-201611291003-338511768
|
||||
|
||||
+4
-2
@@ -18,9 +18,11 @@ public class ModToolUserInfoComposer extends MessageComposer {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ModToolUserInfoComposer.class);
|
||||
|
||||
private final ResultSet set;
|
||||
private final boolean hideMail;
|
||||
|
||||
public ModToolUserInfoComposer(ResultSet set) {
|
||||
public ModToolUserInfoComposer(ResultSet set, boolean hideMail) {
|
||||
this.set = set;
|
||||
this.hideMail = hideMail;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,7 +60,7 @@ public class ModToolUserInfoComposer extends MessageComposer {
|
||||
this.response.appendString(""); //Last Purchase Timestamp
|
||||
this.response.appendInt(this.set.getInt("user_id")); //Personal Identification #
|
||||
this.response.appendInt(0); // Number of account bans
|
||||
this.response.appendString(this.set.getBoolean("hide_mail") ? "" : this.set.getString("mail"));
|
||||
this.response.appendString(this.hideMail ? "" : this.set.getString("mail"));
|
||||
this.response.appendString("Rank (" + this.set.getInt("rank_id") + "): " + this.set.getString("rank_name")); //user_class_txt
|
||||
|
||||
ModToolSanctions modToolSanctions = Emulator.getGameEnvironment().getModToolSanctions();
|
||||
|
||||
+106
-3
@@ -5,7 +5,9 @@ import com.eu.habbo.messages.outgoing.MessageComposer;
|
||||
import com.eu.habbo.messages.outgoing.Outgoing;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class WiredMovementsComposer extends MessageComposer {
|
||||
public static final int TYPE_USER_MOVE = 0;
|
||||
@@ -24,7 +26,7 @@ public class WiredMovementsComposer extends MessageComposer {
|
||||
private final List<MovementData> movements;
|
||||
|
||||
public WiredMovementsComposer(List<MovementData> movements) {
|
||||
this.movements = movements == null ? new ArrayList<>() : movements;
|
||||
this.movements = normalizeMovements(movements == null ? new ArrayList<>() : movements);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -40,6 +42,107 @@ public class WiredMovementsComposer extends MessageComposer {
|
||||
return this.response;
|
||||
}
|
||||
|
||||
private static List<MovementData> normalizeMovements(List<MovementData> source)
|
||||
{
|
||||
if((source == null) || source.isEmpty()) return new ArrayList<>();
|
||||
|
||||
final LinkedHashMap<String, MovementData> normalized = new LinkedHashMap<>();
|
||||
|
||||
for(final MovementData movement : source)
|
||||
{
|
||||
if(movement == null) continue;
|
||||
|
||||
final String key = movementKey(movement);
|
||||
|
||||
if(key == null)
|
||||
{
|
||||
normalized.put(UUID.randomUUID().toString(), movement);
|
||||
continue;
|
||||
}
|
||||
|
||||
final MovementData existing = normalized.get(key);
|
||||
|
||||
if(existing == null)
|
||||
{
|
||||
normalized.put(key, movement);
|
||||
continue;
|
||||
}
|
||||
|
||||
normalized.put(key, mergeMovement(existing, movement));
|
||||
}
|
||||
|
||||
return new ArrayList<>(normalized.values());
|
||||
}
|
||||
|
||||
private static String movementKey(MovementData movement)
|
||||
{
|
||||
if(movement instanceof FurniMovementData)
|
||||
{
|
||||
return "furni:" + ((FurniMovementData) movement).id;
|
||||
}
|
||||
|
||||
if(movement instanceof UserMovementData)
|
||||
{
|
||||
return "user:" + ((UserMovementData) movement).id;
|
||||
}
|
||||
|
||||
if(movement instanceof UserDirectionData)
|
||||
{
|
||||
return "userdir:" + ((UserDirectionData) movement).id;
|
||||
}
|
||||
|
||||
if(movement instanceof WallItemMovementData)
|
||||
{
|
||||
return "wall:" + ((WallItemMovementData) movement).id;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static MovementData mergeMovement(MovementData previous, MovementData current)
|
||||
{
|
||||
if((previous instanceof FurniMovementData) && (current instanceof FurniMovementData))
|
||||
{
|
||||
final FurniMovementData oldMovement = (FurniMovementData) previous;
|
||||
final FurniMovementData newMovement = (FurniMovementData) current;
|
||||
|
||||
return furniMovement(
|
||||
oldMovement.id,
|
||||
oldMovement.fromX,
|
||||
oldMovement.fromY,
|
||||
newMovement.toX,
|
||||
newMovement.toY,
|
||||
oldMovement.fromZ,
|
||||
newMovement.toZ,
|
||||
newMovement.rotation,
|
||||
newMovement.duration,
|
||||
newMovement.elapsed,
|
||||
newMovement.anchorType,
|
||||
newMovement.anchorId);
|
||||
}
|
||||
|
||||
if((previous instanceof UserMovementData) && (current instanceof UserMovementData))
|
||||
{
|
||||
final UserMovementData oldMovement = (UserMovementData) previous;
|
||||
final UserMovementData newMovement = (UserMovementData) current;
|
||||
|
||||
return new UserMovementData(
|
||||
oldMovement.id,
|
||||
oldMovement.fromX,
|
||||
oldMovement.fromY,
|
||||
newMovement.toX,
|
||||
newMovement.toY,
|
||||
oldMovement.fromZ,
|
||||
newMovement.toZ,
|
||||
newMovement.movementType,
|
||||
newMovement.bodyDirection,
|
||||
newMovement.headDirection,
|
||||
newMovement.duration);
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
public static MovementData furniMovement(int id, int fromX, int fromY, int toX, int toY, double fromZ, double toZ) {
|
||||
return furniMovement(id, fromX, fromY, toX, toY, fromZ, toZ, 0, DEFAULT_DURATION, 0, FURNI_ANCHOR_NONE, 0);
|
||||
}
|
||||
@@ -92,13 +195,13 @@ public class WiredMovementsComposer extends MessageComposer {
|
||||
}
|
||||
|
||||
private static final class UserMovementData extends BaseMovementData {
|
||||
private final int id;
|
||||
private final int fromX;
|
||||
private final int fromY;
|
||||
private final int toX;
|
||||
private final int toY;
|
||||
private final double fromZ;
|
||||
private final double toZ;
|
||||
private final int id;
|
||||
private final int movementType;
|
||||
private final int bodyDirection;
|
||||
private final int headDirection;
|
||||
@@ -136,13 +239,13 @@ public class WiredMovementsComposer extends MessageComposer {
|
||||
}
|
||||
|
||||
private static final class FurniMovementData extends BaseMovementData {
|
||||
private final int id;
|
||||
private final int fromX;
|
||||
private final int fromY;
|
||||
private final int toX;
|
||||
private final int toY;
|
||||
private final double fromZ;
|
||||
private final double toZ;
|
||||
private final int id;
|
||||
private final int rotation;
|
||||
private final int duration;
|
||||
private final int elapsed;
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package com.eu.habbo.messages.outgoing.wired;
|
||||
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredRoomDiagnostics;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.outgoing.MessageComposer;
|
||||
import com.eu.habbo.messages.outgoing.Outgoing;
|
||||
|
||||
public class WiredMonitorDataComposer extends MessageComposer {
|
||||
private final WiredRoomDiagnostics.Snapshot snapshot;
|
||||
|
||||
public WiredMonitorDataComposer(WiredRoomDiagnostics.Snapshot snapshot) {
|
||||
this.snapshot = snapshot;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerMessage composeInternal() {
|
||||
this.response.init(Outgoing.WiredMonitorDataComposer);
|
||||
|
||||
if (this.snapshot == null) {
|
||||
this.response.appendInt(0);
|
||||
this.response.appendInt(0);
|
||||
this.response.appendBoolean(false);
|
||||
this.response.appendInt(0);
|
||||
this.response.appendInt(0);
|
||||
this.response.appendInt(0);
|
||||
this.response.appendInt(0);
|
||||
this.response.appendInt(0);
|
||||
this.response.appendInt(0);
|
||||
this.response.appendInt(0);
|
||||
this.response.appendInt(0);
|
||||
this.response.appendInt(0);
|
||||
this.response.appendInt(0);
|
||||
this.response.appendInt(0);
|
||||
this.response.appendInt(0);
|
||||
this.response.appendInt(0);
|
||||
this.response.appendInt(0);
|
||||
this.response.appendInt(0);
|
||||
this.response.appendInt(0);
|
||||
return this.response;
|
||||
}
|
||||
|
||||
this.response.appendInt(this.snapshot.getUsageCurrentWindow());
|
||||
this.response.appendInt(this.snapshot.getUsageLimitPerWindow());
|
||||
this.response.appendBoolean(this.snapshot.isHeavy());
|
||||
this.response.appendInt(this.snapshot.getDelayedEventsPending());
|
||||
this.response.appendInt(this.snapshot.getDelayedEventsLimit());
|
||||
this.response.appendInt(this.snapshot.getAverageExecutionMs());
|
||||
this.response.appendInt(this.snapshot.getPeakExecutionMs());
|
||||
this.response.appendInt(this.snapshot.getRecursionDepthCurrent());
|
||||
this.response.appendInt(this.snapshot.getRecursionDepthLimit());
|
||||
this.response.appendInt(this.snapshot.getKilledRemainingSeconds());
|
||||
this.response.appendInt(this.snapshot.getUsageWindowMs());
|
||||
this.response.appendInt(this.snapshot.getOverloadAverageThresholdMs());
|
||||
this.response.appendInt(this.snapshot.getOverloadPeakThresholdMs());
|
||||
this.response.appendInt(this.snapshot.getHeavyUsageThresholdPercent());
|
||||
this.response.appendInt(this.snapshot.getHeavyConsecutiveWindowsThreshold());
|
||||
this.response.appendInt(this.snapshot.getOverloadConsecutiveWindowsThreshold());
|
||||
this.response.appendInt(this.snapshot.getHeavyDelayedThresholdPercent());
|
||||
this.response.appendInt(this.snapshot.getLogs().size());
|
||||
|
||||
for (WiredRoomDiagnostics.LogEntry log : this.snapshot.getLogs()) {
|
||||
this.response.appendString(log.getType().name());
|
||||
this.response.appendString(log.getSeverity().name());
|
||||
this.response.appendInt(log.getCount());
|
||||
this.response.appendInt((int) (log.getLastOccurredAtMs() / 1000L));
|
||||
this.response.appendString(log.getLatestReason());
|
||||
this.response.appendString(log.getLatestSourceLabel());
|
||||
this.response.appendInt(log.getLatestSourceId());
|
||||
}
|
||||
|
||||
this.response.appendInt(this.snapshot.getHistory().size());
|
||||
|
||||
for (WiredRoomDiagnostics.HistoryEntry historyEntry : this.snapshot.getHistory()) {
|
||||
this.response.appendString(historyEntry.getType().name());
|
||||
this.response.appendString(historyEntry.getSeverity().name());
|
||||
this.response.appendInt((int) (historyEntry.getOccurredAtMs() / 1000L));
|
||||
this.response.appendString(historyEntry.getReason());
|
||||
this.response.appendString(historyEntry.getSourceLabel());
|
||||
this.response.appendInt(historyEntry.getSourceId());
|
||||
}
|
||||
|
||||
return this.response;
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.eu.habbo.messages.outgoing.wired;
|
||||
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.outgoing.MessageComposer;
|
||||
import com.eu.habbo.messages.outgoing.Outgoing;
|
||||
|
||||
public class WiredRoomSettingsDataComposer extends MessageComposer {
|
||||
private final Room room;
|
||||
private final Habbo habbo;
|
||||
|
||||
public WiredRoomSettingsDataComposer(Room room, Habbo habbo) {
|
||||
this.room = room;
|
||||
this.habbo = habbo;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerMessage composeInternal() {
|
||||
this.response.init(Outgoing.WiredRoomSettingsDataComposer);
|
||||
|
||||
int roomId = (this.room != null) ? this.room.getId() : 0;
|
||||
boolean canInspect = this.room != null && this.room.canInspectWired(this.habbo);
|
||||
boolean canModify = this.room != null && this.room.canModifyWired(this.habbo);
|
||||
boolean canManageSettings = this.room != null && this.room.canManageWiredSettings(this.habbo);
|
||||
int inspectMask = canInspect ? this.room.getWiredInspectMask() : 0;
|
||||
int modifyMask = canInspect ? this.room.getWiredModifyMask() : 0;
|
||||
|
||||
this.response.appendInt(roomId);
|
||||
this.response.appendInt(inspectMask);
|
||||
this.response.appendInt(modifyMask);
|
||||
this.response.appendBoolean(canInspect);
|
||||
this.response.appendBoolean(canModify);
|
||||
this.response.appendBoolean(canManageSettings);
|
||||
|
||||
return this.response;
|
||||
}
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
package com.eu.habbo.messages.outgoing.wired;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomFurniVariableManager;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomVariableManager;
|
||||
import com.eu.habbo.habbohotel.rooms.RoomUserVariableManager;
|
||||
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.outgoing.MessageComposer;
|
||||
import com.eu.habbo.messages.outgoing.Outgoing;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class WiredUserVariablesDataComposer extends MessageComposer {
|
||||
private final RoomUserVariableManager.Snapshot userSnapshot;
|
||||
private final RoomFurniVariableManager.Snapshot furniSnapshot;
|
||||
private final RoomVariableManager.Snapshot roomSnapshot;
|
||||
private final List<WiredVariableDefinitionInfo> contextDefinitions;
|
||||
|
||||
public WiredUserVariablesDataComposer(RoomUserVariableManager.Snapshot userSnapshot, RoomFurniVariableManager.Snapshot furniSnapshot, RoomVariableManager.Snapshot roomSnapshot) {
|
||||
this(userSnapshot, furniSnapshot, roomSnapshot, resolveContextDefinitions(userSnapshot, furniSnapshot, roomSnapshot));
|
||||
}
|
||||
|
||||
public WiredUserVariablesDataComposer(RoomUserVariableManager.Snapshot userSnapshot, RoomFurniVariableManager.Snapshot furniSnapshot, RoomVariableManager.Snapshot roomSnapshot, List<WiredVariableDefinitionInfo> contextDefinitions) {
|
||||
this.userSnapshot = userSnapshot;
|
||||
this.furniSnapshot = furniSnapshot;
|
||||
this.roomSnapshot = roomSnapshot;
|
||||
this.contextDefinitions = (contextDefinitions != null) ? contextDefinitions : Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerMessage composeInternal() {
|
||||
this.response.init(Outgoing.WiredUserVariablesDataComposer);
|
||||
|
||||
int roomId = 0;
|
||||
|
||||
if (this.userSnapshot != null) {
|
||||
roomId = this.userSnapshot.getRoomId();
|
||||
} else if (this.furniSnapshot != null) {
|
||||
roomId = this.furniSnapshot.getRoomId();
|
||||
} else if (this.roomSnapshot != null) {
|
||||
roomId = this.roomSnapshot.getRoomId();
|
||||
}
|
||||
|
||||
this.response.appendInt(roomId);
|
||||
|
||||
this.response.appendInt((this.userSnapshot != null) ? this.userSnapshot.getDefinitions().size() : 0);
|
||||
|
||||
if (this.userSnapshot != null) {
|
||||
for (RoomUserVariableManager.DefinitionEntry definition : this.userSnapshot.getDefinitions()) {
|
||||
this.response.appendInt(definition.getItemId());
|
||||
this.response.appendString(definition.getName());
|
||||
this.response.appendBoolean(definition.hasValue());
|
||||
this.response.appendInt(definition.getAvailability());
|
||||
this.response.appendBoolean(definition.isTextConnected());
|
||||
this.response.appendBoolean(definition.isReadOnly());
|
||||
}
|
||||
}
|
||||
|
||||
this.response.appendInt((this.userSnapshot != null) ? this.userSnapshot.getUsers().size() : 0);
|
||||
|
||||
if (this.userSnapshot != null) {
|
||||
for (RoomUserVariableManager.UserAssignmentsEntry user : this.userSnapshot.getUsers()) {
|
||||
this.response.appendInt(user.getUserId());
|
||||
this.response.appendInt(user.getAssignments().size());
|
||||
|
||||
for (RoomUserVariableManager.AssignmentEntry assignment : user.getAssignments()) {
|
||||
this.response.appendInt(assignment.getVariableItemId());
|
||||
this.response.appendBoolean(assignment.hasValue());
|
||||
this.response.appendInt((assignment.getValue() != null) ? assignment.getValue() : 0);
|
||||
this.response.appendInt(assignment.getCreatedAt());
|
||||
this.response.appendInt(assignment.getUpdatedAt());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.response.appendInt((this.furniSnapshot != null) ? this.furniSnapshot.getDefinitions().size() : 0);
|
||||
|
||||
if (this.furniSnapshot != null) {
|
||||
for (RoomFurniVariableManager.DefinitionEntry definition : this.furniSnapshot.getDefinitions()) {
|
||||
this.response.appendInt(definition.getItemId());
|
||||
this.response.appendString(definition.getName());
|
||||
this.response.appendBoolean(definition.hasValue());
|
||||
this.response.appendInt(definition.getAvailability());
|
||||
this.response.appendBoolean(definition.isTextConnected());
|
||||
this.response.appendBoolean(definition.isReadOnly());
|
||||
}
|
||||
}
|
||||
|
||||
this.response.appendInt((this.furniSnapshot != null) ? this.furniSnapshot.getFurnis().size() : 0);
|
||||
|
||||
if (this.furniSnapshot != null) {
|
||||
for (RoomFurniVariableManager.FurniAssignmentsEntry furni : this.furniSnapshot.getFurnis()) {
|
||||
this.response.appendInt(furni.getFurniId());
|
||||
this.response.appendInt(furni.getAssignments().size());
|
||||
|
||||
for (RoomFurniVariableManager.AssignmentEntry assignment : furni.getAssignments()) {
|
||||
this.response.appendInt(assignment.getVariableItemId());
|
||||
this.response.appendBoolean(assignment.hasValue());
|
||||
this.response.appendInt((assignment.getValue() != null) ? assignment.getValue() : 0);
|
||||
this.response.appendInt(assignment.getCreatedAt());
|
||||
this.response.appendInt(assignment.getUpdatedAt());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.response.appendInt((this.roomSnapshot != null) ? this.roomSnapshot.getDefinitions().size() : 0);
|
||||
|
||||
if (this.roomSnapshot != null) {
|
||||
for (RoomVariableManager.DefinitionEntry definition : this.roomSnapshot.getDefinitions()) {
|
||||
this.response.appendInt(definition.getItemId());
|
||||
this.response.appendString(definition.getName());
|
||||
this.response.appendBoolean(definition.hasValue());
|
||||
this.response.appendInt(definition.getAvailability());
|
||||
this.response.appendBoolean(definition.isTextConnected());
|
||||
this.response.appendBoolean(definition.isReadOnly());
|
||||
}
|
||||
}
|
||||
|
||||
this.response.appendInt((this.roomSnapshot != null) ? this.roomSnapshot.getAssignments().size() : 0);
|
||||
|
||||
if (this.roomSnapshot != null) {
|
||||
for (RoomVariableManager.AssignmentEntry assignment : this.roomSnapshot.getAssignments()) {
|
||||
this.response.appendInt(assignment.getVariableItemId());
|
||||
this.response.appendBoolean(assignment.hasValue());
|
||||
this.response.appendInt((assignment.getValue() != null) ? assignment.getValue() : 0);
|
||||
this.response.appendInt(assignment.getCreatedAt());
|
||||
this.response.appendInt(assignment.getUpdatedAt());
|
||||
}
|
||||
}
|
||||
|
||||
this.response.appendInt(this.contextDefinitions.size());
|
||||
|
||||
for (WiredVariableDefinitionInfo definition : this.contextDefinitions) {
|
||||
if (definition == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.response.appendInt(definition.getItemId());
|
||||
this.response.appendString(definition.getName());
|
||||
this.response.appendBoolean(definition.hasValue());
|
||||
this.response.appendInt(definition.getAvailability());
|
||||
this.response.appendBoolean(definition.isTextConnected());
|
||||
this.response.appendBoolean(definition.isReadOnly());
|
||||
}
|
||||
|
||||
return this.response;
|
||||
}
|
||||
|
||||
private static List<WiredVariableDefinitionInfo> resolveContextDefinitions(RoomUserVariableManager.Snapshot userSnapshot, RoomFurniVariableManager.Snapshot furniSnapshot, RoomVariableManager.Snapshot roomSnapshot) {
|
||||
int roomId = 0;
|
||||
|
||||
if (userSnapshot != null) {
|
||||
roomId = userSnapshot.getRoomId();
|
||||
} else if (furniSnapshot != null) {
|
||||
roomId = furniSnapshot.getRoomId();
|
||||
} else if (roomSnapshot != null) {
|
||||
roomId = roomSnapshot.getRoomId();
|
||||
}
|
||||
|
||||
if (roomId <= 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(roomId);
|
||||
return room != null ? WiredContextVariableSupport.createDefinitionInfos(room) : Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,20 @@ public class PluginManager {
|
||||
WiredEngine.MAX_EVENTS_PER_WINDOW = Emulator.getConfig().getInt("wired.abuse.max.events.per.window", 100);
|
||||
WiredEngine.RATE_LIMIT_WINDOW_MS = Emulator.getConfig().getInt("wired.abuse.rate.limit.window.ms", 10000);
|
||||
WiredEngine.WIRED_BAN_DURATION_MS = Emulator.getConfig().getInt("wired.abuse.ban.duration.ms", 600000);
|
||||
WiredEngine.MONITOR_USAGE_WINDOW_MS = Emulator.getConfig().getInt("wired.monitor.usage.window.ms", 1000);
|
||||
WiredEngine.MONITOR_USAGE_LIMIT = Emulator.getConfig().getInt("wired.monitor.usage.limit", 1000);
|
||||
WiredEngine.MONITOR_DELAYED_EVENTS_LIMIT = Emulator.getConfig().getInt("wired.monitor.delayed.events.limit", 100);
|
||||
WiredEngine.MONITOR_OVERLOAD_AVERAGE_MS = Emulator.getConfig().getInt("wired.monitor.overload.average.ms", 50);
|
||||
WiredEngine.MONITOR_OVERLOAD_PEAK_MS = Emulator.getConfig().getInt("wired.monitor.overload.peak.ms", 150);
|
||||
WiredEngine.MONITOR_OVERLOAD_CONSECUTIVE_WINDOWS = Emulator.getConfig().getInt("wired.monitor.overload.consecutive.windows", 2);
|
||||
WiredEngine.MONITOR_HEAVY_USAGE_PERCENT = Emulator.getConfig().getInt("wired.monitor.heavy.usage.percent", 70);
|
||||
WiredEngine.MONITOR_HEAVY_CONSECUTIVE_WINDOWS = Emulator.getConfig().getInt("wired.monitor.heavy.consecutive.windows", 5);
|
||||
WiredEngine.MONITOR_HEAVY_DELAYED_PERCENT = Emulator.getConfig().getInt("wired.monitor.heavy.delayed.percent", 60);
|
||||
|
||||
if (WiredManager.getEngine() != null) {
|
||||
WiredManager.getEngine().clearAllDiagnostics();
|
||||
}
|
||||
|
||||
NavigatorManager.MAXIMUM_RESULTS_PER_PAGE = Emulator.getConfig().getInt("hotel.navigator.search.maxresults");
|
||||
NavigatorManager.CATEGORY_SORT_USING_ORDER_NUM = Emulator.getConfig().getBoolean("hotel.navigator.sort.ordernum");
|
||||
RoomChatMessage.MAXIMUM_LENGTH = Emulator.getConfig().getInt("hotel.chat.max.length");
|
||||
|
||||
@@ -0,0 +1,781 @@
|
||||
# Emulator Settings Reference
|
||||
|
||||
## Scope
|
||||
|
||||
This document inventories the non-wired keys currently stored in `emulator_settings` based on `Default Database/FullDB.sql`. Wired-specific keys are documented separately in `docs/wired_tools_reference.md`.
|
||||
|
||||
Each entry below mirrors the comment written by `Database Updates/003_add_comment_column_to_emulator_settings.sql`, so the documentation and in-database comments stay aligned.
|
||||
|
||||
## Table schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE `emulator_settings` (
|
||||
`key` varchar(100) NOT NULL,
|
||||
`value` varchar(512) NOT NULL,
|
||||
`comment` text NOT NULL,
|
||||
PRIMARY KEY (`key`)
|
||||
);
|
||||
```
|
||||
|
||||
## Inventory summary
|
||||
|
||||
- Total non-wired keys documented here: `329`
|
||||
- Source of defaults: `Default Database/FullDB.sql`
|
||||
- Value type is inferred from the default string stored in SQL.
|
||||
|
||||
## Group index
|
||||
|
||||
- `allowed` (1)
|
||||
- `apollyon` (1)
|
||||
- `basejump` (2)
|
||||
- `bots` (1)
|
||||
- `bubblealerts` (6)
|
||||
- `bundle` (2)
|
||||
- `callback` (3)
|
||||
- `camera` (11)
|
||||
- `catalog` (5)
|
||||
- `commands` (3)
|
||||
- `console` (1)
|
||||
- `custom` (1)
|
||||
- `db` (4)
|
||||
- `debug` (7)
|
||||
- `discount` (5)
|
||||
- `easter_eggs` (1)
|
||||
- `enc` (4)
|
||||
- `essentials` (2)
|
||||
- `flood` (1)
|
||||
- `ftp` (4)
|
||||
- `furniture` (1)
|
||||
- `gamecenter` (16)
|
||||
- `gamedata` (1)
|
||||
- `guardians` (5)
|
||||
- `hotel` (169)
|
||||
- `hotelview` (5)
|
||||
- `imager` (6)
|
||||
- `images` (2)
|
||||
- `info` (1)
|
||||
- `invisible` (1)
|
||||
- `io` (3)
|
||||
- `logging` (6)
|
||||
- `marketplace` (1)
|
||||
- `monsterplant` (2)
|
||||
- `moodlight` (1)
|
||||
- `navigator` (1)
|
||||
- `networking` (1)
|
||||
- `notify` (1)
|
||||
- `path` (1)
|
||||
- `pathfinder` (4)
|
||||
- `pirate_parrot` (2)
|
||||
- `postit` (1)
|
||||
- `pyramids` (1)
|
||||
- `retro` (1)
|
||||
- `room` (4)
|
||||
- `rosie` (2)
|
||||
- `runtime` (1)
|
||||
- `save` (2)
|
||||
- `scripter` (1)
|
||||
- `seasonal` (7)
|
||||
- `subscriptions` (12)
|
||||
- `team` (1)
|
||||
- `youtube` (1)
|
||||
|
||||
## `allowed`
|
||||
|
||||
Validation rules for usernames and account-facing inputs.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `allowed.username.characters` | `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-=!?@:,.` | `list` | Characters allowed when users choose or change a username. |
|
||||
|
||||
## `apollyon`
|
||||
|
||||
Custom project-specific behaviour switches.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `apollyon.cooldown.amount` | `250` | `integer` | Cooldown in milliseconds used by the Apollyon-specific behaviour or command flow. |
|
||||
|
||||
## `basejump`
|
||||
|
||||
BaseJump or FastFood launcher URLs.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `basejump.assets.url` | `http://localhost/gamecenter/gamecenter_basejump/BasicAssets.swf` | `url` | Asset URL used by the BaseJump or FastFood game client. |
|
||||
| `basejump.url` | `http://localhost/game/BaseJump.swf` | `url` | SWF URL used to launch the BaseJump or FastFood game client. |
|
||||
|
||||
## `bots`
|
||||
|
||||
Miscellaneous visitor-bot display settings.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `bots.visitor.dateformat` | `yyyy-mm-dd HH:mm` | `string` | Date format used by visitor bots when they print timestamps. |
|
||||
|
||||
## `bubblealerts`
|
||||
|
||||
Bubble notification behaviour and assets.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `bubblealerts.enabled` | `1` | `boolean` | Master switch for bubble alert notifications. |
|
||||
| `bubblealerts.notif_friendonline.enabled` | `1` | `boolean` | Enable bubble alerts when friends come online. |
|
||||
| `bubblealerts.notif_friendonline.image` | `${image.library.url}notifications/figure?p=%figure%` | `template` | Image template used when showing friend-online bubble alerts. |
|
||||
| `bubblealerts.notif_friendonline.useimage` | `1` | `boolean` | Use the configured figure image inside friend-online bubble alerts. |
|
||||
| `bubblealerts.notif_marketplace.enabled` | `1` | `boolean` | Show bubble alerts for marketplace notifications. |
|
||||
| `bubblealerts.notif_purchase.limited` | `0` | `boolean` | Show bubble alerts for limited-item purchases. |
|
||||
|
||||
## `bundle`
|
||||
|
||||
Bundle-specific toggles for pets and bots.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `bundle.bots.enabled` | `1` | `boolean` | Allow bots to be included in room bundles or package rewards. |
|
||||
| `bundle.pets.enabled` | `1` | `boolean` | Allow pets to be included in room bundles or package rewards. |
|
||||
|
||||
## `callback`
|
||||
|
||||
HTTP callback integrations for external services.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `callback.get.version` | `1` | `boolean` | Enable the GET callback used to report version to external services. |
|
||||
| `callback.post.errors` | `1` | `boolean` | Enable the POST callback used to report errors to external services. |
|
||||
| `callback.post.statistics` | `1` | `boolean` | Enable the POST callback used to report statistics to external services. |
|
||||
|
||||
## `camera`
|
||||
|
||||
Camera costs, storage and publish settings.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `camera.enabled` | `1` | `boolean` | Enable the in-room camera feature. |
|
||||
| `camera.extradata` | `{\"t\":%timestamp%, \"u\":\"%id%\", \"s\":%room_id%, \"w\":\"%url%\"}` | `template` | Extradata template written into camera photo items when they are created. |
|
||||
| `camera.item_id` | `45970` | `integer` | Base item ID used by the generated camera photo furniture. |
|
||||
| `camera.price.credits` | `2` | `integer` | Credit price charged when taking a camera photo. |
|
||||
| `camera.price.points` | `0` | `boolean` | Amount of activity points charged when taking a camera photo. |
|
||||
| `camera.price.points.publish` | `10` | `integer` | Amount of activity points charged when publishing a camera photo. |
|
||||
| `camera.price.points.publish.type` | `0` | `boolean` | Activity point type used for the camera publish cost. |
|
||||
| `camera.price.points.type` | `0` | `boolean` | Activity point type used for the camera capture cost. |
|
||||
| `camera.publish.delay` | `180` | `integer` | Delay in seconds before a published camera photo becomes available. |
|
||||
| `camera.url` | `http://localhost/usercontent/camera/` | `url` | Base URL where camera images are published. |
|
||||
| `camera.use.https` | `1` | `boolean` | Force HTTPS when generating camera image URLs. |
|
||||
|
||||
## `catalog`
|
||||
|
||||
Catalog behaviour that is not wired-specific.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `catalog.guild.hc_required` | `1` | `boolean` | Require HC or VIP status before users can create a guild. |
|
||||
| `catalog.guild.price` | `10` | `integer` | Credit cost required to create a guild. |
|
||||
| `catalog.ltd.page.soldout` | `761` | `integer` | Layout or image ID used when a limited page is sold out. |
|
||||
| `catalog.ltd.random` | `1` | `boolean` | Randomize the order or selection of limited catalog items. |
|
||||
| `catalog.page.vipgifts` | `0` | `boolean` | Catalog page ID used for VIP gift redemption. |
|
||||
|
||||
## `commands`
|
||||
|
||||
Command-specific restrictions and compatibility flags.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `commands.cmd_chatcolor.banned_numbers` | `23;33;34` | `list` | Semicolon-separated list of chat color IDs blocked for the chatcolor command. |
|
||||
| `commands.cmd_staffonline.min_rank` | `2` | `integer` | Minimum permission rank required to use the staffonline command. |
|
||||
| `commands.plugins.oldstyle` | `0` | `boolean` | Use the legacy command plugin loading style. |
|
||||
|
||||
## `console`
|
||||
|
||||
Console behaviour.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `console.mode` | `1` | `boolean` | Controls the emulator console mode or console output style. |
|
||||
|
||||
## `custom`
|
||||
|
||||
Fork-specific custom behaviour switches.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `custom.stacking.enabled` | `0` | `boolean` | Enable custom item stacking behaviour outside the default stacking rules. |
|
||||
|
||||
## `db`
|
||||
|
||||
Database pooling and batching controls.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `db.max.partition.size` | `2` | `integer` | Maximum batch or partition size used by partitioned database operations. |
|
||||
| `db.min.partition.size` | `1` | `boolean` | Minimum batch or partition size used by partitioned database operations. |
|
||||
| `db.pool.maxsize` | `12` | `integer` | Maximum size of the database connection pool. |
|
||||
| `db.pool.minsize` | `8` | `integer` | Minimum number of open connections kept in the database pool. |
|
||||
|
||||
## `debug`
|
||||
|
||||
Verbose debug output toggles.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `debug.mode` | `1` | `boolean` | Enable general emulator debug mode. |
|
||||
| `debug.show.errors` | `1` | `boolean` | Show internal debug error messages. |
|
||||
| `debug.show.headers` | `0` | `boolean` | Show packet headers in debug logs. |
|
||||
| `debug.show.packets` | `0` | `boolean` | Print packet-level debug output. |
|
||||
| `debug.show.packets.undefined` | `0` | `boolean` | Print debug output for undefined incoming or outgoing packets. |
|
||||
| `debug.show.sql.exception` | `1` | `boolean` | Log SQL exceptions to the console. |
|
||||
| `debug.show.users` | `1` | `boolean` | Show user-related debug messages. |
|
||||
|
||||
## `discount`
|
||||
|
||||
Discount batch rules for catalog purchases.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `discount.additional.thresholds` | `40;99` | `list` | Semicolon-separated discount thresholds used for extra batch bonuses. |
|
||||
| `discount.batch.free.items` | `1` | `boolean` | Number of free items granted inside one discount batch. |
|
||||
| `discount.batch.size` | `6` | `integer` | Number of items required for one discount batch. |
|
||||
| `discount.bonus.min.discounts` | `1` | `boolean` | Minimum number of discount batches required before the bonus logic applies. |
|
||||
| `discount.max.allowed.items` | `100` | `integer` | Maximum number of catalog items that can participate in one discount batch. |
|
||||
|
||||
## `easter_eggs`
|
||||
|
||||
Optional easter egg features.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `easter_eggs.enabled` | `1` | `boolean` | Enable or disable the feature controlled by `easter_eggs.enabled`. |
|
||||
|
||||
## `enc`
|
||||
|
||||
Encryption and RSA settings.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `enc.d` | `` | `string` | RSA private exponent used by the encryption layer. |
|
||||
| `enc.e` | `` | `string` | RSA public exponent used by the encryption layer. |
|
||||
| `enc.enabled` | `1` | `boolean` | Enable RSA encryption support for the socket handshake. |
|
||||
| `enc.n` | `` | `string` | RSA modulus used by the encryption layer. |
|
||||
|
||||
## `essentials`
|
||||
|
||||
Essentials plugin or command values.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `essentials.cmd_kill.effect.killer` | `164;182` | `list` | Semicolon-separated effect IDs used by the kill command for the killer. |
|
||||
| `essentials.cmd_kill.effect.victim` | `93;89` | `list` | Semicolon-separated effect IDs used by the kill command for the victim. |
|
||||
|
||||
## `flood`
|
||||
|
||||
Flood-control compatibility switches.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `flood.with.rights` | `0` | `boolean` | Allow users with room rights to bypass the normal flood protection. |
|
||||
|
||||
## `ftp`
|
||||
|
||||
FTP integration settings for generated assets.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `ftp.enabled` | `0` | `boolean` | Enable FTP uploads for generated assets. |
|
||||
| `ftp.host` | `example.com` | `string` | FTP host used for asset uploads. |
|
||||
| `ftp.password` | `password123` | `string` | FTP password used for asset uploads. |
|
||||
| `ftp.user` | `root` | `string` | FTP username used for asset uploads. |
|
||||
|
||||
## `furniture`
|
||||
|
||||
General furniture interaction behaviour.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `furniture.talking.range` | `2` | `integer` | Maximum tile distance at which talking furniture can react to nearby speech. |
|
||||
|
||||
## `gamecenter`
|
||||
|
||||
Gamecenter launchers and theme settings.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `gamecenter.fastfood.apiKey` | `` | `string` | API key used by the FastFood or BaseJump integration. |
|
||||
| `gamecenter.fastfood.assets` | `http://localhost/swf/c_images/gamecenter_basejump/` | `url` | Asset base URL used by the FastFood or BaseJump game client. |
|
||||
| `gamecenter.fastfood.background.color` | `68bbd2` | `string` | Background color used by the FastFood launcher UI. |
|
||||
| `gamecenter.fastfood.enabled` | `true` | `boolean` | Enable the FastFood or BaseJump gamecenter integration. |
|
||||
| `gamecenter.fastfood.text.color` | `ffffff` | `string` | Text color used by the FastFood launcher UI. |
|
||||
| `gamecenter.fastfood.theme` | `default` | `string` | Theme name used by the FastFood launcher. |
|
||||
| `gamecenter.snowwar.artic.bg` | `http://localhost/swf/c_images/gamecenter_snowwar/snst_bg_1_a_big.png` | `url` | Background image used for the SnowWar Arctic map. |
|
||||
| `gamecenter.snowwar.assets` | `http://localhost/swf/c_images/gamecenter_snowwar/` | `url` | Asset base URL used by the SnowWar game client. |
|
||||
| `gamecenter.snowwar.dragoncave.bg` | `http://localhost/swf/c_images/gamecenter_snowwar/snst_bg_2_big.png` | `url` | Background image used for the SnowWar Dragon Cave map. |
|
||||
| `gamecenter.snowwar.enabled` | `true` | `boolean` | Enable the SnowWar gamecenter integration. |
|
||||
| `gamecenter.snowwar.fightnight.bg` | `http://localhost/swf/c_images/gamecenter_snowwar/snst_bg_3_noscale.png` | `url` | Background image used for the SnowWar Fight Night map. |
|
||||
| `gamecenter.snowwar.game.background.color` | `93d4f3` | `string` | Background color used by the SnowWar launcher UI. |
|
||||
| `gamecenter.snowwar.game.start.time` | `15` | `integer` | Countdown in seconds before a SnowWar round starts. |
|
||||
| `gamecenter.snowwar.game.text.color` | `000000` | `integer` | Text color used by the SnowWar launcher UI. |
|
||||
| `gamecenter.snowwar.players.min` | `2` | `integer` | Minimum number of players required to start SnowWar. |
|
||||
| `gamecenter.snowwar.room.id` | `0` | `boolean` | Room ID used as the SnowWar lobby or host room. |
|
||||
|
||||
## `gamedata`
|
||||
|
||||
Remote gamedata sources.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `gamedata.figuredata.url` | `https://habbo.com/gamedata/figuredata/0` | `url` | Remote figuredata URL used when the hotel loads avatar figure definitions. |
|
||||
|
||||
## `guardians`
|
||||
|
||||
Guardians and report-review settings.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `guardians.accept.timer` | `90` | `integer` | Time in seconds that guardians have to accept a case. |
|
||||
| `guardians.maximum.guardians.total` | `10` | `integer` | Maximum number of guardians that can be assigned to one case. |
|
||||
| `guardians.maximum.resends` | `2` | `integer` | Maximum number of times an unanswered guardian case can be resent. |
|
||||
| `guardians.minimum.votes` | `5` | `integer` | Minimum number of guardian votes required to resolve a case. |
|
||||
| `guardians.reporting.cooldown` | `900` | `integer` | Cooldown in seconds before the same user can open a new guardian report. |
|
||||
|
||||
## `hotel`
|
||||
|
||||
Core hotel gameplay, economy, room, catalog and moderation settings.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `hotel.alert.oldstyle` | `0` | `boolean` | Use the legacy generic alert window style. |
|
||||
| `hotel.allow.ignore.staffs` | `1` | `boolean` | Allow users to ignore staff accounts. |
|
||||
| `hotel.auto.credits.amount` | `100` | `integer` | Amount of credits granted on each automatic payout. |
|
||||
| `hotel.auto.credits.enabled` | `1` | `boolean` | Enable automatic credits payouts. |
|
||||
| `hotel.auto.credits.hc_modifier` | `1` | `boolean` | Multiplier applied to automatic credits payouts for HC users. |
|
||||
| `hotel.auto.credits.ignore.hotelview` | `1` | `boolean` | Skip users staying in hotel view when giving automatic credits payouts. |
|
||||
| `hotel.auto.credits.ignore.idled` | `0` | `boolean` | Skip idle users when giving automatic credits payouts. |
|
||||
| `hotel.auto.credits.interval` | `600` | `integer` | Interval in seconds between automatic credits payouts. |
|
||||
| `hotel.auto.gotwpoints.enabled` | `0` | `boolean` | Enable automatic gotwpoints payouts. |
|
||||
| `hotel.auto.gotwpoints.hc_modifier` | `1` | `boolean` | Multiplier applied to automatic gotwpoints payouts for HC users. |
|
||||
| `hotel.auto.gotwpoints.ignore.hotelview` | `1` | `boolean` | Skip users staying in hotel view when giving automatic gotwpoints payouts. |
|
||||
| `hotel.auto.gotwpoints.ignore.idled` | `1` | `boolean` | Skip idle users when giving automatic gotwpoints payouts. |
|
||||
| `hotel.auto.gotwpoints.interval` | `600` | `integer` | Interval in seconds between automatic gotwpoints payouts. |
|
||||
| `hotel.auto.gotwpoints.name` | `shell` | `string` | Internal currency name used by the automatic gotwpoints payout. |
|
||||
| `hotel.auto.gotwpoints.type` | `4` | `integer` | Currency type ID used by the automatic gotwpoints payout. |
|
||||
| `hotel.auto.pixels.amount` | `100` | `integer` | Amount of pixels granted on each automatic payout. |
|
||||
| `hotel.auto.pixels.enabled` | `1` | `boolean` | Enable automatic pixels payouts. |
|
||||
| `hotel.auto.pixels.hc_modifier` | `1` | `boolean` | Multiplier applied to automatic pixels payouts for HC users. |
|
||||
| `hotel.auto.pixels.ignore.hotelview` | `1` | `boolean` | Skip users staying in hotel view when giving automatic pixels payouts. |
|
||||
| `hotel.auto.pixels.ignore.idled` | `1` | `boolean` | Skip idle users when giving automatic pixels payouts. |
|
||||
| `hotel.auto.pixels.interval` | `600` | `integer` | Interval in seconds between automatic pixels payouts. |
|
||||
| `hotel.auto.points.amount` | `5` | `integer` | Amount of points granted on each automatic payout. |
|
||||
| `hotel.auto.points.enabled` | `1` | `boolean` | Enable automatic points payouts. |
|
||||
| `hotel.auto.points.hc_modifier` | `1` | `boolean` | Multiplier applied to automatic points payouts for HC users. |
|
||||
| `hotel.auto.points.ignore.hotelview` | `0` | `boolean` | Skip users staying in hotel view when giving automatic points payouts. |
|
||||
| `hotel.auto.points.ignore.idled` | `0` | `boolean` | Skip idle users when giving automatic points payouts. |
|
||||
| `hotel.auto.points.interval` | `600` | `integer` | Interval in seconds between automatic points payouts. |
|
||||
| `hotel.banzai.points.tile.fill` | `0` | `boolean` | Configuration value used by `hotel.banzai.points.tile.fill`. |
|
||||
| `hotel.banzai.points.tile.lock` | `1` | `boolean` | Configuration value used by `hotel.banzai.points.tile.lock`. |
|
||||
| `hotel.banzai.points.tile.steal` | `0` | `boolean` | Configuration value used by `hotel.banzai.points.tile.steal`. |
|
||||
| `hotel.bot.butler.commanddistance` | `5` | `integer` | Maximum tile distance from which a butler bot accepts commands. |
|
||||
| `hotel.bot.butler.servedistance` | `5` | `integer` | Maximum tile distance from which a butler bot can serve requests. |
|
||||
| `hotel.bot.chat.minimum.interval` | `5` | `integer` | Minimum number of seconds between bot chat lines. |
|
||||
| `hotel.bot.max.chatdelay` | `604800` | `integer` | Maximum bot chat delay allowed when configuring scripted speech. |
|
||||
| `hotel.bot.max.chatlength` | `120` | `integer` | Maximum number of characters allowed in bot chat lines. |
|
||||
| `hotel.bot.max.namelength` | `15` | `integer` | Maximum number of characters allowed in bot names. |
|
||||
| `hotel.bots.max.inventory` | `25` | `integer` | Maximum number of bots allowed in one inventory. |
|
||||
| `hotel.bots.max.room` | `10` | `integer` | Maximum number of bots allowed in one room. |
|
||||
| `hotel.calendar.default` | `test` | `string` | Default calendar campaign name or identifier. |
|
||||
| `hotel.calendar.enabled` | `0` | `boolean` | Enable the hotel calendar feature. |
|
||||
| `hotel.calendar.pixels.hc_modifier` | `2.0` | `number` | Multiplier applied to calendar pixel rewards for HC users. |
|
||||
| `hotel.calendar.starttimestamp` | `1593561600` | `integer` | Unix timestamp used as the calendar start date. |
|
||||
| `hotel.catalog.discounts.amount` | `6` | `integer` | Number of discount slots or discount batches shown by the catalog. |
|
||||
| `hotel.catalog.items.display.ordernum` | `1` | `boolean` | Respect catalog item order numbers when rendering pages. |
|
||||
| `hotel.catalog.ltd.limit.enabled` | `1` | `boolean` | Enable daily purchase limits for limited catalog items. |
|
||||
| `hotel.catalog.purchase.cooldown` | `1` | `boolean` | Cooldown in seconds between catalog purchases. |
|
||||
| `hotel.catalog.recycler.enabled` | `1` | `boolean` | Enable the catalog recycler feature. |
|
||||
| `hotel.chat.max.length` | `100` | `integer` | Maximum number of characters allowed in one public chat message. |
|
||||
| `hotel.daily.respect` | `3` | `integer` | Daily amount of respect points available for users. |
|
||||
| `hotel.daily.respect.pets` | `3` | `integer` | Daily amount of pet respect points available for users. |
|
||||
| `hotel.ecotron.enabled` | `1` | `boolean` | Enable or disable the feature controlled by `hotel.ecotron.enabled`. |
|
||||
| `hotel.ecotron.rarity.chance.1` | `1` | `boolean` | Configuration value used by `hotel.ecotron.rarity.chance.1`. |
|
||||
| `hotel.ecotron.rarity.chance.2` | `4` | `integer` | Configuration value used by `hotel.ecotron.rarity.chance.2`. |
|
||||
| `hotel.ecotron.rarity.chance.3` | `40` | `integer` | Configuration value used by `hotel.ecotron.rarity.chance.3`. |
|
||||
| `hotel.ecotron.rarity.chance.4` | `200` | `integer` | Configuration value used by `hotel.ecotron.rarity.chance.4`. |
|
||||
| `hotel.ecotron.rarity.chance.5` | `2000` | `integer` | Configuration value used by `hotel.ecotron.rarity.chance.5`. |
|
||||
| `hotel.flood.mute.time` | `30` | `integer` | Mute duration in seconds applied by the hotel flood protection. |
|
||||
| `hotel.floorplan.max.totalarea` | `4096` | `integer` | Maximum total floorplan area allowed for custom rooms. |
|
||||
| `hotel.floorplan.max.widthlength` | `64` | `integer` | Maximum floorplan width or length allowed for custom rooms. |
|
||||
| `hotel.freeze.onfreeze.loose.explosionboost` | `3` | `integer` | Number of explosion boosts lost when a player gets frozen. |
|
||||
| `hotel.freeze.onfreeze.loose.snowballs` | `5` | `integer` | Number of snowballs lost when a player gets frozen. |
|
||||
| `hotel.freeze.onfreeze.time.frozen` | `5` | `integer` | Time in seconds a player remains frozen. |
|
||||
| `hotel.freeze.points.block` | `1` | `boolean` | Score awarded for blocking tiles in Freeze. |
|
||||
| `hotel.freeze.points.effect` | `3` | `integer` | Score awarded for using Freeze effects or power-up actions. |
|
||||
| `hotel.freeze.points.freeze` | `10` | `integer` | Score awarded for freezing another player in Freeze. |
|
||||
| `hotel.freeze.powerup.chance` | `33` | `integer` | Chance for Freeze power-ups to spawn. |
|
||||
| `hotel.freeze.powerup.max.lives` | `3` | `integer` | Maximum number of extra lives granted by a Freeze power-up. |
|
||||
| `hotel.freeze.powerup.max.snowballs` | `5` | `integer` | Maximum number of extra snowballs granted by a Freeze power-up. |
|
||||
| `hotel.freeze.powerup.protection.stack` | `1` | `boolean` | Allow Freeze protection power-ups to stack. |
|
||||
| `hotel.freeze.powerup.protection.time` | `10` | `integer` | Protection time in seconds after receiving a Freeze protection power-up. |
|
||||
| `hotel.friendcategory` | `0` | `boolean` | Default friend category ID assigned to new friends. |
|
||||
| `hotel.furni.gym.achievement.olympics_c16_crosstrainer` | `CrossTrainer` | `string` | Configuration value used by `hotel.furni.gym.achievement.olympics_c16_crosstrainer`. |
|
||||
| `hotel.furni.gym.achievement.olympics_c16_trampoline` | `Trampolinist` | `string` | Configuration value used by `hotel.furni.gym.achievement.olympics_c16_trampoline`. |
|
||||
| `hotel.furni.gym.achievement.olympics_c16_treadmill` | `Jogger` | `string` | Configuration value used by `hotel.furni.gym.achievement.olympics_c16_treadmill`. |
|
||||
| `hotel.furni.gym.forcerot.olympics_c16_crosstrainer` | `1` | `boolean` | Configuration value used by `hotel.furni.gym.forcerot.olympics_c16_crosstrainer`. |
|
||||
| `hotel.furni.gym.forcerot.olympics_c16_trampoline` | `0` | `boolean` | Configuration value used by `hotel.furni.gym.forcerot.olympics_c16_trampoline`. |
|
||||
| `hotel.furni.gym.forcerot.olympics_c16_treadmill` | `1` | `boolean` | Configuration value used by `hotel.furni.gym.forcerot.olympics_c16_treadmill`. |
|
||||
| `hotel.gifts.box_types` | `0,1,2,3,4,5,6,8` | `list` | Comma-separated list of gift box type IDs allowed in the catalog. |
|
||||
| `hotel.gifts.length.max` | `300` | `integer` | Maximum message length allowed on gift notes. |
|
||||
| `hotel.gifts.ribbon_types` | `0,1,2,3,4,5,6,7,8,9,10` | `list` | Comma-separated list of ribbon type IDs allowed in the catalog. |
|
||||
| `hotel.gifts.special.price` | `10` | `integer` | Credit price used by special gift boxes. |
|
||||
| `hotel.home.room` | `0` | `boolean` | Room ID used as the default home room for new users. |
|
||||
| `hotel.inventory.max.items` | `7500` | `integer` | Maximum number of items allowed in one inventory. |
|
||||
| `hotel.item.trap.hween14_rare2` | `3000` | `integer` | Configuration value used by `hotel.item.trap.hween14_rare2`. |
|
||||
| `hotel.item.trap.hween_c17_handstrap` | `3000` | `integer` | Configuration value used by `hotel.item.trap.hween_c17_handstrap`. |
|
||||
| `hotel.item.trap.hween_c17_spiketrap` | `3000` | `integer` | Configuration value used by `hotel.item.trap.hween_c17_spiketrap`. |
|
||||
| `hotel.item.trap.pirate_sandtrap` | `3000` | `integer` | Configuration value used by `hotel.item.trap.pirate_sandtrap`. |
|
||||
| `hotel.jukebox.limit.large` | `20` | `integer` | Track limit used by large jukebox furniture. |
|
||||
| `hotel.jukebox.limit.normal` | `10` | `integer` | Track limit used by normal jukebox furniture. |
|
||||
| `hotel.log.chat` | `1` | `boolean` | Enable logging for chat. |
|
||||
| `hotel.log.chat.private` | `1` | `boolean` | Enable logging for chat private. |
|
||||
| `hotel.log.room.enter` | `1` | `boolean` | Enable logging for room enter. |
|
||||
| `hotel.log.trades` | `1` | `boolean` | Enable logging for trades. |
|
||||
| `hotel.marketplace.currency` | `0` | `boolean` | Currency type used for marketplace prices and taxes. |
|
||||
| `hotel.marketplace.enabled` | `1` | `boolean` | Enable or disable the feature controlled by `hotel.marketplace.enabled`. |
|
||||
| `hotel.max.bots.room` | `10` | `integer` | Maximum number of bots allowed in one room. |
|
||||
| `hotel.max.duckets` | `9000000` | `integer` | Maximum amount of duckets a user can hold. |
|
||||
| `hotel.messenger.offline.messaging.enabled` | `1` | `boolean` | Enable or disable the feature controlled by `hotel.messenger.offline.messaging.enabled`. |
|
||||
| `hotel.messenger.search.maxresults` | `50` | `integer` | Maximum number of results returned by messenger user searches. |
|
||||
| `hotel.name` | `Habbo Hotel` | `string` | Public hotel name shown across the client and outgoing messages. |
|
||||
| `hotel.navigator.camera` | `1` | `boolean` | Enable navigator room previews or camera mode. |
|
||||
| `hotel.navigator.owner` | `HabboHotel` | `string` | Default owner name displayed by the navigator. |
|
||||
| `hotel.navigator.popular.amount` | `35` | `integer` | Number of rooms shown in the popular rooms list. |
|
||||
| `hotel.navigator.popular.category.maxresults` | `10` | `integer` | Maximum number of rooms shown per popular category. |
|
||||
| `hotel.navigator.popular.listtype` | `1` | `boolean` | List type used for the popular rooms tab. |
|
||||
| `hotel.navigator.populartab.publics` | `0` | `boolean` | Include public rooms inside the popular rooms tab. |
|
||||
| `hotel.navigator.search.maxresults` | `35` | `integer` | Maximum number of results returned by navigator searches. |
|
||||
| `hotel.navigator.sort.ordernum` | `1` | `boolean` | Respect order numbers when sorting navigator results. |
|
||||
| `hotel.navigator.staffpicks.categoryid` | `1` | `boolean` | Category ID used for the staff picks tab. |
|
||||
| `hotel.nux.gifts.enabled` | `0` | `boolean` | Enable the NUX gift flow for new users. |
|
||||
| `hotel.pets.max.inventory` | `25` | `integer` | Maximum number of pets allowed in one inventory. |
|
||||
| `hotel.pets.max.room` | `10` | `integer` | Maximum number of pets allowed in one room. |
|
||||
| `hotel.pets.name.length.max` | `15` | `integer` | Maximum pet name length. |
|
||||
| `hotel.pets.name.length.min` | `3` | `integer` | Minimum pet name length. |
|
||||
| `hotel.player.name` | `Habbo` | `string` | Generic player label used by text templates and client messages. |
|
||||
| `hotel.purchase.ltd.limit.daily.item` | `3` | `integer` | Maximum number of the same limited item a user can buy per day. |
|
||||
| `hotel.purchase.ltd.limit.daily.total` | `10` | `integer` | Maximum number of limited items a user can buy per day across all limited sales. |
|
||||
| `hotel.refill.daily` | `86400` | `integer` | Cooldown in seconds before daily counters such as respect are refilled. |
|
||||
| `hotel.rollers.speed.maximum` | `100` | `integer` | Maximum roller delay or speed value accepted by roller furniture. |
|
||||
| `hotel.room.enter.logs` | `1` | `boolean` | Enable room-entry logs. |
|
||||
| `hotel.room.floorplan.check.enabled` | `1` | `boolean` | Validate custom floorplans before rooms are saved. |
|
||||
| `hotel.room.furni.max` | `2500` | `integer` | Maximum amount of furniture allowed in one room. |
|
||||
| `hotel.room.nooblobby` | `3` | `integer` | Room ID used as the newbie lobby. |
|
||||
| `hotel.room.public.doortile.kick` | `0` | `boolean` | Kick users who stand on public room door tiles. |
|
||||
| `hotel.room.rollers.norules` | `0` | `boolean` | Allow rollers to ignore normal placement rules. |
|
||||
| `hotel.room.rollers.roll_avatars.max` | `1` | `boolean` | Maximum number of avatars that rollers can move at once. |
|
||||
| `hotel.room.stickies.max` | `200` | `integer` | Maximum number of sticky notes allowed in one room. |
|
||||
| `hotel.room.stickypole.prefix` | `%timestamp%, %username%:\\r` | `template` | Prefix template written by sticky pole furniture. |
|
||||
| `hotel.room.tags.staff` | `staff;official;habbo` | `list` | Semicolon-separated staff room tags. |
|
||||
| `hotel.rooms.auto.idle` | `1` | `boolean` | Allow empty rooms to switch into the idle state automatically. |
|
||||
| `hotel.rooms.deco_hosting` | `1` | `boolean` | Enable decoration-hosting features for rooms. |
|
||||
| `hotel.rooms.handitem.time` | `100` | `integer` | Time in seconds before temporary hand items are cleared. |
|
||||
| `hotel.rooms.max.favorite` | `30` | `integer` | Maximum number of favorite rooms allowed per user. |
|
||||
| `hotel.roomuser.idle.cycles` | `300` | `integer` | Idle cycle count before a room user is marked idle. |
|
||||
| `hotel.roomuser.idle.cycles.kick` | `900` | `integer` | Idle cycle count before a room user is kicked for idling. |
|
||||
| `hotel.roomuser.idle.not_dancing.ignore.wired_idle` | `0` | `boolean` | Ignore the wired idle status when checking the room idle rule. |
|
||||
| `hotel.sanctions.enabled` | `1` | `boolean` | Enable the sanctions system. |
|
||||
| `hotel.shop.discount.modifier` | `6` | `integer` | Modifier used by the shop discount calculation. |
|
||||
| `hotel.talenttrack.enabled` | `1` | `boolean` | Enable the talent track feature. |
|
||||
| `hotel.targetoffer.id` | `1` | `boolean` | Offer ID requested when the client asks for a targeted offer. |
|
||||
| `hotel.teleport.locked.allowed` | `1` | `boolean` | Allow users to use teleports inside locked rooms when they otherwise qualify. |
|
||||
| `hotel.trading.enabled` | `1` | `boolean` | Enable room trading. |
|
||||
| `hotel.trading.requires.perk` | `0` | `boolean` | Require the trading perk before users may trade. |
|
||||
| `hotel.trophies.length.max` | `300` | `integer` | Maximum value used by `hotel.trophies.length.max`. |
|
||||
| `hotel.users.clothingvalidation.onchangelooks` | `0` | `boolean` | Run clothing validation when the related action occurs: onchangelooks. |
|
||||
| `hotel.users.clothingvalidation.onfballgate` | `0` | `boolean` | Run clothing validation when the related action occurs: onfballgate. |
|
||||
| `hotel.users.clothingvalidation.onhcexpired` | `0` | `boolean` | Run clothing validation when the related action occurs: onhcexpired. |
|
||||
| `hotel.users.clothingvalidation.onlogin` | `0` | `boolean` | Run clothing validation when the related action occurs: onlogin. |
|
||||
| `hotel.users.clothingvalidation.onmannequin` | `0` | `boolean` | Run clothing validation when the related action occurs: onmannequin. |
|
||||
| `hotel.users.clothingvalidation.onmimic` | `0` | `boolean` | Run clothing validation when the related action occurs: onmimic. |
|
||||
| `hotel.users.max.friends` | `300` | `integer` | Maximum number of friends allowed for normal users. |
|
||||
| `hotel.users.max.friends.hc` | `1100` | `integer` | Maximum number of friends allowed for HC users. |
|
||||
| `hotel.users.max.rooms` | `50` | `integer` | Maximum number of rooms allowed for normal users. |
|
||||
| `hotel.users.max.rooms.hc` | `75` | `integer` | Maximum number of rooms allowed for HC users. |
|
||||
| `hotel.view.ltdcountdown.enabled` | `1` | `boolean` | Enable the limited-countdown hotel-view widget. |
|
||||
| `hotel.view.ltdcountdown.itemid` | `10388` | `integer` | Item ID shown by the limited-countdown widget. |
|
||||
| `hotel.view.ltdcountdown.itemname` | `trophy_netsafety_0` | `string` | Item name shown by the limited-countdown widget. |
|
||||
| `hotel.view.ltdcountdown.pageid` | `13` | `integer` | Catalog page ID linked by the limited-countdown widget. |
|
||||
| `hotel.view.ltdcountdown.timestamp` | `1519496132` | `integer` | Unix timestamp used by the limited-countdown widget. |
|
||||
| `hotel.welcome.alert.delay` | `10000` | `integer` | Delay in milliseconds before the welcome alert is shown. |
|
||||
| `hotel.welcome.alert.enabled` | `0` | `boolean` | Enable the welcome alert shown after login. |
|
||||
| `hotel.welcome.alert.message` | `Welcome to Habbo Hotel %user%!` | `template` | Message template used by the welcome alert. |
|
||||
| `hotel.welcome.alert.oldstyle` | `0` | `boolean` | Use the legacy welcome alert window style. |
|
||||
| `hotel.wordfilter.automute` | `1` | `boolean` | Mute duration in minutes applied when word-filter automute is triggered. |
|
||||
| `hotel.wordfilter.enabled` | `1` | `boolean` | Enable the word filter system. |
|
||||
| `hotel.wordfilter.messenger` | `1` | `boolean` | Apply the word filter to messenger messages. |
|
||||
| `hotel.wordfilter.normalise` | `1` | `boolean` | Normalise text before checking it against the word filter. |
|
||||
| `hotel.wordfilter.replacement` | `bobba` | `string` | Replacement word used when text is censored. |
|
||||
| `hotel.wordfilter.rooms` | `1` | `boolean` | Apply the word filter to room chat. |
|
||||
|
||||
## `hotelview`
|
||||
|
||||
Hotel-view widgets and promotional data.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `hotelview.halloffame.query` | `SELECT users.look, users.username, users.id, users_settings.hof_points FROM users_settings INNER JOIN users ON users_settings.user_id = users.id WHERE hof_points > 0 ORDER BY hof_points DESC, users.id ASC LIMIT 10` | `sql` | SQL query used to populate the hotel-view hall of fame panel. |
|
||||
| `hotelview.promotional.points` | `100` | `integer` | Amount of activity points awarded by the hotel-view promotion. |
|
||||
| `hotelview.promotional.points.type` | `5` | `integer` | Activity point type used by the hotel-view promotional reward. |
|
||||
| `hotelview.promotional.reward.id` | `11043` | `integer` | Base item ID used by the hotel-view promotional reward. |
|
||||
| `hotelview.promotional.reward.name` | `bonusbag20_2` | `string` | Public item name used by the hotel-view promotional reward. |
|
||||
|
||||
## `imager`
|
||||
|
||||
Internal image generator paths and URLs.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `imager.internal.enabled` | `1` | `boolean` | Generate images locally instead of relying on an external imager service. |
|
||||
| `imager.location.badgeparts` | `/var/www/testhotel/Cosmic/public/usercontent/badgeparts` | `string` | Filesystem path where badge part assets are stored. |
|
||||
| `imager.location.output.badges` | `/var/www/testhotel/Cosmic/public/usercontent/badgeparts/generated/` | `string` | Filesystem output path for generated badges. |
|
||||
| `imager.location.output.camera` | `/var/www/testhotel/Cosmic/public/usercontent/camera/` | `string` | Filesystem output path for saved camera photos. |
|
||||
| `imager.location.output.thumbnail` | `/var/www/testhotel/Cosmic/public/usercontent/camera/thumbnail/` | `string` | Filesystem output path for generated camera thumbnails. |
|
||||
| `imager.url.youtube` | `imager.php?url=http://img.youtube.com/vi/%video%/default.jpg` | `template` | Template URL used to fetch YouTube thumbnails. |
|
||||
|
||||
## `images`
|
||||
|
||||
Static client image path helpers.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `images.gamecenter.basejump` | `c_images/gamecenter_basejump/` | `string` | Client asset path used for the basejump gamecenter images. |
|
||||
| `images.gamecenter.snowwar` | `c_images/gamecenter_snowwar/` | `string` | Client asset path used for the snowwar gamecenter images. |
|
||||
|
||||
## `info`
|
||||
|
||||
Global information panel toggle.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `info.shown` | `1` | `boolean` | Show the hotel information panel or startup information message. |
|
||||
|
||||
## `invisible`
|
||||
|
||||
Invisible-mode behaviour.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `invisible.prevent.chat` | `0` | `boolean` | Prevent invisible users from speaking in rooms. |
|
||||
|
||||
## `io`
|
||||
|
||||
Socket and Netty threading settings.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `io.bossgroup.threads` | `1` | `boolean` | Number of Netty boss-group threads used by the socket server. |
|
||||
| `io.client.multithreaded.handler` | `1` | `boolean` | Handle incoming client packets with a multi-threaded pipeline. |
|
||||
| `io.workergroup.threads` | `5` | `integer` | Number of Netty worker-group threads used by the socket server. |
|
||||
|
||||
## `logging`
|
||||
|
||||
Structured emulator logging toggles.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `logging.debug` | `0` | `boolean` | Enable extra debug logging in the emulator logger. |
|
||||
| `logging.errors.packets` | `0` | `boolean` | Log packet parsing errors. |
|
||||
| `logging.errors.runtime` | `1` | `boolean` | Log runtime exceptions. |
|
||||
| `logging.errors.sql` | `1` | `boolean` | Log SQL errors. |
|
||||
| `logging.packets` | `0` | `boolean` | Log packet traffic in the standard logger. |
|
||||
| `logging.packets.undefined` | `0` | `boolean` | Log undefined packets in the standard logger. |
|
||||
|
||||
## `marketplace`
|
||||
|
||||
Marketplace compatibility flag.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `marketplace.enabled` | `1` | `boolean` | Global switch for the marketplace subsystem. |
|
||||
|
||||
## `monsterplant`
|
||||
|
||||
Monster plant seed item mapping.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `monsterplant.seed.item_id` | `4582` | `integer` | Configuration value used by `monsterplant.seed.item_id`. |
|
||||
| `monsterplant.seed_rare.item_id` | `4604` | `integer` | Configuration value used by `monsterplant.seed_rare.item_id`. |
|
||||
|
||||
## `moodlight`
|
||||
|
||||
Moodlight validation switches.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `moodlight.color_check.enabled` | `1` | `boolean` | Validate moodlight color values before applying them. |
|
||||
|
||||
## `navigator`
|
||||
|
||||
Navigator static definitions.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `navigator.eventcategories` | `1,Hottest Events,false;2,Parties & Music,true;3,Role Play,true;4,Help Desk,true;5,Trading,true;6,Games,true;7,Debates & Discussions,true;8,Grand Openings,true;9,Friending,true;10,Jobs,true;11,Group Events,true` | `list` | Semicolon-separated navigator event category definitions shown in the events tab. |
|
||||
|
||||
## `networking`
|
||||
|
||||
Low-level networking compatibility switches.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `networking.tcp.proxy` | `0` | `boolean` | Enable TCP proxy-aware networking behaviour. |
|
||||
|
||||
## `notify`
|
||||
|
||||
Server-side notification automation.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `notify.staff.chat.auto.report` | `1` | `boolean` | Automatically notify staff when a chat report is created. |
|
||||
|
||||
## `path`
|
||||
|
||||
Asset path helpers.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `path.furniture.icons` | `${image.library.url}/icons/` | `template` | Base path used by the client to load furniture icon assets. |
|
||||
|
||||
## `pathfinder`
|
||||
|
||||
Pathfinder safety and performance settings.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `pathfinder.execution_time.milli` | `25` | `integer` | Maximum pathfinder execution time in milliseconds before aborting. |
|
||||
| `pathfinder.max_execution_time.enabled` | `1` | `boolean` | Enforce the pathfinder execution time limit. |
|
||||
| `pathfinder.step.allow.falling` | `1` | `boolean` | Allow the pathfinder to walk down falling steps. |
|
||||
| `pathfinder.step.maximum.height` | `1.1` | `number` | Maximum height difference the pathfinder may step onto. |
|
||||
|
||||
## `pirate_parrot`
|
||||
|
||||
Pirate parrot text and bubble behaviour.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `pirate_parrot.message.bubble` | `28` | `integer` | Chat bubble style ID used by the pirate parrot. |
|
||||
| `pirate_parrot.message.count` | `6` | `integer` | Number of predefined messages available to the pirate parrot. |
|
||||
|
||||
## `postit`
|
||||
|
||||
Post-it constraints.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `postit.charlimit` | `366` | `integer` | Maximum number of characters allowed on post-it notes. |
|
||||
|
||||
## `pyramids`
|
||||
|
||||
Pyramids minigame timing.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `pyramids.max.delay` | `18` | `integer` | Maximum delay allowed in the Pyramids minigame or puzzle timing. |
|
||||
|
||||
## `retro`
|
||||
|
||||
Retro compatibility switches.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `retro.style.homeroom` | `1` | `boolean` | Use retro-style home room behaviour in the navigator or onboarding flow. |
|
||||
|
||||
## `room`
|
||||
|
||||
Generic room chat and promotion behaviour.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `room.chat.delay` | `0` | `boolean` | Extra room chat delay applied before users can speak again. |
|
||||
| `room.chat.mutearea.allow_whisper` | `1` | `boolean` | Allow whispering while a user stands inside a mute area. |
|
||||
| `room.chat.prefix.format` | `[<font color=\"%color%\">%prefix%</font>] ` | `string` | HTML or text format used for room chat prefixes. |
|
||||
| `room.promotion.badge` | `RADZZ` | `string` | Badge code displayed on promoted rooms. |
|
||||
|
||||
## `rosie`
|
||||
|
||||
Rosie-related client notifications and purchase currency.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `rosie.bubble.image.url` | `${image.library.url}notifications/generic.png` | `template` | Image used by Rosie bubble notifications. |
|
||||
| `rosie.buyroom.currency.type` | `5` | `integer` | Currency type used by Rosie when buying a room or room package. |
|
||||
|
||||
## `runtime`
|
||||
|
||||
Executor and thread sizing.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `runtime.threads` | `8` | `integer` | Configuration value used by `runtime.threads`. |
|
||||
|
||||
## `save`
|
||||
|
||||
Chat persistence toggles.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `save.private.chats` | `1` | `boolean` | Configuration value used by `save.private.chats`. |
|
||||
| `save.room.chats` | `1` | `boolean` | Configuration value used by `save.room.chats`. |
|
||||
|
||||
## `scripter`
|
||||
|
||||
Scripter or modtool integration.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `scripter.modtool.tickets` | `1` | `boolean` | Expose moderation tickets to the scripter or automation tooling. |
|
||||
|
||||
## `seasonal`
|
||||
|
||||
Seasonal currency mapping.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `seasonal.currency.diamond` | `5` | `integer` | Currency type ID used for diamonds. |
|
||||
| `seasonal.currency.ducket` | `0` | `boolean` | Currency type ID used for duckets. |
|
||||
| `seasonal.currency.names` | `ducket;pixel;shell;diamond` | `list` | Semicolon-separated display names for seasonal currency types. |
|
||||
| `seasonal.currency.pixel` | `0` | `boolean` | Currency type ID used for pixels. |
|
||||
| `seasonal.currency.shell` | `4` | `integer` | Currency type ID used for shells. |
|
||||
| `seasonal.primary.type` | `5` | `integer` | Primary seasonal currency type ID. |
|
||||
| `seasonal.types` | `0;1;2;3;4;5;101;102;103;104;105` | `list` | Semicolon-separated list of currency type IDs treated as seasonal currencies. |
|
||||
|
||||
## `subscriptions`
|
||||
|
||||
HC scheduler, payday and discount configuration.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `subscriptions.hc.achievement` | `VipHC` | `string` | Achievement code granted for the HC subscription tier. |
|
||||
| `subscriptions.hc.discount.days_before_end` | `7` | `integer` | Number of days before expiry when HC discount offers become available. |
|
||||
| `subscriptions.hc.discount.enabled` | `1` | `boolean` | Enable discounted HC renewal offers. |
|
||||
| `subscriptions.hc.payday.creditsspent_reset_on_expire` | `1` | `boolean` | Reset tracked credits spent when the HC subscription expires. |
|
||||
| `subscriptions.hc.payday.currency` | `credits` | `string` | Currency rewarded by the HC payday system. |
|
||||
| `subscriptions.hc.payday.enabled` | `1` | `boolean` | Enable the HC payday reward system. |
|
||||
| `subscriptions.hc.payday.interval` | `1 month` | `string` | Date interval used between HC payday reward runs. |
|
||||
| `subscriptions.hc.payday.next_date` | `2020-10-15 00:00:00` | `string` | Next scheduled execution date for HC payday rewards. |
|
||||
| `subscriptions.hc.payday.percentage` | `10` | `integer` | Percentage of eligible spending returned by HC payday. |
|
||||
| `subscriptions.hc.payday.streak` | `7=5;30=10;60=15;90=20;180=25;365=30` | `list` | Semicolon-separated streak thresholds and rewards for HC payday. |
|
||||
| `subscriptions.scheduler.enabled` | `1` | `boolean` | Enable the subscription background scheduler. |
|
||||
| `subscriptions.scheduler.interval` | `10` | `integer` | Interval in minutes between subscription scheduler runs. |
|
||||
|
||||
## `team`
|
||||
|
||||
Compatibility markers for team or wired integrations.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `team.wired.update.rc-1` | `DO NOT REMOVE THIS SETTING!` | `string` | Compatibility marker used by the custom team wired implementation. Do not remove. |
|
||||
|
||||
## `youtube`
|
||||
|
||||
YouTube integration credentials.
|
||||
|
||||
| Key | Default value | Type | Purpose |
|
||||
|---|---|---|---|
|
||||
| `youtube.apikey` | `` | `string` | API key used by the YouTube integration. |
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
# Permissions schema reference
|
||||
|
||||
## Overview
|
||||
|
||||
The legacy `permissions` table stores:
|
||||
|
||||
- one row per rank
|
||||
- one column per permission key
|
||||
|
||||
That works for runtime, but it becomes very hard to read and maintain once the number of permission keys grows.
|
||||
|
||||
The updated design keeps only the rank metadata separated, while the permission matrix itself becomes one readable table:
|
||||
|
||||
- `permission_ranks`
|
||||
- one row per rank
|
||||
- stores rank metadata such as `rank_name`, `badge`, `level`, `prefix`, `room_effect`, and the automatic currency amounts
|
||||
- `permission_definitions`
|
||||
- one row per permission key
|
||||
- stores the permission comment in the same row
|
||||
- stores one column per rank using the format `rank_<id>`
|
||||
|
||||
Example:
|
||||
|
||||
| permission_key | max_value | comment | rank_1 | rank_2 | rank_7 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| `acc_ads_background` | `1` | Allows editing room advertisement backgrounds. | `0` | `0` | `1` |
|
||||
|
||||
## Runtime behavior
|
||||
|
||||
- The emulator still supports the legacy `permissions` table as a fallback.
|
||||
- If `permission_ranks` and `permission_definitions` exist and contain data, the emulator loads the new schema instead.
|
||||
- If the new schema is missing, incomplete, or fails to load, the emulator falls back to the legacy `permissions` table automatically.
|
||||
|
||||
Relevant runtime files:
|
||||
|
||||
- `Arcturus-Morningstar-Extended/Emulator/src/main/java/com/eu/habbo/habbohotel/permissions/PermissionsManager.java:45`
|
||||
- `Arcturus-Morningstar-Extended/Emulator/src/main/java/com/eu/habbo/habbohotel/permissions/Rank.java:71`
|
||||
- `Arcturus-Morningstar-Extended/Emulator/src/main/java/com/eu/habbo/habbohotel/modtool/ModToolManager.java:57`
|
||||
|
||||
## Tables
|
||||
|
||||
### `permission_ranks`
|
||||
|
||||
This table stores only rank metadata:
|
||||
|
||||
- `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`
|
||||
|
||||
#### `permission_ranks` field meanings
|
||||
|
||||
- `id`
|
||||
- Numeric rank id used everywhere else in the emulator, including `users.rank` and the dynamic `rank_<id>` columns in `permission_definitions`.
|
||||
- `rank_name`
|
||||
- Human-readable name of the rank, such as `User`, `Moderator`, or `Administrator`.
|
||||
- `hidden_rank`
|
||||
- When enabled, the rank is treated as hidden in places where staff visibility should be reduced.
|
||||
- `badge`
|
||||
- Badge code automatically associated with the rank.
|
||||
- `job_description`
|
||||
- Staff/job description text shown in features that expose rank profile details.
|
||||
- `staff_color`
|
||||
- Hex color used by staff UI or visuals that depend on the rank color.
|
||||
- `staff_background`
|
||||
- Background asset name used for staff visuals tied to the rank.
|
||||
- `level`
|
||||
- Priority/order value of the rank; higher values usually mean stronger staff level or broader access.
|
||||
- `room_effect`
|
||||
- Default avatar effect id associated with the rank when that feature is used.
|
||||
- `log_commands`
|
||||
- Controls whether commands executed by users with this rank should be logged in command logs.
|
||||
- `prefix`
|
||||
- Short in-room staff prefix/tag associated with the rank.
|
||||
- `prefix_color`
|
||||
- Hex color used for the displayed rank prefix.
|
||||
- `auto_credits_amount`
|
||||
- Automatic credit amount granted by rank-based reward/payday style logic, if used by the hotel.
|
||||
- `auto_pixels_amount`
|
||||
- Automatic duckets/pixels amount granted by rank-based reward/payday style logic, if used by the hotel.
|
||||
- `auto_gotw_amount`
|
||||
- Automatic GOTW-style points amount granted by rank-based reward/payday style logic, if used by the hotel.
|
||||
- `auto_points_amount`
|
||||
- Automatic activity-points amount granted by rank-based reward/payday style logic, if used by the hotel.
|
||||
|
||||
### `permission_definitions`
|
||||
|
||||
This table stores:
|
||||
|
||||
- `permission_key`
|
||||
- `max_value`
|
||||
- `comment`
|
||||
- one dynamic column per rank:
|
||||
- `rank_1`
|
||||
- `rank_2`
|
||||
- `rank_3`
|
||||
- ...
|
||||
|
||||
That means the table itself is already the readable matrix you wanted:
|
||||
|
||||
- rows = permission keys
|
||||
- columns = rank values
|
||||
- comment stays next to the key
|
||||
|
||||
## Value semantics
|
||||
|
||||
Permission values keep the same meaning as today:
|
||||
|
||||
- `0` = disabled
|
||||
- `1` = allowed
|
||||
- `2` = allowed only when room-owner rights may be used
|
||||
|
||||
The schema stores that information in:
|
||||
|
||||
- `permission_definitions.max_value`
|
||||
|
||||
## Migration behavior
|
||||
|
||||
`Database Updates/004_normalize_permissions_schema.sql` does the following:
|
||||
|
||||
1. keeps the legacy `permissions` table untouched
|
||||
2. creates `permission_ranks`
|
||||
3. creates `permission_definitions`
|
||||
4. copies rank metadata from `permissions` into `permission_ranks`
|
||||
5. creates any missing `rank_<id>` columns in `permission_definitions`
|
||||
6. creates one row per permission key with `max_value` and a comment
|
||||
7. applies curated per-key comments so every permission explains what it actually does in code
|
||||
8. copies each old permission value into the proper `rank_<id>` column
|
||||
|
||||
It also removes the older experimental objects if they already exist:
|
||||
|
||||
- `permission_rank_values`
|
||||
- `permission_nodes`
|
||||
- `permissions_matrix_view`
|
||||
- `refresh_permissions_matrix_view`
|
||||
|
||||
## Adding a new rank later
|
||||
|
||||
When you add a new rank after the migration:
|
||||
|
||||
1. insert the rank metadata into `permission_ranks`
|
||||
2. reload permissions with emulator restart or `:update_permissions`
|
||||
3. the emulator automatically creates the missing `rank_<id>` column in `permission_definitions` if it does not exist yet
|
||||
4. set the new `rank_<id>` values in `permission_definitions`
|
||||
|
||||
You can still run the helper procedure manually if you want to sync the schema before the next reload:
|
||||
|
||||
```sql
|
||||
CALL refresh_permission_definition_rank_columns();
|
||||
```
|
||||
|
||||
If you want to refresh all values again from the old legacy table during rollout, you can also run:
|
||||
|
||||
```sql
|
||||
CALL refresh_permission_definition_values();
|
||||
```
|
||||
|
||||
## Notes about comments and legacy keys
|
||||
|
||||
The comments stored in `permission_definitions.comment` are intentionally hand-curated.
|
||||
|
||||
- Where a Java handler exists, the comment follows the real runtime behavior.
|
||||
- Where only legacy command texts exist, the comment marks the key as legacy and explains the intended behavior from those texts.
|
||||
- Where a key is still present for compatibility but no direct handler is found in the current tree, the comment says so explicitly.
|
||||
|
||||
The new schema intentionally preserves legacy and inconsistent permission keys so current functionality stays intact.
|
||||
|
||||
Examples:
|
||||
|
||||
- `cmd_word_quiz`
|
||||
- `cmd_wordquiz`
|
||||
- `cms_dance`
|
||||
- `kiss_cmd`
|
||||
|
||||
Those can be cleaned up later only after runtime behavior has been verified and the hotel no longer depends on the old names.
|
||||
@@ -0,0 +1,382 @@
|
||||
# Wired Bug Audit
|
||||
|
||||
## 1. Scopo
|
||||
|
||||
Questo documento raccoglie i **potenziali bug**, le **aree fragili** e le **incoerenze architetturali** emerse durante l’analisi del sistema wired.
|
||||
|
||||
Non tutti i punti qui sotto sono bug già riprodotti al 100%, ma sono:
|
||||
|
||||
- problemi già visti in comportamento reale
|
||||
- incongruenze tra runtime e UI
|
||||
- zone del codice che possono generare regressioni o risultati non deterministici
|
||||
|
||||
Riferimenti principali:
|
||||
|
||||
- `Arcturus-Morningstar-Extended/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredManager.java`
|
||||
- `Arcturus-Morningstar-Extended/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEngine.java`
|
||||
- `Arcturus-Morningstar-Extended/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredHandler.java`
|
||||
- `Arcturus-Morningstar-Extended/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredMoveCarryHelper.java`
|
||||
- `Arcturus-Morningstar-Extended/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired`
|
||||
|
||||
---
|
||||
|
||||
## 2. Sintesi Priorità
|
||||
|
||||
| Priorità | Tema | Stato |
|
||||
|---|---|---|
|
||||
| Alta | `context` variabili esposto ma non implementato davvero | Incoerenza forte |
|
||||
| Alta | Doppio runtime (`WiredManager` vs `WiredHandler`) | Rischio architetturale |
|
||||
| Alta | Ordine effect non sempre garantito senza extra esplicito | Rischio comportamentale |
|
||||
| Alta | Path movimento legacy può ancora far trapelare update intermedi | Già osservato in stanza |
|
||||
| Media | Tick a `50ms` ma delay wired in step da `500ms` | Semantica non uniforme |
|
||||
| Media | Polling realtime `:wired` a `50ms` | Rischio carico/runtime noise |
|
||||
| Media | `click furni` ora immediato, queue/cancel svuotati | Possibile regressione |
|
||||
| Media | Semantica timestamp variabili non uniforme tra target types | Possibile confusione logica |
|
||||
|
||||
---
|
||||
|
||||
## 3. Audit Dettagliato
|
||||
|
||||
## 3.1 `context` nelle variabili: esposto ma non veramente supportato
|
||||
|
||||
- **Gravità:** Alta
|
||||
- **Confidenza:** Alta
|
||||
- **Area:** effetti/condizioni/extras variabili
|
||||
|
||||
### Problema
|
||||
|
||||
Nel layout e in parte della serializzazione compare il target `context`, ma in più punti il runtime lo rifiuta esplicitamente oppure restituisce direttamente `false`.
|
||||
|
||||
Questo crea una situazione pericolosa:
|
||||
|
||||
- il designer pensa che la feature esista
|
||||
- il box si salva o si configura parzialmente
|
||||
- ma poi in esecuzione non produce il comportamento atteso
|
||||
|
||||
### Evidenze
|
||||
|
||||
- `WiredEffectGiveVariable.java:197`
|
||||
- il save rifiuta `TARGET_CONTEXT`
|
||||
- `WiredConditionVariableValueMatch.java:181`
|
||||
- `case TARGET_CONTEXT -> false`
|
||||
- `WiredConditionVariableAgeMatch.java:146`
|
||||
- `case TARGET_CONTEXT -> false`
|
||||
- `WiredExtraTextOutputVariable.java:83`
|
||||
- il save rifiuta `TARGET_CONTEXT`
|
||||
|
||||
### Impatto pratico
|
||||
|
||||
- stack che sembrano validi in UI ma non funzionano a runtime
|
||||
- falsi negativi nelle condition variabili
|
||||
- placeholder testuali variabili non disponibili quando l’utente si aspetta il target context
|
||||
|
||||
### Fix suggerito
|
||||
|
||||
Scegliere una direzione netta:
|
||||
|
||||
1. **o** implementare davvero `context` in tutti i flow variabili
|
||||
2. **o** rimuoverlo completamente da UI, save e runtime finché non è pronto
|
||||
|
||||
La seconda opzione è la più sicura nel breve periodo.
|
||||
|
||||
---
|
||||
|
||||
## 3.2 Doppio runtime wired ancora presente
|
||||
|
||||
- **Gravità:** Alta
|
||||
- **Confidenza:** Alta
|
||||
- **Area:** architettura core
|
||||
|
||||
### Problema
|
||||
|
||||
`WiredManager` dichiara di essere il runtime esclusivo e tratta i vecchi flag come sola compatibilità, ma `WiredHandler` esiste ancora con entrypoint completi e logica propria.
|
||||
|
||||
### Evidenze
|
||||
|
||||
- `WiredManager.java:136`
|
||||
- warning esplicito: `wired.engine.enabled / wired.engine.exclusive are now compatibility-only flags`
|
||||
- `WiredManager.java:174`
|
||||
- `isEnabled()` dipende solo dall’inizializzazione del manager
|
||||
- `WiredManager.java:182`
|
||||
- `isExclusive()` ritorna sempre `true`
|
||||
- `WiredHandler.java:63`
|
||||
- entrypoint legacy completo `handle(...)`
|
||||
- `WiredHandler.java:114`
|
||||
- supporto separato per `handleCustomTrigger(...)`
|
||||
|
||||
### Impatto pratico
|
||||
|
||||
Se qualunque pezzo di codice, plugin o path legacy entra ancora in `WiredHandler`, si possono avere:
|
||||
|
||||
- ordine effect diverso
|
||||
- scheduling delay diverso
|
||||
- condition flow diverso
|
||||
- diagnostica/monitor non coerente col nuovo engine
|
||||
|
||||
### Fix suggerito
|
||||
|
||||
- definire un solo entrypoint runtime ufficiale
|
||||
- se `WiredHandler` deve restare, trasformarlo in adapter minimo che inoltra sempre al nuovo engine
|
||||
- aggiungere log o metriche per rilevare qualsiasi ingresso nel path legacy
|
||||
|
||||
---
|
||||
|
||||
## 3.3 Ordine degli effect non sempre deterministico senza `wf_xtra_exec_in_order`
|
||||
|
||||
- **Gravità:** Alta
|
||||
- **Confidenza:** Alta
|
||||
- **Area:** esecuzione stack
|
||||
|
||||
### Problema
|
||||
|
||||
Nel path legacy, l’ordinamento stabile viene applicato chiaramente solo in presenza di `wf_xtra_exec_in_order` oppure in casi specifici (`unseen`).
|
||||
|
||||
Negli altri casi, l’ordine si appoggia alla collezione che arriva dal runtime.
|
||||
|
||||
### Evidenze
|
||||
|
||||
- `WiredHandler.java:224`
|
||||
- rileva `hasExtraExecuteInOrder`
|
||||
- `WiredHandler.java:230`
|
||||
- ordina con `WiredExecutionOrderUtil.sort(effects)` solo in alcuni casi
|
||||
- `WiredHandler.java:249`
|
||||
- usa direttamente `effectList` in ordered mode
|
||||
|
||||
### Impatto pratico
|
||||
|
||||
Stack come:
|
||||
|
||||
- `move_rotate` + `match_to_sshot`
|
||||
- `toggle` + `reset`
|
||||
- `give_var` + `change_var_val`
|
||||
|
||||
possono produrre risultati diversi se si assume implicitamente un ordine che il runtime non promette davvero.
|
||||
|
||||
### Fix suggerito
|
||||
|
||||
- decidere se l’ordine stack deve essere sempre stabile di default
|
||||
- in alternativa, mantenere la regola attuale ma documentarla in modo molto esplicito
|
||||
- se si lascia la regola attuale, conviene segnalare in UI che l’ordine è garantito solo con `wf_xtra_exec_in_order`
|
||||
|
||||
---
|
||||
|
||||
## 3.4 Il path movimento legacy può ancora far vedere movimenti intermedi
|
||||
|
||||
- **Gravità:** Alta
|
||||
- **Confidenza:** Alta
|
||||
- **Area:** movement pipeline
|
||||
|
||||
### Problema
|
||||
|
||||
Il helper legacy di movimento usa ancora un fallback che, se il collector non è attivo, invia subito `FloorItemOnRollerComposer`.
|
||||
|
||||
Questo può far trapelare al client uno stato intermedio che in teoria avrebbe dovuto essere nascosto da batching o restore finale.
|
||||
|
||||
### Evidenze
|
||||
|
||||
- `WiredMoveCarryHelper.java:163`
|
||||
- metodo `moveFurniLegacy(...)`
|
||||
- `WiredMoveCarryHelper.java:179`
|
||||
- usa il collector se disponibile
|
||||
- `WiredMoveCarryHelper.java:196`
|
||||
- fallback diretto a `FloorItemOnRollerComposer`
|
||||
|
||||
### Impatto pratico
|
||||
|
||||
È coerente con il tipo di bug già visto:
|
||||
|
||||
- oggetto che “si vede muovere”
|
||||
- poi viene riportato nello stato corretto
|
||||
- ma il client ha già ricevuto un update intermedio
|
||||
|
||||
### Fix suggerito
|
||||
|
||||
- evitare qualsiasi composer diretto nel path legacy quando la logica wired moderna è attiva
|
||||
- centralizzare tutti i movement update in un unico collector finale
|
||||
- aggiungere test specifici per:
|
||||
- `move_rotate` + `match_to_sshot`
|
||||
- stacked move effects nello stesso tick
|
||||
|
||||
---
|
||||
|
||||
## 3.5 Tick a `50ms`, ma delay wired ancora a step da `500ms`
|
||||
|
||||
- **Gravità:** Media
|
||||
- **Confidenza:** Alta
|
||||
- **Area:** semantica temporale
|
||||
|
||||
### Problema
|
||||
|
||||
Il sistema oggi ha due granularità temporali diverse:
|
||||
|
||||
- repeaters / tickables a `50ms`
|
||||
- delay wired classico a `delay * 500ms`
|
||||
|
||||
### Evidenze
|
||||
|
||||
- `WiredTickService.java:48`
|
||||
- `DEFAULT_TICK_INTERVAL_MS = 50`
|
||||
- `WiredTickService.java:175`
|
||||
- `scheduleAtFixedRate(...)`
|
||||
- `WiredEngine.java:753`
|
||||
- `long delayMs = delay * 500L`
|
||||
- `WiredHandler.java:369`
|
||||
- stesso schema `delay * 500L`
|
||||
|
||||
### Impatto pratico
|
||||
|
||||
Non è per forza un bug, ma può creare:
|
||||
|
||||
- aspettative sbagliate nel builder dei wired
|
||||
- sensazione di desync tra repeater e delay
|
||||
- stack “velocissimi” su tick ma “grossolani” sugli effect ritardati
|
||||
|
||||
### Fix suggerito
|
||||
|
||||
- o si accetta questa doppia semantica e la si documenta ovunque
|
||||
- o si introduce una nuova famiglia di delay high-resolution separata dal delay classico
|
||||
|
||||
---
|
||||
|
||||
## 3.6 `:wired` realtime a `50ms` può diventare rumoroso/pesante
|
||||
|
||||
- **Gravità:** Media
|
||||
- **Confidenza:** Alta
|
||||
- **Area:** tooling monitor/inspection
|
||||
|
||||
### Problema
|
||||
|
||||
Le request di monitor e variabili ora sono rate-limitate a `50ms`.
|
||||
|
||||
### Evidenze
|
||||
|
||||
- `WiredMonitorRequestEvent.java:39`
|
||||
- `return 50`
|
||||
- `WiredUserVariablesRequestEvent.java:20`
|
||||
- `return 50`
|
||||
|
||||
### Impatto pratico
|
||||
|
||||
Su una stanza attiva o con più client staff aperti:
|
||||
|
||||
- carico rete maggiore
|
||||
- più rumore sul server
|
||||
- rischio di mascherare problemi reali con spam di refresh
|
||||
|
||||
### Fix suggerito
|
||||
|
||||
- spostare dove possibile a push/event driven
|
||||
- lasciare `50ms` solo per il minimo indispensabile
|
||||
- differenziare:
|
||||
- monitor heavy/debug
|
||||
- inspection live
|
||||
- variables snapshot
|
||||
|
||||
---
|
||||
|
||||
## 3.7 `click furni` ora è immediato: queue/cancel svuotati
|
||||
|
||||
- **Gravità:** Media
|
||||
- **Confidenza:** Alta
|
||||
- **Area:** eventi click furni
|
||||
|
||||
### Problema
|
||||
|
||||
La queue dei click furni è stata semplificata: ora il click parte subito, e il cancel path è vuoto.
|
||||
|
||||
### Evidenze
|
||||
|
||||
- `WiredManager.java:274`
|
||||
- `queueUserClicksFurni(...)` chiama subito `triggerUserClicksFurni(...)`
|
||||
- `WiredManager.java:282`
|
||||
- `cancelPendingUserClicksFurni(...)` non fa nulla
|
||||
|
||||
### Impatto pratico
|
||||
|
||||
Se qualche comportamento vecchio dipendeva da:
|
||||
|
||||
- debounce
|
||||
- cancel
|
||||
- click differito
|
||||
|
||||
ora può cambiare senza che il mapping sia ovvio.
|
||||
|
||||
### Fix suggerito
|
||||
|
||||
- decidere se il comportamento immediato è quello definitivo
|
||||
- se sì, documentarlo come breaking behavior
|
||||
- se no, reintrodurre una queue reale con semantica esplicita
|
||||
|
||||
---
|
||||
|
||||
## 3.8 Semantica timestamp variabili non uniforme tra target type
|
||||
|
||||
- **Gravità:** Media
|
||||
- **Confidenza:** Media
|
||||
- **Area:** sistema variabili
|
||||
|
||||
### Problema
|
||||
|
||||
Le variabili utente e furni hanno senso come “assegnazione con creation/update time”, mentre le room/global variables hanno soprattutto senso sul solo `update time`.
|
||||
|
||||
Questo può diventare ambiguo quando si usano:
|
||||
|
||||
- `wf_cnd_var_age_match`
|
||||
- sorting per creation/update
|
||||
- UI manage/inspection
|
||||
|
||||
### Evidenze
|
||||
|
||||
- `WiredConditionVariableAgeMatch.java`
|
||||
- il target room/global vive soprattutto come valore di update
|
||||
- le scelte di prodotto già fatte in `:wired` vanno in questa direzione
|
||||
|
||||
### Impatto pratico
|
||||
|
||||
- il builder può pensare che “tempo di creazione” sulle global sia forte quanto sulle user/furni
|
||||
- condition o sort possono essere semanticamente strani anche se “funzionano”
|
||||
|
||||
### Fix suggerito
|
||||
|
||||
- trattare esplicitamente `room/global` come `updated-only`
|
||||
- disabilitare in UI le opzioni che non hanno senso forte
|
||||
- o documentare in modo molto chiaro la differenza
|
||||
|
||||
---
|
||||
|
||||
## 4. Backlog Consigliato
|
||||
|
||||
Ordine suggerito di intervento:
|
||||
|
||||
1. **Chiudere il target `context`**
|
||||
- o implementarlo davvero
|
||||
- o toglierlo da UI/save/runtime
|
||||
2. **Unificare il runtime**
|
||||
- lasciare un solo entrypoint ufficiale
|
||||
3. **Stabilire la regola sull’ordine effect**
|
||||
- default stabile o ordine esplicito con extra
|
||||
4. **Chiudere il leak dei movement update legacy**
|
||||
- niente composer fuori collector quando wired moderno è attivo
|
||||
5. **Ripensare il realtime di `:wired`**
|
||||
- spostare il più possibile da polling a push
|
||||
|
||||
---
|
||||
|
||||
## 5. Nota Finale
|
||||
|
||||
Il sistema wired attuale è già molto più potente del modello classico, soprattutto per:
|
||||
|
||||
- variabili
|
||||
- signal routing
|
||||
- selectors avanzati
|
||||
- monitor
|
||||
- manage/inspection
|
||||
|
||||
Proprio per questo, le zone fragili oggi non sono tanto i box semplici, ma:
|
||||
|
||||
- la coesistenza di due runtime
|
||||
- la semantica temporale
|
||||
- i movement stack
|
||||
- le feature variabili ancora “mezze esposte”
|
||||
|
||||
Questi sono i punti che più probabilmente spiegano i bug strani o intermittenti.
|
||||
@@ -0,0 +1,500 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Riferimento Completo Wired</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
wired: {
|
||||
50: '#eef7ff',
|
||||
100: '#d9ecff',
|
||||
200: '#b8dbff',
|
||||
300: '#89c0ff',
|
||||
400: '#569dff',
|
||||
500: '#2d77ff',
|
||||
600: '#1f5ee5',
|
||||
700: '#1d4dcb',
|
||||
800: '#1e42a4',
|
||||
900: '#1d397f'
|
||||
}
|
||||
},
|
||||
boxShadow: {
|
||||
wired: '0 20px 50px rgba(15, 23, 42, 0.14)'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<style>
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(45, 119, 255, 0.12), transparent 35%),
|
||||
radial-gradient(circle at top right, rgba(14, 165, 233, 0.10), transparent 30%),
|
||||
linear-gradient(180deg, #f8fbff 0%, #f1f5f9 100%);
|
||||
}
|
||||
|
||||
.doc-content h1,
|
||||
.doc-content h2,
|
||||
.doc-content h3,
|
||||
.doc-content h4 {
|
||||
scroll-margin-top: 110px;
|
||||
}
|
||||
|
||||
.doc-content h1 {
|
||||
font-size: 2.5rem;
|
||||
line-height: 1.1;
|
||||
font-weight: 800;
|
||||
color: #0f172a;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.doc-content h2 {
|
||||
font-size: 1.65rem;
|
||||
line-height: 1.2;
|
||||
font-weight: 800;
|
||||
color: #0f172a;
|
||||
margin-top: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.6rem;
|
||||
border-bottom: 1px solid #dbeafe;
|
||||
}
|
||||
|
||||
.doc-content h3 {
|
||||
font-size: 1.15rem;
|
||||
line-height: 1.45;
|
||||
font-weight: 700;
|
||||
color: #1e3a8a;
|
||||
margin-top: 1.75rem;
|
||||
margin-bottom: 0.85rem;
|
||||
background: #eff6ff;
|
||||
border: 1px solid #dbeafe;
|
||||
border-radius: 0.9rem;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.doc-content h4 {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: #1e293b;
|
||||
margin-top: 1.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.doc-content p,
|
||||
.doc-content li {
|
||||
color: #334155;
|
||||
line-height: 1.78;
|
||||
}
|
||||
|
||||
.doc-content p {
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
.doc-content ul,
|
||||
.doc-content ol {
|
||||
margin: 0.85rem 0 1rem 1.4rem;
|
||||
}
|
||||
|
||||
.doc-content ul {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
.doc-content ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
.doc-content li {
|
||||
margin: 0.35rem 0;
|
||||
padding-left: 0.2rem;
|
||||
}
|
||||
|
||||
.doc-content hr {
|
||||
border: 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, #bfdbfe, transparent);
|
||||
margin: 2.25rem 0;
|
||||
}
|
||||
|
||||
.doc-content code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
padding: 0.16rem 0.38rem;
|
||||
border-radius: 0.45rem;
|
||||
font-size: 0.92em;
|
||||
}
|
||||
|
||||
.doc-content pre {
|
||||
background: #0f172a;
|
||||
color: #e2e8f0;
|
||||
padding: 1rem 1.1rem;
|
||||
border-radius: 1rem;
|
||||
overflow-x: auto;
|
||||
margin: 1rem 0 1.25rem;
|
||||
box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.15);
|
||||
}
|
||||
|
||||
.doc-content pre code {
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.doc-content table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1rem 0 1.5rem;
|
||||
overflow: hidden;
|
||||
border-radius: 1rem;
|
||||
box-shadow: inset 0 0 0 1px #dbeafe;
|
||||
}
|
||||
|
||||
.doc-content thead tr {
|
||||
background: #dbeafe;
|
||||
}
|
||||
|
||||
.doc-content th,
|
||||
.doc-content td {
|
||||
text-align: left;
|
||||
padding: 0.8rem 0.95rem;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
color: #334155;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.doc-content th {
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.doc-content tbody tr:nth-child(even) {
|
||||
background: #f8fbff;
|
||||
}
|
||||
|
||||
.doc-content blockquote {
|
||||
margin: 1rem 0;
|
||||
padding: 0.9rem 1rem;
|
||||
border-left: 4px solid #60a5fa;
|
||||
background: #f8fbff;
|
||||
border-radius: 0 0.9rem 0.9rem 0;
|
||||
}
|
||||
|
||||
.toc-link.active {
|
||||
background: #dbeafe;
|
||||
color: #1d4ed8;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="min-h-screen text-slate-800">
|
||||
<div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
|
||||
<header class="mb-6 rounded-3xl border border-white/70 bg-white/85 p-6 shadow-wired backdrop-blur">
|
||||
<div class="flex flex-col gap-6 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div class="max-w-3xl">
|
||||
<div class="mb-3 inline-flex items-center gap-2 rounded-full border border-wired-200 bg-wired-50 px-3 py-1 text-xs font-semibold uppercase tracking-[0.18em] text-wired-700">
|
||||
<span class="h-2 w-2 rounded-full bg-wired-500"></span>
|
||||
Documentazione tecnica Wired
|
||||
</div>
|
||||
<h1 class="text-3xl font-extrabold tracking-tight text-slate-900 sm:text-4xl">
|
||||
Riferimento Completo Wired
|
||||
</h1>
|
||||
<p class="mt-3 text-sm leading-7 text-slate-600 sm:text-base">
|
||||
Questa pagina renderizza <code>wired_full_reference.md</code> in una vista HTML consultabile,
|
||||
con struttura e interfaccia in italiano. Gli identificatori tecnici dei wired, delle classi e
|
||||
delle chiavi restano invariati per mantenere la documentazione fedele al runtime.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid gap-3 sm:grid-cols-3 lg:w-[28rem]">
|
||||
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-4">
|
||||
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Sorgente</div>
|
||||
<div class="mt-1 text-sm font-medium text-slate-800">Markdown vivo</div>
|
||||
<div class="mt-1 text-xs text-slate-500">La pagina legge il file `.md` locale.</div>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-4">
|
||||
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Lingua</div>
|
||||
<div class="mt-1 text-sm font-medium text-slate-800">Interfaccia italiana</div>
|
||||
<div class="mt-1 text-xs text-slate-500">Struttura e metadati localizzati.</div>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-4">
|
||||
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500">Stile</div>
|
||||
<div class="mt-1 text-sm font-medium text-slate-800">Tailwind CDN</div>
|
||||
<div class="mt-1 text-xs text-slate-500">Layout leggibile, sticky nav e indice.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-[19rem,minmax(0,1fr)]">
|
||||
<aside class="lg:sticky lg:top-6 lg:self-start">
|
||||
<div class="rounded-3xl border border-white/70 bg-white/90 p-4 shadow-wired backdrop-blur">
|
||||
<div class="mb-4">
|
||||
<label for="toc-search" class="mb-2 block text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">
|
||||
Cerca sezione
|
||||
</label>
|
||||
<input
|
||||
id="toc-search"
|
||||
type="text"
|
||||
placeholder="Es. variabili, trigger, signal..."
|
||||
class="w-full rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-700 outline-none transition focus:border-wired-300 focus:bg-white focus:ring-2 focus:ring-wired-100"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-4 rounded-2xl border border-amber-200 bg-amber-50/80 p-3 text-xs leading-6 text-amber-900">
|
||||
<div class="font-semibold">Nota</div>
|
||||
<div>
|
||||
La pagina mantiene nomi wired, classi Java e chiavi tecniche così come sono nel progetto.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold text-slate-900">Indice</h2>
|
||||
<a
|
||||
href="./wired_full_reference.md"
|
||||
class="rounded-full border border-slate-200 px-3 py-1 text-xs font-medium text-slate-600 transition hover:border-wired-300 hover:text-wired-700"
|
||||
>
|
||||
Apri Markdown
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<nav id="toc" class="space-y-1 text-sm"></nav>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main>
|
||||
<section class="mb-6 rounded-3xl border border-white/70 bg-white/90 p-5 shadow-wired backdrop-blur">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h2 class="text-lg font-bold text-slate-900">Panoramica</h2>
|
||||
<p class="mt-1 text-sm leading-7 text-slate-600">
|
||||
Trovi all’inizio le regole del motore wired, poi il catalogo completo di trigger, effect,
|
||||
selector, condition, extra e variabili.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2 text-xs font-medium">
|
||||
<span class="rounded-full bg-wired-50 px-3 py-2 text-wired-700">Engine + Tick</span>
|
||||
<span class="rounded-full bg-emerald-50 px-3 py-2 text-emerald-700">154 wired catalogati</span>
|
||||
<span class="rounded-full bg-fuchsia-50 px-3 py-2 text-fuchsia-700">HTML da Markdown</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<article class="rounded-3xl border border-white/70 bg-white/95 p-6 shadow-wired backdrop-blur sm:p-8">
|
||||
<div id="loading" class="rounded-2xl border border-slate-200 bg-slate-50 p-5 text-sm text-slate-600">
|
||||
Caricamento della reference in corso...
|
||||
</div>
|
||||
<div id="error" class="hidden rounded-2xl border border-rose-200 bg-rose-50 p-5 text-sm text-rose-700"></div>
|
||||
<div id="content" class="doc-content hidden"></div>
|
||||
</article>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const headingTranslations = new Map([
|
||||
['# Wired Full Reference', '# Riferimento Completo Wired'],
|
||||
['## 1. Scope', '## 1. Ambito'],
|
||||
['## 2. Wired Engine, Tick, and General Runtime Rules', '## 2. Motore Wired, Tick e Regole Generali del Runtime'],
|
||||
['## 3. Triggers', '## 3. Trigger'],
|
||||
['## 4. Effects', '## 4. Effetti'],
|
||||
['## 5. Selectors', '## 5. Selettori'],
|
||||
['## 6. Conditions', '## 6. Condizioni'],
|
||||
['## 7. Extras', '## 7. Extra'],
|
||||
['## 8. Variable Definitions', '## 8. Definizioni Variabili'],
|
||||
['## 9. Special Wired Items', '## 9. Elementi Wired Speciali'],
|
||||
['## 10. Practical Design Notes', '## 10. Note Pratiche di Progettazione'],
|
||||
['## 11. Quick Alias / Shared Runtime Notes', '## 11. Alias Rapidi e Note sul Runtime Condiviso'],
|
||||
['### General selector notes', '### Note generali sui selettori'],
|
||||
['### General condition notes', '### Note generali sulle condizioni']
|
||||
]);
|
||||
|
||||
const lineTranslations = [
|
||||
[/^- \*\*Class:\*\*/g, '- **Classe:**'],
|
||||
[/^- \*\*Behavior:\*\*/g, '- **Comportamento:**'],
|
||||
[/^- \*\*Main settings:\*\*/g, '- **Impostazioni principali:**'],
|
||||
[/^- \*\*Notes:\*\*/g, '- **Note:**'],
|
||||
[/^- \*\*Supported operations:\*\*/g, '- **Operazioni supportate:**'],
|
||||
[/^- \*\*Supported sort modes:\*\*/g, '- **Modalità di ordinamento supportate:**']
|
||||
];
|
||||
|
||||
const paragraphTranslations = new Map([
|
||||
['This document is a code-based reference for the current wired runtime in `Arcturus-Morningstar-Extended`.', 'Questo documento è un riferimento tecnico basato sul codice per il runtime wired attuale di `Arcturus-Morningstar-Extended`.'],
|
||||
['It covers:', 'Copre:'],
|
||||
['Primary runtime sources used for this reference:', 'Sorgenti principali del runtime usate per questa reference:'],
|
||||
['This file is meant to describe the runtime behavior and configuration surface, not the Nitro UI layout in detail. For `:wired` monitor and inspection tooling, also see `Arcturus-Morningstar-Extended/docs/wired_tools_reference.md`.', 'Questo file descrive il comportamento del runtime e la superficie di configurazione, non il layout Nitro nel dettaglio. Per gli strumenti `:wired` di monitor e inspection, vedi anche `Arcturus-Morningstar-Extended/docs/wired_tools_reference.md`.'],
|
||||
['The modern wired runtime is centered around these components:', 'Il runtime wired moderno ruota attorno a questi componenti:'],
|
||||
['At a high level, the engine processes a stack like this:', 'A livello alto, il motore processa uno stack in questo modo:'],
|
||||
['Important consequences of this model:', 'Conseguenze importanti di questo modello:'],
|
||||
['The centralized tick service is defined in `WiredTickService`.', 'Il servizio tick centralizzato è definito in `WiredTickService`.'],
|
||||
['Current core rules:', 'Regole principali attuali:'],
|
||||
['This means:', 'Questo significa:'],
|
||||
['The classic wired delay value is stored in half-second steps.', 'Il valore classico del delay wired è memorizzato in step da mezzo secondo.'],
|
||||
['Runtime rule:', 'Regola runtime:'],
|
||||
['Examples:', 'Esempi:'],
|
||||
['There are several separate notions of order:', 'Esistono più concetti distinti di ordine di esecuzione:'],
|
||||
['Practical takeaway:', 'Conclusione pratica:'],
|
||||
['Selectors build or refine the `WiredTargets` inside `WiredContext`.', 'I selettori costruiscono o raffinano i `WiredTargets` dentro `WiredContext`.'],
|
||||
['In practice:', 'In pratica:'],
|
||||
['Conditions are evaluated after selectors.', 'Le condizioni vengono valutate dopo i selettori.'],
|
||||
['General behavior:', 'Comportamento generale:'],
|
||||
['The runtime supports both:', 'Il runtime supporta sia:'],
|
||||
['The wired runtime has multiple safety layers:', 'Il runtime wired ha più livelli di protezione:'],
|
||||
['Main defaults from runtime/config:', 'Default principali da runtime/config:'],
|
||||
['The new engine tracks room diagnostics through `WiredRoomDiagnostics`.', 'Il nuovo motore traccia le diagnostiche stanza tramite `WiredRoomDiagnostics`.'],
|
||||
['This is where `:wired` monitor gets values such as:', 'È da qui che il monitor `:wired` ottiene valori come:'],
|
||||
['Heavy/overload decisions are based on rolling windows, not on a single event.', 'Le decisioni di heavy/overload si basano su finestre scorrevoli, non su un singolo evento.'],
|
||||
['The project still contains `WiredHandler`.', 'Il progetto contiene ancora `WiredHandler`.'],
|
||||
['Important practical notes:', 'Note pratiche importanti:'],
|
||||
['So when documenting stacks, it is best to think in terms of:', 'Quindi, quando si documentano gli stack, conviene ragionare in termini di:'],
|
||||
['Custom wired variables are defined by:', 'Le variabili wired custom sono definite da:'],
|
||||
['Shared rules:', 'Regole condivise:'],
|
||||
['Availability rules:', 'Regole di disponibilità:'],
|
||||
['Timestamp rules:', 'Regole sui timestamp:'],
|
||||
['Current context-status note:', 'Nota sullo stato del contesto:'],
|
||||
['These are part of the wired ecosystem, even if they are not regular trigger/effect/selector/condition/extra boxes.', 'Questi fanno parte dell’ecosistema wired anche se non sono box classici trigger/effect/selector/condition/extra.'],
|
||||
['Use:', 'Usa:'],
|
||||
['Remember:', 'Ricorda:'],
|
||||
['Check:', 'Controlla:']
|
||||
]);
|
||||
|
||||
function normalizeText(raw) {
|
||||
return raw
|
||||
.replace(/“/g, '“')
|
||||
.replace(/â€/g, '”')
|
||||
.replace(/’/g, '’')
|
||||
.replace(/–/g, '–')
|
||||
.replace(/—/g, '—')
|
||||
.replace(/…/g, '…');
|
||||
}
|
||||
|
||||
function localizeMarkdown(raw) {
|
||||
let output = normalizeText(raw);
|
||||
|
||||
headingTranslations.forEach((translated, original) => {
|
||||
output = output.replace(original, translated);
|
||||
});
|
||||
|
||||
paragraphTranslations.forEach((translated, original) => {
|
||||
output = output.replaceAll(original, translated);
|
||||
});
|
||||
|
||||
lineTranslations.forEach(([regex, replacement]) => {
|
||||
output = output.replace(regex, replacement);
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function slugify(text) {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.replace(/[`"'’“”]/g, '')
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
}
|
||||
|
||||
function buildToc() {
|
||||
const toc = document.getElementById('toc');
|
||||
const headings = [...document.querySelectorAll('#content h2, #content h3')];
|
||||
|
||||
toc.innerHTML = '';
|
||||
|
||||
headings.forEach((heading) => {
|
||||
const text = heading.textContent.trim();
|
||||
const id = slugify(text);
|
||||
heading.id = id;
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = `#${id}`;
|
||||
link.textContent = text;
|
||||
link.className =
|
||||
`toc-link block rounded-2xl px-3 py-2 transition hover:bg-slate-100 hover:text-wired-700 ${
|
||||
heading.tagName === 'H3' ? 'ml-3 text-xs text-slate-500' : 'text-sm font-medium text-slate-700'
|
||||
}`;
|
||||
|
||||
toc.appendChild(link);
|
||||
});
|
||||
}
|
||||
|
||||
function wireSearch() {
|
||||
const search = document.getElementById('toc-search');
|
||||
const toc = document.getElementById('toc');
|
||||
|
||||
search.addEventListener('input', () => {
|
||||
const needle = search.value.trim().toLowerCase();
|
||||
const links = [...toc.querySelectorAll('a')];
|
||||
|
||||
links.forEach((link) => {
|
||||
const visible = !needle || link.textContent.toLowerCase().includes(needle);
|
||||
link.classList.toggle('hidden', !visible);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function wireActiveSection() {
|
||||
const links = [...document.querySelectorAll('#toc a')];
|
||||
const headingMap = links
|
||||
.map((link) => {
|
||||
const target = document.querySelector(link.getAttribute('href'));
|
||||
return target ? { link, target } : null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
const item = headingMap.find(({ target }) => target === entry.target);
|
||||
if (!item) return;
|
||||
item.link.classList.toggle('active', entry.isIntersecting);
|
||||
});
|
||||
},
|
||||
{ rootMargin: '-20% 0px -70% 0px', threshold: 0 }
|
||||
);
|
||||
|
||||
headingMap.forEach(({ target }) => observer.observe(target));
|
||||
}
|
||||
|
||||
async function loadReference() {
|
||||
const loading = document.getElementById('loading');
|
||||
const error = document.getElementById('error');
|
||||
const content = document.getElementById('content');
|
||||
|
||||
try {
|
||||
const response = await fetch('./wired_full_reference.md');
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const raw = await response.text();
|
||||
const localized = localizeMarkdown(raw);
|
||||
const html = marked.parse(localized, { mangle: false, headerIds: false });
|
||||
|
||||
content.innerHTML = html;
|
||||
content.classList.remove('hidden');
|
||||
loading.classList.add('hidden');
|
||||
|
||||
buildToc();
|
||||
wireSearch();
|
||||
wireActiveSection();
|
||||
} catch (err) {
|
||||
loading.classList.add('hidden');
|
||||
error.classList.remove('hidden');
|
||||
error.textContent = `Impossibile caricare wired_full_reference.md (${err.message}).`;
|
||||
}
|
||||
}
|
||||
|
||||
loadReference();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,283 @@
|
||||
# Wired Creator Tools Implementation Summary
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
This document summarizes the `:wired` work completed in this development cycle.
|
||||
|
||||
It is intended as a project-facing summary of:
|
||||
|
||||
- what was added
|
||||
- where it lives
|
||||
- what is already working
|
||||
- what is still intentionally left as future work
|
||||
|
||||
---
|
||||
|
||||
## 2. Main goals completed
|
||||
|
||||
The current `:wired` implementation now provides:
|
||||
|
||||
1. a dedicated Nitro UI window
|
||||
2. monitor and inspection tooling
|
||||
3. room/user/furni/global variable views
|
||||
4. inline editing for selected values
|
||||
5. live wired diagnostics from the server
|
||||
6. error/warning history with details
|
||||
7. server-side diagnostics configuration through DB settings
|
||||
|
||||
---
|
||||
|
||||
## 3. Nitro-V3 work
|
||||
|
||||
Main file:
|
||||
|
||||
- `Nitro-V3/src/components/wired-tools/WiredCreatorToolsView.tsx`
|
||||
|
||||
### 3.1 UI window
|
||||
|
||||
The `:wired` tool now has these main tabs:
|
||||
|
||||
- `Monitor`
|
||||
- `Variables`
|
||||
- `Inspection`
|
||||
- `Chests`
|
||||
- `Settings`
|
||||
|
||||
Current active work is mainly in:
|
||||
|
||||
- `Monitor`
|
||||
- `Inspection`
|
||||
|
||||
`Chests` and `Settings` are currently placeholder/future-facing areas.
|
||||
|
||||
### 3.2 Inspection
|
||||
|
||||
Implemented:
|
||||
|
||||
- element type switcher (`furni`, `user`, `global`)
|
||||
- preview area
|
||||
- variable table
|
||||
- `Keep selected`
|
||||
- inline editing
|
||||
|
||||
#### Furni
|
||||
|
||||
Added support for:
|
||||
|
||||
- detailed furni variables
|
||||
- live preview
|
||||
- wall/floor-specific handling
|
||||
- teleport metadata
|
||||
- inline edits for state/position/rotation/altitude/wall offset
|
||||
|
||||
#### User
|
||||
|
||||
Added support for:
|
||||
|
||||
- user/bot/pet identity
|
||||
- rights / owner / group admin flags
|
||||
- mute / trading / frozen flags
|
||||
- team / sign / dance / idle / hand item / effect display
|
||||
- room entry method and teleport entry id
|
||||
- inline edits for position and direction
|
||||
|
||||
#### Global
|
||||
|
||||
Added support for:
|
||||
|
||||
- room counts
|
||||
- wired timer
|
||||
- team scores and sizes
|
||||
- room/group ids
|
||||
- server/client timezone
|
||||
- current server time breakdown
|
||||
|
||||
### 3.3 Monitor
|
||||
|
||||
Implemented:
|
||||
|
||||
- live stats table
|
||||
- log summary list
|
||||
- full log history
|
||||
- info/documentation popup
|
||||
- error information popup
|
||||
|
||||
The auxiliary monitor windows now use proper Nitro card windows, so they are:
|
||||
|
||||
- draggable
|
||||
- resizable
|
||||
- closed through the normal Nitro close button
|
||||
|
||||
---
|
||||
|
||||
## 4. Nitro_Render_V3 work
|
||||
|
||||
Renderer-side work was focused on making Nitro receive enough metadata for the new UI.
|
||||
|
||||
Main areas:
|
||||
|
||||
- new wired monitor packet parsing
|
||||
- room/session metadata extensions
|
||||
- furni metadata extensions
|
||||
- user metadata extensions
|
||||
|
||||
### 4.1 Monitor data
|
||||
|
||||
The renderer now parses and exposes:
|
||||
|
||||
- usage budget values
|
||||
- delayed queue values
|
||||
- execution timing values
|
||||
- heavy/overload thresholds
|
||||
- current logs
|
||||
- history rows
|
||||
|
||||
### 4.2 Room metadata
|
||||
|
||||
The renderer/session flow was extended to expose values used by Nitro:
|
||||
|
||||
- room furni limit
|
||||
- room group id
|
||||
- hotel timezone / hotel time snapshot
|
||||
|
||||
### 4.3 Furni metadata
|
||||
|
||||
The furni info path now exposes values used by the inspector, including:
|
||||
|
||||
- dimensions
|
||||
- `items_base`-driven flags such as sit/lay/stand/stack
|
||||
- teleport target metadata
|
||||
|
||||
### 4.4 User metadata
|
||||
|
||||
The user/unit data path now exposes values used by the inspector, including:
|
||||
|
||||
- room entry method
|
||||
- room entry teleport id
|
||||
- identity data for user/bot/pet
|
||||
|
||||
---
|
||||
|
||||
## 5. Emulator work
|
||||
|
||||
Main areas:
|
||||
|
||||
- wired diagnostics engine
|
||||
- monitor request/response packet
|
||||
- room/user/furni metadata support
|
||||
- configuration migration to `wired_emulator_settings`
|
||||
|
||||
### 5.1 Wired diagnostics
|
||||
|
||||
Added server-side room diagnostics with:
|
||||
|
||||
- usage budget tracking
|
||||
- delayed event queue tracking
|
||||
- average/peak execution timing
|
||||
- overload detection
|
||||
- heavy-room detection
|
||||
- recursion protection logging
|
||||
- killed-room protection logging
|
||||
|
||||
### 5.2 Diagnostics logs
|
||||
|
||||
Logs now carry:
|
||||
|
||||
- type
|
||||
- severity
|
||||
- count
|
||||
- reason
|
||||
- source label
|
||||
- source id
|
||||
- history entries with occurrence timestamps
|
||||
|
||||
### 5.3 Trigger/runtime fixes
|
||||
|
||||
Important behaviour fixes added during this work:
|
||||
|
||||
- empty repeater stacks no longer count as executable work
|
||||
- monitor usage is consumed later in the execution path, closer to real execution
|
||||
- timer/repeater behaviour is less noisy in diagnostics
|
||||
|
||||
### 5.4 Monitor packet
|
||||
|
||||
A dedicated request/response path was added so Nitro can poll live room diagnostics.
|
||||
|
||||
### 5.5 Configuration migration
|
||||
|
||||
All wired config is being moved out of `emulator_settings` and into:
|
||||
|
||||
- `wired_emulator_settings`
|
||||
|
||||
This now includes both:
|
||||
|
||||
- existing wired runtime settings
|
||||
- the new `:wired` monitor threshold settings
|
||||
|
||||
Migration file:
|
||||
|
||||
- `Database Updates/002_move_wired_settings_to_wired_emulator_settings.sql`
|
||||
|
||||
---
|
||||
|
||||
## 6. What the monitor currently measures
|
||||
|
||||
The monitor currently measures:
|
||||
|
||||
- execution budget consumed in the current server window
|
||||
- delayed events currently pending
|
||||
- average execution time inside the current window
|
||||
- peak execution time inside the current window
|
||||
- recursion depth
|
||||
- remaining killed-room cooldown
|
||||
- room heavy state
|
||||
- room furni counts
|
||||
- renderer custom variable counts on room items
|
||||
|
||||
---
|
||||
|
||||
## 7. What is configurable now
|
||||
|
||||
Current DB-configurable areas include:
|
||||
|
||||
- engine enable/debug/exclusive/max-steps
|
||||
- custom wired compatibility mode
|
||||
- furni selection limit
|
||||
- max delay / max text length
|
||||
- teleport delay
|
||||
- tick interval/debug/priority
|
||||
- abuse protection thresholds
|
||||
- monitor usage/delayed/heavy/overload thresholds
|
||||
|
||||
All of these are documented in:
|
||||
|
||||
- `docs/wired_tools_reference.md`
|
||||
|
||||
---
|
||||
|
||||
## 8. Known limitations / future work
|
||||
|
||||
Current known limitations:
|
||||
|
||||
- `Permanent furni vars` uses a fixed UI denominator (`60`)
|
||||
- `@wired_timer` is still client-side time since room entry
|
||||
- `Chests` and `Settings` are not fully implemented yet
|
||||
- legacy wired configuration keys are still present for database compatibility, but runtime execution now goes only through the new engine
|
||||
|
||||
Good future tasks:
|
||||
|
||||
- make `Permanent furni vars` fully server-driven
|
||||
- add export/copy actions for monitor history
|
||||
- add more detailed filtering/search in history
|
||||
- document chest/settings once implemented
|
||||
- optionally remove the compatibility keys entirely once old database defaults are no longer needed
|
||||
|
||||
---
|
||||
|
||||
## 9. Recommended rollout order
|
||||
|
||||
1. run the wired settings migration SQL
|
||||
2. restart the emulator
|
||||
3. refresh renderer/client
|
||||
4. verify monitor values in a real room
|
||||
5. tune `wired.monitor.*` thresholds using the new DB table
|
||||
@@ -0,0 +1,554 @@
|
||||
# Wired Creator Tools (`:wired`) Reference
|
||||
|
||||
## 1. Scope
|
||||
|
||||
This document describes the current `:wired` tooling that was added across:
|
||||
|
||||
- `Arcturus-Morningstar-Extended` (server-side data, diagnostics, config)
|
||||
- `Nitro_Render_V3` (packet parsing and room/session metadata)
|
||||
- `Nitro-V3` (UI, monitor, inspection, previews, inline editing)
|
||||
|
||||
It focuses on:
|
||||
|
||||
- the `Monitor` tab
|
||||
- the `Inspection` tab (`furni`, `user`, `global`)
|
||||
- the `wired_emulator_settings` database table
|
||||
- the formulas and thresholds behind the monitor statistics
|
||||
|
||||
---
|
||||
|
||||
## 2. High-level architecture
|
||||
|
||||
### 2.1 Data flow
|
||||
|
||||
`Emulator` -> `Nitro_Render_V3` -> `Nitro-V3`
|
||||
|
||||
- The emulator computes room diagnostics and exposes extra room, furni, user, and monitor metadata.
|
||||
- The renderer parses those packets and stores the values in room/session data objects.
|
||||
- Nitro reads those values and renders them in the `:wired` UI.
|
||||
|
||||
### 2.2 Main files
|
||||
|
||||
- Emulator diagnostics:
|
||||
- `Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEngine.java`
|
||||
- `Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredRoomDiagnostics.java`
|
||||
- `Emulator/src/main/java/com/eu/habbo/messages/outgoing/wired/WiredMonitorDataComposer.java`
|
||||
- Renderer parsing:
|
||||
- `Nitro_Render_V3/packages/communication/src/messages/parser/roomevents/WiredMonitorDataParser.ts`
|
||||
- Nitro UI:
|
||||
- `Nitro-V3/src/components/wired-tools/WiredCreatorToolsView.tsx`
|
||||
|
||||
---
|
||||
|
||||
## 3. Database configuration
|
||||
|
||||
## 3.1 New table
|
||||
|
||||
Wired configuration is now separated from `emulator_settings` into:
|
||||
|
||||
```sql
|
||||
wired_emulator_settings (
|
||||
key,
|
||||
value,
|
||||
comment
|
||||
)
|
||||
```
|
||||
|
||||
Migration file:
|
||||
|
||||
- `Database Updates/002_move_wired_settings_to_wired_emulator_settings.sql`
|
||||
|
||||
The migration:
|
||||
|
||||
1. creates `wired_emulator_settings`
|
||||
2. imports existing wired values from `emulator_settings`
|
||||
3. inserts defaults for new monitor/diagnostic keys when missing
|
||||
4. removes migrated wired keys from `emulator_settings`
|
||||
|
||||
### 3.2 Compatibility behaviour
|
||||
|
||||
The emulator still has a **fallback read path**:
|
||||
|
||||
- first it reads wired keys from `wired_emulator_settings`
|
||||
- if a wired key is still missing there, it can still read it from the old `emulator_settings`
|
||||
|
||||
This allows safe rollout during migration.
|
||||
|
||||
## 3.3 Wired configuration keys
|
||||
|
||||
| Key | Default | Purpose |
|
||||
|---|---:|---|
|
||||
| `wired.engine.enabled` | `1` | Compatibility flag kept for old configs. Wired now runs through the new engine only. |
|
||||
| `wired.engine.exclusive` | `1` | Compatibility flag kept for old configs. Wired now runs through the new engine only. |
|
||||
| `wired.engine.maxStepsPerStack` | `100` | Maximum internal processing steps allowed for one stack execution. |
|
||||
| `wired.engine.debug` | `0` | Enables verbose wired engine logging. |
|
||||
| `wired.custom.enabled` | `0` | Enables legacy custom wired compatibility logic. |
|
||||
| `hotel.wired.furni.selection.count` | `5` | Maximum furni count selectable/storable by wired boxes. |
|
||||
| `hotel.wired.max_delay` | `20` | Maximum accepted wired delay value for delayed effects. |
|
||||
| `hotel.wired.message.max_length` | `100` | Maximum length of wired/bot text fields. |
|
||||
| `wired.effect.teleport.delay` | `500` | Delay in milliseconds used by wired teleports. |
|
||||
| `wired.place.under` | `0` | Allows wired furniture placement under other items. |
|
||||
| `wired.tick.interval.ms` | `50` | Global tick interval in milliseconds for repeater-style wired. |
|
||||
| `wired.tick.resolution` | `100` | Legacy compatibility tick resolution value. |
|
||||
| `wired.tick.debug` | `0` | Enables verbose logging for the tick service. |
|
||||
| `wired.tick.thread.priority` | `6` | Java thread priority for the tick service. |
|
||||
| `wired.highscores.displaycount` | `25` | Maximum wired highscore entries shown to the user. |
|
||||
| `wired.abuse.max.recursion.depth` | `10` | Maximum recursive wired depth before execution stops. |
|
||||
| `wired.abuse.max.events.per.window` | `100` | Maximum identical events allowed inside the abuse rate-limit window. |
|
||||
| `wired.abuse.rate.limit.window.ms` | `10000` | Time window in milliseconds used by the abuse limiter. |
|
||||
| `wired.abuse.ban.duration.ms` | `600000` | Room wired-ban duration in milliseconds after abuse detection. |
|
||||
| `wired.monitor.usage.window.ms` | `1000` | Rolling window size used to calculate monitor usage. |
|
||||
| `wired.monitor.usage.limit` | `1000` | Maximum usage budget allowed in one monitor window. |
|
||||
| `wired.monitor.delayed.events.limit` | `100` | Maximum delayed wired events that may be pending in one room. |
|
||||
| `wired.monitor.overload.average.ms` | `50` | Average execution threshold in milliseconds for overload tracking. |
|
||||
| `wired.monitor.overload.peak.ms` | `150` | Peak execution threshold in milliseconds for overload tracking. |
|
||||
| `wired.monitor.overload.consecutive.windows` | `2` | Consecutive overloaded windows required before `EXECUTOR_OVERLOAD`. |
|
||||
| `wired.monitor.heavy.usage.percent` | `70` | Usage percentage threshold that contributes to `MARKED_AS_HEAVY`. |
|
||||
| `wired.monitor.heavy.consecutive.windows` | `5` | Consecutive heavy windows required before the room is marked heavy. |
|
||||
| `wired.monitor.heavy.delayed.percent` | `60` | Delayed queue percentage threshold that contributes to the heavy state. |
|
||||
|
||||
---
|
||||
|
||||
## 4. Monitor tab
|
||||
|
||||
## 4.1 Statistics shown in the UI
|
||||
|
||||
The `Monitor` tab currently shows:
|
||||
|
||||
- `Wired usage`
|
||||
- `Is heavy`
|
||||
- `Room furni`
|
||||
- `Wall furni`
|
||||
- `Delayed events`
|
||||
- `Average execution`
|
||||
- `Peak execution`
|
||||
- `Recursion`
|
||||
- `Killed remaining`
|
||||
- `Permanent furni vars`
|
||||
|
||||
### 4.1.1 `Wired usage`
|
||||
|
||||
Format:
|
||||
|
||||
```text
|
||||
usageCurrentWindow / usageLimitPerWindow
|
||||
```
|
||||
|
||||
Source:
|
||||
|
||||
- server-side `WiredRoomDiagnostics`
|
||||
|
||||
Meaning:
|
||||
|
||||
- `usageCurrentWindow` = cost consumed in the current rolling monitor window
|
||||
- `usageLimitPerWindow` = max allowed budget before `EXECUTION_CAP`
|
||||
|
||||
### 4.1.2 `Is heavy`
|
||||
|
||||
Format:
|
||||
|
||||
```text
|
||||
Yes / No
|
||||
```
|
||||
|
||||
Source:
|
||||
|
||||
- server-side boolean from `WiredRoomDiagnostics`
|
||||
|
||||
Meaning:
|
||||
|
||||
- `Yes` if the room has crossed the heavy thresholds for enough consecutive windows
|
||||
|
||||
### 4.1.3 `Room furni`
|
||||
|
||||
Format:
|
||||
|
||||
```text
|
||||
(floor count + wall count) / roomItemLimit
|
||||
```
|
||||
|
||||
Source:
|
||||
|
||||
- numerator: renderer room object counts
|
||||
- denominator: server room item limit exposed in room/session data
|
||||
|
||||
### 4.1.4 `Wall furni`
|
||||
|
||||
Format:
|
||||
|
||||
```text
|
||||
wall count / roomItemLimit
|
||||
```
|
||||
|
||||
Important note:
|
||||
|
||||
- there is **no separate wall-only cap** here
|
||||
- the denominator is the same room furni limit exposed by the server
|
||||
|
||||
### 4.1.5 `Delayed events`
|
||||
|
||||
Format:
|
||||
|
||||
```text
|
||||
delayedEventsPending / delayedEventsLimit
|
||||
```
|
||||
|
||||
Source:
|
||||
|
||||
- server-side `WiredRoomDiagnostics`
|
||||
|
||||
### 4.1.6 `Average execution`
|
||||
|
||||
Format:
|
||||
|
||||
```text
|
||||
averageExecutionMs + "ms"
|
||||
```
|
||||
|
||||
Meaning:
|
||||
|
||||
- average execution time of sampled stacks inside the current monitor window
|
||||
|
||||
### 4.1.7 `Peak execution`
|
||||
|
||||
Format:
|
||||
|
||||
```text
|
||||
peakExecutionMs + "ms"
|
||||
```
|
||||
|
||||
Meaning:
|
||||
|
||||
- highest sampled execution time inside the current monitor window
|
||||
|
||||
### 4.1.8 `Recursion`
|
||||
|
||||
Format:
|
||||
|
||||
```text
|
||||
recursionDepthCurrent / recursionDepthLimit
|
||||
```
|
||||
|
||||
Meaning:
|
||||
|
||||
- current nested wired call depth vs the configured recursion cap
|
||||
|
||||
### 4.1.9 `Killed remaining`
|
||||
|
||||
Format:
|
||||
|
||||
```text
|
||||
killedRemainingSeconds + "s"
|
||||
```
|
||||
|
||||
Meaning:
|
||||
|
||||
- remaining room cooldown while wired execution is temporarily halted by protection logic
|
||||
|
||||
### 4.1.10 `Permanent furni vars`
|
||||
|
||||
Format:
|
||||
|
||||
```text
|
||||
customVariableEntryCount / 60
|
||||
```
|
||||
|
||||
Current meaning:
|
||||
|
||||
- the numerator is the total number of renderer-side entries stored inside `RoomObjectVariable.FURNITURE_CUSTOM_VARIABLES`
|
||||
- the denominator `60` is currently a fixed UI denominator
|
||||
|
||||
This is currently **renderer-side custom variable count**, not a DB row count.
|
||||
|
||||
---
|
||||
|
||||
## 4.2 Cost model behind `Wired usage`
|
||||
|
||||
The current estimated stack cost is computed in the emulator.
|
||||
|
||||
### 4.2.1 Base formula
|
||||
|
||||
```text
|
||||
cost = 1
|
||||
cost += number_of_conditions
|
||||
cost += 2 for each selector effect
|
||||
cost += 3 for each non-selector effect
|
||||
cost += 4 extra for each delayed effect
|
||||
cost += recursionDepth * 2
|
||||
cost = max(1, cost)
|
||||
```
|
||||
|
||||
### 4.2.2 Practical breakdown
|
||||
|
||||
| Element | Cost |
|
||||
|---|---:|
|
||||
| Base stack cost | `1` |
|
||||
| Each condition | `+1` |
|
||||
| Each selector effect | `+2` |
|
||||
| Each regular effect | `+3` |
|
||||
| Each delayed effect | `+4` extra |
|
||||
| Each recursion level | `+2` |
|
||||
|
||||
### 4.2.3 Example
|
||||
|
||||
If a stack has:
|
||||
|
||||
- `2` conditions
|
||||
- `1` selector
|
||||
- `2` normal effects
|
||||
- `1` delayed effect
|
||||
- recursion depth `1`
|
||||
|
||||
Then:
|
||||
|
||||
```text
|
||||
1
|
||||
+ 2 conditions
|
||||
+ 2 selector
|
||||
+ 6 regular effects
|
||||
+ 4 delayed effect extra
|
||||
+ 2 recursion
|
||||
= 17
|
||||
```
|
||||
|
||||
That `17` is what is attempted against:
|
||||
|
||||
```text
|
||||
usageCurrentWindow + estimatedCost <= usageLimitPerWindow
|
||||
```
|
||||
|
||||
If the result would exceed the budget, the engine records `EXECUTION_CAP`.
|
||||
|
||||
---
|
||||
|
||||
## 4.3 Heavy / overload calculations
|
||||
|
||||
### 4.3.1 Overload
|
||||
|
||||
A monitor window is considered overloaded when:
|
||||
|
||||
```text
|
||||
executionSamplesCurrentWindow > 0
|
||||
AND (
|
||||
averageExecutionMs >= overloadAverageThresholdMs
|
||||
OR
|
||||
peakExecutionMs >= overloadPeakThresholdMs
|
||||
)
|
||||
```
|
||||
|
||||
After `wired.monitor.overload.consecutive.windows` consecutive overloaded windows:
|
||||
|
||||
- the room logs `EXECUTOR_OVERLOAD`
|
||||
|
||||
### 4.3.2 Heavy
|
||||
|
||||
A monitor window is considered heavy when at least one of these is true:
|
||||
|
||||
```text
|
||||
usagePercent >= heavyUsageThresholdPercent
|
||||
OR
|
||||
delayedPercent >= heavyDelayedThresholdPercent
|
||||
OR
|
||||
overloadWindow == true
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
```text
|
||||
usagePercent = round(usageCurrentWindow * 100 / usageLimitPerWindow)
|
||||
delayedPercent = round(delayedEventsPending * 100 / delayedEventsLimit)
|
||||
```
|
||||
|
||||
After `wired.monitor.heavy.consecutive.windows` consecutive heavy windows:
|
||||
|
||||
- the room is marked heavy
|
||||
- the monitor logs `MARKED_AS_HEAVY`
|
||||
|
||||
---
|
||||
|
||||
## 4.4 Error / warning log types
|
||||
|
||||
The monitor currently supports:
|
||||
|
||||
- `EXECUTION_CAP`
|
||||
- `DELAYED_EVENTS_CAP`
|
||||
- `EXECUTOR_OVERLOAD`
|
||||
- `MARKED_AS_HEAVY`
|
||||
- `KILLED`
|
||||
- `RECURSION_TIMEOUT`
|
||||
|
||||
Each log/history entry can carry:
|
||||
|
||||
- type
|
||||
- severity
|
||||
- amount/count
|
||||
- latest occurrence
|
||||
- reason/motivation
|
||||
- trigger/source label
|
||||
- trigger/source id
|
||||
|
||||
---
|
||||
|
||||
## 5. Inspection tab
|
||||
|
||||
## 5.1 Furni inspection
|
||||
|
||||
Current variables include:
|
||||
|
||||
- `@id`
|
||||
- `@class_id`
|
||||
- `@height`
|
||||
- `@state`
|
||||
- `@position.x`
|
||||
- `@position.y`
|
||||
- `@rotation`
|
||||
- `@altitude`
|
||||
- `@wallitem_offset` (wall items only)
|
||||
- `@type`
|
||||
- `@dimensions.x`
|
||||
- `@dimensions.y`
|
||||
- `@owner_id`
|
||||
- dynamic flags:
|
||||
- `@can_sit_on`
|
||||
- `@can_lay_on`
|
||||
- `@can_stand_on`
|
||||
- `@is_stackable`
|
||||
- extra teleport variable when relevant:
|
||||
- `~teleport.target_id`
|
||||
|
||||
Editable fields:
|
||||
|
||||
- `@state`
|
||||
- `@position.x`
|
||||
- `@position.y`
|
||||
- `@rotation`
|
||||
- `@altitude`
|
||||
- `@wallitem_offset` (wall items)
|
||||
|
||||
Important notes:
|
||||
|
||||
- floor moves are sent through wired-style movement flow/animation
|
||||
- wall item updates use wall position recomposition
|
||||
- booleans such as sit/lay/stand/stack come from `items_base`-derived metadata, not from `FurnitureData.json`
|
||||
|
||||
## 5.2 User inspection
|
||||
|
||||
Current variables include:
|
||||
|
||||
- `@index`
|
||||
- `@type`
|
||||
- `@gender`
|
||||
- `@level`
|
||||
- `@achievement_score`
|
||||
- `@position.x`
|
||||
- `@position.y`
|
||||
- `@direction`
|
||||
- `@altitude`
|
||||
- `@favourite_group_id`
|
||||
- `@room_entry`
|
||||
- `@room_entry.teleport_id`
|
||||
- `@user_id` / `@bot_id` / `@pet_id`
|
||||
|
||||
Dynamic flags/actions include:
|
||||
|
||||
- `@is_hc`
|
||||
- `@has_rights`
|
||||
- `@is_owner`
|
||||
- `@is_group_admin`
|
||||
- `@is_mute`
|
||||
- `@is_trading`
|
||||
- `@is_frozen`
|
||||
- `@effect`
|
||||
- `@team_score`
|
||||
- `@team_color`
|
||||
- `@team_type`
|
||||
- `@sign`
|
||||
- `@dance`
|
||||
- `@is_idle`
|
||||
- `@handitems`
|
||||
|
||||
Editable fields:
|
||||
|
||||
- `@position.x`
|
||||
- `@position.y`
|
||||
- `@direction`
|
||||
|
||||
## 5.3 Global inspection
|
||||
|
||||
Current variables include:
|
||||
|
||||
- `@furni_count`
|
||||
- `@user_count`
|
||||
- `@wired_timer`
|
||||
- `@teams.red.score`
|
||||
- `@teams.green.score`
|
||||
- `@teams.blue.score`
|
||||
- `@teams.yellow.score`
|
||||
- `@teams.red.size`
|
||||
- `@teams.green.size`
|
||||
- `@teams.blue.size`
|
||||
- `@teams.yellow.size`
|
||||
- `@room_id`
|
||||
- `@group_id`
|
||||
- `@timezone_server`
|
||||
- `@timezone_client`
|
||||
- `@current_time`
|
||||
- `@current_time.millisecond_of_second`
|
||||
- `@current_time.seconds_of_minute`
|
||||
- `@current_time.minute_of_hour`
|
||||
- `@current_time.hour_of_day`
|
||||
- `@current_time.day_of_week`
|
||||
- `@current_time.day_of_month`
|
||||
- `@current_time.day_of_year`
|
||||
- `@current_time.week_of_year`
|
||||
- `@current_time.month_of_year`
|
||||
- `@current_time.year`
|
||||
|
||||
Important notes:
|
||||
|
||||
- `@timezone_server` comes from the emulator room/session snapshot and follows `hotel.timezone`
|
||||
- `@timezone_client` comes from the browser
|
||||
- `@wired_timer` is currently client-side time since room entry
|
||||
- `@current_time.*` is currently based on the server hotel time snapshot plus client-side progression
|
||||
|
||||
---
|
||||
|
||||
## 6. UI behaviour notes
|
||||
|
||||
### 6.1 Monitor windows
|
||||
|
||||
The monitor now uses real Nitro card windows for:
|
||||
|
||||
- info
|
||||
- log history
|
||||
- error information
|
||||
|
||||
This means they are:
|
||||
|
||||
- closable with the standard Nitro card close button
|
||||
- draggable
|
||||
- resizable
|
||||
|
||||
### 6.2 Keep selected
|
||||
|
||||
In `Inspection`:
|
||||
|
||||
- when `Keep selected` is enabled
|
||||
- clicking another furni/user does **not** replace the current preview/selection
|
||||
|
||||
### 6.3 Inline editing
|
||||
|
||||
Inline editors:
|
||||
|
||||
- can be opened by clicking the row
|
||||
- submit on `Enter`
|
||||
- stop accidental room chat typing while the input is focused
|
||||
|
||||
---
|
||||
|
||||
## 7. Current limitations
|
||||
|
||||
- `Permanent furni vars` currently uses a fixed denominator (`60`) in the Nitro UI
|
||||
- `@wired_timer` is still client-side, not a dedicated server timer
|
||||
- `wired.tick.resolution` is kept for compatibility/documentation, but the current tick service uses `wired.tick.interval.ms`
|
||||
- `wired.highscores.displaycount` is migrated/documented, but its usage should be validated in the current runtime path if highscore behaviour is changed later
|
||||
Reference in New Issue
Block a user