mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-29 15:47:26 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66f23833c6 | ||
|
|
333948591f | ||
|
|
51d605f67b |
@@ -70,8 +70,7 @@ public sealed partial class DisabledTraitsPopup : FancyWindow
|
||||
{
|
||||
Margin = new Thickness(10, 2, 0, 0)
|
||||
};
|
||||
reasonLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(
|
||||
Loc.GetString("disabled-traits-popup-reason", ("reason", reason))));
|
||||
reasonLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(reason));
|
||||
container.AddChild(reasonLabel);
|
||||
}
|
||||
|
||||
|
||||
@@ -171,11 +171,11 @@ public sealed partial class TraitCategory : BoxContainer
|
||||
/// Updates condition states for all trait entries based on current job/species.
|
||||
/// Traits that don't meet conditions are disabled but still visible.
|
||||
/// </summary>
|
||||
public void UpdateConditions(ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences)
|
||||
public void UpdateConditions(ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences, IReadOnlySet<ProtoId<TraitPrototype>>? traits)
|
||||
{
|
||||
foreach (var (_, entry) in _traitEntries)
|
||||
{
|
||||
entry.UpdateConditionsMet(jobId, speciesId, antagPreferences);
|
||||
entry.UpdateConditionsMet(jobId, speciesId, antagPreferences, traits);
|
||||
}
|
||||
|
||||
// Update stats since some traits may have been deselected
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Text;
|
||||
using Content.Shared._DV.Traits;
|
||||
using Content.Shared._DV.Traits.Conditions;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
@@ -25,7 +26,6 @@ public sealed partial class TraitEntry : PanelContainer
|
||||
|
||||
private readonly TraitPrototype _trait;
|
||||
private bool _isUpdating;
|
||||
private readonly List<string> _failedConditionTooltips = new();
|
||||
|
||||
public TraitEntry(TraitPrototype trait)
|
||||
{
|
||||
@@ -55,24 +55,23 @@ public sealed partial class TraitEntry : PanelContainer
|
||||
|
||||
private void UpdateConditionTooltips()
|
||||
{
|
||||
var tooltips = new List<string>();
|
||||
var conditionsTooltip = new StringBuilder();
|
||||
|
||||
foreach (var condition in _trait.Conditions)
|
||||
{
|
||||
var tooltip = condition.GetTooltip(_prototype, _loc);
|
||||
if (!string.IsNullOrEmpty(tooltip))
|
||||
tooltips.Add(tooltip);
|
||||
conditionsTooltip.Append(condition.GetTooltip(_prototype, _loc, 0));
|
||||
}
|
||||
|
||||
if (tooltips.Count > 0)
|
||||
if (conditionsTooltip.Length > 0)
|
||||
{
|
||||
var tooltipText = Loc.GetString("trait-conditions-tooltip",
|
||||
("requirements", string.Join("\n", tooltips)));
|
||||
|
||||
TooltipSupplier = _ => CreateMarkupTooltip(tooltipText);
|
||||
var tooltip = Loc.GetString("trait-conditions-tooltip", ("conditions", conditionsTooltip.ToString())).ToString().Trim();
|
||||
var markupTooltip = CreateMarkupTooltip(tooltip);
|
||||
TooltipSupplier = _ => markupTooltip;
|
||||
}
|
||||
else
|
||||
{
|
||||
TooltipSupplier = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -91,9 +90,8 @@ public sealed partial class TraitEntry : PanelContainer
|
||||
/// <summary>
|
||||
/// Updates whether conditions are met based on current job/species.
|
||||
/// </summary>
|
||||
public void UpdateConditionsMet(ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences)
|
||||
public void UpdateConditionsMet(ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences, IReadOnlySet<ProtoId<TraitPrototype>>? traits)
|
||||
{
|
||||
_failedConditionTooltips.Clear();
|
||||
MeetsConditions = true;
|
||||
|
||||
foreach (var condition in _trait.Conditions)
|
||||
@@ -105,7 +103,8 @@ public sealed partial class TraitEntry : PanelContainer
|
||||
InDepartmentCondition deptCond => CheckDepartmentCondition(deptCond, jobId),
|
||||
HasCompCondition compCond => !compCond.Invert, // can't check in lobby but screws with the inversion logic
|
||||
IsAntagEligibleCondition antagEligibleCond => CheckAntagEligibleCondition(antagEligibleCond, antagPreferences),
|
||||
AnyOfCondition anyOfCond => CheckAnyOfCondition(anyOfCond, jobId, speciesId, antagPreferences),
|
||||
HasTraitCondition hasTrait => CheckHasTraitCondition(hasTrait, traits),
|
||||
AnyOfCondition anyOfCond => CheckAnyOfCondition(anyOfCond, jobId, speciesId, antagPreferences, traits),
|
||||
_ => true,
|
||||
};
|
||||
|
||||
@@ -115,9 +114,6 @@ public sealed partial class TraitEntry : PanelContainer
|
||||
if (!result)
|
||||
{
|
||||
MeetsConditions = false;
|
||||
var tooltip = condition.GetTooltip(_prototype, _loc);
|
||||
if (!string.IsNullOrEmpty(tooltip))
|
||||
_failedConditionTooltips.Add(tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +155,15 @@ public sealed partial class TraitEntry : PanelContainer
|
||||
return antagPreferences.Contains(condition.Antag);
|
||||
}
|
||||
|
||||
private bool CheckAnyOfCondition(AnyOfCondition condition, ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences)
|
||||
private bool CheckHasTraitCondition(HasTraitCondition condition, IReadOnlySet<ProtoId<TraitPrototype>>? traits)
|
||||
{
|
||||
if (traits is null)
|
||||
return false;
|
||||
|
||||
return traits.Contains(condition.Trait);
|
||||
}
|
||||
|
||||
private bool CheckAnyOfCondition(AnyOfCondition condition, ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences, IReadOnlySet<ProtoId<TraitPrototype>>? traits)
|
||||
{
|
||||
if (condition.Conditions.Count == 0)
|
||||
return false;
|
||||
@@ -173,8 +177,9 @@ public sealed partial class TraitEntry : PanelContainer
|
||||
HasJobCondition jobCond => CheckJobCondition(jobCond, jobId),
|
||||
InDepartmentCondition deptCond => CheckDepartmentCondition(deptCond, jobId),
|
||||
HasCompCondition compCond => !compCond.Invert, // can't check in lobby
|
||||
AnyOfCondition nestedAnyOf => CheckAnyOfCondition(nestedAnyOf, jobId, speciesId, antagPreferences), // Recursive!
|
||||
IsAntagEligibleCondition antagEligibleCond => CheckAntagEligibleCondition(antagEligibleCond, antagPreferences),
|
||||
HasTraitCondition hasTrait => CheckHasTraitCondition(hasTrait, traits),
|
||||
AnyOfCondition nestedAnyOf => CheckAnyOfCondition(nestedAnyOf, jobId, speciesId, antagPreferences, traits), // Recursive!
|
||||
_ => true,
|
||||
};
|
||||
|
||||
@@ -210,15 +215,6 @@ public sealed partial class TraitEntry : PanelContainer
|
||||
|
||||
// Add disabled styling
|
||||
AddStyleClass("TraitsEntryDisabled");
|
||||
|
||||
// Update tooltip to show failed conditions
|
||||
if (_failedConditionTooltips.Count > 0)
|
||||
{
|
||||
var tooltipText = Loc.GetString("trait-conditions-not-met-tooltip",
|
||||
("requirements", string.Join("\n", _failedConditionTooltips)));
|
||||
|
||||
TooltipSupplier = _ => CreateMarkupTooltip(tooltipText);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Linq;
|
||||
using Content.Shared._DV.CCVars;
|
||||
using Content.Shared._DV.Traits;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -154,16 +153,6 @@ public sealed partial class TraitsTab : BoxContainer
|
||||
}
|
||||
}
|
||||
|
||||
// Check conflicts
|
||||
foreach (var conflict in trait.Conflicts)
|
||||
{
|
||||
if (!_selectedTraits.Contains(conflict))
|
||||
continue;
|
||||
|
||||
RevertTraitToggle(traitId);
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedTraits.Add(traitId);
|
||||
_currentTraitCount++;
|
||||
_currentPointsSpent += trait.Cost;
|
||||
@@ -177,6 +166,7 @@ public sealed partial class TraitsTab : BoxContainer
|
||||
|
||||
UpdateGlobalStats();
|
||||
UpdateCategoryStats(trait.Category);
|
||||
UpdateAllConditions();
|
||||
OnTraitsChanged?.Invoke(_selectedTraits);
|
||||
}
|
||||
|
||||
@@ -211,7 +201,7 @@ public sealed partial class TraitsTab : BoxContainer
|
||||
// If parent width is 0 (not laid out yet), defer until layout happens
|
||||
if (parentWidth > 0)
|
||||
{
|
||||
GlobalPointsBar.SetWidth = (int)((parentWidth - 2 ) * percentage);
|
||||
GlobalPointsBar.SetWidth = (int)((parentWidth - 2) * percentage);
|
||||
_awaitingLayoutUpdate = false;
|
||||
}
|
||||
else if (!_awaitingLayoutUpdate)
|
||||
@@ -281,7 +271,7 @@ public sealed partial class TraitsTab : BoxContainer
|
||||
foreach (var (_, categoryUi) in _categoryUis)
|
||||
{
|
||||
// If some fork wants to use the top selected job as well, just add that to the UpdateConditions method in the editor
|
||||
categoryUi.UpdateConditions(null, _profile?.Species, _profile?.AntagPreferences);
|
||||
categoryUi.UpdateConditions(null, _profile?.Species, _profile?.AntagPreferences, _selectedTraits);
|
||||
}
|
||||
|
||||
RecalculateStats();
|
||||
|
||||
@@ -6,7 +6,10 @@ using Content.Shared._DV.Traits.Conditions;
|
||||
using Content.Shared._DV.Traits.Effects;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
@@ -20,7 +23,7 @@ namespace Content.IntegrationTests.Tests._DV;
|
||||
/// Tests all conditions, effects, and validation logic.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
[TestOf(typeof(TraitSystemTest))]
|
||||
[TestOf(typeof(TraitSystem))]
|
||||
public sealed partial class TraitSystemTest
|
||||
{
|
||||
[TestPrototypes]
|
||||
@@ -107,8 +110,10 @@ public sealed partial class TraitSystemTest
|
||||
description: trait-dysgraphia-name
|
||||
category: TestCategoryUnlimited
|
||||
cost: 0
|
||||
conflicts:
|
||||
- TestTraitConflictB
|
||||
conditions:
|
||||
- !type:HasTraitCondition
|
||||
trait: TestTraitConflictB
|
||||
invert: true
|
||||
effects:
|
||||
- !type:AddCompsEffect
|
||||
components:
|
||||
@@ -582,31 +587,29 @@ public sealed partial class TraitSystemTest
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings { Dirty = true });
|
||||
var server = pair.Server;
|
||||
var entMan = server.ResolveDependency<IEntityManager>();
|
||||
var protoMan = server.ProtoMan;
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var player = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
|
||||
var selectedTraits = new HashSet<ProtoId<TraitPrototype>>
|
||||
{
|
||||
"TestTraitConflictA",
|
||||
"TestTraitConflictB",
|
||||
};
|
||||
var profile = new HumanoidCharacterProfile()
|
||||
.WithTraitPreference("TestTraitConflictA", protoMan)
|
||||
.WithTraitPreference("TestTraitConflictB", protoMan);
|
||||
|
||||
var traitSys = entMan.System<TraitSystem>();
|
||||
var method = typeof(TraitSystem).GetMethod("ValidateTraits",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
var disabledTraits = new Dictionary<ProtoId<TraitPrototype>, List<string>>();
|
||||
var validTraits = (HashSet<ProtoId<TraitPrototype>>)method?.Invoke(traitSys,
|
||||
new object[] { player, selectedTraits, null, null, null, null, new Dictionary<ProtoId<TraitPrototype>, List<string>>() });
|
||||
new object[] { player, profile.TraitPreferences, null, null, null, profile, disabledTraits });
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(validTraits?.Count, Is.EqualTo(1), "Only one conflicting trait should be valid");
|
||||
Assert.That(validTraits.Contains("TestTraitConflictA"), Is.True, "First trait should be kept");
|
||||
Assert.That(validTraits.Contains("TestTraitConflictB"),
|
||||
Is.False,
|
||||
"Conflicting trait should be rejected");
|
||||
Assert.That(validTraits.Contains("TestTraitConflictB"), Is.True, "Non-complaining trait should be kept");
|
||||
Assert.That(disabledTraits.ContainsKey("TestTraitConflictA"), Is.True, "Trait with conflicts should be rejected");
|
||||
});
|
||||
|
||||
entMan.DeleteEntity(player);
|
||||
@@ -639,7 +642,7 @@ public sealed partial class TraitSystemTest
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
var validTraits = (HashSet<ProtoId<TraitPrototype>>)method?.Invoke(traitSys,
|
||||
new object[] { player, selectedTraits, null, null, null, null, new Dictionary<ProtoId<TraitPrototype>, List<string>>()});
|
||||
new object[] { player, selectedTraits, null, null, null, null, new Dictionary<ProtoId<TraitPrototype>, List<string>>() });
|
||||
|
||||
Assert.That(validTraits?.Count, Is.EqualTo(2), "Should respect category maxTraits limit");
|
||||
|
||||
@@ -669,6 +672,7 @@ public sealed partial class TraitSystemTest
|
||||
};
|
||||
|
||||
var traitSys = entMan.System<TraitSystem>();
|
||||
|
||||
var method = typeof(TraitSystem).GetMethod("ValidateTraits",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
@@ -760,8 +764,8 @@ public sealed partial class TraitSystemTest
|
||||
IPrototypeManager protoMan,
|
||||
IComponentFactory factory,
|
||||
EntityUid player,
|
||||
string? jobId = null,
|
||||
string? speciesId = null)
|
||||
ProtoId<JobPrototype>? jobId = null,
|
||||
ProtoId<SpeciesPrototype>? speciesId = null)
|
||||
{
|
||||
return new TraitConditionContext
|
||||
{
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
using Content.Shared.Body.Part;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server._DV.Traits.Assorted;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class AmputeeComponent : Component
|
||||
{
|
||||
[DataField]
|
||||
public BodyPartType RemoveBodyPart { get; private set; } = BodyPartType.Arm;
|
||||
|
||||
[DataField]
|
||||
public BodyPartSymmetry PartSymmetry { get; private set; } = BodyPartSymmetry.Left;
|
||||
|
||||
/// <summary>
|
||||
/// Body part prototype to use as a replacement limb, if applicable.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId? ProtoId { get; private set; }
|
||||
|
||||
[DataField]
|
||||
public string? SlotId { get; private set; } = "left arm";
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Part;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server._DV.Traits.Assorted;
|
||||
|
||||
public sealed class AmputeeSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly BodySystem _body = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<AmputeeComponent, MapInitEvent>(OnMapInit);
|
||||
}
|
||||
|
||||
// Logic here taken from Den at https://github.com/TheDenSS14/TheDen/blob/d6f85a10fccf0f282981438e55b808d7ece73ad9/Content.Server/Traits/TraitSystem.Functions.cs
|
||||
private void OnMapInit(Entity<AmputeeComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
if (!TryComp(ent, out BodyComponent? body) || !TryComp(ent, out TransformComponent? xform))
|
||||
return;
|
||||
|
||||
var root = _body.GetRootPartOrNull(ent, body);
|
||||
if (root is null)
|
||||
return;
|
||||
|
||||
var parts = _body.GetBodyChildrenOfType(ent, ent.Comp.RemoveBodyPart, body);
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var partComp = part.Component;
|
||||
if (partComp.Symmetry != ent.Comp.PartSymmetry)
|
||||
continue;
|
||||
|
||||
foreach (var child in _body.GetBodyPartChildren(part.Id, part.Component))
|
||||
{
|
||||
QueueDel(child.Id);
|
||||
}
|
||||
|
||||
_transform.AttachToGridOrMap(part.Id);
|
||||
QueueDel(part.Id);
|
||||
|
||||
// apparently chopping off limbs makes people bleed a lot. Who would have guessed?
|
||||
_bloodstream.TryModifyBleedAmount(ent.Owner, -10f);
|
||||
|
||||
// goes unused for the purposes of the arm amputee traits, but might as well keep it in
|
||||
if (ent.Comp.ProtoId is null || ent.Comp.SlotId == null)
|
||||
continue;
|
||||
|
||||
var newLimb = SpawnAtPosition(ent.Comp.ProtoId, xform.Coordinates);
|
||||
if (TryComp<BodyPartComponent>(newLimb, out var limbComp) && limbComp.Symmetry == ent.Comp.PartSymmetry)
|
||||
_body.AttachPart(root.Value.Entity, ent.Comp.SlotId, newLimb, root.Value.BodyPart, limbComp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,8 +81,8 @@ public sealed class TraitSystem : EntitySystem
|
||||
EntityUid player,
|
||||
IReadOnlySet<ProtoId<TraitPrototype>> selectedTraits,
|
||||
ICommonSession? session,
|
||||
string? jobId,
|
||||
string? speciesId,
|
||||
ProtoId<JobPrototype>? jobId,
|
||||
ProtoId<SpeciesPrototype>? speciesId,
|
||||
HumanoidCharacterProfile? profile,
|
||||
Dictionary<ProtoId<TraitPrototype>, List<string>> disabledTraits)
|
||||
{
|
||||
@@ -142,41 +142,6 @@ public sealed class TraitSystem : EntitySystem
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check conflicts with already selected traits
|
||||
var hasConflict = false;
|
||||
foreach (var validTraitId in validTraits)
|
||||
{
|
||||
// Check if current trait conflicts with valid trait
|
||||
if (trait.Conflicts.Contains(validTraitId))
|
||||
{
|
||||
Log.Warning($"Trait {traitId} rejected: conflicts with {validTraitId}");
|
||||
if (_prototype.TryIndex(validTraitId, out var conflictTrait))
|
||||
{
|
||||
rejectionReasons.Add(Loc.GetString("disabled-traits-reason-conflict",
|
||||
("trait", Loc.GetString(conflictTrait.Name))));
|
||||
}
|
||||
hasConflict = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if valid trait conflicts with current trait
|
||||
if (_prototype.TryIndex(validTraitId, out var validTrait) &&
|
||||
validTrait.Conflicts.Contains(traitId))
|
||||
{
|
||||
Log.Warning($"Trait {traitId} rejected: {validTraitId} conflicts with it");
|
||||
rejectionReasons.Add(Loc.GetString("disabled-traits-reason-conflict",
|
||||
("trait", Loc.GetString(validTrait.Name))));
|
||||
hasConflict = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasConflict)
|
||||
{
|
||||
disabledTraits[traitId] = rejectionReasons;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check all conditions
|
||||
if (!CheckConditions(trait, conditionCtx, rejectionReasons))
|
||||
{
|
||||
@@ -246,7 +211,7 @@ public sealed class TraitSystem : EntitySystem
|
||||
continue;
|
||||
|
||||
// Get human-readable reason from the condition
|
||||
var tooltip = condition.GetTooltip(ctx.Proto, Loc);
|
||||
var tooltip = condition.GetTooltip(ctx.Proto, Loc, 0);
|
||||
|
||||
if (!string.IsNullOrEmpty(tooltip))
|
||||
rejectionReasons.Add(tooltip);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Text;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._DV.Traits.Conditions;
|
||||
@@ -38,27 +39,25 @@ public sealed partial class AnyOfCondition : BaseTraitCondition
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string GetTooltip(IPrototypeManager proto, ILocalizationManager loc)
|
||||
public override string GetTooltip(IPrototypeManager proto, ILocalizationManager loc, int depth)
|
||||
{
|
||||
if (Conditions.Count == 0)
|
||||
return string.Empty;
|
||||
|
||||
var requirements = new List<string>();
|
||||
var requirementsTooltip = new StringBuilder();
|
||||
|
||||
foreach (var condition in Conditions)
|
||||
{
|
||||
var tooltip = condition.GetTooltip(proto, loc);
|
||||
if (!string.IsNullOrEmpty(tooltip))
|
||||
requirements.Add(tooltip);
|
||||
var conditionTooltip = condition.GetTooltip(proto, loc, depth + 1);
|
||||
if (conditionTooltip.Length > 0)
|
||||
requirementsTooltip.Append(conditionTooltip);
|
||||
}
|
||||
|
||||
if (requirements.Count == 0)
|
||||
if (requirementsTooltip.Length == 0)
|
||||
return string.Empty;
|
||||
|
||||
// AAAAAAAAAAA
|
||||
var joinedRequirements = string.Join("\n• ", requirements);
|
||||
var tooltip = loc.GetString("trait-condition-any-of", ("requirements", requirementsTooltip));
|
||||
|
||||
// Handle inversion in tooltip (ANY becomes NONE when inverted)
|
||||
return Loc.GetString("trait-condition-any-of", ("requirements", joinedRequirements));
|
||||
return new string(' ', depth * 2) + "- " + tooltip;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -31,7 +33,7 @@ public abstract partial class BaseTraitCondition
|
||||
/// Generates a human-readable tooltip describing this condition's requirements.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public abstract string GetTooltip(IPrototypeManager proto, ILocalizationManager loc);
|
||||
public abstract string GetTooltip(IPrototypeManager proto, ILocalizationManager loc, int depth);
|
||||
|
||||
protected abstract bool EvaluateImplementation(TraitConditionContext ctx);
|
||||
}
|
||||
@@ -52,12 +54,12 @@ public sealed class TraitConditionContext
|
||||
/// <summary>
|
||||
/// The job ID of the player, if available.
|
||||
/// </summary>
|
||||
public string? JobId { get; init; }
|
||||
public ProtoId<JobPrototype>? JobId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The species ID of the player, if available.
|
||||
/// </summary>
|
||||
public string? SpeciesId { get; init; }
|
||||
public ProtoId<SpeciesPrototype>? SpeciesId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="HumanoidCharacterProfile"/> of the player, if available.
|
||||
|
||||
@@ -39,11 +39,11 @@ public sealed partial class HasCompCondition : BaseTraitCondition
|
||||
}
|
||||
}
|
||||
|
||||
public override string GetTooltip(IPrototypeManager proto, ILocalizationManager loc)
|
||||
public override string GetTooltip(IPrototypeManager proto, ILocalizationManager loc, int depth)
|
||||
{
|
||||
// If there's a custom tooltip supplied, use that
|
||||
if (Tooltip is not null)
|
||||
return Loc.GetString(Tooltip);
|
||||
return new string(' ', depth * 2) + "- " + loc.GetString(Tooltip) + Environment.NewLine;
|
||||
|
||||
// No tooltip for this condition since we're dealing with comps
|
||||
return string.Empty;
|
||||
|
||||
@@ -17,13 +17,13 @@ public sealed partial class HasJobCondition : BaseTraitCondition
|
||||
|
||||
protected override bool EvaluateImplementation(TraitConditionContext ctx)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ctx.JobId))
|
||||
if (ctx.JobId is not { } jobId)
|
||||
return false;
|
||||
|
||||
return ctx.JobId == Job;
|
||||
return jobId == Job;
|
||||
}
|
||||
|
||||
public override string GetTooltip(IPrototypeManager proto, ILocalizationManager loc)
|
||||
public override string GetTooltip(IPrototypeManager proto, ILocalizationManager loc, int depth)
|
||||
{
|
||||
var jobName = Job.Id;
|
||||
var jobColor = "#ffffff";
|
||||
@@ -43,8 +43,10 @@ public sealed partial class HasJobCondition : BaseTraitCondition
|
||||
}
|
||||
}
|
||||
|
||||
return Invert
|
||||
var tooltip = Invert
|
||||
? loc.GetString("trait-condition-job-not", ("job", jobName), ("color", jobColor))
|
||||
: loc.GetString("trait-condition-job-is", ("job", jobName), ("color", jobColor));
|
||||
|
||||
return new string(' ', depth * 2) + "- " + tooltip + Environment.NewLine;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared._DV.Traits.Conditions;
|
||||
|
||||
/// <summary>
|
||||
/// Condition that checks if the player has activated a specific trait.
|
||||
/// </summary>
|
||||
public sealed partial class HasTraitCondition : BaseTraitCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// List of conditions to check. Passes if any condition evaluates to true.
|
||||
/// </summary>
|
||||
[DataField(required: true)]
|
||||
public ProtoId<TraitPrototype> Trait = default!;
|
||||
|
||||
protected override bool EvaluateImplementation(TraitConditionContext ctx)
|
||||
{
|
||||
if (ctx.Profile is not { } profile)
|
||||
return false;
|
||||
return profile
|
||||
.GetValidTraits(ctx.Profile.TraitPreferences, ctx.Proto)
|
||||
.Contains(Trait);
|
||||
}
|
||||
|
||||
public override string GetTooltip(IPrototypeManager proto, ILocalizationManager loc, int depth)
|
||||
{
|
||||
if (!proto.TryIndex(Trait, out var trait))
|
||||
return string.Empty;
|
||||
|
||||
var tooltip = Invert
|
||||
? loc.GetString("trait-condition-trait-has-not", ("trait", loc.GetString(trait.Name)))
|
||||
: loc.GetString("trait-condition-trait-has", ("trait", loc.GetString(trait.Name)));
|
||||
|
||||
return new string(' ', depth * 2) + "- " + tooltip + Environment.NewLine;
|
||||
}
|
||||
}
|
||||
@@ -17,16 +17,16 @@ public sealed partial class InDepartmentCondition : BaseTraitCondition
|
||||
|
||||
protected override bool EvaluateImplementation(TraitConditionContext ctx)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ctx.JobId))
|
||||
if (ctx.JobId is not { } jobId)
|
||||
return false;
|
||||
|
||||
if (!ctx.Proto.TryIndex(Department, out var department))
|
||||
return false;
|
||||
|
||||
return department.Roles.Contains(ctx.JobId);
|
||||
return department.Roles.Contains(jobId);
|
||||
}
|
||||
|
||||
public override string GetTooltip(IPrototypeManager proto, ILocalizationManager loc)
|
||||
public override string GetTooltip(IPrototypeManager proto, ILocalizationManager loc, int depth)
|
||||
{
|
||||
var deptName = Department.Id;
|
||||
var deptColor = "#ffffff";
|
||||
@@ -37,9 +37,10 @@ public sealed partial class InDepartmentCondition : BaseTraitCondition
|
||||
deptColor = deptProto.Color.ToHex();
|
||||
}
|
||||
|
||||
|
||||
return Invert
|
||||
var tooltip = Invert
|
||||
? loc.GetString("trait-condition-department-not", ("department", deptName), ("color", deptColor))
|
||||
: loc.GetString("trait-condition-department-is", ("department", deptName), ("color", deptColor));
|
||||
|
||||
return new string(' ', depth * 2) + "- " + tooltip + Environment.NewLine;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,15 +23,17 @@ public sealed partial class IsAntagEligibleCondition : BaseTraitCondition
|
||||
return ctx.Profile.AntagPreferences.Contains(Antag);
|
||||
}
|
||||
|
||||
public override string GetTooltip(IPrototypeManager proto, ILocalizationManager loc)
|
||||
public override string GetTooltip(IPrototypeManager proto, ILocalizationManager loc, int depth)
|
||||
{
|
||||
if (!proto.TryIndex(Antag, out var antagProto))
|
||||
return string.Empty;
|
||||
|
||||
var antagName = loc.GetString(antagProto.Name);
|
||||
|
||||
return Invert
|
||||
var tooltip = Invert
|
||||
? loc.GetString("trait-condition-antag-not", ("antag", antagName))
|
||||
: loc.GetString("trait-condition-antag-is", ("antag", antagName));
|
||||
: loc.GetString("trait-condition-antag-is", ("antag", antagName));
|
||||
|
||||
return new string(' ', depth * 2) + "- " + tooltip + Environment.NewLine;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,13 @@ public sealed partial class IsSpeciesCondition : BaseTraitCondition
|
||||
|
||||
protected override bool EvaluateImplementation(TraitConditionContext ctx)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ctx.SpeciesId))
|
||||
if (ctx.SpeciesId is not { } speciesId)
|
||||
return false;
|
||||
|
||||
return ctx.SpeciesId == Species;
|
||||
return speciesId == Species;
|
||||
}
|
||||
|
||||
public override string GetTooltip(IPrototypeManager proto, ILocalizationManager loc)
|
||||
public override string GetTooltip(IPrototypeManager proto, ILocalizationManager loc, int depth)
|
||||
{
|
||||
var speciesName = Species.Id;
|
||||
if (proto.TryIndex(Species, out var speciesProto))
|
||||
@@ -31,8 +31,10 @@ public sealed partial class IsSpeciesCondition : BaseTraitCondition
|
||||
speciesName = loc.GetString(speciesProto.Name);
|
||||
}
|
||||
|
||||
return Invert
|
||||
var tooltip = Invert
|
||||
? loc.GetString("trait-condition-species-not", ("species", speciesName))
|
||||
: loc.GetString("trait-condition-species-is", ("species", speciesName));
|
||||
|
||||
return new string(' ', depth * 2) + "- " + tooltip + Environment.NewLine;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Part;
|
||||
using Content.Shared.Body.Systems;
|
||||
|
||||
namespace Content.Shared._DV.Traits.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Effect that removes body parts of the player body.
|
||||
/// </summary>
|
||||
public sealed partial class RemoveBodyPartEffect : BaseTraitEffect
|
||||
{
|
||||
|
||||
[DataField(required: true)]
|
||||
public BodyPartType Part = default;
|
||||
|
||||
[DataField(required: true)]
|
||||
public BodyPartSymmetry Symmetry = default!;
|
||||
|
||||
public override void Apply(TraitEffectContext ctx)
|
||||
{
|
||||
if (!ctx.EntMan.TryGetComponent(ctx.Player, out BodyComponent? body))
|
||||
return;
|
||||
|
||||
if (!ctx.EntMan.TrySystem<SharedBodySystem>(out var bodySys))
|
||||
return;
|
||||
|
||||
if (!ctx.EntMan.TrySystem<SharedBloodstreamSystem>(out var bloodstreamSys))
|
||||
return;
|
||||
|
||||
if (bodySys.GetRootPartOrNull(ctx.Player, body) is not { } root)
|
||||
return;
|
||||
|
||||
foreach (var part in bodySys.GetBodyChildrenOfType(ctx.Player, Part, body, Symmetry))
|
||||
{
|
||||
foreach (var child in bodySys.GetBodyPartChildren(part.Id, part.Component))
|
||||
{
|
||||
ctx.EntMan.DeleteEntity(child.Id);
|
||||
}
|
||||
ctx.EntMan.DeleteEntity(part.Id);
|
||||
|
||||
// apparently chopping off limbs makes people bleed a lot. Who would have guessed?
|
||||
bloodstreamSys.TryModifyBleedAmount(ctx.Player, -100f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,10 +51,4 @@ public sealed partial class TraitPrototype : IPrototype
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<BaseTraitEffect> Effects = new();
|
||||
|
||||
/// <summary>
|
||||
/// Other traits that are mutually exclusive with this one.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<ProtoId<TraitPrototype>> Conflicts = new();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ disabled-traits-popup-title = Traits Disabled
|
||||
disabled-traits-popup-label = Traits Disabled
|
||||
disabled-traits-popup-message = Some of your selected traits could not be applied because they did not meet the required conditions.
|
||||
disabled-traits-popup-list-header = The following traits were disabled:
|
||||
disabled-traits-popup-reason = • {$reason}
|
||||
disabled-traits-popup-skip-checkbox = Don't show this again
|
||||
disabled-traits-popup-close-button = Close
|
||||
|
||||
@@ -27,30 +26,29 @@ trait-category-traits-unlimited = {$selected} traits
|
||||
trait-category-points = ({$selected} / {$max} pts)
|
||||
|
||||
## Condition tooltips
|
||||
trait-conditions-tooltip = [bold]Requirements:[/bold]
|
||||
{$requirements}
|
||||
trait-conditions-not-met-tooltip = Requirements not met:
|
||||
{$requirements}
|
||||
trait-conditions-tooltip = [bold]Conditions:[/bold]
|
||||
{$conditions}
|
||||
|
||||
## Composite conditions
|
||||
trait-condition-any-of = Any of the following must be true:
|
||||
• {$requirements}
|
||||
{$requirements}
|
||||
|
||||
## Species conditions
|
||||
trait-condition-species-is = You must be a [color=yellow]{$species}[/color].
|
||||
trait-condition-species-not = You must not be a [color=yellow]{$species}[/color].
|
||||
trait-condition-species-is = You must be {INDEFINITE($species)} [color=yellow]{$species}[/color].
|
||||
trait-condition-species-not = You must not be {INDEFINITE($species)} [color=yellow]{$species}[/color].
|
||||
|
||||
## Job conditions
|
||||
trait-condition-job-is = You must be a [color={$color}]{$job}[/color].
|
||||
trait-condition-job-not = You must not be a [color={$color}]{$job}[/color].
|
||||
trait-condition-job-is = You must be {INDEFINITE($job)} [color={$color}]{$job}[/color].
|
||||
trait-condition-job-not = You must not be {INDEFINITE($job)} [color={$color}]{$job}[/color].
|
||||
|
||||
## Department conditions
|
||||
trait-condition-department-is = You must be in the [color={$color}]{$department}[/color] department.
|
||||
trait-condition-department-not = You must not be in the [color={$color}]{$department}[/color] department.
|
||||
|
||||
## HasComp borg conditions
|
||||
trait-condition-borg-not = You must not be a [color=yellow]borg[/color].
|
||||
|
||||
# Antag conditions
|
||||
trait-condition-antag-is = Must be eligible for [color=red]{$antag}[/color] antag role.
|
||||
trait-condition-antag-not = Must not be eligible for [color=red]{$antag}[/color] antag role.
|
||||
|
||||
# Trait requirements and conflicts
|
||||
trait-condition-trait-has-not = Must not have the trait [color=yellow]{$trait}[/color].
|
||||
trait-condition-trait-has = Must have the trait [color=yellow]{$trait}[/color].
|
||||
|
||||
@@ -15,11 +15,11 @@ trait-irish-accent-desc = Ya sap! Seems you got a pet hate fer rubbish!
|
||||
|
||||
trait-ultravision-name = Ultraviolet vision
|
||||
trait-ultravision-desc = Whether through custom bionic eyes, random mutation,
|
||||
or being a Harpy, you perceive the world with ultraviolet light.
|
||||
or a bad organ replacement, you perceive the world with ultraviolet light.
|
||||
|
||||
trait-deuteranopia-name = Deuteranopia
|
||||
trait-deuteranopia-desc = Whether through custom bionic eyes, random mutation,
|
||||
or being a Vulpkanin, you have red-green colour blindness.
|
||||
or a bad organ replacement, you have red-green colour blindness.
|
||||
|
||||
trait-hushed-name = Hushed
|
||||
trait-hushed-desc = You are unable to speak louder than a whisper.
|
||||
@@ -52,6 +52,17 @@ trait-amputee-right-arm-desc =
|
||||
Your right arm is missing!
|
||||
(Note: Amputee traits do not appear in the character editor preview!)
|
||||
|
||||
trait-amputee-left-leg-name = Amputee (Leg, Left)
|
||||
trait-amputee-left-leg-desc =
|
||||
Your left leg is missing!
|
||||
(Note: Amputee traits do not appear in the character editor preview!)
|
||||
|
||||
trait-amputee-right-leg-name = Amputee (Leg, Right)
|
||||
trait-amputee-right-leg-desc =
|
||||
Your right leg is missing!
|
||||
(Note: Amputee traits do not appear in the character editor preview!)
|
||||
(Note: Amputee traits do not appear in the character editor preview!)(Note: Amputee traits do not appear in the character editor preview!)(Note: Amputee traits do not appear in the character editor preview!)(Note: Amputee traits do not appear in the character editor preview!)(Note: Amputee traits do not appear in the character editor preview!)(Note: Amputee traits do not appear in the character editor preview!)(Note: Amputee traits do not appear in the character editor preview!)(Note: Amputee traits do not appear in the character editor preview!)(Note: Amputee traits do not appear in the character editor preview!)(Note: Amputee traits do not appear in the character editor preview!)(Note: Amputee traits do not appear in the character editor preview!)(Note: Amputee traits do not appear in the character editor preview!)
|
||||
|
||||
trait-redshirt-name = Redshirt
|
||||
trait-redshirt-desc =
|
||||
Sets your death threshold to 100 damage. You no longer have a critical state.
|
||||
|
||||
@@ -6,6 +6,18 @@
|
||||
description: trait-blindness-desc
|
||||
category: Disabilities
|
||||
conditions:
|
||||
- !type:HasTraitCondition
|
||||
trait: PoorVision
|
||||
invert: true
|
||||
- !type:HasTraitCondition
|
||||
trait: Deuteranopia
|
||||
invert: true
|
||||
- !type:HasTraitCondition
|
||||
trait: UltraVision
|
||||
invert: true
|
||||
- !type:HasTraitCondition
|
||||
trait: Monochromacy
|
||||
invert: true
|
||||
- !type:HasCompCondition
|
||||
component: Blindable
|
||||
effects:
|
||||
@@ -21,6 +33,9 @@
|
||||
description: trait-poor-vision-desc
|
||||
category: Disabilities
|
||||
conditions:
|
||||
- !type:HasTraitCondition
|
||||
trait: Blindness
|
||||
invert: true
|
||||
- !type:HasCompCondition
|
||||
component: Blindable
|
||||
effects:
|
||||
@@ -36,6 +51,16 @@
|
||||
name: trait-monochromacy-name
|
||||
description: trait-monochromacy-desc
|
||||
category: Disabilities
|
||||
conditions:
|
||||
- !type:HasTraitCondition
|
||||
trait: Blindness
|
||||
invert: true
|
||||
- !type:HasTraitCondition
|
||||
trait: Deuteranopia
|
||||
invert: true
|
||||
- !type:HasTraitCondition
|
||||
trait: UltraVision
|
||||
invert: true
|
||||
effects:
|
||||
- !type:AddCompsEffect
|
||||
components:
|
||||
@@ -46,6 +71,19 @@
|
||||
name: trait-deuteranopia-name
|
||||
description: trait-deuteranopia-desc
|
||||
category: Disabilities
|
||||
conditions:
|
||||
- !type:HasTraitCondition
|
||||
trait: Blindness
|
||||
invert: true
|
||||
- !type:HasTraitCondition
|
||||
trait: Monochromacy
|
||||
invert: true
|
||||
- !type:HasTraitCondition
|
||||
trait: UltraVision
|
||||
invert: true
|
||||
- !type:IsSpeciesCondition
|
||||
species: Vulpkanin
|
||||
invert: true
|
||||
effects:
|
||||
- !type:AddCompsEffect
|
||||
components:
|
||||
@@ -56,6 +94,19 @@
|
||||
name: trait-ultravision-name
|
||||
description: trait-ultravision-desc
|
||||
category: Disabilities
|
||||
conditions:
|
||||
- !type:HasTraitCondition
|
||||
trait: Blindness
|
||||
invert: true
|
||||
- !type:HasTraitCondition
|
||||
trait: Monochromacy
|
||||
invert: true
|
||||
- !type:HasTraitCondition
|
||||
trait: Deuteranopia
|
||||
invert: true
|
||||
- !type:IsSpeciesCondition
|
||||
species: Harpy
|
||||
invert: true
|
||||
effects:
|
||||
- !type:AddCompsEffect
|
||||
components:
|
||||
@@ -78,28 +129,40 @@
|
||||
name: trait-amputee-left-arm-name
|
||||
description: trait-amputee-left-arm-desc
|
||||
category: Disabilities
|
||||
conflicts:
|
||||
- ArmAmputeeRight # TODO: AmputeeComponent doesn't stack, should be an effect instead
|
||||
effects:
|
||||
- !type:AddCompsEffect
|
||||
components:
|
||||
- type: Amputee
|
||||
removeBodyPart: Arm
|
||||
partSymmetry: Left
|
||||
- !type:RemoveBodyPartEffect
|
||||
part: Arm
|
||||
symmetry: Left
|
||||
|
||||
- type: trait
|
||||
id: ArmAmputeeRight
|
||||
name: trait-amputee-right-arm-name
|
||||
description: trait-amputee-right-arm-desc
|
||||
category: Disabilities
|
||||
conflicts:
|
||||
- ArmAmputeeLeft
|
||||
effects:
|
||||
- !type:AddCompsEffect
|
||||
components:
|
||||
- type: Amputee
|
||||
removeBodyPart: Arm
|
||||
partSymmetry: Right
|
||||
- !type:RemoveBodyPartEffect
|
||||
part: Arm
|
||||
symmetry: Right
|
||||
|
||||
- type: trait
|
||||
id: LegAmputeeLeft
|
||||
name: trait-amputee-left-leg-name
|
||||
description: trait-amputee-left-leg-desc
|
||||
category: Disabilities
|
||||
effects:
|
||||
- !type:RemoveBodyPartEffect
|
||||
part: Leg
|
||||
symmetry: Left
|
||||
|
||||
- type: trait
|
||||
id: LegAmputeeRight
|
||||
name: trait-amputee-right-leg-name
|
||||
description: trait-amputee-right-leg-desc
|
||||
category: Disabilities
|
||||
effects:
|
||||
- !type:RemoveBodyPartEffect
|
||||
part: Leg
|
||||
symmetry: Right
|
||||
|
||||
- type: trait
|
||||
id: Dysgraphia
|
||||
@@ -116,6 +179,10 @@
|
||||
name: trait-painnumbness-name
|
||||
description: trait-painnumbness-desc
|
||||
category: Disabilities
|
||||
conditions:
|
||||
- !type:IsSpeciesCondition
|
||||
species: IPC
|
||||
invert: true
|
||||
effects:
|
||||
- !type:AddCompsEffect
|
||||
components:
|
||||
@@ -126,6 +193,10 @@
|
||||
name: trait-hemophilia-name
|
||||
description: trait-hemophilia-desc
|
||||
category: Disabilities
|
||||
conditions:
|
||||
- !type:IsSpeciesCondition
|
||||
species: IPC
|
||||
invert: true
|
||||
effects:
|
||||
- !type:AddCompsEffect
|
||||
components:
|
||||
|
||||
@@ -94,10 +94,6 @@
|
||||
- !type:HasCompCondition
|
||||
component: Silicon
|
||||
invert: true
|
||||
- !type:HasCompCondition
|
||||
component: BorgChassis
|
||||
invert: true
|
||||
tooltip: trait-condition-borg-not
|
||||
- !type:IsSpeciesCondition
|
||||
species: IPC
|
||||
invert: true
|
||||
|
||||
@@ -52,11 +52,6 @@
|
||||
description: trait-muted-desc
|
||||
category: Accents
|
||||
cost: 2
|
||||
conditions:
|
||||
- !type:HasCompCondition
|
||||
component: BorgChassis
|
||||
invert: true
|
||||
tooltip: trait-condition-borg-not
|
||||
effects:
|
||||
- !type:AddCompsEffect
|
||||
components:
|
||||
@@ -165,11 +160,6 @@
|
||||
description: trait-hushed-desc
|
||||
category: Accents
|
||||
cost: 0
|
||||
conditions:
|
||||
- !type:HasCompCondition
|
||||
component: BorgChassis
|
||||
invert: true
|
||||
tooltip: trait-condition-borg-not
|
||||
effects:
|
||||
- !type:AddCompsEffect
|
||||
components:
|
||||
|
||||
Reference in New Issue
Block a user