Merge branch 'master' into WanderingFox95-TheUnrollingPin
This commit is contained in:
@@ -412,6 +412,24 @@ If not set, defaults to check_completion instead. Set it. It's used by cryo.
|
||||
counter++
|
||||
return counter >= 8
|
||||
|
||||
/datum/objective/freedom
|
||||
name = "freedom"
|
||||
explanation_text = "Don't get captured by nanotrasen."
|
||||
team_explanation_text = "Have all members of your team free of nanotrasen custody."
|
||||
|
||||
/datum/objective/freedom/check_completion()
|
||||
var/list/datum/mind/owners = get_owners()
|
||||
for(var/m in owners)
|
||||
var/datum/mind/M = m
|
||||
if(!considered_alive(M))
|
||||
return FALSE
|
||||
if(SSshuttle.emergency.mode != SHUTTLE_ENDGAME)
|
||||
return FALSE
|
||||
var/turf/location = get_turf(M.current)
|
||||
if(!location || istype(location, /turf/open/floor/plasteel/shuttle/red) || istype(location, /turf/open/floor/mineral/plastitanium/red/brig)) // Fails if they are in the shuttle brig
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/datum/objective/escape
|
||||
name = "escape"
|
||||
explanation_text = "Escape on the shuttle or an escape pod alive and without being in custody."
|
||||
|
||||
+234
-195
@@ -1,9 +1,5 @@
|
||||
#define LIMBGROWER_MAIN_MENU 1
|
||||
#define LIMBGROWER_CATEGORY_MENU 2
|
||||
#define LIMBGROWER_CHEMICAL_MENU 3
|
||||
//use these for the menu system
|
||||
|
||||
|
||||
/// The limbgrower. Makes organd and limbs with synthflesh and chems.
|
||||
/// See [limbgrower_designs.dm] for everything we can make.
|
||||
/obj/machinery/limbgrower
|
||||
name = "limb grower"
|
||||
desc = "It grows new limbs using Synthflesh."
|
||||
@@ -15,161 +11,235 @@
|
||||
active_power_usage = 100
|
||||
circuit = /obj/item/circuitboard/machine/limbgrower
|
||||
|
||||
var/operating = FALSE
|
||||
var/disabled = FALSE
|
||||
/// The category of limbs we're browing in our UI.
|
||||
var/selected_category = "human"
|
||||
/// If we're currently printing something.
|
||||
var/busy = FALSE
|
||||
var/prod_coeff = 1
|
||||
/// How efficient our machine is. Better parts = less chemicals used and less power used. Range of 1 to 0.25.
|
||||
var/production_coefficient = 1
|
||||
/// How long it takes for us to print a limb. Affected by production_coefficient.
|
||||
var/production_speed = 3 SECONDS
|
||||
/// The design we're printing currently.
|
||||
var/datum/design/being_built
|
||||
/// Our internal techweb for limbgrower designs.
|
||||
var/datum/techweb/stored_research
|
||||
var/selected_category
|
||||
var/screen = 1
|
||||
/// All the categories of organs we can print.
|
||||
var/list/categories = list(
|
||||
"human" = /datum/species/human,
|
||||
"lizard" = /datum/species/lizard,
|
||||
"mammal" = /datum/species/mammal,
|
||||
"insect" = /datum/species/insect,
|
||||
"fly" = /datum/species/fly,
|
||||
"plasmaman" = /datum/species/plasmaman,
|
||||
"xeno" = /datum/species/xeno,
|
||||
"other" = /datum/species,
|
||||
)
|
||||
var/list/stored_species = list()
|
||||
"human",
|
||||
"lizard",
|
||||
"mammal",
|
||||
"insect",
|
||||
"fly",
|
||||
"plasmaman",
|
||||
"xeno",
|
||||
"other",
|
||||
)
|
||||
var/obj/item/disk/data/dna_disk
|
||||
|
||||
/obj/machinery/limbgrower/Initialize()
|
||||
create_reagents(100, OPENCONTAINER)
|
||||
stored_research = new /datum/techweb/specialized/autounlocking/limbgrower
|
||||
for(var/i in categories)
|
||||
var/species = categories[i]
|
||||
stored_species[i] = new species()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/plumbing/simple_demand)
|
||||
AddComponent(/datum/component/simple_rotation, ROTATION_WRENCH | ROTATION_CLOCKWISE, null, CALLBACK(src, .proc/can_be_rotated))
|
||||
|
||||
/obj/machinery/limbgrower/ui_interact(mob/user)
|
||||
/obj/machinery/limbgrower/ui_interact(mob/user, datum/tgui/ui)
|
||||
. = ..()
|
||||
if(!is_operational())
|
||||
return
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "Limbgrower", src)
|
||||
ui.open()
|
||||
|
||||
var/dat = main_win(user)
|
||||
/obj/machinery/limbgrower/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
switch(screen)
|
||||
if(LIMBGROWER_MAIN_MENU)
|
||||
dat = main_win(user)
|
||||
if(LIMBGROWER_CATEGORY_MENU)
|
||||
dat = category_win(user,selected_category)
|
||||
if(LIMBGROWER_CHEMICAL_MENU)
|
||||
dat = chemical_win(user)
|
||||
for(var/datum/reagent/reagent_id in reagents.reagent_list)
|
||||
var/list/reagent_data = list(
|
||||
reagent_name = reagent_id.name,
|
||||
reagent_amount = reagent_id.volume,
|
||||
reagent_type = reagent_id.type
|
||||
)
|
||||
data["reagents"] += list(reagent_data)
|
||||
|
||||
var/datum/browser/popup = new(user, "Limb Grower", name, 400, 500)
|
||||
popup.set_content(dat)
|
||||
popup.open()
|
||||
data["total_reagents"] = reagents.total_volume
|
||||
data["max_reagents"] = reagents.maximum_volume
|
||||
data["busy"] = busy
|
||||
var/list/disk_data = list()
|
||||
disk_data["disk"] = dna_disk //Do i, the machine, have a disk?
|
||||
disk_data["name"] = dna_disk?.fields["name"] //Name for the human saved if there is one
|
||||
data["disk"] = disk_data
|
||||
|
||||
return data
|
||||
|
||||
/obj/machinery/limbgrower/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["categories"] = list()
|
||||
|
||||
var/species_categories = categories.Copy()
|
||||
for(var/species in species_categories)
|
||||
species_categories[species] = list()
|
||||
for(var/design_id in stored_research.researched_designs)
|
||||
var/datum/design/limb_design = SSresearch.techweb_design_by_id(design_id)
|
||||
for(var/found_category in species_categories)
|
||||
if(found_category in limb_design.category)
|
||||
species_categories[found_category] += limb_design
|
||||
|
||||
for(var/category in species_categories)
|
||||
var/list/category_data = list(
|
||||
name = category,
|
||||
designs = list(),
|
||||
)
|
||||
for(var/datum/design/found_design in species_categories[category])
|
||||
var/list/all_reagents = list()
|
||||
for(var/reagent_typepath in found_design.reagents_list)
|
||||
var/datum/reagent/reagent_id = find_reagent_object_from_type(reagent_typepath)
|
||||
var/list/reagent_data = list(
|
||||
name = reagent_id.name,
|
||||
amount = (found_design.reagents_list[reagent_typepath] * production_coefficient),
|
||||
)
|
||||
all_reagents += list(reagent_data)
|
||||
|
||||
category_data["designs"] += list(list(
|
||||
parent_category = category,
|
||||
name = found_design.name,
|
||||
id = found_design.id,
|
||||
needed_reagents = all_reagents,
|
||||
))
|
||||
|
||||
data["categories"] += list(category_data)
|
||||
|
||||
return data
|
||||
|
||||
/obj/machinery/limbgrower/on_deconstruction()
|
||||
for(var/obj/item/reagent_containers/glass/G in component_parts)
|
||||
reagents.trans_to(G, G.reagents.maximum_volume)
|
||||
for(var/obj/item/reagent_containers/glass/our_beaker in component_parts)
|
||||
reagents.trans_to(our_beaker, our_beaker.reagents.maximum_volume)
|
||||
..()
|
||||
|
||||
/obj/machinery/limbgrower/attackby(obj/item/O, mob/user, params)
|
||||
if(busy)
|
||||
/obj/machinery/limbgrower/attackby(obj/item/user_item, mob/living/user, params)
|
||||
if (busy)
|
||||
to_chat(user, "<span class=\"alert\">\The [src] is busy. Please wait for completion of previous operation.</span>")
|
||||
return
|
||||
|
||||
if(default_deconstruction_screwdriver(user, "limbgrower_panelopen", "limbgrower_idleoff", O))
|
||||
updateUsrDialog()
|
||||
if(default_deconstruction_screwdriver(user, "limbgrower_panelopen", "limbgrower_idleoff", user_item))
|
||||
ui_close(user)
|
||||
return
|
||||
|
||||
if(panel_open && default_deconstruction_crowbar(O))
|
||||
return
|
||||
|
||||
if(user.a_intent == INTENT_HARM) //so we can hit the machine
|
||||
if(user_item.tool_behaviour == TOOL_WRENCH && panel_open)
|
||||
return ..()
|
||||
|
||||
if(istype(O, /obj/item/disk))
|
||||
if(panel_open && default_deconstruction_crowbar(user_item))
|
||||
return
|
||||
|
||||
if(istype(user_item, /obj/item/disk))
|
||||
if(dna_disk)
|
||||
to_chat(user, "<span class='warning'>\The [src] already has a dna disk, take it out first!</span>")
|
||||
return
|
||||
else
|
||||
O.forceMove(src)
|
||||
dna_disk = O
|
||||
to_chat(user, "<span class='notice'>You insert \the [O] into \the [src].</span>")
|
||||
user_item.forceMove(src)
|
||||
dna_disk = user_item
|
||||
to_chat(user, "<span class='notice'>You insert \the [user_item] into \the [src].</span>")
|
||||
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
|
||||
return
|
||||
|
||||
/obj/machinery/limbgrower/Topic(href, href_list)
|
||||
if(..())
|
||||
if(user.a_intent != INTENT_HELP)
|
||||
return ..()
|
||||
|
||||
/obj/machinery/limbgrower/proc/can_be_rotated()
|
||||
if(panel_open)
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/obj/machinery/limbgrower/ui_act(action, list/params)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
if (!busy)
|
||||
if(href_list["menu"])
|
||||
screen = text2num(href_list["menu"])
|
||||
|
||||
if(href_list["category"])
|
||||
selected_category = href_list["category"]
|
||||
if (busy)
|
||||
to_chat(usr, "<span class='danger'>\The [src] is busy. Please wait for completion of previous operation.</span>")
|
||||
return
|
||||
|
||||
if(href_list["disposeI"]) //Get rid of a reagent incase you add the wrong one by mistake
|
||||
reagents.del_reagent(text2path(href_list["disposeI"]))
|
||||
switch(action)
|
||||
|
||||
if(href_list["make"])
|
||||
if("empty_reagent")
|
||||
reagents.del_reagent(text2path(params["reagent_type"]))
|
||||
. = TRUE
|
||||
|
||||
/////////////////
|
||||
//href protection
|
||||
being_built = stored_research.isDesignResearchedID(href_list["make"]) //check if it's a valid design
|
||||
if("eject_disk")
|
||||
eject_disk(usr)
|
||||
|
||||
if("make_limb")
|
||||
being_built = stored_research.isDesignResearchedID(params["design_id"])
|
||||
if(!being_built)
|
||||
return
|
||||
CRASH("[src] was passed an invalid design id!")
|
||||
|
||||
/// All the reagents we're using to make our organ.
|
||||
var/list/consumed_reagents_list = being_built.reagents_list.Copy()
|
||||
/// The amount of power we're going to use, based on how much reagent we use.
|
||||
var/power = 0
|
||||
|
||||
var/synth_cost = being_built.reagents_list[/datum/reagent/medicine/synthflesh]*prod_coeff
|
||||
var/power = max(2000, synth_cost/5)
|
||||
for(var/reagent_id in consumed_reagents_list)
|
||||
consumed_reagents_list[reagent_id] *= production_coefficient
|
||||
if(!reagents.has_reagent(reagent_id, consumed_reagents_list[reagent_id]))
|
||||
audible_message("<span class='notice'>\The [src] buzzes.</span>")
|
||||
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
|
||||
return
|
||||
|
||||
if(reagents.has_reagent(/datum/reagent/medicine/synthflesh, being_built.reagents_list[/datum/reagent/medicine/synthflesh]*prod_coeff))
|
||||
busy = TRUE
|
||||
use_power(power)
|
||||
flick("limbgrower_fill",src)
|
||||
icon_state = "limbgrower_idleon"
|
||||
addtimer(CALLBACK(src, .proc/build_item),32*prod_coeff)
|
||||
power = max(2000, (power + consumed_reagents_list[reagent_id]))
|
||||
|
||||
if(href_list["dna_disk"])
|
||||
var/mob/living/carbon/user = usr
|
||||
if(istype(user))
|
||||
if(!dna_disk)
|
||||
var/obj/item/disk/diskette = user.get_active_held_item()
|
||||
if(istype(diskette))
|
||||
diskette.forceMove(src)
|
||||
dna_disk = diskette
|
||||
to_chat(user, "<span class='notice'>You insert \the [diskette] into \the [src].</span>")
|
||||
else
|
||||
dna_disk.forceMove(src.loc)
|
||||
user.put_in_active_hand(dna_disk)
|
||||
to_chat(user, "<span class='notice'>You remove \the [dna_disk] from \the [src].</span>")
|
||||
dna_disk = null
|
||||
else
|
||||
to_chat(user, "<span class='warning'>You are unable to grasp \the [dna_disk] disk from \the [src].</span>")
|
||||
else
|
||||
to_chat(usr, "<span class=\"alert\">\The [src] is busy. Please wait for completion of previous operation.</span>")
|
||||
busy = TRUE
|
||||
use_power(power)
|
||||
flick("limbgrower_fill",src)
|
||||
icon_state = "limbgrower_idleon"
|
||||
selected_category = params["active_tab"]
|
||||
addtimer(CALLBACK(src, .proc/build_item, consumed_reagents_list), production_speed * production_coefficient)
|
||||
. = TRUE
|
||||
|
||||
updateUsrDialog()
|
||||
return
|
||||
|
||||
/obj/machinery/limbgrower/proc/build_item()
|
||||
if(reagents.has_reagent(/datum/reagent/medicine/synthflesh, being_built.reagents_list[/datum/reagent/medicine/synthflesh]*prod_coeff)) //sanity check, if this happens we are in big trouble
|
||||
reagents.remove_reagent(/datum/reagent/medicine/synthflesh, being_built.reagents_list[/datum/reagent/medicine/synthflesh]*prod_coeff)
|
||||
var/buildpath = being_built.build_path
|
||||
if(ispath(buildpath, /obj/item/bodypart)) //This feels like spaghetti code, but i need to initiliaze a limb somehow
|
||||
build_limb(buildpath)
|
||||
else if(ispath(buildpath, /obj/item/organ/genital)) //genitals are uhh... customizable
|
||||
build_genital(buildpath)
|
||||
else
|
||||
//Just build whatever it is
|
||||
new buildpath(loc)
|
||||
else
|
||||
src.visible_message("<span class=\"error\"> Something went very wrong and there isnt enough synthflesh anymore!</span>")
|
||||
busy = FALSE
|
||||
flick("limbgrower_unfill",src)
|
||||
icon_state = "limbgrower_idleoff"
|
||||
updateUsrDialog()
|
||||
/*
|
||||
* The process of beginning to build a limb or organ.
|
||||
* Goes through and sanity checks that we actually have enough reagent to build our item.
|
||||
* Then, remove those reagents from our reagents datum.
|
||||
*
|
||||
* After the reagents are handled, we can proceede with making the limb or organ. (Limbs are handled in a separate proc)
|
||||
*
|
||||
* modified_consumed_reagents_list - the list of reagents we will consume on build, modified by the production coefficient.
|
||||
*/
|
||||
/obj/machinery/limbgrower/proc/build_item(list/modified_consumed_reagents_list)
|
||||
for(var/reagent_id in modified_consumed_reagents_list)
|
||||
if(!reagents.has_reagent(reagent_id, modified_consumed_reagents_list[reagent_id]))
|
||||
audible_message("<span class='notice'>\The [src] buzzes.</span>")
|
||||
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
|
||||
break
|
||||
|
||||
/obj/machinery/limbgrower/proc/build_limb(buildpath)
|
||||
reagents.remove_reagent(reagent_id, modified_consumed_reagents_list[reagent_id])
|
||||
|
||||
var/built_typepath = being_built.build_path
|
||||
// If we have a bodypart, we need to initialize the limb on its own. Otherwise we can build it here.
|
||||
if(ispath(built_typepath, /obj/item/bodypart))
|
||||
build_limb(built_typepath)
|
||||
else if(ispath(built_typepath, /obj/item/organ/genital)) //genitals are uhh... customizable
|
||||
build_genital(built_typepath)
|
||||
else
|
||||
new built_typepath(loc)
|
||||
|
||||
busy = FALSE
|
||||
flick("limbgrower_unfill", src)
|
||||
icon_state = "limbgrower_idleoff"
|
||||
|
||||
/*
|
||||
* The process of putting together a limb.
|
||||
* This is called from after we remove the reagents, so this proc is just initializing the limb type.
|
||||
*
|
||||
* This proc handles skin / mutant color, greyscaling, names and descriptions, and various other limb creation steps.
|
||||
*
|
||||
* built_typepath - the path of the bodypart we're building.
|
||||
*/
|
||||
/obj/machinery/limbgrower/proc/build_limb(built_typepath)
|
||||
//i need to create a body part manually using a set icon (otherwise it doesnt appear)
|
||||
var/obj/item/bodypart/limb
|
||||
var/datum/species/selected = stored_species[selected_category]
|
||||
limb = new buildpath(loc)
|
||||
var/datum/species/selected = GLOB.species_datums[selected_category]
|
||||
limb = new built_typepath(loc)
|
||||
limb.base_bp_icon = selected.icon_limbs || DEFAULT_BODYPART_ICON_ORGANIC
|
||||
limb.species_id = selected.limbs_id
|
||||
limb.color_src = (MUTCOLORS in selected.species_traits ? MUTCOLORS : (selected.use_skintones ? SKINTONE : FALSE))
|
||||
@@ -189,135 +259,103 @@
|
||||
BP.name = "\improper synthetic [lowertext(selected.name)] [limb.name]"
|
||||
BP.desc = "A synthetic [selected_category] limb that will morph on its first use in surgery. This one is for the [parse_zone(limb.body_zone)]."
|
||||
|
||||
/obj/machinery/limbgrower/proc/build_genital(buildpath)
|
||||
/*
|
||||
* Builds genitals, modifies to be the same
|
||||
* as the person's cloning data on the data disk
|
||||
*/
|
||||
/obj/machinery/limbgrower/proc/build_genital(built_typepath)
|
||||
//i needed to create a way to customize gene tools using dna
|
||||
var/list/features = dna_disk?.fields["features"]
|
||||
if(length(features))
|
||||
switch(buildpath)
|
||||
switch(built_typepath)
|
||||
if(/obj/item/organ/genital/penis)
|
||||
var/obj/item/organ/genital/penis/penis = new(loc)
|
||||
if(features["has_cock"])
|
||||
penis.shape = features["cock_shape"]
|
||||
penis.length = features["cock_shape"]
|
||||
penis.diameter_ratio = features["cock_diameter_ratio"]
|
||||
penis.color = sanitize_hexcolor(features["cock_color"], 6)
|
||||
penis.update_icon()
|
||||
penis.color = sanitize_hexcolor(features["cock_color"], 6, TRUE)
|
||||
penis.update()
|
||||
if(/obj/item/organ/genital/testicles)
|
||||
var/obj/item/organ/genital/testicles/balls = new(loc)
|
||||
if(features["has_balls"])
|
||||
balls.color = sanitize_hexcolor(features["balls_color"], 6)
|
||||
balls.color = sanitize_hexcolor(features["balls_color"], 6, TRUE)
|
||||
balls.shape = features["balls_shape"]
|
||||
balls.size = features["balls_size"]
|
||||
balls.fluid_rate = features["balls_cum_rate"]
|
||||
balls.fluid_mult = features["balls_cum_mult"]
|
||||
balls.fluid_efficiency = features["balls_efficiency"]
|
||||
balls.update()
|
||||
if(/obj/item/organ/genital/vagina)
|
||||
var/obj/item/organ/genital/vagina/vegana = new(loc)
|
||||
if(features["has_vagina"])
|
||||
vegana.color = sanitize_hexcolor(features["vag_color"], 6)
|
||||
if(features["has_vag"])
|
||||
vegana.color = sanitize_hexcolor(features["vag_color"], 6, TRUE)
|
||||
vegana.shape = features["vag_shape"]
|
||||
vegana.update()
|
||||
if(/obj/item/organ/genital/breasts)
|
||||
var/obj/item/organ/genital/breasts/boobs = new(loc)
|
||||
if(features["has_breasts"])
|
||||
boobs.color = sanitize_hexcolor(features["breasts_color"], 6)
|
||||
boobs.color = sanitize_hexcolor(features["breasts_color"], 6, TRUE)
|
||||
boobs.size = features["breasts_size"]
|
||||
boobs.shape = features["breasts_shape"]
|
||||
if(!features["breasts_producing"])
|
||||
boobs.genital_flags &= ~(GENITAL_FUID_PRODUCTION|CAN_CLIMAX_WITH|CAN_MASTURBATE_WITH)
|
||||
boobs.update()
|
||||
else
|
||||
new buildpath(loc)
|
||||
new built_typepath(loc)
|
||||
else
|
||||
new buildpath(loc)
|
||||
new built_typepath(loc)
|
||||
|
||||
/obj/machinery/limbgrower/RefreshParts()
|
||||
reagents.maximum_volume = 0
|
||||
for(var/obj/item/reagent_containers/glass/G in component_parts)
|
||||
reagents.maximum_volume += G.volume
|
||||
G.reagents.trans_to(src, G.reagents.total_volume)
|
||||
var/T=1.2
|
||||
for(var/obj/item/stock_parts/manipulator/M in component_parts)
|
||||
T -= M.rating*0.2
|
||||
prod_coeff = min(1,max(0,T)) // Coeff going 1 -> 0,8 -> 0,6 -> 0,4
|
||||
for(var/obj/item/reagent_containers/glass/our_beaker in component_parts)
|
||||
reagents.maximum_volume += our_beaker.volume
|
||||
our_beaker.reagents.trans_to(src, our_beaker.reagents.total_volume)
|
||||
production_coefficient = 1.2
|
||||
for(var/obj/item/stock_parts/manipulator/our_manipulator in component_parts)
|
||||
production_coefficient -= our_manipulator.rating * 0.2
|
||||
production_coefficient = clamp(production_coefficient, 0, 1) // coefficient goes from 1 -> 0.8 -> 0.6 -> 0.4
|
||||
|
||||
/obj/machinery/limbgrower/examine(mob/user)
|
||||
. = ..()
|
||||
if(!panel_open)
|
||||
. += "<span class='notice'>It looks like as if the panel were open you could rotate it with a <b>wrench</b>.</span>"
|
||||
else
|
||||
. += "<span class='notice'>The panel is open.</span>"
|
||||
if(in_range(user, src) || isobserver(user))
|
||||
. += "<span class='notice'>The status display reads: Storing up to <b>[reagents.maximum_volume]u</b> of synthflesh.<br>Synthflesh consumption at <b>[prod_coeff*100]%</b>.<span>"
|
||||
. += "<span class='notice'>The status display reads: Storing up to <b>[reagents.maximum_volume]u</b> of reagents.<br>Reagent consumption rate at <b>[production_coefficient * 100]%</b>.</span>"
|
||||
|
||||
/obj/machinery/limbgrower/proc/main_win(mob/user)
|
||||
var/dat = "<div class='statusDisplay'><h3>[src] Menu:</h3><br>"
|
||||
dat += "<A href='?src=[REF(src)];dna_disk=1'>[dna_disk ? "Remove" : "Insert"] cloning data disk</A>"
|
||||
dat += "<hr>"
|
||||
dat += "<A href='?src=[REF(src)];menu=[LIMBGROWER_CHEMICAL_MENU]'>Chemical Storage</A>"
|
||||
dat += materials_printout()
|
||||
dat += "<table style='width:100%' align='center'><tr>"
|
||||
|
||||
for(var/C in categories)
|
||||
dat += "<td><A href='?src=[REF(src)];category=[C];menu=[LIMBGROWER_CATEGORY_MENU]'>[C]</A></td>"
|
||||
dat += "</tr><tr>"
|
||||
//one category per line
|
||||
|
||||
dat += "</tr></table></div>"
|
||||
return dat
|
||||
|
||||
/obj/machinery/limbgrower/proc/category_win(mob/user,selected_category)
|
||||
var/dat = "<A href='?src=[REF(src)];menu=[LIMBGROWER_MAIN_MENU]'>Return to main menu</A>"
|
||||
dat += "<div class='statusDisplay'><h3>Browsing [selected_category]:</h3><br>"
|
||||
dat += materials_printout()
|
||||
|
||||
for(var/v in stored_research.researched_designs)
|
||||
var/datum/design/D = SSresearch.techweb_design_by_id(v)
|
||||
if(!(selected_category in D.category))
|
||||
continue
|
||||
if(disabled || !can_build(D))
|
||||
dat += "<span class='linkOff'>[D.name]</span>"
|
||||
else
|
||||
dat += "<a href='?src=[REF(src)];make=[D.id];multiplier=1'>[D.name]</a>"
|
||||
dat += "[get_design_cost(D)]<br>"
|
||||
|
||||
dat += "</div>"
|
||||
return dat
|
||||
|
||||
|
||||
/obj/machinery/limbgrower/proc/chemical_win(mob/user)
|
||||
var/dat = "<A href='?src=[REF(src)];menu=[LIMBGROWER_MAIN_MENU]'>Return to main menu</A>"
|
||||
dat += "<div class='statusDisplay'><h3>Browsing Chemical Storage:</h3><br>"
|
||||
dat += materials_printout()
|
||||
|
||||
for(var/datum/reagent/R in reagents.reagent_list)
|
||||
dat += "[R.name]: [R.volume]"
|
||||
dat += "<A href='?src=[REF(src)];disposeI=[R]'>Purge</A><BR>"
|
||||
|
||||
dat += "</div>"
|
||||
return dat
|
||||
|
||||
/obj/machinery/limbgrower/proc/materials_printout()
|
||||
var/dat = "<b>Total amount:></b> [reagents.total_volume] / [reagents.maximum_volume] cm<sup>3</sup><br>"
|
||||
return dat
|
||||
|
||||
/obj/machinery/limbgrower/proc/can_build(datum/design/D)
|
||||
return (reagents.has_reagent(/datum/reagent/medicine/synthflesh, D.reagents_list[/datum/reagent/medicine/synthflesh]*prod_coeff)) //Return whether the machine has enough synthflesh to produce the design
|
||||
|
||||
/obj/machinery/limbgrower/proc/get_design_cost(datum/design/D)
|
||||
var/dat
|
||||
if(D.reagents_list[/datum/reagent/medicine/synthflesh])
|
||||
dat += "[D.reagents_list[/datum/reagent/medicine/synthflesh] * prod_coeff] Synthetic flesh "
|
||||
return dat
|
||||
/*
|
||||
* Checks our reagent list to see if a design can be built.
|
||||
*
|
||||
* limb_design - the design we're checking for buildability.
|
||||
*
|
||||
* returns TRUE if we have enough reagent to build it. Returns FALSE if we do not.
|
||||
*/
|
||||
/obj/machinery/limbgrower/proc/can_build(datum/design/limb_design)
|
||||
for(var/datum/reagent/reagent_id in limb_design.reagents_list)
|
||||
if(!reagents.has_reagent(reagent_id, limb_design.reagents_list[reagent_id] * production_coefficient))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/// Emagging a limbgrower allows you to build synthetic armblades.
|
||||
/obj/machinery/limbgrower/emag_act(mob/user)
|
||||
. = ..()
|
||||
if(obj_flags & EMAGGED)
|
||||
return
|
||||
for(var/id in SSresearch.techweb_designs)
|
||||
var/datum/design/D = SSresearch.techweb_design_by_id(id)
|
||||
if((D.build_type & LIMBGROWER) && ("emagged" in D.category))
|
||||
stored_research.add_design(D)
|
||||
for(var/design_id in SSresearch.techweb_designs)
|
||||
var/datum/design/found_design = SSresearch.techweb_design_by_id(design_id)
|
||||
if((found_design.build_type & LIMBGROWER) && ("emagged" in found_design.category))
|
||||
stored_research.add_design(found_design)
|
||||
to_chat(user, "<span class='warning'>A warning flashes onto the screen, stating that safety overrides have been deactivated!</span>")
|
||||
obj_flags |= EMAGGED
|
||||
return TRUE
|
||||
update_static_data(user)
|
||||
|
||||
/obj/machinery/limbgrower/AltClick(mob/living/user)
|
||||
. = ..()
|
||||
eject_disk(user)
|
||||
|
||||
/obj/machinery/limbgrower/proc/eject_disk(mob/user)
|
||||
if(istype(user) && user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
|
||||
if(busy)
|
||||
to_chat(user, "<span class=\"alert\">\The [src] is busy. Please wait for completion of previous operation.</span>")
|
||||
@@ -326,6 +364,7 @@
|
||||
dna_disk.forceMove(src.loc)
|
||||
user.put_in_active_hand(dna_disk)
|
||||
to_chat(user, "<span class='notice'>You remove \the [dna_disk] from \the [src].</span>")
|
||||
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
|
||||
dna_disk = null
|
||||
else
|
||||
to_chat(user, "<span class='warning'>\The [src] has doesn't have a disk on it!")
|
||||
|
||||
@@ -21,6 +21,7 @@ Buildable meters
|
||||
level = 2
|
||||
var/piping_layer = PIPING_LAYER_DEFAULT
|
||||
var/RPD_type
|
||||
var/disposable = TRUE
|
||||
|
||||
/obj/item/pipe/directional
|
||||
RPD_type = PIPE_UNARY
|
||||
@@ -236,3 +237,22 @@ Buildable meters
|
||||
/obj/item/pipe_meter/proc/setAttachLayer(new_layer = PIPING_LAYER_DEFAULT)
|
||||
piping_layer = new_layer
|
||||
PIPING_LAYER_DOUBLE_SHIFT(src, piping_layer)
|
||||
|
||||
/obj/item/pipe/bluespace
|
||||
pipe_type = /obj/machinery/atmospherics/pipe/bluespace
|
||||
var/bluespace_network_name = "default"
|
||||
icon_state = "bluespace"
|
||||
disposable = FALSE
|
||||
|
||||
/obj/item/pipe/bluespace/attack_self(mob/user)
|
||||
var/new_name = input(user, "Enter identifier for bluespace pipe network", "bluespace pipe", bluespace_network_name) as text|null
|
||||
if(!isnull(new_name))
|
||||
bluespace_network_name = new_name
|
||||
|
||||
/obj/item/pipe/bluespace/make_from_existing(obj/machinery/atmospherics/pipe/bluespace/make_from)
|
||||
bluespace_network_name = make_from.bluespace_network_name
|
||||
return ..()
|
||||
|
||||
/obj/item/pipe/bluespace/build_pipe(obj/machinery/atmospherics/pipe/bluespace/A)
|
||||
A.bluespace_network_name = bluespace_network_name
|
||||
return ..()
|
||||
|
||||
@@ -375,12 +375,14 @@ GLOBAL_LIST_INIT(fluid_duct_recipes, list(
|
||||
. = TRUE
|
||||
|
||||
if((mode & DESTROY_MODE) && istype(A, /obj/item/pipe) || istype(A, /obj/structure/disposalconstruct) || istype(A, /obj/structure/c_transit_tube) || istype(A, /obj/structure/c_transit_tube_pod) || istype(A, /obj/item/pipe_meter))
|
||||
to_chat(user, "<span class='notice'>You start destroying a pipe...</span>")
|
||||
playsound(get_turf(src), 'sound/machines/click.ogg', 50, 1)
|
||||
if(do_after(user, destroy_speed, target = A))
|
||||
activate()
|
||||
qdel(A)
|
||||
return
|
||||
var/obj/item/pipe/P = A
|
||||
if(!istype(P) || P.disposable)
|
||||
to_chat(user, "<span class='notice'>You start destroying a pipe...</span>")
|
||||
playsound(get_turf(src), 'sound/machines/click.ogg', 50, 1)
|
||||
if(do_after(user, destroy_speed, target = A))
|
||||
activate()
|
||||
qdel(A)
|
||||
return
|
||||
|
||||
if((mode & PAINT_MODE))
|
||||
if(istype(A, /obj/machinery/atmospherics/pipe) && !istype(A, /obj/machinery/atmospherics/pipe/layer_manifold))
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
to_chat(user, "<span class='notice'>You need to get closer!</span>")
|
||||
return
|
||||
if(use_paint(user) && isturf(F))
|
||||
F.AddElement(/datum/element/decal, 'icons/turf/decals.dmi', stored_decal_total, turn(stored_dir, -dir2angle(F.dir)), CLEAN_STRONG, color, null, null, alpha)
|
||||
F.AddElement(/datum/element/decal, 'icons/turf/decals.dmi', stored_decal_total, stored_dir, CLEAN_STRONG, color, null, null, alpha)
|
||||
|
||||
/obj/item/airlock_painter/decal/attack_self(mob/user)
|
||||
if((ink) && (ink.charges >= 1))
|
||||
@@ -180,6 +180,11 @@
|
||||
stored_decal_total = "[stored_decal][yellow_fix][stored_color]"
|
||||
return
|
||||
|
||||
/obj/item/airlock_painter/decal/ui_assets(mob/user)
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/spritesheet/decals)
|
||||
)
|
||||
|
||||
/obj/item/airlock_painter/decal/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
@@ -189,6 +194,7 @@
|
||||
/obj/item/airlock_painter/decal/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["decal_direction"] = stored_dir
|
||||
data["decal_dir_text"] = dir2text(stored_dir)
|
||||
data["decal_color"] = stored_color
|
||||
data["decal_style"] = stored_decal
|
||||
data["decal_list"] = list()
|
||||
|
||||
@@ -75,18 +75,3 @@
|
||||
desc = "A chromosome that reduces action based mutation cooldowns by by 50%."
|
||||
icon_state = "energy"
|
||||
energy_coeff = 0.5
|
||||
|
||||
/obj/item/chromosome/reinforcer
|
||||
name = "reinforcement chromosome"
|
||||
desc = "A chromosome that renders mutations immune to mutadone."
|
||||
icon_state = "reinforcer"
|
||||
weight = 3
|
||||
|
||||
/obj/item/chromosome/reinforcer/can_apply(datum/mutation/human/HM)
|
||||
if(!HM || !(HM.can_chromosome == CHROMOSOME_NONE))
|
||||
return FALSE
|
||||
return !HM.mutadone_proof
|
||||
|
||||
/obj/item/chromosome/reinforcer/apply(datum/mutation/human/HM)
|
||||
HM.mutadone_proof = TRUE
|
||||
..()
|
||||
|
||||
@@ -210,8 +210,7 @@
|
||||
target.apply_effect(EFFECT_STUTTER, stunforce)
|
||||
SEND_SIGNAL(target, COMSIG_LIVING_MINOR_SHOCK)
|
||||
if(user)
|
||||
target.lastattacker = user.real_name
|
||||
target.lastattackerckey = user.ckey
|
||||
target.set_last_attacker(user)
|
||||
target.visible_message("<span class='danger'>[user] has shocked [target] with [src]!</span>", \
|
||||
"<span class='userdanger'>[user] has shocked you with [src]!</span>")
|
||||
log_combat(user, target, "stunned with an electrostaff")
|
||||
@@ -237,8 +236,7 @@
|
||||
target.adjustFireLoss(lethal_force) //good against ointment spam
|
||||
SEND_SIGNAL(target, COMSIG_LIVING_MINOR_SHOCK)
|
||||
if(user)
|
||||
target.lastattacker = user.real_name
|
||||
target.lastattackerckey = user.ckey
|
||||
target.set_last_attacker(user)
|
||||
target.visible_message("<span class='danger'>[user] has seared [target] with [src]!</span>", \
|
||||
"<span class='userdanger'>[user] has seared you with [src]!</span>")
|
||||
log_combat(user, target, "burned with an electrostaff")
|
||||
|
||||
@@ -312,7 +312,7 @@
|
||||
trap_damage = 0
|
||||
item_flags = DROPDEL
|
||||
flags_1 = NONE
|
||||
breakouttime = 50
|
||||
breakouttime = 25
|
||||
|
||||
/obj/item/restraints/legcuffs/beartrap/energy/New()
|
||||
..()
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/datum/deathrattle_group
|
||||
var/name
|
||||
var/list/datum/weakref/implant_refs = list()
|
||||
|
||||
/datum/deathrattle_group/New()
|
||||
// Give the group a unique name for debugging, and possible future
|
||||
// use for making custom linked groups.
|
||||
name = "[rand(100,999)] [pick(GLOB.phonetic_alphabet)]"
|
||||
|
||||
/datum/deathrattle_group/proc/rattle(obj/item/implant/deathrattle/origin, mob/living/owner)
|
||||
var/name = owner.mind ? owner.mind.name : owner.real_name
|
||||
var/area = get_area_name(get_turf(owner))
|
||||
|
||||
for(var/r in implant_refs)
|
||||
var/datum/weakref/R = r
|
||||
|
||||
var/obj/item/implant/deathrattle/implant = R.resolve()
|
||||
if(!implant || implant == origin)
|
||||
continue
|
||||
|
||||
// Not all the implants may be actually implanted in people.
|
||||
if(!implant.imp_in)
|
||||
continue
|
||||
|
||||
// Deliberately the same message framing as nanite message + ghost deathrattle
|
||||
var/mob/living/recipient = implant.imp_in
|
||||
to_chat(recipient, "<i>You hear a strange, robotic voice in your head...</i> \"<span class='robot'><b>[name]</b> has died at <b>[area]</b>.</span>\"")
|
||||
SEND_SOUND(recipient, pick(
|
||||
'sound/items/knell1.ogg',
|
||||
'sound/items/knell2.ogg',
|
||||
'sound/items/knell3.ogg',
|
||||
'sound/items/knell4.ogg',
|
||||
))
|
||||
|
||||
/datum/deathrattle_group/proc/register(obj/item/implant/deathrattle/implant)
|
||||
implant.group = src
|
||||
implant_refs += WEAKREF(implant)
|
||||
|
||||
|
||||
/obj/item/implant/deathrattle
|
||||
name = "deathrattle implant"
|
||||
desc = "Hope no one else dies, prepare for when they do."
|
||||
|
||||
activated = FALSE
|
||||
|
||||
var/datum/deathrattle_group/group = null
|
||||
|
||||
/obj/item/implant/deathrattle/Destroy()
|
||||
group = null
|
||||
return ..()
|
||||
|
||||
/obj/item/implant/deathrattle/can_be_implanted_in(mob/living/target)
|
||||
// Can be implanted in anything that's a mob. Syndicate cyborgs, talking fish, humans...
|
||||
return TRUE
|
||||
|
||||
/obj/item/implant/deathrattle/proc/on_predeath(datum/source, gibbed)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(group)
|
||||
group.rattle(origin = src, owner = source)
|
||||
|
||||
/obj/item/implant/deathrattle/implant(mob/living/target, mob/user, silent = FALSE, force = FALSE)
|
||||
. = ..()
|
||||
if(.)
|
||||
RegisterSignal(target, COMSIG_LIVING_PREDEATH, .proc/on_predeath)
|
||||
|
||||
if(!group)
|
||||
to_chat(target, "<i>You hear a strange, robotic voice in your head...</i> \"<span class='robot'>Warning: No other linked implants detected.</span>\"")
|
||||
|
||||
|
||||
/obj/item/implantcase/deathrattle
|
||||
name = "implant case - 'Deathrattle'"
|
||||
desc = "A glass case containing a deathrattle implant."
|
||||
imp_type = /obj/item/implant/deathrattle
|
||||
@@ -135,8 +135,8 @@
|
||||
/obj/item/organ/cyberimp/arm/toolset,
|
||||
/obj/item/organ/cyberimp/arm/surgery,
|
||||
/obj/item/organ/cyberimp/chest/thrusters,
|
||||
/obj/item/organ/lungs/cybernetic,
|
||||
/obj/item/organ/liver/cybernetic) //cyberimplants range from a nice bonus to fucking broken bullshit so no subtypesof
|
||||
/obj/item/organ/lungs/cybernetic/tier3,
|
||||
/obj/item/organ/liver/cybernetic/tier3) //cyberimplants range from a nice bonus to fucking broken bullshit so no subtypesof
|
||||
for(var/V in templist)
|
||||
var/atom/A = V
|
||||
augment_list[initial(A.name)] = A
|
||||
|
||||
@@ -272,7 +272,7 @@ GLOBAL_LIST_INIT(plastitaniumglass_recipes, list(
|
||||
. = ..()
|
||||
. += GLOB.plastitaniumglass_recipes
|
||||
|
||||
/obj/item/stack/sheet/titaniumglass/on_solar_construction(obj/machinery/power/solar/S)
|
||||
/obj/item/stack/sheet/plastitaniumglass/on_solar_construction(obj/machinery/power/solar/S)
|
||||
S.max_integrity *= 2
|
||||
S.efficiency *= 2
|
||||
|
||||
|
||||
@@ -351,6 +351,75 @@
|
||||
if(!contents.len)
|
||||
. += "[icon_state]_empty"
|
||||
|
||||
//Derringer "Cigarettes"//
|
||||
/obj/item/storage/fancy/cigarettes/derringer
|
||||
name = "\improper Robust packet"
|
||||
desc = "Smoked by the robust."
|
||||
icon_state = "robust"
|
||||
spawn_type = /obj/item/gun/ballistic/derringer/traitor
|
||||
|
||||
/obj/item/storage/fancy/cigarettes/derringer/ComponentInitialize()
|
||||
. = ..()
|
||||
var/datum/component/storage/STR = GetComponent(/datum/component/storage)
|
||||
STR.max_items = 6
|
||||
STR.can_hold = typecacheof(list(/obj/item/clothing/mask/cigarette, /obj/item/lighter, /obj/item/gun/ballistic/derringer, /obj/item/ammo_casing/c38, /obj/item/ammo_casing/a357, /obj/item/ammo_casing/g4570))
|
||||
|
||||
/obj/item/storage/fancy/cigarettes/derringer/AltClick(mob/living/carbon/user)
|
||||
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
|
||||
return
|
||||
var/obj/item/W = (locate(/obj/item/ammo_casing/a357) in contents) || (locate(/obj/item/clothing/mask/cigarette) in contents) || locate(/obj/item/ammo_casing/g4570) //Easy access smokes and bullets
|
||||
if(W && contents.len > 0)
|
||||
SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, user)
|
||||
user.put_in_hands(W)
|
||||
contents -= W
|
||||
to_chat(user, "<span class='notice'>You take \a [W] out of the pack.</span>")
|
||||
else
|
||||
to_chat(user, "<span class='notice'>There are no items left in the pack.</span>")
|
||||
|
||||
/obj/item/storage/fancy/cigarettes/derringer/PopulateContents()
|
||||
new spawn_type(src)
|
||||
new /obj/item/ammo_casing/a357(src)
|
||||
new /obj/item/ammo_casing/a357(src)
|
||||
new /obj/item/ammo_casing/a357(src)
|
||||
new /obj/item/ammo_casing/a357(src)
|
||||
new /obj/item/clothing/mask/cigarette/syndicate(src)
|
||||
|
||||
//For traitors with luck/class
|
||||
/obj/item/storage/fancy/cigarettes/derringer/gold
|
||||
name = "\improper Robust Gold packet"
|
||||
desc = "Smoked by the truly robust."
|
||||
icon_state = "robustg"
|
||||
spawn_type = /obj/item/gun/ballistic/derringer/gold
|
||||
|
||||
//For operatives, bound in a ka-tet.
|
||||
/obj/item/storage/fancy/cigarettes/derringer/midworld
|
||||
name = "\improper Midworld's Lime Bend"
|
||||
desc = "The wheel of Ka turns, Gunslinger."
|
||||
icon_state = "slime"
|
||||
spawn_type = /obj/item/gun/ballistic/derringer/nukeop
|
||||
|
||||
/obj/item/storage/fancy/cigarettes/derringer/midworld/PopulateContents()
|
||||
new spawn_type(src)
|
||||
new /obj/item/ammo_casing/g4570(src)
|
||||
new /obj/item/ammo_casing/g4570(src)
|
||||
new /obj/item/ammo_casing/g4570(src)
|
||||
new /obj/item/ammo_casing/g4570(src)
|
||||
new /obj/item/clothing/mask/cigarette/xeno(src)
|
||||
|
||||
//For Cargomen, looking for a good deal on arms, with no quarrels as to where they're from.
|
||||
/obj/item/storage/fancy/cigarettes/derringer/smuggled
|
||||
name = "\improper Shady Jim's Super Slims packet"
|
||||
desc = "If you get caught with this, we don't know you, capiche?"
|
||||
icon_state = "shadyjim"
|
||||
spawn_type = /obj/item/gun/ballistic/derringer
|
||||
|
||||
/obj/item/storage/fancy/cigarettes/derringer/smuggled/PopulateContents()
|
||||
new spawn_type(src)
|
||||
new /obj/item/ammo_casing/c38/lethal(src)
|
||||
new /obj/item/ammo_casing/c38/lethal(src)
|
||||
new /obj/item/ammo_casing/c38/lethal(src)
|
||||
new /obj/item/ammo_casing/c38/lethal(src)
|
||||
new /obj/item/clothing/mask/cigarette/shadyjims (src)
|
||||
/////////////
|
||||
//CIGAR BOX//
|
||||
/////////////
|
||||
|
||||
@@ -546,3 +546,21 @@
|
||||
. = ..()
|
||||
new /obj/item/cardpack/syndicate(src)
|
||||
new /obj/item/cardpack/syndicate(src)
|
||||
|
||||
/obj/item/storage/box/syndie_kit/imp_deathrattle
|
||||
name = "deathrattle implant box"
|
||||
desc = "Contains eight linked deathrattle implants."
|
||||
|
||||
/obj/item/storage/box/syndie_kit/imp_deathrattle/PopulateContents()
|
||||
new /obj/item/implanter(src)
|
||||
|
||||
var/datum/deathrattle_group/group = new
|
||||
|
||||
var/implants = list()
|
||||
for(var/j in 1 to 8)
|
||||
var/obj/item/implantcase/deathrattle/case = new (src)
|
||||
implants += case.imp
|
||||
|
||||
for(var/i in implants)
|
||||
group.register(i)
|
||||
desc += " The implants are registered to the \"[group.name]\" group."
|
||||
|
||||
@@ -46,7 +46,8 @@
|
||||
/obj/item/instrument/harmonica,
|
||||
/obj/item/mining_voucher,
|
||||
/obj/item/suit_voucher,
|
||||
/obj/item/reagent_containers/pill))
|
||||
/obj/item/reagent_containers/pill,
|
||||
/obj/item/gun/ballistic/derringer))
|
||||
|
||||
/obj/item/storage/wallet/Exited(atom/movable/AM)
|
||||
. = ..()
|
||||
|
||||
@@ -16,17 +16,21 @@
|
||||
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80)
|
||||
attack_speed = CLICK_CD_MELEE
|
||||
|
||||
var/stamforce = 35
|
||||
var/stamina_loss_amount = 35
|
||||
var/turned_on = FALSE
|
||||
var/knockdown = TRUE
|
||||
var/obj/item/stock_parts/cell/cell
|
||||
var/hitcost = 750
|
||||
var/throw_hit_chance = 35
|
||||
var/preload_cell_type //if not empty the baton starts with this type of cell
|
||||
var/cooldown_duration = 5 SECONDS //How long our baton rightclick goes on cooldown for after applying a knockdown
|
||||
var/status_duration = 5 SECONDS //how long our status effects last for otherwise
|
||||
COOLDOWN_DECLARE(shove_cooldown)
|
||||
|
||||
/obj/item/melee/baton/examine(mob/user)
|
||||
. = ..()
|
||||
. += "<span class='notice'>Right click attack while in combat mode to disarm instead of stun.</span>"
|
||||
. += "<span class='notice'>Right click attack while in combat mode to knockdown, but only once per [cooldown_duration / 10] seconds.</span>"
|
||||
. += "<span class='notice'>This knockdown will also put them off balance for [status_duration / 20] seconds, allowing you to shove a weapon out of their hand with a right click in Disarm intent.</span>"
|
||||
|
||||
/obj/item/melee/baton/get_cell()
|
||||
. = cell
|
||||
@@ -53,8 +57,8 @@
|
||||
/obj/item/melee/baton/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
|
||||
..()
|
||||
//Only mob/living types have stun handling
|
||||
if(turned_on && prob(throw_hit_chance) && iscarbon(hit_atom))
|
||||
baton_stun(hit_atom)
|
||||
if(turned_on && prob(throw_hit_chance) && iscarbon(hit_atom) && thrownby)
|
||||
baton_stun(hit_atom, thrownby, shoving = TRUE)
|
||||
|
||||
/obj/item/melee/baton/loaded //this one starts with a cell pre-installed.
|
||||
preload_cell_type = /obj/item/stock_parts/cell/high/plus
|
||||
@@ -74,6 +78,16 @@
|
||||
//we're below minimum, turn off
|
||||
switch_status(FALSE)
|
||||
|
||||
///Check for our cell to determine how much penetration our weapon does.
|
||||
/obj/item/melee/baton/proc/get_cell_zap_pen()
|
||||
var/obj/item/stock_parts/cell/copper_top = get_cell()
|
||||
if(copper_top)
|
||||
var/chargepower = copper_top.maxcharge
|
||||
var/zap_penetration = (chargepower/1000) //This is our effective penetration. Every 1000 max charge, we get 1 pen power. A high capacity cell is equal to 10 armor pen, as an example.
|
||||
return zap_penetration
|
||||
else
|
||||
return 0
|
||||
|
||||
/obj/item/melee/baton/proc/switch_status(new_status = FALSE, silent = FALSE)
|
||||
if(turned_on != new_status)
|
||||
turned_on = new_status
|
||||
@@ -101,6 +115,7 @@
|
||||
var/obj/item/stock_parts/cell/copper_top = get_cell()
|
||||
if(copper_top)
|
||||
. += "<span class='notice'>\The [src] is [round(copper_top.percent())]% charged.</span>"
|
||||
. += "<span class='notice'>\The [src] currently can penetrate [round(copper_top.maxcharge/1000)]% of enemy armor thanks to it's loaded cell.</span>"
|
||||
else
|
||||
. += "<span class='warning'>\The [src] does not have a power source installed.</span>"
|
||||
|
||||
@@ -150,10 +165,10 @@
|
||||
/obj/item/melee/baton/alt_pre_attack(atom/A, mob/living/user, params)
|
||||
if(!user.CheckActionCooldown(CLICK_CD_MELEE))
|
||||
return
|
||||
. = common_baton_melee(A, user, TRUE) //return true (attackchain interrupt) if this also returns true. no harm-disarming.
|
||||
. = common_baton_melee(A, user, TRUE) //return true (attackchain interrupt) if this also returns true. no harm-shoving.
|
||||
|
||||
//return TRUE to interrupt attack chain.
|
||||
/obj/item/melee/baton/proc/common_baton_melee(mob/M, mob/living/user, disarming = FALSE)
|
||||
/obj/item/melee/baton/proc/common_baton_melee(mob/M, mob/living/user, shoving = FALSE)
|
||||
if(iscyborg(M) || !isliving(M)) //can't baton cyborgs
|
||||
return FALSE
|
||||
if(turned_on && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
|
||||
@@ -167,21 +182,26 @@
|
||||
if(check_martial_counter(L, user))
|
||||
return TRUE
|
||||
if(turned_on)
|
||||
if(baton_stun(M, user, disarming))
|
||||
if(baton_stun(M, user, shoving))
|
||||
user.do_attack_animation(M)
|
||||
else if(user.a_intent != INTENT_HARM) //they'll try to bash in the last proc.
|
||||
M.visible_message("<span class='warning'>[user] has prodded [M] with [src]. Luckily it was off.</span>", \
|
||||
"<span class='warning'>[user] has prodded you with [src]. Luckily it was off</span>")
|
||||
return disarming || (user.a_intent != INTENT_HARM)
|
||||
return shoving || (user.a_intent != INTENT_HARM)
|
||||
|
||||
/obj/item/melee/baton/proc/baton_stun(mob/living/L, mob/living/user, disarming = FALSE)
|
||||
/obj/item/melee/baton/proc/baton_stun(mob/living/L, mob/living/user, shoving = FALSE)
|
||||
var/list/return_list = list()
|
||||
if(L.mob_run_block(src, 0, "[user]'s [name]", ATTACK_TYPE_MELEE, 0, user, null, return_list) & BLOCK_SUCCESS) //No message; check_shields() handles that
|
||||
playsound(L, 'sound/weapons/genhit.ogg', 50, 1)
|
||||
return FALSE
|
||||
var/stunpwr = stamforce
|
||||
stunpwr = block_calculate_resultant_damage(stunpwr, return_list)
|
||||
var/final_stamina_loss_amount = stamina_loss_amount //Our stunning power for the baton
|
||||
var/shoved = FALSE //Did we succeed on knocking our target over?
|
||||
var/zap_penetration = get_cell_zap_pen() //Find out what kind of cell we have, and calculating the resultant armor pen we get from it
|
||||
var/zap_block = L.run_armor_check(BODY_ZONE_CHEST, "melee", null, null, zap_penetration) //armor check, including calculation for armor penetration, for our attack
|
||||
final_stamina_loss_amount = block_calculate_resultant_damage(final_stamina_loss_amount, return_list)
|
||||
|
||||
var/obj/item/stock_parts/cell/our_cell = get_cell()
|
||||
|
||||
if(!our_cell)
|
||||
switch_status(FALSE)
|
||||
return FALSE
|
||||
@@ -194,26 +214,31 @@
|
||||
L.visible_message("<span class='warning'>[user] has prodded [L] with [src]. Luckily it was out of charge.</span>", \
|
||||
"<span class='warning'>[user] has prodded you with [src]. Luckily it was out of charge.</span>")
|
||||
return FALSE
|
||||
stunpwr *= round(stuncharge/hitcost, 0.1)
|
||||
final_stamina_loss_amount *= round(stuncharge/hitcost, 0.1)
|
||||
|
||||
if(user && !user.UseStaminaBuffer(getweight(user, STAM_COST_BATON_MOB_MULT), warn = TRUE))
|
||||
return FALSE
|
||||
|
||||
if(!disarming)
|
||||
if(knockdown)
|
||||
L.DefaultCombatKnockdown(50, override_stamdmg = 0) //knockdown
|
||||
L.adjustStaminaLoss(stunpwr)
|
||||
else
|
||||
L.drop_all_held_items() //no knockdown/stamina damage, instead disarm.
|
||||
if(shoving && COOLDOWN_FINISHED(src, shove_cooldown) && !HAS_TRAIT(L, TRAIT_IWASBATONED)) //Rightclicking applies a knockdown, but only once every couple of seconds, based on the cooldown_duration var. If they were recently knocked down, they can't be knocked down again by a baton.
|
||||
L.DefaultCombatKnockdown(50, override_stamdmg = 0)
|
||||
L.apply_status_effect(STATUS_EFFECT_TASED_WEAK, status_duration) //Even if they shove themselves up, they're still slowed.
|
||||
L.apply_status_effect(STATUS_EFFECT_OFF_BALANCE, status_duration) //They're very likely to drop items if shoved briefly after a knockdown.
|
||||
shoved = TRUE
|
||||
COOLDOWN_START(src, shove_cooldown, cooldown_duration)
|
||||
ADD_TRAIT(L, TRAIT_IWASBATONED, STATUS_EFFECT_TRAIT) //Prevents swapping to a new baton to avoid the cooldown by just acquiring more batons
|
||||
addtimer(TRAIT_CALLBACK_REMOVE(L, TRAIT_IWASBATONED, STATUS_EFFECT_TRAIT), cooldown_duration)
|
||||
playsound(loc, 'sound/weapons/zapbang.ogg', 50, 1, -1)
|
||||
else //If we cannot/don't knock down the target, we apply a stagger, which keeps them from just running off
|
||||
L.apply_status_effect(STATUS_EFFECT_STAGGERED, status_duration)
|
||||
|
||||
L.apply_effect(EFFECT_STUTTER, stamforce)
|
||||
L.apply_damage (final_stamina_loss_amount, STAMINA, BODY_ZONE_CHEST, zap_block)
|
||||
L.apply_effect(EFFECT_STUTTER, stamina_loss_amount)
|
||||
SEND_SIGNAL(L, COMSIG_LIVING_MINOR_SHOCK)
|
||||
if(user)
|
||||
L.lastattacker = user.real_name
|
||||
L.lastattackerckey = user.ckey
|
||||
L.visible_message("<span class='danger'>[user] has [disarming? "disarmed" : "stunned"] [L] with [src]!</span>", \
|
||||
"<span class='userdanger'>[user] has [disarming? "disarmed" : "stunned"] you with [src]!</span>")
|
||||
log_combat(user, L, disarming? "disarmed" : "stunned")
|
||||
L.set_last_attacker(user)
|
||||
L.visible_message("<span class='danger'>[user] has [shoved ? "brutally stunned" : "stunned"] [L] with [src]!</span>", \
|
||||
"<span class='userdanger'>[user] has [shoved ? "brutally stunnned" : "stunned"] you with [src]!</span>")
|
||||
log_combat(user, L, shoved ? "stunned and attempted knockdown" : "stunned")
|
||||
|
||||
playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1)
|
||||
|
||||
@@ -228,7 +253,7 @@
|
||||
user.visible_message("<span class='danger'>[user] accidentally hits [user.p_them()]self with [src]!</span>", \
|
||||
"<span class='userdanger'>You accidentally hit yourself with [src]!</span>")
|
||||
SEND_SIGNAL(user, COMSIG_LIVING_MINOR_SHOCK)
|
||||
user.DefaultCombatKnockdown(stamforce*6)
|
||||
user.DefaultCombatKnockdown(stamina_loss_amount*6)
|
||||
playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1)
|
||||
deductcharge(hitcost)
|
||||
|
||||
@@ -306,18 +331,20 @@
|
||||
w_class = WEIGHT_CLASS_BULKY
|
||||
force = 3
|
||||
throwforce = 5
|
||||
stamforce = 25
|
||||
stamina_loss_amount = 25
|
||||
hitcost = 1000
|
||||
throw_hit_chance = 10
|
||||
slot_flags = ITEM_SLOT_BACK
|
||||
cooldown_duration = 7 SECONDS //It's a little on the weak side
|
||||
status_duration = 3 //Slows someone for a tiny bit
|
||||
var/obj/item/assembly/igniter/sparkler
|
||||
|
||||
/obj/item/melee/baton/cattleprod/Initialize()
|
||||
. = ..()
|
||||
sparkler = new (src)
|
||||
sparkler.activate_cooldown = 5
|
||||
sparkler.activate_cooldown = 7 //Helps visualize the knockdown
|
||||
|
||||
/obj/item/melee/baton/cattleprod/baton_stun()
|
||||
/obj/item/melee/baton/cattleprod/baton_stun(mob/living/L, mob/living/carbon/user, shoving = FALSE)
|
||||
sparkler?.activate()
|
||||
. = ..()
|
||||
|
||||
@@ -344,8 +371,8 @@
|
||||
/obj/item/melee/baton/boomerang/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
|
||||
if(turned_on)
|
||||
var/caught = hit_atom.hitby(src, FALSE, FALSE, throwingdatum=throwingdatum)
|
||||
if(ishuman(hit_atom) && !caught && prob(throw_hit_chance))//if they are a carbon and they didn't catch it
|
||||
baton_stun(hit_atom)
|
||||
if(ishuman(hit_atom) && !caught && prob(throw_hit_chance) && thrownby)//if they are a carbon and they didn't catch it
|
||||
baton_stun(hit_atom, thrownby, shoving = TRUE)
|
||||
if(thrownby && !caught)
|
||||
sleep(1)
|
||||
if(!QDELETED(src))
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
item_state = "teleprod"
|
||||
slot_flags = null
|
||||
|
||||
/obj/item/melee/baton/cattleprod/teleprod/baton_stun(mob/living/L, mob/living/carbon/user)//handles making things teleport when hit
|
||||
/obj/item/melee/baton/cattleprod/teleprod/baton_stun(mob/living/L, mob/living/carbon/user, shoving = FALSE)//handles making things teleport when hit
|
||||
. = ..()
|
||||
if(!. || L.anchored)
|
||||
return
|
||||
@@ -16,7 +16,7 @@
|
||||
user.visible_message("<span class='danger'>[user] accidentally hits [user.p_them()]self with [src]!</span>", \
|
||||
"<span class='userdanger'>You accidentally hit yourself with [src]!</span>")
|
||||
SEND_SIGNAL(user, COMSIG_LIVING_MINOR_SHOCK)
|
||||
user.DefaultCombatKnockdown(stamforce * 6)
|
||||
user.DefaultCombatKnockdown(stamina_loss_amount * 6)
|
||||
playsound(loc, 'sound/weapons/egloves.ogg', 50, 1, -1)
|
||||
if(do_teleport(user, get_turf(user), 50, channel = TELEPORT_CHANNEL_BLUESPACE))
|
||||
deductcharge(hitcost)
|
||||
|
||||
@@ -19,3 +19,50 @@
|
||||
desc = "A sturdier card-locked storage unit used for bulky shipments."
|
||||
max_integrity = 500 // Same as crates.
|
||||
melee_min_damage = 25 // Idem.
|
||||
|
||||
/obj/structure/closet/secure_closet/goodies/owned
|
||||
name = "private locker"
|
||||
desc = "A locker designed to only open for who purchased its contents."
|
||||
///Account of the person buying the crate if private purchasing.
|
||||
var/datum/bank_account/buyer_account
|
||||
///Department of the person buying the crate if buying via the NIRN app.
|
||||
var/datum/bank_account/department/department_account
|
||||
///Is the secure crate opened or closed?
|
||||
var/privacy_lock = TRUE
|
||||
///Is the crate being bought by a person, or a budget card?
|
||||
var/department_purchase = FALSE
|
||||
|
||||
/obj/structure/closet/secure_closet/goodies/owned/examine(mob/user)
|
||||
. = ..()
|
||||
. += "<span class='notice'>It's locked with a privacy lock, and can only be unlocked by the buyer's ID.</span>"
|
||||
|
||||
/obj/structure/closet/secure_closet/goodies/owned/Initialize(mapload, datum/bank_account/_buyer_account)
|
||||
. = ..()
|
||||
buyer_account = _buyer_account
|
||||
if(istype(buyer_account, /datum/bank_account/department))
|
||||
department_purchase = TRUE
|
||||
department_account = buyer_account
|
||||
|
||||
/obj/structure/closet/secure_closet/goodies/owned/togglelock(mob/living/user, silent)
|
||||
if(privacy_lock)
|
||||
if(!broken)
|
||||
var/obj/item/card/id/id_card = user.get_idcard(TRUE)
|
||||
if(id_card)
|
||||
if(id_card.registered_account)
|
||||
if(id_card.registered_account == buyer_account || (department_purchase && (id_card.registered_account?.account_job?.paycheck_department) == (department_account.department_id)))
|
||||
if(iscarbon(user))
|
||||
add_fingerprint(user)
|
||||
locked = !locked
|
||||
user.visible_message("<span class='notice'>[user] unlocks [src]'s privacy lock.</span>",
|
||||
"<span class='notice'>You unlock [src]'s privacy lock.</span>")
|
||||
privacy_lock = FALSE
|
||||
update_icon()
|
||||
else if(!silent)
|
||||
to_chat(user, "<span class='notice'>Bank account does not match with buyer!</span>")
|
||||
else if(!silent)
|
||||
to_chat(user, "<span class='notice'>No linked bank account detected!</span>")
|
||||
else if(!silent)
|
||||
to_chat(user, "<span class='notice'>No ID detected!</span>")
|
||||
else if(!silent)
|
||||
to_chat(user, "<span class='warning'>[src] is broken!</span>")
|
||||
else ..()
|
||||
|
||||
@@ -590,7 +590,7 @@
|
||||
|
||||
/obj/effect/mob_spawn/human/pirate
|
||||
name = "space pirate sleeper"
|
||||
desc = "A cryo sleeper smelling faintly of rum."
|
||||
desc = "A cryo sleeper smelling faintly of rum. The sleeper looks unstable. <i>Perhaps the pirate within can be killed with the right tools...</i>"
|
||||
job_description = "Space Pirate"
|
||||
random = TRUE
|
||||
icon = 'icons/obj/machines/sleeper.dmi'
|
||||
@@ -608,6 +608,54 @@
|
||||
assignedrole = "Space Pirate"
|
||||
var/rank = "Mate"
|
||||
|
||||
/obj/effect/mob_spawn/human/pirate/on_attack_hand(mob/living/user, act_intent = user.a_intent, unarmed_attack_flags)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
if(user.mind.has_antag_datum(/datum/antagonist/pirate))
|
||||
to_chat(user, "<span class='notice'>Your shipmate sails within their dreams for now. Perhaps they may wake up eventually.</span>")
|
||||
else
|
||||
to_chat(user, "<span class='notice'>If you want to kill the pirate off, something to pry open the sleeper might be the best way to do it.</span>")
|
||||
|
||||
|
||||
/obj/effect/mob_spawn/human/pirate/attackby(obj/item/W, mob/user, params)
|
||||
if(W.tool_behaviour == TOOL_CROWBAR && user.a_intent != INTENT_HARM)
|
||||
if(user.mind.has_antag_datum(/datum/antagonist/pirate))
|
||||
to_chat(user,"<span class='warning'>Why would you want to do that to your shipmate? That'd kill them.</span>")
|
||||
return
|
||||
user.visible_message("<span class='warning'>[user] start to pry open [src]...</span>",
|
||||
"<span class='notice'>You start to pry open [src]...</span>",
|
||||
"<span class='italics'>You hear prying...</span>")
|
||||
W.play_tool_sound(src)
|
||||
if(do_after(user, 100*W.toolspeed, target = src))
|
||||
user.visible_message("<span class='warning'>[user] pries open [src], disrupting the sleep of the pirate within and killing them.</span>",
|
||||
"<span class='notice'>You pry open [src], disrupting the sleep of the pirate within and killing them.</span>",
|
||||
"<span class='italics'>You hear prying, followed by the death rattling of bones.</span>")
|
||||
log_game("[key_name(user)] has successfully pried open [src] and disabled a space pirate spawner.")
|
||||
W.play_tool_sound(src)
|
||||
playsound(src.loc, 'modular_citadel/sound/voice/scream_skeleton.ogg', 50, 1, 4, 1.2)
|
||||
if(rank == "Captain")
|
||||
new /obj/effect/mob_spawn/human/pirate/corpse/captain(get_turf(src))
|
||||
else
|
||||
new /obj/effect/mob_spawn/human/pirate/corpse(get_turf(src))
|
||||
qdel(src)
|
||||
else
|
||||
..()
|
||||
|
||||
/obj/effect/mob_spawn/human/pirate/corpse //occurs when someone pries a pirate out of their sleeper.
|
||||
mob_name = "Dead Space Pirate"
|
||||
death = TRUE
|
||||
instant = TRUE
|
||||
random = FALSE
|
||||
|
||||
/obj/effect/mob_spawn/human/pirate/corpse/Destroy()
|
||||
return ..()
|
||||
|
||||
/obj/effect/mob_spawn/human/pirate/corpse/captain
|
||||
rank = "Captain"
|
||||
mob_name = "Dead Space Pirate Captain"
|
||||
outfit = /datum/outfit/pirate/space/captain
|
||||
|
||||
/obj/effect/mob_spawn/human/pirate/special(mob/living/new_spawn)
|
||||
new_spawn.fully_replace_character_name(new_spawn.real_name,generate_pirate_name())
|
||||
new_spawn.mind.add_antag_datum(/datum/antagonist/pirate)
|
||||
|
||||
@@ -199,8 +199,7 @@
|
||||
return TRUE
|
||||
|
||||
/obj/item/gun_control/attack(mob/living/M, mob/living/user)
|
||||
M.lastattacker = user.real_name
|
||||
M.lastattackerckey = user.ckey
|
||||
M.set_last_attacker(user)
|
||||
M.attacked_by(src, user)
|
||||
add_fingerprint(user)
|
||||
|
||||
|
||||
@@ -554,7 +554,7 @@
|
||||
if(B.cell)
|
||||
if(B.cell.charge > 0 && B.turned_on)
|
||||
flick("baton_active", src)
|
||||
var/stunforce = B.stamforce
|
||||
var/stunforce = B.stamina_loss_amount
|
||||
user.DefaultCombatKnockdown(stunforce * 2)
|
||||
user.stuttering = stunforce/20
|
||||
B.deductcharge(B.hitcost)
|
||||
|
||||
Reference in New Issue
Block a user