Move configuration to command line only

This commit is contained in:
Ben 2019-12-02 23:11:46 +00:00
parent 3e7943c6db
commit ade40f914b
11 changed files with 139 additions and 264 deletions

View File

@ -1,32 +1,36 @@
import asyncio import asyncio
import logging import logging
import argparse import argparse
import config
from houdini.houdini import Houdini from houdini.houdini import Houdini
from houdini.constants import ConflictResolution, Language, ClientType from houdini.constants import Language, ConflictResolution, ClientType
if __name__ == '__main__': if __name__ == '__main__':
logger = logging.getLogger('houdini') logger = logging.getLogger('houdini')
parser = argparse.ArgumentParser(description='Boot a Houdini server') parser = argparse.ArgumentParser(description='Boot a Houdini server')
parser.add_argument('server', action='store', default='Login', parser.add_argument('type', action='store', default='login',
help='Name of the server to boot') choices=['login', 'world'], help='Name of the server to boot')
parser.add_argument('-id', action='store', help='Houdini server ID') parser.add_argument('-id', action='store', default=3100, type=int, help='Houdini server ID')
parser.add_argument('-a', '--address', action='store', help='Houdini server address') parser.add_argument('-n', '--name', action='store', help='Houdini server name')
parser.add_argument('-p', '--port', action='store', help='Houdini server port', type=int) parser.add_argument('-a', '--address', action='store', default='0.0.0.0',
parser.add_argument('-c', '--capacity', action='store', help='Houdini server capacity', type=int) help='Houdini server address')
parser.add_argument('-C', '--cache-expiry', dest='cache_expiry', action='store', parser.add_argument('-p', '--port', action='store', help='Houdini server port', default=None, type=int)
parser.add_argument('-c', '--capacity', action='store', default=200,
help='Houdini server capacity', type=int)
parser.add_argument('-C', '--cache-expiry', dest='cache_expiry', action='store', default=3600,
help='Cache expiry (seconds)', type=int) help='Cache expiry (seconds)', type=int)
parser.add_argument('-P', '--plugins', action='store', parser.add_argument('-P', '--plugins', action='store', default='*',
nargs='*', help='Plugins to load') nargs='*', help='Plugins to load')
parser.add_argument('-l', '--language', action='store', help='Houdini language', parser.add_argument('-l', '--lang', action='store', default='en', help='Houdini language',
choices=['En', 'Fr', 'Pt', 'Es', 'De', 'Ru']) choices=['en', 'fr', 'pt', 'es', 'de', 'ru'])
boot_modes = parser.add_mutually_exclusive_group() login_group = parser.add_argument_group('login')
boot_modes.add_argument('-W', '--world', action='store_true', help='Run server in world mode') login_group.add_argument('--login-failure-limit', action='store', default=5, help='Limit before flood limit',
boot_modes.add_argument('-L', '--login', action='store_true', help='Run server in login mode') type=int)
login_group.add_argument('--login-failure-timer', action='store', default=3600, help='Timeout after flood limit',
type=int)
logging_group = parser.add_argument_group('logging') logging_group = parser.add_argument_group('logging')
logging_group.add_argument('-lg', '--logging-general', action='store', logging_group.add_argument('-lg', '--logging-general', action='store',
@ -36,139 +40,95 @@ if __name__ == '__main__':
dest='logging_error_path', dest='logging_error_path',
help='Error log path') help='Error log path')
logging_group.add_argument('-ll', '--logging-level', action='store', logging_group.add_argument('-ll', '--logging-level', action='store',
default='INFO',
dest='logging_level', dest='logging_level',
help='Logging level') help='Logging level')
database_group = parser.add_argument_group('database') database_group = parser.add_argument_group('database')
database_group.add_argument('-da', '--database-address', action='store', database_group.add_argument('-da', '--database-address', action='store',
dest='database_address', dest='database_address',
default=config.database['Address'], default='localhost',
help='Postgresql database address') help='Postgresql database address')
database_group.add_argument('-du', '--database-username', action='store', database_group.add_argument('-du', '--database-username', action='store',
dest='database_username', dest='database_username',
default=config.database['Username'], default='postgres',
help='Postgresql database username') help='Postgresql database username')
database_group.add_argument('-dp', '--database-password', action='store', database_group.add_argument('-dp', '--database-password', action='store',
dest='database_password', dest='database_password',
default=config.database['Password'], default='password',
help='Postgresql database password') help='Postgresql database password')
database_group.add_argument('-dn', '--database-name', action='store', database_group.add_argument('-dn', '--database-name', action='store',
dest='database_name', dest='database_name',
default=config.database['Name'], default='postgres',
help='Postgresql database name') help='Postgresql database name')
redis_group = parser.add_argument_group('redis') redis_group = parser.add_argument_group('redis')
redis_group.add_argument('-ra', '--redis-address', action='store', redis_group.add_argument('-ra', '--redis-address', action='store',
dest='redis_address', dest='redis_address',
default=config.redis['Address'], default='localhost',
help='Redis server address') help='Redis server address')
redis_group.add_argument('-rp', '--redis-port', action='store', redis_group.add_argument('-rp', '--redis-port', action='store',
dest='redis_port', dest='redis_port',
type=int, type=int,
default=config.redis['Port'], default=6379,
help='Redis server port') help='Redis server port')
command_group = parser.add_argument_group('commands') command_group = parser.add_argument_group('commands')
command_group.add_argument('-cp', '--command-prefix', action='store', dest='command_prefix', command_group.add_argument('-cp', '--command-prefix', action='store', dest='command_prefix',
nargs='*', nargs='*',
default=config.commands['Prefix'], default=['!', '?', '.'],
help='Command prefixes') help='Command prefixes')
command_group.add_argument('-csd', '--command-string-delimiters', action='store', dest='command_string_delimiters', command_group.add_argument('-csd', '--command-string-delimiters', action='store', dest='command_string_delimiters',
nargs='*', nargs='*',
default=config.commands['StringDelimiters'], default=['"', "'"],
help='Command string delimiters') help='Command string delimiters')
command_group.add_argument('-ccm', '--command-conflict-mode', action='store', dest='command_conflict_mode', command_group.add_argument('-ccm', '--command-conflict-mode', action='store', dest='command_conflict_mode',
default=config.commands['ConflictMode'].name, default='silent',
help='Command conflict mode', choices=['Silent', 'Append', 'Exception']) help='Command conflict mode', choices=['silent', 'append', 'exception'])
games_group = parser.add_argument_group('games')
games_group.add_argument('--max-coins', action='store',
default=1000000, type=int, help='Max coins earnable')
games_group.add_argument('--max-coins-per-min', action='store',
default=250, type=int, help='Max coins per min')
client_group = parser.add_argument_group('client') client_group = parser.add_argument_group('client')
client_mode = client_group.add_mutually_exclusive_group() client_mode = client_group.add_mutually_exclusive_group()
client_mode.add_argument('--multi-client-mode', action='store_true',
help='Run server with support for both clients')
client_mode.add_argument('--single-client-mode', action='store_true', client_mode.add_argument('--single-client-mode', action='store_true',
help='Run server with support for default client only') help='Run server with support for default client only')
client_group.add_argument('--legacy-version', action='store', client_group.add_argument('--legacy-version', action='store',
type=int, type=int,
default=config.client['LegacyVersionChk'], default=153,
help='Legacy client version to identify legacy clients') help='Legacy client version to identify legacy clients')
client_group.add_argument('--vanilla-version', action='store', client_group.add_argument('--vanilla-version', action='store',
type=int, type=int,
default=config.client['VanillaVersionChk'], default=253,
help='Vanilla client version to identify vanilla clients') help='Vanilla client version to identify vanilla clients')
client_group.add_argument('--default-version', action='store', client_group.add_argument('--default-version', action='store',
type=int, type=int,
default=config.client['DefaultVersionChk'], default=153,
help='Default version to identify clients when multi-client is off') help='Default version to identify clients when multi-client is off')
client_group.add_argument('--default-client', action='store', client_group.add_argument('--default-client', action='store',
choices=['Legacy', 'Vanilla'], choices=['legacy', 'vanilla'],
default=config.client['DefaultClientType'].name, default='legacy',
help='Default client when multi-client is off') help='Default client when multi-client is off')
client_group.add_argument('-k', '--auth-key', action='store', client_group.add_argument('-k', '--auth-key', action='store',
default='houdini', default='houdini',
help='Static key to use in place of the deprecated random key') help='Static key to use in place of the deprecated random key')
client_group.add_argument('-kt', '--auth-ttl', action='store', type=int, default=3000,
help='Auth key TTL (seconds)')
args = parser.parse_args() args = parser.parse_args()
database = { args.port = args.port if args.port else 9875 if args.type == 'world' else 6112
'Address': args.database_address, args.name = args.name if args.name else 'World' if args.type == 'world' else 'Login'
'Username': args.database_username, args.lang = dict(en=Language.En, fr=Language.Fr, pt=Language.Pt,
'Password': args.database_password, es=Language.Es, de=Language.De, ru=Language.Ru).get(args.lang)
'Name': args.database_name args.command_conflict_mode = dict(silent=ConflictResolution.Silent, append=ConflictResolution.Append,
} exception=ConflictResolution.Exception).get(args.command_conflict_mode)
args.default_client = dict(legacy=ClientType.Legacy, vanilla=ClientType.Vanilla).get(args.default_client)
redis = { factory_instance = Houdini(args)
'Address': args.redis_address,
'Port': args.redis_port
}
commands = {
'Prefix': args.command_prefix,
'StringDelimiters': args.command_string_delimiters,
'ConflictMode': getattr(ConflictResolution, args.command_conflict_mode)
}
client = {
'MultiClientSupport': True if args.multi_client_mode else False if args.single_client_mode
else config.client['MultiClientSupport'],
'LegacyVersionChk': args.legacy_version,
'VanillaVersionChk': args.vanilla_version,
'DefaultVersionChk': args.default_version,
'DefaultClientType': getattr(ClientType, args.default_client),
'AuthStaticKey': args.auth_key
}
server = {
'Address': args.address or config.servers[args.server]['Address'],
'Port': args.port or config.servers[args.server]['Port'],
'World': True if args.world else False if args.login else None or config.servers[args.server]['World'],
'Plugins': True if args.plugins and '*' in args.plugins
else args.plugins or config.servers[args.server]['Plugins']
}
logging = {
'General': args.logging_general_path or config.servers[args.server]['Logging']['General'],
'Errors': args.logging_error_path or config.servers[args.server]['Logging']['Errors'],
'Level': args.logging_level or config.servers[args.server]['Logging']['Level']
}
if server['World']:
server.update({
'Id': args.id or config.servers[args.server]['Id'],
'Language': getattr(Language, args.language) if args.language else config.servers[args.server]['Language'],
'Capacity': args.capacity or config.servers[args.server]['Capacity'],
'CacheExpiry': args.cache_expiry or config.servers[args.server]['CacheExpiry']
})
server['Logging'] = logging
factory_instance = Houdini(args.server,
database=database,
redis=redis,
commands=commands,
client=client,
server=server)
try: try:
asyncio.run(factory_instance.start()) asyncio.run(factory_instance.start())
except KeyboardInterrupt: except KeyboardInterrupt:

