From 8390efc8a7f34d29edfe9c60f03ed5d299f015f1 Mon Sep 17 00:00:00 2001 From: Frederik <112972665+FrederikFraJylland@users.noreply.github.com> Date: Sun, 23 Oct 2022 16:43:04 +0200 Subject: [PATCH 01/24] Added danish translations for auth. Important note The translation for "auth.errors.unknown" instructs the user to ask for help via the helpdeskchat on the sendou discord server. Added Danish translations for the following lines auth.errors.aborted auth.errors.failed auth.errors.discordPermissions auth.errors.unknown actions.close --- public/locales/da/common.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/public/locales/da/common.json b/public/locales/da/common.json index 189a84bc4..4a01188dc 100644 --- a/public/locales/da/common.json +++ b/public/locales/da/common.json @@ -14,6 +14,11 @@ "header.logout": "Log ud", "header.login": "Log ind", + "auth.errors.aborted": "logindforsøg afbrudt", + "auth.errors.failed": "Loginforsøg fejlet", + "auth.errors.discordPermissions": "Før at du kan oprette en profil på Sendou.ink, skal Sendou.ink have adgang til din Discordprofils navn, brugerbillede og sociale forbindelser (de sociale medier, som du har tilknyttet din discordprofil).", + "auth.errors.unknown": "Loginforsøget via discord fejlede grundet en ukendt årsag. Hvis dette bliver ved med at ske, så spørg om hjælp (på engelsk) på discord-serveren https://discord.gg/sendou via #helpdesk-chatten.", + "footer.github.subtitle": "Kildekode", "footer.twitter.subtitle": "Opdateringer", "footer.discord.subtitle": "Hjælp og feedback", @@ -29,6 +34,7 @@ "actions.delete": "Slet", "actions.loadMore": "Indlæs flere", "actions.copyToClipboard": "Kopiér til udklipsholderen", + "actions.close": "Luk", "maps.createMapList": "Lav bane-liste", "maps.halfSz": "50% SZ", From 1234ff83dce1d51946937059a9d4881c91004165 Mon Sep 17 00:00:00 2001 From: Frederik <112972665+FrederikFraJylland@users.noreply.github.com> Date: Sun, 23 Oct 2022 16:52:08 +0200 Subject: [PATCH 02/24] Added missing danish translations Fixed wrong translation of "results.placing", so it stated which place a player/team got in a tournament instead of thier "rank" in the tournament added Danish translations for results.title results.highlights results.nonHighlights results.highlights.choose results.highlights.explanation --- public/locales/da/user.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/public/locales/da/user.json b/public/locales/da/user.json index b6fd43150..63cb205a5 100644 --- a/public/locales/da/user.json +++ b/public/locales/da/user.json @@ -10,12 +10,17 @@ "stick": "Styrepind", "sens": "Følsomhed", - "results.placing": "Rang", + "results.title": "Resultater", + "results.placing": "Plads", "results.team": "Hold", "results.tournament": "Turnering", "results.participants": "Deltagere", "results.date": "Dato", "results.mates": "Holdkammerater", + "results.highlights": "Højdepunkter", + "results.nonHighlights": "Andre resultater", + "results.highlights.choose": "Vælg højdepunkter", + "results.highlights.explanation": "Vælg de resultater, som du vil fremhæve", "forms.errors.invalidCustomUrl.numbers": "Brugerdefineret URL må ikke kun indeholde numre", "forms.errors.invalidCustomUrl.strangeCharacter": "Brugerdefineret URL må ikke indeholde specialtegn (Gælder også æ, ø og å)", From 2f7d62d4d31165cd82e0f4201b905ec5a5905738 Mon Sep 17 00:00:00 2001 From: Frederik <112972665+FrederikFraJylland@users.noreply.github.com> Date: Sun, 23 Oct 2022 16:56:16 +0200 Subject: [PATCH 03/24] Fixed wrong translation Fixed wrong translation of "forms.team.placing". The previous translation was a verb instead of a noun. --- public/locales/da/calendar.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/da/calendar.json b/public/locales/da/calendar.json index a5969e530..bdab6d658 100644 --- a/public/locales/da/calendar.json +++ b/public/locales/da/calendar.json @@ -26,7 +26,7 @@ "forms.team.add": "Tilføj hold", "forms.team.remove": "Fjern hold", "forms.team.name": "Holdnavn", - "forms.team.placing": "Placerer", + "forms.team.placing": "Placering", "forms.team.player.header": "Spiller {{number}}", "forms.team.player.add": "Tilføj spiller", From a2547fa31219b6475e65ed5bad80b7721352a641 Mon Sep 17 00:00:00 2001 From: Frederik <112972665+FrederikFraJylland@users.noreply.github.com> Date: Sun, 23 Oct 2022 16:58:28 +0200 Subject: [PATCH 04/24] Changed Danish translation of line Changed the Danish translation of "results.placing", so it has the same translation as "forms.team.placing" from the calander.json file in order to have consistency. --- public/locales/da/user.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/da/user.json b/public/locales/da/user.json index 63cb205a5..5084718a5 100644 --- a/public/locales/da/user.json +++ b/public/locales/da/user.json @@ -11,7 +11,7 @@ "sens": "Følsomhed", "results.title": "Resultater", - "results.placing": "Plads", + "results.placing": "Placering", "results.team": "Hold", "results.tournament": "Turnering", "results.participants": "Deltagere", From 9b15169eb5da186881744009166c6f9fcba59cbf Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Mon, 24 Oct 2022 19:17:29 +0300 Subject: [PATCH 05/24] Fix more bad custom urls --- scripts/remove-bad-custom-urls.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/remove-bad-custom-urls.ts b/scripts/remove-bad-custom-urls.ts index 921572c71..4ef2b56cf 100644 --- a/scripts/remove-bad-custom-urls.ts +++ b/scripts/remove-bad-custom-urls.ts @@ -3,7 +3,7 @@ import "dotenv/config"; import { sql } from "~/db/sql"; sql - .prepare(`update "User" set "customUrl" = NULL where "customUrl" like '%/%'`) + .prepare(`update "User" set "customUrl" = NULL where "customUrl" like '%#%'`) .run(); console.log("Done"); From 12bd09cb14963d2781b39eab14727c0d309e81a4 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Mon, 24 Oct 2022 19:24:26 +0300 Subject: [PATCH 06/24] Add splatted by RP user stat to analyzer --- app/modules/analyzer/stats.ts | 19 ++++++++++++++----- app/modules/analyzer/types.ts | 1 + app/routes/analyzer.tsx | 5 +++++ public/locales/en/analyzer.json | 1 + 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/app/modules/analyzer/stats.ts b/app/modules/analyzer/stats.ts index 5bd3badd0..bcda4260d 100644 --- a/app/modules/analyzer/stats.ts +++ b/app/modules/analyzer/stats.ts @@ -97,7 +97,8 @@ export function buildStats({ enemyInkDamageLimit: enemyInkDamageLimit(input), framesBeforeTakingDamageInEnemyInk: framesBeforeTakingDamageInEnemyInk(input), - quickRespawnTime: quickRespawnTime(input), + quickRespawnTime: respawnTime(input), + quickRespawnTimeSplattedByRP: respawnTime(input, true), superJumpTimeGroundFrames: superJumpTimeGroundFrames(input), superJumpTimeTotal: superJumpTimeTotal(input), shotSpreadAir: shotSpreadAir(input), @@ -516,9 +517,11 @@ function swimSpeed( const RESPAWN_CHASE_FRAME = 150; const OWN_RESPAWN_PUNISHER_EXTRA_RESPAWN_FRAMES = 68; +const ENEMY_RESPAWN_PUNISHER_EXTRA_RESPAWN_FRAMES = 45; const SPLATOON_3_FASTER_RESPAWN = 60; -function quickRespawnTime( - args: StatFunctionInput +function respawnTime( + args: StatFunctionInput, + splattedByRP = false ): AnalyzedBuild["stats"]["quickRespawnTime"] { const QUICK_RESPAWN_TIME_ABILITY = "QR"; const hasRespawnPunisher = args.mainOnlyAbilities.includes("RP"); @@ -542,14 +545,19 @@ function quickRespawnTime( weapon: args.mainWeaponParams, }); - const extraFrames = hasRespawnPunisher + const ownRPExtraFrames = hasRespawnPunisher ? OWN_RESPAWN_PUNISHER_EXTRA_RESPAWN_FRAMES : 0; + const splattedByExtraFrames = splattedByRP + ? ENEMY_RESPAWN_PUNISHER_EXTRA_RESPAWN_FRAMES + : 0; + return { baseValue: framesToSeconds( RESPAWN_CHASE_FRAME + chase.baseEffect + + splattedByExtraFrames + around.baseEffect - SPLATOON_3_FASTER_RESPAWN ), @@ -557,7 +565,8 @@ function quickRespawnTime( RESPAWN_CHASE_FRAME + chase.effect + around.effect + - extraFrames - + splattedByExtraFrames + + ownRPExtraFrames - SPLATOON_3_FASTER_RESPAWN ), modifiedBy: [QUICK_RESPAWN_TIME_ABILITY, "RP"], diff --git a/app/modules/analyzer/types.ts b/app/modules/analyzer/types.ts index 1803e4da2..e5e23a821 100644 --- a/app/modules/analyzer/types.ts +++ b/app/modules/analyzer/types.ts @@ -202,6 +202,7 @@ export interface AnalyzedBuild { damageTakenInEnemyInkPerSecond: Stat; enemyInkDamageLimit: Stat; quickRespawnTime: Stat; + quickRespawnTimeSplattedByRP: Stat; superJumpTimeGroundFrames: Stat; superJumpTimeTotal: Stat; shotSpreadAir?: Stat; diff --git a/app/routes/analyzer.tsx b/app/routes/analyzer.tsx index 7e4148b32..33306a93f 100644 --- a/app/routes/analyzer.tsx +++ b/app/routes/analyzer.tsx @@ -544,6 +544,11 @@ export default function BuildAnalyzerPage() { title={t("analyzer:stat.quickRespawnTime")} suffix={t("analyzer:suffix.seconds")} /> + Date: Mon, 24 Oct 2022 19:32:53 +0300 Subject: [PATCH 07/24] Add splatted by RP user stat to analyzer (ss) --- app/modules/analyzer/stats.ts | 25 +++++++++++++++++-------- app/modules/analyzer/types.ts | 4 ++-- app/routes/analyzer.tsx | 7 ++++++- public/locales/en/analyzer.json | 1 + 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/app/modules/analyzer/stats.ts b/app/modules/analyzer/stats.ts index bcda4260d..dbad75bfa 100644 --- a/app/modules/analyzer/stats.ts +++ b/app/modules/analyzer/stats.ts @@ -75,7 +75,8 @@ export function buildStats({ }, stats: { specialPoint: specialPoint(input), - specialSavedAfterDeath: specialSavedAfterDeath(input), + specialLost: specialLost(input), + specialLostSplattedByRP: specialLost(input, true), fullInkTankOptions: fullInkTankOptions(input), damages: damages(input), mainWeaponWhiteInkSeconds: @@ -166,11 +167,11 @@ function specialPoint({ } const OWN_RESPAWN_PUNISHER_EXTRA_SPECIAL_LOST = 0.225; -function specialSavedAfterDeath({ - abilityPoints, - mainWeaponParams, - mainOnlyAbilities, -}: StatFunctionInput): AnalyzedBuild["stats"]["specialPoint"] { +const ENEMY_RESPAWN_PUNISHER_EXTRA_SPECIAL_LOST = 0.15; +function specialLost( + { abilityPoints, mainWeaponParams, mainOnlyAbilities }: StatFunctionInput, + splattedByRP = false +): AnalyzedBuild["stats"]["specialPoint"] { const SPECIAL_SAVED_AFTER_DEATH_ABILITY = "SS"; const hasRespawnPunisher = mainOnlyAbilities.includes("RP"); const extraSpecialLost = hasRespawnPunisher @@ -190,9 +191,17 @@ function specialSavedAfterDeath({ weapon: mainWeaponParams, }); + const splattedByExtraPenalty = splattedByRP + ? ENEMY_RESPAWN_PUNISHER_EXTRA_SPECIAL_LOST + : 0; + return { - baseValue: specialSavedAfterDeathForDisplay(baseEffect), - value: specialSavedAfterDeathForDisplay(effect - extraSpecialLost), + baseValue: specialSavedAfterDeathForDisplay( + baseEffect - splattedByExtraPenalty + ), + value: specialSavedAfterDeathForDisplay( + effect - splattedByExtraPenalty - extraSpecialLost + ), modifiedBy: [SPECIAL_SAVED_AFTER_DEATH_ABILITY, "RP"], }; } diff --git a/app/modules/analyzer/types.ts b/app/modules/analyzer/types.ts index e5e23a821..7d6fe14f5 100644 --- a/app/modules/analyzer/types.ts +++ b/app/modules/analyzer/types.ts @@ -185,8 +185,8 @@ export interface AnalyzedBuild { }; stats: { specialPoint: Stat; - /** % of special charge saved when dying */ - specialSavedAfterDeath: Stat; + specialLost: Stat; + specialLostSplattedByRP: Stat; mainWeaponWhiteInkSeconds?: number; subWeaponWhiteInkSeconds: number; fullInkTankOptions: Array; diff --git a/app/routes/analyzer.tsx b/app/routes/analyzer.tsx index 33306a93f..574048e0d 100644 --- a/app/routes/analyzer.tsx +++ b/app/routes/analyzer.tsx @@ -245,10 +245,15 @@ export default function BuildAnalyzerPage() { suffix={t("analyzer:suffix.specialPointsShort")} /> + {analyzed.stats.specialDurationInSeconds && ( Date: Mon, 24 Oct 2022 19:37:49 +0300 Subject: [PATCH 08/24] Update translation-progres.md --- translation-progress.md | 144 +++++++++++++++++++++++++++++++++------- 1 file changed, 120 insertions(+), 24 deletions(-) diff --git a/translation-progress.md b/translation-progress.md index e8a162076..df1835937 100644 --- a/translation-progress.md +++ b/translation-progress.md @@ -2,9 +2,24 @@ ## /da (🟡 In progress) -### 🟢 analyzer.json +### 🟡 analyzer.json -**94/94** +**94/103** + +
+Missing + +- stat.specialLostSplattedByRP +- stat.quickRespawnTimeSplattedByRP +- distanceInline +- damageShort +- hitsToDestroyLong +- hitsToDestroyShort +- labels.amountOf +- labels.damageType +- labels.weapon + +
### 🟢 badges.json @@ -20,11 +35,12 @@ ### 🟡 common.json -**55/60** +**55/61**
Missing +- pages.object-damage - auth.errors.aborted - auth.errors.failed - auth.errors.discordPermissions @@ -68,9 +84,24 @@ ## /de (🟡 In progress) -### 🟢 analyzer.json +### 🟡 analyzer.json -**94/94** +**94/103** + +
+Missing + +- stat.specialLostSplattedByRP +- stat.quickRespawnTimeSplattedByRP +- distanceInline +- damageShort +- hitsToDestroyLong +- hitsToDestroyShort +- labels.amountOf +- labels.damageType +- labels.weapon + +
### 🟢 badges.json @@ -86,11 +117,12 @@ ### 🟡 common.json -**59/60** +**59/61**
Missing +- pages.object-damage - actions.copyToClipboard
@@ -121,14 +153,23 @@ ### 🟡 analyzer.json -**91/94** +**91/103**
Missing +- stat.specialLostSplattedByRP +- stat.quickRespawnTimeSplattedByRP - stat.shootingRunSpeed - stat.shootingRunSpeedCharging - stat.shootingRunSpeedFullCharge +- distanceInline +- damageShort +- hitsToDestroyLong +- hitsToDestroyShort +- labels.amountOf +- labels.damageType +- labels.weapon
@@ -154,13 +195,14 @@ ### 🟡 common.json -**48/60** +**48/61**
Missing - pages.s2 - pages.maps +- pages.object-damage - auth.errors.aborted - auth.errors.failed - auth.errors.discordPermissions @@ -242,14 +284,23 @@ ### 🟡 analyzer.json -**91/94** +**91/103**
Missing +- stat.specialLostSplattedByRP +- stat.quickRespawnTimeSplattedByRP - stat.shootingRunSpeed - stat.shootingRunSpeedCharging - stat.shootingRunSpeedFullCharge +- distanceInline +- damageShort +- hitsToDestroyLong +- hitsToDestroyShort +- labels.amountOf +- labels.damageType +- labels.weapon
@@ -275,7 +326,7 @@ ### 🟡 common.json -**46/60** +**46/61**
Missing @@ -283,6 +334,7 @@ - pages.s2 - pages.analyzer - pages.maps +- pages.object-damage - auth.errors.aborted - auth.errors.failed - auth.errors.discordPermissions @@ -375,7 +427,7 @@ ### 🔴 analyzer.json -**0/94** +**0/103** ### 🔴 badges.json @@ -391,7 +443,7 @@ ### 🔴 common.json -**0/60** +**0/61** ### 🔴 contributions.json @@ -430,7 +482,7 @@ ### 🟡 analyzer.json -**12/94** +**12/103**
Missing @@ -440,9 +492,11 @@ - stat.maxChargeHoldSeconds - stat.specialPoints - stat.specialLost +- stat.specialLostSplattedByRP - stat.whiteInk - stat.squidFormInkRecoverySeconds - stat.quickRespawnTime +- stat.quickRespawnTimeSplattedByRP - stat.superJumpTimeGround - stat.superJumpTimeTotal - stat.jumpShotSpread @@ -517,6 +571,13 @@ - abilityPoints.short - consumptionExplanation - trackingSubDefExplanation +- distanceInline +- damageShort +- hitsToDestroyLong +- hitsToDestroyShort +- labels.amountOf +- labels.damageType +- labels.weapon
@@ -542,7 +603,7 @@ ### 🟡 common.json -**46/60** +**46/61**
Missing @@ -550,6 +611,7 @@ - pages.s2 - pages.analyzer - pages.maps +- pages.object-damage - auth.errors.aborted - auth.errors.failed - auth.errors.discordPermissions @@ -641,7 +703,7 @@ ### 🔴 analyzer.json -**0/94** +**0/103** ### 🟢 badges.json @@ -665,7 +727,7 @@ ### 🟡 common.json -**35/60** +**35/61**
Missing @@ -673,6 +735,7 @@ - pages.s2 - pages.analyzer - pages.maps +- pages.object-damage - auth.errors.aborted - auth.errors.failed - auth.errors.discordPermissions @@ -773,9 +836,24 @@ ## /nl (🟡 In progress) -### 🟢 analyzer.json +### 🟡 analyzer.json -**94/94** +**94/103** + +
+Missing + +- stat.specialLostSplattedByRP +- stat.quickRespawnTimeSplattedByRP +- distanceInline +- damageShort +- hitsToDestroyLong +- hitsToDestroyShort +- labels.amountOf +- labels.damageType +- labels.weapon + +
### 🟢 badges.json @@ -799,13 +877,14 @@ ### 🟡 common.json -**48/60** +**48/61**
Missing - pages.analyzer - pages.maps +- pages.object-damage - auth.errors.aborted - auth.errors.failed - auth.errors.discordPermissions @@ -875,7 +954,7 @@ ### 🔴 analyzer.json -**0/94** +**0/103** ### 🟢 badges.json @@ -899,7 +978,7 @@ ### 🟡 common.json -**35/60** +**35/61**
Missing @@ -907,6 +986,7 @@ - pages.s2 - pages.analyzer - pages.maps +- pages.object-damage - auth.errors.aborted - auth.errors.failed - auth.errors.discordPermissions @@ -1007,9 +1087,24 @@ ## /zh (🟡 In progress) -### 🟢 analyzer.json +### 🟡 analyzer.json -**94/94** +**94/103** + +
+Missing + +- stat.specialLostSplattedByRP +- stat.quickRespawnTimeSplattedByRP +- distanceInline +- damageShort +- hitsToDestroyLong +- hitsToDestroyShort +- labels.amountOf +- labels.damageType +- labels.weapon + +
### 🟢 badges.json @@ -1033,7 +1128,7 @@ ### 🟡 common.json -**35/60** +**35/61**
Missing @@ -1041,6 +1136,7 @@ - pages.s2 - pages.analyzer - pages.maps +- pages.object-damage - auth.errors.aborted - auth.errors.failed - auth.errors.discordPermissions From 292e3c27599ac09fe7fc6142b8ada8d19b26a656 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Tue, 25 Oct 2022 00:19:30 +0300 Subject: [PATCH 09/24] More relevant damage receivers order --- app/modules/analyzer/constants.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/app/modules/analyzer/constants.ts b/app/modules/analyzer/constants.ts index 046c160c0..312052a5a 100644 --- a/app/modules/analyzer/constants.ts +++ b/app/modules/analyzer/constants.ts @@ -4,21 +4,19 @@ export const MAX_LDE_INTENSITY = 21; export const MAX_AP = 57; export const DAMAGE_RECEIVERS = [ - "Bomb_TorpedoBullet", // Torpedo "Chariot", // Crab Tank - "Gachihoko_Barrier", // Rainmaker Shield - "GreatBarrier_Barrier", // Big Bubbler Shield - "GreatBarrier_WeakPoint", // Big Bubbler Weak Point - // "InkRail", // InkRail "NiceBall_Armor", // Booyah Bomb Armor "ShockSonar", // Wave Breaker - // "Sponge_Versus", // Sponge - "Wsb_Flag", // Squid Beakon - "Wsb_Shield", // Splash Wall - "Wsb_Sprinkler", // Sprinkler + "GreatBarrier_Barrier", // Big Bubbler Shield + "GreatBarrier_WeakPoint", // Big Bubbler Weak Point + "Gachihoko_Barrier", // Rainmaker Shield "BulletUmbrellaCanopyCompact", // Undercover Brella Canopy "BulletUmbrellaCanopyNormal", // Splat Brella Canopy "BulletUmbrellaCanopyWide", // Tenta Brella Canopy + "Wsb_Flag", // Squid Beakon + "Wsb_Shield", // Splash Wall + "Wsb_Sprinkler", // Sprinkler + "Bomb_TorpedoBullet", // Torpedo ] as const; export const DAMAGE_TYPE = [ From 08161b93466cca28e3a255533999ff64b50b46f7 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Tue, 25 Oct 2022 00:21:59 +0300 Subject: [PATCH 10/24] Object damage calculator mobile layout --- app/routes/object-damage.tsx | 2 ++ app/styles/object-damage.css | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/app/routes/object-damage.tsx b/app/routes/object-damage.tsx index 10942e4e0..46275d4bb 100644 --- a/app/routes/object-damage.tsx +++ b/app/routes/object-damage.tsx @@ -90,6 +90,7 @@ export default function ObjectDamagePage() { diff --git a/app/styles/object-damage.css b/app/styles/object-damage.css index 3320ea2d8..d142de70f 100644 --- a/app/styles/object-damage.css +++ b/app/styles/object-damage.css @@ -1,6 +1,8 @@ .object-damage__controls { display: flex; + flex-wrap: wrap; justify-content: space-between; + gap: var(--s-4); } .object-damage__ap-label { @@ -13,6 +15,7 @@ display: grid; width: 100%; column-gap: var(--s-6); + overflow-x: auto; place-items: center; row-gap: var(--s-3); } @@ -31,7 +34,9 @@ font-size: var(--fonts-xs); font-weight: var(--semi-bold); padding-block: var(--s-2); + padding-inline: var(--s-2); text-align: center; + white-space: nowrap; } .object-damage__distance { @@ -56,6 +61,11 @@ display: grid; column-gap: var(--s-2); grid-template-columns: 1fr 1fr; + padding-inline: var(--s-2); +} + +.object-damage__select { + width: initial; } .object-damage__abbr { From 2ab4acde77de2d0e5d2a1e1bf8a7f6ad11651adc Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Tue, 25 Oct 2022 17:40:43 +0300 Subject: [PATCH 11/24] Object damage acronyms explanation --- app/routes/object-damage.tsx | 3 +++ app/styles/utils.css | 8 ++++++-- public/locales/en/analyzer.json | 3 ++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/routes/object-damage.tsx b/app/routes/object-damage.tsx index 46275d4bb..2617a4352 100644 --- a/app/routes/object-damage.tsx +++ b/app/routes/object-damage.tsx @@ -110,6 +110,9 @@ export default function ObjectDamagePage() { subWeaponId={subWeaponId} damagesToReceivers={damagesToReceivers} /> +
+ {t("analyzer:dmgHtdExplanation")} +
); } diff --git a/app/styles/utils.css b/app/styles/utils.css index 8a580823f..fef6c85f5 100644 --- a/app/styles/utils.css +++ b/app/styles/utils.css @@ -1,3 +1,7 @@ +.text-lg { + font-size: var(--fonts-lg); +} + .text-sm { font-size: var(--fonts-sm); } @@ -6,8 +10,8 @@ font-size: var(--fonts-xs); } -.text-lg { - font-size: var(--fonts-lg); +.text-xxs { + font-size: var(--fonts-xxs); } .text-center { diff --git a/public/locales/en/analyzer.json b/public/locales/en/analyzer.json index 4af906a70..bf14acb29 100644 --- a/public/locales/en/analyzer.json +++ b/public/locales/en/analyzer.json @@ -101,5 +101,6 @@ "hitsToDestroyShort": "HTD", "labels.amountOf": "Amount of", "labels.damageType": "Damage type", - "labels.weapon": "Weapon" + "labels.weapon": "Weapon", + "dmgHtdExplanation": "DMG = Damage • HTD = Hits to destroy" } From 7a936fd4bc1e4232629012864330041fdd9df055 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Tue, 25 Oct 2022 17:43:13 +0300 Subject: [PATCH 12/24] Fix damage table showing translation key --- app/routes/analyzer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/analyzer.tsx b/app/routes/analyzer.tsx index 574048e0d..b9b89b763 100644 --- a/app/routes/analyzer.tsx +++ b/app/routes/analyzer.tsx @@ -844,7 +844,7 @@ function DamageTable({ return ( - {typeRowName} + {t(typeRowName)} {damage}{" "} {val.shotsToSplat && ( From 5be4abf6cc31f437e39b08c83520019054b039a6 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Tue, 25 Oct 2022 17:44:17 +0300 Subject: [PATCH 13/24] Add inktv return article --- content/articles/inktv-announces-return.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 content/articles/inktv-announces-return.md diff --git a/content/articles/inktv-announces-return.md b/content/articles/inktv-announces-return.md new file mode 100644 index 000000000..93ce1d530 --- /dev/null +++ b/content/articles/inktv-announces-return.md @@ -0,0 +1,13 @@ +--- +title: InkTV Announces Its Return +date: 2022-10-24 +author: Riczi +--- + +In a [Twitter post](https://twitter.com/InkTVSplat/status/1584590544205930496) released on Monday, October 24th, 2022, InkTV announced that it would be returning to the Splatoon competitive scene. This platform is dedicated highlighting the players and teams involved in competitive Splatoon. They were heavily involved in developing tournaments, podcast feeds, and player interviews in an effort to grow the community. + +InkTV was retired in early 2020, just as the Covid-19 pandemic began to escalate. Now with tournaments being held again and the incredibly successful launch of a new game in Splatoon 3, InkTV is ready to make its return. + +The announcement video features some of Splatoon's most prominent community members, including caster NineWholeGrains and professional players ProChara and KyochaN. In the footage they talk about what InkTV did in the past and how it helped them grow and develop as individuals. Everyone was then surprised with the return announcement. + +Be sure to follow InkTV on social media so you can be part of the ever-growing Splatoon community. Check out the video if you haven't already so you can get excited for what InkTV will have in store. From 6e43b6bbd310636184e4a765ec95f6086d27f2a5 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Wed, 26 Oct 2022 08:42:04 +0300 Subject: [PATCH 14/24] Add tests and fix damage calculation of some wpns --- app/modules/analyzer/constants.ts | 111 ++++++++++++++++++++- app/modules/analyzer/objectDamage.test.ts | 114 ++++++++++++++++++++++ app/modules/analyzer/objectDamage.ts | 61 ++++++------ 3 files changed, 254 insertions(+), 32 deletions(-) create mode 100644 app/modules/analyzer/objectDamage.test.ts diff --git a/app/modules/analyzer/constants.ts b/app/modules/analyzer/constants.ts index 312052a5a..ea90740d4 100644 --- a/app/modules/analyzer/constants.ts +++ b/app/modules/analyzer/constants.ts @@ -1,4 +1,5 @@ import type { DamageType } from "./types"; +import type objectDamages from "./object-dmg.json"; export const MAX_LDE_INTENSITY = 21; export const MAX_AP = 57; @@ -10,13 +11,13 @@ export const DAMAGE_RECEIVERS = [ "GreatBarrier_Barrier", // Big Bubbler Shield "GreatBarrier_WeakPoint", // Big Bubbler Weak Point "Gachihoko_Barrier", // Rainmaker Shield - "BulletUmbrellaCanopyCompact", // Undercover Brella Canopy - "BulletUmbrellaCanopyNormal", // Splat Brella Canopy - "BulletUmbrellaCanopyWide", // Tenta Brella Canopy "Wsb_Flag", // Squid Beakon "Wsb_Shield", // Splash Wall "Wsb_Sprinkler", // Sprinkler "Bomb_TorpedoBullet", // Torpedo + "BulletUmbrellaCanopyCompact", // Undercover Brella Canopy + "BulletUmbrellaCanopyNormal", // Splat Brella Canopy + "BulletUmbrellaCanopyWide", // Tenta Brella Canopy ] as const; export const DAMAGE_TYPE = [ @@ -45,3 +46,107 @@ export const damageTypeToWeaponType: Record< BOMB_NORMAL: "SUB", BOMB_DIRECT: "SUB", }; + +export const objectDamageJsonKeyPriority: Record< + keyof typeof objectDamages, + Array | null +> = { + Blaster_BlasterMiddle: null, + Blaster_BlasterShort: null, + Blaster_KillOneShot: ["DIRECT"], + Blaster: null, + BlowerExhale_BombCore: null, + BlowerInhale: null, + BombFlower: null, + Bomb_CurlingBullet: null, + Bomb_DirectHit: ["BOMB_DIRECT"], + Bomb_Fizzy: null, + Bomb_Suction: null, + Bomb_TorpedoBullet: null, + Bomb_TorpedoSplashBurst: null, + Bomb_Trap: null, + Bomb: null, + BrushCore: null, + BrushSplash: null, + CannonMissile: null, + ChargerFull_Light: ["FULL_CHARGE"], + ChargerFull_Long: ["FULL_CHARGE"], + ChargerFull: ["FULL_CHARGE"], + Charger_Light: ["MAX_CHARGE", "TAP_SHOT"], + Charger_Long: ["MAX_CHARGE", "TAP_SHOT"], + Charger: ["MAX_CHARGE", "TAP_SHOT"], + Chariot_Body: null, + Chariot_Cannon: null, + Default: null, + EnemyFlyingHohei_BombCore: null, + GachihokoTimeUpBurst: null, + Gachihoko_BombCore: null, + Gachihoko_Bullet: null, + Geyser: null, + GoldenIkuraAttack: null, + InkStormRain: null, + InkStorm: null, + Jetpack_BombCore: null, + Jetpack_Bullet: null, + Jetpack_Coop: null, + Jetpack_Jet: null, + Maneuver_Short: null, + Maneuver: null, + MicroLaser: null, + MissionSalmonBuddy: null, + MovePainter_Burst: null, + MovePainter_Direct: null, + MultiMissile_BombCore: null, + MultiMissile_Bullet: null, + NiceBall: null, + ObjectEffect_Up: null, + RollerCore: null, + RollerSplash_Compact: null, + RollerSplash_Heavy: null, + RollerSplash_Hunter: null, + RollerSplash: null, + Saber_ChargeShot: null, + Saber_ChargeSlash: null, + Saber_Shot: null, + Saber_Slash: null, + SakerocketBullet: null, + ShelterCanopy_Compact: null, + ShelterCanopy_Wide: null, + ShelterCanopy: null, + ShelterShot_Compact: null, + ShelterShot_Wide: null, + ShelterShot: null, + Shield: null, + ShockSonar_Wave: null, + Shooter_Blaze: ["NORMAL_MAX", "NORMAL_MIN"], + Shooter_Expert: ["NORMAL_MAX", "NORMAL_MIN"], + Shooter_First: ["NORMAL_MAX", "NORMAL_MIN"], + Shooter_FlashRepeat: ["NORMAL_MAX", "NORMAL_MIN"], + Shooter_Flash: ["NORMAL_MAX", "NORMAL_MIN"], + Shooter_Gravity: ["NORMAL_MAX", "NORMAL_MIN"], + Shooter_Heavy: ["NORMAL_MAX", "NORMAL_MIN"], + Shooter_Long: ["NORMAL_MAX", "NORMAL_MIN"], + Shooter_Precision: ["NORMAL_MAX", "NORMAL_MIN"], + Shooter_Short: ["NORMAL_MAX", "NORMAL_MIN"], + Shooter_TripleMiddle: ["NORMAL_MAX", "NORMAL_MIN"], + Shooter_TripleQuick: ["NORMAL_MAX", "NORMAL_MIN"], + Shooter: null, + Skewer_Body: null, + Skewer: null, + Slosher_Bathtub: null, + Slosher_Bear: null, + Slosher_WashtubBombCore: null, + Slosher_Washtub: null, + Slosher: null, + Spinner: null, + Sprinkler: null, + Stringer_Short: null, + Stringer: null, + SuperHook: null, + SuperLanding: null, + TripleTornado: null, + UltraShot: null, + UltraStamp_Swing: null, + UltraStamp_Throw_BombCore: null, + UltraStamp_Throw: null, +}; diff --git a/app/modules/analyzer/objectDamage.test.ts b/app/modules/analyzer/objectDamage.test.ts new file mode 100644 index 000000000..cae7e7eeb --- /dev/null +++ b/app/modules/analyzer/objectDamage.test.ts @@ -0,0 +1,114 @@ +import { suite } from "uvu"; +import * as assert from "uvu/assert"; +import type { MainWeaponId } from "../in-game-lists"; +import { calculateDamage } from "./objectDamage"; +import { buildStats } from "./stats"; +import type { AbilityPoints, DamageType } from "./types"; + +function calculate({ + mainWeaponId = 10, + abilityPoints = new Map(), + damageType = "NORMAL_MAX", +}: { + mainWeaponId?: MainWeaponId; + abilityPoints?: AbilityPoints; + damageType?: DamageType; +}) { + const analyzed = buildStats({ + weaponSplId: mainWeaponId, + }); + + return calculateDamage({ + abilityPoints, + analyzed, + mainWeaponId, + damageType, + }); +} + +const CalculateDamage = suite("calculateDamage()"); + +CalculateDamage("BRU increases Splash Wall hitpoints", () => { + const withoutBRU = calculate({}); + const withBRU = calculate({ + abilityPoints: new Map([["BRU", { ap: 10, apBeforeTacticooler: 10 }]]), + }); + + const hpWithoutBRU = withoutBRU.find( + (d) => d.receiver === "Wsb_Shield" + )?.hitPoints; + const hpWithBRU = withBRU.find((d) => d.receiver === "Wsb_Shield")?.hitPoints; + + assert.ok(typeof hpWithoutBRU === "number"); + assert.ok(typeof hpWithBRU === "number"); + assert.ok(hpWithoutBRU < hpWithBRU); +}); + +CalculateDamage("SPU increases Big Bubbler hitpoints", () => { + const withoutSPU = calculate({}); + const withSPU = calculate({ + abilityPoints: new Map([["SPU", { ap: 10, apBeforeTacticooler: 10 }]]), + }); + + const hpWithoutSPU = withoutSPU.find( + (d) => d.receiver === "GreatBarrier_Barrier" + )?.hitPoints; + const hpWithSPU = withSPU.find( + (d) => d.receiver === "GreatBarrier_Barrier" + )?.hitPoints; + + assert.ok(typeof hpWithoutSPU === "number"); + assert.ok(typeof hpWithSPU === "number"); + assert.ok(hpWithoutSPU < hpWithSPU); +}); + +const shotsToPopRM: Array< + [ + weaponId: MainWeaponId, + damageType: DamageType, + shotsToPop: number, + shotsToPopOS: number + ] +> = [ + // Splattershot + [40, "NORMAL_MAX", 28, 26], + // Range Blaster + [220, "DIRECT", 5, 4], + // .96 Gal + [80, "NORMAL_MAX", 17, 15], + // Splat Charger + [2010, "FULL_CHARGE", 4, 3], + // E-liter 4K + [2030, "TAP_SHOT", 13, 12], +]; + +CalculateDamage( + "Calculates matching HTD Rainmaker shield to in-game tests", + () => { + for (const [ + mainWeaponId, + damageType, + shotsToPop, + shotsToPopOS, + ] of shotsToPopRM) { + const damages = calculate({ mainWeaponId, damageType }); + + const damageVsRM = damages.find( + (d) => d.receiver === "Gachihoko_Barrier" + )!; + + assert.equal( + damageVsRM.damages.find((d) => !d.objectShredder)!.hitsToDestroy, + shotsToPop, + `Shots to pop wrong for weapon id: ${mainWeaponId}` + ); + assert.equal( + damageVsRM.damages.find((d) => d.objectShredder)!.hitsToDestroy, + shotsToPopOS, + `Shots to pop wrong with OS for weapon id: ${mainWeaponId}` + ); + } + } +); + +CalculateDamage.run(); diff --git a/app/modules/analyzer/objectDamage.ts b/app/modules/analyzer/objectDamage.ts index 97810f643..b8c357b33 100644 --- a/app/modules/analyzer/objectDamage.ts +++ b/app/modules/analyzer/objectDamage.ts @@ -10,33 +10,14 @@ import type { SpecialWeaponId, SubWeaponId, } from "../in-game-lists"; -import { damageTypeToWeaponType, DAMAGE_RECEIVERS } from "./constants"; +import { + damageTypeToWeaponType, + DAMAGE_RECEIVERS, + objectDamageJsonKeyPriority, +} from "./constants"; import { roundToNDecimalPlaces } from "~/utils/number"; import { objectHitPoints } from "./objectHitPoints"; -/** Keys to check in the json. Lower index takes priority over higher. If key is omitted means any key with valid weapon id is okay. One json key can only map to one DamageType. */ -const objectDamageJsonKeyPriority: Partial< - Record> -> = { - // NORMAL_MIN: [], - // NORMAL_MAX: [], - DIRECT: ["Blaster_KillOneShot"], - FULL_CHARGE: [], - MAX_CHARGE: [], - TAP_SHOT: [], - // DISTANCE: [], - // BOMB_NORMAL: [], - BOMB_DIRECT: ["Bomb_DirectHit"], -}; - -const commonObjectDamageJsonKeys = () => - Object.keys(objectDamages).filter( - (key) => - !Object.values(objectDamageJsonKeyPriority) - .flat() - .includes(key as any) - ) as Array; - export function damageTypeToMultipliers({ type, weapon, @@ -56,10 +37,7 @@ export function damageTypeToMultipliers({ id: SpecialWeaponId; }; }) { - const keysToCheck = - objectDamageJsonKeyPriority[type] ?? commonObjectDamageJsonKeys(); - - for (const key of keysToCheck) { + for (const key of jsonKeysToCeck(type)) { const objectDamagesObj = objectDamages[key]; let ok = false; @@ -77,13 +55,38 @@ export function damageTypeToMultipliers({ } if (ok) { - console.log(`for ${type} used ${key ?? "FALLBACK"}`); return objectDamagesObj.rates; } } return null; } +const objectDamageJsonKeyPriorityEntries = Object.entries( + objectDamageJsonKeyPriority +); + +// for example blaster belongs to both Blaster_KillOneShot +// and Blaster categories so it needs to be specified +// which damage type uses which +function jsonKeysToCeck(type: DamageType) { + const result: Array = []; + + for (const [key, value] of objectDamageJsonKeyPriorityEntries) { + if (value?.includes(type)) { + result.push(key as keyof typeof objectDamages); + } + } + + if (result.length) return result; + + for (const [key, value] of objectDamageJsonKeyPriorityEntries) { + if (!value) { + result.push(key as keyof typeof objectDamages); + } + } + + return result; +} export function multipliersToRecordWithFallbacks( multipliers: ReturnType From bf4b6fab13ff14fe3f36ef6c80e8a955e31c545e Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Wed, 26 Oct 2022 09:00:43 +0300 Subject: [PATCH 15/24] Add object dmg calc to nav and front page --- app/components/layout/nav-items.json | 1 + app/routes/index.tsx | 7 ++ app/routes/object-damage.tsx | 2 +- app/utils/urls.ts | 1 + .../img/layout/object-damage-calculator.avif | Bin 0 -> 6593 bytes .../img/layout/object-damage-calculator.png | Bin 0 -> 36380 bytes public/locales/en/common.json | 2 +- public/locales/en/front.json | 1 + translation-progress.md | 90 ++++++++++++------ 9 files changed, 71 insertions(+), 33 deletions(-) create mode 100644 public/img/layout/object-damage-calculator.avif create mode 100644 public/img/layout/object-damage-calculator.png diff --git a/app/components/layout/nav-items.json b/app/components/layout/nav-items.json index a31567fa2..8f44c84c2 100644 --- a/app/components/layout/nav-items.json +++ b/app/components/layout/nav-items.json @@ -5,6 +5,7 @@ }, { "name": "builds", "url": "builds" }, { "name": "analyzer", "url": "analyzer" }, + { "name": "object-damage-calculator", "url": "object-damage-calculator" }, { "name": "calendar", "url": "calendar" }, { "name": "maps", "url": "maps" }, { "name": "badges", "url": "badges" }, diff --git a/app/routes/index.tsx b/app/routes/index.tsx index 334a69d73..e10b66453 100644 --- a/app/routes/index.tsx +++ b/app/routes/index.tsx @@ -22,6 +22,7 @@ import { CALENDAR_PAGE, mapsPage, navIconUrl, + OBJECT_DAMAGE_CALCULATOR, plusSuggestionPage, userPage, } from "~/utils/urls"; @@ -75,6 +76,12 @@ export default function Index() { description={t("front:analyzer.description")} to={analyzerPage()} /> + ) { const { t } = useTranslation(["weapons", "analyzer", "common"]); - useSetTitle(t("common:pages.object-damage")); + useSetTitle(t("common:pages.object-damage-calculator")); return (
=8Lhkol@ z@BMgZt$X)5XP>*zx%bxv006Xh9&e#mUSK=Gll?_!upOT>*vd{_hEEm%K+1Kt^|1QO zJsGQm4aEIFE&u=pgFOE0|3wJc`oA)GP_T#dKMv_>=LADsL4PB8000^AH}kYG9{>RS zo~QUV;ZLdmPvYtF|B*oYKLV701%#i*k^Ad-y1R%v zLv8KuFbFCNdkgchb%u#P)qo*jEBCjeR{y;cH5lsr?`}^=pB@ku3Hd!L02vhx zJ^ZNy8Nk`b%Hufz3!RJ#fmqPvFD7_SK<%ULQG@uihKA$#fu~wlca_geq0SF37}!S4 zjIeCh{`#jx9`wG9Ssi=Yjkr#7ggi5DV^PKK>O&mIeV_H5hYw#+_wS`B)kdB%1j3p| z9gQ6Shj?V?@8ZAH6YAKDpl&T)P9W{`%21ISx~WI{Nf9G1?wG6Qt@TC&=rXc=gygHD zDvAgwFhe&{-P6_3Mz_4;90toOp}IECX+=#EcnHWNlm835?KS7w?I_>*H{JkPwLuCO z6(|)d^D-?!(dKtZ#Z_LK zS%g=k<6+hy;0rS*Ryc(AM?jYam<~d7n-R&J*CG885hqBQd<^Gc1tOz$M zJ;-a!pHO~v#y5Fwsmxhf)+P}mN*jQIb2Op~GIXX>lpN$`v`~G}_k&?!U9lr+003*W z8P>5WfF10BI0f9BL}XY8e+UZ}*!lh#x^#R#QsgDJZ$^6jbv!Jc4DD0=fRpO7$gLB{ zWGMpZbc9FU`cO%L^L;H^Sa?{YCNWXsXB0epx0IbyfAPZ3XNmsp7$lauPeV|wXMz_? zJ7wn2RJTC?$qr)nZuRr7VBLhqIhSxjYVPJx8-4LroU2){V-XsSL+o2sEeu1L;Nn>9PQ;w{==W>65b|2}qMc zCD%1bbWdKhG`|#9~B}g+`2CUICV;tK)?| zi%|o|^^RsjUsAtDLdj`xuc_O+d`aO>pd^hQ<2|i?-4S*t?69I!%_g{`V`oB3lBEQ+ zZq^3(bLt#|uM%i|D_gNjla{i!olMb!_VsOz6Vq9|zASfG zarRLW;vH2~Uuzd%8cfpg(XMumJ^rwIuQnFXt*g@SaRLk~lI0Rty$o53kBh+nKt`AY zYl=6)rHHUUXpx#E<5!_v<^e}aJ)Wx2z0=| zq0{w+-L>z?21_SJ&++Ot&!tH(1<-*c``7ZkW_4L%8N1Da>52pH;V6?y_X^kMub=)3 zx~t~TdI&-wd7GNw`uic)L3sWv?W1bONn@-hBRyxm@6w;o(b$#ierJsA8FntX6y)=p zK~C-(4IB_6WUT&SfNYP{L~dmEZkC%1587m!a+HJpx@NKWIl!-3|0>qlr+o8Pno(!# zY#9SV_-oq0KqoQxUY-EaAr}IL=2eL_wKU*}tydl{lH_fuSXVnu)siCaHHT#(0%CJ~EonqJ^>jm+`cg+G&psony;MzVK-5H5|!vnR&!E43`j%AuP z;vV#^ETC`Cs}Gs|SRwL4!oaF}fm`O0mF<1*m>1+gvcB3k#DEoXt2Ep>l!F}lOX~!B zZ+{_8D=b7qXiHb^c_6{r|6?|htSZ(#`ZnAq6-P|h!+sU zv|#w{T&Iv-){PUrP4XL|QtFtncj3|RmE47AeH<8c0J8nqDQngFh} zpz7Re7xUt?Y&&CxBxEG1hgYl_B7&W?31ra@U24UWEf0rNXqGyKIM~c5kz{<^74kB|T4{ zw3>gL?`lgvg0wHO|9eq7j)Uh9XvmW*b5m)l>`hO;0gp|9{03O!D)vDdYuVsW&L0t{ z5J91A`pVyV7vrI5RVkl3^ZPCp5kYL$VTz^wDAL}1KSb-lm1MPFJqtMu4iG$@Vm>lF zW)+U_-$ebLlh%zw2BrcjOPa9E9A-%n69?QZy7c&A@NWd+9;d7P0d0sT4Z;^!57tER&YumVq%EACfL2kq)9oEgxP|l${`N9=%k>eI;=y62bE`PnAYLM1p*r7CM zm&y`wfQ>>*LT5qfISsu^=`>7e-7&_zBbMZO==&z)$8T3N;j3^t^uPx-8p*yU_dH%R zxkZMTaSLr{Y^-EANatC{fmG!rTDlc+h!@J)sxVsJ%i)>=Y+IL5Wg6GF<1T5Z2sdbw zz@H6P(b$qB%K??TMC2YSWc;95nd=m;E$@c_lRdIH55Gg>-ghX3#DV14_C5C1#z!|d zr)Z279QUzgmuYPUqGH0BEV-i;mhnD{$Q&9rsK$jv`3IHJ?{p@={VvJ~E*yR}8179n zas@PpSWu)4@hecr$0rFOd9xyKlPYyA{7P%+r#eRfH(@ck3oar-Ou7T#FKi1X9_2{e zBg=`k95h}p0kHxzN86zV$tM{W^p~eR591try*igwP6}h*hbjzvtHW4NCF8OttUUSiZU5&5d~BQ;L<1x(Fyp+G)$I@G)Gs-KYJsx7T6g0%~Q! z-i`KabM5TyT-d7DM&hw|4La zEs)Yh@F3Hlu(=PS-aNkKW$lZzAhFx^)=zOD^T$`YuZZ@Gb7rWNdb($^`)91$A_^Rq zdaYiw^ay%(A2Cg)m*heSfH1lYGd)EaeDH1fFo#hxR&hBhPsz-n zulxIhcXjhWE$9P!wl45Y19U!d^Z&r>{}HvN+(K+Z)+az(8PX)Mz&OO*&N5@sD*&~HTDqnvj*yrO5*WFMUwNan8W9ORiy@F} zsYdcz*mR-f>7uR^xvj_U>mab{f6)cs8L(Y_Gfkn3A51j&JET-)D67^7RmGNEn>O$+ zujx2Y3lgc-Dv@weQY*%_a24+rLWs)35=bMx780hyh?Zoh(;_5(a1%yn3eAox$PXVq zfLHjF#wh5Dz7!20q`n<8_jZ{Ydp>c#oRRQgPG?l7!qX7546>Otm~3G!ZyR&l_2pr6 z{WbSaB`hy6DN`;w0nyBj|1JW`8H=I6p38=#@JuVtsiC^uoOs55mRfoi=?Fa}87oL}Xh;5#Kj23)hrMRu&~6uyaD zwGP>f18K(5E_PwbZ5y#=9hy;bWbu0JUlzA~<_5j)*LA9HeZ)$|gHVn+JD3S3+*!k>UOiwGWSe4pH>73h$v>7!BuZbYXYNTO zNiC5P-}0U#Fij`XGyS7{7fCetxZ2|*1G$xq_TCJZx`<8bI@bNmRYUk#t2h%Y*I51` zy6T;>NJyHgywf6q*~M!s$qxFa?DN{LC7EQ z$MXJFJFrcXRH{a_0zEvTkP-Tka-Wyi85hrX+aaNKz^Nvpa+=nL!b4V@>uKLqSE7yfn%fYnwW$J30#<6my$^`WyrRMr} zDW^Nu%>`WueUNL)A{bNRW9$TcYVzFLYU(6QQKVfbBxfOd;I@zioRE=`K#Jh#2B(Ws zR)?xIx8Ret9hw%6A~)8Zi3@l(sOTxF3C@pr0t&dH>nUV{36fuM@-J2CS85iJ;TgKS zf*0JR{yH%nX#t??2eSlr;m>E0bKm^*kIB4>2TTDK+XrXYYC?!=?srlGPA8qDE zT2lRcCu(0B%VJeK5iIN9(gWz$Msvx4RLqzogAZF`X69m(7|uR+s_=j+^sXax3;G|N zWx{2gZe#!#B@hs4&eu2L-e3G#=Oj8#e>&LQoC+CIfiP>nEC*Wl*C7g4Ryg2$jIr>dZNV|FZT`;hf0)+W>7gMZF^D56dseRxj zpYI%6Q~j8-$`W7hOBnj@geqtSoBNpf6rf#FfcwYF;xeqwjY^Ie)PyDA9cL{S>`BZo zls8EL!<*=2XV5;9Ted6=;X4F`@sZJU@HkO$RhkXo z7zYl#B{i6^*(CDpqjp6Kqd1I7yVJtc%1cG-!GIwcZ@*7E9gVJ;YQ1>|Y@vSfa(~l0oXDd5M$Y>U zbG^kG++Jt9aZj|tsxib$9;^K`PKR)S-HCEv3~tz$SHgQnfhQnGr6MxAJ*9D3>OMlk zCE+BlCiC978^@lp{eBOznA{XLw}Jf!chKBIap!gkU~(LX<43(8zlFf`xK_8jzS5Y4 z)D0TwdWXt6`@psO#-w+e`r>S%ggQJK(P^mVuv_n-xTYD66o3>TIxp#wRubF*($%@$ z6Fn%h>ZZc@7C$bh!Y;#`tRhpzTT4?}fm>I$MR1Y*S}Y#hj~vPh}czqr&T zOHfW$2(m6r=>8}#9qFWM@jEqZ^dFY`-m{jT<8m)R(pKQ?3COv@r~9(qtV&sZGV?1d zzfkOgU(;i&qDYiNqr7B^UAt-G)eXlx2`O%s_I?cS+%V!y;fwWG0`D`#ES=OCA3A0C z>Ljqw#S?4RKM3YCh4*9bIL$5Q8PW(7w4xgmqfH`h=-yLnBn{`3QP_6*hC)^;kT~`+ zvbJC8nINIy=d8csS6Uy$qolAX z&Dl$ZgZs%QTq#sWE?-wGnhwv3gbzA~vLn&NTlC$y$-D2-jd&=%UKp#-9_0(MB~8UA z`rK6DjdVl>DAhX9LK^Uo)1u7rgC{uIPi>Rmt~TboOa_?1-oV=O1uWhw+T1*@MNBuO z*A|F{(0*60G+(@_!K3*DY7!;``U4-bUV13d*3NCafMMpN(m#tl)fQ z3wU4IHKy#jES0*JQv_pO0$g@aHnphcl*n_*qfF7%YC!Wr49LX?;{ zk$3U)rDb0Vys-<3Aw9re#-GwG&!d5^FoiO3G z*X4SzpU=mF_2pGvH1(N$Hu27oR}hEp!F>;r-4=_*tp;(DWFGLvY}lJ^wS%ja({71w z?}kn1(l?WO)Go1S!CouL8#~z$Y#USgargM|ifIhp&}$MmB@ z;l{Mj@nDC#+0;h0%Ru2od7E;lnkZ?oX2;;!k|Gv4GCsCMoqGs~Gg9m-NX@v549 zSFKwHx}R6&6&(d9MjsBElt`T$5RSkT+0~qaSD4wsTQo?wm}u~sl9%MC{H0@?(GeR> z_3Zw-$nZ&g8fK?=p2(ciQ%L7;3>A}3Oh1287SRlSZrbaKn73zTA{}(MFUGHv`jA1- z4@6ckN-Ye#oYkO8N251Rj=AN|VX?{$?uRL9Le%(}k#Z_zQ->w)D1Hy6HHg|e zOy-cBc%Cr|@M+<%550Gju5>D|t;r^uLQ@r#V18LUT!(a?X*zYA#SnIfuA)&j-aIY# z{h2E#32@r*d$;5s6U(}2_0KK~!c;|p1{=dX(MIN<&My#;ExaE>e*t2AzBMb8uW|W0 z)x2k9a6ORop?A@4$XC)bcg>-cyOCXdbQWD-vFI%$qDZdB23%q%XT!cWw%W_64ba-q zb)@$uZ**v~D(tRg5v-vTjym$W3QSuPf4r|}<`#1#N$nlkL--R$GW=}u{JkBs8NTKl z6-aDwVmc40pMqr_vtKaLaWRMmcwuMgQ;hU?aP4fu43#3 z0ALdQcL67sLmyrqQg|yGdF#2^diz;<+5qHjoV^0PX!X2(tR38F_1)Z@JASMM0|2xD zRRvjn|J=hoKY#t{nZIE{w)}hXklogdu{c)xa8Te;>=zQ*3MebBVh8OzWX0Rzl{>>285)Qj$p@g!7vqHBJq1?{ zh2YQxv*P~0c`ws+ONGa98$pd&dzYGU3?og?kERG0OX@0%`!)rd0dyK*?t9j6X)`7{ zdXIL{A_&;hP)+CN-hQHyZ}@G4b??6;N^$SL`(k-`IUJ+B{(@3yHRb|K8YJ3#N)L5~ z$FQn%)F~B>L*K|Q9%oFp_!#a_VQhzw3_Hwz7vnH^=xGVK5DmVmy4NRf#J(vR;6&0d zBSHgvj(f$uUAb{jSYv=X}Ijd7-#Oz3ZwDA zcDHX3FE+t+_mvP#+CH-K+ETk`=vFTGymn|Bjab&vJWGW_tjOr-=%yi8(`9DOGxy$= zfXTXn%n5eF5ANSyGx zEC^0aPA=!n0z~wIAp68Q&Nh&_+^EkR_qbB27kH{QK3?1Hn%;uPtQoxnF~XhH5tYdM z(|rExj>COGV%D)o8?q|5RcYJd?4BP5p&uvrz;jrpKk8S4m>2wJ3g1Zp3xEZH#M^g3 zcIq7|AnUhRo(0jf4S0prc%LqLujRF)a7KYC^)t#XqkexBw4nfU?1+(3IeRH|5Q@9Q zolo?vGP{Wg>l3A0`=jv2-*_(yq(o3El&5~CsfR`fz3?3mAQZ5I^u1_jTmL_kOOb#^ z^)uW8H_QNgfGN@gCLOSk1=^{CG6Tef9hn?(rfQ2)5wJo4K9x|{N;!W*_i#^GgKTPg zm3yo$mX-5V&LSR6Llj#XOsbRv{jYwk;!q(g{H9E%3%Z2CNRUHJR@IoafDs}&>vwF33xZ| zmCD|zIQ>77T44BDyUaz58EsXV*7>d)pNL3ybR~me;?U86Mfm+FAFUJ)K}IJGHxgV& zn5I}<&u@s~hJb5(4UldvT2&1*IO`X-R;;aX&l6!j|;e4NJWImX>ttg+^_hKL$f$)UVmI$k)d z2tI{OTZVlZNV0#KT7^!-b)adEx3i7YCOJg8`-(WA#oX)B&41+~n>;0o)}-SO%EZ+wc~*wQ*da%-8mU zIUI?C7$h24EwpuC86jPMmG0TFC&x%u(!2>0j9#IEw8qimrU4gdK%Iz=P!xd}IS+7E z*mLOJza#bFV9D4kOVFwBw;@dJY9Z(km<040$@a_6aoL>Gt#&zDq3iM=$>)cQrQnNU zsNWy#O>som7OyMaX4aFxU{VIwTk7Z#w}3@t&*Z&-BHvpwxZ`BC*=$gcm6f{CK#ua2 z*M)0aHQ+HQ_Cth~29LjEBXeJSi3s8)&tj1a#&W%FXms0gjRde41ikK=K{i(aYq!bT z0&+MSbfon;KqZEi)T7e(oL$k_95n@9=&R*1L8~!R=>9WnM$oTsPOrI=bZAGmJrL;S zKAOgoAoFnA44@m}^(kgV=KRDP=a!W!JxbbE^E=wj%I#1k(z2>BX zA^S($1m~SG%&h;b32=mR?h)hR0?Rt+U3d!Yr^$iez#O#*&|8sy-K3CnY}0r5lr>U_ z)e_B_2s?2FtBad6u;P7h0-xumO6ctt$@%#?;2-GPKux~zqYLr_$vDIm)(FgaW1kaT zLJS@w_g{WjeJOwy5gUs}%w^JzEwZF-y;^2E+#BX_?Ee}KBBoJWfZP`wv5E@$>#9wx zI-%VDwLah4{D+1IQ0->}{2AH+h-`r6#}z(B)xZJTcz6Ip6nM)Ta^fuczmvr-1nhCX z=?ApouRfwjjfIdo01yW?S26AJzFe|k|1ShDPDmUA+yNq^qtBPTM)?9Scj@mPvDF*M zP47gZJ%V_dj+B5wx9A(KR$Sw#SdRze1YaQt!TSo7SHv>AkLYzR=A}UUZc+!B(|bfJ z+bbRPg+K3U3~|U_FO6&=E)~Ed;QR>_!wLloqD3NfkR$FxL?kF z)0f@a*4{3o_e0ZWdngNxib^u?7e+7nBoi@eV4~ueTshjwMPOf1>^XIaeDXSINlGYpVWDPYGmghXBL ztthX57!~{6j;uS2kiJ|>{O|j7XTgYX?G`j1Qf7eN(8s%)&m{ci2Z?`hY#5}F5du_HH#aAomKFd7?QZjzFzuu|3l~q|hUdhHB z0&b`IESIN!1cU-3KYZbd!oih4QjhdrvWUl&i?Dp4^>;@D-$x80^E{&^a!04pxJ5ON zE`d*ldCi>6nmV1|9JWIZfxl(m69gWsvxs%ZctJ0+0ZZCyQFJ=?8%dvq{#%+aapCfP zJ~VCIp}JWmf9;swspl)7CFAr1K0?x;$T&s;U96v>SguXbZ5RXv!>n1tr9kiUBMmk% z2mt5L*+uT}d3LAJ))Tti-dcu$Ho2NH|FvRQP~!bOz2~31?@v5X5Y z%*F%wce)jTvVmA+{E;s|{_K|m07zirW_$O}-WX-+F6Euxcex)>;iND*&Z-RoBPnu9 zhwsX86=Yk-atp5aIh?VgyUUFiAd}b8uA_rEQ4R`7zE$%!RU(vVdF5f0zLE?__@LNn z6PA2c{M5=* zSaCR~fFwz`OhE|c%5xLPQJkop~y!B{hS`~FM zpr4nBB&{zxx?x3`0JOS~vn;*IhC1;062mzeSDyg{&{hj6O(VycSmS9#fI=Uy48z02 zKOQ>S4~sZiE(BF^m=$^T(wm=*`?jw9U5aL-w(FW>40NJ)3oI6bVC^1Zc_l!EGtbZ1yBQMYt
J2_ES#>I%4}6{5_fpw?Qk4~`qV6&?1kkq%4WTgmkw|`375BM@)Jo)kgTWt3*UjV z0d8i)8*m}NK3LWho1yqK_%M_;5hhed23K|msW^9z*VL_*$@Q>zV@u$p3H)x0AO&gZ z8#M=)_<}8g`N+)8QL^HG$3yEqaSn4;R(#6#D|gu6!+wgUE9h*&TirUgvyzTfomS)K zK+@cC-kGWzwZN>8f@J8+=AdU@y1aFSN-Frtc>e=A ziLD#_M@J9t<9AU9bo}C)uw{Tm&X-d$J_=06$|M&sGkBfRr<2FpDZ!o>6?~u z==Hl3En<0mHpjimdD|{$3y^*d#ZaiH1-NB40li=cfQQFNGQM}J##yU zXy~RhHQI_-X*=Z^_DcfHU@TA|RHE=!q8kM<2`+)?OZ*2RGwhb)w2xCJghWI=ol%%g z$A1n!E3_ zbTvzQyuIEsGBU2MSF4g&Q>KnHWl_`FJRS8i=zpI`LKiW(PkOh^F(@Cc)QD+k(?Z-*$`&xp@NKIQBf<6eK^`hY<1Mf9M>>Z>}!)F3594Z1v3g6k$ zVyLV*l@Y0^ou04bp0Ak}F5p8ALm?vFcGMeBL$235(K*WJeM^Q8rh{rw<1az?bfjnO zW%G(U4$_!OL2_80i?+=nXS7#$(GKO;Y0Q#FY}4re)OwAPi0@-ZME0JF9l>BXYntfK z40Imo_AyG4N){6)Cqmc-QEv|t_2t=ra^WzNHGLh8<5W?zvD2;|1^T6&00BlHV#99+ zA`NLv1%P0>;tBcu(K1+M9hK^J5zocGJ%`KCAK7*nSh8D=Fu+e5xo48QW0Y!2#ra2My!t%5xSu#AykF{ zX1Mqert0DWVo4&?$iDrJ#K-$RbCGbMOuX#fDj+(?#q&M39H9O4o~d-M&I|rdUpNNp zbzurjXb;L23f|O?8M7JT@MklGB|qm4SvnOU=50wa?*nvW(Q=tlbRWN|AGuA*5iPAW zDK@L}6r@el?rtwYwea_&vxkS7Fb{Hr4Ep_QOfEQSWS^LrM?C&Mgke>c7N#B^~cmkI=%)Vbhfi# zjGGW`GyQ}9sXe|Z=K2Q}m2t|EH=;EycGpo_@171rNM=4E88%3$?(*tZo?OVya;|?7 z5W@Tmdhb4qY40xO9Y{`Z@@g0p;DPKKMCsegNfbX2dYxuUT#aU0s1ZJn^Hot5ZUO%d zKQVFAf`so-_aNWA`hvV$?odaDkHS^oJQX95=%@}bSBGgw0&Fu2Anpu}n*wE5frtV5 zzteii*LOhG97bMN9|qf<0mx>PEs57-LhNe0*aV*={X4RHkHoa6@pT;MYnq>Wrlcd@ zbG2wVAvj{n9gZ2%tW4m@+qcH2)&#~6aC0P(V+0pk?mU4qLIoIOjIJv2mGmP66(!4( zaWoShna+)>LOOaFpfU)QJS9I1Sz&;%V2nU~Y3qR)NyWH3#M9}3C0?PsUb?3JZ6yvt z3Z}E0WGc+;Hnl)gVp<-vn)8NXzF+8TaBn|{@>z~=cMSA}2^RTZ9PU z^kpM938J0aN3t5$@-(X~{5tWav9iT|86R6T4>6EL24#4iE-a=@7r;dMgrhq{>}4b?cB$kqk+iWai>W;FT@DEaC(_988~4VC7S34PL19%Ok2t# zNW&rkVr?UwhYl$i`QdhlgwN9nCIP?^7(D+VLD=I!G$(W;c)PHIPs~`lC0hr;pS$qm ztp_j*J*my?9KR+}MeTgtB2KEu5Nk~>+v-B!B$-gcJx^5aZ_C-k!?|H#1`k73ID!E{ z_}zJ~I2OO6#FQ7(MK`v_7i(`A{G*PRO#Xf~l@cad3(@i@4z|Zcb3zTp=O37D#R%Ug zma?;X2y8JVM){(M_l#~$m0r^Qez2pj2j^jE__BzPSgTwJz(KZois5uE{rBTZjeGFd zdUG8FK^s|z7%UN>k$LR6r4-L2J+x^op`pTVD7KK>PJ3QnKNcCFymhwWHT z<52E<81N>39-+*An@o-w22`(~X&&7w*SC9NMoC$}Wz(v)hQM;5;`vuDM^6NWgk)YR z#!w{RRxrgeVZ@gnlw_&y64DvX?+>z9=Lfidq)y%t9it)n(TuA0%19)~rT#l#Z}?m{ zJjAMyN(>(eetL%zmXuX+=uUdG{9ydO{;Agsef1Nf46Y1dfi-bvT0U)#j=t6UEFKD^ zLvRXWYD3q;yH~AdgV9{+Js#Kiw2S3Kr>%Z>JW58^$VC!H_qe>jOFyyC^(c#t#}5xk zB+@{je)QnC1q~)GMNgX?bk{C#?_<2suAuKZr(Ot)d}D}z&~7!JqlI=b6vt%dSqvRd zn}ptR6Ls1kqPH9>KEJngJxUOLvI_BbRpJXWzLlKnpBs~+)H24H@A-Eo4^M>Wz)f5X z-bL$f)84qNy}0R>wpzTbCklDy+rnu^wmqs>SOH9PAj#qozJh{YZF_RN@8=J9Zlvzs zB1I$XDP;UN6&;F8^M9yN^`4>fs~-*oul->HS)ZCP*bdLDXYLvk!6TIaQFg4~wgH44 z1Q;kcQSv)>h4WPFF_r9z<8NoHG?L-pV&o?2rLsmd+Ld!I8J(3p!cnp~KOFbXQOnO8 zGk_)%yE;Z0%f#L%HurrH!J(J?Qt4EVn}2IysP#VdMw9OmAZyBwPC8P06b`9 zmwJqhtC1^4vp1P6wECKwe)WikYv3#jD5#8#}LHT#oiB#iW1 zu2&FgELMY?*S`9L+}(#{a>b5NP95Vg`)Z9ZHrSV#$P>6flYfe76RxHD3=EX10`Lt{ zqtpzV#B{_2zql4JO&17xm69z%U~g*u!?w6+@B)v3o}s-0tR#Ihn6perPr4r|->PCi zix*fQ=^CF`SGob;CjqeP@_!pR4YxpOf7GXxpyID}jL`gvF4?eUhr)E&ZSZc@drjYW z`}g7S#t~`~0qJf%)_BV2UC-G4@tK%41PNlm7wFOV=@65#TYOd-u%%qj44BM5lroP- zG6c%QV40sIF=@9w>?M#M$`s!NMwl=vcYtAGRf|j+qLL6V4~pSfbdak{!Uo3A3WQJXYxLXjl@ zv6YQN#M@IJXqj1lm{NoPP-?6+eG4oG+hIP zxkAkV#F!Jg=3nQ6Y=l$MnG4>8oPyxTzN5T7CAy7-<%ckw$oWDiOnU=$Ld%%!qK|DZ zg?{SBANpEVwyS!aOdXhbJGgAC5_?H|=`EKL z4w*eN{5~=_{P-hnIN{Xx(@`LkIz#x{4a!fSq&f{jRL|7bvJ~<6p|(6rTr1PbSrvQG|cQ9pC$UjHb{ycnPjq zXYj8vN{ePo9Nb=ntP~_wDJUq{U09RHz@`{$0_lsQJ7&E61Qx;N%I{Z3W!S4=Lz8wgBP%mmZxQZd{u_UTBWDJvU^7m z*$@rgt)`prMzD%E(v9qtW#Oxed`1yD+})thHMr zC)b7*zOqGyE2RvqhaRY{OmJ*%Dkew1!kZ z{)6q?vC_;#2eQ)Bu6of9J(?q$!Q%YcQO-`Ckw%bVK8g(KiXmSMk3x*RJwWgS%3E}X zmY}Z{-hD%`z16N?9DRP<1z5rpeU>(Xq%zI_or`9!1+Kq4DToLh{Ag(yp_60toBK0`h05xtbrED(@S(D(RI~Y=zzVu z3|bIoePo-gS(_c!t>67aJvMSM_t9O`5kfT>{v3>A;r7j&KFXk`Z;fbXPwe4yQ23I* z!mCYr1^qfE_v%~1SnoUXo@q#BW^5?bseMD6vW*D zuKygxLE_TbMfDd9OlI@+>Di}9lusxrPn^f5Y<>J&*nY2LCtk(;M0+6@wfo>3)3;py z(Hnmv3J;v{=Ib`agKeTDW>&&ARF23AigdgbPiDyDibzB0u=rG%^q+RHm(S?LEUkxEyzuf^699 z?2+V5dM)uLu;9hH~^*&1y* z!;4&3i%vwxk)8U%6ck^B!9?27_|}mh9NEuD>p_=qn)-n5mLRQ<)L|eIPRO3{ zD@PtoPZZt8E9~;CP{6k|Z1*>HW{4IhK;_6PK$W_wbMUk{woP9@#8H@|ZC}%&sMX-B zE&LM~R`fq8MUcD^pzbFw_LmkMwjcPTdnrE8HzHPA)HM_Og+pwm4GaParm-d1QrVqV z`R*5rc+NbJ_1Ikyr}*hWPx+O*tAi}*5b?p;%FY1Ec$+0Pt!!OkhL3r#DK}k1ZN;LL z)?!IZ!Xh3TX2VY#@N_v=z1EZ8aQvF?T3QYIp6-k1D~TLLA~&h?<%v?CkP?UO#_=_u6vSn(S1MR=?$+Cq4=1ol zbOn!m>aPLmBR`6(ve;H5*^N7b(6=%gUS@thW0*^rVXTLr&yNu@&XGdRMY4>ha}BJ< zuU2R*?`0d$$>zesAIRaZP1OY97j;<&G?OOjbR@r{{$bP2$vmg41(vs}7V_qY-Z>n% zKZgRs&o)p@_I8?vh!hLUopGnt!kKbDbOha6c(V$&5$<1g=tXunYn~ZL-Bv9(Sh;97 zc&alm6)pCARBc&_)`^Cx7M6WCgY3||?kW!%m-edrRCufqKy^!LYL4ADZVg;y$8L@7 zg*1KH6AcQ&3(;QdvwD|e|28Wm#X~BRCCEcXnV%OkO6!!Nt?FBgj<#8}T8OD~tjs<6 zn816+Z*NTEEL=jwt>=4!keBcJj&%CqS9O=UZO_k0qua^%{Y#?V<+W=?0eEH&04g#w zYy8Gyx}qwHQiR-9s1}u*OUe8?yE_~i6Cz7RHKg!o@8_jAisc2z+eDwFLz2$Tgp4!u z#W%f@O>VrM5TMX*+_bU<$!s$Rd6sq+=b5Inf6D%0!YeGvpbANq&Wy zLf#YTjkh*8AxP^>ovMR$r?Y7~4yqlIgFdVsQo$|G29bRC-D2@5?879XFRF z=^ln0pe>;0^<2*zT18(~LaACkmj$pW5ASiZ91iX4FD}7dqCgQWL*l`Tp$JSrCU<3I zVG#&@@RxoPZ9kkR65N(>rCXJ>AQtVfy%DH{jc54 z^?J!!{8aj-OPJ@u{o->%pi#2_nH`MctO0&?!&66@Lo@!y;0ET|@yQ8MF^Ku+av+?? zplcU7DfhT_I!>|1vKtbSp)6~!J&&#Zc75&EZEfeZyJ*i)d3AOJuyXJgx%0J|<-hWG za8Ati^>tDSUrw2)8wP-{I>65;=%RFket<-EtJA9b!ZU6~l2jmx^PWwu%J7Xn5{NS? z9elw2;hayq@Ab_sn0xROh$T{dD>^TtG60*!eYm1}c!$!`lOKuOxAx6rl zMk&6R_dlzT(87qp%)i6fen1iH#jNj2%Pb))3OF|)U2cCkSRJA?s-ZH!*GWfa@$A_y z<0Qb2o2YXs3fQvk`9?JmniuwT8zXVjlFFo>4j7}Y9&gR!e?;8A+F)m6s0FCf@7vOEn?14PZl?FCugQ@(H-Yd1P?qf)=u ziYWF`XZ^D8_0mdwbMSNQ7>M?X-z-#nn?#@C)~e|=OwUq9tZX|# z)BhiZ|L(H}m5!TwD@qyt?(G8FaEbchxRM*Gt^JqVFGu1a8lD)xba+0Jdk-?oNn#AS zRZTZ*oLDz67BRPJA48&~oLo3ss42BOur|W2*zTNea@9y0y-N4?LRR4hxk!v+NG zKKyvExBIC}(C8U0WTWjps-|zLKEHQ`(&k73h}fZgkg(LiQQsJJs{0Qeq906yL>~10 zS*H~dj!WBeyQi4wz=$t|8XA(M$9_m0^NI52dhqlJ=uY*cFmaLc@xB)x_gg+@a(LUT zF%o=epfx%J=gC0~zCmey+i}{qh2{=SNlD2R^I~>uIYXf}do*wo*-9jJ(fc?&^OfgT=n5%is8!<{zN!LDYag-Ag?wfLoR&Q1e?8yZvZ15jqfa@ zC1Egoi!`J+nSeTOAI=Y`232C%(SP0EO|8^B$2<28eSh7zz>7{0dYa!(4zGkunH*5r zH5}cRq<|4ZceCJp)M%2qqVeIeGd47b;5+Vb5PNc9!KWB%u7Oa0rHXABE)ixZ%oJ1c z@e(6@D(dXV~B!!Pwt;3Nis(tY3!e`fu@ssn_X` zclf()-%#sYfp|TWsgDf*2*2%+8SVdc)Gpw)coIBcDD^%NhiQ5g2fZEs(kpH$J=L)Q zrPj@O*vT804B$hGem;{Tj0F&He9@e+&CH<-8S0w;Vs2W%h1|PUA2LZk4fdlnqI&|H zyJtg6g3WcQN0ALoB~vV~=+CRIMqnqL4!342caB{n-u}YI55E?1EJ;y_)%#U>ei>G7 zCrBJyzO-}!Q*@a>_Ih0|zXlt>(e^lgpo+&?W+Y&p79|n;K%dmDbZI5rG7XYb+-h)R<(jlMep>pG)BD4-|x66Om zSpt?`E5W1=FO|Y-aZQ)VS4fe{2Su+?jqk($fT(Xs6U1e0f{<~^3gru3(OMqd8jC3Z z_7I%S0&naYOc$w91h1X{3Ezn2zT-wa=ked|{}dkd?{Mu!7q|VQr~nmW)(3nHHPxgT zD9|&DLWfW!c^M64p$bC_?+_#aa!&4exs95)kglp$iYBj7wL7L`ISvXxzvhJ5#Y=vq z$)sy87S$TG-;#2CJSX0aalF?W3rMA}fQ?0Ijxi;_akwn6-1w-)hX{Hy&sWkNKC=K8 zrX!<@i~Kk)iV4J(RprPz{JP9At>kmFzHZoj@-52Ilbl{_H*i3(>0wz7Z(oS<;B2y=vn?{MapSZ1_7*{$r^H_O4z5&FYOsSM`CRov=`;4J_iCBfie2`6o=rz z;-J$r{(;&97a)Imxk81SkqIJ?ijvSoF1K2g^}oRVSyQ`Lm_@(A7K+Zwch|~G`6r{R z(O*sSH`ehZfzoxfNHlFnmv=6{RI=_KUG?G3HGtlzHdWnRCQ{dHygV2Rw*pShZr(Cm2$ZnlnLMkwjs-2 z4M^T`Ak|@?#-PaZmhzY`QvJ#DtxYBCZn3H5WYWN$sI@vXG5U@BkHrCp(LtD+*WK2n zqr%@oa7hg68MT>D;Dp;zGHQ7t<34KXe`aF)j8>apY>%%0+iHSov}=`Sg|e7z;VseI z(8EEC_MPUrj3$S8nv8MuWMy`qQc)l)92Zo3L0W%+>$-4;ow12RAI*s5w{Wu)-z@NL zl6JjQu0v;F>t{0NTbc+LtrQvbF~9}P*yMolhXiH`KiD$iNiUfU`bqsssOV9C%=KLs z;dmN~3;Dgt6im#p^K}Tuu~#YYe5$H#K-XUQl*SyfTa|x#$3&~aA0gdO?WUVeBN+dF zz+<&6_tbW$KW}K^u03^K8C~L+z{86ZI|UVPInuD1EieQsqe{lYe|Wct&x_4FhuplE z%Qsh|aP$h4YH-AdN^(qEzNn|KH9qd7BTLR z!S;Is4~mi#J|$HZD9HG<<^WR#5!TIA=Kg!srvusJwjf^u6uva|yNum7)igcS8K=MO z+yRHjOSh7ZACv$4 zZlV+L-HrVEN@!us^0&J>?gv%-K>piv!T?d*$4IBAO0a*a*Jl!L(}6_A;XeYux*!A` zd_beRzg4V>Def19{g-M98*NnTdnp7lf~MaeP{=W#?D?BAm?T&07`i3FMX%LMTHIlRaW~{W;X3+WPA_(wQ7yC9*SZ4dRzQ|#b zG{4c@V>xQO*GA=&n4YN9?2s?y@cMKDgEpQ^5HTVxZ#-UR10!oDh4kQg>Ud>_p?W)% zess%e>Zsljec17Ocv{dr^d&g2>MMW~bx%|mUMV-~W=E~P`E<3R>q9cQ9n z&Qp?Ae`XMH>KplDD}qZx?O#*H?RMvz;Ulj-1J0RAB7(qjWHzm(U`lTPBR70n z6%7<;PS+`zN1huk9p|g9zJdW0uCcGoErJt0pUt$mywdL30b>1(ZtwEL_^XCkCMKq8yHV=(jSW>z&A*QI6V>mP1OIUr`x=P3e^>3hT2t~r zW#POh(6+BBryKf2)_rkw*XJcrKATLO>Cdzz&_~OzpVxxlghQ$I@WJVy8<@1o-uVaH zs)Hpf2aV8vcLZO=91U@RZ{71CCbChXxe~ejhKNAmiKBS#TwEEF^{rmrMYAX=X$MsR z&1tGra{xK!Lb=BB8JoS7>_~{3jpM}y!16?-YEAm5Qznnvt;jl2ERR;E`x`pwTj&l{ z9H8L)S$~z>MeTP!KYSmR&Wg8@K{7~Ss&cK9w_7*paE|Xqm946wv02&v^nZ^koU}jx zUBerEx7rpGaI+S(H&%o+K5p~t=#6fgr4sj7+t>j&s2NzfocNm0E*cFdtd3CXC}^K% zA?DLm_Gg{=tc6>4gDE#G&1BSrNz#=+>XU`h=XFKh_r_tQJIfYDPb)SkfIL_EEvcsl zbBdRpj&;azpk14|7g-VSh8!neT8{GURM%3Q8<*eVBPMzP4%_$1W-BODY>-KvWoRd* z%Bqg`B_^WH5cwg7@DOp7RR+tYI92A)`h1IupmKUJFF74l`%vhsx88kB z$MdU6MK9h-%~-4mzDGgAdlo{X+}CQ~u`>oc{v9peK3t66-tH8L1zxgpXT;^^e(^Y3 zTU#@I(Ti*3OJ-s6w2V#u)H2Z0V(etj`b+5axFDNJqR0cg=e<1RY2T`W=O0(& zvo4N%^sF`J^s@eaZF0|VFN3*%DTMq^&5E!JskcEj4KZ%fDl?DuY22ie^Pt>-2$UJC zuZvDwBTyHnfuTrTWN;F(X;}DX!)>@{^;j$B^tA$0tL%OUwrE}63%Qa&CGq#^@m{L9 zm1HT|09m-fbu!u*SGq5WP96;{sg#_B0eC_gbQ0Scv=VtMbs;1!JYJqYnv(%Fs?V{&|8=00`u~}ld#!)Z-;eiTj%G9&<{ap0*vpK-?RO@ z?<>HzPoHrd_{u$7(`IX?Vv7DtNtMh)>)}U+xy_?f(V!RJOj+6>HFgI^LkPf$MP;Ut z(Vvm70eU?UJoq^eFhsd~4~u~U9Is<4Dj^CKsbYEW7BX75GK3)xX*KHwt(ec3^_%&z z3@mFRq7Q^DSzZBgK3i7Y=V`hFU%t`dVA0dlXP&{a{w9#AD=6%pA6@9Vu7z=|t!Y`^ z^I>aFmcK%yj8VF>RB_v#(&op{nI`7LWexa1+auZGc$~BWJ)`NENK80AFYUq7Sdo(k z_8oC0x_0KsSM21I`Rq=OVa0GtZ{nAybr-c&6ThFBjkLAXZakddliiFfaP>mJzHVF} z(s$%$S_%!ZS+ScOvUYvp5Eie?iyUkT&NYptJP3QV?vWR}9QnTY#&cHA8%CIQ!my(PHQQ(xD~04>XcONq9PZqQ&(`@>g-nGyQ&F2?FEA z;=#RXlLDVpO50#QaCdZ|e};>1F7&Xtq-7(ii6pP?Rf?h7-*g?Qt$J3XSbu%pqC8$KvlY0tmQPYleQWV4wU;J-^&=zyLgDBeH!IR_j2?uSka)~K^ zJ}&B9l>aJ(%|CSxtv~w7i6@K~6t}&Ni3ezg9l4oz3IvMZ7HLWxA9^5p(Ba!H6C(C* z`U2Zkl`xidariw~#Vb^9P;-1|&18J;WxW`A)AsTzHdCXiJdDb3SNFD!tDM?eGV;Z> zZ`MM4K?icPw`0~SdoDD-md~^FJo!zdtv(#?1v7Usx-wvPF<@r(h!vcX@ku5rTD}_z zitG%N10-nB!Co9yN0|_esv0JD%s07b!MUDPuZLRbF&_7JD_0FR*r7PJ#XpTz*MXDQ zO{bZpf+BzNC*o(~=vSqwpVN}(xu@H1r|^uVl+J!-F-+E4>B41X62TZgBt#8q30bK} z6NDqG;LUrT+ZWm5I-&u&$^o^mPkQ9QsofGj9t;Bb{qT~tr@hO^)Y0RPE=j$)@XFhi zI_GGqq72d$=C=_`P1?ICf}@!``rvE*Zra=&Y!--J5Zm+Ckm`&_{q3&zz)XM6?{R;z zw#m>&!ELiv{V#*Ba=6F(?eJWfh|G8A$k)}nd`49?WYup2H1VeOR0<77C%BLwplN1R z`#35l`zzV#lDie+rY8D)AZ?gntJ!yr0Tftr5;gAqO8BNwQV5!70@~uAnz)09UZ@a+ zp^%DyzHXr9*O(Vt8SeNaoE*&NRK3l(?&!}TrJKmh&6XGXhx_d71#(b{z_#vRX`u-6 z*TFtZ4bZUaJy8hs$3PjNvH$A(aU#< z93YNyX@fTSq*tDpGFVcpdQrZP<3lHcT2GCt;&#GlUhHs$0D&r@g@$CWcE&s?#T8shykad#%O zK1`>={#`*ZzgfA4OLqlSoY!It({7-z4X&8_*$ z?(4B!BYO&?&W;pN`)D}k+Yf#nLH!l5Pd*>!m_t<&7;X%f9%I@fluyfK70%c$#p85O zC0xpg&+Upvj1n;dGFLp`sUX4HjL%MEu&sJ>FJ9W*Vpvf(vLfQ#@K(VcW#faYlw8~=Yw=uBxx!z;PfB~)npj)C-4(N^O11f-oHGv!bZ%fkb2~JC@?zES z26RKNQ|}1w^Q=89A3G?Yz|X_((+Gd_4C{IxT?vTk)iHB<@UzP?=qWX*UJ5!97XF*L z4?fS@hRE5|T{!#wwYcU zyO)d`8W}k}99p{04;l#CHaUJU&{xXWBp9+@vLf6RGE^ySt}QEcvZFLVie5euU5rI!S-#{bo76< z0OB9nyth(zJv4l-W5W5X7OhmsO?ff?`YK)@4Gm~|w2088yFr(cffyXL?Fu?3-#49I zVi{Ng>{ZWfU@N2tUDcbOqX~nSmCd5hL5S?Iu7_zsH9$03zWe_H=0F+0J7Giy3`~Vs zC;YGjR^~!YOB#N(6f(T3Yz_)s(QJrKA%?aZ#7AtDiS@F^sFdiP)QyW3wAK1bT7_;ADa$f=s>f7lSce5hW4Sc^VEPz+%a@2 z6%HpxKoh9X03^+x;~TL8tmVbZJ0&qF zgcr5emq+@W4hI5=18xU(9m-w=eg`_>L1ua%qDP&JnE07uvuiWS)w+7t9~*Q%3;JIV zF}(l{Q5hnZA8u+k1{+Xa3aiW;8VG6O4bSMqUFTfMbjJvIdlBr+1tSK!CBV3@cuyNb z<*06ycTJ|kui0A=V8J>YA1;AK+a-Lbw5(*X5h8@Jhd~)YG-TKz`#d;j0=OFLBK-Y! zxM3drWF-PMbfL}vXur5aTy`-jDr?L+tv${Dh9Fj#U)k{~8(zb}YuKnU?Cgn(V|`p_ z3SH3>oj0$K&zyxbs!HiwUOyL{B+8k zu23YWp&g@uH7~=`ncyo2nW^sqwgS(9>KhCjbQ%aJEdXai9R2sx|4>c2y zGz%*nP9WzCSbQ~<)lzF=%G48i;oisTAKMMySqv{Ogxz}q8zjd=w@z^7QE={X=oCwN z0CEdp-Sz;hauh3b6yL0c-P_GmipJCw44dK&&8frT%^9lRuwW4L)FMzOU_?n>QXJKXfeV#UeYsZ|%hQ!X1 zh3?$I`nO^6Rp7BCzj+O~AL(gvPw=y@6e)4fJPjQ2jUU$l%C-psYzyW96(Lo#v$bxz zTgxF$005@`e+uHoibu?*X~<@oOo`U7joIJRh%zkJkbFz?BZ;y2Aergz1B!uhbWm1YpMw z&D!0tc$;F?4#^wXzQ;UgC(s#AN{1&dfMNRwFGC1ucx^G<^9AJO(KczjhYISFLS0y1 zvbKhwoakh3A2yZ0_a7a(5V8x%`7bQG9LjZae&AITY;x5@AwbnA{Vl z^n#Q7g4va)8rYl*Kd#mMv;{U6!0uuZ`g!DZ#dW7pKf*1A`@VwvzXVSWZJW9G^MXtj zUckXq{Ut z+u5?VcO^-?rlux}7N4~F?K$&c*-cQkA-Kuyc5~^ae;_;iVWOk!3B6T$VeT5S2)^A4 z+qTL3X_;`&a5%2F^djq#07|g|Of}OS&0=D7TwS{%|ReKKuagn7D=yQQ?sU zo41z3_uF92cEATQ$uKw-#$>{%E-)+&QllZ!1uhp=I8XtN%c)SVdL9m60E(;N#h>8O zZ=t9dlAO3CYqZw$lv1gPFW_#V z6UqkTUW6VOghJ1a!2xWa2Mc}+z6uZr`3JzQTI;Qi?RwA%piY~wTai}Cjf@y1W2)QP z;vfllC1#(L5n9&=OLflmo>utt?~FyZ?EX;%K1)`CWCY%#wSEmiDK#09 z`09$XLBH!@;LYHUpk?N>^jkM#I+@8`jH zCPMN=NE`|FPSOJ);}qzeDlF^+eBmDJ0|*1D-rDd4tiInW_COhSGKWrM{Fx7M{G>r# zGzsGBdg?6)WZhH#dJDX=2>!VOYRU=Oj;4H%Jyvq|eF2HxCr3*QaKDy9gR&afRSGp_ zfDH`O^B?FSfR;gKmB05K&sAg2pu+9M@MyH6`2^qu_AHXMdeKslmEB{&{brMo-oQLy z5XzAC|F8!j<(Q_e06S2;36{(ZmkcXF!h!P|(+fPPO8}+RNx*Z6*&jmEIEWhro%%t> zsgO2WSk8wGe6PRhwNZP+iYgVal`Jna8%qH?x;ON@hop%YaKlLfrVP>y>28n|Tl=92 z+9?4Y>N{3eBU*lMFNdYO06)_FSj)eIT3!(B<`MjVgJ1%IGCF4dRzZq2KFt`yjU#`> z+LTn3De0~2OZaIDP9T4^SO5h-fZVtbfZMdz>rLO++epY;mb9b)4BgHFdsO2~I0Nuh z!sh3tkAQR+E;Nt*{2@mGW{v+~;0<7St(m{e|G}uIAR$a7;Xs-7&4rnf$CB%%iM_gzz(2f2Q0cGyfSbDQoQqtQYsm^54aRdTYSXB()G$7 z9a&P)p0A{xSovm<1i7cR*0&vs1mFbz2AqSH-lhW*uRr=9kkmIU1CB%Yp|`>?OX?PT zC`dmU#ytyhJ)p*q+gVO(l8ydpQCv91U_w7T$#IYvYaOT_?5lu>{tJIu03HwRpNrpt zfKnK`2Cw1fhLQirGXu;5sn$A`?Lg%|SaBO{e$je&FL0yQ`b|^!Vmgu(Bo^_YTSZ0K z5!1vN&>|F4xE_8u4+<9pss{92;C5isAwvM>?B5;u66hV=5S0PrUxqGbfcsD#^x;s* znB4G^SONa3(6*>F=zS>+y-zy4`0~NfP?|tyPdoiHHA6G}Ty%_LcrQDK(SRVQCrsWu zzl=j8JMYLuP$vN|?{pi>b)!#bZ>Lykq|(}JZUBCtlm)N8*J|>S2V`ljUjoC)q-= z=KyHKRIPbwA?m@eU~8d#siYnr;2(UXLjxYZOBuiw;*qSt(*b#YhCd{K4SVA!VgoAkVdWj++5_7WA)S955(Hr8_8*NnKT?7l z2HXsTWr!+N797Clf5MOFOL)>kbPs7;o_sCeK*+twG#KepV6%#-*g8wBu(9cmchf8*mP!&n#PA+2T74n zOPzl8!2uL)57lRzMq} ziVwPu<<6levnoOQ&$RhvwF70lr7CdOT#)M4yS3I&0hR#nxxj276Hp?7_ZYa7n-)O< zDELWgRQ7!jLI4kHt+N~4tsMx!hWymJ2M!=5y>|c?f)Zul@vlPXUnWLujY~KlZ?6&qvCAS0Rd!&qf=liff0oaimmWr*7P~qfYPnf zGe8`H2ZjLZr1g$Py!&sq94(*X-Y%8L^-nK61A-*xT@m}~#GzNU7d zJkKP6_n-~|04Sw;0NF^0+#ur=>4F`d9vK0Y?}DW_T2+VbO90laZ<_aR;19sXpi+VA zdY*V2LIRTWpF9DkdEm9ql3>=3Jq8BOlEfgV)jyzh50<4pcS7D~ z<}CVv0cH73@6>??pz#`Z9v?87IejOR6Vr~*q8+H%Cj^l54%ANoF-Q}fn*bXbzZ5Gu zQUa*Rg=IIx_BY@F6Tmr$>C!iXJ;g!R>klw&cGDit!vPLp-)a#FDOyS>ZBK~;(byLu z?F8`;w9(9Bpt)+(4?Mo~BV2(RIueCi0!@u!=gwhMd8PL_3S8~@EZTvJ0+Rs#3H1|z z3$c)0O}0>iNK$L@wM_*-I);E56U-~RZ7;*Hs~~1nj>bOUc0l; z;R+j)>9zJD*zl*?VkL$H#ylfUO_k*yX#*?3Q1mS=;Mv9RbNt>d*mQWEKnEg&X#2n1 zdmN7sn9Rmb^$Se32_jnpxR7+2RbsMzr~v^N2St@|GaC7}x| zke_i1^t}??-Nhs>_yLL*LxI`AG+~5z$D_M)#5}YHP_P&lUk3#X!rphm$j3zO6-0rz z(H?NkfP(Y4E#=Qk=91_sqeJ2s7=T zCuNQk-w2C~AMVhS@>`w@i>w=LUJt_|5@o~g6UQgeW~66>uH~h?KmV_cEy|&N8+RP3 zkV?z{#iwxnxN}(AwKsUd@V2n@w;==zg08Z>^d-PO;qAOk0=O7>8nBZ(1tz@NJOWrc z6LQ{#1Ksq_NM&)kU_noNvq)+9Dnf*a-Kvf~Ex*InB zrS>*w5)8Qm`dn%DG;AXs0|gGhpV_Ov;F2v1Nv*N!1v=1tlrcZP8@KlE$9H{ChL}zg z%L+zWT6tZaK;dTT?3?#rsFMf;!7=rF;2FS9=VM{wTg@PV-O?>+$3Nr;!F^imM;qFu zwFKY--T^f&XfXA;9L7B_=8m_{TGvW;2_W5kl#L_mVYS>Jg+EHwm!x}!Hgx}ugmmoD zk`AmCZK7+SU`*jIo?bkci3MBfC>Ux!g4xcDwpjw1#QOI9 zH~CG@GCByLWeAZ#75S9%a#A9%^*@RA-A)9j!}JZbFh9WBW#dBN0PO4q(UKsd+Vzr&JYfF7wH1q z>q4;t0zRbUXB({Bqu|jWKW4^;?{EYEJUf2Ms&5AEp+B2^}zV_5ZkI z%~vE_nt&Z>5w!U-MEMmc<+J!6yxC_GAEjopFR70teg&J%(Dd_e^#)jSU3fFk&A|2H z-F(e@nVS)XU|X|%00vOJ9+qAQdp-wQ0?gK0Khe-Gts#KHz~6z3fLH1fRK1QgsQS1+c?K8y9ounlI^7zK@P&K-(Zxa$NzG z@mhQubNWqUOMEv9Tv3w7?nn~8)wOmM1kC@5Qm}g={CE+RuEl&Fz6Ea9TK@#W+?u|K ziq8dr(X2cG11Mf2;xv1|1R;Ps!kvtD(Ha664g3q~&nH=KqyGXagCcL4bR@$73YNlW z!jHiUL04x{K1!b|`2pH=2DWy_w!`#otGIjB7Yr-OHQ($YfVP9>;jLsV`Fv!n=B?xy zHl++9FJ-v2m$${pJyqti&Tz_7&(dn$_=be&YW9IllYd2`0C^Cc{eO+r@DBzQnbRbT zG?Hbt0Ti!>#n(e#P;VehYyCn)yEK~stOOvX)QP|=KzHmh($=`&3~-3I3}8ss6>4BD2ePCE^&&dodAi$pmZ}VzuBq+1aSb~89q48I)IxH3qaCiyPpfA zp8|L1$Oxct1uUHbd0&HQ4rW=s2kQMbEwKR1!k_zqD}Y#{(qZfi(0$s^d;MP*+7BhG z;rp{|wGRW}=y##pbU=fzQj&#QlM?NKv$w3|uGL>Mw8H{uH-x-13ajdU9ozVvjr^A_ ze8)bDFq;S2T#$OAG_omO2U}hSpOvSfHvpGvt>;_ER!SuSw_p_l*`i^<4bmW_se(D{ zR`2^>qE8ZL+>H>x-y7Vmg#>U0lB(Dp5T-l&ACNIlni6$DU?5F8zL{!i!G|#FPm(iX zbDKTmZD5OHzs^CC!t?>$vF0oKmgm#q189GQ$iNQR$sv#T*~kKRv66f?v9F%t5UiE? z2Dn6Py&16D??Ksz5gI>jAGI|*8lrKOHcSE*GOid_7fBV9Qg$F zy9Vse*5^$);kIS}C2Q^1#Q7@eAb^7jDJAmY_y0t0@sljv^)XvYy!d?qbhL)W4vZ%} z+P^iphY-L3;5;k>=zSRsyH}L6Y7Nt{FtJqu>MIvt)9U-6a=Z1x4TvoG`)x)5rgG^M zNc=yF_~9_-X-GRZvUupv4GQp8ixTYiHxaFGbF}xqL}Z_%J4=7R;0`T76#VJ?54mR3 z0_@sMFzUde06(xv!y-R{&v#JvX>a}P-r!c1rO?^BHUX$vxH{X(I8`Qy#8LaFj2l38 zk*xI#7KjE#$x5i$W*yiM`Uk|J_-$Jfz$J*TUrzwKPM6}JxNaS@e?tg(VAC_O>=w{< zWW$Hv1wGDh+zP0V6Eq?{R=r35`U-h{%{tC2D5YcY9})=9=91R-uoL(i_!877=!b$t z5U-+arPPE7AiAra#8KiWN;_Ge4{aW$A{X+$g?;m-uH_^U%-D;+YAE9I!X?{De`j6w3K##DUJ8%dfd{AhF3`&8|fmeYwkeG+QrCKR95OD%# zp$vnh@zCcoNIOZEjD`cMcEP&GqziDsD@y=f@65XpX*%+GoAw4&O1%e6!)jnSC`(?h z_@8h7f27#_oX9o#%4@&x>I^;3g}zrqY<~z;i`7uI8}h$_{Qp699(eMlrIgl^K;*B1 zKov&7#|d=fL5^b_ndU(|2;dNh2WjBB9@O6osrvdT{^s05@fiX1!DTM0W+79Iq)&R?ZrAS+_1?KnACIn!nD!z+F04bBiOWFAt`9Q}r*9bK1{5LH4 z9R#ZO-|R|(xB(&@8ZcQsPzhCgp(anPhCuxx7B_aTVYFukV^vo=*+F-hVTTtAfc!iv zR=>i^ecAjlzJ!`l*Z^m;C`ftAg!K1^A6Np+N0t{$5S;;!*4nF-GOS2LeP}KLq#)UU zzXTi|vwt&DyaJZp0Quiq<-k>-+|6W8FLKkpO4?SI0h{J?UjIpS%~T!k|HBpnWnflGbq{4@|UzQVq``DaFM=h1R-W)1V;s*O3Ph^%@1bheVabPRd=v*Ix?Jsl6 zFE3>1eRgb_9cI{pKz-M^)kasiSccXF0tHl6~)9&Z(d02Bg$M%Hif z?0;n}VYPB#2e1Vw(^|`VK)uroR{aG$Q$Zgo0*eP9Rz{|_{C0lxrP;sqPnp~58ecZV zRc76mK-jC%FQ+!4SpW7@gMXC^$5#-&0OB`-zu3p_N}$w^yAnOSStF~t1n@9$Hc|m- zF#69Drf3cUV9Kqwl`8JxhMEFccnR$J4E#Y~!1-G1H=2Dcm31ue2yj#zo?!4uv`nlOGstdo`{a2?nnUN zC5U|Vp!PY@4nROUR!{8*7mb15Nf6@%rwx38;KW?%(R}!ADervhqpaFM`whSVe;MR{ z4!b@;SLG0cYuoYkczXtuegdB%VH}E(^?NxI9xOv=*X!+THrjyzmLqk_qETlFvM(@`7?w<5;uOh5DI^?-ra#@Y&BT4A^T>zfU|&8&A-v+mW~9BHiwzJ zbKWSp=S1k6(%>h5*aN?NqJpjaVrpl@8o=%!M5HtC2Y?m8O0h3DtaJqaIl>yI-~J!${wRFQpAdn_`t_7l)^Wfoz!^aQ zc09ul0N{bxIQZ+?aMnPGv)aj#2x!Ut-25E)DnJO}ZmspZ2k?h9ik1_=abG~jiIG(R8bHM^Se_*% ziz6O}0W*HS1OQ?MEV@yOg!~>1)ca@A|JHZ7A{F-j4(YWp63gQ1zyXF=qFXOchu@Fc zZ|nRu0nU5IM^2@Is<4YsKO6?&tATG$vAO^OtVIamo%TKV$kB2FIQd8DJU+4tKm#b- z7Sbypc0UZc{pU>pF~GumNnG<3J^Y#z>AQ9oM=eVwei-2rSO{$xP3LV3eFy;-mU)ur&wFT)$f{($rX{LcaU5ly1y1Tb|mq>PT7 z07^H*Pk)d!*#0v_66;7reM2-X`60?97XMM`RXBebZSJJ(*n5ck{0cH7gtt=+dQ$^3pBKnmT z;P!iQ2K+?(JjD7u!EY1%oYzB#GAiNdLGapi%KN4A_&Wjac|8CLc2V|kh^`G_=SQLk zS-uIRi0gc<^*;~PnMZ>5CjjdL<;|Dmm&$D-Y|!UQu>y=kGkrZO;H6i&gy`IUAF0)4 z#Q3~8wU1uqMMV3(#CxlV3i#L(lf+(E97*0v;yn_^Ppc_sL{Tn-O7n>G)!}DBCETG|3$Ch@=v?MT_hKW*;N15UNt<3q@BI?(uar+|jYi|bzT#NA-Tn$MeFy(`gUgph zkdR;+QZabwT(H9#b{n{C@_bhN!rU0C4VG(PPy09D-xUnpSH3$W@=dEN!f zfvdIF-?!l}Xf;|923!c8$3{i~fdK5D3kxrmgdmXCNj=VmG0#Z zcwin<5xBL%uTfdaKnBnUX&*JYjekf78byFohntsrkLUK`zo6P-2fr*EwYAobQn3CF z=>u40wP+(3i2!_Z@Xoz{G?xIX5lJFAX#u28h>!q$(kgr5<=`z1ZF45TfImW?D?}6T za9Y-y6`xp*eXD`|`J&WY@T0K5HR$VCZ7oCOoc@WFgH|H)&ECdTTmev7cBCrvF=TmA zH%+($O~Fv6$#02#)c3M(vW<|`dVsvBPCVP^C>|Pg0yRz>$`49w0(J%amP!+teG9_B z?MuY`uhSs%;G?+&P=zI3b>e(TJti`7lRyA={!4@~!U+H+Byj8}kYZ|D9Ez5J3VRvI zZ>}kas-42}{%R;$1v}n?vW+6&UB7Cp7kCSKAT|M8>S_QqIrwXRI4cY3(Gb(ppZ2pv zaPWH}E*7F4Qs*D*60wiOXtdLYDz3n@p$OZ~B5YIDp>~jAoq{#seU+l$;46pnjj-o)$?>i>$FH|cB%A9uV?IUHT6X~7Xsy?^?%brC z3@YnNL=h;iRmc0eK_P4N$-UvMVG!$tco(F^Kw2C+)`?2B@!iK2y!zr^dTigt_<}w3 zsVpF+rh)`-6;Mfy(TNYb4q^6?lUUm+5jH*xs~->*=TI|{9})9^^+Ei_4WhXOP=SnwP06l+AQ+;VV%BLT*}0A0_NynsW&3UFYSF$23l5l3Eio|yCA zVww4UW%cYB?Ebm|SP!fM-ZuZ=t9AYBffhk!-2mK+bx%4F84LxZGa>tzFtLY(1MN!w zhT(ya;jXs>xT|aETv1Jmw}Qc?d7QX+6B7z{(7$XiP{9Y86Sz0UVCm-BR??3(36ucW z1Ao_A>tQ8i%R|6u-UR{gsrg_Ir~F#WHGVGn{U=HRv% zv)1*SUxUIWGT2MkK;dF&*h%3>blvh1ZMJQ|-;pK_MOy31)|^s<501*pMjo(^ioxc< zX299Q;E_|ITYSAAnY&Q)$Jb$Fz6xo8niE3z%3@Nh%ji*A#t8+vjL%z3Rrx0V>G_5^ zEDN6#vklb!ejTW2N}vB=p}7RG4@d^!m=7TH^sou95!irzi{b03mYG}*yo0nsl9+aM zR~Y*|WSna5bdZ(;HF*-l-SkYzyx$*w#EPlGH0yz{kb&<7O10KGZFySzk$}qT4?Kf7 z0Xy;nngc_@iGAVTlVMy}uo=OXr}e~L_;i53f2JT-p>)_IZaIC;SIn+e9Q)l&MpYCN zz`+`d*g*-KDgLSq&(&J%_4S;=A%m7nf=zr=1VJLv1GE7JE8yE-TA`70;AKSVW(d;F z5W4>s#=ZdVIPeBIpvuA4o!>PN=1*(vlPiGNfUU?c&jU7Tt@9)PDy2pP{g9Aqfc?{d zEgkA&V71nIm$_ev1XR{Tz*SflSqCDALKJ=x@2S%V?$APW31AlzhgOVv7J8lsj=0DO zplB_8e~x&|WtQBC%$iqX&0vmBFz!VmmPjm%wnoKPSala{d%f|D#TBK1t$X0?=QN9#E1_o<_IXrjiCDM_miktnn!RV z(p?jQuEEm}QafN>7gh%fAN9Lx?Fa!iwEvL{ z1tiD7qo=~TBhW583aIwN%vUro`~cdcP=4!4sIxvOTq>DgDν@=*8*46IQW2te@80z5*AJ)D ziQn(1sHlkb>(^6QXoeIO3Qh4yQmmH<%L30 zi+dmhk&Gl3rSNmG9+ZNv@$m33Me)}q2I#vBG?)GrN^%uOlxTd)=aFZ`Qzh!Y>mP@G zW=el(@RtLRXsz!%;J>F8Xf6SK1B?ZtaVCkXPxs$|&Dr=sRe;|Ex&M(YsmdIX2k~vB z+2|vPSuBX@EtP^@#*3S+J)w~J{?7Mc#cfcr-P%$C%+XrU0#HiDA_HVHFbbFo3{gr^ z-!-(`?PO$R5EmDR+wI2XauF33h0Eo_X0uUMRYgWd29qaGX5`3`IGs*B9uL*k)y$hW zkC$G2iK1dDM@}HB@-B|vcmtDTpt|pVUnYKt4TwW>HPRk>8L78_ces(62M;(rP|JbV&r4c_gZj7&>GB=1)F~cl&-Sgtp+Ye*2s^6 zN(wT`s3&2-O!;?vUP>EKz8jWYC)tX@ED^p$X80W77l>&;0x85vgW&DgE{CDg($k5L zkH=67!|tG8zX42{GKF5fdeOOaXOfeXNlHp0KE7$6T}^cjGj6(p=jS|2EH-@wqtz@X zkV-rb4 z1W>tGRDnV&sQ|Ac$t*8}Iw3gPZP761Y3Tb0(@)+`VL=;EwgVPkA$<$v_E!M+1D^n6 zfIm`S3od1V)(jpoforb0hH+!Z;B-Y1dN~5%r(ha|v%DsE8H1-I!aWkPjpqC&?K69X%Y21ehno9uVfro)(06YDz zg~4}1bZTT7fu0gr^AN0igs^G-TV%%Rf{;P);M|&dhC~ITQ`#>%$OaUxgP$(8#2bA` z+V0NC_EgBgd?S{}lZPnT14 z<9lSRFGk^F75jLbHI+qF|NBL*%7SgB=Fysu#QJRyMU{UW^Oh}HFi*7`#@JO@8Cp8(vz8^Heo4q|)3 z@CTvmbW!z{|+mT3~a-(AhBXX0agrZJZkrtW^A$G;pZg z0kUp>1Neh{LOE(FbH@*rNJZi(NI4djFcgQ&hSE?|R)y2);o4uhx#@H#T@uVwHXX=T z!`V98AweI4QVMpJzzaXY`)k4HV}9`_Uda8Lh3sW3B|Z=S&$QORYP*Y2-*pMr8jJ^C zK$bp77&PTa_v7@Qr}34Na&%Q)g3kLI_I@ePMAa_&sHIyeS={-+`$%p7PB?rAKQxm7 z0F+X10cQoh_Ps8I5s!)$5J_pUw*pq*Ygz$8d=H$1#Liy;jtvgDPJ?0G^U&oO2^_X1 zY(T|cSaUCI+HZYV8&PQz@*H@xv^BHVht}^hd{9+NoV|uABjY)Kcr*huATbtVqad{t zbdERmC&HjDMkudAE!hpo%<~fZEo8 z%DND-`XcB7cX**NHjcacpTMhqMpEf1ko=Fe4@+C`AQVVXdjsk0bBmP+qEv{=e@+B2 z2lzc!{&}~v#bFnl85sfis$k0tuxyrDG6_gu^gFcH&nczuM9L>ai|6jYg^`a!T-SzW zm|Fq|kiSe4l*%?X{JE$!=ySP*CZjV=qb2;hX|W`9KrA6MENUo-kA}W!@aqZk>S4+r zC?!2rsrJdC=5K}97J=IVGbh8aGyrz)+{x_O_wdq7FNdFz2l1P{fYchaOC;5Y%AID$-QU8+GBzNo!5Uc#{4)6YgyAsmCCC^DHEpyMwN@WH{sD`x zwrUjiN3TCfG{T)JnTpEN`U*P-%W_a=(o$INlG4&nJ}DMbIzeUv4C@BxOoq`t!B~|G zFD(R@11=g3gVSoQ6TDt8Pd@o1x7>0IVIju=Qe}8K67uu5+YBTPN@pUD(ByXcj)Mlp z(kLHiI=^OnpbT-6o~dXG#k6OrXA`ytQlB zbI09x@vnD8Qr>2>1^j;h8sIwMf9+xc)cOrsV}O^C{D30}iXD`4llm`n^#(klhgSpt zLAv-XP)a#0r6xFh&Heh&QUbUMxC2x|FnxFYE08kO9Gp!IOdWut#WM32Ee*E7VSu0=bY$88#Q-dFe0iGIIal2%7)rW-PF#cso9NW;r!u}n> zIhaI{QeDBsq8()VN-2$sVzsR^>7!COWwMig=@1_agS&|DARH@KujP+7T+8QQe2p1q z_5kkz4*_f1bw*NIvA{ooQ=9r|3sB#U^aBgczuk~@>!{GjGyyY7vh}v0;#+p;-!WJ1 zq8y}5{b}G8t@ZZC_dWd4QUbUdcmk9Qv?jWTV$!>iF*b4&-Cqs6KZGAIGE=XD5rI>+ z)}JV)rUI{l$}nwggSh@MJR5qQDZYSak$^e)ZH5@o8=ip`S)%Y$``1BS9~k*0r2j(J z^lbo}*0}v%hLr4KXz5;Z+zBj9$)w2c#vZHaln6!@q&i{h2sm{l49FB8g3AR}RenDD z@O^H(`C4*z?X|8~HIl6Kj@EiV`?bxWvTVTPh{e&Cbpv&487h(R+Bb+}SOGne)aL;} zw=z6O#St9?0W^VD5kLTqfsJasoWlpJKV;KKz;_1fN52jnQtG|!g_aUPCNlF_i6YSZ zau|9K#AQU_s542TG6y~%2i3K%^*rEOt@U=L)NJ53F`&(*K<8s%^j{do^}gIsVoC2+PaCh z^8?f!{2P(HgEtTxuL8MSPV?o4B;z4IetH63rXNdJY}jc!0j2R1P=FuJO7i9{VE5fu z>Hh@RgNJi?FH*F`38+kYqcf0)PuiwBlVQ@k(BnAK#EAs{3fMSD@~bPh5jLV9)>>yN zrJ|6K*EDm+lY5qNsyGGXGr$v}#i0Yz%(1ix7F_{3VJ+GXJETs+=rW-&b}1MhFGi0s*4^UTSQ1YHU_qnc9CXg3<&tew}XUk|;}pK-qS-FZc_^t6mBZ zRc%6M<}cdK7a$hE4TvK!t&NUqY36|>gWZqL{=BK35>(U~$++*ooRLFrVjOm&Xl;C0 zM7@n_aQ+@W`yT+us&49AopVGFerO#5i~&AG2tY>RQGbR07dGR5XjuyUXM`1ovqtky z;3}>4Jf+kmglLA>B7t7NhtW?%bZpZkpabAEWKb=;4c0zn{Tf8m9dKkY^t6XK<+m5H zZy%Ia!qz-kvIUusto9sMJ)KokN8?{z3)bhFy5!-6V11Vpx+Flw-gT6G^LvpV4=tPC zMcPiakNAWTz>Nq2q_)8k!w3KCz$57FmF9P;#_AbNN8Fz>h`r)ACc4gNFbTM6qk@q~ zfP33CwfYc8iWrhSB8<^%c2e9HnSofId1qMri^<3a0 zt#z?d>O5o(nihQMl>xmkgAv&flh6R^nBIDaA-k=e150m^Sg*2$J0Ce}{5WPm^e09i zbu^{rQ0;-70$8*KHWa|td|15$igJ-kdUxn6LiIV+3IVnW!brm~0ADqMB{#z6XTv{L z3}hjB>8;Wqtp6OcY>4@P6RjhFx@P~Yz*VMIrz=V=hwh%nZ(At!8E4O8_BuU zt@Tgj05j0phg1X4_C$*b06;0#7x)OJ`Uf3ygKvW|5129(5fFeii7mVgw!CC+P|6&g zcWbR5Fu!XC@F*b_9Rp=R&x>IA{SezFG#H__#~)zBE3jggv?eo)c&yn!C8aZuJ@zOU zU3AfYXX)`lc{St~!hcr4f{n1f6m}HB-aN!@ry_dTkqgQ*DS3;E(33a|@4RT@_2e;8kR{-DZAA{e!=r_Z`ZW;=^+?5c_Y=>Twza zt^?gEbkkeqsC*H)9-Up?6d@f6Xe|M_fX`6s*kE>C`cz2_itA|(#-`Tj8k`FDER?l* z(bDjpb^w=Ytv@rr?-Af;bH|Vw+2vO-WHxjf0IpcmYz~-m&%oLjVEHUlh(wvk4yN;| zVZ(;;=9_Oabm-9fll4thd!eWtN-D%M_+%NZ$brHdDD^;9wMYwC&b(TWVH*^P1`U2+ zIjnyIR^1)Gxe9UAz1rqW0kHu7h*$tEA%MCA^KZai=zaQm3asL3ArM2E@79&|w*r1d~n=bm~qagDv=zccD_6jNN?RXCsT?IAy5V9`Hk&3Q# z!!S7Qw9|O<$tUUAv*&)N)d(N3qX2dlLTLr$7QwP@@Z%;}n+qk?gsgzJuBB5z@hZuq z*tbBe0>Ku9Fu#H}^kNGE+=BQ4S`q~a|Mj0CX7)Ps*bNVU*}mT_9Q<8jE*wF1fb0Q14^mO5djS`58Yj0#3RuAy#3e0jll`*T_Uyl#VY{J`d0|k_Chb31_`KmvdfmMNAmzPb4S?fta z2;eq^bQ7Anw{>1szzyi^=gnib-TFFwmR4C;BUWN-+ogR3+=$LTXkgvrur}M8*(?(919t%rSr4>Q>O$a5;6&=)aAh~G zeP39{AoGxJJ!gO#3Ytk1$MNoa{~|r3D*>%B8nX9Wi9kSF9pn_ir_12OpCG3ODmCQg zLsj^yU7MbYrgD(9U(}VB&VZ6N5Sqch1TNQFZ)ua0mnFcR2(h%Z55eQWE$D22yExZE z)r$_qPjovX(%M{D4{IgxTXgn^N3t`pO$Y!$DfK(xX)Im8s5B7`?fu(O2j)f*7P9jp zngQz{x4H>iBv6Lr2fPvXy~Ba&2oVgVeh;t>36)JlZtmNdn)=60oz9zof02yTMEn7b zt!}}7JHYFQ{8HFc40%QH@e=rGBkZjaCt`IuAxC0k*G?h7_NQxM+iTzphOhS_*3zqO za>8N(+=UQ8q%&(c>%Rxw6m7sqdSuyRoo}RGnDEa%0^EbnZof3^gN(K$fMLK> zppFkdvB?u*%rlTSDzfAt%em_^BoMzM3pP9*?&JcIyuL;vg&oi;Rbdi zv)Ew7r;tHZA2N@>o13pXn=4O=vecmuILfNw^JTDpH*Cv;Z#TiZVw3VJF%@df57+%= zHlT0~EWIA~eii6h7UVxX#eE~WEQ3e$6hNUyZ(sqLmzps%Q zZeiGW+kj3Dof;4@lZsA9{0Uu1KOV;Xow&42uALg-;z@pbXBgOQwgwK<_NW_WD64{x zm%$e+U~?|4&x75?5KMdD&mz#^RUG***GHwoYwAOm-%B%0}yI&th^8*ZmO*A6?_4^Sg5ttG*=#wQ`gPnW`+FJN1xWHWnfM2QR# z5?K2btiDgO9R=HfORT(r7NhwVfDftX{|!33O}iww2r4TX2@hV}!Xvy2T!zj*B2<9F z(Uv{{%L<4?W}b6{CcDiI{jP@5k42ar7zP9Id8IMK>U+W;ZVQ#j&3zNt+>|v?%K`wY z`9JD4RBXDeT5MWImCr}4Q;`;LkP_>Ab%YTB01bCZL_t(1IW|C#G$&^r<=~is5ann* zNvN%%wG=3A+Xvh7;fIaz(Nb8q7kn%^U&G*J$#2d+QR}ckc)gD&^<^3&Sa4d8#2qx z4e%3`TW%2MJu-lrGU?2+<^ij>U^uFPxxnM$et`zgZyI8nN`Jldli{e>A*PP1u>$O% z9DbDDkM?RD9y>$38jS22AU!_7=-!HxN7(rP_Rc*xs_Ks8pR;)p!jcdSkw8Si2n7UG zlqsVv4nIM|Q9J5P)!tSIncC`7I^wI&XsseuJ3%eB;|pKK2eb$XN(B@G zQh`t)5R;I1_Vkb6z57abvsnRP44?D%nLVGGOv2vH-jikT_nz}Rzu)&axX}MOij*i6 z54>8w>YF4TdHa5-IS8NZf%Tu))3JRK?D!M3`f7qcMnoX?SwP={fSpK{=+$W#Jr@zU zAOB1suqrVUUm!UFuP16~sUq$I0ASAo9t0Ky9Vi?DQ~v;CuZ*Hv&_-)}DWIUOUR;EB zJnl5{2&4^3g{=nusI@*E`UHDyQ;-jV*jtU93uiqIg{3|F8Ty8&AxDu@2)P40X>av1 zYJiJtXSg`~6vdE1n$yR)7!V%=p{VHSk|jcy7e3z$D>lIME8ge8n>RqS&*j()+^MyG zFy<3QKLgN+x$9lNz+bwdtP_Bn5Lac0UIbnRuIq2JfOzi$__EHFQlpR__Ll@bX;77P z|E-wQhi*S1LUD96Y`I5zvj=Yx9z^QP7i+B#Mi}QUNSvaG!Q-*DoU?P?Pfg)h%1~3cfjZ`^M@k6QO zcURHpBzWiP4Lc+*u=8=-QxJe!Bp|q?7Xi4=9+zwzo_K-uHYl4Aou^@F{=Qdc2SZAr zq{z!@Lp8Tu>|*w$;H5#L;{`1p5*t{w^hG|XUI>lbokMqNyLn|yhyeTsNv??`09$~Y z(VmZDK3B4kzv&#r_8WSijvhkFmmf>?(2_}_mH>ewFaz=VACIH7d4r+qRygB6+uXCS zC$PV_9)g1(z}^?(>%W2StOXs70NjkUbCwCaazXhVn7kN9oG$sf(F&6@cApqPUasb} zN~jtRm8F_%XDKFC9^cjP{6DEMD&h!CV;g+9rG-UzUd#H`KHIb76@WY>rgJNPE0j>a zLHb2Jlu>s_Wlaa1^<;0$fwhRheVXubC5eOxK(~`fGm)o|twFiE3d&&Oy)gb3zg0xk z*q`=X;8;Cu{sruQ2GG&Y0A?Y^4`YK444n=W7s8l}B%N9(rm?wZZK^xPBd+4-4s=y(MnA6eVqf-`Fp0xJ+71@1?C z8e=|BDpA(y$c{-a5}oQPf>V$Djj_L%X+~0J002s{57u!JxW8%YXWyF;Mk$ zsJsFOSNKbH`|?%=#tG~#MOq=x4J9RzuQcOI;Ie6o=~Ynb26rJ0EP%rNh_4((M@I(@ z4Gq-n+{shVEa#nf|H**^wX`-jqP@N-tEIJG+=tNzBLc7#iJteQSet=MecpbVin7iC z-a{%L!_5RfMz#dkBy(tqBH;oM5)U{V$-*Ksm1NV~Q4gCsRAK z7m#gGm!@o(Vnn$ttVa!!xWU9qIBP6aluBPgg{<*iAs@$m`}R>?UCrv%tEs7}Ve7Up z?5b&zpMYM1_c-2)F`=viB*nX@OuD1MV`$I9m|s5~@TU$>LALxx+D9BI5B%;ydw$&? zmI29%0R*}SO$Vw$jS9Mw%4frbd!T%lyv?mgpz%v++67HJq2Y7+`Pf$>4miGxzZJWm z8}MnQjKDxM6Z_xaM}%p#^J2aiE}07D=RxIVP&!SnNGE0y%SM+iAWGr)u+>ODV-6G* zL-`OGJq*f=rM39zayWl7oLMPP-?V8HD_5>$-MV#b+_;gty1L*o3tpsWz;+}r;;k4) zD@cRTzaZ6?q5K{3(A(R?vCP6T7E#uC#ENd5eIzv0`!euTw5K)MLyR}cTml4&z+7bg zT^01CvhTpKncymb)>^snn|90hZr8K#Zry~Wp1z70V(zfd5zz^!2N9Tw)Q`_5c%3Vh zpC|nSOD4;Vpm?G`9Oflh5Mroz@cABTq;$Fk=r{({GPUvuSDyLyRX$69DKo^nj|9dAUPF5>PRaPipxeo%W?(LGF4AN)^bX}hHsTH-7yNeM3$1m+ zx0;kv#lTO1DTs?u75>0bcXA3LuLN=jNh@<#o``^Q=J$5sKx;`~_I85qkR^_{t+(NL zk?Hm~h@q*{S|39^?B7K?0{L>(k(_YU!gtqJ^p?MkTL;?!<{;5om-VsCn4+K3xF|z8&nZc1bQIxYnk1G)HAdp(}#8> z%6(0*y$S^kZmG zU;M7phLkP=G99H<8PW@(7}?6tLsEX6T*XK@sYM;g9km_UskJ`Phw~?12<6VV1h@wO zOlLLHIHO;C<_8fh0uSN0z?VQM{3{cM&ua4tAV?(8F_C~GwEEE1;yS^F*qmZCkq z1~2+0$|^zn99)mYXKzQ$L({heIt)et43UvSnPtF4`;m3PjcCtjVSgtZ5E1x2G81?L z2?oZuDQgli0$_-Y1OOif>^wxOfd!fPVUIgvHo6$N6ltFNY}$ArMu`ysLwKYN*h1F0 z#Yph)UbN@=h`*OjC~Fw-JERTSg0%BN>;)qLhVaPx{{i4~Bmnp{QVG}1l{Kr8KbxZ%#vs?5JE!3cn%XCwjWIi%L%XK2rpDISp_ z0T=-=^o%3`Ek|k;E<}4aWJTdmmSF_I&@;-q0|@}W4_uG-?CRw=3>k{yb1pGZ;SvKf)N3zM%;#<>JQy-4B;^X zUx-FfFT&EXq$&r|82~asy0Jx7y&T&k-L8>Vo6?)l(m` Missing @@ -18,6 +18,7 @@ - labels.amountOf - labels.damageType - labels.weapon +- dmgHtdExplanation
@@ -40,7 +41,7 @@
Missing -- pages.object-damage +- pages.object-damage-calculator - auth.errors.aborted - auth.errors.failed - auth.errors.discordPermissions @@ -57,9 +58,16 @@ **6/6** -### 🟢 front.json +### 🟡 front.json -**11/11** +**11/12** + +
+Missing + +- object-damage-calculator.description + +
### 🟢 game-misc.json @@ -86,7 +94,7 @@ ### 🟡 analyzer.json -**94/103** +**94/104**
Missing @@ -100,6 +108,7 @@ - labels.amountOf - labels.damageType - labels.weapon +- dmgHtdExplanation
@@ -122,7 +131,7 @@
Missing -- pages.object-damage +- pages.object-damage-calculator - actions.copyToClipboard
@@ -135,9 +144,16 @@ **6/6** -### 🟢 front.json +### 🟡 front.json -**11/11** +**11/12** + +
+Missing + +- object-damage-calculator.description + +
### 🟢 game-misc.json @@ -153,7 +169,7 @@ ### 🟡 analyzer.json -**91/103** +**91/104**
Missing @@ -170,6 +186,7 @@ - labels.amountOf - labels.damageType - labels.weapon +- dmgHtdExplanation
@@ -202,7 +219,7 @@ - pages.s2 - pages.maps -- pages.object-damage +- pages.object-damage-calculator - auth.errors.aborted - auth.errors.failed - auth.errors.discordPermissions @@ -226,12 +243,13 @@ ### 🟡 front.json -**10/11** +**10/12**
Missing - maps.description +- object-damage-calculator.description
@@ -284,7 +302,7 @@ ### 🟡 analyzer.json -**91/103** +**91/104**
Missing @@ -301,6 +319,7 @@ - labels.amountOf - labels.damageType - labels.weapon +- dmgHtdExplanation
@@ -334,7 +353,7 @@ - pages.s2 - pages.analyzer - pages.maps -- pages.object-damage +- pages.object-damage-calculator - auth.errors.aborted - auth.errors.failed - auth.errors.discordPermissions @@ -367,7 +386,7 @@ ### 🟡 front.json -**8/11** +**8/12**
Missing @@ -375,6 +394,7 @@ - buildsGoTo - analyzer.description - maps.description +- object-damage-calculator.description
@@ -427,7 +447,7 @@ ### 🔴 analyzer.json -**0/103** +**0/104** ### 🔴 badges.json @@ -455,7 +475,7 @@ ### 🔴 front.json -**0/11** +**0/12** ### 🟡 game-misc.json @@ -482,7 +502,7 @@ ### 🟡 analyzer.json -**12/103** +**12/104**
Missing @@ -578,6 +598,7 @@ - labels.amountOf - labels.damageType - labels.weapon +- dmgHtdExplanation
@@ -611,7 +632,7 @@ - pages.s2 - pages.analyzer - pages.maps -- pages.object-damage +- pages.object-damage-calculator - auth.errors.aborted - auth.errors.failed - auth.errors.discordPermissions @@ -643,7 +664,7 @@ ### 🟡 front.json -**8/11** +**8/12**
Missing @@ -651,6 +672,7 @@ - buildsGoTo - analyzer.description - maps.description +- object-damage-calculator.description
@@ -703,7 +725,7 @@ ### 🔴 analyzer.json -**0/103** +**0/104** ### 🟢 badges.json @@ -735,7 +757,7 @@ - pages.s2 - pages.analyzer - pages.maps -- pages.object-damage +- pages.object-damage-calculator - auth.errors.aborted - auth.errors.failed - auth.errors.discordPermissions @@ -778,7 +800,7 @@ ### 🟡 front.json -**8/11** +**8/12**
Missing @@ -786,6 +808,7 @@ - buildsGoTo - analyzer.description - maps.description +- object-damage-calculator.description
@@ -838,7 +861,7 @@ ### 🟡 analyzer.json -**94/103** +**94/104**
Missing @@ -852,6 +875,7 @@ - labels.amountOf - labels.damageType - labels.weapon +- dmgHtdExplanation
@@ -884,7 +908,7 @@ - pages.analyzer - pages.maps -- pages.object-damage +- pages.object-damage-calculator - auth.errors.aborted - auth.errors.failed - auth.errors.discordPermissions @@ -908,12 +932,13 @@ ### 🟡 front.json -**10/11** +**10/12**
Missing - maps.description +- object-damage-calculator.description
@@ -954,7 +979,7 @@ ### 🔴 analyzer.json -**0/103** +**0/104** ### 🟢 badges.json @@ -986,7 +1011,7 @@ - pages.s2 - pages.analyzer - pages.maps -- pages.object-damage +- pages.object-damage-calculator - auth.errors.aborted - auth.errors.failed - auth.errors.discordPermissions @@ -1029,7 +1054,7 @@ ### 🟡 front.json -**8/11** +**8/12**
Missing @@ -1037,6 +1062,7 @@ - buildsGoTo - analyzer.description - maps.description +- object-damage-calculator.description
@@ -1089,7 +1115,7 @@ ### 🟡 analyzer.json -**94/103** +**94/104**
Missing @@ -1103,6 +1129,7 @@ - labels.amountOf - labels.damageType - labels.weapon +- dmgHtdExplanation
@@ -1136,7 +1163,7 @@ - pages.s2 - pages.analyzer - pages.maps -- pages.object-damage +- pages.object-damage-calculator - auth.errors.aborted - auth.errors.failed - auth.errors.discordPermissions @@ -1179,7 +1206,7 @@ ### 🟡 front.json -**8/11** +**8/12**
Missing @@ -1187,6 +1214,7 @@ - buildsGoTo - analyzer.description - maps.description +- object-damage-calculator.description
From 4e9e3527ae53098ec73113525b286f6c77b2b5bb Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Wed, 26 Oct 2022 09:01:14 +0300 Subject: [PATCH 16/24] Rename object dmg calc page --- app/routes/{object-damage.tsx => object-damage-calculator.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/routes/{object-damage.tsx => object-damage-calculator.tsx} (100%) diff --git a/app/routes/object-damage.tsx b/app/routes/object-damage-calculator.tsx similarity index 100% rename from app/routes/object-damage.tsx rename to app/routes/object-damage-calculator.tsx From 019e455c618eb0d0f91fec503db8d3bc578b8b9b Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Wed, 26 Oct 2022 09:01:34 +0300 Subject: [PATCH 17/24] Allow access to obj dmg calc page --- app/routes/object-damage-calculator.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/routes/object-damage-calculator.tsx b/app/routes/object-damage-calculator.tsx index 93aa05ee4..6d01e2477 100644 --- a/app/routes/object-damage-calculator.tsx +++ b/app/routes/object-damage-calculator.tsx @@ -51,10 +51,6 @@ export default function ObjectDamagePage() { allDamageTypes, } = useObjectDamage(); - if (process.env.NODE_ENV !== "development") { - return
WIP :)
; - } - return (
From 01b0b55e510c00a60434f02eb71ef7951fb802d0 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Wed, 26 Oct 2022 09:07:35 +0300 Subject: [PATCH 18/24] Add patch badge to obj dmg calc page --- app/routes/object-damage-calculator.tsx | 11 +++++++++-- app/styles/object-damage.css | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/routes/object-damage-calculator.tsx b/app/routes/object-damage-calculator.tsx index 6d01e2477..c5572dd01 100644 --- a/app/routes/object-damage-calculator.tsx +++ b/app/routes/object-damage-calculator.tsx @@ -31,6 +31,8 @@ import { Ability } from "~/components/Ability"; import { damageTypeTranslationString } from "~/utils/i18next"; import { useSetTitle } from "~/hooks/useSetTitle"; +export const CURRENT_PATCH = "1.2"; + export const links: LinksFunction = () => { return [{ rel: "stylesheet", href: styles }]; }; @@ -106,8 +108,13 @@ export default function ObjectDamagePage() { subWeaponId={subWeaponId} damagesToReceivers={damagesToReceivers} /> -
- {t("analyzer:dmgHtdExplanation")} +
+
+ {t("analyzer:dmgHtdExplanation")} +
+
+ {t("analyzer:patch")} {CURRENT_PATCH} +
); diff --git a/app/styles/object-damage.css b/app/styles/object-damage.css index d142de70f..c781599f0 100644 --- a/app/styles/object-damage.css +++ b/app/styles/object-damage.css @@ -81,3 +81,20 @@ letter-spacing: 0.5px; margin-block-start: var(--s-2); } + +.object-damage__bottom-container { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; +} + +.object-damage__patch { + border-radius: var(--rounded); + background-color: var(--theme-transparent); + color: var(--text-lighter); + font-size: var(--fonts-xxxs); + font-weight: var(--bold); + padding-block: var(--s-0-5); + padding-inline: var(--s-1); +} From b8a19e0190952c17a4b2d15a27bf886ab7b0882a Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Wed, 26 Oct 2022 09:10:51 +0300 Subject: [PATCH 19/24] Update translation-progress.md --- translation-progress.md | 64 +---------------------------------------- 1 file changed, 1 insertion(+), 63 deletions(-) diff --git a/translation-progress.md b/translation-progress.md index e8a162076..106482a11 100644 --- a/translation-progress.md +++ b/translation-progress.md @@ -1,68 +1,6 @@ # Translation Progress -## /da (🟡 In progress) - -### 🟢 analyzer.json - -**94/94** - -### 🟢 badges.json - -**7/7** - -### 🟢 builds.json - -**11/11** - -### 🟢 calendar.json - -**46/46** - -### 🟡 common.json - -**55/60** - -
-Missing - -- auth.errors.aborted -- auth.errors.failed -- auth.errors.discordPermissions -- auth.errors.unknown -- actions.close - -
- -### 🟢 contributions.json - -**6/6** - -### 🟢 faq.json - -**6/6** - -### 🟢 front.json - -**11/11** - -### 🟢 game-misc.json - -**17/17** - -### 🟡 user.json - -**20/25** - -
-Missing - -- results.title -- results.highlights -- results.nonHighlights -- results.highlights.choose -- results.highlights.explanation - -
+## /da (🟢 Done) --- From b4762d6455cc7c5562f2b82b453c5e64adb08564 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Wed, 26 Oct 2022 17:29:32 +0300 Subject: [PATCH 20/24] Fix crash when no damage data on obj dmg calc --- app/modules/analyzer/useObjectDamage.ts | 24 +++++++++++----------- app/routes/object-damage-calculator.tsx | 24 +++++++++++++--------- public/locales/en/analyzer.json | 3 ++- translation-progress.md | 27 ++++++++++++++++--------- 4 files changed, 46 insertions(+), 32 deletions(-) diff --git a/app/modules/analyzer/useObjectDamage.ts b/app/modules/analyzer/useObjectDamage.ts index 8f44c8b0c..6b20383f9 100644 --- a/app/modules/analyzer/useObjectDamage.ts +++ b/app/modules/analyzer/useObjectDamage.ts @@ -1,5 +1,4 @@ import { useSearchParams } from "@remix-run/react"; -import invariant from "tiny-invariant"; import { type MainWeaponId } from "../in-game-lists"; import { calculateDamage } from "./objectDamage"; import { buildStats } from "./stats"; @@ -37,7 +36,7 @@ export function useObjectDamage() { { weapon: String(newMainWeaponId), [ABILITY_POINTS_SP_KEY]: String(newAbilityPoints), - [DAMAGE_TYPE_SP_KEY]: newDamageType, + [DAMAGE_TYPE_SP_KEY]: newDamageType ?? "", }, { replace: true, state: { scroll: false } } ); @@ -47,15 +46,17 @@ export function useObjectDamage() { mainWeaponId, subWeaponId: analyzed.weapon.subWeaponSplId, handleChange, - damagesToReceivers: calculateDamage({ - abilityPoints: new Map([ - ["BRU", { ap: abilityPoints, apBeforeTacticooler: abilityPoints }], - ["SPU", { ap: abilityPoints, apBeforeTacticooler: abilityPoints }], - ]), - analyzed, - mainWeaponId, - damageType, - }), + damagesToReceivers: damageType + ? calculateDamage({ + abilityPoints: new Map([ + ["BRU", { ap: abilityPoints, apBeforeTacticooler: abilityPoints }], + ["SPU", { ap: abilityPoints, apBeforeTacticooler: abilityPoints }], + ]), + analyzed, + mainWeaponId, + damageType, + }) + : null, abilityPoints: String(abilityPoints), damageType, allDamageTypes: Array.from( @@ -99,7 +100,6 @@ function validatedDamageTypeFromSearchParams({ const fallbackFound = damageTypePriorityList.find((type) => analyzed.stats.damages.some((d) => d.type === type) ); - invariant(fallbackFound); return fallbackFound; } diff --git a/app/routes/object-damage-calculator.tsx b/app/routes/object-damage-calculator.tsx index c5572dd01..3cf903dbd 100644 --- a/app/routes/object-damage-calculator.tsx +++ b/app/routes/object-damage-calculator.tsx @@ -72,7 +72,7 @@ export default function ObjectDamagePage() { clearsInputOnFocus /> -
+
- + {damagesToReceivers ? ( + + ) : ( +
{t("analyzer:noDmgData")}
+ )}
{t("analyzer:dmgHtdExplanation")} @@ -175,10 +179,12 @@ const damageReceiverImages: Record = { function DamageReceiversGrid({ subWeaponId, damagesToReceivers, -}: Pick< - ReturnType, - "damagesToReceivers" | "subWeaponId" ->) { +}: { + subWeaponId: ReturnType["subWeaponId"]; + damagesToReceivers: NonNullable< + ReturnType["damagesToReceivers"] + >; +}) { const { t } = useTranslation(["weapons", "analyzer", "common"]); useSetTitle(t("common:pages.object-damage-calculator")); diff --git a/public/locales/en/analyzer.json b/public/locales/en/analyzer.json index bf14acb29..5034589c7 100644 --- a/public/locales/en/analyzer.json +++ b/public/locales/en/analyzer.json @@ -102,5 +102,6 @@ "labels.amountOf": "Amount of", "labels.damageType": "Damage type", "labels.weapon": "Weapon", - "dmgHtdExplanation": "DMG = Damage • HTD = Hits to destroy" + "dmgHtdExplanation": "DMG = Damage • HTD = Hits to destroy", + "noDmgData": "No damage data yet for this weapon. Check back later!" } diff --git a/translation-progress.md b/translation-progress.md index f412d1841..fb499a8ba 100644 --- a/translation-progress.md +++ b/translation-progress.md @@ -4,7 +4,7 @@ ### 🟡 analyzer.json -**94/104** +**94/105**
Missing @@ -19,6 +19,7 @@ - labels.damageType - labels.weapon - dmgHtdExplanation +- noDmgData
@@ -78,7 +79,7 @@ ### 🟡 analyzer.json -**94/104** +**94/105**
Missing @@ -93,6 +94,7 @@ - labels.damageType - labels.weapon - dmgHtdExplanation +- noDmgData
@@ -153,7 +155,7 @@ ### 🟡 analyzer.json -**91/104** +**91/105**
Missing @@ -171,6 +173,7 @@ - labels.damageType - labels.weapon - dmgHtdExplanation +- noDmgData
@@ -286,7 +289,7 @@ ### 🟡 analyzer.json -**91/104** +**91/105**
Missing @@ -304,6 +307,7 @@ - labels.damageType - labels.weapon - dmgHtdExplanation +- noDmgData
@@ -431,7 +435,7 @@ ### 🔴 analyzer.json -**0/104** +**0/105** ### 🔴 badges.json @@ -486,7 +490,7 @@ ### 🟡 analyzer.json -**12/104** +**12/105**
Missing @@ -583,6 +587,7 @@ - labels.damageType - labels.weapon - dmgHtdExplanation +- noDmgData
@@ -709,7 +714,7 @@ ### 🔴 analyzer.json -**0/104** +**0/105** ### 🟢 badges.json @@ -845,7 +850,7 @@ ### 🟡 analyzer.json -**94/104** +**94/105**
Missing @@ -860,6 +865,7 @@ - labels.damageType - labels.weapon - dmgHtdExplanation +- noDmgData
@@ -963,7 +969,7 @@ ### 🔴 analyzer.json -**0/104** +**0/105** ### 🟢 badges.json @@ -1099,7 +1105,7 @@ ### 🟡 analyzer.json -**94/104** +**94/105**
Missing @@ -1114,6 +1120,7 @@ - labels.damageType - labels.weapon - dmgHtdExplanation +- noDmgData
From 4353b30f7e5a61a00bccd4ed8f28796204c29b46 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Wed, 26 Oct 2022 17:30:40 +0300 Subject: [PATCH 21/24] Add object-damage-calculator page title --- app/routes/object-damage-calculator.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/routes/object-damage-calculator.tsx b/app/routes/object-damage-calculator.tsx index 3cf903dbd..979cb7736 100644 --- a/app/routes/object-damage-calculator.tsx +++ b/app/routes/object-damage-calculator.tsx @@ -39,6 +39,7 @@ export const links: LinksFunction = () => { export const handle: SendouRouteHandle = { i18n: ["weapons", "analyzer"], + navItemName: "object-damage-calculator", }; export default function ObjectDamagePage() { From 672fa07152263e152ee65a0a4a91732e8fb8cbd6 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Thu, 27 Oct 2022 09:14:02 +0300 Subject: [PATCH 22/24] Update Build Analyzer to patch 1.2.0 --- app/modules/analyzer/weapon-params.json | 8 ++++---- app/routes/analyzer.tsx | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/modules/analyzer/weapon-params.json b/app/modules/analyzer/weapon-params.json index a01399359..e2ccd2823 100644 --- a/app/modules/analyzer/weapon-params.json +++ b/app/modules/analyzer/weapon-params.json @@ -375,7 +375,7 @@ "InkConsume_WeaponSwingParam": 0.18 }, "1030": { - "SpecialPoint": 200, + "SpecialPoint": 210, "subWeaponId": 10, "specialWeaponId": 4, "InkConsume_WeaponVerticalSwingParam": 0.12, @@ -513,7 +513,7 @@ "InkConsumeSlosher": 0.06 }, "3020": { - "SpecialPoint": 200, + "SpecialPoint": 210, "subWeaponId": 5, "specialWeaponId": 6, "MoveSpeed": 0.07, @@ -748,7 +748,7 @@ "InkConsumeFullCharge_ChargeParam": 0.085 }, "7020": { - "SpecialPoint": 200, + "SpecialPoint": 210, "subWeaponId": 6, "specialWeaponId": 4, "WeaponSpeedType": "Fast", @@ -881,7 +881,7 @@ }, "SubInkSaveLv": 1, "InkConsume": 0.6, - "InkRecoverStop": 70, + "InkRecoverStop": 85, "DistanceDamage_BlastParamArray": [ [ { diff --git a/app/routes/analyzer.tsx b/app/routes/analyzer.tsx index b9b89b763..32c1e3ea8 100644 --- a/app/routes/analyzer.tsx +++ b/app/routes/analyzer.tsx @@ -39,7 +39,7 @@ import { type SendouRouteHandle } from "~/utils/remix"; import { makeTitle } from "~/utils/strings"; import { specialWeaponImageUrl, subWeaponImageUrl } from "~/utils/urls"; -export const CURRENT_PATCH = "1.1"; +export const CURRENT_PATCH = "1.2"; export const meta: MetaFunction = () => { return { From 41120088e0fc031cbb0b9a4082f0d9d251b200a7 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Thu, 27 Oct 2022 09:27:12 +0300 Subject: [PATCH 23/24] Add Hydra fully charged shot damage to analyzer Closes #1046 --- app/modules/analyzer/constants.ts | 2 ++ app/modules/analyzer/stats.ts | 3 ++- app/modules/analyzer/types.ts | 1 + app/modules/analyzer/weapon-params.json | 1 + public/locales/en/analyzer.json | 1 + scripts/create-analyzer-json.ts | 4 ++++ 6 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/modules/analyzer/constants.ts b/app/modules/analyzer/constants.ts index ea90740d4..a1baf0cbf 100644 --- a/app/modules/analyzer/constants.ts +++ b/app/modules/analyzer/constants.ts @@ -23,6 +23,7 @@ export const DAMAGE_RECEIVERS = [ export const DAMAGE_TYPE = [ "NORMAL_MIN", "NORMAL_MAX", + "NORMAL_MAX_FULL_CHARGE", // Hydra Splatling goes from 32 to 40 dmg when fully charged "DIRECT", "FULL_CHARGE", "MAX_CHARGE", @@ -38,6 +39,7 @@ export const damageTypeToWeaponType: Record< > = { NORMAL_MIN: "MAIN", NORMAL_MAX: "MAIN", + NORMAL_MAX_FULL_CHARGE: "MAIN", DIRECT: "MAIN", FULL_CHARGE: "MAIN", MAX_CHARGE: "MAIN", diff --git a/app/modules/analyzer/stats.ts b/app/modules/analyzer/stats.ts index dbad75bfa..5f67f7fda 100644 --- a/app/modules/analyzer/stats.ts +++ b/app/modules/analyzer/stats.ts @@ -341,6 +341,7 @@ const damageTypeToParamsKey: Record< > = { NORMAL_MIN: "DamageParam_ValueMin", NORMAL_MAX: "DamageParam_ValueMax", + NORMAL_MAX_FULL_CHARGE: "DamageParam_ValueFullChargeMax", DIRECT: "DamageParam_ValueDirect", DISTANCE: "BlastParam_DistanceDamage", FULL_CHARGE: "DamageParam_ValueFullCharge", @@ -398,7 +399,7 @@ function shotsToSplat({ type: DamageType; isTripleShooter: boolean; }) { - if (type !== "NORMAL_MAX") return; + if (type !== "NORMAL_MAX" && type !== "NORMAL_MAX_FULL_CHARGE") return; const multiplier = isTripleShooter ? 3 : 1; diff --git a/app/modules/analyzer/types.ts b/app/modules/analyzer/types.ts index 7d6fe14f5..bedf7fe6b 100644 --- a/app/modules/analyzer/types.ts +++ b/app/modules/analyzer/types.ts @@ -33,6 +33,7 @@ export interface MainWeaponParams { MoveSpeedFullCharge?: number; DamageParam_ValueMax?: number; DamageParam_ValueMin?: number; + DamageParam_ValueFullChargeMax?: number; DamageParam_ValueDirect?: number; Jump_DegSwerve?: number; Stand_DegSwerve?: number; diff --git a/app/modules/analyzer/weapon-params.json b/app/modules/analyzer/weapon-params.json index e2ccd2823..172d0b5ba 100644 --- a/app/modules/analyzer/weapon-params.json +++ b/app/modules/analyzer/weapon-params.json @@ -597,6 +597,7 @@ "MoveSpeed_Charge": 0.04, "DamageParam_ValueMax": 320, "DamageParam_ValueMin": 160, + "DamageParam_ValueFullChargeMax": 400, "Jump_DegSwerve": 6, "Stand_DegSwerve": 3, "InkRecoverStop": 40, diff --git a/public/locales/en/analyzer.json b/public/locales/en/analyzer.json index 5034589c7..9cfcb8bc7 100644 --- a/public/locales/en/analyzer.json +++ b/public/locales/en/analyzer.json @@ -79,6 +79,7 @@ "damage.toSplat": "{{count}} hits to splat", "damage.NORMAL_MIN": "Minimum", "damage.NORMAL_MAX": "Maximum", + "damage.NORMAL_MAX_FULL_CHARGE": "Maximum (Fully charged)", "damage.DIRECT": "Direct", "damage.FULL_CHARGE": "Fully charged shot", "damage.MAX_CHARGE": "Maximum partial charge", diff --git a/scripts/create-analyzer-json.ts b/scripts/create-analyzer-json.ts index 75aab23cc..1933bb4d8 100644 --- a/scripts/create-analyzer-json.ts +++ b/scripts/create-analyzer-json.ts @@ -172,6 +172,10 @@ function parametersToMainWeaponResult( DamageParam_ValueDirect, BlastParam_DistanceDamage: params["BlastParam"]?.["DistanceDamage"], DamageParam_ValueFullCharge: params["DamageParam"]?.["ValueFullCharge"], + DamageParam_ValueFullChargeMax: + params["DamageParam"]?.["ValueFullChargeMax"] !== DamageParam_ValueMax() + ? params["DamageParam"]?.["ValueFullChargeMax"] + : undefined, DamageParam_ValueMaxCharge: params["DamageParam"]?.["ValueMaxCharge"], DamageParam_ValueMinCharge: params["DamageParam"]?.["ValueMinCharge"], CanopyHP: params["spl__BulletShelterCanopyParam"]?.["CanopyHP"], From e6c25967e9e49d7d93b46ed550e3be2fab6a1e46 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Thu, 27 Oct 2022 09:43:56 +0300 Subject: [PATCH 24/24] Analyzer add link to Obj DMG page when OS selected Closes #1039 --- app/routes/analyzer.tsx | 24 ++++++++++++++++++++++- app/routes/index.tsx | 4 ++-- app/styles/analyzer.css | 13 +++++++++++++ app/utils/urls.ts | 5 ++++- public/locales/en/analyzer.json | 1 + translation-progress.md | 34 +++++++++++++++++++++++---------- 6 files changed, 67 insertions(+), 14 deletions(-) diff --git a/app/routes/analyzer.tsx b/app/routes/analyzer.tsx index 32c1e3ea8..11e6fc99d 100644 --- a/app/routes/analyzer.tsx +++ b/app/routes/analyzer.tsx @@ -1,4 +1,5 @@ import { type LinksFunction, type MetaFunction } from "@remix-run/node"; +import { Link } from "@remix-run/react"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { AbilitiesSelector } from "~/components/AbilitiesSelector"; @@ -37,7 +38,12 @@ import styles from "~/styles/analyzer.css"; import { damageTypeTranslationString } from "~/utils/i18next"; import { type SendouRouteHandle } from "~/utils/remix"; import { makeTitle } from "~/utils/strings"; -import { specialWeaponImageUrl, subWeaponImageUrl } from "~/utils/urls"; +import { + navIconUrl, + objectDamageCalculatorPage, + specialWeaponImageUrl, + subWeaponImageUrl, +} from "~/utils/urls"; export const CURRENT_PATCH = "1.2"; @@ -69,6 +75,8 @@ export default function BuildAnalyzerPage() { effects, } = useAnalyzeBuild(); + const objectShredderSelected = build[2][0] === "OS"; + const mainWeaponCategoryItems = [ analyzed.stats.shotSpreadAir && ( + {objectShredderSelected && ( + + + {t("analyzer:objCalcAd")} + + )}
diff --git a/app/routes/index.tsx b/app/routes/index.tsx index e10b66453..fb7c0b3cb 100644 --- a/app/routes/index.tsx +++ b/app/routes/index.tsx @@ -22,7 +22,7 @@ import { CALENDAR_PAGE, mapsPage, navIconUrl, - OBJECT_DAMAGE_CALCULATOR, + objectDamageCalculatorPage, plusSuggestionPage, userPage, } from "~/utils/urls"; @@ -80,7 +80,7 @@ export default function Index() { navItem="object-damage-calculator" title={t("common:pages.object-damage-calculator")} description={t("front:object-damage-calculator.description")} - to={OBJECT_DAMAGE_CALCULATOR} + to={objectDamageCalculatorPage()} /> + `/object-damage-calculator${ + typeof weaponId === "number" ? `?weapon=${weaponId}` : "" + }`; export const badgeUrl = ({ code, diff --git a/public/locales/en/analyzer.json b/public/locales/en/analyzer.json index 9cfcb8bc7..e6d5e9a2a 100644 --- a/public/locales/en/analyzer.json +++ b/public/locales/en/analyzer.json @@ -3,6 +3,7 @@ "attribute.weight.Fast": "Light", "attribute.weight.Slow": "Heavy", "attribute.weight.Normal": "Normal", + "objCalcAd": "For info on Object Shredder check out Object DMG Calc", "stat.category.main": "Main weapon", "stat.category.sub": "Sub weapon", "stat.category.special": "Special weapon", diff --git a/translation-progress.md b/translation-progress.md index fb499a8ba..aca22c713 100644 --- a/translation-progress.md +++ b/translation-progress.md @@ -4,13 +4,15 @@ ### 🟡 analyzer.json -**94/105** +**94/107**
Missing +- objCalcAd - stat.specialLostSplattedByRP - stat.quickRespawnTimeSplattedByRP +- damage.NORMAL_MAX_FULL_CHARGE - distanceInline - damageShort - hitsToDestroyLong @@ -79,13 +81,15 @@ ### 🟡 analyzer.json -**94/105** +**94/107**
Missing +- objCalcAd - stat.specialLostSplattedByRP - stat.quickRespawnTimeSplattedByRP +- damage.NORMAL_MAX_FULL_CHARGE - distanceInline - damageShort - hitsToDestroyLong @@ -155,16 +159,18 @@ ### 🟡 analyzer.json -**91/105** +**91/107**
Missing +- objCalcAd - stat.specialLostSplattedByRP - stat.quickRespawnTimeSplattedByRP - stat.shootingRunSpeed - stat.shootingRunSpeedCharging - stat.shootingRunSpeedFullCharge +- damage.NORMAL_MAX_FULL_CHARGE - distanceInline - damageShort - hitsToDestroyLong @@ -289,16 +295,18 @@ ### 🟡 analyzer.json -**91/105** +**91/107**
Missing +- objCalcAd - stat.specialLostSplattedByRP - stat.quickRespawnTimeSplattedByRP - stat.shootingRunSpeed - stat.shootingRunSpeedCharging - stat.shootingRunSpeedFullCharge +- damage.NORMAL_MAX_FULL_CHARGE - distanceInline - damageShort - hitsToDestroyLong @@ -435,7 +443,7 @@ ### 🔴 analyzer.json -**0/105** +**0/107** ### 🔴 badges.json @@ -490,11 +498,12 @@ ### 🟡 analyzer.json -**12/105** +**12/107**
Missing +- objCalcAd - stat.canopyHp - stat.fullChargeSeconds - stat.maxChargeHoldSeconds @@ -563,6 +572,7 @@ - damage.toSplat - damage.NORMAL_MIN - damage.NORMAL_MAX +- damage.NORMAL_MAX_FULL_CHARGE - damage.DIRECT - damage.FULL_CHARGE - damage.MAX_CHARGE @@ -714,7 +724,7 @@ ### 🔴 analyzer.json -**0/105** +**0/107** ### 🟢 badges.json @@ -850,13 +860,15 @@ ### 🟡 analyzer.json -**94/105** +**94/107**
Missing +- objCalcAd - stat.specialLostSplattedByRP - stat.quickRespawnTimeSplattedByRP +- damage.NORMAL_MAX_FULL_CHARGE - distanceInline - damageShort - hitsToDestroyLong @@ -969,7 +981,7 @@ ### 🔴 analyzer.json -**0/105** +**0/107** ### 🟢 badges.json @@ -1105,13 +1117,15 @@ ### 🟡 analyzer.json -**94/105** +**94/107**
Missing +- objCalcAd - stat.specialLostSplattedByRP - stat.quickRespawnTimeSplattedByRP +- damage.NORMAL_MAX_FULL_CHARGE - distanceInline - damageShort - hitsToDestroyLong