Public Repository for the Magicbane Shadowbane Emulator
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

582 lines
20 KiB

// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2022
// www.magicbane.com
package engine.InterestManagement;
import engine.Enum;
import engine.Enum.DispatchChannel;
import engine.Enum.GameObjectType;
import engine.gameManager.GroupManager;
import engine.gameManager.SessionManager;
import engine.job.JobScheduler;
import engine.jobs.RefreshGroupJob;
import engine.net.AbstractNetMsg;
import engine.net.Dispatch;
import engine.net.DispatchMessage;
import engine.net.client.ClientConnection;
import engine.net.client.msg.LoadCharacterMsg;
import engine.net.client.msg.LoadStructureMsg;
import engine.net.client.msg.MoveToPointMsg;
import engine.net.client.msg.UnloadObjectsMsg;
import engine.objects.*;
import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
import java.util.HashSet;
import static engine.math.FastMath.sqr;
public enum InterestManager implements Runnable {
INTERESTMANAGER;
private static long lastTime;
InterestManager() {
Logger.info(" Interest Management thread is running.");
}
public static void forceLoad(AbstractWorldObject awo) {
AbstractNetMsg msg = null;
LoadStructureMsg lsm;
LoadCharacterMsg lcm;
NPC npc;
Corpse corpse;
HashSet<AbstractWorldObject> toUpdate;
switch (awo.getObjectType()) {
case Building:
lsm = new LoadStructureMsg();
lsm.addObject((Building) awo);
msg = lsm;
break;
case Corpse:
corpse = (Corpse) awo;
lcm = new LoadCharacterMsg(corpse, false);
msg = lcm;
break;
case NPC:
npc = (NPC) awo;
lcm = new LoadCharacterMsg(npc, false);
msg = lcm;
break;
default:
return;
}
toUpdate = WorldGrid.getObjectsInRangePartial(awo.getLoc(), MBServerStatics.CHARACTER_LOAD_RANGE, MBServerStatics.MASK_PLAYER);
boolean send;
for (AbstractWorldObject tar : toUpdate) {
PlayerCharacter player = (PlayerCharacter) tar;
HashSet<AbstractWorldObject> loadedStaticObjects = player.getLoadedStaticObjects();
send = false;
if (!loadedStaticObjects.contains(awo)) {
loadedStaticObjects.add(awo);
send = true;
}
if (send) {
Dispatch dispatch = Dispatch.borrow(player, msg);
DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
}
}
}
public static void HandleSpecialUnload(Building building, ClientConnection origin) {
if (Regions.FurnitureRegionMap.get(building.getObjectUUID()) == null)
return;
Regions buildingRegion = Regions.FurnitureRegionMap.get(building.getObjectUUID());
if (!buildingRegion.isOutside())
return;
MoveToPointMsg moveMsg = new MoveToPointMsg(building);
if (origin != null)
origin.sendMsg(moveMsg);
}
public static void reloadCharacter(AbstractCharacter absChar) {
UnloadObjectsMsg uom = new UnloadObjectsMsg();
uom.addObject(absChar);
LoadCharacterMsg lcm = new LoadCharacterMsg(absChar, false);
HashSet<AbstractWorldObject> toSend = WorldGrid.getObjectsInRangePartial(absChar.getLoc(), MBServerStatics.CHARACTER_LOAD_RANGE,
MBServerStatics.MASK_PLAYER);
PlayerCharacter pc = null;
if (absChar.getObjectType().equals(GameObjectType.PlayerCharacter))
pc = (PlayerCharacter) absChar;
for (AbstractWorldObject awo : toSend) {
PlayerCharacter pcc = (PlayerCharacter) awo;
if (pcc == null)
continue;
ClientConnection cc = SessionManager.getClientConnection(pcc);
if (cc == null)
continue;
if (pcc.getObjectUUID() == absChar.getObjectUUID())
continue;
else {
if (pc != null)
if (pcc.getSeeInvis() < pc.getHidden())
continue;
if (!cc.sendMsg(uom)) {
String classType = uom.getClass().getSimpleName();
Logger.error("Failed to send message ");
}
if (!cc.sendMsg(lcm)) {
String classType = lcm.getClass().getSimpleName();
Logger.error("Failed to send message");
}
}
}
}
@Override
public void run() {
beginLoadJob();
}
private void beginLoadJob() {
InterestManager.lastTime = System.currentTimeMillis();
while (true) {
try {
updateAllPlayers();
} catch (Exception e) {
Logger.error("InterestManager.BeginLoadJob:updateAllPlayers", e);
}
try {
Thread.sleep(advanceOneSecond());
} catch (Exception e) {
Logger.error("InterestManager.BeginLoadJob:advanceOneSecond", e);
}
}
}
private long advanceOneSecond() {
long curTime = System.currentTimeMillis();
long dur = 1000 + this.lastTime - curTime;
if (dur < 0) {
// Last update took more then one second, not good...
Logger.warn("LoadJob took more then one second to complete.");
this.lastTime = curTime + 100;
return 100;
}
this.lastTime += 1000;
return dur;
}
// Forces the loading of static objects (corpses and buildings).
// Needed to override threshold limits on loading statics
private void updateAllPlayers() {
// get all players
for (PlayerCharacter pc : SessionManager.getAllActivePlayerCharacters()) {
if (pc == null)
continue;
ClientConnection origin = pc.getClientConnection();
if (origin == null)
continue;
if (!pc.isEnteredWorld())
continue;
if (pc.getTeleportLock().readLock().tryLock()) {
try {
updateStaticList(pc, origin);
updateMobileList(pc, origin);
} catch (Exception e) {
Logger.error(e);
} finally {
pc.getTeleportLock().readLock().unlock();
}
}
}
}
private void updateStaticList(PlayerCharacter player, ClientConnection origin) {
// Only update if we've moved far enough to warrant it
float distanceSquared = player.getLoc().distanceSquared2D(player.getLastStaticLoc());
if (distanceSquared > sqr(25)) {
player.setLastStaticLoc(player.getLoc());
player.dirtyLoad = true;
}
if (player.dirtyLoad == false)
return;
// Get Statics in range
HashSet<AbstractWorldObject> toLoad = WorldGrid.getObjectsInRangePartial(player.getLoc(), MBServerStatics.STRUCTURE_LOAD_RANGE,
MBServerStatics.MASK_STATIC);
// get list of obects loaded that need removed
HashSet<AbstractWorldObject> loadedStaticObjects = player.getLoadedStaticObjects();
HashSet<AbstractWorldObject> toRemove = null;
toRemove = new HashSet<>(loadedStaticObjects);
toRemove.removeAll(toLoad);
// unload static objects now out of range
if (toRemove.size() > 0) {
UnloadObjectsMsg uom = new UnloadObjectsMsg();
for (AbstractWorldObject obj : toRemove) {
if (obj.getObjectType().equals(GameObjectType.Building))
InterestManager.HandleSpecialUnload((Building) obj, origin);
if (obj != null && !obj.equals(player))
uom.addObject(obj);
}
Dispatch dispatch = Dispatch.borrow(player, uom);
DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
}
loadedStaticObjects.removeAll(toRemove);
// remove any object to load that are already loaded
toLoad.removeAll(loadedStaticObjects);
LoadStructureMsg lsm = new LoadStructureMsg();
LoadCharacterMsg lcm = null;
ArrayList<LoadCharacterMsg> lcmList = new ArrayList<>();
for (AbstractWorldObject awo : toLoad) {
if (awo.getObjectType().equals(GameObjectType.Building))
lsm.addObject((Building) awo);
else if (awo.getObjectType().equals(GameObjectType.Corpse)) {
Corpse corpse = (Corpse) awo;
lcm = new LoadCharacterMsg(corpse, PlayerCharacter.hideNonAscii());
Dispatch dispatch = Dispatch.borrow(player, lcm);
DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
} else if (awo.getObjectType().equals(GameObjectType.NPC)) {
NPC npc = (NPC) awo;
lcm = new LoadCharacterMsg(npc, PlayerCharacter.hideNonAscii());
lcmList.add(lcm);
}
}
if (lsm.getStructureList().size() > 0) {
Dispatch dispatch = Dispatch.borrow(player, lsm);
DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
}
for (LoadCharacterMsg lc : lcmList) {
Dispatch dispatch = Dispatch.borrow(player, lc);
DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
}
loadedStaticObjects.addAll(toLoad);
player.dirtyLoad = false;
}
private void updateMobileList(PlayerCharacter player, ClientConnection origin) {
if (player == null)
return;
// Get list of players in range
// TODO for now use a generic getALL list, later tie into Quad Tree
HashSet<AbstractWorldObject> toLoad = WorldGrid.getObjectsInRangePartial(player.getLoc(), MBServerStatics.CHARACTER_LOAD_RANGE,
MBServerStatics.MASK_MOBILE);
HashSet<AbstractWorldObject> toRemove = new HashSet<>();
HashSet<AbstractWorldObject> toLoadToPlayer = new HashSet<>();
for (AbstractWorldObject loadedObject : toLoad) {
switch (loadedObject.getObjectType()) {
case PlayerCharacter:
PlayerCharacter loadedPlayer = (PlayerCharacter) loadedObject;
if (loadedPlayer.getObjectUUID() == player.getObjectUUID())
continue;
if (player.getSeeInvis() < loadedPlayer.getHidden())
continue;
if (loadedPlayer.safemodeInvis())
continue;
if (player.getLoadedObjects().contains(loadedPlayer))
continue;
if (!loadedPlayer.isInWorldGrid())
continue;
toLoadToPlayer.add(loadedPlayer);
break;
//not playerCharacter, mobs,npcs and corpses cant be invis or safemode, just add normaly
default:
if (player.getLoadedObjects().contains(loadedObject))
continue;
if (!loadedObject.isInWorldGrid())
continue;
toLoadToPlayer.add(loadedObject);
break;
}
}
float unloadDistance = MBServerStatics.CHARACTER_LOAD_RANGE;
for (AbstractWorldObject playerLoadedObject : player.getLoadedObjects()) {
if (playerLoadedObject.getObjectType().equals(GameObjectType.PlayerCharacter)) {
PlayerCharacter loadedPlayer = (PlayerCharacter) playerLoadedObject;
if (player.getSeeInvis() < loadedPlayer.getHidden())
toRemove.add(playerLoadedObject);
else if (loadedPlayer.safemodeInvis())
toRemove.add(playerLoadedObject);
}
if (!playerLoadedObject.isInWorldGrid())
toRemove.add(playerLoadedObject);
else if (playerLoadedObject.getLoc().distanceSquared2D(player.getLoc()) > unloadDistance * unloadDistance)
toRemove.add(playerLoadedObject);
}
player.getLoadedObjects().addAll(toLoadToPlayer);
player.getLoadedObjects().removeAll(toRemove);
// get list of obects loaded to remove
// unload objects now out of range
if (toRemove.size() > 0) {
UnloadObjectsMsg uom = new UnloadObjectsMsg();
for (AbstractWorldObject obj : toRemove) {
try {
if (obj != null)
if (obj.equals(player)) // don't unload self
continue;
uom.addObject(obj);
if (obj.getObjectType() == GameObjectType.Mob)
((Mob) obj).playerAgroMap.remove(player.getObjectUUID());
} catch (Exception e) {
Logger.error("UnloadCharacter", obj.getObjectUUID() + " " + e.getMessage());
}
}
if (!uom.getObjectList().isEmpty()) {
Dispatch dispatch = Dispatch.borrow(player, uom);
DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
}
}
LoadCharacterMsg lcm = null;
ArrayList<AbstractWorldObject> players = new ArrayList<>();
ArrayList<AbstractWorldObject> addToList = new ArrayList<>();
for (AbstractWorldObject awo : toLoadToPlayer) {
try {
if (awo.equals(player)) // dont load yourself
continue;
if ((awo.getObjectTypeMask() & MBServerStatics.MASK_PLAYER) != 0) {
// object to load is a player
PlayerCharacter awopc = (PlayerCharacter) awo;
// dont load if invis
if (player.getSeeInvis() < awopc.getHidden())
continue;
lcm = new LoadCharacterMsg(awopc, PlayerCharacter.hideNonAscii());
players.add(awo);
// check if in a group with the person being loaded
// and if so set updateGroup flag
if (GroupManager.getGroup(player) != null
&& GroupManager.getGroup(player) == GroupManager.getGroup(awopc))
// submit a job as for some reason the client needs a delay
// with group updates
// as it wont update if we do RefreshGroup directly after
// sending the lcm below
JobScheduler.getInstance().scheduleJob(new RefreshGroupJob(player, awopc), MBServerStatics.LOAD_OBJECT_DELAY);
} else if ((awo.getObjectTypeMask() & MBServerStatics.MASK_MOB) != 0) {
Mob awonpc = (Mob) awo;
if (!awonpc.isAlive() && (awonpc.isPet() || awonpc.isSiege() || awonpc.isNecroPet() || awonpc.isPlayerGuard()))
continue;
//removed, interest manager should still load mob corpses
if (awonpc.despawned == true)
continue;
awonpc.playerAgroMap.put(player.getObjectUUID(), false);
((Mob) awonpc).setCombatTarget(null);
lcm = new LoadCharacterMsg(awonpc, PlayerCharacter.hideNonAscii());
} else if ((awo.getObjectTypeMask() & MBServerStatics.MASK_NPC) != 0) {
NPC awonpc = (NPC) awo;
lcm = new LoadCharacterMsg(awonpc, PlayerCharacter.hideNonAscii());
} else if ((awo.getObjectTypeMask() & MBServerStatics.MASK_PET) != 0) {
Mob awonpc = (Mob) awo;
if (!awonpc.isAlive())
continue;
awonpc.playerAgroMap.put(player.getObjectUUID(), false);
if ((awonpc.agentType.equals(Enum.AIAgentType.MOBILE)))
((Mob) awonpc).setCombatTarget(null);
lcm = new LoadCharacterMsg(awonpc, PlayerCharacter.hideNonAscii());
}
addToList.add(awo);
if (lcm != null) {
Dispatch dispatch = Dispatch.borrow(player, lcm);
DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
}
} catch (Exception e) {
Logger.error(awo.getObjectUUID() + " " + e.getMessage());
}
//Delaying character loading to reduce bandwidth consumption
}
// send effects for all players being loaded
// do it on a timer otherwise we may get failures as te client needs
// time to process lcm
//Added effects to LoadCharacter Serialization.
//JobScheduler.getInstance().scheduleJob(new LoadEffectsJob(players, origin), MBServerStatics.LOAD_OBJECT_DELAY);
}
public synchronized void HandleLoadForEnterWorld(PlayerCharacter player) {
if (player == null)
return;
ClientConnection origin = player.getClientConnection();
if (origin == null)
return;
//Update static list
try {
updateStaticList(player, origin);
} catch (Exception e) {
Logger.error("InterestManager.updateAllStaticPlayers: " + player.getObjectUUID(), e);
}
//Update mobile list
try {
updateMobileList(player, origin);
} catch (Exception e) {
Logger.error("InterestManager.updateAllMobilePlayers: " + player.getObjectUUID(), e);
}
}
public synchronized void HandleLoadForTeleport(PlayerCharacter player) {
if (player == null)
return;
ClientConnection origin = player.getClientConnection();
if (origin == null)
return;
//Update static list
try {
updateStaticList(player, origin);
} catch (Exception e) {
Logger.error("InterestManager.updateAllStaticPlayers: " + player.getObjectUUID(), e);
}
//Update mobile list
try {
updateMobileList(player, origin);
} catch (Exception e) {
Logger.error("InterestManager.updateAllMobilePlayers: " + player.getObjectUUID(), e);
}
}
public static void setObjectDirty(AbstractWorldObject abstractWorldObject) {
HashSet<AbstractWorldObject> playerList = WorldGrid.getObjectsInRangePartial(abstractWorldObject.getLoc(), 128, MBServerStatics.MASK_PLAYER);
if (playerList.isEmpty())
return;
for (AbstractWorldObject playerObject : playerList) {
PlayerCharacter playerCharacter = (PlayerCharacter) playerObject;
playerCharacter.dirtyLoad = true;
}
}
}