Skip to content

Commit 7b3a6ec

Browse files
committed
rework interactive mode (add scaling + recommended options) + autodetect HUD mode
1 parent 25b008b commit 7b3a6ec

File tree

5 files changed

+190
-112
lines changed

5 files changed

+190
-112
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Add scaling algorithm selector to interactive mode.
13+
- Add `[RECOMMENDED]` tags to recommended options in interactive mode (maps to the default CLI options). The recommended option can be selected either by using its number (as usual) or by pressing ENTER (as a shorthand).
14+
1015
### Changed
1116

1217
- Improve warning displayed when unable to patch custom resolution in `ProfileX.json` by making it more explicit it can usually be ignored.
18+
- Auto-determine default HUD resizing mode when using the CLI: `expand` for for 16:10, 21:9, and 32:9 / `center` for 48:9 and wider.
1319

1420
## [1.7.4] - 2022-10-14
1521

README.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,9 @@ Hephaistos supports the following HUD resizing modes: **(Click on items to show
282282
<details>
283283
<summary><code>expand</code> (default)</summary>
284284

285-
Expand the HUD horizontally and vertically, i.e. HUD will scale with screen size.
285+
Expand the HUD horizontally and vertically.
286286
Static HUD elements will be repositioned to their intended location for the new screen size, e.g. health indicator will be in the bottom left, resource indicator will be in the bottom right.
287+
This is the default HUD resizing mode used by Hephaistos for 16:10, 21:9, and 32:9, but note that you may want to try out `--hud=center` for 32:9 to see what you prefer.
287288

288289
![hud_21-9-vanilla](https://user-images.githubusercontent.com/4659919/178168394-99b68f49-b391-4fa9-9f5b-89be99981a91.jpg)
289290
![hud_21-9_expand](https://user-images.githubusercontent.com/4659919/178168395-2f730460-a8c8-4d11-8a35-8f3b0c003626.jpg)
@@ -293,15 +294,15 @@ Static HUD elements will be repositioned to their intended location for the new
293294
<details>
294295
<summary><code>center</code></summary>
295296

296-
Keep HUD in the center of the screen with the same size as the original HUD, i.e. screen size will change but HUD will not move.
297+
Keep HUD in the center of the screen with the same size as the original 16:9 HUD.
298+
Screen size will change but HUD will not move, static HUD elements will remain at their default 16:9 position.
299+
This is the default HUD resizing mode used by Hephaistos for 48:9 and wider.
297300

298301
![hud_21-9-vanilla](https://user-images.githubusercontent.com/4659919/178168394-99b68f49-b391-4fa9-9f5b-89be99981a91.jpg)
299302
![hud_21-9_center](https://user-images.githubusercontent.com/4659919/178168396-37eb931d-0158-409c-8e8d-702e37fa5435.jpg)
300303

301304
</details>
302305

303-
You might want to use `--hud=center` for 32:9 or wider resolutions.
304-
305306
### Scaling
306307

307308
Hephaistos supports the following scaling algorithms: **(Click on items to show details)**
@@ -338,8 +339,6 @@ This scaling is not recommended for general usage as it effectively "zooms out"
338339

339340
</details>
340341

341-
Use `--scaling=pixel` if you wish to use pixel-based scaling.
342-
343342
### Custom resolution
344343

345344
By default, Hephaistos patches a custom resolution in the [`ProfileX.sjson` configuration file](https://www.pcgamingwiki.com/wiki/Hades#Configuration_file.28s.29_location) by updating its `WindowWidth`/`WindowHeight` and `X`/`Y` values.

hephaistos/cli.py

Lines changed: 135 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from hephaistos import backups, config, hashes, helpers, interactive, lua_mod, patchers, sjson_data
1010
from hephaistos.config import LOGGER
11-
from hephaistos.helpers import HadesNotFound, HUD, ModImporterRuntimeError, Platform, Scaling
11+
from hephaistos.helpers import AspectRatio, HadesNotFound, HUD, ModImporterRuntimeError, Platform, Scaling
1212

1313

1414
class ParserBase(ArgumentParser):
@@ -105,90 +105,6 @@ def __start(self) -> None:
105105
# if in interactive mode, loop until user manually closes
106106
self.__restart() if config.interactive_mode else self.__end()
107107

108-
def __interactive(self, raw_args: list[str]) -> None:
109-
interactive.clear()
110-
try:
111-
msg = f"""Hi! This interactive wizard will help you to set up Hephaistos.
112-
Note: while Hephaistos can be used in interactive mode for basic usage, you will need to switch to non-interactive mode for any advanced usage. See the README for more details.
113-
114-
{helpers.check_version()}
115-
"""
116-
print(msg)
117-
available_subcommands = {
118-
subcommand: helpers.capitalize(self.subcommands[subcommand].description)
119-
for subcommand in ['patch', 'restore', 'status']
120-
}
121-
subcommand = interactive.pick(
122-
add_option=interactive.EXIT_OPTION,
123-
**available_subcommands,
124-
)
125-
raw_args.append(subcommand)
126-
if subcommand == 'patch':
127-
choice = interactive.pick(
128-
common1610="Select from common 16:10 resolutions",
129-
common219="Select from common 21:9 resolutions",
130-
common329="Select from common 32:9 resolutions",
131-
common489="Select from common 48:9 / triple screen resolutions",
132-
manual="Input resolution manually",
133-
)
134-
if choice == 'common1610':
135-
(width, height) = interactive.pick(
136-
prompt="Select resolution:",
137-
options=[
138-
'1280 x 800',
139-
'1440 x 900',
140-
'1680 x 1050',
141-
'1920 x 1200',
142-
'2560 x 1600',
143-
'3840 x 2400',
144-
],
145-
).split(' x ')
146-
elif choice == 'common219':
147-
(width, height) = interactive.pick(
148-
prompt="Select resolution:",
149-
options=[
150-
'2560 x 1080',
151-
'3440 x 1440',
152-
'3840 x 1600',
153-
'5120 x 2160',
154-
],
155-
).split(' x ')
156-
elif choice == 'common329':
157-
(width, height) = interactive.pick(
158-
prompt="Select resolution:",
159-
options=[
160-
'3840 x 1080',
161-
'5120 x 1440',
162-
],
163-
).split(' x ')
164-
elif choice == 'common489':
165-
(width, height) = interactive.pick(
166-
prompt="Select resolution:",
167-
options=[
168-
'5760 x 1080',
169-
'7680 x 1440',
170-
],
171-
).split(' x ')
172-
else:
173-
width = interactive.input_number("Width: ")
174-
height = interactive.input_number("Height: ")
175-
print()
176-
raw_args.append(width)
177-
raw_args.append(height)
178-
choice = interactive.pick(
179-
prompt="Select HUD preference (for 32:9, try out both options and see what you prefer!):",
180-
expand="Expand HUD horizontally / vertically (recommended for 21:9 and 16:10)",
181-
center="Keep HUD in the center (recommended for 48:9 / triple screen)",
182-
)
183-
raw_args.append('--hud')
184-
raw_args.append(choice)
185-
# repass modified raw_args to parse_args after selection is done
186-
return self.parse_args(raw_args)
187-
except interactive.InteractiveCancel:
188-
self.__restart(prompt_user=False)
189-
except interactive.InteractiveExit:
190-
self.__end()
191-
192108
def __handle_global_args(self, args: list[str]) -> None:
193109
# logging verbosity level
194110
level = ParserBase.VERBOSE_TO_LOG_LEVEL[min(args.verbose, 2)]
@@ -230,6 +146,135 @@ def __configure_hades_dir(self, hades_dir_arg: str) -> None:
230146
LOGGER.error(msg)
231147
self.__end(1, prompt_user=config.interactive_mode)
232148

149+
def __interactive(self, raw_args: list[str]) -> None:
150+
interactive.clear()
151+
try:
152+
msg = f"""Hi! This interactive wizard will help you to set up Hephaistos.
153+
Note: while Hephaistos can be used in interactive mode for basic usage, you will need to switch to non-interactive mode for any advanced usage. See the README for more details.
154+
155+
{helpers.check_version()}
156+
"""
157+
print(msg)
158+
available_subcommands = {
159+
subcommand: helpers.capitalize(self.subcommands[subcommand].description)
160+
for subcommand in ['patch', 'restore', 'status']
161+
}
162+
subcommand = interactive.pick(
163+
options=available_subcommands,
164+
additional_option=interactive.EXIT_OPTION,
165+
)
166+
raw_args.append(subcommand)
167+
if subcommand == 'patch':
168+
self.__interactive_patch_handler(raw_args)
169+
# repass modified raw_args to parse_args after selection is done
170+
return self.parse_args(raw_args)
171+
except interactive.InteractiveCancel:
172+
self.__restart(prompt_user=False)
173+
except interactive.InteractiveExit:
174+
self.__end()
175+
176+
def __interactive_patch_handler(self, raw_args) -> None:
177+
options = {
178+
AspectRatio._16_10: "Select from common 16:10 resolutions",
179+
AspectRatio._21_9: "Select from common 21:9 resolutions",
180+
AspectRatio._32_9: "Select from common 32:9 resolutions",
181+
AspectRatio._48_9: "Select from common 48:9 resolutions",
182+
AspectRatio.manual: "Input resolution manually",
183+
}
184+
aspect_ratio = interactive.pick(options=options)
185+
dispatch = {
186+
AspectRatio._16_10: lambda: self.__interactive_pick_resolution([
187+
'1280 x 800',
188+
'1440 x 900',
189+
'1680 x 1050',
190+
'1920 x 1200',
191+
'2560 x 1600',
192+
'3840 x 2400',
193+
]),
194+
AspectRatio._21_9: lambda: self.__interactive_pick_resolution([
195+
'2560 x 1080',
196+
'3440 x 1440',
197+
'3840 x 1600',
198+
'5120 x 2160',
199+
]),
200+
AspectRatio._32_9: lambda: self.__interactive_pick_resolution([
201+
'3840 x 1080',
202+
'5120 x 1440',
203+
]),
204+
AspectRatio._48_9: lambda: self.__interactive_pick_resolution([
205+
'5760 x 1080',
206+
'7680 x 1440',
207+
]),
208+
AspectRatio.manual: self.__interactive_input_resolution,
209+
}
210+
(width, height) = dispatch[aspect_ratio]()
211+
raw_args += [width, height]
212+
213+
enabled = "enabled"
214+
recommended = "recommended"
215+
args = {
216+
AspectRatio._16_10: {
217+
enabled: [Scaling.VERT_PLUS, Scaling.PIXEL_BASED],
218+
recommended: Scaling.VERT_PLUS
219+
},
220+
AspectRatio._21_9: {
221+
enabled: [Scaling.HOR_PLUS, Scaling.PIXEL_BASED],
222+
recommended: Scaling.HOR_PLUS
223+
},
224+
AspectRatio._32_9: {
225+
enabled: [Scaling.HOR_PLUS, Scaling.PIXEL_BASED],
226+
recommended: Scaling.HOR_PLUS
227+
},
228+
AspectRatio._48_9: {
229+
enabled: [Scaling.HOR_PLUS, Scaling.PIXEL_BASED],
230+
recommended: Scaling.HOR_PLUS
231+
},
232+
AspectRatio.manual: {
233+
enabled: [Scaling.AUTODETECT, Scaling.PIXEL_BASED],
234+
recommended: Scaling.AUTODETECT
235+
},
236+
}
237+
scaling = self.__interactive_pick_scaling(enabled=args[aspect_ratio][enabled], recommended=args[aspect_ratio][recommended])
238+
raw_args += ['--scaling', scaling]
239+
240+
recommended = {
241+
AspectRatio._16_10: HUD.EXPAND,
242+
AspectRatio._21_9: HUD.EXPAND,
243+
AspectRatio._32_9: None,
244+
AspectRatio._48_9: HUD.CENTER,
245+
AspectRatio.manual: None,
246+
}
247+
hud = self.__interactive_pick_hud(recommended=recommended[aspect_ratio])
248+
raw_args += ['--hud', hud]
249+
250+
return raw_args
251+
252+
def __interactive_pick_resolution(self, options: list[str]) -> tuple[int, int]:
253+
return interactive.pick(prompt="Select resolution:", options=options).split(' x ')
254+
255+
def __interactive_input_resolution(self) -> tuple[int, int]:
256+
width = interactive.input_number("Width: ")
257+
height = interactive.input_number("Height: ")
258+
print()
259+
return (width, height)
260+
261+
def __interactive_pick_scaling(self, enabled: list[Scaling], recommended: Scaling=None):
262+
options = {
263+
Scaling.AUTODETECT: "Hor+ / Vert+ (based on input resolution): keeps experience close to vanilla game.",
264+
Scaling.HOR_PLUS: "Hor+: keeps experience close to vanilla game.",
265+
Scaling.VERT_PLUS: "Vert+: keeps experience close to vanilla game.",
266+
Scaling.PIXEL_BASED: """Pixel-based: effectively "zooms out" the camera.""",
267+
}
268+
options = {option: description for option, description in options.items() if option in enabled}
269+
return interactive.pick(prompt="Select scaling algorithm (see README for more details):", options=options, recommended_option=recommended)
270+
271+
def __interactive_pick_hud(self, recommended: HUD=None) -> HUD:
272+
options = {
273+
HUD.EXPAND: "Expand the HUD horizontally and vertically.",
274+
HUD.CENTER: "Keep HUD in the center of the screen with the same size as the original 16:9 HUD.",
275+
}
276+
return interactive.pick(prompt="Select HUD resizing mode (see README for more details):", options=options, recommended_option=recommended)
277+
233278
def __restart(self, prompt_user=True) -> None:
234279
if prompt_user:
235280
interactive.any_key("\nPress any key to continue...")
@@ -261,9 +306,9 @@ def __init__(self, **kwargs) -> None:
261306
self.add_argument('--scaling', default=Scaling.AUTODETECT,
262307
choices=[Scaling.HOR_PLUS.value, Scaling.VERT_PLUS.value, Scaling.PIXEL_BASED.value],
263308
help="scaling type (default: 'hor+' for wider aspect ratios / 'vert+' for taller aspect ratios)")
264-
self.add_argument('--hud', default=HUD.EXPAND,
309+
self.add_argument('--hud', default=HUD.AUTODETECT,
265310
choices=[HUD.EXPAND.value, HUD.CENTER.value],
266-
help="HUD mode (default: 'expand')")
311+
help="HUD mode (default: 'expand' for most aspect ratios / 'center' for 48:9 and wider)")
267312
self.add_argument('--no-custom-resolution', action='store_false', default=True, dest='custom_resolution',
268313
help="do not patch custom resolution in 'ProfileX.sjson' configuration file (default: patch custom resolution)")
269314
self.add_argument('-f', '--force', action='store_true',
@@ -272,7 +317,8 @@ def __init__(self, **kwargs) -> None:
272317
def handler(self, width: int, height: int, scaling: Scaling, hud: HUD, custom_resolution: bool, force: bool, **kwargs) -> None:
273318
"""Compute viewport depending on arguments, then patch all needed files and install Lua mod.
274319
If using '--force', discard backups, hashes and SJSON data, and uninstall Lua mod."""
275-
scaling = helpers.configure_screen_variables(width, height, scaling)
320+
scaling, hud = helpers.autodetect(width, height, scaling, hud)
321+
helpers.configure_screen_variables(width, height, scaling)
276322
LOGGER.info(f"Using resolution: {config.resolution.width, config.resolution.height}")
277323
LOGGER.info(f"Using '--scaling={scaling}': computed patch viewport {config.new_screen.width, config.new_screen.height}")
278324

@@ -302,7 +348,7 @@ def handler(self, width: int, height: int, scaling: Scaling, hud: HUD, custom_re
302348
LOGGER.error(e)
303349
if config.interactive_mode:
304350
LOGGER.error("It looks like the game was updated. Do you wish to discard previous backups and re-patch Hades from its current state?")
305-
choice = interactive.pick(options=['Yes', 'No',], add_option=None)
351+
choice = interactive.pick(options=['Yes', 'No',], recommended_option='Yes', additional_option=None)
306352
if choice == 'Yes':
307353
self.handler(width, height, scaling, hud, custom_resolution, force=True)
308354
else:

hephaistos/helpers.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,19 @@
2121

2222
# Type definitions
2323
IntOrFloat = Union[int, float]
24+
class AspectRatio(str, Enum):
25+
_16_10 = '16:10'
26+
_21_9 = '21:9'
27+
_32_9 = '32:9'
28+
_48_9 = '48:9'
29+
manual = 'manual'
2430
class Scaling(str, Enum):
2531
AUTODETECT = 'autodetect'
2632
HOR_PLUS = 'hor+'
2733
VERT_PLUS = 'vert+'
2834
PIXEL_BASED = 'pixel'
2935
class HUD(str, Enum):
36+
AUTODETECT = 'autodetect'
3037
EXPAND = 'expand'
3138
CENTER = 'center'
3239
class Platform(str, Enum):
@@ -335,15 +342,27 @@ def run_modimporter(modimporter_file: Path, clean_only: bool=False) -> None:
335342
raise e
336343

337344

338-
def configure_screen_variables(width: int, height: int, scaling: Scaling) -> Scaling:
339-
"""Compute virtual viewport size to patch depending on scaling type and given resolution width / height."""
345+
def autodetect(width: int, height: int, scaling: Scaling, hud: HUD) -> tuple[Scaling, HUD]:
346+
"""Set default values based on aspect ratios for arguments with autodetection."""
340347
if scaling == Scaling.AUTODETECT:
341-
# use hor+ for aspect ratios wider than default (e.g. 21:9)
348+
# use 'hor+' for aspect ratios wider than default (e.g. 21:9)
342349
if (width / height) >= (config.DEFAULT_SCREEN.width / config.DEFAULT_SCREEN.height):
343350
scaling = Scaling.HOR_PLUS
344-
# use vert+ for aspect ratios taller than default (e.g. 16:10)
351+
# use 'vert+' for aspect ratios taller than default (e.g. 16:10)
345352
else:
346353
scaling = Scaling.VERT_PLUS
354+
if hud == HUD.AUTODETECT:
355+
# use 'center' for 48:9 and wider
356+
if (width / height) >= (48 / 9):
357+
hud = hud.CENTER
358+
# use 'expand' for anything else
359+
else:
360+
hud = hud.EXPAND
361+
return (scaling, hud)
362+
363+
364+
def configure_screen_variables(width: int, height: int, scaling: Scaling):
365+
"""Compute virtual viewport size to patch depending on scaling type and given resolution width / height."""
347366
config.resolution = config.Screen(width, height)
348367
if scaling == Scaling.HOR_PLUS:
349368
virtual_width = int(width / height * config.DEFAULT_SCREEN.height)
@@ -358,7 +377,6 @@ def configure_screen_variables(width: int, height: int, scaling: Scaling) -> Sca
358377
config.scale_factor_X = config.new_screen.width / config.DEFAULT_SCREEN.width
359378
config.scale_factor_Y = config.new_screen.height / config.DEFAULT_SCREEN.height
360379
config.scale_factor = max(config.scale_factor_X, config.scale_factor_Y)
361-
return scaling
362380

363381

364382
def recompute_fixed_value(original_value: IntOrFloat, original_reference_point: IntOrFloat, new_reference_point: IntOrFloat) -> IntOrFloat:

0 commit comments

Comments
 (0)