code:
/////RESTAURANT/////
/datum/venue/restaurant
	name = "restaurant"
	req_access = ACCESS_KITCHEN
	venue_type = VENUE_RESTAURANT
	min_time_between_visitor = 80 SECONDS
	max_time_between_visitor = 100 SECONDS
	customer_types = list(
		/datum/customer_data/american = 50,
		/datum/customer_data/italian = 30,
		/datum/customer_data/french = 30,
		/datum/customer_data/mexican = 30,
		/datum/customer_data/japanese = 30,
		/datum/customer_data/japanese/salaryman = 20,
		/datum/customer_data/british/bobby = 20,
		/datum/customer_data/british/gent = 20,
		/datum/customer_data/moth = 1,
		/datum/customer_data/malfunction = 1,
	)

/datum/venue/restaurant/order_food(mob/living/simple_animal/robot_customer/customer_pawn, datum/customer_data/customer_data)
	var/obj/item/object_to_order = customer_data.get_order(src)

	. = object_to_order

	customer_pawn.say(order_food_line(object_to_order))

	var/appearance = SSrestaurant.food_appearance_cache[object_to_order]

	if(!appearance) //We havn't made this one before, do so now.
		var/obj/item/temp_object = new object_to_order() //Make a temp object so we can see it including any overlays
		appearance = temp_object.appearance //And then steal its appearance
		SSrestaurant.food_appearance_cache[object_to_order] = appearance //and cache it for future orders
		qdel(temp_object)

	var/image/I = image(icon = 'icons/obj/machines/restaurant_portal.dmi' , icon_state = "thought_bubble", loc = customer_pawn)

	I.appearance = appearance
	I.underlays += mutable_appearance(icon = 'icons/obj/machines/restaurant_portal.dmi' , icon_state = "thought_bubble")
	I.pixel_y = 32
	I.pixel_x = 16
	I.plane = HUD_PLANE
	I.appearance_flags = RESET_COLOR
	customer_pawn.hud_to_show_on_hover = customer_pawn.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/food_demands, "food_thoughts", I)

/datum/venue/restaurant/is_correct_order(atom/movable/object_used, wanted_item)
	return object_used.type == wanted_item

/datum/venue/restaurant/order_food_line(obj/item/order)
	return "I'll take \a [initial(order.name)]"

/datum/venue/restaurant/on_get_order(mob/living/simple_animal/robot_customer/customer_pawn, obj/item/order_item)
	. = ..()
	var/obj/item/food/ordered_food = order_item
	customer_pawn.visible_message(span_danger("[customer_pawn] pushes [ordered_food] into their mouth-shaped hole!"), span_danger("You push [ordered_food] into your mouth-shaped hole."))
	playsound(get_turf(customer_pawn),'sound/items/eatfood.ogg', rand(10,50), TRUE)
	customers_served += 1
	qdel(ordered_food)

/obj/machinery/restaurant_portal/restaurant
	linked_venue = /datum/venue/restaurant
/obj/item/holosign_creator/robot_seat/restaurant
	name = "restaurant seating indicator placer"
	holosign_type = /obj/structure/holosign/robot_seat/restaurant

/obj/structure/holosign/robot_seat/restaurant
	name = "restaurant seating"
	linked_venue = /datum/venue/restaurant

/////BAR/////
/datum/venue/bar
	name = "bar"
	req_access = ACCESS_BAR
	venue_type = VENUE_BAR
	min_time_between_visitor = 40 SECONDS
	max_time_between_visitor = 60 SECONDS
	customer_types = list(
		/datum/customer_data/american = 50,
		/datum/customer_data/italian = 30,
		/datum/customer_data/french = 30,
		/datum/customer_data/mexican = 30,
		/datum/customer_data/japanese = 30,
		/datum/customer_data/japanese/salaryman = 20,
		/datum/customer_data/british/bobby = 20,
		/datum/customer_data/british/gent = 20,
		/datum/customer_data/malfunction = 1,
	)

/datum/venue/bar/order_food(mob/living/simple_animal/robot_customer/customer_pawn, datum/customer_data/customer_data)
	var/datum/reagent/reagent_to_order = pick_weight(customer_data.orderable_objects[venue_type])

	var/glass_visual

	if(initial(reagent_to_order.glass_icon_state))
		glass_visual = initial(reagent_to_order.glass_icon_state)
	else if(initial(reagent_to_order.shot_glass_icon_state))
		glass_visual = initial(reagent_to_order.shot_glass_icon_state)
	else if(initial(reagent_to_order.fallback_icon_state))
		glass_visual = initial(reagent_to_order.fallback_icon_state)
	else
		CRASH("[reagent_to_order] has no icon sprite for restaurant code, please set a fallback_icon_state for this reagent.")

	customer_pawn.say(order_food_line(reagent_to_order))

	var/image/I = image(icon = 'icons/obj/machines/restaurant_portal.dmi' , icon_state = "thought_bubble", loc = customer_pawn)
	I.add_overlay(mutable_appearance('icons/obj/drinks.dmi', glass_visual))
	I.pixel_y = 32
	I.pixel_x = 16
	I.plane = HUD_PLANE
	I.appearance_flags = RESET_COLOR
	customer_pawn.hud_to_show_on_hover = customer_pawn.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/food_demands, "food_thoughts", I)

	return reagent_to_order

/datum/venue/bar/order_food_line(datum/reagent/order)
	return "I'll take a glass of [initial(order.name)]"

/datum/venue/bar/on_get_order(mob/living/simple_animal/robot_customer/customer_pawn, obj/item/order_item)
	var/datum/reagent/consumable/ordered_reagent_type = customer_pawn.ai_controller.blackboard[BB_CUSTOMER_CURRENT_ORDER]

	for(var/datum/reagent/reagent as anything in order_item.reagents.reagent_list)
		if(reagent.type != ordered_reagent_type)
			continue
		SEND_SIGNAL(reagent, COMSIG_ITEM_SOLD_TO_CUSTOMER, customer_pawn, order_item)

	customer_pawn.visible_message(span_danger("[customer_pawn] slurps up [order_item] in one go!"), span_danger("You slurp up [order_item] in one go."))
	playsound(get_turf(customer_pawn), 'sound/items/drink.ogg', 50, TRUE)
	customers_served += 1
	order_item.reagents.clear_reagents()

///The bar needs to have a minimum amount of the reagent
/datum/venue/bar/is_correct_order(object_used, wanted_item)
	if(istype(object_used, /obj/item/reagent_containers/food/drinks))
		var/obj/item/reagent_containers/food/drinks/potential_drink = object_used
		return potential_drink.reagents.has_reagent(wanted_item, VENUE_BAR_MINIMUM_REAGENTS)
/obj/machinery/restaurant_portal/bar
	linked_venue = /datum/venue/bar

/obj/item/holosign_creator/robot_seat/bar
	name = "bar seating indicator placer"
	holosign_type = /obj/structure/holosign/robot_seat/bar

/obj/structure/holosign/robot_seat/bar
	name = "bar seating"
	linked_venue = /datum/venue/bar


code:
// CARDS AGAINST SPESS
// This is a parody of Cards Against Humanity (https://en.wikipedia.org/wiki/Cards_Against_Humanity)
// which is licensed under CC BY-NC-SA 2.0, the full text of which can be found at the following URL:
// https://creativecommons.org/licenses/by-nc-sa/2.0/legalcode
// Original code by Zuhayr, Polaris Station, ported with modifications
/datum/playingcard
	var/name = "playing card"
	var/card_icon = "card_back"
	var/suit
	var/number

/obj/item/toy/cards/deck/cas
	name = "\improper CAS deck (white)"
	desc = "A deck for the game Cards Against Spess, still popular after all these centuries. Warning: may include traces of broken fourth wall. This is the white deck."
	icon = 'icons/obj/toy.dmi'
	icon_state = "deck_caswhite_full"
	deckstyle = "caswhite"
	var/card_face = "cas_white"
	var/blanks = 25
	var/decksize = 150
	var/card_text_file = "strings/cas_white.txt"
	var/list/allcards = list()

/obj/item/toy/cards/deck/cas/black
	name = "\improper CAS deck (black)"
	desc = "A deck for the game Cards Against Spess, still popular after all these centuries. Warning: may include traces of broken fourth wall. This is the black deck."
	icon_state = "deck_casblack_full"
	deckstyle = "casblack"
	card_face = "cas_black"
	blanks = 0
	decksize = 50
	card_text_file = "strings/cas_black.txt"

/obj/item/toy/cards/deck/cas/populate_deck()
	var/static/list/cards_against_space = list("cas_white" = world.file2list("strings/cas_white.txt"),"cas_black" = world.file2list("strings/cas_black.txt"))
	allcards = cards_against_space[card_face]
	var/list/possiblecards = allcards.Copy()
	if(possiblecards.len < decksize) // sanity check
		decksize = (possiblecards.len - 1)
	var/list/randomcards = list()
	for(var/x in 1 to decksize)
		randomcards += pick_n_take(possiblecards)
	for(var/x in 1 to randomcards.len)
		var/cardtext = randomcards[x]
		var/datum/playingcard/P
		P = new()
		P.name = "[cardtext]"
		P.card_icon = "[src.card_face]"
		cards += P
	if(!blanks)
		return
	for(var/x in 1 to blanks)
		var/datum/playingcard/P
		P = new()
		P.name = "Blank Card"
		P.card_icon = "cas_white"
		cards += P
	shuffle_inplace(cards) // distribute blank cards throughout deck

/obj/item/toy/cards/deck/cas/draw_card(mob/user)
	if(isliving(user))
		var/mob/living/L = user
		if(!(L.mobility_flags & MOBILITY_PICKUP))
			return
	if(cards.len == 0)
		to_chat(user, span_warning("There are no more cards to draw!"))
		return
	var/obj/item/toy/cards/singlecard/cas/H = new/obj/item/toy/cards/singlecard/cas(user.loc)
	var/datum/playingcard/choice = cards[1]
	if (choice.name == "Blank Card")
		H.blank = 1
	H.name = choice.name
	H.buffertext = choice.name
	H.icon_state = choice.card_icon
	H.card_face = choice.card_icon
	H.parentdeck = src
	src.cards -= choice
	H.pickup(user)
	user.put_in_hands(H)
	user.visible_message(span_notice("[user] draws a card from the deck."), span_notice("You draw a card from the deck."))
	update_appearance()

/obj/item/toy/cards/deck/cas/attackby(obj/item/I, mob/living/user, params)
	if(istype(I, /obj/item/toy/cards/singlecard/cas))
		var/obj/item/toy/cards/singlecard/cas/SC = I
		if(!user.temporarilyRemoveItemFromInventory(SC))
			to_chat(user, span_warning("The card is stuck to your hand, you can't add it to the deck!"))
			return
		var/datum/playingcard/RC // replace null datum for the re-added card
		RC = new()
		RC.name = "[SC.name]"
		RC.card_icon = SC.card_face
		cards += RC
		user.visible_message(span_notice("[user] adds a card to the bottom of the deck."),span_notice("You add the card to the bottom of the deck."))
		qdel(SC)
	update_appearance()

/obj/item/toy/cards/deck/cas/update_icon_state()
	if(cards.len < 26)
		icon_state = "deck_[deckstyle]_low"
	return ..()

/obj/item/toy/cards/singlecard/cas
	name = "CAS card"
	desc = "A CAS card."
	icon_state = "cas_white"
	flipped = 0
	var/card_face = "cas_white"
	var/blank = 0
	var/buffertext = "A funny bit of text."

/obj/item/toy/cards/singlecard/cas/examine(mob/user)
	. = ..()
	if (flipped)
		. += span_notice("The card is face down.")
	else if (blank)
		. += span_notice("The card is blank. Write on it with a pen.")
	else
		. += span_notice("The card reads: [name]")
	. += span_notice("Alt-click to flip it.")

/obj/item/toy/cards/singlecard/cas/Flip()
	set name = "Flip Card"
	set category = "Object"
	set src in range(1)
	if(!ishuman(usr) || !usr.canUseTopic(src, BE_CLOSE))
		return
	if(!flipped)
		name = "CAS card"
	else if(flipped)
		name = buffertext
	flipped = !flipped
	update_appearance()

/obj/item/toy/cards/singlecard/cas/AltClick(mob/living/user)
	if(!ishuman(user) || !user.canUseTopic(src, BE_CLOSE))
		return
	Flip()

/obj/item/toy/cards/singlecard/cas/update_icon_state()
	icon_state = "[card_face][flipped ? "_flipped" : null]"
	return ..()

/obj/item/toy/cards/singlecard/cas/attackby(obj/item/I, mob/living/user, params)
	if(istype(I, /obj/item/pen))
		if(!user.is_literate())
			to_chat(user, span_notice("You scribble illegibly on [src]!"))
			return
		if(!blank)
			to_chat(user, span_warning("You cannot write on that card!"))
			return
		var/cardtext = stripped_input(user, "What do you wish to write on the card?", "Card Writing", "", 50)
		if(!cardtext || !user.canUseTopic(src, BE_CLOSE))
			return
		name = cardtext
		buffertext = cardtext
		blank = 0

repo_name: AnalWerewolf/sundown

license: mit


code:
/obj/item/toy/cards/deck/kotahi
	name = "\improper KOTAHI deck"
	desc = "A deck of kotahi cards. House rules to argue over not included."
	icon = 'icons/obj/toy.dmi'
	icon_state = "deck_kotahi_full"
	deckstyle = "kotahi"

//Populate the deck.
/obj/item/toy/cards/deck/kotahi/populate_deck()
	for(var/colour in list("Red","Yellow","Green","Blue"))
		cards += "[colour] 0" //kotahi decks have only one colour of each 0, weird huh?
		for(var/k in 0 to 1) //two of each colour of number
			cards += "[colour] skip"
			cards += "[colour] reverse"
			cards += "[colour] draw 2"
			for(var/i in 1 to 9)
				cards += "[colour] [i]"
	for(var/k in 0 to 3) //4 wilds and draw 4s
		cards += "Wildcard"
		cards += "Draw 4"


