diff --git a/dodo.py b/dodo.py index 476271f..6c3f963 100644 --- a/dodo.py +++ b/dodo.py @@ -1,77 +1,151 @@ #!/usr/bin/env python3 from pathlib import Path -from yaml import safe_load +from doit import get_var + +DOIT_CONFIG = { + 'default_tasks': ['build'], + 'action_string_formatting': 'new' +} def files(p): for i in p: if i.is_file(): yield i +class RawFiles(list): + def __init__(self, *args, **kwargs): + self.src_path = Path(kwargs.pop('src_path')) + self.image_path = Path(kwargs.pop('image_path')) + super().__init__(*args, **kwargs) -class Patchwork(dict): + def sources(self): + for i in self: + yield self.src_path/i + def targets(self): + for i in self: + yield self.image_path/i + +class BasePatchwork(dict): patches_path = None patchwork_path = None + vanilla_path = None def __init__(self, *arkg, **kwargs): - self.patches_path = Path(kwargs.pop("patches_path")) - self.patchwork_path = Path(kwargs.pop("patchwork_path")) + self.patches_path = Path(kwargs.pop('patches_path')) + self.patchwork_path = Path(kwargs.pop('patchwork_path')) + self.vanilla_path = Path(kwargs.pop('vanilla_path')) super().__init__(*arkg, **kwargs) - patchwork_mkdir = 'mkdir -p {}/{}' def targets(self): for i in self.keys(): yield self.patchwork_path/i - def tasks(self, vanilla_path): + def key_vanilla(self, orig_key): + return self.vanilla_path/orig_key + def key_target(self, orig_key): + return self.patchwork_path/orig_key + def tasks(self, actions): + def repack_action(action, **kwargs): + if callable(action): + return (action, [], kwargs) + elif isinstance(action, str): + return action.format(**kwargs) + else: + return action for k, v in self.items(): - yield ( - [ self.patches_path/v, vanilla_path/k ], # src ( patch file, orig file ) - [ self.patchwork_path/k ], #dst ( patched file ) - [ + original = self.vanilla_path/k + patch = self.patches_path/v + target = self.patchwork_path/k + yield { + 'name' : k, + 'file_dep' : [ original, patch ], + 'targets' : [ target ], + 'actions' : [ repack_action(action, original=original, patch=patch, target=target) + for action in actions ], + 'clean' : True + } + + + +class Patchwork(BasePatchwork): + def tasks_(self, actions): + for k, v in self.items(): + yield { + 'name' : k, + 'file_dep' : [ self.patches_path/v, self.vanilla_path/k ], + 'targets' : [ self.patchwork_path/k ], + 'actions' : [ f"mkdir -p {self.patchwork_path}/{Path(k).parent}", f"patch -u --binary -N -o '{self.patchwork_path}/{k}' "\ - f"'{vanilla_path}/{k}' '{self.patches_path}/{v}'" - ] # action ( mkdir, patch ) - ) + f"'{self.vanilla_path}/{k}' '{self.patches_path}/{v}'" + ], # action ( mkdir, patch ) + 'clean' : True + } -class ScriptedPatchwork(dict): - def __init__(self, source_file): - with open(source_file) as s: - super().__init__(safe_load(s)) +class ScriptedPatchwork(BasePatchwork): + def __init__(self, *args, **kwargs): + from yaml import safe_load + file = kwargs.pop('source_file') + super().__init__(*args, **kwargs) + with open(file) as s: + for key, data in safe_load(s).items(): + discovered_source = next((Path(self.vanilla_path)/'history'/'states').glob(f"{key}*")) + relative_path = discovered_source.relative_to(self.vanilla_path) + self[relative_path] = data +class RasterPatchwork(BasePatchwork): + pass mod_name = 'randchgs' -mod_install_path = 'C:/Users/User/Documents/Paradox Interactive/Hearts of Iron IV/mod/randchgs' -dir_vanilla = Path("../vanilla/v1.13.4") +mod_install_path = get_var('descriptor_mod_path', 'C:/Users/User/Documents/Paradox Interactive/Hearts of Iron IV/mod/randchgs') +dir_vanilla = Path(get_var('vanilla_path', '../vanilla/current')) dir_image = Path('build/image') -dir_modimage = Path(f"{dir_image}/{mod_name}") +dir_modimage = dir_image/mod_name dir_src_raw = Path('src/raw') -dir_src_raw_files = list(files(Path(dir_src_raw).rglob("*"))) +dir_src_raw_files = RawFiles( + files(Path(dir_src_raw).rglob("*")), + src_path = dir_src_raw, + image_path = dir_image +) dir_patches_path = Path('src/patches') dir_patchwork_path = Path('build/patched') -patchwork = Patchwork({ - 'map/definition.csv': 'map_definition.patch', - 'history/countries/ISR - Israel.txt': 'history_countries_ISR.patch', -}, +patchwork = Patchwork( + { + 'map/definition.csv': 'map_definition.patch', + 'history/countries/ISR - Israel.txt': 'history_countries_ISR.patch', + 'history/states/454-Israel.txt': 'history_states_454.patch', + }, patches_path = dir_patches_path, - patchwork_path = dir_patchwork_path + patchwork_path = dir_patchwork_path, + vanilla_path = dir_vanilla ) -patchwork_scripted = ScriptedPatchwork(dir_patches_path/'extrapoint.yaml') +patchwork_scripted = ScriptedPatchwork( + source_file = dir_patches_path/'extrapoint.yaml', + patches_path = '.', + patchwork_path = dir_patchwork_path, + vanilla_path = dir_vanilla +) -dir_image_files = [ f"{dir_modimage}/{f.relative_to(dir_src_raw)}" for f in dir_src_raw_files ] + \ - [ f"{dir_modimage}/{f.relative_to(patchwork.patchwork_path)}" for f in patchwork.targets() ] +patchwork_raster = RasterPatchwork( + { + 'map/terrain.bmp' : 'terrain.bmp', + }, + patches_path = dir_patches_path, + patchwork_path = dir_patchwork_path, + vanilla_path = dir_vanilla +) -DOIT_CONFIG = { - 'default_tasks': ['build'], - 'action_string_formatting': 'both' -} -def history_states_path(state_id): - history_state_file = next((Path(dir_vanilla)/'history'/'states').glob(f"{state_id}*")) - return { - 'src' : history_state_file, - 'dest': Path(dir_patchwork_path)/history_state_file.relative_to(dir_vanilla) - } +def collect_image_files(patchworks, dir_modimage): + for pw in patchworks: + for target in pw: + yield dir_modimage/target +dir_image_files = list(collect_image_files( + [patchwork, patchwork_scripted, patchwork_raster], + dir_modimage=dir_modimage +)) +dir_image_files += [ dir_modimage/f.relative_to(dir_src_raw) for f in dir_src_raw_files ] + def task_patch_history_states_scripted(): def history_state_patch(task, provinces): @@ -80,46 +154,60 @@ def task_patch_history_states_scripted(): target.parent.mkdir(parents=True, exist_ok=True) seen_history = False wrote_history = False - with source.open(newline='') as infile: - with target.open('w') as outfile: - for line_in in infile: - outfile.write(line_in) - seen_history = seen_history or ( 'history' in line_in ) - if not wrote_history and seen_history and '{' in line_in: - for province_id, province_info in provinces.items(): - outfile.write( - "\t\tvictory_points = {\r\n"\ - f"\t\t\t{province_id} {province_info['points']}\r\n"\ - "\t\t}\r\n"\ - ) - wrote_history = True - for state_id, state_data in patchwork_scripted.items(): - files = history_states_path(state_id) + with source.open(newline='') as infile, target.open('w') as outfile: + for line_in in infile: + outfile.write(line_in) + seen_history = seen_history or ( 'history' in line_in ) + if not wrote_history and seen_history and '{' in line_in: + for province_id, province_info in provinces.items(): + outfile.write( + "\t\tvictory_points = {\r\n"\ + f"\t\t\t{province_id} {province_info['points']}\r\n"\ + "\t\t}\r\n"\ + ) + wrote_history = True + for state_src, state_data in patchwork_scripted.items(): yield { - 'name' : f"{state_id}", - 'file_dep' : [ files['src'] ], - 'targets' : [ files['dest'] ], + 'name' : state_src, + 'file_dep' : [ patchwork_scripted.key_vanilla(state_src) ], + 'targets' : [ patchwork_scripted.key_target(state_src) ], 'actions' : [ ( history_state_patch, [], {'provinces' : state_data}) ], 'clean' : True } +def task_patch_raster(): + def raster_patch(target, original, patch): + from PIL import Image + with Image.open(original) as base, Image.open(patch) as patch: + def mask(): + for bg, p in zip(base.tobytes(), patch.tobytes()): + if p == 35: + yield bg + else: + yield p + + output = Image.frombytes(base.mode, base.size, bytes(mask())) + output.putpalette(base.palette) + output.save(target) + output.close() + + yield from patchwork_raster.tasks([raster_patch]) + def task_patch(): - for src, dest, patch in patchwork.tasks(dir_vanilla): - yield { - 'name' : f"{dest[0]}", - 'file_dep' : src, - 'targets' : dest, - 'actions' : patch, - 'clean' : True - } + def mkdir(target, original, patch): + Path(target).parent.mkdir(parents=True, exist_ok=True) + yield from patchwork.tasks([ + mkdir, + "patch -u --binary -N -o '{target}' '{original}' '{patch}'" + ]) def task_image(): - filelist = dir_src_raw_files + list(patchwork.targets()) - + filelist = dir_src_raw_files + \ + list(patchwork.targets()) + \ + list(patchwork_scripted.targets()) + \ + list(patchwork_raster.targets()) return { - 'file_dep' : filelist + - [ history_states_path(state_id)['dest'] - for state_id in patchwork_scripted.keys() ], + 'file_dep' : filelist, 'targets' : dir_image_files, 'actions' : [f"mkdir -p {dir_modimage}", f"rsync -rv {dir_src_raw}/ {patchwork.patchwork_path}/"\ @@ -128,7 +216,6 @@ def task_image(): } def task_image_mod(): - dep = 'src/raw/descriptor.mod' def prepare_modname_mod(task): source = Path(next(iter(task.file_dep))) target = Path(task.targets[0]) @@ -136,9 +223,9 @@ def task_image_mod(): with source.open() as s: with target.open('w') as o: o.write(s.read()) - o.write(f"path=\"{mod_install_path}\"") + o.write(f"\npath=\"{mod_install_path}\"\n") return { - 'file_dep' : [ dep ], + 'file_dep' : [ 'src/raw/descriptor.mod' ], 'targets' : [f"{dir_image}/{mod_name}.mod" ], 'actions' : [ prepare_modname_mod ], 'clean' : True @@ -146,15 +233,10 @@ def task_image_mod(): def task_build(): return { - 'task_dep' : [ 'image' ], 'file_dep' : [f"{dir_image}/{mod_name}.mod" ] + dir_image_files, 'targets' : [f"{dir_image}/{mod_name}.zip" ], - 'actions' : [f"cd {dir_image} && zip -r {mod_name}.zip {mod_name}", + 'actions' : [ "rm -f %(targets)s", + f"cd {dir_image} && zip -r {mod_name}.zip {mod_name}", f"cd {dir_image} && zip -r {mod_name}.zip {mod_name}.mod" ], - 'clean' : True - } - -def task_purge_build(): - return { - 'actions' : [ 'rm -rf build' ] + 'clean' : [ 'rm -rf build' ] } diff --git a/src/patches/history_states_454.patch b/src/patches/history_states_454.patch new file mode 100644 index 0000000..aaa74d1 --- /dev/null +++ b/src/patches/history_states_454.patch @@ -0,0 +1,42 @@ +--- ../vanilla/v1.13.4/history/states/454-Israel.txt 2023-11-12 00:08:53.206004927 +0400 ++++ build/patched/history/states/454-Israel.txt 2023-11-16 13:23:39.618844427 +0400 +@@ -4,7 +4,13 @@ + name="STATE_454" #Palestine + manpower = 933142 + +- state_category = rural ++ state_category = metropolis ++ ++ resources={ ++ steel = 15 ++ tungsten = 10 ++ aluminium = 15 ++ } + + history={ + owner = ENG +@@ -17,7 +23,7 @@ + } + } + victory_points = { +- 1086 1 ++ 1086 10 + } + victory_points = { + 4206 1 +@@ -25,6 +31,9 @@ + victory_points = { + 1065 1 + } ++ victory_points = { ++ 1015 1 ++ } + add_core_of = PAL + add_core_of = ISR + } +@@ -34,4 +43,5 @@ + } + + local_supplies=0.0 ++ manpower = 7527019 + } diff --git a/src/raw/history/states/454-Israel.txt b/src/raw/history/states/454-Israel.txt deleted file mode 100644 index 499bba1..0000000 --- a/src/raw/history/states/454-Israel.txt +++ /dev/null @@ -1,46 +0,0 @@ - -state={ - id=454 - name="STATE_454" #Palestine - manpower = 933142 - - state_category = metropolis - - resources={ - steel = 15 - tungsten = 10 - aluminium = 15 - } - - history={ - owner = ENG - buildings = { - infrastructure = 3 #was: 5 - industrial_complex = 1 - air_base = 2 - 4206 = { - naval_base = 3 - } - } - victory_points = { - 1086 10 - } - victory_points = { - 4206 1 - } - victory_points = { - 1065 1 - } - victory_points = { - 1015 1 - } - add_core_of = PAL - add_core_of = ISR - } - - provinces={ - 1015 1065 1086 1201 4088 4206 7107 7176 11970 1 - } - - local_supplies=0.0 -}