large amount of restructuring
This commit is contained in:
parent
c377749789
commit
ea650019d4
|
@ -1,7 +1,7 @@
|
|||
.vscode/
|
||||
.vscode
|
||||
*.pyc
|
||||
__pycache__/
|
||||
__pycache__
|
||||
.mypy_cache/
|
||||
config.ini
|
||||
scratch.md
|
||||
test.py
|
||||
test*.py
|
|
@ -5,8 +5,9 @@ Maplestory emulator written in Python
|
|||
## Requirements
|
||||
|
||||
- Postgresql (only tested with 11)
|
||||
- Python 3.8+
|
||||
- Python 3.10+
|
||||
- asyncpg
|
||||
- pycryptodomex
|
||||
|
||||
## To-Do
|
||||
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
from common.abc import WildcardData
|
||||
from common.constants import PERMANENT
|
||||
|
||||
def default_level_data():
|
||||
return []
|
||||
|
||||
@dataclass
|
||||
class SkillEntry(WildcardData):
|
||||
id: int = 0
|
||||
level: int = 0
|
||||
mastery_level: int = 0
|
||||
max_level: int = 0
|
||||
expiration: int = field(default=PERMANENT)
|
||||
level_data: List = field(default_factory=default_level_data)
|
||||
|
||||
def encode(self, packet):
|
||||
packet.encode_int(self.id)
|
||||
packet.encode_int(self.level)
|
||||
packet.encode_long(PERMANENT) # skill.expiration
|
|
@ -1,85 +0,0 @@
|
|||
import server
|
||||
from net.packets.packet import Packet
|
||||
from net.packets.crypto import MapleIV
|
||||
from net.client.client_socket import ClientSocket
|
||||
from common.constants import VERSION, SUB_VERSION, LOCALE
|
||||
from asyncio import create_task
|
||||
from random import randint
|
||||
|
||||
from time import time
|
||||
|
||||
from utils import log
|
||||
|
||||
|
||||
class ClientBase:
|
||||
def __init__(self, parent, socket):
|
||||
self.m_socket = socket
|
||||
self._parent = parent
|
||||
self.port = None
|
||||
self._is_alive = False
|
||||
|
||||
self.logged_in = False
|
||||
self.world_id = None
|
||||
self.channel_id = None
|
||||
|
||||
async def initialize(self):
|
||||
self.m_socket.m_siv = MapleIV(randint(0, 2**31-1))
|
||||
# self.m_socket.m_siv = MapleIV(100)
|
||||
self.m_socket.m_riv = MapleIV(randint(0, 2**31-1))
|
||||
# self.m_socket.m_riv = MapleIV(50)
|
||||
|
||||
packet = Packet(op_code=0x0E)
|
||||
packet.encode_short(VERSION)
|
||||
packet.encode_string(SUB_VERSION)
|
||||
packet.encode_int(self.m_socket.m_riv.value)
|
||||
packet.encode_int(self.m_socket.m_siv.value)
|
||||
packet.encode_byte(LOCALE)
|
||||
|
||||
await self.send_packet_raw(packet)
|
||||
|
||||
await self.m_socket.receive(self)
|
||||
|
||||
# async def receive(self):
|
||||
# self._is_alive = True
|
||||
|
||||
# while self._is_alive:
|
||||
# m_recv_buffer = await self.sock_recv()
|
||||
|
||||
# if not m_recv_buffer:
|
||||
# self._parent.on_client_disconnect(self)
|
||||
# return
|
||||
# if self.m_socket.m_riv:
|
||||
# m_recv_buffer = self.manipulate_buffer(m_recv_buffer)
|
||||
|
||||
# self.dispatch(Packet(m_recv_buffer))
|
||||
|
||||
def dispatch(self, packet):
|
||||
self._parent.dispatcher.push(self, packet)
|
||||
|
||||
# async def sock_recv(self):
|
||||
# return await self.m_socket.sock_recv()
|
||||
|
||||
async def send_packet(self, packet):
|
||||
log.packet(f"{packet.name} {self.ip} {packet.to_string()}", "out")
|
||||
|
||||
await self.m_socket.send_packet(packet)
|
||||
|
||||
async def send_packet_raw(self, packet):
|
||||
log.packet(f"{packet.name} {self.ip} {packet.to_string()}", "out")
|
||||
|
||||
await self.m_socket.send_packet_raw(packet)
|
||||
|
||||
# def manipulate_buffer(self, buffer):
|
||||
# return self.m_socket.manipulate_buffer(buffer)
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self._parent
|
||||
|
||||
@property
|
||||
def ip(self):
|
||||
return self.m_socket.identifier[0]
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._parent.data
|
|
@ -1,74 +0,0 @@
|
|||
from enum import Enum
|
||||
|
||||
class Worlds(Enum):
|
||||
Scania = 0
|
||||
Broa = 1
|
||||
Windia = 2
|
||||
Khaini = 3
|
||||
Bellocan = 4
|
||||
Mardia = 5
|
||||
Kradia = 6
|
||||
Yellonde = 7
|
||||
Galacia = 8
|
||||
El_Nido = 9
|
||||
Zenith = 11
|
||||
Arcenia = 12
|
||||
Judis = 13
|
||||
Plana = 14
|
||||
Kastia = 15
|
||||
Kalluna = 16
|
||||
Stius = 17
|
||||
Croa = 18
|
||||
Medere = 19
|
||||
|
||||
|
||||
class WorldFlag(Enum):
|
||||
Null = 0x00
|
||||
Event = 0x01
|
||||
New = 0x02
|
||||
Hot = 0x03
|
||||
|
||||
|
||||
class InventoryType(Enum):
|
||||
TRACKER = 0x0
|
||||
EQUIP = 0x1
|
||||
CONSUME = 0x2
|
||||
INSTALL = 0x3
|
||||
ETC = 0x4
|
||||
CASH = 0x5
|
||||
|
||||
class StatModifiers(int, Enum):
|
||||
def __new__(cls, value, encode_type):
|
||||
obj = int.__new__(cls, value)
|
||||
obj._value_ = value
|
||||
obj.encode = encode_type
|
||||
return obj
|
||||
|
||||
SKIN = (0x1, 'byte')
|
||||
FACE = (0x2, 'int')
|
||||
HAIR = (0x4, 'int')
|
||||
|
||||
PET = (0x8, 'long')
|
||||
PET2 = (0x80000, 'long')
|
||||
PET3 = (0x100000, 'long')
|
||||
|
||||
LEVEL = (0x10, 'byte')
|
||||
JOB = (0x20, 'short')
|
||||
STR = (0x40, 'short')
|
||||
DEX = (0x80, 'short')
|
||||
INT = (0x100, 'short')
|
||||
LUK = (0x200, 'short')
|
||||
|
||||
HP = (0x400, 'int')
|
||||
MAX_HP = (0x800, 'int')
|
||||
MP = (0x1000, 'int')
|
||||
MAX_MP = (0x2000, 'int')
|
||||
|
||||
AP = (0x4000, 'short')
|
||||
SP = (0x8000, 'short')
|
||||
|
||||
EXP = (0x10000, 'int')
|
||||
POP = (0x20000, 'short')
|
||||
|
||||
MONEY = (0x40000, 'int')
|
||||
# TEMP_EXP = 0x200000
|
|
@ -1 +0,0 @@
|
|||
from .db_client import DatabaseClient, Account
|
617
db/structure.py
617
db/structure.py
|
@ -1,617 +0,0 @@
|
|||
from enum import Enum
|
||||
|
||||
class Meta(Enum):
|
||||
def __str__(self):
|
||||
return f"{self.__class__.__name__.lower()}.{super().__str__()}"
|
||||
|
||||
|
||||
class Schema(Meta):
|
||||
def __new__(cls, value):
|
||||
value.schema = cls.__name__.lower()
|
||||
value.primary_key = value.__dict__.get('__primary_key__')
|
||||
value.foreign_keys = value.__dict__.get('__foreign_keys__')
|
||||
value.columns = [item.lower() for item in value._member_names_]
|
||||
return value
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.__class__.__name__.lower()}.{super().__str__()}"
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name not in ('_value_'):
|
||||
return getattr(self.value, name)
|
||||
return super().__getattr__(name)
|
||||
|
||||
@classmethod
|
||||
def create(cls):
|
||||
# async def create(db):
|
||||
# schema = db.schema(cls.__name__.lower())
|
||||
# await schema.create(skip_if_exists=False)
|
||||
# fks = []
|
||||
# for table in cls:
|
||||
# t = db.table(table._name_.lower(), schema=schema)
|
||||
# if await t.exists(): continue
|
||||
# cols = []
|
||||
# for c in table:
|
||||
# primary_key = c.getattr('primary_key', False)
|
||||
# col = Column(c._name_.lower(), c.data_type, primary_key=primary_key, **c.options)
|
||||
# if t.foreign_keys and c.name in getattr(table, 'foreign_keys', []):
|
||||
# fks.append(types.ForeignKey(t, col, sql_type=c.data_type))
|
||||
# cols.append(col)
|
||||
# await t.create(*cols)
|
||||
# for fkey in fks:
|
||||
# await db.execute_transaction(fk.to_sql())
|
||||
|
||||
# return create
|
||||
pass
|
||||
|
||||
class Table(int, Meta):
|
||||
_ignore_ = ('data_type')
|
||||
|
||||
def __new__(cls, data_type, options=None):
|
||||
value = len(cls.__members__) + 1
|
||||
enum_class = super().__new__(cls, value)
|
||||
|
||||
enum_class._value_ = value
|
||||
enum_class.data_type = data_type
|
||||
|
||||
# if data_type in ['integer', 'serial']:
|
||||
# enum_class.data_type = types.Integer(auto_increment=data_type == 'serial')
|
||||
# elif data_type == 'smallint':
|
||||
# enum_class.data_type = types.Integer(small=True)
|
||||
# elif data_type == 'bigint':
|
||||
# enum_class.data_type = types.Integer(big=True)
|
||||
# elif data_type in ['varchar', 'text']:
|
||||
# enum_class.data_type = types.String()
|
||||
# elif data_type == 'jsonb':
|
||||
# enum_class.data_type = types.JSON()
|
||||
# elif data_type == 'double':
|
||||
# enum_class.data_type = types.Double()
|
||||
# elif data_type == 'boolean':
|
||||
# enum_class.data_type = types.Boolean()
|
||||
# elif data_type == 'date':
|
||||
# enum_class.data_type = types.Date()
|
||||
|
||||
# from re import search
|
||||
# if search(r'(\[\])', data_type):
|
||||
# enum_class.data_type = types.ArraySQL(enum_class.data_type)
|
||||
|
||||
enum_class.options = options if options else {}
|
||||
return enum_class
|
||||
|
||||
|
||||
class ItemConsumeableData(Table):
|
||||
ITEM_ID = ("integer")
|
||||
FLAGS = ("varchar[]")
|
||||
CURES = ("varchar[]")
|
||||
HP = ("smallint")
|
||||
HP_PERCENTAGE = ("smallint")
|
||||
MAX_HP_PERCENTAGE = ("smallint")
|
||||
MP = ("smallint")
|
||||
MP_PERCENTAGE = ("smallint")
|
||||
MAX_MP_PERCENTAGE = ("smallint")
|
||||
WEAPON_ATTACK = ("smallint")
|
||||
WEAPON_ATTACK_PERCENTAGE = ("smallint")
|
||||
WEAPON_DEFENSE = ("smallint")
|
||||
WEAPON_DEFENSE_PERCENTAGE = ("smallint")
|
||||
MAGIC_ATTACK = ("smallint")
|
||||
MAGIC_ATTACK_PERCENTAGE = ("smallint")
|
||||
MAGIC_DEFENSE = ("smallint")
|
||||
MAGIC_DEFENSE_PERCENTAGE = ("smallint")
|
||||
ACCURACY = ("smallint")
|
||||
ACCURACY_PERCENTAGE = ("smallint")
|
||||
AVOID = ("smallint")
|
||||
AVOID_PERCENTAGE = ("smallint")
|
||||
SPEED = ("smallint")
|
||||
SPEED_PERCENTAGE = ("smallint")
|
||||
JUMP = ("smallint")
|
||||
SECONDARY_STAT = ("jsonb")
|
||||
BUFF_TIME = ("integer")
|
||||
PROB = ("smallint")
|
||||
EVENT_POINT = ("integer")
|
||||
MOB_ID = ("integer")
|
||||
MOB_HP = ("integer")
|
||||
SCREEN_MESSAGE = ("text")
|
||||
ATTACK_INDEX = ("smallint")
|
||||
ATTACK_MOB_ID = ("integer")
|
||||
MOVE_TO = ("integer")
|
||||
RETURN_MAP_QR = ("integer")
|
||||
DECREASE_HUNGER = ("smallint")
|
||||
MORPH = ("smallint")
|
||||
CARNIVAL_TYPE = ("smallint")
|
||||
CARNIVAL_POINTS = ("smallint")
|
||||
CARNIVAL_SKILL = ("smallint")
|
||||
EXPERIENCE = ("integer")
|
||||
|
||||
__primary_key__ = ("ITEM_ID",)
|
||||
|
||||
|
||||
class ItemData(Table):
|
||||
ITEM_ID = ("integer")
|
||||
INVENTORY = ("smallint")
|
||||
PRICE = ("integer", {"default": 0})
|
||||
MAX_SLOT_QUANTITY = ("smallint")
|
||||
MAX_POSSESSION_COUNT = ("smallint")
|
||||
MIN_LEVEL = ("smallint")
|
||||
MAX_LEVEL = ("smallint", {"default": 250})
|
||||
EXPERIENCE = ("integer")
|
||||
MONEY = ("integer")
|
||||
STATE_CHANGE_ITEM = ("integer")
|
||||
LEVEL_FOR_MAKER = ("smallint")
|
||||
NPC = ("integer")
|
||||
FLAGS = ("varchar[]", {"default": "'{}'"})
|
||||
PET_LIFE_EXTEND = ("smallint")
|
||||
MAPLE_POINT = ("integer")
|
||||
MONEY_MIN = ("integer")
|
||||
MONEY_MAX = ("integer")
|
||||
EXP_RATE = ("double")
|
||||
ADD_TIME = ("smallint")
|
||||
SLOT_INDEX = ("smallint")
|
||||
|
||||
__primary_key__ = ("ITEM_ID",)
|
||||
|
||||
|
||||
class ItemEquipData(Table):
|
||||
ITEM_ID = ("integer")
|
||||
FLAGS = ("varchar[]", {"default": "'{}'"})
|
||||
EQUIP_SLOTS = ("varchar[]", {"default": "'{}'"})
|
||||
ATTACK_SPEED = ("smallint", {"default": 0})
|
||||
RUC = ("smallint", {"default": 0})
|
||||
REQ_STR = ("smallint", {"default": 0})
|
||||
REQ_DEX = ("smallint", {"default": 0})
|
||||
REQ_INT = ("smallint", {"default": 0})
|
||||
REQ_LUK = ("smallint", {"default": 0})
|
||||
REQ_FAME = ("smallint", {"default": 0})
|
||||
REQ_JOB = ("smallint[]", {"default": "'{}'"})
|
||||
HP = ("smallint", {"default": 0})
|
||||
HP_PERCENTAGE = ("smallint", {"default": 0})
|
||||
MP = ("smallint", {"default": 0})
|
||||
MP_PERCENTAGE = ("smallint", {"default": 0})
|
||||
STR = ("smallint", {"default": 0})
|
||||
DEX = ("smallint", {"default": 0})
|
||||
INT = ("smallint", {"default": 0})
|
||||
LUK = ("smallint", {"default": 0})
|
||||
HANDS = ("smallint", {"default": 0})
|
||||
WEAPON_ATTACK = ("smallint", {"default": 0})
|
||||
WEAPON_DEFENSE = ("smallint", {"default": 0})
|
||||
MAGIC_ATTACK = ("smallint", {"default": 0})
|
||||
MAGIC_DEFENSE = ("smallint", {"default": 0})
|
||||
ACCURACY = ("smallint", {"default": 0})
|
||||
AVOID = ("smallint", {"default": 0})
|
||||
JUMP = ("smallint", {"default": 0})
|
||||
SPEED = ("smallint", {"default": 0})
|
||||
TRACTION = ("double", {"default": 0})
|
||||
RECOVERY = ("double", {"default": 0})
|
||||
KNOCKBACK = ("smallint", {"default": 0})
|
||||
TAMING_MOB = ("smallint", {"default": 0})
|
||||
DURABILITY = ("integer", {"default": "'-1'::integer"})
|
||||
INC_LIGHTNING_DAMAGE = ("smallint", {"default": 0})
|
||||
INC_ICE_DAMAGE = ("smallint", {"default": 0})
|
||||
INC_FIRE_DAMAGE = ("smallint", {"default": 0})
|
||||
INC_POISON_DAMAGE = ("smallint", {"default": 0})
|
||||
ELEMENTAL_DEFAULT = ("smallint", {"default": 0})
|
||||
CRAFT = ("smallint", {"default": 0})
|
||||
SET_ID = ("integer", {"default": 0})
|
||||
ENCHANT_CATEGORY = ("smallint", {"default": 0})
|
||||
HEAL_HP = ("smallint", {"default": 0})
|
||||
SPECIAL_ID = ("integer", {"default": 0})
|
||||
|
||||
__primary_key__ = ("ITEM_ID")
|
||||
|
||||
|
||||
class ItemPetData(Table):
|
||||
ITEM_ID = ("integer")
|
||||
HUNGER = ("smallint")
|
||||
LIFE = ("smallint")
|
||||
LIMITED_LIFE = ("integer")
|
||||
EVOLUTION_ITEM = ("integer")
|
||||
EVOLUTION_LEVEL = ("smallint")
|
||||
FLAGS = ("varchar[]")
|
||||
|
||||
__primary_key__ = ("ITEM_ID",)
|
||||
|
||||
|
||||
class ItemRechargeableData(Table):
|
||||
ITEM_ID = ("integer")
|
||||
UNIT_PRICE = ("smallint")
|
||||
WEAPON_ATTACK = ("smallint")
|
||||
|
||||
__primary_key__ = ("ITEM_ID")
|
||||
|
||||
|
||||
class MapData(Table):
|
||||
MAP_ID = ("integer")
|
||||
MAP_NAME = ("text", {"default": "''::text"})
|
||||
STREET_NAME = ("text", {"default": "''::text"})
|
||||
MAP_MARK = ("text", {"default": "''::text"})
|
||||
FLAGS = ("varchar[]", {"default": "'{}'"})
|
||||
MOB_RATE = ("double", {"default": 1.0})
|
||||
FIXED_MOB_CAPACITY = ("smallint")
|
||||
SPAWN_MOB_INTERVAL = ("smallint")
|
||||
DROP_RATE = ("double", {"default": 1.0})
|
||||
REGEN_RATE = ("double", {"default": 1.0})
|
||||
SHUFFLE_NAME = ("text", {"default": "NULL::varchar"})
|
||||
DEFAULT_BGM = ("text", {"default": "NULL::varchar"})
|
||||
EFFECT = ("text", {"default": "NULL::varchar"})
|
||||
MIN_LEVEL_LIMIT = ("smallint")
|
||||
MAX_LEVEL_LIMIT = ("smallint")
|
||||
TIME_LIMIT = ("smallint")
|
||||
DEFAULT_TRACTION = ("double", {"default": 1})
|
||||
MAP_LTX = ("smallint")
|
||||
MAP_LTY = ("smallint")
|
||||
MAP_RBX = ("smallint")
|
||||
MAP_RBY = ("smallint")
|
||||
LP_BOTTOM = ("smallint")
|
||||
LP_TOP = ("smallint")
|
||||
LP_SIDE = ("smallint")
|
||||
FORCED_MAP_RETURN = ("integer")
|
||||
FIELD_TYPE = ("smallint")
|
||||
DECREASE_HP = ("integer")
|
||||
DECREASE_MP = ("integer")
|
||||
DECREASE_INTERVAL = ("smallint")
|
||||
DAMAGE_PER_SECOND = ("smallint")
|
||||
PROTECT_ITEM = ("integer")
|
||||
SHIP_KIND = ("smallint")
|
||||
CONSUME_COOLDOWN = ("smallint")
|
||||
LINK = ("integer")
|
||||
FIELD_LIMITATIONS = ("bigint", {"default": 0})
|
||||
RETURN_MAP = ("integer")
|
||||
MAP_LIMIT = ("smallint")
|
||||
MAP_VERSION = ("smallint")
|
||||
ON_FIRST_USER_ENTER = ("text")
|
||||
ON_USER_ENTER = ("text")
|
||||
DESCRIPTION = ("text", {"default": "''::text"})
|
||||
MOVE_LIMIT = ("smallint")
|
||||
PROTECT_SET = ("integer")
|
||||
ALLOWED_ITEM_DROP = ("integer")
|
||||
DROP_EXPIRE = ("smallint")
|
||||
TIME_OUT_LIMIT = ("smallint")
|
||||
PHASE_ALPHA = ("smallint")
|
||||
PHASE_BACKGROUND = ("smallint")
|
||||
TIME_MOB_SPAWN = ("smallint")
|
||||
|
||||
__primary_key__ = ("MAP_ID")
|
||||
|
||||
|
||||
class MapFootholds(Table):
|
||||
MAP_ID = ("integer")
|
||||
GROUP_ID = ("smallint")
|
||||
ID = ("smallint")
|
||||
X_1 = ("smallint")
|
||||
Y_1 = ("smallint")
|
||||
X_2 = ("smallint")
|
||||
Y_2 = ("smallint")
|
||||
DRAG_FORCE = ("smallint")
|
||||
PREVIOUS_ID = ("smallint")
|
||||
NEXT_ID = ("smallint")
|
||||
FLAGS = ("varchar[]")
|
||||
|
||||
__primary_key__ = ("MAP_ID", "GROUP_ID", "ID")
|
||||
|
||||
|
||||
class MapLife(Table):
|
||||
MAP_ID = ("integer")
|
||||
LIFE_ID = ("integer")
|
||||
LIFE_TYPE = ("text")
|
||||
LIFE_NAME = ("text", {"default": "''::text"})
|
||||
X_POS = ("smallint")
|
||||
Y_POS = ("smallint")
|
||||
FOOTHOLD = ("smallint")
|
||||
MIN_CLICK_POS = ("smallint")
|
||||
MAX_CLICK_POS = ("smallint")
|
||||
RESPAWN_TIME = ("integer")
|
||||
FLAGS = ("varchar[]", {"default": "'{}'"})
|
||||
CY = ("smallint")
|
||||
FACE = ("boolean", {"default": 0})
|
||||
HIDE = ("boolean", {"default": 0})
|
||||
|
||||
__primary_key__ = None
|
||||
|
||||
|
||||
class MapPortals(Table):
|
||||
MAP_ID = ("integer")
|
||||
ID = ("smallint")
|
||||
NAME = ("text")
|
||||
X_POS = ("smallint")
|
||||
Y_POS = ("smallint")
|
||||
DESTINATION = ("integer")
|
||||
DESTINATION_LABEL = ("text")
|
||||
PORTAL_TYPE = ("smallint")
|
||||
|
||||
__primary_key__ = ("MAP_ID", "ID")
|
||||
|
||||
|
||||
class MapReactors(Table):
|
||||
MAP_ID = ("integer")
|
||||
REACTOR_ID = ("integer")
|
||||
REACTOR_TIME = ("integer")
|
||||
NAME = ("text")
|
||||
X = ("smallint")
|
||||
Y = ("smallint")
|
||||
F = ("smallint")
|
||||
|
||||
__primary_key__ = ("MAP_ID", "REACTOR_ID")
|
||||
|
||||
|
||||
class MapTimedMobs(Table):
|
||||
MAP_ID = ("integer")
|
||||
MOB_ID = ("integer")
|
||||
START_HOUR = ("smallint")
|
||||
END_HOUR = ("smallint")
|
||||
MESSAGE = ("text")
|
||||
|
||||
__primary_key__ = ("MAP_ID", "MOB_ID")
|
||||
|
||||
|
||||
class MobData(Table):
|
||||
MOB_ID = ("integer")
|
||||
MOB_LEVEL = ("smallint")
|
||||
FLAGS = ("varchar[]", {"default": "'{}'"})
|
||||
HP = ("integer")
|
||||
HP_RECOVERY = ("integer")
|
||||
MP = ("integer")
|
||||
MP_RECOVERY = ("integer")
|
||||
EXPERIENCE = ("integer")
|
||||
KNOCKBACK = ("integer")
|
||||
FIXED_DAMAGE = ("integer")
|
||||
EXPLODE_HP = ("integer")
|
||||
LINK = ("integer")
|
||||
SUMMON_TYPE = ("smallint")
|
||||
DEATH_BUFF = ("integer")
|
||||
DEATH_AFTER = ("integer")
|
||||
TRACTION = ("double", {"default": 1.0})
|
||||
DAMAGED_BY_SKILL_ONLY = ("smallint")
|
||||
DAMAGED_BY_MOB_ONLY = ("integer")
|
||||
DROP_ITEM_PERIOD = ("smallint")
|
||||
HP_BAR_COLOR = ("smallint")
|
||||
HP_BAR_BG_COLOR = ("smallint")
|
||||
CARNIVAL_POINTS = ("smallint")
|
||||
PHYSICAL_ATTACK = ("smallint")
|
||||
PHYSICAL_DEFENSE = ("smallint")
|
||||
PHYSICAL_DEFENSE_RATE = ("smallint")
|
||||
MAGICAL_ATTACK = ("smallint")
|
||||
MAGICAL_DEFENSE = ("smallint")
|
||||
MAGICAL_DEFENSE_RATE = ("smallint")
|
||||
ACCURACY = ("smallint")
|
||||
AVOID = ("smallint")
|
||||
SPEED = ("smallint")
|
||||
FLY_SPEED = ("smallint")
|
||||
CHASE_SPEED = ("smallint")
|
||||
ICE_MODIFIER = ("smallint")
|
||||
FIRE_MODIFIER = ("smallint")
|
||||
POISON_MODIFIER = ("smallint")
|
||||
LIGHTNING_MODIFIER = ("smallint")
|
||||
HOLY_MODIFIER = ("smallint")
|
||||
DARK_MODIFIER = ("smallint")
|
||||
NONELEMENTAL_MODIFIER = ("smallint")
|
||||
|
||||
__primary_key__ = ("MOB_ID")
|
||||
|
||||
|
||||
class MobSkills(Table):
|
||||
MOB_ID = ("integer")
|
||||
SKILL_ID = ("smallint")
|
||||
LEVEL = ("smallint")
|
||||
EFFECT_AFTER = ("smallint")
|
||||
|
||||
__primary_key__ = ("MOB_ID", "SKILL_ID")
|
||||
|
||||
|
||||
class PlayerSkillData(Table):
|
||||
SKILL_ID = ("integer")
|
||||
FLAGS = ("varchar[]")
|
||||
WEAPON = ("smallint")
|
||||
SUB_WEAPON = ("smallint")
|
||||
MAX_LEVEL = ("smallint")
|
||||
BASE_MAX_LEVEL = ("smallint")
|
||||
SKILL_TYPE = ("varchar[]")
|
||||
ELEMENT = ("text")
|
||||
MOB_COUNT = ("text")
|
||||
HIT_COUNT = ("text")
|
||||
BUFF_TIME = ("text")
|
||||
HP_COST = ("text")
|
||||
MP_COST = ("text")
|
||||
DAMAGE = ("text")
|
||||
FIXED_DAMAGE = ("text")
|
||||
CRITICAL_DAMAGE = ("text")
|
||||
MASTERY = ("text")
|
||||
OPTIONAL_ITEM_COST = ("smallint")
|
||||
ITEM_COST = ("integer")
|
||||
ITEM_COUNT = ("smallint")
|
||||
BULLET_COST = ("smallint")
|
||||
MONEY_COST = ("text")
|
||||
X_PROPERTY = ("text")
|
||||
Y_PROPERTY = ("text")
|
||||
SPEED = ("text")
|
||||
JUMP = ("text")
|
||||
STR = ("text")
|
||||
WEAPON_ATTACK = ("text")
|
||||
WEAPON_DEFENSE = ("text")
|
||||
MAGIC_ATTACK = ("text")
|
||||
MAGIC_DEFENSE = ("text")
|
||||
ACCURACY = ("text")
|
||||
AVOID = ("text")
|
||||
HP = ("text")
|
||||
MP = ("text")
|
||||
PROBABILITY = ("text")
|
||||
LTX = ("smallint")
|
||||
LTY = ("smallint")
|
||||
RBX = ("smallint")
|
||||
RBY = ("smallint")
|
||||
COOLDOWN_TIME = ("text")
|
||||
AVOID_CHANCE = ("text")
|
||||
RANGE = ("text")
|
||||
MORPH = ("smallint")
|
||||
|
||||
__primary_key__ = ("SKILL_ID")
|
||||
|
||||
|
||||
class PlayerSkillRequirementData(Table):
|
||||
SKILL_ID = ("integer")
|
||||
REQ_SKILL_ID = ("integer")
|
||||
REQ_LEVEL = ("smallint")
|
||||
|
||||
__primary_key__ = ("SKILL_ID", "REQ_SKILL_ID")
|
||||
|
||||
|
||||
class String(Table):
|
||||
OBJECT_ID = ("integer")
|
||||
OBJECT_TYPE = ("varchar")
|
||||
DESCRIPTION = ("text")
|
||||
LABEL = ("text")
|
||||
|
||||
__primary_key__ = ("OBJECT_ID", "OBJECT_TYPE")
|
||||
|
||||
|
||||
class RMDB(Schema):
|
||||
ITEM_CONSUMEABLE_DATA = ItemConsumeableData
|
||||
ITEM_DATA = ItemData
|
||||
ITEM_EQUIP_DATA = ItemEquipData
|
||||
ITEM_PET_DATA = ItemPetData
|
||||
ITEM_RECHARGEABLE_DATA = ItemRechargeableData
|
||||
MAP_DATA = MapData
|
||||
MAP_FOOTHOLDS = MapFootholds
|
||||
MAP_LIFE = MapLife
|
||||
MAP_PORTALS = MapPortals
|
||||
MAP_REACTORS = MapReactors
|
||||
MAP_TIMED_MOBS = MapTimedMobs
|
||||
MOB_DATA = MobData
|
||||
MOB_SKILLS = MobSkills
|
||||
PLAYER_SKILL_DATA = PlayerSkillData
|
||||
PLAYER_SKILL_REQS_DATA = PlayerSkillRequirementData
|
||||
STRING = String
|
||||
|
||||
|
||||
class Account(Table):
|
||||
ID = ("serial")
|
||||
USERNAME = ("text")
|
||||
PASSWORD = ("text")
|
||||
SALT = ("text")
|
||||
CREATION = ("date", {"default": "CURRENT_DATE"})
|
||||
LAST_LOGIN = ("date", {"default": '"1970-01-01"::date'})
|
||||
LAST_IP = ("inet", {"default": '"0.0.0.0"::inet'})
|
||||
BAN = ("bool", {"default": 0})
|
||||
ADMIN = ("bool", {"default": 0})
|
||||
GENDER = ("bool")
|
||||
|
||||
__primary_key__ = ("ID",)
|
||||
# __unqiue_index__ = ("USERNAME")
|
||||
|
||||
|
||||
class Buddies(Table):
|
||||
pass
|
||||
|
||||
|
||||
class Character(Table):
|
||||
ID = ("integer")
|
||||
ACCOUNT_ID = ("integer")
|
||||
NAME = ("text")
|
||||
GENDER = ("bool")
|
||||
SKIN = ("smallint")
|
||||
FACE = ("smallint")
|
||||
HAIR = ("smallint")
|
||||
PET_LOCKER = ("int[]")
|
||||
LEVEL = ("smallint")
|
||||
JOB = ("smallint")
|
||||
STR = ("smallint")
|
||||
DEX = ("smallint")
|
||||
INT = ("smallint")
|
||||
LUK = ("smallint")
|
||||
HP = ("smallint")
|
||||
MAX_HP = ("smallint")
|
||||
MP = ("smallint")
|
||||
MAX_MP = ("smallint")
|
||||
AP = ("smallint")
|
||||
SP = ("smallint")
|
||||
EXP = ("integer")
|
||||
MONEY = ("integer")
|
||||
TEMP_EXP = ("integer")
|
||||
FIELD_ID = ("integer")
|
||||
PORTAL = ("text")
|
||||
PLAY_TIME = ("integer")
|
||||
SUB_JOB = ("smallint")
|
||||
FAME = ("smallint")
|
||||
EXTEND_SP = ("smallint[]")
|
||||
WORLD_ID = ("smallint")
|
||||
|
||||
__primary_key__ = ("ID",)
|
||||
__foreign_keys__ = ({"account_id": "accounts.id"})
|
||||
|
||||
|
||||
class InventoryEquipment(Table):
|
||||
INVENTORY_ITEM_ID = ("bigint")
|
||||
UPGRADE_SLOTS = ("integer")
|
||||
STR = ("smallint")
|
||||
DEX = ("smallint")
|
||||
INT = ("smallint")
|
||||
LUK = ("smallint")
|
||||
HP = ("integer")
|
||||
MP = ("integer")
|
||||
WEAPON_ATTACK = ("smallint")
|
||||
MAGIC_ATTACK = ("smallint")
|
||||
WEAPON_DEFENSE = ("smallint")
|
||||
MAGIC_DEFENSE = ("smallint")
|
||||
ACCURACY = ("smallint")
|
||||
AVOID = ("smallint")
|
||||
HANDS = ("smallint")
|
||||
SPEED = ("smallint")
|
||||
JUMP = ("smallint")
|
||||
RING_ID = ("integer")
|
||||
CRAFT = ("smallint")
|
||||
ATTRIBUTE = ("smallint")
|
||||
LEVEL_UP_TYPE = ("smallint")
|
||||
LEVEL = ("smallint")
|
||||
IUC = ("integer")
|
||||
GRADE = ("smallint")
|
||||
EXP = ("smallint")
|
||||
CHUC = ("smallint")
|
||||
OPTION_1 = ("smallint")
|
||||
OPTION_2 = ("smallint")
|
||||
OPTION_3 = ("smallint")
|
||||
SOCKET_1 = ("smallint")
|
||||
SOCKET_2 = ("smallint")
|
||||
LISN = ("bigint")
|
||||
CUC = ("smallint")
|
||||
RUC = ("smallint")
|
||||
|
||||
__primary_key__ = ("inventory_item_id")
|
||||
__foreign_keys__ = {"INVENTORY_ITEM_ID": "inventory_items.inventory_item_id"}
|
||||
|
||||
|
||||
class InventoryItems(Table):
|
||||
INVENTORY_ITEM_ID = ("bigint")
|
||||
CHARACTER_ID = ("integer")
|
||||
STORAGE_ID = ("integer")
|
||||
ITEM_ID = ("integer")
|
||||
INVENTORY_TYPE = ("smallint")
|
||||
POSITION = ("integer")
|
||||
OWNER = ("text")
|
||||
PET_ID = ("bigint")
|
||||
QUANTITY = ("integer")
|
||||
CISN = ("bigint") # Current inventory Serial
|
||||
SN = ("bigint") # Serial Number (Would replace inventory_item_id?)
|
||||
EXPIRE = ("bigint")
|
||||
|
||||
__primary_key__ = ("inventory_item_id")
|
||||
|
||||
|
||||
# class InventoryPets(Table):
|
||||
# pass
|
||||
|
||||
|
||||
# class Keymap(Table):
|
||||
# pass
|
||||
|
||||
|
||||
# class Skills(Table):
|
||||
# pass
|
||||
|
||||
|
||||
class Maplestory(Schema):
|
||||
ACCOUNTS = Account
|
||||
BUDDIES = Buddies
|
||||
CHARACTERS = Character
|
||||
INVENTORY_EQUIPMENT = InventoryEquipment
|
||||
INVENTORY_ITEMS = InventoryItems
|
||||
# INVENTORY_PETS = InventoryPets
|
||||
# KEYMAP = Keymap
|
||||
# SKILLS = Skills
|
|
@ -1,6 +0,0 @@
|
|||
from .field import Field
|
||||
from .field_object import FieldObject
|
||||
from .foothold import Foothold
|
||||
from .mob import Mob
|
||||
from .npc import Npc
|
||||
from .portal import Portal
|
|
@ -1,32 +0,0 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from common.abc import WildcardData
|
||||
from .move_path import MovePath
|
||||
from utils import log
|
||||
|
||||
|
||||
class FieldObject(WildcardData):
|
||||
def __init__(self):
|
||||
self._obj_id = -1
|
||||
self._position = MovePath()
|
||||
self._field = None
|
||||
|
||||
@property
|
||||
def obj_id(self):
|
||||
return self._obj_id
|
||||
|
||||
@obj_id.setter
|
||||
def obj_id(self, value):
|
||||
self._obj_id = value
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
return self._position
|
||||
|
||||
@property
|
||||
def field(self):
|
||||
return self._field
|
||||
|
||||
@field.setter
|
||||
def field(self, value):
|
||||
self._field = value
|
|
@ -1,25 +0,0 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from common.abc import WildcardData
|
||||
|
||||
|
||||
@dataclass
|
||||
class Foothold(WildcardData):
|
||||
id: int = 0
|
||||
prev: int = 0
|
||||
next: int = 0
|
||||
x1: int = 0
|
||||
y1: int = 0
|
||||
x2: int = 0
|
||||
y2: int = 0
|
||||
|
||||
@property
|
||||
def wall(self):
|
||||
return self.x1 == self.x2
|
||||
|
||||
def compare_to(self, foothold):
|
||||
if self.y2 < foothold.y1:
|
||||
return -1
|
||||
if self.y1 > foothold.y2:
|
||||
return 1
|
||||
return 0
|
|
@ -1,18 +0,0 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from .field_object import FieldObject
|
||||
|
||||
|
||||
@dataclass
|
||||
class Life(FieldObject):
|
||||
life_id: int = 0
|
||||
life_type: str = ""
|
||||
foothold: int = 0
|
||||
x: int = 0
|
||||
y: int = 0
|
||||
cy: int = 0
|
||||
f: int = 0
|
||||
hide: int = 0
|
||||
rx0: int = 0 # min click position
|
||||
rx1: int = 0 # max click position
|
||||
mob_time: int = 0
|
51
field/mob.py
51
field/mob.py
|
@ -1,51 +0,0 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from .life import Life
|
||||
from .move_path import MovePath
|
||||
# from utils import TagPoint
|
||||
|
||||
|
||||
@dataclass
|
||||
class Mob(Life):
|
||||
mob_id: int = 0
|
||||
hp: int = 0
|
||||
mp: int = 0
|
||||
hp_recovery: int = 0
|
||||
mp_recovery: int = 0
|
||||
exp: int = 0
|
||||
physical_attack: int = 0
|
||||
|
||||
def __post_init__(self):
|
||||
self.attackers = {}
|
||||
self.pos = MovePath(self.x, self.cy, self.foothold)
|
||||
self.cur_hp = self.hp
|
||||
self.cur_mp = self.mp
|
||||
self.controller = 0
|
||||
|
||||
@property
|
||||
def dead(self):
|
||||
return self.cur_hp <= 0
|
||||
|
||||
def damage(self, character, amount):
|
||||
pass
|
||||
|
||||
def encode_init(self, packet):
|
||||
packet.encode_int(self.obj_id)
|
||||
packet.encode_byte(5)
|
||||
packet.encode_int(self.life_id)
|
||||
|
||||
# Set Temporary Stat
|
||||
packet.encode_long(0)
|
||||
packet.encode_long(0)
|
||||
|
||||
packet.encode_short(self.pos.x)
|
||||
packet.encode_short(self.pos.y)
|
||||
packet.encode_byte(0 & 1 | 2 * 2)
|
||||
packet.encode_short(self.pos.foothold)
|
||||
packet.encode_short(self.pos.foothold)
|
||||
|
||||
packet.encode_byte(abs(-2))
|
||||
|
||||
packet.encode_byte(0)
|
||||
packet.encode_int(0)
|
||||
packet.encode_int(0)
|
|
@ -1,28 +0,0 @@
|
|||
from .object_pool import ObjectPool
|
||||
|
||||
class MobPool(ObjectPool):
|
||||
def __init__(self, field):
|
||||
super().__init__(field)
|
||||
self.spawns = []
|
||||
|
||||
def add(self, mob):
|
||||
mob.field = self.field
|
||||
super().add(mob)
|
||||
self.spawns.append(mob)
|
||||
|
||||
async def remove(self, key):
|
||||
mob = self.get(key)
|
||||
|
||||
if mob:
|
||||
owner = None
|
||||
|
||||
# await self.field.broadcast(CPacket.mob_leave_field(mob))
|
||||
|
||||
if mob.dead:
|
||||
pass
|
||||
# drop_pos_x = mob.position.x
|
||||
# drop_pos_y = mob.position.y
|
||||
|
||||
# self.field.drops.add(Drop(0, mob.position, 50, 1))
|
||||
|
||||
return super().remove(key)
|
11
field/npc.py
11
field/npc.py
|
@ -1,11 +0,0 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from .life import Life
|
||||
from .move_path import MovePath
|
||||
|
||||
|
||||
@dataclass
|
||||
class Npc(Life):
|
||||
def __post_init__(self):
|
||||
self.pos = MovePath(self.x, self.cy, self.foothold)
|
||||
self.id = self.life_id
|
|
@ -1,5 +0,0 @@
|
|||
from .object_pool import ObjectPool
|
||||
|
||||
class NpcPool(ObjectPool):
|
||||
pass
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
class ObjectPool:
|
||||
def __init__(self, field):
|
||||
self.field = field
|
||||
self.cache = {}
|
||||
self.uid_base = 1000
|
||||
|
||||
@property
|
||||
def new_uid(self):
|
||||
self.uid_base += 1
|
||||
return self.uid_base
|
||||
|
||||
def add(self, value):
|
||||
value.obj_id = self.new_uid
|
||||
self.cache[value.obj_id] = value
|
||||
|
||||
def remove(self, key):
|
||||
return self.cache.pop(key)
|
||||
|
||||
def clear(self):
|
||||
self.cache = {}
|
||||
|
||||
def get(self, key):
|
||||
return self.cache.get(key, None)
|
||||
|
||||
def __enumerator__(self):
|
||||
return (obj for obj in self.cache.values())
|
||||
|
||||
def __iter__(self):
|
||||
return (obj for obj in self.cache.values())
|
||||
|
||||
def __aiter__(self):
|
||||
return self.__iter__()
|
|
@ -1,21 +0,0 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from common.abc import WildcardData
|
||||
from utils import TagPoint
|
||||
|
||||
|
||||
@dataclass
|
||||
class Portal(WildcardData):
|
||||
id: int = 0
|
||||
name: str = ""
|
||||
type: int = 0
|
||||
destination: int = 0
|
||||
destination_label: str = ""
|
||||
x: int = 0
|
||||
y: int = 0
|
||||
|
||||
def __post_init__(self):
|
||||
self.point = TagPoint(self.x, self.y)
|
||||
|
||||
def __str__(self):
|
||||
return f"{id} @ {self.point} -> {self.destination}"
|
|
@ -1,14 +0,0 @@
|
|||
from .object_pool import ObjectPool
|
||||
|
||||
|
||||
class UserPool(ObjectPool):
|
||||
def add(self, client):
|
||||
super().add(client)
|
||||
client.character.field = self.field
|
||||
|
||||
@property
|
||||
def characters(self):
|
||||
return [client.character for client in self]
|
||||
|
||||
def __aiter__(self):
|
||||
return [client for client in self]
|
|
@ -1,116 +0,0 @@
|
|||
from aiohttp import ClientSession
|
||||
from loguru import logger
|
||||
|
||||
from character import Character
|
||||
from game import ItemSlotEquip
|
||||
from common.constants import HTTP_API_ROUTE
|
||||
from utils import fix_dict_keys
|
||||
|
||||
|
||||
class Route:
|
||||
def __init__(self, method, route,
|
||||
data=None, params=None, json=None):
|
||||
self._method = method
|
||||
self._route = route
|
||||
self._data = data
|
||||
self._params = params
|
||||
self._json = json
|
||||
|
||||
|
||||
class HTTPClient:
|
||||
def __init__(self, loop=None):
|
||||
self._loop = loop
|
||||
self._host = HTTP_API_ROUTE
|
||||
|
||||
async def request(self, route, content_type="json"):
|
||||
session = ClientSession(loop=self._loop)
|
||||
|
||||
kwargs = {}
|
||||
url = self._host + route._route
|
||||
|
||||
if route._data:
|
||||
kwargs['data'] = route._data
|
||||
|
||||
if route._params:
|
||||
kwargs['params'] = route._params
|
||||
|
||||
if route._json:
|
||||
kwargs['json'] = route._json
|
||||
|
||||
r = await session.request(route._method, url, **kwargs)
|
||||
|
||||
try:
|
||||
if r.status is 200:
|
||||
if content_type == 'json':
|
||||
data = await r.json()
|
||||
|
||||
elif content_type == 'image':
|
||||
data = await r.read()
|
||||
|
||||
else:
|
||||
data = await r.text()
|
||||
|
||||
return data
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
(e)
|
||||
|
||||
finally:
|
||||
await r.release()
|
||||
await session.close()
|
||||
|
||||
##
|
||||
# Main Data Provider
|
||||
##
|
||||
|
||||
async def is_username_taken(self, character_name):
|
||||
response = await self.request(
|
||||
Route("GET", "/character/name/" + character_name))
|
||||
|
||||
return response['resp']
|
||||
|
||||
async def login(self, username, password):
|
||||
route = Route(
|
||||
"POST", "/login",
|
||||
data={
|
||||
'username': username,
|
||||
'password': password,
|
||||
}
|
||||
)
|
||||
|
||||
response = await self.request(route)
|
||||
return response
|
||||
|
||||
async def get_characters(self, account_id, world_id=None):
|
||||
if not world_id:
|
||||
url = f"/account/{account_id}/characters"
|
||||
|
||||
else:
|
||||
url = f"/account/{account_id}/characters/{world_id}"
|
||||
|
||||
response = fix_dict_keys(await self.request(Route("GET", url)))
|
||||
|
||||
return [Character.from_data(**character) for character in response['characters']]
|
||||
|
||||
async def create_new_character(self, account_id, character:Character):
|
||||
route = Route(
|
||||
"POST", f"/account/{account_id}/character/new",
|
||||
json=character.__serialize__()
|
||||
)
|
||||
|
||||
response = await self.request(route)
|
||||
|
||||
return response['id']
|
||||
|
||||
##
|
||||
# Static Data Provider
|
||||
##
|
||||
|
||||
async def get_item(self, item_id):
|
||||
route = Route("GET", f"/item/{item_id}")
|
||||
response = await self.request(route)
|
||||
|
||||
item = ItemSlotEquip(**response)
|
||||
return item
|
|
@ -0,0 +1,7 @@
|
|||
__all__ = "log", "WvsCenter", "constants", "Packet", "CSendOps", "CRecvOps"
|
||||
|
||||
from .common import constants
|
||||
from .logger import log
|
||||
from .server import WvsCenter
|
||||
from .net.packet import Packet
|
||||
from .net.opcodes import CSendOps, CRecvOps
|
|
@ -4,4 +4,4 @@ from .character_entry import CharacterEntry
|
|||
from .modifiers import CharacterModifiers
|
||||
from .character_stats import CharacterStats
|
||||
from .func_key import FuncKey, FuncKeys
|
||||
from .skill_entry import SkillEntry
|
||||
from .skill_entry import SkillEntry
|
|
@ -1,16 +1,16 @@
|
|||
class Account:
|
||||
def __init__(
|
||||
self,
|
||||
id=0,
|
||||
username="",
|
||||
password="",
|
||||
gender=0,
|
||||
creation="",
|
||||
last_login="",
|
||||
last_ip="",
|
||||
ban=0,
|
||||
admin=0,
|
||||
last_connected_world=0
|
||||
self,
|
||||
id=0,
|
||||
username="",
|
||||
password="",
|
||||
gender=0,
|
||||
creation="",
|
||||
last_login="",
|
||||
last_ip="",
|
||||
ban=0,
|
||||
admin=0,
|
||||
last_connected_world=0,
|
||||
):
|
||||
self.id: int = id
|
||||
self.username: str = username
|
|
@ -1,12 +1,12 @@
|
|||
from common import abc
|
||||
from utils import filter_out_to, Random
|
||||
from mapy.common import abc
|
||||
from mapy.field import FieldObject
|
||||
from mapy.game import item as Item
|
||||
from mapy.utils.tools import Random, filter_out_to
|
||||
|
||||
from .modifiers import CharacterModifiers
|
||||
from .character_stats import CharacterStats
|
||||
from .func_key import FuncKeys
|
||||
from .inventory import InventoryManager, InventoryType
|
||||
from field import FieldObject
|
||||
from game import item as Item
|
||||
from .modifiers import CharacterModifiers
|
||||
|
||||
|
||||
class Character(FieldObject):
|
||||
|
@ -83,11 +83,11 @@ class Character(FieldObject):
|
|||
|
||||
def encode(self, packet):
|
||||
packet.encode_long(-1 & 0xFFFFFFFF)
|
||||
packet.encode_byte(0) # combat orders
|
||||
packet.encode_byte(0) # combat orders
|
||||
packet.encode_byte(0)
|
||||
|
||||
self.stats.encode(packet)
|
||||
packet.encode_byte(100) # Buddylist capacity
|
||||
packet.encode_byte(100) # Buddylist capacity
|
||||
packet.encode_byte(False)
|
||||
packet.encode_int(self.stats.money)
|
||||
|
||||
|
@ -133,9 +133,19 @@ class Character(FieldObject):
|
|||
new_index = index + 100 if index < -100 else index
|
||||
stickers[new_index] = item
|
||||
|
||||
inv_equip = {slot: item for slot, item in self.equip_inventory.items.items() if slot >= 0}
|
||||
dragon_equip = {slot: item for slot, item in self.equip_inventory.items.items() if slot >= -1100 and slot < -1000}
|
||||
mechanic_equip = {slot: item for slot, item in self.equip_inventory.items.items() if slot >= -1200 and slot < -1100}
|
||||
inv_equip = {
|
||||
slot: item for slot, item in self.equip_inventory.items.items() if slot >= 0
|
||||
}
|
||||
dragon_equip = {
|
||||
slot: item
|
||||
for slot, item in self.equip_inventory.items.items()
|
||||
if slot >= -1100 and slot < -1000
|
||||
}
|
||||
mechanic_equip = {
|
||||
slot: item
|
||||
for slot, item in self.equip_inventory.items.items()
|
||||
if slot >= -1200 and slot < -1100
|
||||
}
|
||||
|
||||
for inv in [eqp_normal, stickers, inv_equip, dragon_equip, mechanic_equip]:
|
||||
for slot, item in inv.items():
|
||||
|
@ -158,7 +168,7 @@ class Character(FieldObject):
|
|||
skill.encode(packet)
|
||||
|
||||
if False:
|
||||
packet.encode_int(skill.mastery_level) # is skill needed for mastery
|
||||
packet.encode_int(skill.mastery_level) # is skill needed for mastery
|
||||
|
||||
packet.encode_short(0)
|
||||
|
||||
|
@ -225,8 +235,7 @@ class Character(FieldObject):
|
|||
|
||||
packet.encode_byte(0xFF)
|
||||
|
||||
packet.encode_int(
|
||||
0 if not equipped.get(-111) else equipped[-111].item_id)
|
||||
packet.encode_int(0 if not equipped.get(-111) else equipped[-111].item_id)
|
||||
|
||||
# for pet_id in self.pet_ids:
|
||||
for pet_id in range(3):
|
|
@ -1,12 +1,13 @@
|
|||
from common.enum import InventoryType
|
||||
from mapy.common import InventoryType
|
||||
from .character_stats import CharacterStats
|
||||
from .inventory import Inventory
|
||||
|
||||
|
||||
class CharacterEntry:
|
||||
def __init__(self, **stats):
|
||||
self._stats = CharacterStats(**stats)
|
||||
self._equip = Inventory(InventoryType.EQUIP, 96)
|
||||
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self._stats.id
|
||||
|
@ -24,12 +25,12 @@ class CharacterEntry:
|
|||
|
||||
self._stats.encode(packet)
|
||||
self.encode_look(packet)
|
||||
packet.encode_byte(0) # VAC
|
||||
packet.encode_byte(0) # VAC
|
||||
packet.encode_byte(ranking)
|
||||
|
||||
if ranking:
|
||||
packet.skip(16)
|
||||
|
||||
|
||||
def encode_look(self, packet):
|
||||
packet.encode_byte(self.stats.gender)
|
||||
packet.encode_byte(self.stats.skin)
|
||||
|
@ -51,7 +52,7 @@ class CharacterEntry:
|
|||
for index, item in equipped.items():
|
||||
if index > -100 and equipped.get(index - 100):
|
||||
eqp_normal[index] = item
|
||||
|
||||
|
||||
else:
|
||||
new_index = index + 100 if index < -100 else index
|
||||
stickers[new_index] = item
|
||||
|
@ -63,9 +64,8 @@ class CharacterEntry:
|
|||
|
||||
packet.encode_byte(0xFF)
|
||||
|
||||
packet.encode_int(
|
||||
0 if not equipped.get(-111) else equipped[-111].item_id)
|
||||
packet.encode_int(0 if not equipped.get(-111) else equipped[-111].item_id)
|
||||
|
||||
# for pet_id in self.pet_ids:
|
||||
for pet_id in range(3):
|
||||
packet.encode_int(pet_id)
|
||||
packet.encode_int(pet_id)
|
|
@ -1,14 +1,14 @@
|
|||
class CharacterSkills(dict):
|
||||
def __init__(self, parent):
|
||||
self._parent = parent
|
||||
|
||||
|
||||
async def cast(self, skill_id):
|
||||
skill = self.get(skill_id)
|
||||
|
||||
if skill:
|
||||
await self._parent.modify.stats(
|
||||
hp = self._parent.stats.hp - 1,
|
||||
mp = self._parent.stats.mp - 1,
|
||||
hp=self._parent.stats.hp - 1,
|
||||
mp=self._parent.stats.mp - 1,
|
||||
)
|
||||
|
||||
# if skill.level_data.buff_time > 0:
|
||||
|
@ -18,6 +18,6 @@ class CharacterSkills(dict):
|
|||
# buff.generate(skill.level_data)
|
||||
|
||||
# await self._parent.buff.add(buff)
|
||||
|
||||
|
||||
return True
|
||||
return False
|
||||
return False
|
|
@ -1,52 +1,54 @@
|
|||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
from common import abc
|
||||
from mapy.common import WildcardData
|
||||
|
||||
|
||||
def default_extend_sp():
|
||||
return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
|
||||
|
||||
def default_pet_locker():
|
||||
return [0, 0, 0]
|
||||
|
||||
|
||||
@dataclass
|
||||
class CharacterStats(abc.WildcardData):
|
||||
id: int = 0
|
||||
name: str = "Maple Char"
|
||||
world_id: int = 0
|
||||
class CharacterStats(WildcardData):
|
||||
id: int = 0
|
||||
name: str = "Maple Char"
|
||||
world_id: int = 0
|
||||
|
||||
gender: int = 0
|
||||
skin: int = 0
|
||||
face: int = 20001
|
||||
hair: int = 30003
|
||||
skin: int = 0
|
||||
face: int = 20001
|
||||
hair: int = 30003
|
||||
|
||||
level: int = 10
|
||||
job: int = 0
|
||||
str: int = 10
|
||||
dex: int = 10
|
||||
int: int = 10
|
||||
luk: int = 10
|
||||
level: int = 10
|
||||
job: int = 0
|
||||
str: int = 10
|
||||
dex: int = 10
|
||||
int: int = 10
|
||||
luk: int = 10
|
||||
|
||||
hp: int = 50
|
||||
m_hp: int = 50
|
||||
mp: int = 5
|
||||
m_mp: int = 5
|
||||
hp: int = 50
|
||||
m_hp: int = 50
|
||||
mp: int = 5
|
||||
m_mp: int = 5
|
||||
|
||||
ap: int = 0
|
||||
sp: int = 0
|
||||
extend_sp: List = field(default_factory=default_extend_sp)
|
||||
ap: int = 0
|
||||
sp: int = 0
|
||||
extend_sp: List = field(default_factory=default_extend_sp)
|
||||
|
||||
exp: int = 0
|
||||
money: int = 0
|
||||
fame: int = 0
|
||||
exp: int = 0
|
||||
money: int = 0
|
||||
fame: int = 0
|
||||
|
||||
temp_exp: int = 0
|
||||
field_id: int = 100000000
|
||||
portal: int = 0
|
||||
play_time: int = 0
|
||||
sub_job: int = 0
|
||||
pet_locker: List = field(default_factory=default_pet_locker)
|
||||
temp_exp: int = 0
|
||||
field_id: int = 100000000
|
||||
portal: int = 0
|
||||
play_time: int = 0
|
||||
sub_job: int = 0
|
||||
pet_locker: List = field(default_factory=default_pet_locker)
|
||||
|
||||
def encode(self, packet) -> None:
|
||||
packet.encode_int(self.id)
|
|
@ -1,10 +1,12 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class FuncKey:
|
||||
type: int
|
||||
action: int
|
||||
|
||||
|
||||
class FuncKeys:
|
||||
def __init__(self, character):
|
||||
self._parent = character
|
||||
|
@ -12,8 +14,6 @@ class FuncKeys:
|
|||
|
||||
def __setitem__(self, key, value):
|
||||
self._func_keys[key] = value
|
||||
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._func_keys.get(key, FuncKey(0, 0))
|
||||
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
from common import abc
|
||||
from common.enum import InventoryType
|
||||
from game import item as Item
|
||||
from mapy.common import Inventory as _Inventory, InventoryType
|
||||
from mapy.game import item as Item
|
||||
|
||||
|
||||
class InventoryManager:
|
||||
|
@ -71,8 +70,7 @@ class Tracker:
|
|||
|
||||
for _, inv in self._items.items():
|
||||
for _, item in inv.items():
|
||||
if (item is None
|
||||
or item.inventory_item_id in self._starting):
|
||||
if item is None or item.inventory_item_id in self._starting:
|
||||
continue
|
||||
|
||||
throwaway.append(item.inventory_item_id)
|
||||
|
@ -86,7 +84,7 @@ class Tracker:
|
|||
self._starting.append(item.inventory_item_id)
|
||||
|
||||
|
||||
class Inventory(abc.Inventory):
|
||||
class Inventory(_Inventory):
|
||||
def __init__(self, type_, slots):
|
||||
self._unique_id = None
|
||||
self.type = type_
|
|
@ -1,12 +1,13 @@
|
|||
from common.enum import StatModifiers
|
||||
from common.constants import is_extend_sp_job
|
||||
from utils import CPacket
|
||||
from mapy.common import StatModifiers
|
||||
from mapy.common.constants import is_extend_sp_job
|
||||
from mapy.utils.cpacket import CPacket
|
||||
|
||||
|
||||
class StatModifier:
|
||||
def __init__(self, character_stats):
|
||||
self._modifiers = []
|
||||
self._stats = character_stats
|
||||
|
||||
|
||||
@property
|
||||
def modifiers(self):
|
||||
return self._modifiers
|
||||
|
@ -16,14 +17,14 @@ class StatModifier:
|
|||
_flag = 0
|
||||
for mod in self._modifiers:
|
||||
_flag |= mod.value
|
||||
return _flag
|
||||
return _flag
|
||||
|
||||
def alter(self, **stats):
|
||||
for key, val in stats.items():
|
||||
modifier = StatModifiers[key.upper()]
|
||||
self._modifiers.append(modifier)
|
||||
setattr(self._stats, key, val)
|
||||
|
||||
|
||||
def encode(self, packet):
|
||||
packet.encode_int(self.flag)
|
||||
|
||||
|
@ -37,13 +38,15 @@ class StatModifier:
|
|||
else:
|
||||
packet.encode_short(self._stats.sp)
|
||||
else:
|
||||
getattr(packet, f"encode_{modifier.encode}")(packet, getattr(self._stats, modifier.name.lower()))
|
||||
getattr(packet, f"encode_{modifier.encode}")(
|
||||
packet, getattr(self._stats, modifier.name.lower())
|
||||
)
|
||||
|
||||
|
||||
class CharacterModifiers:
|
||||
def __init__(self, character):
|
||||
self._parent = character
|
||||
|
||||
|
||||
async def stats(self, *, excl_req=True, **stats):
|
||||
modifier = StatModifier(self._parent.stats)
|
||||
modifier.alter(**stats)
|
|
@ -0,0 +1,24 @@
|
|||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
from mapy.common import WildcardData
|
||||
from mapy.common.constants import PERMANENT
|
||||
|
||||
|
||||
def default_level_data():
|
||||
return []
|
||||
|
||||
|
||||
@dataclass
|
||||
class SkillEntry(WildcardData):
|
||||
id: int = 0
|
||||
level: int = 0
|
||||
mastery_level: int = 0
|
||||
max_level: int = 0
|
||||
expiration: int = field(default=PERMANENT)
|
||||
level_data: List = field(default_factory=default_level_data)
|
||||
|
||||
def encode(self, packet):
|
||||
packet.encode_int(self.id)
|
||||
packet.encode_int(self.level)
|
||||
packet.encode_long(PERMANENT) # skill.expiration
|
|
@ -1,3 +1,3 @@
|
|||
from .client_base import ClientBase
|
||||
from .wvs_game_client import WvsGameClient
|
||||
from .wvs_login_client import WvsLoginClient
|
||||
from .wvs_login_client import WvsLoginClient
|
|
@ -0,0 +1,137 @@
|
|||
from mapy.net.packet import Packet
|
||||
from mapy.net.crypto import (
|
||||
MapleIV,
|
||||
decrypt_transform,
|
||||
encrypt_transform,
|
||||
MapleAes,
|
||||
)
|
||||
from mapy.common.constants import VERSION, SUB_VERSION, LOCALE
|
||||
from random import randint
|
||||
|
||||
from asyncio import get_event_loop, Lock
|
||||
from mapy import logger as log
|
||||
|
||||
|
||||
class ClientSocket:
|
||||
def __init__(self, socket):
|
||||
self._loop = get_event_loop()
|
||||
self._socket = socket
|
||||
self._lock = Lock()
|
||||
self.recieve_size = 16384
|
||||
self.m_riv = None
|
||||
self.m_siv = None
|
||||
self._is_alive = False
|
||||
self._overflow = None
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
return self._socket.getpeername()
|
||||
|
||||
def close(self):
|
||||
return self._socket.close()
|
||||
|
||||
async def receive(self, client):
|
||||
self._is_alive = True
|
||||
while self._is_alive:
|
||||
if not self._overflow:
|
||||
m_recv_buffer = await self._loop.sock_recv(
|
||||
self._socket, self.recieve_size
|
||||
)
|
||||
if not m_recv_buffer:
|
||||
await client._parent.on_client_disconnect(client)
|
||||
return
|
||||
|
||||
else:
|
||||
m_recv_buffer = self._overflow
|
||||
self._overflow = None
|
||||
|
||||
if self.m_riv:
|
||||
async with self._lock:
|
||||
length = MapleAes.get_length(m_recv_buffer)
|
||||
if length != len(m_recv_buffer) - 4:
|
||||
self._overflow = m_recv_buffer[length + 4 :]
|
||||
m_recv_buffer = m_recv_buffer[: length + 4]
|
||||
|
||||
m_recv_buffer = self.manipulate_buffer(m_recv_buffer)
|
||||
|
||||
client.dispatch(Packet(m_recv_buffer))
|
||||
|
||||
async def send_packet(self, out_packet):
|
||||
packet_length = len(out_packet)
|
||||
packet = bytearray(out_packet.getvalue())
|
||||
|
||||
buf = packet[:]
|
||||
|
||||
final_length = packet_length + 4
|
||||
final = bytearray(final_length)
|
||||
async with self._lock:
|
||||
MapleAes.get_header(final, self.m_siv, packet_length, VERSION)
|
||||
buf = encrypt_transform(buf)
|
||||
final[4:] = MapleAes.transform(buf, self.m_siv)
|
||||
|
||||
await self._loop.sock_sendall(self._socket, final)
|
||||
|
||||
async def send_packet_raw(self, packet):
|
||||
await self._loop.sock_sendall(self._socket, packet.getvalue())
|
||||
|
||||
def manipulate_buffer(self, buffer):
|
||||
buf = bytearray(buffer)[4:]
|
||||
|
||||
buf = MapleAes.transform(buf, self.m_riv)
|
||||
buf = decrypt_transform(buf)
|
||||
|
||||
return buf
|
||||
|
||||
|
||||
class ClientBase:
|
||||
def __init__(self, parent, socket):
|
||||
self.m_socket = socket
|
||||
self._parent = parent
|
||||
self.port = None
|
||||
self._is_alive = False
|
||||
|
||||
self.logged_in = False
|
||||
self.world_id = None
|
||||
self.channel_id = None
|
||||
|
||||
async def initialize(self):
|
||||
self.m_socket.m_siv = MapleIV(randint(0, 2 ** 31 - 1))
|
||||
# self.m_socket.m_siv = MapleIV(100)
|
||||
self.m_socket.m_riv = MapleIV(randint(0, 2 ** 31 - 1))
|
||||
# self.m_socket.m_riv = MapleIV(50)
|
||||
|
||||
packet = Packet(op_code=0x0E)
|
||||
packet.encode_short(VERSION)
|
||||
packet.encode_string(SUB_VERSION)
|
||||
packet.encode_int(self.m_socket.m_riv.value)
|
||||
packet.encode_int(self.m_socket.m_siv.value)
|
||||
packet.encode_byte(LOCALE)
|
||||
|
||||
await self.send_packet_raw(packet)
|
||||
|
||||
await self.m_socket.receive(self)
|
||||
|
||||
def dispatch(self, packet):
|
||||
self._parent.dispatcher.push(self, packet)
|
||||
|
||||
async def send_packet(self, packet):
|
||||
log.packet(f"{packet.name} {self.ip} {packet.to_string()}", "out")
|
||||
|
||||
await self.m_socket.send_packet(packet)
|
||||
|
||||
async def send_packet_raw(self, packet):
|
||||
log.packet(f"{packet.name} {self.ip} {packet.to_string()}", "out")
|
||||
|
||||
await self.m_socket.send_packet_raw(packet)
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self._parent
|
||||
|
||||
@property
|
||||
def ip(self):
|
||||
return self.m_socket.identifier[0]
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._parent.data
|
|
@ -14,4 +14,4 @@ class WvsGameClient(ClientBase):
|
|||
await self.character.field.broadcast(packet, self)
|
||||
|
||||
def get_field(self):
|
||||
return self._parent.get_field(self.character.field_id)
|
||||
return self._parent.get_field(self.character.field_id)
|
|
@ -22,10 +22,9 @@ class WvsLoginClient(ClientBase):
|
|||
self.avatars = []
|
||||
|
||||
async def login(self, username, password):
|
||||
resp, account = await (self.data
|
||||
.account(username=username,
|
||||
password=password)
|
||||
.login())
|
||||
resp, account = await (
|
||||
self.data.account(username=username, password=password).login()
|
||||
)
|
||||
|
||||
if not resp:
|
||||
self.account = account
|
||||
|
@ -35,10 +34,10 @@ class WvsLoginClient(ClientBase):
|
|||
return resp
|
||||
|
||||
async def load_avatars(self, world_id=None):
|
||||
self.avatars = await (self.data
|
||||
.account(id=self.account.id)
|
||||
.get_entries(world_id=world_id))
|
||||
self.avatars = await (
|
||||
self.data.account(id=self.account.id).get_entries(world_id=world_id)
|
||||
)
|
||||
|
||||
@property
|
||||
def account_id(self):
|
||||
return getattr(self.account, 'id', -1)
|
||||
return getattr(self.account, "id", -1)
|
|
@ -0,0 +1,11 @@
|
|||
__all__ = (
|
||||
"WildcardData",
|
||||
"InventoryType",
|
||||
"Inventory",
|
||||
"StatModifiers",
|
||||
"WorldFlag",
|
||||
"Worlds",
|
||||
)
|
||||
|
||||
from .abc import WildcardData, Inventory
|
||||
from .enum import InventoryType, StatModifiers, WorldFlag, Worlds
|
|
@ -1,22 +1,23 @@
|
|||
from abc import ABCMeta
|
||||
from copy import deepcopy
|
||||
from random import randint
|
||||
|
||||
|
||||
class Serializable(metaclass=ABCMeta):
|
||||
def __serialize__(self):
|
||||
serialized = {}
|
||||
for key, value in self.__dict__.items():
|
||||
if isinstance(value, Serializable):
|
||||
if issubclass(value.__class__, Serializable):
|
||||
value = value.__serialize__()
|
||||
|
||||
serialized[key] = value
|
||||
|
||||
return serialized
|
||||
|
||||
|
||||
class WildcardData:
|
||||
@classmethod
|
||||
def __new__(cls, *args, **kwargs):
|
||||
old_init = cls.__init__
|
||||
|
||||
def _new_init_(self, *args, **kwargs):
|
||||
cleaned = {}
|
||||
for key, value in kwargs.items():
|
||||
|
@ -24,12 +25,13 @@ class WildcardData:
|
|||
continue
|
||||
cleaned[key] = value
|
||||
old_init(self, *args, **cleaned)
|
||||
|
||||
|
||||
cls.__init__ = _new_init_
|
||||
return super(WildcardData, cls).__new__(cls)
|
||||
|
||||
|
||||
class Inventory:
|
||||
pass
|
||||
...
|
||||
# def add(self, item, slot=None):
|
||||
# if isinstance(item, ItemSlotEquip):
|
||||
# free_slot = self.get_free_slot() if not slot else slot
|
||||
|
@ -46,4 +48,4 @@ class Inventory:
|
|||
# # or insert into free slot
|
||||
# pass
|
||||
|
||||
# return items
|
||||
# return items
|
|
@ -45,27 +45,73 @@ DISABLE_CHARACTER_CREATION = False
|
|||
PERMANENT = 150841440000000000
|
||||
|
||||
ANTIREPEAT_BUFFS = [
|
||||
11101004, 5221000, 11001001, 5211007,
|
||||
5121000, 5121007, 5111007, 4341000,
|
||||
5111007, 4121000, 4201003, 2121000,
|
||||
1221000, 1201006, 1211008, 1211009,
|
||||
1211010, 1121000, 1001003, 1101006,
|
||||
1111007, 2101001, 2101003, 1321000,
|
||||
1311007, 1311006
|
||||
11101004,
|
||||
5221000,
|
||||
11001001,
|
||||
5211007,
|
||||
5121000,
|
||||
5121007,
|
||||
5111007,
|
||||
4341000,
|
||||
5111007,
|
||||
4121000,
|
||||
4201003,
|
||||
2121000,
|
||||
1221000,
|
||||
1201006,
|
||||
1211008,
|
||||
1211009,
|
||||
1211010,
|
||||
1121000,
|
||||
1001003,
|
||||
1101006,
|
||||
1111007,
|
||||
2101001,
|
||||
2101003,
|
||||
1321000,
|
||||
1311007,
|
||||
1311006,
|
||||
]
|
||||
|
||||
EVENT_VEHICLE_SKILLS = [
|
||||
1025, 1027, 1028, 1029, 1030, 1031, 1033,
|
||||
1034, 1035, 1036, 1037, 1038, 1039, 1040,
|
||||
1042, 1044, 1049, 1050, 1051, 1052, 1053,
|
||||
1054, 1063, 1064, 1065, 1069, 1070, 1071
|
||||
1025,
|
||||
1027,
|
||||
1028,
|
||||
1029,
|
||||
1030,
|
||||
1031,
|
||||
1033,
|
||||
1034,
|
||||
1035,
|
||||
1036,
|
||||
1037,
|
||||
1038,
|
||||
1039,
|
||||
1040,
|
||||
1042,
|
||||
1044,
|
||||
1049,
|
||||
1050,
|
||||
1051,
|
||||
1052,
|
||||
1053,
|
||||
1054,
|
||||
1063,
|
||||
1064,
|
||||
1065,
|
||||
1069,
|
||||
1070,
|
||||
1071,
|
||||
]
|
||||
|
||||
|
||||
def is_event_vehicle_skill(skill_id):
|
||||
return skill_id % 10000 in EVENT_VEHICLE_SKILLS
|
||||
|
||||
|
||||
def get_job_from_creation(job_id):
|
||||
return {0: 3000, 1: 0, 2: 1000, 3: 2000, 4: 2001}.get(job_id, 0)
|
||||
|
||||
|
||||
def is_extend_sp_job(job_id):
|
||||
return job_id / 1000 == 3 or job_id / 100 == 22 or job_id == 2001
|
||||
return job_id / 1000 == 3 or job_id / 100 == 22 or job_id == 2001
|
|
@ -0,0 +1,76 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
class Worlds(Enum):
|
||||
Scania = 0
|
||||
Broa = 1
|
||||
Windia = 2
|
||||
Khaini = 3
|
||||
Bellocan = 4
|
||||
Mardia = 5
|
||||
Kradia = 6
|
||||
Yellonde = 7
|
||||
Galacia = 8
|
||||
El_Nido = 9
|
||||
Zenith = 11
|
||||
Arcenia = 12
|
||||
Judis = 13
|
||||
Plana = 14
|
||||
Kastia = 15
|
||||
Kalluna = 16
|
||||
Stius = 17
|
||||
Croa = 18
|
||||
Medere = 19
|
||||
|
||||
|
||||
class WorldFlag(Enum):
|
||||
Null = 0x00
|
||||
Event = 0x01
|
||||
New = 0x02
|
||||
Hot = 0x03
|
||||
|
||||
|
||||
class InventoryType(Enum):
|
||||
TRACKER = 0x0
|
||||
EQUIP = 0x1
|
||||
CONSUME = 0x2
|
||||
INSTALL = 0x3
|
||||
ETC = 0x4
|
||||
CASH = 0x5
|
||||
|
||||
|
||||
class StatModifiers(int, Enum):
|
||||
def __new__(cls, value, encode_type):
|
||||
obj = int.__new__(cls, value)
|
||||
obj._value_ = value
|
||||
obj.encode = encode_type
|
||||
return obj
|
||||
|
||||
SKIN = (0x1, "byte")
|
||||
FACE = (0x2, "int")
|
||||
HAIR = (0x4, "int")
|
||||
|
||||
PET = (0x8, "long")
|
||||
PET2 = (0x80000, "long")
|
||||
PET3 = (0x100000, "long")
|
||||
|
||||
LEVEL = (0x10, "byte")
|
||||
JOB = (0x20, "short")
|
||||
STR = (0x40, "short")
|
||||
DEX = (0x80, "short")
|
||||
INT = (0x100, "short")
|
||||
LUK = (0x200, "short")
|
||||
|
||||
HP = (0x400, "int")
|
||||
MAX_HP = (0x800, "int")
|
||||
MP = (0x1000, "int")
|
||||
MAX_MP = (0x2000, "int")
|
||||
|
||||
AP = (0x4000, "short")
|
||||
SP = (0x8000, "short")
|
||||
|
||||
EXP = (0x10000, "int")
|
||||
POP = (0x20000, "short")
|
||||
|
||||
MONEY = (0x40000, "int")
|
||||
# TEMP_EXP = 0x200000
|
|
@ -0,0 +1 @@
|
|||
from .db_client import DatabaseClient, Account
|
|
@ -1,21 +1,30 @@
|
|||
import json
|
||||
from asyncio import get_event_loop
|
||||
from copy import deepcopy
|
||||
from datetime import date
|
||||
from typing import Union
|
||||
|
||||
from asyncpg import PostgresError, create_pool
|
||||
from asyncpg.exceptions import InterfaceError
|
||||
from character import Account as Account_
|
||||
from character import Character, CharacterEntry, FuncKey, SkillEntry
|
||||
from field import Field as field
|
||||
from field import Foothold, Mob, Npc, Portal
|
||||
from game import ItemSlotEquip, SkillLevelData
|
||||
from game import item as Item # ItemSlotBundle,
|
||||
from utils import get, log
|
||||
from mapy import log
|
||||
from mapy.character import Account as Account_
|
||||
from mapy.character import Character, CharacterEntry, FuncKey, SkillEntry
|
||||
from mapy.field import Field as field
|
||||
from mapy.field import Foothold, Mob, Npc, Portal
|
||||
from mapy.game import ItemSlotEquip, SkillLevelData
|
||||
from mapy.game import item as Item # ItemSlotBundle,
|
||||
from mapy.utils import get
|
||||
|
||||
from .schema import (Column, Insert, IntColumn, ListArguments, Query, Schema,
|
||||
StringColumn, Table, Update)
|
||||
from .schema import (
|
||||
Column,
|
||||
Insert,
|
||||
IntColumn,
|
||||
ListArguments,
|
||||
Query,
|
||||
Schema,
|
||||
StringColumn,
|
||||
Table,
|
||||
Update,
|
||||
)
|
||||
from .structure import RMDB, Maplestory
|
||||
|
||||
|
||||
|
@ -51,13 +60,15 @@ SKILLS = f"{DATABASE}.skills"
|
|||
class DatabaseClient:
|
||||
_name = "Database Client"
|
||||
|
||||
def __init__(self,
|
||||
user="postgres",
|
||||
password="",
|
||||
host="127.0.0.1",
|
||||
port=5432,
|
||||
database="postgres",
|
||||
loop=None):
|
||||
def __init__(
|
||||
self,
|
||||
user="postgres",
|
||||
password="",
|
||||
host="127.0.0.1",
|
||||
port=5432,
|
||||
database="postgres",
|
||||
loop=None,
|
||||
):
|
||||
self._user = user
|
||||
self._pass = password
|
||||
self._host = host
|
||||
|
@ -71,23 +82,24 @@ class DatabaseClient:
|
|||
|
||||
@property
|
||||
def dsn(self):
|
||||
return ("postgres://"
|
||||
f"{self._user}:{self._pass}"
|
||||
f"@{self._host}:{self._port}"
|
||||
f"/{self._database}")
|
||||
return (
|
||||
"postgres://"
|
||||
f"{self._user}:{self._pass}"
|
||||
f"@{self._host}:{self._port}"
|
||||
f"/{self._database}"
|
||||
)
|
||||
|
||||
def log(self, message, level=None):
|
||||
level = level if level else 'info'
|
||||
level = level if level else "info"
|
||||
getattr(log, level)(f"{self._name} {message}")
|
||||
|
||||
async def start(self):
|
||||
# try:
|
||||
self.pool = await create_pool(
|
||||
self.dsn,
|
||||
loop=self._loop,
|
||||
init=init_conn)
|
||||
self.log(f"PostgreSQL Client connected at <lc>{self._host}</lc>:"
|
||||
f"<lr>{self._port}</lr>")
|
||||
self.pool = await create_pool(self.dsn, loop=self._loop, init=init_conn)
|
||||
self.log(
|
||||
f"PostgreSQL Client connected at <lc>{self._host}</lc>:"
|
||||
f"<lr>{self._port}</lr>"
|
||||
)
|
||||
|
||||
# await Maplestory.create()
|
||||
|
||||
|
@ -334,7 +346,8 @@ class Characters(QueryTable):
|
|||
)
|
||||
else:
|
||||
characters = await (
|
||||
self.query().where(account_id=self.account_id, world_id=world_id)
|
||||
self.query()
|
||||
.where(account_id=self.account_id, world_id=world_id)
|
||||
.order_by("id")
|
||||
.get()
|
||||
)
|
||||
|
@ -375,7 +388,9 @@ class Characters(QueryTable):
|
|||
# Get static data from rmdb for character skills
|
||||
for skill in skills:
|
||||
level_data = await self._db.skills.get_skill_level_data(skill["skill_id"])
|
||||
character.skills[skill["skill_id"]] = SkillEntry(level_data=level_data, **skill)
|
||||
character.skills[skill["skill_id"]] = SkillEntry(
|
||||
level_data=level_data, **skill
|
||||
)
|
||||
|
||||
return character
|
||||
|
||||
|
@ -525,8 +540,7 @@ class Inventories:
|
|||
equips.append(equip_data)
|
||||
|
||||
q = (
|
||||
await self.items_table
|
||||
.insert.row(items)
|
||||
await self.items_table.insert.row(items)
|
||||
.primaries("inventory_item_id")
|
||||
.returning("inventory_items.inventory_item_id, inventory_items.position")
|
||||
.commit(do_update=True)
|
|
@ -2,12 +2,12 @@ from asyncpg import PostgresError
|
|||
|
||||
|
||||
class SchemaError(PostgresError):
|
||||
pass
|
||||
...
|
||||
|
||||
|
||||
class ResponseError(PostgresError):
|
||||
pass
|
||||
...
|
||||
|
||||
|
||||
class QueryError(PostgresError):
|
||||
pass
|
||||
...
|
|
@ -20,10 +20,7 @@ def default_tmpl(c, o, v):
|
|||
|
||||
|
||||
class SQLOperator:
|
||||
def __init__(self,
|
||||
sql_oper: str,
|
||||
py_oper: str,
|
||||
str_tmpl: str = None):
|
||||
def __init__(self, sql_oper: str, py_oper: str, str_tmpl: str = None):
|
||||
self.__sql_oper = sql_oper
|
||||
self.__py_oper = py_oper
|
||||
self.__str_tmpl = default_tmpl if not str_tmpl else str_tmpl
|
||||
|
@ -82,7 +79,9 @@ class SQLOperator:
|
|||
|
||||
@classmethod
|
||||
def between(cls):
|
||||
return cls("BETWEEN", None, lambda c, o, mn_v, mx_v: f"{c} {o} {mn_v} AND {mx_v}")
|
||||
return cls(
|
||||
"BETWEEN", None, lambda c, o, mn_v, mx_v: f"{c} {o} {mn_v} AND {mx_v}"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def in_(cls):
|
||||
|
@ -260,10 +259,12 @@ class Column:
|
|||
|
||||
return SQLComparison(SQLOperator.in_(), self.aggregate, self.full_name, value)
|
||||
|
||||
def contains(self, value): # *values
|
||||
def contains(self, value): # *values
|
||||
# if (isinstance(v, Column) == True for v in values):
|
||||
if isinstance(value, Column) or isinstance(value, (int, str, bool)):
|
||||
return SQLComparison(SQLOperator.contains(), self.aggregate, self.full_name, value)
|
||||
return SQLComparison(
|
||||
SQLOperator.contains(), self.aggregate, self.full_name, value
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data):
|
||||
|
@ -282,7 +283,7 @@ class Column:
|
|||
elif isinstance(self.default, bool):
|
||||
default = str(self.default).upper()
|
||||
elif isinstance(self.data_type, ArraySQL):
|
||||
default = f"{default}::{self.data_type.to_sql()}"
|
||||
default = f"{self.default}::{self.data_type.to_sql()}"
|
||||
else:
|
||||
default = f"{self.default}"
|
||||
sql.append(f"DEFAULT {default}")
|
||||
|
@ -932,9 +933,7 @@ class Query:
|
|||
)
|
||||
|
||||
elif self._inner_join:
|
||||
sql.append(
|
||||
(f"INNER JOIN {self._inner_join} " f"USING({self._using}) ")
|
||||
)
|
||||
sql.append((f"INNER JOIN {self._inner_join} " f"USING({self._using}) "))
|
||||
|
||||
if self.conditions._queued_conditions:
|
||||
self.conditions.add_conditions()
|
|
@ -0,0 +1,619 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
class Meta(Enum):
|
||||
def __str__(self):
|
||||
return f"{self.__class__.__name__.lower()}.{super().__str__()}"
|
||||
|
||||
|
||||
class Schema(Meta):
|
||||
def __new__(cls, value):
|
||||
value.schema = cls.__name__.lower()
|
||||
value.primary_key = value.__dict__.get("__primary_key__")
|
||||
value.foreign_keys = value.__dict__.get("__foreign_keys__")
|
||||
value.columns = [item.lower() for item in value._member_names_]
|
||||
return value
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.__class__.__name__.lower()}.{super().__str__()}"
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name not in ("_value_"):
|
||||
return getattr(self.value, name)
|
||||
return super().__getattr__(name)
|
||||
|
||||
@classmethod
|
||||
def create(cls):
|
||||
# async def create(db):
|
||||
# schema = db.schema(cls.__name__.lower())
|
||||
# await schema.create(skip_if_exists=False)
|
||||
# fks = []
|
||||
# for table in cls:
|
||||
# t = db.table(table._name_.lower(), schema=schema)
|
||||
# if await t.exists(): continue
|
||||
# cols = []
|
||||
# for c in table:
|
||||
# primary_key = c.getattr('primary_key', False)
|
||||
# col = Column(c._name_.lower(), c.data_type, primary_key=primary_key, **c.options)
|
||||
# if t.foreign_keys and c.name in getattr(table, 'foreign_keys', []):
|
||||
# fks.append(types.ForeignKey(t, col, sql_type=c.data_type))
|
||||
# cols.append(col)
|
||||
# await t.create(*cols)
|
||||
# for fkey in fks:
|
||||
# await db.execute_transaction(fk.to_sql())
|
||||
|
||||
# return create
|
||||
pass
|
||||
|
||||
|
||||
class Table(int, Meta):
|
||||
_ignore_ = "data_type"
|
||||
|
||||
def __new__(cls, data_type, options=None):
|
||||
value = len(cls.__members__) + 1
|
||||
enum_class = super().__new__(cls, value)
|
||||
|
||||
enum_class._value_ = value
|
||||
enum_class.data_type = data_type
|
||||
|
||||
# if data_type in ['integer', 'serial']:
|
||||
# enum_class.data_type = types.Integer(auto_increment=data_type == 'serial')
|
||||
# elif data_type == 'smallint':
|
||||
# enum_class.data_type = types.Integer(small=True)
|
||||
# elif data_type == 'bigint':
|
||||
# enum_class.data_type = types.Integer(big=True)
|
||||
# elif data_type in ['varchar', 'text']:
|
||||
# enum_class.data_type = types.String()
|
||||
# elif data_type == 'jsonb':
|
||||
# enum_class.data_type = types.JSON()
|
||||
# elif data_type == 'double':
|
||||
# enum_class.data_type = types.Double()
|
||||
# elif data_type == 'boolean':
|
||||
# enum_class.data_type = types.Boolean()
|
||||
# elif data_type == 'date':
|
||||
# enum_class.data_type = types.Date()
|
||||
|
||||
# from re import search
|
||||
# if search(r'(\[\])', data_type):
|
||||
# enum_class.data_type = types.ArraySQL(enum_class.data_type)
|
||||
|
||||
enum_class.options = options if options else {}
|
||||
return enum_class
|
||||
|
||||
|
||||
class ItemConsumeableData(Table):
|
||||
ITEM_ID = "integer"
|
||||
FLAGS = "varchar[]"
|
||||
CURES = "varchar[]"
|
||||
HP = "smallint"
|
||||
HP_PERCENTAGE = "smallint"
|
||||
MAX_HP_PERCENTAGE = "smallint"
|
||||
MP = "smallint"
|
||||
MP_PERCENTAGE = "smallint"
|
||||
MAX_MP_PERCENTAGE = "smallint"
|
||||
WEAPON_ATTACK = "smallint"
|
||||
WEAPON_ATTACK_PERCENTAGE = "smallint"
|
||||
WEAPON_DEFENSE = "smallint"
|
||||
WEAPON_DEFENSE_PERCENTAGE = "smallint"
|
||||
MAGIC_ATTACK = "smallint"
|
||||
MAGIC_ATTACK_PERCENTAGE = "smallint"
|
||||
MAGIC_DEFENSE = "smallint"
|
||||
MAGIC_DEFENSE_PERCENTAGE = "smallint"
|
||||
ACCURACY = "smallint"
|
||||
ACCURACY_PERCENTAGE = "smallint"
|
||||
AVOID = "smallint"
|
||||
AVOID_PERCENTAGE = "smallint"
|
||||
SPEED = "smallint"
|
||||
SPEED_PERCENTAGE = "smallint"
|
||||
JUMP = "smallint"
|
||||
SECONDARY_STAT = "jsonb"
|
||||
BUFF_TIME = "integer"
|
||||
PROB = "smallint"
|
||||
EVENT_POINT = "integer"
|
||||
MOB_ID = "integer"
|
||||
MOB_HP = "integer"
|
||||
SCREEN_MESSAGE = "text"
|
||||
ATTACK_INDEX = "smallint"
|
||||
ATTACK_MOB_ID = "integer"
|
||||
MOVE_TO = "integer"
|
||||
RETURN_MAP_QR = "integer"
|
||||
DECREASE_HUNGER = "smallint"
|
||||
MORPH = "smallint"
|
||||
CARNIVAL_TYPE = "smallint"
|
||||
CARNIVAL_POINTS = "smallint"
|
||||
CARNIVAL_SKILL = "smallint"
|
||||
EXPERIENCE = "integer"
|
||||
|
||||
__primary_key__ = ("ITEM_ID",)
|
||||
|
||||
|
||||
class ItemData(Table):
|
||||
ITEM_ID = "integer"
|
||||
INVENTORY = "smallint"
|
||||
PRICE = ("integer", {"default": 0})
|
||||
MAX_SLOT_QUANTITY = "smallint"
|
||||
MAX_POSSESSION_COUNT = "smallint"
|
||||
MIN_LEVEL = "smallint"
|
||||
MAX_LEVEL = ("smallint", {"default": 250})
|
||||
EXPERIENCE = "integer"
|
||||
MONEY = "integer"
|
||||
STATE_CHANGE_ITEM = "integer"
|
||||
LEVEL_FOR_MAKER = "smallint"
|
||||
NPC = "integer"
|
||||
FLAGS = ("varchar[]", {"default": "'{}'"})
|
||||
PET_LIFE_EXTEND = "smallint"
|
||||
MAPLE_POINT = "integer"
|
||||
MONEY_MIN = "integer"
|
||||
MONEY_MAX = "integer"
|
||||
EXP_RATE = "double"
|
||||
ADD_TIME = "smallint"
|
||||
SLOT_INDEX = "smallint"
|
||||
|
||||
__primary_key__ = ("ITEM_ID",)
|
||||
|
||||
|
||||
class ItemEquipData(Table):
|
||||
ITEM_ID = "integer"
|
||||
FLAGS = ("varchar[]", {"default": "'{}'"})
|
||||
EQUIP_SLOTS = ("varchar[]", {"default": "'{}'"})
|
||||
ATTACK_SPEED = ("smallint", {"default": 0})
|
||||
RUC = ("smallint", {"default": 0})
|
||||
REQ_STR = ("smallint", {"default": 0})
|
||||
REQ_DEX = ("smallint", {"default": 0})
|
||||
REQ_INT = ("smallint", {"default": 0})
|
||||
REQ_LUK = ("smallint", {"default": 0})
|
||||
REQ_FAME = ("smallint", {"default": 0})
|
||||
REQ_JOB = ("smallint[]", {"default": "'{}'"})
|
||||
HP = ("smallint", {"default": 0})
|
||||
HP_PERCENTAGE = ("smallint", {"default": 0})
|
||||
MP = ("smallint", {"default": 0})
|
||||
MP_PERCENTAGE = ("smallint", {"default": 0})
|
||||
STR = ("smallint", {"default": 0})
|
||||
DEX = ("smallint", {"default": 0})
|
||||
INT = ("smallint", {"default": 0})
|
||||
LUK = ("smallint", {"default": 0})
|
||||
HANDS = ("smallint", {"default": 0})
|
||||
WEAPON_ATTACK = ("smallint", {"default": 0})
|
||||
WEAPON_DEFENSE = ("smallint", {"default": 0})
|
||||
MAGIC_ATTACK = ("smallint", {"default": 0})
|
||||
MAGIC_DEFENSE = ("smallint", {"default": 0})
|
||||
ACCURACY = ("smallint", {"default": 0})
|
||||
AVOID = ("smallint", {"default": 0})
|
||||
JUMP = ("smallint", {"default": 0})
|
||||
SPEED = ("smallint", {"default": 0})
|
||||
TRACTION = ("double", {"default": 0})
|
||||
RECOVERY = ("double", {"default": 0})
|
||||
KNOCKBACK = ("smallint", {"default": 0})
|
||||
TAMING_MOB = ("smallint", {"default": 0})
|
||||
DURABILITY = ("integer", {"default": "'-1'::integer"})
|
||||
INC_LIGHTNING_DAMAGE = ("smallint", {"default": 0})
|
||||
INC_ICE_DAMAGE = ("smallint", {"default": 0})
|
||||
INC_FIRE_DAMAGE = ("smallint", {"default": 0})
|
||||
INC_POISON_DAMAGE = ("smallint", {"default": 0})
|
||||
ELEMENTAL_DEFAULT = ("smallint", {"default": 0})
|
||||
CRAFT = ("smallint", {"default": 0})
|
||||
SET_ID = ("integer", {"default": 0})
|
||||
ENCHANT_CATEGORY = ("smallint", {"default": 0})
|
||||
HEAL_HP = ("smallint", {"default": 0})
|
||||
SPECIAL_ID = ("integer", {"default": 0})
|
||||
|
||||
__primary_key__ = "ITEM_ID"
|
||||
|
||||
|
||||
class ItemPetData(Table):
|
||||
ITEM_ID = "integer"
|
||||
HUNGER = "smallint"
|
||||
LIFE = "smallint"
|
||||
LIMITED_LIFE = "integer"
|
||||
EVOLUTION_ITEM = "integer"
|
||||
EVOLUTION_LEVEL = "smallint"
|
||||
FLAGS = "varchar[]"
|
||||
|
||||
__primary_key__ = ("ITEM_ID",)
|
||||
|
||||
|
||||
class ItemRechargeableData(Table):
|
||||
ITEM_ID = "integer"
|
||||
UNIT_PRICE = "smallint"
|
||||
WEAPON_ATTACK = "smallint"
|
||||
|
||||
__primary_key__ = "ITEM_ID"
|
||||
|
||||
|
||||
class MapData(Table):
|
||||
MAP_ID = "integer"
|
||||
MAP_NAME = ("text", {"default": "''::text"})
|
||||
STREET_NAME = ("text", {"default": "''::text"})
|
||||
MAP_MARK = ("text", {"default": "''::text"})
|
||||
FLAGS = ("varchar[]", {"default": "'{}'"})
|
||||
MOB_RATE = ("double", {"default": 1.0})
|
||||
FIXED_MOB_CAPACITY = "smallint"
|
||||
SPAWN_MOB_INTERVAL = "smallint"
|
||||
DROP_RATE = ("double", {"default": 1.0})
|
||||
REGEN_RATE = ("double", {"default": 1.0})
|
||||
SHUFFLE_NAME = ("text", {"default": "NULL::varchar"})
|
||||
DEFAULT_BGM = ("text", {"default": "NULL::varchar"})
|
||||
EFFECT = ("text", {"default": "NULL::varchar"})
|
||||
MIN_LEVEL_LIMIT = "smallint"
|
||||
MAX_LEVEL_LIMIT = "smallint"
|
||||
TIME_LIMIT = "smallint"
|
||||
DEFAULT_TRACTION = ("double", {"default": 1})
|
||||
MAP_LTX = "smallint"
|
||||
MAP_LTY = "smallint"
|
||||
MAP_RBX = "smallint"
|
||||
MAP_RBY = "smallint"
|
||||
LP_BOTTOM = "smallint"
|
||||
LP_TOP = "smallint"
|
||||
LP_SIDE = "smallint"
|
||||
FORCED_MAP_RETURN = "integer"
|
||||
FIELD_TYPE = "smallint"
|
||||
DECREASE_HP = "integer"
|
||||
DECREASE_MP = "integer"
|
||||
DECREASE_INTERVAL = "smallint"
|
||||
DAMAGE_PER_SECOND = "smallint"
|
||||
PROTECT_ITEM = "integer"
|
||||
SHIP_KIND = "smallint"
|
||||
CONSUME_COOLDOWN = "smallint"
|
||||
LINK = "integer"
|
||||
FIELD_LIMITATIONS = ("bigint", {"default": 0})
|
||||
RETURN_MAP = "integer"
|
||||
MAP_LIMIT = "smallint"
|
||||
MAP_VERSION = "smallint"
|
||||
ON_FIRST_USER_ENTER = "text"
|
||||
ON_USER_ENTER = "text"
|
||||
DESCRIPTION = ("text", {"default": "''::text"})
|
||||
MOVE_LIMIT = "smallint"
|
||||
PROTECT_SET = "integer"
|
||||
ALLOWED_ITEM_DROP = "integer"
|
||||
DROP_EXPIRE = "smallint"
|
||||
TIME_OUT_LIMIT = "smallint"
|
||||
PHASE_ALPHA = "smallint"
|
||||
PHASE_BACKGROUND = "smallint"
|
||||
TIME_MOB_SPAWN = "smallint"
|
||||
|
||||
__primary_key__ = "MAP_ID"
|
||||
|
||||
|
||||
class MapFootholds(Table):
|
||||
MAP_ID = "integer"
|
||||
GROUP_ID = "smallint"
|
||||
ID = "smallint"
|
||||
X_1 = "smallint"
|
||||
Y_1 = "smallint"
|
||||
X_2 = "smallint"
|
||||
Y_2 = "smallint"
|
||||
DRAG_FORCE = "smallint"
|
||||
PREVIOUS_ID = "smallint"
|
||||
NEXT_ID = "smallint"
|
||||
FLAGS = "varchar[]"
|
||||
|
||||
__primary_key__ = ("MAP_ID", "GROUP_ID", "ID")
|
||||
|
||||
|
||||
class MapLife(Table):
|
||||
MAP_ID = "integer"
|
||||
LIFE_ID = "integer"
|
||||
LIFE_TYPE = "text"
|
||||
LIFE_NAME = ("text", {"default": "''::text"})
|
||||
X_POS = "smallint"
|
||||
Y_POS = "smallint"
|
||||
FOOTHOLD = "smallint"
|
||||
MIN_CLICK_POS = "smallint"
|
||||
MAX_CLICK_POS = "smallint"
|
||||
RESPAWN_TIME = "integer"
|
||||
FLAGS = ("varchar[]", {"default": "'{}'"})
|
||||
CY = "smallint"
|
||||
FACE = ("boolean", {"default": 0})
|
||||
HIDE = ("boolean", {"default": 0})
|
||||
|
||||
__primary_key__ = None
|
||||
|
||||
|
||||
class MapPortals(Table):
|
||||
MAP_ID = "integer"
|
||||
ID = "smallint"
|
||||
NAME = "text"
|
||||
X_POS = "smallint"
|
||||
Y_POS = "smallint"
|
||||
DESTINATION = "integer"
|
||||
DESTINATION_LABEL = "text"
|
||||
PORTAL_TYPE = "smallint"
|
||||
|
||||
__primary_key__ = ("MAP_ID", "ID")
|
||||
|
||||
|
||||
class MapReactors(Table):
|
||||
MAP_ID = "integer"
|
||||
REACTOR_ID = "integer"
|
||||
REACTOR_TIME = "integer"
|
||||
NAME = "text"
|
||||
X = "smallint"
|
||||
Y = "smallint"
|
||||
F = "smallint"
|
||||
|
||||
__primary_key__ = ("MAP_ID", "REACTOR_ID")
|
||||
|
||||
|
||||
class MapTimedMobs(Table):
|
||||
MAP_ID = "integer"
|
||||
MOB_ID = "integer"
|
||||
START_HOUR = "smallint"
|
||||
END_HOUR = "smallint"
|
||||
MESSAGE = "text"
|
||||
|
||||
__primary_key__ = ("MAP_ID", "MOB_ID")
|
||||
|
||||
|
||||
class MobData(Table):
|
||||
MOB_ID = "integer"
|
||||
MOB_LEVEL = "smallint"
|
||||
FLAGS = ("varchar[]", {"default": "'{}'"})
|
||||
HP = "integer"
|
||||
HP_RECOVERY = "integer"
|
||||
MP = "integer"
|
||||
MP_RECOVERY = "integer"
|
||||
EXPERIENCE = "integer"
|
||||
KNOCKBACK = "integer"
|
||||
FIXED_DAMAGE = "integer"
|
||||
EXPLODE_HP = "integer"
|
||||
LINK = "integer"
|
||||
SUMMON_TYPE = "smallint"
|
||||
DEATH_BUFF = "integer"
|
||||
DEATH_AFTER = "integer"
|
||||
TRACTION = ("double", {"default": 1.0})
|
||||
DAMAGED_BY_SKILL_ONLY = "smallint"
|
||||
DAMAGED_BY_MOB_ONLY = "integer"
|
||||
DROP_ITEM_PERIOD = "smallint"
|
||||
HP_BAR_COLOR = "smallint"
|
||||
HP_BAR_BG_COLOR = "smallint"
|
||||
CARNIVAL_POINTS = "smallint"
|
||||
PHYSICAL_ATTACK = "smallint"
|
||||
PHYSICAL_DEFENSE = "smallint"
|
||||
PHYSICAL_DEFENSE_RATE = "smallint"
|
||||
MAGICAL_ATTACK = "smallint"
|
||||
MAGICAL_DEFENSE = "smallint"
|
||||
MAGICAL_DEFENSE_RATE = "smallint"
|
||||
ACCURACY = "smallint"
|
||||
AVOID = "smallint"
|
||||
SPEED = "smallint"
|
||||
FLY_SPEED = "smallint"
|
||||
CHASE_SPEED = "smallint"
|
||||
ICE_MODIFIER = "smallint"
|
||||
FIRE_MODIFIER = "smallint"
|
||||
POISON_MODIFIER = "smallint"
|
||||
LIGHTNING_MODIFIER = "smallint"
|
||||
HOLY_MODIFIER = "smallint"
|
||||
DARK_MODIFIER = "smallint"
|
||||
NONELEMENTAL_MODIFIER = "smallint"
|
||||
|
||||
__primary_key__ = "MOB_ID"
|
||||
|
||||
|
||||
class MobSkills(Table):
|
||||
MOB_ID = "integer"
|
||||
SKILL_ID = "smallint"
|
||||
LEVEL = "smallint"
|
||||
EFFECT_AFTER = "smallint"
|
||||
|
||||
__primary_key__ = ("MOB_ID", "SKILL_ID")
|
||||
|
||||
|
||||
class PlayerSkillData(Table):
|
||||
SKILL_ID = "integer"
|
||||
FLAGS = "varchar[]"
|
||||
WEAPON = "smallint"
|
||||
SUB_WEAPON = "smallint"
|
||||
MAX_LEVEL = "smallint"
|
||||
BASE_MAX_LEVEL = "smallint"
|
||||
SKILL_TYPE = "varchar[]"
|
||||
ELEMENT = "text"
|
||||
MOB_COUNT = "text"
|
||||
HIT_COUNT = "text"
|
||||
BUFF_TIME = "text"
|
||||
HP_COST = "text"
|
||||
MP_COST = "text"
|
||||
DAMAGE = "text"
|
||||
FIXED_DAMAGE = "text"
|
||||
CRITICAL_DAMAGE = "text"
|
||||
MASTERY = "text"
|
||||
OPTIONAL_ITEM_COST = "smallint"
|
||||
ITEM_COST = "integer"
|
||||
ITEM_COUNT = "smallint"
|
||||
BULLET_COST = "smallint"
|
||||
MONEY_COST = "text"
|
||||
X_PROPERTY = "text"
|
||||
Y_PROPERTY = "text"
|
||||
SPEED = "text"
|
||||
JUMP = "text"
|
||||
STR = "text"
|
||||
WEAPON_ATTACK = "text"
|
||||
WEAPON_DEFENSE = "text"
|
||||
MAGIC_ATTACK = "text"
|
||||
MAGIC_DEFENSE = "text"
|
||||
ACCURACY = "text"
|
||||
AVOID = "text"
|
||||
HP = "text"
|
||||
MP = "text"
|
||||
PROBABILITY = "text"
|
||||
LTX = "smallint"
|
||||
LTY = "smallint"
|
||||
RBX = "smallint"
|
||||
RBY = "smallint"
|
||||
COOLDOWN_TIME = "text"
|
||||
AVOID_CHANCE = "text"
|
||||
RANGE = "text"
|
||||
MORPH = "smallint"
|
||||
|
||||
__primary_key__ = "SKILL_ID"
|
||||
|
||||
|
||||
class PlayerSkillRequirementData(Table):
|
||||
SKILL_ID = "integer"
|
||||
REQ_SKILL_ID = "integer"
|
||||
REQ_LEVEL = "smallint"
|
||||
|
||||
__primary_key__ = ("SKILL_ID", "REQ_SKILL_ID")
|
||||
|
||||
|
||||
class String(Table):
|
||||
OBJECT_ID = "integer"
|
||||
OBJECT_TYPE = "varchar"
|
||||
DESCRIPTION = "text"
|
||||
LABEL = "text"
|
||||
|
||||
__primary_key__ = ("OBJECT_ID", "OBJECT_TYPE")
|
||||
|
||||
|
||||
class RMDB(Schema):
|
||||
ITEM_CONSUMEABLE_DATA = ItemConsumeableData
|
||||
ITEM_DATA = ItemData
|
||||
ITEM_EQUIP_DATA = ItemEquipData
|
||||
ITEM_PET_DATA = ItemPetData
|
||||
ITEM_RECHARGEABLE_DATA = ItemRechargeableData
|
||||
MAP_DATA = MapData
|
||||
MAP_FOOTHOLDS = MapFootholds
|
||||
MAP_LIFE = MapLife
|
||||
MAP_PORTALS = MapPortals
|
||||
MAP_REACTORS = MapReactors
|
||||
MAP_TIMED_MOBS = MapTimedMobs
|
||||
MOB_DATA = MobData
|
||||
MOB_SKILLS = MobSkills
|
||||
PLAYER_SKILL_DATA = PlayerSkillData
|
||||
PLAYER_SKILL_REQS_DATA = PlayerSkillRequirementData
|
||||
STRING = String
|
||||
|
||||
|
||||
class Account(Table):
|
||||
ID = "serial"
|
||||
USERNAME = "text"
|
||||
PASSWORD = "text"
|
||||
SALT = "text"
|
||||
CREATION = ("date", {"default": "CURRENT_DATE"})
|
||||
LAST_LOGIN = ("date", {"default": '"1970-01-01"::date'})
|
||||
LAST_IP = ("inet", {"default": '"0.0.0.0"::inet'})
|
||||
BAN = ("bool", {"default": 0})
|
||||
ADMIN = ("bool", {"default": 0})
|
||||
GENDER = "bool"
|
||||
|
||||
__primary_key__ = ("ID",)
|
||||
# __unqiue_index__ = ("USERNAME")
|
||||
|
||||
|
||||
class Buddies(Table):
|
||||
pass
|
||||
|
||||
|
||||
class Character(Table):
|
||||
ID = "integer"
|
||||
ACCOUNT_ID = "integer"
|
||||
NAME = "text"
|
||||
GENDER = "bool"
|
||||
SKIN = "smallint"
|
||||
FACE = "smallint"
|
||||
HAIR = "smallint"
|
||||
PET_LOCKER = "int[]"
|
||||
LEVEL = "smallint"
|
||||
JOB = "smallint"
|
||||
STR = "smallint"
|
||||
DEX = "smallint"
|
||||
INT = "smallint"
|
||||
LUK = "smallint"
|
||||
HP = "smallint"
|
||||
MAX_HP = "smallint"
|
||||
MP = "smallint"
|
||||
MAX_MP = "smallint"
|
||||
AP = "smallint"
|
||||
SP = "smallint"
|
||||
EXP = "integer"
|
||||
MONEY = "integer"
|
||||
TEMP_EXP = "integer"
|
||||
FIELD_ID = "integer"
|
||||
PORTAL = "text"
|
||||
PLAY_TIME = "integer"
|
||||
SUB_JOB = "smallint"
|
||||
FAME = "smallint"
|
||||
EXTEND_SP = "smallint[]"
|
||||
WORLD_ID = "smallint"
|
||||
|
||||
__primary_key__ = ("ID",)
|
||||
__foreign_keys__ = {"account_id": "accounts.id"}
|
||||
|
||||
|
||||
class InventoryEquipment(Table):
|
||||
INVENTORY_ITEM_ID = "bigint"
|
||||
UPGRADE_SLOTS = "integer"
|
||||
STR = "smallint"
|
||||
DEX = "smallint"
|
||||
INT = "smallint"
|
||||
LUK = "smallint"
|
||||
HP = "integer"
|
||||
MP = "integer"
|
||||
WEAPON_ATTACK = "smallint"
|
||||
MAGIC_ATTACK = "smallint"
|
||||
WEAPON_DEFENSE = "smallint"
|
||||
MAGIC_DEFENSE = "smallint"
|
||||
ACCURACY = "smallint"
|
||||
AVOID = "smallint"
|
||||
HANDS = "smallint"
|
||||
SPEED = "smallint"
|
||||
JUMP = "smallint"
|
||||
RING_ID = "integer"
|
||||
CRAFT = "smallint"
|
||||
ATTRIBUTE = "smallint"
|
||||
LEVEL_UP_TYPE = "smallint"
|
||||
LEVEL = "smallint"
|
||||
IUC = "integer"
|
||||
GRADE = "smallint"
|
||||
EXP = "smallint"
|
||||
CHUC = "smallint"
|
||||
OPTION_1 = "smallint"
|
||||
OPTION_2 = "smallint"
|
||||
OPTION_3 = "smallint"
|
||||
SOCKET_1 = "smallint"
|
||||
SOCKET_2 = "smallint"
|
||||
LISN = "bigint"
|
||||
CUC = "smallint"
|
||||
RUC = "smallint"
|
||||
|
||||
__primary_key__ = "inventory_item_id"
|
||||
__foreign_keys__ = {"INVENTORY_ITEM_ID": "inventory_items.inventory_item_id"}
|
||||
|
||||
|
||||
class InventoryItems(Table):
|
||||
INVENTORY_ITEM_ID = "bigint"
|
||||
CHARACTER_ID = "integer"
|
||||
STORAGE_ID = "integer"
|
||||
ITEM_ID = "integer"
|
||||
INVENTORY_TYPE = "smallint"
|
||||
POSITION = "integer"
|
||||
OWNER = "text"
|
||||
PET_ID = "bigint"
|
||||
QUANTITY = "integer"
|
||||
CISN = "bigint" # Current inventory Serial
|
||||
SN = "bigint" # Serial Number (Would replace inventory_item_id?)
|
||||
EXPIRE = "bigint"
|
||||
|
||||
__primary_key__ = "inventory_item_id"
|
||||
|
||||
|
||||
# class InventoryPets(Table):
|
||||
# pass
|
||||
|
||||
|
||||
# class Keymap(Table):
|
||||
# pass
|
||||
|
||||
|
||||
# class Skills(Table):
|
||||
# pass
|
||||
|
||||
|
||||
class Maplestory(Schema):
|
||||
ACCOUNTS = Account
|
||||
BUDDIES = Buddies
|
||||
CHARACTERS = Character
|
||||
INVENTORY_EQUIPMENT = InventoryEquipment
|
||||
INVENTORY_ITEMS = InventoryItems
|
||||
# INVENTORY_PETS = InventoryPets
|
||||
# KEYMAP = Keymap
|
||||
# SKILLS = Skills
|
|
@ -12,13 +12,13 @@ class SQLType:
|
|||
def to_dict(self):
|
||||
dct = self.__dict__.copy()
|
||||
clas = self.__class__
|
||||
dct['__meta__'] = clas.__module__ + '.' + clas.__qualname__
|
||||
dct["__meta__"] = clas.__module__ + "." + clas.__qualname__
|
||||
return dct
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data):
|
||||
meta = data.pop('__meta__')
|
||||
given = cls.__module__ + '.' + cls.__qualname__
|
||||
meta = data.pop("__meta__")
|
||||
given = cls.__module__ + "." + cls.__qualname__
|
||||
|
||||
if given != meta:
|
||||
cls = pydoc.locate(meta)
|
||||
|
@ -30,8 +30,7 @@ class SQLType:
|
|||
return self
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, self.__class__)\
|
||||
and self.__dict__ == other.__dict__
|
||||
return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
@ -47,14 +46,14 @@ class Boolean(SQLType):
|
|||
python = bool
|
||||
|
||||
def to_sql(self):
|
||||
return 'BOOLEAN'
|
||||
return "BOOLEAN"
|
||||
|
||||
|
||||
class Date(SQLType):
|
||||
python = datetime.date
|
||||
|
||||
def to_sql(self):
|
||||
return 'DATE'
|
||||
return "DATE"
|
||||
|
||||
|
||||
class Datetime(SQLType):
|
||||
|
@ -65,16 +64,16 @@ class Datetime(SQLType):
|
|||
|
||||
def to_sql(self):
|
||||
if self.timezone:
|
||||
return 'TIMESTAMP WITH TIMEZONE'
|
||||
return "TIMESTAMP WITH TIMEZONE"
|
||||
|
||||
return 'TIMESTAMP'
|
||||
return "TIMESTAMP"
|
||||
|
||||
|
||||
class Double(SQLType):
|
||||
python = float
|
||||
|
||||
def to_sql(self):
|
||||
return 'REAL'
|
||||
return "REAL"
|
||||
|
||||
|
||||
class Integer(SQLType):
|
||||
|
@ -86,21 +85,21 @@ class Integer(SQLType):
|
|||
self.auto_increment = auto_increment
|
||||
|
||||
if big and small:
|
||||
raise SchemaError('Integer cannot be both big and small')
|
||||
raise SchemaError("Integer cannot be both big and small")
|
||||
|
||||
def to_sql(self):
|
||||
if self.auto_increment:
|
||||
if self.big:
|
||||
return 'BIGSERIAL'
|
||||
return "BIGSERIAL"
|
||||
if self.small:
|
||||
return 'SMALLSERIAL'
|
||||
return 'SERIAL'
|
||||
return "SMALLSERIAL"
|
||||
return "SERIAL"
|
||||
|
||||
if self.big:
|
||||
return 'BIGINT'
|
||||
return "BIGINT"
|
||||
if self.small:
|
||||
return 'SMALLINT'
|
||||
return 'INTEGER'
|
||||
return "SMALLINT"
|
||||
return "INTEGER"
|
||||
|
||||
def is_real_type(self):
|
||||
return not self.auto_increment
|
||||
|
@ -108,32 +107,34 @@ class Integer(SQLType):
|
|||
|
||||
class Interval(SQLType):
|
||||
python = datetime.timedelta
|
||||
valid_fields = ('YEAR',
|
||||
'MONTH',
|
||||
'DAY',
|
||||
'HOUR',
|
||||
'MINUTE',
|
||||
'SECOND',
|
||||
'YEAR TO MONTH',
|
||||
'DAY TO HOUR',
|
||||
'DAY TO MINUTE',
|
||||
'DAY TO SECOND',
|
||||
'HOUR TO MINUTE',
|
||||
'HOUR TO SECOND',
|
||||
'MINUTE TO SECOND')
|
||||
valid_fields = (
|
||||
"YEAR",
|
||||
"MONTH",
|
||||
"DAY",
|
||||
"HOUR",
|
||||
"MINUTE",
|
||||
"SECOND",
|
||||
"YEAR TO MONTH",
|
||||
"DAY TO HOUR",
|
||||
"DAY TO MINUTE",
|
||||
"DAY TO SECOND",
|
||||
"HOUR TO MINUTE",
|
||||
"HOUR TO SECOND",
|
||||
"MINUTE TO SECOND",
|
||||
)
|
||||
|
||||
def __init__(self, field=None):
|
||||
if field:
|
||||
field = field.upper()
|
||||
if field not in self.valid_fields:
|
||||
raise SchemaError('invalid interval specified')
|
||||
raise SchemaError("invalid interval specified")
|
||||
|
||||
self.field = field
|
||||
|
||||
def to_sql(self):
|
||||
if self.field:
|
||||
return 'INTERVAL ' + self.field
|
||||
return 'INTERVAL'
|
||||
return "INTERVAL " + self.field
|
||||
return "INTERVAL"
|
||||
|
||||
|
||||
class Decimal(SQLType):
|
||||
|
@ -142,8 +143,7 @@ class Decimal(SQLType):
|
|||
def __init__(self, *, precision=None, scale=None):
|
||||
if precision is not None:
|
||||
if precision < 0 or precision > 1000:
|
||||
raise SchemaError(
|
||||
'precision must be greater than 0 and below 1000')
|
||||
raise SchemaError("precision must be greater than 0 and below 1000")
|
||||
if scale is None:
|
||||
scale = 0
|
||||
|
||||
|
@ -153,7 +153,7 @@ class Decimal(SQLType):
|
|||
def to_sql(self):
|
||||
if self.precision is not None:
|
||||
return f"NUMERIC({self.precision}, {self.scale})"
|
||||
return 'NUMERIC'
|
||||
return "NUMERIC"
|
||||
|
||||
|
||||
class Numeric(SQLType):
|
||||
|
@ -162,8 +162,7 @@ class Numeric(SQLType):
|
|||
def __init__(self, *, precision=None, scale=None):
|
||||
if precision is not None:
|
||||
if precision < 0 or precision > 1000:
|
||||
raise SchemaError("precision must be greater than 0"
|
||||
"and below 1000")
|
||||
raise SchemaError("precision must be greater than 0" "and below 1000")
|
||||
if scale is None:
|
||||
scale = 0
|
||||
|
||||
|
@ -173,7 +172,7 @@ class Numeric(SQLType):
|
|||
def to_sql(self):
|
||||
if self.precision is not None:
|
||||
return f"NUMERIC({self.precision}, {self.scale})"
|
||||
return 'NUMERIC'
|
||||
return "NUMERIC"
|
||||
|
||||
|
||||
class String(SQLType):
|
||||
|
@ -184,11 +183,11 @@ class String(SQLType):
|
|||
self.fixed = fixed
|
||||
|
||||
if fixed and length is None:
|
||||
raise SchemaError('Cannot have fixed string with no length')
|
||||
raise SchemaError("Cannot have fixed string with no length")
|
||||
|
||||
def to_sql(self):
|
||||
if self.length is None:
|
||||
return 'TEXT'
|
||||
return "TEXT"
|
||||
if self.fixed:
|
||||
return f"CHAR({self.length})"
|
||||
return f"VARCHAR({self.length})"
|
||||
|
@ -202,39 +201,46 @@ class Time(SQLType):
|
|||
|
||||
def to_sql(self):
|
||||
if self.timezone:
|
||||
return 'TIME WITH TIME ZONE'
|
||||
return 'TIME'
|
||||
return "TIME WITH TIME ZONE"
|
||||
return "TIME"
|
||||
|
||||
|
||||
class JSON(SQLType):
|
||||
python = None
|
||||
|
||||
def to_sql(self):
|
||||
return 'JSONB'
|
||||
return "JSONB"
|
||||
|
||||
|
||||
class ForeignKey(SQLType):
|
||||
def __init__(self, table, column, *, sql_type=None, on_delete='CASCADE', on_update='NO ACTION'):
|
||||
def __init__(
|
||||
self,
|
||||
table,
|
||||
column,
|
||||
*,
|
||||
sql_type=None,
|
||||
on_delete="CASCADE",
|
||||
on_update="NO ACTION",
|
||||
):
|
||||
if not table or not isinstance(table, str):
|
||||
raise SchemaError('Missing table to reference (must be string)')
|
||||
raise SchemaError("Missing table to reference (must be string)")
|
||||
|
||||
valid_actions = (
|
||||
'NO ACTION',
|
||||
'RESTRICT',
|
||||
'CASCADE',
|
||||
'SET NULL',
|
||||
'SET DEFAULT',
|
||||
"NO ACTION",
|
||||
"RESTRICT",
|
||||
"CASCADE",
|
||||
"SET NULL",
|
||||
"SET DEFAULT",
|
||||
)
|
||||
|
||||
on_delete = on_delete.upper()
|
||||
on_update = on_update.upper()
|
||||
|
||||
if on_delete not in valid_actions:
|
||||
raise TypeError('on_delete must be one of %s.' % valid_actions)
|
||||
raise TypeError("on_delete must be one of %s." % valid_actions)
|
||||
|
||||
if on_update not in valid_actions:
|
||||
raise TypeError('on_update must be one of %s.' % valid_actions)
|
||||
|
||||
raise TypeError("on_update must be one of %s." % valid_actions)
|
||||
|
||||
self.table = table
|
||||
self.column = column
|
||||
|
@ -248,7 +254,7 @@ class ForeignKey(SQLType):
|
|||
sql_type = sql_type()
|
||||
|
||||
if not isinstance(sql_type, SQLType):
|
||||
raise TypeError('Cannot have non-SQLType derived sql_type')
|
||||
raise TypeError("Cannot have non-SQLType derived sql_type")
|
||||
|
||||
if not sql_type.is_real_type():
|
||||
raise SchemaError('sql_type must be a "real" type')
|
||||
|
@ -259,14 +265,16 @@ class ForeignKey(SQLType):
|
|||
return False
|
||||
|
||||
def to_sql(self):
|
||||
return (f'{self.column} REFERENCES {self.table} ({self.column})'
|
||||
f" ON DELETE {self.on_delete} ON UPDATE {self.on_update}")
|
||||
return (
|
||||
f"{self.column} REFERENCES {self.table} ({self.column})"
|
||||
f" ON DELETE {self.on_delete} ON UPDATE {self.on_update}"
|
||||
)
|
||||
|
||||
|
||||
class ArraySQL(SQLType):
|
||||
def __init__(self, inner_type, size: int = None):
|
||||
if not isinstance(inner_type, SQLType):
|
||||
raise SchemaError('Array inner type must be an SQLType')
|
||||
raise SchemaError("Array inner type must be an SQLType")
|
||||
self.type = inner_type
|
||||
self.size = size
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
__all__ = (
|
||||
"MobPool",
|
||||
"NpcPool",
|
||||
"UserPool",
|
||||
"Field",
|
||||
"FieldObject",
|
||||
"Foothold",
|
||||
"Mob",
|
||||
"Npc",
|
||||
"Portal",
|
||||
)
|
||||
|
||||
from .field import Field
|
||||
from .field_object import FieldObject, Foothold, Mob, Npc, Portal
|
||||
from .pool import MobPool, NpcPool, UserPool
|
|
@ -1,10 +1,9 @@
|
|||
from .foothold_manager import FootholdManager
|
||||
from .portal_manager import PortalManager
|
||||
from .mob_pool import MobPool
|
||||
from .npc_pool import NpcPool
|
||||
from .user_pool import UserPool
|
||||
from .pool import MobPool, NpcPool, UserPool
|
||||
|
||||
from mapy.utils.cpacket import CPacket
|
||||
|
||||
from utils import CPacket, get
|
||||
|
||||
class Field:
|
||||
def __init__(self, map_id):
|
||||
|
@ -26,29 +25,33 @@ class Field:
|
|||
character = client.character
|
||||
|
||||
if client.sent_char_data:
|
||||
await client.send_packet(CPacket.set_field(character, False, client.channel_id))
|
||||
await client.send_packet(
|
||||
CPacket.set_field(character, False, client.channel_id)
|
||||
)
|
||||
|
||||
else:
|
||||
client.sent_char_data = True
|
||||
character.stats.portal = 0
|
||||
|
||||
await client.send_packet(CPacket.set_field(character, True, client.channel_id))
|
||||
|
||||
await client.send_packet(
|
||||
CPacket.set_field(character, True, client.channel_id)
|
||||
)
|
||||
|
||||
for _character in self.clients.characters:
|
||||
await client.send_packet(CPacket.user_enter_field(_character))
|
||||
|
||||
|
||||
await self.spawn_mobs(client)
|
||||
await self.spawn_npcs(client)
|
||||
|
||||
self.clients.add(client)
|
||||
|
||||
await self.broadcast(CPacket.user_enter_field(character))
|
||||
|
||||
|
||||
async def broadcast(self, packet, *ignore):
|
||||
for client in self.clients:
|
||||
if client in ignore or client.character in ignore:
|
||||
continue
|
||||
|
||||
|
||||
await client.send_packet(packet)
|
||||
|
||||
async def swap_mob_controller(self, client, mob):
|
||||
|
@ -56,7 +59,7 @@ class Field:
|
|||
controller = next(filter(lambda c: c.character.id == mob.controller), None)
|
||||
if controller:
|
||||
await controller.send_packet(CPacket.mob_change_controller(mob, 0))
|
||||
|
||||
|
||||
mob.controller = client.character.id
|
||||
await client.send_packet(CPacket.mob_change_controller(mob, 1))
|
||||
|
||||
|
@ -64,10 +67,9 @@ class Field:
|
|||
for mob in self.mobs:
|
||||
if mob.controller == 0:
|
||||
await self.swap_mob_controller(client, mob)
|
||||
|
||||
|
||||
await client.send_packet(CPacket.mob_enter_field(mob))
|
||||
|
||||
async def spawn_npcs(self, client):
|
||||
for npc in self.npcs:
|
||||
await client.send_packet(CPacket.npc_enter_field(npc))
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from mapy.common import WildcardData
|
||||
from mapy.utils import TagPoint
|
||||
|
||||
from .move_path import MovePath
|
||||
|
||||
|
||||
class FieldObject(WildcardData):
|
||||
def __init__(self):
|
||||
self._obj_id = -1
|
||||
self._position = MovePath()
|
||||
self._field = None
|
||||
|
||||
@property
|
||||
def obj_id(self):
|
||||
return self._obj_id
|
||||
|
||||
@obj_id.setter
|
||||
def obj_id(self, value):
|
||||
self._obj_id = value
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
return self._position
|
||||
|
||||
@property
|
||||
def field(self):
|
||||
return self._field
|
||||
|
||||
@field.setter
|
||||
def field(self, value):
|
||||
self._field = value
|
||||
|
||||
|
||||
@dataclass
|
||||
class Foothold(WildcardData):
|
||||
id: int = 0
|
||||
prev: int = 0
|
||||
next: int = 0
|
||||
x1: int = 0
|
||||
y1: int = 0
|
||||
x2: int = 0
|
||||
y2: int = 0
|
||||
|
||||
@property
|
||||
def wall(self):
|
||||
return self.x1 == self.x2
|
||||
|
||||
def compare_to(self, foothold):
|
||||
if self.y2 < foothold.y1:
|
||||
return -1
|
||||
if self.y1 > foothold.y2:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class Portal(WildcardData):
|
||||
id: int = 0
|
||||
name: str = ""
|
||||
type: int = 0
|
||||
destination: int = 0
|
||||
destination_label: str = ""
|
||||
x: int = 0
|
||||
y: int = 0
|
||||
|
||||
def __post_init__(self):
|
||||
self.point = TagPoint(self.x, self.y)
|
||||
|
||||
def __str__(self):
|
||||
return f"{id} @ {self.point} -> {self.destination}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Life(FieldObject):
|
||||
life_id: int = 0
|
||||
life_type: str = ""
|
||||
foothold: int = 0
|
||||
x: int = 0
|
||||
y: int = 0
|
||||
cy: int = 0
|
||||
f: int = 0
|
||||
hide: int = 0
|
||||
rx0: int = 0 # min click position
|
||||
rx1: int = 0 # max click position
|
||||
mob_time: int = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class Mob(Life):
|
||||
mob_id: int = 0
|
||||
hp: int = 0
|
||||
mp: int = 0
|
||||
hp_recovery: int = 0
|
||||
mp_recovery: int = 0
|
||||
exp: int = 0
|
||||
physical_attack: int = 0
|
||||
|
||||
def __post_init__(self):
|
||||
self.attackers = {}
|
||||
self.pos = MovePath(self.x, self.cy, self.foothold)
|
||||
self.cur_hp = self.hp
|
||||
self.cur_mp = self.mp
|
||||
self.controller = 0
|
||||
|
||||
@property
|
||||
def dead(self):
|
||||
return self.cur_hp <= 0
|
||||
|
||||
def damage(self, character, amount):
|
||||
pass
|
||||
|
||||
def encode_init(self, packet):
|
||||
packet.encode_int(self.obj_id)
|
||||
packet.encode_byte(5)
|
||||
packet.encode_int(self.life_id)
|
||||
|
||||
# Set Temporary Stat
|
||||
packet.encode_long(0)
|
||||
packet.encode_long(0)
|
||||
|
||||
packet.encode_short(self.pos.x)
|
||||
packet.encode_short(self.pos.y)
|
||||
packet.encode_byte(0 & 1 | 2 * 2)
|
||||
packet.encode_short(self.pos.foothold)
|
||||
packet.encode_short(self.pos.foothold)
|
||||
|
||||
packet.encode_byte(abs(-2))
|
||||
|
||||
packet.encode_byte(0)
|
||||
packet.encode_int(0)
|
||||
packet.encode_int(0)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Npc(Life):
|
||||
def __post_init__(self):
|
||||
self.pos = MovePath(self.x, self.cy, self.foothold)
|
||||
self.id = self.life_id
|
|
@ -1,4 +1,5 @@
|
|||
from net.packets import packet
|
||||
from mapy.net.packet import ByteBuffer
|
||||
|
||||
|
||||
class MovePath:
|
||||
def __init__(self, x=0, y=0, foothold=0, position=0):
|
||||
|
@ -10,13 +11,13 @@ class MovePath:
|
|||
self.vy = None
|
||||
|
||||
def decode_move_path(self, move_path):
|
||||
ipacket = packet.ByteBuffer(move_path)
|
||||
ipacket = ByteBuffer(move_path)
|
||||
|
||||
self.x = ipacket.decode_short()
|
||||
self.y = ipacket.decode_short()
|
||||
self.vx = ipacket.decode_short()
|
||||
self.vy = ipacket.decode_short()
|
||||
|
||||
|
||||
size = ipacket.decode_byte()
|
||||
|
||||
for i in range(size):
|
||||
|
@ -42,6 +43,6 @@ class MovePath:
|
|||
unk = ipacket.decode_short()
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return f"Position: {self.x},{self.y} - Foothold: {self.foothold} - Stance: {self.stance}"
|
||||
return f"Position: {self.x},{self.y} - Foothold: {self.foothold} - Stance: {self.stance}"
|
|
@ -0,0 +1,77 @@
|
|||
class ObjectPool:
|
||||
def __init__(self, field):
|
||||
self.field = field
|
||||
self.cache = {}
|
||||
self.uid_base = 1000
|
||||
|
||||
@property
|
||||
def new_uid(self):
|
||||
self.uid_base += 1
|
||||
return self.uid_base
|
||||
|
||||
def add(self, value):
|
||||
value.obj_id = self.new_uid
|
||||
self.cache[value.obj_id] = value
|
||||
|
||||
def remove(self, key):
|
||||
return self.cache.pop(key)
|
||||
|
||||
def clear(self):
|
||||
self.cache = {}
|
||||
|
||||
def get(self, key):
|
||||
return self.cache.get(key, None)
|
||||
|
||||
def __enumerator__(self):
|
||||
return (obj for obj in self.cache.values())
|
||||
|
||||
def __iter__(self):
|
||||
return (obj for obj in self.cache.values())
|
||||
|
||||
def __aiter__(self):
|
||||
return self.__iter__()
|
||||
|
||||
|
||||
class MobPool(ObjectPool):
|
||||
def __init__(self, field):
|
||||
super().__init__(field)
|
||||
self.spawns = []
|
||||
|
||||
def add(self, mob):
|
||||
mob.field = self.field
|
||||
super().add(mob)
|
||||
self.spawns.append(mob)
|
||||
|
||||
async def remove(self, key):
|
||||
mob = self.get(key)
|
||||
|
||||
if mob:
|
||||
owner = None
|
||||
|
||||
# await self.field.broadcast(CPacket.mob_leave_field(mob))
|
||||
|
||||
if mob.dead:
|
||||
pass
|
||||
# drop_pos_x = mob.position.x
|
||||
# drop_pos_y = mob.position.y
|
||||
|
||||
# self.field.drops.add(Drop(0, mob.position, 50, 1))
|
||||
|
||||
return super().remove(key)
|
||||
|
||||
|
||||
class NpcPool(ObjectPool):
|
||||
...
|
||||
|
||||
|
||||
class UserPool(ObjectPool):
|
||||
def add(self, client):
|
||||
super().add(client)
|
||||
client.character.field = self.field
|
||||
|
||||
@property
|
||||
def characters(self):
|
||||
return [client.character for client in self]
|
||||
|
||||
def __aiter__(self):
|
||||
return [client for client in self]
|
|
@ -1,2 +1,2 @@
|
|||
from .skill import Skill, SkillLevel, SkillLevelData
|
||||
from .item import ItemSlotEquip, ItemSlotBundle
|
||||
from .item import ItemSlotEquip, ItemSlotBundle
|
|
@ -1,17 +1,18 @@
|
|||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
|
||||
from common import abc
|
||||
from mapy.common import WildcardData
|
||||
|
||||
|
||||
class ItemInventoryTypes(Enum):
|
||||
ItemSlotEquip = 0x1
|
||||
|
||||
|
||||
@dataclass
|
||||
class ItemSlotBase(abc.WildcardData):
|
||||
class ItemSlotBase(WildcardData):
|
||||
"""Base item class for all items
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
item_id: int
|
||||
|
@ -29,16 +30,16 @@ class ItemSlotBase(abc.WildcardData):
|
|||
|
||||
"""
|
||||
|
||||
item_id: int = 0
|
||||
cisn: int = 0
|
||||
expire: int = 0
|
||||
inventory_item_id: int = 0
|
||||
quantity: int = 0
|
||||
flag: int = 0
|
||||
item_id: int = 0
|
||||
cisn: int = 0
|
||||
expire: int = 0
|
||||
inventory_item_id: int = 0
|
||||
quantity: int = 0
|
||||
flag: int = 0
|
||||
|
||||
def encode(self, packet) -> None:
|
||||
"""Encode base item information onto packet
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
packet: :class:`net.packets.Packet`
|
||||
|
@ -47,58 +48,59 @@ class ItemSlotBase(abc.WildcardData):
|
|||
"""
|
||||
|
||||
packet.encode_int(self.item_id)
|
||||
packet.encode_byte(self.cisn is not 0)
|
||||
packet.encode_byte(self.cisn == 0)
|
||||
|
||||
if self.cisn:
|
||||
packet.encode_long(self.cisn)
|
||||
|
||||
packet.encode_long(0)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ItemSlotEquip(ItemSlotBase):
|
||||
req_job: List = list
|
||||
ruc: int = 0
|
||||
cuc: int = 0
|
||||
req_job: List = list
|
||||
ruc: int = 0
|
||||
cuc: int = 0
|
||||
|
||||
str: int = 0
|
||||
dex: int = 0
|
||||
int: int = 0
|
||||
luk: int = 0
|
||||
hp: int = 0
|
||||
mp: int = 0
|
||||
weapon_attack: int = 0
|
||||
str: int = 0
|
||||
dex: int = 0
|
||||
int: int = 0
|
||||
luk: int = 0
|
||||
hp: int = 0
|
||||
mp: int = 0
|
||||
weapon_attack: int = 0
|
||||
weapon_defense: int = 0
|
||||
magic_attack: int = 0
|
||||
magic_defense: int = 0
|
||||
accuracy: int = 0
|
||||
avoid: int = 0
|
||||
magic_attack: int = 0
|
||||
magic_defense: int = 0
|
||||
accuracy: int = 0
|
||||
avoid: int = 0
|
||||
|
||||
hands: int = 0
|
||||
speed: int = 0
|
||||
jump: int = 0
|
||||
hands: int = 0
|
||||
speed: int = 0
|
||||
jump: int = 0
|
||||
|
||||
title: str = ""
|
||||
craft: int = 0
|
||||
attribute: int = 0
|
||||
level_up_type: int = 0
|
||||
level: int = 0
|
||||
durability: int = 0
|
||||
iuc: int = 0
|
||||
exp: int = 0
|
||||
title: str = ""
|
||||
craft: int = 0
|
||||
attribute: int = 0
|
||||
level_up_type: int = 0
|
||||
level: int = 0
|
||||
durability: int = 0
|
||||
iuc: int = 0
|
||||
exp: int = 0
|
||||
|
||||
grade: int = 0
|
||||
chuc: int = 0
|
||||
grade: int = 0
|
||||
chuc: int = 0
|
||||
|
||||
option_1: int = 0
|
||||
option_2: int = 0
|
||||
option_3: int = 0
|
||||
socket_1: int = 0
|
||||
socket_2: int = 0
|
||||
option_1: int = 0
|
||||
option_2: int = 0
|
||||
option_3: int = 0
|
||||
socket_1: int = 0
|
||||
socket_2: int = 0
|
||||
|
||||
lisn: int = 0
|
||||
lisn: int = 0
|
||||
storage_id: int = 0
|
||||
sn: int = 0
|
||||
|
||||
sn: int = 0
|
||||
|
||||
def encode(self, packet):
|
||||
packet.encode_byte(1)
|
||||
|
||||
|
@ -139,20 +141,20 @@ class ItemSlotEquip(ItemSlotBase):
|
|||
packet.encode_short(self.option_3)
|
||||
packet.encode_short(self.socket_1)
|
||||
packet.encode_short(self.socket_2)
|
||||
|
||||
|
||||
if not self.cisn:
|
||||
packet.encode_long(0)
|
||||
|
||||
|
||||
packet.encode_long(0)
|
||||
packet.encode_int(0)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ItemSlotBundle(ItemSlotBase):
|
||||
number: int = 1
|
||||
attribute: int = 0
|
||||
lisn: int = 0
|
||||
title: str = ""
|
||||
number: int = 1
|
||||
attribute: int = 0
|
||||
lisn: int = 0
|
||||
title: str = ""
|
||||
|
||||
def encode(self, packet):
|
||||
packet.encode_byte(2)
|
||||
|
@ -164,4 +166,4 @@ class ItemSlotBundle(ItemSlotBase):
|
|||
packet.encode_short(self.attribute)
|
||||
|
||||
if self.item_id / 10000 == 207:
|
||||
packet.encode_long(self.lisn)
|
||||
packet.encode_long(self.lisn)
|
|
@ -1,36 +1,39 @@
|
|||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
from common.abc import WildcardData
|
||||
from mapy.common import WildcardData
|
||||
|
||||
|
||||
class Skill:
|
||||
def __init__(self, id):
|
||||
self._id = id
|
||||
self._skill_level_data = []
|
||||
|
||||
|
||||
class SkillLevel:
|
||||
def __init__(self, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SkillLevelData(WildcardData):
|
||||
flags: List = field(default=list)
|
||||
weapon: int = 0
|
||||
sub_weapon: int = 0
|
||||
max_level: int = 0
|
||||
base_max_level: int = 0
|
||||
skill_type: List = field(default=list)
|
||||
element: str = ""
|
||||
mob_count: str = ""
|
||||
hit_count: str = ""
|
||||
buff_time: str = ""
|
||||
mp_cost: str = ""
|
||||
hp_cost: str = ""
|
||||
damage: str = ""
|
||||
fixed_damage: str = ""
|
||||
critical_damage: str = ""
|
||||
mastery: str = ""
|
||||
flags: List = field(default=list)
|
||||
weapon: int = 0
|
||||
sub_weapon: int = 0
|
||||
max_level: int = 0
|
||||
base_max_level: int = 0
|
||||
skill_type: List = field(default=list)
|
||||
element: str = ""
|
||||
mob_count: str = ""
|
||||
hit_count: str = ""
|
||||
buff_time: str = ""
|
||||
mp_cost: str = ""
|
||||
hp_cost: str = ""
|
||||
damage: str = ""
|
||||
fixed_damage: str = ""
|
||||
critical_damage: str = ""
|
||||
mastery: str = ""
|
||||
|
||||
def __post_init__(self):
|
||||
self._levels = {}
|
||||
|
@ -42,18 +45,19 @@ class SkillLevelData(WildcardData):
|
|||
pass
|
||||
else:
|
||||
kwargs[name] = value
|
||||
|
||||
|
||||
self._levels[i] = SkillLevel(**kwargs)
|
||||
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self._levels[index]
|
||||
|
||||
|
||||
# @dataclass
|
||||
# class SkillLevel(WildcardData):
|
||||
# max_level:int = 0
|
||||
# mob_count:int = 0
|
||||
# hit_count:int = 0
|
||||
# range:int = 0
|
||||
# range:int = 0
|
||||
# buff_time:int = 0
|
||||
# cost_hp:int = 0
|
||||
# cost_mp:int = 0
|
|
@ -1,2 +1,2 @@
|
|||
from .client import HTTPClient
|
||||
from .server import HTTPServer
|
||||
from .server import HTTPServer
|
|
@ -0,0 +1,111 @@
|
|||
from aiohttp import ClientSession
|
||||
|
||||
# from loguru import logger
|
||||
|
||||
from mapy.character import Character
|
||||
from mapy.game import ItemSlotEquip
|
||||
from mapy.common.constants import HTTP_API_ROUTE
|
||||
from mapy.utils import fix_dict_keys
|
||||
|
||||
|
||||
class Route:
|
||||
def __init__(self, method, route, data=None, params=None, json=None):
|
||||
self._method = method
|
||||
self._route = route
|
||||
self._data = data
|
||||
self._params = params
|
||||
self._json = json
|
||||
|
||||
|
||||
class HTTPClient:
|
||||
def __init__(self, provider=None):
|
||||
self._host = provider | HTTP_API_ROUTE
|
||||
|
||||
async def request(self, route, *, content_type="json", **kwargs):
|
||||
async with ClientSession() as session:
|
||||
url = self._host + route._route
|
||||
|
||||
if route._data:
|
||||
kwargs["data"] = route._data
|
||||
|
||||
if route._params:
|
||||
kwargs["params"] = route._params
|
||||
|
||||
if route._json:
|
||||
kwargs["json"] = route._json
|
||||
|
||||
r = await session.request(route._method, url, **kwargs)
|
||||
|
||||
try:
|
||||
match [r.status, content_type]:
|
||||
case [200, "json"]:
|
||||
return await r.json()
|
||||
case [200, "image"]:
|
||||
return await r.read()
|
||||
case [200, _]:
|
||||
return await r.text()
|
||||
case _:
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
(e)
|
||||
|
||||
finally:
|
||||
await r.release()
|
||||
|
||||
##
|
||||
# Main Data Provider
|
||||
##
|
||||
|
||||
async def is_username_taken(self, character_name):
|
||||
response = await self.request(Route("GET", "/character/name/" + character_name))
|
||||
|
||||
return response["resp"]
|
||||
|
||||
async def login(self, username, password):
|
||||
route = Route(
|
||||
"POST",
|
||||
"/login",
|
||||
data={
|
||||
"username": username,
|
||||
"password": password,
|
||||
},
|
||||
)
|
||||
|
||||
response = await self.request(route)
|
||||
return response
|
||||
|
||||
async def get_characters(self, account_id, world_id=None):
|
||||
if not world_id:
|
||||
url = f"/account/{account_id}/characters"
|
||||
|
||||
else:
|
||||
url = f"/account/{account_id}/characters/{world_id}"
|
||||
|
||||
response = fix_dict_keys(await self.request(Route("GET", url)))
|
||||
|
||||
return [
|
||||
Character.from_data(**character) for character in response["characters"]
|
||||
]
|
||||
|
||||
async def create_new_character(self, account_id, character: Character):
|
||||
route = Route(
|
||||
"POST",
|
||||
f"/account/{account_id}/character/new",
|
||||
json=character.__serialize__(),
|
||||
)
|
||||
|
||||
response = await self.request(route)
|
||||
|
||||
return response["id"]
|
||||
|
||||
##
|
||||
# Static Data Provider
|
||||
##
|
||||
|
||||
async def get_item(self, item_id):
|
||||
route = Route("GET", f"/item/{item_id}")
|
||||
response = await self.request(route)
|
||||
|
||||
item = ItemSlotEquip(**response)
|
||||
return item
|
|
@ -20,12 +20,7 @@ class Base(RouteTableDef):
|
|||
path = handler._path
|
||||
kwargs = handler._kwargs
|
||||
self._items.append(
|
||||
RouteDef(
|
||||
method,
|
||||
path,
|
||||
getattr(self, handler.__name__),
|
||||
kwargs
|
||||
)
|
||||
RouteDef(method, path, getattr(self, handler.__name__), kwargs)
|
||||
)
|
||||
|
||||
|
||||
|
@ -35,6 +30,7 @@ def route(method, path, **kwargs):
|
|||
handler._path = path
|
||||
handler._kwargs = kwargs
|
||||
return handler
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
|
@ -47,22 +43,24 @@ class Routes(Base):
|
|||
@route("GET", "/")
|
||||
async def get_status(self, request):
|
||||
resp = {
|
||||
'uptime': self._server.uptime,
|
||||
'population': self._server.population,
|
||||
'login_server': {
|
||||
'alive': self._server.login.alive,
|
||||
'port': self._server.login.port,
|
||||
'population': self._server.login.population
|
||||
"uptime": self._server.uptime,
|
||||
"population": self._server.population,
|
||||
"login_server": {
|
||||
"alive": self._server.login.alive,
|
||||
"port": self._server.login.port,
|
||||
"population": self._server.login.population,
|
||||
},
|
||||
'game_servers': {
|
||||
"game_servers": {
|
||||
world.name: {
|
||||
i: {
|
||||
'alive': channel.alive,
|
||||
'port': channel.port,
|
||||
'population': channel.population
|
||||
} for i, channel in enumerate(world.channels, 1)
|
||||
} for world in self._server.worlds.values()
|
||||
}
|
||||
"alive": channel.alive,
|
||||
"port": channel.port,
|
||||
"population": channel.population,
|
||||
}
|
||||
for i, channel in enumerate(world.channels, 1)
|
||||
}
|
||||
for world in self._server.worlds.values()
|
||||
},
|
||||
}
|
||||
|
||||
return json_response(resp)
|
|
@ -2,7 +2,7 @@ from aiohttp import web
|
|||
from os import walk
|
||||
import importlib
|
||||
|
||||
from utils import log
|
||||
from mapy import log
|
||||
|
||||
|
||||
class HTTPServer(web.Application):
|
||||
|
@ -17,10 +17,8 @@ class HTTPServer(web.Application):
|
|||
self.load_routes()
|
||||
|
||||
def load_routes(self):
|
||||
self._routes = importlib.import_module('.routes', 'http_api')
|
||||
self.router.add_routes(
|
||||
self._routes.Routes(self)
|
||||
)
|
||||
self._routes = importlib.import_module(".routes", "mapy.http_api")
|
||||
self.router.add_routes(self._routes.Routes(self))
|
||||
|
||||
def run(self):
|
||||
runner = web.AppRunner(self)
|
||||
|
@ -34,5 +32,5 @@ class HTTPServer(web.Application):
|
|||
return self._server
|
||||
|
||||
def log(self, message, level=None):
|
||||
level = level if level else 'debug'
|
||||
level = level if level else "debug"
|
||||
getattr(log, level)(f"{self._name} {message}")
|
|
@ -0,0 +1,99 @@
|
|||
from re import IGNORECASE, compile, X
|
||||
from .common.enum import Worlds
|
||||
|
||||
|
||||
PACKET_RE = compile(
|
||||
r"(?P<opcode>[A-Za-z0-9\._]+)\s(?P<ip>[0-9\.]+)\s(?P<packet>[A-Z0-9\s]*)"
|
||||
)
|
||||
|
||||
SERVER_RE = compile(
|
||||
r"""^
|
||||
(?P<server>
|
||||
(?P<name>[a-zA-Z]+\s?[a-zA-Z]+?)
|
||||
(?P<game>
|
||||
\[(?P<world_id>[0-9]+)\]
|
||||
\[(?P<ch_id>[0-9]+)\]
|
||||
)?
|
||||
)\s
|
||||
(?P<message>.+)$""",
|
||||
flags=IGNORECASE | X,
|
||||
)
|
||||
|
||||
try:
|
||||
from loguru._logger import Logger as _Logger, Core
|
||||
from sys import stdout
|
||||
|
||||
def fmt_packet(rec):
|
||||
direction = in_ if rec["level"].name == (in_ := "INPACKET") else "OUTPACKET"
|
||||
match_packet = PACKET_RE.search(rec["message"])
|
||||
op_code, ip, packet = list(match_packet.group(1, 2, 3))
|
||||
string = (
|
||||
f"<lg>[</lg><level>{direction:^12}</level><lg>]</lg> "
|
||||
f"<r>[</r><level>{op_code}</level><r>]</r> "
|
||||
f"<g>[</g>{ip}<g>]</g> <w>{packet}</w>"
|
||||
"\n"
|
||||
)
|
||||
return string
|
||||
|
||||
def fmt_record(record):
|
||||
name, message = "Unnamed", ""
|
||||
if grps := getattr(SERVER_RE.search(record["message"]), "group", None):
|
||||
message = grps("message")
|
||||
name = grps("name")
|
||||
if grps("game"):
|
||||
name = f"""{(
|
||||
f"<lc>{Worlds(int(grps('world_id'))).name}"
|
||||
f"</lc>: <lr>{int(grps('ch_id')) + 1}</lr>"): <33}"""
|
||||
|
||||
return (
|
||||
"<lg>[</lg>"
|
||||
f"<level>{record['level']: ^10}</level>"
|
||||
"<lg>]</lg>"
|
||||
f"<lg>[</lg>{name: <15}<lg>]</lg> "
|
||||
f"<level>{message}</level>"
|
||||
"\n"
|
||||
)
|
||||
|
||||
def filter_packets(record):
|
||||
return not record["level"] in ["INPACKET", "OUTPACKET"]
|
||||
|
||||
class Logger(_Logger):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
Core(), None, 0, False, False, False, False, True, None, {}
|
||||
)
|
||||
self.remove()
|
||||
self.level("INPACKET", no=50, color="<c>")
|
||||
self.level("OUTPACKET", no=50, color="<lm>")
|
||||
self.add(
|
||||
stdout,
|
||||
format=fmt_record,
|
||||
filter=filter_packets,
|
||||
colorize=True,
|
||||
diagnose=True,
|
||||
)
|
||||
self.add(
|
||||
stdout,
|
||||
level="INPACKET",
|
||||
colorize=True,
|
||||
format=fmt_packet,
|
||||
)
|
||||
self.add(
|
||||
stdout,
|
||||
level="OUTPACKET",
|
||||
colorize=True,
|
||||
format=fmt_packet,
|
||||
)
|
||||
|
||||
def i_packet(self, message):
|
||||
return self._log("INPACKET", None, False, self._options, message, (), {})
|
||||
|
||||
def o_packet(self, message):
|
||||
return self._log("OUTPACKET", None, False, self._options, message, (), {})
|
||||
|
||||
log = Logger()
|
||||
|
||||
except ImportError:
|
||||
from logging import getLogger
|
||||
|
||||
log = getLogger()
|
|
@ -0,0 +1,4 @@
|
|||
__all__ = "CSendOps", "CRecvOps", "Packet"
|
||||
|
||||
from .packet import Packet
|
||||
from .opcodes import CSendOps, CRecvOps
|
|
@ -0,0 +1,12 @@
|
|||
__all__ = (
|
||||
"MapleIV",
|
||||
"MapleAes",
|
||||
"decrypt_transform",
|
||||
"encrypt_transform",
|
||||
"roll_left",
|
||||
"roll_right",
|
||||
)
|
||||
|
||||
from .maple_iv import MapleIV
|
||||
from .aes import MapleAes
|
||||
from .shanda import decrypt_transform, encrypt_transform, roll_left, roll_right
|
|
@ -1,18 +1,50 @@
|
|||
from Crypto.Cipher import AES
|
||||
try:
|
||||
from Cryptodome.Cipher import AES
|
||||
except ImportError:
|
||||
from Crypto.Cipher import AES # type: ignore
|
||||
except ImportError:
|
||||
raise Exception("Please install pycryptodomex")
|
||||
|
||||
# from struct import unpack, pack
|
||||
|
||||
|
||||
class MapleAes:
|
||||
_user_key = bytearray([
|
||||
0x13, 0x00, 0x00, 0x00,
|
||||
0x08, 0x00, 0x00, 0x00,
|
||||
0x06, 0x00, 0x00, 0x00,
|
||||
0xb4, 0x00, 0x00, 0x00,
|
||||
0x1b, 0x00, 0x00, 0x00,
|
||||
0x0f, 0x00, 0x00, 0x00,
|
||||
0x33, 0x00, 0x00, 0x00,
|
||||
0x52, 0x00, 0x00, 0x00
|
||||
])
|
||||
_user_key = bytearray(
|
||||
[
|
||||
0x13,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x08,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x06,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0xB4,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x1B,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x0F,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x33,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x52,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def transform(cls, buffer, iv):
|
||||
|
@ -42,9 +74,7 @@ class MapleAes:
|
|||
sub = index - start
|
||||
|
||||
if (sub % 16) == 0:
|
||||
real_iv = (
|
||||
AES.new(cls._user_key, AES.MODE_ECB).encrypt(real_iv)
|
||||
)
|
||||
real_iv = AES.new(cls._user_key, AES.MODE_ECB).encrypt(real_iv)
|
||||
|
||||
buffer[index] ^= real_iv[sub % 16]
|
||||
index += 1
|
||||
|
@ -71,7 +101,7 @@ class MapleAes:
|
|||
@staticmethod
|
||||
def get_header(data, iv, length, major_ver):
|
||||
first = -(major_ver + 1) ^ iv.hiword
|
||||
second = (first + 2**16) ^ length
|
||||
second = (first + 2 ** 16) ^ length
|
||||
data[0:2] = bytes([first & 0xFF, first >> 8 & 0xFF])
|
||||
data[2:4] = bytes([second & 0xFF, second >> 8 & 0xFF])
|
||||
|
|
@ -0,0 +1,346 @@
|
|||
class MapleIV:
|
||||
_shuffle = bytearray(
|
||||
[
|
||||
0xEC,
|
||||
0x3F,
|
||||
0x77,
|
||||
0xA4,
|
||||
0x45,
|
||||
0xD0,
|
||||
0x71,
|
||||
0xBF,
|
||||
0xB7,
|
||||
0x98,
|
||||
0x20,
|
||||
0xFC,
|
||||
0x4B,
|
||||
0xE9,
|
||||
0xB3,
|
||||
0xE1,
|
||||
0x5C,
|
||||
0x22,
|
||||
0xF7,
|
||||
0x0C,
|
||||
0x44,
|
||||
0x1B,
|
||||
0x81,
|
||||
0xBD,
|
||||
0x63,
|
||||
0x8D,
|
||||
0xD4,
|
||||
0xC3,
|
||||
0xF2,
|
||||
0x10,
|
||||
0x19,
|
||||
0xE0,
|
||||
0xFB,
|
||||
0xA1,
|
||||
0x6E,
|
||||
0x66,
|
||||
0xEA,
|
||||
0xAE,
|
||||
0xD6,
|
||||
0xCE,
|
||||
0x06,
|
||||
0x18,
|
||||
0x4E,
|
||||
0xEB,
|
||||
0x78,
|
||||
0x95,
|
||||
0xDB,
|
||||
0xBA,
|
||||
0xB6,
|
||||
0x42,
|
||||
0x7A,
|
||||
0x2A,
|
||||
0x83,
|
||||
0x0B,
|
||||
0x54,
|
||||
0x67,
|
||||
0x6D,
|
||||
0xE8,
|
||||
0x65,
|
||||
0xE7,
|
||||
0x2F,
|
||||
0x07,
|
||||
0xF3,
|
||||
0xAA,
|
||||
0x27,
|
||||
0x7B,
|
||||
0x85,
|
||||
0xB0,
|
||||
0x26,
|
||||
0xFD,
|
||||
0x8B,
|
||||
0xA9,
|
||||
0xFA,
|
||||
0xBE,
|
||||
0xA8,
|
||||
0xD7,
|
||||
0xCB,
|
||||
0xCC,
|
||||
0x92,
|
||||
0xDA,
|
||||
0xF9,
|
||||
0x93,
|
||||
0x60,
|
||||
0x2D,
|
||||
0xDD,
|
||||
0xD2,
|
||||
0xA2,
|
||||
0x9B,
|
||||
0x39,
|
||||
0x5F,
|
||||
0x82,
|
||||
0x21,
|
||||
0x4C,
|
||||
0x69,
|
||||
0xF8,
|
||||
0x31,
|
||||
0x87,
|
||||
0xEE,
|
||||
0x8E,
|
||||
0xAD,
|
||||
0x8C,
|
||||
0x6A,
|
||||
0xBC,
|
||||
0xB5,
|
||||
0x6B,
|
||||
0x59,
|
||||
0x13,
|
||||
0xF1,
|
||||
0x04,
|
||||
0x00,
|
||||
0xF6,
|
||||
0x5A,
|
||||
0x35,
|
||||
0x79,
|
||||
0x48,
|
||||
0x8F,
|
||||
0x15,
|
||||
0xCD,
|
||||
0x97,
|
||||
0x57,
|
||||
0x12,
|
||||
0x3E,
|
||||
0x37,
|
||||
0xFF,
|
||||
0x9D,
|
||||
0x4F,
|
||||
0x51,
|
||||
0xF5,
|
||||
0xA3,
|
||||
0x70,
|
||||
0xBB,
|
||||
0x14,
|
||||
0x75,
|
||||
0xC2,
|
||||
0xB8,
|
||||
0x72,
|
||||
0xC0,
|
||||
0xED,
|
||||
0x7D,
|
||||
0x68,
|
||||
0xC9,
|
||||
0x2E,
|
||||
0x0D,
|
||||
0x62,
|
||||
0x46,
|
||||
0x17,
|
||||
0x11,
|
||||
0x4D,
|
||||
0x6C,
|
||||
0xC4,
|
||||
0x7E,
|
||||
0x53,
|
||||
0xC1,
|
||||
0x25,
|
||||
0xC7,
|
||||
0x9A,
|
||||
0x1C,
|
||||
0x88,
|
||||
0x58,
|
||||
0x2C,
|
||||
0x89,
|
||||
0xDC,
|
||||
0x02,
|
||||
0x64,
|
||||
0x40,
|
||||
0x01,
|
||||
0x5D,
|
||||
0x38,
|
||||
0xA5,
|
||||
0xE2,
|
||||
0xAF,
|
||||
0x55,
|
||||
0xD5,
|
||||
0xEF,
|
||||
0x1A,
|
||||
0x7C,
|
||||
0xA7,
|
||||
0x5B,
|
||||
0xA6,
|
||||
0x6F,
|
||||
0x86,
|
||||
0x9F,
|
||||
0x73,
|
||||
0xE6,
|
||||
0x0A,
|
||||
0xDE,
|
||||
0x2B,
|
||||
0x99,
|
||||
0x4A,
|
||||
0x47,
|
||||
0x9C,
|
||||
0xDF,
|
||||
0x09,
|
||||
0x76,
|
||||
0x9E,
|
||||
0x30,
|
||||
0x0E,
|
||||
0xE4,
|
||||
0xB2,
|
||||
0x94,
|
||||
0xA0,
|
||||
0x3B,
|
||||
0x34,
|
||||
0x1D,
|
||||
0x28,
|
||||
0x0F,
|
||||
0x36,
|
||||
0xE3,
|
||||
0x23,
|
||||
0xB4,
|
||||
0x03,
|
||||
0xD8,
|
||||
0x90,
|
||||
0xC8,
|
||||
0x3C,
|
||||
0xFE,
|
||||
0x5E,
|
||||
0x32,
|
||||
0x24,
|
||||
0x50,
|
||||
0x1F,
|
||||
0x3A,
|
||||
0x43,
|
||||
0x8A,
|
||||
0x96,
|
||||
0x41,
|
||||
0x74,
|
||||
0xAC,
|
||||
0x52,
|
||||
0x33,
|
||||
0xF0,
|
||||
0xD9,
|
||||
0x29,
|
||||
0x80,
|
||||
0xB1,
|
||||
0x16,
|
||||
0xD3,
|
||||
0xAB,
|
||||
0x91,
|
||||
0xB9,
|
||||
0x84,
|
||||
0x7F,
|
||||
0x61,
|
||||
0x1E,
|
||||
0xCF,
|
||||
0xC5,
|
||||
0xD1,
|
||||
0x56,
|
||||
0x3D,
|
||||
0xCA,
|
||||
0xF4,
|
||||
0x05,
|
||||
0xC6,
|
||||
0xE5,
|
||||
0x08,
|
||||
0x49,
|
||||
]
|
||||
)
|
||||
|
||||
def __init__(self, vector):
|
||||
self.value = vector
|
||||
|
||||
def __int__(self):
|
||||
return self.value
|
||||
|
||||
@property
|
||||
def hiword(self):
|
||||
return self.value >> 16
|
||||
|
||||
@property
|
||||
def loword(self):
|
||||
return self.value
|
||||
|
||||
# def shuffle(self):
|
||||
# def to_int(arr):
|
||||
# return (seed[0] & 0xFF) + (seed[1] << 8 & 0xFF)\
|
||||
# + (seed[2] << 16 & 0xFF) + (seed[3] << 24 * 0xFF)
|
||||
|
||||
# def to_arr(int_):
|
||||
# return [
|
||||
# int_ & 0xFF,
|
||||
# int_ >> 8 & 0xFF,
|
||||
# int_ >> 16 & 0xFF,
|
||||
# int_ >> 24 & 0xFF
|
||||
# ]
|
||||
|
||||
# seed = [0xf2, 0x53, 0x50, 0xc6]
|
||||
# p_iv = self.value
|
||||
|
||||
# for i in range (4):
|
||||
# temp_iv = (p_iv >> (8 * i)) & 0xFF
|
||||
# seed[0] += (self._shuffle[seed[1] & 0xFF]) - temp_iv
|
||||
# seed[1] -= seed[2] ^ (self._shuffle[temp_iv])
|
||||
# seed[2] ^= temp_iv + (self._shuffle[seed[3]])
|
||||
# seed[3] = seed[3] - seed[0] + (self._shuffle[temp_iv])
|
||||
# seed = to_arr((to_int(seed) << 3) | (to_int(seed) >> 29))
|
||||
|
||||
# self.value = to_int(seed)
|
||||
|
||||
def shuffle(self):
|
||||
seed = [0xF2, 0x53, 0x50, 0xC6]
|
||||
p_iv = self.value
|
||||
|
||||
for i in range(4):
|
||||
temp_p_iv = (p_iv >> (8 * i)) & 0xFF
|
||||
|
||||
a = seed[1]
|
||||
b = a
|
||||
b = self._shuffle[b & 0xFF]
|
||||
b -= temp_p_iv
|
||||
seed[0] += b
|
||||
b = seed[2]
|
||||
b ^= self._shuffle[int(temp_p_iv) & 0xFF]
|
||||
a -= int(b) & 0xFF
|
||||
seed[1] = a
|
||||
a = seed[3]
|
||||
b = a
|
||||
a -= seed[0] & 0xFF
|
||||
b = self._shuffle[b & 0xFF]
|
||||
b += temp_p_iv
|
||||
b ^= seed[2]
|
||||
seed[2] = b & 0xFF
|
||||
a += self._shuffle[temp_p_iv & 0xFF] & 0xFF
|
||||
seed[3] = a
|
||||
|
||||
c = seed[0] & 0xFF
|
||||
c |= (seed[1] << 8) & 0xFFFF
|
||||
c |= (seed[2] << 16) & 0xFFFFFF
|
||||
c |= (seed[3] << 24) & 0xFFFFFFFF
|
||||
|
||||
c = (c << 0x03) | (c >> 0x1D)
|
||||
|
||||
seed[0] = c & 0xFF
|
||||
seed[1] = (c >> 8) & 0xFFFF
|
||||
seed[2] = (c >> 16) & 0xFFFFFF
|
||||
seed[3] = (c >> 24) & 0xFFFFFFFF
|
||||
|
||||
c = seed[0] & 0xFF
|
||||
c |= (seed[1] << 8) & 0xFFFF
|
||||
c |= (seed[2] << 16) & 0xFFFFFF
|
||||
c |= (seed[3] << 24) & 0xFFFFFFFF
|
||||
|
||||
self.value = c
|
|
@ -31,6 +31,7 @@ def decrypt_transform(data):
|
|||
|
||||
return data
|
||||
|
||||
|
||||
def encrypt_transform(data):
|
||||
b = {str(i): 0 for i in range(len(data))}
|
||||
cur = 0
|
||||
|
@ -40,7 +41,7 @@ def encrypt_transform(data):
|
|||
xor_key = 0
|
||||
i = 0
|
||||
while i < len(data):
|
||||
|
||||
|
||||
cur = roll_left(data[i], 3)
|
||||
cur = cur + length
|
||||
cur = (cur ^ xor_key) & 0xFF
|
||||
|
@ -52,7 +53,6 @@ def encrypt_transform(data):
|
|||
length -= 1
|
||||
i += 1
|
||||
|
||||
|
||||
xor_key = 0
|
||||
length = len(data) & 0xFF
|
||||
i = len(data) - 1
|
||||
|
@ -74,8 +74,9 @@ def encrypt_transform(data):
|
|||
|
||||
def roll_left(value, shift):
|
||||
num = value << (shift % 8)
|
||||
return ((num & 0xFF) | (num >> 8))
|
||||
return (num & 0xFF) | (num >> 8)
|
||||
|
||||
|
||||
def roll_right(value, shift):
|
||||
num = (value << 8) >> (shift % 8)
|
||||
return ((num & 0xFF) | (num >> 8))
|
||||
return (num & 0xFF) | (num >> 8)
|
|
@ -1,7 +1,9 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
class OpCodes(Enum):
|
||||
pass
|
||||
...
|
||||
|
||||
|
||||
class CRecvOps(OpCodes):
|
||||
CP_CheckPassword = 0x1
|
||||
|
@ -319,6 +321,7 @@ class CRecvOps(OpCodes):
|
|||
CP_LogoutGiftSelect = 0x139
|
||||
CP_NO = 0x13A
|
||||
|
||||
|
||||
class CSendOps(OpCodes):
|
||||
LP_CheckPasswordResult = 0x0
|
||||
LP_GuestIDLoginResult = 0x1
|
||||
|
@ -826,4 +829,4 @@ class CSendOps(OpCodes):
|
|||
LP_Vega_e = 0x1AF
|
||||
# LP_END_VEGA = 0x1AF
|
||||
LP_LogoutGift = 0x1B0
|
||||
LP_NO = 0x1B1
|
||||
LP_NO = 0x1B1
|
|
@ -2,22 +2,22 @@ from enum import Enum
|
|||
from io import BytesIO
|
||||
from struct import pack, unpack
|
||||
|
||||
from . import CRecvOps
|
||||
from utils.tools import to_string
|
||||
from .opcodes import CRecvOps
|
||||
from mapy.utils.tools import to_string
|
||||
|
||||
|
||||
# Junk codes for colorizing incoming packets from custom client
|
||||
debug_codes = [
|
||||
('r', ('|', '|')),
|
||||
('lr', ('|', '&')),
|
||||
('c', ('~', '~')),
|
||||
('lc', ('~', '&')),
|
||||
('y', ('#', '#')),
|
||||
('ly', ('#', '&')),
|
||||
('g', ('^', '^')),
|
||||
('lg', ('^', '&')),
|
||||
('m', ('@', '@')),
|
||||
('lm', ('@', '&'))
|
||||
("r", ("|", "|")),
|
||||
("lr", ("|", "&")),
|
||||
("c", ("~", "~")),
|
||||
("lc", ("~", "&")),
|
||||
("y", ("#", "#")),
|
||||
("ly", ("#", "&")),
|
||||
("g", ("^", "^")),
|
||||
("lg", ("^", "&")),
|
||||
("m", ("@", "@")),
|
||||
("lm", ("@", "&")),
|
||||
]
|
||||
|
||||
|
||||
|
@ -44,15 +44,15 @@ class ByteBuffer(BytesIO):
|
|||
return self
|
||||
|
||||
def encode_short(self, value):
|
||||
self.write(pack('H', value))
|
||||
self.write(pack("H", value))
|
||||
return self
|
||||
|
||||
def encode_int(self, value):
|
||||
self.write(pack('I', value))
|
||||
self.write(pack("I", value))
|
||||
return self
|
||||
|
||||
def encode_long(self, value):
|
||||
self.write(pack('Q', value))
|
||||
self.write(pack("Q", value))
|
||||
return self
|
||||
|
||||
def encode_buffer(self, buffer):
|
||||
|
@ -64,7 +64,7 @@ class ByteBuffer(BytesIO):
|
|||
return self
|
||||
|
||||
def encode_string(self, string):
|
||||
self.write(pack('H', len(string)))
|
||||
self.write(pack("H", len(string)))
|
||||
|
||||
for ch in string:
|
||||
self.write(ch.encode())
|
||||
|
@ -82,7 +82,7 @@ class ByteBuffer(BytesIO):
|
|||
return self
|
||||
|
||||
def encode_hex_string(self, string):
|
||||
string = string.strip(' -')
|
||||
string = string.strip(" -")
|
||||
self.write(bytes.fromhex(string))
|
||||
return self
|
||||
|
||||
|
@ -93,13 +93,13 @@ class ByteBuffer(BytesIO):
|
|||
return bool(self.decode_byte())
|
||||
|
||||
def decode_short(self):
|
||||
return unpack('H', self.read(2))[0]
|
||||
return unpack("H", self.read(2))[0]
|
||||
|
||||
def decode_int(self):
|
||||
return unpack('I', self.read(4))[0]
|
||||
return unpack("I", self.read(4))[0]
|
||||
|
||||
def decode_long(self):
|
||||
return unpack('Q', self.read(8))[0]
|
||||
return unpack("Q", self.read(8))[0]
|
||||
|
||||
def decode_buffer(self, size):
|
||||
return self.read(size)
|
||||
|
@ -131,7 +131,7 @@ class Packet(ByteBuffer):
|
|||
def __init__(self, data=None, op_code=None, raw=False):
|
||||
|
||||
if data == None:
|
||||
data = b''
|
||||
data = b""
|
||||
|
||||
super().__init__(data)
|
||||
|
||||
|
@ -172,7 +172,7 @@ class PacketHandler:
|
|||
def __init__(self, name, callback, **kwargs):
|
||||
self.name = name
|
||||
self.callback = callback
|
||||
self.op_code = kwargs.get('op_code')
|
||||
self.op_code = kwargs.get("op_code")
|
||||
|
||||
|
||||
def packet_handler(op_code=None):
|
|
@ -1,2 +1,2 @@
|
|||
from .context_base import ContextBase
|
||||
from .script_base import ScriptBase
|
||||
from .script_base import ScriptBase
|
|
@ -1,3 +1,3 @@
|
|||
class ContextBase:
|
||||
def __init__(self, script):
|
||||
self._script = script
|
||||
self._script = script
|
|
@ -0,0 +1 @@
|
|||
from .npc_script import NpcScript
|
|
@ -1 +1 @@
|
|||
await ctx.say(f"Npc ID [{ctx.npc_id}] My script is not yet made")
|
||||
await ctx.say(f"Npc ID [{ctx.npc_id}] My script is not yet made")
|
|
@ -1,37 +1,39 @@
|
|||
from dataclasses import dataclass
|
||||
from scripts.context_base import ContextBase
|
||||
from mapy.scripts.context_base import ContextBase
|
||||
|
||||
|
||||
@dataclass
|
||||
class Message:
|
||||
msg: str = ""
|
||||
prev: bool = False
|
||||
nxt: bool = False
|
||||
msg: str = ""
|
||||
prev: bool = False
|
||||
nxt: bool = False
|
||||
|
||||
def encode(self, packet):
|
||||
packet.encode_string(self.msg)
|
||||
packet.encode_byte(self.prev)
|
||||
packet.encode_byte(self.nxt)
|
||||
|
||||
|
||||
class NpcContext(ContextBase):
|
||||
@property
|
||||
def npc_id(self):
|
||||
return self._script.npc_id
|
||||
|
||||
|
||||
async def say(self, msg, prev=False, nxt=False):
|
||||
self._script._prev_msgs.append(Message(msg, prev, nxt))
|
||||
|
||||
|
||||
def action(packet):
|
||||
packet.encode_string(msg)
|
||||
packet.encode_byte(prev)
|
||||
packet.encode_byte(nxt)
|
||||
|
||||
await self._script.send_message(0, action)
|
||||
|
||||
|
||||
async def ask_yes_no(self, msg):
|
||||
def action(packet):
|
||||
packet.encode_string(msg)
|
||||
|
||||
|
||||
await self._script.send_message(2, action)
|
||||
|
||||
|
||||
def end_chat(self):
|
||||
self._script.end_chat()
|
|
@ -1,10 +1,9 @@
|
|||
from asyncio import Queue
|
||||
from os.path import isfile
|
||||
|
||||
from net.packets import CSendOps
|
||||
from net import packets
|
||||
from scripts import ScriptBase
|
||||
from scripts.npc.npc_context import NpcContext
|
||||
from mapy.net import CSendOps, Packet
|
||||
from mapy.scripts.script_base import ScriptBase
|
||||
from mapy.scripts.npc.npc_context import NpcContext
|
||||
|
||||
|
||||
class NpcScript(ScriptBase):
|
||||
|
@ -13,7 +12,7 @@ class NpcScript(ScriptBase):
|
|||
script = f"scripts/npc/default.py"
|
||||
else:
|
||||
script = f"scripts/npc/{npc_id}.py"
|
||||
|
||||
|
||||
super().__init__(script, client)
|
||||
self._npc_id = npc_id
|
||||
self._context = NpcContext(self)
|
||||
|
@ -38,7 +37,7 @@ class NpcScript(ScriptBase):
|
|||
return resp
|
||||
|
||||
async def send_dialogue(self, type_, action, flag, param):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_ScriptMessage)
|
||||
packet = Packet(op_code=CSendOps.LP_ScriptMessage)
|
||||
packet.encode_byte(flag)
|
||||
packet.encode_int(self._npc_id)
|
||||
packet.encode_byte(type_)
|
||||
|
@ -48,14 +47,14 @@ class NpcScript(ScriptBase):
|
|||
|
||||
self._last_msg_type = type_
|
||||
await self._parent.send_packet(packet)
|
||||
|
||||
|
||||
async def reuse_dialogue(self, msg):
|
||||
await self.send_dialogue(0, msg.encode, 4, 0)
|
||||
|
||||
async def proceed_back(self):
|
||||
if self._prev_id == 0:
|
||||
return
|
||||
|
||||
|
||||
self._prev_id -= 1
|
||||
|
||||
await self.reuse_dialogue(self._prev_msgs[self._prev_id])
|
||||
|
@ -65,7 +64,7 @@ class NpcScript(ScriptBase):
|
|||
|
||||
if self._prev_id < len(self._prev_msgs):
|
||||
await self.reuse_dialogue(self._prev_msgs[self._prev_id])
|
||||
|
||||
|
||||
else:
|
||||
await self._response.put(resp)
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
class ScriptBase:
|
||||
def __init__(self, script, client):
|
||||
self._parent = client
|
||||
self._script = compile(
|
||||
"async def ex():\n"
|
||||
+ "".join([f" {line}" for line in open(script, "r").readlines()]),
|
||||
"<string>",
|
||||
"exec",
|
||||
)
|
||||
self._context = None
|
||||
|
||||
async def execute(self):
|
||||
async def run(script, _globals):
|
||||
exec(script, _globals)
|
||||
await _globals["ex"]()
|
||||
|
||||
env = {"ctx": self._context}
|
||||
env.update(globals())
|
||||
await run(self._script, env)
|
||||
|
||||
self._parent.npc_script = None
|
||||
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self._parent
|
|
@ -0,0 +1,7 @@
|
|||
__all__ = "WvsCenter", "WvsLogin", "WvsGame", "World", "ServerBase"
|
||||
|
||||
from .server_base import ServerBase
|
||||
from .wvs_center import WvsCenter
|
||||
from .wvs_login import WvsLogin
|
||||
from .wvs_game import WvsGame
|
||||
from .world import World
|
|
@ -1,15 +1,43 @@
|
|||
from asyncio import Event, get_event_loop, get_running_loop, run_coroutine_threadsafe
|
||||
from socket import AF_INET, IPPROTO_TCP, SOCK_STREAM, TCP_NODELAY, socket
|
||||
|
||||
from net.client import ClientSocket
|
||||
from net.packets.packet import PacketHandler
|
||||
from net.server import Dispatcher
|
||||
from utils import log
|
||||
from mapy.client.client_base import ClientSocket
|
||||
from mapy.net.packet import PacketHandler
|
||||
from mapy import log
|
||||
|
||||
|
||||
class Dispatcher:
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
|
||||
def push(self, client, packet):
|
||||
log.packet(
|
||||
f"{self.parent.name} {packet.name} {client.ip} {packet.to_string()}", "in"
|
||||
)
|
||||
|
||||
try:
|
||||
coro = None
|
||||
|
||||
for packet_handler in self.parent._packet_handlers:
|
||||
if packet_handler.op_code == packet.op_code:
|
||||
coro = packet_handler.callback
|
||||
break
|
||||
|
||||
if not coro:
|
||||
raise AttributeError
|
||||
|
||||
except AttributeError:
|
||||
log.warning(f"{self.parent.name} Unhandled event in : <w>{packet.name}</w>")
|
||||
|
||||
else:
|
||||
self.parent._loop.create_task(self._run_event(coro, client, packet))
|
||||
|
||||
async def _run_event(self, coro, *args):
|
||||
await coro(self.parent, *args)
|
||||
|
||||
|
||||
class ServerBase:
|
||||
"""Server base for center, channel, and login servers
|
||||
"""
|
||||
"""Server base for center, channel, and login servers"""
|
||||
|
||||
def __init__(self, parent, port):
|
||||
self._loop = get_event_loop()
|
||||
|
@ -18,8 +46,8 @@ class ServerBase:
|
|||
self._is_alive = False
|
||||
self._clients = []
|
||||
self._packet_handlers = []
|
||||
self._ready = Event(loop=self._loop)
|
||||
self._alive = Event(loop=self._loop)
|
||||
self._ready = Event()
|
||||
self._alive = Event()
|
||||
self._dispatcher = Dispatcher(self)
|
||||
|
||||
self._serv_sock = socket(AF_INET, SOCK_STREAM)
|
||||
|
@ -49,7 +77,7 @@ class ServerBase:
|
|||
|
||||
async def on_client_accepted(self, socket):
|
||||
client_sock = ClientSocket(socket)
|
||||
client = await getattr(self, 'client_connect')(client_sock)
|
||||
client = await getattr(self, "client_connect")(client_sock)
|
||||
self.log(f"{self.name} Accepted <lg>{client.ip}</lg>")
|
||||
|
||||
self._clients.append(client)
|
||||
|
@ -69,13 +97,14 @@ class ServerBase:
|
|||
for _, member in members:
|
||||
# Register all packet handlers for inheriting server
|
||||
|
||||
if (isinstance(member, PacketHandler)
|
||||
and member not in self._packet_handlers):
|
||||
if (
|
||||
isinstance(member, PacketHandler)
|
||||
and member not in self._packet_handlers
|
||||
):
|
||||
self._packet_handlers.append(member)
|
||||
|
||||
async def wait_until_ready(self) -> bool:
|
||||
"""Block event loop until the GameServer has started listening for clients.
|
||||
"""
|
||||
"""Block event loop until the GameServer has started listening for clients."""
|
||||
return await self._ready.wait()
|
||||
|
||||
async def listen(self):
|
|
@ -1,10 +1,10 @@
|
|||
from common import constants
|
||||
from common.enum import WorldFlag, Worlds
|
||||
from mapy import constants
|
||||
from mapy.common.enum import WorldFlag, Worlds
|
||||
|
||||
|
||||
class World:
|
||||
def __init__(self, id):
|
||||
self._world = Worlds(id)
|
||||
self._shop_port = 9000
|
||||
self._channels = []
|
||||
self._flag = WorldFlag.New
|
||||
self._allow_multi_leveling = constants.ALLOW_MULTI_LEVELING
|
|
@ -3,17 +3,19 @@ from asyncio import get_event_loop
|
|||
from configparser import ConfigParser
|
||||
from time import time
|
||||
|
||||
from common.constants import (CHANNEL_COUNT, GAME_PORT)
|
||||
from common.enum import Worlds
|
||||
from db import DatabaseClient
|
||||
from http_api import HTTPServer
|
||||
from utils import log
|
||||
from utils.tools import wakeup
|
||||
from mapy import log
|
||||
from mapy.common.constants import CHANNEL_COUNT, GAME_PORT
|
||||
from mapy.common.enum import Worlds
|
||||
from mapy.db import DatabaseClient
|
||||
from mapy.http_api import HTTPServer
|
||||
from mapy.utils import wakeup
|
||||
|
||||
from . import World, WvsGame, WvsLogin, WvsShop
|
||||
from .world import World
|
||||
from .wvs_game import WvsGame
|
||||
from .wvs_login import WvsLogin
|
||||
|
||||
|
||||
class ServerApp:
|
||||
class WvsCenter:
|
||||
_name = "Server Core"
|
||||
|
||||
"""Server connection listener for incoming client socket connections
|
||||
|
@ -64,50 +66,55 @@ class ServerApp:
|
|||
return self._worlds
|
||||
|
||||
def log(self, message, level=None):
|
||||
level = level or 'info'
|
||||
level = level or "info"
|
||||
getattr(log, level)(f"{self._name} {message}")
|
||||
|
||||
def _load_config(self):
|
||||
self._config = ConfigParser()
|
||||
self._config.read('config.ini')
|
||||
self._config.read("config.ini")
|
||||
if len(self._config.sections()) < 1:
|
||||
self._make_config()
|
||||
|
||||
def _make_config(self):
|
||||
self.log("Please setup the database and other configuration")
|
||||
self._config.add_section('database')
|
||||
self._config['database']['user'] = input("DB User: ")
|
||||
self._config['database']['password'] = input("DB Password: ")
|
||||
self._config['database']['host'] = input("DB Host: ")
|
||||
self._config['database']['port'] = input("DB Port: ")
|
||||
self._config['database']['database'] = input("DB Name: ")
|
||||
self._config.add_section("database")
|
||||
self._config["database"]["user"] = input("DB User: ")
|
||||
self._config["database"]["password"] = input("DB Password: ")
|
||||
self._config["database"]["host"] = input("DB Host: ")
|
||||
self._config["database"]["port"] = input("DB Port: ")
|
||||
self._config["database"]["database"] = input("DB Name: ")
|
||||
|
||||
self._config.add_section('worlds')
|
||||
self._config.add_section("worlds")
|
||||
|
||||
use_worlds = input("Setup worlds? (y/n) [Defaults will be used otherwise] ")
|
||||
if use_worlds.lower() in ['y', 'yes']:
|
||||
if use_worlds.lower() in ["y", "yes"]:
|
||||
world_num = int(input("Number of worlds: [Max 20] "))
|
||||
for i in range(world_num):
|
||||
name = Worlds(i).name
|
||||
self.log(f"Setting up {Worlds(i).name}...")
|
||||
|
||||
self._config['worlds'][name] = 'active'
|
||||
self._config["worlds"][name] = "active"
|
||||
self._config[name] = {}
|
||||
self._config[name]['channels'] = input("Channels: ")
|
||||
self._config[name]['exp_rate'] = input("Exp Rate: ")
|
||||
self._config[name]['drop_rate'] = input("Drop Rate: ")
|
||||
self._config[name]['meso_rate'] = input("Meso Rate: ")
|
||||
self._config[name]["channels"] = input("Channels: ")
|
||||
self._config[name]["exp_rate"] = input("Exp Rate: ")
|
||||
self._config[name]["drop_rate"] = input("Drop Rate: ")
|
||||
self._config[name]["meso_rate"] = input("Meso Rate: ")
|
||||
|
||||
else:
|
||||
self._config['worlds']['Scania'] = 'active'
|
||||
self._config['Scania'] = {'channels': 3, 'exp_rate': 1.0, 'drop_rate': 1.0, 'meso_rate': 1.0}
|
||||
self._config["worlds"]["Scania"] = "active"
|
||||
self._config["Scania"] = {
|
||||
"channels": 3,
|
||||
"exp_rate": 1.0,
|
||||
"drop_rate": 1.0,
|
||||
"meso_rate": 1.0,
|
||||
}
|
||||
|
||||
with open("config.ini", "w") as config:
|
||||
self._config.write(config)
|
||||
|
||||
@classmethod
|
||||
def run(cls):
|
||||
self = ServerApp()
|
||||
self = WvsCenter()
|
||||
self._http_api.run()
|
||||
loop = self._loop
|
||||
|
||||
|
@ -139,14 +146,14 @@ class ServerApp:
|
|||
self._start_time = int(time())
|
||||
self.log("Initializing Server", "debug")
|
||||
|
||||
self.data = DatabaseClient(loop=self._loop, **self._config['database'])
|
||||
self.data = DatabaseClient(loop=self._loop, **self._config["database"])
|
||||
await self.data.start()
|
||||
|
||||
channel_port = GAME_PORT
|
||||
self.login = await WvsLogin.run(self)
|
||||
|
||||
for world in self._config['worlds']:
|
||||
if self._config['worlds'][world] != 'active':
|
||||
for world in self._config["worlds"]:
|
||||
if self._config["worlds"][world] != "active":
|
||||
continue
|
||||
|
||||
world_id = Worlds[world.title()].value
|
|
@ -1,18 +1,18 @@
|
|||
from .server_base import ServerBase
|
||||
from mapy.client import WvsGameClient
|
||||
from mapy.common import Worlds
|
||||
from mapy.common.constants import ANTIREPEAT_BUFFS, is_event_vehicle_skill
|
||||
from mapy.net import CRecvOps
|
||||
from mapy.net.packet import packet_handler
|
||||
from mapy.scripts.npc import NpcScript
|
||||
from mapy.utils.cpacket import CPacket
|
||||
|
||||
from client import WvsGameClient
|
||||
from common.enum import Worlds
|
||||
from common.constants import ANTIREPEAT_BUFFS, is_event_vehicle_skill
|
||||
from net.packets import crypto, Packet, CRecvOps
|
||||
from net.packets.packet import packet_handler
|
||||
from scripts.npc import NpcScript
|
||||
from utils import CPacket, log
|
||||
from .server_base import ServerBase
|
||||
|
||||
|
||||
class WvsGame(ServerBase):
|
||||
def __init__(self, parent, port, world_id, channel_id):
|
||||
super().__init__(parent, port)
|
||||
self._name = f'Game Server[{world_id}][{channel_id}]'
|
||||
self._name = f"Game Server[{world_id}][{channel_id}]"
|
||||
self._field_manager = {}
|
||||
self._allow_multi_leveling = False
|
||||
|
||||
|
@ -20,9 +20,9 @@ class WvsGame(ServerBase):
|
|||
self.world_name = Worlds(world_id).name
|
||||
self.channel_id = channel_id
|
||||
self.ticker_message = "In game wow"
|
||||
self.meso_rate = int(parent._config[self.world_name]['meso_rate'])
|
||||
self.drop_rate = int(parent._config[self.world_name]['drop_rate'])
|
||||
self.exp_rate = int(parent._config[self.world_name]['exp_rate'])
|
||||
self.meso_rate = int(parent._config[self.world_name]["meso_rate"])
|
||||
self.drop_rate = int(parent._config[self.world_name]["drop_rate"])
|
||||
self.exp_rate = int(parent._config[self.world_name]["exp_rate"])
|
||||
self.quest_exp_rate = 1
|
||||
self.party_quest_exp_rate = 1
|
||||
|
||||
|
@ -79,18 +79,12 @@ class WvsGame(ServerBase):
|
|||
login_req.character._client = client
|
||||
|
||||
client.character = await self.data.characters.load(uid, client)
|
||||
await (
|
||||
await self.get_field(client.character.field_id)
|
||||
).add(client)
|
||||
await (await self.get_field(client.character.field_id)).add(client)
|
||||
|
||||
await client.send_packet(
|
||||
CPacket.claim_svr_changed(True))
|
||||
await client.send_packet(
|
||||
CPacket.set_gender(client.character.stats.gender))
|
||||
await client.send_packet(
|
||||
CPacket.func_keys_init(client.character.func_keys))
|
||||
await client.send_packet(
|
||||
CPacket.broadcast_server_msg(self.ticker_message))
|
||||
await client.send_packet(CPacket.claim_svr_changed(True))
|
||||
await client.send_packet(CPacket.set_gender(client.character.stats.gender))
|
||||
await client.send_packet(CPacket.func_keys_init(client.character.func_keys))
|
||||
await client.send_packet(CPacket.broadcast_server_msg(self.ticker_message))
|
||||
|
||||
@packet_handler(CRecvOps.CP_UserMove)
|
||||
async def handle_user_move(self, client, packet):
|
||||
|
@ -103,8 +97,7 @@ class WvsGame(ServerBase):
|
|||
|
||||
move_path = packet.decode_buffer(-1)
|
||||
client.character.position.decode_move_path(move_path)
|
||||
await client.broadcast(
|
||||
CPacket.user_movement(client.character.id, move_path))
|
||||
await client.broadcast(CPacket.user_movement(client.character.id, move_path))
|
||||
|
||||
@packet_handler(CRecvOps.CP_UserSkillUseRequest)
|
||||
async def handle_skill_use_request(self, client, packet):
|
||||
|
@ -134,12 +127,14 @@ class WvsGame(ServerBase):
|
|||
|
||||
if casted:
|
||||
client.broadcast(
|
||||
CPacket.effect_remote(client.character.obj_id,
|
||||
1,
|
||||
skill_id,
|
||||
client.character.stats.level,
|
||||
1)
|
||||
CPacket.effect_remote(
|
||||
client.character.obj_id,
|
||||
1,
|
||||
skill_id,
|
||||
client.character.stats.level,
|
||||
1,
|
||||
)
|
||||
)
|
||||
|
||||
@packet_handler(CRecvOps.CP_UserSelectNpc)
|
||||
async def handle_user_select_npc(self, client, packet):
|
||||
|
@ -148,7 +143,7 @@ class WvsGame(ServerBase):
|
|||
packet.decode_short() # y
|
||||
|
||||
if client.npc_script:
|
||||
pass # client has npc script already?
|
||||
... # client has npc script already?
|
||||
|
||||
npc = client.character.field.npcs.get(obj_id)
|
||||
|
||||
|
@ -165,8 +160,8 @@ class WvsGame(ServerBase):
|
|||
|
||||
if type_ != type_expected:
|
||||
self.log(
|
||||
f"User answered type: [{type_}], expected [{type_expected}]"
|
||||
"debug")
|
||||
f"User answered type: [{type_}], expected [{type_expected}]" "debug"
|
||||
)
|
||||
return
|
||||
|
||||
resp = packet.decode_byte()
|
||||
|
@ -182,8 +177,8 @@ class WvsGame(ServerBase):
|
|||
|
||||
@packet_handler(CRecvOps.CP_UpdateGMBoard)
|
||||
async def handle_update_gm_board(self, client, packets):
|
||||
pass
|
||||
...
|
||||
|
||||
@packet_handler(CRecvOps.CP_RequireFieldObstacleStatus)
|
||||
async def handle_require_field_obstacle(self, client, packets):
|
||||
pass
|
||||
...
|
|
@ -1,20 +1,22 @@
|
|||
import asyncio
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from client import WvsLoginClient
|
||||
from character import Account, Character
|
||||
from character.inventory import InventoryType
|
||||
from common.constants import (
|
||||
AUTO_LOGIN, AUTO_REGISTER, CENTER_PORT, HOST_IP, LOGIN_PORT,
|
||||
MAX_CHARACTERS, REQUEST_PIC, REQUEST_PIN, REQUIRE_STAFF_IP, WORLD_COUNT,
|
||||
get_job_from_creation)
|
||||
from db import DatabaseClient
|
||||
from net.packets import Packet
|
||||
from net.packets.opcodes import CRecvOps, CSendOps
|
||||
from net.packets.packet import packet_handler
|
||||
from server.server_base import ServerBase
|
||||
from utils.cpacket import CPacket
|
||||
from mapy.client import WvsLoginClient
|
||||
from mapy.character import Character
|
||||
from mapy.common.constants import (
|
||||
AUTO_LOGIN,
|
||||
AUTO_REGISTER,
|
||||
LOGIN_PORT,
|
||||
MAX_CHARACTERS,
|
||||
REQUEST_PIC,
|
||||
REQUEST_PIN,
|
||||
REQUIRE_STAFF_IP,
|
||||
get_job_from_creation,
|
||||
)
|
||||
from mapy.net import Packet, CRecvOps
|
||||
from mapy.net.packet import packet_handler
|
||||
from mapy.server.server_base import ServerBase
|
||||
from mapy.utils.cpacket import CPacket
|
||||
|
||||
|
||||
class PendingLogin:
|
||||
|
@ -68,10 +70,10 @@ class WvsLogin(ServerBase):
|
|||
|
||||
if response == 0:
|
||||
return await client.send_packet(
|
||||
CPacket.check_password_result(client, response))
|
||||
CPacket.check_password_result(client, response)
|
||||
)
|
||||
|
||||
await client.send_packet(
|
||||
CPacket.check_password_result(response=response))
|
||||
await client.send_packet(CPacket.check_password_result(response=response))
|
||||
|
||||
async def send_world_information(self, client) -> None:
|
||||
for world in self._worlds:
|
||||
|
@ -116,8 +118,7 @@ class WvsLogin(ServerBase):
|
|||
name = packet.decode_string()
|
||||
exists = await self.data.characters.get(name=name) is not None
|
||||
|
||||
await client.send_packet(
|
||||
CPacket.check_duplicated_id_result(name, exists))
|
||||
await client.send_packet(CPacket.check_duplicated_id_result(name, exists))
|
||||
|
||||
@packet_handler(CRecvOps.CP_ViewAllChar)
|
||||
async def view_all_characters(self, client, packet):
|
||||
|
@ -125,12 +126,10 @@ class WvsLogin(ServerBase):
|
|||
packet.decode_byte() # game_start_mode
|
||||
|
||||
await asyncio.sleep(2)
|
||||
await client.send_packet(
|
||||
CPacket.start_view_all_characters(client.avatars))
|
||||
await client.send_packet(CPacket.start_view_all_characters(client.avatars))
|
||||
|
||||
for world in self._worlds:
|
||||
await client.send_packet(
|
||||
CPacket.view_all_characters(world, client.avatars))
|
||||
await client.send_packet(CPacket.view_all_characters(world, client.avatars))
|
||||
|
||||
@packet_handler(CRecvOps.CP_CreateNewCharacter)
|
||||
async def create_new_character(self, client, packet):
|
||||
|
@ -156,18 +155,19 @@ class WvsLogin(ServerBase):
|
|||
|
||||
character.gender = packet.decode_byte()
|
||||
|
||||
character_id = await self.data.account(id=client.account.id)\
|
||||
.create_character(character)
|
||||
character_id = await self.data.account(id=client.account.id).create_character(
|
||||
character
|
||||
)
|
||||
|
||||
if character_id:
|
||||
character.id = character_id
|
||||
client.avatars.append(character)
|
||||
|
||||
return await client.send_packet(
|
||||
CPacket.create_new_character(character, False))
|
||||
CPacket.create_new_character(character, False)
|
||||
)
|
||||
|
||||
return await client.send_packet(
|
||||
CPacket.create_new_character(character, True))
|
||||
return await client.send_packet(CPacket.create_new_character(character, True))
|
||||
|
||||
@packet_handler(CRecvOps.CP_SelectCharacter)
|
||||
async def select_character(self, client, packet):
|
||||
|
@ -176,7 +176,7 @@ class WvsLogin(ServerBase):
|
|||
port = self.parent.worlds[client.world_id][client.channel_id].port
|
||||
|
||||
self.parent._pending_logins.append(
|
||||
PendingLogin(character, client.account, datetime.now()))
|
||||
PendingLogin(character, client.account, datetime.now())
|
||||
)
|
||||
|
||||
await client.send_packet(
|
||||
CPacket.select_character_result(uid, port))
|
||||
await client.send_packet(CPacket.select_character_result(uid, port))
|
|
@ -0,0 +1,6 @@
|
|||
from mapy.server.server_base import ServerBase
|
||||
|
||||
|
||||
class WvsShop(ServerBase):
|
||||
def __init__(self):
|
||||
pass
|
|
@ -0,0 +1,23 @@
|
|||
__all__ = (
|
||||
"get",
|
||||
"to_string",
|
||||
"wakeup",
|
||||
"Manager",
|
||||
"first_or_default",
|
||||
"filter_out_to",
|
||||
"fix_dict_keys",
|
||||
"Random",
|
||||
"TagPoint",
|
||||
)
|
||||
|
||||
from .tools import (
|
||||
Manager,
|
||||
first_or_default,
|
||||
wakeup,
|
||||
filter_out_to,
|
||||
fix_dict_keys,
|
||||
get,
|
||||
to_string,
|
||||
)
|
||||
from .random import Random
|
||||
from .tag_point import TagPoint
|
|
@ -1,13 +1,11 @@
|
|||
from common import constants
|
||||
from net import packets
|
||||
from net.packets import CSendOps
|
||||
from .tools import get
|
||||
from mapy import constants
|
||||
from mapy.net import Packet, CSendOps
|
||||
|
||||
|
||||
class CPacket:
|
||||
@staticmethod
|
||||
def check_password_result(client=None, response=None):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_CheckPasswordResult)
|
||||
packet = Packet(op_code=CSendOps.LP_CheckPasswordResult)
|
||||
|
||||
if response != 0:
|
||||
packet.encode_int(response)
|
||||
|
@ -40,7 +38,7 @@ class CPacket:
|
|||
@staticmethod
|
||||
def world_information(world):
|
||||
|
||||
packet = packets.Packet(op_code=CSendOps.LP_WorldInformation)
|
||||
packet = Packet(op_code=CSendOps.LP_WorldInformation)
|
||||
packet.encode_byte(world.id)
|
||||
packet.encode_string(world.name)
|
||||
packet.encode_byte(2) # 0 : Normal 1 : Event 2 : New 3 : Hot
|
||||
|
@ -64,20 +62,20 @@ class CPacket:
|
|||
|
||||
@staticmethod
|
||||
def end_world_information():
|
||||
packet = packets.Packet(op_code=CSendOps.LP_WorldInformation)
|
||||
packet = Packet(op_code=CSendOps.LP_WorldInformation)
|
||||
packet.encode_byte(0xFF)
|
||||
return packet
|
||||
|
||||
@staticmethod
|
||||
def last_connected_world(world_id):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_LatestConnectedWorld)
|
||||
packet = Packet(op_code=CSendOps.LP_LatestConnectedWorld)
|
||||
# default: WorldID, 253: None, 255: Recommended World
|
||||
packet.encode_int(world_id)
|
||||
return packet
|
||||
|
||||
@staticmethod
|
||||
def send_recommended_world(worlds):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_RecommendWorldMessage)
|
||||
packet = Packet(op_code=CSendOps.LP_RecommendWorldMessage)
|
||||
packet.encode_byte(len(worlds))
|
||||
|
||||
for world in worlds:
|
||||
|
@ -88,7 +86,7 @@ class CPacket:
|
|||
|
||||
@staticmethod
|
||||
def check_user_limit(status):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_CheckUserLimitResult)
|
||||
packet = Packet(op_code=CSendOps.LP_CheckUserLimitResult)
|
||||
|
||||
# 0: Open 1: Over user limit
|
||||
packet.encode_byte(0)
|
||||
|
@ -98,7 +96,7 @@ class CPacket:
|
|||
|
||||
@staticmethod
|
||||
def world_result(entries):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_SelectWorldResult)
|
||||
packet = Packet(op_code=CSendOps.LP_SelectWorldResult)
|
||||
|
||||
packet.encode_byte(0)
|
||||
packet.encode_byte(len(entries))
|
||||
|
@ -114,19 +112,19 @@ class CPacket:
|
|||
|
||||
@staticmethod
|
||||
def check_duplicated_id_result(name, is_available):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_CheckDuplicatedIDResult)
|
||||
packet = Packet(op_code=CSendOps.LP_CheckDuplicatedIDResult)
|
||||
packet.encode_string(name)
|
||||
packet.encode_byte(is_available)
|
||||
return packet
|
||||
|
||||
@staticmethod
|
||||
def extra_char_info(character):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_CheckExtraCharInfoResult)
|
||||
packet = Packet(op_code=CSendOps.LP_CheckExtraCharInfoResult)
|
||||
return packet
|
||||
|
||||
@staticmethod
|
||||
def start_view_all_characters(characters):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_ViewAllCharResult)
|
||||
packet = Packet(op_code=CSendOps.LP_ViewAllCharResult)
|
||||
packet.encode_byte(1)
|
||||
packet.encode_int(2)
|
||||
packet.encode_int(len(characters))
|
||||
|
@ -134,16 +132,13 @@ class CPacket:
|
|||
|
||||
@staticmethod
|
||||
def view_all_characters(world, characters):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_ViewAllCharResult)
|
||||
packet = Packet(op_code=CSendOps.LP_ViewAllCharResult)
|
||||
|
||||
packet.encode_byte(0)
|
||||
packet.encode_byte(world.id)
|
||||
|
||||
characters = list(
|
||||
filter(
|
||||
lambda character: character.world_id == world.id,
|
||||
characters
|
||||
)
|
||||
filter(lambda character: character.world_id == world.id, characters)
|
||||
)
|
||||
|
||||
packet.encode_byte(len(characters))
|
||||
|
@ -159,7 +154,7 @@ class CPacket:
|
|||
|
||||
@staticmethod
|
||||
def create_new_character(character, response: bool):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_CreateNewCharacterResult)
|
||||
packet = Packet(op_code=CSendOps.LP_CreateNewCharacterResult)
|
||||
packet.encode_byte(response)
|
||||
|
||||
if not response:
|
||||
|
@ -169,10 +164,10 @@ class CPacket:
|
|||
|
||||
@staticmethod
|
||||
def select_character_result(uid, port):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_SelectCharacterResult)
|
||||
packet = Packet(op_code=CSendOps.LP_SelectCharacterResult)
|
||||
|
||||
packet.encode_byte(0) # world
|
||||
packet.encode_byte(0) # selected char
|
||||
packet.encode_byte(0) # world
|
||||
packet.encode_byte(0) # selected char
|
||||
|
||||
packet.encode_buffer(constants.SERVER_ADDRESS)
|
||||
packet.encode_short(port)
|
||||
|
@ -186,7 +181,7 @@ class CPacket:
|
|||
|
||||
@staticmethod
|
||||
def set_field(character, character_data, channel: int):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_SetField)
|
||||
packet = Packet(op_code=CSendOps.LP_SetField)
|
||||
# CPacket.cclient_opt_man__encode_opt(packet, 0)
|
||||
packet.encode_short(0)
|
||||
|
||||
|
@ -208,39 +203,39 @@ class CPacket:
|
|||
packet.encode_int(0)
|
||||
packet.encode_int(0)
|
||||
packet.encode_int(0)
|
||||
|
||||
|
||||
else:
|
||||
packet.encode_byte(0)
|
||||
packet.encode_int(character.field_id)
|
||||
packet.encode_byte(character.stats.portal)
|
||||
packet.encode_int(character.stats.hp)
|
||||
packet.encode_byte(0)
|
||||
|
||||
|
||||
packet.encode_long(150842304000000000)
|
||||
|
||||
return packet
|
||||
|
||||
@staticmethod
|
||||
def func_keys_init(keys):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_FuncKeyMappedInit)
|
||||
packet = Packet(op_code=CSendOps.LP_FuncKeyMappedInit)
|
||||
packet.encode_byte(0)
|
||||
|
||||
for i in range(90):
|
||||
key = keys[i]
|
||||
packet.encode_byte(getattr(key, 'type', 0))
|
||||
packet.encode_int(getattr(key, 'action', 0))
|
||||
|
||||
packet.encode_byte(getattr(key, "type", 0))
|
||||
packet.encode_int(getattr(key, "action", 0))
|
||||
|
||||
return packet
|
||||
|
||||
@staticmethod
|
||||
def set_gender(gender):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_SetGender)
|
||||
packet = Packet(op_code=CSendOps.LP_SetGender)
|
||||
packet.encode_byte(gender)
|
||||
return packet
|
||||
|
||||
@staticmethod
|
||||
def stat_changed(modifier=None, excl_req=False):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_StatChanged)
|
||||
packet = Packet(op_code=CSendOps.LP_StatChanged)
|
||||
packet.encode_byte(excl_req)
|
||||
if modifier:
|
||||
modifier.encode(packet)
|
||||
|
@ -257,7 +252,7 @@ class CPacket:
|
|||
|
||||
@staticmethod
|
||||
def claim_svr_changed(claim_svr_con: bool):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_ClaimSvrStatusChanged)
|
||||
packet = Packet(op_code=CSendOps.LP_ClaimSvrStatusChanged)
|
||||
packet.encode_byte(claim_svr_con)
|
||||
return packet
|
||||
|
||||
|
@ -265,93 +260,90 @@ class CPacket:
|
|||
|
||||
@staticmethod
|
||||
def user_enter_field(character):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_UserEnterField)
|
||||
packet = Packet(op_code=CSendOps.LP_UserEnterField)
|
||||
packet.encode_int(character.id)
|
||||
|
||||
packet.encode_byte(character.stats.level)
|
||||
packet.encode_string(character.stats.name)
|
||||
|
||||
|
||||
packet.skip(8)
|
||||
|
||||
packet.encode_long(0)\
|
||||
.encode_long(0)\
|
||||
.encode_byte(0)\
|
||||
.encode_byte(0)
|
||||
|
||||
packet.encode_long(0).encode_long(0).encode_byte(0).encode_byte(0)
|
||||
|
||||
packet.encode_short(character.stats.job)
|
||||
character.encode_look(packet)
|
||||
|
||||
packet.encode_int(0) # driver ID
|
||||
packet.encode_int(0) # passenger ID
|
||||
packet.encode_int(0) # choco count
|
||||
packet.encode_int(0) # active effeect item ID
|
||||
packet.encode_int(0) # completed set item ID
|
||||
packet.encode_int(0) # portable chair ID
|
||||
packet.encode_int(0) # driver ID
|
||||
packet.encode_int(0) # passenger ID
|
||||
packet.encode_int(0) # choco count
|
||||
packet.encode_int(0) # active effeect item ID
|
||||
packet.encode_int(0) # completed set item ID
|
||||
packet.encode_int(0) # portable chair ID
|
||||
|
||||
packet.encode_short(0) # private?
|
||||
packet.encode_short(0) # private?
|
||||
|
||||
packet.encode_short(0)
|
||||
packet.encode_byte(character.position.stance)
|
||||
packet.encode_short(character.position.foothold)
|
||||
packet.encode_byte(0) # show admin effect
|
||||
packet.encode_byte(0) # show admin effect
|
||||
|
||||
packet.encode_byte(0) # pets?
|
||||
packet.encode_byte(0) # pets?
|
||||
|
||||
packet.encode_int(0) # taming mob level
|
||||
packet.encode_int(0) # taming mob exp
|
||||
packet.encode_int(0) # taming mob fatigue
|
||||
packet.encode_int(0) # taming mob level
|
||||
packet.encode_int(0) # taming mob exp
|
||||
packet.encode_int(0) # taming mob fatigue
|
||||
|
||||
packet.encode_byte(0) # mini room type
|
||||
packet.encode_byte(0) # mini room type
|
||||
|
||||
packet.encode_byte(0) # ad board remote
|
||||
packet.encode_byte(0) # on couple record add
|
||||
packet.encode_byte(0) # on friend record add
|
||||
packet.encode_byte(0) # on marriage record add
|
||||
packet.encode_byte(0) # ad board remote
|
||||
packet.encode_byte(0) # on couple record add
|
||||
packet.encode_byte(0) # on friend record add
|
||||
packet.encode_byte(0) # on marriage record add
|
||||
|
||||
packet.encode_byte(0) # some sort of effect bit flag
|
||||
packet.encode_byte(0) # some sort of effect bit flag
|
||||
|
||||
packet.encode_byte(0) # new year card record add
|
||||
packet.encode_int(0) # phase
|
||||
packet.encode_byte(0) # new year card record add
|
||||
packet.encode_int(0) # phase
|
||||
return packet
|
||||
|
||||
@staticmethod
|
||||
def user_leave_field(character):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_UserLeaveField)
|
||||
packet = Packet(op_code=CSendOps.LP_UserLeaveField)
|
||||
packet.encode_int(character.id)
|
||||
return packet
|
||||
|
||||
@staticmethod
|
||||
def user_movement(uid, move_path):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_UserMove)
|
||||
packet = Packet(op_code=CSendOps.LP_UserMove)
|
||||
packet.encode_int(uid)
|
||||
packet.encode_buffer(move_path)
|
||||
return packet
|
||||
|
||||
|
||||
# --------------------- Mob Pool ------------------- #
|
||||
|
||||
@staticmethod
|
||||
def mob_enter_field(mob):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_MobEnterField)
|
||||
packet = Packet(op_code=CSendOps.LP_MobEnterField)
|
||||
mob.encode_init(packet)
|
||||
return packet
|
||||
|
||||
@staticmethod
|
||||
def mob_change_controller(mob, level):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_MobChangeController)
|
||||
packet = Packet(op_code=CSendOps.LP_MobChangeController)
|
||||
packet.encode_byte(level)
|
||||
|
||||
if level == 0:
|
||||
packet.encode_int(mob.obj_id)
|
||||
else:
|
||||
mob.encode_init(packet)
|
||||
|
||||
|
||||
return packet
|
||||
|
||||
# --------------------- Npc Pool ----------------------#
|
||||
|
||||
@staticmethod
|
||||
def npc_enter_field(npc):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_NpcEnterField)
|
||||
packet = Packet(op_code=CSendOps.LP_NpcEnterField)
|
||||
packet.encode_int(npc.obj_id)
|
||||
packet.encode_int(npc.life_id)
|
||||
|
||||
|
@ -369,7 +361,7 @@ class CPacket:
|
|||
|
||||
@staticmethod
|
||||
def npc_script_message(npc, msg_type, msg, end_bytes, type_, other_npc):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_ScriptMessage)
|
||||
packet = Packet(op_code=CSendOps.LP_ScriptMessage)
|
||||
|
||||
packet.encode_byte(4)
|
||||
packet.encode_int(npc)
|
||||
|
@ -378,12 +370,12 @@ class CPacket:
|
|||
|
||||
if type_ in [4, 5]:
|
||||
packet.encode_int(other_npc)
|
||||
|
||||
|
||||
packet.encode_string(msg)
|
||||
|
||||
if end_bytes:
|
||||
packet.encode(bytes(end_bytes))
|
||||
|
||||
|
||||
return packet
|
||||
|
||||
@staticmethod
|
||||
|
@ -392,11 +384,11 @@ class CPacket:
|
|||
|
||||
@staticmethod
|
||||
def broadcast_msg(type_, msg):
|
||||
packet = packets.Packet(op_code=CSendOps.LP_BroadcastMsg)
|
||||
packet = Packet(op_code=CSendOps.LP_BroadcastMsg)
|
||||
packet.encode_byte(type_)
|
||||
|
||||
if type_ == 4:
|
||||
packet.encode_byte(True)
|
||||
|
||||
|
||||
packet.encode_string(msg)
|
||||
return packet
|
||||
return packet
|
|
@ -0,0 +1,13 @@
|
|||
from random import randint
|
||||
|
||||
|
||||
class Random:
|
||||
def __init__(self):
|
||||
self.seed_1 = randint(1, 2 ** 31 - 1)
|
||||
self.seed_2 = randint(1, 2 ** 31 - 1)
|
||||
self.seed_3 = randint(1, 2 ** 31 - 1)
|
||||
|
||||
def encode(self, packet):
|
||||
packet.encode_int(self.seed_1)
|
||||
packet.encode_int(self.seed_2)
|
||||
packet.encode_int(self.seed_3)
|
|
@ -2,6 +2,6 @@ class TagPoint:
|
|||
def __init__(self, x=0, y=0):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.x},{self.y}"
|
||||
return f"{self.x},{self.y}"
|
|
@ -1,6 +1,19 @@
|
|||
from asyncio import sleep
|
||||
|
||||
from dataclasses import dataclass, is_dataclass
|
||||
from random import randint
|
||||
|
||||
|
||||
class Random:
|
||||
def __init__(self):
|
||||
self.seed_1 = randint(1, 2 ** 31 - 1)
|
||||
self.seed_2 = randint(1, 2 ** 31 - 1)
|
||||
self.seed_3 = randint(1, 2 ** 31 - 1)
|
||||
|
||||
def encode(self, packet):
|
||||
packet.encode_int(self.seed_1)
|
||||
packet.encode_int(self.seed_2)
|
||||
packet.encode_int(self.seed_3)
|
||||
|
||||
|
||||
def find(predicate, seq):
|
||||
|
@ -9,10 +22,11 @@ def find(predicate, seq):
|
|||
return element
|
||||
return None
|
||||
|
||||
|
||||
def get(iterable, **attrs):
|
||||
def predicate(elem):
|
||||
for attr, val in attrs.items():
|
||||
nested = attr.split('__')
|
||||
nested = attr.split("__")
|
||||
obj = elem
|
||||
for attribute in nested:
|
||||
obj = getattr(obj, attribute)
|
||||
|
@ -58,13 +72,14 @@ def fix_dict_keys(dict_):
|
|||
|
||||
|
||||
def to_string(bytes_):
|
||||
return ' '.join(
|
||||
[bytes_.hex()[i:i+2].upper() for i in range(0, len(bytes_.hex()), 2)])
|
||||
return " ".join(
|
||||
[bytes_.hex()[i : i + 2].upper() for i in range(0, len(bytes_.hex()), 2)]
|
||||
)
|
||||
|
||||
|
||||
async def wakeup():
|
||||
while True:
|
||||
await sleep(.01)
|
||||
await sleep(0.01)
|
||||
|
||||
|
||||
def nested_dataclass(*args, **kwargs):
|
||||
|
@ -94,4 +109,3 @@ class Manager(list):
|
|||
|
||||
def first_or_default(self, func):
|
||||
return next((val for val in self if func(val)), None)
|
||||
|
|
@ -1 +0,0 @@
|
|||
from .client_socket import ClientSocket
|
|
@ -1,80 +0,0 @@
|
|||
from io import BytesIO
|
||||
from random import randint
|
||||
from socket import SHUT_RD
|
||||
from time import time
|
||||
|
||||
from utils.tools import to_string
|
||||
from asyncio import create_task, Lock, get_event_loop
|
||||
|
||||
from common.constants import VERSION, SUB_VERSION, LOCALE
|
||||
from net.packets.crypto import MapleIV, MapleAes, shanda
|
||||
from net.packets.packet import Packet
|
||||
|
||||
|
||||
class ClientSocket:
|
||||
def __init__(self, socket):
|
||||
self._loop = get_event_loop()
|
||||
self._socket = socket
|
||||
self._lock = Lock()
|
||||
self.recieve_size = 16384
|
||||
self.m_riv = None
|
||||
self.m_siv = None
|
||||
self._is_alive = False
|
||||
self._overflow = None
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
return self._socket.getpeername()
|
||||
|
||||
def close(self):
|
||||
return self._socket.close()
|
||||
|
||||
async def receive(self, client):
|
||||
self._is_alive = True
|
||||
while self._is_alive:
|
||||
if not self._overflow:
|
||||
m_recv_buffer = await self._loop.sock_recv(self._socket, self.recieve_size)
|
||||
if not m_recv_buffer:
|
||||
await client._parent.on_client_disconnect(client)
|
||||
return
|
||||
|
||||
else:
|
||||
m_recv_buffer = self._overflow
|
||||
self._overflow = None
|
||||
|
||||
if self.m_riv:
|
||||
async with self._lock:
|
||||
length = MapleAes.get_length(m_recv_buffer)
|
||||
if length != len(m_recv_buffer) - 4:
|
||||
self._overflow = m_recv_buffer[length + 4:]
|
||||
m_recv_buffer = m_recv_buffer[:length + 4]
|
||||
|
||||
m_recv_buffer = self.manipulate_buffer(m_recv_buffer)
|
||||
|
||||
client.dispatch(Packet(m_recv_buffer))
|
||||
|
||||
async def send_packet(self, out_packet):
|
||||
packet_length = len(out_packet)
|
||||
packet = bytearray(out_packet.getvalue())
|
||||
|
||||
buf = packet[:]
|
||||
|
||||
final_length = packet_length + 4
|
||||
final = bytearray(final_length)
|
||||
async with self._lock:
|
||||
MapleAes.get_header(final, self.m_siv, packet_length, VERSION)
|
||||
buf = shanda.encrypt_transform(buf)
|
||||
final[4:] = MapleAes.transform(buf, self.m_siv)
|
||||
|
||||
await self._loop.sock_sendall(self._socket, final)
|
||||
|
||||
async def send_packet_raw(self, packet):
|
||||
await self._loop.sock_sendall(self._socket, packet.getvalue())
|
||||
|
||||
def manipulate_buffer(self, buffer):
|
||||
buf = bytearray(buffer)[4:]
|
||||
|
||||
buf = MapleAes.transform(buf, self.m_riv)
|
||||
buf = shanda.decrypt_transform(buf)
|
||||
|
||||
return buf
|
|
@ -1,2 +0,0 @@
|
|||
from .opcodes import CRecvOps, CSendOps
|
||||
from .packet import Packet
|
|
@ -1,2 +0,0 @@
|
|||
from .maple_iv import MapleIV
|
||||
from .aes import MapleAes
|
|
@ -1,110 +0,0 @@
|
|||
class MapleIV:
|
||||
_shuffle = bytearray([
|
||||
0xEC, 0x3F, 0x77, 0xA4, 0x45, 0xD0, 0x71, 0xBF, 0xB7, 0x98, 0x20, 0xFC,
|
||||
0x4B, 0xE9, 0xB3, 0xE1, 0x5C, 0x22, 0xF7, 0x0C, 0x44, 0x1B, 0x81, 0xBD,
|
||||
0x63, 0x8D, 0xD4, 0xC3, 0xF2, 0x10, 0x19, 0xE0, 0xFB, 0xA1, 0x6E, 0x66,
|
||||
0xEA, 0xAE, 0xD6, 0xCE, 0x06, 0x18, 0x4E, 0xEB, 0x78, 0x95, 0xDB, 0xBA,
|
||||
0xB6, 0x42, 0x7A, 0x2A, 0x83, 0x0B, 0x54, 0x67, 0x6D, 0xE8, 0x65, 0xE7,
|
||||
0x2F, 0x07, 0xF3, 0xAA, 0x27, 0x7B, 0x85, 0xB0, 0x26, 0xFD, 0x8B, 0xA9,
|
||||
0xFA, 0xBE, 0xA8, 0xD7, 0xCB, 0xCC, 0x92, 0xDA, 0xF9, 0x93, 0x60, 0x2D,
|
||||
0xDD, 0xD2, 0xA2, 0x9B, 0x39, 0x5F, 0x82, 0x21, 0x4C, 0x69, 0xF8, 0x31,
|
||||
0x87, 0xEE, 0x8E, 0xAD, 0x8C, 0x6A, 0xBC, 0xB5, 0x6B, 0x59, 0x13, 0xF1,
|
||||
0x04, 0x00, 0xF6, 0x5A, 0x35, 0x79, 0x48, 0x8F, 0x15, 0xCD, 0x97, 0x57,
|
||||
0x12, 0x3E, 0x37, 0xFF, 0x9D, 0x4F, 0x51, 0xF5, 0xA3, 0x70, 0xBB, 0x14,
|
||||
0x75, 0xC2, 0xB8, 0x72, 0xC0, 0xED, 0x7D, 0x68, 0xC9, 0x2E, 0x0D, 0x62,
|
||||
0x46, 0x17, 0x11, 0x4D, 0x6C, 0xC4, 0x7E, 0x53, 0xC1, 0x25, 0xC7, 0x9A,
|
||||
0x1C, 0x88, 0x58, 0x2C, 0x89, 0xDC, 0x02, 0x64, 0x40, 0x01, 0x5D, 0x38,
|
||||
0xA5, 0xE2, 0xAF, 0x55, 0xD5, 0xEF, 0x1A, 0x7C, 0xA7, 0x5B, 0xA6, 0x6F,
|
||||
0x86, 0x9F, 0x73, 0xE6, 0x0A, 0xDE, 0x2B, 0x99, 0x4A, 0x47, 0x9C, 0xDF,
|
||||
0x09, 0x76, 0x9E, 0x30, 0x0E, 0xE4, 0xB2, 0x94, 0xA0, 0x3B, 0x34, 0x1D,
|
||||
0x28, 0x0F, 0x36, 0xE3, 0x23, 0xB4, 0x03, 0xD8, 0x90, 0xC8, 0x3C, 0xFE,
|
||||
0x5E, 0x32, 0x24, 0x50, 0x1F, 0x3A, 0x43, 0x8A, 0x96, 0x41, 0x74, 0xAC,
|
||||
0x52, 0x33, 0xF0, 0xD9, 0x29, 0x80, 0xB1, 0x16, 0xD3, 0xAB, 0x91, 0xB9,
|
||||
0x84, 0x7F, 0x61, 0x1E, 0xCF, 0xC5, 0xD1, 0x56, 0x3D, 0xCA, 0xF4, 0x05,
|
||||
0xC6, 0xE5, 0x08, 0x49
|
||||
])
|
||||
|
||||
def __init__(self, vector):
|
||||
self.value = vector
|
||||
|
||||
def __int__(self):
|
||||
return self.value
|
||||
|
||||
@property
|
||||
def hiword(self):
|
||||
return self.value >> 16
|
||||
|
||||
@property
|
||||
def loword(self):
|
||||
return self.value
|
||||
|
||||
# def shuffle(self):
|
||||
# def to_int(arr):
|
||||
# return (seed[0] & 0xFF) + (seed[1] << 8 & 0xFF)\
|
||||
# + (seed[2] << 16 & 0xFF) + (seed[3] << 24 * 0xFF)
|
||||
|
||||
# def to_arr(int_):
|
||||
# return [
|
||||
# int_ & 0xFF,
|
||||
# int_ >> 8 & 0xFF,
|
||||
# int_ >> 16 & 0xFF,
|
||||
# int_ >> 24 & 0xFF
|
||||
# ]
|
||||
|
||||
# seed = [0xf2, 0x53, 0x50, 0xc6]
|
||||
# p_iv = self.value
|
||||
|
||||
# for i in range (4):
|
||||
# temp_iv = (p_iv >> (8 * i)) & 0xFF
|
||||
# seed[0] += (self._shuffle[seed[1] & 0xFF]) - temp_iv
|
||||
# seed[1] -= seed[2] ^ (self._shuffle[temp_iv])
|
||||
# seed[2] ^= temp_iv + (self._shuffle[seed[3]])
|
||||
# seed[3] = seed[3] - seed[0] + (self._shuffle[temp_iv])
|
||||
# seed = to_arr((to_int(seed) << 3) | (to_int(seed) >> 29))
|
||||
|
||||
# self.value = to_int(seed)
|
||||
|
||||
def shuffle(self):
|
||||
seed = [0xf2, 0x53, 0x50, 0xc6]
|
||||
p_iv = self.value
|
||||
|
||||
for i in range(4):
|
||||
temp_p_iv = (p_iv >> (8 * i)) & 0xFF
|
||||
|
||||
a = seed[1]
|
||||
b = a
|
||||
b = self._shuffle[b & 0xFF]
|
||||
b -= temp_p_iv
|
||||
seed[0] += b
|
||||
b = seed[2]
|
||||
b ^= self._shuffle[int(temp_p_iv) & 0xFF]
|
||||
a -= int(b) & 0xFF
|
||||
seed[1] = a
|
||||
a = seed[3]
|
||||
b = a
|
||||
a -= seed[0] & 0xFF
|
||||
b = self._shuffle[b & 0xFF]
|
||||
b += temp_p_iv
|
||||
b ^= seed[2]
|
||||
seed[2] = b & 0xFF
|
||||
a += self._shuffle[temp_p_iv & 0xFF] & 0xFF
|
||||
seed[3] = a
|
||||
|
||||
c = seed[0] & 0xFF
|
||||
c |= (seed[1] << 8) & 0xFFFF
|
||||
c |= (seed[2] << 16) & 0xFFFFFF
|
||||
c |= (seed[3] << 24) & 0xFFFFFFFF
|
||||
|
||||
c = (c << 0x03) | (c >> 0x1D)
|
||||
|
||||
seed[0] = c & 0xFF
|
||||
seed[1] = (c >> 8) & 0xFFFF
|
||||
seed[2] = (c >> 16) & 0xFFFFFF
|
||||
seed[3] = (c >> 24) & 0xFFFFFFFF
|
||||
|
||||
c = seed[0] & 0xFF
|
||||
c |= (seed[1] << 8) & 0xFFFF
|
||||
c |= (seed[2] << 16) & 0xFFFFFF
|
||||
c |= (seed[3] << 24) & 0xFFFFFFFF
|
||||
|
||||
self.value = c
|
|
@ -1 +0,0 @@
|
|||
from .dispatcher import Dispatcher
|
|
@ -1,31 +0,0 @@
|
|||
from utils import log
|
||||
|
||||
|
||||
class Dispatcher:
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
|
||||
def push(self, client, packet):
|
||||
log.packet(f"{self.parent.name} {packet.name} {client.ip} {packet.to_string()}", "in")
|
||||
|
||||
try:
|
||||
coro = None
|
||||
|
||||
for packet_handler in self.parent._packet_handlers:
|
||||
if packet_handler.op_code == packet.op_code:
|
||||
coro = packet_handler.callback
|
||||
break
|
||||
|
||||
if not coro:
|
||||
raise AttributeError
|
||||
|
||||
except AttributeError:
|
||||
log.warning(
|
||||
f"{self.parent.name} Unhandled event in : <w>{packet.name}</w>")
|
||||
|
||||
else:
|
||||
self.parent._loop.create_task(
|
||||
self._run_event(coro, client, packet))
|
||||
|
||||
async def _run_event(self, coro, *args):
|
||||
await coro(self.parent, *args)
|
9
run.py
9
run.py
|
@ -1,8 +1,3 @@
|
|||
from asyncio import get_event_loop
|
||||
from sys import argv
|
||||
from mapy import WvsCenter
|
||||
|
||||
from server.server import ServerApp
|
||||
|
||||
loop = get_event_loop()
|
||||
|
||||
ServerApp.run()
|
||||
WvsCenter.run()
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
from .npc_script import NpcScript
|
|
@ -1,33 +0,0 @@
|
|||
from asyncio import create_task
|
||||
|
||||
from utils import log
|
||||
|
||||
class ScriptBase:
|
||||
def __init__(self, script, client):
|
||||
self._parent = client
|
||||
self._script = compile(
|
||||
"async def ex():\n" + "".join([f" {line}" for line in open(script, "r").readlines()]),
|
||||
'<string>',
|
||||
'exec'
|
||||
)
|
||||
self._context = None
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self._parent
|
||||
|
||||
async def execute(self):
|
||||
async def run(script, _globals):
|
||||
exec(script, _globals)
|
||||
await _globals['ex']()
|
||||
env = {
|
||||
'ctx': self._context
|
||||
}
|
||||
env.update(globals())
|
||||
await run(
|
||||
self._script,
|
||||
env
|
||||
)
|
||||
|
||||
self._parent.npc_script = None
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
from .world import World
|
||||
from .wvs_game import WvsGame
|
||||
from .wvs_login import WvsLogin
|
||||
from .wvs_shop import WvsShop
|
|
@ -1,5 +0,0 @@
|
|||
from server.server_base import ServerBase
|
||||
|
||||
class WvsShop(ServerBase):
|
||||
def __init__(self):
|
||||
pass
|
6
test2.py
6
test2.py
|
@ -1,6 +1,8 @@
|
|||
# a = [(z, i,) for z in [(a := 2), (2, 4,), (1, 2,), (3, 4,)] for i in ([1,2,3,4,5,6])]
|
||||
a, b = 0, 0
|
||||
for z, y in [(d := (a := 2**i - 1), ((b := a + (2 * i) & b) % 250)) for i in range(1, 20)]:
|
||||
for z, y in [
|
||||
(d := (a := 2 ** i - 1), ((b := a + (2 * i) & b) % 250)) for i in range(1, 20)
|
||||
]:
|
||||
while z > (c := b // 4):
|
||||
z = (b := z**2-1) >> 0xFF & (y := a ^ b)
|
||||
z = (b := z ** 2 - 1) >> 0xFF & (y := a ^ b)
|
||||
print(z, c, b, y, a)
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
from .cpacket import CPacket
|
||||
from .logger import logger as log
|
||||
from .tools import Manager, first_or_default, wakeup, \
|
||||
filter_out_to, fix_dict_keys, get, to_string
|
||||
from .random import Random
|
||||
from .tag_point import TagPoint
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue