🆙 Update to 4.0.2

This commit is contained in:
duckietm
2026-01-09 09:41:22 +01:00
parent ad2c52d3af
commit c650e411da
66 changed files with 3187 additions and 220 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.eu.habbo</groupId>
<artifactId>Habbo</artifactId>
<version>4.0.1</version>
<version>4.0.2</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -3,7 +3,10 @@ package com.eu.habbo.habbohotel.items;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.items.interactions.*;
import com.eu.habbo.habbohotel.items.interactions.games.InteractionGameTimer;
import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.*;
import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.InteractionBattleBanzaiPuck;
import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.InteractionBattleBanzaiSphere;
import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.InteractionBattleBanzaiTeleporter;
import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.InteractionBattleBanzaiTile;
import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.gates.InteractionBattleBanzaiGateBlue;
import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.gates.InteractionBattleBanzaiGateGreen;
import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.gates.InteractionBattleBanzaiGateRed;
@@ -48,10 +51,10 @@ import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredBlob;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraOrEval;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRandom;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraUnseen;
import com.eu.habbo.habbohotel.wired.highscores.WiredHighscoreManager;
import com.eu.habbo.habbohotel.items.interactions.wired.triggers.*;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.habbohotel.wired.highscores.WiredHighscoreManager;
import com.eu.habbo.messages.outgoing.inventory.AddHabboItemComposer;
import com.eu.habbo.plugin.events.emulator.EmulatorLoadItemsManagerEvent;
import com.eu.habbo.threading.runnables.QueryDeleteHabboItem;
@@ -134,6 +137,8 @@ public class ItemManager {
this.interactionsList.add(new ItemInteraction("pet_drink", InteractionPetDrink.class));
this.interactionsList.add(new ItemInteraction("pet_food", InteractionPetFood.class));
this.interactionsList.add(new ItemInteraction("pet_toy", InteractionPetToy.class));
this.interactionsList.add(new ItemInteraction("pet_tree", InteractionPetTree.class));
this.interactionsList.add(new ItemInteraction("pet_trampoline", InteractionPetTrampoline.class));
this.interactionsList.add(new ItemInteraction("breeding_nest", InteractionPetBreedingNest.class));
this.interactionsList.add(new ItemInteraction("obstacle", InteractionObstacle.class));
this.interactionsList.add(new ItemInteraction("monsterplant_seed", InteractionMonsterPlantSeed.class));
@@ -121,9 +121,9 @@ public class InteractionPetBreedingNest extends HabboItem {
if (this.petOne != null) {
habbo.getClient().sendResponse(new PetPackageNameValidationComposer(this.getId(), PetPackageNameValidationComposer.CLOSE_WIDGET, ""));
}
if (this.petTwo.getUserId() != habbo.getHabboInfo().getId()) {
Habbo owner = this.petTwo.getRoom().getHabbo(this.petTwo.getUserId());
if (owner != null) {
if (this.petTwo != null && this.petTwo.getUserId() != habbo.getHabboInfo().getId()) {
Habbo owner = this.petTwo.getRoom() != null ? this.petTwo.getRoom().getHabbo(this.petTwo.getUserId()) : null;
if (owner != null && owner.getClient() != null) {
owner.getClient().sendResponse(new PetPackageNameValidationComposer(this.getId(), PetPackageNameValidationComposer.CLOSE_WIDGET, ""));
}
}
@@ -2,22 +2,27 @@ package com.eu.habbo.habbohotel.items.interactions.pets;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.achievements.AchievementManager;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionDefault;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.pets.RideablePet;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.rooms.RoomUserRotation;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer;
import com.eu.habbo.habbohotel.rooms.*;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.threading.runnables.PetClearPosture;
import com.eu.habbo.threading.runnables.RoomUnitWalkToLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class InteractionPetDrink extends InteractionDefault {
private static final Logger LOGGER = LoggerFactory.getLogger(InteractionPetDrink.class);
public InteractionPetDrink(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
@@ -26,32 +31,81 @@ public class InteractionPetDrink extends InteractionDefault {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
public boolean canToggle(Habbo habbo, Room room) {
return RoomLayout.tilesAdjecent(room.getLayout().getTile(this.getX(), this.getY()), habbo.getRoomUnit().getCurrentLocation());
}
@Override
public void onClick(GameClient client, Room room, Object[] objects) throws Exception {
if (client == null)
return;
if (!this.canToggle(client.getHabbo(), room)) {
RoomTile closestTile = null;
for (RoomTile tile : room.getLayout().getTilesAround(room.getLayout().getTile(this.getX(), this.getY()))) {
if (tile.isWalkable() && (closestTile == null || closestTile.distance(client.getHabbo().getRoomUnit().getCurrentLocation()) > tile.distance(client.getHabbo().getRoomUnit().getCurrentLocation()))) {
closestTile = tile;
}
}
if (closestTile != null && !closestTile.equals(client.getHabbo().getRoomUnit().getCurrentLocation())) {
List<Runnable> onSuccess = new ArrayList<>();
onSuccess.add(() -> {
this.change(room, this.getBaseItem().getStateCount() - 1);
});
client.getHabbo().getRoomUnit().setGoalLocation(closestTile);
Emulator.getThreading().run(new RoomUnitWalkToLocation(client.getHabbo().getRoomUnit(), closestTile, room, onSuccess, new ArrayList<>()));
}
}
}
@Override
public void onWalkOn(RoomUnit roomUnit, Room room, Object[] objects) throws Exception {
super.onWalkOn(roomUnit, room, objects);
if (this.getExtradata() == null || this.getExtradata().isEmpty())
this.setExtradata("0");
// Check if there's water left (state 0 = full, higher = less water)
int currentState = 0;
try {
currentState = Integer.parseInt(this.getExtradata());
} catch (NumberFormatException e) {
currentState = 0;
}
// If water bowl is empty (state >= max states), don't allow drinking
if (currentState >= this.getBaseItem().getStateCount() - 1) {
return;
}
Pet pet = room.getPet(roomUnit);
if (pet != null) {
// Don't let ridden pets drink
if (pet instanceof RideablePet && ((RideablePet) pet).getRider() != null)
return;
if (pet != null && !(pet instanceof RideablePet && ((RideablePet) pet).getRider() != null)
&& pet.getPetData().haveDrinkItem(this) && pet.levelThirst >= 35) {
pet.clearPosture();
pet.getRoomUnit().setGoalLocation(room.getLayout().getTile(this.getX(), this.getY()));
pet.getRoomUnit().setRotation(RoomUserRotation.values()[this.getRotation()]);
pet.getRoomUnit().clearStatus();
pet.getRoomUnit().setStatus(RoomUnitStatus.EAT, pet.getRoomUnit().getCurrentLocation().getStackHeight() + "");
pet.packetUpdate = true;
// Say drinking vocal
pet.say(pet.getPetData().randomVocal(PetVocalsType.DRINKING));
if (pet.getPetData().haveDrinkItem(this)) {
if (pet.levelThirst >= 35) {
pet.setTask(PetTasks.EAT);
pet.getRoomUnit().setGoalLocation(room.getLayout().getTile(this.getX(), this.getY()));
pet.getRoomUnit().setRotation(RoomUserRotation.values()[this.getRotation()]);
pet.getRoomUnit().clearStatus();
pet.getRoomUnit().removeStatus(RoomUnitStatus.MOVE);
pet.getRoomUnit().setStatus(RoomUnitStatus.EAT, "0");
pet.addThirst(-75);
room.sendComposer(new RoomUserStatusComposer(roomUnit).compose());
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.EAT, null, true), 500);
// Faster drinking - 500ms instead of 1000ms
Emulator.getThreading().run(() -> {
pet.addThirst(-75);
// Increase state to show less water (+1, not -1)
this.change(room, 1);
pet.getRoomUnit().clearStatus();
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.EAT, null, true), 0);
pet.packetUpdate = true;
}, 500);
AchievementManager.progressAchievement(Emulator.getGameEnvironment().getHabboManager().getHabbo(pet.getUserId()), Emulator.getGameEnvironment().getAchievementManager().getAchievement("PetFeeding"), 75);
}
}
AchievementManager.progressAchievement(Emulator.getGameEnvironment().getHabboManager().getHabbo(pet.getUserId()), Emulator.getGameEnvironment().getAchievementManager().getAchievement("PetFeeding"), 75);
}
}
@@ -59,4 +113,31 @@ public class InteractionPetDrink extends InteractionDefault {
public boolean allowWiredResetState() {
return false;
}
private void change(Room room, int amount) {
int state = 0;
if (this.getExtradata() == null || this.getExtradata().isEmpty()) {
this.setExtradata("0");
}
try {
state = Integer.parseInt(this.getExtradata());
} catch (Exception e) {
LOGGER.error("Caught exception", e);
}
state += amount;
if (state > this.getBaseItem().getStateCount() - 1) {
state = this.getBaseItem().getStateCount() - 1;
}
if (state < 0) {
state = 0;
}
this.setExtradata(state + "");
this.needsUpdate(true);
room.updateItemState(this);
}
}
@@ -32,6 +32,19 @@ public class InteractionPetFood extends InteractionDefault {
if (this.getExtradata().length() == 0)
this.setExtradata("0");
// Check if there's food left (state < stateCount means food remaining)
int currentState = 0;
try {
currentState = Integer.parseInt(this.getExtradata());
} catch (NumberFormatException e) {
currentState = 0;
}
// If food is empty (state >= max states), don't allow eating
if (currentState >= this.getBaseItem().getStateCount()) {
return;
}
Pet pet = room.getPet(roomUnit);
if (pet != null) {
@@ -1,15 +1,13 @@
package com.eu.habbo.habbohotel.items.interactions.pets;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionDefault;
import com.eu.habbo.habbohotel.pets.Pet;
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.RoomUnit;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.rooms.RoomUserRotation;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.*;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.threading.runnables.PetClearPosture;
@@ -27,17 +25,41 @@ public class InteractionPetToy extends InteractionDefault {
this.setExtradata("0");
}
@Override
public void onClick(GameClient client, Room room, Object[] objects) {
// Toys are not clickable by users
}
@Override
public void onMove(Room room, RoomTile oldLocation, RoomTile newLocation) {
this.setExtradata("0");
room.updateItem(this);
for (Pet pet : room.getPetsAt(oldLocation)) {
pet.getRoomUnit().clearStatus();
pet.packetUpdate = true;
}
}
@Override
public void onPickUp(Room room) {
this.setExtradata("0");
for (RoomTile tile : this.getOccupyingTiles(room.getLayout())) {
for (Pet pet : room.getPetsAt(tile)) {
pet.getRoomUnit().clearStatus();
pet.packetUpdate = true;
}
}
}
@Override
public void onWalkOn(RoomUnit roomUnit, Room room, Object[] objects) throws Exception {
super.onWalkOn(roomUnit, room, objects);
Pet pet = room.getPet(roomUnit);
if (pet != null) {
// Don't let ridden pets play with toys
if (pet instanceof RideablePet && ((RideablePet) pet).getRider() != null)
return;
if (pet != null && pet.getPetData().haveToyItem(this.getBaseItem())) {
if (pet.getEnergy() <= 35) {
return;
}
@@ -46,15 +68,20 @@ public class InteractionPetToy extends InteractionDefault {
pet.getRoomUnit().setGoalLocation(room.getLayout().getTile(this.getX(), this.getY()));
pet.getRoomUnit().setRotation(RoomUserRotation.values()[this.getRotation()]);
pet.getRoomUnit().clearStatus();
pet.getRoomUnit().removeStatus(RoomUnitStatus.MOVE);
pet.getRoomUnit().setStatus(RoomUnitStatus.PLAY, "0");
pet.getRoomUnit().setStatus(RoomUnitStatus.PLAY, pet.getRoomUnit().getCurrentLocation().getStackHeight() + "");
pet.packetUpdate = true;
// Say playful vocal
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
HabboItem item = this;
Emulator.getThreading().run(() -> {
pet.addHappiness(25);
item.setExtradata("0");
room.updateItem(item);
pet.getRoomUnit().clearStatus();
new PetClearPosture(pet, RoomUnitStatus.PLAY, null, true).run();
pet.packetUpdate = true;
}, 2500 + (Emulator.getRandom().nextInt(20) * 500));
this.setExtradata("1");
room.updateItemState(this);
@@ -69,10 +96,18 @@ public class InteractionPetToy extends InteractionDefault {
if (pet != null) {
this.setExtradata("0");
room.updateItemState(this);
room.updateItem(this);
pet.getRoomUnit().clearStatus();
pet.packetUpdate = true;
}
}
@Override
public boolean canWalkOn(RoomUnit roomUnit, Room room, Object[] objects) {
Pet pet = room.getPet(roomUnit);
return roomUnit.getRoomUnitType() == RoomUnitType.PET && pet != null && pet.getPetData().haveToyItem(this.getBaseItem());
}
@Override
public boolean allowWiredResetState() {
return false;
@@ -0,0 +1,106 @@
package com.eu.habbo.habbohotel.items.interactions.pets;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionDefault;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.rooms.*;
import com.eu.habbo.threading.runnables.PetClearPosture;
import java.sql.ResultSet;
import java.sql.SQLException;
public class InteractionPetTrampoline extends InteractionDefault {
public InteractionPetTrampoline(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
this.setExtradata("0");
}
public InteractionPetTrampoline(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
this.setExtradata("0");
}
@Override
public void onClick(GameClient client, Room room, Object[] objects) {
// Trampolines are not clickable by users
}
@Override
public void onMove(Room room, RoomTile oldLocation, RoomTile newLocation) {
this.setExtradata("0");
room.updateItem(this);
for (Pet pet : room.getPetsAt(oldLocation)) {
pet.getRoomUnit().removeStatus(RoomUnitStatus.JUMP);
pet.packetUpdate = true;
}
}
@Override
public void onPickUp(Room room) {
this.setExtradata("0");
for (Pet pet : room.getPetsAt(room.getLayout().getTile(this.getX(), this.getY()))) {
pet.getRoomUnit().removeStatus(RoomUnitStatus.JUMP);
pet.packetUpdate = true;
}
}
@Override
public void onWalkOn(RoomUnit roomUnit, Room room, Object[] objects) throws Exception {
super.onWalkOn(roomUnit, room, objects);
Pet pet = room.getPet(roomUnit);
if (pet != null && pet.getPetData().haveToyItem(this.getBaseItem()) && this.getOccupyingTiles(room.getLayout()).contains(pet.getRoomUnit().getGoal())) {
if (pet.getEnergy() <= 35) {
return;
}
pet.clearPosture();
pet.setTask(PetTasks.JUMP);
pet.getRoomUnit().setStatus(RoomUnitStatus.JUMP, "");
pet.packetUpdate = true;
Emulator.getThreading().run(() -> {
new PetClearPosture(pet, RoomUnitStatus.JUMP, null, false);
pet.getRoomUnit().setGoalLocation(room.getRandomWalkableTile());
this.setExtradata("0");
room.updateItemState(this);
}, 4000);
pet.addHappiness(25);
this.setExtradata("1");
room.updateItemState(this);
}
}
@Override
public void onWalkOff(RoomUnit roomUnit, Room room, Object[] objects) throws Exception {
super.onWalkOff(roomUnit, room, objects);
Pet pet = room.getPet(roomUnit);
if (pet != null) {
this.setExtradata("0");
room.updateItem(this);
pet.getRoomUnit().removeStatus(RoomUnitStatus.JUMP);
pet.packetUpdate = true;
}
}
@Override
public boolean canWalkOn(RoomUnit roomUnit, Room room, Object[] objects) {
Pet pet = room.getPet(roomUnit);
return roomUnit.getRoomUnitType() == RoomUnitType.PET && pet != null && pet.getPetData().haveToyItem(this.getBaseItem());
}
@Override
public boolean allowWiredResetState() {
return false;
}
}
@@ -0,0 +1,126 @@
package com.eu.habbo.habbohotel.items.interactions.pets;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionDefault;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.*;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Interaction for pet trees (dragon tree, monkey tree, etc.)
* Pets can hang from these and perform special actions like Ring of Fire.
*/
public class InteractionPetTree extends InteractionDefault {
public InteractionPetTree(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
this.setExtradata("0");
}
public InteractionPetTree(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
this.setExtradata("0");
}
@Override
public void onClick(GameClient client, Room room, Object[] objects) {
// Trees are not clickable by users
}
@Override
public void onMove(Room room, RoomTile oldLocation, RoomTile newLocation) {
this.setExtradata("0");
room.updateItem(this);
for (Pet pet : room.getPetsAt(oldLocation)) {
pet.getRoomUnit().removeStatus(RoomUnitStatus.HANG);
pet.getRoomUnit().removeStatus(RoomUnitStatus.SWING);
pet.getRoomUnit().removeStatus(RoomUnitStatus.FLAME);
pet.packetUpdate = true;
}
}
@Override
public void onPickUp(Room room) {
this.setExtradata("0");
for (Pet pet : room.getPetsAt(room.getLayout().getTile(this.getX(), this.getY()))) {
pet.getRoomUnit().removeStatus(RoomUnitStatus.HANG);
pet.getRoomUnit().removeStatus(RoomUnitStatus.SWING);
pet.getRoomUnit().removeStatus(RoomUnitStatus.FLAME);
pet.packetUpdate = true;
}
}
@Override
public void onWalkOn(RoomUnit roomUnit, Room room, Object[] objects) throws Exception {
super.onWalkOn(roomUnit, room, objects);
Pet pet = room.getPet(roomUnit);
// Only dragons (type 12) can use the tree
if (pet != null && pet.getPetData().getType() == 12 && this.getOccupyingTiles(room.getLayout()).contains(pet.getRoomUnit().getGoal())) {
if (pet.getEnergy() <= 35) {
return;
}
RoomUnitStatus task = RoomUnitStatus.HANG;
switch (pet.getTask()) {
case RING_OF_FIRE:
task = RoomUnitStatus.RINGOFFIRE;
break;
case SWING:
task = RoomUnitStatus.SWING;
break;
default:
// Default to HANG for all other tasks
break;
}
// Pet arrived at tree - set hang status
pet.setTask(PetTasks.FREE);
pet.getRoomUnit().setGoalLocation(room.getLayout().getTile(this.getX(), this.getY()));
pet.getRoomUnit().setRotation(RoomUserRotation.values()[this.getRotation()]);
pet.getRoomUnit().clearStatus();
pet.getRoomUnit().setStatus(task, "");
pet.packetUpdate = true;
// Say playful vocal
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
this.setExtradata("1");
room.updateItemState(this);
}
}
@Override
public void onWalkOff(RoomUnit roomUnit, Room room, Object[] objects) throws Exception {
super.onWalkOff(roomUnit, room, objects);
Pet pet = room.getPet(roomUnit);
if (pet != null) {
this.setExtradata("0");
room.updateItem(this);
pet.getRoomUnit().removeStatus(RoomUnitStatus.HANG);
pet.getRoomUnit().removeStatus(RoomUnitStatus.SWING);
pet.getRoomUnit().removeStatus(RoomUnitStatus.FLAME);
pet.packetUpdate = true;
}
}
@Override
public boolean canWalkOn(RoomUnit roomUnit, Room room, Object[] objects) {
Pet pet = room.getPet(roomUnit);
return roomUnit.getRoomUnitType() == RoomUnitType.PET && pet != null;
}
@Override
public boolean allowWiredResetState() {
return false;
}
}
@@ -2,6 +2,9 @@ package com.eu.habbo.habbohotel.pets;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.achievements.AchievementManager;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetToy;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetTree;
import com.eu.habbo.habbohotel.rooms.*;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
@@ -14,6 +17,7 @@ import com.eu.habbo.messages.outgoing.rooms.users.RoomUserRemoveComposer;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserTalkComposer;
import com.eu.habbo.plugin.events.pets.PetTalkEvent;
import gnu.trove.map.hash.THashMap;
import gnu.trove.set.hash.THashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -52,6 +56,16 @@ public class Pet implements ISerialize, Runnable {
private int stayStartedAt = 0;
private int idleCommandTicks = 0;
private int freeCommandTicks = -1;
// Command cooldown tracking to prevent spam
private int lastCommandId = -1;
private long lastCommandTime = 0;
private int sameCommandCount = 0;
// New managers for improved pet behavior
private PetStatsManager statsManager;
private PetBehaviorManager behaviorManager;
private PetTasks task = PetTasks.FREE;
@@ -78,6 +92,10 @@ public class Pet implements ISerialize, Runnable {
this.levelThirst = set.getInt("thirst");
this.levelHunger = set.getInt("hunger");
this.level = PetManager.getLevel(this.experience);
// Initialize managers
this.statsManager = new PetStatsManager(this);
this.behaviorManager = new PetBehaviorManager(this);
}
public Pet(int type, int race, String color, String name, int userId) {
@@ -101,6 +119,10 @@ public class Pet implements ISerialize, Runnable {
this.levelHunger = 0;
this.created = Emulator.getIntUnixTimestamp();
this.level = 1;
// Initialize managers
this.statsManager = new PetStatsManager(this);
this.behaviorManager = new PetBehaviorManager(this);
}
@@ -216,10 +238,15 @@ public class Pet implements ISerialize, Runnable {
}
public void cycle() {
// Guard clause for null room or roomUnit
if (this.room == null || this.roomUnit == null) {
return;
}
this.idleCommandTicks++;
int time = Emulator.getIntUnixTimestamp();
if (this.roomUnit != null && this.task != PetTasks.RIDE) {
if (this.task != PetTasks.RIDE) {
if (time - this.gestureTickTimeout > 5 && this.roomUnit.hasStatus(RoomUnitStatus.GESTURE)) {
this.roomUnit.removeStatus(RoomUnitStatus.GESTURE);
this.packetUpdate = true;
@@ -254,17 +281,28 @@ public class Pet implements ISerialize, Runnable {
if (this.levelThirst > 0)
this.levelThirst--;
this.addEnergy(5);
// Check if we're about to reach max energy before adding
int maxEnergy = PetManager.maxEnergy(this.level);
boolean wasResting = this.energy < maxEnergy;
// Nest gives faster regeneration than resting on floor
int energyGain = (this.task == PetTasks.NEST) ? 5 : 2;
this.addEnergy(energyGain);
this.addHappiness(1);
if (this.energy == PetManager.maxEnergy(this.level)) {
// Wake up when fully rested
if (wasResting && this.energy >= maxEnergy) {
this.roomUnit.removeStatus(RoomUnitStatus.LAY);
this.roomUnit.setCanWalk(true);
this.roomUnit.setGoalLocation(this.room.getRandomWalkableTile());
RoomTile tile = this.room.getRandomWalkableTile();
if (tile != null) {
this.roomUnit.setGoalLocation(tile);
}
this.task = null;
this.roomUnit.setStatus(RoomUnitStatus.GESTURE, PetGestures.ENERGY.getKey());
this.gestureTickTimeout = time;
this.say(this.petData.randomVocal(PetVocalsType.GENERIC_HAPPY));
}
} else if (this.tickTimeout >= 5) {
if (this.levelHunger < 100)
@@ -319,6 +357,13 @@ public class Pet implements ISerialize, Runnable {
this.say(this.petData.randomVocal(PetVocalsType.GENERIC_HAPPY));
} else if (this.happiness < 15) {
this.say(this.petData.randomVocal(PetVocalsType.GENERIC_SAD));
// When bored and has energy, try to find a toy to play with
if (this.energy > 40 && this.task == null) {
this.findToy();
}
} else if (this.happiness < 40 && this.energy > 50 && this.task == null && Emulator.getRandom().nextInt(100) < 30) {
// 30% chance to seek toy when moderately bored
this.findToy();
} else if (this.levelHunger > 50) {
this.say(this.petData.randomVocal(PetVocalsType.HUNGRY));
this.eat();
@@ -363,10 +408,13 @@ public class Pet implements ISerialize, Runnable {
case PLAY_FOOTBALL:
case PLAY_DEAD:
case FOLLOW:
case FOLLOW_LEFT:
case FOLLOW_RIGHT:
case JUMP:
case STAND:
case NEST:
case RIDE:
case STAY:
return false;
default:
break;
@@ -448,6 +496,9 @@ public class Pet implements ISerialize, Runnable {
public void findNest() {
if (this.room == null || this.room.getRoomSpecialTypes() == null || this.petData == null) {
return;
}
HabboItem item = this.petData.randomNest(this.room.getRoomSpecialTypes().getNests());
this.roomUnit.setCanWalk(true);
if (item != null) {
@@ -460,7 +511,37 @@ public class Pet implements ISerialize, Runnable {
}
/**
* Finds a suitable drink item for this pet in the current room.
* @return The drink Item if found, null otherwise
*/
public Item findDrink() {
if (this.room == null || this.room.getRoomSpecialTypes() == null || this.petData == null) {
return null;
}
HabboItem drinkItem = this.petData.randomDrinkItem(this.room.getRoomSpecialTypes().getPetDrinks());
return drinkItem != null ? drinkItem.getBaseItem() : null;
}
/**
* Finds a suitable food item for this pet in the current room.
* @return The food Item if found, null otherwise
*/
public Item findFood() {
if (this.room == null || this.room.getRoomSpecialTypes() == null || this.petData == null) {
return null;
}
HabboItem foodItem = this.petData.randomFoodItem(this.room.getRoomSpecialTypes().getPetFoods());
return foodItem != null ? foodItem.getBaseItem() : null;
}
/**
* Makes the pet walk to a drink item and drink from it.
*/
public void drink() {
if (this.room == null || this.room.getRoomSpecialTypes() == null || this.petData == null) {
return;
}
HabboItem item = this.petData.randomDrinkItem(this.room.getRoomSpecialTypes().getPetDrinks());
if (item != null) {
this.roomUnit.setCanWalk(true);
@@ -468,26 +549,86 @@ public class Pet implements ISerialize, Runnable {
}
}
/**
* Makes the pet walk to a food item and eat from it.
*/
public void eat() {
if (this.room == null || this.room.getRoomSpecialTypes() == null || this.petData == null) {
return;
}
HabboItem item = this.petData.randomFoodItem(this.room.getRoomSpecialTypes().getPetFoods());
{
if (item != null) {
this.roomUnit.setCanWalk(true);
this.roomUnit.setGoalLocation(this.room.getLayout().getTile(item.getX(), item.getY()));
}
if (item != null) {
this.roomUnit.setCanWalk(true);
this.roomUnit.setGoalLocation(this.room.getLayout().getTile(item.getX(), item.getY()));
}
}
public void findToy() {
HabboItem item = this.petData.randomToyItem(this.room.getRoomSpecialTypes().getPetToys());
{
if (item != null) {
this.roomUnit.setCanWalk(true);
this.roomUnit.setGoalLocation(this.room.getLayout().getTile(item.getX(), item.getY()));
if (this.room == null || this.room.getRoomSpecialTypes() == null || this.petData == null) {
return;
}
// Get all pet toys in the room
THashSet<InteractionPetToy> toys = this.room.getRoomSpecialTypes().getPetToys();
if (toys.isEmpty()) {
return;
}
// First try to find a toy this pet can use
HabboItem item = this.petData.randomToyItem(toys);
// If no compatible toy found, just pick any toy in the room
if (item == null) {
for (InteractionPetToy toy : toys) {
item = toy;
break;
}
}
if (item != null) {
this.roomUnit.setCanWalk(true);
this.setTask(PetTasks.PLAY);
this.roomUnit.setGoalLocation(this.room.getLayout().getTile(item.getX(), item.getY()));
this.say(this.petData.randomVocal(PetVocalsType.PLAYFUL));
}
}
/**
* Finds a pet tree (for dragons/monkeys) and walks to it.
* Used for hang, swing, ring of fire actions.
*/
public void findTree() {
this.findPetItem(PetTasks.FREE, InteractionPetTree.class);
}
/**
* Finds a pet item of a specific type and walks to it.
* Used for trampolines, trees, and other special pet furniture.
* @param task The task to set on the pet
* @param type The class type of the item to find
* @return true if an item was found and pet is walking to it
*/
public boolean findPetItem(PetTasks task, Class<? extends HabboItem> type) {
if (this.room == null || this.room.getRoomSpecialTypes() == null || this.petData == null) {
return false;
}
HabboItem item = this.petData.randomToyHabboItem(this.room.getRoomSpecialTypes().getItemsOfType(type));
if (item != null) {
this.roomUnit.setCanWalk(true);
this.setTask(task);
if (this.getRoomUnit().getCurrentLocation().distance(this.room.getLayout().getTile(item.getX(), item.getY())) == 0) {
try {
item.onWalkOn(this.getRoomUnit(), this.getRoom(), null);
} catch (Exception ignored) {}
return true;
}
this.roomUnit.setGoalLocation(this.room.getLayout().getTile(item.getX(), item.getY()));
return true;
}
return false;
}
@@ -753,4 +894,74 @@ public class Pet implements ISerialize, Runnable {
public void setStayStartedAt(int stayStartedAt) {
this.stayStartedAt = stayStartedAt;
}
/**
* Gets the stats manager for this pet.
* @return The PetStatsManager instance
*/
public PetStatsManager getStatsManager() {
return this.statsManager;
}
/**
* Gets the behavior manager for this pet.
* @return The PetBehaviorManager instance
*/
public PetBehaviorManager getBehaviorManager() {
return this.behaviorManager;
}
/**
* Checks if a command can be executed based on cooldown and spam prevention.
* @param commandId The command ID to check
* @return true if the command can be executed, false if on cooldown
*/
public boolean canExecuteCommand(int commandId) {
long now = System.currentTimeMillis();
int globalCooldownMs = Emulator.getConfig().getInt("pet.command.cooldown_ms", 2000);
int maxSameCommandSpam = Emulator.getConfig().getInt("pet.command.max_same_spam", 3);
int spamResetMs = Emulator.getConfig().getInt("pet.command.spam_reset_ms", 10000);
// Global cooldown - applies to ALL commands to prevent switching between commands
if (now - this.lastCommandTime < globalCooldownMs) {
return false;
}
// Reset spam counter if enough time has passed
if (now - this.lastCommandTime > spamResetMs) {
this.sameCommandCount = 0;
}
// Check if same command is being spammed
if (commandId == this.lastCommandId) {
this.sameCommandCount++;
// Pet gets annoyed if same command spammed too much
if (this.sameCommandCount > maxSameCommandSpam) {
return false;
}
} else {
// Different command - reset counter but still subject to global cooldown
this.sameCommandCount = 1;
}
return true;
}
/**
* Records that a command was executed.
* @param commandId The command ID that was executed
*/
public void recordCommandExecution(int commandId) {
this.lastCommandId = commandId;
this.lastCommandTime = System.currentTimeMillis();
}
/**
* Gets the number of times the same command has been repeated.
* @return The spam count
*/
public int getSameCommandCount() {
return this.sameCommandCount;
}
}
@@ -0,0 +1,219 @@
package com.eu.habbo.habbohotel.pets;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.rooms.RoomTile;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
/**
* Manages pet AI behavior using a state machine pattern.
* Handles autonomous pet actions and state transitions.
*/
public class PetBehaviorManager {
private final Pet pet;
private PetBehaviorState currentState;
private long stateEnteredAt;
private long lastAutonomousAction;
// Configurable delays
private int autonomousActionDelay;
private int idleWanderMinMs;
private int idleWanderMaxMs;
/**
* Represents the various behavioral states a pet can be in.
*/
public enum PetBehaviorState {
IDLE, // Standing around
WANDERING, // Random walking
FOLLOWING, // Following owner/habbo
EXECUTING_COMMAND, // Doing a commanded action
EATING, // At food bowl
DRINKING, // At water bowl
PLAYING, // With toy
RESTING, // In nest/laying down
BREEDING, // In breeding box
DEAD // Monsterplant only
}
public PetBehaviorManager(Pet pet) {
this.pet = pet;
this.currentState = PetBehaviorState.IDLE;
this.stateEnteredAt = System.currentTimeMillis();
this.lastAutonomousAction = 0;
this.loadConfig();
}
/**
* Loads configuration values from the emulator config.
*/
private void loadConfig() {
this.autonomousActionDelay = Emulator.getConfig().getInt("pet.behavior.autonomous_action_delay", 5000);
this.idleWanderMinMs = Emulator.getConfig().getInt("pet.behavior.idle_wander_min_ms", 10000);
this.idleWanderMaxMs = Emulator.getConfig().getInt("pet.behavior.idle_wander_max_ms", 30000);
}
/**
* Transitions the pet to a new behavior state.
* @param newState The new state to transition to
*/
public void transition(PetBehaviorState newState) {
if (this.currentState == newState) return;
this.onExitState(this.currentState);
this.currentState = newState;
this.stateEnteredAt = System.currentTimeMillis();
this.onEnterState(newState);
}
/**
* Called when entering a new state to set up the appropriate room unit status.
*/
private void onEnterState(PetBehaviorState state) {
if (this.pet.getRoomUnit() == null) return;
switch (state) {
case RESTING -> {
this.pet.getRoomUnit().setCanWalk(false);
this.pet.getRoomUnit().setStatus(RoomUnitStatus.LAY, "0");
}
case EATING -> {
this.pet.getRoomUnit().setStatus(RoomUnitStatus.EAT, "0");
}
case PLAYING -> {
// Play status handled by specific toy interaction
}
case IDLE -> {
// Clear any lingering action statuses
}
case FOLLOWING -> {
this.pet.getRoomUnit().setCanWalk(true);
}
default -> {
// No special handling needed
}
}
}
/**
* Called when exiting a state to clean up room unit status.
*/
private void onExitState(PetBehaviorState state) {
if (this.pet.getRoomUnit() == null) return;
switch (state) {
case RESTING -> {
this.pet.getRoomUnit().removeStatus(RoomUnitStatus.LAY);
this.pet.getRoomUnit().setCanWalk(true);
}
case EATING -> {
this.pet.getRoomUnit().removeStatus(RoomUnitStatus.EAT);
}
case PLAYING -> {
// Play status cleanup handled by toy interaction
}
default -> {
// No special cleanup needed
}
}
}
/**
* Processes autonomous pet behavior each cycle.
* Called every cycle to handle autonomous pet actions based on needs.
*/
public void processAutonomousBehavior() {
// Rate limit autonomous actions
if (System.currentTimeMillis() - this.lastAutonomousAction < this.autonomousActionDelay) {
return;
}
if (this.pet.getRoom() == null) return;
PetStatsManager stats = this.pet.getStatsManager();
if (stats == null) return;
// Priority-based autonomous behavior
if (stats.needsRest() && this.currentState != PetBehaviorState.RESTING) {
this.pet.findNest();
this.lastAutonomousAction = System.currentTimeMillis();
return;
}
if (stats.needsFood() && this.currentState != PetBehaviorState.EATING) {
this.pet.eat();
this.lastAutonomousAction = System.currentTimeMillis();
return;
}
if (stats.needsWater() && this.currentState != PetBehaviorState.DRINKING) {
this.pet.drink();
this.lastAutonomousAction = System.currentTimeMillis();
return;
}
if (stats.needsAttention() && this.currentState == PetBehaviorState.IDLE) {
this.pet.findToy();
this.lastAutonomousAction = System.currentTimeMillis();
return;
}
// Random wandering when idle
if (this.currentState == PetBehaviorState.IDLE) {
long idleTime = System.currentTimeMillis() - this.stateEnteredAt;
int wanderDelay = this.idleWanderMinMs + Emulator.getRandom().nextInt(
this.idleWanderMaxMs - this.idleWanderMinMs);
if (idleTime > wanderDelay) {
RoomTile tile = this.pet.getRoom().getRandomWalkableTile();
if (tile != null && this.pet.getRoomUnit() != null) {
this.pet.getRoomUnit().setGoalLocation(tile);
this.transition(PetBehaviorState.WANDERING);
}
this.lastAutonomousAction = System.currentTimeMillis();
}
}
}
/**
* Checks if the pet can currently accept commands.
* @return true if the pet can accept commands
*/
public boolean canAcceptCommand() {
return this.currentState != PetBehaviorState.DEAD
&& this.currentState != PetBehaviorState.BREEDING;
}
/**
* Interrupts the current action and returns to idle state.
*/
public void interruptCurrentAction() {
if (this.currentState == PetBehaviorState.EXECUTING_COMMAND
|| this.currentState == PetBehaviorState.WANDERING) {
this.transition(PetBehaviorState.IDLE);
}
}
/**
* Gets the current behavior state.
* @return The current PetBehaviorState
*/
public PetBehaviorState getCurrentState() {
return this.currentState;
}
/**
* Gets the timestamp when the current state was entered.
* @return Timestamp in milliseconds
*/
public long getStateEnteredAt() {
return this.stateEnteredAt;
}
/**
* Gets how long the pet has been in the current state.
* @return Duration in milliseconds
*/
public long getTimeInCurrentState() {
return System.currentTimeMillis() - this.stateEnteredAt;
}
}
@@ -45,31 +45,75 @@ public class PetCommand implements Comparable<PetCommand> {
}
public void handle(Pet pet, Habbo habbo, String[] data) {
if (Emulator.getRandom().nextInt((pet.level - this.level <= 0 ? 2 : pet.level - this.level) + 2) == 0) {
pet.say(pet.petData.randomVocal(PetVocalsType.DISOBEY));
// Check command cooldown to prevent spam (global cooldown for ALL commands)
if (!pet.canExecuteCommand(this.id)) {
// Pet ignores spammed commands - maybe give a tired/annoyed response occasionally
if (pet.getSameCommandCount() > Emulator.getConfig().getInt("pet.command.max_same_spam", 3)) {
if (Emulator.getRandom().nextInt(3) == 0) {
pet.say(pet.getPetData().randomVocal(PetVocalsType.DISOBEY));
}
}
return;
}
// Check if pet has enough energy to perform the command
int minEnergy = Emulator.getConfig().getInt("pet.command.min_energy", 15);
if (pet.getEnergy() < minEnergy || pet.getEnergy() < this.energyCost) {
pet.say(pet.getPetData().randomVocal(PetVocalsType.TIRED));
pet.recordCommandExecution(this.id);
return;
}
// Check if pet is too unhappy to obey
int minHappiness = Emulator.getConfig().getInt("pet.command.min_happiness", 10);
if (pet.getHappiness() < minHappiness) {
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_SAD));
pet.recordCommandExecution(this.id);
return;
}
// Improved obedience formula - configurable base chance with level scaling
int levelDifference = pet.getLevel() - this.level;
int baseChance = Emulator.getConfig().getInt("pet.command.base_obey_chance", 70); // 70% base
int levelBonus = Math.max(0, levelDifference * 5); // +5% per level above requirement
int obeyChance = Math.min(95, baseChance + levelBonus); // Cap at 95%
if (Emulator.getRandom().nextInt(100) >= obeyChance) {
pet.say(pet.getPetData().randomVocal(PetVocalsType.DISOBEY));
pet.recordCommandExecution(this.id);
return;
}
if (this.action != null) {
if (this.action.petTask != pet.getTask()) {
if (this.action.stopsPetWalking) {
pet.getRoomUnit().setGoalLocation(pet.getRoomUnit().getCurrentLocation());
// Allow repeating actions - removed the task comparison check
if (this.action.stopsPetWalking) {
pet.getRoomUnit().setGoalLocation(pet.getRoomUnit().getCurrentLocation());
}
if (this.action.apply(pet, habbo, data)) {
// Set the pet's task from the action
if (this.action.petTask != null) {
pet.setTask(this.action.petTask);
}
if (this.action.apply(pet, habbo, data)) {
for (RoomUnitStatus status : this.action.statusToRemove) {
pet.getRoomUnit().removeStatus(status);
}
for (RoomUnitStatus status : this.action.statusToSet) {
pet.getRoomUnit().setStatus(status, "0");
}
pet.getRoomUnit().setStatus(RoomUnitStatus.GESTURE, this.action.gestureToSet);
pet.addEnergy(-this.energyCost);
pet.addHappiness(-this.happinessCost);
pet.addExperience(this.xp);
for (RoomUnitStatus status : this.action.statusToRemove) {
pet.getRoomUnit().removeStatus(status);
}
for (RoomUnitStatus status : this.action.statusToSet) {
pet.getRoomUnit().setStatus(status, "0");
}
pet.getRoomUnit().setStatus(RoomUnitStatus.GESTURE, this.action.gestureToSet);
pet.addEnergy(-this.energyCost);
pet.addHappiness(-this.happinessCost);
pet.addExperience(this.xp);
// Mark pet for status update so clients see the animation
pet.packetUpdate = true;
// Record successful command execution
pet.recordCommandExecution(this.id);
}
}
}
@@ -97,15 +97,22 @@ public class PetData implements Comparable<PetData> {
boolean haveNest(Item nest) {
// If no nest items are registered, allow all nest items
if (this.nestItems.isEmpty() && PetData.generalNestItems.isEmpty()) {
return true;
}
return PetData.generalNestItems.contains(nest) || this.nestItems.contains(nest);
}
public HabboItem randomNest(THashSet<InteractionNest> items) {
List<HabboItem> nestList = new ArrayList<>();
// If no nest items are registered, allow all nests in the room
boolean allowAll = this.nestItems.isEmpty() && PetData.generalNestItems.isEmpty();
for (InteractionNest nest : items) {
if (this.haveNest(nest)) {
if (allowAll || this.haveNest(nest)) {
nestList.add(nest);
}
}
@@ -136,15 +143,22 @@ public class PetData implements Comparable<PetData> {
boolean haveFoodItem(Item food) {
// If no food items are registered, allow all food items
if (this.foodItems.isEmpty() && PetData.generalFoodItems.isEmpty()) {
return true;
}
return this.foodItems.contains(food) || PetData.generalFoodItems.contains(food);
}
public HabboItem randomFoodItem(THashSet<InteractionPetFood> items) {
List<HabboItem> foodList = new ArrayList<>();
// If no food items are registered, allow all food in the room
boolean allowAll = this.foodItems.isEmpty() && PetData.generalFoodItems.isEmpty();
for (InteractionPetFood food : items) {
if (this.haveFoodItem(food)) {
if (allowAll || this.haveFoodItem(food)) {
foodList.add(food);
}
}
@@ -174,15 +188,22 @@ public class PetData implements Comparable<PetData> {
boolean haveDrinkItem(Item item) {
// If no drink items are registered, allow all drink items
if (this.drinkItems.isEmpty() && PetData.generalDrinkItems.isEmpty()) {
return true;
}
return this.drinkItems.contains(item) || PetData.generalDrinkItems.contains(item);
}
public HabboItem randomDrinkItem(THashSet<InteractionPetDrink> items) {
List<HabboItem> drinkList = new ArrayList<>();
// If no drink items are registered, allow all drinks in the room
boolean allowAll = this.drinkItems.isEmpty() && PetData.generalDrinkItems.isEmpty();
for (InteractionPetDrink drink : items) {
if (this.haveDrinkItem(drink)) {
if (allowAll || this.haveDrinkItem(drink)) {
drinkList.add(drink);
}
}
@@ -212,15 +233,22 @@ public class PetData implements Comparable<PetData> {
public boolean haveToyItem(Item toy) {
// If no toy items are registered, allow all toy items
if (this.toyItems.isEmpty() && PetData.generalToyItems.isEmpty()) {
return true;
}
return this.toyItems.contains(toy) || PetData.generalToyItems.contains(toy);
}
public HabboItem randomToyItem(THashSet<InteractionPetToy> toys) {
List<HabboItem> toyList = new ArrayList<>();
// If no toy items are registered, allow all toys in the room
boolean allowAll = this.toyItems.isEmpty() && PetData.generalToyItems.isEmpty();
for (InteractionPetToy toy : toys) {
if (this.haveToyItem(toy)) {
if (allowAll || this.haveToyItem(toy)) {
toyList.add(toy);
}
}
@@ -233,6 +261,30 @@ public class PetData implements Comparable<PetData> {
return null;
}
/**
* Finds a random toy item from a generic set of HabboItems.
* Used for finding pet items like trampolines, trees, etc.
*/
public HabboItem randomToyHabboItem(THashSet<HabboItem> items) {
List<HabboItem> itemList = new ArrayList<>();
// If no toy items are registered, allow all toys in the room
boolean allowAll = this.toyItems.isEmpty() && PetData.generalToyItems.isEmpty();
for (HabboItem item : items) {
if (allowAll || this.haveToyItem(item)) {
itemList.add(item);
}
}
if (!itemList.isEmpty()) {
Collections.shuffle(itemList);
return itemList.get(0);
}
return null;
}
public PetVocal randomVocal(PetVocalsType type) {
THashSet<PetVocal> petTypeVocals = this.petVocals.get(type);
@@ -242,8 +294,10 @@ public class PetData implements Comparable<PetData> {
int generalSize = generalVocals != null ? generalVocals.size() : 0;
int totalSize = petTypeSize + generalSize;
if (totalSize == 0)
return null;
if (totalSize == 0) {
// Return a default vocal instead of null
return getDefaultVocal(type);
}
int randomIndex = Emulator.getRandom().nextInt(totalSize);
@@ -262,7 +316,31 @@ public class PetData implements Comparable<PetData> {
}
}
return null;
return getDefaultVocal(type);
}
/**
* Returns a default vocal message when no configured vocals exist for the type.
* This prevents null pointer exceptions and silent pets.
*/
private static PetVocal getDefaultVocal(PetVocalsType type) {
return switch (type) {
case GENERIC_HAPPY -> new PetVocal("*wags tail happily*");
case GENERIC_SAD -> new PetVocal("*whimpers*");
case GENERIC_NEUTRAL -> new PetVocal("*looks around*");
case HUNGRY -> new PetVocal("*stomach growls*");
case THIRSTY -> new PetVocal("*pants*");
case TIRED -> new PetVocal("*yawns*");
case SLEEPING -> new PetVocal("*snores softly*");
case PLAYFUL -> new PetVocal("*bounces excitedly*");
case DISOBEY -> new PetVocal("*ignores command*");
case EATING -> new PetVocal("*munches happily*");
case DRINKING -> new PetVocal("*laps up water*");
case LEVEL_UP -> new PetVocal("*jumps with joy*");
case GREET_OWNER -> new PetVocal("*perks up excitedly*");
case MUTED -> new PetVocal("*stays quiet*");
case UNKNOWN_COMMAND -> new PetVocal("*tilts head confused*");
};
}
@Override
@@ -7,6 +7,7 @@ import com.eu.habbo.habbohotel.items.interactions.pets.InteractionNest;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetDrink;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetFood;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetToy;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetTrampoline;
import com.eu.habbo.habbohotel.pets.actions.*;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomTile;
@@ -52,6 +53,12 @@ public class PetManager {
this.put(15, new ActionFollowLeft());
this.put(16, new ActionFollowRight());
this.put(17, new ActionPlayFootball());
this.put(18, new ActionTeleport());
this.put(19, new ActionBounce());
this.put(20, new ActionFlatten());
this.put(21, new ActionDance());
this.put(22, new ActionSpin());
this.put(23, new ActionSwitch());
this.put(24, new ActionMoveForward());
this.put(25, new ActionTurnLeft());
this.put(26, new ActionTurnRight());
@@ -59,10 +66,20 @@ public class PetManager {
this.put(28, new ActionCroak());
this.put(29, new ActionDip());
this.put(30, new ActionWave());
this.put(31, new ActionMambo());
this.put(32, new ActionHighJump());
this.put(33, new ActionChickenDance());
this.put(34, new ActionTripleJump());
this.put(35, new ActionWings());
this.put(36, new ActionBreatheFire());
this.put(37, new ActionHang());
this.put(38, new ActionTorch());
this.put(40, new ActionSwing());
this.put(41, new ActionRoll());
this.put(42, new ActionRingOfFire());
this.put(43, new ActionEat());
this.put(44, new ActionWagTail());
this.put(45, new ActionCount());
this.put(46, new ActionBreed());
}
@@ -200,7 +217,7 @@ public class PetManager {
PetData.generalFoodItems.add(baseItem);
else if (baseItem.getInteractionType().getType() == InteractionPetDrink.class)
PetData.generalDrinkItems.add(baseItem);
else if (baseItem.getInteractionType().getType() == InteractionPetToy.class)
else if (baseItem.getInteractionType().getType() == InteractionPetToy.class || baseItem.getInteractionType().getType() == InteractionPetTrampoline.class)
PetData.generalToyItems.add(baseItem);
} else {
PetData data = this.getPetData(set.getInt("pet_id"));
@@ -212,7 +229,7 @@ public class PetManager {
data.addFoodItem(baseItem);
else if (baseItem.getInteractionType().getType() == InteractionPetDrink.class)
data.addDrinkItem(baseItem);
else if (baseItem.getInteractionType().getType() == InteractionPetToy.class)
else if (baseItem.getInteractionType().getType() == InteractionPetToy.class || baseItem.getInteractionType().getType() == InteractionPetTrampoline.class)
data.addToyItem(baseItem);
}
}
@@ -0,0 +1,50 @@
package com.eu.habbo.habbohotel.pets;
/**
* Represents the various mood states a pet can be in based on its stats.
*/
public enum PetMood {
EXHAUSTED("exhausted", 0),
STARVING("starving", 1),
PARCHED("parched", 2),
DEPRESSED("depressed", 3),
NEUTRAL("neutral", 4),
HAPPY("happy", 5),
ECSTATIC("ecstatic", 6);
private final String key;
private final int priority;
PetMood(String key, int priority) {
this.key = key;
this.priority = priority;
}
public String getKey() {
return this.key;
}
/**
* Gets the priority of this mood. Lower values indicate more urgent moods.
* @return The priority value
*/
public int getPriority() {
return this.priority;
}
/**
* Checks if this mood is a negative/urgent mood that needs addressing.
* @return true if this is a negative mood
*/
public boolean isNegative() {
return this.priority <= 3;
}
/**
* Checks if this mood is a positive mood.
* @return true if this is a positive mood
*/
public boolean isPositive() {
return this.priority >= 5;
}
}
@@ -0,0 +1,150 @@
package com.eu.habbo.habbohotel.pets;
import com.eu.habbo.Emulator;
/**
* Manages all stat-related logic for pets including decay rates, recovery rates,
* and mood calculations. This centralizes stat management for better maintainability.
*/
public class PetStatsManager {
private final Pet pet;
// Configurable decay rates
private int hungerDecayRate;
private int thirstDecayRate;
private int energyDecayRate;
private int happinessDecayRate;
// Configurable recovery rates
private int energyRecoveryRate;
private int happinessRecoveryRate;
// Configurable thresholds
private int hungryThreshold;
private int thirstyThreshold;
private int tiredThreshold;
private int sadThreshold;
public PetStatsManager(Pet pet) {
this.pet = pet;
this.loadConfig();
}
/**
* Loads configuration values from the emulator config.
*/
private void loadConfig() {
this.hungerDecayRate = Emulator.getConfig().getInt("pet.stats.hunger_decay", 1);
this.thirstDecayRate = Emulator.getConfig().getInt("pet.stats.thirst_decay", 1);
this.energyDecayRate = Emulator.getConfig().getInt("pet.stats.energy_decay", 1);
this.happinessDecayRate = Emulator.getConfig().getInt("pet.stats.happiness_decay", 1);
this.energyRecoveryRate = Emulator.getConfig().getInt("pet.stats.energy_recovery", 5);
this.happinessRecoveryRate = Emulator.getConfig().getInt("pet.stats.happiness_recovery", 1);
this.hungryThreshold = Emulator.getConfig().getInt("pet.threshold.hungry", 50);
this.thirstyThreshold = Emulator.getConfig().getInt("pet.threshold.thirsty", 50);
this.tiredThreshold = Emulator.getConfig().getInt("pet.threshold.tired", 30);
this.sadThreshold = Emulator.getConfig().getInt("pet.threshold.sad", 30);
}
/**
* Process stat changes when pet is walking/active.
*/
public void processActiveTick() {
this.pet.addHunger(this.hungerDecayRate);
this.pet.addThirst(this.thirstDecayRate);
this.pet.addEnergy(-this.energyDecayRate);
}
/**
* Process stat changes when pet is in nest/down (resting).
*/
public void processRestingTick() {
this.pet.addHunger(-1);
this.pet.addThirst(-1);
this.pet.addEnergy(this.energyRecoveryRate);
this.pet.addHappiness(this.happinessRecoveryRate);
}
/**
* Process stat changes when pet is standing still/idle.
*/
public void processIdleTick() {
this.pet.addHunger(this.hungerDecayRate / 2);
this.pet.addThirst(this.thirstDecayRate / 2);
}
/**
* Gets the current mood of the pet based on its stats.
* @return The current PetMood
*/
public PetMood getCurrentMood() {
if (this.pet.getEnergy() < 20) return PetMood.EXHAUSTED;
if (this.pet.getLevelHunger() > 80) return PetMood.STARVING;
if (this.pet.getLevelThirst() > 80) return PetMood.PARCHED;
if (this.pet.getHappiness() < 20) return PetMood.DEPRESSED;
if (this.pet.getHappiness() > 80 && this.pet.getEnergy() > 60) return PetMood.ECSTATIC;
if (this.pet.getHappiness() > 50) return PetMood.HAPPY;
return PetMood.NEUTRAL;
}
/**
* Checks if the pet needs food.
* @return true if hunger level exceeds the hungry threshold
*/
public boolean needsFood() {
return this.pet.getLevelHunger() > this.hungryThreshold;
}
/**
* Checks if the pet needs water.
* @return true if thirst level exceeds the thirsty threshold
*/
public boolean needsWater() {
return this.pet.getLevelThirst() > this.thirstyThreshold;
}
/**
* Checks if the pet needs rest.
* @return true if energy level is below the tired threshold
*/
public boolean needsRest() {
return this.pet.getEnergy() < this.tiredThreshold;
}
/**
* Checks if the pet needs attention/play.
* @return true if happiness level is below the sad threshold
*/
public boolean needsAttention() {
return this.pet.getHappiness() < this.sadThreshold;
}
/**
* Gets the overall health score of the pet (0-100).
* @return An integer representing overall pet health
*/
public int getOverallHealth() {
int maxEnergy = PetManager.maxEnergy(this.pet.getLevel());
int energyPercent = (this.pet.getEnergy() * 100) / maxEnergy;
int hungerPercent = 100 - this.pet.getLevelHunger();
int thirstPercent = 100 - this.pet.getLevelThirst();
int happinessPercent = this.pet.getHappiness();
return (energyPercent + hungerPercent + thirstPercent + happinessPercent) / 4;
}
// Getters for decay/recovery rates
public int getHungerDecayRate() { return hungerDecayRate; }
public int getThirstDecayRate() { return thirstDecayRate; }
public int getEnergyDecayRate() { return energyDecayRate; }
public int getHappinessDecayRate() { return happinessDecayRate; }
public int getEnergyRecoveryRate() { return energyRecoveryRate; }
public int getHappinessRecoveryRate() { return happinessRecoveryRate; }
// Getters for thresholds
public int getHungryThreshold() { return hungryThreshold; }
public int getThirstyThreshold() { return thirstyThreshold; }
public int getTiredThreshold() { return tiredThreshold; }
public int getSadThreshold() { return sadThreshold; }
}
@@ -17,6 +17,9 @@ public class ActionBeg extends PetAction {
public boolean apply(Pet pet, Habbo habbo, String[] data) {
pet.clearPosture();
// Begging is fun interaction with owner
pet.addHappiness(5);
if (pet.getHappiness() > 90)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
@@ -0,0 +1,35 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.threading.runnables.PetClearPosture;
public class ActionBounce extends PetAction {
public ActionBounce() {
super(PetTasks.JUMP, true);
this.minimumActionDuration = 3000;
this.statusToSet.add(RoomUnitStatus.JUMP);
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
pet.clearPosture();
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.JUMP, null, false), 3000);
// Bouncing is fun!
pet.addHappiness(8);
if (pet.getHappiness() > 60)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
}
@@ -4,6 +4,7 @@ import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetBreedingNes
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.messages.outgoing.rooms.pets.breeding.PetBreedingStartFailedComposer;
@@ -28,6 +29,7 @@ public class ActionBreed extends PetAction {
if (nest != null) {
pet.getRoomUnit().setGoalLocation(pet.getRoom().getLayout().getTile(nest.getX(), nest.getY()));
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_HAPPY));
return true;
} else {
@@ -0,0 +1,33 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.threading.runnables.PetClearPosture;
public class ActionChickenDance extends PetAction {
public ActionChickenDance() {
super(PetTasks.FREE, true);
this.minimumActionDuration = 4000;
this.statusToSet.add(RoomUnitStatus.DANCE);
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
pet.clearPosture();
pet.getRoomUnit().setStatus(RoomUnitStatus.DANCE, "");
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.DANCE, null, false), 4000);
if (pet.getHappiness() > 50)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
}
@@ -0,0 +1,27 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.users.Habbo;
public class ActionCount extends PetAction {
public ActionCount() {
super(PetTasks.FREE, true);
this.minimumActionDuration = 3000;
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
pet.clearPosture();
// Count by speaking
if (pet.getHappiness() > 50)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
}
@@ -23,6 +23,8 @@ public class ActionCroak extends PetAction {
if (pet.getHappiness() > 80)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
@@ -0,0 +1,36 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.threading.runnables.PetClearPosture;
public class ActionDance extends PetAction {
public ActionDance() {
super(PetTasks.FREE, true);
this.minimumActionDuration = 5000;
this.statusToSet.add(RoomUnitStatus.DANCE);
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
pet.clearPosture();
pet.getRoomUnit().setStatus(RoomUnitStatus.DANCE, "");
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.DANCE, null, false), 5000);
// Dancing is fun!
pet.addHappiness(10);
if (pet.getHappiness() > 60)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
}
@@ -4,6 +4,7 @@ import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.items.interactions.InteractionWater;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import gnu.trove.set.hash.THashSet;
@@ -23,6 +24,8 @@ public class ActionDip extends PetAction {
HabboItem waterPatch = (HabboItem) waterItems.toArray()[Emulator.getRandom().nextInt(waterItems.size())];
pet.getRoomUnit().setGoalLocation(pet.getRoom().getLayout().getTile(waterPatch.getX(), waterPatch.getY()));
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
return true;
}
@@ -1,5 +1,6 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
@@ -10,18 +11,33 @@ import com.eu.habbo.habbohotel.users.Habbo;
public class ActionDown extends PetAction {
public ActionDown() {
super(PetTasks.DOWN, true);
this.minimumActionDuration = 4000;
this.statusToRemove.add(RoomUnitStatus.BEG);
this.statusToRemove.add(RoomUnitStatus.MOVE);
this.statusToRemove.add(RoomUnitStatus.SIT);
this.statusToRemove.add(RoomUnitStatus.DEAD);
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
pet.getRoomUnit().setStatus(RoomUnitStatus.LAY, pet.getRoom().getStackHeight(pet.getRoomUnit().getX(), pet.getRoomUnit().getY(), false) + "");
if (pet.getTask() != PetTasks.DOWN && !pet.getRoomUnit().hasStatus(RoomUnitStatus.LAY)) {
pet.getRoomUnit().cmdLay = true;
pet.getRoomUnit().setStatus(RoomUnitStatus.LAY, pet.getRoomUnit().getCurrentLocation().getStackHeight() + "");
if (pet.getHappiness() > 50)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
// Lying down is a bit boring but restful
pet.addHappiness(-2);
Emulator.getThreading().run(() -> {
pet.getRoomUnit().cmdLay = false;
pet.clearPosture();
}, this.minimumActionDuration);
if (pet.getHappiness() > 50)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
return true;
}
@@ -1,5 +1,6 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
@@ -12,17 +13,30 @@ public class ActionDrink extends PetAction {
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
if (pet.getLevelThirst() > 40) {
pet.drink();
if (pet.getLevelThirst() > 65)
pet.say(pet.getPetData().randomVocal(PetVocalsType.THIRSTY));
return true;
// Check if pet is thirsty enough to want water (threshold: 35 to match InteractionPetDrink)
if (pet.getLevelThirst() >= 35) {
// Check if there's water available in the room before sending pet to drink
if (pet.getRoom() != null && pet.getRoom().getRoomSpecialTypes() != null) {
Item drinkItem = pet.findDrink();
if (drinkItem != null) {
// Water exists - pet goes to drink
if (pet.getLevelThirst() > 65) {
pet.say(pet.getPetData().randomVocal(PetVocalsType.THIRSTY));
}
pet.drink();
return true;
} else {
// No suitable water in room - pet complains
pet.say(pet.getPetData().randomVocal(PetVocalsType.THIRSTY));
return false;
}
}
return false;
} else {
// Pet is not thirsty - disobeys command
pet.say(pet.getPetData().randomVocal(PetVocalsType.DISOBEY));
return false;
}
return false;
}
}
@@ -1,33 +1,42 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.threading.runnables.PetClearPosture;
public class ActionEat extends PetAction {
public ActionEat() {
super(null, true);
this.statusToSet.add(RoomUnitStatus.EAT);
// stopsPetWalking=false so pet can walk to food
// Don't set EAT status here - let InteractionPetFood.onWalkOn() handle it when pet arrives
super(null, false);
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
//Eat
if (pet.getLevelHunger() > 40) {
pet.say(pet.getPetData().randomVocal(PetVocalsType.HUNGRY));
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.EAT, null, false), 500);
pet.eat();
return true;
// Check if pet is hungry enough to want food (threshold: 35 to match InteractionPetFood)
if (pet.getLevelHunger() >= 35) {
// Check if there's food available in the room before sending pet to eat
if (pet.getRoom() != null && pet.getRoom().getRoomSpecialTypes() != null) {
Item foodItem = pet.findFood();
if (foodItem != null) {
// Food exists - pet goes to eat
pet.say(pet.getPetData().randomVocal(PetVocalsType.HUNGRY));
pet.eat();
return true;
} else {
// No suitable food in room - pet complains
pet.say(pet.getPetData().randomVocal(PetVocalsType.HUNGRY));
return false;
}
}
return false;
} else {
// Pet is not hungry - disobeys command
pet.say(pet.getPetData().randomVocal(PetVocalsType.DISOBEY));
return false;
}
}
}
@@ -0,0 +1,33 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.threading.runnables.PetClearPosture;
public class ActionFlatten extends PetAction {
public ActionFlatten() {
super(PetTasks.DOWN, true);
this.minimumActionDuration = 3000;
this.statusToSet.add(RoomUnitStatus.FLAT);
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
pet.clearPosture();
pet.getRoomUnit().setStatus(RoomUnitStatus.FLAT, pet.getRoomUnit().getCurrentLocation().getStackHeight() + "");
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.FLAT, null, false), 3000);
if (pet.getHappiness() > 50)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
}
@@ -23,6 +23,9 @@ public class ActionFollow extends PetAction {
Emulator.getThreading().run(new PetFollowHabbo(pet, habbo, 0));
// Following owner is enjoyable
pet.addHappiness(5);
if (pet.getHappiness() > 75)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
@@ -10,7 +10,7 @@ import com.eu.habbo.threading.runnables.PetFollowHabbo;
public class ActionFollowLeft extends PetAction {
public ActionFollowLeft() {
super(PetTasks.FOLLOW, true);
super(PetTasks.FOLLOW_LEFT, true);
}
@Override
@@ -18,7 +18,10 @@ public class ActionFollowLeft extends PetAction {
//Follow left.
pet.clearPosture();
Emulator.getThreading().run(new PetFollowHabbo(pet, habbo, -2));
Emulator.getThreading().run(new PetFollowHabbo(pet, habbo, +2));
// Following owner is enjoyable
pet.addHappiness(5);
if (pet.getHappiness() > 75)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
@@ -10,7 +10,7 @@ import com.eu.habbo.threading.runnables.PetFollowHabbo;
public class ActionFollowRight extends PetAction {
public ActionFollowRight() {
super(PetTasks.FOLLOW, true);
super(PetTasks.FOLLOW_RIGHT, true);
}
@Override
@@ -18,7 +18,10 @@ public class ActionFollowRight extends PetAction {
//Follow right.
pet.clearPosture();
Emulator.getThreading().run(new PetFollowHabbo(pet, habbo, +2));
Emulator.getThreading().run(new PetFollowHabbo(pet, habbo, -2));
// Following owner is enjoyable
pet.addHappiness(5);
if (pet.getHappiness() > 75)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
@@ -3,6 +3,7 @@ package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.users.Habbo;
public class ActionFree extends PetAction {
@@ -13,6 +14,9 @@ public class ActionFree extends PetAction {
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
pet.freeCommand();
if (pet.getHappiness() > 50)
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_HAPPY));
return true;
}
@@ -0,0 +1,47 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetTree;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.threading.runnables.PetClearPosture;
public class ActionHang extends PetAction {
public ActionHang() {
super(PetTasks.FREE, true);
this.minimumActionDuration = 4000;
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
// Hang requires pet to be on a pet tree (dragon/monkey tree)
if (pet.getRoom() == null) {
return false;
}
HabboItem itemBelow = pet.getRoom().getTopItemAt(pet.getRoomUnit().getX(), pet.getRoomUnit().getY());
if (!(itemBelow instanceof InteractionPetTree)) {
// Pet must go to tree first
pet.findTree();
pet.say(pet.getPetData().randomVocal(PetVocalsType.DISOBEY));
return false;
}
pet.clearPosture();
pet.getRoomUnit().setStatus(RoomUnitStatus.HANG, "");
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.HANG, null, false), 4000);
if (pet.getHappiness() > 50)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
}
@@ -4,6 +4,7 @@ import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomTile;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
@@ -16,8 +17,27 @@ public class ActionHere extends PetAction {
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
pet.getRoomUnit().setGoalLocation(pet.getRoom().getLayout().getTileInFront(habbo.getRoomUnit().getCurrentLocation(), habbo.getRoomUnit().getBodyRotation().getValue()));
pet.getRoomUnit().setCanWalk(true);
if (pet.getRoom() == null || habbo.getRoomUnit() == null) {
return false;
}
pet.clearPosture();
// Try tile in front of habbo first
RoomTile target = pet.getRoom().getLayout().getTileInFront(
habbo.getRoomUnit().getCurrentLocation(),
habbo.getRoomUnit().getBodyRotation().getValue()
);
// If not walkable, try habbo's current tile
if (target == null || !pet.getRoom().getLayout().tileWalkable(target.x, target.y)) {
target = habbo.getRoomUnit().getCurrentLocation();
}
if (target != null) {
pet.getRoomUnit().setGoalLocation(target);
pet.getRoomUnit().setCanWalk(true);
}
if (pet.getHappiness() > 75)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
@@ -0,0 +1,32 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.threading.runnables.PetClearPosture;
public class ActionHighJump extends PetAction {
public ActionHighJump() {
super(PetTasks.JUMP, true);
this.minimumActionDuration = 3000;
this.statusToSet.add(RoomUnitStatus.JUMP);
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
pet.clearPosture();
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.JUMP, null, false), 3000);
if (pet.getHappiness() > 70)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
}
@@ -22,6 +22,9 @@ public class ActionJump extends PetAction {
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.JUMP, null, false), 2000);
// Jumping is fun!
pet.addHappiness(8);
if (pet.getHappiness() > 60)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
@@ -0,0 +1,33 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.threading.runnables.PetClearPosture;
public class ActionMambo extends PetAction {
public ActionMambo() {
super(PetTasks.FREE, true);
this.minimumActionDuration = 5000;
this.statusToSet.add(RoomUnitStatus.DANCE);
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
pet.clearPosture();
pet.getRoomUnit().setStatus(RoomUnitStatus.DANCE, "");
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.DANCE, null, false), 5000);
if (pet.getHappiness() > 60)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
}
@@ -3,6 +3,7 @@ package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomTile;
import com.eu.habbo.habbohotel.users.Habbo;
public class ActionMoveForward extends PetAction {
@@ -12,12 +13,23 @@ public class ActionMoveForward extends PetAction {
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
pet.getRoomUnit().setGoalLocation(pet.getRoom().getLayout().getTileInFront(pet.getRoomUnit().getCurrentLocation(), pet.getRoomUnit().getBodyRotation().getValue()));
pet.getRoomUnit().setCanWalk(true);
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
if (pet.getRoom() == null || pet.getRoomUnit() == null) {
return false;
}
RoomTile targetTile = pet.getRoom().getLayout().getTileInFront(
pet.getRoomUnit().getCurrentLocation(),
pet.getRoomUnit().getBodyRotation().getValue()
);
if (targetTile != null && pet.getRoom().getLayout().tileWalkable(targetTile.x, targetTile.y)) {
pet.getRoomUnit().setGoalLocation(targetTile);
pet.getRoomUnit().setCanWalk(true);
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
pet.say(pet.getPetData().randomVocal(PetVocalsType.DISOBEY));
return false;
}
}
@@ -12,17 +12,14 @@ public class ActionNest extends PetAction {
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
if (pet.getEnergy() < 65) {
pet.findNest();
// Pet always obeys nest command - will go to nest or lay down if no nest available
pet.findNest();
if (pet.getEnergy() < 30)
pet.say(pet.getPetData().randomVocal(PetVocalsType.TIRED));
if (pet.getEnergy() < 30)
pet.say(pet.getPetData().randomVocal(PetVocalsType.TIRED));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
} else {
pet.say(pet.getPetData().randomVocal(PetVocalsType.DISOBEY));
}
return false;
return true;
}
}
@@ -1,25 +1,133 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetToy;
import com.eu.habbo.habbohotel.pets.*;
import com.eu.habbo.habbohotel.rooms.RoomTile;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import gnu.trove.set.hash.THashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ActionPlay extends PetAction {
private static final Logger LOGGER = LoggerFactory.getLogger(ActionPlay.class);
public ActionPlay() {
super(null, false);
super(PetTasks.PLAY, false);
this.minimumActionDuration = 4000;
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
//Play
//TODO Implement playing for pets. For example; go to ball, toy etc.
if (pet.getHappiness() > 75)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else {
pet.say(pet.getPetData().randomVocal(PetVocalsType.DISOBEY));
LOGGER.info("[ActionPlay] apply() called for pet: {}", pet.getName());
// Check if pet has enough energy to play
if (pet.getEnergy() < 25) {
LOGGER.info("[ActionPlay] Pet too tired, energy: {}", pet.getEnergy());
pet.say(pet.getPetData().randomVocal(PetVocalsType.TIRED));
return false;
}
if (pet.getRoom() == null || pet.getRoom().getRoomSpecialTypes() == null) {
LOGGER.info("[ActionPlay] Room or RoomSpecialTypes is null");
return false;
}
// Get all pet toys in the room
THashSet<InteractionPetToy> toys = pet.getRoom().getRoomSpecialTypes().getPetToys();
LOGGER.info("[ActionPlay] Found {} pet toys in room", toys.size());
// Find a toy to play with
HabboItem toy = pet.getPetData().randomToyItem(toys);
LOGGER.info("[ActionPlay] randomToyItem returned: {}", toy != null ? toy.getId() : "null");
// If no compatible toy, just pick any toy in the room
if (toy == null && !toys.isEmpty()) {
for (InteractionPetToy t : toys) {
toy = t;
LOGGER.info("[ActionPlay] Using any toy: {}", toy.getId());
break;
}
}
if (toy != null) {
RoomTile toyTile = pet.getRoom().getLayout().getTile(toy.getX(), toy.getY());
LOGGER.info("[ActionPlay] Toy at tile: ({}, {}), tile found: {}", toy.getX(), toy.getY(), toyTile != null);
if (toyTile != null) {
pet.clearPosture();
pet.getRoomUnit().setCanWalk(true);
pet.setTask(PetTasks.PLAY);
double distance = pet.getRoomUnit().getCurrentLocation().distance(toyTile);
LOGGER.info("[ActionPlay] Distance to toy: {}", distance);
// Check if already at the toy
if (distance == 0) {
// Already at toy - start playing immediately
LOGGER.info("[ActionPlay] Already at toy, starting play");
this.startPlaying(pet, toy);
} else {
// Walk to toy first
LOGGER.info("[ActionPlay] Setting goal location to toy");
pet.getRoomUnit().setGoalLocation(toyTile);
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
// The InteractionPetToy.onWalkOn will handle the actual play when pet arrives
}
return true;
}
}
LOGGER.info("[ActionPlay] No toy found, doing solo play");
// No toy found - play solo animation
pet.clearPosture();
pet.setTask(PetTasks.PLAY);
pet.getRoomUnit().setStatus(RoomUnitStatus.PLAY, "0");
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
pet.packetUpdate = true;
// Give smaller rewards for solo play
pet.addHappiness(10);
pet.addEnergy(-5);
pet.addExperience(3);
Emulator.getThreading().run(() -> {
if (pet.getRoomUnit() != null) {
pet.getRoomUnit().removeStatus(RoomUnitStatus.PLAY);
pet.packetUpdate = true;
}
pet.setTask(PetTasks.FREE);
}, this.minimumActionDuration);
return true;
}
private void startPlaying(Pet pet, HabboItem toy) {
pet.getRoomUnit().clearStatus();
pet.getRoomUnit().setStatus(RoomUnitStatus.PLAY, pet.getRoomUnit().getCurrentLocation().getStackHeight() + "");
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
pet.packetUpdate = true;
// Playing with toy gives better rewards
pet.addHappiness(25);
pet.addEnergy(-10);
pet.addExperience(10);
// Update toy state
toy.setExtradata("1");
pet.getRoom().updateItemState(toy);
int playDuration = 2500 + (Emulator.getRandom().nextInt(20) * 500);
Emulator.getThreading().run(() -> {
toy.setExtradata("0");
pet.getRoom().updateItem(toy);
if (pet.getRoomUnit() != null) {
pet.getRoomUnit().removeStatus(RoomUnitStatus.PLAY);
pet.packetUpdate = true;
}
pet.setTask(PetTasks.FREE);
}, playDuration);
}
}
@@ -21,6 +21,9 @@ public class ActionPlayDead extends PetAction {
pet.getRoomUnit().setStatus(RoomUnitStatus.DEAD, pet.getRoom().getStackHeight(pet.getRoomUnit().getX(), pet.getRoomUnit().getY(), false) + "");
// Playing dead is not very fun
pet.addHappiness(-3);
if (pet.getHappiness() > 50)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
@@ -3,41 +3,68 @@ package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.habbohotel.items.interactions.InteractionPushable;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomTile;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
public class ActionPlayFootball extends PetAction {
public ActionPlayFootball() {
super(null, false);
super(PetTasks.PLAY_FOOTBALL, false);
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
Room room = pet.getRoom();
if(room == null || room.getLayout() == null)
if (room == null || room.getLayout() == null) {
return false;
}
if (pet.getRoomUnit() == null) {
return false;
}
HabboItem foundBall = null;
// Find the nearest ball to the pet
HabboItem nearestBall = null;
double nearestDistance = Double.MAX_VALUE;
RoomTile petTile = pet.getRoomUnit().getCurrentLocation();
for(HabboItem item : room.getFloorItems()) {
if(item instanceof InteractionPushable) {
foundBall = item;
if (petTile == null) {
return false;
}
for (HabboItem item : room.getFloorItems()) {
if (item instanceof InteractionPushable) {
RoomTile ballTile = room.getLayout().getTile(item.getX(), item.getY());
if (ballTile != null) {
double distance = petTile.distance(ballTile);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestBall = item;
}
}
}
}
if(foundBall == null)
if (nearestBall == null) {
// No ball in room - disobey
pet.say(pet.getPetData().randomVocal(PetVocalsType.DISOBEY));
return false;
}
pet.getRoomUnit().setGoalLocation(room.getLayout().getTile(foundBall.getX(), foundBall.getY()));
// Set task and pathfind to the ball
pet.setTask(PetTasks.PLAY_FOOTBALL);
pet.getRoomUnit().setCanWalk(true);
pet.getRoomUnit().setGoalLocation(room.getLayout().getTile(nearestBall.getX(), nearestBall.getY()));
if (pet.getHappiness() > 75)
if (pet.getHappiness() > 75) {
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
} else {
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
}
return true;
}
@@ -0,0 +1,43 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
public class ActionRingOfFire extends PetAction {
public ActionRingOfFire() {
super(null, true);
this.minimumActionDuration = 4000;
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
if (pet.getRoom() == null || pet.getRoomUnit() == null) {
return false;
}
// Ring of Fire can only be performed while hanging on a tree
if (!pet.getRoomUnit().hasStatus(RoomUnitStatus.HANG)) {
pet.say(pet.getPetData().randomVocal(PetVocalsType.DISOBEY));
return false;
}
// Transition from HANG to RINGOFFIRE
pet.getRoomUnit().removeStatus(RoomUnitStatus.HANG);
pet.getRoomUnit().setStatus(RoomUnitStatus.RINGOFFIRE, pet.getRoomUnit().getCurrentLocation().getStackHeight() + "");
pet.packetUpdate = true;
// After ring of fire, go back to hanging
Emulator.getThreading().run(() -> {
pet.getRoomUnit().removeStatus(RoomUnitStatus.RINGOFFIRE);
pet.getRoomUnit().setStatus(RoomUnitStatus.HANG, "");
pet.packetUpdate = true;
}, minimumActionDuration);
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
return true;
}
}
@@ -0,0 +1,32 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.threading.runnables.PetClearPosture;
public class ActionRoll extends PetAction {
public ActionRoll() {
super(PetTasks.FREE, true);
this.minimumActionDuration = 3000;
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
pet.clearPosture();
pet.getRoomUnit().setStatus(RoomUnitStatus.LAY, pet.getRoomUnit().getCurrentLocation().getStackHeight() + "");
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.LAY, null, false), 3000);
if (pet.getHappiness() > 50)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
}
@@ -1,5 +1,6 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
@@ -10,12 +11,26 @@ import com.eu.habbo.habbohotel.users.Habbo;
public class ActionSit extends PetAction {
public ActionSit() {
super(PetTasks.SIT, true);
this.minimumActionDuration = 4000;
this.statusToRemove.add(RoomUnitStatus.BEG);
this.statusToRemove.add(RoomUnitStatus.MOVE);
this.statusToRemove.add(RoomUnitStatus.LAY);
this.statusToRemove.add(RoomUnitStatus.DEAD);
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
if (pet.getTask() != PetTasks.SIT && !pet.getRoomUnit().hasStatus(RoomUnitStatus.SIT)) {
pet.getRoomUnit().setStatus(RoomUnitStatus.SIT, pet.getRoom().getStackHeight(pet.getRoomUnit().getX(), pet.getRoomUnit().getY(), false) - 0.50 + "");
pet.getRoomUnit().cmdSit = true;
pet.getRoomUnit().setStatus(RoomUnitStatus.SIT, pet.getRoomUnit().getCurrentLocation().getStackHeight() + "");
// Sitting is a bit boring
pet.addHappiness(-2);
Emulator.getThreading().run(() -> {
pet.getRoomUnit().cmdSit = false;
pet.clearPosture();
}, this.minimumActionDuration);
if (pet.getHappiness() > 75)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
@@ -25,6 +40,6 @@ public class ActionSit extends PetAction {
return true;
}
return false;
return true;
}
}
@@ -21,6 +21,9 @@ public class ActionSpeak extends PetAction {
pet.setMuted(false);
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.SPEAK, null, false), 2000);
// Speaking/expressing itself makes pet happy
pet.addHappiness(3);
if (pet.getHappiness() > 70)
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_HAPPY));
else if (pet.getHappiness() < 30)
@@ -0,0 +1,37 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomUserRotation;
import com.eu.habbo.habbohotel.users.Habbo;
public class ActionSpin extends PetAction {
public ActionSpin() {
super(PetTasks.FREE, true);
this.minimumActionDuration = 2000;
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
pet.clearPosture();
// Spin animation - rotate through all directions
for (int i = 0; i < 8; i++) {
final int rotation = i;
Emulator.getThreading().run(() -> {
pet.getRoomUnit().setRotation(RoomUserRotation.values()[rotation]);
pet.packetUpdate = true;
}, i * 250);
}
if (pet.getHappiness() > 50)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
}
@@ -22,6 +22,10 @@ public class ActionStay extends PetAction {
pet.getRoomUnit().setCanWalk(false);
pet.setStayStartedAt(Emulator.getIntUnixTimestamp());
// Staying still is boring
pet.addHappiness(-5);
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
@@ -0,0 +1,47 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetTree;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.threading.runnables.PetClearPosture;
public class ActionSwing extends PetAction {
public ActionSwing() {
super(PetTasks.FREE, true);
this.minimumActionDuration = 4000;
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
// Swing requires pet to be on a pet tree (dragon/monkey tree)
if (pet.getRoom() == null) {
return false;
}
HabboItem itemBelow = pet.getRoom().getTopItemAt(pet.getRoomUnit().getX(), pet.getRoomUnit().getY());
if (!(itemBelow instanceof InteractionPetTree)) {
// Pet must go to tree first
pet.findTree();
pet.say(pet.getPetData().randomVocal(PetVocalsType.DISOBEY));
return false;
}
pet.clearPosture();
pet.getRoomUnit().setStatus(RoomUnitStatus.SWING, "");
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.SWING, null, false), 4000);
if (pet.getHappiness() > 50)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
}
@@ -0,0 +1,27 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.users.Habbo;
public class ActionSwitch extends PetAction {
public ActionSwitch() {
super(PetTasks.FREE, true);
this.minimumActionDuration = 1000;
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
pet.clearPosture();
// Switch/toggle behavior - pet acknowledges command
if (pet.getHappiness() > 50)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
}
@@ -0,0 +1,39 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomTile;
import com.eu.habbo.habbohotel.users.Habbo;
public class ActionTeleport extends PetAction {
public ActionTeleport() {
super(PetTasks.FREE, true);
this.minimumActionDuration = 1000;
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
// Teleport pet to a random walkable tile near owner
if (habbo != null && habbo.getRoomUnit() != null && pet.getRoom() != null) {
RoomTile targetTile = pet.getRoom().getLayout().getTileInFront(
habbo.getRoomUnit().getCurrentLocation(),
habbo.getRoomUnit().getBodyRotation().getValue()
);
if (targetTile != null && targetTile.isWalkable()) {
pet.getRoomUnit().setLocation(targetTile);
pet.getRoomUnit().setZ(targetTile.getStackHeight());
pet.packetUpdate = true;
}
}
if (pet.getHappiness() > 50)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
}
@@ -23,6 +23,7 @@ public class ActionTorch extends PetAction {
return false;
}
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.EAT, null, false), this.minimumActionDuration);
return true;
}
@@ -0,0 +1,40 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.threading.runnables.PetClearPosture;
public class ActionTripleJump extends PetAction {
public ActionTripleJump() {
super(PetTasks.JUMP, true);
this.minimumActionDuration = 4000;
this.statusToSet.add(RoomUnitStatus.JUMP);
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
pet.clearPosture();
// Triple jump - three jump animations
for (int i = 0; i < 3; i++) {
Emulator.getThreading().run(() -> {
pet.getRoomUnit().setStatus(RoomUnitStatus.JUMP, "");
pet.packetUpdate = true;
}, i * 1200);
}
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.JUMP, null, false), 4000);
if (pet.getHappiness() > 70)
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
}
@@ -0,0 +1,32 @@
package com.eu.habbo.habbohotel.pets.actions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.threading.runnables.PetClearPosture;
public class ActionWagTail extends PetAction {
public ActionWagTail() {
super(PetTasks.FREE, true);
this.minimumActionDuration = 2000;
}
@Override
public boolean apply(Pet pet, Habbo habbo, String[] data) {
pet.clearPosture();
pet.getRoomUnit().setStatus(RoomUnitStatus.WAG_TAIL, "");
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.WAG_TAIL, null, false), 2000);
if (pet.getHappiness() > 40)
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_HAPPY));
else
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return true;
}
}
@@ -4,6 +4,7 @@ import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetAction;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.threading.runnables.PetClearPosture;
@@ -22,9 +23,15 @@ public class ActionWave extends PetAction {
pet.getRoomUnit().setStatus(RoomUnitStatus.WAVE, "0");
Emulator.getThreading().run(new PetClearPosture(pet, RoomUnitStatus.WAVE, null, false), 2000);
// Waving is a fun trick
pet.addHappiness(5);
pet.say(pet.getPetData().randomVocal(PetVocalsType.PLAYFUL));
return true;
}
pet.say(pet.getPetData().randomVocal(PetVocalsType.GENERIC_NEUTRAL));
return false;
}
}
@@ -0,0 +1,175 @@
package com.eu.habbo.habbohotel.pets.breeding;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetBreedingNest;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.users.Habbo;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Represents an active breeding session between two pets.
* Manages the state and lifecycle of the breeding process.
*/
public class PetBreedingSession {
private final InteractionPetBreedingNest nest;
private final Pet petOne;
private Pet petTwo;
private final long startTime;
private BreedingState state;
private ScheduledFuture<?> timeoutTask;
/**
* Represents the various states of a breeding session.
*/
public enum BreedingState {
WAITING_FOR_SECOND_PET,
WAITING_FOR_CONFIRMATION,
BREEDING_IN_PROGRESS,
COMPLETED,
CANCELLED
}
/**
* Creates a new breeding session with the first pet.
* @param nest The breeding nest item
* @param firstPet The first pet to enter the nest
*/
public PetBreedingSession(InteractionPetBreedingNest nest, Pet firstPet) {
this.nest = nest;
this.petOne = firstPet;
this.petTwo = null;
this.startTime = System.currentTimeMillis();
this.state = BreedingState.WAITING_FOR_SECOND_PET;
// Auto-cancel if second pet doesn't arrive within configured timeout
int timeoutSeconds = Emulator.getConfig().getInt("pet.breeding.timeout_seconds", 120);
this.timeoutTask = Emulator.getThreading().getService().schedule(() -> {
if (this.state == BreedingState.WAITING_FOR_SECOND_PET) {
this.cancel("Timeout waiting for second pet");
}
}, timeoutSeconds, TimeUnit.SECONDS);
}
/**
* Attempts to add the second pet to the breeding session.
* @param pet The second pet to add
* @return true if the pet was successfully added
*/
public boolean addSecondPet(Pet pet) {
if (this.state != BreedingState.WAITING_FOR_SECOND_PET) {
return false;
}
// Validate compatibility - must be same pet type
if (pet.getPetData().getType() != this.petOne.getPetData().getType()) {
return false;
}
// Check if breeding is possible for this pet type
if (pet.getPetData().getOffspringType() == -1) {
return false;
}
// Don't allow breeding with self
if (pet.getId() == this.petOne.getId()) {
return false;
}
this.petTwo = pet;
this.state = BreedingState.WAITING_FOR_CONFIRMATION;
// Cancel the timeout task since we have both pets
if (this.timeoutTask != null && !this.timeoutTask.isDone()) {
this.timeoutTask.cancel(false);
}
return true;
}
/**
* Confirms the breeding and starts the process.
* @param habbo The habbo confirming the breeding
* @param offspringName The name for the offspring
*/
public void confirm(Habbo habbo, String offspringName) {
if (this.state != BreedingState.WAITING_FOR_CONFIRMATION) {
return;
}
this.state = BreedingState.BREEDING_IN_PROGRESS;
this.nest.breed(habbo, offspringName, this.petOne.getId(), this.petTwo.getId());
this.state = BreedingState.COMPLETED;
}
/**
* Cancels the breeding session and releases the pets.
* @param reason The reason for cancellation
*/
public void cancel(String reason) {
if (this.state == BreedingState.COMPLETED || this.state == BreedingState.CANCELLED) {
return;
}
this.state = BreedingState.CANCELLED;
// Release first pet
if (this.petOne != null && this.petOne.getRoomUnit() != null) {
this.petOne.getRoomUnit().setCanWalk(true);
this.petOne.setTask(PetTasks.FREE);
}
// Release second pet
if (this.petTwo != null && this.petTwo.getRoomUnit() != null) {
this.petTwo.getRoomUnit().setCanWalk(true);
this.petTwo.setTask(PetTasks.FREE);
}
// Reset nest state
this.nest.setExtradata("0");
if (this.nest.getRoomId() > 0) {
com.eu.habbo.habbohotel.rooms.Room room = com.eu.habbo.Emulator.getGameEnvironment().getRoomManager().getRoom(this.nest.getRoomId());
if (room != null) {
room.updateItem(this.nest);
}
}
// Cancel any pending timeout task
if (this.timeoutTask != null && !this.timeoutTask.isDone()) {
this.timeoutTask.cancel(false);
}
}
/**
* Checks if the breeding session is still valid.
* @return true if both pets are still in the same room
*/
public boolean isValid() {
if (this.petOne == null || this.petOne.getRoom() == null) {
return false;
}
if (this.petTwo != null && this.petTwo.getRoom() != this.petOne.getRoom()) {
return false;
}
return true;
}
// Getters
public InteractionPetBreedingNest getNest() { return nest; }
public Pet getPetOne() { return petOne; }
public Pet getPetTwo() { return petTwo; }
public long getStartTime() { return startTime; }
public BreedingState getState() { return state; }
/**
* Gets how long the session has been active in milliseconds.
* @return Duration in milliseconds
*/
public long getDuration() {
return System.currentTimeMillis() - this.startTime;
}
}
@@ -946,8 +946,20 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
}
}
this.unitManager.clear();
// Save ALL remaining pets (including owner's pets) BEFORE clearing
TIntObjectIterator<Pet> petIterator = this.getCurrentPets().iterator();
for (int i = this.getCurrentPets().size(); i-- > 0; ) {
try {
petIterator.advance();
petIterator.value().needsUpdate = true;
petIterator.value().run(); // Run synchronously to ensure DB is updated before room reload
} catch (NoSuchElementException e) {
LOGGER.error("Caught exception", e);
break;
}
}
this.unitManager.clear();
this.unitManager.clearBots();
this.unitManager.clearPets();
} catch (Exception e) {
@@ -434,6 +434,12 @@ public class RoomCycleManager {
}
if (!unit.isWalking() && !unit.cmdSit) {
// Don't override special pet statuses with SIT
boolean hasSpecialPetStatus = unit.hasStatus(RoomUnitStatus.HANG)
|| unit.hasStatus(RoomUnitStatus.SWING)
|| unit.hasStatus(RoomUnitStatus.FLAME)
|| unit.hasStatus(RoomUnitStatus.PLAY);
RoomTile thisTile = this.room.getLayout().getTile(unit.getX(), unit.getY());
HabboItem topItem = this.room.getTallestChair(thisTile);
@@ -442,7 +448,7 @@ public class RoomCycleManager {
unit.removeStatus(RoomUnitStatus.SIT);
update = true;
}
} else if (thisTile.state == RoomTileState.SIT && (!unit.hasStatus(RoomUnitStatus.SIT)
} else if (!hasSpecialPetStatus && thisTile.state == RoomTileState.SIT && (!unit.hasStatus(RoomUnitStatus.SIT)
|| unit.sitUpdate)) {
this.room.dance(unit, DanceType.NONE);
unit.setStatus(RoomUnitStatus.SIT, (Item.getCurrentHeight(topItem) * 1.0D) + "");
@@ -13,11 +13,9 @@ import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.Interaction
import com.eu.habbo.habbohotel.items.interactions.games.freeze.InteractionFreezeExitTile;
import com.eu.habbo.habbohotel.items.interactions.games.tag.InteractionTagField;
import com.eu.habbo.habbohotel.items.interactions.games.tag.InteractionTagPole;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionNest;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetBreedingNest;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetDrink;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetFood;
import com.eu.habbo.habbohotel.items.interactions.pets.*;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredBlob;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboInfo;
import com.eu.habbo.habbohotel.users.HabboItem;
@@ -25,23 +23,10 @@ import com.eu.habbo.habbohotel.users.HabboManager;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.habbohotel.wired.tick.WiredTickable;
import com.eu.habbo.messages.outgoing.inventory.AddHabboItemComposer;
import com.eu.habbo.messages.outgoing.rooms.items.FloorItemUpdateComposer;
import com.eu.habbo.messages.outgoing.rooms.items.ItemStateComposer;
import com.eu.habbo.messages.outgoing.rooms.items.RemoveFloorItemComposer;
import com.eu.habbo.messages.outgoing.rooms.items.RemoveWallItemComposer;
import com.eu.habbo.messages.outgoing.rooms.items.WallItemUpdateComposer;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.messages.outgoing.rooms.items.AddFloorItemComposer;
import com.eu.habbo.messages.outgoing.rooms.items.AddWallItemComposer;
import com.eu.habbo.messages.outgoing.rooms.items.FloorItemOnRollerComposer;
import com.eu.habbo.messages.outgoing.rooms.items.*;
import com.eu.habbo.plugin.Event;
import com.eu.habbo.plugin.events.furniture.FurnitureBuildheightEvent;
import com.eu.habbo.plugin.events.furniture.FurnitureMovedEvent;
import com.eu.habbo.plugin.events.furniture.FurniturePickedUpEvent;
import com.eu.habbo.plugin.events.furniture.FurniturePlacedEvent;
import com.eu.habbo.plugin.events.furniture.FurnitureRotatedEvent;
import com.eu.habbo.plugin.events.furniture.*;
import gnu.trove.TCollections;
import org.apache.commons.math3.util.Pair;
import gnu.trove.iterator.TIntObjectIterator;
import gnu.trove.map.TIntIntMap;
import gnu.trove.map.TIntObjectMap;
@@ -49,6 +34,7 @@ import gnu.trove.map.hash.THashMap;
import gnu.trove.map.hash.TIntIntHashMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.set.hash.THashSet;
import org.apache.commons.math3.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -661,6 +647,10 @@ public class RoomItemManager {
specialTypes.addPetDrink((InteractionPetDrink) item);
} else if (item instanceof InteractionPetFood) {
specialTypes.addPetFood((InteractionPetFood) item);
} else if (item instanceof InteractionPetToy) {
specialTypes.addPetToy((InteractionPetToy) item);
} else if (item instanceof InteractionPetTree) {
specialTypes.addPetTree((InteractionPetTree) item);
} else if (item instanceof InteractionMoodLight ||
item instanceof InteractionPyramid ||
item instanceof InteractionMusicDisc ||
@@ -780,6 +770,10 @@ public class RoomItemManager {
specialTypes.removePetDrink((InteractionPetDrink) item);
} else if (item instanceof InteractionPetFood) {
specialTypes.removePetFood((InteractionPetFood) item);
} else if (item instanceof InteractionPetToy) {
specialTypes.removePetToy((InteractionPetToy) item);
} else if (item instanceof InteractionPetTree) {
specialTypes.removePetTree((InteractionPetTree) item);
} else if (item instanceof InteractionMoodLight ||
item instanceof InteractionPyramid ||
item instanceof InteractionMusicDisc ||
@@ -19,6 +19,7 @@ import com.eu.habbo.habbohotel.items.interactions.pets.InteractionNest;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetDrink;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetFood;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetToy;
import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetTree;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.habbohotel.wired.WiredConditionType;
import com.eu.habbo.habbohotel.wired.WiredEffectType;
@@ -45,6 +46,7 @@ public class RoomSpecialTypes {
private final THashMap<Integer, InteractionPetDrink> petDrinks;
private final THashMap<Integer, InteractionPetFood> petFoods;
private final THashMap<Integer, InteractionPetToy> petToys;
private final THashMap<Integer, InteractionPetTree> petTrees;
private final THashMap<Integer, InteractionRoller> rollers;
// Thread-safe wired collections using ConcurrentHashMap for better concurrency
@@ -73,6 +75,7 @@ public class RoomSpecialTypes {
this.petDrinks = new THashMap<>(0);
this.petFoods = new THashMap<>(0);
this.petToys = new THashMap<>(0);
this.petTrees = new THashMap<>(0);
this.rollers = new THashMap<>(0);
this.wiredTriggers = new ConcurrentHashMap<>();
@@ -232,6 +235,28 @@ public class RoomSpecialTypes {
}
public InteractionPetTree getPetTree(int itemId) {
return this.petTrees.get(itemId);
}
public void addPetTree(InteractionPetTree item) {
this.petTrees.put(item.getId(), item);
}
public void removePetTree(InteractionPetTree petTree) {
this.petTrees.remove(petTree.getId());
}
public THashSet<InteractionPetTree> getPetTrees() {
synchronized (this.petTrees) {
THashSet<InteractionPetTree> petTrees = new THashSet<>();
petTrees.addAll(this.petTrees.values());
return petTrees;
}
}
public InteractionRoller getRoller(int itemId) {
synchronized (this.rollers) {
return this.rollers.get(itemId);
@@ -913,6 +938,23 @@ public class RoomSpecialTypes {
public THashSet<HabboItem> getItemsOfType(Class<? extends HabboItem> type) {
THashSet<HabboItem> items = new THashSet<>();
// Check pet trees collection for InteractionPetTree type
if (type == InteractionPetTree.class) {
synchronized (this.petTrees) {
items.addAll(this.petTrees.values());
}
return items;
}
// Check pet toys collection for InteractionPetToy type
if (type == InteractionPetToy.class) {
synchronized (this.petToys) {
items.addAll(this.petToys.values());
}
return items;
}
synchronized (this.undefined) {
for (HabboItem item : this.undefined.values()) {
if (item.getClass() == type)
@@ -959,6 +1001,8 @@ public class RoomSpecialTypes {
this.nests.clear();
this.petDrinks.clear();
this.petFoods.clear();
this.petToys.clear();
this.petTrees.clear();
this.rollers.clear();
this.wiredTriggers.clear();
@@ -6,6 +6,7 @@ import com.eu.habbo.habbohotel.bots.VisitorBot;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetManager;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.pets.RideablePet;
import com.eu.habbo.habbohotel.users.DanceType;
import com.eu.habbo.habbohotel.users.Habbo;
@@ -983,7 +984,7 @@ public class RoomUnitManager {
((RideablePet) pet).setRider(null);
}
Emulator.getThreading().run(pet);
pet.run(); // Run synchronously to ensure DB is updated before returning pet to inventory
habbo.getInventory().getPetsComponent().addPet(pet);
habbo.getClient().sendResponse(new AddPetComposer(pet));
this.currentPets.remove(pet.getId());
@@ -1266,11 +1267,29 @@ public class RoomUnitManager {
// ==================== VISITOR BOT HANDLING ====================
/**
* Handles Habbo entering the room (visitor bot notification).
* Handles Habbo entering the room (visitor bot notification and pet greeting).
*/
public void habboEntered(Habbo habbo) {
habbo.getRoomUnit().animateWalk = false;
// Have pets greet their owner
synchronized (this.currentPets) {
TIntObjectIterator<Pet> petIterator = this.currentPets.iterator();
for (int i = this.currentPets.size(); i-- > 0; ) {
try {
petIterator.advance();
Pet pet = petIterator.value();
if (pet.getUserId() == habbo.getHabboInfo().getId()) {
// Pet sees its owner - greet them!
pet.say(pet.getPetData().randomVocal(PetVocalsType.GREET_OWNER));
pet.addHappiness(10);
}
} catch (Exception e) {
break;
}
}
}
synchronized (this.currentBots) {
if (habbo.getHabboInfo().getId() != this.room.getOwnerId()) {
return;
@@ -55,6 +55,11 @@ public enum RoomUnitStatus {
KICK("kck"),
WAG_TAIL("wag"),
DANCE("dan"),
RINGOFFIRE("rng"),
SWING("swg"),
HANG("hg"),
ROLL("rll"),
FLAT("flt"),
AMS("ams"),
SWIM("swm"),
TURN("trn"),
@@ -37,7 +37,7 @@ public class PetPickupEvent extends MessageHandler {
}
pet.removeFromRoom();
Emulator.getThreading().run(pet);
pet.run(); // Run synchronously to ensure DB is updated before returning pet to inventory
if (this.client.getHabbo().getHabboInfo().getId() == pet.getUserId()) {
this.client.sendResponse(new AddPetComposer(pet));
@@ -6,6 +6,7 @@ import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetFood;
import com.eu.habbo.habbohotel.pets.GnomePet;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.messages.outgoing.rooms.items.RemoveFloorItemComposer;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer;
@@ -22,28 +23,45 @@ public class PetEatAction implements Runnable {
@Override
public void run() {
if (this.pet.getRoomUnit() != null && this.pet.getRoom() != null) {
if (this.pet.levelHunger >= 20 && this.food != null && Integer.parseInt(this.food.getExtradata()) < this.food.getBaseItem().getStateCount()) {
this.pet.addHunger(-20);
// Check if food still has portions left (state < stateCount means food remaining)
int currentState = 0;
try {
currentState = Integer.parseInt(this.food.getExtradata());
} catch (NumberFormatException e) {
currentState = 0;
}
if (this.pet.levelHunger >= 10 && this.food != null && currentState < this.food.getBaseItem().getStateCount()) {
// Say eating vocal on first bite
if (currentState == 0 || Emulator.getRandom().nextInt(3) == 0) {
this.pet.say(this.pet.getPetData().randomVocal(PetVocalsType.EATING));
}
// Faster eating: reduce 40 hunger per bite instead of 20
this.pet.addHunger(-40);
this.pet.setTask(PetTasks.EAT);
this.pet.getRoomUnit().setCanWalk(false);
this.food.setExtradata(Integer.valueOf(this.food.getExtradata()) + 1 + "");
// Advance food state (each bite uses up a portion)
this.food.setExtradata((currentState + 1) + "");
this.pet.getRoom().updateItem(this.food);
if (this.pet instanceof GnomePet) {
if (this.pet.getPetData().getType() == 26) {
AchievementManager.progressAchievement(Emulator.getGameEnvironment().getHabboManager().getHabbo(this.pet.getUserId()), Emulator.getGameEnvironment().getAchievementManager().getAchievement("GnomeFeeding"), 20);
AchievementManager.progressAchievement(Emulator.getGameEnvironment().getHabboManager().getHabbo(this.pet.getUserId()), Emulator.getGameEnvironment().getAchievementManager().getAchievement("GnomeFeeding"), 40);
} else {
AchievementManager.progressAchievement(Emulator.getGameEnvironment().getHabboManager().getHabbo(this.pet.getUserId()), Emulator.getGameEnvironment().getAchievementManager().getAchievement("LeprechaunFeeding"), 20);
AchievementManager.progressAchievement(Emulator.getGameEnvironment().getHabboManager().getHabbo(this.pet.getUserId()), Emulator.getGameEnvironment().getAchievementManager().getAchievement("LeprechaunFeeding"), 40);
}
} else {
AchievementManager.progressAchievement(Emulator.getGameEnvironment().getHabboManager().getHabbo(this.pet.getUserId()), Emulator.getGameEnvironment().getAchievementManager().getAchievement("PetFeeding"), 20);
AchievementManager.progressAchievement(Emulator.getGameEnvironment().getHabboManager().getHabbo(this.pet.getUserId()), Emulator.getGameEnvironment().getAchievementManager().getAchievement("PetFeeding"), 40);
}
Emulator.getThreading().run(this, 1000);
// Faster eating: 500ms between bites instead of 1000ms
Emulator.getThreading().run(this, 500);
} else {
if (this.food != null && Integer.parseInt(this.food.getExtradata()) == this.food.getBaseItem().getStateCount()) {
Emulator.getThreading().run(new QueryDeleteHabboItem(this.food.getId()), 500);
// Food is empty - remove it
if (this.food != null && currentState >= this.food.getBaseItem().getStateCount()) {
Emulator.getThreading().run(new QueryDeleteHabboItem(this.food.getId()), 250);
if (this.pet.getRoom() != null) {
this.pet.getRoom().removeHabboItem(this.food);
this.pet.getRoom().sendComposer(new RemoveFloorItemComposer(this.food, true).compose());
@@ -3,6 +3,7 @@ package com.eu.habbo.threading.runnables;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.pets.PetTasks;
import com.eu.habbo.habbohotel.pets.PetVocalsType;
import com.eu.habbo.habbohotel.rooms.RoomTile;
import com.eu.habbo.habbohotel.users.Habbo;
@@ -19,31 +20,81 @@ public class PetFollowHabbo implements Runnable {
@Override
public void run() {
if (this.pet != null) {
if (this.pet.getTask() != PetTasks.FOLLOW)
return;
if (this.habbo != null) {
if (this.habbo.getRoomUnit() != null) {
if (this.pet.getRoomUnit() != null) {
RoomTile target = this.habbo.getHabboInfo().getCurrentRoom().getLayout().getTileInFront(this.habbo.getRoomUnit().getCurrentLocation(), Math.abs((this.habbo.getRoomUnit().getBodyRotation().getValue() + this.directionOffset + 4) % 8));
if (target != null) {
if (target.x < 0 || target.y < 0)
target = this.habbo.getHabboInfo().getCurrentRoom().getLayout().getTileInFront(this.habbo.getRoomUnit().getCurrentLocation(), this.habbo.getRoomUnit().getBodyRotation().getValue());
if (target.x >= 0 && target.y >= 0) {
if (this.pet.getRoom().getLayout().tileWalkable(target.x, target.y)) {
this.pet.getRoomUnit().setGoalLocation(target);
this.pet.getRoomUnit().setCanWalk(true);
this.pet.setTask(PetTasks.FOLLOW);
}
}
Emulator.getThreading().run(this, 500);
}
}
}
// Comprehensive null checks
if (this.pet == null || this.pet.getRoom() == null || this.pet.getRoomUnit() == null) {
return; // Stop following - pet or room is gone
}
// Check if task is any follow type
PetTasks task = this.pet.getTask();
if (task != PetTasks.FOLLOW && task != PetTasks.FOLLOW_LEFT && task != PetTasks.FOLLOW_RIGHT) {
return; // Task was changed, stop
}
// Check if habbo is still valid
if (this.habbo == null || this.habbo.getRoomUnit() == null) {
this.pet.setTask(PetTasks.FREE);
return; // Owner gone, stop following
}
// Check if habbo is still in the same room as the pet
if (this.habbo.getHabboInfo().getCurrentRoom() != this.pet.getRoom()) {
this.pet.setTask(PetTasks.FREE);
this.pet.say(this.pet.getPetData().randomVocal(PetVocalsType.GENERIC_SAD));
return;
}
// Calculate target position
RoomTile habboTile = this.habbo.getRoomUnit().getCurrentLocation();
if (habboTile == null) {
Emulator.getThreading().run(this, 500);
return;
}
int targetRotation = Math.abs((this.habbo.getRoomUnit().getBodyRotation().getValue()
+ this.directionOffset + 4) % 8);
RoomTile target = this.pet.getRoom().getLayout().getTileInFront(habboTile, targetRotation);
// Validate target tile - try alternative positions if needed
if (target == null || target.x < 0 || target.y < 0
|| !this.pet.getRoom().getLayout().tileWalkable(target.x, target.y)) {
// Try directly behind habbo
target = this.pet.getRoom().getLayout().getTileInFront(
habboTile,
(this.habbo.getRoomUnit().getBodyRotation().getValue() + 4) % 8
);
}
// Try other adjacent positions if still invalid
if (target == null || target.x < 0 || target.y < 0
|| !this.pet.getRoom().getLayout().tileWalkable(target.x, target.y)) {
// Try to the left
target = this.pet.getRoom().getLayout().getTileInFront(
habboTile,
(this.habbo.getRoomUnit().getBodyRotation().getValue() + 2) % 8
);
}
if (target == null || target.x < 0 || target.y < 0
|| !this.pet.getRoom().getLayout().tileWalkable(target.x, target.y)) {
// Try to the right
target = this.pet.getRoom().getLayout().getTileInFront(
habboTile,
(this.habbo.getRoomUnit().getBodyRotation().getValue() + 6) % 8
);
}
// If we found a valid target, move there
if (target != null && target.x >= 0 && target.y >= 0) {
if (this.pet.getRoom().getLayout().tileWalkable(target.x, target.y)) {
this.pet.getRoomUnit().setGoalLocation(target);
this.pet.getRoomUnit().setCanWalk(true);
}
}
// Continue following with slight randomization for natural behavior
int nextDelay = 400 + Emulator.getRandom().nextInt(200);
Emulator.getThreading().run(this, nextDelay);
}
}
@@ -75,7 +75,8 @@ class FreezeHandleSnowballExplosion implements Runnable {
habbos.addAll(this.thrownData.room.getHabbosAt(freezeTile.getX(), freezeTile.getY()));
for (Habbo habbo : habbos) {
if (habbo.getHabboInfo().getGamePlayer() != null && habbo.getHabboInfo().getGamePlayer() instanceof FreezeGamePlayer hPlayer) {
if (habbo.getHabboInfo().getGamePlayer() != null && habbo.getHabboInfo().getGamePlayer() instanceof FreezeGamePlayer) {
FreezeGamePlayer hPlayer = (FreezeGamePlayer) habbo.getHabboInfo().getGamePlayer();
if (!hPlayer.canGetFrozen())
continue;