private void FaceClickCoordinates(IEntity user, EntityCoordinates coordinates) { var diff = coordinates.ToMapPos(EntityManager) - user.Transform.MapPosition.Position; if (diff.LengthSquared <= 0.01f) { return; } var diffAngle = Angle.FromWorldVec(diff); if (Get <ActionBlockerSystem>().CanChangeDirection(user)) { user.Transform.WorldRotation = diffAngle; } else { if (user.TryGetComponent(out BuckleComponent? buckle) && (buckle.BuckledTo != null)) { // We're buckled to another object. Is that object rotatable? if (buckle.BuckledTo !.Owner.TryGetComponent(out SharedRotatableComponent? rotatable) && rotatable.RotateWhileAnchored) { // Note the assumption that even if unanchored, user can only do spinnychair with an "independent wheel". // (Since the user being buckled to it holds it down with their weight.) // This is logically equivalent to RotateWhileAnchored. // Barstools and office chairs have independent wheels, while regular chairs don't. rotatable.Owner.Transform.LocalRotation = diffAngle; } } } }
private bool CheckRangeAndSetFacing(EntityCoordinates target, IEntity player) { // ensure it's within their clickable range var targetWorldPos = target.ToMapPos(EntityManager); var rangeBox = new Box2(player.Transform.WorldPosition, player.Transform.WorldPosition) .Enlarged(_entityManager.MaxUpdateRange); if (!rangeBox.Contains(targetWorldPos)) { Logger.DebugS("action", "user {0} attempted to" + " perform target action further than allowed range", player.Name); return(false); } if (!ActionBlockerSystem.CanChangeDirection(player)) { return(true); } // don't set facing unless they clicked far enough away var diff = targetWorldPos - player.Transform.WorldPosition; if (diff.LengthSquared > 0.01f) { player.Transform.LocalRotation = new Angle(diff); } return(true); }
/// <inheritdoc /> public IEnumerable <IEntity> GetEntitiesInRange(EntityCoordinates position, float range, bool approximate = false) { var mapPosition = position.ToMapPos(this); var aabb = new Box2(mapPosition - new Vector2(range / 2, range / 2), mapPosition + new Vector2(range / 2, range / 2)); return(GetEntitiesIntersecting(_mapManager.GetGrid(position.GetGridId(this)).ParentMapId, aabb, approximate)); }
protected bool ValidateInteractAndFace(EntityUid user, EntityCoordinates coordinates) { // Verify user is on the same map as the entity they clicked on if (coordinates.GetMapId(EntityManager) != Transform(user).MapID) { return(false); } _rotateToFaceSystem.TryFaceCoordinates(user, coordinates.ToMapPos(EntityManager)); return(true); }
private bool ValidateInteractAndFace(EntityUid user, EntityCoordinates coordinates) { // Verify user is on the same map as the entity they clicked on if (coordinates.GetMapId(_entityManager) != EntityManager.GetComponent <TransformComponent>(user).MapID) { Logger.WarningS("system.interaction", $"User entity named {EntityManager.GetComponent<MetaDataComponent>(user).EntityName} clicked on a map they aren't located on"); return(false); } _rotateToFaceSystem.TryFaceCoordinates(user, coordinates.ToMapPos(EntityManager)); return(true); }
private bool ValidateInteractAndFace(EntityUid user, EntityCoordinates coordinates) { // Verify user is on the same map as the entity they clicked on if (coordinates.GetMapId(EntityManager) != Transform(user).MapID) { Logger.WarningS("system.interaction", $"User entity {ToPrettyString(user):user} clicked on a map they aren't located on"); return(false); } _rotateToFaceSystem.TryFaceCoordinates(user, coordinates.ToMapPos(EntityManager)); return(true); }
public bool IsColliding(EntityCoordinates coordinates) { var bounds = pManager.ColliderAABB; var worldcoords = coordinates.ToMapPos(pManager.EntityManager); var collisionbox = Box2.FromDimensions( bounds.Left + worldcoords.X, bounds.Bottom + worldcoords.Y, bounds.Width, bounds.Height); if (pManager.PhysicsManager.TryCollideRect(collisionbox, pManager.MapManager.GetGrid(coordinates.GetGridId(pManager.EntityManager)).ParentMapId)) { return(true); } return(false); }
private bool CheckRangeAndSetFacing(EntityCoordinates target, EntityUid player) { // ensure it's within their clickable range var targetWorldPos = target.ToMapPos(EntityManager); var rangeBox = new Box2(_entities.GetComponent <TransformComponent>(player).WorldPosition, _entities.GetComponent <TransformComponent>(player).WorldPosition) .Enlarged(MaxUpdateRange); if (!rangeBox.Contains(targetWorldPos)) { Logger.DebugS("action", "user {0} attempted to" + " perform target action further than allowed range", _entities.GetComponent <MetaDataComponent>(player).EntityName); return(false); } EntitySystem.Get <RotateToFaceSystem>().TryFaceCoordinates(player, targetWorldPos); return(true); }
private bool HandleDrop(ICommonSession?session, EntityCoordinates coords, EntityUid uid) { var ent = ((IPlayerSession?)session)?.AttachedEntity; if (ent == null || !ent.IsValid()) { return(false); } if (!ent.TryGetComponent(out HandsComponent? handsComp)) { return(false); } if (handsComp.ActiveHand == null || handsComp.GetActiveHand == null) { return(false); } // It's important to note that the calculations are done in map coordinates (they're absolute). // They're translated back to EntityCoordinates at the end. var entMap = ent.Transform.MapPosition; var targetPos = coords.ToMapPos(EntityManager); var dropVector = targetPos - entMap.Position; var targetVector = Vector2.Zero; if (dropVector != Vector2.Zero) { var targetLength = MathF.Min(dropVector.Length, SharedInteractionSystem.InteractionRange - 0.001f); // InteractionRange is reduced due to InRange not dealing with floating point error var newCoords = new MapCoordinates(dropVector.Normalized * targetLength + entMap.Position, entMap.MapId); var rayLength = Get <SharedInteractionSystem>().UnobstructedDistance(entMap, newCoords, ignoredEnt: ent); targetVector = dropVector.Normalized * rayLength; } var resultMapCoordinates = new MapCoordinates(entMap.Position + targetVector, entMap.MapId); var resultEntCoordinates = EntityCoordinates.FromMap(coords.GetParent(EntityManager), resultMapCoordinates); handsComp.Drop(handsComp.ActiveHand, resultEntCoordinates); return(true); }
private bool HandleDrop(ICommonSession session, EntityCoordinates coords, EntityUid uid) { var ent = ((IPlayerSession)session).AttachedEntity; if (ent == null || !ent.IsValid()) { return(false); } if (!ent.TryGetComponent(out HandsComponent handsComp)) { return(false); } if (handsComp.ActiveHand == null || handsComp.GetActiveHand == null) { return(false); } var entMap = ent.Transform.MapPosition; var targetPos = coords.ToMapPos(EntityManager); var dropVector = targetPos - entMap.Position; var targetVector = Vector2.Zero; if (dropVector != Vector2.Zero) { var targetLength = MathF.Min(dropVector.Length, SharedInteractionSystem.InteractionRange - 0.001f); // InteractionRange is reduced due to InRange not dealing with floating point error var newCoords = coords.WithPosition(dropVector.Normalized * targetLength + entMap.Position).ToMap(EntityManager); var rayLength = Get <SharedInteractionSystem>().UnobstructedDistance(entMap, newCoords, ignoredEnt: ent); targetVector = dropVector.Normalized * rayLength; } handsComp.Drop(handsComp.ActiveHand, coords.WithPosition(entMap.Position + targetVector)); return(true); }
/// <summary> /// Throw an entity in the direction of <paramref name="targetLoc"/> from <paramref name="sourceLoc"/>. /// </summary> /// <param name="thrownEnt">The entity to throw.</param> /// <param name="throwForce"> /// The force to throw the entity with. /// Total impulse applied is equal to this force applied for one second. /// </param> /// <param name="targetLoc"> /// The target location to throw at. /// This is only used to calculate a direction, /// actual distance is purely determined by <paramref name="throwForce"/>. /// </param> /// <param name="sourceLoc"> /// The position to start the throw from. /// </param> /// <param name="spread"> /// If true, slightly spread the actual throw angle. /// </param> /// <param name="throwSourceEnt"> /// The entity that did the throwing. An opposite impulse will be applied to this entity if passed in. /// </param> public static void Throw(this IEntity thrownEnt, float throwForce, EntityCoordinates targetLoc, EntityCoordinates sourceLoc, bool spread = false, IEntity throwSourceEnt = null) { if (!thrownEnt.TryGetComponent(out IPhysicsComponent colComp)) { return; } var entityManager = IoCManager.Resolve <IEntityManager>(); colComp.CanCollide = true; // I can now collide with player, so that i can do damage. if (!thrownEnt.TryGetComponent(out ThrownItemComponent projComp)) { projComp = thrownEnt.AddComponent <ThrownItemComponent>(); if (colComp.PhysicsShapes.Count == 0) { colComp.PhysicsShapes.Add(new PhysShapeAabb()); } colComp.PhysicsShapes[0].CollisionMask |= (int)CollisionGroup.ThrownItem; colComp.Status = BodyStatus.InAir; } var angle = new Angle(targetLoc.ToMapPos(entityManager) - sourceLoc.ToMapPos(entityManager)); if (spread) { var spreadRandom = IoCManager.Resolve <IRobustRandom>(); angle += Angle.FromDegrees(spreadRandom.NextGaussian(0, 3)); } if (throwSourceEnt != null) { projComp.User = throwSourceEnt; projComp.IgnoreEntity(throwSourceEnt); if (ActionBlockerSystem.CanChangeDirection(throwSourceEnt)) { throwSourceEnt.Transform.LocalRotation = angle.GetCardinalDir().ToAngle(); } } // scaling is handled elsewhere, this is just multiplying by 60 independent of timing as a fix until elsewhere values are updated var spd = throwForce * 60; projComp.StartThrow(angle.ToVec(), spd); if (throwSourceEnt != null && throwSourceEnt.TryGetComponent <IPhysicsComponent>(out var physics)) { if (throwSourceEnt.IsWeightless()) { // We don't check for surrounding entities, // so you'll still get knocked around if you're hugging the station wall in zero g. // I got kinda lazy is the reason why. Also it makes a bit of sense. // If somebody wants they can come along and make it so magboots completely hold you still. // Would be a cool incentive to use them. const float throwFactor = 0.2f; // Break Newton's Third Law for better gameplay var mover = physics.EnsureController <ThrowKnockbackController>(); mover.Push(-angle.ToVec(), spd * throwFactor); } } }
public override void Shoot(GunComponent gun, List <IShootable> ammo, EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, EntityUid?user = null) { var fromMap = fromCoordinates.ToMap(EntityManager); var toMap = toCoordinates.ToMapPos(EntityManager); var mapDirection = toMap - fromMap.Position; var mapAngle = mapDirection.ToAngle(); var angle = GetRecoilAngle(Timing.CurTime, gun, mapDirection.ToAngle()); // Update shot based on the recoil toMap = fromMap.Position + angle.ToVec() * mapDirection.Length; mapDirection = toMap - fromMap.Position; var entityDirection = Transform(fromCoordinates.EntityId).InvWorldMatrix.Transform(toMap) - fromCoordinates.Position; // I must be high because this was getting tripped even when true. // DebugTools.Assert(direction != Vector2.Zero); var shotProjectiles = new List <EntityUid>(ammo.Count); foreach (var shootable in ammo) { switch (shootable) { // Cartridge shoots something else case CartridgeAmmoComponent cartridge: if (!cartridge.Spent) { if (cartridge.Count > 1) { var angles = LinearSpread(mapAngle - cartridge.Spread / 2, mapAngle + cartridge.Spread / 2, cartridge.Count); for (var i = 0; i < cartridge.Count; i++) { var uid = Spawn(cartridge.Prototype, fromCoordinates); ShootProjectile(uid, angles[i].ToVec(), user); shotProjectiles.Add(uid); } } else { var uid = Spawn(cartridge.Prototype, fromCoordinates); ShootProjectile(uid, mapDirection, user); shotProjectiles.Add(uid); } SetCartridgeSpent(cartridge, true); MuzzleFlash(gun.Owner, cartridge, user); PlaySound(gun.Owner, gun.SoundGunshot?.GetSound(Random, ProtoManager), user); if (cartridge.DeleteOnSpawn) { Del(cartridge.Owner); } } else { PlaySound(gun.Owner, gun.SoundEmpty?.GetSound(Random, ProtoManager), user); } // Something like ballistic might want to leave it in the container still if (!cartridge.DeleteOnSpawn && !Containers.IsEntityInContainer(cartridge.Owner)) { EjectCartridge(cartridge.Owner); } Dirty(cartridge); break; // Ammo shoots itself case AmmoComponent newAmmo: shotProjectiles.Add(newAmmo.Owner); MuzzleFlash(gun.Owner, newAmmo, user); PlaySound(gun.Owner, gun.SoundGunshot?.GetSound(Random, ProtoManager), user); // Do a throw if (!HasComp <ProjectileComponent>(newAmmo.Owner)) { RemComp <AmmoComponent>(newAmmo.Owner); // TODO: Someone can probably yeet this a billion miles so need to pre-validate input somewhere up the call stack. ThrowingSystem.TryThrow(newAmmo.Owner, mapDirection, 20f, user); break; } ShootProjectile(newAmmo.Owner, mapDirection, user); break; case HitscanPrototype hitscan: var ray = new CollisionRay(fromMap.Position, mapDirection.Normalized, hitscan.CollisionMask); var rayCastResults = Physics.IntersectRay(fromMap.MapId, ray, hitscan.MaxLength, user, false).ToList(); if (rayCastResults.Count >= 1) { var result = rayCastResults[0]; var distance = result.Distance; FireEffects(fromCoordinates, distance, entityDirection.ToAngle(), hitscan, result.HitEntity); var dmg = hitscan.Damage; if (dmg != null) { dmg = Damageable.TryChangeDamage(result.HitEntity, dmg); } if (dmg != null) { PlayImpactSound(result.HitEntity, dmg, hitscan.Sound, hitscan.ForceSound); if (user != null) { Logs.Add(LogType.HitScanHit, $"{ToPrettyString(user.Value):user} hit {ToPrettyString(result.HitEntity):target} using hitscan and dealt {dmg.Total:damage} damage"); } else { Logs.Add(LogType.HitScanHit, $"Hit {ToPrettyString(result.HitEntity):target} using hitscan and dealt {dmg.Total:damage} damage"); } } } else { FireEffects(fromCoordinates, hitscan.MaxLength, entityDirection.ToAngle(), hitscan); } PlaySound(gun.Owner, gun.SoundGunshot?.GetSound(Random, ProtoManager), user); break; default: throw new ArgumentOutOfRangeException(); } } RaiseLocalEvent(gun.Owner, new AmmoShotEvent() { FiredProjectiles = shotProjectiles, }, false); }
private DockingConfig?GetDockingConfig(ShuttleComponent component, EntityUid targetGrid) { var gridDocks = GetDocks(targetGrid); if (gridDocks.Count <= 0) { return(null); } var xformQuery = GetEntityQuery <TransformComponent>(); var targetGridGrid = Comp <IMapGridComponent>(targetGrid); var targetGridXform = xformQuery.GetComponent(targetGrid); var targetGridAngle = targetGridXform.WorldRotation.Reduced(); var targetGridRotation = targetGridAngle.ToVec(); var shuttleDocks = GetDocks(component.Owner); var shuttleAABB = Comp <IMapGridComponent>(component.Owner).Grid.LocalAABB; var validDockConfigs = new List <DockingConfig>(); if (shuttleDocks.Count > 0) { // We'll try all combinations of shuttle docks and see which one is most suitable foreach (var shuttleDock in shuttleDocks) { var shuttleDockXform = xformQuery.GetComponent(shuttleDock.Owner); foreach (var gridDock in gridDocks) { var gridXform = xformQuery.GetComponent(gridDock.Owner); if (!CanDock( shuttleDock, shuttleDockXform, gridDock, gridXform, targetGridRotation, shuttleAABB, targetGridGrid, out var dockedAABB, out var matty, out var targetAngle)) { continue; } // Can't just use the AABB as we want to get bounds as tight as possible. var spawnPosition = new EntityCoordinates(targetGrid, matty.Transform(Vector2.Zero)); spawnPosition = new EntityCoordinates(targetGridXform.MapUid !.Value, spawnPosition.ToMapPos(EntityManager)); var dockedBounds = new Box2Rotated(shuttleAABB.Translated(spawnPosition.Position), targetGridAngle, spawnPosition.Position); // Check if there's no intersecting grids (AKA oh god it's docking at cargo). if (_mapManager.FindGridsIntersecting(targetGridXform.MapID, dockedBounds).Any(o => o.GridEntityId != targetGrid)) { continue; } // Alright well the spawn is valid now to check how many we can connect // Get the matrix for each shuttle dock and test it against the grid docks to see // if the connected position / direction matches. var dockedPorts = new List <(DockingComponent DockA, DockingComponent DockB)>() { (shuttleDock, gridDock), }; // TODO: Check shuttle orientation as the tiebreaker. foreach (var other in shuttleDocks) { if (other == shuttleDock) { continue; } foreach (var otherGrid in gridDocks) { if (otherGrid == gridDock) { continue; } if (!CanDock( other, xformQuery.GetComponent(other.Owner), otherGrid, xformQuery.GetComponent(otherGrid.Owner), targetGridRotation, shuttleAABB, targetGridGrid, out var otherDockedAABB, out _, out var otherTargetAngle) || !otherDockedAABB.Equals(dockedAABB) || !targetAngle.Equals(otherTargetAngle)) { continue; } dockedPorts.Add((other, otherGrid)); } } var spawnRotation = shuttleDockXform.LocalRotation + gridXform.LocalRotation + targetGridXform.LocalRotation; validDockConfigs.Add(new DockingConfig() { Docks = dockedPorts, Area = dockedAABB.Value, Coordinates = spawnPosition, Angle = spawnRotation, }); } } } if (validDockConfigs.Count <= 0) { return(null); } // Prioritise by priority docks, then by maximum connected ports, then by most similar angle. validDockConfigs = validDockConfigs .OrderByDescending(x => x.Docks.Any(docks => HasComp <EmergencyDockComponent>(docks.DockB.Owner))) .ThenByDescending(x => x.Docks.Count) .ThenBy(x => Math.Abs(Angle.ShortestDistance(x.Angle.Reduced(), targetGridAngle).Theta)).ToList(); var location = validDockConfigs.First(); location.TargetGrid = targetGrid; // TODO: Ideally do a hyperspace warpin, just have it run on like a 10 second timer. return(location); }
public void Update(float frameTime) { Age += TimeSpan.FromSeconds(frameTime); if (Age >= Deathtime) { return; } Velocity += Acceleration * frameTime; RadialVelocity += RadialAcceleration * frameTime; TangentialVelocity += TangentialAcceleration * frameTime; var deltaPosition = new Vector2(0f, 0f); //If we have an emitter we can do special effects around that emitter position if (_mapManager.GridExists(EmitterCoordinates.GetGridId(_entityManager))) { //Calculate delta p due to radial velocity var positionRelativeToEmitter = Coordinates.ToMapPos(_entityManager) - EmitterCoordinates.ToMapPos(_entityManager); var deltaRadial = RadialVelocity * frameTime; deltaPosition = positionRelativeToEmitter * (deltaRadial / positionRelativeToEmitter.Length); //Calculate delta p due to tangential velocity var radius = positionRelativeToEmitter.Length; if (radius > 0) { var theta = (float)Math.Atan2(positionRelativeToEmitter.Y, positionRelativeToEmitter.X); theta += TangentialVelocity * frameTime; deltaPosition += new Vector2(radius * (float)Math.Cos(theta), radius * (float)Math.Sin(theta)) - positionRelativeToEmitter; } } //Calculate new position from our velocity as well as possible rotation/movement around emitter deltaPosition += Velocity * frameTime; Coordinates = Coordinates.Offset(deltaPosition); //Finish calculating new rotation, size, color Rotation += RotationRate * frameTime; Size += SizeDelta * frameTime; Color += ColorDelta * frameTime; if (RsiState == null) { return; } // Calculate RSI animations. var delayCount = RsiState.DelayCount; if (delayCount > 0 && (AnimationLoops || AnimationIndex < delayCount - 1)) { AnimationTime += frameTime; while (RsiState.GetDelay(AnimationIndex) < AnimationTime) { var delay = RsiState.GetDelay(AnimationIndex); AnimationIndex += 1; AnimationTime -= delay; if (AnimationIndex == delayCount) { if (AnimationLoops) { AnimationIndex = 0; } else { break; } } EffectSprite = RsiState.GetFrame(RSI.State.Direction.South, AnimationIndex); } } }
/// <summary> /// Fires a round of ammo out of the weapon. /// </summary> private void Fire(EntityUid shooter, ServerRangedBarrelComponent component, EntityCoordinates coordinates) { if (component.ShotsLeft == 0) { SoundSystem.Play(Filter.Pvs(component.Owner), component.SoundEmpty.GetSound(), component.Owner); return; } var ammo = PeekAtAmmo(component); if (TakeOutProjectile(component, Transform(shooter).Coordinates) is not { Valid: true } projectile) { SoundSystem.Play(Filter.Pvs(component.Owner), component.SoundEmpty.GetSound(), component.Owner); return; } var targetPos = coordinates.ToMapPos(EntityManager); // At this point firing is confirmed var direction = (targetPos - Transform(shooter).WorldPosition).ToAngle(); var angle = GetRecoilAngle(component, direction); // This should really be client-side but for now we'll just leave it here if (HasComp <CameraRecoilComponent>(shooter)) { var kick = -angle.ToVec() * 0.15f; _recoil.KickCamera(shooter, kick); } // This section probably needs tweaking so there can be caseless hitscan etc. if (TryComp(projectile, out HitscanComponent? hitscan)) { FireHitscan(shooter, hitscan, component, angle); } else if (HasComp <ProjectileComponent>(projectile) && TryComp(ammo, out AmmoComponent? ammoComponent)) { FireProjectiles(shooter, projectile, component, ammoComponent.ProjectilesFired, ammoComponent.EvenSpreadAngle, angle, ammoComponent.Velocity, ammo !.Value); if (component.CanMuzzleFlash) { MuzzleFlash(component.Owner, ammoComponent, angle); } if (ammoComponent.Caseless) { EntityManager.DeleteEntity(ammo.Value); } } else { // Invalid types throw new InvalidOperationException(); } SoundSystem.Play(Filter.Broadcast(), component.SoundGunshot.GetSound(), component.Owner); component.Dirty(EntityManager); component.LastFire = _gameTiming.CurTime; }
public bool TryPoint(ICommonSession?session, EntityCoordinates coords, EntityUid uid) { var player = (session as IPlayerSession)?.ContentData()?.Mind?.CurrentEntity; if (player == null) { return(false); } if (_pointers.TryGetValue(session !, out var lastTime) && _gameTiming.CurTime < lastTime + PointDelay) { return(false); } if (EntityManager.TryGetEntity(uid, out var entity) && entity.HasComponent <PointingArrowComponent>()) { // this is a pointing arrow. no pointing here... return(false); } if (!InRange(coords, player.Transform.Coordinates)) { player.PopupMessage(Loc.GetString("You can't reach there!")); return(false); } if (ActionBlockerSystem.CanChangeDirection(player)) { var diff = coords.ToMapPos(EntityManager) - player.Transform.MapPosition.Position; if (diff.LengthSquared > 0.01f) { player.Transform.LocalRotation = new Angle(diff); } } var arrow = EntityManager.SpawnEntity("pointingarrow", coords); var layer = (int)VisibilityFlags.Normal; if (player.TryGetComponent(out VisibilityComponent? playerVisibility)) { var arrowVisibility = arrow.EnsureComponent <VisibilityComponent>(); layer = arrowVisibility.Layer = playerVisibility.Layer; } // Get players that are in range and whose visibility layer matches the arrow's. var viewers = _playerManager.GetPlayersBy((playerSession) => { if ((playerSession.VisibilityMask & layer) == 0) { return(false); } var ent = playerSession.ContentData()?.Mind?.CurrentEntity; return(ent != null && ent.Transform.MapPosition.InRange(player.Transform.MapPosition, PointingRange)); }); string selfMessage; string viewerMessage; string?viewerPointedAtMessage = null; if (EntityManager.TryGetEntity(uid, out var pointed)) { selfMessage = player == pointed ? Loc.GetString("You point at yourself.") : Loc.GetString("You point at {0:theName}.", pointed); viewerMessage = player == pointed ? $"{player.Name} {Loc.GetString("points at {0:themself}.", player)}" : $"{player.Name} {Loc.GetString("points at {0:theName}.", pointed)}"; viewerPointedAtMessage = $"{player.Name} {Loc.GetString("points at you.")}"; } else { var tileRef = _mapManager.GetGrid(coords.GetGridId(EntityManager)).GetTileRef(coords); var tileDef = _tileDefinitionManager[tileRef.Tile.TypeId]; selfMessage = Loc.GetString("You point at {0}.", tileDef.DisplayName); viewerMessage = $"{player.Name} {Loc.GetString("points at {0}.", tileDef.DisplayName)}"; } _pointers[session !] = _gameTiming.CurTime;