-
Notifications
You must be signed in to change notification settings - Fork 473
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #60 from new-frontiers-14/cherrypick_july
Little cherry pick for some fun stuff early in july
- Loading branch information
Showing
162 changed files
with
1,682 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using Content.Shared.Nutrition.AnimalHusbandry; | ||
using Robust.Client.GameObjects; | ||
|
||
namespace Content.Client.Nutrition.EntitySystems; | ||
|
||
/// <summary> | ||
/// This handles visuals for <see cref="InfantComponent"/> | ||
/// </summary> | ||
public sealed class InfantSystem : EntitySystem | ||
{ | ||
/// <inheritdoc/> | ||
public override void Initialize() | ||
{ | ||
SubscribeLocalEvent<InfantComponent, ComponentStartup>(OnStartup); | ||
SubscribeLocalEvent<InfantComponent, ComponentShutdown>(OnShutdown); | ||
} | ||
|
||
private void OnStartup(EntityUid uid, InfantComponent component, ComponentStartup args) | ||
{ | ||
if (!TryComp<SpriteComponent>(uid, out var sprite)) | ||
return; | ||
|
||
component.DefaultScale = sprite.Scale; | ||
sprite.Scale = component.VisualScale; | ||
} | ||
|
||
private void OnShutdown(EntityUid uid, InfantComponent component, ComponentShutdown args) | ||
{ | ||
if (!TryComp<SpriteComponent>(uid, out var sprite)) | ||
return; | ||
|
||
sprite.Scale = component.DefaultScale; | ||
} | ||
} |
253 changes: 253 additions & 0 deletions
253
Content.Server/Nutrition/EntitySystems/AnimalHusbandrySystem.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,253 @@ | ||
using Content.Server.Administration.Logs; | ||
using Content.Server.Interaction.Components; | ||
using Content.Server.Mind.Components; | ||
using Content.Server.Nutrition.Components; | ||
using Content.Server.Popups; | ||
using Content.Shared.Database; | ||
using Content.Shared.IdentityManagement; | ||
using Content.Shared.Mobs.Systems; | ||
using Content.Shared.Nutrition.AnimalHusbandry; | ||
using Content.Shared.Nutrition.Components; | ||
using Content.Shared.Nutrition.EntitySystems; | ||
using Content.Shared.Storage; | ||
using Robust.Server.GameObjects; | ||
using Robust.Shared.Random; | ||
using Robust.Shared.Timing; | ||
|
||
namespace Content.Server.Nutrition.EntitySystems; | ||
|
||
/// <summary> | ||
/// This handles logic and interactions related to <see cref="ReproductiveComponent"/> | ||
/// </summary> | ||
public sealed class AnimalHusbandrySystem : EntitySystem | ||
{ | ||
[Dependency] private readonly IAdminLogManager _adminLog = default!; | ||
[Dependency] private readonly IGameTiming _timing = default!; | ||
[Dependency] private readonly IRobustRandom _random = default!; | ||
[Dependency] private readonly EntityLookupSystem _entityLookup = default!; | ||
[Dependency] private readonly HungerSystem _hunger = default!; | ||
[Dependency] private readonly MetaDataSystem _metaData = default!; | ||
[Dependency] private readonly MobStateSystem _mobState = default!; | ||
[Dependency] private readonly PopupSystem _popup = default!; | ||
[Dependency] private readonly SharedAudioSystem _audio = default!; | ||
|
||
private readonly HashSet<EntityUid> _failedAttempts = new(); | ||
|
||
/// <inheritdoc/> | ||
public override void Initialize() | ||
{ | ||
SubscribeLocalEvent<ReproductiveComponent, EntityUnpausedEvent>(OnUnpaused); | ||
SubscribeLocalEvent<ReproductiveComponent, MindAddedMessage>(OnMindAdded); | ||
SubscribeLocalEvent<InfantComponent, EntityUnpausedEvent>(OnInfantUnpaused); | ||
SubscribeLocalEvent<InfantComponent, ComponentStartup>(OnInfantStartup); | ||
SubscribeLocalEvent<InfantComponent, ComponentShutdown>(OnInfantShutdown); | ||
} | ||
|
||
private void OnUnpaused(EntityUid uid, ReproductiveComponent component, ref EntityUnpausedEvent args) | ||
{ | ||
component.NextBreedAttempt += args.PausedTime; | ||
} | ||
|
||
private void OnInfantUnpaused(EntityUid uid, InfantComponent component, ref EntityUnpausedEvent args) | ||
{ | ||
component.InfantEndTime += args.PausedTime; | ||
} | ||
|
||
// we express EZ-pass terminate the pregnancy if a player takes the role | ||
private void OnMindAdded(EntityUid uid, ReproductiveComponent component, MindAddedMessage args) | ||
{ | ||
component.Gestating = false; | ||
component.GestationEndTime = null; | ||
} | ||
|
||
private void OnInfantStartup(EntityUid uid, InfantComponent component, ComponentStartup args) | ||
{ | ||
var meta = MetaData(uid); | ||
component.OriginalName = meta.EntityName; | ||
_metaData.SetEntityName(uid, Loc.GetString("infant-name-prefix", ("name", meta.EntityName)), meta); | ||
} | ||
|
||
private void OnInfantShutdown(EntityUid uid, InfantComponent component, ComponentShutdown args) | ||
{ | ||
_metaData.SetEntityName(uid, component.OriginalName); | ||
} | ||
|
||
/// <summary> | ||
/// Attempts to breed the entity with a valid | ||
/// partner nearby. | ||
/// </summary> | ||
public bool TryReproduceNearby(EntityUid uid, ReproductiveComponent? component = null) | ||
{ | ||
if (!Resolve(uid, ref component)) | ||
return false; | ||
|
||
var xform = Transform(uid); | ||
var partners = _entityLookup.GetComponentsInRange<ReproductivePartnerComponent>(xform.Coordinates, component.BreedRange); | ||
foreach (var comp in partners) | ||
{ | ||
var partner = comp.Owner; | ||
if (TryReproduce(uid, partner, component)) | ||
return true; | ||
|
||
// exit early if a valid attempt failed | ||
if (_failedAttempts.Contains(uid)) | ||
return false; | ||
} | ||
return false; | ||
} | ||
|
||
/// <summary> | ||
/// Attempts to breed an entity with | ||
/// the specified partner. | ||
/// </summary> | ||
public bool TryReproduce(EntityUid uid, EntityUid partner, ReproductiveComponent? component = null) | ||
{ | ||
if (!Resolve(uid, ref component)) | ||
return false; | ||
|
||
if (uid == partner) | ||
return false; | ||
|
||
if (!CanReproduce(uid, component)) | ||
return false; | ||
|
||
if (!IsValidPartner(uid, partner, component)) | ||
return false; | ||
|
||
// if the partner is valid, yet it fails the random check | ||
// invalidate the entity from further attempts this tick | ||
// in order to reduce total possible pairs. | ||
if (!_random.Prob(component.BreedChance)) | ||
{ | ||
_failedAttempts.Add(uid); | ||
_failedAttempts.Add(partner); | ||
return false; | ||
} | ||
|
||
// this is kinda wack but it's the only sound associated with most animals | ||
if (TryComp<InteractionPopupComponent>(uid, out var interactionPopup)) | ||
_audio.PlayPvs(interactionPopup.InteractSuccessSound, uid); | ||
|
||
_hunger.ModifyHunger(uid, -component.HungerPerBirth); | ||
_hunger.ModifyHunger(partner, -component.HungerPerBirth); | ||
|
||
component.GestationEndTime = _timing.CurTime + component.GestationDuration; | ||
component.Gestating = true; | ||
_adminLog.Add(LogType.Action, $"{ToPrettyString(uid)} (carrier) and {ToPrettyString(partner)} (partner) successfully bred."); | ||
return true; | ||
} | ||
|
||
/// <summary> | ||
/// Checks if an entity satisfies | ||
/// the conditions to be able to breed. | ||
/// </summary> | ||
public bool CanReproduce(EntityUid uid, ReproductiveComponent? component = null) | ||
{ | ||
if (_failedAttempts.Contains(uid)) | ||
return false; | ||
|
||
if (Resolve(uid, ref component, false) && component.Gestating) | ||
return false; | ||
|
||
if (HasComp<InfantComponent>(uid)) | ||
return false; | ||
|
||
if (_mobState.IsIncapacitated(uid)) | ||
return false; | ||
|
||
if (TryComp<HungerComponent>(uid, out var hunger) && _hunger.GetHungerThreshold(hunger) < HungerThreshold.Okay) | ||
return false; | ||
|
||
if (TryComp<ThirstComponent>(uid, out var thirst) && thirst.CurrentThirstThreshold < ThirstThreshold.Okay) | ||
return false; | ||
|
||
return true; | ||
} | ||
|
||
/// <summary> | ||
/// Checks if a given entity is a valid partner. | ||
/// Does not include the random check, for sane API reasons. | ||
/// </summary> | ||
public bool IsValidPartner(EntityUid uid, EntityUid partner, ReproductiveComponent? component = null) | ||
{ | ||
if (!Resolve(uid, ref component)) | ||
return false; | ||
|
||
if (!CanReproduce(partner)) | ||
return false; | ||
|
||
return component.PartnerWhitelist.IsValid(partner); | ||
} | ||
|
||
/// <summary> | ||
/// Gives birth to offspring and | ||
/// resets the parent entity. | ||
/// </summary> | ||
public void Birth(EntityUid uid, ReproductiveComponent? component = null) | ||
{ | ||
if (!Resolve(uid, ref component)) | ||
return; | ||
|
||
// this is kinda wack but it's the only sound associated with most animals | ||
if (TryComp<InteractionPopupComponent>(uid, out var interactionPopup)) | ||
_audio.PlayPvs(interactionPopup.InteractSuccessSound, uid); | ||
|
||
var xform = Transform(uid); | ||
var spawns = EntitySpawnCollection.GetSpawns(component.Offspring, _random); | ||
foreach (var spawn in spawns) | ||
{ | ||
var offspring = Spawn(spawn, xform.Coordinates.Offset(_random.NextVector2(0.3f))); | ||
if (component.MakeOffspringInfant) | ||
{ | ||
var infant = AddComp<InfantComponent>(offspring); | ||
infant.InfantEndTime = _timing.CurTime + infant.InfantDuration; | ||
} | ||
_adminLog.Add(LogType.Action, $"{ToPrettyString(uid)} gave birth to {ToPrettyString(offspring)}."); | ||
} | ||
|
||
_popup.PopupEntity(Loc.GetString(component.BirthPopup, ("parent", Identity.Entity(uid, EntityManager))), uid); | ||
|
||
component.Gestating = false; | ||
component.GestationEndTime = null; | ||
} | ||
|
||
public override void Update(float frameTime) | ||
{ | ||
base.Update(frameTime); | ||
|
||
HashSet<EntityUid> birthQueue = new(); | ||
_failedAttempts.Clear(); | ||
|
||
var query = EntityQueryEnumerator<ReproductiveComponent>(); | ||
while (query.MoveNext(out var uid, out var reproductive)) | ||
{ | ||
if (reproductive.GestationEndTime != null && _timing.CurTime >= reproductive.GestationEndTime) | ||
{ | ||
birthQueue.Add(uid); | ||
} | ||
|
||
if (_timing.CurTime < reproductive.NextBreedAttempt) | ||
continue; | ||
reproductive.NextBreedAttempt += _random.Next(reproductive.MinBreedAttemptInterval, reproductive.MaxBreedAttemptInterval); | ||
|
||
// no. | ||
if (HasComp<ActorComponent>(uid) || TryComp<MindContainerComponent>(uid, out var mind) && mind.HasMind) | ||
continue; | ||
|
||
TryReproduceNearby(uid, reproductive); | ||
} | ||
|
||
foreach (var queued in birthQueue) | ||
{ | ||
Birth(queued); | ||
} | ||
|
||
var infantQuery = EntityQueryEnumerator<InfantComponent>(); | ||
while (infantQuery.MoveNext(out var uid, out var infant)) | ||
{ | ||
if (_timing.CurTime < infant.InfantEndTime) | ||
continue; | ||
RemCompDeferred(uid, infant); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
Content.Shared/Nutrition/AnimalHusbandry/InfantComponent.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
using Robust.Shared.GameStates; | ||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; | ||
|
||
namespace Content.Shared.Nutrition.AnimalHusbandry; | ||
|
||
/// <summary> | ||
/// This is used for marking entities as infants. | ||
/// Infants have half the size, visually, and cannot breed. | ||
/// </summary> | ||
[RegisterComponent, NetworkedComponent] | ||
public sealed class InfantComponent : Component | ||
{ | ||
/// <summary> | ||
/// How long the entity remains an infant. | ||
/// </summary> | ||
[DataField("infantDuration")] | ||
public TimeSpan InfantDuration = TimeSpan.FromMinutes(3); | ||
|
||
/// <summary> | ||
/// The base scale of the entity | ||
/// </summary> | ||
[DataField("defaultScale")] | ||
public Vector2 DefaultScale = Vector2.One; | ||
|
||
/// <summary> | ||
/// The size difference of the entity while it's an infant. | ||
/// </summary> | ||
[DataField("visualScale")] | ||
public Vector2 VisualScale = new(.5f, .5f); | ||
|
||
/// <summary> | ||
/// When the entity will stop being an infant. | ||
/// </summary> | ||
[DataField("infantEndTime", customTypeSerializer: typeof(TimeOffsetSerializer))] | ||
public TimeSpan InfantEndTime; | ||
|
||
/// <summary> | ||
/// The entity's name before the "baby" prefix is added. | ||
/// </summary> | ||
[DataField("originalName")] | ||
public string OriginalName = string.Empty; | ||
} |
Oops, something went wrong.