mirror of
https://github.com/solero/houdini.git
synced 2025-04-26 00:26:31 +00:00
491 lines
19 KiB
Python
491 lines
19 KiB
Python
import itertools
|
|
import ujson
|
|
import time
|
|
from datetime import datetime, timedelta
|
|
|
|
from houdini import handlers
|
|
from houdini.handlers import XTPacket
|
|
from houdini.converters import SeparatorConverter
|
|
from houdini.constants import ClientType
|
|
from houdini.handlers.play.navigation import handle_join_server
|
|
|
|
from houdini.data import db
|
|
from houdini.data.penguin import Penguin
|
|
from houdini.data.room import PenguinIglooRoom
|
|
from houdini.data.igloo import IglooFurniture, IglooLike, Igloo, Furniture, Flooring, Location
|
|
|
|
from sqlalchemy.dialects.postgresql import insert
|
|
|
|
from aiocache import cached
|
|
|
|
|
|
def get_layout_furniture_key(_, p, igloo_id):
|
|
return f'layout_furniture.{igloo_id}'
|
|
|
|
|
|
def get_active_igloo_string_key(_, p, penguin_id):
|
|
return f'active_igloo.{penguin_id}'
|
|
|
|
|
|
def get_legacy_igloo_string_key(_, p, penguin_id):
|
|
return f'legacy_igloo.{penguin_id}'
|
|
|
|
|
|
def get_igloo_layouts_key(_, p):
|
|
return f'igloo_layouts.{p.data.id}'
|
|
|
|
|
|
def get_layout_like_count_key(_, igloo_id):
|
|
return f'layout_like_count.{igloo_id}'
|
|
|
|
|
|
@cached(alias='default', key_builder=get_layout_furniture_key)
|
|
async def get_layout_furniture(p, igloo_id):
|
|
igloo_furniture = IglooFurniture.query.where(IglooFurniture.igloo_id == igloo_id).gino
|
|
async with p.server.db.transaction():
|
|
furniture_string = ','.join([f'{furniture.furniture_id}|{furniture.x}|{furniture.y}|'
|
|
f'{furniture.rotation}|{furniture.frame}'
|
|
async for furniture in igloo_furniture.iterate()])
|
|
return furniture_string
|
|
|
|
|
|
@cached(alias='default', key_builder=get_active_igloo_string_key)
|
|
async def get_active_igloo_string(p, penguin_id):
|
|
igloo = await PenguinIglooRoom.load(parent=Penguin.on(Penguin.igloo == PenguinIglooRoom.id))\
|
|
.where(PenguinIglooRoom.penguin_id == penguin_id).gino.first()
|
|
|
|
furniture_string = await get_layout_furniture(p, igloo.id)
|
|
like_count = await get_layout_like_count(igloo.id)
|
|
return f'{igloo.id}:1:0:{int(igloo.locked)}:{igloo.music}:{igloo.flooring}:' \
|
|
f'{igloo.location}:{igloo.type}:{like_count}:{furniture_string}'
|
|
|
|
|
|
@cached(alias='default', key_builder=get_legacy_igloo_string_key)
|
|
async def get_legacy_igloo_string(p, penguin_id):
|
|
igloo = await PenguinIglooRoom.load(parent=Penguin.on(Penguin.igloo == PenguinIglooRoom.id))\
|
|
.where(PenguinIglooRoom.penguin_id == penguin_id).gino.first()
|
|
|
|
furniture_string = await get_layout_furniture(p, igloo.id)
|
|
return f'{igloo.type}%{igloo.music}%{igloo.flooring}%{furniture_string}'
|
|
|
|
|
|
@cached(alias='default', key_builder=get_igloo_layouts_key)
|
|
async def get_all_igloo_layouts(p):
|
|
layout_details = []
|
|
slot = 0
|
|
for igloo in p.data.igloo_rooms.values():
|
|
slot += 1
|
|
furniture_string = await get_layout_furniture(p, igloo.id)
|
|
like_count = await get_layout_like_count(igloo.id)
|
|
igloo_details = f'{igloo.id}:{slot}:0:{int(igloo.locked)}:{igloo.music}:{igloo.flooring}' \
|
|
f':{igloo.location}:{igloo.type}:{like_count}:{furniture_string}'
|
|
layout_details.append(igloo_details)
|
|
return '%'.join(layout_details)
|
|
|
|
|
|
@cached(alias='default', key_builder=get_layout_like_count_key)
|
|
async def get_layout_like_count(igloo_id):
|
|
layout_like_count = await db.select([db.func.sum(IglooLike.count)])\
|
|
.where(IglooLike.igloo_id == igloo_id).gino.scalar()
|
|
return layout_like_count or 0
|
|
|
|
|
|
async def create_first_igloo(p, penguin_id):
|
|
igloo = await PenguinIglooRoom.query.where(PenguinIglooRoom.penguin_id == penguin_id).gino.scalar()
|
|
if igloo is None:
|
|
if penguin_id in p.server.penguins_by_id:
|
|
penguin = p.server.penguins_by_id[penguin_id]
|
|
igloo = await penguin.data.igloo_rooms.insert(penguin_id=penguin_id, type=1, flooring=0, location=1)
|
|
await penguin.data.update(igloo=igloo.id).apply()
|
|
else:
|
|
igloo = await PenguinIglooRoom.create(penguin_id=penguin_id, type=1, flooring=0, location=1)
|
|
await Penguin.update.values(igloo=igloo.id)\
|
|
.where(Penguin.id == penguin_id).gino.status()
|
|
|
|
|
|
async def save_igloo_furniture(p, furniture_list=None):
|
|
await IglooFurniture.delete.where(IglooFurniture.igloo_id == p.data.igloo).gino.status()
|
|
|
|
if furniture_list:
|
|
furniture_tracker = {}
|
|
furniture = []
|
|
for furniture_string in itertools.islice(furniture_list, 0, 100):
|
|
furniture_id, x, y, rotation, frame = map(int, furniture_string.split('|'))
|
|
|
|
if furniture_id not in p.data.furniture:
|
|
return
|
|
|
|
if furniture_id not in furniture_tracker:
|
|
furniture_tracker[furniture_id] = 0
|
|
else:
|
|
furniture_tracker[furniture_id] += 1
|
|
|
|
if furniture_tracker[furniture_id] > p.data.furniture[furniture_id].quantity:
|
|
return
|
|
|
|
if not (0 <= x <= 700 and 0 <= y <= 700 and 1 <= rotation <= 8 and 1 <= frame <= 10):
|
|
return
|
|
|
|
furniture.append({
|
|
'igloo_id': p.data.igloo,
|
|
'furniture_id': furniture_id,
|
|
'x': x, 'y': y,
|
|
'frame': frame,
|
|
'rotation': rotation
|
|
})
|
|
|
|
await IglooFurniture.insert().values(furniture).gino.status()
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'gm'))
|
|
@handlers.cooldown(1)
|
|
async def handle_get_igloo_details(p, penguin_id: int):
|
|
await create_first_igloo(p, penguin_id)
|
|
igloo_string_method = get_active_igloo_string if p.is_vanilla_client else get_legacy_igloo_string
|
|
await p.send_xt('gm', penguin_id, await igloo_string_method(p, penguin_id))
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'gail'), client=ClientType.Vanilla)
|
|
async def handle_get_all_igloo_layouts(p):
|
|
await p.send_xt('gail', p.data.id, 0, await get_all_igloo_layouts(p))
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'ag'))
|
|
async def handle_buy_flooring(p, flooring: Flooring):
|
|
if flooring is None:
|
|
return await p.send_error(402)
|
|
|
|
if p.is_vanilla_client:
|
|
if flooring.id in p.data.flooring:
|
|
return await p.send_error(400)
|
|
|
|
if p.data.coins < flooring.cost:
|
|
return await p.send_error(401)
|
|
|
|
await p.add_flooring(flooring)
|
|
else:
|
|
igloo = p.data.igloo_rooms[p.data.igloo]
|
|
|
|
await igloo.update(flooring=flooring.id).apply()
|
|
await p.data.update(coins=p.data.coins - flooring.cost).apply()
|
|
|
|
await p.send_xt('ag', flooring.id, p.data.coins)
|
|
|
|
await p.server.cache.delete(f'active_igloo.{p.data.id}')
|
|
await p.server.cache.delete(f'legacy_igloo.{p.data.id}')
|
|
await p.server.cache.delete(f'igloo_layouts.{p.data.id}')
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'aloc'), client=ClientType.Vanilla)
|
|
async def handle_buy_igloo_location(p, location: Location):
|
|
if location is None:
|
|
return await p.send_error(402)
|
|
|
|
if location.id in p.data.locations:
|
|
return await p.send_error(400)
|
|
|
|
if p.data.coins < location.cost:
|
|
return await p.send_error(401)
|
|
|
|
await p.add_location(location)
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'au'))
|
|
async def handle_buy_igloo_type(p, igloo: Igloo):
|
|
if igloo is None:
|
|
return await p.send_error(402)
|
|
|
|
if igloo.id in p.data.igloos:
|
|
return await p.send_error(400)
|
|
|
|
if p.data.coins < igloo.cost:
|
|
return await p.send_error(401)
|
|
|
|
await p.add_igloo(igloo)
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'af'))
|
|
async def handle_buy_furniture(p, furniture: Furniture):
|
|
if furniture is None:
|
|
return await p.send_error(402)
|
|
|
|
if furniture.id in p.data.igloos:
|
|
return await p.send_error(400)
|
|
|
|
if p.data.coins < furniture.cost:
|
|
return await p.send_error(401)
|
|
|
|
await p.add_furniture(furniture)
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'uic'), client=ClientType.Vanilla)
|
|
@handlers.cooldown(1)
|
|
async def handle_update_igloo_configuration(p, igloo_id: int, igloo_type_id: int, flooring_id: int, location_id: int,
|
|
music_id: int, furniture_data):
|
|
if p.room.igloo and p.room.penguin_id == p.data.id and igloo_id in p.data.igloo_rooms:
|
|
igloo = p.data.igloo_rooms[igloo_id]
|
|
|
|
await p.data.update(igloo=igloo_id).apply()
|
|
p.server.igloos_by_penguin_id[p.data.id] = igloo
|
|
|
|
furniture_list = furniture_data.split(',') if furniture_data else None
|
|
await save_igloo_furniture(p, furniture_list)
|
|
|
|
if not igloo_type_id or igloo_type_id in p.data.igloos\
|
|
and not flooring_id or flooring_id in p.data.flooring\
|
|
and not location_id or location_id in p.data.locations:
|
|
await igloo.update(
|
|
type=igloo_type_id,
|
|
flooring=flooring_id,
|
|
location=location_id,
|
|
music=music_id
|
|
).apply()
|
|
|
|
like_count = await get_layout_like_count(igloo.id)
|
|
active_igloo_string = f'{igloo.id}:1:0:{int(igloo.locked)}:{igloo.music}:{igloo.flooring}:' \
|
|
f'{igloo.location}:{igloo.type}:{like_count}:{furniture_data}'
|
|
await p.room.send_xt('uvi', p.data.id, active_igloo_string)
|
|
|
|
await p.server.cache.set(f'layout_furniture.{igloo.id}', furniture_data)
|
|
await p.server.cache.set(f'active_igloo.{p.data.id}', active_igloo_string)
|
|
await p.server.cache.delete(f'legacy_igloo.{p.data.id}')
|
|
await p.server.cache.delete(f'igloo_layouts.{p.data.id}')
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'ur'), client=ClientType.Legacy)
|
|
@handlers.cooldown(1)
|
|
async def handle_save_igloo_furniture(p, *furniture_data):
|
|
await save_igloo_furniture(p, furniture_data)
|
|
|
|
await p.server.cache.set(f'layout_furniture.{p.data.igloo}', ','.join(furniture_data))
|
|
await p.server.cache.delete(f'legacy_igloo.{p.data.id}')
|
|
|
|
|
|
_slot_converter = SeparatorConverter(separator=',', mapper=str)
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'uiss'), client=ClientType.Vanilla)
|
|
@handlers.cooldown(1)
|
|
async def handle_update_igloo_slot_summary(p, igloo_id: int, slot_summary: _slot_converter):
|
|
if p.room.igloo and p.room.penguin_id == p.data.id and igloo_id in p.data.igloo_rooms:
|
|
igloo = p.data.igloo_rooms[igloo_id]
|
|
|
|
await p.data.update(igloo=igloo_id).apply()
|
|
p.server.igloos_by_penguin_id[p.data.id] = igloo
|
|
|
|
if p.data.id in p.server.open_igloos_by_penguin_id:
|
|
del p.server.open_igloos_by_penguin_id[p.data.id]
|
|
|
|
for slot in slot_summary:
|
|
igloo_id, locked = map(int, slot.split('|'))
|
|
igloo = p.data.igloo_rooms[igloo_id]
|
|
|
|
if igloo_id == p.data.igloo:
|
|
if not locked:
|
|
p.server.open_igloos_by_penguin_id[p.data.id] = igloo
|
|
|
|
if igloo.locked != bool(locked):
|
|
await igloo.update(locked=bool(locked)).apply()
|
|
|
|
await p.server.cache.delete(f'active_igloo.{p.data.id}')
|
|
await p.server.cache.delete(f'legacy_igloo.{p.data.id}')
|
|
await p.server.cache.delete(f'igloo_layouts.{p.data.id}')
|
|
|
|
active_igloo_string = await get_active_igloo_string(p, p.data.id)
|
|
await p.room.send_xt('uvi', p.data.id, active_igloo_string)
|
|
|
|
|
|
@handlers.handler(XTPacket('j', 'js'), after=handle_join_server, client=ClientType.Vanilla)
|
|
async def handle_add_igloo_map(p):
|
|
if p.data.igloo is not None:
|
|
igloo = p.data.igloo_rooms[p.data.igloo]
|
|
|
|
if not igloo.locked:
|
|
p.server.open_igloos_by_penguin_id[p.data.id] = igloo
|
|
|
|
|
|
@handlers.disconnected
|
|
async def handle_remove_igloo_map(p):
|
|
if p.data.id in p.server.open_igloos_by_penguin_id:
|
|
del p.server.open_igloos_by_penguin_id[p.data.id]
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'pio'), client=ClientType.Vanilla)
|
|
async def handle_is_player_igloo_open(p, penguin_id: int):
|
|
await p.send_xt('pio', int(penguin_id in p.server.open_igloos_by_penguin_id))
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'al'), client=ClientType.Vanilla)
|
|
async def handle_add_igloo_layout(p):
|
|
if len(p.data.igloo_rooms) < 4:
|
|
igloo = await p.data.igloo_rooms.insert(penguin_id=p.data.id, type=1, flooring=0, location=1)
|
|
slot_id = len(p.data.igloo_rooms)
|
|
|
|
await p.send_xt('al', p.data.id, f'{igloo.id}:{slot_id}:0:{int(igloo.locked)}:{igloo.music}:{igloo.flooring}:'
|
|
f'{igloo.location}:{igloo.type}:0:')
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'gili'), client=ClientType.Vanilla)
|
|
@handlers.cooldown(1)
|
|
async def handle_get_igloo_like_by(p, pagination_start: int, pagination_end: int):
|
|
if p.room.igloo:
|
|
like_count = await get_layout_like_count(p.room.id)
|
|
|
|
liked_by = IglooLike.query.where(IglooLike.igloo_id == p.room.id). \
|
|
limit(pagination_end - pagination_start).offset(pagination_start).gino
|
|
|
|
async with p.server.db.transaction():
|
|
like_collection = {
|
|
'likedby': {
|
|
'counts': {
|
|
'count': like_count,
|
|
'maxCount': like_count,
|
|
'accumCount': like_count
|
|
},
|
|
'IDs': [
|
|
{
|
|
'id': like.player_id,
|
|
'time': int(time.mktime(like.date.timetuple())),
|
|
'count': like.count,
|
|
'isFriend': like.player_id in p.data.buddies
|
|
} async for like in liked_by.iterate()
|
|
]
|
|
},
|
|
}
|
|
|
|
await p.send_xt('gili', p.room.id, 200, ujson.dumps(like_collection))
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'cli'), client=ClientType.Vanilla)
|
|
async def handle_can_like_igloo(p):
|
|
last_like = await db.select([IglooLike.date]).where((IglooLike.igloo_id == p.room.id)
|
|
& (IglooLike.player_id == p.data.id)).gino.scalar()
|
|
|
|
time_elapsed = datetime.now()
|
|
if last_like is not None:
|
|
time_elapsed = datetime.now() - last_like
|
|
|
|
can_like = ujson.dumps({'canLike': True, 'periodicity': 'ScheduleDaily', 'nextLike_msecs': 0})
|
|
|
|
if last_like is None or time_elapsed > timedelta(1):
|
|
await p.send_xt('cli', p.room.id, 200, can_like)
|
|
else:
|
|
next_like = int((timedelta(1) - time_elapsed).total_seconds() * 1000)
|
|
await p.send_xt('cli', p.room.id, 200, ujson.dumps({'canLike': False, 'periodicity': 'ScheduleDaily',
|
|
'nextLike_msecs': next_like}))
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'gr'), client=ClientType.Vanilla)
|
|
async def handle_get_open_igloo_list(p):
|
|
async def get_igloo_string(igloo):
|
|
owner_name = p.server.penguins_by_id[igloo.penguin_id].data.nickname
|
|
like_count = await get_layout_like_count(igloo.id)
|
|
igloo_population = len(igloo.penguins_by_id)
|
|
return f'{igloo.penguin_id}|{owner_name}|{like_count}|{igloo_population}|{int(igloo.locked)}'
|
|
|
|
open_igloos = [await get_igloo_string(igloo) for igloo in p.server.open_igloos_by_penguin_id.values()]
|
|
local_room_population = 0
|
|
own_layout_like_count = 0 if p.data.igloo is None else await get_layout_like_count(p.data.igloo)
|
|
await p.send_xt('gr', own_layout_like_count, local_room_population, *open_igloos)
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'gr'), client=ClientType.Legacy)
|
|
async def handle_get_open_igloo_list_legacy(p):
|
|
if not p.server.open_igloos_by_penguin_id:
|
|
return await p.send_line('%xt%gr%-1%')
|
|
|
|
async def get_igloo_string(igloo):
|
|
owner_name = p.server.penguins_by_id[igloo.penguin_id].data.nickname
|
|
return f'{igloo.penguin_id}|{owner_name}'
|
|
|
|
open_igloos = [await get_igloo_string(igloo) for igloo in p.server.open_igloos_by_penguin_id.values()]
|
|
await p.send_xt('gr', *open_igloos)
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'or'), client=ClientType.Legacy)
|
|
async def handle_unlock_igloo(p):
|
|
igloo = p.data.igloo_rooms[p.data.igloo]
|
|
p.server.open_igloos_by_penguin_id[p.data.id] = igloo
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'cr'), client=ClientType.Legacy)
|
|
async def handle_lock_igloo(p):
|
|
del p.server.open_igloos_by_penguin_id[p.data.id]
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'go'), client=ClientType.Legacy)
|
|
async def handle_get_owned_igloos(p):
|
|
await p.send_xt('go', '|'.join(str(igloo_id) for igloo_id in p.data.igloos.keys()))
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'gf'), client=ClientType.Legacy)
|
|
async def handle_get_furniture(p):
|
|
furniture_string = '%'.join(f'{furniture.furniture_id}|{furniture.quantity}'
|
|
for furniture in p.data.furniture.values())
|
|
await p.send_xt('gf', furniture_string)
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'um'), client=ClientType.Legacy)
|
|
async def handle_update_igloo_music(p, music_id: int):
|
|
if p.room.igloo and p.room.penguin_id == p.data.id and p.room.music != music_id:
|
|
await p.room.update(music=music_id).apply()
|
|
|
|
await p.server.cache.delete(f'active_igloo.{p.data.id}')
|
|
await p.server.cache.delete(f'legacy_igloo.{p.data.id}')
|
|
await p.server.cache.delete(f'igloo_layouts.{p.data.id}')
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'ao'), client=ClientType.Legacy)
|
|
async def handle_activate_igloo_type(p, igloo_type_id: int):
|
|
if p.room.igloo and p.room.penguin_id == p.data.id and p.room.type != igloo_type_id \
|
|
and igloo_type_id in p.data.igloos:
|
|
await p.room.update(type=igloo_type_id, flooring=0).apply()
|
|
|
|
await p.server.cache.delete(f'active_igloo.{p.data.id}')
|
|
await p.server.cache.delete(f'legacy_igloo.{p.data.id}')
|
|
await p.server.cache.delete(f'igloo_layouts.{p.data.id}')
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'grf'), client=ClientType.Vanilla)
|
|
async def handle_get_friends_igloo_list(p):
|
|
async def get_friend_igloo_string(penguin):
|
|
like_count = 0 if penguin.data.igloo is None else await get_layout_like_count(penguin.data.igloo)
|
|
return f'{penguin.data.id}|{like_count}'
|
|
|
|
friend_igloos = [await get_friend_igloo_string(penguin) for penguin in p.server.penguins_by_id.values()
|
|
if penguin.data.id in p.data.buddies]
|
|
|
|
await p.send_xt('grf', *friend_igloos)
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'li'), client=ClientType.Vanilla)
|
|
@handlers.cooldown(1)
|
|
async def handle_like_igloo(p):
|
|
if p.room.igloo:
|
|
like_insert = insert(IglooLike).values(igloo_id=p.room.id, player_id=p.data.id)
|
|
like_insert = like_insert.on_conflict_do_update(
|
|
constraint='igloo_like_pkey',
|
|
set_=dict(count=IglooLike.count + 1, date=datetime.now()),
|
|
where=(IglooLike.date < datetime.today())
|
|
)
|
|
await like_insert.gino.status()
|
|
|
|
await p.server.cache.delete(f'layout_like_count.{p.room.id}')
|
|
|
|
if len(p.room.penguins_by_id) > 1:
|
|
like_count = await get_layout_like_count(p.room.id)
|
|
for penguin in p.room.penguins_by_id.values():
|
|
if penguin.data.id != p.data.id:
|
|
await p.send_xt('lue', p.data.id, like_count)
|
|
|
|
|
|
@handlers.handler(XTPacket('g', 'gii'), client=ClientType.Vanilla)
|
|
async def handle_get_furniture_inventory(p):
|
|
furniture = ','.join(f'{furniture_id}|0000000000|{furniture_item.quantity}'
|
|
for furniture_id, furniture_item in p.data.furniture.items())
|
|
flooring = ','.join(f'{flooring_id}|0000000000' for flooring_id in p.data.flooring.keys())
|
|
igloos = ','.join(f'{igloo_id}|0000000000' for igloo_id in p.data.igloos.keys())
|
|
locations = ','.join(f'{location_id}|0000000000' for location_id in p.data.locations.keys())
|
|
|
|
await p.send_xt('gii', furniture, flooring, igloos, locations)
|