🆙 Update to 4.0.2

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