diff --git a/eslint.config.mjs b/eslint.config.mjs index bd1748c9..c57b58a9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -29,7 +29,13 @@ export default [ rules: { "vue/multi-word-component-names": "off", "@typescript-eslint/no-unused-vars": "warn", - "@typescript-eslint/no-unused-expressions": "warn", + "@typescript-eslint/no-unused-expressions": [ + "warn", + { + allowShortCircuit: true, + allowTernary: true, + }, + ], "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-non-null-asserted-optional-chain": "warn", "@typescript-eslint/no-unsafe-function-type": "warn", diff --git a/src/main/content/maps/map-metadata.ts b/src/main/content/maps/map-metadata.ts index d89cbab1..d8ea3348 100644 --- a/src/main/content/maps/map-metadata.ts +++ b/src/main/content/maps/map-metadata.ts @@ -20,8 +20,8 @@ export interface MapMetadata { positions: Record; team?: Team[]; }; - tags: string[]; - terrain: string[]; + tags: GameType[]; + terrain: Terrain[]; tidalStrength?: number; windMax: number; windMin: number; @@ -42,3 +42,50 @@ export interface Team { }[]; teamCount: number; } + +export type Terrain = + | "acidic" + | "alien" + | "ice" + | "lava" + | "space" + | "asteroid" + | "desert" + | "forests" + | "grassy" + | "industrial" + | "jungle" + | "metal" + | "ruins" + | "swamp" + | "tropical" + | "wasteland" + | "shallows" + | "sea" + | "island" + | "water" + | "chokepoints" + | "asymmetrical" + | "flat" + | "hills"; + +export type GameType = + | "1v1" + | "1v1v1" + | "2v2" + | "2v2v2" + | "2v2v2v2" + | "3v3" + | "3v3v3v3" + | "4v4" + | "4v4v4" + | "4v4v4v4" + | "5v5" + | "6v6" + | "6v6v6v6" + | "7v7" + | "7v7v7v7" + | "8v8" + | "8v8v8v8" + | "ffa" + | "pve"; diff --git a/src/renderer/assets/images/icons/terrains/map_acidic.png b/src/renderer/assets/images/icons/terrains/map_acidic.png new file mode 100644 index 00000000..e2a4e31e Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_acidic.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_alien.png b/src/renderer/assets/images/icons/terrains/map_alien.png new file mode 100644 index 00000000..ea326b96 Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_alien.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_asteroid.png b/src/renderer/assets/images/icons/terrains/map_asteroid.png new file mode 100644 index 00000000..c2d3621c Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_asteroid.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_asymmetrical.png b/src/renderer/assets/images/icons/terrains/map_asymmetrical.png new file mode 100644 index 00000000..bd5abf01 Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_asymmetrical.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_chokepoints.png b/src/renderer/assets/images/icons/terrains/map_chokepoints.png new file mode 100644 index 00000000..6841f0e7 Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_chokepoints.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_desert.png b/src/renderer/assets/images/icons/terrains/map_desert.png new file mode 100644 index 00000000..9f150f34 Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_desert.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_flat.png b/src/renderer/assets/images/icons/terrains/map_flat.png new file mode 100644 index 00000000..0e50cb22 Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_flat.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_forests.png b/src/renderer/assets/images/icons/terrains/map_forests.png new file mode 100644 index 00000000..299a5b6b Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_forests.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_grassy.png b/src/renderer/assets/images/icons/terrains/map_grassy.png new file mode 100644 index 00000000..f56ef2e0 Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_grassy.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_hills.png b/src/renderer/assets/images/icons/terrains/map_hills.png new file mode 100644 index 00000000..3eff7dd0 Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_hills.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_ice.png b/src/renderer/assets/images/icons/terrains/map_ice.png new file mode 100644 index 00000000..7e37882c Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_ice.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_industrial.png b/src/renderer/assets/images/icons/terrains/map_industrial.png new file mode 100644 index 00000000..773e6b47 Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_industrial.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_island.png b/src/renderer/assets/images/icons/terrains/map_island.png new file mode 100644 index 00000000..adff8a08 Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_island.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_jungle.png b/src/renderer/assets/images/icons/terrains/map_jungle.png new file mode 100644 index 00000000..85ca8e0c Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_jungle.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_lava.png b/src/renderer/assets/images/icons/terrains/map_lava.png new file mode 100644 index 00000000..6d27417b Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_lava.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_metal.png b/src/renderer/assets/images/icons/terrains/map_metal.png new file mode 100644 index 00000000..3e94981d Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_metal.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_ruins.png b/src/renderer/assets/images/icons/terrains/map_ruins.png new file mode 100644 index 00000000..44d5c781 Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_ruins.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_sea.png b/src/renderer/assets/images/icons/terrains/map_sea.png new file mode 100644 index 00000000..7600f3d2 Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_sea.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_shallows.png b/src/renderer/assets/images/icons/terrains/map_shallows.png new file mode 100644 index 00000000..98fefdb0 Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_shallows.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_space.png b/src/renderer/assets/images/icons/terrains/map_space.png new file mode 100644 index 00000000..ba1eb3d1 Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_space.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_swamp.png b/src/renderer/assets/images/icons/terrains/map_swamp.png new file mode 100644 index 00000000..3736f3ed Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_swamp.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_tropical.png b/src/renderer/assets/images/icons/terrains/map_tropical.png new file mode 100644 index 00000000..8932b81d Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_tropical.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_wasteland.png b/src/renderer/assets/images/icons/terrains/map_wasteland.png new file mode 100644 index 00000000..72704740 Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_wasteland.png differ diff --git a/src/renderer/assets/images/icons/terrains/map_water.png b/src/renderer/assets/images/icons/terrains/map_water.png new file mode 100644 index 00000000..3559e1c4 Binary files /dev/null and b/src/renderer/assets/images/icons/terrains/map_water.png differ diff --git a/src/renderer/components/battle/MapListModal.vue b/src/renderer/components/battle/MapListModal.vue index af4920d7..b858c985 100644 --- a/src/renderer/components/battle/MapListModal.vue +++ b/src/renderer/components/battle/MapListModal.vue @@ -1,12 +1,18 @@ diff --git a/src/renderer/components/controls/Checkbox.vue b/src/renderer/components/controls/Checkbox.vue index b4ed088b..38d2a53c 100644 --- a/src/renderer/components/controls/Checkbox.vue +++ b/src/renderer/components/controls/Checkbox.vue @@ -50,7 +50,7 @@ function onClick() { align-items: center; justify-content: center; min-height: 33px; - max-width: 33px; + max-height: 33px; max-width: 33px; min-width: 33px; } diff --git a/src/renderer/components/controls/Range.vue b/src/renderer/components/controls/Range.vue index 6b0f842c..db5bd29d 100644 --- a/src/renderer/components/controls/Range.vue +++ b/src/renderer/components/controls/Range.vue @@ -1,46 +1,65 @@ @@ -85,7 +104,11 @@ function onInput(input: number | number[]) { background-color: #fff; } } -:deep(.p-inputtext) { +.min :deep(.p-inputtext) { + width: v-bind(minInputWidth); + text-align: center; +} +.max :deep(.p-inputtext) { width: v-bind(maxInputWidth); text-align: center; } @@ -102,5 +125,9 @@ function onInput(input: number | number[]) { top: 0; background: rgba(255, 255, 255, 0.1); } + &.min:before { + left: unset; + right: 0; + } } diff --git a/src/renderer/components/maps/MapFiltersComponent.vue b/src/renderer/components/maps/MapFiltersComponent.vue new file mode 100644 index 00000000..59697b95 --- /dev/null +++ b/src/renderer/components/maps/MapFiltersComponent.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/src/renderer/components/maps/MapListComponent.vue b/src/renderer/components/maps/MapListComponent.vue index 9a97c85f..4713cdd9 100644 --- a/src/renderer/components/maps/MapListComponent.vue +++ b/src/renderer/components/maps/MapListComponent.vue @@ -11,6 +11,10 @@ +
+

No maps found!

+ Please try different keywords / filters +
@@ -25,18 +29,21 @@ * - Easy one click install button * - Demo map button that launches a simple offline game on the map */ - import { Ref, ref } from "vue"; import SearchBox from "@renderer/components/controls/SearchBox.vue"; import Select from "@renderer/components/controls/Select.vue"; import MapOverviewCard from "@renderer/components/maps/MapOverviewCard.vue"; -import { MapData } from "@main/content/maps/map-data"; +import { type MapData } from "@main/content/maps/map-data"; +import type { GameType, Terrain } from "@main/content/maps/map-metadata"; import { db } from "@renderer/store/db"; import { useDexieLiveQueryWithDeps } from "@renderer/composables/useDexieLiveQuery"; +import { mapsStore } from "@renderer/store/maps.store"; import { useInfiniteScroll } from "@vueuse/core"; +const { filters } = mapsStore; + type SortMethod = { label: string; dbKey: string }; const sortMethods: SortMethod[] = [ @@ -58,12 +65,23 @@ useInfiniteScroll( }, { distance: 300, interval: 550 } ); -const maps = useDexieLiveQueryWithDeps([searchVal, sortMethod, limit], () => - db.maps - .filter((map) => map.displayName.toLocaleLowerCase().includes(searchVal.value.toLocaleLowerCase())) + +const maps = useDexieLiveQueryWithDeps([searchVal, sortMethod, limit, filters], () => { + const { terrain, gameType } = filters; + const terrainFilters = new Set([...(Object.keys(terrain)).filter((key) => !!terrain[key]).map((k) => k)]); + const gameTypeFilters = new Set([...(Object.keys(gameType)).filter((key) => gameType[key]).map((k) => k)]); + return db.maps + .filter( + (map) => + map.displayName.toLocaleLowerCase().includes(searchVal.value.toLocaleLowerCase()) && + filters.minPlayers < map.playerCountMax && + filters.maxPlayers > map.playerCountMax && + (terrainFilters.size === 0 || terrainFilters.isSubsetOf(new Set([...map.terrain]))) && + (gameTypeFilters.size === 0 || !gameTypeFilters.isDisjointFrom(new Set([...map.tags]))) + ) .limit(limit.value) - .sortBy(sortMethod.value.dbKey) -); + .sortBy(sortMethod.value.dbKey); +}); function mapSelected(map: MapData) { emit("map-selected", map); diff --git a/src/renderer/components/maps/MapOverviewCard.vue b/src/renderer/components/maps/MapOverviewCard.vue index 9e854e3c..e57d4cbf 100644 --- a/src/renderer/components/maps/MapOverviewCard.vue +++ b/src/renderer/components/maps/MapOverviewCard.vue @@ -4,8 +4,14 @@
{{ map?.displayName }}
-
-
{{ mapSize }}
+
+
{{ mapSize }}
+
+ {{ map?.playerCountMin }} - {{ map?.playerCountMax }} +
+
+
+
@@ -15,6 +21,10 @@ import defaultMiniMap from "/src/renderer/assets/images/default-minimap.png?url" import { computed } from "vue"; import { useImageBlobUrlCache } from "@renderer/composables/useImageBlobUrlCache"; import { MapData } from "@main/content/maps/map-data"; +import TerrainIcon from "@renderer/components/maps/filters/TerrainIcon.vue"; +import { Icon } from "@iconify/vue/dist/iconify.js"; +import personIcon from "@iconify-icons/mdi/person-multiple"; +import gridIcon from "@iconify-icons/mdi/grid"; const props = defineProps<{ map: MapData; @@ -99,13 +109,20 @@ const imageUrl = computed(() => } .attributes { position: absolute; - bottom: 10px; - left: 10px; border: 1px solid rgba(0, 0, 0, 0.1); background: rgba(0, 0, 0, 0.2); font-size: 16px; font-weight: 600; padding: 2px 5px; transition: 0.2s opacity; + + &.bl { + bottom: 10px; + left: 10px; + } + &.br { + bottom: 10px; + right: 10px; + } } diff --git a/src/renderer/components/maps/filters/GameTypeFilter.vue b/src/renderer/components/maps/filters/GameTypeFilter.vue new file mode 100644 index 00000000..7ea5b996 --- /dev/null +++ b/src/renderer/components/maps/filters/GameTypeFilter.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/renderer/components/maps/filters/IconFilterCheckbox.vue b/src/renderer/components/maps/filters/IconFilterCheckbox.vue new file mode 100644 index 00000000..d6918f61 --- /dev/null +++ b/src/renderer/components/maps/filters/IconFilterCheckbox.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/src/renderer/components/maps/filters/MaxPlayersFilter.vue b/src/renderer/components/maps/filters/MaxPlayersFilter.vue new file mode 100644 index 00000000..037ea650 --- /dev/null +++ b/src/renderer/components/maps/filters/MaxPlayersFilter.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/src/renderer/components/maps/filters/TerrainFilter.vue b/src/renderer/components/maps/filters/TerrainFilter.vue new file mode 100644 index 00000000..e0d5c147 --- /dev/null +++ b/src/renderer/components/maps/filters/TerrainFilter.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/renderer/components/maps/filters/TerrainIcon.vue b/src/renderer/components/maps/filters/TerrainIcon.vue new file mode 100644 index 00000000..3c6abece --- /dev/null +++ b/src/renderer/components/maps/filters/TerrainIcon.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/src/renderer/composables/useTerrainIcon.ts b/src/renderer/composables/useTerrainIcon.ts new file mode 100644 index 00000000..ece54a05 --- /dev/null +++ b/src/renderer/composables/useTerrainIcon.ts @@ -0,0 +1,109 @@ +import { computed } from "vue"; + +export function useTerrain(terrain: string) { + return computed(() => terrains[terrain]); +} + +export type TerrainData = { + tooltip: string; + icon: string; +}; + +const terrains: Record = { + acidic: { + tooltip: "Acidic and dangerous waters", + icon: "map_acidic.png", + }, + alien: { + tooltip: "Exotic alient biomes", + icon: "map_alien.png", + }, + asteroid: { + tooltip: "Lunar landscape, with low gravity and (no) wind", + icon: "map_asteroid.png", + }, + asymmetrical: { + tooltip: "Asymmetrical map", + icon: "map_asymmetrical.png", + }, + chokepoints: { + tooltip: "Narrow passages", + icon: "map_chokepoints.png", + }, + desert: { + tooltip: "Rocky or sandy, barren desert", + icon: "map_desert.png", + }, + flat: { + tooltip: "Contains large flat areas", + icon: "map_flat.png", + }, + forests: { + tooltip: "Lots of trees", + icon: "map_forests.png", + }, + grassy: { + tooltip: "Lots of crispy green grass", + icon: "map_grassy.png", + }, + hills: { + tooltip: "Hilly or mountainous terrain", + icon: "map_hills.png", + }, + ice: { + tooltip: "A cold place", + icon: "map_ice.png", + }, + industrial: { + tooltip: "Industrial map with ancient constructions", + icon: "map_industrial.png", + }, + island: { + tooltip: "Island map, land fully surrounded by water", + icon: "map_island.png", + }, + jungle: { + tooltip: "Dense foliage and forests", + icon: "map_jungle.png", + }, + lava: { + tooltip: "Dangerous lava pools", + icon: "map_lava.png", + }, + metal: { + tooltip: "Metal map, where (most of) the surface is made of extractable metal", + icon: "map_metal.png", + }, + ruins: { + tooltip: "Structures or ruins", + icon: "map_ruins.png", + }, + sea: { + tooltip: "Large bodies of water or Sea", + icon: "map_sea.png", + }, + shallows: { + tooltip: "Passable shallow water like creeks, rivers and beaches", + icon: "map_shallows.png", + }, + space: { + tooltip: "In outerspace, usually without wind", + icon: "map_space.png", + }, + swamp: { + tooltip: "Swampy, wet terrain with lots of ponds and foliage", + icon: "map_swamp.png", + }, + tropical: { + tooltip: "Tropical biome with beaches, palms and tropical fish", + icon: "map_tropical.png", + }, + wasteland: { + tooltip: "Forgotten wasteland that hasn't seen life in a long time", + icon: "map_wasteland.png", + }, + water: { + tooltip: "Smaller bodies of water like lakes, ponds or rivers", + icon: "map_water.png", + }, +}; diff --git a/src/renderer/store/maps.store.ts b/src/renderer/store/maps.store.ts index 586f3b5c..cd351acb 100644 --- a/src/renderer/store/maps.store.ts +++ b/src/renderer/store/maps.store.ts @@ -1,11 +1,24 @@ import { reactive } from "vue"; -import { MapData } from "@main/content/maps/map-data"; +import type { MapData } from "@main/content/maps/map-data"; +import type { GameType, Terrain } from "@main/content/maps/map-metadata"; import { db } from "@renderer/store/db"; export const mapsStore = reactive({ isInitialized: false, + filters: { + terrain: {}, + gameType: {}, + minPlayers: 2, + maxPlayers: 40, + }, } as { isInitialized: boolean; + filters: { + terrain: Partial>; + gameType: Partial>; + minPlayers: number; + maxPlayers: number; + }; }); export async function getRandomMap(): Promise { @@ -73,3 +86,7 @@ export async function downloadMap(springName: string) { db.maps.update(springName, { isDownloading: false }); }); } + +export function getFilters() { + return mapsStore.filters; +} diff --git a/src/renderer/views/library/maps/index.vue b/src/renderer/views/library/maps/index.vue index 9cdeb4a7..b0ab8b30 100644 --- a/src/renderer/views/library/maps/index.vue +++ b/src/renderer/views/library/maps/index.vue @@ -4,12 +4,23 @@ @@ -25,17 +36,56 @@ * - Filterable * - Paginated */ - -import { MapData } from "@main/content/maps/map-data"; +/** + * Sort by: + * - name + * - size + * - ideal max player count + * - type + * - terrain + * - downloaded (X) + * - certified (X) + * - date created? + * + * Filter by: + * - name (text search) + * - map size (min and max, as slider?) + * - max players (min and max, as slider?) + * - game type + * - terrain + * - water type + * - general layout + */ +import type { MapData } from "@main/content/maps/map-data"; import Panel from "@renderer/components/common/Panel.vue"; +import MapFiltersComponent from "@renderer/components/maps/MapFiltersComponent.vue"; import MapListComponent from "@renderer/components/maps/MapListComponent.vue"; import { useRouter } from "vue-router"; - const router = useRouter(); - async function onMapSelected(map: MapData) { await router.push(`/library/maps/${map.springName}`); } - + diff --git a/tsconfig.json b/tsconfig.json index 0c844d8e..cc54bade 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,8 +9,9 @@ "sourceMap": true, "baseUrl": ".", "outDir": "dist", - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, + "isolatedModules": true, "paths": { "@main/*": ["src/main/*"], "@preload/*": ["src/preload/*"], diff --git a/vendor/sdfz-demo-parser/index.ts b/vendor/sdfz-demo-parser/index.ts index 896a9f42..11612351 100644 --- a/vendor/sdfz-demo-parser/index.ts +++ b/vendor/sdfz-demo-parser/index.ts @@ -2,4 +2,4 @@ import { DemoParser, DemoParserConfig } from "./demo-parser"; import { DemoModel } from "./model/demo-model"; import { isPacket, isPlayer, isSpec } from "./utils"; -export { DemoModel, DemoParser, DemoParserConfig, isPacket, isPlayer, isSpec }; \ No newline at end of file +export { DemoModel, DemoParser, type DemoParserConfig, isPacket, isPlayer, isSpec }; \ No newline at end of file