View File

@ -1,65 +0,0 @@
from houdini.constants import ConflictResolution, Language, ClientType
database = {
'Address': 'localhost',
'Username': 'postgres',
'Password': 'password',
'Name': 'houdini',
}
redis = {
'Address': '127.0.0.1',
'Port': 6379
}
commands = {
'Prefix': ['!', '?', '.'],
'StringDelimiters': ['"', "'"],
'ConflictMode': ConflictResolution.Silent
}
client = {
'MultiClientSupport': True,
'LegacyVersionChk': 153,
'VanillaVersionChk': 253,
'DefaultVersionChk': 253,
'DefaultClientType': ClientType.Vanilla,
'AuthStaticKey': 'houdini'
}
servers = {
'Login': {
'Address': '0.0.0.0',
'Port': 6112,
'World': False,
'Plugins': [
'Example'
],
'Logging': {
'General': 'logs/login.log',
'Errors': 'logs/login-errors.log',
'Level': 'DEBUG'
},
'LoginFailureLimit': 5,
'LoginFailureTimer': 3600,
'KeyTTL': 3000
},
'Blizzard': {
'Id': '100',
'Address': '0.0.0.0',
'Port': 9875,
'Language': Language.En,
'World': True,
'Capacity': 200,
'CacheExpiry': 3600,
'Plugins': [
],
'Logging': {
'General': 'logs/blizzard.log',
'Errors': 'logs/blizzard-errors.log',
'Level': 'INFO'
}
}
}

View File

@ -1,5 +1,4 @@
import inspect import inspect
import config
from houdini import handlers from houdini import handlers
from houdini import plugins from houdini import plugins
@ -46,12 +45,12 @@ class _CommandGroup(_Command):
def command(name=None, **kwargs): def command(name=None, **kwargs):
return _listener(_Command, name, string_delimiter=config.commands['StringDelimiters'], return _listener(_Command, name, string_delimiter=['"', "'"],
string_separator=' ', **kwargs) string_separator=' ', **kwargs)
def group(name=None, **kwargs): def group(name=None, **kwargs):
return _listener(_CommandGroup, name, string_delimiter=config.commands['StringDelimiters'], return _listener(_CommandGroup, name, string_delimiter=['"', "'"],
string_separator=' ', **kwargs) string_separator=' ', **kwargs)
@ -59,7 +58,6 @@ cooldown = handlers.cooldown
check = handlers.check check = handlers.check
player_attribute = handlers.player_attribute player_attribute = handlers.player_attribute
player_data_attribute = handlers.player_data_attribute
player_in_room = handlers.player_in_room player_in_room = handlers.player_in_room
@ -84,13 +82,14 @@ class CommandManager(_AbstractManager):
for name in command_object.alias: for name in command_object.alias:
if name in parent_commands and len(parent_commands[name]): if name in parent_commands and len(parent_commands[name]):
conflict_command = parent_commands[name][0] conflict_command = parent_commands[name][0]
if config.commands['ConflictMode'] == ConflictResolution.Exception: conflict_resolution = self.server.config.command_conflict_mode
if conflict_resolution == ConflictResolution.Exception:
raise NameError(f'Command name conflict: \'{name}\' from plugin \'{module.__class__.__name__}\' ' raise NameError(f'Command name conflict: \'{name}\' from plugin \'{module.__class__.__name__}\' '
f'conflicts with \'{conflict_command.name}\' from ' f'conflicts with \'{conflict_command.name}\' from '
f'module \'{conflict_command.instance.__class__.__name__}\'') f'module \'{conflict_command.instance.__class__.__name__}\'')
elif config.commands['ConflictMode'] == ConflictResolution.Append: elif conflict_resolution == ConflictResolution.Append:
parent_commands[name].append(command_object) parent_commands[name].append(command_object)
elif config.commands['ConflictMode'] == ConflictResolution.Silent: elif conflict_resolution == ConflictResolution.Silent:
module.server.logger.warning(f'Command \'{name}\' from module \'{module.__class__.__name__}\' ' module.server.logger.warning(f'Command \'{name}\' from module \'{module.__class__.__name__}\' '
f'disabled due to conflict with ' f'disabled due to conflict with '
f'\'{conflict_command.instance.__class__.__name__}\'') f'\'{conflict_command.instance.__class__.__name__}\'')
@ -102,25 +101,21 @@ def is_command(command_object):
return issubclass(type(command_object), _Command) return issubclass(type(command_object), _Command)
if type(config.commands['Prefix']) == str: def has_command_prefix(pre, command_string):
config.commands['Prefix'] = [config.commands['Prefix']] for prefix in pre:
def has_command_prefix(command_string):
for prefix in config.commands['Prefix']:
if command_string.startswith(prefix): if command_string.startswith(prefix):
return True return True
return False return False
def get_command_prefix(command_string): def get_command_prefix(pre, command_string):
for prefix in config.commands['Prefix']: for prefix in pre:
if command_string.startswith(prefix): if command_string.startswith(prefix):
return prefix return prefix
async def invoke_command_string(commands, p, command_string): async def invoke_command_string(commands, p, command_string):
prefix = get_command_prefix(command_string) prefix = get_command_prefix(p.server.config.command_prefix, command_string)
no_prefix = command_string[len(prefix):] no_prefix = command_string[len(prefix):]
data = no_prefix.split(' ') data = no_prefix.split(' ')

