/// <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); }
public void HandlePreTeleportVisibility(ACE.Entity.Position newPosition) { // repro steps without this function: // - /teleloc 0x8A0201C2 [59.822445 -59.574703 0.005000] 0.999998 0.000000 0.000000 -0.002014 for 2 players // - click minimap to teleport player 1 elsewhere in world // - /teleto <player 1 name> for player 2 // - use facility hub portal gem for player 2 (49563) // - player 2 runs down the stairs, into the room with the torch // - player 1 waits 25s+ -- /knownplayers from player 1 to confirm once player 2 has exited the destruction queue // - player 1 uses facility hub portal gem, runs to player 2 // - expected: consistent visibility for both players on each others screens // - actual: player 2's dot will be on radar for player 1, but they will be invisible in 3d game world, floating weapon if they are wielding // after analyzing this bug from many different perspectives, i believe this is some kind of odd client bug // even resending the CO does nothing, as player 1's client does indeed know about player 2 // a DO and then a CO is the only thing that fixes this issue (/objsend can help with this) // this part probably deviates from retail a bit, but is the equivalent automated fix //var fixLevel = PropertyManager.GetLong("teleport_visibility_fix").Item; var fixLevel = 3; // instanced mode always uses level 3 // disabled by default if (fixLevel < 1) { return; } if (Location.LongObjCellID == newPosition.LongObjCellID) { return; } var knownObjs = GetKnownObjects(); if (fixLevel == 1) { // filter to players only knownObjs = knownObjs.Where(i => i is Player).ToList(); } else if (fixLevel == 2) { // filter to creatures only knownObjs = knownObjs.Where(i => i is Creature).ToList(); } foreach (var knownObj in knownObjs) { knownObj.PhysicsObj.ObjMaint.RemoveObject(PhysicsObj); if (knownObj is Player knownPlayer) { knownPlayer.RemoveTrackedObject(this, false); } ObjMaint.RemoveObject(knownObj.PhysicsObj); RemoveTrackedObject(knownObj, false); } }
/// <summary> /// NOTE: Cannot be sent while objects are moving (the physics/motion portion of WorldManager)! depends on object positions not changing, and objects not moving between landblocks /// </summary> private void SendBroadcasts() { while (!broadcastQueue.IsEmpty) { bool success = broadcastQueue.TryDequeue(out var tuple); if (!success) { log.Error("Unexpected TryDequeue Failure!"); break; } Position pos = tuple.Item1; float distance = tuple.Item2; GameMessage msg = tuple.Item3; // NOTE: Doesn't need locking -- players cannot change while in "Act" SendBroadcasts is the last thing done in Act // foreach player within range, do send List <Landblock> landblocksInRange = GetLandblocksInRange(pos, distance); foreach (Landblock lb in landblocksInRange) { List <Player> allPlayers = lb.worldObjects.Values.OfType <Player>().ToList(); foreach (Player p in allPlayers) { if (p.Location.SquaredDistanceTo(pos) < distance * distance) { p.Session.Network.EnqueueSend(msg); } } } } // Sets broadcastQueued to 0, so we're ready to re-queue broadcasts later broadcastQueued = 0; }
public static Position FromGlobal(this Position p, Vector3 pos) { var landblock = LScape.get_landblock(p.LandblockId.Raw); // TODO: investigate dungeons that are below actual traversable overworld terrain // ex., 010AFFFF //if (landblock.IsDungeon) if (p.Indoors) { var iPos = new Position(); iPos.LandblockId = p.LandblockId; iPos.Pos = new Vector3(pos.X, pos.Y, pos.Z); iPos.Rotation = p.Rotation; iPos.LandblockId = new LandblockId(GetCell(iPos)); return(iPos); } var blockX = (uint)pos.X / Position.BlockLength; var blockY = (uint)pos.Y / Position.BlockLength; var localX = pos.X % Position.BlockLength; var localY = pos.Y % Position.BlockLength; var landblockID = blockX << 24 | blockY << 16 | 0xFFFF; var position = new Position(); position.LandblockId = new LandblockId((byte)blockX, (byte)blockY); position.PositionX = localX; position.PositionY = localY; position.PositionZ = pos.Z; position.Rotation = p.Rotation; position.LandblockId = new LandblockId(GetCell(position)); return(position); }
/// <summary> /// Returns the greatest single-dimension square distance between 2 positions /// </summary> public static uint CellDist(this Position p1, Position p2) { if (!p1.Indoors && !p2.Indoors) { return(Math.Max(p1.GlobalCellX, p1.GlobalCellY)); } // handle dungeons /*var block1 = LScape.get_landblock(p1.LandblockId.Raw); * var block2 = LScape.get_landblock(p2.LandblockId.Raw); * if (block1.IsDungeon || block2.IsDungeon) * { * // 2 separate dungeons = infinite distance * if (block1.ID != block2.ID) * return uint.MaxValue; * * return GetDungeonCellDist(p1, p2); * }*/ var _p1 = new Position(p1); var _p2 = new Position(p2); if (_p1.Indoors) { _p1.LandblockId = new LandblockId(_p1.GetOutdoorCell()); } if (_p2.Indoors) { _p2.LandblockId = new LandblockId(_p2.GetIndoorCell()); } return(Math.Max(_p1.GlobalCellX, _p2.GlobalCellY)); }
public static Position FromGlobal(this Position p, Vector3 pos) { // TODO: Is this necessary? It seemed to be loading rogue physics landblocks. Commented out 2019-04 Mag-nus //var landblock = LScape.get_landblock(p.LandblockId.Raw); // TODO: investigate dungeons that are below actual traversable overworld terrain // ex., 010AFFFF //if (landblock.IsDungeon) if (p.Indoors) { var iPos = new Position(p.ObjCellID, pos, p.Rotation, false, p.Instance); iPos.ObjCellID = GetCell(iPos); return(iPos); } var blockX = (uint)pos.X / Position.BlockLength; var blockY = (uint)pos.Y / Position.BlockLength; var localX = pos.X % Position.BlockLength; var localY = pos.Y % Position.BlockLength; var landblock = (uint)(blockX << 24 | blockY << 16); var position = new Position(); position.Instance = p.Instance; position.ObjCellID = landblock; position.Pos = new Vector3(localX, localY, pos.Z); position.Rotation = p.Rotation; position.ObjCellID = GetCell(position); return(position); }
public static Position FromGlobal(this Position p, Vector3 pos) { var landblock = LScape.get_landblock(p.LandblockId.Raw); if (landblock.IsDungeon) { var iPos = new Position(); iPos.LandblockId = p.LandblockId; iPos.Pos = new Vector3(pos.X, pos.Y, pos.Z); iPos.Rotation = p.Rotation; iPos.LandblockId = new LandblockId(GetCell(iPos)); return(iPos); } var blockX = (uint)pos.X / Position.BlockLength; var blockY = (uint)pos.Y / Position.BlockLength; var localX = pos.X % Position.BlockLength; var localY = pos.Y % Position.BlockLength; var landblockID = blockX << 24 | blockY << 16 | 0xFFFF; var position = new Position(); position.LandblockId = new LandblockId((byte)blockX, (byte)blockY); position.PositionX = localX; position.PositionY = localY; position.PositionZ = pos.Z; position.Rotation = p.Rotation; position.LandblockId = new LandblockId(GetCell(position)); return(position); }
public static Position ACEPosition(this Physics.Common.Position pos, Position source) { var newPos = new Position(pos.ObjCellID, pos.Frame.Origin, pos.Frame.Orientation, false, source.Instance); newPos.Instance = source.Instance; return(newPos); }
/// <summary> /// This signature services MoveToObject and TurnToObject /// Update Position prior to start, start them moving or turning, set statemachine to moving. /// Moved from player - we need to be able to move creatures as well. Og II /// </summary> /// <param name="worldObjectPosition">Position in the world</param> /// <param name="sequence">Sequence for the object getting the message.</param> /// <param name="movementType">What type of movement are we about to execute</param> /// <param name="targetGuid">Who are we moving or turning toward</param> public void OnAutonomousMove(ACE.Entity.Position worldObjectPosition, SequenceManager sequence, MovementTypes movementType, ObjectGuid targetGuid) { UniversalMotion newMotion = new UniversalMotion(MotionStance.Standing, worldObjectPosition, targetGuid); newMotion.DistanceFrom = 0.60f; newMotion.MovementTypes = movementType; CurrentLandblock.EnqueueBroadcast(Location, Landblock.MaxObjectRange, new GameMessageUpdatePosition(this)); CurrentLandblock.EnqueueBroadcastMotion(this, newMotion); }
public void HandleActionJump(JumpPack jump) { StartJump = new ACE.Entity.Position(Location); //Console.WriteLine($"JumpPack: Velocity: {jump.Velocity}, Extent: {jump.Extent}"); var strength = Strength.Current; var capacity = EncumbranceSystem.EncumbranceCapacity((int)strength, AugmentationIncreasedCarryingCapacity); var burden = EncumbranceSystem.GetBurden(capacity, EncumbranceVal ?? 0); // calculate stamina cost for this jump var extent = Math.Clamp(jump.Extent, 0.0f, 1.0f); var staminaCost = MovementSystem.JumpStaminaCost(extent, burden, PKTimerActive); //Console.WriteLine($"Strength: {strength}, Capacity: {capacity}, Encumbrance: {EncumbranceVal ?? 0}, Burden: {burden}, StaminaCost: {staminaCost}"); // ensure player has enough stamina to jump /*if (staminaCost > Stamina.Current) * { * // get adjusted power * extent = MovementSystem.GetJumpPower(Stamina.Current, burden, false); * * staminaCost = (int)Stamina.Current; * * // adjust jump velocity * var velocityZ = MovementSystem.GetJumpHeight(burden, GetCreatureSkill(Skill.Jump).Current, extent, 1.0f); * * jump.Velocity.Z = velocityZ; * }*/ IsJumping = true; LastJumpTime = DateTime.UtcNow; UpdateVitalDelta(Stamina, -staminaCost); IsJumping = false; //Console.WriteLine($"Jump velocity: {jump.Velocity}"); // set jump velocity // TODO: have server verify / scale magnitude PhysicsObj.set_velocity(jump.Velocity, true); // this shouldn't be needed, but without sending this update motion / simulated movement event beforehand, // running forward and then performing a charged jump does an uncharged shallow arc jump instead // this hack fixes that... var movementData = new MovementData(this); movementData.IsAutonomous = true; movementData.MovementType = MovementType.Invalid; movementData.Invalid = new MovementInvalid(movementData); EnqueueBroadcast(new GameMessageUpdateMotion(this, movementData)); // broadcast jump EnqueueBroadcast(new GameMessageVectorUpdate(this)); }
/// <summary> /// Saves a CharacterPosition to the character position dictionary /// </summary> public void SetCharacterPosition(PositionType type, Position newPosition) { // reset the landblock id if (newPosition.LandblockId.Landblock == 0 && newPosition.Cell > 0) { newPosition.LandblockId = new LandblockId(newPosition.Cell); } Positions[type] = newPosition; }
/// <summary> /// Gets an outdoor cell ID for a position within a landblock /// </summary> public static uint GetOutdoorCell(this Position p) { var cellX = (uint)p.PositionX / Position.CellLength; var cellY = (uint)p.PositionY / Position.CellLength; var cellID = cellX * Position.CellSide + cellY + 1; var blockCellID = (uint)((p.LandblockId.Raw & 0xFFFF0000) | cellID); return(blockCellID); }
/// <summary> /// Processes physics objects in all active landblocks for updating /// </summary> private static IEnumerable <WorldObject> HandlePhysics(double timeTick) { ConcurrentQueue <WorldObject> movedObjects = new ConcurrentQueue <WorldObject>(); // Access ActiveLandblocks should be safe here, but sometimes crashes with // System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.' try { Parallel.ForEach(LandblockManager.ActiveLandblocks.Keys, landblock => { foreach (WorldObject wo in landblock.GetPhysicsWorldObjects()) { Position newPosition = null; // detect player movement // TODO: handle players the same as everything else var player = wo as Player; if (player != null) { newPosition = HandlePlayerPhysics(player, timeTick); if (newPosition != null) { movedObjects.Enqueue(wo); // update position through physics engine wo.UpdatePlayerPhysics(newPosition); } } else if (wo.Missile.HasValue && wo.Missile.Value) { // physics minimum quantum? var isMoved = wo.UpdateObjectPhysics(); if (isMoved) { movedObjects.Enqueue(wo); // send update? } } } }); foreach (var wo in UpdateLandblock) { wo.PreviousLocation = wo.Location; LandblockManager.RelocateObjectForPhysics(wo); } UpdateLandblock.Clear(); } catch (Exception e) { Console.WriteLine(e); // FIXME: concurrency } return(movedObjects); }
private List <WorldObject> GetWorldObjectsInRange(Position pos, float distance) { List <Landblock> landblocksInRange = GetLandblocksInRange(pos, distance); List <WorldObject> ret = new List <WorldObject>(); foreach (Landblock lb in landblocksInRange) { ret.AddRange(lb.worldObjects.Values.Where(x => x.Location.SquaredDistanceTo(pos) < distance * distance).ToList()); } return(ret); }
/// <summary> /// Enqueues a message for broadcast, thread safe /// </summary> public void EnqueueBroadcast(Position pos, params GameMessage[] msgs) { // Atomically checks and sets the broadcastQueued bit -- // guarantees that if we need a broadcast it will be enqueued in the world-managers broadcast queue exactly once if (Interlocked.CompareExchange(ref broadcastQueued, 1, 0) == 0) { WorldManager.BroadcastQueue.EnqueueAction(new ActionEventDelegate(() => SendBroadcasts())); } foreach (GameMessage msg in msgs) { broadcastQueue.Enqueue(new Tuple <Position, float, GameMessage>(pos, MaxObjectRange, msg)); } }
/// <summary> /// Gets an indoor cell ID for a position within a dungeon /// </summary> private static uint GetIndoorCell(this Position p) { var adjustCell = AdjustCell.Get(p.Landblock); var envCell = adjustCell.GetCell(p.Pos); if (envCell != null) { return(envCell.Value); } else { return(p.Cell); } }
public static Vector3 ToGlobal(this Position p) { var landblock = LScape.get_landblock(p.LandblockId.Raw); if (landblock.IsDungeon) { return(p.Pos); } var x = p.LandblockId.LandblockX * Position.BlockLength + p.PositionX; var y = p.LandblockId.LandblockY * Position.BlockLength + p.PositionY; var z = p.PositionZ; return(new Vector3(x, y, z)); }
public static string GetMapCoordStr(this Position pos) { var mapCoords = pos.GetMapCoords(); if (mapCoords == null) { return(null); } var northSouth = mapCoords.Value.Y >= 0 ? "N" : "S"; var eastWest = mapCoords.Value.X >= 0 ? "E" : "W"; return(string.Format("{0:0.0}", Math.Abs(mapCoords.Value.Y) - 0.05f) + northSouth + ", " + string.Format("{0:0.0}", Math.Abs(mapCoords.Value.X) - 0.05f) + eastWest); }
// End wrappers /// <summary> /// Runs an action on all players within a certain distance from a point. /// </summary> /// <param name="pos"></param> /// <param name="distance"></param> /// <param name="delegateAction"></param> public void EnqueueActionBroadcast(Position pos, float distance, Action <Player> delegateAction) { List <Landblock> landblocksInRange = GetLandblocksInRange(pos, distance); foreach (Landblock lb in landblocksInRange) { List <Player> allPlayers = lb.worldObjects.Values.OfType <Player>().ToList(); foreach (Player p in allPlayers) { if (p.Location.SquaredDistanceTo(pos) < distance * distance) { p.EnqueueAction(new ActionEventDelegate(() => delegateAction(p))); } } } }
public static void Translate(this Position pos, uint blockCell) { var newBlockX = blockCell >> 24; var newBlockY = (blockCell >> 16) & 0xFF; var xDiff = (int)newBlockX - pos.LandblockX; var yDiff = (int)newBlockY - pos.LandblockY; //pos.Origin.X -= xDiff * 192; pos.PositionX -= xDiff * 192; //pos.Origin.Y -= yDiff * 192; pos.PositionY -= yDiff * 192; //pos.ObjCellID = blockCell; pos.LandblockId = new LandblockId(blockCell); }
public static Vector3 ToGlobal(this Position p) { var landblock = LScape.get_landblock(p.LandblockId.Raw); // TODO: investigate dungeons that are below actual traversable overworld terrain // ex., 010AFFFF //if (landblock.IsDungeon) if (p.Indoors) { return(p.Pos); } var x = p.LandblockId.LandblockX * Position.BlockLength + p.PositionX; var y = p.LandblockId.LandblockY * Position.BlockLength + p.PositionY; var z = p.PositionZ; return(new Vector3(x, y, z)); }
public static bool AttemptToFixRotation(this Position pos, WorldObject wo, PositionType positionType) { log.Warn($"detected bad quaternion x y z w for {wo.Name} (0x{wo.Guid}) | WCID: {wo.WeenieClassId} | WeenieType: {wo.WeenieType} | PositionType: {positionType}"); log.Warn($"before fix: {pos.ToLOCString()}"); var normalized = Quaternion.Normalize(pos.Rotation); var success = IsRotationValid(normalized); if (success) { pos.Rotation = normalized; } log.Warn($" after fix: {pos.ToLOCString()}"); return(success); }
public static Vector3 ToGlobal(this Position p, bool skipIndoors = true) { // TODO: Is this necessary? It seemed to be loading rogue physics landblocks. Commented out 2019-04 Mag-nus //var landblock = LScape.get_landblock(p.LandblockId.Raw); // TODO: investigate dungeons that are below actual traversable overworld terrain // ex., 010AFFFF //if (landblock.IsDungeon) if (p.Indoors && skipIndoors) { return(p.Pos); } var x = p.LandblockId.LandblockX * Position.BlockLength + p.PositionX; var y = p.LandblockId.LandblockY * Position.BlockLength + p.PositionY; var z = p.PositionZ; return(new Vector3(x, y, z)); }
/// <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)); }
/// <summary> /// Detects if player has moved through ForcedLocation or RequestedLocation /// </summary> private static Position HandlePlayerPhysics(Player player) { Position newPosition = null; if (player.ForcedLocation != null) { newPosition = player.ForcedLocation; } else if (player.RequestedLocation != null) { newPosition = player.RequestedLocation; } if (newPosition != null) { player.ClearRequestedPositions(); } return(newPosition); }
private static IEnumerable <WorldObject> HandlePhysics(double timeTick) { ConcurrentQueue <WorldObject> movedObjects = new ConcurrentQueue <WorldObject>(); // Accessing ActiveLandblocks is safe here -- nothing can modify the landblocks at this point // This crashes sometimes with the following exception: System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.' try { Parallel.ForEach(LandblockManager.ActiveLandblocks, landblock => { foreach (WorldObject wo in landblock.GetPhysicsWorldObjects()) { Position newPosition = wo.Location; if (wo.ForcedLocation != null) { newPosition = wo.ForcedLocation; movedObjects.Enqueue(wo); } else if (wo.RequestedLocation != null) { newPosition = wo.RequestedLocation; movedObjects.Enqueue(wo); } if (newPosition != wo.Location) { wo.PhysicsUpdatePosition(newPosition); } wo.ClearRequestedPositions(); } }); } catch (Exception e) { Console.WriteLine(e); // FIXME: concurrency } return(movedObjects); }
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); }
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 static Vector2?GetMapCoords(this Position pos) { // no map coords available for dungeons / indoors? if ((pos.Cell & 0xFFFF) >= 0x100) { return(null); } var globalPos = pos.ToGlobal(); // 1 landblock = 192 meters // 1 landblock = 0.8 map units // 1 map unit = 1.25 landblocks // 1 map unit = 240 meters var mapCoords = new Vector2(globalPos.X / 240, globalPos.Y / 240); // dereth is 204 map units across, -102 to +102 mapCoords -= Vector2.One * 102; return(mapCoords); }
public static void HandlePhysicsLandblock(Landblock landblock, double timeTick, ConcurrentQueue <WorldObject> movedObjects) { foreach (WorldObject wo in landblock.GetPhysicsWorldObjects()) { Position newPosition = null; // set to TRUE if object changes landblock var landblockUpdate = false; // detect player movement // TODO: handle players the same as everything else var player = wo as Player; if (player != null) { wo.InUpdate = true; newPosition = HandlePlayerPhysics(player, timeTick); // update position through physics engine if (newPosition != null) { landblockUpdate = wo.UpdatePlayerPhysics(newPosition); } wo.InUpdate = false; } else { landblockUpdate = wo.UpdateObjectPhysics(); } if (landblockUpdate) { movedObjects.Enqueue(wo); } } }