package javastory.channel.maps;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReentrantLock;
import javastory.channel.life.AbstractLoadedLife;
import javastory.channel.life.LifeFactory;
import javastory.channel.life.Monster;
import javastory.channel.server.Portal;
import javastory.channel.server.PortalFactory;
import javastory.game.data.ReactorFactory;
import javastory.game.data.ReactorInfo;
import javastory.tools.StringUtil;
import javastory.wz.WzData;
import javastory.wz.WzDataProvider;
import javastory.wz.WzDataProviderFactory;
import javastory.wz.WzDataTool;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Maps;
public final class GameMapFactory {
private static final WzDataProvider mapDataProvider = WzDataProviderFactory.getDataProvider("Map.wz");
private final Map<Integer, GameMap> maps;
private final ConcurrentMap<Integer, GameMap> instanceMap;
private final ReentrantLock lock;
public GameMapFactory() {
this.maps = Maps.newHashMap();
// TODO: WeakReference is bad news in general. Do not use. Never use. The GC has its hands full with this thing anyways.
this.instanceMap = new MapMaker().weakValues().makeMap();
this.lock = new ReentrantLock();
}
public final GameMap getMap(final int mapId) {
return this.getMap(mapId, true, true, true);
}
// backwards-compatible
public final GameMap getMap(final int mapId, final boolean respawns, final boolean npcs) {
return this.getMap(mapId, respawns, npcs, true);
}
public final GameMap getMap(final int mapId, final boolean mobsRespawn, final boolean loadNpcs, final boolean loadReactors) {
final Integer mapIdInt = Integer.valueOf(mapId);
GameMap map = this.maps.get(mapIdInt);
if (map == null) {
this.lock.lock();
try {
// check if someone else who was also synchronized has loaded
// the map already
map = this.maps.get(mapIdInt);
if (map != null) {
return map;
}
map = this.loadMap(mapId, mobsRespawn, loadNpcs, loadReactors);
this.maps.put(mapIdInt, map);
} finally {
this.lock.unlock();
}
}
return map;
}
// TODO: Reuse this in createInstance, or vice-versa, or a common portion of both.
private GameMap loadMap(final int mapId, final boolean mobsRespawn, final boolean loadNpcs, final boolean loadReactors) {
WzData data = mapDataProvider.getData(this.getPaddedMapName(mapId));
final WzData link = data.getChildByPath("info/link");
if (link != null) {
data = mapDataProvider.getData(this.getPaddedMapName(WzDataTool.getIntConvert("info/link", data)));
}
float monsterRate = 0;
if (mobsRespawn) {
final WzData mobRate = data.getChildByPath("info/mobRate");
if (mobRate != null) {
monsterRate = ((Float) mobRate.getData()).floatValue();
}
}
final int returnMapId = WzDataTool.getInt("info/returnMap", data);
final GameMap map = new GameMap(mapId, returnMapId, monsterRate);
final PortalFactory portalFactory = new PortalFactory();
for (final WzData portalData : data.getChildByPath("portal")) {
final Portal portal = portalFactory.createPortal(portalData);
map.addPortal(portal);
}
final FootholdTree footholds = loadFootholds(data);
map.setFootholds(footholds);
// load areas (EG PQ platforms)
final WzData areaData = data.getChildByPath("area");
if (areaData != null) {
for (final WzData area : areaData) {
final Rectangle mapArea = WzDataTool.getRectangle("x1", "y1", "x2", "y2", area);
map.addArea(mapArea);
}
}
String message = null;
final WzData timeMob = data.getChildByPath("info/timeMob");
if (timeMob != null) {
message = WzDataTool.getString("message", timeMob, null);
}
// load life data (npc, monsters)
for (final WzData life : data.getChildByPath("life")) {
final String type = WzDataTool.getString("type", life);
if (loadNpcs || !type.equals("n")) {
final String id = WzDataTool.getString("id", life);
final AbstractLoadedLife myLife = this.loadLife(life, id, type);
if (myLife instanceof Monster) {
final Monster mob = (Monster) myLife;
final int mobTime = WzDataTool.getInt("mobTime", life, 0);
final byte team = (byte) WzDataTool.getInt("team", life, -1);
map.addMonsterSpawn(mob, mobTime, team, message);
} else {
final boolean bAdd = true;
if (bAdd) {
map.addMapObject(myLife);
}
}
}
}
this.addAreaBossSpawn(map);
map.loadMonsterRate(true);
// load reactor data
final WzData reactorEntry = data.getChildByPath("reactor");
if (loadReactors && reactorEntry != null) {
for (final WzData reactor : reactorEntry) {
final String id = WzDataTool.getString("id", reactor);
if (id == null) {
continue;
}
final Reactor loadedReactor = this.loadReactor(reactor, id);
map.spawnReactor(loadedReactor);
}
}
map.setClock(data.getChildByPath("clock") != null);
map.setEverlast(data.getChildByPath("info/everlast") != null);
map.setTown(data.getChildByPath("info/town") != null);
map.setForcedReturnMap(WzDataTool.getInt("info/forcedReturn", data, 999999999));
map.setRecoveryRate(WzDataTool.getFloat("info/recovery", data, 1.0f));
map.setHPDec(WzDataTool.getInt("info/decHP", data, 0));
map.setHPDecProtect(WzDataTool.getInt("info/protectItem", data, 0));
map.setFieldLimit(WzDataTool.getInt("info/fieldLimit", data, 0));
map.setTimeLimit(WzDataTool.getInt("info/timeLimit", data, -1));
map.setPersonalShop(data.getChildByPath("info/personalShop") != null);
map.setCreateMobInterval((short) WzDataTool.getInt("info/createMobInterval", data, 9000));
map.setFirstUserEnter(WzDataTool.getString("info/onFirstUserEnter", data, ""));
map.setUserEnter(WzDataTool.getString("info/onUserEnter", data, ""));
return map;
}
private FootholdTree loadFootholds(WzData data) {
final List<Foothold> footholds = Lists.newLinkedList();
final Point lBound = new Point();
final Point uBound = new Point();
for (final WzData footholdData : data.getChildByPath("foothold")) {
for (final WzData category : footholdData) {
for (final WzData entry : category) {
final Foothold fh = new Foothold(entry);
if (fh.getX1() < lBound.x) {
lBound.x = fh.getX1();
}
if (fh.getX2() > uBound.x) {
uBound.x = fh.getX2();
}
if (fh.getY1() < lBound.y) {
lBound.y = fh.getY1();
}
if (fh.getY2() > uBound.y) {
uBound.y = fh.getY2();
}
footholds.add(fh);
}
}
}
final FootholdTree footholdTree = new FootholdTree(lBound, uBound);
for (final Foothold item : footholds) {
footholdTree.insert(item);
}
return footholdTree;
}
public GameMap getInstanceMap(final int instanceid) {
return this.instanceMap.get(instanceid);
}
public void removeInstanceMap(final int instanceid) {
this.instanceMap.remove(instanceid);
}
public GameMap createInstanceMap(final int mapId, final boolean mobsRespawn, final boolean loadNpcs, final boolean loadReactors, final int instanceId) {
WzData data = mapDataProvider.getData(this.getPaddedMapName(mapId));
final WzData link = data.getChildByPath("info/link");
if (link != null) {
data = mapDataProvider.getData(this.getPaddedMapName(WzDataTool.getIntConvert("info/link", data)));
}
float monsterRate = 0;
if (mobsRespawn) {
final WzData mobRate = data.getChildByPath("info/mobRate");
if (mobRate != null) {
monsterRate = ((Float) mobRate.getData()).floatValue();
}
}
final GameMap map = new GameMap(mapId, WzDataTool.getInt("info/returnMap", data), monsterRate);
final PortalFactory portalFactory = new PortalFactory();
for (final WzData portalData : data.getChildByPath("portal")) {
final Portal portal = portalFactory.createPortal(portalData);
map.addPortal(portal);
}
final FootholdTree fTree = loadFootholds(data);
map.setFootholds(fTree);
// load areas (EG PQ platforms)
if (data.getChildByPath("area") != null) {
for (final WzData area : data.getChildByPath("area")) {
final int x1 = WzDataTool.getInt("x1", area);
final int y1 = WzDataTool.getInt("y1", area);
final int x2 = WzDataTool.getInt("x2", area);
final int y2 = WzDataTool.getInt("y2", area);
final Rectangle mapArea = new Rectangle(x1, y1, x2 - x1, y2 - y1);
map.addArea(mapArea);
}
}
String message = null;
final WzData timeMob = data.getChildByPath("info/timeMob");
if (timeMob != null) {
message = WzDataTool.getString("message", timeMob, null);
}
// load life data (npc, monsters)
for (final WzData life : data.getChildByPath("life")) {
final String type = WzDataTool.getString("type", life);
if (loadNpcs || !type.equals("n")) {
final String id = WzDataTool.getString("id", life);
final AbstractLoadedLife myLife = this.loadLife(life, id, type);
if (myLife instanceof Monster) {
final Monster mob = (Monster) myLife;
final int mobTime = WzDataTool.getInt("mobTime", life, 0);
final byte team = (byte) WzDataTool.getInt("team", life, -1);
map.addMonsterSpawn(mob, mobTime, team, message);
} else {
final boolean bAdd = true;
if (bAdd) {
map.addMapObject(myLife);
}
}
}
}
this.addAreaBossSpawn(map);
map.loadMonsterRate(true);
// load reactor data
if (loadReactors && data.getChildByPath("reactor") != null) {
for (final WzData reactor : data.getChildByPath("reactor")) {
final String id = WzDataTool.getString("id", reactor);
if (id == null) {
continue;
}
map.spawnReactor(this.loadReactor(reactor, id));
}
}
map.setClock(data.getChildByPath("clock") != null);
map.setEverlast(data.getChildByPath("info/everlast") != null);
map.setTown(data.getChildByPath("info/town") != null);
map.setForcedReturnMap(WzDataTool.getInt("info/forcedReturn", data, 999999999));
map.setRecoveryRate(WzDataTool.getFloat("info/recovery", data, 1.0f));
map.setHPDec(WzDataTool.getInt("info/decHP", data, 0));
map.setHPDecProtect(WzDataTool.getInt("info/protectItem", data, 0));
map.setFieldLimit(WzDataTool.getInt("info/fieldLimit", data, 0));
map.setTimeLimit(WzDataTool.getInt("info/timeLimit", data, -1));
map.setCreateMobInterval((short) WzDataTool.getInt("info/createMobInterval", data, 9000));
map.setFirstUserEnter(WzDataTool.getString("info/onFirstUserEnter", data, ""));
map.setUserEnter(WzDataTool.getString("info/onUserEnter", data, ""));
this.instanceMap.put(instanceId, map);
return map;
}
public int getLoadedMaps() {
return this.maps.size();
}
public boolean isMapLoaded(final int mapId) {
return this.maps.containsKey(mapId);
}
public boolean isInstanceMapLoaded(final int instanceid) {
return this.instanceMap.containsKey(instanceid);
}
public void clearLoadedMap() {
this.maps.clear();
}
public Map<Integer, GameMap> getMaps() {
return this.maps;
}
private AbstractLoadedLife loadLife(final WzData life, final String id, final String type) {
final AbstractLoadedLife myLife = LifeFactory.getLife(Integer.parseInt(id), type);
myLife.setCy(WzDataTool.getInt("cy", life));
myLife.setFacingDirection(WzDataTool.getInt("f", life, 0));
myLife.setFoothold(WzDataTool.getInt("fh", life));
myLife.setRx0(WzDataTool.getInt("rx0", life));
myLife.setRx1(WzDataTool.getInt("rx1", life));
final int x = WzDataTool.getInt("x", life);
final int y = WzDataTool.getInt("y", life);
final Point position = new Point(x, y);
myLife.setPosition(position);
if (WzDataTool.getInt("hide", life, 0) == 1) {
myLife.setHidden(true);
}
return myLife;
}
private Reactor loadReactor(final WzData data, final String reactorId) {
final ReactorInfo info = ReactorFactory.getReactor(Integer.parseInt(reactorId));
final MapReactorInfo mapInfo = new MapReactorInfo(info, data);
final Reactor reactor = new Reactor(mapInfo, Integer.parseInt(reactorId));
reactor.setStateId((byte) 0);
return reactor;
}
private String getPaddedMapName(final int mapId) {
String paddedId = StringUtil.getLeftPaddedStr(Integer.toString(mapId), '0', 9);
final StringBuilder builder = new StringBuilder("Map/Map");
builder.append(mapId / 100_000_000);
builder.append("/");
builder.append(paddedId);
builder.append(".img");
paddedId = builder.toString();
return paddedId;
}
private void addAreaBossSpawn(final GameMap map) {
int monsterid = -1;
int mobtime = -1;
String msg = null;
Point pos1 = null, pos2 = null, pos3 = null;
switch (map.getId()) {
case 104_000_400: // Mano
mobtime = 2700;
monsterid = 2220000;
msg = "A cool breeze was felt when Mano appeared.";
pos1 = new Point(439, 185);
pos2 = new Point(301, -85);
pos3 = new Point(107, -355);
break;
case 101_030_404: // Stumpy
mobtime = 2700;
monsterid = 3220000;
msg = "Stumpy has appeared with a stumping sound that rings the Stone Mountain.";
pos1 = new Point(867, 1282);
pos2 = new Point(810, 1570);
pos3 = new Point(838, 2197);
break;
case 110_040_000: // King Clang
mobtime = 1200;
monsterid = 5220001;
msg = "A strange turban shell has appeared on the beach.";
pos1 = new Point(-355, 179);
pos2 = new Point(-1283, -113);
pos3 = new Point(-571, -593);
break;
case 250_010_304: // Tae Roon
mobtime = 2100;
monsterid = 7220000;
msg = "Tae Roon appeared with a loud grow.";
pos1 = new Point(-210, 33);
pos2 = new Point(-234, 393);
pos3 = new Point(-654, 33);
break;
case 200_010_300: // Eliza
mobtime = 1200;
monsterid = 8220000;
msg = "Eliza has appeared with a black whirlwind.";
pos1 = new Point(665, 83);
pos2 = new Point(672, -217);
pos3 = new Point(-123, -217);
break;
case 250_010_503: // Ghost Priest
mobtime = 1800;
monsterid = 7220002;
msg = "The area fills with an unpleasant force of evil.. even the occasional ones of the cats sound disturbing";
pos1 = new Point(-303, 543);
pos2 = new Point(227, 543);
pos3 = new Point(719, 543);
break;
case 222_010_310: // Old Fox
mobtime = 2700;
monsterid = 7220001;
msg = "As the moon light dims,a long fox cry can be heard and the presence of the old fox can be felt.";
pos1 = new Point(-169, -147);
pos2 = new Point(-517, 93);
pos3 = new Point(247, 93);
break;
case 107_000_300: // Dale
mobtime = 1800;
monsterid = 6220000;
msg = "The huge crocodile Dale has come out from the swamp.";
pos1 = new Point(710, 118);
pos2 = new Point(95, 119);
pos3 = new Point(-535, 120);
break;
case 100_040_105: // Faust
mobtime = 1800;
monsterid = 5220002;
msg = "The blue fog became darker when Faust appeared.";
pos1 = new Point(1000, 278);
pos2 = new Point(557, 278);
pos3 = new Point(95, 278);
break;
case 100_040_106: // Faust
mobtime = 1800;
monsterid = 5220002;
msg = "The blue fog became darker when Faust appeared.";
pos1 = new Point(1000, 278);
pos2 = new Point(557, 278);
pos3 = new Point(95, 278);
break;
case 220_050_100: // Timer
mobtime = 1500;
monsterid = 5220003;
msg = "Click clock! Timer has appeared with an irregular clock sound.";
pos1 = new Point(-467, 1032);
pos2 = new Point(532, 1032);
pos3 = new Point(-47, 1032);
break;
case 221_040_301: // Jeno
mobtime = 2400;
monsterid = 6220001;
msg = "Jeno has appeared with a heavy sound of machinery.";
pos1 = new Point(-4134, 416);
pos2 = new Point(-4283, 776);
pos3 = new Point(-3292, 776);
break;
case 240_040_401: // Lev
mobtime = 7200;
monsterid = 8220003;
msg = "Leviathan has appeared with a cold wind from over the gorge.";
pos1 = new Point(-15, 2481);
pos2 = new Point(127, 1634);
pos3 = new Point(159, 1142);
break;
case 260_010_201: // Dewu
mobtime = 3600;
monsterid = 3220001;
msg = "Dewu slowly appeared out of the sand dust.";
pos1 = new Point(-215, 275);
pos2 = new Point(298, 275);
pos3 = new Point(592, 275);
break;
case 261_030_000: // Chimera
mobtime = 2700;
monsterid = 8220002;
msg = "Chimera has appeared out of the darkness of the underground with a glitter in her eyes.";
pos1 = new Point(-1094, -405);
pos2 = new Point(-772, -116);
pos3 = new Point(-108, 181);
break;
case 230_020_100: // Sherp
mobtime = 2700;
monsterid = 4220000;
msg = "A strange shell has appeared from a grove of seaweed.";
pos1 = new Point(-291, -20);
pos2 = new Point(-272, -500);
pos3 = new Point(-462, 640);
break;
/*
* case 910000000: // FM mobtime = 300; monsterid = 9420015; msg =
* "NooNoo has appeared out of anger, it seems that NooNoo is stuffed with Christmas gifts!"
* ; pos1 = new Point(498, 4); pos2 = new Point(498, 4); pos3 = new
* Point(498, 4); break;
*/
case 209_000_000: // Happyvile
mobtime = 300;
monsterid = 9500317;
msg = "Little Snowman has appeared!";
pos1 = new Point(-115, 154);
pos2 = new Point(-115, 154);
pos3 = new Point(-115, 154);
break;
default:
return;
}
map.addAreaMonsterSpawn(LifeFactory.getMonster(monsterid), pos1, pos2, pos3, mobtime, msg);
}
}