Skip to content

Commit

Permalink
Merge pull request #60 from new-frontiers-14/cherrypick_july
Browse files Browse the repository at this point in the history
Little cherry pick for some fun stuff early in july
  • Loading branch information
Cheackraze committed Jul 21, 2023
2 parents cf2bc96 + c8f8806 commit 0777861
Show file tree
Hide file tree
Showing 162 changed files with 1,682 additions and 13 deletions.
34 changes: 34 additions & 0 deletions Content.Client/Nutrition/EntitySystems/InfantSystem.cs
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 Content.Server/Nutrition/EntitySystems/AnimalHusbandrySystem.cs
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);
}
}
}
7 changes: 7 additions & 0 deletions Content.Shared/Blocking/BlockingSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@ public bool StartBlocking(EntityUid item, BlockingComponent component, EntityUid
return false;
}

// Don't allow someone to block if they're not holding the shield
if(!_handsSystem.IsHolding(user, item, out _))
{
CantBlockError(user);
return false;
}

//Don't allow someone to block if someone else is on the same tile
var playerTileRef = xform.Coordinates.GetTileRef();
if (playerTileRef != null)
Expand Down
9 changes: 1 addition & 8 deletions Content.Shared/Construction/Conditions/NoWindowsInTile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using Content.Shared.Tag;
using JetBrains.Annotations;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;

namespace Content.Shared.Construction.Conditions
{
Expand All @@ -13,17 +12,11 @@ public sealed class NoWindowsInTile : IConstructionCondition
public bool Condition(EntityUid user, EntityCoordinates location, Direction direction)
{
var entManager = IoCManager.Resolve<IEntityManager>();
var gridUid = location.GetGridUid(entManager);

if (!entManager.TryGetComponent<MapGridComponent>(gridUid, out var grid))
return true;

var tagQuery = entManager.GetEntityQuery<TagComponent>();
var sysMan = entManager.EntitySysManager;
var tagSystem = sysMan.GetEntitySystem<TagSystem>();
var lookup = sysMan.GetEntitySystem<EntityLookupSystem>();

foreach (var entity in lookup.GetEntitiesIntersecting(gridUid.Value, grid.LocalToTile(location)))
foreach (var entity in location.GetEntitiesInTile(LookupFlags.Approximate | LookupFlags.Static))
{
if (tagSystem.HasTag(entity, "Window", tagQuery))
return false;
Expand Down
42 changes: 42 additions & 0 deletions Content.Shared/Nutrition/AnimalHusbandry/InfantComponent.cs
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;
}
Loading

0 comments on commit 0777861

Please sign in to comment.