复制这段内容后打开百度网盘App,操作更方便哦。
链接:https://pan.baidu.com/s/1mTcTMBAoeSCx4JwRjDae-A
提取码:9F1w --来自百度网盘超级会员V3的分享
[IMG]https://www.img.in.th/images/2511542...85083ede64.jpg[/IMG]
Printable View
复制这段内容后打开百度网盘App,操作更方便哦。
链接:https://pan.baidu.com/s/1mTcTMBAoeSCx4JwRjDae-A
提取码:9F1w --来自百度网盘超级会员V3的分享
[IMG]https://www.img.in.th/images/2511542...85083ede64.jpg[/IMG]
Well, It's about time.
I hope you enjoy this release as I did a long time ago
If you like Python, this is for you. 90% of the entire game is coded in Python which is pretty much what caused the downfall of this project that led to development hell and lead to cancellation.
I don't have tutorials, nor do I have any interest. But I just want to warm that this game is very incomplete and contains a lot of bugs. You can only have fun if you know Python.
I just need that link to be in Gdrive since I lost the client a long time ago and I just want to play with the assets models
Would anyone be able to re-upload to google drive/mega/mediafire?
Would take some time, but yeah
- - - Updated - - -
Server
Client
pack/unpack
GM Commands
Code:/notice
/summon
/bookset
/teleport
/transfer
/enterHome
/leaveHome
/run
/startQuest
/cancelQuest
/addToFinishedQuests
/removeFromFinishedQuests
/questStates
/test_menu_selection_marker
/testload
/move
/whereami
/action
/getProperty
/setProperty
/resetCutscene
/item
/uncarve
/batch
/forcetransfer
/whereis
/playerlist
/checkmemory
/behaviortree
/kickout
/kickoutAll
/stamina
/refreshkiranaconditionz
/refreshkc
/time
/dump
/resetZone
/resetTerrain
/controlZoneEnter
/getRelationship
/setRelationship
/level
/emptyinven
/loot
/hottimez
/ho
/spmaxz
/sp
/kimaxz
/ki
/giveItem
/ban
/account
/character
/server
/debugAI
/dumpuser
/resetNPC
https://i.imgur.com/J1HpSek.png https://i.imgur.com/vkaxLQT.png
Just some extra info about the files themselves.
Hope this helps out.
Client - not work...
Server[Maybe, it is test client...] - work
(However, because of the high system requirements of this game,
I have a hard time playing this game.
Too slow. :*:)
https://i.imgur.com/Sp4KCG9.jpg
Server and client works using the pre-registered account Tac. I think server settings are compiled directly into the pyc files.
thought it was Peru chronicles anywway nice release
Game cannot be played on non-korean systems. So anybody who has the src could please share app.py with us so we can remove that check?
I don't think anyone has the src according to my connections.
Have you determine if it's a codepage issue? I can probably write something quickly if that's the issue
Your connections said earlier that it contains sources for both Python and C++ :p.
Anyway yes it CP issue, but usual "codepage fakery" doesn't work since python uses its own system to determine
I never really got a hold of the server files myself. So I was just going based on what I was told in the past. I really thought it was Python which it is, but I wouldn't think it would be compiled Python code I guess that makes things a bit difficult huh.
If the usual LC Emu trick doesn't work, we'll have to dig deeper. I'm mostly busy with other projects however and this is the lowest of my priorities, I can probably look into it in a week or two.
Does the client CTD or has a MessageBox? Those are usually easy to detect with a debugger.
It does have a messagebox yes, I also found it in decompiled app.py
issue is that the decompilation failed half way down the file :S and its not possible to just replace the app.pyc with app.py because of this.Code:if currentLocale != 'cp949':
if self.Renderer.NativeWindowHandle:
text = 'Unsupported system locale : {}'.format(currentLocale)
text += '\n\nTo execute the client, change system locale to cp949 (Korean)'
self.showWindowsMessageBox(text)
self.nxlog = None
self.quit(0)
return
Hey so, I'm just not going to have time to look into this since I have too many going on. I'll just provide some additional steps from this point on.
1). With your favorite debugger, add a BP on the last RETN to both MessageBoxA and MessageBoxW
2). Wait for the MsgBox to appear, then click on OK.
3). The debugger should now capture the BP. Step into and follow the on-screen assembly instructions. Eventually you're going to see a familiar condition toggle in the disassembly. (Based on the code provided above, looks like it's going to be a JNZ, the edit is probably going to be a JMP)
4). Implement the modification and pray that it'll work.
If you find it, please provide the AoB.
If you can't figure it out within a month, I'll handle it.
Line 314 to 402
Line 1247 doesnt even exist on mine :P.
Output from uncompyle6
Code:# uncompyle6 version 3.7.4
# Python bytecode 3.7 (3394)
# Decompiled from: Python 2.7.17 (default, Sep 30 2020, 13:38:04)
# [GCC 7.5.0]
# Warning: this version of Python has problems handling the Python 3 "byte" type in constants properly.
# Embedded file name: C:\NT_GPM_WORKSPACE\peria_server_stage\..\PYC\sources\python\natuum\client\app.py
# Compiled at: 2019-05-10 19:51:18
# Size of source mod 2**32: 69315 bytes
import collections, logging, os, sys, weakref, time, datetime, platform, enum, locale, thing.accessor
import thing.asyncio as asyncio
import thing.system
from natuum.activeobjects import ApartmentActiveObject
import natuum.apartment, natuum.app, natuum.builder, natuum.renderer, natuum.jobQueue, natuum.configuration, natuum.client.action, natuum.client.pager, natuum.client.resource, natuum.client.craft, natuum.client.logic, natuum.util, natuum.variation, natuum.indexBook, natuum.reward, natuum.growth, natuum.emotionCutscene, natuum.client.synergy, natuum.client.contract, natuum.client.relationship, natuum._entity.apartmentEvents, natuum._entity.framework.cluster
import natuum._entity.messageRPC as messageRPC
from natuum.nexon import NXLogClient
from natuum._entity.interfaces.clientSubscriber import IGatewaySubscriber
import natuum.gameService.wordFilter
if __thing_can_get_zcom_objects__:
import multiprocessing
if not __final__:
import natuum.objects
DEFAULT_CLIENT_RESOLUTION = (1920, 1080)
if not __final__:
LOG_DEBUG = natuum.getLogger('client.app').debug
LOG_WARNING = natuum.getLogger('client.app').warning
import socket
def callbackBeforeDump():
if natuum.client.app.Instance:
natuum.client.app.Instance.nxlog.stageLog(1020, 'crash')
natuum.flushLog()
def callbackAfterDump():
pass
TEXT_FORMAT_TO_PRELOAD = ((0, '2002', 24.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 12.0, 3, 0, 5, 1.0),
(0, 'KoPubDotum', 13.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 13.0, 5, 0, 5, 1.0),
(0, 'KoPubDotum', 14.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 14.0, 5, 0, 5, 1.0),
(0, 'KoPubDotum', 15.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 15.0, 5, 0, 5, 1.0),
(0, 'KoPubDotum', 17.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 17.0, 5, 0, 5, 1.0),
(0, 'KoPubDotum', 18.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 18.0, 5, 0, 5, 1.0),
(0, 'KoPubDotum', 21.0, 5, 0, 5, 1.0), (0, 'KoPubDotum', 22.0, 5, 0, 5, 1.0),
(0, 'KoPubDotum', 23.0, 5, 0, 5, 1.0), (0, 'KoPubDotum', 24.0, 3, 0, 5, 1.0),
(0, 'KoPubDotum', 26.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 27.36, 3, 0, 5, 1.0),
(0, 'KoPubDotum', 27.6, 5, 0, 5, 1.0), (0, 'KoPubDotum', 33.0, 5, 0, 5, 1.0),
(0, 'KoPubDotum', 34.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 50.0, 5, 0, 5, 1.0),
(0, 'NanumSquare', 12.0, 5, 0, 5, 1.0), (0, 'NanumSquare', 14.0, 3, 0, 5, 1.0),
(0, 'NanumSquare', 14.0, 5, 0, 5, 1.0), (0, 'NanumSquare', 15.0, 5, 0, 5, 1.0),
(0, 'NanumSquare', 16.0, 5, 2, 5, 1.0), (0, 'NanumSquare', 17.0, 5, 0, 5, 1.0),
(0, 'NanumSquare', 40.0, 5, 0, 5, 1.0))
SIDEMENU_HTMLIVEWS = ('sideMenu_page_collection', 'sideMenu_page_map', 'sideMenu_page_present',
'sideMenuAvatar_editor')
Instance = None
class ListenerType(enum.IntEnum):
ZONE = 0
PERSON = 1
class App(natuum.app.App):
_App__LONG_TIME_PLAY_WARNING_INTERVAL = 3600000
def __init__(self):
global Instance
super().__init__()
Instance = self
self.RealTimeClock = thing.Value.RealTimeClock()
self.RunOptions = self._getRunOptions('Natuum Client', '', (self._App__RunOptionHandler(),), sys.argv[1:])
now = time.clock()
self._App__clientTime = now
self._App__serverTime = now
self._App__fps = 60
self.nxlog = None
@Property
def FPS(self):
return self._App__fps
@FPS.setter
def FPS(self, fps):
self._App__fps = fps
def __sendPacket(self, blob):
pass
Resolution = thing.accessor.ri(doc=u'\ud074\ub77c\uc774\uc5b8\ud2b8 \ud574\uc0c1\ub3c4')
Renderer = thing.accessor.ri(doc=u'\ub80c\ub354\ub7ec \uac1d\uccb4')
RPCManager = thing.accessor.ri(doc=u'RPC \uad00\ub9ac\uc790 \uac1d\uccb4')
SoundManager = thing.accessor.ri(None, doc=u'\uc74c\ud5a5 \uad00\ub9ac\uc790 \uac1d\uccb4')
Apartment = thing.accessor.ri(doc=u'\ud074\ub77c\uc774\uc5b8\ud2b8 \uc544\ud30c\ud2b8\uba3c\ud2b8')
EventLoop = thing.accessor.ri(None, doc=u'\ud074\ub77c\uc774\uc5b8\ud2b8 \uc774\ubca4\ud2b8\ub8e8\ud504')
ClusterGuestNode = thing.accessor.ri(doc=u'\ud074\ub77c\uc774\uc5b8\ud2b8 \ud074\ub7ec\uc2a4\ud130 \ub178\ub4dc')
ClientZone = thing.accessor.rw(None)
QuitOnRendererShutdown = thing.accessor.ri(True, doc=u'\ub80c\ub354\ub7ec \uc167\ub2e4\uc6b4\uc2dc \ud504\ub85c\uadf8\ub7a8 \uc885\ub8cc')
Platform = thing.accessor.ri(doc=u'\ud50c\ub7ab\ud3fc \uac1d\uccb4')
GatewayEntityID = thing.accessor.rw(None, doc=u'\uac8c\uc774\ud2b8\uc6e8\uc774\uc5d4\ud2f0\ud2f0 ID')
ChatManagerEntityID = thing.accessor.rw(None, doc=u'\ucc44\ud305\uad00\ub9ac\uc790\uc5d4\ud2f0\ud2f0 ID')
ItemDesignLibraryEntityID = thing.accessor.rw(None, doc=u'\uc544\uc774\ud15c\uc124\uacc4\uad00\ub9ac\uc790\uc5d4\ud2f0\ud2f0 ID')
DocumentContentLibraryEntityID = thing.accessor.rw(None, doc=u'\ubb38\uc11c\ub0b4\uc6a9\uad00\ub9ac\uc790\uc5d4\ud2f0\ud2f0 ID')
ServerHostAddress = thing.accessor.ri(None, doc=u'\uac8c\uc784 \uc11c\ubc84 \uc8fc\uc18c')
LoaderThreadCount = thing.accessor.ri(1, doc=u'\ub85c\ub354 \uc2a4\ub808\ub4dc \uac2f\uc218')
RealTimeClock = thing.accessor.ri(None, doc='RealTimeClock')
EngineConfig = thing.accessor.rw({}, doc='EngineConfig')
ServerConfig = thing.accessor.rw({}, doc='ServerConfig')
ControlModeParams = thing.accessor.ri({}, doc='ControlModeParams')
PlayerInitConfig = thing.accessor.ri({}, doc='PlayerInitConfig')
MaxFPS = thing.accessor.rw(None, doc='MaxFPS')
AccountSession = thing.accessor.ri(None, doc='AccountSession')
ClientSpace = thing.accessor.ri(None, doc='ClientSpace')
GameLogic = thing.accessor.ri(None, doc='GameLogic')
Context = thing.accessor.ri(None, doc='Context')
DummyAvatarSceneObjectData = thing.accessor.ri(None, doc='DummyAvatarSceneObjectData')
IsPreloading = thing.accessor.ri(True, doc=u'\uc120\ub85c\ub529 \uc5ec\ubd80')
UserConfig = thing.accessor.ri(None, doc='UserConfig')
InitialSplashLoadTask = thing.accessor.ri(None, doc='InitialSplashLoadTask')
_App__SERVER_EPOCH = datetime.datetime(2018, 1, 1)
def __setup(self) -> 'int, exit code':
if not __final__:
thing.system.setQuietMode(self.RunOptions.Quiet)
DebugLogCategories = '*,-action,-builder,-cutscene,' if self.RunOptions.SSOAuthToken is not None else ''
DebugLogCategories += os.environ.get('NT_DEBUG_LOG', '*') + ',' + self.RunOptions.DebugLogCategories
for category in DebugLogCategories.split(','):
if not category:
continue
if category.startswith('-'):
logger = natuum.getLogger(category[1:])
logger.setLevel(logging.INFO)
LOG_INFO('Log category [{}] level=debug was disabled'.format(logger.name))
from thing.changeset import changeset, NexonCrashReporterVersion
LOG_INFO('Changeset for Code:{}'.format(changeset))
LOG_INFO('BuildVersion:{}'.format(NexonCrashReporterVersion))
LOG_INFO('LauncherVer:{}, NGM Param:{}, NexonSN:{}, NexonSID:{}'.format(self.RunOptions.LauncherBuildVersion, self.RunOptions.LauncherOptionalParam, self.RunOptions.NexonSN, self.RunOptions.NexonSID))
self.Platform = platform = thing.Platform.Platform()
self.ETWSession_findObjects = platform.createETWSession('findObjects')
if self.RunOptions.SendCrashReport:
installed = platform.installCrashReporter(self.RunOptions.ProjectID)
installed or LOG_WARNING('exception handler is not installed')
else:
platform.setCrashReporterInformation('ProcessType', self.RunOptions.ProductName)
from thing.changeset import NexonCrashReporterVersion
platform.setCrashReporterInformation('ProjectVersion', NexonCrashReporterVersion)
if natuum.m_logFilePath:
relPath = os.path.relpath(natuum.m_logFilePath, os.path.dirname(sys.executable))
platform.setCrashReporterAuxFile(relPath)
if __thing_supports_gc__:
if natuum.objects.m_refCycleFilePath:
relPath = os.path.relpath(natuum.objects.m_refCycleFilePath, os.path.dirname(sys.executable))
platform.setCrashReporterAuxFile(relPath)
platform.registerCrashReporterCallbackBeforeDump(callbackBeforeDump)
thing.system._installExceptHook(fromInit=False)
else:
__final__ or self._App__initConsoleCtrlHandler()
if not __final__:
platform.listenEasyProfileGUI(28077)
else:
self._App__thMan, self.RPCManager, self._App__chMan, self._App__chManThread = self._App__setupRPCManager()
self._App__clientLoaderApartments = self._App__createLoaderApartments()
rootFS = thing.FileSystem.FileSystem(natuum.getRootPath())
dataFS = rootFS['data']
try:
resourcesFS = rootFS['resources']
except Exception:
resourcesFS = rootFS.createDirectory('resources')
rm = natuum.client.resource.Manager(dataFS, resourcesFS)
rm.IsPreloading = self.IsPreloading
self._App__clientLoaderRegisterTokens = self._App__setupResourceManager(rm, self._App__clientLoaderApartments)
self.JobQueue = natuum.jobQueue.JobQueue()
self.EngineConfig = engineConfig = rm.load(natuum.builder.combineID('_', 'EngineConfig')).lockForReading().Data
self.ServerConfig = rm.load(natuum.builder.combineID('_', 'ServerConfig')).lockForReading().Data[self.RunOptions.ClusterID]
self.GatewayEntityID = self.ServerConfig['GatewayEntityID']
self.ChatManagerEntityID = self.ServerConfig['ChatManagerEntityID']
self.ItemDesignLibraryEntityID = self.ServerConfig['ItemDesignLibraryEntityID']
self.DocumentContentLibraryEntityID = self.ServerConfig['DocumentContentLibraryEntityID']
self.ServerHostAddress = self.RunOptions.Host
self.ControlModeParams = rm.ensureResource('controlModeParams@GameSetting')
self.PlayerInitConfig = rm.ensureResource('playerInitialization@GameSetting')
fullScreen = self.RunOptions.FullScreen
if fullScreen:
import ctypes
user32 = ctypes.windll.user32
screenWidth = user32.GetSystemMetrics(0)
screenHeight = user32.GetSystemMetrics(1)
screenRatio = screenWidth / screenHeight
if screenRatio > 1.7777777777777777:
width, height = (2400, 1080)
else:
if screenRatio > 1.6:
if screenWidth == 2048 and screenHeight == 1152:
width, height = screenWidth, screenHeight
else:
width, height = (1920, 1080)
else:
if screenRatio > 1.3333333333333333:
width, height = (1920, 1200)
else:
width, height = (1600, 1200)
else:
width, height = DEFAULT_CLIENT_RESOLUTION
self.AspectRatio = thing.Dirp.Dirp_Static(thing.system.vec(width / height, 1))
self.MaxFPS = max(int(engineConfig.Config.get('maxFPS', 60)), 1)
rendererMode = thing.Renderer.RendererManager().createRendererMode(fullScreen, width, height, 32)
self.Renderer = self._App__Renderer(rendererMode, engineConfig.Config)
self.Renderer.TerrainTessellationQueueLength = 3
self.Renderer.HangDetector = thing.ToolHelpers.HangDetector(self._App__onHangDetected)
self.PropCanvasMipHeadCutCountOnLoad = 0
userConfig = self._loadUserConfig()
if userConfig is not None:
self.UserConfig = userConfig
self._refreshCanvasLoadQuality(userConfig)
self.Renderer.UserConfig = userConfig
else:
self.UserConfig = self.Renderer.UserConfigValue['']
currentLocale = locale.getdefaultlocale()[1]
# if currentLocale != 'cp949':
# if self.Renderer.NativeWindowHandle:
# text = 'Unsupported system locale : {}'.format(currentLocale)
# text += '\n\nTo execute the client, change system locale to cp949 (Korean)'
# self.showWindowsMessageBox(text)
# self.nxlog = None
# self.quit(0)
# return
self._App__rendererModeWatchToken = self.Renderer.ModeVersion.addWatcher(self._App__onRendererModeChangedEntry, '__call__', weakref.ref(self))
self.nxlog = NXLogClient(self.RunOptions.NexonSN, self.RunOptions.NexonSID)
self.nxlog.stageLog(420, '__setup')
self._App__rendererReadyAtom = rendererReadyAtom = thing.Thread.Atom()
self._App__pagerThread = thread = self._App__thMan.createThread(thing.system.nativeThreadEntry, '__call__', self._App__pagerThreadEntry, rendererReadyAtom)
thread.Name = 'Main Updater'
thread.run()
_App__settingsDirName = 'settings'
_App__userConfigFileName = 'userConfig.nothing'
def _loadUserConfig(self):
settingsDirPath = os.path.join(natuum.getRootPath(), 'storage', __class__._App__settingsDirName)
try:
if os.path.exists(settingsDirPath):
fs = thing.FileSystem.FileSystem(settingsDirPath)
else:
raise KeyError
f = fs[__class__._App__userConfigFileName]
ra = thing.Archive.ReadingArchive_FromFile(f)
userConfig = thing.Collection.Dict()
userConfig.load(ra)
LOG_INFO('user config loaded')
except KeyError:
LOG_WARNING('cannot load user config')
userConfig = None
return userConfig
def _saveUserConfig(self, userConfig):
settingsDirPath = os.path.join(natuum.getRootPath(), 'storage', __class__._App__settingsDirName)
try:
os.makedirs(settingsDirPath)
except OSError:
pass
try:
fs = thing.FileSystem.FileSystem(settingsDirPath)
f = fs.createFile(__class__._App__userConfigFileName, True)
wa = thing.Archive.WritingArchive_FromFile(f)
userConfig.save(wa)
wa.flush()
LOG_INFO('user config saved')
except:
LOG_WARNING('cannot save user config')
def _refreshCanvasLoadQuality(self, userConfig):
propTexQuality = userConfig.get('PropTextureQuality', -1)
if propTexQuality >= 0:
propTexQuality = min(2, propTexQuality)
oldValue = self.PropCanvasMipHeadCutCountOnLoad
newValue = (2, 1, 0)[propTexQuality]
if oldValue != newValue:
self.PropCanvasMipHeadCutCountOnLoad = newValue
return True
return False
def __preload(self):
markerResourceIDs = self._App__collectMarkerResources()
effectPreloadList = tuple(markerResourceIDs.get('Effect', set()) | set(self._App__PRELOAD_EFFECTS))
rm = natuum.client.resource.Manager.Instance
rm.preload('Avatar', preloadList=('AT099800(man_default)', 'AT099900(woman_default)'))
rm.preload('Effect', preloadList=effectPreloadList)
rm.preload('CameraAngle')
rm.preload('CameraAngleBlender')
rm.preload('CameraAngleBlenderSet')
rm.preload('HTMLView', preloadList=SIDEMENU_HTMLIVEWS)
rm.preload('HTML', preloadList=('sideMenu', 'sideMenuAvatar'))
rm.preload('ItemDesign')
rm.preload('ItemIO')
rm.preload('MeshSet', preloadList=(self._App__DUMMY_AVATAR_MESHSETS))
rm.preload('PropPart', preloadList=(self._App__PRELOAD_PROPPARTS))
rm.preload('Quest')
rm.preload('VueApp', preloadList=('app', ))
rm.preload('Zone', preloadList=('ZR0000(carveKiranaArea)', ))
for typeName, resourceIDs in markerResourceIDs.items():
if typeName in ('Effect', 'ItemDesign'):
continue
rm.preload(typeName, preloadList=(tuple(resourceIDs)))
rm.preload('GameSetting')
def __collectMarkerResources(self):
def collectResourceIDs--- This code section failed: ---
L. 433 0 LOAD_GLOBAL isinstance
2 LOAD_FAST 'value'
4 LOAD_GLOBAL str
6 CALL_FUNCTION_2 2 '2 positional arguments'
8 POP_JUMP_IF_FALSE 56 'to 56'
10 LOAD_GLOBAL natuum
12 LOAD_ATTR builder
14 LOAD_METHOD isResourceID
16 LOAD_FAST 'value'
18 CALL_METHOD_1 1 '1 positional argument'
20 POP_JUMP_IF_FALSE 56 'to 56'
L. 434 22 LOAD_GLOBAL natuum
24 LOAD_ATTR builder
26 LOAD_METHOD splitID
28 LOAD_FAST 'value'
30 CALL_METHOD_1 1 '1 positional argument'
32 UNPACK_SEQUENCE_3 3
34 STORE_FAST 'name'
36 STORE_FAST 'typeName'
38 STORE_FAST '_'
L. 435 40 LOAD_FAST 'resourceIDs_'
42 LOAD_FAST 'typeName'
44 BINARY_SUBSCR
46 LOAD_METHOD add
48 LOAD_FAST 'name'
50 CALL_METHOD_1 1 '1 positional argument'
52 POP_TOP
54 JUMP_FORWARD 144 'to 144'
56_0 COME_FROM 20 '20'
56_1 COME_FROM 8 '8'
L. 436 56 LOAD_GLOBAL isinstance
58 LOAD_FAST 'value'
60 LOAD_GLOBAL str
62 CALL_FUNCTION_2 2 '2 positional arguments'
64 POP_JUMP_IF_TRUE 104 'to 104'
66 LOAD_GLOBAL isinstance
68 LOAD_FAST 'value'
70 LOAD_GLOBAL collections
72 LOAD_ATTR Sequence
74 CALL_FUNCTION_2 2 '2 positional arguments'
76 POP_JUMP_IF_FALSE 104 'to 104'
L. 437 78 SETUP_LOOP 144 'to 144'
80 LOAD_FAST 'value'
82 GET_ITER
84 FOR_ITER 100 'to 100'
86 STORE_FAST 'value_'
L. 438 88 LOAD_DEREF 'collectResourceIDs'
90 LOAD_FAST 'value_'
92 LOAD_FAST 'resourceIDs_'
94 CALL_FUNCTION_2 2 '2 positional arguments'
96 POP_TOP
98 JUMP_BACK 84 'to 84'
100 POP_BLOCK
102 JUMP_FORWARD 144 'to 144'
104_0 COME_FROM 76 '76'
104_1 COME_FROM 64 '64'
L. 439 104 LOAD_GLOBAL isinstance
106 LOAD_FAST 'value'
108 LOAD_GLOBAL collections
110 LOAD_ATTR Mapping
112 CALL_FUNCTION_2 2 '2 positional arguments'
114 POP_JUMP_IF_FALSE 144 'to 144'
L. 440 116 SETUP_LOOP 144 'to 144'
118 LOAD_FAST 'value'
120 LOAD_METHOD values
122 CALL_METHOD_0 0 '0 positional arguments'
124 GET_ITER
126 FOR_ITER 142 'to 142'
128 STORE_FAST 'value_'
L. 441 130 LOAD_DEREF 'collectResourceIDs'
132 LOAD_FAST 'value_'
134 LOAD_FAST 'resourceIDs_'
136 CALL_FUNCTION_2 2 '2 positional arguments'
138 POP_TOP
140 JUMP_BACK 126 'to 126'
142 POP_BLOCK
144_0 COME_FROM_LOOP 116 '116'
144_1 COME_FROM 114 '114'
144_2 COME_FROM 102 '102'
144_3 COME_FROM_LOOP 78 '78'
144_4 COME_FROM 54 '54'
Parse error at or near `COME_FROM_LOOP' instruction at offset 144_3
def collectMarkerClasses(marker, markerClasses_):
markerClasses.add(marker)
for marker_ in marker.__subclasses__():
collectMarkerClasses(marker_, markerClasses_)
markerResourceIDs = collections.defaultdict(set)
markerClasses = set()
collectMarkerClasses(natuum.client.clientSpace.marker.Marker, markerClasses)
for arranger in natuum.client.clientSpace.arranger.__dict__.values():
if isinstance(arranger, type):
collectMarkerClasses(arranger, markerClasses)
for marker in markerClasses:
for attribute in marker.__dict__.values():
collectResourceIDs(attribute, markerResourceIDs)
collectMarkerClasses = None
collectResourceIDs = None
return markerResourceIDs
def __run(self) -> 'int, exit code':
if not __final__:
if not self.RunOptions.NoUsageReport:
self._App__sendUsageReport()
windowTitle = 'Peria Chronicles{}'.format(natuum.util.getBuildVersion())
rendererExitCode = self.Renderer.start(windowTitle, thing.system.ICanvas.PIXEL_FORMAT_R8G8B8A8, self._App__rendererReadyAtom, self.QuitOnRendererShutdown)
return self._App__exitCode
def __cleanup(self) -> None:
global Instance
if not __final__:
for i in range(5):
if self._App__pagerThread.wait(1):
break
(LOG_DEBUG if i <= 2 else LOG_WARNING)(u'Pager \uc2a4\ub808\ub4dc \uc885\ub8cc \ub300\uae30\uc911 (%d\ucd08)', i + 1)
else:
self._App__pagerThread.wait(5)
del self._App__pagerThread
self.JobQueue.shutdown()
self._App__clientLoaderRegisterTokens.clear()
for apartment in self._App__clientLoaderApartments.values():
apartment.shutdown()
self._App__shutdownTableManagers()
self._App__cleanupResourceManager()
if self._App__chMan is not None:
self._App__chMan.shutdown()
VERIFY(self._App__chManThread.wait(5))
del self._App__chManThread
del self._App__mainApartment
self.RPCManager.shutdown()
Instance = None
@Thing.overrides(natuum.app.App)
def run(self) -> 'int, exit code':
self._App__setup()
ret = self._App__run()
self._App__cleanup()
return ret
@Thing.overrides(natuum.app.App)
def quit(self, exitCode):
LOG_INFO('client quit called')
self.Renderer.shutdown()
curPage = natuum.client.pager.CurPage
if curPage is not None:
if not natuum.client.pager.command((curPage.canQuitApp), wait=True):
return
super().quit()
self._App__exitCode = exitCode
if self.RunOptions.SendCrashReport:
self.Platform.setCrashReporterClientExit(exitCode)
if natuum.client.app.Instance.nxlog is not None:
natuum.client.app.Instance.nxlog.stageLog(1010, str(exitCode))
self.Renderer.quit(True)
def isInRendererThreadGroup(self, threadID):
return threadID == self.Renderer.ThreadID
def collectZcomObjects(self, showBackrefsOfIDs=False):
if not __thing_can_get_zcom_objects__:
return
createdZcomObjects = self._App__createdZcomObjects
if createdZcomObjects is None:
self._App__createdZcomObjects = thing.ToolHelpers.CreatedObjects()
else:
self._App__createdZcomObjects = None
objs = None
try:
backrefsDir = os.path.join(natuum.getLogPath(), 'backrefs')
os.makedirs(backrefsDir, exist_ok=True)
today = datetime.datetime.today()
timestamp = '%4d%02d%02d_%02d%02d%02d' % (today.year, today.month, today.day, today.hour, today.minute, today.second)
graphFilePath = os.path.join(backrefsDir, 'graph_c{}.txt'.format(timestamp))
with open(graphFilePath, 'w', encoding='utf_8-sig') as (file):
natuum.objects.Graph.collectToFile(file)
objs = list(createdZcomObjects)
objectIDsFilePath = os.path.join(backrefsDir, 'objects_c{}.txt'.format(timestamp))
with open(objectIDsFilePath, 'w') as (file):
natuum.objects.saveObjectIDsToFile(file, objs)
if showBackrefsOfIDs:
multiprocessing.Process(target=(natuum.objects.showBackrefsOfIDs),
args=(
backrefsDir, graphFilePath, objectIDsFilePath)).start()
thing.system._debugBreak()
objs.clear()
finally:
if objs is not None:
objs.clear()
def checkMemory(self):
if __final__ or self._App__memoryReclaimer is None:
return
for i in range(10):
self._App__memoryReclaimer.reclaimAll()
natuum.objects.GarbageCollector().collect(-1, True)
trendsCollector = self._App__objectCountTrendsCollector
if trendsCollector is None:
self._App__objectCountTrendsCollector = trendsCollector = natuum.objects.CountTrendsCollector()
trendsCollector.collect()
trends = [(v, trendsCollector.getCount(k), k) for k, v in trendsCollector.items()]
trends.sort(reverse=True)
tracker = self._App__memoryTracker
lines = []
lines.append('**** object count trend log start ****')
for trend, count, typeName in trends:
countList = tracker.get(typeName, None)
if countList is None:
countList = list()
tracker[typeName] = countList
countList.append(count)
lines.append('{:G}\t{}\t{}'.format(trend, typeName, '\t'.join([str(i) for i in countList])))
lines.append('**** object count trend log end ****')
def getRoughServerTime(self) -> 'int. milliseconds':
passed = int(time.time() - self._App__clientTime) * 1000
return (self._App__serverTime + passed) % 2147483647
def setListener(self, flag, position) -> None:
soundManager = self.SoundManager
if soundManager is not None:
configuration = self.ControlModeParams.get('sound')
if configuration is None:
if ListenerType.ZONE == flag:
soundManager.Listener = position
elif configuration['listener'] == flag:
soundManager.Listener = thing.Dirp.Dirp_FromLocal(configuration['local'], thing.math.ROT_IDENTITY, position)
def _setServerTime(self, serverNow):
self._App__clientTime = time.time()
self._App__serverTime = int(serverNow * 1000)
class __Renderer(natuum.renderer.Renderer):
@Thing.overrides(natuum.renderer.Renderer)
def onRendererCloseButton(self, renderer: 'thing.system.IRenderer', private) -> None:
Instance.quit(0)
@Thing.overrides(natuum.renderer.Renderer)
def onRendererConfigChange(self, renderer: 'thing.system.IRenderer', private) -> None:
if isinstance(private, thing.system.IMutableMapping):
userConfig = private
natuum.client.app.Instance._saveUserConfig(userConfig)
if natuum.client.app.Instance._refreshCanvasLoadQuality(userConfig):
natuum.client.pager.command(natuum.client.app.Instance.GameLogic.onCanvasLoadQualityChange)
def _registerLayerCategories(self) -> None:
self.registerLayerCategory('UI').forget()
class __RunOptionHandler(natuum.runOptions.IHandler):
def fillDefaults(self, options):
options.Host = None
options.Quiet = False
options.InfuseActor = None
options.CrashReportURL = 'http://socorro.thingsoft.com:8882/submit'
options.ProductName = 'client'
options.ProductVersion = '0.8.1'
options.NoUsageReport = False
options.NoEffect = False
options.BatchJob = None
options.Locale = None
options.ClusterID = 'default'
options.SSOAuthToken = None
options.FullScreen = False
options.DebugLogCategories = '*,-action,-builder,-cutscene' if __final__ else ''
options.ProjectID = 'peria_dev'
options.LauncherBuildVersion = -1
options.LauncherOptionalParam = ''
options.NexonSN = 0
options.NexonSID = None
options.disableNXLog = False
crashReport = os.environ.get('NT_NO_CRASH_REPORT', '0')
options.SendCrashReport = crashReport == '0'
def buildParser(self, parser: 'optparse.OptionParser', options) -> None:
parser.add_option('-a', '--address', dest='Host', help='server address')
parser.add_option('-q', '--quiet', dest='Quiet', default=False, action='store_true', help='quiet mode')
parser.add_option('-i', '--infuseActor', dest='InfuseActor', help='auto infuse actor')
parser.add_option('-b', '--batch', dest='BatchJob', help='batch job file')
parser.add_option('-l', '--locale', dest='Locale', help='locale')
parser.add_option('-C', '--cluster', dest='ClusterID', help='cluster id')
parser.add_option('--crashreport-url', dest='CrashReportURL', help='crash report url')
parser.add_option('--product-name', dest='ProductName', help='product name')
parser.add_option('--product-version', dest='ProductVersion', help='product version')
parser.add_option('--nousagereport', dest='NoUsageReport', default=False, action='store_true', help="don't send usage report")
parser.add_option('-t', '--auth', dest='SSOAuthToken', help='auth token')
parser.add_option('-f', '--fullScreen', dest='FullScreen', default=False, action='store_true', help='full screen mode')
parser.add_option('--debugLog', type='str', dest='DebugLogCategories', help='comma seperated log categories')
parser.add_option('--projectid', dest='ProjectID', help='crash reporter project id')
parser.add_option('--launcherBuildVersion', dest='LauncherBuildVersion', help='launcher build version')
parser.add_option('--launcherParam', dest='LauncherOptionalParam', default='', help='argument from NexonGameManager')
parser.add_option('--nexonsn', dest='NexonSN', default=0, help='argument from NexonGameManager')
parser.add_option('--nexonsid', dest='NexonSID', default='', help='argument from NexonGameManager')
parser.add_option('--disableNXLog', dest='disableNXLog', default=False, action='store_true', help='disable NXLOG')
def onParsed(self, parser: 'optparse.OptionParser', options, args: tuple) -> None:
if options.BatchJob is not None:
batchJobPath = os.path.join(natuum.getRootPath(), 'settings', options.BatchJob)
options.BatchJob = natuum.util.readXDF(batchJobPath)
if options.Locale is not None:
if options.Locale in ('ko', 'ja', 'zh', 'en'):
natuum.util.DefaultLocale = options.Locale
def __createLoaderApartments(self) -> '{ str: thing.system.IApartment }':
apartments = {}
for i in range(self.LoaderThreadCount):
name = 'Loader{}'.format(i)
apartmentObject = ApartmentActiveObject(self._App__thMan, self.RPCManager)
apartmentObject.start(name, -0.13333333333333333)
apartments[name] = apartmentObject.Apartment
return apartments
if not __final__:
def __initConsoleCtrlHandler(self):
def _terminateProcess(ctrlType):
thing.system._TerminateProcess()
thing.system.setConsoleCtrlHandler(_terminateProcess)
def __setupRPCManager(self):
thMan = thing.Thread.ThreadManager()
rpcManager = thing.RPC.RPCManager(thMan)
self._App__mainApartment = natuum.apartment.Apartment()
return (
thMan, rpcManager, None, None)
def __setupResourceManager(self, rm, loaderApartments) -> '{ str : thing.system.IForgettableToken }':
tokens = {}
for name, apartment in loaderApartments.items():
token = rm.CacheManager.registerDelayedLoaderApartment(name, apartment.Inner)
tokens[name] = token
return tokens
def __cleanupResourceManager(self) -> None:
natuum.client.resource.Manager.Instance.shutdown()
@staticmethod
def __onRendererModeChangedEntry(watched, weakSelf, hint, funcName) -> None:
self = weakSelf()
if self is not None:
clientSpace = self.ClientSpace
if clientSpace is not None:
natuum.client.pager.command(clientSpace.onRendererModeChanged)
def __pagerThreadEntry(self, rendererReadyAtom: 'thing.system.IAtom') -> None:
self.Context = thing.Common.Sites()
self.EventLoop = loop = natuum._entity.apartmentEvents.EventLoop()
asyncio.set_event_loop(loop)
self.Apartment = loop.Apartment
thing.system.setSwitchIntervalForCurrentThread(1)
rendererReadyAtom.wait()
inputManager = natuum.client.input.Manager()
self._App__initFontManager()
self.showInitialSplash(True)
if self.InitialSplashLoadTask:
loop.run_until_complete(self.InitialSplashLoadTask)
_ = self.Platform.disableAccessibility()
self.GUI = guiSystem = thing.Renderer.GUISystem()
self.Renderer.GUISystem = guiSystem
self.SoundManager = soundManager = self._App__createSoundManager(parameters=(self.ControlModeParams))
try:
soundManager.setVolume('music', self.UserConfig['BGMVolume'])
soundFXVol = self.UserConfig['SoundEffectVolume']
for bus in ('effect', 'voice', 'environment', 'kiranaContract'):
soundManager.setVolume(bus, soundFXVol)
except KeyError:
pass
natuum.configuration.Configuration.initialize()
self.ClusterGuestNode = node = natuum._entity.framework.cluster.setup_guest(self.ServerConfig['GatewayServers'])
if self.ServerHostAddress is not None:
node.overrideConnectingtHost(self.ServerHostAddress)
self.AccountSession = natuum.client.app.AccountSession()
self.ZoneChanged = natuum.signal.Signal(natuum.client.zone.Zone)
self._App__initClassTables()
if self.IsPreloading:
self._App__preload()
self._App__initTableManagers()
clientSpace = natuum.client.clientSpace.ClientSpace()
clientSpace.onRendererModeChanged()
clientSpace.addDesktop()
clientSpace.addDesktop()
clientSpace.addInventory()
self.ClientSpace = clientSpace
inputManager.setEventHandler(clientSpace)
self.GameLogic = gameLogic = natuum.client.logic.GameLogic(clientSpace)
gameLogic.SyncClock.defer(App._App__LONG_TIME_PLAY_WARNING_INTERVAL, App._App__warnLongTimePlay, 1).forget()
self._App__setupIDE()
self._App__buildDummyAvatar()
self._App__processorToken = thing.Thread.Processor().addTask(0, self.MaxFPS)
self._App__memoryReclaimer = memoryReclaimer = thing.Common.MemoryReclaimer()
memoryReclaimer.clearPolicies()
memoryReclaimer.appendPolicy(memoryReclaimer.MEMORY_RECLAIMER_CRITERION_AVAILABLE_PHYSICAL_MB_LESS_THAN, 100, 209715200, 5)
memoryReclaimer.appendPolicy(memoryReclaimer.MEMORY_RECLAIMER_CRITERION_AVAILABLE_PHYSICAL_MB_LESS_THAN, 500, 104857600, 10)
memoryReclaimer.appendPolicy(memoryReclaimer.MEMORY_RECLAIMER_CRITERION_AVAILABLE_PHYSICAL_MB_LESS_THAN, 1000, 1, 15)
memoryReclaimer.appendPolicy(memoryReclaimer.MEMORY_RECLAIMER_CRITERION_PAGE_FILE_USAGE_MB_GREATER_THAN, 7168, 102400, 15)
natuum.apartment.getCurrentApartment().Alarm.postTask(self._App__reclaimMemory, 0).forget()
if not __final__:
natuum.apartment.getCurrentApartment().Alarm.postTask(self._App__logMemoryReclaimer, 60000).forget()
self._App__memoryTracker = {}
loop.call_soon(natuum.client.pager.enter)
loop.run_forever()
loop.run_until_complete(node.shutdown())
loop.close()
gameLogic.shutdown()
thing.Client.EffectManager().terminate()
soundManager.shutdown()
guiSystem.shutdown()
self.Renderer.GUISystem = None
def __initClassTables(self) -> None:
rm = natuum.client.resource.Manager.Instance
natuum.client.action.ActionTableManager.ensureInstance()
rawClassTables = rm.load(natuum.gameClass.CLASS_TABLE).lockForReading().Data
natuum.gameClass.initManager(rawClassTables)
if self.IsPreloading:
natuum.gameClass.getManagerInstance().preloadClasses()
def __initTableManagers(self) -> None:
rm = natuum.client.resource.Manager.Instance
natuum.emotionCutscene.initManager(rm)
natuum.client.craft.initManager(rm)
natuum.variation.initManager(rm)
natuum.indexBook.initManager(rm)
natuum.client.contract.initManager(rm)
natuum.reward.initManager(rm)
natuum.growth.initManager(rm)
natuum.actor.initManager(rm)
natuum.client.quest.initManager(rm)
natuum.town.initManager(rm)
natuum.client.relationship.initManager(rm)
natuum.client.synergy.initManager(rm)
natuum.gameService.wordFilter.initManager(rm)
def __initFontManager(self) -> None:
fontManager = thing.Renderer.FontManager()
rm = natuum.client.resource.Manager.Instance
rm.preload('Font', preloadList=(natuum.util.FontNames))
for fontName in natuum.util.FontNames:
fontID = natuum.builder.combineID(fontName, 'Font')
fontData = rm.ensureResource(fontID)
fontManager.registerFontData(fontData[0], fontData[1])
for info in TEXT_FORMAT_TO_PRELOAD:
(fontManager.createRasterTextFormat)(self.Renderer, *info)
def __setupIDE(self) -> None:
pass
def __buildDummyAvatar(self) -> None:
rm = natuum.client.resource.Manager.Instance
meshSet = rm.ensureResource('AP7250@MeshSet')
sceneObjectData = thing.Scene.SceneObjectData_Mesh()
sceneObjectData.ShaderFlags = thing.system.IShader.SHADER_SHADOW_MAP | thing.system.IShader.SHADER_REALISTIC
for meshName, meshAndBone in meshSet.items():
sceneObjectMeshElementData = thing.Scene.SceneObjectMeshElementData()
sceneObjectMeshElementData.Mesh = meshAndBone['mesh']
sceneObjectData.Elements[meshName] = sceneObjectMeshElementData
self.Renderer.warm(sceneObjectData, 0)
self.DummyAvatarSceneObjectData = sceneObjectData
def __shutdownTableManagers(self) -> None:
natuum.client.craft.shutdownManager()
natuum.client.action.ActionTableManager.shutdown()
natuum.gameClass.shutdownManager()
def __reclaimMemory(self) -> None:
if self.IsQuitting:
return
waitDuration = self._App__memoryReclaimer.reclaim()
natuum.apartment.getCurrentApartment().Alarm.postTask(self._App__reclaimMemory, waitDuration).forget()
if not __final__:
def __logMemoryReclaimer(self) -> None:
if self.IsQuitting:
return
memoryReclaimer = self._App__memoryReclaimer
LOG_INFO('available physical memory: {} GB'.format(memoryReclaimer.AvailablePhysicalMemoryGB))
LOG_INFO('page file usage: {} GB'.format(memoryReclaimer.PageFileUsageGB))
natuum.apartment.getCurrentApartment().Alarm.postTask(self._App__logMemoryReclaimer, 60000).forget()
def __sendUsageReport(self) -> None:
def sendReport(info: 'sequence of ( str, str )') -> str:
tokenDelim = '_TDELIM_'
emptyToken = '_EMPTY'
stream = tokenDelim.join([value if value != '' else emptyToken for value in info])
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(stream.encode('utf-8'), ('wild.thingsoft.com', 8025))
LOG_INFO('Usage report sent..')
except socket.error:
pass
sock.close()
rendererManager = thing.Renderer.RendererManager()
usageInfo = (
'{}'.format(platform.node()),
'{} {}'.format(platform.system(), platform.version()),
'{}'.format(platform.processor()),
'{}'.format(self.Platform.MemorySize),
'{}'.format(self.Platform.HardDiskSize),
'{}'.format(rendererManager.DeviceDescription.split('\x00')[0].strip()),
'{}'.format(natuum.util.getBuildVersion()))
sendReport(usageInfo)
sendReport = None
@classmethod
def __createSoundManager(cls, parameters=None, liveUpdate=False) -> 'thing.system.ISoundManager':
rm = natuum.client.resource.Manager.Instance
masterBank = rm.load(cls._App__masterBankResourceID)
masterBank = masterBank.lockForReading().Data
try:
masterBank = masterBank.SoundData
except KeyError:
LOG_WARNING(u'{}\ub97c \uc77d\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uc774\ubca4\ud2b8 \uacbd\ub85c\ub97c \uc77d\uc9c0 \ubabb\ud574 \uc18c\ub9ac\uac00 \ub098\uc624\uc9c0 ' + (u'\uc54a\uc2b5\ub2c8\ub2e4.').format(cls._App__masterBankResourceID))
return
else:
masterStringBank = rm.load(cls._App__masterStringBankResourceID)
try:
masterStringBank = masterStringBank.lockForReading().Data.SoundData
except KeyError:
LOG_WARNING(u'{}\ub97c \uc77d\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \uc774\ubca4\ud2b8 \uacbd\ub85c\ub97c \uc77d\uc9c0 \ubabb\ud574 \uc18c\ub9ac\uac00 \ub098\uc624\uc9c0 ' + (u'\uc54a\uc2b5\ub2c8\ub2e4.').format(cls._App__masterStringBankResourceID))
return
else:
busGroups = cls._App__getBusGroups(parameters)
return thing.Sound.SoundManager(masterBank, masterStringBank, liveUpdate, natuum.getRootPath(), busGroups)
def __closeSplashZoneViewerMarker(self):
self._App__splashZoneViewerMarker.close()
self._App__splashZoneViewerMarker = None
def showInitialSplash(self, show):
self._App__splashNoticeLayer = None
self._App__splashLayer = None
if self._App__splashZoneViewerMarker:
self.Renderer.Timer.defer(0, self._App__closeSplashZoneViewerMarker).forget()
if show:
rm = natuum.client.resource.Manager.Instance
canvas = rm.load('ui_title_booting@Canvas').lockForReading().Data
self._App__splashLayer = self.Renderer.createOverlayLayer(thing.Dirp.Dirp_Static(), thing.Dirp.Dirp_Static(self.AspectRatio), canvas)
self.Renderer.updateLayers()
@staticmethod
def __onSplashZoneLoaded(fut_) -> None:
app = natuum.client.app.Instance
canvas = app._App__splashZoneViewerMarker.getViewCanvas()
app._App__splashLayer = app.Renderer.createOverlayLayer(thing.Dirp.Dirp_Static(), thing.Dirp.Dirp_Static(app.AspectRatio), canvas)
text = natuum.client.text.Text(u'\ub85c\ub529 \uc911\uc785\ub2c8\ub2e4. \uae30\ub2e4\ub9ac\ub294 \ub3d9\uc548 \uc9c0\ud615 \ud3b8\uc9d1\uc744 \uc5f0\uc2b5\ud574 \ubcf4\uc138\uc694.', 22, 'NanumSquare', thing.system.vec(xyzw=1), thing.system.vec(a=1), 'bold', 0.0)
app._App__splashNoticeLayer = app.Renderer.createOverlayLayer(thing.Dirp.Dirp_Static(thing.system.vec(0.5, 0.8)), thing.Dirp.Dirp_Static(thing.system.vec(text.getWidth(), text.getHeight()) / text.getHeight() * 0.04), text.getCanvas())
app.Renderer.updateLayers()
def showWindowsMessageBox(self, text, title='Peria Chronicles', uType=0, quit=False):
import ctypes
MessageBox = ctypes.windll.user32.MessageBoxW
ret = None
if natuum.client.app.Instance.Renderer.NativeWindowHandle:
ret = MessageBox(natuum.client.app.Instance.Renderer.NativeWindowHandle, text, 'Peria Chronicles', uType)
if quit:
self.GameLogic.turnOffGame()
return ret
@staticmethod
def __warnLongTimePlay(count):
app = Instance
if app is None:
return
gameLogic = app.GameLogic
if gameLogic is None:
return
getLocalString = natuum.action.ActionTableManager.getInstance().getLocalString
author = getLocalString('NoticeAuthor')
longTimePlayWarning = getLocalString('LongTimePlayWarning').format(N=count)
ageRatingInformation = getLocalString('AgeRatingInformation')
gameLogic.createMarker('message', ((u'(\uc0c9\uc0c1:\ube68\uac15)(\ud06c\uae30:\ud06c\uac8c){}(/\ud06c\uae30)(/\uc0c9\uc0c1)').format(longTimePlayWarning)), forced=True)
gameLogic.SyncClock.addWork(5000, (gameLogic.createMarker), 'message', ((u'(\uc0c9\uc0c1:\ube68\uac15)(\ud06c\uae30:\ud06c\uac8c){}(/\ud06c\uae30)(/\uc0c9\uc0c1)').format(ageRatingInformation)), forced=True).forget()
gameLogic.vueUiController.ChattingView.addChatText('notice', author, longTimePlayWarning)
gameLogic.vueUiController.ChattingView.addChatText('notice', author, ageRatingInformation)
gameLogic.SyncClock.defer(App._App__LONG_TIME_PLAY_WARNING_INTERVAL, App._App__warnLongTimePlay, count + 1).forget()
@staticmethod
def __getBusGroups(parameters) -> '{ str : var }':
if parameters is None:
return {}
try:
return parameters['sound']['bus']['groups']
except KeyError:
return {}
@staticmethod
def __onHangDetected(detected: 'thing.system.IHangDetector') -> None:
if not __final__:
import thing.debugger
if thing.debugger.checkAttached():
LOG_WARNING('hang detected')
thing.debugger.breakIfAttached()
return
try:
raise RuntimeError('hang detected')
except Exception as e:
try:
sys.excepthook(type(e), e, e.__traceback__)
finally:
e = None
del e
_App__thMan = None
_App__chMan = None
_App__clientLoaderApartments = None
_App__clientLoaderRegisterTokens = None
_App__chManThread = None
_App__pagerThread = None
_App__rendererReadyAtom = None
_App__exitCode = 0
_App__serverPort = 9999
_App__server = None
_App__memoryReclaimer = None
_App__memoryChecker = None
if not __final__:
_App__objectCountTrendsCollector = None
if __thing_can_get_zcom_objects__:
_App__createdZcomObjects = None
_App__actionTableManager = None
_App__masterBankResourceID = natuum.builder.combineID('MasterBank', 'Sound')
_App__masterStringBankResourceID = natuum.builder.combineID('MasterBankstrings', 'Sound')
_App__splashLayer = None
_App__splashNoticeLayer = None
_App__splashZoneViewerMarker = None
_App__PRELOAD_EFFECTS = ('EF7000', 'EF7000_lumi', 'EF7001', 'EF7002', 'EF7003',
'EF7004', 'EF7006', 'EF7007', 'EF7010', 'EF7012', 'EF7013',
'EF7016', 'EF7019', 'EF7020', 'EF7022', 'EF7024', 'EF7033',
'EF7050', 'EF7051', 'EF7052', 'EF7053', 'EF7054', 'EF7058',
'EF7074', 'EF7075', 'EF7080', 'EF7081', 'EF7082', 'EF7083',
'EF7084', 'EF7093', 'EF7094', 'EF7095', 'EF7096', 'EF7098',
'EF7099', 'EF8000', 'EF8001', 'EF8002', 'EF8003', 'EF8003',
'EF8004', 'EF8008', 'EF8010', 'EF8011', 'EF8012', 'EF8013',
'EF8015', 'EF8016', 'EF8017', 'EF8020', 'EF8021', 'EF8022',
'EF8030', 'EF8031', 'EF8040', 'EF8041', 'EF8050', 'EF8051',
'EF8100', 'EF8100a', 'EF8100b', 'EF8100d', 'EF8107',
'EF8130', 'EF8131', 'EF8132', 'EF8133', 'EF8134', 'EF8135',
'EF8136', 'EF8137', 'EF8139', 'EF8142', 'EF8142', 'EF8143',
'EF8144', 'EF8145', 'EF8146', 'EF8147', 'EF8148', 'EF8149',
'EF8149', 'EF8149', 'EF8150', 'EF8151', 'EF8152', 'EF8153',
'EF8154', 'EF8156', 'EF8159', 'EF8160', 'EF8162', 'EF8165',
'EF8167', 'EF8168', 'EF8170', 'EF8171', 'EF8172', 'EF8173',
'EF8174', 'EF8175', 'EF8176', 'EF8179', 'EF8182', 'EF8184',
'EF8186', 'EF8187', 'EF8188', 'EF8189', 'EF8191', 'EF8192',
'EF8194', 'EF8196', 'EF8197', 'EF8198', 'EF8199', 'EF8200',
'EF8201', 'EF8205', 'EF8207', 'EF8210', 'EF8211', 'EF8212',
'EF8213', 'EF8216', 'EF8217', 'EF8218', 'EF8219', 'EF8220',
'EF8158a', 'EF8158b', 'EF8158c', 'EF8224', 'EF8233',
'EF8234', 'EF8236', 'EF8237', 'SEF(warning_01)', 'SEF1018(world_map)',
'SEF1020(marker_open)', 'SEF1023(guardian_qattack_ready)',
'SEF1025(guardian_qattack_fire)', 'SEF1026(guardian_qattack_targeting)',
'SEF1028(actionmaker_click)', 'SEF1029(actionmaker_hover)',
'SEF1030(actionmaker_open)', 'SEF1031(actionmaker_release)',
'SEF1032(conversation_click)', 'SEF1033(conversation_hover)',
'SEF1034(f_key_to_continue_conversation)', 'SEF1035(f_key_to_start_conversation)',
'SEF1036(general_button_click)', 'SEF1037(general_button_release)',
'SEF1038(changing_tab)', 'SEF1043(list_open)', 'SEF1044(list_close)',
'SEF1045(Xbutton)', 'SEF1046(popup_window)', 'SEF1047(conversation_release)',
'SEF1048(f_key_hover)', 'SEF1049(general_button_hover)',
'SEF1050(tab_hover)', 'SEF1051(small_button_click)',
'SEF1052(small_button_hover)', 'SEF1053(small_button_release)',
'SEF1054(kirana_listup_hover)', 'SEF1055(kirana_listup_click)',
'SEF1057(kirana_listup_release)', 'SEF1058(kirana_listup_release_cancel)',
'SEF1063(f_key_click)', 'SEF1065(battle_book_use)',
'SEF1066(inventory_hover)', 'SEF1067(inventory_click)',
'SEF1068(inventory_block)', 'SEF1069(inventory_release)',
'SEF1070(tab_release)', 'SEF1071(battle_book_hover)',
'SEF1075(battle_book_appearance)', 'SEF1076(battle_book_disappearance)',
'UI_deliveryRangeSphere', 'balloon_marker', 'button_marker',
'caption_marker', 'close_marker', 'coordinates_marker',
'gauge_marker', 'label', 'list_marker', 'list_marker3',
'text_balloon_marker', 'text_edit_marker', 'tooltip_marker',
'window', 'window2')
_App__PRELOAD_PROPPARTS = ('PP6954', 'PP6366(blue)', 'PP6366(green)', 'PP6366(purple)',
'PP6366(red)', 'PP6366(white)', 'PP6366(yellow)',
'PP6851', 'PP8200', 'PP8201', 'PP8202', 'PP8203',
'PP8204', 'PP8205', 'PP8206', 'PP8207', 'PP8208',
'PP8209', 'PP8210', 'PP8211', 'PP8212', 'PP8213',
'PP8214')
_App__DUMMY_AVATAR_MESHSETS = ('AP7250', )
class AccountSession(natuum._entity.IEntitySubscriber, IGatewaySubscriber):
def __init__(self):
self._AccountSession__connected = False
self._AccountSession__gatewaySubscription = None
self._AccountSession__NGSClient = None
self._AccountSession__JYPClient = None
self._AccountSession__accountID = None
self._AccountSession__kickoutMessage = None
self._AccountSession__weakOnConntionQueueChangedCallback = None
self._AccountSession__weakOnAccountAuthenticatedAfterWaitingQueueCallback = None
self.OnDisconnected = natuum.signal.Signal(bool, str)
self.OnBeginTransferMessage = natuum.signal.Signal(bool)
self.OnPrepareTransferMessage = natuum.signal.Signal(str)
self.OnFinishTransferMessage = natuum.signal.Signal(thing.system.uuid, str, bool)
async def connect(self, callback=None, maxTryCount=-1):
if self._AccountSession__connected:
return
await natuum.client.app.Instance.ClusterGuestNode.start()
apartment = asyncio.get_event_loop().Apartment
tryCount = 0
entity = None
while maxTryCount < 0 or maxTryCount > tryCount:
try:
entity = await apartment.EntityManager.findEntity(natuum.client.app.Instance.GatewayEntityID)
break
except natuum._entity.error.RemoteEntityServerNotFound:
LOG_WARNING('RemoteEntityServer not found')
return
except natuum._entity.error.EntityNotExist:
LOG_WARNING('GatewayEntity not exist')
return
except natuum._entity.error.EntityNotRestored:
LOG_WARNING('GatewayEntity not restored. try after 5 seconds. try count : {}'.format(tryCount))
tryCount += 1
await asyncio.sleep(5)
except natuum._entity.error.SubscriptionChannelClosed:
tryCount += 1
await asyncio.sleep(5)
except asyncio.TimeoutError:
return
except asyncio.CancelledError:
return
if entity is None:
return
try:
subscription = await natuum._entity.subscribeEntity(entity, 'hello', None, self)
except natuum._entity.error.SubscribeFailed:
return
else:
self._AccountSession__gatewaySubscription = subscription
self._AccountSession__connected = True
LOG_INFO('Connection to game server established')
async def disconnect(self, forced=True):
if not self._AccountSession__connected:
return
LOG_INFO('Connection to game server closed')
self._AccountSession__connected = False
self._AccountSession__gatewaySubscription = None
self._AccountSession__NGSClient = None
self._AccountSession__JYPClient = None
await natuum.client.app.Instance.ClusterGuestNode.shutdown()
self.OnDisconnected.emit(forced, self.KickOutMessage)
self.KickOutMessage = None
def setAuthenticationWaitingCallback(self, onConntionQueueChanged, onAccountAuthenticatedAfterWaitingQueue):
self._AccountSession__weakOnConntionQueueChangedCallback = weakref.WeakMethod(onConntionQueueChanged) if onConntionQueueChanged is not None else None
self._AccountSession__weakOnAccountAuthenticatedAfterWaitingQueueCallback = weakref.WeakMethod(onAccountAuthenticatedAfterWaitingQueue) if onAccountAuthenticatedAfterWaitingQueue is not None else None
@Property
def Connected(self) -> 'bool':
return self._AccountSession__connected
@Property
def AccountID(self) -> 'str':
return self._AccountSession__accountID
@accountID.setter
def AccountID(self, accountID: 'str'):
self._AccountSession__accountID = accountID
@Property
def KickOutMessage(self) -> 'str':
return self._AccountSession__kickoutMessage
@KickOutMessage.setter
def KickOutMessage(self, msg: 'str'):
self._AccountSession__kickoutMessage = msg
@Property
def GatewaySubscription(self):
return self._AccountSession__gatewaySubscription
def setupNGS(self):
LOG_INFO('NGS:setupNGS')
if self._AccountSession__NGSClient is not None:
return
natuum.client.app.Instance.nxlog.stageLog(510, 'setupNGS')
def _sendMessage(weakSubscription, data):
subscription = weakSubscription()
if subscription is not None:
_data = data.tobytes()
subscription.MessageProxy.onNGSMessageReceived(_data, nowait=True)
def _callSendMessage(loop, weakSelf, data):
loop.call_soon_threadsafe(_sendMessage, weakSelf, data)
callableFactory = thing.Common.CallableFactory()
args = thing.Collection.Array()
args.append(asyncio.get_event_loop())
args.append(weakref.ref(self._AccountSession__gatewaySubscription))
args.append(callableFactory.PlaceHolder[1])
sendNGSMessage = callableFactory.bind(_callSendMessage, '__call__', args.iterate())
self._AccountSession__NGSClient = thing.NGSClient.NGSClient(sendNGSMessage)
if not self._AccountSession__NGSClient.isInitialized():
natuum.client.app.Instance.showWindowsMessageBox(u'NGS \ucd08\uae30\ud654\uc5d0 \uc2e4\ud328\ud558\uc5ec \ud504\ub85c\uadf8\ub7a8\uc744 \uc885\ub8cc\ud569\ub2c8\ub2e4.')
natuum.client.app.Instance.GameLogic.turnOffGame()
natuum.client.app.Instance.nxlog.stageLog(520, 'setupNGS')
def setupJYP(self, nexonID):
LOG_INFO('NGS:setupJYP')
if self._AccountSession__JYPClient is not None:
return
self._AccountSession__JYPClient = thing.JYP.JYPClient(nexonID)
self._AccountSession__JYPClient.collectThisThread()
def onFrameRenderForJYP(self):
if self._AccountSession__JYPClient is not None:
self._AccountSession__JYPClient.onFrameRender()
def onZoneEnterForJYP(self, zoneID):
if self._AccountSession__JYPClient is not None:
self._AccountSession__JYPClient.onSetZone(zoneID)
@messageRPC.impl(IGatewaySubscriber)
def doAnything(self, *args, **kwds):
pass
@messageRPC.impl(IGatewaySubscriber)
def onBeginTransfer(self, targetZoneName: str):
self.OnBeginTransferMessage.emit(targetZoneName)
@messageRPC.impl(IGatewaySubscriber)
def onPrepareTransfer(self, zoneStyle: str):
self.OnPrepareTransferMessage.emit(zoneStyle)
@messageRPC.impl(IGatewaySubscriber)
async def onFinishTransfer(self, zoneEntityID: 'thing.system.uuid or None', reason: str, isTimeout: bool):
self.OnFinishTransferMessage.emit(zoneEntityID, reason, isTimeout)
@messageRPC.impl(IGatewaySubscriber)
def onNGSMessageReceivedFromServer(self, data):
if self._AccountSession__NGSClient is not None:
self._AccountSession__NGSClient.onReceive(data)
@messageRPC.impl(IGatewaySubscriber)
async def notifyKickOut(self, type, errorCode):
LOG_INFO((u'\uc11c\ubc84\uc5d0 \uc758\ud574 \uac8c\uc784\uc744 \uac15\uc81c \uc885\ub8cc:{},{}.').format(type, errorCode))
text = (u'\uc11c\ubc84\uc5d0 \uc758\ud574 \ud504\ub85c\uadf8\ub7a8\uc774 \uc885\ub8cc\ub429\ub2c8\ub2e4.\n\n\uc0ac\uc720 : {}\n\uc5d0\ub7ec \ucf54\ub4dc :{}').format(type, errorCode)
self._AccountSession__kickoutMessage = text
@messageRPC.impl(IGatewaySubscriber)
async def notifyServerClock(self, now):
Instance._setServerTime(now)
@Thing.overrides(natuum._entity.IEntitySubscriber)
def onMessage(self, serial: 'int', message: 'var'):
pass
@messageRPC.impl(IGatewaySubscriber)
async def checkAlive(self, channelID=None):
pass
@Thing.overrides(natuum._entity.IEntitySubscriber)
def onChannelStatusChanged(self, status: 'int', detailedStatus: 'str'):
asyncio.ensure_future_and_forget(self.disconnect())
@messageRPC.impl(IGatewaySubscriber)
def onConnectionQueueChanged(self, left: 'int'):
callback = self._AccountSession__weakOnConntionQueueChangedCallback()
if callback is not None:
callback(left)
@messageRPC.impl(IGatewaySubscriber)
def onAccountAuthenticatedAfterWaitingQueue(self, userID: 'str', actors: 'collections.Sequence'):
callback = self._AccountSession__weakOnAccountAuthenticatedAfterWaitingQueueCallback()
if callback is not None:
callback(userID, actors)
_AccountSession__connected = None
class EngineConfig:
def __init__(self, id: 'str', config: 'collisions.Mapping'):
self._EngineConfig__id = id
self._EngineConfig__config = config
@Property
def ID(self):
return self._EngineConfig__id
@Property
def Config(self):
return self._EngineConfig__config
def apply(self):
pass
def onReload(self, newFactory):
self._EngineConfig__config = newFactory._EngineConfig__config
self.apply()
_EngineConfig__id = None
_EngineConfig__config = None
Tried to post something but it's just the same as you
This is decompiled with something else than uncompyle6 and says : okay decompilingCode:import collections, logging, os, sys, weakref, time, datetime, platform, enum, locale, thing.accessor
import thing.asyncio as asyncio
import thing.system
from natuum.activeobjects import ApartmentActiveObject
import natuum.apartment, natuum.app, natuum.builder, natuum.renderer, natuum.jobQueue, natuum.configuration, natuum.client.action, natuum.client.pager, natuum.client.resource, natuum.client.craft, natuum.client.logic, natuum.util,
natuum.variation, natuum.indexBook, natuum.reward, natuum.growth, natuum.emotionCutscene, natuum.client.synergy, natuum.client.contract, natuum.client.relationship, natuum._entity.apartmentEvents, natuum._entity.framework.cluster
import natuum._entity.messageRPC as messageRPC
from natuum.nexon import NXLogClient
from natuum._entity.interfaces.clientSubscriber import IGatewaySubscriber
import natuum.gameService.wordFilter
if __thing_can_get_zcom_objects__:
import multiprocessing
if not __final__:
import natuum.objects
DEFAULT_CLIENT_RESOLUTION = (1920, 1080)
if not __final__:
LOG_DEBUG = natuum.getLogger('client.app').debug
LOG_WARNING = natuum.getLogger('client.app').warning
import socket
def callbackBeforeDump():
if natuum.client.app.Instance:
natuum.client.app.Instance.nxlog.stageLog(1020, 'crash')
natuum.flushLog()
def callbackAfterDump():
pass
TEXT_FORMAT_TO_PRELOAD = ((0, '2002', 24.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 12.0, 3, 0, 5, 1.0),
(0, 'KoPubDotum', 13.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 13.0, 5, 0, 5, 1.0),
(0, 'KoPubDotum', 14.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 14.0, 5, 0, 5, 1.0),
(0, 'KoPubDotum', 15.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 15.0, 5, 0, 5, 1.0),
(0, 'KoPubDotum', 17.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 17.0, 5, 0, 5, 1.0),
(0, 'KoPubDotum', 18.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 18.0, 5, 0, 5, 1.0),
(0, 'KoPubDotum', 21.0, 5, 0, 5, 1.0), (0, 'KoPubDotum', 22.0, 5, 0, 5, 1.0),
(0, 'KoPubDotum', 23.0, 5, 0, 5, 1.0), (0, 'KoPubDotum', 24.0, 3, 0, 5, 1.0),
(0, 'KoPubDotum', 26.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 27.36, 3, 0, 5, 1.0),
(0, 'KoPubDotum', 27.6, 5, 0, 5, 1.0), (0, 'KoPubDotum', 33.0, 5, 0, 5, 1.0),
(0, 'KoPubDotum', 34.0, 3, 0, 5, 1.0), (0, 'KoPubDotum', 50.0, 5, 0, 5, 1.0),
(0, 'NanumSquare', 12.0, 5, 0, 5, 1.0), (0, 'NanumSquare', 14.0, 3, 0, 5, 1.0),
(0, 'NanumSquare', 14.0, 5, 0, 5, 1.0), (0, 'NanumSquare', 15.0, 5, 0, 5, 1.0),
(0, 'NanumSquare', 16.0, 5, 2, 5, 1.0), (0, 'NanumSquare', 17.0, 5, 0, 5, 1.0),
(0, 'NanumSquare', 40.0, 5, 0, 5, 1.0))
SIDEMENU_HTMLIVEWS = ('sideMenu_page_collection', 'sideMenu_page_map', 'sideMenu_page_present',
'sideMenuAvatar_editor')
Instance = None
class ListenerType(enum.IntEnum):
ZONE = 0
PERSON = 1
class App(natuum.app.App):
_App__LONG_TIME_PLAY_WARNING_INTERVAL = 3600000
def __init__(self):
global Instance
super().__init__()
Instance = self
self.RealTimeClock = thing.Value.RealTimeClock()
self.RunOptions = self._getRunOptions('Natuum Client', '', (self._App__RunOptionHandler(),), sys.argv[1:])
now = time.clock()
self._App__clientTime = now
self._App__serverTime = now
self._App__fps = 60
self.nxlog = None
@Property
def FPS(self):
return self._App__fps
@FPS.setter
def FPS(self, fps):
self._App__fps = fps
def __sendPacket(self, blob):
pass
Resolution = thing.accessor.ri(doc='클라이언트 해상도')
Renderer = thing.accessor.ri(doc='렌더러 객체')
RPCManager = thing.accessor.ri(doc='RPC 관리자 객체')
SoundManager = thing.accessor.ri(None, doc='음향 관리자 객체')
Apartment = thing.accessor.ri(doc='클라이언트 아파트먼트')
EventLoop = thing.accessor.ri(None, doc='클라이언트 이벤트루프')
ClusterGuestNode = thing.accessor.ri(doc='클라이언트 클러스터 노드')
ClientZone = thing.accessor.rw(None)
QuitOnRendererShutdown = thing.accessor.ri(True, doc='렌더러 셧다운시 프로그램 종료')
Platform = thing.accessor.ri(doc='플랫폼 객체')
GatewayEntityID = thing.accessor.rw(None, doc='게이트웨이엔티티 ID')
ChatManagerEntityID = thing.accessor.rw(None, doc='채팅관리자엔티티 ID')
ItemDesignLibraryEntityID = thing.accessor.rw(None, doc='아이템설계관리자엔티티 ID')
DocumentContentLibraryEntityID = thing.accessor.rw(None, doc='문서내용관리자엔티티 ID')
ServerHostAddress = thing.accessor.ri(None, doc='게임 서버 주소')
LoaderThreadCount = thing.accessor.ri(1, doc='로더 스레드 갯수')
RealTimeClock = thing.accessor.ri(None, doc='RealTimeClock')
EngineConfig = thing.accessor.rw({}, doc='EngineConfig')
ServerConfig = thing.accessor.rw({}, doc='ServerConfig')
ControlModeParams = thing.accessor.ri({}, doc='ControlModeParams')
PlayerInitConfig = thing.accessor.ri({}, doc='PlayerInitConfig')
MaxFPS = thing.accessor.rw(None, doc='MaxFPS')
AccountSession = thing.accessor.ri(None, doc='AccountSession')
ClientSpace = thing.accessor.ri(None, doc='ClientSpace')
GameLogic = thing.accessor.ri(None, doc='GameLogic')
Context = thing.accessor.ri(None, doc='Context')
DummyAvatarSceneObjectData = thing.accessor.ri(None, doc='DummyAvatarSceneObjectData')
IsPreloading = thing.accessor.ri(True, doc='선로딩 여부')
UserConfig = thing.accessor.ri(None, doc='UserConfig')
InitialSplashLoadTask = thing.accessor.ri(None, doc='InitialSplashLoadTask')
_App__SERVER_EPOCH = datetime.datetime(2018, 1, 1)
def __setup(self) -> 'int, exit code':
if not __final__:
thing.system.setQuietMode(self.RunOptions.Quiet)
DebugLogCategories = '*,-action,-builder,-cutscene,' if self.RunOptions.SSOAuthToken is not None else ''
DebugLogCategories += os.environ.get('NT_DEBUG_LOG', '*') + ',' + self.RunOptions.DebugLogCategories
for category in DebugLogCategories.split(','):
if not category:
continue
if category.startswith('-'):
logger = natuum.getLogger(category[1:])
logger.setLevel(logging.INFO)
LOG_INFO('Log category [{}] level=debug was disabled'.format(logger.name))
from thing.changeset import changeset, NexonCrashReporterVersion
LOG_INFO('Changeset for Code:{}'.format(changeset))
LOG_INFO('BuildVersion:{}'.format(NexonCrashReporterVersion))
LOG_INFO('LauncherVer:{}, NGM Param:{}, NexonSN:{}, NexonSID:{}'.format(self.RunOptions.LauncherBuildVersion, self.RunOptions.LauncherOptionalParam, self.RunOptions.NexonSN, self.RunOptions.NexonSID))
self.Platform = platform = thing.Platform.Platform()
self.ETWSession_findObjects = platform.createETWSession('findObjects')
if self.RunOptions.SendCrashReport:
installed = platform.installCrashReporter(self.RunOptions.ProjectID)
if not installed:
LOG_WARNING('exception handler is not installed')
else:
platform.setCrashReporterInformation('ProcessType', self.RunOptions.ProductName)
from thing.changeset import NexonCrashReporterVersion
platform.setCrashReporterInformation('ProjectVersion', NexonCrashReporterVersion)
if natuum.m_logFilePath:
relPath = os.path.relpath(natuum.m_logFilePath, os.path.dirname(sys.executable))
platform.setCrashReporterAuxFile(relPath)
if __thing_supports_gc__:
if natuum.objects.m_refCycleFilePath:
relPath = os.path.relpath(natuum.objects.m_refCycleFilePath, os.path.dirname(sys.executable))
platform.setCrashReporterAuxFile(relPath)
platform.registerCrashReporterCallbackBeforeDump(callbackBeforeDump)
thing.system._installExceptHook(fromInit=False)
if not __final__:
self._App__initConsoleCtrlHandler()
if not __final__:
platform.listenEasyProfileGUI(28077)
self._App__thMan, self.RPCManager, self._App__chMan, self._App__chManThread = self._App__setupRPCManager()
self._App__clientLoaderApartments = self._App__createLoaderApartments()
rootFS = thing.FileSystem.FileSystem(natuum.getRootPath())
dataFS = rootFS['data']
try:
resourcesFS = rootFS['resources']
except Exception:
resourcesFS = rootFS.createDirectory('resources')
rm = natuum.client.resource.Manager(dataFS, resourcesFS)
rm.IsPreloading = self.IsPreloading
self._App__clientLoaderRegisterTokens = self._App__setupResourceManager(rm, self._App__clientLoaderApartments)
self.JobQueue = natuum.jobQueue.JobQueue()
self.EngineConfig = engineConfig = rm.load(natuum.builder.combineID('_', 'EngineConfig')).lockForReading().Data
self.ServerConfig = rm.load(natuum.builder.combineID('_', 'ServerConfig')).lockForReading().Data[self.RunOptions.ClusterID]
self.GatewayEntityID = self.ServerConfig['GatewayEntityID']
self.ChatManagerEntityID = self.ServerConfig['ChatManagerEntityID']
self.ItemDesignLibraryEntityID = self.ServerConfig['ItemDesignLibraryEntityID']
self.DocumentContentLibraryEntityID = self.ServerConfig['DocumentContentLibraryEntityID']
self.ServerHostAddress = self.RunOptions.Host
self.ControlModeParams = rm.ensureResource('controlModeParams@GameSetting')
self.PlayerInitConfig = rm.ensureResource('playerInitialization@GameSetting')
fullScreen = self.RunOptions.FullScreen
if fullScreen:
import ctypes
user32 = ctypes.windll.user32
screenWidth = user32.GetSystemMetrics(0)
screenHeight = user32.GetSystemMetrics(1)
screenRatio = screenWidth / screenHeight
if screenRatio > 1.7777777777777777:
width, height = (2400, 1080)
elif screenRatio > 1.6:
if screenWidth == 2048 and screenHeight == 1152:
width, height = screenWidth, screenHeight
else:
width, height = (1920, 1080)
elif screenRatio > 1.3333333333333333:
width, height = (1920, 1200)
else:
width, height = (1600, 1200)
else:
width, height = DEFAULT_CLIENT_RESOLUTION
self.AspectRatio = thing.Dirp.Dirp_Static(thing.system.vec(width / height, 1))
self.MaxFPS = max(int(engineConfig.Config.get('maxFPS', 60)), 1)
rendererMode = thing.Renderer.RendererManager().createRendererMode(fullScreen, width, height, 32)
self.Renderer = self._App__Renderer(rendererMode, engineConfig.Config)
self.Renderer.TerrainTessellationQueueLength = 3
self.Renderer.HangDetector = thing.ToolHelpers.HangDetector(self._App__onHangDetected)
self.PropCanvasMipHeadCutCountOnLoad = 0
userConfig = self._loadUserConfig()
if userConfig is not None:
self.UserConfig = userConfig
self._refreshCanvasLoadQuality(userConfig)
self.Renderer.UserConfig = userConfig
else:
self.UserConfig = self.Renderer.UserConfigValue['']
currentLocale = locale.getdefaultlocale()[1]
if currentLocale != 'cp949':
if self.Renderer.NativeWindowHandle:
text = 'Unsupported system locale : {}'.format(currentLocale)
text += '\n\nTo execute the client, change system locale to cp949 (Korean)'
self.showWindowsMessageBox(text)
self.nxlog = None
self.quit(0)
return
self._App__rendererModeWatchToken = self.Renderer.ModeVersion.addWatcher(self._App__onRendererModeChangedEntry, '__call__', weakref.ref(self))
self.nxlog = NXLogClient(self.RunOptions.NexonSN, self.RunOptions.NexonSID)
self.nxlog.stageLog(420, '__setup')
self._App__rendererReadyAtom = rendererReadyAtom = thing.Thread.Atom()
self._App__pagerThread = thread = self._App__thMan.createThread(thing.system.nativeThreadEntry, '__call__', self._App__pagerThreadEntry, rendererReadyAtom)
thread.Name = 'Main Updater'
thread.run()
_App__settingsDirName = 'settings'
_App__userConfigFileName = 'userConfig.nothing'
def _loadUserConfig(self):
settingsDirPath = os.path.join(natuum.getRootPath(), 'storage', __class__._App__settingsDirName)
try:
if os.path.exists(settingsDirPath):
fs = thing.FileSystem.FileSystem(settingsDirPath)
else:
raise KeyError
f = fs[__class__._App__userConfigFileName]
ra = thing.Archive.ReadingArchive_FromFile(f)
userConfig = thing.Collection.Dict()
userConfig.load(ra)
LOG_INFO('user config loaded')
except KeyError:
LOG_WARNING('cannot load user config')
userConfig = None
return userConfig
def _saveUserConfig(self, userConfig):
settingsDirPath = os.path.join(natuum.getRootPath(), 'storage', __class__._App__settingsDirName)
try:
os.makedirs(settingsDirPath)
except OSError:
pass
try:
fs = thing.FileSystem.FileSystem(settingsDirPath)
f = fs.createFile(__class__._App__userConfigFileName, True)
wa = thing.Archive.WritingArchive_FromFile(f)
userConfig.save(wa)
wa.flush()
LOG_INFO('user config saved')
except:
LOG_WARNING('cannot save user config')
def _refreshCanvasLoadQuality(self, userConfig):
propTexQuality = userConfig.get('PropTextureQuality', -1)
if propTexQuality >= 0:
propTexQuality = min(2, propTexQuality)
oldValue = self.PropCanvasMipHeadCutCountOnLoad
newValue = (2, 1, 0)[propTexQuality]
if oldValue != newValue:
self.PropCanvasMipHeadCutCountOnLoad = newValue
return True
return False
def __preload(self):
markerResourceIDs = self._App__collectMarkerResources()
effectPreloadList = tuple(markerResourceIDs.get('Effect', set()) | set(self._App__PRELOAD_EFFECTS))
rm = natuum.client.resource.Manager.Instance
rm.preload('Avatar', preloadList=('AT099800(man_default)', 'AT099900(woman_default)'))
rm.preload('Effect', preloadList=effectPreloadList)
rm.preload('CameraAngle')
rm.preload('CameraAngleBlender')
rm.preload('CameraAngleBlenderSet')
rm.preload('HTMLView', preloadList=SIDEMENU_HTMLIVEWS)
rm.preload('HTML', preloadList=('sideMenu', 'sideMenuAvatar'))
rm.preload('ItemDesign')
rm.preload('ItemIO')
rm.preload('MeshSet', preloadList=(self._App__DUMMY_AVATAR_MESHSETS))
rm.preload('PropPart', preloadList=(self._App__PRELOAD_PROPPARTS))
rm.preload('Quest')
rm.preload('VueApp', preloadList=('app', ))
rm.preload('Zone', preloadList=('ZR0000(carveKiranaArea)', ))
for typeName, resourceIDs in markerResourceIDs.items():
if typeName in ('Effect', 'ItemDesign'):
continue
else:
rm.preload(typeName, preloadList=(tuple(resourceIDs)))
rm.preload('GameSetting')
def __collectMarkerResources(self):
def collectResourceIDs(value, resourceIDs_):
if isinstance(value, str) and natuum.builder.isResourceID(value):
name, typeName, _ = natuum.builder.splitID(value)
resourceIDs_[typeName].add(name)
elif not isinstance(value, str) or isinstance(value, collections.Sequence):
for value_ in value:
collectResourceIDs(value_, resourceIDs_)
elif isinstance(value, collections.Mapping):
for value_ in value.values():
collectResourceIDs(value_, resourceIDs_)
def collectMarkerClasses(marker, markerClasses_):
markerClasses.add(marker)
for marker_ in marker.__subclasses__():
collectMarkerClasses(marker_, markerClasses_)
markerResourceIDs = collections.defaultdict(set)
markerClasses = set()
collectMarkerClasses(natuum.client.clientSpace.marker.Marker, markerClasses)
for arranger in natuum.client.clientSpace.arranger.__dict__.values():
if isinstance(arranger, type):
collectMarkerClasses(arranger, markerClasses)
for marker in markerClasses:
for attribute in marker.__dict__.values():
collectResourceIDs(attribute, markerResourceIDs)
collectMarkerClasses = None
collectResourceIDs = None
return markerResourceIDs
def __run(self) -> 'int, exit code':
if not __final__:
if not self.RunOptions.NoUsageReport:
self._App__sendUsageReport()
windowTitle = 'Peria Chronicles{}'.format(natuum.util.getBuildVersion())
rendererExitCode = self.Renderer.start(windowTitle, thing.system.ICanvas.PIXEL_FORMAT_R8G8B8A8, self._App__rendererReadyAtom, self.QuitOnRendererShutdown)
return self._App__exitCode
def __cleanup(self) -> None:
global Instance
if not __final__:
for i in range(5):
if self._App__pagerThread.wait(1):
break
else:
(LOG_DEBUG if i <= 2 else LOG_WARNING)('Pager 스레드 종료 대기중 (%d초)', i + 1)
else:
self._App__pagerThread.wait(5)
del self._App__pagerThread
self.JobQueue.shutdown()
self._App__clientLoaderRegisterTokens.clear()
for apartment in self._App__clientLoaderApartments.values():
apartment.shutdown()
self._App__shutdownTableManagers()
self._App__cleanupResourceManager()
if self._App__chMan is not None:
self._App__chMan.shutdown()
VERIFY(self._App__chManThread.wait(5))
del self._App__chManThread
del self._App__mainApartment
self.RPCManager.shutdown()
Instance = None
@Thing.overrides(natuum.app.App)
def run(self) -> 'int, exit code':
self._App__setup()
ret = self._App__run()
self._App__cleanup()
return ret
@Thing.overrides(natuum.app.App)
def quit(self, exitCode):
LOG_INFO('client quit called')
self.Renderer.shutdown()
curPage = natuum.client.pager.CurPage
if curPage is not None:
if not natuum.client.pager.command((curPage.canQuitApp), wait=True):
return
super().quit()
self._App__exitCode = exitCode
if self.RunOptions.SendCrashReport:
self.Platform.setCrashReporterClientExit(exitCode)
if natuum.client.app.Instance.nxlog is not None:
natuum.client.app.Instance.nxlog.stageLog(1010, str(exitCode))
self.Renderer.quit(True)
def isInRendererThreadGroup(self, threadID):
return threadID == self.Renderer.ThreadID
def collectZcomObjects(self, showBackrefsOfIDs=False):
if not __thing_can_get_zcom_objects__:
return
createdZcomObjects = self._App__createdZcomObjects
if createdZcomObjects is None:
self._App__createdZcomObjects = thing.ToolHelpers.CreatedObjects()
else:
self._App__createdZcomObjects = None
objs = None
try:
backrefsDir = os.path.join(natuum.getLogPath(), 'backrefs')
os.makedirs(backrefsDir, exist_ok=True)
today = datetime.datetime.today()
timestamp = '%4d%02d%02d_%02d%02d%02d' % (today.year, today.month, today.day, today.hour, today.minute, today.second)
graphFilePath = os.path.join(backrefsDir, 'graph_c{}.txt'.format(timestamp))
with open(graphFilePath, 'w', encoding='utf_8-sig') as file:
natuum.objects.Graph.collectToFile(file)
objs = list(createdZcomObjects)
objectIDsFilePath = os.path.join(backrefsDir, 'objects_c{}.txt'.format(timestamp))
with open(objectIDsFilePath, 'w') as file:
natuum.objects.saveObjectIDsToFile(file, objs)
if showBackrefsOfIDs:
multiprocessing.Process(target=(natuum.objects.showBackrefsOfIDs),
args=(
backrefsDir, graphFilePath, objectIDsFilePath)).start()
thing.system._debugBreak()
objs.clear()
finally:
if objs is not None:
objs.clear()
def checkMemory(self):
if __final__ or (self._App__memoryReclaimer is None):
return
for i in range(10):
self._App__memoryReclaimer.reclaimAll()
natuum.objects.GarbageCollector().collect(-1, True)
trendsCollector = self._App__objectCountTrendsCollector
if trendsCollector is None:
self._App__objectCountTrendsCollector = trendsCollector = natuum.objects.CountTrendsCollector()
trendsCollector.collect()
trends = [(v, trendsCollector.getCount(k), k) for k, v in trendsCollector.items()]
trends.sort(reverse=True)
tracker = self._App__memoryTracker
lines = []
lines.append('**** object count trend log start ****')
for trend, count, typeName in trends:
countList = tracker.get(typeName, None)
if countList is None:
countList = list()
tracker[typeName] = countList
else:
countList.append(count)
lines.append('{:G}\t{}\t{}'.format(trend, typeName, '\t'.join([str(i) for i in countList])))
lines.append('**** object count trend log end ****')
def getRoughServerTime(self) -> 'int. milliseconds':
passed = int(time.time() - self._App__clientTime) * 1000
return (self._App__serverTime + passed) % 2147483647
def setListener(self, flag, position) -> None:
soundManager = self.SoundManager
if soundManager is not None:
configuration = self.ControlModeParams.get('sound')
if configuration is None:
if ListenerType.ZONE == flag:
soundManager.Listener = position
elif configuration['listener'] == flag:
soundManager.Listener = thing.Dirp.Dirp_FromLocal(configuration['local'], thing.math.ROT_IDENTITY, position)
def _setServerTime(self, serverNow):
self._App__clientTime = time.time()
self._App__serverTime = int(serverNow * 1000)
class __Renderer(natuum.renderer.Renderer):
@Thing.overrides(natuum.renderer.Renderer)
def onRendererCloseButton(self, renderer: 'thing.system.IRenderer', private) -> None:
Instance.quit(0)
@Thing.overrides(natuum.renderer.Renderer)
def onRendererConfigChange(self, renderer: 'thing.system.IRenderer', private) -> None:
if isinstance(private, thing.system.IMutableMapping):
userConfig = private
natuum.client.app.Instance._saveUserConfig(userConfig)
if natuum.client.app.Instance._refreshCanvasLoadQuality(userConfig):
natuum.client.pager.command(natuum.client.app.Instance.GameLogic.onCanvasLoadQualityChange)
def _registerLayerCategories(self) -> None:
self.registerLayerCategory('UI').forget()
class __RunOptionHandler(natuum.runOptions.IHandler):
def fillDefaults(self, options):
options.Host = None
options.Quiet = False
options.InfuseActor = None
options.CrashReportURL = 'http://socorro.thingsoft.com:8882/submit'
options.ProductName = 'client'
options.ProductVersion = '0.8.1'
options.NoUsageReport = False
options.NoEffect = False
options.BatchJob = None
options.Locale = None
options.ClusterID = 'default'
options.SSOAuthToken = None
options.FullScreen = False
options.DebugLogCategories = '*,-action,-builder,-cutscene' if __final__ else ''
options.ProjectID = 'peria_dev'
options.LauncherBuildVersion = -1
options.LauncherOptionalParam = ''
options.NexonSN = 0
options.NexonSID = None
options.disableNXLog = False
crashReport = os.environ.get('NT_NO_CRASH_REPORT', '0')
options.SendCrashReport = crashReport == '0'
def buildParser(self, parser: 'optparse.OptionParser', options) -> None:
parser.add_option('-a', '--address', dest='Host', help='server address')
parser.add_option('-q', '--quiet', dest='Quiet', default=False, action='store_true', help='quiet mode')
parser.add_option('-i', '--infuseActor', dest='InfuseActor', help='auto infuse actor')
parser.add_option('-b', '--batch', dest='BatchJob', help='batch job file')
parser.add_option('-l', '--locale', dest='Locale', help='locale')
parser.add_option('-C', '--cluster', dest='ClusterID', help='cluster id')
parser.add_option('--crashreport-url', dest='CrashReportURL', help='crash report url')
parser.add_option('--product-name', dest='ProductName', help='product name')
parser.add_option('--product-version', dest='ProductVersion', help='product version')
parser.add_option('--nousagereport', dest='NoUsageReport', default=False, action='store_true', help="don't send usage report")
parser.add_option('-t', '--auth', dest='SSOAuthToken', help='auth token')
parser.add_option('-f', '--fullScreen', dest='FullScreen', default=False, action='store_true', help='full screen mode')
parser.add_option('--debugLog', type='str', dest='DebugLogCategories', help='comma seperated log categories')
parser.add_option('--projectid', dest='ProjectID', help='crash reporter project id')
parser.add_option('--launcherBuildVersion', dest='LauncherBuildVersion', help='launcher build version')
parser.add_option('--launcherParam', dest='LauncherOptionalParam', default='', help='argument from NexonGameManager')
parser.add_option('--nexonsn', dest='NexonSN', default=0, help='argument from NexonGameManager')
parser.add_option('--nexonsid', dest='NexonSID', default='', help='argument from NexonGameManager')
parser.add_option('--disableNXLog', dest='disableNXLog', default=False, action='store_true', help='disable NXLOG')
def onParsed(self, parser: 'optparse.OptionParser', options, args: tuple) -> None:
if options.BatchJob is not None:
batchJobPath = os.path.join(natuum.getRootPath(), 'settings', options.BatchJob)
options.BatchJob = natuum.util.readXDF(batchJobPath)
if options.Locale is not None:
if options.Locale in ('ko', 'ja', 'zh', 'en'):
natuum.util.DefaultLocale = options.Locale
def __createLoaderApartments(self) -> '{ str: thing.system.IApartment }':
apartments = {}
for i in range(self.LoaderThreadCount):
name = 'Loader{}'.format(i)
apartmentObject = ApartmentActiveObject(self._App__thMan, self.RPCManager)
apartmentObject.start(name, -0.13333333333333333)
apartments[name] = apartmentObject.Apartment
return apartments
if not __final__:
def __initConsoleCtrlHandler(self):
def _terminateProcess(ctrlType):
thing.system._TerminateProcess()
thing.system.setConsoleCtrlHandler(_terminateProcess)
def __setupRPCManager(self):
thMan = thing.Thread.ThreadManager()
rpcManager = thing.RPC.RPCManager(thMan)
self._App__mainApartment = natuum.apartment.Apartment()
return (
thMan, rpcManager, None, None)
def __setupResourceManager(self, rm, loaderApartments) -> '{ str : thing.system.IForgettableToken }':
tokens = {}
for name, apartment in loaderApartments.items():
token = rm.CacheManager.registerDelayedLoaderApartment(name, apartment.Inner)
tokens[name] = token
return tokens
def __cleanupResourceManager(self) -> None:
natuum.client.resource.Manager.Instance.shutdown()
@staticmethod
def __onRendererModeChangedEntry(watched, weakSelf, hint, funcName) -> None:
self = weakSelf()
if self is not None:
clientSpace = self.ClientSpace
if clientSpace is not None:
natuum.client.pager.command(clientSpace.onRendererModeChanged)
def __pagerThreadEntry(self, rendererReadyAtom: 'thing.system.IAtom') -> None:
self.Context = thing.Common.Sites()
self.EventLoop = loop = natuum._entity.apartmentEvents.EventLoop()
asyncio.set_event_loop(loop)
self.Apartment = loop.Apartment
thing.system.setSwitchIntervalForCurrentThread(1)
rendererReadyAtom.wait()
inputManager = natuum.client.input.Manager()
self._App__initFontManager()
self.showInitialSplash(True)
if self.InitialSplashLoadTask:
loop.run_until_complete(self.InitialSplashLoadTask)
_ = self.Platform.disableAccessibility()
self.GUI = guiSystem = thing.Renderer.GUISystem()
self.Renderer.GUISystem = guiSystem
self.SoundManager = soundManager = self._App__createSoundManager(parameters=(self.ControlModeParams))
try:
soundManager.setVolume('music', self.UserConfig['BGMVolume'])
soundFXVol = self.UserConfig['SoundEffectVolume']
for bus in ('effect', 'voice', 'environment', 'kiranaContract'):
soundManager.setVolume(bus, soundFXVol)
except KeyError:
pass
natuum.configuration.Configuration.initialize()
self.ClusterGuestNode = node = natuum._entity.framework.cluster.setup_guest(self.ServerConfig['GatewayServers'])
if self.ServerHostAddress is not None:
node.overrideConnectingtHost(self.ServerHostAddress)
self.AccountSession = natuum.client.app.AccountSession()
self.ZoneChanged = natuum.signal.Signal(natuum.client.zone.Zone)
self._App__initClassTables()
if self.IsPreloading:
self._App__preload()
self._App__initTableManagers()
clientSpace = natuum.client.clientSpace.ClientSpace()
clientSpace.onRendererModeChanged()
clientSpace.addDesktop()
clientSpace.addDesktop()
clientSpace.addInventory()
self.ClientSpace = clientSpace
inputManager.setEventHandler(clientSpace)
self.GameLogic = gameLogic = natuum.client.logic.GameLogic(clientSpace)
gameLogic.SyncClock.defer(App._App__LONG_TIME_PLAY_WARNING_INTERVAL, App._App__warnLongTimePlay, 1).forget()
self._App__setupIDE()
self._App__buildDummyAvatar()
self._App__processorToken = thing.Thread.Processor().addTask(0, self.MaxFPS)
self._App__memoryReclaimer = memoryReclaimer = thing.Common.MemoryReclaimer()
memoryReclaimer.clearPolicies()
memoryReclaimer.appendPolicy(memoryReclaimer.MEMORY_RECLAIMER_CRITERION_AVAILABLE_PHYSICAL_MB_LESS_THAN, 100, 209715200, 5)
memoryReclaimer.appendPolicy(memoryReclaimer.MEMORY_RECLAIMER_CRITERION_AVAILABLE_PHYSICAL_MB_LESS_THAN, 500, 104857600, 10)
memoryReclaimer.appendPolicy(memoryReclaimer.MEMORY_RECLAIMER_CRITERION_AVAILABLE_PHYSICAL_MB_LESS_THAN, 1000, 1, 15)
memoryReclaimer.appendPolicy(memoryReclaimer.MEMORY_RECLAIMER_CRITERION_PAGE_FILE_USAGE_MB_GREATER_THAN, 7168, 102400, 15)
natuum.apartment.getCurrentApartment().Alarm.postTask(self._App__reclaimMemory, 0).forget()
if not __final__:
natuum.apartment.getCurrentApartment().Alarm.postTask(self._App__logMemoryReclaimer, 60000).forget()
self._App__memoryTracker = {}
loop.call_soon(natuum.client.pager.enter)
loop.run_forever()
loop.run_until_complete(node.shutdown())
loop.close()
gameLogic.shutdown()
thing.Client.EffectManager().terminate()
soundManager.shutdown()
guiSystem.shutdown()
self.Renderer.GUISystem = None
def __initClassTables(self) -> None:
rm = natuum.client.resource.Manager.Instance
natuum.client.action.ActionTableManager.ensureInstance()
rawClassTables = rm.load(natuum.gameClass.CLASS_TABLE).lockForReading().Data
natuum.gameClass.initManager(rawClassTables)
if self.IsPreloading:
natuum.gameClass.getManagerInstance().preloadClasses()
def __initTableManagers(self) -> None:
rm = natuum.client.resource.Manager.Instance
natuum.emotionCutscene.initManager(rm)
natuum.client.craft.initManager(rm)
natuum.variation.initManager(rm)
natuum.indexBook.initManager(rm)
natuum.client.contract.initManager(rm)
natuum.reward.initManager(rm)
natuum.growth.initManager(rm)
natuum.actor.initManager(rm)
natuum.client.quest.initManager(rm)
natuum.town.initManager(rm)
natuum.client.relationship.initManager(rm)
natuum.client.synergy.initManager(rm)
natuum.gameService.wordFilter.initManager(rm)
def __initFontManager(self) -> None:
fontManager = thing.Renderer.FontManager()
rm = natuum.client.resource.Manager.Instance
rm.preload('Font', preloadList=(natuum.util.FontNames))
for fontName in natuum.util.FontNames:
fontID = natuum.builder.combineID(fontName, 'Font')
fontData = rm.ensureResource(fontID)
fontManager.registerFontData(fontData[0], fontData[1])
for info in TEXT_FORMAT_TO_PRELOAD:
(fontManager.createRasterTextFormat)(self.Renderer, *info)
def __setupIDE(self) -> None:
pass
def __buildDummyAvatar(self) -> None:
rm = natuum.client.resource.Manager.Instance
meshSet = rm.ensureResource('AP7250@MeshSet')
sceneObjectData = thing.Scene.SceneObjectData_Mesh()
sceneObjectData.ShaderFlags = thing.system.IShader.SHADER_SHADOW_MAP | thing.system.IShader.SHADER_REALISTIC
for meshName, meshAndBone in meshSet.items():
sceneObjectMeshElementData = thing.Scene.SceneObjectMeshElementData()
sceneObjectMeshElementData.Mesh = meshAndBone['mesh']
sceneObjectData.Elements[meshName] = sceneObjectMeshElementData
self.Renderer.warm(sceneObjectData, 0)
self.DummyAvatarSceneObjectData = sceneObjectData
def __shutdownTableManagers(self) -> None:
natuum.client.craft.shutdownManager()
natuum.client.action.ActionTableManager.shutdown()
natuum.gameClass.shutdownManager()
def __reclaimMemory(self) -> None:
if self.IsQuitting:
return
waitDuration = self._App__memoryReclaimer.reclaim()
natuum.apartment.getCurrentApartment().Alarm.postTask(self._App__reclaimMemory, waitDuration).forget()
if not __final__:
def __logMemoryReclaimer(self) -> None:
if self.IsQuitting:
return
memoryReclaimer = self._App__memoryReclaimer
LOG_INFO('available physical memory: {} GB'.format(memoryReclaimer.AvailablePhysicalMemoryGB))
LOG_INFO('page file usage: {} GB'.format(memoryReclaimer.PageFileUsageGB))
natuum.apartment.getCurrentApartment().Alarm.postTask(self._App__logMemoryReclaimer, 60000).forget()
def __sendUsageReport(self) -> None:
def sendReport(info: 'sequence of ( str, str )') -> str:
tokenDelim = '_TDELIM_'
emptyToken = '_EMPTY'
stream = tokenDelim.join([value if value != '' else emptyToken for value in info])
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(stream.encode('utf-8'), ('wild.thingsoft.com', 8025))
LOG_INFO('Usage report sent..')
except socket.error:
pass
sock.close()
rendererManager = thing.Renderer.RendererManager()
usageInfo = (
'{}'.format(platform.node()),
'{} {}'.format(platform.system(), platform.version()),
'{}'.format(platform.processor()),
'{}'.format(self.Platform.MemorySize),
'{}'.format(self.Platform.HardDiskSize),
'{}'.format(rendererManager.DeviceDescription.split('\x00')[0].strip()),
'{}'.format(natuum.util.getBuildVersion()))
sendReport(usageInfo)
sendReport = None
@classmethod
def __createSoundManager(cls, parameters=None, liveUpdate=False) -> 'thing.system.ISoundManager':
rm = natuum.client.resource.Manager.Instance
masterBank = rm.load(cls._App__masterBankResourceID)
masterBank = masterBank.lockForReading().Data
try:
masterBank = masterBank.SoundData
except KeyError:
LOG_WARNING('{}를 읽지 못했습니다. 이벤트 경로를 읽지 못해 소리가 나오지 ' + '않습니다.'.format(cls._App__masterBankResourceID))
return
else:
masterStringBank = rm.load(cls._App__masterStringBankResourceID)
try:
masterStringBank = masterStringBank.lockForReading().Data.SoundData
except KeyError:
LOG_WARNING('{}를 읽지 못했습니다. 이벤트 경로를 읽지 못해 소리가 나오지 ' + '않습니다.'.format(cls._App__masterStringBankResourceID))
return
else:
busGroups = cls._App__getBusGroups(parameters)
return thing.Sound.SoundManager(masterBank, masterStringBank, liveUpdate, natuum.getRootPath(), busGroups)
def __closeSplashZoneViewerMarker(self):
self._App__splashZoneViewerMarker.close()
self._App__splashZoneViewerMarker = None
def showInitialSplash(self, show):
self._App__splashNoticeLayer = None
self._App__splashLayer = None
if self._App__splashZoneViewerMarker:
self.Renderer.Timer.defer(0, self._App__closeSplashZoneViewerMarker).forget()
if show:
rm = natuum.client.resource.Manager.Instance
canvas = rm.load('ui_title_booting@Canvas').lockForReading().Data
self._App__splashLayer = self.Renderer.createOverlayLayer(thing.Dirp.Dirp_Static(), thing.Dirp.Dirp_Static(self.AspectRatio), canvas)
self.Renderer.updateLayers()
@staticmethod
def __onSplashZoneLoaded(fut_) -> None:
app = natuum.client.app.Instance
canvas = app._App__splashZoneViewerMarker.getViewCanvas()
app._App__splashLayer = app.Renderer.createOverlayLayer(thing.Dirp.Dirp_Static(), thing.Dirp.Dirp_Static(app.AspectRatio), canvas)
text = natuum.client.text.Text('로딩 중입니다. 기다리는 동안 지형 편집을 연습해 보세요.', 22, 'NanumSquare', thing.system.vec(xyzw=1), thing.system.vec(a=1), 'bold', 0.0)
app._App__splashNoticeLayer = app.Renderer.createOverlayLayer(thing.Dirp.Dirp_Static(thing.system.vec(0.5, 0.8)), thing.Dirp.Dirp_Static(thing.system.vec(text.getWidth(), text.getHeight()) / text.getHeight() * 0.04), text.
getCanvas())
app.Renderer.updateLayers()
def showWindowsMessageBox(self, text, title='Peria Chronicles', uType=0, quit=False):
import ctypes
MessageBox = ctypes.windll.user32.MessageBoxW
ret = None
if natuum.client.app.Instance.Renderer.NativeWindowHandle:
ret = MessageBox(natuum.client.app.Instance.Renderer.NativeWindowHandle, text, 'Peria Chronicles', uType)
if quit:
self.GameLogic.turnOffGame()
return ret
@staticmethod
def __warnLongTimePlay(count):
app = Instance
if app is None:
return
gameLogic = app.GameLogic
if gameLogic is None:
return
getLocalString = natuum.action.ActionTableManager.getInstance().getLocalString
author = getLocalString('NoticeAuthor')
longTimePlayWarning = getLocalString('LongTimePlayWarning').format(N=count)
ageRatingInformation = getLocalString('AgeRatingInformation')
gameLogic.createMarker('message', ('(색상:빨강)(크기:크게){}(/크기)(/색상)'.format(longTimePlayWarning)), forced=True)
gameLogic.SyncClock.addWork(5000, (gameLogic.createMarker), 'message', ('(색상:빨강)(크기:크게){}(/크기)(/색상)'.format(ageRatingInformation)), forced=True).forget()
gameLogic.vueUiController.ChattingView.addChatText('notice', author, longTimePlayWarning)
gameLogic.vueUiController.ChattingView.addChatText('notice', author, ageRatingInformation)
gameLogic.SyncClock.defer(App._App__LONG_TIME_PLAY_WARNING_INTERVAL, App._App__warnLongTimePlay, count + 1).forget()
@staticmethod
def __getBusGroups(parameters) -> '{ str : var }':
if parameters is None:
return {}
try:
return parameters['sound']['bus']['groups']
except KeyError:
return {}
@staticmethod
def __onHangDetected(detected: 'thing.system.IHangDetector') -> None:
if not __final__:
import thing.debugger
if thing.debugger.checkAttached():
LOG_WARNING('hang detected')
thing.debugger.breakIfAttached()
return
try:
raise RuntimeError('hang detected')
except Exception as e:
try:
sys.excepthook(type(e), e, e.__traceback__)
finally:
e = None
del e
_App__thMan = None
_App__chMan = None
_App__clientLoaderApartments = None
_App__clientLoaderRegisterTokens = None
_App__chManThread = None
_App__pagerThread = None
_App__rendererReadyAtom = None
_App__exitCode = 0
_App__serverPort = 9999
_App__server = None
_App__memoryReclaimer = None
_App__memoryChecker = None
if not __final__:
_App__objectCountTrendsCollector = None
if __thing_can_get_zcom_objects__:
_App__createdZcomObjects = None
_App__actionTableManager = None
_App__masterBankResourceID = natuum.builder.combineID('MasterBank', 'Sound')
_App__masterStringBankResourceID = natuum.builder.combineID('MasterBankstrings', 'Sound')
_App__splashLayer = None
_App__splashNoticeLayer = None
_App__splashZoneViewerMarker = None
_App__PRELOAD_EFFECTS = ('EF7000', 'EF7000_lumi', 'EF7001', 'EF7002', 'EF7003',
'EF7004', 'EF7006', 'EF7007', 'EF7010', 'EF7012', 'EF7013',
'EF7016', 'EF7019', 'EF7020', 'EF7022', 'EF7024', 'EF7033',
'EF7050', 'EF7051', 'EF7052', 'EF7053', 'EF7054', 'EF7058',
'EF7074', 'EF7075', 'EF7080', 'EF7081', 'EF7082', 'EF7083',
'EF7084', 'EF7093', 'EF7094', 'EF7095', 'EF7096', 'EF7098',
'EF7099', 'EF8000', 'EF8001', 'EF8002', 'EF8003', 'EF8003',
'EF8004', 'EF8008', 'EF8010', 'EF8011', 'EF8012', 'EF8013',
'EF8015', 'EF8016', 'EF8017', 'EF8020', 'EF8021', 'EF8022',
'EF8030', 'EF8031', 'EF8040', 'EF8041', 'EF8050', 'EF8051',
'EF8100', 'EF8100a', 'EF8100b', 'EF8100d', 'EF8107',
'EF8130', 'EF8131', 'EF8132', 'EF8133', 'EF8134', 'EF8135',
'EF8136', 'EF8137', 'EF8139', 'EF8142', 'EF8142', 'EF8143',
'EF8144', 'EF8145', 'EF8146', 'EF8147', 'EF8148', 'EF8149',
'EF8149', 'EF8149', 'EF8150', 'EF8151', 'EF8152', 'EF8153',
'EF8154', 'EF8156', 'EF8159', 'EF8160', 'EF8162', 'EF8165',
'EF8167', 'EF8168', 'EF8170', 'EF8171', 'EF8172', 'EF8173',
'EF8174', 'EF8175', 'EF8176', 'EF8179', 'EF8182', 'EF8184',
'EF8186', 'EF8187', 'EF8188', 'EF8189', 'EF8191', 'EF8192',
'EF8194', 'EF8196', 'EF8197', 'EF8198', 'EF8199', 'EF8200',
'EF8201', 'EF8205', 'EF8207', 'EF8210', 'EF8211', 'EF8212',
'EF8213', 'EF8216', 'EF8217', 'EF8218', 'EF8219', 'EF8220',
'EF8158a', 'EF8158b', 'EF8158c', 'EF8224', 'EF8233',
'EF8234', 'EF8236', 'EF8237', 'SEF(warning_01)', 'SEF1018(world_map)',
'SEF1020(marker_open)', 'SEF1023(guardian_qattack_ready)',
'SEF1025(guardian_qattack_fire)', 'SEF1026(guardian_qattack_targeting)',
'SEF1028(actionmaker_click)', 'SEF1029(actionmaker_hover)',
'SEF1030(actionmaker_open)', 'SEF1031(actionmaker_release)',
'SEF1032(conversation_click)', 'SEF1033(conversation_hover)',
'SEF1034(f_key_to_continue_conversation)', 'SEF1035(f_key_to_start_conversation)',
'SEF1036(general_button_click)', 'SEF1037(general_button_release)',
'SEF1038(changing_tab)', 'SEF1043(list_open)', 'SEF1044(list_close)',
'SEF1045(Xbutton)', 'SEF1046(popup_window)', 'SEF1047(conversation_release)',
'SEF1048(f_key_hover)', 'SEF1049(general_button_hover)',
'SEF1050(tab_hover)', 'SEF1051(small_button_click)',
'SEF1052(small_button_hover)', 'SEF1053(small_button_release)',
'SEF1054(kirana_listup_hover)', 'SEF1055(kirana_listup_click)',
'SEF1057(kirana_listup_release)', 'SEF1058(kirana_listup_release_cancel)',
'SEF1063(f_key_click)', 'SEF1065(battle_book_use)',
'SEF1066(inventory_hover)', 'SEF1067(inventory_click)',
'SEF1068(inventory_block)', 'SEF1069(inventory_release)',
'SEF1070(tab_release)', 'SEF1071(battle_book_hover)',
'SEF1075(battle_book_appearance)', 'SEF1076(battle_book_disappearance)',
'UI_deliveryRangeSphere', 'balloon_marker', 'button_marker',
'caption_marker', 'close_marker', 'coordinates_marker',
'gauge_marker', 'label', 'list_marker', 'list_marker3',
'text_balloon_marker', 'text_edit_marker', 'tooltip_marker',
'window', 'window2')
_App__PRELOAD_PROPPARTS = ('PP6954', 'PP6366(blue)', 'PP6366(green)', 'PP6366(purple)',
'PP6366(red)', 'PP6366(white)', 'PP6366(yellow)',
'PP6851', 'PP8200', 'PP8201', 'PP8202', 'PP8203',
'PP8204', 'PP8205', 'PP8206', 'PP8207', 'PP8208',
'PP8209', 'PP8210', 'PP8211', 'PP8212', 'PP8213',
'PP8214')
_App__DUMMY_AVATAR_MESHSETS = ('AP7250', )
class AccountSession(natuum._entity.IEntitySubscriber, IGatewaySubscriber):
def __init__(self):
self._AccountSession__connected = False
self._AccountSession__gatewaySubscription = None
self._AccountSession__NGSClient = None
self._AccountSession__JYPClient = None
self._AccountSession__accountID = None
self._AccountSession__kickoutMessage = None
self._AccountSession__weakOnConntionQueueChangedCallback = None
self._AccountSession__weakOnAccountAuthenticatedAfterWaitingQueueCallback = None
self.OnDisconnected = natuum.signal.Signal(bool, str)
self.OnBeginTransferMessage = natuum.signal.Signal(bool)
self.OnPrepareTransferMessage = natuum.signal.Signal(str)
self.OnFinishTransferMessage = natuum.signal.Signal(thing.system.uuid, str, bool)
async def connect(self, callback=None, maxTryCount=-1):
if self._AccountSession__connected:
return
await natuum.client.app.Instance.ClusterGuestNode.start()
apartment = asyncio.get_event_loop().Apartment
tryCount = 0
entity = None
while maxTryCount < 0 or maxTryCount > tryCount:
try:
entity = await apartment.EntityManager.findEntity(natuum.client.app.Instance.GatewayEntityID)
break
except natuum._entity.error.RemoteEntityServerNotFound:
LOG_WARNING('RemoteEntityServer not found')
return
except natuum._entity.error.EntityNotExist:
LOG_WARNING('GatewayEntity not exist')
return
except natuum._entity.error.EntityNotRestored:
LOG_WARNING('GatewayEntity not restored. try after 5 seconds. try count : {}'.format(tryCount))
tryCount += 1
await asyncio.sleep(5)
except natuum._entity.error.SubscriptionChannelClosed:
tryCount += 1
await asyncio.sleep(5)
except asyncio.TimeoutError:
return
except asyncio.CancelledError:
return
if entity is None:
return
try:
subscription = await natuum._entity.subscribeEntity(entity, 'hello', None, self)
except natuum._entity.error.SubscribeFailed:
return
else:
self._AccountSession__gatewaySubscription = subscription
self._AccountSession__connected = True
LOG_INFO('Connection to game server established')
async def disconnect(self, forced=True):
if not self._AccountSession__connected:
return
LOG_INFO('Connection to game server closed')
self._AccountSession__connected = False
self._AccountSession__gatewaySubscription = None
self._AccountSession__NGSClient = None
self._AccountSession__JYPClient = None
await natuum.client.app.Instance.ClusterGuestNode.shutdown()
self.OnDisconnected.emit(forced, self.KickOutMessage)
self.KickOutMessage = None
def setAuthenticationWaitingCallback(self, onConntionQueueChanged, onAccountAuthenticatedAfterWaitingQueue):
self._AccountSession__weakOnConntionQueueChangedCallback = weakref.WeakMethod(onConntionQueueChanged) if onConntionQueueChanged is not None else None
self._AccountSession__weakOnAccountAuthenticatedAfterWaitingQueueCallback = weakref.WeakMethod(onAccountAuthenticatedAfterWaitingQueue) if onAccountAuthenticatedAfterWaitingQueue is not None else None
@Property
def Connected(self) -> 'bool':
return self._AccountSession__connected
@Property
def AccountID(self) -> 'str':
return self._AccountSession__accountID
@accountID.setter
def AccountID(self, accountID: 'str'):
self._AccountSession__accountID = accountID
@Property
def KickOutMessage(self) -> 'str':
return self._AccountSession__kickoutMessage
@KickOutMessage.setter
def KickOutMessage(self, msg: 'str'):
self._AccountSession__kickoutMessage = msg
@Property
def GatewaySubscription(self):
return self._AccountSession__gatewaySubscription
def setupNGS(self):
LOG_INFO('NGS:setupNGS')
if self._AccountSession__NGSClient is not None:
return
natuum.client.app.Instance.nxlog.stageLog(510, 'setupNGS')
def _sendMessage(weakSubscription, data):
subscription = weakSubscription()
if subscription is not None:
_data = data.tobytes()
subscription.MessageProxy.onNGSMessageReceived(_data, nowait=True)
def _callSendMessage(loop, weakSelf, data):
loop.call_soon_threadsafe(_sendMessage, weakSelf, data)
callableFactory = thing.Common.CallableFactory()
args = thing.Collection.Array()
args.append(asyncio.get_event_loop())
args.append(weakref.ref(self._AccountSession__gatewaySubscription))
args.append(callableFactory.PlaceHolder[1])
sendNGSMessage = callableFactory.bind(_callSendMessage, '__call__', args.iterate())
self._AccountSession__NGSClient = thing.NGSClient.NGSClient(sendNGSMessage)
if not self._AccountSession__NGSClient.isInitialized():
natuum.client.app.Instance.showWindowsMessageBox('NGS 초기화에 실패하여 프로그램을 종료합니다.')
natuum.client.app.Instance.GameLogic.turnOffGame()
natuum.client.app.Instance.nxlog.stageLog(520, 'setupNGS')
def setupJYP(self, nexonID):
LOG_INFO('NGS:setupJYP')
if self._AccountSession__JYPClient is not None:
return
self._AccountSession__JYPClient = thing.JYP.JYPClient(nexonID)
self._AccountSession__JYPClient.collectThisThread()
def onFrameRenderForJYP(self):
if self._AccountSession__JYPClient is not None:
self._AccountSession__JYPClient.onFrameRender()
def onZoneEnterForJYP(self, zoneID):
if self._AccountSession__JYPClient is not None:
self._AccountSession__JYPClient.onSetZone(zoneID)
@messageRPC.impl(IGatewaySubscriber)
def doAnything(self, *args, **kwds):
pass
@messageRPC.impl(IGatewaySubscriber)
def onBeginTransfer(self, targetZoneName: str):
self.OnBeginTransferMessage.emit(targetZoneName)
@messageRPC.impl(IGatewaySubscriber)
def onPrepareTransfer(self, zoneStyle: str):
self.OnPrepareTransferMessage.emit(zoneStyle)
@messageRPC.impl(IGatewaySubscriber)
async def onFinishTransfer(self, zoneEntityID: 'thing.system.uuid or None', reason: str, isTimeout: bool):
self.OnFinishTransferMessage.emit(zoneEntityID, reason, isTimeout)
@messageRPC.impl(IGatewaySubscriber)
def onNGSMessageReceivedFromServer(self, data):
if self._AccountSession__NGSClient is not None:
self._AccountSession__NGSClient.onReceive(data)
@messageRPC.impl(IGatewaySubscriber)
async def notifyKickOut(self, type, errorCode):
LOG_INFO('서버에 의해 게임을 강제 종료:{},{}.'.format(type, errorCode))
text = '서버에 의해 프로그램이 종료됩니다.\n\n사유 : {}\n에러 코드 :{}'.format(type, errorCode)
self._AccountSession__kickoutMessage = text
@messageRPC.impl(IGatewaySubscriber)
async def notifyServerClock(self, now):
Instance._setServerTime(now)
@Thing.overrides(natuum._entity.IEntitySubscriber)
def onMessage(self, serial: 'int', message: 'var'):
pass
@messageRPC.impl(IGatewaySubscriber)
async def checkAlive(self, channelID=None):
pass
@Thing.overrides(natuum._entity.IEntitySubscriber)
def onChannelStatusChanged(self, status: 'int', detailedStatus: 'str'):
asyncio.ensure_future_and_forget(self.disconnect())
@messageRPC.impl(IGatewaySubscriber)
def onConnectionQueueChanged(self, left: 'int'):
callback = self._AccountSession__weakOnConntionQueueChangedCallback()
if callback is not None:
callback(left)
@messageRPC.impl(IGatewaySubscriber)
def onAccountAuthenticatedAfterWaitingQueue(self, userID: 'str', actors: 'collections.Sequence'):
callback = self._AccountSession__weakOnAccountAuthenticatedAfterWaitingQueueCallback()
if callback is not None:
callback(userID, actors)
_AccountSession__connected = None
class EngineConfig:
def __init__(self, id: 'str', config: 'collisions.Mapping'):
self._EngineConfig__id = id
self._EngineConfig__config = config
@Property
def ID(self):
return self._EngineConfig__id
@Property
def Config(self):
return self._EngineConfig__config
def apply(self):
pass
def onReload(self, newFactory):
self._EngineConfig__config = newFactory._EngineConfig__config
self.apply()
_EngineConfig__id = None
_EngineConfig__config = None
# okay decompiling app.pyc
Edit n°654141621:
It is the same code as yours btw
Is not the same as mine, I mean yes its app.pyc but it did compile successfully ;)
This was missing in mineCode:def __collectMarkerResources(self):
def collectResourceIDs(value, resourceIDs_):
if isinstance(value, str) and natuum.builder.isResourceID(value):
name, typeName, _ = natuum.builder.splitID(value)
resourceIDs_[typeName].add(name)
elif not isinstance(value, str) or isinstance(value, collections.Sequence):
for value_ in value:
collectResourceIDs(value_, resourceIDs_)
elif isinstance(value, collections.Mapping):
for value_ in value.values():
collectResourceIDs(value_, resourceIDs_)
EDIT:
Thank you works <3 how did you do it?
Altho there is some kind of recursion error with the lines mine couldnt decompile lol:
RecursionError: maximum recursion depth exceeded
https://pastebin.com/KSxpb1Vw
I used decompile3, pycharm professionnal edition with a python 3.8 venv
I am actually decompiling everything i can, gonna take a while, i'll post everything later
Edit :
Found something interesting in the engineUpdater.pyc, there is a link to the engine sources svn.
I believe it is down at the moment we speak, but we might find some links working to the sources without having to decompile everything
Unfortunately the SVN will never be online, the domain thingsoft.com is owned by a sedo parking service and not by the developer anymore
If anyone wants it, there is the compiled app.py
compiled
Deleted this part of the code :
Code:if currentLocale != 'cp949':
if self.Renderer.NativeWindowHandle:
text = 'Unsupported system locale : {}'.format(currentLocale)
text += '\n\nTo execute the client, change system locale to cp949 (Korean)'
self.showWindowsMessageBox(text)
self.nxlog = None
self.quit(0)
return
Okay deleting this check works however the client will not work on non-korean language systems XD
Quote:
18:18:19.278:natuum:WARNING:Build error [CB1_QSR1034@Quest] - ValueError: '완료 가능' is not a valid MarkerTypes
18:18:19.278:natuum.builder:INFO: D:/Jenkins/workspace/NT_Live_CI/code/python/lib/enum.py:574 in _missing_() -
18:18:19.278:natuum.builder:INFO: D:/Jenkins/workspace/NT_Live_CI/code/python/lib/enum.py:545 in __new__() -
18:18:19.278:natuum.builder:INFO: D:/Jenkins/workspace/NT_Live_CI/code/python/lib/enum.py:561 in __new__() -
18:18:19.382:natuum.client.resource:INFO:Preloading data [CB1_QSR1034@Quest] failed : '완료 가능' is not a valid MarkerTypes
You happened to have the quest.py?Quote:
I am actually decompiling everything i can, gonna take a while, i'll post everything later
Hello,
There is the quest.py
You can also try using Locale Emulator to simulate a korean system (might work this way)Code:import abc, collections, enum, numbers, os, re, math, thing, natuum.util, natuum.cutscene, natuum.relationship, natuum.gameClass
from natuum.gameClass.common import CarveResultEnum
logQuest = os.environ.get('NT_QUEST_LOG', '0') != '0'
if logQuest:
if not __final__:
LOG_DEBUG = natuum.getLogger('quest').debug
LOG_INFO = natuum.getLogger('quest').info
else:
def LOG_DEBUG(*args):
return True
QUEST_STATE_KEY_STATE = 'state'
QUEST_STATE_KEY_PENDING_NEXT_STATE = 'pendingNextState'
QUEST_STATE_KEY_RESULT_STATE = 'resultState'
QUEST_STATE_KEY_INSTANCE_ID = 'instanceID'
QUEST_STATE_KEY_VARIABLES = 'variables'
QUEST_STATE_KEY_TOKEN_ID = 'tokenID'
QUEST_STATE_KEY_PATTERN_QUEST_KEY = 'patternQuestKey'
QUEST_STATE_KEY_QUEST_GIVER_UID = 'questGiverUID'
QUEST_STATE_KEY_FORWARD_REVERTIBLE = 'forwardRevertible'
QUEST_STATE_KEY_BACKWARD_REVERTIBLE = 'backwardRevertible'
QUEST_STATE_KEY_TRIGGERED_FORWARD_INDEX = 'triggeredForwardIndex'
QUEST_STATE_KEY_PLAYED_COMPLETE_CUTSCENE = 'playedCompleteCutscene'
QUEST_STATE_KEY_REWARD_APPLIED = 'rewardApplied'
QUEST_STATE_CHANGE_TIMESTAMP = 'changedTime'
QUEST_STATE_KEY_SELECTED = 'selected'
QUEST_VAR_ELAPSED_TIME = 'elapsedTime'
QUEST_VAR_FINISH_TIME = 'finishTime'
QUEST_VAR_KEY_COUNT = 'count'
QUEST_VAR_KEY_TARGET_CLASS_ID = 'targetClassID'
QUEST_VAR_KEY_TARGET_COUNT = 'targetCount'
SYSTEM_MESSAGE_ID_ITEM_TURNED_IN = 'Quest_msg_item_turned_in'
_STRING_TRIGGER_TYPE_CONVERSATION = 'conversation'
_STRING_TRIGGER_TYPE_PROXIMITY_ENTER = 'enter'
_STRING_TRIGGER_TYPE_KILL_COUNT = 'killCount'
_STRING_TRIGGER_TYPE_ITEM_COUNT = 'itemCount'
_STRING_TRIGGER_TYPE_RANDOM_KILL_COUNT = 'randomKillCount'
_STRING_TRIGGER_TYPE_RANDOM_ITEM_COUNT = 'randomItemCount'
_STRING_TRIGGER_TYPE_TIMER = 'timer'
_STRING_TRIGGER_TYPE_SELECTION = 'selection'
_STRING_TRIGGER_TYPE_GUARDIAN_CHANGED = 'guardianChanged'
_STRING_TRIGGER_TYPE_PROPERTY = 'property'
_STRING_TRIGGER_TYPE_GUARDIAN_PROPERTY = 'guardianProperty'
_STRING_TRIGGER_TYPE_CONTRACT = 'contract'
_STRING_TRIGGER_TYPE_HAND = 'hand'
_STRING_TRIGGER_TYPE_DECK = 'deck'
_STRING_TRIGGER_TYPE_ACTION = 'action'
_STRING_TRIGGER_TYPE_RELATIONSHIP = 'relationship'
_STRING_TRIGGER_TYPE_DINE = 'dine'
TutorialQuestsForStats = {v:i + 2000 for i, v in enumerate(('QDtutorial', 'QDtutorial2',
'QDtutorial3', 'QDtutorial3_1',
'QDtutorial4', 'QDtutorial4_1',
'QDtutorial4_2', 'QDtutorial5',
'QDtutorial5_1', 'QDtutorial6',
'QDtutorial7'))}
MainQuestsForStats = {v:i + 3000 for i, v in enumerate(('CB1_QMR1000', 'CB1_QMR1001',
'CB1_QMR1002', 'CB1_QMR1004',
'CB1_QMR1003', 'CB1_QMR1005',
'CB1_QMR1006', 'CB1_QMR1007',
'CB1_QMR1008', 'CB1_QMR1009',
'CB1_QMR1042', 'CB1_QMR1010',
'CB1_QMR1011', 'CB1_QMR1066',
'CB1_QMR1067', 'CB1_QMR1068',
'CB1_QMR1012', 'CB1_QMR1013',
'CB1_QMR1014', 'CB1_QMR1015',
'CB1_QMR1017', 'CB1_QMR1016',
'CB1_QMR1018', 'CB1_QMR1019',
'CB1_QMR1020', 'CB1_QMR1021',
'CB1_QMR1022', 'CB1_QMR1023',
'CB1_QMR1043', 'CB1_QMR1024',
'CB1_QMR1025', 'CB1_QMR1060',
'CB1_QMR1061', 'CB1_QMR1062',
'CB1_QMR1026', 'CB1_QMR1027',
'CB1_QMR1028', 'CB1_QMR1029',
'CB1_QMR1031', 'CB1_QMR1030',
'CB1_QMR1032', 'CB1_QMR1033',
'CB1_QMR1034', 'CB1_QMR1035',
'CB1_QMR1036', 'CB1_QMR1037',
'CB1_QMR1044', 'CB1_QMR1038',
'CB1_QMR1039', 'CB1_QMR1063',
'CB1_QMR1064', 'CB1_QMR1065',
'CB1_QMR1040', 'CB1_QMR1041',
'CB1_QS1121', 'CB1_QS1122',
'CB1_QS1123', 'CB1_QS1204',
'CB1_QS1207', 'CB1_QS1208',
'CB1_QS1213', 'CB1_QS1137',
'CB1_QS1138', 'CB1_QS1139',
'CB1_QS1205', 'CB1_QS1209',
'CB1_QS1210', 'CB1_QS1215',
'CB1_QS1140', 'CB1_QS1141',
'CB1_QS1142', 'CB1_QS1206',
'CB1_QS1211', 'CB1_QS1212',
'CB1_QS1217'))}
MainQuestsForStats.update(TutorialQuestsForStats)
class ReportTargetType(enum.IntEnum):
All = 0
QuestGiver = 1
@staticmethod
def convert(value: 'str or None') -> 'ReportTargetType':
if value is None or (value == natuum.quest.ReportTargetType.All.name):
return natuum.quest.ReportTargetType.All
return natuum.quest.ReportTargetType.QuestGiver
class QuestType(enum.IntEnum):
MainQuest = 0
SubQuest = 1
DailyQuest = 2
GroupQuest = 3
@staticmethod
def convert(questType: 'str or None') -> 'QuestType':
if questType is None or (questType == natuum.quest.QuestType.MainQuest.name):
return natuum.quest.QuestType.MainQuest
if questType == natuum.quest.QuestType.SubQuest.name:
return natuum.quest.QuestType.SubQuest
if questType == natuum.quest.QuestType.DailyQuest.name:
return natuum.quest.QuestType.DailyQuest
return natuum.quest.QuestType.GroupQuest
class CountTypes(enum.Enum):
LessThan = 'lessThan'
GreaterThanOrEqualTo = 'greaterThanOrEqualTo'
@staticmethod
def convert(countType: 'None or str') -> 'CountTypes':
if countType is None:
return CountTypes.GreaterThanOrEqualTo
return CountTypes(countType)
class ComparisonTypes(enum.Enum):
EQUAL = '=='
NOT_EQUAL = '!='
GREATER_THAN = '>'
GREATER_THAN_OR_EQUAL_TO = '>='
LESS_THAN = '<'
LESS_THAN_OR_EQUAL_TO = '<='
CONTAINS = 'contains'
IN = 'in'
RANGE = 'range'
@classmethod
def compare(cls, compType: 'ComparisonTypes', value: 'var', target: 'var'):
if cls.CONTAINS == compType:
return target in value
if cls.IN == compType:
return value in target
if cls.RANGE == compType:
return target[0] <= value and target[1] > value
if cls.EQUAL == compType:
return value == target
if cls.NOT_EQUAL == compType:
return value != target
if cls.GREATER_THAN == compType:
return value > target
if cls.GREATER_THAN_OR_EQUAL_TO == compType:
return value >= target
if cls.LESS_THAN == compType:
return value < target
if cls.LESS_THAN_OR_EQUAL_TO == compType:
return value <= target
raise ValueError('invalid comparison type : {}'.format(compType))
class QuantifierTypes(enum.Enum):
FOR_ALL = 'forAll'
THERE_EXSITS = 'thereExists'
class ActionRoleTypes(enum.Enum):
SUBJECT = 'subject'
OBJECT = 'object'
INDIRECT_OBJECT = 'indirectObject'
class MarkerTypes(enum.Enum):
EMPHASIS = '강조 환상'
MOVE = '이동'
STARTABLE = '시작 가능'
UNSTARTABLE = '시작 불가능'
ACTIVE = '진행 중'
COMPLETE = '완료 가능'
REQUEST_SELECTION = '선택 요청 환상'
@staticmethod
def isQuestStateMarker(markerType: 'MarkerTypes'):
return MarkerTypes.STARTABLE == markerType or MarkerTypes.UNSTARTABLE == markerType or MarkerTypes.ACTIVE == markerType or MarkerTypes.COMPLETE == markerType
class UIStates(enum.Enum):
DEFAULT = 'Default'
ACTIVE_COMPLETED = 'ActiveCompleted'
ACTIVE_FAILED = 'ActiveFailed'
INACTIVE_FINISHED = 'InactiveFinished'
INACTIVE_NOT_STARTED = 'InactiveNotStarted'
@classmethod
def isActiveQuestState(cls, uiStates: 'UIStates') -> 'bool':
return cls.DEFAULT == uiStates or cls.ACTIVE_COMPLETED == uiStates or cls.ACTIVE_FAILED == uiStates
@classmethod
def isDetailedActiveQuestState(cls, uiStates: 'UIStates') -> 'bool':
return cls.ACTIVE_COMPLETED == uiStates or cls.ACTIVE_FAILED == uiStates
@classmethod
def iterateActiveStates(cls) -> 'generator of UIStates':
for state in cls:
if cls.isActiveQuestState(state):
yield state
@classmethod
def convertToUIStateMap(cls, stateMap: 'collections.Mapping', finished: 'collections.Sequence') -> 'collections.Mapping':
uiStateMap = dict()
for questID, subMap in stateMap.items():
uiState = cls.getUIState(subMap)
if uiState is None:
continue
else:
uiStateMap[questID] = uiState
for questID in finished:
uiStateMap[questID] = cls.INACTIVE_FINISHED
return uiStateMap
@classmethod
def toDescString(cls, value: 'UIStates') -> 'str':
if cls.DEFAULT == value:
return '기본'
if cls.ACTIVE_COMPLETED == value:
return '진행(조건 만족 상태)'
if cls.ACTIVE_FAILED == value:
return '진행(실패 상태)'
if cls.INACTIVE_FINISHED == value:
return '완료'
if cls.INACTIVE_NOT_STARTED == value:
return '시작 전'
return ''
@classmethod
def getUIState(cls, subMap: 'collections.Mapping') -> 'None or UIStates':
try:
state = subMap[QUEST_STATE_KEY_STATE]
except KeyError:
return
else:
uiState = cls._translate(QuestStates(state))
return uiState
@classmethod
def _translate(cls, state: 'QuestStates') -> 'None or UIStates':
if QuestStates.COMPLETED == state or (QuestStates.FINISHED_CUTSCENE == state or QuestStates.FINISHED == state):
return cls.ACTIVE_COMPLETED
if QuestStates.ACCEPTED == state or (QuestStates.WATCH == state):
return cls.DEFAULT
if QuestStates.FAILED == state:
return cls.ACTIVE_FAILED
class MarkerStates(enum.Enum):
ALL = 'All'
ACCEPTED = 'Accepted'
COMPLETED = 'Completed'
FAILED = 'Failed'
@classmethod
def _translate(cls, state: 'MarkerStates') -> 'None or UIStates':
if cls.ACCEPTED == state:
return UIStates.DEFAULT
if cls.COMPLETED == state:
return UIStates.ACTIVE_COMPLETED
if cls.FAILED == state:
return UIStates.ACTIVE_FAILED
class UIStateBoundInfo:
def __init__(self, rawMap: 'collections.Mapping'):
self._UIStateBoundInfo__infoMap = dict()
for key, value in rawMap.items():
self._UIStateBoundInfo__infoMap[UIStates(key)] = value
def getValue(self, uiState: 'UIStates') -> 'None or var':
if uiState in self._UIStateBoundInfo__infoMap:
return self._UIStateBoundInfo__infoMap[uiState]
return self._UIStateBoundInfo__infoMap.get(UIStates.DEFAULT)
def ensureType(self, expectedType: 'var'):
for value in self._UIStateBoundInfo__infoMap.values():
if not isinstance(value, expectedType):
msg = 'invalid value : {}, expected type : {}'
raise ValueError(msg.format(value, expectedType))
def ensureStates(self, checkActive: 'bool'=False, isNotDetailed: 'bool'=False):
if checkActive:
for state in self._UIStateBoundInfo__infoMap.keys():
if not UIStates.isActiveQuestState(state):
msg = 'invalid state : {}, expected active ui state'
raise ValueError(msg.format(state))
if isNotDetailed:
for state in self._UIStateBoundInfo__infoMap.keys():
if UIStates.isDetailedActiveQuestState(state):
msg = 'invalid state : {}, unexpected ui state'
raise ValueError(msg.format(state))
_UIStateBoundInfo__infoMap = None
class UIInfo:
StateInfo = collections.namedtuple('StateInfo', ('Title', 'Description', 'SimpleDescription'))
MarkerInfo = collections.namedtuple('MarkerInfo', ('Target', 'MarkerType', 'Tooltip',
'QuestType', 'Repeatable'))
_UIInfo__FINISHED_TRIGGER_FORMAT = '<del>{}</del>'
def __init__(self, questType: 'natuum.quest.QuestType', repeatable: 'bool', title: 'str', rawMarkerInfos: 'None or collections.Mapping', rawGuideTarget: 'None or collections.Mapping', rewardDesc: 'str', rawUIDesc: 'None or col
lections.Mapping', rawUIDescPreq: 'None or collections.Mapping', rawUIDescTrigger: 'None or collections.Mapping'):
self._UIInfo__title = title
if rawMarkerInfos:
stateMap = dict()
markerMap = dict()
for state, value in rawMarkerInfos.items():
markerState = MarkerStates(state)
stateMap[markerState] = self.StateInfo(title, '', value['questMarkerText'])
infoList = list()
for marker in value['markers']:
infoList.append(self.MarkerInfo(marker['markerTarget'], natuum.quest.MarkerTypes(marker['markerType']), marker.get('tooltip'), questType, repeatable))
markerMap[markerState] = infoList
self._UIInfo__stringInfo = self._UIInfo__translateInfoMap(stateMap)
self._UIInfo__markerInfo = self._UIInfo__translateInfoMap(markerMap)
if rawGuideTarget:
guideTarget = dict()
for key, subMap in rawGuideTarget.items():
guideTarget[key] = AuxInfo.GuideTarget(subMap)
self._UIInfo__guideTarget = UIStateBoundInfo(guideTarget)
self._UIInfo__guideTarget.ensureType(AuxInfo.GuideTarget)
self._UIInfo__rewardDesc = rewardDesc
if rawUIDesc:
self._UIInfo__uiDesc = UIStateBoundInfo(rawUIDesc)
self._UIInfo__uiDesc.ensureType((str, type(None)))
self._UIInfo__uiDesc.ensureStates(isNotDetailed=True)
if rawUIDescPreq:
self._UIInfo__uiDescPreq = UIStateBoundInfo(rawUIDescPreq)
self._UIInfo__uiDescPreq.ensureType((str, type(None)))
self._UIInfo__uiDescPreq.ensureStates(isNotDetailed=True)
if rawUIDescTrigger:
self._UIInfo__uiDescTrigger = UIStateBoundInfo(rawUIDescTrigger)
self._UIInfo__uiDescTrigger.ensureType((str, type(None)))
@Property
def Title(self) -> 'str':
return self._UIInfo__title
@Property
def RewardDesc(self) -> 'str':
return self._UIInfo__rewardDesc
@Property
def HasGuideTarget(self) -> 'bool':
return self._UIInfo__guideTarget is not None
def getInfo(self, uiState: 'UIStates') -> 'None or .StateInfo':
if self._UIInfo__stringInfo is not None:
return self._UIInfo__stringInfo.getValue(uiState)
def getMarkerInfo(self, uiState: 'UIStates') -> 'None or sequence of .MarkerInfo':
if self._UIInfo__markerInfo is not None:
return self._UIInfo__markerInfo.getValue(uiState)
def getGuideTarget(self, uiState: 'UIStates') -> 'None or AuxInfo.GuideTarget':
if self._UIInfo__guideTarget is not None:
return self._UIInfo__guideTarget.getValue(uiState)
def getDesc(self, uiState: 'UIStates') -> 'None or str':
if self._UIInfo__uiDesc is not None:
return self._UIInfo__uiDesc.getValue(uiState)
def getPrerequisiteDesc(self, uiState: 'UIStates') -> 'None or str':
if self._UIInfo__uiDescPreq is not None:
return self._UIInfo__uiDescPreq.getValue(uiState)
def getRawTriggerDesc(self, uiState: 'UIStates') -> 'None or str':
if self._UIInfo__uiDescTrigger is not None:
return self._UIInfo__uiDescTrigger.getValue(uiState)
def getTriggerDesc(self, uiState: 'UIStates', descTriggerAux: 'None or str') -> 'None or str':
raw = self.getRawTriggerDesc(uiState)
if UIStates.DEFAULT == uiState:
textList = list()
if raw:
textList.append(raw)
if descTriggerAux:
textList.append(descTriggerAux)
text = '\n'.join(textList)
elif UIStates.ACTIVE_COMPLETED == uiState:
textList = list()
defaultText = self.getRawTriggerDesc(UIStates.DEFAULT)
if defaultText:
textList.append(self._UIInfo__FINISHED_TRIGGER_FORMAT.format(defaultText))
if descTriggerAux:
textList.append(self._UIInfo__FINISHED_TRIGGER_FORMAT.format(descTriggerAux))
if raw:
if defaultText != raw:
textList.append(raw)
text = '\n'.join(textList)
else:
text = raw
if text:
return text
@staticmethod
def __translateInfoMap(infoMap: 'collections.Mapping') -> 'UIStateBoundInfo':
translated = dict()
for markerState, info in infoMap.items():
key = MarkerStates._translate(markerState)
if key is not None:
translated[key] = info
defaultInfo = infoMap.get(MarkerStates.ALL)
if defaultInfo is None:
defaultInfo = infoMap.get(MarkerStates.ACCEPTED)
if defaultInfo is not None:
translated[UIStates.DEFAULT] = defaultInfo
return UIStateBoundInfo(translated)
_UIInfo__title = None
_UIInfo__stringInfo = None
_UIInfo__markerInfo = None
_UIInfo__guideTarget = None
_UIInfo__rewardDesc = None
_UIInfo__uiDesc = None
_UIInfo__uiDescPreq = None
_UIInfo__uiDescTrigger = None
class AuxInfo:
_AuxInfo__AUX_KEY_GUIDED_QUEST_ID = 'guidedQuestID'
class GuideTarget:
class Types(enum.Enum):
PERSON_NAME = 'PersonName'
PERSON_UID = 'PersonUID'
QUEST_ROLE = 'QuestRole'
ITEM_NAME = 'ItemName'
SIGNPOST = 'Signpost'
Target = thing.accessor.ri(None, doc='str')
TargetType = thing.accessor.ri(None, doc='.Types')
TargetName = thing.accessor.ri(None, doc='str')
ZoneName = thing.accessor.ri(None, doc='str')
def __init__(self, subMap: 'collections.Mapping'):
target = subMap['target']
if not isinstance(target, str):
raise ValueError('invalid guide target : {}'.format(target))
self.Target = target
self.TargetType = self.Types(subMap['targetType'])
self.TargetName = subMap.get('targetName')
self.ZoneName = subMap.get('zoneName')
@classmethod
def getGuidedQuestID(cls, auxInfo: 'collections.Mapping') -> 'None or str':
guidedQuestID = auxInfo.get(cls._AuxInfo__AUX_KEY_GUIDED_QUEST_ID)
return guidedQuestID
@classmethod
def setGuidedQuestID(cls, auxInfo: 'collections.Mapping', questID: 'None or str') -> 'collections.Mapping':
if questID is None:
auxInfo.pop(cls._AuxInfo__AUX_KEY_GUIDED_QUEST_ID, None)
else:
auxInfo[cls._AuxInfo__AUX_KEY_GUIDED_QUEST_ID] = questID
return auxInfo
class QuestStates(enum.IntEnum):
INIT = 0
SELECT = 1
ACCEPTED = 2
REJECTED = 3
WATCH = 4
COMPLETED = 5
FAILED = 6
FINISHED_CUTSCENE = 7
CANCELED_CUTSCENE = 8
CANCELED = 9
FINISHED = 10
@staticmethod
def isActive(state: 'QuestStates', isRejectable: 'bool') -> 'bool':
if isRejectable:
return state >= QuestStates.WATCH
return True
@classmethod
def toString(cls, value: 'QuestStates') -> 'str':
if cls.INIT == value:
return '시작'
if cls.SELECT == value:
return '선택'
if cls.ACCEPTED == value:
return '수락'
if cls.REJECTED == value:
return '거절'
if cls.WATCH == value:
return '감시'
if cls.COMPLETED == value:
return '조건 만족'
if cls.FAILED == value:
return '실패'
if cls.FINISHED_CUTSCENE == value:
return '완료 컷신'
if cls.CANCELED_CUTSCENE == value:
return '취소 컷신'
if cls.CANCELED == value:
return '취소'
if cls.FINISHED == value:
return '완료'
return '알 수 없는 상태'
@classmethod
def convertToStringMap(cls, stateMap: 'collections.Mapping', finished: 'collections.Sequence') -> 'collections.Mapping':
textMap = dict()
for questID, subMap in stateMap.items():
state = subMap.get(QUEST_STATE_KEY_STATE)
if state is not None:
textMap[questID] = cls.toString(state)
for questID in finished:
textMap[questID] = cls.toString(cls.FINISHED)
return textMap
@classmethod
def isCompletable(cls, subMap: 'collections.Mapping') -> 'bool':
return cls.COMPLETED == subMap.get(QUEST_STATE_KEY_STATE)
class ResultStates(enum.IntEnum):
SUCCEEDED = 0
FAILED = 1
FAILED_INVALID_ITEM_INFO = 2
FAILED_ENTER_LIMIT = 3
FAILED_CARVE_TARGET_NOT_FOUND = 4
FAILED_CARVE_INVALID_KIRANA = 5
FAILED_CARVE_INVALID_ITEM = 6
FAILED_CARVE_ALREADY_CARVED = 7
FAILED_CARVE_RESERVED_KIRANA = 8
@classmethod
def convertFromCarveResult(cls, value: 'int') -> 'ResultStates':
if CarveResultEnum.SUCCEEDED == value:
return cls.SUCCEEDED
if CarveResultEnum.FAILED_ALREADY_CARVED == value:
return cls.FAILED_CARVE_ALREADY_CARVED
if CarveResultEnum.FAILED_INVALID_ITEM == value:
return cls.FAILED_CARVE_INVALID_ITEM
if CarveResultEnum.FAILED_INVALID_KIRANA == value:
return cls.FAILED_CARVE_INVALID_KIRANA
if CarveResultEnum.FAILED_RESERVED_KIRANA == value:
return cls.FAILED_CARVE_RESERVED_KIRANA
return cls.FAILED_ENTER_LIMIT
@classmethod
def convertToFailedMessageID(cls, value: 'int') -> 'None or str':
if cls.FAILED == value:
return 'Quest_msg_result_failed_default'
if cls.FAILED_INVALID_ITEM_INFO == value:
return 'Quest_msg_result_failed_invalid_item_info'
if cls.FAILED_ENTER_LIMIT == value:
return 'Quest_msg_result_failed_enter_limit'
if cls.FAILED_CARVE_TARGET_NOT_FOUND == value:
return 'Quest_msg_result_failed_carve_target_not_found'
if cls.FAILED_CARVE_INVALID_KIRANA == value:
return 'Quest_msg_result_failed_carve_invalid_kirana'
if cls.FAILED_CARVE_INVALID_ITEM == value:
return 'Quest_msg_result_failed_carve_invalid_item'
if cls.FAILED_CARVE_ALREADY_CARVED == value:
return 'Quest_msg_result_failed_carve_already_carved'
if cls.FAILED_CARVE_RESERVED_KIRANA == value:
return 'Quest_msg_result_failed_carve_reserved_kirana'
class TriggerTypes(enum.IntEnum):
CONVERSATION = 0
PROXIMITY_ENTER = 1
ITEM_COUNT = 2
KILL_COUNT = 3
RANDOM_ITEM_COUNT = 4
RANDOM_KILL_COUNT = 5
ACTION = 6
SELECTION = 7
TIMER = 8
GUARDIAN_CHANGED = 9
PROPERTY = 10
GUARDIAN_PROPERTY = 11
SELECT_STATE = 12
CONTRACT = 13
DECK = 14
HAND = 15
RELATIONSHIP = 16
DINE = 17
@staticmethod
def isRevertible(triggerType: 'TriggerTypes') -> 'bool':
return TriggerTypes.RANDOM_ITEM_COUNT == triggerType or TriggerTypes.PROPERTY == triggerType or TriggerTypes.GUARDIAN_PROPERTY == triggerType or TriggerTypes.ITEM_COUNT == triggerType or TriggerTypes.DECK == triggerType or
TriggerTypes.HAND == triggerType
@staticmethod
def isInstant(triggerType: 'TriggerTypes') -> 'bool':
return TriggerTypes.SELECTION == triggerType or TriggerTypes.SELECT_STATE == triggerType
@staticmethod
def _convert(value: 'str'):
if value == _STRING_TRIGGER_TYPE_CONVERSATION:
return TriggerTypes.CONVERSATION
if value == _STRING_TRIGGER_TYPE_PROXIMITY_ENTER:
return TriggerTypes.PROXIMITY_ENTER
if value == _STRING_TRIGGER_TYPE_ITEM_COUNT:
return TriggerTypes.ITEM_COUNT
if value == _STRING_TRIGGER_TYPE_KILL_COUNT:
return TriggerTypes.KILL_COUNT
if value == _STRING_TRIGGER_TYPE_RANDOM_ITEM_COUNT:
return TriggerTypes.RANDOM_ITEM_COUNT
if value == _STRING_TRIGGER_TYPE_RANDOM_KILL_COUNT:
return TriggerTypes.RANDOM_KILL_COUNT
if value == _STRING_TRIGGER_TYPE_TIMER:
return TriggerTypes.TIMER
if value == _STRING_TRIGGER_TYPE_SELECTION:
return TriggerTypes.SELECTION
if value == _STRING_TRIGGER_TYPE_GUARDIAN_CHANGED:
return TriggerTypes.GUARDIAN_CHANGED
if value == _STRING_TRIGGER_TYPE_PROPERTY:
return TriggerTypes.PROPERTY
if value == _STRING_TRIGGER_TYPE_GUARDIAN_PROPERTY:
return TriggerTypes.GUARDIAN_PROPERTY
if value == _STRING_TRIGGER_TYPE_CONTRACT:
return TriggerTypes.CONTRACT
if value == _STRING_TRIGGER_TYPE_DECK:
return TriggerTypes.DECK
if value == _STRING_TRIGGER_TYPE_HAND:
return TriggerTypes.HAND
if value == _STRING_TRIGGER_TYPE_ACTION:
return TriggerTypes.ACTION
if value == _STRING_TRIGGER_TYPE_RELATIONSHIP:
return TriggerTypes.RELATIONSHIP
if value == _STRING_TRIGGER_TYPE_DINE:
return TriggerTypes.DINE
raise ValueError('invalid trigger type : {}'.format(value))
class TriggerUsageTypes(enum.IntEnum):
START = 0
UPDATE = 1
class DescDelims(enum.Enum):
DungeonDesc = '<D>'
EpicKiranaClassIDs = '<E>'
TownDesc = '<T>'
class DungeonDescDelims(enum.Enum):
KiranaClassIDs = '<DA>'
DungeonRecordKey = '<DB>'
class ConversationTriggerInfo:
QuestID = thing.accessor.ri(None, doc='str')
UsageType = thing.accessor.ri(None, doc='TriggerUsageTypes')
ListenerQuestRole = thing.accessor.ri(None, doc='str')
SelectionText = thing.accessor.ri(None, doc='str')
ConfirmText = thing.accessor.ri(None, doc='None or str')
def __init__(self, listenerQuestRole: 'str', selectionText: 'str', confirmText: 'None or str', questID: 'str', usageType: 'TriggerUsageTypes'):
self.QuestID = questID
self.ListenerQuestRole = listenerQuestRole
self.SelectionText = selectionText
self.ConfirmText = confirmText
self.UsageType = usageType
class ContractInfo:
TargetClassID = thing.accessor.ri(None, doc='thing.system.uuid')
def __init__(self, targetClassID: 'str'):
self.TargetClassID = natuum.util.convertToUUID(targetClassID)
class DeckInfo:
ClassIDCounts = thing.accessor.ri(None, doc='list')
def __init__(self, classIDs: 'collections.Sequence'):
countMap = dict()
for classIDStr in classIDs:
classID = natuum.util.convertToUUID(classIDStr)
if classID not in countMap:
countMap[classID] = 0
else:
countMap[classID] += 1
self.ClassIDCounts = list()
for classID, count in countMap.items():
self.ClassIDCounts.append((classID, count))
class HandInfo:
class HandCondition:
Quantifier = thing.accessor.ri(None, doc='None or QuantifierTypes')
ClassIDs = thing.accessor.ri(None, doc='None or collections.Sequence')
PropertyCondition = thing.accessor.ri(None, doc='None or PropertyCondition')
def initialize(self, raw: 'None or collections.Mapping') -> 'bool':
if raw is not None:
self.Quantifier = QuantifierTypes(raw['quantifier'])
classIDs = raw.get('classIDs')
if classIDs:
self.ClassIDs = list()
for classID in classIDs:
self.ClassIDs.append(natuum.util.convertToUUID(classID))
property = raw.get('property')
if property:
self.PropertyCondition = PropertyCondition(property)
return self.Quantifier and (self.ClassIDs or self.PropertyCondition)
AverageLevel = thing.accessor.ri(None, doc='float')
Count = thing.accessor.ri(None, doc='int')
Condition = thing.accessor.ri(None, doc='None or HandInfo.HandCondition')
def __init__(self, raw: 'collections.Mapping'):
self.AverageLevel = raw.get('minAverageLevel', 0.0)
self.Count = int(raw['minCount'])
condition = self.HandCondition()
if condition.initialize(raw.get('condition')):
self.Condition = condition
class ActionInfo:
ActionID = thing.accessor.ri(None, doc='str')
HostRole = thing.accessor.ri(None, doc='str')
def __init__(self, actionID: 'str', hostRole: 'str'):
if not isinstance(actionID, str):
raise ValueError('invalid actionID : {}'.format(actionID))
self.ActionID = actionID
self.HostRole = ActionRoleTypes(hostRole)
class CountTriggerInfo:
TargetClassID = thing.accessor.ri(None, doc='thing.system.uuid')
TargetCount = thing.accessor.ri(None, doc='int')
Type = thing.accessor.ri(None, doc='CountTypes')
PropertyCondition = thing.accessor.ri(None, doc='None or PropertyCondition')
TargetQuestRole = thing.accessor.ri(None, doc='None or str')
UIName = thing.accessor.ri(None, doc='None or str')
def __init__(self, targetClassID: 'str', targetCount: 'int', countType: 'str'=None, propertyCondition: 'PropertyCondition'=None, questRole: 'str'=None, uiName: 'str'=None):
self.TargetClassID = natuum.util.convertToUUID(targetClassID)
try:
natuum.gameClass.getClassByID(self.TargetClassID)
except Exception:
raise ValueError('unknown classID : {}'.format(targetClassID))
self.TargetCount = targetCount
self.Type = CountTypes.convert(countType)
self.PropertyCondition = propertyCondition
self.TargetQuestRole = questRole
self.UIName = uiName
class RandomCountTriggerInfo:
TargetClassIDCandidates = thing.accessor.ri(None, doc='list')
TargetCountRangeMax = thing.accessor.ri(None, doc='int')
TargetCountRangeMin = thing.accessor.ri(None, doc='int')
Type = thing.accessor.ri(None, doc='CountTypes')
def __init__(self, candidates: 'collections.Sequence', maxCount: 'int', minCount: 'int', countType: 'str'=None):
if not candidates:
raise ValueError('random trigger should have at least one candidate')
if maxCount < minCount or (maxCount < 1 or minCount < 1):
msg = 'invalid range : [ {}, {} ]'
raise ValueError(msg.format(minCount, maxCount))
self.TargetClassIDCandidates = list()
for value in candidates:
classID = natuum.util.convertToUUID(value)
try:
natuum.gameClass.getClassByID(classID)
except Exception:
raise ValueError('unknown classID : {}'.format(value))
self.TargetClassIDCandidates.append(classID)
self.TargetCountRangeMax = maxCount
self.TargetCountRangeMin = minCount
self.Type = CountTypes.convert(countType)
class EnterTriggerInfo:
class TargetTypes(enum.IntEnum):
QuestRole = 0
Name = 1
Signpost = 2
@staticmethod
def convert(value: 'str') -> 'EnterTriggerInfo.TargetTypes':
if value == EnterTriggerInfo.TargetTypes.QuestRole.name:
return EnterTriggerInfo.TargetTypes.QuestRole
if value == EnterTriggerInfo.TargetTypes.Name.name:
return EnterTriggerInfo.TargetTypes.Name
if value == EnterTriggerInfo.TargetTypes.Signpost.name:
return EnterTriggerInfo.TargetTypes.Signpost
msg = 'invalid enter trigger target type : {}'
raise ValueError(msg.format(value))
Target = thing.accessor.ri(None, doc='str')
Range = thing.accessor.ri(None, doc='float')
Type = thing.accessor.ri(None, doc='EnterTriggerInfo.TargetTypes')
def __init__(self, target: 'str', targetType: 'str', range: 'float'):
self.Target = target
self.Type = EnterTriggerInfo.TargetTypes.convert(targetType)
self.Range = range
class AcceptTriggerInfo:
def __init__(self, acceptText: 'None or str', rejectText: 'None or str'):
self._AcceptTriggerInfo__selectionMap = dict()
self._AcceptTriggerInfo__keys = list()
if acceptText is not None:
self._AcceptTriggerInfo__selectionMap[acceptText] = QuestStates.ACCEPTED
self._AcceptTriggerInfo__keys.append(acceptText)
if rejectText is not None:
self._AcceptTriggerInfo__selectionMap[rejectText] = QuestStates.REJECTED
self._AcceptTriggerInfo__keys.append(rejectText)
self._AcceptTriggerInfo__isRejectable = True
else:
self._AcceptTriggerInfo__isRejectable = False
@Property
def Keys(self) -> 'collections.Sequence':
return self._AcceptTriggerInfo__keys
@Property
def IsRejectable(self) -> 'bool':
return self._AcceptTriggerInfo__isRejectable
def getValue(self, key: 'str') -> 'None or str':
return self._AcceptTriggerInfo__selectionMap.get(key)
_AcceptTriggerInfo__selectionMap = None
_AcceptTriggerInfo__keys = None
_AcceptTriggerInfo__isRejectable = None
class SelectionTriggerInfo:
Results = collections.namedtuple('Results', ('NextQuestIDs', ))
def __init__(self, selectionMap: 'collecctions.Mapping'):
self._SelectionTriggerInfo__selectionMap = collections.OrderedDict()
self._SelectionTriggerInfo__keys = list()
for selectionText, nextQuestIDs in selectionMap.items():
if nextQuestIDs:
if isinstance(nextQuestIDs, collections.Sequence):
self._SelectionTriggerInfo__selectionMap[selectionText] = self.Results(nextQuestIDs)
self._SelectionTriggerInfo__keys.append(selectionText)
@Property
def Keys(self) -> 'collections.Sequence':
return self._SelectionTriggerInfo__keys
def getResults(self, key: 'str') -> 'None or .Results':
return self._SelectionTriggerInfo__selectionMap.get(key)
_SelectionTriggerInfo__selectionMap = None
_SelectionTriggerInfo__keys = None
class GuardianChangedTriggerInfo:
FromClassID = thing.accessor.ri(None, doc='None or thing.system.uuid')
ToClassID = thing.accessor.ri(None, doc='None or thing.system.uuid')
UsageType = thing.accessor.ri(None, doc='TriggerUsageTypes')
def __init__(self, fromClassID: 'None or thing.system.uuid', classID: 'None or thing.system.uuid', usageType: 'TriggerUsageTypes'):
if fromClassID is not None:
self.FromClassID = natuum.util.convertToUUID(fromClassID)
else:
self.FromClassID = None
if classID is not None:
self.ToClassID = natuum.util.convertToUUID(classID)
else:
self.ToClassID = None
self.UsageType = usageType
class PropertyTriggerInfo:
Condition = thing.accessor.ri(None, doc='PropertyCondition')
def __init__(self, condition: 'collections.Mapping'):
self.Condition = PropertyCondition(condition)
class TriggerInfo:
def __init__(self, triggerType: 'TriggerTypes', triggerName: 'str', auxArg: 'var'):
self._TriggerInfo__triggerType = triggerType
self._TriggerInfo__triggerName = triggerName
self._TriggerInfo__auxArg = auxArg
@Property
def TriggerType(self) -> 'TriggerTypes':
return self._TriggerInfo__triggerType
@Property
def TriggerName(self) -> 'str':
return self._TriggerInfo__triggerName
@Property
def AuxArg(self) -> 'var':
return self._TriggerInfo__auxArg
_TriggerInfo__triggerType = None
_TriggerInfo__triggerName = None
_TriggerInfo__auxArg = None
class TriggerInfoList:
def __init__(self, infoList: 'collections.Sequence', isInstant: 'bool'):
if not infoList:
raise ValueError('invalid info list : {}'.format(infoList))
for subList in infoList:
if not subList:
raise ValueError('invalid sub info list : {}'.format(subList))
self._TriggerInfoList__infoList = infoList
self._TriggerInfoList__isInstant = isInstant
@Property
def IsInstant(self):
return self._TriggerInfoList__isInstant
def iterate(self) -> 'generator of list of TriggerInfo':
for subList in self._TriggerInfoList__infoList:
yield subList
def getSubListByIndex(self, index: 'int') -> 'None or list of TriggerInfo':
try:
return self._TriggerInfoList__infoList[index]
except IndexError:
return
def iterateWithIndex(self) -> 'generator of ( int, list of TriggerInfo )':
for index, subList in enumerate(self._TriggerInfoList__infoList):
yield (
index, subList)
_TriggerInfoList__infoList = None
_TriggerInfoList__isInstant = None
class InverseTriggerInfoList(TriggerInfoList):
ModifierTargets = thing.accessor.ri(None, doc='None or dict')
def __init__(self, infoMap):
super().__init__(list(infoMap.values()), False)
self.ModifierTargets = self._InverseTriggerInfoList__getModifierTargets()
self._InverseTriggerInfoList__infoMap = infoMap
def getSubListByIndex(self, index: 'int') -> 'None or list of TriggerInfo':
return self._InverseTriggerInfoList__infoMap.get(index)
def iterateWithIndex(self) -> 'generator of ( int, list of TriggerInfo )':
for index, subList in self._InverseTriggerInfoList__infoMap.items():
yield (
index, subList)
@staticmethod
def create(triggerList: 'TriggerInfoList') -> 'None or InverseTriggerInfoList':
revertible = dict()
for index, subList in triggerList.iterateWithIndex():
subRevertible = list()
for info in subList:
if TriggerTypes.isRevertible(info.TriggerType):
subRevertible.append(info)
if subRevertible:
revertible[index] = subRevertible
if revertible:
return InverseTriggerInfoList(revertible)
def __getModifierTargets(self) -> 'None or collections.Mapping':
targetMap = dict()
for subList in self.iterate():
for info in subList:
if not TriggerTypes.isRevertible(info.TriggerType):
continue
else:
if info.TriggerType not in targetMap:
targetMap[info.TriggerType] = set()
subSet = targetMap[info.TriggerType]
if TriggerTypes.ITEM_COUNT == info.TriggerType:
subSet.add((info.AuxArg.TargetClassID,
info.AuxArg.PropertyCondition))
else:
if TriggerTypes.RANDOM_ITEM_COUNT == info.TriggerType:
subSet.add(info)
else:
if TriggerTypes.PROPERTY == info.TriggerType:
subSet.add(info.AuxArg.Condition.PropertyName)
else:
if TriggerTypes.GUARDIAN_PROPERTY == info.TriggerType:
subSet.add(info.AuxArg.Condition.PropertyName)
else:
if TriggerTypes.DECK == info.TriggerType:
continue
if TriggerTypes.HAND == info.TriggerType:
continue
if targetMap:
return targetMap
_InverseTriggerInfoList__infoMap = None
class CutsceneInfo:
ScriptData = thing.accessor.ri(None, doc='natuum.cutscene.ScriptData')
RoleNameMap = thing.accessor.ri(None, doc='dict')
CaptionPositionMap = thing.accessor.ri(None, doc='dict')
IsStaged = thing.accessor.ri(None, doc='bool')
StageFlag = thing.accessor.ri(None, doc='None or int')
def __init__(self, scriptData: 'natuum.cutscene.ScriptData', roleNameMap: 'collections.Mapping', captionPositionMap: 'collections.Mapping', isStaged: 'int or bool'):
self.ScriptData = scriptData
self.RoleNameMap = roleNameMap
self.CaptionPositionMap = captionPositionMap
self.IsStaged = bool(isStaged)
self.StageFlag = natuum.cutscene.QuestStagedFlag if isStaged else None
class RelationshipTarget(natuum.relationship.RelationshipTarget):
STR_QUEST_GIVER = 'QUEST_GIVER'
@Property
def TargetQuestGiver(self):
return self.Target == self.STR_QUEST_GIVER
class RelationshipCondition:
RelationshipTarget = thing.accessor.ri(None, doc='RelationshipTarget')
Value = thing.accessor.ri(None, doc='Value')
CountType = thing.accessor.ri(None, doc='CountTypes')
def __init__(self, info: 'collections.Mapping'):
self.RelationshipTarget = RelationshipTarget(info['target'])
self.CountType = CountTypes.convert(info['countType'])
value = info['value']
if not isinstance(value, int):
msg = 'invalid relationship value : {}'.format(value)
raise ValueError(msg)
self.Value = value
def check(self, level: 'int') -> 'bool':
if CountTypes.LessThan == self.CountType:
return level < self.Value
return level >= self.Value
class RelationshipConditionList:
class IGetter(metaclass=abc.ABCMeta):
@abc.abstractmethod
def getRelationship(self, target: 'str', group: 'natuum.relationship.RelationshipGroup') -> 'int':
pass
def __init__(self, value: 'collections.Sequence'):
self._RelationshipConditionList__giverRelationship = list()
self._RelationshipConditionList__targetedRelationship = list()
for info in value:
cond = RelationshipCondition(info)
if cond.RelationshipTarget.TargetQuestGiver:
self._RelationshipConditionList__giverRelationship.append(cond)
else:
self._RelationshipConditionList__targetedRelationship.append(cond)
def checkGiver(self, getter: '.IGetter', giverUID: 'str') -> 'bool':
for cond in self._RelationshipConditionList__giverRelationship:
relTarget = cond.RelationshipTarget
level = getter.getRelationship(giverUID, relTarget.Group)
if not cond.check(level):
return False
return True
def checkTargeted(self, getter: '.IGetter') -> 'bool':
for cond in self._RelationshipConditionList__targetedRelationship:
relTarget = cond.RelationshipTarget
level = getter.getRelationship(relTarget.Target, relTarget.Group)
if not cond.check(level):
return False
return True
_RelationshipConditionList__giverRelationship = None
_RelationshipConditionList__targetedRelationship = None
class DineEventTriggerInfo:
FoodClassID = thing.accessor.ri(None, doc='thing.system.uuid')
def __init__(self, info: 'collections.Mapping'):
classID = natuum.util.convertToUUID(info['foodClassID'])
try:
natuum.gameClass.getClassByID(classID)
except Exception:
raise ValueError('invalid classID : {}'.format(classID))
if not natuum.gameClass.isSameOrDescendant(classID, '{CL1096(Dish)}'):
raise ValueError('{} is not a descendant of CL1096'.format(classID))
self.FoodClassID = classID
class Result:
class Base:
pass
class Action(Base):
def __init__(self, actionID: 'str', roleMap: 'None or collections.Mapping'):
self._Action__actionID = actionID
self._Action__roleMap = roleMap
@Property
def ActionID(self) -> 'str':
return self._Action__actionID
@Property
def RoleMap(self) -> 'None or collections.Mapping':
return self._Action__roleMap
_Action__actionID = None
_Action__roleMap = None
class ItemBase(Base):
def __init__(self, itemClassID: 'str', itemCount: 'int'):
if not isinstance(itemClassID, str):
msg = 'invalid item classID : {}'.format(itemClassID)
raise ValueError(msg)
self._ItemBase__itemClassID = itemClassID
if not isinstance(itemCount, int):
msg = 'invalid item count : {}'.format(itemCount)
raise ValueError(msg)
self._ItemBase__itemCount = itemCount
@Property
def ItemClassID(self) -> 'str':
return self._ItemBase__itemClassID
@Property
def ItemCount(self) -> 'int':
return self._ItemBase__itemCount
_ItemBase__itemClassID = None
_ItemBase__itemCount = None
class CreateItem(ItemBase):
def __init__(self, bind, *args):
(super().__init__)(*args)
self._CreateItem__bind = bind
@Property
def Bind(self) -> 'bool':
return self._CreateItem__bind
_CreateItem__bind = None
class DeleteItem(ItemBase):
pass
class Teleport(Base):
NamedLocation = collections.namedtuple('NamedLocation', ('Name', 'RelativePosition'))
def __init__(self, target: 'str', position: 'collections.Mapping or thing.system.vec'):
self._Teleport__target = target
if isinstance(position, thing.system.vec):
self._Teleport__position = position
elif isinstance(position, collections.Mapping):
name = position.get('beaconName')
relativePos = position.get('relativePosition')
if isinstance(relativePos, (type(None), thing.system.vec)):
if isinstance(name, str):
self._Teleport__position = self.NamedLocation(name, relativePos)
if self._Teleport__position is None:
raise ValueError('invalid position : {}'.format(position))
@Property
def Target(self) -> 'str':
return self._Teleport__target
@Property
def Position(self) -> 'thing.system.vec or .NamedLocation':
return self._Teleport__position
_Teleport__target = None
_Teleport__position = None
class KiranaCarve(Base):
class Types(enum.IntEnum):
ClassID = 0
QuestRole = 1
ActorTable = 2
@classmethod
def convert(cls, value: 'str') -> 'Result.KiranaCarve.Types':
if value == cls.ClassID.name:
return cls.ClassID
if value == cls.QuestRole.name:
return cls.QuestRole
if value == cls.ActorTable.name:
return cls.ActorTable
msg = 'invalid kirana carve target type : {}'
raise ValueError(msg.format(value))
def __init__(self, targetType: 'str', targetPerson: 'str', targetItemClassID: 'str', questRolesToBeRemoved: 'None or collections.Sequence', targetItemBans: 'None or collections.Sequence', addToHand: 'bool'):
if not isinstance(targetPerson, str):
msg = 'invalid kirana carve target : {}'
raise ValueError(msg.format(targetPerson))
self._KiranaCarve__targetType = Result.KiranaCarve.Types.convert(targetType)
if Result.KiranaCarve.Types.ClassID == self._KiranaCarve__targetType:
try:
natuum.gameClass.getClassByID(targetPerson)
except Exception:
raise ValueError('invalid classID : {}'.format(targetPerson))
self._KiranaCarve__targetPerson = targetPerson
self._KiranaCarve__targetItemClassID = targetItemClassID
self._KiranaCarve__questRolesToBeRemoved = questRolesToBeRemoved
self._KiranaCarve__targetItemBans = targetItemBans
self._KiranaCarve__addToHand = addToHand
@Property
def TargetType(self) -> 'Result.KiranaCarve.Types':
return self._KiranaCarve__targetType
@Property
def TargetPerson(self) -> 'str':
return self._KiranaCarve__targetPerson
@Property
def TargetItemClassID(self) -> 'str':
return self._KiranaCarve__targetItemClassID
@Property
def QuestRolesToBeRemoved(self) -> 'None or collections.Sequence':
return self._KiranaCarve__questRolesToBeRemoved
@Property
def TargetItemBans(self) -> 'None or collections.Sequence':
return self._KiranaCarve__targetItemBans
@Property
def AddToHand(self) -> 'bool':
return self._KiranaCarve__addToHand
_KiranaCarve__targetType = None
_KiranaCarve__targetPerson = None
_KiranaCarve__targetItemClassID = None
_KiranaCarve__questRolesToBeRemoved = None
_KiranaCarve__targetItemBans = None
_KiranaCarve__addToHand = None
class Experience(Base):
def __init__(self, value: 'int'):
self._Experience__value = value
@Property
def Value(self) -> 'int':
return self._Experience__value
_Experience__value = None
class Relationship(Base):
def __init__(self, target: 'collections.Mapping', value: 'int'):
self._Relationship__target = RelationshipTarget(target)
self._Relationship__value = value
@Property
def RelationshipTarget(self) -> 'RelationshipTarget':
return self._Relationship__target
@Property
def Value(self) -> 'int':
return self._Relationship__value
_Relationship__target = None
_Relationship__value = None
class Town(Base):
def __init__(self, townID: 'str'):
self._Town__townID = townID
@Property
def TownID(self) -> 'str':
return self._Town__townID
_Town__townID = None
class ResultList:
PropertyNames = thing.accessor.ri(None, doc='list')
Entry = collections.namedtuple('Entry', ('PropertyConditions', 'Results'))
def __init__(self, raw: 'collections.Sequence'):
self.PropertyNames = list()
self._ResultList__resultList = list()
propertyNameSet = set()
for subMap in raw:
propertyConditions = subMap.get('propertyConditions')
if propertyConditions is not None:
propertyConditions = PropertyConditionList(propertyConditions)
propertyNameSet.update(propertyConditions.PropertyNames)
else:
resultMap = dict()
for resultType, rawResults in subMap['results'].items():
if resultType == 'town':
resultMap[Result.Town] = {
Result.Town(rawResults)}
else:
if resultType == 'experience':
resultMap[Result.Experience] = {
Result.Experience(rawResults)}
else:
for sub in rawResults:
if resultType == 'action':
resultClass = Result.Action
result = resultClass(sub['actionID'], sub.get('roleMap'))
elif resultType == 'createItem':
resultClass = Result.CreateItem
result = resultClass(sub.get('bind', False), sub['classID'], sub['count'])
elif resultType == 'deleteItem':
resultClass = Result.DeleteItem
result = resultClass(sub['classID'], sub['count'])
elif resultType == 'teleport':
resultClass = Result.Teleport
result = resultClass(sub['target'], sub['position'])
elif resultType == 'relationship':
resultClass = Result.Relationship
result = resultClass(sub['target'], sub['value'])
elif resultType == 'kiranaCarve':
resultClass = Result.KiranaCarve
result = resultClass(sub['targetType'], sub['targetPerson'], sub['targetItemClassID'], sub.get('questRolesToBeRemoved'), sub.get('targetItemBans'), sub.get('addToHand', False))
else:
raise ValueError('invalid result type : {}'.format(resultType))
if resultClass not in resultMap:
resultMap[resultClass] = set()
else:
resultMap[resultClass].add(result)
self._ResultList__resultList.append(self.Entry(propertyConditions, resultMap))
self.PropertyNames.extend(propertyNameSet)
def filterResults(self, propertyMap: 'None or collections.Mapping') -> 'collections.Mapping':
filtered = dict()
for entry in self._ResultList__resultList:
conditions = entry.PropertyConditions
if conditions is not None:
if propertyMap:
if not conditions.check(propertyMap):
continue
for resultClass, subList in entry.Results.items():
if resultClass not in filtered:
filtered[resultClass] = list()
else:
filtered[resultClass].extend(subList)
return filtered
_ResultList__resultList = None
class PatternQuestStates(enum.IntEnum):
ACTIVE = 0
FINISHED = 2
CANCELED = 1
@staticmethod
def serialize(questID: 'str', state: 'PatternQuestStates', timeStamp: 'int') -> '( str, int, str )':
return (
questID, int(state), str(timeStamp))
@staticmethod
def deserialize(value: 'collections.Sequence') -> '( str, PatternQuestStates, int )':
questID, stateInt, timeStampStr = value
return (
questID, PatternQuestStates(stateInt), int(timeStampStr))
@staticmethod
def canStartPatternQuest(serialized: 'None or collections.Sequence', questID: 'None or str', current: 'int') -> 'bool or str':
if not isinstance(serialized, collections.Sequence):
return True
prevQuestID, prevState, prevTimeStamp = PatternQuestStates.deserialize(serialized)
if PatternQuestStates.ACTIVE == prevState:
return False
if PatternQuestStates.FINISHED == prevState:
return prevTimeStamp < current
if PatternQuestStates.CANCELED == prevState:
if prevTimeStamp < current:
return True
if questID is None:
return prevQuestID
return questID == prevQuestID
msg = 'invalid pattern quest state : {}'.format(prevState)
raise NotImplementedError(msg)
@staticmethod
def hasPatternQuest(stateMap: 'collections.Mapping', questID: 'str', state: 'PatternQuestStates') -> 'bool':
for serialized in stateMap.values():
otherQuestID, otherState, *_ = PatternQuestStates.deserialize(serialized)
if otherQuestID == questID:
if otherState == state:
return True
return False
class PatternQuestInfo:
ResetDelay = thing.accessor.ri(None, doc='int')
def __init__(self, resetDelay: 'int'):
self.ResetDelay = resetDelay
class PropertyCondition(natuum.cutscene.IPropertyCondition):
def __init__(self, value: 'collections.Mapping'):
self._PropertyCondition__propertyName = value['propertyName']
self._PropertyCondition__comp = ComparisonTypes(value['comparisonMethod'])
self._PropertyCondition__targetValueRaw = value['targetValue']
try:
self._PropertyCondition__targetValueUUID = natuum.util.convertToUUID(self._PropertyCondition__targetValueRaw)
except ValueError:
self._PropertyCondition__targetValueUUID = None
@Property
@Thing.overrides(natuum.cutscene.IPropertyCondition)
def PropertyName(self) -> 'str':
return self._PropertyCondition__propertyName
@Thing.overrides(natuum.cutscene.IPropertyCondition)
def check(self, value: 'var') -> 'bool':
if self._PropertyCondition__targetValueUUID is not None:
if self._PropertyCondition__check(value, self._PropertyCondition__targetValueUUID):
return True
return self._PropertyCondition__check(value, self._PropertyCondition__targetValueRaw)
def propertyConditionToString(self) -> 'str':
return self.PropertyName + str(self._PropertyCondition__comp) + str(self._PropertyCondition__targetValueRaw)
def __check(self, value: 'var', targetValue: 'var') -> 'bool':
try:
return ComparisonTypes.compare(self._PropertyCondition__comp, value, targetValue)
except Exception:
return False
_PropertyCondition__propertyName = None
_PropertyCondition__comp = None
_PropertyCondition__targetValueRaw = None
_PropertyCondition__targetValueUUID = None
class PropertyConditionList:
PropertyNames = thing.accessor.ri(None, doc='list')
def __init__(self, value: 'collections.Sequence'):
self._PropertyConditionList__conditionList = list()
self.PropertyNames = list()
for subMap in value:
condition = PropertyCondition(subMap)
self._PropertyConditionList__conditionList.append(condition)
self.PropertyNames.append(condition.PropertyName)
def check(self, propertyMap: 'collections.Mapping') -> 'bool':
if not propertyMap:
return False
for key, value in propertyMap.items():
if value == natuum.util.UUID_PROPERTY_NOT_FOUND:
return False
for condition in self._PropertyConditionList__conditionList:
if condition.PropertyName not in propertyMap:
return False
if not condition.check(propertyMap[condition.PropertyName]):
return False
return True
def _checkProperty(self, name: 'str', value: 'var') -> 'bool':
for condition in self._PropertyConditionList__conditionList:
if condition.PropertyName == name:
if not condition.check(value):
return False
return True
_PropertyConditionList__conditionList = None
class PrerequisiteInfo:
def __init__(self, questID: 'str', raw: 'None or collections.Mapping'):
self._PrerequisiteInfo__questID = questID
if raw is None:
self._PrerequisiteInfo__activeQuestIDs = None
self._PrerequisiteInfo__inactiveQuestIDs = None
self._PrerequisiteInfo__finishedQuestIDs = None
self._PrerequisiteInfo__notInFinishedQuestIDs = None
self._PrerequisiteInfo__notInDeck = None
self._PrerequisiteInfo__gInfo = None
self._PrerequisiteInfo__rInfo = None
self._PrerequisiteInfo__guardianInfo = None
self._PrerequisiteInfo__kiranaInfoMap = None
self._PrerequisiteInfo__kiranaInfoNameMap = None
self._PrerequisiteInfo__relationship = None
self._PrerequisiteInfo__rPropNames = None
self._PrerequisiteInfo__townID = None
self._PrerequisiteInfo__unregTownIDs = None
else:
value = raw.get('activeQuestIDs')
self._PrerequisiteInfo__activeQuestIDs = set(value) if value else None
value = raw.get('inactiveQuestIDs')
self._PrerequisiteInfo__inactiveQuestIDs = set(value) if value else None
value = raw.get('finishedQuestIDs')
self._PrerequisiteInfo__finishedQuestIDs = set(value) if value else None
value = raw.get('notInFinishedQuestIDs')
self._PrerequisiteInfo__notInFinishedQuestIDs = set(value) if value else None
value = raw.get('notInDeck')
if isinstance(value, collections.Sequence):
self._PrerequisiteInfo__notInDeck = set()
for classIDStr in value:
self._PrerequisiteInfo__notInDeck.add(natuum.util.convertToUUID(classIDStr))
else:
self._PrerequisiteInfo__notInDeck = None
value = raw.get('giverProperties')
if value:
self._PrerequisiteInfo__gInfo = PropertyConditionList(value)
rPropNames = set()
value = raw.get('receiverProperties')
if value:
self._PrerequisiteInfo__rInfo = PropertyConditionList(value)
rPropNames.update(self._PrerequisiteInfo__rInfo.PropertyNames)
value = raw.get('guardianProperties')
if value:
self._PrerequisiteInfo__guardianInfo = PropertyConditionList(value)
value = raw.get('kiranaProperties')
if value:
self._PrerequisiteInfo__kiranaInfoMap = dict()
self._PrerequisiteInfo__kiranaInfoNameMap = dict()
for rawClassID, condition in value.items():
classID = natuum.util.convertToUUID(rawClassID)
conditionList = PropertyConditionList(condition)
self._PrerequisiteInfo__kiranaInfoMap[classID] = conditionList
self._PrerequisiteInfo__kiranaInfoNameMap[classID] = conditionList.PropertyNames
value = raw.get('relationship')
if value:
self._PrerequisiteInfo__relationship = RelationshipConditionList(value)
value = raw.get('registeredTown')
if value:
self._PrerequisiteInfo__townID = value
rPropNames.add(natuum.gameClass.PROP_REGISTERED_TOWN)
value = raw.get('unregisteredTowns')
if value:
self._PrerequisiteInfo__unregTownIDs = value
rPropNames.add(natuum.gameClass.PROP_REGISTERED_TOWN)
self._PrerequisiteInfo__rPropNames = list(rPropNames) if rPropNames else None
@Property
def GiverPropertyNames(self) -> 'None or collections.Sequence':
if self._PrerequisiteInfo__gInfo is None:
return
return self._PrerequisiteInfo__gInfo.PropertyNames
@Property
def ReceiverPropertyNames(self) -> 'None or collections.Sequence':
return self._PrerequisiteInfo__rPropNames
@Property
def GuardianPropertyNames(self) -> 'None or collections.Sequence':
if self._PrerequisiteInfo__guardianInfo is None:
return
return self._PrerequisiteInfo__guardianInfo.PropertyNames
@Property
def KiranaPropertyNameMap(self) -> 'None or collections.Mapping':
if self._PrerequisiteInfo__kiranaInfoNameMap is None:
return
return self._PrerequisiteInfo__kiranaInfoNameMap
@Property
def Relationship(self) -> 'None or RelationshipConditionList':
return self._PrerequisiteInfo__relationship
def canStart(self, questStates: 'collections.Mapping', finished: 'collections.Sequence', contracted: 'collections.Sequence') -> 'bool':
if self._PrerequisiteInfo__questID in questStates or (self._PrerequisiteInfo__questID in finished):
return False
if self._PrerequisiteInfo__activeQuestIDs is not None:
if not self._PrerequisiteInfo__activeQuestIDs.issubset(questStates):
return False
if self._PrerequisiteInfo__inactiveQuestIDs is not None:
if self._PrerequisiteInfo__inactiveQuestIDs.intersection(questStates):
return False
if self._PrerequisiteInfo__finishedQuestIDs is not None:
if not self._PrerequisiteInfo__finishedQuestIDs.issubset(finished):
return False
if self._PrerequisiteInfo__notInFinishedQuestIDs is not None:
if self._PrerequisiteInfo__notInFinishedQuestIDs.intersection(finished):
return False
if self._PrerequisiteInfo__notInDeck is not None:
if self._PrerequisiteInfo__notInDeck.intersection(contracted):
return False
return True
def checkGiverProperties(self, propMap: 'None or collections.Mapping') -> 'bool':
if self._PrerequisiteInfo__gInfo is None:
return True
return propMap and self._PrerequisiteInfo__gInfo.check(propMap)
def checkReceiverProperties(self, propMap: 'None or collections.Mapping') -> 'bool':
if self._PrerequisiteInfo__townID or self._PrerequisiteInfo__unregTownIDs:
townID = propMap.get(natuum.gameClass.PROP_REGISTERED_TOWN)
if not self._PrerequisiteInfo__checkTownID(townID):
return False
if self._PrerequisiteInfo__rInfo is None:
return True
return propMap and self._PrerequisiteInfo__rInfo.check(propMap)
def checkGuardianProperties(self, propMap: 'None or collections.Mapping') -> 'bool':
if self._PrerequisiteInfo__guardianInfo is None:
return True
return propMap and self._PrerequisiteInfo__guardianInfo.check(propMap)
def checkKiranaProperties(self, classID: 'thing.system.uuid', propMap: 'None or collections.Mapping') -> 'bool':
info = self._PrerequisiteInfo__kiranaInfoMap.get(classID)
if info is None:
return True
return propMap and info.check(propMap)
def checkGiverRelationship(self, getter: '.IGetter', giverUID: 'str') -> 'bool':
if not self._PrerequisiteInfo__relationship:
return True
return self._PrerequisiteInfo__relationship.checkGiver(getter, giverUID)
def checkTargetedRelationship(self, getter: '.IGetter') -> 'bool':
if not self._PrerequisiteInfo__relationship:
return True
return self._PrerequisiteInfo__relationship.checkTargeted(getter)
def checkReceiverTownID(self, townID: 'str') -> 'bool':
if not self._PrerequisiteInfo__checkTownID(townID):
return False
if not self._PrerequisiteInfo__rInfo:
return True
return self._PrerequisiteInfo__rInfo._checkProperty(natuum.gameClass.PROP_REGISTERED_TOWN, townID)
def __checkTownID(self, townID: 'str') -> 'bool':
if self._PrerequisiteInfo__townID:
if self._PrerequisiteInfo__townID != townID:
return False
if self._PrerequisiteInfo__unregTownIDs:
if townID in self._PrerequisiteInfo__unregTownIDs:
return False
return True
_PrerequisiteInfo__questID = None
_PrerequisiteInfo__activeQuestIDs = None
_PrerequisiteInfo__inactiveQuestIDs = None
_PrerequisiteInfo__finishedQuestIDs = None
_PrerequisiteInfo__notInFinishedQuestIDs = None
_PrerequisiteInfo__notInDeck = None
_PrerequisiteInfo__gInfo = None
_PrerequisiteInfo__rInfo = None
_PrerequisiteInfo__guardianInfo = None
_PrerequisiteInfo__kiranaInfoMap = None
_PrerequisiteInfo__kiranaInfoNameMap = None
_PrerequisiteInfo__relationship = None
_PrerequisiteInfo__rPropNames = None
_PrerequisiteInfo__townID = None
_PrerequisiteInfo__unregTownIDs = None
class QuestData:
QuestID = thing.accessor.ri(None, doc='str')
QuestName = thing.accessor.ri(None, doc='str')
QuestType = thing.accessor.ri(None, doc='QuestType')
UIInfo = thing.accessor.ri(None, doc='UIInfo')
GuideKeyword = thing.accessor.ri(None, doc='None or str')
Prerequisites = thing.accessor.ri(None, doc='PrerequisiteInfo')
ReportTargetType = thing.accessor.ri(None, doc='ReportTargetType')
ManuallyCancellable = thing.accessor.ri(None, doc='bool')
Repeatable = thing.accessor.ri(None, doc='bool')
StartableByTownUI = thing.accessor.ri(None, doc='bool')
PatternQuestSettings = thing.accessor.ri(None, doc='PatternQuestInfo')
Rewards = thing.accessor.ri(None, doc='None or ResultList')
AcceptResults = thing.accessor.ri(None, doc='None or ResultList')
ForwardResults = thing.accessor.ri(None, doc='None or ResultList')
EndResults = thing.accessor.ri(None, doc='None or ResultList')
CancelResults = thing.accessor.ri(None, doc='None or ResultList')
StartTriggers = thing.accessor.ri(None, doc='set')
TokenItem = thing.accessor.ri(None, doc='str')
NextQuestIDs = thing.accessor.ri(None, doc='list')
TurnInItems = thing.accessor.ri(None, doc='bool')
ReceiverNotReadyCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
InitialCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
AcceptCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
RejectCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
CompleteCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
EndCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
ProgressCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
CancelCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
RewardCutscene = thing.accessor.ri(None, doc='CutsceneInfo')
AcceptSelections = thing.accessor.ri(None, doc='TriggerInfo')
ForwardTrigger = thing.accessor.ri(None, doc='TriggerInfoList')
BackwardTrigger = thing.accessor.ri(None, doc='InverseTriggerInfoList')
EndTrigger = thing.accessor.ri(None, doc='TriggerInfoList')
ProgressTrigger = thing.accessor.ri(None, doc='TriggerInfoList')
CancelTrigger = thing.accessor.ri(None, doc='TriggerInfoList')
FailTrigger = thing.accessor.ri(None, doc='TriggerInfoList')
RetryTrigger = thing.accessor.ri(None, doc='TriggerInfoList')
HasStagedInitSequence = thing.accessor.ri(None, doc='bool')
HasStagedEndCutscene = thing.accessor.ri(None, doc='bool')
HasNonInstantMainTrigger = thing.accessor.ri(None, doc='bool')
MIN_TRIGGER_INDEX = 0
TRIGGER_NAME_PATTERN = re.compile('(.*?)([\\d]+)')
TRIGGER_NAME_PREFIX_START = 'start/'
TRIGGER_NAME_PREFIX_FORWARD = 'forward/'
TRIGGER_NAME_PREFIX_END = 'end/'
TRIGGER_NAME_PREFIX_PROGRESS = 'progress/'
TRIGGER_NAME_PREFIX_CANCEL = 'cancel/'
TRIGGER_NAME_PREFIX_FAIL = 'fail/'
TRIGGER_NAME_PREFIX_RETRY = 'retry/'
TRIGGER_NAME_PREFIX_SELECTION = 'selection/'
TRIGGER_NAME_SORT_KEY_MAP = {TRIGGER_NAME_PREFIX_START: 1,
TRIGGER_NAME_PREFIX_FORWARD: MIN_TRIGGER_INDEX,
TRIGGER_NAME_PREFIX_END: MIN_TRIGGER_INDEX,
TRIGGER_NAME_PREFIX_PROGRESS: math.inf,
TRIGGER_NAME_PREFIX_CANCEL: math.inf,
TRIGGER_NAME_PREFIX_FAIL: math.inf,
TRIGGER_NAME_PREFIX_RETRY: math.inf,
TRIGGER_NAME_PREFIX_SELECTION: math.inf}
def __init__(self, questID: 'str', questName: 'str', manuallyCancellable: 'bool', repeatable: 'bool', startableByTownUI: 'bool', questType: 'QuestType', uiInfo: 'natuum.quest.UIInfo', guideKeyword: 'None or str', reportTargetT
ype: 'ReportTargetType', patternQuestSettings: 'None or collections.Mapping', prerequisites: 'None or collections.Mapping', defaultRoleNameMap: 'collections.Mapping', defaultCaptionPositionMap: 'collections.Mapping', rewards: 'Non
e or collections.Sequence', acceptResults: 'None or collections.Sequence', forwardResults: 'None or collections.Sequence', endResults: 'None or collections.Sequence', cancelResults: 'None or collections.Sequence', tokenItem: 'None
or str', nextQuestIDs: 'None or collections.Sequence', acceptSelectionText: 'None or str', rejectSelectionText: 'None or str', startTriggers: 'None or collections.Sequence', forwardTrigger: 'None or collections.Mapping', endTrigg
er: 'None or collections.Mapping', progressTrigger: 'None or collections.Mapping', cancelTrigger: 'None or collections.Mapping', failTrigger: 'None or collections.Mapping', retryTrigger: 'None or collections.Mapping', enableBackwa
rdTrigger: 'bool', turnInItems: 'bool', hasStagedInitSequence: 'bool', receiverNotReadyCutscene: 'None or collections.Mapping', initialCutscene: 'None or collections.Mapping', acceptCutscene: 'None or collections.Mapping', rejectC
utscene: 'None or collections.Mapping', completeCutscene: 'None or collections.Mapping', endCutscene: 'None or collections.Mapping', progressCutscene: 'None or collections.Mapping', cancelCutscene: 'None or collections.Mapping', r
ewardCutscene: 'None or collections.Mapping'):
if not isinstance(questID, str):
raise ValueError('invalid quest ID : {}'.format(questID))
if not isinstance(questName, str):
raise ValueError('invalid quest name : {}'.format(questName))
if not all((isinstance(key, str) and isinstance(value, str) for key, value in defaultRoleNameMap.items())):
raise ValueError('invalid defaultRoleNameMap : {}'.format(defaultRoleNameMap))
self.QuestID = questID
self.QuestName = questName
self.ManuallyCancellable = manuallyCancellable
self.Repeatable = repeatable
self.StartableByTownUI = startableByTownUI
self.QuestType = questType
self.UIInfo = uiInfo
self.GuideKeyword = guideKeyword
self.ReportTargetType = reportTargetType
self.Prerequisites = PrerequisiteInfo(questID, prerequisites)
self.TurnInItems = turnInItems
if patternQuestSettings is not None:
self.PatternQuestSettings = PatternQuestInfo(patternQuestSettings['resetDelay'])
self._QuestData__defaultRoleNameMap = defaultRoleNameMap
self._QuestData__defaultCaptionPositionMap = self._QuestData__resolveCaptionPositionMap(defaultCaptionPositionMap)
self.Rewards = ResultList(rewards) if rewards else None
self.AcceptResults = ResultList(acceptResults) if acceptResults else None
self.ForwardResults = ResultList(forwardResults) if forwardResults else None
self.EndResults = ResultList(endResults) if endResults else None
self.CancelResults = ResultList(cancelResults) if cancelResults else None
self.TokenItem = tokenItem
self.NextQuestIDs = nextQuestIDs
if acceptSelectionText is not None or (rejectSelectionText is not None):
triggerName = QuestData.createTriggerName(self.TRIGGER_NAME_PREFIX_SELECTION, self.MIN_TRIGGER_INDEX)
self.AcceptSelections = TriggerInfo(TriggerTypes.SELECT_STATE, triggerName, AcceptTriggerInfo(acceptSelectionText, rejectSelectionText))
self.StartTriggers = set()
if startTriggers is not None:
index = self.MIN_TRIGGER_INDEX
for rawTrigger in startTriggers:
triggerName = QuestData.createTriggerName(self.TRIGGER_NAME_PREFIX_START, index)
self.StartTriggers.add(self._QuestData__resolveTrigger(rawTrigger, triggerName, TriggerUsageTypes.START))
index += 1
if forwardTrigger is not None:
self.ForwardTrigger = self._QuestData__resolveTriggerInfoList(forwardTrigger, self.TRIGGER_NAME_PREFIX_FORWARD)
if endTrigger is not None:
self.EndTrigger = self._QuestData__resolveTriggerInfoList(endTrigger, self.TRIGGER_NAME_PREFIX_END)
if progressTrigger is not None:
self.ProgressTrigger = self._QuestData__resolveTriggerInfoList(progressTrigger, self.TRIGGER_NAME_PREFIX_PROGRESS)
if cancelTrigger is not None:
self.CancelTrigger = self._QuestData__resolveTriggerInfoList(cancelTrigger, self.TRIGGER_NAME_PREFIX_CANCEL)
if failTrigger is not None:
self.FailTrigger = self._QuestData__resolveTriggerInfoList(failTrigger, self.TRIGGER_NAME_PREFIX_FAIL)
if retryTrigger is not None:
self.RetryTrigger = self._QuestData__resolveTriggerInfoList(retryTrigger, self.TRIGGER_NAME_PREFIX_RETRY)
if self.ForwardTrigger is not None and enableBackwardTrigger:
self.BackwardTrigger = InverseTriggerInfoList.create(self.ForwardTrigger)
else:
self.BackwardTrigger = None
if receiverNotReadyCutscene is not None:
self.ReceiverNotReadyCutscene = (self._QuestData__resolveCutscene)(*receiverNotReadyCutscene)
if initialCutscene is not None:
self.InitialCutscene = (self._QuestData__resolveCutscene)(*initialCutscene[:-1], *(hasStagedInitSequence,))
if acceptCutscene is not None:
self.AcceptCutscene = (self._QuestData__resolveCutscene)(*acceptCutscene[:-1], *(hasStagedInitSequence,))
if rejectCutscene is not None:
self.RejectCutscene = (self._QuestData__resolveCutscene)(*rejectCutscene[:-1], *(hasStagedInitSequence,))
if completeCutscene is not None:
self.CompleteCutscene = (self._QuestData__resolveCutscene)(*completeCutscene)
if endCutscene is not None:
self.EndCutscene = (self._QuestData__resolveCutscene)(*endCutscene)
if progressCutscene is not None:
self.ProgressCutscene = (self._QuestData__resolveCutscene)(*progressCutscene)
if cancelCutscene is not None:
self.CancelCutscene = (self._QuestData__resolveCutscene)(*cancelCutscene)
if rewardCutscene is not None:
self.RewardCutscene = (self._QuestData__resolveCutscene)(*rewardCutscene)
if self.AcceptSelections is not None or self.InitialCutscene is not None or self.AcceptCutscene is not None or self.RejectCutscene is not None:
self.HasStagedInitSequence = hasStagedInitSequence
else:
self.HasStagedInitSequence = False
if self.EndCutscene is not None:
self.HasStagedEndCutscene = self.EndCutscene.IsStaged
else:
self.HasStagedEndCutscene = False
hasNonInstantMainTrigger = False
if self.ForwardTrigger is not None:
hasNonInstantMainTrigger |= not self.ForwardTrigger.IsInstant
if self.EndTrigger is not None:
hasNonInstantMainTrigger |= not self.EndTrigger.IsInstant
self.HasNonInstantMainTrigger = hasNonInstantMainTrigger
@staticmethod
def createTriggerName(prefix: 'str', index: 'int') -> 'str':
return prefix + str(index)
@staticmethod
def getTriggerNameSortKey(triggerName: 'str') -> 'collections.Sequence':
match = QuestData.TRIGGER_NAME_PATTERN.fullmatch(triggerName)
if match is None:
return (QuestData.MIN_TRIGGER_INDEX, QuestData.MIN_TRIGGER_INDEX)
try:
index = int(match.group(2))
except Exception:
index = QuestData.MIN_TRIGGER_INDEX
return (
QuestData.TRIGGER_NAME_SORT_KEY_MAP.get(match.group(1), QuestData.MIN_TRIGGER_INDEX), index)
@staticmethod
def isStartTrigger(triggerName: 'str') -> 'bool':
return triggerName.startswith(QuestData.TRIGGER_NAME_PREFIX_START)
def iterateTriggerInfos(self) -> 'generator of TriggerInfo':
def iterate(trigger: 'None or natuum.quest.TriggerInfoList') -> 'generator of TriggerInfo':
if isinstance(trigger, natuum.quest.TriggerInfoList):
for subList in trigger.iterate():
for triggerInfo in subList:
yield triggerInfo
for triggerInfo in iterate(self.ForwardTrigger):
yield triggerInfo
for triggerInfo in iterate(self.EndTrigger):
yield triggerInfo
for triggerInfo in iterate(self.ProgressTrigger):
yield triggerInfo
for triggerInfo in iterate(self.CancelTrigger):
yield triggerInfo
for triggerInfo in iterate(self.FailTrigger):
yield triggerInfo
for triggerInfo in iterate(self.RetryTrigger):
yield triggerInfo
if self.StartTriggers:
for triggerInfo in self.StartTriggers:
yield triggerInfo
if self.AcceptSelections:
yield self.AcceptSelections
def __resolveTriggerInfoList(self, raw: 'collections.Sequence', prefix: 'str') -> 'None or TriggerInfoList':
index = self.MIN_TRIGGER_INDEX
orList = list()
isInstant = False
for subRaw in raw:
if not subRaw:
continue
else:
isInstantAndList = True
andList = list()
for sub in subRaw:
triggerName = QuestData.createTriggerName(prefix, index)
index += 1
trigger = self._QuestData__resolveTrigger(sub, triggerName, TriggerUsageTypes.UPDATE)
isInstantAndList &= TriggerTypes.isInstant(trigger.TriggerType)
andList.append(trigger)
isInstant |= isInstantAndList
orList.append(andList)
if orList:
return TriggerInfoList(orList, isInstant)
def __resolveTrigger(self, raw: 'collections.Mapping', triggerName: 'str', usageType: 'TriggerUsageTypes') -> 'TriggerInfo':
if not len(raw) == 1:
raise ValueError('invalid trigger data : {}'.format(raw))
for rawTriggerType, rawAuxArg in raw.items():
triggerType = TriggerTypes._convert(rawTriggerType)
if TriggerTypes.CONVERSATION == triggerType:
listenerQuestRole = rawAuxArg['listenerQuestRole']
selectionText = rawAuxArg['selectionText']
confirmText = rawAuxArg.get('confirmText')
auxArg = ConversationTriggerInfo(listenerQuestRole, selectionText, confirmText, self.QuestID, usageType)
elif TriggerTypes.GUARDIAN_CHANGED == triggerType:
fromClassID = rawAuxArg['fromClassID']
toClassID = rawAuxArg['toClassID']
auxArg = GuardianChangedTriggerInfo(fromClassID, toClassID, usageType)
elif TriggerTypes.PROPERTY == triggerType:
auxArg = PropertyTriggerInfo(rawAuxArg['condition'])
elif TriggerTypes.GUARDIAN_PROPERTY == triggerType:
auxArg = PropertyTriggerInfo(rawAuxArg['condition'])
elif TriggerTypes.RELATIONSHIP == triggerType:
auxArg = RelationshipCondition(rawAuxArg)
elif TriggerTypes.DINE == triggerType:
auxArg = DineEventTriggerInfo(rawAuxArg)
elif TriggerTypes.CONTRACT == triggerType:
auxArg = ContractInfo(rawAuxArg['classID'])
elif TriggerTypes.DECK == triggerType:
auxArg = DeckInfo(rawAuxArg['classIDs'])
elif TriggerTypes.HAND == triggerType:
auxArg = HandInfo(rawAuxArg)
elif TriggerTypes.ACTION == triggerType:
auxArg = ActionInfo(rawAuxArg['actionID'], rawAuxArg['hostRole'])
elif TriggerTypes.KILL_COUNT == triggerType:
targetClassID = rawAuxArg['classID']
targetCount = rawAuxArg['count']
questRole = rawAuxArg.get('questRole')
uiName = rawAuxArg.get('uiName')
auxArg = CountTriggerInfo(targetClassID, targetCount, questRole=questRole,
uiName=uiName)
elif TriggerTypes.ITEM_COUNT == triggerType:
targetClassID = rawAuxArg['classID']
targetCount = rawAuxArg['count']
countType = rawAuxArg['countType']
propertyCondition = rawAuxArg.get('propertyCondition')
if propertyCondition is not None:
propertyCondition = PropertyCondition(propertyCondition)
uiName = rawAuxArg.get('uiName')
auxArg = CountTriggerInfo(targetClassID, targetCount, countType=countType,
propertyCondition=propertyCondition,
uiName=uiName)
elif TriggerTypes.RANDOM_KILL_COUNT == triggerType:
candidates = rawAuxArg['classIDCandidates']
maxCount = rawAuxArg['countRange']['max']
minCount = rawAuxArg['countRange']['min']
auxArg = RandomCountTriggerInfo(candidates, maxCount, minCount)
elif TriggerTypes.RANDOM_ITEM_COUNT == triggerType:
candidates = rawAuxArg['classIDCandidates']
maxCount = rawAuxArg['countRange']['max']
minCount = rawAuxArg['countRange']['min']
countType = rawAuxArg['countType']
auxArg = RandomCountTriggerInfo(candidates, maxCount, minCount, countType)
elif TriggerTypes.PROXIMITY_ENTER == triggerType:
try:
auxArg = EnterTriggerInfo(rawAuxArg['target'], rawAuxArg['targetType'], rawAuxArg['range'])
except ValueError as e:
try:
raise ValueError('{} - {}'.format(self.QuestID, e))
finally:
e = None
del e
elif TriggerTypes.TIMER == triggerType:
auxArg = rawAuxArg['delay']
elif TriggerTypes.SELECTION == triggerType:
selectionMap = dict()
for selectionText, subMap in rawAuxArg.items():
selectionMap[selectionText] = subMap.get('nextQuestIDs')
auxArg = SelectionTriggerInfo(selectionMap)
else:
raise ValueError('invalid trigger type : {}'.format(rawTriggerType))
return TriggerInfo(triggerType, triggerName, auxArg)
def __resolveCutscene(self, scriptData: 'natuum.cutscene.ScriptData', roleNameMap: 'None or collections.Mapping', captionPositionMap: 'None or collections.Mapping', isStaged: 'bool') -> 'CutsceneInfo':
if roleNameMap is None:
roleNameMap = self._QuestData__defaultRoleNameMap
if captionPositionMap is None:
captionPositionMap = self._QuestData__defaultCaptionPositionMap
else:
captionPositionMap = self._QuestData__resolveCaptionPositionMap(captionPositionMap)
return CutsceneInfo(scriptData, roleNameMap, captionPositionMap, isStaged)
def __resolveCaptionPositionMap(self, valueMap: 'None or collections.Mapping') -> 'collections.Mapping':
converted = dict()
if valueMap is not None:
for key, value in valueMap.items():
converted[key] = natuum.cutscene._CaptionPositionTypes.fromString(value)
return converted
_QuestData__defaultRoleNameMap = None
_QuestData__defaultCaptionPositionMap = None
-- EDIT --
Here is a link for every quest in the game
Quest
You can open it with your favorite text application
By the way, what i did to get this is putting a file from the data folder, to the tpk folder (the one from unpack/pack).
What you need to get this working, is to just have unpack.py and pack.py in a folder and a file.tpk / file.tpk.table in the root on this same folder.
Open the unpack.py, change the name of the file @ line 256 and then run the script.
I ran this script with pycharm, but any python ide will do.
To pack it, it is the same thing as unpacking, place pack.py on the root of your pack/unpack folder, you should now have a folder named Quest with .cache folder inside and every quest.
pack.py @ line 222, change the folder name for yours (ie : quest / race / soil / serverconfig... etc)
Then run pack.py
You have now quest.tpk and quest.tpk.table
If this is not clear, i will provide screenshots
As an exemple, this is the serverConfig.tpk
Code:{
"default": {
"GatewayServers": [
{
"port": 10010,
"host": "127.0.0.1"
}
],
"NXLogServer": "54.180.20.36:24200",
"ChatManagerEntityID": null,
"UnstableZoneManagerEntityID": null,
"DocumentContentLibraryEntityID": null,
"NXLogServiceID": "430007416",
"terrainServers": {
"99999": [
null,
"192.168.7.8:29000"
],
"0": [
"192.168.7.8:29000",
"192.168.7.8:29000"
],
"1": [
"192.168.7.8:29001",
"192.168.7.8:29001"
]
},
"ItemDesignLibraryEntityID": null,
"GatewayEntityID": null
},
"peria-live": {
"GatewayServers": [
{
"port": 10010,
"host": "183.110.2.132"
}
],
"NXLogServer": "192.168.128.89:24200",
"ChatManagerEntityID": null,
"UnstableZoneManagerEntityID": null,
"DocumentContentLibraryEntityID": null,
"NXLogServiceID": "430011512",
"terrainServers": {
"99999": [
null,
"10.168.20.136:29000"
],
"0": [
"183.110.2.136:29000",
"10.168.20.136:29000"
],
"1": [
"183.110.2.136:29001",
"10.168.20.136:29001"
]
},
"ItemDesignLibraryEntityID": null,
"GatewayEntityID": null
},
"peria-stage": {
"GatewayServers": [
{
"port": 10010,
"host": "183.110.1.70"
}
],
"NXLogServer": "192.168.128.88:24200",
"ChatManagerEntityID": null,
"UnstableZoneManagerEntityID": null,
"DocumentContentLibraryEntityID": null,
"NXLogServiceID": "430008440",
"terrainServers": {
"99999": [
null,
"10.168.10.72:29000"
],
"0": [
"183.110.1.72:29000",
"10.168.10.72:29000"
],
"1": [
"183.110.1.72:29001",
"10.168.10.72:29001"
]
},
"ItemDesignLibraryEntityID": null,
"GatewayEntityID": null
},
"peria-dev": {
"GatewayServers": [
{
"port": 10010,
"host": "192.168.8.12"
}
],
"NXLogServer": "54.180.20.36:24200",
"ChatManagerEntityID": null,
"UnstableZoneManagerEntityID": null,
"DocumentContentLibraryEntityID": null,
"NXLogServiceID": null,
"terrainServers": {
"99999": [
null,
"192.168.7.8:29000"
],
"0": [
"192.168.7.8:29000",
"192.168.7.8:29000"
],
"1": [
"192.168.7.8:29001",
"192.168.7.8:29001"
]
},
"ItemDesignLibraryEntityID": null,
"GatewayEntityID": null
}
}