2022-04-30 09:41:17 -04:00
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2022
// www.magicbane.com
package engine.gameManager ;
import engine.Enum.DispatchChannel ;
import engine.Enum.GameObjectType ;
import engine.Enum.ModType ;
import engine.Enum.SourceType ;
import engine.InterestManagement.InterestManager ;
import engine.exception.MsgSendException ;
import engine.math.Bounds ;
import engine.math.Vector3f ;
import engine.math.Vector3fImmutable ;
import engine.net.DispatchMessage ;
import engine.net.client.ClientConnection ;
import engine.net.client.msg.MoveToPointMsg ;
import engine.net.client.msg.TeleportToPointMsg ;
import engine.net.client.msg.UpdateStateMsg ;
import engine.objects.* ;
import engine.server.MBServerStatics ;
import org.pmw.tinylog.Logger ;
import java.util.Set ;
import static engine.math.FastMath.sqr ;
public enum MovementManager {
MOVEMENTMANAGER ;
private static final String changeAltitudeTimerJobName = "ChangeHeight" ;
private static final String flightTimerJobName = "Flight" ;
public static void sendOOS ( PlayerCharacter pc ) {
pc . setWalkMode ( true );
MovementManager . sendRWSSMsg ( pc );
}
public static void sendRWSSMsg ( AbstractCharacter ac ) {
if ( ! ac . isAlive ())
return ;
UpdateStateMsg rssm = new UpdateStateMsg ();
rssm . setPlayer ( ac );
if ( ac . getObjectType () == GameObjectType . PlayerCharacter )
DispatchMessage . dispatchMsgToInterestArea ( ac , rssm , DispatchChannel . PRIMARY , MBServerStatics . CHARACTER_LOAD_RANGE , true , false );
else
DispatchMessage . sendToAllInRange ( ac , rssm );
}
/*
* Sets the first combat target for the AbstractCharacter. Used to clear the
* combat
* target upon each move, unless something has set the firstHitCombatTarget
* Also used to determine the size of a monster's hitbox
*/
public static void movement ( MoveToPointMsg msg , AbstractCharacter toMove ) throws MsgSendException {
// check for stun/root
if ( ! toMove . isAlive ())
return ;
if ( toMove . getObjectType (). equals ( GameObjectType . PlayerCharacter )){
if ((( PlayerCharacter ) toMove ). isCasting ())
(( PlayerCharacter ) toMove ). update ();
}
toMove . setIsCasting ( false );
toMove . setItemCasting ( false );
if ( toMove . getBonuses (). getBool ( ModType . Stunned , SourceType . None ) || toMove . getBonuses (). getBool ( ModType . CannotMove , SourceType . None )) {
return ;
}
if ( msg . getEndLat () > MBServerStatics . MAX_WORLD_WIDTH )
msg . setEndLat (( float ) MBServerStatics . MAX_WORLD_WIDTH );
if ( msg . getEndLon () < MBServerStatics . MAX_WORLD_HEIGHT ){
msg . setEndLon (( float ) MBServerStatics . MAX_WORLD_HEIGHT );
}
// if (msg.getEndLat() < 0)
// msg.setEndLat(0);
//
// if (msg.getEndLon() > 0)
// msg.setEndLon(0);
if ( ! toMove . isMoving ())
toMove . resetLastSetLocUpdate ();
else
toMove . update ();
// Update movement for the player
// else if (toMove.getObjectType() == GameObjectType.Mob)
// ((Mob)toMove).updateLocation();
// get start and end locations for the move
Vector3fImmutable startLocation = new Vector3fImmutable ( msg . getStartLat (), msg . getStartAlt (), msg . getStartLon ());
Vector3fImmutable endLocation = new Vector3fImmutable ( msg . getEndLat (), msg . getEndAlt (), msg . getEndLon ());
// if (toMove.getObjectType() == GameObjectType.PlayerCharacter)
// if (msg.getEndAlt() == 0 && msg.getTargetID() == 0){
// MovementManager.sendRWSSMsg(toMove);
// }
//If in Building, let's see if we need to Fix
// if inside a building, convert both locations from the building local reference frame to the world reference frame
if ( msg . getTargetID () > 0 ) {
Building building = BuildingManager . getBuildingFromCache ( msg . getTargetID ());
if ( building != null ) {
Vector3fImmutable convertLocEnd = new Vector3fImmutable ( ZoneManager . convertLocalToWorld ( building , endLocation ));
// if (!Bounds.collide(convertLocEnd, b) || !b.loadObjectsInside()) {
// toMove.setInBuilding(-1);
// toMove.setInFloorID(-1);
// toMove.setInBuildingID(0);
// }
// else {
toMove . setInBuilding ( msg . getInBuilding ());
toMove . setInFloorID ( msg . getUnknown01 ());
toMove . setInBuildingID ( msg . getTargetID ());
msg . setStartCoord ( ZoneManager . convertWorldToLocal ( building , toMove . getLoc ()));
if ( toMove . getObjectType () == GameObjectType . PlayerCharacter ) {
if ( convertLocEnd . distanceSquared2D ( toMove . getLoc ()) > 6000 * 6000 ) {
Logger . info ( "ENDLOC:" + convertLocEnd . x + ',' + convertLocEnd . y + ',' + convertLocEnd . z +
',' + "GETLOC:" + toMove . getLoc (). x + ',' + toMove . getLoc (). y + ',' + toMove . getLoc (). z + " Name " + (( PlayerCharacter ) toMove ). getCombinedName ());
toMove . teleport ( toMove . getLoc ());
return ;
}
}
startLocation = toMove . getLoc ();
endLocation = convertLocEnd ;
} else {
toMove . setInBuilding ( - 1 );
toMove . setInFloorID ( - 1 );
toMove . setInBuildingID ( 0 );
//SYNC PLAYER
toMove . teleport ( toMove . getLoc ());
return ;
}
} else {
toMove . setInBuildingID ( 0 );
toMove . setInFloorID ( - 1 );
toMove . setInBuilding ( - 1 );
msg . setStartCoord ( toMove . getLoc ());
}
2023-04-29 12:33:21 -04:00
2022-04-30 09:41:17 -04:00
//make sure we set the correct player.
msg . setSourceType ( toMove . getObjectType (). ordinal ());
msg . setSourceID ( toMove . getObjectUUID ());
2023-04-29 12:33:21 -04:00
2022-04-30 09:41:17 -04:00
//if player in region, modify location to local location of building. set target to building.
2023-04-29 12:33:21 -04:00
if ( toMove . region != null ) {
Building regionBuilding = Regions . GetBuildingForRegion ( toMove . region );
if ( regionBuilding != null ) {
msg . setStartCoord ( ZoneManager . convertWorldToLocal ( Regions . GetBuildingForRegion ( toMove . region ), toMove . getLoc ()));
2022-04-30 09:41:17 -04:00
msg . setEndCoord ( ZoneManager . convertWorldToLocal ( regionBuilding , endLocation ));
2023-04-29 12:33:21 -04:00
msg . setInBuilding ( toMove . region . level );
msg . setUnknown01 ( toMove . region . room );
2022-04-30 09:41:17 -04:00
msg . setTargetType ( GameObjectType . Building . ordinal ());
msg . setTargetID ( regionBuilding . getObjectUUID ());
}
2023-04-29 12:33:21 -04:00
} else {
2022-04-30 09:41:17 -04:00
toMove . setInBuildingID ( 0 );
toMove . setInFloorID ( - 1 );
toMove . setInBuilding ( - 1 );
msg . setStartCoord ( toMove . getLoc ());
msg . setEndCoord ( endLocation );
msg . setTargetType ( 0 );
msg . setTargetID ( 0 );
}
//checks sync between character and server, if out of sync, teleport player to original position and return.
if ( toMove . getObjectType () == GameObjectType . PlayerCharacter ) {
boolean startLocInSync = checkSync ( toMove , startLocation , toMove . getLoc ());
if ( ! startLocInSync ){
syncLoc ( toMove , toMove . getLoc (), startLocInSync );
return ;
}
}
// set direction, based on the current location which has just been sync'd
// with the client and the calc'd destination
toMove . setFaceDir ( endLocation . subtract2D ( toMove . getLoc ()). normalize ());
boolean collide = false ;
if ( toMove . getObjectType (). equals ( GameObjectType . PlayerCharacter )) {
Vector3fImmutable collidePoint = Bounds . PlayerBuildingCollisionPoint (( PlayerCharacter ) toMove , toMove . getLoc (), endLocation );
if ( collidePoint != null ) {
msg . setEndCoord ( collidePoint );
endLocation = collidePoint ;
collide = true ;
}
}
if ( toMove . getObjectType () == GameObjectType . PlayerCharacter && (( PlayerCharacter ) toMove ). isTeleportMode ()) {
toMove . teleport ( endLocation );
return ;
}
// move to end location, this can interrupt the current move
toMove . setEndLoc ( endLocation );
// ChatManager.chatSystemInfo((PlayerCharacter)toMove, "Moving to " + Vector3fImmutable.toString(endLocation));
// make sure server knows player is not sitting
toMove . setSit ( false );
// cancel any effects that break upon movement
toMove . cancelOnMove ();
//cancel any attacks for manual move.
if (( toMove . getObjectType () == GameObjectType . PlayerCharacter ) && msg . getUnknown02 () == 0 )
toMove . setCombatTarget ( null );
// If it's not a player moving just send the message
if (( toMove . getObjectType () == GameObjectType . PlayerCharacter ) == false ) {
DispatchMessage . sendToAllInRange ( toMove , msg );
return ;
}
// If it's a player who is moving then we need to handle characters
// who should see the message via group follow
PlayerCharacter player = ( PlayerCharacter ) toMove ;
player . setTimeStamp ( "lastMoveGate" , System . currentTimeMillis ());
if ( collide )
DispatchMessage . dispatchMsgToInterestArea ( player , msg , DispatchChannel . PRIMARY , MBServerStatics . CHARACTER_LOAD_RANGE , true , false );
else
DispatchMessage . dispatchMsgToInterestArea ( player , msg , DispatchChannel . PRIMARY , MBServerStatics . CHARACTER_LOAD_RANGE , false , false );
// Handle formation movement if needed
if ( player . getFollow () == false )
return ;
City cityObject = null ;
Zone serverZone = null ;
serverZone = ZoneManager . findSmallestZone ( player . getLoc ());
cityObject = ( City ) DbManager . getFromCache ( GameObjectType . City , serverZone . getPlayerCityUUID ());
// Do not send group messages if player is on grid
if ( cityObject != null )
return ;
// If player is not in a group we can exit here
Group group = GroupManager . getGroup ( player );
if ( group == null )
return ;
// Echo group movement messages
if ( group . getGroupLead (). getObjectUUID () == player . getObjectUUID ())
moveGroup ( player , player . getClientConnection (), msg );
}
/**
* compare client and server location to verify that the two are in sync
*
* @param ac the player character
* @param clientLoc location as reported by the client
* @param serverLoc location known to the server
* @return true if the two are in sync
*/
private static boolean checkSync ( AbstractCharacter ac , Vector3fImmutable clientLoc , Vector3fImmutable serverLoc ) {
float desyncDist = clientLoc . distanceSquared2D ( serverLoc );
// desync logging
if ( MBServerStatics . MOVEMENT_SYNC_DEBUG )
if ( desyncDist > MBServerStatics . MOVEMENT_DESYNC_TOLERANCE * MBServerStatics . MOVEMENT_DESYNC_TOLERANCE )
// our current location server side is a calc of last known loc + direction + speed and known time of last update
Logger . debug ( "Movement out of sync for " + ac . getFirstName ()
+ ", Server Loc: " + serverLoc . getX () + ' ' + serverLoc . getZ ()
+ " , Client loc: " + clientLoc . getX () + ' ' + clientLoc . getZ ()
+ " desync distance " + desyncDist
+ " moving=" + ac . isMoving ());
else
Logger . debug ( "Movement sync is good - desyncDist = " + desyncDist );
if ( ac . getDebug ( 1 ) && ac . getObjectType (). equals ( GameObjectType . PlayerCharacter ))
if ( desyncDist > MBServerStatics . MOVEMENT_DESYNC_TOLERANCE * MBServerStatics . MOVEMENT_DESYNC_TOLERANCE ) {
PlayerCharacter pc = ( PlayerCharacter ) ac ;
ChatManager . chatSystemInfo ( pc ,
"Movement out of sync for " + ac . getFirstName ()
+ ", Server Loc: " + serverLoc . getX () + ' ' + serverLoc . getZ ()
+ " , Client loc: " + clientLoc . getX () + ' ' + clientLoc . getZ ()
+ " desync distance " + desyncDist
+ " moving=" + ac . isMoving ());
}
// return indicator that the two are in sync or not
return ( desyncDist < 100f * 100f );
}
public static void finishChangeAltitude ( AbstractCharacter ac , float targetAlt ) {
if ( ac . getObjectType (). equals ( GameObjectType . PlayerCharacter ) == false )
return ;
//reset the getLoc timer before we clear other timers
// otherwise the next call to getLoc will not be correct
ac . resetLastSetLocUpdate ();
// call getLoc once as it processes loc to the ms
Vector3fImmutable curLoc = ac . getLoc ();
if ( MBServerStatics . MOVEMENT_SYNC_DEBUG )
Logger . info ( "Finished Alt change, setting the end location to "
+ ac . getEndLoc (). getX () + ' ' + ac . getEndLoc (). getZ ()
+ " moving=" + ac . isMoving ()
+ " and current location is " + curLoc . getX () + ' ' + curLoc . getZ ());
if ( ac . getDebug ( 1 ) && ac . getObjectType (). equals ( GameObjectType . PlayerCharacter ))
ChatManager . chatSystemInfo (( PlayerCharacter ) ac , "Finished Alt change, setting the end location to " + ac . getEndLoc (). getX () + ' ' + ac . getEndLoc (). getZ () + " moving=" + ac . isMoving () + " and current location is " + curLoc . getX () + ' ' + curLoc . getZ ());
//Send run/walk/sit/stand to tell the client we are flying / landing etc
ac . update ();
ac . stopMovement ( ac . getLoc ());
if ( ac . isAlive ())
MovementManager . sendRWSSMsg ( ac );
//Check collision again
}
// Handle formation movement in group
public static void moveGroup ( PlayerCharacter pc , ClientConnection origin , MoveToPointMsg msg ) throws MsgSendException {
// get forward vector
Vector3f faceDir = new Vector3f ( pc . getFaceDir (). x , 0 , pc . getFaceDir (). z ). normalize ();
// get perpendicular vector
Vector3f crossDir = new Vector3f ( faceDir . z , 0 , - faceDir . x );
//get source loc with altitude
Vector3f sLoc = new Vector3f ( pc . getLoc (). x , pc . getAltitude (), pc . getLoc (). z );
Group group = GroupManager . getGroup ( pc );
Set < PlayerCharacter > members = group . getMembers ();
int pos = 0 ;
for ( PlayerCharacter member : members ) {
if ( member == null )
continue ;
if ( member . getObjectUUID () == pc . getObjectUUID ())
continue ;
MoveToPointMsg groupMsg = new MoveToPointMsg ( msg );
// Verify group member should be moved
pos ++ ;
if ( member . getFollow () != true )
continue ;
//get member loc with altitude, then range against source loc
Vector3f mLoc = new Vector3f ( member . getLoc (). x , member . getAltitude (), member . getLoc (). z );
if ( sLoc . distanceSquared2D ( mLoc ) > sqr ( MBServerStatics . FORMATION_RANGE ))
continue ;
//don't move if player has taken damage from another player in last 60 seconds
long lastAttacked = System . currentTimeMillis () - pc . getLastPlayerAttackTime ();
if ( lastAttacked < 60000 )
continue ;
if ( ! member . isAlive ())
continue ;
//don't move if player is stunned or rooted
PlayerBonuses bonus = member . getBonuses ();
if ( bonus . getBool ( ModType . Stunned , SourceType . None ) || bonus . getBool ( ModType . CannotMove , SourceType . None ))
continue ;
member . update ();
// All checks passed, let's move the player
// First get the offset position
Vector3f offset = Formation . getOffset ( group . getFormation (), pos );
Vector3fImmutable destination = pc . getEndLoc ();
// offset forwards or backwards
destination = destination . add ( faceDir . mult ( offset . z ));
// offset left or right
destination = destination . add ( crossDir . mult ( offset . x ));
// ArrayList<AbstractWorldObject> awoList = WorldGrid.INSTANCE.getObjectsInRangePartial(member, member.getLoc().distance2D(destination) +1000, MBServerStatics.MASK_BUILDING);
//
// boolean skip = false;
//
// for (AbstractWorldObject awo: awoList){
// Building building = (Building)awo;
//
// if (building.getBounds() != null){
// if (Bounds.collide(building, member.getLoc(), destination)){
// skip = true;
// break;
// }
//
// }
//
// }
//
// if (skip)
// continue;
// if (member.isMoving())
// member.stopMovement();
// Update player speed to match group lead speed and make standing
if ( member . isSit () || ( member . isWalk () != pc . isWalk ())) {
member . setSit ( false );
member . setWalkMode ( pc . isWalk ());
MovementManager . sendRWSSMsg ( member );
}
//cancel any effects that break upon movement
member . cancelOnMove ();
// send movement for other players to see
groupMsg . setSourceID ( member . getObjectUUID ());
groupMsg . setStartCoord ( member . getLoc ());
groupMsg . setEndCoord ( destination );
groupMsg . clearTarget ();
DispatchMessage . sendToAllInRange ( member , groupMsg );
// update group member
member . setFaceDir ( destination . subtract2D ( member . getLoc ()). normalize ());
member . setEndLoc ( destination );
}
}
public static void translocate ( AbstractCharacter teleporter , Vector3fImmutable targetLoc , Regions region ) {
if ( targetLoc == null )
return ;
Vector3fImmutable oldLoc = new Vector3fImmutable ( teleporter . getLoc ());
2022-06-04 13:30:35 -04:00
2022-04-30 09:41:17 -04:00
teleporter . stopMovement ( targetLoc );
teleporter . setRegion ( region );
//mobs ignore region sets for now.
if ( teleporter . getObjectType (). equals ( GameObjectType . Mob )){
teleporter . setInBuildingID ( 0 );
teleporter . setInBuilding ( - 1 );
teleporter . setInFloorID ( - 1 );
TeleportToPointMsg msg = new TeleportToPointMsg ( teleporter , targetLoc . getX (), targetLoc . getY (), targetLoc . getZ (), 0 , - 1 , - 1 );
DispatchMessage . dispatchMsgToInterestArea ( oldLoc , teleporter , msg , DispatchChannel . PRIMARY , MBServerStatics . CHARACTER_LOAD_RANGE , false , false );
return ;
}
TeleportToPointMsg msg = new TeleportToPointMsg ( teleporter , targetLoc . getX (), targetLoc . getY (), targetLoc . getZ (), 0 , - 1 , - 1 );
//we shouldnt need to send teleport message to new area, as loadjob should pick it up.
// DispatchMessage.dispatchMsgToInterestArea(teleporter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
DispatchMessage . dispatchMsgToInterestArea ( oldLoc , teleporter , msg , DispatchChannel . PRIMARY , MBServerStatics . CHARACTER_LOAD_RANGE , true , false );
if ( teleporter . getObjectType (). equals ( GameObjectType . PlayerCharacter ))
InterestManager . INTERESTMANAGER . HandleLoadForTeleport (( PlayerCharacter ) teleporter );
}
private static void syncLoc ( AbstractCharacter ac , Vector3fImmutable clientLoc , boolean useClientLoc ) {
ac . teleport ( ac . getLoc ());
}
}