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.
638 lines
16 KiB
638 lines
16 KiB
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . |
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· |
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ |
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ |
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ |
|
// 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 { |
|
|
|
private String name = ""; |
|
|
|
protected final ReadWriteLock locationLock = new ReentrantReadWriteLock(true); |
|
protected final ReadWriteLock updateLock = new ReentrantReadWriteLock(true); |
|
|
|
protected Vector3fImmutable loc = new Vector3fImmutable(0.0f, 0.0f, 0.0f); |
|
private byte tier = 0; |
|
private Vector3f rot = new Vector3f(0.0f, 0.0f, 0.0f); |
|
protected AtomicFloat health = new AtomicFloat(); |
|
public float healthMax; |
|
protected boolean load = true; |
|
public ConcurrentHashMap<String, Effect> effects = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); |
|
private int objectTypeMask = 0; |
|
private Bounds bounds; |
|
|
|
public int gridX = -1; |
|
public int gridZ = -1; |
|
|
|
protected GridObjectType gridObjectType; |
|
|
|
protected float altitude = 0; |
|
protected Regions region; |
|
protected boolean movingUp = false; |
|
public Regions landingRegion = null; |
|
public Vector3fImmutable lastLoc = Vector3fImmutable.ZERO; |
|
|
|
/** |
|
* No Id Constructor |
|
*/ |
|
public AbstractWorldObject() { |
|
super(); |
|
} |
|
|
|
/** |
|
* Normal Constructor |
|
*/ |
|
public AbstractWorldObject(int objectUUID) { |
|
super(objectUUID); |
|
} |
|
|
|
|
|
/** |
|
* ResultSet Constructor |
|
*/ |
|
public AbstractWorldObject(ResultSet rs) throws SQLException { |
|
super(rs); |
|
} |
|
|
|
//this should be called to handle any after load functions. |
|
public abstract void runAfterLoad(); |
|
|
|
/* |
|
* Getters |
|
*/ |
|
public float getHealth() { |
|
|
|
|
|
return this.health.get(); |
|
|
|
} |
|
public float getCurrentHitpoints(){ |
|
return this.health.get(); |
|
} |
|
|
|
public float getHealthMax() { |
|
return this.healthMax; |
|
} |
|
|
|
public ConcurrentHashMap<String, Effect> 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 = mob.getOwner(); |
|
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; |
|
} |
|
|
|
|
|
public Vector3fImmutable getLoc() { |
|
return this.loc; |
|
} |
|
|
|
public Vector3f getRot() { |
|
return rot; |
|
} |
|
|
|
public byte getTier() { |
|
return 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; |
|
} |
|
} |
|
|
|
/* |
|
* Setters |
|
*/ |
|
public void setObjectTypeMask(int mask) { |
|
this.objectTypeMask = mask; |
|
} |
|
|
|
//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) + 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 void setY(float y){ |
|
this.loc = this.loc.setY(y); |
|
} |
|
|
|
|
|
public void setRot(Vector3f rotation) { |
|
synchronized (this.rot) { |
|
this.rot = rotation; |
|
} |
|
} |
|
|
|
public void setTier(byte tier) { |
|
synchronized (this.rot) { |
|
this.tier = tier; |
|
} |
|
} |
|
|
|
public static int getType() { |
|
return 0; |
|
} |
|
|
|
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; |
|
} |
|
|
|
/** |
|
* @param health the health to set |
|
*/ |
|
public void setHealth(float health) { |
|
|
|
this.health.set(health); |
|
} |
|
|
|
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<Integer,AbstractWorldObject> 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<Integer,AbstractWorldObject> 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; |
|
} |
|
|
|
public float getAltitude() { |
|
return altitude; |
|
} |
|
|
|
|
|
public ReadWriteLock getUpdateLock() { |
|
return updateLock; |
|
} |
|
|
|
public GridObjectType getGridObjectType() { |
|
return gridObjectType; |
|
} |
|
|
|
public Regions getRegion() { |
|
return region; |
|
} |
|
|
|
|
|
public boolean isMovingUp() { |
|
return movingUp; |
|
} |
|
|
|
public void setMovingUp(boolean movingUp) { |
|
this.movingUp = movingUp; |
|
} |
|
|
|
public void setRegion(Regions region) { |
|
this.region = region; |
|
} |
|
|
|
//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; |
|
} |
|
|
|
|
|
}
|
|
|