From 60a05f99c09c80845b8e31b5d2a91f671763afda Mon Sep 17 00:00:00 2001 From: ReeZer2 Date: Tue, 11 Feb 2025 14:00:25 +0200 Subject: [PATCH] move contractor target comp from shared to server, some fixes, and really work photo view on client --- .../Systems/ContractorClientSystem.cs | 13 -- .../UI/ContractorBoundUserInterface.cs | 75 +++++-- .../SS220/Contractor/UI/ContractorStyle.cs | 41 +--- .../UI/CharacterVisualisation.xaml.cs | 9 +- .../Systems/AdminVerbSystem.Antags.cs | 55 +++++ .../ContractorPortalServerSystem.cs | 44 +++- .../Contractor/ContractorServerSystem.cs | 206 +++++++++--------- .../ContractorTajpanWarpPointComponent.cs | 7 + .../Contractor/ContractorTargetComponent.cs | 52 +++++ .../Contractor/ContractorTargetSystem.cs | 29 +++ .../SS220/Contractor/ContractorComponent.cs | 38 +++- .../SS220/Contractor/ContractorMessagesUi.cs | 38 +--- .../Contractor/ContractorTargetComponent.cs | 36 --- .../Contractor/SharedContractorSystem.cs | 11 +- .../SharedContractorTargetSystem.cs | 32 --- .../ru-RU/ss220/administration/contractor.ftl | 1 + .../ru-RU/ss220/contractor/contractor.ftl | 1 + .../SS220/Antags/contractor_items.yml | 14 ++ .../contractor_baton_off.png} | Bin .../contractor_baton_on.png | Bin 0 -> 388 bytes 20 files changed, 402 insertions(+), 300 deletions(-) create mode 100644 Content.Server/SS220/Contractor/ContractorTajpanWarpPointComponent.cs create mode 100644 Content.Server/SS220/Contractor/ContractorTargetComponent.cs create mode 100644 Content.Server/SS220/Contractor/ContractorTargetSystem.cs delete mode 100644 Content.Shared/SS220/Contractor/ContractorTargetComponent.cs delete mode 100644 Content.Shared/SS220/Contractor/SharedContractorTargetSystem.cs create mode 100644 Resources/Locale/ru-RU/ss220/administration/contractor.ftl rename Resources/Textures/SS220/Objects/Weapons/Melee/Contractor/{contractor-buton.png => contractor-baton.rsi/contractor_baton_off.png} (100%) create mode 100644 Resources/Textures/SS220/Objects/Weapons/Melee/Contractor/contractor-baton.rsi/contractor_baton_on.png diff --git a/Content.Client/SS220/Contractor/Systems/ContractorClientSystem.cs b/Content.Client/SS220/Contractor/Systems/ContractorClientSystem.cs index 2773b6c74744..cbf86aad51df 100644 --- a/Content.Client/SS220/Contractor/Systems/ContractorClientSystem.cs +++ b/Content.Client/SS220/Contractor/Systems/ContractorClientSystem.cs @@ -1,20 +1,7 @@ -using Content.Shared.Preferences; using Content.Shared.SS220.Contractor; namespace Content.Client.SS220.Contractor.Systems; public sealed class ContractorClientSystem : SharedContractorSystem { - public Dictionary ProfileForTarget = []; - public override void Initialize() - { - base.Initialize(); - SubscribeNetworkEvent(OnReceiveHumanoid); - } - - private void OnReceiveHumanoid(ContractorReceiveHumanoidMessage msg) - { - ProfileForTarget.Add(GetEntity(msg.Target), msg.Profile); - } - } diff --git a/Content.Client/SS220/Contractor/UI/ContractorBoundUserInterface.cs b/Content.Client/SS220/Contractor/UI/ContractorBoundUserInterface.cs index b1aa2bd73f3a..689df93708d6 100644 --- a/Content.Client/SS220/Contractor/UI/ContractorBoundUserInterface.cs +++ b/Content.Client/SS220/Contractor/UI/ContractorBoundUserInterface.cs @@ -1,5 +1,5 @@ +using System.Linq; using System.Numerics; -using Content.Client.SS220.Contractor.Systems; using Content.Client.SS220.CriminalRecords.UI; using Content.Shared.FixedPoint; using Content.Shared.SS220.Contractor; @@ -23,7 +23,6 @@ public sealed class ContractorBoundUserInterface : BoundUserInterface private DefaultWindow? _withdrawWindow; private DefaultWindow? _photoWindow; - private readonly ContractorClientSystem _contractorSystem; private readonly ContractorPdaComponent _contractorPdaComponent; private readonly ContractorComponent? _contractorComponent; @@ -32,7 +31,6 @@ public sealed class ContractorBoundUserInterface : BoundUserInterface public ContractorBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { IoCManager.InjectDependencies(this); - _contractorSystem = EntMan.System(); _contractorPdaComponent = EntMan.GetComponent(owner); EntMan.TryGetComponent( EntMan.GetEntity(_contractorPdaComponent.PdaOwner), @@ -45,7 +43,7 @@ protected override void ReceiveMessage(BoundUserInterfaceMessage message) { base.ReceiveMessage(message); - if(_menu == null) + if (_menu == null) return; switch (message) @@ -152,7 +150,7 @@ private void AddContract(NetEntity key, ContractorContract contract) if (_menu == null) return; - var contractContainer = new PanelContainer() + var contractContainer = new PanelContainer { Margin = new Thickness(0), HorizontalExpand = true, @@ -173,7 +171,7 @@ private void AddContract(NetEntity key, ContractorContract contract) Text = "Замечен в контакте с вульпой, что вызывает подозрения в утечке информации.", StyleClasses = { "ContractorRichLabelStyle" }, HorizontalAlignment = Control.HAlignment.Left, - Margin = new Thickness(2, 20, 170, 0), + Margin = new Thickness(2, 45, 170, 0), SetSize = new Vector2(137, 93), Modulate = Color.FromHex("#647b88"), RectClipContent = true, @@ -219,18 +217,20 @@ private void AddContract(NetEntity key, ContractorContract contract) Title = "Фото цели", }; - _contractorSystem.ProfileForTarget.TryGetValue(EntMan.GetEntity(key), out var profile); - - if (profile == null) + if (_contractorComponent == null) return; + var profile = _contractorComponent.Profiles[key]; + var iconTargetSprite = new CharacterVisualisation { - SetSize = new Vector2(400, 300), - Margin = new Thickness(100, 0, 0, 0), + SetSize = new Vector2(200, 200), + VerticalAlignment = Control.VAlignment.Center, + HorizontalAlignment = Control.HAlignment.Center, + Margin = new Thickness(10, 0, 0, 0), }; - iconTargetSprite.SetupCharacterSpriteView(profile, _prototypeManager.Index(contract.Job).ID); + iconTargetSprite.SetupCharacterSpriteView(profile, _prototypeManager.Index(contract.Job).ID, true); _photoWindow.OnClose += () => _photoWindow = null; @@ -245,7 +245,7 @@ private void AddContract(NetEntity key, ContractorContract contract) { Orientation = BoxContainer.LayoutOrientation.Vertical, HorizontalExpand = true, - VerticalAlignment = Control.VAlignment.Center, + VerticalAlignment = Control.VAlignment.Top, Margin = new Thickness(5, 30, 0, 0), }; @@ -254,26 +254,42 @@ private void AddContract(NetEntity key, ContractorContract contract) var abortButton = new Button { - Text = "Отмена", VerticalExpand = false, HorizontalExpand = false, Visible = false, - MaxSize = new Vector2(100, 30), + HorizontalAlignment = Control.HAlignment.Center, + VerticalAlignment = Control.VAlignment.Top, + SetSize = new Vector2(80, 30), + Margin = new Thickness(0, 18, 2, 0), + StyleClasses= { "ContractorExecutionButton" }, + Modulate = Color.DarkRed, }; + var abortLabel = new Label + { + Text = "Отменить", + HorizontalAlignment = Control.HAlignment.Center, + VerticalAlignment = Control.VAlignment.Center, + StyleClasses = { "ContractorLabelStyle" }, + }; + + abortButton.AddChild(abortLabel); + abortButton.OnPressed += _ => { SendMessage(new ContractorAbortContractMessage(key)); - topContainer.RemoveChild(abortButton); + positionsContainer.RemoveChild(abortButton); }; - topContainer.AddChild(abortButton); + positionsContainer.AddChild(abortButton); foreach (var amountPosition in contract.AmountPositions) { if (_contractorPdaComponent.CurrentContractEntity == key) { abortButton.Visible = true; + contractContainer.AddStyleClass("ContractAcceptedBorder"); + ClearPositionButtons(positionsContainer); } var positionButton = new Button @@ -306,13 +322,19 @@ private void AddContract(NetEntity key, ContractorContract contract) abortButton.Visible = true; + contractContainer.AddStyleClass("ContractAcceptedBorder"); + + ClearPositionButtons(positionsContainer); + + positionsContainer.AddChild(positionButton); + _allPositionButtons.Add(positionButton); + foreach (var buttons in _allPositionButtons) { buttons.Disabled = true; } }; - _allPositionButtons.Add(positionButton); positionsContainer.AddChild(positionButton); } @@ -323,6 +345,17 @@ private void AddContract(NetEntity key, ContractorContract contract) _menu.ContractsListPanel.AddChild(contractContainer); } + private void ClearPositionButtons(Control positionsContainer) + { + foreach (var button in _allPositionButtons.ToList()) + { + if (button.Parent == positionsContainer) + { + positionsContainer.RemoveChild(button); + } + } + } + private void UpdateHub(Dictionary shopItems) { if (_menu == null) @@ -382,11 +415,6 @@ private void UpdateHub(Dictionary shopItems) buyButton.OnPressed += _ => { SendMessage(new ContractorHubBuyItemMessage(item.Key, item.Value)); - - //if (_contractorComponent is not null) - //UpdateStats(_contractorComponent); - - }; topRow.AddChild(itemIcon); @@ -481,7 +509,6 @@ public void OnWithdrawButtonPressed() _withdrawWindow.OnClose += () => _withdrawWindow = null; - var mainContainer = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Vertical, diff --git a/Content.Client/SS220/Contractor/UI/ContractorStyle.cs b/Content.Client/SS220/Contractor/UI/ContractorStyle.cs index 72e0e59b673a..d944378a18c2 100644 --- a/Content.Client/SS220/Contractor/UI/ContractorStyle.cs +++ b/Content.Client/SS220/Contractor/UI/ContractorStyle.cs @@ -181,43 +181,16 @@ protected override void CreateRules() .Class("ContractorPhotoImage") .Prop(PanelContainer.StylePropertyPanel, StrechedStyleBoxTexture(Tex("/Textures/SS220/Interface/Contractor/contractor-pda-photo-image.png"))); - /* Builder .Element() - .Class("ContractorFontPanel") + .Class("ContractAcceptedBorder") .Prop(PanelContainer.StylePropertyPanel, - StrechedStyleBoxTexture(Tex("/Textures/SS220/Interface/Contractor/font-uplink-contractor.png"))); - - Builder - .Element() - .Class("ContractorContractsButton") - .Prop(SpriteButton.StylePropertySprite, - Sprite("/Textures/SS220/Interface/Contractor/buttons.rsi", "contracts")); - - Builder - .Element() - .Class("ContractorHubButton") - .Prop(SpriteButton.StylePropertySprite, - Sprite("/Textures/SS220/Interface/Contractor/button-hub.rsi", "hub")); - - Builder - .Element() - .Class("ContractorWithdrawButton") - .Prop(SpriteButton.StylePropertySprite, - Sprite("/Textures/SS220/Interface/Contractor/button-withdraw.rsi", "withdraw")); - - Builder - .Element() - .Class("ContractorExecutionButton") - .Prop(SpriteButton.StylePropertySprite, - Sprite("/Textures/SS220/Interface/Contractor/button-execution.rsi", "execution")); - - Builder - .Element() - .Class("ContractorPhotoButton") - .Prop(SpriteButton.StylePropertySprite, - Sprite("/Textures/SS220/Interface/Contractor/button-photo.rsi", "photo")); - */ + new StyleBoxFlat + { + BorderColor = Color.FromHex("#43ff4a"), + BorderThickness = new Thickness(2), + BackgroundColor = Color.FromHex("#93abb2"), + }); } } diff --git a/Content.Client/SS220/CriminalRecords/UI/CharacterVisualisation.xaml.cs b/Content.Client/SS220/CriminalRecords/UI/CharacterVisualisation.xaml.cs index a3798781c9a7..4c8aed79af94 100644 --- a/Content.Client/SS220/CriminalRecords/UI/CharacterVisualisation.xaml.cs +++ b/Content.Client/SS220/CriminalRecords/UI/CharacterVisualisation.xaml.cs @@ -51,7 +51,7 @@ public void ResetCharacterSpriteView() _entMan.DeleteEntity(_previewDummy); } - public void SetupCharacterSpriteView(HumanoidCharacterProfile profile, string jobPrototype) + public void SetupCharacterSpriteView(HumanoidCharacterProfile profile, string jobPrototype, bool onlyFaceSide = false) { HumanoidAppearanceSystem appearanceSystem = IoCManager.Resolve().GetEntitySystem(); @@ -63,6 +63,13 @@ public void SetupCharacterSpriteView(HumanoidCharacterProfile profile, string jo GiveDummyJobClothes(_previewDummy, profile, realjobprototype); _face.SetEntity(_previewDummy); + + if (onlyFaceSide) + { + RemoveChild(_side); + return; + } + _side.SetEntity(_previewDummy); } diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs index 3e9a7ce1c696..616cb57f5b48 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs @@ -1,6 +1,9 @@ +using System.Linq; using Content.Server.Administration.Commands; using Content.Server.Antag; using Content.Server.GameTicking.Rules.Components; +using Content.Server.Implants; +using Content.Server.Roles; using Content.Server.Zombies; using Content.Shared.Administration; using Content.Shared.Database; @@ -11,6 +14,13 @@ using Robust.Shared.Prototypes; using Robust.Shared.Utility; using Content.Server.SS220.GameTicking.Rules.Components; +using Content.Server.Traitor.Uplink; +using Content.Shared.FixedPoint; +using Content.Shared.Implants; +using Content.Shared.Implants.Components; +using Content.Shared.SS220.Contractor; +using Content.Shared.Store.Components; +using Robust.Shared.Map; namespace Content.Server.Administration.Systems; @@ -18,6 +28,9 @@ public sealed partial class AdminVerbSystem { [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly ZombieSystem _zombie = default!; + [Dependency] private readonly SharedRoleSystem _role = default!; + [Dependency] private readonly UplinkSystem _uplink = default!; + [Dependency] private readonly ImplanterSystem _implant = default!; [ValidatePrototypeId] private const string DefaultTraitorRule = "Traitor"; @@ -72,6 +85,48 @@ private void AddAntagVerbs(GetVerbsEvent args) }; args.Verbs.Add(traitor); + //ss220 add verb for shitspawn contractor start + Verb contractor = new() + { + Text = Loc.GetString("admin-verb-make-contractor"), + Category = VerbCategory.Antag, + Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Structures/Wallmounts/posters.rsi"), "poster2_legit"), + Act = () => + { + if (!_mindSystem.TryGetMind(targetPlayer, out var mindId, out var mind)) + return; + + if (!_role.MindHasRole(mindId)) + { + _antag.ForceMakeAntag(targetPlayer, DefaultTraitorRule); + } + + var uplink = _uplink.FindUplinkTarget(targetPlayer.AttachedEntity!.Value); + + if (uplink is null) + { + if (!TryComp(targetPlayer.AttachedEntity.Value, out var implanted)) + return; + + uplink = implanted.ImplantContainer.ContainedEntities + .FirstOrDefault(entity => Prototype(entity)?.ID == "UplinkImplant"); + } + + var uplinkComp = EnsureComp(uplink.Value); + uplinkComp.Balance["Telecrystal"] -= FixedPoint2.New(20); + + EnsureComp(targetPlayer.AttachedEntity!.Value); + + var box = Spawn("BoxContractor", MapCoordinates.Nullspace); + uplinkComp.FullListingsCatalog.FirstOrDefault(boxContractor => boxContractor.ID == "UplinkContractor")!.PurchaseAmount++; + _handsSystem.PickupOrDrop(targetPlayer.AttachedEntity.Value, box); + }, + Impact = LogImpact.High, + Message = Loc.GetString("admin-verb-make-contractor"), + }; + args.Verbs.Add(contractor); + //ss220 add verb for shitspawn contractor end + Verb initialInfected = new() { Text = Loc.GetString("admin-verb-text-make-initial-infected"), diff --git a/Content.Server/SS220/Contractor/ContractorPortalServerSystem.cs b/Content.Server/SS220/Contractor/ContractorPortalServerSystem.cs index 6e0fad0b7462..be73eecd4e02 100644 --- a/Content.Server/SS220/Contractor/ContractorPortalServerSystem.cs +++ b/Content.Server/SS220/Contractor/ContractorPortalServerSystem.cs @@ -1,3 +1,5 @@ +using Content.Shared.DoAfter; +using Content.Shared.Mobs.Systems; using Content.Shared.Popups; using Content.Shared.SS220.Contractor; using Robust.Shared.Physics.Events; @@ -12,6 +14,8 @@ public sealed class ContractorPortalServerSystem : EntitySystem [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly ContractorServerSystem _contractorServer = default!; + [Dependency] private readonly MobStateSystem _mob = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; public override void Initialize() { @@ -29,14 +33,11 @@ private void OnEnterPortal(Entity ent, ref S return; } - var needsPortalEntity = targetComponent.PortalEntity; var contractorEntity = targetComponent.Performer; - if (!TryComp(contractorEntity, out var contractorComponent)) return; - var contractorPdaEntity = GetEntity(contractorComponent.PdaEntity)!.Value; - + var contractorPdaEntity = contractorComponent.PdaEntity; if (!TryComp(contractorPdaEntity, out var contractorPdaComponent)) return; @@ -54,25 +55,50 @@ private void OnEnterPortal(Entity ent, ref S contractorPdaComponent.CurrentContractData = null; contractorPdaComponent.Contracts.Remove(GetNetEntity(args.OtherEntity)); + var needsPortalEntity = targetComponent.PortalOnStationEntity; + if (needsPortalEntity != args.OurEntity) { _popup.PopupClient(Loc.GetString("contractor-portal-for-another-target"), args.OtherEntity, PopupType.Medium); return; } + if (targetComponent.PortalOnTajpanEntity == null) + { + _popup.PopupClient(Loc.GetString("contractor-portal-but-not-in-other-side"), args.OtherEntity, PopupType.Medium); + return; + } + + if (_mob.IsDead(args.OtherEntity)) + { + targetComponent.AmountTc = MathF.Max(0, targetComponent.AmountTc.Float() * 0.2f); + targetComponent.AmountTc = MathF.Ceiling(targetComponent.AmountTc.Float()); + } + contractorComponent.Reputation += contractorComponent.ReputationAward; contractorComponent.AmountTc += targetComponent.AmountTc; contractorComponent.ContractsCompleted++; + contractorComponent.Profiles.Remove(GetNetEntity(args.OtherEntity)); - _uiSystem.ServerSendUiMessage(GetEntity(contractorComponent.PdaEntity)!.Value, ContractorPdaKey.Key, new ContractorUpdateStatsMessage()); - _uiSystem.ServerSendUiMessage(GetEntity(contractorComponent.PdaEntity)!.Value, ContractorPdaKey.Key, new ContractorCompletedContractMessage()); + _uiSystem.ServerSendUiMessage(contractorComponent.PdaEntity!.Value, ContractorPdaKey.Key, new ContractorUpdateStatsMessage()); + _uiSystem.ServerSendUiMessage(contractorComponent.PdaEntity!.Value, ContractorPdaKey.Key, new ContractorCompletedContractMessage()); _contractorServer.GenerateContracts((contractorEntity, contractorComponent)); // generate new contracts - // TODO: create new warp point for another map (tajpan???) - _transformSystem.SetCoordinates(args.OtherEntity, Transform(contractorEntity).Coordinates); + _transformSystem.SetCoordinates(args.OtherEntity, Transform(targetComponent.PortalOnTajpanEntity.Value).Coordinates); - Dirty(contractorPdaEntity, contractorPdaComponent); + Dirty(contractorComponent.PdaEntity!.Value, contractorPdaComponent); Dirty(contractorEntity, contractorComponent); + + _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, + args.OtherEntity, + 5f, + new TeleportTargetToStationEvent(), + args.OtherEntity, + args.OtherEntity) + { + Hidden = true, + RequireCanInteract = false, + }); } } diff --git a/Content.Server/SS220/Contractor/ContractorServerSystem.cs b/Content.Server/SS220/Contractor/ContractorServerSystem.cs index d8a2db8486cf..4b02cd600910 100644 --- a/Content.Server/SS220/Contractor/ContractorServerSystem.cs +++ b/Content.Server/SS220/Contractor/ContractorServerSystem.cs @@ -6,8 +6,6 @@ using Content.Server.Roles; using Content.Server.Roles.Jobs; using Content.Server.Stack; -using Content.Server.Station.Systems; -using Content.Server.StationRecords.Systems; using Content.Shared.Database; using Content.Shared.DoAfter; using Content.Shared.FixedPoint; @@ -26,7 +24,9 @@ namespace Content.Server.SS220.Contractor; /// -/// This handles... +/// This handles generation contracts for contractor, +/// any other stuff with contractor pda, opening portal, +/// handling any contracts, and buy listing in uplink /// public sealed class ContractorServerSystem : SharedContractorSystem { @@ -41,9 +41,6 @@ public sealed class ContractorServerSystem : SharedContractorSystem [Dependency] private readonly ISharedPlayerManager _playerManager = default!; [Dependency] private readonly SharedRoleSystem _roleSystem = default!; [Dependency] private readonly IServerPreferencesManager _pref = default!; - [Dependency] private readonly ActorSystem _actor = default!; - [Dependency] private readonly StationSystem _stationSystem = default!; - [Dependency] private readonly StationRecordsSystem _stationRecords = default!; public override void Initialize() { @@ -74,10 +71,10 @@ public override void Update(float frameTime) var isEnabled = IsCloseWithPosition(uid); - if (_uiSystem.IsUiOpen(GetEntity(comp.PdaEntity)!.Value, ContractorPdaKey.Key)) + if (_uiSystem.IsUiOpen(comp.PdaEntity.Value, ContractorPdaKey.Key)) { _uiSystem.SetUiState( - GetEntity(comp.PdaEntity.Value), + comp.PdaEntity.Value, ContractorPdaKey.Key, new ContractorExecutionBoundUserInterfaceState(isEnabled)); } @@ -97,18 +94,30 @@ private void OnContractorCompInit(Entity ent, ref Component /// private void OnOpenPortalEvent(Entity ent, ref OpenPortalContractorEvent args) { - if (!TryComp(GetEntity(ent.Comp.CurrentContractEntity), out var target)) + if (!TryComp(GetEntity(ent.Comp.CurrentContractEntity), out var targetComponent)) return; - if (args.Cancelled || args.Handled || target.PortalEntity != null) + if (args.Cancelled || args.Handled || targetComponent.PortalOnStationEntity != null) return; - target.PortalEntity = SpawnAtPosition("ContractorPortal", Transform(ent.Owner).Coordinates); - target.PositionOnStation = Transform(ent.Owner).Coordinates.Position; + targetComponent.PortalOnStationEntity = SpawnAtPosition("ContractorPortal", Transform(ent.Owner).Coordinates); - args.Handled = true; + var query = EntityQueryEnumerator(); + + var warpPoints = new List(); + + while (query.MoveNext(out var portalOnTajpan, out _)) + { + warpPoints.Add(portalOnTajpan); + } - Dirty(GetEntity(ent.Comp.CurrentContractEntity!.Value), target); + if (warpPoints.Count > 0) + { + var randomIndex = _random.Next(warpPoints.Count); + targetComponent.PortalOnTajpanEntity = warpPoints[randomIndex]; + } + + args.Handled = true; } //TODO server and checks @@ -128,6 +137,8 @@ private void OnNewContractAccepted(Entity ent, ref Contr EnsureComp(GetEntity(ev.ContractEntity), out var target); + target.CanBeAssigned = false; + if (target.AmountTc > 8 || ev.TcReward > 8) { _adminLogger.Add( @@ -142,8 +153,6 @@ private void OnNewContractAccepted(Entity ent, ref Contr target.AmountTc = ev.TcReward; target.Performer = ev.Actor; - Dirty(GetEntity(ev.ContractEntity), target); - contractorComponent.CurrentContractData = ev.ContractData; contractorComponent.CurrentContractEntity = ev.ContractEntity; @@ -169,10 +178,10 @@ private void OnExecuteContract(Entity ent, ref Contracto if (!TryComp(entity, out var contractorComponent)) return; - if (contractorComponent.PortalEntity != null) + if (contractorComponent.PortalOnStationEntity != null) return; - _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, ev.Actor, 5f, new OpenPortalContractorEvent(), ev.Actor, ev.Actor) + _doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, ev.Actor, 5f, new OpenPortalContractorEvent(), ev.Actor, entity) { BreakOnMove = true, BreakOnDamage = true, @@ -216,16 +225,13 @@ private void OnBuyContractorKit(StoreBuyListingMessage ev) /// /// Target, on what contractor accepted /// Contractor, who accepted - public void HandleContractAccepted(NetEntity acceptedPlayer, EntityUid contractor) + private void HandleContractAccepted(NetEntity acceptedPlayer, EntityUid contractor) { var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var contractorComp)) { - if (uid == contractor) - continue; - - if (!contractorComp.Contracts.Remove(acceptedPlayer)) + if (uid == contractor || !contractorComp.Contracts.Remove(acceptedPlayer)) continue; if (contractorComp.Contracts.Count >= contractorComp.MaxAvailableContracts) @@ -233,14 +239,19 @@ public void HandleContractAccepted(NetEntity acceptedPlayer, EntityUid contracto var newContract = GenerateContractForContractor((uid, contractorComp)); - if (newContract != null) - { - contractorComp.Contracts[newContract.Value.Target] = newContract.Value.Contract; - } + if (!newContract.HasValue) + continue; + contractorComp.Contracts[newContract.Value.Target] = newContract.Value.Contract; Dirty(uid, contractorComp); - _uiSystem.ServerSendUiMessage(GetEntity(contractorComp.PdaEntity!.Value), ContractorPdaKey.Key, new ContractorUpdateStatsMessage()); + if (contractorComp.PdaEntity.HasValue) + { + _uiSystem.ServerSendUiMessage( + contractorComp.PdaEntity.Value, + ContractorPdaKey.Key, + new ContractorUpdateStatsMessage()); + } } } @@ -251,37 +262,28 @@ public void HandleContractAccepted(NetEntity acceptedPlayer, EntityUid contracto /// private (NetEntity Target, ContractorContract Contract)? GenerateContractForContractor(Entity contractor) { - var playerPool = _playerManager.Sessions - .Where(p => p is { Status: SessionStatus.InGame, AttachedEntity: not null }) - .Select(p => p.AttachedEntity!.Value) - .ToList(); - + var playerPool = GetPlayerPool(contractor); _random.Shuffle(playerPool); foreach (var player in playerPool) { - if (HasComp(player) || - HasComp(player) || - HasComp(player) || - (TryComp(player, out var ssdIndicatorComponent) && ssdIndicatorComponent.IsSSD)) - { - continue; - } - - if (contractor.Comp.Contracts.ContainsKey(GetNetEntity(player))) - continue; + if (contractor.Comp.Contracts.Count >= contractor.Comp.MaxAvailableContracts) + return null; if (!_mindSystem.TryGetMind(player, out var mindId, out _)) continue; - if (_roleSystem.MindHasRole(mindId)) + if (!_jobs.MindTryGetJob(mindId, out var jobProto)) // || jobProto.ID == "Captain" - disabled for testing continue; - _jobs.MindTryGetJob(mindId, out var jobProto); // && jobName == "JobCaptain" - disable for testing - - if (jobProto == null) + if (!_playerManager.TryGetSessionByEntity(player, out var session)) continue; + if (_pref.GetPreferences(session.UserId).SelectedCharacter is HumanoidCharacterProfile pref) + { + contractor.Comp.Profiles.TryAdd(GetNetEntity(player), pref); + } + return (GetNetEntity(player), new ContractorContract { @@ -293,57 +295,35 @@ public void HandleContractAccepted(NetEntity acceptedPlayer, EntityUid contracto return null; } - public void GenerateContracts(Entity ent) + public void GenerateContracts(Entity contractor) { - var playerPool = _playerManager.Sessions - .Where(p => p is { Status: SessionStatus.InGame, AttachedEntity: not null }) - .Select(p => p.AttachedEntity!.Value) - .ToList(); - + var playerPool = GetPlayerPool(contractor); _random.Shuffle(playerPool); foreach (var player in playerPool) { - if (HasComp(player) || - HasComp(player) || - HasComp(player) || - (TryComp(player, out var ssdIndicatorComponent) && ssdIndicatorComponent.IsSSD)) - { - continue; - } + if (contractor.Comp.Contracts.Count >= contractor.Comp.MaxAvailableContracts) + return; if (!_mindSystem.TryGetMind(player, out var mindId, out _)) continue; - if (_roleSystem.MindHasRole(mindId)) + if (!_jobs.MindTryGetJob(mindId, out var jobProto)) // || jobProto.ID == "Captain" - disabled for testing continue; - if (ent.Comp.Contracts.ContainsKey(GetNetEntity(player))) - continue; - - _jobs.MindTryGetJob(mindId, out var jobProto); // && jobName == "JobCaptain" - disable for testing + contractor.Comp.Contracts[GetNetEntity(player)] = new ContractorContract + { + Job = jobProto, + AmountPositions = GeneratePositionsForTarget(), + }; - if (jobProto == null) + if (!_playerManager.TryGetSessionByEntity(player, out var session)) continue; - if (ent.Comp.Contracts.Count == ent.Comp.MaxAvailableContracts) - return; - - ent.Comp.Contracts.Add(GetNetEntity(player), - new ContractorContract - { - Job = jobProto, - AmountPositions = GeneratePositionsForTarget(), - }); - - _playerManager.TryGetSessionByEntity(player, out var session); - - if (session == null) - return; - - var pref = (HumanoidCharacterProfile) _pref.GetPreferences(session.UserId).SelectedCharacter; - - RaiseNetworkEvent(new ContractorReceiveHumanoidMessage(GetNetEntity(player), pref, jobProto.ID)); + if (_pref.GetPreferences(session.UserId).SelectedCharacter is HumanoidCharacterProfile pref) + { + contractor.Comp.Profiles.TryAdd(GetNetEntity(player), pref); + } } } @@ -366,23 +346,19 @@ public void GenerateContracts(Entity ent) _random.Shuffle(mediumLocations); _random.Shuffle(hardLocations); - allLocations.Clear(); - - if (easyLocations.Count > 0) - allLocations.Add(easyLocations[0]); - - if (mediumLocations.Count > 0) - allLocations.Add(mediumLocations[0]); - - if (hardLocations.Count > 0) - allLocations.Add(hardLocations[0]); + var result = new List<(NetEntity, string, FixedPoint2, Difficulty)> + { + easyLocations.FirstOrDefault(), + mediumLocations.FirstOrDefault(), + hardLocations.FirstOrDefault() + }; - return allLocations; + return result.Where(loc => loc.Item1.Valid).ToList(); } - private bool IsCloseWithPosition(EntityUid player) + private bool IsCloseWithPosition(EntityUid contractor) { - if (!TryComp(player, out var contractorComponent)) + if (!TryComp(contractor, out var contractorComponent)) return false; if (contractorComponent.CurrentContractEntity is null && @@ -394,15 +370,16 @@ private bool IsCloseWithPosition(EntityUid player) if (!TryComp(targetEntity, out var targetComponent)) return false; - var playerPosition = Transform(player).Coordinates.Position; + var contractorPosition = Transform(contractor).Coordinates.Position; var targetPosition = Transform(targetEntity.Value).Coordinates.Position; var targetPortalPosition = targetComponent.PortalPosition.Position; - var isPlayerCloseToPortal = (playerPosition - targetPortalPosition).Length() < 1f; - var isTargetCloseToPortal = (targetPosition - targetPortalPosition).Length() < 1f; + var isCloseToPortal = (contractorPosition - targetPortalPosition).Length() < 1f && + (targetPosition - targetPortalPosition).Length() < 1f; - return isPlayerCloseToPortal && isTargetCloseToPortal; + + return isCloseToPortal; } private void AbortContract(Entity ent, ref ContractorAbortContractMessage ev) @@ -416,12 +393,14 @@ private void AbortContract(Entity ent, ref ContractorAbo return; if (!contractorComponent.Contracts.Remove(ev.ContractEntity)) - return; + { + _adminLogger.Add( + LogType.Action, + LogImpact.High, + $"Contractor {ev.Actor} aborted unknown contract {ev.ContractEntity}"); - _adminLogger.Add( - LogType.Action, - LogImpact.High, - $"Contractor {ev.Actor} aborted unknown contract {ev.ContractEntity}"); + return; + } contractorComponent.MaxAvailableContracts--; contractorComponent.Reputation--; @@ -431,14 +410,25 @@ private void AbortContract(Entity ent, ref ContractorAbo ent.Comp.CurrentContractEntity = null; ent.Comp.CurrentContractData = null; + GenerateContracts((pdaOwner.Value, contractorComponent)); _uiSystem.ServerSendUiMessage(ent.Owner, ContractorPdaKey.Key, new ContractorUpdateStatsMessage()); Dirty(ent); Dirty(pdaOwner.Value, contractorComponent); } - public HumanoidCharacterProfile GetProfile(EntityUid uid) + private List GetPlayerPool(Entity ent) { - return (HumanoidCharacterProfile) _pref.GetPreferences(_actor.GetSession(uid)!.UserId).SelectedCharacter; + return _playerManager.Sessions + .Where(p => p is { Status: SessionStatus.InGame, AttachedEntity: not null }) + .Select(p => p.AttachedEntity!.Value) + .Where(player => + TryComp(player, out var ssdIndicatorComponent) && !ssdIndicatorComponent.IsSSD && + !HasComp(player) && + !HasComp(player) && + (!TryComp(player, out var targetComponent) || targetComponent.CanBeAssigned) && + (!_mindSystem.TryGetMind(player, out var mindId, out _) || !_roleSystem.MindHasRole(mindId)) && + !ent.Comp.Contracts.ContainsKey(GetNetEntity(player))) + .ToList(); } } diff --git a/Content.Server/SS220/Contractor/ContractorTajpanWarpPointComponent.cs b/Content.Server/SS220/Contractor/ContractorTajpanWarpPointComponent.cs new file mode 100644 index 000000000000..04607986fd7a --- /dev/null +++ b/Content.Server/SS220/Contractor/ContractorTajpanWarpPointComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server.SS220.Contractor; + +[RegisterComponent] +public sealed partial class ContractorTajpanWarpPointComponent : Component +{ + public int TeleportsCount = 0; +} diff --git a/Content.Server/SS220/Contractor/ContractorTargetComponent.cs b/Content.Server/SS220/Contractor/ContractorTargetComponent.cs new file mode 100644 index 000000000000..c598e505ae8e --- /dev/null +++ b/Content.Server/SS220/Contractor/ContractorTargetComponent.cs @@ -0,0 +1,52 @@ +using Content.Shared.FixedPoint; +using Robust.Shared.Map; + +namespace Content.Server.SS220.Contractor; + +/// +/// This is used for marking target for contractor. +/// +[RegisterComponent] +public sealed partial class ContractorTargetComponent : Component +{ + /// + /// Contractor entity + /// + [DataField] + public EntityUid Performer; + + /// + /// The amount of TC, given to the contractor after a successful contract. + /// + [DataField] + public FixedPoint2 AmountTc; + + /// + /// The coordinates, where the target entered the contractor portal + /// + [DataField] + public EntityCoordinates PortalPosition; + + /// + /// The time, in what moment target was entered in portal + /// + [DataField] + public TimeSpan EnteredPortalTime; + + /// + /// The portal entity, which was opened by contractor on station + /// + [DataField] + public EntityUid? PortalOnStationEntity; + + /// + /// The portal entity, which was opened by contractor on tajpan + /// + [DataField] + public EntityUid? PortalOnTajpanEntity; + + /// + /// The boolean, which indicates, if the target can be assigned to another contractor + /// + public bool CanBeAssigned = true; +} diff --git a/Content.Server/SS220/Contractor/ContractorTargetSystem.cs b/Content.Server/SS220/Contractor/ContractorTargetSystem.cs new file mode 100644 index 000000000000..61e0192582c9 --- /dev/null +++ b/Content.Server/SS220/Contractor/ContractorTargetSystem.cs @@ -0,0 +1,29 @@ +using Content.Server.Storage.EntitySystems; +using Content.Shared.SS220.Contractor; + +namespace Content.Server.SS220.Contractor; + +/// +/// This handles... +/// +public sealed class ContractorTargetSystem : EntitySystem +{ + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly EntityStorageSystem _entityStorage = default!; + + /// + public override void Initialize() + { + SubscribeLocalEvent(OnTeleportTargetToStation); + } + + private void OnTeleportTargetToStation(Entity ent, ref TeleportTargetToStationEvent args) + { + _transform.SetCoordinates(ent.Owner, ent.Comp.PortalPosition); + var closetEntity = SpawnAtPosition("ClosetSteelBase", ent.Comp.PortalPosition); + + _entityStorage.Insert(ent.Owner, closetEntity); + _entityStorage.OpenStorage(closetEntity); + ent.Comp.EnteredPortalTime = TimeSpan.Zero; + } +} diff --git a/Content.Shared/SS220/Contractor/ContractorComponent.cs b/Content.Shared/SS220/Contractor/ContractorComponent.cs index f11466a79d37..18132f0cf503 100644 --- a/Content.Shared/SS220/Contractor/ContractorComponent.cs +++ b/Content.Shared/SS220/Contractor/ContractorComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.FixedPoint; +using Content.Shared.Preferences; using Content.Shared.Roles; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -6,42 +7,65 @@ namespace Content.Shared.SS220.Contractor; +/// +/// This is used for marking contractor +/// [RegisterComponent, NetworkedComponent] [AutoGenerateComponentState(true)] public sealed partial class ContractorComponent : Component { + /// + /// Dictionary, where key is target and value is "details" of contract + /// [DataField] [AutoNetworkedField] public Dictionary Contracts = new(); [DataField] [AutoNetworkedField] - public NetEntity? CurrentContractEntity; - - [DataField] - [AutoNetworkedField] - public ContractorContract? CurrentContractData; + public Dictionary Profiles = new(); + /// + /// Used for marking, that contractor is currently with contract + /// [DataField] [AutoNetworkedField] - public NetEntity? PdaEntity; + public NetEntity? CurrentContractEntity; + /// + /// Used for marking, that contractor is currently with contract + /// [DataField] [AutoNetworkedField] - public TimeSpan OpenPortalTime; + public ContractorContract? CurrentContractData; + /// + /// Current amount reputation + /// [DataField] [AutoNetworkedField] public int Reputation; + /// + /// Current amount telecrystals + /// [DataField] [AutoNetworkedField] public FixedPoint2 AmountTc = FixedPoint2.Zero; + /// + /// Current amount successful completed contracts + /// [DataField] [AutoNetworkedField] public int ContractsCompleted; + /// + /// Used for marking, that pda already have "only one owner" + /// + [DataField] + public EntityUid? PdaEntity; + public readonly int ReputationAward = 2; public int MaxAvailableContracts = 5; diff --git a/Content.Shared/SS220/Contractor/ContractorMessagesUi.cs b/Content.Shared/SS220/Contractor/ContractorMessagesUi.cs index 817de69705d8..52423007da8f 100644 --- a/Content.Shared/SS220/Contractor/ContractorMessagesUi.cs +++ b/Content.Shared/SS220/Contractor/ContractorMessagesUi.cs @@ -1,5 +1,4 @@ using Content.Shared.FixedPoint; -using Content.Shared.Preferences; using Robust.Shared.Serialization; namespace Content.Shared.SS220.Contractor; @@ -16,29 +15,13 @@ public ContractorExecutionBoundUserInterfaceState(bool isEnabled) } [Serializable, NetSerializable] -public sealed class ContractorUpdateContractsMessage : BoundUserInterfaceMessage -{ -} +public sealed class ContractorUpdateStatsMessage : BoundUserInterfaceMessage; [Serializable, NetSerializable] -public sealed class ContractorGenerateContractsMessage : BoundUserInterfaceMessage -{ -} +public sealed class ContractorCompletedContractMessage : BoundUserInterfaceMessage; [Serializable, NetSerializable] -public sealed class ContractorUpdateStatsMessage : BoundUserInterfaceMessage -{ -} - -[Serializable, NetSerializable] -public sealed class ContractorCompletedContractMessage : BoundUserInterfaceMessage -{ -} - -[Serializable, NetSerializable] -public sealed class ContractorExecutionButtonPressedMessage : BoundUserInterfaceMessage -{ -} +public sealed class ContractorExecutionButtonPressedMessage : BoundUserInterfaceMessage; [Serializable, NetSerializable] public sealed class ContractorNewContractAcceptedMessage : BoundUserInterfaceMessage @@ -92,18 +75,3 @@ public ContractorWithdrawTcMessage(FixedPoint2 amount) } } -[Serializable, NetSerializable] -public sealed class ContractorReceiveHumanoidMessage : BoundUserInterfaceMessage -{ - public readonly NetEntity Target; - public readonly HumanoidCharacterProfile Profile; - public string JobPrototype; - - public ContractorReceiveHumanoidMessage(NetEntity target, HumanoidCharacterProfile profile, string jobPrototype) - { - Target = target; - Profile = profile; - JobPrototype = jobPrototype; - } -} - diff --git a/Content.Shared/SS220/Contractor/ContractorTargetComponent.cs b/Content.Shared/SS220/Contractor/ContractorTargetComponent.cs deleted file mode 100644 index 756aeb4b6db0..000000000000 --- a/Content.Shared/SS220/Contractor/ContractorTargetComponent.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Numerics; -using Content.Shared.FixedPoint; -using Robust.Shared.GameStates; -using Robust.Shared.Map; - -namespace Content.Shared.SS220.Contractor; - -/// -/// This is used for marking target for contractor. -/// -[RegisterComponent] -[NetworkedComponent] -[AutoGenerateComponentState] -public sealed partial class ContractorTargetComponent : Component -{ - [DataField] - public EntityUid Performer; - - [DataField] - public FixedPoint2 AmountTc; - - [DataField] - public EntityCoordinates PortalPosition; //position where target must be placed - - [DataField] - [AutoNetworkedField] - public Vector2 PositionOnStation; // position where target was placed on station - - [DataField] - public TimeSpan EnteredPortalTime; - - [DataField] - public EntityUid? PortalEntity; - - public TimeSpan TimeInJail = TimeSpan.FromSeconds(5); // Time in another map -} diff --git a/Content.Shared/SS220/Contractor/SharedContractorSystem.cs b/Content.Shared/SS220/Contractor/SharedContractorSystem.cs index 17b54c6e32fb..cd974373dfeb 100644 --- a/Content.Shared/SS220/Contractor/SharedContractorSystem.cs +++ b/Content.Shared/SS220/Contractor/SharedContractorSystem.cs @@ -52,7 +52,7 @@ private void OnOpenUI(Entity ent, ref BoundUIOpenedEvent if (contractorComponent.PdaEntity != null) return; - contractorComponent.PdaEntity = GetNetEntity(ent.Owner); + contractorComponent.PdaEntity = ent.Owner; ent.Comp.PdaOwner = GetNetEntity(args.Actor); @@ -93,3 +93,12 @@ public sealed partial class OpenPortalContractorEvent : DoAfterEvent { public override DoAfterEvent Clone() => this; } + +/// +/// Event for teleporting target to station +/// +[Serializable, NetSerializable] +public sealed partial class TeleportTargetToStationEvent : DoAfterEvent +{ + public override DoAfterEvent Clone() => this; +} diff --git a/Content.Shared/SS220/Contractor/SharedContractorTargetSystem.cs b/Content.Shared/SS220/Contractor/SharedContractorTargetSystem.cs deleted file mode 100644 index 4aeb7d29cfef..000000000000 --- a/Content.Shared/SS220/Contractor/SharedContractorTargetSystem.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Robust.Shared.Timing; - -namespace Content.Shared.SS220.Contractor; - -/// -/// This handles... -/// -public sealed class SharedContractorTargetSystem : EntitySystem -{ - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - - public override void Update(float frameTime) - { - base.Update(frameTime); - - var query = EntityQueryEnumerator(); - - while (query.MoveNext(out var uid, out var comp)) - { - if (comp.EnteredPortalTime == TimeSpan.Zero) - return; - - if (comp.EnteredPortalTime + comp.TimeInJail < _timing.CurTime) - { - _transform.SetCoordinates(uid, comp.PortalPosition); - comp.EnteredPortalTime = TimeSpan.Zero; - RemComp(uid); - } - } - } -} diff --git a/Resources/Locale/ru-RU/ss220/administration/contractor.ftl b/Resources/Locale/ru-RU/ss220/administration/contractor.ftl new file mode 100644 index 000000000000..2010761229f6 --- /dev/null +++ b/Resources/Locale/ru-RU/ss220/administration/contractor.ftl @@ -0,0 +1 @@ +admin-verb-make-contractor = Сделать цель контрактором. diff --git a/Resources/Locale/ru-RU/ss220/contractor/contractor.ftl b/Resources/Locale/ru-RU/ss220/contractor/contractor.ftl index 7556f4e80e3e..abd7e1b8e714 100644 --- a/Resources/Locale/ru-RU/ss220/contractor/contractor.ftl +++ b/Resources/Locale/ru-RU/ss220/contractor/contractor.ftl @@ -4,3 +4,4 @@ contractor-uplink-current-contracts-completed = Выполнено контра contractor-portal-for-non-target = Портал недоступен для вас contractor-portal-for-another-target = Этот портал предназначен не для этой цели +contractor-portal-but-not-in-other-side = Портал на другой стороне недоступен diff --git a/Resources/Prototypes/SS220/Antags/contractor_items.yml b/Resources/Prototypes/SS220/Antags/contractor_items.yml index 2c2aa0db2f8d..248d4672f278 100644 --- a/Resources/Prototypes/SS220/Antags/contractor_items.yml +++ b/Resources/Prototypes/SS220/Antags/contractor_items.yml @@ -23,6 +23,20 @@ - sprite: Objects/Weapons/Bombs/spidercharge.rsi #TODO Sprite state: icon +- type: entity + parent: MarkerBase + id: ContractorTajpanWarpPoint + name: warp point + suffix: contractor tajpan target point + components: + - type: ContractorTajpanWarpPoint + - type: Sprite + layers: + - state: green + - sprite: Objects/Weapons/Bombs/spidercharge.rsi #TODO Sprite + state: icon + +# Contractor box - type: entity name: cardboard box parent: BoxBase diff --git a/Resources/Textures/SS220/Objects/Weapons/Melee/Contractor/contractor-buton.png b/Resources/Textures/SS220/Objects/Weapons/Melee/Contractor/contractor-baton.rsi/contractor_baton_off.png similarity index 100% rename from Resources/Textures/SS220/Objects/Weapons/Melee/Contractor/contractor-buton.png rename to Resources/Textures/SS220/Objects/Weapons/Melee/Contractor/contractor-baton.rsi/contractor_baton_off.png diff --git a/Resources/Textures/SS220/Objects/Weapons/Melee/Contractor/contractor-baton.rsi/contractor_baton_on.png b/Resources/Textures/SS220/Objects/Weapons/Melee/Contractor/contractor-baton.rsi/contractor_baton_on.png new file mode 100644 index 0000000000000000000000000000000000000000..f674b492bb9318e0f8cb2026b7c17ed594165ecc GIT binary patch literal 388 zcmV-~0ek+5P)fDMWa3(In#?&)#CXT=X$lN7T z*Mg%OIU(sM*zJNPq=ZU+&vrlVUTqwIfz<@G);+GRwYyaX0A5wqXK#$@Q9@jRW_~|E z)aiEgj*ALl=I@N%K7@-3(6Unri09R+!$nYl!U0^CrTevQi*UJ%kiu!tV{mqXF!L!m zt3Y_GLCL4!Yyz>K&w;ZDEbTd~Ku*uu1af=MA^>oJ)QxcunXc=y;8_BgIlxgO^^jo~ zpk)VW-KarAWx*E-P&kylC<>Q#-Dvk;4t%x%eXC))A#hCx40eGSd;s=%<^K|+0wZv? zND8b0PYQ&SObCbLTvDC@o!?TKbHY7=$mA*x_XIHL8%-J>6^Ko)QgBZIP7EImbRVpH i@w;FQAOZgp*dktwYaJlmj4hY|0000