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
@@ -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');
|
||||||
@@ -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(";")) {
|
||||||
|
|||||||
+4
@@ -175,6 +175,10 @@ 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) {
|
if (item == null) {
|
||||||
for (CatalogItem candidate : page.getCatalogItems().valueCollection()) {
|
for (CatalogItem candidate : page.getCatalogItems().valueCollection()) {
|
||||||
if (candidate != null && candidate.getOfferId() == itemId) {
|
if (candidate != null && candidate.getOfferId() == itemId) {
|
||||||
|
|||||||
+18
-2
@@ -14,7 +14,11 @@ import com.eu.habbo.habbohotel.users.HabboBadge;
|
|||||||
import com.eu.habbo.habbohotel.users.HabboInventory;
|
import com.eu.habbo.habbohotel.users.HabboInventory;
|
||||||
import com.eu.habbo.habbohotel.users.subscriptions.Subscription;
|
import com.eu.habbo.habbohotel.users.subscriptions.Subscription;
|
||||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||||
import com.eu.habbo.messages.outgoing.catalog.*;
|
import com.eu.habbo.messages.outgoing.catalog.AlertPurchaseFailedComposer;
|
||||||
|
import com.eu.habbo.messages.outgoing.catalog.AlertPurchaseUnavailableComposer;
|
||||||
|
import com.eu.habbo.messages.outgoing.catalog.BuildersClubFurniCountComposer;
|
||||||
|
import com.eu.habbo.messages.outgoing.catalog.BuildersClubSubscriptionStatusComposer;
|
||||||
|
import com.eu.habbo.messages.outgoing.catalog.PurchaseOKComposer;
|
||||||
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.HotelWillCloseInMinutesComposer;
|
import com.eu.habbo.messages.outgoing.generic.alerts.HotelWillCloseInMinutesComposer;
|
||||||
@@ -199,6 +203,12 @@ public class CatalogBuyItemEvent extends MessageHandler {
|
|||||||
else
|
else
|
||||||
item = page.getCatalogItem(itemId);
|
item = page.getCatalogItem(itemId);
|
||||||
|
|
||||||
|
// Search-results buy sends the catalog offer_id as itemId
|
||||||
|
// (FurnitureOffer.offerId is derived from furnidata's
|
||||||
|
// purchaseOfferId, which matches `catalog_items.offer_id`),
|
||||||
|
// 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)) {
|
if (item == null && !(page instanceof RecentPurchasesLayout)) {
|
||||||
for (CatalogItem candidate : page.getCatalogItems().valueCollection()) {
|
for (CatalogItem candidate : page.getCatalogItems().valueCollection()) {
|
||||||
if (candidate != null && candidate.getOfferId() == itemId) {
|
if (candidate != null && candidate.getOfferId() == itemId) {
|
||||||
@@ -207,7 +217,13 @@ public class CatalogBuyItemEvent extends MessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Inventory cap check based on the actual base items the
|
||||||
|
// 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 itemHasBot = false;
|
||||||
boolean itemHasPet = false;
|
boolean itemHasPet = false;
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -17,7 +17,11 @@ import gnu.trove.set.hash.THashSet;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.StringJoiner;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class FloorPlanEditorSaveEvent extends MessageHandler {
|
public class FloorPlanEditorSaveEvent extends MessageHandler {
|
||||||
|
|||||||
+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