code:
//These cards certainly won't tell the future, but you can play some nice games with them.
/obj/item/toy/cards/deck/tarot
	name = "tarot game deck"
	desc = "A full 78 card game deck of tarot cards. Complete with 4 suites of 14 cards, and a full suite of trump cards."
	icon = 'icons/obj/toy.dmi'
	icon_state = "deck_tarot_full"
	deckstyle = "tarot"

/obj/item/toy/cards/deck/tarot/populate_deck()
	for(var/suit in list("Hearts", "Pikes", "Clovers", "Tiles"))
		for(var/i in 1 to 10)
			cards += "[i] of [suit]"
		for(var/person in list("Valet", "Chevalier", "Dame", "Roi"))
			cards += "[person] of [suit]"
	for(var/trump in list("The Magician", "The High Priestess", "The Empress", "The Emperor", "The Hierophant", "The Lover", "The Chariot", "Justice", "The Hermit", "The Wheel of Fortune", "Strength", "The Hanged Man", "Death", "Temperance", "The Devil", "The Tower", "The Star", "The Moon", "The Sun", "Judgement", "The World", "The Fool"))
		cards += "[trump]"

/obj/item/toy/cards/deck/tarot/draw_card(mob/user)
	. = ..()
	var/obj/item/toy/cards/singlecard/C = .
	var/matrix/M = matrix()
	M.Turn(180)
	if(prob(50))
		C.transform = M
	return


code:
//It's Wiz-Off, the wizard themed card game! It's modular too, in case you might want to make it Syndie, Sec and Clown themed or something stupid like that.
/obj/item/toy/cards/deck/wizoff
	name = "\improper Wiz-Off deck"
	desc = "A Wiz-Off deck. Fight an arcane battle for the fate of the universe: Draw 5! Play 5! Best of 5! A rules card is attached."
	icon = 'icons/obj/toy.dmi'
	icon_state = "deck_wizoff_full"
	deckstyle = "wizoff"
	var/theme = "wizard"

/obj/item/toy/cards/deck/wizoff/populate_deck()
	var/card_list = strings("wizoff.json", theme)
	for(var/card in card_list)
		cards += "[card]"

/obj/item/toy/cards/deck/wizoff/examine_more(mob/user)
	var/list/msg = list(span_notice("Remember the rules of Wiz-Off!"))
	msg += "\t[span_info("Each player draws 5 cards.")]"
	msg += "\t[span_info("There are five rounds. Each round, a player selects a card to play, and the winner is selected based on the following rules:")]"
	msg += "\t[span_info("Defensive beats Offensive!")]"
	msg += "\t[span_info("Offensive beats Utility!")]"
	msg += "\t[span_info("Utility beats Defensive!")]"
	msg += "\t[span_info("If both players play the same type of spell, the higher number wins!")]"
	msg += "\t[span_info("The player who wins the most of the 5 rounds wins the game!")]"
	msg += "\t[span_notice("Now get ready to battle for the fate of the universe: Wiz-Off!")]"
	return msg


code:
/datum/round_event_control/easter
	name = "Easter Eggselence"
	holidayID = EASTER
	typepath = /datum/round_event/easter
	weight = -1
	max_occurrences = 1
	earliest_start = 0 MINUTES

/datum/round_event/easter/announce(fake)
	priority_announce(pick("Hip-hop into Easter!","Find some Bunny's stash!","Today is National 'Hunt a Wabbit' Day.","Be kind, give Chocolate Eggs!"))

/datum/round_event_control/rabbitrelease
	name = "Release the Rabbits!"
	holidayID = EASTER
	typepath = /datum/round_event/rabbitrelease
	weight = 5
	max_occurrences = 10

/datum/round_event/rabbitrelease/announce(fake)
	priority_announce("Unidentified furry objects detected coming aboard [station_name()]. Beware of Adorable-ness.", "Fluffy Alert", ANNOUNCER_ALIENS)

/datum/round_event/rabbitrelease/start()
	for(var/obj/effect/landmark/R in GLOB.landmarks_list)
		if(R.name != "blobspawn")
			if(prob(35))
				if(isspaceturf(R.loc))
					new /mob/living/simple_animal/rabbit/space(R.loc)
				else
					new /mob/living/simple_animal/rabbit(R.loc)

/mob/living/simple_animal/rabbit
	name = "\improper rabbit"
	desc = "The hippiest hop around."
	gender = PLURAL
	mob_biotypes = MOB_ORGANIC|MOB_BEAST
	health = 15
	maxHealth = 15
	icon = 'icons/mob/easter.dmi'
	icon_state = "rabbit_white"
	icon_living = "rabbit_white"
	icon_dead = "rabbit_white_dead"
	speak = list("Hop into Easter!","Come get your eggs!","Prizes for everyone!")
	speak_emote = list("sniffles","twitches")
	emote_hear = list("hops.")
	emote_see = list("hops around","bounces up and down")
	butcher_results = list(/obj/item/food/meat/slab = 1)
	can_be_held = TRUE
	density = FALSE
	speak_chance = 2
	turns_per_move = 3
	response_help_continuous = "pets"
	response_help_simple = "pet"
	response_disarm_continuous = "gently pushes aside"
	response_disarm_simple = "gently push aside"
	response_harm_continuous = "kicks"
	response_harm_simple = "kick"
	attack_verb_continuous = "kicks"
	attack_verb_simple = "kick"
	pass_flags = PASSTABLE | PASSMOB
	mob_size = MOB_SIZE_SMALL
	gold_core_spawnable = FRIENDLY_SPAWN
	///passed to animal_variety component as the prefix icon.
	var/icon_prefix = "rabbit"
	///passed to egg_layer component as how many eggs it starts out as able to lay.
	var/initial_egg_amount = 10

/mob/living/simple_animal/rabbit/Initialize(mapload)
	. = ..()
	AddElement(/datum/element/pet_bonus, "hops around happily!")
	AddElement(/datum/element/animal_variety, icon_prefix, pick("brown","black","white"), TRUE)
	var/list/feed_messages = list("[p_they()] nibbles happily.", "[p_they()] noms happily.")
	var/eggs_added_from_eating = rand(1, 4)
	var/max_eggs_held = 8
	AddComponent(/datum/component/egg_layer,\
		/obj/item/surprise_egg,\
		list(/obj/item/food/grown/carrot),\
		feed_messages,\
		list("hides an egg.","scampers around suspiciously.","begins making a huge racket.","begins shuffling."),\
		initial_egg_amount,\
		eggs_added_from_eating,\
		max_eggs_held\
	)

/mob/living/simple_animal/rabbit/empty //top hats summon these kinds of rabbits instead of the normal kind
	initial_egg_amount = 0

/mob/living/simple_animal/rabbit/space
	icon_state = "s_rabbit_white"
	icon_living = "s_rabbit_white"
	icon_dead = "s_rabbit_white_dead"
	icon_prefix = "s_rabbit"
	atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
	minbodytemp = 0
	maxbodytemp = 1500
	unsuitable_atmos_damage = 0

//Easter Baskets
/obj/item/storage/basket/easter
	name = "Easter Basket"

/obj/item/storage/basket/easter/Initialize(mapload)
	. = ..()
	var/datum/component/storage/STR = GetComponent(/datum/component/storage)
	STR.set_holdable(list(/obj/item/food/egg, /obj/item/food/chocolateegg, /obj/item/food/boiledegg))

/obj/item/storage/basket/easter/proc/countEggs()
	cut_overlays()
	add_overlay("basket-grass")
	add_overlay("basket-egg[min(contents.len, 5)]")

/obj/item/storage/basket/easter/Exited(atom/movable/gone, direction)
	. = ..()
	countEggs()

/obj/item/storage/basket/easter/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
	. = ..()
	countEggs()

//Bunny Suit
/obj/item/clothing/head/bunnyhead
	name = "Easter Bunny Head"
	icon_state = "bunnyhead"
	inhand_icon_state = "bunnyhead"
	desc = "Considerably more cute than 'Frank'."
	slowdown = -0.3
	clothing_flags = THICKMATERIAL | SNUG_FIT
	flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT

/obj/item/clothing/suit/bunnysuit
	name = "Easter Bunny Suit"
	desc = "Hop Hop Hop!"
	icon_state = "bunnysuit"
	inhand_icon_state = "bunnysuit"
	slowdown = -0.3
	clothing_flags = THICKMATERIAL
	body_parts_covered = CHEST|GROIN|LEGS|ARMS
	flags_inv = HIDEGLOVES|HIDESHOES|HIDEJUMPSUIT

//Bunny bag!
/obj/item/storage/backpack/satchel/bunnysatchel
	name = "Easter Bunny Satchel"
	desc = "Good for your eyes."
	icon_state = "satchel_carrot"
	inhand_icon_state = "satchel_carrot"

//Egg prizes and egg spawns!
/obj/item/surprise_egg
	name = "wrapped egg"
	desc = "A chocolate egg containing a little something special. Unwrap and enjoy!"
	icon_state = "egg"
	resistance_flags = FLAMMABLE
	w_class = WEIGHT_CLASS_TINY
	icon = 'icons/obj/food/food.dmi'
	lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi'
	righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi'
	obj_flags = UNIQUE_RENAME

/obj/item/surprise_egg/Initialize(mapload)
	. = ..()
	var/eggcolor = pick("blue","green","mime","orange","purple","rainbow","red","yellow")
	icon_state = "egg-[eggcolor]"

/obj/item/surprise_egg/proc/dispensePrize(turf/where)
	var/static/list/prize_list = list(/obj/item/clothing/head/bunnyhead,
		/obj/item/clothing/suit/bunnysuit,
		/obj/item/storage/backpack/satchel/bunnysatchel,
		/obj/item/food/grown/carrot,
		/obj/item/toy/balloon,
		/obj/item/toy/gun,
		/obj/item/toy/sword,
		/obj/item/toy/talking/ai,
		/obj/item/toy/talking/owl,
		/obj/item/toy/talking/griffin,
		/obj/item/toy/minimeteor,
		/obj/item/toy/clockwork_watch,
		/obj/item/toy/toy_xeno,
		/obj/item/toy/foamblade,
		/obj/item/toy/plush/carpplushie,
		/obj/item/toy/redbutton,
		/obj/item/toy/windup_toolbox,
		/obj/item/clothing/head/collectable/rabbitears
		) + subtypesof(/obj/item/toy/mecha)
	var/won = pick(prize_list)
	new won(where)
	new/obj/item/food/chocolateegg(where)

/obj/item/surprise_egg/attack_self(mob/user)
	..()
	to_chat(user, span_notice("You unwrap [src] and find a prize inside!"))
	dispensePrize(get_turf(user))
	qdel(src)

//Easter Recipes + food
/obj/item/food/hotcrossbun
	name = "hot cross bun"
	desc = "The cross represents the Assistants that died for your sins."
	icon_state = "hotcrossbun"
	food_reagents = list(/datum/reagent/consumable/nutriment = 6, /datum/reagent/consumable/sugar = 1)
	foodtypes = SUGAR | GRAIN | BREAKFAST
	tastes = list("pastry" = 1, "easter" = 1)
	bite_consumption = 2

/datum/crafting_recipe/food/hotcrossbun
	name = "Hot Cross Bun"
	reqs = list(
		/obj/item/food/breadslice/plain = 1,
		/datum/reagent/consumable/sugar = 1
	)
	result = /obj/item/food/hotcrossbun

	subcategory = CAT_BREAD

/datum/crafting_recipe/food/briochecake
	name = "Brioche cake"
	reqs = list(
		/obj/item/food/cake/plain = 1,
		/datum/reagent/consumable/sugar = 2
	)
	result = /obj/item/food/cake/brioche
	subcategory = CAT_MISCFOOD

/obj/item/food/scotchegg
	name = "scotch egg"
	desc = "A boiled egg wrapped in a delicious, seasoned meatball."
	icon_state = "scotchegg"
	bite_consumption = 3
	food_reagents = list(/datum/reagent/consumable/nutriment = 6, /datum/reagent/consumable/nutriment/vitamin = 2)

/datum/crafting_recipe/food/scotchegg
	name = "Scotch egg"
	reqs = list(
		/datum/reagent/consumable/salt = 1,
		/datum/reagent/consumable/blackpepper = 1,
		/obj/item/food/boiledegg = 1,
		/obj/item/food/meatball = 1
	)
	result = /obj/item/food/scotchegg
	subcategory = CAT_EGG

/datum/crafting_recipe/food/mammi
	name = "Mammi"
	reqs = list(
		/obj/item/food/bread/plain = 1,
		/obj/item/food/chocolatebar = 1,
		/datum/reagent/consumable/milk = 5
	)
	result = /obj/item/food/soup/mammi
	subcategory = CAT_MISCFOOD

/obj/item/food/chocolatebunny
	name = "chocolate bunny"
	desc = "Contains less than 10% real rabbit!"
	icon_state = "chocolatebunny"
	food_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/consumable/sugar = 2, /datum/reagent/consumable/coco = 2, /datum/reagent/consumable/nutriment/vitamin = 1)

/datum/crafting_recipe/food/chocolatebunny
	name = "Chocolate bunny"
	reqs = list(
		/datum/reagent/consumable/sugar = 2,
		/obj/item/food/chocolatebar = 1
	)
	result = /obj/item/food/chocolatebunny
	subcategory = CAT_MISCFOOD


code:
#define BYOND_EPOCH 2451544.5
#define HEBREW_EPOCH 347995.5
#define ISLAMIC_EPOCH 1948439.5
#define GREGORIAN_EPOCH 1721424.5

/*
Source for the method of calcuation
https://www.fourmilab.ch/documents/calendar/
by John Walker 2015, released under public domain
*/
/datum/foreign_calendar
	var/jd
	var/yyyy
	var/mm
	var/dd

/datum/foreign_calendar/New(yyyy, mm, dd)
	if (!jd)
		jd = gregorian_to_jd(yyyy, mm, dd)
	set_date(jd)

/datum/foreign_calendar/proc/set_date()
	return

