public override bool IsValidPosition(EntityCoordinates position) { return(true); }
public abstract IEntity?TakeProjectile(EntityCoordinates spawnAt);
public override void DoAttack(EntityUid user, EntityCoordinates coordinates, bool wideAttack, EntityUid?target = null) { // TODO PREDICTION move server-side interaction logic into the shared system for interaction prediction. if (!ValidateInteractAndFace(user, coordinates)) { return; } // Check general interaction blocking. if (!_actionBlockerSystem.CanInteract(user, target)) { return; } // Check combat-specific action blocking. if (!_actionBlockerSystem.CanAttack(user, target)) { return; } if (!wideAttack) { // Check if interacted entity is in the same container, the direct child, or direct parent of the user. if (target != null && !Deleted(target.Value) && !ContainerSystem.IsInSameOrParentContainer(user, target.Value) && !CanAccessViaStorage(user, target.Value)) { Logger.WarningS("system.interaction", $"User entity {ToPrettyString(user):user} clicked on object {ToPrettyString(target.Value):target} that isn't the parent, child, or in the same container"); return; } // TODO: Replace with body attack range when we get something like arm length or telekinesis or something. var unobstructed = (target == null) ? InRangeUnobstructed(user, coordinates) : InRangeUnobstructed(user, target.Value); if (!unobstructed) { return; } } // Verify user has a hand, and find what object they are currently holding in their active hand if (TryComp(user, out HandsComponent? hands)) { var item = hands.ActiveHandEntity; if (item != null && !Deleted(item.Value)) { if (wideAttack) { var ev = new WideAttackEvent(item.Value, user, coordinates); RaiseLocalEvent(item.Value, ev, false); if (ev.Handled) { _adminLogSystem.Add(LogType.AttackArmedWide, LogImpact.Medium, $"{ToPrettyString(user):user} wide attacked with {ToPrettyString(item.Value):used} at {coordinates}"); return; } } else { var ev = new ClickAttackEvent(item.Value, user, coordinates, target); RaiseLocalEvent(item.Value, ev, false); if (ev.Handled) { if (target != null) { _adminLogSystem.Add(LogType.AttackArmedClick, LogImpact.Medium, $"{ToPrettyString(user):user} attacked {ToPrettyString(target.Value):target} with {ToPrettyString(item.Value):used} at {coordinates}"); } else { _adminLogSystem.Add(LogType.AttackArmedClick, LogImpact.Medium, $"{ToPrettyString(user):user} attacked with {ToPrettyString(item.Value):used} at {coordinates}"); } return; } } } else if (!wideAttack && target != null && HasComp <SharedItemComponent>(target.Value)) { // We pick up items if our hand is empty, even if we're in combat mode. InteractHand(user, target.Value); return; } } // TODO: Make this saner? // Attempt to do unarmed combat. We don't check for handled just because at this point it doesn't matter. if (wideAttack) { var ev = new WideAttackEvent(user, user, coordinates); RaiseLocalEvent(user, ev, false); if (ev.Handled) { _adminLogSystem.Add(LogType.AttackUnarmedWide, $"{ToPrettyString(user):user} wide attacked at {coordinates}"); } } else { var ev = new ClickAttackEvent(user, user, coordinates, target); RaiseLocalEvent(user, ev, false); if (ev.Handled) { if (target != null) { _adminLogSystem.Add(LogType.AttackUnarmedClick, LogImpact.Medium, $"{ToPrettyString(user):user} attacked {ToPrettyString(target.Value):target} at {coordinates}"); } else { _adminLogSystem.Add(LogType.AttackUnarmedClick, LogImpact.Medium, $"{ToPrettyString(user):user} attacked at {coordinates}"); } } } }
public FirePosEvent(EntityCoordinates coordinates) { Coordinates = coordinates; }
public PerformTargetPointItemActionMessage(ItemActionType actionType, EntityUid item, EntityCoordinates target) : base(actionType, item) { _target = target; }
public void DoAttack(EntityUid user, EntityCoordinates coordinates, bool wideAttack, EntityUid targetUid = default) { if (!ValidateInteractAndFace(user, coordinates)) { return; } if (!_actionBlockerSystem.CanAttack(user)) { return; } if (!wideAttack) { // Check if interacted entity is in the same container, the direct child, or direct parent of the user. if (targetUid != default && !user.IsInSameOrParentContainer(targetUid) && !CanAccessViaStorage(user, targetUid)) { Logger.WarningS("system.interaction", $"User entity named {EntityManager.GetComponent<MetaDataComponent>(user).EntityName} clicked on object {EntityManager.GetComponent<MetaDataComponent>(targetUid).EntityName} that isn't the parent, child, or in the same container"); return; } // TODO: Replace with body attack range when we get something like arm length or telekinesis or something. if (!user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true)) { return; } } // Verify user has a hand, and find what object they are currently holding in their active hand if (EntityManager.TryGetComponent <HandsComponent?>(user, out var hands)) { if (hands.GetActiveHand?.Owner is { Valid : true } item) { if (wideAttack) { var ev = new WideAttackEvent(item, user, coordinates); RaiseLocalEvent(item, ev, false); if (ev.Handled) { _adminLogSystem.Add(LogType.AttackArmedWide, LogImpact.Medium, $"{user} wide attacked with {item} at {coordinates}"); return; } } else { var ev = new ClickAttackEvent(item, user, coordinates, targetUid); RaiseLocalEvent(item, ev, false); if (ev.Handled) { if (targetUid != default) { _adminLogSystem.Add(LogType.AttackArmedClick, LogImpact.Medium, $"{user} attacked {targetUid} with {item} at {coordinates}"); } else { _adminLogSystem.Add(LogType.AttackArmedClick, LogImpact.Medium, $"{user} attacked with {item} at {coordinates}"); } return; } } } else if (!wideAttack && targetUid != default && _entityManager.HasComponent <ItemComponent>(targetUid)) { // We pick up items if our hand is empty, even if we're in combat mode. InteractHand(user, targetUid); return; } }
public RequestDecalRemovalEvent(EntityCoordinates coordinates) { Coordinates = coordinates; }
public static IEntity?SpawnIfUnobstructed( this IEntityManager entityManager, string?prototypeName, EntityCoordinates coordinates, CollisionGroup collisionLayer, in Box2?box = null,
public async Task PuddlePauseTest() { var server = StartServer(); await server.WaitIdleAsync(); var sMapManager = server.ResolveDependency <IMapManager>(); var sTileDefinitionManager = server.ResolveDependency <ITileDefinitionManager>(); var sGameTiming = server.ResolveDependency <IGameTiming>(); var entityManager = server.ResolveDependency <IEntityManager>(); MapId sMapId = default; IMapGrid sGrid; GridId sGridId = default; EntityUid sGridEntity = default; EntityCoordinates sCoordinates = default; // Spawn a paused map with one tile to spawn puddles on await server.WaitPost(() => { sMapId = sMapManager.CreateMap(); sMapManager.SetMapPaused(sMapId, true); sGrid = sMapManager.CreateGrid(sMapId); sGridId = sGrid.Index; sGridEntity = sGrid.GridEntityId; entityManager.GetComponent <MetaDataComponent>(sGridEntity).EntityPaused = true; // See https://github.com/space-wizards/RobustToolbox/issues/1444 var tileDefinition = sTileDefinitionManager["underplating"]; var tile = new Tile(tileDefinition.TileId); sCoordinates = sGrid.ToCoordinates(); sGrid.SetTile(sCoordinates, tile); }); // Check that the map and grid are paused await server.WaitAssertion(() => { Assert.True(sMapManager.IsGridPaused(sGridId)); Assert.True(sMapManager.IsMapPaused(sMapId)); }); float evaporateTime = default; PuddleComponent puddle = null; MetaDataComponent meta = null; EvaporationComponent evaporation; var amount = 2; var entitySystemManager = server.ResolveDependency <IEntitySystemManager>(); var spillSystem = entitySystemManager.GetEntitySystem <SpillableSystem>(); // Spawn a puddle await server.WaitAssertion(() => { var solution = new Solution("Water", FixedPoint2.New(amount)); puddle = spillSystem.SpillAt(solution, sCoordinates, "PuddleSmear"); meta = entityManager.GetComponent <MetaDataComponent>(puddle.Owner); // Check that the puddle was created Assert.NotNull(puddle); evaporation = entityManager.GetComponent <EvaporationComponent>(puddle.Owner); meta.EntityPaused = true; // See https://github.com/space-wizards/RobustToolbox/issues/1445 Assert.True(meta.EntityPaused); // Check that the puddle is going to evaporate Assert.Positive(evaporation.EvaporateTime); // Should have a timer component added to it for evaporation Assert.That(evaporation.Accumulator, Is.EqualTo(0f)); evaporateTime = evaporation.EvaporateTime; }); // Wait enough time for it to evaporate if it was unpaused var sTimeToWait = (5 + (int)Math.Ceiling(amount * evaporateTime * sGameTiming.TickRate)); await server.WaitRunTicks(sTimeToWait); // No evaporation due to being paused await server.WaitAssertion(() => { Assert.True(meta.EntityPaused); // Check that the puddle still exists Assert.False(meta.EntityDeleted); }); // Unpause the map await server.WaitPost(() => { sMapManager.SetMapPaused(sMapId, false); }); // Check that the map, grid and puddle are unpaused await server.WaitAssertion(() => { Assert.False(sMapManager.IsMapPaused(sMapId)); Assert.False(sMapManager.IsGridPaused(sGridId)); Assert.False(meta.EntityPaused); // Check that the puddle still exists Assert.False(meta.EntityDeleted); }); // Wait enough time for it to evaporate await server.WaitRunTicks(sTimeToWait); // Puddle evaporation should have ticked await server.WaitAssertion(() => { // Check that puddle has been deleted Assert.True(puddle.Deleted); }); }
protected bool TryFindRandomTile(out Vector2i tile, out EntityUid targetStation, out EntityUid targetGrid, out EntityCoordinates targetCoords) { tile = default; targetCoords = EntityCoordinates.Invalid; if (StationSystem.Stations.Count == 0) { targetStation = EntityUid.Invalid; targetGrid = EntityUid.Invalid; return(false); } targetStation = RobustRandom.Pick(StationSystem.Stations); var possibleTargets = Comp <StationDataComponent>(targetStation).Grids; if (possibleTargets.Count == 0) { targetGrid = EntityUid.Invalid; return(false); } targetGrid = RobustRandom.Pick(possibleTargets); if (!TryComp <IMapGridComponent>(targetGrid, out var gridComp)) { return(false); } var grid = gridComp.Grid; var atmosphereSystem = Get <AtmosphereSystem>(); var found = false; var gridBounds = grid.WorldAABB; var gridPos = grid.WorldPosition; for (var i = 0; i < 10; i++) { var randomX = RobustRandom.Next((int)gridBounds.Left, (int)gridBounds.Right); var randomY = RobustRandom.Next((int)gridBounds.Bottom, (int)gridBounds.Top); tile = new Vector2i(randomX - (int)gridPos.X, randomY - (int)gridPos.Y); if (atmosphereSystem.IsTileSpace(grid.GridEntityId, Transform(targetGrid).MapUid, tile, mapGridComp:gridComp) || atmosphereSystem.IsTileAirBlocked(grid.GridEntityId, tile, mapGridComp:gridComp)) { continue; } found = true; targetCoords = grid.GridTileToLocal(tile); break; } if (!found) { return(false); } return(true); }
public override IEntity TakeProjectile(EntityCoordinates spawnAt) { var powerCellEntity = _powerCellContainer.ContainedEntity; if (powerCellEntity == null) { return(null); } var capacitor = powerCellEntity.GetComponent <BatteryComponent>(); if (capacitor.CurrentCharge < _lowerChargeLimit) { return(null); } // Can fire confirmed // Multiply the entity's damage / whatever by the percentage of charge the shot has. IEntity entity; var chargeChange = Math.Min(capacitor.CurrentCharge, _baseFireCost); if (capacitor.UseCharge(chargeChange) < _lowerChargeLimit) { // Handling of funny exploding cells. return(null); } var energyRatio = chargeChange / _baseFireCost; if (_ammoContainer.ContainedEntity != null) { entity = _ammoContainer.ContainedEntity; _ammoContainer.Remove(entity); entity.Transform.Coordinates = spawnAt; } else { entity = Owner.EntityManager.SpawnEntity(_ammoPrototype, spawnAt); } if (entity.TryGetComponent(out ProjectileComponent projectileComponent)) { if (energyRatio < 1.0) { var newDamages = new Dictionary <DamageType, int>(projectileComponent.Damages.Count); foreach (var(damageType, damage) in projectileComponent.Damages) { newDamages.Add(damageType, (int)(damage * energyRatio)); } projectileComponent.Damages = newDamages; } } else if (entity.TryGetComponent(out HitscanComponent hitscanComponent)) { hitscanComponent.Damage *= energyRatio; hitscanComponent.ColorModifier = energyRatio; } else { throw new InvalidOperationException("Ammo doesn't have hitscan or projectile?"); } Dirty(); UpdateAppearance(); return(entity); }
public AnimatePickupEntityMessage(EntityUid entity, EntityCoordinates entityPosition) { Directed = true; EntityId = entity; EntityPosition = entityPosition; }
/// <summary> /// Gets all entities intersecting the given position. /// /// Static alternative to GetEntitiesUnderPosition to cut out /// some of the boilerplate needed to get state manager and check the current state. /// </summary> /// <param name="stateManager">state manager to use to get the current game screen</param> /// <param name="coordinates">coordinates to check</param> /// <returns>the entities under the position, empty list if none found</returns> public static IList<IEntity> GetEntitiesUnderPosition(IStateManager stateManager, EntityCoordinates coordinates) { if (stateManager.CurrentState is GameScreenBase gameScreenBase) { return gameScreenBase.GetEntitiesUnderPosition(coordinates); } return ImmutableList<IEntity>.Empty; }
public IList<IEntity> GetEntitiesUnderPosition(EntityCoordinates coordinates) { return GetEntitiesUnderPosition(coordinates.ToMap(EntityManager)); }
/// <summary> /// Return the slot position relative to a particular coordinates as "center". /// and perspective. /// </summary> /// <param name="coord"></param> /// <param name="reverseCoordinates"></param> /// <returns></returns> private Vector3 GetSlotPosition(EntityCoordinates coord, Vector2 center, bool reverseCoordinates) { Vector3 pos = Vector3.zero; float xDiff = coord.x - center.x; float yDiff = center.y - coord.y; pos.x = (xDiff * horizontalSpacing);// + (horizontalSpacing / 2); pos.z = (yDiff * verticalSpacing) - (verticalSpacing / 2); if (reverseCoordinates) { pos.x = -pos.x; pos.z = -pos.z; if (coord.x % 2 == 0) { pos.z -= verticalSpacing / 2; } } else { if (coord.x % 2 == 0) { pos.z += verticalSpacing / 2; } } return pos; }
/// <summary> /// Makes a string of text float up from a location on a grid. /// </summary> /// <param name="coordinates">Location on a grid that the message floats up from.</param> /// <param name="viewer">The client attached entity that the message is being sent to.</param> /// <param name="message">Text contents of the message.</param> public static void PopupMessage(this EntityCoordinates coordinates, EntityUid viewer, string message) { var popupSystem = EntitySystem.Get <SharedPopupSystem>(); popupSystem.PopupCoordinates(message, coordinates, Filter.Entities(viewer)); }
/// <summary> /// Will have two behaviors, either "uses" the used entity at range on the target entity if it is capable of accepting that action /// Or it will use the used entity itself on the position clicked, regardless of what was there /// </summary> public async Task <bool> InteractUsingRanged(EntityUid user, EntityUid used, EntityUid target, EntityCoordinates clickLocation, bool inRangeUnobstructed) { if (InteractDoBefore(user, used, inRangeUnobstructed ? target : null, clickLocation, false)) { return(true); } if (target != default) { var rangedMsg = new RangedInteractEvent(user, used, target, clickLocation); RaiseLocalEvent(target, rangedMsg); if (rangedMsg.Handled) { return(true); } var rangedInteractions = EntityManager.GetComponents <IRangedInteract>(target).ToList(); var rangedInteractionEventArgs = new RangedInteractEventArgs(user, used, clickLocation); // See if we have a ranged interaction foreach (var t in rangedInteractions) { // If an InteractUsingRanged returns a status completion we finish our interaction #pragma warning disable 618 if (t.RangedInteract(rangedInteractionEventArgs)) #pragma warning restore 618 { return(true); } } } return(await InteractDoAfter(user, used, inRangeUnobstructed?target : null, clickLocation, false)); }
private void PlaceAt(IMapGrid mapGrid, EntityCoordinates location, ushort tileId, float offset = 0) { mapGrid.SetTile(location.Offset(new Vector2(offset, offset)), new Tile(tileId)); EntitySystem.Get <AudioSystem>().PlayAtCoords("/Audio/Items/genhit.ogg", location, AudioHelpers.WithVariation(0.125f)); }
public RequestDecalPlacementEvent(Decal decal, EntityCoordinates coordinates) { Decal = decal; Coordinates = coordinates; }
/// <summary> /// Will have two behaviors, either "uses" the used entity at range on the target entity if it is capable of accepting that action /// Or it will use the used entity itself on the position clicked, regardless of what was there /// </summary> public override async Task <bool> InteractUsingRanged(EntityUid user, EntityUid used, EntityUid?target, EntityCoordinates clickLocation, bool inRangeUnobstructed) { // TODO PREDICTION move server-side interaction logic into the shared system for interaction prediction. if (InteractDoBefore(user, used, inRangeUnobstructed ? target : null, clickLocation, false)) { return(true); } if (target != null) { var rangedMsg = new RangedInteractEvent(user, used, target.Value, clickLocation); RaiseLocalEvent(target.Value, rangedMsg); if (rangedMsg.Handled) { return(true); } var rangedInteractions = AllComps <IRangedInteract>(target.Value).ToList(); var rangedInteractionEventArgs = new RangedInteractEventArgs(user, used, clickLocation); // See if we have a ranged interaction foreach (var t in rangedInteractions) { // If an InteractUsingRanged returns a status completion we finish our interaction #pragma warning disable 618 if (t.RangedInteract(rangedInteractionEventArgs)) #pragma warning restore 618 { return(true); } } } return(await InteractDoAfter(user, used, inRangeUnobstructed?target : null, clickLocation, false)); }
public EntityUid Spawn(string?prototype, EntityCoordinates coordinates) { return(EntityManager.SpawnEntity(prototype, coordinates)); }
public AfterInteractUsingEvent(EntityUid user, EntityUid used, EntityUid?target, EntityCoordinates clickLocation, bool canReach) : base(user, used, target, clickLocation, canReach) { }
public PerformTargetPointActionMessage(ActionType actionType, EntityCoordinates target) : base(actionType) { _target = target; }
public virtual bool HijackPlacementRequest(EntityCoordinates coordinates) { return(false); }
public DragDropRequestEvent(EntityCoordinates dropLocation, EntityUid dropped, EntityUid target) { DropLocation = dropLocation; Dropped = dropped; Target = target; }
public virtual bool HijackDeletion(EntityCoordinates coordinates) { return(false); }
public TeleportMessage(EntityCoordinates target) { Target = target; }
public override void AlignPlacementMode(ScreenCoordinates mouseScreen) { const float SearchBoxSize = 1.5f; // size of search box in meters MouseCoords = ScreenToCursorGrid(mouseScreen); var gridId = MouseCoords.GetGridId(pManager.EntityManager); IMapGrid?mapGrid = null; if (!gridId.IsValid() || !pManager.MapManager.TryGetGrid(gridId, out mapGrid)) { // create a box around the cursor var gridSearchBox = Box2.UnitCentered.Scale(SearchBoxSize).Translated(MouseCoords.Position); // find grids in search box var gridsInArea = pManager.MapManager.FindGridsIntersecting(MouseCoords.GetMapId(pManager.EntityManager), gridSearchBox); // find closest grid intersecting our search box. IMapGrid?closest = null; var distance = float.PositiveInfinity; var intersect = new Box2(); foreach (var grid in gridsInArea) { // figure out closest intersect var gridIntersect = gridSearchBox.Intersect(grid.WorldBounds); var gridDist = (gridIntersect.Center - MouseCoords.Position).LengthSquared; if (gridDist >= distance) { continue; } distance = gridDist; closest = grid; intersect = gridIntersect; } if (closest != null) // stick to existing grid { // round to nearest cardinal dir var normal = new Angle(MouseCoords.Position - intersect.Center).GetCardinalDir().ToVec(); // round coords to center of tile var tileIndices = closest.WorldToTile(intersect.Center); var tileCenterWorld = closest.GridTileToWorldPos(tileIndices); // move mouse one tile out along normal var newTilePos = tileCenterWorld + normal * closest.TileSize; MouseCoords = new EntityCoordinates(closest.GridEntityId, closest.WorldToLocal(newTilePos)); mapGrid = closest; } //else free place } if (mapGrid == null) { return; } CurrentTile = mapGrid.GetTileRef(MouseCoords); float tileSize = mapGrid.TileSize; //convert from ushort to float GridDistancing = tileSize; if (pManager.CurrentPermission !.IsTile) { MouseCoords = new EntityCoordinates(MouseCoords.EntityId, (CurrentTile.X + tileSize / 2, CurrentTile.Y + tileSize / 2)); }
public async Task GrantsAndRevokesItemActions() { var serverOptions = new ServerIntegrationOptions { ExtraPrototypes = Prototypes }; var clientOptions = new ClientIntegrationOptions { ExtraPrototypes = Prototypes }; var(client, server) = await StartConnectedServerClientPair(serverOptions : serverOptions, clientOptions : clientOptions); await server.WaitIdleAsync(); await client.WaitIdleAsync(); var serverPlayerManager = server.ResolveDependency <IPlayerManager>(); var serverEntManager = server.ResolveDependency <IEntityManager>(); var serverGameTiming = server.ResolveDependency <IGameTiming>(); var cooldown = Cooldowns.SecondsFromNow(30, serverGameTiming); ServerActionsComponent serverActionsComponent = null; ClientActionsComponent clientActionsComponent = null; IEntity serverPlayerEnt = null; IEntity serverFlashlight = null; await server.WaitAssertion(() => { serverPlayerEnt = serverPlayerManager.GetAllPlayers().Single().AttachedEntity; serverActionsComponent = serverPlayerEnt !.GetComponent <ServerActionsComponent>(); // spawn and give them an item that has actions serverFlashlight = serverEntManager.SpawnEntity("TestFlashlight", new EntityCoordinates(new EntityUid(1), (0, 0))); Assert.That(serverFlashlight.TryGetComponent <ItemActionsComponent>(out var itemActions)); // we expect this only to have a toggle light action initially var actionConfigs = itemActions.ActionConfigs.ToList(); Assert.That(actionConfigs.Count == 1); Assert.That(actionConfigs[0].ActionType == ItemActionType.ToggleLight); Assert.That(actionConfigs[0].Enabled); // grant an extra item action, before pickup, initially disabled itemActions.GrantOrUpdate(ItemActionType.DebugToggle, false); serverPlayerEnt.GetComponent <HandsComponent>().PutInHand(serverFlashlight.GetComponent <ItemComponent>(), false); // grant an extra item action, after pickup, with a cooldown itemActions.GrantOrUpdate(ItemActionType.DebugInstant, cooldown: cooldown); Assert.That(serverActionsComponent.TryGetItemActionStates(serverFlashlight.Uid, out var state)); // they should have been granted all 3 actions Assert.That(state.Count == 3); Assert.That(state.TryGetValue(ItemActionType.ToggleLight, out var toggleLightState)); Assert.That(toggleLightState.Equals(new ActionState(true))); Assert.That(state.TryGetValue(ItemActionType.DebugInstant, out var debugInstantState)); Assert.That(debugInstantState.Equals(new ActionState(true, cooldown: cooldown))); Assert.That(state.TryGetValue(ItemActionType.DebugToggle, out var debugToggleState)); Assert.That(debugToggleState.Equals(new ActionState(false))); }); await server.WaitRunTicks(5); await client.WaitRunTicks(5); // check that client has the actions, and toggle the light on via the action slot it was auto-assigned to var clientPlayerMgr = client.ResolveDependency <Robust.Client.Player.IPlayerManager>(); var clientUIMgr = client.ResolveDependency <IUserInterfaceManager>(); EntityUid clientFlashlight = default; await client.WaitAssertion(() => { var local = clientPlayerMgr.LocalPlayer; var controlled = local !.ControlledEntity; clientActionsComponent = controlled !.GetComponent <ClientActionsComponent>(); var lightEntry = clientActionsComponent.ItemActionStates() .Where(entry => entry.Value.ContainsKey(ItemActionType.ToggleLight)) .FirstOrNull(); clientFlashlight = lightEntry !.Value.Key; Assert.That(lightEntry, Is.Not.Null); Assert.That(lightEntry.Value.Value.TryGetValue(ItemActionType.ToggleLight, out var lightState)); Assert.That(lightState.Equals(new ActionState(true))); Assert.That(lightEntry.Value.Value.TryGetValue(ItemActionType.DebugInstant, out var debugInstantState)); Assert.That(debugInstantState.Equals(new ActionState(true, cooldown: cooldown))); Assert.That(lightEntry.Value.Value.TryGetValue(ItemActionType.DebugToggle, out var debugToggleState)); Assert.That(debugToggleState.Equals(new ActionState(false))); var actionsUI = clientUIMgr.StateRoot.Children.FirstOrDefault(c => c is ActionsUI) as ActionsUI; Assert.That(actionsUI, Is.Not.Null); var toggleLightSlot = actionsUI.Slots.FirstOrDefault(slot => slot.Action is ItemActionPrototype { ActionType: ItemActionType.ToggleLight }); Assert.That(toggleLightSlot, Is.Not.Null); clientActionsComponent.AttemptAction(toggleLightSlot); });
/// <summary> /// Resolves user interactions with objects. /// </summary> /// <remarks> /// Checks Whether combat mode is enabled and whether the user can actually interact with the given entity. /// </remarks> /// <param name="altInteract">Whether to use default or alternative interactions (usually as a result of /// alt+clicking). If combat mode is enabled, the alternative action is to perform the default non-combat /// interaction. Having an item in the active hand also disables alternative interactions.</param> public async void UserInteraction(EntityUid user, EntityCoordinates coordinates, EntityUid target, bool altInteract = false) { // TODO COMBAT Consider using alt-interact for advanced combat? maybe alt-interact disarms? if (!altInteract && EntityManager.TryGetComponent(user, out CombatModeComponent? combatMode) && combatMode.IsInCombatMode) { DoAttack(user, coordinates, false, target); return; } if (!ValidateInteractAndFace(user, coordinates)) { return; } if (!_actionBlockerSystem.CanInteract(user)) { return; } // Check if interacted entity is in the same container, the direct child, or direct parent of the user. // This is bypassed IF the interaction happened through an item slot (e.g., backpack UI) if (target != default && !user.IsInSameOrParentContainer(target) && !CanAccessViaStorage(user, target)) { Logger.WarningS("system.interaction", $"User entity named {EntityManager.GetComponent<MetaDataComponent>(user).EntityName} clicked on object {EntityManager.GetComponent<MetaDataComponent>(target).EntityName} that isn't the parent, child, or in the same container"); return; } // Verify user has a hand, and find what object they are currently holding in their active hand if (!EntityManager.TryGetComponent <HandsComponent?>(user, out var hands)) { return; } var item = hands.GetActiveHand?.Owner; // TODO: Replace with body interaction range when we get something like arm length or telekinesis or something. var inRangeUnobstructed = user.InRangeUnobstructed(coordinates, ignoreInsideBlocker: true); if (target == default || !inRangeUnobstructed) { if (item == null) { return; } if (!await InteractUsingRanged(user, item.Value, target, coordinates, inRangeUnobstructed) && !inRangeUnobstructed) { var message = Loc.GetString("interaction-system-user-interaction-cannot-reach"); user.PopupMessage(message); } return; } else { // We are close to the nearby object. if (altInteract) { // Perform alternative interactions, using context menu verbs. AltInteract(user, target); } else if (item != null && item != target) { // We are performing a standard interaction with an item, and the target isn't the same as the item // currently in our hand. We will use the item in our hand on the nearby object via InteractUsing await InteractUsing(user, item.Value, target, coordinates); } else if (item == null) { // Since our hand is empty we will use InteractHand/Activate InteractHand(user, target); } } }
public AtmosExposedUpdateEvent(EntityCoordinates coordinates, GasMixture mixture) { Coordinates = coordinates; GasMixture = mixture; }
public UnitSlot GetSlot(EntityCoordinates coord) { return unitSlots[coord.x, coord.y]; }