diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm
index c72678fdc84..37d1a619c71 100644
--- a/code/__defines/mobs.dm
+++ b/code/__defines/mobs.dm
@@ -125,3 +125,6 @@
#define FLASH_PROTECTION_NONE 0
#define FLASH_PROTECTION_MODERATE 1
#define FLASH_PROTECTION_MAJOR 2
+
+#define ANIMAL_SPAWN_DELAY round(config.respawn_delay / 6)
+#define DRONE_SPAWN_DELAY round(config.respawn_delay / 3)
\ No newline at end of file
diff --git a/code/_helpers/game.dm b/code/_helpers/game.dm
index 4eec974e670..cf955a022c7 100644
--- a/code/_helpers/game.dm
+++ b/code/_helpers/game.dm
@@ -532,3 +532,6 @@ datum/projectile_data
/proc/SecondsToTicks(var/seconds)
return seconds * 10
+
+/proc/round_is_spooky(var/spookiness_threshold = config.cult_ghostwriter_req_cultists)
+ return (cult.current_antagonists.len > spookiness_threshold)
diff --git a/code/_helpers/mobs.dm b/code/_helpers/mobs.dm
index 6e2a7fae360..ac3b4b7e4ae 100644
--- a/code/_helpers/mobs.dm
+++ b/code/_helpers/mobs.dm
@@ -61,12 +61,12 @@ proc/random_facial_hair_style(gender, species = "Human")
f_style = pick(valid_facialhairstyles)
return f_style
-
+
proc/sanitize_name(name, species = "Human")
var/datum/species/current_species
if(species)
current_species = all_species[species]
-
+
return current_species ? current_species.sanitize_name(name) : sanitizeName(name)
proc/random_name(gender, species = "Human")
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index ad0eaa689d8..9dec281ed86 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -214,6 +214,8 @@ var/list/gamemode_cache = list()
var/list/language_prefixes = list(",","#","-")//Default language prefixes
+ var/ghosts_can_possess_animals = 0
+
/datum/configuration/New()
var/list/L = typesof(/datum/game_mode) - /datum/game_mode
for (var/T in L)
@@ -420,6 +422,9 @@ var/list/gamemode_cache = list()
if ("githuburl")
config.githuburl = value
+ if ("ghosts_can_possess_animals")
+ config.ghosts_can_possess_animals = value
+
if ("guest_jobban")
config.guest_jobban = 1
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index f6c84d77759..451afd48743 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -462,7 +462,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
src << "Spawning as a mouse is currently disabled."
return
- if(!MayRespawn(1, round(config.respawn_delay / 6)))
+ if(!MayRespawn(1, ANIMAL_SPAWN_DELAY))
return
var/turf/T = get_turf(src)
@@ -507,12 +507,28 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
//This is called when a ghost is drag clicked to something.
/mob/dead/observer/MouseDrop(atom/over)
if(!usr || !over) return
- if (isobserver(usr) && usr.client && usr.client.holder && isliving(over))
- if (usr.client.holder.cmd_ghost_drag(src,over))
+ if(isobserver(usr) && usr.client && isliving(over))
+ var/mob/living/M = over
+ // If they an admin, see if control can be resolved.
+ if(usr.client.holder && usr.client.holder.cmd_ghost_drag(src,M))
+ return
+ // Otherwise, see if we can possess the target.
+ if(usr == src && try_possession(M))
+ return
+ if(istype(over, /obj/machinery/drone_fabricator))
+ if(try_drone_spawn(src, over))
return
return ..()
+/mob/dead/observer/proc/try_possession(var/mob/living/M)
+ if(!config.ghosts_can_possess_animals)
+ usr << "Ghosts are not permitted to possess animals."
+ return 0
+ if(!M.can_be_possessed_by(src))
+ return 0
+ return M.do_possession(src)
+
//Used for drawing on walls with blood puddles as a spooky ghost.
/mob/dead/verb/bloody_doodle()
@@ -530,12 +546,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
if (usr != src)
return 0 //something is terribly wrong
- var/ghosts_can_write
- if(ticker.mode.name == "cult")
- if(cult.current_antagonists.len > config.cult_ghostwriter_req_cultists)
- ghosts_can_write = 1
-
- if(!ghosts_can_write)
+ if(!round_is_spooky())
src << "\red The veil is not thin enough for you to do that."
return
@@ -662,7 +673,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
/mob/proc/can_admin_interact()
return 0
-
+
/mob/dead/observer/can_admin_interact()
return check_rights(R_ADMIN, 0, src)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 73e8137faf0..656fae59927 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -810,3 +810,43 @@ default behaviour is:
ear_damage = damage
if(deaf >= 0)
ear_deaf = deaf
+
+/mob/living/proc/can_be_possessed_by(var/mob/dead/observer/possessor)
+ if(!istype(possessor))
+ return 0
+ if(!possession_candidate)
+ possessor << "That animal cannot be possessed."
+ return 0
+ if(jobban_isbanned(possessor, "Animal"))
+ possessor << "You are banned from animal roles."
+ return 0
+ if(!possessor.MayRespawn(1,ANIMAL_SPAWN_DELAY))
+ return 0
+ return 1
+
+/mob/living/proc/do_possession(var/mob/dead/observer/possessor)
+
+ if(!(istype(possessor) && possessor.ckey))
+ return 0
+
+ if(src.ckey || src.client)
+ possessor << "\The [src] already has a player."
+ return 0
+
+ message_admins("[key_name_admin(possessor)] has taken control of \the [src].")
+ log_admin("[key_name(possessor)] took control of \the [src].")
+ src.ckey = possessor.ckey
+ qdel(possessor)
+
+ if(round_is_spooky(6)) // Six or more active cultists.
+ src << "You reach out with tendrils of ectoplasm and invade the mind of \the [src]..."
+ src << "You have assumed direct control of \the [src]."
+ src << "Due to the spookiness of the round, you have taken control of the poor animal as an invading, possessing spirit - roleplay accordingly."
+ src.universal_speak = 1
+ src.universal_understand = 1
+ //src.cultify() // Maybe another time.
+ return
+
+ src << "You are now \the [src]!"
+ src << "Remember to stay in character for a mob of this type!"
+ return 1
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 5a671afe992..2164c7ec187 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -43,3 +43,4 @@
var/fire_stacks
var/failed_last_breath = 0 //This is used to determine if the mob failed a breath. If they did fail a brath, they will attempt to breathe each tick, otherwise just once per 4 ticks.
+ var/possession_candidate // Can be possessed by ghosts if unplayed.
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/robot/drone/drone.dm b/code/modules/mob/living/silicon/robot/drone/drone.dm
index 56d608fd658..e1239181470 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone.dm
@@ -36,6 +36,7 @@ var/list/mob_hat_cache = list()
req_access = list(access_engine, access_robotics)
integrated_light_power = 3
local_transmit = 1
+ possession_candidate = 1
mob_bump_flag = SIMPLE_ANIMAL
mob_swap_flags = SIMPLE_ANIMAL
@@ -57,6 +58,31 @@ var/list/mob_hat_cache = list()
holder_type = /obj/item/weapon/holder/drone
+/mob/living/silicon/robot/drone/can_be_possessed_by(var/mob/dead/observer/possessor)
+ if(!istype(possessor))
+ return 0
+ if(!config.allow_drone_spawn)
+ src << "Playing as drones is not currently permitted."
+ return 0
+ if(jobban_isbanned(possessor,"Cyborg"))
+ usr << "You are banned from playing synthetics and cannot spawn as a drone."
+ return 0
+ if(!possessor.MayRespawn(1,DRONE_SPAWN_DELAY))
+ return 0
+ return 1
+
+/mob/living/silicon/robot/drone/do_possession(var/mob/dead/observer/possessor)
+ if(!(istype(possessor) && possessor.ckey))
+ return 0
+ if(src.ckey || src.client)
+ possessor << "\The [src] already has a player."
+ return 0
+ message_admins("[key_name_admin(possessor)] has taken control of \the [src].")
+ log_admin("[key_name(possessor)] took control of \the [src].")
+ transfer_personality(possessor.client)
+ qdel(possessor)
+ return 1
+
/mob/living/silicon/robot/drone/Destroy()
if(hat)
hat.loc = get_turf(src)
diff --git a/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm b/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm
index f82a669f5df..0ddc764dbe5 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm
@@ -85,45 +85,47 @@
drone_progress = 0
-/mob/dead/verb/join_as_drone()
-
+/mob/dead/observer/verb/join_as_drone()
set category = "Ghost"
set name = "Join As Drone"
set desc = "If there is a powered, enabled fabricator in the game world with a prepared chassis, join as a maintenance drone."
+ try_drone_spawn(src)
+
+/proc/try_drone_spawn(var/mob/user, var/obj/machinery/drone_fabricator/fabricator)
if(ticker.current_state < GAME_STATE_PLAYING)
- src << "The game hasn't started yet!"
+ user << "The game hasn't started yet!"
return
if(!(config.allow_drone_spawn))
- src << "That verb is not currently permitted."
+ user << "That verb is not currently permitted."
return
- if (!src.stat)
+ if(jobban_isbanned(user,"Cyborg"))
+ user << "You are banned from playing synthetics and cannot spawn as a drone."
return
- if (usr != src)
- return 0 //something is terribly wrong
-
- if(jobban_isbanned(src,"Cyborg"))
- usr << "You are banned from playing synthetics and cannot spawn as a drone."
+ if(!user.MayRespawn(1, DRONE_SPAWN_DELAY))
return
- if(!MayRespawn(1, round(config.respawn_delay / 3)))
- return
+ if(!fabricator)
- var/list/all_fabricators = list()
- for(var/obj/machinery/drone_fabricator/DF in world)
- if(DF.stat & NOPOWER || !DF.produce_drones)
- continue
- if(DF.drone_progress >= 100)
+ var/list/all_fabricators = list()
+ for(var/obj/machinery/drone_fabricator/DF in machines)
+ if((DF.stat & NOPOWER) || !DF.produce_drones || DF.drone_progress < 100)
+ continue
all_fabricators[DF.fabricator_tag] = DF
- if(!all_fabricators.len)
- src << "There are no available drone spawn points, sorry."
- return
+ if(!all_fabricators.len)
+ user << "There are no available drone spawn points, sorry."
+ return
- var/choice = input(src,"Which fabricator do you wish to use?") as null|anything in all_fabricators
- if(choice)
- var/obj/machinery/drone_fabricator/chosen_fabricator = all_fabricators[choice]
- chosen_fabricator.create_drone(src.client)
+ var/choice = input(user,"Which fabricator do you wish to use?") as null|anything in all_fabricators
+ if(!choice || !all_fabricators[choice])
+ return
+ fabricator = all_fabricators[choice]
+
+ if(user && fabricator && !((fabricator.stat & NOPOWER) || !fabricator.produce_drones || fabricator.drone_progress < 100))
+ fabricator.create_drone(user.client)
+ return 1
+ return
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm
index f2cc7686c0b..85d1cc15849 100644
--- a/code/modules/mob/living/simple_animal/friendly/mouse.dm
+++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm
@@ -29,6 +29,7 @@
universal_understand = 1
holder_type = /obj/item/weapon/holder/mouse
mob_size = MOB_MINISCULE
+ possession_candidate = 1
/mob/living/simple_animal/mouse/Life()
..()