You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-19 15:06:19 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e772686c4b | |||
| a00f7b01f5 | |||
| 6b4089cace | |||
| 9ea7acf05c | |||
| bab43af41e | |||
| 55b38e7b85 | |||
| 4a96c5baaf | |||
| 539c5b5b96 | |||
| 7b7154e68f | |||
| 4aabb738a3 | |||
| 691dc42627 | |||
| 226873c1fb | |||
| a06a204b39 | |||
| e213609609 | |||
| 44d38b8661 |
@@ -1 +1,17 @@
|
|||||||
INSERT INTO `camwijsnew`.`permission_definitions` (`permission_key`, `max_value`, `comment`, `rank_1`, `rank_2`, `rank_3`, `rank_4`, `rank_5`, `rank_6`, `rank_7`) VALUES ('acc_housekeeping', '1', 'Allow housekeeping in the client', '0', '0', '0', '0', '0', '0', '1');
|
INSERT INTO `permission_definitions` (`permission_key`, `max_value`, `comment`, `rank_1`, `rank_2`, `rank_3`, `rank_4`, `rank_5`, `rank_6`, `rank_7`) VALUES ('acc_housekeeping', '1', 'Allow housekeeping in the client', '0', '0', '0', '0', '0', '0', '1');
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `housekeeping_log` (
|
||||||
|
`id` INT NOT NULL AUTO_INCREMENT,
|
||||||
|
`timestamp` INT NOT NULL,
|
||||||
|
`actor_id` INT NOT NULL,
|
||||||
|
`actor_name` VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
`target_type` VARCHAR(16) NOT NULL DEFAULT 'user',
|
||||||
|
`target_id` INT NOT NULL DEFAULT 0,
|
||||||
|
`target_label` VARCHAR(128) NOT NULL DEFAULT '',
|
||||||
|
`action` VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
`detail` VARCHAR(500) NOT NULL DEFAULT '',
|
||||||
|
`success` TINYINT NOT NULL DEFAULT 1,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `timestamp` (`timestamp`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
ALTER TABLE `bots`
|
||||||
|
MODIFY COLUMN `type` ENUM('generic','visitor_log','bartender','weapons_dealer','frank')
|
||||||
|
NOT NULL DEFAULT 'generic';
|
||||||
|
|
||||||
|
INSERT INTO `permission_definitions`
|
||||||
|
(`permission_key`, `max_value`, `comment`,
|
||||||
|
`rank_1`, `rank_2`, `rank_3`, `rank_4`, `rank_5`, `rank_6`, `rank_7`)
|
||||||
|
VALUES
|
||||||
|
('acc_bot_frank', 1, 'Required to purchase the Frank mascot bot from the catalog.',
|
||||||
|
0, 0, 0, 0, 0, 0, 1)
|
||||||
|
ON DUPLICATE KEY UPDATE `comment` = VALUES(`comment`);
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `bot_chat_responses` (
|
||||||
|
`id` INT NOT NULL AUTO_INCREMENT,
|
||||||
|
`bot_type` VARCHAR(32) NOT NULL,
|
||||||
|
`keys` VARCHAR(255) NOT NULL COMMENT 'semicolon-separated trigger words',
|
||||||
|
`responses` TEXT NOT NULL COMMENT 'newline-separated replies; bot picks one at random',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `bot_type` (`bot_type`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
INSERT INTO `bot_chat_responses` (`bot_type`, `keys`, `responses`) VALUES
|
||||||
|
('frank', '__door_triggers', 'show me the door\nkick me\ni want to leave\nlet me out'),
|
||||||
|
('frank', '__door_lines', 'Right this way - mind the step!\nAnd out you go. Come back soon!\nAllow me to escort you to the exit.\nThere''s the door. Farewell, true believer!'),
|
||||||
|
('frank', '__busy_whisper', 'Sorry, I am currently busy. Please wait until I am available.'),
|
||||||
|
('frank', 'frank', 'Hello, I''m Frank! Welcome to Habbo.'),
|
||||||
|
('frank', 'help', 'What do you need help with?'),
|
||||||
|
('frank', 'thanks;thank you', 'Just doing my job, true believer!'),
|
||||||
|
('frank', 'new', 'Welcome to Habbo! I hope you have a great time here.'),
|
||||||
|
('frank', 'rooms', 'Looking for somewhere fun? Try the Navigator - thousands of rooms to explore!'),
|
||||||
|
('frank', 'sulake', 'Sulake is the company behind Habbo. Take a look: https://www.sulake.com'),
|
||||||
|
('frank', 'vip;hc', 'VIP gets you more outfits, more furni, more everything. Worth it!'),
|
||||||
|
('frank', 'music', 'Snoop Dogg, Frank Sinatra and a little Beethoven on Sundays.'),
|
||||||
|
('frank', 'movie', 'I''m a Casablanca man. Black and white films are an underrated art.'),
|
||||||
|
('frank', 'game', 'Battleship. Always Battleship.'),
|
||||||
|
('frank', 'snowstorm', 'Honestly? I''m terrible at Snowstorm. Don''t tell anyone.'),
|
||||||
|
('frank', 'furni', 'Best furniture maker in town - hands down, the folks at Sulake.'),
|
||||||
|
('frank', 'animal;cat;pet','I have a cat called Mr. Whiskers. He runs the place, really.'),
|
||||||
|
('frank', 'miranda', 'Miranda. The love of my life. Don''t get me started.'),
|
||||||
|
('frank', 'frank black', 'Named after the man himself. Frank Black is a hero of mine.'),
|
||||||
|
('frank', 'life', 'Life is like a bowl of popcorn - warm, salty and buttery.'),
|
||||||
|
('frank', 'job;work', 'I''m sure you can find work in one of the guest rooms!'),
|
||||||
|
('frank', 'snouthill', 'Snouthill... so many memories.'),
|
||||||
|
('frank', 'wife', 'I had a wife once. She broke my stereo.'),
|
||||||
|
('frank', 'baseball', 'Oh, I used to love to go down to the old ball park and watch Christy Mathewson and Honus Wagner at bat.'),
|
||||||
|
('frank', 'mark', 'I don''t trust Mark.'),
|
||||||
|
('frank', 'vietnam', 'Vietnam? Don''t ask. Worst trip of my life.'),
|
||||||
|
('frank', 'pills;drugs', 'Drugs are bad, mmkay?');
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `bot_serves` (`keys`, `item`) VALUES
|
||||||
|
('sunflower', 21),
|
||||||
|
('cola;habbo cola', 32),
|
||||||
|
('rose', 1000),
|
||||||
|
('book', 20),
|
||||||
|
('tea', 6),
|
||||||
|
('coffee', 1),
|
||||||
|
('migraine;headache;pills', 34),
|
||||||
|
('radioactive liquid;radioactive', 36),
|
||||||
|
('turkey;can of turkey', 38);
|
||||||
|
|
||||||
|
-- VERY IMPORTANT !!!!
|
||||||
|
-- First check if the items_base ID and catalog_items ID is not in use !
|
||||||
|
-- After the SQL please go to the catalog_items table and change the page_id to where your BOTS are located
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `items_base` (`id`, `sprite_id`, `item_name`, `public_name`, `width`, `length`, `stack_height`, `allow_stack`, `allow_sit`, `allow_lay`, `allow_walk`, `allow_gift`, `allow_trade`, `allow_recycle`, `allow_marketplace_sell`, `allow_inventory_stack`, `type`, `interaction_type`, `interaction_modes_count`, `vending_ids`, `multiheight`, `customparams`)
|
||||||
|
VALUES (99001, 0, 'bot_frank', 'Frank', 1, 1, 0.00, '0', '0', '0', '1', '0', '0', '0', '0', '0', 'r', 'default', 1, '0', '0', 'name:Frank;motto:Welcome to Habbo!;figure:hr-3499-33.sh-290-90.ch-3971-72-73.lg-270-73.hd-205-1-1.fa-1206-67.ha-3409-73-72;gender:M');
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `catalog_items` (`item_ids`, `page_id`, `offer_id`, `catalog_name`, `cost_credits`, `cost_points`, `points_type`, `amount`, `extradata`)
|
||||||
|
VALUES ('99001', 1, 99001, 'Frank', 0, 0, 0, 1, '0');
|
||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>com.eu.habbo</groupId>
|
<groupId>com.eu.habbo</groupId>
|
||||||
<artifactId>Habbo</artifactId>
|
<artifactId>Habbo</artifactId>
|
||||||
<version>4.2.19</version>
|
<version>4.2.22</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|||||||
@@ -539,5 +539,9 @@ public class Bot implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final short[] DEFAULT_OWNER_ACTION_IDS = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||||
|
|
||||||
|
public short[] getOwnerActionIds() {
|
||||||
|
return DEFAULT_OWNER_ACTION_IDS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public class BotManager {
|
|||||||
addBotDefinition("generic", Bot.class);
|
addBotDefinition("generic", Bot.class);
|
||||||
addBotDefinition("bartender", ButlerBot.class);
|
addBotDefinition("bartender", ButlerBot.class);
|
||||||
addBotDefinition("visitor_log", VisitorBot.class);
|
addBotDefinition("visitor_log", VisitorBot.class);
|
||||||
|
addBotDefinition(FrankBot.BOT_TYPE, FrankBot.class);
|
||||||
|
|
||||||
this.reload();
|
this.reload();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,449 @@
|
|||||||
|
package com.eu.habbo.habbohotel.bots;
|
||||||
|
|
||||||
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.rooms.*;
|
||||||
|
import com.eu.habbo.habbohotel.users.Habbo;
|
||||||
|
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer;
|
||||||
|
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserWhisperComposer;
|
||||||
|
import com.eu.habbo.threading.runnables.RoomUnitWalkToLocation;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class FrankBot extends ButlerBot {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(FrankBot.class);
|
||||||
|
|
||||||
|
public static final String BOT_TYPE = "frank";
|
||||||
|
public static final String PERMISSION_USE = "acc_bot_frank";
|
||||||
|
private static final String KEY_DOOR_LINES = "__door_lines";
|
||||||
|
private static final String KEY_BUSY_WHISPER = "__busy_whisper";
|
||||||
|
private static final String KEY_DOOR_TRIGGERS = "__door_triggers";
|
||||||
|
private static final List<String> DEFAULT_DOOR_LINES = List.of(
|
||||||
|
"Right this way - mind the step!",
|
||||||
|
"And out you go. Come back soon!",
|
||||||
|
"Allow me to escort you to the exit.",
|
||||||
|
"There's the door. Farewell, true believer!"
|
||||||
|
);
|
||||||
|
private static final String DEFAULT_BUSY_WHISPER =
|
||||||
|
"Sorry, I am currently busy. Please wait until I am available.";
|
||||||
|
private static final Pattern DEFAULT_DOOR_PATTERN = Pattern.compile(
|
||||||
|
"\\b(show me the door|kick me|i want to leave|let me out)\\b");
|
||||||
|
|
||||||
|
private static final ConcurrentHashMap<Pattern, List<String>> chatResponses = new ConcurrentHashMap<>();
|
||||||
|
private static volatile List<String> doorLines = DEFAULT_DOOR_LINES;
|
||||||
|
private static volatile String busyWhisper = DEFAULT_BUSY_WHISPER;
|
||||||
|
private static volatile Pattern doorTriggerPattern = DEFAULT_DOOR_PATTERN;
|
||||||
|
|
||||||
|
private static final Random RANDOM = new Random();
|
||||||
|
|
||||||
|
private static final int MAX_CHAT_KEYWORDS = 256;
|
||||||
|
private static final int MAX_DOOR_TRIGGERS = 32;
|
||||||
|
private static final int MAX_MESSAGE_LEN = 256;
|
||||||
|
private static final long BUSY_WHISPER_COOLDOWN_MS = 5000L;
|
||||||
|
|
||||||
|
private volatile RoomTile homeTile;
|
||||||
|
private volatile RoomUserRotation homeRotation;
|
||||||
|
private final AtomicBoolean busy = new AtomicBoolean(false);
|
||||||
|
private final AtomicBoolean returnScheduled = new AtomicBoolean(false);
|
||||||
|
private final ConcurrentHashMap<Integer, Long> lastBusyWhisperAt = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public FrankBot(ResultSet set) throws SQLException {
|
||||||
|
super(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrankBot(Bot bot) {
|
||||||
|
super(bot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlace(Habbo habbo, Room room) {
|
||||||
|
super.onPlace(habbo, room);
|
||||||
|
if (this.getRoomUnit() != null) {
|
||||||
|
this.homeTile = this.getRoomUnit().getCurrentLocation();
|
||||||
|
this.homeRotation = this.getRoomUnit().getBodyRotation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final short[] FRANK_OWNER_ACTIONS = new short[0];
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short[] getOwnerActionIds() {
|
||||||
|
return FRANK_OWNER_ACTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void initialise() {
|
||||||
|
chatResponses.clear();
|
||||||
|
doorLines = DEFAULT_DOOR_LINES;
|
||||||
|
busyWhisper = DEFAULT_BUSY_WHISPER;
|
||||||
|
doorTriggerPattern = DEFAULT_DOOR_PATTERN;
|
||||||
|
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
Statement statement = connection.createStatement();
|
||||||
|
ResultSet set = statement.executeQuery("SELECT `keys`, `responses` FROM bot_chat_responses WHERE bot_type = '" + BOT_TYPE + "'")) {
|
||||||
|
while (set.next()) {
|
||||||
|
String keysRaw = set.getString("keys");
|
||||||
|
String responsesRaw = set.getString("responses");
|
||||||
|
|
||||||
|
if (keysRaw == null || responsesRaw == null) continue;
|
||||||
|
|
||||||
|
List<String> responses = new ArrayList<>();
|
||||||
|
for (String line : responsesRaw.split("\n")) {
|
||||||
|
String trimmed = line.trim();
|
||||||
|
if (!trimmed.isEmpty()) responses.add(trimmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responses.isEmpty()) continue;
|
||||||
|
|
||||||
|
String firstKey = keysRaw.split(";", 2)[0].trim();
|
||||||
|
if (firstKey.startsWith("__")) {
|
||||||
|
switch (firstKey) {
|
||||||
|
case KEY_DOOR_LINES:
|
||||||
|
doorLines = new CopyOnWriteArrayList<>(responses);
|
||||||
|
break;
|
||||||
|
case KEY_BUSY_WHISPER:
|
||||||
|
busyWhisper = responses.get(0);
|
||||||
|
break;
|
||||||
|
case KEY_DOOR_TRIGGERS:
|
||||||
|
doorTriggerPattern = buildDoorTriggerPattern(responses);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOGGER.warn("FrankBot: unknown system key '{}', ignored", firstKey);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> shared = new CopyOnWriteArrayList<>(responses);
|
||||||
|
|
||||||
|
for (String key : keysRaw.split(";")) {
|
||||||
|
if (chatResponses.size() >= MAX_CHAT_KEYWORDS) {
|
||||||
|
LOGGER.warn("FrankBot: chat keyword cap ({}) reached, remaining rows ignored",
|
||||||
|
MAX_CHAT_KEYWORDS);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
String k = key == null ? "" : key.trim().toLowerCase();
|
||||||
|
if (k.isEmpty()) continue;
|
||||||
|
try {
|
||||||
|
Pattern pattern = Pattern.compile("\\b" + Pattern.quote(k) + "\\b");
|
||||||
|
chatResponses.put(pattern, shared);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Failed to compile Frank chat keyword pattern: {}", k, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
LOGGER.warn("FrankBot: could not load bot_chat_responses ({}). Frank will still serve items.", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
ButlerBot.initialise();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void dispose() {
|
||||||
|
chatResponses.clear();
|
||||||
|
doorLines = DEFAULT_DOOR_LINES;
|
||||||
|
busyWhisper = DEFAULT_BUSY_WHISPER;
|
||||||
|
doorTriggerPattern = DEFAULT_DOOR_PATTERN;
|
||||||
|
ButlerBot.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Pattern buildDoorTriggerPattern(List<String> triggers) {
|
||||||
|
StringBuilder sb = new StringBuilder("\\b(");
|
||||||
|
boolean first = true;
|
||||||
|
int count = 0;
|
||||||
|
for (String trigger : triggers) {
|
||||||
|
if (count >= MAX_DOOR_TRIGGERS) {
|
||||||
|
LOGGER.warn("FrankBot: door trigger cap ({}) reached, extra entries ignored",
|
||||||
|
MAX_DOOR_TRIGGERS);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
String t = trigger == null ? "" : trigger.trim().toLowerCase();
|
||||||
|
if (t.isEmpty()) continue;
|
||||||
|
if (!first) sb.append('|');
|
||||||
|
sb.append(Pattern.quote(t));
|
||||||
|
first = false;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
sb.append(")\\b");
|
||||||
|
|
||||||
|
if (first) return DEFAULT_DOOR_PATTERN;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Pattern.compile(sb.toString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("FrankBot: failed to compile door trigger pattern from {}, falling back to default", triggers, e);
|
||||||
|
return DEFAULT_DOOR_PATTERN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUserSay(final RoomChatMessage message) {
|
||||||
|
Room currentRoom = this.getRoom();
|
||||||
|
if (currentRoom == null) return;
|
||||||
|
|
||||||
|
Habbo asker = message.getHabbo();
|
||||||
|
if (asker == null || asker.getClient() == null) return;
|
||||||
|
|
||||||
|
if (this.getRoomUnit() == null) return;
|
||||||
|
|
||||||
|
String raw = message.getUnfilteredMessage();
|
||||||
|
if (raw != null && raw.length() > MAX_MESSAGE_LEN) return;
|
||||||
|
|
||||||
|
if (this.homeTile == null) {
|
||||||
|
this.homeTile = this.getRoomUnit().getCurrentLocation();
|
||||||
|
this.homeRotation = this.getRoomUnit().getBodyRotation();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.busy.get() || this.getRoomUnit().hasStatus(RoomUnitStatus.MOVE)) {
|
||||||
|
this.whisperThrottled(asker, busyWhisper);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (raw != null) {
|
||||||
|
double distance = this.getRoomUnit().getCurrentLocation().distance(asker.getRoomUnit().getCurrentLocation());
|
||||||
|
int commandDistance = Emulator.getConfig().getInt("hotel.bot.butler.commanddistance");
|
||||||
|
|
||||||
|
if (distance <= commandDistance) {
|
||||||
|
String lower = raw.toLowerCase();
|
||||||
|
|
||||||
|
if (doorTriggerPattern.matcher(lower).find()) {
|
||||||
|
if (!this.busy.compareAndSet(false, true)) {
|
||||||
|
this.whisperThrottled(asker, busyWhisper);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.showToTheDoor(asker);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (java.util.Map.Entry<Pattern, List<String>> entry : chatResponses.entrySet()) {
|
||||||
|
if (entry.getKey().matcher(lower).find()) {
|
||||||
|
List<String> options = entry.getValue();
|
||||||
|
if (options.isEmpty()) continue;
|
||||||
|
|
||||||
|
String reply = options.get(RANDOM.nextInt(options.size()));
|
||||||
|
this.talk(reply);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.busy.compareAndSet(false, true)) {
|
||||||
|
this.whisperThrottled(asker, busyWhisper);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.onUserSay(message);
|
||||||
|
this.schedulePostServeReturn(currentRoom.getId(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void whisperThrottled(Habbo target, String text) {
|
||||||
|
if (target == null || text == null || text.isEmpty() || this.getRoomUnit() == null) return;
|
||||||
|
int userId = target.getHabboInfo().getId();
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
Long last = lastBusyWhisperAt.get(userId);
|
||||||
|
if (last != null && (now - last) < BUSY_WHISPER_COOLDOWN_MS) return;
|
||||||
|
lastBusyWhisperAt.put(userId, now);
|
||||||
|
RoomChatMessage msg = new RoomChatMessage(text, this.getRoomUnit(), RoomChatMessageBubbles.BOT);
|
||||||
|
target.getClient().sendResponse(new RoomUserWhisperComposer(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showToTheDoor(final Habbo target) {
|
||||||
|
final Room room = this.getRoom();
|
||||||
|
if (room == null || room.getLayout() == null || target == null) {
|
||||||
|
this.busy.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final RoomTile doorTile = room.getLayout().getDoorTile();
|
||||||
|
if (doorTile == null) {
|
||||||
|
this.busy.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lookAt(target);
|
||||||
|
List<String> lines = doorLines;
|
||||||
|
String line = lines.isEmpty() ? DEFAULT_DOOR_LINES.get(RANDOM.nextInt(DEFAULT_DOOR_LINES.size()))
|
||||||
|
: lines.get(RANDOM.nextInt(lines.size()));
|
||||||
|
this.talk(line);
|
||||||
|
|
||||||
|
final int targetId = target.getHabboInfo().getId();
|
||||||
|
final int roomId = room.getId();
|
||||||
|
final AtomicBoolean fired = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
final Runnable kickThenReturn = () -> {
|
||||||
|
if (!fired.compareAndSet(false, true)) return;
|
||||||
|
Room currentRoom = this.getRoom();
|
||||||
|
if (currentRoom == null || currentRoom.getId() != roomId) {
|
||||||
|
this.busy.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Habbo stillHere = currentRoom.getHabbo(targetId);
|
||||||
|
if (stillHere != null) {
|
||||||
|
currentRoom.kickHabbo(stillHere, false);
|
||||||
|
}
|
||||||
|
this.scheduleReturnHome(targetId, roomId, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.getRoomUnit().canWalk() && !this.getRoomUnit().getCurrentLocation().equals(doorTile)) {
|
||||||
|
List<Runnable> onArrive = new ArrayList<>();
|
||||||
|
onArrive.add(kickThenReturn);
|
||||||
|
|
||||||
|
List<Runnable> onFail = new ArrayList<>();
|
||||||
|
onFail.add(() -> Emulator.getThreading().run(kickThenReturn, 1500));
|
||||||
|
|
||||||
|
this.getRoomUnit().setGoalLocation(doorTile);
|
||||||
|
Emulator.getThreading().run(
|
||||||
|
new RoomUnitWalkToLocation(this.getRoomUnit(), doorTile, room, onArrive, onFail));
|
||||||
|
} else {
|
||||||
|
Emulator.getThreading().run(kickThenReturn, 1500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int RETURN_HOME_POLL_MS = 500;
|
||||||
|
private static final int RETURN_HOME_MAX_WAIT_MS = 8000;
|
||||||
|
private static final int POST_SERVE_POLL_MS = 750;
|
||||||
|
private static final int POST_SERVE_MAX_WAIT_MS = 30000;
|
||||||
|
|
||||||
|
private void schedulePostServeReturn(final int roomId, final int waitedMs) {
|
||||||
|
if (waitedMs == 0 && !this.returnScheduled.compareAndSet(false, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (waitedMs >= POST_SERVE_MAX_WAIT_MS) {
|
||||||
|
this.returnScheduled.set(false);
|
||||||
|
this.busy.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.homeTile == null) {
|
||||||
|
this.returnScheduled.set(false);
|
||||||
|
this.busy.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Emulator.getThreading().run(() -> {
|
||||||
|
Room r = this.getRoom();
|
||||||
|
if (r == null || r.getId() != roomId || this.getRoomUnit() == null || this.homeTile == null) {
|
||||||
|
this.returnScheduled.set(false);
|
||||||
|
this.busy.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.getRoomUnit().getCurrentLocation().equals(this.homeTile)) {
|
||||||
|
if (this.homeRotation != null && this.getRoomUnit().getBodyRotation() != this.homeRotation) {
|
||||||
|
this.getRoomUnit().setRotation(this.homeRotation);
|
||||||
|
r.sendComposer(new RoomUserStatusComposer(this.getRoomUnit()).compose());
|
||||||
|
this.persistPosition();
|
||||||
|
} else {
|
||||||
|
this.busy.set(false);
|
||||||
|
}
|
||||||
|
this.returnScheduled.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean stillWalking = this.getRoomUnit().hasStatus(RoomUnitStatus.MOVE)
|
||||||
|
|| (this.getRoomUnit().getPath() != null && !this.getRoomUnit().getPath().isEmpty());
|
||||||
|
|
||||||
|
if (stillWalking) {
|
||||||
|
this.schedulePostServeReturn(roomId, waitedMs + POST_SERVE_POLL_MS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.returnScheduled.set(false);
|
||||||
|
this.returnHome(-1, false);
|
||||||
|
}, POST_SERVE_POLL_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleReturnHome(final int kickedHabboId, final int roomId, final int waitedMs) {
|
||||||
|
Room currentRoom = this.getRoom();
|
||||||
|
if (currentRoom == null || currentRoom.getId() != roomId) return;
|
||||||
|
|
||||||
|
boolean stillEscorting = currentRoom.getHabbo(kickedHabboId) != null;
|
||||||
|
|
||||||
|
if (!stillEscorting || waitedMs >= RETURN_HOME_MAX_WAIT_MS) {
|
||||||
|
this.returnHome(kickedHabboId, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Emulator.getThreading().run(
|
||||||
|
() -> this.scheduleReturnHome(kickedHabboId, roomId, waitedMs + RETURN_HOME_POLL_MS),
|
||||||
|
RETURN_HOME_POLL_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void returnHome(int kickedHabboId, boolean alwaysTeleport) {
|
||||||
|
final Room room = this.getRoom();
|
||||||
|
if (room == null || this.homeTile == null || this.getRoomUnit() == null) {
|
||||||
|
this.busy.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Runnable teleportHome = () -> {
|
||||||
|
Room r = this.getRoom();
|
||||||
|
if (r == null || this.getRoomUnit() == null) return;
|
||||||
|
|
||||||
|
double homeZ = r.getTopHeightAt(this.homeTile.x, this.homeTile.y);
|
||||||
|
|
||||||
|
this.getRoomUnit().stopWalking();
|
||||||
|
this.getRoomUnit().setZ(homeZ);
|
||||||
|
this.getRoomUnit().setLocation(this.homeTile);
|
||||||
|
this.getRoomUnit().setPreviousLocationZ(homeZ);
|
||||||
|
if (this.homeRotation != null) {
|
||||||
|
this.getRoomUnit().setRotation(this.homeRotation);
|
||||||
|
}
|
||||||
|
this.getRoomUnit().statusUpdate(true);
|
||||||
|
r.sendComposer(new RoomUserStatusComposer(this.getRoomUnit()).compose());
|
||||||
|
this.persistPosition();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.getRoomUnit().getCurrentLocation().equals(this.homeTile)) {
|
||||||
|
if (this.homeRotation != null) {
|
||||||
|
this.getRoomUnit().setRotation(this.homeRotation);
|
||||||
|
room.sendComposer(new RoomUserStatusComposer(this.getRoomUnit()).compose());
|
||||||
|
}
|
||||||
|
this.persistPosition();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasOtherWatchers = false;
|
||||||
|
for (Habbo h : room.getCurrentHabbos().values()) {
|
||||||
|
if (h.getHabboInfo().getId() != kickedHabboId) {
|
||||||
|
hasOtherWatchers = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alwaysTeleport || !hasOtherWatchers || !this.getRoomUnit().canWalk()) {
|
||||||
|
teleportHome.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Runnable> onArrive = new ArrayList<>();
|
||||||
|
onArrive.add(() -> {
|
||||||
|
if (this.homeRotation != null && this.getRoom() != null) {
|
||||||
|
this.getRoomUnit().setRotation(this.homeRotation);
|
||||||
|
this.getRoom().sendComposer(new RoomUserStatusComposer(this.getRoomUnit()).compose());
|
||||||
|
}
|
||||||
|
this.persistPosition();
|
||||||
|
});
|
||||||
|
|
||||||
|
List<Runnable> onFail = new ArrayList<>();
|
||||||
|
onFail.add(teleportHome);
|
||||||
|
|
||||||
|
this.getRoomUnit().setGoalLocation(this.homeTile);
|
||||||
|
Emulator.getThreading().run(
|
||||||
|
new RoomUnitWalkToLocation(this.getRoomUnit(), this.homeTile, room, onArrive, onFail));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void persistPosition() {
|
||||||
|
this.needsUpdate(true);
|
||||||
|
this.run();
|
||||||
|
this.busy.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1046,10 +1046,22 @@ public class CatalogManager {
|
|||||||
for (Item baseItem : item.getBaseItems()) {
|
for (Item baseItem : item.getBaseItems()) {
|
||||||
for (int k = 0; k < item.getItemAmount(baseItem.getId()); k++) {
|
for (int k = 0; k < item.getItemAmount(baseItem.getId()); k++) {
|
||||||
if (baseItem.getName().startsWith("rentable_bot_") || baseItem.getName().startsWith("bot_")) {
|
if (baseItem.getName().startsWith("rentable_bot_") || baseItem.getName().startsWith("bot_")) {
|
||||||
|
String baseName = baseItem.getName();
|
||||||
String type = item.getName().replace("rentable_bot_", "");
|
String type = item.getName().replace("rentable_bot_", "");
|
||||||
type = type.replace("bot_", "");
|
type = type.replace("bot_", "");
|
||||||
type = type.replace("visitor_logger", "visitor_log");
|
type = type.replace("visitor_logger", "visitor_log");
|
||||||
|
|
||||||
|
// Permission gate keyed on the canonical base-item name
|
||||||
|
// (admin-controlled but stable), not the catalog page name
|
||||||
|
// which can be renamed and bypass the check.
|
||||||
|
if (("bot_" + com.eu.habbo.habbohotel.bots.FrankBot.BOT_TYPE).equals(baseName)
|
||||||
|
|| ("rentable_bot_" + com.eu.habbo.habbohotel.bots.FrankBot.BOT_TYPE).equals(baseName)) {
|
||||||
|
if (!habbo.getClient().getHabbo().hasPermission(com.eu.habbo.habbohotel.bots.FrankBot.PERMISSION_USE)) {
|
||||||
|
habbo.getClient().sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
THashMap<String, String> data = new THashMap<>();
|
THashMap<String, String> data = new THashMap<>();
|
||||||
|
|
||||||
for (String s : item.getExtradata().split(";")) {
|
for (String s : item.getExtradata().split(";")) {
|
||||||
|
|||||||
@@ -48,6 +48,12 @@ public class Item implements ISerialize {
|
|||||||
return item.getName().toLowerCase().startsWith("a0 pet");
|
return item.getName().toLowerCase().startsWith("a0 pet");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isBot(Item item) {
|
||||||
|
if (item == null) return false;
|
||||||
|
String name = item.getName();
|
||||||
|
return name != null && (name.startsWith("bot_") || name.startsWith("rentable_bot_"));
|
||||||
|
}
|
||||||
|
|
||||||
public static double getCurrentHeight(HabboItem item) {
|
public static double getCurrentHeight(HabboItem item) {
|
||||||
if (item instanceof InteractionMultiHeight && item.getBaseItem().getMultiHeights().length > 0) {
|
if (item instanceof InteractionMultiHeight && item.getBaseItem().getMultiHeights().length > 0) {
|
||||||
if (item.getExtradata().isEmpty()) {
|
if (item.getExtradata().isEmpty()) {
|
||||||
|
|||||||
+13
@@ -175,6 +175,19 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
|||||||
|
|
||||||
CatalogItem item = page.getCatalogItem(itemId);
|
CatalogItem item = page.getCatalogItem(itemId);
|
||||||
|
|
||||||
|
// Search-results gift sends the catalog offer_id as
|
||||||
|
// itemId, not catalog_items.id - see the same fix in
|
||||||
|
// CatalogBuyItemEvent. Fall back to scanning the
|
||||||
|
// page for the matching offer_id.
|
||||||
|
if (item == null) {
|
||||||
|
for (CatalogItem candidate : page.getCatalogItems().valueCollection()) {
|
||||||
|
if (candidate != null && candidate.getOfferId() == itemId) {
|
||||||
|
item = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
LOGGER.debug("catalog item null -> {}", itemId);
|
LOGGER.debug("catalog item null -> {}", itemId);
|
||||||
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR).compose());
|
||||||
|
|||||||
+41
-7
@@ -5,6 +5,7 @@ import com.eu.habbo.habbohotel.bots.BotManager;
|
|||||||
import com.eu.habbo.habbohotel.catalog.*;
|
import com.eu.habbo.habbohotel.catalog.*;
|
||||||
import com.eu.habbo.habbohotel.catalog.layouts.*;
|
import com.eu.habbo.habbohotel.catalog.layouts.*;
|
||||||
import com.eu.habbo.habbohotel.items.FurnitureType;
|
import com.eu.habbo.habbohotel.items.FurnitureType;
|
||||||
|
import com.eu.habbo.habbohotel.items.Item;
|
||||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
import com.eu.habbo.habbohotel.pets.PetManager;
|
import com.eu.habbo.habbohotel.pets.PetManager;
|
||||||
import com.eu.habbo.habbohotel.rooms.BuildersClubRoomSupport;
|
import com.eu.habbo.habbohotel.rooms.BuildersClubRoomSupport;
|
||||||
@@ -201,15 +202,48 @@ public class CatalogBuyItemEvent extends MessageHandler {
|
|||||||
|
|
||||||
else
|
else
|
||||||
item = page.getCatalogItem(itemId);
|
item = page.getCatalogItem(itemId);
|
||||||
// temp patch, can a dev with better knowledge than me look into this asap pls.
|
|
||||||
if (page instanceof BotsLayout) {
|
// Search-results buy sends the catalog offer_id as itemId
|
||||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_UNLIMITED_BOTS) && this.client.getHabbo().getInventory().getBotsComponent().getBots().size() >= BotManager.MAXIMUM_BOT_INVENTORY_SIZE) {
|
// (FurnitureOffer.offerId is derived from furnidata's
|
||||||
this.client.getHabbo().alert(Emulator.getTexts().getValue("error.bots.max.inventory").replace("%amount%", BotManager.MAXIMUM_BOT_INVENTORY_SIZE + ""));
|
// purchaseOfferId, which matches `catalog_items.offer_id`),
|
||||||
return;
|
// not the `catalog_items.id` primary key that getCatalogItem
|
||||||
|
// expects. Fall back to scanning the page for the matching
|
||||||
|
// offer_id so the search → buy flow works.
|
||||||
|
if (item == null && !(page instanceof RecentPurchasesLayout)) {
|
||||||
|
for (CatalogItem candidate : page.getCatalogItems().valueCollection()) {
|
||||||
|
if (candidate != null && candidate.getOfferId() == itemId) {
|
||||||
|
item = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (page instanceof PetsLayout) {
|
// Inventory cap check based on the actual base items the
|
||||||
if (!this.client.getHabbo().hasPermission(Permission.ACC_UNLIMITED_PETS) && this.client.getHabbo().getInventory().getPetsComponent().getPets().size() >= PetManager.MAXIMUM_PET_INVENTORY_SIZE) {
|
// purchase will create, not the page layout - bots/pets
|
||||||
|
// can legitimately live on bundle pages, search results,
|
||||||
|
// recent-purchases, etc., and the layout-instanceof check
|
||||||
|
// missed all those paths. Mirrors the bot/pet branches
|
||||||
|
// inside CatalogManager.purchaseItem (Item.isBot / isPet
|
||||||
|
// and the same prefix check) so detection stays in sync.
|
||||||
|
boolean itemHasBot = false;
|
||||||
|
boolean itemHasPet = false;
|
||||||
|
|
||||||
|
if (item != null) {
|
||||||
|
for (Item baseItem : item.getBaseItems()) {
|
||||||
|
if (baseItem == null) continue;
|
||||||
|
if (Item.isBot(baseItem)) itemHasBot = true;
|
||||||
|
if (Item.isPet(baseItem)) itemHasPet = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemHasBot && !this.client.getHabbo().hasPermission(Permission.ACC_UNLIMITED_BOTS)
|
||||||
|
&& this.client.getHabbo().getInventory().getBotsComponent().getBots().size() >= BotManager.MAXIMUM_BOT_INVENTORY_SIZE) {
|
||||||
|
this.client.getHabbo().alert(Emulator.getTexts().getValue("error.bots.max.inventory").replace("%amount%", BotManager.MAXIMUM_BOT_INVENTORY_SIZE + ""));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemHasPet) {
|
||||||
|
if (!this.client.getHabbo().hasPermission(Permission.ACC_UNLIMITED_PETS)
|
||||||
|
&& this.client.getHabbo().getInventory().getPetsComponent().getPets().size() >= PetManager.MAXIMUM_PET_INVENTORY_SIZE) {
|
||||||
this.client.getHabbo().alert(Emulator.getTexts().getValue("error.pets.max.inventory").replace("%amount%", PetManager.MAXIMUM_PET_INVENTORY_SIZE + ""));
|
this.client.getHabbo().alert(Emulator.getTexts().getValue("error.pets.max.inventory").replace("%amount%", PetManager.MAXIMUM_PET_INVENTORY_SIZE + ""));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-3
@@ -4,11 +4,14 @@ import com.eu.habbo.Emulator;
|
|||||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||||
import com.eu.habbo.habbohotel.rooms.*;
|
import com.eu.habbo.habbohotel.rooms.*;
|
||||||
import com.eu.habbo.habbohotel.users.Habbo;
|
import com.eu.habbo.habbohotel.users.Habbo;
|
||||||
|
import com.eu.habbo.habbohotel.users.HabboItem;
|
||||||
import com.eu.habbo.messages.ServerMessage;
|
import com.eu.habbo.messages.ServerMessage;
|
||||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
|
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
|
||||||
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys;
|
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys;
|
||||||
import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer;
|
import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer;
|
||||||
|
import com.eu.habbo.messages.outgoing.inventory.AddHabboItemComposer;
|
||||||
|
import com.eu.habbo.messages.outgoing.inventory.InventoryRefreshComposer;
|
||||||
import com.eu.habbo.messages.outgoing.rooms.ForwardToRoomComposer;
|
import com.eu.habbo.messages.outgoing.rooms.ForwardToRoomComposer;
|
||||||
import gnu.trove.set.hash.THashSet;
|
import gnu.trove.set.hash.THashSet;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -16,6 +19,8 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.StringJoiner;
|
import java.util.StringJoiner;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -26,6 +31,7 @@ public class FloorPlanEditorSaveEvent extends MessageHandler {
|
|||||||
public static int MAXIMUM_FLOORPLAN_SIZE = 64 * 64;
|
public static int MAXIMUM_FLOORPLAN_SIZE = 64 * 64;
|
||||||
|
|
||||||
private static final int SAVE_COOLDOWN_SECONDS = 3;
|
private static final int SAVE_COOLDOWN_SECONDS = 3;
|
||||||
|
private static final int MAX_AUTO_PICKUP_ITEMS = 500;
|
||||||
private static final Pattern ALLOWED_MAP_CHARS = Pattern.compile("[a-zA-Z0-9\r]+");
|
private static final Pattern ALLOWED_MAP_CHARS = Pattern.compile("[a-zA-Z0-9\r]+");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -127,6 +133,11 @@ public class FloorPlanEditorSaveEvent extends MessageHandler {
|
|||||||
errors.add("${notification.floorplan_editor.error.message.invalid_walls_fixed_height}");
|
errors.add("${notification.floorplan_editor.error.message.invalid_walls_fixed_height}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean autoPickup = false;
|
||||||
|
if (this.packet.bytesAvailable() >= 1) {
|
||||||
|
autoPickup = this.packet.readBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
if (errors.length() > 0) {
|
if (errors.length() > 0) {
|
||||||
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FLOORPLAN_EDITOR_ERROR.key, errors.toString()));
|
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FLOORPLAN_EDITOR_ERROR.key, errors.toString()));
|
||||||
return;
|
return;
|
||||||
@@ -134,6 +145,7 @@ public class FloorPlanEditorSaveEvent extends MessageHandler {
|
|||||||
|
|
||||||
THashSet<RoomTile> locked_tileList = room.getLockedTiles();
|
THashSet<RoomTile> locked_tileList = room.getLockedTiles();
|
||||||
THashSet<RoomTile> new_tileList = new THashSet<>();
|
THashSet<RoomTile> new_tileList = new THashSet<>();
|
||||||
|
THashSet<HabboItem> itemsToPickup = new THashSet<>();
|
||||||
int blockedX = -1;
|
int blockedX = -1;
|
||||||
int blockedY = -1;
|
int blockedY = -1;
|
||||||
blockingRoomItemScan:
|
blockingRoomItemScan:
|
||||||
@@ -146,6 +158,11 @@ public class FloorPlanEditorSaveEvent extends MessageHandler {
|
|||||||
short height;
|
short height;
|
||||||
|
|
||||||
if (square.equalsIgnoreCase("x") && room.getTopItemAt(x, y) != null) {
|
if (square.equalsIgnoreCase("x") && room.getTopItemAt(x, y) != null) {
|
||||||
|
if (autoPickup) {
|
||||||
|
THashSet<HabboItem> here = room.getItemsAt(x, y);
|
||||||
|
if (here != null) itemsToPickup.addAll(here);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
blockedX = x;
|
blockedX = x;
|
||||||
blockedY = y;
|
blockedY = y;
|
||||||
break blockingRoomItemScan;
|
break blockingRoomItemScan;
|
||||||
@@ -168,6 +185,11 @@ public class FloorPlanEditorSaveEvent extends MessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tile != null && tile.state != RoomTileState.INVALID && height != tile.z && room.getTopItemAt(x, y) != null) {
|
if (tile != null && tile.state != RoomTileState.INVALID && height != tile.z && room.getTopItemAt(x, y) != null) {
|
||||||
|
if (autoPickup) {
|
||||||
|
THashSet<HabboItem> here = room.getItemsAt(x, y);
|
||||||
|
if (here != null) itemsToPickup.addAll(here);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
blockedX = x;
|
blockedX = x;
|
||||||
blockedY = y;
|
blockedY = y;
|
||||||
break blockingRoomItemScan;
|
break blockingRoomItemScan;
|
||||||
@@ -178,9 +200,16 @@ public class FloorPlanEditorSaveEvent extends MessageHandler {
|
|||||||
if (blockedX < 0) {
|
if (blockedX < 0) {
|
||||||
locked_tileList.removeAll(new_tileList);
|
locked_tileList.removeAll(new_tileList);
|
||||||
if (!locked_tileList.isEmpty()) {
|
if (!locked_tileList.isEmpty()) {
|
||||||
RoomTile first = locked_tileList.iterator().next();
|
if (autoPickup) {
|
||||||
blockedX = first.x;
|
for (RoomTile lt : locked_tileList) {
|
||||||
blockedY = first.y;
|
THashSet<HabboItem> here = room.getItemsAt(lt.x, lt.y);
|
||||||
|
if (here != null) itemsToPickup.addAll(here);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RoomTile first = locked_tileList.iterator().next();
|
||||||
|
blockedX = first.x;
|
||||||
|
blockedY = first.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,6 +219,35 @@ public class FloorPlanEditorSaveEvent extends MessageHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (autoPickup && !itemsToPickup.isEmpty()) {
|
||||||
|
if (itemsToPickup.size() > MAX_AUTO_PICKUP_ITEMS) {
|
||||||
|
LOGGER.warn("Floorplan auto-pickup rejected (over cap): user={} room={} itemCount={} cap={}",
|
||||||
|
this.client.getHabbo().getHabboInfo().getId(), room.getId(), itemsToPickup.size(), MAX_AUTO_PICKUP_ITEMS);
|
||||||
|
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FLOORPLAN_EDITOR_ERROR.key,
|
||||||
|
"Too many items would be picked up (" + itemsToPickup.size() + " > " + MAX_AUTO_PICKUP_ITEMS + "). Remove some furniture manually and save again."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Integer, ArrayList<HabboItem>> byOwner = new HashMap<>();
|
||||||
|
for (HabboItem itm : itemsToPickup) {
|
||||||
|
if (itm == null) continue;
|
||||||
|
byOwner.computeIfAbsent(itm.getUserId(), k -> new ArrayList<>()).add(itm);
|
||||||
|
room.pickUpItem(itm, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<Integer, ArrayList<HabboItem>> entry : byOwner.entrySet()) {
|
||||||
|
Habbo owner = Emulator.getGameEnvironment().getHabboManager().getHabbo(entry.getKey());
|
||||||
|
if (owner == null) continue;
|
||||||
|
for (HabboItem itm : entry.getValue()) {
|
||||||
|
owner.getClient().sendResponse(new AddHabboItemComposer(itm));
|
||||||
|
}
|
||||||
|
owner.getClient().sendResponse(new InventoryRefreshComposer());
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Floorplan auto-pickup: user={} room={} itemCount={} owners={}",
|
||||||
|
this.client.getHabbo().getHabboInfo().getId(), room.getId(), itemsToPickup.size(), byOwner.size());
|
||||||
|
}
|
||||||
|
|
||||||
RoomLayout layout = room.getLayout();
|
RoomLayout layout = room.getLayout();
|
||||||
|
|
||||||
if (layout instanceof CustomRoomLayout) {
|
if (layout instanceof CustomRoomLayout) {
|
||||||
|
|||||||
+3
@@ -34,6 +34,9 @@ public class BotSaveSettingsEvent extends MessageHandler {
|
|||||||
if (bot == null)
|
if (bot == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (bot.getOwnerActionIds().length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
int settingId = this.packet.readInt();
|
int settingId = this.packet.readInt();
|
||||||
|
|
||||||
switch (settingId) {
|
switch (settingId) {
|
||||||
|
|||||||
+16
-28
@@ -86,7 +86,7 @@ public class RoomUsersComposer extends MessageComposer {
|
|||||||
this.response.appendInt(habbo.getHabboInfo().getId());
|
this.response.appendInt(habbo.getHabboInfo().getId());
|
||||||
this.response.appendString(habbo.getHabboInfo().getUsername());
|
this.response.appendString(habbo.getHabboInfo().getUsername());
|
||||||
this.response.appendString(habbo.getHabboInfo().getMotto());
|
this.response.appendString(habbo.getHabboInfo().getMotto());
|
||||||
this.response.appendInt(habbo.getHabboInfo().getInfostandBg());
|
this.response.appendInt(habbo.getHabboInfo().getInfostandBg());
|
||||||
this.response.appendInt(habbo.getHabboInfo().getInfostandStand());
|
this.response.appendInt(habbo.getHabboInfo().getInfostandStand());
|
||||||
this.response.appendInt(habbo.getHabboInfo().getInfostandOverlay());
|
this.response.appendInt(habbo.getHabboInfo().getInfostandOverlay());
|
||||||
this.response.appendInt(habbo.getHabboInfo().getInfostandCardBg());
|
this.response.appendInt(habbo.getHabboInfo().getInfostandCardBg());
|
||||||
@@ -129,7 +129,7 @@ public class RoomUsersComposer extends MessageComposer {
|
|||||||
this.response.appendInt(0 - this.bot.getId());
|
this.response.appendInt(0 - this.bot.getId());
|
||||||
this.response.appendString(this.bot.getName());
|
this.response.appendString(this.bot.getName());
|
||||||
this.response.appendString(this.bot.getMotto());
|
this.response.appendString(this.bot.getMotto());
|
||||||
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);
|
||||||
@@ -143,17 +143,11 @@ public class RoomUsersComposer extends MessageComposer {
|
|||||||
this.response.appendString(this.bot.getGender().name().toUpperCase());
|
this.response.appendString(this.bot.getGender().name().toUpperCase());
|
||||||
this.response.appendInt(this.bot.getOwnerId());
|
this.response.appendInt(this.bot.getOwnerId());
|
||||||
this.response.appendString(this.bot.getOwnerName());
|
this.response.appendString(this.bot.getOwnerName());
|
||||||
this.response.appendInt(10);
|
short[] singleActions = this.bot.getOwnerActionIds();
|
||||||
this.response.appendShort(0);
|
this.response.appendInt(singleActions.length);
|
||||||
this.response.appendShort(1);
|
for (short action : singleActions) {
|
||||||
this.response.appendShort(2);
|
this.response.appendShort(action);
|
||||||
this.response.appendShort(3);
|
}
|
||||||
this.response.appendShort(4);
|
|
||||||
this.response.appendShort(5);
|
|
||||||
this.response.appendShort(6);
|
|
||||||
this.response.appendShort(7);
|
|
||||||
this.response.appendShort(8);
|
|
||||||
this.response.appendShort(9);
|
|
||||||
this.response.appendString("unknown");
|
this.response.appendString("unknown");
|
||||||
this.response.appendInt(0);
|
this.response.appendInt(0);
|
||||||
this.response.appendInt(0);
|
this.response.appendInt(0);
|
||||||
@@ -163,10 +157,10 @@ public class RoomUsersComposer extends MessageComposer {
|
|||||||
this.response.appendInt(0 - bot.getId());
|
this.response.appendInt(0 - bot.getId());
|
||||||
this.response.appendString(bot.getName());
|
this.response.appendString(bot.getName());
|
||||||
this.response.appendString(bot.getMotto());
|
this.response.appendString(bot.getMotto());
|
||||||
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.appendString(bot.getFigure());
|
this.response.appendString(bot.getFigure());
|
||||||
this.response.appendInt(bot.getRoomUnit().getId());
|
this.response.appendInt(bot.getRoomUnit().getId());
|
||||||
this.response.appendInt(bot.getRoomUnit().getX());
|
this.response.appendInt(bot.getRoomUnit().getX());
|
||||||
@@ -177,17 +171,11 @@ public class RoomUsersComposer extends MessageComposer {
|
|||||||
this.response.appendString(bot.getGender().name().toUpperCase());
|
this.response.appendString(bot.getGender().name().toUpperCase());
|
||||||
this.response.appendInt(bot.getOwnerId());
|
this.response.appendInt(bot.getOwnerId());
|
||||||
this.response.appendString(bot.getOwnerName());
|
this.response.appendString(bot.getOwnerName());
|
||||||
this.response.appendInt(10);
|
short[] listActions = bot.getOwnerActionIds();
|
||||||
this.response.appendShort(0);
|
this.response.appendInt(listActions.length);
|
||||||
this.response.appendShort(1);
|
for (short action : listActions) {
|
||||||
this.response.appendShort(2);
|
this.response.appendShort(action);
|
||||||
this.response.appendShort(3);
|
}
|
||||||
this.response.appendShort(4);
|
|
||||||
this.response.appendShort(5);
|
|
||||||
this.response.appendShort(6);
|
|
||||||
this.response.appendShort(7);
|
|
||||||
this.response.appendShort(8);
|
|
||||||
this.response.appendShort(9);
|
|
||||||
this.response.appendString("unknown");
|
this.response.appendString("unknown");
|
||||||
this.response.appendInt(0);
|
this.response.appendInt(0);
|
||||||
this.response.appendInt(0);
|
this.response.appendInt(0);
|
||||||
|
|||||||
Reference in New Issue
Block a user