///Converts Gregorian date to Julian Day
/datum/foreign_calendar/proc/gregorian_to_jd(year, month, day)
	. = day // days this month
	. += (GREGORIAN_EPOCH - 1) // start at gregorian epoch
	. += (365 * (year - 1)) // add number of days
	. += round(((367 * month) - 362) / 12) // days from previous months this year
	. += round((year - 1) / 4) // figuring out leap years
	. -= round((year - 1) / 100)
	. += round((year - 1) / 400)
	if (month > 2)
		if (leap_gregorian(year))
			. -= 1
		else
			. -= 2

///Returns whether a year is a leap year in the Gregorian calendar
/datum/foreign_calendar/proc/leap_gregorian(year)
	return (year % 4 == 0) && ((year % 400 == 0) || (year % 100 != 0))

///Converts BYOND realtime to Julian Day
/datum/foreign_calendar/proc/realtime_to_jd(realtime)
	return round(realtime / 864000) + BYOND_EPOCH

//////////////////////////////
//     Islamic Calendar     //
//////////////////////////////
/datum/foreign_calendar/islamic/proc/leap_islamic(yr)
	return ((yr * 11 + 14) % 30) < 11

/datum/foreign_calendar/islamic/set_date()
	var/jd_adj = round(jd) + 0.5 // adjust julian date so it ends in .5
	yyyy = round(((30 * (jd_adj - ISLAMIC_EPOCH)) + 10646) / 10631)
	mm = min(12, CEILING(((jd - (29 + islamic_to_jd(yyyy, 1, 1))) / 29.5) + 1, 1))
	dd = jd - islamic_to_jd(yyyy, mm, 1) + 1

/datum/foreign_calendar/islamic/proc/islamic_to_jd(year, month, day)
	return day + CEILING(29.5 * (month - 1), 1) + (year - 1) * 354 + round((3 + (11 * year)) / 30) + ISLAMIC_EPOCH - 1

//////////////////////////////
//      Hebrew Calendar     //
//////////////////////////////
/datum/foreign_calendar/hebrew/proc/hebrew_leap(year)
	switch (year % 19)
		if (0, 3, 6, 8, 11, 14, 17)
			return TRUE
		else
			return FALSE

// Hebrew to Julian
/datum/foreign_calendar/hebrew/proc/hebrew_to_jd(year, month, day)
	var/months = hebrew_year_months(year)
	var/jd = HEBREW_EPOCH + hebrew_delay_1(year) + hebrew_delay_2(year) + day + 1
	if (month < 7)
		for (var/mon = 7; mon <= months; mon++)
			jd += hebrew_month_days(year, mon)
		for (var/mon = 1; mon < month; mon++)
			jd += hebrew_month_days(year, mon)
	else
		for (var/mon = 7; mon < month; mon++)
			jd += hebrew_month_days(year, mon)
	return jd

// Julian to Hebrew
/datum/foreign_calendar/hebrew/set_date(jd)
	if (yyyy && mm && dd)
		return
	jd = round(jd) + 0.5
	var/count = round(((jd - HEBREW_EPOCH) * 98496) / 35975351)
	var/year = count - 1
	for (var/i = count; jd >= hebrew_to_jd(i, 7, 1); i++)
		year++
	var/month = (jd < hebrew_to_jd(year, 1, 1)) ? 7 : 1
	for (var/i = month; jd > hebrew_to_jd(year, i, hebrew_month_days(year, i)); i++)
		month++
	var/day = (jd - hebrew_to_jd(year, month, 1)) + 1
	yyyy = year
	mm = month
	dd = day

/datum/foreign_calendar/hebrew/proc/hebrew_year_months(year)
	if (hebrew_leap(year))
		return 13
	else
		return 12

// Delay based on starting day of the year
/datum/foreign_calendar/hebrew/proc/hebrew_delay_1(year)
	var/months = round(((235 * year) - 234) / 19)
	var/parts = 12084 + (13753 * months)
	var/day = (months * 29) + round(parts / 25920)
	if (3 * (day + 1) % 7 < 3)
		day++
	return day

// Delay based on length of adjacent years
/datum/foreign_calendar/hebrew/proc/hebrew_delay_2(year)
	var/last = hebrew_delay_1(year - 1)
	var/present = hebrew_delay_1(year)
	var/next = hebrew_delay_1(year + 1)
	if (next - present == 356)
		return 2
	else if (present - last == 382)
		return 1
	else
		return 0

/datum/foreign_calendar/hebrew/proc/hebrew_year_days(year)
	return hebrew_to_jd(year + 1, 7, 1) - hebrew_to_jd(year, 7, 1)

/datum/foreign_calendar/hebrew/proc/hebrew_month_days(year, month)
	switch (month)
		//  First of all, dispose of fixed-length 29 day months
		if (2, 4, 6, 10, 13)
			return 29
		//  If it's not a leap year, Adar has 29 days
		if (12)
			if (!hebrew_leap(year))
				return 29
		//  If it's Heshvan, days depend on length of year
		if (8)
			if (hebrew_year_days(year) % 10 != 5)
				return 29
		//  Similarly, Kislev varies with the length of year
		if (9)
			if (hebrew_year_days(year) % 10 == 3)
				return 29
	//  Nope, it's a 30 day month
	return 30

#undef ISLAMIC_EPOCH
#undef BYOND_EPOCH
#undef HEBREW_EPOCH
#undef GREGORIAN_EPOCH


code:
///////////////////////////////////////
///////////HALLOWEEN CONTENT///////////
///////////////////////////////////////

//spooky recipes

/datum/recipe/sugarcookie/spookyskull
	reagents_list = list(/datum/reagent/consumable/flour = 5, /datum/reagent/consumable/sugar = 5, /datum/reagent/consumable/milk = 5)
	items = list(
		/obj/item/food/egg,
	)
	result = /obj/item/food/cookie/sugar/spookyskull

/datum/recipe/sugarcookie/spookycoffin
	reagents_list = list(/datum/reagent/consumable/flour = 5, /datum/reagent/consumable/sugar = 5, /datum/reagent/consumable/coffee = 5)
	items = list(
		/obj/item/food/egg,
	)
	result = /obj/item/food/cookie/sugar/spookycoffin

//////////////////////////////
//Spookoween trapped closets//
//////////////////////////////

#define SPOOKY_SKELETON 1
#define ANGRY_FAITHLESS 2
#define SCARY_BATS 3
#define INSANE_CLOWN 4
#define HOWLING_GHOST 5

//Spookoween variables
/obj/structure/closet
	var/trapped = 0
	var/mob/trapped_mob

/obj/structure/closet/Initialize(mapload)
	. = ..()
	if(prob(30))
		set_spooky_trap()

/obj/structure/closet/dump_contents()
	..()
	trigger_spooky_trap()

/obj/structure/closet/proc/set_spooky_trap()
	if(prob(0.1))
		trapped = INSANE_CLOWN
		return
	if(prob(1))
		trapped = ANGRY_FAITHLESS
		return
	if(prob(15))
		trapped = SCARY_BATS
		return
	if(prob(20))
		trapped = HOWLING_GHOST
		return
	else
		var/mob/living/carbon/human/H = new(loc)
		H.makeSkeleton()
		H.health = 1e5
		insert(H)
		trapped_mob = H
		trapped = SPOOKY_SKELETON
		return

/obj/structure/closet/proc/trigger_spooky_trap()
	if(!trapped)
		return

	else if(trapped == SPOOKY_SKELETON)
		visible_message(span_userdanger("BOO!"))
		playsound(loc, 'sound/spookoween/girlscream.ogg', 300, TRUE)
		trapped = 0
		QDEL_IN(trapped_mob, 90)

	else if(trapped == HOWLING_GHOST)
		visible_message(span_userdanger("[pick("OooOOooooOOOoOoOOooooOOOOO", "BooOOooOooooOOOO", "BOO!", "WoOOoOoooOooo")]"))
		playsound(loc, 'sound/spookoween/ghosty_wind.ogg', 300, TRUE)
		new /mob/living/simple_animal/shade/howling_ghost(loc)
		trapped = 0

	else if(trapped == SCARY_BATS)
		visible_message(span_userdanger("Protect your hair!"))
		playsound(loc, 'sound/spookoween/bats.ogg', 300, TRUE)
		var/number = rand(1,3)
		for(var/i=0,i < number,i++)
			new /mob/living/simple_animal/hostile/retaliate/bat(loc)
		trapped = 0

	else if(trapped == ANGRY_FAITHLESS)
		visible_message(span_userdanger("The closet bursts open!"))
		visible_message(span_userdanger("THIS BEING RADIATES PURE EVIL! YOU BETTER RUN!!!"))
		playsound(loc, 'sound/hallucinations/wail.ogg', 300, TRUE)
		var/mob/living/simple_animal/hostile/faithless/F = new(loc)
		trapped = 0
		QDEL_IN(F, 120)

	else if(trapped == INSANE_CLOWN)
		visible_message(span_userdanger("..."))
		playsound(loc, 'sound/spookoween/scary_clown_appear.ogg', 300, TRUE)
		spawn_atom_to_turf(/mob/living/simple_animal/hostile/clown_insane, loc, 1, FALSE)
		trapped = 0

//don't spawn in crates
/obj/structure/closet/crate/trigger_spooky_trap()
	return

/obj/structure/closet/crate/set_spooky_trap()
	return

////////////////////
//Spookoween Ghost//
////////////////////

/mob/living/simple_animal/shade/howling_ghost
	name = "ghost"
	real_name = "ghost"
	icon = 'icons/mob/mob.dmi'
	maxHealth = 1e6
	health = 1e6
	speak_emote = list("howls")
	emote_hear = list("wails","screeches")
	density = FALSE
	anchored = TRUE
	incorporeal_move = 1
	layer = 4
	var/timer = 0

/mob/living/simple_animal/shade/howling_ghost/Initialize(mapload)
	. = ..()
	icon_state = pick("ghost","ghostian","ghostian2","ghostking","ghost1","ghost2")
	icon_living = icon_state
	status_flags |= GODMODE
	timer = rand(1,15)

/mob/living/simple_animal/shade/howling_ghost/Life()
	..()
	timer--
	if(prob(20))
		roam()
	if(timer == 0)
		spooky_ghosty()
		timer = rand(1,15)

/mob/living/simple_animal/shade/howling_ghost/proc/EtherealMove(direction)
	forceMove(get_step(src, direction))
	setDir(direction)

/mob/living/simple_animal/shade/howling_ghost/proc/roam()
	if(prob(80))
		var/direction = pick(NORTH,SOUTH,EAST,WEST,NORTHEAST,NORTHWEST,SOUTHEAST,SOUTHWEST)
		EtherealMove(direction)

/mob/living/simple_animal/shade/howling_ghost/proc/spooky_ghosty()
	if(prob(20)) //haunt
		playsound(loc, pick('sound/spookoween/ghosty_wind.ogg','sound/spookoween/ghost_whisper.ogg','sound/spookoween/chain_rattling.ogg'), 300, TRUE)
	if(prob(10)) //flickers
		var/obj/machinery/light/L = locate(/obj/machinery/light) in view(5, src)
		if(L)
			L.flicker()
	if(prob(5)) //poltergeist
		var/obj/item/I = locate(/obj/item) in view(3, src)
		if(I)
			var/direction = pick(NORTH,SOUTH,EAST,WEST,NORTHEAST,NORTHWEST,SOUTHEAST,SOUTHWEST)
			step(I,direction)
		return

/mob/living/simple_animal/shade/howling_ghost/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
	. = 0

///////////////////////////
//Spookoween Insane Clown//
///////////////////////////

///Insane clown mob. Basically a clown that haunts you.
/mob/living/simple_animal/hostile/clown_insane
	name = "insane clown"
	desc = "Some clowns do not manage to be accepted, and go insane. This is one of them."
	icon = 'icons/mob/clown_mobs.dmi'
	icon_state = "scary_clown"
	icon_living = "scary_clown"
	icon_dead = "scary_clown"
	icon_gib = "scary_clown"
	speak = list("...", ". . .")
	maxHealth = INFINITY
	health = INFINITY
	emote_see = list("silently stares")
	atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
	unsuitable_atmos_damage = 0
	minbodytemp = 0
	maxbodytemp = INFINITY
	var/timer

/mob/living/simple_animal/hostile/clown_insane/Initialize(mapload)
	. = ..()
	status_flags |= GODMODE //Slightly easier to maintain.

/mob/living/simple_animal/hostile/clown_insane/Destroy()
	timer = null
	return ..()

/mob/living/simple_animal/hostile/clown_insane/ex_act()
	return

///Adds a timer to call stalk() on Aggro
/mob/living/simple_animal/hostile/clown_insane/Aggro()
	. = ..()
	timer = addtimer(CALLBACK(src, .proc/stalk), 30, TIMER_STOPPABLE|TIMER_UNIQUE)

/mob/living/simple_animal/hostile/clown_insane/LoseAggro()
	. = ..()
	if(timer)
		deltimer(timer)
		timer = null

///Plays scary noises and adds some timers.
/mob/living/simple_animal/hostile/clown_insane/proc/stalk()
	var/mob/living/M = target
	if(!istype(M))
		LoseAggro()
		return
	if(M.stat == DEAD)
		playsound(M.loc, 'sound/spookoween/insane_low_laugh.ogg', 100, TRUE)
		qdel(src)
		return
	playsound(M, pick('sound/spookoween/scary_horn.ogg','sound/spookoween/scary_horn2.ogg', 'sound/spookoween/scary_horn3.ogg'), 100, TRUE)
	timer = addtimer(CALLBACK(src, .proc/stalk), 30, TIMER_STOPPABLE|TIMER_UNIQUE)
	addtimer(CALLBACK(src, .proc/teleport_to_target), 12, TIMER_STOPPABLE|TIMER_UNIQUE)

///Does what's in the name. Teleports to target.loc. Called from a timer.
/mob/living/simple_animal/hostile/clown_insane/proc/teleport_to_target()
	if(target && isturf(target.loc)) //Hiding in lockers works to get rid of this thing.
		forceMove(target.loc)

/mob/living/simple_animal/hostile/clown_insane/MoveToTarget()
	return

/mob/living/simple_animal/hostile/clown_insane/AttackingTarget()
	return