View File

@ -1,5 +1,3 @@
import config
from houdini import handlers from houdini import handlers
from houdini.handlers import XMLPacket from houdini.handlers import XMLPacket
from houdini.converters import VersionChkConverter from houdini.converters import VersionChkConverter
@ -11,13 +9,13 @@ from houdini.data.buddy import BuddyList
@handlers.handler(XMLPacket('verChk')) @handlers.handler(XMLPacket('verChk'))
@handlers.allow_once @handlers.allow_once
async def handle_version_check(p, version: VersionChkConverter): async def handle_version_check(p, version: VersionChkConverter):
if config.client['MultiClientSupport']: if not p.server.config.single_client_mode:
if config.client['LegacyVersionChk'] == version: if p.server.config.legacy_version == version:
p.client_type = ClientType.Legacy p.client_type = ClientType.Legacy
elif config.client['VanillaVersionChk'] == version: elif p.server.config.vanilla_version == version:
p.client_type = ClientType.Vanilla p.client_type = ClientType.Vanilla
elif config.client['DefaultVersionChk'] == version: elif p.server.config.default_version == version:
p.client_type = config.client['DefaultClientType'] p.client_type = p.server.config.default_version
if p.client_type is None: if p.client_type is None:
await p.send_xml({'body': {'action': 'apiKO', 'r': '0'}}) await p.send_xml({'body': {'action': 'apiKO', 'r': '0'}})
@ -28,24 +26,23 @@ async def handle_version_check(p, version: VersionChkConverter):
@handlers.handler(XMLPacket('rndK')) @handlers.handler(XMLPacket('rndK'))
@handlers.allow_once @handlers.allow_once
async def handle_random_key(p, data): async def handle_random_key(p, _):
await p.send_xml({'body': {'action': 'rndK', 'r': '-1'}, 'k': config.client['AuthStaticKey']}) await p.send_xml({'body': {'action': 'rndK', 'r': '-1'}, 'k': p.server.config.auth_key})
async def get_server_presence(p, pid): async def get_server_presence(p, pid):
buddy_worlds = [] buddy_worlds = []
world_populations = [] world_populations = []
for server_name, server_config in config.servers.items(): pops = await p.server.redis.hgetall('houdini.population')
if server_config['World']: for server_id, server_population in pops.items():
server_population = await p.server.redis.hget('houdini.population', server_config['Id']) server_population = (7 if int(server_population) == p.server.config.capacity
server_population = (7 if int(server_population) == server_config['Capacity'] else int(server_population) // (p.server.config.capacity // 6)) \
else int(server_population) // (server_config['Capacity'] // 6)) \
if server_population else 0 if server_population else 0
world_populations.append(f'{server_config["Id"]},{server_population}') world_populations.append(f'{int(server_id)},{int(server_population)}')
server_key = f'houdini.players.{server_config["Id"]}' server_key = f'houdini.players.{int(server_id)}'
if await p.server.redis.scard(server_key): if await p.server.redis.scard(server_key):
async with p.server.db.transaction(): async with p.server.db.transaction():
buddies = BuddyList.select('buddy_id').where(BuddyList.penguin_id == pid).gino.iterate() buddies = BuddyList.select('buddy_id').where(BuddyList.penguin_id == pid).gino.iterate()
@ -54,6 +51,6 @@ async def get_server_presence(p, pid):
tr.sismember(server_key, buddy_id) tr.sismember(server_key, buddy_id)
online_buddies = await tr.execute() online_buddies = await tr.execute()
if any(online_buddies): if any(online_buddies):
buddy_worlds.append(server_config['Id']) buddy_worlds.append(str(int(server_id)))
return '|'.join(world_populations), '|'.join(buddy_worlds) return '|'.join(world_populations), '|'.join(buddy_worlds)

View File

@ -41,19 +41,19 @@ async def handle_login(p, credentials: Credentials):
if await p.server.redis.exists(flood_key): if await p.server.redis.exists(flood_key):
tr = p.server.redis.multi_exec() tr = p.server.redis.multi_exec()
tr.incr(flood_key) tr.incr(flood_key)
tr.expire(flood_key, p.server.server_config['LoginFailureTimer']) tr.expire(flood_key, p.server.config.login_failure_timer)
failure_count, _ = await tr.execute() failure_count, _ = await tr.execute()
if failure_count >= p.server.server_config['LoginFailureLimit']: if failure_count >= p.server.config.login_failure_limit:
return await p.send_error_and_disconnect(150) return await p.send_error_and_disconnect(150)
else: else:
await p.server.redis.setex(flood_key, p.server.server_config['LoginFailureTimer'], 1) await p.server.redis.setex(flood_key, p.server.config.login_failure_timer, 1)
return await p.send_error_and_disconnect(101) return await p.send_error_and_disconnect(101)
failure_count = await p.server.redis.get(flood_key) failure_count = await p.server.redis.get(flood_key)
if failure_count: if failure_count:
max_attempts_exceeded = int(failure_count) >= p.server.server_config['LoginFailureLimit'] max_attempts_exceeded = int(failure_count) >= p.server.config.login_failure_limit
if max_attempts_exceeded: if max_attempts_exceeded:
return await p.send_error_and_disconnect(150) return await p.send_error_and_disconnect(150)
@ -98,8 +98,8 @@ async def handle_login(p, credentials: Credentials):
confirmation_hash = Crypto.hash(os.urandom(24)) confirmation_hash = Crypto.hash(os.urandom(24))
tr = p.server.redis.multi_exec() tr = p.server.redis.multi_exec()
tr.setex(f'{data.username}.lkey', p.server.server_config['KeyTTL'], login_key) tr.setex(f'{data.username}.lkey', p.server.config.auth_ttl, login_key)
tr.setex(f'{data.username}.ckey', p.server.server_config['KeyTTL'], confirmation_hash) tr.setex(f'{data.username}.ckey', p.server.config.auth_ttl, confirmation_hash)
await tr.execute() await tr.execute()
world_populations, buddy_presence = await get_server_presence(p, data.id) world_populations, buddy_presence = await get_server_presence(p, data.id)

View File

@ -1,5 +1,3 @@
import config
from houdini import handlers from houdini import handlers
from houdini.handlers import XMLPacket, login from houdini.handlers import XMLPacket, login
from houdini.converters import WorldCredentials, Credentials from houdini.converters import WorldCredentials, Credentials
@ -15,7 +13,7 @@ handle_random_key = login.handle_random_key
async def world_login(p, data): async def world_login(p, data):
if len(p.server.penguins_by_id) >= p.server.server_config['Capacity']: if len(p.server.penguins_by_id) >= p.server.config.capacity:
return await p.send_error_and_disconnect(103) return await p.send_error_and_disconnect(103)
if data is None: if data is None:
@ -53,7 +51,7 @@ async def handle_login(p, credentials: WorldCredentials):
return await p.close() return await p.close()
login_key = login_key.decode() login_key = login_key.decode()
login_hash = Crypto.encrypt_password(login_key + config.client['AuthStaticKey']) + login_key login_hash = Crypto.encrypt_password(login_key + p.server.config.auth_key) + login_key
if credentials.client_key != login_hash: if credentials.client_key != login_hash:
return await p.close() return await p.close()
@ -73,11 +71,11 @@ async def handle_login(p, credentials: WorldCredentials):
async def handle_legacy_login(p, credentials: Credentials): async def handle_legacy_login(p, credentials: Credentials):
tr = p.server.redis.multi_exec() tr = p.server.redis.multi_exec()
tr.get(f'{credentials.username}.lkey') tr.get(f'{credentials.username}.lkey')
tr.delete(f'{credentials.username}.lkey', '{credentials.username}.ckey') tr.delete(f'{credentials.username}.lkey', f'{credentials.username}.ckey')
login_key, _ = await tr.execute() login_key, _ = await tr.execute()
login_key = login_key.decode() login_key = login_key.decode()
login_hash = Crypto.encrypt_password(login_key + config.client['AuthStaticKey']) + login_key login_hash = Crypto.encrypt_password(login_key + p.server.config.auth_key) + login_key
if login_key is None or login_hash != credentials.password: if login_key is None or login_hash != credentials.password:
return await p.close() return await p.close()

View File

@ -15,7 +15,8 @@ async def handle_send_message(p, penguin_id: int, message: str):
await penguin.send_xt("mm", message, penguin_id) await penguin.send_xt("mm", message, penguin_id)
return return
if has_command_prefix(message): tokens = message.lower()
if has_command_prefix(p.server.config.command_prefix, message):
await invoke_command_string(p.server.commands, p, message) await invoke_command_string(p.server.commands, p, message)
else: else:
await p.room.send_xt('sm', p.id, message) await p.room.send_xt('sm', p.id, message)

View File

@ -58,9 +58,9 @@ async def handle_join_server(p, penguin_id: int, login_key: str):
p.login_timestamp = datetime.now() p.login_timestamp = datetime.now()
p.joined_world = True p.joined_world = True
server_key = f'houdini.players.{p.server.server_config["Id"]}' server_key = f'houdini.players.{p.server.config.id}'
await p.server.redis.sadd(server_key, p.data.id) await p.server.redis.sadd(server_key, p.id)
await p.server.redis.hincrby('houdini.population', p.server.server_config['Id'], 1) await p.server.redis.hincrby('houdini.population', p.server.config.id, 1)
@handlers.handler(XTPacket('j', 'jr')) @handlers.handler(XTPacket('j', 'jr'))
@ -127,6 +127,6 @@ async def handle_disconnect_room(p):
del p.server.penguins_by_id[p.id] del p.server.penguins_by_id[p.id]
del p.server.penguins_by_username[p.username] del p.server.penguins_by_username[p.username]
server_key = f'houdini.players.{p.server.server_config["Id"]}' server_key = f'houdini.players.{p.server.config.id}'
await p.server.redis.srem(server_key, p.data.id) await p.server.redis.srem(server_key, p.id)
await p.server.redis.hincrby('houdini.population', p.server.server_config['Id'], -1) await p.server.redis.hincrby('houdini.population', p.server.config.id, -1)

View File

@ -6,7 +6,6 @@ import copy
from houdini.spheniscidae import Spheniscidae from houdini.spheniscidae import Spheniscidae
from houdini.penguin import Penguin from houdini.penguin import Penguin
from houdini import PenguinStringCompiler from houdini import PenguinStringCompiler
import config
import logging import logging
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
@ -47,22 +46,14 @@ from houdini.handlers.play.music import SoundStudio
class Houdini: class Houdini:
def __init__(self, server_name, **kwargs): def __init__(self, config):
self.server = None self.server = None
self.redis = None self.redis = None
self.config = None
self.cache = None self.cache = None
self.config = config
self.db = db self.db = db
self.peers_by_ip = {} self.peers_by_ip = {}
self.server_name = server_name
self.database_config_override = kwargs.get('database')
self.redis_config_override = kwargs.get('redis')
self.commands_config_override = kwargs.get('commands')
self.client_config_override = kwargs.get('client')
self.server_config_override = kwargs.get('server')
self.server_config = None
self.logger = None self.logger = None
self.client_class = Spheniscidae self.client_class = Spheniscidae
@ -105,18 +96,14 @@ class Houdini:
self.music = None self.music = None
async def start(self): async def start(self):
self.config = config
self.server_config = copy.deepcopy(self.config.servers[self.server_name]) async def start(self):
self.server_config.update(self.server_config_override) general_log_file = self.config.logging_general_path if self.config.logging_general_path \
else f'logs/{self.config.name.lower()}.log'
self.config.database.update(self.database_config_override) errors_log_file = self.config.logging_error_path if self.config.logging_error_path \
self.config.redis.update(self.redis_config_override) else f'logs/{self.config.name.lower()}-errors.log'
self.config.commands.update(self.commands_config_override) general_log_directory = os.path.dirname(general_log_file)
self.config.client.update(self.client_config_override) errors_log_directory = os.path.dirname(errors_log_file)
general_log_directory = os.path.dirname(self.server_config["Logging"]["General"])
errors_log_directory = os.path.dirname(self.server_config["Logging"]["Errors"])
if not os.path.exists(general_log_directory): if not os.path.exists(general_log_directory):
os.mkdir(general_log_directory) os.mkdir(general_log_directory)
@ -125,10 +112,10 @@ class Houdini:
os.mkdir(errors_log_directory) os.mkdir(errors_log_directory)
self.logger = logging.getLogger('houdini') self.logger = logging.getLogger('houdini')
universal_handler = RotatingFileHandler(self.server_config['Logging']['General'], universal_handler = RotatingFileHandler(general_log_file,
maxBytes=2097152, backupCount=3, encoding='utf-8') maxBytes=2097152, backupCount=3, encoding='utf-8')
error_handler = logging.FileHandler(self.server_config['Logging']['Errors']) error_handler = logging.FileHandler(errors_log_file)
console_handler = logging.StreamHandler(stream=sys.stdout) console_handler = logging.StreamHandler(stream=sys.stdout)
log_formatter = logging.Formatter('%(asctime)s [%(levelname)-5.5s] %(message)s') log_formatter = logging.Formatter('%(asctime)s [%(levelname)-5.5s] %(message)s')
@ -141,35 +128,34 @@ class Houdini:
self.logger.addHandler(console_handler) self.logger.addHandler(console_handler)
self.logger.addHandler(error_handler) self.logger.addHandler(error_handler)
level = logging.getLevelName(self.server_config['Logging']['Level']) level = logging.getLevelName(self.config.logging_level)
self.logger.setLevel(level) self.logger.setLevel(level)
self.server = await asyncio.start_server( self.server = await asyncio.start_server(
self.client_connected, self.server_config['Address'], self.client_connected, self.config.address,
self.server_config['Port'] self.config.port
) )
await self.db.set_bind('postgresql://{}:{}@{}/{}'.format( await self.db.set_bind('postgresql://{}:{}@{}/{}'.format(
self.config.database['Username'], self.config.database['Password'], self.config.database_username, self.config.database_password,
self.config.database['Address'], self.config.database_address,
self.config.database['Name'])) self.config.database_name))
self.logger.info('Booting Houdini') self.logger.info('Booting Houdini')
self.redis = await aioredis.create_redis_pool('redis://{}:{}'.format( self.redis = await aioredis.create_redis_pool('redis://{}:{}'.format(
self.config.redis['Address'], self.config.redis['Port']), self.config.redis_address, self.config.redis_port),
minsize=5, maxsize=10) minsize=5, maxsize=10)
if self.server_config['World']: if self.config.type == 'world':
await self.redis.delete(f'houdini.players.{self.server_config["Id"]}') await self.redis.delete(f'houdini.players.{self.config.id}')
await self.redis.hdel(f'houdini.population', self.server_config["Id"]) await self.redis.hset(f'houdini.population', self.config.id, 0)
caches.set_config({ caches.set_config(dict(default=dict(
'default': { cache=SimpleMemoryCache,
'cache': SimpleMemoryCache, namespace='houdini',
'namespace': 'houdini', ttl=self.config.cache_expiry
'ttl': self.server_config['CacheExpiry'] )))
}})
self.cache = caches.get('default') self.cache = caches.get('default')
self.client_class = Penguin self.client_class = Penguin
@ -227,12 +213,13 @@ class Houdini:
self.logger.info(f'Loaded {len(self.characters)} characters') self.logger.info(f'Loaded {len(self.characters)} characters')
self.permissions = await PermissionCollection.get_collection() self.permissions = await PermissionCollection.get_collection()
self.chat_filter_words = await ChatFilterRuleCollection.get_collection()
self.logger.info(f'Multi-client support is ' self.logger.info(f'Multi-client support is '
f'{"enabled" if self.config.client["MultiClientSupport"] else "disabled"}') f'{"enabled" if not self.config.single_client_mode else "disabled"}')
self.logger.info(f'Listening on {self.server_config["Address"]}:{self.server_config["Port"]}') self.logger.info(f'Listening on {self.config.address}:{self.config.port}')
if self.config.client['AuthStaticKey'] != 'houdini': if self.config.auth_key != 'houdini':
self.logger.warning('The static key has been changed from the default, ' self.logger.warning('The static key has been changed from the default, '
'this may cause authentication issues!') 'this may cause authentication issues!')

View File

@ -55,8 +55,8 @@ class Penguin(Spheniscidae, penguin.Penguin):
return self.server.penguin_string_compiler.compile(self) return self.server.penguin_string_compiler.compile(self)
@property @property
def nickname(self): def safe_name(self):
return self.data.safe_nickname(self.server.server_config['Language']) return self.safe_nickname(self.server.config.lang)
async def join_room(self, room): async def join_room(self, room):
await room.add_penguin(self) await room.add_penguin(self)

View File

@ -34,6 +34,8 @@ class Spheniscidae:
self.received_packets = set() self.received_packets = set()
super().__init__()
@property @property
def is_vanilla_client(self): def is_vanilla_client(self):
return self.client_type == ClientType.Vanilla return self.client_type == ClientType.Vanilla
@ -51,7 +53,7 @@ class Spheniscidae:
async def send_policy_file(self): async def send_policy_file(self):
await self.send_line(f'<cross-domain-policy><allow-access-from domain="*" to-ports="' await self.send_line(f'<cross-domain-policy><allow-access-from domain="*" to-ports="'
f'{self.server.server_config["Port"]}" /></cross-domain-policy>') f'{self.server.config.port}" /></cross-domain-policy>')
await self.close() await self.close()
async def send_xt(self, handler_id, *data): async def send_xt(self, handler_id, *data):