This commit is contained in:
Ghommie
2019-12-23 04:32:09 +01:00
541 changed files with 13581 additions and 3235 deletions

View File

@@ -40,5 +40,5 @@
/datum/action/quit_vr/Trigger() //this merely a trigger for /datum/component/virtual_reality
. = ..()
if(!.)
if(.) //The component was not there to block the trigger.
Remove(owner)

View File

@@ -52,18 +52,16 @@
flags_1 = NODECONSTRUCT_1
only_current_user_can_interact = TRUE
/obj/machinery/vr_sleeper/hugbox/emag_act(mob/user)
return SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
/obj/machinery/vr_sleeper/emag_act(mob/user)
. = ..()
if(!(obj_flags & EMAGGED))
return
obj_flags |= EMAGGED
you_die_in_the_game_you_die_for_real = TRUE
sparks.start()
addtimer(CALLBACK(src, .proc/emagNotify), 150)
return TRUE
if(!only_current_user_can_interact)
obj_flags |= EMAGGED
you_die_in_the_game_you_die_for_real = TRUE
sparks.start()
addtimer(CALLBACK(src, .proc/emagNotify), 150)
return TRUE
/obj/machinery/vr_sleeper/update_icon()
icon_state = "[initial(icon_state)][state_open ? "-open" : ""]"
@@ -76,7 +74,7 @@
return ..()
/obj/machinery/vr_sleeper/MouseDrop_T(mob/target, mob/user)
if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser())
if(user.lying || !iscarbon(target) || !Adjacent(target) || !user.canUseTopic(src, BE_CLOSE, TRUE, NO_TK))
return
close_machine(target)
@@ -91,26 +89,25 @@
return
switch(action)
if("vr_connect")
var/mob/living/carbon/human/human_occupant = occupant
if(human_occupant && human_occupant.mind && usr == occupant)
to_chat(occupant, "<span class='warning'>Transferring to virtual reality...</span>")
if(vr_mob && (!istype(vr_mob) || !vr_mob.InCritical()) && !vr_mob.GetComponent(/datum/component/virtual_reality))
vr_mob.AddComponent(/datum/component/virtual_reality, human_occupant, src, you_die_in_the_game_you_die_for_real)
to_chat(vr_mob, "<span class='notice'>Transfer successful! You are now playing as [vr_mob] in VR!</span>")
else
var/mob/M = occupant
if(M?.mind && M == usr)
to_chat(M, "<span class='warning'>Transferring to virtual reality...</span>")
var/datum/component/virtual_reality/VR
if(vr_mob)
VR = vr_mob.GetComponent(/datum/component/virtual_reality)
if(!(VR?.connect(M)))
if(allow_creating_vr_mobs)
to_chat(occupant, "<span class='warning'>Virtual avatar not found, attempting to create one...</span>")
to_chat(occupant, "<span class='warning'>Virtual avatar [vr_mob ? "corrupted" : "missing"], attempting to create one...</span>")
var/obj/effect/landmark/vr_spawn/V = get_vr_spawnpoint()
var/turf/T = get_turf(V)
if(T)
SStgui.close_user_uis(occupant, src)
new_player(occupant, T, V.vr_outfit)
to_chat(vr_mob, "<span class='notice'>Transfer successful! You are now playing as [vr_mob] in VR!</span>")
else
to_chat(occupant, "<span class='warning'>Virtual world misconfigured, aborting transfer</span>")
else
to_chat(occupant, "<span class='warning'>The virtual world does not support the creation of new virtual avatars, aborting transfer</span>")
else
to_chat(vr_mob, "<span class='notice'>Transfer successful! You are now playing as [vr_mob] in VR!</span>")
. = TRUE
if("delete_avatar")
if(!occupant || usr == occupant)
@@ -157,17 +154,31 @@
for(var/obj/effect/landmark/vr_spawn/V in GLOB.landmarks_list)
GLOB.vr_spawnpoints[V.vr_category] = V
/obj/machinery/vr_sleeper/proc/new_player(mob/living/carbon/human/H, location, datum/outfit/outfit, transfer = TRUE)
if(!H)
/obj/machinery/vr_sleeper/proc/new_player(mob/M, location, datum/outfit/outfit, transfer = TRUE)
if(!M)
return
cleanup_vr_mob()
vr_mob = new virtual_mob_type(location)
if(vr_mob.build_virtual_character(H, outfit))
var/mob/living/carbon/human/vr_H = vr_mob
vr_H.updateappearance(TRUE, TRUE, TRUE)
if(!transfer || !H.mind)
return
vr_mob.AddComponent(/datum/component/virtual_reality, H, src, you_die_in_the_game_you_die_for_real)
if(vr_mob.build_virtual_character(M, outfit) && iscarbon(vr_mob))
var/mob/living/carbon/C = vr_mob
C.updateappearance(TRUE, TRUE, TRUE)
var/datum/component/virtual_reality/VR = vr_mob.AddComponent(/datum/component/virtual_reality, you_die_in_the_game_you_die_for_real)
if(VR.connect(M))
RegisterSignal(VR, COMSIG_COMPONENT_UNREGISTER_PARENT, .proc/unset_vr_mob)
RegisterSignal(VR, COMSIG_COMPONENT_REGISTER_PARENT, .proc/set_vr_mob)
if(!only_current_user_can_interact)
VR.RegisterSignal(src, COMSIG_ATOM_EMAG_ACT, /datum/component/virtual_reality.proc/you_only_live_once)
VR.RegisterSignal(src, COMSIG_MACHINE_EJECT_OCCUPANT, /datum/component/virtual_reality.proc/revert_to_reality)
VR.RegisterSignal(src, COMSIG_PARENT_QDELETING, /datum/component/virtual_reality.proc/machine_destroyed)
to_chat(vr_mob, "<span class='notice'>Transfer successful! You are now playing as [vr_mob] in VR!</span>")
else
to_chat(M, "<span class='notice'>Transfer failed! virtual reality data likely corrupted!</span>")
/obj/machinery/vr_sleeper/proc/unset_vr_mob(datum/component/virtual_reality/VR)
vr_mob = null
/obj/machinery/vr_sleeper/proc/set_vr_mob(datum/component/virtual_reality/VR)
vr_mob = VR.parent
/obj/machinery/vr_sleeper/proc/cleanup_vr_mob()
if(vr_mob)
@@ -222,6 +233,7 @@
qdel(C)
for (var/A in corpse_party)
var/mob/M = A
if(get_area(M) == vr_area && M.stat == DEAD)
if(M && M.stat == DEAD && get_area(M) == vr_area)
qdel(M)
corpse_party -= M
addtimer(CALLBACK(src, .proc/clean_up), 3 MINUTES)

View File

@@ -37,6 +37,8 @@
dat += "<I>Your apprentice is training to cast spells that will aid your survival. They know Forcewall and Charge and come with a Staff of Healing.</I><BR>"
dat += "<A href='byond://?src=[REF(src)];school=[APPRENTICE_ROBELESS]'>Robeless</A><BR>"
dat += "<I>Your apprentice is training to cast spells without their robes. They know Knock and Mindswap.</I><BR>"
dat += "<A href='byond://?src=[REF(src)];school=[APPRENTICE_MARTIAL]'>Martial Artist</a><BR>"
dat += "<I>Your apprentice is training in ancient martial arts. They know the Plasmafist and Nuclear Fist.</I><BR>"
user << browse(dat, "window=radio")
onclose(user, "radio")
return

View File

@@ -65,6 +65,8 @@
//Equip
var/mob/living/carbon/human/H = owner.current
H.set_species(/datum/species/abductor)
var/obj/item/organ/tongue/abductor/T = H.getorganslot(ORGAN_SLOT_TONGUE)
T.mothership = "[team.name]"
H.real_name = "[team.name] [sub_role]"
H.equipOutfit(outfit)

View File

@@ -17,7 +17,7 @@
actions_types = list(/datum/action/item_action/hands_free/activate)
allowed = list(
/obj/item/abductor,
/obj/item/abductor_baton,
/obj/item/abductor/baton,
/obj/item/melee/baton,
/obj/item/gun/energy,
/obj/item/restraints/handcuffs
@@ -57,7 +57,7 @@
/obj/item/clothing/suit/armor/abductor/vest/item_action_slot_check(slot, mob/user, datum/action/A)
if(slot == SLOT_WEAR_SUIT) //we only give the mob the ability to activate the vest if he's actually wearing it.
return 1
return TRUE
/obj/item/clothing/suit/armor/abductor/vest/proc/SetDisguise(datum/icon_snapshot/entry)
disguise = entry
@@ -89,11 +89,9 @@
/obj/item/clothing/suit/armor/abductor/vest/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
DeactivateStealth()
return 0
/obj/item/clothing/suit/armor/abductor/vest/IsReflect()
DeactivateStealth()
return 0
/obj/item/clothing/suit/armor/abductor/vest/ui_action_click()
switch(mode)
@@ -111,7 +109,10 @@
to_chat(loc, "<span class='warning'>Combat injection is still recharging.</span>")
return
var/mob/living/carbon/human/M = loc
M.do_adrenaline(150, FALSE, 0, 0, TRUE, list("inaprovaline" = 3, "synaptizine" = 10, "omnizine" = 10), "<span class='boldnotice'>You feel a sudden surge of energy!</span>")
M.adjustStaminaLoss(-75)
M.SetUnconscious(0)
M.SetStun(0)
M.SetKnockdown(0)
combat_cooldown = 0
START_PROCESSING(SSobj, src)
@@ -131,9 +132,11 @@
/obj/item/abductor
icon = 'icons/obj/abductor.dmi'
lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi'
righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi'
/obj/item/abductor/proc/AbductorCheck(mob/user)
if(HAS_TRAIT(user, TRAIT_ABDUCTOR_TRAINING))
if (HAS_TRAIT(user, TRAIT_ABDUCTOR_TRAINING))
return TRUE
if (istype(user) && user.mind && HAS_TRAIT(user.mind, TRAIT_ABDUCTOR_TRAINING))
return TRUE
@@ -158,8 +161,6 @@
desc = "A dual-mode tool for retrieving specimens and scanning appearances. Scanning can be done through cameras."
icon_state = "gizmo_scan"
item_state = "silencer"
lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi'
righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi'
var/mode = GIZMO_SCAN
var/mob/living/marked = null
var/obj/machinery/abductor/console/console
@@ -218,12 +219,9 @@
if(marked == target)
to_chat(user, "<span class='warning'>This specimen is already marked!</span>")
return
if(ishuman(target))
if(isabductor(target))
marked = target
to_chat(user, "<span class='notice'>You mark [target] for future retrieval.</span>")
else
prepare(target,user)
if(isabductor(target) || iscow(target))
marked = target
to_chat(user, "<span class='notice'>You mark [target] for future retrieval.</span>")
else
prepare(target,user)
@@ -247,8 +245,6 @@
desc = "A compact device used to shut down communications equipment."
icon_state = "silencer"
item_state = "gizmo"
lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi'
righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi'
/obj/item/abductor/silencer/attack(mob/living/M, mob/user)
if(!AbductorCheck(user))
@@ -292,8 +288,6 @@
or to send a command to a test subject with a charged gland."
icon_state = "mind_device_message"
item_state = "silencer"
lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi'
righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi'
var/mode = MIND_DEVICE_MESSAGE
/obj/item/abductor/mind_device/attack_self(mob/user)
@@ -389,6 +383,17 @@
item_state = "alienpistol"
trigger_guard = TRIGGER_GUARD_ALLOW_ALL
/obj/item/gun/energy/shrink_ray
name = "shrink ray blaster"
desc = "This is a piece of frightening alien tech that enhances the magnetic pull of atoms in a localized space to temporarily make an object shrink. \
That or it's just space magic. Either way, it shrinks stuff."
ammo_type = list(/obj/item/ammo_casing/energy/shrink)
item_state = "shrink_ray"
icon_state = "shrink_ray"
fire_delay = 30
selfcharge = 1//shot costs 200 energy, has a max capacity of 1000 for 5 shots. self charge returns 25 energy every couple ticks, so about 1 shot charged every 12~ seconds
trigger_guard = TRIGGER_GUARD_ALLOW_ALL// variable-size trigger, get it? (abductors need this to be set so the gun is usable for them)
/obj/item/paper/guides/antag/abductor
name = "Dissection Guide"
icon_state = "alienpaper_words"
@@ -422,21 +427,18 @@
#define BATON_PROBE 3
#define BATON_MODES 4
/obj/item/abductor_baton
/obj/item/abductor/baton
name = "advanced baton"
desc = "A quad-mode baton used for incapacitation and restraining of specimens."
var/mode = BATON_STUN
icon = 'icons/obj/abductor.dmi'
icon_state = "wonderprodStun"
item_state = "wonderprod"
lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi'
righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi'
slot_flags = ITEM_SLOT_BELT
force = 7
w_class = WEIGHT_CLASS_NORMAL
actions_types = list(/datum/action/item_action/toggle_mode)
/obj/item/abductor_baton/proc/toggle(mob/living/user=usr)
/obj/item/abductor/baton/proc/toggle(mob/living/user=usr)
mode = (mode+1)%BATON_MODES
var/txt
switch(mode)
@@ -452,7 +454,7 @@
to_chat(usr, "<span class='notice'>You switch the baton to [txt] mode.</span>")
update_icon()
/obj/item/abductor_baton/update_icon()
/obj/item/abductor/baton/update_icon()
switch(mode)
if(BATON_STUN)
icon_state = "wonderprodStun"
@@ -467,8 +469,8 @@
icon_state = "wonderprodProbe"
item_state = "wonderprodProbe"
/obj/item/abductor_baton/attack(mob/target, mob/living/user)
if(!isabductor(user))
/obj/item/abductor/baton/attack(mob/target, mob/living/user)
if(!AbductorCheck(user))
return
if(iscyborg(target))
@@ -485,8 +487,8 @@
if(ishuman(L))
var/mob/living/carbon/human/H = L
if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK))
playsound(L, 'sound/weapons/genhit.ogg', 50, 1)
return 0
playsound(H, 'sound/weapons/genhit.ogg', 50, TRUE)
return FALSE
switch (mode)
if(BATON_STUN)
@@ -498,10 +500,10 @@
if(BATON_PROBE)
ProbeAttack(L,user)
/obj/item/abductor_baton/attack_self(mob/living/user)
/obj/item/abductor/baton/attack_self(mob/living/user)
toggle(user)
/obj/item/abductor_baton/proc/StunAttack(mob/living/L,mob/living/user)
/obj/item/abductor/baton/proc/StunAttack(mob/living/L,mob/living/user)
L.lastattacker = user.real_name
L.lastattackerckey = user.ckey
@@ -513,7 +515,7 @@
L.visible_message("<span class='danger'>[user] has stunned [L] with [src]!</span>", \
"<span class='userdanger'>[user] has stunned you with [src]!</span>")
playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1)
playsound(src, 'sound/weapons/egloves.ogg', 50, TRUE, -1)
if(ishuman(L))
var/mob/living/carbon/human/H = L
@@ -521,7 +523,7 @@
log_combat(user, L, "stunned")
/obj/item/abductor_baton/proc/SleepAttack(mob/living/L,mob/living/user)
/obj/item/abductor/baton/proc/SleepAttack(mob/living/L,mob/living/user)
if(L.incapacitated(TRUE, TRUE))
if(L.anti_magic_check(FALSE, FALSE, TRUE, 0))
to_chat(user, "<span class='warning'>The specimen's tinfoil protection is interfering with the sleep inducement!</span>")
@@ -531,7 +533,7 @@
return
L.visible_message("<span class='danger'>[user] has induced sleep in [L] with [src]!</span>", \
"<span class='userdanger'>You suddenly feel very drowsy!</span>")
playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1)
playsound(src, 'sound/weapons/egloves.ogg', 50, TRUE, -1)
L.Sleeping(1200)
log_combat(user, L, "put to sleep")
else
@@ -545,13 +547,13 @@
L.visible_message("<span class='danger'>[user] tried to induce sleep in [L] with [src]!</span>", \
"<span class='userdanger'>You suddenly feel drowsy!</span>")
/obj/item/abductor_baton/proc/CuffAttack(mob/living/L,mob/living/user)
/obj/item/abductor/baton/proc/CuffAttack(mob/living/L,mob/living/user)
if(!iscarbon(L))
return
var/mob/living/carbon/C = L
if(!C.handcuffed)
if(C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore())
playsound(loc, 'sound/weapons/cablecuff.ogg', 30, 1, -2)
playsound(src, 'sound/weapons/cablecuff.ogg', 30, TRUE, -2)
C.visible_message("<span class='danger'>[user] begins restraining [C] with [src]!</span>", \
"<span class='userdanger'>[user] begins shaping an energy field around your hands!</span>")
if(do_mob(user, C, 30) && (C.get_num_arms(FALSE) >= 2 || C.get_arm_ignore()))
@@ -565,7 +567,7 @@
else
to_chat(user, "<span class='warning'>[C] doesn't have two hands...</span>")
/obj/item/abductor_baton/proc/ProbeAttack(mob/living/L,mob/living/user)
/obj/item/abductor/baton/proc/ProbeAttack(mob/living/L,mob/living/user)
L.visible_message("<span class='danger'>[user] probes [L] with [src]!</span>", \
"<span class='userdanger'>[user] probes you!</span>")
@@ -610,7 +612,7 @@
S.start()
. = ..()
/obj/item/abductor_baton/examine(mob/user)
/obj/item/abductor/baton/examine(mob/user)
. = ..()
switch(mode)
if(BATON_STUN)
@@ -639,10 +641,44 @@
AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_EARS))
/obj/item/radio/headset/abductor/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/screwdriver))
if(W.tool_behaviour == TOOL_SCREWDRIVER)
return // Stops humans from disassembling abductor headsets.
return ..()
/obj/item/abductor_machine_beacon
name = "machine beacon"
desc = "A beacon designed to instantly tele-construct abductor machinery."
icon = 'icons/obj/abductor.dmi'
icon_state = "beacon"
w_class = WEIGHT_CLASS_TINY
var/obj/machinery/spawned_machine
/obj/item/abductor_machine_beacon/attack_self(mob/user)
..()
user.visible_message("<span class='notice'>[user] places down [src] and activates it.</span>", "<span class='notice'>You place down [src] and activate it.</span>")
user.dropItemToGround(src)
playsound(src, 'sound/machines/terminal_alert.ogg', 50)
addtimer(CALLBACK(src, .proc/try_spawn_machine), 30)
/obj/item/abductor_machine_beacon/proc/try_spawn_machine()
var/viable = FALSE
if(isfloorturf(loc))
var/turf/T = loc
viable = TRUE
for(var/obj/thing in T.contents)
if(thing.density || ismachinery(thing) || isstructure(thing))
viable = FALSE
if(viable)
playsound(src, 'sound/effects/phasein.ogg', 50, TRUE)
var/new_machine = new spawned_machine(loc)
visible_message("<span class='notice'>[new_machine] warps on top of the beacon!</span>")
qdel(src)
else
playsound(src, 'sound/machines/buzz-two.ogg', 50)
/obj/item/abductor_machine_beacon/chem_dispenser
name = "beacon - Reagent Synthesizer"
spawned_machine = /obj/machinery/chem_dispenser/abductor
/obj/item/scalpel/alien
name = "alien scalpel"
@@ -706,11 +742,11 @@
framestackamount = 1
/obj/structure/table_frame/abductor/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/wrench))
if(I.tool_behaviour == TOOL_WRENCH)
to_chat(user, "<span class='notice'>You start disassembling [src]...</span>")
I.play_tool_sound(src)
if(I.use_tool(src, user, 30))
playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1)
playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
for(var/i = 1, i <= framestackamount, i++)
new framestack(get_turf(src))
qdel(src)
@@ -760,7 +796,6 @@
icon = 'icons/obj/abductor.dmi'
icon_state = "bed"
can_buckle = 1
buckle_lying = 1
var/static/list/injected_reagents = list("corazone")
@@ -798,3 +833,11 @@
airlock_type = /obj/machinery/door/airlock/abductor
material_type = /obj/item/stack/sheet/mineral/abductor
noglass = TRUE
/obj/item/clothing/under/abductor
desc = "The most advanced form of jumpsuit known to reality, looks uncomfortable."
name = "alien jumpsuit"
icon_state = "abductor"
item_state = "bl_suit"
armor = list(melee = 0, bullet = 0, laser = 0, energy = 0, bomb = 10, bio = 10, rad = 0, fire = 0, acid = 0)
can_adjust = 0

View File

@@ -1,6 +1,6 @@
/datum/outfit/abductor
name = "Abductor Basic"
uniform = /obj/item/clothing/under/color/grey //they're greys gettit
uniform = /obj/item/clothing/under/abductor
shoes = /obj/item/clothing/shoes/combat
back = /obj/item/storage/backpack
ears = /obj/item/radio/headset/abductor
@@ -34,7 +34,7 @@
name = "Abductor Agent"
head = /obj/item/clothing/head/helmet/abductor
suit = /obj/item/clothing/suit/armor/abductor/vest
suit_store = /obj/item/abductor_baton
suit_store = /obj/item/abductor/baton
belt = /obj/item/storage/belt/military/abductor/full
backpack_contents = list(

View File

@@ -5,18 +5,23 @@
icon_state = "gland"
status = ORGAN_ROBOTIC
beating = TRUE
organ_flags = ORGAN_NO_SPOIL
var/true_name = "baseline placebo referencer"
var/cooldown_low = 300
var/cooldown_high = 300
var/next_activation = 0
var/uses // -1 For infinite
var/human_only = 0
var/active = 0
var/human_only = FALSE
var/active = FALSE
var/mind_control_uses = 1
var/mind_control_duration = 1800
var/active_mind_control = FALSE
/obj/item/organ/heart/gland/Initialize()
. = ..()
icon_state = pick(list("health", "spider", "slime", "emp", "species", "egg", "vent", "mindshock", "viral"))
/obj/item/organ/heart/gland/examine(mob/user)
. = ..()
if((user.mind && HAS_TRAIT(user.mind, TRAIT_ABDUCTOR_SCIENTIST_TRAINING)) || isobserver(user))
@@ -55,14 +60,18 @@
active_mind_control = TRUE
message_admins("[key_name(user)] sent an abductor mind control message to [key_name(owner)]: [command]")
update_gland_hud()
var/obj/screen/alert/mind_control/mind_alert = owner.throw_alert("mind_control", /obj/screen/alert/mind_control)
mind_alert.command = command
addtimer(CALLBACK(src, .proc/clear_mind_control), mind_control_duration)
return TRUE
/obj/item/organ/heart/gland/proc/clear_mind_control()
if(!ownerCheck() || !active_mind_control)
return FALSE
to_chat(owner, "<span class='userdanger'>You feel the compulsion fade, and you completely forget about your previous orders.</span>")
to_chat(owner, "<span class='userdanger'>You feel the compulsion fade, and you <i>completely forget</i> about your previous orders.</span>")
owner.clear_alert("mind_control")
active_mind_control = FALSE
return TRUE
/obj/item/organ/heart/gland/Remove(mob/living/carbon/M, special = 0)
active = 0
@@ -98,257 +107,4 @@
active = 0
/obj/item/organ/heart/gland/proc/activate()
return
/obj/item/organ/heart/gland/heals
true_name = "coherency harmonizer"
cooldown_low = 200
cooldown_high = 400
uses = -1
icon_state = "health"
mind_control_uses = 3
mind_control_duration = 3000
/obj/item/organ/heart/gland/heals/activate()
to_chat(owner, "<span class='notice'>You feel curiously revitalized.</span>")
owner.adjustToxLoss(-20, FALSE, TRUE)
owner.heal_bodypart_damage(20, 20, 0, TRUE)
owner.adjustOxyLoss(-20)
/obj/item/organ/heart/gland/slime
true_name = "gastric animation galvanizer"
cooldown_low = 600
cooldown_high = 1200
uses = -1
icon_state = "slime"
mind_control_uses = 1
mind_control_duration = 2400
/obj/item/organ/heart/gland/slime/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE)
..()
owner.faction |= "slime"
owner.grant_language(/datum/language/slime)
/obj/item/organ/heart/gland/slime/activate()
to_chat(owner, "<span class='warning'>You feel nauseated!</span>")
owner.vomit(20)
var/mob/living/simple_animal/slime/Slime = new(get_turf(owner), "grey")
Slime.Friends = list(owner)
Slime.Leader = owner
/obj/item/organ/heart/gland/mindshock
true_name = "neural crosstalk uninhibitor"
cooldown_low = 400
cooldown_high = 700
uses = -1
icon_state = "mindshock"
mind_control_uses = 1
mind_control_duration = 6000
/obj/item/organ/heart/gland/mindshock/activate()
to_chat(owner, "<span class='notice'>You get a headache.</span>")
var/turf/T = get_turf(owner)
for(var/mob/living/carbon/H in orange(4,T))
if(H == owner)
continue
switch(pick(1,3))
if(1)
to_chat(H, "<span class='userdanger'>You hear a loud buzz in your head, silencing your thoughts!</span>")
H.Stun(50)
if(2)
to_chat(H, "<span class='warning'>You hear an annoying buzz in your head.</span>")
H.confused += 15
H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10, 160)
if(3)
H.hallucination += 60
/obj/item/organ/heart/gland/pop
true_name = "anthropmorphic translocator"
cooldown_low = 900
cooldown_high = 1800
uses = -1
human_only = TRUE
icon_state = "species"
mind_control_uses = 5
mind_control_duration = 300
/obj/item/organ/heart/gland/pop/activate()
to_chat(owner, "<span class='notice'>You feel unlike yourself.</span>")
randomize_human(owner)
var/species = pick(list(/datum/species/human, /datum/species/lizard, /datum/species/insect, /datum/species/fly))
owner.set_species(species)
/obj/item/organ/heart/gland/ventcrawling
true_name = "pliant cartilage enabler"
cooldown_low = 1800
cooldown_high = 2400
uses = 1
icon_state = "vent"
mind_control_uses = 4
mind_control_duration = 1800
/obj/item/organ/heart/gland/ventcrawling/activate()
to_chat(owner, "<span class='notice'>You feel very stretchy.</span>")
owner.ventcrawler = VENTCRAWLER_ALWAYS
/obj/item/organ/heart/gland/viral
true_name = "contamination incubator"
cooldown_low = 1800
cooldown_high = 2400
uses = 1
icon_state = "viral"
mind_control_uses = 1
mind_control_duration = 1800
/obj/item/organ/heart/gland/viral/activate()
to_chat(owner, "<span class='warning'>You feel sick.</span>")
var/datum/disease/advance/A = random_virus(pick(2,6),6)
A.carrier = TRUE
owner.ForceContractDisease(A, FALSE, TRUE)
/obj/item/organ/heart/gland/viral/proc/random_virus(max_symptoms, max_level)
if(max_symptoms > VIRUS_SYMPTOM_LIMIT)
max_symptoms = VIRUS_SYMPTOM_LIMIT
var/datum/disease/advance/A = new /datum/disease/advance()
var/list/datum/symptom/possible_symptoms = list()
for(var/symptom in subtypesof(/datum/symptom))
var/datum/symptom/S = symptom
if(initial(S.level) > max_level)
continue
if(initial(S.level) <= 0) //unobtainable symptoms
continue
possible_symptoms += S
for(var/i in 1 to max_symptoms)
var/datum/symptom/chosen_symptom = pick_n_take(possible_symptoms)
if(chosen_symptom)
var/datum/symptom/S = new chosen_symptom
A.symptoms += S
A.Refresh() //just in case someone already made and named the same disease
return A
/obj/item/organ/heart/gland/trauma
true_name = "white matter randomiser"
cooldown_low = 800
cooldown_high = 1200
uses = 5
icon_state = "emp"
mind_control_uses = 3
mind_control_duration = 1800
/obj/item/organ/heart/gland/trauma/activate()
to_chat(owner, "<span class='warning'>You feel a spike of pain in your head.</span>")
if(prob(33))
owner.gain_trauma_type(BRAIN_TRAUMA_SPECIAL, rand(TRAUMA_RESILIENCE_BASIC, TRAUMA_RESILIENCE_LOBOTOMY))
else
if(prob(20))
owner.gain_trauma_type(BRAIN_TRAUMA_SEVERE, rand(TRAUMA_RESILIENCE_BASIC, TRAUMA_RESILIENCE_LOBOTOMY))
else
owner.gain_trauma_type(BRAIN_TRAUMA_MILD, rand(TRAUMA_RESILIENCE_BASIC, TRAUMA_RESILIENCE_LOBOTOMY))
/obj/item/organ/heart/gland/spiderman
true_name = "araneae cloister accelerator"
cooldown_low = 450
cooldown_high = 900
uses = -1
icon_state = "spider"
mind_control_uses = 2
mind_control_duration = 2400
/obj/item/organ/heart/gland/spiderman/activate()
to_chat(owner, "<span class='warning'>You feel something crawling in your skin.</span>")
owner.faction |= "spiders"
var/obj/structure/spider/spiderling/S = new(owner.drop_location())
S.directive = "Protect your nest inside [owner.real_name]."
/obj/item/organ/heart/gland/egg
true_name = "roe/enzymatic synthesizer"
cooldown_low = 300
cooldown_high = 400
uses = -1
icon_state = "egg"
lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi'
mind_control_uses = 2
mind_control_duration = 1800
/obj/item/organ/heart/gland/egg/activate()
owner.visible_message("<span class='alertalien'>[owner] [pick(EGG_LAYING_MESSAGES)]</span>")
var/turf/T = owner.drop_location()
new /obj/item/reagent_containers/food/snacks/egg/gland(T)
/obj/item/organ/heart/gland/electric
true_name = "electron accumulator/discharger"
cooldown_low = 800
cooldown_high = 1200
uses = -1
mind_control_uses = 2
mind_control_duration = 900
/obj/item/organ/heart/gland/electric/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE)
..()
ADD_TRAIT(owner, TRAIT_SHOCKIMMUNE, ORGAN_TRAIT)
/obj/item/organ/heart/gland/electric/Remove(mob/living/carbon/M, special = 0)
REMOVE_TRAIT(owner, TRAIT_SHOCKIMMUNE, ORGAN_TRAIT)
..()
/obj/item/organ/heart/gland/electric/activate()
owner.visible_message("<span class='danger'>[owner]'s skin starts emitting electric arcs!</span>",\
"<span class='warning'>You feel electric energy building up inside you!</span>")
playsound(get_turf(owner), "sparks", 100, 1, -1)
addtimer(CALLBACK(src, .proc/zap), rand(30, 100))
/obj/item/organ/heart/gland/electric/proc/zap()
tesla_zap(owner, 4, 8000, TESLA_MOB_DAMAGE | TESLA_OBJ_DAMAGE | TESLA_MOB_STUN)
playsound(get_turf(owner), 'sound/magic/lightningshock.ogg', 50, 1)
/obj/item/organ/heart/gland/chem
true_name = "intrinsic pharma-provider"
cooldown_low = 50
cooldown_high = 50
uses = -1
mind_control_uses = 3
mind_control_duration = 1200
var/list/possible_reagents = list()
/obj/item/organ/heart/gland/chem/Initialize()
..()
for(var/X in subtypesof(/datum/reagent/drug))
var/datum/reagent/R = X
possible_reagents += initial(R.id)
for(var/X in subtypesof(/datum/reagent/medicine))
var/datum/reagent/R = X
possible_reagents += initial(R.id)
for(var/X in typesof(/datum/reagent/toxin))
var/datum/reagent/R = X
possible_reagents += initial(R.id)
/obj/item/organ/heart/gland/chem/activate()
var/chem_to_add = pick(possible_reagents)
owner.reagents.add_reagent(chem_to_add, 2)
owner.adjustToxLoss(-2, TRUE, TRUE)
..()
/obj/item/organ/heart/gland/plasma
true_name = "effluvium sanguine-synonym emitter"
cooldown_low = 1200
cooldown_high = 1800
uses = -1
mind_control_uses = 1
mind_control_duration = 800
/obj/item/organ/heart/gland/plasma/activate()
to_chat(owner, "<span class='warning'>You feel bloated.</span>")
addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, owner, "<span class='userdanger'>A massive stomachache overcomes you.</span>"), 150)
addtimer(CALLBACK(src, .proc/vomit_plasma), 200)
/obj/item/organ/heart/gland/plasma/proc/vomit_plasma()
if(!owner)
return
owner.visible_message("<span class='danger'>[owner] vomits a cloud of plasma!</span>")
var/turf/open/T = get_turf(owner)
if(istype(T))
T.atmos_spawn_air("plasma=50;TEMP=[T20C]")
owner.vomit()
return

View File

@@ -0,0 +1,19 @@
/obj/item/organ/heart/gland/access
true_name = "anagraphic electro-scrambler"
cooldown_low = 600
cooldown_high = 1200
uses = 1
icon_state = "mindshock"
mind_control_uses = 3
mind_control_duration = 900
/obj/item/organ/heart/gland/access/activate()
to_chat(owner, "<span class='notice'>You feel like a VIP for some reason.</span>")
RegisterSignal(owner, COMSIG_MOB_ALLOWED, .proc/free_access)
/obj/item/organ/heart/gland/access/proc/free_access(datum/source, obj/O)
return TRUE
/obj/item/organ/heart/gland/access/Remove(mob/living/carbon/M, special = 0)
UnregisterSignal(owner, COMSIG_MOB_ALLOWED)
..()

View File

@@ -0,0 +1,18 @@
/obj/item/organ/heart/gland/blood
true_name = "pseudonuclear hemo-destabilizer"
cooldown_low = 1200
cooldown_high = 1800
uses = -1
icon_state = "egg"
lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi'
mind_control_uses = 3
mind_control_duration = 1500
/obj/item/organ/heart/gland/blood/activate()
if(!ishuman(owner) || !owner.dna.species)
return
var/mob/living/carbon/human/H = owner
var/datum/species/species = H.dna.species
to_chat(H, "<span class='warning'>You feel your blood heat up for a moment.</span>")
species.exotic_blood = get_random_reagent_id()

View File

@@ -0,0 +1,20 @@
/obj/item/organ/heart/gland/chem
true_name = "intrinsic pharma-provider"
cooldown_low = 50
cooldown_high = 50
uses = -1
icon_state = "viral"
mind_control_uses = 3
mind_control_duration = 1200
var/list/possible_reagents = list()
/obj/item/organ/heart/gland/chem/Initialize()
. = ..()
for(var/R in subtypesof(/datum/reagent/drug) + subtypesof(/datum/reagent/medicine) + typesof(/datum/reagent/toxin))
possible_reagents += R
/obj/item/organ/heart/gland/chem/activate()
var/chem_to_add = pick(possible_reagents)
owner.reagents.add_reagent(chem_to_add, 2)
owner.adjustToxLoss(-5, TRUE, TRUE)
..()

View File

@@ -0,0 +1,15 @@
/obj/item/organ/heart/gland/egg
true_name = "roe/enzymatic synthesizer"
cooldown_low = 300
cooldown_high = 400
uses = -1
icon_state = "egg"
lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi'
mind_control_uses = 2
mind_control_duration = 1800
/obj/item/organ/heart/gland/egg/activate()
owner.visible_message("<span class='alertalien'>[owner] [pick(EGG_LAYING_MESSAGES)]</span>")
var/turf/T = owner.drop_location()
new /obj/item/reagent_containers/food/snacks/egg/gland(T)

View File

@@ -0,0 +1,26 @@
/obj/item/organ/heart/gland/electric
true_name = "electron accumulator/discharger"
cooldown_low = 800
cooldown_high = 1200
icon_state = "species"
uses = -1
mind_control_uses = 2
mind_control_duration = 900
/obj/item/organ/heart/gland/electric/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE)
..()
ADD_TRAIT(owner, TRAIT_SHOCKIMMUNE, "abductor_gland")
/obj/item/organ/heart/gland/electric/Remove(mob/living/carbon/M, special = 0)
REMOVE_TRAIT(owner, TRAIT_SHOCKIMMUNE, "abductor_gland")
..()
/obj/item/organ/heart/gland/electric/activate()
owner.visible_message("<span class='danger'>[owner]'s skin starts emitting electric arcs!</span>",\
"<span class='warning'>You feel electric energy building up inside you!</span>")
playsound(get_turf(owner), "sparks", 100, TRUE, -1)
addtimer(CALLBACK(src, .proc/zap), rand(30, 100))
/obj/item/organ/heart/gland/electric/proc/zap()
tesla_zap(owner, 4, 8000, TESLA_MOB_DAMAGE | TESLA_OBJ_DAMAGE | TESLA_MOB_STUN)
playsound(get_turf(owner), 'sound/magic/lightningshock.ogg', 50, TRUE)

View File

@@ -0,0 +1,178 @@
/obj/item/organ/heart/gland/heal
true_name = "organic replicator"
cooldown_low = 200
cooldown_high = 400
uses = -1
human_only = TRUE
icon_state = "health"
mind_control_uses = 3
mind_control_duration = 3000
/obj/item/organ/heart/gland/heal/activate()
if(!(owner.mob_biotypes & MOB_ORGANIC))
return
for(var/organ in owner.internal_organs)
if(istype(organ, /obj/item/organ/cyberimp))
reject_implant(organ)
return
var/obj/item/organ/liver/liver = owner.getorganslot(ORGAN_SLOT_LIVER)
if((!liver/* && !HAS_TRAIT(owner, TRAIT_NOMETABOLISM)*/) || (liver && ((liver.damage > (liver.maxHealth / 2)) || (istype(liver, /obj/item/organ/liver/cybernetic)))))
replace_liver(liver)
return
var/obj/item/organ/lungs/lungs = owner.getorganslot(ORGAN_SLOT_LUNGS)
if((!lungs && !HAS_TRAIT(owner, TRAIT_NOBREATH)) || (lungs && (istype(lungs, /obj/item/organ/lungs/cybernetic))))
replace_lungs(lungs)
return
var/obj/item/organ/eyes/eyes = owner.getorganslot(ORGAN_SLOT_EYES)
if(!eyes || (eyes && ((HAS_TRAIT_FROM(owner, TRAIT_NEARSIGHT, EYE_DAMAGE)) || (HAS_TRAIT_FROM(owner, TRAIT_BLIND, EYE_DAMAGE)) || (istype(eyes, /obj/item/organ/eyes/robotic)))))
replace_eyes(eyes)
return
var/obj/item/bodypart/limb
var/list/limb_list = list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
for(var/zone in limb_list)
limb = owner.get_bodypart(zone)
if(!limb)
replace_limb(zone)
return
if((limb.get_damage() >= (limb.max_damage / 2)) || (limb.status == BODYPART_ROBOTIC))
replace_limb(zone, limb)
return
if(owner.getToxLoss() > 40)
replace_blood()
return
var/tox_amount = 0
for(var/datum/reagent/toxin/T in owner.reagents.reagent_list)
tox_amount += owner.reagents.get_reagent_amount(T.type)
if(tox_amount > 10)
replace_blood()
return
if(owner.blood_volume < BLOOD_VOLUME_OKAY)
owner.blood_volume = BLOOD_VOLUME_NORMAL
to_chat(owner, "<span class='warning'>You feel your blood pulsing within you.</span>")
return
var/obj/item/bodypart/chest/chest = owner.get_bodypart(BODY_ZONE_CHEST)
if((chest.get_damage() >= (chest.max_damage / 4)) || (chest.status == BODYPART_ROBOTIC))
replace_chest(chest)
return
/obj/item/organ/heart/gland/heal/proc/reject_implant(obj/item/organ/cyberimp/implant)
owner.visible_message("<span class='warning'>[owner] vomits up his [implant.name]!</span>", "<span class='userdanger'>You suddenly vomit up your [implant.name]!</span>")
owner.vomit(0, TRUE, TRUE, 1, FALSE, FALSE, FALSE, TRUE)
implant.Remove(owner)
implant.forceMove(owner.drop_location())
/obj/item/organ/heart/gland/heal/proc/replace_liver(obj/item/organ/liver/liver)
if(liver)
owner.visible_message("<span class='warning'>[owner] vomits up his [liver.name]!</span>", "<span class='userdanger'>You suddenly vomit up your [liver.name]!</span>")
owner.vomit(0, TRUE, TRUE, 1, FALSE, FALSE, FALSE, TRUE)
liver.Remove(owner)
liver.forceMove(owner.drop_location())
else
to_chat(owner, "<span class='warning'>You feel a weird rumble in your bowels...</span>")
var/liver_type = /obj/item/organ/liver
if(owner?.dna?.species?.mutantliver)
liver_type = owner.dna.species.mutantliver
var/obj/item/organ/liver/new_liver = new liver_type()
new_liver.Insert(owner)
/obj/item/organ/heart/gland/heal/proc/replace_lungs(obj/item/organ/lungs/lungs)
if(lungs)
owner.visible_message("<span class='warning'>[owner] vomits up his [lungs.name]!</span>", "<span class='userdanger'>You suddenly vomit up your [lungs.name]!</span>")
owner.vomit(0, TRUE, TRUE, 1, FALSE, FALSE, FALSE, TRUE)
lungs.Remove(owner)
lungs.forceMove(owner.drop_location())
else
to_chat(owner, "<span class='warning'>You feel a weird rumble inside your chest...</span>")
var/lung_type = /obj/item/organ/lungs
if(owner.dna.species && owner.dna.species.mutantlungs)
lung_type = owner.dna.species.mutantlungs
var/obj/item/organ/lungs/new_lungs = new lung_type()
new_lungs.Insert(owner)
/obj/item/organ/heart/gland/heal/proc/replace_eyes(obj/item/organ/eyes/eyes)
if(eyes)
owner.visible_message("<span class='warning'>[owner]'s [eyes.name] fall out of their sockets!</span>", "<span class='userdanger'>Your [eyes.name] fall out of their sockets!</span>")
playsound(owner, 'sound/effects/splat.ogg', 50, TRUE)
eyes.Remove(owner)
eyes.forceMove(owner.drop_location())
else
to_chat(owner, "<span class='warning'>You feel a weird rumble behind your eye sockets...</span>")
addtimer(CALLBACK(src, .proc/finish_replace_eyes), rand(100, 200))
/obj/item/organ/heart/gland/heal/proc/finish_replace_eyes()
var/eye_type = /obj/item/organ/eyes
if(owner.dna.species && owner.dna.species.mutanteyes)
eye_type = owner.dna.species.mutanteyes
var/obj/item/organ/eyes/new_eyes = new eye_type()
new_eyes.Insert(owner)
owner.visible_message("<span class='warning'>A pair of new eyes suddenly inflates into [owner]'s eye sockets!</span>", "<span class='userdanger'>A pair of new eyes suddenly inflates into your eye sockets!</span>")
/obj/item/organ/heart/gland/heal/proc/replace_limb(body_zone, obj/item/bodypart/limb)
if(limb)
owner.visible_message("<span class='warning'>[owner]'s [limb.name] suddenly detaches from [owner.p_their()] body!</span>", "<span class='userdanger'>Your [limb.name] suddenly detaches from your body!</span>")
playsound(owner, "desceration", 50, TRUE, -1)
limb.drop_limb()
else
to_chat(owner, "<span class='warning'>You feel a weird tingle in your [parse_zone(body_zone)]... even if you don't have one.</span>")
addtimer(CALLBACK(src, .proc/finish_replace_limb, body_zone), rand(150, 300))
/obj/item/organ/heart/gland/heal/proc/finish_replace_limb(body_zone)
owner.visible_message("<span class='warning'>With a loud snap, [owner]'s [parse_zone(body_zone)] rapidly grows back from [owner.p_their()] body!</span>",
"<span class='userdanger'>With a loud snap, your [parse_zone(body_zone)] rapidly grows back from your body!</span>",
"<span class='warning'>Your hear a loud snap.</span>")
playsound(owner, 'sound/magic/demon_consume.ogg', 50, TRUE)
owner.regenerate_limb(body_zone)
/obj/item/organ/heart/gland/heal/proc/replace_blood()
owner.visible_message("<span class='warning'>[owner] starts vomiting huge amounts of blood!</span>", "<span class='userdanger'>You suddenly start vomiting huge amounts of blood!</span>")
keep_replacing_blood()
/obj/item/organ/heart/gland/heal/proc/keep_replacing_blood()
var/keep_going = FALSE
owner.vomit(0, TRUE, FALSE, 3, FALSE, FALSE, FALSE, TRUE)
owner.Stun(15)
owner.adjustToxLoss(-15, TRUE, TRUE)
owner.blood_volume = min(BLOOD_VOLUME_NORMAL, owner.blood_volume + 20)
if(owner.blood_volume < BLOOD_VOLUME_NORMAL)
keep_going = TRUE
if(owner.getToxLoss())
keep_going = TRUE
for(var/datum/reagent/toxin/R in owner.reagents.reagent_list)
owner.reagents.remove_reagent(R.type, 4)
if(owner.reagents.has_reagent(R.type))
keep_going = TRUE
if(keep_going)
addtimer(CALLBACK(src, .proc/keep_replacing_blood), 30)
/obj/item/organ/heart/gland/heal/proc/replace_chest(obj/item/bodypart/chest/chest)
if(chest.status == BODYPART_ROBOTIC)
owner.visible_message("<span class='warning'>[owner]'s [chest.name] rapidly expels its mechanical components, replacing them with flesh!</span>", "<span class='userdanger'>Your [chest.name] rapidly expels its mechanical components, replacing them with flesh!</span>")
playsound(owner, 'sound/magic/clockwork/anima_fragment_attack.ogg', 50, TRUE)
var/list/dirs = GLOB.alldirs.Copy()
for(var/i in 1 to 3)
var/obj/effect/decal/cleanable/robot_debris/debris = new(get_turf(owner))
debris.streak(dirs)
else
owner.visible_message("<span class='warning'>[owner]'s [chest.name] sheds off its damaged flesh, rapidly replacing it!</span>", "<span class='warning'>Your [chest.name] sheds off its damaged flesh, rapidly replacing it!</span>")
playsound(owner, 'sound/effects/splat.ogg', 50, TRUE)
var/list/dirs = GLOB.alldirs.Copy()
for(var/i in 1 to 3)
var/obj/effect/decal/cleanable/blood/gibs/gibs = new(get_turf(owner))
gibs.streak(dirs)
var/obj/item/bodypart/chest/new_chest = new(null)
new_chest.replace_limb(owner, TRUE)
qdel(chest)

View File

@@ -0,0 +1,64 @@
/obj/item/organ/heart/gland/mindshock
true_name = "neural crosstalk uninhibitor"
cooldown_low = 400
cooldown_high = 700
uses = -1
icon_state = "mindshock"
mind_control_uses = 1
mind_control_duration = 6000
var/list/mob/living/carbon/human/broadcasted_mobs = list()
/obj/item/organ/heart/gland/mindshock/activate()
to_chat(owner, "<span class='notice'>You get a headache.</span>")
var/turf/T = get_turf(owner)
for(var/mob/living/carbon/H in orange(4,T))
if(H == owner)
continue
switch(pick(1,3))
if(1)
to_chat(H, "<span class='userdanger'>You hear a loud buzz in your head, silencing your thoughts!</span>")
H.Stun(50)
if(2)
to_chat(H, "<span class='warning'>You hear an annoying buzz in your head.</span>")
H.confused += 15
H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10, 160)
if(3)
H.hallucination += 60
/obj/item/organ/heart/gland/mindshock/mind_control(command, mob/living/user)
if(!ownerCheck() || !mind_control_uses || active_mind_control)
return FALSE
mind_control_uses--
for(var/mob/M in oview(7, owner))
if(!ishuman(M))
continue
var/mob/living/carbon/human/H = M
if(H.stat)
continue
broadcasted_mobs += H
to_chat(H, "<span class='userdanger'>You suddenly feel an irresistible compulsion to follow an order...</span>")
to_chat(H, "<span class='mind_control'>[command]</span>")
message_admins("[key_name(user)] broadcasted an abductor mind control message from [key_name(owner)] to [key_name(H)]: [command]")
var/obj/screen/alert/mind_control/mind_alert = H.throw_alert("mind_control", /obj/screen/alert/mind_control)
mind_alert.command = command
if(LAZYLEN(broadcasted_mobs))
active_mind_control = TRUE
addtimer(CALLBACK(src, .proc/clear_mind_control), mind_control_duration)
update_gland_hud()
return TRUE
/obj/item/organ/heart/gland/mindshock/clear_mind_control()
if(!active_mind_control || !LAZYLEN(broadcasted_mobs))
return FALSE
for(var/M in broadcasted_mobs)
var/mob/living/carbon/human/H = M
to_chat(H, "<span class='userdanger'>You feel the compulsion fade, and you <i>completely forget</i> about your previous orders.</span>")
H.clear_alert("mind_control")
active_mind_control = FALSE
return TRUE

View File

@@ -0,0 +1,22 @@
/obj/item/organ/heart/gland/plasma
true_name = "effluvium sanguine-synonym emitter"
cooldown_low = 1200
cooldown_high = 1800
icon_state = "slime"
uses = -1
mind_control_uses = 1
mind_control_duration = 800
/obj/item/organ/heart/gland/plasma/activate()
to_chat(owner, "<span class='warning'>You feel bloated.</span>")
addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, owner, "<span class='userdanger'>A massive stomachache overcomes you.</span>"), 150)
addtimer(CALLBACK(src, .proc/vomit_plasma), 200)
/obj/item/organ/heart/gland/plasma/proc/vomit_plasma()
if(!owner)
return
owner.visible_message("<span class='danger'>[owner] vomits a cloud of plasma!</span>")
var/turf/open/T = get_turf(owner)
if(istype(T))
T.atmos_spawn_air("plasma=50;TEMP=[T20C]")
owner.vomit()

View File

@@ -0,0 +1,47 @@
/obj/item/organ/heart/gland/quantum
true_name = "quantic de-observation matrix"
cooldown_low = 150
cooldown_high = 150
uses = -1
icon_state = "emp"
mind_control_uses = 2
mind_control_duration = 1200
var/mob/living/carbon/entangled_mob
/obj/item/organ/heart/gland/quantum/activate()
if(entangled_mob)
return
for(var/mob/M in oview(owner, 7))
if(!iscarbon(M))
continue
entangled_mob = M
addtimer(CALLBACK(src, .proc/quantum_swap), rand(600, 2400))
return
/obj/item/organ/heart/gland/quantum/proc/quantum_swap()
if(QDELETED(entangled_mob))
entangled_mob = null
return
var/turf/T = get_turf(owner)
do_teleport(owner, get_turf(entangled_mob),null,TRUE,channel = TELEPORT_CHANNEL_QUANTUM)
do_teleport(entangled_mob, T,null,TRUE,channel = TELEPORT_CHANNEL_QUANTUM)
to_chat(owner, "<span class='warning'>You suddenly find yourself somewhere else!</span>")
to_chat(entangled_mob, "<span class='warning'>You suddenly find yourself somewhere else!</span>")
if(!active_mind_control) //Do not reset entangled mob while mind control is active
entangled_mob = null
/obj/item/organ/heart/gland/quantum/mind_control(command, mob/living/user)
if(..())
if(entangled_mob && ishuman(entangled_mob) && (entangled_mob.stat < DEAD))
to_chat(entangled_mob, "<span class='userdanger'>You suddenly feel an irresistible compulsion to follow an order...</span>")
to_chat(entangled_mob, "<span class='mind_control'>[command]</span>")
var/obj/screen/alert/mind_control/mind_alert = entangled_mob.throw_alert("mind_control", /obj/screen/alert/mind_control)
mind_alert.command = command
message_admins("[key_name(owner)] mirrored an abductor mind control message to [key_name(entangled_mob)]: [command]")
update_gland_hud()
/obj/item/organ/heart/gland/quantum/clear_mind_control()
if(active_mind_control)
to_chat(entangled_mob, "<span class='userdanger'>You feel the compulsion fade, and you completely forget about your previous orders.</span>")
entangled_mob.clear_alert("mind_control")
..()

View File

@@ -0,0 +1,21 @@
/obj/item/organ/heart/gland/slime
true_name = "gastric animation galvanizer"
cooldown_low = 600
cooldown_high = 1200
uses = -1
icon_state = "slime"
mind_control_uses = 1
mind_control_duration = 2400
/obj/item/organ/heart/gland/slime/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE)
..()
owner.faction |= "slime"
owner.grant_language(/datum/language/slime)
/obj/item/organ/heart/gland/slime/activate()
to_chat(owner, "<span class='warning'>You feel nauseated!</span>")
owner.vomit(20)
var/mob/living/simple_animal/slime/Slime = new(get_turf(owner), "grey")
Slime.Friends = list(owner)
Slime.Leader = owner

View File

@@ -0,0 +1,14 @@
/obj/item/organ/heart/gland/spiderman
true_name = "araneae cloister accelerator"
cooldown_low = 450
cooldown_high = 900
uses = -1
icon_state = "spider"
mind_control_uses = 2
mind_control_duration = 2400
/obj/item/organ/heart/gland/spiderman/activate()
to_chat(owner, "<span class='warning'>You feel something crawling in your skin.</span>")
owner.faction |= "spiders"
var/obj/structure/spider/spiderling/S = new(owner.drop_location())
S.directive = "Protect your nest inside [owner.real_name]."

View File

@@ -0,0 +1,15 @@
/obj/item/organ/heart/gland/transform
true_name = "anthropmorphic transmorphosizer"
cooldown_low = 900
cooldown_high = 1800
uses = -1
human_only = TRUE
icon_state = "species"
mind_control_uses = 7
mind_control_duration = 300
/obj/item/organ/heart/gland/transform/activate()
to_chat(owner, "<span class='notice'>You feel unlike yourself.</span>")
randomize_human(owner)
var/species = pick(list(/datum/species/human, /datum/species/lizard, /datum/species/insect, /datum/species/fly))
owner.set_species(species)

View File

@@ -0,0 +1,18 @@
/obj/item/organ/heart/gland/trauma
true_name = "white matter randomiser"
cooldown_low = 800
cooldown_high = 1200
uses = 5
icon_state = "emp"
mind_control_uses = 3
mind_control_duration = 1800
/obj/item/organ/heart/gland/trauma/activate()
to_chat(owner, "<span class='warning'>You feel a spike of pain in your head.</span>")
if(prob(33))
owner.gain_trauma_type(BRAIN_TRAUMA_SPECIAL, rand(TRAUMA_RESILIENCE_BASIC, TRAUMA_RESILIENCE_LOBOTOMY))
else
if(prob(20))
owner.gain_trauma_type(BRAIN_TRAUMA_SEVERE, rand(TRAUMA_RESILIENCE_BASIC, TRAUMA_RESILIENCE_LOBOTOMY))
else
owner.gain_trauma_type(BRAIN_TRAUMA_MILD, rand(TRAUMA_RESILIENCE_BASIC, TRAUMA_RESILIENCE_LOBOTOMY))

View File

@@ -0,0 +1,12 @@
/obj/item/organ/heart/gland/ventcrawling
true_name = "pliant cartilage enabler"
cooldown_low = 1800
cooldown_high = 2400
uses = 1
icon_state = "vent"
mind_control_uses = 4
mind_control_duration = 1800
/obj/item/organ/heart/gland/ventcrawling/activate()
to_chat(owner, "<span class='notice'>You feel very stretchy.</span>")
owner.ventcrawler = VENTCRAWLER_ALWAYS

View File

@@ -0,0 +1,34 @@
/obj/item/organ/heart/gland/viral
true_name = "contamination incubator"
cooldown_low = 1800
cooldown_high = 2400
uses = 1
icon_state = "viral"
mind_control_uses = 1
mind_control_duration = 1800
/obj/item/organ/heart/gland/viral/activate()
to_chat(owner, "<span class='warning'>You feel sick.</span>")
var/datum/disease/advance/A = random_virus(pick(2,6),6)
A.carrier = TRUE
owner.ForceContractDisease(A, FALSE, TRUE)
/obj/item/organ/heart/gland/viral/proc/random_virus(max_symptoms, max_level)
if(max_symptoms > VIRUS_SYMPTOM_LIMIT)
max_symptoms = VIRUS_SYMPTOM_LIMIT
var/datum/disease/advance/A = new /datum/disease/advance()
var/list/datum/symptom/possible_symptoms = list()
for(var/symptom in subtypesof(/datum/symptom))
var/datum/symptom/S = symptom
if(initial(S.level) > max_level)
continue
if(initial(S.level) <= 0) //unobtainable symptoms
continue
possible_symptoms += S
for(var/i in 1 to max_symptoms)
var/datum/symptom/chosen_symptom = pick_n_take(possible_symptoms)
if(chosen_symptom)
var/datum/symptom/S = new chosen_symptom
A.symptoms += S
A.Refresh() //just in case someone already made and named the same disease
return A

View File

@@ -43,12 +43,15 @@
dat += "Collected Samples : [points] <br>"
dat += "Gear Credits: [credits] <br>"
dat += "<b>Transfer data in exchange for supplies:</b><br>"
dat += "<a href='?src=[REF(src)];dispense=baton'>Advanced Baton</A><br>"
dat += "<a href='?src=[REF(src)];dispense=helmet'>Agent Helmet</A><br>"
dat += "<a href='?src=[REF(src)];dispense=vest'>Agent Vest</A><br>"
dat += "<a href='?src=[REF(src)];dispense=silencer'>Radio Silencer</A><br>"
dat += "<a href='?src=[REF(src)];dispense=tool'>Science Tool</A><br>"
dat += "<a href='?src=[REF(src)];dispense=mind_device'>Mental Interface Device</A><br>"
dat += "<a href='?src=[REF(src)];dispense=baton'>Advanced Baton (2 Credits)</A><br>"
dat += "<a href='?src=[REF(src)];dispense=mind_device'>Mental Interface Device (2 Credits)</A><br>"
dat += "<a href='?src=[REF(src)];dispense=chem_dispenser'>Reagent Synthesizer (2 Credits)</A><br>"
dat += "<a href='?src=[REF(src)];dispense=shrink_ray'>Shrink Ray Blaster (2 Credits)</a><br>"
dat += "<a href='?src=[REF(src)];dispense=helmet'>Agent Helmet (1 Credit)</A><br>"
dat += "<a href='?src=[REF(src)];dispense=vest'>Agent Vest (1 Credit)</A><br>"
dat += "<a href='?src=[REF(src)];dispense=silencer'>Radio Silencer (1 Credit)</A><br>"
dat += "<a href='?src=[REF(src)];dispense=tool'>Science Tool (1 Credit)</A><br>"
dat += "<a href='?src=[REF(src)];dispense=tongue'>Superlingual Matrix (1 Credit)</a><br>"
else
dat += "<span class='bad'>NO EXPERIMENT MACHINE DETECTED</span> <br>"
@@ -101,7 +104,7 @@
else if(href_list["dispense"])
switch(href_list["dispense"])
if("baton")
Dispense(/obj/item/abductor_baton,cost=2)
Dispense(/obj/item/abductor/baton,cost=2)
if("helmet")
Dispense(/obj/item/clothing/head/helmet/abductor)
if("silencer")
@@ -112,6 +115,12 @@
Dispense(/obj/item/clothing/suit/armor/abductor/vest)
if("mind_device")
Dispense(/obj/item/abductor/mind_device,cost=2)
if("chem_dispenser")
Dispense(/obj/item/abductor_machine_beacon/chem_dispenser,cost=2)
if("tongue")
Dispense(/obj/item/organ/tongue/abductor)
if("shrink_ray")
Dispense(/obj/item/gun/energy/shrink_ray,cost=2)
updateUsrDialog()
/obj/machinery/abductor/console/proc/TeleporterRetrieve()
@@ -136,9 +145,9 @@
var/entry_name
if(remote)
entry_name = show_radial_menu(usr, camera.eyeobj, disguises2)
entry_name = show_radial_menu(usr, camera.eyeobj, disguises2, tooltips = TRUE)
else
entry_name = show_radial_menu(usr, src, disguises2)
entry_name = show_radial_menu(usr, src, disguises2, require_near = TRUE, tooltips = TRUE)
var/datum/icon_snapshot/chosen = disguises[entry_name]
if(chosen && vest && (remote || in_range(usr,src)))
@@ -236,4 +245,4 @@
new item(drop_location)
else
say("Insufficent data!")
say("Insufficent data!")

View File

@@ -0,0 +1,84 @@
// Getting Flaws:
//
// Killing crew
//
// Gaining ranks
// * COMPULSION * Things you must do
//
// SELECTIVE: -Gender/BloodType/Job sustains you, but others give you less.
//
// * WEAKNESSES * Things that may harm you
//
// LIGHTS: -Bright light nullifies the Examine benefits of Masquerade.
// -Bright lights disable your healing (including in Torpor)
//
// STAKES: -Stakes kill you immediately.
//
// PAINFUL: -Your feed victims scream, despite being unconscious.
//
// FIRE: -You only need your max health (not x2) in fire damage to die.
//
// CORPSE: -Your Masquerade turns off when unconscious or crit.
//
// FERAL: -
//
// CRAVEN
// BANES //
// These are basically small weaknesses that affect your character in certain circumstances.
// As a rule, they should be specific as to when they happen, or have only some certain
// drawback.
// (core ideas)
// SENSITIVE: You are slightly blinded by bright lights.
// DARKFRIEND: Your automatic healing is at a crawl when in bright light.
// TRADITIONAL: Every five minutes spent outside a coffin lowers your rate of automatic healing.
// CONSUMED: Every five minutes spent outside a coffin increases the rate at which your blood ticks down.
// GOURMAND: Animals and blood bags offer you no nourishment when feeding.
// DEATHMASK: You no longer fake having a heartbeat, and always show up as pale when examined.
// BESTIAL: When your blood is low, you will twitch involuntarily.
// (alternate ideas)
// STERILE: There is a high chance that turning corpses to Bloodsuckers will fail, and further attempts on them by you are impossible.
// FERAL: You're a threat to Vampire-kind: New Bloodsuckers may have an Objective to destroy you.
// UNHOLY: The Chapel, the Bible, and Holy Water set you on fire.
// PARANOID: Only your own claimed coffin counts for healing and banes.
// ON LEVEL-UP:
// Burn Damage increases
// Regen Rate increases
// Max Punch Damage increase
// Reset Level Timer
// Select Bane
// How to Burn Vamps:
// C.adjustFireLoss(20)
// C.adjust_fire_stacks(6)
// C.IgniteMob()
/datum/antagonist/bloodsucker/proc/AssignRandomBane()
return

View File

@@ -0,0 +1,148 @@
// INTEGRATION: Adding Procs and Datums to existing "classes"
/mob/living/proc/AmBloodsucker(falseIfInDisguise=FALSE)
// No Datum
if(!mind || !mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
return FALSE
return TRUE
/mob/living/proc/HaveBloodsuckerBodyparts(var/displaymessage="") // displaymessage can be something such as "rising from death" for Torpid Sleep. givewarningto is the person receiving messages.
if(!getorganslot(ORGAN_SLOT_HEART))
if(displaymessage != "")
to_chat(src, "<span class='warning'>Without a heart, you are incapable of [displaymessage].</span>")
return FALSE
if(!get_bodypart(BODY_ZONE_HEAD))
if(displaymessage != "")
to_chat(src, "<span class='warning'>Without a head, you are incapable of [displaymessage].</span>")
return FALSE
if(!getorgan(/obj/item/organ/brain)) // NOTE: This is mostly just here so we can do one scan for all needed parts when creating a vamp. You probably won't be trying to use powers w/out a brain.
if(displaymessage != "")
to_chat(src, "<span class='warning'>Without a brain, you are incapable of [displaymessage].</span>")
return FALSE
return TRUE
// GET DAMAGE
// Do NOT count the damage on prosthetics for this.
/mob/living/proc/getBruteLoss_nonProsthetic()
return getBruteLoss()
/mob/living/proc/getFireLoss_nonProsthetic()
return getFireLoss()
/mob/living/carbon/getBruteLoss_nonProsthetic()
var/amount = 0
for(var/obj/item/bodypart/BP in bodyparts)
if(BP.status < 2)
amount += BP.brute_dam
return amount
/mob/living/carbon/getFireLoss_nonProsthetic()
var/amount = 0
for(var/obj/item/bodypart/BP in bodyparts)
if(BP.status < 2)
amount += BP.burn_dam
return amount
/mob/living/carbon
// EXAMINING
/mob/living/carbon/human/proc/ReturnVampExamine(var/mob/viewer)
if(!mind || !viewer.mind)
return ""
// Target must be a Vamp
var/datum/antagonist/bloodsucker/bloodsuckerdatum = mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if(!bloodsuckerdatum)
return ""
// Viewer is Target's Vassal?
if(viewer.mind.has_antag_datum(ANTAG_DATUM_VASSAL) in bloodsuckerdatum.vassals)
var/returnString = "\[<span class='warning'><EM>This is your Master!</EM></span>\]"
var/returnIcon = "[icon2html('icons/misc/language.dmi', world, "bloodsucker")]"
returnString += "\n"
return returnIcon + returnString
// Viewer not a Vamp AND not the target's vassal?
if(!viewer.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER) && !(viewer in bloodsuckerdatum.vassals))
return ""
// Default String
var/returnString = "\[<span class='warning'><EM>[bloodsuckerdatum.ReturnFullName(1)]</EM></span>\]"
var/returnIcon = "[icon2html('icons/misc/language.dmi', world, "bloodsucker")]"
// In Disguise (Veil)?
//if (name_override != null)
// returnString += "<span class='suicide'> ([real_name] in disguise!) </span>"
//returnString += "\n" Don't need spacers. Using . += "" in examine.dm does this on its own.
return returnIcon + returnString
/mob/living/carbon/human/proc/ReturnVassalExamine(var/mob/viewer)
if(!mind || !viewer.mind)
return ""
// Am I not even a Vassal? Then I am not marked.
var/datum/antagonist/vassal/vassaldatum = mind.has_antag_datum(ANTAG_DATUM_VASSAL)
if(!vassaldatum)
return ""
// Only Vassals and Bloodsuckers can recognize marks.
if(!viewer.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER) && !viewer.mind.has_antag_datum(ANTAG_DATUM_VASSAL))
return ""
// Default String
var/returnString = "\[<span class='warning'>"
var/returnIcon = ""
// Am I Viewer's Vassal?
if(vassaldatum.master.owner == viewer.mind)
returnString += "This [dna.species.name] bears YOUR mark!"
returnIcon = "[icon2html('icons/misc/mark_icons.dmi', world, "vassal")]"
// Am I someone ELSE'S Vassal?
else if(viewer.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
returnString += "This [dna.species.name] bears the mark of <span class='boldwarning'>[vassaldatum.master.ReturnFullName(vassaldatum.master.owner.current,1)]</span>"
returnIcon = "[icon2html('icons/misc/mark_icons.dmi', world, "vassal_grey")]"
// Are you serving the same master as I am?
else if(viewer.mind.has_antag_datum(ANTAG_DATUM_VASSAL) in vassaldatum.master.vassals)
returnString += "[p_they(TRUE)] bears the mark of your Master"
returnIcon = "[icon2html('icons/misc/mark_icons.dmi', world, "vassal")]"
// You serve a different Master than I do.
else
returnString += "[p_they(TRUE)] bears the mark of another Bloodsucker"
returnIcon = "[icon2html('icons/misc/mark_icons.dmi', world, "vassal_grey")]"
returnString += "</span>\]" // \n" Don't need spacers. Using . += "" in examine.dm does this on its own.
return returnIcon + returnString
// Am I "pale" when examined? Bloodsuckers can trick this.
/mob/living/carbon/proc/ShowAsPaleExamine()
// Normal Creatures:
if(!mind || !mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
return blood_volume < (BLOOD_VOLUME_SAFE * blood_ratio)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if(bloodsuckerdatum.poweron_masquerade)
return FALSE
// If a Bloodsucker is malnourished, AND if his temperature matches his surroundings (aka he hasn't fed recently and looks COLD)...
return blood_volume < (BLOOD_VOLUME_OKAY * blood_ratio) // && !(bodytemperature <= get_temperature() + 2)
/mob/living/carbon/human/ShowAsPaleExamine()
// Check for albino, as per human/examine.dm's check.
if(skin_tone == "albino")
return TRUE
return ..() // Return vamp check
/mob/living/carbon/proc/scan_blood_volume()
// Vamps don't show up normally to scanners unless Masquerade power is on ----> scanner.dm
if(mind)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if (istype(bloodsuckerdatum) && bloodsuckerdatum.poweron_masquerade)
return BLOOD_VOLUME_NORMAL
return blood_volume
/mob/living/proc/IsFrenzied()
return FALSE
/mob/living/proc/StartFrenzy(inTime = 120)
set waitfor = FALSE

View File

@@ -0,0 +1,361 @@
// TO PLUG INTO LIFE:
// Cancel BLOOD life
// Cancel METABOLISM life (or find a way to control what gets digested)
// Create COLDBLOODED trait (thermal homeostasis)
// EXAMINE
//
// Show as dead when...
/datum/antagonist/bloodsucker/proc/LifeTick()// Should probably run from life.dm, same as handle_changeling, but will be an utter pain to move
set waitfor = FALSE // Don't make on_gain() wait for this function to finish. This lets this code run on the side.
var/notice_healing = FALSE
while(owner && !AmFinalDeath()) // owner.has_antag_datum(ANTAG_DATUM_BLOODSUCKER) == src
if(owner.current.stat == CONSCIOUS && !poweron_feed && !HAS_TRAIT(owner.current, TRAIT_DEATHCOMA)) // Deduct Blood
AddBloodVolume(-0.1) // -.15 (before tick went from 10 to 30, but we also charge more for faking life now)
if(HandleHealing(1)) // Heal
if(notice_healing == FALSE && owner.current.blood_volume > 0)
to_chat(owner, "<span class='notice'>The power of your blood begins knitting your wounds...</span>")
notice_healing = TRUE
else if(notice_healing == TRUE)
notice_healing = FALSE // Apply Low Blood Effects
HandleStarving() // Death
HandleDeath() // Standard Update
update_hud()// Daytime Sleep in Coffin
if (SSticker.mode.is_daylight() && !HAS_TRAIT_FROM(owner.current, TRAIT_DEATHCOMA, "bloodsucker"))
if(istype(owner.current.loc, /obj/structure/closet/crate/coffin))
Torpor_Begin()
// Wait before next pass
sleep(10)
FreeAllVassals() // Free my Vassals! (if I haven't yet)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// BLOOD
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/datum/antagonist/bloodsucker/proc/AddBloodVolume(value)
owner.current.blood_volume = CLAMP(owner.current.blood_volume + value, 0, maxBloodVolume)
update_hud()
/datum/antagonist/bloodsucker/proc/HandleFeeding(mob/living/carbon/target, mult=1)
// mult: SILENT feed is 1/3 the amount
var/blood_taken = min(feedAmount, target.blood_volume) * mult // Starts at 15 (now 8 since we doubled the Feed time)
target.blood_volume -= blood_taken
// Simple Animals lose a LOT of blood, and take damage. This is to keep cats, cows, and so forth from giving you insane amounts of blood.
if(!ishuman(target))
target.blood_volume -= (blood_taken / max(target.mob_size, 0.1)) * 3.5 // max() to prevent divide-by-zero
target.apply_damage_type(blood_taken / 3.5) // Don't do too much damage, or else they die and provide no blood nourishment.
if(target.blood_volume <= 0)
target.blood_volume = 0
target.death(0)
///////////
// Shift Body Temp (toward Target's temp, by volume taken)
owner.current.bodytemperature = ((owner.current.blood_volume * owner.current.bodytemperature) + (blood_taken * target.bodytemperature)) / (owner.current.blood_volume + blood_taken)
// our volume * temp, + their volume * temp, / total volume
///////////
// Reduce Value Quantity
if(target.stat == DEAD) // Penalty for Dead Blood
blood_taken /= 3
if(!ishuman(target)) // Penalty for Non-Human Blood
blood_taken /= 2
//if (!iscarbon(target)) // Penalty for Animals (they're junk food)
// Apply to Volume
AddBloodVolume(blood_taken)
// Reagents (NOT Blood!)
if(target.reagents && target.reagents.total_volume)
target.reagents.reaction(owner.current, INGEST, 1) // Run Reaction: what happens when what they have mixes with what I have?
target.reagents.trans_to(owner.current, 1) // Run transfer of 1 unit of reagent from them to me.
// Blood Gulp Sound
owner.current.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, 1) // Play THIS sound for user only. The "null" is where turf would go if a location was needed. Null puts it right in their head.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HEALING
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/datum/antagonist/bloodsucker/proc/HandleHealing(mult = 1)
// NOTE: Mult of 0 is just a TEST to see if we are injured and need to go into Torpor!
//It is called from your coffin on close (by you only)
if(poweron_masquerade == TRUE || owner.current.AmStaked())
return FALSE
owner.current.adjustStaminaLoss(-2 + (regenRate * -10) * mult, 0) // Humans lose stamina damage really quickly. Vamps should heal more.
owner.current.adjustCloneLoss(-1 * (regenRate * 4) * mult, 0)
owner.current.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1 * (regenRate * 4) * mult) //adjustBrainLoss(-1 * (regenRate * 4) * mult, 0)
// No Bleeding
if(ishuman(owner.current)) //NOTE Current bleeding is horrible, not to count the amount of blood ballistics delete.
var/mob/living/carbon/human/H = owner.current
H.bleed_rate = 0
if(iscarbon(owner.current)) // Damage Heal: Do I have damage to ANY bodypart?
var/mob/living/carbon/C = owner.current
var/costMult = 1 // Coffin makes it cheaper
var/fireheal = 0 // BURN: Heal in Coffin while Fakedeath, or when damage above maxhealth (you can never fully heal fire)
var/amInCoffinWhileTorpor = istype(C.loc, /obj/structure/closet/crate/coffin) && (mult == 0 || HAS_TRAIT(C, TRAIT_DEATHCOMA)) // Check for mult 0 OR death coma. (mult 0 means we're testing from coffin)
if(amInCoffinWhileTorpor)
mult *= 5 // Increase multiplier if we're sleeping in a coffin.
fireheal = min(C.getFireLoss_nonProsthetic(), regenRate) // NOTE: Burn damage ONLY heals in torpor.
costMult = 0.25
C.ExtinguishMob()
CureDisabilities() // Extinguish Fire
C.remove_all_embedded_objects() // Remove Embedded!
owner.current.regenerate_organs() // Heal Organs (will respawn original eyes etc. but we replace right away, next)
CheckVampOrgans() // Heart, Eyes
else
if(owner.current.blood_volume <= 0) // No Blood? Lower Mult
mult = 0.25
// Crit from burn? Lower damage to maximum allowed.
//if (C.getFireLoss() > owner.current.getMaxHealth())
// fireheal = regenRate / 2
// BRUTE: Always Heal
var/bruteheal = min(C.getBruteLoss_nonProsthetic(), regenRate)
var/toxinheal = min(C.getToxLoss(), regenRate)
// Heal if Damaged
if(bruteheal + fireheal + toxinheal > 0) // Just a check? Don't heal/spend, and return.
if(mult == 0)
return TRUE
// We have damage. Let's heal (one time)
C.adjustBruteLoss(-bruteheal * mult, forced = TRUE)// Heal BRUTE / BURN in random portions throughout the body.
C.adjustFireLoss(-fireheal * mult, forced = TRUE)
C.adjustToxLoss(-toxinheal * mult * 2, forced = TRUE) //Toxin healing because vamps arent immune
//C.heal_overall_damage(bruteheal * mult, fireheal * mult) // REMOVED: We need to FORCE this, because otherwise, vamps won't heal EVER. Swapped to above.
AddBloodVolume((bruteheal * -0.5 + fireheal * -1) / mult * costMult) // Costs blood to heal
return TRUE // Healed! Done for this tick.
if(amInCoffinWhileTorpor) // Limbs? (And I have no other healing)
var/list/missing = owner.current.get_missing_limbs() // Heal Missing
if (missing.len) // Cycle through ALL limbs and regen them!
for (var/targetLimbZone in missing) // 1) Find ONE Limb and regenerate it.
owner.current.regenerate_limb(targetLimbZone, 0) // regenerate_limbs() <--- If you want to EXCLUDE certain parts, do it like this ----> regenerate_limbs(0, list("head"))
var/obj/item/bodypart/L = owner.current.get_bodypart( targetLimbZone ) // 2) Limb returns Damaged
AddBloodVolume(50 * costMult) // Costs blood to heal
L.brute_dam = 60
to_chat(owner.current, "<span class='notice'>Your flesh knits as it regrows [L]!</span>")
playsound(owner.current, 'sound/magic/demon_consume.ogg', 50, 1)
// DONE! After regenerating ANY number of limbs, we stop here.
return TRUE
/*else // REMOVED: For now, let's just leave prosthetics on. Maybe you WANT to be a robovamp.
// Remove Prosthetic/False Limb
for(var/obj/item/bodypart/BP in C.bodyparts)
message_admins("T1: [BP] ")
if (istype(BP) && BP.status == 2)
message_admins("T2: [BP] ")
BP.drop_limb()
return TRUE */
// NOTE: Limbs have a "status", like their hosts "stat". 2 is dead (aka Prosthetic). 1 seems to be idle/alive.*/
return FALSE
/datum/antagonist/bloodsucker/proc/CureDisabilities()
var/mob/living/carbon/C = owner.current
C.cure_blind(list(EYE_DAMAGE))//()
C.cure_nearsighted(EYE_DAMAGE)
C.set_blindness(0) // Added 9/2/19
C.set_blurriness(0) // Added 9/2/19
C.update_tint() // Added 9/2/19
C.update_sight() // Added 9/2/19
for(var/O in C.internal_organs) //owner.current.adjust_eye_damage(-100) // This was removed by TG
var/obj/item/organ/organ = O
organ.setOrganDamage(0)
owner.current.cure_husk()
// I am thirsty for blud!
/datum/antagonist/bloodsucker/proc/HandleStarving()
// High: Faster Healing
// Med: Pale
// Low: Twitch
// V.Low: Blur Vision
// EMPTY: Frenzy!
// BLOOD_VOLUME_GOOD: [336] Pale (handled in bloodsucker_integration.dm
// BLOOD_VOLUME_BAD: [224] Jitter
if(owner.current.blood_volume < BLOOD_VOLUME_BAD && !prob(0.5))
owner.current.Jitter(3)
// BLOOD_VOLUME_SURVIVE: [122] Blur Vision
if(owner.current.blood_volume < BLOOD_VOLUME_BAD / 2)
owner.current.blur_eyes(8 - 8 * (owner.current.blood_volume / BLOOD_VOLUME_BAD))
// Nutrition
owner.current.nutrition = min(owner.current.blood_volume, NUTRITION_LEVEL_FED) // <-- 350 //NUTRITION_LEVEL_FULL
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// DEATH
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/datum/antagonist/bloodsucker/proc/HandleDeath()
// FINAL DEATH
// Fire Damage? (above double health)
if (owner.current.getFireLoss_nonProsthetic() >= owner.current.getMaxHealth() * 2)
FinalDeath()
return
// Staked while "Temp Death" or Asleep
if (owner.current.StakeCanKillMe() && owner.current.AmStaked())
FinalDeath()
return
// Not "Alive"?
if (!owner.current || !isliving(owner.current) || isbrain(owner.current) || !get_turf(owner.current))
FinalDeath()
return
// Missing Brain or Heart?
if (!owner.current.HaveBloodsuckerBodyparts())
FinalDeath()
return
// Disable Powers: Masquerade * NOTE * This should happen as a FLAW!
//if (stat >= UNCONSCIOUS)
// for (var/datum/action/bloodsucker/masquerade/P in powers)
// P.Deactivate()
// TEMP DEATH
var/total_brute = owner.current.getBruteLoss_nonProsthetic()
var/total_burn = owner.current.getFireLoss_nonProsthetic()
var/total_toxloss = owner.current.getToxLoss() //This is neater than just putting it in total_damage
var/total_damage = total_brute + total_burn + total_toxloss
// Died? Convert to Torpor (fake death)
if (owner.current.stat >= DEAD)
Torpor_Begin()
to_chat(owner, "<span class='danger'>Your immortal body will not yet relinquish your soul to the abyss. You enter Torpor.</span>")
if (poweron_masquerade == TRUE)
to_chat(owner, "<span class='warning'>Your wounds will not heal until you disable the <span class='boldnotice'>Masquerade</span> power.</span>")
// End Torpor:
else // No damage, OR toxin healed AND brute healed and NOT in coffin (since you cannot heal burn)
if (total_damage <= 0 || total_toxloss <= 0 && total_brute <= 0 && !istype(owner.current.loc, /obj/structure/closet/crate/coffin))
// Not Daytime, Not in Torpor
if (!SSticker.mode.is_daylight() && HAS_TRAIT_FROM(owner.current, TRAIT_DEATHCOMA, "bloodsucker"))
Torpor_End()
// Fake Unconscious
if (poweron_masquerade == TRUE && total_damage >= owner.current.getMaxHealth() - HEALTH_THRESHOLD_FULLCRIT)
owner.current.Unconscious(20,1)
//HEALTH_THRESHOLD_CRIT 0
//HEALTH_THRESHOLD_FULLCRIT -30
//HEALTH_THRESHOLD_DEAD -100
/datum/antagonist/bloodsucker/proc/Torpor_Begin(amInCoffin=FALSE)
owner.current.stat = UNCONSCIOUS
owner.current.fakedeath("bloodsucker") // Come after UNCONSCIOUS or else it fails
ADD_TRAIT(owner.current, TRAIT_NODEATH, "bloodsucker") // Without this, you'll just keep dying while you recover.
ADD_TRAIT(owner.current, TRAIT_RESISTHIGHPRESSURE, "bloodsucker") // So you can heal in 0 G. otherwise you just...heal forever.
ADD_TRAIT(owner.current, TRAIT_RESISTLOWPRESSURE, "bloodsucker") // So you can heal in 0 G. otherwise you just...heal forever.
// Visuals
owner.current.update_sight()
owner.current.reload_fullscreen()
// Disable ALL Powers
for (var/datum/action/bloodsucker/power in powers)
if (power.active && !power.can_use_in_torpor)
power.DeactivatePower()
/datum/antagonist/bloodsucker/proc/Torpor_End()
owner.current.stat = SOFT_CRIT
owner.current.cure_fakedeath("bloodsucker") // Come after SOFT_CRIT or else it fails
REMOVE_TRAIT(owner.current, TRAIT_NODEATH, "bloodsucker")
REMOVE_TRAIT(owner.current, TRAIT_RESISTHIGHPRESSURE, "bloodsucker")
REMOVE_TRAIT(owner.current, TRAIT_RESISTLOWPRESSURE, "bloodsucker")
to_chat(owner, "<span class='warning'>You have recovered from Torpor.</span>")
/datum/antagonist/proc/AmFinalDeath()
// Standard Antags can be dead OR final death
return owner && (owner.current && owner.current.stat >= DEAD || owner.AmFinalDeath())
/datum/antagonist/bloodsucker/AmFinalDeath()
return owner && owner.AmFinalDeath()
/datum/antagonist/changeling/AmFinalDeath()
return owner && owner.AmFinalDeath()
/datum/mind/proc/AmFinalDeath()
return !current || QDELETED(current) || !isliving(current) || isbrain(current) || !get_turf(current) // NOTE: "isliving()" is not the same as STAT == CONSCIOUS. This is to make sure you're not a BORG (aka silicon)
/datum/antagonist/bloodsucker/proc/FinalDeath()
if(!iscarbon(owner.current)) //Check for non carbons.
owner.current.gib()
return
playsound(get_turf(owner.current), 'sound/effects/tendril_destroyed.ogg', 60, 1)
owner.current.drop_all_held_items()
owner.current.unequip_everything()
var/mob/living/carbon/C = owner.current
C.remove_all_embedded_objects()
// Make me UN-CLONEABLE
owner.current.hellbound = TRUE // This was done during creation, but let's do it again one more time...to make SURE this guy stays dead, but they dont stay dead because brains can be cloned!
// Free my Vassals!
FreeAllVassals()
// Elders get Dusted
if (vamplevel >= 4) // (vamptitle)
owner.current.visible_message("<span class='warning'>[owner.current]'s skin crackles and dries, their skin and bones withering to dust. A hollow cry whips from what is now a sandy pile of remains.</span>", \
"<span class='userdanger'>Your soul escapes your withering body as the abyss welcomes you to your Final Death.</span>", \
"<span class='italics'>You hear a dry, crackling sound.</span>")
owner.current.dust()
// Fledglings get Gibbed
else
owner.current.visible_message("<span class='warning'>[owner.current]'s skin bursts forth in a spray of gore and detritus. A horrible cry echoes from what is now a wet pile of decaying meat.</span>", \
"<span class='userdanger'>Your soul escapes your withering body as the abyss welcomes you to your Final Death.</span>", \
"<span class='italics'>You hear a wet, bursting sound.</span>")
owner.current.gib(TRUE, FALSE, FALSE)//Brain cloning is wierd and allows hellbounds. Lets destroy the brain for safety.
playsound(owner.current.loc, 'sound/effects/tendril_destroyed.ogg', 40, 1)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HUMAN FOOD
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/mob/proc/CheckBloodsuckerEatFood(var/food_nutrition)
if (!isliving(src))
return
var/mob/living/L = src
if (!L.AmBloodsucker())
return
// We're a vamp? Try to eat food...
var/datum/antagonist/bloodsucker/bloodsuckerdatum = mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
bloodsuckerdatum.handle_eat_human_food(food_nutrition)
/datum/antagonist/bloodsucker/proc/handle_eat_human_food(var/food_nutrition) // Called from snacks.dm and drinks.dm
set waitfor = FALSE
if (!owner.current || !iscarbon(owner.current))
return
var/mob/living/carbon/C = owner.current
// Remove Nutrition, Give Bad Food
C.nutrition -= food_nutrition
foodInGut += food_nutrition
// Already ate some bad clams? Then we can back out, because we're already sick from it.
if (foodInGut != food_nutrition)
return
// Haven't eaten, but I'm in a Human Disguise.
else if (poweron_masquerade)
to_chat(C, "<span class='notice'>Your stomach turns, but your \"human disguise\" keeps the food down...for now.</span>")
// Keep looping until we purge. If we have activated our Human Disguise, we ignore the food. But it'll come up eventually...
var/sickphase = 0
while (foodInGut)
sleep(50)
C.adjust_disgust(10 * sickphase)
// Wait an interval...
sleep(50 + 50 * sickphase) // At intervals of 100, 150, and 200. (10 seconds, 15 seconds, and 20 seconds)
// Died? Cancel
if(C.stat == DEAD)
return
// Put up disguise? Then hold off the vomit.
if(poweron_masquerade)
if(sickphase > 0)
to_chat(C, "<span class='notice'>Your stomach settles temporarily. You regain your composure...for now.</span>")
sickphase = 0
continue
switch(sickphase)
if (1)
to_chat(C, "<span class='warning'>You feel unwell. You can taste ash on your tongue.</span>")
C.Stun(10)
if (2)
to_chat(C, "<span class='warning'>Your stomach turns. Whatever you ate tastes of grave dirt and brimstone.</span>")
C.Dizzy(15)
C.Stun(13)
if (3)
to_chat(C, "<span class='warning'>You purge the food of the living from your viscera! You've never felt worse.</span>")
C.vomit(foodInGut * 4, foodInGut * 2, 0) // (var/lost_nutrition = 10, var/blood = 0, var/stun = 1, var/distance = 0, var/message = 1, var/toxic = 0)
C.blood_volume = max(0, C.blood_volume - foodInGut * 2)
C.Stun(30)
//C.Dizzy(50)
foodInGut = 0
sickphase ++

View File

@@ -0,0 +1,351 @@
// Hide a random object somewhere on the station:
// var/turf/targetturf = get_random_station_turf()
// var/turf/targetturf = get_safe_random_station_turf()
/datum/objective/bloodsucker
martyr_compatible = TRUE
// GENERATE!
/datum/objective/bloodsucker/proc/generate_objective()
update_explanation_text()
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// // PROCS // //
/datum/objective/bloodsucker/proc/return_possible_targets()
var/list/possible_targets = list()
// Look at all crew members, and for/loop through.
for(var/datum/mind/possible_target in get_crewmember_minds())
// Check One: Default Valid User
if(possible_target != owner && ishuman(possible_target.current) && possible_target.current.stat != DEAD)// && is_unique_objective(possible_target))
// Check Two: Am Bloodsucker? OR in Bloodsucker list?
if (possible_target.has_antag_datum(ANTAG_DATUM_BLOODSUCKER) || (possible_target in SSticker.mode.bloodsuckers))
continue
else
possible_targets += possible_target
return possible_targets
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/datum/objective/bloodsucker/lair
// EXPLANATION
/datum/objective/bloodsucker/lair/update_explanation_text()
explanation_text = "Create a lair by claiming a coffin, and protect it until the end of the shift"// Make sure to keep it safe!"
// WIN CONDITIONS?
/datum/objective/bloodsucker/lair/check_completion()
var/datum/antagonist/bloodsucker/antagdatum = owner.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if (antagdatum && antagdatum.coffin && antagdatum.lair)
return TRUE
return FALSE
// Space_Station_13_areas.dm <--- all the areas
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Vassal becomes a Head, or part of a department
/datum/objective/bloodsucker/protege
// LOOKUP: /datum/crewmonitor/proc/update_data(z) for .assignment to see how to get a person's PDA.
var/list/roles = list(
"Captain",
"Head of Personnel",
"Research Director",
"Chief Engineer",
"Chief Medical Officer",
"Quartermaster"
)
var/list/departs = list(
"Research Director",
"Chief Engineer",
"Chief Medical Officer",
"Quartermaster"
)
var/target_role // Equals "HEAD" when it's not a department role.
var/department_string
// GENERATE!
/datum/objective/bloodsucker/protege/generate_objective()
target_role = rand(0,2) == 0 ? "HEAD" : pick(departs)
// Heads?
if (target_role == "HEAD")
target_amount = rand(1, round(SSticker.mode.num_players() / 20))
target_amount = CLAMP(target_amount,1,3)
// Department?
else
switch(target_role)
if("Research Director")
department_string = "Science"
if("Chief Engineer")
department_string = "Engineering"
if("Chief Medical Officer")
department_string = "Medical"
if("Quartermaster")
department_string = "Cargo"
target_amount = rand(round(SSticker.mode.num_players() / 20), round(SSticker.mode.num_players() / 10))
target_amount = CLAMP(target_amount, 2, 4)
..()
// EXPLANATION
/datum/objective/bloodsucker/protege/update_explanation_text()
if (target_role == "HEAD")
if (target_amount == 1)
explanation_text = "Guarantee a Vassal ends up as a Department Head or in a Leadership role."
else
explanation_text = "Guarantee [target_amount] Vassals end up as different Leadership or Department Heads."
else
explanation_text = "Have [target_amount] Vassal[target_amount==1?"":"s"] in the [department_string] department."
// WIN CONDITIONS?
/datum/objective/bloodsucker/protege/check_completion()
var/datum/antagonist/bloodsucker/antagdatum = owner.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if (!antagdatum || antagdatum.vassals.len == 0)
return FALSE
// Get list of all jobs that are qualified (for HEAD, this is already done)
var/list/valid_jobs
if (target_role == "HEAD")
valid_jobs = roles
else
valid_jobs = list()
var/list/alljobs = subtypesof(/datum/job) // This is just a list of TYPES, not the actual variables!
for(var/T in alljobs)
var/datum/job/J = SSjob.GetJobType(T) //
if (!istype(J))
continue
// Found a job whose Dept Head matches either list of heads, or this job IS the head
if ((target_role in J.department_head) || target_role == J.title)
valid_jobs += J.title
// Check Vassals, and see if they match
var/objcount = 0
var/list/counted_roles = list() // So you can't have more than one Captain count.
for(var/datum/antagonist/vassal/V in antagdatum.vassals)
if (!V || !V.owner) // Must exist somewhere, and as a vassal.
continue
var/thisRole = "none"
// Mind Assigned
if ((V.owner.assigned_role in valid_jobs) && !(V.owner.assigned_role in counted_roles))
//to_chat(owner, "<span class='userdanger'>PROTEGE OBJECTIVE: (MIND ROLE)</span>")
thisRole = V.owner.assigned_role
// Mob Assigned
else if ((V.owner.current.job in valid_jobs) && !(V.owner.current.job in counted_roles))
//to_chat(owner, "<span class='userdanger'>PROTEGE OBJECTIVE: (MOB JOB)</span>")
thisRole = V.owner.current.job
// PDA Assigned
else if (V.owner.current && ishuman(V.owner.current))
var/mob/living/carbon/human/H = V.owner.current
var/obj/item/card/id/I = H.wear_id ? H.wear_id.GetID() : null
if (I && (I.assignment in valid_jobs) && !(I.assignment in counted_roles))
//to_chat(owner, "<span class='userdanger'>PROTEGE OBJECTIVE: (GET ID)</span>")
thisRole = I.assignment
// NO MATCH
if (thisRole == "none")
continue
// SUCCESS!
objcount ++
if (target_role == "HEAD")
counted_roles += thisRole // Add to list so we don't count it again (but only if it's a Head)
// NOTE!!!!!!!!!!!
// Look for jobs value on mobs! This is assigned at start, but COULD be assigned from HoP?
//
// ALSO - Search through all jobs (look for prefs earlier that look for all jobs, and search through all jobs to see if their head matches the head listed, or it IS the head)
//
// ALSO - registered_account in _vending.dm for banks, and assigning new ones.
//to_chat(antagdatum.owner, "<span class='userdanger'>PROTEGE OBJECTIVE: Final Count: [objcount] of [antagdatum.vassals.len] vassals</span>")
return objcount >= target_amount
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Eat blood from a lot of people
/datum/objective/bloodsucker/gourmand
// HOW: Track each feed (if human). Count victory.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Convert a crewmate
/datum/objective/bloodsucker/embrace
// HOW: Find crewmate. Check if person is a bloodsucker
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Defile a facility with blood
/datum/objective/bloodsucker/desecrate
// Space_Station_13_areas.dm <--- all the areas
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Destroy the Solar Arrays
/datum/objective/bloodsucker/solars
// Space_Station_13_areas.dm <--- all the areas
/datum/objective/bloodsucker/solars/update_explanation_text()
explanation_text = "Prevent all solar arrays on the station from functioning."
/datum/objective/bloodsucker/solars/check_completion()
// Sort through all /obj/machinery/power/solar_control in the station ONLY, and check that they are functioning.
// Make sure that lastgen is 0 or connected_panels.len is 0. Doesnt matter if it's tracking.
for (var/obj/machinery/power/solar_control/SC in SSsun.solars)
// Check On Station:
var/turf/T = get_turf(SC)
if(!T || !is_station_level(T.z)) // <------ Taken from NukeOp
//message_admins("DEBUG A: [SC] not on station!")
continue // Not on station! We don't care about this.
if (SC && SC.lastgen > 0 && SC.connected_panels.len > 0 && SC.connected_tracker)
return FALSE
return TRUE
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Steal hearts. You just really wanna have some hearts.
/datum/objective/bloodsucker/heartthief
// NOTE: Look up /steal in objective.dm for inspiration.
// GENERATE!
/datum/objective/bloodsucker/heartthief/generate_objective()
target_amount = rand(2,3)
update_explanation_text()
//dangerrating += target_amount * 2
// EXPLANATION
/datum/objective/bloodsucker/heartthief/update_explanation_text()
explanation_text = "Steal and keep [target_amount] heart[target_amount == 1 ? "" : "s"]." // TO DO: Limit them to Human Only!
// WIN CONDITIONS?
/datum/objective/bloodsucker/heartthief/check_completion()
// -Must have a body.
if (!owner.current)
return FALSE
// Taken from /steal in objective.dm
var/list/all_items = owner.current.GetAllContents() // Includes items inside other items.
var/itemcount = FALSE
for(var/obj/I in all_items) //Check for items
if(I == /obj/item/organ/heart)
itemcount ++
if (itemcount >= target_amount) // Got the right amount?
return TRUE
return FALSE
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/datum/objective/bloodsucker/survive
martyr_compatible = FALSE
// EXPLANATION
/datum/objective/bloodsucker/survive/update_explanation_text()
explanation_text = "Survive the entire shift without succumbing to Final Death."
// WIN CONDITIONS?
/datum/objective/bloodsucker/survive/check_completion()
// -Must have a body.
if (!owner.current || !isliving(owner.current))
return FALSE
// Dead, without a head or heart? Cya
return owner.current.stat != DEAD// || owner.current.HaveBloodsuckerBodyparts()
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/datum/objective/bloodsucker/vamphunter
// GENERATE!
/datum/objective/bloodsucker/vamphunter/generate_objective()
update_explanation_text()
// EXPLANATION
/datum/objective/bloodsucker/vamphunter/update_explanation_text()
explanation_text = "Destroy all Bloodsuckers on [station_name()]."
// WIN CONDITIONS?
/datum/objective/bloodsucker/vamphunter/check_completion()
for (var/datum/mind/M in SSticker.mode.bloodsuckers)
if (M && M.current && M.current.stat != DEAD && get_turf(M.current))
return FALSE
return TRUE
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/datum/objective/bloodsucker/monsterhunter
// GENERATE!
/datum/objective/bloodsucker/monsterhunter/generate_objective()
update_explanation_text()
// EXPLANATION
/datum/objective/bloodsucker/monsterhunter/update_explanation_text()
explanation_text = "Destroy all monsters on [station_name()]."
// WIN CONDITIONS?
/datum/objective/bloodsucker/monsterhunter/check_completion()
var/list/datum/mind/monsters = list()
monsters += SSticker.mode.bloodsuckers
monsters += SSticker.mode.devils
monsters += SSticker.mode.cult
monsters += SSticker.mode.wizards
monsters += SSticker.mode.apprentices
//monsters += SSticker.mode.servants_of_ratvar
//monsters += SSticker.mode.changelings disabled anyways
for (var/datum/mind/M in monsters)
if (M && M != owner && M.current && M.current.stat != DEAD && get_turf(M.current))
return FALSE
return TRUE
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/datum/objective/bloodsucker/vassal
// GENERATE!
/datum/objective/bloodsucker/vassal/generate_objective()
update_explanation_text()
// EXPLANATION
/datum/objective/bloodsucker/vassal/update_explanation_text()
explanation_text = "Guarantee the success of your Master's mission!"
// WIN CONDITIONS?
/datum/objective/bloodsucker/vassal/check_completion()
var/datum/antagonist/vassal/antag_datum = owner.has_antag_datum(ANTAG_DATUM_VASSAL)
return antag_datum.master && antag_datum.master.owner && antag_datum.master.owner.current && antag_datum.master.owner.current.stat != DEAD

View File

@@ -0,0 +1,284 @@
/datum/action/bloodsucker
name = "Vampiric Gift"
desc = "A vampiric gift."
button_icon = 'icons/mob/actions/bloodsucker.dmi' //This is the file for the BACKGROUND icon
background_icon_state = "vamp_power_off" //And this is the state for the background icon
var/background_icon_state_on = "vamp_power_on" // FULP: Our "ON" icon alternative.
var/background_icon_state_off = "vamp_power_off" // FULP: Our "OFF" icon alternative.
icon_icon = 'icons/mob/actions/bloodsucker.dmi' //This is the file for the ACTION icon
button_icon_state = "power_feed" //And this is the state for the action icon
buttontooltipstyle = "cult"
// Action-Related
//var/amPassive = FALSE // REMOVED: Just made it its own kind. // Am I just "on" at all times? (aka NO ICON)
var/amTargetted = FALSE // Am I asked to choose a target when enabled? (Shows as toggled ON when armed)
var/amToggle = FALSE // Can I be actively turned on and off?
var/amSingleUse = FALSE // Am I removed after a single use?
var/active = FALSE
var/cooldown = 20 // 10 ticks, 1 second.
var/cooldownUntil = 0 // From action.dm: next_use_time = world.time + cooldown_time
// Power-Related
var/level_current = 0 // Can increase to yield new abilities. Each power goes up in strength each Rank.
//var/level_max = 1 //
var/bloodcost = 10
var/needs_button = TRUE // Taken from Changeling - for passive abilities that dont need a button
var/bloodsucker_can_buy = FALSE // Must be a bloodsucker to use this power.
var/warn_constant_cost = FALSE // Some powers charge you for staying on. Masquerade, Cloak, Veil, etc.
var/can_use_in_torpor = FALSE // Most powers don't function if you're in torpor.
var/must_be_capacitated = FALSE // Some powers require you to be standing and ready.
var/can_be_immobilized = FALSE // Brawn can be used when incapacitated/laying if it's because you're being immobilized. NOTE: If must_be_capacitated is FALSE, this is irrelevant.
var/can_be_staked = FALSE // Only Feed can happen with a stake in you.
var/cooldown_static = FALSE // Feed, Masquerade, and One-Shot powers don't improve their cooldown.
//var/not_bloodsucker = FALSE // This goes to Vassals or Hunters, but NOT bloodsuckers.
/datum/action/bloodsucker/New()
if (bloodcost > 0)
desc += "<br><br><b>COST:</b> [bloodcost] Blood" // Modify description to add cost.
if (warn_constant_cost)
desc += "<br><br><i>Your over-time blood consumption increases while [name] is active.</i>"
if (amSingleUse)
desc += "<br><br><i>Useable once per night.</i>"
..()
// NOTES
//
// click.dm <--- Where we can take over mouse clicks
// spells.dm /add_ranged_ability() <--- How we take over the mouse click to use a power on a target.
/datum/action/bloodsucker/Trigger()
// Active? DEACTIVATE AND END!
if (active && CheckCanDeactivate(TRUE))
DeactivatePower()
return
if (!CheckCanPayCost(TRUE) || !CheckCanUse(TRUE))
return
PayCost()
if (amToggle)
active = !active
UpdateButtonIcon()
if (!amToggle || !active)
StartCooldown() // Must come AFTER UpdateButton(), otherwise icon will revert.
ActivatePower() // NOTE: ActivatePower() freezes this power in place until it ends.
if (active) // Did we not manually disable? Handle it here.
DeactivatePower()
if (amSingleUse)
RemoveAfterUse()
/datum/action/bloodsucker/proc/CheckCanPayCost(display_error)
if(!owner || !owner.mind)
return FALSE
// Cooldown?
if (cooldownUntil > world.time)
if (display_error)
to_chat(owner, "[src] is unavailable. Wait [(cooldownUntil - world.time) / 10] seconds.")
return FALSE
// Have enough blood?
var/mob/living/L = owner
if (L.blood_volume < bloodcost)
if (display_error)
to_chat(owner, "<span class='warning'>You need at least [bloodcost] blood to activate [name]</span>")
return FALSE
return TRUE
/datum/action/bloodsucker/proc/CheckCanUse(display_error) // These checks can be scanned every frame while a ranged power is on.
if(!owner || !owner.mind)
return FALSE
// Torpor?
if(!can_use_in_torpor && HAS_TRAIT(owner, TRAIT_DEATHCOMA))
if(display_error)
to_chat(owner, "<span class='warning'>Not while you're in Torpor.</span>")
return FALSE
// Stake?
if(!can_be_staked && owner.AmStaked())
if(display_error)
to_chat(owner, "<span class='warning'>You have a stake in your chest! Your powers are useless.</span>")
return FALSE
// Incap?
if(must_be_capacitated)
var/mob/living/L = owner
if (L.incapacitated(TRUE, TRUE) || L.resting && !can_be_immobilized)
if(display_error)
to_chat(owner, "<span class='warning'>Not while you're incapacitated!</span>")
return FALSE
// Constant Cost (out of blood)
if(warn_constant_cost)
var/mob/living/L = owner
if(L.blood_volume <= 0)
if(display_error)
to_chat(owner, "<span class='warning'>You don't have the blood to upkeep [src].</span>")
return FALSE
return TRUE
/datum/action/bloodsucker/proc/StartCooldown()
set waitfor = FALSE
// Alpha Out
button.color = rgb(128,0,0,128)
button.alpha = 100
// Calculate Cooldown (by power's level)
var/this_cooldown = (cooldown_static || amSingleUse) ? cooldown : max(cooldown / 2, cooldown - (cooldown / 16 * (level_current-1)))
// NOTE: With this formula, you'll hit half cooldown at level 8 for that power.
// Wait for cooldown
cooldownUntil = world.time + this_cooldown
spawn(this_cooldown)
// Alpha In
button.color = rgb(255,255,255,255)
button.alpha = 255
/datum/action/bloodsucker/proc/CheckCanDeactivate(display_error)
return TRUE
/datum/action/bloodsucker/UpdateButtonIcon(force = FALSE)
background_icon_state = active? background_icon_state_on : background_icon_state_off
..()//UpdateButtonIcon()
/datum/action/bloodsucker/proc/PayCost()
// owner for actions is the mob, not mind.
var/mob/living/L = owner
L.blood_volume -= bloodcost
/datum/action/bloodsucker/proc/ActivatePower()
/datum/action/bloodsucker/proc/DeactivatePower(mob/living/user = owner, mob/living/target)
active = FALSE
UpdateButtonIcon()
StartCooldown()
/datum/action/bloodsucker/proc/ContinueActive(mob/living/user, mob/living/target) // Used by loops to make sure this power can stay active.
return active && user && (!warn_constant_cost || user.blood_volume > 0)
/datum/action/bloodsucker/proc/RemoveAfterUse()
// Un-Learn Me! (GO HOME
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if (istype(bloodsuckerdatum))
bloodsuckerdatum.powers -= src
Remove(owner)
/datum/action/bloodsucker/proc/Upgrade()
level_current ++
/////////////////////////////////// PASSIVE POWERS ///////////////////////////////////
// New Type: Passive (Always on, no button)
/datum/action/bloodsucker/passive
/datum/action/bloodsucker/passive/New()
// REMOVED: DO NOTHBING!
..()
// Don't Display Button! (it doesn't do anything anyhow)
button.screen_loc = DEFAULT_BLOODSPELLS
button.moved = DEFAULT_BLOODSPELLS
button.ordered = FALSE
/datum/action/bloodsucker/passive/Destroy()
if(owner)
Remove(owner)
target = null
/////////////////////////////////// TARGETTED POWERS ///////////////////////////////////
/datum/action/bloodsucker/targeted
// NOTE: All Targeted spells are Toggles! We just don't bother checking here.
var/target_range = 99
var/message_Trigger = "Select a target."
var/obj/effect/proc_holder/bloodsucker/bs_proc_holder
var/power_activates_immediately = TRUE // Most powers happen the moment you click. Some, like Mesmerize, require time and shouldn't cost you if they fail.
var/power_in_use = FALSE // Is this power LOCKED due to being used?
/datum/action/bloodsucker/targeted/New(Target)
desc += "<br>\[<i>Targeted Power</i>\]" // Modify description to add notice that this is aimed.
..()
// Create Proc Holder for intercepting clicks
bs_proc_holder = new ()
bs_proc_holder.linked_power = src
// Click power: Begin Aim
/datum/action/bloodsucker/targeted/Trigger()
if(active && CheckCanDeactivate(TRUE))
DeactivateRangedAbility()
DeactivatePower()
return
if(!CheckCanPayCost(TRUE) || !CheckCanUse(TRUE))
return
active = !active
UpdateButtonIcon()
// Create & Link Targeting Proc
var/mob/living/L = owner
if(L.ranged_ability)
L.ranged_ability.remove_ranged_ability()
bs_proc_holder.add_ranged_ability(L)
if(message_Trigger != "")
to_chat(owner, "<span class='announce'>[message_Trigger]</span>")
/datum/action/bloodsucker/targeted/CheckCanUse(display_error)
. = ..()
if(!.)
return
if(!owner.client) // <--- We don't allow non client usage so that using powers like mesmerize will FAIL if you try to use them as ghost. Why? because ranged_abvility in spell.dm
return FALSE // doesn't let you remove powers if you're not there. So, let's just cancel the power entirely.
return TRUE
/datum/action/bloodsucker/targeted/DeactivatePower(mob/living/user = owner, mob/living/target)
// Don't run ..(), we don't want to engage the cooldown until we USE this power!
active = FALSE
UpdateButtonIcon()
/datum/action/bloodsucker/targeted/proc/DeactivateRangedAbility()
// Only Turned off when CLICK is disabled...aka, when you successfully clicked (or
bs_proc_holder.remove_ranged_ability()
// Check if target is VALID (wall, turf, or character?)
/datum/action/bloodsucker/targeted/proc/CheckValidTarget(atom/A)
return FALSE // FALSE targets nothing.
// Check if valid target meets conditions
/datum/action/bloodsucker/targeted/proc/CheckCanTarget(atom/A, display_error)
// Out of Range
if(!(A in view(target_range, owner)))
if(display_error && target_range > 1) // Only warn for range if it's greater than 1. Brawn doesn't need to announce itself.
to_chat(owner, "<span class='warning'>Your target is out of range.</span>")
return FALSE
return istype(A)
// Click Target
/datum/action/bloodsucker/targeted/proc/ClickWithPower(atom/A)
// CANCEL RANGED TARGET check
if(power_in_use || !CheckValidTarget(A))
return FALSE
// Valid? (return true means DON'T cancel power!)
if(!CheckCanPayCost(TRUE) || !CheckCanUse(TRUE) || !CheckCanTarget(A, TRUE))
return TRUE
// Skip this part so we can return TRUE right away.
if(power_activates_immediately)
PowerActivatedSuccessfully() // Mesmerize pays only after success.
power_in_use = TRUE // Lock us into this ability until it successfully fires off. Otherwise, we pay the blood even if we fail.
FireTargetedPower(A) // We use this instead of ActivatePower(), which has no input
power_in_use = FALSE
return TRUE
/datum/action/bloodsucker/targeted/proc/FireTargetedPower(atom/A)
// Like ActivatePower, but specific to Targeted (and takes an atom input). We don't use ActivatePower for targeted.
/datum/action/bloodsucker/targeted/proc/PowerActivatedSuccessfully()
// The power went off! We now pay the cost of the power.
PayCost()
DeactivateRangedAbility()
DeactivatePower()
StartCooldown() // Do AFTER UpdateIcon() inside of DeactivatePower. Otherwise icon just gets wiped.
/datum/action/bloodsucker/targeted/ContinueActive(mob/living/user, mob/living/target) // Used by loops to make sure this power can stay active.
return ..()
// Target Proc Holder
/obj/effect/proc_holder/bloodsucker
var/datum/action/bloodsucker/targeted/linked_power
/obj/effect/proc_holder/bloodsucker/remove_ranged_ability(msg)
..()
linked_power.DeactivatePower()
/obj/effect/proc_holder/bloodsucker/InterceptClickOn(mob/living/caller, params, atom/A)
return linked_power.ClickWithPower(A)

View File

@@ -0,0 +1,195 @@
#define TIME_BLOODSUCKER_NIGHT 900 // 15 minutes
#define TIME_BLOODSUCKER_DAY_WARN 90 // 1.5 minutes
#define TIME_BLOODSUCKER_DAY_FINAL_WARN 25 // 25 sec
#define TIME_BLOODSUCKER_DAY 60 // 1.5 minutes // 10 is a second, 600 is a minute.
#define TIME_BLOODSUCKER_BURN_INTERVAL 40 // 4 sec
// Over Time, tick down toward a "Solar Flare" of UV buffeting the station. This period is harmful to vamps.
/obj/effect/sunlight
//var/amDay = FALSE
var/cancel_me = FALSE
var/amDay = FALSE
var/time_til_cycle = 0
/obj/effect/sunlight/Initialize()
countdown()
hud_tick()
/obj/effect/sunlight/proc/countdown()
set waitfor = FALSE
while(!cancel_me)
time_til_cycle = TIME_BLOODSUCKER_NIGHT
// Part 1: Night (all is well)
while(time_til_cycle > TIME_BLOODSUCKER_DAY_WARN)
sleep(10)
if(cancel_me)
return
//sleep(TIME_BLOODSUCKER_NIGHT - TIME_BLOODSUCKER_DAY_WARN)
warn_daylight(1,"<span class = 'danger'>Solar Flares will bombard the station with dangerous UV in [TIME_BLOODSUCKER_DAY_WARN / 60] minutes. <b>Prepare to seek cover in a coffin or closet.</b></span>") // time2text <-- use Help On
give_home_power() // Give VANISHING ACT power to all vamps with a lair!
// Part 2: Night Ending
while(time_til_cycle > TIME_BLOODSUCKER_DAY_FINAL_WARN)
sleep(10)
if(cancel_me)
return
//sleep(TIME_BLOODSUCKER_DAY_WARN - TIME_BLOODSUCKER_DAY_FINAL_WARN)
message_admins("BLOODSUCKER NOTICE: Daylight beginning in [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds.)")
warn_daylight(2,"<span class = 'userdanger'>Solar Flares are about to bombard the station! You have [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds to find cover!</span>",\
"<span class = 'danger'>In [TIME_BLOODSUCKER_DAY_FINAL_WARN / 10], your master will be at risk of a Solar Flare. Make sure they find cover!</span>")
// (FINAL LIL WARNING)
while(time_til_cycle > 5)
sleep(10)
if (cancel_me)
return
//sleep(TIME_BLOODSUCKER_DAY_FINAL_WARN - 50)
warn_daylight(3,"<span class = 'userdanger'>Seek cover, for Sol rises!</span>")
// Part 3: Night Ending
while (time_til_cycle > 0)
sleep(10)
if (cancel_me)
return
//sleep(50)
warn_daylight(4,"<span class = 'userdanger'>Solar flares bombard the station with deadly UV light!</span><br><span class = ''>Stay in cover for the next [TIME_BLOODSUCKER_DAY / 60] minutes or risk Final Death!</span>",\
"<span class = 'danger'>Solar flares bombard the station with UV light!</span>")
// Part 4: Day
amDay = TRUE
message_admins("BLOODSUCKER NOTICE: Daylight Beginning (Lasts for [TIME_BLOODSUCKER_DAY / 60] minutes.)")
time_til_cycle = TIME_BLOODSUCKER_DAY
sleep(10) // One second grace period.
//var/daylight_time = TIME_BLOODSUCKER_DAY
var/issued_XP = FALSE
while(time_til_cycle > 0)
punish_vamps()
sleep(TIME_BLOODSUCKER_BURN_INTERVAL)
if (cancel_me)
return
//daylight_time -= TIME_BLOODSUCKER_BURN_INTERVAL
// Issue Level Up!
if(!issued_XP && time_til_cycle <= 15)
issued_XP = TRUE
vamps_rank_up()
warn_daylight(5,"<span class = 'announce'>The solar flare has ended, and the daylight danger has passed...for now.</span>",\
"<span class = 'announce'>The solar flare has ended, and the daylight danger has passed...for now.</span>")
amDay = FALSE
day_end() // Remove VANISHING ACT power from all vamps who have it! Clear Warnings (sunlight, locker protection)
message_admins("BLOODSUCKER NOTICE: Daylight Ended. Resetting to Night (Lasts for [TIME_BLOODSUCKER_NIGHT / 60] minutes.)")
/obj/effect/sunlight/proc/hud_tick()
set waitfor = FALSE
while(!cancel_me)
// Update all Bloodsucker sunlight huds
for(var/datum/mind/M in SSticker.mode.bloodsuckers)
if(!istype(M) || !istype(M.current))
continue
var/datum/antagonist/bloodsucker/bloodsuckerdatum = M.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if(istype(bloodsuckerdatum))
bloodsuckerdatum.update_sunlight(max(0, time_til_cycle), amDay) // This pings all HUDs
sleep(10)
time_til_cycle --
/obj/effect/sunlight/proc/warn_daylight(danger_level=0, vampwarn = "", vassalwarn = "")
for(var/datum/mind/M in SSticker.mode.bloodsuckers)
if(!istype(M))
continue
to_chat(M,vampwarn)
if(M.current)
if(danger_level == 1)
M.current.playsound_local(null, 'sound/chatter/griffin_3.ogg', 50 + danger_level, 1)
else if(danger_level == 2)
M.current.playsound_local(null, 'sound/chatter/griffin_5.ogg', 50 + danger_level, 1)
else if(danger_level == 3)
M.current.playsound_local(null, 'sound/effects/alert.ogg', 75, 1)
else if(danger_level == 4)
M.current.playsound_local(null, 'sound/ambience/ambimystery.ogg', 100, 1)
else if(danger_level == 5)
M.current.playsound_local(null, 'sound/spookoween/ghosty_wind.ogg', 90, 1)
if(vassalwarn != "")
for(var/datum/mind/M in SSticker.mode.vassals)
if(!istype(M))
continue
to_chat(M,vassalwarn)
/obj/effect/sunlight/proc/punish_vamps()
// Cycle through all vamp antags and check if they're inside a closet.
for(var/datum/mind/M in SSticker.mode.bloodsuckers)
if(!istype(M) || !istype(M.current))
continue
var/datum/antagonist/bloodsucker/bloodsuckerdatum = M.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if(!istype(bloodsuckerdatum))
continue
// Closets offer SOME protection
if(istype(M.current.loc, /obj/structure))
// Coffins offer the BEST protection
if(istype(M.current.loc, /obj/structure/closet/crate/coffin))
SEND_SIGNAL(M.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/coffinsleep)
continue
else
if(!bloodsuckerdatum.warn_sun_locker)
to_chat(M, "<span class='warning'>Your skin sizzles. The [M.current.loc] doesn't protect well against UV bombardment.</span>")
bloodsuckerdatum.warn_sun_locker = TRUE
M.current.adjustFireLoss(0.5 + bloodsuckerdatum.vamplevel / 2) // M.current.fireloss += 0.5 + bloodsuckerdatum.vamplevel / 2 // Do DIRECT damage. Being spaced was causing this to not occur. setFireLoss(bloodsuckerdatum.vamplevel)
M.current.updatehealth()
SEND_SIGNAL(M.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/daylight_1)
// Out in the Open? Buh Bye
else
if(!bloodsuckerdatum.warn_sun_burn)
if(bloodsuckerdatum.vamplevel > 0)
to_chat(M, "<span class='userdanger'>The solar flare sets your skin ablaze!</span>")
else
to_chat(M, "<span class='userdanger'>The solar flare scalds your neophyte skin!</span>")
bloodsuckerdatum.warn_sun_burn = TRUE
if(M.current.fire_stacks <= 0)
M.current.fire_stacks = 0
if(bloodsuckerdatum.vamplevel > 0)
M.current.adjust_fire_stacks(0.2 + bloodsuckerdatum.vamplevel / 10)
M.current.IgniteMob()
M.current.adjustFireLoss(2 + bloodsuckerdatum.vamplevel) // M.current.fireloss += 2 + bloodsuckerdatum.vamplevel // Do DIRECT damage. Being spaced was causing this to not occur. //setFireLoss(2 + bloodsuckerdatum.vamplevel)
M.current.updatehealth()
SEND_SIGNAL(M.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/daylight_2)
/obj/effect/sunlight/proc/day_end()
for(var/datum/mind/M in SSticker.mode.bloodsuckers)
if(!istype(M) || !istype(M.current))
continue
var/datum/antagonist/bloodsucker/bloodsuckerdatum = M.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if(!istype(bloodsuckerdatum))
continue
// Reset Warnings
bloodsuckerdatum.warn_sun_locker = FALSE
bloodsuckerdatum.warn_sun_burn = FALSE
// Remove Dawn Powers
for(var/datum/action/bloodsucker/P in bloodsuckerdatum.powers)
if(istype(P, /datum/action/bloodsucker/gohome))
bloodsuckerdatum.powers -= P
P.Remove(M.current)
/obj/effect/sunlight/proc/vamps_rank_up()
set waitfor = FALSE
// Cycle through all vamp antags and check if they're inside a closet.
for(var/datum/mind/M in SSticker.mode.bloodsuckers)
if(!istype(M) || !istype(M.current))
continue
var/datum/antagonist/bloodsucker/bloodsuckerdatum = M.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if(istype(bloodsuckerdatum))
bloodsuckerdatum.RankUp() // Rank up! Must still be in a coffin to level!
/obj/effect/sunlight/proc/give_home_power()
// It's late...! Give the "Vanishing Act" gohome power to bloodsuckers.
for(var/datum/mind/M in SSticker.mode.bloodsuckers)
if(!istype(M) || !istype(M.current))
continue
var/datum/antagonist/bloodsucker/bloodsuckerdatum = M.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if(istype(bloodsuckerdatum) && bloodsuckerdatum.lair && !(locate(/datum/action/bloodsucker/gohome) in bloodsuckerdatum.powers))
bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/gohome)

View File

@@ -0,0 +1,116 @@
// For all things visual, such as leveling up
// Look up: _vending.dm proc/ui_interact()
// Malf_Modules.dm proc/use()
/*
/datum/antagonist/bloodsucker/proc/LevelUpMenu()
var/list/dat = list()
dat += "<h3>You have become more ancient.<BR>Direct the path of your blood</h3>"
dat += "<HR>"
// Step One: Decide powers you CAN buy.
for(var/pickedpower in typesof(/datum/action/bloodsucker))
var/obj/effect/proc_holder/spell/bloodsucker/power = pickedpower
// NAME
dat += "<A href='byond://?src=[REF(src)];[module.mod_pick_name]=1'>[power.name]</A>"
// COST
dat += "<td align='right'><b>[power.name]&nbsp;</b><a href='byond://?src=[REF(src)];vend=[REF(R)]'>Vend</a></td>"
dat == "<BR>"
var/datum/browser/popup = new(owner.current, "bloodsuckerrank", "Bloodsucker Rank Up")
popup.set_content(dat.Join())
popup.open()
/datum/antagonist/bloodsucker/Topic(href, href_list)
if(..())
return
*/
// From browser.dm: /datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, var/atom/nref = null)
/*
var/list/dat = list()
dat += "<B>Select use of processing time: (currently #[processing_time] left.)</B><BR>"
dat += "<HR>"
dat += "<B>Install Module:</B><BR>"
dat += "<I>The number afterwards is the amount of processing time it consumes.</I><BR>"
for(var/datum/AI_Module/large/module in possible_modules)
dat += "<A href='byond://?src=[REF(src)];[module.mod_pick_name]=1'>[module.module_name]</A><A href='byond://?src=[REF(src)];showdesc=[module.mod_pick_name]'>\[?\]</A> ([module.cost])<BR>"
for(var/datum/AI_Module/small/module in possible_modules)
dat += "<A href='byond://?src=[REF(src)];[module.mod_pick_name]=1'>[module.module_name]</A><A href='byond://?src=[REF(src)];showdesc=[module.mod_pick_name]'>\[?\]</A> ([module.cost])<BR>"
dat += "<HR>"
if(temp)
dat += "[temp]"
var/datum/browser/popup = new(user, "modpicker", "Malf Module Menu")
popup.set_content(dat.Join())
popup.open()
*/
/*
var/dat = ""
var/datum/bank_account/account
var/mob/living/carbon/human/H
var/obj/item/card/id/C
if(ishuman(user))
H = user
C = H.get_idcard(TRUE)
if(!C)
dat += "<font color = 'red'><h3>No ID Card detected!</h3></font>"
else if (!C.registered_account)
dat += "<font color = 'red'><h3>No account on registered ID card!</h3></font>"
if(onstation && C && C.registered_account)
account = C.registered_account
dat += "<h3>Select an item</h3>"
dat += "<div class='statusDisplay'>"
if(!product_records.len)
dat += "<font color = 'red'>No product loaded!</font>"
else
var/list/display_records = product_records + coin_records
if(extended_inventory)
display_records = product_records + coin_records + hidden_records
dat += "<table>"
for (var/datum/data/vending_product/R in display_records)
var/price_listed = "$[default_price]"
var/is_hidden = hidden_records.Find(R)
if(is_hidden && !extended_inventory)
continue
if(R.custom_price)
price_listed = "$[R.custom_price]"
if(!onstation || account && account.account_job && account.account_job.paycheck_department == payment_department)
price_listed = "FREE"
if(coin_records.Find(R) || is_hidden)
price_listed = "$[R.custom_premium_price ? R.custom_premium_price : extra_price]"
dat += "<tr><td><img src='data:image/jpeg;base64,[GetIconForProduct(R)]'/></td>"
dat += "<td style=\"width: 100%\"><b>[sanitize(R.name)] ([price_listed])</b></td>"
if(R.amount > 0 && ((C && C.registered_account && onstation) || (!onstation && isliving(user))))
dat += "<td align='right'><b>[R.amount]&nbsp;</b><a href='byond://?src=[REF(src)];vend=[REF(R)]'>Vend</a></td>"
else
dat += "<td align='right'><span class='linkOff'>Not&nbsp;Available</span></td>"
dat += "</tr>"
dat += "</table>"
dat += "</div>"
if(onstation && C && C.registered_account)
dat += "<b>Balance: $[account.account_balance]</b>"
if(istype(src, /obj/machinery/vending/snack))
dat += "<h3>Chef's Food Selection</h3>"
dat += "<div class='statusDisplay'>"
for (var/O in dish_quants)
if(dish_quants[O] > 0)
var/N = dish_quants[O]
dat += "<a href='byond://?src=[REF(src)];dispense=[sanitize(O)]'>Dispense</A> "
dat += "<B>[capitalize(O)] ($[default_price]): [N]</B><br>"
dat += "</div>"
var/datum/browser/popup = new(user, "vending", (name))
popup.set_content(dat)
popup.set_title_image(user.browse_rsc_icon(icon, icon_state))
popup.open()
*/

View File

@@ -0,0 +1,765 @@
/datum/team/vampireclan
name = "Clan" // Teravanni,
/datum/antagonist/bloodsucker
name = "Bloodsucker"
roundend_category = "bloodsuckers"
antagpanel_category = "Bloodsucker"
job_rank = ROLE_BLOODSUCKER
// NAME
var/vampname // My Dracula name
var/vamptitle // My Dracula title
var/vampreputation // My "Surname" or description of my deeds
// CLAN
var/datum/team/vampireclan/clan
var/list/datum/antagonist/vassal/vassals = list()// Vassals under my control. Periodically remove the dead ones.
var/datum/mind/creator // Who made me? For both Vassals AND Bloodsuckers (though Master Vamps won't have one)
// POWERS
var/list/datum/action/powers = list()// Purchased powers
var/poweron_feed = FALSE // Am I feeding?
var/poweron_masquerade = FALSE
// STATS
var/vamplevel = 0
var/vamplevel_unspent = 1
var/regenRate = 0.3 // How many points of Brute do I heal per tick?
var/feedAmount = 15 // Amount of blood drawn from a target per tick.
var/maxBloodVolume = 600 // Maximum blood a Vamp can hold via feeding. // BLOOD_VOLUME_NORMAL 550 // BLOOD_VOLUME_SAFE 475 //BLOOD_VOLUME_OKAY 336 //BLOOD_VOLUME_BAD 224 // BLOOD_VOLUME_SURVIVE 122
// OBJECTIVES
var/list/datum/objective/objectives_given = list() // For removal if needed.
var/area/lair
var/obj/structure/closet/crate/coffin
// TRACKING
var/foodInGut = 0 // How much food to throw up later. You shouldn't have eaten that.
var/warn_sun_locker = FALSE // So we only get the locker burn message once per day.
var/warn_sun_burn = FALSE // So we only get the sun burn message once per day.
var/had_toxlover = FALSE
// LISTS
var/static/list/defaultTraits = list (TRAIT_STABLEHEART, TRAIT_NOBREATH, TRAIT_SLEEPIMMUNE, TRAIT_NOCRITDAMAGE, TRAIT_RESISTCOLD, TRAIT_RADIMMUNE, TRAIT_VIRUSIMMUNE, TRAIT_NIGHT_VISION, \
TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT, TRAIT_AGEUSIA, TRAIT_COLDBLOODED, TRAIT_NONATURALHEAL, TRAIT_NOMARROW, TRAIT_NOPULSE, TRAIT_NOCLONE)
// NOTES: TRAIT_AGEUSIA <-- Doesn't like flavors.
// REMOVED: TRAIT_NODEATH
// TO ADD:
//var/static/list/defaultOrgans = list (/obj/item/organ/heart/vampheart,/obj/item/organ/heart/vampeyes)
/datum/antagonist/bloodsucker/on_gain()
SSticker.mode.bloodsuckers |= owner // Add if not already in here (and you might be, if you were picked at round start)
SSticker.mode.check_start_sunlight()// Start Sunlight? (if first Vamp)
SelectFirstName()// Name & Title
SelectTitle(am_fledgling=TRUE) // If I have a creator, then set as Fledgling.
SelectReputation(am_fledgling=TRUE)
AssignStarterPowersAndStats()// Give Powers & Stats
forge_bloodsucker_objectives()// Objectives & Team
update_bloodsucker_icons_added(owner.current, "bloodsucker") // Add Antag HUD
LifeTick() // Run Life Function
. = ..()
/datum/antagonist/bloodsucker/on_removal()
SSticker.mode.bloodsuckers -= owner
SSticker.mode.check_cancel_sunlight()// End Sunlight? (if last Vamp)
ClearAllPowersAndStats()// Clear Powers & Stats
clear_bloodsucker_objectives() // Objectives
update_bloodsucker_icons_removed(owner.current)// Clear Antag HUD
. = ..()
/datum/antagonist/bloodsucker/greet()
var/fullname = ReturnFullName(TRUE)
to_chat(owner, "<span class='userdanger'>You are [fullname], a bloodsucking vampire!</span><br>")
owner.announce_objectives()
to_chat(owner, "<span class='boldannounce'>* You regenerate your health slowly, you're weak to fire, and you depend on blood to survive. Allow your stolen blood to run too low, and you will find yourself at \
risk of being discovered!</span><br>")
//to_chat(owner, "<span class='boldannounce'>As an immortal, your power is linked to your age. The older you grow, the more abilities you will have access to.<span>")
var/vamp_greet
vamp_greet += "<span class='boldannounce'>* Other Bloodsuckers are not necessarily your friends, but your survival may depend on cooperation. Betray them at your own discretion and peril.</span><br>"
vamp_greet += "<span class='boldannounce'><i>* Use \",b\" to speak your ancient Bloodsucker language.</span><br>"
vamp_greet += "<span class='announce'>Bloodsucker Tip: Rest in a <i>Coffin</i> to claim it, and that area, as your lair.</span><br>"
vamp_greet += "<span class='announce'>Bloodsucker Tip: Fear the daylight! Solar flares will bombard the station periodically, and only your coffin can guarantee your safety.</span><br>"
vamp_greet += "<span class='announce'>Bloodsucker Tip: You wont loose blood if you are unconcious or sleeping. Use this to your advantage to conserve blood.</span><br>"
to_chat(owner, vamp_greet)
owner.current.playsound_local(null, 'sound/bloodsucker/BloodsuckerAlert.ogg', 100, FALSE, pressure_affected = FALSE)
antag_memory += "Although you were born a mortal, in un-death you earned the name <b>[fullname]</b>.<br>"
/datum/antagonist/bloodsucker/farewell()
owner.current.visible_message("[owner.current]'s skin flushes with color, their eyes growing glossier. They look...alive.",\
"<span class='userdanger'><FONT size = 3>With a snap, your curse has ended. You are no longer a Bloodsucker. You live once more!</FONT></span>")
// Refill with Blood
owner.current.blood_volume = max(owner.current.blood_volume,BLOOD_VOLUME_SAFE)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/datum/antagonist/bloodsucker/proc/SelectFirstName()
// Names (EVERYONE gets one))
if (owner.current.gender == MALE)
vampname = pick("Desmond","Rudolph","Dracul","Vlad","Pyotr","Gregor","Cristian","Christoff","Marcu","Andrei","Constantin","Gheorghe","Grigore","Ilie","Iacob","Luca","Mihail","Pavel","Vasile","Octavian","Sorin", \
"Sveyn","Aurel","Alexe","Iustin","Theodor","Dimitrie","Octav","Damien","Magnus","Caine","Abel", // Romanian/Ancient
"Lucius","Gaius","Otho","Balbinus","Arcadius","Romanos","Alexios","Vitellius", // Latin
"Melanthus","Teuthras","Orchamus","Amyntor","Axion", // Greek
"Thoth","Thutmose","Osorkon,","Nofret","Minmotu","Khafra", // Egyptian
"Dio")
else
vampname = pick("Islana","Tyrra","Greganna","Pytra","Hilda","Andra","Crina","Viorela","Viorica","Anemona","Camelia","Narcisa","Sorina","Alessia","Sophia","Gladda","Arcana","Morgan","Lasarra","Ioana","Elena", \
"Alina","Rodica","Teodora","Denisa","Mihaela","Svetla","Stefania","Diyana","Kelssa","Lilith", // Romanian/Ancient
"Alexia","Athanasia","Callista","Karena","Nephele","Scylla","Ursa", // Latin
"Alcestis","Damaris","Elisavet","Khthonia","Teodora", // Greek
"Nefret","Ankhesenpep") // Egyptian
/datum/antagonist/bloodsucker/proc/SelectTitle(am_fledgling = 0, forced = FALSE)
// Already have Title
if (!forced && vamptitle != null)
return
// Titles [Master]
if (!am_fledgling)
if (owner.current.gender == MALE)
vamptitle = pick ("Count","Baron","Viscount","Prince","Duke","Tzar","Dreadlord","Lord","Master")
else
vamptitle = pick ("Countess","Baroness","Viscountess","Princess","Duchess","Tzarina","Dreadlady","Lady","Mistress")
to_chat(owner, "<span class='announce'>You have earned a title! You are now known as <i>[ReturnFullName(TRUE)]</i>!</span>")
// Titles [Fledgling]
else
vamptitle = null
/datum/antagonist/bloodsucker/proc/SelectReputation(am_fledgling = 0, forced=FALSE)
// Already have Reputation
if (!forced && vampreputation != null)
return
// Reputations [Master]
if (!am_fledgling)
vampreputation = pick("Butcher","Blood Fiend","Crimson","Red","Black","Terror","Nightman","Feared","Ravenous","Fiend","Malevolent","Wicked","Ancient","Plaguebringer","Sinister","Forgotten","Wretched","Baleful", \
"Inqisitor","Harvester","Reviled","Robust","Betrayer","Destructor","Damned","Accursed","Terrible","Vicious","Profane","Vile","Depraved","Foul","Slayer","Manslayer","Sovereign","Slaughterer", \
"Forsaken","Mad","Dragon","Savage","Villainous","Nefarious","Inquisitor","Marauder","Horrible","Immortal","Undying","Overlord","Corrupt","Hellspawn","Tyrant","Sanguineous")
if (owner.current.gender == MALE)
if (prob(10)) // Gender override
vampreputation = pick("King of the Damned", "Blood King", "Emperor of Blades", "Sinlord", "God-King")
else
if (prob(10)) // Gender override
vampreputation = pick("Queen of the Damned", "Blood Queen", "Empress of Blades", "Sinlady", "God-Queen")
to_chat(owner, "<span class='announce'>You have earned a reputation! You are now known as <i>[ReturnFullName(TRUE)]</i>!</span>")
// Reputations [Fledgling]
else
vampreputation = pick ("Crude","Callow","Unlearned","Neophyte","Novice","Unseasoned","Fledgling","Young","Neonate","Scrapling","Untested","Unproven","Unknown","Newly Risen","Born","Scavenger","Unknowing",\
"Unspoiled","Disgraced","Defrocked","Shamed","Meek","Timid","Broken")//,"Fresh")
/datum/antagonist/bloodsucker/proc/AmFledgling()
return !vamptitle
/datum/antagonist/bloodsucker/proc/ReturnFullName(var/include_rep=0)
var/fullname
// Name First
fullname = (vampname ? vampname : owner.current.name)
// Title
if(vamptitle)
fullname = vamptitle + " " + fullname
// Rep
if(include_rep && vampreputation)
fullname = fullname + " the " + vampreputation
return fullname
/datum/antagonist/bloodsucker/proc/BuyPower(datum/action/bloodsucker/power)//(obj/effect/proc_holder/spell/power)
powers += power
power.Grant(owner.current)// owner.AddSpell(power)
/datum/antagonist/bloodsucker/proc/AssignStarterPowersAndStats()
// Blood/Rank Counter
add_hud()
update_hud(TRUE) // Set blood value, current rank
// Powers
BuyPower(new /datum/action/bloodsucker/feed)
BuyPower(new /datum/action/bloodsucker/masquerade)
BuyPower(new /datum/action/bloodsucker/veil)
// Traits
for (var/T in defaultTraits)
ADD_TRAIT(owner.current, T, "bloodsucker")
if(HAS_TRAIT(owner.current, TRAIT_TOXINLOVER)) //No slime bonuses here, no thank you
had_toxlover = TRUE
REMOVE_TRAIT(owner.current, TRAIT_TOXINLOVER, "species")
// Traits: Species
if(ishuman(owner.current))
var/mob/living/carbon/human/H = owner.current
var/datum/species/S = H.dna.species
S.species_traits |= DRINKSBLOOD
// Clear Addictions
owner.current.reagents.addiction_list = list() // Start over from scratch. Lucky you! At least you're not addicted to blood anymore (if you were)
// Stats
if(ishuman(owner.current))
var/mob/living/carbon/human/H = owner.current
var/datum/species/S = H.dna.species
// Make Changes
S.brutemod *= 0.5 // <-------------------- Start small, but burn mod increases based on rank!
S.coldmod = 0
S.stunmod *= 0.25
S.siemens_coeff *= 0.75 //base electrocution coefficient 1
//S.heatmod += 0.5 // Heat shouldn't affect. Only Fire.
//S.punchstunthreshold = 8 //damage at which punches from this race will stun 9
S.punchdamagelow += 1 //lowest possible punch damage 0
S.punchdamagehigh += 1 //highest possible punch damage 9
// Clown
if(istype(H) && owner.assigned_role == "Clown")
H.dna.remove_mutation(CLOWNMUT)
to_chat(H, "As a vampiric clown, you are no longer a danger to yourself. Your nature is subdued.")
// Physiology
CheckVampOrgans() // Heart, Eyes
// Language
owner.current.grant_language(/datum/language/vampiric)
// Soul
//owner.current.hellbound = TRUE Causes wierd stuff
owner.hasSoul = FALSE // If false, renders the character unable to sell their soul.
owner.isholy = FALSE // is this person a chaplain or admin role allowed to use bibles
// Disabilities
CureDisabilities()
/datum/antagonist/bloodsucker/proc/ClearAllPowersAndStats()
// Blood/Rank Counter
remove_hud()
// Powers
while(powers.len)
var/datum/action/bloodsucker/power = pick(powers)
powers -= power
power.Remove(owner.current)
// owner.RemoveSpell(power)
// Traits
for(var/T in defaultTraits)
REMOVE_TRAIT(owner.current, T, "bloodsucker")
if(had_toxlover == TRUE)
ADD_TRAIT(owner.current, TRAIT_TOXINLOVER, "species")
// Traits: Species
if(ishuman(owner.current))
var/mob/living/carbon/human/H = owner.current
H.set_species(H.dna.species.type)
// Stats
if(ishuman(owner.current))
var/mob/living/carbon/human/H = owner.current
H.set_species(H.dna.species.type)
// Clown
if(istype(H) && owner.assigned_role == "Clown")
H.dna.add_mutation(CLOWNMUT)
// NOTE: Use initial() to return things to default!
// Physiology
owner.current.regenerate_organs()
// Update Health
owner.current.setMaxHealth(100)
// Language
owner.current.remove_language(/datum/language/vampiric)
// Soul
if (owner.soulOwner == owner) // Return soul, if *I* own it.
owner.hasSoul = TRUE
//owner.current.hellbound = FALSE
datum/antagonist/bloodsucker/proc/RankUp()
set waitfor = FALSE
if(!owner || !owner.current)
return
vamplevel_unspent ++
// Spend Rank Immediately?
if(istype(owner.current.loc, /obj/structure/closet/crate/coffin))
SpendRank()
else
to_chat(owner, "<EM><span class='notice'>You have grown more ancient! Sleep in a coffin that you have claimed to thicken your blood and become more powerful.</span></EM>")
if(vamplevel_unspent >= 2)
to_chat(owner, "<span class='announce'>Bloodsucker Tip: If you cannot find or steal a coffin to use, they can be built from wooden planks.</span><br>")
datum/antagonist/bloodsucker/proc/LevelUpPowers()
for(var/datum/action/bloodsucker/power in powers)
power.level_current ++
datum/antagonist/bloodsucker/proc/SpendRank()
set waitfor = FALSE
if (vamplevel_unspent <= 0 || !owner || !owner.current || !owner.current.client)
return
/////////
// Powers
//TODO: Make this into a radial
// Purchase Power Prompt
var/list/options = list() // Taken from gasmask.dm, for Clown Masks.
for(var/pickedpower in typesof(/datum/action/bloodsucker))
var/datum/action/bloodsucker/power = pickedpower
// If I don't own it, and I'm allowed to buy it.
if(!(locate(power) in powers) && initial(power.bloodsucker_can_buy))
options[initial(power.name)] = power // TESTING: After working with TGUI, it seems you can use initial() to view the variables inside a path?
options["\[ Not Now \]"] = null
// Abort?
if(options.len > 1)
var/choice = input(owner.current, "You have the opportunity to grow more ancient. Select a power to advance your Rank.", "Your Blood Thickens...") in options
// Cheat-Safety: Can't keep opening/closing coffin to spam levels
if(vamplevel_unspent <= 0) // Already spent all your points, and tried opening/closing your coffin, pal.
return
if(!istype(owner.current.loc, /obj/structure/closet/crate/coffin))
to_chat(owner.current, "<span class='warning'>Return to your coffin to advance your Rank.</span>")
return
if(!choice || !options[choice] || (locate(options[choice]) in powers)) // ADDED: Check to see if you already have this power, due to window stacking.
to_chat(owner.current, "<span class='notice'>You prevent your blood from thickening just yet, but you may try again later.</span>")
return
// Buy New Powers
var/datum/action/bloodsucker/P = options[choice]
BuyPower(new P)
to_chat(owner.current, "<span class='notice'>You have learned [initial(P.name)]!</span>")
else
to_chat(owner.current, "<span class='notice'>You grow more ancient by the night!</span>")
/////////
// Advance Powers (including new)
LevelUpPowers()
////////
// Advance Stats
if(ishuman(owner.current))
var/mob/living/carbon/human/H = owner.current
var/datum/species/S = H.dna.species
S.burnmod *= 0.025 // Slightly more burn damage
S.stunmod *= 0.95 // Slightly less stun time.
S.punchdamagelow += 0.5
S.punchdamagehigh += 0.5 // NOTE: This affects the hitting power of Brawn.
// More Health
owner.current.setMaxHealth(owner.current.maxHealth + 5)
// Vamp Stats
regenRate += 0.05 // Points of brute healed (starts at 0.3)
feedAmount += 2 // Increase how quickly I munch down vics (15)
maxBloodVolume += 50 // Increase my max blood (600)
/////////
vamplevel ++
vamplevel_unspent --
// Assign True Reputation
if(vamplevel == 4)
SelectReputation(am_fledgling = FALSE, forced = TRUE)
to_chat(owner.current, "<span class='notice'>You are now a rank [vamplevel] Bloodsucker. Your strength, resistence, health, feed rate, regen rate, and maximum blood have all increased!</span>")
to_chat(owner.current, "<span class='notice'>Your existing powers have all ranked up as well!</span>")
update_hud(TRUE)
owner.current.playsound_local(null, 'sound/effects/pope_entry.ogg', 25, 1) // Play THIS sound for user only. The "null" is where turf would go if a location was needed. Null puts it right in their head.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//This handles the application of antag huds/special abilities
/datum/antagonist/bloodsucker/apply_innate_effects(mob/living/mob_override)
return
//This handles the removal of antag huds/special abilities
/datum/antagonist/bloodsucker/remove_innate_effects(mob/living/mob_override)
return
//Assign default team and creates one for one of a kind team antagonists
/datum/antagonist/bloodsucker/create_team(datum/team/team)
return
// Create Objectives
/datum/antagonist/bloodsucker/proc/forge_bloodsucker_objectives() // Fledgling vampires can have different objectives.
// TEAM
//clan = new /datum/team/vampireclan(owner)
// Lair Objective: Create a Lair
var/datum/objective/bloodsucker/lair/lair_objective = new
lair_objective.owner = owner
lair_objective.generate_objective()
add_objective(lair_objective)
// Protege Objective
var/datum/objective/bloodsucker/protege/protege_objective = new
protege_objective.owner = owner
protege_objective.generate_objective()
add_objective(protege_objective)
if (rand(0,1) == 0)
// Heart Thief Objective
var/datum/objective/bloodsucker/heartthief/heartthief_objective = new
heartthief_objective.owner = owner
heartthief_objective.generate_objective()
add_objective(heartthief_objective)
else
// Solars Objective
var/datum/objective/bloodsucker/solars/solars_objective = new
solars_objective.owner = owner
solars_objective.generate_objective()
add_objective(solars_objective)
// Survive Objective
var/datum/objective/bloodsucker/survive/survive_objective = new
survive_objective.owner = owner
survive_objective.generate_objective()
add_objective(survive_objective)
/datum/antagonist/bloodsucker/proc/add_objective(var/datum/objective/O)
objectives += O
objectives_given += O
/datum/antagonist/bloodsucker/proc/clear_bloodsucker_objectives()
var/datum/team/team = get_team()
if(team)
team.remove_member(owner)
for(var/O in objectives_given)
objectives -= O
qdel(O)
objectives_given = list() // Traitors had this, so I added it. Not sure why.
/datum/antagonist/bloodsucker/get_team()
return clan
//Name shown on antag list
/datum/antagonist/bloodsucker/antag_listing_name()
return ..() + "([ReturnFullName(TRUE)])"
//Whatever interesting things happened to the antag admins should know about
//Include additional information about antag in this part
/datum/antagonist/bloodsucker/antag_listing_status()
if (owner && owner.AmFinalDeath())
return "<font color=red>Final Death</font>"
return ..()
//Individual roundend report
/datum/antagonist/bloodsucker/roundend_report()
// Get the default Objectives
var/list/report = list()
// Vamp Name
report += "<br><span class='header'><b>\[[ReturnFullName(TRUE)]\]</b></span>"
// Default Report
report += ..()
// Now list their vassals
if (vassals.len > 0)
report += "<span class='header'>Their Vassals were...</span>"
for (var/datum/antagonist/vassal/V in vassals)
if (V.owner)
var/jobname = V.owner.assigned_role ? "the [V.owner.assigned_role]" : ""
report += "<b>[V.owner.name]</b> [jobname]"
return report.Join("<br>")
//Displayed at the start of roundend_category section, default to roundend_category header
/datum/antagonist/bloodsucker/roundend_report_header()
return "<span class='header'>Lurking in the darkness, the Bloodsuckers were:</span><br>"
// 2019 Breakdown of Bloodsuckers:
// G A M E P L A Y
//
// Bloodsuckers should be inherrently powerful: they never stay dead, and they can hide in plain sight
// better than any other antagonist aboard the station.
//
// However, only elder Bloodsuckers are the powerful creatures of legend. Ranking up as a Bloodsucker
// should impart slight strength and health benefits, as well as powers that can grow over time. But
// their weaknesses should grow as well, and not just to fire.
// A B I L I T I E S
//
// * Bloodsuckers can FEIGN LIFE + DEATH.
// Feigning LIFE:
// - Warms up the body
// - Creates a heartbeat
// - Fake blood amount (550)
// Feign DEATH: Not yet done
// - When lying down or sitting, you appear "dead and lifeless"
// * Bloodsuckers REGENERATE
// - Brute damage heals rather rapidly. Burn damage heals slowly.
// - Healing is reduced when hungry or starved.
// - Burn does not heal when starved. A starved vampire remains "dead" until burns can heal.
// - Bodyparts and organs regrow in Torpor (except for the Heart and Brain).
//
// * Bloodsuckers are IMMORTAL
// - Brute damage cannot destroy them (and it caps very low, so they don't stack too much)
// - Burn damage can only kill them at very high amounts.
// - Removing the head kills the vamp forever.
// - Removing the heart kills the vamp until replaced.
//
// * Bloodsuckers are DEAD
// - They do not breathe.
// - Cold affects them less.
// - They are immune to disease (but can spread it)
// - Food is useless and cause sickness.
// - Nothing can heal the vamp other than his own blood.
//
// * Bloodsuckers are PREDATORS
// - They detect life/heartbeats nearby.
// - They know other predators instantly (Vamps, Werewolves, and alien types) regardless of disguise.
//
//
//
// * Bloodsuckers enter Torpor when DEAD or RESTING in coffin
// - Torpid vampires regenerate their health. Coffins negate cost and speed up the process.
// ** To rest in a coffin, either SLEEP or CLOSE THE LID while you're in it. You will be given a prompt to sleep until healed. Healing in a coffin costs NO blood!
//
// O B J E C T I V E S
//
//
//
//
// 1) GROOM AN HEIR: Find a person with appropriate traits (hair, blood type, gender) to be turned as a Vampire. Before they rise, they must be properly trained. Raise them to great power after their change.
//
// 2) BIBLIOPHILE: Research objects of interest, study items looking for clues of ancient secrets, and hunt down the clues to a Vampiric artifact of horrible power.
//
// 3) CRYPT LORD: Build a terrifying sepulcher to your evil, with servants to lavish upon you in undeath. The trappings of a true crypt lord come at grave cost.
//
// 4) GOURMOND: Oh, to taste all the delicacies the station has to offer! DRINK ## BLOOD FROM VICTIMS WHO LIVE, EAT ## ORGANS FROM VICTIMS WHO LIVE
// Vassals
//
// - Loyal to (and In Love With) Master
// - Master can speak to, summon, or punish his Vassals, even while asleep or torpid.
// - Master may have as many Vassals as Rank
// - Vassals see their Master's speech emboldened!
// Dev Notes
//
// HEALING: Maybe Vamps metabolize specially? Like, they slowly drip their own blood into their system?
// - Give Vamps their own metabolization proc, perhaps?
// ** shadowpeople.dm has rules for healing.
//
// KILLING: It's almost impossible to track who someone has directly killed. But an Admin could be given
// an easy way to whip a Bloodsucker for cruel behavior, as a RP mechanic but not a punishment.
// **
//
// HUNGER: Just keep adjusting mob's nutrition to Blood Hunger level. No need to cancel nutrition from eating.
// ** mob.dm /set_nutrition()
// ** snacks.dm / attack() <-- Stop food from doing anything?
// ORGANS: Liver
// ** life.dm /handle_liver()
//
// CORPSE: Most of these effects likely go away when using "Masquerade" to appear alive.
// ** status_procs.dm /adjust_bodytemperature()
// ** traits.dm /TRAIT_NOBREATH /TRAIT_SLEEPIMMUNE /TRAIT_RESISTCOLD /TRAIT_RADIMMUNE /TRAIT_VIRUSIMMUNE
// * MASQUERADE ON/OFF: /TRAIT_FAKEDEATH (M)
// * /TRAIT_NIGHT_VISION
// * /TRAIT_DEATHCOMA <-- This basically makes you immobile. When using status_procs /fakedeath(), make sure to remove Coma unless we're in Torpor!
// * /TRAIT_NODEATH <--- ???
// ** species /NOZOMBIE
// * ADD: TRAIT_COLDBLOODED <-- add to carbon/life.dm /natural_bodytemperature_stabilization()
//
// MASQUERADE Appear as human!
// ** examine.dm /examine() <-- Change "blood_volume < BLOOD_VOLUME_SAFE" to a new examine
//
// NOSFERATU ** human.add_trait(TRAIT_DISFIGURED, "insert_vamp_datum_here") <-- Makes you UNKNOWN unless your ID says otherwise.
// STEALTH ** human_helpers.dm /get_visible_name() ** shadowpeople.dm has rules for Light.
//
// FRENZY ** living.dm /update_mobility() (USED TO be update_canmove)
//
// PREDATOR See other Vamps!
// * examine.dm /examine()
//
// WEAKNESSES: -Poor mood in Chapel or near Chaplain. -Stamina damage from Bible
//message_admins("DEBUG3: attempt_cast() [name] / [user_C.handcuffed] ")
// TODO:
//
// Death (fire, heart, brain, head)
// Disable Life: BLOOD
// Body Temp
// Spend blood over time (more if imitating life) (none if sleeping in coffin)
// Auto-Heal (brute to 0, fire to 99) (toxin/o2 always 0)
//
// Hud Icons
// UI Blood Counter
// Examine Name (+Masquerade, only "Dead and lifeless" if not standing?)
//
//
// Turn vamps
// Create vassals
//
// FIX LIST
//
/////////////////////////////////////
// HUD! //
/datum/antagonist/bloodsucker/proc/update_bloodsucker_icons_added(datum/mind/m)
var/datum/atom_hud/antag/vamphud = GLOB.huds[ANTAG_HUD_BLOODSUCKER]
vamphud.join_hud(owner.current)
set_antag_hud(owner.current, "bloodsucker") // "bloodsucker"
owner.current.hud_list[ANTAG_HUD].icon = image('icons/mob/hud.dmi', owner.current, "bloodsucker") //Check prepare_huds in mob.dm to see why.
/datum/antagonist/bloodsucker/proc/update_bloodsucker_icons_removed(datum/mind/m)
var/datum/atom_hud/antag/vamphud = GLOB.huds[ANTAG_HUD_BLOODSUCKER]
vamphud.leave_hud(owner.current)
set_antag_hud(owner.current, null)
/datum/atom_hud/antag/bloodsucker // from hud.dm in /datums/ Also see data_huds.dm + antag_hud.dm
/datum/atom_hud/antag/bloodsucker/add_to_single_hud(mob/M, atom/A)
if (!check_valid_hud_user(M,A)) // FULP: This checks if the Mob is a Vassal, and if the Atom is his master OR on his team.
return
..()
/datum/atom_hud/antag/bloodsucker/proc/check_valid_hud_user(mob/M, atom/A) // Remember: A is being added to M's hud. Because M's hud is a /antag/vassal hud, this means M is the vassal here.
// Ghost Admins always see Bloodsuckers/Vassals
if (isobserver(M))
return TRUE
// GOAL: Vassals see their Master and his other Vassals.
// GOAL: Vassals can BE seen by their Bloodsucker and his other Vassals.
// GOAL: Bloodsuckers can see each other.
if (!M || !A || !ismob(A) || !M.mind)// || !A.mind)
return FALSE
var/mob/A_mob = A
if (!A_mob.mind)
return FALSE
// Find Datums: Bloodsucker
var/datum/antagonist/bloodsucker/atom_B = A_mob.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
var/datum/antagonist/bloodsucker/mob_B = M.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
// Check 1) Are we both Bloodsuckers?
if (atom_B && mob_B)
return TRUE
// Find Datums: Vassal
var/datum/antagonist/vassal/atom_V = A_mob.mind.has_antag_datum(ANTAG_DATUM_VASSAL)
var/datum/antagonist/vassal/mob_V = M.mind.has_antag_datum(ANTAG_DATUM_VASSAL)
// Check 2) If they are a BLOODSUCKER, then are they my Master?
if (mob_V && atom_B == mob_V.master)
return TRUE // SUCCESS!
// Check 3) If I am a BLOODSUCKER, then are they my Vassal?
if (mob_B && atom_V && (atom_V in mob_B.vassals))
return TRUE // SUCCESS!
// Check 4) If we are both VASSAL, then do we have the same master?
if (atom_V && mob_V && atom_V.master == mob_V.master)
return TRUE // SUCCESS!
return FALSE
/////////////////////////////////////
// BLOOD COUNTER & RANK MARKER ! //
#define ui_sunlight_display "WEST:6,CENTER-0:0" // 6 pixels to the right, zero tiles & 5 pixels DOWN.
#define ui_blood_display "WEST:6,CENTER-1:0" // 1 tile down
#define ui_vamprank_display "WEST:6,CENTER-2:-5" // 2 tiles down
/datum/hud
var/obj/screen/bloodsucker/blood_counter/blood_display
var/obj/screen/bloodsucker/rank_counter/vamprank_display
var/obj/screen/bloodsucker/sunlight_counter/sunlight_display
/datum/antagonist/bloodsucker/proc/add_hud()
return
/datum/antagonist/bloodsucker/proc/remove_hud()
// No Hud? Get out.
if (!owner.current.hud_used)
return
owner.current.hud_used.blood_display.invisibility = INVISIBILITY_ABSTRACT
owner.current.hud_used.vamprank_display.invisibility = INVISIBILITY_ABSTRACT
owner.current.hud_used.sunlight_display.invisibility = INVISIBILITY_ABSTRACT
/datum/antagonist/bloodsucker/proc/update_hud(updateRank=FALSE)
// No Hud? Get out.
if(!owner.current.hud_used)
return
// Update Blood Counter
if (owner.current.hud_used.blood_display)
var/valuecolor = "#FF6666"
if(owner.current.blood_volume > BLOOD_VOLUME_SAFE)
valuecolor = "#FFDDDD"
else if(owner.current.blood_volume > BLOOD_VOLUME_BAD)
valuecolor = "#FFAAAA"
owner.current.hud_used.blood_display.update_counter(owner.current.blood_volume, valuecolor)
// Update Rank Counter
if(owner.current.hud_used.vamprank_display)
var/valuecolor = vamplevel_unspent ? "#FFFF00" : "#FF0000"
owner.current.hud_used.vamprank_display.update_counter(vamplevel, valuecolor)
if(updateRank) // Only change icon on special request.
owner.current.hud_used.vamprank_display.icon_state = (vamplevel_unspent > 0) ? "rank_up" : "rank"
/obj/screen/bloodsucker
invisibility = INVISIBILITY_ABSTRACT
/obj/screen/bloodsucker/proc/clear()
invisibility = INVISIBILITY_ABSTRACT
/obj/screen/bloodsucker/proc/update_counter(value, valuecolor)
invisibility = 0 // Make Visible
/obj/screen/bloodsucker/blood_counter // NOTE: Look up /obj/screen/devil/soul_counter in _onclick / hud / human.dm
icon = 'icons/mob/actions/bloodsucker.dmi'//'icons/mob/screen_gen.dmi'
name = "Blood Consumed"
icon_state = "blood_display"//"power_display"
screen_loc = ui_blood_display
/obj/screen/bloodsucker/blood_counter/update_counter(value, valuecolor)
..()
maptext = "<div align='center' valign='middle' style='position:relative; top:0px; left:6px'><font color='[valuecolor]'>[round(value,1)]</font></div>"
/obj/screen/bloodsucker/rank_counter
name = "Bloodsucker Rank"
icon = 'icons/mob/actions/bloodsucker.dmi'
icon_state = "rank"
screen_loc = ui_vamprank_display
/obj/screen/bloodsucker/rank_counter/update_counter(value, valuecolor)
..()
maptext = "<div align='center' valign='middle' style='position:relative; top:0px; left:6px'><font color='[valuecolor]'>[round(value,1)]</font></div>"
/obj/screen/bloodsucker/sunlight_counter
icon = 'icons/mob/actions/bloodsucker.dmi'
name = "Solar Flare Timer"
icon_state = "sunlight_night"
screen_loc = ui_sunlight_display
/datum/antagonist/bloodsucker/proc/update_sunlight(value, amDay = FALSE)
// No Hud? Get out.
if (!owner.current.hud_used)
return
// Update Sun Time
if (owner.current.hud_used.sunlight_display)
var/valuecolor = "#BBBBFF"
if (amDay)
valuecolor = "#FF5555"
else if(value <= 25)
valuecolor = "#FFCCCC"
else if(value < 10)
valuecolor = "#FF5555"
var/value_string = (value >= 60) ? "[round(value / 60, 1)] m" : "[round(value,1)] s"
owner.current.hud_used.sunlight_display.update_counter( value_string, valuecolor )
owner.current.hud_used.sunlight_display.icon_state = "sunlight_" + (amDay ? "day":"night")
/obj/screen/bloodsucker/sunlight_counter/update_counter(value, valuecolor)
..()
maptext = "<div align='center' valign='bottom' style='position:relative; top:0px; left:6px'><font color='[valuecolor]'>[value]</font></div>"

View File

@@ -0,0 +1,295 @@
/*
#define HUNTER_SCAN_MIN_DISTANCE 8
#define HUNTER_SCAN_MAX_DISTANCE 35
#define HUNTER_SCAN_PING_TIME 20 //5s update time.
/datum/antagonist/vamphunter
name = "Hunter"
roundend_category = "hunters"
antagpanel_category = "Monster Hunter"
job_rank = ROLE_MONSTERHUNTER
var/list/datum/action/powers = list()// Purchased powers
var/list/datum/objective/objectives_given = list() // For removal if needed.
var/datum/martial_art/my_kungfu // Hunters know a lil kung fu.
var/bad_dude = FALSE // Every first hunter spawned is a SHIT LORD.
/datum/antagonist/vamphunter/on_gain()
SSticker.mode.vamphunters |= owner // Add if not already in here (and you might be, if you were picked at round start)
// Hunter Pinpointer
//owner.current.apply_status_effect(/datum/status_effect/agent_pinpointer/hunter_edition)
// Give Hunter Power
var/datum/action/P = new /datum/action/bloodsucker/trackvamp
P.Grant(owner.current)
// Give Hunter Martial Arts
//if (rand(1,3) == 1)
// var/datum/martial_art/pick_type = pick (/datum/martial_art/cqc, /datum/martial_art/krav_maga, /datum/martial_art/cqc, /datum/martial_art/krav_maga, /datum/martial_art/wrestling) // /datum/martial_art/boxing <--- doesn't include grabbing, so don't use!
// my_kungfu = new pick_type //pick (/datum/martial_art/boxing, /datum/martial_art/cqc) // ick_type
// my_kungfu.teach(owner.current, 0)
// Give Hunter Objective
var/datum/objective/bloodsucker/monsterhunter/monsterhunter_objective = new
monsterhunter_objective.owner = owner
monsterhunter_objective.generate_objective()
objectives += monsterhunter_objective
objectives_given += monsterhunter_objective
// Badguy Hunter? (Give him BADGUY objectives)
if (bad_dude)
// Stolen DIRECTLY from datum_traitor.dm
if(prob(15) && !(locate(/datum/objective/download) in objectives) && !(owner.assigned_role in list("Research Director", "Scientist", "Roboticist")))
var/datum/objective/download/download_objective = new
download_objective.owner = owner
download_objective.gen_amount_goal()
objectives += download_objective
objectives_given += download_objective
else
var/datum/objective/steal/steal_objective = new
steal_objective.owner = owner
steal_objective.find_target()
objectives += steal_objective
objectives_given += steal_objective
. = ..()
/datum/antagonist/vamphunter/on_removal()
SSticker.mode.vamphunters -= owner // Add if not already in here (and you might be, if you were picked at round start)
// Master Pinpointer
//owner.current.remove_status_effect(/datum/status_effect/agent_pinpointer/hunter_edition)
// Take Hunter Power
if (owner.current)
for (var/datum/action/bloodsucker/P in owner.current.actions)
P.Remove(owner.current)
// Take Hunter Martial Arts
//my_kungfu.remove(owner.current)
// Remove Hunter Objectives
for(var/O in objectives_given)
objectives -= O
qdel(O)
objectives_given = list()
. = ..()
/datum/antagonist/vamphunter/greet()
var/vamp_hunter_greet = "<span class='userdanger'>You are a fearless Monster Hunter!</span>"
vamp_hunter_greet += "<span class='boldannounce'>You know there's one or more filthy creature onboard the station, though their identities elude you.<span>"
vamp_hunter_greet += "<span class='boldannounce'>It's your job to root them out, destroy their nests, and save the crew.<span>"
vamp_hunter_greet += "<span class='boldannounce'>Use <b>WHATEVER MEANS NECESSARY</b> to find these creatures...no matter who gets hurt or what you have to destroy to do it.</span>"
vamp_hunter_greet += "There are greater stakes at hand than the safety of the station!<span>"
vamp_hunter_greet += "<span class='boldannounce'>However, security may detain you if they discover your mission...<span>"
antag_memory += "You remember your training:<br>"
antag_memory += " -Bloodsuckers are weak to fire, or a stake to the heart. Removing their head or heart will also destroy them permanently.<br>"
antag_memory += " -Wooden stakes can be made from planks, and hardened by a welding tool. Your recipes list has ways of making them even stronger.<br>"
antag_memory += " -Changelings return to life unless their body is destroyed. Not even decapitation can stop them for long.<br>"
antag_memory += " -Cultists are weak to the Chaplain's holy water.<br>"
antag_memory += " -Wizards are notoriously hard to outmatch. Rob or steal whatever weapons you need to destroy them, and shoot before asking questions.<br><br>"
if (my_kungfu != null)
vamp_hunter_greet += "<span class='announce'>Hunter Tip: Use your [my_kungfu.name] techniques to give you an advantage over the enemy.</span><br>"
antag_memory += "You remember your training: You are skilled in the [my_kungfu.name] style of combat.<br>"
to_chat(owner, vamp_hunter_greet)
/datum/antagonist/vamphunter/farewell()
to_chat(owner, "<span class='userdanger'>Your hunt has ended: you are no longer a monster hunter!</span>")
// TAKEN FROM: /datum/action/changeling/pheromone_receptors // pheromone_receptors.dm for a version of tracking that Changelings have!
/datum/status_effect/agent_pinpointer/hunter_edition
alert_type = /obj/screen/alert/status_effect/agent_pinpointer/hunter_edition
minimum_range = HUNTER_SCAN_MIN_DISTANCE
tick_interval = HUNTER_SCAN_PING_TIME
duration = 160 // Lasts 10s
range_fuzz_factor = 5//PINPOINTER_EXTRA_RANDOM_RANGE
/obj/screen/alert/status_effect/agent_pinpointer/hunter_edition
name = "Monster Tracking"
desc = "You always know where the hellspawn are."
/datum/status_effect/agent_pinpointer/hunter_edition/on_creation(mob/living/new_owner, ...)
..()
// Pick target
var/turf/my_loc = get_turf(owner)
var/list/mob/living/carbon/vamps = list()
for(var/datum/mind/M in SSticker.mode.bloodsuckers)
if (!M.current || M.current == owner || !get_turf(M.current) || !get_turf(new_owner))
continue
var/datum/antagonist/bloodsucker/antag_datum = M.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if(!istype(antag_datum))
continue
var/their_loc = get_turf(M.current)
var/distance = get_dist_euclidian(my_loc, their_loc)
if (distance < HUNTER_SCAN_MAX_DISTANCE)
vamps[M.current] = (HUNTER_SCAN_MAX_DISTANCE ** 2) - (distance ** 2)
// Found one!
if(vamps.len)
scan_target = pickweight(vamps) //Point at a 'random' vamp, biasing heavily towards closer ones.
to_chat(owner, "<span class='warning'>You detect signs of monsters to the <b>[dir2text(get_dir(my_loc,get_turf(scan_target)))]!</b></span>")
// Will yield a "?"
else
to_chat(owner, "<span class='notice'>There are no monsters nearby.</span>")
// Force Point-To Immediately
point_to_target()
/datum/status_effect/agent_pinpointer/hunter_edition/scan_for_target()
// Lose target? Done. Otherwise, scan for target's current position.
if (!scan_target && owner)
owner.remove_status_effect(/datum/status_effect/agent_pinpointer/hunter_edition)
// NOTE: Do NOT run ..(), or else we'll remove our target.
/datum/status_effect/agent_pinpointer/hunter_edition/Destroy()
if (scan_target)
to_chat(owner, "<span class='notice'>You've lost the trail.</span>")
..()
*/
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
/datum/action/bloodsucker/trackvamp/
name = "Track Monster"//"Cellular Emporium"
desc = "Take a moment to look for clues of any nearby monsters.<br>These creatures are slippery, and often look like the crew."
button_icon = 'icons/mob/actions/bloodsucker.dmi' //This is the file for the BACKGROUND icon
background_icon_state = "vamp_power_off" //And this is the state for the background icon
icon_icon = 'icons/mob/actions/bloodsucker.dmi' //This is the file for the ACTION icon
button_icon_state = "power_hunter" //And this is the state for the action icon
amToggle = FALSE // Action-Related
cooldown = 300 // 10 ticks, 1 second.
bloodcost = 0
/datum/action/bloodsucker/trackvamp/ActivatePower()
var/mob/living/user = owner
to_chat(user, "<span class='notice'>You look around, scanning your environment and discerning signs of any filthy, wretched affronts to the natural order.</span>")
if (!do_mob(user,owner,80))
return
// Add Power
// REMOVED //user.apply_status_effect(/datum/status_effect/agent_pinpointer/hunter_edition)
// We don't track direction anymore!
// Return text indicating direction
display_proximity()
// NOTE: DON'T DEACTIVATE!
//DeactivatePower()
/datum/action/bloodsucker/trackvamp/proc/display_proximity()
// Pick target
var/turf/my_loc = get_turf(owner)
//var/list/mob/living/carbon/vamps = list()
var/best_dist = 9999
var/mob/living/best_vamp
// Track ALL MONSTERS in Game Mode
var/list/datum/mind/monsters = list()
monsters += SSticker.mode.bloodsuckers
monsters += SSticker.mode.devils
//monsters += SSticker.mode.cult
monsters += SSticker.mode.wizards
monsters += SSticker.mode.apprentices
monsters += SSticker.mode.servants_of_ratvar
//monsters += SSticker.mode.changelings Disabled anyways
//
for(var/datum/mind/M in monsters)
if (!M.current || M.current == owner)// || !get_turf(M.current) || !get_turf(owner))
continue
for(var/a in M.antag_datums)
var/datum/antagonist/antag_datum = a // var/datum/antagonist/antag_datum = M.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if(!istype(antag_datum) || antag_datum.AmFinalDeath())
continue
var/their_loc = get_turf(M.current)
var/distance = get_dist_euclidian(my_loc, their_loc)
// Found One: Closer than previous/max distance
if (distance < best_dist && distance <= HUNTER_SCAN_MAX_DISTANCE)
best_dist = distance
best_vamp = M.current
break // Stop searching through my antag datums and go to the next guy
// Found one!
if(best_vamp)
var/distString = best_dist <= HUNTER_SCAN_MAX_DISTANCE / 2 ? "<b>somewhere closeby!</b>" : "somewhere in the distance."
//to_chat(owner, "<span class='warning'>You detect signs of Bloodsuckers to the <b>[dir2text(get_dir(my_loc,get_turf(targetVamp)))]!</b></span>")
to_chat(owner, "<span class='warning'>You detect signs of monsters [distString]</span>")
// Will yield a "?"
else
to_chat(owner, "<span class='notice'>There are no monsters nearby.</span>")
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// From martial.dm
/*
/datum/martial_art/hunter
name = "Hunter-Fu"
id = "MARTIALART_HUNTER" //ID, used by mind/has_martialart
//streak = ""
//max_streak_length = 6
//current_target
//datum/martial_art/base // The permanent style. This will be null unless the martial art is temporary
//deflection_chance = 0 //Chance to deflect projectiles
//reroute_deflection = FALSE //Delete the bullet, or actually deflect it in some direction?
//block_chance = 0 //Chance to block melee attacks using items while on throw mode.
//restraining = 0 //used in cqc's disarm_act to check if the disarmed is being restrained and so whether they should be put in a chokehold or not
//help_verb
//no_guns = FALSE
//allow_temp_override = TRUE //if this martial art can be overridden by temporary martial arts
/datum/martial_art/hunter/disarm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
return FALSE
/datum/martial_art/hunter/harm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
return FALSE
/datum/martial_art/hunter/grab_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
return FALSE
/datum/martial_art/hunter/can_use(mob/living/carbon/human/H)
return TRUE
/datum/martial_art/hunter/add_to_streak(element,mob/living/carbon/human/D)
if(D != current_target)
current_target = D
streak = ""
restraining = 0
streak = streak+element
if(length(streak) > max_streak_length)
streak = copytext(streak,2)
return
/datum/martial_art/hunter/basic_hit(mob/living/carbon/human/A,mob/living/carbon/human/D)
var/damage = rand(A.dna.species.punchdamagelow, A.dna.species.punchdamagehigh)
*/
*/

View File

@@ -0,0 +1,151 @@
#define VASSAL_SCAN_MIN_DISTANCE 5
#define VASSAL_SCAN_MAX_DISTANCE 500
#define VASSAL_SCAN_PING_TIME 20 //2s update time.
/datum/antagonist/bloodsucker/proc/attempt_turn_vassal(mob/living/carbon/C)
C.silent = 0
return SSticker.mode.make_vassal(C,owner)
/datum/antagonist/bloodsucker/proc/FreeAllVassals()
for (var/datum/antagonist/vassal/V in vassals)
SSticker.mode.remove_vassal(V.owner)
/datum/antagonist/vassal
name = "Vassal"//WARNING: DO NOT SELECT" // "Vassal"
roundend_category = "vassals"
antagpanel_category = "Bloodsucker"
job_rank = ROLE_BLOODSUCKER
var/datum/antagonist/bloodsucker/master // Who made me?
var/list/datum/action/powers = list()// Purchased powers
var/list/datum/objective/objectives_given = list() // For removal if needed.
/datum/antagonist/vassal/can_be_owned(datum/mind/new_owner)
// If we weren't created by a bloodsucker, then we cannot be a vassal (assigned from antag panel)
if (!master)
return FALSE
return ..()
/datum/antagonist/vassal/on_gain()
SSticker.mode.vassals |= owner // Add if not already in here (and you might be, if you were picked at round start)
// Mindslave Add
if(master)
var/datum/antagonist/bloodsucker/B = master.owner.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if(B)
B.vassals |= src
owner.enslave_mind_to_creator(master.owner.current)
// Master Pinpointer
owner.current.apply_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition)
// Powers
var/datum/action/bloodsucker/vassal/recuperate/new_Recuperate = new ()
new_Recuperate.Grant(owner.current)
powers += new_Recuperate
// Give Vassal Objective
var/datum/objective/bloodsucker/vassal/vassal_objective = new
vassal_objective.owner = owner
vassal_objective.generate_objective()
objectives += vassal_objective
objectives_given += vassal_objective
give_thrall_eyes()
owner.current.grant_language(/datum/language/vampiric)
// Add Antag HUD
update_vassal_icons_added(owner.current, "vassal")
. = ..()
/datum/antagonist/vassal/proc/give_thrall_eyes()
var/obj/item/organ/eyes/vassal/E = new
E.Insert(owner.current)
/obj/item/organ/eyes/vassal/
lighting_alpha = 180 // LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE <--- This is too low a value at 128. We need to SEE what the darkness is so we can hide in it.
see_in_dark = 12
flash_protect = -1 //These eyes are weaker to flashes, but let you see in the dark
/datum/antagonist/vassal/proc/remove_thrall_eyes()
var/obj/item/organ/eyes/E = new
E.Insert(owner.current)
/datum/antagonist/vassal/on_removal()
SSticker.mode.vassals -= owner // Add if not already in here (and you might be, if you were picked at round start)
// Mindslave Remove
if (master && master.owner)
master.vassals -= src
if (owner.enslaved_to == master.owner.current)
owner.enslaved_to = null
// Master Pinpointer
owner.current.remove_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition)
// Powers
while(powers.len)
var/datum/action/power = pick(powers)
powers -= power
power.Remove(owner.current)
// Remove Hunter Objectives
for(var/O in objectives_given)
objectives -= O
qdel(O)
objectives_given = list()
remove_thrall_eyes()
owner.current.remove_language(/datum/language/vampiric)
// Clear Antag HUD
update_vassal_icons_removed(owner.current)
. = ..()
/datum/antagonist/vassal/greet()
to_chat(owner, "<span class='userdanger'>You are now the mortal servant of [master.owner.current], a bloodsucking vampire!</span>")
to_chat(owner, "<span class='boldannounce'>The power of [master.owner.current.p_their()] immortal blood compells you to obey [master.owner.current.p_them()] in all things, even offering your own life to prolong theirs.<br>\
You are not required to obey any other Bloodsucker, for only [master.owner.current] is your master. The laws of Nanotransen do not apply to you now; only your vampiric master's word must be obeyed.<span>")
// Effects...
owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE)
//owner.store_memory("You became the mortal servant of [master.owner.current], a bloodsucking vampire!")
antag_memory += "You became the mortal servant of <b>[master.owner.current]</b>, a bloodsucking vampire!<br>"
// And to your new Master...
to_chat(master.owner, "<span class='userdanger'>[owner.current] has become addicted to your immortal blood. [owner.current.p_they(TRUE)] [owner.current.p_are()] now your undying servant!</span>")
master.owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE)
/datum/antagonist/vassal/farewell()
owner.current.visible_message("[owner.current]'s eyes dart feverishly from side to side, and then stop. [owner.current.p_they(TRUE)] seem[owner.current.p_s()] calm, \
like [owner.current.p_they()] [owner.current.p_have()] regained some lost part of [owner.current.p_them()]self.",\
"<span class='userdanger'><FONT size = 3>With a snap, you are no longer enslaved to [master.owner]! You breathe in heavily, having regained your free will.</FONT></span>")
owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE)
// And to your former Master...
//if (master && master.owner)
// to_chat(master.owner, "<span class='userdanger'>You feel the bond with your vassal [owner.current] has somehow been broken!</span>")
/datum/status_effect/agent_pinpointer/vassal_edition
id = "agent_pinpointer"
alert_type = /obj/screen/alert/status_effect/agent_pinpointer/vassal_edition
minimum_range = VASSAL_SCAN_MIN_DISTANCE
tick_interval = VASSAL_SCAN_PING_TIME
duration = -1 // runs out fast
range_fuzz_factor = 0
/obj/screen/alert/status_effect/agent_pinpointer/vassal_edition
name = "Blood Bond"
desc = "You always know where your master is."
//icon = 'icons/obj/device.dmi'
//icon_state = "pinon"
/datum/status_effect/agent_pinpointer/vassal_edition/on_creation(mob/living/new_owner, ...)
..()
var/datum/antagonist/vassal/antag_datum = new_owner.mind.has_antag_datum(ANTAG_DATUM_VASSAL)
scan_target = antag_datum?.master?.owner?.current
/datum/status_effect/agent_pinpointer/vassal_edition/scan_for_target()
// DO NOTHING. We already have our target, and don't wanna do anything from agent_pinpointer
/datum/antagonist/vassal/proc/update_vassal_icons_added(mob/living/vassal, icontype="vassal")
var/datum/atom_hud/antag/bloodsucker/hud = GLOB.huds[ANTAG_HUD_BLOODSUCKER]
hud.join_hud(vassal)
set_antag_hud(vassal, icontype) // Located in icons/mob/hud.dmi
owner.current.hud_list[ANTAG_HUD].icon = image('icons/mob/hud.dmi', owner.current, "bloodsucker")
/datum/antagonist/vassal/proc/update_vassal_icons_removed(mob/living/vassal)
var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_BLOODSUCKER]
hud.leave_hud(vassal)
set_antag_hud(vassal, null)
//Displayed at the start of roundend_category section, default to roundend_category header
/*/datum/antagonist/vassal/roundend_report_header()
return "<span class='header'>Loyal to their bloodsucking masters, the Vassals were:</span><br><br>"*/

View File

@@ -0,0 +1,70 @@
/datum/antagonist/bloodsucker/proc/CheckVampOrgans()
// Do I have any parts that need replacing?
var/obj/item/organ/O
// Heart
O = owner.current.getorganslot(ORGAN_SLOT_HEART)
if(!istype(O, /obj/item/organ/heart/vampheart))
qdel(O)
var/obj/item/organ/heart/vampheart/H = new
H.Insert(owner.current)
H.Stop() // Now...stop beating!
// Eyes
O = owner.current.getorganslot(ORGAN_SLOT_EYES)
if(!istype(O, /obj/item/organ/eyes/vassal/bloodsucker))
qdel(O)
var/obj/item/organ/eyes/vassal/bloodsucker/E = new
E.Insert(owner.current)
/datum/antagonist/bloodsucker/proc/RemoveVampOrgans()
// Heart
var/obj/item/organ/heart/H = new
H.Insert(owner.current)
// Eyes
var/obj/item/organ/eyes/E = new
E.Insert(owner.current)
// HEART: OVERWRITE //
// HEART //
/obj/item/organ/heart/vampheart
beating = 0
var/fakingit = 0
/obj/item/organ/heart/vampheart/prepare_eat()
..()
// Do cool stuff for eating vamp heart?
/obj/item/organ/heart/vampheart/Restart()
beating = 0 // DONT run ..(). We don't want to start beating again.
return 0
/obj/item/organ/heart/vampheart/Stop()
fakingit = 0
return ..()
/obj/item/organ/heart/vampheart/proc/FakeStart()
fakingit = 1 // We're pretending to beat, to fool people.
/obj/item/organ/heart/vampheart/HeartStrengthMessage()
if(fakingit)
return "a healthy"
return "<span class='danger'>no</span>" // Bloodsuckers don't have a heartbeat at all when stopped (default is "an unstable")
// EYES //
/obj/item/organ/eyes/vassal/bloodsucker
flash_protect = 2 //Eye healing isnt working properly
sight_flags = SEE_MOBS // Taken from augmented_eyesight.dm
/*
// LIVER //
/obj/item/organ/liver/vampliver
// Livers run on_life(), which calls reagents.metabolize() in holder.dm, which calls on_mob_life.dm in the cheam (medicine_reagents.dm)
// Holder also calls reagents.reaction_mob for the moment it happens
/obj/item/organ/liver/vampliver/on_life()
var/mob/living/carbon/C = owner
if(!istype(C))
return
*/

View File

@@ -0,0 +1,176 @@
// organ_internal.dm -- /obj/item/organ
// Do I have a stake in my heart?
/mob/living/AmStaked()
var/obj/item/bodypart/BP = get_bodypart("chest")
if (!BP)
return FALSE
for(var/obj/item/I in BP.embedded_objects)
if (istype(I,/obj/item/stake/))
return TRUE
return FALSE
/mob/proc/AmStaked()
return FALSE
/mob/living/proc/StakeCanKillMe()
return IsSleeping() || stat >= UNCONSCIOUS || blood_volume <= 0 || HAS_TRAIT(src, TRAIT_DEATHCOMA) // NOTE: You can't go to sleep in a coffin with a stake in you.
///obj/item/weapon/melee/stake
/obj/item/stake/
name = "wooden stake"
desc = "A simple wooden stake carved to a sharp point."
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "wood" // Inventory Icon
item_state = "wood" // In-hand Icon
lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi' // File for in-hand icon
righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
attack_verb = list("staked")
slot_flags = ITEM_SLOT_BELT
w_class = WEIGHT_CLASS_SMALL
hitsound = 'sound/weapons/bladeslice.ogg'
force = 6
throwforce = 10
embedding = list("embed_chance" = 25, "embedded_fall_chance" = 0.5) // UPDATE 2/10/18 embedding_behavior.dm is how this is handled
//embed_chance = 25 // Look up "is_pointed" to see where we set stakes able to do this.
//embedded_fall_chance = 0.5 // Chance it will fall out.
obj_integrity = 30
max_integrity = 30
//embedded_fall_pain_multiplier
var/staketime = 120 // Time it takes to embed the stake into someone's chest.
/obj/item/stake/basic
name = "wooden stake"
// This exists so Hardened/Silver Stake can't have a welding torch used on them.
/obj/item/stake/basic/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/weldingtool))
//if (amWelded)
// to_chat(user, "<span class='warning'>This stake has already been treated with fire.</span>")
// return
//amWelded = TRUE
// Weld it
var/obj/item/weldingtool/WT = W
if(WT.use(0))//remove_fuel(0,user))
user.visible_message("[user.name] scorched the pointy end of [src] with the welding tool.", \
"<span class='notice'>You scorch the pointy end of [src] with the welding tool.</span>", \
"<span class='italics'>You hear welding.</span>")
// 8 Second Timer
if(!do_mob(user, src, 80))
return
// Create the Stake
qdel(src)
var/obj/item/stake/hardened/new_item = new(usr.loc)
user.put_in_hands(new_item)
else
return ..()
/obj/item/stake/afterattack(atom/target, mob/user, proximity)
//to_chat(world, "<span class='notice'>DEBUG: Staking </span>")
// Invalid Target, or not targetting chest with HARM intent?
if(!iscarbon(target) || check_zone(user.zone_selected) != "chest" || user.a_intent != INTENT_HARM)
return
var/mob/living/carbon/C = target
// Needs to be Down/Slipped in some way to Stake.
if(!C.can_be_staked() || target == user)
to_chat(user, "<span class='danger'>You cant stake [target] when they are moving moving about! They have to be laying down!</span>")
return
// Oops! Can't.
if(HAS_TRAIT(C, TRAIT_PIERCEIMMUNE))
to_chat(user, "<span class='danger'>[target]'s chest resists the stake. It won't go in.</span>")
return
// Make Attempt...
to_chat(user, "<span class='notice'>You put all your weight into embedding the stake into [target]'s chest...</span>")
playsound(user, 'sound/magic/Demon_consume.ogg', 50, 1)
if(!do_mob(user, C, staketime, 0, 1, extra_checks=CALLBACK(C, /mob/living/carbon/proc/can_be_staked))) // user / target / time / uninterruptable / show progress bar / extra checks
return
// Drop & Embed Stake
user.visible_message("<span class='danger'>[user.name] drives the [src] into [target]'s chest!</span>", \
"<span class='danger'>You drive the [src] into [target]'s chest!</span>")
playsound(get_turf(target), 'sound/effects/splat.ogg', 40, 1)
user.dropItemToGround(src, TRUE) //user.drop_item() // "drop item" doesn't seem to exist anymore. New proc is user.dropItemToGround() but it doesn't seem like it's needed now?
var/obj/item/bodypart/B = C.get_bodypart("chest") // This was all taken from hitby() in human_defense.dm
B.embedded_objects |= src
add_mob_blood(target)//Place blood on the stake
loc = C // Put INSIDE the character
B.receive_damage(w_class * embedding.embedded_impact_pain_multiplier)
if(C.mind)
var/datum/antagonist/bloodsucker/bloodsucker = C.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if(bloodsucker)
// If DEAD or TORPID...kill vamp!
if(C.StakeCanKillMe()) // NOTE: This is the ONLY time a staked Torpid vamp dies.
bloodsucker.FinalDeath()
return
else
to_chat(target, "<span class='userdanger'>You have been staked! Your powers are useless, your death forever, while it remains in place.</span>")
to_chat(user, "<span class='warning'>You missed [C.p_their(TRUE)]'s heart! It would be easier if [C.p_they(TRUE)] weren't struggling so much.</span>")
// Can this target be staked? If someone stands up before this is complete, it fails. Best used on someone stationary.
/mob/living/carbon/proc/can_be_staked()
//return resting || IsKnockdown() || IsUnconscious() || (stat && (stat != SOFT_CRIT || pulledby)) || (has_trait(TRAIT_FAKEDEATH)) || resting || IsStun() || IsFrozen() || (pulledby && pulledby.grab_state >= GRAB_NECK)
return (src.resting || src.lying)
// ABOVE: Taken from update_mobility() in living.dm
/obj/item/stake/hardened
// Created by welding and acid-treating a simple stake.
name = "hardened stake"
desc = "A hardened wooden stake carved to a sharp point and scorched at the end."
icon_state = "hardened" // Inventory Icon
force = 8
throwforce = 12
armour_penetration = 10
embedding = list("embed_chance" = 50, "embedded_fall_chance" = 0) // UPDATE 2/10/18 embedding_behavior.dm is how this is handled
obj_integrity = 120
max_integrity = 120
staketime = 80
/obj/item/stake/hardened/silver
name = "silver stake"
desc = "Polished and sharp at the end. For when some mofo is always trying to iceskate uphill."
icon_state = "silver" // Inventory Icon
item_state = "silver" // In-hand Icon
siemens_coefficient = 1 //flags = CONDUCT // var/siemens_coefficient = 1 // for electrical admittance/conductance (electrocution checks and shit)
force = 9
armour_penetration = 25
embedding = list("embed_chance" = 65) // UPDATE 2/10/18 embedding_behavior.dm is how this is handled
obj_integrity = 300
max_integrity = 300
staketime = 60
// Convert back to Silver
/obj/item/stake/hardened/silver/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/weldingtool))
var/obj/item/weldingtool/WT = I
if(WT.use(0))//remove_fuel(0, user))
var/obj/item/stack/sheet/mineral/silver/newsheet = new (user.loc)
for(var/obj/item/stack/sheet/mineral/silver/S in user.loc)
if(S == newsheet)
continue
if(S.amount >= S.max_amount)
continue
S.attackby(newsheet, user)
to_chat(user, "<span class='notice'>You melt down the stake and add it to the stack. It now contains [newsheet.amount] sheet\s.</span>")
qdel(src)
else
return ..()
// Look up recipes.dm OR pneumaticCannon.dm
/datum/crafting_recipe/silver_stake
name = "Silver Stake"
result = /obj/item/stake/hardened/silver
tools = list(/obj/item/weldingtool)
reqs = list(/obj/item/stack/sheet/mineral/silver = 1,
/obj/item/stake/hardened = 1)
///obj/item/stack/packageWrap = 8,
///obj/item/pipe = 2)
time = 80
category = CAT_WEAPONRY
subcategory = CAT_WEAPON

View File

@@ -0,0 +1,242 @@
// TRAIT_DEATHCOMA - Activate this when you're in your coffin to simulate sleep/death.
// Coffins...
// -heal all wounds, and quickly.
// -restore limbs & organs
//
// Without Coffins...
// -
// -limbs stay lost
// To put to sleep: use owner.current.fakedeath("bloodsucker") but change name to "bloodsucker_coffin" so you continue to stay fakedeath despite healing in the main thread!
/datum/antagonist/bloodsucker/proc/ClaimCoffin(obj/structure/closet/crate/claimed) // NOTE: This can be any "closet" that you are resting AND inside of.
// ALREADY CLAIMED
if(claimed.resident)
if(claimed.resident == owner.current)
to_chat(owner, "This is your [src].")
else
to_chat(owner, "This [src] has already been claimed by another.")
return FALSE
// Bloodsucker Learns new Recipes!
owner.teach_crafting_recipe(/datum/crafting_recipe/bloodsucker/vassalrack)
owner.teach_crafting_recipe(/datum/crafting_recipe/bloodsucker/candelabrum)
// This is my Lair
coffin = claimed
lair = get_area(claimed)
// DONE
to_chat(owner, "<span class='userdanger'>You have claimed the [claimed] as your place of immortal rest! Your lair is now [lair].</span>")
to_chat(owner, "<span class='danger'>You have learned new construction recipes to improve your lair.</span>")
to_chat(owner, "<span class='announce'>Bloodsucker Tip: Find new lair recipes in the misc tab of the <i>Crafting Menu</i> at the bottom of the screen, including the <i>Persuasion Rack</i> for converting crew into Vassals.</span><br><br>")
RunLair() // Start
return TRUE
// crate.dm
/obj/structure/closet/crate
var/mob/living/resident // This lets bloodsuckers claim any "closet" as a Coffin, so long as they could get into it and close it. This locks it in place, too.
/obj/structure/closet/crate/coffin
var/pryLidTimer = 250
can_weld_shut = FALSE
breakout_time = 200
/obj/structure/closet/crate/coffin/blackcoffin
name = "black coffin"
desc = "For those departed who are not so dear."
icon_state = "coffin"
icon = 'icons/obj/vamp_obj.dmi'
can_weld_shut = FALSE
resistance_flags = 0 // Start off with no bonuses.
open_sound = 'sound/bloodsucker/coffin_open.ogg'
close_sound = 'sound/bloodsucker/coffin_close.ogg'
breakout_time = 600
pryLidTimer = 400
resistance_flags = NONE
/obj/structure/closet/crate/coffin/meatcoffin
name = "meat coffin"
desc = "When you're ready to meat your maker, the steaks can never be too high."
icon_state = "meatcoffin"
icon = 'icons/obj/vamp_obj.dmi'
can_weld_shut = FALSE
resistance_flags = 0 // Start off with no bonuses.
open_sound = 'sound/effects/footstep/slime1.ogg'
close_sound = 'sound/effects/footstep/slime1.ogg'
breakout_time = 200
pryLidTimer = 200
resistance_flags = NONE
material_drop = /obj/item/reagent_containers/food/snacks/meat/slab
material_drop_amount = 3
/obj/structure/closet/crate/coffin/metalcoffin
name = "metal coffin"
desc = "A big metal sardine can inside of another big metal sardine can, in space."
icon_state = "metalcoffin"
icon = 'icons/obj/vamp_obj.dmi'
can_weld_shut = FALSE
resistance_flags = FIRE_PROOF | LAVA_PROOF
open_sound = 'sound/effects/pressureplate.ogg'
close_sound = 'sound/effects/pressureplate.ogg'
breakout_time = 300
pryLidTimer = 200
resistance_flags = NONE
material_drop = /obj/item/stack/sheet/metal
material_drop_amount = 5
//////////////////////////////////////////////
/obj/structure/closet/crate/proc/ClaimCoffin(mob/living/claimant) // NOTE: This can be any "closet" that you are resting AND inside of.
// Bloodsucker Claim
var/datum/antagonist/bloodsucker/bloodsuckerdatum = claimant.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if(bloodsuckerdatum)
// Vamp Successfuly Claims Me?
if(bloodsuckerdatum.ClaimCoffin(src))
resident = claimant
anchored = 1 // No moving this
/obj/structure/closet/crate/coffin/Destroy()
UnclaimCoffin()
return ..()
/obj/structure/closet/crate/proc/UnclaimCoffin()
if (resident)
// Vamp Un-Claim
if (resident.mind)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = resident.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if (bloodsuckerdatum && bloodsuckerdatum.coffin == src)
bloodsuckerdatum.coffin = null
bloodsuckerdatum.lair = null
to_chat(resident, "<span class='danger'><span class='italics'>You sense that the link with your coffin, your sacred place of rest, has been brokem! You will need to seek another.</span></span>")
resident = null // Remove resident. Because this object isnt removed from the game immediately (GC?) we need to give them a way to see they don't have a home anymore.
/obj/structure/closet/crate/coffin/can_open(mob/living/user)
// You cannot lock in/out a coffin's owner. SORRY.
if (locked)
if(user == resident)
if (welded)
welded = FALSE
update_icon()
//to_chat(user, "<span class='notice'>You flip a secret latch and unlock [src].</span>") // Don't bother. We know it's unlocked.
locked = FALSE
return 1
else
playsound(get_turf(src), 'sound/machines/door_locked.ogg', 20, 1)
to_chat(user, "<span class='notice'>[src] is locked tight from the inside.</span>")
return ..()
/obj/structure/closet/crate/coffin/close(mob/living/user)
var/turf/Turf = get_turf(src)
var/area/A = get_area(src)
if (!..())
return FALSE
// Only the User can put themself into Torpor (if you're already in it, you'll start to heal)
if((user in src))
// Bloodsucker Only
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if(bloodsuckerdatum)
LockMe(user)
Turf = get_turf(user) //we may have moved. adjust as needed...
A = get_area(src)
// Claim?
if(!bloodsuckerdatum.coffin && !resident && (is_station_level(Turf.z) || !A.map_name == "Space"))
switch(alert(user,"Do you wish to claim this as your coffin? [get_area(src)] will be your lair.","Claim Lair","Yes", "No"))
if("Yes")
ClaimCoffin(user)
if (user.AmStaked()) // Stake? No Heal!
to_chat(bloodsuckerdatum.owner.current, "<span class='userdanger'>You are staked! Remove the offending weapon from your heart before sleeping.</span>")
return
// Heal
if(bloodsuckerdatum.HandleHealing(0)) // Healing Mult 0 <--- We only want to check if healing is valid!
to_chat(bloodsuckerdatum.owner.current, "<span class='notice'>You enter the horrible slumber of deathless Torpor. You will heal until you are renewed.</span>")
bloodsuckerdatum.Torpor_Begin()
// Level Up?
bloodsuckerdatum.SpendRank() // Auto-Fails if not appropriate
return TRUE
/obj/structure/closet/crate/coffin/attackby(obj/item/W, mob/user, params)
// You cannot weld or deconstruct an owned coffin. STILL NOT SORRY.
if (resident != null && user != resident) // Owner can destroy their own coffin.
if(opened)
if(istype(W, cutting_tool))
to_chat(user, "<span class='notice'>This is a much more complex mechanical structure than you thought. You don't know where to begin cutting [src].</span>")
return
else if(anchored && istype(W, /obj/item/wrench)) // Can't unanchor unless owner.
to_chat(user, "<span class='danger'>The coffin won't come unanchored from the floor.</span>")
return
if(locked && istype(W, /obj/item/crowbar))
var/pry_time = pryLidTimer * W.toolspeed // Pry speed must be affected by the speed of the tool.
user.visible_message("<span class='notice'>[user] tries to pry the lid off of [src] with [W].</span>", \
"<span class='notice'>You begin prying the lid off of [src] with [W]. This should take about [DisplayTimeText(pry_time)].</span>")
if (!do_mob(user,src,pry_time))
return
bust_open()
user.visible_message("<span class='notice'>[user] snaps the door of [src] wide open.</span>", \
"<span class='notice'>The door of [src] snaps open.</span>")
return
..()
/obj/structure/closet/crate/coffin/AltClick(mob/user)
// Distance Check (Inside Of)
if (user in src) // user.Adjacent(src)
LockMe(user, !locked)
/obj/structure/closet/crate/proc/LockMe(mob/user, inLocked = TRUE)
// Lock
if (user == resident)
if (!broken)
locked = inLocked
to_chat(user, "<span class='notice'>You flip a secret latch and [locked?"":"un"]lock yourself inside [src].</span>")
else
to_chat(resident, "<span class='notice'>The secret latch to lock [src] from the inside is broken. You set it back into place...</span>")
if (do_mob(resident, src, 50))//sleep(10)
if (broken) // Spam Safety
to_chat(resident, "<span class='notice'>You fix the mechanism and lock it.</span>")
broken = FALSE
locked = TRUE
// Look up recipes.dm OR pneumaticCannon.dm
/datum/crafting_recipe/bloodsucker/blackcoffin
name = "Black Coffin"
result = /obj/structure/closet/crate/coffin/blackcoffin
tools = list(/obj/item/weldingtool,
/obj/item/screwdriver)
reqs = list(/obj/item/stack/sheet/cloth = 1,
/obj/item/stack/sheet/mineral/wood = 5,
/obj/item/stack/sheet/metal = 1)
///obj/item/stack/packageWrap = 8,
///obj/item/pipe = 2)
time = 150
category = CAT_MISC
always_availible = TRUE
/datum/crafting_recipe/bloodsucker/meatcoffin
name = "Meat Coffin"
result =/obj/structure/closet/crate/coffin/meatcoffin
tools = list(/obj/item/kitchen/knife,
/obj/item/kitchen/rollingpin)
reqs = list(/obj/item/reagent_containers/food/snacks/meat/slab = 5,
/obj/item/restraints/handcuffs/cable = 1)
time = 150
category = CAT_MISC
always_availible = TRUE
/datum/crafting_recipe/bloodsucker/metalcoffin
name = "Metal Coffin"
result =/obj/structure/closet/crate/coffin/metalcoffin
tools = list(/obj/item/weldingtool,
/obj/item/screwdriver)
reqs = list(/obj/item/stack/sheet/metal = 5)
time = 100
category = CAT_MISC
always_availible = TRUE

View File

@@ -0,0 +1,497 @@
// IDEAS --
// An object that disguises your coffin while you're in it!
//
// An object that lets your lair itself protect you from sunlight, like a coffin would (no healing tho)
// Hide a random object somewhere on the station:
// var/turf/targetturf = get_random_station_turf()
// var/turf/targetturf = get_safe_random_station_turf()
// CRYPT OBJECTS
//
//
// PODIUM Stores your Relics
//
// ALTAR Transmute items into sacred items.
//
// PORTRAIT Gaze into your past to: restore mood boost?
//
// BOOKSHELF Discover secrets about crew and locations. Learn languages. Learn marial arts.
//
// BRAZER Burn rare ingredients to gleen insights.
//
// RUG Ornate, and creaks when stepped upon by any humanoid other than yourself and your vassals.
//
// X COFFIN (Handled elsewhere)
//
// X CANDELABRA (Handled elsewhere)
//
// THRONE Your mental powers work at any range on anyone inside your crypt.
//
// MIRROR Find any person
//
// BUST/STATUE Create terror, but looks just like you (maybe just in Examine?)
// RELICS
//
// RITUAL DAGGER
//
// SKULL
//
// VAMPIRIC SCROLL
//
// SAINTS BONES
//
// GRIMOIRE
// RARE INGREDIENTS
// Ore
// Books (Manuals)
// NOTE: Look up AI and Sentient Disease to see how the game handles the selector logo that only one player is allowed to see. We could add hud for vamps to that?
// ALTERNATIVELY, use the Vamp Huds on relics to mark them, but only show to relevant vamps?
/obj/structure/bloodsucker
var/mob/living/owner
/*
/obj/structure/bloodsucker/bloodthrone
name = "wicked throne"
desc = "Twisted metal shards jut from the arm rests. Very uncomfortable looking. It would take a sadistic sort to sit on this jagged piece of furniture."
/obj/structure/bloodsucker/bloodaltar
name = "bloody altar"
desc = "It is marble, lined with basalt, and radiates an unnerving chill that puts your skin on edge."
/obj/structure/bloodsucker/bloodstatue
name = "bloody countenance"
desc = "It looks upsettingly familiar..."
/obj/structure/bloodsucker/bloodportrait
name = "oil portrait"
desc = "A disturbingly familiar face stares back at you. On second thought, the reds don't seem to be painted in oil..."
/obj/structure/bloodsucker/bloodbrazer
name = "lit brazer"
desc = "It burns slowly, but doesn't radiate any heat."
/obj/structure/bloodsucker/bloodmirror
name = "faded mirror"
desc = "You get the sense that the foggy reflection looking back at you has an alien intelligence to it."
*/
/obj/structure/bloodsucker/vassalrack
name = "persuasion rack"
desc = "If this wasn't meant for torture, then someone has some fairly horrifying hobbies."
icon = 'icons/obj/vamp_obj.dmi'
icon_state = "vassalrack"
buckle_lying = FALSE
anchored = FALSE
density = TRUE // Start dense. Once fixed in place, go non-dense.
can_buckle = TRUE
var/useLock = FALSE // So we can't just keep dragging ppl on here.
var/mob/buckled
var/convert_progress = 3 // Resets on each new character to be added to the chair. Some effects should lower it...
var/disloyalty_confirm = FALSE // Command & Antags need to CONFIRM they are willing to lose their role (and will only do it if the Vassal'ing succeeds)
var/disloyalty_offered = FALSE // Has the popup been issued? Don't spam them.
var/convert_cost = 100
/obj/structure/bloodsucker/vassalrack/deconstruct(disassembled = TRUE)
new /obj/item/stack/sheet/metal(src.loc, 4)
new /obj/item/stack/rods(loc, 4)
qdel(src)
/obj/structure/bloodsucker/vassalrack/examine(mob/user)
. = ..()
if((user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)) || isobserver(user))
. += {"<span class='cult'>This is the vassal rack, which allows you to thrall crewmembers into loyal minions in your service.</span>"}
. += {"<span class='cult'>You need to first secure the vassal rack by clicking on it while it is in your lair.</span>"}
. += {"<span class='cult'>Simply click and hold on a victim, and then drag their sprite on the vassal rack.</span>"}
. += {"<span class='cult'>Make sure that the victim is handcuffed, or else they can simply run away or resist, as the process is not instant.</span>"}
. += {"<span class='cult'>To convert the victim, simply click on the vassal rack itself. Sharp weapons work faster than other tools.</span>"}
/* if(user.mind.has_antag_datum(ANTAG_DATUM_VASSAL)
. += {"<span class='cult'>This is the vassal rack, which allows your master to thrall crewmembers into his minions.\n
Aid your master in bringing their victims here and keeping them secure.\n
You can secure victims to the vassal rack by click dragging the victim onto the rack while it is secured</span>"} */
/obj/structure/bloodsucker/vassalrack/MouseDrop_T(atom/movable/O, mob/user)
if(!O.Adjacent(src) || O == user || !isliving(O) || !isliving(user) || useLock || has_buckled_mobs() || user.incapacitated())
return
if(!anchored && user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
to_chat(user, "<span class='danger'>Until this rack is secured in place, it cannot serve its purpose.</span>")
return
// PULL TARGET: Remember if I was pullin this guy, so we can restore this
var/waspulling = (O == owner.pulling)
var/wasgrabstate = owner.grab_state
// * MOVE! *
O.forceMove(drop_location())
// PULL TARGET: Restore?
if(waspulling)
owner.start_pulling(O, wasgrabstate, TRUE)
// NOTE: in bs_lunge.dm, we use [target.grabbedby(owner)], which simulates doing a grab action. We don't want that though...we're cutting directly back to where we were in a grab.
// Do Action!
useLock = TRUE
if(do_mob(user, O, 50))
attach_victim(O,user)
useLock = FALSE
/obj/structure/bloodsucker/vassalrack/AltClick(mob/user)
if(!has_buckled_mobs() || !isliving(user) || useLock)
return
// Attempt Release (Owner vs Non Owner)
var/mob/living/carbon/C = pick(buckled_mobs)
if(C)
if(user == owner)
unbuckle_mob(C)
else
user_unbuckle_mob(C,user)
/obj/structure/bloodsucker/vassalrack/proc/attach_victim(mob/living/M, mob/living/user)
// Standard Buckle Check
if(!buckle_mob(M)) // force=TRUE))
return
// Attempt Buckle
user.visible_message("<span class='notice'>[user] straps [M] into the rack, immobilizing them.</span>", \
"<span class='boldnotice'>You secure [M] tightly in place. They won't escape you now.</span>")
playsound(src.loc, 'sound/effects/pop_expl.ogg', 25, 1)
//M.forceMove(drop_location()) <--- CANT DO! This cancels the buckle_mob() we JUST did (even if we foced the move)
M.setDir(2)
density = TRUE
var/matrix/m180 = matrix(M.transform)
m180.Turn(180)//90)//180
animate(M, transform = m180, time = 2)
M.pixel_y = -2 //M.get_standard_pixel_y_offset(120)//180)
update_icon()
// Torture Stuff
convert_progress = 2 // Goes down unless you start over.
disloyalty_confirm = FALSE // New guy gets the chance to say NO if he's special.
disloyalty_offered = FALSE // Prevents spamming torture window.
/obj/structure/bloodsucker/vassalrack/user_unbuckle_mob(mob/living/M, mob/user)
// Attempt Unbuckle
if(!user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
if(M == user)
M.visible_message("<span class='danger'>[user] tries to release themself from the rack!</span>",\
"<span class='danger'>You attempt to release yourself from the rack!</span>") // For sound if not seen --> "<span class='italics'>You hear a squishy wet noise.</span>")
else
M.visible_message("<span class='danger'>[user] tries to pull [M] rack!</span>",\
"<span class='danger'>[user] attempts to release you from the rack!</span>") // For sound if not seen --> "<span class='italics'>You hear a squishy wet noise.</span>")
if(!do_mob(user, M, 100))
return
// Did the time. Now try to do it.
..()
unbuckle_mob(M)
/obj/structure/bloodsucker/vassalrack/unbuckle_mob(mob/living/buckled_mob, force = FALSE)
if(!..())
return
var/matrix/m180 = matrix(buckled_mob.transform)
m180.Turn(180)//-90)//180
animate(buckled_mob, transform = m180, time = 2)
buckled_mob.pixel_y = buckled_mob.get_standard_pixel_y_offset(180)
src.visible_message(text("<span class='danger'>[buckled_mob][buckled_mob.stat==DEAD?"'s corpse":""] slides off of the rack.</span>"))
density = FALSE
buckled_mob.AdjustKnockdown(30)
update_icon()
useLock = FALSE // Failsafe
/obj/structure/bloodsucker/vassalrack/attackby(obj/item/W, mob/user, params)
if(has_buckled_mobs()) // Attack w/weapon vs guy standing there? Don't do an attack.
attack_hand(user)
return FALSE
return ..()
/obj/structure/bloodsucker/vassalrack/attack_hand(mob/user)
//. = ..() // Taken from sacrificial altar in divine.dm
//if(.)
// return
// Go away. Torturing.
if(useLock)
return
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
// CHECK ONE: Am I claiming this? Is it in the right place?
if(istype(bloodsuckerdatum) && !owner)
if(!bloodsuckerdatum.lair)
to_chat(user, "<span class='danger'>You don't have a lair. Claim a coffin to make that location your lair.</span>")
if(bloodsuckerdatum.lair != get_area(src))
to_chat(user, "<span class='danger'>You may only activate this structure in your lair: [bloodsuckerdatum.lair].</span>")
return
switch(alert(user,"Do you wish to afix this structure here? Be aware you wont be able to unsecure it anymore","Secure [src]","Yes", "No"))
if("Yes")
owner = user
density = FALSE
anchored = TRUE
return //No, you cant move this ever again
// No One Home
if(!has_buckled_mobs())
return
// CHECK TWO: Am I a non-bloodsucker?
var/mob/living/carbon/C = pick(buckled_mobs)
if(!istype(bloodsuckerdatum))
// Try to release this guy
user_unbuckle_mob(C, user)
return
// Bloodsucker Owner! Let the boy go.
if(C.mind)
var/datum/antagonist/vassal/vassaldatum = C.mind.has_antag_datum(ANTAG_DATUM_VASSAL)
if (istype(vassaldatum) && vassaldatum.master == bloodsuckerdatum || C.stat >= DEAD)
unbuckle_mob(C)
useLock = FALSE // Failsafe
return
// Just torture the boy
torture_victim(user, C)
/obj/structure/bloodsucker/vassalrack/proc/torture_victim(mob/living/user, mob/living/target)
// Check Bloodmob/living/M, force = FALSE, check_loc = TRUE
if(user.blood_volume < convert_cost + 5)
to_chat(user, "<span class='notice'>You don't have enough blood to initiate the Dark Communion with [target].</span>")
return
// Prep...
useLock = TRUE
// Step One: Tick Down Conversion from 3 to 0
// Step Two: Break mindshielding/antag (on approve)
// Step Three: Blood Ritual
// Conversion Process
if(convert_progress > 0)
to_chat(user, "<span class='notice'>You prepare to initiate [target] into your service.</span>")
if(!do_torture(user,target))
to_chat(user, "<span class='danger'><i>The ritual has been interrupted!</i></span>")
else
convert_progress -- // Ouch. Stop. Don't.
// All done!
if(convert_progress <= 0)
// FAIL: Can't be Vassal
if(!SSticker.mode.can_make_vassal(target, user, display_warning=FALSE) && HAS_TRAIT(target, TRAIT_MINDSHIELD)) // If I'm an unconvertable Antag ONLY
to_chat(user, "<span class='danger'>[target] doesn't respond to your persuasion. It doesn't appear they can be converted to follow you, they either have a mindshield or their external loyalties are too difficult for you to break.<i>\[ALT+click to release\]</span>")
convert_progress ++ // Pop it back up some. Avoids wasting Blood on a lost cause.
// SUCCESS: All done!
else
if(RequireDisloyalty(target))
to_chat(user, "<span class='boldwarning'>[target] has external loyalties! [target.p_they(TRUE)] will require more <i>persuasion</i> to break [target.p_them()] to your will!</span>")
else
to_chat(user, "<span class='notice'>[target] looks ready for the <b>Dark Communion</b>.</span>")
// Still Need More Persuasion...
else
to_chat(user, "<span class='notice'>[target] could use [convert_progress == 1?"a little":"some"] more <i>persuasion</i>.</span>")
useLock = FALSE
return
// Check: Mindshield & Antag
if(!disloyalty_confirm && RequireDisloyalty(target))
if(!do_disloyalty(user,target))
to_chat(user, "<span class='danger'><i>The ritual has been interrupted!</i></span>")
else if (!disloyalty_confirm)
to_chat(user, "<span class='danger'>[target] refuses to give into your persuasion. Perhaps a little more?</span>")
else
to_chat(user, "<span class='notice'>[target] looks ready for the <b>Dark Communion</b>.</span>")
useLock = FALSE
return
// Check: Blood
if(user.blood_volume < convert_cost)
to_chat(user, "<span class='notice'>You don't have enough blood to initiate the Dark Communion with [target].</span>")
useLock = FALSE
return
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
bloodsuckerdatum.AddBloodVolume(-convert_cost)
target.add_mob_blood(user)
user.visible_message("<span class='notice'>[user] marks a bloody smear on [target]'s forehead and puts a wrist up to [target.p_their()] mouth!</span>", \
"<span class='notice'>You paint a bloody marking across [target]'s forehead, place your wrist to [target.p_their()] mouth, and subject [target.p_them()] to the Dark Communion.</span>")
if(!do_mob(user, src, 50))
to_chat(user, "<span class='danger'><i>The ritual has been interrupted!</i></span>")
useLock = FALSE
return
// Convert to Vassal!
if(bloodsuckerdatum && bloodsuckerdatum.attempt_turn_vassal(target))
//remove_loyalties(target) // In case of Mindshield, or appropriate Antag (Traitor, Internal, etc)
//if (!target.buckled)
// to_chat(user, "<span class='danger'><i>The ritual has been interrupted!</i></span>")
// useLock = FALSE
// return
user.playsound_local(null, 'sound/effects/explosion_distant.ogg', 40, 1) // Play THIS sound for user only. The "null" is where turf would go if a location was needed. Null puts it right in their head.
target.playsound_local(null, 'sound/effects/explosion_distant.ogg', 40, 1) // Play THIS sound for user only. The "null" is where turf would go if a location was needed. Null puts it right in their head.
target.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, 1) // Play THIS sound for user only. The "null" is where turf would go if a location was needed. Null puts it right in their head.
target.Jitter(25)
target.emote("laugh")
//remove_victim(target) // Remove on CLICK ONLY!
useLock = FALSE
/obj/structure/bloodsucker/vassalrack/proc/do_torture(mob/living/user, mob/living/target, mult=1)
var/torture_time = 15 // Fifteen seconds if you aren't using anything. Shorter with weapons and such.
var/torture_dmg_brute = 2
var/torture_dmg_burn = 0
// Get Bodypart
var/target_string = ""
var/obj/item/bodypart/BP = null
if(iscarbon(target))
var/mob/living/carbon/C = target
BP = pick(C.bodyparts)
if(BP)
target_string += BP.name
// Get Weapon
var/obj/item/I = user.get_active_held_item()
if(!istype(I))
I = user.get_inactive_held_item()
// Create Strings
var/method_string = I?.attack_verb?.len ? pick(I.attack_verb) : pick("harmed","tortured","wrenched","twisted","scoured","beaten","lashed","scathed")
var/weapon_string = I ? I.name : pick("bare hands","hands","fingers","fists")
// Weapon Bonus + SFX
if(I)
torture_time -= I.force / 4
torture_dmg_brute += I.force / 4
//torture_dmg_burn += I.
if(I.sharpness == IS_SHARP)
torture_time -= 1
else if(I.sharpness == IS_SHARP_ACCURATE)
torture_time -= 2
if(istype(I, /obj/item/weldingtool))
var/obj/item/weldingtool/welder = I
welder.welding = TRUE
torture_time -= 5
torture_dmg_burn += 5
I.play_tool_sound(target)
torture_time = max(50, torture_time * 10) // Minimum 5 seconds.
// Now run process.
if(!do_mob(user, target, torture_time * mult))
return FALSE
// SUCCESS
if(I)
playsound(loc, I.hitsound, 30, 1, -1)
I.play_tool_sound(target)
target.visible_message("<span class='danger'>[user] has [method_string] [target]'s [target_string] with [user.p_their()] [weapon_string]!</span>", \
"<span class='userdanger'>[user] has [method_string] your [target_string] with [user.p_their()] [weapon_string]!</span>")
if(!target.is_muzzled())
target.emote("scream")
target.Jitter(5)
target.apply_damages(brute = torture_dmg_brute, burn = torture_dmg_burn, def_zone = (BP ? BP.body_zone : null)) // take_overall_damage(6,0)
return TRUE
/obj/structure/bloodsucker/vassalrack/proc/do_disloyalty(mob/living/user, mob/living/target)
// OFFER YES/NO NOW!
spawn(10)
if(useLock && target && target.client) // Are we still torturing? Did we cancel? Are they still here?
to_chat(user, "<span class='notice'>[target] has been given the opportunity for servitude. You await their decision...</span>")
var/alert_text = "You are being tortured! Do you want to give in and pledge your undying loyalty to [user]?"
/* if(HAS_TRAIT(target, TRAIT_MINDSHIELD))
alert_text += "\n\nYou will no longer be loyal to the station!"
if(SSticker.mode.AmValidAntag(target.mind)) */
alert_text += "\n\nYou will not lose your current objectives, but they come second to the will of your new master!"
switch(alert(target, alert_text,"THE HORRIBLE PAIN! WHEN WILL IT END?!","Yes, Master!", "NEVER!"))
if("Yes, Master!")
disloyalty_accept(target)
else
disloyalty_refuse(target)
if(!do_torture(user,target, 2))
return FALSE
// NOTE: We only remove loyalties when we're CONVERTED!
return TRUE
/obj/structure/bloodsucker/vassalrack/proc/RequireDisloyalty(mob/living/target)
return SSticker.mode.AmValidAntag(target.mind) //|| HAS_TRAIT(target, TRAIT_MINDSHIELD)
/obj/structure/bloodsucker/vassalrack/proc/disloyalty_accept(mob/living/target)
// FAILSAFE: Still on the rack?
if(!(locate(target) in buckled_mobs))
return
// NOTE: You can say YES after torture. It'll apply to next time.
disloyalty_confirm = TRUE
/*if(HAS_TRAIT(target, TRAIT_MINDSHIELD))
to_chat(target, "<span class='boldnotice'>You give in to the will of your torturer. If they are successful, you will no longer be loyal to the station!</span>")
*/
/obj/structure/bloodsucker/vassalrack/proc/disloyalty_refuse(mob/living/target)
// FAILSAFE: Still on the rack?
if(!(locate(target) in buckled_mobs))
return
// Failsafe: You already said YES.
if(disloyalty_confirm)
return
to_chat(target, "<span class='notice'>You refuse to give in! You <i>will not</i> break!</span>")
/obj/structure/bloodsucker/vassalrack/proc/remove_loyalties(mob/living/target)
// Find Mind Implant & Destroy
if(HAS_TRAIT(target, TRAIT_MINDSHIELD))
for(var/obj/item/implant/I in target.implants)
if(I.type == /obj/item/implant/mindshield)
I.removed(target,silent=TRUE)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/obj/structure/bloodsucker/candelabrum
name = "candelabrum"
desc = "It burns slowly, but doesn't radiate any heat."
icon = 'icons/obj/vamp_obj.dmi'
icon_state = "candelabrum"
light_color = "#66FFFF"//LIGHT_COLOR_BLUEGREEN // lighting.dm
light_power = 3
light_range = 0 // to 2
density = FALSE
anchored = FALSE
var/lit = FALSE
///obj/structure/bloodsucker/candelabrum/is_hot() // candle.dm
//return FALSE
/obj/structure/bloodsucker/candelabrum/Destroy()
STOP_PROCESSING(SSobj, src)
/obj/structure/bloodsucker/candelabrum/update_icon()
icon_state = "candelabrum[lit ? "_lit" : ""]"
/obj/structure/bloodsucker/candelabrum/examine(mob/user)
. = ..()
if((user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)) || isobserver(user))
. += {"<span class='cult'>This is a magical candle which drains at the sanity of mortals who are not under your command while it is active.</span>"}
. += {"<span class='cult'>You can alt click on it from any range to turn it on remotely, or simply be next to it and click on it to turn it on and off normally.</span>"}
/* if(user.mind.has_antag_datum(ANTAG_DATUM_VASSAL)
. += {"<span class='cult'>This is a magical candle which drains at the sanity of the fools who havent yet accepted your master, as long as it is active.\n
You can turn it on and off by clicking on it while you are next to it</span>"} */
/obj/structure/bloodsucker/candelabrum/attack_hand(mob/user)
var/datum/antagonist/bloodsucker/V = user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER) //I wish there was a better way to do this
var/datum/antagonist/vassal/T = user.mind.has_antag_datum(ANTAG_DATUM_VASSAL)
if(istype(V) || istype(T))
toggle()
/obj/structure/bloodsucker/candelabrum/AltClick(mob/user)
var/datum/antagonist/bloodsucker/V = user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
// Bloodsuckers can turn their candles on from a distance. SPOOOOKY.
if(istype(V))
toggle()
/obj/structure/bloodsucker/candelabrum/proc/toggle(mob/user)
lit = !lit
if(lit)
set_light(2, 3, "#66FFFF")
START_PROCESSING(SSobj, src)
else
set_light(0)
STOP_PROCESSING(SSobj, src)
update_icon()
/obj/structure/bloodsucker/candelabrum/process()
if(lit)
for(var/mob/living/carbon/human/H in viewers(7, src))
var/datum/antagonist/vassal/T = H.mind.has_antag_datum(ANTAG_DATUM_VASSAL)
var/datum/antagonist/bloodsucker/V = H.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if(V || T) //We dont want vassals or vampires affected by this
return
H.hallucination = 20
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "vampcandle", /datum/mood_event/vampcandle)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// OTHER THINGS TO USE: HUMAN BLOOD. /obj/effect/decal/cleanable/blood
/obj/item/restraints/legcuffs/beartrap/bloodsucker

View File

@@ -0,0 +1,88 @@
// Created by claiming a Coffin.
// THINGS TO SPAWN:
//
// /obj/effect/decal/cleanable/cobweb && /obj/effect/decal/cleanable/cobweb/cobweb2
// /obj/effect/decal/cleanable/generic
// /obj/effect/decal/cleanable/dirt/dust <-- Pretty cool, just stains the tile itself.
// /obj/effect/decal/cleanable/blood/old
/*
/area/
// All coffins assigned to this area
var/list/obj/structure/closet/crate/laircoffins = new list()
// Called by Coffin when an area is claimed as a vamp's lair
/area/proc/ClaimAsLair(/obj/structure/closet/crate/inClaimant)
set waitfor = FALSE // Don't make on_gain() wait for this function to finish. This lets this code run on the side.
laircoffins += laircoffins
sleep()
// Cancel!
if (laircoffins.len == 0)
return
*/
/datum/antagonist/bloodsucker/proc/RunLair()
set waitfor = FALSE // Don't make on_gain() wait for this function to finish. This lets this code run on the side.
while(!AmFinalDeath() && coffin && lair)
// WAit 2 min and Repeat
sleep(120)
// Coffin Moved SOMEHOW?
if(lair != get_area(coffin))
if(coffin)
coffin.UnclaimCoffin()
//lair = get_area(coffin)
break // DONE
var/list/turf/area_turfs = get_area_turfs(lair)
// Create Dirt etc.
var/turf/T_Dirty = pick(area_turfs)
if(T_Dirty && !T_Dirty.density)
// Default: Dirt
// CHECK: Cobweb already there?
//if (!locate(var/obj/effect/decal/cleanable/cobweb) in T_Dirty) // REMOVED! Cleanables don't stack.
// STEP ONE: COBWEBS
// CHECK: Wall to North?
var/turf/check_N = get_step(T_Dirty, NORTH)
if(istype(check_N, /turf/closed/wall))
// CHECK: Wall to West?
var/turf/check_W = get_step(T_Dirty, WEST)
if(istype(check_W, /turf/closed/wall))
new /obj/effect/decal/cleanable/cobweb (T_Dirty)
// CHECK: Wall to East?
var/turf/check_E = get_step(T_Dirty, EAST)
if(istype(check_E, /turf/closed/wall))
new /obj/effect/decal/cleanable/cobweb/cobweb2 (T_Dirty)
// STEP TWO: DIRT
new /obj/effect/decal/cleanable/dirt (T_Dirty)
// Find Animals in Area
/* if(rand(0,2) == 0)
var/mobCount = 0
var/mobMax = CLAMP(area_turfs.len / 25, 1, 4)
for (var/turf/T in area_turfs)
if(!T) continue
var/mob/living/simple_animal/SA = locate() in T
if(SA)
mobCount ++
if (mobCount >= mobMax) // Already at max
break
Spawn One
if(mobCount < mobMax)
Seek Out Location
while(area_turfs.len > 0)
var/turf/T = pick(area_turfs) // We use while&pick instead of a for/loop so it's random, rather than from the top of the list.
if(T && !T.density)
var/mob/living/simple_animal/SA = /mob/living/simple_animal/mouse // pick(/mob/living/simple_animal/mouse,/mob/living/simple_animal/mouse,/mob/living/simple_animal/mouse, /mob/living/simple_animal/hostile/retaliate/bat) //prob(300) /mob/living/simple_animal/mouse,
new SA (T)
break
area_turfs -= T*/
// NOTE: area_turfs is now cleared out!
if(coffin)
coffin.UnclaimCoffin()
// Done (somehow)
lair = null

View File

@@ -0,0 +1,174 @@
/datum/action/bloodsucker/targeted/brawn
name = "Brawn"//"Cellular Emporium"
desc = "Snap restraints with ease, or deal terrible damage with your bare hands."
button_icon_state = "power_strength"
bloodcost = 10
cooldown = 130
target_range = 1
power_activates_immediately = TRUE
message_Trigger = ""//"Whom will you subvert to your will?"
must_be_capacitated = TRUE
can_be_immobilized = TRUE
bloodsucker_can_buy = TRUE
// Level Up
var/upgrade_canLocker = FALSE
var/upgrade_canDoor = FALSE
/datum/action/bloodsucker/targeted/brawn/CheckCanUse(display_error)
. = ..()
if(!.)
return
. = TRUE
// Break Out of Restraints! (And then cancel)
if(CheckBreakRestraints())
//PowerActivatedSuccessfully() // PAY COST! BEGIN COOLDOWN!DEACTIVATE!
. = FALSE //return FALSE
// Throw Off Attacker! (And then cancel)
if(CheckEscapePuller())
//PowerActivatedSuccessfully() // PAY COST! BEGIN COOLDOWN!DEACTIVATE!
. = FALSE //return FALSE
/*if(CheckBreakLocker())
.= FALSE */
// Did we successfuly use power to BREAK CUFFS and/or ESCAPE PULLER and/or escape from a locker?
// Then PAY COST!
if(. == FALSE)
PowerActivatedSuccessfully() // PAY COST! BEGIN COOLDOWN!DEACTIVATE!
// NOTE: We use . = FALSE so that we can break cuffs AND throw off our attacker in one use!
//return TRUE
/datum/action/bloodsucker/targeted/brawn/CheckValidTarget(atom/A)
return isliving(A) || istype(A, /obj/machinery/door)
/datum/action/bloodsucker/targeted/brawn/CheckCanTarget(atom/A, display_error)
// DEFAULT CHECKS (Distance)
if(!..()) // Disable range notice for Brawn.
return FALSE
// Must outside Closet to target anyone!
if(!isturf(owner.loc))
return FALSE
// Check: Self
if(A == owner)
return FALSE
// Target Type: Living
if(isliving(A))
return TRUE
// Target Type: Door
else if(istype(A, /obj/machinery/door))
return TRUE
return ..() // yes, FALSE! You failed if you got here! BAD TARGET
/datum/action/bloodsucker/targeted/brawn/FireTargetedPower(atom/A)
// set waitfor = FALSE <---- DONT DO THIS!We WANT this power to hold up ClickWithPower(), so that we can unlock the power when it's done.
var/mob/living/carbon/target = A
var/mob/living/user = owner
// Target Type: Mob
if(isliving(target))
var/mob/living/carbon/user_C = user
var/hitStrength = user_C.dna.species.punchdamagehigh * 1.3 + 5
// Knockdown!
var/powerlevel = min(5, 1 + level_current)
if(rand(5 + powerlevel) >= 5)
target.visible_message("<span class='danger'>[user] lands a vicious punch, sending [target] away!</span>", \
"<span class='userdanger'>[user] has landed a horrifying punch on you, sending you flying!!</span>", null, COMBAT_MESSAGE_RANGE)
target.Knockdown(min(5, rand(10, 10 * powerlevel)) )
// Chance of KO
if(rand(6 + powerlevel) >= 6 && target.stat <= UNCONSCIOUS)
target.Unconscious(40)
// Attack!
playsound(get_turf(target), 'sound/weapons/punch4.ogg', 60, 1, -1)
user.do_attack_animation(target, ATTACK_EFFECT_SMASH)
var/obj/item/bodypart/affecting = target.get_bodypart(ran_zone(target.zone_selected))
target.apply_damage(hitStrength, BRUTE, affecting)
// Knockback
var/send_dir = get_dir(owner, target)
var/turf/T = get_ranged_target_turf(target, send_dir, powerlevel)
owner.newtonian_move(send_dir) // Bounce back in 0 G
target.throw_at(T, powerlevel, TRUE, owner) //new /datum/forced_movement(target, get_ranged_target_turf(target, send_dir, (hitStrength / 4)), 1, FALSE)
// Target Type: Door
else if(istype(target, /obj/machinery/door))
var/obj/machinery/door/D = target
playsound(get_turf(usr), 'sound/machines/airlock_alien_prying.ogg', 40, 1, -1)
to_chat(user, "<span class='notice'>You prepare to tear open [D].</span>")
if(do_mob(usr,target,25))
if (D.Adjacent(user))
to_chat(user, "<span class='notice'>You tear open the [D].</span>")
user.Stun(10)
user.do_attack_animation(D, ATTACK_EFFECT_SMASH)
playsound(get_turf(D), 'sound/effects/bang.ogg', 30, 1, -1)
D.open(2) // open(2) is like a crowbar or jaws of life.
// Target Type: Closet
/datum/action/bloodsucker/targeted/brawn/proc/CheckBreakRestraints()
if(!iscarbon(owner)) // || !owner.restrained()
return FALSE
// (NOTE: Just like biodegrade.dm, we only remove one thing per use //
// Destroy Cuffs
var/mob/living/carbon/user_C = owner
//message_admins("DEBUG3: attempt_cast() [name] / [user_C.handcuffed] ")
if(user_C.handcuffed)
var/obj/O = user_C.get_item_by_slot(SLOT_HANDCUFFED)
if(istype(O))
//user_C.visible_message("<span class='warning'>[user_C] attempts to remove [O]!</span>", \
// "<span class='warning'>You snap [O] like it's nothing!</span>")
user_C.clear_cuffs(O,TRUE)
playsound(get_turf(usr), 'sound/effects/grillehit.ogg', 80, 1, -1)
return TRUE
/* Doesnt work
// Destroy Straightjacket
if(ishuman(owner))
var/mob/living/carbon/human/user_H = owner
if(user_H.wear_suit && user_H.wear_suit.breakouttime)
var/obj/item/clothing/suit/straight_jacket/S = user_H.get_item_by_slot(ITEM_SLOT_ICLOTHING)
if(istype(S))
user_C.visible_message("<span class='warning'>[user_C] attempts to remove [S]!</span>", \
"<span class='warning'>You rip through [S] like it's nothing!</span>")
user_C.clear_cuffs(S,TRUE)
playsound(get_turf(usr), 'sound/effects/grillehit.ogg', 80, 1, -1)
return TRUE */
// Destroy Leg Cuffs
if(user_C.legcuffed)
var/obj/O = user_C.get_item_by_slot(SLOT_LEGCUFFED)
if(istype(O))
//user_C.visible_message("<span class='warning'>[user_C] attempts to remove [O]!</span>", \
// "<span class='warning'>You snap [O] like it's nothing!</span>")
user_C.clear_cuffs(O,TRUE)
playsound(get_turf(usr), 'sound/effects/grillehit.ogg', 80, 1, -1)
return TRUE
return FALSE
/datum/action/bloodsucker/targeted/brawn/proc/CheckEscapePuller()
if(!owner.pulledby)// || owner.pulledby.grab_state <= GRAB_PASSIVE)
return FALSE
var/mob/M = owner.pulledby
var/pull_power = M.grab_state
playsound(get_turf(M), 'sound/effects/woodhit.ogg', 75, 1, -1)
// Knock Down (if Living)
if (isliving(M))
var/mob/living/L = M
L.Knockdown(pull_power * 10 + 20)
// Knock Back (before Knockdown, which probably cancels pull)
var/send_dir = get_dir(owner, M)
var/turf/T = get_ranged_target_turf(M, send_dir, pull_power)
owner.newtonian_move(send_dir) // Bounce back in 0 G
M.throw_at(T, pull_power, TRUE, owner, FALSE) // Throw distance based on grab state! Harder grabs punished more aggressively.
// /proc/log_combat(atom/user, atom/target, what_done, atom/object=null, addition=null)
log_combat(owner, M, "used Brawn power")
owner.visible_message("<span class='warning'>[owner] tears free of [M]'s grasp!</span>", \
"<span class='warning'>You shrug off [M]'s grasp!</span>")
owner.pulledby = null // It's already done, but JUST IN CASE.
return TRUE
/* Doesnt work
/datum/action/bloodsucker/targeted/brawn/proc/CheckBreakLocker()
if(!istype(owner.loc, /obj/structure/closet))
return FALSE
playsound(get_turf(owner), 'sound/machines/airlock_alien_prying.ogg', 40, 1, -1)
if(do_mob(owner ,target, 25))
var/obj/structure/closet/C = owner.loc
to_chat(owner, "<span class='notice'>You prepare to tear open the [C].</span>")
owner.do_attack_animation(C, ATTACK_EFFECT_SMASH)
playsound(get_turf(C), 'sound/effects/bang.ogg', 30, 1, -1)
C.bust_open()
return TRUE
*/

View File

@@ -0,0 +1,57 @@
/datum/action/bloodsucker/cloak
name = "Cloak of Darkness"
desc = "Blend into the shadows and become invisible to the untrained eye."
button_icon_state = "power_cloak"
bloodcost = 5
cooldown = 50
bloodsucker_can_buy = TRUE
amToggle = TRUE
warn_constant_cost = TRUE
var/light_min = 0.5 // If lum is above this, no good.
/datum/action/bloodsucker/cloak/CheckCanUse(display_error)
. = ..()
if(!.)
return
// Must be Dark
var/turf/T = owner.loc
if(istype(T) && T.get_lumcount() > light_min)
to_chat(owner, "<span class='warning'>This area is not dark enough to blend in</span>")
return FALSE
return TRUE
/datum/action/bloodsucker/cloak/ActivatePower()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
var/mob/living/user = owner
var/was_running = (user.m_intent == MOVE_INTENT_RUN)
if(was_running)
user.toggle_move_intent()
ADD_TRAIT(user, TRAIT_NORUNNING, "cloak of darkness")
while(bloodsuckerdatum && ContinueActive(user) || user.m_intent == MOVE_INTENT_RUN)
// Pay Blood Toll (if awake)
owner.alpha = max(0, owner.alpha - min(75, 20 + 15 * level_current))
bloodsuckerdatum.AddBloodVolume(-0.2)
sleep(5) // Check every few ticks that we haven't disabled this power
// Return to Running (if you were before)
if(was_running && user.m_intent != MOVE_INTENT_RUN)
user.toggle_move_intent()
/datum/action/bloodsucker/cloak/ContinueActive(mob/living/user, mob/living/target)
if (!..())
return FALSE
if(user.stat == !CONSCIOUS) // Must be CONSCIOUS
to_chat(owner, "<span class='warning'>Your cloak failed due to you falling unconcious! </span>")
return FALSE
var/turf/T = owner.loc // Must be DARK
if(istype(T) && T.get_lumcount() > light_min)
to_chat(owner, "<span class='warning'>Your cloak failed due to there being too much light!</span>")
return FALSE
return TRUE
/datum/action/bloodsucker/cloak/DeactivatePower(mob/living/user = owner, mob/living/target)
..()
REMOVE_TRAIT(user, TRAIT_NORUNNING, "cloak of darkness")
user.alpha = 255

View File

@@ -0,0 +1,324 @@
/datum/action/bloodsucker/feed
name = "Feed"
desc = "Draw the heartsblood of living victims in your grasp.<br><b>None/Passive:</b> Feed silently and unnoticed by your victim.<br><b>Aggressive: </b>Subdue your target quickly."
button_icon_state = "power_feed"
bloodcost = 0
cooldown = 30
amToggle = TRUE
bloodsucker_can_buy = TRUE
can_be_staked = TRUE
cooldown_static = TRUE
var/notice_range = 2 // Distance before silent feeding is noticed.
var/mob/living/feed_target // So we can validate more than just the guy we're grappling.
var/target_grappled = FALSE // If you started grappled, then ending it will end your Feed.
/datum/action/bloodsucker/feed/CheckCanUse(display_error)
. = ..()
if(!.)
return
// Wearing mask
var/mob/living/L = owner
if (L.is_mouth_covered())
if (display_error)
to_chat(owner, "<span class='warning'>You cannot feed with your mouth covered! Remove your mask.</span>")
return FALSE
// Find my Target!
if (!FindMyTarget(display_error)) // Sets feed_target within after Validating
return FALSE
// Not in correct state
// DONE!
return TRUE
/datum/action/bloodsucker/feed/proc/ValidateTarget(mob/living/target, display_error) // Called twice: validating a subtle victim, or validating your grapple victim.
// Bloodsuckers + Animals MUST be grabbed aggressively!
if (!owner.pulling || target == owner.pulling && owner.grab_state < GRAB_AGGRESSIVE)
// NOTE: It's OKAY that we are checking if(!target) below, AFTER animals here. We want passive check vs animal to warn you first, THEN the standard warning.
// Animals:
if (isliving(target) && !iscarbon(target))
if (display_error)
to_chat(owner, "<span class='warning'>Lesser beings require a tighter grip.</span>")
return FALSE
// Bloodsuckers:
else if (iscarbon(target) && target.mind && target.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
if (display_error)
to_chat(owner, "<span class='warning'>Other Bloodsuckers will not fall for your subtle approach.</span>")
return FALSE
// Must have Target
if (!target) // || !ismob(target)
if (display_error)
to_chat(owner, "<span class='warning'>You must be next to or grabbing a victim to feed from them.</span>")
return FALSE
// Not even living!
if (!isliving(target) || issilicon(target))
if (display_error)
to_chat(owner, "<span class='warning'>You may only feed from living beings.</span>")
return FALSE
if (target.blood_volume <= 0)
if (display_error)
to_chat(owner, "<span class='warning'>Your victim has no blood to take.</span>")
return FALSE
if (ishuman(target))
var/mob/living/carbon/human/H = target
if(NOBLOOD in H.dna.species.species_traits)// || owner.get_blood_id() != target.get_blood_id())
if (display_error)
to_chat(owner, "<span class='warning'>Your victim's blood is not suitable for you to take.</span>")
return FALSE
return TRUE
// If I'm not grabbing someone, find me someone nearby.
/datum/action/bloodsucker/feed/proc/FindMyTarget(display_error)
// Default
feed_target = null
target_grappled = FALSE
// If you are pulling a mob, that's your target. If you don't like it, then release them.
if (owner.pulling && ismob(owner.pulling))
// Check grapple target Valid
if (!ValidateTarget(owner.pulling, display_error)) // Grabbed targets display error.
return FALSE
target_grappled = TRUE
feed_target = owner.pulling
return TRUE
// Find Targets
var/list/mob/living/seen_targets = view(1, owner)
var/list/mob/living/seen_mobs = list()
for(var/mob/living/M in seen_targets)
if (isliving(M) && M != owner)
seen_mobs += M
// None Seen!
if (seen_mobs.len == 0)
if (display_error)
to_chat(owner, "<span class='warning'>You must be next to or grabbing a victim to feed from them.</span>")
return FALSE
// Check Valids...
var/list/targets_valid = list()
var/list/targets_dead = list()
for(var/mob/living/M in seen_mobs)
// Check adjecent Valid target
if (M != owner && ValidateTarget(M, display_error = FALSE)) // Do NOT display errors. We'll be doing this again in CheckCanUse(), which will rule out grabbed targets.
// Prioritize living, but remember dead as backup
if (M.stat < DEAD)
targets_valid += M
else
targets_dead += M
// No Living? Try dead.
if (targets_valid.len == 0 && targets_dead.len > 0)
targets_valid = targets_dead
// No Targets
if (targets_valid.len == 0)
// Did I see targets? Then display at least one error
if (seen_mobs.len > 1)
if (display_error)
to_chat(owner, "<span class='warning'>None of these are valid targets to feed from subtly.</span>")
else
ValidateTarget(seen_mobs[1], display_error)
return FALSE
// Too Many Targets
//else if (targets.len > 1)
// if (display_error)
// to_chat(owner, "<span class='warning'>You are adjecent to too many witnesses. Either grab your victim or move away.</span>")
// return FALSE
// One Target!
else
feed_target = pick(targets_valid)//targets[1]
return TRUE
/datum/action/bloodsucker/feed/ActivatePower()
// set waitfor = FALSE <---- DONT DO THIS!We WANT this power to hold up Activate(), so Deactivate() can happen after.
var/mob/living/target = feed_target // Stored during CheckCanUse(). Can be a grabbed OR adjecent character.
var/mob/living/user = owner
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
// Am I SECRET or LOUD? It stays this way the whole time! I must END IT to try it the other way.
var/amSilent = (!target_grappled || owner.grab_state <= GRAB_PASSIVE) // && iscarbon(target) // Non-carbons (animals) not passive. They go straight into aggressive.
// Initial Wait
var/feed_time = (amSilent ? 45 : 25) - (2.5 * level_current)
feed_time = max(15, feed_time)
if (amSilent)
to_chat(user, "<span class='notice'>You lean quietly toward [target] and secretly draw out your fangs...</span>")
else
to_chat(user, "<span class='warning'>You pull [target] close to you and draw out your fangs...</span>")
if (!do_mob(user, target, feed_time,0,1,extra_checks=CALLBACK(src, .proc/ContinueActive, user, target)))//sleep(10)
to_chat(user, "<span class='warning'>Your feeding was interrupted.</span>")
//DeactivatePower(user,target)
return
// Put target to Sleep (Bloodsuckers are immune to their own bite's sleep effect)
if (!amSilent)
ApplyVictimEffects(target) // Sleep, paralysis, immobile, unconscious, and mute
if(target.stat <= UNCONSCIOUS)
sleep(1)
// Wait, then Cancel if Invalid
if (!ContinueActive(user,target)) // Cancel. They're gone.
//DeactivatePower(user,target)
return
// Pull Target Close
if (!target.density) // Pull target to you if they don't take up space.
target.Move(user.loc)
// Broadcast Message
if (amSilent)
//if (!iscarbon(target))
// user.visible_message("<span class='notice'>[user] shifts [target] closer to [user.p_their()] mouth.</span>", \
// "<span class='notice'>You secretly slip your fangs into [target]'s flesh.</span>", \
// vision_distance = 2, ignored_mobs=target) // Only people who AREN'T the target will notice this action.
//else
var/deadmessage = target.stat == DEAD ? "" : " <i>[target.p_they(TRUE)] looks dazed, and will not remember this.</i>"
user.visible_message("<span class='notice'>[user] puts [target]'s wrist up to [user.p_their()] mouth.</span>", \
"<span class='notice'>You secretly slip your fangs into [target]'s wrist.[deadmessage]</span>", \
vision_distance = notice_range, ignored_mobs=target) // Only people who AREN'T the target will notice this action.
// Warn Feeder about Witnesses...
var/was_unnoticed = TRUE
for(var/mob/living/M in viewers(notice_range, owner))
if(M != owner && M != target && iscarbon(M) && M.mind && !M.has_unlimited_silicon_privilege && !M.eye_blind && !M.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
was_unnoticed = FALSE
break
if (was_unnoticed)
to_chat(user, "<span class='notice'>You think no one saw you...</span>")
else
to_chat(user, "<span class='warning'>Someone may have noticed...</span>")
else // /atom/proc/visible_message(message, self_message, blind_message, vision_distance, ignored_mobs)
user.visible_message("<span class='warning'>[user] closes [user.p_their()] mouth around [target]'s neck!</span>", \
"<span class='warning'>You sink your fangs into [target]'s neck.</span>")
// My mouth is full!
ADD_TRAIT(user, TRAIT_MUTE, "bloodsucker_feed")
// Begin Feed Loop
var/warning_target_inhuman = FALSE
var/warning_target_dead = FALSE
var/warning_full = FALSE
var/warning_target_bloodvol = 99999
var/amount_taken = 0
var/blood_take_mult = amSilent ? 0.3 : 1 // Quantity to take per tick, based on Silent or not.
var/was_alive = target.stat < DEAD && ishuman(target)
// Activate Effects
//target.add_trait(TRAIT_MUTE, "bloodsucker_victim") // <----- Make mute a power you buy?
// FEEEEEEEEED!!! //
bloodsuckerdatum.poweron_feed = TRUE
while (bloodsuckerdatum && target && active)
//user.mobility_flags &= ~MOBILITY_MOVE // user.canmove = 0 // Prevents spilling blood accidentally.
// Abort? A bloody mistake.
if (!do_mob(user, target, 20, 0, 0, extra_checks=CALLBACK(src, .proc/ContinueActive, user, target)))
// May have disabled Feed during do_mob
if (!active || !ContinueActive(user, target))
break
if (amSilent)
to_chat(user, "<span class='warning'>Your feeding has been interrupted...but [target.p_they()] didn't seem to notice you.<span>")
else
to_chat(user, "<span class='warning'>Your feeding has been interrupted!</span>")
user.visible_message("<span class='danger'>[user] is ripped from [target]'s throat. [target.p_their(TRUE)] blood sprays everywhere!</span>", \
"<span class='userdanger'>Your teeth are ripped from [target]'s throat. [target.p_their(TRUE)] blood sprays everywhere!</span>")
// Deal Damage to Target (should have been more careful!)
if (iscarbon(target))
var/mob/living/carbon/C = target
C.bleed(15)
playsound(get_turf(target), 'sound/effects/splat.ogg', 40, 1)
if (ishuman(target))
var/mob/living/carbon/human/H = target
H.bleed_rate += 5
target.add_splatter_floor(get_turf(target))
user.add_mob_blood(target) // Put target's blood on us. The donor goes in the ( )
target.add_mob_blood(target)
target.take_overall_damage(10,0)
target.emote("scream")
// Killed Target?
if (was_alive)
CheckKilledTarget(user,target)
return
///////////////////////////////////////////////////////////
// Handle Feeding! User & Victim Effects (per tick)
bloodsuckerdatum.HandleFeeding(target, blood_take_mult)
amount_taken += amSilent ? 0.3 : 1
if (!amSilent)
ApplyVictimEffects(target) // Sleep, paralysis, immobile, unconscious, and mute
if (amount_taken > 5 && target.stat < DEAD && ishuman(target))
SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood) // GOOD // in bloodsucker_life.dm
///////////////////////////////////////////////////////////
// Not Human?
if (!ishuman(target))
SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_bad) // BAD // in bloodsucker_life.dm
if (!warning_target_inhuman)
to_chat(user, "<span class='notice'>You recoil at the taste of a lesser lifeform.</span>")
warning_target_inhuman = TRUE
// Dead Blood?
if (target.stat >= DEAD)
if (ishuman(target))
SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_dead) // BAD // in bloodsucker_life.dm
if (!warning_target_dead)
to_chat(user, "<span class='notice'>Your victim is dead. [target.p_their(TRUE)] blood barely nourishes you.</span>")
warning_target_dead = TRUE
// Full?
if (!warning_full && user.blood_volume >= bloodsuckerdatum.maxBloodVolume)
to_chat(user, "<span class='notice'>You are full. Further blood will be wasted.</span>")
warning_full = TRUE
// Blood Remaining? (Carbons/Humans only)
if (iscarbon(target) && !target.AmBloodsucker(1))
if (target.blood_volume <= BLOOD_VOLUME_BAD && warning_target_bloodvol > BLOOD_VOLUME_BAD)
to_chat(user, "<span class='warning'>Your victim's blood volume is fatally low!</span>")
else if (target.blood_volume <= BLOOD_VOLUME_OKAY && warning_target_bloodvol > BLOOD_VOLUME_OKAY)
to_chat(user, "<span class='warning'>Your victim's blood volume is dangerously low.</span>")
else if (target.blood_volume <= BLOOD_VOLUME_SAFE && warning_target_bloodvol > BLOOD_VOLUME_SAFE)
to_chat(user, "<span class='notice'>Your victim's blood is at an unsafe level.</span>")
warning_target_bloodvol = target.blood_volume // If we had a warning to give, it's been given by now.
// Done?
if (target.blood_volume <= 0)
to_chat(user, "<span class='notice'>You have bled your victim dry.</span>")
break
// Blood Gulp Sound
owner.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, 1) // Play THIS sound for user only. The "null" is where turf would go if a location was needed. Null puts it right in their head.
// DONE!
//DeactivatePower(user,target)
if (amSilent)
to_chat(user, "<span class='notice'>You slowly release [target]'s wrist." + (target.stat == 0 ? " [target.p_their(TRUE)] face lacks expression, like you've already been forgotten.</span>" : ""))
else
user.visible_message("<span class='warning'>[user] unclenches their teeth from [target]'s neck.</span>", \
"<span class='warning'>You retract your fangs and release [target] from your bite.</span>")
// /proc/log_combat(atom/user, atom/target, what_done, atom/object=null, addition=null)
log_combat(owner, target, "fed on blood", addition="(and took [amount_taken] blood)")
// Killed Target?
if (was_alive)
CheckKilledTarget(user,target)
/datum/action/bloodsucker/feed/proc/CheckKilledTarget(mob/living/user, mob/living/target)
// Bad Vampire. You shouldn't do that.
if (target && target.stat >= DEAD && ishuman(target))
SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankkilled", /datum/mood_event/drankkilled) // BAD // in bloodsucker_life.dm
/datum/action/bloodsucker/feed/ContinueActive(mob/living/user, mob/living/target)
return ..() && target && (!target_grappled || user.pulling == target)// Active, and still Antag,
// NOTE: We only care about pulling if target started off that way. Mostly only important for Aggressive feed.
/datum/action/bloodsucker/feed/proc/ApplyVictimEffects(mob/living/target)
// Bloodsuckers not affected by "the Kiss" of another vampire
if (!target.mind || !target.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
target.Unconscious(50,0)
target.Knockdown(40 + 5 * level_current,1)
// NOTE: THis is based on level of power!
if (ishuman(target))
target.adjustStaminaLoss(5, forced = TRUE)// Base Stamina Damage
/datum/action/bloodsucker/feed/DeactivatePower(mob/living/user = owner, mob/living/target)
..() // activate = FALSE
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
// No longer Feeding
if (bloodsuckerdatum)
bloodsuckerdatum.poweron_feed = FALSE
feed_target = null
// My mouth is no longer full
REMOVE_TRAIT(owner, TRAIT_MUTE, "bloodsucker_feed")
// Let me move immediately
user.update_canmove()

View File

@@ -0,0 +1,54 @@
/datum/action/bloodsucker/fortitude
name = "Fortitude"//"Cellular Emporium"
desc = "Withstand egregious physical wounds and walk away from attacks that would stun, pierce, and dismember lesser beings. You cannot run while active."
button_icon_state = "power_fortitude"
bloodcost = 5
cooldown = 80
bloodsucker_can_buy = TRUE
amToggle = TRUE
warn_constant_cost = TRUE
var/this_resist // So we can raise and lower your brute resist based on what your level_current WAS.
/datum/action/bloodsucker/fortitude/ActivatePower()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
var/mob/living/user = owner
to_chat(user, "<span class='notice'>Your flesh, skin, and muscles become as steel.</span>")
// Traits & Effects
ADD_TRAIT(user, TRAIT_PIERCEIMMUNE, "fortitude")
ADD_TRAIT(user, TRAIT_NODISMEMBER, "fortitude")
ADD_TRAIT(user, TRAIT_STUNIMMUNE, "fortitude")
ADD_TRAIT(user, TRAIT_NORUNNING, "fortitude")
if (ishuman(owner))
var/mob/living/carbon/human/H = owner
this_resist = max(0.3, 0.7 - level_current * 0.1)
H.physiology.brute_mod *= this_resist//0.5
H.physiology.burn_mod *= this_resist//0.5
// Stop Running (Taken from /datum/quirk/nyctophobia in negative.dm)
var/was_running = (user.m_intent == MOVE_INTENT_RUN)
if(was_running)
user.toggle_move_intent()
while(bloodsuckerdatum && ContinueActive(user) || user.m_intent == MOVE_INTENT_RUN)
// Pay Blood Toll (if awake)
if (user.stat == CONSCIOUS)
bloodsuckerdatum.AddBloodVolume(-0.5) // Used to be 0.3 blood per 2 seconds, but we're making it more expensive to keep on.
sleep(20) // Check every few ticks that we haven't disabled this power
// Return to Running (if you were before)
if(was_running && user.m_intent != MOVE_INTENT_RUN)
user.toggle_move_intent()
/datum/action/bloodsucker/fortitude/DeactivatePower(mob/living/user = owner, mob/living/target)
..()
// Restore Traits & Effects
REMOVE_TRAIT(user, TRAIT_PIERCEIMMUNE, "fortitude")
REMOVE_TRAIT(user, TRAIT_NODISMEMBER, "fortitude")
REMOVE_TRAIT(user, TRAIT_STUNIMMUNE, "fortitude")
REMOVE_TRAIT(user, TRAIT_NORUNNING, "fortitude")
if (ishuman(owner))
var/mob/living/carbon/human/H = owner
H.physiology.brute_mod /= this_resist//0.5
H.physiology.burn_mod /= this_resist//0.5

View File

@@ -0,0 +1,115 @@
/datum/action/bloodsucker/gohome
name = "Vanishing Act"
desc = "As dawn aproaches, disperse into mist and return directly to your Lair.<br><b>WARNING:</b> You will drop <b>ALL</b> of your possessions if observed by mortals."
button_icon_state = "power_gohome"
background_icon_state_on = "vamp_power_off_oneshot" // Even though this never goes off.
background_icon_state_off = "vamp_power_off_oneshot"
bloodcost = 25
cooldown = 99999 // It'll never come back.
amToggle = FALSE
amSingleUse = TRUE
bloodsucker_can_buy = FALSE // You only get this if you've claimed a lair, and only just before sunrise.
can_use_in_torpor = TRUE
must_be_capacitated = TRUE
can_be_immobilized = TRUE
/datum/action/bloodsucker/gohome/CheckCanUse(display_error)
. = ..()
if(!.)
return
// Have No Lair (NOTE: You only got this power if you had a lair, so this means it's destroyed)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if (!istype(bloodsuckerdatum) || !bloodsuckerdatum.coffin)
if (display_error)
to_chat(owner, "<span class='warning'>Your coffin has been destroyed!</span>")
return FALSE
return TRUE
/datum/action/bloodsucker/gohome/ActivatePower()
var/mob/living/carbon/user = owner
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
// IMPORTANT: Check for lair at every step! It might get destroyed.
to_chat(user, "<span class='notice'>You focus on separating your consciousness from your physical form...</span>")
// STEP ONE: Flicker Lights
for(var/obj/machinery/light/L in view(3, get_turf(owner))) // /obj/machinery/light/proc/flicker(var/amount = rand(10, 20))
L.flicker(5)
playsound(get_turf(owner), 'sound/effects/singlebeat.ogg', 20, 1)
sleep(50)
for(var/obj/machinery/light/L in view(3, get_turf(owner))) // /obj/machinery/light/proc/flicker(var/amount = rand(10, 20))
L.flicker(5)
playsound(get_turf(owner), 'sound/effects/singlebeat.ogg', 40, 1)
sleep(50)
for(var/obj/machinery/light/L in view(6, get_turf(owner))) // /obj/machinery/light/proc/flicker(var/amount = rand(10, 20))
L.flicker(5)
playsound(get_turf(owner), 'sound/effects/singlebeat.ogg', 60, 1)
// ( STEP TWO: Lights OFF? )
// CHECK: Still have Coffin?
if (!istype(bloodsuckerdatum) || !bloodsuckerdatum.coffin)
to_chat(user, "<span class='warning'>Your coffin has been destroyed! You no longer have a destination.</span>")
return FALSE
if (!owner)
return
// SEEN?: (effects ONLY if there are witnesses! Otherwise you just POOF)
// NOTE: Stolen directly from statue.dm, thanks guys!
var/am_seen = FALSE // Do Effects (seen by anyone)
var/drop_item = FALSE // Drop Stuff (seen by non-vamp)
if (isturf(owner.loc)) // Only check if I'm not in a Locker or something.
// A) Check for Darkness (we can just leave)
var/turf/T = get_turf(user)
if(T && T.lighting_object && T.get_lumcount()>= 0.1)
// B) Check for Viewers
for(var/mob/living/M in viewers(owner))
if(M != owner && isliving(M) && M.mind && !M.has_unlimited_silicon_privilege && !M.eye_blind) // M.client <--- add this in after testing!
am_seen = TRUE
if (!M.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
drop_item = TRUE
break
// LOSE CUFFS
if(user.handcuffed)
var/obj/O = user.handcuffed
user.dropItemToGround(O)
if(user.legcuffed)
var/obj/O = user.legcuffed
user.dropItemToGround(O)
// SEEN!
if (drop_item)
// DROP: Clothes, held items, and cuffs etc
// NOTE: Taken from unequip_everything() in inventory.dm. We need to
// *force* all items to drop, so we had to just gut the code out of it.
var/list/items = list()
items |= user.get_equipped_items()
for(var/I in items)
user.dropItemToGround(I,TRUE)
for(var/obj/item/I in owner.held_items) // drop_all_held_items()
user.dropItemToGround(I, TRUE)
if (am_seen)
// POOF EFFECTS
playsound(get_turf(owner), 'sound/magic/summon_karp.ogg', 60, 1)
var/datum/effect_system/steam_spread/puff = new /datum/effect_system/steam_spread/()
puff.effect_type = /obj/effect/particle_effect/smoke/vampsmoke
puff.set_up(3, 0, get_turf(owner))
puff.start()
// TELEPORT: Move to Coffin & Close it!
do_teleport(owner, bloodsuckerdatum.coffin, no_effects = TRUE, forced = TRUE, channel = TELEPORT_CHANNEL_QUANTUM) // in teleport.dm?
// SLEEP
user.resting = TRUE
//user.Unconscious(30,0)
user.Stun(30,1)
// CLOSE LID: If fail, force me in.
if (!bloodsuckerdatum.coffin.close(owner))
bloodsuckerdatum.coffin.insert(owner) // Puts me inside.
// The following was taken from close() proc in closets.dm
// (but we had to do it this way because there is no way to force entry)
playsound(bloodsuckerdatum.coffin.loc, bloodsuckerdatum.coffin.close_sound, 15, 1, -3)
bloodsuckerdatum.coffin.opened = FALSE
bloodsuckerdatum.coffin.density = TRUE
bloodsuckerdatum.coffin.update_icon()
// Lock Coffin
bloodsuckerdatum.coffin.LockMe(owner)
// ( STEP FIVE: Create animal at prev location? )
//var/mob/living/simple_animal/SA = /mob/living/simple_animal/hostile/retaliate/bat // pick(/mob/living/simple_animal/mouse,/mob/living/simple_animal/mouse,/mob/living/simple_animal/mouse, /mob/living/simple_animal/hostile/retaliate/bat) //prob(300) /mob/living/simple_animal/mouse,
//new SA (owner.loc)

View File

@@ -0,0 +1,85 @@
// Level 1: Speed to location
// Level 2: Dodge Bullets
// Level 3: Stun People Passed
/datum/action/bloodsucker/targeted/haste
name = "Immortal Haste"
desc = "Dash somewhere with supernatural speed. Those nearby may be knocked away, stunned, or left empty-handed."
button_icon_state = "power_speed"
bloodcost = 6
cooldown = 30
target_range = 15
power_activates_immediately = TRUE
message_Trigger = ""//"Whom will you subvert to your will?"
bloodsucker_can_buy = TRUE
must_be_capacitated = TRUE
/datum/action/bloodsucker/targeted/haste/CheckCanUse(display_error)
. = ..()
if(!.)
return
// Being Grabbed
if(owner.pulledby && owner.pulledby.grab_state >= GRAB_AGGRESSIVE)
if(display_error)
to_chat(owner, "<span class='warning'>You're being grabbed!</span>")
return FALSE
if(!owner.has_gravity(owner.loc)) //We dont want people to be able to use this to fly around in space
if(display_error)
to_chat(owner, "<span class='warning'>You cant dash while floating!</span>")
return FALSE
return TRUE
/datum/action/bloodsucker/targeted/haste/CheckValidTarget(atom/A)
return isturf(A) || A.loc != owner.loc // Anything will do, if it's not me or my square
/datum/action/bloodsucker/targeted/haste/CheckCanTarget(atom/A, display_error)
// DEFAULT CHECKS (Distance)
if (!..())
return FALSE
// Check: Range
//if (!(A in view(target_range, get_turf(owner))))
// return FALSE
return TRUE
/datum/action/bloodsucker/targeted/haste/FireTargetedPower(atom/A)
// set waitfor = FALSE <---- DONT DO THIS!We WANT this power to hold up ClickWithPower(), so that we can unlock the power when it's done.
var/mob/living/user = owner
var/turf/T = isturf(A) ? A : get_turf(A)
// Pulled? Not anymore.
owner.pulledby = null
// Step One: Heatseek toward Target's Turf
walk_to(owner, T, 0, 0.01, 20) // NOTE: this runs in the background! to cancel it, you need to use walk(owner.current,0), or give them a new path.
playsound(get_turf(owner), 'sound/weapons/punchmiss.ogg', 25, 1, -1)
var/safety = 20
while(get_turf(owner) != T && safety > 0 && !(isliving(target) && target.Adjacent(owner)))
user.canmove = FALSE //Dont move while doing the thing, or itll break
safety --
// Did I get knocked down?
if(owner && owner.incapacitated(ignore_restraints=TRUE, ignore_grab=TRUE))// owner.incapacitated())
// We're gonna cancel. But am I on the ground? Spin me!
if(user.resting)
var/send_dir = get_dir(owner, T)
new /datum/forced_movement(owner, get_ranged_target_turf(owner, send_dir, 1), 1, FALSE)
owner.spin(10)
break
// Spin/Stun people we pass.
//var/mob/living/newtarget = locate(/mob/living) in oview(1, owner)
var/list/mob/living/foundtargets = list()
for(var/mob/living/newtarget in oview(1, owner))
if (newtarget && newtarget != target && !(newtarget in foundtargets))//!newtarget.IsKnockdown())
if (rand(0, 5) < level_current)
playsound(get_turf(newtarget), "sound/weapons/punch[rand(1,4)].ogg", 15, 1, -1)
newtarget.Knockdown(10 + level_current * 5)
if(newtarget.IsStun())
newtarget.spin(10,1)
if (rand(0,4))
newtarget.drop_all_held_items()
foundtargets += newtarget
sleep(1)
if(user)
user.update_canmove() //Let the poor guy move again
/datum/action/bloodsucker/targeted/haste/DeactivatePower(mob/living/user = owner, mob/living/target)
..() // activate = FALSE
user.update_canmove()

View File

@@ -0,0 +1,83 @@
// Level 1: Grapple level 2
// Level 2: Grapple 3 from Behind
// Level 3: Grapple 3 from Shadows
/datum/action/bloodsucker/targeted/lunge
name = "Predatory Lunge"
desc = "Spring at your target and aggressively grapple them without warning. Attacks from concealment or the rear may even knock them down."
button_icon_state = "power_lunge"
bloodcost = 10
cooldown = 100
target_range = 3
power_activates_immediately = TRUE
message_Trigger = ""//"Whom will you subvert to your will?"
must_be_capacitated = TRUE
bloodsucker_can_buy = TRUE
/datum/action/bloodsucker/targeted/lunge/CheckCanUse(display_error)
if(!..(display_error))// DEFAULT CHECKS
return FALSE
// Being Grabbed
if(owner.pulledby && owner.pulledby.grab_state >= GRAB_AGGRESSIVE)
if(display_error)
to_chat(owner, "<span class='warning'>You're being grabbed!</span>")
return FALSE
if(!owner.has_gravity(owner.loc))//TODO figure out how to check if theyre able to move while in nograv
if(display_error)
to_chat(owner, "<span class='warning'>You cant lunge while floating!</span>")
return FALSE
return TRUE
/datum/action/bloodsucker/targeted/lunge/CheckValidTarget(atom/A)
return isliving(A)
/datum/action/bloodsucker/targeted/lunge/CheckCanTarget(atom/A, display_error)
// Check: Self
if(target == owner)
return FALSE
// Check: Range
//if (!(target in view(target_range, get_turf(owner))))
// if (display_error)
// to_chat(owner, "<span class='warning'>Your victim is too far away.</span>")
// return FALSE
// DEFAULT CHECKS (Distance)
if(!..())
return FALSE
// Check: Turf
var/mob/living/L = A
if(!isturf(L.loc))
return FALSE
return TRUE
/datum/action/bloodsucker/targeted/lunge/FireTargetedPower(atom/A)
// set waitfor = FALSE <---- DONT DO THIS!We WANT this power to hold up ClickWithPower(), so that we can unlock the power when it's done.
var/mob/living/carbon/target = A
var/turf/T = get_turf(target)
// Clear Vars
owner.pulling = null
// Will we Knock them Down?
var/do_knockdown = !is_A_facing_B(target,owner) || owner.alpha <= 0 || istype(owner.loc, /obj/structure/closet)
// CAUSES: Target has their back to me, I'm invisible, or I'm in a Closet
// Step One: Heatseek toward Target's Turf
walk_towards(owner, T, 0.1, 10) // NOTE: this runs in the background! to cancel it, you need to use walk(owner.current,0), or give them a new path.
addtimer(CALLBACK(owner, .proc/_walk, 0), 2 SECONDS)
if(get_turf(owner) != T && !(isliving(target) && target.Adjacent(owner)) && owner.incapacitated() && owner.resting)
var/send_dir = get_dir(owner, T)
new /datum/forced_movement(owner, get_ranged_target_turf(owner, send_dir, 1), 1, FALSE)
owner.spin(10)
// Step Two: Check if I'm at/adjectent to Target's CURRENT turf (not original...that was just a destination)
sleep(1)
if(target.Adjacent(owner))
// LEVEL 2: If behind target, mute or unconscious!
if(do_knockdown) // && level_current >= 1)
target.Knockdown(15 + 10 * level_current,1)
target.adjustStaminaLoss(40 + 10 * level_current)
// Cancel Walk (we were close enough to contact them)
walk(owner, 0)
target.Stun(10,1) //Without this the victim can just walk away
target.grabbedby(owner) // Taken from mutations.dm under changelings
target.grippedby(owner, instant = TRUE) //instant aggro grab
/datum/action/bloodsucker/targeted/lunge/DeactivatePower(mob/living/user = owner, mob/living/target)
..() // activate = FALSE
user.update_canmove()

View File

@@ -0,0 +1,97 @@
// WITHOUT THIS POWER:
//
// - Mid-Blood: SHOW AS PALE
// - Low-Blood: SHOW AS DEAD
// - No Heartbeat
// - Examine shows actual blood
// - Thermal homeostasis (ColdBlooded)
// WITH THIS POWER:
// - Normal body temp -- remove Cold Blooded (return on deactivate)
// -
/datum/action/bloodsucker/masquerade
name = "Masquerade"
desc = "Feign the vital signs of a mortal, and escape both casual and medical notice as the monster you truly are."
button_icon_state = "power_human"
bloodcost = 10
cooldown = 50
amToggle = TRUE
bloodsucker_can_buy = TRUE
warn_constant_cost = TRUE
can_use_in_torpor = TRUE // Masquerade is maybe the only one that can do this. It stops your healing.
cooldown_static = TRUE
// NOTE: Firing off vulgar powers disables your Masquerade!
/*/datum/action/bloodsucker/masquerade/CheckCanUse(display_error)
if(!..(display_error))// DEFAULT CHECKS
return FALSE
// DONE!
return TRUE
*/
/datum/action/bloodsucker/masquerade/ActivatePower()
var/mob/living/user = owner
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
to_chat(user, "<span class='notice'>Your heart beats falsely within your lifeless chest. You may yet pass for a mortal.</span>")
to_chat(user, "<span class='warning'>Your vampiric healing is halted while imitating life.</span>")
// Remove ColdBlooded & Hard/SoftCrit
REMOVE_TRAIT(user, TRAIT_COLDBLOODED, "bloodsucker")
REMOVE_TRAIT(user, TRAIT_NOHARDCRIT, "bloodsucker")
REMOVE_TRAIT(user, TRAIT_NOSOFTCRIT, "bloodsucker")
var/obj/item/organ/heart/vampheart/H = user.getorganslot(ORGAN_SLOT_HEART)
// WE ARE ALIVE! //
bloodsuckerdatum.poweron_masquerade = TRUE
while(bloodsuckerdatum && ContinueActive(user))
// HEART
if (istype(H))
H.FakeStart()
// PASSIVE (done from LIFE)
// Don't Show Pale/Dead on low blood
// Don't vomit food
// Don't Heal
// Pay Blood Toll (if awake)
if (user.stat == CONSCIOUS)
bloodsuckerdatum.AddBloodVolume(-0.2)
sleep(20) // Check every few ticks that we haven't disabled this power
/datum/action/bloodsucker/masquerade/ContinueActive(mob/living/user)
// Disable if unable to use power anymore.
//if (user.stat == DEAD || user.blood_volume <= 0) // not conscious or soft critor uncon, just dead
// return FALSE
return ..() // Active, and still Antag
/datum/action/bloodsucker/masquerade/DeactivatePower(mob/living/user = owner, mob/living/target)
..() // activate = FALSE
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
bloodsuckerdatum.poweron_masquerade = FALSE
ADD_TRAIT(user, TRAIT_COLDBLOODED, "bloodsucker")
ADD_TRAIT(user, TRAIT_NOHARDCRIT, "bloodsucker")
ADD_TRAIT(user, TRAIT_NOSOFTCRIT, "bloodsucker")
// HEART
var/obj/item/organ/heart/H = user.getorganslot(ORGAN_SLOT_HEART)
H.Stop()
to_chat(user, "<span class='notice'>Your heart beats one final time, while your skin dries out and your icy pallor returns.</span>")

View File

@@ -0,0 +1,114 @@
// * MEZMERIZE
// LOVE: Target falls in love with you. Being harmed directly causes them harm if they see it?
// STAY: Target will do everything they can to stand in the same place.
// FOLLOW: Target follows you, spouting random phrases from their history (or maybe Poly's or NPC's vocab?)
// ATTACK: Target finds a nearby non-Bloodsucker victim to attack.
/datum/action/bloodsucker/targeted/mesmerize
name = "Mesmerize"
desc = "Dominate the mind of a mortal who can see your eyes."
button_icon_state = "power_mez"
bloodcost = 30
cooldown = 200
target_range = 1
power_activates_immediately = FALSE
message_Trigger = "Whom will you subvert to your will?"
must_be_capacitated = TRUE
bloodsucker_can_buy = TRUE
/datum/action/bloodsucker/targeted/mesmerize/CheckCanUse(display_error)
. = ..()
if(!.)
return
if (!owner.getorganslot(ORGAN_SLOT_EYES))
if (display_error)
to_chat(owner, "<span class='warning'>You have no eyes with which to mesmerize.</span>")
return FALSE
// Check: Eyes covered?
var/mob/living/L = owner
if (istype(L) && L.is_eyes_covered() || !isturf(owner.loc))
if (display_error)
to_chat(owner, "<span class='warning'>Your eyes are concealed from sight.</span>")
return FALSE
return TRUE
/datum/action/bloodsucker/targeted/mesmerize/CheckValidTarget(atom/A)
return iscarbon(A)
/datum/action/bloodsucker/targeted/mesmerize/CheckCanTarget(atom/A,display_error)
// Check: Self
if (A == owner)
return FALSE
var/mob/living/carbon/target = A // We already know it's carbon due to CheckValidTarget()
// Bloodsucker
if (target.mind && target.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
if (display_error)
to_chat(owner, "<span class='warning'>Bloodsuckers are immune to [src].</span>")
return FALSE
// Dead/Unconscious
if (target.stat > CONSCIOUS)
if (display_error)
to_chat(owner, "<span class='warning'>Your victim is not [(target.stat == DEAD || HAS_TRAIT(target, TRAIT_FAKEDEATH))?"alive":"conscious"].</span>")
return FALSE
// Check: Target has eyes?
if (!target.getorganslot(ORGAN_SLOT_EYES))
if (display_error)
to_chat(owner, "<span class='warning'>They have no eyes!</span>")
return FALSE
// Check: Target blind?
if (target.eye_blind > 0)
if (display_error)
to_chat(owner, "<span class='warning'>Your victim's eyes are glazed over. They cannot perceive you.</span>")
return FALSE
// Check: Target See Me? (behind wall)
if (!(owner in view(target_range, get_turf(target))))
// Sub-Check: GET CLOSER
//if (!(owner in range(target_range, get_turf(target)))
// if (display_error)
// to_chat(owner, "<span class='warning'>You're too far from your victim.</span>")
if (display_error)
to_chat(owner, "<span class='warning'>You're too far outside your victim's view.</span>")
return FALSE
// Check: Facing target?
if (!is_A_facing_B(owner,target)) // in unsorted.dm
if (display_error)
to_chat(owner, "<span class='warning'>You must be facing your victim.</span>")
return FALSE
// Check: Target facing me?
if (!target.resting && !is_A_facing_B(target,owner))
if (display_error)
to_chat(owner, "<span class='warning'>Your victim must be facing you to see into your eyes.</span>")
return FALSE
return TRUE
/datum/action/bloodsucker/targeted/mesmerize/FireTargetedPower(atom/A)
// set waitfor = FALSE <---- DONT DO THIS!We WANT this power to hold up ClickWithPower(), so that we can unlock the power when it's done.
var/mob/living/carbon/target = A
var/mob/living/user = owner
if(istype(target))
target.Stun(40) //Utterly useless without this, its okay since there are so many checks to go through
target.silent = 45 //Shhhh little lamb
target.apply_status_effect(STATUS_EFFECT_MESMERIZE, 45) //So you cant rotate with combat mode, plus fancy status alert
if(do_mob(user, target, 40, 0, TRUE, extra_checks=CALLBACK(src, .proc/ContinueActive, user, target)))
PowerActivatedSuccessfully() // PAY COST! BEGIN COOLDOWN!
var/power_time = 90 + level_current * 12
target.silent = power_time + 20
target.apply_status_effect(STATUS_EFFECT_MESMERIZE, 100 + level_current * 15)
to_chat(user, "<span class='notice'>[target] is fixed in place by your hypnotic gaze.</span>")
target.Stun(power_time)
//target.silent += power_time / 10 // Silent isn't based on ticks.
target.next_move = world.time + power_time // <--- Use direct change instead. We want an unmodified delay to their next move // target.changeNext_move(power_time) // check click.dm
target.notransform = TRUE // <--- Fuck it. We tried using next_move, but they could STILL resist. We're just doing a hard freeze.
spawn(power_time)
if(istype(target))
target.notransform = FALSE
// They Woke Up! (Notice if within view)
if(istype(user) && target.stat == CONSCIOUS && (target in view(10, get_turf(user))) )
to_chat(user, "<span class='warning'>[target] has snapped out of their trance.</span>")
/datum/action/bloodsucker/targeted/mesmerize/ContinueActive(mob/living/user, mob/living/target)
return ..() && CheckCanUse() && CheckCanTarget(target)

View File

@@ -0,0 +1,124 @@
/datum/action/bloodsucker/targeted/trespass
name = "Trespass"
desc = "Become mist and advance two tiles in one direction, ignoring all obstacles except for walls. Useful for skipping past doors and barricades."
button_icon_state = "power_tres"
bloodcost = 10
cooldown = 60
amToggle = FALSE
//target_range = 2
bloodsucker_can_buy = TRUE
must_be_capacitated = FALSE
can_be_immobilized = TRUE
var/turf/target_turf // We need to decide where we're going based on where we clicked. It's not actually the tile we clicked.
/datum/action/bloodsucker/targeted/trespass/CheckCanUse(display_error)
. = ..()
if(!.)
return
if(owner.notransform || !get_turf(owner))
return FALSE
return TRUE
/datum/action/bloodsucker/targeted/trespass/CheckValidTarget(atom/A)
// Can't target my tile
if (A == get_turf(owner) || get_turf(A) == get_turf(owner))
return FALSE
return TRUE // All we care about is destination. Anything you click is fine.
/datum/action/bloodsucker/targeted/trespass/CheckCanTarget(atom/A, display_error)
// NOTE: Do NOT use ..()! We don't want to check distance or anything.
// Get clicked tile
var/final_turf = isturf(A) ? A : get_turf(A)
// Are either tiles WALLS?
var/turf/from_turf = get_turf(owner)
var/this_dir // = get_dir(from_turf, target_turf)
for (var/i=1 to 2)
// Keep Prev Direction if we've reached final turf
if (from_turf != final_turf)
this_dir = get_dir(from_turf, final_turf) // Recalculate dir so we don't overshoot on a diagonal.
from_turf = get_step(from_turf, this_dir)
// ERROR! Wall!
if (iswallturf(from_turf))
if (display_error)
var/wallwarning = (i == 1) ? "in the way" : "at your destination"
to_chat(owner, "<span class='warning'>There is a solid wall [wallwarning].</span>")
return FALSE
// Done
target_turf = from_turf
return TRUE
/datum/action/bloodsucker/targeted/trespass/FireTargetedPower(atom/A)
// set waitfor = FALSE <---- DONT DO THIS!We WANT this power to hold up ClickWithPower(), so that we can unlock the power when it's done.
// Find target turf, at or below Atom
var/mob/living/carbon/user = owner
var/turf/my_turf = get_turf(owner)
user.visible_message("<span class='warning'>[user]'s form dissipates into a cloud of mist!</span>", \
"<span class='notice'>You disspiate into formless mist.</span>")
// Effect Origin
playsound(get_turf(owner), 'sound/magic/summon_karp.ogg', 60, 1)
var/datum/effect_system/steam_spread/puff = new /datum/effect_system/steam_spread/()
puff.effect_type = /obj/effect/particle_effect/smoke/vampsmoke
puff.set_up(3, 0, my_turf)
puff.start()
var/mist_delay = max(5, 20 - level_current * 2.5) // Level up and do this faster.
// Freeze Me
user.next_move = world.time + mist_delay
user.Stun(mist_delay, ignore_canstun = TRUE)
user.notransform = TRUE
user.density = 0
var/invis_was = user.invisibility
user.invisibility = INVISIBILITY_MAXIMUM
// LOSE CUFFS
if(user.handcuffed)
var/obj/O = user.handcuffed
user.dropItemToGround(O)
if(user.legcuffed)
var/obj/O = user.legcuffed
user.dropItemToGround(O)
// Wait...
sleep(mist_delay / 2)
// Move & Freeze
if (isturf(target_turf))
do_teleport(owner, target_turf, no_effects=TRUE, channel = TELEPORT_CHANNEL_QUANTUM) // in teleport.dm?
user.next_move = world.time + mist_delay / 2
user.Stun(mist_delay / 2, ignore_canstun = TRUE)
// Wait...
sleep(mist_delay / 2)
// Un-Hide & Freeze
user.dir = get_dir(my_turf, target_turf)
user.next_move = world.time + mist_delay / 2
user.Stun(mist_delay / 2, ignore_canstun = TRUE)
user.notransform = FALSE
user.density = 1
user.invisibility = invis_was
// Effect Destination
playsound(get_turf(owner), 'sound/magic/summon_karp.ogg', 60, 1)
puff = new /datum/effect_system/steam_spread/()
puff.effect_type = /obj/effect/particle_effect/smoke/vampsmoke
puff.set_up(3, 0, target_turf)
puff.start()

View File

@@ -0,0 +1,163 @@
/datum/action/bloodsucker/veil
name = "Veil of Many Faces"
desc = "Disguise yourself in the illusion of another identity."
button_icon_state = "power_veil"
bloodcost = 15
cooldown = 100
amToggle = TRUE
bloodsucker_can_buy = TRUE
warn_constant_cost = TRUE
// Outfit Vars
var/list/original_items = list()
// Identity Vars
var/prev_gender
var/prev_skin_tone
var/prev_hair_style
var/prev_facial_hair_style
var/prev_hair_color
var/prev_facial_hair_color
var/prev_underwear
var/prev_undie_color
var/prev_undershirt
var/prev_shirt_color
var/prev_socks
var/prev_socks_color
var/prev_disfigured
var/list/prev_features // For lizards and such
/datum/action/bloodsucker/veil/CheckCanUse(display_error)
. = ..()
if(!.)
return
return TRUE
/datum/action/bloodsucker/veil/ActivatePower()
cast_effect() // POOF
//if (blahblahblah)
// Disguise_Outfit()
Disguise_FaceName()
/datum/action/bloodsucker/veil/proc/Disguise_Outfit()
// Step One: Back up original items
/datum/action/bloodsucker/veil/proc/Disguise_FaceName()
// Change Name/Voice
var/mob/living/carbon/human/H = owner
H.name_override = H.dna.species.random_name(H.gender)
H.name = H.name_override
H.SetSpecialVoice(H.name_override)
to_chat(owner, "<span class='warning'>You mystify the air around your person. Your identity is now altered.</span>")
// Store Prev Appearance
prev_gender = H.gender
prev_skin_tone = H.skin_tone
prev_hair_style = H.hair_style
prev_facial_hair_style = H.facial_hair_style
prev_hair_color = H.hair_color
prev_facial_hair_color = H.facial_hair_color
prev_underwear = H.underwear
prev_undie_color = H.undie_color
prev_undershirt = H.undershirt
prev_shirt_color = H.shirt_color
prev_socks = H.socks
prev_socks_color = H.socks_color
//prev_eye_color
prev_disfigured = HAS_TRAIT(H, TRAIT_DISFIGURED) // I was disfigured! //prev_disabilities = H.disabilities
prev_features = H.dna.features
// Change Appearance, not randomizing clothes colour, itll just be janky
H.gender = pick(MALE, FEMALE)
H.skin_tone = random_skin_tone()
H.hair_style = random_hair_style(H.gender)
H.facial_hair_style = pick(random_facial_hair_style(H.gender),"Shaved")
H.hair_color = random_short_color()
H.facial_hair_color = H.hair_color
H.underwear = random_underwear(H.gender)
H.undershirt = random_undershirt(H.gender)
H.socks = random_socks(H.gender)
//H.eye_color = random_eye_color()
REMOVE_TRAIT(H, TRAIT_DISFIGURED, null) //
H.dna.features = random_features()
// Apply Appearance
H.update_body() // Outfit and underware, also body.
//H.update_mutant_bodyparts() // Lizard tails etc
H.update_hair()
H.update_body_parts()
// Wait here til we deactivate power or go unconscious
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
while (ContinueActive(owner) && istype(bloodsuckerdatum))//active && owner && owner.stat == CONSCIOUS)
bloodsuckerdatum.AddBloodVolume(-0.2)
sleep(10)
// Wait for a moment if you fell unconscious...
if (owner && owner.stat > CONSCIOUS)
sleep(50)
/datum/action/bloodsucker/veil/DeactivatePower(mob/living/user = owner, mob/living/target)
..()
if (ishuman(user))
var/mob/living/carbon/human/H = user
// Revert Identity
H.UnsetSpecialVoice()
H.name_override = null
H.name = H.real_name
// Revert Appearance
H.gender = prev_gender
H.skin_tone = prev_skin_tone
H.hair_style = prev_hair_style
H.facial_hair_style = prev_facial_hair_style
H.hair_color = prev_hair_color
H.facial_hair_color = prev_facial_hair_color
H.underwear = prev_underwear
H.undie_color = prev_undie_color
H.undershirt = prev_undershirt
H.shirt_color = prev_shirt_color
H.socks = prev_socks
H.socks_color = prev_socks_color
//H.disabilities = prev_disabilities // Restore HUSK, CLUMSY, etc.
if (prev_disfigured)
ADD_TRAIT(H, TRAIT_DISFIGURED, "husk") // NOTE: We are ASSUMING husk. // H.status_flags |= DISFIGURED // Restore "Unknown" disfigurement
H.dna.features = prev_features
// Apply Appearance
H.update_body() // Outfit and underware, also body.
H.update_hair()
H.update_body_parts() // Body itself, maybe skin color?
cast_effect() // POOF
// CAST EFFECT // // General effect (poof, splat, etc) when you cast. Doesn't happen automatically!
/datum/action/bloodsucker/veil/proc/cast_effect()
// Effect
playsound(get_turf(owner), 'sound/magic/smoke.ogg', 20, 1)
var/datum/effect_system/steam_spread/puff = new /datum/effect_system/steam_spread/()
puff.effect_type = /obj/effect/particle_effect/smoke/vampsmoke
puff.set_up(3, 0, get_turf(owner))
puff.attach(owner) // OPTIONAL
puff.start()
owner.spin(8, 1) // Spin around like a loon.
/obj/effect/particle_effect/smoke/vampsmoke
opaque = FALSE
lifetime = 0
/obj/effect/particle_effect/smoke/vampsmoke/fade_out(frames = 6)
..(frames)

View File

@@ -0,0 +1,38 @@
/datum/action/bloodsucker/vassal/recuperate
name = "Sanguine Recuperation"
desc = "Slowly heal brute damage while active. This process is exhausting, and requires some of your tainted blood."
button_icon_state = "power_recup"
amToggle = TRUE
bloodcost = 5
cooldown = 100
/datum/action/bloodsucker/vassal/recuperate/CheckCanUse(display_error)
. = ..()
if(!.)
return
if (owner.stat >= DEAD)
return FALSE
return TRUE
/datum/action/bloodsucker/vassal/recuperate/ActivatePower()
to_chat(owner, "<span class='notice'>Your muscles clench and your skin crawls as your master's immortal blood knits your wounds and gives you stamina.</span>")
var/mob/living/carbon/C = owner
var/mob/living/carbon/human/H
if(ishuman(owner))
H = owner
while(ContinueActive(owner))
C.adjustBruteLoss(-1.5)
C.adjustFireLoss(-0.5)
C.adjustToxLoss(-2, forced = TRUE)
C.blood_volume -= 0.2
C.adjustStaminaLoss(-15)
// Stop Bleeding
if(istype(H) && H.bleed_rate > 0 && rand(20) == 0)
H.bleed_rate --
C.Jitter(5)
sleep(10)
// DONE!
//DeactivatePower(owner)
/datum/action/bloodsucker/vassal/recuperate/ContinueActive(mob/living/user, mob/living/target)
return ..() && user.stat <= DEAD && user.blood_volume > 500

View File

@@ -77,13 +77,16 @@
for(var/mob/living/M in viewers(5, src))
if(!is_servant_of_ratvar(M) && M != L)
M.flash_act()
if(iscultist(L))
if(iscultist(L)) //No longer stuns cultists, instead sets them on fire and burns them
to_chat(L, "<span class='heavy_brass'>\"Watch your step, wretch.\"</span>")
L.adjustBruteLoss(10)
L.Knockdown(80, FALSE)
L.adjustFireLoss(10)
L.Knockdown(20, FALSE)
L.adjust_fire_stacks(5) //Burn!
L.IgniteMob()
else
L.Stun(40)
L.visible_message("<span class='warning'>[src] appears around [L] in a burst of light!</span>", \
"<span class='userdanger'>[target_flashed ? "An unseen force":"The glowing sigil around you"] holds you in place!</span>")
L.Stun(40)
"<span class='userdanger'>[target_flashed ? "An unseen force":"The glowing sigil around you"] [iscultist(L) ? "painfully bursts into flames!" : "holds you in place!"]</span>")
L.apply_status_effect(STATUS_EFFECT_BELLIGERENT)
new /obj/effect/temp_visual/ratvar/sigil/transgression(get_turf(src))
qdel(src)

View File

@@ -193,15 +193,26 @@
L.visible_message("<span class='warning'>[L]'s eyes flare with dim light!</span>")
playsound(L, 'sound/weapons/sear.ogg', 50, TRUE)
else
L.visible_message("<span class='warning'>[L]'s eyes blaze with brilliant light!</span>", \
"<span class='userdanger'>Your vision suddenly screams with white-hot light!</span>")
L.Knockdown(15, TRUE, FALSE, 15)
L.apply_status_effect(STATUS_EFFECT_KINDLE)
L.flash_act(1, 1)
if(issilicon(target))
var/mob/living/silicon/S = L
S.emp_act(EMP_HEAVY)
if(iscultist(L))
if(!iscultist(L))
L.visible_message("<span class='warning'>[L]'s eyes blaze with brilliant light!</span>", \
"<span class='userdanger'>Your vision suddenly screams with white-hot light!</span>")
L.Knockdown(15, TRUE, FALSE, 15)
L.apply_status_effect(STATUS_EFFECT_KINDLE)
L.flash_act(1, 1)
if(issilicon(target))
var/mob/living/silicon/S = L
S.emp_act(EMP_HEAVY)
else //for Nar'sian weaklings
to_chat(L, "<span class='heavy_brass'>\"How does it feel to see the light, dog?\"</span>")
L.visible_message("<span class='warning'>[L]'s eyes flare with burning light!</span>", \
"<span class='userdanger'>Your vision suddenly screams with a flash of burning hot light!</span>") //Debuffs Narsian cultists hard + deals some burn instead of just hardstunning them; Only the confusion part can stack
L.flash_act(1,1)
if(iscarbon(target))
var/mob/living/carbon/C = L
C.stuttering = max(8, C.stuttering)
C.drowsyness = max(8, C.drowsyness)
C.confused += CLAMP(16 - C.confused, 0, 8)
C.apply_status_effect(STATUS_EFFECT_BELLIGERENT)
L.adjustFireLoss(15)
..()

View File

@@ -56,8 +56,14 @@
L.visible_message("<span class='warning'>[src] bounces off of [L], as if repelled by an unseen force!</span>")
else if(!..())
if(!L.anti_magic_check())
if(issilicon(L) || iscultist(L))
if(issilicon(L))
L.Knockdown(100)
else if(iscultist(L))
L.confused += CLAMP(10 - L.confused, 0, 5) // Spearthrow now confuses enemy cultists + just deals extra damage / sets on fire instead of hardstunning + damage
to_chat(L, "<span class ='userdanger'>[src] crashes into you with burning force, sending you reeling!</span>")
L.adjust_fire_stacks(2)
L.Knockdown(1)
L.IgniteMob()
else
L.Knockdown(40)
GLOB.clockwork_vitality += L.adjustFireLoss(bonus_burn * 3) //normally a total of 40 damage, 70 with ratvar

View File

@@ -1,5 +1,7 @@
#define MARAUDER_SLOWDOWN_PERCENTAGE 0.40 //Below this percentage of health, marauders will become slower
#define MARAUDER_SHIELD_REGEN_TIME 200 //In deciseconds, how long it takes for shields to regenerate after breaking
#define MARAUDER_SPACE_FULL_DAMAGE 6 //amount of damage per life tick while inside space
#define MARAUDER_SPACE_NEAR_DAMAGE 4 //amount of damage taking per Life() tick from being next to space.
//Clockwork marauder: A well-rounded frontline construct. Only one can exist for every two human servants.
/mob/living/simple_animal/hostile/clockwork/marauder
@@ -20,12 +22,14 @@
movement_type = FLYING
a_intent = INTENT_HARM
loot = list(/obj/item/clockwork/component/geis_capacitor/fallen_armor)
light_range = 2
light_power = 1.1
light_range = 3
light_power = 1.7
playstyle_string = "<span class='big bold'><span class='neovgre'>You are a clockwork marauder,</span></span><b> a well-rounded frontline construct of Ratvar. Although you have no \
unique abilities, you're a fearsome fighter in one-on-one combat, and your shield protects from projectiles!<br><br>Obey the Servants and do as they \
tell you. Your primary goal is to defend the Ark from destruction; they are your allies in this, and should be protected from harm.</b>"
tell you. Your primary goal is to defend the Ark from destruction; they are your allies in this, and should be protected from harm.</b> \
<span class='danger big'>Be warned, however, that you will rapidly decay near the void of space.</span>"
empower_string = "<span class='neovgre'>The Anima Bulwark's power flows through you! Your weapon will strike harder, your armor is sturdier, and your shield is more durable.</span>"
var/default_speed = 0
var/max_shield_health = 3
var/shield_health = 3 //Amount of projectiles that can be deflected within
var/shield_health_regen = 0 //When world.time equals this, shield health will regenerate
@@ -36,10 +40,21 @@
/mob/living/simple_animal/hostile/clockwork/marauder/Life()
..()
var/turf/T = get_turf(src)
var/turf/open/space/S = isspaceturf(T)? T : null
var/less_space_damage
if(!istype(S))
var/turf/open/space/nearS = locate() in oview(1)
if(nearS)
S = nearS
less_space_damage = TRUE
if(S)
to_chat(src, "<span class='userdanger'>The void of space drains Ratvar's Light from you! You feel yourself rapidly decaying. It would be wise to get back inside!</span>")
adjustBruteLoss(less_space_damage? MARAUDER_SPACE_NEAR_DAMAGE : MARAUDER_SPACE_FULL_DAMAGE)
if(!GLOB.ratvar_awakens && health / maxHealth <= MARAUDER_SLOWDOWN_PERCENTAGE)
speed = initial(speed) + 1 //Yes, this slows them down
speed = default_speed + 1 //Yes, this slows them down
else
speed = initial(speed)
speed = default_speed
if(shield_health < max_shield_health && world.time >= shield_health_regen)
shield_health_regen = world.time + MARAUDER_SHIELD_REGEN_TIME
to_chat(src, "<span class='neovgre'>Your shield has recovered, <b>[shield_health]</b> blocks remaining!</span>")

View File

@@ -87,9 +87,22 @@
object_path = /obj/item/clockwork/construct_chassis/clockwork_marauder
construct_type = /mob/living/simple_animal/hostile/clockwork/marauder
combat_construct = TRUE
var/static/recent_marauders = 0
var/static/time_since_last_marauder = 0
var/static/scaled_recital_time = 0
var/static/last_marauder = 0
/datum/clockwork_scripture/create_object/construct/clockwork_marauder/post_recital()
last_marauder = world.time
return ..()
/datum/clockwork_scripture/create_object/construct/clockwork_marauder/pre_recital()
if(!is_reebe(invoker.z))
if(!CONFIG_GET(flag/allow_clockwork_marauder_on_station))
to_chat(invoker, "<span class='brass'>This particular station is too far from the influence of the Hierophant Network. You can not summon a marauder here.</span>")
return FALSE
if(world.time < (last_marauder + CONFIG_GET(number/marauder_delay_non_reebe)))
to_chat(invoker, "<span class='brass'>The hierophant network is still strained from the last summoning of a marauder on a plane without the strong energy connection of Reebe to support it. \
You must wait another [DisplayTimeText((last_marauder + CONFIG_GET(number/marauder_delay_non_reebe)) - world.time, TRUE)]!</span>")
return FALSE
return ..()
/datum/clockwork_scripture/create_object/construct/clockwork_marauder/update_construct_limit()
var/human_servants = 0
@@ -98,27 +111,7 @@
var/mob/living/L = M.current
if(ishuman(L) && L.stat != DEAD)
human_servants++
construct_limit = round(CLAMP((human_servants / 4), 1, 3)) - recent_marauders //1 per 4 human servants, maximum of 3, reduced by recent marauder creation
if(recent_marauders)
to_chat(invoker, "<span class='warning'>The Hierophant Network is depleted by a summoning in the last [DisplayTimeText(MARAUDER_SCRIPTURE_SCALING_THRESHOLD, TRUE)] - limiting the number of available marauders by [recent_marauders]!</span>")
/datum/clockwork_scripture/create_object/construct/clockwork_marauder/pre_recital()
channel_time = initial(channel_time)
if(recent_marauders)
scaled_recital_time = min(recent_marauders * MARAUDER_SCRIPTURE_SCALING_TIME, MARAUDER_SCRIPTURE_SCALING_MAX)
to_chat(invoker, "<span class='warning'>The Hierophant Network is under strain from repeated summoning, making this scripture [DisplayTimeText(scaled_recital_time)] slower!</span>")
channel_time += scaled_recital_time
return TRUE
/datum/clockwork_scripture/create_object/construct/clockwork_marauder/scripture_effects()
. = ..()
recent_marauders++
addtimer(CALLBACK(GLOBAL_PROC, .proc/marauder_reset),MARAUDER_SCRIPTURE_SCALING_THRESHOLD)
/proc/marauder_reset()
var/datum/clockwork_scripture/create_object/construct/clockwork_marauder/CM = new()
CM.recent_marauders--
qdel(CM)
construct_limit = round(CLAMP((human_servants / 4), 1, 3)) //1 per 4 human servants, maximum of 3
//Summon Neovgre: Summon a very powerful combat mech that explodes when destroyed for massive damage.
/datum/clockwork_scripture/create_object/summon_arbiter
@@ -146,6 +139,6 @@
/datum/clockwork_scripture/create_object/summon_arbiter/check_special_requirements()
if(GLOB.neovgre_exists)
to_chat(invoker, "<span class='brass'>\"You've already got one...\"</span>")
to_chat(invoker, "<span class='nezbere'>\"Only one of my weapons may exist in this temporal stream!\"</span>")
return FALSE
return ..()

View File

@@ -438,21 +438,27 @@
target.visible_message("<span class='warning'>[L] starts to glow in a halo of light!</span>", \
"<span class='userdanger'>A feeling of warmth washes over you, rays of holy light surround your body and protect you from the flash of light!</span>")
else
to_chat(user, "<span class='cultitalic'>In an brilliant flash of red, [L] falls to the ground!</span>")
L.Knockdown(160)
L.adjustStaminaLoss(140) //Ensures hard stamcrit
L.flash_act(1,1)
if(issilicon(target))
var/mob/living/silicon/S = L
S.emp_act(EMP_HEAVY)
else if(iscarbon(target))
var/mob/living/carbon/C = L
C.silent += 6
C.stuttering += 15
C.cultslurring += 15
C.Jitter(15)
if(is_servant_of_ratvar(L))
if(!iscultist(L))
L.Knockdown(160)
L.adjustStaminaLoss(140) //Ensures hard stamcrit
L.flash_act(1,1)
if(issilicon(target))
var/mob/living/silicon/S = L
S.emp_act(EMP_HEAVY)
else if(iscarbon(target))
var/mob/living/carbon/C = L
C.silent += CLAMP(12 - C.silent, 0, 6)
C.stuttering += CLAMP(30 - C.stuttering, 0, 15)
C.cultslurring += CLAMP(30 - C.cultslurring, 0, 15)
C.Jitter(15)
else // cultstun no longer hardstuns + damages hostile cultists, instead debuffs them hard + deals some damage; debuffs for a bit longer since they don't add the clockie belligerent debuff
if(iscarbon(target))
var/mob/living/carbon/C = L
C.stuttering = max(10, C.stuttering)
C.drowsyness = max(10, C.drowsyness)
C.confused += CLAMP(20 - C.confused, 0, 10)
L.adjustBruteLoss(15)
to_chat(user, "<span class='cultitalic'>In an brilliant flash of red, [L] [iscultist(L) ? "writhes in pain" : "falls to the ground!"]</span>")
uses--
..()

View File

@@ -703,7 +703,10 @@
else if(!..())
if(!L.anti_magic_check())
if(is_servant_of_ratvar(L))
L.Knockdown(100)
to_chat(L, "<span class='cultlarge'>\"Kneel for me, scum\"</span>")
L.confused += CLAMP(10 - L.confused, 0, 5) //confuses and lightly knockdowns + damages hostile cultists instead of hardstunning like before
L.Knockdown(15)
L.adjustBruteLoss(10)
else
L.Knockdown(50)
break_spear(T)

View File

@@ -34,6 +34,12 @@
. = ..()
name_source = GLOB.commando_names
/datum/antagonist/ert/deathsquad/apply_innate_effects(mob/living/mob_override)
ADD_TRAIT(owner, TRAIT_DISK_VERIFIER, DEATHSQUAD_TRAIT)
/datum/antagonist/ert/deathsquad/remove_innate_effects(mob/living/mob_override)
REMOVE_TRAIT(owner, TRAIT_DISK_VERIFIER, DEATHSQUAD_TRAIT)
/datum/antagonist/ert/security // kinda handled by the base template but here for completion
/datum/antagonist/ert/security/amber

View File

@@ -611,10 +611,7 @@ This is here to make the tiles around the station mininuke change when it's arme
if(!fake)
return
var/ghost = isobserver(user)
var/captain = user.mind && user.mind.assigned_role == "Captain"
var/nukie = user.mind && user.mind.has_antag_datum(/datum/antagonist/nukeop)
if(ghost || captain || nukie)
if(isobserver(user) || HAS_TRAIT(user, TRAIT_DISK_VERIFIER) || (user.mind && HAS_TRAIT(user.mind, TRAIT_DISK_VERIFIER)))
. += "<span class='warning'>The serial numbers on [src] are incorrect.</span>"
/obj/item/disk/nuclear/attackby(obj/item/I, mob/living/user, params)
@@ -653,3 +650,7 @@ This is here to make the tiles around the station mininuke change when it's arme
/obj/item/disk/nuclear/fake
fake = TRUE
/obj/item/disk/nuclear/fake/obvious
name = "cheap plastic imitation of the nuclear authentication disk"
desc = "How anyone could mistake this for the real thing is beyond you."

View File

@@ -23,10 +23,12 @@
/datum/antagonist/nukeop/apply_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
update_synd_icons_added(M)
ADD_TRAIT(owner, TRAIT_DISK_VERIFIER, NUKEOP_TRAIT)
/datum/antagonist/nukeop/remove_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
update_synd_icons_removed(M)
REMOVE_TRAIT(owner, TRAIT_DISK_VERIFIER, NUKEOP_TRAIT)
/datum/antagonist/nukeop/proc/equip_op()
if(!ishuman(owner.current))
@@ -42,7 +44,6 @@
owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/ops.ogg',100,0)
to_chat(owner, "<span class='notice'>You are a [nuke_team ? nuke_team.syndicate_name : "syndicate"] agent!</span>")
owner.announce_objectives()
return
/datum/antagonist/nukeop/on_gain()
give_alias()

View File

@@ -33,6 +33,7 @@
melee_damage_lower = 30
melee_damage_upper = 30
see_in_dark = 8
blood_volume = 0 //No bleeding on getting shot, for skeddadles
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
bloodcrawl = BLOODCRAWL_EAT
var/playstyle_string = "<span class='big bold'>You are a slaughter demon,</span><B> a terrible creature from another realm. You have a single desire: To kill. \

View File

@@ -1,8 +1,8 @@
/datum/round_event_control/spawn_swarmer
name = "Spawn Swarmer Shell"
typepath = /datum/round_event/spawn_swarmer
weight = 7
max_occurrences = 1 //Only once okay fam
weight = 0
max_occurrences = 0
earliest_start = 30 MINUTES
min_players = 15

View File

@@ -505,6 +505,7 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list(
if(!is_station_level(F.z))
continue
F.obj_flags |= EMAGGED
F.update_icon()
to_chat(owner, "<span class='notice'>All thermal sensors on the station have been disabled. Fire alerts will no longer be recognized.</span>")
owner.playsound_local(owner, 'sound/machines/terminal_off.ogg', 50, 0)

View File

@@ -141,6 +141,10 @@
name = "Disintegrate"
spell_type = /obj/effect/proc_holder/spell/targeted/touch/disintegrate
/datum/spellbook_entry/nuclearfist
name = "Nuclear Fist"
spell_type = /obj/effect/proc_holder/spell/targeted/touch/nuclear_fist
/datum/spellbook_entry/disabletech
name = "Disable Tech"
spell_type = /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech

View File

@@ -177,6 +177,10 @@
owner.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock(null))
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/mind_transfer(null))
to_chat(owner, "<B>Your service has not gone unrewarded, however. Studying under [master.current.real_name], you have learned stealthy, robeless spells. You are able to cast knock and mindswap.")
if(APPRENTICE_MARTIAL)
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/touch/nuclear_fist(null))
H.put_in_hands(new /obj/item/book/granter/martial/plasma_fist(H))
to_chat(owner, "<B>Your service has not gone unrewarded, however. Studying under [master.current.real_name], you have learned mystical martial abilities. You are also able to use the Nuclear Fist at will.")
/datum/antagonist/wizard/apprentice/create_objectives()
var/datum/objective/protect/new_objective = new /datum/objective/protect

View File

@@ -154,6 +154,11 @@
var/partial_heat_capacity = total_heat_capacity*(share_volume/air.volume)
var/target_temperature
var/target_heat_capacity
// first calculate heat from radiation. there's an implied "* 1 tick" here.
// 0.05 magic multiplicand is, first, 0.1 deciseconds; second, half of the radiation's going right back into the gas.
var/share_constant = STEFANBOLTZMANN*(share_volume**(2/3))*0.05
// Minimizing temp to 4 billion is mostly to prevent -infinity temperatures.
var/heat = share_constant*(min(air.temperature,4000000000)**4)
if(isopenturf(target))
@@ -165,8 +170,8 @@
if((modeled_location.heat_capacity>0) && (partial_heat_capacity>0))
var/delta_temperature = air.temperature - target_temperature
var/heat = thermal_conductivity*delta_temperature* \
heat -= share_constant*(min(target_temperature,4000000000)**4)
heat += thermal_conductivity*delta_temperature* \
(partial_heat_capacity*target_heat_capacity/(partial_heat_capacity+target_heat_capacity))
air.temperature -= heat/total_heat_capacity
@@ -183,7 +188,8 @@
var/sharer_temperature_delta = 0
if((sharer_heat_capacity>0) && (partial_heat_capacity>0))
var/heat = thermal_conductivity*delta_temperature* \
heat -= share_constant*(min(target_temperature,4000000000)**4)
heat += thermal_conductivity*delta_temperature* \
(partial_heat_capacity*sharer_heat_capacity/(partial_heat_capacity+sharer_heat_capacity))
self_temperature_delta = -heat/total_heat_capacity
@@ -199,10 +205,12 @@
if((target.heat_capacity>0) && (partial_heat_capacity>0))
var/delta_temperature = air.temperature - target.temperature
var/heat = thermal_conductivity*delta_temperature* \
heat -= share_constant*(min(target.temperature,4000000000)**4)
heat += thermal_conductivity*delta_temperature* \
(partial_heat_capacity*target.heat_capacity/(partial_heat_capacity+target.heat_capacity))
air.temperature -= heat/total_heat_capacity
air.temperature = CLAMP(air.temperature,TCMB,INFINITY) // i have no idea why TCMB needs to be the min but i ain't changing it
update = TRUE
/datum/pipeline/proc/return_air()

View File

@@ -26,9 +26,7 @@
var/turf/T = loc
if(istype(T))
if(islava(T))
environment_temperature = 5000
else if(T.blocks_air)
if(T.blocks_air)
environment_temperature = T.temperature
else
var/turf/open/OT = T

View File

@@ -27,7 +27,7 @@
/datum/supply_pack/engine/am_shielding
name = "Antimatter Shielding Crate"
desc = "Contains ten Antimatter shields, somehow crammed into a crate."
desc = "Contains nine Antimatter shields, somehow crammed into a crate."
cost = 2500
contains = list(/obj/item/am_shielding_container,
/obj/item/am_shielding_container,
@@ -37,8 +37,7 @@
/obj/item/am_shielding_container,
/obj/item/am_shielding_container,
/obj/item/am_shielding_container,
/obj/item/am_shielding_container,
/obj/item/am_shielding_container) //10 shields: 3x3 containment and a core
/obj/item/am_shielding_container) //9 shields: 3x3 containment and a core
crate_name = "antimatter shielding crate"
/datum/supply_pack/engine/emitter

View File

@@ -47,6 +47,26 @@
/obj/item/storage/fancy/donut_box)
crate_name = "candy crate"
/datum/supply_pack/organic/exoticseeds
name = "Exotic Seeds Crate"
desc = "Any entrepreneuring botanist's dream. Contains twelve different seeds, including three replica-pod seeds and two mystery seeds!"
cost = 1500
contains = list(/obj/item/seeds/nettle,
/obj/item/seeds/replicapod,
/obj/item/seeds/replicapod,
/obj/item/seeds/replicapod,
/obj/item/seeds/plump,
/obj/item/seeds/liberty,
/obj/item/seeds/amanita,
/obj/item/seeds/reishi,
/obj/item/seeds/banana,
/obj/item/seeds/bamboo,
/obj/item/seeds/eggplant/eggy,
/obj/item/seeds/random,
/obj/item/seeds/random)
crate_name = "exotic seeds crate"
crate_type = /obj/structure/closet/crate/hydroponics
/datum/supply_pack/organic/food
name = "Food Crate"
desc = "Get things cooking with this crate full of useful ingredients! Contains a two dozen eggs, three bananas, and two bags of flour and rice, two cartons of milk, soymilk, as well as salt and pepper shakers, an enzyme and sugar bottle, and three slabs of monkeymeat."
@@ -156,6 +176,23 @@
/obj/item/storage/bag/tray)
crate_name = "fruit crate"
/datum/supply_pack/organic/grill
name = "Grilling Starter Kit"
desc = "Hey dad I'm Hungry. Hi Hungry I'm THE NEW GRILLING STARTER KIT ONLY 5000 BUX GET NOW! Contains a cooking grill and five fuel coal sheets."
cost = 3000
crate_type = /obj/structure/closet/crate
contains = list(/obj/item/stack/sheet/mineral/coal/five,
/obj/machinery/grill/unwrenched)
crate_name = "grilling starter kit crate"
/datum/supply_pack/organic/grillfuel
name = "Grilling Fuel Kit"
desc = "Contains coal and coal accessories. (Note: only ten coal sheets.)"
cost = 1000
crate_type = /obj/structure/closet/crate
contains = list(/obj/item/stack/sheet/mineral/coal/ten)
crate_name = "grilling fuel kit crate"
/datum/supply_pack/organic/cream_piee
name = "High-yield Clown-grade Cream Pie Crate"
desc = "Designed by Aussec's Advanced Warfare Research Division, these high-yield, Clown-grade cream pies are powered by a synergy of performance and efficiency. Guaranteed to provide maximum results."

View File

@@ -14,7 +14,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
//doohickeys for savefiles
var/path
var/default_slot = 1 //Holder so it doesn't default to slot 1, rather the last one used
var/max_save_slots = 8
var/max_save_slots = 16
//non-preference stuff
var/muted = 0
@@ -170,12 +170,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/prefered_security_department = SEC_DEPT_RANDOM
var/custom_species = null
//Quirk list
var/list/positive_quirks = list()
var/list/negative_quirks = list()
var/list/neutral_quirks = list()
//Quirk list
var/list/all_quirks = list()
var/list/character_quirks = list()
//Job preferences 2.0 - indexed by job title , no key or value implies never
var/list/job_preferences = list()
@@ -221,7 +217,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
load_path(C.ckey)
unlock_content = C.IsByondMember()
if(unlock_content)
max_save_slots = 16
max_save_slots = 24
var/loaded_preferences_successfully = load_preferences()
if(loaded_preferences_successfully)
if(load_character())
@@ -1200,7 +1196,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "<center><a href='?_src_=prefs;preference=trait;task=close'>Done</a></center>"
dat += "<hr>"
dat += "<center><b>Current quirks:</b> [all_quirks.len ? all_quirks.Join(", ") : "None"]</center>"
dat += "<center>[positive_quirks.len] / [MAX_QUIRKS] max positive quirks<br>\
dat += "<center>[GetPositiveQuirkCount()] / [MAX_QUIRKS] max positive quirks<br>\
<b>Quirk balance remaining:</b> [GetQuirkBalance()]</center><br>"
for(var/V in SSquirks.quirks)
var/datum/quirk/T = SSquirks.quirks[V]
@@ -1231,12 +1227,12 @@ GLOBAL_LIST_EMPTY(preferences_datums)
<font color='red'><b>LOCKED: [lock_reason]</b></font><br>"
else
if(has_quirk)
dat += "<b><font color='[font_color]'>[quirk_name]</font></b> - [initial(T.desc)] \
<a href='?_src_=prefs;preference=trait;task=update;trait=[quirk_name]'>[has_quirk ? "Lose" : "Take"] ([quirk_cost] pts.)</a><br>"
dat += "<a href='?_src_=prefs;preference=trait;task=update;trait=[quirk_name]'>[has_quirk ? "Remove" : "Take"] ([quirk_cost] pts.)</a> \
<b><font color='[font_color]'>[quirk_name]</font></b> - [initial(T.desc)]<br>"
else
dat += "<font color='[font_color]'>[quirk_name]</font> - [initial(T.desc)] \
<a href='?_src_=prefs;preference=trait;task=update;trait=[quirk_name]'>[has_quirk ? "Lose" : "Take"] ([quirk_cost] pts.)</a><br>"
dat += "<br><center><a href='?_src_=prefs;preference=trait;task=reset'>Reset Traits</a></center>"
dat += "<a href='?_src_=prefs;preference=trait;task=update;trait=[quirk_name]'>[has_quirk ? "Remove" : "Take"] ([quirk_cost] pts.)</a> \
<font color='[font_color]'>[quirk_name]</font> - [initial(T.desc)]<br>"
dat += "<br><center><a href='?_src_=prefs;preference=trait;task=reset'>Reset Quirks</a></center>"
var/datum/browser/popup = new(user, "mob_occupation", "<div align='center'>Quirk Preferences</div>", 900, 600) //no reason not to reuse the occupation window, as it's cleaner that way
popup.set_window_options("can_close=0")
@@ -1250,6 +1246,12 @@ GLOBAL_LIST_EMPTY(preferences_datums)
bal -= initial(T.value)
return bal
/datum/preferences/proc/GetPositiveQuirkCount()
. = 0
for(var/q in all_quirks)
if(SSquirks.quirk_points[q] > 0)
.++
/datum/preferences/Topic(href, href_list, hsrc) //yeah, gotta do this I guess..
. = ..()
if(href_list["close"])
@@ -1315,43 +1317,30 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/quirk = href_list["trait"]
if(!SSquirks.quirks[quirk])
return
for(var/V in SSquirks.quirk_blacklist) //V is a list
var/list/L = V
for(var/Q in all_quirks)
if((quirk in L) && (Q in L) && !(Q == quirk)) //two quirks have lined up in the list of the list of quirks that conflict with each other, so return (see quirks.dm for more details)
to_chat(user, "<span class='danger'>[quirk] is incompatible with [Q].</span>")
return
var/value = SSquirks.quirk_points[quirk]
if(value == 0)
if(quirk in neutral_quirks)
neutral_quirks -= quirk
all_quirks -= quirk
else
neutral_quirks += quirk
all_quirks += quirk
var/balance = GetQuirkBalance()
if(quirk in all_quirks)
if(balance + value < 0)
to_chat(user, "<span class='warning'>Refunding this would cause you to go below your balance!</span>")
return
all_quirks -= quirk
else
var/balance = GetQuirkBalance()
if(quirk in positive_quirks)
positive_quirks -= quirk
all_quirks -= quirk
else if(quirk in negative_quirks)
if(balance + value < 0)
to_chat(user, "<span class='warning'>Refunding this would cause you to go below your balance!</span>")
return
negative_quirks -= quirk
all_quirks -= quirk
else if(value > 0)
if(positive_quirks.len >= MAX_QUIRKS)
to_chat(user, "<span class='warning'>You can't have more than [MAX_QUIRKS] positive quirks!</span>")
return
if(balance - value < 0)
to_chat(user, "<span class='warning'>You don't have enough balance to gain this quirk!</span>")
return
positive_quirks += quirk
all_quirks += quirk
else
negative_quirks += quirk
all_quirks += quirk
if(GetPositiveQuirkCount() >= MAX_QUIRKS)
to_chat(user, "<span class='warning'>You can't have more than [MAX_QUIRKS] positive quirks!</span>")
return
if(balance - value < 0)
to_chat(user, "<span class='warning'>You don't have enough balance to gain this quirk!</span>")
return
all_quirks += quirk
SetQuirks(user)
if("reset")
all_quirks = list()
positive_quirks = list()
negative_quirks = list()
neutral_quirks = list()
SetQuirks(user)
else
SetQuirks(user)

View File

@@ -112,7 +112,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
if(current_version < 24 && S["feature_exhibitionist"])
var/datum/quirk/exhibitionism/E
var/quirk_name = initial(E.name)
neutral_quirks += quirk_name
all_quirks += quirk_name
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
@@ -386,9 +385,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
//Quirks
S["all_quirks"] >> all_quirks
S["positive_quirks"] >> positive_quirks
S["negative_quirks"] >> negative_quirks
S["neutral_quirks"] >> neutral_quirks
//Citadel code
S["feature_genitals_use_skintone"] >> features["genitals_use_skintone"]
@@ -519,10 +515,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
all_quirks = SANITIZE_LIST(all_quirks)
positive_quirks = SANITIZE_LIST(positive_quirks)
negative_quirks = SANITIZE_LIST(negative_quirks)
neutral_quirks = SANITIZE_LIST(neutral_quirks)
cit_character_pref_load(S)
return 1
@@ -598,9 +590,6 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
//Quirks
WRITE_FILE(S["all_quirks"] , all_quirks)
WRITE_FILE(S["positive_quirks"] , positive_quirks)
WRITE_FILE(S["negative_quirks"] , negative_quirks)
WRITE_FILE(S["neutral_quirks"] , neutral_quirks)
cit_character_pref_save(S)

View File

@@ -370,13 +370,25 @@
/obj/item/clothing/head/hotel
name = "Telegram cap"
desc = "A bright red cap warn by hotel staff. Or people who want to be a singing telegram"
icon_state = "telegramhat"
item_color = "telegramhat"
dog_fashion = null
icon_state = "telegram"
item_color = "telegram"
dog_fashion = /datum/dog_fashion/head/telegram
/obj/item/clothing/head/colour
name = "Singer cap"
desc = "A light white hat that has bands of color. Just makes you want to sing and dance!"
icon_state = "colour"
item_color = "colour"
dog_fashion = /datum/dog_fashion/head/colour
dog_fashion = /datum/dog_fashion/head/colour
/obj/item/clothing/head/christmashat
name = "red santa hat"
desc = "A red Christmas Hat! How festive!"
icon_state = "christmashat"
item_state = "christmashat"
/obj/item/clothing/head/christmashatg
name = "green santa hat"
desc = "A green Christmas Hat! How festive!"
icon_state = "christmashatg"
item_state = "christmashatg"

View File

@@ -309,4 +309,4 @@
//The "pocket" for the M1 helmet so you can tuck things into the elastic band
/datum/component/storage/concrete/pockets/tiny/spacenam
attack_hand_interact = TRUE //So you can actually see what you stuff in there
attack_hand_interact = TRUE //So you can actually see what you stuff in there

View File

@@ -66,7 +66,9 @@
var/obj/item/organ/heart/heart = M.getorganslot(ORGAN_SLOT_HEART)
var/obj/item/organ/lungs/lungs = M.getorganslot(ORGAN_SLOT_LUNGS)
if(!(M.stat == DEAD || (HAS_TRAIT(M, TRAIT_FAKEDEATH))))
if (!do_mob(user,M,60)) // Stethoscope should take a moment to listen
return // FAIL
if(!(M.stat == DEAD || (HAS_TRAIT(M, TRAIT_FAKEDEATH)) || (HAS_TRAIT(M, TRAIT_NOPULSE))))
if(heart && istype(heart))
heart_strength = "<span class='danger'>an unstable</span>"
if(heart.beating)

View File

@@ -129,6 +129,21 @@
max_heat_protection_temperature = SHOES_MAX_TEMP_PROTECT
pocket_storage_component_path = /datum/component/storage/concrete/pockets/shoes
/obj/item/clothing/shoes/winterboots/christmasbootsr
name = "red christmas boots"
desc = "A pair of fluffy red christmas boots!"
icon_state = "christmasbootsr"
/obj/item/clothing/shoes/winterboots/christmasbootsg
name = "green christmas boots"
desc = "A pair of fluffy green christmas boots!"
icon_state = "christmasbootsg"
/obj/item/clothing/shoes/winterboots/santaboots
name = "santa boots"
desc = "A pair of santa boots! How traditional!!"
icon_state = "santaboots"
/obj/item/clothing/shoes/workboots
name = "work boots"
desc = "Nanotrasen-issue Engineering lace-up work boots for the especially blue-collar."

View File

@@ -211,7 +211,7 @@
icon_state = "hardsuit0-white"
item_state = "ce_helm"
item_color = "white"
armor = list("melee" = 40, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 90, "fire" = 100, "acid" = 90)
armor = list("melee" = 40, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 90)
heat_protection = HEAD
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
@@ -220,7 +220,7 @@
name = "advanced hardsuit"
desc = "An advanced suit that protects against hazardous, low pressure environments. Shines with a high polish."
item_state = "ce_hardsuit"
armor = list("melee" = 40, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 90, "fire" = 100, "acid" = 90)
armor = list("melee" = 40, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 95, "fire" = 100, "acid" = 90)
heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
helmettype = /obj/item/clothing/head/helmet/space/hardsuit/engine/elite

View File

@@ -828,3 +828,33 @@
body_parts_covered = CHEST|GROIN|ARMS|LEGS
flags_inv = HIDEJUMPSUIT
resistance_flags = NONE
/obj/item/clothing/suit/hooded/wintercoat/christmascoatr
name = "red christmas coat"
desc = "A festive red Christmas coat! Smells like Candy Cane!"
icon_state = "christmascoatr"
item_state = "christmascoatr"
hoodtype = /obj/item/clothing/head/hooded/winterhood/christmashoodr
/obj/item/clothing/head/hooded/winterhood/christmashoodr
icon_state = "christmashoodr"
/obj/item/clothing/suit/hooded/wintercoat/christmascoatg
name = "green christmas coat"
desc = "A festive green Christmas coat! Smells like Candy Cane!"
icon_state = "christmascoatg"
item_state = "christmascoatg"
hoodtype = /obj/item/clothing/head/hooded/winterhood/christmashoodg
/obj/item/clothing/head/hooded/winterhood/christmashoodg
icon_state = "christmashoodg"
/obj/item/clothing/suit/hooded/wintercoat/christmascoatrg
name = "red and green christmas coat"
desc = "A festive red and green Christmas coat! Smells like Candy Cane!"
icon_state = "christmascoatrg"
item_state = "christmascoatrg"
hoodtype = /obj/item/clothing/head/hooded/winterhood/christmashoodrg
/obj/item/clothing/head/hooded/winterhood/christmashoodrg
icon_state = "christmashoodrg"

View File

@@ -50,7 +50,8 @@
icon_state = "reactiveoff"
item_state = "reactiveoff"
add_fingerprint(user)
return
if(user.get_item_by_slot(SLOT_WEAR_SUIT) == src)
user.update_inv_wear_suit()
/obj/item/clothing/suit/armor/reactive/emp_act(severity)
. = ..()
@@ -71,8 +72,9 @@
reactivearmor_cooldown_duration = 100
/obj/item/clothing/suit/armor/reactive/teleport/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
. = FALSE
if(!active)
return 0
return
if(prob(hit_reaction_chance))
var/mob/living/carbon/human/H = owner
if(world.time < reactivearmor_cooldown)
@@ -95,12 +97,11 @@
var/turf/picked = pick(turfs)
if(!isturf(picked))
return
H.forceMove(picked)
do_teleport(H, picked, no_effects = TRUE, channel = TELEPORT_CHANNEL_WORMHOLE)
radiation_pulse(old, rad_amount_before)
radiation_pulse(src, rad_amount)
reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration
return 1
return 0
return TRUE
//Fire

View File

@@ -1,4 +1,4 @@
/obj/item/clothing/accessory //Ties moved to neck slot items, but as there are still things like medals, pokadots, and armbands, this accessory system is being kept as-is
/obj/item/clothing/accessory //Ties moved to neck slot items, but as there are still things like medals and armbands, this accessory system is being kept as-is
name = "Accessory"
desc = "Something has gone wrong!"
icon = 'icons/obj/clothing/accessories.dmi'
@@ -366,7 +366,7 @@
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
/////////////////////
//Synda Accessories//
//Syndie Accessories//
/////////////////////
/obj/item/clothing/accessory/padding
@@ -389,35 +389,3 @@
icon_state = "plastics"
item_color = "nothing"
armor = list("melee" = 0, "bullet" = 0, "laser" = 20, "energy" = 10, "bomb" = 0, "bio" = 30, "rad" = 0, "fire" = 0, "acid" = -40)
/////////////////////
//Pokadots On Pants//
/////////////////////
/obj/item/clothing/accessory/attrocious_pokadots
name = "atrocious pokadots"
desc = "They look like something out of a thrift store. Attaches to clothing not to be worn by itself."
icon_state = "attrocious_pokadots"
item_color = "attrocious_pokadots"
attack_verb = list("horrifed", "eye bleeded")
/obj/item/clothing/accessory/black_white_pokadots
name = "checkered pokadots"
desc = "You can play a game of chess on these! Attaches to clothing not to be worn by itself."
icon_state = "black_white_pokadots"
item_color = "black_white_pokadots"
attack_verb = list("check", "mate")
/obj/item/clothing/accessory/nt_pokadots
name = "blue and white pokadots"
desc = "To show your pride in your workplace, in the most annoying possable way. Attaches to clothing not to be worn by itself."
icon_state = "nt_pokadots"
item_color = "nt_pokadots"
attack_verb = list("eye bleeded", "annoyed")
/obj/item/clothing/accessory/syndi_pokadots
name = "black and red pokadots"
desc = "King me. Attaches to clothing not to be worn by itself." //checkers!
icon_state = "syndi_pokadots"
item_color = "syndi_pokadots"
attack_verb = list("jumped", "taken")

View File

@@ -820,4 +820,36 @@
item_color = "durathread"
can_adjust = FALSE
body_parts_covered = CHEST|GROIN|ARMS
armor = list("melee" = 10, "laser" = 10, "fire" = 40, "acid" = 10, "bomb" = 5)
armor = list("melee" = 10, "laser" = 10, "fire" = 40, "acid" = 10, "bomb" = 5)
/obj/item/clothing/under/christmas/christmasmaler
name = "red masculine christmas suit"
desc = "A simple red christmas suit that looks close to Santa's!"
icon_state = "christmasmaler"
item_state = "christmasmaler"
body_parts_covered = CHEST|GROIN
can_adjust = FALSE
/obj/item/clothing/under/christmas/christmasmaleg
name = "green masculine christmas suit"
desc = "A simple green christmas suit that smells minty!"
icon_state = "christmasmaleg"
item_state = "christmasmaleg"
body_parts_covered = CHEST|GROIN
can_adjust = FALSE
/obj/item/clothing/under/christmas/christmasfemaler
name = "red feminine christmas suit"
desc = "A simple red christmas suit that looks like Mrs Claus!"
icon_state = "christmasfemaler"
item_state = "christmasfemaler"
body_parts_covered = CHEST|GROIN
can_adjust = FALSE
/obj/item/clothing/under/christmas/christmasfemaleg
name = "green feminine christmas suit"
desc = "A simple green christmas suit that smells minty!"
icon_state = "christmasfemaleg"
item_state = "christmasfemaleg"
body_parts_covered = CHEST|GROIN
can_adjust = FALSE

View File

@@ -439,4 +439,4 @@
/datum/mind/proc/teach_crafting_recipe(R)
if(!learned_recipes)
learned_recipes = list()
learned_recipes |= R
learned_recipes |= R

View File

@@ -163,26 +163,7 @@
name = "Cosmic Winter Coat"
result = /obj/item/clothing/suit/hooded/wintercoat/cosmic
reqs = list(/obj/item/clothing/suit/hooded/wintercoat = 1,
/obj/item/clothing/suit/hooded/wintercoat/captain = 1,
/obj/item/clothing/suit/hooded/wintercoat/hop = 1,
/obj/item/clothing/suit/hooded/wintercoat/hos = 1,
/obj/item/clothing/suit/hooded/wintercoat/rd = 1,
/obj/item/clothing/suit/hooded/wintercoat/ce = 1,
/obj/item/clothing/suit/hooded/wintercoat/cmo = 1,
/obj/item/clothing/suit/hooded/wintercoat/qm = 1,
/obj/item/clothing/suit/hooded/wintercoat/robotics = 1,
/obj/item/clothing/suit/hooded/wintercoat/engineering/atmos = 1,
/obj/item/clothing/suit/hooded/wintercoat/engineering = 1,
/obj/item/clothing/suit/hooded/wintercoat/science = 1,
/obj/item/clothing/suit/hooded/wintercoat/genetics = 1,
/obj/item/clothing/suit/hooded/wintercoat/chemistry = 1,
/obj/item/clothing/suit/hooded/wintercoat/medical = 1,
/obj/item/clothing/suit/hooded/wintercoat/viro = 1,
/obj/item/clothing/suit/hooded/wintercoat/janitor = 1,
/obj/item/clothing/suit/hooded/wintercoat/security = 1,
/obj/item/clothing/suit/hooded/wintercoat/cargo = 1,
/obj/item/clothing/suit/hooded/wintercoat/hydro = 1,
/obj/item/clothing/suit/hooded/wintercoat/miner = 1)
/obj/item/bedsheet/cosmos = 1)
time = 60
always_availible = TRUE
category = CAT_CLOTHING

View File

@@ -320,3 +320,41 @@
reqs = list(/obj/item/stack/rods = 2,
/obj/item/clothing/under/rank/security = 1)
category = CAT_MISC
/datum/crafting_recipe/bloodsucker/vassalrack
name = "Persuasion Rack"
//desc = "For converting crewmembers into loyal Vassals."
result = /obj/structure/bloodsucker/vassalrack
tools = list(/obj/item/weldingtool,
///obj/item/screwdriver,
/obj/item/wrench
)
reqs = list(/obj/item/stack/sheet/mineral/wood = 3,
/obj/item/stack/sheet/metal = 2,
/obj/item/restraints/handcuffs/cable = 2,
///obj/item/storage/belt = 1
///obj/item/stack/sheet/animalhide = 1, // /obj/item/stack/sheet/leather = 1,
///obj/item/stack/sheet/plasteel = 5
)
//parts = list(/obj/item/storage/belt = 1
// )
time = 150
category = CAT_MISC
always_availible = FALSE // Disabled til learned
/datum/crafting_recipe/bloodsucker/candelabrum
name = "Candelabrum"
//desc = "For converting crewmembers into loyal Vassals."
result = /obj/structure/bloodsucker/candelabrum
tools = list(/obj/item/weldingtool,
/obj/item/wrench
)
reqs = list(/obj/item/stack/sheet/metal = 3,
/obj/item/stack/rods = 1,
/obj/item/candle = 1
)
time = 100
category = CAT_MISC
always_availible = FALSE // Disabled til learned

View File

@@ -132,14 +132,10 @@
transfer_fingerprints_to(B)
qdel(src)
////////////////////////////////////////////////////////////////////////////////
/// Drinks. END
////////////////////////////////////////////////////////////////////////////////
/obj/item/reagent_containers/food/drinks/trophy
name = "pewter cup"
desc = "Everyone gets a trophy."
@@ -178,7 +174,6 @@
materials = list(MAT_SILVER=800)
volume = 100
/obj/item/reagent_containers/food/drinks/trophy/bronze_cup
name = "bronze cup"
desc = "At least you ranked!"
@@ -190,7 +185,7 @@
materials = list(MAT_METAL=400)
volume = 25
///////////////////////////////////////////////Drinks
///////////////////////////////////////////////Drinks/////////////////////////////////////////
//Notes by Darem: Drinks are simply containers that start preloaded. Unlike condiments, the contents can be ingested directly
// rather then having to add it to something else first. They should only contain liquids. They have a default container size of 50.
// Formatting is the same as food.
@@ -203,6 +198,7 @@
spillable = TRUE
resistance_flags = FREEZE_PROOF
isGlass = FALSE
foodtype = BREAKFAST
//Used by MREs
/obj/item/reagent_containers/food/drinks/coffee/type2
@@ -211,7 +207,6 @@
icon = 'icons/obj/food/containers.dmi'
icon_state = "condi_cornoil"
/obj/item/reagent_containers/food/drinks/ice
name = "ice cup"
desc = "Careful, cold ice, do not chew."
@@ -243,10 +238,8 @@
desc = "Made in Space South America."
list_reagents = list("hot_coco" = 30, "sugar" = 5)
foodtype = SUGAR
resistance_flags = FREEZE_PROOF
/obj/item/reagent_containers/food/drinks/dry_ramen
name = "cup ramen"
desc = "Just add 10ml of water, self heats! A taste that reminds you of your school years."
@@ -325,27 +318,27 @@
icon_state = "orangebox"
name = "orange juice box"
desc = "A great source of vitamins. Stay healthy!"
foodtype = FRUIT
foodtype = FRUIT | BREAKFAST
if("milk")
icon_state = "milkbox"
name = "carton of milk"
desc = "An excellent source of calcium for growing space explorers."
foodtype = DAIRY
foodtype = DAIRY | BREAKFAST
if("applejuice")
icon_state = "juicebox"
name = "apple juice box"
desc = "Sweet apple juice. Don't be late for school!"
foodtype = FRUIT
foodtype = FRUIT | BREAKFAST
if("grapejuice")
icon_state = "grapebox"
name = "grape juice box"
desc = "Tasty grape juice in a fun little container. Non-alcoholic!"
foodtype = FRUIT
foodtype = FRUIT | BREAKFAST
if("chocolate_milk")
icon_state = "chocolatebox"
name = "carton of chocolate milk"
desc = "Milk for cool kids!"
foodtype = SUGAR
foodtype = SUGAR | BREAKFAST
if("eggnog")
icon_state = "nog2"
name = "carton of eggnog"
@@ -357,8 +350,7 @@
desc = "A small carton, intended for holding drinks."
//////////////////////////drinkingglass and shaker//
//////////////////////////drinkingglass and shaker/////////////////////////////////////////////////////////////////////////////////////
//Note by Darem: This code handles the mixing of drinks. New drinks go in three places: In Chemistry-Reagents.dm (for the drink
// itself), in Chemistry-Recipes.dm (for the reaction that changes the components into the drink), and here (for the drinking glass
// icon states.
@@ -399,8 +391,8 @@
volume = 30
spillable = TRUE
//////////////////////////soda_cans//
//These are in their own group to be used as IED's in /obj/item/grenade/ghettobomb.dm
//////////////////////////soda_cans////////////////////////////////////////////////////
//These are in their own group to be used as IED's in /obj/item/grenade/ghettobomb.dm//
/obj/item/reagent_containers/food/drinks/soda_cans
name = "soda can"
@@ -426,7 +418,6 @@
qdel(src)
..()
/obj/item/reagent_containers/food/drinks/soda_cans/attack_self(mob/user)
if(!is_drainable())
to_chat(user, "You pull back the tab of \the [src] with a satisfying pop.") //Ahhhhhhhh
@@ -515,6 +506,13 @@
list_reagents = list("shamblers" = 30)
foodtype = SUGAR | JUNKFOOD
/obj/item/reagent_containers/food/drinks/soda_cans/buzz_fuzz
name = "Buzz Fuzz"
desc = "The sister drink of Shambler's Juice! Uses real honey, making it a sweet tooth's dream drink. The slogan reads ''A Hive of Flavour'', there's also a label about how it is adddicting."
icon_state = "honeysoda_can"
list_reagents = list("buzz_fuzz" = 25, "honey" = 5)
foodtype = SUGAR | JUNKFOOD
/obj/item/reagent_containers/food/drinks/soda_cans/grey_bull
name = "Grey Bull"
desc = "Grey Bull, it gives you gloves!"
@@ -527,3 +525,10 @@
desc = "There is no air shortage. Do not drink."
icon_state = "air"
list_reagents = list("nitrogen" = 24, "oxygen" = 6)
/obj/item/reagent_containers/food/drinks/soda_cans/monkey_energy
name = "Monkey Energy"
desc = "Unleash the ape!"
icon_state = "monkey_energy"
list_reagents = list("monkey_energy" = 50)
foodtype = SUGAR | JUNKFOOD

View File

@@ -429,7 +429,7 @@
righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi'
isGlass = FALSE
list_reagents = list("orangejuice" = 100)
foodtype = FRUIT
foodtype = FRUIT| BREAKFAST
/obj/item/reagent_containers/food/drinks/bottle/cream
name = "milk cream"

View File

@@ -5,6 +5,9 @@
/// the parent to the exclusion list in code/__HELPERS/unsorted.dm's
/// get_random_food proc.
////////////////////////////////////////////////////////////////////////////////
#define STOP_SERVING_BREAKFAST (15 MINUTES)
/obj/item/reagent_containers/food
possible_transfer_amounts = list()
volume = 50 //Sets the default container amount for all food items.
@@ -40,4 +43,8 @@
if(foodtype & H.dna.species.toxic_food)
to_chat(H, "<span class='warning'>You don't feel so good...</span>")
H.adjust_disgust(25 + 30 * fraction)
if((foodtype & BREAKFAST) && world.time - SSticker.round_start_time < STOP_SERVING_BREAKFAST)
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "breakfast", /datum/mood_event/breakfast)
last_check_time = world.time
#undef STOP_SERVING_BREAKFAST

View File

@@ -26,6 +26,7 @@
"cornoil" = list("oliveoil", "corn oil bottle", "A delicious oil used in cooking. Made from corn"),
"sugar" = list("emptycondiment", "sugar bottle", "Tasty spacey sugar!"),
"mayonnaise" = list("mayonnaise", "mayonnaise jar", "An oily condiment made from egg yolks."),
"bbqsauce" = list("condi_bbq", "BBQ sauce", "Hand wipes not included."),
"peanut_butter" = list("peanutbutter", "peanut butter jar", "A deliciously and sticky spread made from peanuts."))
var/originalname = "condiment" //Can't use initial(name) for this. This stores the name set by condimasters.
@@ -299,3 +300,8 @@
name = "astrotame pack"
originalname = "astrotame"
list_reagents = list("astrotame" = 5)
/obj/item/reagent_containers/food/condiment/pack/bbqsauce
name = "bbq sauce pack"
originalname = "bbq sauce"
list_reagents = list("bbqsauce" = 10)

View File

@@ -52,6 +52,8 @@ All foods are distributed among various categories. Use common sense.
var/list/bonus_reagents //the amount of reagents (usually nutriment and vitamin) added to crafted/cooked snacks, on top of the ingredients reagents.
var/customfoodfilling = 1 // whether it can be used as filling in custom food
var/list/tastes // for example list("crisps" = 2, "salt" = 1)
var/dunkable = FALSE // for dunkable food, make true
var/dunk_amount = 10 // how much reagent is transferred per dunk
//Placeholder for effect that trigger on eating that aren't tied to reagents.
@@ -328,6 +330,24 @@ All foods are distributed among various categories. Use common sense.
M.emote("me", EMOTE_VISIBLE, "[sattisfaction_text]")
qdel(src)
//////////////////////////////////////////Dunking///////////////////////////////////////////
/obj/item/reagent_containers/food/snacks/afterattack(obj/item/reagent_containers/M, mob/user, proximity)
. = ..()
if(!dunkable || !proximity)
return
if(istype(M, /obj/item/reagent_containers/glass) || istype(M, /obj/item/reagent_containers/food/drinks)) //you can dunk dunkable snacks into beakers or drinks
if(!M.is_drainable())
to_chat(user, "<span class='warning'>[M] is unable to be dunked in!</span>")
return
if(M.reagents.trans_to(src, dunk_amount)) //if reagents were transfered, show the message
to_chat(user, "<span class='notice'>You dunk the [M].</span>")
return
if(!M.reagents.total_volume)
to_chat(user, "<span class='warning'>[M] is empty!</span>")
else
to_chat(user, "<span class='warning'>[src] is full!</span>")
// //////////////////////////////////////////////Store////////////////////////////////////////
/// All the food items that can store an item inside itself, like bread or cake.
/obj/item/reagent_containers/food/snacks/store

View File

@@ -57,6 +57,33 @@
else if(subjectjob)
S.name = "[subjectjob] meatsteak"
/obj/item/reagent_containers/food/snacks/meat/rawcrab
name = "raw crab meat"
desc = "A pile of raw crab meat."
icon_state = "crabmeatraw"
cooked_type = /obj/item/reagent_containers/food/snacks/meat/crab
bitesize = 3
list_reagents = list("nutriment" = 1, "cooking_oil" = 3)
filling_color = "#EAD079"
tastes = list("raw crab" = 1)
foodtype = RAW | MEAT
/obj/item/reagent_containers/food/snacks/meat/crab
name = "crab meat"
desc = "Some deliciously cooked crab meat."
icon_state = "crabmeat"
list_reagents = list("nutriment" = 2)
bonus_reagents = list("nutriment" = 3, "vitamin" = 2, "cooking_oil" = 2)
filling_color = "#DFB73A"
tastes = list("crab" = 1)
foodtype = MEAT
/obj/item/reagent_containers/food/snacks/meat/slab/chicken
name = "chicken meat"
desc = "A slab of raw chicken. Remember to wash your hands!"
cooked_type = /obj/item/reagent_containers/food/snacks/meat/steak/chicken
slice_path = /obj/item/reagent_containers/food/snacks/meat/rawcutlet/chicken
tastes = list("chicken" = 1)
/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/slime
icon_state = "slimemeat"
@@ -132,6 +159,28 @@
tastes = list("brains" = 1, "meat" = 1)
foodtype = RAW | MEAT | TOXIC
/obj/item/reagent_containers/food/snacks/carpmeat/aquatic
name = "fillet"
desc = "A fillet of one of the local water dwelling species."
/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/ipc
icon_state = "ipcmeat"
desc = "Gross robot meat."
filling_color = "#000000"
tastes = list("metal" = 1)
/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/avian
desc = "Tastes like chicken, that's because it is!"
icon_state = "birdmeat"
filling_color = "#BF896B"
tastes = list("chicken" = 1)
/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/mammal
desc = "Tastes sweet... reminds you vaguely of chicken."
filling_color = "#6B8E23"
tastes = list("brains" = 1, "meat" = 1)
////////////////////////////////////// OTHER MEATS ////////////////////////////////////////////////////////
@@ -183,7 +232,6 @@
tastes = list("meat" = 1, "salmon" = 1)
foodtype = RAW | MEAT
/obj/item/reagent_containers/food/snacks/meat/slab/xeno
name = "xeno meat"
desc = "A slab of meat."
@@ -207,7 +255,6 @@
tastes = list("cobwebs" = 1)
foodtype = RAW | MEAT | TOXIC
/obj/item/reagent_containers/food/snacks/meat/slab/goliath
name = "goliath meat"
desc = "A slab of goliath meat. It's not very edible now, but it cooks great in lava."
@@ -255,7 +302,7 @@
bonus_reagents = list("nutriment" = 1, "vitamin" = 1, "cooking_oil" = 2)
filling_color = "#854817"
tastes = list("bacon" = 1)
foodtype = MEAT
foodtype = MEAT | BREAKFAST
/obj/item/reagent_containers/food/snacks/meat/slab/gondola
name = "gondola meat"
@@ -281,6 +328,10 @@
foodtype = MEAT
tastes = list("meat" = 1)
/obj/item/reagent_containers/food/snacks/meat/steak/chicken
name = "chicken steak" //Can you have chicken steaks? Maybe this should be renamed once it gets new sprites.
tastes = list("chicken" = 1)
/obj/item/reagent_containers/food/snacks/meat/steak/plain
foodtype = MEAT
@@ -288,6 +339,10 @@
tastes = list("tender meat" = 1)
foodtype = MEAT | GROSS
/obj/item/reagent_containers/food/snacks/meat/steak/penguin
name = "penguin steak"
tastes = list("beef" = 1, "cod fish" = 1)
/obj/item/reagent_containers/food/snacks/meat/steak/killertomato
name = "killer tomato steak"
tastes = list("tomato" = 1)
@@ -354,6 +409,11 @@
else if(subjectjob)
S.name = "[subjectjob] [initial(S.name)]"
/obj/item/reagent_containers/food/snacks/meat/rawcutlet/chicken
name = "raw chicken cutlet"
cooked_type = /obj/item/reagent_containers/food/snacks/meat/cutlet/chicken
tastes = list("chicken" = 1)
/obj/item/reagent_containers/food/snacks/meat/rawcutlet/killertomato
name = "raw killer tomato cutlet"
cooked_type = /obj/item/reagent_containers/food/snacks/meat/cutlet/killertomato
@@ -419,3 +479,11 @@
/obj/item/reagent_containers/food/snacks/meat/cutlet/gondola
name = "gondola cutlet"
tastes = list("meat" = 1, "tranquility" = 1)
/obj/item/reagent_containers/food/snacks/meat/cutlet/penguin
name = "penguin cutlet"
tastes = list("beef" = 1, "cod fish" = 1)
/obj/item/reagent_containers/food/snacks/meat/cutlet/chicken
name = "chicken cutlet"
tastes = list("chicken" = 1)

View File

@@ -5,7 +5,7 @@
slices_num = 5
tastes = list("bread" = 10)
foodtype = GRAIN
dunkable = TRUE
/obj/item/reagent_containers/food/snacks/breadslice
icon = 'icons/obj/food/burgerbread.dmi'
@@ -16,6 +16,7 @@
slot_flags = ITEM_SLOT_HEAD
customfoodfilling = 0 //to avoid infinite bread-ception
foodtype = GRAIN
dunkable = TRUE
/obj/item/reagent_containers/food/snacks/store/bread/plain
name = "bread"
@@ -270,6 +271,7 @@ GLOBAL_LIST_INIT(frying_bad_chems, list(
list_reagents = list("nutriment" = 4)
bonus_reagents = list("nutriment" = 1, "vitamin" = 1)
tastes = list("butter" = 1, "toast" = 1)
foodtype = GRAIN | BREAKFAST
/obj/item/reagent_containers/food/snacks/butterbiscuit
name = "butter biscuit"
@@ -280,6 +282,7 @@ GLOBAL_LIST_INIT(frying_bad_chems, list(
list_reagents = list("nutriment" = 5)
bonus_reagents = list("nutriment" = 1, "vitamin" = 1)
tastes = list("butter" = 1, "biscuit" = 1)
foodtype = GRAIN | BREAKFAST
/obj/item/reagent_containers/food/snacks/butterdog
name = "butterdog"

View File

@@ -134,13 +134,54 @@
tastes = list("bun" = 4, "brains" = 2)
foodtype = GRAIN | MEAT | GROSS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/obj/item/reagent_containers/food/snacks/burger/ghost
name = "ghost burger"
desc = "Too Spooky!"
alpha = 125
icon_state = "ghostburger"
bonus_reagents = list("nutriment" = 5, "vitamin" = 12)
tastes = list("bun" = 4, "ectoplasm" = 2)
foodtype = GRAIN
alpha = 170
verb_say = "moans"
verb_yell = "wails"
/obj/item/reagent_containers/food/snacks/burger/ghost/Initialize()
. = ..()
START_PROCESSING(SSobj, src)
/obj/item/reagent_containers/food/snacks/burger/ghost/process()
if(!isturf(loc)) //no floating out of bags
return
var/paranormal_activity = rand(100)
switch(paranormal_activity)
if(97 to 100)
audible_message("[src] rattles a length of chain.")
playsound(loc,'sound/spookoween/chain_rattling.ogg', 300, TRUE)
if(91 to 96)
say(pick("OoOoOoo.", "OoooOOooOoo!!"))
if(84 to 90)
dir = pick(NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST)
step(src, dir)
if(71 to 83)
step(src, dir)
if(65 to 70)
var/obj/machinery/light/L = locate(/obj/machinery/light) in view(4, src)
if(L)
L.flicker()
if(62 to 64)
playsound(loc,pick('sound/hallucinations/i_see_you1.ogg', 'sound/hallucinations/i_see_you2.ogg'), 50, TRUE, ignore_walls = FALSE)
if(61)
visible_message("[src] spews out a glob of ectoplasm!")
new /obj/effect/decal/cleanable/greenglow/ecto(loc)
playsound(loc,'sound/effects/splat.ogg', 200, TRUE)
//If i was less lazy i would make the burger forcefeed itself to a nearby mob here.
/obj/item/reagent_containers/food/snacks/burger/ghost/Destroy()
STOP_PROCESSING(SSobj, src)
. = ..()
//////////////////////////////////////////////////////////////////////////////////////////////////////////
/obj/item/reagent_containers/food/snacks/burger/red
name = "red burger"
@@ -281,3 +322,49 @@
bonus_reagents = list("nutriment" = 8, "vitamin" = 1)
tastes = list("bun" = 4, "bacon" = 2)
foodtype = GRAIN | MEAT
/obj/item/reagent_containers/food/snacks/burger/soylent
name = "soylent burger"
desc = "A eco-friendly burger made using upcycled low value biomass."
icon_state = "soylentburger"
bonus_reagents = list("nutriment" = 5, "vitamin" = 3)
tastes = list("bun" = 2, "assistant" = 4)
foodtype = GRAIN | MEAT | DAIRY
/obj/item/reagent_containers/food/snacks/burger/rib
name = "mcrib"
desc = "An elusive rib shaped burger with limited availablity across the galaxy. Not as good as you remember it."
icon_state = "mcrib"
bonus_reagents = list("bbqsauce" = 5, "vitamin" = 3)
tastes = list("bun" = 2, "pork patty" = 4)
foodtype = GRAIN | MEAT
/obj/item/reagent_containers/food/snacks/burger/mcguffin
name = "mcguffin"
desc = "A cheap and greasy imitation of an eggs benedict."
icon_state = "mcguffin"
tastes = list("muffin" = 2, "bacon" = 3)
bonus_reagents = list("eggyolk" = 3, "nutriment" = 1)
foodtype = GRAIN | MEAT | BREAKFAST
/obj/item/reagent_containers/food/snacks/burger/chicken
name = "chicken sandwich" //Apparently the proud people of Americlapstan object to this thing being called a burger. Apparently McDonald's just calls it a burger in Europe as to not scare and confuse us.
desc = "A delicious chicken sandwich, it is said the proceeds from this treat helps criminalize homosexuality on the space frontier."
icon_state = "chickenburger"
tastes = list("bun" = 2, "chicken" = 4, "God's covenant" = 1)
bonus_reagents = list("mayonnaise" = 3, "cooking_oil" = 2, "nutriment" = 2)
foodtype = GRAIN | MEAT | FRIED
/obj/item/reagent_containers/food/snacks/burger/cheese
name = "cheese burger"
desc = "This noble burger stands proudly clad in golden cheese."
icon_state = "cheeseburger"
tastes = list("bun" = 2, "beef patty" = 4, "cheese" = 3)
bonus_reagents = list("nutriment" = 1)
foodtype = GRAIN | MEAT | DAIRY
/obj/item/reagent_containers/food/snacks/burger/cheese/Initialize()
. = ..()
if(prob(33))
icon_state = "cheeseburgeralt"

View File

@@ -379,3 +379,21 @@ obj/item/reagent_containers/food/snacks/store/cake/pound_cake
filling_color = "#00FFFF"
tastes = list("cake" = 1, "sugar" = 1, "peachjuice" = 10)
foodtype = GRAIN | SUGAR | DAIRY
/obj/item/reagent_containers/food/snacks/store/cake/trumpet
name = "spaceman's cake"
desc = "A spaceman's trumpet frosted cake."
icon_state = "trumpetcake"
slice_path = /obj/item/reagent_containers/food/snacks/cakeslice/trumpet
bonus_reagents = list("polypyr" = 15, "cream" = 5, "vitamin" = 5, "berryjuice" = 5)
filling_color = "#7A3D80"
tastes = list("cake" = 4, "violets" = 2, "jam" = 2)
foodtype = GRAIN | DAIRY | FRUIT | SUGAR
/obj/item/reagent_containers/food/snacks/cakeslice/trumpet
name = "spaceman's cake"
desc = "A spaceman's trumpet frosted cake."
icon_state = "trumpetcakeslice"
filling_color = "#7A3D80"
tastes = list("cake" = 4, "violets" = 2, "jam" = 2)
foodtype = GRAIN | DAIRY | FRUIT | SUGAR

View File

@@ -94,7 +94,7 @@
filling_color = "#FFFFF0"
list_reagents = list("nutriment" = 3)
tastes = list("egg" = 4, "salt" = 1, "pepper" = 1)
foodtype = MEAT | FRIED
foodtype = MEAT | FRIED | BREAKFAST
/obj/item/reagent_containers/food/snacks/boiledegg
name = "boiled egg"
@@ -104,7 +104,7 @@
filling_color = "#FFFFF0"
list_reagents = list("nutriment" = 2, "vitamin" = 1)
tastes = list("egg" = 1)
foodtype = MEAT
foodtype = MEAT | BREAKFAST
/obj/item/reagent_containers/food/snacks/omelette //FUCK THIS
name = "omelette du fromage"
@@ -116,7 +116,7 @@
bitesize = 1
w_class = WEIGHT_CLASS_NORMAL
tastes = list("egg" = 1, "cheese" = 1)
foodtype = MEAT
foodtype = MEAT | BREAKFAST
/obj/item/reagent_containers/food/snacks/omelette/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/kitchen/fork))
@@ -145,5 +145,4 @@
w_class = WEIGHT_CLASS_NORMAL
list_reagents = list("nutriment" = 6, "vitamin" = 4)
tastes = list("egg" = 1, "bacon" = 1, "bun" = 1)
foodtype = MEAT
foodtype = MEAT | BREAKFAST

View File

@@ -81,16 +81,15 @@
filling_color = "#87CEFA"
tastes = list("blue cherries" = 2, "ice cream" = 2)
foodtype = FRUIT | DAIRY
/////////////
//SNOWCONES//
/////////////
/obj/item/reagent_containers/food/snacks/snowcones //We use this as a base for all other snowcones
name = "flaverless snowcone"
desc = "Its just harden water slivers. Still fun to chew on."
name = "flavorless snowcone"
desc = "It's just shaved ice. Still fun to chew on."
icon = 'icons/obj/food/snowcones.dmi'
icon_state = "flaverless_sc"
icon_state = "flavorless_sc"
trash = /obj/item/reagent_containers/food/drinks/sillycup //We dont eat paper cups
bonus_reagents = list("water" = 10) //Base line will allways give water
list_reagents = list("water" = 1) // We dont get food for water/juices
@@ -99,106 +98,106 @@
foodtype = SUGAR //We use SUGAR as a base line to act in as junkfood, other wise we use fruit
/obj/item/reagent_containers/food/snacks/snowcones/lime
name = "lime flavored snowcone"
desc = "A lime flavord snowball in a paper cup."
name = "lime snowcone"
desc = "Lime syrup drizzled over a snowball in a paper cup."
icon_state = "lime_sc"
list_reagents = list("nutriment" = 1, "limejuice" = 5)
tastes = list("ice" = 1, "water" = 1, "limes" = 5)
foodtype = FRUIT
/obj/item/reagent_containers/food/snacks/snowcones/lemon
name = "lemon flavored snowcone"
desc = "A lemon flavord snowball in a paper cup."
name = "lemon snowcone"
desc = "Lemon syrup drizzled over a snowball in a paper cup."
icon_state = "lemon_sc"
list_reagents = list("nutriment" = 1, "lemonjuice" = 5)
tastes = list("ice" = 1, "water" = 1, "lemons" = 5)
foodtype = FRUIT
/obj/item/reagent_containers/food/snacks/snowcones/apple
name = "apple flavored snowcone"
desc = "A apple flavord snowball in a paper cup."
icon_state = "blue_sc"
name = "apple snowcone"
desc = "Apple syrup drizzled over a snowball in a paper cup."
icon_state = "amber_sc"
list_reagents = list("nutriment" = 1, "applejuice" = 5)
tastes = list("ice" = 1, "water" = 1, "apples" = 5)
foodtype = FRUIT
/obj/item/reagent_containers/food/snacks/snowcones/grape
name = "grape flavored snowcone"
desc = "A grape flavord snowball in a paper cup."
name = "grape snowcone"
desc = "Grape syrup drizzled over a snowball in a paper cup."
icon_state = "grape_sc"
list_reagents = list("nutriment" = 1, "berryjuice" = 5)
list_reagents = list("nutriment" = 1, "grapejuice" = 5)
tastes = list("ice" = 1, "water" = 1, "grape" = 5)
foodtype = FRUIT
/obj/item/reagent_containers/food/snacks/snowcones/orange
name = "orange flavored snowcone"
desc = "A orange flavor dizzled on a snowball in a paper cup."
name = "orange snowcone"
desc = "Orange syrup drizzled over a snowball in a paper cup."
icon_state = "orange_sc"
list_reagents = list("nutriment" = 1, "orangejuice" = 10)
tastes = list("ice" = 1, "water" = 1, "berries" = 5)
list_reagents = list("nutriment" = 1, "orangejuice" = 5)
tastes = list("ice" = 1, "water" = 1, "orange" = 5)
foodtype = FRUIT
/obj/item/reagent_containers/food/snacks/snowcones/blue
name = "bluecherry flavored snowcone"
desc = "A bluecharry flavord snowball in a paper cup, how rare!"
icon_state = "red_sc"
name = "bluecherry snowcone"
desc = "Bluecherry syrup drizzled over a snowball in a paper cup, how rare!"
icon_state = "blue_sc"
list_reagents = list("nutriment" = 1, "bluecherryjelly" = 5)
tastes = list("ice" = 1, "water" = 1, "blue" = 5)
tastes = list("ice" = 1, "water" = 1, "blue" = 5, "cherries" = 5)
foodtype = FRUIT
/obj/item/reagent_containers/food/snacks/snowcones/red
name = "cherry flavored snowcone"
desc = "A cherry flavord snowball in a paper cup."
icon_state = "blue_sc"
name = "cherry snowcone"
desc = "Cherry syrup drizzled over a snowball in a paper cup."
icon_state = "red_sc"
list_reagents = list("nutriment" = 1, "cherryjelly" = 5)
tastes = list("ice" = 1, "water" = 1, "red" = 5)
tastes = list("ice" = 1, "water" = 1, "red" = 5, "cherries" = 5)
foodtype = FRUIT
/obj/item/reagent_containers/food/snacks/snowcones/kiwi
name = "kiwi flavored snowcone"
desc = "A kiwi flavord snowball in a paper cup."
name = "kiwi snowcone"
desc = "A kiwi snowball in a paper cup."
icon_state = "kiwi_sc"
list_reagents = list("nutriment" = 3, "vitamin" = 6)
tastes = list("ice" = 1, "space" = 3, "kiwi" = 5)
foodtype = FRUIT
/obj/item/reagent_containers/food/snacks/snowcones/mix
name = "mixed berry flavored snowcone"
desc = "A mix of different flavors dizzled on a snowball in a paper cup."
icon_state = "berry_sc"
list_reagents = list("nutriment" = 1, "berryjuice" = 10)
tastes = list("ice" = 1, "water" = 1, "berries" = 5)
foodtype = FRUIT
/obj/item/reagent_containers/food/snacks/snowcones/peach
name = "peach flavored snowcone"
desc = "A peach flavord snowball in a paper cup."
name = "peach snowcone"
desc = "A peach snowball in a paper cup."
icon_state = "peach_sc"
list_reagents = list("nutriment" = 1, "peachjuice" = 10)
tastes = list("ice" = 1, "water" = 1, " peach" = 5)
foodtype = FRUIT
/obj/item/reagent_containers/food/snacks/snowcones/strawberry
name = "strawberry flavored snowcone"
desc = "A strawberry flavord snowball in a paper cup."
name = "strawberry snowcone"
desc = "A strawberry snowball in a paper cup."
icon_state = "blue_sc"
list_reagents = list("nutriment" = 1, "berryjuice" = 10)
tastes = list("ice" = 1, "water" = 1, " strawberry" = 5)
foodtype = FRUIT
/obj/item/reagent_containers/food/snacks/snowcones/berry
name = "berry snowcone"
desc = "Berry syrup drizzled over a snowball in a paper cup."
icon_state = "berry_sc"
list_reagents = list("nutriment" = 1, "berryjuice" = 5)
tastes = list("ice" = 1, "water" = 1, "berries" = 5)
foodtype = FRUIT
/obj/item/reagent_containers/food/snacks/snowcones/fruitsalad
name = "mixed fruit flavored snowcone"
desc = "A mix of different flavors dizzled on a snowball in a paper cup."
name = "fruit salad snowcone"
desc = "A delightful mix of citrus syrups drizzled over a snowball in a paper cup."
icon_state = "fruitsalad_sc"
list_reagents = list("nutriment" = 1, "limejuice" = 5, "lemonjuice" = 5, "orangejuice" = 5)
tastes = list("ice" = 1, "water" = 1, "fruits" = 25)
list_reagents = list("nutriment" = 1, "lemonjuice" = 5, "limejuice" = 5, "orangejuice" = 5)
tastes = list("ice" = 1, "water" = 1, "oranges" = 5, "limes" = 5, "lemons" = 5, "citrus" = 5, "salad" = 5)
foodtype = FRUIT
/obj/item/reagent_containers/food/snacks/snowcones/pineapple
name = "pineapple flavored snowcone"
desc = "A pineapple flavord snowball in a paper cup."
name = "pineapple snowcone"
desc = "Pineapple syrup drizzled over a snowball in a paper cup."
icon_state = "pineapple_sc"
list_reagents = list("nutriment" = 1, "water" = 1)
list_reagents = list("nutriment" = 1, "water" = 10)
tastes = list("ice" = 1, "water" = 1, "pineapples" = 5)
foodtype = PINEAPPLE //Pineapple to allow all that like pineapple to enjoy
@@ -207,41 +206,46 @@
desc = "..."
icon_state = "mime_sc"
list_reagents = list("nutriment" = 1, "nothing" = 5)
tastes = list("nothing" = 5)
tastes = list("ice" = 1, "water" = 1, "nothing" = 5)
/obj/item/reagent_containers/food/snacks/snowcones/clown
name = "joke flavored snowcone"
desc = "A waterd down jokeful flavord snowball in a paper cup."
name = "clown snowcone"
desc = "Laughter drizzled over a snowball in a paper cup."
icon_state = "clown_sc"
list_reagents = list("nutriment" = 1, "laughter" = 5)
tastes = list("jokes" = 5, "brainfreeze" = 5, "joy" = 5)
tastes = list("ice" = 1, "water" = 1, "jokes" = 5, "brainfreeze" = 5, "joy" = 5)
/obj/item/reagent_containers/food/snacks/snowcones/soda
name = "sodawater flavored snowcone"
desc = "A waterd down sodawater flavored snowcone snowball in a paper cup."
name = "space cola snowcone"
desc = "Space Cola drizzled over a snowball in a paper cup."
icon_state = "soda_sc"
list_reagents = list("nutriment" = 1, "sodawater" = 5)
tastes = list("surgar" = 1, "water" = 5, "soda" = 5)
foodtype = JUNKFOOD | SUGAR
list_reagents = list("nutriment" = 1, "space_cola" = 5)
tastes = list("ice" = 1, "water" = 1, "cola" = 5)
/obj/item/reagent_containers/food/snacks/snowcones/pwgrmer
name = "pwergamer flavored snowcone"
desc = "A waterd down pwergamer soda flavord snowball in a paper cup."
icon_state = "pwergamer_sc"
list_reagents = list("nutriment" = 1, "laughter" = 1)
tastes = list("vaild" = 5, "salt" = 5, "wats" = 5)
foodtype = JUNKFOOD | SUGAR
/obj/item/reagent_containers/food/snacks/snowcones/spacemountainwind
name = "Space Mountain Wind snowcone"
desc = "Space Mountain Wind drizzled over a snowball in a paper cup."
icon_state = "kiwi_sc"
list_reagents = list("nutriment" = 1, "spacemountainwind" = 5)
tastes = list("ice" = 1, "water" = 1, "mountain wind" = 5)
/obj/item/reagent_containers/food/snacks/snowcones/pwrgame
name = "pwrgame snowcone"
desc = "Pwrgame soda drizzled over a snowball in a paper cup."
icon_state = "pwrgame_sc"
list_reagents = list("nutriment" = 1, "pwr_game" = 5)
tastes = list("ice" = 1, "water" = 1, "valid" = 5, "salt" = 5, "wats" = 5)
/obj/item/reagent_containers/food/snacks/snowcones/honey
name = "honey flavored snowcone"
desc = "A honey flavord snowball in a paper cup."
icon_state = "honey_sc"
name = "honey snowcone"
desc = "Honey drizzled over a snowball in a paper cup."
icon_state = "amber_sc"
list_reagents = list("nutriment" = 1, "honey" = 5)
tastes = list("pollen" = 5, "sweetness" = 5, "wax" = 1)
tastes = list("ice" = 1, "water" = 1, "flowers" = 5, "sweetness" = 5, "wax" = 1)
/obj/item/reagent_containers/food/snacks/snowcones/rainbow
name = "rainbow color snowcone"
desc = "A rainbow color snowball in a paper cup."
name = "rainbow snowcone"
desc = "A very colorful snowball in a paper cup."
icon_state = "rainbow_sc"
list_reagents = list("nutriment" = 5, "laughter" = 25)
tastes = list("sunlight" = 5, "light" = 5, "slime" = 5, "paint" = 3, "clouds" = 3)
tastes = list("ice" = 1, "water" = 1, "sunlight" = 5, "light" = 5, "slime" = 5, "paint" = 3, "clouds" = 3)

View File

@@ -149,7 +149,7 @@
bonus_reagents = list("nutriment" = 1, "vitamin" = 1)
list_reagents = list("nutriment" = 6, "vitamin" = 1)
tastes = list("meat" = 1)
foodtype = MEAT
foodtype = MEAT | BREAKFAST
var/roasted = FALSE
/obj/item/reagent_containers/food/snacks/sausage/Initialize()
@@ -344,3 +344,19 @@
icon_state = "doubleratkebab"
tastes = list("rat meat" = 2, "metal" = 1)
bonus_reagents = list("nutriment" = 6, "vitamin" = 2)
/obj/item/reagent_containers/food/snacks/kebab/fiesta
name = "fiesta skewer"
icon_state = "fiestaskewer"
tastes = list("tex-mex" = 3, "cumin" = 2)
bonus_reagents = list("vitamin" = 5, "capsaicin" = 3)
/obj/item/reagent_containers/food/snacks/bbqribs
name = "bbq ribs"
desc = "BBQ ribs, slathered in a healthy coating of BBQ sauce. The least vegan thing to ever exist."
icon_state = "ribs"
w_class = WEIGHT_CLASS_NORMAL
list_reagents = list("nutriment" = 8, "vitamin" = 2, "bbqsauce" = 5)
bonus_reagents = list("nutriment" = 1, "vitamin" = 1)
tastes = list("meat" = 3, "smokey sauce" = 1)
foodtype = MEAT

View File

@@ -47,6 +47,7 @@
filling_color = "#A0522D"
tastes = list("chocolate" = 1)
foodtype = JUNKFOOD | SUGAR
dunkable = TRUE
/obj/item/reagent_containers/food/snacks/hugemushroomslice
name = "huge mushroom slice"
@@ -90,6 +91,7 @@
filling_color = "#FFD700"
tastes = list("fries" = 3, "salt" = 1)
foodtype = VEGETABLES | GRAIN | FRIED
dunkable = TRUE
/obj/item/reagent_containers/food/snacks/tatortot
name = "tator tot"
@@ -99,6 +101,7 @@
filling_color = "FFD700"
tastes = list("potato" = 3, "valids" = 1)
foodtype = FRIED | VEGETABLES
dunkable = TRUE
/obj/item/reagent_containers/food/snacks/soydope
name = "soy dope"
@@ -109,6 +112,7 @@
filling_color = "#DEB887"
tastes = list("soy" = 1)
foodtype = VEGETABLES
dunkable = TRUE
/obj/item/reagent_containers/food/snacks/cheesyfries
name = "cheesy fries"
@@ -128,6 +132,7 @@
list_reagents = list("bad_food" = 30)
filling_color = "#8B4513"
foodtype = GROSS
dunkable = TRUE
/obj/item/reagent_containers/food/snacks/carrotfries
name = "carrot fries"
@@ -401,7 +406,6 @@
tastes = list("death" = 2, "rock" = 1, "meat" = 1, "hot peppers" = 1)
foodtype = MEAT
/obj/item/reagent_containers/food/snacks/powercrepe
name = "Powercrepe"
desc = "With great power, comes great crepes. It looks like a pancake filled with jelly but packs quite a punch."
@@ -583,6 +587,7 @@
filling_color = "#ffdf26"
tastes = list("strawberries" = 5, "chocolate" = 3)
foodtype = FRUIT | SUGAR
dunkable = TRUE
/obj/item/reagent_containers/food/snacks/chocolatebanana
name = "Chocolate dipped banana"
@@ -591,4 +596,17 @@
list_reagents = list("sugar" = 5, "nutriment" = 3, "vitamin" = 1)
filling_color = "#ffdf26"
tastes = list("banana" = 5, "chocolate" = 3)
foodtype = FRUIT | SUGAR
foodtype = FRUIT | SUGAR
dunkable = TRUE
/obj/item/reagent_containers/food/snacks/cornchips
name = "boritos corn chips"
desc = "Triangular corn chips. They do seem a bit bland but would probably go well with some kind of dipping sauce."
icon_state = "boritos"
trash = /obj/item/trash/boritos
bitesize = 2
list_reagents = list("nutriment" = 3, "cooking_oil" = 2, "sodiumchloride" = 3)
filling_color = "#ECA735"
tastes = list("fried corn" = 1)
foodtype = JUNKFOOD | FRIED
dunkable = TRUE

View File

@@ -5,28 +5,31 @@
/obj/item/reagent_containers/food/snacks/donut
name = "donut"
desc = "Goes great with robust coffee."
icon_state = "donut1"
icon = 'icons/obj/food/donut.dmi'
icon_state = "donut"
bitesize = 5
bonus_reagents = list("sugar" = 1)
list_reagents = list("nutriment" = 3, "sprinkles" = 1, "sugar" = 2)
filling_color = "#D2691E"
tastes = list("donut" = 1)
foodtype = JUNKFOOD | GRAIN | FRIED | SUGAR
var/frosted_icon = "donut2"
var/is_frosted = FALSE
foodtype = JUNKFOOD | GRAIN | FRIED | SUGAR | BREAKFAST
dunkable = TRUE
var/decorated_icon = "donut_homer"
var/is_decorated = FALSE
var/extra_reagent = null
var/decorated_adjective = "sprinkled"
/obj/item/reagent_containers/food/snacks/donut/Initialize()
. = ..()
if(prob(30))
frost_donut()
decorate_donut()
/obj/item/reagent_containers/food/snacks/donut/proc/frost_donut()
if(is_frosted || !frosted_icon)
/obj/item/reagent_containers/food/snacks/donut/proc/decorate_donut()
if(is_decorated || !decorated_icon)
return
is_frosted = TRUE
name = "frosted [name]"
icon_state = frosted_icon //delish~!
is_decorated = TRUE
name = "[decorated_adjective] [name]"
icon_state = decorated_icon //delish~!
reagents.add_reagent("sprinkles", 1)
filling_color = "#FF69B4"
return TRUE
@@ -43,64 +46,283 @@
return
..()
/obj/item/reagent_containers/food/snacks/donut/plain
//Use this donut ingame
/obj/item/reagent_containers/food/snacks/donut/chaos
name = "chaos donut"
desc = "Like life, it never quite tastes the same."
icon_state = "donut_chaos"
bitesize = 10
tastes = list("donut" = 3, "chaos" = 1)
/obj/item/reagent_containers/food/snacks/donut/chaos/Initialize()
. = ..()
extra_reagent = pick("nutriment", "capsaicin", "frostoil", "krokodil", "plasma", "cocoa", "slimejelly", "banana", "berryjuice", "omnizine")
reagents.add_reagent("[extra_reagent]", 3)
reagents.add_reagent(extra_reagent, 3)
/obj/item/reagent_containers/food/snacks/donut/meat
name = "Meat Donut"
desc = "Tastes as gross as it looks."
icon_state = "donut_meat"
bonus_reagents = list("ketchup" = 1)
list_reagents = list("nutriment" = 3, "ketchup" = 2)
tastes = list("meat" = 1)
foodtype = JUNKFOOD | MEAT | GROSS | FRIED | BREAKFAST
/obj/item/reagent_containers/food/snacks/donut/berry
name = "pink donut"
desc = "Goes great with a soy latte."
icon_state = "donut_pink"
bonus_reagents = list("berryjuice" = 3, "sprinkles" = 1) //Extra sprinkles to reward frosting
filling_color = "#E57d9A"
decorated_icon = "donut_homer"
/obj/item/reagent_containers/food/snacks/donut/trumpet
name = "spaceman's donut"
desc = "Goes great with a cold beaker of malk."
icon_state = "donut_purple"
bonus_reagents = list("polypyr" = 3, "sprinkles" = 1)
tastes = list("donut" = 3, "violets" = 1)
is_decorated = TRUE
filling_color = "#8739BF"
/obj/item/reagent_containers/food/snacks/donut/apple
name = "apple donut"
desc = "Goes great with a shot of cinnamon schnapps."
icon_state = "donut_green"
bonus_reagents = list("applejuice" = 3, "sprinkles" = 1)
tastes = list("donut" = 3, "green apples" = 1)
is_decorated = TRUE
filling_color = "#6ABE30"
/obj/item/reagent_containers/food/snacks/donut/caramel
name = "caramel donut"
desc = "Goes great with a mug of hot coco."
icon_state = "donut_beige"
bonus_reagents = list("caramel" = 3, "sprinkles" = 1)
tastes = list("donut" = 3, "buttery sweetness" = 1)
is_decorated = TRUE
filling_color = "#D4AD5B"
/obj/item/reagent_containers/food/snacks/donut/choco
name = "chocolate donut"
desc = "Goes great with a glass of warm milk."
icon_state = "donut_choc"
bonus_reagents = list("hot_coco" = 3, "sprinkles" = 1) //the coco reagent is just bitter.
tastes = list("donut" = 4, "bitterness" = 1)
decorated_icon = "donut_choc_sprinkles"
filling_color = "#4F230D"
/obj/item/reagent_containers/food/snacks/donut/blumpkin
name = "blumpkin donut"
desc = "Goes great with a mug of soothing drunken blumpkin."
icon_state = "donut_blue"
bonus_reagents = list("blumpkinjuice" = 3, "sprinkles" = 1)
tastes = list("donut" = 2, "blumpkin" = 1)
is_decorated = TRUE
filling_color = "#2788C4"
/obj/item/reagent_containers/food/snacks/donut/bungo
name = "bungo donut"
desc = "Goes great with a mason jar of hippie's delight."
icon_state = "donut_yellow"
bonus_reagents = list("bungojuice" = 3, "sprinkles" = 1)
tastes = list("donut" = 3, "tropical sweetness" = 1)
is_decorated = TRUE
filling_color = "#DEC128"
/obj/item/reagent_containers/food/snacks/donut/matcha
name = "matcha donut"
desc = "Goes great with a cup of tea."
icon_state = "donut_olive"
bonus_reagents = list("teapowder = 3", "sprinkles" = 1)
tastes = list("donut" = 3, "matcha" = 1)
is_decorated = TRUE
filling_color = "#879630"
//////////////////////JELLY DONUTS/////////////////////////
/obj/item/reagent_containers/food/snacks/donut/jelly
name = "jelly donut"
desc = "You jelly?"
icon_state = "jdonut1"
frosted_icon = "jdonut2"
icon_state = "jelly"
decorated_icon = "jelly_homer"
bonus_reagents = list("sugar" = 1, "vitamin" = 1)
extra_reagent = "berryjuice"
tastes = list("jelly" = 1, "donut" = 3)
foodtype = JUNKFOOD | GRAIN | FRIED | FRUIT | SUGAR
foodtype = JUNKFOOD | GRAIN | FRIED | FRUIT | SUGAR | BREAKFAST
/obj/item/reagent_containers/food/snacks/donut/jelly/Initialize()
. = ..()
if(extra_reagent)
reagents.add_reagent("[extra_reagent]", 3)
/obj/item/reagent_containers/food/snacks/donut/jelly/plain //use this ingame to avoid inheritance related crafting issues.
/obj/item/reagent_containers/food/snacks/donut/jelly/berry
name = "pink jelly donut"
desc = "Goes great with a soy latte."
icon_state = "jelly_pink"
bonus_reagents = list("berryjuice" = 3, "sprinkles" = 1, "vitamin" = 1) //Extra sprinkles to reward frosting.
filling_color = "#E57d9A"
decorated_icon = "jelly_homer"
/obj/item/reagent_containers/food/snacks/donut/jelly/trumpet
name = "spaceman's jelly donut"
desc = "Goes great with a cold beaker of malk."
icon_state = "jelly_purple"
bonus_reagents = list("polypyr" = 3, "sprinkles" = 1, "vitamin" = 1)
tastes = list("jelly" = 1, "donut" = 3, "violets" = 1)
is_decorated = TRUE
filling_color = "#8739BF"
/obj/item/reagent_containers/food/snacks/donut/jelly/apple
name = "apple jelly donut"
desc = "Goes great with a shot of cinnamon schnapps."
icon_state = "jelly_green"
bonus_reagents = list("applejuice" = 3, "sprinkles" = 1, "vitamin" = 1)
tastes = list("jelly" = 1, "donut" = 3, "green apples" = 1)
is_decorated = TRUE
filling_color = "#6ABE30"
/obj/item/reagent_containers/food/snacks/donut/jelly/caramel
name = "caramel jelly donut"
desc = "Goes great with a mug of hot coco."
icon_state = "jelly_beige"
bonus_reagents = list("caramel" = 3, "sprinkles" = 1, "vitamin" = 1)
tastes = list("jelly" = 1, "donut" = 3, "buttery sweetness" = 1)
is_decorated = TRUE
filling_color = "#D4AD5B"
/obj/item/reagent_containers/food/snacks/donut/jelly/choco
name = "chocolate jelly donut"
desc = "Goes great with a glass of warm milk."
icon_state = "jelly_choc"
bonus_reagents = list("hot_coco" = 3, "sprinkles" = 1, "vitamin" = 1) //the coco reagent is just bitter.
tastes = list("jelly" = 1, "donut" = 4, "bitterness" = 1)
decorated_icon = "jelly_choc_sprinkles"
filling_color = "#4F230D"
/obj/item/reagent_containers/food/snacks/donut/jelly/blumpkin
name = "blumpkin jelly donut"
desc = "Goes great with a mug of soothing drunken blumpkin."
icon_state = "jelly_blue"
bonus_reagents = list("blumpkinjuice" = 3, "sprinkles" = 1, "vitamin" = 1)
tastes = list("jelly" = 1, "donut" = 2, "blumpkin" = 1)
is_decorated = TRUE
filling_color = "#2788C4"
/obj/item/reagent_containers/food/snacks/donut/jelly/bungo
name = "bungo jelly donut"
desc = "Goes great with a mason jar of hippie's delight."
icon_state = "jelly_yellow"
bonus_reagents = list("bungojuice" = 3, "sprinkles" = 1, "vitamin" = 1)
tastes = list("jelly" = 1, "donut" = 3, "tropical sweetness" = 1)
is_decorated = TRUE
filling_color = "#DEC128"
/obj/item/reagent_containers/food/snacks/donut/jelly/matcha
name = "matcha jelly donut"
desc = "Goes great with a cup of tea."
icon_state = "jelly_olive"
bonus_reagents = list("teapowder" = 3, "sprinkles" = 1, "vitamin" = 1)
tastes = list("jelly" = 1, "donut" = 3, "matcha" = 1)
is_decorated = TRUE
filling_color = "#879630"
//////////////////////////SLIME DONUTS/////////////////////////
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly
name = "jelly donut"
desc = "You jelly?"
icon_state = "jdonut1"
icon_state = "jelly"
extra_reagent = "slimejelly"
foodtype = JUNKFOOD | GRAIN | FRIED | TOXIC | SUGAR
foodtype = JUNKFOOD | GRAIN | FRIED | TOXIC | SUGAR | BREAKFAST
/obj/item/reagent_containers/food/snacks/donut/jelly/cherryjelly
name = "jelly donut"
desc = "You jelly?"
icon_state = "jdonut1"
extra_reagent = "cherryjelly"
foodtype = JUNKFOOD | GRAIN | FRIED | FRUIT
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/plain
/obj/item/reagent_containers/food/snacks/donut/meat
bonus_reagents = list("ketchup" = 1)
list_reagents = list("nutriment" = 3, "ketchup" = 2)
tastes = list("meat" = 1)
foodtype = JUNKFOOD | MEAT | GROSS | FRIED
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/berry
name = "pink jelly donut"
desc = "Goes great with a soy latte."
icon_state = "jelly_pink"
bonus_reagents = list("berryjuice" = 3, "sprinkles" = 1, "vitamin" = 1) //Extra sprinkles to reward frosting
filling_color = "#E57d9A"
/obj/item/reagent_containers/food/snacks/donut/semen
name = "\"cream\" donut"
desc = "That cream looks a little runny..."
icon_state = "donut3"
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/trumpet
name = "spaceman's jelly donut"
desc = "Goes great with a cold beaker of malk."
icon_state = "jelly_purple"
bonus_reagents = list("polypyr" = 3, "sprinkles" = 1, "vitamin" = 1)
tastes = list("jelly" = 1, "donut" = 3, "violets" = 1)
is_decorated = TRUE
filling_color = "#8739BF"
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/apple
name = "apple jelly donut"
desc = "Goes great with a shot of cinnamon schnapps."
icon_state = "jelly_green"
bonus_reagents = list("applejuice" = 3, "sprinkles" = 1, "vitamin" = 1)
tastes = list("jelly" = 1, "donut" = 3, "green apples" = 1)
is_decorated = TRUE
filling_color = "#6ABE30"
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/caramel
name = "caramel jelly donut"
desc = "Goes great with a mug of hot coco."
icon_state = "jelly_beige"
bonus_reagents = list("caramel" = 3, "sprinkles" = 1, "vitamin" = 1)
tastes = list("jelly" = 1, "donut" = 3, "buttery sweetness" = 1)
is_decorated = TRUE
filling_color = "#D4AD5B"
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/choco
name = "chocolate jelly donut"
desc = "Goes great with a glass of warm milk."
icon_state = "jelly_choc"
bonus_reagents = list("hot_coco" = 3, "sprinkles" = 1, "vitamin" = 1) //the coco reagent is just bitter.
tastes = list("jelly" = 1, "donut" = 4, "bitterness" = 1)
decorated_icon = "jelly_choc_sprinkles"
filling_color = "#4F230D"
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/blumpkin
name = "blumpkin jelly donut"
desc = "Goes great with a mug of soothing drunken blumpkin."
icon_state = "jelly_blue"
bonus_reagents = list("blumpkinjuice" = 3, "sprinkles" = 1, "vitamin" = 1)
tastes = list("jelly" = 1, "donut" = 2, "blumpkin" = 1)
is_decorated = TRUE
filling_color = "#2788C4"
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/bungo
name = "bungo jelly donut"
desc = "Goes great with a mason jar of hippie's delight."
icon_state = "jelly_yellow"
bonus_reagents = list("bungojuice" = 3, "sprinkles" = 1, "vitamin" = 1)
tastes = list("jelly" = 1, "donut" = 3, "tropical sweetness" = 1)
is_decorated = TRUE
filling_color = "#DEC128"
/obj/item/reagent_containers/food/snacks/donut/jelly/slimejelly/matcha
name = "matcha jelly donut"
desc = "Goes great with a cup of tea."
icon_state = "jelly_olive"
bonus_reagents = list("teapowder" = 3, "sprinkles" = 1, "vitamin" = 1)
tastes = list("jelly" = 1, "donut" = 3, "matcha" = 1)
is_decorated = TRUE
filling_color = "#879630"
/obj/item/reagent_containers/food/snacks/donut/glaze
name = "glazed donut"
desc = "A sugar glazed donut."
icon_state = "donut_glaze"
bitesize = 10
bonus_reagents = list("semen" = 1)
list_reagents = list("nutriment" = 3, "sugar" = 2, "semen" = 5)
bonus_reagents = list("sugar" = 3)
list_reagents = list("nutriment" = 3, "sugar" = 8)
filling_color = "#FFFFFF"
tastes = list("donut" = 1, "salt" = 3)
foodtype = JUNKFOOD | GRAIN | FRIED | SUGAR
////////////////////////////////////////////MUFFINS////////////////////////////////////////////
/obj/item/reagent_containers/food/snacks/muffin
@@ -111,14 +333,14 @@
list_reagents = list("nutriment" = 6)
filling_color = "#F4A460"
tastes = list("muffin" = 1)
foodtype = GRAIN | SUGAR
foodtype = GRAIN | SUGAR | BREAKFAST
/obj/item/reagent_containers/food/snacks/muffin/berry
name = "berry muffin"
icon_state = "berrymuffin"
desc = "A delicious and spongy little cake, with berries."
tastes = list("muffin" = 3, "berry" = 1)
foodtype = GRAIN | FRUIT | SUGAR
foodtype = GRAIN | FRUIT | SUGAR | BREAKFAST
/obj/item/reagent_containers/food/snacks/muffin/booberry
name = "booberry muffin"
@@ -126,7 +348,7 @@
alpha = 125
desc = "My stomach is a graveyard! No living being can quench my bloodthirst!"
tastes = list("muffin" = 3, "spookiness" = 1)
foodtype = GRAIN | FRUIT | SUGAR
foodtype = GRAIN | FRUIT | SUGAR | BREAKFAST
/obj/item/reagent_containers/food/snacks/chawanmushi
name = "chawanmushi"
@@ -149,7 +371,7 @@
list_reagents = list("nutriment" = 8, "vitamin" = 1)
filling_color = "#D2691E"
tastes = list("waffles" = 1)
foodtype = GRAIN | SUGAR
foodtype = GRAIN | SUGAR | BREAKFAST
/obj/item/reagent_containers/food/snacks/soylentgreen
name = "\improper Soylent Green"
@@ -160,7 +382,7 @@
list_reagents = list("nutriment" = 10, "vitamin" = 1)
filling_color = "#9ACD32"
tastes = list("waffles" = 7, "people" = 1)
foodtype = GRAIN | GROSS | MEAT
foodtype = GRAIN | MEAT
/obj/item/reagent_containers/food/snacks/soylenviridians
name = "\improper Soylent Virdians"
@@ -183,7 +405,7 @@
list_reagents = list("nutriment" = 8, "mushroomhallucinogen" = 2, "vitamin" = 2)
filling_color = "#00BFFF"
tastes = list("waffle" = 1, "mushrooms" = 1)
foodtype = GRAIN | VEGETABLES | TOXIC | SUGAR
foodtype = GRAIN | VEGETABLES | SUGAR | BREAKFAST
////////////////////////////////////////////OTHER////////////////////////////////////////////
@@ -196,7 +418,8 @@
list_reagents = list("nutriment" = 1)
filling_color = "#F0E68C"
tastes = list("cookie" = 1)
foodtype = SUGAR
foodtype = GRAIN | SUGAR
dunkable = TRUE
/obj/item/reagent_containers/food/snacks/donkpocket
name = "\improper Donk-pocket"
@@ -213,6 +436,7 @@
desc = "The heated food of choice for the seasoned traitor."
bonus_reagents = list("omnizine" = 3)
list_reagents = list("nutriment" = 4, "omnizine" = 3)
cooked_type = null
tastes = list("meat" = 2, "dough" = 2, "laziness" = 1)
foodtype = GRAIN
@@ -315,6 +539,7 @@
filling_color = "#CD853F"
tastes = list("sweetness" = 1)
foodtype = GRAIN | JUNKFOOD | SUGAR
dunkable = TRUE
/obj/item/reagent_containers/food/snacks/chococornet
name = "chocolate cornet"
@@ -335,6 +560,7 @@
filling_color = "#D2691E"
tastes = list("cookie" = 2, "oat" = 1)
foodtype = GRAIN
dunkable = TRUE
/obj/item/reagent_containers/food/snacks/raisincookie
name = "raisin cookie"
@@ -345,6 +571,7 @@
filling_color = "#F0E68C"
tastes = list("cookie" = 1, "raisins" = 1)
foodtype = GRAIN | FRUIT
dunkable = TRUE
/obj/item/reagent_containers/food/snacks/cherrycupcake
name = "cherry cupcake"
@@ -367,7 +594,7 @@
foodtype = GRAIN | FRUIT | SUGAR
/obj/item/reagent_containers/food/snacks/strawberrycupcake
name = "Strawberry cupcake"
name = "strawberry cupcake"
desc = "Strawberry inside a delicious cupcake."
icon_state = "strawberrycupcake"
bonus_reagents = list("nutriment" = 1, "vitamin" = 3)
@@ -384,9 +611,9 @@
list_reagents = list("nutriment" = 5, "honey" = 5)
filling_color = "#F2CE91"
tastes = list("pastry" = 1, "sweetness" = 1)
foodtype = GRAIN
foodtype = GRAIN | SUGAR
#define PANCAKE_MAX_STACK 30
#define PANCAKE_MAX_STACK 10
/obj/item/reagent_containers/food/snacks/pancakes
name = "pancake"
@@ -397,7 +624,7 @@
list_reagents = list("nutriment" = 4, "vitamin" = 1)
filling_color = "#D2691E"
tastes = list("pancakes" = 1)
foodtype = GRAIN | SUGAR
foodtype = GRAIN | SUGAR | BREAKFAST
/obj/item/reagent_containers/food/snacks/pancakes/blueberry
name = "blueberry pancake"

View File

@@ -47,7 +47,7 @@
bonus_reagents = list("nutriment" = 4, "vitamin" = 4)
list_reagents = list("nutriment" = 7, "milk" = 10, "vitamin" = 2)
tastes = list("oats" = 1, "milk" = 1)
foodtype = DAIRY | GRAIN
foodtype = DAIRY | GRAIN | BREAKFAST
/obj/item/reagent_containers/food/snacks/salad/fruit
name = "fruit salad"
@@ -55,7 +55,7 @@
icon_state = "fruitsalad"
bonus_reagents = list("nutriment" = 2, "vitamin" = 4)
tastes = list("fruit" = 1)
foodtype = FRUIT
foodtype = FRUIT | BREAKFAST
/obj/item/reagent_containers/food/snacks/salad/jungle
name = "jungle salad"
@@ -64,7 +64,7 @@
bonus_reagents = list("nutriment" = 4, "vitamin" = 4)
list_reagents = list("nutriment" = 7, "banana" = 5, "vitamin" = 4)
tastes = list("fruit" = 1, "the jungle" = 1)
foodtype = FRUIT
foodtype = FRUIT | BREAKFAST
/obj/item/reagent_containers/food/snacks/salad/citrusdelight
name = "citrus delight"
@@ -73,7 +73,7 @@
bonus_reagents = list("nutriment" = 4, "vitamin" = 4)
list_reagents = list("nutriment" = 7, "vitamin" = 5)
tastes = list("sourness" = 1, "leaves" = 1)
foodtype = FRUIT
foodtype = FRUIT | BREAKFAST
/obj/item/reagent_containers/food/snacks/salad/ricebowl
name = "ricebowl"
@@ -91,7 +91,7 @@
bonus_reagents = list("nutriment" = 1, "vitamin" = 1)
list_reagents = list("nutriment" = 5, "vitamin" = 1)
tastes = list("rice" = 1)
foodtype = GRAIN
foodtype = GRAIN | BREAKFAST
/obj/item/reagent_containers/food/snacks/salad/ricepudding
name = "rice pudding"

View File

@@ -225,3 +225,22 @@
bonus_reagents = list("nutriment" = 4, "vitamin" = 6)
tastes = list("beet" = 1)
foodtype = VEGETABLES
/obj/item/reagent_containers/food/snacks/soup/electron
name = "electron soup"
desc = "A gastronomic curiosity of ethereal origin. It is famed for the minature weather system formed over a properly prepared soup."
icon_state = "electronsoup"
list_reagents = list("nutriment" = 3, "liquidelectricity" = 5)
tastes = list("mushroom" = 1, "electrons" = 4, "shockingly good")
filling_color = "#CC2B52"
foodtype = VEGETABLES | TOXIC
/obj/item/reagent_containers/food/snacks/soup/bungocurry
name = "bungo curry"
desc = "A spicy vegetable curry made with the humble bungo fruit, Exotic!"
icon_state = "bungocurry"
bonus_reagents = list("vitamin" = 11)
list_reagents = list("nutriment" = 6, "capsaicin" = 5)
tastes = list("bungo" = 2, "hot curry" = 4, "tropical sweetness" = 1)
filling_color = "#E6A625"
foodtype = VEGETABLES | FRUIT | DAIRY

View File

@@ -80,6 +80,9 @@ God bless America.
I.reagents.trans_to(src, I.reagents.total_volume)
qdel(I)
return
if(istype(I,/obj/item/clothing/head/mob_holder))
to_chat(user, "<span class='warning'>This does not fit in the fryer.</span>") // TODO: Deepfrying instakills mobs, spawns a whole deep-fried mob.
return
if(!reagents.has_reagent("cooking_oil"))
to_chat(user, "<span class='warning'>[src] has no cooking oil to fry with!</span>")
return

Some files were not shown because too many files have changed in this diff Show More