Browse Source

Merge remote-tracking branch 'origin/magicbox-1.5.2' into feature-workorder

# Conflicts:
#	src/engine/mobileAI/Threads/MobRespawnThread.java
feature-workorder
MagicBot 1 year ago
parent
commit
6484e7eb93
  1. 19
      src/engine/InterestManagement/Terrain.java
  2. 2
      src/engine/db/handlers/dbMobHandler.java
  3. 2
      src/engine/devcmd/cmds/GetHeightCmd.java
  4. 8
      src/engine/gameManager/ZoneManager.java
  5. 9
      src/engine/mobileAI/MobAI.java
  6. 38
      src/engine/mobileAI/Threads/Respawner.java
  7. 4
      src/engine/net/client/msg/ManageCityAssetsMsg.java
  8. 12
      src/engine/net/client/msg/ManageNPCMsg.java
  9. 40
      src/engine/objects/Mob.java
  10. 13
      src/engine/objects/Zone.java
  11. 4
      src/engine/server/world/WorldServer.java

19
src/engine/InterestManagement/Terrain.java

@ -62,11 +62,22 @@ public class Terrain {
// the blending area between child and parent terrains when // the blending area between child and parent terrains when
// they are stitched together. // they are stitched together.
Vector2f major_blend = new Vector2f(this.zone.template.max_blend / this.zone.major_radius, float max_blend = this.zone.template.max_blend;
this.zone.template.min_blend / this.zone.major_radius); float min_blend = this.zone.template.min_blend;
Vector2f minor_blend = new Vector2f(this.zone.template.max_blend / this.zone.minor_radius, // Zones with a zero blend inherit from their parent terrain
this.zone.template.min_blend / this.zone.minor_radius);
if (this.zone.template.max_blend == 0) {
Zone parentZone = this.getNextZoneWithTerrain(this.zone.parent);
max_blend = parentZone.template.max_blend;
min_blend = parentZone.template.min_blend;
}
Vector2f major_blend = new Vector2f(max_blend / this.zone.major_radius,
min_blend / this.zone.major_radius);
Vector2f minor_blend = new Vector2f(max_blend / this.zone.minor_radius,
min_blend / this.zone.minor_radius);
if (major_blend.y > 0.4f) if (major_blend.y > 0.4f)
blend_ratio.x = major_blend.y; blend_ratio.x = major_blend.y;

2
src/engine/db/handlers/dbMobHandler.java

@ -42,7 +42,7 @@ public class dbMobHandler extends dbHandlerBase {
preparedStatement.setFloat(6, toAdd.bindLoc.z); preparedStatement.setFloat(6, toAdd.bindLoc.z);
preparedStatement.setInt(7, 0); preparedStatement.setInt(7, 0);
preparedStatement.setFloat(8, toAdd.spawnRadius); preparedStatement.setFloat(8, toAdd.spawnRadius);
preparedStatement.setInt(9, toAdd.spawnTime); preparedStatement.setInt(9, toAdd.spawnDelay);
preparedStatement.setInt(10, toAdd.contractUUID); preparedStatement.setInt(10, toAdd.contractUUID);
preparedStatement.setInt(11, toAdd.buildingUUID); preparedStatement.setInt(11, toAdd.buildingUUID);
preparedStatement.setInt(12, toAdd.level); preparedStatement.setInt(12, toAdd.level);

2
src/engine/devcmd/cmds/GetHeightCmd.java

@ -60,6 +60,8 @@ public class GetHeightCmd extends AbstractDevCmd {
this.throwbackInfo(playerCharacter, "Grid : " + "[" + gridSquare.x + "]" + "[" + gridSquare.y + "]"); this.throwbackInfo(playerCharacter, "Grid : " + "[" + gridSquare.x + "]" + "[" + gridSquare.y + "]");
this.throwbackInfo(playerCharacter, "offset: " + "[" + childZoneOffset.x + "]" + "[" + childZoneOffset.y + "]"); this.throwbackInfo(playerCharacter, "offset: " + "[" + childZoneOffset.x + "]" + "[" + childZoneOffset.y + "]");
this.throwbackInfo(playerCharacter, "Normalized offset: " + "[" + normalizedOffset.x + "]" + "[" + normalizedOffset.y + "]"); this.throwbackInfo(playerCharacter, "Normalized offset: " + "[" + normalizedOffset.x + "]" + "[" + normalizedOffset.y + "]");
this.throwbackInfo(playerCharacter, "Heightmap blend Values: max/min" + heightmapZone.template.min_blend + " /" + heightmapZone.template.max_blend);
this.throwbackInfo(playerCharacter, "Parent blend Values: nax/min" + parentZone.template.min_blend + " /" + parentZone.template.max_blend);
this.throwbackInfo(playerCharacter, "Blend coefficient: " + heightmapZone.terrain.getTerrainBlendCoefficient(childZoneOffset)); this.throwbackInfo(playerCharacter, "Blend coefficient: " + heightmapZone.terrain.getTerrainBlendCoefficient(childZoneOffset));
this.throwbackInfo(playerCharacter, "------------"); this.throwbackInfo(playerCharacter, "------------");

8
src/engine/gameManager/ZoneManager.java

@ -121,8 +121,8 @@ public enum ZoneManager {
public static void resetHotZones() { public static void resetHotZones() {
for (Zone zone : ZoneManager.macroZones) for (Zone zone : ZoneManager.macroZones)
if (zone.hasBeenHotzone) if (zone.wasHotzonw)
zone.hasBeenHotzone = false; zone.wasHotzonw = false;
} }
@ -150,7 +150,7 @@ public enum ZoneManager {
ZoneManager.hotZone = zone; ZoneManager.hotZone = zone;
ZoneManager.hotZoneCycle = 1; // Used with HOTZONE_DURATION from config. ZoneManager.hotZoneCycle = 1; // Used with HOTZONE_DURATION from config.
zone.hasBeenHotzone = true; zone.wasHotzonw = true;
ZoneManager.hotZoneLastUpdate = LocalDateTime.now().withMinute(0).withSecond(0).atZone(ZoneId.systemDefault()).toInstant(); ZoneManager.hotZoneLastUpdate = LocalDateTime.now().withMinute(0).withSecond(0).atZone(ZoneId.systemDefault()).toInstant();
} }
@ -242,7 +242,7 @@ public enum ZoneManager {
//no duplicate hotZones //no duplicate hotZones
if (zone.hasBeenHotzone == true) if (zone.wasHotzonw == true)
return false; return false;
// Enforce min level // Enforce min level

9
src/engine/mobileAI/MobAI.java

@ -15,6 +15,7 @@ import engine.gameManager.*;
import engine.math.Vector3f; import engine.math.Vector3f;
import engine.math.Vector3fImmutable; import engine.math.Vector3fImmutable;
import engine.mobileAI.Threads.MobAIThread; import engine.mobileAI.Threads.MobAIThread;
import engine.mobileAI.Threads.Respawner;
import engine.mobileAI.utilities.CombatUtilities; import engine.mobileAI.utilities.CombatUtilities;
import engine.mobileAI.utilities.MovementUtilities; import engine.mobileAI.utilities.MovementUtilities;
import engine.net.DispatchMessage; import engine.net.DispatchMessage;
@ -813,11 +814,9 @@ public class MobAI {
} }
} }
} }
} else if (System.currentTimeMillis() > (aiAgent.deathTime + (aiAgent.spawnTime * 1000))) { } else if (System.currentTimeMillis() > (aiAgent.deathTime + (aiAgent.spawnDelay * 1000))) {
aiAgent.respawnTime = aiAgent.deathTime + (aiAgent.spawnDelay * 1000);
if (Zone.respawnQue.contains(aiAgent) == false) { Respawner.respawnQueue.put(aiAgent);
Zone.respawnQue.add(aiAgent);
}
} }
} catch (Exception e) { } catch (Exception e) {
Logger.info(aiAgent.getObjectUUID() + " " + aiAgent.getName() + " Failed At: CheckForRespawn" + " " + e.getMessage()); Logger.info(aiAgent.getObjectUUID() + " " + aiAgent.getName() + " Failed At: CheckForRespawn" + " " + e.getMessage());

38
src/engine/mobileAI/Threads/MobRespawnThread.java → src/engine/mobileAI/Threads/Respawner.java

@ -6,20 +6,17 @@
// Magicbane Emulator Project © 2013 - 2022 // Magicbane Emulator Project © 2013 - 2022
// www.magicbane.com // www.magicbane.com
package engine.mobileAI.Threads;
import engine.gameManager.ZoneManager; package engine.mobileAI.Threads;
import engine.objects.Mob; import engine.objects.Mob;
import engine.objects.Zone;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
public class MobRespawnThread implements Runnable { import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
public MobRespawnThread() { public enum Respawner implements Runnable {
Logger.info(" MobRespawnThread thread has started!"); RESPAWNER;
public static BlockingQueue<Mob> respawnQueue = new DelayQueue();
}
@Override @Override
public void run() { public void run() {
@ -27,27 +24,18 @@ public class MobRespawnThread implements Runnable {
while (true) { while (true) {
try { try {
for (Zone zone : ZoneManager.getAllZones()) Mob mobile = respawnQueue.take();
if (zone.respawnQue.isEmpty() == false && zone.lastRespawn + 100 < System.currentTimeMillis()) { mobile.respawn();
} catch (InterruptedException e) {
Mob respawner = zone.respawnQue.iterator().next(); Logger.error(e.toString());
if (respawner == null)
continue;
respawner.respawn();
zone.respawnQue.remove(respawner);
zone.lastRespawn = System.currentTimeMillis();
}
} catch (Exception e) {
Logger.error(e);
} }
} }
} }
public static void startRespawnThread() {
public static void start() {
Thread respawnThread; Thread respawnThread;
respawnThread = new Thread(new MobRespawnThread()); respawnThread = new Thread(RESPAWNER);
respawnThread.setName("respawnThread"); respawnThread.setName("respawnThread");
respawnThread.start(); respawnThread.start();
} }

4
src/engine/net/client/msg/ManageCityAssetsMsg.java

@ -727,9 +727,9 @@ public class ManageCityAssetsMsg extends ClientNetMsg {
if (!npcHire.isAlive()) { if (!npcHire.isAlive()) {
writer.put((byte) 1); // 1 SHOWs respawning writer.put((byte) 1); // 1 SHOWs respawning
int respawnRemaining = (int) (((Mob) npcHire).deathTime + ((Mob) npcHire).spawnTime * 1000 - System.currentTimeMillis()) / 1000; int respawnRemaining = (int) (((Mob) npcHire).deathTime + ((Mob) npcHire).spawnDelay * 1000 - System.currentTimeMillis()) / 1000;
writer.putInt(respawnRemaining); // Seconds in respawn remaining. writer.putInt(respawnRemaining); // Seconds in respawn remaining.
writer.putInt(((Mob) npcHire).spawnTime); // max seconds for respawn writer.putInt(((Mob) npcHire).spawnDelay); // max seconds for respawn
} else } else
writer.put((byte) 0); writer.put((byte) 0);

12
src/engine/net/client/msg/ManageNPCMsg.java

@ -364,14 +364,14 @@ public class ManageNPCMsg extends ClientNetMsg {
writer.putInt(1); writer.putInt(1);
long curTime = System.currentTimeMillis() / 1000; long curTime = System.currentTimeMillis() / 1000;
long upgradeTime = (mob.deathTime + (mob.spawnTime * 1000)) / 1000; long upgradeTime = (mob.deathTime + (mob.spawnDelay * 1000)) / 1000;
long timeLife = upgradeTime - curTime; long timeLife = upgradeTime - curTime;
if (upgradeTime * 1000 > System.currentTimeMillis()) { if (upgradeTime * 1000 > System.currentTimeMillis()) {
if (mob.guardCaptain.isAlive()) { if (mob.guardCaptain.isAlive()) {
writer.put((byte) 0);//shows respawning timer writer.put((byte) 0);//shows respawning timer
writer.putInt(mob.spawnTime); writer.putInt(mob.spawnDelay);
writer.putInt(mob.spawnTime); writer.putInt(mob.spawnDelay);
writer.putInt((int) timeLife); //time remaining for mob that is dead writer.putInt((int) timeLife); //time remaining for mob that is dead
writer.putInt(0); writer.putInt(0);
writer.put((byte) 0); writer.put((byte) 0);
@ -688,14 +688,14 @@ public class ManageNPCMsg extends ClientNetMsg {
writer.putInt(1); writer.putInt(1);
long curTime = System.currentTimeMillis() / 1000; long curTime = System.currentTimeMillis() / 1000;
long upgradeTime = (mob.deathTime + (mob.spawnTime * 1000)) / 1000; long upgradeTime = (mob.deathTime + (mob.spawnDelay * 1000)) / 1000;
long timeLife = upgradeTime - curTime; long timeLife = upgradeTime - curTime;
if (upgradeTime * 1000 > System.currentTimeMillis()) { if (upgradeTime * 1000 > System.currentTimeMillis()) {
if (mob.guardCaptain.isAlive()) { if (mob.guardCaptain.isAlive()) {
writer.put((byte) 0);//shows respawning timer writer.put((byte) 0);//shows respawning timer
writer.putInt(mob.spawnTime); writer.putInt(mob.spawnDelay);
writer.putInt(mob.spawnTime); writer.putInt(mob.spawnDelay);
writer.putInt((int) timeLife); //time remaining for mob that is dead writer.putInt((int) timeLife); //time remaining for mob that is dead
writer.putInt(0); writer.putInt(0);
writer.put((byte) 0); writer.put((byte) 0);

40
src/engine/objects/Mob.java

@ -26,6 +26,7 @@ import engine.net.DispatchMessage;
import engine.net.client.msg.PetMsg; import engine.net.client.msg.PetMsg;
import engine.net.client.msg.PlaceAssetMsg; import engine.net.client.msg.PlaceAssetMsg;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.jetbrains.annotations.NotNull;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@ -34,11 +35,14 @@ import java.sql.SQLException;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import static engine.net.client.msg.ErrorPopupMsg.sendErrorPopup; import static engine.net.client.msg.ErrorPopupMsg.sendErrorPopup;
import static java.lang.Math.toIntExact;
public class Mob extends AbstractIntelligenceAgent { public class Mob extends AbstractIntelligenceAgent implements Delayed {
private static int staticID = 0; private static int staticID = 0;
//mob specific //mob specific
@ -51,10 +55,11 @@ public class Mob extends AbstractIntelligenceAgent {
public boolean despawned = false; public boolean despawned = false;
public Vector3fImmutable destination = Vector3fImmutable.ZERO; public Vector3fImmutable destination = Vector3fImmutable.ZERO;
public MobBase mobBase; public MobBase mobBase;
public int spawnTime; public int spawnDelay;
public Zone parentZone; public Zone parentZone;
public boolean hasLoot = false; public boolean hasLoot = false;
public long deathTime = 0; public long deathTime = 0;
public long respawnTime = 0;
public int equipmentSetID = 0; public int equipmentSetID = 0;
public int runeSet = 0; public int runeSet = 0;
public int bootySet = 0; public int bootySet = 0;
@ -104,7 +109,7 @@ public class Mob extends AbstractIntelligenceAgent {
this.agentType = AIAgentType.MOBILE; this.agentType = AIAgentType.MOBILE;
this.spawnRadius = rs.getFloat("mob_spawnRadius"); this.spawnRadius = rs.getFloat("mob_spawnRadius");
this.spawnTime = rs.getInt("mob_spawnTime"); this.spawnDelay = rs.getInt("mob_spawnTime");
statLat = rs.getFloat("mob_spawnX"); statLat = rs.getFloat("mob_spawnX");
statAlt = rs.getFloat("mob_spawnY"); statAlt = rs.getFloat("mob_spawnY");
@ -135,8 +140,8 @@ public class Mob extends AbstractIntelligenceAgent {
if (this.upgradeDateTime != null) if (this.upgradeDateTime != null)
Mob.submitUpgradeJob(this); Mob.submitUpgradeJob(this);
if (this.mobBase != null && this.spawnTime == 0) if (this.mobBase != null && this.spawnDelay == 0)
this.spawnTime = this.mobBase.getSpawnTime(); this.spawnDelay = this.mobBase.getSpawnTime();
this.runeSet = rs.getInt("runeSet"); this.runeSet = rs.getInt("runeSet");
this.bootySet = rs.getInt("bootySet"); this.bootySet = rs.getInt("bootySet");
@ -450,7 +455,7 @@ public class Mob extends AbstractIntelligenceAgent {
minionMobile.deathTime = System.currentTimeMillis(); minionMobile.deathTime = System.currentTimeMillis();
minionMobile.guardCaptain = guardCaptain; minionMobile.guardCaptain = guardCaptain;
minionMobile.spawnTime = (int) (-2.500 * guardCaptain.building.getRank() + 22.5) * 60; minionMobile.spawnDelay = (int) (-2.500 * guardCaptain.building.getRank() + 22.5) * 60;
minionMobile.behaviourType = Enum.MobBehaviourType.GuardMinion; minionMobile.behaviourType = Enum.MobBehaviourType.GuardMinion;
minionMobile.agentType = AIAgentType.GUARDMINION; minionMobile.agentType = AIAgentType.GUARDMINION;
minionMobile.guardedCity = guardCaptain.guardedCity; minionMobile.guardedCity = guardCaptain.guardedCity;
@ -509,7 +514,7 @@ public class Mob extends AbstractIntelligenceAgent {
siegeMinion.behaviourType = MobBehaviourType.SiegeEngine; siegeMinion.behaviourType = MobBehaviourType.SiegeEngine;
siegeMinion.agentType = AIAgentType.SIEGEENGINE; siegeMinion.agentType = AIAgentType.SIEGEENGINE;
siegeMinion.bindLoc = Vector3fImmutable.ZERO; siegeMinion.bindLoc = Vector3fImmutable.ZERO;
siegeMinion.spawnTime = (60 * 15); siegeMinion.spawnDelay = (60 * 15);
siegeMinion.runAfterLoad(); siegeMinion.runAfterLoad();
@ -650,10 +655,10 @@ public class Mob extends AbstractIntelligenceAgent {
} }
public String getSpawnTimeAsString() { public String getSpawnTimeAsString() {
if (this.spawnTime == 0) if (this.spawnDelay == 0)
return MBServerStatics.DEFAULT_SPAWN_TIME_MS / 1000 + " seconds (Default)"; return MBServerStatics.DEFAULT_SPAWN_TIME_MS / 1000 + " seconds (Default)";
else else
return this.spawnTime + " seconds"; return this.spawnDelay + " seconds";
} }
@ -875,7 +880,7 @@ public class Mob extends AbstractIntelligenceAgent {
this.playerAgroMap.clear(); this.playerAgroMap.clear();
if (this.behaviourType.ordinal() == Enum.MobBehaviourType.GuardMinion.ordinal()) if (this.behaviourType.ordinal() == Enum.MobBehaviourType.GuardMinion.ordinal())
this.spawnTime = (int) (-2.500 * this.guardCaptain.building.getRank() + 22.5) * 60; this.spawnDelay = (int) (-2.500 * this.guardCaptain.building.getRank() + 22.5) * 60;
if (this.isPet()) { if (this.isPet()) {
@ -1547,12 +1552,12 @@ public class Mob extends AbstractIntelligenceAgent {
switch (this.behaviourType) { switch (this.behaviourType) {
case GuardCaptain: case GuardCaptain:
this.agentType = AIAgentType.GUARDCAPTAIN; this.agentType = AIAgentType.GUARDCAPTAIN;
this.spawnTime = 600; this.spawnDelay = 600;
this.guardedCity = ZoneManager.getCityAtLocation(this.building.getLoc()); this.guardedCity = ZoneManager.getCityAtLocation(this.building.getLoc());
break; break;
case GuardWallArcher: case GuardWallArcher:
this.agentType = AIAgentType.GUARDWALLARCHER; this.agentType = AIAgentType.GUARDWALLARCHER;
this.spawnTime = 450; this.spawnDelay = 450;
this.guardedCity = ZoneManager.getCityAtLocation(this.building.getLoc()); this.guardedCity = ZoneManager.getCityAtLocation(this.building.getLoc());
break; break;
} }
@ -1883,4 +1888,15 @@ public class Mob extends AbstractIntelligenceAgent {
lock.writeLock().unlock(); lock.writeLock().unlock();
} }
} }
@Override
public long getDelay(@NotNull TimeUnit unit) {
long timeRemaining = this.respawnTime - System.currentTimeMillis();
return unit.convert(timeRemaining, TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(@NotNull Delayed o) {
return toIntExact(this.respawnTime - ((Mob) o).respawnTime);
}
} }

13
src/engine/objects/Zone.java

@ -29,8 +29,6 @@ import java.util.concurrent.ConcurrentHashMap;
public class Zone extends AbstractWorldObject { public class Zone extends AbstractWorldObject {
public static final Set<Mob> respawnQue = Collections.newSetFromMap(new ConcurrentHashMap<>());
public static long lastRespawn = 0;
public final Set<Building> zoneBuildingSet = Collections.newSetFromMap(new ConcurrentHashMap<>()); public final Set<Building> zoneBuildingSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
public final Set<NPC> zoneNPCSet = Collections.newSetFromMap(new ConcurrentHashMap<>()); public final Set<NPC> zoneNPCSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
public final Set<Mob> zoneMobSet = Collections.newSetFromMap(new ConcurrentHashMap<>()); public final Set<Mob> zoneMobSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
@ -54,7 +52,7 @@ public class Zone extends AbstractWorldObject {
public float absZ = 0.0f; public float absZ = 0.0f;
public int min_level; public int min_level;
public int max_level; public int max_level;
public boolean hasBeenHotzone = false; public boolean wasHotzonw = false;
public ArrayList<Zone> nodes = new ArrayList<>(); public ArrayList<Zone> nodes = new ArrayList<>();
public int parentZoneID; public int parentZoneID;
public Zone parent = null; public Zone parent = null;
@ -165,12 +163,16 @@ public class Zone extends AbstractWorldObject {
if (ZoneManager.seaFloor == null) if (ZoneManager.seaFloor == null)
ZoneManager.seaFloor = this; ZoneManager.seaFloor = this;
this.setParent();
this.setBounds();
if (this.template.terrain_type.equals("NONE")) if (this.template.terrain_type.equals("NONE"))
this.terrain = null; this.terrain = null;
else else
this.terrain = new Terrain(this); this.terrain = new Terrain(this);
this.setParent(); this.global_height = ZoneManager.calculateGlobalZoneHeight(this);
setSeaLevel();
if (this.min_level == 0 && this.parent != null) { if (this.min_level == 0 && this.parent != null) {
this.min_level = this.parent.min_level; this.min_level = this.parent.min_level;
@ -222,9 +224,6 @@ public class Zone extends AbstractWorldObject {
this.max_level = this.parent.max_level; this.max_level = this.parent.max_level;
} }
this.setBounds();
this.global_height = ZoneManager.calculateGlobalZoneHeight(this);
setSeaLevel();
} }
private void setSeaLevel() { private void setSeaLevel() {

4
src/engine/server/world/WorldServer.java

@ -23,7 +23,7 @@ import engine.job.JobContainer;
import engine.job.JobScheduler; import engine.job.JobScheduler;
import engine.jobs.LogoutCharacterJob; import engine.jobs.LogoutCharacterJob;
import engine.mobileAI.Threads.MobAIThread; import engine.mobileAI.Threads.MobAIThread;
import engine.mobileAI.Threads.MobRespawnThread; import engine.mobileAI.Threads.Respawner;
import engine.net.Dispatch; import engine.net.Dispatch;
import engine.net.DispatchMessage; import engine.net.DispatchMessage;
import engine.net.ItemProductionManager; import engine.net.ItemProductionManager;
@ -486,7 +486,7 @@ public class WorldServer {
//intiate mob respawn thread //intiate mob respawn thread
Logger.info("Starting Mob Respawn Thread"); Logger.info("Starting Mob Respawn Thread");
MobRespawnThread.startRespawnThread(); Respawner.start();
// Run maintenance // Run maintenance

Loading…
Cancel
Save