Puffle handlers

This commit is contained in:
Ben 2020-01-02 23:21:37 +00:00
parent 2f6c53c872
commit 909cb88a21
7 changed files with 818 additions and 82 deletions

View File

@ -275,18 +275,18 @@ COMMENT ON COLUMN puffle_item.clean_effect IS 'Effect on puffle clean level';
DROP TABLE IF EXISTS puffle; DROP TABLE IF EXISTS puffle;
CREATE TABLE puffle ( CREATE TABLE puffle (
id INT NOT NULL, id INT NOT NULL,
parent_id SMALLINT NOT NULL, parent_id SMALLINT DEFAULT NULL,
name VARCHAR(50) NOT NULL DEFAULT '', name VARCHAR(50) NOT NULL DEFAULT '',
cost INT NOT NULL DEFAULT 0,
member BOOLEAN NOT NULL DEFAULT FALSE, member BOOLEAN NOT NULL DEFAULT FALSE,
favourite_food SMALLINT NOT NULL, favourite_food SMALLINT NOT NULL,
favourite_toy SMALLINT DEFAULT NULL,
runaway_postcard SMALLINT DEFAULT NULL, runaway_postcard SMALLINT DEFAULT NULL,
max_food SMALLINT NOT NULL DEFAULT 100,
max_rest SMALLINT NOT NULL DEFAULT 100,
max_clean SMALLINT NOT NULL DEFAULT 100,
PRIMARY KEY (id), PRIMARY KEY (id),
CONSTRAINT puffle_ibfk_1 FOREIGN KEY (parent_id) REFERENCES puffle (id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT puffle_ibfk_1 FOREIGN KEY (parent_id) REFERENCES puffle (id) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT puffle_ibfk_2 FOREIGN KEY (favourite_food) REFERENCES puffle_item (id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT puffle_ibfk_2 FOREIGN KEY (favourite_food) REFERENCES puffle_item (id) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT puffle_ibfk_3 FOREIGN KEY (runaway_postcard) REFERENCES postcard (id) ON DELETE RESTRICT ON UPDATE CASCADE CONSTRAINT puffle_ibfk_3 FOREIGN KEY (favourite_toy) REFERENCES puffle_item (id) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT puffle_ibfk_4 FOREIGN KEY (runaway_postcard) REFERENCES postcard (id) ON DELETE RESTRICT ON UPDATE CASCADE
); );
COMMENT ON TABLE puffle IS 'Server puffle crumbs'; COMMENT ON TABLE puffle IS 'Server puffle crumbs';
@ -294,12 +294,10 @@ COMMENT ON TABLE puffle IS 'Server puffle crumbs';
COMMENT ON COLUMN puffle.id IS 'Unique puffle ID'; COMMENT ON COLUMN puffle.id IS 'Unique puffle ID';
COMMENT ON COLUMN puffle.parent_id IS 'Base color puffle ID'; COMMENT ON COLUMN puffle.parent_id IS 'Base color puffle ID';
COMMENT ON COLUMN puffle.name IS 'Puffle name'; COMMENT ON COLUMN puffle.name IS 'Puffle name';
COMMENT ON COLUMN puffle.cost IS 'Puffle cost';
COMMENT ON COLUMN puffle.member IS 'Is member-only?'; COMMENT ON COLUMN puffle.member IS 'Is member-only?';
COMMENT ON COLUMN puffle.favourite_food IS 'Favourite puffle-care item'; COMMENT ON COLUMN puffle.favourite_food IS 'Favourite puffle-care item';
COMMENT ON COLUMN puffle.runaway_postcard IS 'Runaway postcard ID'; COMMENT ON COLUMN puffle.runaway_postcard IS 'Runaway postcard ID';
COMMENT ON COLUMN puffle.max_food IS 'Maximum food level';
COMMENT ON COLUMN puffle.max_rest IS 'Maximum rest level';
COMMENT ON COLUMN puffle.max_clean IS 'Maximum clean level';
DROP TABLE IF EXISTS puffle_treasure_item; DROP TABLE IF EXISTS puffle_treasure_item;
CREATE TABLE puffle_treasure_item ( CREATE TABLE puffle_treasure_item (
@ -520,9 +518,11 @@ CREATE TABLE penguin (
ninja_matches_won INT NOT NULL DEFAULT 0, ninja_matches_won INT NOT NULL DEFAULT 0,
fire_matches_won INT NOT NULL DEFAULT 0, fire_matches_won INT NOT NULL DEFAULT 0,
water_matches_won INT NOT NULL DEFAULT 0, water_matches_won INT NOT NULL DEFAULT 0,
rainbow_adoptability SMALLINT NOT NULL DEFAULT 0, rainbow_adoptability BOOLEAN NOT NULL DEFAULT FALSE,
has_dug BOOLEAN NOT NULL DEFAULT FALSE, has_dug BOOLEAN NOT NULL DEFAULT FALSE,
puffle_handler BOOLEAN NOT NULL DEFAULT FALSE,
nuggets SMALLINT NOT NULL DEFAULT 0, nuggets SMALLINT NOT NULL DEFAULT 0,
walking INT DEFAULT NULL,
opened_playercard BOOLEAN NOT NULL DEFAULT FALSE, opened_playercard BOOLEAN NOT NULL DEFAULT FALSE,
special_wave BOOLEAN NOT NULL DEFAULT FALSE, special_wave BOOLEAN NOT NULL DEFAULT FALSE,
special_dance BOOLEAN NOT NULL DEFAULT FALSE, special_dance BOOLEAN NOT NULL DEFAULT FALSE,
@ -616,7 +616,9 @@ COMMENT ON COLUMN penguin.fire_matches_won IS 'JitsuFire matches won';
COMMENT ON COLUMN penguin.water_matches_won IS 'JitsuWater matces won'; COMMENT ON COLUMN penguin.water_matches_won IS 'JitsuWater matces won';
COMMENT ON COLUMN penguin.rainbow_adoptability IS 'Rainbow puffle adoptability status'; COMMENT ON COLUMN penguin.rainbow_adoptability IS 'Rainbow puffle adoptability status';
COMMENT ON COLUMN penguin.has_dug IS 'Puffle digging boolean'; COMMENT ON COLUMN penguin.has_dug IS 'Puffle digging boolean';
COMMENT ON COLUMN penguin.puffle_handler IS 'Has met puffle handler?';
COMMENT ON COLUMN penguin.nuggets IS 'Golden puffle nuggets'; COMMENT ON COLUMN penguin.nuggets IS 'Golden puffle nuggets';
COMMENT ON COLUMN penguin.walking IS 'Walking puffle ID';
COMMENT ON COLUMN penguin.opened_playercard IS 'Has player opened playercard?'; COMMENT ON COLUMN penguin.opened_playercard IS 'Has player opened playercard?';
COMMENT ON COLUMN penguin.map_category IS 'Currently selected map category'; COMMENT ON COLUMN penguin.map_category IS 'Currently selected map category';
COMMENT ON COLUMN penguin.status_field IS 'New player status field'; COMMENT ON COLUMN penguin.status_field IS 'New player status field';
@ -880,7 +882,7 @@ CREATE TABLE penguin_igloo_room (
flooring INT NOT NULL, flooring INT NOT NULL,
music SMALLINT NOT NULL DEFAULT 0, music SMALLINT NOT NULL DEFAULT 0,
location INT NOT NULL, location INT NOT NULL,
locked BOOLEAN NOT NULL DEFAULT FALSE, locked BOOLEAN NOT NULL DEFAULT TRUE,
PRIMARY KEY (id), PRIMARY KEY (id),
CONSTRAINT igloo_room_ibfk_1 FOREIGN KEY (penguin_id) REFERENCES penguin (id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT igloo_room_ibfk_1 FOREIGN KEY (penguin_id) REFERENCES penguin (id) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT igloo_room_ibfk_2 FOREIGN KEY (type) REFERENCES igloo (id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT igloo_room_ibfk_2 FOREIGN KEY (type) REFERENCES igloo (id) ON DELETE RESTRICT ON UPDATE CASCADE,
@ -1111,7 +1113,6 @@ CREATE TABLE penguin_puffle (
play SMALLINT NOT NULL DEFAULT 100, play SMALLINT NOT NULL DEFAULT 100,
rest SMALLINT NOT NULL DEFAULT 100, rest SMALLINT NOT NULL DEFAULT 100,
clean SMALLINT NOT NULL DEFAULT 100, clean SMALLINT NOT NULL DEFAULT 100,
walking BOOLEAN DEFAULT FALSE,
hat INT DEFAULT NULL, hat INT DEFAULT NULL,
backyard BOOLEAN DEFAULT FALSE, backyard BOOLEAN DEFAULT FALSE,
has_dug BOOLEAN DEFAULT FALSE, has_dug BOOLEAN DEFAULT FALSE,
@ -1134,11 +1135,12 @@ COMMENT ON COLUMN penguin_puffle.food IS 'Puffle health %';
COMMENT ON COLUMN penguin_puffle.play IS 'Puffle hunger %'; COMMENT ON COLUMN penguin_puffle.play IS 'Puffle hunger %';
COMMENT ON COLUMN penguin_puffle.rest IS 'Puffle rest %'; COMMENT ON COLUMN penguin_puffle.rest IS 'Puffle rest %';
COMMENT ON COLUMN penguin_puffle.clean IS 'Puffle clean %'; COMMENT ON COLUMN penguin_puffle.clean IS 'Puffle clean %';
COMMENT ON COLUMN penguin_puffle.walking IS 'Is being walked?';
COMMENT ON COLUMN penguin_puffle.hat IS 'Puffle hat item ID'; COMMENT ON COLUMN penguin_puffle.hat IS 'Puffle hat item ID';
COMMENT ON COLUMN penguin_puffle.backyard IS 'Is in backyard?'; COMMENT ON COLUMN penguin_puffle.backyard IS 'Is in backyard?';
COMMENT ON COLUMN penguin_puffle.has_dug IS 'Has dug?'; COMMENT ON COLUMN penguin_puffle.has_dug IS 'Has dug?';
ALTER TABLE penguin ADD CONSTRAINT penguin_ibfk_12 FOREIGN KEY (walking) REFERENCES penguin_puffle (id) ON DELETE RESTRICT ON UPDATE CASCADE;
DROP TABLE IF EXISTS penguin_puffle_item; DROP TABLE IF EXISTS penguin_puffle_item;
CREATE TABLE penguin_puffle_item ( CREATE TABLE penguin_puffle_item (
penguin_id INT NOT NULL, penguin_id INT NOT NULL,
@ -9102,47 +9104,47 @@ INSERT INTO puffle_item (id, parent_id, name, type, play_external, cost, quantit
(158, 158, 'Drum Roll', 'head', 'none', 0, 1, TRUE, 0, 0, 0, 0), (158, 158, 'Drum Roll', 'head', 'none', 0, 1, TRUE, 0, 0, 0, 0),
(159, 159, 'Swashbuckler Hat', 'head', 'none', 0, 1, TRUE, 0, 0, 0, 0); (159, 159, 'Swashbuckler Hat', 'head', 'none', 0, 1, TRUE, 0, 0, 0, 0);
INSERT INTO puffle (id, parent_id, name, member, favourite_food, runaway_postcard, max_food, max_rest, max_clean) VALUES INSERT INTO puffle (id, parent_id, name, cost, member, favourite_food, favourite_toy, runaway_postcard) VALUES
(0, 0, 'Blue', FALSE, 101, 100, 100, 100, 100), (0, NULL, 'Blue', 400, FALSE, 101, 27, 100),
(1, 1, 'Pink', TRUE, 107, 101, 100, 120, 80), (1, NULL, 'Pink', 400, TRUE, 107, 28, 101),
(2, 2, 'Black', TRUE, 112, 102, 120, 80, 100), (2, NULL, 'Black', 400, TRUE, 112, 31, 102),
(3, 3, 'Green', TRUE, 109, 103, 80, 100, 120), (3, NULL, 'Green', 400, TRUE, 109, 30, 103),
(4, 4, 'Purple', TRUE, 110, 104, 80, 120, 80), (4, NULL, 'Purple', 400, TRUE, 110, 35, 104),
(5, 5, 'Red', FALSE, 106, 105, 100, 80, 120), (5, NULL, 'Red', 400, FALSE, 106, 29, 105),
(6, 6, 'Yellow', TRUE, 114, 106, 100, 100, 100), (6, NULL, 'Yellow', 400, TRUE, 114, 32, 106),
(7, 7, 'White', TRUE, 111, 169, 120, 80, 100), (7, NULL, 'White', 400, TRUE, 111, 33, 169),
(8, 8, 'Orange', TRUE, 108, 109, 100, 80, 120), (8, NULL, 'Orange', 400, TRUE, 108, 34, 109),
(9, 9, 'Brown', TRUE, 113, NULL, 100, 100, 100), (9, NULL, 'Brown', 400, TRUE, 113, 36, NULL),
(10, 10, 'Rainbow', TRUE, 115, NULL, 100, 100, 100), (10, NULL, 'Rainbow', 0, TRUE, 115, 103, NULL),
(11, 11, 'Gold', TRUE, 128, NULL, 100, 100, 100), (11, NULL, 'Gold', 0, TRUE, 128, 125, NULL),
(1000, 2, 'Black T-Rex', TRUE, 112, NULL, 100, 100, 100), (1000, 2, 'Black T-Rex', 0, TRUE, 112, NULL, NULL),
(1001, 4, 'Purple T-Rex', TRUE, 110, NULL, 100, 100, 100), (1001, 4, 'Purple T-Rex', 0, TRUE, 110, NULL, NULL),
(1002, 5, 'Red Triceratops', TRUE, 106, NULL, 100, 100, 100), (1002, 5, 'Red Triceratops', 0, TRUE, 106, NULL, NULL),
(1003, 0, 'Blue Triceratops', TRUE, 101, NULL, 100, 100, 100), (1003, 0, 'Blue Triceratops', 0, TRUE, 101, NULL, NULL),
(1004, 6, 'Yellow Stegasaurus', TRUE, 114, NULL, 100, 100, 100), (1004, 6, 'Yellow Stegasaurus', 0, TRUE, 114, NULL, NULL),
(1005, 1, 'Pink Stegasaurus', TRUE, 107, NULL, 100, 100, 100), (1005, 1, 'Pink Stegasaurus', 0, TRUE, 107, NULL, NULL),
(1006, 0, 'Blue Dog', TRUE, 101, NULL, 100, 100, 100), (1006, 0, 'Blue Dog', 800, TRUE, 101, NULL, NULL),
(1007, 8, 'Orange Cat', TRUE, 108, NULL, 100, 100, 100), (1007, 8, 'Orange Cat', 800, TRUE, 108, NULL, NULL),
(1008, 3, 'Green Raccoon', TRUE, 109, NULL, 100, 100, 100), (1008, 3, 'Green Raccoon', 800, TRUE, 109, NULL, NULL),
(1009, 8, 'Orange Raccoon', TRUE, 108, NULL, 100, 100, 100), (1009, 8, 'Orange Raccoon', 800, TRUE, 108, NULL, NULL),
(1010, 1, 'Pink Raccoon', TRUE, 107, NULL, 100, 100, 100), (1010, 1, 'Pink Raccoon', 800, TRUE, 107, NULL, NULL),
(1011, 0, 'Blue Raccoon', TRUE, 101, NULL, 100, 100, 100), (1011, 0, 'Blue Raccoon', 800, TRUE, 101, NULL, NULL),
(1012, 3, 'Green Rabbit', TRUE, 109, NULL, 100, 100, 100), (1012, 3, 'Green Rabbit', 800, TRUE, 109, NULL, NULL),
(1013, 1, 'Pink Rabbit', TRUE, 107, NULL, 100, 100, 100), (1013, 1, 'Pink Rabbit', 800, TRUE, 107, NULL, NULL),
(1014, 7, 'White Rabbit', TRUE, 111, NULL, 100, 100, 100), (1014, 7, 'White Rabbit', 800, TRUE, 111, NULL, NULL),
(1015, 5, 'Red Rabbit', TRUE, 106, NULL, 100, 100, 100), (1015, 5, 'Red Rabbit', 800, TRUE, 106, NULL, NULL),
(1016, 0, 'Blue Deer', TRUE, 101, NULL, 100, 100, 100), (1016, 0, 'Blue Deer', 800, TRUE, 101, NULL, NULL),
(1017, 2, 'Black Deer', TRUE, 112, NULL, 100, 100, 100), (1017, 2, 'Black Deer', 800, TRUE, 112, NULL, NULL),
(1018, 5, 'Red Deer', TRUE, 106, NULL, 100, 100, 100), (1018, 5, 'Red Deer', 800, TRUE, 106, NULL, NULL),
(1019, 4, 'Purple Deer', TRUE, 110, NULL, 100, 100, 100), (1019, 4, 'Purple Deer', 800, TRUE, 110, NULL, NULL),
(1020, 6, 'Yellow Unicorn', TRUE, 114, NULL, 100, 100, 100), (1020, 6, 'Yellow Unicorn', 800, TRUE, 114, NULL, NULL),
(1021, 7, 'Snowman', TRUE, 111, NULL, 100, 100, 100), (1021, 7, 'Snowman', 0, TRUE, 111, NULL, NULL),
(1022, 4, 'Ghost', TRUE, 110, NULL, 100, 100, 100), (1022, 4, 'Ghost', 0, TRUE, 110, NULL, NULL),
(1023, 0, 'Crystal', TRUE, 101, NULL, 100, 100, 100), (1023, 0, 'Crystal', 0, TRUE, 101, NULL, NULL),
(1024, 3, 'Green Alien', FALSE, 109, NULL, 100, 100, 100), (1024, 3, 'Green Alien', 0, FALSE, 109, NULL, NULL),
(1025, 8, 'Orange Alien', TRUE, 108, NULL, 100, 100, 100), (1025, 8, 'Orange Alien', 0, TRUE, 108, NULL, NULL),
(1026, 6, 'Yellow Alien', TRUE, 114, NULL, 100, 100, 100), (1026, 6, 'Yellow Alien', 0, TRUE, 114, NULL, NULL),
(1027, 4, 'Purple Alien', TRUE, 110, NULL, 100, 100, 100); (1027, 4, 'Purple Alien', 0, TRUE, 110, NULL, NULL);
INSERT INTO puffle_treasure_puffle_item (puffle_id, puffle_item_id) VALUES INSERT INTO puffle_treasure_puffle_item (puffle_id, puffle_item_id) VALUES
(0, 115), (1, 115), (2, 115), (3, 115), (4, 115), (5, 115), (6, 115), (7, 115), (8, 115), (9, 115), (10, 115), (11, 115), (1000, 115), (1001, 115), (1002, 115), (1003, 115), (1004, 115), (1005, 115), (1006, 115), (1007, 115), (1008, 115), (1009, 115), (1010, 115), (1011, 115), (1012, 115), (1013, 115), (1014, 115), (1015, 115), (1016, 115), (1017, 115), (1018, 115), (1019, 115), (1020, 115), (1021, 115), (1022, 115), (1023, 115), (1024, 115), (1025, 115), (1026, 115), (1027, 115), (0, 115), (1, 115), (2, 115), (3, 115), (4, 115), (5, 115), (6, 115), (7, 115), (8, 115), (9, 115), (10, 115), (11, 115), (1000, 115), (1001, 115), (1002, 115), (1003, 115), (1004, 115), (1005, 115), (1006, 115), (1007, 115), (1008, 115), (1009, 115), (1010, 115), (1011, 115), (1012, 115), (1013, 115), (1014, 115), (1015, 115), (1016, 115), (1017, 115), (1018, 115), (1019, 115), (1020, 115), (1021, 115), (1022, 115), (1023, 115), (1024, 115), (1025, 115), (1026, 115), (1027, 115),
@ -9819,7 +9821,7 @@ INSERT INTO quest (id, name) VALUES (3, 'igloo');
INSERT INTO quest_award_item (quest_id, item_id) VALUES (1, 24023); INSERT INTO quest_award_item (quest_id, item_id) VALUES (1, 24023);
INSERT INTO quest_award_furniture (quest_id, furniture_id) VALUES (3, 2166); INSERT INTO quest_award_furniture (quest_id, furniture_id) VALUES (3, 2166);
INSERT INTO quest_award_puffle_item (quest_id, puffle_item_id) VALUES (2, 146); INSERT INTO quest_award_puffle_item (quest_id, puffle_item_id) VALUES (2, 70);
INSERT INTO quest_task (quest_id, description, room_id) VALUES (1, 'Visit the Clothes Shop', 130); INSERT INTO quest_task (quest_id, description, room_id) VALUES (1, 'Visit the Clothes Shop', 130);
INSERT INTO quest_task (quest_id, description, room_id) VALUES (2, 'Visit the Pet Shop', 310); INSERT INTO quest_task (quest_id, description, room_id) VALUES (2, 'Visit the Pet Shop', 310);

View File

@ -12,6 +12,7 @@ from houdini.data.room import Room
from houdini.data.item import Item from houdini.data.item import Item
from houdini.data.igloo import Igloo, Furniture, Flooring, Location from houdini.data.igloo import Igloo, Furniture, Flooring, Location
from houdini.data.stamp import Stamp from houdini.data.stamp import Stamp
from houdini.data.pet import Puffle, PenguinPuffle
class ChecklistError(Exception): class ChecklistError(Exception):
@ -324,6 +325,30 @@ class StampConverter(IConverter):
return None return None
class PuffleConverter(IConverter):
description = """Converts a puffle ID into a houdini.data.Puffle instance"""
async def convert(self, ctx):
puffle_id = int(ctx.argument)
try:
return ctx.p.server.puffles[puffle_id]
except KeyError:
return None
class PenguinPuffleConverter(IConverter):
description = """Converts a penguin puffle ID into a houdini.data.PenguinPuffle instance"""
async def convert(self, ctx):
puffle_id = int(ctx.argument)
try:
return ctx.p.puffles[puffle_id]
except KeyError:
return None
class SeparatorConverter(IConverter): class SeparatorConverter(IConverter):
__slots__ = ['separator', 'mapper'] __slots__ = ['separator', 'mapper']
@ -415,7 +440,10 @@ ConverterTypes = {
Igloo: IglooConverter, Igloo: IglooConverter,
Flooring: FlooringConverter, Flooring: FlooringConverter,
Location: LocationConverter, Location: LocationConverter,
Stamp: StampConverter Stamp: StampConverter,
Puffle: PuffleConverter,
PenguinPuffle: PenguinPuffleConverter
} }

View File

@ -53,9 +53,11 @@ class Penguin(db.Model):
ninja_matches_won = db.Column(db.Integer, nullable=False, server_default=db.text("0")) ninja_matches_won = db.Column(db.Integer, nullable=False, server_default=db.text("0"))
fire_matches_won = db.Column(db.Integer, nullable=False, server_default=db.text("0")) fire_matches_won = db.Column(db.Integer, nullable=False, server_default=db.text("0"))
water_matches_won = db.Column(db.Integer, nullable=False, server_default=db.text("0")) water_matches_won = db.Column(db.Integer, nullable=False, server_default=db.text("0"))
rainbow_adoptability = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) rainbow_adoptability = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
has_dug = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) has_dug = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
puffle_handler = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
nuggets = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0")) nuggets = db.Column(db.SmallInteger, nullable=False, server_default=db.text("0"))
walking = db.Column(db.ForeignKey('penguin_puffle.id', ondelete='CASCADE', onupdate='CASCADE'))
opened_playercard = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) opened_playercard = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
special_wave = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) special_wave = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
special_dance = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) special_dance = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
@ -107,6 +109,9 @@ class Penguin(db.Model):
if (self.status_field & field_bitmask) == 0: if (self.status_field & field_bitmask) == 0:
await self.update(status_field=self.status_field ^ field_bitmask).apply() await self.update(status_field=self.status_field ^ field_bitmask).apply()
def status_field_get(self, field_bitmask):
return (self.status_field & field_bitmask) != 0
@property @property
def minutes_played_today(self): def minutes_played_today(self):
async def get_minutes(): async def get_minutes():

View File

@ -7,12 +7,11 @@ class Puffle(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
parent_id = db.Column(db.ForeignKey('puffle.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) parent_id = db.Column(db.ForeignKey('puffle.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False)
name = db.Column(db.String(50), nullable=False, server_default=db.text("''::character varying")) name = db.Column(db.String(50), nullable=False, server_default=db.text("''::character varying"))
cost = db.Column(db.Integer, nullable=False, server_default=db.text("0"))
member = db.Column(db.Boolean, nullable=False, server_default=db.text("false")) member = db.Column(db.Boolean, nullable=False, server_default=db.text("false"))
favourite_food = db.Column(db.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False) favourite_food = db.Column(db.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=False)
favourite_toy = db.Column(db.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE'))
runaway_postcard = db.Column(db.ForeignKey('postcard.id', ondelete='CASCADE', onupdate='CASCADE')) runaway_postcard = db.Column(db.ForeignKey('postcard.id', ondelete='CASCADE', onupdate='CASCADE'))
max_food = db.Column(db.SmallInteger, nullable=False, server_default=db.text("100"))
max_rest = db.Column(db.SmallInteger, nullable=False, server_default=db.text("100"))
max_clean = db.Column(db.SmallInteger, nullable=False, server_default=db.text("100"))
class PuffleItem(db.Model): class PuffleItem(db.Model):
@ -72,7 +71,6 @@ class PenguinPuffle(db.Model):
play = db.Column(db.SmallInteger, nullable=False, server_default=db.text("100")) play = db.Column(db.SmallInteger, nullable=False, server_default=db.text("100"))
rest = db.Column(db.SmallInteger, nullable=False, server_default=db.text("100")) rest = db.Column(db.SmallInteger, nullable=False, server_default=db.text("100"))
clean = db.Column(db.SmallInteger, nullable=False, server_default=db.text("100")) clean = db.Column(db.SmallInteger, nullable=False, server_default=db.text("100"))
walking = db.Column(db.Boolean, server_default=db.text("false"))
hat = db.Column(db.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE')) hat = db.Column(db.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE'))
backyard = db.Column(db.Boolean, server_default=db.text("false")) backyard = db.Column(db.Boolean, server_default=db.text("false"))
has_dug = db.Column(db.Boolean, server_default=db.text("false")) has_dug = db.Column(db.Boolean, server_default=db.text("false"))

View File

@ -1,35 +1,714 @@
from houdini import handlers from houdini import handlers
from houdini.handlers import XTPacket from houdini.handlers import XTPacket
from houdini.handlers.play.navigation import handle_join_server from houdini.handlers.play.navigation import handle_join_server
from houdini.constants import ClientType from houdini.constants import ClientType, StatusField
from houdini.data.pet import PenguinPuffleCollection, PenguinPuffleItemCollection from houdini.data.pet import PenguinPuffleCollection, PenguinPuffleItemCollection, PenguinPuffle
from houdini.data.room import PenguinBackyardRoom, PenguinIglooRoom
from houdini.data.mail import PenguinPostcard
from datetime import datetime, timedelta
import time
import random
import asyncio
import operator
@handlers.handler(XTPacket('j', 'js'), after=handle_join_server) PuffleKillerInterval = 600
@handlers.player_attribute(joined_world=True) LegacyPuffleIds = [0, 1, 2, 3, 4, 5, 6, 7, 8]
BrushCareItemId = 1
BathCareItemId = 8
SleepCareItemId = 37
BasicCareInventory = [BrushCareItemId, BathCareItemId, SleepCareItemId]
async def decrease_stats(server):
while True:
await asyncio.sleep(PuffleKillerInterval)
for penguin in server.penguins_by_id.values():
if type(penguin.room) != PenguinIglooRoom or penguin.room.penguin_id != penguin.id:
for puffle_id in list(penguin.puffles.keys()):
puffle = penguin.puffles[puffle_id]
puffle_crumbs = server.puffles[puffle.puffle_id]
is_legacy_puffle = penguin.is_legacy_client and puffle.puffle_id in LegacyPuffleIds
is_vanilla_puffle = penguin.is_vanilla_client and not puffle.backyard
if is_vanilla_puffle or is_legacy_puffle:
if puffle.id == penguin.walking:
await puffle.update(
food=max(10, puffle.food - 8),
rest=max(10, puffle.rest - 8),
clean=max(10, puffle.clean - 8)
).apply()
else:
await puffle.update(
food=max(0, puffle.food - 4),
play=max(0, puffle.play - 4),
rest=max(0, puffle.rest - 4),
clean=max(0, puffle.clean - 4)
).apply()
if is_legacy_puffle and puffle.food == puffle.rest == puffle.clean == 0:
await penguin.add_inbox(server.postcards[puffle_crumbs.runaway_postcard], details=puffle.name)
await penguin.puffles.delete(puffle.id)
elif is_legacy_puffle and puffle.food < 10:
notification_aware = await PenguinPostcard.query.where(
(PenguinPostcard.penguin_id == penguin.id)
& (PenguinPostcard.postcard_id == 110)
& (PenguinPostcard.details == puffle.name)).gino.scalar()
if not notification_aware:
await penguin.add_inbox(server.postcards[110], details=puffle.name)
async def dig(p, on_command=False):
if p.walking is not None:
treasure_types = {0: 'coins', 1: 'food', 2: 'furniture', 3: 'clothing', None: None}
walking_puffle = p.puffles[p.walking]
treasure_quantity, item_id = 1, 0
if p.can_dig_gold:
treasure_types = {4: 'golden', None: None}
puffle_age = (datetime.now() - walking_puffle.adoption_date).days
puffle_health = walking_puffle.food + walking_puffle.play + walking_puffle.rest + walking_puffle.clean
age_percent = puffle_age / 365
health_percent = puffle_health / 400
overall_percent = (age_percent + health_percent * 2) / 3
if overall_percent > random.random() and p.is_member:
treasure_type_id = random.choice(list(treasure_types))
treasure_type = treasure_types[treasure_type_id]
else:
treasure_type_id = random.choice([0, None])
treasure_type = treasure_types[treasure_type_id]
if not on_command and treasure_type is None:
return await p.room.send_xt('nodig', p.id, 1)
elif treasure_type == 'food':
diggable_food_ids = [t.puffle_item_id for t in p.server.puffle_food_treasure
if t.puffle_id == walking_puffle.puffle_id
and t.puffle_item_id not in p.puffle_items]
if diggable_food_ids:
item_id = random.choice(diggable_food_ids)
await p.add_puffle_item(p.server.puffle_items[item_id], notify=False, cost=0)
if item_id == p.server.puffles[walking_puffle.puffle_id].favourite_food:
await p.add_stamp(p.server.stamps[495])
elif treasure_type == 'furniture':
diggable_furniture_ids = [t.furniture_id for t in p.server.puffle_furniture_treasure
if t.puffle_id == walking_puffle.puffle_id
and t.furniture_id not in p.furniture]
if diggable_furniture_ids:
item_id = random.choice(diggable_furniture_ids)
await p.add_furniture(p.server.furniture[item_id], notify=False, cost=0)
await p.add_stamp(p.server.stamps[494])
elif treasure_type == 'clothing':
diggable_clothing_ids = [t.item_id for t in p.server.puffle_clothing_treasure
if t.puffle_id == walking_puffle.puffle_id
and t.item_id not in p.inventory]
if diggable_clothing_ids:
item_id = random.choice(diggable_clothing_ids)
await p.add_inventory(p.server.items[item_id], notify=False, cost=0)
await p.add_stamp(p.server.stamps[494])
elif treasure_type == 'golden':
item_id = 1
treasure_quantity = random.randrange(1, 4)
await p.update(nuggets=p.nuggets + treasure_quantity).apply()
await p.send_xt('currencies', f'1|{p.nuggets}')
if not item_id:
treasure_type_id, treasure_type = 0, 'coins'
if (on_command and treasure_type is None) or treasure_type == 'coins':
treasure_quantity = random.randrange(10, 250)
await p.update(coins=p.coins + treasure_quantity).apply()
if treasure_quantity >= 50:
await p.add_stamp(p.server.stamps[493])
if not p.has_dug:
await p.add_stamp(p.server.stamps[489])
for player in p.room.penguins_by_id.values():
if player.id != p.id:
await player.add_stamp(p.server.stamps[490])
await p.room.send_xt('puffledig', p.id, p.walking, treasure_type_id, item_id,
treasure_quantity, int(not p.has_dug))
await p.update(has_dug=True).apply()
await walking_puffle.update(has_dug=True).apply()
color_dig_count = len({puffle.puffle_id for puffle in p.puffles.values() if puffle.has_dug})
if color_dig_count >= 11:
await p.add_stamp(p.server.stamps[491])
await p.server.redis.setex(f'houdini.last_dig.{p.id}', 120, int(time.time()))
dig_count = await p.server.redis.incr(f'houdini.dig_count.{p.id}')
if dig_count == 1:
await p.server.redis.expireat(f'houdini.dig_count.{p.id}',
(datetime.now() + timedelta(days=1)).timestamp())
if dig_count == 5:
await p.add_stamp(p.server.stamps[492])
await p.status_field_set(StatusField.PuffleTreasureInfographic)
async def deliver(p, care_item, puffle):
if care_item.cost != 0 and care_item.id not in p.puffle_items:
await p.add_puffle_item(care_item)
if care_item.cost == 0 or care_item.id in p.puffle_items:
if care_item.type == 'food':
quantity_owned = p.puffle_items[care_item.id].quantity
if quantity_owned > 1:
await p.puffle_items[care_item.id].update(quantity=quantity_owned - 1).apply()
elif quantity_owned == 1:
await p.puffle_items.delete(care_item.id)
if care_item.id == p.server.puffles[puffle.puffle_id].favourite_food:
await puffle.update(food=100, play=100, rest=100, clean=100).apply()
else:
await puffle.update(
food=max(0, min(puffle.food + care_item.food_effect, 100)),
play=max(0, min(puffle.play + care_item.play_effect, 100)),
rest=max(0, min(puffle.rest + care_item.rest_effect, 100)),
clean=max(0, min(puffle.clean + care_item.clean_effect, 100)),
).apply()
celebration = puffle.food == puffle.play == puffle.rest == puffle.clean == 100
care_item_delivery = f'{puffle.id}|{puffle.food}|{puffle.play}|{puffle.rest}|{puffle.clean}|{int(celebration)}'
await p.room.send_xt('pcid', p.id, care_item_delivery)
if care_item.id == 126:
p.can_dig_gold = True
await p.room.send_xt('oberry', p.id, p.walking)
await p.send_xt('currencies', f'1|{p.nuggets}')
def get_client_puffle_id(p, puffle_id):
parent_id = p.server.puffles[puffle_id].parent_id
return (parent_id, puffle_id) if parent_id is not None else (puffle_id, '')
def get_client_puffle_id_string(p, puffle_id):
parent_id, puffle_id = get_client_puffle_id(p, puffle_id)
return f'{parent_id}|{puffle_id}'
def get_my_player_puffles(p):
if p.is_vanilla_client:
return [f'{puffle.id}|{get_client_puffle_id_string(p, puffle.puffle_id)}|'
f'{puffle.name}|{int(time.mktime(puffle.adoption_date.timetuple()))}|{puffle.food}|{puffle.play}|'
f'{puffle.rest}|{puffle.clean}|{puffle.hat or 0}|0' for puffle in p.puffles.values()]
else:
return [f'{puffle.id}|{puffle.name}|{puffle.puffle_id}|{puffle.clean}|'
f'{puffle.food}|{puffle.rest}|100|100|100' for puffle in p.puffles.values()
if puffle.puffle_id in LegacyPuffleIds]
def get_my_player_walking_puffle(p):
if p.walking is not None and p.is_vanilla_client:
puffle = p.puffles[p.walking]
parent_id, puffle_id = get_client_puffle_id(p, puffle.puffle_id)
return f'{puffle.id}|{parent_id}|{puffle_id}|{puffle.hat or 0}|0'
return str()
def check_name(p, puffle_name):
tokens = puffle_name.lower().split()
clean = not any(word in tokens for word in p.server.chat_filter_words.keys())
length_ok = 1 <= len(puffle_name) <= 12
characters_ok = puffle_name.isalpha()
return characters_ok and length_ok and clean
@handlers.handler(XTPacket('j', 'js'), before=handle_join_server, pre_login=True)
@handlers.allow_once @handlers.allow_once
async def load_pet_inventory(p): async def load_pet_inventory(p):
p.puffles = await PenguinPuffleCollection.get_collection(p.id) p.puffles = await PenguinPuffleCollection.get_collection(p.id)
p.puffle_items = await PenguinPuffleItemCollection.get_collection(p.id) p.puffle_items = await PenguinPuffleItemCollection.get_collection(p.id)
await p.send_xt('pgu', *get_my_player_puffles(p))
@handlers.handler(XTPacket('p', 'getdigcooldown'), pre_login=True) @handlers.handler(XTPacket('p', 'getdigcooldown'), pre_login=True)
async def handle_get_dig_cooldown(p): async def handle_get_dig_cooldown(p):
await p.send_xt('getdigcooldown', 1) last_dig = await p.server.redis.get(f'houdini.last_dig.{p.id}')
if last_dig is not None:
cooldown_remaining = max(0, 120 - (int(time.time()) - int(last_dig)))
return await p.send_xt('getdigcooldown', cooldown_remaining)
await p.send_xt('getdigcooldown', 0)
@handlers.handler(XTPacket('p', 'checkpufflename')) @handlers.handler(XTPacket('p', 'checkpufflename'))
async def handle_check_puffle_name_with_response(p, puffle_name): async def handle_check_puffle_name_with_response(p, puffle_name):
name_ok = puffle_name.isalnum() name_ok = check_name(p, puffle_name)
await p.send_xt('checkpufflename', puffle_name, int(name_ok)) await p.send_xt('checkpufflename', puffle_name, int(name_ok))
@handlers.handler(XTPacket('p', 'pcn'))
async def handle_check_puffle_name(p, puffle_name):
name_ok = check_name(p, puffle_name)
await p.send_xt('pcn', puffle_name, int(name_ok))
@handlers.handler(XTPacket('p', 'pg'), client=ClientType.Vanilla) @handlers.handler(XTPacket('p', 'pg'), client=ClientType.Vanilla)
async def handle_get_player_puffles(p, penguin_id: int, room_type: str): async def handle_get_player_puffles_vanilla(p, penguin_id: int, room_type: str):
await p.send_xt('pg') is_backyard = room_type == 'backyard'
owned_puffles = await PenguinPuffle.query.where((PenguinPuffle.penguin_id == penguin_id)
& (PenguinPuffle.backyard == is_backyard)).gino.all()
walking = p.server.penguins_by_id[penguin_id].walking if penguin_id in p.server.penguins_by_id else None
player_puffles = [f'{puffle.id}|{get_client_puffle_id_string(p, puffle.puffle_id)}|'
f'{puffle.name}||{puffle.food}|{puffle.play}|{puffle.rest}|{puffle.clean}|'
f'{puffle.hat or 0}|0|0|{int(puffle.id == walking)}' for puffle in owned_puffles]
await p.send_xt('pg', len(owned_puffles), *player_puffles)
if len(owned_puffles) >= 10:
await p.status_field_set(StatusField.MoreThanTenPufflesBackyardMessage)
@handlers.handler(XTPacket('p', 'pg'), client=ClientType.Legacy) @handlers.handler(XTPacket('p', 'pg'), client=ClientType.Legacy)
async def handle_get_player_puffles_legacy(p, penguin_id: int): async def handle_get_player_puffles_legacy(p, penguin_id: int):
await p.send_xt('pg') owned_puffles = await PenguinPuffle.query.where((PenguinPuffle.penguin_id == penguin_id)).gino.all()
walking = p.server.penguins_by_id[penguin_id].walking if penguin_id in p.server.penguins_by_id else None
player_puffles = [f'{puffle.id}|{puffle.name}|{puffle.puffle_id}|'
f'{puffle.clean}|{puffle.food}|{puffle.rest}|100|100|100|0|0|0|{int(puffle.id == walking)}'
for puffle in owned_puffles if puffle.puffle_id in LegacyPuffleIds]
await p.send_xt('pg', *player_puffles)
@handlers.handler(XTPacket('p', 'pgu'))
async def handle_get_my_player_puffles(p):
await p.send_xt('pgu', *get_my_player_puffles(p))
@handlers.handler(XTPacket('p', 'pn'), client=ClientType.Vanilla)
async def handle_adopt_puffle_vanilla(p, type_id: int, name: str, subtype_id: int):
if type_id not in p.server.puffles or not check_name(p, name):
return await p.send_error(441)
name = name.title()
cost = p.server.puffles[type_id].cost
if p.coins < cost:
return await p.send_error(401)
if len(p.puffles) >= 75:
return await p.send_error(440)
puffle_id = subtype_id if bool(subtype_id) else type_id
if type_id == 10:
if not p.rainbow_adoptability:
return await p.send_error(441)
await p.update(rainbow_adoptability=False).apply()
elif type_id == 11:
await p.update(nuggets=p.nuggets - 15).apply()
p.can_dig_gold = False
elif subtype_id == 0:
await p.add_puffle_item(p.server.puffle_items[3], quantity=5, cost=0)
await p.add_puffle_item(p.server.puffle_items[79], cost=0)
await p.add_puffle_item(p.server.puffle_items[p.server.puffles[puffle_id].favourite_toy])
await p.update(coins=p.coins - cost).apply()
puffle = await p.puffles.insert(puffle_id=puffle_id, name=name)
parent_id, puffle_id = get_client_puffle_id(p, puffle.puffle_id)
puffle_string = f'{puffle.id}|{parent_id}|{puffle_id}|{puffle.name}|{int(time.time())}' \
f'|100|100|100|100|0|0'
await p.send_xt('pn', p.coins, puffle_string)
await p.add_inbox(p.server.postcards[111], details=puffle.name)
igloo_puffle_count = sum(not puff.backyard for puff in p.puffles.values())
if igloo_puffle_count > 10:
puffle_to_relocate = next(puff for puff in p.puffles.values() if not puff.backyard)
await puffle_to_relocate.update(backyard=True).apply()
@handlers.handler(XTPacket('p', 'pn'), client=ClientType.Legacy)
async def handle_adopt_puffle_legacy(p, type_id: int, name: str):
if type_id not in LegacyPuffleIds or not check_name(p, name):
return await p.send_error(441)
name = name.title()
cost = 800
if p.coins < cost:
return await p.send_error(401)
if len(p.puffles) >= 18:
return await p.send_error(440)
await p.update(coins=p.coins - cost).apply()
puffle = await p.puffles.insert(puffle_id=type_id, name=name)
puffle_string = f'{puffle.id}|{puffle.name}|{puffle.puffle_id}|100|100|100|100|100|100'
await p.send_xt('pn', p.coins, puffle_string)
await p.add_inbox(p.server.postcards[111], details=puffle.name)
await p.add_puffle_item(p.server.puffle_items[p.server.puffles[type_id].favourite_toy], notify=False)
await p.send_xt('pgu', *get_my_player_puffles(p))
@handlers.handler(XTPacket('p', 'pgpi'), client=ClientType.Vanilla)
async def handle_get_care_inventory(p):
await p.send_xt('pgpi',
*(f'{item_id}|1' for item_id in BasicCareInventory),
*(f'{care_item.item_id}|{care_item.quantity}' for care_item in p.puffle_items.values()))
@handlers.handler(XTPacket('p', 'pm'))
async def handle_puffle_move(p, puffle: PenguinPuffle, x: int, y: int):
await p.room.send_xt('pm', f'{puffle.id}|{x}|{y}', f=operator.attrgetter('is_vanilla_client'))
await p.room.send_xt('pm', puffle.id, x, y, f=operator.attrgetter('is_legacy_client'))
@handlers.handler(XTPacket('p', 'ps'))
async def handle_puffle_frame(p, puffle_id: int, frame_id: int):
if puffle_id in p.puffles:
await p.room.send_xt('ps', puffle_id, frame_id)
@handlers.handler(XTPacket('p', 'pw'), client=ClientType.Vanilla)
async def handle_puffle_walk_vanilla(p, puffle: PenguinPuffle, walking: int):
if not p.walking and walking:
await p.update(walking=puffle.id).apply()
parent_id, puffle_id = get_client_puffle_id(p, puffle.puffle_id)
await p.room.send_xt('pw', p.id, puffle.id, parent_id, puffle_id, 1, puffle.hat or 0)
elif not walking and puffle.id == p.walking:
igloo_puffle_count = sum(not puff.backyard and puff.id != puffle.id for puff in p.puffles.values())
in_backyard = type(p.room) == PenguinBackyardRoom
return_to_backyard = in_backyard or type(p.room) != PenguinIglooRoom and puffle.backyard
if igloo_puffle_count >= 10 and not return_to_backyard:
return await p.send_error(443)
await puffle.update(backyard=return_to_backyard).apply()
await p.update(walking=None).apply()
await p.room.send_xt('pw', p.id, puffle.id, 0, 0, 0, 0)
puffle_string = f'{puffle.id}||||||||||||{walking}'
await p.room.send_xt('pw', p.id, puffle_string, f=operator.attrgetter('is_legacy_client'))
p.can_dig_gold = False
if not p.status_field_get(StatusField.HasWalkedPuffleFirstTime):
await p.status_field_set(StatusField.HasWalkedPuffleFirstTime)
else:
await p.status_field_set(StatusField.HasWalkedPuffleSecondTime)
@handlers.handler(XTPacket('p', 'pw'), client=ClientType.Legacy)
async def handle_puffle_walk_legacy(p, puffle: PenguinPuffle, walking: int):
if puffle.id != p.walking and walking:
if puffle.rest < 20 and puffle.food < 40:
return
await p.update(walking=puffle.id).apply()
await p.room.send_xt('pw', p.id, puffle.id, -1, str(), 1, 0,
f=operator.attrgetter('is_vanilla_client'))
elif puffle.id == p.walking and not walking:
await p.update(walking=None).apply()
await p.room.send_xt('pw', p.id, puffle.id, 0, 0, 0, 0,
f=operator.attrgetter('is_vanilla_client'))
puffle_string = f'{puffle.id}||||||||||||{walking}'
await p.room.send_xt('pw', p.id, puffle_string, f=operator.attrgetter('is_legacy_client'))
@handlers.handler(XTPacket('s', 'upa'), client=ClientType.Legacy)
async def handle_wear_puffle(p, item_id: int):
if p.walking:
walking_puffle = p.puffles[p.walking]
if item_id == walking_puffle.puffle_id + 750:
await p.update(hand=item_id).apply()
await p.room.send_xt('upa', p.id, item_id)
else:
await p.update(walking=None).apply()
@handlers.disconnected
@handlers.player_attribute(client_type=ClientType.Legacy)
async def handle_stop_walking(p):
if p.walking:
await p.update(hand=None, walking=None).apply()
@handlers.handler(XTPacket('p', 'pp'), client=ClientType.Vanilla)
async def handle_puffle_play_vanilla(p, puffle: PenguinPuffle):
favourite_toy = p.server.puffle_items[p.server.puffles[puffle.puffle_id].favourite_toy]
await deliver(p, favourite_toy, puffle)
legacy_puffle_string = f'{puffle.id}|puf|fle|{puffle.clean}|{puffle.food}|{puffle.rest}'
vanilla_puffle_string = f'{puffle.id}|{puffle.food}|{puffle.play}|{puffle.rest}|{puffle.clean}'
await p.room.send_xt('pp', legacy_puffle_string, 1, f=operator.attrgetter('is_legacy_client'))
await p.room.send_xt('pp', p.id, vanilla_puffle_string, f=operator.attrgetter('is_vanilla_client'))
@handlers.handler(XTPacket('p', 'pp'), client=ClientType.Legacy)
async def handle_puffle_play_legacy(p, puffle: PenguinPuffle):
if puffle.rest < 20 or puffle.clean < 10:
return
negative_food = random.randrange(10, 25)
negative_rest = random.randrange(10, 25)
await puffle.update(
food=max(0, puffle.food - negative_food),
rest=max(0, puffle.rest - negative_rest),
clean=min(100, puffle.clean + 10)
).apply()
play_type = 1 if puffle.rest > 80 else random.choice([0, 2])
puffle_string = f'{puffle.id}|puf|fle|{puffle.clean}|{puffle.food}|{puffle.rest}'
await p.room.send_xt('pp', puffle_string, play_type, f=operator.attrgetter('is_legacy_client'))
@handlers.handler(XTPacket('p', 'pr'), client=ClientType.Vanilla)
async def handle_puffle_rest_vanilla(p, puffle: PenguinPuffle):
sleep = p.server.puffle_items[37]
await deliver(p, sleep, puffle)
legacy_puffle_string = f'{puffle.id}|puf|fle|{puffle.clean}|{puffle.food}|{puffle.rest}'
vanilla_puffle_string = f'{puffle.id}|{puffle.food}|{puffle.play}|{puffle.rest}|{puffle.clean}'
await p.room.send_xt('pr', legacy_puffle_string, f=operator.attrgetter('is_legacy_client'))
await p.room.send_xt('pr', p.id, vanilla_puffle_string, f=operator.attrgetter('is_vanilla_client'))
@handlers.handler(XTPacket('p', 'pr'), client=ClientType.Legacy)
async def handle_puffle_rest_legacy(p, puffle: PenguinPuffle):
positive_rest = random.randrange(15, 40)
await puffle.update(rest=min(100, puffle.rest + positive_rest)).apply()
puffle_string = f'{puffle.id}|puf|fle|{puffle.clean}|{puffle.food}|{puffle.rest}'
await p.room.send_xt('pr', puffle_string, f=operator.attrgetter('is_legacy_client'))
@handlers.handler(XTPacket('p', 'pt'), client=ClientType.Legacy)
async def handle_puffle_treat_legacy(p, puffle: PenguinPuffle, treat_id: int):
if p.coins > 5:
positive_food = random.randrange(5, 15)
await puffle.update(food=min(100, puffle.food + positive_food)).apply()
await p.update(coins=p.coins - 5).apply()
puffle_string = f'{puffle.id}|puf|fle|{puffle.clean}|{puffle.food}|{puffle.rest}'
await p.room.send_xt('pt', p.coins, puffle_string, treat_id, f=operator.attrgetter('is_legacy_client'))
@handlers.handler(XTPacket('p', 'pf'), client=ClientType.Legacy)
async def handle_puffle_feed_legacy(p, puffle: PenguinPuffle):
if p.coins > 10:
positive_food = random.randrange(15, 40)
await puffle.update(food=min(100, puffle.food + positive_food)).apply()
await p.update(coins=p.coins - 10).apply()
puffle_string = f'{puffle.id}|puf|fle|{puffle.clean}|{puffle.food}|{puffle.rest}'
await p.room.send_xt('pf', p.coins, puffle_string, f=operator.attrgetter('is_legacy_client'))
@handlers.handler(XTPacket('p', 'pb'), client=ClientType.Legacy)
async def handle_puffle_bath_legacy(p, puffle: PenguinPuffle):
if p.coins > 5:
additional_rest = random.randrange(5, 15)
additional_clean = random.randrange(15, 40)
await puffle.update(
rest=min(100, puffle.rest + additional_rest),
clean=min(100, puffle.clean + additional_clean)
).apply()
await p.update(coins=p.coins - 5).apply()
puffle_string = f'{puffle.id}|puf|fle|{puffle.clean}|{puffle.food}|{puffle.rest}'
await p.room.send_xt('pb', p.coins, puffle_string, f=operator.attrgetter('is_legacy_client'))
@handlers.handler(XTPacket('p', 'ip'), client=ClientType.Legacy)
async def handle_puffle_play_interation_legacy(p, puffle: PenguinPuffle, x: int, y: int):
if puffle.rest < 20 or puffle.clean < 10:
return
negative_food = random.randrange(15, 25)
negative_rest = random.randrange(15, 25)
await puffle.update(
food=max(0, puffle.food - negative_food),
rest=max(0, puffle.rest - negative_rest),
clean=min(100, puffle.clean + 20)
).apply()
puffle_string = f'{puffle.id}|puf|fle|{puffle.clean}|{puffle.food}|{puffle.rest}'
await p.room.send_xt('ip', puffle_string, x, y, f=operator.attrgetter('is_legacy_client'))
@handlers.handler(XTPacket('p', 'ir'), client=ClientType.Legacy)
async def handle_puffle_rest_interation_legacy(p, puffle: PenguinPuffle, x: int, y: int):
positive_rest = random.randrange(20, 50)
await puffle.update(rest=min(100, puffle.rest + positive_rest)).apply()
puffle_string = f'{puffle.id}|puf|fle|{puffle.clean}|{puffle.food}|{puffle.rest}'
await p.room.send_xt('ir', puffle_string, x, y, f=operator.attrgetter('is_legacy_client'))
@handlers.handler(XTPacket('p', 'if'), client=ClientType.Legacy)
async def handle_puffle_feed_interation_legacy(p, puffle: PenguinPuffle, x: int, y: int):
positive_food = random.randrange(20, 50)
await puffle.update(
food=min(100, puffle.food + positive_food)
).apply()
await p.update(coins=p.coins - 10).apply()
puffle_string = f'{puffle.id}|puf|fle|{puffle.clean}|{puffle.food}|{puffle.rest}'
await p.room.send_xt('if', p.coins, puffle_string, x, y, f=operator.attrgetter('is_legacy_client'))
@handlers.handler(XTPacket('p', 'ip'), client=ClientType.Vanilla)
async def handle_puffle_play_interation_legacy(p, puffle: PenguinPuffle, x: int, y: int):
favourite_toy = p.server.puffle_items[p.server.puffles[puffle.puffle_id].favourite_toy]
await deliver(p, favourite_toy, puffle)
puffle_string = f'{puffle.id}|{puffle.food}|{puffle.play}|{puffle.rest}|{puffle.clean}|{x}|{y}'
await p.room.send_xt('ip', puffle_string)
@handlers.handler(XTPacket('p', 'ir'), client=ClientType.Vanilla)
async def handle_puffle_rest_interation_vanilla(p, puffle: PenguinPuffle, x: int, y: int):
sleep = p.server.puffle_items[37]
await deliver(p, sleep, puffle)
puffle_string = f'{puffle.id}|{puffle.food}|{puffle.play}|{puffle.rest}|{puffle.clean}|{x}|{y}'
await p.room.send_xt('ir', puffle_string)
@handlers.handler(XTPacket('p', 'pip'))
async def handle_puffle_init_play_interation(p, puffle: PenguinPuffle, x: int, y: int):
await p.room.send_xt('pip', f'{puffle.id}|{x}|{y}', f=operator.attrgetter('is_vanilla_client'))
await p.room.send_xt('pip', puffle.id, x, y, f=operator.attrgetter('is_legacy_client'))
@handlers.handler(XTPacket('p', 'pir'))
async def handle_puffle_init_rest_interaction(p, puffle: PenguinPuffle, x: int, y: int):
await p.room.send_xt('pir', f'{puffle.id}|{x}|{y}', f=operator.attrgetter('is_vanilla_client'))
await p.room.send_xt('pir', puffle.id, x, y, f=operator.attrgetter('is_legacy_client'))
@handlers.handler(XTPacket('p', 'papi'), client=ClientType.Vanilla)
async def handle_add_puffle_care_item(p, item_id: int):
if item_id not in p.server.puffle_items:
return await p.send_error(402)
care_item = p.server.puffle_items[item_id]
if care_item.cost > p.coins:
return await p.send_error(401)
await p.add_puffle_item(care_item)
@handlers.handler(XTPacket('p', 'pgmps'), client=ClientType.Vanilla)
async def handle_get_my_puffle_stats(p):
puffle_stats = ','.join(f'{puffle.id}|{puffle.food}|{puffle.play}|{puffle.rest}|{puffle.clean}'
for puffle in p.puffles.values())
await p.room.send_xt('pgmps', puffle_stats)
@handlers.handler(XTPacket('p', 'pcid'), client=ClientType.Vanilla)
async def handle_puffle_care_item_delivered(p, puffle: PenguinPuffle, care_item_id: int):
care_item = p.server.puffle_items[care_item_id]
await deliver(p, care_item, puffle)
@handlers.handler(XTPacket('p', 'phg'))
async def handle_get_puffle_handler(p):
await p.send_xt('phg', int(p.puffle_handler))
@handlers.handler(XTPacket('p', 'phs'))
@handlers.allow_once
async def handle_set_puffle_handler(p):
await p.update(puffle_handler=True).apply()
@handlers.handler(XTPacket('p', 'puphi'), client=ClientType.Vanilla)
async def handle_puffle_visitor_hat_update(p, puffle: PenguinPuffle, hat_id: int):
if hat_id in p.puffle_items:
await puffle.update(hat=hat_id).apply()
await p.room.send_xt('puphi', puffle.id, hat_id)
@handlers.handler(XTPacket('p', 'pufflewalkswap'), client=ClientType.Vanilla)
async def handle_walk_swap_puffles(p, puffle: PenguinPuffle):
if puffle.id != p.walking:
walking_puffle = p.puffles[p.walking]
in_backyard = type(p.room) == PenguinBackyardRoom
return_to_backyard = in_backyard or type(p.room) != PenguinIglooRoom and walking_puffle.backyard
await walking_puffle.update(backyard=return_to_backyard).apply()
puffle = p.puffles[puffle.id]
await p.update(walking=puffle.id).apply()
parent_id, puffle_id = get_client_puffle_id(p, puffle.puffle_id)
await p.room.send_xt('pufflewalkswap', p.id, puffle.id, parent_id, puffle_id, 1, puffle.hat or 0)
p.can_dig_gold = False
@handlers.handler(XTPacket('p', 'puffletrick'), client=ClientType.Vanilla)
async def handle_puffle_trick(p, trick_id: int):
if p.walking is not None:
await p.room.send_xt('puffletrick', p.id, trick_id)
@handlers.handler(XTPacket('p', 'puffleswap'), client=ClientType.Vanilla)
async def handle_change_puffle_room(p, puffle: PenguinPuffle, room_type: str):
to_backyard = room_type == 'backyard'
igloo_puffle_count = sum(not puff.backyard and puff.id != p.walking for puff in p.puffles.values())
if igloo_puffle_count >= 10 and not to_backyard:
return await p.send_error(443)
await puffle.update(backyard=1 if to_backyard else 0).apply()
await p.room.send_xt('puffleswap', puffle.id, room_type)
await p.status_field_set(StatusField.PlayerSwapPuffle)
@handlers.handler(XTPacket('p', 'prp'), client=ClientType.Vanilla)
async def handle_return_puffle(p, puffle: PenguinPuffle):
if p.walking == puffle.id:
await p.update(walking=None).apply()
await p.puffles.delete(puffle.id)
await p.room.send_xt('prp', puffle.id)
@handlers.handler(XTPacket('p', 'carestationmenu'), client=ClientType.Vanilla)
async def handle_care_station_menu(p):
await p.send_xt('carestationmenu', '7|117', '119')
@handlers.handler(XTPacket('p', 'carestationmenuchoice'), client=ClientType.Vanilla)
async def handle_care_station_menu_choice(p, item_id: int):
await p.room.send_xt('carestationmenuchoice', p.id, item_id)
@handlers.handler(XTPacket('p', 'puffledig'), client=ClientType.Vanilla)
@handlers.cooldown(119)
async def handle_puffle_dig(p):
await dig(p)
@handlers.handler(XTPacket('p', 'puffledigoncommand'), client=ClientType.Vanilla)
@handlers.cooldown(119)
async def handle_puffle_dig_on_command(p):
await dig(p, on_command=True)
@handlers.handler(XTPacket('p', 'revealgoldpuffle'))
async def handle_reveal_gold_puffle(p):
if p.can_dig_gold and p.nuggets >= 15:
await p.room.send_xt('revealgoldpuffle', p.id)

View File

@ -20,7 +20,8 @@ from houdini.data.room import RoomCollection
from houdini.data.stamp import StampCollection from houdini.data.stamp import StampCollection
from houdini.data.ninja import CardCollection from houdini.data.ninja import CardCollection
from houdini.data.mail import PostcardCollection from houdini.data.mail import PostcardCollection
from houdini.data.pet import PuffleCollection, PuffleItemCollection from houdini.data.pet import PuffleCollection, PuffleItemCollection, PuffleTreasurePuffleItem, \
PuffleTreasureFurniture, PuffleTreasureItem
from houdini.data.permission import PermissionCollection from houdini.data.permission import PermissionCollection
from houdini.data.buddy import CharacterCollection from houdini.data.buddy import CharacterCollection
from houdini.data.moderator import ChatFilterRuleCollection from houdini.data.moderator import ChatFilterRuleCollection
@ -38,8 +39,8 @@ from houdini.handlers import XTListenerManager, XMLListenerManager, DummyEventLi
from houdini.plugins import PluginManager from houdini.plugins import PluginManager
from houdini.commands import CommandManager from houdini.commands import CommandManager
from houdini.handlers.play.player import server_heartbeat from houdini.handlers.play.player import server_heartbeat, server_egg_timer
from houdini.handlers.play.player import server_egg_timer from houdini.handlers.play.pet import decrease_stats
from houdini.handlers.play.music import SoundStudio from houdini.handlers.play.music import SoundStudio
@ -87,12 +88,16 @@ class Houdini:
self.postcards = None self.postcards = None
self.puffles = None self.puffles = None
self.puffle_items = None self.puffle_items = None
self.puffle_food_treasure = None
self.puffle_furniture_treasure = None
self.puffle_clothing_treasure = None
self.characters = None self.characters = None
self.spawn_rooms = None self.spawn_rooms = None
self.heartbeat = None self.heartbeat = None
self.egg_timer = None self.egg_timer = None
self.puffle_killer = None
self.music = None self.music = None
@ -210,6 +215,10 @@ class Houdini:
self.puffle_items = await PuffleItemCollection.get_collection() self.puffle_items = await PuffleItemCollection.get_collection()
self.logger.info(f'Loaded {len(self.puffle_items)} puffle care items') self.logger.info(f'Loaded {len(self.puffle_items)} puffle care items')
self.puffle_food_treasure = await PuffleTreasurePuffleItem.query.gino.all()
self.puffle_furniture_treasure = await PuffleTreasureFurniture.query.gino.all()
self.puffle_clothing_treasure = await PuffleTreasureItem.query.gino.all()
self.characters = await CharacterCollection.get_collection() self.characters = await CharacterCollection.get_collection()
self.logger.info(f'Loaded {len(self.characters)} characters') self.logger.info(f'Loaded {len(self.characters)} characters')
@ -228,6 +237,7 @@ class Houdini:
self.heartbeat = asyncio.create_task(server_heartbeat(self)) self.heartbeat = asyncio.create_task(server_heartbeat(self))
self.egg_timer = asyncio.create_task(server_egg_timer(self)) self.egg_timer = asyncio.create_task(server_egg_timer(self))
self.puffle_killer = asyncio.create_task(decrease_stats(self))
self.music = SoundStudio(self) self.music = SoundStudio(self)

View File

@ -2,6 +2,8 @@ import time
from houdini.spheniscidae import Spheniscidae from houdini.spheniscidae import Spheniscidae
from houdini.data import penguin from houdini.data import penguin
from houdini.data.mail import PenguinPostcard
from houdini.handlers.play.pet import get_my_player_walking_puffle
class Penguin(Spheniscidae, penguin.Penguin): class Penguin(Spheniscidae, penguin.Penguin):
@ -30,7 +32,6 @@ class Penguin(Spheniscidae, penguin.Penguin):
self.membership_days_remain = -1 self.membership_days_remain = -1
self.avatar = None self.avatar = None
self.walking_puffle = None
self.active_quests = None self.active_quests = None
self.legacy_buddy_requests = set() self.legacy_buddy_requests = set()
@ -39,13 +40,15 @@ class Penguin(Spheniscidae, penguin.Penguin):
self.login_timestamp = None self.login_timestamp = None
self.egg_timer_minutes = None self.egg_timer_minutes = None
self.can_dig_gold = False
@property @property
def party_state(self): def party_state(self):
return str() return str()
@property @property
def puffle_state(self): def puffle_state(self):
return str() return get_my_player_walking_puffle(self)
@property @property
def penguin_state(self): def penguin_state(self):
@ -120,21 +123,32 @@ class Penguin(Spheniscidae, penguin.Penguin):
return True return True
async def add_puffle_item(self, care_item, quantity=1, notify=True): async def add_puffle_item(self, care_item, quantity=1, notify=True, cost=None):
if care_item.id in self.puffle_items: if care_item.type not in ['food', 'head', 'play']:
penguin_care_item = self.puffle_items[care_item.id] return False
care_item_id = care_item.parent_id
quantity = quantity * care_item.quantity
if care_item.type == 'play' and care_item_id in self.puffle_items:
return False
if care_item_id in self.puffle_items:
penguin_care_item = self.puffle_items[care_item_id]
if penguin_care_item.quantity >= 100: if penguin_care_item.quantity >= 100:
return False return False
await penguin_care_item.update( await penguin_care_item.update(
quantity=penguin_care_item.quantity + quantity).apply() quantity=penguin_care_item.quantity + quantity).apply()
else: else:
await self.puffle_items.insert(item_id=care_item.id) penguin_care_item = await self.puffle_items.insert(item_id=care_item_id,
quantity=quantity)
await self.update(coins=self.coins - care_item.cost).apply() cost = cost if cost is not None else care_item.cost
await self.update(coins=self.coins - cost).apply()
if notify: if notify:
await self.send_xt('papi', self.coins, care_item.id, quantity) await self.send_xt('papi', self.coins, care_item_id, penguin_care_item.quantity)
self.logger.info(f'{self.username} added \'{care_item.name}\' to their puffle care inventory') self.logger.info(f'{self.username} added \'{care_item.name}\' to their puffle care inventory')
@ -221,8 +235,8 @@ class Penguin(Spheniscidae, penguin.Penguin):
return True return True
async def add_inbox(self, postcard, sender_name="sys", sender_id=None, details=""): async def add_inbox(self, postcard, sender_name="sys", sender_id=None, details=""):
penguin_postcard = await self.postcards.insert(penguin_id=self.id, sender_id=sender_id, penguin_postcard = await PenguinPostcard.create(penguin_id=self.id, sender_id=sender_id,
postcard_id=postcard.id, details=details) postcard_id=postcard.id, details=details)
await self.send_xt('mr', sender_name, 0, postcard.id, details, int(time.time()), penguin_postcard.id) await self.send_xt('mr', sender_name, 0, postcard.id, details, int(time.time()), penguin_postcard.id)