diff --git a/assets/Data/game/biomes.json b/assets/Data/game/biomes.json index b6049077..3cc5e25a 100644 --- a/assets/Data/game/biomes.json +++ b/assets/Data/game/biomes.json @@ -51,7 +51,20 @@ ], "surfaceGenerationParams": { "surfaceGenTag": "hills", - "heightOffset": 10 + "heightOffset": 10, + "floorVariants": [ + ], + "foliageDescriptions": [ + { + "entityIDs": [ + "pine2" + ], + "regularity": 0.6, + "threshold": 0.05, + "scale": 0.5, + "priority": 1.0 + } + ] } }, { diff --git a/docs/src/highlevel-design/creatures/creaturesindex.md b/docs/src/highlevel-design/creatures/creaturesindex.md index fc1d5f2c..3d7955aa 100644 --- a/docs/src/highlevel-design/creatures/creaturesindex.md +++ b/docs/src/highlevel-design/creatures/creaturesindex.md @@ -4,4 +4,5 @@ - @subpage creaturetodo - @subpage creatureanimations - @subpage creatureideas - - @subpage creaturemechanicsideas \ No newline at end of file + - @subpage creaturemechanicsideas + - @subpage foliageideas \ No newline at end of file diff --git a/docs/src/highlevel-design/creatures/foliageideas.md b/docs/src/highlevel-design/creatures/foliageideas.md new file mode 100644 index 00000000..f547fb1f --- /dev/null +++ b/docs/src/highlevel-design/creatures/foliageideas.md @@ -0,0 +1,5 @@ +@page foliageideas Foliage Ideas + +"Tree" that shoots up a lantern at night time which sifts through the air to collect bugs for food. +Lantern is a glowing particle. + diff --git a/docs/src/highlevel-design/narrativemanager/narrativearcdesign.md b/docs/src/highlevel-design/narrativemanager/narrativearcdesign.md index 89c48eb6..5b3cdb6b 100644 --- a/docs/src/highlevel-design/narrativemanager/narrativearcdesign.md +++ b/docs/src/highlevel-design/narrativemanager/narrativearcdesign.md @@ -8,4 +8,9 @@ Another challenge to consider is creating teasers for the narrative peaks. IE, if your big bad is supposed to be a dark paladin, how do you tease him in the story without making it a final encounter. - "Visions", "Proxies", etc that aren't the real version - For certain types of creatures (ie dragons), you can have them fly by in a way that wouldn't be easily interacted with - \ No newline at end of file + +Yet another challenge to consider is adding blocks where side quests can become relevant +IE, you hit a quest where you ask someone for a favor (lets say they need to repair something), you now have a timer where side quests can be introduced in the interim +Another idea is having the quest pend on a building being constructed +Another idea is waiting for an npc to move to a given location, ie if a character went off on another task sometime earlier in the quest and you are now waiting to regroup with them + diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 453d52ad..366e3a79 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -941,6 +941,19 @@ Hill Gen tweaks (11/05/2024) More normals fixes for terrain +(11/06/2024) +Fix server caching of terrain +Implement server side striding of chunk data + +(11/07/2024) +Add quarter, eighth, and sixteenth scale chunk gen +Add network pressure capping +Add stratified updates that prioritize game chunks +Add message deduplication +Fix LOD bounding sphere calculation +Hook up content generation to test generation realm +Reorganize biome data + # TODO diff --git a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiPlayerEntity.java b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiPlayerEntity.java index 4c912d63..2fb3a708 100644 --- a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiPlayerEntity.java +++ b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiPlayerEntity.java @@ -115,19 +115,24 @@ public class ImGuiPlayerEntity { //server pos int serverIdForClientEntity = Globals.clientSceneWrapper.mapClientToServerId(Globals.playerEntity.getId()); Entity serverPlayerEntity = EntityLookupUtils.getEntityById(serverIdForClientEntity); - ImGui.text("Position (Server): " + EntityUtils.getPosition(serverPlayerEntity)); - ImGui.text("Rotation (Server): " + EntityUtils.getRotation(serverPlayerEntity)); + if(serverPlayerEntity != null){ + ImGui.text("Position (Server): " + EntityUtils.getPosition(serverPlayerEntity)); + ImGui.text("Rotation (Server): " + EntityUtils.getRotation(serverPlayerEntity)); - //server-side physics stuff - DBody serverBody = PhysicsEntityUtils.getDBody(serverPlayerEntity); - if(serverBody != null){ - ImGui.text("Velocity (Server): " + serverBody.getLinearVel()); - ImGui.text("Force (Server): " + serverBody.getForce()); - ImGui.text("Move Vector (Server): " + CreatureUtils.getFacingVector(serverPlayerEntity)); - ImGui.text("Velocity (Server): " + CreatureUtils.getVelocity(serverPlayerEntity)); + //server-side physics stuff + DBody serverBody = PhysicsEntityUtils.getDBody(serverPlayerEntity); + if(serverBody != null){ + ImGui.text("Velocity (Server): " + serverBody.getLinearVel()); + ImGui.text("Force (Server): " + serverBody.getForce()); + ImGui.text("Move Vector (Server): " + CreatureUtils.getFacingVector(serverPlayerEntity)); + ImGui.text("Velocity (Server): " + CreatureUtils.getVelocity(serverPlayerEntity)); + } + ImGui.text("View yaw (Server): " + ServerPlayerViewDirTree.getTree(serverPlayerEntity).getYaw()); + ImGui.text("View pitch (Server): " + ServerPlayerViewDirTree.getTree(serverPlayerEntity).getPitch()); + } + if(Globals.server != null && Globals.server.getFirstConnection() != null && Globals.server.getFirstConnection().getPlayer() != null){ + ImGui.text("Player Object World Pos (Server): " + Globals.server.getFirstConnection().getPlayer().getWorldPos()); } - ImGui.text("View yaw (Server): " + ServerPlayerViewDirTree.getTree(serverPlayerEntity).getYaw()); - ImGui.text("View pitch (Server): " + ServerPlayerViewDirTree.getTree(serverPlayerEntity).getPitch()); } /** diff --git a/src/main/java/electrosphere/collision/CollisionWorldData.java b/src/main/java/electrosphere/collision/CollisionWorldData.java index ade9da9d..396cb752 100644 --- a/src/main/java/electrosphere/collision/CollisionWorldData.java +++ b/src/main/java/electrosphere/collision/CollisionWorldData.java @@ -76,12 +76,4 @@ public class CollisionWorldData { } } - public float getRandomDampener(){ - if(clientWorldData != null){ - return clientWorldData.getRandomDampener(); - } else { - return serverWorldData.getRandomDampener(); - } - } - } diff --git a/src/main/java/electrosphere/engine/loadingthreads/ChunkGenerationTestLoading.java b/src/main/java/electrosphere/engine/loadingthreads/ChunkGenerationTestLoading.java index 14495156..760d330c 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/ChunkGenerationTestLoading.java +++ b/src/main/java/electrosphere/engine/loadingthreads/ChunkGenerationTestLoading.java @@ -45,6 +45,8 @@ public class ChunkGenerationTestLoading { String saveName = "generation_testing"; + //delete if it exists + SaveUtils.deleteSave(saveName); if(!SaveUtils.getSaves().contains(saveName)){ // //the juicy server GENERATION part diff --git a/src/main/java/electrosphere/entity/ServerEntityUtils.java b/src/main/java/electrosphere/entity/ServerEntityUtils.java index 77cf6409..b3a6aa5a 100644 --- a/src/main/java/electrosphere/entity/ServerEntityUtils.java +++ b/src/main/java/electrosphere/entity/ServerEntityUtils.java @@ -8,6 +8,7 @@ import electrosphere.engine.Globals; import electrosphere.entity.state.attach.AttachUtils; import electrosphere.entity.state.hitbox.HitboxCollectionState; import electrosphere.entity.types.collision.CollisionObjUtils; +import electrosphere.logger.LoggerInterface; import electrosphere.net.parser.net.message.EntityMessage; import electrosphere.server.datacell.Realm; import electrosphere.server.datacell.ServerDataCell; @@ -57,6 +58,9 @@ public class ServerEntityUtils { //if server, get current server data cell ServerDataCell oldDataCell = realm.getDataCellManager().getDataCellAtPoint(EntityUtils.getPosition(entity)); ServerDataCell newDataCell = realm.getDataCellManager().getDataCellAtPoint(position); + if(oldDataCell == null){ + LoggerInterface.loggerEngine.WARNING("Trying to reposition entity on server when it's former position is null!"); + } if(oldDataCell != newDataCell){ if(newDataCell != null){ ServerDataCell.moveEntityFromCellToCell(entity, oldDataCell, newDataCell); diff --git a/src/main/java/electrosphere/entity/scene/SceneLoader.java b/src/main/java/electrosphere/entity/scene/SceneLoader.java index 0b12cc9f..f113f173 100644 --- a/src/main/java/electrosphere/entity/scene/SceneLoader.java +++ b/src/main/java/electrosphere/entity/scene/SceneLoader.java @@ -58,7 +58,10 @@ public class SceneLoader { //Content manager // ServerContentManager serverContentManager = null; - if(file.realmDescriptor.getType() == RealmDescriptor.REALM_DESCRIPTOR_PROCEDURAL){ + if( + file.realmDescriptor.getType().matches(RealmDescriptor.REALM_DESCRIPTOR_PROCEDURAL) || + file.realmDescriptor.getType().matches(RealmDescriptor.REALM_DESCRIPTOR_GENERATION_TESTING) + ){ serverContentManager = ServerContentManager.createServerContentManager(true); } else { serverContentManager = ServerContentManager.createServerContentManager(false); diff --git a/src/main/java/electrosphere/entity/state/movement/editor/ClientEditorMovementTree.java b/src/main/java/electrosphere/entity/state/movement/editor/ClientEditorMovementTree.java index 98a2fde6..8b1b6cc9 100644 --- a/src/main/java/electrosphere/entity/state/movement/editor/ClientEditorMovementTree.java +++ b/src/main/java/electrosphere/entity/state/movement/editor/ClientEditorMovementTree.java @@ -9,6 +9,7 @@ import electrosphere.game.data.creature.type.movement.EditorMovementSystem; import electrosphere.entity.Entity; import electrosphere.entity.EntityDataStrings; import electrosphere.entity.EntityUtils; +import electrosphere.entity.ServerEntityUtils; import electrosphere.entity.btree.BehaviorTree; import electrosphere.entity.state.attack.ClientAttackTree; import electrosphere.entity.state.movement.fall.ClientFallTree; @@ -21,6 +22,7 @@ import electrosphere.net.synchronization.annotation.SynchronizableEnum; import electrosphere.net.synchronization.annotation.SynchronizedBehaviorTree; import electrosphere.net.synchronization.enums.BehaviorTreeIdEnums; import electrosphere.renderer.anim.Animation; +import electrosphere.server.datacell.utils.EntityLookupUtils; import electrosphere.util.math.SpatialMathUtils; import java.util.concurrent.CopyOnWriteArrayList; @@ -194,6 +196,8 @@ public class ClientEditorMovementTree implements BehaviorTree { Vector3d facingVector = CreatureUtils.getFacingVector(parent); float maxNaturalVelocity = EDITOR_MAX_VELOCITY; + Entity serverEntity = EntityLookupUtils.getEntityById(Globals.clientSceneWrapper.mapClientToServerId(parent.getId())); + // //rotation update if(this.state != MovementTreeState.IDLE){ @@ -304,6 +308,9 @@ public class ClientEditorMovementTree implements BehaviorTree { //actually update rotation.set(movementQuaternion); position.set(new Vector3d(position).add(new Vector3d(movementVector).mul(velocity))); + if(serverEntity != null){ + ServerEntityUtils.repositionEntity(serverEntity, new Vector3d(position)); + } GravityUtils.clientAttemptActivateGravity(parent); } break; @@ -316,6 +323,9 @@ public class ClientEditorMovementTree implements BehaviorTree { float velocity = this.getModifiedVelocity(); rotation.set(movementQuaternion); position.set(new Vector3d(position).add(new Vector3d(movementVector).mul(velocity))); + if(serverEntity != null){ + ServerEntityUtils.repositionEntity(serverEntity, new Vector3d(position)); + } GravityUtils.clientAttemptActivateGravity(parent); } break; @@ -334,6 +344,9 @@ public class ClientEditorMovementTree implements BehaviorTree { } rotation.set(movementQuaternion); position.set(new Vector3d(position).add(new Vector3d(movementVector).mul(velocity))); + if(serverEntity != null){ + ServerEntityUtils.repositionEntity(serverEntity, new Vector3d(position)); + } GravityUtils.clientAttemptActivateGravity(parent); } break; diff --git a/src/main/java/electrosphere/game/data/biome/BiomeFoliageDescription.java b/src/main/java/electrosphere/game/data/biome/BiomeFoliageDescription.java index 9d043a9a..58936a26 100644 --- a/src/main/java/electrosphere/game/data/biome/BiomeFoliageDescription.java +++ b/src/main/java/electrosphere/game/data/biome/BiomeFoliageDescription.java @@ -13,18 +13,63 @@ public class BiomeFoliageDescription { List entityIDs; /** - * The frequency of this element in particular + * How regular the placement of foliage is. Low values will create very uneven foliage, while high values will place them along a grid. */ - Double frequency; + Double regularity; /** - * The scale of the noise used to generate this element + * The percentage of the ground to cover with foliage */ - Double dispersion; + Double threshold; /** * The priority of this floor element in particular */ Double priority; + /** + * The scale of the noise used to place foliage + */ + Double scale; + + /** + * Gets the entity ids for this foliage description + * @return The list of entity ids + */ + public List getEntityIDs(){ + return this.entityIDs; + } + + /** + * Gets the regularity of the foliage placement + * @return The regularity + */ + public Double getRegularity(){ + return regularity; + } + + /** + * Gets the percentage of the ground to cover with foliage + * @return The percentage of the ground to cover with foliage + */ + public Double getThreshold(){ + return threshold; + } + + /** + * Gets the priority of this floor element in particular + * @return The priority of this floor element in particular + */ + public Double getPriority(){ + return priority; + } + + /** + * Gets the scale of the noise used to place foliage + * @return The scale of the noise used to place foliage + */ + public Double getScale(){ + return scale; + } + } diff --git a/src/main/java/electrosphere/game/data/biome/BiomeSurfaceGenerationParams.java b/src/main/java/electrosphere/game/data/biome/BiomeSurfaceGenerationParams.java index 705e767d..9c7e0a75 100644 --- a/src/main/java/electrosphere/game/data/biome/BiomeSurfaceGenerationParams.java +++ b/src/main/java/electrosphere/game/data/biome/BiomeSurfaceGenerationParams.java @@ -1,5 +1,7 @@ package electrosphere.game.data.biome; +import java.util.List; + /** * Params for the surface generation algorithm */ @@ -15,6 +17,16 @@ public class BiomeSurfaceGenerationParams { */ Float heightOffset; + /** + * The different floor elements + */ + List floorVariants; + + /** + * The list of foliage descriptions available to this biome type + */ + List foliageDescriptions; + /** * Gets the tag for the generation algorithm for generating the surface * @return The tag for the generation algorithm for generating the surface @@ -31,6 +43,22 @@ public class BiomeSurfaceGenerationParams { return heightOffset; } + /** + * Gets the list of floor variants + * @return The list of floor variants + */ + public List getFloorVariants(){ + return floorVariants; + } + + /** + * Gets the list of foliage descriptions + * @return The list of foliage descriptions + */ + public List getFoliageDescriptions(){ + return foliageDescriptions; + } + } diff --git a/src/main/java/electrosphere/game/server/world/ServerWorldData.java b/src/main/java/electrosphere/game/server/world/ServerWorldData.java index c1440419..192058df 100644 --- a/src/main/java/electrosphere/game/server/world/ServerWorldData.java +++ b/src/main/java/electrosphere/game/server/world/ServerWorldData.java @@ -147,7 +147,7 @@ public class ServerWorldData { ServerTerrainManager serverTerrainManager = null; ServerFluidManager serverFluidManager = null; //TODO: Allow loading procedurally generated terrain from disk (the chunk generator is always default currently) - serverWorldData = ServerWorldData.createFixedWorldData(new Vector3d(0),new Vector3d(16 * 4 * 4)); + serverWorldData = ServerWorldData.createFixedWorldData(new Vector3d(0),new Vector3d(TestGenerationChunkGenerator.GENERATOR_REALM_SIZE * ServerTerrainChunk.CHUNK_DIMENSION)); serverWorldData.worldSizeDiscrete = TestGenerationChunkGenerator.GENERATOR_REALM_SIZE; serverWorldData.worldSizeDiscreteVertical = TestGenerationChunkGenerator.GENERATOR_REALM_SIZE; @@ -171,6 +171,10 @@ public class ServerWorldData { return worldMaxPoint; } + /** + * Gets the discrete size of the world (in chunks) + * @return The discrete size of the world (in chunks) + */ public int getWorldSizeDiscrete() { return worldSizeDiscrete; } @@ -179,10 +183,6 @@ public class ServerWorldData { return dynamicInterpolationRatio; } - public float getRandomDampener() { - return randomDampener; - } - public int convertRealToChunkSpace(double real){ return (int)Math.floor(real / ServerTerrainChunk.CHUNK_DIMENSION); } diff --git a/src/main/java/electrosphere/net/server/player/Player.java b/src/main/java/electrosphere/net/server/player/Player.java index 63b0e9c1..3f9e31e0 100644 --- a/src/main/java/electrosphere/net/server/player/Player.java +++ b/src/main/java/electrosphere/net/server/player/Player.java @@ -13,26 +13,69 @@ import org.joml.Vector3i; * A client logged into the server */ public class Player { - - ServerConnectionHandler connectionHandler; - int id; + + /** + * The default server-side simulation radius in chunks + */ + public static final int DEFAULT_SIMULATION_RADIUS = 3; + + /** + * Id incrementer lock + */ static Semaphore idIncrementerLock = new Semaphore(1); + + /** + * The actual incrementing id counter + */ static int idIncrementer = 0; + + /** + * The corresponding connection handler + */ + ServerConnectionHandler connectionHandler; + + /** + * The id of the player + */ + int id; + + /** + * The world position of this player + */ Vector3i worldPos; - int simulationRadius = 3; + + /** + * The simulation radius of this player + */ + int simulationRadius = DEFAULT_SIMULATION_RADIUS; + + /** + * The player's primary entity + */ Entity playerEntity; + /** + * Constructor + * @param connectionHandler The corresponding connection + */ public Player(ServerConnectionHandler connectionHandler){ this.connectionHandler = connectionHandler; id = connectionHandler.getPlayerId(); this.simulationRadius = Globals.userSettings.getGameplayPhysicsCellRadius(); } - //used for when client is creating a player object for itself + /** + * Used when initing a local connection + * @param id The id of the local connection + */ public Player(int id){ this.id = id; } + /** + * Gets the id of the player + * @return The player's id + */ public int getId() { if(connectionHandler != null){ return this.connectionHandler.getPlayerId(); @@ -40,30 +83,58 @@ public class Player { return id; } + /** + * Adds a message that should be sent to this player + * @param message The message + */ public void addMessage(NetworkMessage message){ connectionHandler.addMessagetoOutgoingQueue(message); } + /** + * Gets the world position of the player + * @return The world position + */ public Vector3i getWorldPos() { return worldPos; } + /** + * Sets the world position of the player + * @param worldPos The world position + */ public void setWorldPos(Vector3i worldPos) { this.worldPos = worldPos; } + /** + * Gets the simulation radius of the player + * @return The simulation radius + */ public int getSimulationRadius() { return simulationRadius; } + /** + * Sets the simulation radius of the player + * @param simulationRadius The simulation radius + */ public void setSimulationRadius(int simulationRadius) { this.simulationRadius = simulationRadius; } + /** + * Gets the player's entity + * @return The player's entity + */ public Entity getPlayerEntity() { return playerEntity; } + /** + * Sets the player's entity + * @param playerEntity The player's entity + */ public void setPlayerEntity(Entity playerEntity) { this.playerEntity = playerEntity; } diff --git a/src/main/java/electrosphere/net/server/protocol/CharacterProtocol.java b/src/main/java/electrosphere/net/server/protocol/CharacterProtocol.java index 69984461..5c49901d 100644 --- a/src/main/java/electrosphere/net/server/protocol/CharacterProtocol.java +++ b/src/main/java/electrosphere/net/server/protocol/CharacterProtocol.java @@ -28,7 +28,7 @@ public class CharacterProtocol implements ServerProtocolTemplate { TerrainMessage.constructResponseMetadataMessage( realm.getServerWorldData().getWorldSizeDiscrete(), realm.getServerWorldData().getDynamicInterpolationRatio(), - realm.getServerWorldData().getRandomDampener(), + 0, (int)realm.getServerWorldData().getWorldBoundMin().x, (int)realm.getServerWorldData().getWorldBoundMin().z, (int)realm.getServerWorldData().getWorldBoundMax().x, diff --git a/src/main/java/electrosphere/server/character/PlayerCharacterCreation.java b/src/main/java/electrosphere/server/character/PlayerCharacterCreation.java index f8f4553c..67d2bb47 100644 --- a/src/main/java/electrosphere/server/character/PlayerCharacterCreation.java +++ b/src/main/java/electrosphere/server/character/PlayerCharacterCreation.java @@ -68,6 +68,8 @@ public class PlayerCharacterCreation { int playerCharacterId = entity.getId(); serverConnectionHandler.setPlayerEntityId(playerCharacterId); CreatureUtils.setControllerPlayerId(entity, serverConnectionHandler.getPlayerId()); + Player player = serverConnectionHandler.getPlayer(); + player.setPlayerEntity(entity); //custom player btrees addPlayerServerBTrees(entity); } diff --git a/src/main/java/electrosphere/server/content/ServerContentGenerator.java b/src/main/java/electrosphere/server/content/ServerContentGenerator.java new file mode 100644 index 00000000..c9014aca --- /dev/null +++ b/src/main/java/electrosphere/server/content/ServerContentGenerator.java @@ -0,0 +1,88 @@ +package electrosphere.server.content; + +import java.util.List; +import java.util.Random; + +import org.joml.Vector3d; +import org.joml.Vector3i; + +import electrosphere.entity.types.foliage.FoliageUtils; +import electrosphere.game.data.biome.BiomeData; +import electrosphere.game.data.biome.BiomeFoliageDescription; +import electrosphere.server.datacell.Realm; +import electrosphere.server.datacell.ServerDataCell; +import electrosphere.server.terrain.manager.ServerTerrainChunk; +import io.github.studiorailgun.NoiseUtils; + +/** + * Generates content for a given datacell + */ +public class ServerContentGenerator { + + /** + * The seed of the foliage + */ + public static final int FOLIAGE_SEED = 0; + + /** + * Generates content for a given data cell + * @param realm The realm + * @param cell The cell + * @param worldPos The world position of the cell + * @param randomizer The randomizer + */ + public static void generateContent(Realm realm, ServerDataCell cell, Vector3i worldPos, long randomizer){ + //verify we have everything for chunk content gen + if(realm.getServerWorldData() == null && realm.getServerWorldData().getServerTerrainManager() == null && realm.getServerWorldData().getServerTerrainManager().getModel() == null){ + throw new Error( + "Trying to generate content for a realm that does not have a terrain model defined!\n" + + realm.getServerWorldData() + "\n" + + realm.getServerWorldData().getServerTerrainManager() + "\n" + + realm.getServerWorldData().getServerTerrainManager().getModel() + ); + } + + //setup + Random random = new Random(randomizer); + + + //generate foliage + BiomeData biome = null; + if(realm.getServerWorldData() != null && realm.getServerWorldData().getServerTerrainManager() != null && realm.getServerWorldData().getServerTerrainManager().getModel() != null){ + biome = realm.getServerWorldData().getServerTerrainManager().getModel().getSurfaceBiome(worldPos.x, worldPos.z); + } + List foliageDescriptions = biome.getSurfaceGenerationParams().getFoliageDescriptions(); + if(foliageDescriptions != null){ + for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){ + for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){ + double height = realm.getServerWorldData().getServerTerrainManager().getElevation(worldPos.x, worldPos.z, x, z); + if( + realm.getServerWorldData().convertVoxelToRealSpace(0, worldPos.y) <= height && + realm.getServerWorldData().convertVoxelToRealSpace(ServerTerrainChunk.CHUNK_DIMENSION, worldPos.y) > height + ){ + for(BiomeFoliageDescription foliageDescription : foliageDescriptions){ + double scale = foliageDescription.getScale(); + double regularity = foliageDescription.getRegularity(); + double threshold = foliageDescription.getThreshold(); + if(NoiseUtils.relaxedPointGen(x * scale, z * scale, regularity, threshold) > 0){ + String type = foliageDescription.getEntityIDs().get(random.nextInt(0,foliageDescription.getEntityIDs().size())); + FoliageUtils.serverSpawnTreeFoliage( + realm, + new Vector3d( + realm.getServerWorldData().convertVoxelToRealSpace(x, worldPos.x), + height, + realm.getServerWorldData().convertVoxelToRealSpace(z, worldPos.z) + ), + type, + random.nextLong() + ); + } + } + } + } + } + } + + } + +} diff --git a/src/main/java/electrosphere/server/content/ServerContentManager.java b/src/main/java/electrosphere/server/content/ServerContentManager.java index d37169e8..089d9d48 100644 --- a/src/main/java/electrosphere/server/content/ServerContentManager.java +++ b/src/main/java/electrosphere/server/content/ServerContentManager.java @@ -11,6 +11,7 @@ import electrosphere.server.datacell.Realm; import electrosphere.server.datacell.ServerDataCell; import electrosphere.server.saves.SaveUtils; import electrosphere.util.FileUtils; +import io.github.studiorailgun.HashUtils; public class ServerContentManager { @@ -39,7 +40,7 @@ public class ServerContentManager { * @param worldPos The world position * @param cell The cell */ - public void generateContentForDataCell(Realm realm, Vector3i worldPos, ServerDataCell cell, String cellKey){ + public void generateContentForDataCell(Realm realm, Vector3i worldPos, ServerDataCell cell, Long cellKey){ String fullPath = "/content/" + cellKey + ".dat"; if(generateContent){ //in other words, if not arena mode if(FileUtils.checkSavePathExists(Globals.currentSave.getName(), fullPath)){ @@ -48,8 +49,7 @@ public class ServerContentManager { contentRaw.hydrateRawContent(realm,cell); } else { //else create from scratch - //UNCOMMENT THIS WHEN YOU WANT CONTENT GENERATED FOR WORLDS AGAIN - // EnvironmentGenerator.generateForest(realm, cell, worldPos, 0); + ServerContentGenerator.generateContent(realm, cell, worldPos, HashUtils.cantorHash(worldPos.x, worldPos.y, worldPos.z)); } } else { //just because content wasn't generated doesn't mean there isn't data saved under that key @@ -75,7 +75,7 @@ public class ServerContentManager { * @param locationKey the location key to save under * @param entities the list of entities to save */ - public void saveContentToDisk(String locationKey, List entities){ + public void saveContentToDisk(Long locationKey, List entities){ ContentSerialization serialization = ContentSerialization.constructContentSerialization(entities); String dirPath = SaveUtils.deriveSaveDirectoryPath(Globals.currentSave.getName()); String fullPath = dirPath + "/content/" + locationKey + ".dat"; diff --git a/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java b/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java index 81bfdb28..b7abb191 100644 --- a/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java +++ b/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java @@ -14,11 +14,13 @@ import org.joml.Vector3i; import electrosphere.engine.Globals; import electrosphere.engine.threads.LabeledThread.ThreadLabel; -import electrosphere.entity.ClientEntityUtils; import electrosphere.entity.Entity; import electrosphere.entity.EntityUtils; +import electrosphere.entity.ServerEntityUtils; +import electrosphere.entity.state.server.ServerPlayerViewDirTree; import electrosphere.game.server.world.ServerWorldData; import electrosphere.logger.LoggerInterface; +import electrosphere.net.parser.net.message.EntityMessage; import electrosphere.net.parser.net.message.TerrainMessage; import electrosphere.net.server.player.Player; import electrosphere.net.server.protocol.TerrainProtocol; @@ -29,6 +31,8 @@ import electrosphere.server.datacell.physics.PhysicsDataCell; import electrosphere.server.fluid.manager.ServerFluidChunk; import electrosphere.server.fluid.manager.ServerFluidManager; import electrosphere.server.terrain.manager.ServerTerrainManager; +import electrosphere.server.terrain.models.TerrainModel; +import io.github.studiorailgun.HashUtils; import electrosphere.server.terrain.manager.ServerTerrainChunk; /** @@ -44,7 +48,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager /** * The max grid size allowed */ - public static final int MAX_GRID_SIZE = 2000 * 1024; + public static final int MAX_GRID_SIZE = TerrainModel.MAX_MACRO_DATA_SIZE * TerrainModel.DEFAULT_MACRO_DATA_SCALE * ServerTerrainChunk.CHUNK_DIMENSION; /** * Tracks whether this manager has been flagged to unload cells or not @@ -52,7 +56,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager boolean unloadCells = true; //these are going to be the natural ground grid of data cells, but we're going to have more than this - Map groundDataCells = new HashMap(); + Map groundDataCells = new HashMap(); Map cellPositionMap = new HashMap(); //Map of server data cell to the number of frames said cell has had no players Map cellPlayerlessFrameMap = new HashMap(); @@ -77,7 +81,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager /** * Map of world position key -> physics cell */ - Map posPhysicsMap = new HashMap(); + Map posPhysicsMap = new HashMap(); /** * Constructor @@ -140,9 +144,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager loadedCellsLock.acquireUninterruptibly(); loadedCells.add(groundDataCells.get(getServerDataCellKey(targetPos))); cellPlayerlessFrameMap.put(groundDataCells.get(getServerDataCellKey(targetPos)),0); - loadedCellsLock.release(); - //generate/handle content for new server data cell - + loadedCellsLock.release(); //add player groundDataCells.get(getServerDataCellKey(targetPos)).addPlayer(player); } @@ -181,11 +183,14 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager ) ){ Vector3i targetPos = new Vector3i(x,y,z); - if(groundDataCells.get(getServerDataCellKey(targetPos)) != null){ - if(groundDataCells.get(getServerDataCellKey(targetPos)).containsPlayer(player)){ - // removals++; - groundDataCells.get(getServerDataCellKey(targetPos)).removePlayer(player); - } + Long key = this.getServerDataCellKey(targetPos); + if( + groundDataCells.get(key) != null && + groundDataCells.get(key).containsPlayer(player) + ){ + // removals++; + groundDataCells.get(key).removePlayer(player); + this.broadcastDestructionToPlayer(player, groundDataCells.get(key)); } } } @@ -235,11 +240,22 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager // System.out.println("removals: " + removals + "\tadditions: " + additions); } + /** + * Broadcasts messages to player to destroy all entities in a given cell + * @param player The player + * @param cell The cell + */ + private void broadcastDestructionToPlayer(Player player, ServerDataCell cell){ + for(Entity entity : cell.getScene().getEntityList()){ + player.addMessage(EntityMessage.constructDestroyMessage(entity.getId())); + } + } + /** * Creates physics entities when new data cell being created */ private void createTerrainPhysicsEntities(Vector3i worldPos){ - String key = this.getServerDataCellKey(worldPos); + Long key = this.getServerDataCellKey(worldPos); if(posPhysicsMap.containsKey(key)){ PhysicsDataCell cell = posPhysicsMap.get(key); cell.retireCell(); @@ -300,13 +316,16 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager parent.deregisterCell(cell); loadedCells.remove(cell); Vector3i worldPos = getCellWorldPosition(cell); - String key = getServerDataCellKey(worldPos); + Long key = getServerDataCellKey(worldPos); groundDataCells.remove(key); //offload all entities in cell to chunk file serverContentManager.saveContentToDisk(key, cell.getScene().getEntityList()); //clear all entities in cell for(Entity entity : cell.getScene().getEntityList()){ - ClientEntityUtils.destroyEntity(entity); + if(ServerPlayerViewDirTree.hasTree(entity)){ + throw new Error("Trying to unload a player's entity! " + entity + " " + worldPos); + } + ServerEntityUtils.destroyEntity(entity); } //save terrain to disk serverTerrainManager.savePositionToDisk(worldPos); @@ -334,13 +353,13 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager parent.deregisterCell(cell); loadedCells.remove(cell); Vector3i worldPos = getCellWorldPosition(cell); - String key = getServerDataCellKey(worldPos); + Long key = getServerDataCellKey(worldPos); groundDataCells.remove(key); //offload all entities in cell to chunk file serverContentManager.saveContentToDisk(key, cell.getScene().getEntityList()); //clear all entities in cell for(Entity entity : cell.getScene().getEntityList()){ - ClientEntityUtils.destroyEntity(entity); + ServerEntityUtils.destroyEntity(entity); } } this.serverTerrainManager.evictAll(); @@ -399,21 +418,25 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager worldPos.y >= 0 && worldPos.y < this.serverWorldData.getWorldSizeDiscrete() && worldPos.z >= 0 && worldPos.z < this.serverWorldData.getWorldSizeDiscrete() && //isn't null - groundDataCells.get(getServerDataCellKey(worldPos)) == null + groundDataCells.get(this.getServerDataCellKey(worldPos)) == null ){ //create data cell - createServerDataCell(worldPos); + this.createServerDataCell(worldPos); //generates physics for the cell in a dedicated thread then finally registers - runPhysicsGenerationThread(worldPos); + this.runPhysicsGenerationThread(worldPos); //add to loaded cells loadedCellsLock.acquireUninterruptibly(); - loadedCells.add(groundDataCells.get(getServerDataCellKey(worldPos))); - cellPlayerlessFrameMap.put(groundDataCells.get(getServerDataCellKey(worldPos)),0); + loadedCells.add(groundDataCells.get(this.getServerDataCellKey(worldPos))); + cellPlayerlessFrameMap.put(groundDataCells.get(this.getServerDataCellKey(worldPos)),0); loadedCellsLock.release(); - } else { - LoggerInterface.loggerEngine.WARNING("Trying to create data cell outside world bounds! " + worldPos); + } else if(groundDataCells.get(this.getServerDataCellKey(worldPos)) == null) { + LoggerInterface.loggerEngine.WARNING( + "Trying to create data cell outside world bounds!\n" + + worldPos + "\n" + + this.serverWorldData.getWorldSizeDiscrete() + ); } - return groundDataCells.get(getServerDataCellKey(worldPos)); + return groundDataCells.get(this.getServerDataCellKey(worldPos)); } /** @@ -428,9 +451,9 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager position.y >= 0 && position.y < this.serverWorldData.getWorldSizeDiscrete() && position.z >= 0 && position.z < this.serverWorldData.getWorldSizeDiscrete() && //isn't null - groundDataCells.get(getServerDataCellKey(position)) != null + groundDataCells.get(this.getServerDataCellKey(position)) != null ){ - return groundDataCells.get(getServerDataCellKey(position)); + return groundDataCells.get(this.getServerDataCellKey(position)); } return null; } @@ -486,7 +509,11 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager //create physics entities createTerrainPhysicsEntities(worldPos); //set ready - groundDataCells.get(getServerDataCellKey(worldPos)).setReady(true); + if(groundDataCells.get(getServerDataCellKey(worldPos)) != null){ + groundDataCells.get(getServerDataCellKey(worldPos)).setReady(true); + } else { + LoggerInterface.loggerEngine.WARNING("Finished generating physics for server cell, but cell is null!"); + } } }); groundDataCells.get(getServerDataCellKey(worldPos)).setReady(false); @@ -498,8 +525,8 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager * @param worldPos The position in world coordinates of the server data cell * @return The server data cell if it exists, otherwise null */ - private String getServerDataCellKey(Vector3i worldPos){ - return worldPos.x + "_" + worldPos.y + "_" + worldPos.z; + private Long getServerDataCellKey(Vector3i worldPos){ + return (long)HashUtils.cantorHash(worldPos.x, worldPos.y, worldPos.z); } /** @@ -509,7 +536,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager */ private ServerDataCell createServerDataCell(Vector3i worldPos){ ServerDataCell rVal = parent.createNewCell(); - String cellKey = getServerDataCellKey(worldPos); + Long cellKey = getServerDataCellKey(worldPos); groundDataCells.put(cellKey,rVal); LoggerInterface.loggerEngine.DEBUG("Create server data cell with key " + cellKey); cellPositionMap.put(rVal,new Vector3i(worldPos)); @@ -648,7 +675,7 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager @Override public void save(String saveName) { for(ServerDataCell cell : loadedCells){ - String key = this.getServerDataCellKey(this.getCellWorldPosition(cell)); + Long key = this.getServerDataCellKey(this.getCellWorldPosition(cell)); //offload all entities in cell to chunk file serverContentManager.saveContentToDisk(key, cell.getScene().getEntityList()); } diff --git a/src/main/java/electrosphere/server/datacell/utils/ServerBehaviorTreeUtils.java b/src/main/java/electrosphere/server/datacell/utils/ServerBehaviorTreeUtils.java index 60623879..9312a593 100644 --- a/src/main/java/electrosphere/server/datacell/utils/ServerBehaviorTreeUtils.java +++ b/src/main/java/electrosphere/server/datacell/utils/ServerBehaviorTreeUtils.java @@ -75,7 +75,9 @@ public class ServerBehaviorTreeUtils { Set trees = entityBTreeMap.get(entity); ServerDataCell newCell = DataCellSearchUtils.getEntityDataCell(entity); for(BehaviorTree tree : trees){ - oldCell.getScene().deregisterBehaviorTree(tree); + if(oldCell != null){ + oldCell.getScene().deregisterBehaviorTree(tree); + } newCell.getScene().registerBehaviorTree(tree); } } diff --git a/src/main/java/electrosphere/server/terrain/generation/DefaultChunkGenerator.java b/src/main/java/electrosphere/server/terrain/generation/DefaultChunkGenerator.java index 2a5f5b5a..9bab74c0 100644 --- a/src/main/java/electrosphere/server/terrain/generation/DefaultChunkGenerator.java +++ b/src/main/java/electrosphere/server/terrain/generation/DefaultChunkGenerator.java @@ -47,6 +47,11 @@ public class DefaultChunkGenerator implements ChunkGenerator { return rVal; } + @Override + public double getElevation(int worldX, int worldZ, int chunkX, int chunkZ){ + return 0.1; + } + @Override public void setModel(TerrainModel model) { //Does nothing as the arena is not based on a terrain model diff --git a/src/main/java/electrosphere/server/terrain/generation/OverworldChunkGenerator.java b/src/main/java/electrosphere/server/terrain/generation/OverworldChunkGenerator.java index 6674257a..09721e00 100644 --- a/src/main/java/electrosphere/server/terrain/generation/OverworldChunkGenerator.java +++ b/src/main/java/electrosphere/server/terrain/generation/OverworldChunkGenerator.java @@ -56,6 +56,12 @@ public class OverworldChunkGenerator implements ChunkGenerator { return returnedChunk; } + @Override + public double getElevation(int worldX, int worldZ, int chunkX, int chunkZ){ + float[][] heightmap = getHeightmap(worldX, worldZ); + return heightmap[chunkX][chunkZ]; + } + @Override /** * Sets the terrain model for the overworld algo diff --git a/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java b/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java index 16d83fce..34347ea9 100644 --- a/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java +++ b/src/main/java/electrosphere/server/terrain/generation/TestGenerationChunkGenerator.java @@ -74,7 +74,7 @@ public class TestGenerationChunkGenerator implements ChunkGenerator { //actual generation algo //biome of the current chunk - BiomeData surfaceBiome = this.terrainModel.getSurfaceBiome(worldX, worldY, worldZ); + BiomeData surfaceBiome = this.terrainModel.getSurfaceBiome(worldX, worldZ); BiomeSurfaceGenerationParams surfaceParams = surfaceBiome.getSurfaceGenerationParams(); HeightmapGenerator heightmapGen = this.tagGeneratorMap.get(surfaceParams.getSurfaceGenTag()); @@ -126,6 +126,23 @@ public class TestGenerationChunkGenerator implements ChunkGenerator { return rVal; } + @Override + public double getElevation(int worldX, int worldZ, int chunkX, int chunkZ){ + BiomeData surfaceBiome = this.terrainModel.getSurfaceBiome(worldX, worldZ); + + BiomeSurfaceGenerationParams surfaceParams = surfaceBiome.getSurfaceGenerationParams(); + HeightmapGenerator heightmapGen = this.tagGeneratorMap.get(surfaceParams.getSurfaceGenTag()); + if(heightmapGen == null){ + throw new Error("Undefined heightmap generator in biome! " + surfaceBiome.getId() + " " + surfaceBiome.getDisplayName() + " " + surfaceParams.getSurfaceGenTag()); + } + double rVal = heightmapGen.getHeight( + this.terrainModel.getSeed(), + this.serverWorldData.convertVoxelToRealSpace(chunkX, worldX), + this.serverWorldData.convertVoxelToRealSpace(chunkZ, worldZ) + ); + return rVal; + } + @Override public void setModel(TerrainModel model) { this.terrainModel = model; diff --git a/src/main/java/electrosphere/server/terrain/generation/interfaces/ChunkGenerator.java b/src/main/java/electrosphere/server/terrain/generation/interfaces/ChunkGenerator.java index d6e5afa3..18659401 100644 --- a/src/main/java/electrosphere/server/terrain/generation/interfaces/ChunkGenerator.java +++ b/src/main/java/electrosphere/server/terrain/generation/interfaces/ChunkGenerator.java @@ -18,6 +18,16 @@ public interface ChunkGenerator { */ public ServerTerrainChunk generateChunk(int worldX, int worldY, int worldZ, int stride); + /** + * Gets the elevation at a given 2d coordinate + * @param worldX The world x coordinate + * @param worldZ The world z coordinate + * @param chunkX The chunk x coordinate + * @param chunkZ The chunk z coordinate + * @return The elevation at that specific coordinate + */ + public double getElevation(int worldX, int worldZ, int chunkX, int chunkZ); + /** * Sets the terrain model for the generation algorithm * @param model The terrain model diff --git a/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java b/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java index e022d83b..ee555ba4 100644 --- a/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java +++ b/src/main/java/electrosphere/server/terrain/manager/ServerTerrainManager.java @@ -181,10 +181,6 @@ public class ServerTerrainManager { return model.getElevationForChunk(x, y); } - public double getHeightAtPosition(double x, double y, double z){ - return y; - } - public float getDiscreteValue(int x, int y){ if(model != null){ return model.getElevation()[x][y]; @@ -251,6 +247,21 @@ public class ServerTerrainManager { return returnedChunk; } + /** + * Performs logic once a server chunk is available + * @param worldX The world x position + * @param worldZ The world z position + * @param chunkX The chunk x position + * @param chunkZ THe chunk z position + * @return The ServerTerrainChunk + */ + public double getElevation(int worldX, int worldZ, int chunkX, int chunkZ){ + Globals.profiler.beginCpuSample("ServerTerrainManager.getChunk"); + double elevation = chunkGenerator.getElevation(worldX, worldZ, chunkX, chunkZ); + Globals.profiler.endCpuSample(); + return elevation; + } + /** * Performs logic once a server chunk is available * @param worldX The world x position diff --git a/src/main/java/electrosphere/server/terrain/models/TerrainModel.java b/src/main/java/electrosphere/server/terrain/models/TerrainModel.java index 17f4757e..c473539e 100644 --- a/src/main/java/electrosphere/server/terrain/models/TerrainModel.java +++ b/src/main/java/electrosphere/server/terrain/models/TerrainModel.java @@ -13,6 +13,11 @@ import electrosphere.util.annotation.Exclude; */ public class TerrainModel { + /** + * Maximum size of the macro data + */ + public static final int MAX_MACRO_DATA_SIZE = 2000; + /** * The scale of the macro data @@ -514,11 +519,10 @@ public class TerrainModel { /** * Gets the surface biome for a given world position * @param worldX The world X - * @param worldY The world Y * @param worldZ The world Z * @return The biome */ - public BiomeData getSurfaceBiome(int worldX, int worldY, int worldZ){ + public BiomeData getSurfaceBiome(int worldX, int worldZ){ int macroX = worldX / macroDataScale; int macroZ = worldZ / macroDataScale; int surfaceBiomeIndex = this.biome[macroX][macroZ];