From e1418f378c66343a35db3791cbf0d54a4be3fbd3 Mon Sep 17 00:00:00 2001 From: Ben Longbons Date: Sat, 15 Nov 2014 12:29:33 -0800 Subject: Generate most config parsers --- tools/config.py | 692 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 692 insertions(+) create mode 100755 tools/config.py (limited to 'tools') diff --git a/tools/config.py b/tools/config.py new file mode 100755 index 0000000..8d1423b --- /dev/null +++ b/tools/config.py @@ -0,0 +1,692 @@ +#!/usr/bin/env python +# coding: utf-8 + +# config.py - generator for config file parsers +# +# Copyright © 2014 Ben Longbons +# +# This file is part of The Mana World (Athena server) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from __future__ import print_function + +import glob +import os + +from protocol import OpenWrite + + +generated = '// This is a generated file, edit %s instead\n' % __file__ + +copyright = '''// {filename} - {description} +// +// Copyright © 2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +''' + + +class AnyHeader(object): + __slots__ = ('name') + + def __init__(self, name): + self.name = name + +class SystemHeader(AnyHeader): + __slots__ = () + meta = 0 + + def relative_to(self, path): + return '<%s>' % self.name + +class Header(AnyHeader): + __slots__ = () + meta = 1 + + def relative_to(self, path): + return '"%s"' % os.path.relpath(self.name, path) + + +class ConfigType(object): + __slots__ = () + +class SimpleType(ConfigType): + __slots__ = ('name', 'headers') + + def __init__(self, name, headers): + self.name = name + self.headers = frozenset(headers) + + def __repr__(self): + return 'SimpleType(%r, %r)' % (self.name, self.headers) + + def type_name(self): + return self.name + + def dump_extract(self, cpp, var): + cpp.write( +''' + if (!extract(value.data, &{var})) + {{ + value.span.error("Failed to extract value"_s); + return false; + }} +'''.lstrip('\n').format(var=var)) + +class PhonyType(ConfigType): + __slots__ = ('type', 'name', 'call', 'headers') + + def __init__(self, type, name, call, extra_headers): + self.type = type + self.name = name + self.call = call + self.headers = type.headers | extra_headers + + def __repr__(self): + return 'PhonyType(%r, %r, %r, %r)' % (self.type, self.name, self.call, self.headers) + + def type_name(self): + return '// special %s' % self.type.type_name() + + def dump_extract(self, cpp, var): + cpp.write(' %s %s;\n' % (self.type.type_name(), self.name)) + self.type.dump_extract(cpp, self.name) + cpp.write(' %s\n' % self.call) + +class TransformedType(ConfigType): + __slots__ = ('type', 'transform', 'headers') + + def __init__(self, type, transform, extra_headers=set()): + self.type = type + self.transform = transform + self.headers = type.headers | extra_headers + + def __repr__(self): + return 'TransformedType(%r, %r)' % (self.type, self.transform) + + def type_name(self): + return self.type.type_name() + + def dump_extract(self, cpp, var): + self.type.dump_extract(cpp, var) + cpp.write(' %s;\n' % self.transform) + +class BoundedType(ConfigType): + __slots__ = ('type', 'low', 'high', 'headers') + + def __init__(self, type, low, high, extra_headers=set()): + assert isinstance(type, ConfigType) + self.type = type + self.low = low + self.high = high + self.headers = type.headers | extra_headers + + def __repr__(self): + return 'BoundedType(%r, %r, %r, %r)' % (self.type, self.low, self.high, self.headers) + + def type_name(self): + return self.type.type_name() + + def dump_extract(self, cpp, var): + self.type.dump_extract(cpp, var) + cpp.write( +''' + if (!({low} <= {var} && {var} <= {high})) + {{ + value.span.error("Value of {name} not in range [{low}, {high}]"_s); + return false; + }} +'''.format(low=self.low, high=self.high, var=var, name=var.split('.')[-1])) + +class MinBoundedType(ConfigType): + __slots__ = ('type', 'low', 'headers') + + def __init__(self, type, low, extra_headers=set()): + assert isinstance(type, ConfigType) + self.type = type + self.low = low + self.headers = type.headers | extra_headers + + def __repr__(self): + return 'MinBoundedType(%r, %r, %r, %r)' % (self.type, self.low, self.headers) + + def type_name(self): + return self.type.type_name() + + def dump_extract(self, cpp, var): + self.type.dump_extract(cpp, var) + cpp.write( +''' + if (!({low} <= {var})) + {{ + value.span.error("Value of {name} not at least {low}"_s); + return false; + }} +'''.format(low=self.low, var=var, name=var.split('.')[-1])) + +class Option(object): + __slots__ = ('name', 'type', 'default', 'headers') + + def __init__(self, name, type, default, extra_headers=set()): + self.name = name + self.type = type + self.default = default + self.headers = type.headers | extra_headers + + def dump1(self, hpp): + hpp.write(' %s %s = %s;\n' % (self.type.type_name(), self.name, self.default)) + def dump2(self, cpp, x): + # NOTE about hashing + # dead simple hash: pack 6 bits of first 5 letters into an int + y = self.name[:5] + if x != y: + if x is not None: + cpp.write(' break;\n') + c0 = y[0] if len(y) > 0 else '\\0' + c1 = y[1] if len(y) > 1 else '\\0' + c2 = y[2] if len(y) > 2 else '\\0' + c3 = y[3] if len(y) > 3 else '\\0' + c4 = y[4] if len(y) > 4 else '\\0' + assert len(y) >= 3, '<-- change this number in the source file for: %r' % self.name + cpp.write(" case (('%s' << 24) | ('%s' << 18) | ('%s' << 12) | ('%s' << 6) | ('%s' << 0)):\n" % (c0, c1, c2, c3, c4)) + cpp.write(' if (key.data == "{name}"_s)\n'.format(name=self.name)) + cpp.write(' {\n') + self.type.dump_extract(cpp, 'conf.%s' % self.name) + cpp.write(' return true;\n') + cpp.write(' }\n') + return y + +class Group(object): + __slots__ = ('name', 'options', 'extra_headers') + + def __init__(self, name): + self.name = name + self.options = {} + self.extra_headers = [] + + def extra(self, h): + self.extra_headers.append(h) + + def opt(self, name, type, default, extra_headers=set(), pre=None, post=None, min=None, max=None): + assert name not in self.options, 'Duplicate option name: %s' % name + assert isinstance(default, str) + if pre is not None: + type = TransformedType(type, pre) + if min is not None: + if max is not None: + type = BoundedType(type, min, max) + else: + type = MinBoundedType(type, min) + else: + assert max is None + if post is not None: + type = TransformedType(type, post) + self.options[name] = rv = Option(name, type, default, extra_headers) + return rv + + def dump_in(self, path, namespace_name): + if namespace_name == 'char': + namespace_name += '_' + var_name = '%s_conf' % self.name + class_name = var_name.replace('_', ' ').title().replace(' ', '') + short_hpp_name = '%s.hpp' % var_name + hpp_name = os.path.join(path, short_hpp_name) + short_cpp_name = '%s.cpp' % var_name + cpp_name = os.path.join(path, short_cpp_name) + + values = sorted(self.options.values(), key=lambda o: o.name) + + desc = 'Config for %s::%s' % (namespace_name, self.name) + with OpenWrite(hpp_name) as hpp, \ + OpenWrite(cpp_name) as cpp: + hpp.write('#pragma once\n') + hpp.write(copyright.format(filename=short_hpp_name, description=desc)) + hpp.write('\n') + hpp.write(generated) + cpp.write('#include "%s"\n' % short_hpp_name) + cpp.write(copyright.format(filename=short_cpp_name, description=desc)) + cpp.write('\n') + cpp.write(generated) + headers = { + Header('src/io/fwd.hpp'), + Header('src/strings/fwd.hpp') + } + for o in values: + headers |= o.headers + + hpp.write('\n') + hpp.write('#include "fwd.hpp"\n') + for h in sorted(headers, key=lambda h: (h.meta, h.name)): + hpp.write('#include %s\n' % h.relative_to(path)) + hpp.write('\n') + cpp.write('\n') + for h in [ + SystemHeader('bitset'), + Header('src/io/extract.hpp'), + Header('src/io/span.hpp'), + Header('src/mmo/extract_enums.hpp'), + Header('src/high/extract_mmo.hpp'), + ] + self.extra_headers: + cpp.write('#include %s\n' % h.relative_to(path)) + cpp.write('\n') + cpp.write('#include "../poison.hpp"\n') + cpp.write('\n') + + hpp.write('namespace tmwa\n{\n') + cpp.write('namespace tmwa\n{\n') + cpp.write(''' +static __attribute__((unused)) +bool extract(XString str, bool *v) +{ + if (str == "true"_s || str == "on"_s || str == "yes"_s + || str == "oui"_s || str == "ja"_s + || str == "si"_s || str == "1"_s) + { + *v = 1; + return true; + } + if (str == "false"_s || str == "off"_s || str == "no"_s + || str == "non"_s || str == "nein"_s || str == "0"_s) + { + *v = 0; + return true; + } + return false; +} + +static __attribute__((unused)) +bool extract(XString str, std::bitset<256> *v) +{ + if (!str) + { + v->reset(); + return true; + } + for (uint8_t c : str) + { + (*v)[c] = true; + } + return true; +} +''') + hpp.write('namespace %s\n{\n' % namespace_name) + cpp.write('namespace %s\n{\n' % namespace_name) + hpp.write('struct %s\n{\n' % class_name) + for o in values: + o.dump1(hpp) + hpp.write('}; // struct %s\n' % class_name) + hpp.write('bool parse_%s(%s& conf, io::Spanned key, io::Spanned value);\n' % (var_name, class_name)) + hpp.write('} // namespace %s\n' % namespace_name) + hpp.write('} // namespace tmwa\n') + cpp.write('bool parse_%s(%s& conf, io::Spanned key, io::Spanned value)\n{\n' % (var_name, class_name)) + # see NOTE about hashing in Option.dump2 + cpp.write(' int key_hash = 0;\n') + cpp.write(' if (key.data.size() > 0)\n') + cpp.write(' key_hash |= key.data[0] << 24;\n') + cpp.write(' if (key.data.size() > 1)\n') + cpp.write(' key_hash |= key.data[1] << 18;\n') + cpp.write(' if (key.data.size() > 2)\n') + cpp.write(' key_hash |= key.data[2] << 12;\n') + cpp.write(' if (key.data.size() > 3)\n') + cpp.write(' key_hash |= key.data[3] << 6;\n') + cpp.write(' if (key.data.size() > 4)\n') + cpp.write(' key_hash |= key.data[4] << 0;\n') + cpp.write(' switch (key_hash)\n') + cpp.write(' {\n') + x = None + for o in values: + x = o.dump2(cpp, x) + cpp.write(' break;\n') + cpp.write(' } // switch\n') + cpp.write(' key.span.error("Unknown config key"_s);\n') + cpp.write(' return false;\n') + cpp.write('} // fn parse_%s_conf()\n' % var_name) + cpp.write('} // namespace %s\n' % namespace_name) + cpp.write('} // namespace tmwa\n') + +class Realm(object): + __slots__ = ('path', 'groups') + + def __init__(self, path): + self.path = path + self.groups = {} + + def conf(self, name=None): + if not name: + name = self.path.split('/')[-1] + assert name not in self.groups, 'Duplicate group name: %s' % name + self.groups[name] = rv = Group(name) + return rv + + def dump(self): + for g in self.groups.values(): + g.dump_in(self.path, self.path.split('/')[-1]) + +class Everything(object): + __slots__ = ('realms') + + def __init__(self): + self.realms = {} + + def realm(self, path): + assert path not in self.realms, 'Duplicate realm path: %s' % path + self.realms[path] = rv = Realm(path) + return rv + + def dump(self): + for g in glob.glob('src/*/*_conf.[ch]pp'): + os.rename(g, g + '.old') + for v in self.realms.values(): + v.dump() + for g in glob.glob('src/*/*_conf.[ch]pp.old'): + print('Obsolete: %s' % g) + os.remove(g) + + +def lit(s): + return '"%s"_s' % s.replace('\\', '\\\\').replace('"', '\\"') + +def build_config(): + rv = Everything() + + # realms + login_realm = rv.realm('src/login') + admin_realm = rv.realm('src/admin') + char_realm = rv.realm('src/char') + map_realm = rv.realm('src/map') + monitor_realm = rv.realm('src/monitor') + + # confs + login_conf = login_realm.conf() + login_lan_conf = login_realm.conf('login_lan') + + admin_conf = admin_realm.conf() + + char_conf = char_realm.conf() + char_lan_conf = char_realm.conf('char_lan') + inter_conf = char_realm.conf('inter') + + map_conf = map_realm.conf() + battle_conf = map_realm.conf('battle') + + monitor_conf = monitor_realm.conf() + + # headers + cstdint_sys = SystemHeader('cstdint') + vector_sys = SystemHeader('vector') + bitset_sys = SystemHeader('bitset') + + ip_h = Header('src/net/ip.hpp') + rstring_h = Header('src/strings/rstring.hpp') + literal_h = Header('src/strings/literal.hpp') + ids_h = Header('src/mmo/ids.hpp') + strs_h = Header('src/mmo/strs.hpp') + timer_th = Header('src/net/timer.t.hpp') + login_th = Header('src/login/login.t.hpp') + udl_h = Header('src/ints/udl.hpp') + net_point_h = Header('src/proto2/net-Point.hpp') + char_h = Header('src/char/char.hpp') + map_h = Header('src/map/map.hpp') + map_th = Header('src/map/map.t.hpp') + npc_h = Header('src/map/npc.hpp') + npc_parse_h = Header('src/map/npc-parse.hpp') + + map_conf.extra(npc_parse_h) + + # types + bool = SimpleType('bool', set()) + #double = SimpleType('double', set()) + u8 = SimpleType('uint8_t', {cstdint_sys}) + u16 = SimpleType('uint16_t', {cstdint_sys}) + u32 = SimpleType('uint32_t', {cstdint_sys}) + u64 = SimpleType('uint64_t', {cstdint_sys}) + i8 = SimpleType('int8_t', {cstdint_sys}) + i16 = SimpleType('int16_t', {cstdint_sys}) + i32 = SimpleType('int32_t', {cstdint_sys}) + i64 = SimpleType('int64_t', {cstdint_sys}) + + percent = i32 + perk = i32 + per10k = i32 + #per10kd = double + per10kd = i32 + + IP4Address = SimpleType('IP4Address', {ip_h}) + IP4Mask = SimpleType('IP4Mask', {ip_h}) + IpSet = SimpleType('std::vector', {vector_sys, ip_h}) + RString = SimpleType('RString', {rstring_h, literal_h}) + GmLevel = SimpleType('GmLevel', {ids_h}) + hours = SimpleType('std::chrono::hours', {timer_th}) + seconds = SimpleType('std::chrono::seconds', {timer_th}) + milliseconds = SimpleType('std::chrono::milliseconds', {timer_th}) + ACO = SimpleType('ACO', {login_th}) + ServerName = SimpleType('ServerName', {strs_h}) + AccountName = SimpleType('AccountName', {strs_h}) + AccountPass = SimpleType('AccountPass', {strs_h}) + Point = SimpleType('Point', {net_point_h}) + CharName = SimpleType('CharName', {strs_h}) + CharBitset = SimpleType('std::bitset<256>', {bitset_sys}) + MapName = SimpleType('MapName', {strs_h}) + ATK = SimpleType('ATK', {map_th}) + + addmap = PhonyType(MapName, 'name', 'map_addmap(name);', {map_h}) + delmap = PhonyType(MapName, 'name', 'map_delmap(name);', {map_h}) + addnpc = PhonyType(RString, 'npc', 'npc_addsrcfile(npc);', {npc_h}) + delnpc = PhonyType(RString, 'npc', 'npc_delsrcfile(npc);', {npc_h}) + + + # options + login_lan_conf.opt('lan_char_ip', IP4Address, 'IP4_LOCALHOST') + login_lan_conf.opt('lan_subnet', IP4Mask, 'IP4Mask(IP4_LOCALHOST, IP4_BROADCAST)') + + login_conf.opt('admin_state', bool, 'false') + login_conf.opt('admin_pass', AccountPass, '{}') + login_conf.opt('ladminallowip', IpSet, '{}') + login_conf.opt('gm_pass', RString, '{}') + login_conf.opt('level_new_gm', GmLevel, 'GmLevel::from(60_u32)', {udl_h}) + login_conf.opt('new_account', bool, 'false') + login_conf.opt('login_port', u16, '6901', min='1024') + login_conf.opt('account_filename', RString, lit('save/account.txt')) + login_conf.opt('gm_account_filename', RString, lit('save/gm_account.txt')) + login_conf.opt('gm_account_filename_check_timer', seconds, '15_s') + login_conf.opt('login_log_filename', RString, lit('log/login.log')) + login_conf.opt('display_parse_login', bool, 'false') + login_conf.opt('display_parse_admin', bool, 'false') + login_conf.opt('display_parse_fromchar', i32, '0', min='0', max='2') + login_conf.opt('min_level_to_connect', GmLevel, 'GmLevel::from(0_u32)', {udl_h}) + login_conf.opt('order', ACO, 'ACO::DENY_ALLOW') + login_conf.opt('allow', IpSet, '{}') + login_conf.opt('deny', IpSet, '{}') + login_conf.opt('anti_freeze_enable', bool, 'false') + login_conf.opt('anti_freeze_interval', seconds, '15_s') + login_conf.opt('update_host', RString, '{}') + login_conf.opt('main_server', ServerName, '{}') + login_conf.opt('userid', AccountName, '{}') + login_conf.opt('passwd', AccountPass, '{}') + + + admin_conf.opt('login_ip', IP4Address, 'IP4_LOCALHOST') + admin_conf.opt('login_port', u16, '6901', min='1024') + admin_conf.opt('admin_pass', AccountPass, 'stringish("admin"_s)') + admin_conf.opt('ladmin_log_filename', RString, lit('log/ladmin.log')) + + + char_lan_conf.opt('lan_map_ip', IP4Address, 'IP4_LOCALHOST') + char_lan_conf.opt('lan_subnet', IP4Mask, 'IP4Mask(IP4_LOCALHOST, IP4_BROADCAST)') + + char_conf.opt('userid', AccountName, '{}') + char_conf.opt('passwd', AccountPass, '{}') + char_conf.opt('server_name', ServerName, '{}') + char_conf.opt('login_ip', IP4Address, '{}') + char_conf.opt('login_port', u16, '6901', min='1024') + char_conf.opt('char_ip', IP4Address, '{}') + char_conf.opt('char_port', u16, '6121', min='1024') + char_conf.opt('char_txt', RString, '{}') + char_conf.opt('max_connect_user', u32, '0') + char_conf.opt('autosave_time', seconds, 'DEFAULT_AUTOSAVE_INTERVAL', {char_h}, min='1_s') + char_conf.opt('start_point', Point, '{ {"001-1.gat"_s}, 273, 354 }') + char_conf.opt('unknown_char_name', CharName, 'stringish("Unknown"_s)') + char_conf.opt('char_log_filename', RString, lit('log/char.log')) + char_conf.opt('char_name_letters', CharBitset, '{}') + char_conf.opt('online_txt_filename', RString, lit('online.txt')) + char_conf.opt('online_html_filename', RString, lit('online.html')) + char_conf.opt('online_gm_display_min_level', GmLevel, 'GmLevel::from(20_u32)', {udl_h}) + char_conf.opt('online_refresh_html', u32, '20', min=1) + char_conf.opt('anti_freeze_enable', bool, 'false') + char_conf.opt('anti_freeze_interval', seconds, '6_s', min='5_s') + + inter_conf.opt('storage_txt', RString, lit('save/storage.txt')) + inter_conf.opt('party_txt', RString, lit('save/party.txt')) + inter_conf.opt('accreg_txt', RString, lit('save/accreg.txt')) + inter_conf.opt('party_share_level', u32, '10') + + map_conf.opt('userid', AccountName, '{}') + map_conf.opt('passwd', AccountPass, '{}') + map_conf.opt('char_ip', IP4Address, '{}') + map_conf.opt('char_port', u16, '6121', min='1024') + map_conf.opt('map_ip', IP4Address, '{}') + map_conf.opt('map_port', u16, '5121', min='1024') + map_conf.opt('map', addmap, '{}') + map_conf.opt('delmap', delmap, '{}') + map_conf.opt('npc', addnpc, '{}') + map_conf.opt('delnpc', delnpc, '{}') + map_conf.opt('autosave_time', seconds, 'DEFAULT_AUTOSAVE_INTERVAL', {map_h}) + map_conf.opt('motd_txt', RString, lit('conf/motd.txt')) + map_conf.opt('mapreg_txt', RString, lit('save/mapreg.txt')) + map_conf.opt('gm_log', RString, '{}') + map_conf.opt('log_file', RString, '{}') + + battle_conf.opt('warp_point_debug', bool, 'false') + battle_conf.opt('enemy_critical', bool, 'false') + battle_conf.opt('enemy_critical_rate', percent, '100') + battle_conf.opt('enemy_str', bool, 'true') + battle_conf.opt('enemy_perfect_flee', bool, 'false') + battle_conf.opt('casting_rate', percent, '100') + battle_conf.opt('delay_rate', percent, '100') + battle_conf.opt('delay_dependon_dex', bool, 'false') + battle_conf.opt('skill_delay_attack_enable', bool, 'false') + battle_conf.opt('monster_skill_add_range', i32, '0') + battle_conf.opt('player_damage_delay', bool, '1') + battle_conf.opt('flooritem_lifetime', milliseconds, 'LIFETIME_FLOORITEM', min='1_s') + battle_conf.opt('item_auto_get', bool, 'false') + battle_conf.opt('item_first_get_time', milliseconds, '3_s') + battle_conf.opt('item_second_get_time', milliseconds, '1_s') + battle_conf.opt('item_third_get_time', milliseconds, '1_s') + battle_conf.opt('base_exp_rate', percent, '100') + battle_conf.opt('job_exp_rate', percent, '100') + battle_conf.opt('death_penalty_type', i32, '0', min='0', max='2') + battle_conf.opt('death_penalty_base', per10kd, '0') + battle_conf.opt('death_penalty_job', per10kd, '0') + battle_conf.opt('restart_hp_rate', percent, '0', min='0', max='100') + battle_conf.opt('restart_sp_rate', percent, '0', min='0', max='100') + battle_conf.opt('monster_hp_rate', percent, '0') + battle_conf.opt('monster_max_aspd', milliseconds, '199_ms', pre='conf.monster_max_aspd = 2000_ms - conf.monster_max_aspd * 10;', min='10_ms', max='1000_ms') + battle_conf.opt('atcommand_gm_only', bool, '0') + battle_conf.opt('atcommand_spawn_quantity_limit', i32, '{}') + battle_conf.opt('gm_all_equipment', GmLevel, 'GmLevel::from(0_u32)', {udl_h}) + battle_conf.opt('monster_active_enable', bool, 'true') + battle_conf.opt('mob_skill_use', bool, 'true') + battle_conf.opt('mob_count_rate', percent, '100') + battle_conf.opt('basic_skill_check', bool, 'true') + battle_conf.opt('player_invincible_time', milliseconds, '5_s') + battle_conf.opt('skill_min_damage', bool, 'false') + battle_conf.opt('natural_healhp_interval', milliseconds, '6_s', {map_h}, min='NATURAL_HEAL_INTERVAL') + battle_conf.opt('natural_healsp_interval', milliseconds, '8_s', {map_h}, min='NATURAL_HEAL_INTERVAL') + battle_conf.opt('natural_heal_weight_rate', percent, '50', min='50', max='101') + battle_conf.opt('arrow_decrement', bool, 'true') + battle_conf.opt('max_aspd', milliseconds, '199_ms', pre='conf.max_aspd = 2000_ms - conf.max_aspd * 10;', min='10_ms', max='1000_ms') + battle_conf.opt('max_hp', i32, '32500', min='100', max='1000000') + battle_conf.opt('max_sp', i32, '32500', min='100', max='1000000') + battle_conf.opt('max_lv', i32, '99') + battle_conf.opt('max_parameter', i32, '99', min='10', max='10000') + battle_conf.opt('monster_skill_log', bool, 'false') + battle_conf.opt('battle_log', bool, 'false') + battle_conf.opt('save_log', bool, 'false') + battle_conf.opt('error_log', bool, 'true') + battle_conf.opt('etc_log', bool, 'true') + battle_conf.opt('save_clothcolor', bool, 'false') + battle_conf.opt('undead_detect_type', i32, '0', min='0', max='2') + battle_conf.opt('agi_penaly_type', i32, '0', min='0', max='2') + battle_conf.opt('agi_penaly_count', i32, '3', min='2') + battle_conf.opt('agi_penaly_num', i32, '0') + battle_conf.opt('vit_penaly_type', i32, '0', min='0', max='2') + battle_conf.opt('vit_penaly_count', i32, '3', min='2') + battle_conf.opt('vit_penaly_num', i32, '0') + battle_conf.opt('mob_changetarget_byskill', bool, 'false') + battle_conf.opt('player_attack_direction_change', bool, 'true') + battle_conf.opt('monster_attack_direction_change', bool, 'true') + battle_conf.opt('display_delay_skill_fail', bool, 'true') + battle_conf.opt('prevent_logout', bool, 'true') + battle_conf.opt('alchemist_summon_reward', bool, '{}') + battle_conf.opt('maximum_level', i32, '255') + battle_conf.opt('drops_by_luk', percent, '0') + battle_conf.opt('monsters_ignore_gm', bool, '{}') + battle_conf.opt('multi_level_up', bool, 'false') + battle_conf.opt('pk_mode', bool, 'false') + battle_conf.opt('agi_penaly_count_lv', ATK, 'ATK::FLEE') + battle_conf.opt('vit_penaly_count_lv', ATK, 'ATK::DEF') + battle_conf.opt('hide_GM_session', bool, 'false') + battle_conf.opt('invite_request_check', bool, 'true') + battle_conf.opt('disp_experience', bool, '0') + battle_conf.opt('hack_info_GM_level', GmLevel, 'GmLevel::from(60_u32)', {udl_h}) + battle_conf.opt('any_warp_GM_min_level', GmLevel, 'GmLevel::from(20_u32)', {udl_h}) + battle_conf.opt('min_hair_style', i32, '0') + battle_conf.opt('max_hair_style', i32, '20') + battle_conf.opt('min_hair_color', i32, '0') + battle_conf.opt('max_hair_color', i32, '9') + battle_conf.opt('min_cloth_color', i32, '0') + battle_conf.opt('max_cloth_color', i32, '4') + battle_conf.opt('castrate_dex_scale', i32, '150') + battle_conf.opt('area_size', i32, '14') + battle_conf.opt('chat_lame_penalty', i32, '2') + battle_conf.opt('chat_spam_threshold', seconds, '10_s', min='0_s', max='32767_s') + battle_conf.opt('chat_spam_flood', i32, '10', min='0', max='32767') + battle_conf.opt('chat_spam_ban', hours, '1_h', min='0_h', max='32767_h') + battle_conf.opt('chat_spam_warn', i32, '8', min='0', max='32767') + battle_conf.opt('chat_maxline', i32, '255', min='1', max='512') + battle_conf.opt('packet_spam_threshold', seconds, '2_s', min='0_s', max='32767_s') + battle_conf.opt('packet_spam_flood', i32, '30', min='0', max='32767') + battle_conf.opt('packet_spam_kick', bool, 'true') + battle_conf.opt('mask_ip_gms', bool, 'true') + battle_conf.opt('drop_pickup_safety_zone', i32, '20') + battle_conf.opt('itemheal_regeneration_factor', i32, '1') + battle_conf.opt('mob_splash_radius', i32, '-1', min='-1') + + monitor_conf.opt('login_server', RString, lit('./login-server')) + monitor_conf.opt('char_server', RString, lit('./char-server')) + monitor_conf.opt('map_server', RString, lit('./map-server')) + monitor_conf.opt('workdir', RString, '{}') # initialized specially + + return rv + +def main(): + cfg = build_config() + cfg.dump() + +if __name__ == '__main__': + main() -- cgit v1.2.3-60-g2f50