using System.Linq; using Content.Server._Horizon.Planet; using Content.Server._NF.Cargo.Systems; using Content.Server.Access.Systems; using Content.Server.Cargo.Systems; using Content.Server.CartridgeLoader; using Content.Server.Hands.Systems; using Content.Server.Popups; using Content.Shared._Horizon.Expeditions; using Content.Shared.Cargo; using Content.Shared.CartridgeLoader; using Content.Shared.PDA; using Content.Shared.Tag; using Robust.Server.Audio; using Robust.Server.Containers; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; namespace Content.Server._Horizon.Expeditions; public sealed class ExpeditionGoalsSystem : EntitySystem { [Dependency] private readonly IPrototypeManager _proto = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!; [Dependency] private readonly PlanetSystem _planet = default!; [Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IdCardSystem _idCard = default!; [Dependency] private readonly CartridgeLoaderSystem _cartridgeLoader = default!; [Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly HandsSystem _hands = default!; private Dictionary, Dictionary> _goals = new(); private Dictionary _claimedGoals = new(); private int _nextId = 1; private TimeSpan _nextOffer; public TimeSpan Cooldown = TimeSpan.FromMinutes(5); /// /// Количество целей на категорию /// public const int GoalsCount = 3; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnConsoleInit); SubscribeLocalEvent(OnClaim); SubscribeLocalEvent(OnUiReady); SubscribeLocalEvent(OnCartridgeMessage); SubscribeLocalEvent(OnSpawnEntities); SubscribeLocalEvent(GetPrice); SubscribeLocalEvent(OnSold); SubscribeLocalEvent(OnSold); } private void OnConsoleInit(Entity ent, ref MapInitEvent args) { UpdateUi(ent.Owner); } private void OnClaim(Entity ent, ref ClaimExpeditionGoalMessage args) { if (!_idCard.TryFindIdCard(args.Actor, out var idCard)) return; if (!TryClaimGoal(idCard.Owner, args.OptionId, args.Specification)) { _audio.PlayPvs(_audio.ResolveSound(new SoundCollectionSpecifier("CargoError")), ent.Owner); _popup.PopupEntity(Loc.GetString("exp-goal-cannot-claim"), ent.Owner, args.Actor); } else { _audio.PlayPvs(_audio.ResolveSound(new SoundPathSpecifier("/Audio/Items/appraiser.ogg")), ent.Owner); _popup.PopupEntity(Loc.GetString("exp-goal-claimed"), ent.Owner, args.Actor); } } private void OnUiReady(Entity ent, ref CartridgeUiReadyEvent args) { Dictionary goals = new(); // Проклятое получение всех целей с КПК if (TryComp(args.Loader, out var pda) && pda.IdSlot?.ContainerSlot?.ContainedEntity is { Valid: true } card && TryComp(card, out var goalCard)) goals = goalCard.AssignedGoals.Select(x => new KeyValuePair(x, _claimedGoals[x])).ToDictionary(); var state = new GoalsListCartridgeUiState(goals); _cartridgeLoader.UpdateCartridgeUiState(args.Loader, state); } private void OnCartridgeMessage(Entity ent, ref CartridgeUiMessage args) { if (args.MessageEvent is not GoalsListRemoveMessage cast) return; _claimedGoals.Remove(cast.Id); _cartridgeLoader.UpdateUiState(GetEntity(args.MessageEvent.LoaderUid), null, null); } private void OnSpawnEntities(SpawnExpeditionGoalEntityEvent args) { if (!_planet.LoadedPlanets.TryGetValue(args.Planet, out var planetUid)) { Log.Warning("Tried to spawn expedition goal target on non-exsisting planet."); return; } var markers = EntityManager.AllEntities().Where(x => _tag.HasTag(x.Owner, args.SpawnerTag) && Transform(x).Coordinates.EntityId == planetUid).ToList(); _random.Shuffle(markers); if (markers.Count <= 0) { Log.Warning("Tried to spawn expedition goal target without having markers."); return; } for (var i = 0; i < markers.Count && i < args.MarkersCount; i++) { var markerCoords = Transform(markers[i]).Coordinates; for (var e = 0; e < args.SpawnsPerMarker; e++) { var ent = Spawn(_random.Pick(args.SpawnedEntities), markerCoords); } } } private void GetPrice(ref PriceCalculationEvent args) { // Нам нужен юзер для проверки по КПК if (!args.User.HasValue) return; if (!_idCard.TryFindIdCard(args.User.Value, out var idCard) || !TryComp(idCard.Owner, out var goalsCard)) return; foreach (var item in goalsCard.AssignedGoals) { if (!_claimedGoals.TryGetValue(item, out var goal)) continue; if (!goal.TryComplete(args.Entity, EntityManager)) continue; // Проверяем, соответствует ли валюта if (args.Currency != goal.RequiredStack) continue; // Контрабандные бонусы получаются отдельно if (goal.IsContraband) continue; args.Price = goal.Reward; args.Handled = true; return; } } private void OnSold(ref EntitySoldEvent args) { if (!_idCard.TryFindIdCard(args.Actor, out var idCard) || !TryComp(idCard.Owner, out var goalsCard)) return; foreach (var sold in args.Sold) { foreach (var item in goalsCard.AssignedGoals.ToList()) { if (!_claimedGoals.TryGetValue(item, out var goal)) continue; // Если цель контрабандная, на обычной консоли она не выполнится if (goal.IsContraband) continue; if (!goal.TryComplete(sold, EntityManager)) continue; // Выдаём доп награду if (goal.RewardEntity != null) { var ent = Spawn(goal.RewardEntity, Transform(args.Actor).Coordinates); _hands.TryPickupAnyHand(args.Actor, ent); } goalsCard.AssignedGoals.Remove(item); } Dirty(idCard.Owner, goalsCard); // Обновление UI КПК if (_container.TryGetContainingContainer(idCard.Owner, out var container) && TryComp(container.Owner, out var loader)) _cartridgeLoader.UpdateUiState(container.Owner, null, loader); } } private void OnSold(ref NFEntitySoldEvent args) { if (!_idCard.TryFindIdCard(args.Actor, out var idCard) || !TryComp(idCard.Owner, out var goalsCard)) return; foreach (var sold in args.Sold) { foreach (var item in goalsCard.AssignedGoals.ToList()) { if (!_claimedGoals.TryGetValue(item, out var goal)) continue; if (!goal.TryComplete(sold, EntityManager)) continue; // Выдаём доп награду if (goal.RewardEntity != null) { var ent = Spawn(goal.RewardEntity, Transform(args.Actor).Coordinates); _hands.TryPickupAnyHand(args.Actor, ent); } goalsCard.AssignedGoals.Remove(item); } Dirty(idCard.Owner, goalsCard); // Обновление UI КПК if (_container.TryGetContainingContainer(idCard.Owner, out var container) && TryComp(container.Owner, out var loader)) _cartridgeLoader.UpdateUiState(container.Owner, null, loader); } } /// /// Получает бонус для контрабандных целей /// /// /// /// /// public int GetContrabandBonus(EntityUid actor, EntityUid ent, string currency) { if (!_idCard.TryFindIdCard(actor, out var idCard) || !TryComp(idCard.Owner, out var goalsCard)) return 0; foreach (var item in goalsCard.AssignedGoals) { if (!_claimedGoals.TryGetValue(item, out var goal)) continue; if (!goal.IsContraband) continue; if (goal.RequiredStack != currency) continue; if (goal.TryComplete(ent, EntityManager)) return goal.Reward; } return 0; } /// /// Принимает цель с определённым айди /// /// Карта, к которой будет привязана цель /// Айди цели /// Категория private void ClaimGoal(EntityUid idCard, int goalId, ProtoId specification) { if (!_goals[specification].TryGetValue(goalId, out var goal)) return; if (goal.ClaimEvent != null) RaiseLocalEvent(goal.ClaimEvent); var card = EnsureComp(idCard); card.AssignedGoals.Add(goalId); Dirty(idCard, card); _claimedGoals[goalId] = goal; GenerateGoals(); UpdateUi(); if (_container.TryGetContainingContainer(idCard, out var container)) _cartridgeLoader.UpdateUiState(container.Owner, null, null); } /// /// Пытается принять цель с определённым id /// /// Карта, к которой будет привязана цель /// Айди цели /// Категория /// Принята цель, или нет private bool TryClaimGoal(EntityUid idCard, int goalId, ProtoId specification) { if (!_goals[specification].TryGetValue(goalId, out var goal)) return false; if (TryComp(idCard, out var card) && card.AssignedGoals.Count >= card.MaxGoals) return false; ClaimGoal(idCard, goalId, specification); return true; } /// /// Выполняет ли указанная сущность какую-либо из целей /// /// /// /// public bool IsCompleted(EntityUid user, EntityUid target) { if (!_idCard.TryFindIdCard(user, out var idCard) || !TryComp(idCard.Owner, out var goalsCard)) return false; foreach (var item in goalsCard.AssignedGoals) { if (!_claimedGoals.TryGetValue(item, out var goal)) continue; if (goal.TryComplete(target, EntityManager)) return true; } return false; } /// /// Генерирует новые цели, удаляя предыдущие /// private void GenerateGoals() { _goals.Clear(); _nextOffer = _timing.CurTime + Cooldown; var prototypes = _proto.EnumeratePrototypes().ToList(); var categories = _proto.EnumeratePrototypes().ToList(); foreach (var item in categories) { _goals[item] = new(); var specificated = prototypes.Where(x => x.Specification == item).ToList(); if (specificated.Count <= 0) continue; for (var i = 0; i < GoalsCount; i++) { var proto = _random.Pick(specificated); var goal = proto.Goal.Instantiate(proto.RandomAmount.Next(_random) * proto.AmountMultiplier); // Добавляю отдельно сущность goal.RewardEntity = proto.RewardEntity; // Добавление цели в список _goals[item].Add(_nextId, goal); _nextId++; } } } /// /// Обновляет UI одной конкретной консоли /// private void UpdateUi(EntityUid uid) { if (!TryComp(uid, out var console)) return; _ui.SetUiState(uid, ExpeditionGoalsConsoleUiKey.Key, new ExpeditionGoalsConsoleUiState(_goals, console.Categories, Cooldown, _nextOffer)); } /// /// Обновляет UI всех консолей с целями /// private void UpdateUi() { var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var console)) { _ui.SetUiState(uid, ExpeditionGoalsConsoleUiKey.Key, new ExpeditionGoalsConsoleUiState(_goals, console.Categories, Cooldown, _nextOffer)); } } public override void Update(float frameTime) { base.Update(frameTime); if (_timing.CurTime < _nextOffer) return; GenerateGoals(); UpdateUi(); } }