/mob/living/simple_animal/hostile/clown_insane/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
	. = 0
	if(prob(5))
		playsound(loc, 'sound/spookoween/insane_low_laugh.ogg', 300, TRUE)

/mob/living/simple_animal/hostile/clown_insane/attackby(obj/item/O, mob/user)
	if(istype(O, /obj/item/nullrod))
		if(prob(5))
			visible_message(span_notice("[src] finally found the peace it deserves. You hear honks echoing off into the distance."))
			playsound(loc, 'sound/spookoween/insane_low_laugh.ogg', 300, TRUE)
			qdel(src)
		else
			visible_message(span_danger("[src] seems to be resisting the effect!"))
		return
	return ..()

/////////////////////////
// Spooky Uplink Items //
/////////////////////////

/datum/uplink_item/dangerous/crossbow/candy
	name = "Candy Corn Crossbow"
	desc = "A standard miniature energy crossbow that uses a hard-light projector to transform bolts into candy corn. Happy Halloween!"
	category = "Holiday"
	item = /obj/item/gun/energy/kinetic_accelerator/crossbow/halloween
	surplus = 0

/datum/uplink_item/device_tools/emag/hack_o_lantern
	name = "Hack-o'-Lantern"
	desc = "An emag fitted to support the Halloween season. Candle not included."
	category = "Holiday"
	item = /obj/item/card/emag/halloween
	surplus = 0


code:
/datum/holiday
	///Name of the holiday itself. Visible to players.
	var/name = "If you see this the holiday calendar code is broken"

	///What day of begin_month does the holiday begin on?
	var/begin_day = 1
	///What month does the holiday begin on?
	var/begin_month = 0
	/// What day of end_month does the holiday end? Default of 0 means the holiday lasts a single.
	var/end_day = 0
	/// What month does the holiday end on?
	var/end_month = 0
	/// for christmas neverending, or testing. Forces a holiday to be celebrated.
	var/always_celebrate = FALSE
	/// Held variable to better calculate when certain holidays may fall on, like easter.
	var/current_year = 0
	/// How many years are you offsetting your calculations for begin_day and end_day on. Used for holidays like easter.
	var/year_offset = 0
	///Timezones this holiday is celebrated in (defaults to three timezones spanning a 50 hour window covering all timezones)
	var/list/timezones = list(TIMEZONE_LINT, TIMEZONE_UTC, TIMEZONE_ANYWHERE_ON_EARTH)
	///If this is defined, drones without a default hat will spawn with this one during the holiday; check drones_as_items.dm to see this used
	var/obj/item/drone_hat
	///When this holiday is active, does this prevent mail from arriving to cargo? Try not to use this for longer holidays.
	var/mail_holiday = FALSE

// This proc gets run before the game starts when the holiday is activated. Do festive shit here.
/datum/holiday/proc/celebrate()
	if(mail_holiday)
		SSeconomy.mail_blocked = TRUE
	return

// 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, " ")
	return copytext(name, 1, i)

// Return 1 if this holidy should be celebrated today
/datum/holiday/proc/shouldCelebrate(dd, mm, yyyy, ddd)
	if(always_celebrate)
		return TRUE

	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 TRUE

		else if(mm == begin_month)//in first month
			if(dd >= begin_day)
				return TRUE

		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 TRUE

	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 TRUE

	else // starts in one year, ends in the next
		if(mm >= begin_month && dd >= begin_day) // Holiday ends next year
			return TRUE
		if(mm <= end_month && dd <= end_day) // Holiday started last year
			return TRUE

	return FALSE

// The actual holidays

// JANUARY

//Fleet Day is celebrated on Jan 19th, the date on which moths were merged (#34498)
/datum/holiday/fleet_day
	name = "Fleet Day"
	begin_month = JANUARY
	begin_day = 19

/datum/holiday/fleet_day/greet()
	return "This day commemorates another year of successful survival aboard the Mothic Grand Nomad Fleet. Moths galaxywide are encouraged to eat, drink, and be merry."

/datum/holiday/fleet_day/getStationPrefix()
	return pick("Moth", "Fleet", "Nomadic")

// FEBRUARY

/datum/holiday/groundhog
	name = "Groundhog Day"
	begin_day = 2
	begin_month = FEBRUARY
	drone_hat = /obj/item/clothing/head/helmet/space/chronos

/datum/holiday/groundhog/getStationPrefix()
	return pick("Deja Vu") //I have been to this place before

/datum/holiday/nz
	name = "Waitangi Day"
	timezones = list(TIMEZONE_NZDT, TIMEZONE_CHADT)
	begin_day = 6
	begin_month = FEBRUARY

/datum/holiday/nz/getStationPrefix()
	return pick("Aotearoa","Kiwi","Fish 'n' Chips","Kākāpō","Southern Cross")

/datum/holiday/nz/greet()
	var/nz_age = text2num(time2text(world.timeofday, "YYYY")) - 1840
	return "On this day [nz_age] years ago, New Zealand's Treaty of Waitangi, the founding document of the nation, was signed!"

/datum/holiday/valentines
	name = VALENTINES
	begin_day = 14
	end_day = 15
	begin_month = FEBRUARY

/datum/holiday/valentines/getStationPrefix()
	return pick("Love","Amore","Single","Smootch","Hug")

/datum/holiday/birthday
	name = "Birthday of Space Station 13"
	begin_day = 16
	begin_month = FEBRUARY
	drone_hat = /obj/item/clothing/head/festive

/datum/holiday/birthday/greet()
	var/game_age = text2num(time2text(world.timeofday, "YYYY")) - 2003
	var/Fact
	switch(game_age)
		if(16)
			Fact = " SS13 is now old enough to drive!"
		if(18)
			Fact = " SS13 is now legal!"
		if(21)
			Fact = " SS13 can now drink!"
		if(26)
			Fact = " SS13 can now rent a car!"
		if(30)
			Fact = " SS13 can now go home and be a family man!"
		if(35)
			Fact = " SS13 can now run for President of the United States!"
		if(40)
			Fact = " SS13 can now suffer a midlife crisis!"
		if(50)
			Fact = " Happy golden anniversary!"
		if(65)
			Fact = " SS13 can now start thinking about retirement!"
	if(!Fact)
		Fact = " SS13 is now [game_age] years old!"

	return "Say 'Happy Birthday' to Space Station 13, first publicly playable on February 16th, 2003![Fact]"

/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/leap
	name = "Leap Day"
	begin_day = 29
	begin_month = FEBRUARY

// MARCH

/datum/holiday/pi
	name = "Pi Day"
	begin_day = 14
	begin_month = MARCH

/datum/holiday/pi/getStationPrefix()
	return pick("Sine","Cosine","Tangent","Secant", "Cosecant", "Cotangent")

/datum/holiday/no_this_is_patrick
	name = "St. Patrick's Day"
	begin_day = 17
	begin_month = MARCH
	drone_hat = /obj/item/clothing/head/soft/green

/datum/holiday/no_this_is_patrick/getStationPrefix()
	return pick("Blarney","Green","Leprechaun","Booze")

/datum/holiday/no_this_is_patrick/greet()
	return "Happy National Inebriation Day!"

// APRIL

/datum/holiday/april_fools
	name = APRIL_FOOLS
	begin_month = APRIL
	begin_day = 1
	end_day = 2

/datum/holiday/april_fools/celebrate()
	. = ..()
	SSjob.set_overflow_role(/datum/job/clown)
	SSticker.login_music = 'sound/ambience/clown.ogg'
	for(var/i in GLOB.new_player_list)
		var/mob/dead/new_player/P = i
		if(P.client)
			P.client.playtitlemusic()

/datum/holiday/spess
	name = "Cosmonautics Day"
	begin_day = 12
	begin_month = APRIL
	drone_hat = /obj/item/clothing/head/syndicatefake

/datum/holiday/spess/greet()
	return "On this day over 600 years ago, Comrade Yuri Gagarin first ventured into space!"

/datum/holiday/fourtwenty
	name = "Four-Twenty"
	begin_day = 20
	begin_month = APRIL

/datum/holiday/fourtwenty/getStationPrefix()
	return pick("Snoop","Blunt","Toke","Dank","Cheech","Chong")

/datum/holiday/tea
	name = "National Tea Day"
	begin_day = 21
	begin_month = APRIL

/datum/holiday/tea/getStationPrefix()
	return pick("Crumpet","Assam","Oolong","Pu-erh","Sweet Tea","Green","Black")

/datum/holiday/earth
	name = "Earth Day"
	begin_day = 22
	begin_month = APRIL

/datum/holiday/anz
	name = "ANZAC Day"
	timezones = list(TIMEZONE_TKT, TIMEZONE_TOT, TIMEZONE_NZST, TIMEZONE_NFT, TIMEZONE_LHST, TIMEZONE_AEST, TIMEZONE_ACST, TIMEZONE_ACWST, TIMEZONE_AWST, TIMEZONE_CXT, TIMEZONE_CCT, TIMEZONE_CKT, TIMEZONE_NUT)
	begin_day = 25
	begin_month = APRIL
	drone_hat = /obj/item/food/grown/poppy

/datum/holiday/anz/getStationPrefix()
	return pick("Australian","New Zealand","Poppy", "Southern Cross")

// MAY

/datum/holiday/labor
	name = "Labor Day"
	begin_day = 1
	begin_month = MAY
	drone_hat = /obj/item/clothing/head/hardhat
	mail_holiday = TRUE

//Draconic Day is celebrated on May 3rd, the date on which the Draconic language was merged (#26780)
/datum/holiday/draconic_day
	name = "Draconic Language Day"
	begin_month = MAY
	begin_day = 3

/datum/holiday/draconic_day/greet()
	return "On this day, Lizardkind celebrates their language with literature and other cultural works."

/datum/holiday/draconic_day/getStationPrefix()
	return pick("Draconic", "Literature", "Reading")

/datum/holiday/firefighter
	name = "Firefighter's Day"
	begin_day = 4
	begin_month = MAY
	drone_hat = /obj/item/clothing/head/hardhat/red

/datum/holiday/firefighter/getStationPrefix()
	return pick("Burning","Blazing","Plasma","Fire")

/datum/holiday/bee
	name = "Bee Day"
	begin_day = 20
	begin_month = MAY
	drone_hat = /obj/item/clothing/mask/animal/rat/bee

/datum/holiday/bee/getStationPrefix()
	return pick("Bee","Honey","Hive","Africanized","Mead","Buzz")

// JUNE

//The Festival of Atrakor's Might (Tizira's Moon) is celebrated on June 15th, the date on which the lizard visual revamp was merged (#9808)
/datum/holiday/atrakor_festival
	name = "Festival of Atrakor's Might"
	begin_month = JUNE
	begin_day = 15

/datum/holiday/atrakor_festival/greet()
	return "On this day, the Lizards traditionally celebrate the Festival of Atrakor's Might, where they honour the moon god with lavishly adorned clothing, large portions of food, and a massive celebration into the night."

/datum/holiday/atrakor_festival/getStationPrefix()
	return pick("Moon", "Night Sky", "Celebration")

/// Garbage DAYYYYY
/// Huh?.... NOOOO
/// *GUNSHOT*
/// AHHHGHHHHHHH
/datum/holiday/garbageday
	name = GARBAGEDAY
	begin_day = 17
	end_day = 17
	begin_month = JUNE

/datum/holiday/summersolstice
	name = "Summer Solstice"
	begin_day = 21
	begin_month = JUNE
/datum/holiday/pride_week
	name = PRIDE_WEEK
	begin_month = JUNE
	// Stonewall was June 28th, this captures its week.
	begin_day = 23
	end_day = 29

	var/static/list/rainbow_colors = list(
		COLOR_PRIDE_PURPLE,
		COLOR_PRIDE_BLUE,
		COLOR_PRIDE_GREEN,
		COLOR_PRIDE_YELLOW,
		COLOR_PRIDE_ORANGE,
		COLOR_PRIDE_RED,
	)

/// Given an atom, will return what color it should be to match the pride flag.
/datum/holiday/pride_week/proc/get_floor_tile_color(atom/atom)
	var/turf/turf = get_turf(atom)
	return rainbow_colors[(turf.y % rainbow_colors.len) + 1]

// JULY

/datum/holiday/doctor
	name = "Doctor's Day"
	begin_day = 1
	begin_month = JULY
	drone_hat = /obj/item/clothing/head/nursehat

/datum/holiday/ufo
	name = "UFO Day"
	begin_day = 2
	begin_month = JULY
	drone_hat = /obj/item/clothing/mask/facehugger/dead

/datum/holiday/ufo/getStationPrefix() //Is such a thing even possible?
	return pick("Ayy","Truth","Tsoukalos","Mulder","Scully") //Yes it is!

/datum/holiday/usa
	name = "US Independence Day"
	timezones = list(TIMEZONE_EDT, TIMEZONE_CDT, TIMEZONE_MDT, TIMEZONE_MST, TIMEZONE_PDT, TIMEZONE_AKDT, TIMEZONE_HDT, TIMEZONE_HST)
	begin_day = 4
	begin_month = JULY
	mail_holiday = TRUE

/datum/holiday/usa/getStationPrefix()
	return pick("Independent","American","Burger","Bald Eagle","Star-Spangled", "Fireworks")

/datum/holiday/writer
	name = "Writer's Day"
	begin_day = 8
	begin_month = JULY

/datum/holiday/france
	name = "Bastille Day"
	timezones = list(TIMEZONE_CEST)
	begin_day = 14
	begin_month = JULY
	drone_hat = /obj/item/clothing/head/beret
	mail_holiday = TRUE

/datum/holiday/france/getStationPrefix()
	return pick("Francais","Fromage", "Zut", "Merde")

/datum/holiday/france/greet()
	return "Do you hear the people sing?"

/datum/holiday/hotdogday //I have plans for this.
	name = "National Hot Dog Day"
	begin_day = 17
	begin_month = JULY

/datum/holiday/hotdogday/greet()
	return "Happy National Hot Dog Day!"

//Gary Gygax's birthday, a fitting day for Wizard's Day
/datum/holiday/wizards_day
	name = "Wizard's Day"
	begin_month = JULY
	begin_day = 27
	drone_hat = /obj/item/clothing/head/wizard

