Advertisement
Guest User

import_workers

a guest
Aug 22nd, 2023
619
1
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import json
  2. import os
  3. from datetime import datetime
  4. import re
  5. import struct
  6. from dataclasses import dataclass
  7. from enum import Enum
  8.  
  9.  
  10. def find_shallow(base_dir, needle):
  11.     latest_file = None
  12.     latest_time = 0
  13.  
  14.     for subdir in os.listdir(base_dir):
  15.         file_path = os.path.join(base_dir, subdir, needle)
  16.         if os.path.exists(file_path):
  17.             ts = os.path.getmtime(file_path)
  18.             if ts > latest_time:
  19.                 latest_file = file_path
  20.                 latest_time = ts
  21.  
  22.     assert latest_file
  23.     print(f'using {latest_file} modified {datetime.fromtimestamp(latest_time)}')
  24.     return latest_file
  25.  
  26.  
  27. def find_deep(base_dir, needle):
  28.     latest_file = None
  29.     latest_time = 0
  30.     ts = 0
  31.  
  32.     for root, dirs, files in os.walk(base_dir):
  33.         for file in files:
  34.             if file == needle:
  35.                 file_path = os.path.join(root, file)
  36.                 ts = os.path.getmtime(file_path)
  37.                 if ts > latest_time:
  38.                     latest_file = file_path
  39.                     latest_time = ts
  40.  
  41.     return latest_file, ts
  42.  
  43.  
  44. def parse_worker_names(filename):
  45.     with open(filename, "r") as file:
  46.         text = file.read()
  47.  
  48.     pattern = re.compile(r'workerNo="(.*?)" name="(.*?)"')
  49.     tuples = re.findall(pattern, text)
  50.     wid_names = {int(wid): name for (wid, name) in tuples}
  51.  
  52.     assert wid_names
  53.     print('parsed', len(wid_names), 'worker names')
  54.     return wid_names
  55.  
  56.  
  57.  
  58. class JobType(Enum):
  59.     UNKNOWN = -1
  60.     PLANTZONE = 0
  61.     RENTHOUSE = 2
  62.     RENTHOUSELARGE = 6
  63.     HARVESTING = 9
  64.  
  65.  
  66. @dataclass
  67. class WorkerRecord:
  68.     wid: int
  69.     charkey: int
  70.     tnk: int
  71.     level: int
  72.     mspdLvlup: int              # sheet = static * (1 + lvlup / 1000000)
  73.     wspdLvlup: int              # sheet = static + lvlup / 1000000
  74.     luckLvlup: int              # sheet = static + lvlup / 10000
  75.     skills: tuple[int]
  76.     jobtype: int
  77.     repeats: int
  78.     workplace: int
  79.     label: str
  80.  
  81.     @classmethod
  82.     def from_file(cls, f, label=''):
  83.         raw = f.read(68)
  84.         tup = struct.unpack("=QHIIIII9HBH", raw[:51])
  85.         fields = (*tup[:7], tup[7:16], *tup[16:], 0, label)
  86.         me = cls(*fields)
  87.         if me.jobtype == 0:  # plantzone
  88.             exk, pzk = struct.unpack("HH", raw[51:55])
  89.             me.workplace = pzk
  90.         if me.jobtype == 2:  # renthouse
  91.             exk, hk = struct.unpack("HH", raw[51:55])
  92.             me.workplace = hk
  93.         if me.jobtype == 6:  # renthouse large
  94.             hk, = struct.unpack("H", raw[51:53])
  95.             me.workplace = hk
  96.         return me
  97.  
  98.     def to_dict(self):
  99.         d = self.__dict__.copy()
  100.         d.pop('wid')
  101.         d.pop('repeats')
  102.  
  103.         if d['label'] == '':
  104.             d['label'] = self.wid % 10000000000000000
  105.  
  106.         d.pop('jobtype')
  107.         d.pop('workplace')
  108.  
  109.         if self.jobtype == 0:           # plantzone
  110.             d['job'] = self.workplace
  111.         elif self.jobtype == 2:         # renthouse
  112.             d['job'] = {'kind': 'workshop', 'hk': self.workplace}
  113.         elif self.jobtype == 6:         # renthouse large
  114.             d['job'] = {'kind': 'workshop', 'hk': self.workplace}
  115.         elif self.jobtype == 9:         # harvesting
  116.             d['job'] = 'farming'
  117.         else:
  118.             d['job'] = None
  119.  
  120.         return d
  121.  
  122.  
  123. def parse_worker_data(filename, wid_names):
  124.     workers = []
  125.     with open(filename, "rb") as f:
  126.         head, count, version = struct.unpack("4sII", f.read(12))
  127.         assert head == b'PABR'
  128.         assert version == 2
  129.         for i in range(count):
  130.             worker = WorkerRecord.from_file(f)
  131.             if worker.wid in wid_names:
  132.                 worker.label = wid_names[worker.wid]
  133.                 workers.append(worker)
  134.     print('parsed', len(workers), 'workers')
  135.     return workers
  136.  
  137.  
  138.  
  139. def to_workerman(workers, filename):
  140.     out = {
  141.         "activateAncado": False,
  142.         "lodgingTaken": {},             # insert lodging data here OR leave empty to
  143.         "lodgingP2W": {},               # keep old lodging data unchanged after import
  144.         'userWorkers': workers,
  145.     }
  146.     with open(filename, "w") as f:
  147.         json.dump(out, f, default=WorkerRecord.to_dict)
  148.         print('output written to:', os.path.abspath(filename))
  149.  
  150.  
  151. def parse_all(documents_path, verbose=False):
  152.     cache_path = os.path.join(documents_path, "Black Desert\\UserCache\\")
  153.     gamevars = find_shallow(cache_path, "gamevariable.xml")
  154.     wid_names = parse_worker_names(gamevars)
  155.     cache, ts = find_deep(cache_path, 'worker.cache')
  156.     if cache:
  157.         print(f'using {cache} modified {datetime.fromtimestamp(ts)}')
  158.     else:
  159.         while True:
  160.             cache = input(r"Search for worker.cache failed, find it yourself and enter its full pathname (X:\path\to\worker.cache): ")
  161.             if os.path.exists(cache):
  162.                 break
  163.     worker_list = parse_worker_data(cache, wid_names)
  164.  
  165.     worker_list.sort(key=lambda w: w.tnk)
  166.  
  167.     if verbose:
  168.         for worker in worker_list:
  169.             print(worker)
  170.  
  171.     to_workerman(worker_list, "to_workerman.json")
  172.  
  173. def main():
  174.     paths = [
  175.         os.path.expanduser("~/Documents"),
  176.         os.path.expanduser("~/Documenten"),
  177.         os.path.join(os.environ['USERPROFILE'], "OneDrive/Documents"),
  178.         os.path.join(os.environ['USERPROFILE'], "OneDrive/Documenten"),
  179.     ]
  180.  
  181.     while True:
  182.         try:
  183.             path = paths.pop(0)
  184.         except IndexError:
  185.             path = input("Enter your User Documents path: ")
  186.         try:
  187.             parse_all(path)
  188.             return
  189.         except FileNotFoundError as e:
  190.             print(e, "- retrying")
  191.  
  192.  
  193. if __name__ == "__main__":
  194.     main()
  195.     input("Press Enter to continue...")
  196.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement