Christmas trees now grant gifts. (#13118)

* Christmas trees now grant gifts.

* docs

* add holiday requirements, fix sound.

* doc

* remove redundant stuff

* rename GiftPackin to RandomGift.

* a word

* Update Content.Server/Holiday/Christmas/LimitedItemGiverSystem.cs

Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>

Co-authored-by: moonheart08 <moonheart08@users.noreply.github.com>
Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
This commit is contained in:
Moony
2022-12-20 16:34:34 -06:00
committed by GitHub
parent c3d2e93051
commit 0a45782532
8 changed files with 297 additions and 13 deletions

View File

@@ -0,0 +1,41 @@
using Content.Shared.Storage;
using Robust.Shared.Network;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Holiday.Christmas;
/// <summary>
/// This is used for granting items to lucky souls, exactly once.
/// </summary>
[RegisterComponent, Access(typeof(LimitedItemGiverSystem))]
public sealed class LimitedItemGiverComponent : Component
{
/// <summary>
/// Santa knows who you are behind the screen, only one gift per player per round!
/// </summary>
public readonly HashSet<NetUserId> GrantedPlayers = new();
/// <summary>
/// Selects what entities can be given out by the giver.
/// </summary>
[DataField("spawnEntries", required: true)]
public List<EntitySpawnEntry> SpawnEntries = default!;
/// <summary>
/// The (localized) message shown upon receiving something.
/// </summary>
[DataField("receivedPopup", required: true)]
public string ReceivedPopup = default!;
/// <summary>
/// The (localized) message shown upon being denied.
/// </summary>
[DataField("deniedPopup", required: true)]
public string DeniedPopup = default!;
/// <summary>
/// The holiday required for this giver to work, if any.
/// </summary>
[DataField("requiredHoliday", customTypeSerializer: typeof(PrototypeIdSerializer<HolidayPrototype>)), ViewVariables(VVAccess.ReadWrite)]
public string? RequiredHoliday = null;
}

View File

@@ -0,0 +1,50 @@
using Content.Server.Hands.Systems;
using Content.Server.Mind.Components;
using Content.Server.Popups;
using Content.Shared.Interaction;
using Content.Shared.Storage;
using Robust.Server.GameObjects;
namespace Content.Server.Holiday.Christmas;
/// <summary>
/// This handles handing out items from item givers.
/// </summary>
public sealed class LimitedItemGiverSystem : EntitySystem
{
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly HolidaySystem _holiday = default!;
[Dependency] private readonly PopupSystem _popup = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<LimitedItemGiverComponent, InteractHandEvent>(OnInteractHand);
}
private void OnInteractHand(EntityUid uid, LimitedItemGiverComponent component, InteractHandEvent args)
{
if (!TryComp<ActorComponent>(args.User, out var actor))
return;
if (component.GrantedPlayers.Contains(actor.PlayerSession.UserId) || (component.RequiredHoliday is not null && !_holiday.IsCurrentlyHoliday(component.RequiredHoliday)))
{
_popup.PopupEntity(Loc.GetString(component.DeniedPopup), uid, args.User);
return;
}
var toGive = EntitySpawnCollection.GetSpawns(component.SpawnEntries);
var coords = Transform(args.User).Coordinates;
foreach (var item in toGive)
{
if (item is null)
continue;
var spawned = Spawn(item, coords);
_hands.PickupOrDrop(args.User, spawned);
}
component.GrantedPlayers.Add(actor.PlayerSession.UserId);
_popup.PopupEntity(Loc.GetString(component.ReceivedPopup), uid, args.User);
}
}

View File

