added patching raster images

fixes #2
This commit is contained in:
Alexey Chubukov 2023-11-17 16:40:27 +04:00
parent 569a44307e
commit 2f48ac6fd2
3 changed files with 205 additions and 127 deletions

216
dodo.py
View File

@ -1,77 +1,151 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from pathlib import Path 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): def files(p):
for i in p: for i in p:
if i.is_file(): if i.is_file():
yield i 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 patches_path = None
patchwork_path = None patchwork_path = None
vanilla_path = None
def __init__(self, *arkg, **kwargs): def __init__(self, *arkg, **kwargs):
self.patches_path = Path(kwargs.pop("patches_path")) self.patches_path = Path(kwargs.pop('patches_path'))
self.patchwork_path = Path(kwargs.pop("patchwork_path")) self.patchwork_path = Path(kwargs.pop('patchwork_path'))
self.vanilla_path = Path(kwargs.pop('vanilla_path'))
super().__init__(*arkg, **kwargs) super().__init__(*arkg, **kwargs)
patchwork_mkdir = 'mkdir -p {}/{}'
def targets(self): def targets(self):
for i in self.keys(): for i in self.keys():
yield self.patchwork_path/i 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(): for k, v in self.items():
yield ( original = self.vanilla_path/k
[ self.patches_path/v, vanilla_path/k ], # src ( patch file, orig file ) patch = self.patches_path/v
[ self.patchwork_path/k ], #dst ( patched file ) 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"mkdir -p {self.patchwork_path}/{Path(k).parent}",
f"patch -u --binary -N -o '{self.patchwork_path}/{k}' "\ f"patch -u --binary -N -o '{self.patchwork_path}/{k}' "\
f"'{vanilla_path}/{k}' '{self.patches_path}/{v}'" f"'{self.vanilla_path}/{k}' '{self.patches_path}/{v}'"
] # action ( mkdir, patch ) ], # action ( mkdir, patch )
) 'clean' : True
}
class ScriptedPatchwork(dict): class ScriptedPatchwork(BasePatchwork):
def __init__(self, source_file): def __init__(self, *args, **kwargs):
with open(source_file) as s: from yaml import safe_load
super().__init__(safe_load(s)) 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_name = 'randchgs'
mod_install_path = 'C:/Users/User/Documents/Paradox Interactive/Hearts of Iron IV/mod/randchgs' mod_install_path = get_var('descriptor_mod_path', 'C:/Users/User/Documents/Paradox Interactive/Hearts of Iron IV/mod/randchgs')
dir_vanilla = Path("../vanilla/v1.13.4") dir_vanilla = Path(get_var('vanilla_path', '../vanilla/current'))
dir_image = Path('build/image') 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 = 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_patches_path = Path('src/patches')
dir_patchwork_path = Path('build/patched') dir_patchwork_path = Path('build/patched')
patchwork = Patchwork({ patchwork = Patchwork(
{
'map/definition.csv': 'map_definition.patch', 'map/definition.csv': 'map_definition.patch',
'history/countries/ISR - Israel.txt': 'history_countries_ISR.patch', 'history/countries/ISR - Israel.txt': 'history_countries_ISR.patch',
'history/states/454-Israel.txt': 'history_states_454.patch',
}, },
patches_path = dir_patches_path, 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 ] + \ patchwork_raster = RasterPatchwork(
[ f"{dir_modimage}/{f.relative_to(patchwork.patchwork_path)}" for f in patchwork.targets() ] {
'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): def collect_image_files(patchworks, dir_modimage):
history_state_file = next((Path(dir_vanilla)/'history'/'states').glob(f"{state_id}*")) for pw in patchworks:
return { for target in pw:
'src' : history_state_file, yield dir_modimage/target
'dest': Path(dir_patchwork_path)/history_state_file.relative_to(dir_vanilla) 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 task_patch_history_states_scripted():
def history_state_patch(task, provinces): def history_state_patch(task, provinces):
@ -80,8 +154,7 @@ def task_patch_history_states_scripted():
target.parent.mkdir(parents=True, exist_ok=True) target.parent.mkdir(parents=True, exist_ok=True)
seen_history = False seen_history = False
wrote_history = False wrote_history = False
with source.open(newline='') as infile: with source.open(newline='') as infile, target.open('w') as outfile:
with target.open('w') as outfile:
for line_in in infile: for line_in in infile:
outfile.write(line_in) outfile.write(line_in)
seen_history = seen_history or ( 'history' in line_in ) seen_history = seen_history or ( 'history' in line_in )
@ -93,33 +166,48 @@ def task_patch_history_states_scripted():
"\t\t}\r\n"\ "\t\t}\r\n"\
) )
wrote_history = True wrote_history = True
for state_id, state_data in patchwork_scripted.items(): for state_src, state_data in patchwork_scripted.items():
files = history_states_path(state_id)
yield { yield {
'name' : f"{state_id}", 'name' : state_src,
'file_dep' : [ files['src'] ], 'file_dep' : [ patchwork_scripted.key_vanilla(state_src) ],
'targets' : [ files['dest'] ], 'targets' : [ patchwork_scripted.key_target(state_src) ],
'actions' : [ ( history_state_patch, [], {'provinces' : state_data}) ], 'actions' : [ ( history_state_patch, [], {'provinces' : state_data}) ],
'clean' : True '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(): def task_patch():
for src, dest, patch in patchwork.tasks(dir_vanilla): def mkdir(target, original, patch):
yield { Path(target).parent.mkdir(parents=True, exist_ok=True)
'name' : f"{dest[0]}", yield from patchwork.tasks([
'file_dep' : src, mkdir,
'targets' : dest, "patch -u --binary -N -o '{target}' '{original}' '{patch}'"
'actions' : patch, ])
'clean' : True
}
def task_image(): 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 { return {
'file_dep' : filelist + 'file_dep' : filelist,
[ history_states_path(state_id)['dest']
for state_id in patchwork_scripted.keys() ],
'targets' : dir_image_files, 'targets' : dir_image_files,
'actions' : [f"mkdir -p {dir_modimage}", 'actions' : [f"mkdir -p {dir_modimage}",
f"rsync -rv {dir_src_raw}/ {patchwork.patchwork_path}/"\ f"rsync -rv {dir_src_raw}/ {patchwork.patchwork_path}/"\
@ -128,7 +216,6 @@ def task_image():
} }
def task_image_mod(): def task_image_mod():
dep = 'src/raw/descriptor.mod'
def prepare_modname_mod(task): def prepare_modname_mod(task):
source = Path(next(iter(task.file_dep))) source = Path(next(iter(task.file_dep)))
target = Path(task.targets[0]) target = Path(task.targets[0])
@ -136,9 +223,9 @@ def task_image_mod():
with source.open() as s: with source.open() as s:
with target.open('w') as o: with target.open('w') as o:
o.write(s.read()) o.write(s.read())
o.write(f"path=\"{mod_install_path}\"") o.write(f"\npath=\"{mod_install_path}\"\n")
return { return {
'file_dep' : [ dep ], 'file_dep' : [ 'src/raw/descriptor.mod' ],
'targets' : [f"{dir_image}/{mod_name}.mod" ], 'targets' : [f"{dir_image}/{mod_name}.mod" ],
'actions' : [ prepare_modname_mod ], 'actions' : [ prepare_modname_mod ],
'clean' : True 'clean' : True
@ -146,15 +233,10 @@ def task_image_mod():
def task_build(): def task_build():
return { return {
'task_dep' : [ 'image' ],
'file_dep' : [f"{dir_image}/{mod_name}.mod" ] + dir_image_files, 'file_dep' : [f"{dir_image}/{mod_name}.mod" ] + dir_image_files,
'targets' : [f"{dir_image}/{mod_name}.zip" ], '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" ], f"cd {dir_image} && zip -r {mod_name}.zip {mod_name}.mod" ],
'clean' : True 'clean' : [ 'rm -rf build' ]
}
def task_purge_build():
return {
'actions' : [ 'rm -rf build' ]
} }

View File

@ -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
}

View File

@ -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
}