mirror of
https://github.com/kuitoi/kuitoi-Server.git
synced 2026-10-26 14:10:22 +00:00
[~] 0.4.5 > 0.4.6
[+] Recreate config (if empty) [+] Hidden config [!] FIX rl [!] Update getting loop [-] core_player_set_id [~] Update copyrights [~] Minor
This commit is contained in:
1 parent
2eb7e8801b
commit
a923dbea1f
13 files changed
+136
-103
No files matched your search
+31
-34
@@ -19,7 +19,7 @@ class Client:
|
|||||||
def __init__(self, reader, writer, core):
|
def __init__(self, reader, writer, core):
|
||||||
self.__reader = reader
|
self.__reader = reader
|
||||||
self.__writer = writer
|
self.__writer = writer
|
||||||
self.__Core = core
|
self._core = core
|
||||||
self.__alive = True
|
self.__alive = True
|
||||||
self.__packets_queue = []
|
self.__packets_queue = []
|
||||||
self.__tasks = []
|
self.__tasks = []
|
||||||
@@ -38,7 +38,7 @@ class Client:
|
|||||||
self._identifiers = []
|
self._identifiers = []
|
||||||
self._cars = [None] * 21 # Max 20 cars per player + 1 snowman
|
self._cars = [None] * 21 # Max 20 cars per player + 1 snowman
|
||||||
self._focus_car = -1
|
self._focus_car = -1
|
||||||
self._snowman = {"id": -1, "packet": ""}
|
self._unicycle = {"id": -1, "packet": ""}
|
||||||
self._connect_time = 0
|
self._connect_time = 0
|
||||||
self._last_position = {}
|
self._last_position = {}
|
||||||
self._lock = Lock()
|
self._lock = Lock()
|
||||||
@@ -155,7 +155,7 @@ class Client:
|
|||||||
|
|
||||||
if to_all:
|
if to_all:
|
||||||
code = chr(data[0])
|
code = chr(data[0])
|
||||||
for client in self.__Core.clients:
|
for client in self._core.clients:
|
||||||
if not client or (client is self and not to_self):
|
if not client or (client is self and not to_self):
|
||||||
continue
|
continue
|
||||||
if not to_udp or code in ['V', 'W', 'Y', 'E']:
|
if not to_udp or code in ['V', 'W', 'Y', 'E']:
|
||||||
@@ -174,8 +174,7 @@ class Client:
|
|||||||
data = b"ABG:" + zlib.compress(data, level=zlib.Z_BEST_COMPRESSION)
|
data = b"ABG:" + zlib.compress(data, level=zlib.Z_BEST_COMPRESSION)
|
||||||
|
|
||||||
if to_udp:
|
if to_udp:
|
||||||
udp_sock = self._udp_sock[0]
|
udp_sock, udp_addr = self._udp_sock
|
||||||
udp_addr = self._udp_sock[1]
|
|
||||||
# self.log.debug(f'[UDP] len: {len(data)}; send: {data!r}')
|
# self.log.debug(f'[UDP] len: {len(data)}; send: {data!r}')
|
||||||
if udp_sock and udp_addr:
|
if udp_sock and udp_addr:
|
||||||
try:
|
try:
|
||||||
@@ -297,7 +296,7 @@ class Client:
|
|||||||
file = data[1:].decode(config.enc)
|
file = data[1:].decode(config.enc)
|
||||||
self.log.info(i18n.client_mod_request.format(repr(file)))
|
self.log.info(i18n.client_mod_request.format(repr(file)))
|
||||||
size = -1
|
size = -1
|
||||||
for mod in self.__Core.mods_list:
|
for mod in self._core.mods_list:
|
||||||
if type(mod) == int:
|
if type(mod) == int:
|
||||||
continue
|
continue
|
||||||
if mod.get('path') == file:
|
if mod.get('path') == file:
|
||||||
@@ -318,9 +317,9 @@ class Client:
|
|||||||
await self.kick("Missing download socket")
|
await self.kick("Missing download socket")
|
||||||
return
|
return
|
||||||
if config.Options['use_queue']:
|
if config.Options['use_queue']:
|
||||||
while self.__Core.lock_upload:
|
while self._core.lock_upload:
|
||||||
await asyncio.sleep(.2)
|
await asyncio.sleep(.2)
|
||||||
self.__Core.lock_upload = True
|
self._core.lock_upload = True
|
||||||
speed = config.Options["speed_limit"]
|
speed = config.Options["speed_limit"]
|
||||||
if speed:
|
if speed:
|
||||||
speed = speed / 2
|
speed = speed / 2
|
||||||
@@ -332,8 +331,8 @@ class Client:
|
|||||||
]
|
]
|
||||||
sl0, sl1 = await asyncio.gather(*uploads)
|
sl0, sl1 = await asyncio.gather(*uploads)
|
||||||
tr = (time.monotonic() - t) or 0.0001
|
tr = (time.monotonic() - t) or 0.0001
|
||||||
if self.__Core.lock_upload:
|
if self._core.lock_upload:
|
||||||
self.__Core.lock_upload = False
|
self._core.lock_upload = False
|
||||||
msg = i18n.client_mod_sent.format(round(size / MB, 3), math.ceil(size / tr / MB), int(tr))
|
msg = i18n.client_mod_sent.format(round(size / MB, 3), math.ceil(size / tr / MB), int(tr))
|
||||||
if speed:
|
if speed:
|
||||||
msg += i18n.client_mod_sent_limit.format(int(speed * 2))
|
msg += i18n.client_mod_sent_limit.format(int(speed * 2))
|
||||||
@@ -349,7 +348,7 @@ class Client:
|
|||||||
elif data.startswith(b"SR"):
|
elif data.startswith(b"SR"):
|
||||||
path_list = ''
|
path_list = ''
|
||||||
size_list = ''
|
size_list = ''
|
||||||
for mod in self.__Core.mods_list:
|
for mod in self._core.mods_list:
|
||||||
if type(mod) == int:
|
if type(mod) == int:
|
||||||
continue
|
continue
|
||||||
path_list += f"{mod['path']};"
|
path_list += f"{mod['path']};"
|
||||||
@@ -391,7 +390,7 @@ class Client:
|
|||||||
car_data = data[2:]
|
car_data = data[2:]
|
||||||
car_id = next((i for i, car in enumerate(self._cars) if car is None), len(self._cars))
|
car_id = next((i for i, car in enumerate(self._cars) if car is None), len(self._cars))
|
||||||
cars_count = len(self._cars) - self._cars.count(None)
|
cars_count = len(self._cars) - self._cars.count(None)
|
||||||
if self._snowman['id'] != -1:
|
if self._unicycle['id'] != -1:
|
||||||
cars_count -= 1 # -1 for unicycle
|
cars_count -= 1 # -1 for unicycle
|
||||||
self.log.debug(f"car_id={car_id}, cars_count={cars_count}")
|
self.log.debug(f"car_id={car_id}, cars_count={cars_count}")
|
||||||
car_json = {}
|
car_json = {}
|
||||||
@@ -416,12 +415,12 @@ class Client:
|
|||||||
snowman = car_json.get("jbm") == "unicycle"
|
snowman = car_json.get("jbm") == "unicycle"
|
||||||
if allow and config.Game['cars'] > cars_count or (snowman and allow_snowman) or over_spawn:
|
if allow and config.Game['cars'] > cars_count or (snowman and allow_snowman) or over_spawn:
|
||||||
if snowman:
|
if snowman:
|
||||||
unicycle_id = self._snowman['id']
|
unicycle_id = self._unicycle['id']
|
||||||
if unicycle_id != -1:
|
if unicycle_id != -1:
|
||||||
self.log.debug(f"Delete old unicycle: unicycle_id={unicycle_id}")
|
self.log.debug(f"Delete old unicycle: car_id={unicycle_id}")
|
||||||
self._cars[unicycle_id] = None
|
self._cars[unicycle_id] = None
|
||||||
await self._send(f"Od:{self.cid}-{unicycle_id}", to_all=True, to_self=True)
|
await self._send(f"Od:{self.cid}-{unicycle_id}", to_all=True, to_self=True)
|
||||||
self._snowman = {"id": car_id, "packet": pkt}
|
self._unicycle = {"id": car_id, "packet": pkt}
|
||||||
self.log.debug(f"Unicycle spawn accepted: car_id={car_id}")
|
self.log.debug(f"Unicycle spawn accepted: car_id={car_id}")
|
||||||
else:
|
else:
|
||||||
self.log.debug(f"Car spawn accepted: car_id={car_id}")
|
self.log.debug(f"Car spawn accepted: car_id={car_id}")
|
||||||
@@ -469,8 +468,8 @@ class Client:
|
|||||||
car = self._cars[car_id]
|
car = self._cars[car_id]
|
||||||
if car['snowman']:
|
if car['snowman']:
|
||||||
self.log.debug(f"Snowman found")
|
self.log.debug(f"Snowman found")
|
||||||
unicycle_id = self._snowman['id']
|
unicycle_id = self._unicycle['id']
|
||||||
self._snowman['id'] = -1
|
self._unicycle['id'] = -1
|
||||||
self._cars[unicycle_id] = None
|
self._cars[unicycle_id] = None
|
||||||
self._cars[car_id] = None
|
self._cars[car_id] = None
|
||||||
await self._send(f"Od:{self.cid}-{car_id}", to_all=True, to_self=True)
|
await self._send(f"Od:{self.cid}-{car_id}", to_all=True, to_self=True)
|
||||||
@@ -482,7 +481,7 @@ class Client:
|
|||||||
async def _edit_car(self, raw_data, data):
|
async def _edit_car(self, raw_data, data):
|
||||||
cid, car_id = self._get_cid_vid(raw_data)
|
cid, car_id = self._get_cid_vid(raw_data)
|
||||||
if car_id != -1 and self._cars[car_id]:
|
if car_id != -1 and self._cars[car_id]:
|
||||||
client = self.__Core.get_client(cid=cid)
|
client = self._core.get_client(cid=cid)
|
||||||
if client:
|
if client:
|
||||||
car = client._cars[car_id]
|
car = client._cars[car_id]
|
||||||
new_car_json = {}
|
new_car_json = {}
|
||||||
@@ -506,8 +505,8 @@ class Client:
|
|||||||
|
|
||||||
if cid == self.cid or allow or admin_allow:
|
if cid == self.cid or allow or admin_allow:
|
||||||
if car['snowman']:
|
if car['snowman']:
|
||||||
unicycle_id = self._snowman['id']
|
unicycle_id = self._unicycle['id']
|
||||||
self._snowman['id'] = -1
|
self._unicycle['id'] = -1
|
||||||
self.log.debug(f"Delete snowman")
|
self.log.debug(f"Delete snowman")
|
||||||
await self._send(f"Od:{self.cid}-{unicycle_id}", to_all=True, to_self=True)
|
await self._send(f"Od:{self.cid}-{unicycle_id}", to_all=True, to_self=True)
|
||||||
self._cars[unicycle_id] = None
|
self._cars[unicycle_id] = None
|
||||||
@@ -593,7 +592,7 @@ class Client:
|
|||||||
await self._send(f"Sn{self.nick}", to_all=True) # I don't know for what it
|
await self._send(f"Sn{self.nick}", to_all=True) # I don't know for what it
|
||||||
await self._send(f"J{i18n.game_welcome_message.format(self.nick)}", to_all=True) # Hello message
|
await self._send(f"J{i18n.game_welcome_message.format(self.nick)}", to_all=True) # Hello message
|
||||||
|
|
||||||
for client in self.__Core.clients:
|
for client in self._core.clients:
|
||||||
if not client:
|
if not client:
|
||||||
continue
|
continue
|
||||||
for car in client._cars:
|
for car in client._cars:
|
||||||
@@ -656,20 +655,18 @@ class Client:
|
|||||||
self.__alive = False
|
self.__alive = False
|
||||||
return
|
return
|
||||||
|
|
||||||
# Codes: V W X Y
|
|
||||||
if 89 >= data[0] >= 86:
|
|
||||||
await self._send(data, to_all=True, to_self=False)
|
|
||||||
return
|
|
||||||
|
|
||||||
_bytes = False
|
_bytes = False
|
||||||
try:
|
try:
|
||||||
data = data.decode()
|
data = data.decode()
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
_bytes = True
|
_bytes = True
|
||||||
self.log.error(f"UnicodeDecodeError: {data}")
|
self.log.error(f"UnicodeDecodeError: {data}")
|
||||||
self.log.info("Some things are skipping...")
|
|
||||||
|
|
||||||
# Codes: p, Z in udp_server.py
|
if data[0] in ['V', 'W', 'Y', 'E', 'N']:
|
||||||
|
await self._send(data, to_all=True, to_self=False)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Codes: p, Z, X in udp_server.py
|
||||||
match data[0]: # At data[0] code
|
match data[0]: # At data[0] code
|
||||||
case "H": # Map load, client ready
|
case "H": # Map load, client ready
|
||||||
await self._connected_handler()
|
await self._connected_handler()
|
||||||
@@ -700,8 +697,8 @@ class Client:
|
|||||||
ev.call_lua_event(event_name, self.cid, even_data)
|
ev.call_lua_event(event_name, self.cid, even_data)
|
||||||
ev.call_event(event_name, data=even_data, player=self)
|
ev.call_event(event_name, data=even_data, player=self)
|
||||||
await ev.call_async_event(event_name, data=even_data, player=self)
|
await ev.call_async_event(event_name, data=even_data, player=self)
|
||||||
case "N":
|
case _:
|
||||||
await self._send(data, to_all=True, to_self=False)
|
self.log.warning(f"TCP [{self.cid}] Unknown code: {data[0]}; {data}")
|
||||||
|
|
||||||
async def _looper(self):
|
async def _looper(self):
|
||||||
ev.call_lua_event("onPlayerConnecting", self.cid)
|
ev.call_lua_event("onPlayerConnecting", self.cid)
|
||||||
@@ -728,7 +725,7 @@ class Client:
|
|||||||
await asyncio.sleep(0.3)
|
await asyncio.sleep(0.3)
|
||||||
self.__alive = False
|
self.__alive = False
|
||||||
if (self.cid > 0 or self.nick is not None) and \
|
if (self.cid > 0 or self.nick is not None) and \
|
||||||
self.__Core.clients_by_nick.get(self.nick):
|
self._core.clients_by_nick.get(self.nick):
|
||||||
for i, car in enumerate(self._cars):
|
for i, car in enumerate(self._cars):
|
||||||
if not car:
|
if not car:
|
||||||
continue
|
continue
|
||||||
@@ -746,9 +743,9 @@ class Client:
|
|||||||
round((time.monotonic() - self._connect_time) / 60, 2)
|
round((time.monotonic() - self._connect_time) / 60, 2)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.__Core.clients[self.cid] = None
|
self._core.clients[self.cid] = None
|
||||||
del self.__Core.clients_by_id[self.cid]
|
del self._core.clients_by_id[self.cid]
|
||||||
del self.__Core.clients_by_nick[self.nick]
|
del self._core.clients_by_nick[self.nick]
|
||||||
else:
|
else:
|
||||||
self.log.debug(f"Removing client; Closing connection...")
|
self.log.debug(f"Removing client; Closing connection...")
|
||||||
try:
|
try:
|
||||||
|
|||||||
+2
-2
@@ -25,7 +25,7 @@ class Client:
|
|||||||
self._log = utils.get_logger("client(id: )")
|
self._log = utils.get_logger("client(id: )")
|
||||||
self._addr: Tuple[str, int] = writer.get_extra_info("sockname")
|
self._addr: Tuple[str, int] = writer.get_extra_info("sockname")
|
||||||
self._loop = asyncio.get_event_loop()
|
self._loop = asyncio.get_event_loop()
|
||||||
self.__Core: Core = core
|
self._core: Core = core
|
||||||
self._cid: int = -1
|
self._cid: int = -1
|
||||||
self._key: str = None
|
self._key: str = None
|
||||||
self.nick: str = None
|
self.nick: str = None
|
||||||
@@ -37,7 +37,7 @@ class Client:
|
|||||||
self._focus_car = -1
|
self._focus_car = -1
|
||||||
self._identifiers = []
|
self._identifiers = []
|
||||||
self._cars: List[Union[Dict[str, Union[str, bool, Dict[str, Union[str, List[int], float]]]], None]] = []
|
self._cars: List[Union[Dict[str, Union[str, bool, Dict[str, Union[str, List[int], float]]]], None]] = []
|
||||||
self._snowman: Dict[str, Union[int, str]] = {"id": -1, "packet": ""}
|
self._unicycle: Dict[str, Union[int, str]] = {"id": -1, "packet": ""}
|
||||||
self._last_position = {}
|
self._last_position = {}
|
||||||
self._lock = Lock()
|
self._lock = Lock()
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
__title__ = 'KuiToi-Server'
|
__title__ = 'KuiToi-Server'
|
||||||
__description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.'
|
__description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.'
|
||||||
__url__ = 'https://github.com/kuitoi/kuitoi-Server'
|
__url__ = 'https://github.com/kuitoi/kuitoi-Server'
|
||||||
__version__ = '0.4.5'
|
__version__ = '0.4.6'
|
||||||
__build__ = 2303 # Я это считаю лог файлами
|
__build__ = 2421 # Я это считаю лог файлами
|
||||||
__author__ = 'SantaSpeen'
|
__author__ = 'SantaSpeen'
|
||||||
__author_email__ = 'admin@kuitoi.su'
|
__author_email__ = 'admin@kuitoi.su'
|
||||||
__license__ = "FPA"
|
__license__ = "FPA"
|
||||||
__copyright__ = 'Copyright 2023 © SantaSpeen (Maxim Khomutov)'
|
__copyright__ = 'Copyright 2024 © SantaSpeen (Maxim Khomutov)'
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import builtins
|
import builtins
|
||||||
@@ -43,7 +43,7 @@ config_path = "kuitoi.yml"
|
|||||||
if args.config:
|
if args.config:
|
||||||
config_path = args.config
|
config_path = args.config
|
||||||
config_provider = ConfigProvider(config_path)
|
config_provider = ConfigProvider(config_path)
|
||||||
config = config_provider.open_config()
|
config = config_provider.read()
|
||||||
builtins.config = config
|
builtins.config = config
|
||||||
config.enc = config.Options['encoding']
|
config.enc = config.Options['encoding']
|
||||||
if config.Options['debug'] is True:
|
if config.Options['debug'] is True:
|
||||||
@@ -60,6 +60,7 @@ ml.builtins_hook()
|
|||||||
log.debug("Initializing EventsSystem...")
|
log.debug("Initializing EventsSystem...")
|
||||||
ev = EventsSystem()
|
ev = EventsSystem()
|
||||||
ev.builtins_hook()
|
ev.builtins_hook()
|
||||||
|
ev.register("get_version", lambda _: {"version": __version__, "build": __build__})
|
||||||
|
|
||||||
log.info(i18n.hello)
|
log.info(i18n.hello)
|
||||||
log.info(i18n.config_path.format(config_path))
|
log.info(i18n.config_path.format(config_path))
|
||||||
@@ -68,7 +69,7 @@ log.debug("Initializing BeamMP Server system...")
|
|||||||
# Key handler..
|
# Key handler..
|
||||||
if not config.Auth['private'] and not config.Auth['key']:
|
if not config.Auth['private'] and not config.Auth['key']:
|
||||||
log.warn(i18n.auth_need_key)
|
log.warn(i18n.auth_need_key)
|
||||||
url = "https://beammp.com/k/keys"
|
url = "https://keymaster.beammp.com/login"
|
||||||
if shortcuts.yes_no_dialog(
|
if shortcuts.yes_no_dialog(
|
||||||
title='BeamMP Server Key',
|
title='BeamMP Server Key',
|
||||||
text=i18n.GUI_need_key_message,
|
text=i18n.GUI_need_key_message,
|
||||||
@@ -90,7 +91,7 @@ if not config.Auth['private'] and not config.Auth['key']:
|
|||||||
text=i18n.GUI_enter_key_message,
|
text=i18n.GUI_enter_key_message,
|
||||||
ok_text=i18n.GUI_ok,
|
ok_text=i18n.GUI_ok,
|
||||||
cancel_text=i18n.GUI_cancel).run()
|
cancel_text=i18n.GUI_cancel).run()
|
||||||
config_provider.save_config()
|
config_provider.save()
|
||||||
if not config.Auth['private'] and not config.Auth['key']:
|
if not config.Auth['private'] and not config.Auth['key']:
|
||||||
log.error(i18n.auth_empty_key)
|
log.error(i18n.auth_empty_key)
|
||||||
log.info(i18n.stop)
|
log.info(i18n.stop)
|
||||||
|
|||||||
+48
-30
@@ -27,7 +27,8 @@ class Core:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.log = utils.get_logger("core")
|
self.log = utils.get_logger("core")
|
||||||
self.loop = asyncio.get_event_loop()
|
self.loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(self.loop)
|
||||||
self.start_time = time.monotonic()
|
self.start_time = time.monotonic()
|
||||||
self.run = False
|
self.run = False
|
||||||
self.direct = False
|
self.direct = False
|
||||||
@@ -47,7 +48,7 @@ class Core:
|
|||||||
self.lock_upload = False
|
self.lock_upload = False
|
||||||
|
|
||||||
self.client_major_version = "2.0"
|
self.client_major_version = "2.0"
|
||||||
self.BeamMP_version = "3.4.1" # 20.07.2023
|
self.BeamMP_version = "3.4.1" # 16.07.2024
|
||||||
|
|
||||||
ev.register("_get_BeamMP_version", lambda x: tuple([int(i) for i in self.BeamMP_version.split(".")]))
|
ev.register("_get_BeamMP_version", lambda x: tuple([int(i) for i in self.BeamMP_version.split(".")]))
|
||||||
ev.register("_get_player", lambda x: self.get_client(**x['kwargs']))
|
ev.register("_get_player", lambda x: self.get_client(**x['kwargs']))
|
||||||
@@ -103,6 +104,7 @@ class Core:
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
async def check_alive(self):
|
async def check_alive(self):
|
||||||
|
self.log.debug("Starting alive checker.")
|
||||||
maxp = config.Game['players']
|
maxp = config.Game['players']
|
||||||
try:
|
try:
|
||||||
while self.run:
|
while self.run:
|
||||||
@@ -138,6 +140,8 @@ class Core:
|
|||||||
uvserver.run()
|
uvserver.run()
|
||||||
|
|
||||||
async def stop_me(self):
|
async def stop_me(self):
|
||||||
|
if not config.WebAPI['enabled']:
|
||||||
|
return
|
||||||
while webapp.data_run[0]:
|
while webapp.data_run[0]:
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
self.run = False
|
self.run = False
|
||||||
@@ -145,31 +149,44 @@ class Core:
|
|||||||
|
|
||||||
# noinspection SpellCheckingInspection,PyPep8Naming
|
# noinspection SpellCheckingInspection,PyPep8Naming
|
||||||
async def heartbeat(self, test=False):
|
async def heartbeat(self, test=False):
|
||||||
if config.Auth["private"] or self.direct:
|
try:
|
||||||
if test:
|
self.log.debug("Starting heartbeat.")
|
||||||
self.log.info(i18n.core_direct_mode)
|
if config.Auth["private"] or self.direct:
|
||||||
self.direct = True
|
if test:
|
||||||
return
|
self.log.info(i18n.core_direct_mode)
|
||||||
|
self.direct = True
|
||||||
|
return
|
||||||
|
|
||||||
BEAM_backend = ["backend.beammp.com", "backup1.beammp.com", "backup2.beammp.com"]
|
BEAM_backend = ["backend.beammp.com", "backup1.beammp.com", "backup2.beammp.com"]
|
||||||
modlist = ""
|
_map = config.Game['map'] if "/" in config.Game['map'] else f"/levels/{config.Game['map']}/info.json"
|
||||||
for mod in self.mods_list:
|
tags = config.Server['tags'].replace(", ", ";").replace(",", ";")
|
||||||
if type(mod) == int:
|
if tags and tags[-1:] != ";":
|
||||||
continue
|
tags += ";"
|
||||||
modlist += f"/{os.path.basename(mod['path'])};"
|
modlist = "".join(f"/{os.path.basename(mod['path'])};" for mod in self.mods_list[1:])
|
||||||
modstotalsize = self.mods_list[0]
|
modstotalsize = self.mods_list[0]
|
||||||
modstotal = len(self.mods_list) - 1
|
modstotal = len(self.mods_list) - 1
|
||||||
while self.run:
|
while self.run:
|
||||||
try:
|
playerslist = "".join(f"{client.nick};" for client in self.clients if client and client.alive)
|
||||||
data = {"uuid": config.Auth["key"], "players": len(self.clients_by_id),
|
data = {
|
||||||
"maxplayers": config.Game["players"], "port": config.Server["server_port"],
|
"uuid": config.Auth["key"],
|
||||||
"map": f"/levels/{config.Game['map']}/info.json", "private": config.Auth['private'],
|
"players": len(self.clients_by_id),
|
||||||
"version": self.BeamMP_version, "clientversion": self.client_major_version,
|
"maxplayers": config.Game["players"],
|
||||||
"name": config.Server["name"], "modlist": modlist, "modstotalsize": modstotalsize,
|
"port": config.Server["server_port"],
|
||||||
"modstotal": modstotal, "playerslist": "", "desc": config.Server['description'], "pass": False}
|
"map": _map,
|
||||||
|
"private": config.Auth['private'],
|
||||||
|
"version": self.BeamMP_version,
|
||||||
|
"clientversion": self.client_major_version,
|
||||||
|
"name": config.Server["name"],
|
||||||
|
"tags": tags,
|
||||||
|
"guests": not config.Auth["private"],
|
||||||
|
"modlist": modlist,
|
||||||
|
"modstotalsize": modstotalsize,
|
||||||
|
"modstotal": modstotal,
|
||||||
|
"playerslist": playerslist,
|
||||||
|
"desc": config.Server['description'],
|
||||||
|
"pass": False
|
||||||
|
}
|
||||||
|
|
||||||
# Sentry?
|
|
||||||
ok = False
|
|
||||||
body = {}
|
body = {}
|
||||||
for server_url in BEAM_backend:
|
for server_url in BEAM_backend:
|
||||||
url = "https://" + server_url + "/heartbeat"
|
url = "https://" + server_url + "/heartbeat"
|
||||||
@@ -177,14 +194,15 @@ class Core:
|
|||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.post(url, data=data, headers={"api-v": "2"}) as response:
|
async with session.post(url, data=data, headers={"api-v": "2"}) as response:
|
||||||
code = response.status
|
code = response.status
|
||||||
|
# text = await response.text()
|
||||||
|
# self.log.debug(f"[HB] res={text}")
|
||||||
body = await response.json()
|
body = await response.json()
|
||||||
ok = True
|
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.debug(f"Auth: Error `{e}` while auth with `{server_url}`")
|
self.log.debug(f"Auth: Error `{e}` while auth with `{server_url}`")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if ok:
|
if body:
|
||||||
if not (body.get("status") is not None and
|
if not (body.get("status") is not None and
|
||||||
body.get("code") is not None and
|
body.get("code") is not None and
|
||||||
body.get("msg") is not None):
|
body.get("msg") is not None):
|
||||||
@@ -216,11 +234,11 @@ class Core:
|
|||||||
# raise KeyboardInterrupt
|
# raise KeyboardInterrupt
|
||||||
|
|
||||||
if test:
|
if test:
|
||||||
return ok
|
return bool(body)
|
||||||
|
|
||||||
await asyncio.sleep(5)
|
await asyncio.sleep(5)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.error(f"Error in heartbeat: {e}")
|
self.log.error(f"Error in heartbeat: {e}")
|
||||||
|
|
||||||
async def kick_cmd(self, args):
|
async def kick_cmd(self, args):
|
||||||
if not len(args) > 0:
|
if not len(args) > 0:
|
||||||
|
|||||||
@@ -94,9 +94,8 @@ class TCPServer:
|
|||||||
await client.kick(i18n.core_player_kick_server_full)
|
await client.kick(i18n.core_player_kick_server_full)
|
||||||
return False, client
|
return False, client
|
||||||
else:
|
else:
|
||||||
self.log.info(i18n.core_identifying_okay)
|
|
||||||
await self.Core.insert_client(client)
|
await self.Core.insert_client(client)
|
||||||
client.log.info(i18n.core_player_set_id.format(client.pid))
|
client.log.info(i18n.core_identifying_okay)
|
||||||
|
|
||||||
return True, client
|
return True, client
|
||||||
|
|
||||||
@@ -137,7 +136,8 @@ class TCPServer:
|
|||||||
try:
|
try:
|
||||||
ip = writer.get_extra_info('peername')[0]
|
ip = writer.get_extra_info('peername')[0]
|
||||||
if self.rl.is_banned(ip):
|
if self.rl.is_banned(ip):
|
||||||
self.rl.notify(ip, writer)
|
await self.rl.notify(ip, writer)
|
||||||
|
writer.close()
|
||||||
break
|
break
|
||||||
data = await reader.read(1)
|
data = await reader.read(1)
|
||||||
if not data:
|
if not data:
|
||||||
|
|||||||
+17
-13
@@ -18,7 +18,7 @@ class UDPServer(asyncio.DatagramTransport):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.log = utils.get_logger("UDPServer")
|
self.log = utils.get_logger("UDPServer")
|
||||||
self.loop = asyncio.get_event_loop()
|
self.loop = asyncio.get_event_loop()
|
||||||
self.Core = core
|
self._core = core
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
self.run = False
|
self.run = False
|
||||||
@@ -33,8 +33,10 @@ class UDPServer(asyncio.DatagramTransport):
|
|||||||
code = data[2:3].decode()
|
code = data[2:3].decode()
|
||||||
data = data[2:].decode()
|
data = data[2:].decode()
|
||||||
|
|
||||||
client = self.Core.get_client(cid=cid)
|
client = self._core.get_client(cid=cid)
|
||||||
if client:
|
if client:
|
||||||
|
if not client.alive:
|
||||||
|
self.log.debug(f"{client.nick}:{cid} still sending UDP data: {data}")
|
||||||
match code:
|
match code:
|
||||||
case "p": # Ping packet
|
case "p": # Ping packet
|
||||||
ev.call_event("onSentPing")
|
ev.call_event("onSentPing")
|
||||||
@@ -45,24 +47,26 @@ class UDPServer(asyncio.DatagramTransport):
|
|||||||
self.log.debug(f"Set UDP Sock for CID: {cid}")
|
self.log.debug(f"Set UDP Sock for CID: {cid}")
|
||||||
ev.call_event("onChangePosition", data=data)
|
ev.call_event("onChangePosition", data=data)
|
||||||
sub = data.find("{", 1)
|
sub = data.find("{", 1)
|
||||||
last_pos_data = data[sub:]
|
last_pos = data[sub:]
|
||||||
try:
|
try:
|
||||||
last_pos = json.loads(last_pos_data)
|
|
||||||
client._last_position = last_pos
|
|
||||||
_, car_id = client._get_cid_vid(data)
|
_, car_id = client._get_cid_vid(data)
|
||||||
client._cars[car_id]['pos'] = last_pos
|
if client._cars[car_id]:
|
||||||
|
last_pos = json.loads(last_pos)
|
||||||
|
client._last_position = last_pos
|
||||||
|
client._cars[car_id]['pos'] = last_pos
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.debug(f"Cannot parse position packet: {e}")
|
self.log.warning(f"Cannot parse position packet: {e}")
|
||||||
self.log.debug(f"data: {data}, sup: {sub}")
|
self.log.debug(f"data: '{data}', sup: {sub}")
|
||||||
self.log.debug(f"last_pos_data: {last_pos_data}")
|
self.log.debug(f"last_pos ({type(last_pos)}): {last_pos}")
|
||||||
|
await client._send(data, to_all=True, to_self=False, to_udp=True)
|
||||||
|
case "X":
|
||||||
await client._send(data, to_all=True, to_self=False, to_udp=True)
|
await client._send(data, to_all=True, to_self=False, to_udp=True)
|
||||||
case _:
|
case _:
|
||||||
self.log.debug(f"[{cid}] Unknown code: {code}")
|
self.log.warning(f" UDP [{cid}] Unknown code: {code}; {data}")
|
||||||
else:
|
else:
|
||||||
self.log.debug(f"[{cid}] Client not found.")
|
self.log.debug(f"[{cid}] Client not found.")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
||||||
self.log.error(f"Error handle_datagram: {e}")
|
self.log.error(f"Error handle_datagram: {e}")
|
||||||
|
|
||||||
def datagram_received(self, *args, **kwargs):
|
def datagram_received(self, *args, **kwargs):
|
||||||
@@ -81,14 +85,14 @@ class UDPServer(asyncio.DatagramTransport):
|
|||||||
|
|
||||||
async def _start(self):
|
async def _start(self):
|
||||||
self.log.debug("Starting UDP server.")
|
self.log.debug("Starting UDP server.")
|
||||||
while self.Core.run:
|
while self._core.run:
|
||||||
try:
|
try:
|
||||||
|
|
||||||
await asyncio.sleep(0.2)
|
await asyncio.sleep(0.2)
|
||||||
|
|
||||||
d = UDPServer
|
d = UDPServer
|
||||||
self.transport, p = await self.loop.create_datagram_endpoint(
|
self.transport, p = await self.loop.create_datagram_endpoint(
|
||||||
lambda: d(self.Core),
|
lambda: d(self._core),
|
||||||
local_addr=(self.host, self.port)
|
local_addr=(self.host, self.port)
|
||||||
)
|
)
|
||||||
d.transport = self.transport
|
d.transport = self.transport
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class UDPServer(asyncio.DatagramTransport):
|
|||||||
def __init__(self, core: Core, host=None, port=None, transport=None):
|
def __init__(self, core: Core, host=None, port=None, transport=None):
|
||||||
self.log = utils.get_logger("UDPServer")
|
self.log = utils.get_logger("UDPServer")
|
||||||
self.loop = asyncio.get_event_loop()
|
self.loop = asyncio.get_event_loop()
|
||||||
self.Core = core
|
self._core = core
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
self.run = False
|
self.run = False
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import copy
|
||||||
# Developed by KuiToi Dev
|
# Developed by KuiToi Dev
|
||||||
# File modules.ConfigProvider
|
# File modules.ConfigProvider
|
||||||
# Written by: SantaSpeen
|
# Written by: SantaSpeen
|
||||||
@@ -16,12 +16,12 @@ class Config:
|
|||||||
def __init__(self, auth=None, game=None, server=None, rcon=None, options=None, web=None):
|
def __init__(self, auth=None, game=None, server=None, rcon=None, options=None, web=None):
|
||||||
self.Auth = auth or {"key": None, "private": True}
|
self.Auth = auth or {"key": None, "private": True}
|
||||||
self.Game = game or {"map": "gridmap_v2", "players": 8, "cars": 1}
|
self.Game = game or {"map": "gridmap_v2", "players": 8, "cars": 1}
|
||||||
self.Server = server or {"name": "KuiToi-Server", "description": "Welcome to KuiToi Server!",
|
self.Server = server or {"name": "KuiToi-Server", "description": "Welcome to KuiToi Server!", "tags": "Freroam",
|
||||||
"server_ip": "0.0.0.0", "server_port": 30814}
|
"server_ip": "0.0.0.0", "server_port": 30814}
|
||||||
|
self.Options = options or {"language": "en", "speed_limit": 0, "use_queue": False,
|
||||||
|
"use_lua": False, "log_chat": True}
|
||||||
self.RCON = rcon or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 10383,
|
self.RCON = rcon or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 10383,
|
||||||
"password": secrets.token_hex(16)}
|
"password": secrets.token_hex(6)}
|
||||||
self.Options = options or {"language": "en", "encoding": "utf-8", "speed_limit": 0, "use_queue": False,
|
|
||||||
"debug": False, "use_lua": False, "log_chat": True}
|
|
||||||
self.WebAPI = web or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 8433,
|
self.WebAPI = web or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 8433,
|
||||||
"access_token": secrets.token_hex(16)}
|
"access_token": secrets.token_hex(16)}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ class ConfigProvider:
|
|||||||
self.config_path = config_path
|
self.config_path = config_path
|
||||||
self.config = Config()
|
self.config = Config()
|
||||||
|
|
||||||
def open_config(self):
|
def read(self, _again=False):
|
||||||
if not os.path.exists(self.config_path):
|
if not os.path.exists(self.config_path):
|
||||||
with open(self.config_path, "w", encoding="utf-8") as f:
|
with open(self.config_path, "w", encoding="utf-8") as f:
|
||||||
yaml.dump(self.config, f)
|
yaml.dump(self.config, f)
|
||||||
@@ -47,9 +47,26 @@ class ConfigProvider:
|
|||||||
print("You have errors in the YAML syntax.")
|
print("You have errors in the YAML syntax.")
|
||||||
print("Stopping server.")
|
print("Stopping server.")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
if not self.config:
|
||||||
|
if _again:
|
||||||
|
print("Error: empty configuration.")
|
||||||
|
exit(1)
|
||||||
|
print("Empty config?..")
|
||||||
|
os.remove(self.config_path)
|
||||||
|
self.config = Config()
|
||||||
|
return self.read(True)
|
||||||
|
|
||||||
|
if not self.config.Options.get("debug"):
|
||||||
|
self.config.Options['debug'] = False
|
||||||
|
if not self.config.Options.get("encoding"):
|
||||||
|
self.config.Options['encoding'] = "utf-8"
|
||||||
|
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
def save_config(self):
|
def save(self):
|
||||||
|
_config = copy.deepcopy(self.config)
|
||||||
|
del _config.enc
|
||||||
|
del _config.Options['debug']
|
||||||
|
del _config.Options['encoding']
|
||||||
with open(self.config_path, "w", encoding="utf-8") as f:
|
with open(self.config_path, "w", encoding="utf-8") as f:
|
||||||
yaml.dump(self.config, f)
|
yaml.dump(_config, f)
|
||||||
@@ -40,6 +40,7 @@ class RateLimiter:
|
|||||||
if len(x) == 2:
|
if len(x) == 2:
|
||||||
ip = x[1]
|
ip = x[1]
|
||||||
if ip in _banned_ips:
|
if ip in _banned_ips:
|
||||||
|
self._notified[ip] = False
|
||||||
self._calls[ip].clear()
|
self._calls[ip].clear()
|
||||||
self._banned_until[ip] = datetime.now()
|
self._banned_until[ip] = datetime.now()
|
||||||
return f"{ip} removed from banlist."
|
return f"{ip} removed from banlist."
|
||||||
@@ -52,6 +53,7 @@ class RateLimiter:
|
|||||||
sec = x[2]
|
sec = x[2]
|
||||||
if not sec.isdigit():
|
if not sec.isdigit():
|
||||||
return f"{sec!r} is not digit."
|
return f"{sec!r} is not digit."
|
||||||
|
self._notified[ip] = False
|
||||||
self._calls[ip].clear()
|
self._calls[ip].clear()
|
||||||
self._banned_until[ip] = datetime.now() + timedelta(seconds=int(sec))
|
self._banned_until[ip] = datetime.now() + timedelta(seconds=int(sec))
|
||||||
return f"{ip} banned until {self._banned_until[ip]}"
|
return f"{ip} banned until {self._banned_until[ip]}"
|
||||||
@@ -69,7 +71,6 @@ class RateLimiter:
|
|||||||
try:
|
try:
|
||||||
writer.write(b'\x0b\x00\x00\x00Eip banned.')
|
writer.write(b'\x0b\x00\x00\x00Eip banned.')
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
writer.close()
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ class i18n:
|
|||||||
core_player_kick_stale: str
|
core_player_kick_stale: str
|
||||||
core_player_kick_no_allowed_default_reason: str
|
core_player_kick_no_allowed_default_reason: str
|
||||||
core_player_kick_server_full: str
|
core_player_kick_server_full: str
|
||||||
core_player_set_id: str
|
|
||||||
core_identifying_okay: str
|
core_identifying_okay: str
|
||||||
|
|
||||||
# In-game phrases
|
# In-game phrases
|
||||||
@@ -110,5 +109,4 @@ class i18n:
|
|||||||
# Command: exit
|
# Command: exit
|
||||||
man_message_exit: str
|
man_message_exit: str
|
||||||
help_message_exit: str
|
help_message_exit: str
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -41,7 +41,6 @@
|
|||||||
"core_player_kick_stale": "过时的客户端。(由新连接替换)",
|
"core_player_kick_stale": "过时的客户端。(由新连接替换)",
|
||||||
"core_player_kick_no_allowed_default_reason": "您不受欢迎。拒绝访问。",
|
"core_player_kick_no_allowed_default_reason": "您不受欢迎。拒绝访问。",
|
||||||
"core_player_kick_server_full": "服务器已满。",
|
"core_player_kick_server_full": "服务器已满。",
|
||||||
"core_player_set_id": "玩家设置ID {}",
|
|
||||||
"core_identifying_okay": "成功登录。",
|
"core_identifying_okay": "成功登录。",
|
||||||
|
|
||||||
"": "游戏内短语",
|
"": "游戏内短语",
|
||||||
|
|||||||
@@ -41,7 +41,6 @@
|
|||||||
"core_player_kick_stale": "Stale client. (Replaced by new connection)",
|
"core_player_kick_stale": "Stale client. (Replaced by new connection)",
|
||||||
"core_player_kick_no_allowed_default_reason": "You are not welcome on this server. Access denied.",
|
"core_player_kick_no_allowed_default_reason": "You are not welcome on this server. Access denied.",
|
||||||
"core_player_kick_server_full": "Server is full.",
|
"core_player_kick_server_full": "Server is full.",
|
||||||
"core_player_set_id": "Player set ID {}",
|
|
||||||
"core_identifying_okay": "Successful login.",
|
"core_identifying_okay": "Successful login.",
|
||||||
|
|
||||||
"": "In-game phrases",
|
"": "In-game phrases",
|
||||||
|
|||||||
@@ -41,7 +41,6 @@
|
|||||||
"core_player_kick_stale": "Устаревший клиент. (Заменено новым подключением)",
|
"core_player_kick_stale": "Устаревший клиент. (Заменено новым подключением)",
|
||||||
"core_player_kick_no_allowed_default_reason": "Вам не рады на этом сервере. Вход запрещён.",
|
"core_player_kick_no_allowed_default_reason": "Вам не рады на этом сервере. Вход запрещён.",
|
||||||
"core_player_kick_server_full": "Сервер полон.",
|
"core_player_kick_server_full": "Сервер полон.",
|
||||||
"core_player_set_id": "Игрок получил ID {}",
|
|
||||||
"core_identifying_okay": "Успешный вход.",
|
"core_identifying_okay": "Успешный вход.",
|
||||||
|
|
||||||
"": "In-game phrases",
|
"": "In-game phrases",
|
||||||
|
|||||||
Reference in new issue
Block a user