package jk_5.nailed.server.map;
import gnu.trove.map.hash.TIntObjectHashMap;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise;
import jk_5.eventbus.EventHandler;
import jk_5.nailed.api.event.teleport.TeleportEventEnd;
import jk_5.nailed.api.map.Map;
import jk_5.nailed.api.map.MapLoader;
import jk_5.nailed.api.mappack.Mappack;
import jk_5.nailed.api.mappack.MappackLoadingFailedException;
import jk_5.nailed.api.mappack.metadata.MappackWorld;
import jk_5.nailed.api.world.Dimension;
import jk_5.nailed.api.world.World;
import jk_5.nailed.api.world.WorldContext;
import jk_5.nailed.api.world.WorldProvider;
import jk_5.nailed.server.NailedEventFactory;
import jk_5.nailed.server.NailedPlatform;
import jk_5.nailed.server.event.EntityDamageEvent;
import jk_5.nailed.server.event.EntityFallEvent;
import jk_5.nailed.server.mappack.NailedMappackRegistry;
import jk_5.nailed.server.scheduler.NailedScheduler;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.DamageSource;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class NailedMapLoader implements MapLoader {
private static final NailedMapLoader INSTANCE = new NailedMapLoader();
private static final Logger logger = LogManager.getLogger();
private final TIntObjectHashMap<Map> maps = new TIntObjectHashMap<Map>();
private final java.util.Map<Map, List<World>> mapWorlds = new HashMap<Map, List<World>>();
private final AtomicInteger nextMapId = new AtomicInteger(0);
private final File mapsDir = new File("maps");
private Mappack lobbyMappack;
private Map lobby;
@Nonnull
@Override
public Mappack getLobbyMappack() {
String lobbyName = NailedPlatform.instance().getConfig().getString("lobbyMappack");
Mappack mp = NailedMappackRegistry.instance().getByName(lobbyName);
if(mp == null){
logger.warn("Mappack {} was defined as a lobby mappack in the config, but it does not exist. Falling back to a dummy", lobbyName);
this.lobbyMappack = DummyLobbyMappack.instance();
}else{
this.lobbyMappack = mp;
}
return this.lobbyMappack;
}
@Nonnull
@Override
public Map getLobby() {
return this.lobby;
}
@Nullable
@Override
public Map getMap(int mapid) {
return this.maps.get(mapid);
}
public NailedMap registerMap(NailedMap map){
if(map.id() == 0){
this.lobby = map;
}
this.maps.put(map.id(), map);
logger.info("Registered {}", map);
return map;
}
public NailedMap createLobbyMap(){
int id = nextMapId.getAndIncrement();
final Promise<Void> finishPromise = new DefaultPromise<Void>(NailedScheduler.instance().getExecutor().next());
final File baseDir = new File(mapsDir, "lobby");
NailedScheduler.instance().submit(new Runnable() {
@Override
public void run() {
baseDir.mkdir();
getLobbyMappack().prepareWorld(baseDir, finishPromise);
}
});
try{
finishPromise.get();
}catch(Exception e){
logger.error("Exception while waiting for the promise to finish", e);
}
NailedMap map = new NailedMap(id, getLobbyMappack(), baseDir);
if(finishPromise.isSuccess()){
this.registerMap(map);
this.loadMappackWorlds(map, getLobbyMappack(), "lobby");
return map;
}else{
logger.warn("Loading of map {} with mappack {} failed.", map, getLobbyMappack());
throw new MappackLoadingFailedException("Map loading failed", finishPromise.cause());
}
}
@Nonnull
@Override
public Future<Map> createMapFor(@Nonnull final Mappack mappack) {
final int id = nextMapId.getAndIncrement();
final Promise<Map> allDonePromise = new DefaultPromise<Map>(NailedScheduler.instance().getExecutor().next());
final Promise<Void> finishPromise = new DefaultPromise<Void>(NailedScheduler.instance().getExecutor().next());
finishPromise.addListener(new FutureListener<Void>() {
@Override
public void operationComplete(Future<Void> future){
if(future.isSuccess()){
NailedScheduler.instance().executeSync(new Runnable() {
@Override
public void run() {
NailedMap map = new NailedMap(id, mappack, new File(mapsDir, "map_" + id));
registerMap(map);
loadMappackWorlds(map, mappack, "map_" + id);
allDonePromise.setSuccess(map);
}
});
}else{
logger.warn("Loading of map " + mappack.getId() + "_" + id + " with mappack " + mappack.toString() + " failed. ", future.cause());
}
}
});
NailedScheduler.instance().submit(new Runnable(){
@Override
public void run(){
File dir = new File(mapsDir, "map_" + id);
dir.mkdir();
mappack.prepareWorld(dir, finishPromise);
}
});
return allDonePromise;
}
private void loadMappackWorlds(Map map, Mappack mappack, String saveDir){
for(final MappackWorld world : mappack.getMetadata().worlds()){
WorldProvider provider = new WorldProvider() {
private int id;
@Override
public int getId() {
return id;
}
@Override
public void setId(int id) {
this.id = id;
}
@Nonnull
@Override
public Dimension getDimension() {
return world.dimension();
}
@Nonnull
@Override
public String getType() {
return world.generator();
}
@Nonnull
@Override
public String getOptions() {
return null;
}
};
map.addWorld(NailedPlatform.instance().createNewWorld(provider, new WorldContext(saveDir, world.name(), world)));
}
}
public void addWorldToMap(World world, Map map){
List<World> list = this.mapWorlds.get(map);
if(list == null){
list = new ArrayList<World>();
this.mapWorlds.put(map, list);
}
list.add(world);
world.setMap(map);
logger.info("World {} was added to {}", world, map);
}
public List<World> getWorldsForMap(Map map){
return this.mapWorlds.get(map);
}
@EventHandler
public void onPlayerTeleported(TeleportEventEnd event){
World oldWorld = event.getOldWorld();
World newWorld = event.getNewWorld();
Map oldMap = oldWorld.getMap();
Map newMap = newWorld.getMap();
if(oldWorld != newWorld){
oldWorld.onPlayerLeft(event.getPlayer());
NailedEventFactory.firePlayerLeftWorld(event.getPlayer(), oldWorld);
newWorld.onPlayerJoined(event.getPlayer());
NailedEventFactory.firePlayerJoinWorld(event.getPlayer(), newWorld);
}
if(oldMap != null || newMap != null){
if(oldMap != null && newMap != null){
if(oldMap != newMap){
if(oldMap instanceof NailedMap){
((NailedMap) oldMap).onPlayerLeft(event.getPlayer());
}
NailedEventFactory.firePlayerLeftMap(event.getPlayer(), oldMap);
if(newMap instanceof NailedMap){
((NailedMap) newMap).onPlayerJoined(event.getPlayer());
}
NailedEventFactory.firePlayerJoinMap(event.getPlayer(), newMap);
}
}else{
if(oldMap != null){
if(oldMap instanceof NailedMap){
((NailedMap) oldMap).onPlayerLeft(event.getPlayer());
}
NailedEventFactory.firePlayerLeftMap(event.getPlayer(), oldMap);
}
if(newMap != null){
if(newMap instanceof NailedMap){
((NailedMap) newMap).onPlayerJoined(event.getPlayer());
}
NailedEventFactory.firePlayerJoinMap(event.getPlayer(), newMap);
}
}
}
}
@EventHandler
public void onEntityFall(EntityFallEvent event){
if(event.getEntity() instanceof EntityPlayerMP){
EntityPlayerMP e = (EntityPlayerMP) event.getEntity();
World world = NailedPlatform.instance().getPlayerFromEntity(e).getWorld();
if(world.getConfig() != null && world.getConfig().disableDamage()){
event.setCanceled(true);
}
}
}
@EventHandler
public void onEntityDamage(EntityDamageEvent event){
if(event.getSource() == DamageSource.outOfWorld) return;
if(event.getEntity() instanceof EntityPlayerMP){
EntityPlayerMP e = (EntityPlayerMP) event.getEntity();
World world = NailedPlatform.instance().getPlayerFromEntity(e).getWorld();
if(world.getConfig() != null && world.getConfig().disableDamage()){
event.setCanceled(true);
}
}
}
//TODO
/*@EventHandler
def onBlockPlace(event: BlockPlaceEvent){
if(event.world.getConfig != null && event.world.getConfig.disableBlockPlacement){
event.setCanceled(true)
}
}
@EventHandler
def onBlockBreak(event: BlockBreakEvent){
if(event.world.getConfig != null && event.world.getConfig.disableBlockBreaking){
event.setCanceled(true)
}
}*/
public static NailedMapLoader instance(){
return INSTANCE;
}
}