diff --git a/Content.Client/Jobs/JanitorSpecial.cs b/Content.Client/Jobs/JanitorSpecial.cs new file mode 100644 index 0000000000..a2ad46b619 --- /dev/null +++ b/Content.Client/Jobs/JanitorSpecial.cs @@ -0,0 +1,12 @@ +using Content.Shared.Roles; +using JetBrains.Annotations; + +namespace Content.Client.Jobs +{ + [UsedImplicitly] + public class JanitorSpecial : JobSpecial + { + // Dummy class that exists solely to avoid an exception on the client, + // but allow the server-side counterpart to exist. + } +} diff --git a/Content.Client/UserInterface/GhostRoleWindow.cs b/Content.Client/UserInterface/GhostRoleWindow.cs index 9157e78a7d..4cf956bb87 100644 --- a/Content.Client/UserInterface/GhostRoleWindow.cs +++ b/Content.Client/UserInterface/GhostRoleWindow.cs @@ -1,6 +1,4 @@ -using Content.Client.GameObjects.EntitySystems; using Robust.Client.UserInterface.CustomControls; -using Robust.Shared.GameObjects.Systems; namespace Content.Client.UserInterface { diff --git a/Content.IntegrationTests/ContentIntegrationTest.cs b/Content.IntegrationTests/ContentIntegrationTest.cs index 4f945895e4..db86308cd8 100644 --- a/Content.IntegrationTests/ContentIntegrationTest.cs +++ b/Content.IntegrationTests/ContentIntegrationTest.cs @@ -79,6 +79,9 @@ namespace Content.IntegrationTests // Avoid funny race conditions with the database. options.CVarOverrides[CCVars.DatabaseSynchronous.Name] = "true"; + // Disable holidays as some of them might mess with the map at round start. + options.CVarOverrides[CCVars.HolidaysEnabled.Name] = "false"; + // Avoid loading a large map by default for integration tests. options.CVarOverrides[CCVars.GameMap.Name] = "Maps/Test/empty.yml"; diff --git a/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs b/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs index 804386ab33..70a0b3e549 100644 --- a/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs +++ b/Content.IntegrationTests/Tests/ContainerOcclusionTest.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using System.Threading.Tasks; using Content.Server.GameObjects.Components.Items.Storage; diff --git a/Content.Server/Chat/ChatManager.cs b/Content.Server/Chat/ChatManager.cs index 3d1a4f936b..f949e5054d 100644 --- a/Content.Server/Chat/ChatManager.cs +++ b/Content.Server/Chat/ChatManager.cs @@ -17,6 +17,7 @@ using Robust.Server.Player; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; +using Robust.Shared.Log; using Robust.Shared.Network; using static Content.Server.Interfaces.Chat.IChatManager; @@ -67,6 +68,7 @@ namespace Content.Server.Chat msg.Message = message; msg.MessageWrap = "SERVER: {0}"; _netManager.ServerSendToAll(msg); + Logger.InfoS("SERVER", message); } public void DispatchStationAnnouncement(string message, string sender = "CentComm") diff --git a/Content.Server/EntryPoint.cs b/Content.Server/EntryPoint.cs index 0eea4d7db4..f34ea0606c 100644 --- a/Content.Server/EntryPoint.cs +++ b/Content.Server/EntryPoint.cs @@ -5,6 +5,7 @@ using Content.Server.Database; using Content.Server.Eui; using Content.Server.GameObjects.Components.Mobs.Speech; using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; +using Content.Server.Holiday.Interfaces; using Content.Server.Interfaces; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameTicking; @@ -78,6 +79,7 @@ namespace Content.Server { base.PostInit(); + IoCManager.Resolve().Initialize(); _gameTicker.Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); diff --git a/Content.Server/GameObjects/Components/Observer/GhostRoleComponent.cs b/Content.Server/GameObjects/Components/Observer/GhostRoleComponent.cs index a021a190d9..209b9f4a9a 100644 --- a/Content.Server/GameObjects/Components/Observer/GhostRoleComponent.cs +++ b/Content.Server/GameObjects/Components/Observer/GhostRoleComponent.cs @@ -1,7 +1,6 @@ using Content.Server.GameObjects.EntitySystems; -using Robust.Server.Interfaces.Player; +using Robust.Server.Player; using Robust.Shared.GameObjects; -using Robust.Shared.GameObjects.Systems; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; diff --git a/Content.Server/GameObjects/Components/Observer/GhostRoleMobSpawnerComponent.cs b/Content.Server/GameObjects/Components/Observer/GhostRoleMobSpawnerComponent.cs index 421a97144f..06d824c0fa 100644 --- a/Content.Server/GameObjects/Components/Observer/GhostRoleMobSpawnerComponent.cs +++ b/Content.Server/GameObjects/Components/Observer/GhostRoleMobSpawnerComponent.cs @@ -2,11 +2,8 @@ using Content.Server.GameObjects.Components.Mobs; using Content.Server.Players; using JetBrains.Annotations; -using Robust.Server.Interfaces.GameObjects; -using Robust.Server.Interfaces.Player; +using Robust.Server.Player; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; diff --git a/Content.Server/GameObjects/Components/Observer/GhostRolesEui.cs b/Content.Server/GameObjects/Components/Observer/GhostRolesEui.cs index a074aa2400..4e1b431844 100644 --- a/Content.Server/GameObjects/Components/Observer/GhostRolesEui.cs +++ b/Content.Server/GameObjects/Components/Observer/GhostRolesEui.cs @@ -2,7 +2,7 @@ using Content.Server.Eui; using Content.Server.GameObjects.EntitySystems; using Content.Shared.Eui; using Content.Shared.GameObjects.Components.Observer; -using Robust.Shared.GameObjects.Systems; +using Robust.Shared.GameObjects; namespace Content.Server.GameObjects.Components.Observer { diff --git a/Content.Server/GameObjects/Components/Observer/GhostTakeoverAvailableComponent.cs b/Content.Server/GameObjects/Components/Observer/GhostTakeoverAvailableComponent.cs index a0adda9907..5ac64295f3 100644 --- a/Content.Server/GameObjects/Components/Observer/GhostTakeoverAvailableComponent.cs +++ b/Content.Server/GameObjects/Components/Observer/GhostTakeoverAvailableComponent.cs @@ -2,9 +2,8 @@ using System; using Content.Server.GameObjects.Components.Mobs; using Content.Server.GameObjects.EntitySystems; using Content.Server.Players; -using Robust.Server.Interfaces.Player; +using Robust.Server.Player; using Robust.Shared.GameObjects; -using Robust.Shared.GameObjects.Systems; namespace Content.Server.GameObjects.Components.Observer { diff --git a/Content.Server/GameObjects/EntitySystems/GhostRoleSystem.cs b/Content.Server/GameObjects/EntitySystems/GhostRoleSystem.cs index f39d6d628c..f3eff20a12 100644 --- a/Content.Server/GameObjects/EntitySystems/GhostRoleSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/GhostRoleSystem.cs @@ -1,18 +1,15 @@ using System.Collections.Generic; -using System.Drawing; using Content.Server.Administration; using Content.Server.Eui; using Content.Server.GameObjects.Components.Observer; using Content.Shared.GameObjects.Components.Observer; -using Content.Shared.GameObjects.EntitySystemMessages; using Content.Shared.GameTicking; using JetBrains.Annotations; using Robust.Server.GameObjects; -using Robust.Server.Interfaces.Player; +using Robust.Server.Player; using Robust.Shared.Console; -using Robust.Shared.GameObjects.Systems; +using Robust.Shared.GameObjects; using Robust.Shared.IoC; -using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.EntitySystems diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 5a61caf297..6876d1872e 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -11,6 +11,8 @@ using Content.Server.GameObjects.Components.Mobs.Speech; using Content.Server.GameObjects.Components.Observer; using Content.Server.GameObjects.Components.PDA; using Content.Server.GameTicking.GamePresets; +using Content.Server.Holiday; +using Content.Server.Holiday.Interfaces; using Content.Server.Interfaces; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameTicking; diff --git a/Content.Server/Holiday/Celebrate/DefaultHolidayCelebrate.cs b/Content.Server/Holiday/Celebrate/DefaultHolidayCelebrate.cs new file mode 100644 index 0000000000..6e5f274629 --- /dev/null +++ b/Content.Server/Holiday/Celebrate/DefaultHolidayCelebrate.cs @@ -0,0 +1,12 @@ +using Content.Server.Holiday.Interfaces; + +namespace Content.Server.Holiday.Celebrate +{ + public class DefaultHolidayCelebrate : IHolidayCelebrate + { + public void Celebrate(HolidayPrototype holiday) + { + // Nada. + } + } +} diff --git a/Content.Server/Holiday/Greet/Custom.cs b/Content.Server/Holiday/Greet/Custom.cs new file mode 100644 index 0000000000..bf28cc72a0 --- /dev/null +++ b/Content.Server/Holiday/Greet/Custom.cs @@ -0,0 +1,22 @@ +using Content.Server.Holiday.Interfaces; +using JetBrains.Annotations; +using Robust.Shared.Serialization; + +namespace Content.Server.Holiday.Greet +{ + [UsedImplicitly] + public class Custom : IHolidayGreet + { + private string _greet; + + void IExposeData.ExposeData(ObjectSerializer serializer) + { + serializer.DataField(ref _greet, "text", string.Empty); + } + + public string Greet(HolidayPrototype holiday) + { + return _greet; + } + } +} diff --git a/Content.Server/Holiday/Greet/DefaultHolidayGreet.cs b/Content.Server/Holiday/Greet/DefaultHolidayGreet.cs new file mode 100644 index 0000000000..f240d8bcd5 --- /dev/null +++ b/Content.Server/Holiday/Greet/DefaultHolidayGreet.cs @@ -0,0 +1,13 @@ +using Content.Server.Holiday.Interfaces; +using Robust.Shared.Localization; + +namespace Content.Server.Holiday.Greet +{ + public class DefaultHolidayGreet : IHolidayGreet + { + public string Greet(HolidayPrototype holiday) + { + return Loc.GetString("Have a happy {0}!", holiday.Name); + } + } +} diff --git a/Content.Server/Holiday/HolidayManager.cs b/Content.Server/Holiday/HolidayManager.cs new file mode 100644 index 0000000000..803a5965a6 --- /dev/null +++ b/Content.Server/Holiday/HolidayManager.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using Content.Server.GameTicking; +using Content.Server.Holiday.Interfaces; +using Content.Server.Interfaces.Chat; +using Content.Server.Interfaces.GameTicking; +using Content.Shared; +using Robust.Shared.Configuration; +using Robust.Shared.IoC; +using Robust.Shared.Prototypes; +using Robust.Shared.ViewVariables; + +namespace Content.Server.Holiday +{ + // ReSharper disable once ClassNeverInstantiated.Global + public class HolidayManager : IHolidayManager + { + [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IGameTicker _gameTicker = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + + [ViewVariables] + private readonly List _currentHolidays = new(); + + [ViewVariables] + private bool _enabled = true; + + public void RefreshCurrentHolidays() + { + _currentHolidays.Clear(); + + if (!_enabled) return; + + var now = DateTime.Now; + + foreach (var holiday in _prototypeManager.EnumeratePrototypes()) + { + if(holiday.ShouldCelebrate(now)) + _currentHolidays.Add(holiday); + } + } + + public void DoGreet() + { + foreach (var holiday in _currentHolidays) + { + _chatManager.DispatchServerAnnouncement(holiday.Greet()); + } + } + + public void DoCelebrate() + { + foreach (var holiday in _currentHolidays) + { + holiday.Celebrate(); + } + } + + public IEnumerable GetCurrentHolidays() + { + return _currentHolidays; + } + + public bool IsCurrentlyHoliday(string holiday) + { + if (!_prototypeManager.TryIndex(holiday, out HolidayPrototype prototype)) + return false; + + return _currentHolidays.Contains(prototype); + } + + public void Initialize() + { + _configManager.OnValueChanged(CCVars.HolidaysEnabled, OnHolidaysEnableChange, true); + + _gameTicker.OnRunLevelChanged += OnRunLevelChanged; + } + + private void OnHolidaysEnableChange(bool enabled) + { + _enabled = enabled; + + RefreshCurrentHolidays(); + } + + private void OnRunLevelChanged(GameRunLevelChangedEventArgs eventArgs) + { + if (!_enabled) return; + + switch (eventArgs.NewRunLevel) + { + case GameRunLevel.PreRoundLobby: + RefreshCurrentHolidays(); + break; + case GameRunLevel.InRound: + DoGreet(); + DoCelebrate(); + break; + case GameRunLevel.PostRound: + break; + } + } + } +} diff --git a/Content.Server/Holiday/HolidayPrototype.cs b/Content.Server/Holiday/HolidayPrototype.cs new file mode 100644 index 0000000000..0e98dc4b84 --- /dev/null +++ b/Content.Server/Holiday/HolidayPrototype.cs @@ -0,0 +1,78 @@ +#nullable enable +using System; +using Content.Server.Holiday.Celebrate; +using Content.Server.Holiday.Greet; +using Content.Server.Holiday.Interfaces; +using Content.Server.Holiday.ShouldCelebrate; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; +using YamlDotNet.RepresentationModel; + +namespace Content.Server.Holiday +{ + [Prototype("holiday")] + public class HolidayPrototype : IPrototype, IIndexedPrototype, IExposeData + { + [ViewVariables] public string Name { get; private set; } = string.Empty; + [ViewVariables] public string ID { get; private set; } = string.Empty; + [ViewVariables] public byte BeginDay { get; set; } = 1; + [ViewVariables] public Month BeginMonth { get; set; } = Month.Invalid; + + /// + /// Day this holiday will end. Zero means it lasts a single day. + /// + [ViewVariables] public byte EndDay { get; set; } = 0; + + /// + /// Month this holiday will end in. Invalid means it lasts a single month. + /// + [ViewVariables] public Month EndMonth { get; set; } = Month.Invalid; + + [ViewVariables] + private IHolidayShouldCelebrate _shouldCelebrate = new DefaultHolidayShouldCelebrate(); + + [ViewVariables] + private IHolidayGreet _greet = new DefaultHolidayGreet(); + + [ViewVariables] + private IHolidayCelebrate _celebrate = new DefaultHolidayCelebrate(); + + public void LoadFrom(YamlMappingNode mapping) + { + var serializer = YamlObjectSerializer.NewReader(mapping); + ExposeData(serializer); + } + + public void ExposeData(ObjectSerializer serializer) + { + serializer.DataField(this, x => x.ID, "id", string.Empty); + serializer.DataField(this, x => x.Name, "name", string.Empty); + serializer.DataField(this, x => x.BeginDay, "beginDay", (byte)1); + serializer.DataField(this, x => x.BeginMonth, "beginMonth", Month.Invalid); + serializer.DataField(this, x => x.EndDay, "endDay", (byte)0); + serializer.DataField(this, x => x.EndMonth, "endMonth", Month.Invalid); + serializer.DataField(ref _shouldCelebrate, "shouldCelebrate", new DefaultHolidayShouldCelebrate()); + serializer.DataField(ref _greet, "greet", new DefaultHolidayGreet()); + serializer.DataField(ref _celebrate, "celebrate", new DefaultHolidayCelebrate()); + } + + public bool ShouldCelebrate(DateTime date) + { + return _shouldCelebrate.ShouldCelebrate(date, this); + } + + public string Greet() + { + return _greet.Greet(this); + } + + /// + /// Called before the round starts to set up any festive shenanigans. + /// + public void Celebrate() + { + _celebrate.Celebrate(this); + } + } +} diff --git a/Content.Server/Holiday/Interfaces/IHolidayCelebrate.cs b/Content.Server/Holiday/Interfaces/IHolidayCelebrate.cs new file mode 100644 index 0000000000..da6f15584c --- /dev/null +++ b/Content.Server/Holiday/Interfaces/IHolidayCelebrate.cs @@ -0,0 +1,15 @@ +using Robust.Shared.Serialization; + +namespace Content.Server.Holiday.Interfaces +{ + public interface IHolidayCelebrate : IExposeData + { + void IExposeData.ExposeData(ObjectSerializer serializer) {} + + /// + /// This method is called before a round starts. + /// Use it to do any fun festive modifications. + /// + void Celebrate(HolidayPrototype holiday); + } +} diff --git a/Content.Server/Holiday/Interfaces/IHolidayGreet.cs b/Content.Server/Holiday/Interfaces/IHolidayGreet.cs new file mode 100644 index 0000000000..edd81abe68 --- /dev/null +++ b/Content.Server/Holiday/Interfaces/IHolidayGreet.cs @@ -0,0 +1,10 @@ +using Robust.Shared.Serialization; + +namespace Content.Server.Holiday.Interfaces +{ + public interface IHolidayGreet : IExposeData + { + void IExposeData.ExposeData(ObjectSerializer serializer) { } + string Greet(HolidayPrototype holiday); + } +} diff --git a/Content.Server/Holiday/Interfaces/IHolidayManager.cs b/Content.Server/Holiday/Interfaces/IHolidayManager.cs new file mode 100644 index 0000000000..1211f3d9f2 --- /dev/null +++ b/Content.Server/Holiday/Interfaces/IHolidayManager.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Content.Server.Holiday.Interfaces +{ + public interface IHolidayManager + { + void Initialize(); + void RefreshCurrentHolidays(); + void DoGreet(); + void DoCelebrate(); + IEnumerable GetCurrentHolidays(); + bool IsCurrentlyHoliday(string holiday); + } +} diff --git a/Content.Server/Holiday/Interfaces/IHolidayShouldCelebrate.cs b/Content.Server/Holiday/Interfaces/IHolidayShouldCelebrate.cs new file mode 100644 index 0000000000..99c0ddd120 --- /dev/null +++ b/Content.Server/Holiday/Interfaces/IHolidayShouldCelebrate.cs @@ -0,0 +1,11 @@ +using System; +using Robust.Shared.Serialization; + +namespace Content.Server.Holiday.Interfaces +{ + public interface IHolidayShouldCelebrate : IExposeData + { + void IExposeData.ExposeData(ObjectSerializer serializer) {} + bool ShouldCelebrate(DateTime date, HolidayPrototype holiday); + } +} diff --git a/Content.Server/Holiday/MonthEnum.cs b/Content.Server/Holiday/MonthEnum.cs new file mode 100644 index 0000000000..21b96670a6 --- /dev/null +++ b/Content.Server/Holiday/MonthEnum.cs @@ -0,0 +1,19 @@ +namespace Content.Server.Holiday +{ + public enum Month : byte + { + Invalid = 0, + January = 1, + February = 2, + March = 3, + April = 4, + May = 5, + June = 6, + July = 7, + August = 8, + September = 9, + October = 10, + November = 11, + December = 12 + } +} diff --git a/Content.Server/Holiday/ShouldCelebrate/ChineseNewYear.cs b/Content.Server/Holiday/ShouldCelebrate/ChineseNewYear.cs new file mode 100644 index 0000000000..54e90b223d --- /dev/null +++ b/Content.Server/Holiday/ShouldCelebrate/ChineseNewYear.cs @@ -0,0 +1,19 @@ +using System; +using System.Globalization; +using Content.Server.Holiday.Interfaces; + +namespace Content.Server.Holiday.ShouldCelebrate +{ + public class ChineseNewYear : IHolidayShouldCelebrate + { + public bool ShouldCelebrate(DateTime date, HolidayPrototype holiday) + { + var chinese = new ChineseLunisolarCalendar(); + var gregorian = new GregorianCalendar(); + + var chineseNewYear = chinese.ToDateTime(date.Year, 1, 1, 0, 0, 0, 0); + + return date.Day == chineseNewYear.Day && date.Month == chineseNewYear.Month; + } + } +} diff --git a/Content.Server/Holiday/ShouldCelebrate/Computus.cs b/Content.Server/Holiday/ShouldCelebrate/Computus.cs new file mode 100644 index 0000000000..a1c16cdb13 --- /dev/null +++ b/Content.Server/Holiday/ShouldCelebrate/Computus.cs @@ -0,0 +1,117 @@ +using System; +using System.IO; +using JetBrains.Annotations; +using Robust.Shared.Serialization; + +namespace Content.Server.Holiday.ShouldCelebrate +{ + /// + /// Computus for easter calculation. + /// + [UsedImplicitly] + public class Computus : DefaultHolidayShouldCelebrate, IExposeData + { + private byte _daysEarly = 1; + private byte _daysExtra = 1; + + void IExposeData.ExposeData(ObjectSerializer serializer) + { + serializer.DataField(ref _daysEarly, "daysEarly", (byte) 1); + serializer.DataField(ref _daysExtra, "daysExtra", (byte) 1); + } + + public (int day, int month) DoComputus(DateTime date) + { + var currentYear = date.Year; + var m = 0; + var n = 0; + + switch (currentYear) + { + case var i when i >= 1900 && i <= 2099: + m = 24; + n = 5; + break; + + case var i when i >= 2100 && i <= 2199: + m = 24; + n = 6; + break; + + case var i when i >= 2200 && i <= 2299: + m = 25; + n = 0; + break; + + // Hello, future person! If you're living in the year >=2300, you might want to fix this method. + // t. earth coder living in 2021 + default: + throw new InvalidDataException("Easter machine broke."); + } + + var a = currentYear % 19; + var b = currentYear % 4; + var c = currentYear % 7; + var d = (19 * a + m) % 30; + var e = (2 * b + 4 * c + 6 * d + n) % 7; + + (int day, int month) easterDate = (0, 0); + + if (d + e < 10) + { + easterDate.month = 3; + easterDate.day = (d + e + 22); + } else if (d + e > 9) + { + easterDate.month = 4; + easterDate.day = (d + e - 9); + } + + if (easterDate.month == 4 && easterDate.day == 26) + easterDate.day = 19; + + if (easterDate.month == 4 && easterDate.day == 25 && d == 28 && e == 6 && a > 10) + easterDate.day = 18; + + return easterDate; + } + + public override bool ShouldCelebrate(DateTime date, HolidayPrototype holiday) + { + if (holiday.BeginMonth == Month.Invalid) + { + var (day, month) = DoComputus(date); + + holiday.BeginDay = (byte) day; + holiday.BeginMonth = (Month) month; + + holiday.EndDay = (byte) (holiday.BeginDay + _daysExtra); + holiday.EndMonth = holiday.BeginMonth; + + // Begins in march, ends in april + if (holiday.EndDay >= 32 && holiday.EndMonth == Month.March) + { + holiday.EndDay -= 31; + holiday.EndMonth++; + } + + // Begins in april, ends in june. + if (holiday.EndDay >= 31 && holiday.EndMonth == Month.April) + { + holiday.EndDay -= 30; + holiday.EndMonth++; + } + + holiday.BeginDay -= _daysEarly; + // Begins in march, ends in april. + if (holiday.BeginDay <= 0 && holiday.BeginMonth == Month.April) + { + holiday.BeginDay += 31; + holiday.BeginMonth--; + } + } + + return base.ShouldCelebrate(date, holiday); + } + } +} diff --git a/Content.Server/Holiday/ShouldCelebrate/DayOfYear.cs b/Content.Server/Holiday/ShouldCelebrate/DayOfYear.cs new file mode 100644 index 0000000000..9b4cf49691 --- /dev/null +++ b/Content.Server/Holiday/ShouldCelebrate/DayOfYear.cs @@ -0,0 +1,26 @@ +using System; +using Content.Server.Holiday.Interfaces; +using JetBrains.Annotations; +using Robust.Shared.Serialization; + +namespace Content.Server.Holiday.ShouldCelebrate +{ + /// + /// For a holiday that occurs on a certain day of the year. + /// + [UsedImplicitly] + public class DayOfYear : IHolidayShouldCelebrate + { + private uint _dayOfYear; + + void IExposeData.ExposeData(ObjectSerializer serializer) + { + serializer.DataField(ref _dayOfYear, "dayOfYear", 1u); + } + + public bool ShouldCelebrate(DateTime date, HolidayPrototype holiday) + { + return date.DayOfYear == _dayOfYear; + } + } +} diff --git a/Content.Server/Holiday/ShouldCelebrate/DefaultHolidayShouldCelebrate.cs b/Content.Server/Holiday/ShouldCelebrate/DefaultHolidayShouldCelebrate.cs new file mode 100644 index 0000000000..9f5c574b92 --- /dev/null +++ b/Content.Server/Holiday/ShouldCelebrate/DefaultHolidayShouldCelebrate.cs @@ -0,0 +1,55 @@ +using System; +using System.Globalization; +using Content.Server.Holiday.Interfaces; + +namespace Content.Server.Holiday.ShouldCelebrate +{ + public class DefaultHolidayShouldCelebrate : IHolidayShouldCelebrate + { + public virtual bool ShouldCelebrate(DateTime date, HolidayPrototype holiday) + { + if (holiday.EndDay == 0) + holiday.EndDay = holiday.BeginDay; + + if (holiday.EndMonth == Month.Invalid) + holiday.EndMonth = holiday.BeginMonth; + + // Holiday spans multiple months in one year. + if(holiday.EndMonth > holiday.BeginMonth) + { + // In final month. + if (date.Month == (int) holiday.EndMonth && date.Day <= holiday.EndDay) + return true; + + // In first month. + if (date.Month == (int) holiday.BeginMonth && date.Day >= holiday.BeginDay) + return true; + + // Holiday spans more than 2 months, and we're in the middle. + if (date.Month > (int) holiday.BeginMonth && date.Month < (int) holiday.EndMonth) + return true; + } + + // Holiday starts and stops in the same month. + else if (holiday.EndMonth == holiday.BeginMonth) + { + if (date.Month == (int) holiday.BeginMonth && date.Day >= holiday.BeginDay && date.Day <= holiday.EndDay) + return true; + } + + // Holiday starts in one year and ends in the next. + else + { + // Holiday ends next year. + if (date.Month >= (int) holiday.BeginMonth && date.Day >= holiday.BeginDay) + return true; + + // Holiday started last year. + if (date.Month <= (int) holiday.EndMonth && date.Day <= holiday.EndDay) + return true; + } + + return false; + } + } +} diff --git a/Content.Server/Holiday/ShouldCelebrate/FridayThirteenth.cs b/Content.Server/Holiday/ShouldCelebrate/FridayThirteenth.cs new file mode 100644 index 0000000000..2cc7bda7d4 --- /dev/null +++ b/Content.Server/Holiday/ShouldCelebrate/FridayThirteenth.cs @@ -0,0 +1,18 @@ +using System; +using Content.Server.Holiday.Interfaces; +using JetBrains.Annotations; + +namespace Content.Server.Holiday.ShouldCelebrate +{ + /// + /// For Friday the 13th. Spooky! + /// + [UsedImplicitly] + public class FridayThirteenth : IHolidayShouldCelebrate + { + public bool ShouldCelebrate(DateTime date, HolidayPrototype holiday) + { + return date.Day == 13 && date.DayOfWeek == DayOfWeek.Friday; + } + } +} diff --git a/Content.Server/Holiday/ShouldCelebrate/WeekdayInMonth.cs b/Content.Server/Holiday/ShouldCelebrate/WeekdayInMonth.cs new file mode 100644 index 0000000000..8bd2915f58 --- /dev/null +++ b/Content.Server/Holiday/ShouldCelebrate/WeekdayInMonth.cs @@ -0,0 +1,47 @@ +using System; +using System.Globalization; +using JetBrains.Annotations; +using Robust.Shared.Serialization; + +namespace Content.Server.Holiday.ShouldCelebrate +{ + /// + /// For a holiday that happens the first instance of a weekday on a month. + /// + [UsedImplicitly] + public class WeekdayInMonth : DefaultHolidayShouldCelebrate, IExposeData + { + private DayOfWeek _weekday; + private uint _occurrence; + + void IExposeData.ExposeData(ObjectSerializer serializer) + { + serializer.DataField(ref _weekday, "weekday", DayOfWeek.Monday); + serializer.DataField(ref _occurrence, "occurrence", 1u); + } + + public override bool ShouldCelebrate(DateTime date, HolidayPrototype holiday) + { + // Occurrence NEEDS to be between 1 and 4. + _occurrence = Math.Max(1, Math.Min(_occurrence, 4)); + + var calendar = new GregorianCalendar(); + + var d = new DateTime(date.Year, date.Month, 1, calendar); + for (var i = 1; i <= 7; i++) + { + if (d.DayOfWeek != _weekday) + { + d = d.AddDays(1); + continue; + } + + d = d.AddDays(7 * (_occurrence-1)); + + return date.Day == d.Day; + } + + return false; + } + } +} diff --git a/Content.Server/Jobs/JanitorSpecial.cs b/Content.Server/Jobs/JanitorSpecial.cs new file mode 100644 index 0000000000..225a7168e6 --- /dev/null +++ b/Content.Server/Jobs/JanitorSpecial.cs @@ -0,0 +1,40 @@ +#nullable enable +using Content.Server.GameObjects.Components.GUI; +using Content.Server.GameObjects.Components.Items.Storage; +using Content.Server.Holiday.Interfaces; +using Content.Shared.Roles; +using JetBrains.Annotations; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Serialization; + +namespace Content.Server.Jobs +{ + [UsedImplicitly] + public class JanitorSpecial : JobSpecial + { + private string _holiday = string.Empty; + private string _prototype = string.Empty; + + protected override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + + serializer.DataField(ref _holiday, "holiday", string.Empty); + serializer.DataField(ref _prototype, "prototype", string.Empty); + } + + public override void AfterEquip(IEntity mob) + { + base.AfterEquip(mob); + + if (string.IsNullOrEmpty(_holiday) || string.IsNullOrEmpty(_prototype)) return; + if (!IoCManager.Resolve().IsCurrentlyHoliday(_holiday)) return; + + var item = mob.EntityManager.SpawnEntity(_prototype, mob.Transform.Coordinates); + if (!item.TryGetComponent(out ItemComponent? itemComp)) return; + if (!mob.TryGetComponent(out HandsComponent? handsComponent)) return; + handsComponent.PutInHand(itemComp, false); + } + } +} diff --git a/Content.Server/ServerContentIoC.cs b/Content.Server/ServerContentIoC.cs index b3c5630a2c..08b7b7c84f 100644 --- a/Content.Server/ServerContentIoC.cs +++ b/Content.Server/ServerContentIoC.cs @@ -9,6 +9,8 @@ using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Content.Server.GameObjects.Components.Power.PowerNetComponents; using Content.Server.GameObjects.EntitySystems.DeviceNetwork; using Content.Server.GameTicking; +using Content.Server.Holiday; +using Content.Server.Holiday.Interfaces; using Content.Server.Interfaces; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameTicking; @@ -55,6 +57,7 @@ namespace Content.Server IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } } diff --git a/Content.Server/StationEvents/GasLeak.cs b/Content.Server/StationEvents/GasLeak.cs index 86571b9acd..8d3c05d161 100644 --- a/Content.Server/StationEvents/GasLeak.cs +++ b/Content.Server/StationEvents/GasLeak.cs @@ -1,13 +1,8 @@ using Content.Server.GameObjects.Components.Atmos; using Content.Server.Interfaces.GameTicking; using Content.Shared.Atmos; -using Robust.Server.GameObjects.EntitySystems; +using Robust.Server.GameObjects; using Robust.Shared.GameObjects; -using Robust.Shared.GameObjects.Components.Map; -using Robust.Shared.GameObjects.Systems; -using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Interfaces.Map; -using Robust.Shared.Interfaces.Random; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Map; @@ -30,7 +25,7 @@ namespace Content.Server.StationEvents protected override string? EndAnnouncement => "The source of the gas leak has been fixed. Please be cautious around areas with gas remaining."; private static readonly Gas[] LeakableGases = { - Gas.Phoron, + Gas.Plasma, Gas.Tritium, }; diff --git a/Content.Shared/CCVars.cs b/Content.Shared/CCVars.cs index 2d69e529f8..c37c01cb99 100644 --- a/Content.Shared/CCVars.cs +++ b/Content.Shared/CCVars.cs @@ -241,6 +241,12 @@ namespace Content.Shared public static readonly CVarDef MaxMidiLaggedBatches = CVarDef.Create("midi.max_lagged_batches", 8, CVar.SERVERONLY); + /* + * Holidays + */ + + public static readonly CVarDef HolidaysEnabled = CVarDef.Create("holidays.enabled", true, CVar.SERVERONLY); + /* * Branding stuff */ diff --git a/Content.Shared/GameObjects/EntitySystems/SharedGhostRoleSystem.cs b/Content.Shared/GameObjects/EntitySystems/SharedGhostRoleSystem.cs index 1aa269c4ea..2cc466794c 100644 --- a/Content.Shared/GameObjects/EntitySystems/SharedGhostRoleSystem.cs +++ b/Content.Shared/GameObjects/EntitySystems/SharedGhostRoleSystem.cs @@ -1,6 +1,5 @@ using System; using Robust.Shared.GameObjects; -using Robust.Shared.GameObjects.Systems; using Robust.Shared.Serialization; namespace Content.Shared.GameObjects.EntitySystems diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml b/Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml index b294e5e463..8800fe1656 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/janitor.yml @@ -9,6 +9,10 @@ access: - Janitor - Maintenance + special: + !type:JanitorSpecial + holiday: GarbageDay + prototype: RevolverInspector - type: startingGear id: JanitorGear diff --git a/Resources/Prototypes/holidays.yml b/Resources/Prototypes/holidays.yml new file mode 100644 index 0000000000..13e058b570 --- /dev/null +++ b/Resources/Prototypes/holidays.yml @@ -0,0 +1,421 @@ +- type: holiday + id: NewYear + name: New Year + beginDay: 30 + beginMonth: December + endDay: 2 + endMonth: January + +- type: holiday + id: MisterLizard + name: Mister Lizard's Birthday + beginDay: 15 + beginMonth: January + +- type: holiday + id: ChineseNewYear + name: Chinese New Year + shouldCelebrate: + !type:ChineseNewYear {} + +- type: holiday + id: GroundhogDay + name: Groundhog Day + beginDay: 2 + beginMonth: February + +- type: holiday + id: ValentinesDay + name: Valentine's Day + beginDay: 13 + endDay: 15 + beginMonth: February + +- type: holiday + id: Birthday13 + name: Birthday of Space Station 13 + beginDay: 16 + beginMonth: February + +- type: holiday + id: RandomKindness + name: Random Acts of Kindness Day + beginDay: 17 + beginMonth: February + +- type: holiday + id: LeapDay + name: Leap Day + beginDay: 29 + beginMonth: February + +- type: holiday + id: PiDay + name: Pi Day + beginDay: 14 + beginMonth: March + +- type: holiday + id: StPatricksDay + name: St. Patrick's Day + beginDay: 17 + beginMonth: March + +- type: holiday + id: Easter + name: Easter + shouldCelebrate: + !type:Computus { } + +- type: holiday + id: AprilFoolDay + name: April Fools Day + beginDay: 1 + beginMonth: April + +- type: holiday + id: AutismAwarenessDay + name: Autism Awareness Day + beginDay: 2 + beginMonth: April + +- type: holiday + id: CosmonauticsDay + name: Cosmonautics Day + beginDay: 12 + beginMonth: April + greet: + !type:Custom + text: On this day over 600 years ago, Comrade Yuri Gagarin first ventured into space! + +- type: holiday + id: FourTwenty + name: Four-Twenty + beginDay: 20 + beginMonth: April + +- type: holiday + id: TeaDay + name: National Tea Day + beginDay: 21 + beginMonth: April + +- type: holiday + id: EarthDay + name: Earth Day + beginDay: 22 + beginMonth: April + +- type: holiday + id: AnzacDay + name: Anzac Day + beginDay: 25 + beginMonth: April + +- type: holiday + id: Birthday14 + name: Birthday of Space Station 14 + beginDay: 26 + beginMonth: April + +- type: holiday + id: LaborDay + name: Labor Day + beginDay: 1 + beginMonth: May + +- type: holiday + id: FirefighterDay + name: Firefighter's Day + beginDay: 4 + beginMonth: May + +- type: holiday + id: MothersDay + name: Mother's Day + beginMonth: May + shouldCelebrate: + !type:WeekdayInMonth + weekday: Sunday + occurrence: 2 + greet: + !type:Custom + text: "Happy Mother's Day in most of the Americas, Asia, and Oceania!" + +- type: holiday + id: OwlAndPussycatDay + name: Owl and Pussycat Day + beginDay: 12 + beginMonth: May + +- type: holiday + id: MoMMIDay + name: MoMMI Day + beginDay: 30 + beginMonth: May + +- type: holiday + id: GarbageDay + name: Garbage Day + beginDay: 17 + beginMonth: June + +- type: holiday + id: InternationalPicnicDay + name: International Picnic Day + beginDay: 18 + beginMonth: June + +- type: holiday + id: FathersDay + name: Father's Day + beginMonth: August + shouldCelebrate: + !type:WeekdayInMonth + weekday: Sunday + occurrence: 3 + +- type: holiday + id: SummerSolstice + name: Summer Solstice + beginDay: 21 + beginMonth: June + +- type: holiday + id: StonewallRiotsAnniversary + name: Stonewall Riots Anniversary + beginDay: 28 + beginMonth: June + +- type: holiday + id: DoctorDay + name: Doctor's Day + beginDay: 1 + beginMonth: July + +- type: holiday + id: UFODay + name: UFO Day + beginDay: 2 + beginMonth: July + +- type: holiday + id: USIndependenceDay + name: US Independence Day + beginDay: 4 + beginMonth: July + +- type: holiday + id: WritersDay + name: Writer's Day + beginDay: 8 + beginMonth: July + +- type: holiday + id: BastilleDay + name: Bastille Day + beginDay: 14 + beginMonth: July + greet: + !type:Custom + text: Do you hear the people sing? + +- type: holiday + id: FriendshipDay + name: Friendship Day + beginDay: 30 + beginMonth: July + greet: + !type:Custom + text: Have a magical Friendship Day! + +- type: holiday + id: BeerDay + name: Beer Day + shouldCelebrate: + !type:WeekdayInMonth + weekday: Friday + beginMonth: August + +- type: holiday + id: TalkLikeAPirateDay + name: Talk-Like-a-Pirate Day + beginDay: 19 + beginMonth: September + greet: + !type:Custom + text: "Ye be talkin' like a pirate today or else ye'r walkin' tha plank, matey!" + +- type: holiday + id: ProgrammersDay + name: Programmers' Day + shouldCelebrate: + !type:DayOfYear + dayOfYear: 256 + +- type: holiday + id: BisexualPrideDay + name: Bisexual Pride Day + beginDay: 23 + beginMonth: September + +- type: holiday + id: StupidQuestionsDay + name: Stupid-Questions Day + beginDay: 28 + beginMonth: September + +- type: holiday + id: AnimalsDay + name: Animal's Day + beginDay: 4 + beginMonth: October + +- type: holiday + id: SmilingDay + name: Smiling Day + beginDay: 7 + beginMonth: October + +- type: holiday + id: LesbianDay + name: Lesbian Day + beginDay: 8 + beginMonth: October + +- type: holiday + id: CanadianThanksgiving + name: Thanksgiving in Canada + beginMonth: October + shouldCelebrate: + !type:WeekdayInMonth + occurrence: 2 + weekday: Monday + +- type: holiday + id: SpiritDay + name: Spirit Day + beginMonth: October + shouldCelebrate: + !type:WeekdayInMonth + occurrence: 3 + weekday: Thursday + +- type: holiday + id: Halloween + name: Halloween + beginDay: 31 + beginMonth: October + greet: + !type:Custom + text: Have a spooky Halloween! + +- type: holiday + id: VeganDay + name: Vegan Day + beginDay: 1 + beginMonth: November + +- type: holiday + id: ArmisticeDay + name: Armistice Day + beginDay: 11 + beginMonth: November + +- type: holiday + id: KindnessDay + name: Kindness Day + beginDay: 13 + beginMonth: November + greet: + !type:Custom + text: Go do some random acts of kindness for a stranger! + +- type: holiday + id: LifeDay + name: Life Day + beginDay: 17 + beginMonth: November + +- type: holiday + id: FlowersDay + name: Flower's Day + beginDay: 19 + beginMonth: November + +- type: holiday + id: TransgenderRemembranceDay + name: Transgender Day of Remembrance + beginDay: 20 + beginMonth: November + +- type: holiday + id: SayingHelloDay + name: Saying Hello Day + beginDay: 21 + beginMonth: November + +- type: holiday + id: Thanksgiving + name: Thanksgiving in the United States + beginMonth: November + shouldCelebrate: + !type:WeekdayInMonth + occurrence: 4 + weekday: Thursday + +- type: holiday + id: Sinterklaas + name: Sinterklaas + beginDay: 5 + beginMonth: December + +- type: holiday + id: HumanRightsDay + name: Human-Rights Day + beginDay: 10 + beginMonth: December + +- type: holiday + id: MonkeyDay + name: Monkey Day + beginDay: 14 + beginMonth: December + +- type: holiday + id: MayanDoomsday + name: Mayan Doomsday Anniversary + beginDay: 21 + beginMonth: December + +- type: holiday + id: Christmas + name: Christmas + beginDay: 24 + endDay: 26 + beginMonth: December + greet: + !type:Custom + text: Have a merry Christmas! + +- type: holiday + id: FestiveSeason + name: Festive Season + beginDay: 1 + endDay: 31 + beginMonth: December + greet: + !type:Custom + text: Have a nice festive season! + +- type: holiday + id: BoxingDay + name: Boxing Day + beginDay: 26 + beginMonth: December + +- type: holiday + id: FridayThirteenth + name: Friday the 13th + shouldCelebrate: + !type:FridayThirteenth { } diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 6e80925829..938fcbec59 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -86,6 +86,7 @@ True True True + True True True True