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
Merge branch 'duckietm:main' into Dev
This commit is contained in:
@@ -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', 1002),
|
||||
('cola;habbo cola', 19),
|
||||
('rose', 1000),
|
||||
('book', 1003),
|
||||
('tea', 27),
|
||||
('coffee', 8),
|
||||
('migraine;headache;pills', 1015),
|
||||
('radioactive liquid;radioactive', 30),
|
||||
('turkey;can of turkey', 70);
|
||||
|
||||
-- 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 (19001, 0, 'bot_frank', 'Frank', 1, 1, 0.00, '0', '0', '0', '1', '0', '0', '0', '0', '0', 'r', 'default', 1, '0', '0', '0');
|
||||
|
||||
INSERT IGNORE INTO `catalog_items` (`item_ids`, `page_id`, `offer_id`, `catalog_name`, `cost_credits`, `cost_points`, `points_type`, `amount`, `extradata`)
|
||||
VALUES ('19001', 8, 19001, 'Frank', 0, 0, 0, 1, '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');
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>com.eu.habbo</groupId>
|
||||
<artifactId>Habbo</artifactId>
|
||||
<version>4.2.21</version>
|
||||
<version>4.2.24</version>
|
||||
|
||||
<properties>
|
||||
<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("bartender", ButlerBot.class);
|
||||
addBotDefinition("visitor_log", VisitorBot.class);
|
||||
addBotDefinition(FrankBot.BOT_TYPE, FrankBot.class);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1099,10 +1099,22 @@ public class CatalogManager {
|
||||
for (Item baseItem : item.getBaseItems()) {
|
||||
for (int k = 0; k < item.getItemAmount(baseItem.getId()); k++) {
|
||||
if (baseItem.getName().startsWith("rentable_bot_") || baseItem.getName().startsWith("bot_")) {
|
||||
String baseName = baseItem.getName();
|
||||
String type = item.getName().replace("rentable_bot_", "");
|
||||
type = type.replace("bot_", "");
|
||||
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<>();
|
||||
|
||||
for (String s : item.getExtradata().split(";")) {
|
||||
|
||||
+4
@@ -175,6 +175,10 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler {
|
||||
|
||||
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) {
|
||||
|
||||
+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.subscriptions.Subscription;
|
||||
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.BubbleAlertKeys;
|
||||
import com.eu.habbo.messages.outgoing.generic.alerts.HotelWillCloseInMinutesComposer;
|
||||
@@ -199,6 +203,12 @@ public class CatalogBuyItemEvent extends MessageHandler {
|
||||
else
|
||||
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)) {
|
||||
for (CatalogItem candidate : page.getCatalogItems().valueCollection()) {
|
||||
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 itemHasPet = false;
|
||||
|
||||
|
||||
+5
-1
@@ -17,7 +17,11 @@ import gnu.trove.set.hash.THashSet;
|
||||
import org.slf4j.Logger;
|
||||
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;
|
||||
|
||||
public class FloorPlanEditorSaveEvent extends MessageHandler {
|
||||
|
||||
+3
@@ -34,6 +34,9 @@ public class BotSaveSettingsEvent extends MessageHandler {
|
||||
if (bot == null)
|
||||
return;
|
||||
|
||||
if (bot.getOwnerActionIds().length == 0)
|
||||
return;
|
||||
|
||||
int settingId = this.packet.readInt();
|
||||
|
||||
switch (settingId) {
|
||||
|
||||
+16
-28
@@ -86,7 +86,7 @@ public class RoomUsersComposer extends MessageComposer {
|
||||
this.response.appendInt(habbo.getHabboInfo().getId());
|
||||
this.response.appendString(habbo.getHabboInfo().getUsername());
|
||||
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().getInfostandOverlay());
|
||||
this.response.appendInt(habbo.getHabboInfo().getInfostandCardBg());
|
||||
@@ -129,7 +129,7 @@ public class RoomUsersComposer extends MessageComposer {
|
||||
this.response.appendInt(0 - this.bot.getId());
|
||||
this.response.appendString(this.bot.getName());
|
||||
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);
|
||||
@@ -143,17 +143,11 @@ public class RoomUsersComposer extends MessageComposer {
|
||||
this.response.appendString(this.bot.getGender().name().toUpperCase());
|
||||
this.response.appendInt(this.bot.getOwnerId());
|
||||
this.response.appendString(this.bot.getOwnerName());
|
||||
this.response.appendInt(10);
|
||||
this.response.appendShort(0);
|
||||
this.response.appendShort(1);
|
||||
this.response.appendShort(2);
|
||||
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);
|
||||
short[] singleActions = this.bot.getOwnerActionIds();
|
||||
this.response.appendInt(singleActions.length);
|
||||
for (short action : singleActions) {
|
||||
this.response.appendShort(action);
|
||||
}
|
||||
this.response.appendString("unknown");
|
||||
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.appendString(bot.getName());
|
||||
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.appendInt(bot.getRoomUnit().getId());
|
||||
this.response.appendInt(bot.getRoomUnit().getX());
|
||||
@@ -177,17 +171,11 @@ public class RoomUsersComposer extends MessageComposer {
|
||||
this.response.appendString(bot.getGender().name().toUpperCase());
|
||||
this.response.appendInt(bot.getOwnerId());
|
||||
this.response.appendString(bot.getOwnerName());
|
||||
this.response.appendInt(10);
|
||||
this.response.appendShort(0);
|
||||
this.response.appendShort(1);
|
||||
this.response.appendShort(2);
|
||||
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);
|
||||
short[] listActions = bot.getOwnerActionIds();
|
||||
this.response.appendInt(listActions.length);
|
||||
for (short action : listActions) {
|
||||
this.response.appendShort(action);
|
||||
}
|
||||
this.response.appendString("unknown");
|
||||
this.response.appendInt(0);
|
||||
this.response.appendInt(0);
|
||||
|
||||
Reference in New Issue
Block a user