| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- using System.Linq;
- using System.Numerics;
- using Content.Server.Cargo.Systems;
- using Content.Server.Emp;
- using Content.Server.Power.Components;
- using Content.Server.Power.EntitySystems;
- using Content.Shared.Damage;
- using Content.Shared.Destructible;
- using Content.Shared.DoAfter;
- using Content.Shared.Emp;
- using Content.Shared.Popups;
- using Content.Shared.Power;
- using Content.Shared.Throwing;
- using Content.Shared.UserInterface;
- using Content.Shared.VendingMachines;
- using Content.Shared.Wall;
- using Robust.Shared.Audio;
- using Robust.Shared.Prototypes;
- using Robust.Shared.Random;
- using Robust.Shared.Timing;
- namespace Content.Server.VendingMachines
- {
- public sealed class VendingMachineSystem : SharedVendingMachineSystem
- {
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly PricingSystem _pricing = default!;
- [Dependency] private readonly ThrowingSystem _throwingSystem = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
- private const float WallVendEjectDistanceFromWall = 1f;
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<VendingMachineComponent, PowerChangedEvent>(OnPowerChanged);
- SubscribeLocalEvent<VendingMachineComponent, BreakageEventArgs>(OnBreak);
- SubscribeLocalEvent<VendingMachineComponent, DamageChangedEvent>(OnDamageChanged);
- SubscribeLocalEvent<VendingMachineComponent, PriceCalculationEvent>(OnVendingPrice);
- SubscribeLocalEvent<VendingMachineComponent, EmpPulseEvent>(OnEmpPulse);
- SubscribeLocalEvent<VendingMachineComponent, ActivatableUIOpenAttemptEvent>(OnActivatableUIOpenAttempt);
- SubscribeLocalEvent<VendingMachineComponent, VendingMachineSelfDispenseEvent>(OnSelfDispense);
- SubscribeLocalEvent<VendingMachineComponent, RestockDoAfterEvent>(OnDoAfter);
- SubscribeLocalEvent<VendingMachineRestockComponent, PriceCalculationEvent>(OnPriceCalculation);
- }
- private void OnVendingPrice(EntityUid uid, VendingMachineComponent component, ref PriceCalculationEvent args)
- {
- var price = 0.0;
- foreach (var entry in component.Inventory.Values)
- {
- if (!PrototypeManager.TryIndex<EntityPrototype>(entry.ID, out var proto))
- {
- Log.Error($"Unable to find entity prototype {entry.ID} on {ToPrettyString(uid)} vending.");
- continue;
- }
- price += entry.Amount * _pricing.GetEstimatedPrice(proto);
- }
- args.Price += price;
- }
- protected override void OnMapInit(EntityUid uid, VendingMachineComponent component, MapInitEvent args)
- {
- base.OnMapInit(uid, component, args);
- if (HasComp<ApcPowerReceiverComponent>(uid))
- {
- TryUpdateVisualState((uid, component));
- }
- }
- private void OnActivatableUIOpenAttempt(EntityUid uid, VendingMachineComponent component, ActivatableUIOpenAttemptEvent args)
- {
- if (component.Broken)
- args.Cancel();
- }
- private void OnPowerChanged(EntityUid uid, VendingMachineComponent component, ref PowerChangedEvent args)
- {
- TryUpdateVisualState((uid, component));
- }
- private void OnBreak(EntityUid uid, VendingMachineComponent vendComponent, BreakageEventArgs eventArgs)
- {
- vendComponent.Broken = true;
- TryUpdateVisualState((uid, vendComponent));
- }
- private void OnDamageChanged(EntityUid uid, VendingMachineComponent component, DamageChangedEvent args)
- {
- if (!args.DamageIncreased && component.Broken)
- {
- component.Broken = false;
- TryUpdateVisualState((uid, component));
- return;
- }
- if (component.Broken || component.DispenseOnHitCoolingDown ||
- component.DispenseOnHitChance == null || args.DamageDelta == null)
- return;
- if (args.DamageIncreased && args.DamageDelta.GetTotal() >= component.DispenseOnHitThreshold &&
- _random.Prob(component.DispenseOnHitChance.Value))
- {
- if (component.DispenseOnHitCooldown != null)
- {
- component.DispenseOnHitEnd = Timing.CurTime + component.DispenseOnHitCooldown.Value;
- }
- EjectRandom(uid, throwItem: true, forceEject: true, component);
- }
- }
- private void OnSelfDispense(EntityUid uid, VendingMachineComponent component, VendingMachineSelfDispenseEvent args)
- {
- if (args.Handled)
- return;
- args.Handled = true;
- EjectRandom(uid, throwItem: true, forceEject: false, component);
- }
- private void OnDoAfter(EntityUid uid, VendingMachineComponent component, DoAfterEvent args)
- {
- if (args.Handled || args.Cancelled || args.Args.Used == null)
- return;
- if (!TryComp<VendingMachineRestockComponent>(args.Args.Used, out var restockComponent))
- {
- Log.Error($"{ToPrettyString(args.Args.User)} tried to restock {ToPrettyString(uid)} with {ToPrettyString(args.Args.Used.Value)} which did not have a VendingMachineRestockComponent.");
- return;
- }
- TryRestockInventory(uid, component);
- Popup.PopupEntity(Loc.GetString("vending-machine-restock-done", ("this", args.Args.Used), ("user", args.Args.User), ("target", uid)), args.Args.User, PopupType.Medium);
- Audio.PlayPvs(restockComponent.SoundRestockDone, uid, AudioParams.Default.WithVolume(-2f).WithVariation(0.2f));
- Del(args.Args.Used.Value);
- args.Handled = true;
- }
- /// <summary>
- /// Sets the <see cref="VendingMachineComponent.CanShoot"/> property of the vending machine.
- /// </summary>
- public void SetShooting(EntityUid uid, bool canShoot, VendingMachineComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
- component.CanShoot = canShoot;
- }
- /// <summary>
- /// Sets the <see cref="VendingMachineComponent.Contraband"/> property of the vending machine.
- /// </summary>
- public void SetContraband(EntityUid uid, bool contraband, VendingMachineComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
- component.Contraband = contraband;
- Dirty(uid, component);
- }
- /// <summary>
- /// Ejects a random item from the available stock. Will do nothing if the vending machine is empty.
- /// </summary>
- /// <param name="uid"></param>
- /// <param name="throwItem">Whether to throw the item in a random direction after dispensing it.</param>
- /// <param name="forceEject">Whether to skip the regular ejection checks and immediately dispense the item without animation.</param>
- /// <param name="vendComponent"></param>
- public void EjectRandom(EntityUid uid, bool throwItem, bool forceEject = false, VendingMachineComponent? vendComponent = null)
- {
- if (!Resolve(uid, ref vendComponent))
- return;
- var availableItems = GetAvailableInventory(uid, vendComponent);
- if (availableItems.Count <= 0)
- return;
- var item = _random.Pick(availableItems);
- if (forceEject)
- {
- vendComponent.NextItemToEject = item.ID;
- vendComponent.ThrowNextItem = throwItem;
- var entry = GetEntry(uid, item.ID, item.Type, vendComponent);
- if (entry != null)
- entry.Amount--;
- EjectItem(uid, vendComponent, forceEject);
- }
- else
- {
- TryEjectVendorItem(uid, item.Type, item.ID, throwItem, user: null, vendComponent: vendComponent);
- }
- }
- protected override void EjectItem(EntityUid uid, VendingMachineComponent? vendComponent = null, bool forceEject = false)
- {
- if (!Resolve(uid, ref vendComponent))
- return;
- // No need to update the visual state because we never changed it during a forced eject
- if (!forceEject)
- TryUpdateVisualState((uid, vendComponent));
- if (string.IsNullOrEmpty(vendComponent.NextItemToEject))
- {
- vendComponent.ThrowNextItem = false;
- return;
- }
- // Default spawn coordinates
- var spawnCoordinates = Transform(uid).Coordinates;
- //Make sure the wallvends spawn outside of the wall.
- if (TryComp<WallMountComponent>(uid, out var wallMountComponent))
- {
- var offset = wallMountComponent.Direction.ToWorldVec() * WallVendEjectDistanceFromWall;
- spawnCoordinates = spawnCoordinates.Offset(offset);
- }
- var ent = Spawn(vendComponent.NextItemToEject, spawnCoordinates);
- if (vendComponent.ThrowNextItem)
- {
- var range = vendComponent.NonLimitedEjectRange;
- var direction = new Vector2(_random.NextFloat(-range, range), _random.NextFloat(-range, range));
- _throwingSystem.TryThrow(ent, direction, vendComponent.NonLimitedEjectForce);
- }
- vendComponent.NextItemToEject = null;
- vendComponent.ThrowNextItem = false;
- }
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
- var disabled = EntityQueryEnumerator<EmpDisabledComponent, VendingMachineComponent>();
- while (disabled.MoveNext(out var uid, out _, out var comp))
- {
- if (comp.NextEmpEject < _timing.CurTime)
- {
- EjectRandom(uid, true, false, comp);
- comp.NextEmpEject += (5 * comp.EjectDelay);
- }
- }
- }
- public void TryRestockInventory(EntityUid uid, VendingMachineComponent? vendComponent = null)
- {
- if (!Resolve(uid, ref vendComponent))
- return;
- RestockInventoryFromPrototype(uid, vendComponent);
- Dirty(uid, vendComponent);
- TryUpdateVisualState((uid, vendComponent));
- }
- private void OnPriceCalculation(EntityUid uid, VendingMachineRestockComponent component, ref PriceCalculationEvent args)
- {
- List<double> priceSets = new();
- // Find the most expensive inventory and use that as the highest price.
- foreach (var vendingInventory in component.CanRestock)
- {
- double total = 0;
- if (PrototypeManager.TryIndex(vendingInventory, out VendingMachineInventoryPrototype? inventoryPrototype))
- {
- foreach (var (item, amount) in inventoryPrototype.StartingInventory)
- {
- if (PrototypeManager.TryIndex(item, out EntityPrototype? entity))
- total += _pricing.GetEstimatedPrice(entity) * amount;
- }
- }
- priceSets.Add(total);
- }
- args.Price += priceSets.Max();
- }
- private void OnEmpPulse(EntityUid uid, VendingMachineComponent component, ref EmpPulseEvent args)
- {
- if (!component.Broken && this.IsPowered(uid, EntityManager))
- {
- args.Affected = true;
- args.Disabled = true;
- component.NextEmpEject = _timing.CurTime;
- }
- }
- }
- }
|