diff --git a/.vscode/settings.json b/.vscode/settings.json index 022f9a0..5820867 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,8 +26,5 @@ "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer" }, - "eslint.run": "onType", - "editor.codeActionsOnSave": { - "source.organizeImports": "explicit" - } + "eslint.run": "onType" } diff --git a/package-lock.json b/package-lock.json index b013f5a..2baec26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "platform-paths": "^1.2.2", "polylabel": "^1.1.0", "rambda": "^9.2.0", + "romans": "^2.0.16", "steam-path": "^1.0.2", "tauri-plugin-fs-watch-api": "github:tauri-apps/tauri-plugin-fs-watch#v1", "topojson-client": "^3.1.0", @@ -12038,6 +12039,11 @@ "fsevents": "~2.3.2" } }, + "node_modules/romans": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/romans/-/romans-2.0.16.tgz", + "integrity": "sha512-VsNzu29gZf73w+q4LgNJ4eZ4qss7Wyhwzd4b/GDaOaVINs5OTuvr5NuE/fKnpm0ODBCqXxMcIal1elAbybwWpA==" + }, "node_modules/rrweb-cssom": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", diff --git a/package.json b/package.json index bc2bcf4..ba98b71 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "platform-paths": "^1.2.2", "polylabel": "^1.1.0", "rambda": "^9.2.0", + "romans": "^2.0.16", "steam-path": "^1.0.2", "tauri-plugin-fs-watch-api": "github:tauri-apps/tauri-plugin-fs-watch#v1", "topojson-client": "^3.1.0", diff --git a/src/renderer/src/intl/en-US.ts b/src/renderer/src/intl/en-US.ts index 776683c..29e7338 100644 --- a/src/renderer/src/intl/en-US.ts +++ b/src/renderer/src/intl/en-US.ts @@ -162,6 +162,7 @@ export default { }, triangle: 'Triangle', triangle_flat: 'Triangle (flat top)', + triangle_narrow: 'Triangle (narrow)', diamond: 'Diamond', square: 'Square', pentagon: 'Pentagon', @@ -425,6 +426,13 @@ export default { systemMapLabelMoonsEnabled: 'Moon Names Enabled', systemMapLabelAsteroidsEnabled: 'Asteroid Names Enabled', systemMapHyperlanesEnabled: 'Hyperlane Connections Enabled', + systemMapCivilianFleetIcon: 'Civilian Fleet Icon', + systemMapCivilianStationIcon: 'Civilian Station Icon', + systemMapMilitaryFleetIcon: 'Military Fleet Icon', + systemMapMilitaryStationIcon: 'Military Station Icon', + systemMapLabelFleetsEnabled: 'Fleet/Station Names', + systemMapLabelFleetsFontSize: 'Fleet/Station Name Font Size', + systemMapLabelFleetsPosition: 'Fleet/Station Name Position', appLocale: 'StellarMaps Language', appLocale_tooltip: 'Join the Discord server (link in the top bar) if you want to help translate!', diff --git a/src/renderer/src/lib/ExportModal.svelte b/src/renderer/src/lib/ExportModal.svelte index 50afaa3..1aa21b6 100644 --- a/src/renderer/src/lib/ExportModal.svelte +++ b/src/renderer/src/lib/ExportModal.svelte @@ -37,8 +37,9 @@ target: solarSystemMapTarget, props: { id: 'exportSystemMap', - colors: colors, - gameState: gameState, + colors, + mapData, + gameState, system: openedSystem, exportMode: true, }, @@ -453,6 +454,7 @@ {#if openedSystem} >; const shipSchema = z.object({ fleet: z.number(), + coordinate: z.object({ x: z.number(), y: z.number() }), + rotation: z.number(), }); /** @@ -258,8 +260,15 @@ const shipSchema = z.object({ export type Ship = WithId>; const fleetSchema = z.object({ + name: localizedTextSchema, station: z.boolean().optional(), military_power: z.number(), + mobile: z.boolean().optional(), + ships: z.array(z.number()), + movement_manager: z.object({ + coordinate: z.object({ x: z.number(), y: z.number() }), + formation: z.object({ angle: z.number() }), + }), }); /** diff --git a/src/renderer/src/lib/SettingControl/IconSettingControl.svelte b/src/renderer/src/lib/SettingControl/IconSettingControl.svelte index 3bb3d2d..269b311 100644 --- a/src/renderer/src/lib/SettingControl/IconSettingControl.svelte +++ b/src/renderer/src/lib/SettingControl/IconSettingControl.svelte @@ -68,61 +68,63 @@ /> - - - - {$t('control.icon.advanced_options.header')} - -
-
- - -
-
- - { - const parsed = parseFloat(e.currentTarget.value); - if (Number.isNaN(parsed)) { + {#if !config.noAdvanced} + + + + {$t('control.icon.advanced_options.header')} + +
+
+ + +
+
+ + { + const parsed = parseFloat(e.currentTarget.value); + if (Number.isNaN(parsed)) { + value = { + ...value, + priority: parseInt(e.currentTarget.value), + }; + } + }} + /> +
-
- - + + + {/if} (i === 0 ? `M${p}` : `L${p}`)).join(' ')} z`; + } + function makeRegularPolygonPath(n: number, { flatTop }: { flatTop: boolean }) { const points = R.range(0, n).map((i) => pointOnCircle(i / n - 0.25 - (flatTop ? 0.5 / n : 0), 90), ); - return `${points.map((p, i) => (i === 0 ? `M${p}` : `L${p}`)).join(' ')} z`; + return pointsToSvgPath(points); } function makeStarPath(n: number) { @@ -21,7 +25,7 @@ pointOnCircle(i / n - 0.25, 90), pointOnCircle((i * 2 + 1) / (n * 2) - 0.25, 30), ]); - return `${points.map((p, i) => (i === 0 ? `M${p}` : `L${p}`)).join(' ')} z`; + return pointsToSvgPath(points); } @@ -33,6 +37,16 @@ + + + + diff --git a/src/renderer/src/lib/map/MapContainer.svelte b/src/renderer/src/lib/map/MapContainer.svelte index 80e478c..ca7c293 100644 --- a/src/renderer/src/lib/map/MapContainer.svelte +++ b/src/renderer/src/lib/map/MapContainer.svelte @@ -489,10 +489,11 @@
{/await} - {#if openedSystem && gameStateOrNull && colorsOrNull} + {#if openedSystem && gameStateOrNull && colorsOrNull && dataOrNull} { diff --git a/src/renderer/src/lib/map/MapTooltip.svelte b/src/renderer/src/lib/map/MapTooltip.svelte index e1af778..fd00e27 100644 --- a/src/renderer/src/lib/map/MapTooltip.svelte +++ b/src/renderer/src/lib/map/MapTooltip.svelte @@ -3,6 +3,7 @@ import { onDestroy, onMount } from 'svelte'; import { locale, type MessageID, t } from '../../intl'; + import debug from '../debug'; import type { GalacticObject, GameState, LocalizedText } from '../GameState'; import HeroiconUserMicro from '../icons/HeroiconUserMicro.svelte'; import { mapSettings } from '../settings'; @@ -91,9 +92,13 @@ {#await localizeText(system.name)} {$t('generic.loading')} {:then name} - {name} {system.id} + {name} {/await} + {#if $debug} +
System ID: {system.id}
+
Country ID: {processedSystem?.countryId}
+ {/if} {#if processedSystem?.mapModeCountryLabel}
diff --git a/src/renderer/src/lib/map/SystemIcons.svelte b/src/renderer/src/lib/map/SystemIcons.svelte index 67092db..bfdcd44 100644 --- a/src/renderer/src/lib/map/SystemIcons.svelte +++ b/src/renderer/src/lib/map/SystemIcons.svelte @@ -21,7 +21,7 @@ systemProperty?: keyof PickByValue; mustKnowOwner?: boolean; } - const metadata: Record = { + const metadata: Partial> = { countryCapitalIcon: { systemProperty: 'isCountryCapital', mustKnowOwner: true }, sectorCapitalIcon: { systemProperty: 'isSectorCapital', mustKnowOwner: true }, populatedSystemIcon: { systemProperty: 'isColonized', mustKnowOwner: true }, @@ -41,10 +41,11 @@ ) .filter((config) => mapSettings[config.id].enabled) .filter((config) => { - const systemProperty = metadata[config.id].systemProperty; + const meta = metadata[config.id]; + if (!meta) return false; + const systemProperty = meta.systemProperty; if (systemProperty != null && !system[systemProperty]) return false; - if (metadata[config.id].mustKnowOwner && mapSettings.terraIncognita && !system.ownerIsKnown) - return false; + if (meta.mustKnowOwner && mapSettings.terraIncognita && !system.ownerIsKnown) return false; return true; }) .sort((a, b) => mapSettings[b.id].priority - mapSettings[a.id].priority); diff --git a/src/renderer/src/lib/map/data/locUtils.ts b/src/renderer/src/lib/map/data/locUtils.ts index 3d15e61..8cbf9a7 100644 --- a/src/renderer/src/lib/map/data/locUtils.ts +++ b/src/renderer/src/lib/map/data/locUtils.ts @@ -1,7 +1,33 @@ +import { romanize } from 'romans'; import { get } from 'svelte/store'; import type { LocalizedText } from '../../GameState'; import { stellarisDataPromiseStore } from '../../loadStellarisData'; +import { appStellarisLanguageCode, appStellarisLanguageOrdinals } from '../../settings'; +import { isDefined } from '../../utils'; + +const formatOrdinals = (n: number) => { + const enOrdinalRules = new Intl.PluralRules(get(appStellarisLanguageCode), { type: 'ordinal' }); + const rule = enOrdinalRules.select(n); + const suffix = get(appStellarisLanguageOrdinals)[rule] ?? ''; + return `${new Intl.NumberFormat(get(appStellarisLanguageCode)).format(n)}${suffix}`; +}; + +const numFormatters = { + CARD: (n: number) => new Intl.NumberFormat(get(appStellarisLanguageCode)).format(n), + C: (n: number) => + new Intl.NumberFormat(get(appStellarisLanguageCode), { minimumIntegerDigits: 1 }).format(n), + CC: (n: number) => + new Intl.NumberFormat(get(appStellarisLanguageCode), { minimumIntegerDigits: 2 }).format(n), + CCC: (n: number) => + new Intl.NumberFormat(get(appStellarisLanguageCode), { minimumIntegerDigits: 3 }).format(n), + CC0: (n: number) => + new Intl.NumberFormat(get(appStellarisLanguageCode), { minimumIntegerDigits: 2 }).format(n - 1), + ORD: formatOrdinals, + ORD0: (n: number) => formatOrdinals(n - 1), + R: (n: number) => romanize(n), + HEX: (n: number) => n.toString(16), +}; export function localizeText(text: LocalizedText) { return get(stellarisDataPromiseStore).then(({ loc }) => localizeTextSync(text, loc)); @@ -12,7 +38,36 @@ export function localizeTextSync( loc: Record, ): string { if (text == null) return 'NULL'; - if (text.key === '%ADJECTIVE%') { + if (text.key === '%ACRONYM%') { + const base = text.variables?.find((v) => v.key === 'base')?.value; + return base + ? localizeTextSync(base, loc) + .split(' ') + .filter((s) => s.length > 0) + // first char of each word + last char of final word (cause that's how acronyms work in Stellaris *shrug*) + .flatMap((s, i, a) => (i < a.length - 1 ? [s[0]] : [s[0], s[s.length - 1]])) + .filter(isDefined) + .join('') + .toLocaleUpperCase(get(appStellarisLanguageCode)) + : ''; + } else if (text.key === '%SEQ%') { + const fmt = text.variables?.find((v) => v.key === 'fmt')?.value; + const num = text.variables?.find((v) => v.key === 'num')?.value; + if (fmt == null || num == null) { + console.warn('localization failed', text); + return 'LOCALIZATION FAILED'; + } + return localizeTextSync( + { + key: fmt.key, + variables: Object.entries(numFormatters).map(([key, formatter]) => ({ + key, + value: { key: formatter(parseInt(num.key)) }, + })), + }, + loc, + ); + } else if (text.key === '%ADJECTIVE%') { try { const var0 = text.variables?.[0]; const var1 = text.variables?.[1]; diff --git a/src/renderer/src/lib/map/data/processEmblems.ts b/src/renderer/src/lib/map/data/processEmblems.ts index 9bc4740..fe66f22 100644 --- a/src/renderer/src/lib/map/data/processEmblems.ts +++ b/src/renderer/src/lib/map/data/processEmblems.ts @@ -24,6 +24,7 @@ export async function processEmblems(countries: Country[]) { } const promise = emblems[key]; if (promise != null) promises.push(promise); + if (promise != null && c.flag.icon.file === 'flag_07.dds') promise.then(console.log); } } }); diff --git a/src/renderer/src/lib/map/data/processSystems.ts b/src/renderer/src/lib/map/data/processSystems.ts index 9475e08..d8913d7 100644 --- a/src/renderer/src/lib/map/data/processSystems.ts +++ b/src/renderer/src/lib/map/data/processSystems.ts @@ -12,6 +12,7 @@ import type processSystemOwnership from './processSystemOwnership'; export interface ProcessedSystem extends MapModeCountryInfo { id: number; + countryId?: number; isColonized: boolean; isSectorCapital: boolean; isCountryCapital: boolean; @@ -102,6 +103,7 @@ export default function processSystems( return { id: system.id, + countryId, ...mapModeInfo, isColonized, isSectorCapital, diff --git a/src/renderer/src/lib/map/solarSystemMap/SolarSystemMap.svelte b/src/renderer/src/lib/map/solarSystemMap/SolarSystemMap.svelte index 79462e3..cf45c61 100644 --- a/src/renderer/src/lib/map/solarSystemMap/SolarSystemMap.svelte +++ b/src/renderer/src/lib/map/solarSystemMap/SolarSystemMap.svelte @@ -1,17 +1,25 @@