/// <summary> /// Gets the cell ID for a position within a landblock /// </summary> public static uint GetCell(this Position p) { var landblock = LScape.get_landblock(p.LandblockId.Raw); // dungeons // TODO: investigate dungeons that are below actual traversable overworld terrain // ex., 010AFFFF //if (landblock.IsDungeon) if (p.Indoors) { return(GetIndoorCell(p)); } // outside - could be on landscape, in building, or underground cave var cellID = GetOutdoorCell(p); var landcell = LScape.get_landcell(cellID) as LandCell; if (landcell == null) { return(cellID); } if (landcell.has_building()) { var envCells = landcell.Building.get_building_cells(); foreach (var envCell in envCells) { if (envCell.point_in_cell(p.Pos)) { return(envCell.ID); } } } // handle underground areas ie. caves // get the terrain Z-height for this X/Y Physics.Polygon walkable = null; var terrainPoly = landcell.find_terrain_poly(p.Pos, ref walkable); if (walkable != null) { Vector3 terrainPos = p.Pos; walkable.Plane.set_height(ref terrainPos); // are we below ground? if so, search all of the indoor cells for this landblock if (terrainPos.Z > p.Pos.Z) { var envCells = landblock.get_envcells(); foreach (var envCell in envCells) { if (envCell.point_in_cell(p.Pos)) { return(envCell.ID); } } } } return(cellID); }
/// <summary> /// Called when a monster changes cells /// </summary> public void UpdateCell() { var curCell = LScape.get_landcell(Location.LandblockId.Raw); //Console.WriteLine("Moving " + Name + " to " + curCell.ID.ToString("X8")); PhysicsObj.change_cell_server(curCell); //PhysicsObj.remove_shadows_from_cells(); PhysicsObj.add_shadows_to_cell(curCell); }
/// <summary> /// Spawns the semi-randomized monsters scattered around the outdoors<para /> /// This will be called from a separate task from our constructor. Use thread safety when interacting with this landblock. /// </summary> private void SpawnEncounters() { // get the encounter spawns for this landblock var encounters = DatabaseManager.World.GetCachedEncountersByLandblock(Id.Landblock); foreach (var encounter in encounters) { var wo = WorldObjectFactory.CreateNewWorldObject(encounter.WeenieClassId); if (wo == null) { continue; } var xPos = Math.Clamp(encounter.CellX * 24.0f, 0.5f, 191.5f); var yPos = Math.Clamp(encounter.CellY * 24.0f, 0.5f, 191.5f); var pos = new Physics.Common.Position(); pos.ObjCellID = (uint)(Id.Landblock << 16) | 1; pos.Frame = new Physics.Animation.AFrame(new Vector3(xPos, yPos, 0), Quaternion.Identity); pos.adjust_to_outside(); pos.Frame.Origin.Z = _landblock.GetZ(pos.Frame.Origin); wo.Location = new Position(pos.ObjCellID, pos.Frame.Origin, pos.Frame.Orientation); var sortCell = LScape.get_landcell(pos.ObjCellID) as SortCell; if (sortCell != null && sortCell.has_building()) { continue; } if (PropertyManager.GetBool("override_encounter_spawn_rates").Item) { wo.RegenerationInterval = PropertyManager.GetDouble("encounter_regen_interval").Item; wo.ReinitializeHeartbeats(); foreach (var profile in wo.Biota.BiotaPropertiesGenerator) { profile.Delay = (float)PropertyManager.GetDouble("encounter_delay").Item; } } actionQueue.EnqueueAction(new ActionEventDelegate(() => { AddWorldObject(wo); })); } }
/// <summary> /// Updates the position using the simple physics method /// </summary> public void UpdatePosition_PhysicsInner(Vector3 requestPos, Vector3 dir) { Location.Rotate(dir); PhysicsObj.Position.Frame.Orientation = Location.Rotation; var cell = LScape.get_landcell(Location.Cell); PhysicsObj.set_request_pos(requestPos, Location.Rotation, null, Location.LandblockId.Raw); // simulate running forward for this amount of time PhysicsObj.update_object_server(false); UpdatePosition_SyncLocation(); }
/// <summary> /// Returns TRUE if outdoor position is located on walkable slope /// </summary> public static bool IsWalkable(this Position p) { if (p.Indoors) { return(true); } var landcell = (LandCell)LScape.get_landcell(p.Cell); Physics.Polygon walkable = null; var terrainPoly = landcell.find_terrain_poly(p.Pos, ref walkable); if (walkable == null) { return(false); } return(Physics.PhysicsObj.is_valid_walkable(walkable.Plane.Normal)); }
public void UpdatePosition_PhysicsInner(Vector3 newPos, Vector3 dir) { Location.Rotate(dir); PhysicsObj.Position.Frame.Orientation = Location.Rotation; var cell = LScape.get_landcell(Location.Cell); PhysicsObj.set_request_pos(newPos, Location.Rotation, cell); // simulate running forward for this amount of time PhysicsObj.update_object_server(false); // was the position successfully moved to? // use the physics position as the source-of-truth? Location.Pos = PhysicsObj.Position.Frame.Origin; if (PhysicsObj.CurCell != null && PhysicsObj.CurCell.ID != Location.Cell) { Location.LandblockId = new LandblockId(PhysicsObj.CurCell.ID); } }
public static void AdjustMapCoords(this Position pos) { // adjust Z to terrain height pos.PositionZ = pos.GetTerrainZ(); // adjust to building height, if applicable var sortCell = LScape.get_landcell(pos.Cell) as SortCell; if (sortCell != null && sortCell.has_building()) { var building = sortCell.Building; var minZ = building.GetMinZ(); if (minZ > 0 && minZ < float.MaxValue) { pos.PositionZ += minZ; } pos.LandblockId = new LandblockId(pos.GetCell()); } }
public void BuildEnvCells() { EnvCells = new List <R_EnvCell>(); if (Landblock.Info == null || Landblock.Info.NumCells == 0) { return; } var numCells = Landblock.Info.NumCells; var landblockID = Landblock.ID & 0xFFFF0000; for (uint i = 0; i < numCells; i++) { var envCellID = landblockID | (0x100 + i); var envCell = (EnvCell)LScape.get_landcell(envCellID); EnvCells.Add(new R_EnvCell(envCell)); } }
public static float GetTerrainZ(this Position p) { var cellID = GetOutdoorCell(p); var landcell = (LandCell)LScape.get_landcell(cellID); if (landcell == null) { return(p.Pos.Z); } Physics.Polygon walkable = null; if (!landcell.find_terrain_poly(p.Pos, ref walkable)) { return(p.Pos.Z); } Vector3 terrainPos = p.Pos; walkable.Plane.set_height(ref terrainPos); return(terrainPos.Z); }
/// <summary> /// Spawns the semi-randomized monsters scattered around the outdoors<para /> /// This will be called from a separate task from our constructor. Use thread safety when interacting with this landblock. /// </summary> private void SpawnEncounters() { // get the encounter spawns for this landblock var encounters = DatabaseManager.World.GetCachedEncountersByLandblock(Id.Landblock); foreach (var encounter in encounters) { var wo = WorldObjectFactory.CreateNewWorldObject(encounter.WeenieClassId); if (wo == null) { continue; } var xPos = Math.Clamp(encounter.CellX * 24.0f, 0.5f, 191.5f); var yPos = Math.Clamp(encounter.CellY * 24.0f, 0.5f, 191.5f); var pos = new Physics.Common.Position(); pos.ObjCellID = (uint)(Id.Landblock << 16) | 1; pos.Frame = new Physics.Animation.AFrame(new Vector3(xPos, yPos, 0), Quaternion.Identity); pos.adjust_to_outside(); pos.Frame.Origin.Z = _landblock.GetZ(pos.Frame.Origin); wo.Location = new Position(pos.ObjCellID, pos.Frame.Origin, pos.Frame.Orientation); var sortCell = LScape.get_landcell(pos.ObjCellID) as SortCell; if (sortCell != null && sortCell.has_building()) { continue; } actionQueue.EnqueueAction(new ActionEventDelegate(() => { AddWorldObject(wo); })); } }
private void AddWorldObjectInternal(WorldObject wo) { Log($"adding {wo.Guid.Full:X}"); if (!worldObjects.ContainsKey(wo.Guid)) { worldObjects[wo.Guid] = wo; } wo.SetParent(this); wo.PhysicsObj.Position.Frame.Origin = wo.Location.Pos; wo.PhysicsObj.Position.Frame.Orientation = wo.Location.Rotation; //wo.AdjustDungeonCells(wo.Location); var cell = LScape.get_landcell(wo.Location.Cell); if (cell != null) { wo.PhysicsObj.enter_cell(cell); wo.PhysicsObj.add_shadows_to_cell(cell); } // var args = BroadcastEventArgs.CreateAction(BroadcastAction.AddOrUpdate, wo); // Broadcast(args, true, Quadrant.All); // Alert all nearby players of the object EnqueueActionBroadcast(wo.Location, MaxObjectRange, (Player p) => p.TrackObject(wo)); // if this is a player, tell them about everything else we have in range of them. if (wo is Player) { List <WorldObject> wolist = null; wolist = GetWorldObjectsInRange(wo, MaxObjectRange); AddPlayerTracking(wolist, ((Player)wo)); } }
/// <summary> /// Used by physics engine to actually update a player position /// Automatically notifies clients of updated position /// </summary> /// <param name="newPosition">The new position being requested, before verification through physics engine</param> /// <returns>TRUE if object moves to a different landblock</returns> public bool UpdatePlayerPosition(ACE.Entity.Position newPosition, bool forceUpdate = false) { //Console.WriteLine($"{Name}.UpdatePlayerPhysics({newPosition}, {forceUpdate}, {Teleporting})"); bool verifyContact = false; // possible bug: while teleporting, client can still send AutoPos packets from old landblock if (Teleporting && !forceUpdate) { return(false); } // pre-validate movement if (!ValidateMovement(newPosition)) { log.Error($"{Name}.UpdatePlayerPosition() - movement pre-validation failed from {Location} to {newPosition}"); return(false); } try { if (!forceUpdate) // This is needed beacuse this function might be called recursively { stopwatch.Restart(); } var success = true; if (PhysicsObj != null) { var distSq = Location.SquaredDistanceTo(newPosition); if (distSq > PhysicsGlobals.EpsilonSq) { /*var p = new Physics.Common.Position(newPosition); * var dist = PhysicsObj.Position.Distance(p); * Console.WriteLine($"Dist: {dist}");*/ if (newPosition.Landblock == 0x18A && Location.Landblock != 0x18A) { log.Info($"{Name} is getting swanky"); } if (!Teleporting) { var blockDist = PhysicsObj.GetBlockDist(Location.ObjCellID, newPosition.ObjCellID); // verify movement if (distSq > MaxSpeedSq && blockDist > 1) { //Session.Network.EnqueueSend(new GameMessageSystemChat("Movement error", ChatMessageType.Broadcast)); log.Warn($"MOVEMENT SPEED: {Name} trying to move from {Location} to {newPosition}, speed: {Math.Sqrt(distSq)}"); return(false); } // verify z-pos if (blockDist == 0 && LastGroundPos != null && newPosition.Pos.Z - LastGroundPos.Pos.Z > 10 && DateTime.UtcNow - LastJumpTime > TimeSpan.FromSeconds(1) && GetCreatureSkill(Skill.Jump).Current < 1000) { verifyContact = true; } } var curCell = LScape.get_landcell(newPosition.LongObjCellID); if (curCell != null) { //if (PhysicsObj.CurCell == null || curCell.ID != PhysicsObj.CurCell.ID) //PhysicsObj.change_cell_server(curCell); PhysicsObj.set_request_pos(newPosition.Pos, newPosition.Rotation, newPosition.Instance, curCell, Location.ObjCellID); if (FastTick) { success = PhysicsObj.update_object_server_new(true, newPosition.Instance); } else { success = PhysicsObj.update_object_server(true, newPosition.Instance); } if (PhysicsObj.CurCell == null && curCell.ID >> 16 != 0x18A) { PhysicsObj.CurCell = curCell; } if (verifyContact && IsJumping) { log.Warn($"z-pos hacking detected for {Name}, lastGroundPos: {LastGroundPos.ToLOCString()} - requestPos: {newPosition.ToLOCString()}"); Location = new ACE.Entity.Position(LastGroundPos); Sequences.GetNextSequence(SequenceType.ObjectForcePosition); SendUpdatePosition(); return(false); } CheckMonsters(); } } else { PhysicsObj.Position.Frame.Orientation = newPosition.Rotation; } } // double update path: landblock physics update -> updateplayerphysics() -> update_object_server() -> Teleport() -> updateplayerphysics() -> return to end of original branch if (Teleporting && !forceUpdate) { return(true); } if (!success) { return(false); } var landblockUpdate = Location.Landblock != newPosition.Landblock; Location = newPosition; if (RecordCast.Enabled) { RecordCast.Log($"CurPos: {Location.ToLOCString()}"); } if (RequestedLocationBroadcast || DateTime.UtcNow - LastUpdatePosition >= MoveToState_UpdatePosition_Threshold) { SendUpdatePosition(); } else { Session.Network.EnqueueSend(new GameMessageUpdatePosition(this)); } if (!InUpdate) { // todo: improve this logic /*if (CurrentLandblock.Instance > 0) * { * ClearInstance(CurrentLandblock.LongId); * }*/ LandblockManager.RelocateObjectForPhysics(this, true); } return(landblockUpdate); } finally { if (!forceUpdate) // This is needed beacuse this function might be called recursively { var elapsedSeconds = stopwatch.Elapsed.TotalSeconds; ServerPerformanceMonitor.AddToCumulativeEvent(ServerPerformanceMonitor.CumulativeEventHistoryType.Player_Tick_UpdateObjectPhysics, elapsedSeconds); if (elapsedSeconds >= 0.100) // Yea, that ain't good.... { log.Warn($"[PERFORMANCE][PHYSICS] {Guid}:{Name} took {(elapsedSeconds * 1000):N1} ms to process UpdatePlayerPosition() at loc: {Location}"); } else if (elapsedSeconds >= 0.010) { log.Debug($"[PERFORMANCE][PHYSICS] {Guid}:{Name} took {(elapsedSeconds * 1000):N1} ms to process UpdatePlayerPosition() at loc: {Location}"); } } } }
/// <summary> /// Used by physics engine to actually update a player position /// Automatically notifies clients of updated position /// </summary> /// <param name="newPosition">The new position being requested, before verification through physics engine</param> /// <returns>TRUE if object moves to a different landblock</returns> public bool UpdatePlayerPhysics(ACE.Entity.Position newPosition, bool forceUpdate = false) { //Console.WriteLine($"{Name}.UpdatePlayerPhysics({newPosition}, {forceUpdate}, {Teleporting})"); // possible bug: while teleporting, client can still send AutoPos packets from old landblock if (Teleporting && !forceUpdate) { return(false); } // pre-validate movement if (!ValidateMovement(newPosition)) { log.Error($"{Name}.UpdatePlayerPhysics() - movement pre-validation failed from {Location} to {newPosition}"); return(false); } try { if (!forceUpdate) // This is needed beacuse this function might be called recursively { stopwatch.Restart(); } var success = true; if (PhysicsObj != null) { var distSq = Location.SquaredDistanceTo(newPosition); if (distSq > PhysicsGlobals.EpsilonSq) { var curCell = LScape.get_landcell(newPosition.Cell); if (curCell != null) { //if (PhysicsObj.CurCell == null || curCell.ID != PhysicsObj.CurCell.ID) //PhysicsObj.change_cell_server(curCell); PhysicsObj.set_request_pos(newPosition.Pos, newPosition.Rotation, curCell, Location.LandblockId.Raw); success = PhysicsObj.update_object_server(); if (PhysicsObj.CurCell == null && curCell.ID >> 16 != 0x18A) { PhysicsObj.CurCell = curCell; } CheckMonsters(); } } } // double update path: landblock physics update -> updateplayerphysics() -> update_object_server() -> Teleport() -> updateplayerphysics() -> return to end of original branch if (Teleporting && !forceUpdate) { return(true); } if (!success) { return(false); } var landblockUpdate = Location.Cell >> 16 != newPosition.Cell >> 16; Location = newPosition; SendUpdatePosition(); if (!InUpdate) { LandblockManager.RelocateObjectForPhysics(this, true); } return(landblockUpdate); } finally { if (!forceUpdate) // This is needed beacuse this function might be called recursively { var elapsedSeconds = stopwatch.Elapsed.TotalSeconds; ServerPerformanceMonitor.AddToCumulativeEvent(ServerPerformanceMonitor.CumulativeEventHistoryType.WorldObject_Tick_UpdatePlayerPhysics, elapsedSeconds); if (elapsedSeconds >= 1) // Yea, that ain't good.... { log.Warn($"[PERFORMANCE][PHYSICS] {Guid}:{Name} took {(elapsedSeconds * 1000):N1} ms to process UpdatePlayerPhysics() at loc: {Location}"); } else if (elapsedSeconds >= 0.010) { log.Debug($"[PERFORMANCE][PHYSICS] {Guid}:{Name} took {(elapsedSeconds * 1000):N1} ms to process UpdatePlayerPhysics() at loc: {Location}"); } } } }
/// <summary> /// Used by physics engine to actually update a player position /// Automatically notifies clients of updated position /// </summary> /// <param name="newPosition">The new position being requested, before verification through physics engine</param> /// <returns>TRUE if object moves to a different landblock</returns> public bool UpdatePlayerPhysics(ACE.Entity.Position newPosition, bool forceUpdate = false) { //Console.WriteLine($"UpdatePlayerPhysics: {newPosition.Cell:X8}, {newPosition.Pos}"); var player = this as Player; // only handles player movement if (player == null) { return(false); } // possible bug: while teleporting, client can still send AutoPos packets from old landblock if (Teleporting && !forceUpdate) { return(false); } if (PhysicsObj != null) { var dist = (newPosition.Pos - PhysicsObj.Position.Frame.Origin).Length(); if (dist > PhysicsGlobals.EPSILON) { var curCell = LScape.get_landcell(newPosition.Cell); if (curCell != null) { //if (PhysicsObj.CurCell == null || curCell.ID != PhysicsObj.CurCell.ID) //PhysicsObj.change_cell_server(curCell); PhysicsObj.set_request_pos(newPosition.Pos, newPosition.Rotation, curCell, Location.LandblockId.Raw); PhysicsObj.update_object_server(); if (PhysicsObj.CurCell == null) { PhysicsObj.CurCell = curCell; } player.CheckMonsters(); if (curCell.ID != prevCell) { //prevCell = curCell.ID; //Console.WriteLine("Player cell: " + curCell.ID.ToString("X8")); //var envCell = curCell as Physics.Common.EnvCell; //var seenOutside = envCell != null ? envCell.SeenOutside : true; //Console.WriteLine($"CurCell: {curCell.ID:X8}, SeenOutside: {seenOutside}"); } } } } // double update path: landblock physics update -> updateplayerphysics() -> update_object_server() -> Teleport() -> updateplayerphysics() -> return to end of original branch if (Teleporting && !forceUpdate) { return(true); } var landblockUpdate = Location.Cell >> 16 != newPosition.Cell >> 16; Location = newPosition; SendUpdatePosition(); if (!InUpdate) { LandblockManager.RelocateObjectForPhysics(this, true); } return(landblockUpdate); }
public static void LoadEncounters() { Initting = true; MainWindow.Instance.SuppressStatusText = true; SetDatabaseConfig(); Encounters = new List <WorldObject>(); var timer = Stopwatch.StartNew(); // get the list of loaded landblocks foreach (var lbid in LScape.Landblocks.Keys) { var encounters = DatabaseManager.World.GetCachedEncountersByLandblock((ushort)(lbid >> 16)); Console.WriteLine($"Found {encounters.Count:N0} encounters for {lbid:X8}"); var landblock = LScape.get_landblock(lbid); foreach (var encounter in encounters) { var wo = WorldObjectFactory.CreateNewWorldObject(encounter.WeenieClassId); if (wo == null) { continue; } wo.InitPhysicsObj(); var xPos = Math.Clamp(encounter.CellX * 24.0f, 0.5f, 191.5f); var yPos = Math.Clamp(encounter.CellY * 24.0f, 0.5f, 191.5f); var pos = new Position(); pos.ObjCellID = lbid & 0xFFFF0000 | 1; pos.Frame = new AFrame(new Vector3(xPos, yPos, 0), Quaternion.Identity); pos.adjust_to_outside(); pos.Frame.Origin.Z = landblock.GetZ(pos.Frame.Origin); wo.Location = new ACE.Entity.Position(pos.ObjCellID, pos.Frame.Origin, pos.Frame.Orientation); var sortCell = LScape.get_landcell(pos.ObjCellID) as SortCell; if (sortCell != null && sortCell.has_building()) { continue; } var success = wo.AddPhysicsObj(pos); if (!success) { Console.WriteLine($"LoadEncounters({lbid:X8}).AddPhysicsObj({wo.Name}, {pos}) - failed to spawn"); continue; } Console.WriteLine($"Spawned {encounter.WeenieClassId} - {wo.Name} @ {pos}"); AddEncounter(wo); } } TickGenerators(GeneratorTickMode.Encounters); timer.Stop(); Console.WriteLine($"Completed in {timer.Elapsed.TotalSeconds}s"); }