- add: interaction part 2

This commit is contained in:
2024-02-23 18:52:03 +03:00
parent 28c8b45eec
commit 2ec981e9e6
22 changed files with 608 additions and 79 deletions

View File

@@ -1,16 +1,40 @@
using Content.Client.Eui;
using Content.Shared._Amour.InteractionPanel;
using Content.Shared.Eui;
namespace Content.Client._Amour.InteractionPanel;
public sealed class InteractionPanelEui : BaseEui
{
private InteractionPanelWindow _interactionPanelWindow;
private readonly IEntityManager _entityManager;
private UI.InteractionPanelWindow _interactionPanelWindow;
private InteractionState _interactionState = default!;
public InteractionPanelEui()
{
_interactionPanelWindow = new InteractionPanelWindow();
IoCManager.InjectDependencies(this);
_entityManager = IoCManager.Resolve<IEntityManager>();
_interactionPanelWindow = new UI.InteractionPanelWindow();
_interactionPanelWindow.OnClose += () => SendMessage(new CloseEuiMessage());
_interactionPanelWindow.OnInteraction += InteractionPanelWindowOnInteraction;
}
private void InteractionPanelWindowOnInteraction(string id)
{
SendMessage(new InteractionSelectedMessage(id));
}
public override void HandleState(EuiStateBase state)
{
base.HandleState(state);
if(state is not InteractionState interactionState)
return;
_interactionState = interactionState;
_interactionPanelWindow.Update(_interactionState);
}
public override void Closed()

View File

@@ -1,51 +0,0 @@
<controls:InteractionPanelWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client._Amour.InteractionPanel"
xmlns:controls1="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'interaction-panel-title'}"
MinSize="300 500">
<BoxContainer Orientation="Vertical" Margin="5 5 5 5">
<BoxContainer Orientation="Vertical" Margin="5 20 5 20" HorizontalAlignment="Stretch" HorizontalExpand="True">
<TextureRect TexturePath="/Textures/Interface/Misc/beakerlarge.png" Margin="5 10 5 15" HorizontalAlignment="Center"/>
<BoxContainer HorizontalAlignment="Center" HorizontalExpand="True">
<BoxContainer Orientation="Vertical" HorizontalAlignment="Left" Margin="5 5 5 5">
<PanelContainer/>
<Label Text="- У меня есть ручки"/>
<Label Text="- У меня есть ножки"/>
</BoxContainer>
<controls1:HighDivider HorizontalAlignment="Center" Margin="5 5 5 5"/>
<BoxContainer Orientation="Vertical" HorizontalAlignment="Right" Margin="5 5 5 5">
<PanelContainer/>
<Label Text="- У него есть дырка"/>
<Label Text="- Он няшный"/>
</BoxContainer>
</BoxContainer>
<ProgressBar Name="CummingProgress"
HorizontalExpand="True"
MinValue="0"
MaxValue="1"
MinHeight = "25"
Page="0"
Margin="10 10 0 0"
Value="0.5"
Modulate="#795695">
<Label Text="Смешной прогресс"/>
</ProgressBar>
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer Orientation="Vertical" Margin="5 20 5 20">
<Button Modulate="#ffaaaa">
<BoxContainer HorizontalAlignment="Left">
<TextureRect TexturePath="/Textures/Interface/NavMap/beveled_circle.png" Margin="5 5 5 5"/>
<Label Text="Трахать" Margin="10 0 15 0"/>
</BoxContainer>
<controls1:HighDivider HorizontalAlignment="Center"/>
<Label Text=">" Margin="5 0 5 0" HorizontalAlignment="Right"/>
</Button>
</BoxContainer>
</BoxContainer>
</controls:InteractionPanelWindow>

View File

@@ -1,16 +0,0 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._Amour.InteractionPanel;
[GenerateTypedNameReferences]
public sealed partial class InteractionPanelWindow : DefaultWindow
{
public InteractionPanelWindow()
{
RobustXamlLoader.Load(this);
}
}

View File

@@ -0,0 +1,12 @@
<ui:InteractionPanelButton
xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client._Amour.InteractionPanel.UI"
xmlns:controls1="clr-namespace:Content.Client.UserInterface.Controls">
<Control Name="ColorControl">
<BoxContainer HorizontalAlignment="Left">
<TextureRect TexturePath="/Textures/Interface/NavMap/beveled_circle.png" Name="Rect" Margin="5 5 5 5"/>
<Label Name="InteractionName" Margin="10 0 15 0"/>
</BoxContainer>
<Label Text=">" Margin="5 0 5 0" HorizontalAlignment="Right"/>
</Control>
</ui:InteractionPanelButton>

View File

@@ -0,0 +1,35 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._Amour.InteractionPanel.UI;
[GenerateTypedNameReferences]
public sealed partial class InteractionPanelButton : ContainerButton
{
public event Action<string>? OnInteraction;
private string _interactionId = "Interaction";
public string InteractionId
{
get => _interactionId;
set
{
_interactionId = value;
InteractionName.Text = Loc.GetString($"interaction-name-{_interactionId.ToLower()}");
}
}
public InteractionPanelButton()
{
RobustXamlLoader.Load(this);
OnPressed += OnOnPressed;
}
private void OnOnPressed(ButtonEventArgs obj)
{
OnInteraction?.Invoke(InteractionId);
}
}

View File

@@ -0,0 +1,42 @@
<ui:InteractionPanelWindow
xmlns="https://spacestation14.io"
xmlns:controls1="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:ui="clr-namespace:Content.Client._Amour.InteractionPanel.UI"
Title="{Loc 'interaction-panel-title'}"
MinSize="400 500">
<BoxContainer Orientation="Vertical" Margin="5 5 5 5">
<BoxContainer Orientation="Vertical" Margin="5 20 5 20" HorizontalAlignment="Stretch" HorizontalExpand="True">
<BoxContainer HorizontalAlignment="Center" HorizontalExpand="True">
<BoxContainer Orientation="Vertical" Margin="5 5 5 5">
<SpriteView Name="PerformerView" Margin="5 10 5 15" HorizontalAlignment="Center"/>
<RichTextLabel Name="PerformerDesc"/>
</BoxContainer>
<controls1:HighDivider Margin="5 5 5 5"/>
<BoxContainer Orientation="Vertical" Margin="5 5 5 5">
<SpriteView Name="TargetView" Margin="5 10 5 15" HorizontalAlignment="Center"/>
<RichTextLabel Name="TargerDesc"/>
</BoxContainer>
</BoxContainer>
<ProgressBar Name="CummingProgress"
HorizontalExpand="True"
MinValue="0"
MaxValue="1"
MinHeight = "25"
Page="0"
Margin="10 10 0 0"
Value="0.5"
Modulate="#795695"
Visible="False">
<Label Text="Смешной прогресс" HorizontalAlignment="Center"/>
</ProgressBar>
</BoxContainer>
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer Orientation="Vertical" Margin="5 20 5 20" Name="Interactions">
</BoxContainer>
</BoxContainer>
</ui:InteractionPanelWindow>

View File

@@ -0,0 +1,40 @@
using Content.Shared._Amour.InteractionPanel;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._Amour.InteractionPanel.UI;
[GenerateTypedNameReferences]
public sealed partial class InteractionPanelWindow : DefaultWindow
{
public event Action<string>? OnInteraction;
public InteractionPanelWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}
public void AddButton(string id)
{
var btn = new InteractionPanelButton();
btn.InteractionId = id;
btn.OnInteraction += _id => OnInteraction?.Invoke(_id);
Interactions.AddChild(btn);
}
public void Update(InteractionState state)
{
Interactions.Children.Clear();
TargetView.SetEntity(state.Target);
PerformerView.SetEntity(state.Performer);
foreach (var interaction in state.AvailableInteractions)
{
AddButton(interaction);
}
}
}

View File

@@ -1,11 +1,60 @@
using Content.Server.EUI;
using System.Linq;
using Content.Server.EUI;
using Content.Server.Interaction;
using Content.Shared._Amour.Arousal;
using Content.Shared._Amour.InteractionPanel;
using Content.Shared.Eui;
using Robust.Shared.Prototypes;
namespace Content.Server._Amour.InteractionPanel;
public sealed class InteractionPanelEui : BaseEui
{
public InteractionPanelEui()
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public readonly Entity<InteractionPanelComponent> User;
public readonly Entity<InteractionPanelComponent> Target;
public InteractionPanelEui(Entity<InteractionPanelComponent> user, Entity<InteractionPanelComponent> target)
{
IoCManager.InjectDependencies(this);
User = user;
Target = target;
}
public override void Opened()
{
base.Opened();
StateDirty();
}
public override EuiStateBase GetNewState()
{
byte? arousal = null;
if (_entityManager.TryGetComponent<ArousalComponent>(User, out var arousalComponent))
arousal = (byte) (arousalComponent.Arousal / 100 * 255);
var availableActions = new HashSet<string>();
foreach (var protoId in Target.Comp.ActionPrototypes)
{
if(!_prototypeManager.TryIndex(protoId,out var prototype)
|| !prototype.Checks.All(check => check.IsAvailable(User,Target,_entityManager)))
continue;
availableActions.Add(protoId);
}
return new InteractionState(_entityManager.GetNetEntity(User), _entityManager.GetNetEntity(Target),availableActions,arousal);
}
public override void HandleMessage(EuiMessageBase msg)
{
base.HandleMessage(msg);
if (msg is InteractionSelectedMessage selectedMessage && Target.Comp.ActionPrototypes.Contains(selectedMessage.Id))
{
_entityManager.System<InteractionPanelSystem>().Interact(User.Owner,Target.Owner,selectedMessage.Id);
}
}
}

