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
Improve emulator monitoring and wired stability safeguards
This commit is contained in:
@@ -6,6 +6,7 @@ import ch.qos.logback.core.ConsoleAppender;
|
||||
import com.eu.habbo.core.*;
|
||||
import com.eu.habbo.core.consolecommands.ConsoleCommand;
|
||||
import com.eu.habbo.database.Database;
|
||||
import com.eu.habbo.gui.EmulatorDashboard;
|
||||
import com.eu.habbo.habbohotel.GameEnvironment;
|
||||
import com.eu.habbo.habbohotel.gameclients.SessionResumeManager;
|
||||
import com.eu.habbo.networking.gameserver.GameServer;
|
||||
@@ -186,6 +187,10 @@ public final class Emulator {
|
||||
Emulator.isReady = true;
|
||||
Emulator.timeStarted = getIntUnixTimestamp();
|
||||
|
||||
if (Emulator.getConfig().getBoolean("gui.enabled", true)) {
|
||||
EmulatorDashboard.launch();
|
||||
}
|
||||
|
||||
if (Emulator.getConfig().getInt("runtime.threads") < (Runtime.getRuntime().availableProcessors() * 2)) {
|
||||
LOGGER.warn("Emulator settings runtime.threads ({}) can be increased to ({}) to possibly increase performance.",
|
||||
Emulator.getConfig().getInt("runtime.threads"),
|
||||
|
||||
@@ -0,0 +1,631 @@
|
||||
package com.eu.habbo.gui;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.monitoring.EmulatorStatsService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.CompoundBorder;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.border.MatteBorder;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.JTableHeader;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class EmulatorDashboard extends JFrame {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(EmulatorDashboard.class);
|
||||
|
||||
// Modern Dark Theme Colors
|
||||
private static final Color COLOR_BG = new Color(18, 18, 18);
|
||||
private static final Color COLOR_SURFACE = new Color(30, 30, 30);
|
||||
private static final Color COLOR_SURFACE_HOVER = new Color(45, 45, 45);
|
||||
private static final Color COLOR_PRIMARY = new Color(99, 102, 241);
|
||||
private static final Color COLOR_PRIMARY_SOFT = new Color(99, 102, 241, 45);
|
||||
private static final Color COLOR_SUCCESS = new Color(34, 197, 94);
|
||||
private static final Color COLOR_WARNING = new Color(245, 158, 11);
|
||||
private static final Color COLOR_TEXT = new Color(240, 240, 240);
|
||||
private static final Color COLOR_TEXT_MUTED = new Color(150, 150, 150);
|
||||
private static final Color COLOR_TEXT_SUBTLE = new Color(110, 110, 110);
|
||||
private static final Color COLOR_BORDER = new Color(50, 50, 50);
|
||||
private static final Font FONT_TITLE = new Font("Segoe UI", Font.BOLD, 26);
|
||||
private static final Font FONT_SECTION = new Font("Segoe UI", Font.BOLD, 16);
|
||||
private static final Font FONT_SMALL = new Font("Segoe UI", Font.PLAIN, 12);
|
||||
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
|
||||
private static EmulatorDashboard instance;
|
||||
|
||||
// Overview Tab
|
||||
private final JLabel memLabel = createMetricLabel();
|
||||
private final JLabel cpuLabel = createMetricLabel();
|
||||
private final JLabel threadLabel = createMetricLabel();
|
||||
private final JLabel usersLabel = createMetricLabel();
|
||||
private final JLabel roomsLabel = createMetricLabel();
|
||||
private final JLabel wiredLabel = createMetricLabel();
|
||||
private final JLabel uptimeLabel = createStatusValueLabel();
|
||||
private final JLabel lastUpdatedLabel = createStatusValueLabel();
|
||||
private final JLabel footerStatusLabel = createStatusValueLabel();
|
||||
private final MemoryGraphPanel memoryGraph = new MemoryGraphPanel();
|
||||
|
||||
// Tables
|
||||
private final DefaultTableModel usersTableModel;
|
||||
private final DefaultTableModel roomsTableModel;
|
||||
private final DefaultTableModel wiredTableModel;
|
||||
private final JTable usersTable;
|
||||
private final JTable roomsTable;
|
||||
private final JTable wiredTable;
|
||||
private final JLabel usersCountLabel = createCountLabel();
|
||||
private final JLabel roomsCountLabel = createCountLabel();
|
||||
private final JLabel wiredCountLabel = createCountLabel();
|
||||
|
||||
// UI Components
|
||||
private final JPanel cardsPanel;
|
||||
private final CardLayout cardLayout;
|
||||
private final Map<String, JPanel> navButtons = new HashMap<>();
|
||||
private String selectedCardName = "Overview";
|
||||
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
Thread t = new Thread(r, "Dashboard-Updater");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
|
||||
private EmulatorDashboard() {
|
||||
setTitle("Arcturus Morningstar - System Dashboard");
|
||||
setSize(1100, 700);
|
||||
setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
|
||||
setLocationRelativeTo(null);
|
||||
getContentPane().setBackground(COLOR_BG);
|
||||
setLayout(new BorderLayout());
|
||||
|
||||
// Setup custom Look & Feel basics to remove weird Swing borders
|
||||
UIManager.put("ScrollBar.background", COLOR_BG);
|
||||
UIManager.put("ScrollBar.thumb", COLOR_SURFACE_HOVER);
|
||||
|
||||
// Sidebar
|
||||
JPanel sidebar = new JPanel();
|
||||
sidebar.setLayout(new BoxLayout(sidebar, BoxLayout.Y_AXIS));
|
||||
sidebar.setBackground(COLOR_SURFACE);
|
||||
sidebar.setPreferredSize(new Dimension(220, 0));
|
||||
sidebar.setBorder(new MatteBorder(0, 0, 0, 1, COLOR_BORDER));
|
||||
|
||||
// Sidebar Header
|
||||
JPanel brandPanel = new JPanel(new BorderLayout());
|
||||
brandPanel.setBackground(COLOR_SURFACE);
|
||||
brandPanel.setBorder(new EmptyBorder(20, 20, 30, 20));
|
||||
|
||||
JLabel brandTitle = new JLabel("Arcturus");
|
||||
brandTitle.setFont(new Font("Segoe UI", Font.BOLD, 22));
|
||||
brandTitle.setForeground(COLOR_TEXT);
|
||||
|
||||
JLabel brandSub = new JLabel("v" + Emulator.version);
|
||||
brandSub.setFont(new Font("Segoe UI", Font.PLAIN, 12));
|
||||
brandSub.setForeground(COLOR_PRIMARY);
|
||||
|
||||
brandPanel.add(brandTitle, BorderLayout.NORTH);
|
||||
brandPanel.add(brandSub, BorderLayout.SOUTH);
|
||||
sidebar.add(brandPanel);
|
||||
|
||||
// Main Cards
|
||||
cardLayout = new CardLayout();
|
||||
cardsPanel = new JPanel(cardLayout);
|
||||
cardsPanel.setBackground(COLOR_BG);
|
||||
|
||||
// Setup Tabs
|
||||
usersTableModel = createTableModel(new String[]{"ID", "Username", "Rank", "Credits", "Room ID"});
|
||||
roomsTableModel = createTableModel(new String[]{"Room ID", "Name", "Players", "Items", "Tickables", "CPU (ms)", "Est. RAM (KB)", "Thread"});
|
||||
wiredTableModel = createTableModel(new String[]{"Room ID", "Avg Tick", "Peak Tick", "Usage %", "Delayed", "Overloaded?", "Heavy?"});
|
||||
usersTable = createDashboardTable(usersTableModel);
|
||||
roomsTable = createDashboardTable(roomsTableModel);
|
||||
wiredTable = createDashboardTable(wiredTableModel);
|
||||
|
||||
cardsPanel.add(createOverviewTab(), "Overview");
|
||||
cardsPanel.add(createTableTab("Online Users", "Players currently connected to the emulator.", usersTable, usersCountLabel), "Online Users");
|
||||
cardsPanel.add(createTableTab("Active Rooms", "Loaded rooms with lightweight performance indicators.", roomsTable, roomsCountLabel), "Active Rooms");
|
||||
cardsPanel.add(createTableTab("Wired Diagnostics", "Rooms currently using wired timing, delay and execution budget.", wiredTable, wiredCountLabel), "Wired Diagnostics");
|
||||
|
||||
// Sidebar Navigation
|
||||
sidebar.add(createNavButton("Overview", "Overview"));
|
||||
sidebar.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
sidebar.add(createNavButton("Online Users", "Online Users"));
|
||||
sidebar.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
sidebar.add(createNavButton("Active Rooms", "Active Rooms"));
|
||||
sidebar.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
sidebar.add(createNavButton("Wired Diagnostics", "Wired Diagnostics"));
|
||||
sidebar.add(Box.createVerticalGlue());
|
||||
|
||||
add(sidebar, BorderLayout.WEST);
|
||||
add(cardsPanel, BorderLayout.CENTER);
|
||||
add(createStatusBar(), BorderLayout.SOUTH);
|
||||
|
||||
addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentShown(ComponentEvent e) {
|
||||
setActiveCard("Overview");
|
||||
}
|
||||
});
|
||||
|
||||
// Start updates
|
||||
scheduler.scheduleAtFixedRate(this::updateMetrics, 0, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private DefaultTableModel createTableModel(String[] cols) {
|
||||
return new DefaultTableModel(cols, 0) {
|
||||
@Override public boolean isCellEditable(int row, int column) { return false; }
|
||||
};
|
||||
}
|
||||
|
||||
private JPanel createNavButton(String text, String cardName) {
|
||||
JPanel btn = new JPanel(new BorderLayout());
|
||||
btn.setBackground(COLOR_SURFACE);
|
||||
btn.setMaximumSize(new Dimension(220, 45));
|
||||
btn.setBorder(new EmptyBorder(0, 18, 0, 0));
|
||||
btn.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
navButtons.put(cardName, btn);
|
||||
|
||||
JLabel lbl = new JLabel(text);
|
||||
lbl.setFont(new Font("Segoe UI", Font.BOLD, 14));
|
||||
lbl.setForeground(COLOR_TEXT_MUTED);
|
||||
btn.add(lbl, BorderLayout.CENTER);
|
||||
|
||||
btn.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
btn.setBackground(COLOR_SURFACE_HOVER);
|
||||
lbl.setForeground(COLOR_TEXT);
|
||||
}
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
updateNavButtonStyle(cardName, btn, lbl);
|
||||
}
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
setActiveCard(cardName);
|
||||
}
|
||||
});
|
||||
|
||||
updateNavButtonStyle(cardName, btn, lbl);
|
||||
return btn;
|
||||
}
|
||||
|
||||
private JPanel createOverviewTab() {
|
||||
JPanel wrapper = new JPanel(new BorderLayout());
|
||||
wrapper.setBackground(COLOR_BG);
|
||||
wrapper.setBorder(new EmptyBorder(30, 30, 30, 30));
|
||||
|
||||
JPanel header = new JPanel(new BorderLayout(0, 14));
|
||||
header.setOpaque(false);
|
||||
|
||||
JLabel title = new JLabel("Dashboard Overview");
|
||||
title.setFont(FONT_TITLE);
|
||||
title.setForeground(COLOR_TEXT);
|
||||
|
||||
JLabel subtitle = new JLabel("Operational view of emulator health, activity and wired performance.");
|
||||
subtitle.setFont(new Font("Segoe UI", Font.PLAIN, 13));
|
||||
subtitle.setForeground(COLOR_TEXT_MUTED);
|
||||
|
||||
JPanel titleBlock = new JPanel();
|
||||
titleBlock.setOpaque(false);
|
||||
titleBlock.setLayout(new BoxLayout(titleBlock, BoxLayout.Y_AXIS));
|
||||
titleBlock.add(title);
|
||||
titleBlock.add(Box.createRigidArea(new Dimension(0, 4)));
|
||||
titleBlock.add(subtitle);
|
||||
|
||||
header.add(titleBlock, BorderLayout.NORTH);
|
||||
header.add(createOverviewMetaPanel(), BorderLayout.SOUTH);
|
||||
wrapper.add(header, BorderLayout.NORTH);
|
||||
|
||||
JPanel content = new JPanel(new GridLayout(1, 2, 20, 20));
|
||||
content.setOpaque(false);
|
||||
content.setBorder(new EmptyBorder(20, 0, 0, 0));
|
||||
|
||||
// Left Stats
|
||||
JPanel statsPanel = new JPanel(new GridLayout(3, 2, 12, 12));
|
||||
statsPanel.setOpaque(false);
|
||||
statsPanel.add(createMetricCard("Memory Allocation", memLabel));
|
||||
statsPanel.add(createMetricCard("CPU Load", cpuLabel));
|
||||
statsPanel.add(createMetricCard("Active OS Threads", threadLabel));
|
||||
statsPanel.add(createMetricCard("Connected Players", usersLabel));
|
||||
statsPanel.add(createMetricCard("Loaded Rooms", roomsLabel));
|
||||
statsPanel.add(createMetricCard("Wired Tickables", wiredLabel));
|
||||
content.add(statsPanel);
|
||||
|
||||
// Right Graph
|
||||
JPanel graphContainer = new JPanel(new BorderLayout());
|
||||
graphContainer.setBackground(COLOR_SURFACE);
|
||||
graphContainer.setBorder(BorderFactory.createCompoundBorder(
|
||||
new MatteBorder(1, 1, 1, 1, COLOR_BORDER),
|
||||
new EmptyBorder(15, 15, 15, 15)
|
||||
));
|
||||
|
||||
JLabel gTitle = new JLabel("Realtime Memory Usage");
|
||||
gTitle.setFont(FONT_SECTION);
|
||||
gTitle.setForeground(COLOR_TEXT_MUTED);
|
||||
gTitle.setBorder(new EmptyBorder(0, 0, 15, 0));
|
||||
graphContainer.add(gTitle, BorderLayout.NORTH);
|
||||
graphContainer.add(memoryGraph, BorderLayout.CENTER);
|
||||
content.add(graphContainer);
|
||||
|
||||
wrapper.add(content, BorderLayout.CENTER);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
private JPanel createMetricCard(String title, JLabel valueLabel) {
|
||||
JPanel card = new JPanel(new BorderLayout());
|
||||
card.setBackground(COLOR_SURFACE);
|
||||
card.setBorder(BorderFactory.createCompoundBorder(
|
||||
new MatteBorder(1, 1, 1, 1, COLOR_BORDER),
|
||||
new EmptyBorder(15, 20, 15, 20)
|
||||
));
|
||||
|
||||
JLabel tLabel = new JLabel(title);
|
||||
tLabel.setFont(new Font("Segoe UI", Font.BOLD, 13));
|
||||
tLabel.setForeground(COLOR_TEXT_MUTED);
|
||||
|
||||
card.add(tLabel, BorderLayout.NORTH);
|
||||
card.add(valueLabel, BorderLayout.SOUTH);
|
||||
return card;
|
||||
}
|
||||
|
||||
private JLabel createMetricLabel() {
|
||||
JLabel label = new JLabel("-");
|
||||
label.setFont(new Font("Segoe UI", Font.BOLD, 28));
|
||||
label.setForeground(COLOR_TEXT);
|
||||
return label;
|
||||
}
|
||||
|
||||
private JPanel createOverviewMetaPanel() {
|
||||
JPanel panel = new JPanel(new GridLayout(1, 3, 12, 12));
|
||||
panel.setOpaque(false);
|
||||
panel.add(createStatusCard("Uptime", uptimeLabel, COLOR_PRIMARY));
|
||||
panel.add(createStatusCard("Last Refresh", lastUpdatedLabel, COLOR_SUCCESS));
|
||||
panel.add(createStatusCard("GUI Status", footerStatusLabel, COLOR_WARNING));
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel createStatusCard(String title, JLabel valueLabel, Color accent) {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
panel.setBackground(COLOR_SURFACE);
|
||||
panel.setBorder(BorderFactory.createCompoundBorder(
|
||||
new MatteBorder(1, 1, 1, 1, COLOR_BORDER),
|
||||
new EmptyBorder(12, 14, 12, 14)
|
||||
));
|
||||
|
||||
JPanel accentBar = new JPanel();
|
||||
accentBar.setBackground(accent);
|
||||
accentBar.setPreferredSize(new Dimension(6, 0));
|
||||
|
||||
JPanel content = new JPanel();
|
||||
content.setOpaque(false);
|
||||
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
|
||||
|
||||
JLabel label = new JLabel(title);
|
||||
label.setFont(FONT_SMALL);
|
||||
label.setForeground(COLOR_TEXT_MUTED);
|
||||
|
||||
content.add(label);
|
||||
content.add(Box.createRigidArea(new Dimension(0, 6)));
|
||||
content.add(valueLabel);
|
||||
|
||||
panel.add(accentBar, BorderLayout.WEST);
|
||||
panel.add(content, BorderLayout.CENTER);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JLabel createStatusValueLabel() {
|
||||
JLabel label = new JLabel("-");
|
||||
label.setFont(new Font("Segoe UI", Font.BOLD, 16));
|
||||
label.setForeground(COLOR_TEXT);
|
||||
return label;
|
||||
}
|
||||
|
||||
private JLabel createCountLabel() {
|
||||
JLabel label = new JLabel("0 rows");
|
||||
label.setFont(FONT_SMALL);
|
||||
label.setForeground(COLOR_TEXT_MUTED);
|
||||
return label;
|
||||
}
|
||||
|
||||
private JTable createDashboardTable(DefaultTableModel model) {
|
||||
JTable table = new JTable(model);
|
||||
table.setBackground(COLOR_SURFACE);
|
||||
table.setForeground(COLOR_TEXT);
|
||||
table.setGridColor(COLOR_BORDER);
|
||||
table.setRowHeight(34);
|
||||
table.setFont(new Font("Segoe UI", Font.PLAIN, 13));
|
||||
table.setFillsViewportHeight(true);
|
||||
table.setSelectionBackground(COLOR_PRIMARY);
|
||||
table.setSelectionForeground(Color.WHITE);
|
||||
table.setShowVerticalLines(false);
|
||||
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
table.setAutoCreateRowSorter(true);
|
||||
|
||||
JTableHeader header = table.getTableHeader();
|
||||
header.setBackground(new Color(22, 22, 22));
|
||||
header.setForeground(COLOR_TEXT_MUTED);
|
||||
header.setFont(new Font("Segoe UI", Font.BOLD, 13));
|
||||
header.setPreferredSize(new Dimension(0, 38));
|
||||
header.setBorder(BorderFactory.createMatteBorder(1, 0, 1, 0, COLOR_BORDER));
|
||||
((DefaultTableCellRenderer) header.getDefaultRenderer()).setHorizontalAlignment(JLabel.LEFT);
|
||||
((DefaultTableCellRenderer) header.getDefaultRenderer()).setBorder(new EmptyBorder(0, 10, 0, 0));
|
||||
|
||||
table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
label.setBorder(new EmptyBorder(0, 10, 0, 10));
|
||||
label.setForeground(isSelected ? Color.WHITE : COLOR_TEXT);
|
||||
label.setBackground(isSelected ? COLOR_PRIMARY : ((row % 2 == 0) ? COLOR_SURFACE : new Color(35, 35, 35)));
|
||||
return label;
|
||||
}
|
||||
});
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private JPanel createTableTab(String title, String subtitle, JTable table, JLabel countLabel) {
|
||||
JPanel wrapper = new JPanel(new BorderLayout());
|
||||
wrapper.setBackground(COLOR_BG);
|
||||
wrapper.setBorder(new EmptyBorder(30, 30, 30, 30));
|
||||
|
||||
JPanel titlePanel = new JPanel(new BorderLayout());
|
||||
titlePanel.setOpaque(false);
|
||||
|
||||
JLabel titleLbl = new JLabel(title);
|
||||
titleLbl.setFont(FONT_TITLE);
|
||||
titleLbl.setForeground(COLOR_TEXT);
|
||||
|
||||
JLabel subtitleLbl = new JLabel(subtitle);
|
||||
subtitleLbl.setFont(new Font("Segoe UI", Font.PLAIN, 13));
|
||||
subtitleLbl.setForeground(COLOR_TEXT_MUTED);
|
||||
subtitleLbl.setBorder(new EmptyBorder(6, 0, 0, 0));
|
||||
|
||||
JPanel textPanel = new JPanel();
|
||||
textPanel.setOpaque(false);
|
||||
textPanel.setLayout(new BoxLayout(textPanel, BoxLayout.Y_AXIS));
|
||||
textPanel.add(titleLbl);
|
||||
textPanel.add(subtitleLbl);
|
||||
|
||||
titlePanel.add(textPanel, BorderLayout.WEST);
|
||||
titlePanel.add(countLabel, BorderLayout.EAST);
|
||||
wrapper.add(titlePanel, BorderLayout.NORTH);
|
||||
|
||||
JScrollPane scrollPane = new JScrollPane(table);
|
||||
scrollPane.getViewport().setBackground(COLOR_SURFACE);
|
||||
scrollPane.setBorder(new MatteBorder(1, 1, 1, 1, COLOR_BORDER));
|
||||
scrollPane.setBorder(new CompoundBorder(
|
||||
new EmptyBorder(20, 0, 0, 0),
|
||||
new MatteBorder(1, 1, 1, 1, COLOR_BORDER)
|
||||
));
|
||||
wrapper.add(scrollPane, BorderLayout.CENTER);
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
private JPanel createStatusBar() {
|
||||
JPanel statusBar = new JPanel(new BorderLayout());
|
||||
statusBar.setBackground(COLOR_SURFACE);
|
||||
statusBar.setBorder(new CompoundBorder(
|
||||
new MatteBorder(1, 0, 0, 0, COLOR_BORDER),
|
||||
new EmptyBorder(8, 14, 8, 14)
|
||||
));
|
||||
|
||||
JLabel left = new JLabel("Dashboard running locally");
|
||||
left.setFont(FONT_SMALL);
|
||||
left.setForeground(COLOR_TEXT_SUBTLE);
|
||||
|
||||
JLabel right = new JLabel("Tip: table columns are sortable");
|
||||
right.setFont(FONT_SMALL);
|
||||
right.setForeground(COLOR_TEXT_SUBTLE);
|
||||
|
||||
statusBar.add(left, BorderLayout.WEST);
|
||||
statusBar.add(right, BorderLayout.EAST);
|
||||
return statusBar;
|
||||
}
|
||||
|
||||
private void setActiveCard(String cardName) {
|
||||
selectedCardName = cardName;
|
||||
cardLayout.show(cardsPanel, cardName);
|
||||
navButtons.forEach((name, button) -> {
|
||||
JLabel label = (JLabel) button.getComponent(0);
|
||||
updateNavButtonStyle(name, button, label);
|
||||
});
|
||||
}
|
||||
|
||||
private void updateNavButtonStyle(String cardName, JPanel button, JLabel label) {
|
||||
boolean active = cardName.equals(selectedCardName);
|
||||
button.setBackground(active ? COLOR_PRIMARY_SOFT : COLOR_SURFACE);
|
||||
label.setForeground(active ? COLOR_TEXT : COLOR_TEXT_MUTED);
|
||||
}
|
||||
|
||||
private void updateMetrics() {
|
||||
try {
|
||||
EmulatorStatsService.Snapshot snapshot = EmulatorStatsService.collectSnapshot();
|
||||
EmulatorStatsService.Overview overview = snapshot.overview;
|
||||
|
||||
Object[][] usersData = new Object[snapshot.users.size()][5];
|
||||
for (int i = 0; i < snapshot.users.size(); i++) {
|
||||
EmulatorStatsService.OnlineUserRow user = snapshot.users.get(i);
|
||||
usersData[i] = new Object[]{user.id, user.username, user.rank, user.credits, user.roomId};
|
||||
}
|
||||
|
||||
Object[][] roomsData = new Object[snapshot.rooms.size()][8];
|
||||
for (int i = 0; i < snapshot.rooms.size(); i++) {
|
||||
EmulatorStatsService.ActiveRoomRow room = snapshot.rooms.get(i);
|
||||
roomsData[i] = new Object[]{
|
||||
room.roomId,
|
||||
room.name,
|
||||
room.players,
|
||||
room.items,
|
||||
room.tickables,
|
||||
String.format("%.2f", room.cpuMs),
|
||||
room.estimatedRamKb,
|
||||
room.thread
|
||||
};
|
||||
}
|
||||
|
||||
Object[][] wiredData = new Object[snapshot.wired.size()][7];
|
||||
for (int i = 0; i < snapshot.wired.size(); i++) {
|
||||
EmulatorStatsService.WiredRoomRow wiredRoom = snapshot.wired.get(i);
|
||||
wiredData[i] = new Object[]{
|
||||
wiredRoom.roomId,
|
||||
wiredRoom.averageTickMs + " ms",
|
||||
wiredRoom.peakTickMs + " ms",
|
||||
wiredRoom.usagePercent + "%",
|
||||
wiredRoom.delayedEventsPending,
|
||||
wiredRoom.overloaded ? "YES" : "NO",
|
||||
wiredRoom.heavy ? "YES" : "NO"
|
||||
};
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
memLabel.setText(String.format("%d MB / %d MB", overview.memoryUsedMb, overview.memoryMaxMb));
|
||||
cpuLabel.setText(String.format("%.1f %%", overview.cpuLoadPercent));
|
||||
threadLabel.setText(String.valueOf(overview.activeOsThreads));
|
||||
usersLabel.setText(String.valueOf(overview.connectedPlayers));
|
||||
roomsLabel.setText(String.valueOf(overview.loadedRooms));
|
||||
wiredLabel.setText(String.valueOf(overview.wiredTickables));
|
||||
uptimeLabel.setText(EmulatorStatsService.formatDuration(overview.uptimeSeconds));
|
||||
lastUpdatedLabel.setText(LocalDateTime.now().format(TIME_FORMAT));
|
||||
footerStatusLabel.setText(overview.guiStatus);
|
||||
memoryGraph.addValue((long) overview.memoryUsedMb * 1024L * 1024L, (long) overview.memoryMaxMb * 1024L * 1024L);
|
||||
|
||||
usersTableModel.setDataVector(usersData, new String[]{"ID", "Username", "Rank", "Credits", "Room ID"});
|
||||
roomsTableModel.setDataVector(roomsData, new String[]{"Room ID", "Name", "Players", "Items", "Tickables", "CPU (ms)", "Est. RAM (KB)", "Thread"});
|
||||
wiredTableModel.setDataVector(wiredData, new String[]{"Room ID", "Avg Tick", "Peak Tick", "Usage %", "Delayed", "Overloaded?", "Heavy?"});
|
||||
usersCountLabel.setText(snapshot.users.size() + " rows");
|
||||
roomsCountLabel.setText(snapshot.rooms.size() + " rows");
|
||||
wiredCountLabel.setText(snapshot.wired.size() + " rows");
|
||||
});
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Error updating dashboard metrics", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void launch() {
|
||||
if (instance == null) {
|
||||
instance = new EmulatorDashboard();
|
||||
}
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
instance.setVisible(true);
|
||||
});
|
||||
}
|
||||
|
||||
private static class MemoryGraphPanel extends JPanel {
|
||||
private final LinkedList<Double> history = new LinkedList<>();
|
||||
private static final int MAX_POINTS = 100;
|
||||
|
||||
public MemoryGraphPanel() {
|
||||
setOpaque(false);
|
||||
}
|
||||
|
||||
public void addValue(long used, long max) {
|
||||
double percent = (double) used / (double) max;
|
||||
history.addLast(percent);
|
||||
if (history.size() > MAX_POINTS) {
|
||||
history.removeFirst();
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
|
||||
// Background grid and labels
|
||||
g2.setFont(new Font("Segoe UI", Font.PLAIN, 10));
|
||||
long maxMemRaw = Runtime.getRuntime().maxMemory();
|
||||
|
||||
for(int i = 0; i <= 4; i++) {
|
||||
int y = i == 0 ? 15 : height * i / 4;
|
||||
if (i == 4) y = height - 5;
|
||||
|
||||
g2.setColor(COLOR_BORDER);
|
||||
g2.drawLine(0, y, width, y);
|
||||
|
||||
// Draw Y-axis numbers
|
||||
g2.setColor(COLOR_TEXT_MUTED);
|
||||
long labelVal = (long) (maxMemRaw * (1.0 - (double)i / 4.0)) / 1024 / 1024;
|
||||
g2.drawString(labelVal + " MB", 5, y - 5);
|
||||
}
|
||||
|
||||
if (history.size() < 2) return;
|
||||
|
||||
double dx = (double) width / (MAX_POINTS - 1);
|
||||
Path2D path = new Path2D.Double();
|
||||
path.moveTo(0, height);
|
||||
|
||||
int i = MAX_POINTS - history.size();
|
||||
for (Double val : history) {
|
||||
double x = i * dx;
|
||||
double y = height - (val * height);
|
||||
if (i == MAX_POINTS - history.size()) {
|
||||
path.moveTo(x, y);
|
||||
} else {
|
||||
path.lineTo(x, y);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
// Draw line
|
||||
g2.setStroke(new BasicStroke(3.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
|
||||
g2.setColor(COLOR_PRIMARY);
|
||||
g2.draw(path);
|
||||
|
||||
// Fill area
|
||||
path.lineTo(width, height);
|
||||
path.lineTo((MAX_POINTS - history.size()) * dx, height);
|
||||
path.closePath();
|
||||
|
||||
GradientPaint fillPaint = new GradientPaint(
|
||||
0, 0, new Color(COLOR_PRIMARY.getRed(), COLOR_PRIMARY.getGreen(), COLOR_PRIMARY.getBlue(), 120),
|
||||
0, height, new Color(COLOR_PRIMARY.getRed(), COLOR_PRIMARY.getGreen(), COLOR_PRIMARY.getBlue(), 10)
|
||||
);
|
||||
g2.setPaint(fillPaint);
|
||||
g2.fill(path);
|
||||
|
||||
Double lastValue = history.peekLast();
|
||||
if (lastValue != null) {
|
||||
String usageLabel = String.format("Usage %.1f%%", lastValue * 100.0);
|
||||
g2.setFont(new Font("Segoe UI", Font.BOLD, 12));
|
||||
FontMetrics metrics = g2.getFontMetrics();
|
||||
int labelWidth = metrics.stringWidth(usageLabel) + 16;
|
||||
int labelHeight = 24;
|
||||
int labelX = Math.max(8, width - labelWidth - 8);
|
||||
int labelY = 8;
|
||||
|
||||
g2.setColor(new Color(0, 0, 0, 130));
|
||||
g2.fillRoundRect(labelX, labelY, labelWidth, labelHeight, 12, 12);
|
||||
g2.setColor(COLOR_TEXT);
|
||||
g2.drawString(usageLabel, labelX + 8, labelY + 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatDuration(long millis) {
|
||||
long totalSeconds = Math.max(0L, millis / 1000L);
|
||||
long hours = totalSeconds / 3600L;
|
||||
long minutes = (totalSeconds % 3600L) / 60L;
|
||||
long seconds = totalSeconds % 60L;
|
||||
return String.format("%02dh %02dm %02ds", hours, minutes, seconds);
|
||||
}
|
||||
}
|
||||
@@ -138,18 +138,20 @@ public class Bot implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
if (this.needsUpdate) {
|
||||
Room localRoom = this.room;
|
||||
RoomUnit localRoomUnit = this.roomUnit;
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE bots SET name = ?, motto = ?, figure = ?, gender = ?, user_id = ?, room_id = ?, x = ?, y = ?, z = ?, rot = ?, dance = ?, freeroam = ?, chat_lines = ?, chat_auto = ?, chat_random = ?, chat_delay = ?, effect = ?, bubble_id = ? WHERE id = ?")) {
|
||||
statement.setString(1, this.name);
|
||||
statement.setString(2, this.motto);
|
||||
statement.setString(3, this.figure);
|
||||
statement.setString(4, this.gender.toString());
|
||||
statement.setInt(5, this.ownerId);
|
||||
statement.setInt(6, this.room == null ? 0 : this.room.getId());
|
||||
statement.setInt(7, this.roomUnit == null ? 0 : this.roomUnit.getX());
|
||||
statement.setInt(8, this.roomUnit == null ? 0 : this.roomUnit.getY());
|
||||
statement.setDouble(9, this.roomUnit == null ? 0 : this.roomUnit.getZ());
|
||||
statement.setInt(10, this.roomUnit == null ? 0 : this.roomUnit.getBodyRotation().getValue());
|
||||
statement.setInt(11, this.roomUnit == null ? 0 : this.roomUnit.getDanceType().getType());
|
||||
statement.setInt(6, localRoom == null ? 0 : localRoom.getId());
|
||||
statement.setInt(7, localRoomUnit == null ? 0 : localRoomUnit.getX());
|
||||
statement.setInt(8, localRoomUnit == null ? 0 : localRoomUnit.getY());
|
||||
statement.setDouble(9, localRoomUnit == null ? 0 : localRoomUnit.getZ());
|
||||
statement.setInt(10, localRoomUnit == null ? 0 : localRoomUnit.getBodyRotation().getValue());
|
||||
statement.setInt(11, localRoomUnit == null ? 0 : localRoomUnit.getDanceType().getType());
|
||||
statement.setString(12, this.canWalk ? "1" : "0");
|
||||
StringBuilder text = new StringBuilder();
|
||||
for (String s : this.chatLines) {
|
||||
@@ -282,7 +284,7 @@ public class Bot implements Runnable {
|
||||
}
|
||||
|
||||
public void onPickUp(Habbo habbo, Room room) {
|
||||
|
||||
this.stopFollowingHabbo();
|
||||
}
|
||||
|
||||
public void onUserSay(final RoomChatMessage message) {
|
||||
|
||||
@@ -20,10 +20,13 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ButlerBot extends Bot {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ButlerBot.class);
|
||||
public static THashMap<THashSet<String>, Integer> serveItems = new THashMap<>();
|
||||
private static final ConcurrentHashMap<Pattern, Integer> serveItemsCompiled = new ConcurrentHashMap<>();
|
||||
|
||||
public ButlerBot(ResultSet set) throws SQLException {
|
||||
super(set);
|
||||
@@ -38,6 +41,7 @@ public class ButlerBot extends Bot {
|
||||
serveItems = new THashMap<>();
|
||||
|
||||
serveItems.clear();
|
||||
serveItemsCompiled.clear();
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); Statement statement = connection.createStatement(); ResultSet set = statement.executeQuery("SELECT * FROM bot_serves")) {
|
||||
while (set.next()) {
|
||||
@@ -45,6 +49,17 @@ public class ButlerBot extends Bot {
|
||||
THashSet<String> ks = new THashSet<>();
|
||||
Collections.addAll(ks, keys);
|
||||
serveItems.put(ks, set.getInt("item"));
|
||||
|
||||
for (String key : keys) {
|
||||
if (key != null && !key.trim().isEmpty()) {
|
||||
try {
|
||||
Pattern pattern = Pattern.compile("\\b" + Pattern.quote(key.toLowerCase()) + "\\b");
|
||||
serveItemsCompiled.put(pattern, set.getInt("item"));
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to compile butler bot keyword pattern: {}", key, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Caught SQL exception", e);
|
||||
@@ -53,6 +68,7 @@ public class ButlerBot extends Bot {
|
||||
|
||||
public static void dispose() {
|
||||
serveItems.clear();
|
||||
serveItemsCompiled.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,74 +82,73 @@ public class ButlerBot extends Bot {
|
||||
if (distanceBetweenBotAndHabbo <= Emulator.getConfig().getInt("hotel.bot.butler.commanddistance")) {
|
||||
|
||||
if (message.getUnfilteredMessage() != null) {
|
||||
for (Map.Entry<THashSet<String>, Integer> set : serveItems.entrySet()) {
|
||||
for (String keyword : set.getKey()) {
|
||||
String unfilteredLower = message.getUnfilteredMessage().toLowerCase();
|
||||
for (Map.Entry<Pattern, Integer> entry : serveItemsCompiled.entrySet()) {
|
||||
Pattern pattern = entry.getKey();
|
||||
if (pattern.matcher(unfilteredLower).matches()) {
|
||||
int itemId = entry.getValue();
|
||||
String keyword = pattern.pattern().replace("\\b", "").replace("\\Q", "").replace("\\E", "");
|
||||
|
||||
// Check if the string contains a certain keyword using a regex.
|
||||
// If keyword = tea, teapot wouldn't trigger it.
|
||||
if (message.getUnfilteredMessage().toLowerCase().matches("\\b" + keyword + "\\b")) {
|
||||
|
||||
// Enable plugins to cancel this event
|
||||
BotServerItemEvent serveEvent = new BotServerItemEvent(this, message.getHabbo(), set.getValue());
|
||||
if (Emulator.getPluginManager().fireEvent(serveEvent).isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start give handitem process
|
||||
if (this.getRoomUnit().canWalk()) {
|
||||
final String key = keyword;
|
||||
final Bot bot = this;
|
||||
|
||||
// Step 1: Look at Habbo
|
||||
bot.lookAt(serveEvent.habbo);
|
||||
|
||||
// Step 2: Prepare tasks for when the Bot (carrying the handitem) reaches the Habbo
|
||||
final List<Runnable> tasks = new ArrayList<>();
|
||||
tasks.add(new RoomUnitGiveHanditem(serveEvent.habbo.getRoomUnit(), serveEvent.habbo.getHabboInfo().getCurrentRoom(), serveEvent.itemId));
|
||||
tasks.add(new RoomUnitGiveHanditem(this.getRoomUnit(), serveEvent.habbo.getHabboInfo().getCurrentRoom(), 0));
|
||||
|
||||
tasks.add(() -> {
|
||||
if(this.getRoom() != null) {
|
||||
String botMessage = Emulator.getTexts()
|
||||
.getValue("bots.butler.given")
|
||||
.replace("%key%", key)
|
||||
.replace("%username%", serveEvent.habbo.getHabboInfo().getUsername());
|
||||
|
||||
if (!WiredManager.triggerUserSays(this.getRoom(), this.getRoomUnit(), botMessage)) {
|
||||
bot.talk(botMessage);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
List<Runnable> failedReached = new ArrayList<>();
|
||||
failedReached.add(() -> {
|
||||
if (distanceBetweenBotAndHabbo <= Emulator.getConfig().getInt("hotel.bot.butler.servedistance", 8)) {
|
||||
for (Runnable task : tasks) {
|
||||
task.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Give bot the handitem that it's going to give the Habbo
|
||||
Emulator.getThreading().run(new RoomUnitGiveHanditem(this.getRoomUnit(), serveEvent.habbo.getHabboInfo().getCurrentRoom(), serveEvent.itemId));
|
||||
|
||||
if (distanceBetweenBotAndHabbo > Emulator.getConfig().getInt("hotel.bot.butler.reachdistance", 3)) {
|
||||
Emulator.getThreading().run(new RoomUnitWalkToRoomUnit(this.getRoomUnit(), serveEvent.habbo.getRoomUnit(), serveEvent.habbo.getHabboInfo().getCurrentRoom(), tasks, failedReached, Emulator.getConfig().getInt("hotel.bot.butler.reachdistance", 3)));
|
||||
} else {
|
||||
Emulator.getThreading().run(failedReached.get(0), 1000);
|
||||
}
|
||||
} else {
|
||||
if(this.getRoom() != null) {
|
||||
this.getRoom().giveHandItem(serveEvent.habbo, serveEvent.itemId);
|
||||
|
||||
String msg = Emulator.getTexts().getValue("bots.butler.given").replace("%key%", keyword).replace("%username%", serveEvent.habbo.getHabboInfo().getUsername());
|
||||
if (!WiredManager.triggerUserSays(this.getRoom(), this.getRoomUnit(), msg)) {
|
||||
this.talk(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Enable plugins to cancel this event
|
||||
BotServerItemEvent serveEvent = new BotServerItemEvent(this, message.getHabbo(), itemId);
|
||||
if (Emulator.getPluginManager().fireEvent(serveEvent).isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start give handitem process
|
||||
if (this.getRoomUnit().canWalk()) {
|
||||
final String key = keyword;
|
||||
final Bot bot = this;
|
||||
|
||||
// Step 1: Look at Habbo
|
||||
bot.lookAt(serveEvent.habbo);
|
||||
|
||||
// Step 2: Prepare tasks for when the Bot (carrying the handitem) reaches the Habbo
|
||||
final List<Runnable> tasks = new ArrayList<>();
|
||||
tasks.add(new RoomUnitGiveHanditem(serveEvent.habbo.getRoomUnit(), serveEvent.habbo.getHabboInfo().getCurrentRoom(), serveEvent.itemId));
|
||||
tasks.add(new RoomUnitGiveHanditem(this.getRoomUnit(), serveEvent.habbo.getHabboInfo().getCurrentRoom(), 0));
|
||||
|
||||
tasks.add(() -> {
|
||||
if(this.getRoom() != null) {
|
||||
String botMessage = Emulator.getTexts()
|
||||
.getValue("bots.butler.given")
|
||||
.replace("%key%", key)
|
||||
.replace("%username%", serveEvent.habbo.getHabboInfo().getUsername());
|
||||
|
||||
if (!WiredManager.triggerUserSays(this.getRoom(), this.getRoomUnit(), botMessage)) {
|
||||
bot.talk(botMessage);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
List<Runnable> failedReached = new ArrayList<>();
|
||||
failedReached.add(() -> {
|
||||
if (distanceBetweenBotAndHabbo <= Emulator.getConfig().getInt("hotel.bot.butler.servedistance", 8)) {
|
||||
for (Runnable task : tasks) {
|
||||
task.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Give bot the handitem that it's going to give the Habbo
|
||||
Emulator.getThreading().run(new RoomUnitGiveHanditem(this.getRoomUnit(), serveEvent.habbo.getHabboInfo().getCurrentRoom(), serveEvent.itemId));
|
||||
|
||||
if (distanceBetweenBotAndHabbo > Emulator.getConfig().getInt("hotel.bot.butler.reachdistance", 3)) {
|
||||
Emulator.getThreading().run(new RoomUnitWalkToRoomUnit(this.getRoomUnit(), serveEvent.habbo.getRoomUnit(), serveEvent.habbo.getHabboInfo().getCurrentRoom(), tasks, failedReached, Emulator.getConfig().getInt("hotel.bot.butler.reachdistance", 3)));
|
||||
} else {
|
||||
Emulator.getThreading().run(failedReached.get(0), 1000);
|
||||
}
|
||||
} else {
|
||||
if(this.getRoom() != null) {
|
||||
this.getRoom().giveHandItem(serveEvent.habbo, serveEvent.itemId);
|
||||
|
||||
String msg = Emulator.getTexts().getValue("bots.butler.given").replace("%key%", keyword).replace("%username%", serveEvent.habbo.getHabboInfo().getUsername());
|
||||
if (!WiredManager.triggerUserSays(this.getRoom(), this.getRoomUnit(), msg)) {
|
||||
this.talk(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,6 +196,7 @@ public class CommandHandler {
|
||||
addCommand(new EmptyInventoryCommand());
|
||||
addCommand(new EmptyBotsInventoryCommand());
|
||||
addCommand(new EmptyPetsInventoryCommand());
|
||||
addCommand(new EmuStatsCommand());
|
||||
addCommand(new EnableCommand());
|
||||
addCommand(new EventCommand());
|
||||
addCommand(new FacelessCommand());
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.eu.habbo.habbohotel.commands;
|
||||
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
|
||||
public class EmuStatsCommand extends Command {
|
||||
public EmuStatsCommand() {
|
||||
super(Permission.ACC_MODTOOL_ROOM_INFO, new String[]{"emustats"});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(GameClient gameClient, String[] params) {
|
||||
gameClient.getHabbo().whisper("Emulator stats are available in the Nitro stats window.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -208,10 +208,10 @@ public abstract class Game implements Runnable {
|
||||
this.state = GameState.IDLE;
|
||||
|
||||
boolean gamesActive = false;
|
||||
for (HabboItem timer : room.getFloorItems()) {
|
||||
if (timer instanceof InteractionGameTimer) {
|
||||
if (((InteractionGameTimer) timer).isRunning())
|
||||
gamesActive = true;
|
||||
for (InteractionGameTimer timer : room.getRoomSpecialTypes().getGameTimers().values()) {
|
||||
if (timer.isRunning()) {
|
||||
gamesActive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,49 +6,55 @@ import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
public class GamePlayer {
|
||||
|
||||
private final Habbo habbo;
|
||||
|
||||
|
||||
private GameTeamColors teamColor;
|
||||
|
||||
|
||||
private int score;
|
||||
private int wiredScore;
|
||||
|
||||
|
||||
public GamePlayer(Habbo habbo, GameTeamColors teamColor) {
|
||||
this.habbo = habbo;
|
||||
this.teamColor = teamColor;
|
||||
}
|
||||
|
||||
|
||||
public void reset() {
|
||||
this.score = 0;
|
||||
this.wiredScore = 0;
|
||||
}
|
||||
|
||||
public synchronized void addScore(int amount) {
|
||||
public void addScore(int amount) {
|
||||
addScore(amount, false);
|
||||
}
|
||||
|
||||
public synchronized void addScore(int amount, boolean isWired) {
|
||||
if (habbo.getHabboInfo().getGamePlayer() != null && this.habbo.getHabboInfo().getCurrentGame() != null && this.habbo.getHabboInfo().getCurrentRoom().getGame(this.habbo.getHabboInfo().getCurrentGame()).getTeamForHabbo(this.habbo) != null) {
|
||||
this.score += amount;
|
||||
public void addScore(int amount, boolean isWired) {
|
||||
com.eu.habbo.habbohotel.rooms.Room roomToTrigger = null;
|
||||
com.eu.habbo.habbohotel.rooms.RoomUnit roomUnitToTrigger = null;
|
||||
int currentScore = 0;
|
||||
|
||||
if (this.score < 0) this.score = 0;
|
||||
synchronized (this) {
|
||||
if (this.habbo.getHabboInfo().getGamePlayer() != null && this.habbo.getHabboInfo().getCurrentGame() != null && this.habbo.getHabboInfo().getCurrentRoom().getGame(this.habbo.getHabboInfo().getCurrentGame()).getTeamForHabbo(this.habbo) != null) {
|
||||
this.score += amount;
|
||||
|
||||
if(isWired) {
|
||||
this.wiredScore += amount;
|
||||
if (this.score < 0) this.score = 0;
|
||||
|
||||
if (this.wiredScore < 0) {
|
||||
this.wiredScore = 0;
|
||||
if (isWired) {
|
||||
this.wiredScore += amount;
|
||||
|
||||
if (this.wiredScore < 0) {
|
||||
this.wiredScore = 0;
|
||||
}
|
||||
|
||||
if (this.wiredScore > this.score) {
|
||||
this.wiredScore = this.score;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.wiredScore > this.score) {
|
||||
this.wiredScore = this.score;
|
||||
}
|
||||
roomToTrigger = this.habbo.getHabboInfo().getCurrentRoom();
|
||||
roomUnitToTrigger = this.habbo.getRoomUnit();
|
||||
currentScore = this.score;
|
||||
}
|
||||
}
|
||||
|
||||
WiredManager.triggerScoreAchieved(this.habbo.getHabboInfo().getCurrentRoom(), this.habbo.getRoomUnit(), this.score, amount);
|
||||
if (roomToTrigger != null && roomUnitToTrigger != null) {
|
||||
WiredManager.triggerScoreAchieved(roomToTrigger, roomUnitToTrigger, currentScore, amount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,12 +62,10 @@ public class GamePlayer {
|
||||
return this.habbo;
|
||||
}
|
||||
|
||||
|
||||
public GameTeamColors getTeamColor() {
|
||||
return this.teamColor;
|
||||
}
|
||||
|
||||
|
||||
public int getScore() {
|
||||
return this.score;
|
||||
}
|
||||
|
||||
+3
-1
@@ -89,7 +89,9 @@ public class InteractionOneWayGate extends HabboItem {
|
||||
Emulator.getThreading().run(new RoomUnitWalkToLocation(unit, tile, room, onFail, onFail));
|
||||
|
||||
Emulator.getThreading().run(() -> {
|
||||
WiredManager.triggerUserWalksOn(room, unit, this);
|
||||
if (room.isLoaded()) {
|
||||
WiredManager.triggerUserWalksOn(room, unit, this);
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
|
||||
|
||||
+23
-15
@@ -93,23 +93,24 @@ public abstract class InteractionWired extends InteractionDefault {
|
||||
@Override
|
||||
public void run() {
|
||||
if (this.needsUpdate()) {
|
||||
String wiredData = this.getWiredData();
|
||||
String wiredDataRaw = this.getWiredData();
|
||||
final String wiredData = (wiredDataRaw == null) ? "" : wiredDataRaw;
|
||||
final int currentRoomId = this.getRoomId();
|
||||
final int currentId = this.getId();
|
||||
|
||||
if (wiredData == null) {
|
||||
wiredData = "";
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE items SET wired_data = ? WHERE id = ?")) {
|
||||
if (this.getRoomId() != 0) {
|
||||
statement.setString(1, wiredData);
|
||||
} else {
|
||||
statement.setString(1, "");
|
||||
Emulator.getThreading().run(() -> {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE items SET wired_data = ? WHERE id = ?")) {
|
||||
if (currentRoomId != 0) {
|
||||
statement.setString(1, wiredData);
|
||||
} else {
|
||||
statement.setString(1, "");
|
||||
}
|
||||
statement.setInt(2, currentId);
|
||||
statement.execute();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Caught SQL exception", e);
|
||||
}
|
||||
statement.setInt(2, this.getId());
|
||||
statement.execute();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Caught SQL exception", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
super.run();
|
||||
}
|
||||
@@ -216,6 +217,9 @@ public abstract class InteractionWired extends InteractionDefault {
|
||||
public static WiredSettings readSettings(ClientMessage packet, boolean isEffect)
|
||||
{
|
||||
int intParamCount = packet.readInt();
|
||||
if (intParamCount < 0 || intParamCount > 100) {
|
||||
throw new IllegalArgumentException("Invalid intParamCount: " + intParamCount);
|
||||
}
|
||||
int[] intParams = new int[intParamCount];
|
||||
|
||||
for(int i = 0; i < intParamCount; i++)
|
||||
@@ -226,6 +230,10 @@ public abstract class InteractionWired extends InteractionDefault {
|
||||
String stringParam = packet.readString();
|
||||
|
||||
int itemCount = packet.readInt();
|
||||
int selectionLimit = Emulator.getConfig() != null ? Emulator.getConfig().getInt("hotel.wired.furni.selection.count", 5) : 5;
|
||||
if (itemCount < 0 || itemCount > selectionLimit * 20) {
|
||||
throw new IllegalArgumentException("Invalid itemCount: " + itemCount + " exceeds maximum allowed limit");
|
||||
}
|
||||
int[] itemIds = new int[itemCount];
|
||||
|
||||
for(int i = 0; i < itemCount; i++)
|
||||
|
||||
+20
-11
@@ -154,6 +154,7 @@ public class InteractionGameTimer extends HabboItem {
|
||||
@Override
|
||||
public void onPickUp(Room room) {
|
||||
this.endGame(room);
|
||||
this.threadActive = false;
|
||||
|
||||
this.timeNow = this.getInitialTimeValue();
|
||||
this.setExtradata(this.timeNow + "\t" + this.baseTime);
|
||||
@@ -220,8 +221,7 @@ public class InteractionGameTimer extends HabboItem {
|
||||
room.updateItem(this);
|
||||
WiredManager.triggerGameStarts(room);
|
||||
|
||||
if (!this.threadActive) {
|
||||
this.threadActive = true;
|
||||
if (this.tryActivateTimerThread()) {
|
||||
this.scheduleTimerRunnable(this.getTimerStartDelayMs());
|
||||
}
|
||||
} else if (client != null) {
|
||||
@@ -243,8 +243,7 @@ public class InteractionGameTimer extends HabboItem {
|
||||
} else {
|
||||
this.unpause(room);
|
||||
|
||||
if (!this.threadActive) {
|
||||
this.threadActive = true;
|
||||
if (this.tryActivateTimerThread()) {
|
||||
this.scheduleTimerRunnable(this.getTimerResumeDelayMs());
|
||||
}
|
||||
}
|
||||
@@ -257,8 +256,7 @@ public class InteractionGameTimer extends HabboItem {
|
||||
this.createNewGame(room);
|
||||
WiredManager.triggerGameStarts(room);
|
||||
|
||||
if (!this.threadActive) {
|
||||
this.threadActive = true;
|
||||
if (this.tryActivateTimerThread()) {
|
||||
this.scheduleTimerRunnable(this.getTimerStartDelayMs());
|
||||
}
|
||||
}
|
||||
@@ -297,8 +295,7 @@ public class InteractionGameTimer extends HabboItem {
|
||||
}
|
||||
this.createNewGame(room);
|
||||
WiredManager.triggerGameStarts(room);
|
||||
if (!threadActive) {
|
||||
threadActive = true;
|
||||
if (this.tryActivateTimerThread()) {
|
||||
this.scheduleTimerRunnable(this.getTimerStartDelayMs());
|
||||
}
|
||||
}
|
||||
@@ -321,8 +318,7 @@ public class InteractionGameTimer extends HabboItem {
|
||||
this.isPaused = false;
|
||||
this.unpause(room);
|
||||
|
||||
if (!this.threadActive) {
|
||||
this.threadActive = true;
|
||||
if (this.tryActivateTimerThread()) {
|
||||
this.scheduleTimerRunnable(this.getTimerResumeDelayMs());
|
||||
}
|
||||
}
|
||||
@@ -406,7 +402,9 @@ public class InteractionGameTimer extends HabboItem {
|
||||
}
|
||||
|
||||
public void setThreadActive(boolean threadActive) {
|
||||
this.threadActive = threadActive;
|
||||
synchronized (this) {
|
||||
this.threadActive = threadActive;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPaused() {
|
||||
@@ -428,4 +426,15 @@ public class InteractionGameTimer extends HabboItem {
|
||||
public int getBaseTime() {
|
||||
return this.baseTime;
|
||||
}
|
||||
|
||||
public boolean tryActivateTimerThread() {
|
||||
synchronized (this) {
|
||||
if (this.threadActive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.threadActive = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-14
@@ -20,12 +20,13 @@ import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
|
||||
import com.eu.habbo.messages.outgoing.generic.alerts.UpdateFailedComposer;
|
||||
import gnu.trove.procedure.TObjectProcedure;
|
||||
import gnu.trove.set.hash.THashSet;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class WiredEffectGiveReward extends InteractionWiredEffect {
|
||||
public static final int LIMIT_ONCE = 0;
|
||||
@@ -37,10 +38,10 @@ public class WiredEffectGiveReward extends InteractionWiredEffect {
|
||||
|
||||
public int limit;
|
||||
public int limitationInterval;
|
||||
public int given;
|
||||
public AtomicInteger given = new AtomicInteger(0);
|
||||
public int rewardTime;
|
||||
public boolean uniqueRewards;
|
||||
public THashSet<WiredGiveRewardItem> rewardItems = new THashSet<>();
|
||||
public List<WiredGiveRewardItem> rewardItems = new CopyOnWriteArrayList<>();
|
||||
public int userSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||
|
||||
public WiredEffectGiveReward(ResultSet set, Item baseItem) throws SQLException {
|
||||
@@ -71,9 +72,8 @@ public class WiredEffectGiveReward extends InteractionWiredEffect {
|
||||
|
||||
@Override
|
||||
public String getWiredData() {
|
||||
|
||||
ArrayList<WiredGiveRewardItem> rewards = new ArrayList<>(this.rewardItems);
|
||||
return WiredManager.getGson().toJson(new JsonData(this.limit, this.given, this.rewardTime, this.uniqueRewards, this.limitationInterval, rewards, this.getDelay(), this.userSource));
|
||||
return WiredManager.getGson().toJson(new JsonData(this.limit, this.given.get(), this.rewardTime, this.uniqueRewards, this.limitationInterval, rewards, this.getDelay(), this.userSource));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,7 +84,7 @@ public class WiredEffectGiveReward extends InteractionWiredEffect {
|
||||
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
|
||||
this.setDelay(data.delay);
|
||||
this.limit = data.limit;
|
||||
this.given = data.given;
|
||||
this.given.set(data.given);
|
||||
this.rewardTime = data.reward_time;
|
||||
this.uniqueRewards = data.unique_rewards;
|
||||
this.limitationInterval = data.limit_interval;
|
||||
@@ -96,7 +96,7 @@ public class WiredEffectGiveReward extends InteractionWiredEffect {
|
||||
String[] data = wiredData.split(":");
|
||||
if (data.length > 0) {
|
||||
this.limit = Integer.parseInt(data[0]);
|
||||
this.given = Integer.parseInt(data[1]);
|
||||
this.given.set(Integer.parseInt(data[1]));
|
||||
this.rewardTime = Integer.parseInt(data[2]);
|
||||
this.uniqueRewards = data[3].equals("1");
|
||||
this.limitationInterval = Integer.parseInt(data[4]);
|
||||
@@ -127,7 +127,7 @@ public class WiredEffectGiveReward extends InteractionWiredEffect {
|
||||
public void onPickUp() {
|
||||
this.limit = 0;
|
||||
this.limitationInterval = 0;
|
||||
this.given = 0;
|
||||
this.given.set(0);
|
||||
this.rewardTime = 0;
|
||||
this.uniqueRewards = false;
|
||||
this.rewardItems.clear();
|
||||
@@ -192,7 +192,7 @@ public class WiredEffectGiveReward extends InteractionWiredEffect {
|
||||
this.limit = settings.getIntParams()[2];
|
||||
this.limitationInterval = settings.getIntParams()[3];
|
||||
this.userSource = settings.getIntParams()[4];
|
||||
this.given = 0;
|
||||
this.given.set(0);
|
||||
|
||||
String data = settings.getStringParam();
|
||||
|
||||
@@ -276,15 +276,15 @@ public class WiredEffectGiveReward extends InteractionWiredEffect {
|
||||
}
|
||||
|
||||
public int getGiven() {
|
||||
return this.given;
|
||||
return this.given.get();
|
||||
}
|
||||
|
||||
public void setGiven(int given) {
|
||||
this.given = given;
|
||||
this.given.set(given);
|
||||
}
|
||||
|
||||
public void incrementGiven() {
|
||||
this.given++;
|
||||
this.given.incrementAndGet();
|
||||
}
|
||||
|
||||
public int getRewardTime() {
|
||||
@@ -303,11 +303,11 @@ public class WiredEffectGiveReward extends InteractionWiredEffect {
|
||||
this.uniqueRewards = uniqueRewards;
|
||||
}
|
||||
|
||||
public THashSet<WiredGiveRewardItem> getRewardItems() {
|
||||
public List<WiredGiveRewardItem> getRewardItems() {
|
||||
return this.rewardItems;
|
||||
}
|
||||
|
||||
public void setRewardItems(THashSet<WiredGiveRewardItem> rewardItems) {
|
||||
public void setRewardItems(List<WiredGiveRewardItem> rewardItems) {
|
||||
this.rewardItems = rewardItems;
|
||||
}
|
||||
}
|
||||
|
||||
+52
-44
@@ -384,61 +384,69 @@ public final class WiredVariableReferenceSupport {
|
||||
}
|
||||
|
||||
private static void upsertSharedUserAssignment(int sourceRoomId, int sourceVariableItemId, int userId, SharedUserAssignment assignment) {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("INSERT INTO room_user_wired_variables (room_id, user_id, variable_item_id, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = VALUES(updated_at)")) {
|
||||
statement.setInt(1, sourceRoomId);
|
||||
statement.setInt(2, userId);
|
||||
statement.setInt(3, sourceVariableItemId);
|
||||
Emulator.getThreading().run(() -> {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("INSERT INTO room_user_wired_variables (room_id, user_id, variable_item_id, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = VALUES(updated_at)")) {
|
||||
statement.setInt(1, sourceRoomId);
|
||||
statement.setInt(2, userId);
|
||||
statement.setInt(3, sourceVariableItemId);
|
||||
|
||||
if (assignment.getValue() == null) {
|
||||
statement.setNull(4, java.sql.Types.INTEGER);
|
||||
} else {
|
||||
statement.setInt(4, assignment.getValue());
|
||||
if (assignment.getValue() == null) {
|
||||
statement.setNull(4, java.sql.Types.INTEGER);
|
||||
} else {
|
||||
statement.setInt(4, assignment.getValue());
|
||||
}
|
||||
|
||||
statement.setInt(5, assignment.getCreatedAt());
|
||||
statement.setInt(6, assignment.getUpdatedAt());
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to store shared wired user variable {} for room {} user {}", sourceVariableItemId, sourceRoomId, userId, e);
|
||||
}
|
||||
|
||||
statement.setInt(5, assignment.getCreatedAt());
|
||||
statement.setInt(6, assignment.getUpdatedAt());
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to store shared wired user variable {} for room {} user {}", sourceVariableItemId, sourceRoomId, userId, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void deleteSharedUserAssignment(int sourceRoomId, int sourceVariableItemId, int userId) {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("DELETE FROM room_user_wired_variables WHERE room_id = ? AND user_id = ? AND variable_item_id = ?")) {
|
||||
statement.setInt(1, sourceRoomId);
|
||||
statement.setInt(2, userId);
|
||||
statement.setInt(3, sourceVariableItemId);
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to delete shared wired user variable {} for room {} user {}", sourceVariableItemId, sourceRoomId, userId, e);
|
||||
}
|
||||
Emulator.getThreading().run(() -> {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("DELETE FROM room_user_wired_variables WHERE room_id = ? AND user_id = ? AND variable_item_id = ?")) {
|
||||
statement.setInt(1, sourceRoomId);
|
||||
statement.setInt(2, userId);
|
||||
statement.setInt(3, sourceVariableItemId);
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to delete shared wired user variable {} for room {} user {}", sourceVariableItemId, sourceRoomId, userId, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void upsertSharedRoomAssignment(int sourceRoomId, int sourceVariableItemId, SharedRoomAssignment assignment) {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("INSERT INTO room_wired_variables (room_id, variable_item_id, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = VALUES(updated_at)")) {
|
||||
statement.setInt(1, sourceRoomId);
|
||||
statement.setInt(2, sourceVariableItemId);
|
||||
statement.setInt(3, assignment.getValue());
|
||||
statement.setInt(4, 0);
|
||||
statement.setInt(5, assignment.getUpdatedAt());
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to store shared wired room variable {} for room {}", sourceVariableItemId, sourceRoomId, e);
|
||||
}
|
||||
Emulator.getThreading().run(() -> {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("INSERT INTO room_wired_variables (room_id, variable_item_id, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = VALUES(updated_at)")) {
|
||||
statement.setInt(1, sourceRoomId);
|
||||
statement.setInt(2, sourceVariableItemId);
|
||||
statement.setInt(3, assignment.getValue());
|
||||
statement.setInt(4, 0);
|
||||
statement.setInt(5, assignment.getUpdatedAt());
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to store shared wired room variable {} for room {}", sourceVariableItemId, sourceRoomId, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void deleteSharedRoomAssignment(int sourceRoomId, int sourceVariableItemId) {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("DELETE FROM room_wired_variables WHERE room_id = ? AND variable_item_id = ?")) {
|
||||
statement.setInt(1, sourceRoomId);
|
||||
statement.setInt(2, sourceVariableItemId);
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to delete shared wired room variable {} for room {}", sourceVariableItemId, sourceRoomId, e);
|
||||
}
|
||||
Emulator.getThreading().run(() -> {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("DELETE FROM room_wired_variables WHERE room_id = ? AND variable_item_id = ?")) {
|
||||
statement.setInt(1, sourceRoomId);
|
||||
statement.setInt(2, sourceVariableItemId);
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to delete shared wired room variable {} for room {}", sourceVariableItemId, sourceRoomId, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static String createDefinitionPrefix(int sourceRoomId, int sourceVariableItemId) {
|
||||
|
||||
+5
-1
@@ -123,7 +123,11 @@ public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements
|
||||
@Override
|
||||
public boolean saveData(WiredSettings settings) {
|
||||
if (settings.getIntParams().length < 1) return false;
|
||||
this.repeatTime = settings.getIntParams()[0] * 5000;
|
||||
int interval = settings.getIntParams()[0];
|
||||
if (interval < 1) {
|
||||
interval = 1;
|
||||
}
|
||||
this.repeatTime = interval * 5000;
|
||||
// No accumulated time reset needed - using global tick count
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -115,19 +115,21 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
}
|
||||
|
||||
public final Object roomUnitLock = new Object();
|
||||
public final ConcurrentHashMap<RoomTile, THashSet<HabboItem>> tileCache = new ConcurrentHashMap<>();
|
||||
public final List<Integer> userVotes;
|
||||
private final TIntArrayList rights;
|
||||
private final TIntIntHashMap mutedHabbos;
|
||||
private final TIntObjectHashMap<RoomBan> bannedHabbos;
|
||||
private final Set<Game> games;
|
||||
private final TIntObjectMap<RoomMoodlightData> moodlightData;
|
||||
public volatile double lastCycleCpuMs = 0.0;
|
||||
public volatile String lastCycleThread = "N/A";
|
||||
|
||||
private final Object loadLock = new Object();
|
||||
//Use appropriately. Could potentially cause memory leaks when used incorrectly.
|
||||
public volatile boolean preventUnloading = false;
|
||||
public volatile boolean preventUncaching = false;
|
||||
public Set<ServerMessage> scheduledComposers = ConcurrentHashMap.newKeySet();
|
||||
public Set<Runnable> scheduledTasks = ConcurrentHashMap.newKeySet();
|
||||
public final java.util.concurrent.ConcurrentLinkedQueue<Runnable> scheduledTasks = new java.util.concurrent.ConcurrentLinkedQueue<>();
|
||||
public String wordQuiz = "";
|
||||
public int noVotes = 0;
|
||||
public int yesVotes = 0;
|
||||
@@ -981,8 +983,6 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
this.scheduledTasks.clear();
|
||||
this.scheduledComposers.clear();
|
||||
|
||||
this.tileCache.clear();
|
||||
|
||||
synchronized (this.mutedHabbos) {
|
||||
this.mutedHabbos.clear();
|
||||
}
|
||||
@@ -1160,10 +1160,13 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
synchronized (this.loadLock) {
|
||||
if (this.loaded) {
|
||||
try {
|
||||
long startTime = System.nanoTime();
|
||||
this.lastCycleThread = Thread.currentThread().getName();
|
||||
// Run cycle directly instead of scheduling on thread pool
|
||||
// This ensures all cycle tasks in the same tick execute synchronously
|
||||
// preventing wired desync issues
|
||||
this.cycle();
|
||||
this.lastCycleCpuMs = (System.nanoTime() - startTime) / 1000000.0;
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Caught exception", e);
|
||||
}
|
||||
@@ -2320,27 +2323,37 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
sanitizedInspectMask |= sanitizedModifyMask;
|
||||
|
||||
synchronized (this.wiredSettingsLock) {
|
||||
int previousInspectMask = this.wiredInspectMask;
|
||||
int previousModifyMask = this.wiredModifyMask;
|
||||
final int finalInspectMask = sanitizedInspectMask;
|
||||
final int finalModifyMask = sanitizedModifyMask;
|
||||
final int finalId = this.id;
|
||||
final int previousInspectMask = this.wiredInspectMask;
|
||||
final int previousModifyMask = this.wiredModifyMask;
|
||||
|
||||
this.wiredInspectMask = sanitizedInspectMask;
|
||||
this.wiredModifyMask = sanitizedModifyMask;
|
||||
this.wiredSettingsLoaded = true;
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
"INSERT INTO room_wired_settings (room_id, inspect_mask, modify_mask) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE inspect_mask = VALUES(inspect_mask), modify_mask = VALUES(modify_mask)")) {
|
||||
statement.setInt(1, this.id);
|
||||
statement.setInt(2, sanitizedInspectMask);
|
||||
statement.setInt(3, sanitizedModifyMask);
|
||||
statement.executeUpdate();
|
||||
this.pushWiredSettingsToCurrentHabbos();
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
this.wiredInspectMask = previousInspectMask;
|
||||
this.wiredModifyMask = previousModifyMask;
|
||||
LOGGER.error("Caught SQL exception while saving wired room settings", e);
|
||||
return false;
|
||||
}
|
||||
Emulator.getThreading().run(() -> {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(
|
||||
"INSERT INTO room_wired_settings (room_id, inspect_mask, modify_mask) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE inspect_mask = VALUES(inspect_mask), modify_mask = VALUES(modify_mask)")) {
|
||||
statement.setInt(1, finalId);
|
||||
statement.setInt(2, finalInspectMask);
|
||||
statement.setInt(3, finalModifyMask);
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
synchronized (this.wiredSettingsLock) {
|
||||
if (this.wiredInspectMask == finalInspectMask && this.wiredModifyMask == finalModifyMask) {
|
||||
this.wiredInspectMask = previousInspectMask;
|
||||
this.wiredModifyMask = previousModifyMask;
|
||||
}
|
||||
}
|
||||
LOGGER.error("Caught SQL exception while saving wired room settings", e);
|
||||
}
|
||||
});
|
||||
|
||||
this.pushWiredSettingsToCurrentHabbos();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2878,4 +2891,20 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
public Collection<RoomUnit> getRoomUnitsAt(RoomTile tile) {
|
||||
return this.unitManager.getRoomUnitsAt(tile);
|
||||
}
|
||||
|
||||
public long getEstimatedMemoryUsage() {
|
||||
long bytes = 1024 * 10; // Base footprint
|
||||
if (this.itemManager != null) {
|
||||
bytes += this.itemManager.itemCount() * 512L;
|
||||
}
|
||||
bytes += this.getUserCount() * 2048L;
|
||||
if (this.layout != null) {
|
||||
bytes += this.layout.getMapSize() * 128L;
|
||||
}
|
||||
com.eu.habbo.habbohotel.wired.tick.WiredTickService wired = com.eu.habbo.habbohotel.wired.tick.WiredTickService.getInstance();
|
||||
if (wired != null) {
|
||||
bytes += wired.getTickableCount(this.getId()) * 256L;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,27 +313,6 @@ public class RoomChatManager {
|
||||
}
|
||||
}
|
||||
|
||||
String wiredSayMessage = roomChatMessage.getMessage();
|
||||
|
||||
// Handle commands and wired
|
||||
boolean suppressSaysOutput = false;
|
||||
if (chatType != RoomChatType.WHISPER) {
|
||||
if (CommandHandler.handleCommand(habbo.getClient(), roomChatMessage.getUnfilteredMessage())) {
|
||||
WiredManager.triggerUserSays(habbo.getHabboInfo().getCurrentRoom(), habbo.getRoomUnit(), wiredSayMessage);
|
||||
roomChatMessage.isCommand = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ignoreWired) {
|
||||
suppressSaysOutput = WiredManager.shouldSuppressUserSaysOutput(
|
||||
habbo.getHabboInfo().getCurrentRoom(),
|
||||
habbo.getRoomUnit(),
|
||||
wiredSayMessage,
|
||||
chatType.ordinal(),
|
||||
roomChatMessage.getBubble() != null ? roomChatMessage.getBubble().getType() : -1);
|
||||
}
|
||||
}
|
||||
|
||||
// Flood protection
|
||||
if (!habbo.hasPermission(Permission.ACC_CHAT_NO_FLOOD)) {
|
||||
final int chatCounter = habbo.getHabboStats().chatCounter.addAndGet(1);
|
||||
@@ -357,6 +336,27 @@ public class RoomChatManager {
|
||||
}
|
||||
}
|
||||
|
||||
String wiredSayMessage = roomChatMessage.getMessage();
|
||||
|
||||
// Handle commands and wired
|
||||
boolean suppressSaysOutput = false;
|
||||
if (chatType != RoomChatType.WHISPER) {
|
||||
if (CommandHandler.handleCommand(habbo.getClient(), roomChatMessage.getUnfilteredMessage())) {
|
||||
WiredManager.triggerUserSays(habbo.getHabboInfo().getCurrentRoom(), habbo.getRoomUnit(), wiredSayMessage);
|
||||
roomChatMessage.isCommand = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ignoreWired) {
|
||||
suppressSaysOutput = WiredManager.shouldSuppressUserSaysOutput(
|
||||
habbo.getHabboInfo().getCurrentRoom(),
|
||||
habbo.getRoomUnit(),
|
||||
wiredSayMessage,
|
||||
chatType.ordinal(),
|
||||
roomChatMessage.getBubble() != null ? roomChatMessage.getBubble().getType() : -1);
|
||||
}
|
||||
}
|
||||
|
||||
// Build prefix messages
|
||||
ServerMessage prefixMessage = null;
|
||||
|
||||
@@ -615,6 +615,9 @@ public class RoomChatManager {
|
||||
InteractionTalkingFurniture.class);
|
||||
|
||||
for (HabboItem item : items) {
|
||||
if (item.getExtradata().equals("1")) {
|
||||
continue;
|
||||
}
|
||||
if (this.room.getLayout().getTile(item.getX(), item.getY())
|
||||
.distance(habbo.getRoomUnit().getCurrentLocation()) <= Emulator.getConfig()
|
||||
.getInt("furniture.talking.range")) {
|
||||
|
||||
@@ -75,7 +75,6 @@ public class RoomCycleManager {
|
||||
final boolean[] foundRightHolder = {false};
|
||||
|
||||
boolean loaded = this.room.isLoaded();
|
||||
this.room.tileCache.clear();
|
||||
|
||||
if (loaded) {
|
||||
processScheduledTasks();
|
||||
@@ -164,13 +163,9 @@ public class RoomCycleManager {
|
||||
* Processes scheduled tasks.
|
||||
*/
|
||||
private void processScheduledTasks() {
|
||||
if (!this.room.scheduledTasks.isEmpty()) {
|
||||
Set<Runnable> tasks = this.room.scheduledTasks;
|
||||
this.room.scheduledTasks = ConcurrentHashMap.newKeySet();
|
||||
|
||||
for (Runnable runnable : tasks) {
|
||||
Emulator.getThreading().run(runnable);
|
||||
}
|
||||
Runnable task;
|
||||
while ((task = this.room.scheduledTasks.poll()) != null) {
|
||||
Emulator.getThreading().run(task);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,7 +481,7 @@ public class RoomCycleManager {
|
||||
if (!unit.hasStatus(RoomUnitStatus.LAY)) {
|
||||
BedProfile bedProfile = new BedProfile(topItem);
|
||||
double layHeight = Item.getCurrentHeight(topItem) * 1.0D + bedProfile.getLayZOffset();
|
||||
LOGGER.info("[BedProfile] item={} stackHeight={} isFlat={} isDouble={} X={} Y={} Z={}",
|
||||
LOGGER.debug("[BedProfile] item={} stackHeight={} isFlat={} isDouble={} X={} Y={} Z={}",
|
||||
topItem.getBaseItem().getName(), topItem.getBaseItem().getHeight(),
|
||||
bedProfile.isFlat(), bedProfile.isDouble(),
|
||||
bedProfile.getLayXOffset(), bedProfile.getLayYOffset(), bedProfile.getLayZOffset());
|
||||
|
||||
@@ -35,6 +35,7 @@ public class RoomFurniVariableManager {
|
||||
private final Room room;
|
||||
private final ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, VariableAssignment>> activeAssignmentsByFurniId;
|
||||
private volatile boolean permanentAssignmentsLoaded;
|
||||
private final java.util.concurrent.atomic.AtomicBoolean broadcastRequested = new java.util.concurrent.atomic.AtomicBoolean(false);
|
||||
|
||||
public RoomFurniVariableManager(Room room) {
|
||||
this.room = room;
|
||||
@@ -591,7 +592,22 @@ public class RoomFurniVariableManager {
|
||||
habbo.getClient().sendResponse(new WiredUserVariablesDataComposer(this.room.getUserVariableManager().createSnapshot(), this.createSnapshot(), this.room.getRoomVariableManager().createSnapshot()));
|
||||
}
|
||||
|
||||
public void requestBroadcast() {
|
||||
if (this.broadcastRequested.compareAndSet(false, true)) {
|
||||
Emulator.getThreading().run(() -> {
|
||||
this.broadcastRequested.set(false);
|
||||
if (this.room.isLoaded()) {
|
||||
this.broadcastSnapshotRaw();
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
public void broadcastSnapshot() {
|
||||
this.requestBroadcast();
|
||||
}
|
||||
|
||||
public void broadcastSnapshotRaw() {
|
||||
RoomUserVariableManager.Snapshot userSnapshot = this.room.getUserVariableManager().createSnapshot();
|
||||
Snapshot furniSnapshot = this.createSnapshot();
|
||||
RoomVariableManager.Snapshot roomSnapshot = this.room.getRoomVariableManager().createSnapshot();
|
||||
|
||||
@@ -148,55 +148,8 @@ public class RoomItemManager {
|
||||
item = this.roomItems.get(id);
|
||||
}
|
||||
|
||||
// Check special types if not found in main storage
|
||||
RoomSpecialTypes specialTypes = this.room.getRoomSpecialTypes();
|
||||
|
||||
if (item == null) {
|
||||
item = specialTypes.getBanzaiTeleporter(id);
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
item = specialTypes.getTrigger(id);
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
item = specialTypes.getEffect(id);
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
item = specialTypes.getCondition(id);
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
item = specialTypes.getGameGate(id);
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
item = specialTypes.getGameScorebord(id);
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
item = specialTypes.getGameTimer(id);
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
item = specialTypes.getFreezeExitTiles().get(id);
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
item = specialTypes.getRoller(id);
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
item = specialTypes.getNest(id);
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
item = specialTypes.getPetDrink(id);
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
item = specialTypes.getPetFood(id);
|
||||
item = this.room.getRoomSpecialTypes().getSpecialItem(id);
|
||||
}
|
||||
|
||||
return item;
|
||||
@@ -726,7 +679,7 @@ public class RoomItemManager {
|
||||
item instanceof WiredBlob ||
|
||||
item instanceof InteractionTent ||
|
||||
item instanceof InteractionSnowboardSlope ||
|
||||
item instanceof InteractionFireworks) {
|
||||
item instanceof InteractionFireworks || item instanceof InteractionVoteCounter) {
|
||||
specialTypes.addUndefined(item);
|
||||
}
|
||||
}
|
||||
@@ -899,7 +852,7 @@ public class RoomItemManager {
|
||||
item instanceof InteractionStickyPole ||
|
||||
item instanceof WiredBlob ||
|
||||
item instanceof InteractionTent ||
|
||||
item instanceof InteractionSnowboardSlope) {
|
||||
item instanceof InteractionSnowboardSlope || item instanceof InteractionVoteCounter) {
|
||||
specialTypes.removeUndefined(item);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ public class RoomSpecialTypes {
|
||||
private final THashMap<Integer, InteractionFreezeExitTile> freezeExitTile;
|
||||
private final THashMap<Integer, HabboItem> undefined;
|
||||
private final Set<ICycleable> cycleTasks;
|
||||
private final ConcurrentHashMap<Integer, HabboItem> specialItemsById = new ConcurrentHashMap<>();
|
||||
|
||||
public RoomSpecialTypes() {
|
||||
this.banzaiTeleporters = new THashMap<>(0);
|
||||
@@ -115,11 +116,11 @@ public class RoomSpecialTypes {
|
||||
}
|
||||
|
||||
public void addBanzaiTeleporter(InteractionBattleBanzaiTeleporter item) {
|
||||
this.banzaiTeleporters.put(item.getId(), item);
|
||||
this.banzaiTeleporters.put(item.getId(), item); this.specialItemsById.put(item.getId(), item);
|
||||
}
|
||||
|
||||
public void removeBanzaiTeleporter(InteractionBattleBanzaiTeleporter item) {
|
||||
this.banzaiTeleporters.remove(item.getId());
|
||||
this.banzaiTeleporters.remove(item.getId()); this.specialItemsById.remove(item.getId());
|
||||
}
|
||||
|
||||
public THashSet<InteractionBattleBanzaiTeleporter> getBanzaiTeleporters() {
|
||||
@@ -155,11 +156,11 @@ public class RoomSpecialTypes {
|
||||
}
|
||||
|
||||
public void addNest(InteractionNest item) {
|
||||
this.nests.put(item.getId(), item);
|
||||
this.nests.put(item.getId(), item); this.specialItemsById.put(item.getId(), item);
|
||||
}
|
||||
|
||||
public void removeNest(InteractionNest item) {
|
||||
this.nests.remove(item.getId());
|
||||
this.nests.remove(item.getId()); this.specialItemsById.remove(item.getId());
|
||||
}
|
||||
|
||||
public THashSet<InteractionNest> getNests() {
|
||||
@@ -177,11 +178,11 @@ public class RoomSpecialTypes {
|
||||
}
|
||||
|
||||
public void addPetDrink(InteractionPetDrink item) {
|
||||
this.petDrinks.put(item.getId(), item);
|
||||
this.petDrinks.put(item.getId(), item); this.specialItemsById.put(item.getId(), item);
|
||||
}
|
||||
|
||||
public void removePetDrink(InteractionPetDrink item) {
|
||||
this.petDrinks.remove(item.getId());
|
||||
this.petDrinks.remove(item.getId()); this.specialItemsById.remove(item.getId());
|
||||
}
|
||||
|
||||
public THashSet<InteractionPetDrink> getPetDrinks() {
|
||||
@@ -199,11 +200,11 @@ public class RoomSpecialTypes {
|
||||
}
|
||||
|
||||
public void addPetFood(InteractionPetFood item) {
|
||||
this.petFoods.put(item.getId(), item);
|
||||
this.petFoods.put(item.getId(), item); this.specialItemsById.put(item.getId(), item);
|
||||
}
|
||||
|
||||
public void removePetFood(InteractionPetFood petFood) {
|
||||
this.petFoods.remove(petFood.getId());
|
||||
this.petFoods.remove(petFood.getId()); this.specialItemsById.remove(petFood.getId());
|
||||
}
|
||||
|
||||
public THashSet<InteractionPetFood> getPetFoods() {
|
||||
@@ -221,11 +222,11 @@ public class RoomSpecialTypes {
|
||||
}
|
||||
|
||||
public void addPetToy(InteractionPetToy item) {
|
||||
this.petToys.put(item.getId(), item);
|
||||
this.petToys.put(item.getId(), item); this.specialItemsById.put(item.getId(), item);
|
||||
}
|
||||
|
||||
public void removePetToy(InteractionPetToy petToy) {
|
||||
this.petToys.remove(petToy.getId());
|
||||
this.petToys.remove(petToy.getId()); this.specialItemsById.remove(petToy.getId());
|
||||
}
|
||||
|
||||
public THashSet<InteractionPetToy> getPetToys() {
|
||||
@@ -243,11 +244,11 @@ public class RoomSpecialTypes {
|
||||
}
|
||||
|
||||
public void addPetTree(InteractionPetTree item) {
|
||||
this.petTrees.put(item.getId(), item);
|
||||
this.petTrees.put(item.getId(), item); this.specialItemsById.put(item.getId(), item);
|
||||
}
|
||||
|
||||
public void removePetTree(InteractionPetTree petTree) {
|
||||
this.petTrees.remove(petTree.getId());
|
||||
this.petTrees.remove(petTree.getId()); this.specialItemsById.remove(petTree.getId());
|
||||
}
|
||||
|
||||
public THashSet<InteractionPetTree> getPetTrees() {
|
||||
@@ -270,12 +271,14 @@ public class RoomSpecialTypes {
|
||||
synchronized (this.rollers) {
|
||||
this.rollers.put(item.getId(), item);
|
||||
}
|
||||
this.specialItemsById.put(item.getId(), item);
|
||||
}
|
||||
|
||||
public void removeRoller(InteractionRoller roller) {
|
||||
synchronized (this.rollers) {
|
||||
this.rollers.remove(roller.getId());
|
||||
}
|
||||
this.specialItemsById.remove(roller.getId());
|
||||
}
|
||||
|
||||
public THashMap<Integer, InteractionRoller> getRollers() {
|
||||
@@ -469,11 +472,11 @@ public class RoomSpecialTypes {
|
||||
// Add to type-based index
|
||||
this.wiredTriggers.computeIfAbsent(trigger.getType(), k -> ConcurrentHashMap.newKeySet())
|
||||
.add(trigger);
|
||||
|
||||
// Add to spatial index
|
||||
long key = coordinateKey(trigger.getX(), trigger.getY());
|
||||
this.wiredTriggersByLocation.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet())
|
||||
.add(trigger);
|
||||
this.specialItemsById.put(trigger.getId(), trigger);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -489,7 +492,6 @@ public class RoomSpecialTypes {
|
||||
this.wiredTriggers.remove(trigger.getType());
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from spatial index
|
||||
long key = coordinateKey(trigger.getX(), trigger.getY());
|
||||
Set<InteractionWiredTrigger> locationTriggers = this.wiredTriggersByLocation.get(key);
|
||||
@@ -499,6 +501,7 @@ public class RoomSpecialTypes {
|
||||
this.wiredTriggersByLocation.remove(key);
|
||||
}
|
||||
}
|
||||
this.specialItemsById.remove(trigger.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -589,11 +592,11 @@ public class RoomSpecialTypes {
|
||||
// Add to type-based index
|
||||
this.wiredEffects.computeIfAbsent(effect.getType(), k -> ConcurrentHashMap.newKeySet())
|
||||
.add(effect);
|
||||
|
||||
// Add to spatial index
|
||||
long key = coordinateKey(effect.getX(), effect.getY());
|
||||
this.wiredEffectsByLocation.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet())
|
||||
.add(effect);
|
||||
this.specialItemsById.put(effect.getId(), effect);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -609,7 +612,6 @@ public class RoomSpecialTypes {
|
||||
this.wiredEffects.remove(effect.getType());
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from spatial index
|
||||
long key = coordinateKey(effect.getX(), effect.getY());
|
||||
Set<InteractionWiredEffect> locationEffects = this.wiredEffectsByLocation.get(key);
|
||||
@@ -619,6 +621,7 @@ public class RoomSpecialTypes {
|
||||
this.wiredEffectsByLocation.remove(key);
|
||||
}
|
||||
}
|
||||
this.specialItemsById.remove(effect.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -709,11 +712,11 @@ public class RoomSpecialTypes {
|
||||
// Add to type-based index
|
||||
this.wiredConditions.computeIfAbsent(condition.getType(), k -> ConcurrentHashMap.newKeySet())
|
||||
.add(condition);
|
||||
|
||||
// Add to spatial index
|
||||
long key = coordinateKey(condition.getX(), condition.getY());
|
||||
this.wiredConditionsByLocation.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet())
|
||||
.add(condition);
|
||||
this.specialItemsById.put(condition.getId(), condition);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -729,7 +732,6 @@ public class RoomSpecialTypes {
|
||||
this.wiredConditions.remove(condition.getType());
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from spatial index
|
||||
long key = coordinateKey(condition.getX(), condition.getY());
|
||||
Set<InteractionWiredCondition> locationConditions = this.wiredConditionsByLocation.get(key);
|
||||
@@ -739,6 +741,7 @@ public class RoomSpecialTypes {
|
||||
this.wiredConditionsByLocation.remove(key);
|
||||
}
|
||||
}
|
||||
this.specialItemsById.remove(condition.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -805,11 +808,11 @@ public class RoomSpecialTypes {
|
||||
*/
|
||||
public void addExtra(InteractionWiredExtra extra) {
|
||||
this.wiredExtras.put(extra.getId(), extra);
|
||||
|
||||
// Add to spatial index
|
||||
long key = coordinateKey(extra.getX(), extra.getY());
|
||||
this.wiredExtrasByLocation.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet())
|
||||
.add(extra);
|
||||
this.specialItemsById.put(extra.getId(), extra);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -818,7 +821,6 @@ public class RoomSpecialTypes {
|
||||
*/
|
||||
public void removeExtra(InteractionWiredExtra extra) {
|
||||
this.wiredExtras.remove(extra.getId());
|
||||
|
||||
// Remove from spatial index
|
||||
long key = coordinateKey(extra.getX(), extra.getY());
|
||||
Set<InteractionWiredExtra> locationExtras = this.wiredExtrasByLocation.get(key);
|
||||
@@ -828,6 +830,7 @@ public class RoomSpecialTypes {
|
||||
this.wiredExtrasByLocation.remove(key);
|
||||
}
|
||||
}
|
||||
this.specialItemsById.remove(extra.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -880,11 +883,11 @@ public class RoomSpecialTypes {
|
||||
}
|
||||
|
||||
public void addGameScoreboard(InteractionGameScoreboard scoreboard) {
|
||||
this.gameScoreboards.put(scoreboard.getId(), scoreboard);
|
||||
this.gameScoreboards.put(scoreboard.getId(), scoreboard); this.specialItemsById.put(scoreboard.getId(), scoreboard);
|
||||
}
|
||||
|
||||
public void removeScoreboard(InteractionGameScoreboard scoreboard) {
|
||||
this.gameScoreboards.remove(scoreboard.getId());
|
||||
this.gameScoreboards.remove(scoreboard.getId()); this.specialItemsById.remove(scoreboard.getId());
|
||||
}
|
||||
|
||||
public THashMap<Integer, InteractionFreezeScoreboard> getFreezeScoreboards() {
|
||||
@@ -980,11 +983,11 @@ public class RoomSpecialTypes {
|
||||
}
|
||||
|
||||
public void addGameGate(InteractionGameGate gameGate) {
|
||||
this.gameGates.put(gameGate.getId(), gameGate);
|
||||
this.gameGates.put(gameGate.getId(), gameGate); this.specialItemsById.put(gameGate.getId(), gameGate);
|
||||
}
|
||||
|
||||
public void removeGameGate(InteractionGameGate gameGate) {
|
||||
this.gameGates.remove(gameGate.getId());
|
||||
this.gameGates.remove(gameGate.getId()); this.specialItemsById.remove(gameGate.getId());
|
||||
}
|
||||
|
||||
public THashMap<Integer, InteractionFreezeGate> getFreezeGates() {
|
||||
@@ -1021,11 +1024,11 @@ public class RoomSpecialTypes {
|
||||
}
|
||||
|
||||
public void addGameTimer(InteractionGameTimer gameTimer) {
|
||||
this.gameTimers.put(gameTimer.getId(), gameTimer);
|
||||
this.gameTimers.put(gameTimer.getId(), gameTimer); this.specialItemsById.put(gameTimer.getId(), gameTimer);
|
||||
}
|
||||
|
||||
public void removeGameTimer(InteractionGameTimer gameTimer) {
|
||||
this.gameTimers.remove(gameTimer.getId());
|
||||
this.gameTimers.remove(gameTimer.getId()); this.specialItemsById.remove(gameTimer.getId());
|
||||
}
|
||||
|
||||
public THashMap<Integer, InteractionGameTimer> getGameTimers() {
|
||||
@@ -1043,7 +1046,7 @@ public class RoomSpecialTypes {
|
||||
}
|
||||
|
||||
public void addFreezeExitTile(InteractionFreezeExitTile freezeExitTile) {
|
||||
this.freezeExitTile.put(freezeExitTile.getId(), freezeExitTile);
|
||||
this.freezeExitTile.put(freezeExitTile.getId(), freezeExitTile); this.specialItemsById.put(freezeExitTile.getId(), freezeExitTile);
|
||||
}
|
||||
|
||||
public THashMap<Integer, InteractionFreezeExitTile> getFreezeExitTiles() {
|
||||
@@ -1051,7 +1054,7 @@ public class RoomSpecialTypes {
|
||||
}
|
||||
|
||||
public void removeFreezeExitTile(InteractionFreezeExitTile freezeExitTile) {
|
||||
this.freezeExitTile.remove(freezeExitTile.getId());
|
||||
this.freezeExitTile.remove(freezeExitTile.getId()); this.specialItemsById.remove(freezeExitTile.getId());
|
||||
}
|
||||
|
||||
public boolean hasFreezeExitTile() {
|
||||
@@ -1062,12 +1065,14 @@ public class RoomSpecialTypes {
|
||||
synchronized (this.undefined) {
|
||||
this.undefined.put(item.getId(), item);
|
||||
}
|
||||
this.specialItemsById.put(item.getId(), item);
|
||||
}
|
||||
|
||||
public void removeUndefined(HabboItem item) {
|
||||
synchronized (this.undefined) {
|
||||
this.undefined.remove(item.getId());
|
||||
}
|
||||
this.specialItemsById.remove(item.getId());
|
||||
}
|
||||
|
||||
public THashSet<HabboItem> getItemsOfType(Class<? extends HabboItem> type) {
|
||||
@@ -1130,6 +1135,10 @@ public class RoomSpecialTypes {
|
||||
this.cycleTasks.remove(task);
|
||||
}
|
||||
|
||||
public HabboItem getSpecialItem(int itemId) {
|
||||
return this.specialItemsById.get(itemId);
|
||||
}
|
||||
|
||||
public synchronized void dispose() {
|
||||
this.banzaiTeleporters.clear();
|
||||
this.nests.clear();
|
||||
@@ -1142,6 +1151,7 @@ public class RoomSpecialTypes {
|
||||
this.wiredTriggers.clear();
|
||||
this.wiredEffects.clear();
|
||||
this.wiredConditions.clear();
|
||||
this.wiredExtras.clear();
|
||||
|
||||
this.gameScoreboards.clear();
|
||||
this.gameGates.clear();
|
||||
@@ -1150,6 +1160,7 @@ public class RoomSpecialTypes {
|
||||
this.freezeExitTile.clear();
|
||||
this.undefined.clear();
|
||||
this.cycleTasks.clear();
|
||||
this.specialItemsById.clear();
|
||||
}
|
||||
|
||||
public Rectangle tentAt(RoomTile location) {
|
||||
|
||||
@@ -29,7 +29,6 @@ public class RoomTileManager {
|
||||
*/
|
||||
public void updateTile(RoomTile tile) {
|
||||
if (tile != null) {
|
||||
this.room.tileCache.remove(tile);
|
||||
this.room.getItemManager().tileCache.remove(tile);
|
||||
tile.setStackHeight(this.getStackHeight(tile.x, tile.y, false));
|
||||
tile.setState(this.calculateTileState(tile));
|
||||
@@ -41,7 +40,6 @@ public class RoomTileManager {
|
||||
*/
|
||||
public void updateTiles(THashSet<RoomTile> tiles) {
|
||||
for (RoomTile tile : tiles) {
|
||||
this.room.tileCache.remove(tile);
|
||||
this.room.getItemManager().tileCache.remove(tile);
|
||||
tile.setStackHeight(this.getStackHeight(tile.x, tile.y, false));
|
||||
tile.setState(this.calculateTileState(tile));
|
||||
|
||||
@@ -71,6 +71,24 @@ public class RoomUnitManager {
|
||||
*/
|
||||
public void clear() {
|
||||
synchronized (this.room.roomUnitLock) {
|
||||
for (Habbo habbo : this.currentHabbos.values()) {
|
||||
if (habbo.getRoomUnit() != null) {
|
||||
WiredMoveCarryHelper.cleanupRoomUnit(habbo.getRoomUnit());
|
||||
WiredUserMovementHelper.cleanupRoomUnit(habbo.getRoomUnit());
|
||||
}
|
||||
}
|
||||
for (Bot bot : this.currentBots.valueCollection()) {
|
||||
if (bot.getRoomUnit() != null) {
|
||||
WiredMoveCarryHelper.cleanupRoomUnit(bot.getRoomUnit());
|
||||
WiredUserMovementHelper.cleanupRoomUnit(bot.getRoomUnit());
|
||||
}
|
||||
}
|
||||
for (Pet pet : this.currentPets.valueCollection()) {
|
||||
if (pet.getRoomUnit() != null) {
|
||||
WiredMoveCarryHelper.cleanupRoomUnit(pet.getRoomUnit());
|
||||
WiredUserMovementHelper.cleanupRoomUnit(pet.getRoomUnit());
|
||||
}
|
||||
}
|
||||
this.unitCounter = 0;
|
||||
this.currentHabbos.clear();
|
||||
this.currentPets.clear();
|
||||
@@ -222,6 +240,8 @@ public class RoomUnitManager {
|
||||
}
|
||||
|
||||
if (habbo.getRoomUnit() != null) {
|
||||
WiredMoveCarryHelper.cleanupRoomUnit(habbo.getRoomUnit());
|
||||
WiredUserMovementHelper.cleanupRoomUnit(habbo.getRoomUnit());
|
||||
WiredManager.triggerUserLeavesRoom(this.room, habbo.getRoomUnit());
|
||||
if (WiredFreezeUtil.isFrozen(habbo.getRoomUnit())) {
|
||||
WiredFreezeUtil.unfreeze(this.room, habbo.getRoomUnit());
|
||||
@@ -646,14 +666,22 @@ public class RoomUnitManager {
|
||||
public boolean removeBot(Bot bot) {
|
||||
synchronized (this.currentBots) {
|
||||
if (this.currentBots.containsKey(bot.getId())) {
|
||||
if (bot.getRoomUnit() != null && bot.getRoomUnit().getCurrentLocation() != null) {
|
||||
bot.getRoomUnit().getCurrentLocation().removeUnit(bot.getRoomUnit());
|
||||
if (bot.getRoomUnit() != null) {
|
||||
WiredMoveCarryHelper.cleanupRoomUnit(bot.getRoomUnit());
|
||||
WiredUserMovementHelper.cleanupRoomUnit(bot.getRoomUnit());
|
||||
if (bot.getRoomUnit().getCurrentLocation() != null) {
|
||||
bot.getRoomUnit().getCurrentLocation().removeUnit(bot.getRoomUnit());
|
||||
}
|
||||
}
|
||||
|
||||
this.currentBots.remove(bot.getId());
|
||||
bot.getRoomUnit().setInRoom(false);
|
||||
if (bot.getRoomUnit() != null) {
|
||||
bot.getRoomUnit().setInRoom(false);
|
||||
}
|
||||
bot.setRoom(null);
|
||||
this.room.sendComposer(new RoomUserRemoveComposer(bot.getRoomUnit()).compose());
|
||||
if (bot.getRoomUnit() != null) {
|
||||
this.room.sendComposer(new RoomUserRemoveComposer(bot.getRoomUnit()).compose());
|
||||
}
|
||||
bot.setRoomUnit(null);
|
||||
return true;
|
||||
}
|
||||
@@ -876,7 +904,12 @@ public class RoomUnitManager {
|
||||
* Removes a Pet from the room.
|
||||
*/
|
||||
public Pet removePet(int petId) {
|
||||
return this.currentPets.remove(petId);
|
||||
Pet pet = this.currentPets.remove(petId);
|
||||
if (pet != null && pet.getRoomUnit() != null) {
|
||||
WiredMoveCarryHelper.cleanupRoomUnit(pet.getRoomUnit());
|
||||
WiredUserMovementHelper.cleanupRoomUnit(pet.getRoomUnit());
|
||||
}
|
||||
return pet;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1454,6 +1487,24 @@ public class RoomUnitManager {
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
for (Habbo habbo : this.currentHabbos.values()) {
|
||||
if (habbo.getRoomUnit() != null) {
|
||||
WiredMoveCarryHelper.cleanupRoomUnit(habbo.getRoomUnit());
|
||||
WiredUserMovementHelper.cleanupRoomUnit(habbo.getRoomUnit());
|
||||
}
|
||||
}
|
||||
for (Bot bot : this.currentBots.valueCollection()) {
|
||||
if (bot.getRoomUnit() != null) {
|
||||
WiredMoveCarryHelper.cleanupRoomUnit(bot.getRoomUnit());
|
||||
WiredUserMovementHelper.cleanupRoomUnit(bot.getRoomUnit());
|
||||
}
|
||||
}
|
||||
for (Pet pet : this.currentPets.valueCollection()) {
|
||||
if (pet.getRoomUnit() != null) {
|
||||
WiredMoveCarryHelper.cleanupRoomUnit(pet.getRoomUnit());
|
||||
WiredUserMovementHelper.cleanupRoomUnit(pet.getRoomUnit());
|
||||
}
|
||||
}
|
||||
this.currentHabbos.clear();
|
||||
this.currentBots.clear();
|
||||
this.currentPets.clear();
|
||||
|
||||
@@ -35,6 +35,7 @@ public class RoomUserVariableManager {
|
||||
|
||||
private final Room room;
|
||||
private final ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, VariableAssignment>> activeAssignmentsByUserId;
|
||||
private final java.util.concurrent.atomic.AtomicBoolean broadcastRequested = new java.util.concurrent.atomic.AtomicBoolean(false);
|
||||
|
||||
public RoomUserVariableManager(Room room) {
|
||||
this.room = room;
|
||||
@@ -660,7 +661,22 @@ public class RoomUserVariableManager {
|
||||
habbo.getClient().sendResponse(new WiredUserVariablesDataComposer(this.createSnapshot(), this.room.getFurniVariableManager().createSnapshot(), this.room.getRoomVariableManager().createSnapshot()));
|
||||
}
|
||||
|
||||
public void requestBroadcast() {
|
||||
if (this.broadcastRequested.compareAndSet(false, true)) {
|
||||
Emulator.getThreading().run(() -> {
|
||||
this.broadcastRequested.set(false);
|
||||
if (this.room.isLoaded()) {
|
||||
this.broadcastSnapshotRaw();
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
public void broadcastSnapshot() {
|
||||
this.requestBroadcast();
|
||||
}
|
||||
|
||||
public void broadcastSnapshotRaw() {
|
||||
Snapshot userSnapshot = this.createSnapshot();
|
||||
RoomFurniVariableManager.Snapshot furniSnapshot = this.room.getFurniVariableManager().createSnapshot();
|
||||
RoomVariableManager.Snapshot roomSnapshot = this.room.getRoomVariableManager().createSnapshot();
|
||||
|
||||
@@ -35,6 +35,7 @@ public class RoomVariableManager {
|
||||
private final Room room;
|
||||
private final ConcurrentHashMap<Integer, VariableAssignment> activeAssignmentsByDefinitionId;
|
||||
private volatile boolean persistentValuesLoaded;
|
||||
private final java.util.concurrent.atomic.AtomicBoolean broadcastRequested = new java.util.concurrent.atomic.AtomicBoolean(false);
|
||||
|
||||
public RoomVariableManager(Room room) {
|
||||
this.room = room;
|
||||
@@ -433,7 +434,22 @@ public class RoomVariableManager {
|
||||
habbo.getClient().sendResponse(new WiredUserVariablesDataComposer(this.room.getUserVariableManager().createSnapshot(), this.room.getFurniVariableManager().createSnapshot(), this.createSnapshot()));
|
||||
}
|
||||
|
||||
public void requestBroadcast() {
|
||||
if (this.broadcastRequested.compareAndSet(false, true)) {
|
||||
Emulator.getThreading().run(() -> {
|
||||
this.broadcastRequested.set(false);
|
||||
if (this.room.isLoaded()) {
|
||||
this.broadcastSnapshotRaw();
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
public void broadcastSnapshot() {
|
||||
this.requestBroadcast();
|
||||
}
|
||||
|
||||
public void broadcastSnapshotRaw() {
|
||||
RoomUserVariableManager.Snapshot userSnapshot = this.room.getUserVariableManager().createSnapshot();
|
||||
RoomFurniVariableManager.Snapshot furniSnapshot = this.room.getFurniVariableManager().createSnapshot();
|
||||
Snapshot roomSnapshot = this.createSnapshot();
|
||||
|
||||
@@ -38,6 +38,7 @@ public class HabboManager {
|
||||
|
||||
private final ConcurrentHashMap<Integer, Habbo> onlineHabbos;
|
||||
private final ConcurrentHashMap<String, Habbo> onlineHabbosByName;
|
||||
private final ConcurrentHashMap<Integer, String> usernameCache = new ConcurrentHashMap<>();
|
||||
|
||||
public HabboManager() {
|
||||
long millis = System.currentTimeMillis();
|
||||
@@ -158,6 +159,26 @@ public class HabboManager {
|
||||
return this.getHabbo(id).getHabboInfo();
|
||||
}
|
||||
|
||||
public String getCachedUsername(int id) {
|
||||
String cached = this.usernameCache.get(id);
|
||||
if (cached != null) return cached;
|
||||
|
||||
Habbo online = this.getHabbo(id);
|
||||
if (online != null) {
|
||||
String name = online.getHabboInfo().getUsername();
|
||||
this.usernameCache.put(id, name);
|
||||
return name;
|
||||
}
|
||||
|
||||
HabboInfo offline = getOfflineHabboInfo(id);
|
||||
if (offline != null) {
|
||||
String name = offline.getUsername();
|
||||
this.usernameCache.put(id, name);
|
||||
return name;
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
public int getOnlineCount() {
|
||||
return this.onlineHabbos.size();
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -89,11 +90,11 @@ public class WiredHandler {
|
||||
long millis = System.currentTimeMillis();
|
||||
List<LegacyExecutionPlan> executionPlans = new ArrayList<>();
|
||||
|
||||
List<RoomTile> triggeredTiles = new ArrayList<>();
|
||||
LinkedHashSet<Long> triggeredTiles = new LinkedHashSet<>();
|
||||
for (InteractionWiredTrigger trigger : triggers) {
|
||||
RoomTile tile = room.getLayout().getTile(trigger.getX(), trigger.getY());
|
||||
long coordinateKey = toTileCoordinateKey(trigger.getX(), trigger.getY());
|
||||
|
||||
if (triggeredTiles.contains(tile))
|
||||
if (!triggeredTiles.add(coordinateKey))
|
||||
continue;
|
||||
|
||||
LegacyExecutionPlan executionPlan = new LegacyExecutionPlan();
|
||||
@@ -103,8 +104,6 @@ public class WiredHandler {
|
||||
|
||||
if (triggerType.equals(WiredTriggerType.SAY_SOMETHING))
|
||||
talked = true;
|
||||
|
||||
triggeredTiles.add(tile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,20 +138,19 @@ public class WiredHandler {
|
||||
long millis = System.currentTimeMillis();
|
||||
List<LegacyExecutionPlan> executionPlans = new ArrayList<>();
|
||||
|
||||
List<RoomTile> triggeredTiles = new ArrayList<>();
|
||||
LinkedHashSet<Long> triggeredTiles = new LinkedHashSet<>();
|
||||
for (InteractionWiredTrigger trigger : triggers) {
|
||||
if (trigger.getClass() != triggerType) continue;
|
||||
|
||||
RoomTile tile = room.getLayout().getTile(trigger.getX(), trigger.getY());
|
||||
long coordinateKey = toTileCoordinateKey(trigger.getX(), trigger.getY());
|
||||
|
||||
if (triggeredTiles.contains(tile))
|
||||
if (!triggeredTiles.add(coordinateKey))
|
||||
continue;
|
||||
|
||||
LegacyExecutionPlan executionPlan = new LegacyExecutionPlan();
|
||||
|
||||
if (handle(trigger, roomUnit, room, stuff, executionPlan)) {
|
||||
executionPlans.add(executionPlan);
|
||||
triggeredTiles.add(tile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +185,11 @@ public class WiredHandler {
|
||||
WiredExtraExecutionLimit executionLimitExtra = null;
|
||||
WiredExtraRandom randomExtra = null;
|
||||
|
||||
int conditionEvaluationMode = WiredExtraOrEval.MODE_ALL;
|
||||
int conditionEvaluationValue = 1;
|
||||
boolean hasExtraUnseen = false;
|
||||
boolean hasExtraExecuteInOrder = false;
|
||||
|
||||
for (InteractionWiredExtra extra : extras) {
|
||||
if (executionLimitExtra == null && extra instanceof WiredExtraExecutionLimit) {
|
||||
executionLimitExtra = (WiredExtraExecutionLimit) extra;
|
||||
@@ -195,18 +198,22 @@ public class WiredHandler {
|
||||
if (randomExtra == null && extra instanceof WiredExtraRandom) {
|
||||
randomExtra = (WiredExtraRandom) extra;
|
||||
}
|
||||
|
||||
if (!hasExtraUnseen && extra instanceof WiredExtraUnseen) {
|
||||
hasExtraUnseen = true;
|
||||
}
|
||||
|
||||
if (!hasExtraExecuteInOrder && extra instanceof WiredExtraExecuteInOrder) {
|
||||
hasExtraExecuteInOrder = true;
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraOrEval) {
|
||||
conditionEvaluationMode = ((WiredExtraOrEval) extra).getEvaluationMode();
|
||||
conditionEvaluationValue = ((WiredExtraOrEval) extra).getCompareValue();
|
||||
}
|
||||
}
|
||||
|
||||
if (!conditions.isEmpty()) {
|
||||
int conditionEvaluationMode = WiredExtraOrEval.MODE_ALL;
|
||||
int conditionEvaluationValue = 1;
|
||||
for (InteractionWiredExtra extra : extras) {
|
||||
if (extra instanceof WiredExtraOrEval) {
|
||||
conditionEvaluationMode = ((WiredExtraOrEval) extra).getEvaluationMode();
|
||||
conditionEvaluationValue = ((WiredExtraOrEval) extra).getCompareValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!evaluateConditions(conditions, roomUnit, room, stuff, conditionEvaluationMode, conditionEvaluationValue)) {
|
||||
for (InteractionWiredCondition condition : conditions) {
|
||||
@@ -230,9 +237,6 @@ public class WiredHandler {
|
||||
|
||||
trigger.setCooldown(millis);
|
||||
|
||||
boolean hasExtraUnseen = room.getRoomSpecialTypes().hasExtraType(trigger.getX(), trigger.getY(), WiredExtraUnseen.class);
|
||||
boolean hasExtraExecuteInOrder = room.getRoomSpecialTypes().hasExtraType(trigger.getX(), trigger.getY(), WiredExtraExecuteInOrder.class);
|
||||
|
||||
for (InteractionWiredExtra extra : extras) {
|
||||
extra.activateBox(room, roomUnit, millis);
|
||||
}
|
||||
@@ -244,7 +248,7 @@ public class WiredHandler {
|
||||
executionPlan.executeInOrder = hasExtraExecuteInOrder;
|
||||
|
||||
if (hasExtraUnseen) {
|
||||
for (InteractionWiredExtra extra : room.getRoomSpecialTypes().getExtras(trigger.getX(), trigger.getY())) {
|
||||
for (InteractionWiredExtra extra : extras) {
|
||||
if (extra instanceof WiredExtraUnseen) {
|
||||
extra.setExtradata(extra.getExtradata().equals("1") ? "0" : "1");
|
||||
InteractionWiredEffect effect = ((WiredExtraUnseen) extra).getUnseenEffect(effectList);
|
||||
@@ -357,20 +361,14 @@ public class WiredHandler {
|
||||
}
|
||||
}
|
||||
|
||||
LinkedHashSet<Integer> delays = new LinkedHashSet<>();
|
||||
Map<Integer, List<InteractionWiredEffect>> delayBatches = new LinkedHashMap<>();
|
||||
for (InteractionWiredEffect effect : queueableEffects) {
|
||||
delays.add(effect.getDelay());
|
||||
delayBatches.computeIfAbsent(effect.getDelay(), ignored -> new ArrayList<>()).add(effect);
|
||||
}
|
||||
|
||||
for (Integer delay : delays) {
|
||||
List<InteractionWiredEffect> delayBatch = new ArrayList<>();
|
||||
|
||||
for (InteractionWiredEffect effect : queueableEffects) {
|
||||
if (effect.getDelay() == delay) {
|
||||
delayBatch.add(effect);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<Integer, List<InteractionWiredEffect>> entry : delayBatches.entrySet()) {
|
||||
Integer delay = entry.getKey();
|
||||
List<InteractionWiredEffect> delayBatch = entry.getValue();
|
||||
if (delayBatch.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
@@ -424,11 +422,19 @@ public class WiredHandler {
|
||||
|
||||
public static GsonBuilder getGsonBuilder() {
|
||||
if(gsonBuilder == null) {
|
||||
gsonBuilder = new GsonBuilder();
|
||||
synchronized (WiredHandler.class) {
|
||||
if (gsonBuilder == null) {
|
||||
gsonBuilder = new GsonBuilder();
|
||||
}
|
||||
}
|
||||
}
|
||||
return gsonBuilder;
|
||||
}
|
||||
|
||||
private static long toTileCoordinateKey(int x, int y) {
|
||||
return (((long) x) << 32) | (y & 0xffffffffL);
|
||||
}
|
||||
|
||||
public static boolean executeEffectsAtTiles(THashSet<RoomTile> tiles, final RoomUnit roomUnit, final Room room, final Object[] stuff) {
|
||||
for (RoomTile tile : tiles) {
|
||||
if (room != null) {
|
||||
@@ -470,7 +476,7 @@ public class WiredHandler {
|
||||
|
||||
private static void completeReward(Habbo habbo, WiredEffectGiveReward wiredBox, WiredGiveRewardItem reward, int successCode) {
|
||||
if (wiredBox.limit > 0)
|
||||
wiredBox.given++;
|
||||
wiredBox.incrementGiven();
|
||||
|
||||
persistReward(wiredBox.getId(), habbo.getHabboInfo().getId(), reward.id, Emulator.getIntUnixTimestamp());
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(successCode));
|
||||
@@ -569,93 +575,124 @@ public class WiredHandler {
|
||||
|
||||
public static boolean getReward(Habbo habbo, WiredEffectGiveReward wiredBox) {
|
||||
if (wiredBox.limit > 0) {
|
||||
if (wiredBox.limit - wiredBox.given == 0) {
|
||||
if (wiredBox.limit - wiredBox.getGiven() == 0) {
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.LIMITED_NO_MORE_AVAILABLE));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT COUNT(*) as row_count, wired_rewards_given.* FROM wired_rewards_given WHERE user_id = ? AND wired_item = ? ORDER BY timestamp DESC LIMIT ?", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) {
|
||||
WiredGiveRewardItem rewardToGive = null;
|
||||
int failureCode = -1;
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM wired_rewards_given WHERE user_id = ? AND wired_item = ? ORDER BY timestamp DESC LIMIT ?", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) {
|
||||
statement.setInt(1, habbo.getHabboInfo().getId());
|
||||
statement.setInt(2, wiredBox.getId());
|
||||
statement.setInt(3, wiredBox.rewardItems.size());
|
||||
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
if (set.first()) {
|
||||
if (set.getInt("row_count") >= 1) {
|
||||
set.last();
|
||||
int rowCount = set.getRow();
|
||||
set.first();
|
||||
|
||||
if (rowCount >= 1) {
|
||||
if (wiredBox.rewardTime == WiredEffectGiveReward.LIMIT_ONCE) {
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED));
|
||||
return false;
|
||||
failureCode = WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED;
|
||||
}
|
||||
}
|
||||
|
||||
set.beforeFirst();
|
||||
if (set.next()) {
|
||||
if (failureCode == -1) {
|
||||
if (wiredBox.rewardTime == WiredEffectGiveReward.LIMIT_N_MINUTES) {
|
||||
if (Emulator.getIntUnixTimestamp() - set.getInt("timestamp") <= 60) {
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_MINUTE));
|
||||
return false;
|
||||
failureCode = WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_MINUTE;
|
||||
}
|
||||
}
|
||||
|
||||
if (wiredBox.uniqueRewards) {
|
||||
if (set.getInt("row_count") == wiredBox.rewardItems.size()) {
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALL_COLLECTED));
|
||||
return false;
|
||||
if (failureCode == -1 && wiredBox.uniqueRewards) {
|
||||
if (rowCount == wiredBox.rewardItems.size()) {
|
||||
failureCode = WiredRewardAlertComposer.REWARD_ALL_COLLECTED;
|
||||
}
|
||||
}
|
||||
|
||||
if (wiredBox.rewardTime == WiredEffectGiveReward.LIMIT_N_HOURS) {
|
||||
if (failureCode == -1 && wiredBox.rewardTime == WiredEffectGiveReward.LIMIT_N_HOURS) {
|
||||
if (!(Emulator.getIntUnixTimestamp() - set.getInt("timestamp") >= (3600 * wiredBox.limitationInterval))) {
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_HOUR));
|
||||
return false;
|
||||
failureCode = WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_HOUR;
|
||||
}
|
||||
}
|
||||
|
||||
if (wiredBox.rewardTime == WiredEffectGiveReward.LIMIT_N_DAY) {
|
||||
if (failureCode == -1 && wiredBox.rewardTime == WiredEffectGiveReward.LIMIT_N_DAY) {
|
||||
if (!(Emulator.getIntUnixTimestamp() - set.getInt("timestamp") >= (86400 * wiredBox.limitationInterval))) {
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_TODAY));
|
||||
return false;
|
||||
failureCode = WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_TODAY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wiredBox.uniqueRewards) {
|
||||
for (WiredGiveRewardItem item : wiredBox.rewardItems) {
|
||||
set.beforeFirst();
|
||||
boolean found = false;
|
||||
if (failureCode == -1) {
|
||||
if (wiredBox.uniqueRewards) {
|
||||
for (WiredGiveRewardItem item : wiredBox.rewardItems) {
|
||||
set.beforeFirst();
|
||||
boolean found = false;
|
||||
|
||||
while (set.next()) {
|
||||
if (set.getInt("reward_id") == item.id)
|
||||
found = true;
|
||||
while (set.next()) {
|
||||
if (set.getInt("reward_id") == item.id)
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
rewardToGive = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return giveReward(habbo, wiredBox, item);
|
||||
if (rewardToGive == null) {
|
||||
failureCode = WiredRewardAlertComposer.REWARD_ALL_COLLECTED;
|
||||
}
|
||||
}
|
||||
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALL_COLLECTED));
|
||||
return false;
|
||||
} else {
|
||||
int randomNumber = Emulator.getRandom().nextInt(101);
|
||||
|
||||
int count = 0;
|
||||
for (WiredGiveRewardItem item : wiredBox.rewardItems) {
|
||||
if (randomNumber >= count && randomNumber <= (count + item.probability)) {
|
||||
return giveReward(habbo, wiredBox, item);
|
||||
}
|
||||
|
||||
count += item.probability;
|
||||
}
|
||||
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.UNLUCKY_NO_REWARD));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Caught SQL exception", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (failureCode != -1) {
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(failureCode));
|
||||
return false;
|
||||
}
|
||||
|
||||
// If no unique reward was determined and there are no failures, pick a random reward or the first unique one
|
||||
if (rewardToGive == null) {
|
||||
if (wiredBox.uniqueRewards) {
|
||||
if (!wiredBox.rewardItems.isEmpty()) {
|
||||
rewardToGive = wiredBox.rewardItems.get(0);
|
||||
} else {
|
||||
failureCode = WiredRewardAlertComposer.REWARD_ALL_COLLECTED;
|
||||
}
|
||||
} else {
|
||||
int randomNumber = Emulator.getRandom().nextInt(101);
|
||||
int count = 0;
|
||||
for (WiredGiveRewardItem item : wiredBox.rewardItems) {
|
||||
if (randomNumber >= count && randomNumber <= (count + item.probability)) {
|
||||
rewardToGive = item;
|
||||
break;
|
||||
}
|
||||
count += item.probability;
|
||||
}
|
||||
|
||||
if (rewardToGive == null) {
|
||||
failureCode = WiredRewardAlertComposer.UNLUCKY_NO_REWARD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failureCode != -1) {
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(failureCode));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rewardToGive != null) {
|
||||
return giveReward(habbo, wiredBox, rewardToGive);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
+19
-23
@@ -71,27 +71,12 @@ public final class RoomWiredStackIndex implements WiredStackIndex {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// Check cache first
|
||||
if (useCache) {
|
||||
Map<WiredEvent.Type, List<WiredStack>> roomCache = cache.get(room.getId());
|
||||
if (roomCache != null) {
|
||||
List<WiredStack> cached = roomCache.get(type);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
return cache.computeIfAbsent(room.getId(), k -> new ConcurrentHashMap<>())
|
||||
.computeIfAbsent(type, t -> buildStacks(room, t));
|
||||
} else {
|
||||
return buildStacks(room, type);
|
||||
}
|
||||
|
||||
// Build stacks for this event type
|
||||
List<WiredStack> stacks = buildStacks(room, type);
|
||||
|
||||
// Cache the result
|
||||
if (useCache) {
|
||||
cache.computeIfAbsent(room.getId(), k -> new ConcurrentHashMap<>())
|
||||
.put(type, stacks);
|
||||
}
|
||||
|
||||
return stacks;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -206,16 +191,27 @@ public final class RoomWiredStackIndex implements WiredStackIndex {
|
||||
THashSet<InteractionWiredExtra> extras = specialTypes.getExtras(x, y);
|
||||
int conditionEvaluationMode = WiredExtraOrEval.MODE_ALL;
|
||||
int conditionEvaluationValue = 1;
|
||||
boolean useRandom = specialTypes.hasExtraType(x, y, WiredExtraRandom.class);
|
||||
boolean useUnseen = specialTypes.hasExtraType(x, y, WiredExtraUnseen.class);
|
||||
boolean executeInOrder = specialTypes.hasExtraType(x, y, WiredExtraExecuteInOrder.class);
|
||||
boolean useRandom = false;
|
||||
boolean useUnseen = false;
|
||||
boolean executeInOrder = false;
|
||||
|
||||
if (extras != null) {
|
||||
for (InteractionWiredExtra extra : extras) {
|
||||
if (!useRandom && extra instanceof WiredExtraRandom) {
|
||||
useRandom = true;
|
||||
}
|
||||
|
||||
if (!useUnseen && extra instanceof WiredExtraUnseen) {
|
||||
useUnseen = true;
|
||||
}
|
||||
|
||||
if (!executeInOrder && extra instanceof WiredExtraExecuteInOrder) {
|
||||
executeInOrder = true;
|
||||
}
|
||||
|
||||
if (extra instanceof WiredExtraOrEval) {
|
||||
conditionEvaluationMode = ((WiredExtraOrEval) extra).getEvaluationMode();
|
||||
conditionEvaluationValue = ((WiredExtraOrEval) extra).getCompareValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,10 +86,10 @@ public final class WiredEngine {
|
||||
public static int MONITOR_USAGE_WINDOW_MS = 1000;
|
||||
|
||||
/** Monitor execution cap per room window */
|
||||
public static int MONITOR_USAGE_LIMIT = 1000;
|
||||
public static int MONITOR_USAGE_LIMIT = 50000;
|
||||
|
||||
/** Maximum delayed events allowed per room at the same time */
|
||||
public static int MONITOR_DELAYED_EVENTS_LIMIT = 100;
|
||||
public static int MONITOR_DELAYED_EVENTS_LIMIT = 50000;
|
||||
|
||||
/** Average execution threshold that marks overload */
|
||||
public static int MONITOR_OVERLOAD_AVERAGE_MS = 50;
|
||||
@@ -180,14 +180,19 @@ public final class WiredEngine {
|
||||
|
||||
int roomId = room.getId();
|
||||
|
||||
if (this.isRoomBanned(roomId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Soft rate limiting to prevent rapid-fire event spam without banning whole rooms
|
||||
if (isRateLimited(roomId, room, event.getType())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check and increment recursion depth to prevent infinite loops
|
||||
int currentDepth = roomRecursionDepth.getOrDefault(roomId, 0);
|
||||
if (currentDepth >= MAX_RECURSION_DEPTH) {
|
||||
int currentDepth = roomRecursionDepth.merge(roomId, 1, Integer::sum);
|
||||
if (currentDepth > MAX_RECURSION_DEPTH) {
|
||||
roomRecursionDepth.merge(roomId, -1, Integer::sum);
|
||||
getDiagnostics(roomId).recordRecursionTimeout(
|
||||
System.currentTimeMillis(),
|
||||
String.format("Recursion depth %d/%d while handling %s", currentDepth, MAX_RECURSION_DEPTH, event.getType().name()),
|
||||
@@ -199,18 +204,12 @@ public final class WiredEngine {
|
||||
debug(room, "RECURSION LIMIT REACHED - aborting to prevent crash");
|
||||
return false;
|
||||
}
|
||||
roomRecursionDepth.put(roomId, currentDepth + 1);
|
||||
|
||||
try {
|
||||
return handleEventInternal(event, room, negateConditions);
|
||||
} finally {
|
||||
// Decrement recursion depth
|
||||
int newDepth = roomRecursionDepth.getOrDefault(roomId, 1) - 1;
|
||||
if (newDepth <= 0) {
|
||||
roomRecursionDepth.remove(roomId);
|
||||
} else {
|
||||
roomRecursionDepth.put(roomId, newDepth);
|
||||
}
|
||||
roomRecursionDepth.compute(roomId, (k, v) -> (v == null || v <= 1) ? null : v - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,28 +233,27 @@ public final class WiredEngine {
|
||||
|
||||
int roomId = room.getId();
|
||||
|
||||
if (this.isRoomBanned(roomId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isRateLimited(roomId, room, event.getType())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int currentDepth = roomRecursionDepth.getOrDefault(roomId, 0);
|
||||
if (currentDepth >= MAX_RECURSION_DEPTH) {
|
||||
int currentDepth = roomRecursionDepth.merge(roomId, 1, Integer::sum);
|
||||
if (currentDepth > MAX_RECURSION_DEPTH) {
|
||||
roomRecursionDepth.merge(roomId, -1, Integer::sum);
|
||||
LOGGER.warn("Wired recursion limit reached in room {} (depth: {}). " +
|
||||
"Possible infinite loop detected (source item execution). Aborting.", roomId, currentDepth);
|
||||
debug(room, "RECURSION LIMIT REACHED - aborting source-item execution");
|
||||
return false;
|
||||
}
|
||||
roomRecursionDepth.put(roomId, currentDepth + 1);
|
||||
|
||||
try {
|
||||
return handleEventForSourceItemInternal(event, room, sourceItemId);
|
||||
} finally {
|
||||
int newDepth = roomRecursionDepth.getOrDefault(roomId, 1) - 1;
|
||||
if (newDepth <= 0) {
|
||||
roomRecursionDepth.remove(roomId);
|
||||
} else {
|
||||
roomRecursionDepth.put(roomId, newDepth);
|
||||
}
|
||||
roomRecursionDepth.compute(roomId, (k, v) -> (v == null || v <= 1) ? null : v - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1094,7 +1092,13 @@ public final class WiredEngine {
|
||||
}
|
||||
|
||||
private void animateFilteredSelectorBox(Room room, InteractionWiredEffect wiredEffect) {
|
||||
if (room == null || wiredEffect == null || room.isHideWired()) {
|
||||
if (room == null || wiredEffect == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If wired is hidden, skip animation but ensure any stale token is cleaned up
|
||||
if (room.isHideWired()) {
|
||||
this.filteredSelectorAnimationTokens.remove(wiredEffect.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1364,11 +1368,10 @@ public final class WiredEngine {
|
||||
? String.valueOf(stack.triggerItem().getId())
|
||||
: "default";
|
||||
|
||||
int current = unseenIndices.getOrDefault(key, -1);
|
||||
int next = (current + 1) % effectCount;
|
||||
unseenIndices.put(key, next);
|
||||
|
||||
return next;
|
||||
return unseenIndices.compute(key, (k, current) -> {
|
||||
if (current == null) current = -1;
|
||||
return (current + 1) % effectCount;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1622,6 +1625,8 @@ public final class WiredEngine {
|
||||
clearRoomRecursionDepth(roomId);
|
||||
clearRoomRateLimiters(roomId);
|
||||
clearRoomSourceStackCache(roomId);
|
||||
clearRoomDiagnostics(roomId);
|
||||
clearRoomBan(roomId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1684,38 +1689,46 @@ public final class WiredEngine {
|
||||
* @param room the room object
|
||||
*/
|
||||
private void banRoom(int roomId, Room room, WiredEvent.Type eventType, int eventCount) {
|
||||
long banExpiry = System.currentTimeMillis() + WIRED_BAN_DURATION_MS;
|
||||
bannedRooms.put(roomId, banExpiry);
|
||||
getDiagnostics(roomId).recordKilled(
|
||||
System.currentTimeMillis(),
|
||||
String.format("Rate limit exceeded for %s with %d event(s) in %dms", eventType.name(), eventCount, RATE_LIMIT_WINDOW_MS),
|
||||
eventType.name(),
|
||||
0
|
||||
);
|
||||
|
||||
long banMinutes = WIRED_BAN_DURATION_MS / 60000;
|
||||
|
||||
// Send alert to all users in the room
|
||||
String roomAlertMessage = Emulator.getTexts().getValue("wired.abuse.room.alert")
|
||||
.replace("%minutes%", String.valueOf(banMinutes));
|
||||
room.sendComposer(new GenericAlertComposer(roomAlertMessage).compose());
|
||||
|
||||
// Send scripter bubble alert to staff with room link
|
||||
THashMap<String, String> keys = new THashMap<>();
|
||||
keys.put("title", Emulator.getTexts().getValue("wired.abuse.staff.title"));
|
||||
keys.put("message", Emulator.getTexts().getValue("wired.abuse.staff.message")
|
||||
.replace("%roomname%", room.getName())
|
||||
.replace("%owner%", room.getOwnerName())
|
||||
.replace("%minutes%", String.valueOf(banMinutes)));
|
||||
keys.put("linkUrl", "event:navigator/goto/" + roomId);
|
||||
keys.put("linkTitle", Emulator.getTexts().getValue("wired.abuse.staff.link"));
|
||||
Emulator.getGameEnvironment().getHabboManager().sendPacketToHabbosWithPermission(
|
||||
new BubbleAlertComposer("admin.staffalert", keys).compose(),
|
||||
"acc_modtool_room_info"
|
||||
);
|
||||
|
||||
LOGGER.warn("Wired abuse detected in room {} ({}). Owner: {}. Wired banned for {} minutes.",
|
||||
roomId, room.getName(), room.getOwnerName(), banMinutes);
|
||||
|
||||
// Only actually ban the room if ban duration is configured (> 0)
|
||||
if (WIRED_BAN_DURATION_MS > 0) {
|
||||
long banExpiry = System.currentTimeMillis() + WIRED_BAN_DURATION_MS;
|
||||
bannedRooms.put(roomId, banExpiry);
|
||||
|
||||
long banMinutes = WIRED_BAN_DURATION_MS / 60000;
|
||||
|
||||
// Send alert to all users in the room
|
||||
String roomAlertMessage = Emulator.getTexts().getValue("wired.abuse.room.alert")
|
||||
.replace("%minutes%", String.valueOf(banMinutes));
|
||||
room.sendComposer(new GenericAlertComposer(roomAlertMessage).compose());
|
||||
|
||||
// Send scripter bubble alert to staff with room link
|
||||
THashMap<String, String> keys = new THashMap<>();
|
||||
keys.put("title", Emulator.getTexts().getValue("wired.abuse.staff.title"));
|
||||
keys.put("message", Emulator.getTexts().getValue("wired.abuse.staff.message")
|
||||
.replace("%roomname%", room.getName())
|
||||
.replace("%owner%", room.getOwnerName())
|
||||
.replace("%minutes%", String.valueOf(banMinutes)));
|
||||
keys.put("linkUrl", "event:navigator/goto/" + roomId);
|
||||
keys.put("linkTitle", Emulator.getTexts().getValue("wired.abuse.staff.link"));
|
||||
Emulator.getGameEnvironment().getHabboManager().sendPacketToHabbosWithPermission(
|
||||
new BubbleAlertComposer("admin.staffalert", keys).compose(),
|
||||
"acc_modtool_room_info"
|
||||
);
|
||||
|
||||
LOGGER.warn("Wired abuse detected in room {} ({}). Owner: {}. Wired banned for {} minutes.",
|
||||
roomId, room.getName(), room.getOwnerName(), banMinutes);
|
||||
} else {
|
||||
// Ban duration is 0 - only log, do not spam alerts or put a ban entry
|
||||
LOGGER.warn("Wired rate limit exceeded in room {} ({}) for event {} ({} events). Ban disabled (wired.abuse.ban.duration.ms=0).",
|
||||
roomId, room.getName(), eventType.name(), eventCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -913,10 +913,7 @@ public final class WiredManager {
|
||||
if (room != null) {
|
||||
room.getFurniVariableManager().clearTransientAssignments();
|
||||
room.getRoomVariableManager().clearTransientAssignments();
|
||||
}
|
||||
|
||||
if (engine != null && room != null) {
|
||||
engine.clearRoomExecutionCaches(room.getId());
|
||||
invalidateRoom(room);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1112,18 +1109,16 @@ public final class WiredManager {
|
||||
}
|
||||
|
||||
private static void persistReward(int wiredId, int habboId, int rewardId, int timestamp) {
|
||||
Emulator.getThreading().run(() -> {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("INSERT INTO wired_rewards_given (wired_item, user_id, reward_id, timestamp) VALUES ( ?, ?, ?, ?)")) {
|
||||
statement.setInt(1, wiredId);
|
||||
statement.setInt(2, habboId);
|
||||
statement.setInt(3, rewardId);
|
||||
statement.setInt(4, timestamp);
|
||||
statement.execute();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Caught SQL exception", e);
|
||||
}
|
||||
});
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("INSERT INTO wired_rewards_given (wired_item, user_id, reward_id, timestamp) VALUES (?, ?, ?, ?)")) {
|
||||
statement.setInt(1, wiredId);
|
||||
statement.setInt(2, habboId);
|
||||
statement.setInt(3, rewardId);
|
||||
statement.setInt(4, timestamp);
|
||||
statement.execute();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Caught SQL exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void completeReward(Habbo habbo, WiredEffectGiveReward wiredBox, WiredGiveRewardItem reward, int successCode) {
|
||||
@@ -1246,96 +1241,128 @@ public final class WiredManager {
|
||||
}
|
||||
|
||||
public static boolean getReward(Habbo habbo, WiredEffectGiveReward wiredBox) {
|
||||
if (wiredBox.getLimit() > 0) {
|
||||
if (wiredBox.getLimit() - wiredBox.getGiven() == 0) {
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.LIMITED_NO_MORE_AVAILABLE));
|
||||
synchronized (wiredBox) {
|
||||
if (wiredBox.getLimit() > 0) {
|
||||
if (wiredBox.getLimit() - wiredBox.getGiven() == 0) {
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.LIMITED_NO_MORE_AVAILABLE));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
WiredGiveRewardItem rewardToGive = null;
|
||||
int failureCode = -1;
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM wired_rewards_given WHERE user_id = ? AND wired_item = ? ORDER BY timestamp DESC LIMIT ?", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) {
|
||||
statement.setInt(1, habbo.getHabboInfo().getId());
|
||||
statement.setInt(2, wiredBox.getId());
|
||||
statement.setInt(3, wiredBox.getRewardItems().size());
|
||||
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
if (set.first()) {
|
||||
set.last();
|
||||
int rowCount = set.getRow();
|
||||
set.first();
|
||||
|
||||
if (rowCount >= 1) {
|
||||
if (wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_ONCE) {
|
||||
failureCode = WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED;
|
||||
}
|
||||
}
|
||||
|
||||
if (failureCode == -1) {
|
||||
if (wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_N_MINUTES) {
|
||||
if (Emulator.getIntUnixTimestamp() - set.getInt("timestamp") <= 60) {
|
||||
failureCode = WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_MINUTE;
|
||||
}
|
||||
}
|
||||
|
||||
if (failureCode == -1 && wiredBox.isUniqueRewards()) {
|
||||
if (rowCount == wiredBox.getRewardItems().size()) {
|
||||
failureCode = WiredRewardAlertComposer.REWARD_ALL_COLLECTED;
|
||||
}
|
||||
}
|
||||
|
||||
if (failureCode == -1 && wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_N_HOURS) {
|
||||
if (!(Emulator.getIntUnixTimestamp() - set.getInt("timestamp") >= (3600 * wiredBox.getLimitationInterval()))) {
|
||||
failureCode = WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_HOUR;
|
||||
}
|
||||
}
|
||||
|
||||
if (failureCode == -1 && wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_N_DAY) {
|
||||
if (!(Emulator.getIntUnixTimestamp() - set.getInt("timestamp") >= (86400 * wiredBox.getLimitationInterval()))) {
|
||||
failureCode = WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_TODAY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failureCode == -1) {
|
||||
if (wiredBox.isUniqueRewards()) {
|
||||
for (WiredGiveRewardItem item : wiredBox.getRewardItems()) {
|
||||
set.beforeFirst();
|
||||
boolean found = false;
|
||||
|
||||
while (set.next()) {
|
||||
if (set.getInt("reward_id") == item.id)
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
rewardToGive = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (rewardToGive == null) {
|
||||
failureCode = WiredRewardAlertComposer.REWARD_ALL_COLLECTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Caught SQL exception", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT COUNT(*) as row_count, wired_rewards_given.* FROM wired_rewards_given WHERE user_id = ? AND wired_item = ? ORDER BY timestamp DESC LIMIT ?", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) {
|
||||
statement.setInt(1, habbo.getHabboInfo().getId());
|
||||
statement.setInt(2, wiredBox.getId());
|
||||
statement.setInt(3, wiredBox.getRewardItems().size());
|
||||
if (failureCode != -1) {
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(failureCode));
|
||||
return false;
|
||||
}
|
||||
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
if (set.first()) {
|
||||
if (set.getInt("row_count") >= 1) {
|
||||
if (wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_ONCE) {
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
set.beforeFirst();
|
||||
if (set.next()) {
|
||||
if (wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_N_MINUTES) {
|
||||
if (Emulator.getIntUnixTimestamp() - set.getInt("timestamp") <= 60) {
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_MINUTE));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (wiredBox.isUniqueRewards()) {
|
||||
if (set.getInt("row_count") == wiredBox.getRewardItems().size()) {
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALL_COLLECTED));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_N_HOURS) {
|
||||
if (!(Emulator.getIntUnixTimestamp() - set.getInt("timestamp") >= (3600 * wiredBox.getLimitationInterval()))) {
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_HOUR));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_N_DAY) {
|
||||
if (!(Emulator.getIntUnixTimestamp() - set.getInt("timestamp") >= (86400 * wiredBox.getLimitationInterval()))) {
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_TODAY));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wiredBox.isUniqueRewards()) {
|
||||
for (WiredGiveRewardItem item : wiredBox.getRewardItems()) {
|
||||
set.beforeFirst();
|
||||
boolean found = false;
|
||||
|
||||
while (set.next()) {
|
||||
if (set.getInt("reward_id") == item.id)
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return giveReward(habbo, wiredBox, item);
|
||||
}
|
||||
}
|
||||
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALL_COLLECTED));
|
||||
return false;
|
||||
if (rewardToGive == null) {
|
||||
if (wiredBox.isUniqueRewards()) {
|
||||
if (!wiredBox.getRewardItems().isEmpty()) {
|
||||
rewardToGive = wiredBox.getRewardItems().get(0);
|
||||
} else {
|
||||
int randomNumber = Emulator.getRandom().nextInt(101);
|
||||
|
||||
int count = 0;
|
||||
for (WiredGiveRewardItem item : wiredBox.getRewardItems()) {
|
||||
if (randomNumber >= count && randomNumber <= (count + item.probability)) {
|
||||
return giveReward(habbo, wiredBox, item);
|
||||
}
|
||||
|
||||
count += item.probability;
|
||||
failureCode = WiredRewardAlertComposer.REWARD_ALL_COLLECTED;
|
||||
}
|
||||
} else {
|
||||
int randomNumber = Emulator.getRandom().nextInt(101);
|
||||
int count = 0;
|
||||
for (WiredGiveRewardItem item : wiredBox.getRewardItems()) {
|
||||
if (randomNumber >= count && randomNumber <= (count + item.probability)) {
|
||||
rewardToGive = item;
|
||||
break;
|
||||
}
|
||||
count += item.probability;
|
||||
}
|
||||
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.UNLUCKY_NO_REWARD));
|
||||
return false;
|
||||
if (rewardToGive == null) {
|
||||
failureCode = WiredRewardAlertComposer.UNLUCKY_NO_REWARD;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Caught SQL exception", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
if (failureCode != -1) {
|
||||
habbo.getClient().sendResponse(new WiredRewardAlertComposer(failureCode));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rewardToGive != null) {
|
||||
return giveReward(habbo, wiredBox, rewardToGive);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1105,4 +1105,11 @@ public final class WiredMoveCarryHelper {
|
||||
this.expiresAt = System.currentTimeMillis() + USER_FOLLOWER_TTL_MS;
|
||||
}
|
||||
}
|
||||
|
||||
public static void cleanupRoomUnit(RoomUnit roomUnit) {
|
||||
if (roomUnit != null) {
|
||||
SUPPRESSED_STATUS_COMPOSER_UNTIL.remove(roomUnit.getId());
|
||||
ACTIVE_USER_FOLLOWERS.remove(roomUnit.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+77
-65
@@ -266,19 +266,19 @@ public final class WiredRoomDiagnostics {
|
||||
private final ArrayDeque<HistoryEntry> history;
|
||||
private final int maxHistoryEntries;
|
||||
|
||||
private long windowStartedAt;
|
||||
private int usageCurrentWindow;
|
||||
private int delayedEventsPending;
|
||||
private long totalExecutionMsCurrentWindow;
|
||||
private int executionSamplesCurrentWindow;
|
||||
private int averageExecutionMs;
|
||||
private int peakExecutionMs;
|
||||
private int consecutiveHeavyWindows;
|
||||
private int consecutiveOverloadWindows;
|
||||
private boolean heavy;
|
||||
private String peakExecutionSourceLabel;
|
||||
private int peakExecutionSourceId;
|
||||
private String peakExecutionReason;
|
||||
private final java.util.concurrent.atomic.AtomicLong windowStartedAt = new java.util.concurrent.atomic.AtomicLong();
|
||||
private final java.util.concurrent.atomic.AtomicInteger usageCurrentWindow = new java.util.concurrent.atomic.AtomicInteger();
|
||||
private final java.util.concurrent.atomic.AtomicInteger delayedEventsPending = new java.util.concurrent.atomic.AtomicInteger();
|
||||
private final java.util.concurrent.atomic.AtomicLong totalExecutionMsCurrentWindow = new java.util.concurrent.atomic.AtomicLong();
|
||||
private final java.util.concurrent.atomic.AtomicInteger executionSamplesCurrentWindow = new java.util.concurrent.atomic.AtomicInteger();
|
||||
private volatile int averageExecutionMs;
|
||||
private volatile int peakExecutionMs;
|
||||
private volatile int consecutiveHeavyWindows;
|
||||
private volatile int consecutiveOverloadWindows;
|
||||
private volatile boolean heavy;
|
||||
private volatile String peakExecutionSourceLabel;
|
||||
private volatile int peakExecutionSourceId;
|
||||
private volatile String peakExecutionReason;
|
||||
|
||||
public WiredRoomDiagnostics(int usageWindowMs, int usageLimitPerWindow, int delayedEventsLimit,
|
||||
int overloadAverageThresholdMs, int overloadPeakThresholdMs,
|
||||
@@ -310,67 +310,69 @@ public final class WiredRoomDiagnostics {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean tryConsumeExecutionBudget(int estimatedCost, long now, String sourceLabel, int sourceId, String reason) {
|
||||
rollWindow(now);
|
||||
public boolean tryConsumeExecutionBudget(int estimatedCost, long now, String sourceLabel, int sourceId, String reason) {
|
||||
rollWindowIfNeeded(now);
|
||||
|
||||
int normalizedCost = Math.max(0, estimatedCost);
|
||||
if ((this.usageCurrentWindow + normalizedCost) > this.usageLimitPerWindow) {
|
||||
int currentUsage = this.usageCurrentWindow.addAndGet(normalizedCost);
|
||||
if (currentUsage > this.usageLimitPerWindow) {
|
||||
record(Type.EXECUTION_CAP, now,
|
||||
buildExecutionCapReason(normalizedCost, reason),
|
||||
buildExecutionCapReason(normalizedCost, reason, currentUsage),
|
||||
sourceLabel,
|
||||
sourceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.usageCurrentWindow += normalizedCost;
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized boolean tryScheduleDelayedEvent(long now, String sourceLabel, int sourceId, String reason) {
|
||||
rollWindow(now);
|
||||
public boolean tryScheduleDelayedEvent(long now, String sourceLabel, int sourceId, String reason) {
|
||||
rollWindowIfNeeded(now);
|
||||
|
||||
if ((this.delayedEventsPending + 1) > this.delayedEventsLimit) {
|
||||
int currentPending = this.delayedEventsPending.incrementAndGet();
|
||||
if (currentPending > this.delayedEventsLimit) {
|
||||
record(Type.DELAYED_EVENTS_CAP, now,
|
||||
buildDelayedCapReason(reason),
|
||||
buildDelayedCapReason(reason, currentPending),
|
||||
sourceLabel,
|
||||
sourceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.delayedEventsPending++;
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized void completeDelayedEvent() {
|
||||
if (this.delayedEventsPending > 0) {
|
||||
this.delayedEventsPending--;
|
||||
}
|
||||
public void completeDelayedEvent() {
|
||||
this.delayedEventsPending.updateAndGet(v -> v > 0 ? v - 1 : 0);
|
||||
}
|
||||
|
||||
public synchronized void recordExecution(long elapsedMs, long now, String sourceLabel, int sourceId, String reason) {
|
||||
rollWindow(now);
|
||||
public void recordExecution(long elapsedMs, long now, String sourceLabel, int sourceId, String reason) {
|
||||
rollWindowIfNeeded(now);
|
||||
|
||||
int normalizedElapsed = (int) Math.max(0L, elapsedMs);
|
||||
|
||||
this.totalExecutionMsCurrentWindow += normalizedElapsed;
|
||||
this.executionSamplesCurrentWindow++;
|
||||
this.averageExecutionMs = (int) Math.round(this.totalExecutionMsCurrentWindow / (double) this.executionSamplesCurrentWindow);
|
||||
long total = this.totalExecutionMsCurrentWindow.addAndGet(normalizedElapsed);
|
||||
int samples = this.executionSamplesCurrentWindow.incrementAndGet();
|
||||
this.averageExecutionMs = (int) Math.round(total / (double) samples);
|
||||
|
||||
if (normalizedElapsed >= this.peakExecutionMs) {
|
||||
this.peakExecutionMs = normalizedElapsed;
|
||||
this.peakExecutionSourceLabel = sanitizeSourceLabel(sourceLabel);
|
||||
this.peakExecutionSourceId = Math.max(0, sourceId);
|
||||
this.peakExecutionReason = sanitizeReason(reason);
|
||||
synchronized (this) {
|
||||
if (normalizedElapsed >= this.peakExecutionMs) {
|
||||
this.peakExecutionMs = normalizedElapsed;
|
||||
this.peakExecutionSourceLabel = sanitizeSourceLabel(sourceLabel);
|
||||
this.peakExecutionSourceId = Math.max(0, sourceId);
|
||||
this.peakExecutionReason = sanitizeReason(reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void recordKilled(long now, String reason, String sourceLabel, int sourceId) {
|
||||
rollWindow(now);
|
||||
public void recordKilled(long now, String reason, String sourceLabel, int sourceId) {
|
||||
rollWindowIfNeeded(now);
|
||||
record(Type.KILLED, now, reason, sourceLabel, sourceId);
|
||||
}
|
||||
|
||||
public synchronized void recordRecursionTimeout(long now, String reason, String sourceLabel, int sourceId) {
|
||||
rollWindow(now);
|
||||
public void recordRecursionTimeout(long now, String reason, String sourceLabel, int sourceId) {
|
||||
rollWindowIfNeeded(now);
|
||||
record(Type.RECURSION_TIMEOUT, now, reason, sourceLabel, sourceId);
|
||||
}
|
||||
|
||||
@@ -394,7 +396,7 @@ public final class WiredRoomDiagnostics {
|
||||
}
|
||||
|
||||
public synchronized Snapshot snapshot(int recursionDepthCurrent, int recursionDepthLimit, long killedUntilMs, long now) {
|
||||
rollWindow(now);
|
||||
rollWindowIfNeeded(now);
|
||||
|
||||
List<LogEntry> logEntries = new ArrayList<>(Type.values().length);
|
||||
List<HistoryEntry> historyEntries = new ArrayList<>(this.history.size());
|
||||
@@ -422,10 +424,10 @@ public final class WiredRoomDiagnostics {
|
||||
}
|
||||
|
||||
return new Snapshot(
|
||||
this.usageCurrentWindow,
|
||||
this.usageCurrentWindow.get(),
|
||||
this.usageLimitPerWindow,
|
||||
this.heavy,
|
||||
this.delayedEventsPending,
|
||||
this.delayedEventsPending.get(),
|
||||
this.delayedEventsLimit,
|
||||
this.averageExecutionMs,
|
||||
this.peakExecutionMs,
|
||||
@@ -444,30 +446,40 @@ public final class WiredRoomDiagnostics {
|
||||
);
|
||||
}
|
||||
|
||||
private void rollWindow(long now) {
|
||||
if (this.windowStartedAt <= 0L) {
|
||||
this.windowStartedAt = now;
|
||||
private void rollWindowIfNeeded(long now) {
|
||||
long startedAt = this.windowStartedAt.get();
|
||||
if (startedAt <= 0L) {
|
||||
this.windowStartedAt.compareAndSet(startedAt, now);
|
||||
return;
|
||||
}
|
||||
|
||||
while ((now - this.windowStartedAt) >= this.usageWindowMs) {
|
||||
evaluateWindow(this.windowStartedAt + this.usageWindowMs);
|
||||
this.windowStartedAt += this.usageWindowMs;
|
||||
this.usageCurrentWindow = 0;
|
||||
this.totalExecutionMsCurrentWindow = 0L;
|
||||
this.executionSamplesCurrentWindow = 0;
|
||||
this.averageExecutionMs = 0;
|
||||
this.peakExecutionMs = 0;
|
||||
this.peakExecutionSourceLabel = null;
|
||||
this.peakExecutionSourceId = 0;
|
||||
this.peakExecutionReason = null;
|
||||
if ((now - startedAt) >= this.usageWindowMs) {
|
||||
synchronized (this) {
|
||||
startedAt = this.windowStartedAt.get();
|
||||
if ((now - startedAt) >= this.usageWindowMs) {
|
||||
while ((now - startedAt) >= this.usageWindowMs) {
|
||||
evaluateWindow(startedAt + this.usageWindowMs);
|
||||
startedAt += this.usageWindowMs;
|
||||
|
||||
this.usageCurrentWindow.set(0);
|
||||
this.totalExecutionMsCurrentWindow.set(0L);
|
||||
this.executionSamplesCurrentWindow.set(0);
|
||||
this.averageExecutionMs = 0;
|
||||
this.peakExecutionMs = 0;
|
||||
this.peakExecutionSourceLabel = null;
|
||||
this.peakExecutionSourceId = 0;
|
||||
this.peakExecutionReason = null;
|
||||
}
|
||||
this.windowStartedAt.set(startedAt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void evaluateWindow(long now) {
|
||||
int usagePercent = (int) Math.round((this.usageCurrentWindow * 100D) / this.usageLimitPerWindow);
|
||||
int delayedPercent = (int) Math.round((this.delayedEventsPending * 100D) / this.delayedEventsLimit);
|
||||
boolean overloadWindow = (this.executionSamplesCurrentWindow > 0)
|
||||
int usagePercent = (int) Math.round((this.usageCurrentWindow.get() * 100D) / this.usageLimitPerWindow);
|
||||
int delayedPercent = (int) Math.round((this.delayedEventsPending.get() * 100D) / this.delayedEventsLimit);
|
||||
boolean overloadWindow = (this.executionSamplesCurrentWindow.get() > 0)
|
||||
&& ((this.averageExecutionMs >= this.overloadAverageThresholdMs) || (this.peakExecutionMs >= this.overloadPeakThresholdMs));
|
||||
boolean heavyWindow = (usagePercent >= this.heavyUsageThresholdPercent)
|
||||
|| (delayedPercent >= this.heavyDelayedThresholdPercent)
|
||||
@@ -516,22 +528,22 @@ public final class WiredRoomDiagnostics {
|
||||
}
|
||||
}
|
||||
|
||||
private String buildExecutionCapReason(int normalizedCost, String reason) {
|
||||
private String buildExecutionCapReason(int normalizedCost, String reason, int currentUsage) {
|
||||
return joinReason(
|
||||
reason,
|
||||
String.format("Estimated stack cost %d would exceed usage budget %d/%d in %dms window",
|
||||
normalizedCost,
|
||||
this.usageCurrentWindow,
|
||||
currentUsage,
|
||||
this.usageLimitPerWindow,
|
||||
this.usageWindowMs)
|
||||
);
|
||||
}
|
||||
|
||||
private String buildDelayedCapReason(String reason) {
|
||||
private String buildDelayedCapReason(String reason, int currentPending) {
|
||||
return joinReason(
|
||||
reason,
|
||||
String.format("Pending delayed events would exceed queue %d/%d",
|
||||
this.delayedEventsPending,
|
||||
currentPending,
|
||||
this.delayedEventsLimit)
|
||||
);
|
||||
}
|
||||
@@ -544,7 +556,7 @@ public final class WiredRoomDiagnostics {
|
||||
this.overloadAverageThresholdMs,
|
||||
this.peakExecutionMs,
|
||||
this.overloadPeakThresholdMs,
|
||||
this.executionSamplesCurrentWindow,
|
||||
this.executionSamplesCurrentWindow.get(),
|
||||
this.usageWindowMs)
|
||||
);
|
||||
}
|
||||
|
||||
+77
-3
@@ -33,6 +33,8 @@ import java.util.Locale;
|
||||
|
||||
public final class WiredTextPlaceholderUtil {
|
||||
private static final char PRESERVED_SPACE = '\u00A0';
|
||||
private static final int MAX_PLACEHOLDER_EXPANSION_LENGTH = 16384;
|
||||
private static final int MAX_PLACEHOLDER_REPLACEMENTS = 512;
|
||||
|
||||
private WiredTextPlaceholderUtil() {
|
||||
}
|
||||
@@ -56,13 +58,20 @@ public final class WiredTextPlaceholderUtil {
|
||||
|
||||
String resolvedText = text;
|
||||
|
||||
int replacementCount = 0;
|
||||
|
||||
for (InteractionWiredExtra extra : WiredExecutionOrderUtil.sort(extras)) {
|
||||
if (extra instanceof WiredExtraTextOutputUsername) {
|
||||
WiredExtraTextOutputUsername usernameExtra = (WiredExtraTextOutputUsername) extra;
|
||||
String placeholderToken = usernameExtra.getPlaceholderToken();
|
||||
|
||||
if (!placeholderToken.isEmpty() && resolvedText.contains(placeholderToken)) {
|
||||
resolvedText = resolvedText.replace(placeholderToken, buildUsernameReplacement(ctx, usernameExtra));
|
||||
resolvedText = replaceWithBudget(resolvedText, placeholderToken, buildUsernameReplacement(ctx, usernameExtra));
|
||||
replacementCount++;
|
||||
}
|
||||
|
||||
if (shouldStopPlaceholderExpansion(resolvedText, replacementCount)) {
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -73,7 +82,12 @@ public final class WiredTextPlaceholderUtil {
|
||||
String placeholderToken = furniExtra.getPlaceholderToken();
|
||||
|
||||
if (!placeholderToken.isEmpty() && resolvedText.contains(placeholderToken)) {
|
||||
resolvedText = resolvedText.replace(placeholderToken, buildFurniNameReplacement(ctx, furniExtra));
|
||||
resolvedText = replaceWithBudget(resolvedText, placeholderToken, buildFurniNameReplacement(ctx, furniExtra));
|
||||
replacementCount++;
|
||||
}
|
||||
|
||||
if (shouldStopPlaceholderExpansion(resolvedText, replacementCount)) {
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -84,7 +98,12 @@ public final class WiredTextPlaceholderUtil {
|
||||
String placeholderToken = variableExtra.getPlaceholderToken();
|
||||
|
||||
if (!placeholderToken.isEmpty() && resolvedText.contains(placeholderToken)) {
|
||||
resolvedText = resolvedText.replace(placeholderToken, buildVariableReplacement(ctx, variableExtra));
|
||||
resolvedText = replaceWithBudget(resolvedText, placeholderToken, buildVariableReplacement(ctx, variableExtra));
|
||||
replacementCount++;
|
||||
}
|
||||
|
||||
if (shouldStopPlaceholderExpansion(resolvedText, replacementCount)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,6 +111,61 @@ public final class WiredTextPlaceholderUtil {
|
||||
return preserveRepeatedSpaces(resolvedText);
|
||||
}
|
||||
|
||||
private static boolean shouldStopPlaceholderExpansion(String resolvedText, int replacementCount) {
|
||||
return replacementCount >= MAX_PLACEHOLDER_REPLACEMENTS
|
||||
|| (resolvedText != null && resolvedText.length() >= MAX_PLACEHOLDER_EXPANSION_LENGTH);
|
||||
}
|
||||
|
||||
private static String replaceWithBudget(String input, String placeholderToken, String replacement) {
|
||||
if (input == null || input.isEmpty() || placeholderToken == null || placeholderToken.isEmpty()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
if (replacement == null) {
|
||||
replacement = "";
|
||||
}
|
||||
|
||||
int matchIndex = input.indexOf(placeholderToken);
|
||||
if (matchIndex < 0) {
|
||||
return input;
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder(Math.min(MAX_PLACEHOLDER_EXPANSION_LENGTH, input.length()));
|
||||
int searchIndex = 0;
|
||||
|
||||
while (matchIndex >= 0) {
|
||||
builder.append(input, searchIndex, matchIndex);
|
||||
|
||||
int remainingCapacity = MAX_PLACEHOLDER_EXPANSION_LENGTH - builder.length();
|
||||
if (remainingCapacity <= 0) {
|
||||
return builder.substring(0, MAX_PLACEHOLDER_EXPANSION_LENGTH);
|
||||
}
|
||||
|
||||
if (replacement.length() <= remainingCapacity) {
|
||||
builder.append(replacement);
|
||||
} else {
|
||||
builder.append(replacement, 0, remainingCapacity);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
searchIndex = matchIndex + placeholderToken.length();
|
||||
if (builder.length() >= MAX_PLACEHOLDER_EXPANSION_LENGTH) {
|
||||
return builder.substring(0, MAX_PLACEHOLDER_EXPANSION_LENGTH);
|
||||
}
|
||||
|
||||
matchIndex = input.indexOf(placeholderToken, searchIndex);
|
||||
}
|
||||
|
||||
int remainingCapacity = MAX_PLACEHOLDER_EXPANSION_LENGTH - builder.length();
|
||||
if (remainingCapacity <= 0) {
|
||||
return builder.substring(0, MAX_PLACEHOLDER_EXPANSION_LENGTH);
|
||||
}
|
||||
|
||||
int tailLength = Math.min(input.length() - searchIndex, remainingCapacity);
|
||||
builder.append(input, searchIndex, searchIndex + tailLength);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String preserveRepeatedSpaces(String text) {
|
||||
if (text == null || text.length() < 2) {
|
||||
return text;
|
||||
|
||||
@@ -587,4 +587,10 @@ public final class WiredUserMovementHelper {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void cleanupRoomUnit(RoomUnit roomUnit) {
|
||||
if (roomUnit != null) {
|
||||
SUPPRESSED_STATUS_COMPOSER_UNTIL.remove(roomUnit.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-25
@@ -16,6 +16,7 @@ import java.time.LocalTime;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -23,7 +24,7 @@ import java.util.stream.Stream;
|
||||
public class WiredHighscoreManager {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(WiredHighscoreManager.class);
|
||||
|
||||
private final HashMap<Integer, List<WiredHighscoreDataEntry>> data = new HashMap<>();
|
||||
private final ConcurrentHashMap<Integer, List<WiredHighscoreDataEntry>> data = new ConcurrentHashMap<>();
|
||||
|
||||
private final static String locale = (System.getProperty("user.language") != null ? System.getProperty("user.language") : "en");
|
||||
private final static String country = (System.getProperty("user.country") != null ? System.getProperty("user.country") : "US");
|
||||
@@ -60,15 +61,12 @@ public class WiredHighscoreManager {
|
||||
|
||||
private void loadHighscoreData() {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM items_highscore_data")) {
|
||||
statement.setFetchSize(1000);
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
while (set.next()) {
|
||||
WiredHighscoreDataEntry entry = new WiredHighscoreDataEntry(set);
|
||||
|
||||
if (!this.data.containsKey(entry.getItemId())) {
|
||||
this.data.put(entry.getItemId(), new ArrayList<>());
|
||||
}
|
||||
|
||||
this.data.get(entry.getItemId()).add(entry);
|
||||
this.data.computeIfAbsent(entry.getItemId(), k -> Collections.synchronizedList(new ArrayList<>())).add(entry);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
@@ -77,33 +75,39 @@ public class WiredHighscoreManager {
|
||||
}
|
||||
|
||||
public void addHighscoreData(WiredHighscoreDataEntry entry) {
|
||||
if (!this.data.containsKey(entry.getItemId())) {
|
||||
this.data.put(entry.getItemId(), new ArrayList<>());
|
||||
}
|
||||
this.data.computeIfAbsent(entry.getItemId(), k -> Collections.synchronizedList(new ArrayList<>())).add(entry);
|
||||
|
||||
this.data.get(entry.getItemId()).add(entry);
|
||||
Emulator.getThreading().run(() -> {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO `items_highscore_data` (`item_id`, `user_ids`, `score`, `is_win`, `timestamp`) VALUES (?, ?, ?, ?, ?)")) {
|
||||
statement.setInt(1, entry.getItemId());
|
||||
statement.setString(2, String.join(",", entry.getUserIds().stream().map(Object::toString).collect(Collectors.toList())));
|
||||
statement.setInt(3, entry.getScore());
|
||||
statement.setInt(4, entry.isWin() ? 1 : 0);
|
||||
statement.setInt(5, entry.getTimestamp());
|
||||
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO `items_highscore_data` (`item_id`, `user_ids`, `score`, `is_win`, `timestamp`) VALUES (?, ?, ?, ?, ?)")) {
|
||||
statement.setInt(1, entry.getItemId());
|
||||
statement.setString(2, String.join(",", entry.getUserIds().stream().map(Object::toString).collect(Collectors.toList())));
|
||||
statement.setInt(3, entry.getScore());
|
||||
statement.setInt(4, entry.isWin() ? 1 : 0);
|
||||
statement.setInt(5, entry.getTimestamp());
|
||||
|
||||
statement.execute();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Caught SQL exception", e);
|
||||
}
|
||||
statement.execute();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Caught SQL exception", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public List<WiredHighscoreRow> getHighscoreRowsForItem(int itemId, WiredHighscoreClearType clearType, WiredHighscoreScoreType scoreType) {
|
||||
if (!this.data.containsKey(itemId)) return null;
|
||||
|
||||
Stream<WiredHighscoreRow> highscores = new ArrayList<>(this.data.get(itemId)).stream()
|
||||
List<WiredHighscoreDataEntry> list = this.data.get(itemId);
|
||||
if (list == null) return null;
|
||||
|
||||
List<WiredHighscoreDataEntry> copy;
|
||||
synchronized (list) {
|
||||
copy = new ArrayList<>(list);
|
||||
}
|
||||
|
||||
Stream<WiredHighscoreRow> highscores = copy.stream()
|
||||
.filter(entry -> this.timeMatchesEntry(entry, clearType) && (scoreType != WiredHighscoreScoreType.MOSTWIN || entry.isWin()))
|
||||
.map(entry -> new WiredHighscoreRow(
|
||||
entry.getUserIds().stream()
|
||||
.map(id -> Emulator.getGameEnvironment().getHabboManager().getHabboInfo(id).getUsername())
|
||||
.map(id -> Emulator.getGameEnvironment().getHabboManager().getCachedUsername(id))
|
||||
.collect(Collectors.toList()),
|
||||
entry.getScore()
|
||||
));
|
||||
@@ -167,7 +171,7 @@ public class WiredHighscoreManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
public HashMap<Integer, List<WiredHighscoreDataEntry>> getData() {
|
||||
public Map<Integer, List<WiredHighscoreDataEntry>> getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
@@ -176,7 +180,7 @@ public class WiredHighscoreManager {
|
||||
}
|
||||
|
||||
public void setEntriesForItemId(int itemId, List<WiredHighscoreDataEntry> entries) {
|
||||
this.data.put(itemId, entries);
|
||||
this.data.put(itemId, Collections.synchronizedList(entries));
|
||||
}
|
||||
|
||||
private long getTodayStartTimestamp() {
|
||||
|
||||
@@ -60,11 +60,15 @@ public final class WiredTickService {
|
||||
/** Whether a shard worker loop is currently scheduled/running. */
|
||||
private AtomicBoolean[] shardScheduled;
|
||||
|
||||
private final ConcurrentHashMap<Integer, Set<WiredTickable>> roomTickables;
|
||||
private final ConcurrentHashMap<Integer, Set<WiredTickable>>[] shardRoomTickables;
|
||||
private final AtomicBoolean running;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private WiredTickService() {
|
||||
this.roomTickables = new ConcurrentHashMap<>();
|
||||
this.shardRoomTickables = new ConcurrentHashMap[MAX_WORKER_COUNT];
|
||||
for (int i = 0; i < MAX_WORKER_COUNT; i++) {
|
||||
this.shardRoomTickables[i] = new ConcurrentHashMap<>();
|
||||
}
|
||||
this.running = new AtomicBoolean(false);
|
||||
}
|
||||
|
||||
@@ -232,7 +236,9 @@ public final class WiredTickService {
|
||||
shardProcessedTicks = null;
|
||||
shardScheduled = null;
|
||||
|
||||
roomTickables.clear();
|
||||
for (int i = 0; i < MAX_WORKER_COUNT; i++) {
|
||||
shardRoomTickables[i].clear();
|
||||
}
|
||||
LOGGER.info("WiredTickService stopped");
|
||||
}
|
||||
|
||||
@@ -246,7 +252,8 @@ public final class WiredTickService {
|
||||
}
|
||||
|
||||
int roomId = room.getId();
|
||||
Set<WiredTickable> tickables = roomTickables.computeIfAbsent(roomId, k -> ConcurrentHashMap.newKeySet());
|
||||
int shardIndex = getShardIndex(roomId);
|
||||
Set<WiredTickable> tickables = shardRoomTickables[shardIndex].computeIfAbsent(roomId, k -> ConcurrentHashMap.newKeySet());
|
||||
|
||||
if (tickables.add(tickable)) {
|
||||
tickable.onRegistered(room, System.currentTimeMillis());
|
||||
@@ -259,7 +266,8 @@ public final class WiredTickService {
|
||||
}
|
||||
|
||||
int roomId = room.getId();
|
||||
Set<WiredTickable> tickables = roomTickables.get(roomId);
|
||||
int shardIndex = getShardIndex(roomId);
|
||||
Set<WiredTickable> tickables = shardRoomTickables[shardIndex].get(roomId);
|
||||
|
||||
if (tickables != null) {
|
||||
if (tickables.remove(tickable)) {
|
||||
@@ -267,13 +275,14 @@ public final class WiredTickService {
|
||||
}
|
||||
|
||||
if (tickables.isEmpty()) {
|
||||
roomTickables.remove(roomId);
|
||||
shardRoomTickables[shardIndex].remove(roomId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void unregister(int roomId, int tickableId) {
|
||||
Set<WiredTickable> tickables = roomTickables.get(roomId);
|
||||
int shardIndex = getShardIndex(roomId);
|
||||
Set<WiredTickable> tickables = shardRoomTickables[shardIndex].get(roomId);
|
||||
|
||||
if (tickables != null) {
|
||||
tickables.removeIf(t -> {
|
||||
@@ -288,7 +297,7 @@ public final class WiredTickService {
|
||||
});
|
||||
|
||||
if (tickables.isEmpty()) {
|
||||
roomTickables.remove(roomId);
|
||||
shardRoomTickables[shardIndex].remove(roomId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,11 +307,12 @@ public final class WiredTickService {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<WiredTickable> tickables = roomTickables.remove(room.getId());
|
||||
int roomId = room.getId();
|
||||
int shardIndex = getShardIndex(roomId);
|
||||
Set<WiredTickable> tickables = shardRoomTickables[shardIndex].remove(roomId);
|
||||
|
||||
if (tickables != null) {
|
||||
WiredTickable[] snapshot = tickables.toArray(new WiredTickable[0]);
|
||||
for (WiredTickable tickable : snapshot) {
|
||||
for (WiredTickable tickable : tickables) {
|
||||
try {
|
||||
if (tickable != null) {
|
||||
tickable.onUnregistered(room);
|
||||
@@ -316,7 +326,7 @@ public final class WiredTickService {
|
||||
);
|
||||
}
|
||||
}
|
||||
LOGGER.debug("Unregistered {} tickables from room {}", snapshot.length, room.getId());
|
||||
LOGGER.debug("Unregistered {} tickables from room {}", tickables.size(), room.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,11 +335,12 @@ public final class WiredTickService {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<WiredTickable> tickables = roomTickables.get(room.getId());
|
||||
int roomId = room.getId();
|
||||
int shardIndex = getShardIndex(roomId);
|
||||
Set<WiredTickable> tickables = shardRoomTickables[shardIndex].get(roomId);
|
||||
|
||||
if (tickables != null) {
|
||||
WiredTickable[] snapshot = tickables.toArray(new WiredTickable[0]);
|
||||
for (WiredTickable tickable : snapshot) {
|
||||
for (WiredTickable tickable : tickables) {
|
||||
try {
|
||||
if (tickable != null) {
|
||||
tickable.resetTimer();
|
||||
@@ -347,16 +358,25 @@ public final class WiredTickService {
|
||||
}
|
||||
|
||||
public int getTickableCount(int roomId) {
|
||||
Set<WiredTickable> tickables = roomTickables.get(roomId);
|
||||
int shardIndex = getShardIndex(roomId);
|
||||
Set<WiredTickable> tickables = shardRoomTickables[shardIndex].get(roomId);
|
||||
return tickables != null ? tickables.size() : 0;
|
||||
}
|
||||
|
||||
public int getTotalTickableCount() {
|
||||
return roomTickables.values().stream().mapToInt(Set::size).sum();
|
||||
int count = 0;
|
||||
for (int i = 0; i < MAX_WORKER_COUNT; i++) {
|
||||
count += shardRoomTickables[i].values().stream().mapToInt(Set::size).sum();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public int getActiveRoomCount() {
|
||||
return roomTickables.size();
|
||||
int count = 0;
|
||||
for (int i = 0; i < MAX_WORKER_COUNT; i++) {
|
||||
count += shardRoomTickables[i].size();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public long getTickCount() {
|
||||
@@ -396,6 +416,12 @@ public final class WiredTickService {
|
||||
break;
|
||||
}
|
||||
|
||||
// If lagging by more than 5 ticks (250ms), skip intermediate ticks to avoid CPU starvation
|
||||
if (requestedTick - nextTick > 5) {
|
||||
nextTick = requestedTick - 5;
|
||||
shardProcessedTicks[shardIndex].set(nextTick);
|
||||
}
|
||||
|
||||
processShardTick(shardIndex, nextTick);
|
||||
shardProcessedTicks[shardIndex].set(nextTick);
|
||||
}
|
||||
@@ -414,12 +440,8 @@ public final class WiredTickService {
|
||||
int processedTickables = 0;
|
||||
int processedRooms = 0;
|
||||
|
||||
for (Map.Entry<Integer, Set<WiredTickable>> entry : roomTickables.entrySet()) {
|
||||
for (Map.Entry<Integer, Set<WiredTickable>> entry : shardRoomTickables[shardIndex].entrySet()) {
|
||||
int roomId = entry.getKey();
|
||||
if (getShardIndex(roomId) != shardIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Set<WiredTickable> tickables = entry.getValue();
|
||||
if (tickables == null || tickables.isEmpty()) {
|
||||
continue;
|
||||
@@ -435,14 +457,9 @@ public final class WiredTickService {
|
||||
}
|
||||
|
||||
long roomStart = System.currentTimeMillis();
|
||||
WiredTickable[] snapshot = tickables.toArray(new WiredTickable[0]);
|
||||
if (snapshot.length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
processedRooms++;
|
||||
|
||||
for (WiredTickable tickable : snapshot) {
|
||||
for (WiredTickable tickable : tickables) {
|
||||
long tickableStart = System.currentTimeMillis();
|
||||
|
||||
if (tickable == null) {
|
||||
@@ -489,7 +506,7 @@ public final class WiredTickService {
|
||||
shardIndex,
|
||||
roomId,
|
||||
currentTick,
|
||||
snapshot.length,
|
||||
tickables.size(),
|
||||
roomDuration
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.eu.habbo.messages;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.gameclients.GameClient;
|
||||
import com.eu.habbo.monitoring.EmulatorNetworkStats;
|
||||
import com.eu.habbo.messages.incoming.Incoming;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.incoming.achievements.RequestAchievementConfigurationEvent;
|
||||
@@ -180,6 +181,8 @@ public class PacketManager {
|
||||
return;
|
||||
|
||||
try {
|
||||
EmulatorNetworkStats.recordIncoming(packet.bytesAvailable() + 6);
|
||||
|
||||
if (this.isRegistered(packet.getMessageId())) {
|
||||
Class<? extends MessageHandler> handlerClass = this.incoming.get(packet.getMessageId());
|
||||
|
||||
|
||||
+1
-2
@@ -27,9 +27,8 @@ public class RoomUserSignEvent extends MessageHandler {
|
||||
WiredManager.triggerUserPerformsAction(room, this.client.getHabbo().getRoomUnit(), WiredUserActionType.SIGN, event.sign);
|
||||
|
||||
if(signId <= 10) {
|
||||
|
||||
int userId = this.client.getHabbo().getHabboInfo().getId();
|
||||
for (HabboItem item : room.getFloorItems()) {
|
||||
for (HabboItem item : room.getRoomSpecialTypes().getItemsOfType(InteractionVoteCounter.class)) {
|
||||
if (item instanceof InteractionVoteCounter) {
|
||||
((InteractionVoteCounter)item).vote(room, userId, signId);
|
||||
}
|
||||
|
||||
@@ -125,10 +125,28 @@ public class UserProfileComposer extends MessageComposer {
|
||||
this.response.appendString(customizationData.prefixEffect);
|
||||
this.response.appendString(customizationData.prefixFont);
|
||||
this.response.appendString(customizationData.displayOrder);
|
||||
this.response.appendInt(this.getTotalBadges());
|
||||
|
||||
return this.response;
|
||||
}
|
||||
|
||||
private int getTotalBadges() {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("SELECT COUNT(DISTINCT badge_code) AS total_badges FROM users_badges WHERE user_id = ?")) {
|
||||
statement.setInt(1, this.habboInfo.getId());
|
||||
|
||||
try (ResultSet set = statement.executeQuery()) {
|
||||
if (set.next()) {
|
||||
return set.getInt("total_badges");
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Caught SQL exception while loading total badges for extended profile", e);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public HabboInfo getHabboInfo() {
|
||||
return habboInfo;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.eu.habbo.monitoring;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public final class EmulatorNetworkStats {
|
||||
private static final AtomicLong INCOMING_PACKETS = new AtomicLong();
|
||||
private static final AtomicLong OUTGOING_PACKETS = new AtomicLong();
|
||||
private static final AtomicLong INCOMING_BYTES = new AtomicLong();
|
||||
private static final AtomicLong OUTGOING_BYTES = new AtomicLong();
|
||||
|
||||
private EmulatorNetworkStats() {
|
||||
}
|
||||
|
||||
public static void recordIncoming(int byteCount) {
|
||||
INCOMING_PACKETS.incrementAndGet();
|
||||
if (byteCount > 0) {
|
||||
INCOMING_BYTES.addAndGet(byteCount);
|
||||
}
|
||||
}
|
||||
|
||||
public static void recordOutgoing(int byteCount) {
|
||||
OUTGOING_PACKETS.incrementAndGet();
|
||||
if (byteCount > 0) {
|
||||
OUTGOING_BYTES.addAndGet(byteCount);
|
||||
}
|
||||
}
|
||||
|
||||
public static long getIncomingPackets() {
|
||||
return INCOMING_PACKETS.get();
|
||||
}
|
||||
|
||||
public static long getOutgoingPackets() {
|
||||
return OUTGOING_PACKETS.get();
|
||||
}
|
||||
|
||||
public static long getIncomingBytes() {
|
||||
return INCOMING_BYTES.get();
|
||||
}
|
||||
|
||||
public static long getOutgoingBytes() {
|
||||
return OUTGOING_BYTES.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,627 @@
|
||||
package com.eu.habbo.monitoring;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredManager;
|
||||
import com.eu.habbo.habbohotel.wired.core.WiredRoomDiagnostics;
|
||||
import com.eu.habbo.habbohotel.wired.tick.WiredTickService;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import com.zaxxer.hikari.HikariPoolMXBean;
|
||||
|
||||
import java.lang.management.GarbageCollectorMXBean;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.OperatingSystemMXBean;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
|
||||
public final class EmulatorStatsService {
|
||||
private static final long CACHE_TTL_MS = 1_000L;
|
||||
private static final int MAX_HISTORY_POINTS = 90;
|
||||
|
||||
private static final ArrayDeque<MemoryPoint> MEMORY_HISTORY = new ArrayDeque<>();
|
||||
|
||||
private static volatile Snapshot cachedSnapshot = null;
|
||||
private static volatile long cachedAt = 0L;
|
||||
private static volatile int peakPlayers = 0;
|
||||
private static volatile int peakWebSocketSessions = 0;
|
||||
private static volatile long previousIncomingPackets = 0L;
|
||||
private static volatile long previousOutgoingPackets = 0L;
|
||||
private static volatile long previousIncomingBytes = 0L;
|
||||
private static volatile long previousOutgoingBytes = 0L;
|
||||
private static volatile long previousGcCount = 0L;
|
||||
private static volatile long previousGcTimeMs = 0L;
|
||||
private static volatile long previousTelemetryAt = 0L;
|
||||
|
||||
private EmulatorStatsService() {
|
||||
}
|
||||
|
||||
public static Snapshot collectSnapshot() {
|
||||
long now = System.currentTimeMillis();
|
||||
Snapshot current = cachedSnapshot;
|
||||
|
||||
if (current != null && (now - cachedAt) < CACHE_TTL_MS) {
|
||||
return current;
|
||||
}
|
||||
|
||||
synchronized (EmulatorStatsService.class) {
|
||||
current = cachedSnapshot;
|
||||
|
||||
if (current != null && (now - cachedAt) < CACHE_TTL_MS) {
|
||||
return current;
|
||||
}
|
||||
|
||||
Snapshot built = buildSnapshot(now);
|
||||
cachedSnapshot = built;
|
||||
cachedAt = now;
|
||||
return built;
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatDuration(long totalSeconds) {
|
||||
long hours = totalSeconds / 3600L;
|
||||
long minutes = (totalSeconds % 3600L) / 60L;
|
||||
long seconds = totalSeconds % 60L;
|
||||
|
||||
return String.format("%02dh %02dm %02ds", hours, minutes, seconds);
|
||||
}
|
||||
|
||||
private static Snapshot buildSnapshot(long now) {
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
|
||||
long totalMemBytes = runtime.totalMemory();
|
||||
long freeMemBytes = runtime.freeMemory();
|
||||
long usedMemBytes = totalMemBytes - freeMemBytes;
|
||||
long maxMemBytes = runtime.maxMemory();
|
||||
|
||||
int usedMemMb = (int) (usedMemBytes / 1024L / 1024L);
|
||||
int maxMemMb = (int) (maxMemBytes / 1024L / 1024L);
|
||||
int estimatedAllocMb = (int) (totalMemBytes / 1024L / 1024L);
|
||||
double memoryUsagePercent = maxMemBytes > 0
|
||||
? (usedMemBytes * 100D) / maxMemBytes
|
||||
: 0D;
|
||||
|
||||
OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
|
||||
double cpuLoadPercent = 0D;
|
||||
|
||||
if (osBean instanceof com.sun.management.OperatingSystemMXBean managedOsBean) {
|
||||
cpuLoadPercent = Math.max(0D, managedOsBean.getProcessCpuLoad() * 100D);
|
||||
}
|
||||
|
||||
int threadCount = ManagementFactory.getThreadMXBean().getThreadCount();
|
||||
|
||||
List<Habbo> habbos = List.of();
|
||||
List<Room> rooms = List.of();
|
||||
int webSocketSessions = 0;
|
||||
|
||||
if (Emulator.getGameEnvironment() != null) {
|
||||
if (Emulator.getGameEnvironment().getHabboManager() != null) {
|
||||
habbos = Emulator.getGameEnvironment().getHabboManager().getOnlineHabbos().values().stream().toList();
|
||||
}
|
||||
|
||||
if (Emulator.getGameEnvironment().getRoomManager() != null) {
|
||||
rooms = Emulator.getGameEnvironment().getRoomManager().getActiveRooms();
|
||||
}
|
||||
}
|
||||
|
||||
if (Emulator.getGameServer() != null && Emulator.getGameServer().getGameClientManager() != null) {
|
||||
webSocketSessions = Emulator.getGameServer().getGameClientManager().getSessions().size();
|
||||
}
|
||||
|
||||
peakPlayers = Math.max(peakPlayers, habbos.size());
|
||||
peakWebSocketSessions = Math.max(peakWebSocketSessions, webSocketSessions);
|
||||
|
||||
WiredTickService wiredTickService = WiredTickService.getInstance();
|
||||
int totalTickables = (wiredTickService != null) ? wiredTickService.getTotalTickableCount() : 0;
|
||||
|
||||
appendMemoryHistory(now, usedMemMb, maxMemMb, memoryUsagePercent);
|
||||
|
||||
double averageRoomCycleMs = 0D;
|
||||
double worstRoomCycleMs = 0D;
|
||||
int worstRoomCycleRoomId = 0;
|
||||
String worstRoomCycleRoomName = "-";
|
||||
|
||||
long totalDelayedEventsPending = 0L;
|
||||
int overloadedWiredRooms = 0;
|
||||
int heavyWiredRooms = 0;
|
||||
double wiredActivityPerSecond = 0D;
|
||||
|
||||
List<OnlineUserRow> users = new ArrayList<>(habbos.size());
|
||||
for (Habbo habbo : habbos) {
|
||||
int roomId = (habbo.getHabboInfo().getCurrentRoom() != null) ? habbo.getHabboInfo().getCurrentRoom().getId() : 0;
|
||||
|
||||
users.add(new OnlineUserRow(
|
||||
habbo.getHabboInfo().getId(),
|
||||
habbo.getHabboInfo().getUsername(),
|
||||
habbo.getHabboInfo().getRank().getName(),
|
||||
habbo.getHabboInfo().getCurrencyAmount(0),
|
||||
roomId
|
||||
));
|
||||
}
|
||||
|
||||
List<ActiveRoomRow> activeRooms = new ArrayList<>(rooms.size());
|
||||
List<WiredRoomRow> wiredRooms = new ArrayList<>();
|
||||
List<WiredTopRoomRow> wiredTopRooms = new ArrayList<>();
|
||||
|
||||
double roomCycleAccumulator = 0D;
|
||||
int roomCycleSamples = 0;
|
||||
|
||||
for (Room room : rooms) {
|
||||
int tickables = (wiredTickService != null) ? wiredTickService.getTickableCount(room.getId()) : 0;
|
||||
double roomCycleMs = Math.max(0D, room.lastCycleCpuMs);
|
||||
|
||||
roomCycleAccumulator += roomCycleMs;
|
||||
roomCycleSamples++;
|
||||
|
||||
if (roomCycleMs >= worstRoomCycleMs) {
|
||||
worstRoomCycleMs = roomCycleMs;
|
||||
worstRoomCycleRoomId = room.getId();
|
||||
worstRoomCycleRoomName = room.getName();
|
||||
}
|
||||
|
||||
activeRooms.add(new ActiveRoomRow(
|
||||
room.getId(),
|
||||
room.getName(),
|
||||
room.getUserCount(),
|
||||
room.itemCount(),
|
||||
tickables,
|
||||
room.lastCycleCpuMs,
|
||||
room.getEstimatedMemoryUsage() / 1024L,
|
||||
room.lastCycleThread
|
||||
));
|
||||
|
||||
WiredRoomDiagnostics.Snapshot diagnostics = WiredManager.getDiagnosticsSnapshot(room.getId());
|
||||
|
||||
if (diagnostics == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean shouldShow = diagnostics.getAverageExecutionMs() > 0
|
||||
|| diagnostics.getPeakExecutionMs() > 0
|
||||
|| tickables > 0
|
||||
|| diagnostics.getDelayedEventsPending() > 0;
|
||||
|
||||
if (!shouldShow) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int usagePercent = (int) Math.round((diagnostics.getUsageCurrentWindow() * 100D) / Math.max(1, diagnostics.getUsageLimitPerWindow()));
|
||||
double roomActivityPerSecond = (diagnostics.getUsageCurrentWindow() * 1000D) / Math.max(1, diagnostics.getUsageWindowMs());
|
||||
|
||||
totalDelayedEventsPending += diagnostics.getDelayedEventsPending();
|
||||
wiredActivityPerSecond += roomActivityPerSecond;
|
||||
|
||||
if (diagnostics.getAverageExecutionMs() >= diagnostics.getOverloadAverageThresholdMs()) {
|
||||
overloadedWiredRooms++;
|
||||
}
|
||||
|
||||
if (diagnostics.isHeavy()) {
|
||||
heavyWiredRooms++;
|
||||
}
|
||||
|
||||
wiredRooms.add(new WiredRoomRow(
|
||||
room.getId(),
|
||||
diagnostics.getAverageExecutionMs(),
|
||||
diagnostics.getPeakExecutionMs(),
|
||||
usagePercent,
|
||||
diagnostics.getDelayedEventsPending(),
|
||||
diagnostics.getAverageExecutionMs() >= diagnostics.getOverloadAverageThresholdMs(),
|
||||
diagnostics.isHeavy()
|
||||
));
|
||||
|
||||
wiredTopRooms.add(new WiredTopRoomRow(
|
||||
room.getId(),
|
||||
room.getName(),
|
||||
usagePercent,
|
||||
diagnostics.getAverageExecutionMs(),
|
||||
diagnostics.getPeakExecutionMs(),
|
||||
diagnostics.getDelayedEventsPending(),
|
||||
roomActivityPerSecond,
|
||||
diagnostics.isHeavy()
|
||||
));
|
||||
}
|
||||
|
||||
if (roomCycleSamples > 0) {
|
||||
averageRoomCycleMs = roomCycleAccumulator / roomCycleSamples;
|
||||
}
|
||||
|
||||
wiredTopRooms.sort(Comparator
|
||||
.comparingInt((WiredTopRoomRow row) -> row.usagePercent).reversed()
|
||||
.thenComparingInt(row -> row.averageTickMs).reversed()
|
||||
.thenComparingInt(row -> row.peakTickMs).reversed());
|
||||
|
||||
if (wiredTopRooms.size() > 5) {
|
||||
wiredTopRooms = new ArrayList<>(wiredTopRooms.subList(0, 5));
|
||||
}
|
||||
|
||||
HikariPoolMetrics hikariPoolMetrics = collectHikariPoolMetrics();
|
||||
SchedulerMetrics schedulerMetrics = collectSchedulerMetrics();
|
||||
NetworkMetrics networkMetrics = collectNetworkMetrics(now);
|
||||
GarbageCollectorMetrics garbageCollectorMetrics = collectGarbageCollectorMetrics(now);
|
||||
|
||||
Overview overview = new Overview(
|
||||
Emulator.getOnlineTime(),
|
||||
now,
|
||||
cpuLoadPercent >= 80D ? "Attention needed" : "Healthy",
|
||||
usedMemMb,
|
||||
maxMemMb,
|
||||
estimatedAllocMb,
|
||||
memoryUsagePercent,
|
||||
cpuLoadPercent,
|
||||
threadCount,
|
||||
habbos.size(),
|
||||
rooms.size(),
|
||||
totalTickables,
|
||||
peakPlayers,
|
||||
webSocketSessions,
|
||||
peakWebSocketSessions,
|
||||
averageRoomCycleMs,
|
||||
worstRoomCycleMs,
|
||||
worstRoomCycleRoomId,
|
||||
worstRoomCycleRoomName,
|
||||
totalDelayedEventsPending,
|
||||
overloadedWiredRooms,
|
||||
heavyWiredRooms,
|
||||
wiredActivityPerSecond
|
||||
);
|
||||
|
||||
return new Snapshot(
|
||||
overview,
|
||||
new ArrayList<>(MEMORY_HISTORY),
|
||||
users,
|
||||
activeRooms,
|
||||
wiredRooms,
|
||||
wiredTopRooms,
|
||||
hikariPoolMetrics,
|
||||
schedulerMetrics,
|
||||
networkMetrics,
|
||||
garbageCollectorMetrics
|
||||
);
|
||||
}
|
||||
|
||||
private static HikariPoolMetrics collectHikariPoolMetrics() {
|
||||
HikariDataSource dataSource = (Emulator.getDatabase() != null) ? Emulator.getDatabase().getDataSource() : null;
|
||||
HikariPoolMXBean poolMxBean = (dataSource != null) ? dataSource.getHikariPoolMXBean() : null;
|
||||
|
||||
if (poolMxBean == null) {
|
||||
return new HikariPoolMetrics(0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
return new HikariPoolMetrics(
|
||||
poolMxBean.getActiveConnections(),
|
||||
poolMxBean.getIdleConnections(),
|
||||
poolMxBean.getTotalConnections(),
|
||||
poolMxBean.getThreadsAwaitingConnection(),
|
||||
dataSource.getMaximumPoolSize()
|
||||
);
|
||||
}
|
||||
|
||||
private static SchedulerMetrics collectSchedulerMetrics() {
|
||||
if (Emulator.getThreading() == null) {
|
||||
return new SchedulerMetrics(0, 0, 0, 0, false);
|
||||
}
|
||||
|
||||
if (!(Emulator.getThreading().getService() instanceof ScheduledThreadPoolExecutor executor)) {
|
||||
return new SchedulerMetrics(0, 0, 0, 0, false);
|
||||
}
|
||||
|
||||
return new SchedulerMetrics(
|
||||
executor.getQueue().size(),
|
||||
executor.getActiveCount(),
|
||||
executor.getPoolSize(),
|
||||
executor.getCompletedTaskCount(),
|
||||
!executor.isShutdown()
|
||||
);
|
||||
}
|
||||
|
||||
private static NetworkMetrics collectNetworkMetrics(long now) {
|
||||
long incomingPackets = EmulatorNetworkStats.getIncomingPackets();
|
||||
long outgoingPackets = EmulatorNetworkStats.getOutgoingPackets();
|
||||
long incomingBytes = EmulatorNetworkStats.getIncomingBytes();
|
||||
long outgoingBytes = EmulatorNetworkStats.getOutgoingBytes();
|
||||
|
||||
long previousAt = previousTelemetryAt;
|
||||
long elapsedMs = (previousAt > 0L) ? Math.max(1L, now - previousAt) : CACHE_TTL_MS;
|
||||
|
||||
double incomingPacketsPerSecond = ((incomingPackets - previousIncomingPackets) * 1000D) / elapsedMs;
|
||||
double outgoingPacketsPerSecond = ((outgoingPackets - previousOutgoingPackets) * 1000D) / elapsedMs;
|
||||
double incomingKilobytesPerSecond = ((incomingBytes - previousIncomingBytes) / 1024D) * 1000D / elapsedMs;
|
||||
double outgoingKilobytesPerSecond = ((outgoingBytes - previousOutgoingBytes) / 1024D) * 1000D / elapsedMs;
|
||||
|
||||
previousIncomingPackets = incomingPackets;
|
||||
previousOutgoingPackets = outgoingPackets;
|
||||
previousIncomingBytes = incomingBytes;
|
||||
previousOutgoingBytes = outgoingBytes;
|
||||
previousTelemetryAt = now;
|
||||
|
||||
return new NetworkMetrics(
|
||||
Math.max(0D, incomingPacketsPerSecond),
|
||||
Math.max(0D, outgoingPacketsPerSecond),
|
||||
Math.max(0D, incomingKilobytesPerSecond),
|
||||
Math.max(0D, outgoingKilobytesPerSecond),
|
||||
incomingPackets,
|
||||
outgoingPackets
|
||||
);
|
||||
}
|
||||
|
||||
private static GarbageCollectorMetrics collectGarbageCollectorMetrics(long now) {
|
||||
long totalCollections = 0L;
|
||||
long totalCollectionTimeMs = 0L;
|
||||
|
||||
for (GarbageCollectorMXBean garbageCollectorMXBean : ManagementFactory.getGarbageCollectorMXBeans()) {
|
||||
long collectionCount = garbageCollectorMXBean.getCollectionCount();
|
||||
long collectionTime = garbageCollectorMXBean.getCollectionTime();
|
||||
|
||||
if (collectionCount > 0) {
|
||||
totalCollections += collectionCount;
|
||||
}
|
||||
|
||||
if (collectionTime > 0) {
|
||||
totalCollectionTimeMs += collectionTime;
|
||||
}
|
||||
}
|
||||
|
||||
long lastObservedPauseMs = Math.max(0L, totalCollectionTimeMs - previousGcTimeMs);
|
||||
long collectionsSinceLastSample = Math.max(0L, totalCollections - previousGcCount);
|
||||
|
||||
previousGcCount = totalCollections;
|
||||
previousGcTimeMs = totalCollectionTimeMs;
|
||||
|
||||
return new GarbageCollectorMetrics(
|
||||
totalCollections,
|
||||
totalCollectionTimeMs,
|
||||
collectionsSinceLastSample,
|
||||
lastObservedPauseMs,
|
||||
now
|
||||
);
|
||||
}
|
||||
|
||||
private static void appendMemoryHistory(long timestamp, int usedMemMb, int maxMemMb, double usagePercent) {
|
||||
MEMORY_HISTORY.addLast(new MemoryPoint(timestamp, usedMemMb, maxMemMb, usagePercent));
|
||||
|
||||
while (MEMORY_HISTORY.size() > MAX_HISTORY_POINTS) {
|
||||
MEMORY_HISTORY.removeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Snapshot {
|
||||
public final Overview overview;
|
||||
public final List<MemoryPoint> memoryHistory;
|
||||
public final List<OnlineUserRow> users;
|
||||
public final List<ActiveRoomRow> rooms;
|
||||
public final List<WiredRoomRow> wired;
|
||||
public final List<WiredTopRoomRow> wiredTopRooms;
|
||||
public final HikariPoolMetrics databasePool;
|
||||
public final SchedulerMetrics scheduler;
|
||||
public final NetworkMetrics network;
|
||||
public final GarbageCollectorMetrics garbageCollector;
|
||||
|
||||
public Snapshot(Overview overview, List<MemoryPoint> memoryHistory, List<OnlineUserRow> users, List<ActiveRoomRow> rooms, List<WiredRoomRow> wired, List<WiredTopRoomRow> wiredTopRooms, HikariPoolMetrics databasePool, SchedulerMetrics scheduler, NetworkMetrics network, GarbageCollectorMetrics garbageCollector) {
|
||||
this.overview = overview;
|
||||
this.memoryHistory = memoryHistory;
|
||||
this.users = users;
|
||||
this.rooms = rooms;
|
||||
this.wired = wired;
|
||||
this.wiredTopRooms = wiredTopRooms;
|
||||
this.databasePool = databasePool;
|
||||
this.scheduler = scheduler;
|
||||
this.network = network;
|
||||
this.garbageCollector = garbageCollector;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Overview {
|
||||
public final long uptimeSeconds;
|
||||
public final long lastRefreshEpochMs;
|
||||
public final String guiStatus;
|
||||
public final int memoryUsedMb;
|
||||
public final int memoryMaxMb;
|
||||
public final int memoryAllocatedMb;
|
||||
public final double memoryUsagePercent;
|
||||
public final double cpuLoadPercent;
|
||||
public final int activeOsThreads;
|
||||
public final int connectedPlayers;
|
||||
public final int loadedRooms;
|
||||
public final int wiredTickables;
|
||||
public final int peakPlayers;
|
||||
public final int activeWebSocketSessions;
|
||||
public final int peakWebSocketSessions;
|
||||
public final double averageRoomCycleMs;
|
||||
public final double worstRoomCycleMs;
|
||||
public final int worstRoomCycleRoomId;
|
||||
public final String worstRoomCycleRoomName;
|
||||
public final long delayedEventsPending;
|
||||
public final int overloadedWiredRooms;
|
||||
public final int heavyWiredRooms;
|
||||
public final double wiredActivityPerSecond;
|
||||
|
||||
public Overview(long uptimeSeconds, long lastRefreshEpochMs, String guiStatus, int memoryUsedMb, int memoryMaxMb, int memoryAllocatedMb, double memoryUsagePercent, double cpuLoadPercent, int activeOsThreads, int connectedPlayers, int loadedRooms, int wiredTickables, int peakPlayers, int activeWebSocketSessions, int peakWebSocketSessions, double averageRoomCycleMs, double worstRoomCycleMs, int worstRoomCycleRoomId, String worstRoomCycleRoomName, long delayedEventsPending, int overloadedWiredRooms, int heavyWiredRooms, double wiredActivityPerSecond) {
|
||||
this.uptimeSeconds = uptimeSeconds;
|
||||
this.lastRefreshEpochMs = lastRefreshEpochMs;
|
||||
this.guiStatus = guiStatus;
|
||||
this.memoryUsedMb = memoryUsedMb;
|
||||
this.memoryMaxMb = memoryMaxMb;
|
||||
this.memoryAllocatedMb = memoryAllocatedMb;
|
||||
this.memoryUsagePercent = memoryUsagePercent;
|
||||
this.cpuLoadPercent = cpuLoadPercent;
|
||||
this.activeOsThreads = activeOsThreads;
|
||||
this.connectedPlayers = connectedPlayers;
|
||||
this.loadedRooms = loadedRooms;
|
||||
this.wiredTickables = wiredTickables;
|
||||
this.peakPlayers = peakPlayers;
|
||||
this.activeWebSocketSessions = activeWebSocketSessions;
|
||||
this.peakWebSocketSessions = peakWebSocketSessions;
|
||||
this.averageRoomCycleMs = averageRoomCycleMs;
|
||||
this.worstRoomCycleMs = worstRoomCycleMs;
|
||||
this.worstRoomCycleRoomId = worstRoomCycleRoomId;
|
||||
this.worstRoomCycleRoomName = worstRoomCycleRoomName;
|
||||
this.delayedEventsPending = delayedEventsPending;
|
||||
this.overloadedWiredRooms = overloadedWiredRooms;
|
||||
this.heavyWiredRooms = heavyWiredRooms;
|
||||
this.wiredActivityPerSecond = wiredActivityPerSecond;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class MemoryPoint {
|
||||
public final long timestamp;
|
||||
public final int usedMb;
|
||||
public final int maxMb;
|
||||
public final double usagePercent;
|
||||
|
||||
public MemoryPoint(long timestamp, int usedMb, int maxMb, double usagePercent) {
|
||||
this.timestamp = timestamp;
|
||||
this.usedMb = usedMb;
|
||||
this.maxMb = maxMb;
|
||||
this.usagePercent = usagePercent;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class OnlineUserRow {
|
||||
public final int id;
|
||||
public final String username;
|
||||
public final String rank;
|
||||
public final int credits;
|
||||
public final int roomId;
|
||||
|
||||
public OnlineUserRow(int id, String username, String rank, int credits, int roomId) {
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.rank = rank;
|
||||
this.credits = credits;
|
||||
this.roomId = roomId;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ActiveRoomRow {
|
||||
public final int roomId;
|
||||
public final String name;
|
||||
public final int players;
|
||||
public final int items;
|
||||
public final int tickables;
|
||||
public final double cpuMs;
|
||||
public final long estimatedRamKb;
|
||||
public final String thread;
|
||||
|
||||
public ActiveRoomRow(int roomId, String name, int players, int items, int tickables, double cpuMs, long estimatedRamKb, String thread) {
|
||||
this.roomId = roomId;
|
||||
this.name = name;
|
||||
this.players = players;
|
||||
this.items = items;
|
||||
this.tickables = tickables;
|
||||
this.cpuMs = cpuMs;
|
||||
this.estimatedRamKb = estimatedRamKb;
|
||||
this.thread = thread;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class WiredRoomRow {
|
||||
public final int roomId;
|
||||
public final long averageTickMs;
|
||||
public final long peakTickMs;
|
||||
public final int usagePercent;
|
||||
public final int delayedEventsPending;
|
||||
public final boolean overloaded;
|
||||
public final boolean heavy;
|
||||
|
||||
public WiredRoomRow(int roomId, long averageTickMs, long peakTickMs, int usagePercent, int delayedEventsPending, boolean overloaded, boolean heavy) {
|
||||
this.roomId = roomId;
|
||||
this.averageTickMs = averageTickMs;
|
||||
this.peakTickMs = peakTickMs;
|
||||
this.usagePercent = usagePercent;
|
||||
this.delayedEventsPending = delayedEventsPending;
|
||||
this.overloaded = overloaded;
|
||||
this.heavy = heavy;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class WiredTopRoomRow {
|
||||
public final int roomId;
|
||||
public final String name;
|
||||
public final int usagePercent;
|
||||
public final int averageTickMs;
|
||||
public final int peakTickMs;
|
||||
public final int delayedEventsPending;
|
||||
public final double activityPerSecond;
|
||||
public final boolean heavy;
|
||||
|
||||
public WiredTopRoomRow(int roomId, String name, int usagePercent, int averageTickMs, int peakTickMs, int delayedEventsPending, double activityPerSecond, boolean heavy) {
|
||||
this.roomId = roomId;
|
||||
this.name = name;
|
||||
this.usagePercent = usagePercent;
|
||||
this.averageTickMs = averageTickMs;
|
||||
this.peakTickMs = peakTickMs;
|
||||
this.delayedEventsPending = delayedEventsPending;
|
||||
this.activityPerSecond = activityPerSecond;
|
||||
this.heavy = heavy;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class HikariPoolMetrics {
|
||||
public final int activeConnections;
|
||||
public final int idleConnections;
|
||||
public final int totalConnections;
|
||||
public final int waitingThreads;
|
||||
public final int maxConnections;
|
||||
|
||||
public HikariPoolMetrics(int activeConnections, int idleConnections, int totalConnections, int waitingThreads, int maxConnections) {
|
||||
this.activeConnections = activeConnections;
|
||||
this.idleConnections = idleConnections;
|
||||
this.totalConnections = totalConnections;
|
||||
this.waitingThreads = waitingThreads;
|
||||
this.maxConnections = maxConnections;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class SchedulerMetrics {
|
||||
public final int queuedTasks;
|
||||
public final int activeThreads;
|
||||
public final int poolSize;
|
||||
public final long completedTasks;
|
||||
public final boolean running;
|
||||
|
||||
public SchedulerMetrics(int queuedTasks, int activeThreads, int poolSize, long completedTasks, boolean running) {
|
||||
this.queuedTasks = queuedTasks;
|
||||
this.activeThreads = activeThreads;
|
||||
this.poolSize = poolSize;
|
||||
this.completedTasks = completedTasks;
|
||||
this.running = running;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class NetworkMetrics {
|
||||
public final double incomingPacketsPerSecond;
|
||||
public final double outgoingPacketsPerSecond;
|
||||
public final double incomingKilobytesPerSecond;
|
||||
public final double outgoingKilobytesPerSecond;
|
||||
public final long totalIncomingPackets;
|
||||
public final long totalOutgoingPackets;
|
||||
|
||||
public NetworkMetrics(double incomingPacketsPerSecond, double outgoingPacketsPerSecond, double incomingKilobytesPerSecond, double outgoingKilobytesPerSecond, long totalIncomingPackets, long totalOutgoingPackets) {
|
||||
this.incomingPacketsPerSecond = incomingPacketsPerSecond;
|
||||
this.outgoingPacketsPerSecond = outgoingPacketsPerSecond;
|
||||
this.incomingKilobytesPerSecond = incomingKilobytesPerSecond;
|
||||
this.outgoingKilobytesPerSecond = outgoingKilobytesPerSecond;
|
||||
this.totalIncomingPackets = totalIncomingPackets;
|
||||
this.totalOutgoingPackets = totalOutgoingPackets;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class GarbageCollectorMetrics {
|
||||
public final long totalCollections;
|
||||
public final long totalCollectionTimeMs;
|
||||
public final long collectionsSinceLastSample;
|
||||
public final long lastObservedPauseMs;
|
||||
public final long sampledAtEpochMs;
|
||||
|
||||
public GarbageCollectorMetrics(long totalCollections, long totalCollectionTimeMs, long collectionsSinceLastSample, long lastObservedPauseMs, long sampledAtEpochMs) {
|
||||
this.totalCollections = totalCollections;
|
||||
this.totalCollectionTimeMs = totalCollectionTimeMs;
|
||||
this.collectionsSinceLastSample = collectionsSinceLastSample;
|
||||
this.lastObservedPauseMs = lastObservedPauseMs;
|
||||
this.sampledAtEpochMs = sampledAtEpochMs;
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -14,6 +14,7 @@ import com.eu.habbo.networking.gameserver.encoders.GameServerMessageEncoder;
|
||||
import com.eu.habbo.networking.gameserver.encoders.GameServerMessageLogger;
|
||||
import com.eu.habbo.networking.gameserver.handlers.IdleTimeoutHandler;
|
||||
import com.eu.habbo.networking.gameserver.handlers.WebSocketHttpHandler;
|
||||
import com.eu.habbo.networking.gameserver.stats.EmuStatsHttpHandler;
|
||||
import com.eu.habbo.networking.gameserver.ssl.SSLCertificateLoader;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
@@ -62,6 +63,7 @@ public class WebSocketChannelInitializer extends ChannelInitializer<SocketChanne
|
||||
ch.pipeline().addLast("authHttpHandler", new AuthHttpHandler());
|
||||
ch.pipeline().addLast("badgeHttpHandler", new BadgeHttpHandler());
|
||||
ch.pipeline().addLast("badgeLeaderboardHttpHandler", new BadgeLeaderboardHttpHandler());
|
||||
ch.pipeline().addLast("emuStatsHttpHandler", new EmuStatsHttpHandler());
|
||||
ch.pipeline().addLast("wsProtocolHandler", new WebSocketServerProtocolHandler(this.wsConfig));
|
||||
ch.pipeline().addLast("wsFrameAggregator", new WebSocketFrameAggregator(MAX_FRAME_SIZE));
|
||||
ch.pipeline().addLast("wsCodec", new WebSocketCodec());
|
||||
|
||||
+2
@@ -1,5 +1,6 @@
|
||||
package com.eu.habbo.networking.gameserver.encoders;
|
||||
|
||||
import com.eu.habbo.monitoring.EmulatorNetworkStats;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
@@ -16,6 +17,7 @@ public class GameServerMessageEncoder extends MessageToByteEncoder<ServerMessage
|
||||
ByteBuf buf = message.get();
|
||||
|
||||
try {
|
||||
EmulatorNetworkStats.recordOutgoing(buf.readableBytes());
|
||||
out.writeBytes(buf);
|
||||
} finally {
|
||||
// Release copied buffer.
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package com.eu.habbo.networking.gameserver.stats;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.monitoring.EmulatorStatsService;
|
||||
import com.eu.habbo.networking.gameserver.auth.AccessTokenService;
|
||||
import com.eu.habbo.networking.gameserver.auth.CorsOriginGate;
|
||||
import com.google.gson.Gson;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class EmuStatsHttpHandler extends ChannelInboundHandlerAdapter {
|
||||
private static final String BASE_PATH = "/api/emustats";
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (!(msg instanceof FullHttpRequest req)) {
|
||||
super.channelRead(ctx, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
String path = new QueryStringDecoder(req.uri()).path();
|
||||
if (!BASE_PATH.equals(path)) {
|
||||
super.channelRead(ctx, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
handle(ctx, req);
|
||||
} finally {
|
||||
ReferenceCountUtil.release(req);
|
||||
}
|
||||
}
|
||||
|
||||
private void handle(ChannelHandlerContext ctx, FullHttpRequest req) {
|
||||
if (req.method() == HttpMethod.OPTIONS) {
|
||||
sendCors(ctx, req);
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method() != HttpMethod.GET && req.method() != HttpMethod.HEAD) {
|
||||
sendJson(ctx, req, HttpResponseStatus.METHOD_NOT_ALLOWED, "{\"error\":\"Use GET.\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
int userId = authenticate(req);
|
||||
if (userId <= 0) {
|
||||
sendJson(ctx, req, HttpResponseStatus.UNAUTHORIZED, "{\"error\":\"Unauthorized.\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
Habbo habbo = Emulator.getGameServer().getGameClientManager().getHabbo(userId);
|
||||
if (habbo == null || !habbo.hasPermission(Permission.ACC_MODTOOL_ROOM_INFO)) {
|
||||
sendJson(ctx, req, HttpResponseStatus.FORBIDDEN, "{\"error\":\"Forbidden.\"}");
|
||||
return;
|
||||
}
|
||||
|
||||
EmulatorStatsService.Snapshot snapshot = EmulatorStatsService.collectSnapshot();
|
||||
sendJson(ctx, req, HttpResponseStatus.OK, GSON.toJson(snapshot));
|
||||
}
|
||||
|
||||
private static int authenticate(FullHttpRequest req) {
|
||||
String header = req.headers().get(HttpHeaderNames.AUTHORIZATION);
|
||||
if (header == null || header.isBlank()) return 0;
|
||||
|
||||
String token = header.startsWith("Bearer ") ? header.substring(7).trim() : header.trim();
|
||||
return AccessTokenService.verify(token);
|
||||
}
|
||||
|
||||
private static void sendCors(ChannelHandlerContext ctx, FullHttpRequest req) {
|
||||
if (!CorsOriginGate.isAllowed(req)) {
|
||||
FullHttpResponse forbidden = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN);
|
||||
forbidden.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
|
||||
ctx.writeAndFlush(forbidden).addListener(ChannelFutureListener.CLOSE);
|
||||
return;
|
||||
}
|
||||
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NO_CONTENT);
|
||||
applyCors(req, response);
|
||||
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
|
||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
private static void sendJson(ChannelHandlerContext ctx, FullHttpRequest req, HttpResponseStatus status, String json) {
|
||||
boolean headRequest = req.method() == HttpMethod.HEAD;
|
||||
byte[] bytes = (json == null ? "{}" : json).getBytes(StandardCharsets.UTF_8);
|
||||
FullHttpResponse response = headRequest
|
||||
? new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status)
|
||||
: new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.wrappedBuffer(bytes));
|
||||
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8");
|
||||
response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, bytes.length);
|
||||
response.headers().set(HttpHeaderNames.CACHE_CONTROL, "no-store, no-cache, must-revalidate");
|
||||
applyCors(req, response);
|
||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
|
||||
private static void applyCors(FullHttpRequest req, FullHttpResponse response) {
|
||||
String origin = req.headers().get(HttpHeaderNames.ORIGIN);
|
||||
if (origin != null && !origin.isEmpty() && CorsOriginGate.isAllowed(req)) {
|
||||
response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
|
||||
response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
||||
response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS, "GET, OPTIONS");
|
||||
response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS, "Authorization, Content-Type, X-Requested-With, X-Nitro-Key, X-Nitro-Api");
|
||||
response.headers().set(HttpHeaderNames.VARY, "Origin");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,8 +123,8 @@ public class PluginManager {
|
||||
WiredEngine.RATE_LIMIT_WINDOW_MS = Emulator.getConfig().getInt("wired.abuse.rate.limit.window.ms", 10000);
|
||||
WiredEngine.WIRED_BAN_DURATION_MS = Emulator.getConfig().getInt("wired.abuse.ban.duration.ms", 600000);
|
||||
WiredEngine.MONITOR_USAGE_WINDOW_MS = Emulator.getConfig().getInt("wired.monitor.usage.window.ms", 1000);
|
||||
WiredEngine.MONITOR_USAGE_LIMIT = Emulator.getConfig().getInt("wired.monitor.usage.limit", 1000);
|
||||
WiredEngine.MONITOR_DELAYED_EVENTS_LIMIT = Emulator.getConfig().getInt("wired.monitor.delayed.events.limit", 100);
|
||||
WiredEngine.MONITOR_USAGE_LIMIT = Emulator.getConfig().getInt("wired.monitor.usage.limit", 50000);
|
||||
WiredEngine.MONITOR_DELAYED_EVENTS_LIMIT = Emulator.getConfig().getInt("wired.monitor.delayed.events.limit", 50000);
|
||||
WiredEngine.MONITOR_OVERLOAD_AVERAGE_MS = Emulator.getConfig().getInt("wired.monitor.overload.average.ms", 50);
|
||||
WiredEngine.MONITOR_OVERLOAD_PEAK_MS = Emulator.getConfig().getInt("wired.monitor.overload.peak.ms", 150);
|
||||
WiredEngine.MONITOR_OVERLOAD_CONSECUTIVE_WINDOWS = Emulator.getConfig().getInt("wired.monitor.overload.consecutive.windows", 2);
|
||||
|
||||
@@ -22,6 +22,9 @@ public class BotFollowHabbo implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (this.bot == null || this.bot.getRoom() == null || this.bot.getRoom() != this.room) {
|
||||
return;
|
||||
}
|
||||
if (this.bot != null) {
|
||||
if (this.habbo != null && this.bot.getFollowingHabboId() == this.habbo.getHabboInfo().getId()) {
|
||||
if (this.habbo.getHabboInfo().getCurrentRoom() != null && this.habbo.getHabboInfo().getCurrentRoom() == this.room) {
|
||||
|
||||
@@ -38,6 +38,10 @@ public class RoomUnitWalkToRoomUnit implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (this.room == null || !this.room.isLoaded() || this.walker == null || this.target == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.goalTile == null) {
|
||||
this.findNewLocation();
|
||||
Emulator.getThreading().run(this, 500);
|
||||
@@ -48,9 +52,8 @@ public class RoomUnitWalkToRoomUnit implements Runnable {
|
||||
if (this.walker.getCurrentLocation().distance(this.goalTile) <= this.minDistance) {
|
||||
for (Runnable r : this.targetReached) {
|
||||
Emulator.getThreading().run(r);
|
||||
|
||||
WiredManager.triggerBotReachedHabbo(this.room, this.walker, this.target);
|
||||
}
|
||||
WiredManager.triggerBotReachedHabbo(this.room, this.walker, this.target);
|
||||
} else {
|
||||
Emulator.getThreading().run(this, 500);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ public class GameTimer implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
timer.setThreadActive(false);
|
||||
|
||||
if (timer.getRoomId() == 0) {
|
||||
timer.setRunning(false);
|
||||
return;
|
||||
@@ -23,7 +25,6 @@ public class GameTimer implements Runnable {
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(timer.getRoomId());
|
||||
|
||||
if (room == null || !timer.isRunning() || timer.isPaused()) {
|
||||
timer.setThreadActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -31,10 +32,10 @@ public class GameTimer implements Runnable {
|
||||
if (timer.getTimeNow() < 0) timer.setTimeNow(0);
|
||||
|
||||
if (timer.getTimeNow() > 0) {
|
||||
timer.setThreadActive(true);
|
||||
Emulator.getThreading().run(this, 1000);
|
||||
if (timer.tryActivateTimerThread()) {
|
||||
Emulator.getThreading().run(this, 1000);
|
||||
}
|
||||
} else {
|
||||
timer.setThreadActive(false);
|
||||
timer.setTimeNow(0);
|
||||
timer.endGame(room);
|
||||
WiredManager.triggerGameEnds(room);
|
||||
|
||||
@@ -14,28 +14,30 @@ public class GameUpCounter implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
timer.setThreadActive(false);
|
||||
|
||||
if (timer.getRoomId() == 0) {
|
||||
timer.setRunning(false);
|
||||
timer.setThreadActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(timer.getRoomId());
|
||||
|
||||
if (room == null || !timer.isRunning() || timer.isPaused()) {
|
||||
timer.setThreadActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
int tickDelayMs = (int) timer.getNextTickDelayMs();
|
||||
timer.advanceCounterInMs(tickDelayMs);
|
||||
WiredManager.triggerClockCounter(room, timer);
|
||||
if (timer.getCurrentTimeInMs() % 1000 == 0) {
|
||||
WiredManager.triggerClockCounter(room, timer);
|
||||
}
|
||||
|
||||
if (timer.getCurrentTimeInMs() < timer.getMaximumTimeInMs()) {
|
||||
timer.setThreadActive(true);
|
||||
Emulator.getThreading().run(this, timer.getNextTickDelayMs());
|
||||
if (timer.tryActivateTimerThread()) {
|
||||
Emulator.getThreading().run(this, timer.getNextTickDelayMs());
|
||||
}
|
||||
} else {
|
||||
timer.setThreadActive(false);
|
||||
timer.setCurrentTimeInMs(timer.getMaximumTimeInMs());
|
||||
timer.endGame(room);
|
||||
WiredManager.triggerGameEnds(room);
|
||||
|
||||
Reference in New Issue
Block a user