/// <summary> /// Handle adding keys to the ignition, give stuff the InVehicleComponent so it can't be picked /// up by people not in the vehicle. /// </summary> private void OnEntInserted(EntityUid uid, VehicleComponent component, EntInsertedIntoContainerMessage args) { var inVehicle = AddComp <InVehicleComponent>(args.Entity); inVehicle.Vehicle = component; if (_tagSystem.HasTag(args.Entity, "VehicleKey")) { // Return if the slot is not the key slot // That slot ID should be inherited from basevehicle in the .yml if (args.Container.ID != "key_slot") { return; } // This lets the vehicle move EnsureComp <SharedPlayerInputMoverComponent>(uid); // This lets the vehicle open doors if (component.HasRider) { _tagSystem.AddTag(uid, "DoorBumpOpener"); } component.HasKey = true; // Audiovisual feedback _ambientSound.SetAmbience(uid, true); } }
private void OnActivateUIAttempt(EntityUid uid, DroneComponent component, UserOpenActivatableUIAttemptEvent args) { if (!component.ApplyLaws) { return; } if (!_tagSystem.HasTag(args.Target, "DroneUsable")) { args.Cancel(); } }
/// <summary> /// For now pilots just interact with the console and can start piloting with wasd. /// </summary> private void HandleConsoleInteract(EntityUid uid, ShuttleConsoleComponent component, ActivateInWorldEvent args) { if (!_tags.HasTag(args.User, "CanPilot")) { return; } var pilotComponent = EntityManager.EnsureComponent <PilotComponent>(args.User); if (!component.Enabled) { args.User.PopupMessage($"Console is not powered."); return; } args.Handled = true; var console = pilotComponent.Console; if (console != null) { RemovePilot(pilotComponent); if (console == component) { return; } } AddPilot(args.User, component); }
private void HandleGeneratorCollide(EntityUid uid, ContainmentFieldGeneratorComponent component, StartCollideEvent args) { if (_tags.HasTag(args.OtherFixture.Body.Owner, "EmitterBolt")) { ReceivePower(6, component); } }
private void OnAfterInteract(EntityUid uid, EmagComponent component, AfterInteractEvent args) { if (!args.CanReach || args.Target == null) { return; } if (_tagSystem.HasTag(args.Target.Value, "EmagImmune")) { return; } if (component.Charges <= 0) { _popupSystem.PopupEntity(Loc.GetString("emag-no-charges"), args.User, Filter.Entities(args.User)); return; } var emaggedEvent = new GotEmaggedEvent(args.User); RaiseLocalEvent(args.Target.Value, emaggedEvent, false); if (emaggedEvent.Handled) { _popupSystem.PopupEntity(Loc.GetString("emag-success", ("target", Identity.Entity(args.Target.Value, EntityManager))), args.User, Filter.Entities(args.User), PopupType.Medium); _adminLogger.Add(LogType.Emag, LogImpact.High, $"{ToPrettyString(args.User):player} emagged {ToPrettyString(args.Target.Value):target}"); component.Charges--; return; } }
private bool TryPilot(EntityUid user, EntityUid uid) { if (!_tags.HasTag(user, "CanPilot") || !TryComp <ShuttleConsoleComponent>(uid, out var component) || !this.IsPowered(uid, EntityManager) || !Transform(uid).Anchored || !_blocker.CanInteract(user, uid)) { return(false); } var pilotComponent = EntityManager.EnsureComponent <PilotComponent>(user); var console = pilotComponent.Console; if (console != null) { RemovePilot(pilotComponent); if (console == component) { return(false); } } AddPilot(user, component); return(true); }
private void OnInteractUsing(EntityUid uid, PaperComponent paperComp, InteractUsingEvent args) { if (_tagSystem.HasTag(args.Used, "Write")) { if (!TryComp <ActorComponent>(args.User, out var actor)) { return; } paperComp.Mode = PaperAction.Write; UpdateUserInterface(uid, paperComp); _uiSystem.GetUiOrNull(uid, PaperUiKey.Key)?.Open(actor.PlayerSession); return; } // If a stamp, attempt to stamp paper if (TryComp <StampComponent>(args.Used, out var stampComp) && TryStamp(uid, stampComp.StampedName, stampComp.StampState, paperComp)) { // successfully stamped, play popup var stampPaperOtherMessage = Loc.GetString("paper-component-action-stamp-paper-other", ("user", args.User), ("target", args.Target), ("stamp", args.Used)); _popupSystem.PopupEntity(stampPaperOtherMessage, args.User, Filter.Pvs(args.User, entityManager: EntityManager).RemoveWhereAttachedEntity(puid => puid == args.User)); var stampPaperSelfMessage = Loc.GetString("paper-component-action-stamp-paper-self", ("target", args.Target), ("stamp", args.Used)); _popupSystem.PopupEntity(stampPaperSelfMessage, args.User, Filter.Entities(args.User)); } }
public bool Suicide(EntityUid victim) { // Checks to see if the CannotSuicide tag exits, ghosts instead. if (_tagSystem.HasTag(victim, "CannotSuicide")) { return(false); } // Checks to see if the player is dead. if (!TryComp <MobStateComponent>(victim, out var mobState) || _mobState.IsDead(victim, mobState)) { return(false); } _adminLogger.Add(LogType.Suicide, $"{EntityManager.ToPrettyString(victim):player} is committing suicide"); var suicideEvent = new SuicideEvent(victim); // If you are critical, you wouldn't be able to use your surroundings to suicide, so you do the default suicide if (!_mobState.IsCritical(victim, mobState)) { EnvironmentSuicideHandler(victim, suicideEvent); } DefaultSuicideHandler(victim, suicideEvent); ApplyDeath(victim, suicideEvent.Kind !.Value); return(true); }
public IEnumerable <EntityUid> GetAllPayloads(EntityUid uid, ContainerManagerComponent?contMan = null) { if (!Resolve(uid, ref contMan, false)) { yield break; } foreach (var container in contMan.Containers.Values) { foreach (var entity in container.ContainedEntities) { if (_tagSystem.HasTag(entity, "Payload")) { yield return(entity); } } } }
private void OnCaseTriggered(EntityUid uid, PayloadCaseComponent component, TriggerEvent args) { if (!TryComp(uid, out ContainerManagerComponent? contMan)) { return; } // Pass trigger event onto all contained payloads. Payload capacity configurable by construction graphs. foreach (var container in contMan.Containers.Values) { foreach (var entity in container.ContainedEntities) { if (_tagSystem.HasTag(entity, "Payload")) { RaiseLocalEvent(entity, args, false); } } } }
/// <summary> /// Handle adding keys to the ignition, give stuff the InVehicleComponent so it can't be picked /// up by people not in the vehicle. /// </summary> private void OnEntInserted(EntityUid uid, VehicleComponent component, EntInsertedIntoContainerMessage args) { if (args.Container.ID != KeySlot || !_tagSystem.HasTag(args.Entity, "VehicleKey")) { return; } // Enable vehicle var inVehicle = AddComp <InVehicleComponent>(args.Entity); inVehicle.Vehicle = component; component.HasKey = true; // Audiovisual feedback _ambientSound.SetAmbience(uid, true); _tagSystem.AddTag(uid, "DoorBumpOpener"); _modifier.RefreshMovementSpeedModifiers(uid); }
private void Recycle(RecyclerComponent component, EntityUid entity) { RecyclableComponent?recyclable = null; // Can only recycle things that are recyclable... And also check the safety of the thing to recycle. if (!_tags.HasTag(entity, "Recyclable") && (!TryComp(entity, out recyclable) || !recyclable.Safe && component.Safe)) { return; } // TODO: Prevent collision with recycled items // Mobs are a special case! if (CanGib(component, entity)) { Comp <SharedBodyComponent>(entity).Gib(true); Bloodstain(component); return; } if (recyclable == null) { QueueDel(entity); } else { Recycle(recyclable, component.Efficiency); } if (component.Sound != null && (_timing.CurTime - component.LastSound).TotalSeconds > RecyclerSoundCooldown) { SoundSystem.Play(Filter.Pvs(component.Owner, entityManager: EntityManager), component.Sound.GetSound(), component.Owner, AudioHelpers.WithVariation(0.01f).WithVolume(-3)); component.LastSound = _timing.CurTime; } }
/// <summary> /// Tries to throw the entity if it has a physics component, otherwise does nothing. /// </summary> /// <param name="entity">The entity being thrown.</param> /// <param name="direction">A vector pointing from the entity to its destination.</param> /// <param name="strength">How much the direction vector should be multiplied for velocity.</param> /// <param name="user"></param> /// <param name="pushbackRatio">The ratio of impulse applied to the thrower - defaults to 10 because otherwise it's not enough to properly recover from getting spaced</param> public void TryThrow( EntityUid uid, Vector2 direction, float strength = 1.0f, EntityUid?user = null, float pushbackRatio = 10.0f, PhysicsComponent?physics = null, TransformComponent?transform = null, EntityQuery <PhysicsComponent>?physicsQuery = null, EntityQuery <TransformComponent>?xformQuery = null) { if (strength <= 0 || direction == Vector2.Infinity || direction == Vector2.NaN || direction == Vector2.Zero) { return; } physicsQuery ??= GetEntityQuery <PhysicsComponent>(); if (physics == null && !physicsQuery.Value.TryGetComponent(uid, out physics)) { return; } if (physics.BodyType != BodyType.Dynamic) { Logger.Warning($"Tried to throw entity {ToPrettyString(uid)} but can't throw {physics.BodyType} bodies!"); return; } var comp = EnsureComp <ThrownItemComponent>(uid); comp.Thrower = user; // Give it a l'il spin. if (!_tagSystem.HasTag(uid, "NoSpinOnThrow")) { physics.ApplyAngularImpulse(ThrowAngularImpulse); } else { if (transform == null) { xformQuery ??= GetEntityQuery <TransformComponent>(); transform = xformQuery.Value.GetComponent(uid); } transform.LocalRotation = direction.ToWorldAngle() - Math.PI; } if (user != null) { _interactionSystem.ThrownInteraction(user.Value, uid); } var impulseVector = direction.Normalized * strength * physics.Mass; physics.ApplyLinearImpulse(impulseVector); // Estimate time to arrival so we can apply OnGround status and slow it much faster. var time = (direction / strength).Length; if (time < FlyTime) { physics.BodyStatus = BodyStatus.OnGround; _thrownSystem.LandComponent(comp); } else { physics.BodyStatus = BodyStatus.InAir; Timer.Spawn(TimeSpan.FromSeconds(time - FlyTime), () => { if (physics.Deleted) { return; } physics.BodyStatus = BodyStatus.OnGround; _thrownSystem.LandComponent(comp); }); } // Give thrower an impulse in the other direction if (user != null && pushbackRatio > 0.0f && physicsQuery.Value.TryGetComponent(user.Value, out var userPhysics)) { var msg = new ThrowPushbackAttemptEvent(); RaiseLocalEvent(physics.Owner, msg, false); if (!msg.Cancelled) { userPhysics.ApplyLinearImpulse(-impulseVector * pushbackRatio); } } }
public bool TryDoElectrifiedAct(EntityUid uid, EntityUid targetUid, float siemens = 1, ElectrifiedComponent?electrified = null, NodeContainerComponent?nodeContainer = null, TransformComponent?transform = null) { if (!Resolve(uid, ref electrified, ref transform, false)) { return(false); } if (!electrified.Enabled) { return(false); } if (electrified.NoWindowInTile) { foreach (var entity in transform.Coordinates.GetEntitiesInTile( LookupFlags.Approximate | LookupFlags.IncludeAnchored, _entityLookup)) { if (_tagSystem.HasTag(entity, "Window")) { return(false); } } } siemens *= electrified.SiemensCoefficient; if (!DoCommonElectrocutionAttempt(targetUid, uid, ref siemens) || siemens <= 0) { return(false); // If electrocution would fail, do nothing. } var targets = new List <(EntityUid entity, int depth)>(); GetChainedElectrocutionTargets(targetUid, targets); if (!electrified.RequirePower) { var lastRet = true; for (var i = targets.Count - 1; i >= 0; i--) { var(entity, depth) = targets[i]; lastRet = TryDoElectrocution( entity, uid, (int)(electrified.ShockDamage * MathF.Pow(RecursiveDamageMultiplier, depth)), TimeSpan.FromSeconds(electrified.ShockTime * MathF.Pow(RecursiveTimeMultiplier, depth)), true, electrified.SiemensCoefficient); } return(lastRet); } if (!Resolve(uid, ref nodeContainer, false)) { return(false); } var node = TryNode(electrified.HighVoltageNode) ?? TryNode(electrified.MediumVoltageNode) ?? TryNode(electrified.LowVoltageNode); if (node == null) { return(false); } var(damageMult, timeMult) = node.NodeGroupID switch { NodeGroupID.HVPower => (electrified.HighVoltageDamageMultiplier, electrified.HighVoltageTimeMultiplier), NodeGroupID.MVPower => (electrified.MediumVoltageDamageMultiplier, electrified.MediumVoltageTimeMultiplier), _ => (1f, 1f) }; { var lastRet = true; for (var i = targets.Count - 1; i >= 0; i--) { var(entity, depth) = targets[i]; lastRet = TryDoElectrocutionPowered( entity, uid, node, (int)(electrified.ShockDamage * MathF.Pow(RecursiveDamageMultiplier, depth) * damageMult), TimeSpan.FromSeconds(electrified.ShockTime * MathF.Pow(RecursiveTimeMultiplier, depth) * timeMult), true, electrified.SiemensCoefficient); } return(lastRet); } Node?TryNode(string?id) { if (id != null && nodeContainer.TryGetNode <Node>(id, out var tryNode) && tryNode.NodeGroup is IBasePowerNet { NetworkNode : { LastAvailableSupplySum : > 0 } }) { return(tryNode); } return(null); }
// Handles logic for our different types of valid target. // Checks for conditions that would prevent a doAfter from starting. private void HandleDoAfter(EntityUid user, EntityUid used, EntityUid target, AbsorbentComponent component, FixedPoint2 currentVolume, FixedPoint2 availableVolume) { // Below variables will be set within this function depending on what kind of target was clicked. // They will be passed to the OnTransferComplete if the doAfter succeeds. EntityUid donor; EntityUid acceptor; string donorSolutionName; string acceptorSolutionName; FixedPoint2 transferAmount; var delay = 1.0f; //default do_after delay in seconds. string msg; SoundSpecifier sfx; // For our purposes, if our target has a PuddleComponent, treat it as a puddle above all else. if (TryComp <PuddleComponent>(target, out var puddle)) { // These return conditions will abort BEFORE the do_after is called: if (!_solutionSystem.TryGetSolution(target, puddle.SolutionName, out var puddleSolution) || // puddle Solution is null (puddleSolution.TotalVolume <= 0)) // puddle is completely empty { return; } else if (availableVolume < 0) // mop is completely full { msg = "mopping-system-tool-full"; user.PopupMessage(user, Loc.GetString(msg, ("used", used))); // play message now because we are aborting. return; } // adding to puddles else if (puddleSolution.TotalVolume < component.MopLowerLimit && // if the puddle is too small for the tool to effectively absorb any more solution from it currentVolume > 0) // tool needs a solution to dilute the puddle with. { // Dilutes the puddle with some solution from the tool transferAmount = FixedPoint2.Max(component.ResidueAmount, currentVolume); TryTransfer(used, target, "absorbed", puddle.SolutionName, transferAmount); // Complete the transfer right away, with no doAfter. sfx = component.TransferSound; SoundSystem.Play(sfx.GetSound(), Filter.Pvs(user), used); // Give instant feedback for diluting puddle, so that it's clear that the player is adding to the puddle (as opposed to other behaviours, which have a doAfter). msg = "mopping-system-puddle-diluted"; user.PopupMessage(user, Loc.GetString(msg)); // play message now because we are aborting. return; // Do not begin a doAfter. } else { // Taking from puddles: // Determine transferAmount: transferAmount = FixedPoint2.Min(component.PickupAmount, puddleSolution.TotalVolume, availableVolume); // TODO: consider onelining this with the above, using additional args on Min()? if ((puddleSolution.TotalVolume - transferAmount) < component.MopLowerLimit) // If the transferAmount would bring the puddle below the MopLowerLimit { transferAmount = puddleSolution.TotalVolume - component.MopLowerLimit; // Then the transferAmount should bring the puddle down to the MopLowerLimit exactly } donor = target; // the puddle Uid donorSolutionName = puddle.SolutionName; acceptor = used; // the mop/tool Uid acceptorSolutionName = "absorbed"; // by definition on AbsorbentComponent // Set delay/popup/sound if nondefault. Popup and sound will only play on a successful doAfter. delay = (component.PickupAmount.Float() / 10.0f) * component.MopSpeed; // Delay should scale with PickupAmount, which represents the maximum we can pick up per click. msg = "mopping-system-puddle-success"; sfx = component.PickupSound; DoMopInteraction(user, used, target, donor, acceptor, component, donorSolutionName, acceptorSolutionName, transferAmount, delay, msg, sfx); } } else if ((TryComp <RefillableSolutionComponent>(target, out var refillable)) && // We can put solution from the tool into the target (currentVolume > 0)) // And the tool is wet { // These return conditions will abort BEFORE the do_after is called: if (!_solutionSystem.TryGetRefillableSolution(target, out var refillableSolution)) // refillable Solution is null { return; } else if (refillableSolution.AvailableVolume <= 0) // target container is full (liquid destination) { msg = "mopping-system-target-container-full"; user.PopupMessage(user, Loc.GetString(msg, ("target", target))); // play message now because we are aborting. return; } else if (refillableSolution.MaxVolume <= FixedPoint2.New(20)) // target container is too small (e.g. syringe) { msg = "mopping-system-target-container-too-small"; user.PopupMessage(user, Loc.GetString(msg, ("target", target))); // play message now because we are aborting. return; } else { // Determine transferAmount if (_tagSystem.HasTag(used, "Mop") && // if the tool used is a literal mop (and not a sponge, rag, etc.) !_tagSystem.HasTag(target, "Wringer")) // and if the target does not have a wringer for properly drying the mop { delay = 5.0f; // Should take much longer if you don't have a wringer if ((currentVolume / (currentVolume + availableVolume)) > 0.25) // mop is more than one-quarter full { transferAmount = FixedPoint2.Min(refillableSolution.AvailableVolume, currentVolume * 0.6); // squeeze up to 60% of the solution from the mop. msg = "mopping-system-hand-squeeze-little-wet"; if ((currentVolume / (currentVolume + availableVolume)) > 0.5) // if the mop is more than half full { msg = "mopping-system-hand-squeeze-still-wet"; // overwrites the above } } else // mop is less than one-quarter full { transferAmount = FixedPoint2.Min(refillableSolution.AvailableVolume, currentVolume); // squeeze remainder of solution from the mop. msg = "mopping-system-hand-squeeze-dry"; } } else { transferAmount = FixedPoint2.Min(refillableSolution.AvailableVolume, currentVolume); //Transfer all liquid from the tool to the container, but only if it will fit. msg = "mopping-system-refillable-success"; delay = 1.0f; } donor = used; // the mop/tool Uid donorSolutionName = "absorbed"; // by definition on AbsorbentComponent acceptor = target; // the refillable container's Uid acceptorSolutionName = refillable.Solution; // Set delay/popup/sound if nondefault. Popup and sound will only play on a successful doAfter. sfx = component.TransferSound; DoMopInteraction(user, used, target, donor, acceptor, component, donorSolutionName, acceptorSolutionName, transferAmount, delay, msg, sfx); } }
private void OnInteractUsing(EntityUid uid, PlantHolderComponent component, InteractUsingEvent args) { if (TryComp(args.Used, out SeedComponent? seeds)) { if (component.Seed == null) { if (!_botanySystem.TryGetSeed(seeds, out var seed)) { return; } _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-plant-success-message", ("seedName", seed.Name), ("seedNoun", seed.Noun)), Filter.Entities(args.User), PopupType.Medium); component.Seed = seed; component.Dead = false; component.Age = 1; component.Health = component.Seed.Endurance; component.LastCycle = _gameTiming.CurTime; EntityManager.QueueDeleteEntity(args.Used); component.CheckLevelSanity(); component.UpdateSprite(); return; } _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-already-seeded-message", ("name", Comp <MetaDataComponent>(uid).EntityName)), Filter.Entities(args.User), PopupType.Medium); return; } if (_tagSystem.HasTag(args.Used, "Hoe")) { if (component.WeedLevel > 0) { _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-remove-weeds-message", ("name", Comp <MetaDataComponent>(uid).EntityName)), Filter.Entities(args.User), PopupType.Medium); _popupSystem.PopupEntity(Loc.GetString("plant-holder-component-remove-weeds-others-message", ("otherName", Comp <MetaDataComponent>(args.User).EntityName)), uid, Filter.PvsExcept(args.User)); component.WeedLevel = 0; component.UpdateSprite(); } else { _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-no-weeds-message"), Filter.Entities(args.User)); } return; } if (_tagSystem.HasTag(args.Used, "Shovel")) { if (component.Seed != null) { _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-remove-plant-message", ("name", Comp <MetaDataComponent>(uid).EntityName)), Filter.Entities(args.User), PopupType.Medium); _popupSystem.PopupEntity(Loc.GetString("plant-holder-component-remove-plant-others-message", ("name", Comp <MetaDataComponent>(args.User).EntityName)), uid, Filter.PvsExcept(args.User)); component.RemovePlant(); } else { _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-no-plant-message", ("name", Comp <MetaDataComponent>(uid).EntityName)), Filter.Entities(args.User)); } return; } if (_solutionSystem.TryGetDrainableSolution(args.Used, out var solution) && _solutionSystem.TryGetSolution(uid, component.SoilSolutionName, out var targetSolution) && TryComp(args.Used, out SprayComponent? spray)) { var amount = FixedPoint2.New(1); var targetEntity = uid; var solutionEntity = args.Used; SoundSystem.Play(spray.SpraySound.GetSound(), Filter.Pvs(args.Used), args.Used, AudioHelpers.WithVariation(0.125f)); var split = _solutionSystem.Drain(solutionEntity, solution, amount); if (split.TotalVolume == 0) { _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-no-plant-message", ("owner", args.Used)), Filter.Entities(args.User)); return; } _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-spray-message", ("owner", uid), ("amount", split.TotalVolume)), Filter.Entities(args.User), PopupType.Medium); _solutionSystem.TryAddSolution(targetEntity, targetSolution, split); component.ForceUpdateByExternalCause(); return; } if (_tagSystem.HasTag(args.Used, "PlantSampleTaker")) { if (component.Seed == null) { _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-nothing-to-sample-message"), Filter.Entities(args.User)); return; } if (component.Sampled) { _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-already-sampled-message"), Filter.Entities(args.User)); return; } if (component.Dead) { _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-dead-plant-message"), Filter.Entities(args.User)); return; } component.Seed.Unique = false; var seed = _botanySystem.SpawnSeedPacket(component.Seed, Transform(args.User).Coordinates); seed.RandomOffset(0.25f); _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-take-sample-message", ("seedName", component.Seed.DisplayName)), Filter.Entities(args.User)); component.Health -= (_random.Next(3, 5) * 10); if (_random.Prob(0.3f)) { component.Sampled = true; } // Just in case. component.CheckLevelSanity(); component.ForceUpdateByExternalCause(); return; } if (HasComp <SharpComponent>(args.Used)) { component.DoHarvest(args.User); } if (TryComp <ProduceComponent?>(args.Used, out var produce)) { _popupSystem.PopupCursor(Loc.GetString("plant-holder-component-compost-message", ("owner", uid), ("usingItem", args.Used)), Filter.Entities(args.User), PopupType.Medium); _popupSystem.PopupEntity(Loc.GetString("plant-holder-component-compost-others-message", ("user", Identity.Entity(args.User, EntityManager)), ("usingItem", args.Used), ("owner", uid)), uid, Filter.PvsExcept(args.User)); if (_solutionSystem.TryGetSolution(args.Used, produce.SolutionName, out var solution2)) { // This deliberately discards overfill. _solutionSystem.TryAddSolution(args.Used, solution2, _solutionSystem.SplitSolution(args.Used, solution2, solution2.TotalVolume)); component.ForceUpdateByExternalCause(); } EntityManager.QueueDeleteEntity(args.Used); } }
private void OnInteractUsing(EntityUid uid, MachineFrameComponent component, InteractUsingEvent args) { if (!component.HasBoard && TryComp <MachineBoardComponent?>(args.Used, out var machineBoard)) { if (args.Used.TryRemoveFromContainer()) { // Valid board! component.BoardContainer.Insert(args.Used); // Setup requirements and progress... ResetProgressAndRequirements(component, machineBoard); if (TryComp <AppearanceComponent?>(uid, out var appearance)) { appearance.SetData(MachineFrameVisuals.State, 2); } if (TryComp(uid, out ConstructionComponent? construction)) { // So prying the components off works correctly. _construction.ResetEdge(uid, construction); } } } else if (component.HasBoard) { if (TryComp <MachinePartComponent>(args.Used, out var machinePart)) { if (!component.Requirements.ContainsKey(machinePart.PartType)) { return; } if (component.Progress[machinePart.PartType] != component.Requirements[machinePart.PartType] && args.Used.TryRemoveFromContainer() && component.PartContainer.Insert(args.Used)) { component.Progress[machinePart.PartType]++; args.Handled = true; return; } } if (TryComp <StackComponent?>(args.Used, out var stack)) { var type = stack.StackTypeId; if (!component.MaterialRequirements.ContainsKey(type)) { return; } if (component.MaterialProgress[type] == component.MaterialRequirements[type]) { return; } var needed = component.MaterialRequirements[type] - component.MaterialProgress[type]; var count = stack.Count; if (count < needed) { if (!component.PartContainer.Insert(stack.Owner)) { return; } component.MaterialProgress[type] += count; args.Handled = true; return; } var splitStack = _stack.Split(args.Used, needed, Comp <TransformComponent>(uid).Coordinates, stack); if (splitStack == null) { return; } if (!component.PartContainer.Insert(splitStack.Value)) { return; } component.MaterialProgress[type] += needed; args.Handled = true; return; } foreach (var(compName, info) in component.ComponentRequirements) { if (component.ComponentProgress[compName] >= info.Amount) { continue; } var registration = _factory.GetRegistration(compName); if (!HasComp(args.Used, registration.Type)) { continue; } if (!args.Used.TryRemoveFromContainer() || !component.PartContainer.Insert(args.Used)) { continue; } component.ComponentProgress[compName]++; args.Handled = true; return; } foreach (var(tagName, info) in component.TagRequirements) { if (component.TagProgress[tagName] >= info.Amount) { continue; } if (!_tag.HasTag(args.Used, tagName)) { continue; } if (!args.Used.TryRemoveFromContainer() || !component.PartContainer.Insert(args.Used)) { continue; } component.TagProgress[tagName]++; args.Handled = true; return; } } }
private bool IsRCDStillValid(RCDComponent rcd, AfterInteractEvent eventArgs, IMapGrid mapGrid, TileRef tile, RcdMode startingMode) { //Less expensive checks first. Failing those ones, we need to check that the tile isn't obstructed. if (rcd.CurrentAmmo <= 0) { _popup.PopupEntity(Loc.GetString("rcd-component-no-ammo-message"), rcd.Owner, Filter.Entities(eventArgs.User)); return(false); } if (rcd.Mode != startingMode) { return(false); } var unobstructed = eventArgs.Target == null ? _interactionSystem.InRangeUnobstructed(eventArgs.User, mapGrid.GridTileToWorld(tile.GridIndices), popup : true) : _interactionSystem.InRangeUnobstructed(eventArgs.User, eventArgs.Target.Value, popup: true); if (!unobstructed) { return(false); } switch (rcd.Mode) { //Floor mode just needs the tile to be a space tile (subFloor) case RcdMode.Floors: if (!tile.Tile.IsEmpty) { _popup.PopupEntity(Loc.GetString("rcd-component-cannot-build-floor-tile-not-empty-message"), rcd.Owner, Filter.Entities(eventArgs.User)); return(false); } return(true); //We don't want to place a space tile on something that's already a space tile. Let's do the inverse of the last check. case RcdMode.Deconstruct: if (tile.Tile.IsEmpty) { return(false); } //They tried to decon a turf but the turf is blocked if (eventArgs.Target == null && tile.IsBlockedTurf(true)) { _popup.PopupEntity(Loc.GetString("rcd-component-tile-obstructed-message"), rcd.Owner, Filter.Entities(eventArgs.User)); return(false); } //They tried to decon a non-turf but it's not in the whitelist if (eventArgs.Target != null && !_tagSystem.HasTag(eventArgs.Target.Value, "RCDDeconstructWhitelist")) { _popup.PopupEntity(Loc.GetString("rcd-component-deconstruct-target-not-on-whitelist-message"), rcd.Owner, Filter.Entities(eventArgs.User)); return(false); } return(true); //Walls are a special behaviour, and require us to build a new object with a transform rather than setting a grid tile, thus we early return to avoid the tile set code. case RcdMode.Walls: if (tile.Tile.IsEmpty) { _popup.PopupEntity(Loc.GetString("rcd-component-cannot-build-wall-tile-not-empty-message"), rcd.Owner, Filter.Entities(eventArgs.User)); return(false); } if (tile.IsBlockedTurf(true)) { _popup.PopupEntity(Loc.GetString("rcd-component-tile-obstructed-message"), rcd.Owner, Filter.Entities(eventArgs.User)); return(false); } return(true); case RcdMode.Airlock: if (tile.Tile.IsEmpty) { _popup.PopupEntity(Loc.GetString("rcd-component-cannot-build-airlock-tile-not-empty-message"), rcd.Owner, Filter.Entities(eventArgs.User)); return(false); } if (tile.IsBlockedTurf(true)) { _popup.PopupEntity(Loc.GetString("rcd-component-tile-obstructed-message"), rcd.Owner, Filter.Entities(eventArgs.User)); return(false); } return(true); default: return(false); //I don't know why this would happen, but sure I guess. Get out of here invalid state! } }
/// <summary> /// Get all of the entities in an area for displaying on the context menu. /// </summary> public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true)] out List <EntityUid>?result) { result = null; if (_stateManager.CurrentState is not GameScreenBase gameScreenBase) { return(false); } var player = _playerManager.LocalPlayer?.ControlledEntity; if (player == null) { return(false); } // If FOV drawing is disabled, we will modify the visibility option to ignore visiblity checks. var visibility = _eyeManager.CurrentEye.DrawFov ? Visibility : Visibility | MenuVisibility.NoFov; // Do we have to do FoV checks? if ((visibility & MenuVisibility.NoFov) == 0) { var entitiesUnderMouse = gameScreenBase.GetEntitiesUnderPosition(targetPos); bool Predicate(EntityUid e) => e == player || entitiesUnderMouse.Contains(e); if (!_examineSystem.CanExamine(player.Value, targetPos, Predicate)) { return(false); } } // Get entities var entities = _entityLookup.GetEntitiesInRange(targetPos.MapId, targetPos.Position, EntityMenuLookupSize) .ToList(); if (entities.Count == 0) { return(false); } if (visibility == MenuVisibility.All) { result = entities; return(true); } // remove any entities in containers if ((visibility & MenuVisibility.InContainer) == 0) { foreach (var entity in entities.ToList()) { if (!ContainerSystem.IsInSameOrTransparentContainer(player.Value, entity)) { entities.Remove(entity); } } } // remove any invisible entities if ((visibility & MenuVisibility.Invisible) == 0) { foreach (var entity in entities.ToList()) { if (!EntityManager.TryGetComponent(entity, out ISpriteComponent? spriteComponent) || !spriteComponent.Visible) { entities.Remove(entity); continue; } if (_tagSystem.HasTag(entity, "HideContextMenu")) { entities.Remove(entity); } } } // Remove any entities that do not have LOS if ((visibility & MenuVisibility.NoFov) == 0) { var playerPos = EntityManager.GetComponent <TransformComponent>(player.Value).MapPosition; foreach (var entity in entities.ToList()) { if (!ExamineSystemShared.InRangeUnOccluded( playerPos, EntityManager.GetComponent <TransformComponent>(entity).MapPosition, ExamineSystemShared.ExamineRange, null)) { entities.Remove(entity); } } } if (entities.Count == 0) { return(false); } result = entities; return(true); }