@@ -0,0 +1,43 @@
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Holiday.Christmas;
/// <summary>
/// This is used for gifts with COMPLETELY random things.
/// </summary>
[RegisterComponent, Access(typeof(RandomGiftSystem))]
public sealed class RandomGiftComponent : Component
{
/// <summary>
/// The wrapper entity to spawn when unwrapping the gift.
/// </summary>
[DataField("wrapper", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string? Wrapper;
/// <summary>
/// A sound to play when the items are spawned. For example, gift boxes being unwrapped.
/// </summary>
[DataField("sound", required: true)]
public SoundSpecifier? Sound;
/// <summary>
/// Whether or not the gift should be limited only to actual items.
/// </summary>
[DataField("insaneMode", required: true), ViewVariables(VVAccess.ReadWrite)]
public bool InsaneMode;
/// <summary>
/// What entities are allowed to examine this gift to see its contents.
/// </summary>
[DataField("contentsViewers", required: true)]
public EntityWhitelist ContentsViewers = default!;
/// <summary>
/// The currently selected entity to give out. Used so contents viewers can see inside.
/// </summary>
[DataField("selectedEntity"), ViewVariables(VVAccess.ReadWrite)]
public string? SelectedEntity;
}

View File

@@ -0,0 +1,102 @@
using Content.Server.Hands.Systems;
using Content.Shared.Examine;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
using Robust.Server.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server.Holiday.Christmas;
/// <summary>
/// This handles granting players their gift.
/// </summary>
public sealed class RandomGiftSystem : EntitySystem
{
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IRobustRandom _random = default!;
private readonly List<string> _possibleGiftsSafe = new();
private readonly List<string> _possibleGiftsUnsafe = new();
/// <inheritdoc/>
public override void Initialize()
{
_prototype.PrototypesReloaded += OnPrototypesReloaded;
SubscribeLocalEvent<RandomGiftComponent, MapInitEvent>(OnGiftMapInit);
SubscribeLocalEvent<RandomGiftComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<RandomGiftComponent, ExaminedEvent>(OnExamined);
BuildIndex();
}
private void OnExamined(EntityUid uid, RandomGiftComponent component, ExaminedEvent args)
{
if (!component.ContentsViewers.IsValid(args.Examiner, EntityManager) || component.SelectedEntity is null)
return;
var name = _prototype.Index<EntityPrototype>(component.SelectedEntity).Name;
args.Message.PushNewline();
args.Message.AddText(Loc.GetString("gift-packin-contains", ("name", name)));
}
private void OnUseInHand(EntityUid uid, RandomGiftComponent component, UseInHandEvent args)
{
if (args.Handled)
return;
if (component.SelectedEntity is null)
return;
var coords = Transform(args.User).Coordinates;
var handsEnt = Spawn(component.SelectedEntity, coords);
EnsureComp<ItemComponent>(handsEnt); // For insane mode.
if (component.Wrapper is not null)
Spawn(component.Wrapper, coords);
args.Handled = true;
_audio.PlayPvs(component.Sound, args.User);
Del(uid);
_hands.PickupOrDrop(args.User, handsEnt);
}
private void OnGiftMapInit(EntityUid uid, RandomGiftComponent component, MapInitEvent args)
{
if (component.InsaneMode)
component.SelectedEntity = _random.Pick(_possibleGiftsUnsafe);
else
component.SelectedEntity = _random.Pick(_possibleGiftsSafe);
}
private void OnPrototypesReloaded(PrototypesReloadedEventArgs obj)
{
BuildIndex();
}
private void BuildIndex()
{
_possibleGiftsSafe.Clear();
_possibleGiftsUnsafe.Clear();
var itemCompName = _componentFactory.GetComponentName(typeof(ItemComponent));
var mapGridCompName = _componentFactory.GetComponentName(typeof(MapGridComponent));
var physicsCompName = _componentFactory.GetComponentName(typeof(PhysicsComponent));
foreach (var proto in _prototype.EnumeratePrototypes<EntityPrototype>())
{
if (proto.Abstract || proto.NoSpawn || proto.Components.ContainsKey(mapGridCompName) || !proto.Components.ContainsKey(physicsCompName))
continue;
_possibleGiftsUnsafe.Add(proto.ID);
if (!proto.Components.ContainsKey(itemCompName))
continue;
_possibleGiftsSafe.Add(proto.ID);
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Content.Server.Holiday.Christmas;
/// <summary>
/// This is used as a marker component, allows them to see gift contents.
/// </summary>
[RegisterComponent]
public sealed class SantaComponent : Component
{
}

View File

@@ -0,0 +1,3 @@
gift-packin-contains = This present appears to contain {INDEFINITE($name)} {$name}.
christmas-tree-got-gift = After a bit of digging, you find a present with your name on it!
christmas-tree-no-gift = There isn't a gift under the tree for you...

View File

@@ -334,6 +334,7 @@
- type: entity
parent: BaseTreeConifer
id: FloraTreeChristmas02
suffix: PresentsGiver
name: christmas tree
components:
- type: Sprite
@@ -346,6 +347,19 @@
density: 4500
layer:
- WallLayer
- type: LimitedItemGiver
spawnEntries:
- id: PresentRandom
orGroup: present
- id: PresentRandomUnsafe
prob: 0.5
orGroup: present
- id: PresentRandomInsane
prob: 0.2
orGroup: present
receivedPopup: christmas-tree-got-gift
deniedPopup: christmas-tree-no-gift
requiredHoliday: FestiveSeason
- type: entity
parent: BaseTreeConifer

View File

@@ -1,32 +1,53 @@
- type: entity
id: Present
parent: BaseStorageItem
id: PresentBase
name: Present
suffix: Empty
description: A little box with incredible surprises inside.
abstract: true
components:
- type: Sprite
sprite: Objects/Decoration/present.rsi
netsync: false
layers:
- state: present
- type: entity
id: Present
parent: [PresentBase, BaseStorageItem]
suffix: Empty
components:
- type: Item
size: 30
- type: Storage
capacity: 30
- type: entity
id: PresentRandom
parent: BaseItem
name: Present
suffix: Filled Random
description: A little box with incredible surprises inside.
id: PresentRandomUnsafe
parent: [PresentBase, BaseItem]
suffix: Filled Unsafe
components:
- type: RandomGift
wrapper: PresentTrash
sound:
path: /Audio/Effects/unwrap.ogg
insaneMode: false
contentsViewers:
components:
- Ghost
- Santa
- type: entity
id: PresentRandomInsane
parent: PresentRandomUnsafe
suffix: Filled Insane
components:
- type: RandomGift
insaneMode: true
- type: entity
id: PresentRandom
parent: [PresentBase, BaseItem]
suffix: Filled Safe
components:
- type: Sprite
sprite: Objects/Decoration/present.rsi
netsync: false
layers:
- state: present
- type: SpawnItemsOnUse
items:
- id: PresentTrash