Work on integrating sqlite
This commit is contained in:
parent
9fdb9a23c0
commit
194592c18d
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<diagram version="1" name="MyDiagram">
|
||||
<entities/>
|
||||
<relations/>
|
||||
</diagram>
|
||||
0
saves/default/central.db
Normal file
0
saves/default/central.db
Normal file
@ -25,6 +25,7 @@ import electrosphere.game.collision.PhysicsUtils;
|
||||
import electrosphere.game.collision.collidable.Collidable;
|
||||
import electrosphere.game.server.ai.creature.MindlessAttacker;
|
||||
import electrosphere.game.server.effects.ParticleEffects;
|
||||
import electrosphere.game.server.saves.SaveUtils;
|
||||
import electrosphere.game.server.terrain.models.TerrainModification;
|
||||
import electrosphere.game.server.town.Town;
|
||||
import electrosphere.game.server.world.MacroData;
|
||||
@ -108,18 +109,28 @@ public class LoadingThread extends Thread {
|
||||
Globals.controlHandler.setHandlerState(ControlHandler.ControlsState.NO_INPUT);
|
||||
|
||||
//initialize the terrain manager (server only)
|
||||
// if(Globals.RUN_SERVER){
|
||||
// initServerGameTerrainManager();
|
||||
// }
|
||||
|
||||
if(Globals.RUN_SERVER){
|
||||
initServerGameTerrainManager();
|
||||
//TODO: Globals.serverTerrainManager = new ServerTerrainManager(2000,50,100,randomDampener,0);
|
||||
Globals.serverTerrainManager = new ServerTerrainManager(2000,50,100,0.0f,0);
|
||||
SaveUtils.loadSave(Globals.currentSaveName);
|
||||
//TODO: set spawnpoint
|
||||
//TODO: Globals.serverWorldData = ServerWorldData.createGameWorld(Globals.serverTerrainManager);
|
||||
//TODO: Globals.dataCellManager = new DataCellManager(Globals.serverWorldData);
|
||||
Globals.serverWorldData = ServerWorldData.createGameWorld(Globals.serverTerrainManager);
|
||||
Globals.dataCellManager = new DataCellManager(Globals.serverWorldData);
|
||||
}
|
||||
|
||||
LoggerInterface.loggerEngine.INFO("run server: " + Globals.RUN_SERVER + " run client: " + Globals.RUN_CLIENT);
|
||||
|
||||
//init the data of the world
|
||||
if(Globals.RUN_SERVER){
|
||||
initServerGameWorldData();
|
||||
createServerWorld();
|
||||
initDataCellManager();
|
||||
}
|
||||
// if(Globals.RUN_SERVER){
|
||||
// initServerGameWorldData();
|
||||
// initDataCellManager();
|
||||
// }
|
||||
|
||||
//initialize the server thread (server only)
|
||||
if(Globals.RUN_SERVER){
|
||||
@ -210,6 +221,7 @@ public class LoadingThread extends Thread {
|
||||
initCollisionEngine(Globals.RUN_SERVER);
|
||||
|
||||
//initialize the "virtual" objects simulation
|
||||
//not really relevant in arena mode
|
||||
// initMacroSimulation();
|
||||
|
||||
//initialize the "real" objects simulation
|
||||
@ -291,7 +303,7 @@ public class LoadingThread extends Thread {
|
||||
Globals.serverTerrainManager.generate();
|
||||
Globals.serverTerrainManager.save();
|
||||
} else {
|
||||
Globals.serverTerrainManager.load();
|
||||
SaveUtils.loadSave(Globals.currentSaveName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -345,16 +357,6 @@ public class LoadingThread extends Thread {
|
||||
Globals.serverTerrainManager.getChunk(0, 0).addModification(new TerrainModification(0,0,5,5,5));
|
||||
}
|
||||
|
||||
static void createServerWorld(){
|
||||
// Vector2i townLoc = Town.findValidTownLocation();
|
||||
// if(townLoc != null){
|
||||
// int chunkSize = Globals.serverTerrainManager.getChunkWidth();
|
||||
// Globals.spawnPoint = new Vector3f(townLoc.x * chunkSize, (float)Globals.serverTerrainManager.getHeightAtPosition(townLoc.x * chunkSize,townLoc.y * chunkSize), townLoc.y * chunkSize);
|
||||
//// System.out.println("Setting spawn point @ " + Globals.spawnPoint);
|
||||
//// Town.createTown(townLoc.x,townLoc.y);
|
||||
// }
|
||||
}
|
||||
|
||||
static void initServerGameWorldData(){
|
||||
Globals.serverWorldData = ServerWorldData.createGameWorld(Globals.serverTerrainManager);
|
||||
}
|
||||
@ -483,6 +485,7 @@ public class LoadingThread extends Thread {
|
||||
double startX = firstPos.x * Globals.serverTerrainManager.getChunkWidth();
|
||||
double startZ = firstPos.y * Globals.serverTerrainManager.getChunkWidth();
|
||||
Globals.spawnPoint.set((float)startX,(float)Globals.commonWorldData.getElevationAtPoint(new Vector3d(startX,0,startZ)),(float)startZ);
|
||||
Globals.macroSimulation.setReady(true);
|
||||
}
|
||||
|
||||
|
||||
@ -544,14 +547,14 @@ public class LoadingThread extends Thread {
|
||||
//// EntityUtils.getEntityRotation(tree).rotateAxis((float)-Math.PI/2.0f, new Vector3f(1,0,0));
|
||||
// }
|
||||
|
||||
Random rand = new Random();
|
||||
for(int i = 0; i < 1000; i++){
|
||||
String wheatPath = "Models/wheat2.fbx";
|
||||
Entity wheatStalk = EntityUtils.spawnDrawableEntity(wheatPath);
|
||||
EntityUtils.getPosition(wheatStalk).set(rand.nextFloat() * 20, 0, rand.nextFloat() * 20);
|
||||
EntityUtils.getRotation(wheatStalk).rotateLocalX(-(float)Math.PI/2.0f);
|
||||
EntityUtils.getScale(wheatStalk).set(1, 1, 2);
|
||||
}
|
||||
// Random rand = new Random();
|
||||
// for(int i = 0; i < 1000; i++){
|
||||
// String wheatPath = "Models/wheat2.fbx";
|
||||
// Entity wheatStalk = EntityUtils.spawnDrawableEntity(wheatPath);
|
||||
// EntityUtils.getPosition(wheatStalk).set(rand.nextFloat() * 20, 0, rand.nextFloat() * 20);
|
||||
// EntityUtils.getRotation(wheatStalk).rotateLocalX(-(float)Math.PI/2.0f);
|
||||
// EntityUtils.getScale(wheatStalk).set(1, 1, 2);
|
||||
// }
|
||||
|
||||
// String buildingPath = "Models/building1.fbx";
|
||||
// Entity building = EntityUtils.spawnDrawableEntity(buildingPath);
|
||||
|
||||
@ -6,7 +6,7 @@ import electrosphere.game.config.item.type.model.ItemTypeMap;
|
||||
import electrosphere.game.config.structure.type.model.StructureTypeMap;
|
||||
import electrosphere.game.server.race.model.RaceMap;
|
||||
import electrosphere.game.server.symbolism.model.SymbolMap;
|
||||
import electrosphere.util.FileLoadingUtils;
|
||||
import electrosphere.util.FileUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -23,12 +23,12 @@ public class Config {
|
||||
|
||||
public static Config loadDefaultConfig(){
|
||||
Config config = new Config();
|
||||
config.creatureMap = FileLoadingUtils.loadObjectFromAssetPath("Data/creatures.json", CreatureTypeMap.class);
|
||||
config.itemMap = FileLoadingUtils.loadObjectFromAssetPath("Data/items.json", ItemTypeMap.class);
|
||||
config.structureTypeMap = FileLoadingUtils.loadObjectFromAssetPath("Data/structures.json", StructureTypeMap.class);
|
||||
config.foliageMap = FileLoadingUtils.loadObjectFromAssetPath("Data/foliage.json", FoliageTypeMap.class);
|
||||
config.symbolMap = FileLoadingUtils.loadObjectFromAssetPath("Data/symbolism.json", SymbolMap.class);
|
||||
config.raceMap = FileLoadingUtils.loadObjectFromAssetPath("Data/races.json", RaceMap.class);
|
||||
config.creatureMap = FileUtils.loadObjectFromAssetPath("Data/creatures.json", CreatureTypeMap.class);
|
||||
config.itemMap = FileUtils.loadObjectFromAssetPath("Data/items.json", ItemTypeMap.class);
|
||||
config.structureTypeMap = FileUtils.loadObjectFromAssetPath("Data/structures.json", StructureTypeMap.class);
|
||||
config.foliageMap = FileUtils.loadObjectFromAssetPath("Data/foliage.json", FoliageTypeMap.class);
|
||||
config.symbolMap = FileUtils.loadObjectFromAssetPath("Data/symbolism.json", SymbolMap.class);
|
||||
config.raceMap = FileUtils.loadObjectFromAssetPath("Data/races.json", RaceMap.class);
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package electrosphere.game.config;
|
||||
|
||||
import electrosphere.main.Globals;
|
||||
import electrosphere.util.FileLoadingUtils;
|
||||
import electrosphere.util.FileUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -126,7 +126,7 @@ public class UserSettings {
|
||||
|
||||
|
||||
public static void loadUserSettings(){
|
||||
Globals.userSettings = FileLoadingUtils.loadObjectFromAssetPath("/Config/settings.json", UserSettings.class);
|
||||
Globals.userSettings = FileUtils.loadObjectFromAssetPath("/Config/settings.json", UserSettings.class);
|
||||
if(Globals.userSettings == null){
|
||||
Globals.userSettings = getDefault();
|
||||
}
|
||||
|
||||
@ -87,4 +87,12 @@ public class DatabaseController {
|
||||
return rVal;
|
||||
}
|
||||
|
||||
public void disconnect(){
|
||||
try {
|
||||
conn.close();
|
||||
} catch (SQLException ex) {
|
||||
LoggerInterface.loggerEngine.ERROR("Error disconnecting from DB", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
package electrosphere.game.server.db;
|
||||
|
||||
import electrosphere.logger.LoggerInterface;
|
||||
import electrosphere.util.FileUtils;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author satellite
|
||||
*/
|
||||
public class DatabaseUtils {
|
||||
public static boolean initCentralDBFile(String path){
|
||||
String sanitizedPath = "." + FileUtils.sanitizeFilePath(path);
|
||||
if(!FileUtils.checkDirectoryExists(sanitizedPath)){
|
||||
return false;
|
||||
}
|
||||
String dbFilePath = sanitizedPath + "/central.db";
|
||||
DatabaseController controller = new DatabaseController();
|
||||
controller.connect(dbFilePath);
|
||||
String rawScript = "";
|
||||
try {
|
||||
rawScript = FileUtils.getSQLScriptFileAsString("createTables.sql");
|
||||
} catch (IOException ex) {
|
||||
LoggerInterface.loggerEngine.ERROR("Failure reading create db script", ex);
|
||||
return false;
|
||||
}
|
||||
String[] scriptLines = rawScript.split("\n");
|
||||
for(String line : scriptLines){
|
||||
if(line.length() > 1 && !line.startsWith("--")){
|
||||
System.out.println("EXECUTE: " + line);
|
||||
controller.executeStatement(line);
|
||||
}
|
||||
}
|
||||
controller.disconnect();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
14
src/main/java/electrosphere/game/server/saves/Save.java
Normal file
14
src/main/java/electrosphere/game/server/saves/Save.java
Normal file
@ -0,0 +1,14 @@
|
||||
package electrosphere.game.server.saves;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author satellite
|
||||
*/
|
||||
public class Save {
|
||||
/*
|
||||
FOR FUTURE USE
|
||||
idea is if we want to dump a bunch of like managers into a json file
|
||||
we can put them all in here then serialize this instead
|
||||
or smthn
|
||||
*/
|
||||
}
|
||||
62
src/main/java/electrosphere/game/server/saves/SaveUtils.java
Normal file
62
src/main/java/electrosphere/game/server/saves/SaveUtils.java
Normal file
@ -0,0 +1,62 @@
|
||||
package electrosphere.game.server.saves;
|
||||
|
||||
import electrosphere.game.server.db.DatabaseUtils;
|
||||
import electrosphere.main.Globals;
|
||||
import electrosphere.util.FileUtils;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author satellite
|
||||
*/
|
||||
public class SaveUtils {
|
||||
|
||||
static String deriveSaveDirectoryPath(String saveName){
|
||||
return "./saves/" + saveName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a save directory
|
||||
* @param saveName
|
||||
* @return true if initialized save, false if couldn't initialize
|
||||
*/
|
||||
public static boolean initSave(String saveName){
|
||||
String dirPath = deriveSaveDirectoryPath(saveName);
|
||||
//check if exists
|
||||
if(FileUtils.checkDirectoryExists(dirPath)){
|
||||
return false;
|
||||
}
|
||||
//create dir
|
||||
if(!FileUtils.createDirectory(dirPath)){
|
||||
//we for some unknown reason, couldn't make the save dir
|
||||
return false;
|
||||
}
|
||||
//init db file
|
||||
if(!DatabaseUtils.initCentralDBFile(dirPath)){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static boolean overwriteSave(String saveName){
|
||||
boolean rVal = false;
|
||||
return rVal;
|
||||
}
|
||||
|
||||
|
||||
public static boolean loadSave(String saveName){
|
||||
String dirPath = deriveSaveDirectoryPath(saveName);
|
||||
String dbFilePath = FileUtils.sanitizeFilePath(dirPath) + "/central.db";
|
||||
Globals.dbController.connect(dbFilePath);
|
||||
Globals.serverTerrainManager.load(saveName);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static List<String> getSaves(){
|
||||
return FileUtils.listDirectory("./saves");
|
||||
}
|
||||
|
||||
}
|
||||
@ -7,7 +7,7 @@ import electrosphere.game.server.terrain.generation.TerrainGen;
|
||||
import electrosphere.game.server.terrain.models.ModificationList;
|
||||
import electrosphere.game.server.terrain.models.TerrainModel;
|
||||
import electrosphere.game.server.terrain.models.TerrainModification;
|
||||
import electrosphere.util.FileLoadingUtils;
|
||||
import electrosphere.util.FileUtils;
|
||||
import electrosphere.util.Utilities;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@ -91,9 +91,8 @@ public class ServerTerrainManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void load(){
|
||||
Gson gson = new Gson();
|
||||
model = FileLoadingUtils.loadObjectFromSavePath("./terrain.json", TerrainModel.class);
|
||||
public void load(String saveName){
|
||||
model = FileUtils.loadObjectFromSavePath(saveName, "./terrain.json", TerrainModel.class);
|
||||
}
|
||||
|
||||
public float[][] getTerrainAtChunk(int x, int y){
|
||||
|
||||
@ -14,7 +14,7 @@ import electrosphere.game.server.character.CharacterDataStrings;
|
||||
import electrosphere.game.server.character.CharacterUtils;
|
||||
import electrosphere.game.server.structure.virtual.StructureDataStrings;
|
||||
import electrosphere.game.server.structure.virtual.VirtualStructureUtils;
|
||||
import electrosphere.util.FileLoadingUtils;
|
||||
import electrosphere.util.FileUtils;
|
||||
import electrosphere.util.Utilities;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
@ -35,7 +35,6 @@ public class MacroSimulation {
|
||||
boolean isReady = false;
|
||||
|
||||
public MacroSimulation(){
|
||||
isReady = true;
|
||||
}
|
||||
|
||||
void init(){
|
||||
@ -46,115 +45,123 @@ public class MacroSimulation {
|
||||
public void simulate(){
|
||||
for(Character character : Globals.macroData.getAliveCharacters()){
|
||||
//do something
|
||||
checkForShelter();
|
||||
checkTownMembership();
|
||||
checkForShelter(character);
|
||||
checkTownMembership(character);
|
||||
}
|
||||
}
|
||||
|
||||
public void setReady(boolean status){
|
||||
isReady = status;
|
||||
}
|
||||
|
||||
public boolean isReady(){
|
||||
return isReady;
|
||||
}
|
||||
|
||||
static final int MAX_PLACE_ATTEMPTS = 10;
|
||||
|
||||
static void checkForShelter(){
|
||||
for(Character chara : Globals.macroData.getAliveCharacters()){
|
||||
/*
|
||||
If doesn’t have shelter, check if in town
|
||||
If in town,
|
||||
check if there’s an inn/church/friendly family
|
||||
if so, try to stay there
|
||||
if can’t find place to stay, fashion makeshift shelter
|
||||
If no town
|
||||
fashion makeshift shelter
|
||||
*/
|
||||
if(!chara.getDataKeys().contains(CharacterDataStrings.SHELTER)){
|
||||
Vector2i charPos = CharacterUtils.getDiscretePosition(chara);
|
||||
Town nearbyTown = Town.getTownAtPosition(charPos.x,charPos.y);
|
||||
if(nearbyTown != null){
|
||||
//if town has a place to day
|
||||
if(false){
|
||||
static void checkForShelter(Character chara){
|
||||
// for(Character chara : Globals.macroData.getAliveCharacters()){
|
||||
/*
|
||||
If doesn’t have shelter, check if in town
|
||||
If in town,
|
||||
check if there’s an inn/church/friendly family
|
||||
if so, try to stay there
|
||||
if can’t find place to stay, fashion makeshift shelter
|
||||
If no town
|
||||
fashion makeshift shelter
|
||||
*/
|
||||
if(!chara.getDataKeys().contains(CharacterDataStrings.SHELTER)){
|
||||
Vector2i charPos = CharacterUtils.getDiscretePosition(chara);
|
||||
Town nearbyTown = Town.getTownAtPosition(charPos.x,charPos.y);
|
||||
if(nearbyTown != null){
|
||||
//if town has a place to day
|
||||
if(false){
|
||||
|
||||
} else {
|
||||
//try to find a place to put down a structure
|
||||
|
||||
}
|
||||
} else {
|
||||
//cry
|
||||
//TODO: Get building type to place
|
||||
String buildingTypeToPlace = "building1";
|
||||
//try to find a place to put down a structure
|
||||
int dynamicInterpRatio = Globals.serverTerrainManager.getDynamicInterpolationRatio();
|
||||
Vector2f placementPos = new Vector2f(
|
||||
(float)(charPos.x * dynamicInterpRatio + Math.random() * dynamicInterpRatio),
|
||||
(float)(charPos.y * dynamicInterpRatio + Math.random() * dynamicInterpRatio)
|
||||
|
||||
}
|
||||
} else {
|
||||
//cry
|
||||
//TODO: Get building type to place
|
||||
String buildingTypeToPlace = "building1";
|
||||
//try to find a place to put down a structure
|
||||
int dynamicInterpRatio = Globals.serverTerrainManager.getDynamicInterpolationRatio();
|
||||
Vector2f placementPos = new Vector2f(
|
||||
(float)(charPos.x * dynamicInterpRatio + Math.random() * dynamicInterpRatio),
|
||||
(float)(charPos.y * dynamicInterpRatio + Math.random() * dynamicInterpRatio)
|
||||
);
|
||||
int attempts = 0;
|
||||
while(!VirtualStructureUtils.validStructurePlacementPosition(placementPos.x, placementPos.y, buildingTypeToPlace)){
|
||||
placementPos = new Vector2f(
|
||||
(float)(charPos.x * dynamicInterpRatio + Math.random() * dynamicInterpRatio),
|
||||
(float)(charPos.y * dynamicInterpRatio + Math.random() * dynamicInterpRatio)
|
||||
);
|
||||
int attempts = 0;
|
||||
while(!VirtualStructureUtils.validStructurePlacementPosition(placementPos.x, placementPos.y, buildingTypeToPlace)){
|
||||
placementPos = new Vector2f(
|
||||
(float)(charPos.x * dynamicInterpRatio + Math.random() * dynamicInterpRatio),
|
||||
(float)(charPos.y * dynamicInterpRatio + Math.random() * dynamicInterpRatio)
|
||||
);
|
||||
attempts++;
|
||||
if(attempts > MAX_PLACE_ATTEMPTS){
|
||||
placementPos = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(placementPos != null){
|
||||
Structure placedStructure = VirtualStructureUtils.placeStructureAtPoint(placementPos.x, placementPos.y, buildingTypeToPlace);
|
||||
CharacterUtils.addShelter(chara, placedStructure);
|
||||
VirtualStructureUtils.addResident(placedStructure, chara);
|
||||
attempts++;
|
||||
if(attempts > MAX_PLACE_ATTEMPTS){
|
||||
placementPos = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(placementPos != null){
|
||||
Structure placedStructure = VirtualStructureUtils.placeStructureAtPoint(placementPos.x, placementPos.y, buildingTypeToPlace);
|
||||
CharacterUtils.addShelter(chara, placedStructure);
|
||||
VirtualStructureUtils.addResident(placedStructure, chara);
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
static void checkTownMembership(){
|
||||
static void checkTownMembership(Character chara){
|
||||
//TODO: eventually exclude people who shouldn't belong to a town (traders, bandits, etc)
|
||||
for(Character chara : Globals.macroData.getAliveCharacters()){
|
||||
boolean hasHometown = chara.getDataKeys().contains(CharacterDataStrings.HOMETOWN);
|
||||
boolean hasShelter = chara.getDataKeys().contains(CharacterDataStrings.SHELTER);
|
||||
//if has structure & no hometown
|
||||
if(!hasHometown && hasShelter){
|
||||
Structure shelter = CharacterUtils.getShelter(chara);
|
||||
//if there's at least one other structure nearby
|
||||
Vector2i shelterDiscretePos = new Vector2i(shelter.getWorldX(),shelter.getWorldY());
|
||||
List<Structure> nearbyPopulatedStructures = new LinkedList();
|
||||
for(Structure currentStruct : Globals.macroData.getStructures()){
|
||||
if(currentStruct.getWorldX() == shelterDiscretePos.x && currentStruct.getWorldY() == shelterDiscretePos.y && currentStruct != shelter){
|
||||
//if has a resident
|
||||
if(shelter.getDataKeys().contains(StructureDataStrings.RESIDENTS) && VirtualStructureUtils.getResidents(shelter).size() > 0){
|
||||
boolean noTown = true;
|
||||
for(Town town : Globals.macroData.getTowns()){
|
||||
if(town.getStructures().contains(currentStruct)){
|
||||
noTown = false;
|
||||
}
|
||||
}
|
||||
if(noTown){
|
||||
nearbyPopulatedStructures.add(currentStruct);
|
||||
// for(Character chara : Globals.macroData.getAliveCharacters()){
|
||||
boolean hasHometown = chara.getDataKeys().contains(CharacterDataStrings.HOMETOWN);
|
||||
boolean hasShelter = chara.getDataKeys().contains(CharacterDataStrings.SHELTER);
|
||||
//if has structure & no hometown
|
||||
if(!hasHometown && hasShelter){
|
||||
Structure shelter = CharacterUtils.getShelter(chara);
|
||||
//if there's at least one other structure nearby
|
||||
Vector2i shelterDiscretePos = new Vector2i(shelter.getWorldX(),shelter.getWorldY());
|
||||
List<Structure> nearbyPopulatedStructures = new LinkedList();
|
||||
for(Structure currentStruct : Globals.macroData.getStructures()){
|
||||
if(currentStruct.getWorldX() == shelterDiscretePos.x && currentStruct.getWorldY() == shelterDiscretePos.y && currentStruct != shelter){
|
||||
//if has a resident
|
||||
if(shelter.getDataKeys().contains(StructureDataStrings.RESIDENTS) && VirtualStructureUtils.getResidents(shelter).size() > 0){
|
||||
boolean noTown = true;
|
||||
for(Town town : Globals.macroData.getTowns()){
|
||||
if(town.getStructures().contains(currentStruct)){
|
||||
noTown = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(nearbyPopulatedStructures.size() > 0){
|
||||
int numStructures = 0;
|
||||
int numResidents = 0;
|
||||
//form town
|
||||
Town newTown = Town.createTown(shelterDiscretePos.x, shelterDiscretePos.y);
|
||||
for(Structure structure : nearbyPopulatedStructures){
|
||||
numStructures++;
|
||||
newTown.addStructure(structure);
|
||||
for(Character resident : VirtualStructureUtils.getResidents(structure)){
|
||||
numResidents++;
|
||||
newTown.addResident(resident);
|
||||
CharacterUtils.addHometown(resident, newTown);
|
||||
if(noTown){
|
||||
nearbyPopulatedStructures.add(currentStruct);
|
||||
}
|
||||
}
|
||||
newTown.addStructure(shelter);
|
||||
newTown.addResident(chara);
|
||||
CharacterUtils.addHometown(chara, newTown);
|
||||
System.out.println("Formed town with " + numStructures + " structures and " + numResidents + " residents");
|
||||
}
|
||||
}
|
||||
if(nearbyPopulatedStructures.size() > 0){
|
||||
int numStructures = 0;
|
||||
int numResidents = 0;
|
||||
//form town
|
||||
Town newTown = Town.createTown(shelterDiscretePos.x, shelterDiscretePos.y);
|
||||
for(Structure structure : nearbyPopulatedStructures){
|
||||
numStructures++;
|
||||
newTown.addStructure(structure);
|
||||
for(Character resident : VirtualStructureUtils.getResidents(structure)){
|
||||
numResidents++;
|
||||
newTown.addResident(resident);
|
||||
CharacterUtils.addHometown(resident, newTown);
|
||||
}
|
||||
}
|
||||
newTown.addStructure(shelter);
|
||||
newTown.addResident(chara);
|
||||
CharacterUtils.addHometown(chara, newTown);
|
||||
System.out.println("Formed town with " + numStructures + " structures and " + numResidents + " residents");
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
static void checkInitCombat(){
|
||||
|
||||
@ -50,7 +50,7 @@ import electrosphere.renderer.ui.WidgetUtils;
|
||||
import electrosphere.renderer.ui.font.FontUtils;
|
||||
import electrosphere.renderer.ui.font.RawFontMap;
|
||||
import electrosphere.renderer.ui.font.TextBox;
|
||||
import electrosphere.util.FileLoadingUtils;
|
||||
import electrosphere.util.FileUtils;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@ -111,6 +111,12 @@ public class Globals {
|
||||
public static CollisionEngine collisionEngine;
|
||||
|
||||
|
||||
//
|
||||
// Game Save stuff
|
||||
//
|
||||
public static String currentSaveName = "default";
|
||||
|
||||
|
||||
//
|
||||
// Game config
|
||||
//
|
||||
@ -291,7 +297,7 @@ public class Globals {
|
||||
// try {
|
||||
//deserializes the texture map from its default path using gson
|
||||
//also done in one line
|
||||
textureMapDefault = FileLoadingUtils.loadObjectFromAssetPath("Textures/default_texture_map.json", TextureMap.class);
|
||||
textureMapDefault = FileUtils.loadObjectFromAssetPath("Textures/default_texture_map.json", TextureMap.class);
|
||||
// textureMapDefault = gson.fromJson(Files.newBufferedReader(new File(Thread.currentThread().getContextClassLoader().getResource("Textures/default_texture_map.json").getFile()).toPath()), TextureMap.class); //only the best of coding practices :)
|
||||
// } catch (IOException ex) { ex.printStackTrace(); } //TODO: handle better :tm:
|
||||
//create entity manager
|
||||
@ -325,7 +331,7 @@ public class Globals {
|
||||
materialDefault.set_specular("Textures/default_specular.png");
|
||||
//create default lights
|
||||
assetManager.registerModelToSpecificString(ModelUtils.createBitmapDisplay(), AssetDataStrings.ASSET_STRING_BITMAP_FONT);
|
||||
RawFontMap fontMap = FileLoadingUtils.loadObjectFromAssetPath("Textures/Fonts/myFontMap.json", RawFontMap.class);
|
||||
RawFontMap fontMap = FileUtils.loadObjectFromAssetPath("Textures/Fonts/myFontMap.json", RawFontMap.class);
|
||||
FontUtils.setFontDataMap(fontMap);
|
||||
//particle billboard model
|
||||
particleBillboardModel = assetManager.registerModel(RenderUtils.createParticleModel());
|
||||
|
||||
@ -16,6 +16,7 @@ import electrosphere.entity.types.attach.AttachUtils;
|
||||
import electrosphere.engine.LoadingThread;
|
||||
import electrosphere.game.client.ClientFunctions;
|
||||
import electrosphere.game.config.UserSettings;
|
||||
import electrosphere.game.server.saves.SaveUtils;
|
||||
import electrosphere.game.server.terrain.manager.ServerTerrainManager;
|
||||
import electrosphere.game.server.world.MacroData;
|
||||
import electrosphere.game.server.world.ServerWorldData;
|
||||
@ -23,7 +24,7 @@ import electrosphere.game.state.MacroSimulation;
|
||||
import electrosphere.game.state.MicroSimulation;
|
||||
import electrosphere.logger.LoggerInterface;
|
||||
import electrosphere.renderer.RenderingEngine;
|
||||
import electrosphere.util.FileLoadingUtils;
|
||||
import electrosphere.util.FileUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Random;
|
||||
@ -136,6 +137,8 @@ public class Main {
|
||||
// ex.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
//debug: create terrain/world viewer
|
||||
// TerrainViewer.runViewer();
|
||||
@ -217,6 +220,13 @@ public class Main {
|
||||
}
|
||||
ClientFunctions.runClientFunctions();
|
||||
|
||||
///
|
||||
/// M A C R O S I M U L A T I O N S T U F F
|
||||
///
|
||||
if(Globals.macroSimulation != null && Globals.macroSimulation.isReady()){
|
||||
Globals.macroSimulation.simulate();
|
||||
}
|
||||
|
||||
//
|
||||
// P L A Y E R W O R L D P O S I T I O N U P D A T E
|
||||
//
|
||||
|
||||
@ -15,6 +15,11 @@ public class Menu {
|
||||
|
||||
public enum MenuType {
|
||||
TITLE_MENU,
|
||||
WORLD_SELECT_MENU, //select preexisting world (Char already created)
|
||||
WORLD_CREATE_MENU, //create world
|
||||
SAVE_CREATE_MENU, //select basic save settings
|
||||
CHARACTER_CREATE_MENU, //create character
|
||||
FINALIZE_SAVE_CREATION_MENU, //finalize the save before we commit
|
||||
MULTIPLAYER_MENU,
|
||||
IP_MENU,
|
||||
OPTIONS_MAIN_MENU,
|
||||
|
||||
@ -20,11 +20,13 @@ public class MenuTransition {
|
||||
switch(m.getCurrentOption()){
|
||||
//single player
|
||||
case 0:
|
||||
// m.dispose();
|
||||
// Globals.loadingThread = new LoadingThread(LoadingThread.LOAD_MAIN_GAME);
|
||||
// Globals.RUN_CLIENT = true;
|
||||
// Globals.RUN_SERVER = true;
|
||||
// Globals.loadingThread.start();
|
||||
m.dispose();
|
||||
Globals.loadingThread = new LoadingThread(LoadingThread.LOAD_MAIN_GAME);
|
||||
Globals.RUN_CLIENT = true;
|
||||
Globals.RUN_SERVER = true;
|
||||
Globals.loadingThread.start();
|
||||
Globals.currentMenu = MenuUtils.createWorldSelectMenu();
|
||||
break;
|
||||
//multi player
|
||||
case 1:
|
||||
@ -51,6 +53,12 @@ public class MenuTransition {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case WORLD_SELECT_MENU:
|
||||
break;
|
||||
case WORLD_CREATE_MENU:
|
||||
break;
|
||||
case SAVE_CREATE_MENU:
|
||||
break;
|
||||
case MULTIPLAYER_MENU:
|
||||
switch(m.getCurrentOption()){
|
||||
//HOST
|
||||
@ -99,6 +107,10 @@ public class MenuTransition {
|
||||
case TITLE_MENU:
|
||||
Main.running = false;
|
||||
break;
|
||||
case WORLD_SELECT_MENU:
|
||||
m.dispose();
|
||||
Globals.currentMenu = MenuUtils.createTitleMenu();
|
||||
break;
|
||||
case MULTIPLAYER_MENU:
|
||||
m.dispose();
|
||||
Globals.currentMenu = MenuUtils.createTitleMenu();
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
package electrosphere.menu;
|
||||
|
||||
import electrosphere.game.server.saves.SaveUtils;
|
||||
import electrosphere.main.Globals;
|
||||
import electrosphere.menu.Menu.MenuType;
|
||||
import electrosphere.net.NetUtils;
|
||||
import electrosphere.net.server.Server;
|
||||
import electrosphere.renderer.ui.Widget;
|
||||
import electrosphere.renderer.ui.WidgetUtils;
|
||||
import java.util.List;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
/**
|
||||
@ -28,13 +30,45 @@ public class MenuUtils {
|
||||
return rVal;
|
||||
}
|
||||
|
||||
public static Menu createMultiplayerMenu(){
|
||||
Menu rVal = new Menu(MenuType.MULTIPLAYER_MENU);
|
||||
public static Menu createWorldSelectMenu(){
|
||||
Menu rVal = new Menu(MenuType.WORLD_SELECT_MENU);
|
||||
int screenTop = Globals.WINDOW_HEIGHT - 150;
|
||||
rVal.addOption( WidgetUtils.createVerticallyAlignedMinSizeTextBoxFromCharCount(40, 40, screenTop - 125, "HOST", true));
|
||||
rVal.setMenuOptionColor(0, new Vector3f(0.2f,0.8f,0.5f));
|
||||
rVal.addOption( WidgetUtils.createVerticallyAlignedMinSizeTextBoxFromCharCount(40, 40, screenTop - 200, "JOIN", true));
|
||||
rVal.addOption( WidgetUtils.createVerticallyAlignedMinSizeTextBoxFromCharCount(40, 40, screenTop - 275, "BACK", true));
|
||||
List<String> saveNames = SaveUtils.getSaves();
|
||||
int verticalPosition = 125;
|
||||
for(String saveName : saveNames){
|
||||
if(!saveName.startsWith(".")){
|
||||
rVal.addOption(WidgetUtils.createVerticallyAlignedMinSizeTextBoxFromCharCount(40, 40, screenTop - verticalPosition, saveName.toUpperCase(), true));
|
||||
verticalPosition = verticalPosition + 75;
|
||||
}
|
||||
}
|
||||
rVal.addOption(WidgetUtils.createVerticallyAlignedMinSizeTextBoxFromCharCount(40, 40, screenTop - verticalPosition - 200, "CREATE WORLD", true));
|
||||
return rVal;
|
||||
}
|
||||
|
||||
public static Menu createWorldCreationMenu(){
|
||||
Menu rVal = new Menu(MenuType.WORLD_CREATE_MENU);
|
||||
int screenTop = Globals.WINDOW_HEIGHT - 150;
|
||||
List<String> saveNames = SaveUtils.getSaves();
|
||||
int verticalPosition = 125;
|
||||
//TODO: add text input to name world
|
||||
rVal.addOption(WidgetUtils.createVerticallyAlignedMinSizeTextBoxFromCharCount(40, 40, screenTop - verticalPosition, "CREATE WORLD", true));
|
||||
return rVal;
|
||||
}
|
||||
|
||||
public static Menu createSaveCreationMenu(){
|
||||
Menu rVal = new Menu(MenuType.WORLD_CREATE_MENU);
|
||||
int screenTop = Globals.WINDOW_HEIGHT - 150;
|
||||
List<String> saveNames = SaveUtils.getSaves();
|
||||
int verticalPosition = 125;
|
||||
//TODO: add text input to name world
|
||||
rVal.addOption(WidgetUtils.createVerticallyAlignedMinSizeTextBoxFromCharCount(40, 40, screenTop - verticalPosition, "CREATE WORLD", true));
|
||||
return rVal;
|
||||
}
|
||||
|
||||
public static Menu createMultiplayerMenu(){
|
||||
Menu rVal = new Menu(MenuType.SAVE_CREATE_MENU);
|
||||
int screenTop = Globals.WINDOW_HEIGHT - 150;
|
||||
rVal.addOption( WidgetUtils.createVerticallyAlignedMinSizeTextBoxFromCharCount(40, 40, screenTop - 125, "CREATE", true));
|
||||
return rVal;
|
||||
}
|
||||
|
||||
|
||||
@ -571,7 +571,6 @@ public class RenderingEngine {
|
||||
|
||||
|
||||
static void renderUI(){
|
||||
Matrix4f modelTransformMatrix = new Matrix4f();
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
for(Widget currentWidget : Globals.widgetManager.getWidgetList()){
|
||||
if(currentWidget.getVisible()){
|
||||
|
||||
@ -2,7 +2,7 @@ package electrosphere.renderer;
|
||||
|
||||
import electrosphere.logger.LoggerInterface;
|
||||
import electrosphere.main.Main;
|
||||
import electrosphere.util.FileLoadingUtils;
|
||||
import electrosphere.util.FileUtils;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
@ -66,7 +66,7 @@ public class ShaderProgram {
|
||||
//
|
||||
String tempForReadingShaders = "";
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(FileLoadingUtils.getAssetFileAsStream(vertex_shader_path)));
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(FileUtils.getAssetFileAsStream(vertex_shader_path)));
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line = br.readLine();
|
||||
@ -85,7 +85,7 @@ public class ShaderProgram {
|
||||
String vertexShaderSource = tempForReadingShaders;
|
||||
//This try-catch block reads the FragmentShader source into memory
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(FileLoadingUtils.getAssetFileAsStream(fragment_shader_path)));
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(FileUtils.getAssetFileAsStream(fragment_shader_path)));
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line = br.readLine();
|
||||
@ -179,7 +179,7 @@ public class ShaderProgram {
|
||||
//
|
||||
String tempForReadingShaders = "";
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(new FileReader(FileLoadingUtils.getAssetFile("/Shaders/VertexShader.vs")));
|
||||
BufferedReader br = new BufferedReader(new FileReader(FileUtils.getAssetFile("/Shaders/VertexShader.vs")));
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line = br.readLine();
|
||||
@ -198,7 +198,7 @@ public class ShaderProgram {
|
||||
String vertexShaderSource = tempForReadingShaders;
|
||||
//This try-catch block reads the FragmentShader source into memory
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(new FileReader(FileLoadingUtils.getAssetFile("/Shaders/FragmentShader.fs")));
|
||||
BufferedReader br = new BufferedReader(new FileReader(FileUtils.getAssetFile("/Shaders/FragmentShader.fs")));
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line = br.readLine();
|
||||
@ -290,8 +290,8 @@ public class ShaderProgram {
|
||||
String vertexShaderSource = "";
|
||||
String fragmentShaderSource = "";
|
||||
try {
|
||||
vertexShaderSource = FileLoadingUtils.getAssetFileAsString(vertex_shader_path);
|
||||
fragmentShaderSource = FileLoadingUtils.getAssetFileAsString(fragment_shader_path);
|
||||
vertexShaderSource = FileUtils.getAssetFileAsString(vertex_shader_path);
|
||||
fragmentShaderSource = FileUtils.getAssetFileAsString(fragment_shader_path);
|
||||
} catch(IOException ex){
|
||||
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
package electrosphere.renderer.texture;
|
||||
|
||||
import electrosphere.main.Main;
|
||||
import electrosphere.util.FileLoadingUtils;
|
||||
import electrosphere.util.FileUtils;
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
@ -53,7 +53,7 @@ public class Texture {
|
||||
width = 1;
|
||||
height = 1;
|
||||
try {
|
||||
BufferedImage image_data = ImageIO.read(FileLoadingUtils.getAssetFile(path));
|
||||
BufferedImage image_data = ImageIO.read(FileUtils.getAssetFile(path));
|
||||
if (
|
||||
image_data.getType() == BufferedImage.TYPE_3BYTE_BGR ||
|
||||
image_data.getType() == BufferedImage.TYPE_INT_RGB
|
||||
|
||||
@ -34,6 +34,7 @@ public class WidgetUtils {
|
||||
int cols = text.length();
|
||||
int rows = 1;
|
||||
TextBox rVal = new TextBox(-cols * charWidth / 2, windowTop, cols * charWidth, rows * charHeight, rows, cols, text, render, false);
|
||||
rVal.setVisible(true);
|
||||
Globals.widgetManager.registerWidget(rVal);
|
||||
return rVal;
|
||||
}
|
||||
|
||||
@ -10,11 +10,13 @@ import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class FileLoadingUtils {
|
||||
public class FileUtils {
|
||||
|
||||
|
||||
|
||||
@ -109,7 +111,9 @@ public class FileLoadingUtils {
|
||||
public static String sanitizeFilePath(String filePath){
|
||||
String rVal = new String(filePath);
|
||||
rVal = rVal.trim();
|
||||
if(!rVal.startsWith("/")){
|
||||
if(rVal.startsWith("./")){
|
||||
return rVal;
|
||||
} else if(!rVal.startsWith("/")){
|
||||
rVal = "/" + rVal;
|
||||
}
|
||||
return rVal;
|
||||
@ -140,9 +144,11 @@ public class FileLoadingUtils {
|
||||
return targetFile;
|
||||
}
|
||||
|
||||
public static File getSaveFile(String pathName){
|
||||
public static File getSaveFile(String saveName, String pathName){
|
||||
String sanitizedFilePath = sanitizeFilePath(pathName);
|
||||
File targetFile = new File("./saves" + sanitizedFilePath);
|
||||
String fullPath = "./saves/" + saveName + "/" + sanitizedFilePath;
|
||||
System.out.println(fullPath);
|
||||
File targetFile = new File(fullPath);
|
||||
return targetFile;
|
||||
}
|
||||
|
||||
@ -170,18 +176,69 @@ public class FileLoadingUtils {
|
||||
return rVal;
|
||||
}
|
||||
|
||||
public static <T>T loadObjectFromSavePath(String pathName, Class<T> className){
|
||||
public static String getSQLScriptFileAsString(String pathName) throws IOException {
|
||||
String sanitizedFilePath = sanitizeFilePath(pathName);
|
||||
File targetFile = new File("./Scripts" + sanitizedFilePath);
|
||||
return Files.readString(targetFile.toPath());
|
||||
}
|
||||
|
||||
public static <T>T loadObjectFromSavePath(String saveName, String pathName, Class<T> className){
|
||||
T rVal = null;
|
||||
String sanitizedFilePath = sanitizeFilePath(pathName);
|
||||
Gson gson = new Gson();
|
||||
try {
|
||||
rVal = gson.fromJson(Files.newBufferedReader(getSaveFile(sanitizedFilePath).toPath()), className);
|
||||
rVal = gson.fromJson(Files.newBufferedReader(getSaveFile(saveName,sanitizedFilePath).toPath()), className);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a directory exists
|
||||
* @param directoryName
|
||||
* @return true if directory exists, false otherwise
|
||||
*/
|
||||
public static boolean checkDirectoryExists(String directoryName){
|
||||
File targetDir = new File(sanitizeFilePath(directoryName));
|
||||
if(targetDir.exists()){
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trys to create a directory
|
||||
* @param directoryName
|
||||
* @return true if directory was created, false if it was not
|
||||
*/
|
||||
public static boolean createDirectory(String directoryName){
|
||||
String sanitizedPath = "." + sanitizeFilePath(directoryName);
|
||||
File targetDir = new File(sanitizedPath);
|
||||
if(targetDir.exists()){
|
||||
return false;
|
||||
} else {
|
||||
return targetDir.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static List<String> listDirectory(String directoryName){
|
||||
List<String> rVal = new LinkedList();
|
||||
String sanitizedPath = sanitizeFilePath(directoryName);
|
||||
File targetDir = new File(sanitizedPath);
|
||||
String[] files = targetDir.list();
|
||||
for(String name : files){
|
||||
rVal.add(name);
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// public static <T>T loadModelObjectFromBakedJsonFile(String fileName, Class<T> className){
|
||||
// T rVal = null;
|
||||
// String sanitizedFilePath = sanitizeBakedFilePath(fileName);
|
||||
@ -30,7 +30,7 @@ public class ModelLoader {
|
||||
AIScene scene;
|
||||
// File file = new File(Thread.currentThread().getContextClassLoader().getResource(fileName).getFile());
|
||||
// Main.class.getResourceAsStream(fileName).readAllBytes();
|
||||
File toRead = FileLoadingUtils.getAssetFile(fileName);
|
||||
File toRead = FileUtils.getAssetFile(fileName);
|
||||
scene = aiImportFile(toRead.getAbsolutePath(),
|
||||
aiProcess_GenSmoothNormals |
|
||||
aiProcess_JoinIdenticalVertices |
|
||||
|
||||
@ -6,7 +6,7 @@ import electrosphere.game.server.terrain.manager.ServerTerrainManager;
|
||||
import electrosphere.game.server.terrain.models.TerrainModel;
|
||||
import electrosphere.main.Globals;
|
||||
import electrosphere.main.Main;
|
||||
import electrosphere.util.FileLoadingUtils;
|
||||
import electrosphere.util.FileUtils;
|
||||
import electrosphere.util.Utilities;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user