#!/usr/bin/env python3 """ ChaCha DoConfig script """ import argparse from enum import Enum from dataclasses import dataclass from collections import OrderedDict from os.path import join from pprint import pprint from ChaChaSimpleINI import ChaChaSimpleINI,ChaChaINI_KeyNotFoundException,ChaChaINI_SectionNotFoundException,ChaChaSimpleINI_section,ChaChaSimpleINI_key from abc import ABCMeta,abstractmethod class ChaChaSimpleINI_UT(ChaChaSimpleINI): def GroupKeysInSection(self,szSectionName,szKeyName): try: _section=self.getSection(szSectionName) if type(_section) is ChaChaSimpleINI_section: ar_ServerPackages = _section.getKey(szKeyName) if isinstance(ar_ServerPackages,ChaChaSimpleINI_key): ar_ServerPackages = [ar_ServerPackages] else: #array pass for ServerPackages in ar_ServerPackages: _section.delKey(ServerPackages.name,None,ServerPackages.value) for ServerPackages in ar_ServerPackages: _section.setAddKeyValue(ServerPackages.name,ServerPackages.value,True) except ChaChaINI_SectionNotFoundException: pass def writeFile(self,bBeautify:bool = False) -> None: self.GroupKeysInSection("XC_Engine.XC_GameEngine","ServerPackages") self.GroupKeysInSection("XC_Engine.XC_GameEngine","ServerActors") self.GroupKeysInSection("Engine.GameEngine","ServerPackages") self.GroupKeysInSection("Engine.GameEngine","ServerActors") self.GroupKeysInSection("Core.System","Suppress") self.GroupKeysInSection("Editor.EditorEngine","EditPackages") super().writeFile(bBeautify) class OptionType(Enum): OT_INVALID = 0 OT_STRING = 1 OT_INTEGER = 2 OT_BOOLEAN = 3 OT_FLOAT = 4 class GameOption(metaclass=ABCMeta): szGameType:str = "" szOptionName:str = "" TValueType:OptionType = OptionType.OT_INVALID szDefaultValue:str = "" szHelp:str = "" szFormatedValue:str = "" @abstractmethod def __init__(self,GameRootDir:str,ConfigFileRelPath:str=None): self.GameRootDir = GameRootDir self.ConfigFileRelPath = ConfigFileRelPath def format(self,value): if self.TValueType == OptionType.OT_STRING: self.szFormatedValue = str(value) elif self.TValueType == OptionType.OT_INTEGER: self.szFormatedValue = str(int(value)) elif self.TValueType == OptionType.OT_BOOLEAN: try: intval = int(value) self.szFormatedValue = str(bool(intval)) except: self.szFormatedValue = str(bool(1)) if value.lower() == "true" else str(bool(0)) elif self.TValueType == OptionType.OT_FLOAT: self.szFormatedValue = str(float(value)) else: raise RuntimeError("Invalid Option TValueType") print("setting option <{0}> to: {1}".format(self.szOptionName,self.szFormatedValue)) @abstractmethod def set(self,value): raise NotImplementedError("method not implemented") @abstractmethod def rem(self,value): raise NotImplementedError("method not implemented") @abstractmethod def get(self,inifile:ChaChaSimpleINI): raise NotImplementedError("method not implemented") class GameOptions_Factory: ar_Options_cls = [] def __init__(self,szGameType:str,GameRootDir:str,ConfigFileRelPath:str=None): self.szGameType = szGameType self.ar_Options = [] for Options_cls in self.ar_Options_cls: if Options_cls.szGameType == szGameType: self.ar_Options.append(Options_cls(GameRootDir,ConfigFileRelPath)) @classmethod def GameOptionRegister(cls,Option:GameOption): cls.ar_Options_cls.append(Option) def set(cls,OptionName:str,value:str): for _option in cls.ar_Options: if _option.szOptionName==OptionName: _option.set(value) return raise RuntimeError("Option not found") def rem(cls,OptionName:str,value:str): for _option in cls.ar_Options: if _option.szOptionName==OptionName: _option.rem(value) return raise RuntimeError("Option not found") def get(cls,OptionName:str) -> str: for _option in cls.ar_Options: if _option.szOptionName==OptionName: return _option.get() raise RuntimeError("Option not found") def GameOptions_Factory_Register(cls): GameOptions_Factory.GameOptionRegister(cls) return cls class GameOption_UT99(GameOption): szGameType = "ut99" TValueType = OptionType.OT_INVALID szOptionName = "" szSectionName = "" szKeyName = "" bForceAdd:bool = False bRemovable:bool = False def __init__(self,GameRootDir:str,ConfigFileRelPath:str): super().__init__(GameRootDir,ConfigFileRelPath) self. mainConfigFilePath = join(GameRootDir,ConfigFileRelPath) def set(self,value:str): if not self.szOptionName: raise RuntimeError("szOptionName is not set") self.format(value) inifile = ChaChaSimpleINI_UT(self.mainConfigFilePath) inifile.setAddKeyValue(self.szSectionName,self.szKeyName,self.szFormatedValue,self.bForceAdd) inifile.writeFile() def rem(self,value:str): if not self.szOptionName: raise RuntimeError("szOptionName is not set") if not self.bRemovable: raise RuntimeError("this options is not removable") inifile = ChaChaSimpleINI(self.mainConfigFilePath) inifile.delKeyEx(self.szSectionName,self.szKeyName,None,value) inifile.writeFile() def get(self): if not self.szOptionName: raise RuntimeError("szOptionName not set") print("get option <{0}>".format(self.szOptionName)) inifile = ChaChaSimpleINI(self.mainConfigFilePath) res = inifile.getKeyValue(self.szSectionName,self.szKeyName) return res class GameOption_UT99_GenAdd(GameOption_UT99): bForceAdd = True def rem(self,value:str): try: super().rem(value) except ChaChaINI_KeyNotFoundException: pass def set(self,value:str): #force to call the rem method of THIS level GameOption_UT99_GenAdd.rem(self,value) super().set(value) class GameOption_UT99_GenAdd__Engine(GameOption_UT99_GenAdd): szSectionName = "Engine.GameEngine" TValueType = OptionType.OT_STRING bRemovable = True def set(self,value:str): prev = self.szSectionName inifile = ChaChaSimpleINI(self.mainConfigFilePath) try: inifile.getSection("XC_Engine.XC_GameEngine") self.szSectionName = "XC_Engine.XC_GameEngine" super().set(value) except ChaChaINI_SectionNotFoundException: pass self.szSectionName = "Engine.GameEngine" super().set(value) self.szSectionName = prev def rem(self,value:str): prev = self.szSectionName inifile = ChaChaSimpleINI(self.mainConfigFilePath) try: inifile.getSection("XC_Engine.XC_GameEngine") self.szSectionName = "XC_Engine.XC_GameEngine" super().rem(value) except ChaChaINI_SectionNotFoundException: pass self.szSectionName = "Engine.GameEngine" super().rem(value) self.szSectionName = prev @GameOptions_Factory_Register class GameOption_UT99_ServerPackages(GameOption_UT99_GenAdd__Engine): szOptionName = "ServerPackages" szKeyName = "ServerPackages" szHelp = "Add a ServerPackages record" @GameOptions_Factory_Register class GameOption_UT99_ServerActors(GameOption_UT99_GenAdd__Engine): szOptionName = "ServerActors" szKeyName = "ServerActors" szHelp = "Add a ServerActors record" @GameOptions_Factory_Register class GameOption_UT99_Port(GameOption_UT99): szOptionName = "Port" szSectionName = "URL" szKeyName = "Port" TValueType = OptionType.OT_INTEGER szDefaultValue = "7777" szHelp = "Server Listening port" @GameOptions_Factory_Register class GameOption_UT99_Map(GameOption_UT99): szOptionName = "Map" szSectionName = "URL" szKeyName = "Map" TValueType = OptionType.OT_STRING szDefaultValue = "dm-deck16][" szHelp = "Server Map" @GameOptions_Factory_Register class GameOption_UT99_GameType(GameOption_UT99): szOptionName = "GameType" szSectionName = "Engine.Engine" szKeyName = "DefaultServerGame" TValueType = OptionType.OT_STRING szDefaultValue = "Botpack.DeathMatchPlus" szHelp = "Server Gametype" @GameOptions_Factory_Register class GameOption_UT99_HostName(GameOption_UT99): szOptionName = "HostName" szSectionName = "Engine.GameReplicationInfo" szKeyName = "ServerName" TValueType = OptionType.OT_STRING szDefaultValue = "ChaCha Test Server" szHelp = "Server's HostName" def set(self,value:str): super().set(value) inifile = ChaChaSimpleINI(self. mainConfigFilePath) inifile.setAddKeyValue("Engine.GameReplicationInfo","ShortName",value) inifile.writeFile() @GameOptions_Factory_Register class GameOption_UT99_MOTD(GameOption_UT99): szOptionName = "MOTD" szSectionName = "Engine.GameReplicationInfo" szKeyName = "MOTDLine1" TValueType = OptionType.OT_STRING szDefaultValue = "Welcome to ChaCha's server" szHelp = "Message of the day" @GameOptions_Factory_Register class GameOption_UT99_MOTD2(GameOption_UT99): szOptionName = "MOTD2" szSectionName = "Engine.GameReplicationInfo" szKeyName = "MOTDLine2" TValueType = OptionType.OT_STRING szDefaultValue = "Enjoy your stay and have fun" szHelp = "Message of the day (2)" @GameOptions_Factory_Register class GameOption_UT99_MOTD3(GameOption_UT99): szOptionName = "MOTD3" szSectionName = "Engine.GameReplicationInfo" szKeyName = "MOTDLine3" TValueType = OptionType.OT_STRING szDefaultValue = "" szHelp = "Message of the day (3)" @GameOptions_Factory_Register class GameOption_UT99_MOTD4(GameOption_UT99): szOptionName = "MOTD4" szSectionName = "Engine.GameReplicationInfo" szKeyName = "MOTDLine4" TValueType = OptionType.OT_STRING szDefaultValue = "Game Server by ChaCha" szHelp = "Message of the day (4)" @GameOptions_Factory_Register class GameOption_UT99_AdminEmail(GameOption_UT99): szOptionName = "AdminEmail" szSectionName = "Engine.GameReplicationInfo" szKeyName = "AdminEmail" TValueType = OptionType.OT_STRING szDefaultValue = "chachacorp@protonmail.com" szHelp = "Admin mail" @GameOptions_Factory_Register class GameOption_UT99_AdminName(GameOption_UT99): szOptionName = "AdminName" szSectionName = "Engine.GameReplicationInfo" szKeyName = "AdminName" TValueType = OptionType.OT_STRING szDefaultValue = "chacha" szHelp = "Admin name" def set(self,value:str): super().set(value) inifile = ChaChaSimpleINI(self. mainConfigFilePath) inifile.setAddKeyValue("UTServerAdmin.UTServerAdmin","AdminUsername",value) inifile.writeFile() @GameOptions_Factory_Register class GameOption_UT99_HTTPDownloadServer(GameOption_UT99): szOptionName = "HTTPDownloadServer" szSectionName = "IpDrv.HTTPDownload" szKeyName = "RedirectToURL" TValueType = OptionType.OT_STRING szDefaultValue = "http://chacha.ddns.net/games/ut99" szHelp = "FastDL url" def set(self,value:str): super().set(value) inifile = ChaChaSimpleINI(self. mainConfigFilePath) inifile.setAddKeyValue("IpDrv.HTTPDownload","UseCompression","True") inifile.delKey("IpDrv.HTTPDownload","ProxyServerHost") inifile.delKey("IpDrv.HTTPDownload","ProxyServerPort") inifile.writeFile() @GameOptions_Factory_Register class GameOption_UT99_MaxClientRate(GameOption_UT99): szOptionName = "MaxClientRate" szSectionName = "IpDrv.TcpNetDriver" szKeyName = "MaxClientRate" TValueType = OptionType.OT_INTEGER szDefaultValue = "25000" szHelp = "Max Client Rate" @GameOptions_Factory_Register class GameOption_UT99_MinClientRate(GameOption_UT99): szOptionName = "MinClientRate" szSectionName = "IpDrv.TcpNetDriver" szKeyName = "MinClientRate" TValueType = OptionType.OT_INTEGER szDefaultValue = "12000" szHelp = "Max Client Rate" @GameOptions_Factory_Register class GameOption_UT99_NetServerMaxTickRate(GameOption_UT99): szOptionName = "NetServerMaxTickRate" szSectionName = "IpDrv.TcpNetDriver" szKeyName = "NetServerMaxTickRate" TValueType = OptionType.OT_INTEGER szDefaultValue = "60" szHelp = "Server Max TickRate" def set(self,value:str): super().set(value) inifile = ChaChaSimpleINI(self. mainConfigFilePath) inifile.setAddKeyValue("Engine.DemoRecDriver","NetServerMaxTickRate",value) inifile.writeFile() @GameOptions_Factory_Register class GameOption_UT99_LanServerMaxTickRate(GameOption_UT99): szOptionName = "LanServerMaxTickRate" szSectionName = "IpDrv.TcpNetDriver" szKeyName = "LanServerMaxTickRate" TValueType = OptionType.OT_INTEGER szDefaultValue = "60" szHelp = "Lan Server Max TickRate" def set(self,value:str): super().set(value) inifile = ChaChaSimpleINI(self. mainConfigFilePath) inifile.setAddKeyValue("Engine.DemoRecDriver","LanServerMaxTickRate",value) inifile.writeFile() @GameOptions_Factory_Register class GameOption_UT99_AdminPassword(GameOption_UT99): szOptionName = "AdminPassword" szSectionName = "Engine.GameInfo" szKeyName = "AdminPassword" TValueType = OptionType.OT_STRING szDefaultValue = "cfographut" szHelp = "Admin password" def set(self,value:str): super().set(value) inifile = ChaChaSimpleINI(self. mainConfigFilePath) inifile.setAddKeyValue("UTServerAdmin.UTServerAdmin","AdminPassword",value) inifile.writeFile() @GameOptions_Factory_Register class GameOption_UT99_GamePassword(GameOption_UT99): szOptionName = "GamePassword" szSectionName = "Engine.GameInfo" szKeyName = "GamePassword" TValueType = OptionType.OT_STRING szDefaultValue = "" szHelp = "Game password" @GameOptions_Factory_Register class GameOption_UT99_MaxPlayers(GameOption_UT99): szOptionName = "MaxPlayers" szSectionName = "Engine.GameInfo" szKeyName = "MaxPlayers" TValueType = OptionType.OT_INTEGER szDefaultValue = "20" szHelp = "Game Max Players" @GameOptions_Factory_Register class GameOption_UT99_MaxSpectators(GameOption_UT99): szOptionName = "MaxSpectators" szSectionName = "Engine.GameInfo" szKeyName = "MaxSpectators" TValueType = OptionType.OT_INTEGER szDefaultValue = "20" szHelp = "Game Max Spectators" @GameOptions_Factory_Register class GameOption_UT99_AS_TimeLimit(GameOption_UT99): szOptionName = "AS_TimeLimit" szSectionName = "Botpack.Assault" szKeyName = "TimeLimit" TValueType = OptionType.OT_INTEGER szDefaultValue = "20" szHelp = "Game Time Limit" @GameOptions_Factory_Register class GameOption_UT99_DOM_TimeLimit(GameOption_UT99): szOptionName = "DOM_TimeLimit" szSectionName = "Botpack.Domination" szKeyName = "TimeLimit" TValueType = OptionType.OT_INTEGER szDefaultValue = "20" szHelp = "Game Time Limit" @GameOptions_Factory_Register class GameOption_UT99_CTF_TimeLimit(GameOption_UT99): szOptionName = "CTF_TimeLimit" szSectionName = "Botpack.CTFGame" szKeyName = "TimeLimit" TValueType = OptionType.OT_INTEGER szDefaultValue = "20" szHelp = "Game Time Limit" @GameOptions_Factory_Register class GameOption_UT99_DM_TimeLimit(GameOption_UT99): szOptionName = "DM_TimeLimit" szSectionName = "Botpack.DeathMatchPlus" szKeyName = "TimeLimit" TValueType = OptionType.OT_INTEGER szDefaultValue = "10" szHelp = "Game Time Limit" @GameOptions_Factory_Register class GameOption_UT99_GoalTeamScore(GameOption_UT99): szOptionName = "GoalTeamScore" szSectionName = "Botpack.TeamGamePlus" szKeyName = "GoalTeamScore" TValueType = OptionType.OT_INTEGER szDefaultValue = "3" szHelp = "Game Score Limit" @GameOptions_Factory_Register class GameOption_UT99_MaxTeamSize(GameOption_UT99): szOptionName = "MaxTeamSize" szSectionName = "Botpack.TeamGamePlus" szKeyName = "MaxTeamSize" TValueType = OptionType.OT_FLOAT szDefaultValue = "12" szHelp = "Maximum Team Size" @GameOptions_Factory_Register class GameOption_UT99_DM_FragLimit(GameOption_UT99): szOptionName = "DM_FragLimit" szSectionName = "Botpack.DeathMatchPlus" szKeyName = "FragLimit" TValueType = OptionType.OT_INTEGER szDefaultValue = "10" szHelp = "Game Score Limit" @GameOptions_Factory_Register class GameOption_UT99_ServerLogName(GameOption_UT99): szOptionName = "ServerLogName" szSectionName = "Engine.GameInfo" szKeyName = "ServerLogName" TValueType = OptionType.OT_STRING szDefaultValue = "server.log" szHelp = "Server Log Name" @GameOptions_Factory_Register class GameOption_UT99_WebServer(GameOption_UT99): szOptionName = "WebServer" szSectionName = "UWeb.WebServer" szKeyName = "ListenPort" TValueType = OptionType.OT_INTEGER szDefaultValue = "8076" szHelp = "enable Web Server" def set(self,value:str): #fix ut99 v469c try: inifile.delKey("UWeb.WebServer","Listenport") except: pass super().set(value) inifile = ChaChaSimpleINI(self.mainConfigFilePath) if int(value) > 0: inifile.setAddKeyValue("UWeb.WebServer","bEnabled","True") else: inifile.setAddKeyValue("UWeb.WebServer","bEnabled","False") inifile.writeFile() @GameOptions_Factory_Register class GameOption_UT99_TournamentMode(GameOption_UT99): szOptionName = "TournamentMode" szSectionName = "Botpack.DeathMatchPlus" szKeyName = "bTournament" TValueType = OptionType.OT_BOOLEAN szDefaultValue = "False" szHelp = "Enable Tournament Mode" @GameOptions_Factory_Register class GameOption_UT99_InitialBots(GameOption_UT99): szOptionName = "InitialBots" szSectionName = "Botpack.DeathMatchPlus" szKeyName = "InitialBots" TValueType = OptionType.OT_INTEGER szDefaultValue = "0" szHelp = "Initial Bots number" @GameOptions_Factory_Register class GameOption_UT99_MinPlayers(GameOption_UT99): szOptionName = "MinPlayers" szSectionName = "Botpack.DeathMatchPlus" szKeyName = "MinPlayers" TValueType = OptionType.OT_INTEGER szDefaultValue = "0" szHelp = "Minimum player count (allow to adjust bots)" @GameOptions_Factory_Register class GameOption_UT99_AS_UseTranslocator(GameOption_UT99): szOptionName = "AS_UseTranslocator" szSectionName = "Botpack.CTFGame" szKeyName = "bUseTranslocator" TValueType = OptionType.OT_BOOLEAN szDefaultValue = "True" szHelp = "Enable Translocator" @GameOptions_Factory_Register class GameOption_UT99_CTF_UseTranslocator(GameOption_UT99): szOptionName = "CTF_UseTranslocator" szSectionName = "Botpack.CTFGame" szKeyName = "bUseTranslocator" TValueType = OptionType.OT_BOOLEAN szDefaultValue = "True" szHelp = "Enable Translocator" @GameOptions_Factory_Register class GameOption_UT99_DM_UseTranslocator(GameOption_UT99): szOptionName = "DM_UseTranslocator" szSectionName = "Botpack.DeathMatchPlus" szKeyName = "bUseTranslocator" TValueType = OptionType.OT_BOOLEAN szDefaultValue = "True" szHelp = "Enable Translocator" @GameOptions_Factory_Register class GameOption_UT99_DOM_UseTranslocator(GameOption_UT99): szOptionName = "DOM_UseTranslocator" szSectionName = "Botpack.Domination" szKeyName = "bUseTranslocator" TValueType = OptionType.OT_BOOLEAN szDefaultValue = "True" szHelp = "Enable Translocator" @GameOptions_Factory_Register class GameOption_UT99_AS_UseTranslocator(GameOption_UT99): szOptionName = "AS_UseTranslocator" szSectionName = "Botpack.Assault" szKeyName = "bUseTranslocator" TValueType = OptionType.OT_BOOLEAN szDefaultValue = "True" szHelp = "Enable Translocator" @GameOptions_Factory_Register class GameOption_UT99_CTF_ForceRespawn(GameOption_UT99): szOptionName = "CTF_ForceRespawn" szSectionName = "Botpack.CTFGame" szKeyName = "bForceRespawn" TValueType = OptionType.OT_BOOLEAN szDefaultValue = "True" szHelp = "Force Player to respawn" @GameOptions_Factory_Register class GameOption_UT99_DM_ForceRespawn(GameOption_UT99): szOptionName = "DM_ForceRespawn" szSectionName = "Botpack.DeathMatchPlus" szKeyName = "bForceRespawn" TValueType = OptionType.OT_BOOLEAN szDefaultValue = "True" szHelp = "Force Player to respawn" @GameOptions_Factory_Register class GameOption_UT99_DOM_ForceRespawn(GameOption_UT99): szOptionName = "DOM_ForceRespawn" szSectionName = "Botpack.Domination" szKeyName = "bForceRespawn" TValueType = OptionType.OT_BOOLEAN szDefaultValue = "True" szHelp = "Force Player to respawn" @GameOptions_Factory_Register class GameOption_UT99_AS_ForceRespawn(GameOption_UT99): szOptionName = "AS_ForceRespawn" szSectionName = "Botpack.Assault" szKeyName = "bForceRespawn" TValueType = OptionType.OT_BOOLEAN szDefaultValue = "True" szHelp = "Force Player to respawn" if __name__ == "__main__": parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("-v", "--verbosity", action="count", default=0, help="increase output verbosity") parser.add_argument("-b", "--basegamedir", help="set the game base dir", default="./") parser.add_argument("-c", "--configfile", help="set the default config file",default="./System/UnrealTournament.ini") subparsers = parser.add_subparsers(dest='command',help='command type',required=True) SetOption_subparser = subparsers.add_parser("SetOption",help="Set/Add a game config file option value (may need reboot)") SetOption_subparser.add_argument('option') SetOption_subparser.add_argument('value') RemOption_subparser = subparsers.add_parser("RemOption",help="Remove a game config file option, w or w/o value (may need reboot)") RemOption_subparser.add_argument('option') RemOption_subparser.add_argument('value') GetOption_subparser = subparsers.add_parser("GetOption",help="Get a game config file option value") GetOption_subparser.add_argument('option') args=parser.parse_args() if args.verbosity : print("Using base game dir: {0}".format(args.basegamedir)) print("Using config file: {0}".format(args.configfile)) _GameOptions = GameOptions_Factory("ut99",args.basegamedir,args.configfile) if args.command == "SetOption": _GameOptions.set(args.option,args.value) elif args.command == "RemOption": _GameOptions.rem(args.option,args.value) elif args.command == "GetOption": res=_GameOptions.get(args.option) print(res) else: raise RuntimeError("Invalid argument")