Update the API to allow for N-sized world generation requests
This breaks old world generators
This commit is contained in:
+15
-5
@@ -19,8 +19,8 @@
|
||||
|
||||
package com.seibel.distanthorizons.api.enums.worldGeneration;
|
||||
|
||||
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
|
||||
import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator;
|
||||
import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.function.Consumer;
|
||||
@@ -28,16 +28,17 @@ import java.util.function.Consumer;
|
||||
/**
|
||||
* VANILLA_CHUNKS, <br>
|
||||
* API_CHUNKS <br>
|
||||
* API_DATA_SOURCES <br>
|
||||
*
|
||||
* @author Builderb0y, James Seibel
|
||||
* @version 2023-12-21
|
||||
* @version 2024-10-5
|
||||
* @since API 2.0.0
|
||||
*/
|
||||
public enum EDhApiWorldGeneratorReturnType
|
||||
{
|
||||
/**
|
||||
* when this constant is returned by {@link IDhApiWorldGenerator#getReturnType()},
|
||||
* {@link IDhApiWorldGenerator#generateChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)}
|
||||
* {@link IDhApiWorldGenerator#generateChunks(int, int, int, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)}
|
||||
* will be used when generating terrain.
|
||||
*
|
||||
* @since API 2.0.0
|
||||
@@ -46,11 +47,20 @@ public enum EDhApiWorldGeneratorReturnType
|
||||
|
||||
/**
|
||||
* when this constant is returned by {@link IDhApiWorldGenerator#getReturnType()},
|
||||
* {@link IDhApiWorldGenerator#generateApiChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)}
|
||||
* {@link IDhApiWorldGenerator#generateApiChunks(int, int, int, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)}
|
||||
* will be used when generating terrain.
|
||||
*
|
||||
* @since API 2.0.0
|
||||
*/
|
||||
API_CHUNKS;
|
||||
API_CHUNKS,
|
||||
|
||||
/**
|
||||
* when this constant is returned by {@link IDhApiWorldGenerator#getReturnType()},
|
||||
* {@link IDhApiWorldGenerator#generateLod(int, int, byte, IDhApiFullDataSource, EDhApiDistantGeneratorMode, ExecutorService, Consumer)}
|
||||
* will be used when generating terrain.
|
||||
*
|
||||
* @since API 4.0.0
|
||||
*/
|
||||
API_DATA_SOURCES;
|
||||
|
||||
}
|
||||
|
||||
+9
-19
@@ -46,10 +46,6 @@ public abstract class AbstractDhApiChunkWorldGenerator implements Closeable, IDh
|
||||
public final byte getSmallestDataDetailLevel() { return EDhApiDetailLevel.BLOCK.detailLevel; }
|
||||
@Override
|
||||
public final byte getLargestDataDetailLevel() { return EDhApiDetailLevel.BLOCK.detailLevel; }
|
||||
@Override
|
||||
public final byte getMinGenerationGranularity() { return EDhApiDetailLevel.CHUNK.detailLevel; }
|
||||
@Override
|
||||
public final byte getMaxGenerationGranularity() { return (byte) (EDhApiDetailLevel.CHUNK.detailLevel + 2); }
|
||||
|
||||
|
||||
|
||||
@@ -60,17 +56,14 @@ public abstract class AbstractDhApiChunkWorldGenerator implements Closeable, IDh
|
||||
@Override
|
||||
public final CompletableFuture<Void> generateChunks(
|
||||
int chunkPosMinX, int chunkPosMinZ,
|
||||
byte granularity, byte targetDataDetail, EDhApiDistantGeneratorMode generatorMode,
|
||||
int generationRequestChunkWidthCount, byte targetDataDetail, EDhApiDistantGeneratorMode generatorMode,
|
||||
ExecutorService worldGeneratorThreadPool, Consumer<Object[]> resultConsumer) throws ClassCastException
|
||||
{
|
||||
return CompletableFuture.runAsync(() ->
|
||||
{
|
||||
// TODO what does this mean?
|
||||
int genChunkWidth = BitShiftUtil.powerOfTwo(granularity - 4);
|
||||
|
||||
for (int chunkX = chunkPosMinX; chunkX < chunkPosMinX + genChunkWidth; chunkX++)
|
||||
for (int chunkX = chunkPosMinX; chunkX < chunkPosMinX + generationRequestChunkWidthCount; chunkX++)
|
||||
{
|
||||
for (int chunkZ = chunkPosMinZ; chunkZ < chunkPosMinZ + genChunkWidth; chunkZ++)
|
||||
for (int chunkZ = chunkPosMinZ; chunkZ < chunkPosMinZ + generationRequestChunkWidthCount; chunkZ++)
|
||||
{
|
||||
Object[] rawMcObjectArray = this.generateChunk(chunkX, chunkZ, generatorMode);
|
||||
resultConsumer.accept(rawMcObjectArray);
|
||||
@@ -83,7 +76,7 @@ public abstract class AbstractDhApiChunkWorldGenerator implements Closeable, IDh
|
||||
public final CompletableFuture<Void> generateApiChunks(
|
||||
int chunkPosMinX,
|
||||
int chunkPosMinZ,
|
||||
byte granularity,
|
||||
int generationRequestChunkWidthCount,
|
||||
byte targetDataDetail,
|
||||
EDhApiDistantGeneratorMode generatorMode,
|
||||
ExecutorService worldGeneratorThreadPool,
|
||||
@@ -92,12 +85,9 @@ public abstract class AbstractDhApiChunkWorldGenerator implements Closeable, IDh
|
||||
{
|
||||
return CompletableFuture.runAsync(() ->
|
||||
{
|
||||
// TODO what does this mean?
|
||||
int genChunkWidth = BitShiftUtil.powerOfTwo(granularity - 4);
|
||||
|
||||
for (int chunkX = chunkPosMinX; chunkX < chunkPosMinX + genChunkWidth; chunkX++)
|
||||
for (int chunkX = chunkPosMinX; chunkX < chunkPosMinX + generationRequestChunkWidthCount; chunkX++)
|
||||
{
|
||||
for (int chunkZ = chunkPosMinZ; chunkZ < chunkPosMinZ + genChunkWidth; chunkZ++)
|
||||
for (int chunkZ = chunkPosMinZ; chunkZ < chunkPosMinZ + generationRequestChunkWidthCount; chunkZ++)
|
||||
{
|
||||
DhApiChunk apiChunk = this.generateApiChunk(chunkX, chunkZ, generatorMode);
|
||||
resultConsumer.accept(apiChunk);
|
||||
@@ -115,10 +105,10 @@ public abstract class AbstractDhApiChunkWorldGenerator implements Closeable, IDh
|
||||
* @param chunkPosZ the chunk Z position in the level (not to be confused with the chunk's BlockPos in the level)
|
||||
* @param generatorMode how far into the world gen pipeline this method should run. See {@link EDhApiDistantGeneratorMode} for additional documentation.
|
||||
*
|
||||
* @return See {@link IDhApiWorldGenerator#generateChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer) IDhApiWorldGenerator.generateChunks}
|
||||
* @return See {@link IDhApiWorldGenerator#generateChunks(int, int, int, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer) IDhApiWorldGenerator.generateChunks}
|
||||
* for the list of Object's this method should return along with additional documentation.
|
||||
*
|
||||
* @see IDhApiWorldGenerator#generateChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer) IDhApiWorldGenerator#generateChunks
|
||||
* @see IDhApiWorldGenerator#generateChunks(int, int, int, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer) IDhApiWorldGenerator#generateChunks
|
||||
*/
|
||||
public abstract Object[] generateChunk(int chunkPosX, int chunkPosZ, EDhApiDistantGeneratorMode generatorMode);
|
||||
|
||||
@@ -133,7 +123,7 @@ public abstract class AbstractDhApiChunkWorldGenerator implements Closeable, IDh
|
||||
* @return A {@link DhApiChunk} with the generated {@link DhApiTerrainDataPoint} including air blocks.
|
||||
* Note: if air blocks aren't included with the proper lighting, lower detail levels will appear as black/unlit.
|
||||
*
|
||||
* @see IDhApiWorldGenerator#generateApiChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)
|
||||
* @see IDhApiWorldGenerator#generateApiChunks(int, int, int, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)
|
||||
*
|
||||
* @since API 3.0.0
|
||||
*/
|
||||
|
||||
+59
-57
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable;
|
||||
import com.seibel.distanthorizons.api.enums.EDhApiDetailLevel;
|
||||
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
|
||||
import com.seibel.distanthorizons.api.objects.data.DhApiChunk;
|
||||
import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -32,7 +33,7 @@ import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* @author James Seibel
|
||||
* @version 2023-6-22
|
||||
* @version 2024-10-07
|
||||
* @since API 1.0.0
|
||||
*/
|
||||
public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
|
||||
@@ -43,19 +44,17 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
|
||||
|
||||
/**
|
||||
* Defines the smallest datapoint size that can be generated at a time. <br>
|
||||
* Minimum detail level is 0 (1 block) <br>
|
||||
* Maximum detail level (smallest numerical value) is 0 (1 block) <br>
|
||||
* Default detail level is 0 <br>
|
||||
* For more information on what detail levels represent see: {@link EDhApiDetailLevel}. <br><br>
|
||||
*
|
||||
* TODO: System currently only supports 1x1 block per data.
|
||||
*
|
||||
*
|
||||
* @see EDhApiDetailLevel
|
||||
* @since API 1.0.0
|
||||
*/
|
||||
default byte getSmallestDataDetailLevel() { return EDhApiDetailLevel.BLOCK.detailLevel; }
|
||||
/**
|
||||
* Defines the largest datapoint size that can be generated at a time. <br>
|
||||
* Minimum detail level is 0 (1 block) <br>
|
||||
* Maximum detail level (smallest numerical value) is 0 (1 block) <br>
|
||||
* Default detail level is 0 <br>
|
||||
* For more information on what detail levels represent see: {@link EDhApiDetailLevel}.
|
||||
*
|
||||
@@ -64,56 +63,18 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
|
||||
*/
|
||||
default byte getLargestDataDetailLevel() { return EDhApiDetailLevel.BLOCK.detailLevel; }
|
||||
|
||||
/**
|
||||
* When creating generation requests the system will attempt to group nearby tasks together. <br><br>
|
||||
* What is the minimum size a single generation call can batch together? <br>
|
||||
*
|
||||
* Minimum detail level is 4 (the size of a MC chunk) <br>
|
||||
* Default detail level is 4 <br>
|
||||
* For more information on what detail levels represent see: {@link EDhApiDetailLevel}.
|
||||
*
|
||||
* @see EDhApiDetailLevel
|
||||
* @since API 1.0.0
|
||||
*/
|
||||
default byte getMinGenerationGranularity() { return EDhApiDetailLevel.CHUNK.detailLevel; }
|
||||
|
||||
/**
|
||||
* When creating generation requests the system will attempt to group nearby tasks together. <br><br>
|
||||
* What is the maximum size a single generation call can batch together? <br>
|
||||
*
|
||||
* Minimum detail level is 4 (the size of a MC chunk) <br>
|
||||
* Default detail level is 6 (4x4 chunks) <br>
|
||||
* For more information on what detail levels represent see: {@link EDhApiDetailLevel}.
|
||||
*
|
||||
* @see EDhApiDetailLevel
|
||||
* @since API 1.0.0
|
||||
*/
|
||||
default byte getMaxGenerationGranularity() { return (byte) (EDhApiDetailLevel.CHUNK.detailLevel + 2); }
|
||||
|
||||
/**
|
||||
* Starting in API 3.0.0 DH now handles future queuing/management internally. <br><br>
|
||||
*
|
||||
* Previous description: <br>
|
||||
* true if the generator is unable to accept new generation requests. <br>
|
||||
*
|
||||
* @since API 1.0.0
|
||||
* @deprecated API 3.0.0
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean isBusy() { return false; }
|
||||
|
||||
/**
|
||||
* Only used if {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}. <Br>
|
||||
* If true DH will run additional validation on the {@link DhApiChunk}'s returned. <Br>
|
||||
* Used if {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS} or {@link EDhApiWorldGeneratorReturnType#API_DATA_SOURCES}. <Br>
|
||||
* If true DH will run additional validation on the {@link DhApiChunk} or {@link IDhApiFullDataSource}'s returned. <Br>
|
||||
* This should be disabled during release but should be enabled during development to help spot issues with your data format.
|
||||
*
|
||||
* @see #getReturnType()
|
||||
* @see DhApiChunk
|
||||
* @see IDhApiFullDataSource
|
||||
* @see EDhApiWorldGeneratorReturnType#API_CHUNKS
|
||||
* @since API 3.0.0
|
||||
* @since API 4.0.0
|
||||
*/
|
||||
default boolean runApiChunkValidation() { return true; }
|
||||
|
||||
default boolean runApiValidation() { return true; }
|
||||
|
||||
|
||||
|
||||
@@ -143,9 +104,9 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
|
||||
*
|
||||
* @param chunkPosMinX the chunk X position closest to negative infinity
|
||||
* @param chunkPosMinZ the chunk Z position closest to negative infinity
|
||||
* @param granularity TODO find a central location to store the definition of granularity. For now it is stored in the Core method: WorldGenerationQueue#startGenerationEvent
|
||||
* @param generationRequestChunkWidthCount how many chunks wide you should generate
|
||||
* @param targetDataDetail the LOD Detail level requested to generate. See {@link EDhApiDetailLevel} for additional information.
|
||||
* @param generatorMode how far into the world gen pipeline this method run. See {@link EDhApiDistantGeneratorMode} for additional documentation.
|
||||
* @param generatorMode how far into the world gen pipeline this method should run. See {@link EDhApiDistantGeneratorMode} for additional documentation.
|
||||
* @param worldGeneratorThreadPool the thread pool that should be used when generating the returned {@link CompletableFuture}.
|
||||
* @param resultConsumer the consumer that should be fired whenever a chunk finishes generating.
|
||||
*
|
||||
@@ -156,7 +117,7 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
|
||||
default CompletableFuture<Void> generateChunks(
|
||||
int chunkPosMinX,
|
||||
int chunkPosMinZ,
|
||||
byte granularity,
|
||||
int generationRequestChunkWidthCount,
|
||||
byte targetDataDetail,
|
||||
EDhApiDistantGeneratorMode generatorMode,
|
||||
ExecutorService worldGeneratorThreadPool,
|
||||
@@ -165,7 +126,7 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method is called by Distant Horizons to generate terrain over a given area when
|
||||
* {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}. <br><br>
|
||||
@@ -179,9 +140,9 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
|
||||
*
|
||||
* @param chunkPosMinX the chunk X position closest to negative infinity
|
||||
* @param chunkPosMinZ the chunk Z position closest to negative infinity
|
||||
* @param granularity TODO find a central location to store the definition of granularity. For now it is stored in the Core method: WorldGenerationQueue#startGenerationEvent
|
||||
* @param generationRequestChunkWidthCount how many chunks wide you should generate
|
||||
* @param targetDataDetail the LOD Detail level requested to generate. See {@link EDhApiDetailLevel} for additional information.
|
||||
* @param generatorMode how far into the world gen pipeline this method run. See {@link EDhApiDistantGeneratorMode} for additional documentation.
|
||||
* @param generatorMode how far into the world gen pipeline this method should run. See {@link EDhApiDistantGeneratorMode} for additional documentation.
|
||||
* @param worldGeneratorThreadPool the thread pool that should be used when generating the returned {@link CompletableFuture}.
|
||||
* @param resultConsumer the consumer that should be fired whenever a chunk finishes generating.
|
||||
*
|
||||
@@ -192,7 +153,7 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
|
||||
default CompletableFuture<Void> generateApiChunks(
|
||||
int chunkPosMinX,
|
||||
int chunkPosMinZ,
|
||||
byte granularity,
|
||||
int generationRequestChunkWidthCount,
|
||||
byte targetDataDetail,
|
||||
EDhApiDistantGeneratorMode generatorMode,
|
||||
ExecutorService worldGeneratorThreadPool,
|
||||
@@ -201,11 +162,51 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by Distant Horizons to generate terrain over a given area when
|
||||
* {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_DATA_SOURCES}. <br><br>
|
||||
*
|
||||
* After the {@link IDhApiWorldGenerator} has been generated, it should be passed into the
|
||||
* resultConsumer's {@link Consumer#accept(Object)} method.
|
||||
* Note: if air blocks aren't included in the with the {@link DhApiChunk} with proper lighting, lower detail levels will appear as black/unlit.
|
||||
*
|
||||
* @implNote the default implementation of this method throws an {@link UnsupportedOperationException},
|
||||
* and must be overridden when {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}.
|
||||
*
|
||||
* @param chunkPosMinX the chunk X position closest to negative infinity
|
||||
* @param chunkPosMinZ the chunk Z position closest to negative infinity
|
||||
* @param lodPosX the LOD's X position, relative to the given {@link EDhApiDetailLevel}
|
||||
* @param lodPosZ the LOD's Z position, relative to the given {@link EDhApiDetailLevel}
|
||||
* @param detailLevel the LOD Detail level requested to generate. See {@link EDhApiDetailLevel} for additional information.
|
||||
* @param pooledFullDataSource The data source you should populate during your world generation.
|
||||
* This data source is pooled by DH and may be reused multiple times by different internal DH systems. <br>
|
||||
* This data source should <strong>not</strong> be referenced or stored outside of this method nor the executor provided by worldGeneratorThreadPool.
|
||||
* <strong>Attempting to do so will corrupt DH's data.</strong>
|
||||
* @param generatorMode how far into the world gen pipeline this method should run. See {@link EDhApiDistantGeneratorMode} for additional documentation.
|
||||
* @param worldGeneratorThreadPool the thread pool that should be used when generating the returned {@link CompletableFuture}.
|
||||
* @param resultConsumer the consumer that should be fired whenever a chunk finishes generating.
|
||||
*
|
||||
* @return a future that should run on the worldGeneratorThreadPool and complete once the given generation task has completed.
|
||||
*
|
||||
* @since API 4.0.0
|
||||
*/
|
||||
default CompletableFuture<Void> generateLod(
|
||||
int chunkPosMinX, int chunkPosMinZ,
|
||||
int lodPosX, int lodPosZ, byte detailLevel,
|
||||
IDhApiFullDataSource pooledFullDataSource,
|
||||
EDhApiDistantGeneratorMode generatorMode,
|
||||
ExecutorService worldGeneratorThreadPool,
|
||||
Consumer<IDhApiFullDataSource> resultConsumer
|
||||
)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method controls how Distant Horizons requests generated chunks.
|
||||
* By default, the return value is {@link EDhApiWorldGeneratorReturnType#VANILLA_CHUNKS},
|
||||
* which means that {@link #generateChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)}
|
||||
* which means that {@link #generateChunks(int, int, int, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)}
|
||||
* will be invoked whenever Distant Horizons wants to generate terrain with this world generator.
|
||||
*
|
||||
* @since API 2.0.0
|
||||
@@ -238,4 +239,5 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
|
||||
void close();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.seibel.distanthorizons.api.objects.data;
|
||||
|
||||
import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGenerator;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a single full LOD backed by Distant Horizons' ID system.
|
||||
*
|
||||
* @see IDhApiWorldGenerator
|
||||
* @since API 4.0.0
|
||||
*/
|
||||
public interface IDhApiFullDataSource
|
||||
{
|
||||
/** @return how many data columns wide this data source is */
|
||||
int getWidthInDataColumns();
|
||||
|
||||
/**
|
||||
* Sets the data column at the relative X and Z position to the list given.
|
||||
* The given list may be resorted based on the internal format DH requires.
|
||||
*
|
||||
* @param relX can be in the range 0 to {@link IDhApiFullDataSource#getWidthInDataColumns()}-1 (both inclusive)
|
||||
* @param relZ can be in the range 0 to {@link IDhApiFullDataSource#getWidthInDataColumns()}-1 (both inclusive)
|
||||
*
|
||||
* @return the same columnDataPoints list after it has been imported into the data source.
|
||||
* The returned list and contained objects can then be re-used.
|
||||
*
|
||||
* @throws IndexOutOfBoundsException if the relative positions are negative or outside the bounds of this data source.
|
||||
*/
|
||||
List<DhApiTerrainDataPoint> setApiDataPointColumn(int relX, int relZ, List<DhApiTerrainDataPoint> columnDataPoints)
|
||||
throws IndexOutOfBoundsException, IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* @param relX can be in the range 0 to {@link IDhApiFullDataSource#getWidthInDataColumns()}-1 (both inclusive)
|
||||
* @param relZ can be in the range 0 to {@link IDhApiFullDataSource#getWidthInDataColumns()}-1 (both inclusive)
|
||||
*
|
||||
* @return a {@link List} of {@link DhApiTerrainDataPoint} representing the data for the given relative position.
|
||||
*
|
||||
* @throws IndexOutOfBoundsException if the relative positions are negative or outside the bounds of this data source.
|
||||
*/
|
||||
List<DhApiTerrainDataPoint> getApiDataPointColumn(int relX, int relZ) throws IndexOutOfBoundsException;
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user