Skip to content

Commit

Permalink
feat(map): add optional occupation stripes to map and legend
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelMakesGames committed Aug 20, 2024
1 parent 19af168 commit 24cd624
Show file tree
Hide file tree
Showing 14 changed files with 388 additions and 46 deletions.
7 changes: 7 additions & 0 deletions src/renderer/src/intl/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ export default {
group: {
borders: 'Borders',
unions: 'Union Mode',
occupation: 'Occupation',
countryLabels: 'Country Labels',
systemLabels: 'System Labels',
systemIcons: 'System Icons',
Expand Down Expand Up @@ -276,6 +277,8 @@ export default {
unionLeaderSymbol: 'Union Leader Symbol',
unionLeaderSymbolSize: 'Union Leader Symbol Size',
unionLeaderUnderline: 'Underline Union Leader Name',
occupation: 'Occupation',
occupationColor: 'Occupation Color',
countryNames: 'Names',
countryNamesType: 'Name Type',
countryNamesMinSize: 'Name Min Size',
Expand Down Expand Up @@ -408,4 +411,8 @@ export default {
at_peace: 'At Peace',
},
},
legend: {
fully_occupied: 'Fully Occupied',
partially_occupied: 'Partially Occupied',
},
};
50 changes: 32 additions & 18 deletions src/renderer/src/lib/GameState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,38 @@ const localizedTextSchemaNoDefault: z.ZodType<LocalizedText> = z.object({
});
const localizedTextSchema = localizedTextSchemaNoDefault.default({ key: 'UNKNOWN' });

const galacticObjectSchema = z.object({
name: localizedTextSchema,
coordinate: z.object({
x: z.number(),
y: z.number(),
}),
starbases: preprocessedArray(z.number()),
hyperlane: preprocessedArray(z.object({ to: z.number(), length: z.number() })),
megastructures: preprocessedArray(z.number()),
colonies: preprocessedArray(z.number()),
bypasses: preprocessedArray(z.number()),
flags: z
.record(
z.string(),
z.union([z.number().optional(), z.object({ flag_date: z.number(), flag_days: z.number() })]),
)
.optional(),
});
const galacticObjectSchema = z
.object({
name: localizedTextSchema,
coordinate: z.object({
x: z.number(),
y: z.number(),
}),
starbases: preprocessedArray(z.number()),
hyperlane: preprocessedArray(z.object({ to: z.number(), length: z.number() })),
megastructures: preprocessedArray(z.number()),
colonies: preprocessedArray(z.number()),
bypasses: preprocessedArray(z.number()),
flags: z
.record(
z.string(),
z.union([
z.number().optional(),
z.object({ flag_date: z.number(), flag_days: z.number() }),
]),
)
.optional(),
planet: z.number().optional(),
$multiKeys: z
.object({
planet: preprocessedArray(z.number()).optional(),
})
.optional(),
})
.transform((obj) => ({
...obj,
planet: (obj.planet == null ? [] : [obj.planet]).concat(obj.$multiKeys?.planet ?? []),
}));

/**
* @public
Expand Down
12 changes: 12 additions & 0 deletions src/renderer/src/lib/map/CountryBorders.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,17 @@
{filter}
/>
</Glow>

{#if $mapSettings.occupation}
{#each data.occupationBorders.filter((b) => b.occupied === border.countryId) as occupationBorder}
<path
d={occupationBorder.path}
fill="url(#pattern-${occupationBorder.partial
? 'partial'
: 'full'}-occupation-{occupationBorder.occupier}-on-{occupationBorder.occupied}"
clip-path={`url(#border-${border.countryId}-inner-clip-path)`}
/>
{/each}
{/if}
{/each}
{/if}
26 changes: 25 additions & 1 deletion src/renderer/src/lib/map/Legend.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
getStrokeAttributes,
getStrokeColorAttributes,
} from './mapUtils';
import OccupationPatternDefs from './OccupationPatternDefs.svelte';
export let data: null | MapData;
export let colors: null | Record<string, string>;
Expand All @@ -28,6 +29,11 @@

{#if data?.legend.items.length && $mapSettings.legend}
<svg viewBox="0 0 {width} {height}" width="{width}px" height="{height}px">
<defs>
{#if colors}
<OccupationPatternDefs {colors} {data} />
{/if}
</defs>
<rect
x={borderWidth / 2}
y={borderWidth / 2}
Expand Down Expand Up @@ -71,11 +77,29 @@
})}
/>
{/if}
{#if item.symbol.type === 'pattern'}
<rect width={symbolSize} height={symbolSize} fill="url(#{item.symbol.pattern})" />
{/if}
{#if item.symbol.type === 'hr'}
<line
x1="0"
x2={width - borderWidth - padding * 2}
y1={symbolSize / 2}
y2={symbolSize / 2}
{...getStrokeAttributes($mapSettings.legendBorderStroke)}
stroke-width="1"
{...getStrokeColorAttributes({
mapSettings: $mapSettings,
colorStack: [$mapSettings.legendBorderColor],
colors: colors ?? {},
})}
/>
{/if}
<text
fill="#FFFFFF"
x={symbolSize + symbolLabelGap}
y={symbolSize / 2}
dominant-baseline="middle"
dominant-baseline="central"
font-size={$mapSettings.legendFontSize}
>
{item.label}
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/lib/map/Map.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import CountryLabels from './CountryLabels.svelte';
import Hyperlanes from './Hyperlanes.svelte';
import Icons from './Icons.svelte';
import OccupationPatternDefs from './OccupationPatternDefs.svelte';
import SystemIcons from './SystemIcons.svelte';
import TerraIncognita from './TerraIncognita.svelte';
import TerraIncognitaDefs from './TerraIncognitaDefs.svelte';
Expand Down Expand Up @@ -41,6 +42,7 @@
<use href="#border-{border.countryId}-outer" />
</clipPath>
<TerraIncognitaDefs />
<OccupationPatternDefs colors={colors ?? {}} {data} />
<filter
id="glow"
filterUnits="objectBoundingBox"
Expand Down
83 changes: 83 additions & 0 deletions src/renderer/src/lib/map/OccupationPatternDefs.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<script lang="ts">
import { mapSettings } from '../settings';
import type { MapData } from './data/processMapData';
import { getFillColorAttributes } from './mapUtils';
export let data: MapData;
export let colors: Record<string, string>;
$: fullOccupiers = data.occupationBorders.filter((b) => !b.partial).map((b) => b.occupier);
$: partialOccupiers = new Set(
data.occupationBorders.filter((b) => b.partial).map((b) => b.occupier),
);
</script>

<pattern
id="pattern-full-occupier"
viewBox="0,0,10,20"
preserveAspectRatio="none"
width="10"
height="5"
patternUnits="userSpaceOnUse"
patternTransform="rotate(-45)"
>
<rect fill="white" width="10" height="6" />
</pattern>
<pattern
id="pattern-partial-occupier"
viewBox="0,0,10,20"
preserveAspectRatio="none"
width="10"
height="5"
patternUnits="userSpaceOnUse"
patternTransform="rotate(-45)"
>
<rect fill="white" width="10" height="3" />
</pattern>

{#each data.occupationBorders as occupation}
<pattern
id="pattern-${occupation.partial
? 'partial'
: 'full'}-occupation-{occupation.occupier}-on-{occupation.occupied}"
viewBox="0,0,10,20"
preserveAspectRatio="none"
width="10"
height="5"
patternUnits="userSpaceOnUse"
patternTransform="rotate(-45)"
>
<rect
{...getFillColorAttributes({
mapSettings: $mapSettings,
colors,
countryColors: [occupation, data.borders.find((b) => b.countryId === occupation.occupied)],
colorStack: [$mapSettings.occupationColor, $mapSettings.borderFillColor],
})}
width="10"
height={occupation.partial ? 3 : 6}
/>
</pattern>
{/each}
<!-- {#each partialOccupiers as occupier}
<pattern
id="pattern-partial-occupier-{occupier}-on-{occupied}"
viewBox="0,0,10,20"
preserveAspectRatio="none"
width="5"
height="5"
patternUnits="userSpaceOnUse"
patternTransform="rotate(-45)"
>
<rect
{...getFillColorAttributes({
mapSettings: $mapSettings,
colors,
countryColors: data.occupationBorders.find((b) => b.occupier === occupier),
colorStack: [$mapSettings.borderColor, $mapSettings.borderFillColor],
})}
width="10"
height="3"
/>
</pattern>
{/each} -->
48 changes: 42 additions & 6 deletions src/renderer/src/lib/map/data/processLegend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,19 @@ import type processBorders from './processBorders';
import { getTextAspectRatio } from './utils';

interface LegendItem {
symbol: {
type: 'border';
primaryColor: string;
secondaryColor: string;
};
symbol:
| {
type: 'border';
primaryColor: string;
secondaryColor: string;
}
| {
type: 'pattern';
pattern: string;
}
| {
type: 'hr';
};
label: string;
}

Expand All @@ -24,6 +32,7 @@ export const processLegendDeps = [
'mapMode',
'mapModePointOfView',
'legendFontSize',
'occupation',
] satisfies (keyof MapSettings)[];

export default function processLegend(
Expand All @@ -50,11 +59,38 @@ export default function processLegend(
secondaryColor: mapModeCountry.secondaryColor ?? mapModeCountry.primaryColor,
},
}));
const items = [...mapModeLegendItems];
const occupationLegendItems: LegendItem[] = settings.occupation
? [
{
label: get(t)('legend.fully_occupied'),
symbol: {
type: 'pattern',
pattern: 'pattern-full-occupier',
},
},
{
label: get(t)('legend.partially_occupied'),
symbol: {
type: 'pattern',
pattern: 'pattern-partial-occupier',
},
},
]
: [];
const items = insertHrBetweenGroups([mapModeLegendItems, occupationLegendItems]);
return {
items,
maxLabelWidth: Math.max(
...items.map((item) => settings.legendFontSize / getTextAspectRatio(item.label, 'system-ui')),
),
};
}

function insertHrBetweenGroups(groups: LegendItem[][]): LegendItem[] {
return groups
.filter((group) => group.length)
.map<LegendItem[]>((group, i) =>
i === 0 ? group : [{ label: '', symbol: { type: 'hr' } }, ...group],
)
.flat();
}
23 changes: 22 additions & 1 deletion src/renderer/src/lib/map/data/processMapData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import processHyperRelays from './processHyperRelays';
import processLabels, { processLabelsDeps } from './processLabels';
import processLegend, { processLegendDeps } from './processLegend';
import processNames from './processNames';
import processOccupationBorders, { processOccupationBordersDeps } from './processOccupationBorders';
import processPolygons, { processPolygonsDeps } from './processPolygons';
import processSystemCoordinates, { processSystemCoordinatesDeps } from './processSystemCoordinates';
import processSystemOwnership, { processSystemOwnershipDeps } from './processSystemOwnership';
Expand Down Expand Up @@ -76,6 +77,8 @@ export default async function processMapData(
systemIdToUnionLeader,
sectorToCountry,
unionLeaderToSectors,
fullOccupiedOccupierToSystemIds,
partialOccupiedOccupierToSystemIds,
} = timeIt(
'system ownership',
cached(processSystemOwnership),
Expand All @@ -93,7 +96,14 @@ export default async function processMapData(
galaxyBorderCircles,
);

const { sectorToGeojson, countryToGeojson, unionLeaderToGeojson, terraIncognitaGeojson } = timeIt(
const {
sectorToGeojson,
countryToGeojson,
unionLeaderToGeojson,
terraIncognitaGeojson,
fullOccupiedOccupierToGeojson,
partialOccupiedOccupierToGeojson,
} = timeIt(
'polygons',
cached(processPolygons),
gameState,
Expand All @@ -106,6 +116,8 @@ export default async function processMapData(
unionLeaderToSectors,
sectorToCountry,
knownSystems,
fullOccupiedOccupierToSystemIds,
partialOccupiedOccupierToSystemIds,
);

const { terraIncognitaPath } = timeIt(
Expand Down Expand Up @@ -147,6 +159,14 @@ export default async function processMapData(
galaxyBorderCirclesGeoJSON,
getSystemCoordinates,
);
const occupationBorders = timeIt(
'borders',
cached(processOccupationBorders),
gameState,
pickSettings(settings, processOccupationBordersDeps),
fullOccupiedOccupierToGeojson,
partialOccupiedOccupierToGeojson,
);
const { countryNames, systemNames } = await namesPromise;
const labels = timeIt(
'labels',
Expand Down Expand Up @@ -192,6 +212,7 @@ export default async function processMapData(

return {
borders,
occupationBorders,
unownedHyperlanesPath,
unownedRelayHyperlanesPath,
emblems,
Expand Down
Loading

0 comments on commit 24cd624

Please sign in to comment.