Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a1629c0
rodentia species
portfiend Jun 25, 2026
b487fdc
add sprites
portfiend Jun 25, 2026
2eafc97
add detritivore metabolizer
portfiend Jun 25, 2026
b7f06ed
add rodentia entity prototypes
portfiend Jun 25, 2026
a6a8983
add rodentia names
portfiend Jun 25, 2026
4e78926
allow rodentia to use hair
portfiend Jun 25, 2026
446b976
i take penis out
portfiend Jun 25, 2026
723dc9a
allow detritivores to digest mold and gastrotoxin
portfiend Jun 25, 2026
8717b2c
ok i give them navels
portfiend Jun 26, 2026
57ef291
oops
portfiend Jun 26, 2026
b0101f7
rodentia todolist
portfiend Jun 27, 2026
42f57a4
allow detritivores to digest vent crud + trash
portfiend Jun 27, 2026
334de8c
Revert "allow detritivores to digest vent crud + trash"
portfiend Jul 13, 2026
4647204
Revert "allow detritivores to digest mold and gastrotoxin"
portfiend Jul 13, 2026
5b7fc35
Revert "add detritivore metabolizer"
portfiend Jul 13, 2026
87b4086
Merge branch 'master' into species/rodentia
portfiend Jul 13, 2026
edbc822
rodentia hunger and mob thresholds
portfiend Jul 13, 2026
2711450
damage stuff
portfiend Jul 13, 2026
1f03520
guidebook
portfiend Jul 13, 2026
f84b26f
add tool interaction system
portfiend Jul 19, 2026
da98914
add tool interaction toggle actions
portfiend Jul 19, 2026
7f8774d
hang on let me try something else
portfiend Jul 25, 2026
79d78a2
refactor tool organs
portfiend Aug 1, 2026
068e7ce
let rodentia rummage through vneding machines
portfiend Aug 1, 2026
021b388
trashify vending machibe tables
portfiend Aug 1, 2026
1a07f79
oops
portfiend Aug 1, 2026
61399e8
tweak loot pools
portfiend Aug 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Content.Client/_DEN/Tool/EntitySystems/ToolOrganSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Content.Shared._DEN.Tool.Components;

namespace Content.Client._DEN.Tool.Components;

public sealed partial class ToolOrganSystem : SharedToolOrganSystem
{ }
6 changes: 6 additions & 0 deletions Content.Server/_DEN/Tool/EntitySystems/ToolOrganSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Content.Shared._DEN.Tool.Components;

namespace Content.Server._DEN.Tool.Components;

public sealed partial class ToolOrganSystem : SharedToolOrganSystem
{ }
2 changes: 1 addition & 1 deletion Content.Shared/Nutrition/Components/HungerComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public sealed partial class HungerComponent : Component
/// </summary>
/// <remarks>Any time this is modified, <see cref="HungerSystem.SetAuthoritativeHungerValue"/> should be called.</remarks>
[DataField("baseDecayRate"), ViewVariables(VVAccess.ReadWrite)]
public float BaseDecayRate = 0.01666666666f;
public float BaseDecayRate = BaseHumanoidHungerRate; // DEN: 0.01666666666f -> BaseHumanoidHungerRate

