Skip to content

Commit

Permalink
Add: mod build with Art Manager
Browse files Browse the repository at this point in the history
  • Loading branch information
ezloj committed Sep 21, 2023
1 parent 8982fcf commit 6fe826a
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 17 deletions.
96 changes: 84 additions & 12 deletions src/binary_automation/art_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
"""
import logging
import os
import time

from pywinauto.application import Application
from pywinauto.controls.common_controls import ListViewWrapper


logger = logging.getLogger("tqma")

Expand All @@ -13,30 +16,99 @@ class ArtManager:
""" Represents art manager tool from official TQAE install path """
def __init__(self, installation_path, settings_path):
logger.debug("Instantiating art manager object")
self.app = None
self.build_log = None
self.main_window_dialog = None
self.main_window_specification = None
self.settings_path = settings_path
self.tools_ini_path = os.path.join(self.settings_path, "Tools.ini")
self.path = os.path.join(installation_path, "ArtManager.exe")
self.tools_ini = self.read_tools_ini(os.path.join(settings_path, "Tools.ini"))
self.tools_ini, self.tools_ini_file_contents = self.read_tools_ini()

def build(self, output_mod_name):
""" Builds the selected mod """
logger.debug(f"Building mod {output_mod_name}:)")
self.run()
logger.debug("Pressing F7 to build")
self.main_window_dialog.send_keystrokes('{F7}')
logger.debug("Waiting for the build to complete")
self.main_window_dialog.minimize()
done = False
while not done:
for child in self.main_window_specification.Children():
if isinstance(child, ListViewWrapper):
texts = child.texts()
for text in texts:
if 'Build time' in text:
logger.debug(f"Done. Build time: {text}")
self.build_log = "\n".join(texts)
logger.debug(f"Build log: {self.build_log}")
done = True
time.sleep(0.2)
logger.debug("Build complete! Killing Art Manager...")
self.app.kill()
logger.debug("Copying the output folder")
output_mod_dir = os.path.join(self.tools_ini["builddir"].strip(), "custommaps", output_mod_name)
logger.debug(f"Output mod dir: {output_mod_dir}")

def read_tools_ini(self, tools_ini_path):
return output_mod_dir

def read_tools_ini(self):
""" Reads tools_ini and extracts some important settings related to work/build paths """
parsed_settings = {}
logger.debug(f"Tools.ini path: {tools_ini_path}")
with open(tools_ini_path, 'r', encoding='utf-8') as tools_ini:
for line in tools_ini.readlines():

logger.debug(f"Tools.ini path: {self.tools_ini_path}")
with open(self.tools_ini_path, 'r', encoding='utf-8') as tools_ini:
ini_file_contents = tools_ini.readlines()
for line in ini_file_contents:
if "=" in line:
parsed_settings[line.split("=")[0]] = line.strip().split("=")[1]
logger.debug(f"Working dir: {parsed_settings['localdir']}")
logger.debug(f"Build dir: {parsed_settings['builddir']}")
logger.debug(f"Tools dir: {parsed_settings['toolsdir']}")

return parsed_settings
return parsed_settings, ini_file_contents

def run(self):
""" Runs the ArtManager executable """
logger.debug("Starting art manager")
app = Application(backend="win32").start(self.path)
logger.debug(f"{app}")
self.app = Application(backend="win32").start(self.path)
# wait for the window to come up
self.main_window_dialog = self.app.window(best_match='ArtManager').wait('ready')
self.main_window_specification = self.app.window(best_match='ArtManager')

def build(self, output_mod_name):
""" Builds the selected mod """
logger.debug(f"Building mod {output_mod_name}:)")
self.run()
def update_tools_ini(self, setting, value):
""" Takes a setting with a value and updates the already parsed internal representation of it """
if setting in self.tools_ini:
new_contents = []
value += '\n'
self.tools_ini[setting] = value
for line in self.tools_ini_file_contents:
if setting in line:
line = f"{setting}={value}"
logger.debug(f"Found {line.strip()}, updating")
new_contents.append(line)
self.tools_ini_file_contents = new_contents
logger.debug(f"Updated parsed Tools.ini setting {setting} to {value}")
return True

logger.error(f"Failed to update parsed Tools.ini setting {setting} to {value}")
return False

def write_tools_ini(self):
""" Writes self.tools_ini to the Tools.ini file """
logger.debug("Writing Tools.ini")
with open(self.tools_ini_path, 'w', encoding='utf-8') as tools_ini:
tools_ini.writelines(self.tools_ini_file_contents)

def set_build_mod_name(self, mod_name):
"""
Updates the internal representation of tools.init as well as the original file
with the new_mod_name as moddir. This is needed to avoid clicking the UI to set the build mod
"""
update_result = self.update_tools_ini("moddir", mod_name)
update_result = self.update_tools_ini("lastMod", mod_name)
if not update_result:
raise RuntimeError(f"Failed to update the build mod name to {mod_name}")

self.write_tools_ini()
33 changes: 28 additions & 5 deletions src/gui/frames/mod_merge_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import logging
import os
import re
import shutil
import traceback

from PyQt6.QtWidgets import QListWidget, QAbstractItemView, QPushButton, QLabel, QLineEdit
Expand Down Expand Up @@ -53,6 +54,10 @@ def __init__(self, parent, settings):
self.new_mod_name_field.textChanged.connect(self.new_mod_name_changed)
self.layout().addWidget(self.new_mod_name_field, 3, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignTop)

# Status label
self.status_label = QLabel("Status: provide inputs to begin")
self.layout().addWidget(self.status_label, 4, 0, 1, 1, alignment=Qt.AlignmentFlag.AlignTop)

# Build button
build_button = QPushButton('Build')
build_button.clicked.connect(self.merge_mods)
Expand Down Expand Up @@ -122,21 +127,27 @@ def merge_mods(self):
try:
if not self.selected_mods:
self.mod_list.setStyleSheet(self.bad_style_sheet)
logger.debug("No mods selected so can't really merge anything!")
info_text = "No mods selected!"
logger.debug(info_text)
self.status_label.setText(f"{info_text}")
return
if not self.validate_new_mod_name():
logger.debug("Provided mod name is either empty or doesn't conform to the naming regex")
info_text = "Mod name has to match '^[a-zA-Z0-9_-]+$'"
logger.debug(info_text)
self.status_label.setText(f"{info_text}")
self.new_mod_name_field.setStyleSheet(self.bad_style_sheet)
return

self.status_label.setText("Status: working")
new_mod_name = self.new_mod_name_field.text()

art_manager = ArtManager(
installation_path = self.settings.get_setting("TQAE path"),
settings_path = self.settings.get_setting("TQAE Save folder")
)

mod_merge = ModMerge(
output_path=art_manager.tools_ini['localdir'],
new_mod_name=self.new_mod_name_field.text()
new_mod_name=new_mod_name
)

mod_merge.set_mods(self.selected_mods)
Expand All @@ -146,7 +157,19 @@ def merge_mods(self):
self.resolve_conflicts(mod_merge.overlaps)
mod_merge.merge()

art_manager.build(self.new_mod_name_field.text())
# Art Manager building
art_manager.set_build_mod_name(new_mod_name)
art_manager_output_mod_dir = art_manager.build(self.new_mod_name_field.text())

# Art Manager is done at this point
mods_dir = os.path.join(self.settings.get_setting("TQAE Save folder"), "custommaps")
os.makedirs(mods_dir, exist_ok=True)
mod_destination_dir = os.path.join(mods_dir, new_mod_name)
logger.debug(f"Copying {art_manager_output_mod_dir} into {mod_destination_dir}")
shutil.copytree(art_manager_output_mod_dir, mod_destination_dir)
self.status_label.setText(
f"Status: build complete!\nMod dir:\n{mod_destination_dir}\nYou can play the game with the mod now"
)

except Exception as exc:
traceback_formatted = traceback.format_exc()
Expand Down

0 comments on commit 6fe826a

Please sign in to comment.