diff --git a/client/apps/game/src/three/GameRenderer.ts b/client/apps/game/src/three/GameRenderer.ts index 4c6fbd6185..8c5dd1fc80 100644 --- a/client/apps/game/src/three/GameRenderer.ts +++ b/client/apps/game/src/three/GameRenderer.ts @@ -1,7 +1,7 @@ import { SetupResult } from "@/dojo/setup"; import useUIStore, { AppStore } from "@/hooks/store/useUIStore"; import { SceneName } from "@/types"; -import { GRAPHICS_SETTING, GraphicsSettings } from "@/ui/config"; +import { GRAPHICS_SETTING, GraphicsSettings, IS_FLAT_MODE } from "@/ui/config"; import throttle from "lodash/throttle"; import { BloomEffect, @@ -87,7 +87,7 @@ export default class GameRenderer { this.state = state; }); - this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 30); + this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, IS_FLAT_MODE ? 50 : 30); const cameraHeight = Math.sin(this.cameraAngle) * this.cameraDistance; const cameraDepth = Math.cos(this.cameraAngle) * this.cameraDistance; this.camera.position.set(0, cameraHeight, cameraDepth); diff --git a/client/apps/game/src/three/helpers/GUIManager.ts b/client/apps/game/src/three/helpers/GUIManager.ts index adfc4dd5dd..d9156ff5cf 100644 --- a/client/apps/game/src/three/helpers/GUIManager.ts +++ b/client/apps/game/src/three/helpers/GUIManager.ts @@ -3,7 +3,7 @@ import GUI from "lil-gui"; import { env } from "../../../env"; export const GUIManager = new GUI({ - autoPlace: env.VITE_PUBLIC_DEV == true && !IS_MOBILE, + autoPlace: env.VITE_PUBLIC_GRAPHICS_DEV == true && !IS_MOBILE, }); GUIManager.close(); diff --git a/client/apps/game/src/three/scenes/HexagonScene.ts b/client/apps/game/src/three/scenes/HexagonScene.ts index 61c616ce12..4ef880b65b 100644 --- a/client/apps/game/src/three/scenes/HexagonScene.ts +++ b/client/apps/game/src/three/scenes/HexagonScene.ts @@ -1,6 +1,7 @@ import { type SetupResult } from "@/dojo/setup"; import useUIStore, { type AppStore } from "@/hooks/store/useUIStore"; import { type HexPosition, type SceneName } from "@/types"; +import { GRAPHICS_SETTING, GraphicsSettings, IS_FLAT_MODE } from "@/ui/config"; import { LeftView } from "@/ui/modules/navigation/LeftNavigationModule"; import { RightView } from "@/ui/modules/navigation/RightNavigationModule"; import { getWorldPositionForHex } from "@/ui/utils/utils"; @@ -66,7 +67,9 @@ export abstract class HexagonScene { this.scene.background = new THREE.Color(0x8790a1); this.state = useUIStore.getState(); this.fog = new THREE.Fog(0xffffff, 21, 30); - this.scene.fog = this.fog; + if (!IS_FLAT_MODE && GRAPHICS_SETTING === GraphicsSettings.HIGH) { + this.scene.fog = this.fog; + } // subscribe to state changes useUIStore.subscribe( @@ -311,19 +314,22 @@ export abstract class HexagonScene { public moveCameraToXYZ(x: number, y: number, z: number, duration: number = 2) { const newTarget = new THREE.Vector3(x, y, z); - const target = this.controls.target; const pos = this.controls.object.position; - - // go to new target but keep same view angle const deltaX = newTarget.x - target.x; const deltaZ = newTarget.z - target.z; + + const newPosition = IS_FLAT_MODE + ? new THREE.Vector3(newTarget.x, pos.y, newTarget.z) + : new THREE.Vector3(pos.x + deltaX, pos.y, pos.z + deltaZ); + if (duration) { - this.cameraAnimate(new THREE.Vector3(pos.x + deltaX, pos.y, pos.z + deltaZ), newTarget, duration); + this.cameraAnimate(newPosition, newTarget, duration); } else { - target.set(newTarget.x, newTarget.y, newTarget.z); - pos.set(pos.x + deltaX, pos.y, pos.z + deltaZ); + target.copy(newTarget); + pos.copy(newPosition); } + this.controls.update(); } @@ -338,11 +344,16 @@ export abstract class HexagonScene { // go to new target with but keep same view angle const deltaX = newTarget.x - target.x; const deltaZ = newTarget.z - target.z; + + const newPosition = IS_FLAT_MODE + ? new THREE.Vector3(newTarget.x, pos.y, newTarget.z) + : new THREE.Vector3(pos.x + deltaX, pos.y, pos.z + deltaZ); + if (duration) { - this.cameraAnimate(new THREE.Vector3(pos.x + deltaX, pos.y, pos.z + deltaZ), newTarget, duration); + this.cameraAnimate(newPosition, newTarget, duration); } else { - target.set(newTarget.x, newTarget.y, newTarget.z); - pos.set(pos.x + deltaX, pos.y, pos.z + deltaZ); + target.copy(newTarget); + pos.copy(newPosition); } this.controls.update(); } diff --git a/client/apps/game/src/three/scenes/Hexception.tsx b/client/apps/game/src/three/scenes/Hexception.tsx index b5e252e0ea..702c2bb822 100644 --- a/client/apps/game/src/three/scenes/Hexception.tsx +++ b/client/apps/game/src/three/scenes/Hexception.tsx @@ -5,6 +5,7 @@ import { SetupResult } from "@/dojo/setup"; import useUIStore from "@/hooks/store/useUIStore"; import { HexPosition, ResourceMiningTypes, SceneName } from "@/types"; import { Position } from "@/types/Position"; +import { IS_FLAT_MODE } from "@/ui/config"; import { ResourceIcon } from "@/ui/elements/ResourceIcon"; import { LeftView } from "@/ui/modules/navigation/LeftNavigationModule"; import { @@ -271,7 +272,7 @@ export default class HexceptionScene extends HexagonScene { this.removeCastleFromScene(); this.updateHexceptionGrid(this.hexceptionRadius); - this.controls.maxDistance = 18; + this.controls.maxDistance = IS_FLAT_MODE ? 36 : 18; this.controls.enablePan = false; this.controls.zoomToCursor = false; @@ -540,6 +541,7 @@ export default class HexceptionScene extends HexagonScene { this.pillars!.setColorAt(index + pillarOffset, BIOME_COLORS[biome as BiomeType]); }); pillarOffset += matrices.length; + this.pillars!.position.y = -0.01; this.pillars!.count = pillarOffset; this.pillars!.computeBoundingSphere(); hexMesh.setCount(matrices.length); @@ -670,12 +672,16 @@ export default class HexceptionScene extends HexagonScene { positions.forEach((position) => { dummy.position.x = position.x; dummy.position.z = position.z; - dummy.position.y = isMainHex || isFlat || position.isBorder ? 0 : position.y / 2; + dummy.position.y = isMainHex || isFlat || position.isBorder || IS_FLAT_MODE ? 0 : position.y / 2; dummy.scale.set(HEX_SIZE, HEX_SIZE, HEX_SIZE); const rotationSeed = this.hashCoordinates(position.col, position.row); const rotationIndex = Math.floor(rotationSeed * 6); const randomRotation = (rotationIndex * Math.PI) / 3; - dummy.rotation.y = randomRotation; + if (!IS_FLAT_MODE) { + dummy.rotation.y = randomRotation; + } else { + dummy.rotation.y = 0; + } dummy.updateMatrix(); biomeHexes[biome].push(dummy.matrix.clone()); }); diff --git a/client/apps/game/src/three/scenes/Worldmap.ts b/client/apps/game/src/three/scenes/Worldmap.ts index 734aa3eec0..b0b5710a8f 100644 --- a/client/apps/game/src/three/scenes/Worldmap.ts +++ b/client/apps/game/src/three/scenes/Worldmap.ts @@ -6,7 +6,7 @@ import { LoadingStateKey } from "@/hooks/store/useWorldLoading"; import { soundSelector } from "@/hooks/useUISound"; import { HexPosition, SceneName } from "@/types"; import { Position } from "@/types/Position"; -import { FELT_CENTER, IS_MOBILE } from "@/ui/config"; +import { FELT_CENTER, IS_FLAT_MODE, IS_MOBILE } from "@/ui/config"; import { UNDEFINED_STRUCTURE_ENTITY_ID } from "@/ui/constants"; import { LeftView } from "@/ui/modules/navigation/LeftNavigationModule"; import { getWorldPositionForHex } from "@/ui/utils/utils"; @@ -369,7 +369,7 @@ export default class WorldmapScene extends HexagonScene { } setup() { - this.controls.maxDistance = 20; + this.controls.maxDistance = IS_FLAT_MODE ? 40 : 20; this.controls.enablePan = true; this.controls.zoomToCursor = true; this.moveCameraToURLLocation(); @@ -428,10 +428,15 @@ export default class WorldmapScene extends HexagonScene { dummy.scale.set(HEX_SIZE, HEX_SIZE, HEX_SIZE); } - const rotationSeed = this.hashCoordinates(col, row); - const rotationIndex = Math.floor(rotationSeed * 6); - const randomRotation = (rotationIndex * Math.PI) / 3; - dummy.rotation.y = randomRotation; + if (!IS_FLAT_MODE) { + const rotationSeed = this.hashCoordinates(col, row); + const rotationIndex = Math.floor(rotationSeed * 6); + const randomRotation = (rotationIndex * Math.PI) / 3; + dummy.rotation.y = randomRotation; + } else { + dummy.position.y += 0.1; + dummy.rotation.y = 0; + } const biomePosition = new Position({ x: col, y: row }).getContract(); const biome = this.biome.getBiome(biomePosition.x, biomePosition.y); @@ -483,6 +488,23 @@ export default class WorldmapScene extends HexagonScene { this.removeCachedMatricesAroundColRow(renderedChunkCenterCol, renderedChunkCenterRow); } + getChunksAround(chunkKey: string) { + const startRow = parseInt(chunkKey.split(",")[0]); + const startCol = parseInt(chunkKey.split(",")[1]); + const chunks: string[] = []; + for (let i = -this.renderChunkSize.width / 2; i <= this.renderChunkSize.width / 2; i += this.chunkSize) { + for (let j = -this.renderChunkSize.width / 2; j <= this.renderChunkSize.height / 2; j += this.chunkSize) { + const { x, y, z } = getWorldPositionForHex({ row: startRow + i, col: startCol + j }); + const { chunkX, chunkZ } = this.worldToChunkCoordinates(x, z); + const _chunkKey = `${chunkZ * this.chunkSize},${chunkX * this.chunkSize}`; + if (!chunks.includes(_chunkKey)) { + chunks.push(_chunkKey); + } + } + } + return chunks; + } + removeCachedMatricesAroundColRow(col: number, row: number) { for (let i = -this.renderChunkSize.width / 2; i <= this.renderChunkSize.width / 2; i += 10) { for (let j = -this.renderChunkSize.width / 2; j <= this.renderChunkSize.height / 2; j += 10) { @@ -547,6 +569,7 @@ export default class WorldmapScene extends HexagonScene { async updateHexagonGrid(startRow: number, startCol: number, rows: number, cols: number) { await Promise.all(this.modelLoadPromises); if (this.applyCachedMatricesForChunk(startRow, startCol)) { + console.log("cache applied"); this.computeInteractiveHexes(startRow, startCol, rows, cols); return; } @@ -578,6 +601,12 @@ export default class WorldmapScene extends HexagonScene { let currentIndex = 0; let hashedTiles: string[] = []; + this.computeTileEntities(this.currentChunk); + + // this.getChunksAround(this.currentChunk).forEach((chunkKey) => { + // console.log("chunkKey", chunkKey); + // this.computeTileEntities(chunkKey); + // }); const processBatch = async () => { const endIndex = Math.min(currentIndex + batchSize, rows * cols); @@ -620,7 +649,12 @@ export default class WorldmapScene extends HexagonScene { const rotationSeed = this.hashCoordinates(startCol + col, startRow + row); const rotationIndex = Math.floor(rotationSeed * 6); const randomRotation = (rotationIndex * Math.PI) / 3; - dummy.rotation.y = randomRotation; + if (!IS_FLAT_MODE) { + dummy.rotation.y = randomRotation; + } else { + dummy.position.y += 0.1; + dummy.rotation.y = 0; + } const biome = this.biome.getBiome(startCol + col + FELT_CENTER, startRow + row + FELT_CENTER); @@ -648,8 +682,6 @@ export default class WorldmapScene extends HexagonScene { } this.cacheMatricesForChunk(startRow, startCol); this.interactiveHexManager.renderHexes(); - - await this.computeTileEntities(); } }; @@ -658,26 +690,15 @@ export default class WorldmapScene extends HexagonScene { }); } - private async computeTileEntities() { - const cameraPosition = new THREE.Vector3(); - cameraPosition.copy(this.controls.target); + private async computeTileEntities(chunkKey: string) { + const startCol = parseInt(chunkKey.split(",")[1]) + FELT_CENTER; + const startRow = parseInt(chunkKey.split(",")[0]) + FELT_CENTER; - const adjustedX = cameraPosition.x + (this.chunkSize * HEX_SIZE * Math.sqrt(3)) / 2; - const adjustedZ = cameraPosition.z + (this.chunkSize * HEX_SIZE * 1.5) / 3; - - // Parse current chunk coordinates - const { chunkX, chunkZ } = this.worldToChunkCoordinates(adjustedX, adjustedZ); - - const startCol = chunkX * this.chunkSize + FELT_CENTER; - const startRow = chunkZ * this.chunkSize + FELT_CENTER; + //const range = this.chunkSize / 2; const { width } = this.renderChunkSize; const range = width / 2; - // Create a unique key for this chunk range - const chunkKey = `${startCol - range},${startCol + range},${startRow - range},${startRow + range}`; - console.log({ chunkKey }); - // Skip if we've already fetched this chunk if (this.fetchedChunks.has(chunkKey)) { console.log("Already fetched"); diff --git a/client/apps/game/src/three/scenes/constants.ts b/client/apps/game/src/three/scenes/constants.ts index a1dc21b81e..b04e15741d 100644 --- a/client/apps/game/src/three/scenes/constants.ts +++ b/client/apps/game/src/three/scenes/constants.ts @@ -1,4 +1,5 @@ import { HyperstructureTypesNames, ResourceMiningTypes } from "@/types"; +import { IS_FLAT_MODE } from "@/ui/config"; import { BuildingType, RealmLevelNames, RealmLevels, ResourcesIds, StructureType } from "@bibliothecadao/eternum"; import * as THREE from "three"; import { BiomeType } from "../components/Biome"; @@ -76,24 +77,47 @@ export const buildingModelPaths: Record< }; const BASE_PATH = "/models/biomes-opt/"; +const FLAT_PATH = "/models/biomes-flat-opt/"; +const MODELS_PATH = IS_FLAT_MODE ? FLAT_PATH : BASE_PATH; + +export enum BiomeFilenames { + Bare = "bare.glb", + Beach = "beach.glb", + TemperateDeciduousForest = "deciduousForest.glb", + DeepOcean = "deepOcean.glb", + Grassland = "grassland.glb", + Ocean = "ocean.glb", + Outline = "outline.glb", + Scorched = "scorched.glb", + Tundra = "tundra.glb", + TemperateDesert = "temperateDesert.glb", + Shrubland = "shrubland.glb", + Snow = "snow.glb", + Taiga = "taiga.glb", + TemperateRainForest = "temperateRainforest.glb", + SubtropicalDesert = "subtropicalDesert.glb", + TropicalRainForest = "tropicalRainforest.glb", + TropicalSeasonalForest = "tropicalSeasonalForest.glb", +} + export const biomeModelPaths: Record = { - Bare: BASE_PATH + "bare.glb", - Beach: BASE_PATH + "beach.glb", - TemperateDeciduousForest: BASE_PATH + "deciduousForest.glb", - DeepOcean: BASE_PATH + "deepOcean.glb", - Grassland: BASE_PATH + "grassland.glb", - Ocean: BASE_PATH + "ocean.glb", - Outline: BASE_PATH + "outline.glb", - Scorched: BASE_PATH + "scorched.glb", - Tundra: BASE_PATH + "tundra.glb", - TemperateDesert: BASE_PATH + "temperateDesert.glb", - Shrubland: BASE_PATH + "shrubland.glb", - Snow: BASE_PATH + "snow.glb", - Taiga: BASE_PATH + "taiga.glb", - TemperateRainForest: BASE_PATH + "temperateRainforest.glb", - SubtropicalDesert: BASE_PATH + "subtropicalDesert.glb", - TropicalRainForest: BASE_PATH + "tropicalRainforest.glb", - TropicalSeasonalForest: BASE_PATH + "tropicalSeasonalForest.glb", + Bare: MODELS_PATH + BiomeFilenames.Bare, + Beach: MODELS_PATH + BiomeFilenames.Beach, + TemperateDeciduousForest: MODELS_PATH + BiomeFilenames.TemperateDeciduousForest, + DeepOcean: MODELS_PATH + BiomeFilenames.DeepOcean, + Grassland: MODELS_PATH + BiomeFilenames.Grassland, + Ocean: MODELS_PATH + BiomeFilenames.Ocean, + Outline: BASE_PATH + BiomeFilenames.Outline, + Scorched: MODELS_PATH + BiomeFilenames.Scorched, + Tundra: MODELS_PATH + BiomeFilenames.Tundra, + TemperateDesert: MODELS_PATH + BiomeFilenames.TemperateDesert, + Shrubland: MODELS_PATH + BiomeFilenames.Shrubland, + Snow: MODELS_PATH + BiomeFilenames.Snow, + Taiga: MODELS_PATH + BiomeFilenames.Taiga, + TemperateRainForest: MODELS_PATH + BiomeFilenames.TemperateRainForest, + SubtropicalDesert: MODELS_PATH + BiomeFilenames.SubtropicalDesert, + TropicalRainForest: MODELS_PATH + BiomeFilenames.TropicalRainForest, + TropicalSeasonalForest: MODELS_PATH + BiomeFilenames.TropicalSeasonalForest, }; export const PROGRESS_HALF_THRESHOLD = 50; diff --git a/client/apps/game/src/ui/config.tsx b/client/apps/game/src/ui/config.tsx index f51a3d5593..a22f0d924a 100644 --- a/client/apps/game/src/ui/config.tsx +++ b/client/apps/game/src/ui/config.tsx @@ -6,7 +6,7 @@ export { FELT_CENTER }; export enum GraphicsSettings { LOW = "LOW", MID = "MID", - HIGH = "HIGH" + HIGH = "HIGH", } const checkGraphicsSettings = async () => { @@ -40,10 +40,16 @@ const checkGraphicsSettings = async () => { } } - return localStorage.getItem("GRAPHICS_SETTING") as GraphicsSettings || GraphicsSettings.HIGH; + return (localStorage.getItem("GRAPHICS_SETTING") as GraphicsSettings) || GraphicsSettings.HIGH; +}; + +const getFlatMode = () => { + const flatMode = localStorage.getItem("FLAT_MODE"); + return flatMode === null ? false : flatMode === "true"; }; export const GRAPHICS_SETTING = await checkGraphicsSettings(); +export const IS_FLAT_MODE = getFlatMode(); export const IS_MOBILE = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); diff --git a/client/apps/game/src/ui/modules/settings/Settings.tsx b/client/apps/game/src/ui/modules/settings/Settings.tsx index 90f532b425..285dfa0678 100644 --- a/client/apps/game/src/ui/modules/settings/Settings.tsx +++ b/client/apps/game/src/ui/modules/settings/Settings.tsx @@ -12,7 +12,7 @@ import { useMusicPlayer } from "@/hooks/useMusic"; import useScreenOrientation from "@/hooks/useScreenOrientation"; import { settings } from "@/ui/components/navigation/Config"; import { OSWindow } from "@/ui/components/navigation/OSWindow"; -import { GraphicsSettings } from "@/ui/config"; +import { GraphicsSettings, IS_FLAT_MODE } from "@/ui/config"; import Avatar from "@/ui/elements/Avatar"; import Button from "@/ui/elements/Button"; import { Checkbox } from "@/ui/elements/Checkbox"; @@ -73,6 +73,39 @@ export const SettingsWindow = () => { const GRAPHICS_SETTING = (localStorage.getItem("GRAPHICS_SETTING") as GraphicsSettings) || GraphicsSettings.HIGH; + const { useGuildQuery } = useGuilds(); + const { guilds } = useGuildQuery(); + const [selectedGuilds, setSelectedGuilds] = useState(() => { + const savedGuilds = localStorage.getItem("WHITELIST"); + return savedGuilds ? savedGuilds.split(",") : []; + }); + + const handleGuildSelect = (guildId: string) => { + setSelectedGuilds((prev) => { + const newGuilds = prev.includes(guildId) ? prev.filter((id) => id !== guildId) : [...prev, guildId]; + localStorage.setItem("WHITELIST", newGuilds.join(",")); + toast(prev.includes(guildId) ? "Guild removed from whitelist!" : "Guild added to whitelist!"); + return newGuilds; + }); + }; + + const handleClearGuilds = () => { + setSelectedGuilds([]); + localStorage.removeItem("WHITELIST"); + toast("Guild whitelist cleared!"); + }; + + const [isFlatMode, setIsFlatMode] = useState(() => IS_FLAT_MODE); + + const toggleFlatMode = () => { + setIsFlatMode((prev) => { + const newFlatMode = !prev; + localStorage.setItem("FLAT_MODE", newFlatMode.toString()); + window.location.reload(); // Reload the page to apply changes + return newFlatMode; + }); + }; + return ( togglePopup(settings)} show={isOpen} title={settings}>
@@ -134,8 +167,32 @@ export const SettingsWindow = () => { High
- Sound +
+ +
Flat Mode
+
+
+
Whitelist guilds
+
+ {guilds.map((guild) => ( + + ))} +
+ {selectedGuilds.length > 0 && ( + + )} +
+ Sound
{isSoundOn ? (