/// <summary>
/// The actual amount at which <see cref="LastAuthoritativeHungerValue"/> decays.
Expand Down
1 change: 1 addition & 0 deletions Content.Shared/Nutrition/EntitySystems/HungerSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ private void DoHungerThresholdEffects(EntityUid uid, HungerComponent? component
if (component.HungerThresholdDecayModifiers.TryGetValue(component.CurrentThreshold, out var modifier))
{
component.ActualDecayRate = component.BaseDecayRate * modifier;
component.ActualDecayRate *= component.DecayRateMultiplier; // DEN - Decay rate multipliers
DirtyField(uid, component, nameof(HungerComponent.ActualDecayRate));
SetAuthoritativeHungerValue((uid, component), GetHunger(component));
}
Expand Down
38 changes: 38 additions & 0 deletions Content.Shared/_DEN/Nutrition/Components/HungerComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Content.Shared.Nutrition.EntitySystems;

namespace Content.Shared.Nutrition.Components;

public sealed partial class HungerComponent : Component
{
/// <summary>
/// How many tick intervals (<see cref="DropTickInterval" />) it takes to drop hunger by <see cref="HungerDropAmount"/>
/// </summary>
public const float HungerDropTimeInIntervals = 50.0f;

/// <summary>
/// Amount of hunger lost in <see cref="HungerDropTimeInIntervals"/>.
/// </summary>
public const float HungerDropAmount = 50.0f;

/// <summary>
/// How many ticks per "tick interval". 60 ticks at 1 tick / second = 1 minute.
/// </summary>
/// <remarks>
/// This is a bad way of doing it but the better way is a huge breaking change
/// </remarks>
private const float DropTickInterval = 60.0f;

/// <summary>
/// The hunger rate that humanoid species are balanced around.
/// </summary>
private static readonly float BaseHumanoidHungerRate = HungerDropAmount / (HungerDropTimeInIntervals * DropTickInterval);

/// <summary>
/// A flat multiplier applied to <see cref="BaseDecayRate"/>.
/// </summary>
/// <remarks>
/// This value ideally should not change - this is to make species code more intuitive to write.
/// </remarks>
[DataField, Access(typeof(HungerSystem), Friend = AccessPermissions.Read)]
public float DecayRateMultiplier = 1.0f;
}
68 changes: 68 additions & 0 deletions Content.Shared/_DEN/Tool/Components/ToolOrganComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;

namespace Content.Shared._DEN.Tool.Components;

/// <summary>
/// An organ that contains an entity representing a specific capability of the organ.
/// For example: giving an organ a "sharp" tool if you want this organ to be usable for cutting.
/// </summary>
[RegisterComponent]
[NetworkedComponent, AutoGenerateComponentState]
public sealed partial class ToolOrganComponent : Component
{
/// <summary>
/// Whether or not tool interactions are enabled.
/// </summary>
[DataField, AutoNetworkedField]
public bool IsToolEnabled = false;

/// <summary>
/// The string ID used for the item container.
/// </summary>
[DataField]
public string ContainerId = "toggleable_tool";

/// <summary>
/// Text displayed when the action is enabled.
/// </summary>
[DataField]
public LocId? ToggleOnText = "action-popup-tool-interaction-enabled";

/// <summary>
/// Text displayed when the action is disabled.
/// </summary>
[DataField]
public LocId? ToggleOffText = "action-popup-tool-interaction-disabled";

/// <summary>
/// The entity to use for the toggle action.
/// </summary>
[DataField]
public EntProtoId ToolToggleAction = "ActionToolInteractionToggle";

/// <summary>
/// The entity to put in the mob's hand when this is toggled.
/// </summary>
[DataField(required: true)]
public EntProtoId Item = string.Empty;

/// <summary>
/// The entity representing the toggle action.
/// </summary>
[DataField, AutoNetworkedField]
public EntityUid? ToolToggleActionEntity;

/// <summary>
/// The container holding the tool item.
/// </summary>
[ViewVariables]
public Container? ItemContainer;

/// <summary>
/// The entity representing the toggleable item.
/// </summary>
[ViewVariables]
public EntityUid? ItemEntity;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

namespace Content.Shared._DEN.Tool.Components;

/// <summary>
/// Represents an entity that can use <see cref="ToolOrganComponent"/>s.
/// </summary>
[RegisterComponent]
public sealed partial class ToolOrganPerformerComponent : Component
{ }
149 changes: 149 additions & 0 deletions Content.Shared/_DEN/Tool/EntitySystems/SharedToolOrganSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using Content.Shared.Actions;
using Content.Shared.Body;
using Content.Shared.CombatMode;
using Content.Shared.Inventory.VirtualItem;
using Content.Shared.Popups;
using Robust.Shared.Containers;
using Robust.Shared.Utility;

namespace Content.Shared._DEN.Tool.Components;

/// <summary>
/// System logic for organs that contain pseudo-tools.
/// Using these tools is toggled via action.
/// </summary>
public abstract partial class SharedToolOrganSystem : EntitySystem
{
[Dependency] private SharedActionsSystem _actions = default!;
[Dependency] private BodySystem _body = default!;
[Dependency] private SharedCombatModeSystem _combatMode = default!;
[Dependency] private SharedContainerSystem _container = default!;
[Dependency] private SharedVirtualItemSystem _virtualItem = default!;
[Dependency] private SharedPopupSystem _popup = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<BodyComponent, GetToolOrganEvent>(_body.RelayEvent);

SubscribeLocalEvent<ToolOrganComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<ToolOrganComponent, OrganGotInsertedEvent>(OnOrganGotInserted);
SubscribeLocalEvent<ToolOrganComponent, OrganGotRemovedEvent>(OnOrganRemoved);
SubscribeLocalEvent<ToolOrganComponent, BodyRelayedEvent<GetToolOrganEvent>>(OnGetToolOrgan);

SubscribeLocalEvent<ToolOrganPerformerComponent, ToggleToolInteractionActionEvent>(OnPerformAction);
}

private void OnComponentInit(Entity<ToolOrganComponent> ent, ref ComponentInit args)
{
ent.Comp.ItemContainer = _container.EnsureContainer<Container>(ent.Owner, ent.Comp.ContainerId);

var toolEnt = PredictedSpawnAtPosition(ent.Comp.Item, Transform(ent.Owner).Coordinates);
if (!_container.Insert(toolEnt, ent.Comp.ItemContainer))
{
DebugTools.Assert($"Could not insert {ToPrettyString(toolEnt)} into {ToPrettyString(ent)}. This is likely due to broken YAML!");
PredictedDel(toolEnt);
}
else
ent.Comp.ItemEntity = toolEnt;

Dirty(ent);
}

private void OnOrganGotInserted(Entity<ToolOrganComponent> ent, ref OrganGotInsertedEvent args)
{
_actions.AddAction(args.Target, ref ent.Comp.ToolToggleActionEntity, ent.Comp.ToolToggleAction);
EnsureComp<ToolOrganPerformerComponent>(ent.Owner);
Dirty(ent);
}

private void OnOrganRemoved(Entity<ToolOrganComponent> ent, ref OrganGotRemovedEvent args)
{
_actions.RemoveAction(args.Target, ent.Comp.ToolToggleActionEntity);
TrySetToolInteractionEnabled(ent.Owner, false);
Dirty(ent);
}

private void OnPerformAction(Entity<ToolOrganPerformerComponent> ent, ref ToggleToolInteractionActionEvent args)
{
if (args.Handled)
return;

var ev = new GetToolOrganEvent(Action: args.Action, Mob: ent.Owner);
RaiseLocalEvent(ent.Owner, ref ev);

if (ev.Handled)
args.Handled = true;
}

private void OnGetToolOrgan(Entity<ToolOrganComponent> ent, ref BodyRelayedEvent<GetToolOrganEvent> args)
{
if (args.Args.Handled || args.Args.Action != ent.Comp.ToolToggleActionEntity)
return;

var mob = args.Args.Mob;
var value = !ent.Comp.IsToolEnabled;
if (!TrySetToolInteractionEnabled(ent.AsNullable(), value, mob))
return;

// Display message when toggled.
var msgLocId = value ? ent.Comp.ToggleOnText : ent.Comp.ToggleOffText;
if (msgLocId != null)
_popup.PopupClient(Loc.GetString(msgLocId), mob, mob);

args.Args = args.Args with { Handled = true };
}

/// <summary>
/// Sets whether or not this entity can use their empty-hand interactions to perform tool actions.
/// </summary>
/// <param name="ent">The entity to set tool interaction status.</param>
/// <param name="enabled">If tool interactions should be enabled.</param>
public bool TrySetToolInteractionEnabled(Entity<ToolOrganComponent?> ent, bool enabled, EntityUid? mob = null)
{
if (!Resolve(ent.Owner, ref ent.Comp) // Not a tool organ
|| ent.Comp.IsToolEnabled == enabled // Value is already set to this
|| ent.Comp.ItemEntity == null // No tool entity
|| TerminatingOrDeleted(ent.Comp.ItemEntity) // Tool entity is being deleted
|| !TryComp<OrganComponent>(ent.Owner, out var organ) // Not an organ
|| organ.Body == null) // Not attached to a mob
return false;

mob ??= organ.Body.Value;
var toolEnt = ent.Comp.ItemEntity.Value;

// Put the entity in your hands as a virtual entity
if (enabled && !_virtualItem.TrySpawnVirtualItemInHand(toolEnt, mob.Value, dropOthers: false))
return false;
else if (!enabled)
_virtualItem.DeleteInHandsMatching(mob.Value, toolEnt);

// Set the enabled state of the tool
ent.Comp.IsToolEnabled = enabled;
Dirty(ent);

if (ent.Comp.ToolToggleActionEntity != null)
_actions.SetToggled(ent.Comp.ToolToggleActionEntity, enabled);

if (enabled && TryComp<CombatModeComponent>(mob, out var combatMode))
_combatMode.SetInCombatMode(mob.Value, false, combatMode);

return true;
}
}

/// <summary>
/// Event fired when the "toggle tool interaction" action is used.
/// </summary>
public sealed partial class ToggleToolInteractionActionEvent : InstantActionEvent
{ }

/// <summary>
/// Event fired to attempt to get the appropriate tool entity when it is toggled on.
/// </summary>
/// <param name="Mob">The mob that is using this tool entity.</param>
/// <param name="Action">The action that triggered this.</param>
/// <param name="Handled">Whether or not this event has been handled.</param>
[ByRefEvent]
public record struct GetToolOrganEvent(EntityUid Action, EntityUid Mob, bool Handled = false);
Loading
Loading