Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework Mimic as Rampant Brand Intelligence #395

Merged
merged 14 commits into from
Jul 18, 2024
8 changes: 3 additions & 5 deletions Content.Server/Advertise/AdvertiseComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,21 @@

namespace Content.Server.Advertise
{
[RegisterComponent, Access(typeof(AdvertiseSystem))]
[RegisterComponent]
public sealed partial class AdvertiseComponent : Component
{
/// <summary>
/// Minimum time in seconds to wait before saying a new ad, in seconds. Has to be larger than or equal to 1.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("minWait")]
public int MinimumWait { get; private set; } = 8 * 60;
public int MinimumWait = 8 * 60;

/// <summary>
/// Maximum time in seconds to wait before saying a new ad, in seconds. Has to be larger than or equal
/// to <see cref="MinimumWait"/>
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("maxWait")]
public int MaximumWait { get; private set; } = 10 * 60;
public int MaximumWait = 10 * 60;

/// <summary>
/// The identifier for the advertisements pack prototype.
Expand Down
24 changes: 24 additions & 0 deletions Content.Server/Antag/Mimic/MobReplacementRuleComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,33 @@ public sealed partial class MobReplacementRuleComponent : Component
[DataField]
public EntProtoId Proto = "MobMimic";

[DataField]
public int NumberToReplace { get; set; }

[DataField]
public string Announcement = "station-event-rampant-intelligence-announcement";

/// <summary>
/// Chance per-entity.
/// </summary>
[DataField]
public float Chance = 0.001f;

[DataField]
public bool DoAnnouncement = true;

[DataField]
public float MimicMeleeDamage = 20f;

[DataField]
public float MimicMoveSpeed = 1f;

[DataField]
public string MimicAIType = "SimpleHostileCompound";

[DataField]
public bool MimicSmashGlass = true;

[DataField]
public bool VendorModify = true;
}
152 changes: 147 additions & 5 deletions Content.Server/Antag/MobReplacementRuleSystem.cs
Original file line number Diff line number Diff line change
@@ -1,37 +1,179 @@
using System.Numerics;
using Content.Server.Antag.Mimic;
using Content.Server.Chat.Systems;
using Content.Server.GameTicking.Rules;
using Content.Server.GameTicking.Rules.Components;
using Content.Server.NPC.Systems;
using Content.Server.Station.Systems;
using Content.Server.GameTicking;
using Content.Shared.VendingMachines;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Server.GameObjects;
using Robust.Shared.Physics.Systems;
using System.Linq;
using Robust.Shared.Physics;
using Content.Shared.Movement.Components;
using Content.Shared.Damage;
using Content.Server.NPC.HTN;
using Content.Server.NPC;
using Content.Shared.Weapons.Melee;
using Content.Server.Advertise;
using Content.Server.Power.Components;
using Content.Shared.CombatMode;

namespace Content.Server.Antag;

public sealed class MobReplacementRuleSystem : GameRuleSystem<MobReplacementRuleComponent>
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!;
[Dependency] private readonly NPCSystem _npc = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly AdvertiseSystem _advertise = default!;