View File

@@ -1,7 +1,17 @@
using Content.Server.EUI;
using System.Linq;
using Content.Server.Chat.Systems;
using Content.Server.EUI;
using Content.Shared._Amour.Hole;
using Content.Shared._Amour.InteractionPanel;
using Content.Shared.Emoting;
using Content.Shared.Random.Helpers;
using Content.Shared.Verbs;
using Robust.Server.Audio;
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server._Amour.InteractionPanel;
@@ -9,19 +19,100 @@ public sealed class InteractionPanelSystem : EntitySystem
{
[Dependency] private readonly EuiManager _eui = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly AudioSystem _audioSystem = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
public override void Initialize()
{
SubscribeLocalEvent<HoleContainerComponent,GetVerbsEvent<Verb>>(OnVerb);
SubscribeLocalEvent<InteractionPanelComponent,GetVerbsEvent<Verb>>(OnVerb);
SubscribeLocalEvent<InteractionPanelComponent,ComponentInit>(OnInit);
}
private void OnVerb(EntityUid uid, HoleContainerComponent component, GetVerbsEvent<Verb> args)
private void OnInit(EntityUid uid, InteractionPanelComponent component, ComponentInit args)
{
component.Timeout = _gameTiming.CurTime;
}
private void OnVerb(EntityUid uid, InteractionPanelComponent component, GetVerbsEvent<Verb> args)
{
if(!_playerManager.TryGetSessionByEntity(uid, out var session))
return;
args.Verbs.Add(new Verb()
{
Text = "MEOW",
Act = () => _eui.OpenEui(new InteractionPanelEui(), session)
Text = "Open funny panel",
Act = () => OpenPanel(args.User,args.User,uid)
});
}
public void OpenPanel(EntityUid panelOpener, Entity<InteractionPanelComponent?> user,
Entity<InteractionPanelComponent?> target)
{
if(!Resolve(user,ref user.Comp) || !Resolve(target,ref target.Comp)
|| !_playerManager.TryGetSessionByEntity(panelOpener, out var session))
return;
_eui.OpenEui(new InteractionPanelEui(
new Entity<InteractionPanelComponent>(user,user.Comp),
new Entity<InteractionPanelComponent>(target,target.Comp)),
session);
}
public void Interact(Entity<InteractionPanelComponent?> user,
Entity<InteractionPanelComponent?> target, ProtoId<InteractionPrototype> protoId)
{
//TODO: Пиздец... пиздец.... пиздец....
if( !Resolve(user,ref user.Comp)
|| !Resolve(target,ref target.Comp)
|| user.Comp.IsActive || user.Comp.IsBlocked
|| target.Comp.IsActive || target.Comp.IsBlocked
|| !_prototypeManager.TryIndex(protoId, out var prototype)
|| !prototype.Checks.All(check => check.IsAvailable(user!,target!,EntityManager)))
return;
user.Comp.Timeout = _gameTiming.CurTime + prototype.Timeout;
user.Comp.IsActive = true;
user.Comp.CurrentAction = protoId;
user.Comp.CurrentPartner = new Entity<InteractionPanelComponent>(target,target.Comp);
if(prototype.BeginningMessages.Count > 0)
_chatSystem.TrySendInGameICMessage(user,_robustRandom.Pick(prototype.BeginningMessages),InGameICChatType.Emote,false);
if (prototype.BeginningSound is not null)
_audioSystem.PlayPvs(prototype.BeginningSound, user);
RaiseLocalEvent(user,new InteractionBeginningEvent(protoId,user!,target!));
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<InteractionPanelComponent>();
while (query.MoveNext(out var uid, out var component))
{
if(component.Timeout > _gameTiming.CurTime || !component.IsActive)
continue;
if (component.CurrentPartner is null)
{
continue;
}
if (_prototypeManager.TryIndex(component.CurrentAction, out var prototype))
{
if(prototype.EndingMessages.Count > 0)
_chatSystem.TrySendInGameICMessage(uid,_robustRandom.Pick(prototype.EndingMessages),InGameICChatType.Emote,false);
if (prototype.EndingSound is not null)
_audioSystem.PlayPvs(prototype.EndingSound, uid);
}
component.IsActive = false;
RaiseLocalEvent(uid, new InteractionEndingEvent(component.CurrentAction,
new Entity<InteractionPanelComponent>(uid,component),
component.CurrentPartner.Value));
}
}
}

