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

@@ -12,6 +12,7 @@ from houdini.data.room import Room
from houdini.data.item import Item
from houdini.data.igloo import Igloo, Furniture, Flooring, Location
from houdini.data.stamp import Stamp
from houdini.data.pet import Puffle, PenguinPuffle
class ChecklistError(Exception):
@@ -324,6 +325,30 @@ class StampConverter(IConverter):
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):
__slots__ = ['separator', 'mapper']
@@ -415,7 +440,10 @@ ConverterTypes = {
Igloo: IglooConverter,
Flooring: FlooringConverter,
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"))
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"))
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"))
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"))
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"))
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"))
@@ -107,6 +109,9 @@ class Penguin(db.Model):
if (self.status_field & field_bitmask) == 0:
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
def minutes_played_today(self):
async def get_minutes():

View File

@@ -7,12 +7,11 @@ class Puffle(db.Model):
id = db.Column(db.Integer, primary_key=True)
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"))
cost = db.Column(db.Integer, nullable=False, server_default=db.text("0"))
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_toy = db.Column(db.ForeignKey('puffle_item.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):
@@ -72,7 +71,6 @@ class PenguinPuffle(db.Model):
play = 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"))
walking = db.Column(db.Boolean, server_default=db.text("false"))
hat = db.Column(db.ForeignKey('puffle_item.id', ondelete='CASCADE', onupdate='CASCADE'))
backyard = 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.handlers import XTPacket
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)
@handlers.player_attribute(joined_world=True)
PuffleKillerInterval = 600
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
async def load_pet_inventory(p):
p.puffles = await PenguinPuffleCollection.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)
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'))
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))
@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)
async def handle_get_player_puffles(p, penguin_id: int, room_type: str):
await p.send_xt('pg')
async def handle_get_player_puffles_vanilla(p, penguin_id: int, room_type: str):
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)
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.ninja import CardCollection
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.buddy import CharacterCollection
from houdini.data.moderator import ChatFilterRuleCollection
@@ -38,8 +39,8 @@ from houdini.handlers import XTListenerManager, XMLListenerManager, DummyEventLi
from houdini.plugins import PluginManager
from houdini.commands import CommandManager
from houdini.handlers.play.player import server_heartbeat
from houdini.handlers.play.player import server_egg_timer
from houdini.handlers.play.player import server_heartbeat, server_egg_timer
from houdini.handlers.play.pet import decrease_stats
from houdini.handlers.play.music import SoundStudio
@@ -87,12 +88,16 @@ class Houdini:
self.postcards = None
self.puffles = None
self.puffle_items = None
self.puffle_food_treasure = None
self.puffle_furniture_treasure = None
self.puffle_clothing_treasure = None
self.characters = None
self.spawn_rooms = None
self.heartbeat = None
self.egg_timer = None
self.puffle_killer = None
self.music = None
@@ -210,6 +215,10 @@ class Houdini:
self.puffle_items = await PuffleItemCollection.get_collection()
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.logger.info(f'Loaded {len(self.characters)} characters')
@@ -228,6 +237,7 @@ class Houdini:
self.heartbeat = asyncio.create_task(server_heartbeat(self))
self.egg_timer = asyncio.create_task(server_egg_timer(self))
self.puffle_killer = asyncio.create_task(decrease_stats(self))
self.music = SoundStudio(self)

View File

@@ -2,6 +2,8 @@ import time
from houdini.spheniscidae import Spheniscidae
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):
@@ -30,7 +32,6 @@ class Penguin(Spheniscidae, penguin.Penguin):
self.membership_days_remain = -1
self.avatar = None
self.walking_puffle = None
self.active_quests = None
self.legacy_buddy_requests = set()
@@ -39,13 +40,15 @@ class Penguin(Spheniscidae, penguin.Penguin):
self.login_timestamp = None
self.egg_timer_minutes = None
self.can_dig_gold = False
@property
def party_state(self):
return str()
@property
def puffle_state(self):
return str()
return get_my_player_walking_puffle(self)
@property
def penguin_state(self):
@@ -120,21 +123,32 @@ class Penguin(Spheniscidae, penguin.Penguin):
return True
async def add_puffle_item(self, care_item, quantity=1, notify=True):
if care_item.id in self.puffle_items:
penguin_care_item = self.puffle_items[care_item.id]
async def add_puffle_item(self, care_item, quantity=1, notify=True, cost=None):
if care_item.type not in ['food', 'head', 'play']:
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:
return False
await penguin_care_item.update(
quantity=penguin_care_item.quantity + quantity).apply()
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:
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')
@@ -221,8 +235,8 @@ class Penguin(Spheniscidae, penguin.Penguin):
return True
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,
postcard_id=postcard.id, details=details)
penguin_postcard = await PenguinPostcard.create(penguin_id=self.id, sender_id=sender_id,
postcard_id=postcard.id, details=details)
await self.send_xt('mr', sender_name, 0, postcard.id, details, int(time.time()), penguin_postcard.id)