mirror of
https://github.com/PolyhedralDev/Terra.git
synced 2026-04-04 23:06:05 +00:00
remove region implementation
This commit is contained in:
@@ -1,21 +0,0 @@
|
||||
import com.dfsek.terra.configureCommon
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
configureCommon()
|
||||
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url = uri("https://jitpack.io/") }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
"shadedApi"(project(":common:implementation"))
|
||||
"shadedImplementation"("com.github.Querz:NBT:5.2") // Standalone NBT API
|
||||
"shadedImplementation"("org.yaml:snakeyaml:1.27")
|
||||
"shadedImplementation"("com.googlecode.json-simple:json-simple:1.1.1")
|
||||
}
|
||||
|
||||
tasks.named<ShadowJar>("shadowJar") {
|
||||
relocate("net.querz", "com.dfsek.terra.libs.nbt")
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.dfsek.terra;
|
||||
|
||||
import com.dfsek.terra.api.util.MathUtil;
|
||||
|
||||
import net.querz.mca.MCAUtil;
|
||||
|
||||
|
||||
public final class DirectUtils {
|
||||
|
||||
/**
|
||||
* Compute long region ID from chunk coords
|
||||
*
|
||||
* @param x X
|
||||
* @param z Z
|
||||
*
|
||||
* @return Region IS
|
||||
*/
|
||||
public static long regionID(int x, int z) {
|
||||
return MathUtil.squash(MCAUtil.chunkToRegion(x), MCAUtil.chunkToRegion(z));
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.dfsek.terra;
|
||||
|
||||
import com.dfsek.terra.region.Generator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
|
||||
public class RegionGenerator {
|
||||
public static void main(String[] args) throws IOException {
|
||||
long seed;
|
||||
if(args.length == 1) seed = Long.parseLong(args[0]);
|
||||
else seed = ThreadLocalRandom.current().nextLong();
|
||||
|
||||
StandalonePlugin plugin = new StandalonePlugin();
|
||||
Generator generator = new Generator(seed, plugin);
|
||||
|
||||
generator.generate();
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
package com.dfsek.terra;
|
||||
|
||||
import com.dfsek.tectonic.api.TypeRegistry;
|
||||
|
||||
import com.dfsek.terra.api.TerraPlugin;
|
||||
import com.dfsek.terra.api.addon.TerraAddon;
|
||||
import com.dfsek.terra.api.block.state.BlockState;
|
||||
import com.dfsek.terra.api.config.ConfigPack;
|
||||
import com.dfsek.terra.api.config.PluginConfig;
|
||||
import com.dfsek.terra.api.event.EventManager;
|
||||
import com.dfsek.terra.api.handle.ItemHandle;
|
||||
import com.dfsek.terra.api.handle.WorldHandle;
|
||||
import com.dfsek.terra.api.lang.Language;
|
||||
import com.dfsek.terra.api.profiler.Profiler;
|
||||
import com.dfsek.terra.api.registry.registry.CheckedRegistry;
|
||||
import com.dfsek.terra.api.registry.registry.Registry;
|
||||
import com.dfsek.terra.api.world.biome.Biome;
|
||||
import com.dfsek.terra.config.GenericLoaders;
|
||||
import com.dfsek.terra.config.PluginConfigImpl;
|
||||
import com.dfsek.terra.config.lang.LangUtil;
|
||||
import com.dfsek.terra.config.lang.LanguageImpl;
|
||||
import com.dfsek.terra.event.EventManagerImpl;
|
||||
import com.dfsek.terra.platform.RawBiome;
|
||||
import com.dfsek.terra.platform.RawWorldHandle;
|
||||
import com.dfsek.terra.profiler.ProfilerImpl;
|
||||
import com.dfsek.terra.registry.CheckedRegistryImpl;
|
||||
import com.dfsek.terra.registry.LockedRegistryImpl;
|
||||
import com.dfsek.terra.registry.master.AddonRegistry;
|
||||
import com.dfsek.terra.registry.master.ConfigRegistry;
|
||||
import com.dfsek.terra.util.logging.DebugLogger;
|
||||
import com.dfsek.terra.util.logging.JavaLogger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
public class StandalonePlugin implements TerraPlugin {
|
||||
private final ConfigRegistry registry = new ConfigRegistry();
|
||||
private final AddonRegistry addonRegistry = new AddonRegistry(this);
|
||||
|
||||
private final Registry<TerraAddon> addonLockedRegistry = new LockedRegistryImpl<>(addonRegistry);
|
||||
|
||||
private final PluginConfig config = new PluginConfigImpl();
|
||||
private final RawWorldHandle worldHandle = new RawWorldHandle();
|
||||
private final EventManager eventManager = new EventManagerImpl(this);
|
||||
|
||||
private final Profiler profiler = new ProfilerImpl();
|
||||
|
||||
@Override
|
||||
public WorldHandle getWorldHandle() {
|
||||
return worldHandle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public com.dfsek.terra.api.Logger logger() {
|
||||
return new JavaLogger(Logger.getLogger("Terra"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginConfig getTerraConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDataFolder() {
|
||||
return new File(".");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Language getLanguage() {
|
||||
try {
|
||||
return new LanguageImpl(new File(getDataFolder(), "lang/en_us.yml"));
|
||||
} catch(IOException e) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CheckedRegistry<ConfigPack> getConfigRegistry() {
|
||||
return new CheckedRegistryImpl<>(registry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Registry<TerraAddon> getAddons() {
|
||||
return addonLockedRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean reload() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemHandle getItemHandle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveDefaultConfig() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String platformName() {
|
||||
return "Standalone";
|
||||
}
|
||||
|
||||
@Override
|
||||
public DebugLogger getDebugLogger() {
|
||||
Logger logger = Logger.getLogger("Terra");
|
||||
return new DebugLogger(new com.dfsek.terra.api.Logger() {
|
||||
@Override
|
||||
public void info(String message) {
|
||||
logger.info(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(String message) {
|
||||
logger.warning(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String message) {
|
||||
logger.severe(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(TypeRegistry registry) {
|
||||
registry
|
||||
.registerLoader(BlockState.class, (t, o, l) -> worldHandle.createBlockData((String) o))
|
||||
.registerLoader(Biome.class, (t, o, l) -> new RawBiome(o.toString()));
|
||||
new GenericLoaders(this).register(registry);
|
||||
}
|
||||
|
||||
public void load() {
|
||||
LangUtil.load("en_us", this);
|
||||
registry.loadAll(this);
|
||||
config.load(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventManager getEventManager() {
|
||||
return eventManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Profiler getProfiler() {
|
||||
return profiler;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.dfsek.terra.platform;
|
||||
|
||||
import com.dfsek.terra.api.block.entity.BlockEntity;
|
||||
import com.dfsek.terra.api.block.state.BlockState;
|
||||
import com.dfsek.terra.api.vector.Vector3;
|
||||
|
||||
|
||||
public class DirectBlockEntity implements BlockEntity {
|
||||
@Override
|
||||
public Vector3 getPosition() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getX() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getY() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getZ() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean update(boolean applyPhysics) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHandle() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.dfsek.terra.platform;
|
||||
|
||||
import com.dfsek.terra.api.block.state.BlockState;
|
||||
import com.dfsek.terra.api.world.World;
|
||||
import com.dfsek.terra.api.world.generator.ChunkData;
|
||||
|
||||
import net.querz.mca.Chunk;
|
||||
import net.querz.nbt.tag.CompoundTag;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
public class DirectChunkData implements ChunkData, com.dfsek.terra.api.world.Chunk {
|
||||
private final Chunk delegate;
|
||||
private final DirectWorld world;
|
||||
private final int x;
|
||||
private final int z;
|
||||
|
||||
public DirectChunkData(Chunk delegate, DirectWorld world, int x, int z) {
|
||||
this.delegate = delegate;
|
||||
this.world = world;
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHandle() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHeight() {
|
||||
return 255;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull BlockState blockState) {
|
||||
delegate.setBlockStateAt(x, y, z, ((State) blockState).getHandle(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull
|
||||
BlockState getBlock(int x, int y, int z) {
|
||||
CompoundTag tag = delegate.getBlockStateAt(x, y, z);
|
||||
if(tag == null) return new State("minecraft:air");
|
||||
return new State(tag.getString("Name"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, BlockState data, boolean physics) {
|
||||
setBlock(x, y, z, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getZ() {
|
||||
return z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public World getWorld() {
|
||||
return world;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package com.dfsek.terra.platform;
|
||||
|
||||
import com.dfsek.terra.DirectUtils;
|
||||
import com.dfsek.terra.api.block.entity.BlockEntity;
|
||||
import com.dfsek.terra.api.block.state.BlockState;
|
||||
import com.dfsek.terra.api.entity.Entity;
|
||||
import com.dfsek.terra.api.entity.EntityType;
|
||||
import com.dfsek.terra.api.vector.Vector3;
|
||||
import com.dfsek.terra.api.world.Chunk;
|
||||
import com.dfsek.terra.api.world.World;
|
||||
|
||||
import net.jafama.FastMath;
|
||||
import net.querz.mca.MCAFile;
|
||||
import net.querz.mca.MCAUtil;
|
||||
import net.querz.nbt.tag.CompoundTag;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class DirectWorld implements World {
|
||||
private final long seed;
|
||||
private final GenWrapper generator;
|
||||
private final Map<Long, MCAFile> files = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
public DirectWorld(long seed) {
|
||||
this.seed = seed;
|
||||
this.generator = generator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSeed() {
|
||||
return seed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHeight() {
|
||||
return 255;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Chunk getChunkAt(int x, int z) {
|
||||
MCAFile file = compute(x, z);
|
||||
net.querz.mca.Chunk chunk = file.getChunk(x, z);
|
||||
if(chunk == null) {
|
||||
chunk = net.querz.mca.Chunk.newChunk();
|
||||
file.setChunk(x, z, chunk);
|
||||
}
|
||||
return new DirectChunkData(chunk, this, x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockData(int x, int y, int z) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlockData(int x, int y, int z, BlockState data, boolean physics) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockEntity getBlockState(int x, int y, int z) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entity spawnEntity(Vector3 location, EntityType entityType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinHeight() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHandle() {
|
||||
return generator;
|
||||
}
|
||||
|
||||
public MCAFile compute(int x, int z) {
|
||||
synchronized(files) {
|
||||
return files.computeIfAbsent(DirectUtils.regionID(x, z), k -> {
|
||||
File test = new File("region", MCAUtil.createNameFromChunkLocation(x, z));
|
||||
if(test.exists()) {
|
||||
try {
|
||||
System.out.println("Re-loading " + MCAUtil.createNameFromChunkLocation(x, z));
|
||||
return MCAUtil.read(test);
|
||||
} catch(IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return new MCAFile(MCAUtil.chunkToRegion(x), MCAUtil.chunkToRegion(z));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public CompoundTag getData(int x, int y, int z) {
|
||||
return compute(FastMath.floorDiv(x, 16), FastMath.floorDiv(z, 16)).getBlockStateAt(x, y, z);
|
||||
}
|
||||
|
||||
public Map<Long, MCAFile> getFiles() {
|
||||
return files;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.dfsek.terra.platform;
|
||||
|
||||
import com.dfsek.terra.api.world.biome.Biome;
|
||||
|
||||
|
||||
public class RawBiome implements Biome {
|
||||
private final String id;
|
||||
|
||||
public RawBiome(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHandle() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.dfsek.terra.platform;
|
||||
|
||||
import com.dfsek.terra.api.util.collection.MaterialSet;
|
||||
import com.dfsek.terra.api.vector.Vector3;
|
||||
import com.dfsek.terra.api.world.Tree;
|
||||
import com.dfsek.terra.api.world.World;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
public class RawTree implements Tree { // TODO: implement
|
||||
@Override
|
||||
public boolean plant(Vector3 l, World world, Random r) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialSet getSpawnable() {
|
||||
return MaterialSet.empty();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.dfsek.terra.platform;
|
||||
|
||||
import com.dfsek.terra.api.block.state.BlockState;
|
||||
import com.dfsek.terra.api.entity.EntityType;
|
||||
import com.dfsek.terra.api.handle.WorldHandle;
|
||||
|
||||
|
||||
public class RawWorldHandle implements WorldHandle {
|
||||
|
||||
@Override
|
||||
public BlockState createBlockData(String data) {
|
||||
return new State(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState air() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityType getEntity(String id) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package com.dfsek.terra.platform;
|
||||
|
||||
import com.dfsek.terra.api.block.BlockType;
|
||||
import com.dfsek.terra.api.block.state.BlockState;
|
||||
import com.dfsek.terra.api.block.state.properties.Property;
|
||||
|
||||
import net.querz.nbt.tag.CompoundTag;
|
||||
|
||||
|
||||
public class State implements BlockState, BlockType {
|
||||
private final CompoundTag data;
|
||||
private final String noProp;
|
||||
|
||||
public State(String data) {
|
||||
this.data = new CompoundTag();
|
||||
if(data.contains("[")) {
|
||||
noProp = data.substring(0, data.indexOf('[')); // Strip properties
|
||||
String properties = data.substring(data.indexOf('[') + 1, data.indexOf(']'));
|
||||
String[] props = properties.split(",");
|
||||
CompoundTag pTag = new CompoundTag();
|
||||
for(String property : props) {
|
||||
String name = property.substring(0, property.indexOf('='));
|
||||
String val = property.substring(property.indexOf('=') + 1);
|
||||
|
||||
pTag.putString(name, val);
|
||||
}
|
||||
this.data.put("Properties", pTag);
|
||||
} else noProp = data;
|
||||
this.data.putString("Name", noProp);
|
||||
}
|
||||
|
||||
public State(CompoundTag tag) {
|
||||
if(tag == null) {
|
||||
this.data = new CompoundTag();
|
||||
data.putString("Name", "minecraft:air");
|
||||
} else {
|
||||
this.data = tag;
|
||||
}
|
||||
noProp = data.getString("Name");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BlockType getBlockType() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(BlockState other) {
|
||||
return ((State) other).noProp.equals(noProp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAir() {
|
||||
return noProp.equals("minecraft:air");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStructureVoid() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean has(Property<T> property) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T get(Property<T> property) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> BlockState set(Property<T> property, T value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BlockState clone() {
|
||||
try {
|
||||
return (BlockState) super.clone();
|
||||
} catch(CloneNotSupportedException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAsString() {
|
||||
return noProp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getHandle() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return noProp.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if(!(obj instanceof State)) return false;
|
||||
return ((State) obj).noProp.equals(noProp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getDefaultData() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSolid() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWater() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package com.dfsek.terra.region;
|
||||
|
||||
import com.dfsek.terra.StandalonePlugin;
|
||||
import com.dfsek.terra.api.world.generator.TerraChunkGenerator;
|
||||
import com.dfsek.terra.platform.DirectChunkData;
|
||||
import com.dfsek.terra.platform.DirectWorld;
|
||||
|
||||
import net.querz.mca.MCAFile;
|
||||
import net.querz.mca.MCAUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class Generator {
|
||||
private final long seed;
|
||||
TerraChunkGenerator generator;
|
||||
|
||||
public Generator(long seed, StandalonePlugin plugin) {
|
||||
plugin.load();
|
||||
//generator = new DefaultChunkGenerator3D(plugin.getConfigRegistry().get("DEFAULT"), plugin);
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
public void generate() throws IOException {
|
||||
|
||||
int rad = 64;
|
||||
System.out.println("Total mem: " + Runtime.getRuntime().maxMemory() / 1024 / 1024 / 1024 + "GB");
|
||||
|
||||
|
||||
DirectWorld world = new DirectWorld(seed, null);
|
||||
|
||||
long l = System.nanoTime();
|
||||
int count = 0;
|
||||
|
||||
for(int cx = -rad; cx <= rad; cx++) {
|
||||
for(int cz = -rad; cz <= rad; cz++) {
|
||||
DirectChunkData chunkData = (DirectChunkData) world.getChunkAt(cx, cz);
|
||||
generator.generateChunkData(world, null, cx, cz, chunkData);
|
||||
|
||||
count++;
|
||||
|
||||
if(count % 200 == 0) {
|
||||
long n = System.nanoTime();
|
||||
|
||||
System.out.println("Generated " + count + " chunks. " + 200 / ((double) (n - l) / 1000000) * 1000 + "cps.");
|
||||
|
||||
l = System.nanoTime();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
System.out.println("Saving...");
|
||||
|
||||
for(Map.Entry<Long, MCAFile> entry : world.getFiles().entrySet()) {
|
||||
if(entry.getValue() == null) continue;
|
||||
entry.getValue().cleanupPalettesAndBlockStates();
|
||||
int x = (int) (entry.getKey() >> 32);
|
||||
int z = (int) (long) entry.getKey();
|
||||
File file = new File("region", MCAUtil.createNameFromRegionLocation(x, z));
|
||||
file.getParentFile().mkdirs();
|
||||
MCAUtil.write(entry.getValue(), file);
|
||||
}
|
||||
|
||||
System.out.println("Done in " + (System.nanoTime() - l) / 1000000000 + "s");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user