From 3b53952767802d390922d5a2a216d78c8cef76e6 Mon Sep 17 00:00:00 2001
From: PeachyPeach <72323920+PeachyPeachSM64@users.noreply.github.com>
Date: Tue, 10 Mar 2026 23:38:24 +0100
Subject: [PATCH] DJUI Text improvements and bug fixes (#1131)
- Drastically improve and optimize display list usage for text
- Add text alignment with and without interpolation, and color codes, new lines and tabs are now handled properly
- Restored alpha color code (`#RGBA` or `#RRGGBBAA`) for `djui_print_text` functions (regular DJUI text allows alpha color codes, but ignores the alpha component)
- Add constants for common text alignment and rotation pivot values
- Fix interpolation issues with all `djui_hud` elements
- A few autogen fixes (missing `number` type for constants, missing return values for some functions)
- Fix recolorable hud font offsets on `e`, `i` and `o` letters
---
autogen/convert_constants.py | 2 +
autogen/convert_functions.py | 2 +-
autogen/lua_definitions/constants.lua | 54 +-
autogen/lua_definitions/functions.lua | 27 +-
autogen/lua_definitions/structs.lua | 8 -
docs/lua/constants.md | 13 +
docs/lua/functions-3.md | 89 ++-
docs/lua/functions-4.md | 4 +-
docs/lua/functions-5.md | 7 +-
docs/lua/functions-6.md | 2 +-
docs/lua/functions.md | 3 +
docs/lua/structs.md | 16 -
src/pc/crash_handler.c | 3 +
src/pc/discord/discord_activity.c | 8 +-
src/pc/djui/djui.c | 3 +-
src/pc/djui/djui_font.c | 62 +-
src/pc/djui/djui_font.h | 6 +-
src/pc/djui/djui_gfx.c | 105 ++-
src/pc/djui/djui_gfx.h | 10 +-
src/pc/djui/djui_hud_utils.c | 704 +++++++++++-------
src/pc/djui/djui_hud_utils.h | 40 +-
src/pc/djui/djui_inputbox.c | 3 +
src/pc/djui/djui_panel_mod_menu.c | 4 +-
src/pc/djui/djui_text.c | 250 +++++--
src/pc/djui/djui_text.h | 5 +
src/pc/djui/djui_unicode.c | 14 +-
src/pc/djui/djui_unicode.h | 10 +-
src/pc/lua/smlua_cobject_autogen.c | 14 +-
src/pc/lua/smlua_cobject_autogen.h | 1 -
src/pc/lua/smlua_constants_autogen.c | 13 +
src/pc/lua/smlua_functions.c | 2 +-
src/pc/lua/smlua_functions_autogen.c | 84 ++-
src/pc/lua/smlua_hooks.c | 2 +-
src/pc/mods/mod.c | 36 +-
src/pc/mods/mod.h | 7 +-
src/pc/mods/mods.c | 21 +-
src/pc/network/packets/packet_download.c | 10 +-
src/pc/network/packets/packet_mod_list.c | 7 +-
src/pc/utils/misc.c | 15 -
src/pc/utils/misc.h | 1 -
.../custom_font_hud_recolor.rgba32.png | Bin 25778 -> 39601 bytes
41 files changed, 1133 insertions(+), 534 deletions(-)
diff --git a/autogen/convert_constants.py b/autogen/convert_constants.py
index 46b521710..409cd8427 100644
--- a/autogen/convert_constants.py
+++ b/autogen/convert_constants.py
@@ -535,6 +535,8 @@ def def_constant(fname, processed_constant, skip_constant):
continue
if '"' in c[1]:
s += '\n--- @type string\n'
+ elif "." in c[1]:
+ s += '\n--- @type number\n'
else:
s += '\n--- @type integer\n'
s += '%s = %s\n' % (c[0], c[1])
diff --git a/autogen/convert_functions.py b/autogen/convert_functions.py
index 8846db56f..0ec22b12c 100644
--- a/autogen/convert_functions.py
+++ b/autogen/convert_functions.py
@@ -1342,7 +1342,7 @@ def doc_function(fname, function):
s += '- None\n'
s += '\n### Returns\n'
- if rtype != None:
+ if len(rvalues) > 0:
for _, ptype, plink in rvalues:
if plink:
s += '- [%s](%s)\n' % (ptype, plink)
diff --git a/autogen/lua_definitions/constants.lua b/autogen/lua_definitions/constants.lua
index fef72a27c..8e160c5d4 100644
--- a/autogen/lua_definitions/constants.lua
+++ b/autogen/lua_definitions/constants.lua
@@ -2760,6 +2760,42 @@ CONSOLE_MESSAGE_ERROR = 2 --- @type ConsoleMessageLevel
--- | `CONSOLE_MESSAGE_WARNING`
--- | `CONSOLE_MESSAGE_ERROR`
+--- @type number
+ROTATION_PIVOT_X_LEFT = 0.0
+
+--- @type number
+ROTATION_PIVOT_X_CENTER = 0.5
+
+--- @type number
+ROTATION_PIVOT_X_RIGHT = 1.0
+
+--- @type number
+ROTATION_PIVOT_Y_TOP = 0.0
+
+--- @type number
+ROTATION_PIVOT_Y_CENTER = 0.5
+
+--- @type number
+ROTATION_PIVOT_Y_BOTTOM = 1.0
+
+--- @type number
+TEXT_HALIGN_LEFT = 0.0
+
+--- @type number
+TEXT_HALIGN_CENTER = 0.5
+
+--- @type number
+TEXT_HALIGN_RIGHT = 1.0
+
+--- @type number
+TEXT_VALIGN_TOP = 0.0
+
+--- @type number
+TEXT_VALIGN_CENTER = 0.5
+
+--- @type number
+TEXT_VALIGN_BOTTOM = 1.0
+
RESOLUTION_DJUI = 0 --- @type HudUtilsResolution
RESOLUTION_N64 = 1 --- @type HudUtilsResolution
RESOLUTION_COUNT = 2 --- @type HudUtilsResolution
@@ -2778,16 +2814,18 @@ FILTER_COUNT = 2 --- @type HudUtilsFilter
--- | `FILTER_LINEAR`
--- | `FILTER_COUNT`
-FONT_NORMAL = 0 --- @type DjuiFontType
-FONT_MENU = 1 --- @type DjuiFontType
-FONT_HUD = 2 --- @type DjuiFontType
-FONT_ALIASED = 3 --- @type DjuiFontType
-FONT_CUSTOM_HUD = 4 --- @type DjuiFontType
-FONT_RECOLOR_HUD = 5 --- @type DjuiFontType
-FONT_SPECIAL = 6 --- @type DjuiFontType
-FONT_COUNT = 7 --- @type DjuiFontType
+FONT_LEGACY = -1 --- @type DjuiFontType
+FONT_NORMAL = 0 --- @type DjuiFontType
+FONT_MENU = 1 --- @type DjuiFontType
+FONT_HUD = 2 --- @type DjuiFontType
+FONT_ALIASED = 3 --- @type DjuiFontType
+FONT_CUSTOM_HUD = 4 --- @type DjuiFontType
+FONT_RECOLOR_HUD = 5 --- @type DjuiFontType
+FONT_SPECIAL = 6 --- @type DjuiFontType
+FONT_COUNT = 7 --- @type DjuiFontType
--- @alias DjuiFontType
+--- | `FONT_LEGACY`
--- | `FONT_NORMAL`
--- | `FONT_MENU`
--- | `FONT_HUD`
diff --git a/autogen/lua_definitions/functions.lua b/autogen/lua_definitions/functions.lua
index f7b0aa63d..746a8f4c4 100644
--- a/autogen/lua_definitions/functions.lua
+++ b/autogen/lua_definitions/functions.lua
@@ -3856,7 +3856,9 @@ function djui_hud_reset_color()
-- ...
end
---- @return HudUtilsRotation
+--- @return integer rotation
+--- @return number pivotX
+--- @return number pivotY
--- Gets the current DJUI HUD rotation
function djui_hud_get_rotation()
-- ...
@@ -3881,6 +3883,29 @@ function djui_hud_set_rotation_interpolated(prevRotation, prevPivotX, prevPivotY
-- ...
end
+--- @return number textHAlign
+--- @return number textVAlign
+--- Gets the current DJUI HUD text alignment
+function djui_hud_get_text_alignment()
+ -- ...
+end
+
+--- @param textHAlign number
+--- @param textVAlign number
+--- Sets the current DJUI HUD text alignment
+function djui_hud_set_text_alignment(textHAlign, textVAlign)
+ -- ...
+end
+
+--- @param prevTextHAlign number
+--- @param prevTextVAlign number
+--- @param textHAlign number
+--- @param textVAlign number
+--- Sets the current DJUI HUD text alignment interpolated
+function djui_hud_set_text_alignment_interpolated(prevTextHAlign, prevTextVAlign, textHAlign, textVAlign)
+ -- ...
+end
+
--- @return integer
--- Gets the screen width in the current DJUI HUD resolution
function djui_hud_get_screen_width()
diff --git a/autogen/lua_definitions/structs.lua b/autogen/lua_definitions/structs.lua
index c971ce2ff..5441e7424 100644
--- a/autogen/lua_definitions/structs.lua
+++ b/autogen/lua_definitions/structs.lua
@@ -955,14 +955,6 @@
--- @field public translation Vec3s
--- @field public rotation Vec3s
---- @class HudUtilsRotation
---- @field public rotation number
---- @field public rotationDiff number
---- @field public prevPivotX number
---- @field public prevPivotY number
---- @field public pivotX number
---- @field public pivotY number
-
--- @class InstantWarp
--- @field public id integer
--- @field public area integer
diff --git a/docs/lua/constants.md b/docs/lua/constants.md
index 8f49768aa..0c92edac9 100644
--- a/docs/lua/constants.md
+++ b/docs/lua/constants.md
@@ -1149,6 +1149,18 @@
## [djui_hud_utils.h](#djui_hud_utils.h)
+- ROTATION_PIVOT_X_LEFT
+- ROTATION_PIVOT_X_CENTER
+- ROTATION_PIVOT_X_RIGHT
+- ROTATION_PIVOT_Y_TOP
+- ROTATION_PIVOT_Y_CENTER
+- ROTATION_PIVOT_Y_BOTTOM
+- TEXT_HALIGN_LEFT
+- TEXT_HALIGN_CENTER
+- TEXT_HALIGN_RIGHT
+- TEXT_VALIGN_TOP
+- TEXT_VALIGN_CENTER
+- TEXT_VALIGN_BOTTOM
### [enum HudUtilsResolution](#HudUtilsResolution)
| Identifier | Value |
@@ -1167,6 +1179,7 @@
### [enum DjuiFontType](#DjuiFontType)
| Identifier | Value |
| :--------- | :---- |
+| FONT_LEGACY | -1 |
| FONT_NORMAL | 0 |
| FONT_MENU | 1 |
| FONT_HUD | 2 |
diff --git a/docs/lua/functions-3.md b/docs/lua/functions-3.md
index 7b1704e71..8e4f8c230 100644
--- a/docs/lua/functions-3.md
+++ b/docs/lua/functions-3.md
@@ -1312,7 +1312,8 @@ Calculates and returns the pitch and yaw angles from one 3D position (`from`) to
| to | [Vec3f](structs.md#Vec3f) |
### Returns
-- None
+- `integer`
+- `integer`
### C Prototype
`void calculate_angles(Vec3f from, Vec3f to, RET s16 *pitch, RET s16 *yaw);`
@@ -1585,7 +1586,7 @@ Applies a roll-based shake effect to the camera. Simulates rotational disturbanc
| roll | `integer` |
### Returns
-- None
+- `integer`
### C Prototype
`void shake_camera_roll(INOUT s16 *roll);`
@@ -2825,7 +2826,7 @@ Gets the current DJUI HUD font
- `integer`
### C Prototype
-`u8 djui_hud_get_font(void);`
+`s8 djui_hud_get_font(void);`
[:arrow_up_small:](#)
@@ -2928,16 +2929,18 @@ Resets the current DJUI HUD color
Gets the current DJUI HUD rotation
### Lua Example
-`local hudUtilsRotationValue = djui_hud_get_rotation()`
+`local rotation, pivotX, pivotY = djui_hud_get_rotation()`
### Parameters
- None
### Returns
-- [HudUtilsRotation](structs.md#HudUtilsRotation)
+- `integer`
+- `number`
+- `number`
### C Prototype
-`struct HudUtilsRotation* djui_hud_get_rotation(void);`
+`void djui_hud_get_rotation(RET s16 *rotation, RET f32 *pivotX, RET f32 *pivotY);`
[:arrow_up_small:](#)
@@ -2990,7 +2993,79 @@ Sets the current DJUI HUD rotation interpolated
- None
### C Prototype
-`void djui_hud_set_rotation_interpolated(s32 prevRotation, f32 prevPivotX, f32 prevPivotY, s32 rotation, f32 pivotX, f32 pivotY);`
+`void djui_hud_set_rotation_interpolated(s16 prevRotation, f32 prevPivotX, f32 prevPivotY, s16 rotation, f32 pivotX, f32 pivotY);`
+
+[:arrow_up_small:](#)
+
+
+
+## [djui_hud_get_text_alignment](#djui_hud_get_text_alignment)
+
+### Description
+Gets the current DJUI HUD text alignment
+
+### Lua Example
+`local textHAlign, textVAlign = djui_hud_get_text_alignment()`
+
+### Parameters
+- None
+
+### Returns
+- `number`
+- `number`
+
+### C Prototype
+`void djui_hud_get_text_alignment(RET f32 *textHAlign, RET f32 *textVAlign);`
+
+[:arrow_up_small:](#)
+
+
+
+## [djui_hud_set_text_alignment](#djui_hud_set_text_alignment)
+
+### Description
+Sets the current DJUI HUD text alignment
+
+### Lua Example
+`djui_hud_set_text_alignment(textHAlign, textVAlign)`
+
+### Parameters
+| Field | Type |
+| ----- | ---- |
+| textHAlign | `number` |
+| textVAlign | `number` |
+
+### Returns
+- None
+
+### C Prototype
+`void djui_hud_set_text_alignment(f32 textHAlign, f32 textVAlign);`
+
+[:arrow_up_small:](#)
+
+
+
+## [djui_hud_set_text_alignment_interpolated](#djui_hud_set_text_alignment_interpolated)
+
+### Description
+Sets the current DJUI HUD text alignment interpolated
+
+### Lua Example
+`djui_hud_set_text_alignment_interpolated(prevTextHAlign, prevTextVAlign, textHAlign, textVAlign)`
+
+### Parameters
+| Field | Type |
+| ----- | ---- |
+| prevTextHAlign | `number` |
+| prevTextVAlign | `number` |
+| textHAlign | `number` |
+| textVAlign | `number` |
+
+### Returns
+- None
+
+### C Prototype
+`void djui_hud_set_text_alignment_interpolated(f32 prevTextHAlign, f32 prevTextVAlign, f32 textHAlign, f32 textVAlign);`
[:arrow_up_small:](#)
diff --git a/docs/lua/functions-4.md b/docs/lua/functions-4.md
index 6c8cb8c8b..4bd956272 100644
--- a/docs/lua/functions-4.md
+++ b/docs/lua/functions-4.md
@@ -5134,7 +5134,9 @@ Calculates the distance between two points in 3D space (`from` and `to`), as wel
| to | [Vec3f](structs.md#Vec3f) |
### Returns
-- None
+- `number`
+- `integer`
+- `integer`
### C Prototype
`void vec3f_get_dist_and_angle(Vec3f from, Vec3f to, RET f32 *dist, RET s16 *pitch, RET s16 *yaw);`
diff --git a/docs/lua/functions-5.md b/docs/lua/functions-5.md
index 507dd7401..91636f399 100644
--- a/docs/lua/functions-5.md
+++ b/docs/lua/functions-5.md
@@ -2945,7 +2945,7 @@ Determines an object's forward speed multiplier.
| floor_nY | `number` |
### Returns
-- None
+- `number`
### C Prototype
`void calc_obj_friction(RET f32 *objFriction, f32 floor_nY);`
@@ -4419,7 +4419,7 @@ Begin by increasing the current object's scale by `scaleVel`, and slowly decreas
| blinkLength | `integer` |
### Returns
-- None
+- `integer`
### C Prototype
`void obj_update_blinking(INOUT s32 *blinkTimer, s16 baseCycleLength, s16 cycleLengthRange, s16 blinkLength);`
@@ -4743,7 +4743,8 @@ Treats far home as Mario. Returns the distance and angle to the nearest player
| threshold | `number` |
### Returns
-- None
+- `integer`
+- `integer`
### C Prototype
`void treat_far_home_as_mario(f32 threshold, RET s32* distanceToPlayer, RET s32* angleToPlayer);`
diff --git a/docs/lua/functions-6.md b/docs/lua/functions-6.md
index afd96447e..41b220737 100644
--- a/docs/lua/functions-6.md
+++ b/docs/lua/functions-6.md
@@ -1963,7 +1963,7 @@ Marks an object to be unloaded at the end of the frame
| dragStrength | `number` |
### Returns
-- None
+- `number`
### C Prototype
`void apply_drag_to_value(INOUT f32 *value, f32 dragStrength);`
diff --git a/docs/lua/functions.md b/docs/lua/functions.md
index e2f94b46b..4076375df 100644
--- a/docs/lua/functions.md
+++ b/docs/lua/functions.md
@@ -763,6 +763,9 @@
- [djui_hud_get_rotation](functions-3.md#djui_hud_get_rotation)
- [djui_hud_set_rotation](functions-3.md#djui_hud_set_rotation)
- [djui_hud_set_rotation_interpolated](functions-3.md#djui_hud_set_rotation_interpolated)
+ - [djui_hud_get_text_alignment](functions-3.md#djui_hud_get_text_alignment)
+ - [djui_hud_set_text_alignment](functions-3.md#djui_hud_set_text_alignment)
+ - [djui_hud_set_text_alignment_interpolated](functions-3.md#djui_hud_set_text_alignment_interpolated)
- [djui_hud_get_screen_width](functions-3.md#djui_hud_get_screen_width)
- [djui_hud_get_screen_height](functions-3.md#djui_hud_get_screen_height)
- [djui_hud_get_mouse_x](functions-3.md#djui_hud_get_mouse_x)
diff --git a/docs/lua/structs.md b/docs/lua/structs.md
index f189b286d..79c6d19ff 100644
--- a/docs/lua/structs.md
+++ b/docs/lua/structs.md
@@ -53,7 +53,6 @@
- [GraphNodeSwitchCase](#GraphNodeSwitchCase)
- [GraphNodeTranslation](#GraphNodeTranslation)
- [GraphNodeTranslationRotation](#GraphNodeTranslationRotation)
-- [HudUtilsRotation](#HudUtilsRotation)
- [InstantWarp](#InstantWarp)
- [LakituState](#LakituState)
- [LevelValues](#LevelValues)
@@ -1427,21 +1426,6 @@
-## [HudUtilsRotation](#HudUtilsRotation)
-
-| Field | Type | Access |
-| ----- | ---- | ------ |
-| rotation | `number` | |
-| rotationDiff | `number` | |
-| prevPivotX | `number` | |
-| prevPivotY | `number` | |
-| pivotX | `number` | |
-| pivotY | `number` | |
-
-[:arrow_up_small:](#)
-
-
-
## [InstantWarp](#InstantWarp)
| Field | Type | Access |
diff --git a/src/pc/crash_handler.c b/src/pc/crash_handler.c
index 23301f0f2..470f4e2e6 100644
--- a/src/pc/crash_handler.c
+++ b/src/pc/crash_handler.c
@@ -235,6 +235,8 @@ static void crash_handler_produce_one_frame_callback(void) {
// render the line
f32 addX = 0;
char* c = text->s;
+
+ font->render_begin();
while (*c != '\0') {
f32 charWidth = 0.4f;
@@ -253,6 +255,7 @@ static void crash_handler_produce_one_frame_callback(void) {
create_dl_translation_matrix(DJUI_MTX_NOPUSH, charWidth, 0, 0);
c = djui_unicode_next_char(c);
}
+ font->render_end();
// pop
gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
diff --git a/src/pc/discord/discord_activity.c b/src/pc/discord/discord_activity.c
index 510ac440e..36745701c 100644
--- a/src/pc/discord/discord_activity.c
+++ b/src/pc/discord/discord_activity.c
@@ -122,11 +122,9 @@ void discord_activity_update(void) {
// HACK: give the detail population more space than the Discord details can fit so it gets truncated without cutting off the largest strings
char details[512] = { 0 };
- discord_populate_details(details, 512);
- char* detailsNoColor = str_remove_color_codes(details);
-
- snprintf(sCurActivity.details, 128, "%s", detailsNoColor);
- free(detailsNoColor);
+ discord_populate_details(details, ARRAY_COUNT(details));
+ djui_text_remove_colors(details);
+ snprintf(sCurActivity.details, 128, "%s", details);
if (!app.activities) {
LOG_INFO("no activities");
diff --git a/src/pc/djui/djui.c b/src/pc/djui/djui.c
index 5c79ff3c6..3bd9fc8b1 100644
--- a/src/pc/djui/djui.c
+++ b/src/pc/djui/djui.c
@@ -168,7 +168,8 @@ void djui_lua_error_clear(void) {
void djui_reset_hud_params(void) {
djui_hud_set_resolution(RESOLUTION_DJUI);
djui_hud_set_font(FONT_NORMAL);
- djui_hud_set_rotation(0, 0, 0);
+ djui_hud_set_rotation(0, ROTATION_PIVOT_X_LEFT, ROTATION_PIVOT_Y_TOP);
+ djui_hud_set_text_alignment(TEXT_HALIGN_LEFT, TEXT_VALIGN_TOP);
djui_hud_reset_color();
djui_hud_set_filter(FILTER_NEAREST);
djui_hud_reset_viewport();
diff --git a/src/pc/djui/djui_font.c b/src/pc/djui/djui_font.c
index f2d88cc88..4b649d784 100644
--- a/src/pc/djui/djui_font.c
+++ b/src/pc/djui/djui_font.c
@@ -7,7 +7,7 @@
// font 0 (built-in normal font) //
///////////////////////////////////
-static void djui_font_normal_render_char(char* c) {
+static void djui_font_normal_render_char(const char* c) {
// replace undisplayable characters
if (*c == ' ') { return; }
@@ -18,16 +18,16 @@ static void djui_font_normal_render_char(char* c) {
u32 tx = index % 64;
u32 ty = index / 64;
extern ALIGNED8 const Texture texture_font_jp[];
- djui_gfx_render_texture_tile(texture_font_jp, 512, 1024, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 8, ty * 16, 8, 16, false, true);
+ djui_gfx_render_texture_tile_font(texture_font_jp, 512, 1024, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 8, ty * 16, 8, 16);
} else {
u32 tx = index % 32;
u32 ty = index / 32;
extern ALIGNED8 const Texture texture_font_normal[];
- djui_gfx_render_texture_tile(texture_font_normal, 256, 128, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 8, ty * 16, 8, 16, false, true);
+ djui_gfx_render_texture_tile_font(texture_font_normal, 256, 128, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 8, ty * 16, 8, 16);
}
}
-static f32 djui_font_normal_char_width(char* c) {
+static f32 djui_font_normal_char_width(const char* c) {
if (*c == ' ') { return configExCoopTheme ? 6 / 32.0f : 0.30f; }
extern const f32 font_normal_widths[];
return djui_unicode_get_sprite_width(c, font_normal_widths, 32.0f);
@@ -41,7 +41,9 @@ static const struct DjuiFont sDjuiFontNormal = {
.yOffset = 0.0f,
.defaultFontScale = 32.0f,
.textBeginDisplayList = NULL,
+ .render_begin = djui_gfx_render_texture_tile_font_begin,
.render_char = djui_font_normal_render_char,
+ .render_end = djui_gfx_render_texture_tile_font_end,
.char_width = djui_font_normal_char_width,
};
@@ -49,7 +51,7 @@ static const struct DjuiFont sDjuiFontNormal = {
// font 1 (custom title font) //
////////////////////////////////
-static void djui_font_title_render_char(char* c) {
+static void djui_font_title_render_char(const char* c) {
// replace undisplayable characters
if (*c == ' ') { return; }
@@ -64,10 +66,10 @@ static void djui_font_title_render_char(char* c) {
u32 ty = index / 16;
extern ALIGNED8 const Texture texture_font_title[];
- djui_gfx_render_texture_tile(texture_font_title, 1024, 512, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 64, ty * 64, 64, 64, false, true);
+ djui_gfx_render_texture_tile_font(texture_font_title, 1024, 512, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 64, ty * 64, 64, 64);
}
-static f32 djui_font_title_char_width(char* text) {
+static f32 djui_font_title_char_width(const char* text) {
char c = *text;
if (c == ' ') { return 0.30f; }
c = djui_unicode_get_base_char(text);
@@ -83,7 +85,9 @@ static const struct DjuiFont sDjuiFontTitle = {
.yOffset = 0.0f,
.defaultFontScale = 64.0f,
.textBeginDisplayList = NULL,
+ .render_begin = djui_gfx_render_texture_tile_font_begin,
.render_char = djui_font_title_render_char,
+ .render_end = djui_gfx_render_texture_tile_font_end,
.char_width = djui_font_title_char_width,
};
@@ -124,27 +128,29 @@ static u8 djui_font_hud_index(char c) {
return c;
}
-static void djui_font_hud_render_char(char* text) {
+static void djui_font_hud_render_char(const char* text) {
char c = *text;
if (c == ' ') { return; }
c = djui_unicode_get_base_char(text);
u8 index = djui_font_hud_index(c);
- djui_gfx_render_texture(main_hud_lut[index], 16, 16, G_IM_FMT_RGBA, G_IM_SIZ_16b, djui_hud_get_filter());
+ djui_gfx_render_texture_font(main_hud_lut[index], 16, 16, G_IM_FMT_RGBA, G_IM_SIZ_16b);
}
-static f32 djui_font_hud_char_width(UNUSED char* text) {
+static f32 djui_font_hud_char_width(UNUSED const char* text) {
return 0.75f;
}
static const struct DjuiFont sDjuiFontHud = {
.charWidth = 1.0f,
.charHeight = 0.9f,
- .lineHeight = 0.7f,
+ .lineHeight = 1.25f,
.xOffset = 0.0f,
.yOffset = 0.0f,
.defaultFontScale = 16.0f,
.textBeginDisplayList = NULL,
+ .render_begin = djui_gfx_render_texture_font_begin,
.render_char = djui_font_hud_render_char,
+ .render_end = djui_gfx_render_texture_font_end,
.char_width = djui_font_hud_char_width,
};
@@ -152,7 +158,7 @@ static const struct DjuiFont sDjuiFontHud = {
// font 3 (DJ's aliased font) //
////////////////////////////////
-static void djui_font_aliased_render_char(char* c) {
+static void djui_font_aliased_render_char(const char* c) {
// replace undisplayable characters
if (*c == ' ') { return; }
@@ -163,16 +169,16 @@ static void djui_font_aliased_render_char(char* c) {
u32 tx = index % 64;
u32 ty = index / 64;
extern ALIGNED8 const Texture texture_font_jp_aliased[];
- djui_gfx_render_texture_tile(texture_font_jp_aliased, 1024, 2048, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 16, ty * 32, 16, 32, false, true);
+ djui_gfx_render_texture_tile_font(texture_font_jp_aliased, 1024, 2048, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 16, ty * 32, 16, 32);
} else {
u32 tx = index % 32;
u32 ty = index / 32;
extern ALIGNED8 const Texture texture_font_aliased[];
- djui_gfx_render_texture_tile(texture_font_aliased, 512, 256, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 16, ty * 32, 16, 32, false, true);
+ djui_gfx_render_texture_tile_font(texture_font_aliased, 512, 256, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 16, ty * 32, 16, 32);
}
}
-static f32 djui_font_aliased_char_width(char* c) {
+static f32 djui_font_aliased_char_width(const char* c) {
if (*c == ' ') { return 6 / 32.0f; }
extern const f32 font_aliased_widths[];
return djui_unicode_get_sprite_width(c, font_aliased_widths, 1.0f) / 32.0f;
@@ -186,7 +192,9 @@ static const struct DjuiFont sDjuiFontAliased = {
.lineHeight = 0.8125f,
.defaultFontScale = 32.0f,
.textBeginDisplayList = NULL,
+ .render_begin = djui_gfx_render_texture_tile_font_begin,
.render_char = djui_font_aliased_render_char,
+ .render_end = djui_gfx_render_texture_tile_font_end,
.char_width = djui_font_aliased_char_width,
};
@@ -194,7 +202,7 @@ static const struct DjuiFont sDjuiFontAliased = {
// font 4/5 (custom hud font/recolor) //
////////////////////////////////////////
-static void djui_font_custom_hud_render_char(char* c) {
+static void djui_font_custom_hud_render_char(const char* c) {
// replace undisplayable characters
if (*c == ' ') { return; }
@@ -204,10 +212,10 @@ static void djui_font_custom_hud_render_char(char* c) {
u32 ty = index / 16;
extern ALIGNED8 const Texture texture_font_hud[];
- djui_gfx_render_texture_tile(texture_font_hud, 512, 512, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 32, ty * 32, 32, 32, false, true);
+ djui_gfx_render_texture_tile_font(texture_font_hud, 512, 512, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 32, ty * 32, 32, 32);
}
-static void djui_font_custom_hud_recolor_render_char(char* c) {
+static void djui_font_custom_hud_recolor_render_char(const char* c) {
// replace undisplayable characters
if (*c == ' ') { return; }
@@ -217,10 +225,10 @@ static void djui_font_custom_hud_recolor_render_char(char* c) {
u32 ty = index / 16;
extern ALIGNED8 const Texture texture_font_hud_recolor[];
- djui_gfx_render_texture_tile(texture_font_hud_recolor, 512, 512, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 32, ty * 32, 32, 32, false, true);
+ djui_gfx_render_texture_tile_font(texture_font_hud_recolor, 512, 512, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 32, ty * 32, 32, 32);
}
-static f32 djui_font_custom_hud_char_width(char* text) {
+static f32 djui_font_custom_hud_char_width(const char* text) {
char c = *text;
if (c == ' ') { return 0.3750f; }
c = djui_unicode_get_base_char(text);
@@ -236,7 +244,9 @@ static const struct DjuiFont sDjuiFontCustomHud = {
.yOffset = -10.25f,
.defaultFontScale = 32.0f,
.textBeginDisplayList = NULL,
+ .render_begin = djui_gfx_render_texture_tile_font_begin,
.render_char = djui_font_custom_hud_render_char,
+ .render_end = djui_gfx_render_texture_tile_font_end,
.char_width = djui_font_custom_hud_char_width,
};
@@ -248,7 +258,9 @@ static const struct DjuiFont sDjuiFontCustomHudRecolor = {
.yOffset = -10.25f,
.defaultFontScale = 32.0f,
.textBeginDisplayList = NULL,
+ .render_begin = djui_gfx_render_texture_tile_font_begin,
.render_char = djui_font_custom_hud_recolor_render_char,
+ .render_end = djui_gfx_render_texture_tile_font_end,
.char_width = djui_font_custom_hud_char_width,
};
@@ -256,7 +268,7 @@ static const struct DjuiFont sDjuiFontCustomHudRecolor = {
// font 6 (special font) //
///////////////////////////
-static void djui_font_special_render_char(char* c) {
+static void djui_font_special_render_char(const char* c) {
// replace undisplayable characters
if (*c == ' ') { return; }
@@ -266,17 +278,17 @@ static void djui_font_special_render_char(char* c) {
u32 tx = index % 64;
u32 ty = index / 64;
extern ALIGNED8 const Texture texture_font_jp[];
- djui_gfx_render_texture_tile(texture_font_jp, 512, 1024, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 8, ty * 16, 8, 16, false, true);
+ djui_gfx_render_texture_tile_font(texture_font_jp, 512, 1024, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 8, ty * 16, 8, 16);
} else {
u32 tx = index % 32;
u32 ty = index / 32;
extern ALIGNED8 const Texture texture_font_special[];
- djui_gfx_render_texture_tile(texture_font_special, 256, 128, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 8, ty * 16, 8, 16, false, true);
+ djui_gfx_render_texture_tile_font(texture_font_special, 256, 128, G_IM_FMT_RGBA, G_IM_SIZ_32b, tx * 8, ty * 16, 8, 16);
}
}
-static f32 djui_font_special_char_width(char* c) {
+static f32 djui_font_special_char_width(const char* c) {
if (*c == ' ') { return 0.5f; }
extern const f32 font_special_widths[];
return djui_unicode_get_sprite_width(c, font_special_widths, 32.0f);
@@ -290,7 +302,9 @@ static const struct DjuiFont sDjuiFontSpecial = {
.yOffset = 0.0f,
.defaultFontScale = 32.0f,
.textBeginDisplayList = NULL,
+ .render_begin = djui_gfx_render_texture_tile_font_begin,
.render_char = djui_font_special_render_char,
+ .render_end = djui_gfx_render_texture_tile_font_end,
.char_width = djui_font_special_char_width,
};
diff --git a/src/pc/djui/djui_font.h b/src/pc/djui/djui_font.h
index 02d3eef8a..7acf72a28 100644
--- a/src/pc/djui/djui_font.h
+++ b/src/pc/djui/djui_font.h
@@ -9,8 +9,10 @@ struct DjuiFont {
f32 yOffset;
f32 defaultFontScale;
const Gfx* textBeginDisplayList;
- void (*render_char)(char*);
- f32 (*char_width)(char*);
+ void (*render_begin)();
+ void (*render_char)(const char*);
+ void (*render_end)();
+ f32 (*char_width)(const char*);
};
extern const struct DjuiFont* gDjuiFonts[];
diff --git a/src/pc/djui/djui_gfx.c b/src/pc/djui/djui_gfx.c
index 8ebd6c488..4319782c3 100644
--- a/src/pc/djui/djui_gfx.c
+++ b/src/pc/djui/djui_gfx.c
@@ -1,6 +1,7 @@
#include
#include "sm64.h"
#include "djui.h"
+#include "djui_hud_utils.h"
#include "game/ingame_menu.h"
#include "game/segment2.h"
#include "pc/pc_main.h"
@@ -118,7 +119,93 @@ void djui_gfx_render_texture(const Texture* texture, u32 w, u32 h, u8 fmt, u8 si
gSPDisplayList(gDisplayListHead++, dl_djui_image);
}
-void djui_gfx_render_texture_tile(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz, u32 tileX, u32 tileY, u32 tileW, u32 tileH, bool filter, bool font) {
+void djui_gfx_render_texture_tile(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz, u32 tileX, u32 tileY, u32 tileW, u32 tileH, bool filter) {
+ if (!gDisplayListHead) {
+ LOG_ERROR("Retrieved a null displaylist head");
+ return;
+ }
+
+ if (!texture) {
+ LOG_ERROR("Attempted to render null texture");
+ return;
+ }
+
+ Vtx *vtx = alloc_display_list(sizeof(Vtx) * 4);
+ if (!vtx) {
+ LOG_ERROR("Failed to allocate vertices");
+ return;
+ }
+
+ f32 aspect = tileH ? ((f32)tileW / (f32)tileH) : 1;
+
+ vtx[0] = (Vtx) {{{ 0, -1, 0 }, 0, { ( tileX * 2048.0f) / (f32)w + 1, ((tileY + tileH) * 2048.0f) / (f32)h + 1 }, { 0xff, 0xff, 0xff, 0xff }}};
+ vtx[2] = (Vtx) {{{ 1 * aspect, 0, 0 }, 0, { ((tileX + tileW) * 2048.0f) / (f32)w + 1, ( tileY * 2048.0f) / (f32)h + 1 }, { 0xff, 0xff, 0xff, 0xff }}};
+ vtx[1] = (Vtx) {{{ 1 * aspect, -1, 0 }, 0, { ((tileX + tileW) * 2048.0f) / (f32)w + 1, ((tileY + tileH) * 2048.0f) / (f32)h + 1 }, { 0xff, 0xff, 0xff, 0xff }}};
+ vtx[3] = (Vtx) {{{ 0, 0, 0 }, 0, { ( tileX * 2048.0f) / (f32)w + 1, ( tileY * 2048.0f) / (f32)h + 1 }, { 0xff, 0xff, 0xff, 0xff }}};
+
+ gSPClearGeometryMode(gDisplayListHead++, G_LIGHTING | G_CULL_BOTH);
+ gDPSetCombineMode(gDisplayListHead++, G_CC_FADEA, G_CC_FADEA);
+ gDPSetRenderMode(gDisplayListHead++, G_RM_XLU_SURF, G_RM_XLU_SURF2);
+ gDPSetTextureFilter(gDisplayListHead++, filter ? G_TF_BILERP : G_TF_POINT);
+
+ gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON);
+
+ gDPSetTextureOverrideDjui(gDisplayListHead++, texture, djui_gfx_power_of_two(w), djui_gfx_power_of_two(h), fmt, siz);
+ gDPLoadTextureBlockWithoutTexture(gDisplayListHead++, NULL, G_IM_FMT_RGBA, G_IM_SIZ_16b, 64, 64, 0, G_TX_CLAMP, G_TX_CLAMP, 0, 0, 0, 0);
+
+ *(gDisplayListHead++) = (Gfx) gsSPExecuteDjui(G_TEXOVERRIDE_DJUI);
+
+ gSPVertexNonGlobal(gDisplayListHead++, vtx, 4, 0);
+ *(gDisplayListHead++) = (Gfx) gsSPExecuteDjui(G_TEXCLIP_DJUI);
+ gSP2TrianglesDjui(gDisplayListHead++, 0, 1, 2, 0x0, 0, 2, 3, 0x0);
+
+ gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_OFF);
+ gDPSetCombineMode(gDisplayListHead++, G_CC_SHADE, G_CC_SHADE);
+ gSPSetGeometryMode(gDisplayListHead++, G_LIGHTING | G_CULL_BACK);
+}
+
+void djui_gfx_render_texture_font_begin() {
+ gSPClearGeometryMode(gDisplayListHead++, G_LIGHTING | G_CULL_BOTH);
+ gDPSetCombineMode(gDisplayListHead++, G_CC_FADEA, G_CC_FADEA);
+ gDPSetRenderMode(gDisplayListHead++, G_RM_XLU_SURF, G_RM_XLU_SURF2);
+ gDPSetTextureFilter(gDisplayListHead++, djui_hud_get_filter() ? G_TF_BILERP : G_TF_POINT);
+ gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON);
+ gDPLoadTextureBlockWithoutTexture(gDisplayListHead++, NULL, G_IM_FMT_RGBA, G_IM_SIZ_16b, 64, 64, 0, G_TX_CLAMP, G_TX_CLAMP, 0, 0, 0, 0);
+}
+
+void djui_gfx_render_texture_font(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz) {
+ if (!gDisplayListHead) {
+ LOG_ERROR("Retrieved a null displaylist head");
+ return;
+ }
+
+ if (!texture) {
+ LOG_ERROR("Attempted to render null texture");
+ return;
+ }
+
+ gDPSetTextureOverrideDjui(gDisplayListHead++, texture, djui_gfx_power_of_two(w), djui_gfx_power_of_two(h), fmt, siz);
+ *(gDisplayListHead++) = (Gfx) gsSPExecuteDjui(G_TEXOVERRIDE_DJUI);
+ gSPVertexNonGlobal(gDisplayListHead++, vertex_djui_image, 4, 0);
+ gSP2Triangles(gDisplayListHead++, 0, 1, 2, 0x0, 0, 2, 3, 0x0);
+}
+
+void djui_gfx_render_texture_font_end() {
+ gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_OFF);
+ gDPSetCombineMode(gDisplayListHead++, G_CC_SHADE, G_CC_SHADE);
+ gSPSetGeometryMode(gDisplayListHead++, G_LIGHTING | G_CULL_BACK);
+}
+
+void djui_gfx_render_texture_tile_font_begin() {
+ gSPClearGeometryMode(gDisplayListHead++, G_LIGHTING | G_CULL_BOTH);
+ gDPSetCombineMode(gDisplayListHead++, G_CC_FADEA, G_CC_FADEA);
+ gDPSetRenderMode(gDisplayListHead++, G_RM_XLU_SURF, G_RM_XLU_SURF2);
+ gDPSetTextureFilter(gDisplayListHead++, G_TF_POINT);
+ gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON);
+ gDPLoadTextureBlockWithoutTexture(gDisplayListHead++, NULL, G_IM_FMT_RGBA, G_IM_SIZ_16b, 64, 64, 0, G_TX_CLAMP, G_TX_CLAMP, 0, 0, 0, 0);
+}
+
+void djui_gfx_render_texture_tile_font(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz, u32 tileX, u32 tileY, u32 tileW, u32 tileH) {
if (!gDisplayListHead) {
LOG_ERROR("Retrieved a null displaylist head");
return;
@@ -139,29 +226,21 @@ void djui_gfx_render_texture_tile(const Texture* texture, u32 w, u32 h, u8 fmt,
// I don't know why adding 1 to all of the UVs seems to fix rendering, but it does...
// this should be tested carefully. it definitely fixes some stuff, but what does it break?
- f32 offsetX = (font ? -1024.0f / (f32)w : 0) + 1;
- f32 offsetY = (font ? -1024.0f / (f32)h : 0) + 1;
+ f32 offsetX = (-1024.0f / (f32)w) + 1;
+ f32 offsetY = (-1024.0f / (f32)h) + 1;
vtx[0] = (Vtx) {{{ 0, -1, 0 }, 0, { ( tileX * 2048.0f) / (f32)w + offsetX, ((tileY + tileH) * 2048.0f) / (f32)h + offsetY }, { 0xff, 0xff, 0xff, 0xff }}};
vtx[2] = (Vtx) {{{ 1 * aspect, 0, 0 }, 0, { ((tileX + tileW) * 2048.0f) / (f32)w + offsetX, ( tileY * 2048.0f) / (f32)h + offsetY }, { 0xff, 0xff, 0xff, 0xff }}};
vtx[1] = (Vtx) {{{ 1 * aspect, -1, 0 }, 0, { ((tileX + tileW) * 2048.0f) / (f32)w + offsetX, ((tileY + tileH) * 2048.0f) / (f32)h + offsetY }, { 0xff, 0xff, 0xff, 0xff }}};
vtx[3] = (Vtx) {{{ 0, 0, 0 }, 0, { ( tileX * 2048.0f) / (f32)w + offsetX, ( tileY * 2048.0f) / (f32)h + offsetY }, { 0xff, 0xff, 0xff, 0xff }}};
- gSPClearGeometryMode(gDisplayListHead++, G_LIGHTING | G_CULL_BOTH);
- gDPSetCombineMode(gDisplayListHead++, G_CC_FADEA, G_CC_FADEA);
- gDPSetRenderMode(gDisplayListHead++, G_RM_XLU_SURF, G_RM_XLU_SURF2);
- gDPSetTextureFilter(gDisplayListHead++, filter ? G_TF_BILERP : G_TF_POINT);
-
- gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON);
-
gDPSetTextureOverrideDjui(gDisplayListHead++, texture, djui_gfx_power_of_two(w), djui_gfx_power_of_two(h), fmt, siz);
- gDPLoadTextureBlockWithoutTexture(gDisplayListHead++, NULL, G_IM_FMT_RGBA, G_IM_SIZ_16b, 64, 64, 0, G_TX_CLAMP, G_TX_CLAMP, 0, 0, 0, 0);
-
*(gDisplayListHead++) = (Gfx) gsSPExecuteDjui(G_TEXOVERRIDE_DJUI);
-
gSPVertexNonGlobal(gDisplayListHead++, vtx, 4, 0);
*(gDisplayListHead++) = (Gfx) gsSPExecuteDjui(G_TEXCLIP_DJUI);
gSP2TrianglesDjui(gDisplayListHead++, 0, 1, 2, 0x0, 0, 2, 3, 0x0);
+}
+void djui_gfx_render_texture_tile_font_end() {
gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_OFF);
gDPSetCombineMode(gDisplayListHead++, G_CC_SHADE, G_CC_SHADE);
gSPSetGeometryMode(gDisplayListHead++, G_LIGHTING | G_CULL_BACK);
diff --git a/src/pc/djui/djui_gfx.h b/src/pc/djui/djui_gfx.h
index 8da361e3f..743ca4460 100644
--- a/src/pc/djui/djui_gfx.h
+++ b/src/pc/djui/djui_gfx.h
@@ -16,7 +16,15 @@ void djui_gfx_displaylist_end(void);
f32 djui_gfx_get_scale(void);
void djui_gfx_render_texture(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz, bool filter);
-void djui_gfx_render_texture_tile(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz, u32 tileX, u32 tileY, u32 tileW, u32 tileH, bool filter, bool font);
+void djui_gfx_render_texture_tile(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz, u32 tileX, u32 tileY, u32 tileW, u32 tileH, bool filter);
+
+void djui_gfx_render_texture_font_begin();
+void djui_gfx_render_texture_font(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz);
+void djui_gfx_render_texture_font_end();
+
+void djui_gfx_render_texture_tile_font_begin();
+void djui_gfx_render_texture_tile_font(const Texture* texture, u32 w, u32 h, u8 fmt, u8 siz, u32 tileX, u32 tileY, u32 tileW, u32 tileH);
+void djui_gfx_render_texture_tile_font_end();
void gfx_get_dimensions(u32* width, u32* height);
diff --git a/src/pc/djui/djui_hud_utils.c b/src/pc/djui/djui_hud_utils.c
index 5d7a06d5b..8ad0a5b15 100644
--- a/src/pc/djui/djui_hud_utils.c
+++ b/src/pc/djui/djui_hud_utils.c
@@ -24,13 +24,45 @@
#include "engine/math_util.h"
-static enum HudUtilsResolution sResolution = RESOLUTION_DJUI;
-static enum HudUtilsFilter sFilter = FILTER_NEAREST;
-static enum DjuiFontType sFont = FONT_NORMAL;
-static struct HudUtilsRotation sRotation = { 0, 0, 0, 0, 0, 0 };
-static struct DjuiColor sColor = { 255, 255, 255, 255 };
+#define INTERP_INIT(v) {v, v}
+
+typedef struct {
+ f32 prev, curr;
+} InterpFieldF32;
+
+struct HudUtilsState {
+ enum HudUtilsResolution resolution;
+ enum HudUtilsFilter filter;
+ enum DjuiFontType font;
+ struct DjuiColor color;
+ struct {
+ InterpFieldF32 degrees;
+ InterpFieldF32 pivotX;
+ InterpFieldF32 pivotY;
+ } rotation;
+ struct {
+ InterpFieldF32 h;
+ InterpFieldF32 v;
+ } textAlignment;
+};
+
+static struct HudUtilsState sHudUtilsState = {
+ .resolution = RESOLUTION_DJUI,
+ .filter = FILTER_NEAREST,
+ .font = FONT_NORMAL,
+ .color = { 255, 255, 255, 255 },
+ .rotation = {
+ .degrees = INTERP_INIT(0),
+ .pivotX = INTERP_INIT(ROTATION_PIVOT_X_LEFT),
+ .pivotY = INTERP_INIT(ROTATION_PIVOT_Y_TOP),
+ },
+ .textAlignment = {
+ .h = INTERP_INIT(TEXT_HALIGN_LEFT),
+ .v = INTERP_INIT(TEXT_VALIGN_TOP),
+ },
+};
+
static struct DjuiColor sRefColor = { 255, 255, 255, 255 };
-static bool sLegacy = false;
f32 gDjuiHudUtilsZ = 0;
bool gDjuiHudLockMouse = false;
@@ -67,8 +99,16 @@ struct GlobalTextures gGlobalTextures = {
.wario_head = { .texture = texture_hud_char_wario_head, "texture_hud_char_wario_head", .width = 16, .height = 16, .format = G_IM_FMT_RGBA, .size = G_IM_SIZ_16b }
};
+static inline const struct DjuiFont *djui_hud_get_text_font() {
+ return gDjuiFonts[sHudUtilsState.font < 0 ? FONT_NORMAL : sHudUtilsState.font];
+}
+
+static inline bool djui_hud_text_font_is_legacy() {
+ return sHudUtilsState.font < 0;
+}
+
static void djui_hud_position_translate(f32* x, f32* y) {
- if (sResolution == RESOLUTION_DJUI) {
+ if (sHudUtilsState.resolution == RESOLUTION_DJUI) {
djui_gfx_position_translate(x, y);
} else {
*x = GFX_DIMENSIONS_FROM_LEFT_EDGE(0) + *x;
@@ -77,7 +117,7 @@ static void djui_hud_position_translate(f32* x, f32* y) {
}
static void djui_hud_size_translate(f32* size) {
- if (sResolution == RESOLUTION_DJUI) {
+ if (sHudUtilsState.resolution == RESOLUTION_DJUI) {
djui_gfx_size_translate(size);
}
}
@@ -90,7 +130,7 @@ static void djui_hud_translate_positions(f32 *outX, f32 *outY, f32 *outW, f32 *o
*outY -= SCREEN_HEIGHT;
// translate scale
- if (sResolution == RESOLUTION_DJUI) {
+ if (sHudUtilsState.resolution == RESOLUTION_DJUI) {
u32 windowWidth, windowHeight;
gfx_get_dimensions(&windowWidth, &windowHeight);
f32 screenWidth = (f32) windowWidth / djui_gfx_get_scale();
@@ -105,22 +145,31 @@ static void djui_hud_translate_positions(f32 *outX, f32 *outY, f32 *outW, f32 *o
////////////
#define MAX_INTERP_HUD 512
-struct InterpHud {
- Gfx* headPos;
- f32 z;
- f32 prevX;
- f32 prevY;
- f32 x;
- f32 y;
- f32 prevScaleW;
- f32 prevScaleH;
- f32 scaleW;
- f32 scaleH;
- f32 width;
- f32 height;
- enum HudUtilsResolution resolution;
- struct HudUtilsRotation rotation;
+
+enum InterpHudType {
+ INTERP_HUD_TRANSLATION,
+ INTERP_HUD_ROTATION,
+ INTERP_HUD_SCALE,
+ INTERP_HUD_HALIGN,
+ INTERP_HUD_VALIGN,
+ INTERP_HUD_NEW_LINE,
};
+
+typedef struct {
+ enum InterpHudType type;
+ Gfx *pos;
+ f32 params[1]; // we don't need more for now
+} InterpHudGfx;
+
+struct InterpHud {
+ f32 z;
+ InterpFieldF32 posX, posY;
+ InterpFieldF32 scaleX, scaleY;
+ f32 width, height;
+ struct HudUtilsState state;
+ struct GrowingArray *gfx;
+};
+
static struct InterpHud sInterpHuds[MAX_INTERP_HUD] = { 0 };
static u16 sInterpHudCount = 0;
static u8 sColorAltered = FALSE;
@@ -132,140 +181,211 @@ void patch_djui_hud_before(void) {
void patch_djui_hud(f32 delta) {
f32 savedZ = gDjuiHudUtilsZ;
Gfx* savedHeadPos = gDisplayListHead;
- enum HudUtilsResolution savedResolution = sResolution;
- struct HudUtilsRotation savedRotation = sRotation;
+ struct HudUtilsState savedState = sHudUtilsState;
+
for (u16 i = 0; i < sInterpHudCount; i++) {
struct InterpHud* interp = &sInterpHuds[i];
- f32 x = delta_interpolate_f32(interp->prevX, interp->x, delta);
- f32 y = delta_interpolate_f32(interp->prevY, interp->y, delta);
- f32 scaleW = delta_interpolate_f32(interp->prevScaleW, interp->scaleW, delta);
- f32 scaleH = delta_interpolate_f32(interp->prevScaleH, interp->scaleH, delta);
- sResolution = interp->resolution;
- sRotation = interp->rotation;
+ f32 x = delta_interpolate_f32(interp->posX.prev, interp->posX.curr, delta);
+ f32 y = delta_interpolate_f32(interp->posY.prev, interp->posY.curr, delta);
+ f32 scaleW = delta_interpolate_f32(interp->scaleX.prev, interp->scaleX.curr, delta);
+ f32 scaleH = delta_interpolate_f32(interp->scaleY.prev, interp->scaleY.curr, delta);
+ sHudUtilsState = interp->state;
gDjuiHudUtilsZ = interp->z;
- gDisplayListHead = interp->headPos;
- // translate position
- f32 translatedX = x;
- f32 translatedY = y;
- djui_hud_position_translate(&translatedX, &translatedY);
- create_dl_translation_matrix(DJUI_MTX_PUSH, translatedX, translatedY, gDjuiHudUtilsZ);
+ for (u32 j = 0; j != interp->gfx->count; ++j) {
+ const InterpHudGfx *gfx = interp->gfx->buffer[j];
+ gDisplayListHead = gfx->pos;
- // rotate
- f32 translatedW = scaleW;
- f32 translatedH = scaleH;
- djui_hud_size_translate(&translatedW);
- djui_hud_size_translate(&translatedH);
- if (sRotation.rotationDiff != 0 || sRotation.rotation != 0) {
- s32 rotation = delta_interpolate_s32(sRotation.rotation - sRotation.rotationDiff, sRotation.rotation, delta);
- f32 pivotX = delta_interpolate_f32(sRotation.prevPivotX, sRotation.pivotX, delta);
- f32 pivotY = delta_interpolate_f32(sRotation.prevPivotY, sRotation.pivotY, delta);
- f32 pivotTranslationX = interp->width * translatedW * pivotX;
- f32 pivotTranslationY = interp->height * translatedH * pivotY;
- create_dl_translation_matrix(DJUI_MTX_NOPUSH, +pivotTranslationX, -pivotTranslationY, 0);
- create_dl_rotation_matrix(DJUI_MTX_NOPUSH, rotation, 0, 0, 1);
- create_dl_translation_matrix(DJUI_MTX_NOPUSH, -pivotTranslationX, +pivotTranslationY, 0);
+ switch (gfx->type) {
+ case INTERP_HUD_TRANSLATION: {
+ f32 translatedX = x;
+ f32 translatedY = y;
+ djui_hud_position_translate(&translatedX, &translatedY);
+ create_dl_translation_matrix(DJUI_MTX_PUSH, translatedX, translatedY, gDjuiHudUtilsZ);
+ } break;
+
+ case INTERP_HUD_ROTATION: {
+ if (sHudUtilsState.rotation.degrees.prev != 0 || sHudUtilsState.rotation.degrees.curr != 0) {
+ f32 translatedW = scaleW;
+ f32 translatedH = scaleH;
+ djui_hud_size_translate(&translatedW);
+ djui_hud_size_translate(&translatedH);
+ s16 rotPrev = degrees_to_sm64(sHudUtilsState.rotation.degrees.prev);
+ s16 rotCurr = degrees_to_sm64(sHudUtilsState.rotation.degrees.curr);
+ s32 normalizedDiff = (((s32) rotCurr - (s32) rotPrev + 0x8000) & 0xFFFF) - 0x8000; // Fix modular overflow/underflow
+ s32 rotation = delta_interpolate_s32(rotCurr - normalizedDiff, rotCurr, delta);
+ f32 pivotX = delta_interpolate_f32(sHudUtilsState.rotation.pivotX.prev, sHudUtilsState.rotation.pivotX.curr, delta);
+ f32 pivotY = delta_interpolate_f32(sHudUtilsState.rotation.pivotY.prev, sHudUtilsState.rotation.pivotY.curr, delta);
+ f32 pivotTranslationX = interp->width * translatedW * pivotX;
+ f32 pivotTranslationY = interp->height * translatedH * pivotY;
+ create_dl_translation_matrix(DJUI_MTX_NOPUSH, +pivotTranslationX, -pivotTranslationY, 0);
+ create_dl_rotation_matrix(DJUI_MTX_NOPUSH, sm64_to_degrees(rotation), 0, 0, 1);
+ create_dl_translation_matrix(DJUI_MTX_NOPUSH, -pivotTranslationX, +pivotTranslationY, 0);
+ }
+ } break;
+
+ case INTERP_HUD_SCALE: {
+ f32 translatedW = scaleW;
+ f32 translatedH = scaleH;
+ djui_hud_size_translate(&translatedW);
+ djui_hud_size_translate(&translatedH);
+ create_dl_scale_matrix(DJUI_MTX_NOPUSH, interp->width * translatedW, interp->height * translatedH, 1.0f);
+ } break;
+
+ case INTERP_HUD_HALIGN: {
+ f32 textHAlign = delta_interpolate_f32(sHudUtilsState.textAlignment.h.prev, sHudUtilsState.textAlignment.h.curr, delta);
+ f32 lineWidth = gfx->params[0];
+ create_dl_translation_matrix(DJUI_MTX_NOPUSH, -lineWidth * textHAlign, 0, 0);
+ } break;
+
+ case INTERP_HUD_VALIGN: {
+ f32 textVAlign = delta_interpolate_f32(sHudUtilsState.textAlignment.v.prev, sHudUtilsState.textAlignment.v.curr, delta);
+ f32 textHeight = gfx->params[0];
+ create_dl_translation_matrix(DJUI_MTX_NOPUSH, 0, textHeight * textVAlign, 0);
+ } break;
+
+ case INTERP_HUD_NEW_LINE: {
+ const struct DjuiFont *font = djui_hud_get_text_font();
+ f32 textHAlign = delta_interpolate_f32(sHudUtilsState.textAlignment.h.prev, sHudUtilsState.textAlignment.h.curr, delta);
+ f32 lineWidth = gfx->params[0];
+ create_dl_translation_matrix(DJUI_MTX_NOPUSH, -lineWidth * (1.f - textHAlign), -font->lineHeight, 0);
+ } break;
+ }
}
-
- // scale
- create_dl_scale_matrix(DJUI_MTX_NOPUSH, interp->width * translatedW, interp->height * translatedH, 1.0f);
}
- sResolution = savedResolution;
- sRotation = savedRotation;
+
+ sHudUtilsState = savedState;
gDisplayListHead = savedHeadPos;
gDjuiHudUtilsZ = savedZ;
}
+struct InterpHud *djui_hud_create_interp() {
+ if (sInterpHudCount >= MAX_INTERP_HUD) { return NULL; }
+
+ struct InterpHud *interp = &sInterpHuds[sInterpHudCount++];
+ interp->z = gDjuiHudUtilsZ;
+ interp->state = sHudUtilsState;
+ if (!interp->gfx) {
+ interp->gfx = growing_array_init(NULL, 8, malloc, free);
+ } else {
+ interp->gfx->count = 0;
+ }
+
+ return interp;
+}
+
+InterpHudGfx *djui_hud_create_interp_gfx(struct InterpHud *interp, enum InterpHudType type) {
+ if (!interp) { return NULL; }
+
+ InterpHudGfx *gfx = growing_array_alloc(interp->gfx, sizeof(InterpHudGfx));
+ gfx->type = type;
+ gfx->pos = gDisplayListHead;
+ return gfx;
+}
+
////////////
// others //
////////////
u8 djui_hud_get_resolution(void) {
- return sResolution;
+ return sHudUtilsState.resolution;
}
void djui_hud_set_resolution(enum HudUtilsResolution resolutionType) {
if (resolutionType >= RESOLUTION_COUNT) { return; }
- sResolution = resolutionType;
+ sHudUtilsState.resolution = resolutionType;
}
u8 djui_hud_get_filter(void) {
- return sFilter;
+ return sHudUtilsState.filter;
}
void djui_hud_set_filter(enum HudUtilsFilter filterType) {
if (filterType >= FILTER_COUNT) { return; }
- sFilter = filterType;
+ sHudUtilsState.filter = filterType;
}
-u8 djui_hud_get_font(void) {
- return sFont;
+s8 djui_hud_get_font(void) {
+ return sHudUtilsState.font;
}
void djui_hud_set_font(s8 fontType) {
if (fontType >= FONT_COUNT || fontType < -1) { return; }
- sLegacy = fontType == -1;
- if (sLegacy) { fontType = 0; }
- sFont = fontType;
+ sHudUtilsState.font = fontType;
}
struct DjuiColor* djui_hud_get_color(void) {
- sRefColor.r = sColor.r;
- sRefColor.g = sColor.g;
- sRefColor.b = sColor.b;
- sRefColor.a = sColor.a;
+ sRefColor.r = sHudUtilsState.color.r;
+ sRefColor.g = sHudUtilsState.color.g;
+ sRefColor.b = sHudUtilsState.color.b;
+ sRefColor.a = sHudUtilsState.color.a;
return &sRefColor;
}
void djui_hud_set_color(u8 r, u8 g, u8 b, u8 a) {
- sColor.r = r;
- sColor.g = g;
- sColor.b = b;
- sColor.a = a;
+ sHudUtilsState.color.r = r;
+ sHudUtilsState.color.g = g;
+ sHudUtilsState.color.b = b;
+ sHudUtilsState.color.a = a;
sColorAltered = TRUE;
gDPSetEnvColor(gDisplayListHead++, r, g, b, a);
}
void djui_hud_reset_color(void) {
if (sColorAltered) {
- sColor.r = 255;
- sColor.g = 255;
- sColor.b = 255;
- sColor.a = 255;
+ sHudUtilsState.color.r = 255;
+ sHudUtilsState.color.g = 255;
+ sHudUtilsState.color.b = 255;
+ sHudUtilsState.color.a = 255;
sColorAltered = FALSE;
gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255);
}
}
-struct HudUtilsRotation* djui_hud_get_rotation(void) {
- return &sRotation;
+void djui_hud_get_rotation(RET s16 *rotation, RET f32 *pivotX, RET f32 *pivotY) {
+ *rotation = degrees_to_sm64(sHudUtilsState.rotation.degrees.curr);
+ *pivotX = sHudUtilsState.rotation.pivotX.curr;
+ *pivotY = sHudUtilsState.rotation.pivotY.curr;
}
void djui_hud_set_rotation(s16 rotation, f32 pivotX, f32 pivotY) {
- sRotation.rotationDiff = 0;
- sRotation.prevPivotX = pivotX;
- sRotation.prevPivotY = pivotY;
- sRotation.rotation = (rotation * 180.f) / 0x8000;
- sRotation.pivotX = pivotX;
- sRotation.pivotY = pivotY;
+ sHudUtilsState.rotation.degrees.prev = sHudUtilsState.rotation.degrees.curr = sm64_to_degrees(rotation);
+ sHudUtilsState.rotation.pivotX.prev = sHudUtilsState.rotation.pivotX.curr = pivotX;
+ sHudUtilsState.rotation.pivotY.prev = sHudUtilsState.rotation.pivotY.curr = pivotY;
}
-void djui_hud_set_rotation_interpolated(s32 prevRotation, f32 prevPivotX, f32 prevPivotY, s32 rotation, f32 pivotX, f32 pivotY) {
- f32 normalizedDiff = ((rotation - prevRotation + 0x8000) & 0xFFFF) - 0x8000; // Fix modular overflow/underflow
- sRotation.rotationDiff = (normalizedDiff * 180.f) / 0x8000;
- sRotation.prevPivotX = prevPivotX;
- sRotation.prevPivotY = prevPivotY;
- sRotation.rotation = (rotation * 180.f) / 0x8000;
- sRotation.pivotX = pivotX;
- sRotation.pivotY = pivotY;
+void djui_hud_set_rotation_interpolated(s16 prevRotation, f32 prevPivotX, f32 prevPivotY, s16 rotation, f32 pivotX, f32 pivotY) {
+ sHudUtilsState.rotation.degrees.prev = sm64_to_degrees(prevRotation);
+ sHudUtilsState.rotation.degrees.curr = sm64_to_degrees(rotation);
+ sHudUtilsState.rotation.pivotX.prev = prevPivotX;
+ sHudUtilsState.rotation.pivotX.curr = pivotX;
+ sHudUtilsState.rotation.pivotY.prev = prevPivotY;
+ sHudUtilsState.rotation.pivotY.curr = pivotY;
+}
+
+void djui_hud_get_text_alignment(RET f32 *textHAlign, RET f32 *textVAlign) {
+ *textHAlign = sHudUtilsState.textAlignment.h.curr;
+ *textVAlign = sHudUtilsState.textAlignment.v.curr;
+}
+
+void djui_hud_set_text_alignment(f32 textHAlign, f32 textVAlign) {
+ sHudUtilsState.textAlignment.h.prev = sHudUtilsState.textAlignment.h.curr = textHAlign;
+ sHudUtilsState.textAlignment.v.prev = sHudUtilsState.textAlignment.v.curr = textVAlign;
+}
+
+void djui_hud_set_text_alignment_interpolated(f32 prevTextHAlign, f32 prevTextVAlign, f32 textHAlign, f32 textVAlign) {
+ sHudUtilsState.textAlignment.h.prev = prevTextHAlign;
+ sHudUtilsState.textAlignment.h.curr = textHAlign;
+ sHudUtilsState.textAlignment.v.prev = prevTextVAlign;
+ sHudUtilsState.textAlignment.v.curr = textVAlign;
}
u32 djui_hud_get_screen_width(void) {
u32 windowWidth, windowHeight;
gfx_get_dimensions(&windowWidth, &windowHeight);
- return (sResolution == RESOLUTION_N64)
+ return (sHudUtilsState.resolution == RESOLUTION_N64)
? GFX_DIMENSIONS_ASPECT_RATIO * SCREEN_HEIGHT
: (windowWidth / djui_gfx_get_scale());
}
@@ -274,7 +394,7 @@ u32 djui_hud_get_screen_height(void) {
u32 windowWidth, windowHeight;
gfx_get_dimensions(&windowWidth, &windowHeight);
- return (sResolution == RESOLUTION_N64)
+ return (sHudUtilsState.resolution == RESOLUTION_N64)
? SCREEN_HEIGHT
: (windowHeight / djui_gfx_get_scale());
}
@@ -364,23 +484,58 @@ void djui_hud_reset_scissor(void) {
f32 djui_hud_measure_text(const char* message) {
if (message == NULL) { return 0; }
- const struct DjuiFont* font = gDjuiFonts[sFont];
- f32 width = 0;
- const char* c = message;
- while(*c != '\0') {
- width += font->char_width((char*)c) * (sLegacy ? 0.5f : 1.0f);
- c = djui_unicode_next_char((char*)c);
+ const struct DjuiFont* font = djui_hud_get_text_font();
+ f32 width = 0, maxWidth = 0;
+ char *c = (char *) message;
+ const char *end = message + strlen(message);
+ while (*c != '\0') {
+
+ // check color code
+ if (djui_text_parse_color(c, end, false, NULL, &c, NULL)) {
+ continue;
+ }
+
+ // new line
+ if (*c == '\n') {
+ maxWidth = max(width, maxWidth);
+ width = 0;
+ }
+
+ // tab: align to the next (4 x space width)
+ else if (*c == '\t') {
+ f32 tabWidth = 4 * font->char_width(" ") * (djui_hud_text_font_is_legacy() ? 0.5f : 1.0f);
+ width += tabWidth - fmodf(width, tabWidth);
+ }
+
+ // unprintable chars
+ else if (!djui_text_is_printable(c)) {
+ // treat them as empty
+ }
+
+ // regular chars
+ else {
+ width += font->char_width(c) * (djui_hud_text_font_is_legacy() ? 0.5f : 1.0f);
+ }
+
+ c = djui_unicode_next_char(c);
}
- return width * font->defaultFontScale;
+ return max(width, maxWidth) * font->defaultFontScale;
}
-void djui_hud_print_text(const char* message, f32 x, f32 y, f32 scale) {
+static Mtx *allocate_dl_translation_matrix() {
+ Mtx *matrix = (Mtx *) alloc_display_list(sizeof(Mtx));
+ if (matrix == NULL) { return NULL; }
+ gSPMatrix(gDisplayListHead++, matrix, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH);
+ return matrix;
+}
+
+static void djui_hud_print_text_internal(const char* message, f32 x, f32 y, f32 scale, struct InterpHud *interp) {
if (message == NULL) { return; }
gDjuiHudUtilsZ += 0.001f;
- if (sLegacy) { scale *= 0.5f; }
+ if (djui_hud_text_font_is_legacy()) { scale *= 0.5f; }
- const struct DjuiFont* font = gDjuiFonts[sFont];
+ const struct DjuiFont* font = djui_hud_get_text_font();
f32 fontScale = font->defaultFontScale * scale;
// setup display list
@@ -389,117 +544,147 @@ void djui_hud_print_text(const char* message, f32 x, f32 y, f32 scale) {
}
// translate position
+ djui_hud_create_interp_gfx(interp, INTERP_HUD_TRANSLATION);
f32 translatedX = x + (font->xOffset * scale);
f32 translatedY = y + (font->yOffset * scale);
djui_hud_position_translate(&translatedX, &translatedY);
create_dl_translation_matrix(DJUI_MTX_PUSH, translatedX, translatedY, gDjuiHudUtilsZ);
- // compute font size
+ // rotate
f32 translatedFontSize = fontScale;
djui_hud_size_translate(&translatedFontSize);
+ if (sHudUtilsState.rotation.degrees.prev != 0 || sHudUtilsState.rotation.degrees.curr != 0) {
+ djui_hud_create_interp_gfx(interp, INTERP_HUD_ROTATION);
+ f32 pivotTranslationX = font->defaultFontScale * translatedFontSize * sHudUtilsState.rotation.pivotX.curr;
+ f32 pivotTranslationY = font->defaultFontScale * translatedFontSize * sHudUtilsState.rotation.pivotY.curr;
+ create_dl_translation_matrix(DJUI_MTX_NOPUSH, +pivotTranslationX, -pivotTranslationY, 0);
+ create_dl_rotation_matrix(DJUI_MTX_NOPUSH, sHudUtilsState.rotation.degrees.curr, 0, 0, 1);
+ create_dl_translation_matrix(DJUI_MTX_NOPUSH, -pivotTranslationX, +pivotTranslationY, 0);
+ }
+
+ // compute font size
+ djui_hud_create_interp_gfx(interp, INTERP_HUD_SCALE);
create_dl_scale_matrix(DJUI_MTX_NOPUSH, translatedFontSize, translatedFontSize, 1.0f);
- // render the line
- f32 addX = 0;
- char* c = (char*)message;
- while (*c != '\0') {
- f32 charWidth = font->char_width(c);
+ // allocate the translation matrix for the vertical alignment
+ InterpHudGfx *valignGfx = djui_hud_create_interp_gfx(interp, INTERP_HUD_VALIGN);
+ Mtx *valignMatrix = allocate_dl_translation_matrix();
+ if (valignMatrix == NULL) { return; }
- if (*c == '\n' && *c == ' ') {
- addX += charWidth;
- c++;
+ // allocate the translation matrix for the horizontal alignment
+ InterpHudGfx *halignGfx = djui_hud_create_interp_gfx(interp, INTERP_HUD_HALIGN);
+ Mtx *halignMatrix = allocate_dl_translation_matrix();
+ if (halignMatrix == NULL) { return; }
+
+ // render the line
+ char* c = (char*)message;
+ const char *end = message + strlen(message);
+ f32 lineWidth = 0;
+ f32 textHeight = font->lineHeight;
+
+ font->render_begin();
+ while (*c != '\0') {
+
+ // check color code
+ struct DjuiColor parsedColor;
+ if (djui_text_parse_color(c, end, false, &sHudUtilsState.color, &c, &parsedColor)) {
+ gDPSetEnvColor(gDisplayListHead++, parsedColor.r, parsedColor.g, parsedColor.b, parsedColor.a);
+ continue;
+ }
+
+ // new line
+ if (*c == '\n') {
+
+ // compute the horizontal alignment matrix for the current line
+ guTranslate(halignMatrix, -lineWidth * sHudUtilsState.textAlignment.h.curr, 0, 0);
+ if (halignGfx) { halignGfx->params[0] = lineWidth; }
+
+ // allocate a new translation matrix for the next line
+ halignGfx = djui_hud_create_interp_gfx(interp, INTERP_HUD_HALIGN);
+ halignMatrix = allocate_dl_translation_matrix();
+ if (halignMatrix == NULL) { return; }
+
+ // cancel out the line translation and move to the next line
+ // this is needed because otherwise the text would be rendered in a staircase way
+ InterpHudGfx *newlineGfx = djui_hud_create_interp_gfx(interp, INTERP_HUD_NEW_LINE);
+ create_dl_translation_matrix(DJUI_MTX_NOPUSH, -lineWidth * (1.f - sHudUtilsState.textAlignment.h.curr), -font->lineHeight, 0);
+ if (newlineGfx) { newlineGfx->params[0] = lineWidth; }
+ lineWidth = 0;
+ textHeight += font->lineHeight;
+ c = djui_unicode_next_char(c);
+ continue;
+ }
+
+ // tab: align to the next (4 x space width)
+ else if (*c == '\t') {
+ f32 tabWidth = 4 * font->char_width(" ");
+ f32 newLineWidth = lineWidth + tabWidth - fmodf(lineWidth, tabWidth);
+ create_dl_translation_matrix(DJUI_MTX_NOPUSH, newLineWidth - lineWidth, 0, 0);
+ lineWidth = newLineWidth;
+ c = djui_unicode_next_char(c);
+ continue;
+ }
+
+ // unprintable chars
+ if (!djui_text_is_printable(c)) {
+ c = djui_unicode_next_char(c);
continue;
}
// render
+ f32 charWidth = font->char_width(c);
font->render_char(c);
- create_dl_translation_matrix(DJUI_MTX_NOPUSH, charWidth + addX, 0, 0);
- addX = 0;
+ create_dl_translation_matrix(DJUI_MTX_NOPUSH, charWidth, 0, 0);
+ lineWidth += charWidth;
c = djui_unicode_next_char(c);
}
+ font->render_end();
+
+ // compute the horizontal alignment matrix for the last line
+ guTranslate(halignMatrix, -lineWidth * sHudUtilsState.textAlignment.h.curr, 0, 0);
+ if (halignGfx) { halignGfx->params[0] = lineWidth; }
+
+ // compute the vertical alignment matrix
+ guTranslate(valignMatrix, 0, textHeight * sHudUtilsState.textAlignment.v.curr, 0);
+ if (valignGfx) { valignGfx->params[0] = textHeight; }
// pop
gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
}
+void djui_hud_print_text(const char* message, f32 x, f32 y, f32 scale) {
+ djui_hud_print_text_internal(message, x, y, scale, NULL);
+}
+
void djui_hud_print_text_interpolated(const char* message, f32 prevX, f32 prevY, f32 prevScale, f32 x, f32 y, f32 scale) {
if (message == NULL) { return; }
- f32 savedZ = gDjuiHudUtilsZ;
- gDjuiHudUtilsZ += 0.001f;
- if (sLegacy) {
- prevScale *= 0.5f;
- scale *= 0.5f;
+ if (djui_hud_text_font_is_legacy()) { prevScale *= 0.5f; }
+
+ struct InterpHud *interp = djui_hud_create_interp();
+ if (interp) {
+ const struct DjuiFont* font = djui_hud_get_text_font();
+ interp->posX.prev = prevX;
+ interp->posY.prev = prevY;
+ interp->posX.curr = x;
+ interp->posY.curr = y;
+ interp->scaleX.prev = prevScale;
+ interp->scaleY.prev = prevScale;
+ interp->scaleX.curr = scale;
+ interp->scaleY.curr = scale;
+ interp->width = font->defaultFontScale;
+ interp->height = font->defaultFontScale;
}
- const struct DjuiFont* font = gDjuiFonts[sFont];
- f32 fontScale = font->defaultFontScale * scale;
-
- // setup display list
- if (font->textBeginDisplayList != NULL) {
- gSPDisplayList(gDisplayListHead++, font->textBeginDisplayList);
- }
-
- Gfx* savedHeadPos = gDisplayListHead;
-
- // translate position
- f32 translatedX = x + (font->xOffset * scale);
- f32 translatedY = y + (font->yOffset * scale);
- djui_hud_position_translate(&translatedX, &translatedY);
- create_dl_translation_matrix(DJUI_MTX_PUSH, translatedX, translatedY, gDjuiHudUtilsZ);
-
- // compute font size
- f32 translatedFontSize = fontScale;
- djui_hud_size_translate(&translatedFontSize);
- create_dl_scale_matrix(DJUI_MTX_NOPUSH, translatedFontSize, translatedFontSize, 1.0f);
-
- // render the line
- f32 addX = 0;
- char* c = (char*)message;
- while (*c != '\0') {
- f32 charWidth = font->char_width(c);
-
- if (*c == '\n' && *c == ' ') {
- addX += charWidth;
- c++;
- continue;
- }
-
- // render
- font->render_char(c);
- create_dl_translation_matrix(DJUI_MTX_NOPUSH, charWidth + addX, 0, 0);
- addX = 0;
-
- c = djui_unicode_next_char(c);
- }
-
- // pop
- gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
-
- if (sInterpHudCount >= MAX_INTERP_HUD) { return; }
- struct InterpHud* interp = &sInterpHuds[sInterpHudCount++];
- interp->headPos = savedHeadPos;
- interp->prevX = prevX;
- interp->prevY = prevY;
- interp->prevScaleW = prevScale;
- interp->prevScaleH = prevScale;
- interp->x = x;
- interp->y = y;
- interp->scaleW = scale;
- interp->scaleH = scale;
- interp->width = font->defaultFontScale;
- interp->height = font->defaultFontScale;
- interp->z = savedZ;
- interp->resolution = sResolution;
- interp->rotation = sRotation;
+ djui_hud_print_text_internal(message, x, y, scale, interp);
}
static inline bool is_power_of_two(u32 n) {
return (n > 0) && ((n & (n - 1)) == 0);
}
-static void djui_hud_render_texture_raw(const Texture* texture, u32 width, u32 height, u8 fmt, u8 siz, f32 x, f32 y, f32 scaleW, f32 scaleH) {
+static void djui_hud_render_texture_raw(const Texture* texture, u32 width, u32 height, u8 fmt, u8 siz, f32 x, f32 y, f32 scaleW, f32 scaleH, struct InterpHud *interp) {
if (!is_power_of_two(width) || !is_power_of_two(height)) {
LOG_LUA_LINE("Tried to render DJUI HUD texture with NPOT width or height");
return;
@@ -510,6 +695,7 @@ static void djui_hud_render_texture_raw(const Texture* texture, u32 width, u32 h
gDjuiHudUtilsZ += 0.001f;
// translate position
+ djui_hud_create_interp_gfx(interp, INTERP_HUD_TRANSLATION);
f32 translatedX = x;
f32 translatedY = y;
djui_hud_position_translate(&translatedX, &translatedY);
@@ -520,25 +706,27 @@ static void djui_hud_render_texture_raw(const Texture* texture, u32 width, u32 h
f32 translatedH = scaleH;
djui_hud_size_translate(&translatedW);
djui_hud_size_translate(&translatedH);
- if (sRotation.rotation != 0) {
- f32 pivotTranslationX = width * translatedW * sRotation.pivotX;
- f32 pivotTranslationY = height * translatedH * sRotation.pivotY;
+ if (sHudUtilsState.rotation.degrees.prev != 0 || sHudUtilsState.rotation.degrees.curr != 0) {
+ djui_hud_create_interp_gfx(interp, INTERP_HUD_ROTATION);
+ f32 pivotTranslationX = width * translatedW * sHudUtilsState.rotation.pivotX.curr;
+ f32 pivotTranslationY = height * translatedH * sHudUtilsState.rotation.pivotY.curr;
create_dl_translation_matrix(DJUI_MTX_NOPUSH, +pivotTranslationX, -pivotTranslationY, 0);
- create_dl_rotation_matrix(DJUI_MTX_NOPUSH, sRotation.rotation, 0, 0, 1);
+ create_dl_rotation_matrix(DJUI_MTX_NOPUSH, sHudUtilsState.rotation.degrees.curr, 0, 0, 1);
create_dl_translation_matrix(DJUI_MTX_NOPUSH, -pivotTranslationX, +pivotTranslationY, 0);
}
// translate scale
+ djui_hud_create_interp_gfx(interp, INTERP_HUD_SCALE);
create_dl_scale_matrix(DJUI_MTX_NOPUSH, width * translatedW, height * translatedH, 1.0f);
// render
- djui_gfx_render_texture(texture, width, height, fmt, siz, sFilter);
+ djui_gfx_render_texture(texture, width, height, fmt, siz, sHudUtilsState.filter);
// pop
gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
}
-static void djui_hud_render_texture_tile_raw(const Texture* texture, u32 width, u32 height, u8 fmt, u8 siz, f32 x, f32 y, f32 scaleW, f32 scaleH, u32 tileX, u32 tileY, u32 tileW, u32 tileH) {
+static void djui_hud_render_texture_tile_raw(const Texture* texture, u32 width, u32 height, u8 fmt, u8 siz, f32 x, f32 y, f32 scaleW, f32 scaleH, u32 tileX, u32 tileY, u32 tileW, u32 tileH, struct InterpHud *interp) {
if (!texture) { return; }
gDjuiHudUtilsZ += 0.001f;
@@ -546,6 +734,7 @@ static void djui_hud_render_texture_tile_raw(const Texture* texture, u32 width,
if (height != 0) { scaleH *= (f32) tileH / (f32) height; }
// translate position
+ djui_hud_create_interp_gfx(interp, INTERP_HUD_TRANSLATION);
f32 translatedX = x;
f32 translatedY = y;
djui_hud_position_translate(&translatedX, &translatedY);
@@ -556,20 +745,22 @@ static void djui_hud_render_texture_tile_raw(const Texture* texture, u32 width,
f32 translatedH = scaleH;
djui_hud_size_translate(&translatedW);
djui_hud_size_translate(&translatedH);
- if (sRotation.rotation != 0) {
+ if (sHudUtilsState.rotation.degrees.prev != 0 || sHudUtilsState.rotation.degrees.curr != 0) {
+ djui_hud_create_interp_gfx(interp, INTERP_HUD_ROTATION);
f32 aspect = tileH ? ((f32) tileW / (f32) tileH) : 1.f;
- f32 pivotTranslationX = width * translatedW * aspect * sRotation.pivotX;
- f32 pivotTranslationY = height * translatedH * sRotation.pivotY;
+ f32 pivotTranslationX = width * translatedW * aspect * sHudUtilsState.rotation.pivotX.curr;
+ f32 pivotTranslationY = height * translatedH * sHudUtilsState.rotation.pivotY.curr;
create_dl_translation_matrix(DJUI_MTX_NOPUSH, +pivotTranslationX, -pivotTranslationY, 0);
- create_dl_rotation_matrix(DJUI_MTX_NOPUSH, sRotation.rotation, 0, 0, 1);
+ create_dl_rotation_matrix(DJUI_MTX_NOPUSH, sHudUtilsState.rotation.degrees.curr, 0, 0, 1);
create_dl_translation_matrix(DJUI_MTX_NOPUSH, -pivotTranslationX, +pivotTranslationY, 0);
}
// translate scale
+ djui_hud_create_interp_gfx(interp, INTERP_HUD_SCALE);
create_dl_scale_matrix(DJUI_MTX_NOPUSH, width * translatedW, height * translatedH, 1.0f);
// render
- djui_gfx_render_texture_tile(texture, width, height, fmt, siz, tileX, tileY, tileW, tileH, sFilter, false);
+ djui_gfx_render_texture_tile(texture, width, height, fmt, siz, tileX, tileY, tileW, tileH, sHudUtilsState.filter);
// pop
gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
@@ -577,44 +768,35 @@ static void djui_hud_render_texture_tile_raw(const Texture* texture, u32 width,
void djui_hud_render_texture(struct TextureInfo* texInfo, f32 x, f32 y, f32 scaleW, f32 scaleH) {
if (!texInfo) { return; }
- djui_hud_render_texture_raw(texInfo->texture, texInfo->width, texInfo->height, texInfo->format, texInfo->size, x, y, scaleW, scaleH);
+ djui_hud_render_texture_raw(texInfo->texture, texInfo->width, texInfo->height, texInfo->format, texInfo->size, x, y, scaleW, scaleH, NULL);
}
void djui_hud_render_texture_tile(struct TextureInfo* texInfo, f32 x, f32 y, f32 scaleW, f32 scaleH, u32 tileX, u32 tileY, u32 tileW, u32 tileH) {
if (!texInfo) { return; }
- djui_hud_render_texture_tile_raw(texInfo->texture, texInfo->width, texInfo->height, texInfo->format, texInfo->size, x, y, scaleW, scaleH, tileX, tileY, tileW, tileH);
+ djui_hud_render_texture_tile_raw(texInfo->texture, texInfo->width, texInfo->height, texInfo->format, texInfo->size, x, y, scaleW, scaleH, tileX, tileY, tileW, tileH, NULL);
}
void djui_hud_render_texture_interpolated(struct TextureInfo* texInfo, f32 prevX, f32 prevY, f32 prevScaleW, f32 prevScaleH, f32 x, f32 y, f32 scaleW, f32 scaleH) {
- Gfx* savedHeadPos = gDisplayListHead;
- f32 savedZ = gDjuiHudUtilsZ;
-
if (!texInfo) { return; }
- djui_hud_render_texture_raw(texInfo->texture, texInfo->width, texInfo->height, texInfo->format, texInfo->size, prevX, prevY, prevScaleW, prevScaleH);
+ struct InterpHud *interp = djui_hud_create_interp();
+ if (interp) {
+ interp->posX.prev = prevX;
+ interp->posY.prev = prevY;
+ interp->posX.curr = x;
+ interp->posY.curr = y;
+ interp->scaleX.prev = prevScaleW;
+ interp->scaleY.prev = prevScaleH;
+ interp->scaleX.curr = scaleW;
+ interp->scaleY.curr = scaleH;
+ interp->width = texInfo->width;
+ interp->height = texInfo->height;
+ }
- if (sInterpHudCount >= MAX_INTERP_HUD) { return; }
- struct InterpHud* interp = &sInterpHuds[sInterpHudCount++];
- interp->headPos = savedHeadPos;
- interp->prevX = prevX;
- interp->prevY = prevY;
- interp->prevScaleW = prevScaleW;
- interp->prevScaleH = prevScaleH;
- interp->x = x;
- interp->y = y;
- interp->scaleW = scaleW;
- interp->scaleH = scaleH;
- interp->width = texInfo->width;
- interp->height = texInfo->height;
- interp->z = savedZ;
- interp->resolution = sResolution;
- interp->rotation = sRotation;
+ djui_hud_render_texture_raw(texInfo->texture, texInfo->width, texInfo->height, texInfo->format, texInfo->size, prevX, prevY, prevScaleW, prevScaleH, interp);
}
void djui_hud_render_texture_tile_interpolated(struct TextureInfo* texInfo, f32 prevX, f32 prevY, f32 prevScaleW, f32 prevScaleH, f32 x, f32 y, f32 scaleW, f32 scaleH, u32 tileX, u32 tileY, u32 tileW, u32 tileH) {
- Gfx* savedHeadPos = gDisplayListHead;
- f32 savedZ = gDjuiHudUtilsZ;
-
if (!texInfo) { return; }
// apply scale correction for tiles
@@ -627,30 +809,28 @@ void djui_hud_render_texture_tile_interpolated(struct TextureInfo* texInfo, f32
prevScaleH *= ((f32)tileH / (f32)texInfo->height);
}
- djui_hud_render_texture_tile_raw(texInfo->texture, texInfo->width, texInfo->height, texInfo->format, texInfo->size, prevX, prevY, prevScaleW, prevScaleH, tileX, tileY, tileW, tileH);
+ struct InterpHud *interp = djui_hud_create_interp();
+ if (interp) {
+ interp->posX.prev = prevX;
+ interp->posY.prev = prevY;
+ interp->posX.curr = x;
+ interp->posY.curr = y;
+ interp->scaleX.prev = prevScaleW;
+ interp->scaleY.prev = prevScaleH;
+ interp->scaleX.curr = scaleW;
+ interp->scaleY.curr = scaleH;
+ interp->width = texInfo->width;
+ interp->height = texInfo->height;
+ }
- if (sInterpHudCount >= MAX_INTERP_HUD) { return; }
- struct InterpHud* interp = &sInterpHuds[sInterpHudCount++];
- interp->headPos = savedHeadPos;
- interp->prevX = prevX;
- interp->prevY = prevY;
- interp->prevScaleW = prevScaleW;
- interp->prevScaleH = prevScaleH;
- interp->x = x;
- interp->y = y;
- interp->scaleW = scaleW;
- interp->scaleH = scaleH;
- interp->width = texInfo->width;
- interp->height = texInfo->height;
- interp->z = savedZ;
- interp->resolution = sResolution;
- interp->rotation = sRotation;
+ djui_hud_render_texture_tile_raw(texInfo->texture, texInfo->width, texInfo->height, texInfo->format, texInfo->size, prevX, prevY, prevScaleW, prevScaleH, tileX, tileY, tileW, tileH, interp);
}
-void djui_hud_render_rect(f32 x, f32 y, f32 width, f32 height) {
+static void djui_hud_render_rect_internal(f32 x, f32 y, f32 width, f32 height, struct InterpHud *interp) {
gDjuiHudUtilsZ += 0.001f;
// translate position
+ djui_hud_create_interp_gfx(interp, INTERP_HUD_TRANSLATION);
f32 translatedX = x;
f32 translatedY = y;
djui_hud_position_translate(&translatedX, &translatedY);
@@ -661,15 +841,17 @@ void djui_hud_render_rect(f32 x, f32 y, f32 width, f32 height) {
f32 translatedH = height;
djui_hud_size_translate(&translatedW);
djui_hud_size_translate(&translatedH);
- if (sRotation.rotation != 0) {
- f32 pivotTranslationX = translatedW * sRotation.pivotX;
- f32 pivotTranslationY = translatedH * sRotation.pivotY;
+ if (sHudUtilsState.rotation.degrees.prev != 0 || sHudUtilsState.rotation.degrees.curr != 0) {
+ djui_hud_create_interp_gfx(interp, INTERP_HUD_ROTATION);
+ f32 pivotTranslationX = translatedW * sHudUtilsState.rotation.pivotX.curr;
+ f32 pivotTranslationY = translatedH * sHudUtilsState.rotation.pivotY.curr;
create_dl_translation_matrix(DJUI_MTX_NOPUSH, +pivotTranslationX, -pivotTranslationY, 0);
- create_dl_rotation_matrix(DJUI_MTX_NOPUSH, sRotation.rotation, 0, 0, 1);
+ create_dl_rotation_matrix(DJUI_MTX_NOPUSH, sHudUtilsState.rotation.degrees.curr, 0, 0, 1);
create_dl_translation_matrix(DJUI_MTX_NOPUSH, -pivotTranslationX, +pivotTranslationY, 0);
}
// translate scale
+ djui_hud_create_interp_gfx(interp, INTERP_HUD_SCALE);
create_dl_scale_matrix(DJUI_MTX_NOPUSH, translatedW, translatedH, 1.0f);
// render
@@ -679,28 +861,26 @@ void djui_hud_render_rect(f32 x, f32 y, f32 width, f32 height) {
gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
}
+void djui_hud_render_rect(f32 x, f32 y, f32 width, f32 height) {
+ djui_hud_render_rect_internal(x, y, width, height, NULL);
+}
+
void djui_hud_render_rect_interpolated(f32 prevX, f32 prevY, f32 prevWidth, f32 prevHeight, f32 x, f32 y, f32 width, f32 height) {
- Gfx* savedHeadPos = gDisplayListHead;
- f32 savedZ = gDjuiHudUtilsZ;
+ struct InterpHud *interp = djui_hud_create_interp();
+ if (interp) {
+ interp->posX.prev = prevX;
+ interp->posY.prev = prevY;
+ interp->posX.curr = x;
+ interp->posY.curr = y;
+ interp->scaleX.prev = prevWidth;
+ interp->scaleY.prev = prevHeight;
+ interp->scaleX.curr = width;
+ interp->scaleY.curr = height;
+ interp->width = 1;
+ interp->height = 1;
+ }
- djui_hud_render_rect(prevX, prevY, prevWidth, prevHeight);
-
- if (sInterpHudCount >= MAX_INTERP_HUD) { return; }
- struct InterpHud* interp = &sInterpHuds[sInterpHudCount++];
- interp->headPos = savedHeadPos;
- interp->prevX = prevX;
- interp->prevY = prevY;
- interp->prevScaleW = prevWidth;
- interp->prevScaleH = prevHeight;
- interp->x = x;
- interp->y = y;
- interp->scaleW = width;
- interp->scaleH = height;
- interp->width = 1;
- interp->height = 1;
- interp->z = savedZ;
- interp->resolution = sResolution;
- interp->rotation = sRotation;
+ djui_hud_render_rect_internal(prevX, prevY, prevWidth, prevHeight, interp);
}
void djui_hud_render_line(f32 p1X, f32 p1Y, f32 p2X, f32 p2Y, f32 size) {
@@ -752,7 +932,7 @@ bool djui_hud_world_pos_to_screen_pos(Vec3f pos, VEC_OUT Vec3f out) {
out[1] *= fovCoeff;
f32 screenWidth, screenHeight;
- if (sResolution == RESOLUTION_N64) {
+ if (sHudUtilsState.resolution == RESOLUTION_N64) {
screenWidth = GFX_DIMENSIONS_ASPECT_RATIO * SCREEN_HEIGHT;
screenHeight = SCREEN_HEIGHT;
} else {
diff --git a/src/pc/djui/djui_hud_utils.h b/src/pc/djui/djui_hud_utils.h
index b4e1b9d08..353ee3511 100644
--- a/src/pc/djui/djui_hud_utils.h
+++ b/src/pc/djui/djui_hud_utils.h
@@ -1,6 +1,22 @@
#ifndef DJUI_HUD_UTILS_H
#define DJUI_HUD_UTILS_H
+// Common pivot values for rotation
+#define ROTATION_PIVOT_X_LEFT 0.0
+#define ROTATION_PIVOT_X_CENTER 0.5
+#define ROTATION_PIVOT_X_RIGHT 1.0
+#define ROTATION_PIVOT_Y_TOP 0.0
+#define ROTATION_PIVOT_Y_CENTER 0.5
+#define ROTATION_PIVOT_Y_BOTTOM 1.0
+
+// Common alignment values for text alignment
+#define TEXT_HALIGN_LEFT 0.0
+#define TEXT_HALIGN_CENTER 0.5
+#define TEXT_HALIGN_RIGHT 1.0
+#define TEXT_VALIGN_TOP 0.0
+#define TEXT_VALIGN_CENTER 0.5
+#define TEXT_VALIGN_BOTTOM 1.0
+
enum HudUtilsResolution {
RESOLUTION_DJUI,
RESOLUTION_N64,
@@ -14,7 +30,8 @@ enum HudUtilsFilter {
};
enum DjuiFontType {
- FONT_NORMAL,
+ FONT_LEGACY = -1,
+ FONT_NORMAL = 0,
FONT_MENU,
FONT_HUD,
FONT_ALIASED,
@@ -24,15 +41,6 @@ enum DjuiFontType {
FONT_COUNT,
};
-struct HudUtilsRotation {
- f32 rotation;
- f32 rotationDiff;
- f32 prevPivotX;
- f32 prevPivotY;
- f32 pivotX;
- f32 pivotY;
-};
-
struct GlobalTextures {
struct TextureInfo camera;
struct TextureInfo lakitu;
@@ -63,7 +71,7 @@ u8 djui_hud_get_filter(void);
/* |description|Sets the current DJUI HUD texture filter|descriptionEnd| */
void djui_hud_set_filter(enum HudUtilsFilter filterType);
/* |description|Gets the current DJUI HUD font|descriptionEnd| */
-u8 djui_hud_get_font(void);
+s8 djui_hud_get_font(void);
/* |description|Sets the current DJUI HUD font|descriptionEnd| */
void djui_hud_set_font(s8 fontType);
/* |description|Gets the current DJUI HUD color|descriptionEnd| */
@@ -73,11 +81,17 @@ void djui_hud_set_color(u8 r, u8 g, u8 b, u8 a);
/* |description|Resets the current DJUI HUD color|descriptionEnd| */
void djui_hud_reset_color(void);
/* |description|Gets the current DJUI HUD rotation|descriptionEnd| */
-struct HudUtilsRotation* djui_hud_get_rotation(void);
+void djui_hud_get_rotation(RET s16 *rotation, RET f32 *pivotX, RET f32 *pivotY);
/* |description|Sets the current DJUI HUD rotation|descriptionEnd| */
void djui_hud_set_rotation(s16 rotation, f32 pivotX, f32 pivotY);
/* |description|Sets the current DJUI HUD rotation interpolated|descriptionEnd| */
-void djui_hud_set_rotation_interpolated(s32 prevRotation, f32 prevPivotX, f32 prevPivotY, s32 rotation, f32 pivotX, f32 pivotY);
+void djui_hud_set_rotation_interpolated(s16 prevRotation, f32 prevPivotX, f32 prevPivotY, s16 rotation, f32 pivotX, f32 pivotY);
+/* |description|Gets the current DJUI HUD text alignment|descriptionEnd| */
+void djui_hud_get_text_alignment(RET f32 *textHAlign, RET f32 *textVAlign);
+/* |description|Sets the current DJUI HUD text alignment|descriptionEnd| */
+void djui_hud_set_text_alignment(f32 textHAlign, f32 textVAlign);
+/* |description|Sets the current DJUI HUD text alignment interpolated|descriptionEnd| */
+void djui_hud_set_text_alignment_interpolated(f32 prevTextHAlign, f32 prevTextVAlign, f32 textHAlign, f32 textVAlign);
/* |description|Gets the screen width in the current DJUI HUD resolution|descriptionEnd| */
u32 djui_hud_get_screen_width(void);
diff --git a/src/pc/djui/djui_inputbox.c b/src/pc/djui/djui_inputbox.c
index 878a3a94d..c0a911130 100644
--- a/src/pc/djui/djui_inputbox.c
+++ b/src/pc/djui/djui_inputbox.c
@@ -589,6 +589,8 @@ static bool djui_inputbox_render(struct DjuiBase* base) {
f32 drawX = inputbox->viewX;
f32 additionalShift = 0;
bool wasInsideSelection = false;
+
+ font->render_begin();
for (u16 i = 0; i < inputbox->bufferSize; i++) {
//render composition text
@@ -617,6 +619,7 @@ static bool djui_inputbox_render(struct DjuiBase* base) {
djui_inputbox_render_char(inputbox, c, &drawX, &additionalShift);
c = djui_unicode_next_char(c);
}
+ font->render_end();
gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
gSPDisplayList(gDisplayListHead++, dl_ia_text_end);
diff --git a/src/pc/djui/djui_panel_mod_menu.c b/src/pc/djui/djui_panel_mod_menu.c
index 782fcddee..36437b2c6 100644
--- a/src/pc/djui/djui_panel_mod_menu.c
+++ b/src/pc/djui/djui_panel_mod_menu.c
@@ -113,7 +113,8 @@ void djui_panel_mod_menu_mod_create(struct DjuiBase* caller) {
}
if (mod == NULL) { return; }
- struct DjuiThreePanel* panel = djui_panel_menu_create(to_uppercase(mod->name), false);
+ char *modNameUppercase = to_uppercase(mod->name);
+ struct DjuiThreePanel* panel = djui_panel_menu_create(modNameUppercase, false);
struct DjuiBase* body = djui_three_panel_get_body(panel);
{
struct DjuiPaginated* paginated = djui_paginated_create(body, 8);
@@ -130,6 +131,7 @@ void djui_panel_mod_menu_mod_create(struct DjuiBase* caller) {
}
djui_panel_add(caller, panel, NULL);
+ free(modNameUppercase);
}
void djui_panel_mod_menu_create(struct DjuiBase* caller) {
diff --git a/src/pc/djui/djui_text.c b/src/pc/djui/djui_text.c
index 48f362aac..26fd4ea9c 100644
--- a/src/pc/djui/djui_text.c
+++ b/src/pc/djui/djui_text.c
@@ -4,10 +4,146 @@
#include "djui_hud_utils.h"
#include "game/segment2.h"
-static u8 sSavedR = 0;
-static u8 sSavedG = 0;
-static u8 sSavedB = 0;
-static u8 sSavedA = 0;
+ ///////////
+ // color //
+///////////
+
+static const struct DjuiColor sDjuiTextDefaultColor = { 220, 220, 220, 255 };
+static struct DjuiColor sDjuiTextCurrentColor;
+
+bool djui_text_parse_color(char *begin, const char *end, bool ignoreAlpha, const struct DjuiColor *baseColor, char **nextChar, struct DjuiColor *parsedColor) {
+ char *c = begin;
+
+ // Not an escape
+ if (*c != '\\') {
+ return false;
+ }
+ c = djui_unicode_next_char(c);
+
+ // Not a color
+ if (*c != '#') {
+ return false;
+ }
+ c = djui_unicode_next_char(c);
+
+ // Parse color
+ u32 color = 0;
+ u8 length = 0;
+ while (c < end) {
+ if (*c == '\\') {
+ break;
+ }
+
+ u8 colorPiece = 0;
+ if (*c >= '0' && *c <= '9') { colorPiece = *c - '0'; }
+ else if (*c >= 'a' && *c <= 'f') { colorPiece = 10 + *c - 'a'; }
+ else if (*c >= 'A' && *c <= 'F') { colorPiece = 10 + *c - 'A'; }
+ else { // Not a valid color piece
+ return false;
+ }
+ color = (color << 4) | colorPiece;
+ length++;
+
+ c = djui_unicode_next_char(c);
+ }
+
+ // Unterminated color code
+ if (c == end) {
+ return false;
+ }
+
+ switch (length) {
+
+ // reset to base color
+ case 0: {
+ if (baseColor && parsedColor) {
+ *parsedColor = *baseColor;
+ }
+ } break;
+
+ // #rgb
+ case 3: {
+ if (parsedColor) {
+ u32 r = (color >> 8) & 0xF;
+ u32 g = (color >> 4) & 0xF;
+ u32 b = (color >> 0) & 0xF;
+ parsedColor->r = (r << 4) | r;
+ parsedColor->g = (g << 4) | g;
+ parsedColor->b = (b << 4) | b;
+ parsedColor->a = 0xFF;
+ }
+ } break;
+
+ // #rgba
+ case 4: {
+ if (parsedColor) {
+ u32 r = (color >> 12) & 0xF;
+ u32 g = (color >> 8) & 0xF;
+ u32 b = (color >> 4) & 0xF;
+ u32 a = (color >> 0) & 0xF;
+ parsedColor->r = (r << 4) | r;
+ parsedColor->g = (g << 4) | g;
+ parsedColor->b = (b << 4) | b;
+ parsedColor->a = ignoreAlpha ? 0xFF : ((a << 4) | a);
+ }
+ } break;
+
+ // #rrggbb
+ case 6: {
+ if (parsedColor) {
+ parsedColor->r = ((color >> 16) & 0xFF);
+ parsedColor->g = ((color >> 8) & 0xFF);
+ parsedColor->b = ((color >> 0) & 0xFF);
+ parsedColor->a = 0xFF;
+ }
+ } break;
+
+ // #rrggbbaa
+ case 8: {
+ if (parsedColor) {
+ parsedColor->r = ((color >> 24) & 0xFF);
+ parsedColor->g = ((color >> 16) & 0xFF);
+ parsedColor->b = ((color >> 8) & 0xFF);
+ parsedColor->a = ignoreAlpha ? 0xFF : ((color >> 0) & 0xFF);
+ }
+ } break;
+
+ // Invalid color
+ default: return false;
+ }
+
+ if (nextChar) {
+ *nextChar = djui_unicode_next_char(c);
+ }
+ return true;
+}
+
+void djui_text_remove_colors(char *str) {
+ if (!str) { return; }
+ char *colorStart = str;
+ const char *strEnd = str + strlen(str);
+ while ((colorStart = strstr(colorStart, "\\#"))) {
+ char *colorEnd;
+ if (djui_text_parse_color(colorStart, strEnd, false, NULL, &colorEnd, NULL) && colorEnd > colorStart) {
+ memmove(colorStart, colorEnd, strlen(colorEnd) + 1);
+ } else {
+ colorStart++;
+ }
+ }
+}
+
+char *djui_text_get_uncolored_string(char *dest, size_t length, const char *str) {
+ if (!dest) {
+ dest = malloc(length * sizeof(char));
+ if (!dest) {
+ return NULL;
+ }
+ }
+ strncpy(dest, str, length - 1);
+ dest[length - 1] = 0;
+ djui_text_remove_colors(dest);
+ return dest;
+}
////////////////
// properties //
@@ -59,6 +195,10 @@ static f32 sTextRenderY = 0;
static f32 sTextRenderLastX = 0;
static f32 sTextRenderLastY = 0;
+bool djui_text_is_printable(const char *c) {
+ return c != NULL && (!iscntrl(*c) || *c == 0x7F); // the star
+}
+
static void djui_text_translate(f32 x, f32 y) {
sTextRenderX += x;
sTextRenderY += y;
@@ -90,7 +230,7 @@ static void djui_text_render_char(struct DjuiText* text, char* c) {
sTextRenderY += 1.0f / text->fontScale;
gDPSetEnvColor(gDisplayListHead++, text->dropShadow.r, text->dropShadow.g, text->dropShadow.b, text->dropShadow.a);
djui_text_render_single_char(text, c);
- gDPSetEnvColor(gDisplayListHead++, sSavedR, sSavedG, sSavedB, sSavedA);
+ gDPSetEnvColor(gDisplayListHead++, sDjuiTextCurrentColor.r, sDjuiTextCurrentColor.g, sDjuiTextCurrentColor.b, sDjuiTextCurrentColor.a);
sTextRenderX -= 1.0f / text->fontScale;
sTextRenderY -= 1.0f / text->fontScale;
}
@@ -99,16 +239,21 @@ static void djui_text_render_char(struct DjuiText* text, char* c) {
static f32 djui_text_measure_word_width(struct DjuiText* text, char* message) {
f32 width = 0;
- bool skipping = false;
char* c = message;
+ const char *end = message + strlen(message);
while (*c != '\0') {
- if (*c == ' ') { return width; }
- if (*c == '\n') { return width; }
- if (*c == '\0') { return width; }
- if (*c == '\\') { skipping = !skipping; }
- if (!skipping) {
- width += text->font->char_width(c);
+
+ // color code
+ if (djui_text_parse_color(c, end, true, NULL, &c, NULL)) {
+ continue;
}
+
+ // end of word due to unprintable chars or space
+ if (!djui_text_is_printable(c) || *c == ' ') {
+ return width;
+ }
+
+ width += text->font->char_width(c);
c = djui_unicode_next_char(c);
}
return width;
@@ -122,17 +267,18 @@ static void djui_text_read_line(struct DjuiText* text, char** message, f32* line
u16 lastSafeEllipsesIndex = *index;
u16 lastSafeEllipsesLineWidth = *lineWidth + ellipsesWidth;*/
- bool skipping = false;
char* c = *message;
+ const char *end = *message + strlen(*message);
while (*c != '\0') {
f32 charWidth = text->font->char_width(c);
- // check for special escape sequences
- if (*c == '\\') { skipping = !skipping; }
- if (skipping || *c == '\\') {
+ // check for color code
+ if (*c == '\\') {
lastC = c;
- c = djui_unicode_next_char(c);
- continue;
+ if (djui_text_parse_color(c, end, true, NULL, &c, NULL)) {
+ lastC = c;
+ continue;
+ }
}
// check for newline
@@ -212,59 +358,6 @@ f32 djui_text_find_width(struct DjuiText* text, u16 maxLines) {
return largestWidth * text->fontScale;
}
-static char* djui_text_render_line_parse_escape(char* c1, char* c2) {
- bool parsingColor = (c1[1] == '#');
- char* c = parsingColor ? (c1 + 2) : (c1 + 1);
-
- u32 color = 0;
- u8 colorPieces = 0;
- while (c < c2) {
- if (*c == '\\') { break; }
- if (parsingColor) {
- u8 colorPiece = 0;
- if (*c >= '0' && *c <= '9') { colorPiece = *c - '0'; }
- else if (*c >= 'a' && *c <= 'f') { colorPiece = 10 + *c - 'a'; }
- else if (*c >= 'A' && *c <= 'F') { colorPiece = 10 + *c - 'A'; }
- color = (color << 4) | colorPiece;
- colorPieces++;
- }
- c = djui_unicode_next_char(c);
- }
-
- if (parsingColor) {
- if (colorPieces == 3) {
- u32 r = (color >> 8) & 0xF;
- u32 g = (color >> 4) & 0xF;
- u32 b = (color >> 0) & 0xF;
- sSavedR = (r << 4) | r;
- sSavedG = (g << 4) | g;
- sSavedB = (b << 4) | b;
- /*} else if (colorPieces == 4) {
- u32 r = (color >> 12) & 0xF;
- u32 g = (color >> 8) & 0xF;
- u32 b = (color >> 4) & 0xF;
- u32 a = (color >> 0) & 0xF;
- sSavedR = (r << 4) | r;
- sSavedG = (g << 4) | g;
- sSavedB = (b << 4) | b;
- sSavedA = (a << 4) | a;*/
- } else if (colorPieces == 6) {
- sSavedR = ((color >> 16) & 0xFF);
- sSavedG = ((color >> 8) & 0xFF);
- sSavedB = ((color >> 0) & 0xFF);
- }/*else if (colorPieces == 8) {
- sSavedR = ((color >> 24) & 0xFF);
- sSavedG = ((color >> 16) & 0xFF);
- sSavedB = ((color >> 8) & 0xFF);
- sSavedA = ((color >> 0) & 0xFF);
- }*/
- gDPSetEnvColor(gDisplayListHead++, sSavedR, sSavedG, sSavedB, sSavedA);
- }
-
- c = djui_unicode_next_char(c);
- return c;
-}
-
static void djui_text_render_line(struct DjuiText* text, char* c1, char* c2, f32 lineWidth, bool ellipses) {
struct DjuiBase* base = &text->base;
struct DjuiBaseRect* comp = &base->comp;
@@ -284,9 +377,13 @@ static void djui_text_render_line(struct DjuiText* text, char* c1, char* c2, f32
}
// render the line
+ text->font->render_begin();
+
for (char* c = c1; c < c2;) {
- if (*c == '\\') {
- c = djui_text_render_line_parse_escape(c, c2);
+ struct DjuiColor parsedColor;
+ if (djui_text_parse_color(c, c2, true, &sDjuiTextDefaultColor, &c, &parsedColor)) {
+ gDPSetEnvColor(gDisplayListHead++, parsedColor.r, parsedColor.g, parsedColor.b, parsedColor.a);
+ sDjuiTextCurrentColor = parsedColor;
continue;
}
@@ -312,6 +409,8 @@ static void djui_text_render_line(struct DjuiText* text, char* c1, char* c2, f32
}
}
+ text->font->render_end();
+
// reset translation matrix
djui_text_translate(-curWidth, text->font->lineHeight);
}
@@ -352,10 +451,7 @@ static bool djui_text_render(struct DjuiBase* base) {
// set color
gDPSetEnvColor(gDisplayListHead++, base->color.r, base->color.g, base->color.b, base->color.a);
- sSavedR = base->color.r;
- sSavedG = base->color.g;
- sSavedB = base->color.b;
- sSavedA = base->color.a;
+ sDjuiTextCurrentColor = base->color;
// count lines
u16 maxLines = comp->height / ((f32)text->font->lineHeight * text->fontScale);
diff --git a/src/pc/djui/djui_text.h b/src/pc/djui/djui_text.h
index 2d8f84ee5..89e6da0cb 100644
--- a/src/pc/djui/djui_text.h
+++ b/src/pc/djui/djui_text.h
@@ -11,12 +11,17 @@ struct DjuiText {
enum DjuiVAlign textVAlign;
};
+bool djui_text_parse_color(char *begin, const char *end, bool ignoreAlpha, const struct DjuiColor *baseColor, char **nextChar, struct DjuiColor *parsedColor);
+void djui_text_remove_colors(char *str);
+char *djui_text_get_uncolored_string(char *dest, size_t length, const char *str);
+
void djui_text_set_text(struct DjuiText* text, const char* message);
void djui_text_set_font(struct DjuiText* text, const struct DjuiFont* font);
void djui_text_set_font_scale(struct DjuiText* text, f32 fontScale);
void djui_text_set_drop_shadow(struct DjuiText* text, f32 r, f32 g, f32 b, f32 a);
void djui_text_set_alignment(struct DjuiText* text, enum DjuiHAlign hAlign, enum DjuiVAlign vAlign);
+bool djui_text_is_printable(const char *c);
int djui_text_count_lines(struct DjuiText* text, u16 maxLines);
f32 djui_text_find_width(struct DjuiText* text, u16 maxLines);
diff --git a/src/pc/djui/djui_unicode.c b/src/pc/djui/djui_unicode.c
index e97cd8551..4714be653 100644
--- a/src/pc/djui/djui_unicode.c
+++ b/src/pc/djui/djui_unicode.c
@@ -204,7 +204,7 @@ struct SmCodeGlyph sSmCodeDuplicateGlyphs[] = {
static void* sCharMap = NULL;
-static s32 count_bytes_for_char(char* text) {
+static s32 count_bytes_for_char(const char* text) {
s32 bytes = 0;
u8 mask = (1 << 7);
while (*text & mask) {
@@ -214,7 +214,7 @@ static s32 count_bytes_for_char(char* text) {
return bytes ? bytes : 1;
}
-static u64 convert_unicode_char_to_u64(char* text) {
+static u64 convert_unicode_char_to_u64(const char* text) {
s32 bytes = count_bytes_for_char(text);
u64 value = (u8)*text;
@@ -273,7 +273,7 @@ void djui_unicode_init(void) {
}
}
-u32 djui_unicode_get_sprite_index(char* text) {
+u32 djui_unicode_get_sprite_index(const char* text) {
// check for ASCI
if ((u8)*text < 128) {
// make sure it's in the valid range
@@ -298,7 +298,7 @@ u32 djui_unicode_get_sprite_index(char* text) {
return (u8)'?' - SPRITE_INDEX_START_CHAR;
}
-f32 djui_unicode_get_sprite_width(char* text, const f32 font_widths[], f32 unicodeScale) {
+f32 djui_unicode_get_sprite_width(const char* text, const f32 font_widths[], f32 unicodeScale) {
if (!text) { return 0; }
// check for ASCII
@@ -358,7 +358,7 @@ size_t djui_unicode_len(char* text) {
return len;
}
-bool djui_unicode_valid_char(char* text) {
+bool djui_unicode_valid_char(const char* text) {
if ((u8)*text < 128) {
return ((u8)*text >= ' ');
}
@@ -397,7 +397,7 @@ void djui_unicode_cleanup_end(char* text) {
}
}
-char djui_unicode_get_base_char(char* text) {
+char djui_unicode_get_base_char(const char* text) {
if ((u8)*text < ' ') { return '?'; }
if ((u8)*text < 128) { return *text; }
if (!sCharMap) { return '?'; }
@@ -406,7 +406,7 @@ char djui_unicode_get_base_char(char* text) {
return (glyph == NULL) ? '?' : glyph->base;
}
-void djui_unicode_get_char(char* text, char* output) {
+void djui_unicode_get_char(const char* text, char* output) {
s32 bytes = count_bytes_for_char(text);
while (bytes-- > 0) {
*output = *text;
diff --git a/src/pc/djui/djui_unicode.h b/src/pc/djui/djui_unicode.h
index 59d138162..f99735e6f 100644
--- a/src/pc/djui/djui_unicode.h
+++ b/src/pc/djui/djui_unicode.h
@@ -4,12 +4,12 @@
#include
void djui_unicode_init(void);
-u32 djui_unicode_get_sprite_index(char* text);
-f32 djui_unicode_get_sprite_width(char* text, const f32 font_widths[], f32 unicodeScale);
+u32 djui_unicode_get_sprite_index(const char* text);
+f32 djui_unicode_get_sprite_width(const char* text, const f32 font_widths[], f32 unicodeScale);
char* djui_unicode_next_char(char* text);
char* djui_unicode_at_index(char* text, s32 index);
size_t djui_unicode_len(char* text);
-bool djui_unicode_valid_char(char* text);
+bool djui_unicode_valid_char(const char* text);
void djui_unicode_cleanup_end(char* text);
-char djui_unicode_get_base_char(char* text);
-void djui_unicode_get_char(char* text, char* output);
+char djui_unicode_get_base_char(const char* text);
+void djui_unicode_get_char(const char* text, char* output);
diff --git a/src/pc/lua/smlua_cobject_autogen.c b/src/pc/lua/smlua_cobject_autogen.c
index e2cbf87ea..35a3ce52d 100644
--- a/src/pc/lua/smlua_cobject_autogen.c
+++ b/src/pc/lua/smlua_cobject_autogen.c
@@ -1228,16 +1228,6 @@ static struct LuaObjectField sGraphNodeTranslationRotationFields[LUA_GRAPH_NODE_
{ "translation", LVT_COBJECT, offsetof(struct GraphNodeTranslationRotation, translation), true, LOT_VEC3S, 1, sizeof(Vec3s) },
};
-#define LUA_HUD_UTILS_ROTATION_FIELD_COUNT 6
-static struct LuaObjectField sHudUtilsRotationFields[LUA_HUD_UTILS_ROTATION_FIELD_COUNT] = {
- { "pivotX", LVT_F32, offsetof(struct HudUtilsRotation, pivotX), false, LOT_NONE, 1, sizeof(f32) },
- { "pivotY", LVT_F32, offsetof(struct HudUtilsRotation, pivotY), false, LOT_NONE, 1, sizeof(f32) },
- { "prevPivotX", LVT_F32, offsetof(struct HudUtilsRotation, prevPivotX), false, LOT_NONE, 1, sizeof(f32) },
- { "prevPivotY", LVT_F32, offsetof(struct HudUtilsRotation, prevPivotY), false, LOT_NONE, 1, sizeof(f32) },
- { "rotation", LVT_F32, offsetof(struct HudUtilsRotation, rotation), false, LOT_NONE, 1, sizeof(f32) },
- { "rotationDiff", LVT_F32, offsetof(struct HudUtilsRotation, rotationDiff), false, LOT_NONE, 1, sizeof(f32) },
-};
-
#define LUA_INSTANT_WARP_FIELD_COUNT 3
static struct LuaObjectField sInstantWarpFields[LUA_INSTANT_WARP_FIELD_COUNT] = {
{ "area", LVT_U8, offsetof(struct InstantWarp, area), false, LOT_NONE, 1, sizeof(u8) },
@@ -1484,7 +1474,7 @@ static struct LuaObjectField sModFields[LUA_MOD_FIELD_COUNT] = {
{ "incompatible", LVT_STRING_P, offsetof(struct Mod, incompatible), true, LOT_NONE, 1, sizeof(char*) },
{ "index", LVT_S32, offsetof(struct Mod, index), true, LOT_NONE, 1, sizeof(s32) },
{ "isDirectory", LVT_BOOL, offsetof(struct Mod, isDirectory), true, LOT_NONE, 1, sizeof(bool) },
- { "name", LVT_STRING_P, offsetof(struct Mod, name), true, LOT_NONE, 1, sizeof(char*) },
+ { "name", LVT_STRING, offsetof(struct Mod, name), true, LOT_NONE, 1, sizeof(char) },
{ "pausable", LVT_BOOL, offsetof(struct Mod, pausable), true, LOT_NONE, 1, sizeof(bool) },
{ "relativePath", LVT_STRING, offsetof(struct Mod, relativePath), true, LOT_NONE, 1, sizeof(char) },
{ "renderBehindHud", LVT_BOOL, offsetof(struct Mod, renderBehindHud), true, LOT_NONE, 1, sizeof(bool) },
@@ -2734,7 +2724,6 @@ struct LuaObjectTable sLuaObjectAutogenTable[LOT_AUTOGEN_MAX - LOT_AUTOGEN_MIN]
{ LOT_GRAPHNODESWITCHCASE, sGraphNodeSwitchCaseFields, LUA_GRAPH_NODE_SWITCH_CASE_FIELD_COUNT },
{ LOT_GRAPHNODETRANSLATION, sGraphNodeTranslationFields, LUA_GRAPH_NODE_TRANSLATION_FIELD_COUNT },
{ LOT_GRAPHNODETRANSLATIONROTATION, sGraphNodeTranslationRotationFields, LUA_GRAPH_NODE_TRANSLATION_ROTATION_FIELD_COUNT },
- { LOT_HUDUTILSROTATION, sHudUtilsRotationFields, LUA_HUD_UTILS_ROTATION_FIELD_COUNT },
{ LOT_INSTANTWARP, sInstantWarpFields, LUA_INSTANT_WARP_FIELD_COUNT },
{ LOT_LAKITUSTATE, sLakituStateFields, LUA_LAKITU_STATE_FIELD_COUNT },
{ LOT_LEVELVALUES, sLevelValuesFields, LUA_LEVEL_VALUES_FIELD_COUNT },
@@ -2840,7 +2829,6 @@ const char *sLuaLotNames[] = {
[LOT_GRAPHNODESWITCHCASE] = "GraphNodeSwitchCase",
[LOT_GRAPHNODETRANSLATION] = "GraphNodeTranslation",
[LOT_GRAPHNODETRANSLATIONROTATION] = "GraphNodeTranslationRotation",
- [LOT_HUDUTILSROTATION] = "HudUtilsRotation",
[LOT_INSTANTWARP] = "InstantWarp",
[LOT_LAKITUSTATE] = "LakituState",
[LOT_LEVELVALUES] = "LevelValues",
diff --git a/src/pc/lua/smlua_cobject_autogen.h b/src/pc/lua/smlua_cobject_autogen.h
index 291e0f3ee..d034ce9f7 100644
--- a/src/pc/lua/smlua_cobject_autogen.h
+++ b/src/pc/lua/smlua_cobject_autogen.h
@@ -74,7 +74,6 @@ enum LuaObjectAutogenType {
LOT_GRAPHNODESWITCHCASE,
LOT_GRAPHNODETRANSLATION,
LOT_GRAPHNODETRANSLATIONROTATION,
- LOT_HUDUTILSROTATION,
LOT_INSTANTWARP,
LOT_LAKITUSTATE,
LOT_LEVELVALUES,
diff --git a/src/pc/lua/smlua_constants_autogen.c b/src/pc/lua/smlua_constants_autogen.c
index bc67f2ae0..d7ca5a882 100644
--- a/src/pc/lua/smlua_constants_autogen.c
+++ b/src/pc/lua/smlua_constants_autogen.c
@@ -1488,12 +1488,25 @@ char gSmluaConstants[] = ""
"CONSOLE_MESSAGE_INFO=0\n"
"CONSOLE_MESSAGE_WARNING=1\n"
"CONSOLE_MESSAGE_ERROR=2\n"
+"ROTATION_PIVOT_X_LEFT=0.0\n"
+"ROTATION_PIVOT_X_CENTER=0.5\n"
+"ROTATION_PIVOT_X_RIGHT=1.0\n"
+"ROTATION_PIVOT_Y_TOP=0.0\n"
+"ROTATION_PIVOT_Y_CENTER=0.5\n"
+"ROTATION_PIVOT_Y_BOTTOM=1.0\n"
+"TEXT_HALIGN_LEFT=0.0\n"
+"TEXT_HALIGN_CENTER=0.5\n"
+"TEXT_HALIGN_RIGHT=1.0\n"
+"TEXT_VALIGN_TOP=0.0\n"
+"TEXT_VALIGN_CENTER=0.5\n"
+"TEXT_VALIGN_BOTTOM=1.0\n"
"RESOLUTION_DJUI=0\n"
"RESOLUTION_N64=1\n"
"RESOLUTION_COUNT=2\n"
"FILTER_NEAREST=0\n"
"FILTER_LINEAR=1\n"
"FILTER_COUNT=2\n"
+"FONT_LEGACY=-1\n"
"FONT_NORMAL=0\n"
"FONT_MENU=1\n"
"FONT_HUD=2\n"
diff --git a/src/pc/lua/smlua_functions.c b/src/pc/lua/smlua_functions.c
index cdbd868c0..4054a3ebd 100644
--- a/src/pc/lua/smlua_functions.c
+++ b/src/pc/lua/smlua_functions.c
@@ -945,7 +945,7 @@ int smlua_func_get_uncolored_string(lua_State* L) {
const char *str = smlua_to_string(L, 1);
if (!gSmLuaConvertSuccess) { LOG_LUA("get_uncolored_string: Failed to convert parameter 1"); return 0; }
- char *strNoColor = str_remove_color_codes(str);
+ char *strNoColor = djui_text_get_uncolored_string(NULL, strlen(str) + 1, str);
lua_pushstring(L, strNoColor);
free(strNoColor);
diff --git a/src/pc/lua/smlua_functions_autogen.c b/src/pc/lua/smlua_functions_autogen.c
index c4b6b4f0e..e227a54aa 100644
--- a/src/pc/lua/smlua_functions_autogen.c
+++ b/src/pc/lua/smlua_functions_autogen.c
@@ -12318,7 +12318,7 @@ int smlua_func_djui_hud_reset_color(UNUSED lua_State* L) {
return 1;
}
-int smlua_func_djui_hud_get_rotation(UNUSED lua_State* L) {
+int smlua_func_djui_hud_get_rotation(lua_State* L) {
if (L == NULL) { return 0; }
int top = lua_gettop(L);
@@ -12328,9 +12328,17 @@ int smlua_func_djui_hud_get_rotation(UNUSED lua_State* L) {
}
- smlua_push_object(L, LOT_HUDUTILSROTATION, djui_hud_get_rotation(), NULL);
+ s16 rotation;
+ f32 pivotX;
+ f32 pivotY;
- return 1;
+ djui_hud_get_rotation(&rotation, &pivotX, &pivotY);
+
+ lua_pushinteger(L, rotation);
+ lua_pushnumber(L, pivotX);
+ lua_pushnumber(L, pivotY);
+
+ return 3;
}
int smlua_func_djui_hud_set_rotation(lua_State* L) {
@@ -12363,13 +12371,13 @@ int smlua_func_djui_hud_set_rotation_interpolated(lua_State* L) {
return 0;
}
- s32 prevRotation = smlua_to_integer(L, 1);
+ s16 prevRotation = smlua_to_integer(L, 1);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "djui_hud_set_rotation_interpolated"); return 0; }
f32 prevPivotX = smlua_to_number(L, 2);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "djui_hud_set_rotation_interpolated"); return 0; }
f32 prevPivotY = smlua_to_number(L, 3);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 3, "djui_hud_set_rotation_interpolated"); return 0; }
- s32 rotation = smlua_to_integer(L, 4);
+ s16 rotation = smlua_to_integer(L, 4);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 4, "djui_hud_set_rotation_interpolated"); return 0; }
f32 pivotX = smlua_to_number(L, 5);
if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 5, "djui_hud_set_rotation_interpolated"); return 0; }
@@ -12381,6 +12389,69 @@ int smlua_func_djui_hud_set_rotation_interpolated(lua_State* L) {
return 1;
}
+int smlua_func_djui_hud_get_text_alignment(lua_State* L) {
+ if (L == NULL) { return 0; }
+
+ int top = lua_gettop(L);
+ if (top != 0) {
+ LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "djui_hud_get_text_alignment", 0, top);
+ return 0;
+ }
+
+
+ f32 textHAlign;
+ f32 textVAlign;
+
+ djui_hud_get_text_alignment(&textHAlign, &textVAlign);
+
+ lua_pushnumber(L, textHAlign);
+ lua_pushnumber(L, textVAlign);
+
+ return 2;
+}
+
+int smlua_func_djui_hud_set_text_alignment(lua_State* L) {
+ if (L == NULL) { return 0; }
+
+ int top = lua_gettop(L);
+ if (top != 2) {
+ LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "djui_hud_set_text_alignment", 2, top);
+ return 0;
+ }
+
+ f32 textHAlign = smlua_to_number(L, 1);
+ if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "djui_hud_set_text_alignment"); return 0; }
+ f32 textVAlign = smlua_to_number(L, 2);
+ if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "djui_hud_set_text_alignment"); return 0; }
+
+ djui_hud_set_text_alignment(textHAlign, textVAlign);
+
+ return 1;
+}
+
+int smlua_func_djui_hud_set_text_alignment_interpolated(lua_State* L) {
+ if (L == NULL) { return 0; }
+
+ int top = lua_gettop(L);
+ if (top != 4) {
+ LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "djui_hud_set_text_alignment_interpolated", 4, top);
+ return 0;
+ }
+
+ f32 prevTextHAlign = smlua_to_number(L, 1);
+ if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "djui_hud_set_text_alignment_interpolated"); return 0; }
+ f32 prevTextVAlign = smlua_to_number(L, 2);
+ if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "djui_hud_set_text_alignment_interpolated"); return 0; }
+ f32 textHAlign = smlua_to_number(L, 3);
+ if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 3, "djui_hud_set_text_alignment_interpolated"); return 0; }
+ f32 textVAlign = smlua_to_number(L, 4);
+ if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 4, "djui_hud_set_text_alignment_interpolated"); return 0; }
+
+ djui_hud_set_text_alignment_interpolated(prevTextHAlign, prevTextVAlign, textHAlign, textVAlign);
+
+ return 1;
+}
+
int smlua_func_djui_hud_get_screen_width(UNUSED lua_State* L) {
if (L == NULL) { return 0; }
@@ -37389,6 +37460,9 @@ void smlua_bind_functions_autogen(void) {
smlua_bind_function(L, "djui_hud_get_rotation", smlua_func_djui_hud_get_rotation);
smlua_bind_function(L, "djui_hud_set_rotation", smlua_func_djui_hud_set_rotation);
smlua_bind_function(L, "djui_hud_set_rotation_interpolated", smlua_func_djui_hud_set_rotation_interpolated);
+ smlua_bind_function(L, "djui_hud_get_text_alignment", smlua_func_djui_hud_get_text_alignment);
+ smlua_bind_function(L, "djui_hud_set_text_alignment", smlua_func_djui_hud_set_text_alignment);
+ smlua_bind_function(L, "djui_hud_set_text_alignment_interpolated", smlua_func_djui_hud_set_text_alignment_interpolated);
smlua_bind_function(L, "djui_hud_get_screen_width", smlua_func_djui_hud_get_screen_width);
smlua_bind_function(L, "djui_hud_get_screen_height", smlua_func_djui_hud_get_screen_height);
smlua_bind_function(L, "djui_hud_get_mouse_x", smlua_func_djui_hud_get_mouse_x);
diff --git a/src/pc/lua/smlua_hooks.c b/src/pc/lua/smlua_hooks.c
index 9bfc6dceb..fc1dafcae 100644
--- a/src/pc/lua/smlua_hooks.c
+++ b/src/pc/lua/smlua_hooks.c
@@ -952,7 +952,7 @@ char** smlua_get_chat_subcommands_list(const char* maincommand) {
for (s32 i = 0; i < sHookedChatCommandsCount; i++) {
struct LuaHookedChatCommand* hook = &sHookedChatCommands[i];
if (strcmp(hook->command, maincommand) == 0) {
- char* noColorsDesc = str_remove_color_codes(hook->description);
+ char* noColorsDesc = djui_text_get_uncolored_string(NULL, strlen(hook->description) + 1, hook->description);
char* startSubcommands = strstr(noColorsDesc, "[");
char* endSubcommands = strstr(noColorsDesc, "]");
diff --git a/src/pc/mods/mod.c b/src/pc/mods/mod.c
index aca944c85..03f63ce39 100644
--- a/src/pc/mods/mod.c
+++ b/src/pc/mods/mod.c
@@ -197,11 +197,6 @@ void mod_clear(struct Mod* mod) {
}
}
- if (mod->name != NULL) {
- free(mod->name);
- mod->name = NULL;
- }
-
if (mod->incompatible != NULL) {
free(mod->incompatible);
mod->incompatible = NULL;
@@ -449,7 +444,7 @@ static void mod_extract_fields(struct Mod* mod) {
fseek(f, 0, SEEK_SET);
// default to null
- mod->name = NULL;
+ mod->name[0] = 0;
mod->incompatible = NULL;
mod->category = NULL;
mod->description = NULL;
@@ -457,7 +452,7 @@ static void mod_extract_fields(struct Mod* mod) {
mod->ignoreScriptWarnings = false;
// read line-by-line
- #define BUFFER_SIZE MAX(MAX(MOD_NAME_MAX_LENGTH, MOD_INCOMPATIBLE_MAX_LENGTH), MOD_DESCRIPTION_MAX_LENGTH)
+ #define BUFFER_SIZE MAX(MAX(MOD_NAME_SIZE, MOD_INCOMPATIBLE_SIZE), MOD_DESCRIPTION_SIZE)
char buffer[BUFFER_SIZE] = { 0 };
while (!feof(f)) {
file_get_line(buffer, BUFFER_SIZE, f);
@@ -470,24 +465,23 @@ static void mod_extract_fields(struct Mod* mod) {
// extract the field
char* extracted = NULL;
- if (mod->name == NULL && (extracted = extract_lua_field("-- name:", buffer))) {
- mod->name = calloc(MOD_NAME_MAX_LENGTH + 1, sizeof(char));
- if (snprintf(mod->name, MOD_NAME_MAX_LENGTH, "%s", extracted) < 0) {
+ if (!mod->name[0] && (extracted = extract_lua_field("-- name:", buffer))) {
+ if (snprintf(mod->name, MOD_NAME_SIZE, "%s", extracted) < 0) {
LOG_INFO("Truncated mod name field '%s'", mod->name);
}
} else if (mod->incompatible == NULL && (extracted = extract_lua_field("-- incompatible:", buffer))) {
- mod->incompatible = calloc(MOD_INCOMPATIBLE_MAX_LENGTH + 1, sizeof(char));
- if (snprintf(mod->incompatible, MOD_INCOMPATIBLE_MAX_LENGTH, "%s", extracted) < 0) {
+ mod->incompatible = calloc(MOD_INCOMPATIBLE_SIZE, sizeof(char));
+ if (snprintf(mod->incompatible, MOD_INCOMPATIBLE_SIZE, "%s", extracted) < 0) {
LOG_INFO("Truncated mod incompatible field '%s'", mod->incompatible);
}
} else if (mod->category == NULL && (extracted = extract_lua_field("-- category:", buffer))) {
- mod->category = calloc(MOD_CATEGORY_MAX_LENGTH + 1, sizeof(char));
- if (snprintf(mod->category, MOD_CATEGORY_MAX_LENGTH, "%s", extracted) < 0) {
+ mod->category = calloc(MOD_CATEGORY_SIZE, sizeof(char));
+ if (snprintf(mod->category, MOD_CATEGORY_SIZE, "%s", extracted) < 0) {
LOG_INFO("Truncated mod category field '%s'", mod->category);
}
} else if (mod->description == NULL && (extracted = extract_lua_field("-- description:", buffer))) {
- mod->description = calloc(MOD_DESCRIPTION_MAX_LENGTH + 1, sizeof(char));
- if (snprintf(mod->description, MOD_DESCRIPTION_MAX_LENGTH, "%s", extracted) < 0) {
+ mod->description = calloc(MOD_DESCRIPTION_SIZE, sizeof(char));
+ if (snprintf(mod->description, MOD_DESCRIPTION_SIZE, "%s", extracted) < 0) {
LOG_INFO("Truncated mod description field '%s'", mod->description);
}
} else if ((extracted = extract_lua_field("-- pausable:", buffer))) {
@@ -632,17 +626,19 @@ bool mod_load(struct Mods* mods, char* basePath, char* modName) {
mod_extract_fields(mod);
// set name
- if (mod->name == NULL) {
- mod->name = strdup(modName);
+ if (!mod->name[0]) {
+ if (snprintf(mod->name, MOD_NAME_SIZE, "%s", modName) < 0) {
+ LOG_INFO("Truncated mod name field '%s'", mod->name);
+ }
}
// set category
if (mod->category == NULL) {
- char *modNameNoColor = str_remove_color_codes(mod->name);
+ char modNameNoColor[MOD_NAME_SIZE];
+ djui_text_get_uncolored_string(modNameNoColor, MOD_NAME_SIZE, mod->name);
if (strstr(modNameNoColor, "[CS]") == modNameNoColor) {
mod->category = strdup("cs");
}
- free(modNameNoColor);
}
// print
diff --git a/src/pc/mods/mod.h b/src/pc/mods/mod.h
index ea1e6c72a..9f14f361e 100644
--- a/src/pc/mods/mod.h
+++ b/src/pc/mods/mod.h
@@ -10,6 +10,11 @@
#define MOD_CATEGORY_MAX_LENGTH 64
#define MOD_DESCRIPTION_MAX_LENGTH 800
+#define MOD_NAME_SIZE (MOD_NAME_MAX_LENGTH + 1)
+#define MOD_INCOMPATIBLE_SIZE (MOD_INCOMPATIBLE_MAX_LENGTH + 1)
+#define MOD_CATEGORY_SIZE (MOD_CATEGORY_MAX_LENGTH + 1)
+#define MOD_DESCRIPTION_SIZE (MOD_DESCRIPTION_MAX_LENGTH + 1)
+
struct Mods;
struct ModFile {
@@ -25,7 +30,7 @@ struct ModFile {
};
struct Mod {
- char* name;
+ char name[MOD_NAME_SIZE];
char* incompatible;
char* category;
char* description;
diff --git a/src/pc/mods/mods.c b/src/pc/mods/mods.c
index 9af7ab2b3..84b8fb497 100644
--- a/src/pc/mods/mods.c
+++ b/src/pc/mods/mods.c
@@ -184,19 +184,20 @@ static void mods_sort(struct Mods* mods) {
}
// By default, this is the alphabetical order on name
+ char modNameNoColor_i[MOD_NAME_SIZE];
+ char modNameNoColor_j[MOD_NAME_SIZE];
for (s32 i = 1; i < mods->entryCount; ++i) {
- struct Mod* mod = mods->entries[i];
+ struct Mod* mod_i = mods->entries[i];
+ djui_text_get_uncolored_string(modNameNoColor_i, MOD_NAME_SIZE, mod_i->name);
for (s32 j = 0; j < i; ++j) {
- struct Mod* mod2 = mods->entries[j];
- char* name = str_remove_color_codes(mod->name);
- char* name2 = str_remove_color_codes(mod2->name);
- if (strcmp(name, name2) < 0) {
- mods->entries[i] = mod2;
- mods->entries[j] = mod;
- mod = mods->entries[i];
+ struct Mod* mod_j = mods->entries[j];
+ djui_text_get_uncolored_string(modNameNoColor_j, MOD_NAME_SIZE, mod_j->name);
+ if (strcmp(modNameNoColor_i, modNameNoColor_j) < 0) {
+ mods->entries[i] = mod_j;
+ mods->entries[j] = mod_i;
+ mod_i = mod_j;
+ memcpy(modNameNoColor_i, modNameNoColor_j, MOD_NAME_SIZE * sizeof(char));
}
- free(name);
- free(name2);
}
}
}
diff --git a/src/pc/network/packets/packet_download.c b/src/pc/network/packets/packet_download.c
index ab5aa4917..3d26a7a30 100644
--- a/src/pc/network/packets/packet_download.c
+++ b/src/pc/network/packets/packet_download.c
@@ -318,13 +318,13 @@ after_filled:;
// Cache any mod that doesn't have "(wip)" or "[wip]" in its name (case-insensitive)
static bool should_cache_mod(struct Mod *mod) {
- char *modName = sys_strdup(mod->name);
- sys_strlwr(modName);
+ char modNameLowercase[MOD_NAME_SIZE];
+ memcpy(modNameLowercase, mod->name, MOD_NAME_SIZE * sizeof(char));
+ sys_strlwr(modNameLowercase);
bool shouldCache = (
- !strstr(modName, "(wip)") &&
- !strstr(modName, "[wip]")
+ !strstr(modNameLowercase, "(wip)") &&
+ !strstr(modNameLowercase, "[wip]")
);
- free(modName);
return shouldCache;
}
diff --git a/src/pc/network/packets/packet_mod_list.c b/src/pc/network/packets/packet_mod_list.c
index 2a4a7baf8..23a4411d2 100644
--- a/src/pc/network/packets/packet_mod_list.c
+++ b/src/pc/network/packets/packet_mod_list.c
@@ -197,9 +197,8 @@ void network_receive_mod_list_entry(struct Packet* p) {
}
// get name
- char name[MOD_NAME_MAX_LENGTH + 1] = { 0 };
- packet_read(p, name, nameLength * sizeof(u8));
- mod->name = strdup(name);
+ packet_read(p, mod->name, nameLength * sizeof(u8));
+ mod->name[nameLength] = 0;
// get incompatible length
u16 incompatibleLength = 0;
@@ -211,7 +210,7 @@ void network_receive_mod_list_entry(struct Packet* p) {
// get incompatible
if (incompatibleLength > 0) {
- char incompatible[MOD_INCOMPATIBLE_MAX_LENGTH + 1] = { 0 };
+ char incompatible[MOD_INCOMPATIBLE_SIZE] = { 0 };
packet_read(p, incompatible, incompatibleLength * sizeof(u8));
mod->incompatible = strdup(incompatible);
} else {
diff --git a/src/pc/utils/misc.c b/src/pc/utils/misc.c
index 34e70241a..e358b942d 100644
--- a/src/pc/utils/misc.c
+++ b/src/pc/utils/misc.c
@@ -594,18 +594,3 @@ void str_seperator_concat(char *output_buffer, int buffer_size, char** strings,
}
}
}
-
-char *str_remove_color_codes(const char *str) {
- char *output = strdup(str);
- char *startColor;
- while ((startColor = strstr(output, "\\#"))) {
- char *endColor = strchr(startColor + 2, '\\');
- if (endColor) {
- memmove(startColor, endColor + 1, strlen(endColor + 1) + 1);
- } else {
- *startColor = 0;
- break;
- }
- }
- return output;
-}
diff --git a/src/pc/utils/misc.h b/src/pc/utils/misc.h
index 0efabc869..01d813fa1 100644
--- a/src/pc/utils/misc.h
+++ b/src/pc/utils/misc.h
@@ -37,6 +37,5 @@ void delta_interpolate_mtx(Mtx* out, Mtx* a, Mtx* b, f32 delta);
void detect_and_skip_mtx_interpolation(Mtx** mtxPrev, Mtx** mtx);
void str_seperator_concat(char *output_buffer, int buffer_size, char** strings, int num_strings, char* seperator);
-char *str_remove_color_codes(const char *str);
#endif
\ No newline at end of file
diff --git a/textures/custom_font/custom_font_hud_recolor.rgba32.png b/textures/custom_font/custom_font_hud_recolor.rgba32.png
index 1ad909523f230e5293f4d9e5f2862811c89d2455..3f1bf60bea8f5e84458b49d6da86fd27487862e5 100644
GIT binary patch
literal 39601
zcmb@uc{J4V|Hu28F&GjvC`4u~S&D?R6lUxyJC%|>X|X0twwXfqEhG24Cn-rqgcpkg-g-
zRd+%)=c~=X2MTUiOu5`pkuD0yV&(9X(@*`)C8ch4iWqcCMq0Awevt-)b`Exi^Ia^e
zdB5!Nx@6w3EBwpoXi@0#v=-?qQMt)mOk6w`I!ipXenZiPwL|?-0^WJ`wMV#Mw)Xly-N=QA%
z9V%tfK8U*JUKzUOt$usPQs_5~=fLrq+6VE(vho|h4?BfyEiE>53CNh5%t0OT-kL{^Okgc0Z$ys?ui3JFxsMQQF%D`)|UX=l*dX=74i()R#36m
z8N|oUJ^4}+n*+lYUiL{b60CO)HO&H$`=rkc|LM-ti29di*@t|
zT&%wJFEp*^Yb57rnOn=q?60C)R9+#L^;EKcC}MrejJ#y0Hup*Mi)`5txFADJvhxsh
zD~?D(ss|YH7v1~^o`fH!D93c&`9kr0Z(rG&Qj;cS{8`zpdHZK==+;NL^Y+Tv()@c;
z@=-`(@w5N5IVND4Od@qm+rphG3xnQc4bk4SKVNun&i(doyuWv8*oU;aIZzYy{?zT6
z)Kd}ks#BqqH#N6sAJjxJK8vb*KYd-odoYc_i^kU)B?^zJN}`V8Y*Go2k2>67uQ_u5
zjqlCAH-6x8Z@w_Ttv=R=1Wyb9(R;X;uo3X*WsMgJUbYZg#0)yw|b9$KBok~
z&m%H!lyu}C`(vsjn~_8=_pr#$U`6i{
z8u|VDLinYbKeeGts}n5`kCS?oC%F-z4W)geInqmG|6Jze8)51MXIa~JQ7ZMO8v5@Xc7ZS7^+?BOF}-XX>KBz^
zMBaHUb%n;sC%&C{oL$0lHV3j9O64;TUL5*VCw0jTijrrgj?7|Ym<7fuBjp}lhezoi
zlJ(qr&WxQ+@AF9nz)BzTlIk%O
ztEa$twAiL-NaN+zlW26sYcxCl`$^?yg;Ad-OejyE{kr;^tQ?kKnk|;Ev=-fAaL+v)
zkHw=J9Ob4Y7%42ajw)%_$=sjQ(G?djRUmWCx^g3SW<#?qgX0=>KAy2)UuhYn8_hLZ
z|4qGo1W6n}O7N~*T;n6eWUvy1l;}Ixe#7%jc~avv!nR9g>s38ErO_~+;DkWm5II7W
zZq*UBK5gd_UHZ3hA)de=^?3T$GTEoZl!(CFmx|Tr@HOjdaJXqsNK(U*RP`DNE1(g+
zb36BbGoM6#Tf_(3-|cX$m``y5Az?}0eX?`;?;bRF#&>oootM+}`@Zq9mzD7lvHQB}
ztoiUiCq(u~yMj;Tlz+F)5LkmMPEW0+7~z`x~jhd=X?9tn?`WRGrq1F|DJX&J6#bOit-Nrn7%(2
z6HES})KP^#CJ0NChOEPr0SXgZc}wz^y?Vfi(KvBQbdTo-Izfzh1I{rwet0#jdt8k
z(2Vr)7MSWl`)YJ3o9jT+pd_(@!C^Sg*W)Rfr?AI4nwF|E-X#IWPjrg;S|JKU6AiEjG{Ic)sll|M>XMb&3N141cE?^L$K*XuBRdwLx>(ij;Aa
znY4B^{wY)a0?mK>ONb;f+%h!`eT8!%qwUw7f>cs~!8Vk7o*zopF5V}VlRB`d2}#Ue
zpo$SK5xoL%`BqFR@73ur8*@ZZ;tn)qx={)fw4tfL+KCIM*h&7_$KR?RmEbWvsFibnjF%YGH+KgF^)?wik
zHtTNoMab20VmE7`1OKG;yfbG^beYv;!!*&cUmCNK=f#7zLtFnu7|BLFU+s>E<58E;
zNh?RWbx{XQA-gmP71b9T@muu2(ntTx2eURxw|+X+g$=$1(%{8Mu4FOIaW$Vm3!ek#
zr>rY!|9(9F!vs4X<5Z3RhIYU}ZD{4$aME5Ma^2=XAW4=M2FuS0~%j3&Wi}L+w%q
z0^}*$;pShjrig1_eiurhQ5F>@oKUw`9kh?vFt!fg`{nfEN}TBNYpJ3qZ(p-TJF8WC
z4Nom*b^MPesRE~)Yz+*o+&6=jCksn4eQr!T^~CTc5l*==EhhD4RTyP39~MA(%K&B+JnWz3#TUTx1D`yUxP
z`AEr0yoj`5)E#`->UNaiQ;Xl7?}b;h1;)uJ?^Pq)gLk97Dqxl$eFFY&xf&Ime_TrT
ztCB~eU{@B6!(GeY(7`Py%QS$aMi#f!z?GT5nE2MxH}5n0vs2h=v;`r?Jnsw)^#E6a
zt{PC>|1O&?-{NG@(&eo1N15>=$)%<_T7$zcGEuLrqby_2y*^J>acPKBfMbgoA#U)P
z+)Gfj27wv&)>7z*G6K8UaR^>&FGDBLhRJ>0ZkQa`cYpax-rSUfb~RB!go>AYwDRW&
z#!fK2UQFT{vEa0Bol(_Qb<@gUV^g<%i6c=Z>?YT8@3umGi!q(JmryCPi31y`sF>}_
z-)&U0Tz0y$*5tV0<+AzJjoDvAnd;eN4q*XQ!Okqu!*Z&mXNymqw1DzxA-7
z*`J5DW_qM>`*F-|H=!Rou2KPn$L^;j7%N-bo|9cU$HMgX4gO7~$M9j6*!W`W#Dxod
z|K@0imXOok=NU%98dAe_4{kXQ>S|C2mon1xS2plwuFkCA#S7T<N%!`qxf^JBk5qZWiOAbK
z8?zBw3nSIPo}CQ%PK#0Z`C}ji6&n)WFYCiDOS63SKd0d$o*#zcdwoQZQiQp3=dbX}
z+q2e{A)9kgFjEl=qc!~pb;2|{BgA9~qzDEbL!c~Vk)u5)MWxuB^Hk-Zuh#}HUQ(QE
zpi-8}xS@KaS2n}G())KSj$(9t!J+!r3$rI#p+v7E%IZWH)ywnYtK-pR7+CiHL5!UM
z++R0usGKKiQMI(8wx~&q3(nwU8%ZIk^qg}N3`EB2U2>c%y*_+B4~5&oW+hy+&Rvhgu`gm
z)p}3fOz4#{y`7V;XC+53pm?R{rs1+4e@hSwEwd;K=OlWz1F`!>E}>3B{kGn1d`Us@
zz@dKzvTmZ&wArFunQRo;c1i}-SJ5?#Mhz8x@wF2zDgCW65zel*UPAdE&N11SOTV31
zxJ0A23u7-O^#n520_t_*#mYDbSC&`guD;RjA}ey&`z{m>(BmL3c1*ZWK
zm(I4gR%(~FaT#Ar4)sf?ZQvfX(yEkvwu5
zC3fR2t>ikl_MSk|NFMs(VW;;xd>EZKo2L$`t?nz|`+WO@a{X4}eYaSQ0Wsk8%lPw~pxa
ztB}<(XblEUV3g}VT1dS<*?s_h
zf_whwZA+dQA1iMSst$EBeJFiphBM-WqwPa4=GB?)w~LftVx&*c_@%fF{FuWehW*Pp437!XF+oV4B_M0QO_`QveJH~u&^z{@72i;Ww-QmVT*A3{atzFpf30ZDNCR6T
z@KJ8~!(PGHF=|Wa5k7dZ>gIIZBiA%lUq>
zZuY<1G={y_;x#cyVN)Cbl)H&KIa6YQFT>xT3e%0>oB1v9$s$TQ=XBR+5|?
zF;3++ob*NJGuPRKe?n3tuAz-x6je29On!>q?FS7na}WdP5rvYCU_7tQgQY8ak8ZO~
zQ^^J@X59*R5!Gbb7XEz0S{YK*hP@4o7JiSs+P1$Sdd1(hNt
zb`%60!Ljdr$>J^K$a
z=z>xG&eC*FKey}S5BCpGNY!pb`ZCrg&1rwZ*Chnx#Pb^y9$`*_b$s*_<8Xv*AXy8rFL{QW-_j>R+3tD(7pi0}
za=u)s%K6Mzsvfvtr+)pXwA1z$KmrAtITy>X{{G(g)?Rd}IcD=BDWdkQ`#g}%IUD_N
z?Wccu=j12{laaRQ02f~5S{8`8)6zJ?lqhzsJpteSgAYU(_d~@iblm(G<(3ddk
z&)RTW#dCy)SFUzwg;M)B;R*nfY_cQ4%nfo6I6cPp)-t7+!B`p7@Bgy}&|Sh&o%!$}
zuzP~t|C2Q1!GkdUQ|&cwEyPRnOqkD+8Ub^*g3|)r#L{Ck
z{u*QBh^m13N3u*s`-<(l*z0?Kw)E)J4H{4^z=KACe1Cw1q;~m7&3h7a$n2>`Pe-P5
zqvGYN?r{2Qi`5bf$^4Te6s`X=G
zdrspNfZ{qgW(&+rJ6=ejzr%j(ZpteQ^
z4oCVsZz$IqijvGD
zu$XO9sdeErBOz%2xpAM&pbWo>=HEX{0S#Tfs5vzT{wqu^{~|l&RS*@?{7wnnW%zoB
zx)l(0XK~r2BRiUB88`w{>jo(!jo^uPUVp8zJooTdkwuMkBrL`)DMb;k2P^d){P5%;
z^PnpL%vSR#BW5bDAIGBmFN>K%1DnEDCMI-KZZY0*;)e7f6jcu#(-B$cR&T%{>v#>v
z$z^UavP>|0Xdz7~X#b{7>`u}GmIdH-=vpQJS;&&lnt`;OTcwd%e}<_)oS5VD4Yjd{
zpc(%uk>ZFZ&ITz#s1LL8jm)G}&h(gw+~S6$JWS-8`JweEcR0DMO4$=Pyu__tkPPJMJU+V&*8b>
zufv@tf)Y$*AnVsyYvygd9Mo`sf^L&ef}v61B-0U4l#ZCw<$e+}mSI_6hQMEJa*}2O
zOi3dr{6i$t<90$fX2wpJf`z6t{%NhB#5@rX)$7O;<{&hFXKV{adYHMscddx`IrR5b
zYpw!P?>gYlZS3`6)t4h7{L0tN%b#vM>}n9~pMDK@8!?IX^N!*Zk*G5hD~x3-N&(HD
zIdgA2i*NUAVyJ}gY+M`X%r$oM$FzR;<@>JI^le6ndDOp4#W%x3huSne`}_6dWudyt
z2YS#z5GukNhB9K^Mp(6FD!b7wOeD`A80yn7g`$$~hgqi*LZ!iB-DvA3yc(09w0xRy
zw}hR@U`Vc(55B45yT|V)@WdUaFt^-aGq
z)RT`6#1S@njQ>ce$YBZo=E_oh0#K?;{AgwcHCk=_S}euiROe|*F@R0eeFq^hR?6cZ
z-J(^G^7$10Vcz-MAKX6r$icc!RLyf`3!7LUUImaCLN26kqY?B_utT11)@fqR
z{XXIZ=I73Vuljgrku)|=Ua+YJ@bm{#e@%aU7HVb0EJF|#Yrnz_=-pO;9N;%m;)8?h=^{SVmsu4?{XoKKBA`NH8)#sl2&*T*iKu^gr*L!0ctEfLt%OrdxiI*&iPCYzW=FZ&rc))Wx
zi_7m3w_BI?qmjA0)u;fJ-Qfo_xf7N$+;0R*&f~vv+@Ht%1)I88o$P_vLpqZdB?)Tx
zYb?#OhW5q{E}KHON@vRd^V+`0l2gw>_+U`xS(_d!=A89->gox@S8%Q2f|j1D_Ou11
z*6ve5m9Y~IMW#Hr;~LBMeh@P@RfJrPSsGzd2e^&B5+sQsPjQ4lDP6vIu=}~tcZVL>
zm-zo33oF3PNi;jkF^@8m`6gdsvMk~7DFmaFUp^x9&|SN5XDh-D7@jcnN2;lk9dlkO
zJK<4uxqzhnl;f?nm>I>aqL2
zNx4bH#l7)%e0p4Qq$(z6eN5HmU@0GUU0}`PA?$CC{gd2S4F9W`OgQmQ^S06CDjp^9
zw*r$_b-b4c4x#SwkLs%-y~Vs{q9m>&W92jfmk5{+K|KAj8uPcwJnEAI9{M>Q
z@t0P0zw0aO+jn@>VmZ%cZM5Yl2>WAirIl%zDyQN=e(K$V#VatIE6{^<(mA57$>4!P
z%|j<8Dj;I)@O(x||D>-RiipOstc@=ze_@!O7bus$X#x!JH-Q
zTmK-*Cgcxk+ff!c#_>Up%g9`+xje_Wo_s#32r;GPqn9I(v)cx6PfqhNkDYwSUsxf$
zU_{rDlR-{xuy@5Qheap5uSs8(uFV9^X-GmT1lB)aysn5
zet7JIjNVbI&Yi^Gt{c-2S!lm~W6edcaAuEM-f#WGoK5SHo(C$Mepmb7>jM5zCH#_X
z*C$!HYdS%rzaGQdHugzztBPG^?Yw8*A~6|oKa$HHiHjOR`0YMR?jFLn
z!Yu>c+T{}K`YYm9Jv=L}ap4=q$UYJZe;%7Zz#Drz+bq!BgzuPCAm|NK}OTvu_4x@{Ul@CZtU5Rn~1OtaI6;0?*a>r+Ygm3Q40hCj~k0hMu
zTk^|-p8L3V!ETbj+*t?Ycwz_%O<~uSAB}?A{zv&lnuzMv?XoV5GYV)TLpPFxs&b_j
zwe|agQ}wPu-}(yNhXcS>PaL7+dbHveP*Qey9S057-1uE`soHPi{*Uu%sdSIdr?VjE
zV0)y@0B;2Yw&8~W3xpI#fRhOyBg>09Yp=bT&Xcvb222c|=*sZGf;gd0Ku|
z37Ce*?mr&=KEs2?XLIn0FYE>hj8eoGTMleG*ZsnjadkKU?#Naj1y^-QramrbNKkV%
z{2I3OLBt~;s_$y#9Bd`WVq!jqQEx2=zimRn_PgNMjCM3cxry*CQyT>+C`j}
z$RsCfRq)14nh<0-Ui7}WtHNve2J<<%}?0xK0
zM{VA9P;HO3{3|hQMZP2qE@FiLtda|@|DD5p9fM-On_3B)>3;qZUf+t7jyT*~>Eq;t
z9bc}ay*tmOPvb7=!2A@9=qT+?7yOg;2^w%$JGsipJaOu}HtuwSUaMe@&HYZYVFk3Y>y+bP$RUK#Yr{d6%#{hk&;|AJ^c3rKiXRY#(@L=}s>
z0O5PjRpwTBk?U$@!hm;~^wQlCoI{{hdEF9W&7sQkS6zQh{rPmWUyG`5eQwy}VstAh
zaoaCG1DFdMQu99KoS3%sk{6h4N})$|mplUU=Y^XqLc;bgq143O#;rG(t5xElfolIE
zrLYRZ@br1;Ist*I1EG}Hv+NLWt7Js%^fG}J`W6n1?z%Z-Y`~Fs1Lho1zly$&-$p|3
zCZc?`1jVqtujb5rZ-;5Gsjp~qP-A$JWri6RUqyalMiR*MDfJ#%EF5df8bcPxgm+cd
zi!1}UZOkFh4!QlJ;G682a@=A9kc*&qFksZO#ll;6XR=obz#}|ad2zJ88pJAgl+pfT
z-S3V~aAXDojSPU|26qJ_Q9)qMxf6h$Ob_yu7wXt?K=O1=)?t!j$rQUU}%M8vzc2M>ja`}c;<4V`Uhf19cR83G#hhhc5kKNWb(mNVJBZL1oqZNB{1~`WS1xWV{o|Qo)O$3^aOa9%{PkDDlY9fdk!(D}p!F$S*SU7fU;ONl6A
zq*}>AvF3hF`wup~(yuimK6=9YzNNIw2xkD@_#%$Y&)kB4|e!InST&q-)K1H&r_*LL;=?2WATE0|j-@qkPf_xLSb;^qk4dGvFDH#jXF
zH_k%GHQTBDck=M_4BliSQtlB{KbzSo&kflN#ivD_Ks&Qb5D&Hr>1IsG9gufMLO;zU
zpu}w}=Bo%KP7pFq5#O9GJ{7cNTgTzh4nM}B55r5tm77QC19EVvyZ_PVJv#cHJOj3#
zs(CFxdz4k(nrlXAXPd&mx#J8&&3Yp&kHYXA_dcEqsNb68hgHm`3u>LMvLsG#6+vIIi@Vn86jpseGzhv-{GI0|#_8pTHmb4?HMd<$0ZF>|oy
zhUPt*#j;8T;R8QMJ)6E823LqaZOsq%@1z+eHVC7^{kLMmNX6)J1knKt3jTJ`rrWV_)G}tj*y2c~+O{-q{~0Q50;D
z|H46H_}JnNx%fOmc)h1y6VScR>jD*Wg%N%`1_tgkfS&Y*e$|*{4jXCYk6+ynX@pII
ziceElo9H9`?~e}I<(duhX%%5Bpi+049b&C8DqO^s3u=;?^?oEK?5~N5zM21+2bonl
zAer|j*NaWg_07JVr~&c697jC&@BIPNOW6fJ*a(v%lR!iKgEAku`LVDyJMlj;jeY2Q|2(2@RTUfGB
zxrrj?3Q`$l4-EG=HP^o3l)EOet71?x+@BQpdDTzhtHk{_gGr73CX-3wJJQk6wL;1?
z3Zh_TLWKoH(*|Rj9`J}tEJ|}T&_^w%wcc83DliV-3vl`Xlkja-e#YApUe)~hGB*qr
zPox0J`Qxb)BliqMlpK2@Lzri?-y5EB8Z>
zf;~E%@(Xwn!Cz#`aSeY<0tH}Ye`0jMKde4SdFI~W>PfsQxceH!;PpR8uef3z#cJK9ZlSPl
z-PY~$2zDe~7Y#;3TX?x!TX_ffP<@
z4AbuPzuUv1B72hWxjzuDxVX+bM83c-hH*nRC_NISpkb)6T2~A1H$ShgT2+1Hr&-lk
z_slJ-KX-SXpU!wW$o606VgFkJtT*v>AHanwMUL{zqTTUcKA+T-E`BiZ$FGlqSg82~
z{u)!41(vW=j@F%V8H=1Zx|od)P$9hGzA#Ys?%YtV^VfTqu3wcvoum&}`urzT+WG9%
zP2P+Rpie_Ddy0Zh`?lurjk;8on|%sNY`SnfJTCx&PovB4Vp0$l3?i`@%MPj)LEU+}
zUT4f<-Evt%uNyyJ|1|q)d8?v2?TWIo%N|lC`dnsx8o?383Qpw%?n_|&N
zjUB8FSp!dwx!h9%J4ev^bk~;E76{6WfJyM+q%P&|n~UYmUL#dK={k#Kpuv9a@})Id
z#%@-%A7~$N)8}`#mUCa|gwrTVhoM~vJsQ972tBv}3-|n^JSDfwyXAMZi=a}|J-EF{
zsVEJ&76;e5`mE0m22<|-V?PO@pDS?tnNIkS!{^&=;Vj0MnIilozuurE*|xb%e+T2?
z4~$*wg@6deFla8BgzYdN*zJ2a4_{zER6fAk{GqbtWy2yzeglap_)Ox$R69m#x3A4E0g
z4V1b>ifV`4w&k=-JZT+IR9UNB)&czey|qmmPGAfi2%iLn&1XQ|(TW{tv7v+GN)dds
z6WFZS4YT+*-??7-Wdz7$q=&I|m;clb3)b|#cfxIb6LK=B;Y8ui!V*tBasZ}bN-7`d
zW?3N^2MDhrh#mfPv(=N*O<$a^AHKf`(!7-t5zVJqGNM<{vK4>G114|n&1S1_!xu6T8Llac*KHD}?2w2vW?uY@8t;E2}LSV=tT}@gLums2i*(
z80b|eYY^5KB}2uIXZ(b83JnnEG768N(sW3ze>+ajTv10NqBVJ)CC^?zQte+09UG6y
zkcilM7V%X`3cRkXPX!h-&|2YmC!!rZ&xCoU9+cJvK&eeVml4gT8QrvYIUdS%>wZSX1
zLjq)54S}2_^(h|U#uj2z2ax;hAv^@Y?M=5|D1S|GO;qi^{D^#LNZcMmQL|A|8`RS-
ziWwWnl27iFh`HW2KoRy&<$aL@Re>$*xb;ik#30e2jNY5|l)o4@FLU?oMA6xZpY&~f
z)BZx!{k)_|zN@FP_q{yJns%L+>mDe9(#TMM+A2Ydl*4ER`>Md4`z7dw@H`-ymtT;v
z?2@aO;aEeCdbRw4qrD$SGnI#v%RTr7Pv;~ZGeBTpLikB5PJ7n-3$CXm1y1!%p^CxY
zJn0pOxGQWA`C>ACMz&IPc3S&m?s&*v0a3_-S%O%N*XPLCcuC?(yaDRA-<`bgYWhs1
z`CM9vYUVtY7?)YG8g4*GNu=qx?osYMK~MJTe0Oq>^VXwHW(Wd9(dR+elmE@g_C$ag
zIYbZt;9a2(yPK*9mCn{U(h|t$4nqS`wla{NKn*aoDzJKv(hvbDFz9A-V=qq)2l-BS
z9`v{vYv)xt`fkI&*1rmjgL%rw&{`xeT0r_6U)#E>G(%7p@{EIhI3fTU7#d82QJmN-
z0eoJ+m%g(qH-z62k*x3U=6nvtY-bS)Tim#zA8NXY!Z4e7Vlo2CzU?-2e`YYTr8t4i
zSPF84Qn?tva+#B~MwC9-BvwPK5zu)2YKxfc5MPU9uwYLPADYIDv5FXsE0t?L&B)jr
z78{qLkgoIy^2M7Q^@x>*pG-Q=nqP@J})^fGYN+5GzP*N`(^c?!#R_G>w9n0x(Lv*T0O
zP!ACQ^D32niTr>E!DP?Dy|DUA>isBaOht}%l;@lh+lu;_kO~3Zu@!DyN9#UvVKUu|
zXuc1!N7->aYRo@J;S4=>A+#`41UOF&Cuu&o_^qzW!o=q>wz()b;zji-qNomh3MXe@KikgRM0i;~1X
zhM+gA{rU@=EKpFqDr#)8!MQpt(3XiQGz9$=V!YcE7?#~($n}AxH_TEx+jjl^wIi3W
zN*w-ZbFyrhw{7|i%VrL38f-C>QnF~D_s|Bilc~=SS23@U!DQ2MQEUYd2m`Qh{EjJ`
zVYf1LvU3MkuisEtkjLyzL}GvPb-^sF~WuZ4CU+(-W
znga7{%C5-#*$9ErPoU-oK1h=b8VDke293u#erJX0m$7#Xq-F_m*0Z2xfwU%eKOnm33@{E@~+2
z>3(#RAmF7R-=%P+xxP=qA_HWcYnP=Vgl;N`5X)o}pF;BDjTsQgQ|v~Jo%ug-y?}KWu-8`
zzBv2%3ty+E`lV)AuRpT-+0R{BXYu{KAmoypa`05ei=Ph`KdGLhz2$XjmJejDSGFXI
z{Ca7crSh#KM|)WD(#O`fmp*+5`cL^YQjTAu$>&fsB&-uwwJQkL)Gg}ntiS9t=OS)R
zHvr5v0(9dC-|p`ZkBkD*^pl*R0#E*zNDcj;T_XVJ5HBKM&
zrSC%KiRPr6QqV4KsE;=4#!smWo&axbpY=NFe*0igeVyh9*J7(-_ETV$LD#Xwx94
z_G}{|kVKsl#dVcsumAql8%1_CL%aS96z!u(OEx&@Fx9RpAl^Ncv3M*4K{
zIsa>3!?nHa^&nNz^RnrK51aR7YnnGme7^$)`{g*2a6ULzpF^2C;h$v@`ML(KJg^(V
z)U_8F|8L7)QoO;Gg^T|vN%XkDM#}24lyPz}XA?)6oq2t0N|I5kw-q!}8``f8A{0-;
zcQRJh+?siuVgO~X2J>^r*%`ppKbWTgBJUvruxl(r*dugwV+(T100kxC7SiN?=p&)y
z$%%-ZBA(C4jW>eP#BfZ3j?p$<;Q=mqXQOypJfbdwF(ky?EBoy`PgI?TLeH{s8XZJ}
z7Gw{Vpg2KZXH5=H6eNSV%+13IZOM%_A)6&NAi)#5z4AQhOCg8NgWV>%Fy-k)lQIAJ
zS7h#Fkr)$?+Z22;J7{WgehZvjZ-YV*OUQt)2OP4JCZ
zTFU@3T`SRofC!I66On3Srvlrzs35;Ox;ft;e)0iQb3awJnZ%$BKY>aGU!~#dMb3vW
zgB3Q*{C^=z5^d>JE&~tsaFPkET3Uxsgn%I=Jo`eLwZhCMw8=wnbgo=pIRVUc8vREU
z1Q-0t7>mIielH;1`9*1*#G;W#K`&L9cEnZnDB766Nf=-VM-vDTIwtCf2&bqqQ3BAj6oO4uN@T5Od%Ay
zhh=w=^TGRzvVx-Wegh9
zm@S#d$ET=0L00exhnsWoj7P>&YlQ*qZJ9;+4QseD?Gb|aZU-Eebmi*aA-3BqtQl|o
zjkHcD4Ys_$L{Y7TN>3^?v7ZMdA#1+VxwMW40$VjE?Z|QA0PKv*pR^@)`ZR%AB$RuZ
z)lXntU>y{NS6hG>(zy&%TVY1I>|{ZT&Mjck)|sd^kBgqbqxi8CHJy3DtmuDiRbX_7
zzd`pK3VO4wJop3o=cg(VYI)C|4}9&3DSnj+fbKd}ZL=LDoHiJ6q-+VdCB7OtgFUd9
z9rCb_W4Z#U8aXAN2@$uG1Z}v9A8M75R?Q}f6#b>t_wMz;o=+c?C~gLe$xw>GwkSE?
zygD(HNN@a?k}+wa-3nJ(!ZL*?F*}&2;k+=s=xk84I-(z7)*iz?c_2s>gf8kF0Zuwx
zNpd=*Jp^@o%tK+6Z`MZX9fQQ$Vto;ow8uX>E<=@Silc;X&ixAkS(|0p<@x{(5D)pf
zihz2Qn(Y$qp%za`vh~Uf90Jh)!^FyWr1&A>s|Z(KxPVqF?_zYU7u)&oX3G1il2yJu
z#cHr2ywrggp!(}9!f`M05Y%p-c^%HlE4DIzjDspt2#NXfufeIKo@;ur;p
zVbnAF-b797MTHu47C?0JZMeYiq;{Eb-q{y_==U8Exr;$Jq7D&3iYufi>kMjN+h`R2
z@S6IuCA$gZ4ZSs_3-?*uFyr&bjfQXwp{=>l)0y0pnR3MRzfLM_J&D672_{U+Gea3=
z1X1(L0D|>=Q(3~fb3jr#f5JB4uQVbml3@#n)(jWWX?gAXFi7*>0Gyb}DN0f}a^(uj
z9_rQ!{Oq6G2%?P>IEps1pN4iQZpFRPH(#sFz)1@}AaO<71)}X!#m^?o8{*|Cu{!=f
z@Ir<`J#t)3Buzyk^}F~z)e(kIc9(qnSrrIa!IT3Gd&;wFpJQvdJ8OmHf_;lq<>b|{{LMDj=v>6B(ULid8FJ4(4Jv(#(86=6ia#k*$<^z1jt7OOH}rYeWVtJn
zNRO`WrQy!cooI(&+|fLjTTaVW;44f(b|?b$Tiu4^Z13_@!iL>*G?3LJVpIP?eJr=l
z+Qo0W*X;Hm7;nL-sQ+zy1t*^1P-{fN9)uVog%!AyB_PW`1_w3SiQ0bcLn0+EGA|7c
zih-O(G5Ecs;^MZm|sTcwcx+$H2l>vs;28vow2<>#_8Qy-f
zBO*MubMwra#}Y_p7k5dz?jfo)g)nsij@Ep13F^HWbc7q)6Nrt-_f@GYWJp9HV6Ir6
z#MdA9fa3pz8y7_Z^?@NAyS@_Zoxxk$d*r@f)7a4~88>)fP@4)D2oiB<6&j18r8NGI
zU(FDl^ahC1_B{xQ_waGG%3tz^&I2!DOTn;D8r5>zGg$evJQN9nAn#gYPLy>>f?h2;G
z(|<^o2~Ii*UKu-ZtPq3~?X+9JfPV6PsVMql2uNcF@zhY>B#eg;Yy8hRQKK8o{}0V<-N4=xCaD
z@{2!LadkJBX;-nV?q&SSX1unXTp-F)L3wd?4P!z+Gxr!4y$m<$`D4Uh%-|awbF#qF
z3y3S4YaY>le?XROh-@s_OFNpS$0Idv%X<+OrDf%O6b3#eAo3I^_;i44M^mGT8;$vn
z=>KS7phGX6gG$x5rg1FI*(Y!Qx6GA}X((9Qth=BDKDOYau*Am)>s^CO-A7Ye7iU5q
z1~wYs6&m&?pG6?8^Kad|5wvHc5lqqWvFK~NW&Y~9wDQG_jDS*g@3py6kPEI(e%AtO
zC=&HYO77?$P-;3$y|MJhzwGxj-DJwyxbM`;59?iMM!;>>9lkTk%Fdgu=;-MixQfM3
z1BNimz*=tn-19DN|Ea{9TmRxKTVA*7@1FXfqWC`Wl7N0(JP}nBxOhXvII~ei?H<3E
zpzMg;q|$V5AGIi%IW*696KA5$@rL&!4Ro8gMccijxFu8y{Ph0AG1Rkp7%HA{10P`E
zc;wrKGPZY>iG2C|0CD*5)>;Days*?C=&34nQl5e6H?HaZ&@JYjr-Zqg_m0{P6i|=E
zWg`og0Umj(wZ}e;&9g+=nfbzGZS&oQXvykql!+NLX*}`<8}w5h%W>)3|Ko`S|Ia5%
z-hHBL
zp<(6S+}>xG#{2HWQL?nR?l@^eL?A#-GSF3!xA+l@mO6~%ylkX#CG;8z-38?6DCo63
zB8wgDx=GT8bDw^-n8`n4_U{8o_L~f
zN95+za>Hj3JEo7u3CM@A
z!RRZ)JlIXfM$#Kk3dR)yFuS~{pXcj9$^H?M#-sNocvX^%+7_Wh8}m2E68d&dQ|)_q
z4~9vfx(X1|_4521v)Tq4{QV<2`I42OSMA^xjv8aaCqZd`dbce9jP>Bq*{~aW6nD~@
zP>n!O1(3@|FR~6RztUG8zw%S0N`}_4J1CNp9jA#OSZZ;Av&QX61o!RyXvIAON(>F#
z#!E8Cm$mM!HKnrvZmo4n7H3#V9R68g%$FNdzA*xZqu?-VcZdeA{Pk}KC5fbEE~20;
zI`K+_?K-$Iy3}J3I~NL{u34D=L>7mkMW&?jgSa#U=-5>3&MwwJT_;|$A4>GMrtf#&
zE2goc%>r$;YLy@$9|Tw`=x&{623`^zNPRfEYb8E?G_QLenv@_?rP-;{6)BM(O4{t4
zbMKwMbLY;Qb^LJ_OFzE)zI#7yFUQXa=BqvA?ihZmZ3T$_*rPCXQva7Kp4DsDo-627
z_K&xL4tG5z+X30F93HErRV5F@-y`jB*A=Kg6Jc#wzc_Yhvb`{Cpc?CluAv}<|NIXH
zQ^$mgOLHQYGI;Kk`Dft+&+b$_y@i5IQKnMf@hkleRdev>P9#L~^0j
zS;~S0GRqXyyf1YtE_=K+O5FWnacrH#!<+0%#-#Wsp0W!H)*|ujsiy^5hmkv2?Lxg;
zy)QIqyQCVIX*{)}{L08o+bmD_6E#~e)jun_ctuFrQpk+q)XM>&-e_EAmUwBQX_})U
zL8K1h`UDh@ZTW{W-;;Md`Pt;_!U1hY3&zAqyhf9~G0X&Gvan7miuJlmPN$j
z)@Kqo?fNK;+v<Sl2VT#-T3x4mQK0>Ubs%n*L@H5#hwBrP^zy^boy5gbF)Uucec}DnV?VtI{$2JC@*_F&H#mUR^yv*m
zj$~QUd_OCeLNpN17F-HGH9fF!drm8LNb~=qB9q^hR8faQG|Yd!u&-)#svmoGZEG`6QcHpm31fvWn%yO`@
ztWLhh@&CHUJ906$sheY2&LynOyIegIKn9gzpz1K~@nj6nHd1e&_6^ygilG(RYE|Q~
zllZ8uVC%hhhGzX&1rrnt;&K}^&UFdiky<7wUm*>~drr;pI8){q=c~)s=c_;K_NR_c
zo^;qRRKDHxb&1HW$Mi0J2myJWMn@G*-q@$H6J>|Z@xSm_P3<_waYMAZ2|#@9!<5(T
z=lmw^%s~22NJ5Ed5mO(F(qz&ht|h?)oeV!(Et&2qwzUn4hvpsoFAfv6`i(vFLG1Iy
z*8>@Dp%Ou}>r9f`sO3EhmIj1FmZs&3HJ>=pu|^FQv3rf5nso_0w>Z$Q*43~n<7DG?
zna&0jkG~a8Sr@9A&So?b7q{bDGW~URM{&sOLX5!kxEwN}|HmT(4E@0`Ue7VhRT5
zw@tA8sQdHv1+I|`hs_8Eve@IUBQ*$5XmVK5B_iOYg01`>DH^@0ST`Y<&Aox7~MXa=d7Px>wu=-
zlXh{6?|0|DU4Diu5&?&wIAr~$o4(6YAXqe|D;2hR)21>J~d(KKjI!c8M9S#
z4P;(;{={V5q?q>tQg+UxAu$-dPXQj6SA(_%@zh(;vcI|8Gk|3$pR|rMZ5YVX@whq(
zFX?s49SpdOLIFis6f?iU{#_902&C*lq!nSfM6IhS-CA2^<#)+|3nud(#mkj(v%6z|
zO;@94*kC!Imz5|WkHY~xZgVY=i@Thdp}ldD;XQ=9B5;*wU0W+ijl)|%@?i?<}coGvqqJKb|w%pASv(!w(bqtKm#A-r>T#f%D%_cSQ|P&k|tc$T?%e;T?-
zcI9aG5qgBDE2D^aV5=nA+4F=l&BBi#1nXYndCS0S5V&fN$SyZtMnCLE(S}V^UvP
zF>nS#uSU`MV)iH062Pn1HU9D~#aYEX#UtJ6>>Ng@Wd8T}{&$UvE;1PbR%xx8e_)^c
z_V3O1p-X>xFIwmE+1uJ=sNB&dMVZ;=LCZAXjz0h#UmC+)pfz$EsWEc2Kj+_rePa|!
z1UU*!9cWdF^>OgH7GU55k8JFhRf_19Z(Hrt&z+M7zIw!yRzB6K|+q*e^j0*rZ?=iEROZ|7N)^1Wq4s|s)
zJUjT9Wh1hE8v!@G?S18?#VuP2R@>a
zp|&+Gu)r=izA2O%0ZA}PwS@45?{-vo$7xsGnZX=Xa?Y=Z=IYH3u-B`iS5_zUjD|MY
zZtc>0FL!%pPrD;*ohRr#pX&bye?E<>Rj;?BuaEt$!2GwZG!xdx^vBzkpQqn*s=8F%
zaxl(0;7QI~g*|RA`@27fnLZ1d0uji&W7o`Ts?)K!*bT~DNQzP~2Xrz=WyqP{pgaAr
zI(+sKPhLIw=N-O|*I))M{XMw^Jp9rJF`}nreVe9nNa<1O>>u*vTnk@iENB#q#Abh1
zUfgkFY|LM;1d_^r0X)ymMARxYu}67$Cl}Dwzrb0LTC1Q!yUu3_HdkGpOeLgw{kdP*
z$PgdOCJnwGtn|z)G+y~VR9`;h;d|K7{MxMFc>dMT=lnH*xmEs=24Ow#td5|p<;JQ3
z82^X&rs^!uc82N?e3NcE_R&~(-U>J^z^Bh19O*-51BjtB1rE_x(pw=oj0{ic1zfT8sIrD1h
z2+yH=^v;xCDcf>i^e^iV@cJd~^gZbL?tDt&zem``?85%9BW%$={UvAVBlK{Op19#N
zAsDSpKlFDQtWL`fAn)nsY3St)!#}-trMWONzLAu2YeI5Qg_aVnHl@+3!e`w}-Rp${
zJz8#SWX=F6Ja$?1(Fbq{luy&R#?4Vp5715=@^MGDhkv3h4^(5x~d(!qFt}a?XNNP)6^D8jVx%^A=OW?NNfvYj1z=m7Y7X;CV
z7a$c;%NX~xpqB_u>e9Y_5-ISD6nf`S~}>-J`$q
zB8ez+qTm3MuY64_Nd>v%n`}sw2GqOfHBgw&>zXY64E9V^X5JlaC54r`s9ab!61WqA6b5nF+Z4
zNSCE-?-i%>+f=ZCgrDRi-L!4NGX9!?88
zD4Iy9z;VzXSSiZSL**l@9tk47u^t9NHhh<1ld1qc;Bs-9BgW*#
zUD1+7V`C?EMN1)GBPE3@EkSA;XUaOA(UfP&J||`+B@apos|>nKv81ENo49q89q=2fv1PmX5z{qEnjqS<
z{`ANwNv=ofw1)w?J3UHf%y0=kXN5~QlF+}0AfP9^u5
zcjjn5kMRdKUOYAe`SyR3D)x5GwLFhw|E91i(l+>Vc_`=q+_G%D$DqLplRL?48t>hw
zhDhQ!A2r$(jrm_am#Y2!AV}9cSLEFm@<0BXp->wE{m)&@Yx<9~DKF?yjfcRuL(Zdn
zb@D$v!McCDchj_dPPM|`4BOk=g0KHjt$e7bjxxUl-JPAWI{d?e^(6-1_c#84ZH8q8
z75~56Oq~j4rs7V?-LJ=BPwSa;$L~UUjPTOSAlPhr{hOyRK0r3X8D0cyH-F8ihvssD
zWA)Sa017Vt{n@)&$N#fl4in15Z8uR7mNDx5CTAtymi%M$ec<*_wUWI4#bY6{3KQGF
zcRhXY`A>!IW8~A!6*4T(TQwMZfR@U%ao6#iIBsOVR>54eu#W%9xr82sVz2ndBhl+B
zh$YyXcpqvrxgk+{9K&Pj{zuPaVI8ZkIMk=?+wKh*#^14&14UhdJFP%3_HmQ=_k^S-
z5z|9}#Lh|5e4G>w5Q0
zk49Z9qg$4CIqy4tByz(x$HAkn;cTb~`UQ7Yuo*wNhn{S{&T3mRwIB@X=&$rsld+57
zBj7H8o)EQ8kT$_EpE*{;)A+(}(N{V^n#?PZ&v2Ku~ccpkuqJxbK$;FQf}@{Ji`u9Ymj!
z3N2nKbz19@#rqCIPmlP|Q&4=Y4sj*R<&xfI>UWZ#!aOezcbBOf!`%l(-EBVnRKu0b
zF78<6L9kj^8a5zw!Thf3ERtUBQwqKOHc6ijuf7z9^lAGn*w*c`Br34d|Rj5P!Iz4HZ<~Dy(p3p)6
z<}Ib)nxyi@3%aM`+S_G`IUg+QbWB)aAfJ_V2iuLYMhlx-@LS%JyW33i@~ijXm8xOE
zj4++E70hoPm#TLV89Ho<@OC_It7A5Ocby}>)C+049pUfU5oRqNQGlt5n=&37&=qhO
zq+A+JUXG=DTnnsNN(}q`;AGJ&ESjnxJ!5&N@!93q^BD+snr~#Q?vhTm?>tl@M&sK%
z!F}$x^k{EFq|qO)G8(D(OS&y1E`gV115S5Pusq+3(aABW0D+vnh)le9-k3+OWN4T@JsqVvZ=b<8}sgPbspujTplOg9xk^7AvRd~#n+dJ&N;|W5U
zDWBdp9h^njqqOQCZ&JDxj74x8l0hU)0irHgXz-C6qy#+%PWI
z>+AL+*#;+pZ@;~Bza9Fl7nAb7(fcva5Wveh03(4WuV?QVP-Xj}MZPpjk5KL)ePUEK
z%y8_phi6cT^mg%h&i*iSik-1-*vQV;26PlpZHcDdf}+n5X*m8hL4H`T~%_4ceB38aMKtW3V`j(-g3
z+1~i-ai6~xscZSs${TA+#EDm!3V+3}K~AYvmV(ixZ@(i7Njj?vv<}0@F>TJqX|Upy
zoN!lax%|ne`m4Y)QHjJfsw0M?PgxL;T-s8T^fP76@82HVlxu*QSdqc;o-oNVS#~t5
zr8+k|{v5&3;Ob>WX;FA~I1>N9@KCWNg+(yOWTE0HBVzF#2d*I6V}YObQuQzt^B1b{
zAQ3N&+n~G&QX*`8j2V31;x6uF_&QtB9EMac*cwV9%LlzR=^lw_G=^bZ%B}
zTeMjru;(YF4slBQ6(q>Z5xQa;*k{h5iVHfQq9EtyfQp240Q#Cb<-ubmfnjbp*bsu*
z-9#*o%0PA#?;i|jg8-iNX~$rK#o;ia)}*Wx6Kl=3$5Huyi)^nu?#$)nD(3uQT@Mdt
zC4O-4eR=kR5juM#>VSZKOr0+=K&P>VlrUo;s~vNBMal`;_{`R*?vg0=bwTeOA9Guo
zH`Cd_eCsgKdDwWo-o}}rg9gj1s
zxUS3;B1vG#2VJ>rka%U-=(#hJKZ{JJ@%XPY9Ej#);Z+jyK3;h9v2I{|yj&O%aUqsMUXrXcGz&$0PO
z>AfRYLvN5pnYS+z*a>Jo?N8+bxI=eBl1yV*8cO8*+LDDp*^$SMukNe{bT}$94fIQo%CX=E8K2wP&Ea^B{{^s2cS0z!w;SWcm7Y^Zq
zw)Sd?F3Y0v=~2D(wnE{XxVck`A!t;i!@Y9qiyz#!a$J;CPX)+?-ehX?KC*VAUFn8i
zmtWO!VgzJD92B++p)~QxlFo)Vxf_seAJPKW*j+J>T3xoI-kLdLd4}1Q6u>pdAefCU
z)kY!d!cVQ{O+>pDrKX$T1a1i1&fc3!V7;^q7wx+r^=H8&
zfaCG#?&HVWf*+)!@j@JsVl^tf-(tXY`TkG%pU4DzJBb@V#yXL&8ERrBF}9svC@2vTDn
z=JP4UO-e0C=Jv5dt^Z972&Wi{`x3{|bl7&dgE#n~mRODq-l|+jtxD{>zI@S3HIP1B
zK0BfRZoke(svS8E?Zk6RmZ+7t|cEb2TlcKuDu9^N!~dr(pjd$9tO{?6QZvOjB)kHy-%-)
z?%VyNhpBm9;Z7SA2P=yAB;1qqcjvl{1HYNz61}}p<2DS1*h?7fW02d|O2b%!4E@k2
zPKBN}x+r9&$hJxd^+|sA={l6cz8cHwS1n+YESQSZ^=`ZO{Yh&Uao{y{&CqDKk&`Zp
zso3A|I{SRzxgSa^E>)MluSfXSh)^|2W)Y|2H82xia)&&XYH1zfouK1*^8s{+C#|wF
znn@po5o-E3W(>b@A-E?P#0Bp`1VptXkahq2>-149<hY;%(!V>bOU~%4`^7j|EUZmDk%9wQwp+dd9eUH|IPK})rriBmwA0%1s*@s
z)DKsRU4z((i|`c8IzKPI2>7Zi6aa#hS9`WW6CkzvZfYK48>(6953GGPWn#@-Fn%@jBcVZ1;-3BdXfJ81aRYLD
zqI8mnwm|uC9xx1h73AM|2Z*?{N&cma#VKa9C6I9p61`z$z<382q|f*4ketH6oWRYa
z+1ox7Cwp-LRbT8F28!egb^m1#?QFC(_Gl`(Y8#S8AWz_2Ufy7>wOsb#w+=
z6%Y5FrUo88`i}DiE_7uO!P{y|<%*G)FDv-0qyAflPMaqd9AAf0vP_(b2;ikZ$0xVC
zw*JZb#a}3bGwS@~!&uIrR;;vKfV7FbqiBkq^A^+r!{A%CNQzhe07T!e>EeNi@Vida
zlu)?EokHX;YPzXqk5Vz<=QNi_CF^VGK@dRnOYgvfoS^HHlL^nFvXhC{#w`W|R67deW6+hj~(kP*tc9iz7IzBi?lbmnrC;IU{jaR&2v
zWiqq9X@I0<$(5Y#VQoFT=gKlK#o|g}>#R>pKuPoMDPa!|B&GmGYri4GlHvS8lWA1z
z_dk%tXI}g;m!fGP(sK(0QJJuGA=?xw)Zkg)#AhHHG4jbSWw-MKU#)a7Cib9>QGf{{
zHA{yTw&Rww_w7a21Plju7yhbB&-^5~(SqH8L+xeiH)mbfv_QX#xGhKl%QEon$WI4K?5K_aG-DO81fr+|;&Ry~2lqkPLi4yiYE`AQe3=
zj1xKngD9#hqk3zvtHceB2$#Y8Gxyr6wg~PnWnYq3FAN@PFcD&X*bQ|Tgv4nX=?v3A
z)9zsEF3ay+j8*G_2XcUBVJ3prWv@RqHsm2l8NNz$@5%DZ!5`KicGt!KmZvDL+z9cO
z*!0;fMi=FQ3pxvD-%5LgV8w0WZL+9pWd~K7vNo)M&i1>}F7X{H(N7^B
zQ|3I{6uS@~sV^))vd+YpSd8S0=6im=&J!Yk^tYsJVP`oZT_k}>SASHEykvu`bqT;*ODzE)qqk>zEuPXscb7pH
zPNJnaQcqQ=)vEpC3a^YWF%EHS6RzK@f=1&Pn=T1YvU8#W@E{7_nh0^&@5{(`AO87{C{yF;g*A?1&y5>lxeBhntL#
z-DZ`N8Z&DCCGS3~EnA!m45^%`QMX>qd-Ezlv!o#$~Sb)6fXFR
zwGD?Z80}c6Q1akcJq-KsUXPS~n}O!%i@%>gY9^Ep``9vpg>uiaX5AugY?5D?d6+w~
zCcWFiXgtA_8%pDD#$JGE8OE3kH&@Mg&F*bFO!-EiNqT+QQr5fy_Z1_a^g$Jw!}es&
zv$g6wUCBSa4QG*Tv6VMxOqNfr9JLHu=-6jBCs6;E$V`5flA05wtd)zX8A#(=HlRc;
zRzvsQQd}PE5lhFo!weXYXph+>C`V@;DB7DJgzU)vL^`j`;EM*>6(fl7{^M1G>-i}f
zV%Es$-ZIac&jE|n|M#>dtB4R;IRn+JmfHVay*j=-_go7XAzdyr%OklU2k0jtVwPYt
zvjNTACA$;v<91v=rM{k!ivtP*r^qStY0Yi9)r7ZIZrg$>I
zMQ1@|@E5?`%w>lMdv0B2wV~$TneT%io;(xroSyz&DU1oy70|p`Yb(FwO`m|(q#}u=
z=Y4X6mW%UN{W?mw@HGE~(_b50Y!_uv-IBGe;KCo!dDGHF0$Bld6~?{yUvbhmNn~ic
z{@LMFRzf|zC86o7^4178CdC}9tjamInXBEi4dUU_(SQ4^CJ(ozDWd9CjFcD;&Eswh
zQ65>tScr2LoKtrVF1$(jF6ITMvBiz8<;q9AAw<7h)2bp=6Jl|>aSK*LPOtR2k2^iy
z8g#PH21=1$DTSBQZILt(wAxYPQA!V?oPumwUMNA1@qd8>$2-Bpy-_>+kobXQ*7!7(
z<;CRC^|@$)GZoO?X#mFio(ZJH5~!2={o_W%q2gtC}d$2!HZz`%TR
zYyD)wRC=oM6`caenb`Jk_Exckfe_F0#kB{58S(?Tgvij|>68=?2NVCHL6GSQZRSCW#P_eZ36JF7)rNszkBGyP=6>#E~|Tc;e_85fih
z@D2`-Ir&2YRdv$C?rJBVeExYO4#8F30%+;tfYm!N5U6E-5VQHwbb-eJz!LQNF;|u`
zDQbaDS(pKFB0ClHqsHUa%*845YbWcwk0;Tbt0VPaAZ5JaZIJi|7nKRoWOar@)=wHivnns5q}rr*
z6|PMIEmF93t4^bWIMIT;MB`Sqv3;h1Rv4D@xwomD4HZo-cdDqP^6S96Fu$Fs8E)}~
zC$wYhPsl7~O=4s~cawCQDdw)%ZBrA^Bc7S!m{?2HT~o{M
zK<<_*`)lsgp#{uWFWCL_hBm+YE2i5{?s_wJ;^hm*^A_|SwOskxB9|Mq_twNHZ>?JJ
zq1|gr_X}U9+#d&sN2WPBN?xRJi5KvTlN-WE*u;1TLOOGD3cxA3y$V*@0QbLvq_d|c
zjLjBE-rc_ZQ|7)~A^MTm{7?ZF!2N
z8xAFfH3j4HZCI%>Dng4J5Ne`x#Lv@mR0bhJ5;26X$OTXCnLb@{jyVYqSECLfS7c*@
zL{V-&5wDD?+pz;pStidq(Qetr2h5P1QHUh|Jje_cq}*pHJa5ghxoP%J!=8MYnrQt!
z981}~+Hsn8j@kAkK0yWj5rTE*o|@d@BFWW~K@i!Vf}h>D>*)%fZi
zs9V>4iLS9loFQer==&E;APN`ASGm+5aZ0rPj*b#lt59P%pBBvX5MCkF>=Q(6=NqLF
z78*^dt@73C&F#$+qTKm&F$o`s81kEJLvNca{XoiL>(4sicXl-qH@o!+VPoCr>N|&2
z4d0C3D&bmt;fab#=W6SjvsFD}V`NjsZ6qdFpPqh}MyL+)-du{*d`?L!Ij@M|R8&Nk
ztK{rG%|h(6J9A>OyF-85QJeHx8$Z}0-%_Lg34w-#&yg^ph|Za=f`0d>Y~;^MnH}DV
zla(HHr?=c6PJu?A!iRn%YWz1C{$%yJ(o1+o6D5_A{QeSOzcgj{F9-6*+4&vyCBv0_
zksMbuM7tC3Qujn$E6VKK{_~$!ee=7?IoqfBJL&rMd4K4+h{@?;Br!asN9!l&to72P
zD#)i@WE7pOe~&uo$Xdtw4T`t#MIFYy^LE%1DZq`zvS}wP&U;BAs(?9uj#TR~7W|5#rsXBZwngO@0g+W~6^4Iev=nGWU0?
zb6AsDT{@NaKyaqjq>jlx{jFoi+0DP2(1WfaXHi`YrO^tg`Uj^I{b%>;u5m$U=di7|
ze2AcSht{s-O#hnRwNft-WrvKu9k|L!H^%rbQu^Xozh0V6tQ{WPno7=VB9@q6n}l`w
z7CqvIw4&soI)cRf4jv4{eJXhX(CXI-&`*$K5rTVcYC3|Sh+`2r3xhK5i2>?bA$I(T
z@)4`{v?H>M@7TE*MBsX}*&s_i!t>{DVT*4*lkW#p`K_0jbm^#e)sFg(-3cEe|Kwty
zRs6ce6hTI97uK#k2^=W86rLJ*nwfm$s;O1fuZqmTRVE`8CAQT%ziw1^)b<$w}WS%O|3m?}1lagInlbr5ggX^S|1Oa6rak~|BX
z;>>qmzq#Sp;oTy((UdNMToZd2#%tcWmL>*qa=KvHT6c5I_xH9)-KYZ7bRx}Ipo%1T
zKo1n6o{=X8)SDX?XVxK`>BK|Q8x&OY;+qZ)Pwraz=i`HCz5ksb*CglWB<9^u_L$Ej
zjr(mN6qX~AK#X!3mtA)pZPcTnu&tGV*kMGJF@}xrw>wxw170PjPR2-&A=`u&B#_oH
zCArwEsU#5xlv`b3D#?zuuEXJ3VlTXwDPMdZqz1p8{)!$RxJd0-_+@;aFAo3A9$7eq
zsT+rRm+igN1#h~~zn{J^@8B}dy}?j0T{XL3HkzN_OB!bsHU4wq&$cmxKd2meNs?yJ
zAFCKr+)Q
zT=2lj*{S)rY=?CwdI}?S@~}Bc=OMR$jJtI>9jULcnC^C+W;`_DV{^C_Q9LB_|F=KO
zdM~&pMg**xeb(XDrMd>NrXMMOzC)xjSs^c^o6czc$^mBSOh98t=u!o*tb85PivhLf
z^N@}z0G6&Qb}D1dL}^6;71}p%!Dyd@38xeA_||%Xm;C5+U#!zHv5%m~d;vu8aR?a4
z6365z3wKpf{qMqFZUR}Rc~l1sAMsIu!B*u3Jd!z=JzY6(-#PbQK*3Xz7+5*mV7@sW
z_bdSk*~?#W9g3P``#VC4xxj-L5A=3iZsVAsm!X*HzT$h9`Ml~7UF$nny)!(-{&?+m
zeuM^NMoo)7n{P}240>V@VKaCA)?-J2Mh+{6Sq!hx*>C&-c=Vc;Y1R{@0F@@S9)X!u
zF9Di7Y5|iu`_b7+%dab$e=ANIg(pzL>HhdLlqMDv@F+&41oL`=`izzoQbbhDMBQsvE`V-9@U9
zaMBmrm5!)sA;VPK8?LRV;$T%4nNz;H*y+mNi#Q?AoT*%h1rds+f%hqY`fM%!_yQ3m
zfpK-nC=8AL?vAEJTRpr8Bl|u-MZ@YKaDu-E$Ys72a~96Hjs`yeGXOTdK@UQhlE^Ri
z(tw!w1<^Ty)~!lpS>p~o>Du>X#t4hv?Y5bxKD%(=jr9t%vV)uGzas4#i(OJ2+`|M>
zQ@cBD=gO8<1;^N1O%|-V7A?ZOXdkHd#a0^Y98Q$@pD00T&n4Hd=?MM;Ci{#G
zWG+~~VCYEv(b=N<7^E&)a_1bRIgQ4dEZ}(8H85dvJN|W6A4pPXf!e-OZkadpMZHMy3c5n
zzx!klD&;X-t8&I{muFLo{d-;1K~}3RV%To#djUm3Q5CE@Jjb>&tM!`|tOQmXEjp(0
zWM&4auc~=7Kf(Z78jLlCf~CIaegaEM(|Kc^{J~`LDml?BNH9qrjejk0a2ek9B0+@G
z1_hG9P!VWFwc9OOaFs84sScK$pAbt`0hcuF(;IiW6imE(*|)>u`4QAdFt9lDo1Dp_
zH_9?Hu;OSl5i;NUkW&h%9eaohb8)e
zsO)ZBPup#=h``zNCYo~&V^vR)>^#CsnF*_P9{QUKVWg+=9`|=fpu8dsFk{U=%vxKe
z;>LOUAUK|$dUwyoInOa4+xkn8kQ0f^UAEXRp2#09?f3a625^iBu+fFbvX~Fq!B^wC
z`=69sZ>V`wrv15lP1A}y+F<6g66DtU97It@%(3E&vqqbrB7@bmRFXYp6OfmFUic10
zh5?!)*nB_Rg@^J_!~5IKPeFYanB{XA4opCvy%cLt^2MNGyXo?F@2|(SmN;pmK6!G%
zrS_LGIk5vIr}=VHPqWmihknQFp?<%0m0~X)X0C1672K?e*!_Z_kK$v|;XRgKrX1O~
zP&K>Hm5P?2p1r&bsx^*YEe|=RSQuH*RFL7L(HH23%1RZ
zK`&kf@+ofPbRR}`c023{{(X7d;fe1GDPq0BDfX<2aAIF5anI8zj?`zOlLLdxe9rz!
z8EjHSzSy+)6PXm9aOvdq07!%<@0)`Qt?T^uULRv3Hv}jUi@f^4mkTY87@gbLRKV>B
zNCxZV)!;u?D3jG>y6QfWh=%FVZ>pA}sG1&+DZqhluUW()w8rWSDirR16;U#_IY+C0
zRwb*Cu|js%^OkAAmJIZ`vT$x^MEL7M8L_BRKEnX8OPg%QP~;eX8Hk$!Wx9$VVYkPJ
zzLCAX4q2di$*%54O!mya@7Ocigh>O~EWnL5`RHsQY9?X|PST$?C?>9?J{xY8Sx)c0
zW!vs{2l-RVxL|(o`gs)dO;{irQ>onaRX0w-z1O+%%xctXg-h;<;iz;jBk)qRY;2Z0
zA
zKSCny941%J&1qdYF{!G@rJ5}<$evufvf)vkvTuTt+IL2YT99!@Gq)>zbdK{^tc
zj1^zmx|+9d%itFa#}HK72E2NRqIDHIK4)a&6nI`V@1{R@Rdgxf399
ze`UP-=kbAM5>FO=^XlIc>tnC~xs|J#dw&4*XJ>2$#Sestt-==k9U>TC@C$DcnOJ9d
ze%W`ZjJNHtj^1>50p{#0d1-x$*{=NIXT@u|To{;b@C&xnEvjp@fo7}yv71@)Z3On2
z6A#?%I0aZpx^Q#NQJhqCqmscy^H6J_h3DpGS{k-VhjE$+K!ZMxo9;@IzE8L^E#N1_
z0N|szj`#Be5VHPFU0yN!x-|6*TKOr~hTp_F3DT3M$-~hkhoj`3{;9gMUX?AK{^I
z>yfTZND5K|Mpi7$5t8YPf#^Fv7XTw*XtCI)a{g0(@;CIpSYZx8w0Q3U*of9wUs{en
z8&O>z*#QO9WyCt3M?O4s(Dm>7G@b>SB}c2ylJ7-B`t!bBfXtkM$&KGZ-S=Xb-;c6*
zipu8#7!*1De8w4lXGKnuIC-l1#^zI%Cj>k{^!--kE?n2^|C6SI!f_vTnk6<9HLgMf
z{cg|WJIkR$l$~>pEAy<}be9)k#r-LcF#0MqPp^p?CKxB411ZKP74kloS`alWm^be_
z1IfQM$O@|StPQFG1=!kzls)18#B)kyQpfQIGLVoD!;{H}qi`sBGbAGMB_J6c8_g|v
zCHs{D$+fe^oz;yjQPFgU&X{gM&pMbbYXtM{D{v63Z%JiMQpupmf9O9@ZS*a-6OC^-
z0VN^f)rSsa=Bpjt=K_8>ri(Oo_php@{>d~kCS05sce1!7#L)*OZQM%ZmFBw%ECOlkW}bmsZ9m6FFzwL;wH>{O#?L@o13#prm^{)N(*E(x#;pt
zKw@((Z;pM4{Y!#a@|PG(jwr_>YJ2t_}RC!PIDq13oG;`@083G|8b%
zHi+t2Z20t*D^r>(_*6bv&Tub^o4&YSlIZ6KOWs3=E{PGlm;{6>Vd7WQH12l>z=86FiyKJWL=HT%v)rR3umUdj>ZVMx-VH5NPFD_Yy|!MZb(82)
zeVu}2kh9^JkgliG!I5Fytan2!-mt7t)ZE9ddFQA<6r&F1o&I(rh84hq(O2^+LQfPb
z8;D8g@RTEXvl>q>{+Ua!NY~)6%c6l95-ZX@Za=xmg$;qes?s6^O>#S_o#S)s52O<`ic+_s=~w-h;{(F-|LJK4(R;RMrN
zMS82WYm_5M&RU=0$FV2w^rI;#??;fSkRl{0YGwPDYk~TfO1>ZMR#^o%p|?()=)S`^
zdFbc)rmXTc&EJQh5Fo4_PmMOgI7WwSY|3>Ap_@CY)IMT|zgo#$4NoF^oJ}QdL8dH9
zIyZ>0?am1fnau^CB4@sI0|c}~1#r#yvf$;EH&QDXUh+BN6NOLU8tNa5cIgbOeo?Kx
zI^!{yEAKftC{!1HJR-g_^T3?5&!2)mFxD3n
z60<;`=5m&37dU@^UKpUC-he-!|D^Ijww;*+3Y?$B*dqfliZuqY9tx9$Eu6c3CEnr?
zD&;a1CwaD}5mwojbHOjTV9Wgu30*G+NKB2EW^z-#HRY1F!nV0s%7MY0!OBSqVbmj3
zXl;KelHpIT`%E%is%%tf^ohF8mz2kcZfI!Htxh7nQ3@w@$YS()iF2Npxo!JdvzL^G
zYs%hEm#wI!RL2}hxelqExD;dq+u5_9Ebl3$H?$lK#!Vjn!`m|};f-c16Z{CTg4U-EQsi6{1|5D3gVy#zD9q!=tm
zitgJw@ni%sEU-3>JX1wAh?6V1StA;hXMx-ImkjtF?^jd5^x(QS+P!bm|K5mB
zIAr(`=RY+_JC&@hHOvV#oO&81>mwIJb@;t|@^u=Lzf&&^|8XZbqZXnETuUz}>+@sp
zx<-mzvC~3HbDoK6(O88UV*k)cQB4*m9F+!>`|BAJ$ZSIXy+KRP=_7%}9nt~rUAHz0
zk@*Nl5Da8=;i^TIg(1#t0t;@<`vS;X=kkI{`z}32c#RSe9O3-f-FOAp3U{4DVoJUp
z3ri+5eL^PAb&AUAQH)h_r-d-t@y_=dEa`{@25MegjvR
zvlpj=e)ryB(Lv6UTeAMc@)#~_eA!9pTZt?Xvc2eSw@`z*dZ%Ah2+AubM)jU~+pDQh
zmuZThDTqYyFx}qvlt@8vTiP9J2)?@mVF_q24H+%_Q^%-U^@f4s2*;%A!x?ON?d-)s`vX+0ZsEq3ZJZ
z+s!!0p=3d#RM%4UjQY;od9y~=q>j+ci`8qo4wQ1UCa+U0YiFxwpe*nqaKBYoVH!S4
znw4;pZ8hft3j^!Fs!ZNnY}=D8C+|+VeB!s1T4i!hdP!*J99dT4s$0gjs%Jj=J$l;h
zl@iNsdIPQdIBTqpHOGrwdPnX*5I3IgOO;ype+bsCV#QxC>bIqi0zS{dE5eRpnA_I00a8P)npx^8NKeFpyP%@4?Z?O;N;4u4Bw+1|+O
z7Kl0?_XTsL_Po=VfdG6zNrn66Ij!WI(gnGF2IQ$}5Wp8E`>dBs&sfTB)+-^xuB_;n
zcTO%-ZJcE*m|T)Wa}6K5GA#ORZP=gEKaT?r9%$c_cAiu61Du3Xf|MA){I#Pkl|_eM
zDD3MTN_|!~8ZE;2{B)p`{?~!;({xe^^
z=$TKew}!WoMorQMBabZ2~$1Ryk_ePQQl!U
z4!f3~J&DhG|A$+VO12kIxk28B7L_-2Tpx_lSy{S-EOT+ayfz6|tnFPks?oV59!}^^
z8y|EPmDu;+{G_DxE2iIwA)`$bd1SWVhV7c05%-D(zJp`1g}>y{s2bH@M!037Wjh7K
z_u^U8pg$!Z0S@#~+kO3_doXUd7^at=fsDB;fUpO=wUO2hoODwfHE3Zorr41U>1Ati`_PT)S|4iiTP_AhJs;*Yv*-+3wP&6Ei
z9L^Haycq>JDuI6es{yz!7vXsxfmbJ(fI9TbGCa60`Rj?Ypi}jxmApc(d&w+R7%7UM
z&sFd>VCO>@q^%WXEI;1JyMlJ=yI&IMZ-nb6YVz%4(8I|MKhQp9whJcwOaKx(nMkaT
z_U{Ao`=oW*TeiyfYKY+)i}-P6>D}X>%RkbVPaF6p&*PzGJ1>spvdNMw^AXkUnMMW}
z&vg-h2>xjTl+
zg(Csg4%$jtkQ^QT%z^NvV|kTx`>1p3&rWUNQ{+YBcHbu@IwC32)P!_N_n3|hJuRyI
zHI1u)p&tXT{q=uM#DPmJg+}SmTDwuP*GjRziev|IUTACzo7V}WQ4nwYY;`z63yr$F
z{6Wv82F#3`&M`(7trlRW;9^qV<8WchJ1TKA2l~o5+B^=~%qG(P6v=$VbUc|)5ihgS
z$Yevk9px(#E|0urmWmj+-%d+@ZXAe0K9iste^>>sCXuie4s)7shy@?YcK`~v+>aGN
zCCcc%xt7;^FbnER%w`e7uXTvdxYI*gFm*wE^iS&A8eK_QgRtXb!TWY&8s2|E-IC>=
z9qovDnzCMT{eEKjp6E1~4Evd&9;~I>bcYBd*1y9%0JDn|&!3_{ON8f(b}{SpQm)K8
zisjN9pObr+N^khlpVlb|&y0ewkH~8hLE4QJe(0-Gg<{
z)+RnhYS!3iqP+8-o}B^8@@A&d8(1#{R29-e(Hysg>Z6edjL>gaQ$kc$mFbTAkgm0u
zLnApu;|d#|r$a$y!#_-&FgbE=y5C3&_37$tsdXJx?eFJ}+_m*q8pW}nA67{2o4Cv+
z%;1GD2R0RN*w>Y;!~t3$?#5vxN1O}&_|u8Uk6=
z;AXrXzsYUBrr((0YV~F%kvmwP>vKKwd(ak(s1PGs_ha0^#(*a)x=f^fovjaZ6H9x$
z`(X4E!DK3+E1%#W)x{wno(&-dY$S&oFYvp|60S;q6u;7i%6C6#eI#90eW|27*?}dq
zk?CAA2~$eVk+eDPehWXp&m{`LdnjxVl|N4)m$*7E&1zs<65V=_V!En#*lTaT_~_ue
z=k%lD=SfqZ87k3nNItt?MZonqq#?VBhZqb(4O@>vRENZ47xs&yC`C#=XF_ycl)D;c
z^~U(b59<9!35XlXrgSHYQFIu-e?P{sNyH<@;AgN9-7ruPT93{D&Tz_;1O4JSKgC
z>z!qtb)!A>2VsodwMN-)_Dq_IANUZn-f+)BlfCmk$9Z%NOnuZrCG;mSHGYm~?n_YFozyLpa&9EL#jr71P4GtxykL!*tGjumS9I@;
zNc<#QGF$B~wHMzr#=Czp3sv;-@#w2RefKD;nC^s*nX8pbT1*QD2c-u
zoNyaC>DV-EIFgh)Q@CK?gxG3i&ODVkCgWLYXQ?mrHAGpmWKtgbX8dURpk@*0)Jg$M
zdZ4=b`6fblxLT}v9?jbzhxVD_ts
zIZ1|YK56aIJTMVMJPhA4|0A=oqY^m;4Qj%=)aLwyMEt{j|96YbQmMR!o>j)Wsa+J8
z(lfUtn9L^E?Bg!}j&mMk5F+!Socm#FnLaxIvKMp~u8TkG`*mYN7q8Ef%P&AUPgz!0
zFyxfQUy=I^8)#P@Ad{X6GeYN(V)otmc!DlE|J>i+8Iz=EPO)M>-`o}NFE7lwT0eUz
zWWdr$@rz`YV-Yj=v#iuQht%L;Xqs(BBjc!_B)iYfi`ysR4EllW`a_mWBS7MNpWSbR
z9_nBG6A^*IP7-$J_*nK+08?dP84(Py$~CY?yG`$kgV;!&s~|N$41!x907a|ju8KWd
zE2r-T{t@5a8n5lO;kBaa=Jt=er%(YUenA{{t)xBm>~_xrtA
zJF!O_%{JqOAOT89PQ7pz2O8|6-P%_cobl>$ydS@O^AwNTMd$QrMkecemFd~WBh9d<
zJOSLwF{?PpnCy?NH_=A94$e~mnsM~MQQSdpU3WMK)vSIksy9paZjWyiGJz)Pg{S&M
zeq!JNmAGaH?DTX^XC1}AKZ^6+4z;HS^X_oxLBZ^j0dI21h^GH_--S~L8cpzS2bZp3
zAlQ{KwOazl+8~@;@D$Ma3q6$Pcpg>iLiQiC*gwz9zn7^^+;@4Ih15uB-w=AB?^
z4U7a}8537Nr(9P~D!eVhAV9ef!CX<~^Xwdjd=o23RGR!*$C~g_T
z%eKK4?H5p{sSRQCy9Ci!N+5&E{q3dt`z-=)E~v0bO!f>kJhA4=Ao+CZM#A;1!5}pr
zo5>yp9JVe~`Bl=!)qLN*0DoyP2+e=d#tH7aCcdrpAB(i$QVNSP`h4)g^&gBiC!^+?
z&!Yv`Q=}hnO1K;LQ0P3ynn^pN@dyE))S{K4lh{R@El;n^z~T{ZkO+GJ39!R2*bySg
zk6$J;O_SKnE-DycqZFMn>V-OlG)`1qa*Slay7vekvQvi$%^xjP&oVOx$rUkAHSSN#
zuDPUbU+Yf&Z25ukwS>TVBd;@?dzN$fb+*iD?8Ev3Nabc?(K>!+OgmHKZGf{qBAnT>
zrNaU`qO@z)p}}EbOT4BYNhp=f%X9H4X?-gER5olDYBwJ*j@RmT`cgUd#0P=l$oe7*
zvqgHf+pzW8F8891^Ba&@lSw?1Co(7lTKVhU;w$`U}UDw*_yMvBoeuZ0@{yP58DcZ5_(@
z0&B<|Qkr)I3rWabHSo*fSuN!)l8^byS^)C?TPbe%ufCAtU1kwi?*um
z$jE(ygw#cQ8bnx`zg8W0Zj_QRBjl4FZd2%+=;TSmD!HZ}-WYcD<}W!VB?R`Z33}81
zFdeJ;g&@}6>w(wKPg*Wx>O>^4v}sF^m3q(q5$uKBqX1AX3#P3jJ`Hzi*bDs=1ILr5
zz}-;htP$~=i*d^rn-FZd)IhF3c
egu;PDZ7N(xNn@%=&N+!>keov|NkHOH2_hgMlC$L8sN^g;
z=bUru@cR4CIrrUr-+6b8_vh;|dQ|n;wX19IT2*V#x#o^iS5+V*W+Vmxfb6xRoF)K(
zal2rE5D&Nco%gUCw*k3pD#!w5gUs8wANV#hDl!028Bc<;fZ*EB)s?khVX@e(tgOw=
z&9btx_4W0!v9a6R+r`Dj&d$!5n3%}O$m;59e}Dh4U%&qN@gqM!zoew3x3_nAc=+=2
zGAt~ty}jMp+1cLSzPY)%u&}VIs%l_h;P2nRlarGh8ymB;v-S1$|NQe$a&oeVheupo
zTw-G4pFe;4`ucKnbBl|M%gf7aYHAuA8{692dU|>W2M0$-N2jN!7Zw&)S69D$`4S%=
z@960G^XJdn+S;b3rmL%~>+9=?h={bbw9lVE2L%PCrlyXMkI&4^?Ck6m6ciK{75)DG
z8-+rpr>9$6Tf4ftMn^|~`t&I?GczwQucf7>tE+2nZf<#bd3SgB=;$aXC#So+`{w2b
zjYeZInDg`V@87?Nhlg)%Z6Oedl$4Zj-@a8=RvsK2z~S)L*4B!OivIro{r&yAy1K)|
z!?UxqhK2?t5}A~gl#!9~>({T5k&%gs2`49~i;Ih)p`od%slC0u?Ck87m6fHXrM0!S
z)6>)W`T67HV_#q2_wV1IoSb-hd2MfR2L=Xy{P@wz%F4*dC^R(G($X?EHnz01w4P*x2~P
zhYx0EW`>4_IyyQ50Rbi^Ci?pNy1Kf-!NIslI5bcCg4^VKXsT-A0^t7#AHZXLqyTrx
zL@tU3?f^jE{;w~n{vU^Z0I>M{T24mW$8^6X+Kfh_?y$wWp3IveoiMbU-&OvV6{1_k
zM^50A+yQ&R{en5UNaXSZ;@`T;UJu?uze@UN?gxFVx|H6OI-G@XHtBk13uzU96k>Ry
z3WvTJH7PC=0Epr^5cpkGu#i&=d?3=;
zB=YlqSFpM9P3r$y84t^}QnJ`Su`%<9^!4
z9+Zk*bl|f<3Pp%brpyufhyH#(R3^V3NA^u$kCY7fXw5fCC+dJJt_b)b}Q&xqUj>J6{b`QQy2VlRQ9cze;aX9|2`YxpBiMZN0@T
z9<{D~TEO?};R8L6*RQmKEI&^)rLGxYeHwU)QNykj8Q&gNB-!9yTMZ1f7xTlfqm`%P
zDPewnqIA3n*qeJ8n_YT0wXt!!D@l;^?s@fEEjMWI8vDjyp^G;49;hZWA#JR%M21Ig
zJ!^TzK*x8!k|?V(${oY{xKPXo)LqQf==OO^`Oy}(eC_1Pzs7SF+Wy0XcgZ`DU+W@R
z2Rkq@bMTEi9RN>HuRsaLCXz3_E-X$Lp)S@g+;0G#TI}%yYczH}rN-vdj)Wj@F{SbE
zeOVx*?xQx7;si4jh(k<17F<8zeYaG?!#4#f#FB0J{UJi|U}c1D)C8F7rG}{p^n)w(wjG}_5Rr6V!he8@4$`}
zXz7`oyL-GB9jNt$kei<$P2OfB`E6k<2=~|ZmOIPu_`t{wHZ-|YRXmLe%;D|ZU!r1*
zJ>H*(0EmtjPz>0$wEn`%keE7i-@FDOF0HL~B8S~zWFv18kNnW@VYi|;
zK=+?N(@7mVx*)Sa&vUmPhP|HN^HA6!*i%SWebrP#mg$Iq5ZO4o)Q+b;`Q-XA0~^nS
z5X`(95{Qju)&P7Q8}A@jj|tmea`US^Mtlf2;#E?hgDt#yQ^4pz$*3Un7-+uWDoQSE
znw_zdrSTyar6`*$DWn0mf@Xd<`a5k6J|TiJv#q8V-=g6(Aaep?^i0D-AD-EL;r+`j
ziRbu+Ev2R-Cofe7Vl;uk6eFd$AOhe}vuAXzeOiLU2jly35oGR4#d+sj0~^1XYWEMt
zET5MB9lzNRI|lcL(R$CqZ`0nh$pDXi^ik;fJ5^iwun6ynmhixH$P03Ge5OKUi2LZM
z^QXgjk8_UiCu=`LD%H75JJpngsL6|OfQgAht$fVuGE9(pQZuz_t4s|cC7NFUWNiP#
z^k%H>JA7bVzuhc0TuW!@r+UptbXR+}f=Z6d4^wuYB}(w{bz9^9wayE%238`!GW>G5
z$FF|^e9pI8x%PTL^AdA5i&|RR`Rq}ul5w>p6dv9u`F@?d2q3HW37Do(vvNepn#K>4
ztreAI$kfe3y+SVbNeTe1R_PBrP{0M&2tF?3_l_&tRuKvUFB;4FGy$xUQdAx`0-^#h
z9)8R1toQ9h6jDD(I^#i%r^2ln;gT=)pCd{j^riR7k+}LEhape^%>KGzqhL>Rmb}k!
zo+LP%lJg8ixM^WA_6+b^B&9`8oa)S@7g2S@QXfucda0FtI=5&Xz6F#WwNWCSB3}W%
z%P6L;3&}Z!(iLw&8^I5n!0Ngl=nOnYx-VmRt7AEckh%z6fDvotPvEWNo-ig|!H?K>
zroBVDexAhjWs4=WI{8rhplhIOhM?kTg{!Vl@R6AF4$67uBNFrJbsP1K7O-h~V}uyQ
zMy&4WOknfcbiODK4ocsri|_pt9Ytqj1dpn8n!S
z0dh9}|1A})60*1dA2P+intizdM73rEJsIHRPaAA?hzM|YfC=yh2gDm|!UWfU)e=)<8TacaUsYj~`0pKUVi-2%{(Ac_*%V>vMoKjt
zJQV6Vq$pb0{l%+rt6Sb9uvD(f(fNG~uHldQYpSD5h0P3dZ{B@>5Oq7kzzsnr$>`9d
zqKhzFJI(@1L*Gpb%7plgIrrLS>D>PbA*l;W@-z=JIHiCRV+=T1@ZYDb68-G>tj^3Y
z7({GN#*L4_IAbm#$4++67H56sOYN4$FxJb98#HnFmxUdvextU8!8T0+MjI6u?jO6f
zfGh@zMY)#`QLDdu1qwJ_PiA+Iqk?Hx6(^iKi|mjXdB-oB?Pw9{Djk#GMZj0-sFU
zfBX<`xCgm1De;D=N3N{6IX)C6AUVDOvj3XKzr~7J3DAi;0xgR?sQ6gwZ{kVC+-E5_
zv;=m3#g~-M=OopFCcAnZ8-%L~6lD!iBwS^rrs&!1Fy}J2+WAG56
zuK5Z3%FwR7w=H>{T-cjq9K272cO%{|V4u%mu~p!H*{wti#{g=zU@{*0`2O@_8hrk;9OwFb&2b
z%6k)se&t-qW~o?flDS;zvXnb&l}-yM*8$3GB{YYveTClkf4@l6UwwX~q$I_<@#Nv6
z8iv3Owh!;~+rG41_i15WzlcWX=C*iiz(q?-`|rHP|7#|-fp#V*=Wjo023J6M`OyZA
zZJi35&w*;=*^rA2t$aUFo|<4G1h#1E%!^GY7?*ZNyzOUTEI!8krj7W!uuxtM1|3?a
zx**mmy-b_45nPn^=+}8HfFZi2>@%%Ez@RqoyJ-B#h#@bTK8*m@ay^q7y{X+}AF!R}
z!f=OveuAG2F6}?(y^hYW884PrJi0lW37wc2CPlH2UTEy(ABRg~wDOxRE%}9Ap#^Mo
zwNjbkvZ8h$PryFIjKr=7Ih~;M@tGa1J7tcvH|l^kd{4_(8Ci3onE~{-XRG@?{xSP&
z2#Uwjs<#}(m#!7*J*fqxfSygxmjHk5JME_!<$NJ-l}SW>VWH-yb_QntvZC2;scrxa
z%L)3i%-X5Vde!)JN$I<#AnE4I56S@)+X{33S=Nr6$}cmx0ZI4)IRJ8Y;FZRYiHY@1
zvzh@F!1sRV6vrsxAqZSt!@s+W7kKBfJowPNi;7RUO*~Mbmc9julxG#_i=m14-Y_xssj29ghU8<`%`gx#
zj-Y{P%>9rvjvH&A$m{DJYeLPgPHTN^j$el*kX*jZ`U>cv<-%HWWHy)hei4&~q^rDs
zc4fxgI)lARH1wr15C3q)l%}Db8E8(assbsy^K$C>80gn?2!P(d!Oo5pH(Q$fQANk~r!Yqa!o%{z*E*1+SzY
zLs+D?pd?L>gi|8H+Vv?dyHZ2rydYJ2UlFSDf)x-IK(m3?^#kya8g`&AsU(b+bAz`oXT#YUTuDQMh%r*gCf55uGP{rFbn+jmLtSQ1{YEa*xKESik<
z85Cms@!)=3raxiGXb1W&X^Qsx_AJ=8gRFuAB(T>Wh!Gz}-+kJo2)0-t@Q5uWD847z
zxeLuCGta}L6vEWm9#LK}AP6}%NOBwT-W)Nc?!H7(wc*=Gxkl2pg?q+4F)M46H)3q`3M!gZnAzg|9#0dP5x?lc79buq_yKo;pM|9s
zFF==b?^2BCDN+qXSzhOonCb!v<0r39=iPhtcIA8|iP>u!hKIs1!xHVYKvNwNVX4Gccrs)d8(!(eU@09gzDTXtT;72R2sS!EO9C
z%yo0p_yE;7J1k*7ah52(^2wzDezR#F5yF|30BenQaQBz5&6MDh|w!)BKwlg};V!16$|S!p>8;=Wvu5=_i^p4XeAc
z)!p=-=jDBzc786r%)W-D#a+!lizkJ5W_K7Ku$Eg=*7ORh+CXbNsHleRm~j&o<9*2%
z4)7K0a{-L2g(b};>V-ge#N!$NSmSsAEhshAzkTQ2=X|xHokg7-@6H-VY%FR^)9Y_m
zjknCHs<@=|2*TR!K51Vy9?U>!dzOLF-w>_57Rue!f6K;hYrwE6ahu5cbIvEc?EQOd
z94RQE$UTJ;0rSx5hJtU#Oq$OoCqLL)Tjv79SgUo9x7!#^tD~ooj=8&wE4+7&0ESHL
zl}UxrYjHxFhXdJ+FOF>zTi^P-eV%l7h;O*_&`Fsa!Szrn;SnMB*MArJZ-uq6)Bpv?nZD9Rj##3j>I+S~Jc40=uVdCACyEa^-l+{M
z7hG~uNgE+%52pf3%DlX0z&=He7F+m}bN1ArSTXr?ux)1AVY;b7sYc!XCDz6i4swmh
z2nIciyYn+hI$IsGa>Tvnmg)7~cfCPjKyL`tI(U&z{c$Am3jd$49fu%^ZNLToH=;#*
z%F&VrIG|!gN7UxrL%2WzLBRQZs{e#c74VIoylubP&dkRRQ1=&RBdZYE*=B#17?#BV)dC;O|uR)pZF4@;;)pIHcZV4
zw35bV&S@r`m&Ka|wS5olv4^uDZ)yaS@!Io|80$0}Kj+x}M{hYo+
zn&AE`wgv~<;glU`vP1y`JtE*!zJCdP5jEfk&T1wwm)Tn-gm6bJRhstq4^xsO$yQ!H
z65r3Eb^b@%d1BV=tBrNyF!!-+X$HDkL4Z{_&?4S^4u?|lcxGBs}+y9GH&7rWo
zmIeAn+2debKyikfi~Ho~!mgsvY+xaV=y?>qp#iN_0@UHhy59Frt-{NAs23ts$
z#U?y0GiN)=2trJ*l3AetQZg337pA2v=?k+h+9wTwj9ap}VCpvhr~&wsBuM@vitR{k
zU=uDWdd0QdREKScsOR6y{Sm;CGM)hbGD=Mw))MVIl|)4BNG0Tru)E!ex&1;%LbbG^
zfs+XavB~Er9VF{Vg3mkVF1s0{CZRh||WJ%S#cK
zPsfX<7!`pcyG|QKrasol$Qip@YAngKx;nRQa8^g|r@Y37@8sr|2;TbDNKFFA-YzD!
z<<}q1b4;e3V#J2E_lUOMgUE?u|5L#IKcNPkzWd)b-v|6`l(vFW=oL)X57Ek@r}@G~}{CyI@ukVih;rMuwkKSewk3t}cCq6I3?;Y1G
zV6)>jkM&?J$hN#=h{e-GERkct+y7uehZhhQZoNK_p6P15R`mGN_mbA^+?riPL`$rY
z_I(-(8nE(*s0OSk({v6wJ%u5
z*xlX_25I-!*cN~)ATIk+JHP@Fph0AQhBE#w`R!4-Aot50ZoifZ@MZ7L&R1m#q@#EP
zRyMdi3nt2YcaG2bfOSx+iudwF*omZzIP&r6&vjMXJI;!Jr)}?K@e_`B%iC(3JMmtJ
z?+Wh&WB6G>nH0lY4q1Ru8PKn);#9yZL>#aZc=xUTZc_wC-RAQ(B6cd
zURDi=fz3?)zq2-PNV&9qORGJhKrC!0M-u~sL!KYo?abX(_z^f*)pWIP+LZG#Al0Q|
zMnYAcDL2UVCR$9t*3;@6EllZhKJEn#QkMNkOnh<%GUEkG8+2XR<8giZ4=>pxrR-0l
zm>P6MvCnnWVFB71Zy_-ly#5$=5cRn9bjj2|<28`ma?>-tN?4mV>NO=%pE3^CH}U=q
ze8AnNs3@Yzxq$qGXOQu1fEUrka>+}l4n$AZr5<%0b8#d5sKUySe1L;%V4%}0fnt5n
zrb+oT%pnCj<%V>r8JleiwEu`W^@AW$(?(rfi207Ru^>Ys5_?cdIobJAD7*0|qLD0o
z-Frz=_)LUSr*2@voLfPDYXhN*`9Fk!5!`lbZXKVUArj^xy(;_V2Q=n
z3Sw;Lt8pmVy|*tym?gL*B@HSW3kcSO^YGzuAZjEyp28rvN)VGPi8h#9{OZAZeABTqb
z*~We^oeTJof(s;#s(
z1@#hT`9B$hR>XplB&n(2X$7&{NtCPb7mz4TV2SfmP)rl`L;2$!66Xpq0wW^g6VSic
z>~f*`PrGk|ccYmgBKK3ZB{edtcDkpmOu;9}9vgi_i^tx5PMEd!xV9Yl!LAF$Rtyj!
zzs@YM&gf^c0Ag{Ad%c>r=tyw4gM*P#kSN~)b?=f=m}5>lV4lSfjdl|Pf4QjrFx>qt
z;fvKHZISNBFh^f^biW3Ky_e(Bqel&qzD37`$ghsop-rG~z15*v7ETMSD;7GO`(6K5
zsD-L93apjjiVCdl?<+PjN}ku>CWy1@@uBpfr?PccR+<6=a-STLgupKge|8N7X#Eh-
z=|HPxIyvNVc1haJTId21+~s(<3RfH4t@T8=7fHHMc%DH9@i7tn+av0^+*PeC3FOfY
zqjnMV`GY-MD{D|iw1~cQM6VlDmPe7vO&meQ)Xsf;;O#`Y;QH6QcM--hz|X_;
zhbCL|{41PsfB3Y{$>6gC5?=vgk>0PVzQ1u|Z;%7TTtx_6@nLso*V|l7P)+I&X-GM<5lGPH*0J6#-l=6M%Tv>m^u*=&b+1+VF+4F9~kv-k;Ww#Eb7V8U%++XC;BmsU#
z47K=81&V5uqvggY4M@1ksMPbJda9kc^;HEXDD!F5O^(aIK
zxW@J-9c1~15mvA6x0M&zCs84f62Az!45AU4&=-e4C$2qwuI^7hK>RfSTPH%9TujQP
zwgWLYS$+|GFrSn-J}{6-YYHq2ZNjS?XF|6yJ%g{FD8GC-Trun|p8L*xubo34sKr9D
z-?R`%(x1xv5mB|+atLqgw8-MCP9b7SOkK$$su_EmM;9$N$pBKO?T;F8{&51<=NajY
z9X7g6IA;+H49Nb%Y>)$}+(rPyB_qSrHWWW7dssZ${A=>RF$IVHGGr75+d7y3CmoEB
zAMx$+r${wv&Ax{lUz%C#!GZV)pVor}jDmKK&T@F@0@QOyJASc?2rw_o%VTRxGy$Ep
z(LeaWa33FiNHnZyb|5MbxTiE1j~~=zOK`*hEFpi?Opc<2SY5?=`mW>G&CL$a!6a{xX{Rc6$;-sM9BktOa`A8&}18TlH
zCQmV4ybV>30tA;xOkR0>ECE`7hx093nF3Ey6MS5VblLMQsk?+eJ*Q1WrwpJg!$rk+
zZyqK{M(zOS2jDeqdF#zi{$Uf23;KaegJCnIV(?~4z?%RuKB#I0sEwQ6xg5@rXq2WI
zh##a-2P=IR8`9%Xju|T2j4Nw=PlEg=^Ng`zKd<7a%i{Xbewd;nkgS74E&~H(3qdWw
zkrF?Eom(>)8A>Fgg6(>q1Z>?A3q=&u||IPZ6RDeX%skknFZrUX$s9F4Yd^}N6LmL$h
z5U%jnSK`eH`$L5AeZ;^sQ{XMPjw(Ph)*kwG-@ua$X6VLbSW#~X9PCV6)zQC2q)DUJ
z7frukPDcPfzM6zf0gt&5Unkg(-S5Ob1d3bURoyA>k(%u$G{