🆙 update to 4.0.3

This commit is contained in:
duckietm
2026-01-12 12:03:14 +01:00
parent c650e411da
commit d37a16f4a5
16 changed files with 465 additions and 35 deletions
@@ -0,0 +1,18 @@
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- =====================================================
-- Update 4.0.2-beta to 4.0.3-beta
-- =====================================================
INSERT INTO `emulator_settings` (`key`, `value`) VALUES
-- Maximum pending flood-fill tasks in the executor queue
-- Prevents memory leaks from rapid tile locking
('hotel.banzai.fill.max_queue', '50'),
-- Minimum interval (ms) between flood-fill calculations per game
-- Prevents errors via rapid wired triggering
('hotel.banzai.fill.cooldown_ms', '100')
ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);
SET FOREIGN_KEY_CHECKS = 1;
@@ -0,0 +1,216 @@
-- =====================================================
-- Pet Breeding Complete Setup
-- =====================================================
-- This file sets up all breeding-related data:
-- 1. pet_breeding - Maps parent pet types to offspring types
-- 2. pet_breeding_races - Defines possible breeds/colors for offspring by rarity
-- =====================================================
-- =====================================================
-- SECTION 1: Pet Breeding (Parent -> Offspring Mapping)
-- =====================================================
-- This table maps which pet type produces which baby type
CREATE TABLE IF NOT EXISTS `pet_breeding` (
`pet_id` int(11) NOT NULL COMMENT 'Parent pet type',
`offspring_id` int(11) NOT NULL COMMENT 'Baby pet type',
PRIMARY KEY (`pet_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Clear existing data
TRUNCATE TABLE `pet_breeding`;
-- Insert breeding mappings
INSERT INTO `pet_breeding` (`pet_id`, `offspring_id`) VALUES
(0, 29), -- Dog -> Baby Dog
(1, 28), -- Cat -> Baby Cat
(3, 25), -- Terrier -> Baby Terrier
(4, 24), -- Bear -> Baby Bear
(5, 30); -- Pig -> Baby Pig
-- =====================================================
-- SECTION 2: Pet Breeding Races (Offspring Breeds by Rarity)
-- =====================================================
-- rarity_level: 1=Common, 2=Uncommon, 3=Rare, 4=Epic
-- breed: The visual breed/color variant of the baby pet
--
-- Higher rarity = harder to get, more special colors
-- Each baby pet type should have breeds at all 4 rarity levels
CREATE TABLE IF NOT EXISTS `pet_breeding_races` (
`pet_id` int(11) NOT NULL COMMENT 'Baby pet type (offspring)',
`rarity_level` int(11) NOT NULL COMMENT '1=Common, 2=Uncommon, 3=Rare, 4=Epic',
`breed` int(11) NOT NULL COMMENT 'Visual breed/color variant',
PRIMARY KEY (`pet_id`, `rarity_level`, `breed`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Clear existing data
TRUNCATE TABLE `pet_breeding_races`;
-- =====================================================
-- Baby Dog (29) - Offspring of Dog (0) - 20 breeds
-- =====================================================
INSERT INTO `pet_breeding_races` (`pet_type`, `rarity_level`, `breed`) VALUES
-- Common breeds (rarity 1) - Most likely to get
(29, 1, 0),
(29, 1, 1),
(29, 1, 2),
(29, 1, 3),
(29, 1, 4),
(29, 1, 5),
(29, 1, 6),
(29, 1, 7),
-- Uncommon breeds (rarity 2)
(29, 2, 8),
(29, 2, 9),
(29, 2, 10),
(29, 2, 11),
(29, 2, 12),
-- Rare breeds (rarity 3)
(29, 3, 13),
(29, 3, 14),
(29, 3, 15),
(29, 3, 16),
-- Epic breeds (rarity 4) - Hardest to get
(29, 4, 17),
(29, 4, 18),
(29, 4, 19);
-- =====================================================
-- Baby Cat (28) - Offspring of Cat (1) - 20 breeds
-- =====================================================
INSERT INTO `pet_breeding_races` (`pet_type`, `rarity_level`, `breed`) VALUES
-- Common breeds (rarity 1)
(28, 1, 0),
(28, 1, 1),
(28, 1, 2),
(28, 1, 3),
(28, 1, 4),
(28, 1, 5),
(28, 1, 6),
(28, 1, 7),
-- Uncommon breeds (rarity 2)
(28, 2, 8),
(28, 2, 9),
(28, 2, 10),
(28, 2, 11),
(28, 2, 12),
-- Rare breeds (rarity 3)
(28, 3, 13),
(28, 3, 14),
(28, 3, 15),
(28, 3, 16),
-- Epic breeds (rarity 4)
(28, 4, 17),
(28, 4, 18),
(28, 4, 19);
-- =====================================================
-- Baby Terrier (25) - Offspring of Terrier (3) - 20 breeds
-- =====================================================
INSERT INTO `pet_breeding_races` (`pet_type`, `rarity_level`, `breed`) VALUES
-- Common breeds (rarity 1)
(25, 1, 0),
(25, 1, 1),
(25, 1, 2),
(25, 1, 3),
(25, 1, 4),
(25, 1, 5),
(25, 1, 6),
(25, 1, 7),
-- Uncommon breeds (rarity 2)
(25, 2, 8),
(25, 2, 9),
(25, 2, 10),
(25, 2, 11),
(25, 2, 12),
-- Rare breeds (rarity 3)
(25, 3, 13),
(25, 3, 14),
(25, 3, 15),
(25, 3, 16),
-- Epic breeds (rarity 4)
(25, 4, 17),
(25, 4, 18),
(25, 4, 19);
-- =====================================================
-- Baby Bear (24) - Offspring of Bear (4) - 20 breeds
-- =====================================================
INSERT INTO `pet_breeding_races` (`pet_type`, `rarity_level`, `breed`) VALUES
-- Common breeds (rarity 1)
(24, 1, 0),
(24, 1, 1),
(24, 1, 2),
(24, 1, 3),
(24, 1, 4),
(24, 1, 5),
(24, 1, 6),
(24, 1, 7),
-- Uncommon breeds (rarity 2)
(24, 2, 8),
(24, 2, 9),
(24, 2, 10),
(24, 2, 11),
(24, 2, 12),
-- Rare breeds (rarity 3)
(24, 3, 13),
(24, 3, 14),
(24, 3, 15),
(24, 3, 16),
-- Epic breeds (rarity 4)
(24, 4, 17),
(24, 4, 18),
(24, 4, 19);
-- =====================================================
-- Baby Pig (30) - Offspring of Pig (5) - 20 breeds
-- =====================================================
INSERT INTO `pet_breeding_races` (`pet_type`, `rarity_level`, `breed`) VALUES
-- Common breeds (rarity 1)
(30, 1, 0),
(30, 1, 1),
(30, 1, 2),
(30, 1, 3),
(30, 1, 4),
(30, 1, 5),
(30, 1, 6),
(30, 1, 7),
-- Uncommon breeds (rarity 2)
(30, 2, 8),
(30, 2, 9),
(30, 2, 10),
(30, 2, 11),
(30, 2, 12),
-- Rare breeds (rarity 3)
(30, 3, 13),
(30, 3, 14),
(30, 3, 15),
(30, 3, 16),
-- Epic breeds (rarity 4)
(30, 4, 17),
(30, 4, 18),
(30, 4, 19);
-- =====================================================
-- Also ensure pet_actions has correct offspring_type values
-- =====================================================
UPDATE `pet_actions` SET `offspring_type` = 29 WHERE `pet_type` = 0; -- Dog -> Baby Dog
UPDATE `pet_actions` SET `offspring_type` = 28 WHERE `pet_type` = 1; -- Cat -> Baby Cat
UPDATE `pet_actions` SET `offspring_type` = 25 WHERE `pet_type` = 3; -- Terrier -> Baby Terrier
UPDATE `pet_actions` SET `offspring_type` = 24 WHERE `pet_type` = 4; -- Bear -> Baby Bear
UPDATE `pet_actions` SET `offspring_type` = 30 WHERE `pet_type` = 5; -- Pig -> Baby Pig
-- Set non-breedable pets to -1
UPDATE `pet_actions` SET `offspring_type` = -1 WHERE `pet_type` NOT IN (0, 1, 3, 4, 5);
-- =====================================================
-- Fix any items_base with leading/trailing spaces in interaction_type
-- =====================================================
UPDATE `items_base` SET `interaction_type` = TRIM(`interaction_type`);
-- =====================================================
-- Ensure breeding nest items have correct interaction_type
-- =====================================================
UPDATE `items_base` SET `interaction_type` = 'breeding_nest'
WHERE `item_name` LIKE 'pet_breeding_%' AND `interaction_type` != 'breeding_nest';
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.eu.habbo</groupId>
<artifactId>Habbo</artifactId>
<version>4.0.2</version>
<version>4.0.3</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -38,7 +38,7 @@ public final class Emulator {
public final static int MAJOR = 4;
public final static int MINOR = 0;
public final static int BUILD = 1;
public final static int BUILD = 3;
public final static String PREVIEW = "";
public static final String version = "Arcturus Morningstar" + " " + MAJOR + "." + MINOR + "." + BUILD + " " + PREVIEW;
@@ -21,8 +21,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.*;
public class BattleBanzaiGame extends Game {
private static final Logger LOGGER = LoggerFactory.getLogger(BattleBanzaiGame.class);
@@ -39,9 +38,29 @@ public class BattleBanzaiGame extends Game {
public static final int POINTS_LOCK_TILE = Emulator.getConfig().getInt("hotel.banzai.points.tile.lock", 1);
private static final ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(Emulator.getConfig().getInt("hotel.banzai.fill.threads", 2));
/**
* Maximum number of pending flood-fill tasks allowed in the queue.
* This prevents memory exhaustion from rapid tile locking via wireds.
*/
private static final int MAX_PENDING_FILL_TASKS = Emulator.getConfig().getInt("hotel.banzai.fill.max_queue", 50);
/**
* Minimum interval in milliseconds between flood-fill calculations for the same game.
* This prevents abuse via rapid wired triggering.
*/
private static final int FLOOD_FILL_COOLDOWN_MS = Emulator.getConfig().getInt("hotel.banzai.fill.cooldown_ms", 100);
private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(
Emulator.getConfig().getInt("hotel.banzai.fill.threads", 2),
Emulator.getConfig().getInt("hotel.banzai.fill.threads", 2),
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(MAX_PENDING_FILL_TASKS),
new ThreadPoolExecutor.DiscardOldestPolicy() // Drop oldest task when queue is full
);
private final THashMap<GameTeamColors, THashSet<HabboItem>> lockedTiles;
private final THashMap<Integer, HabboItem> gameTiles;
private volatile long lastFloodFillTime = 0;
private int tileCount;
private int countDown;
private int countDown2;
@@ -267,13 +286,27 @@ public class BattleBanzaiGame extends Game {
if (doNotCheckFill) return;
// Rate limit flood-fill calculations to prevent memory exhaustion from rapid wired triggering
long now = System.currentTimeMillis();
if (now - this.lastFloodFillTime < FLOOD_FILL_COOLDOWN_MS) {
return;
}
this.lastFloodFillTime = now;
// Check if executor queue is getting too full (additional safety check)
if (executor.getQueue().size() >= MAX_PENDING_FILL_TASKS - 5) {
LOGGER.warn("Battle Banzai flood-fill queue is nearly full, skipping calculation to prevent memory issues");
return;
}
final int x = item.getX();
final int y = item.getY();
final List<List<RoomTile>> filledAreas = new ArrayList<>();
final THashSet<HabboItem> lockedTiles = new THashSet<>(this.lockedTiles.get(teamColor));
executor.execute(() -> {
try {
executor.execute(() -> {
filledAreas.add(this.floodFill(x, y - 1, lockedTiles, new ArrayList<>(), teamColor));
filledAreas.add(this.floodFill(x, y + 1, lockedTiles, new ArrayList<>(), teamColor));
filledAreas.add(this.floodFill(x - 1, y, lockedTiles, new ArrayList<>(), teamColor));
@@ -299,6 +332,9 @@ public class BattleBanzaiGame extends Game {
}
}
});
} catch (RejectedExecutionException e) {
LOGGER.warn("Battle Banzai flood-fill task rejected - queue is full");
}
}
}
@@ -8,13 +8,14 @@ import com.eu.habbo.habbohotel.pets.PetManager;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.RideablePet;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomTile;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.rooms.RoomUnitType;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.rooms.pets.PetPackageNameValidationComposer;
import com.eu.habbo.messages.outgoing.rooms.pets.breeding.PetBreedingCompleted;
import com.eu.habbo.messages.outgoing.rooms.pets.breeding.PetBreedingResultComposer;
import com.eu.habbo.threading.runnables.QueryDeleteHabboItem;
import java.sql.ResultSet;
@@ -73,9 +74,11 @@ public class InteractionPetBreedingNest extends HabboItem {
Habbo ownerPetTwo = room.getHabbo(this.petTwo.getUserId());
if (ownerPetOne != null && ownerPetTwo != null && this.petOne.getPetData().getType() == this.petTwo.getPetData().getType() && this.petOne.getPetData().getOffspringType() != -1) {
ownerPetTwo.getClient().sendResponse(new PetBreedingResultComposer(this.getId(), this.petOne.getPetData().getOffspringType(), this.petOne, ownerPetOne.getHabboInfo().getUsername(), this.petTwo, ownerPetTwo.getHabboInfo().getUsername()));
this.setExtradata("1");
room.updateItem(this);
// Auto-breed with generated name (client doesn't have breeding dialog)
String babyName = generateBabyName(this.petOne.getName(), this.petTwo.getName());
this.breed(ownerPetTwo, babyName, this.petOne.getId(), this.petTwo.getId());
} else {
this.freePets();
}
}
}
@@ -114,6 +117,25 @@ public class InteractionPetBreedingNest extends HabboItem {
return false;
}
/**
* Allow pets to walk onto this tile even if another pet is already on it.
* This is required because breeding nests are 1x1 and need 2 pets to breed.
*/
@Override
public boolean canOverrideTile(RoomUnit unit, Room room, RoomTile tile) {
// Only allow override for pets when the box isn't full yet
if (unit.getRoomUnitType() == RoomUnitType.PET && !this.boxFull()) {
Pet pet = room.getPet(unit);
if (pet != null) {
// Make sure it's the right pet type for this nest
if (pet.getPetData() != null && pet.getPetData().getOffspringType() != -1) {
return true;
}
}
}
return false;
}
public void stopBreeding(Habbo habbo) {
this.setExtradata("0");
habbo.getHabboInfo().getCurrentRoom().updateItem(this);
@@ -146,6 +168,27 @@ public class InteractionPetBreedingNest extends HabboItem {
}
}
private String generateBabyName(String nameOne, String nameTwo) {
// Take first half of parent 1's name and second half of parent 2's name
int mid1 = Math.max(1, nameOne.length() / 2);
int mid2 = nameTwo.length() / 2;
String part1 = nameOne.substring(0, mid1);
String part2 = nameTwo.substring(mid2);
String combined = part1 + part2;
// Ensure name is between 1 and 15 characters
if (combined.length() > 15) {
combined = combined.substring(0, 15);
}
if (combined.isEmpty()) {
combined = "Baby";
}
return combined;
}
public void breed(Habbo habbo, String name, int petOneId, int petTwoId) {
Emulator.getThreading().run(new QueryDeleteHabboItem(this.getId()));
@@ -260,6 +260,17 @@ public class MonsterplantPet extends Pet implements IPetLook {
public void setDeathTimestamp(int deathTimestamp) {
this.deathTimestamp = deathTimestamp;
this.needsUpdate = true;
}
/**
* Revives a dead monster plant, resetting its death timestamp and hasDied flag.
* Call this instead of just setDeathTimestamp when reviving with mnstr_revival.
*/
public void revive() {
this.deathTimestamp = Emulator.getIntUnixTimestamp() + MonsterplantPet.timeToLive;
this.hasDied = false; // Reset so achievement can trigger again if plant dies again
this.needsUpdate = true;
}
public int getGrowthStage() {
@@ -284,6 +295,7 @@ public class MonsterplantPet extends Pet implements IPetLook {
public void setCanBreed(boolean canBreed) {
this.canBreed = canBreed;
this.needsUpdate = true;
}
public boolean breedable() {
@@ -296,15 +308,28 @@ public class MonsterplantPet extends Pet implements IPetLook {
public void setPubliclyBreedable(boolean isPubliclyBreedable) {
this.publiclyBreedable = isPubliclyBreedable;
this.needsUpdate = true;
}
public void breed(MonsterplantPet pet) {
// Validate both plants can breed
if (!this.breedable() || !pet.breedable()) {
return;
}
if (this.canBreed && pet.canBreed) {
this.canBreed = false;
this.publiclyBreedable = false;
this.needsUpdate = true;
pet.setCanBreed(false);
pet.setPubliclyBreedable(false);
// pet.needsUpdate is set by setCanBreed and setPubliclyBreedable
// Persist changes to database
Emulator.getThreading().run(this);
Emulator.getThreading().run(pet);
this.room.sendComposer(new PetStatusUpdateComposer(pet).compose());
this.room.sendComposer(new PetStatusUpdateComposer(this).compose());
@@ -387,6 +412,8 @@ public class MonsterplantPet extends Pet implements IPetLook {
this.setDeathTimestamp(Emulator.getIntUnixTimestamp() + MonsterplantPet.timeToLive);
this.addHappiness(10);
this.addExperience(10);
// needsUpdate is set by setDeathTimestamp, persist to database
Emulator.getThreading().run(this);
this.room.sendComposer(new PetStatusUpdateComposer(this).compose());
this.room.sendComposer(new RoomPetRespectComposer(this, RoomPetRespectComposer.PET_TREATED).compose());
}
@@ -122,15 +122,29 @@ public class PetManager {
}
public static int randomBody(int minimumRarity, boolean isRare) {
int randomRarity = isRare ? random(Math.max(minimumRarity - 1, 0), (MonsterplantPet.bodyRarity.size() - minimumRarity) + (minimumRarity - 1), 2.0) : random(Math.max(minimumRarity - 1, 0), MonsterplantPet.bodyRarity.size(), 2.0);
int maxIndex = MonsterplantPet.bodyRarity.size();
int randomIndex = isRare
? random(Math.max(minimumRarity - 1, 0), Math.min(maxIndex, (maxIndex - minimumRarity) + minimumRarity), 2.0)
: random(Math.max(minimumRarity - 1, 0), maxIndex, 2.0);
// Clamp to valid range to prevent ArrayIndexOutOfBoundsException
randomIndex = Math.max(0, Math.min(randomIndex, maxIndex - 1));
return MonsterplantPet.bodyRarity.get(MonsterplantPet.bodyRarity.keySet().toArray()[randomRarity]).getValue();
// Return the body type KEY (1-12), not the rarity value
return (Integer) MonsterplantPet.bodyRarity.keySet().toArray()[randomIndex];
}
public static int randomColor(int minimumRarity, boolean isRare) {
int randomRarity = isRare ? random(Math.max(minimumRarity - 1, 0), (MonsterplantPet.colorRarity.size() - minimumRarity) + (minimumRarity - 1), 2.0) : random(Math.max(minimumRarity - 1, 0), MonsterplantPet.colorRarity.size(), 2.0);
int maxIndex = MonsterplantPet.colorRarity.size();
int randomIndex = isRare
? random(Math.max(minimumRarity - 1, 0), Math.min(maxIndex, (maxIndex - minimumRarity) + minimumRarity), 2.0)
: random(Math.max(minimumRarity - 1, 0), maxIndex, 2.0);
// Clamp to valid range to prevent ArrayIndexOutOfBoundsException
randomIndex = Math.max(0, Math.min(randomIndex, maxIndex - 1));
return MonsterplantPet.colorRarity.get(MonsterplantPet.colorRarity.keySet().toArray()[randomRarity]).getValue();
// Return the color hue KEY (0-10), not the rarity value
return (Integer) MonsterplantPet.colorRarity.keySet().toArray()[randomIndex];
}
public static int random(int low, int high, double bias) {
@@ -2,24 +2,54 @@ package com.eu.habbo.messages.incoming.rooms.pets;
import com.eu.habbo.habbohotel.pets.MonsterplantPet;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.messages.incoming.MessageHandler;
public class BreedMonsterplantsEvent extends MessageHandler {
@Override
public void handle() throws Exception {
int unknownInt = this.packet.readInt(); //Something state. 2 = accept
int unknownInt = this.packet.readInt(); //Something state. 0 = initiate breeding
if (unknownInt == 0) {
Pet petOne = this.client.getHabbo().getHabboInfo().getCurrentRoom().getPet(this.packet.readInt());
Pet petTwo = this.client.getHabbo().getHabboInfo().getCurrentRoom().getPet(this.packet.readInt());
Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom();
if (room == null) return;
Pet petOne = room.getPet(this.packet.readInt());
Pet petTwo = room.getPet(this.packet.readInt());
if (petOne == null || petTwo == null || petOne == petTwo) {
//TODO Add error
return;
}
if (petOne instanceof MonsterplantPet && petTwo instanceof MonsterplantPet) {
((MonsterplantPet) petOne).breed((MonsterplantPet) petTwo);
MonsterplantPet plantOne = (MonsterplantPet) petOne;
MonsterplantPet plantTwo = (MonsterplantPet) petTwo;
// Validate both plants are breedable (fully grown, can breed, not dead)
if (!plantOne.breedable() || !plantTwo.breedable()) {
return;
}
// Validate ownership - at least one plant must belong to the client
// and the other must be publicly breedable or owned by client
int clientId = this.client.getHabbo().getHabboInfo().getId();
boolean ownsOne = plantOne.getUserId() == clientId;
boolean ownsTwo = plantTwo.getUserId() == clientId;
if (!ownsOne && !ownsTwo) {
// Client doesn't own either plant
return;
}
// If client doesn't own one of them, that one must be publicly breedable
if (!ownsOne && !plantOne.isPubliclyBreedable()) {
return;
}
if (!ownsTwo && !plantTwo.isPubliclyBreedable()) {
return;
}
plantOne.breed(plantTwo);
}
}
}
@@ -23,6 +23,8 @@ public class CompostMonsterplantEvent extends MessageHandler {
int petId = this.packet.readInt();
Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom();
if (room == null) return;
Pet pet = room.getPet(petId);
if (pet != null) {
@@ -89,12 +89,23 @@ public class PetUseItemEvent extends MessageHandler {
Emulator.getGameEnvironment().getItemManager().deleteItem(item);
}
} else if (pet instanceof MonsterplantPet) {
// Validate ownership - only owner can use items on their plant
if (pet.getUserId() != this.client.getHabbo().getHabboInfo().getId()) {
return;
}
MonsterplantPet monsterplant = (MonsterplantPet) pet;
if (item.getBaseItem().getName().equalsIgnoreCase("mnstr_revival")) {
if (((MonsterplantPet) pet).isDead()) {
((MonsterplantPet) pet).setDeathTimestamp(Emulator.getIntUnixTimestamp() + MonsterplantPet.timeToLive);
if (monsterplant.isDead()) {
// Use revive() method which properly resets hasDied flag and sets needsUpdate
monsterplant.revive();
pet.getRoomUnit().clearStatus();
pet.getRoomUnit().setStatus(RoomUnitStatus.GESTURE, "rev");
((MonsterplantPet) pet).packetUpdate = true;
monsterplant.packetUpdate = true;
// Persist to database
Emulator.getThreading().run(pet);
this.client.getHabbo().getHabboInfo().getCurrentRoom().removeHabboItem(item);
this.client.getHabbo().getHabboInfo().getCurrentRoom().sendComposer(new RemoveFloorItemComposer(item).compose());
@@ -106,12 +117,17 @@ public class PetUseItemEvent extends MessageHandler {
Emulator.getThreading().run(new QueryDeleteHabboItem(item.getId()));
}
} else if (item.getBaseItem().getName().equalsIgnoreCase("mnstr_fert")) {
if (!((MonsterplantPet) pet).isFullyGrown()) {
if (!monsterplant.isFullyGrown()) {
pet.setCreated(pet.getCreated() - MonsterplantPet.growTime);
pet.needsUpdate = true;
pet.getRoomUnit().clearStatus();
pet.cycle();
pet.getRoomUnit().setStatus(RoomUnitStatus.GESTURE, "spd");
pet.getRoomUnit().setStatus(RoomUnitStatus.fromString("grw" + ((MonsterplantPet) pet).getGrowthStage()), "");
pet.getRoomUnit().setStatus(RoomUnitStatus.fromString("grw" + monsterplant.getGrowthStage()), "");
// Persist to database
Emulator.getThreading().run(pet);
this.client.getHabbo().getHabboInfo().getCurrentRoom().removeHabboItem(item);
this.client.getHabbo().getHabboInfo().getCurrentRoom().sendComposer(new RemoveFloorItemComposer(item).compose());
this.client.getHabbo().getHabboInfo().getCurrentRoom().sendComposer(new RoomUserStatusComposer(pet.getRoomUnit()).compose());
@@ -122,17 +138,20 @@ public class PetUseItemEvent extends MessageHandler {
Emulator.getThreading().run(new QueryDeleteHabboItem(item.getId()));
}
} else if (item.getBaseItem().getName().startsWith("mnstr_rebreed")) {
if (((MonsterplantPet) pet).isFullyGrown() && !((MonsterplantPet) pet).canBreed()) {
if (
(item.getBaseItem().getName().equalsIgnoreCase("mnstr_rebreed") && ((MonsterplantPet) pet).getRarity() <= 5) ||
(item.getBaseItem().getName().equalsIgnoreCase("mnstr_rebreed_2") && ((MonsterplantPet) pet).getRarity() >= 6 && ((MonsterplantPet) pet).getRarity() <= 8) ||
(item.getBaseItem().getName().equalsIgnoreCase("mnstr_rebreed_3") && ((MonsterplantPet) pet).getRarity() >= 9)
)
if (monsterplant.isFullyGrown() && !monsterplant.canBreed() && !monsterplant.isDead()) {
boolean validItem =
(item.getBaseItem().getName().equalsIgnoreCase("mnstr_rebreed") && monsterplant.getRarity() <= 5) ||
(item.getBaseItem().getName().equalsIgnoreCase("mnstr_rebreed_2") && monsterplant.getRarity() >= 6 && monsterplant.getRarity() <= 8) ||
(item.getBaseItem().getName().equalsIgnoreCase("mnstr_rebreed_3") && monsterplant.getRarity() >= 9);
{
((MonsterplantPet) pet).setCanBreed(true);
if (validItem) {
// setCanBreed now automatically sets needsUpdate
monsterplant.setCanBreed(true);
pet.getRoomUnit().clearStatus();
pet.getRoomUnit().setStatus(RoomUnitStatus.GESTURE, "reb");
// Persist to database
Emulator.getThreading().run(pet);
this.client.getHabbo().getHabboInfo().getCurrentRoom().removeHabboItem(item);
this.client.getHabbo().getHabboInfo().getCurrentRoom().sendComposer(new RemoveFloorItemComposer(item).compose());
@@ -1,20 +1,39 @@
package com.eu.habbo.messages.incoming.rooms.pets;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.pets.MonsterplantPet;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.rooms.pets.PetStatusUpdateComposer;
public class ToggleMonsterplantBreedableEvent extends MessageHandler {
@Override
public void handle() throws Exception {
int petId = this.packet.readInt();
Pet pet = this.client.getHabbo().getHabboInfo().getCurrentRoom().getPet(petId);
Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom();
if (room == null) return;
Pet pet = room.getPet(petId);
if (pet != null) {
if (pet.getUserId() == this.client.getHabbo().getHabboInfo().getId()) {
if (pet instanceof MonsterplantPet) {
((MonsterplantPet) pet).setPubliclyBreedable(((MonsterplantPet) pet).isPubliclyBreedable());
MonsterplantPet monsterplant = (MonsterplantPet) pet;
// Only allow toggling if plant is breedable (fully grown, can breed, not dead)
if (monsterplant.breedable()) {
// Toggle the publicly breedable state (was previously setting to same value - bug fix)
monsterplant.setPubliclyBreedable(!monsterplant.isPubliclyBreedable());
// Mark for database update
monsterplant.needsUpdate = true;
Emulator.getThreading().run(monsterplant);
// Send status update to room
room.sendComposer(new PetStatusUpdateComposer(monsterplant).compose());
}
}
}
}
@@ -56,6 +56,9 @@ class FreezeHandleSnowballExplosion implements Runnable {
for (RoomTile roomTile : tiles) {
THashSet<HabboItem> items = this.thrownData.room.getItemsAt(roomTile);
// Track if we already processed a block at this tile to prevent stacking exploit
boolean blockProcessedAtTile = false;
for (HabboItem freezeTile : items) {
if (freezeTile instanceof InteractionFreezeTile || freezeTile instanceof InteractionFreezeBlock) {
int distance = 0;
@@ -93,9 +96,12 @@ class FreezeHandleSnowballExplosion implements Runnable {
}
}
} else if (freezeTile instanceof InteractionFreezeBlock) {
if (freezeTile.getExtradata().equalsIgnoreCase("0")) {
// Only process ONE block per tile to prevent stacking exploit
// Stacking many blocks and exploding them causes massive lag
if (!blockProcessedAtTile && freezeTile.getExtradata().equalsIgnoreCase("0")) {
game.explodeBox((InteractionFreezeBlock) freezeTile, distance * 100);
player.addScore(FreezeGame.DESTROY_BLOCK_POINTS);
blockProcessedAtTile = true;
}
}
}