From c650e411da0fdd2dde5a68036859e7b23c9daeeb Mon Sep 17 00:00:00 2001 From: duckietm Date: Fri, 9 Jan 2026 09:41:22 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=86=99=20Update=20to=204.0.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Database Updates/UpdateDatabase_to_4-0-2.sql | 672 ++++++++++++++++++ Emulator/pom.xml | 2 +- .../habbo/habbohotel/items/ItemManager.java | 9 +- .../pets/InteractionPetBreedingNest.java | 6 +- .../pets/InteractionPetDrink.java | 129 +++- .../interactions/pets/InteractionPetFood.java | 13 + .../interactions/pets/InteractionPetToy.java | 61 +- .../pets/InteractionPetTrampoline.java | 106 +++ .../interactions/pets/InteractionPetTree.java | 126 ++++ .../com/eu/habbo/habbohotel/pets/Pet.java | 241 ++++++- .../habbohotel/pets/PetBehaviorManager.java | 219 ++++++ .../eu/habbo/habbohotel/pets/PetCommand.java | 82 ++- .../com/eu/habbo/habbohotel/pets/PetData.java | 92 ++- .../eu/habbo/habbohotel/pets/PetManager.java | 21 +- .../com/eu/habbo/habbohotel/pets/PetMood.java | 50 ++ .../habbohotel/pets/PetStatsManager.java | 150 ++++ .../habbohotel/pets/actions/ActionBeg.java | 3 + .../habbohotel/pets/actions/ActionBounce.java | 35 + .../habbohotel/pets/actions/ActionBreed.java | 2 + .../pets/actions/ActionChickenDance.java | 33 + .../habbohotel/pets/actions/ActionCount.java | 27 + .../habbohotel/pets/actions/ActionCroak.java | 2 + .../habbohotel/pets/actions/ActionDance.java | 36 + .../habbohotel/pets/actions/ActionDip.java | 3 + .../habbohotel/pets/actions/ActionDown.java | 28 +- .../habbohotel/pets/actions/ActionDrink.java | 32 +- .../habbohotel/pets/actions/ActionEat.java | 37 +- .../pets/actions/ActionFlatten.java | 33 + .../habbohotel/pets/actions/ActionFollow.java | 3 + .../pets/actions/ActionFollowLeft.java | 7 +- .../pets/actions/ActionFollowRight.java | 7 +- .../habbohotel/pets/actions/ActionFree.java | 4 + .../habbohotel/pets/actions/ActionHang.java | 47 ++ .../habbohotel/pets/actions/ActionHere.java | 24 +- .../pets/actions/ActionHighJump.java | 32 + .../habbohotel/pets/actions/ActionJump.java | 3 + .../habbohotel/pets/actions/ActionMambo.java | 33 + .../pets/actions/ActionMoveForward.java | 24 +- .../habbohotel/pets/actions/ActionNest.java | 17 +- .../habbohotel/pets/actions/ActionPlay.java | 130 +++- .../pets/actions/ActionPlayDead.java | 3 + .../pets/actions/ActionPlayFootball.java | 49 +- .../pets/actions/ActionRingOfFire.java | 43 ++ .../habbohotel/pets/actions/ActionRoll.java | 32 + .../habbohotel/pets/actions/ActionSit.java | 19 +- .../habbohotel/pets/actions/ActionSpeak.java | 3 + .../habbohotel/pets/actions/ActionSpin.java | 37 + .../habbohotel/pets/actions/ActionStay.java | 4 + .../habbohotel/pets/actions/ActionSwing.java | 47 ++ .../habbohotel/pets/actions/ActionSwitch.java | 27 + .../pets/actions/ActionTeleport.java | 39 + .../habbohotel/pets/actions/ActionTorch.java | 1 + .../pets/actions/ActionTripleJump.java | 40 ++ .../pets/actions/ActionWagTail.java | 32 + .../habbohotel/pets/actions/ActionWave.java | 9 +- .../pets/breeding/PetBreedingSession.java | 175 +++++ .../com/eu/habbo/habbohotel/rooms/Room.java | 14 +- .../habbohotel/rooms/RoomCycleManager.java | 8 +- .../habbohotel/rooms/RoomItemManager.java | 32 +- .../habbohotel/rooms/RoomSpecialTypes.java | 44 ++ .../habbohotel/rooms/RoomUnitManager.java | 23 +- .../habbohotel/rooms/RoomUnitStatus.java | 5 + .../incoming/rooms/pets/PetPickupEvent.java | 2 +- .../threading/runnables/PetEatAction.java | 36 +- .../threading/runnables/PetFollowHabbo.java | 99 ++- .../freeze/FreezeHandleSnowballExplosion.java | 3 +- 66 files changed, 3187 insertions(+), 220 deletions(-) create mode 100644 Database Updates/UpdateDatabase_to_4-0-2.sql create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetTrampoline.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetTree.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetBehaviorManager.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetMood.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetStatsManager.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionBounce.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionChickenDance.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionCount.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionDance.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFlatten.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionHang.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionHighJump.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionMambo.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionRingOfFire.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionRoll.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSpin.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSwing.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSwitch.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionTeleport.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionTripleJump.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionWagTail.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/pets/breeding/PetBreedingSession.java diff --git a/Database Updates/UpdateDatabase_to_4-0-2.sql b/Database Updates/UpdateDatabase_to_4-0-2.sql new file mode 100644 index 00000000..9d094a91 --- /dev/null +++ b/Database Updates/UpdateDatabase_to_4-0-2.sql @@ -0,0 +1,672 @@ +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ===================================================== +-- SECTION 1: Pet System Emulator Settings +-- ===================================================== + +INSERT INTO `emulator_settings` (`key`, `value`) VALUES +-- Core pet limits +('hotel.pets.max.room', '15'), +('hotel.pets.max.inventory', '25'), +('hotel.pets.name.length.min', '1'), +('hotel.pets.name.length.max', '15'), +('hotel.daily.respect.pets', '3'), + +-- Command cooldown and spam prevention +('pet.command.cooldown_ms', '2000'), +('pet.command.max_same_spam', '3'), +('pet.command.spam_reset_ms', '10000'), +('pet.command.min_energy', '15'), +('pet.command.min_happiness', '10'), +('pet.command.base_obey_chance', '70'), + +-- Pet behavior settings +('pet.behavior.autonomous_action_delay', '5000'), +('pet.behavior.idle_wander_min_ms', '10000'), +('pet.behavior.idle_wander_max_ms', '30000'), + +-- Pet stats decay/recovery rates (per cycle) +('pet.stats.hunger_decay', '1'), +('pet.stats.thirst_decay', '1'), +('pet.stats.energy_decay', '1'), +('pet.stats.happiness_decay', '1'), +('pet.stats.energy_recovery', '5'), +('pet.stats.happiness_recovery', '1'), + +-- Pet thresholds (below this = needs attention) +('pet.threshold.hungry', '50'), +('pet.threshold.thirsty', '50'), +('pet.threshold.tired', '30'), +('pet.threshold.sad', '30'), + +-- Pet breeding +('pet.breeding.timeout_seconds', '120') +ON DUPLICATE KEY UPDATE `value` = VALUES(`value`); + +-- ===================================================== +-- SECTION 2: Pet Actions (Pet Type Definitions) +-- ===================================================== + +DROP TABLE IF EXISTS `pet_actions`; +CREATE TABLE `pet_actions` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `pet_type` int(11) NOT NULL, + `pet_name` varchar(32) NOT NULL DEFAULT '', + `offspring_type` int(11) NOT NULL DEFAULT -1, + `happy_actions` varchar(100) NOT NULL DEFAULT 'sml', + `tired_actions` varchar(100) NOT NULL DEFAULT 'trd', + `random_actions` varchar(100) NOT NULL DEFAULT 'lov', + `can_swim` enum('0','1') NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `pet_type` (`pet_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT INTO `pet_actions` (`pet_type`, `pet_name`, `offspring_type`, `happy_actions`, `tired_actions`, `random_actions`, `can_swim`) VALUES +(0, 'Dog', 29, 'sml,wav,joy', 'trd,yng', 'lov,snf', '0'), +(1, 'Cat', 28, 'sml,pur', 'trd,yng', 'lov,lck', '0'), +(2, 'Crocodile', -1, 'sml', 'trd', 'lov,snp', '1'), +(3, 'Terrier', 25, 'sml,wav,joy', 'trd,yng', 'lov,snf', '0'), +(4, 'Bear', 24, 'sml,grw', 'trd,yng', 'lov', '0'), +(5, 'Pig', 30, 'sml,oink', 'trd,yng', 'lov,rol', '0'), +(6, 'Lion', -1, 'sml,ror', 'trd,yng', 'lov', '0'), +(7, 'Rhino', -1, 'sml', 'trd,yng', 'lov', '0'), +(8, 'Tarantula', -1, 'sml', 'trd', 'lov,crw', '0'), +(9, 'Turtle', -1, 'sml', 'trd', 'lov', '1'), +(10, 'Chick', -1, 'sml,chp', 'trd', 'lov,pck', '0'), +(11, 'Frog', -1, 'sml,crk', 'trd', 'lov,jmp', '1'), +(12, 'Dragon', -1, 'sml,flm', 'trd,smk', 'lov,fly', '0'), +(13, 'Monster', -1, 'sml', 'trd', 'lov', '0'), +(14, 'Monkey', -1, 'sml,ook', 'trd,yng', 'lov,swg', '0'), +(15, 'Horse', -1, 'sml,nei', 'trd,yng', 'lov', '0'), +(16, 'Monsterplant', -1, 'sml', 'trd', 'lov', '0'), +(17, 'Bunny', -1, 'sml,hop', 'trd,yng', 'lov', '0'), +(18, 'Evil Bunny', -1, 'sml', 'trd', 'lov', '0'), +(19, 'Bored Bunny', -1, 'sml', 'trd', 'lov', '0'), +(20, 'Cute Bunny', -1, 'sml,hop', 'trd', 'lov', '0'), +(21, 'Wise Pigeon', -1, 'sml,coo', 'trd', 'lov,pck', '0'), +(22, 'Evil Pigeon', -1, 'sml', 'trd', 'lov,pck', '0'), +(23, 'Evil Monkey', -1, 'sml', 'trd', 'lov,swg', '0'), +(24, 'Baby Bear', -1, 'sml', 'trd,yng', 'lov', '0'), +(25, 'Baby Terrier', -1, 'sml', 'trd,yng', 'lov', '0'), +(26, 'Gnome', -1, 'sml,grn', 'trd', 'lov', '0'), +(27, 'Leprechaun', -1, 'sml,grn', 'trd', 'lov,jig', '0'), +(28, 'Baby Cat', -1, 'sml', 'trd,yng', 'lov', '0'), +(29, 'Baby Dog', -1, 'sml', 'trd,yng', 'lov', '0'), +(30, 'Baby Pig', -1, 'sml', 'trd,yng', 'lov', '0'), +(31, 'Haloompa', -1, 'sml', 'trd', 'lov', '0'), +(32, 'Fools Pet', -1, 'sml', 'trd', 'lov', '0'), +(33, 'Pterodactyl', -1, 'sml,sqk', 'trd', 'lov,fly', '0'), +(34, 'Velociraptor', -1, 'sml,hss', 'trd', 'lov,clw', '0'), +(35, 'Cow', -1, 'sml,moo', 'trd,yng', 'lov,chw', '0'); + +-- ===================================================== +-- SECTION 3: Pet Commands Data (English Command Names) +-- ===================================================== +-- Command IDs mapped to PetManager.petActions: +-- 0=Free, 1=Sit, 2=Down, 3=Here, 4=Beg, 5=PlayDead, 6=Stay, 7=Follow +-- 8=Stand, 9=Jump, 10=Speak, 11=Play, 12=Silent, 13=Nest, 14=Drink +-- 15=FollowLeft, 16=FollowRight, 17=PlayFootball, 18=Teleport, 19=Bounce +-- 20=Flatten, 21=Dance, 22=Spin, 23=Switch, 24=MoveForward +-- 25=TurnLeft, 26=TurnRight, 27=Relax, 28=Croak, 29=Dip, 30=Wave +-- 31=Mambo, 32=HighJump, 33=ChickenDance, 34=TripleJump +-- 35=Wings, 36=BreatheFire, 37=Hang, 38=Torch, 40=Swing, 41=Roll +-- 42=RingOfFire, 43=Eat, 44=WagTail, 45=Count, 46=Breed + +DROP TABLE IF EXISTS `pet_commands_data`; +CREATE TABLE `pet_commands_data` ( + `command_id` int(11) NOT NULL, + `text` varchar(25) NOT NULL, + `required_level` int(11) NOT NULL DEFAULT 1, + `reward_xp` int(11) NOT NULL DEFAULT 5, + `cost_happiness` int(11) NOT NULL DEFAULT 0, + `cost_energy` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY (`command_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT INTO `pet_commands_data` (`command_id`, `text`, `required_level`, `reward_xp`, `cost_happiness`, `cost_energy`) VALUES +(0, 'free', 1, 5, 0, 0), +(1, 'sit', 1, 5, 2, 2), +(2, 'down', 2, 10, 3, 3), +(3, 'come here', 2, 10, 2, 5), +(4, 'beg', 2, 10, 3, 4), +(5, 'play dead', 3, 15, 4, 5), +(6, 'stay', 4, 10, 2, 3), +(7, 'follow', 5, 15, 3, 8), +(8, 'stand', 6, 15, 2, 3), +(9, 'jump', 6, 15, 4, 8), +(10, 'speak', 7, 10, 3, 3), +(11, 'play', 8, 5, 5, 10), +(12, 'silent', 8, 5, 2, 1), +(13, 'nest', 5, 5, 0, 0), +(14, 'drink', 1, 5, 0, 0), +(15, 'follow left', 15, 15, 4, 10), +(16, 'follow right', 15, 15, 4, 10), +(17, 'play football', 10, 5, 5, 12), +(18, 'teleport', 9, 5, 3, 5), +(19, 'bounce', 9, 5, 5, 10), +(20, 'flatten', 11, 5, 3, 4), +(21, 'dance', 12, 10, 6, 12), +(22, 'spin', 10, 5, 4, 8), +(23, 'switch', 12, 5, 3, 3), +(24, 'move forward', 17, 5, 2, 2), +(25, 'turn left', 18, 5, 2, 2), +(26, 'turn right', 18, 5, 2, 2), +(27, 'relax', 13, 5, 0, 0), +(28, 'croak', 14, 5, 3, 3), +(29, 'dip', 14, 5, 5, 10), +(30, 'wave', 5, 5, 2, 3), +(31, 'mambo', 18, 5, 6, 12), +(32, 'high jump', 18, 5, 5, 12), +(33, 'chicken dance', 7, 5, 5, 10), +(34, 'triple jump', 9, 5, 6, 15), +(35, 'spread wings', 8, 5, 4, 6), +(36, 'breathe fire', 10, 5, 5, 8), +(37, 'hang', 12, 5, 4, 6), +(38, 'torch', 6, 5, 3, 5), +(40, 'swing', 13, 5, 4, 8), +(41, 'roll', 10, 5, 5, 10), +(42, 'ring of fire', 20, 10, 8, 15), +(43, 'eat', 1, 5, 0, 0), +(44, 'wag tail', 4, 5, 3, 4), +(45, 'count', 6, 5, 4, 5), +(46, 'breed', 1, 5, 10, 20); + +-- ===================================================== +-- SECTION 4: Pet Commands (Pet Type -> Command Mapping) +-- ===================================================== + +DROP TABLE IF EXISTS `pet_commands`; +CREATE TABLE `pet_commands` ( + `pet_id` int(11) NOT NULL, + `command_id` int(11) NOT NULL, + PRIMARY KEY (`pet_id`, `command_id`), + KEY `pet_id` (`pet_id`), + KEY `command_id` (`command_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- Dog (0) - Full standard pet commands +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (0, 9), +(0, 10), (0, 11), (0, 12), (0, 13), (0, 14), (0, 15), (0, 16), (0, 17), (0, 24), +(0, 25), (0, 26), (0, 43), (0, 44), (0, 46); + +-- Cat (1) - Full standard pet commands + breed +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9), +(1, 10), (1, 11), (1, 12), (1, 13), (1, 14), (1, 15), (1, 16), (1, 17), (1, 24), +(1, 25), (1, 26), (1, 43), (1, 46); + +-- Crocodile (2) - Standard commands (can swim) +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(2, 0), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (2, 6), (2, 7), (2, 8), (2, 9), +(2, 10), (2, 11), (2, 12), (2, 13), (2, 14), (2, 15), (2, 16), (2, 17), (2, 24), +(2, 25), (2, 26), (2, 29), (2, 43); + +-- Terrier (3) - Standard commands + breed +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(3, 0), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5), (3, 6), (3, 7), (3, 8), (3, 9), +(3, 10), (3, 11), (3, 12), (3, 13), (3, 14), (3, 15), (3, 16), (3, 17), (3, 24), +(3, 25), (3, 26), (3, 43), (3, 46); + +-- Bear (4) - Standard commands + breed +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(4, 0), (4, 1), (4, 2), (4, 3), (4, 4), (4, 5), (4, 6), (4, 7), (4, 8), (4, 9), +(4, 10), (4, 11), (4, 12), (4, 13), (4, 14), (4, 15), (4, 16), (4, 17), (4, 24), +(4, 25), (4, 26), (4, 43), (4, 46); + +-- Pig (5) - Standard commands + breed +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(5, 0), (5, 1), (5, 2), (5, 3), (5, 4), (5, 5), (5, 6), (5, 7), (5, 8), (5, 9), +(5, 10), (5, 11), (5, 12), (5, 13), (5, 14), (5, 15), (5, 16), (5, 17), (5, 24), +(5, 25), (5, 26), (5, 43), (5, 46); + +-- Lion (6) - Standard commands +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(6, 0), (6, 1), (6, 2), (6, 3), (6, 4), (6, 5), (6, 6), (6, 7), (6, 8), (6, 9), +(6, 10), (6, 11), (6, 12), (6, 13), (6, 14), (6, 15), (6, 16), (6, 17), (6, 24), +(6, 25), (6, 26), (6, 43); + +-- Rhino (7) - Standard commands +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(7, 0), (7, 1), (7, 2), (7, 3), (7, 4), (7, 5), (7, 6), (7, 7), (7, 8), (7, 9), +(7, 10), (7, 11), (7, 12), (7, 13), (7, 14), (7, 15), (7, 16), (7, 17), (7, 24), +(7, 25), (7, 26), (7, 43); + +-- Tarantula (8) - Spider commands (bounce, flatten, spin, etc.) +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(8, 0), (8, 2), (8, 3), (8, 5), (8, 6), (8, 7), (8, 9), (8, 10), (8, 11), (8, 13), +(8, 14), (8, 15), (8, 16), (8, 17), (8, 19), (8, 20), (8, 21), (8, 22), (8, 23), +(8, 24), (8, 25), (8, 26), (8, 43); + +-- Turtle (9) - Aquatic commands +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(9, 0), (9, 1), (9, 2), (9, 3), (9, 6), (9, 7), (9, 8), (9, 10), (9, 11), (9, 13), +(9, 14), (9, 15), (9, 16), (9, 24), (9, 25), (9, 26), (9, 29), (9, 41), (9, 43); + +-- Chick (10) - Bird commands +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(10, 0), (10, 2), (10, 3), (10, 6), (10, 7), (10, 11), (10, 13), (10, 15), (10, 16), +(10, 17), (10, 33); + +-- Frog (11) - Amphibian commands (croak, dip, wave, mambo) +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(11, 0), (11, 1), (11, 2), (11, 3), (11, 4), (11, 5), (11, 6), (11, 7), (11, 9), +(11, 13), (11, 14), (11, 15), (11, 16), (11, 17), (11, 27), (11, 28), (11, 29), +(11, 30), (11, 31), (11, 43); + +-- Dragon (12) - Dragon special commands (fire, hang, swing, ring of fire) +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(12, 0), (12, 2), (12, 3), (12, 5), (12, 6), (12, 7), (12, 8), (12, 9), (12, 10), +(12, 11), (12, 12), (12, 13), (12, 14), (12, 15), (12, 16), (12, 22), (12, 35), +(12, 36), (12, 37), (12, 38), (12, 40), (12, 41), (12, 42), (12, 43); + +-- Monster (13) - Basic commands +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(13, 0), (13, 2), (13, 3), (13, 6), (13, 7), (13, 13); + +-- Monkey (14) - Monkey commands (wave, hang, swing) +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(14, 0), (14, 1), (14, 2), (14, 3), (14, 4), (14, 5), (14, 6), (14, 7), (14, 9), +(14, 13), (14, 14), (14, 15), (14, 16), (14, 17), (14, 27), (14, 29), (14, 30), +(14, 31), (14, 37), (14, 40), (14, 43); + +-- Horse (15) - Rideable pet commands + wag tail, count +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(15, 0), (15, 2), (15, 3), (15, 6), (15, 7), (15, 10), (15, 11), (15, 12), (15, 13), +(15, 14), (15, 15), (15, 16), (15, 24), (15, 25), (15, 26), (15, 43), (15, 44), (15, 45); + +-- Monsterplant (16) - Minimal commands +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(16, 0), (16, 14), (16, 43); + +-- Bunnies (17-20) - Bunny commands +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(17, 0), (17, 2), (17, 3), (17, 6), (17, 7), (17, 11), (17, 13), (17, 15), (17, 16), (17, 17), +(18, 0), (18, 2), (18, 3), (18, 6), (18, 7), (18, 11), (18, 13), (18, 15), (18, 16), (18, 17), +(19, 0), (19, 2), (19, 3), (19, 6), (19, 7), (19, 11), (19, 13), (19, 15), (19, 16), (19, 17), +(20, 0), (20, 2), (20, 3), (20, 6), (20, 7), (20, 11), (20, 13), (20, 15), (20, 16), (20, 17); + +-- Pigeons (21-22) +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(21, 0), (21, 2), (21, 3), (21, 6), (21, 7), (21, 11), (21, 13), (21, 15), (21, 16), (21, 17), +(22, 0), (22, 2), (22, 3), (22, 6), (22, 7), (22, 11), (22, 13), (22, 15), (22, 16), (22, 17); + +-- Evil Monkey (23) - Monkey commands +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(23, 0), (23, 1), (23, 2), (23, 3), (23, 4), (23, 5), (23, 6), (23, 7), (23, 9), +(23, 13), (23, 14), (23, 15), (23, 16), (23, 17), (23, 25), (23, 26), (23, 27), +(23, 29), (23, 30), (23, 31), (23, 37), (23, 40), (23, 43); + +-- Baby Bear (24) +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(24, 0), (24, 1), (24, 2), (24, 3), (24, 4), (24, 6), (24, 7), (24, 8), (24, 10), +(24, 11), (24, 12), (24, 13), (24, 14), (24, 15), (24, 16), (24, 17), (24, 24), +(24, 25), (24, 26), (24, 43); + +-- Baby Terrier (25) +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(25, 0), (25, 1), (25, 2), (25, 3), (25, 4), (25, 6), (25, 7), (25, 8), (25, 10), +(25, 11), (25, 12), (25, 13), (25, 14), (25, 15), (25, 16), (25, 17), (25, 24), +(25, 25), (25, 26), (25, 43); + +-- Gnome (26) +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(26, 0), (26, 1), (26, 2), (26, 3), (26, 4), (26, 6), (26, 7), (26, 8), (26, 13), +(26, 14), (26, 15), (26, 16), (26, 17), (26, 25), (26, 26), (26, 43); + +-- Leprechaun (27) +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(27, 0), (27, 1), (27, 2), (27, 3), (27, 4), (27, 6), (27, 7), (27, 8), (27, 13), +(27, 14), (27, 15), (27, 16), (27, 17), (27, 25), (27, 26), (27, 43); + +-- Baby Cat (28) +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(28, 0), (28, 1), (28, 2), (28, 3), (28, 4), (28, 6), (28, 7), (28, 8), (28, 10), +(28, 11), (28, 12), (28, 13), (28, 14), (28, 15), (28, 16), (28, 17), (28, 24), +(28, 25), (28, 26), (28, 43); + +-- Baby Dog (29) +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(29, 0), (29, 1), (29, 2), (29, 3), (29, 4), (29, 6), (29, 7), (29, 8), (29, 10), +(29, 11), (29, 12), (29, 13), (29, 14), (29, 15), (29, 16), (29, 17), (29, 24), +(29, 25), (29, 26), (29, 43); + +-- Baby Pig (30) +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(30, 0), (30, 1), (30, 2), (30, 3), (30, 4), (30, 6), (30, 7), (30, 8), (30, 10), +(30, 11), (30, 12), (30, 13), (30, 14), (30, 15), (30, 16), (30, 17), (30, 24), +(30, 25), (30, 26), (30, 43); + +-- Haloompa (31) +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(31, 0), (31, 1), (31, 2), (31, 3), (31, 4), (31, 6), (31, 7), (31, 8), (31, 13), +(31, 14), (31, 15), (31, 16), (31, 17), (31, 25), (31, 26), (31, 43); + +-- Fools Pet (32) - Full dance/trick set +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(32, 0), (32, 1), (32, 2), (32, 3), (32, 4), (32, 5), (32, 6), (32, 7), (32, 8), +(32, 9), (32, 13), (32, 14), (32, 15), (32, 16), (32, 17), (32, 21), (32, 25), +(32, 26), (32, 43); + +-- Pterodactyl (33) +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(33, 0), (33, 2), (33, 3), (33, 4), (33, 6), (33, 7), (33, 11), (33, 13), (33, 14), +(33, 15), (33, 16), (33, 25), (33, 26), (33, 43); + +-- Velociraptor (34) +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(34, 0), (34, 1), (34, 2), (34, 3), (34, 6), (34, 7), (34, 8), (34, 10), (34, 12), +(34, 13), (34, 14), (34, 15), (34, 16), (34, 17), (34, 21), (34, 26), (34, 43); + +-- Cow (35) +INSERT INTO `pet_commands` (`pet_id`, `command_id`) VALUES +(35, 0), (35, 2), (35, 3), (35, 4), (35, 6), (35, 7), (35, 13), (35, 14), (35, 15), +(35, 16), (35, 17), (35, 25), (35, 26), (35, 30), (35, 43); + +-- ===================================================== +-- SECTION 5: Pet Vocals (Pet Speech Messages) +-- ===================================================== +-- pet_id = -1 means general vocals for all pets +-- pet_id >= 0 means specific to that pet type + +CREATE TABLE IF NOT EXISTS `pet_vocals` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `pet_id` int(11) NOT NULL DEFAULT -1, + `type` varchar(20) NOT NULL, + `message` varchar(255) NOT NULL, + PRIMARY KEY (`id`), + KEY `pet_id` (`pet_id`), + KEY `type` (`type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- Clear existing vocals +DELETE FROM `pet_vocals`; + +-- ===================================================== +-- GENERAL VOCALS (pet_id = -1, used by all pets) +-- ===================================================== + +-- GREET_OWNER - When owner enters room +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(-1, 'GREET_OWNER', '*perks up excitedly*'), +(-1, 'GREET_OWNER', 'You''re back!'), +(-1, 'GREET_OWNER', '*bounces with joy*'), +(-1, 'GREET_OWNER', 'I missed you!'), +(-1, 'GREET_OWNER', '*runs in circles happily*'), +(-1, 'GREET_OWNER', 'Yay! My favorite person!'), +(-1, 'GREET_OWNER', '*jumps up and down*'), +(-1, 'GREET_OWNER', 'Finally! You''re here!'), +(-1, 'GREET_OWNER', '*tail wagging intensifies*'); + +-- LEVEL_UP - When pet gains a level +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(-1, 'LEVEL_UP', '*jumps with joy!*'), +(-1, 'LEVEL_UP', 'I leveled up!'), +(-1, 'LEVEL_UP', 'I feel stronger!'), +(-1, 'LEVEL_UP', 'Woohoo! New level!'), +(-1, 'LEVEL_UP', '*celebrates*'), +(-1, 'LEVEL_UP', 'I''m getting better!'), +(-1, 'LEVEL_UP', 'Level up! Yeah!'); + +-- MUTED - When told to be silent +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(-1, 'MUTED', '*stays quiet*'), +(-1, 'MUTED', '...'), +(-1, 'MUTED', '*zips lips*'), +(-1, 'MUTED', '*nods silently*'); + +-- UNKNOWN_COMMAND - When pet doesn't understand +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(-1, 'UNKNOWN_COMMAND', '*tilts head confused*'), +(-1, 'UNKNOWN_COMMAND', 'Huh?'), +(-1, 'UNKNOWN_COMMAND', 'I don''t understand...'), +(-1, 'UNKNOWN_COMMAND', '*looks puzzled*'), +(-1, 'UNKNOWN_COMMAND', 'What do you mean?'), +(-1, 'UNKNOWN_COMMAND', '*scratches head*'); + +-- DISOBEY - When pet refuses command +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(-1, 'DISOBEY', '*ignores command*'), +(-1, 'DISOBEY', 'Maybe later...'), +(-1, 'DISOBEY', 'I don''t feel like it'), +(-1, 'DISOBEY', '*pretends not to hear*'), +(-1, 'DISOBEY', 'Nah...'), +(-1, 'DISOBEY', '*turns away*'), +(-1, 'DISOBEY', 'Not right now'), +(-1, 'DISOBEY', '*yawns dismissively*'), +(-1, 'DISOBEY', 'Too tired for that'), +(-1, 'DISOBEY', 'Ask me again later'), +(-1, 'DISOBEY', '*looks the other way*'), +(-1, 'DISOBEY', 'I''d rather not'), +(-1, 'DISOBEY', '*shakes head*'), +(-1, 'DISOBEY', 'No thanks'), +(-1, 'DISOBEY', '*walks away slowly*'), +(-1, 'DISOBEY', 'Can''t be bothered'), +(-1, 'DISOBEY', '*pretends to be asleep*'), +(-1, 'DISOBEY', 'You can''t make me!'), +(-1, 'DISOBEY', '*stubbornly sits down*'), +(-1, 'DISOBEY', 'I refuse!'); + +-- DRINKING - When drinking water +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(-1, 'DRINKING', '*laps up water*'), +(-1, 'DRINKING', 'Refreshing!'), +(-1, 'DRINKING', '*gulp gulp*'), +(-1, 'DRINKING', 'Ah, that''s good!'), +(-1, 'DRINKING', '*slurp*'); + +-- EATING - When eating food +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(-1, 'EATING', '*munches happily*'), +(-1, 'EATING', 'Yum!'), +(-1, 'EATING', 'Delicious!'), +(-1, 'EATING', '*nom nom nom*'), +(-1, 'EATING', 'This is tasty!'), +(-1, 'EATING', '*chomps*'), +(-1, 'EATING', 'More please!'); + +-- PLAYFUL - When in playful mood +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(-1, 'PLAYFUL', '*bounces excitedly*'), +(-1, 'PLAYFUL', 'Let''s play!'), +(-1, 'PLAYFUL', '*runs around happily*'), +(-1, 'PLAYFUL', 'Play with me!'), +(-1, 'PLAYFUL', '*jumps around*'), +(-1, 'PLAYFUL', 'I wanna play!'), +(-1, 'PLAYFUL', '*brings a toy*'), +(-1, 'PLAYFUL', 'Wheee!'), +(-1, 'PLAYFUL', '*zooms around the room*'), +(-1, 'PLAYFUL', 'Catch me if you can!'); + +-- SLEEPING - When sleeping +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(-1, 'SLEEPING', '*snores softly*'), +(-1, 'SLEEPING', 'Zzz...'), +(-1, 'SLEEPING', '*mumbles in sleep*'), +(-1, 'SLEEPING', 'ZzZzZz...'), +(-1, 'SLEEPING', '*dreams peacefully*'), +(-1, 'SLEEPING', '*twitches while dreaming*'), +(-1, 'SLEEPING', '*snoozes*'), +(-1, 'SLEEPING', '*breathes slowly*'); + +-- TIRED - When tired/low energy +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(-1, 'TIRED', '*yawns*'), +(-1, 'TIRED', 'So sleepy...'), +(-1, 'TIRED', '*eyes drooping*'), +(-1, 'TIRED', 'I need rest...'); + +-- THIRSTY - When thirsty +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(-1, 'THIRSTY', '*pants*'), +(-1, 'THIRSTY', 'Water please!'), +(-1, 'THIRSTY', '*looks at water bowl*'), +(-1, 'THIRSTY', 'So thirsty...'), +(-1, 'THIRSTY', '*dry tongue*'), +(-1, 'THIRSTY', 'Need a drink!'), +(-1, 'THIRSTY', '*licks lips*'), +(-1, 'THIRSTY', 'I''m parched!'); + +-- HUNGRY - When hungry +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(-1, 'HUNGRY', '*stomach growls*'), +(-1, 'HUNGRY', 'I need food!'), +(-1, 'HUNGRY', '*looks at food bowl*'), +(-1, 'HUNGRY', 'Feed me!'), +(-1, 'HUNGRY', 'So hungry...'), +(-1, 'HUNGRY', '*tummy rumbles*'), +(-1, 'HUNGRY', 'Food please!'), +(-1, 'HUNGRY', '*drools at thought of food*'), +(-1, 'HUNGRY', 'Is it dinner time?'), +(-1, 'HUNGRY', '*sniffs around for food*'), +(-1, 'HUNGRY', 'I could eat a horse!'), +(-1, 'HUNGRY', '*begs for food*'), +(-1, 'HUNGRY', 'Starving over here!'); + +-- GENERIC_NEUTRAL - Random idle chat +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(-1, 'GENERIC_NEUTRAL', '*looks around*'), +(-1, 'GENERIC_NEUTRAL', '*sniffs the air*'), +(-1, 'GENERIC_NEUTRAL', '*stretches*'), +(-1, 'GENERIC_NEUTRAL', '*scratches ear*'), +(-1, 'GENERIC_NEUTRAL', '*observes surroundings*'), +(-1, 'GENERIC_NEUTRAL', '*sits quietly*'), +(-1, 'GENERIC_NEUTRAL', '*watches curiously*'); + +-- GENERIC_SAD - When sad/low happiness +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(-1, 'GENERIC_SAD', '*whimpers*'), +(-1, 'GENERIC_SAD', '*looks sad*'), +(-1, 'GENERIC_SAD', '*sighs*'), +(-1, 'GENERIC_SAD', '*droops head*'), +(-1, 'GENERIC_SAD', 'I''m lonely...'), +(-1, 'GENERIC_SAD', '*mopes around*'), +(-1, 'GENERIC_SAD', '*looks dejected*'), +(-1, 'GENERIC_SAD', 'Nobody loves me...'), +(-1, 'GENERIC_SAD', '*sulks in corner*'); + +-- GENERIC_HAPPY - When happy +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(-1, 'GENERIC_HAPPY', '*wags tail happily*'), +(-1, 'GENERIC_HAPPY', '*jumps with joy*'), +(-1, 'GENERIC_HAPPY', ':)'), +(-1, 'GENERIC_HAPPY', 'Life is good!'), +(-1, 'GENERIC_HAPPY', '*prances around*'), +(-1, 'GENERIC_HAPPY', '*does a happy dance*'), +(-1, 'GENERIC_HAPPY', 'I''m so happy!'), +(-1, 'GENERIC_HAPPY', '*beams with joy*'), +(-1, 'GENERIC_HAPPY', 'What a great day!'), +(-1, 'GENERIC_HAPPY', '*grins*'), +(-1, 'GENERIC_HAPPY', '*radiates happiness*'), +(-1, 'GENERIC_HAPPY', 'Yippee!'), +(-1, 'GENERIC_HAPPY', '*spins around happily*'), +(-1, 'GENERIC_HAPPY', 'This is the best!'); + +-- ===================================================== +-- PET-SPECIFIC VOCALS +-- ===================================================== + +-- Dog (0) specific vocals +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(0, 'GENERIC_HAPPY', 'Woof woof!'), +(0, 'GENERIC_HAPPY', '*wags tail furiously*'), +(0, 'GREET_OWNER', '*barks excitedly*'), +(0, 'GREET_OWNER', 'Woof! You''re home!'), +(0, 'PLAYFUL', '*drops ball at your feet*'), +(0, 'PLAYFUL', 'Throw the ball!'), +(0, 'HUNGRY', '*stares at food bowl*'), +(0, 'DISOBEY', '*chases own tail instead*'); + +-- Cat (1) specific vocals +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(1, 'GENERIC_HAPPY', '*purrs*'), +(1, 'GENERIC_HAPPY', 'Meow!'), +(1, 'GENERIC_NEUTRAL', '*grooms self*'), +(1, 'GREET_OWNER', '*rubs against leg*'), +(1, 'DISOBEY', '*looks away disdainfully*'), +(1, 'DISOBEY', '*yawns dismissively*'), +(1, 'PLAYFUL', '*pounces on shadow*'), +(1, 'SLEEPING', '*purrs while sleeping*'); + +-- Dragon (12) specific vocals +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(12, 'GENERIC_HAPPY', '*breathes small flames happily*'), +(12, 'GENERIC_HAPPY', '*roars softly*'), +(12, 'GENERIC_SAD', '*smoke puffs from nostrils*'), +(12, 'DISOBEY', '*snorts flames*'), +(12, 'DISOBEY', 'I am a DRAGON, not a servant!'), +(12, 'HUNGRY', '*eyes the nearest villager*'), +(12, 'HUNGRY', 'I require sustenance!'), +(12, 'THIRSTY', '*smoke rises as throat dries*'), +(12, 'PLAYFUL', '*chases own tail, breathing fire*'), +(12, 'GREET_OWNER', '*bows majestic head*'), +(12, 'SLEEPING', '*snores, causing small fires*'), +(12, 'LEVEL_UP', '*ROARS triumphantly!*'); + +-- Horse (15) specific vocals +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(15, 'GENERIC_HAPPY', '*neighs happily*'), +(15, 'GENERIC_NEUTRAL', '*swishes tail*'), +(15, 'GREET_OWNER', '*whinnies in greeting*'), +(15, 'DISOBEY', 'Nay. (Geddit?)'), +(15, 'HUNGRY', '*looks at hay expectantly*'), +(15, 'PLAYFUL', 'Let''s go for a ride!'), +(15, 'TIRED', '*stamps hoof wearily*'); + +-- Tarantula (8) specific vocals +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(8, 'GREET_OWNER', 'You look more edible every time!'), +(8, 'DISOBEY', '*hisses*'), +(8, 'DISOBEY', 'I do not obey mammals'), +(8, 'HUNGRY', 'Bring me fresh meat!'), +(8, 'PLAYFUL', '*dances on eight legs*'), +(8, 'GENERIC_HAPPY', '*clicks mandibles happily*'); + +-- Frog (11) specific vocals +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(11, 'GENERIC_HAPPY', 'Ribbit!'), +(11, 'GENERIC_NEUTRAL', '*croaks*'), +(11, 'GREET_OWNER', '*hops excitedly*'), +(11, 'PLAYFUL', '*catches fly with tongue*'), +(11, 'THIRSTY', '*seeks water*'); + +-- Cow (35) specific vocals +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(35, 'GENERIC_HAPPY', 'Moooo!'), +(35, 'GREET_OWNER', 'Greetings. Did you bring kale?'), +(35, 'EATING', '*chews grass thoughtfully*'), +(35, 'DISOBEY', 'I''d rather meditate'), +(35, 'LEVEL_UP', '*DING* I''m on the up!'); + +-- Lion (6) specific vocals +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(6, 'GENERIC_HAPPY', '*roars majestically*'), +(6, 'GREET_OWNER', '*nods regally*'), +(6, 'DISOBEY', 'I am the king!'), +(6, 'HUNGRY', '*eyes prey*'), +(6, 'PLAYFUL', '*pounces playfully*'); + +-- Bear (4) specific vocals +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(4, 'GENERIC_HAPPY', '*growls contentedly*'), +(4, 'HUNGRY', '*sniffs for honey*'), +(4, 'SLEEPING', '*hibernates*'), +(4, 'GREET_OWNER', '*bear hug incoming*'); + +-- Monkey (14) specific vocals +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(14, 'GENERIC_HAPPY', 'Ook ook!'), +(14, 'PLAYFUL', '*swings from furniture*'), +(14, 'GREET_OWNER', '*does a backflip*'), +(14, 'DISOBEY', '*throws something*'), +(14, 'HUNGRY', '*looks for bananas*'); + +-- Bunny (17) specific vocals +INSERT INTO `pet_vocals` (`pet_id`, `type`, `message`) VALUES +(17, 'GENERIC_HAPPY', '*hops happily*'), +(17, 'GREET_OWNER', '*twitches nose excitedly*'), +(17, 'PLAYFUL', '*binkies around*'), +(17, 'HUNGRY', '*nibbles on carrot*'); + +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/Emulator/pom.xml b/Emulator/pom.xml index 62e8f0ee..0f362d0a 100644 --- a/Emulator/pom.xml +++ b/Emulator/pom.xml @@ -6,7 +6,7 @@ com.eu.habbo Habbo - 4.0.1 + 4.0.2 UTF-8 diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java index 12baf283..c3e038e7 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java @@ -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)); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetBreedingNest.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetBreedingNest.java index ee598d8d..c9dd8beb 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetBreedingNest.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetBreedingNest.java @@ -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, "")); } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetDrink.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetDrink.java index 178e73e5..5f088ba1 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetDrink.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetDrink.java @@ -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 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); + } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetFood.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetFood.java index aec5233f..4fa374f3 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetFood.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetFood.java @@ -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) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetToy.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetToy.java index ef0a829f..d02e23df 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetToy.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetToy.java @@ -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; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetTrampoline.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetTrampoline.java new file mode 100644 index 00000000..bae8b0a5 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetTrampoline.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetTree.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetTree.java new file mode 100644 index 00000000..e65e1e4f --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetTree.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/Pet.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/Pet.java index 6e4659de..c28e39f0 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/Pet.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/Pet.java @@ -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 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 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; + } } \ No newline at end of file diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetBehaviorManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetBehaviorManager.java new file mode 100644 index 00000000..c06bccfd --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetBehaviorManager.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetCommand.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetCommand.java index 30b84482..d8f2e8e8 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetCommand.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetCommand.java @@ -45,31 +45,75 @@ public class PetCommand implements Comparable { } 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); } } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetData.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetData.java index b07677e2..cae3274b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetData.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetData.java @@ -97,15 +97,22 @@ public class PetData implements Comparable { 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 items) { List 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 { 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 items) { List 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 { 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 items) { List 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 { 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 toys) { List 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 { 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 items) { + List 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 petTypeVocals = this.petVocals.get(type); @@ -242,8 +294,10 @@ public class PetData implements Comparable { 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 { } } - 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 diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetManager.java index ee71ba50..3eac86e1 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetManager.java @@ -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); } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetMood.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetMood.java new file mode 100644 index 00000000..7eaa119f --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetMood.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetStatsManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetStatsManager.java new file mode 100644 index 00000000..4eb865c3 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/PetStatsManager.java @@ -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; } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionBeg.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionBeg.java index 9c3695b8..0b0c9fff 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionBeg.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionBeg.java @@ -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 diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionBounce.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionBounce.java new file mode 100644 index 00000000..978a4675 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionBounce.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionBreed.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionBreed.java index 83ec6207..e2051141 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionBreed.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionBreed.java @@ -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 { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionChickenDance.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionChickenDance.java new file mode 100644 index 00000000..1c73bbbb --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionChickenDance.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionCount.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionCount.java new file mode 100644 index 00000000..6a0cb3a0 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionCount.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionCroak.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionCroak.java index d54d1c5f..6a0bbad2 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionCroak.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionCroak.java @@ -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; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionDance.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionDance.java new file mode 100644 index 00000000..a6339a56 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionDance.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionDip.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionDip.java index 8f47aad6..71aa04fb 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionDip.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionDip.java @@ -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; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionDown.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionDown.java index 14d352e3..8931775b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionDown.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionDown.java @@ -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; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionDrink.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionDrink.java index 7d56c1c2..5744ec89 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionDrink.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionDrink.java @@ -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; } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionEat.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionEat.java index 0b701c6d..0c372b7d 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionEat.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionEat.java @@ -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; } - } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFlatten.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFlatten.java new file mode 100644 index 00000000..d0c37963 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFlatten.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFollow.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFollow.java index d197b43c..f11ea94a 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFollow.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFollow.java @@ -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 diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFollowLeft.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFollowLeft.java index c4c9c573..99dad2b7 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFollowLeft.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFollowLeft.java @@ -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)); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFollowRight.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFollowRight.java index 29e5a41e..1c39c619 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFollowRight.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFollowRight.java @@ -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)); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFree.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFree.java index 39fc50a1..052f95f9 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFree.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionFree.java @@ -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; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionHang.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionHang.java new file mode 100644 index 00000000..4819ecc1 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionHang.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionHere.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionHere.java index ce326c0d..3bfdf2a9 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionHere.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionHere.java @@ -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)); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionHighJump.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionHighJump.java new file mode 100644 index 00000000..bca198aa --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionHighJump.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionJump.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionJump.java index e267382b..53305b34 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionJump.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionJump.java @@ -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 diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionMambo.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionMambo.java new file mode 100644 index 00000000..be4b3ecf --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionMambo.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionMoveForward.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionMoveForward.java index abaac92e..2b9abaed 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionMoveForward.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionMoveForward.java @@ -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; } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionNest.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionNest.java index f7d994d6..fa05183f 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionNest.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionNest.java @@ -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; } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionPlay.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionPlay.java index dffa9b21..c98c6f76 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionPlay.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionPlay.java @@ -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 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); + } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionPlayDead.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionPlayDead.java index ecaee9de..532ea1fa 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionPlayDead.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionPlayDead.java @@ -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 diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionPlayFootball.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionPlayFootball.java index f5f12c94..a7e825c2 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionPlayFootball.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionPlayFootball.java @@ -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; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionRingOfFire.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionRingOfFire.java new file mode 100644 index 00000000..44f187f6 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionRingOfFire.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionRoll.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionRoll.java new file mode 100644 index 00000000..58dc9ce0 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionRoll.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSit.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSit.java index 235ce5ca..0b9886d8 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSit.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSit.java @@ -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; } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSpeak.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSpeak.java index ed3207b4..270285f3 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSpeak.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSpeak.java @@ -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) diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSpin.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSpin.java new file mode 100644 index 00000000..b762e8fa --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSpin.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionStay.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionStay.java index 7b3851cf..5c2701ad 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionStay.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionStay.java @@ -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; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSwing.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSwing.java new file mode 100644 index 00000000..7fc5c57b --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSwing.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSwitch.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSwitch.java new file mode 100644 index 00000000..5d758c0b --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionSwitch.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionTeleport.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionTeleport.java new file mode 100644 index 00000000..626963e5 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionTeleport.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionTorch.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionTorch.java index 04407850..f39c4eaf 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionTorch.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionTorch.java @@ -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; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionTripleJump.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionTripleJump.java new file mode 100644 index 00000000..52fc5c0e --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionTripleJump.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionWagTail.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionWagTail.java new file mode 100644 index 00000000..76869810 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionWagTail.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionWave.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionWave.java index 18b512b6..8986f3d0 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionWave.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/actions/ActionWave.java @@ -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; } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/breeding/PetBreedingSession.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/breeding/PetBreedingSession.java new file mode 100644 index 00000000..47d94ffd --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/breeding/PetBreedingSession.java @@ -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; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java index ab734e44..6c7c5a5a 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java @@ -946,8 +946,20 @@ public class Room implements Comparable, ISerialize, Runnable { } } - this.unitManager.clear(); + // Save ALL remaining pets (including owner's pets) BEFORE clearing + TIntObjectIterator 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) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCycleManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCycleManager.java index be8755fd..c4b6eb62 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCycleManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCycleManager.java @@ -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) + ""); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomItemManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomItemManager.java index 2a66275a..5b83b94d 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomItemManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomItemManager.java @@ -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 || diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomSpecialTypes.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomSpecialTypes.java index 3cc1745d..d6eabd1f 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomSpecialTypes.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomSpecialTypes.java @@ -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 petDrinks; private final THashMap petFoods; private final THashMap petToys; + private final THashMap petTrees; private final THashMap 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 getPetTrees() { + synchronized (this.petTrees) { + THashSet 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 getItemsOfType(Class type) { THashSet 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(); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnitManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnitManager.java index 90cced32..bfaa427a 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnitManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnitManager.java @@ -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 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; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnitStatus.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnitStatus.java index 73191762..b64989b7 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnitStatus.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnitStatus.java @@ -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"), diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetPickupEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetPickupEvent.java index 211375c3..3cce0a34 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetPickupEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetPickupEvent.java @@ -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)); diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/PetEatAction.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/PetEatAction.java index 4d7a4a96..acee6178 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/PetEatAction.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/PetEatAction.java @@ -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()); diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/PetFollowHabbo.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/PetFollowHabbo.java index b790916f..ddcfb649 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/PetFollowHabbo.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/PetFollowHabbo.java @@ -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); } } diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/freeze/FreezeHandleSnowballExplosion.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/freeze/FreezeHandleSnowballExplosion.java index 973234a6..c8ba4612 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/freeze/FreezeHandleSnowballExplosion.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/freeze/FreezeHandleSnowballExplosion.java @@ -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;