protected override void Started(EntityUid uid, MobReplacementRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
{
base.Started(uid, component, gameRule, args);

var query = AllEntityQuery<VendingMachineComponent, TransformComponent>();
var spawns = new List<(EntityUid Entity, EntityCoordinates Coordinates)>();
var stations = _gameTicker.GetSpawnableStations();

while (query.MoveNext(out var vendingUid, out _, out var xform))
{
if (!_random.Prob(component.Chance))
var ownerStation = _station.GetOwningStation(vendingUid);

if (ownerStation == null
|| ownerStation != stations[0])
continue;

// Make sure that we aren't running this on something that is already a mimic
if (HasComp<CombatModeComponent>(vendingUid))
continue;

spawns.Add((vendingUid, xform.Coordinates));
}

foreach (var entity in spawns)
if (spawns == null)
VMSolidus marked this conversation as resolved.
Show resolved Hide resolved
{
var coordinates = entity.Coordinates;
Del(entity.Entity);
//WTF THE STATION DOESN'T EXIST! WE MUST BE IN A TEST! QUICK, PUT A MIMIC AT 0,0!!!
Spawn(component.Proto, new EntityCoordinates(uid, new Vector2(0, 0)));
}
else
{
// This is intentionally not clamped. If a server host wants to replace every vending machine in the entire station with a mimic, who am I to stop them?
var k = MathF.MaxMagnitude(component.NumberToReplace, 1);
while (k > 0 && spawns != null && spawns.Count > 0)
{
if (k > 1)
{
var spawnLocation = _random.PickAndTake(spawns);
BuildAMimicWorkshop(spawnLocation.Entity, component);
}
else
{
BuildAMimicWorkshop(spawns[0].Entity, component);
}

if (k == MathF.MaxMagnitude(component.NumberToReplace, 1)
&& component.DoAnnouncement)
_chat.DispatchStationAnnouncement(stations[0], Loc.GetString("station-event-rampant-intelligence-announcement"), playDefaultSound: true,
colorOverride: Color.Red, sender: "Central Command");

k--;
}
}
}

/// <summary>
/// It's like Build a Bear, but MURDER
/// </summary>
/// <param name="uid"></param>
public void BuildAMimicWorkshop(EntityUid uid, MobReplacementRuleComponent component)
VMSolidus marked this conversation as resolved.
Show resolved Hide resolved
{
var metaData = MetaData(uid);
var vendorPrototype = metaData.EntityPrototype;
var mimicProto = _prototype.Index<EntityPrototype>(component.Proto);

var vendorComponents = vendorPrototype?.Components.Keys
.Where(n => n != "Transform" && n != "MetaData")
.Select(name => (name, _componentFactory.GetRegistration(name).Type))
.ToList() ?? new List<(string name, Type type)>();

var mimicComponents = mimicProto?.Components.Keys
.Where(n => n != "Transform" && n != "MetaData")
.Select(name => (name, _componentFactory.GetRegistration(name).Type))
.ToList() ?? new List<(string name, Type type)>();

Spawn(component.Proto, coordinates);
foreach (var name in mimicComponents.Except(vendorComponents))
{
var newComponent = _componentFactory.GetComponent(name.name);
EntityManager.AddComponent(uid, newComponent);
}
VMSolidus marked this conversation as resolved.
Show resolved Hide resolved

var xform = Transform(uid);
if (xform.Anchored)
_transform.Unanchor(uid, xform);

SetupMimicNPC(uid, component);

if (TryComp<AdvertiseComponent>(uid, out var vendor)
&& component.VendorModify)
SetupMimicVendor(uid, component, vendor);
}
/// <summary>
/// This handles getting the entity ready to be a hostile NPC
/// </summary>
/// <param name="uid"></param>
/// <param name="component"></param>
private void SetupMimicNPC(EntityUid uid, MobReplacementRuleComponent component)
{
_physics.SetBodyType(uid, BodyType.KinematicController);
_npcFaction.AddFaction(uid, "SimpleHostile");

var melee = EnsureComp<MeleeWeaponComponent>(uid);
melee.Angle = 0;
DamageSpecifier dspec = new()
{
DamageDict = new()
{
{ "Blunt", component.MimicMeleeDamage }
}
};
melee.Damage = dspec;

var movementSpeed = EnsureComp<MovementSpeedModifierComponent>(uid);
(movementSpeed.BaseSprintSpeed, movementSpeed.BaseWalkSpeed) = (component.MimicMoveSpeed, component.MimicMoveSpeed);

var htn = EnsureComp<HTNComponent>(uid);
htn.RootTask = new HTNCompoundTask() { Task = component.MimicAIType };
htn.Blackboard.SetValue(NPCBlackboard.NavSmash, component.MimicSmashGlass);
_npc.WakeNPC(uid, htn);
}
VMSolidus marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Handling specific interactions with vending machines
VMSolidus marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <param name="uid"></param>
/// <param name="mimicComponent"></param>
/// <param name="vendorComponent"></param>
private void SetupMimicVendor(EntityUid uid, MobReplacementRuleComponent mimicComponent, AdvertiseComponent vendorComponent)
{
vendorComponent.MinimumWait = 5;
vendorComponent.MaximumWait = 15;
_advertise.SayAdvertisement(uid, vendorComponent);
_advertise.RefreshTimer(uid, vendorComponent);

if (TryComp<ApcPowerReceiverComponent>(uid, out var aPC))
aPC.NeedsPower = false;
}
}
2 changes: 1 addition & 1 deletion Content.Server/GameTicking/GameTicker.Spawning.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public sealed partial class GameTicker
// Mainly to avoid allocations.
private readonly List<EntityCoordinates> _possiblePositions = new();

private List<EntityUid> GetSpawnableStations()
public List<EntityUid> GetSpawnableStations()
{
var spawnableStations = new List<EntityUid>();
var query = EntityQueryEnumerator<StationJobsComponent, StationSpawningComponent>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
station-event-rampant-intelligence-announcement = Rampant brand intelligence has been detected board your station. Please inspect any vendors for aggressive marketing tactics, and reboot them if necessary.
10 changes: 6 additions & 4 deletions Resources/Prototypes/Entities/Mobs/NPCs/mimic.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
- type: entity
name: Mimic
id: MobMimic
parent: [ SimpleMobBase, MobCombat ]
description: Surprise. # When this gets a proper write this should use the object's actual description >:)
components:
- type: Tag
tags:
- FootstepSound
- type: Clickable
- type: CombatMode
- type: InteractionOutline
- type: InputMover
- type: Input
context: "human"
- type: MobMover
- type: NpcFactionMember
factions:
Expand All @@ -20,6 +24,7 @@
- type: Icon
sprite: Structures/Machines/VendingMachines/cola.rsi
state: normal
- type: Physics
- type: Fixtures
fixtures:
fix1:
Expand All @@ -38,6 +43,3 @@
damage:
types:
Blunt: 20
- type: MovementSpeedModifier
baseWalkSpeed : 1
baseSprintSpeed : 1
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
- MachineMask
layer:
- MachineLayer
density: 200
density: 1000
- type: Destructible
thresholds:
- trigger:
Expand Down
1 change: 1 addition & 0 deletions Resources/Prototypes/GameRules/events.yml
Original file line number Diff line number Diff line change
Expand Up @@ -447,3 +447,4 @@
minimumPlayers: 20
weight: 5
- type: MobReplacementRule
numberToReplace: 1
Loading