diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index 901d4ecbab9..bd1d4dd579a 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -201,6 +201,8 @@ datum/hud/New(mob/owner)
human_hud('icons/mob/screen1_Vampire.dmi')
else if(isswarmer(mymob))
swarmer_hud()
+ else if(isguardian(mymob))
+ guardian_hud()
//Triggered when F12 is pressed (Unless someone changed something in the DMF)
/mob/verb/button_pressed_F12(var/full = 0 as null)
diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm
index 974025dbc1b..1b294b38ae8 100644
--- a/code/_onclick/other_mobs.dm
+++ b/code/_onclick/other_mobs.dm
@@ -45,6 +45,11 @@
*/
/mob/living/UnarmedAttack(var/atom/A)
A.attack_animal(src)
+
+/mob/living/simple_animal/hostile/UnarmedAttack(var/atom/A)
+ target = A
+ AttackingTarget()
+
/atom/proc/attack_animal(mob/user as mob)
return
/mob/living/RestrainedClickOn(var/atom/A)
diff --git a/code/datums/uplink_item.dm b/code/datums/uplink_item.dm
index d9f5f6c51c0..1c37cfe76a6 100644
--- a/code/datums/uplink_item.dm
+++ b/code/datums/uplink_item.dm
@@ -412,6 +412,13 @@ var/list/uplink_items = list()
if(istype(T))
T.TC_cost = cost
+/datum/uplink_item/dangerous/guardian
+ name = "Holoparasites"
+ desc = "Though capable of near sorcerous feats via use of hardlight holograms and nanomachines, they require an organic host as a home base and source of fuel."
+ item = /obj/item/weapon/storage/box/syndie_kit/guardian
+ excludefrom = list("nuclear emergency")
+ cost = 12
+
// Ammunition
/datum/uplink_item/ammo
diff --git a/code/game/asteroid.dm b/code/game/asteroid.dm
index 6f6cdd18a2f..48dfe916303 100644
--- a/code/game/asteroid.dm
+++ b/code/game/asteroid.dm
@@ -97,7 +97,7 @@ var/global/max_secret_rooms = 6
theme = "xenoden"
walltypes = list(/turf/simulated/mineral/random/high_chance=1)
floortypes = list(/turf/simulated/floor/plating/airless/asteroid, /turf/simulated/floor/beach/sand)
- treasureitems = list(/obj/item/clothing/mask/facehugger=1,/obj/item/stack/sheet/animalhide/xeno=2,/obj/item/clothing/suit/xenos=2,/obj/item/clothing/head/xenos=2)
+ treasureitems = list(/obj/item/clothing/mask/facehugger=1,/obj/item/stack/sheet/animalhide/xeno=2,/obj/item/clothing/suit/xenos=2,/obj/item/clothing/head/xenos=2,/obj/item/weapon/guardiancreator/biological=1)
fluffitems = list(/obj/effect/decal/remains/human=1,/obj/effect/decal/cleanable/blood/xeno=5)
if("hitech")
diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm
index 54426761a02..e54e2567ce4 100644
--- a/code/game/gamemodes/wizard/spellbook.dm
+++ b/code/game/gamemodes/wizard/spellbook.dm
@@ -258,6 +258,13 @@
log_name = "BB"
limit = 3
+/datum/spellbook_entry/item/tarotdeck
+ name = "Tarot Deck"
+ desc = "A deck of tarot cards that can be used to summon a spirit companion for the wizard."
+ item_path = /obj/item/weapon/guardiancreator
+ log_name = "TD"
+ limit = 1
+
/datum/spellbook_entry/item/scryingorb/Buy(var/mob/living/carbon/human/user,var/obj/item/weapon/spellbook/book)
if(..())
if (!(XRAY in user.mutations))
diff --git a/code/modules/mob/living/simple_animal/bot_swarm/swarmer.dm b/code/modules/mob/living/simple_animal/bot_swarm/swarmer.dm
index 83b74b11e9e..e04147a7c77 100644
--- a/code/modules/mob/living/simple_animal/bot_swarm/swarmer.dm
+++ b/code/modules/mob/living/simple_animal/bot_swarm/swarmer.dm
@@ -315,21 +315,25 @@
D.pixel_x = target.pixel_x
D.pixel_y = target.pixel_y
if(do_mob(src, target, 100))
- src << "Dismantling complete."
- var/obj/item/stack/sheet/metal/M = new /obj/item/stack/sheet/metal(target.loc)
- M.amount = 5
- if(target.component_parts && target.component_parts.len)
- for(var/obj/item/I in target.component_parts)
- I.forceMove(M.loc)
- var/obj/effect/swarmer/disintegration/N = new /obj/effect/swarmer/disintegration(get_turf(target))
- N.pixel_x = target.pixel_x
- N.pixel_y = target.pixel_y
- target.dropContents()
- if(istype(target, /obj/machinery/computer))
- var/obj/machinery/computer/C = target
- if(C.circuit)
- new C.circuit(get_turf(M))
- qdel(target)
+ if(!src.Adjacent(target))
+ src << "Error:Dismantleing aborted."
+ else
+ src << "Dismantling complete."
+ var/obj/item/stack/sheet/metal/M = new /obj/item/stack/sheet/metal(target.loc)
+ M.amount = 5
+ if(target.component_parts && target.component_parts.len)
+ for(var/obj/item/I in target.component_parts)
+ I.forceMove(M.loc)
+ var/obj/effect/swarmer/disintegration/N = new /obj/effect/swarmer/disintegration(get_turf(target))
+ N.pixel_x = target.pixel_x
+ N.pixel_y = target.pixel_y
+ target.dropContents()
+ if(istype(target, /obj/machinery/computer))
+ var/obj/machinery/computer/C = target
+ if(C.circuit)
+ new C.circuit(get_turf(M))
+ qdel(target)
+
/obj/effect/swarmer //Default swarmer effect object visual feedback
diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm
new file mode 100644
index 00000000000..8837919167a
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm
@@ -0,0 +1,895 @@
+/mob/living/simple_animal/hostile/guardian
+ name = "Guardian Spirit"
+ real_name = "Guardian Spirit"
+ desc = "A mysterious being that stands by it's charge, ever vigilant."
+ speak_emote = list("intones")
+ response_help = "passes through"
+ response_disarm = "flails at"
+ response_harm = "punches"
+ icon = 'icons/mob/mob.dmi'
+ icon_state = "stand"
+ icon_living = "stand"
+ speed = 0
+ a_intent = I_HARM
+ stop_automated_movement = 1
+ floating = 1
+ attack_sound = 'sound/weapons/punch1.ogg'
+ minbodytemp = 0
+ maxbodytemp = 10000000
+ min_oxy = 0
+ max_oxy = INFINITY
+ min_tox = 0
+ max_tox = INFINITY
+ min_co2 = 0
+ max_co2 = INFINITY
+ min_n2 = 0
+ max_n2 = INFINITY
+ attacktext = "punches"
+ maxHealth = 100000 //The spirit itself is invincible
+ health = 100000
+ environment_smash = 0
+ melee_damage_lower = 15
+ melee_damage_upper = 15
+ AIStatus = AI_OFF
+ var/animated_manifest = FALSE
+ var/cooldown = 0
+ var/damage_transfer = 1 //how much damage from each attack we transfer to the owner
+ var/mob/living/summoner
+ var/range = 10 //how far from the user the spirit can be
+ var/playstyle_string = "You are a standard Guardian. You shouldn't exist!"
+ var/magic_fluff_string = " You draw the Coder, symbolizing bugs and errors. This shouldn't happen! Submit a bug report!"
+ var/tech_fluff_string = "BOOT SEQUENCE COMPLETE. ERROR MODULE LOADED. THIS SHOULDN'T HAPPEN. Submit a bug report!"
+ var/bio_fluff_string = "Your scarabs fail to mutate. This shouldn't happen! Submit a bug report!"
+ var/admin_fluff_string = "URK URF!"//the wheels on the bus...
+
+/mob/living/simple_animal/hostile/guardian/Life() //Dies if the summoner dies
+ ..()
+ if(summoner)
+ if(summoner.stat == DEAD)
+ // src << "Your summoner has died!"
+ // visible_message("The [src] dies along with its user!")
+ // summoner.visible_message("[summoner]'s body is completely consumed by the strain of sustaining [src]!")
+ // for(var/obj/item/W in summoner)
+ // if(!summoner.unEquip(W))
+ // qdel(W)
+ //summoner.dust()
+ //ghostize()
+ //qdel(src)
+ //else
+ src << "Your summoner has died!"
+ visible_message("The [src] dies along with its user!")
+ ghostize()
+ qdel(src)
+ if(summoner)
+ if (get_dist(get_turf(summoner),get_turf(src)) <= range)
+ return
+ else
+ src << "You moved out of range, and were pulled back! You can only move [range] meters from [summoner.real_name]"
+ visible_message("The [src] jumps back to its user.")
+ Recall()
+
+/mob/living/simple_animal/hostile/guardian/Move() //Returns to summoner if they move out of range
+ ..()
+ if(summoner)
+ if (get_dist(get_turf(summoner),get_turf(src)) <= range)
+ return
+ else
+ src << "You moved out of range, and were pulled back! You can only move [range] meters from [summoner.real_name]"
+ visible_message("The [src] jumps back to its user.")
+ Recall()
+
+
+/mob/living/simple_animal/hostile/guardian/adjustBruteLoss(amount) //The spirit is invincible, but passes on damage to the summoner
+ var/damage = amount * damage_transfer
+ if (summoner)
+ if(loc == summoner)
+ return
+ summoner.adjustBruteLoss(damage)
+ if(damage)
+ summoner << "Your [name] is under attack! You take damage!"
+ summoner.visible_message("Blood sprays from [summoner] as [src] takes damage!")
+ if(summoner.stat == UNCONSCIOUS)
+ summoner << "Your body can't take the strain of sustaining [src] in this condition, it begins to fall apart!"
+ summoner.adjustCloneLoss(damage/2)
+
+/mob/living/simple_animal/hostile/guardian/ex_act(severity, target)
+ switch (severity)
+ if (1)
+ gib()
+ return
+ if (2)
+ adjustBruteLoss(60)
+
+ if(3)
+ adjustBruteLoss(30)
+
+/mob/living/simple_animal/hostile/guardian/gib()
+ if(summoner)
+ summoner << "Your [src] was blown up!"
+ summoner.gib()
+ ghostize()
+ qdel(src)
+
+/mob/living/simple_animal/hostile/guardian/start_pulling(var/atom/movable/AM)
+ return //no pulling things fo you!
+
+//Manifest, Recall, Communicate
+
+/mob/living/simple_animal/hostile/guardian/proc/Manifest()
+ if(cooldown > world.time)
+ return
+ if(loc == summoner)
+ loc = get_turf(summoner)
+ src.client.eye = loc
+ cooldown = world.time + 30
+ if(animated_manifest)
+ var/end_icon = icon_state
+ icon_state = "parasite_forming"
+ spawn(6)
+ icon_state = end_icon
+
+/mob/living/simple_animal/hostile/guardian/proc/Recall()
+ if(cooldown > world.time)
+ return
+ loc = summoner
+ buckled = null
+ cooldown = world.time + 30
+
+/mob/living/simple_animal/hostile/guardian/proc/Communicate()
+ var/input = stripped_input(src, "Please enter a message to tell your summoner.", "Guardian", "")
+ if(!input) return
+
+ for(var/mob/M in mob_list)
+ if(M == summoner || (M in dead_mob_list))
+ M << "[src]: [input]"
+ src << "[src]: [input]"
+ log_say("[src.real_name]/[src.key] : [input]")
+
+/mob/living/simple_animal/hostile/guardian/proc/ToggleMode()
+ src << "You dont have another mode!"
+
+
+/mob/living/proc/guardian_comm()
+ set name = "Communicate"
+ set category = "Guardian"
+ set desc = "Communicate telepathically with your guardian."
+ var/input = stripped_input(src, "Please enter a message to tell your guardian.", "Message", "")
+ if(!input) return
+
+ for(var/mob/M in mob_list)
+ if(istype (M, /mob/living/simple_animal/hostile/guardian))
+ var/mob/living/simple_animal/hostile/guardian/G = M
+ if(G.summoner == src)
+ G << "[src]: [input]"
+ else if (M in dead_mob_list)
+ M << "[src]: [input]"
+ src << "[src]: [input]"
+ log_say("[src.real_name]/[src.key] : [text]")
+
+/mob/living/proc/guardian_recall()
+ set name = "Recall Guardian"
+ set category = "Guardian"
+ set desc = "Forcibly recall your guardian."
+ for(var/mob/living/simple_animal/hostile/guardian/G in mob_list)
+ if(G.summoner == src)
+ G.Recall()
+
+/mob/living/proc/guardian_reset()
+ set name = "Reset Guardian Player (One Use)"
+ set category = "Guardian"
+ set desc = "Re-rolls which ghost will control your Guardian. One use."
+ for(var/mob/living/simple_animal/hostile/guardian/G in mob_list)
+ if(G.summoner == src)
+ var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as [G.real_name]?", "pAI", null, FALSE, 100)
+ var/mob/dead/observer/new_stand = null
+ if(candidates.len)
+ new_stand = pick(candidates)
+ G << "Your user reset you, and your body was taken over by a ghost. Looks like they weren't happy with your performance."
+ src << "Your guardian has been successfully reset."
+ message_admins("[key_name_admin(new_stand)] has taken control of ([key_name_admin(G)])")
+ G.ghostize()
+ G.key = new_stand.key
+ src.verbs -= /mob/living/proc/guardian_reset
+ else
+ src << "There were no ghosts willing to take control. Looks like you're stuck with your Guardian for now."
+
+
+/mob/living/simple_animal/hostile/guardian/proc/ToggleLight()
+ if(!luminosity)
+ set_light(3)
+ else
+ set_light(0)
+
+
+//////////////////////////TYPES OF GUARDIANS
+
+
+//Fire. Low damage, low resistance, sets mobs on fire when bumping
+
+/mob/living/simple_animal/hostile/guardian/fire
+ a_intent = I_HELP
+ melee_damage_lower = 10
+ melee_damage_upper = 10
+ attack_sound = 'sound/items/Welder.ogg'
+ attacktext = "sears"
+ damage_transfer = 0.8
+ range = 10
+ playstyle_string = "As a Chaos type, you have only light damage resistance, but will ignite any enemy you bump into. In addition, your melee attacks will randomly teleport enemies."
+ environment_smash = 1
+ magic_fluff_string = "..And draw the Wizard, bringer of endless chaos!"
+ tech_fluff_string = "Boot sequence complete. Crowd control modules activated. Holoparasite swarm online."
+ bio_fluff_string = "Your scarab swarm finishes mutating and stirs to life, ready to sow havoc at random."
+
+/mob/living/simple_animal/hostile/guardian/fire/Life() //Dies if the summoner dies
+ ..()
+ if(summoner)
+ summoner.ExtinguishMob()
+ summoner.adjust_fire_stacks(-20)
+
+/mob/living/simple_animal/hostile/guardian/fire/AttackingTarget()
+ ..()
+ if(prob(45))
+ if(istype(target, /atom/movable))
+ var/atom/movable/M = target
+ if(!M.anchored && M != summoner)
+ do_teleport(M, M, 10)
+
+/mob/living/simple_animal/hostile/guardian/fire/Crossed(AM as mob|obj)
+ ..()
+ collision_ignite(AM)
+
+/mob/living/simple_animal/hostile/guardian/fire/Bumped(AM as mob|obj)
+ ..()
+ collision_ignite(AM)
+
+/mob/living/simple_animal/hostile/guardian/fire/Bump(AM as mob|obj)
+ ..()
+ collision_ignite(AM)
+
+/mob/living/simple_animal/hostile/guardian/fire/proc/collision_ignite(AM as mob|obj)
+ if(istype(AM, /mob/living/))
+ var/mob/living/M = AM
+ if(AM != summoner && M.fire_stacks < 7)
+ M.fire_stacks = 7
+ M.IgniteMob()
+
+/mob/living/simple_animal/hostile/guardian/fire/Bump(AM as mob|obj)
+ ..()
+ collision_ignite(AM)
+//Standard
+
+/mob/living/simple_animal/hostile/guardian/punch
+ melee_damage_lower = 20
+ melee_damage_upper = 20
+ damage_transfer = 0.5
+ playstyle_string = "As a standard type you have no special abilities, but have a high damage resistance and a powerful attack capable of smashing through walls."
+ environment_smash = 2
+ magic_fluff_string = "..And draw the Assistant, faceless and generic, but never to be underestimated."
+ tech_fluff_string = "Boot sequence complete. Standard combat modules loaded. Holoparasite swarm online."
+ bio_fluff_string = "Your scarab swarm stirs to life, ready to tear apart your enemies."
+ var/battlecry = "AT"
+
+
+/mob/living/simple_animal/hostile/guardian/punch/sealpunch
+ melee_damage_lower = 0
+ melee_damage_upper = 0
+ melee_damage_type = STAMINA
+ damage_transfer = 0.2
+ playstyle_string = "As a standard type you have no special abilities, but have a high damage resistance and a powerful attack capable of smashing through walls."
+ environment_smash = 2
+ battlecry = "URK"
+
+/mob/living/simple_animal/hostile/guardian/punch/verb/Battlecry()
+ set name = "Set Battlecry"
+ set category = "Guardian"
+ set desc = "Choose what you shout as you punch"
+ var/input = stripped_input(src,"What do you want your battlecry to be? Max length of 5 characters.", ,"", 6)
+ if(input)
+ battlecry = input
+
+
+
+/mob/living/simple_animal/hostile/guardian/punch/AttackingTarget()
+ ..()
+ if(iscarbon(target) && target != summoner)
+ if(length(battlecry) > 11)//no more then 11 letters in a battle cry.
+ src.visible_message("[src] punches [target]!")
+ else
+ src.say("[src.battlecry][src.battlecry][src.battlecry][src.battlecry][src.battlecry]")
+ playsound(loc, src.attack_sound, 50, 1, 1)
+ playsound(loc, src.attack_sound, 50, 1, 1)
+ playsound(loc, src.attack_sound, 50, 1, 1)
+ playsound(loc, src.attack_sound, 50, 1, 1)
+
+//Healer
+
+/mob/living/simple_animal/hostile/guardian/healer
+ a_intent = I_HARM
+ friendly = "heals"
+ speed = 0
+ melee_damage_lower = 15
+ melee_damage_upper = 15
+ playstyle_string = "As a Support type, you may toggle your basic attacks to a healing mode. In addition, Alt-Clicking on an adjacent mob will warp them to your bluespace beacon after a short delay."
+ magic_fluff_string = "..And draw the CMO, a potent force of life...and death."
+ tech_fluff_string = "Boot sequence complete. Medical modules active. Bluespace modules activated. Holoparasite swarm online."
+ bio_fluff_string = "Your scarab swarm finishes mutating and stirs to life, capable of mending wounds and travelling via bluespace."
+ var/turf/simulated/floor/beacon
+ var/beacon_cooldown = 0
+ var/toggle = FALSE
+
+/mob/living/simple_animal/hostile/guardian/healer/sealhealer
+ a_intent = I_HARM
+ friendly = "heals"
+ speed = 0
+ melee_damage_lower = 0
+ melee_damage_upper = 0
+ melee_damage_type = STAMINA
+
+
+/mob/living/simple_animal/hostile/guardian/healer/New()
+ ..()
+
+/mob/living/simple_animal/hostile/guardian/healer/Life()
+ ..()
+ regular_hud_updates()
+ process_med_hud(src, 1)
+
+/mob/living/simple_animal/hostile/guardian/healer/AttackingTarget()
+ ..()
+ if(toggle == TRUE)
+ if(src.loc == summoner)
+ src << "You must be manifested to heal!"
+ return
+ if(iscarbon(target))
+ var/mob/living/carbon/C = target
+ C.adjustBruteLoss(-5)
+ C.adjustFireLoss(-5)
+ C.adjustOxyLoss(-5)
+ C.adjustToxLoss(-5)
+
+/mob/living/simple_animal/hostile/guardian/healer/ToggleMode()
+ if(src.loc == summoner)
+ if(toggle)
+ a_intent = I_HARM
+ speed = 0
+ damage_transfer = 0.7
+ melee_damage_lower = 15
+ melee_damage_upper = 15
+ src << "You switch to combat mode."
+ toggle = FALSE
+ else
+ a_intent = I_HELP
+ speed = 1
+ damage_transfer = 1
+ melee_damage_lower = 0
+ melee_damage_upper = 0
+ src << "You switch to healing mode."
+ toggle = TRUE
+ else
+ src << "You have to be recalled to toggle modes!"
+
+
+/mob/living/simple_animal/hostile/guardian/healer/verb/Beacon()
+ set name = "Place Bluespsace Beacon"
+ set category = "Guardian"
+ set desc = "Mark a floor as your beacon point, allowing you to warp targets to it. Your beacon will not work in unfavorable atmospheric conditions."
+ if(beacon_cooldownBeacon placed! You may now warp targets to it, including your user, via Alt+Click. "
+ if(beacon)
+ beacon.ChangeTurf(/turf/simulated/floor/plating)
+ beacon = F
+ beacon_cooldown = world.time+3000
+
+ else
+ src << "Your power is on cooldown. You must wait five minutes between placing beacons."
+
+/mob/living/simple_animal/hostile/guardian/healer/AltClickOn(atom/movable/A)
+ if(!istype(A))
+ return
+ if(src.loc == summoner)
+ src << "You must be manifested to warp a target!"
+ return
+ if(!beacon)
+ src << "You need a beacon placed to warp things!"
+ return
+ if(!Adjacent(A))
+ src << "You must be adjacent to your target!"
+ return
+ if((A.anchored))
+ src << "Your target can not be anchored!"
+ return
+ src << "You begin to warp [A]"
+ if(do_mob(src, A, 50))
+ if(!A.anchored)
+ if(src.beacon) //Check that the beacon still exists and is in a safe place. No instant kills.
+ if(beacon.air)
+ var/datum/gas_mixture/Z = beacon.air
+ if(Z.oxygen >= 16 && !Z.toxins && Z.carbon_dioxide < 10 && !Z.trace_gases.len)
+ if((Z.temperature > 270) && (Z.temperature < 360))
+ var/pressure = Z.return_pressure()
+ if((pressure > 20) && (pressure < 550))
+ do_teleport(A, beacon, 0)
+ else
+ src << "The beacon isn't in a safe location!"
+ else
+ src << "The beacon isn't in a safe location!"
+ else
+ src << "You need a beacon to warp things!"
+ else
+ src << "You need to hold still!"
+
+
+///////////////////Ranged
+
+/obj/item/projectile/guardian
+ name = "crystal spray"
+ icon_state = "guardian"
+ damage = 5
+ damage_type = BRUTE
+ armour_penetration = 100
+
+/mob/living/simple_animal/hostile/guardian/ranged
+ a_intent = I_HELP
+ friendly = "quietly assesses"
+ melee_damage_lower = 10
+ melee_damage_upper = 10
+ damage_transfer = 0.9
+ projectiletype = /obj/item/projectile/guardian
+ ranged_cooldown_cap = 0
+ projectilesound = 'sound/effects/hit_on_shattered_glass.ogg'
+ ranged = 1
+ range = 13
+ playstyle_string = "As a ranged type, you have only light damage resistance, but are capable of spraying shards of crystal at incredibly high speed. You can also deploy surveillance snares to monitor enemy movement. Finally, you can switch to scout mode, in which you can't attack, but can move without limit."
+ magic_fluff_string = "..And draw the Sentinel, an alien master of ranged combat."
+ tech_fluff_string = "Boot sequence complete. Ranged combat modules active. Holoparasite swarm online."
+ bio_fluff_string = "Your scarab swarm finishes mutating and stirs to life, capable of spraying shards of crystal."
+ var/list/snares = list()
+ var/toggle = FALSE
+
+/mob/living/simple_animal/hostile/guardian/ranged/ToggleMode()
+ if(src.loc == summoner)
+ if(toggle)
+ ranged = 1
+ melee_damage_lower = 10
+ melee_damage_upper = 10
+ alpha = 255
+ range = 13
+ incorporeal_move = 0
+ src << "You switch to combat mode."
+ toggle = FALSE
+ else
+ ranged = 0
+ melee_damage_lower = 0
+ melee_damage_upper = 0
+ alpha = 60
+ range = 255
+ incorporeal_move = 1
+ src << "You switch to scout mode."
+ toggle = TRUE
+ else
+ src << "You have to be recalled to toggle modes!"
+
+/mob/living/simple_animal/hostile/guardian/ranged/verb/Snare()
+ set name = "Set Surveillance Trap"
+ set category = "Guardian"
+ set desc = "Set an invisible trap that will alert you when living creatures walk over it. Max of 5"
+ if(src.snares.len <6)
+ var/turf/snare_loc = get_turf(src.loc)
+ var/obj/item/effect/snare/S = new /obj/item/effect/snare(snare_loc)
+ S.spawner = src
+ S.name = "[get_area(snare_loc)] trap ([rand(1, 1000)])"
+ src.snares |= S
+ src << "Surveillance trap deployed!"
+ else
+ src << "You have too many traps deployed. Delete some first."
+
+/mob/living/simple_animal/hostile/guardian/ranged/verb/DisarmSnare()
+ set name = "Remove Surveillance Trap"
+ set category = "Guardian"
+ set desc = "Disarm unwanted surveillance traps."
+ var/picked_snare = input(src, "Pick which trap to disarm", "Disarm Trap") as null|anything in src.snares
+ if(picked_snare)
+ src.snares -= picked_snare
+ qdel(picked_snare)
+ src << "Snare disarmed."
+
+/obj/item/effect/snare
+ name = "snare"
+ desc = "You shouldn't be seeing this!"
+ var/mob/living/spawner
+ invisibility = 1
+
+
+/obj/item/effect/snare/Crossed(AM as mob|obj)
+ if(istype(AM, /mob/living/))
+ var/turf/snare_loc = get_turf(src.loc)
+ if(spawner)
+ spawner << "[AM] has crossed your surveillance trap at [get_area(snare_loc)]."
+ if(istype(spawner, /mob/living/simple_animal/hostile/guardian))
+ var/mob/living/simple_animal/hostile/guardian/G = spawner
+ if(G.summoner)
+ G.summoner << "[AM] has crossed your surveillance trap at [get_area(snare_loc)]."
+
+////Bomb
+
+/mob/living/simple_animal/hostile/guardian/bomb
+ melee_damage_lower = 15
+ melee_damage_upper = 15
+ damage_transfer = 0.6
+ range = 13
+ playstyle_string = "As an explosive type, you have only moderate close combat abilities, but are capable of converting any adjacent item into a disguised bomb via alt click."
+ magic_fluff_string = "..And draw the Scientist, master of explosive death."
+ tech_fluff_string = "Boot sequence complete. Explosive modules active. Holoparasite swarm online."
+ bio_fluff_string = "Your scarab swarm finishes mutating and stirs to life, capable of stealthily booby trapping items."
+ var/bomb_cooldown = 0
+
+/mob/living/simple_animal/hostile/guardian/bomb/AltClickOn(atom/movable/A)
+ if(!istype(A))
+ return
+ if(src.loc == summoner)
+ src << "You must be manifested to create bombs!"
+ return
+ if(istype(A, /obj/))
+ if(bomb_cooldown <= world.time && !stat)
+ var/obj/item/weapon/guardian_bomb/B = new /obj/item/weapon/guardian_bomb(get_turf(A))
+ src << "Success! Bomb armed!"
+ bomb_cooldown = world.time + 200
+ B.spawner = src
+ B.disguise (A)
+ else
+ src << "Your powers are on cooldown! You must wait 20 seconds between bombs."
+
+/obj/item/weapon/guardian_bomb
+ name = "bomb"
+ desc = "You shouldn't be seeing this!"
+ var/obj/stored_obj
+ var/mob/living/spawner
+
+
+/obj/item/weapon/guardian_bomb/proc/disguise(var/obj/A)
+ A.loc = src
+ stored_obj = A
+ anchored = A.anchored
+ density = A.density
+ appearance = A.appearance
+ spawn(600)
+ stored_obj.loc = get_turf(src.loc)
+ spawner << "Failure! Your trap didn't catch anyone this time."
+ qdel(src)
+
+/obj/item/weapon/guardian_bomb/proc/detonate(var/mob/living/user)
+ user << "The [src] was boobytrapped!"
+ spawner << "Success! Your trap caught [user]"
+ stored_obj.loc = get_turf(src.loc)
+ playsound(get_turf(src),'sound/effects/Explosion2.ogg', 200, 1)
+ user.ex_act(2)
+ qdel(src)
+
+/obj/item/weapon/guardian_bomb/attackby(mob/living/user)
+ detonate(user)
+ return
+
+/obj/item/weapon/guardian_bomb/pickup(mob/living/user)
+ detonate(user)
+ return
+
+/obj/item/weapon/guardian_bomb/examine(mob/user)
+ stored_obj.examine(user)
+ if(get_dist(user,src)<=2)
+ user << "Looks odd!"
+
+
+////////Creation
+
+/obj/item/weapon/guardiancreator
+ name = "deck of tarot cards"
+ desc = "An enchanted deck of tarot cards, rumored to be a source of unimaginable power. "
+ icon = 'icons/obj/toy.dmi'
+ icon_state = "deck_syndicate_full"
+ var/used = FALSE
+ var/theme = "magic"
+ var/mob_name = "Guardian Spirit"
+ var/use_message = "You shuffle the deck..."
+ var/used_message = "All the cards seem to be blank now."
+ var/failure_message = "..And draw a card! It's...blank? Maybe you should try again later."
+ var/ling_failure = "The deck refuses to respond to a souless creature such as you."
+ var/list/possible_guardians = list("Chaos", "Standard", "Ranged", "Support", "Explosive")
+ var/random = TRUE
+ var/adminseal = FALSE
+
+/obj/item/weapon/guardiancreator/attack_self(mob/living/user)
+ for(var/mob/living/simple_animal/hostile/guardian/G in living_mob_list)
+ if (G.summoner == user)
+ user << "You already have a [mob_name]!"
+ return
+ if(user.mind && user.mind.changeling)
+ user << "[ling_failure]"
+ return
+ if(used == TRUE)
+ user << "[used_message]"
+ return
+ used = TRUE
+ user << "[use_message]"
+ var/list/mob/dead/observer/candidates = pollCandidates("Do you want to play as the [mob_name] of [user.real_name]?",BE_PAI, null, FALSE, 100)
+ var/mob/dead/observer/theghost = null
+
+ if(candidates.len)
+ theghost = pick(candidates)
+ spawn_guardian(user, theghost.key)
+ else
+ user << "[failure_message]"
+ used = FALSE
+
+
+/obj/item/weapon/guardiancreator/proc/spawn_guardian(var/mob/living/user, var/key)
+ var/gaurdiantype = "Standard"
+ if(random)
+ gaurdiantype = pick(possible_guardians)
+ if(adminseal)
+ gaurdiantype = pick("StandardSeal","SupportSeal")
+ else
+ gaurdiantype = input(user, "Pick the type of [mob_name]", "[mob_name] Creation") as null|anything in possible_guardians
+ var/pickedtype = /mob/living/simple_animal/hostile/guardian/punch
+ var/picked_color = pick("#FFFFFF","#000000","#808080","#A52A2A","#FF0000","#8B0000","#DC143C","#FFA500","#FFFF00","#008000","#00FF00","#006400","#00FFFF","#0000FF","#000080","#008080","#800080","#4B0082")
+ switch(gaurdiantype)
+
+ if("Chaos")
+ pickedtype = /mob/living/simple_animal/hostile/guardian/fire
+
+ if("Standard")
+ pickedtype = /mob/living/simple_animal/hostile/guardian/punch
+
+ if("StandardSeal")
+ pickedtype = /mob/living/simple_animal/hostile/guardian/punch/sealpunch
+
+ if("Ranged")
+ pickedtype = /mob/living/simple_animal/hostile/guardian/ranged
+
+ if("Support")
+ pickedtype = /mob/living/simple_animal/hostile/guardian/healer
+
+ if("SupportSeal")
+ pickedtype = /mob/living/simple_animal/hostile/guardian/healer/sealhealer
+
+ if("Explosive")
+ pickedtype = /mob/living/simple_animal/hostile/guardian/bomb
+
+ var/mob/living/simple_animal/hostile/guardian/G = new pickedtype(user)
+ G.summoner = user
+ G.key = key
+ G << "You are a [mob_name] bound to serve [user.real_name]."
+ G << "You are capable of manifesting or recalling to your master with verbs in the Guardian tab. You will also find a verb to communicate with them privately there."
+ G << "While personally invincible, you will die if [user.real_name] does, and any damage dealt to you will have a portion passed on to them as you feed upon them to sustain yourself."
+ G << "[G.playstyle_string]"
+ user.verbs += /mob/living/proc/guardian_comm
+ if(!adminseal)
+ user.verbs += /mob/living/proc/guardian_recall
+ user.verbs += /mob/living/proc/guardian_reset
+ switch (theme)
+ if("magic")
+ G.name = "[mob_name]"
+ G.color = picked_color
+ G.real_name = "[mob_name]"
+ user << "[G.magic_fluff_string]."
+ if("tech")
+ var/colour = pick("orange", "neon", "pink", "red", "blue", "green")
+ G.name = "[mob_name] [capitalize(colour)]"
+ G.real_name = "[mob_name] [capitalize(colour)]"
+ G.icon_living = "parasite[colour]"
+ G.icon_state = "parasite[colour]"
+ G.animated_manifest = TRUE
+ user << "[G.tech_fluff_string]."
+ G.speak_emote = list("states")
+ if("bio")
+ user << "[G.bio_fluff_string]."
+ G.name = "[mob_name]"
+ G.color = picked_color
+ G.real_name = "[mob_name]"
+ G.icon_living = "headcrab"
+ G.icon_state = "headcrab"
+ G.attacktext = "swarms"
+ G.speak_emote = list("chitters")
+ if("seal")
+ user << "[G.admin_fluff_string]."
+ G.name = "[mob_name]"
+ G.color = picked_color
+ G.real_name = "[mob_name]"
+ G.icon = 'icons/mob/animal.dmi'
+ G.icon_living = "seal"
+ G.icon_state = "seal"
+ G.attacktext = "slaps"
+ G.speak_emote = list("barks")
+
+/obj/item/weapon/guardiancreator/choose
+ random = FALSE
+
+/obj/item/weapon/guardiancreator/tech
+ name = "holoparasite injector"
+ desc = "It contains alien nanoswarm of unknown origin. Though capable of near sorcerous feats via use of hardlight holograms and nanomachines, it requires an organic host as a home base and source of fuel."
+ icon = 'icons/obj/syringe.dmi'
+ icon_state = "combat_hypo"
+ theme = "tech"
+ mob_name = "Holoparasite"
+ use_message = "You start to power on the injector..."
+ used_message = "The injector has already been used."
+ failure_message = "...ERROR. BOOT SEQUENCE ABORTED. AI FAILED TO INTIALIZE. PLEASE CONTACT SUPPORT OR TRY AGAIN LATER."
+ ling_failure = "The holoparasites recoil in horror. They want nothing to do with a creature like you."
+
+/obj/item/weapon/guardiancreator/tech/choose
+ random = FALSE
+
+/obj/item/weapon/guardiancreator/biological
+ name = "scarab egg cluster"
+ desc = "A parasitic species that will nest in the closest living creature upon birth. While not great for your health, they'll defend their new 'hive' to the death."
+ icon = 'icons/obj/fish_items.dmi'
+ icon_state = "eggs"
+ theme = "bio"
+ mob_name = "Scarab Swarm"
+ use_message = "The eggs begin to twitch..."
+ used_message = "The cluster already hatched."
+ failure_message = "...but soon settles again. Guess they weren't ready to hatch after all."
+
+/obj/item/weapon/guardiancreator/biological/choose
+ random = FALSE
+
+
+/obj/item/weapon/guardiancreator/adminbus
+ name = "Avatar deck"
+ desc = "A mystical deck..oddly all the cards have a form of Bus on them."
+ icon = 'icons/obj/toy.dmi'
+ icon_state = "deck_syndicate_full"
+ theme = "seal"
+ mob_name = "Avatar"
+ use_message = "URK!"
+ used_message = "arf?"
+ failure_message = "..."
+ adminseal = TRUE
+
+
+/obj/item/weapon/guardiancreator/adminbus/attack_self(mob/living/user)
+
+ var/list/targets = list()
+ var/target = null
+ targets += getmobs() //Fill list, prompt user with list
+ target = input("Select a Player to guard!", "Player list", null, null) as null|anything in targets
+
+ if(!target) return
+ var/mob/living/carbon/human/H = target
+ if(H.mind )
+ spawn_guardian(H,user.key)
+ else
+ user << "Target has no mind. Aborting."
+
+
+/obj/item/weapon/paper/guardian
+ name = "Holoparasite Guide"
+ icon_state = "paper"
+ info = {"A list of Holoparasite Types
+
+
+ Chaos: Ignites mobs on touch. teleports them at random on attack. Automatically extinguishes the user if they catch fire.
+
+ Standard:Devestating close combat attacks and high damage resist. No special powers.
+
+ Ranged: Has two modes. Ranged: Extremely weak, highly spammable projectile attack. Scout: Can not attack, but can move through walls. Can lay surveillance snares in either mode.
+
+ Support:Has two modes. Combat: Medium power attacks and damage resist. Healer: Attacks heal damage, but low damage resist and slow movemen. Can deploy a bluespace beacon and warp targets to it (including you) in either mode.
+
+ Explosive: High damage resist and medium power attack. Can turn any object into a bomb, dealing explosive damage to the next person to touch it. The object will return to normal after the trap is triggered.
+"}
+
+/obj/item/weapon/paper/guardian/update_icon()
+ return
+
+
+/obj/item/weapon/storage/box/syndie_kit/guardian
+ name = "holoparasite injector kit"
+
+/obj/item/weapon/storage/box/syndie_kit/guardian/New()
+ ..()
+ new /obj/item/weapon/guardiancreator/tech/choose(src)
+ new /obj/item/weapon/paper/guardian(src)
+ return
+
+
+
+
+
+
+
+///HUD
+
+/datum/hud/proc/guardian_hud(ui_style = 'icons/mob/screen1_midnight.dmi')
+ adding = list()
+
+ var/obj/screen/using
+
+ using = new /obj/screen/guardian/Manifest()
+ using.screen_loc = ui_rhand
+ adding += using
+
+ using = new /obj/screen/guardian/Recall()
+ using.screen_loc = ui_lhand
+ adding += using
+
+ using = new /obj/screen/guardian/ToggleMode()
+ using.screen_loc = ui_storage1
+ adding += using
+
+ using = new /obj/screen/guardian/ToggleLight()
+ using.screen_loc = ui_inventory
+ adding += using
+
+ using = new /obj/screen/guardian/Communicate()
+ using.screen_loc = ui_back
+ adding += using
+
+ mymob.client.screen = list()
+ mymob.client.screen += mymob.client.void
+ mymob.client.screen += adding
+
+
+
+
+//HUD BUTTONS
+
+/obj/screen/guardian
+ icon = 'icons/mob/guardian.dmi'
+
+/obj/screen/guardian/Manifest
+ icon_state = "manifest"
+ name = "Manifest"
+ desc = "Spring forth into battle!"
+
+/obj/screen/guardian/Manifest/Click()
+ if(isguardian(usr))
+ var/mob/living/simple_animal/hostile/guardian/G = usr
+ G.Manifest()
+
+
+/obj/screen/guardian/Recall
+ icon_state = "recall"
+ name = "Recall"
+ desc = "Return to your user."
+
+/obj/screen/guardian/Recall/Click()
+ if(isguardian(usr))
+ var/mob/living/simple_animal/hostile/guardian/G = usr
+ G.Recall()
+
+/obj/screen/guardian/ToggleMode
+ icon_state = "toggle"
+ name = "Toggle Mode"
+ desc = "Switch between ability modes."
+
+/obj/screen/guardian/ToggleMode/Click()
+ if(isguardian(usr))
+ var/mob/living/simple_animal/hostile/guardian/G = usr
+ G.ToggleMode()
+
+/obj/screen/guardian/Communicate
+ icon_state = "communicate"
+ name = "Communicate"
+ desc = "Communicate telepathically with your user."
+
+/obj/screen/guardian/Communicate/Click()
+ if(isguardian(usr))
+ var/mob/living/simple_animal/hostile/guardian/G = usr
+ G.Communicate()
+
+/obj/screen/guardian/ToggleLight
+ icon_state = "light"
+ name = "Toggle Light"
+ desc = "Glow like star dust."
+
+/obj/screen/guardian/ToggleLight/Click()
+ if(isguardian(usr))
+ var/mob/living/simple_animal/hostile/guardian/G = usr
+ G.ToggleLight()
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 795027e55b6..2e34a1a5b45 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -142,7 +142,12 @@ proc/isembryo(A)
return 1
return 0
-proc/isobserver(A)
+/proc/isguardian(A)
+ if(istype(A, /mob/living/simple_animal/hostile/guardian))
+ return 1
+ return 0
+
+/proc/isobserver(A)
if(istype(A, /mob/dead/observer))
return 1
return 0
diff --git a/icons/mob/guardian.dmi b/icons/mob/guardian.dmi
new file mode 100644
index 00000000000..9db3fbedc68
Binary files /dev/null and b/icons/mob/guardian.dmi differ
diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi
index 2e3d39e8f9f..36642747502 100644
Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ
diff --git a/paradise.dme b/paradise.dme
index dd5f8e8ccac..0f1016441a2 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -1473,6 +1473,7 @@
#include "code\modules\mob\living\simple_animal\friendly\slime.dm"
#include "code\modules\mob\living\simple_animal\friendly\spiderbot.dm"
#include "code\modules\mob\living\simple_animal\friendly\tomato.dm"
+#include "code\modules\mob\living\simple_animal\guardian\guardian.dm"
#include "code\modules\mob\living\simple_animal\hostile\alien.dm"
#include "code\modules\mob\living\simple_animal\hostile\bat.dm"
#include "code\modules\mob\living\simple_animal\hostile\bear.dm"