View File

@@ -0,0 +1,38 @@
using Content.Shared._Amour.InteractionPanel;
namespace Content.Server._Amour.InteractionPanel;
public sealed class Interactions : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<InteractionPanelComponent,InteractionBeginningEvent>(OnBegin);
SubscribeLocalEvent<InteractionPanelComponent,InteractionEndingEvent>(OnEnd);
}
private void OnEnd(EntityUid uid, InteractionPanelComponent component, InteractionEndingEvent args)
{
if(args.Handled)
return;
Logger.Debug(args.Id + " END");
switch (args.Id)
{
}
}
private void OnBegin(EntityUid uid, InteractionPanelComponent component, InteractionBeginningEvent args)
{
if(args.Handled)
return;
Logger.Debug(args.Id + " START");
switch (args.Id)
{
}
}
}

View File

@@ -0,0 +1,18 @@
using Content.Shared.Interaction;
namespace Content.Shared._Amour.InteractionPanel.Checks;
public sealed class HasSmallDistance : IInteractionCheck
{
[DataField] private readonly float _range = SharedInteractionSystem.InteractionRange;
public bool IsAvailable(Entity<InteractionPanelComponent> user, Entity<InteractionPanelComponent> target, IEntityManager entityManager)
{
var transformSystem = entityManager.System<SharedTransformSystem>();
if (_range <= 0)
return true;
var distance = (transformSystem.GetWorldPosition(user) - transformSystem.GetWorldPosition(target)).Length();
return distance <= _range;
}
}

View File

