diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index 662ad85ca14..5b561406f23 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -83,6 +83,28 @@
#define EVENT_LEVEL_MODERATE 2
#define EVENT_LEVEL_MAJOR 3
+#define JANUARY 1
+#define FEBRUARY 2
+#define MARCH 3
+#define APRIL 4
+#define MAY 5
+#define JUNE 6
+#define JULY 7
+#define AUGUST 8
+#define SEPTEMBER 9
+#define OCTOBER 10
+#define NOVEMBER 11
+#define DECEMBER 12
+
+//Select holiday names -- If you test for a holiday in the code, make the holiday's name a define and test for that instead
+#define NEW_YEAR "New Year"
+#define VALENTINES "Valentine's Day"
+#define APRIL_FOOLS "April Fool's Day"
+#define EASTER "Easter"
+#define HALLOWEEN "Halloween"
+#define CHRISTMAS "Christmas"
+#define FRIDAY_13TH "Friday the 13th"
+
//Light color defs, for light-emitting things
//Some defs may be pure color- this is for neatness, and configurability. Changing #define COLOR_ is a bad idea.
#define LIGHT_COLOR_CYAN "#7BF9FF"
@@ -105,4 +127,4 @@
#define LIGHT_COLOR_PURPLE "#CD00CD"
#define LIGHT_COLOR_PINK "#FF33CC"
-#define LIGHT_COLOR_WHITE "#FFFFFF"
\ No newline at end of file
+#define LIGHT_COLOR_WHITE "#FFFFFF"
diff --git a/code/controllers/Processes/event.dm b/code/controllers/Processes/event.dm
index 9948ef18ea6..64cd6031c8e 100644
--- a/code/controllers/Processes/event.dm
+++ b/code/controllers/Processes/event.dm
@@ -1,6 +1,48 @@
/datum/controller/process/event/setup()
name = "event"
schedule_interval = 20 // every 2 seconds
+ if(!holiday_master)
+ holiday_master = new
+ holiday_master.Setup()
/datum/controller/process/event/doWork()
- event_manager.process()
\ No newline at end of file
+ event_manager.process()
+ holiday_master.process()
+
+/////////
+//Holiday controller
+/////////
+
+var/global/datum/controller/holiday/holiday_master //This has to be defined before world.
+
+/datum/controller/holiday
+ var/list/holidays
+
+/datum/controller/holiday/proc/Setup()
+ getHoliday()
+
+/datum/controller/holiday/proc/process()
+ if(holiday_master.holidays)
+ for(var/datum/holiday/H in holiday_master.holidays)
+ if(H.eventChance)
+ if(prob(H.eventChance))
+ H.handle_event()
+
+/datum/controller/holiday/proc/getHoliday()
+ if(!config.allow_holidays) return //Holiday stuff was not enabled in the config!
+
+ var/YY = text2num(time2text(world.timeofday, "YY")) // get the current year
+ var/MM = text2num(time2text(world.timeofday, "MM")) // get the current month
+ var/DD = text2num(time2text(world.timeofday, "DD")) // get the current day
+
+ for(var/H in typesof(/datum/holiday) - /datum/holiday)
+ var/datum/holiday/holiday = new H()
+ if(holiday.shouldCelebrate(DD, MM, YY))
+ holiday.celebrate()
+ if(!holidays)
+ holidays = list()
+ holidays[holiday.name] = holiday
+
+ if(holidays)
+ holidays = shuffle(holidays)
+ world.update_status()
\ No newline at end of file
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index 9f123cb82de..75253e430e4 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -153,6 +153,7 @@
var/list/event_delay_upper = list(EVENT_LEVEL_MUNDANE = 9000, EVENT_LEVEL_MODERATE = 27000, EVENT_LEVEL_MAJOR = 42000)
var/starlight = 0 // Whether space turfs have ambient light or not
+ var/allow_holidays = 0
/datum/configuration/New()
var/list/L = typesof(/datum/game_mode) - /datum/game_mode
@@ -377,7 +378,7 @@
config.popup_admin_pm = 1
if("allow_holidays")
- Holiday = 1
+ config.allow_holidays = 1
if("use_irc_bot")
use_irc_bot = 1
diff --git a/code/game/gamemodes/gameticker.dm b/code/game/gamemodes/gameticker.dm
index 98f1f81aafc..bafcec2807b 100644
--- a/code/game/gamemodes/gameticker.dm
+++ b/code/game/gamemodes/gameticker.dm
@@ -158,8 +158,11 @@ var/global/datum/controller/gameticker/ticker
world << "Enjoy the game!"
world << sound('sound/AI/welcome.ogg') // Skie
- //Holiday Round-start stuff ~Carn
- Holiday_Game_Start()
+ if(holiday_master.holidays)
+ world << "and..."
+ for(var/holidayname in holiday_master.holidays)
+ var/datum/holiday/holiday = holiday_master.holidays[holidayname]
+ world << "
[holiday.greet()]
"
spawn(0) // Forking dynamic room selection
var/list/area/dynamic/source/available_source_candidates = typesof(/area/dynamic/source) - /area/dynamic/source
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 24fddb2e288..363f3d504da 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -108,8 +108,8 @@ var/list/admin_verbs_spawn = list(
/client/proc/respawn_character
)
var/list/admin_verbs_server = list(
- /client/proc/Set_Holiday,
/client/proc/ToRban,
+ /client/proc/Set_Holiday,
/datum/admins/proc/startnow,
/datum/admins/proc/restart,
/datum/admins/proc/delay,
diff --git a/code/modules/holiday/christmas.dm b/code/modules/holiday/christmas.dm
new file mode 100644
index 00000000000..9636a1681f6
--- /dev/null
+++ b/code/modules/holiday/christmas.dm
@@ -0,0 +1,65 @@
+/datum/holiday/xmas/celebrate()
+ for(var/obj/structure/flora/tree/pine/xmas in world)
+ if(!(xmas.z in config.station_levels)) continue
+ for(var/turf/simulated/floor/T in orange(1,xmas))
+ for(var/i=1,i<=rand(1,5),i++)
+ new /obj/item/weapon/a_gift(T)
+ for(var/mob/living/simple_animal/corgi/Ian/Ian in mob_list)
+ Ian.place_on_head(new /obj/item/clothing/head/helmet/space/santahat(Ian))
+
+/datum/holiday/xmas/handle_event()
+ spawnTree()
+
+/datum/holiday/xmas/proc/spawnTree()
+ for(var/obj/structure/flora/tree/pine/xmas in world)
+ var/mob/living/simple_animal/hostile/tree/evil_tree = new /mob/living/simple_animal/hostile/tree(xmas.loc)
+ evil_tree.icon_state = xmas.icon_state
+ evil_tree.icon_living = evil_tree.icon_state
+ evil_tree.icon_dead = evil_tree.icon_state
+ evil_tree.icon_gib = evil_tree.icon_state
+ del(xmas)
+
+/obj/item/weapon/toy/xmas_cracker
+ name = "xmas cracker"
+ icon = 'icons/obj/christmas.dmi'
+ icon_state = "cracker"
+ desc = "Directions for use: Requires two people, one to pull each end."
+ var/cracked = 0
+
+/obj/item/weapon/toy/xmas_cracker/New()
+ ..()
+
+/obj/item/weapon/toy/xmas_cracker/attack(mob/target, mob/user)
+ if( !cracked && istype(target,/mob/living/carbon/human) && (target.stat == CONSCIOUS) && !target.get_active_hand() )
+ target.visible_message("[user] and [target] pop \an [src]! *pop*", "You pull \an [src] with [target]! *pop*", "You hear a *pop*.")
+ var/obj/item/weapon/paper/Joke = new /obj/item/weapon/paper(user.loc)
+ Joke.name = "[pick("awful","terrible","unfunny")] joke"
+ Joke.info = pick("What did one snowman say to the other?\n\n'Is it me or can you smell carrots?'",
+ "Why couldn't the snowman get laid?\n\nHe was frigid!",
+ "Where are santa's helpers educated?\n\nNowhere, they're ELF-taught.",
+ "What happened to the man who stole advent calanders?\n\nHe got 25 days.",
+ "What does Santa get when he gets stuck in a chimney?\n\nClaus-trophobia.",
+ "Where do you find chili beans?\n\nThe north pole.",
+ "What do you get from eating tree decorations?\n\nTinsilitis!",
+ "What do snowmen wear on their heads?\n\nIce caps!",
+ "Why is Christmas just like life on ss13?\n\nYou do all the work and the fat guy gets all the credit.",
+ "Why doesn’t Santa have any children?\n\nBecause he only comes down the chimney.")
+ new /obj/item/clothing/head/festive(target.loc)
+ user.update_icons()
+ cracked = 1
+ icon_state = "cracker1"
+ var/obj/item/weapon/toy/xmas_cracker/other_half = new /obj/item/weapon/toy/xmas_cracker(target)
+ other_half.cracked = 1
+ other_half.icon_state = "cracker2"
+ target.put_in_active_hand(other_half)
+ playsound(user, 'sound/effects/snap.ogg', 50, 1)
+ return 1
+ return ..()
+
+/obj/item/clothing/head/festive
+ name = "festive paper hat"
+ icon_state = "xmashat"
+ desc = "A crappy paper hat that you are REQUIRED to wear."
+ flags_inv = 0
+ armor = list(melee = 0, bullet = 0, laser = 0,energy = 0, bomb = 0, bio = 0, rad = 0)
+
diff --git a/code/modules/holiday/holiday.dm b/code/modules/holiday/holiday.dm
new file mode 100644
index 00000000000..727c899d2eb
--- /dev/null
+++ b/code/modules/holiday/holiday.dm
@@ -0,0 +1,358 @@
+/datum/holiday
+ var/name = "Bugsgiving"
+ //Right now, only holidays that take place on a certain day or within a time period are supported
+ //It would be nice to support things like "the second monday in march" or "the first sunday after the second sunday in june"
+ var/begin_day = 1
+ var/begin_month = 0
+ var/end_day = 0 // Default of 0 means the holiday lasts a single day
+ var/end_month = 0
+ var/eventChance = 0
+
+// This proc gets run before the game starts when the holiday is activated. Do festive shit here.
+/datum/holiday/proc/celebrate()
+
+// When the round starts, this proc is ran to get a text message to display to everyone to wish them a happy holiday
+/datum/holiday/proc/greet()
+ return "Have a happy [name]!"
+
+// Returns special prefixes for the station name on certain days. You wind up with names like "Christmas Object Epsilon". See new_station_name()
+/datum/holiday/proc/getStationPrefix()
+ //get the first word of the Holiday and use that
+ var/i = findtext(name," ",1,0)
+ return copytext(name,1,i)
+
+// Return 1 if this holidy should be celebrated today
+/datum/holiday/proc/shouldCelebrate(dd, mm, yy)
+ if(!end_day)
+ end_day = begin_day
+ if(!end_month)
+ end_month = begin_month
+
+ if(end_month > begin_month) //holiday spans multiple months in one year
+ if(mm == end_month) //in final month
+ if(dd <= end_day)
+ return 1
+
+ else if(mm == begin_month)//in first month
+ if(dd >= begin_day)
+ return 1
+
+ else if(mm in begin_month to end_month) //holiday spans 3+ months and we're in the middle, day doesn't matter at all
+ return 1
+
+ else if(end_month == begin_month) // starts and stops in same month, simplest case
+ if(mm == begin_month && (dd in begin_day to end_day))
+ return 1
+
+ else // starts in one year, ends in the next
+ if(mm >= begin_month && dd >= begin_day) // Holiday ends next year
+ return 1
+ if(mm <= end_month && dd <= end_day) // Holiday started last year
+ return 1
+
+ return 0
+
+/datum/holiday/proc/handle_event() //used for special holiday events
+ return
+
+// The actual holidays
+
+/datum/holiday/new_year
+ name = NEW_YEAR
+ begin_day = 30 // 1 day early
+ begin_month = DECEMBER
+ end_day = 5 //4 days extra
+ end_month = JANUARY
+
+/datum/holiday/groundhog
+ name = "Groundhog Day"
+ begin_day = 2
+ begin_month = FEBRUARY
+
+/datum/holiday/valentines
+ name = VALENTINES
+ begin_day = 9 //6 days early
+ begin_month = FEBRUARY
+ end_day = 15 //1 day extra
+
+/datum/holiday/random_kindness
+ name = "Random Acts of Kindness Day"
+ begin_day = 17
+ begin_month = FEBRUARY
+
+/datum/holiday/random_kindness/greet()
+ return "Go do some random acts of kindness for a stranger!" //haha yeah right
+
+/datum/holiday/pi
+ name = "Pi Day"
+ begin_day = 14
+ begin_month = MARCH
+
+/datum/holiday/no_this_is_patrick
+ name = "St. Patrick's Day"
+ begin_day = 17
+ begin_month = MARCH
+
+/datum/holiday/april_fools
+ name = APRIL_FOOLS
+ begin_day = 1
+ begin_month = APRIL
+ end_day = 8 //7 days extra so everyone can enjoy the festivities
+
+/datum/holiday/fourtwenty
+ name = "Four-Twenty"
+ begin_day = 20
+ begin_month = APRIL
+
+/datum/holiday/earth
+ name = "Earth Day"
+ begin_day = 22
+ begin_month = APRIL
+
+/datum/holiday/labor
+ name = "Labor Day"
+ begin_day = 1
+ begin_month = MAY
+
+/datum/holiday/firefighter
+ name = "Firefighter's Day"
+ begin_day = 4
+ begin_month = MAY
+
+// No holidays in June :'(
+
+/datum/holiday/doctor
+ name = "Doctor's Day"
+ begin_day = 1
+ begin_month = JULY
+
+/datum/holiday/UFO
+ name = "UFO Day"
+ begin_day = 2
+ begin_month = JULY
+
+/datum/holiday/writer
+ name = "Writer's Day"
+ begin_day = 8
+ begin_month = JULY
+
+/datum/holiday/friendship
+ name = "Friendship Day"
+ begin_day = 30
+ begin_month = JULY
+
+/datum/holiday/friendship/greet()
+ return "Have a magical [name]!"
+
+/datum/holiday/beer
+ name = "Beer Day"
+ begin_day = 5
+ begin_month = AUGUST
+
+/datum/holiday/pirate
+ name = "Talk-Like-a-Pirate Day"
+ begin_day = 19
+ begin_month = SEPTEMBER
+
+/datum/holiday/pirate/greet()
+ return "Ye be talkin' like a pirate today or else ye'r walkin' tha plank, matey!"
+
+/datum/holiday/questions
+ name = "Stupid-Questions Day"
+ begin_day = 28
+ begin_month = SEPTEMBER
+
+/datum/holiday/questions/greet()
+ return "Are you having a happy [name]?"
+
+/datum/holiday/animal
+ name = "Animal's Day"
+ begin_day = 4
+ begin_month = OCTOBER
+
+/datum/holiday/smile
+ name = "Smiling Day"
+ begin_day = 7
+ begin_month = OCTOBER
+
+/datum/holiday/boss
+ name = "Boss' Day"
+ begin_day = 16
+ begin_month = OCTOBER
+
+/datum/holiday/halloween
+ name = HALLOWEEN
+ begin_day = 24 //7 days early
+ begin_month = OCTOBER
+ end_day = 7 //7 days extra
+ end_month = NOVEMBER
+
+/datum/holiday/halloween/greet()
+ return "Have a spooky Halloween!"
+
+/datum/holiday/vegan
+ name = "Vegan Day"
+ begin_day = 1
+ begin_month = NOVEMBER
+
+/datum/holiday/kindness
+ name = "Kindness Day"
+ begin_day = 13
+ begin_month = NOVEMBER
+
+/datum/holiday/flowers
+ name = "Flowers Day"
+ begin_day = 19
+ begin_month = NOVEMBER
+
+/datum/holiday/hello
+ name = "Saying-'Hello' Day"
+ begin_day = 21
+ begin_month = NOVEMBER
+
+/datum/holiday/hello/greet()
+ return "[pick(list("Aloha", "Bonjour", "Hello", "Hi", "Greetings", "Salutations", "Bienvenidos", "Hola", "Howdy"))]! " + ..()
+
+/datum/holiday/human_rights
+ name = "Human-Rights Day"
+ begin_day = 10
+ begin_month = DECEMBER
+
+/datum/holiday/monkey
+ name = "Monkey Day"
+ begin_day = 14
+ begin_month = DECEMBER
+
+/datum/holiday/xmas
+ name = CHRISTMAS
+ begin_day = 18 //7 days early
+ begin_month = DECEMBER
+ end_day = 8 //14 days extra, christmas is important
+ end_month = JANUARY
+ eventChance = 20
+
+/datum/holiday/xmas/greet()
+ return "Have a merry Christmas!"
+
+/datum/holiday/boxing
+ name = "Boxing Day"
+ begin_day = 26
+ begin_month = DECEMBER
+
+/datum/holiday/friday_thirteenth
+ name = "Friday the 13th"
+
+/datum/holiday/friday_thirteenth/shouldCelebrate(dd, mm, yy)
+ if(dd == 13)
+ if(time2text(world.timeofday, "DDD") == "Fri")
+ return 1
+ return 0
+
+/datum/holiday/friday_thirteenth/getStationPrefix()
+ return pick("Mike","Friday","Evil","Myers","Murder","Deathly","Stabby")
+
+/datum/holiday/easter
+ name = EASTER
+ var/const/days_early = 1 //to make editing the holiday easier
+ var/const/days_extra = 6
+
+/datum/holiday/easter/shouldCelebrate(dd, mm, yy)
+// Easter's celebration day is as snowflakey as Uhangi's code
+
+ if(!begin_month)
+
+ var/yy_string = "[yy]"
+// year = days after March 22that Easter falls on that year.
+// For 2015 Easter is on April 5th, so 2015 = 14 since the 5th is 14 days past the 22nd
+// If it's 2040 and this is still in use, invent a time machine and teach me a better way to do this. Also tell us about HL3.
+ var/list/easters = list(
+ "15" = 14,\
+ "16" = 6,\
+ "17" = 25,\
+ "18" = 10,\
+ "19" = 30,\
+ "20" = 22,\
+ "21" = 13,\
+ "22" = 26,\
+ "23" = 18,\
+ "24" = 9,\
+ "25" = 29,\
+ "26" = 14,\
+ "27" = 6,\
+ "28" = 25,\
+ "29" = 10,\
+ "30" = 30,\
+ "31" = 23,\
+ "32" = 6,\
+ "33" = 26,\
+ "34" = 18,\
+ "35" = 3,\
+ "36" = 22,\
+ "37" = 14,\
+ "38" = 34,\
+ "39" = 19,\
+ "40" = 9,\
+ )
+
+ begin_day = easters[yy_string]
+ if(begin_day <= 9)
+ begin_day += 22
+ begin_month = MARCH
+ else
+ begin_day -= 9
+ begin_month = APRIL
+
+ end_day = begin_day + days_extra
+ end_month = begin_month
+ if(end_day >= 32 && end_month == MARCH) //begins in march, ends in april
+ end_day -= 31
+ end_month++
+ if(end_day >= 31 && end_month == APRIL) //begins in april, ends in june
+ end_day -= 30
+ end_month++
+
+ begin_day -= days_early
+ if(begin_day <= 0)
+ if(begin_month == APRIL)
+ begin_day += 31
+ begin_month-- //begins in march, ends in april
+
+// world << "Easter calculates to be on [begin_day] of [begin_month] ([days_early] early) to [end_day] of [end_month] ([days_extra] extra) for 20[yy]"
+ return ..()
+
+
+/client/proc/Set_Holiday(T as text|null)
+ set name = ".Set Holiday"
+ set category = "Event"
+ set desc = "Force-set the Holiday variable to make the game think it's a certain day."
+ if(!check_rights(R_SERVER)) return
+
+ var/list/choice = list()
+ for(var/H in typesof(/datum/holiday) - /datum/holiday)
+ choice += "[H]"
+
+ choice += "--CANCEL--"
+
+ var/selected = input("What holiday would you like to force?","Holiday Forcing","--CANCEL--") in choice
+
+ if(selected == "--CANCEL--")
+ return
+
+ var/selected2path = text2path(selected)
+ if(!ispath(selected2path) || !selected2path) return
+
+ var/datum/holiday/H = new selected2path
+ if(!istype(H)) return
+
+ H.celebrate()
+ if(!holiday_master.holidays)
+ holiday_master.holidays = list()
+ holiday_master.holidays[H.name] = H
+
+ station_name = null
+ station_name()
+ //update our hub status
+ world.update_status()
+
+ message_admins("\blue ADMIN: Event: [key_name(src)] force-set Holiday to \"[H]\"")
+ log_admin("[key_name(src)] force-set Holiday to \"[H]\"")
\ No newline at end of file
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index ac55fa53d1a..7ec4763d572 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -86,7 +86,7 @@
/obj/item/weapon/paper/attack_self(mob/living/user as mob)
src.examine(user)
- if(rigged && (Holiday == "April Fool's Day"))
+ if(rigged && (holiday_master.holidays && holiday_master.holidays[APRIL_FOOLS]))
if(spam_flag == 0)
spam_flag = 1
playsound(loc, 'sound/items/bikehorn.ogg', 50, 1)
diff --git a/code/modules/paperwork/paperbin.dm b/code/modules/paperwork/paperbin.dm
index a30412156ee..67d1eacab72 100644
--- a/code/modules/paperwork/paperbin.dm
+++ b/code/modules/paperwork/paperbin.dm
@@ -71,7 +71,7 @@
papers.Remove(P)
else
P = new /obj/item/weapon/paper
- if(Holiday == "April Fool's Day")
+ if(holiday_master.holidays && holiday_master.holidays[APRIL_FOOLS])
if(prob(30))
P.info = "HONK HONK HONK HONK HONK HONK HONK
HOOOOOOOOOOOOOOOOOOOOOONK
APRIL FOOLS"
P.rigged = 1
diff --git a/paradise.dme b/paradise.dme
index 6c38167ecc7..4ba9dd736c0 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -308,9 +308,6 @@
#include "code\game\gamemodes\events\clang.dm"
#include "code\game\gamemodes\events\power_failure.dm"
#include "code\game\gamemodes\events\wormholes.dm"
-#include "code\game\gamemodes\events\holidays\Christmas.dm"
-#include "code\game\gamemodes\events\holidays\Holidays.dm"
-#include "code\game\gamemodes\events\holidays\Other.dm"
#include "code\game\gamemodes\extended\extended.dm"
#include "code\game\gamemodes\heist\heist.dm"
#include "code\game\gamemodes\malfunction\Malf_Modules.dm"
@@ -1133,6 +1130,8 @@
#include "code\modules\food\recipes_microwave.dm"
#include "code\modules\food\recipes_oven.dm"
#include "code\modules\genetics\side_effects.dm"
+#include "code\modules\holiday\christmas.dm"
+#include "code\modules\holiday\holiday.dm"
#include "code\modules\hydroponics\_hydro_setup.dm"
#include "code\modules\hydroponics\grown.dm"
#include "code\modules\hydroponics\grown_inedible.dm"