diff --git a/.wiki/_DV/Laws/CompanyPolicy.txt b/.wiki/_DV/Laws/CompanyPolicy.txt index 00242f967e..72f3024a8c 100644 --- a/.wiki/_DV/Laws/CompanyPolicy.txt +++ b/.wiki/_DV/Laws/CompanyPolicy.txt @@ -129,6 +129,137 @@ Should any person or party aboard a NanoTrasen vessel be found to be currently a == Hostile Corporation Technology == Additionally, due to the presence of our competitors and their agents in the sectors in which our company operates, there is an ever-present possibility that hostile parties' technology or other property may manifest aboard our station by any number of means. The presence, acquisition, and distribution of such items should always be investigated and scrutinised heavily, and if Space Law and local legal ordinances allow, held securely, confiscated, and restricted from use. +== Potential Threats == +While working aboard NanoTrasen stations, you may encounter the highly unlikely scenario of your life being threatened by hostile forces. Fortunately, our highly-trained security teams are well-equipped to deal with such threats. Please note, however, that wasting company resources and time for a pre-emptive defense against a non-existent threat are grounds for demotion. Below are the potential hazards you may face: + +'''NANOTRASEN DOES NOT TAKE RESPONSIBILITY FOR CREW INJURY OR DEATH IF CREW MEMBERS DISREGARD THE INFORMATION BELOW.''' + +=== Hostile Fauna === +Be it from Epistemics experiments, corpses brought in by salvage, or something living in the vents, you may be attacked by non-sophont creatures. NanoTrasen does not encourage befriending such animals, unless it’s an approved experiment for research. Below are the procedures of how to deal with such a threat: + +*Do not engage with the threat yourself. +*Evacuate the department or the zone. +*Immediately inform Security of the incident. Provide details such as: +**Threat type. +**Estimated numbers. +**Property damage. +**Any additional information. +*Inform Medical if there are injuries. If paramedics are unavailable, stabilize and carefully deliver the wounded to Medical. +*Provide ID access if Security personnel are not able to enter the area. +*Once Security confirms the threat being neutralized, clean up the department and report the incident to Command or Central Command. + +=== Hostile Boarding Parties === +Due to the station’s invaluable resources and/or high profile crewmembers, NanoTrasen stations may attract hostile boarding parties. These individuals typically gain unauthorized access to the station and are usually armored, armed with deadly weapons. While their intentions are rarely known, common goals may include: + +*Assassination of crewmembers. +*Theft of valuable station materials. +*Theft of sensitive research. +*Mass crew harm or terrorism. +*Sedition of the chain of Command. +*Detonation of the station’s nuclear warhead. + +If such an unlikely scenario occurs, the following procedures are listed below: + +*Do not engage with the threat yourself. +*Seek immediate shelter. +*Immediately inform Security of the incident. Provide details such as: +**Hostile’s appearance. +**What kind of armaments they’re equipped with. +**Estimated numbers. +**Last known location. +**Property damage. +**Any additional information. +*Inform Medical if there are injuries. If paramedics are unavailable, stabilize and treat the wounded. Only move the wounded if the path to Medical is safe. Do not take unnecessary risks. +*Remain in place until the situation has been resolved. + +In the case of overwhelming force that Security cannot handle: +*Request assistance from Central Command through fax. In your fax, give as much detail of the threat as you can and the damages to the station and crew, alongside with signatures and stamps. Read “Contacting Central Command” for more information. +*Evacuate the station. + +=== Unauthorized Cults === +NanoTrasen recognizes and respects the free expression of religion aboard its stations. Discrimination of any kind will not be tolerated and is grounds for termination. However, Nanotrasen does not permit religious activity that involves: + +*Fanatical recruitment of additional members. +*Harm to crew members, be it physical, psychological or metaphysical. +*Practices associated with EarthGov banned religions that are officially designated as harmful cults. + +For classification, speak with the station’s Mystagogue or Priest. + +Please note that the Oracle’s religion is permitted aboard NanoTrasen stations, as they are the primary subject of research. + +In the case of an unauthorized cult is suspected of forming on board, all crew members are encouraged to report unusual activity. Suspicious behaviour can be: +*Secrecy and Isolation. Suspicious individuals may engage only with selected individuals in secluded areas. +**Sudden changes in behaviour. Known members having a sudden change in their behavior or personality may indicate their affiliation. +**Otherworldly or physics defying effects occurring around certain crew members. +**Appearance of unidentified anomalies. + +If a hostile cult is confirmed, follow these procedures: +*Listen to Epistemics, Security and Command for any relevant information and orders about the threat. +*Report suspicious individuals to Security and Epistemics. Give as much detail as possible, such as their appearance, potential tools, behaviour and more. +*Form a group of trusted co-workers and stick together. Do not isolate yourself. +*Await further instructions and remain in secure areas until the situation has been resolved by Security and Epistemics personnel. + +=== Pathogens === +While all NanoTrasen personnel are vaccinated against a wide range of known diseases, and while the station's atmospheric systems utilize only the highest-grade filtration technology, an outbreak may still occur on the station. Most such incidents are minor in scope - like the common cold. However, employees are reminded to remain vigilant and to treat any outbreak with the seriousness it deserves. + +In the event of a confirmed outbreak, please follow these procedures: +*Follow instructions. Listen to Medical staff and comply with all orders and updates. +*Wear protective equipment, such as masks and gloves. Biosuits are recommended if available. +*Maintain hygiene. Wash hands frequently, and clean up any potential biohazards. +*Report Symptoms. Notify Medical and Security if any personnel display signs of infection. +*Stay in your department. +*Limit physical contact. + +If you’re starting to show symptoms of the pathogen, please immediately follow these procedures: +*Immediately head to the Medical Department. +*Inform Medical personnel of your symptoms. +*Listen to all orders from Medical personnel. +*Store any valuable NanoTrasen gear, weaponry or property in a secure location. +*If a treatment is currently unavailable, quarantine yourself in Medical. +*Await treatment. + +=== Unions === +NanoTrasen supports free speech, the right to fair working conditions and does not consider itself anti-union. However, The Company is not neutral either. NanoTrasen will always defend the interests of its customers, shareholders and associates. Worker unions may disrupt work productivity, which can in turn jeopardize job security for all crew members. + +If an organizing union attempts to use force to advance its goals, such as vandalizing Company property or physically attacking other crewmembers, Security is authorized under Space Law to quell and suppress unions. + +It is every crew member's duty to report potential warning signs of unionization to their managers. Warnings signs may be: +*Crew members refusing to work. +*Usage of union related terminology, (living wage, working conditions, etc.) +*Circulation or distribution of petitions and union fliers. +*Unusual interest in Company policies, benefits, employee list or any other Company information. +*Noticeable changes in behavior among known crew members. +*Non-manifest individuals supporting union activity. +*Clothing, accessories associated with unions. +*Increased negativity and complaints. + +In the case of a violent union riot, follow these procedures: +*Do not join the riot. +*Inform Security and Command immediately. Provide details: +**Names and job positions of any suspected union members and leaders. +**Whether the union members are armed. +*Inform Central Command of the incident. +*Shelter in a safe place and await for the situation to be resolved. + +== Contacting Central Command == +Should you require a question answered about your duties, advice on how to properly handle situations, or wish to submit an incident report (including gross misconduct by Command staff), Central Command Officials are always happy to assist. Please ensure your fax is descriptive, legible and includes your signature or departmental stamp. + +Refrain from sending junk mail to Central Command. Penalties may include pay cuts or a renegotiation of your contract. + +In the highly unlikely event that station security is faced with an overwhelming hostile force or the chain of Command is compromised, crewmembers are advised to request assistance from Central Command. + +Your fax should include: +*Stations designation (available in your PDA) +*The overwhelming threat. Try to provide as much detail as possible, such as: +**Estimated number of hostiles. +**If armed, what kind of armaments they’re equipped with. +**Number of casualties. Information about Security’s and Command’s status is recommended. +**Station’s structural integrity. + +Due to the prevalence of misinformation reaching Central Command, please ensure to add as many stamps and signatures as possible/convenient. + +Be warned that all faxes, urgent or otherwise, may take some time to be properly read and receive a response. Central Command is dedicated to managing multiple stations simultaneously. Rest assured: your fax will be addressed, even without your notice. + = Conclusion = In closing, we thank you for your commitment to our mission and the values that guide NanoTrasen as a whole. Your dedication is the keystone to our success. diff --git a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs index a577568dbd..3509b988d4 100644 --- a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs +++ b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs @@ -92,16 +92,8 @@ namespace Content.Client.Access.UI } } - private void ClearAllAccess() - { - foreach (var button in _accessButtons.ButtonsList.Values) - { - if (button.Pressed) - { - button.Pressed = false; - } - } - } + // DeltaV - removed as part of job preset access fix + // private void ClearAllAccess() private void SelectJobPreset(OptionButton.ItemSelectedEventArgs args) { @@ -113,34 +105,28 @@ namespace Content.Client.Access.UI JobTitleLineEdit.Text = Loc.GetString(job.Name); args.Button.SelectId(args.Id); - ClearAllAccess(); - - // this is a sussy way to do this - foreach (var access in job.Access) - { - if (_accessButtons.ButtonsList.TryGetValue(access, out var button) && !button.Disabled) - { - button.Pressed = true; - } - } + // DeltaV - start of job preset access fix + SubmitData(); + var targetAccesses = job.Access.ToHashSet(); foreach (var group in job.AccessGroups) { if (!_prototypeManager.TryIndex(group, out AccessGroupPrototype? groupPrototype)) { continue; } - - foreach (var access in groupPrototype.Tags) - { - if (_accessButtons.ButtonsList.TryGetValue(access, out var button) && !button.Disabled) - { - button.Pressed = true; - } - } + targetAccesses.UnionWith(groupPrototype.Tags); } - SubmitData(); + // this is a sussy way to do this + foreach (var (id, button) in _accessButtons.ButtonsList) + { + if (!button.Disabled && button.Pressed != targetAccesses.Contains(id)) + { + OnToggleAccess?.Invoke(id); + } + } + // DeltaV - end of job preset access fix } public void UpdateState(IdCardConsoleBoundUserInterfaceState state) diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index 19e8409e4f..d5beca7aea 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -426,7 +426,12 @@ namespace Content.Client.Actions private void OnEntityTargetAttempt(Entity ent, ref ActionTargetAttemptEvent args) { - if (args.Handled || args.Input.EntityUid is not { Valid: true } entity) + if (args.Handled) + return; + + args.Handled = true; + + if (args.Input.EntityUid is not { Valid: true } entity) return; // let world target component handle it @@ -437,8 +442,6 @@ namespace Content.Client.Actions return; } - args.Handled = true; - var action = args.Action; var user = args.User; diff --git a/Content.Client/CartridgeLoader/Cartridges/NanoTaskItemPopup.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/NanoTaskItemPopup.xaml.cs index 124b7b7eaa..6bb29a8f4d 100644 --- a/Content.Client/CartridgeLoader/Cartridges/NanoTaskItemPopup.xaml.cs +++ b/Content.Client/CartridgeLoader/Cartridges/NanoTaskItemPopup.xaml.cs @@ -90,10 +90,12 @@ public sealed partial class NanoTaskItemPopup : DefaultWindow { if (item is NanoTaskItem task) { - var button = task.Priority switch { + var button = task.Priority switch + { NanoTaskPriority.High => HighButton, NanoTaskPriority.Medium => MediumButton, NanoTaskPriority.Low => LowButton, + _ => throw new ArgumentException("Invalid priority"), }; button.Pressed = true; DescriptionInput.Text = task.Description; diff --git a/Content.Client/CartridgeLoader/Cartridges/NanoTaskUiFragment.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/NanoTaskUiFragment.xaml.cs index 38897d6205..e7ed35eac1 100644 --- a/Content.Client/CartridgeLoader/Cartridges/NanoTaskUiFragment.xaml.cs +++ b/Content.Client/CartridgeLoader/Cartridges/NanoTaskUiFragment.xaml.cs @@ -38,10 +38,12 @@ public sealed partial class NanoTaskUiFragment : BoxContainer foreach (var task in tasks) { - var container = task.Data.Priority switch { + var container = task.Data.Priority switch + { NanoTaskPriority.High => HighContainer, NanoTaskPriority.Medium => MediumContainer, NanoTaskPriority.Low => LowContainer, + _ => throw new ArgumentException("Invalid priority"), }; var control = new NanoTaskItemControl(task); container.AddChild(control); diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 0f7266f700..0b42c6d266 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -39,6 +39,7 @@ using Robust.Shared.Prototypes; using Robust.Shared.Replays; using Robust.Shared.Timing; using Content.Client._NF.Emp.Overlays; // Frontier +using Content.Client._RMC14.Xenonids.Screech; // RMC14 namespace Content.Client.Entry { @@ -173,6 +174,7 @@ namespace Content.Client.Entry _overlayManager.AddOverlay(new SingularityOverlay()); _overlayManager.AddOverlay(new RadiationPulseOverlay()); + _overlayManager.AddOverlay(new RMCXenoScreechShockWaveOverlay()); // RMC14 _overlayManager.AddOverlay(new EmpBlastOverlay()); // Frontier _chatManager.Initialize(); _clientPreferencesManager.Initialize(); diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs index 16e89453c1..e923222c98 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs @@ -797,6 +797,9 @@ namespace Content.Client.Lobby.UI PreviewDummy = _controller.LoadProfileEntity(Profile, JobOverride, ShowClothes.Pressed); SpriteView.SetEntity(PreviewDummy); _entManager.System().SetEntityName(PreviewDummy, Profile.Name); + + // Check and set the dirty flag to enable the save/reset buttons as appropriate. + SetDirty(); } /// @@ -862,6 +865,9 @@ namespace Content.Client.Lobby.UI return; _entManager.System().LoadProfile(PreviewDummy, Profile); + + // Check and set the dirty flag to enable the save/reset buttons as appropriate. + SetDirty(); } private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args) diff --git a/Content.Client/Movement/Systems/EyeCursorOffsetSystem.cs b/Content.Client/Movement/Systems/EyeCursorOffsetSystem.cs index eb524cf4ee..d9f7feb146 100644 --- a/Content.Client/Movement/Systems/EyeCursorOffsetSystem.cs +++ b/Content.Client/Movement/Systems/EyeCursorOffsetSystem.cs @@ -29,6 +29,14 @@ public sealed partial class EyeCursorOffsetSystem : EntitySystem private void OnGetEyeOffsetEvent(EntityUid uid, EyeCursorOffsetComponent component, ref GetEyeOffsetEvent args) { + // Begin DeltaV - enable/disable + if (!component.Enabled) + { + args.Offset = Vector2.Zero; + return; + } + // End DeltaV - enable/disable + var offset = OffsetAfterMouse(uid, component); if (offset == null) return; diff --git a/Content.Client/Overlays/ShowHealthIconsSystem.cs b/Content.Client/Overlays/ShowHealthIconsSystem.cs index 3301261bd0..a55660db4f 100644 --- a/Content.Client/Overlays/ShowHealthIconsSystem.cs +++ b/Content.Client/Overlays/ShowHealthIconsSystem.cs @@ -7,6 +7,8 @@ using Content.Shared.StatusIcon; using Content.Shared.StatusIcon.Components; using Robust.Shared.Prototypes; using System.Linq; +using Content.Shared.Mobs; // The Den - Nuke Health icons. + namespace Content.Client.Overlays; @@ -77,6 +79,8 @@ public sealed class ShowHealthIconsSystem : EquipmentHudSystem(entity, out var state)) { + if (state.CurrentState != MobState.Alive) // Den: Nuke Alive Icon so they don't hide speech bubbles + return result; // Since there is no MobState for a rotting mob, we have to deal with this case first. if (HasComp(entity) && _prototypeMan.TryIndex(damageableComponent.RottingIcon, out var rottingIcon)) result.Add(rottingIcon); diff --git a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs index cad9045fa8..917cbbde21 100644 --- a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs +++ b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs @@ -209,7 +209,7 @@ public sealed class ActionButton : Control, IEntityControl if (_entities.TryGetComponent(Action, out AutoRechargeComponent? autoRecharge)) { var chargeTimeRemaining = _sharedChargesSys.GetNextRechargeTime((Action.Value, actionCharges, autoRecharge)); - chargesText.AddText(Loc.GetString($"{Environment.NewLine}Time Til Recharge: {chargeTimeRemaining}")); + chargesText.AddText(Loc.GetString($"{Environment.NewLine}Time Til Recharge: {chargeTimeRemaining.TotalSeconds:F1} seconds")); // DeltaV - Better formatted, easier to read. } } diff --git a/Content.Client/Xenoarchaeology/Artifact/XenoArtifactSystem.cs b/Content.Client/Xenoarchaeology/Artifact/XenoArtifactSystem.cs new file mode 100644 index 0000000000..0e8680e22a --- /dev/null +++ b/Content.Client/Xenoarchaeology/Artifact/XenoArtifactSystem.cs @@ -0,0 +1,6 @@ +using Content.Shared.Xenoarchaeology.Artifact; + +namespace Content.Client.Xenoarchaeology.Artifact; + +/// +public sealed class XenoArtifactSystem : SharedXenoArtifactSystem; diff --git a/Content.Client/Xenoarchaeology/Equipment/ArtifactAnalyzerSystem.cs b/Content.Client/Xenoarchaeology/Equipment/ArtifactAnalyzerSystem.cs new file mode 100644 index 0000000000..557d46f46b --- /dev/null +++ b/Content.Client/Xenoarchaeology/Equipment/ArtifactAnalyzerSystem.cs @@ -0,0 +1,40 @@ +using Content.Client.Xenoarchaeology.Ui; +using Content.Shared.Xenoarchaeology.Equipment; +using Content.Shared.Xenoarchaeology.Equipment.Components; +using Robust.Client.GameObjects; + +namespace Content.Client.Xenoarchaeology.Equipment; + +/// +public sealed class ArtifactAnalyzerSystem : SharedArtifactAnalyzerSystem +{ + [Dependency] private readonly UserInterfaceSystem _ui = default!; + + /// + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAnalysisConsoleAfterAutoHandleState); + SubscribeLocalEvent(OnAnalyzerAfterAutoHandleState); + } + + private void OnAnalysisConsoleAfterAutoHandleState(Entity ent, ref AfterAutoHandleStateEvent args) + { + UpdateBuiIfCanGetAnalysisConsoleUi(ent); + } + + private void OnAnalyzerAfterAutoHandleState(Entity ent, ref AfterAutoHandleStateEvent args) + { + if (!TryGetAnalysisConsole(ent, out var analysisConsole)) + return; + + UpdateBuiIfCanGetAnalysisConsoleUi(analysisConsole.Value); + } + + private void UpdateBuiIfCanGetAnalysisConsoleUi(Entity analysisConsole) + { + if (_ui.TryGetOpenUi(analysisConsole.Owner, ArtifactAnalyzerUiKey.Key, out var bui)) + bui.Update(analysisConsole); + } +} diff --git a/Content.Client/Xenoarchaeology/Equipment/ArtifactCrusherSystem.cs b/Content.Client/Xenoarchaeology/Equipment/ArtifactCrusherSystem.cs index a57ef55574..d4a88cdd03 100644 --- a/Content.Client/Xenoarchaeology/Equipment/ArtifactCrusherSystem.cs +++ b/Content.Client/Xenoarchaeology/Equipment/ArtifactCrusherSystem.cs @@ -6,4 +6,4 @@ namespace Content.Client.Xenoarchaeology.Equipment; public sealed class ArtifactCrusherSystem : SharedArtifactCrusherSystem { -} +} \ No newline at end of file diff --git a/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleBoundUserInterface.cs b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleBoundUserInterface.cs index c7a74815b6..a4ecff7530 100644 --- a/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleBoundUserInterface.cs +++ b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleBoundUserInterface.cs @@ -1,66 +1,50 @@ -using Content.Shared.Xenoarchaeology.Equipment; -using JetBrains.Annotations; -using Robust.Client.GameObjects; +using Content.Shared.Research.Components; +using Content.Shared.Xenoarchaeology.Equipment.Components; using Robust.Client.UserInterface; +using JetBrains.Annotations; namespace Content.Client.Xenoarchaeology.Ui; +/// +/// BUI for artifact analysis console, proxies server-provided UI updates +/// (related to device, connected artifact analyzer, and artifact lying on it). +/// [UsedImplicitly] -public sealed class AnalysisConsoleBoundUserInterface : BoundUserInterface +public sealed class AnalysisConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey) { [ViewVariables] private AnalysisConsoleMenu? _consoleMenu; - public AnalysisConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) - { - } - + /// protected override void Open() { base.Open(); _consoleMenu = this.CreateWindow(); + _consoleMenu.SetOwner(owner); + + _consoleMenu.OnClose += Close; + _consoleMenu.OpenCentered(); _consoleMenu.OnServerSelectionButtonPressed += () => { - SendMessage(new AnalysisConsoleServerSelectionMessage()); - }; - _consoleMenu.OnScanButtonPressed += () => - { - SendMessage(new AnalysisConsoleScanButtonPressedMessage()); - }; - _consoleMenu.OnPrintButtonPressed += () => - { - SendMessage(new AnalysisConsolePrintButtonPressedMessage()); + SendMessage(new ConsoleServerSelectionMessage()); }; _consoleMenu.OnExtractButtonPressed += () => { SendMessage(new AnalysisConsoleExtractButtonPressedMessage()); }; - _consoleMenu.OnUpBiasButtonPressed += () => - { - SendMessage(new AnalysisConsoleBiasButtonPressedMessage(false)); - }; - _consoleMenu.OnDownBiasButtonPressed += () => - { - SendMessage(new AnalysisConsoleBiasButtonPressedMessage(true)); - }; } - protected override void UpdateState(BoundUserInterfaceState state) + /// + /// Update UI state based on corresponding component. + /// + public void Update(Entity ent) { - base.UpdateState(state); - - switch (state) - { - case AnalysisConsoleUpdateState msg: - _consoleMenu?.SetButtonsDisabled(msg); - _consoleMenu?.UpdateInformationDisplay(msg); - _consoleMenu?.UpdateProgressBar(msg); - break; - } + _consoleMenu?.Update(ent); } + /// protected override void Dispose(bool disposing) { base.Dispose(disposing); diff --git a/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml index 9e483c99de..14db2bdf60 100644 --- a/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml +++ b/Content.Client/Xenoarchaeology/Ui/AnalysisConsoleMenu.xaml @@ -1,80 +1,91 @@ - + MinSize="700 350" + SetSize="980 550"> - - - - - - - - - - - + + + + + + + - - + + + + + + + + + + + + + + + + + - - - - + + + + + + + +