// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2022 // www.magicbane.com package engine.objects; import engine.Enum.DispatchChannel; import engine.Enum.EffectSourceType; import engine.Enum.GameObjectType; import engine.Enum.GridObjectType; import engine.InterestManagement.HeightMap; import engine.InterestManagement.WorldGrid; import engine.job.AbstractScheduleJob; import engine.job.JobContainer; import engine.job.JobScheduler; import engine.jobs.NoTimeJob; import engine.math.AtomicFloat; import engine.math.Bounds; import engine.math.Vector3f; import engine.math.Vector3fImmutable; import engine.net.Dispatch; import engine.net.DispatchMessage; import engine.net.client.ClientConnection; import engine.net.client.msg.UpdateEffectsMsg; import engine.powers.EffectsBase; import engine.server.MBServerStatics; import org.pmw.tinylog.Logger; import java.sql.ResultSet; import java.sql.SQLException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public abstract class AbstractWorldObject extends AbstractGameObject { protected final ReadWriteLock locationLock = new ReentrantReadWriteLock(true); protected final ReadWriteLock updateLock = new ReentrantReadWriteLock(true); public float healthMax; public ConcurrentHashMap effects = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); public int gridX = -1; public int gridZ = -1; public Regions region; public Regions landingRegion = null; public Vector3fImmutable lastLoc = Vector3fImmutable.ZERO; public Vector3fImmutable loc = new Vector3fImmutable(0.0f, 0.0f, 0.0f); protected AtomicFloat health = new AtomicFloat(); protected boolean load = true; protected GridObjectType gridObjectType; protected float altitude = 0; protected boolean movingUp = false; private String name = ""; private byte tier = 0; private Vector3f rot = new Vector3f(0.0f, 0.0f, 0.0f); private int objectTypeMask = 0; private Bounds bounds; /** * No Id Constructor */ public AbstractWorldObject() { super(); } /** * Normal Constructor */ public AbstractWorldObject(int objectUUID) { super(objectUUID); } /** * ResultSet Constructor */ public AbstractWorldObject(ResultSet rs) throws SQLException { super(rs); } public static int getType() { return 0; } public static boolean IsAbstractCharacter(AbstractWorldObject awo) { if (awo == null) return false; if (awo.getObjectType() == GameObjectType.PlayerCharacter || awo.getObjectType() == GameObjectType.Mob || awo.getObjectType() == GameObjectType.NPC) return true; return false; } public static void RemoveFromWorldGrid(AbstractWorldObject gridObjectToRemove) { if (gridObjectToRemove.gridX < 0 || gridObjectToRemove.gridZ < 0) return; ConcurrentHashMap gridMap; switch (gridObjectToRemove.gridObjectType) { case STATIC: gridMap = WorldGrid.StaticGridMap[gridObjectToRemove.gridX][gridObjectToRemove.gridZ]; break; case DYNAMIC: gridMap = WorldGrid.DynamicGridMap[gridObjectToRemove.gridX][gridObjectToRemove.gridZ]; break; default: gridMap = WorldGrid.StaticGridMap[gridObjectToRemove.gridX][gridObjectToRemove.gridZ]; break; } if (gridMap == null) { Logger.info("Null gridmap for Object UUD: " + gridObjectToRemove); return; } gridMap.remove(gridObjectToRemove.getObjectUUID()); gridObjectToRemove.gridX = -1; gridObjectToRemove.gridZ = -1; } public static boolean AddToWorldGrid(AbstractWorldObject gridObjectToAdd, int x, int z) { try { ConcurrentHashMap gridMap; if (gridObjectToAdd.gridObjectType.equals(GridObjectType.STATIC)) gridMap = WorldGrid.StaticGridMap[x][z]; else gridMap = WorldGrid.DynamicGridMap[x][z]; gridMap.put(gridObjectToAdd.getObjectUUID(), gridObjectToAdd); gridObjectToAdd.gridX = x; gridObjectToAdd.gridZ = z; return true; } catch (Exception e) { Logger.error(e); return false; } } public static Regions GetRegionByWorldObject(AbstractWorldObject worldObject) { Regions region = null; if (worldObject.getObjectType().equals(GameObjectType.PlayerCharacter)) if (((PlayerCharacter) worldObject).isFlying()) return null; //Find building for (AbstractWorldObject awo : WorldGrid.getObjectsInRangePartial(worldObject.getLoc(), MBServerStatics.STRUCTURE_LOAD_RANGE, MBServerStatics.MASK_BUILDING)) { Building building = (Building) awo; if (!Bounds.collide(worldObject.getLoc(), building.getBounds())) continue; //find regions that intersect x and z, check if object can enter. for (Regions toEnter : building.getBounds().getRegions()) { if (toEnter.isPointInPolygon(worldObject.getLoc())) { if (Regions.CanEnterRegion(worldObject, toEnter)) if (region == null) region = toEnter; else // we're using a low level to high level tree structure, database not always in order low to high. //check for highest level index. if (region != null && toEnter.highLerp.y > region.highLerp.y) region = toEnter; } } } //set players new altitude to region lerp altitude. if (region != null) if (region.center.y == region.highLerp.y) worldObject.loc = worldObject.loc.setY(region.center.y + worldObject.getAltitude()); else worldObject.loc = worldObject.loc.setY(region.lerpY(worldObject) + worldObject.getAltitude()); return region; } public static Regions GetRegionFromBuilding(Vector3fImmutable worldLoc, Building building) { Regions region = null; return region; } //this should be called to handle any after load functions. public abstract void runAfterLoad(); /* * Getters */ public float getHealth() { return this.health.get(); } /** * @param health the health to set */ public void setHealth(float health) { this.health.set(health); } public float getCurrentHitpoints() { return this.health.get(); } public float getHealthMax() { return this.healthMax; } public ConcurrentHashMap getEffects() { return this.effects; } //Add new effect public void addEffect(String name, int duration, AbstractScheduleJob asj, EffectsBase eb, int trains) { if (!isAlive() && eb.getToken() != 1672601862) { return; } JobContainer jc = JobScheduler.getInstance().scheduleJob(asj, duration); Effect eff = new Effect(jc, eb, trains); this.effects.put(name, eff); applyAllBonuses(); } public Effect addEffectNoTimer(String name, EffectsBase eb, int trains, boolean isStatic) { NoTimeJob ntj = new NoTimeJob(this, name, eb, trains); //infinite timer if (this.getObjectType() == GameObjectType.Item || this.getObjectType() == GameObjectType.City) { ntj.setEffectSourceType(this.getObjectType().ordinal()); ntj.setEffectSourceID(this.getObjectUUID()); } JobContainer jc = new JobContainer(ntj); Effect eff = new Effect(jc, eb, trains); if (isStatic) eff.setIsStatic(isStatic); this.effects.put(name, eff); applyAllBonuses(); return eff; } //called when an effect runs it's course public void endEffect(String name) { Effect eff = this.effects.get(name); if (eff == null) { return; } if (!isAlive() && eff.getEffectsBase().getToken() != 1672601862) { return; } if (eff.cancel()) { eff.endEffect(); this.effects.remove(name); if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) if (name.equals("Flight")) { ((PlayerCharacter) this).update(); PlayerCharacter.GroundPlayer((PlayerCharacter) this); } } applyAllBonuses(); } public void endEffectNoPower(String name) { Effect eff = this.effects.get(name); if (eff == null) { return; } if (!isAlive() && eff.getEffectsBase().getToken() != 1672601862) { return; } if (eff.cancel()) { eff.cancelJob(); eff.endEffectNoPower(); this.effects.remove(name); } applyAllBonuses(); } //Called to cancel an effect prematurely. public void cancelEffect(String name, boolean overwrite) { Effect eff = this.effects.get(name); if (eff == null) { return; } if (!isAlive() && eff.getEffectsBase().getToken() != 1672601862) { return; } if (eff.cancel()) { eff.cancelJob(); this.effects.remove(name); if (AbstractWorldObject.IsAbstractCharacter(this)) { ((AbstractCharacter) this).cancelLastChantIfSame(eff); } } if (!overwrite) { applyAllBonuses(); } } //Called when an object dies/is destroyed public void clearEffects() { for (String name : this.effects.keySet()) { Effect eff = this.effects.get(name); if (eff == null) { return; } //Dont remove deathshroud here! if (eff.getEffectToken() == 1672601862) continue; if (eff.cancel()) { if (eff.getPower() == null) { if (!eff.isStatic()) eff.endEffectNoPower(); } if (!eff.isStatic()) eff.cancelJob(); } this.effects.remove(name); } if (AbstractWorldObject.IsAbstractCharacter(this)) { ((AbstractCharacter) this).cancelLastChant(); } applyAllBonuses(); } public void removeEffectBySource(EffectSourceType source, int trains, boolean removeAll) { if (!isAlive() && source.equals(EffectSourceType.DeathShroud) == false) { return; } //hacky way to dispell trebs. if (this.getObjectType() == GameObjectType.Mob) { Mob mob = (Mob) this; if (mob.isSiege()) { if (mob.isPet()) { PlayerCharacter petOwner = (PlayerCharacter) mob.guardCaptain; if (petOwner != null && source.equals(EffectSourceType.Effect)) { petOwner.dismissPet(); return; } } } } boolean changed = false; String toRemove = ""; int toRemoveToken = Integer.MAX_VALUE; for (String name : this.effects.keySet()) { Effect eff = this.effects.get(name); if (eff == null) { continue; } if (eff.containsSource(source) && trains >= eff.getTrains()) { if (removeAll) { //remove all effects of source type if (eff.cancel()) { eff.cancelJob(); } this.effects.remove(name); changed = true; if (source.equals("Flight")) { //ground player if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) { ((PlayerCharacter) this).update(); PlayerCharacter.GroundPlayer((PlayerCharacter) this); } } } else { //find lowest token of source type to remove int tok = eff.getEffectToken(); if (tok != 0 && tok < toRemoveToken) { toRemove = name; toRemoveToken = tok; } } } } //WTF IS THIS? if (toRemoveToken < Integer.MAX_VALUE && this.effects.containsKey(toRemove)) { //remove lowest found token of source type Effect eff = this.effects.get(toRemove); if (eff != null) { changed = true; if (eff.cancel()) { eff.cancelJob(); } this.effects.remove(toRemove); if (source.equals("Flight")) { //ground player if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) { ((PlayerCharacter) this).update(); PlayerCharacter.GroundPlayer((PlayerCharacter) this); } } } } if (changed) { applyAllBonuses(); } } public void sendAllEffects(ClientConnection cc) { UpdateEffectsMsg msg = new UpdateEffectsMsg(this); Dispatch dispatch = Dispatch.borrow((PlayerCharacter) this, msg); DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); } public void applyAllBonuses() { if (AbstractWorldObject.IsAbstractCharacter(this)) { ((AbstractCharacter) this).applyBonuses(); } } public JobContainer getEffectJobContainer(String name) { Effect ef = this.effects.get(name); if (ef != null) { return ef.getJobContainer(); } return null; } public AbstractScheduleJob getEffectJob(String name) { Effect ef = this.effects.get(name); if (ef == null) { return null; } JobContainer jc = ef.getJobContainer(); if (jc != null) { return (AbstractScheduleJob) jc.getJob(); } return null; } public boolean containsEffect(int token) { for (Effect eff : this.effects.values()) { if (eff != null) { if (eff.getEffectsBase() != null) { if (eff.getEffectsBase().getToken() == token) { return true; } } } } return false; } public int getObjectTypeMask() { return objectTypeMask; } /* * Setters */ public void setObjectTypeMask(int mask) { this.objectTypeMask = mask; } public Vector3fImmutable getLoc() { return this.loc; } //TODO return false if something goes wrong? resync player? public void setLoc(Vector3fImmutable loc) { locationLock.writeLock().lock(); try { if (Float.isNaN(loc.x) || Float.isNaN(loc.z)) return; if (loc.equals(Vector3fImmutable.ZERO)) return; if (loc.x > MBServerStatics.MAX_WORLD_WIDTH || loc.z < MBServerStatics.MAX_WORLD_HEIGHT) return; this.lastLoc = new Vector3fImmutable(this.loc); this.loc = loc; this.loc = this.loc.setY(HeightMap.getWorldHeight(this.getLoc()) + this.getAltitude()); //lets not add mob to world grid if he is currently despawned. if (this.getObjectType().equals(GameObjectType.Mob) && ((Mob) this).despawned) return; //do not add objectUUID 0 to world grid. dunno da fuck this doing why its doing but its doing... da fuck. if (this.getObjectUUID() == 0) return; WorldGrid.addObject(this, loc.x, loc.z); } catch (Exception e) { Logger.error("Failed to set location for World Object. Type = " + this.getObjectType().name() + " : Name = " + this.getName()); e.printStackTrace(); } finally { locationLock.writeLock().unlock(); } } public Vector3f getRot() { return rot; } public void setRot(Vector3f rotation) { synchronized (this.rot) { this.rot = rotation; } } public byte getTier() { return tier; } public void setTier(byte tier) { synchronized (this.rot) { this.tier = tier; } } public boolean isAlive() { if (AbstractWorldObject.IsAbstractCharacter(this)) { return this.isAlive(); } else if (this.getObjectType().equals(GameObjectType.Building)) { return (!(((Building) this).getRank() < 0)); } else { return true; } } public void setY(float y) { this.loc = this.loc.setY(y); } public boolean load() { return this.load; } /* * Utils */ public String getName() { if (this.name.length() == 0) { return "Unnamed " + '(' + this.getObjectUUID() + ')'; } else { return this.name; } } public String getSimpleName() { return this.name; } /** * @return the bounds */ public Bounds getBounds() { return bounds; } public void setBounds(Bounds bounds) { this.bounds = bounds; } public float getAltitude() { return altitude; } public ReadWriteLock getUpdateLock() { return updateLock; } public GridObjectType getGridObjectType() { return gridObjectType; } public boolean isMovingUp() { return movingUp; } public void setMovingUp(boolean movingUp) { this.movingUp = movingUp; } //used for interestmanager loading and unloading objects to client. // if not in grid, unload from player. public boolean isInWorldGrid() { if (this.gridX == -1 && this.gridZ == -1) return false; return true; } }