326 lines
12 KiB
Python

from houdini import handlers
from houdini.converters import SeparatorConverter
from houdini.handlers import XTPacket
from houdini.handlers.play.navigation import handle_join_server
from houdini.data import db
from houdini.data.penguin import Penguin, PenguinMembership
from houdini.data.mail import PenguinPostcard
from houdini.constants import ClientType
from aiocache import cached
from datetime import datetime, timedelta
import random
import asyncio
import time
def get_player_string_key(_, p, player_id):
return f'player.{player_id}'
def get_mascot_string_key(_, p, mascot_id):
return f'mascot.{mascot_id}'
@cached(alias='default', key_builder=get_player_string_key)
async def get_player_string(p, penguin_id: int):
if penguin_id in p.server.penguins_by_id:
return await p.server.penguins_by_id[penguin_id].string
else:
player_data = await Penguin.get(penguin_id)
string = await p.server.anonymous_penguin_string_compiler.compile(player_data)
return string
@cached(alias='default', key_builder=get_mascot_string_key)
async def get_mascot_string(p, mascot_id: int):
if mascot_id in p.server.penguins_by_character_id:
return await p.server.penguins_by_character_id[mascot_id].string
else:
player_data = await Penguin.query.where(Penguin.character == mascot_id).gino.first()
string = await p.server.anonymous_penguin_string_compiler.compile(player_data)
return string
async def server_heartbeat(server):
while True:
timer = time.time()
await asyncio.sleep(61)
for penguin in server.penguins_by_id.values():
if penguin.heartbeat < timer:
await penguin.close()
async def server_egg_timer(server):
while True:
await asyncio.sleep(60)
for p in server.penguins_by_id.values():
if p.timer_active:
p.egg_timer_minutes -= 1
if p.is_vanilla_client:
minutes_until_timer_end = datetime.combine(datetime.today(), p.timer_end) - datetime.now()
minutes_until_timer_end = minutes_until_timer_end.total_seconds() // 60
if minutes_until_timer_end <= p.egg_timer_minutes + 1:
if p.egg_timer_minutes == 7:
await p.send_error(915, p.egg_timer_minutes, p.timer_start, p.timer_end)
elif p.egg_timer_minutes == 5:
await p.send_error(915, p.egg_timer_minutes, p.timer_start, p.timer_end)
else:
if p.egg_timer_minutes == 7:
await p.send_error(914, p.egg_timer_minutes, p.timer_total)
elif p.egg_timer_minutes == 5:
await p.send_error(914, p.egg_timer_minutes, p.timer_total)
await p.send_xt('uet', max(0, p.egg_timer_minutes))
if p.egg_timer_minutes < 0:
await p.send_error_and_disconnect(916, p.timer_start, p.timer_end)
else:
if p.egg_timer_minutes < 0:
await p.send_error_and_disconnect(910)
MemberWarningDaysToExpiry = 14
MemberWarningPostcardsVanilla = [122, 123]
MemberWarningPostcardsLegacy = [163]
MemberExpiredPostcard = 124
MemberStartPostcardVanilla = 121
MemberStartPostcardLegacy = 164
@handlers.handler(XTPacket('j', 'js'), pre_login=True, before=handle_join_server)
@handlers.allow_once
async def handle_setup_membership(p):
if not p.server.config.expire_membership or p.moderator or p.character:
p.is_member = True
p.membership_days_total = p.age
return
membership_history = PenguinMembership.query.where(PenguinMembership.penguin_id == p.id)
current_timestamp = datetime.now()
postcards = []
warning_postcards = MemberWarningPostcardsVanilla if p.is_vanilla_client else MemberWarningPostcardsLegacy
start_postcard = MemberStartPostcardVanilla if p.is_vanilla_client else MemberStartPostcardLegacy
async with db.transaction():
async for membership_record in membership_history.gino.iterate():
membership_recurring = membership_record.expires is None
membership_active = membership_recurring or membership_record.expires >= current_timestamp
if membership_record.start < current_timestamp:
if membership_active:
p.is_member = True
if not membership_recurring:
days_to_expiry = (membership_record.expires.date() - datetime.now().date()).days
p.membership_days_remain = days_to_expiry
if days_to_expiry <= MemberWarningDaysToExpiry and not membership_record.expires_aware:
postcards.append(dict(
penguin_id=p.id,
postcard_id=random.choice(warning_postcards),
send_date=membership_record.expires - timedelta(days=MemberWarningDaysToExpiry)
))
await membership_record.update(expires_aware=True).apply()
else:
if p.membership_days_remain < 0:
days_since_expiry = (membership_record.expires.date() - datetime.now().date()).days
p.membership_days_remain = min(p.membership_days_remain, days_since_expiry)
if not membership_record.expired_aware:
if p.is_vanilla_client:
postcards.append(dict(
penguin_id=p.id,
postcard_id=MemberExpiredPostcard,
send_date=membership_record.expires
))
await membership_record.update(expired_aware=True).apply()
if not membership_record.start_aware:
postcards.append(dict(
penguin_id=p.id,
postcard_id=start_postcard,
send_date=membership_record.start
))
await membership_record.update(start_aware=True).apply()
membership_end_date = current_timestamp if membership_active else membership_record.expires
p.membership_days_total += (membership_end_date - membership_record.start).days
if postcards:
await PenguinPostcard.insert().values(postcards).gino.status()
@handlers.handler(XTPacket('u', 'h'))
@handlers.cooldown(59)
async def handle_heartbeat(p):
p.heartbeat = time.time()
await p.send_xt('h')
@handlers.handler(XTPacket('u', 'gp'))
@handlers.cooldown(1)
async def handle_get_player(p, penguin_id: int):
await p.send_xt('gp', await get_player_string(p, penguin_id))
@handlers.handler(XTPacket('u', 'gmo'), client=ClientType.Vanilla)
@handlers.cooldown(1)
async def handle_get_mascot(p, mascot_id: int):
await p.send_xt('gmo', await get_mascot_string(p, mascot_id))
@handlers.handler(XTPacket('u', 'pbi'), client=ClientType.Vanilla)
@handlers.cooldown(1)
async def handle_get_player_by_id(p, penguin_id: int):
await p.send_xt('pbi', penguin_id)
@handlers.handler(XTPacket('u', 'pbs'), client=ClientType.Vanilla)
@handlers.cooldown(1)
async def handle_get_player_by_swid(p, penguin_id: int):
if penguin_id in p.server.penguins_by_id:
nickname = p.server.penguins_by_id[penguin_id].safe_nick
else:
nickname = await Penguin.select('nickname').where(Penguin.id == penguin_id).gino.scalar()
await p.send_xt('pbs', penguin_id, penguin_id, nickname)
_id_converter = SeparatorConverter(separator=',', mapper=int)
@handlers.handler(XTPacket('u', 'pbsu'), client=ClientType.Vanilla)
@handlers.cooldown(1)
async def handle_get_player_username_by_swid(p, ids: _id_converter):
ids = list(ids)
query = Penguin.select('id', 'nickname').where(Penguin.id.in_(ids))
async with p.server.db.transaction():
nicknames = {
pid: nickname async for pid, nickname in query.gino.iterate()}
await p.send_xt('pbsu', ','.join(nicknames[pid] for pid in ids))
@handlers.handler(XTPacket('u', 'gabcms'))
async def handle_get_ab_test_data(p):
pass
@handlers.handler(XTPacket('u', 'rpfi'))
async def handle_send_refresh_player_friend_info(p):
pass
@handlers.handler(XTPacket('u', 'pbn'), client=ClientType.Vanilla)
@handlers.cooldown(1)
async def handle_get_player_by_name(p, player_name: str):
player_name = player_name.lower()
if player_name in p.server.penguins_by_username:
player = p.server.penguins_by_username[player_name]
await p.send_xt('pbn', player.id, player.id, player.safe_name)
else:
result = await Penguin.select('id', 'nickname').where(
Penguin.username == player_name).gino.first()
if result is not None:
player_id, nickname = result
await p.send_xt('pbn', player_id, player_id, nickname)
else:
await p.send_xt('pbn')
@handlers.handler(XTPacket('u', 'smi'), client=ClientType.Vanilla)
@handlers.cooldown(10)
async def handle_send_mascot_invite(p, num_invites: int, character_id: int):
if character_id in p.server.characters and p.character is not None:
players = random.sample(list(p.server.penguins_by_id.values()), min(num_invites, len(p.server.penguins_by_id)))
for player in players:
await player.send_xt('smi', character_id)
@handlers.handler(XTPacket('u', 'bf'), client=ClientType.Vanilla)
async def handle_find_player(p, player_id: int):
if player_id in p.server.penguins_by_id:
player = p.server.penguins_by_id[player_id]
room_id = player.room.id
room_type = 'backyard' if player.room.backyard else 'igloo' if player.room.igloo else 'invalid'
room_owner = player.room.penguin_id if player.room.igloo else -1
else:
room_id, room_type, room_owner = -1, 'invalid', -1
await p.send_xt('bf', room_id, room_type, room_owner)
@handlers.handler(XTPacket('u', 'gbffl'))
async def handle_get_best_friends(p):
await p.send_xt('gbffl', ','.join((str(buddy.buddy_id) for buddy in p.buddies.values() if buddy.best_buddy)))
@handlers.handler(XTPacket('u', 'pbsms'), client=ClientType.Vanilla)
async def handle_pbsm_start(p):
await p.send_xt('pbsms')
@handlers.handler(XTPacket('u', 'pbsm'), client=ClientType.Vanilla)
async def handle_get_player_ids(p, ids: str):
await p.send_xt('pbsm', ids)
@handlers.handler(XTPacket('u', 'pbsmf'), client=ClientType.Vanilla)
async def handle_pbsm_finish(p):
await p.send_xt('pbsmf')
@handlers.handler(XTPacket('u', 'sp'))
async def handle_set_player_position(p, x: int, y: int):
p.x, p.y = x, y
p.frame = 1
p.toy = None
await p.room.send_xt('sp', p.id, x, y)
@handlers.handler(XTPacket('u', 'sf'))
@handlers.cooldown(.5)
async def handle_set_player_frame(p, frame: int):
p.frame = frame
await p.room.send_xt('sf', p.id, frame)
@handlers.handler(XTPacket('u', 'sb'))
@handlers.cooldown(1)
async def handle_send_throw_ball(p, x: int, y: int):
await p.room.send_xt('sb', p.id, x, y)
@handlers.handler(XTPacket('u', 'se'))
@handlers.cooldown(1)
async def handle_send_emote(p, emote: int):
await p.room.send_xt('se', p.id, emote)
@handlers.handler(XTPacket('u', 'sa'))
@handlers.cooldown(1)
async def handle_send_action(p, action: int):
await p.room.send_xt('sa', p.id, action)
@handlers.handler(XTPacket('u', 'followpath'))
@handlers.cooldown(1)
async def handle_follow_path(p, path: int):
await p.room.send_xt('followpath', p.id, path)
@handlers.handler(XTPacket('u', 'ss'))
async def handle_send_safe_message(p, message_id: int):
await p.room.send_xt('ss', p.id, message_id)
@handlers.handler(XTPacket('u', 'sma'))
async def handle_send_mascot_message(p, message_id: int):
if p.character:
await p.room.send_xt('sma', p.id, message_id)
@handlers.handler(XTPacket('u', 'glr'))
async def handle_get_last_revision(p):
await p.send_xt('glr', 'houdini')