Skip to content

Commit

Permalink
Goob Mechs (#1611)
Browse files Browse the repository at this point in the history
# Description

<!--
Explain this PR in as much detail as applicable

Some example prompts to consider:
How might this affect the game? The codebase?
What might be some alternatives to this?
How/Who does this benefit/hurt [the game/codebase]?
-->

We like mechs here, yeah?

---

# Changelog

<!--
You can add an author after the `:cl:` to change the name that appears
in the changelog (ex: `:cl: Death`)
Leaving it blank will default to your GitHub display name
This includes all available types for the changelog
-->

:cl: Mocho, John Space
- tweak: The H.O.N.K. has received an airtight cabin for honk operations
in outer space.
- add: Added the Ripley MK-II, a heavy, slow all-purpose mech, featuring
a pressurized cabin for space operations.
- add: Added the Clarke, A fast moving mech for space travel, with built
in thrusters (not certain if they work properly though :trollface:)
- add: Added the Gygax, a lightly armored and highly mobile mech with
enough force to rip walls, or someone's head off.
- add: Added the Durand, a slow but beefy combat suit that you dont want
to fight in close quarters.
- add: Added the Marauder, a specialized mech issued to ERT operatives.
- add: Added the Seraph, a specialized combat suit issued to ???
operatives.
- add: The syndicate has started issuing units under the codenames "Dark
Gygax" and "Mauler" to syndicate agents at an introductory price.
- add: The exosuit fabricator can now be emagged to reveal new recipes.
- add: There are 4 new bounties cargo can fulfill for mechs. Feedback on
the cost/reward is welcome!

---------

Signed-off-by: sleepyyapril <[email protected]>
Co-authored-by: John Space <[email protected]>
Co-authored-by: gluesniffler <[email protected]>
Co-authored-by: ScyronX <[email protected]>
  • Loading branch information
4 people authored Jan 23, 2025
1 parent 3d9ac9b commit e3003b6
Show file tree
Hide file tree
Showing 210 changed files with 4,714 additions and 106 deletions.
4 changes: 4 additions & 0 deletions Content.Client/Weapons/Ranged/Systems/GunSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Content.Client.Weapons.Ranged.Components;
using Content.Shared.Camera;
using Content.Shared.CombatMode;
using Content.Shared.Mech.Components; // Goobstation
using Content.Shared.Weapons.Ranged;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events;
Expand Down Expand Up @@ -156,6 +157,9 @@ public override void Update(float frameTime)

var entity = entityNull.Value;

if (TryComp<MechPilotComponent>(entity, out var mechPilot)) // Goobstation
entity = mechPilot.Mech;

if (!TryGetGun(entity, out var gunUid, out var gun))
{
return;
Expand Down
7 changes: 5 additions & 2 deletions Content.Server/Cloning/CloningSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
using Content.Server.Materials;
using Content.Server.Popups;
using Content.Server.Power.EntitySystems;
using Content.Server.Traits.Assorted;
using Content.Shared.Silicon.Components; // Goobstation
using Content.Shared.Atmos;
using Content.Shared.CCVar;
using Content.Shared.Chemistry.Components;
Expand Down Expand Up @@ -204,13 +204,16 @@ public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity<MindComponen
|| !_playerManager.TryGetSessionById(mind.UserId.Value, out var client)
|| !CheckBiomassCost(uid, physics, clonePod, cloningCostMultiplier))
return false;

// Special handling for humanoid data related to metempsychosis. This function is needed for Paradox Anomaly code to play nice with reincarnated people
var pref = humanoid.LastProfileLoaded;
if (pref == null
|| !_prototypeManager.TryIndex(humanoid.Species, out var speciesPrototype))
return false;

if (HasComp<SiliconComponent>(bodyToClone))
return false; // Goobstation: Don't clone IPCs.

// Yes, this can return true without making a body. If it returns true, we're making clone soup instead.
if (CheckGeneticDamage(uid, bodyToClone, clonePod, out var geneticDamage, failChanceModifier))
return true;
Expand Down
2 changes: 0 additions & 2 deletions Content.Server/Mech/Systems/MechSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,6 @@ private void OnMechEntry(EntityUid uid, MechComponent component, MechEntryEvent

TryInsert(uid, args.Args.User, component);
_actionBlocker.UpdateCanMove(uid);

args.Handled = true;
}

Expand All @@ -244,7 +243,6 @@ private void OnMechExit(EntityUid uid, MechComponent component, MechExitEvent ar
return;

TryEject(uid, component);

args.Handled = true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ namespace Content.Server.Objectives.Components;
[RegisterComponent, Access(typeof(KillPersonConditionSystem))]
public sealed partial class PickRandomPersonComponent : Component
{
[DataField]
public bool NeedsOrganic; // Goobstation: Only pick non-silicon players.
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
using Content.Server.Objectives.Components;
using Content.Server.Shuttles.Systems;
using Content.Shared.CCVar;
Expand Down Expand Up @@ -54,7 +55,7 @@ private void OnPersonAssigned(EntityUid uid, PickRandomPersonComponent comp, ref
return;

// no other humans to kill
var allHumans = _mind.GetAliveHumansExcept(args.MindId);
var allHumans = _mind.GetAliveHumans(args.MindId, comp.NeedsOrganic);
if (allHumans.Count == 0)
{
args.Cancelled = true;
Expand All @@ -78,7 +79,7 @@ private void OnHeadAssigned(EntityUid uid, PickRandomHeadComponent comp, ref Obj
return;

// no other humans to kill
var allHumans = _mind.GetAliveHumansExcept(args.MindId);
var allHumans = _mind.GetAliveHumans(args.MindId);
if (allHumans.Count == 0)
{
args.Cancelled = true;
Expand All @@ -94,7 +95,7 @@ private void OnHeadAssigned(EntityUid uid, PickRandomHeadComponent comp, ref Obj
}

if (allHeads.Count == 0)
allHeads = allHumans; // fallback to non-head target
allHeads = allHumans.Select(human => human.Owner).ToList(); // fallback to non-head target

_target.SetTarget(uid, _random.Pick(allHeads), target);
}
Expand Down
4 changes: 2 additions & 2 deletions Content.Server/PowerCell/PowerCellSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,8 @@ private void OnCellEmpAttempt(EntityUid uid, PowerCellComponent component, EmpAt

private void OnCellSlotExamined(EntityUid uid, PowerCellSlotComponent component, ExaminedEvent args)
{
TryGetBatteryFromSlot(uid, out var battery);
OnBatteryExamined(uid, battery, args);
TryGetBatteryFromSlot(uid, out var batteryEnt, out var battery); // Goobstation
OnBatteryExamined(batteryEnt.GetValueOrDefault(uid), battery, args); // Goobstation
}

private void OnBatteryExamined(EntityUid uid, BatteryComponent? component, ExaminedEvent args)
Expand Down
5 changes: 3 additions & 2 deletions Content.Server/Silicon/Charge/Systems/SiliconChargeSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,9 @@ private float SiliconHeatEffects(EntityUid silicon, SiliconComponent siliconComp
if (!_random.Prob(Math.Clamp(temperComp.CurrentTemperature / (upperThresh * 5), 0.001f, 0.9f)))
return hotTempMulti;

_flammable.AdjustFireStacks(silicon, Math.Clamp(siliconComp.FireStackMultiplier, -10, 10), flamComp);
_flammable.Ignite(silicon, silicon, flamComp);
// Goobstation: Replaced by KillOnOverheatSystem
//_flammable.AdjustFireStacks(silicon, Math.Clamp(siliconComp.FireStackMultiplier, -10, 10), flamComp);
//_flammable.Ignite(silicon, silicon, flamComp);
return hotTempMulti;
}

Expand Down
30 changes: 22 additions & 8 deletions Content.Server/Silicon/WeldingHealable/WeldingHealableSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
using Content.Shared.Damage;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Tools;
using Content.Shared._Shitmed.Targeting;
using Content.Shared.Body.Systems;
using Content.Shared.Tools.Components;
using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem;

Expand All @@ -17,7 +20,7 @@ public sealed class WeldingHealableSystem : SharedWeldingHealableSystem
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;

[Dependency] private readonly SharedBodySystem _bodySystem = default!;
public override void Initialize()
{
SubscribeLocalEvent<WeldingHealableComponent, InteractUsingEvent>(Repair);
Expand All @@ -31,7 +34,7 @@ private void OnRepairFinished(EntityUid uid, WeldingHealableComponent healableCo
|| !TryComp<WeldingHealingComponent>(args.Used, out var component)
|| damageable.DamageContainerID is null
|| !component.DamageContainers.Contains(damageable.DamageContainerID)
|| !HasDamage(damageable, component)
|| !HasDamage((args.Target.Value, damageable), component, args.User)
|| !TryComp<WelderComponent>(args.Used, out var welder)
|| !TryComp<SolutionContainerManagerComponent>(args.Used, out var solutionContainer))
return;
Expand Down Expand Up @@ -70,7 +73,7 @@ private async void Repair(EntityUid uid, WeldingHealableComponent healableCompon
|| !EntityManager.TryGetComponent(args.Target, out DamageableComponent? damageable)
|| damageable.DamageContainerID is null
|| !component.DamageContainers.Contains(damageable.DamageContainerID)
|| !HasDamage(damageable, component)
|| !HasDamage((args.Target, damageable), component, args.User)
|| !_toolSystem.HasQuality(args.Used, component.QualityNeeded)
|| args.User == args.Target && !component.AllowSelfHeal)
return;
Expand All @@ -79,8 +82,8 @@ private async void Repair(EntityUid uid, WeldingHealableComponent healableCompon
? component.DoAfterDelay * component.SelfHealPenalty
: component.DoAfterDelay;

args.Handled = _toolSystem.UseTool
(args.Used,
args.Handled = _toolSystem.UseTool(
args.Used,
args.User,
args.Target,
delay,
Expand All @@ -91,15 +94,26 @@ private async void Repair(EntityUid uid, WeldingHealableComponent healableCompon
});
}

private bool HasDamage(DamageableComponent component, WeldingHealingComponent healable)
private bool HasDamage(Entity<DamageableComponent> damageable, WeldingHealingComponent healable, EntityUid user)
{
if (healable.Damage.DamageDict is null)
return false;

foreach (var type in healable.Damage.DamageDict)
if (component.Damage.DamageDict[type.Key].Value > 0)
if (damageable.Comp.Damage.DamageDict[type.Key].Value > 0)
return true;

// In case the healer is a humanoid entity with targeting, we run the check on the targeted parts.
if (!TryComp(user, out TargetingComponent? targeting))
return false;

var (targetType, targetSymmetry) = _bodySystem.ConvertTargetBodyPart(targeting.Target);
foreach (var part in _bodySystem.GetBodyChildrenOfType(damageable, targetType, symmetry: targetSymmetry))
if (TryComp<DamageableComponent>(part.Id, out var damageablePart))
foreach (var type in healable.Damage.DamageDict)
if (damageablePart.Damage.DamageDict[type.Key].Value > 0)
return true;

return false;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using Content.Server.Mech.Systems;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Mech.Components;
using Content.Shared.Mech.EntitySystems;
using Content.Shared.Mech.Equipment.Components;
using Content.Shared.Throwing;
using Content.Shared.Weapons.Ranged.Components;
using Robust.Shared.Random;

namespace Content.Server.Mech.Equipment.EntitySystems;
public sealed class MechGunSystem : EntitySystem
{
[Dependency] private readonly MechSystem _mech = default!;
[Dependency] private readonly BatterySystem _battery = default!;

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MechEquipmentComponent, HandleMechEquipmentBatteryEvent>(OnHandleMechEquipmentBattery);
SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, CheckMechWeaponBatteryEvent>(OnCheckBattery);
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, CheckMechWeaponBatteryEvent>(OnCheckBattery);
}

private void OnHandleMechEquipmentBattery(EntityUid uid, MechEquipmentComponent component, HandleMechEquipmentBatteryEvent args)
{
if (!component.EquipmentOwner.HasValue)
return;

if (!TryComp<MechComponent>(component.EquipmentOwner.Value, out var mech))
return;

if (TryComp<BatteryComponent>(uid, out var battery))
{
var ev = new CheckMechWeaponBatteryEvent(battery);
RaiseLocalEvent(uid, ref ev);

if (ev.Cancelled)
return;

ChargeGunBattery(uid, battery);
}
}

private void OnCheckBattery(EntityUid uid, BatteryAmmoProviderComponent component, CheckMechWeaponBatteryEvent args)
{
if (args.Battery.CurrentCharge > component.FireCost)
args.Cancelled = true;
}

private void ChargeGunBattery(EntityUid uid, BatteryComponent component)
{
if (!TryComp<MechEquipmentComponent>(uid, out var mechEquipment) || !mechEquipment.EquipmentOwner.HasValue)
return;

if (!TryComp<MechComponent>(mechEquipment.EquipmentOwner.Value, out var mech))
return;

var maxCharge = component.MaxCharge;
var currentCharge = component.CurrentCharge;

var chargeDelta = maxCharge - currentCharge;

// TODO: The battery charge of the mech would be spent directly when fired.
if (chargeDelta <= 0 || mech.Energy - chargeDelta < 0)
return;

if (!_mech.TryChangeEnergy(mechEquipment.EquipmentOwner.Value, -chargeDelta, mech))
return;

_battery.SetCharge(uid, component.MaxCharge, component);
}
}

[ByRefEvent]
public record struct CheckMechWeaponBatteryEvent(BatteryComponent Battery, bool Cancelled = false);
16 changes: 16 additions & 0 deletions Content.Server/_Goobstation/Temperature/KillOnOverheatComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Content.Shared.Atmos;

namespace Content.Server._Goobstation.Temperature;

/// <summary>
/// Kills an entity when its temperature goes over a threshold.
/// </summary>
[RegisterComponent, Access(typeof(KillOnOverheatSystem))]
public sealed partial class KillOnOverheatComponent : Component
{
[DataField]
public float OverheatThreshold = Atmospherics.T0C + 110f;

[DataField]
public LocId OverheatPopup = "ipc-overheat-popup";
}
33 changes: 33 additions & 0 deletions Content.Server/_Goobstation/Temperature/KillOnOverheatSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Content.Server.Temperature.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Popups;
using Content.Shared.Damage.Components;

namespace Content.Server._Goobstation.Temperature;

public sealed class KillOnOverheatSystem : EntitySystem
{
[Dependency] private readonly MobStateSystem _mob = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;

public override void Update(float frameTime)
{
base.Update(frameTime);

var query = EntityQueryEnumerator<KillOnOverheatComponent, TemperatureComponent, MobStateComponent>();
while (query.MoveNext(out var uid, out var comp, out var temp, out var mob))
{
if (mob.CurrentState == MobState.Dead
|| temp.CurrentTemperature < comp.OverheatThreshold
|| HasComp<GodmodeComponent>(uid))
continue;

var msg = Loc.GetString(comp.OverheatPopup, ("name", Identity.Name(uid, EntityManager)));
_popup.PopupEntity(msg, uid, PopupType.LargeCaution);
_mob.ChangeMobState(uid, MobState.Dead, mob);
}
}
}
10 changes: 10 additions & 0 deletions Content.Shared/Mech/Components/MechComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ namespace Content.Shared.Mech.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class MechComponent : Component
{
/// <summary>
/// Goobstation: Whether or not an emag disables it.
/// </summary>
[DataField("breakOnEmag")]
[AutoNetworkedField]
public bool BreakOnEmag = true;

/// <summary>
/// How much "health" the mech has left.
/// </summary>
Expand Down Expand Up @@ -141,6 +148,8 @@ public sealed partial class MechComponent : Component
[DataField]
public EntProtoId MechCycleAction = "ActionMechCycleEquipment";
[DataField]
public EntProtoId ToggleAction = "ActionToggleLight"; //Goobstation Mech Lights toggle action
[DataField]
public EntProtoId MechUiAction = "ActionMechOpenUI";
[DataField]
public EntProtoId MechEjectAction = "ActionMechEject";
Expand All @@ -158,4 +167,5 @@ public sealed partial class MechComponent : Component
[DataField] public EntityUid? MechCycleActionEntity;
[DataField] public EntityUid? MechUiActionEntity;
[DataField] public EntityUid? MechEjectActionEntity;
[DataField, AutoNetworkedField] public EntityUid? ToggleActionEntity; //Goobstation Mech Lights toggle action
}
Loading

0 comments on commit e3003b6

Please sign in to comment.