mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-30 08:07:28 +00:00
* Fix the sensor monitoring console forcing a GC every 3 seconds (#38146) * Optimize sensor monitoring window graph drawing * Add shared static Vector2 pool for all GraphView instances * Address requested changes * remove lock * Update submodule to 264.0.0 (#38629) * Toy/Plushie Inhands and Wearables (#38514) * Fixed dependency injection and some other issues in a few places. * More issue fixes * patchfor broken NetworkConfiguratorLinkMenu (#38632) fix * Fix solutions flickering when transferring contents (#34838) * Use Solution clones when applying SolutionComponent states * Revert "Use Solution clones when applying SolutionComponent states" This reverts commit 013fd111cf92b22562e00f98a7aaa49bc4b4ed62. * Make Solution implement ICloneable and rename Clone method. * Copy CanReact value when cloning a Solution * Convert to IRobustCloneable * Fix typos in guidebook: Buisness → Business (#38636) * Fix typo in `MinorAntagonists.xml` Buisness → Business * Fix typo in `YourFirstCharacter.xml` Buisness → Business * Various Headphones Fixes and Tweaks (#38479) * sprites, wearables * neck * icon-on sprite * Cleanup prototype instantiation in `DamageTest` (#38639) Cleanup prototype instantiation in DamageTest * Cleanup prototype instantiation in `ExplosionSystem` (#38642) Cleanup prototype instantiation in ExplosionSystem * AddBodyPartCommand localization. (#38612) commit * fix ItemSlotsSystem debug assert (#38655) * Allow the Command & Super door remotes to use the access of their user. (Re-creation of PR due to changes to game balance) (#35536) * Added directional beacons (#38284) * Added directional beacons Signed-off-by: Nox38 <nebulousnox38@gmail.com> * Fixed names Signed-off-by: Nox38 <nebulousnox38@gmail.com> --------- Signed-off-by: Nox38 <nebulousnox38@gmail.com> * Power stat and nuke codes commands get some LEC love. (#38585) * commit * requested changes. * Dsay Dirty and Follow commands converted to LEC and localized. (#38666) * commit * whoopwhoopwhoop * Retro laser sprite fix (#38676) * Fixed everything except the icon Signed-off-by: Nox38 <nebulousnox38@gmail.com> * fixed icon Signed-off-by: Nox38 <nebulousnox38@gmail.com> --------- Signed-off-by: Nox38 <nebulousnox38@gmail.com> * fix water coolers (#38681) * Monochromacy typo fix (#38686) * fixes the typo * Fixed cloning looking for the trait, not the component, RE https://github.com/space-wizards/space-station-14/pull/38686#issuecomment-3025093504 * Apply suggestions from code review --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Validate `CloningSettingsPrototype`s (#38688) * Validate CloningSettingsPrototypes * Update Content.IntegrationTests/Tests/Cloning/CloningSettingsPrototypeTest.cs Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Check EventComponents too --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Add test of objective-related console commands (#36400) * Add test of objective add/list/remove commands * Not sure why we're validating test prototypes, but sure * We don't need a map * Improvements and fixups for New Status Effect API (#38660) * Predict healing and bloodstream (#38690) * initial commit * reapply 38126 * fix rootable * someone missed an important minus sign here * try this * fix * fix * reenable crit hits * cleanup * fix status time dirtying * fix * camelCase * Healing and bloodstream prediction fixes. * Resolving Wizard casting recall on nuke disk making it impossible to disarm (#38661) * Resolving Wizard Recall on Nuke disk making it impossible to disarm - Adding a DisarmBomb case to nuke status update loop - Changing a few methods and parameters to properly follow formatting standards - Updating some names to follow camelCase * Updating missed tag * Reverting DataField change Should prevent this preventative bugfix being a breaking change. * Switch HSV to the default colorspace for character customization (#38434) * Made HSV default for character editor * Adds/fixes comments to HSV defaulting * Added dropbox fix, potentially cursed * Revert "Added dropbox fix, potentially cursed" This reverts commit a709883366fbee813e839742125e70844672af29. --------- Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com> * Fix: Don't deploy foldables when clicking on items inside containers (#38709) * Fix * Apply suggestions from code review --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Pressure Relief Valve (#36708) * initial system (this math is probably WRONG) * General code cleanup and OnExamined support (holy moly this code sucks) * UICode and related events foundation TODO: - Actually write the XAML UI and the underlying system - Un-shitcode the entire thing - Actually test everything... * Working UI code TODO: Make predicted, as this certainly isn't predicted. Even though I said it was. It isn't. * Remove one TODO for unshitcoding the examine code * Add reminder yea * Make predicted (defenitely isn't) (also defenitely isn't a copypaste from pressure pump code) * It's predicted! TODO: - Give it snazzy predicted visuals! - Have a different field for pressure entry, lest it gets bulldozed every UI update. * Improve gas pressure relief valve UI TODO: Reminder to reduce amount of dirties using deltafields * Implement DirtyField prediction * Entity<T> cleanup A lot of Entity<T> conversions and lukewarm cleanup. Also got caught copy pasting code in 4K UHD but it's not like you couldn't tell. * More cleanup and comments * Remove TODO comment on bulldozing window title * """refactoring""" - Move appearance out of shared and finally fix it. Pointless to predict appearance in this instance. - More Entity<T> conversions because I like them. - Move UI creation handling over entirely to the ActivatableUI system. - Fix a hardcoded locale string (why????). * Add visuals * Revert debugging variable replacememt yea * Revert skissue * Remove unused using directives and remove TODO * Localize, cleanup, document * Fix adminlogging discrepancy * Add ability to construct, add guidebook entry * Clear up comment * Add guidebook tooltip to valve * Convert GasPressureReliefValveBoundUserInterface declaration into primary constructor * Adds more input handling and adds autofill on open * Un-deepfry input validator shitcode Genuinely what was I smoking * improve visuals logic * Refactor again - Update math to the correct implementation - Moved code that could be re-used in the future into a helper method under AtmosphereSystem.Gases.cs * I'm sorry but I hate warnings * Remove unused using directive in AtmosphereSystem.Gases.cs * Review and cleanup * Lukewarm UI glossup * Maintainer for the upstream project btw * Remove redundant state sets and messy logic * Unduplicate valve updater code * Redo UI (im sorry Slarti) * run tests * Test refactored UI messaging * Second round of UI improvements - God please find a way to improve this system. Feels bad. * Update loop implementation * Further predict UI * Clear up SetToCurrentThreshold * cleanup * Update to master + pipe layers and bug fixes want to run tests * fixes * Deploy rename pipebomb * Documentation and requested changes * Rename the method that wiggled away * Undo rounding changes * Fix comment * Rename and cleanup * Apply suggestions from code review --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Fix TextLinkTag (#32203) * Adjust uplink buy button to be under item icon (#38596) * Adjusted uplink buy button to be under item icon * Put the discount subtext under the icon * Indent fixes, added margin --------- Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com> * Scurrets - Audio Improvements (#38482) * Scurret audio tuning * Add new sfx * Update sneezing sfx * YAML support * Rename a folder * make telesci wreck easier (#37569) rel * Validate `ShaderPrototype` IDs (#38728) * Convert all shader prototype string literals to protoids in overlays * Convert more shader prototype literal strings to protoids * Convert ValidatePrototypeId to ProtoId * Later * [BUGFIX] Fixed revenant malfunction ability not working properly only MediBots and Stasis bed (#38664) * fixed * clean up * orks fix smart * review fix 1 * more requested changes * less cursed * more descriptive description * better wording * Tiny Tiny Cleanup of the EyeClosingSystem. (#38734) Update EyeClosingSystem.cs * Fix human skin tone distribution (#38701) * fix: Use PredictedQueueDel for gib spell (#38729) * fix: don't default to uncharged sprite state for cells (#38730) * Fix Hristov description - remove inaccurate technical specs (#38746) - Removes inaccurate 'armor piercing 14.5mm shells' reference - Replaces it with a more funny description, matching the style of the other snipers and guns - Keeps ammunition type '.60 anti-materiel ammo' specification - Fixes issue #38590 Co-authored-by: Arthur Fiorese de Andrade <aandrade@cmcxs.gov.br> * feat: allow mopping evaporating puddles (#38743) * Validate remaining `ProtoId` strings (#38747) Validate remaining ProtoId strings * Validate `ProtoId`s in tests (#38745) * Convert string literals to protoids in Content.Tests * Convert string literals to protoids or consts in Content.IntegrationTests * Fix linter failures Tricksy static using misled me * Cleanup warning in StomachSystem (#38748) you did not see this * Vox scars (#38592) * Added vox scars n'stuff, renamed vox_tattoos.ftl to just vox.ftl * Revert "Added vox scars n'stuff, renamed vox_tattoos.ftl to just vox.ftl" This reverts commit c73da55ba3b39ddf93b493aecd85604c54dd8a15. * locale key fix * Changed top surgery scar names to be more generalized * Adjusted face scars * Formatting fixes --------- Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com> * UnlockNode command to LEC. (#38751) * commit * Update UnlockNodeCommand.cs * commit * move command locale to its own file. * Update Content.Server/Xenoarchaeology/Artifact/UnlockNodeCommand.cs --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Typofixes for figurine dialogue (#38737) * Typofixes for figurine dialogue * Forgot two * Janitor Tool: Wire Brush (#38667) * Wow! It's -brush- * spacing. * Update Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Update Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * fixed changes --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Minor escape menu UX improvements (#38650) * fix: spellbooks can have infinite charges (#38376) * fix: spellbooks can have infinite charges * refactor: indicate infinite spellbook charges with null Not sure if I like this much better...
346 lines
11 KiB
C#
346 lines
11 KiB
C#
using System.Numerics;
|
|
using Content.Client.Message;
|
|
using Content.Client.Resources;
|
|
using Content.Client.UserInterface.Controls;
|
|
using Content.Shared.Singularity.Components;
|
|
using Content.Shared.Access.Systems;
|
|
using Robust.Client.Animations;
|
|
using Robust.Client.AutoGenerated;
|
|
using Robust.Client.Graphics;
|
|
using Robust.Client.ResourceManagement;
|
|
using Robust.Client.UserInterface;
|
|
using Robust.Client.UserInterface.Controls;
|
|
using Robust.Client.UserInterface.XAML;
|
|
using Robust.Shared.Noise;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Timing;
|
|
using Robust.Client.Player;
|
|
|
|
namespace Content.Client.ParticleAccelerator.UI;
|
|
|
|
[GenerateTypedNameReferences]
|
|
public sealed partial class ParticleAcceleratorControlMenu : FancyWindow
|
|
{
|
|
[Dependency] private readonly IResourceCache _cache = default!;
|
|
|
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
|
[Dependency] private readonly IPlayerManager _player = default!;
|
|
|
|
private readonly AccessReaderSystem _accessReader;
|
|
|
|
private readonly FastNoiseLite _drawNoiseGenerator;
|
|
|
|
private readonly Animation _alarmControlAnimation;
|
|
|
|
private float _time;
|
|
private int _lastDraw;
|
|
private int _lastReceive;
|
|
|
|
private bool _assembled;
|
|
private bool _shouldContinueAnimating;
|
|
private int _maxStrength = 3;
|
|
|
|
public event Action<bool>? OnOverallState;
|
|
public event Action? OnScan;
|
|
public event Action<ParticleAcceleratorPowerState>? OnPowerState;
|
|
|
|
private EntityUid _entity;
|
|
|
|
public ParticleAcceleratorControlMenu()
|
|
{
|
|
RobustXamlLoader.Load(this);
|
|
IoCManager.InjectDependencies(this);
|
|
|
|
_accessReader = _entityManager.System<AccessReaderSystem>();
|
|
_drawNoiseGenerator = new();
|
|
_drawNoiseGenerator.SetFractalType(FastNoiseLite.FractalType.FBm);
|
|
_drawNoiseGenerator.SetFrequency(0.5f);
|
|
|
|
var panelTex = _cache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
|
|
|
|
MouseFilter = MouseFilterMode.Stop;
|
|
|
|
_alarmControlAnimation = new Animation
|
|
{
|
|
Length = TimeSpan.FromSeconds(1),
|
|
AnimationTracks =
|
|
{
|
|
new AnimationTrackControlProperty
|
|
{
|
|
Property = nameof(Visible),
|
|
KeyFrames =
|
|
{
|
|
new AnimationTrackProperty.KeyFrame(true, 0),
|
|
new AnimationTrackProperty.KeyFrame(false, 0.75f),
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
if (BackPanel.PanelOverride is StyleBoxTexture tex)
|
|
tex.Texture = panelTex;
|
|
|
|
StatusLabel.SetMarkup(Loc.GetString("particle-accelerator-control-menu-status-label"));
|
|
StatusStateLabel.SetMarkup(Loc.GetString("particle-accelerator-control-menu-status-unknown"));
|
|
PowerLabel.SetMarkup(Loc.GetString("particle-accelerator-control-menu-power-label"));
|
|
StrengthLabel.SetMarkup(Loc.GetString("particle-accelerator-control-menu-strength-label"));
|
|
BigAlarmLabel.SetMarkup(Loc.GetString("particle-accelerator-control-menu-alarm-control-1"));
|
|
BigAlarmLabelTwo.SetMarkup(Loc.GetString("particle-accelerator-control-menu-alarm-control-2"));
|
|
DrawLabel.SetMarkup(Loc.GetString("particle-accelerator-control-menu-draw"));
|
|
|
|
StateSpinBox.IsValid = StrengthSpinBoxValid;
|
|
StateSpinBox.InitDefaultButtons();
|
|
StateSpinBox.ValueChanged += PowerStateChanged;
|
|
StateSpinBox.LineEditDisabled = true;
|
|
|
|
OffButton.OnPressed += _ =>
|
|
{
|
|
OnOverallState?.Invoke(false);
|
|
};
|
|
|
|
OnButton.OnPressed += _ =>
|
|
{
|
|
OnOverallState?.Invoke(true);
|
|
};
|
|
|
|
ScanButton.OnPressed += _ =>
|
|
{
|
|
OnScan?.Invoke();
|
|
};
|
|
|
|
AlarmControl.AnimationCompleted += _ =>
|
|
{
|
|
if (_shouldContinueAnimating)
|
|
{
|
|
AlarmControl.PlayAnimation(_alarmControlAnimation, "warningAnim");
|
|
}
|
|
else
|
|
{
|
|
AlarmControl.Visible = false;
|
|
}
|
|
};
|
|
|
|
UpdateUI(false, false, false, false);
|
|
}
|
|
|
|
public void SetEntity(EntityUid uid)
|
|
{
|
|
_entity = uid;
|
|
}
|
|
|
|
private void PowerStateChanged(ValueChangedEventArgs e)
|
|
{
|
|
ParticleAcceleratorPowerState newState;
|
|
switch (e.Value)
|
|
{
|
|
case 0:
|
|
newState = ParticleAcceleratorPowerState.Standby;
|
|
break;
|
|
case 1:
|
|
newState = ParticleAcceleratorPowerState.Level0;
|
|
break;
|
|
case 2:
|
|
newState = ParticleAcceleratorPowerState.Level1;
|
|
break;
|
|
case 3:
|
|
newState = ParticleAcceleratorPowerState.Level2;
|
|
break;
|
|
case 4:
|
|
newState = ParticleAcceleratorPowerState.Level3;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
StateSpinBox.SetButtonDisabled(true);
|
|
OnPowerState?.Invoke(newState);
|
|
}
|
|
|
|
private bool StrengthSpinBoxValid(int n)
|
|
{
|
|
return n >= 0 && n <= _maxStrength;
|
|
}
|
|
|
|
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
|
|
{
|
|
return DragMode.Move;
|
|
}
|
|
|
|
public void DataUpdate(ParticleAcceleratorUIState uiState)
|
|
{
|
|
_assembled = uiState.Assembled;
|
|
UpdateUI(uiState.Assembled,
|
|
uiState.InterfaceBlock,
|
|
uiState.Enabled,
|
|
uiState.WirePowerBlock);
|
|
StatusStateLabel.SetMarkup(Loc.GetString(uiState.Assembled
|
|
? "particle-accelerator-control-menu-status-operational"
|
|
: "particle-accelerator-control-menu-status-incomplete"));
|
|
UpdatePowerState(uiState.State, uiState.Enabled, uiState.Assembled, uiState.MaxLevel);
|
|
UpdatePreview(uiState);
|
|
_lastDraw = uiState.PowerDraw;
|
|
_lastReceive = uiState.PowerReceive;
|
|
}
|
|
|
|
private void UpdatePowerState(ParticleAcceleratorPowerState state, bool enabled, bool assembled, ParticleAcceleratorPowerState maxState)
|
|
{
|
|
var value = state switch
|
|
{
|
|
ParticleAcceleratorPowerState.Standby => 0,
|
|
ParticleAcceleratorPowerState.Level0 => 1,
|
|
ParticleAcceleratorPowerState.Level1 => 2,
|
|
ParticleAcceleratorPowerState.Level2 => 3,
|
|
ParticleAcceleratorPowerState.Level3 => 4,
|
|
_ => 0
|
|
};
|
|
|
|
StateSpinBox.OverrideValue(value);
|
|
|
|
_maxStrength = maxState == ParticleAcceleratorPowerState.Level3 ? 4 : 3;
|
|
if (_maxStrength > 3 && enabled && assembled)
|
|
{
|
|
_shouldContinueAnimating = true;
|
|
if (!AlarmControl.HasRunningAnimation("warningAnim"))
|
|
AlarmControl.PlayAnimation(_alarmControlAnimation, "warningAnim");
|
|
}
|
|
else
|
|
_shouldContinueAnimating = false;
|
|
}
|
|
|
|
private void UpdateUI(bool assembled, bool blocked, bool enabled, bool powerBlock)
|
|
{
|
|
bool hasAccess = _player.LocalSession?.AttachedEntity is {} player
|
|
&& _accessReader.IsAllowed(player, _entity);
|
|
|
|
OnButton.Pressed = enabled;
|
|
OffButton.Pressed = !enabled;
|
|
|
|
var cantUse = !assembled || blocked || powerBlock || !hasAccess;
|
|
OnButton.Disabled = cantUse;
|
|
OffButton.Disabled = cantUse;
|
|
ScanButton.Disabled = blocked || !hasAccess;
|
|
|
|
var cantChangeLevel = !assembled || blocked || !enabled || cantUse;
|
|
StateSpinBox.SetButtonDisabled(cantChangeLevel);
|
|
}
|
|
|
|
private void UpdatePreview(ParticleAcceleratorUIState updateMessage)
|
|
{
|
|
EndCapTexture.SetPowerState(updateMessage, updateMessage.EndCapExists);
|
|
ControlBoxTexture.SetPowerState(updateMessage, true);
|
|
FuelChamberTexture.SetPowerState(updateMessage, updateMessage.FuelChamberExists);
|
|
PowerBoxTexture.SetPowerState(updateMessage, updateMessage.PowerBoxExists);
|
|
EmitterStarboardTexture.SetPowerState(updateMessage, updateMessage.EmitterStarboardExists);
|
|
EmitterForeTexture.SetPowerState(updateMessage, updateMessage.EmitterForeExists);
|
|
EmitterPortTexture.SetPowerState(updateMessage, updateMessage.EmitterPortExists);
|
|
}
|
|
|
|
protected override void FrameUpdate(FrameEventArgs args)
|
|
{
|
|
base.FrameUpdate(args);
|
|
|
|
if (!_assembled)
|
|
{
|
|
DrawValueLabel.SetMarkup(Loc.GetString("particle-accelerator-control-menu-draw-not-available"));
|
|
return;
|
|
}
|
|
|
|
_time += args.DeltaSeconds;
|
|
|
|
var watts = 0;
|
|
if (_lastDraw != 0)
|
|
{
|
|
var val = _drawNoiseGenerator.GetNoise(_time, 0f);
|
|
watts = (int) (_lastDraw + val * 5);
|
|
}
|
|
|
|
DrawValueLabel.SetMarkup(Loc.GetString("particle-accelerator-control-menu-draw-value",
|
|
("watts", $"{watts:##,##0}"),
|
|
("lastReceive", $"{_lastReceive:##,##0}")));
|
|
}
|
|
}
|
|
|
|
public sealed class PASegmentControl : Control
|
|
{
|
|
private static readonly ProtoId<ShaderPrototype> GreyscaleShaderId = "Greyscale";
|
|
private readonly ShaderInstance _greyScaleShader;
|
|
private readonly TextureRect _base;
|
|
private readonly TextureRect _unlit;
|
|
private RSI? _rsi;
|
|
|
|
public string BaseState { get; set; } = "control_box";
|
|
public bool DefaultVisible { get; set; } = false;
|
|
|
|
public PASegmentControl()
|
|
{
|
|
_greyScaleShader = IoCManager.Resolve<IPrototypeManager>().Index(GreyscaleShaderId).Instance();
|
|
|
|
AddChild(_base = new TextureRect());
|
|
AddChild(_unlit = new TextureRect());
|
|
}
|
|
|
|
protected override void EnteredTree()
|
|
{
|
|
base.EnteredTree();
|
|
_rsi = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>($"/Textures/Structures/Power/Generation/PA/{BaseState}.rsi").RSI;
|
|
MinSize = _rsi.Size;
|
|
_base.Texture = _rsi["completed"].Frame0;
|
|
|
|
SetVisible(DefaultVisible);
|
|
_unlit.Visible = DefaultVisible;
|
|
}
|
|
|
|
public void SetPowerState(ParticleAcceleratorUIState state, bool exists)
|
|
{
|
|
SetVisible(exists);
|
|
|
|
if (!state.Enabled || !exists)
|
|
{
|
|
_unlit.Visible = false;
|
|
return;
|
|
}
|
|
|
|
_unlit.Visible = true;
|
|
|
|
var suffix = state.State switch
|
|
{
|
|
ParticleAcceleratorPowerState.Standby => "_unlitp",
|
|
ParticleAcceleratorPowerState.Level0 => "_unlitp0",
|
|
ParticleAcceleratorPowerState.Level1 => "_unlitp1",
|
|
ParticleAcceleratorPowerState.Level2 => "_unlitp2",
|
|
ParticleAcceleratorPowerState.Level3 => "_unlitp3",
|
|
_ => ""
|
|
};
|
|
|
|
if (_rsi == null)
|
|
return;
|
|
|
|
if (!_rsi.TryGetState(BaseState + suffix, out var rState))
|
|
{
|
|
_unlit.Visible = false;
|
|
return;
|
|
}
|
|
|
|
_unlit.Texture = rState.Frame0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds/Removes the shading to the part in the control menu based on the
|
|
/// input state.
|
|
/// </summary>
|
|
/// <param name="state">True if the part exists, false otherwise</param>
|
|
private void SetVisible(bool state)
|
|
{
|
|
if (state)
|
|
{
|
|
_base.ShaderOverride = null;
|
|
_base.ModulateSelfOverride = null;
|
|
}
|
|
else
|
|
{
|
|
_base.ShaderOverride = _greyScaleShader;
|
|
_base.ModulateSelfOverride = new Color(127, 127, 127);
|
|
}
|
|
}
|
|
}
|