diff --git a/assets/Data/creatures/human.json b/assets/Data/creatures/human.json index bd782333..3abe5b36 100644 --- a/assets/Data/creatures/human.json +++ b/assets/Data/creatures/human.json @@ -334,7 +334,7 @@ "maxHealth" : 100, "onDamageIFrames" : 30 }, - "modelPath" : "Models/baseman5.glb" + "modelPath" : "Models/person2_1.glb" } ], "files" : [] diff --git a/assets/Models/modelPretransforms.json b/assets/Models/modelPretransforms.json index 685f8a17..b2fe37bf 100644 --- a/assets/Models/modelPretransforms.json +++ b/assets/Models/modelPretransforms.json @@ -22,28 +22,6 @@ } ] }, - { - "path" : "Models/proceduralTree1/proceduralTrunk1.fbx", - "meshes" : [ - { - "meshName" : "Cube", - "rotation" : [-0.7071068, 0.0, 0.0, 0.7071068], - "offset" : [0.0, 0.0, 0.0], - "scale" : [1.0, 1.0, 1.0] - } - ] - }, - { - "path" : "Models/proceduralTree2/proceduralTree2.fbx", - "meshes" : [ - { - "meshName" : "Trunk", - "rotation" : [-0.7071068, 0.0, 0.0, 0.7071068], - "offset" : [0.0, 0.0, 0.0], - "scale" : [1.0, 1.0, 1.0] - } - ] - }, { "path" : "Models/proceduralTree2/proceduralTree2v2.fbx", "meshes" : [ @@ -51,7 +29,30 @@ "meshName" : "Trunk", "rotation" : [-0.7071068, 0.0, 0.0, 0.7071068], "offset" : [0.0, 0.0, 0.0], - "scale" : [1.0, 1.0, 1.0] + "scale" : [100.0, 100.0, 100.0] + } + ] + }, + { + "path" : "Models/foliageBlockTemplate1Test1.fbx", + "meshes" : [ + { + "meshName" : "Plane", + "rotation" : [-0.7071068, 0.0, 0.0, 0.7071068], + "offset" : [0.0, 0.0, 0.0], + "scale" : [100.0, 100.0, 100.0] + }, + { + "meshName" : "Plane.001", + "rotation" : [-0.7071068, 0.0, 0.0, 0.7071068], + "offset" : [0.0, 0.0, 0.0], + "scale" : [100.0, 100.0, 100.0] + }, + { + "meshName" : "Plane.002", + "rotation" : [-0.7071068, 0.0, 0.0, 0.7071068], + "offset" : [0.0, 0.0, 0.0], + "scale" : [100.0, 100.0, 100.0] } ] }, @@ -65,6 +66,16 @@ "meshes" : [ ] }, + { + "path" : "Models/person2_1.glb", + "globalTransform": { + "rotation" : [0,0,0,1], + "offset" : [0.0, 0.0, 0.0], + "scale" : [0.6, 0.6, 0.6] + }, + "meshes" : [ + ] + }, { "path" : "Models/grass2.fbx", "globalTransform": { @@ -77,7 +88,7 @@ "meshName" : "Plane", "rotation" : [-0.7071068, 0.0, 0.0, 0.7071068], "offset" : [0.0, 0.0, 0.0], - "scale" : [1.0, 1.0, 1.0] + "scale" : [100.0, 100.0, 100.0] } ] } diff --git a/assets/Models/person2_1.glb b/assets/Models/person2_1.glb new file mode 100644 index 00000000..da9c8e0a Binary files /dev/null and b/assets/Models/person2_1.glb differ diff --git a/assets/Models/shorts.glb b/assets/Models/shorts.glb new file mode 100644 index 00000000..f7c448f1 Binary files /dev/null and b/assets/Models/shorts.glb differ diff --git a/buildNumber.properties b/buildNumber.properties index fe56c10f..32709142 100644 --- a/buildNumber.properties +++ b/buildNumber.properties @@ -1,3 +1,3 @@ #maven.buildNumber.plugin properties file -#Sat Apr 13 21:46:23 EDT 2024 -buildNumber=96 +#Sun Apr 14 15:53:43 EDT 2024 +buildNumber=98 diff --git a/docs/src/blender/BasicClothingGuide.md b/docs/src/blender/BasicClothingGuide.md new file mode 100644 index 00000000..65710779 --- /dev/null +++ b/docs/src/blender/BasicClothingGuide.md @@ -0,0 +1,68 @@ +@page BasicClothingGuide Basic Clothing Guide + + + +## High Level Overview +The idea of this page is to track notes about creating simple clothing items that work with existing creature models. + + + + +## Setting Up The File +Create a new blender file. + +Link the armature from the existing creature model. + +![](/docs/src/images/blender/basicClothing/clothingLinkOption.png) + +![](/docs/src/images/blender/basicClothing/clothingSelectLinkArmature.png) + + + + +Append the relevant meshes from the existing creature model. + +![](/docs/src/images/blender/basicClothing/clothingAppend.png) + +![](/docs/src/images/blender/basicClothing/clothingSelectRightMeshImport.png) + + + +Clean up the existing mesh + +![](/docs/src/images/blender/basicClothing/clothingDeletingVertices.png) + +You can use the circle select mode to significantly speed up selection of vertices to delete + +![](/docs/src/images/blender/basicClothing/clothingSwitchToAreaSelect.png) + + + +## Attach Armature + +Add the armature modifier to the mesh you have created + +![](/docs/src/images/blender/basicClothing/clothingAddArmatureModifier.png) + +Select the dropper icon for Object + +![](/docs/src/images/blender/basicClothing/clothingArmatureModifierSelectArmatureEyedrop.png) + +And click on the armature object in the tree view in the top right + +![](/docs/src/images/blender/basicClothing/clothingClickOnArmature.png) + +You can validate that it added the modifier correctly by selecting the bones, going into pose mode, and setting the pose to watch the mesh move. + +![](/docs/src/images/blender/basicClothing/clothingTestWithPose.png) + + + + +## Solidify Modifier + +Add the solidify modifier to make the clothing have depth + +![](/docs/src/images/blender/basicClothing/clothingSolidifyModifier.png) + +![](/docs/src/images/blender/basicClothing/clothingSolidifyModifierApply.png) diff --git a/docs/src/blender/indexblender.md b/docs/src/blender/indexblender.md index 6f11c447..8698063c 100644 --- a/docs/src/blender/indexblender.md +++ b/docs/src/blender/indexblender.md @@ -2,3 +2,4 @@ [TOC] - @subpage WeavingMeshes +- @subpage BasicClothingGuide \ No newline at end of file diff --git a/docs/src/images/blender/basicClothing/clothingAddArmatureModifier.png b/docs/src/images/blender/basicClothing/clothingAddArmatureModifier.png new file mode 100644 index 00000000..a9646bfa Binary files /dev/null and b/docs/src/images/blender/basicClothing/clothingAddArmatureModifier.png differ diff --git a/docs/src/images/blender/basicClothing/clothingAppend.png b/docs/src/images/blender/basicClothing/clothingAppend.png new file mode 100644 index 00000000..6c4f547a Binary files /dev/null and b/docs/src/images/blender/basicClothing/clothingAppend.png differ diff --git a/docs/src/images/blender/basicClothing/clothingArmatureModifierSelectArmatureEyedrop.png b/docs/src/images/blender/basicClothing/clothingArmatureModifierSelectArmatureEyedrop.png new file mode 100644 index 00000000..d0574fd3 Binary files /dev/null and b/docs/src/images/blender/basicClothing/clothingArmatureModifierSelectArmatureEyedrop.png differ diff --git a/docs/src/images/blender/basicClothing/clothingClickOnArmature.png b/docs/src/images/blender/basicClothing/clothingClickOnArmature.png new file mode 100644 index 00000000..21e8d4c6 Binary files /dev/null and b/docs/src/images/blender/basicClothing/clothingClickOnArmature.png differ diff --git a/docs/src/images/blender/basicClothing/clothingDeletingVertices.png b/docs/src/images/blender/basicClothing/clothingDeletingVertices.png new file mode 100644 index 00000000..af697563 Binary files /dev/null and b/docs/src/images/blender/basicClothing/clothingDeletingVertices.png differ diff --git a/docs/src/images/blender/basicClothing/clothingLinkOption.png b/docs/src/images/blender/basicClothing/clothingLinkOption.png new file mode 100644 index 00000000..95b2dee7 Binary files /dev/null and b/docs/src/images/blender/basicClothing/clothingLinkOption.png differ diff --git a/docs/src/images/blender/basicClothing/clothingSelectLinkArmature.png b/docs/src/images/blender/basicClothing/clothingSelectLinkArmature.png new file mode 100644 index 00000000..e12672c3 Binary files /dev/null and b/docs/src/images/blender/basicClothing/clothingSelectLinkArmature.png differ diff --git a/docs/src/images/blender/basicClothing/clothingSelectRightMeshImport.png b/docs/src/images/blender/basicClothing/clothingSelectRightMeshImport.png new file mode 100644 index 00000000..73a43f71 Binary files /dev/null and b/docs/src/images/blender/basicClothing/clothingSelectRightMeshImport.png differ diff --git a/docs/src/images/blender/basicClothing/clothingSolidifyModifier.png b/docs/src/images/blender/basicClothing/clothingSolidifyModifier.png new file mode 100644 index 00000000..9ec71db5 Binary files /dev/null and b/docs/src/images/blender/basicClothing/clothingSolidifyModifier.png differ diff --git a/docs/src/images/blender/basicClothing/clothingSolidifyModifierApply.png b/docs/src/images/blender/basicClothing/clothingSolidifyModifierApply.png new file mode 100644 index 00000000..4033b5cf Binary files /dev/null and b/docs/src/images/blender/basicClothing/clothingSolidifyModifierApply.png differ diff --git a/docs/src/images/blender/basicClothing/clothingSwitchToAreaSelect.png b/docs/src/images/blender/basicClothing/clothingSwitchToAreaSelect.png new file mode 100644 index 00000000..2f182062 Binary files /dev/null and b/docs/src/images/blender/basicClothing/clothingSwitchToAreaSelect.png differ diff --git a/docs/src/images/blender/basicClothing/clothingTestWithPose.png b/docs/src/images/blender/basicClothing/clothingTestWithPose.png new file mode 100644 index 00000000..1bb54936 Binary files /dev/null and b/docs/src/images/blender/basicClothing/clothingTestWithPose.png differ diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index b07e3a0f..facd0eb1 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -218,6 +218,7 @@ Terrain editing UI (04/13/2024) UI Work - Level editor ability to destroy an entity on server, have it also destroy on client, AND not persist on save + - Environment controls (not persisting in save yet) # TODO diff --git a/src/main/java/electrosphere/collision/CollisionEngine.java b/src/main/java/electrosphere/collision/CollisionEngine.java index a7702ecb..154b097f 100644 --- a/src/main/java/electrosphere/collision/CollisionEngine.java +++ b/src/main/java/electrosphere/collision/CollisionEngine.java @@ -63,7 +63,11 @@ import electrosphere.logger.LoggerInterface; */ public class CollisionEngine { + //step interval time size public static final float ENGINE_STEP_SIZE = 0.01f; + + //gravity constant + public static final float GRAVITY_MAGNITUDE = 9.8f; //world data that the collision engine leverages for position correction and the like CollisionWorldData collisionWorldData; @@ -88,13 +92,10 @@ public class CollisionEngine { //callbacks for collision check RayCastCallback rayCastCallback = new RayCastCallback(); - //physics damping - static final float linearDamping = 0.02f; - public CollisionEngine(){ world = OdeHelper.createWorld(); space = OdeHelper.createBHVSpace(Collidable.TYPE_STATIC_BIT); - world.setGravity(0,-3,0); + world.setGravity(0,-GRAVITY_MAGNITUDE,0); contactgroup = OdeHelper.createJointGroup(); } diff --git a/src/main/java/electrosphere/controls/ControlHandler.java b/src/main/java/electrosphere/controls/ControlHandler.java index fd930d79..ce23a8b9 100644 --- a/src/main/java/electrosphere/controls/ControlHandler.java +++ b/src/main/java/electrosphere/controls/ControlHandler.java @@ -24,6 +24,7 @@ import static org.lwjgl.glfw.GLFW.GLFW_KEY_E; import static org.lwjgl.glfw.GLFW.GLFW_KEY_ENTER; import static org.lwjgl.glfw.GLFW.GLFW_KEY_ESCAPE; import static org.lwjgl.glfw.GLFW.GLFW_KEY_F; +import static org.lwjgl.glfw.GLFW.GLFW_KEY_F2; import static org.lwjgl.glfw.GLFW.GLFW_KEY_F24; import static org.lwjgl.glfw.GLFW.GLFW_KEY_G; import static org.lwjgl.glfw.GLFW.GLFW_KEY_H; @@ -94,9 +95,9 @@ import electrosphere.entity.types.creature.CreatureUtils; import electrosphere.logger.LoggerInterface; import electrosphere.menu.WindowStrings; import electrosphere.menu.WindowUtils; +import electrosphere.menu.debug.ImGuiWindowMacros; import electrosphere.menu.ingame.MenuGeneratorsInGame; import electrosphere.menu.ingame.MenuGeneratorsInventory; -import electrosphere.menu.mainmenu.MenuGeneratorsDebug; import electrosphere.renderer.ui.elements.Window; import electrosphere.renderer.ui.events.ClickEvent; import electrosphere.renderer.ui.events.KeyboardEvent; @@ -378,7 +379,7 @@ public class ControlHandler { Debug controls */ handler.addControl(DATA_STRING_INPUT_CODE_DEBUG_SPAWN_ITEM, new Control(ControlType.KEY,GLFW_KEY_Q)); - handler.addControl(DEBUG_OPEN_DEBUG_MENU, new Control(ControlType.KEY, GLFW_KEY_O)); + handler.addControl(DEBUG_OPEN_DEBUG_MENU, new Control(ControlType.KEY, GLFW_KEY_F2)); /* return @@ -980,7 +981,9 @@ public class ControlHandler { Vector3d eyePos = new Vector3d(CameraEntityUtils.getCameraEye(camera)); Vector3d centerPos = new Vector3d(CameraEntityUtils.getCameraCenter(camera)); Vector3d cursorPos = collisionEngine.rayCastPosition(new Vector3d(centerPos), new Vector3d(eyePos).mul(-1.0), 5.0); - TerrainEditing.editTerrain(cursorPos, 1.1f, 2, 0.01f); + if(Globals.clientSelectedVoxelType != null){ + TerrainEditing.editTerrain(cursorPos, 1.1f, Globals.clientSelectedVoxelType.getId(), 0.1f); + } } }}); controls.get(INPUT_CODE_PLACE_TERRAIN).setOnRepeat(new ControlMethod(){public void execute(){ @@ -993,7 +996,9 @@ public class ControlHandler { Vector3d eyePos = new Vector3d(CameraEntityUtils.getCameraEye(camera)); Vector3d centerPos = new Vector3d(CameraEntityUtils.getCameraCenter(camera)); Vector3d cursorPos = collisionEngine.rayCastPosition(new Vector3d(centerPos), new Vector3d(eyePos).mul(-1.0), 5.0); - TerrainEditing.editTerrain(cursorPos, 1.1f, 2, 0.01f); + if(Globals.clientSelectedVoxelType != null){ + TerrainEditing.editTerrain(cursorPos, 1.1f, Globals.clientSelectedVoxelType.getId(), 0.1f); + } } }}); controls.get(INPUT_CODE_PLACE_TERRAIN).setRepeatTimeout(0.2f * Main.targetFrameRate); @@ -1049,14 +1054,7 @@ public class ControlHandler { alwaysOnDebugControlList.add(controls.get(DEBUG_OPEN_DEBUG_MENU)); controls.get(DEBUG_OPEN_DEBUG_MENU).setOnPress(new ControlMethod(){public void execute(){ LoggerInterface.loggerEngine.INFO("open debug menu"); - // Window mainMenuWindow = new Window(0, 0, Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT); - Window mainMenuInGame = MenuGeneratorsDebug.createTopLevelDebugMenu(); - // mainMenuWindow.addChild(mainMenuInGame); - Globals.elementManager.registerWindow(WindowStrings.WINDOW_DEBUG, mainMenuInGame); - WindowUtils.recursiveSetVisible(Globals.elementManager.getWindow(WindowStrings.WINDOW_DEBUG), true); - Globals.elementManager.focusFirstElement(); - Globals.controlHandler.setHandlerState(ControlsState.IN_GAME_MAIN_MENU); - Globals.controlHandler.showMouse(); + ImGuiWindowMacros.toggleMainDebugMenu(); //play sound effect Globals.virtualAudioSourceManager.createVirtualAudioSource("/Audio/openMenu.ogg", VirtualAudioSourceType.UI, false); }}); diff --git a/src/main/java/electrosphere/engine/Globals.java b/src/main/java/electrosphere/engine/Globals.java index d0334e5b..b78c6357 100644 --- a/src/main/java/electrosphere/engine/Globals.java +++ b/src/main/java/electrosphere/engine/Globals.java @@ -450,6 +450,10 @@ public class Globals { //game config gameConfigDefault = electrosphere.game.data.Config.loadDefaultConfig(); gameConfigCurrent = gameConfigDefault; + + // + //Values that depend on the loaded config + Globals.clientSelectedVoxelType = (VoxelType)gameConfigCurrent.getVoxelData().getTypes().toArray()[1]; //player manager playerManager = new PlayerManager(); //behavior tree tracking service diff --git a/src/main/java/electrosphere/engine/Main.java b/src/main/java/electrosphere/engine/Main.java index feeea720..bfde3a89 100644 --- a/src/main/java/electrosphere/engine/Main.java +++ b/src/main/java/electrosphere/engine/Main.java @@ -146,9 +146,9 @@ public class Main { //uncomment to test loading a model into engine // if(1==1){ - // Globals.assetManager.addModelPathToQueue("/Models/tank1.fbx"); + // Globals.assetManager.addModelPathToQueue("/Models/baseman9.glb"); // Globals.assetManager.loadAssetsInQueue(); - // Model model = Globals.assetManager.fetchModel("/Models/tank1.fbx"); + // Model model = Globals.assetManager.fetchModel("/Models/baseman9.glb"); // // for(electrosphere.renderer.anim.Animation anim : model.animations){ // // if(anim.name.equals("Armature|Idle1")){ // // System.out.println(anim.duration); @@ -161,7 +161,8 @@ public class Main { // // break; // // } // // } - // model.animations.get(0).fullDescribeAnimation(); + // model.describeHighLevel(); + // // model.animations.get(0).fullDescribeAnimation(); // // model.describeHighLevel(); // System.exit(0); // } diff --git a/src/main/java/electrosphere/engine/assetmanager/AssetManager.java b/src/main/java/electrosphere/engine/assetmanager/AssetManager.java index dd9be8a0..3174ca54 100644 --- a/src/main/java/electrosphere/engine/assetmanager/AssetManager.java +++ b/src/main/java/electrosphere/engine/assetmanager/AssetManager.java @@ -28,8 +28,7 @@ import org.ode4j.ode.DBody; import org.ode4j.ode.DWorld; /** - * - * @author amaterasu + * Manages all assets loaded into the engine including initially loading and destructing */ public class AssetManager { diff --git a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java index ab5214e3..82a90957 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java +++ b/src/main/java/electrosphere/engine/loadingthreads/ClientLoading.java @@ -226,7 +226,7 @@ public class ClientLoading { Entity cursorTracker = EntityCreationUtils.createClientSpatialEntity(); EntityCreationUtils.makeEntityDrawable(cursorTracker, "Models/unitsphere_1.fbx"); - EntityUtils.getScale(cursorTracker).set(0.3f); + EntityUtils.getScale(cursorTracker).set(30f); Globals.clientSceneWrapper.getScene().registerBehaviorTree(new BehaviorTree() { @Override public void simulate(float deltaTime) { diff --git a/src/main/java/electrosphere/entity/state/movement/groundmove/ServerGroundMovementTree.java b/src/main/java/electrosphere/entity/state/movement/groundmove/ServerGroundMovementTree.java index d6e51c1d..6e6b8a19 100644 --- a/src/main/java/electrosphere/entity/state/movement/groundmove/ServerGroundMovementTree.java +++ b/src/main/java/electrosphere/entity/state/movement/groundmove/ServerGroundMovementTree.java @@ -215,22 +215,22 @@ public class ServerGroundMovementTree implements BehaviorTree { case SETFACING: break; case MOVEUPDATE: { - if(updateTime > lastUpdateTime){ + if(updateTime >= lastUpdateTime){ lastUpdateTime = updateTime; switch(message.gettreeState()){ case 0: state = MovementTreeState.STARTUP; - // System.out.println("Set state STARTUP"); + // System.out.println("Set state STARTUP"); GravityUtils.serverAttemptActivateGravity(parent); break; case 1: state = MovementTreeState.MOVE; -// System.out.println("Set state MOVE"); + // System.out.println("Set state MOVE"); GravityUtils.serverAttemptActivateGravity(parent); break; case 2: state = MovementTreeState.SLOWDOWN; - // System.out.println("Set state SLOWDOWN"); + // System.out.println("Set state SLOWDOWN"); GravityUtils.serverAttemptActivateGravity(parent); break; case 3: diff --git a/src/main/java/electrosphere/menu/debug/ImGuiWindowMacros.java b/src/main/java/electrosphere/menu/debug/ImGuiWindowMacros.java index 1b1438c7..f4428e39 100644 --- a/src/main/java/electrosphere/menu/debug/ImGuiWindowMacros.java +++ b/src/main/java/electrosphere/menu/debug/ImGuiWindowMacros.java @@ -241,4 +241,11 @@ public class ImGuiWindowMacros { RenderingEngine.addImGuiWindow(mainDebugWindow); } + /** + * Toggles the open state of the menu + */ + public static void toggleMainDebugMenu(){ + mainDebugWindow.setOpen(!mainDebugWindow.isOpen()); + } + } diff --git a/src/main/java/electrosphere/menu/ingame/MenuGeneratorsLevelEditor.java b/src/main/java/electrosphere/menu/ingame/MenuGeneratorsLevelEditor.java index 07a05732..5ced7ec3 100644 --- a/src/main/java/electrosphere/menu/ingame/MenuGeneratorsLevelEditor.java +++ b/src/main/java/electrosphere/menu/ingame/MenuGeneratorsLevelEditor.java @@ -47,6 +47,9 @@ public class MenuGeneratorsLevelEditor { //width of the side panel static final int SIDE_PANEL_WIDTH = 500; + //is the voxel selection window open + static boolean voxelWindowOpen = false; + /** * Creates the level editor side panel top view @@ -106,7 +109,13 @@ public class MenuGeneratorsLevelEditor { //select voxel button scrollable.addChild(Button.createButton("Select Voxel Type", new ClickEventCallback() {public boolean execute(ClickEvent event){ - WindowUtils.replaceWindow(WindowStrings.VOXEL_TYPE_SELECTION,MenuGeneratorsTerrainEditing.createVoxelTypeSelectionPanel()); + if(voxelWindowOpen){ + voxelWindowOpen = false; + WindowUtils.closeWindow(WindowStrings.VOXEL_TYPE_SELECTION); + } else { + voxelWindowOpen = true; + WindowUtils.replaceWindow(WindowStrings.VOXEL_TYPE_SELECTION,MenuGeneratorsTerrainEditing.createVoxelTypeSelectionPanel()); + } return false; }})); diff --git a/src/main/java/electrosphere/renderer/ui/imgui/ImGuiWindow.java b/src/main/java/electrosphere/renderer/ui/imgui/ImGuiWindow.java index 679c5053..7b8fd42f 100644 --- a/src/main/java/electrosphere/renderer/ui/imgui/ImGuiWindow.java +++ b/src/main/java/electrosphere/renderer/ui/imgui/ImGuiWindow.java @@ -81,6 +81,14 @@ public class ImGuiWindow { this.open.set(open); } + /** + * Gets whether the window is open or not + * @return true if open, false otherwise + */ + public boolean isOpen(){ + return this.open.get(); + } + /** * An optional callback for the window that lets you directly call imgui functions diff --git a/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java b/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java index aed03d9d..b12a7172 100644 --- a/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java +++ b/src/main/java/electrosphere/server/datacell/GriddedDataCellManager.java @@ -475,10 +475,13 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager //update terrain serverTerrainManager.deformTerrainAtLocationToValue(worldPosition, voxelPosition, weight, type); //broadcast update to terrain - groundDataCells.get(getServerDataCellKey(worldPosition)).broadcastNetworkMessage(TerrainMessage.constructUpdateVoxelMessage( - worldPosition.x, worldPosition.y, worldPosition.z, - voxelPosition.x, voxelPosition.y, voxelPosition.z, - weight, type)); + ServerDataCell cell = groundDataCells.get(getServerDataCellKey(worldPosition)); + if(cell != null){ + cell.broadcastNetworkMessage(TerrainMessage.constructUpdateVoxelMessage( + worldPosition.x, worldPosition.y, worldPosition.z, + voxelPosition.x, voxelPosition.y, voxelPosition.z, + weight, type)); + } terrainEditLock.release(); }