@@ -0,0 +1,83 @@
using Content.Shared._Amour.Hole;
namespace Content.Shared._Amour.InteractionPanel.Checks;
public sealed class UserHasButt : IInteractionCheck
{
public bool IsAvailable(Entity<InteractionPanelComponent> user, Entity<InteractionPanelComponent> target, IEntityManager entityManager)
{
return entityManager.System<SharedHoleSystem>().TryFind(user.Owner, "Anus", out _);
}
}
public sealed class TargetHasButt : IInteractionCheck
{
public bool IsAvailable(Entity<InteractionPanelComponent> user, Entity<InteractionPanelComponent> target, IEntityManager entityManager)
{
return entityManager.System<SharedHoleSystem>().TryFind(target.Owner, "Anus", out _);
}
}
public sealed class UserHasPenis : IInteractionCheck
{
public bool IsAvailable(Entity<InteractionPanelComponent> user, Entity<InteractionPanelComponent> target, IEntityManager entityManager)
{
return entityManager.System<SharedHoleSystem>().TryFind(user.Owner, "Dick", out _);
}
}
public sealed class TargetHasPenis : IInteractionCheck
{
public bool IsAvailable(Entity<InteractionPanelComponent> user, Entity<InteractionPanelComponent> target, IEntityManager entityManager)
{
return entityManager.System<SharedHoleSystem>().TryFind(target.Owner, "Dick", out _);
}
}
public sealed class UserHasVagina : IInteractionCheck
{
public bool IsAvailable(Entity<InteractionPanelComponent> user, Entity<InteractionPanelComponent> target, IEntityManager entityManager)
{
return entityManager.System<SharedHoleSystem>().TryFind(user.Owner, "Vagina", out _);
}
}
public sealed class TargetHasVagina : IInteractionCheck
{
public bool IsAvailable(Entity<InteractionPanelComponent> user, Entity<InteractionPanelComponent> target, IEntityManager entityManager)
{
return entityManager.System<SharedHoleSystem>().TryFind(target.Owner, "Vagina", out _);
}
}
public sealed class UserHasTesticles : IInteractionCheck
{
public bool IsAvailable(Entity<InteractionPanelComponent> user, Entity<InteractionPanelComponent> target, IEntityManager entityManager)
{
return entityManager.System<SharedHoleSystem>().TryFind(user.Owner, "Testicles", out _);
}
}
public sealed class TargetHasTesticles : IInteractionCheck
{
public bool IsAvailable(Entity<InteractionPanelComponent> user, Entity<InteractionPanelComponent> target, IEntityManager entityManager)
{
return entityManager.System<SharedHoleSystem>().TryFind(target.Owner, "Testicles", out _);
}
}
public sealed class UserHasBreast : IInteractionCheck
{
public bool IsAvailable(Entity<InteractionPanelComponent> user, Entity<InteractionPanelComponent> target, IEntityManager entityManager)
{
return entityManager.System<SharedHoleSystem>().TryFind(user.Owner, "Breast", out _);
}
}
public sealed class TargetHasBreast : IInteractionCheck
{
public bool IsAvailable(Entity<InteractionPanelComponent> user, Entity<InteractionPanelComponent> target, IEntityManager entityManager)
{
return entityManager.System<SharedHoleSystem>().TryFind(target.Owner, "Breast", out _);
}
}

View File

@@ -0,0 +1,17 @@
namespace Content.Shared._Amour.InteractionPanel.Checks;
public sealed class InteractSelf : IInteractionCheck
{
public bool IsAvailable(Entity<InteractionPanelComponent> user, Entity<InteractionPanelComponent> target, IEntityManager entityManager)
{
return user == target;
}
}
public sealed class CantInteractSelf: IInteractionCheck
{
public bool IsAvailable(Entity<InteractionPanelComponent> user, Entity<InteractionPanelComponent> target, IEntityManager entityManager)
{
return user != target;
}
}

View File

@@ -0,0 +1,18 @@
using Content.Shared._Amour.Hole;
namespace Content.Shared._Amour.InteractionPanel;
public interface IInteractionCheck
{
public bool IsAvailable(Entity<InteractionPanelComponent> user,
Entity<InteractionPanelComponent> target,IEntityManager entityManager);
}
public sealed class BasicCheck : IInteractionCheck
{
public bool IsAvailable(Entity<InteractionPanelComponent> user, Entity<InteractionPanelComponent> target, IEntityManager entityManager)
{
Logger.Debug("MEWO!!");
return true;
}
}