/datum/holiday/wizards_day/getStationPrefix()
	return pick("Dungeon", "Elf", "Magic", "D20", "Edition")

/datum/holiday/friendship
	name = "Friendship Day"
	begin_day = 30
	begin_month = JULY

/datum/holiday/friendship/greet()
	return "Have a magical [name]!"

// AUGUST

/datum/holiday/indigenous //Indigenous Peoples' Day from Earth!
	name = "International Day of the World's Indigenous Peoples"
	begin_month = AUGUST
	begin_day = 9

/datum/holiday/indigenous/getStationPrefix()
	return pick("Endangered language", "Word", "Language", "Language revitalization", "Potato", "Corn")

// SEPTEMBER

//Tiziran Unification Day is celebrated on Sept 1st, the day on which lizards were made a roundstart race
/datum/holiday/tiziran_unification
	name = "Tiziran Unification Day"
	begin_month = SEPTEMBER
	begin_day = 1

/datum/holiday/tiziran_unification/greet()
	return "On this day over 400 years ago, Lizardkind first united under a single banner, ready to face the stars as one unified people."

/datum/holiday/tiziran_unification/getStationPrefix()
	return pick("Tizira", "Lizard", "Imperial")

/datum/holiday/ianbirthday
	name = "Ian's Birthday" //github.com/tgstation/tgstation/commit/de7e4f0de0d568cd6e1f0d7bcc3fd34700598acb
	begin_month = SEPTEMBER
	begin_day = 9
	end_day = 10

/datum/holiday/ianbirthday/greet()
	return "Happy birthday, Ian!"

/datum/holiday/ianbirthday/getStationPrefix()
	return pick("Ian", "Corgi", "Erro")

/datum/holiday/pirate
	name = "Talk-Like-a-Pirate Day"
	begin_day = 19
	begin_month = SEPTEMBER
	drone_hat = /obj/item/clothing/head/pirate

/datum/holiday/pirate/greet()
	return "Ye be talkin' like a pirate today or else ye'r walkin' tha plank, matey!"

/datum/holiday/pirate/getStationPrefix()
	return pick("Yarr","Scurvy","Yo-ho-ho")

/datum/holiday/questions
	name = "Stupid-Questions Day"
	begin_day = 28
	begin_month = SEPTEMBER

/datum/holiday/questions/greet()
	return "Are you having a happy [name]?"

// OCTOBER

/datum/holiday/animal
	name = "Animal's Day"
	begin_day = 4
	begin_month = OCTOBER

/datum/holiday/animal/getStationPrefix()
	return pick("Parrot","Corgi","Cat","Pug","Goat","Fox")

/datum/holiday/smile
	name = "Smiling Day"
	begin_day = 7
	begin_month = OCTOBER
	drone_hat = /obj/item/clothing/head/papersack/smiley

/datum/holiday/boss
	name = "Boss' Day"
	begin_day = 16
	begin_month = OCTOBER
	drone_hat = /obj/item/clothing/head/that

/datum/holiday/un_day
	name = "Anniversary of the Foundation of the United Nations"
	begin_month = OCTOBER
	begin_day = 24

/datum/holiday/un_day/greet()
	return "On this day in 1945, the United Nations was founded, laying the foundation for humanity's united government!"

/datum/holiday/un_day/getStationPrefix()
	return pick("United", "Cooperation", "Humanitarian")

/datum/holiday/halloween
	name = HALLOWEEN
	begin_day = 29
	begin_month = OCTOBER
	end_day = 2
	end_month = NOVEMBER

/datum/holiday/halloween/greet()
	return "Have a spooky Halloween!"

/datum/holiday/halloween/getStationPrefix()
	return pick("Bone-Rattling","Mr. Bones' Own","2SPOOKY","Spooky","Scary","Skeletons")

// NOVEMBER

/datum/holiday/vegan
	name = "Vegan Day"
	begin_day = 1
	begin_month = NOVEMBER

/datum/holiday/vegan/getStationPrefix()
	return pick("Tofu", "Tempeh", "Seitan", "Tofurkey")

/datum/holiday/october_revolution
	name = "October Revolution"
	begin_day = 6
	begin_month = NOVEMBER
	end_day = 7

/datum/holiday/october_revolution/getStationPrefix()
	return pick("Communist", "Soviet", "Bolshevik", "Socialist", "Red", "Workers'")

/datum/holiday/remembrance_day
	name = "Remembrance Day"
	begin_month = NOVEMBER
	begin_day = 11
	drone_hat = /obj/item/food/grown/poppy

/datum/holiday/remembrance_day/getStationPrefix()
	return pick("Peace", "Armistice", "Poppy")

/datum/holiday/lifeday
	name = "Life Day"
	begin_day = 17
	begin_month = NOVEMBER

/datum/holiday/lifeday/getStationPrefix()
	return pick("Itchy", "Lumpy", "Malla", "Kazook") //he really pronounced it "Kazook", I wish I was making shit up

/datum/holiday/kindness
	name = "Kindness Day"
	begin_day = 13
	begin_month = NOVEMBER

/datum/holiday/flowers
	name = "Flowers Day"
	begin_day = 19
	begin_month = NOVEMBER
	drone_hat = /obj/item/food/grown/moonflower

/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", "Ni hao", "Guten Tag", "Konnichiwa", "G'day cunt"))]! " + ..()

//The Festival of Holy Lights is celebrated on Nov 28th, the date on which ethereals were merged (#40995)
/datum/holiday/holy_lights
	name = "Festival of Holy Lights"
	begin_month = NOVEMBER
	begin_day = 28

/datum/holiday/holy_lights/greet()
	return "The Festival of Holy Lights is the final day of the Ethereal calendar. It is typically a day of prayer followed by celebration to close out the year in style."

/datum/holiday/holy_lights/getStationPrefix()
	return pick("Ethereal", "Lantern", "Holy")

// DECEMBER

/datum/holiday/festive_season
	name = FESTIVE_SEASON
	begin_day = 1
	begin_month = DECEMBER
	end_day = 31
	drone_hat = /obj/item/clothing/head/santa

/datum/holiday/festive_season/greet()
	return "Have a nice festive season!"

/datum/holiday/human_rights
	name = "Human-Rights Day"
	begin_day = 10
	begin_month = DECEMBER

/datum/holiday/monkey
	name = MONKEYDAY
	begin_day = 14
	begin_month = DECEMBER
	drone_hat = /obj/item/clothing/mask/gas/monkeymask

/datum/holiday/doomsday
	name = "Mayan Doomsday Anniversary"
	begin_day = 21
	begin_month = DECEMBER
	drone_hat = /obj/item/clothing/mask/animal/rat/tribal

/datum/holiday/xmas
	name = CHRISTMAS
	begin_day = 23
	begin_month = DECEMBER
	end_day = 27
	drone_hat = /obj/item/clothing/head/santa
	mail_holiday = TRUE

/datum/holiday/xmas/greet()
	return "Have a merry Christmas!"

/datum/holiday/boxing
	name = "Boxing Day"
	begin_day = 26
	begin_month = DECEMBER

/datum/holiday/new_year
	name = NEW_YEAR
	begin_day = 31
	begin_month = DECEMBER
	end_day = 2
	end_month = JANUARY
	drone_hat = /obj/item/clothing/head/festive
	mail_holiday = TRUE

/datum/holiday/new_year/getStationPrefix()
	return pick("Party","New","Hangover","Resolution", "Auld")

// MOVING DATES

/datum/holiday/friday_thirteenth
	name = "Friday the 13th"

/datum/holiday/friday_thirteenth/shouldCelebrate(dd, mm, yyyy, ddd)
	if(dd == 13 && ddd == FRIDAY)
		return TRUE
	return FALSE

/datum/holiday/friday_thirteenth/getStationPrefix()
	return pick("Mike","Friday","Evil","Myers","Murder","Deathly","Stabby")

/datum/holiday/programmers
	name = "Programmers' Day"

/datum/holiday/programmers/shouldCelebrate(dd, mm, yyyy, ddd) //Programmer's day falls on the 2^8th day of the year
	if(mm == 9)
		if(yyyy/4 == round(yyyy/4)) //Note: Won't work right on September 12th, 2200 (at least it's a Friday!)
			if(dd == 12)
				return TRUE
		else
			if(dd == 13)
				return TRUE
	return FALSE

/datum/holiday/programmers/getStationPrefix()
	return pick("span>","DEBUG: ","null","/list","EVENT PREFIX NOT FOUND") //Portability

// ISLAMIC

/datum/holiday/islamic
	name = "Islamic calendar code broken"

/datum/holiday/islamic/shouldCelebrate(dd, mm, yyyy, ddd)
	var/datum/foreign_calendar/islamic/cal = new(yyyy, mm, dd)
	return ..(cal.dd, cal.mm, cal.yyyy, ddd)

/datum/holiday/islamic/ramadan
	name = "Start of Ramadan"
	begin_month = 9
	begin_day = 1
	end_day = 3

/datum/holiday/islamic/ramadan/getStationPrefix()
	return pick("Haram","Halaal","Jihad","Muslim", "Al", "Mohammad", "Rashidun", "Umayyad", "Abbasid", "Abdul", "Fatimid", "Ayyubid", "Almohad", "Abu")

/datum/holiday/islamic/ramadan/end
	name = "End of Ramadan"
	end_month = 10
	begin_day = 28
	end_day = 1

// HEBREW

/datum/holiday/hebrew
	name = "If you see this the Hebrew holiday calendar code is broken"

/datum/holiday/hebrew/shouldCelebrate(dd, mm, yyyy, ddd)
	var/datum/foreign_calendar/hebrew/cal = new(yyyy, mm, dd)
	return ..(cal.dd, cal.mm, cal.yyyy, ddd)

/datum/holiday/hebrew/hanukkah
	name = "Hanukkah"
	begin_day = 25
	begin_month = 9
	end_day = 2
	end_month = 10

/datum/holiday/hebrew/hanukkah/greet()
	return "Happy [pick("Hanukkah", "Chanukah")]!"

/datum/holiday/hebrew/hanukkah/getStationPrefix()
	return pick("Dreidel", "Menorah", "Latkes", "Gelt")

/datum/holiday/hebrew/passover
	name = "Passover"
	begin_day = 15
	begin_month = 1
	end_day = 22

/datum/holiday/hebrew/passover/getStationPrefix()
	return pick("Matzah", "Moses", "Red Sea")

// HOLIDAY ADDONS

/datum/holiday/xmas/celebrate()
	. = ..()
	SSticker.OnRoundstart(CALLBACK(src, .proc/roundstart_celebrate))
	GLOB.maintenance_loot += list(
		list(
			/obj/item/toy/xmas_cracker = 3,
			/obj/item/clothing/head/santa = 1,
			/obj/item/a_gift/anything = 1
		) = maint_holiday_weight,
	)

/datum/holiday/xmas/proc/roundstart_celebrate()
	for(var/obj/machinery/computer/security/telescreen/entertainment/Monitor in GLOB.machines)
		Monitor.icon_state_on = "entertainment_xmas"

	for(var/mob/living/simple_animal/pet/dog/corgi/ian/Ian in GLOB.mob_living_list)
		Ian.place_on_head(new /obj/item/clothing/head/helmet/space/santahat(Ian))

// EASTER (this having it's own spot should be understandable)

/datum/holiday/easter
	name = EASTER
	drone_hat = /obj/item/clothing/head/rabbitears
	var/const/days_early = 1 //to make editing the holiday easier
	var/const/days_extra = 1

/datum/holiday/easter/shouldCelebrate(dd, mm, yyyy, ddd)
	if(!begin_month)
		current_year = text2num(time2text(world.timeofday, "YYYY"))
		var/list/easterResults = EasterDate(current_year+year_offset)

		begin_day = easterResults["day"]
		begin_month = easterResults["month"]

		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

	return ..()

/datum/holiday/easter/celebrate()
	. = ..()
	GLOB.maintenance_loot += list(
		list(
			/obj/item/surprise_egg = 15,
			/obj/item/storage/basket/easter = 15
		) = maint_holiday_weight,
	)

/datum/holiday/easter/greet()
	return "Greetings! Have a Happy Easter and keep an eye out for Easter Bunnies!"

/datum/holiday/easter/getStationPrefix()
	return pick("Fluffy","Bunny","Easter","Egg")


code:
///A holiday lasting one day only that falls on the nth weekday in a month i.e. 3rd Wednesday of February.
/datum/holiday/nth_week
	///Nth weekday of type begin_weekday in begin_month to start on (1 to 5).
	var/begin_week = 1
	///Weekday of begin_week to start on.
	var/begin_weekday = MONDAY
	///Nth weekday of type end_weekday in end_month to end on (1 to 5, defaults to begin_week).
	var/end_week
	///Weekday of end_week to end on (defaults to begin_weekday).
	var/end_weekday

/datum/holiday/nth_week/shouldCelebrate(dd, mm, yy, ddd)
	// Does not support holidays across multiple years..
	if (!end_month)
		end_month = begin_month
	if (!end_week)
		end_week = begin_week
	if (!end_weekday)
		end_weekday = begin_weekday
	// check that it's not past the last day
	end_day = weekday_to_iso(end_weekday) - first_day_of_month(yy, end_month)
	if (end_day < 0)
		end_day += 7
	end_day += (end_week - 1) * 7 + 1
	// check that it's not past the first day
	begin_day = weekday_to_iso(begin_weekday) - first_day_of_month(yy, begin_month)
	if (begin_day < 0)
		begin_day += 7
	begin_day += (begin_week - 1) * 7 + 1
	return ..(dd, mm, yy, ddd)

/datum/holiday/nth_week/thanksgiving
	name = "Thanksgiving in the United States"
	begin_week = 4
	begin_month = NOVEMBER
	begin_weekday = THURSDAY
	drone_hat = /obj/item/clothing/head/that //This is the closest we can get to a pilgrim's hat

/datum/holiday/nth_week/thanksgiving/canada
	name = "Thanksgiving in Canada"
	begin_week = 2
	begin_month = OCTOBER
	begin_weekday = MONDAY

