diff --git a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/data/IDhApiTerrainDataRepo.java b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/data/IDhApiTerrainDataRepo.java
index 36f3d99ea..e6b838203 100644
--- a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/data/IDhApiTerrainDataRepo.java
+++ b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/data/IDhApiTerrainDataRepo.java
@@ -1,5 +1,6 @@
package com.seibel.distanthorizons.api.interfaces.data;
+import com.seibel.distanthorizons.api.enums.EDhApiDetailLevel;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.api.objects.DhApiResult;
import com.seibel.distanthorizons.api.objects.data.DhApiRaycastResult;
@@ -45,7 +46,8 @@ public interface IDhApiTerrainDataRepo
*
* @param detailLevel a positive byte defining the detail level of the returned data.
* Every increase doubles the width of the returned area.
- * Example values: 0 = block, 1 = 2x2 blocks, 2 = 4x4 blocks, ... 4 = chunk (16x16 blocks), ... 9 = region (512x512 blocks)
+ * Example values: 0 = block, 1 = 2x2 blocks, 2 = 4x4 blocks, ... 4 = chunk (16x16 blocks), ... 9 = region (512x512 blocks)
+ * See {@link EDhApiDetailLevel} for more information.
*/
DhApiResult getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ);
diff --git a/api/src/main/java/com/seibel/distanthorizons/api/methods/events/abstractEvents/DhApiDataFileChangedEvent.java b/api/src/main/java/com/seibel/distanthorizons/api/methods/events/abstractEvents/DhApiDataFileChangedEvent.java
new file mode 100644
index 000000000..b824308ca
--- /dev/null
+++ b/api/src/main/java/com/seibel/distanthorizons/api/methods/events/abstractEvents/DhApiDataFileChangedEvent.java
@@ -0,0 +1,73 @@
+package com.seibel.distanthorizons.api.methods.events.abstractEvents;
+
+import com.seibel.distanthorizons.api.enums.EDhApiDetailLevel;
+import com.seibel.distanthorizons.api.interfaces.data.IDhApiTerrainDataRepo;
+import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
+import com.seibel.distanthorizons.api.objects.events.DhApiEventDefinition;
+
+/**
+ * @author James Seibel
+ * @version 2023-6-19
+ */
+public abstract class DhApiDataFileChangedEvent implements IDhApiEvent
+{
+ /**
+ * Fired after any data files handled by Distant Horizons are modified.
+ * Note: this event may not fire immediately after a change happens,
+ * this event is only fired after the data is saved to disk.
+ */
+ public abstract void onDhDataFileChanged(EventParam input);
+
+
+ //=========================//
+ // internal DH API methods //
+ //=========================//
+
+ @Override
+ public final boolean fireEvent(EventParam input)
+ {
+ this.onDhDataFileChanged(input);
+ return false;
+ }
+
+ public final static DhApiEventDefinition EVENT_DEFINITION = new DhApiEventDefinition(false, false);
+ @Override
+ public final DhApiEventDefinition getEventDefinition() { return EVENT_DEFINITION; }
+
+
+ //==================//
+ // parameter object //
+ //==================//
+
+ /** in order to access the modified data, please use the {@link IDhApiTerrainDataRepo}. */
+ public static class EventParam
+ {
+ /** defines what type of data was modified */
+ public final EDataType dataTypeEnum;
+
+ /** See {@link EDhApiDetailLevel} for more information on detail levels. */
+ public final byte detailLevel;
+ public final int posX;
+ public final int posZ;
+
+
+ public EventParam(EDataType dataTypeEnum, byte detailLevel, int posX, int posZ)
+ {
+ this.dataTypeEnum = dataTypeEnum;
+
+ this.detailLevel = detailLevel;
+ this.posX = posX;
+ this.posZ = posZ;
+ }
+ }
+
+ /** when in doubt, use {@link EDataType#Full}. */
+ public enum EDataType
+ {
+ /** color data, based on the currently selected resource packs */
+ Render,
+ /** ID based data */
+ Full;
+ }
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java
index 767d08d9d..2386eb636 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java
@@ -1,6 +1,7 @@
package com.seibel.distanthorizons.core.file.fullDatafile;
import com.google.common.collect.HashMultimap;
+import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiDataFileChangedEvent;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
@@ -8,20 +9,17 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.HighDetailIn
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.LowDetailIncompleteFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IIncompleteFullDataSource;
-import com.seibel.distanthorizons.core.file.metaData.BaseMetaData;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
-import com.seibel.distanthorizons.core.dataObjects.fullData.sources.*;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource;
import com.seibel.distanthorizons.core.util.FileUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.ThreadUtil;
-import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
-import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
+import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.apache.logging.log4j.Logger;
-import javax.annotation.Nullable;
+
import java.io.File;
import java.io.IOException;
import java.util.*;
@@ -291,6 +289,7 @@ public class FullDataFileHandler implements IFullDataSourceProvider
chunkPos = chunkPos.convertToDetailLevel((byte) this.minDetailLevel);
this.writeChunkDataToMetaFile(new DhSectionPos(chunkPos.detailLevel, chunkPos.x, chunkPos.z), chunkDataView);
+ ApiEventInjector.INSTANCE.fireAllEvents(DhApiDataFileChangedEvent.class, new DhApiDataFileChangedEvent.EventParam(DhApiDataFileChangedEvent.EDataType.Full, (byte)(sectionPos.sectionDetailLevel - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL), sectionPos.sectionX, sectionPos.sectionZ));
}
private void writeChunkDataToMetaFile(DhSectionPos sectionPos, ChunkSizedFullDataAccessor chunkData)
{
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java
index 521e3fe90..5bb8729cd 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java
@@ -1,6 +1,7 @@
package com.seibel.distanthorizons.core.file.renderfile;
import com.google.common.collect.HashMultimap;
+import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiDataFileChangedEvent;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
@@ -14,6 +15,7 @@ import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.util.LodUtil;
+import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.apache.logging.log4j.Logger;
import java.io.File;
@@ -246,6 +248,7 @@ public class RenderSourceFileHandler implements ILodRenderSourceProvider
// convert to the lowest detail level so all detail levels are updated
this.writeChunkDataToFileRecursively(sectionPos.convertToDetailLevel((byte) this.lowestDetailLevel.get()), chunkDataView);
this.fullDataSourceProvider.write(sectionPos, chunkDataView);
+ ApiEventInjector.INSTANCE.fireAllEvents(DhApiDataFileChangedEvent.class, new DhApiDataFileChangedEvent.EventParam(DhApiDataFileChangedEvent.EDataType.Render, (byte)(sectionPos.sectionDetailLevel - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL), sectionPos.sectionX, sectionPos.sectionZ));
}
private void writeChunkDataToFileRecursively(DhSectionPos sectionPos, ChunkSizedFullDataAccessor chunkDataView)
{