View File

@@ -0,0 +1,18 @@
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._Amour.InteractionPanel;
[RegisterComponent]
public sealed partial class InteractionPanelComponent : Component
{
[ViewVariables] public bool IsActive = false;
[ViewVariables] public bool IsBlocked = false;
[ViewVariables] public ProtoId<InteractionPrototype> CurrentAction;
[ViewVariables] public TimeSpan Timeout;
[ViewVariables] public Entity<InteractionPanelComponent>? CurrentPartner;
[DataField] public List<ProtoId<InteractionPrototype>> ActionPrototypes = new();
}

View File

@@ -0,0 +1,33 @@
using Content.Shared.Actions;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._Amour.InteractionPanel;
public abstract class InteractionBaseEvent : HandledEntityEventArgs
{
public ProtoId<InteractionPrototype> Id;
public Entity<InteractionPanelComponent> Performer;
public Entity<InteractionPanelComponent> Target;
protected InteractionBaseEvent(ProtoId<InteractionPrototype> id, Entity<InteractionPanelComponent> performer, Entity<InteractionPanelComponent> target)
{
Id = id;
Performer = performer;
Target = target;
}
}
public sealed class InteractionBeginningEvent : InteractionBaseEvent
{
public InteractionBeginningEvent(ProtoId<InteractionPrototype> id, Entity<InteractionPanelComponent> performer, Entity<InteractionPanelComponent> target) : base(id, performer, target)
{
}
}
public sealed class InteractionEndingEvent : InteractionBaseEvent
{
public InteractionEndingEvent(ProtoId<InteractionPrototype> id, Entity<InteractionPanelComponent> performer, Entity<InteractionPanelComponent> target) : base(id, performer, target)
{
}
}

View File

@@ -0,0 +1,35 @@
using Content.Shared.Eui;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._Amour.InteractionPanel;
[Serializable,NetSerializable]
public sealed class InteractionSelectedMessage : EuiMessageBase
{
public string Id;
public InteractionSelectedMessage(string id)
{
Id = id;
}
}
[Serializable,NetSerializable]
public sealed class InteractionState: EuiStateBase
{
public NetEntity Performer { get; }
public NetEntity Target { get; }
public HashSet<string> AvailableInteractions;
public byte? Arousal;
public InteractionState(NetEntity performer, NetEntity target, HashSet<string> availableInteractions, byte? arousal)
{
Performer = performer;
Target = target;
AvailableInteractions = availableInteractions;
Arousal = arousal;
}
}

View File

@@ -0,0 +1,11 @@
using Content.Shared.Actions;
namespace Content.Shared._Amour.InteractionPanel;
public sealed class InteractionPanelSystem : EntitySystem
{
public override void Initialize()
{
}
}

View File

@@ -0,0 +1,23 @@
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared._Amour.InteractionPanel;
[Prototype("interaction")]
public sealed partial class InteractionPrototype : IPrototype
{
[IdDataField] public string ID { get; private set; } = default!;
[ViewVariables] public PrototypeLayerData? Icon;
[ViewVariables] public SoundSpecifier? BeginningSound;
[ViewVariables] public SoundSpecifier? EndingSound;
[DataField] public List<string> BeginningMessages = new();
[DataField] public List<string> EndingMessages = new();
[DataField] public List<IInteractionCheck> Checks = new();
[DataField] public TimeSpan Timeout = TimeSpan.Zero;
}

View File

@@ -265,6 +265,9 @@
- type: Mood
- type: HoleContainer
- type: Arousal
- type: InteractionPanel
actionPrototypes:
- basic
- type: entity
save: false

View File

@@ -95,7 +95,7 @@
name: Яички
components:
- type: Hole
holeName: Dick
holeName: Testicles
holeNotVisibleIn:
- suitstorage
- outerClothing

View File

@@ -0,0 +1,6 @@
- type: interaction
id: basic
checks:
- !type:HasSmallDistance
beginningMessages:
- шлёпает по попе