Merge pull request #139 from duckietm/dev

Dev
This commit is contained in:
DuckieTM
2026-06-01 08:27:01 +02:00
committed by GitHub
5 changed files with 108 additions and 6 deletions
@@ -134,7 +134,18 @@ public class RoomChatMessageBubbles {
}
public static RoomChatMessageBubbles getBubble(int id) {
return BUBBLES.getOrDefault(id, NORMAL);
RoomChatMessageBubbles bubble = BUBBLES.get(id);
if (bubble != null) return bubble;
// Custom chat bubbles (client-side only, e.g. ids 253+) are not registered
// above. Instead of falling back to NORMAL (which made them render as the
// default bubble), pass the id through so the server relays it as-is and
// the client renders its own .bubble-<id> style. Capped to avoid abuse.
if (id > 0 && id <= 1000) {
return new RoomChatMessageBubbles(id, "CUSTOM_" + id, "", true, false);
}
return NORMAL;
}
private static void registerBubble(RoomChatMessageBubbles bubble) {
@@ -12,9 +12,11 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
@@ -330,26 +332,88 @@ public class WheelManager {
return true;
}
public void savePrize(int id, String type, String value, int amount, int pointsType, int weight, String label) {
/**
* Persists a single prize. An {@code id <= 0} inserts a brand-new prize and
* returns its generated id; a positive id updates the existing row (and
* re-enables it, so a previously soft-deleted prize can be brought back).
* {@code sortOrder} reflects the prize's position in the editor so the
* wheel layout matches what the admin sees. Returns the effective row id,
* or {@code 0} if the write failed.
*/
public int savePrize(int id, String type, String value, int amount, int pointsType, int weight, String label, int sortOrder) {
String safeType = (type != null && VALID_PRIZE_TYPES.contains(type)) ? type : "nothing";
String safeValue = truncate(value, MAX_STRING_LEN);
String safeLabel = truncate(label, MAX_STRING_LEN);
int safeAmount = clamp(amount, 0, MAX_PRIZE_AMOUNT);
int safeWeight = clamp(weight, 0, MAX_WEIGHT);
int safeSort = clamp(sortOrder, 0, MAX_PRIZES_PER_SAVE);
if (id > 0) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"UPDATE wheel_prizes SET type = ?, value = ?, amount = ?, points_type = ?, weight = ?, label = ? WHERE id = ?")) {
"UPDATE wheel_prizes SET type = ?, value = ?, amount = ?, points_type = ?, weight = ?, label = ?, sort_order = ?, enabled = 1 WHERE id = ?")) {
statement.setString(1, safeType);
statement.setString(2, safeValue);
statement.setInt(3, safeAmount);
statement.setInt(4, pointsType);
statement.setInt(5, safeWeight);
statement.setString(6, safeLabel);
statement.setInt(7, id);
statement.setInt(7, safeSort);
statement.setInt(8, id);
statement.executeUpdate();
return id;
} catch (SQLException e) {
LOGGER.error("Failed to save wheel prize {}", id, e);
return 0;
}
}
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"INSERT INTO wheel_prizes (type, value, amount, points_type, weight, label, enabled, sort_order) VALUES (?, ?, ?, ?, ?, ?, 1, ?)",
Statement.RETURN_GENERATED_KEYS)) {
statement.setString(1, safeType);
statement.setString(2, safeValue);
statement.setInt(3, safeAmount);
statement.setInt(4, pointsType);
statement.setInt(5, safeWeight);
statement.setString(6, safeLabel);
statement.setInt(7, safeSort);
statement.executeUpdate();
try (ResultSet keys = statement.getGeneratedKeys()) {
if (keys.next()) return keys.getInt(1);
}
} catch (SQLException e) {
LOGGER.error("Failed to insert wheel prize", e);
}
return 0;
}
/**
* Soft-deletes every enabled prize whose id is not in {@code keptIds} by
* setting {@code enabled = 0}. This is intentionally non-destructive: rows
* stay in the table (so historical references and re-enabling remain
* possible) but {@link #loadPrizes()} only ever loads {@code enabled = 1}.
* An empty set disables all prizes.
*/
public void disablePrizesNotIn(Set<Integer> keptIds) {
if (keptIds == null) return;
StringBuilder sql = new StringBuilder("UPDATE wheel_prizes SET enabled = 0 WHERE enabled = 1");
if (!keptIds.isEmpty()) {
StringJoiner ids = new StringJoiner(",", " AND id NOT IN (", ")");
for (Integer keptId : keptIds) {
ids.add(Integer.toString(keptId));
}
sql.append(ids);
}
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(sql.toString())) {
statement.executeUpdate();
} catch (SQLException e) {
LOGGER.error("Failed to disable removed wheel prizes", e);
}
}
@@ -1,5 +1,6 @@
package com.eu.habbo.messages.incoming.handshake;
import com.eu.habbo.Emulator;
import com.eu.habbo.messages.NoAuthMessage;
import com.eu.habbo.messages.incoming.MessageHandler;
import org.slf4j.Logger;
@@ -24,6 +25,15 @@ public class MachineIDEvent extends MessageHandler {
this.client.setMachineId(storedMachineId);
// Persist the machine fingerprint onto the user so machine/super bans can
// target it (createOfflineUserBan copies users.machine_id). The Nitro client
// sends this UniqueID packet right after the SSO ticket, so the Habbo is
// normally already loaded by the time we get here.
if (!storedMachineId.isEmpty() && this.client.getHabbo() != null && this.client.getHabbo().getHabboInfo() != null) {
this.client.getHabbo().getHabboInfo().setMachineID(storedMachineId);
Emulator.getThreading().run(this.client.getHabbo());
}
LOGGER.debug("Setting client MachineId to {}", storedMachineId);
}
}
@@ -161,6 +161,12 @@ public class SecureLoginEvent extends MessageHandler {
throw new NullPointerException(habbo.getHabboInfo().getUsername() + " has a NON EXISTING RANK!");
}
// If the machine fingerprint already arrived (UniqueID before login),
// persist it so machine/super bans can target this user.
if (this.client.getMachineId() != null && !this.client.getMachineId().isEmpty()) {
this.client.getHabbo().getHabboInfo().setMachineID(this.client.getMachineId());
}
Emulator.getThreading().run(habbo);
Emulator.getGameEnvironment().getHabboManager().addHabbo(habbo);
} catch (Exception e) {
@@ -6,6 +6,9 @@ import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.wheel.WheelAdminPrizesComposer;
import com.eu.habbo.messages.outgoing.wheel.WheelDataComposer;
import java.util.HashSet;
import java.util.Set;
public class WheelAdminSavePrizesEvent extends MessageHandler {
public static final String PERMISSION_KEY = "acc_wheeladmin";
@@ -25,6 +28,12 @@ public class WheelAdminSavePrizesEvent extends MessageHandler {
int count = this.packet.readInt();
if (count <= 0 || count > WheelManager.MAX_PRIZES_PER_SAVE) return;
// The client sends the full authoritative list of prizes in display
// order. id <= 0 means "insert a new prize"; any existing prize whose
// id is absent from this list was removed in the editor and gets
// soft-disabled below.
Set<Integer> keptIds = new HashSet<>();
for (int i = 0; i < count; i++) {
int id = this.packet.readInt();
String type = this.packet.readString();
@@ -33,9 +42,11 @@ public class WheelAdminSavePrizesEvent extends MessageHandler {
int pointsType = this.packet.readInt();
int weight = this.packet.readInt();
String label = this.packet.readString();
wheel.savePrize(id, type, value, amount, pointsType, weight, label);
int savedId = wheel.savePrize(id, type, value, amount, pointsType, weight, label, i);
if (savedId > 0) keptIds.add(savedId);
}
wheel.disablePrizesNotIn(keptIds);
wheel.reload();
this.client.sendResponse(new WheelAdminPrizesComposer(wheel.getPrizes()));