diff --git a/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java b/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java index 564a8a7a..b7ec88f4 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java @@ -735,5 +735,9 @@ public class PacketManager { this.registerHandler(Incoming.HousekeepingKickAllFromRoomEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingKickAllFromRoomEvent.class); this.registerHandler(Incoming.HousekeepingTransferRoomOwnershipEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingTransferRoomOwnershipEvent.class); this.registerHandler(Incoming.HousekeepingDeleteRoomEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingDeleteRoomEvent.class); + this.registerHandler(Incoming.HousekeepingGiveCreditsEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingGiveCreditsEvent.class); + this.registerHandler(Incoming.HousekeepingGiveCurrencyEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingGiveCurrencyEvent.class); + this.registerHandler(Incoming.HousekeepingGrantItemEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingGrantItemEvent.class); + this.registerHandler(Incoming.HousekeepingSetHcSubscriptionEvent, com.eu.habbo.messages.incoming.housekeeping.HousekeepingSetHcSubscriptionEvent.class); } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java index aeb4a766..bf9dcfa8 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java @@ -479,4 +479,8 @@ public class Incoming { public static final int HousekeepingKickAllFromRoomEvent = 9114; public static final int HousekeepingTransferRoomOwnershipEvent = 9115; public static final int HousekeepingDeleteRoomEvent = 9116; + public static final int HousekeepingGiveCreditsEvent = 9117; + public static final int HousekeepingGiveCurrencyEvent = 9118; + public static final int HousekeepingGrantItemEvent = 9119; + public static final int HousekeepingSetHcSubscriptionEvent = 9120; } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGiveCreditsEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGiveCreditsEvent.java new file mode 100644 index 00000000..e0d16e2d --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGiveCreditsEvent.java @@ -0,0 +1,62 @@ +package com.eu.habbo.messages.incoming.housekeeping; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public class HousekeepingGiveCreditsEvent extends MessageHandler { + private static final String ACTION_KEY = "user.give_credits"; + + @Override + public int getRatelimit() { + return 1000; + } + + @Override + public void handle() throws Exception { + if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) { + return; + } + + int userId = this.packet.readInt(); + int amount = this.packet.readInt(); + + if (userId <= 0 || amount == 0) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input")); + return; + } + + Habbo online = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId); + + if (online != null) { + // giveCredits already pushes UserCreditsComposer and persists via the + // standard HabboInfo write path; nothing extra needed for the online branch. + online.giveCredits(amount); + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, "")); + return; + } + + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); + PreparedStatement statement = connection.prepareStatement("UPDATE users SET credits = credits + ? WHERE id = ? LIMIT 1")) { + statement.setInt(1, amount); + statement.setInt(2, userId); + int rows = statement.executeUpdate(); + + if (rows == 0) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.user_not_found")); + return; + } + } catch (SQLException e) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.db_failed")); + return; + } + + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, "")); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGiveCurrencyEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGiveCurrencyEvent.java new file mode 100644 index 00000000..ba347b93 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGiveCurrencyEvent.java @@ -0,0 +1,74 @@ +package com.eu.habbo.messages.incoming.housekeeping; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Generic non-credits currency grant. Wire field `currencyType`: + * 0 => duckets / pixels, 5 => diamonds, 101 => seasonal-primary. + * Online users go through Habbo.givePoints / givePixels which dispatches + * a UserCurrencyComposer; offline goes straight to `users_currency`. + */ +public class HousekeepingGiveCurrencyEvent extends MessageHandler { + private static final int CURRENCY_DUCKETS = 0; + + @Override + public int getRatelimit() { + return 1000; + } + + @Override + public void handle() throws Exception { + if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) { + return; + } + + int userId = this.packet.readInt(); + int currencyType = this.packet.readInt(); + int amount = this.packet.readInt(); + + String actionKey = "user.give_currency_" + currencyType; + + if (userId <= 0 || amount == 0) { + this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, false, 0, "housekeeping.error.invalid_input")); + return; + } + + Habbo online = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId); + + if (online != null) { + // givePixels writes users_currency type=0 and ships UserCurrency; + // givePoints(type, amount) is the generalised path for everything else. + if (currencyType == CURRENCY_DUCKETS) { + online.givePixels(amount); + } else { + online.givePoints(currencyType, amount); + } + + this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, true, userId, "")); + return; + } + + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); + PreparedStatement statement = connection.prepareStatement( + "INSERT INTO users_currency (user_id, type, amount) VALUES (?, ?, ?) " + + "ON DUPLICATE KEY UPDATE amount = amount + VALUES(amount)")) { + statement.setInt(1, userId); + statement.setInt(2, currencyType); + statement.setInt(3, amount); + statement.executeUpdate(); + } catch (SQLException e) { + this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, false, 0, "housekeeping.error.db_failed")); + return; + } + + this.client.sendResponse(new HousekeepingActionResultComposer(actionKey, true, userId, "")); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGrantItemEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGrantItemEvent.java new file mode 100644 index 00000000..cf1ecb6b --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingGrantItemEvent.java @@ -0,0 +1,62 @@ +package com.eu.habbo.messages.incoming.housekeeping; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Grant a furni item (by items_base id) `quantity` times. Each row in + * the `items` table is one furni unit; quantity > 1 just batches the + * insert. The online user's HabboInventory isn't proactively refreshed + * — they'll see the new items next time they open the hand inventory + * (or after a relog). + */ +public class HousekeepingGrantItemEvent extends MessageHandler { + private static final String ACTION_KEY = "user.grant_item"; + private static final int MAX_QUANTITY_PER_CALL = 100; + + @Override + public int getRatelimit() { + return 2000; + } + + @Override + public void handle() throws Exception { + if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) { + return; + } + + int userId = this.packet.readInt(); + int itemId = this.packet.readInt(); + int quantity = this.packet.readInt(); + + if (userId <= 0 || itemId <= 0 || quantity <= 0) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input")); + return; + } + + if (quantity > MAX_QUANTITY_PER_CALL) { + quantity = MAX_QUANTITY_PER_CALL; + } + + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); + PreparedStatement statement = connection.prepareStatement("INSERT INTO items (user_id, item_id, extra_data) VALUES (?, ?, '')")) { + for (int i = 0; i < quantity; i++) { + statement.setInt(1, userId); + statement.setInt(2, itemId); + statement.addBatch(); + } + statement.executeBatch(); + } catch (SQLException e) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.economy_failed")); + return; + } + + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, "")); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingSetHcSubscriptionEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingSetHcSubscriptionEvent.java new file mode 100644 index 00000000..9facf4f4 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/housekeeping/HousekeepingSetHcSubscriptionEvent.java @@ -0,0 +1,76 @@ +package com.eu.habbo.messages.incoming.housekeeping; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.outgoing.housekeeping.HousekeepingActionResultComposer; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * Extend a user's HC by `days`. Adds to the existing club_expire_timestamp + * if it's still in the future, otherwise stretches from `now`. Days==0 + * means cancel the active subscription (timestamp clamped to `now`). + */ +public class HousekeepingSetHcSubscriptionEvent extends MessageHandler { + private static final String ACTION_KEY = "user.set_hc"; + private static final int SECONDS_IN_DAY = 24 * 3600; + + @Override + public int getRatelimit() { + return 1000; + } + + @Override + public void handle() throws Exception { + if (!this.client.getHabbo().hasPermission(Permission.ACC_HOUSEKEEPING)) { + return; + } + + int userId = this.packet.readInt(); + int days = this.packet.readInt(); + + if (userId <= 0 || days < 0) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input")); + return; + } + + int now = Emulator.getIntUnixTimestamp(); + int newExpire; + + Habbo online = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId); + + if (days == 0) { + newExpire = now; + } else if (online != null) { + int current = online.getHabboStats().getClubExpireTimestamp(); + newExpire = (current > now ? current : now) + (days * SECONDS_IN_DAY); + } else { + newExpire = now + (days * SECONDS_IN_DAY); // best-effort offline; can't read previous expiry cheaply + } + + if (online != null) { + online.getHabboStats().setClubExpireTimestamp(newExpire); + } + + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); + PreparedStatement statement = connection.prepareStatement("UPDATE users_settings SET club_expire_timestamp = ? WHERE user_id = ? LIMIT 1")) { + statement.setInt(1, newExpire); + statement.setInt(2, userId); + int rows = statement.executeUpdate(); + + if (rows == 0) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.user_not_found")); + return; + } + } catch (SQLException e) { + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.db_failed")); + return; + } + + this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, "")); + } +}