/datum/holiday/nth_week/mother
	name = "Mother's Day"
	begin_week = 2
	begin_month = MAY
	begin_weekday = SUNDAY

/datum/holiday/nth_week/mother/greet()
	return "Happy Mother's Day in most of the Americas, Asia, and Oceania!"

/datum/holiday/nth_week/father
	name = "Father's Day"
	begin_week = 3
	begin_month = JUNE
	begin_weekday = SUNDAY

/datum/holiday/nth_week/beer
	name = "Beer Day"
	begin_week = 1
	begin_month = AUGUST
	begin_weekday = FRIDAY

/datum/holiday/beer/getStationPrefix()
	return pick("Stout","Porter","Lager","Ale","Malt","Bock","Doppelbock","Hefeweizen","Pilsner","IPA","Lite") //I'm sorry for the last one

/datum/holiday/nth_week/moth
	name = MOTH_WEEK
	begin_week = 3
	end_week = 4
	begin_month = JULY
	begin_weekday = SATURDAY
	end_weekday = SUNDAY

//National Moth Week falls on the last full week of July, including the saturday and sunday before. See http://nationalmothweek.org/ for precise tracking.
/datum/holiday/nth_week/moth/shouldCelebrate(dd, mm, yyyy, ddd)
	if(first_day_of_month(yyyy, mm) >= 5) //Friday or later start of the month means week 5 is a full week.
		begin_week += 1
		end_week += 1
	return ..(dd, mm, yyyy, ddd)

/datum/holiday/nth_week/moth/getStationPrefix()
	return pick("Mothball","Lepidopteran","Lightbulb","Moth","Giant Atlas","Twin-spotted Sphynx","Madagascan Sunset","Luna","Death's Head","Emperor Gum","Polyphenus","Oleander Hawk","Io","Rosy Maple","Cecropia","Noctuidae","Giant Leopard","Dysphania Militaris","Garden Tiger")


code:
//Vars that will not be copied when using /DuplicateObject
GLOBAL_LIST_INIT(duplicate_forbidden_vars,list(
	"tag", "datum_components", "area", "type", "loc", "locs", "vars", "parent", "parent_type", "verbs", "ckey", "key",
	"power_supply", "contents", "reagents", "stat", "x", "y", "z", "group", "atmos_adjacent_turfs", "comp_lookup",
	"client_mobs_in_contents", "bodyparts", "internal_organs", "hand_bodyparts", "overlays_standing", "hud_list",
	"actions", "AIStatus", "appearance", "managed_overlays", "managed_vis_overlays", "computer_id", "lastKnownIP", "implants",
	"tgui_shared_states"
	))

/proc/DuplicateObject(atom/original, perfectcopy = TRUE, sameloc, atom/newloc = null, nerf, holoitem)
	RETURN_TYPE(original.type)
	if(!original)
		return
	var/atom/O

	if(sameloc)
		O = new original.type(original.loc)
	else
		O = new original.type(newloc)

	if(perfectcopy && O && original)
		for(var/V in original.vars - GLOB.duplicate_forbidden_vars)
			if(islist(original.vars[V]))
				var/list/L = original.vars[V]
				O.vars[V] = L.Copy()
			else if(istype(original.vars[V], /datum) || ismob(original.vars[V]))
				continue // this would reference the original's object, that will break when it is used or deleted.
			else
				O.vars[V] = original.vars[V]

	if(isobj(O))
		var/obj/N = O
		if(holoitem)
			N.resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF // holoitems do not burn

		if(nerf && isitem(O))
			var/obj/item/I = O
			I.damtype = STAMINA // thou shalt not

		N.update_appearance()
		if(ismachinery(O))
			var/obj/machinery/M = O
			M.power_change()
			if(istype(O, /obj/machinery/button))
				var/obj/machinery/button/B = O
				B.setup_device()

	if(holoitem)
		O.flags_1 |= HOLOGRAM_1
		for(var/atom/thing in O)
			thing.flags_1 |= HOLOGRAM_1
		if(ismachinery(O))
			var/obj/machinery/M = O
			for(var/atom/contained_atom in M.component_parts)
				contained_atom.flags_1 |= HOLOGRAM_1
			if(M.circuit)
				M.circuit.flags_1 |= HOLOGRAM_1

	if(ismob(O)) //Overlays are carried over despite disallowing them, if a fix is found remove this.
		var/mob/M = O
		M.cut_overlays()
		M.regenerate_icons()
	return O

/area/proc/copy_contents_to(area/A , platingRequired = 0, nerf_weapons = 0 )
	//Takes: Area. Optional: If it should copy to areas that don't have plating
	//Returns: Nothing.
	//Notes: Attempts to move the contents of one area to another area.
	//       Movement based on lower left corner. Tiles that do not fit
	//  into the new area will not be moved.

	if(!A || !src)
		return 0

	var/list/turfs_src = get_area_turfs(src.type)
	var/list/turfs_trg = get_area_turfs(A.type)

	var/src_min_x = 99999
	var/src_min_y = 99999
	var/list/refined_src = new/list()

	for (var/turf/T in turfs_src)
		src_min_x = min(src_min_x,T.x)
		src_min_y = min(src_min_y,T.y)
	for (var/turf/T in turfs_src)
		refined_src[T] = "[T.x - src_min_x].[T.y - src_min_y]"

	var/trg_min_x = 99999
	var/trg_min_y = 99999
	var/list/refined_trg = new/list()

	for (var/turf/T in turfs_trg)
		trg_min_x = min(trg_min_x,T.x)
		trg_min_y = min(trg_min_y,T.y)
	for (var/turf/T in turfs_trg)
		refined_trg["[T.x - trg_min_x].[T.y - trg_min_y]"] = T

	var/list/toupdate = new/list()

	var/copiedobjs = list()

	for (var/turf/T in refined_src)
		var/coordstring = refined_src[T]
		var/turf/B = refined_trg[coordstring]
		if(!istype(B))
			continue

		if(platingRequired)
			if(isspaceturf(B))
				continue

		var/old_dir1 = T.dir
		var/old_icon_state1 = T.icon_state
		var/old_icon1 = T.icon

		B = B.ChangeTurf(T.type)
		B.setDir(old_dir1)
		B.icon = old_icon1
		B.icon_state = old_icon_state1

		for(var/obj/O in T)
			var/obj/O2 = DuplicateObject(O , perfectcopy=TRUE, newloc = B, nerf=nerf_weapons, holoitem=TRUE)
			if(!O2)
				continue
			copiedobjs += O2.get_all_contents()

		for(var/mob/M in T)
			if(iscameramob(M))
				continue // If we need to check for more mobs, I'll add a variable
			var/mob/SM = DuplicateObject(M , perfectcopy=TRUE, newloc = B, holoitem=TRUE)
			copiedobjs += SM.get_all_contents()

		for(var/V in T.vars - GLOB.duplicate_forbidden_vars)
			if(V == "air")
				var/turf/open/O1 = B
				var/turf/open/O2 = T
				O1.air.copy_from(O2.return_air())
				continue
			B.vars[V] = T.vars[V]
		toupdate += B

	if(toupdate.len)
		for(var/turf/T1 in toupdate)
			CALCULATE_ADJACENT_TURFS(T1, KILL_EXCITED)

	return copiedobjs


code:
/*
Map Template Holodeck

Holodeck finds the location of mapped_start_area and loads offline_program in it on LateInitialize. It then passes its program templates to Holodeck.js in the form of program_cache and emag_programs. when a user selects a program the
ui calls load_program() with the id of the selected program.
load_program() -> map_template/load() on map_template/holodeck.

holodeck map templates:
1. have an update_blacklist that doesnt allow placing on non holofloors (except for engine floors so you can repair it)
2. has should_place_on_top = FALSE, so that the baseturfs list doesnt pull a kilostation oom crash
3. has returns_created = TRUE, so that SSatoms gives the map template a list of spawned atoms
all the fancy flags and shit are added to holodeck objects in finish_spawn()

Easiest way to add new holodeck programs:
1. Define new map template datums in code/modules/holodeck/holodeck_map_templates, make sure they have the access flags
of the holodeck you want them to be able to load, for the onstation holodeck the flag is STATION_HOLODECK.
2. Create the new map templates in _maps/templates (remember theyre 9x10, and make sure they have area/noop or else it will fuck with linked)
all turfs in holodeck programs MUST be of type /turf/open/floor/holofloor, OR /turf/open/floor/engine, or they will block future programs!

Note: if youre looking at holodeck code because you want to see how returns_created is handled so that templates return a list of atoms
created from them: make sure you handle that list correctly! Either copy them by value and delete them or reference it and handle qdel'ing
and clear when youre done! if you dont i will use :newspaper2: on you
*/

#define HOLODECK_CD 2 SECONDS
#define HOLODECK_DMG_CD 5 SECONDS

/obj/machinery/computer/holodeck
	name = "holodeck control console"
	desc = "A computer used to control a nearby holodeck."
	icon_screen = "holocontrol"
	idle_power_usage = 10
	active_power_usage = 50

	//new vars
	///what area type this holodeck loads into. linked turns into the nearest instance of this area
	var/area/mapped_start_area = /area/holodeck/rec_center

	///the currently used map template
	var/datum/map_template/holodeck/template

	///bottom left corner of the loading room, used for placing
	var/turf/bottom_left

	///if TRUE the holodeck is busy spawning another simulation and should immediately stop loading the newest one
	var/spawning_simulation = FALSE

	//old vars

	///the area that this holodeck loads templates into, used for power and deleting holo objects that leave it
	var/area/holodeck/linked

	///what program is loaded right now or is about to be loaded
	var/program = "holodeck_offline"
	var/last_program

	///the default program loaded by this holodeck when spawned and when deactivated
	var/offline_program = "holodeck_offline"

	///stores all of the unrestricted holodeck map templates that this computer has access to
	var/list/program_cache
	///stores all of the restricted holodeck map templates that this computer has access to
	var/list/emag_programs

	///subtypes of this (but not this itself) are loadable programs
	var/program_type = /datum/map_template/holodeck

	///every holo object created by the holodeck goes in here to track it
	var/list/spawned = list()
	var/list/effects = list() //like above, but for holo effects

	///TRUE if the holodeck is using extra power because of a program, FALSE otherwise
	var/active = FALSE
	///increases the holodeck cooldown if TRUE, causing the holodeck to take longer to allow loading new programs
	var/damaged = FALSE

	//creates the timer that determines if another program can be manually loaded
	COOLDOWN_DECLARE(holodeck_cooldown)

/obj/machinery/computer/holodeck/Initialize(mapload)
	..()
	return INITIALIZE_HINT_LATELOAD

/obj/machinery/computer/holodeck/LateInitialize()//from here linked is populated and the program list is generated. its also set to load the offline program
	linked = GLOB.areas_by_type[mapped_start_area]
	bottom_left = locate(linked.x, linked.y, src.z)

	var/area/computer_area = get_area(src)
	if(istype(computer_area, /area/holodeck))
		log_mapping("Holodeck computer cannot be in a holodeck, This would cause circular power dependency.")
		qdel(src)
		return

	// the following is necessary for power reasons
	if(!linked)
		log_world("No matching holodeck area found")
		qdel(src)
		return
	else if (!offline_program)
		stack_trace("Holodeck console created without an offline program")
		qdel(src)
		return

	else
		linked.linked = src
		var/area/my_area = get_area(src)
		if(my_area)
			linked.power_usage = my_area.power_usage
		else
			linked.power_usage = list(AREA_USAGE_LEN)

	COOLDOWN_START(src, holodeck_cooldown, HOLODECK_CD)
	generate_program_list()
	load_program(offline_program,TRUE)

///adds all programs that this holodeck has access to, and separates the restricted and unrestricted ones
/obj/machinery/computer/holodeck/proc/generate_program_list()
	for(var/typekey in subtypesof(program_type))
		var/datum/map_template/holodeck/program = typekey
		var/list/info_this = list("id" = initial(program.template_id), "name" = initial(program.name))
		if(initial(program.restricted))
			LAZYADD(emag_programs, list(info_this))
		else
			LAZYADD(program_cache, list(info_this))

/obj/machinery/computer/holodeck/ui_interact(mob/user, datum/tgui/ui)
	ui = SStgui.try_update_ui(user, src, ui)
	if(!ui)
		ui = new(user, src, "Holodeck", name)
		ui.open()

/obj/machinery/computer/holodeck/ui_data(mob/user)
	var/list/data = list()

	data["default_programs"] = program_cache
	if(obj_flags & EMAGGED)
		data["emagged"] = TRUE
		data["emag_programs"] = emag_programs
	data["program"] = program
	data["can_toggle_safety"] = issilicon(user) || isAdminGhostAI(user)
	return data

/obj/machinery/computer/holodeck/ui_act(action, params)
	. = ..()
	if(.)
		return
	. = TRUE
	switch(action)
		if("load_program")
			var/program_to_load = params["id"]

			var/list/checked = program_cache.Copy()
			if (obj_flags & EMAGGED)
				checked |= emag_programs
			var/valid = FALSE //dont tell security about this

			//checks if program_to_load is any one of the loadable programs, if it isnt then it rejects it
			for(var/list/check_list as anything in checked)
				if(check_list["id"] == program_to_load)
					valid = TRUE
					break
			if(!valid)
				return FALSE
			//load the map_template that program_to_load represents
			if(program_to_load)
				load_program(program_to_load)
		if("safety")
			if((obj_flags & EMAGGED) && program)
				emergency_shutdown()
			nerf(obj_flags & EMAGGED,FALSE)
			obj_flags ^= EMAGGED
			say("Safeties reset. Restarting...")
			log_game("[key_name(usr)] disabled Holodeck safeties.")

///this is what makes the holodeck not spawn anything on broken tiles (space and non engine plating / non holofloors)
/datum/map_template/holodeck/update_blacklist(turf/placement, list/input_blacklist)
	for(var/turf/possible_blacklist as anything in get_affected_turfs(placement))
		if (possible_blacklist.holodeck_compatible)
			continue
		input_blacklist += possible_blacklist

///loads the template whose id string it was given ("offline_program" loads datum/map_template/holodeck/offline)
/obj/machinery/computer/holodeck/proc/load_program(map_id, force = FALSE, add_delay = TRUE)
	if (program == map_id)
		return

	if (!is_operational)//load_program is called once with a timer (in toggle_power) we dont want this to load anything if its off
		map_id = offline_program
		force = TRUE

	if (!force && (!COOLDOWN_FINISHED(src, holodeck_cooldown) || spawning_simulation))
		say("ERROR. Recalibrating projection apparatus.")
		return

	if(spawning_simulation)
		return

	if (add_delay)
		COOLDOWN_START(src, holodeck_cooldown, (damaged ? HOLODECK_CD + HOLODECK_DMG_CD : HOLODECK_CD))
		if (damaged && floorcheck())
			damaged = FALSE

	spawning_simulation = TRUE
	active = (map_id != offline_program)
	update_use_power(active + IDLE_POWER_USE)
	program = map_id

	clear_projection()

	template = SSmapping.holodeck_templates[map_id]
	template.load(bottom_left) //this is what actually loads the holodeck simulation into the map

	if(template.restricted)
		log_game("[key_name(usr)] loaded a restricted Holodeck program: [program].")
		message_admins("[ADMIN_LOOKUPFLW(usr)] loaded a restricted Holodeck program: [program].")

	spawned = template.created_atoms //populate the spawned list with the atoms belonging to the holodeck

	if(istype(template, /datum/map_template/holodeck/thunderdome1218) && !SSshuttle.shuttle_purchase_requirements_met[SHUTTLE_UNLOCK_MEDISIM])
		say("Special note from \"1218 AD\" developer: I see you too are interested in the REAL dark ages of humanity! I've made this program also unlock some interesting shuttle designs on any communication console around. Have fun!")
		SSshuttle.shuttle_purchase_requirements_met[SHUTTLE_UNLOCK_MEDISIM] = TRUE

	nerf(!(obj_flags & EMAGGED))
	finish_spawn()

///To be used on destroy, mainly to prevent sleeping inside well, destroy. Missing a lot of the things contained in load_program
/obj/machinery/computer/holodeck/proc/reset_to_default()
	if (program == offline_program)
		return

	program = offline_program
	clear_projection()

	template = SSmapping.holodeck_templates[offline_program]
	INVOKE_ASYNC(template, /datum/map_template/proc/load, bottom_left) //this is what actually loads the holodeck simulation into the map

/obj/machinery/computer/holodeck/proc/clear_projection()
	//clear the items from the previous program
	for(var/holo_atom in spawned)
		derez(holo_atom)

	for(var/obj/effect/holodeck_effect/holo_effect as anything in effects)
		effects -= holo_effect
		holo_effect.deactivate(src)

	//makes sure that any time a holoturf is inside a baseturf list (e.g. if someone put a wall over it) its set to the OFFLINE turf
	//so that you cant bring turfs from previous programs into other ones (like putting the plasma burn turf into lounge for example)
	for(var/turf/closed/holo_turf in linked)
		for(var/baseturf in holo_turf.baseturfs)
			if(ispath(baseturf, /turf/open/floor/holofloor))
				holo_turf.baseturfs -= baseturf
				holo_turf.baseturfs += /turf/open/floor/holofloor/plating

///finalizes objects in the spawned list
/obj/machinery/computer/holodeck/proc/finish_spawn()
	for(var/atom/holo_atom as anything in spawned)
		if(QDELETED(holo_atom))
			spawned -= holo_atom
			continue

		RegisterSignal(holo_atom, COMSIG_PARENT_PREQDELETED, .proc/remove_from_holo_lists)
		holo_atom.flags_1 |= HOLOGRAM_1

		if(isholoeffect(holo_atom))//activates holo effects and transfers them from the spawned list into the effects list
			var/obj/effect/holodeck_effect/holo_effect = holo_atom
			effects += holo_effect
			spawned -= holo_effect
			var/atom/holo_effect_product = holo_effect.activate(src)//change name
			if(istype(holo_effect_product))
				spawned += holo_effect_product // we want mobs or objects spawned via holoeffects to be tracked as objects
				RegisterSignal(holo_effect_product, COMSIG_PARENT_PREQDELETED, .proc/remove_from_holo_lists)
			if(islist(holo_effect_product))
				for(var/atom/atom_product as anything in holo_effect_product)
					RegisterSignal(atom_product, COMSIG_PARENT_PREQDELETED, .proc/remove_from_holo_lists)
			continue

		if(isobj(holo_atom))
			var/obj/holo_object = holo_atom
			holo_object.resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF

			if(isstructure(holo_object))
				holo_object.flags_1 |= NODECONSTRUCT_1
				continue

			if(ismachinery(holo_object))
				var/obj/machinery/holo_machine = holo_object
				holo_machine.flags_1 |= NODECONSTRUCT_1
				holo_machine.power_change()

				if(istype(holo_machine, /obj/machinery/button))
					var/obj/machinery/button/holo_button = holo_machine
					holo_button.setup_device()

	spawning_simulation = FALSE

///this qdels holoitems that should no longer exist for whatever reason
/obj/machinery/computer/holodeck/proc/derez(atom/movable/holo_atom, silent = TRUE, forced = FALSE)
	spawned -= holo_atom
	if(!holo_atom)
		return
	UnregisterSignal(holo_atom, COMSIG_PARENT_PREQDELETED)
	var/turf/target_turf = get_turf(holo_atom)
	for(var/atom/movable/atom_contents as anything in holo_atom) //make sure that things inside of a holoitem are moved outside before destroying it
		atom_contents.forceMove(target_turf)

	if(!silent)
		visible_message(span_notice("[holo_atom] fades away!"))

	qdel(holo_atom)

/obj/machinery/computer/holodeck/proc/remove_from_holo_lists(datum/to_remove, _forced)
	SIGNAL_HANDLER
	spawned -= to_remove
	UnregisterSignal(to_remove, COMSIG_PARENT_PREQDELETED)

/obj/machinery/computer/holodeck/process(delta_time)
	if(damaged && DT_PROB(5, delta_time))
		for(var/turf/holo_turf in linked)
			if(DT_PROB(2.5, delta_time))
				do_sparks(2, 1, holo_turf)
				return
	. = ..()
	if(!. || program == offline_program)//we dont need to scan the holodeck if the holodeck is offline
		return

	if(!floorcheck()) //if any turfs in the floor of the holodeck are broken
		emergency_shutdown()
		damaged = TRUE
		visible_message("The holodeck overloads!")
		for(var/turf/holo_turf in linked)
			if(prob(30))
				do_sparks(2, 1, holo_turf)
			SSexplosions.lowturf += holo_turf
			holo_turf.hotspot_expose(1000,500,1)

	if(!(obj_flags & EMAGGED))
		for(var/item in spawned)
			if(!(get_turf(item) in linked))
				derez(item)
	for(var/obj/effect/holodeck_effect/holo_effect as anything in effects)
		holo_effect.tick()
	update_mode_power_usage(ACTIVE_POWER_USE, 50 + spawned.len * 3 + effects.len * 5)

/obj/machinery/computer/holodeck/proc/toggle_power(toggleOn = FALSE)
	if(active == toggleOn)
		return

	if(toggleOn)
		if(last_program && (last_program != offline_program))
			addtimer(CALLBACK(src,.proc/load_program, last_program, TRUE), 25)
		active = TRUE
	else
		last_program = program
		load_program(offline_program, TRUE)
		active = FALSE

/obj/machinery/computer/holodeck/power_change()
	. = ..()
	INVOKE_ASYNC(src, .proc/toggle_power, !machine_stat)

///shuts down the holodeck and force loads the offline_program
/obj/machinery/computer/holodeck/proc/emergency_shutdown()
	last_program = program
	active = FALSE
	load_program(offline_program, TRUE)

///returns TRUE if the entire floor of the holodeck is intact, returns FALSE if any are broken
/obj/machinery/computer/holodeck/proc/floorcheck()
	for(var/turf/holo_floor in linked)
		if(isspaceturf(holo_floor))
			return FALSE
		if(!holo_floor.intact)
			return FALSE
	return TRUE

///changes all weapons in the holodeck to do stamina damage if set
/obj/machinery/computer/holodeck/proc/nerf(nerf_this, is_loading = TRUE)
	if (!nerf_this && is_loading)
		return
	for(var/obj/item/to_be_nerfed in spawned)
		to_be_nerfed.damtype = nerf_this ? STAMINA : initial(to_be_nerfed.damtype)
	for(var/obj/effect/holodeck_effect/holo_effect as anything in effects)
		holo_effect.safety(nerf_this)

/obj/machinery/computer/holodeck/emag_act(mob/user)
	if(obj_flags & EMAGGED)
		return
	if(!LAZYLEN(emag_programs))
		to_chat(user, "[src] does not seem to have a card swipe port. It must be an inferior model.")
		return
	playsound(src, "sparks", 75, TRUE)
	obj_flags |= EMAGGED
	to_chat(user, span_warning("You vastly increase projector power and override the safety and security protocols."))
	say("Warning. Automatic shutoff and derezzing protocols have been corrupted. Please call Nanotrasen maintenance and do not use the simulator.")
	log_game("[key_name(user)] emagged the Holodeck Control Console")
	nerf(!(obj_flags & EMAGGED),FALSE)

/obj/machinery/computer/holodeck/emp_act(severity)
	. = ..()
	if(. & EMP_PROTECT_SELF)
		return
	emergency_shutdown()

/obj/machinery/computer/holodeck/ex_act(severity, target)
	emergency_shutdown()
	return ..()

/obj/machinery/computer/holodeck/Destroy()
	reset_to_default()
	if(linked)
		linked.linked = null
		linked.power_usage = list(AREA_USAGE_LEN)
	return ..()

/obj/machinery/computer/holodeck/blob_act(obj/structure/blob/B)
	emergency_shutdown()
	return ..()

#undef HOLODECK_CD
#undef HOLODECK_DMG_CD


code:
/*
	The holodeck activates these shortly after the program loads,
	and deactivates them immediately before changing or disabling the holodeck.

	These remove snowflake code for special holodeck functions.
*/
/obj/effect/holodeck_effect
	icon = 'icons/hud/screen_gen.dmi'
	icon_state = "x2"
	invisibility = INVISIBILITY_ABSTRACT

/obj/effect/holodeck_effect/proc/activate(obj/machinery/computer/holodeck/HC)
	return

/obj/effect/holodeck_effect/proc/deactivate(obj/machinery/computer/holodeck/HC)
	qdel(src)
	return

// Called by the holodeck computer as long as the program is running
/obj/effect/holodeck_effect/proc/tick(obj/machinery/computer/holodeck/HC)
	return

/obj/effect/holodeck_effect/proc/safety(active)
	return

// Generates a holodeck-tracked card deck
/obj/effect/holodeck_effect/cards
	icon = 'icons/obj/toy.dmi'
	icon_state = "deck_nanotrasen_full"
	var/obj/item/toy/cards/deck/deck

/obj/effect/holodeck_effect/cards/activate(obj/machinery/computer/holodeck/HC)
	deck = new(loc)
	safety(!(HC.obj_flags & EMAGGED))
	deck.holo = HC
	RegisterSignal(deck, COMSIG_PARENT_QDELETING, .proc/handle_card_delete)
	return deck

/obj/effect/holodeck_effect/cards/proc/handle_card_delete(datum/source)
	SIGNAL_HANDLER
	deck = null

/obj/effect/holodeck_effect/cards/safety(active)
	if(!deck)
		return
	if(active)
		deck.card_hitsound = null
		deck.card_force = 0
		deck.card_throwforce = 0
		deck.card_throw_speed = 3
		deck.card_throw_range = 7
		deck.card_attack_verb_continuous = string_list(list("attacks"))
	else
		deck.card_hitsound = 'sound/weapons/bladeslice.ogg'
		deck.card_force = 5
		deck.card_throwforce = 10
		deck.card_throw_speed = 3
		deck.card_throw_range = 7
		deck.card_attack_verb_continuous = string_list(list("attacks", "slices", "dices", "slashes", "cuts"))

/obj/effect/holodeck_effect/sparks/activate(obj/machinery/computer/holodeck/HC)
	var/turf/T = get_turf(src)
	if(T)
		var/datum/effect_system/spark_spread/s = new
		s.set_up(3, 1, T)
		s.start()
		T.temperature = 5000 //Why? not quite sure to be honest with you
		T.hotspot_expose(50000,50000,1)

/obj/effect/holodeck_effect/random_book

/obj/effect/holodeck_effect/random_book/activate(obj/machinery/computer/holodeck/father_holodeck)
	var/static/banned_books = list(/obj/item/book/manual/random, /obj/item/book/manual/nuclear, /obj/item/book/manual/wiki)
	var/newtype = pick(subtypesof(/obj/item/book/manual) - banned_books)
	var/obj/item/book/manual/to_spawn = new newtype(loc)
	to_spawn.flags_1 |= (HOLOGRAM_1 | NODECONSTRUCT_1)
	return to_spawn

/obj/effect/holodeck_effect/mobspawner
	var/mobtype = /mob/living/simple_animal/hostile/carp/holocarp
	var/mob/our_mob = null

/obj/effect/holodeck_effect/mobspawner/activate(obj/machinery/computer/holodeck/HC)
	if(islist(mobtype))
		mobtype = pick(mobtype)
	our_mob = new mobtype(loc)
	our_mob.flags_1 |= HOLOGRAM_1

	// these vars are not really standardized but all would theoretically create stuff on death
	for(var/v in list("butcher_results","corpse","weapon1","weapon2","blood_volume") & our_mob.vars)
		our_mob.vars[v] = null
	RegisterSignal(our_mob, COMSIG_PARENT_QDELETING, .proc/handle_mob_delete)
	return our_mob

/obj/effect/holodeck_effect/mobspawner/deactivate(obj/machinery/computer/holodeck/HC)
	if(our_mob)
		HC.derez(our_mob)
	qdel(src)

/obj/effect/holodeck_effect/mobspawner/proc/handle_mob_delete(datum/source)
	SIGNAL_HANDLER
	our_mob = null

/obj/effect/holodeck_effect/mobspawner/pet

/obj/effect/holodeck_effect/mobspawner/pet/Initialize(mapload)
	. = ..()
	mobtype = list(/mob/living/simple_animal/butterfly, /mob/living/simple_animal/chick/holo, /mob/living/simple_animal/pet/fox)
	mobtype += pick(/mob/living/simple_animal/pet/dog/corgi, /mob/living/simple_animal/pet/dog/corgi/puppy,
		/mob/living/simple_animal/pet/dog/pug)
	mobtype += pick(/mob/living/simple_animal/pet/cat, /mob/living/simple_animal/pet/cat/kitten)

/obj/effect/holodeck_effect/mobspawner/bee
	mobtype = /mob/living/simple_animal/hostile/bee/toxin

/obj/effect/holodeck_effect/mobspawner/monkey
	mobtype = /mob/living/simple_animal/holodeck_monkey

/obj/effect/holodeck_effect/mobspawner/penguin
	mobtype = /mob/living/simple_animal/pet/penguin/emperor

/obj/effect/holodeck_effect/mobspawner/penguin/Initialize(mapload)
	if(prob(1))
		mobtype = /mob/living/simple_animal/pet/penguin/emperor/shamebrero
	return ..()

/obj/effect/holodeck_effect/mobspawner/penguin_baby
	mobtype = /mob/living/simple_animal/pet/penguin/baby


code:
/datum/map_template/holodeck
	var/template_id
	var/description
	var/restricted = FALSE
	var/datum/parsed_map/lastparsed

	should_place_on_top = FALSE
	returns_created_atoms = TRUE
	keep_cached_map = TRUE

	var/obj/machinery/computer/holodeck/linked

/datum/map_template/holodeck/offline
	name = "Holodeck - Offline"
	template_id = "holodeck_offline"
	description = "benis"
	mappath = "_maps/templates/holodeck_offline.dmm"

/datum/map_template/holodeck/emptycourt
	name = "Holodeck - Empty Court"
	template_id = "holodeck_emptycourt"
	description = "benis"
	mappath = "_maps/templates/holodeck_emptycourt.dmm"

/datum/map_template/holodeck/dodgeball
	name = "Holodeck - Dodgeball Court"
	template_id = "holodeck_dodgeball"
	description = "benis"
	mappath = "_maps/templates/holodeck_dodgeball.dmm"

/datum/map_template/holodeck/basketball
	name = "Holodeck - Basketball Court"
	template_id = "holodeck_basketball"
	description = "benis"
	mappath = "_maps/templates/holodeck_basketball.dmm"

/datum/map_template/holodeck/thunderdome
	name = "Holodeck - Thunderdome Arena"
	template_id = "holodeck_thunderdome"
	description = "benis"
	mappath = "_maps/templates/holodeck_thunderdome.dmm"

/datum/map_template/holodeck/beach
	name = "Holodeck - Beach"
	template_id = "holodeck_beach"
	description = "benis"
	mappath = "_maps/templates/holodeck_beach.dmm"

/datum/map_template/holodeck/lounge
	name = "Holodeck - Lounge"
	template_id = "holodeck_lounge"
	description = "benis"
	mappath = "_maps/templates/holodeck_lounge.dmm"

/datum/map_template/holodeck/petpark
	name = "Holodeck - Pet Park"
	template_id = "holodeck_petpark"
	description = "benis"
	mappath = "_maps/templates/holodeck_petpark.dmm"

/datum/map_template/holodeck/firingrange
	name = "Holodeck - Firing Range"
	template_id = "holodeck_firingrange"
	description = "benis"
	mappath = "_maps/templates/holodeck_firingrange.dmm"

/datum/map_template/holodeck/anime_school
	name = "Holodeck - Anime School"
	template_id = "holodeck_animeschool"
	description = "benis"
	mappath = "_maps/templates/holodeck_animeschool.dmm"

/datum/map_template/holodeck/chapelcourt
	name = "Holodeck - Chapel Courtroom"
	template_id = "holodeck_chapelcourt"
	description = "benis"
	mappath = "_maps/templates/holodeck_chapelcourt.dmm"

/datum/map_template/holodeck/spacechess
	name = "Holodeck - Space Chess"
	template_id = "holodeck_spacechess"
	description = "benis"
	mappath = "_maps/templates/holodeck_spacechess.dmm"

/datum/map_template/holodeck/spacecheckers
	name = "Holodeck - Space Checkers"
	template_id = "holodeck_spacecheckers"
	description = "benis"
	mappath = "_maps/templates/holodeck_spacecheckers.dmm"

/datum/map_template/holodeck/kobayashi
	name = "Holodeck - Kobayashi Maru"
	template_id = "holodeck_kobayashi"
	description = "benis"
	mappath = "_maps/templates/holodeck_kobayashi.dmm"

/datum/map_template/holodeck/winterwonderland
	name = "Holodeck - Winter Wonderland"
	template_id = "holodeck_winterwonderland"
	description = "benis"
	mappath = "_maps/templates/holodeck_winterwonderland.dmm"

/datum/map_template/holodeck/photobooth
	name = "Holodeck - Photobooth"
	template_id = "holodeck_photobooth"
	description = "benis"
	mappath = "_maps/templates/holodeck_photobooth.dmm"

/datum/map_template/holodeck/skatepark
	name = "Holodeck - Skatepark"
	template_id = "holodeck_skatepark"
	description = "benis"
	mappath = "_maps/templates/holodeck_skatepark.dmm"

//bad evil no good programs

/datum/map_template/holodeck/medicalsim
	name = "Holodeck - Emergency Medical"
	template_id = "holodeck_medicalsim"
	description = "benis"
	mappath = "_maps/templates/holodeck_medicalsim.dmm"
	restricted = TRUE

/datum/map_template/holodeck/thunderdome1218
	name = "Holodeck - 1218 AD"
	template_id = "holodeck_thunderdome1218"
	description = "benis"
	mappath = "_maps/templates/holodeck_thunderdome1218.dmm"
	restricted = TRUE

/datum/map_template/holodeck/burntest
	name = "Holodeck - Atmospheric Burn Test"
	template_id = "holodeck_burntest"
	description = "benis"
	mappath = "_maps/templates/holodeck_burntest.dmm"
	restricted = TRUE

/datum/map_template/holodeck/wildlifesim
	name = "Holodeck - Wildlife Simulation"
	template_id = "holodeck_wildlifesim"
	description = "benis"
	mappath = "_maps/templates/holodeck_wildlifesim.dmm"
	restricted = TRUE

/datum/map_template/holodeck/holdoutbunker
	name = "Holodeck - Holdout Bunker"
	template_id = "holodeck_holdoutbunker"
	description = "benis"
	mappath = "_maps/templates/holodeck_holdoutbunker.dmm"
	restricted = TRUE

/datum/map_template/holodeck/anthophillia
	name = "Holodeck - Anthophillia"
	template_id = "holodeck_anthophillia"
	description = "benis"
	mappath = "_maps/templates/holodeck_anthophillia.dmm"
	restricted = TRUE

/datum/map_template/holodeck/refuelingstation
	name = "Holodeck - Refueling Station"
	template_id = "holodeck_refuelingstation"
	description = "benis"
	mappath = "_maps/templates/holodeck_refuelingstation.dmm"
	restricted = TRUE


code:
/*
	Items, Structures, Machines
*/

//
// Items
//

/obj/item/melee/energy/sword/holographic
	name = "holographic energy sword"
	desc = "May the force be with you. Sorta."
	damtype = STAMINA
	throw_speed = 2
	block_chance = 0
	throwforce = 0
	embedding = null
	sword_color_icon = null

	active_throwforce = 0
	active_sharpness = NONE
	active_heat = 0

/obj/item/melee/energy/sword/holographic/Initialize(mapload)
	. = ..()
	if(!sword_color_icon)
		sword_color_icon = pick("red", "blue", "green", "purple")

/obj/item/melee/energy/sword/holographic/green
	sword_color_icon = "green"

/obj/item/melee/energy/sword/holographic/red
	sword_color_icon = "red"

//BASKETBALL OBJECTS

/obj/item/toy/beach_ball/holoball
	name = "basketball"
	icon = 'icons/obj/items_and_weapons.dmi'
	icon_state = "basketball"
	inhand_icon_state = "basketball"
	desc = "Here's your chance, do your dance at the Space Jam."
	w_class = WEIGHT_CLASS_BULKY //Stops people from hiding it in their bags/pockets

/obj/item/toy/beach_ball/holoball/dodgeball
	name = "dodgeball"
	icon_state = "dodgeball"
	inhand_icon_state = "dodgeball"
	desc = "Used for playing the most violent and degrading of childhood games."

/obj/item/toy/beach_ball/holoball/dodgeball/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
	..()
	if((ishuman(hit_atom)))
		var/mob/living/carbon/M = hit_atom
		playsound(src, 'sound/items/dodgeball.ogg', 50, TRUE)
		M.apply_damage(10, STAMINA)
		if(prob(5))
			M.Paralyze(60)
			visible_message(span_danger("[M] is knocked right off [M.p_their()] feet!"))

//
// Structures
//

/obj/structure/holohoop
	name = "basketball hoop"
	desc = "Boom, shakalaka!"
	icon = 'icons/obj/basketball.dmi'
	icon_state = "hoop"
	anchored = TRUE
	density = TRUE

/obj/structure/holohoop/attackby(obj/item/W, mob/user, params)
	if(get_dist(src,user)<2)
		if(user.transferItemToLoc(W, drop_location()))
			visible_message(span_warning("[user] dunks [W] into \the [src]!"))

/obj/structure/holohoop/attack_hand(mob/living/user, list/modifiers)
	. = ..()
	if(.)
		return
	if(user.pulling && isliving(user.pulling))
		var/mob/living/L = user.pulling
		if(user.grab_state < GRAB_AGGRESSIVE)
			to_chat(user, span_warning("You need a better grip to do that!"))
			return
		L.forceMove(loc)
		L.Paralyze(100)
		visible_message(span_danger("[user] dunks [L] into \the [src]!"))
		user.stop_pulling()
	else
		..()

/obj/structure/holohoop/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
	if (isitem(AM) && !istype(AM,/obj/projectile))
		if(prob(50))
			AM.forceMove(get_turf(src))
			visible_message(span_warning("Swish! [AM] lands in [src]."))
			return
		else
			visible_message(span_danger("[AM] bounces off of [src]'s rim!"))
			return ..()
	else
		return ..()

//
// Machines
//

/obj/machinery/readybutton
	name = "ready declaration device"
	desc = "This device is used to declare ready. If all devices in an area are ready, the event will begin!"
	icon = 'icons/obj/monitors.dmi'
	icon_state = "auth_off"
	var/ready = 0
	var/area/currentarea = null
	var/eventstarted = FALSE

	use_power = IDLE_POWER_USE
	idle_power_usage = 2
	active_power_usage = 6
	power_channel = AREA_USAGE_ENVIRON

/obj/machinery/readybutton/attack_ai(mob/user)
	to_chat(user, span_warning("The station AI is not to interact with these devices!"))
	return

/obj/machinery/readybutton/attack_paw(mob/user, list/modifiers)
	to_chat(user, span_warning("You are too primitive to use this device!"))
	return

/obj/machinery/readybutton/attackby(obj/item/W, mob/user, params)
	to_chat(user, span_warning("The device is a solid button, there's nothing you can do with it!"))

/obj/machinery/readybutton/attack_hand(mob/user, list/modifiers)
	. = ..()
	if(.)
		return
	if(user.stat || machine_stat & (NOPOWER|BROKEN))
		to_chat(user, span_warning("This device is not powered!"))
		return

	currentarea = get_area(src.loc)
	if(!currentarea)
		qdel(src)

	if(eventstarted)
		to_chat(usr, span_warning("The event has already begun!"))
		return

	ready = !ready

	update_appearance()

	var/numbuttons = 0
	var/numready = 0
	for(var/obj/machinery/readybutton/button in currentarea)
		numbuttons++
		if (button.ready)
			numready++

	if(numbuttons == numready)
		begin_event()

/obj/machinery/readybutton/update_icon_state()
	icon_state = "auth_[ready ? "on" : "off"]"
	return ..()

/obj/machinery/readybutton/proc/begin_event()

	eventstarted = TRUE

	for(var/obj/structure/window/W in currentarea)
		if(W.flags_1&NODECONSTRUCT_1) // Just in case: only holo-windows
			qdel(W)

	for(var/mob/M in currentarea)
		to_chat(M, span_userdanger("FIGHT!"))

/obj/machinery/conveyor/holodeck

/obj/machinery/conveyor/holodeck/attackby(obj/item/I, mob/user, params)
	if(!user.transferItemToLoc(I, drop_location()))
		return ..()

/obj/item/paper/fluff/holodeck/trek_diploma
	name = "paper - Starfleet Academy Diploma"
	info = {"
Starfleet Academy

Official Diploma

"}

/obj/item/paper/fluff/holodeck/disclaimer
	name = "Holodeck Disclaimer"
	info = "Bruises sustained in the holodeck can be healed simply by sleeping."

/obj/vehicle/ridden/scooter/skateboard/pro/holodeck
	name = "holographic skateboard"
	desc = "A holographic copy of the EightO brand professional skateboard."
	instability = 6

/obj/vehicle/ridden/scooter/skateboard/pro/holodeck/pick_up_board() //picking up normal skateboards spawned in the holodeck gets rid of the holo flag, now you cant pick them up.
	return


code:
/*
	Mobs
*/

/mob/living/simple_animal/holodeck_monkey
	name = "monkey"
	desc = "A holographic creature fond of bananas."
	icon = 'icons/mob/human.dmi'
	icon_state = "monkey"
	icon_living = "monkey"
	icon_dead = "monkey_dead"
	speak_emote = list("chimpers")
	emote_hear = list("chimpers.")
	emote_see = list("scratches.", "looks around.")
	speak_chance = 1
	turns_per_move = 2
	butcher_results = list()
	response_help_continuous = "pets"
	response_help_simple = "pet"
	response_disarm_continuous = "pushes aside"
	response_disarm_simple = "push aside"
	response_harm_continuous = "kicks"
	response_harm_simple = "kick"