diff --git a/code/__defines/_planes+layers.dm b/code/__defines/_planes+layers.dm index a63681cc21..4e9d7fd6ad 100644 --- a/code/__defines/_planes+layers.dm +++ b/code/__defines/_planes+layers.dm @@ -116,6 +116,8 @@ What is the naming convention for planes or layers? #define HUD_LAYER 20 // Above lighting, but below obfuscation. For in-game HUD effects (whereas SCREEN_LAYER is for abstract/OOC things like inventory slots) #define SCREEN_LAYER 22 // Mob HUD/effects layer +#define PLANE_STATUS 2 //Status Indicators that show over mobs' heads when certain things like stuns affect them. + #define PLANE_ADMIN1 3 //Purely for shenanigans (below lighting) #define PLANE_PLANETLIGHTING 4 //Lighting on planets #define PLANE_LIGHTING 5 //Where the lighting (and darkness) lives @@ -138,8 +140,6 @@ What is the naming convention for planes or layers? #define PLANE_MESONS 30 //Stuff seen with mesons, like open ceilings. This is 30 for downstreams. -#define PLANE_STATUS 31 //Status Indicators that show over mobs' heads when certain things like stuns affect them. - #define PLANE_ADMIN2 33 //Purely for shenanigans (above lighting) #define PLANE_BUILDMODE 39 //Things that only show up when you have buildmode on diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm index 0019c3cbd7..da9220b3af 100644 --- a/code/__defines/mobs.dm +++ b/code/__defines/mobs.dm @@ -431,4 +431,6 @@ #define EXAMINE_SKIPLEGS 0x0080 #define EXAMINE_SKIPFEET 0x0100 -#define MAX_NUTRITION 5000 //VOREStation Edit \ No newline at end of file +#define MAX_NUTRITION 5000 //VOREStation Edit + +#define FAKE_INVIS_ALPHA_THRESHOLD 127 // If something's alpha var is at or below this number, certain things will pretend it is invisible. diff --git a/code/__defines/objects.dm b/code/__defines/objects.dm index 3d2891a3c5..d6530fecff 100644 --- a/code/__defines/objects.dm +++ b/code/__defines/objects.dm @@ -40,4 +40,12 @@ #define CATALOGUER_REWARD_SUPERHARD 2560 // Very difficult and dangerous, such as scanning the Advanced Dark Gygax. // 5 10 20 40 80 160 -// 10 40 160 640 2560 \ No newline at end of file +// 10 40 160 640 2560 + +// Defines for Exosuit components. + +#define MECH_HULL "Hull" +#define MECH_ACTUATOR "Actuator" +#define MECH_ARMOR "Plating" +#define MECH_GAS "Life Support" +#define MECH_ELECTRIC "Firmware" diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm index 99a313e454..eba949e02d 100644 --- a/code/_helpers/unsorted.dm +++ b/code/_helpers/unsorted.dm @@ -289,6 +289,11 @@ Turf and target are seperate in case you want to teleport some distance from a t /proc/format_frequency(var/f) return "[round(f / 10)].[f % 10]" +//Opposite of format, returns as a number +/proc/unformat_frequency(frequency) + frequency = text2num(frequency) + return frequency * 10 + //This will update a mob's name, real_name, mind.name, data_core records, pda and id diff --git a/code/controllers/communications.dm b/code/controllers/communications.dm index b99ab68064..c1d87d7ef3 100644 --- a/code/controllers/communications.dm +++ b/code/controllers/communications.dm @@ -148,6 +148,41 @@ var/list/radiochannels = list( "Talon" = TALON_FREQ //VOREStation Add ) +// Hey, if anyone ever needs to update tgui/packages/tgui/constants.js with new radio channels +// I've kept this around just for you. +/* /client/verb/generate_tgui_radio_constants() + set name = "Generate TGUI Radio Constants" + set category = "Generate TGUI Radio Constants" + + var/list/channel_info = list() + + for(var/i in RADIO_LOW_FREQ to RADIO_HIGH_FREQ) + for(var/key in radiochannels) + if(i == radiochannels[key]) + channel_info.Add(list(list("name" = key, "freq" = i, "color" = frequency_span_class(i)))) + + for(var/list/channel in channel_info) + switch(channel["color"]) + if("deadsay") channel["color"] = "#530FAD" + if("radio") channel["color"] = "#008000" + if("deptradio") channel["color"] = "#ff00ff" + if("newscaster") channel["color"] = "#750000" + if("comradio") channel["color"] = "#193A7A" + if("syndradio") channel["color"] = "#6D3F40" + if("centradio") channel["color"] = "#5C5C8A" + if("airadio") channel["color"] = "#FF00FF" + if("entradio") channel["color"] = "#339966" + if("secradio") channel["color"] = "#A30000" + if("engradio") channel["color"] = "#A66300" + if("medradio") channel["color"] = "#008160" + if("sciradio") channel["color"] = "#993399" + if("supradio") channel["color"] = "#5F4519" + if("srvradio") channel["color"] = "#6eaa2c" + if("expradio") channel["color"] = "#555555" + + to_chat(src, json_encode(channel_info)) */ + + // central command channels, i.e deathsquid & response teams var/list/CENT_FREQS = list(ERT_FREQ, DTH_FREQ) diff --git a/code/controllers/subsystems/tgui.dm b/code/controllers/subsystems/tgui.dm index 91533a7783..e94830a5f0 100644 --- a/code/controllers/subsystems/tgui.dm +++ b/code/controllers/subsystems/tgui.dm @@ -255,13 +255,13 @@ SUBSYSTEM_DEF(tgui) * * return int The number of UIs closed. **/ -/datum/controller/subsystem/tgui/proc/close_user_uis(mob/user, datum/src_object) +/datum/controller/subsystem/tgui/proc/close_user_uis(mob/user, datum/src_object, logout = FALSE) var/count = 0 if(length(user?.tgui_open_uis) == 0) return count for(var/datum/tgui/ui in user.tgui_open_uis) if(isnull(src_object) || ui.src_object == src_object) - ui.close() + ui.close(logout = logout) count++ return count @@ -315,7 +315,7 @@ SUBSYSTEM_DEF(tgui) * return int The number of UIs closed. **/ /datum/controller/subsystem/tgui/proc/on_logout(mob/user) - return close_user_uis(user) + return close_user_uis(user, logout = TRUE) /** * private diff --git a/code/datums/autolathe/arms.dm b/code/datums/autolathe/arms.dm index e170d8c885..53a2f14fac 100644 --- a/code/datums/autolathe/arms.dm +++ b/code/datums/autolathe/arms.dm @@ -33,6 +33,12 @@ path =/obj/item/ammo_casing/a12g/stunshell hidden = 1 +/datum/category_item/autolathe/arms/flechetteshell + name = "ammunition (flechette cartridge, shotgun)" + path =/obj/item/ammo_casing/a12g/flechette + hidden = 1 + man_rating = 2 + ////////////////// /*Ammo magazines*/ ////////////////// @@ -64,6 +70,18 @@ name = "pistol magazine (.45 flash)" path =/obj/item/ammo_magazine/m45/flash +/datum/category_item/autolathe/arms/pistol_45ap + name = "pistol magazine (.45 armor piercing)" + path =/obj/item/ammo_magazine/m45/ap + hidden = 1 + resources = list(DEFAULT_WALL_MATERIAL = 500, MAT_PLASTEEL = 300) + +/datum/category_item/autolathe/arms/pistol_45hp + name = "pistol magazine (.45 hollowpoint)" + path =/obj/item/ammo_magazine/m45/hp + hidden = 1 + resources = list(DEFAULT_WALL_MATERIAL = 500, MAT_PLASTIC = 200) + /datum/category_item/autolathe/arms/pistol_45uzi name = "uzi magazine (.45)" path =/obj/item/ammo_magazine/m45uzi @@ -138,6 +156,12 @@ name = "top-mounted SMG magazine (9mm flash)" path =/obj/item/ammo_magazine/m9mmt/flash +/datum/category_item/autolathe/arms/smg_9mmap + name = "top-mounted SMG magazine (9mm armor piercing)" + path =/obj/item/ammo_magazine/m9mmt/ap + hidden = 1 + man_rating = 2 + /////// 10mm /datum/category_item/autolathe/arms/smg_10mm name = "SMG magazine (10mm)" diff --git a/code/datums/autolathe/autolathe.dm b/code/datums/autolathe/autolathe.dm index 91c9ec37b7..003212fe75 100644 --- a/code/datums/autolathe/autolathe.dm +++ b/code/datums/autolathe/autolathe.dm @@ -71,6 +71,7 @@ var/datum/category_collection/autolathe/autolathe_recipes var/is_stack // Creates multiple of an item if applied to non-stack items var/max_stack var/no_scale + var/man_rating = 0 /datum/category_item/autolathe/dd_SortValue() return name \ No newline at end of file diff --git a/code/datums/autolathe/general.dm b/code/datums/autolathe/general.dm index c11319972f..370ffe1d39 100644 --- a/code/datums/autolathe/general.dm +++ b/code/datums/autolathe/general.dm @@ -98,6 +98,18 @@ is_stack = TRUE no_scale = TRUE //prevents material duplication exploits +/datum/category_item/autolathe/general/plasteel + name = "plasteel sheets" + path =/obj/item/stack/material/plasteel + is_stack = TRUE + no_scale = TRUE //prevents material duplication exploits + +/datum/category_item/autolathe/general/plastic + name = "plastic sheets" + path =/obj/item/stack/material/plastic + is_stack = TRUE + no_scale = TRUE //prevents material duplication exploits + //TFF 24/12/19 - Let people print more spray bottles if needed. /datum/category_item/autolathe/general/spraybottle name = "spray bottle" @@ -137,6 +149,12 @@ name = "light fixture battery" path =/obj/item/weapon/cell/emergency_light +/datum/category_item/autolathe/general/idcard + name = "ID Card" + path = /obj/item/weapon/card/id + resources = list(DEFAULT_WALL_MATERIAL = 100, MAT_GLASS = 100, MAT_PLASTIC = 300) + man_rating = 2 + /datum/category_item/autolathe/general/handcuffs name = "handcuffs" path =/obj/item/weapon/handcuffs diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm index ff483432ed..bb9372749a 100644 --- a/code/datums/datacore.dm +++ b/code/datums/datacore.dm @@ -332,6 +332,19 @@ var/global/list/PDA_Manifest = list() var/datum/job/J = SSjob.get_job(assignment) hidden = J?.offmap_spawn + /* Note: Due to cached_character_icon, a number of emergent properties occur due to the initialization + * order of readied-up vs latejoiners. Namely, latejoiners will get a uniform in their datacore picture, but readied-up will + * not. This is due to the fact that SSticker calls data_core.manifest_inject() inside of ticker/proc/create_characters(), + * but does not equip them until ticker/proc/equip_characters(), which is called later. So, this proc is literally called before + * they ever get their equipment, and so it can't get a picture of them in their equipment. + * Latejoiners do not have this problem, because /mob/new_player/proc/AttemptLateSpawn calls EquipRank() before it calls + * this proc, which means that they're already clothed by the time they get their picture taken here. + * The COMPILE_OVERLAYS() here is just to bypass SSoverlays taking for-fucking-ever to update the mob, since we're about to + * take a picture of them, we want all the overlays. + */ + COMPILE_OVERLAYS(H) + SSoverlays.queue -= H + var/id = generate_record_id() //General Record var/datum/data/record/G = CreateGeneralRecord(H, id, hidden) @@ -418,8 +431,8 @@ var/global/list/PDA_Manifest = list() var/icon/side if(H) var/icon/charicon = cached_character_icon(H) - front = icon(charicon, dir = SOUTH) - side = icon(charicon, dir = WEST) + front = icon(charicon, dir = SOUTH, frame = 1) + side = icon(charicon, dir = WEST, frame = 1) else // Sending null things through browse_rsc() makes a runtime and breaks the console trying to view the record. front = icon('html/images/no_image32.png') side = icon('html/images/no_image32.png') diff --git a/code/datums/looping_sounds/machinery_sounds.dm b/code/datums/looping_sounds/machinery_sounds.dm index 3a9a0610a5..e8b0c2aa78 100644 --- a/code/datums/looping_sounds/machinery_sounds.dm +++ b/code/datums/looping_sounds/machinery_sounds.dm @@ -4,7 +4,7 @@ mid_sounds = list('sound/machines/shower/shower_mid1.ogg'=1,'sound/machines/shower/shower_mid2.ogg'=1,'sound/machines/shower/shower_mid3.ogg'=1) mid_length = 10 end_sound = 'sound/machines/shower/shower_end.ogg' - volume = 20 + volume = 15 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/code/datums/supplypacks/supply.dm b/code/datums/supplypacks/supply.dm index 5c35e2ae6f..e9fa3f2d99 100644 --- a/code/datums/supplypacks/supply.dm +++ b/code/datums/supplypacks/supply.dm @@ -89,6 +89,7 @@ name = "Stationery - sticky notes (50)" contains = list(/obj/item/sticky_pad/random) cost = 10 + containertype = /obj/structure/closet/crate/ummarcar containername = "\improper Sticky notes crate" /datum/supply_pack/supply/spare_pda diff --git a/code/datums/wires/wires.dm b/code/datums/wires/wires.dm index d37fbff0fe..83ebb65fa8 100644 --- a/code/datums/wires/wires.dm +++ b/code/datums/wires/wires.dm @@ -154,7 +154,7 @@ /datum/wires/tgui_act(action, list/params) if(..()) - return + return TRUE var/mob/user = usr if(!interactable(user)) diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index f6f2ddfce8..b76d5412b9 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -371,20 +371,22 @@ var/list/mob/living/forced_ambiance_list = new L.lastarea = newarea L.lastareachange = world.time - play_ambience(L) + play_ambience(L, initial = TRUE) if(no_spoilers) L.disable_spoiler_vision() -/area/proc/play_ambience(var/mob/living/L) +/area/proc/play_ambience(var/mob/living/L, initial = TRUE) // Ambience goes down here -- make sure to list each area seperately for ease of adding things in later, thanks! Note: areas adjacent to each other should have the same sounds to prevent cutoff when possible.- LastyScratch if(!(L && L.is_preference_enabled(/datum/client_preference/play_ambiance))) return // If we previously were in an area with force-played ambiance, stop it. - if(L in forced_ambiance_list) + if((L in forced_ambiance_list) && initial) L << sound(null, channel = CHANNEL_AMBIENCE_FORCED) forced_ambiance_list -= L if(forced_ambience) + if(L in forced_ambiance_list) + return if(forced_ambience.len) forced_ambiance_list |= L var/sound/chosen_ambiance = pick(forced_ambience) diff --git a/code/game/dna/dna2.dm b/code/game/dna/dna2.dm index 13138f0eef..ddd49703c6 100644 --- a/code/game/dna/dna2.dm +++ b/code/game/dna/dna2.dm @@ -127,6 +127,7 @@ var/global/list/datum/dna/gene/dna_genes[0] new_dna.species=species new_dna.body_markings=body_markings.Copy() new_dna.base_species=base_species //VOREStation Edit + new_dna.custom_species=custom_species //VOREStaton Edit new_dna.species_traits=species_traits.Copy() //VOREStation Edit new_dna.blood_color=blood_color //VOREStation Edit for(var/b=1;b<=DNA_SE_LENGTH;b++) diff --git a/code/game/dna/dna_modifier.dm b/code/game/dna/dna_modifier.dm index a72c92d47c..eb0ad7000c 100644 --- a/code/game/dna/dna_modifier.dm +++ b/code/game/dna/dna_modifier.dm @@ -455,13 +455,13 @@ /obj/machinery/computer/scan_consolenew/tgui_act(action, params) if(..()) - return FALSE // don't update uis + return TRUE if(!istype(usr.loc, /turf)) - return FALSE // don't update uis + return TRUE if(!src || !src.connected) - return FALSE // don't update uis + return TRUE if(irradiating) // Make sure that it isn't already irradiating someone... - return FALSE // don't update uis + return TRUE add_fingerprint(usr) diff --git a/code/game/jobs/job/science_vr.dm b/code/game/jobs/job/science_vr.dm index 551784cfa5..499bb42e2c 100644 --- a/code/game/jobs/job/science_vr.dm +++ b/code/game/jobs/job/science_vr.dm @@ -55,4 +55,4 @@ outfit_type = /decl/hierarchy/outfit/job/science/xenobiologist job_description = "A Xenobotanist grows and cares for a variety of abnormal, custom made, and frequently dangerous plant life. When the products of these plants \ - are both safe and beneficial to the station, they may choose to introduce it to the rest of the crew." \ No newline at end of file + are both safe and beneficial to the station, they may choose to introduce it to the rest of the crew." diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm index d7d8761db7..a97fbf986f 100644 --- a/code/game/machinery/Sleeper.dm +++ b/code/game/machinery/Sleeper.dm @@ -293,7 +293,7 @@ /obj/machinery/sleeper/tgui_act(action, params, datum/tgui/ui, datum/tgui_state/state) if(..()) - return + return TRUE if(!controls_inside && usr == occupant) return if(panel_open) diff --git a/code/game/machinery/adv_med.dm b/code/game/machinery/adv_med.dm index 12f3b28b97..a3e4d2eeb3 100644 --- a/code/game/machinery/adv_med.dm +++ b/code/game/machinery/adv_med.dm @@ -216,7 +216,11 @@ var/reagentData[0] if(H.reagents.reagent_list.len >= 1) for(var/datum/reagent/R in H.reagents.reagent_list) - reagentData[++reagentData.len] = list("name" = R.name, "amount" = R.volume) + reagentData[++reagentData.len] = list( + "name" = R.name, + "amount" = R.volume, + "overdose" = (R.overdose && R.volume > R.overdose) ? TRUE : FALSE, + ) else reagentData = null @@ -225,7 +229,11 @@ var/ingestedData[0] if(H.ingested.reagent_list.len >= 1) for(var/datum/reagent/R in H.ingested.reagent_list) - ingestedData[++ingestedData.len] = list("name" = R.name, "amount" = R.volume) + ingestedData[++ingestedData.len] = list( + "name" = R.name, + "amount" = R.volume, + "overdose" = (R.overdose && R.volume > R.overdose) ? TRUE : FALSE, + ) else ingestedData = null @@ -315,7 +323,7 @@ /obj/machinery/bodyscanner/tgui_act(action, params) if(..()) - return + return TRUE . = TRUE switch(action) @@ -610,4 +618,4 @@ return if(scanner) - return scanner.tgui_interact(user) \ No newline at end of file + return scanner.tgui_interact(user) diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm index 1629b77bdd..3e1188198b 100644 --- a/code/game/machinery/autolathe.dm +++ b/code/game/machinery/autolathe.dm @@ -12,8 +12,8 @@ circuit = /obj/item/weapon/circuitboard/autolathe var/datum/category_collection/autolathe/machine_recipes - var/list/stored_material = list(DEFAULT_WALL_MATERIAL = 0, "glass" = 0) - var/list/storage_capacity = list(DEFAULT_WALL_MATERIAL = 0, "glass" = 0) + var/list/stored_material = list(DEFAULT_WALL_MATERIAL = 0, MAT_GLASS = 0, MAT_PLASTEEL = 0, MAT_PLASTIC = 0) + var/list/storage_capacity = list(DEFAULT_WALL_MATERIAL = 0, MAT_GLASS = 0, MAT_PLASTEEL = 0, MAT_PLASTIC = 0) var/datum/category_group/autolathe/current_category var/hacked = 0 @@ -26,6 +26,9 @@ var/datum/wires/autolathe/wires = null + var/mb_rating = 0 + var/man_rating = 0 + var/filtertext /obj/machinery/autolathe/Initialize() @@ -64,6 +67,9 @@ var/list/material_bottom = list("") for(var/material in stored_material) + if(material != DEFAULT_WALL_MATERIAL && material != MAT_GLASS) // Don't show the Extras unless people care enough to put them in. + if(stored_material[material] <= 0) + continue material_top += "[material]" material_bottom += "[stored_material[material]]/[storage_capacity[material]]" @@ -72,7 +78,9 @@ dat += "

Printable Designs

Showing: [current_category].

" for(var/datum/category_item/autolathe/R in current_category.items) - if(R.hidden && !hacked) + if(R.hidden && !hacked) // Illegal or nonstandard. + continue + if(R.man_rating > man_rating) // Advanced parts. continue if(filtertext && findtext(R.name, filtertext) == 0) continue @@ -311,14 +319,16 @@ //Updates overall lathe storage size. /obj/machinery/autolathe/RefreshParts() ..() - var/mb_rating = 0 - var/man_rating = 0 + mb_rating = 0 + man_rating = 0 for(var/obj/item/weapon/stock_parts/matter_bin/MB in component_parts) mb_rating += MB.rating for(var/obj/item/weapon/stock_parts/manipulator/M in component_parts) man_rating += M.rating storage_capacity[DEFAULT_WALL_MATERIAL] = mb_rating * 25000 + storage_capacity[MAT_PLASTIC] = mb_rating * 20000 + storage_capacity[MAT_PLASTEEL] = mb_rating * 16250 storage_capacity["glass"] = mb_rating * 12500 build_time = 50 / man_rating mat_efficiency = 1.1 - man_rating * 0.1// Normally, price is 1.25 the amount of material, so this shouldn't go higher than 0.6. Maximum rating of parts is 5 diff --git a/code/game/machinery/computer/Operating.dm b/code/game/machinery/computer/Operating.dm index cf041a74fc..aa76f23077 100644 --- a/code/game/machinery/computer/Operating.dm +++ b/code/game/machinery/computer/Operating.dm @@ -129,7 +129,7 @@ /obj/machinery/computer/operating/tgui_act(action, params) if(..()) - return + return TRUE if((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf))) || (istype(usr, /mob/living/silicon))) usr.set_machine(src) diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm index ffc99e4733..8ef01c9b64 100644 --- a/code/game/machinery/computer/arcade.dm +++ b/code/game/machinery/computer/arcade.dm @@ -14,17 +14,7 @@ /obj/item/clothing/suit/syndicatefake = 2, /obj/item/weapon/storage/fancy/crayons = 2, /obj/item/toy/spinningtoy = 2, - /obj/item/toy/prize/ripley = 1, - /obj/item/toy/prize/fireripley = 1, - /obj/item/toy/prize/deathripley = 1, - /obj/item/toy/prize/gygax = 1, - /obj/item/toy/prize/durand = 1, - /obj/item/toy/prize/honk = 1, - /obj/item/toy/prize/marauder = 1, - /obj/item/toy/prize/seraph = 1, - /obj/item/toy/prize/mauler = 1, - /obj/item/toy/prize/odysseus = 1, - /obj/item/toy/prize/phazon = 1, + /obj/random/mech_toy = 1, /obj/item/weapon/reagent_containers/spray/waterflower = 1, /obj/random/action_figure = 1, /obj/random/plushie = 1, diff --git a/code/game/machinery/computer/cloning.dm b/code/game/machinery/computer/cloning.dm index 07fac6fb0d..9bf3235d8d 100644 --- a/code/game/machinery/computer/cloning.dm +++ b/code/game/machinery/computer/cloning.dm @@ -198,7 +198,7 @@ /obj/machinery/computer/cloning/tgui_act(action, params) if(..()) - return + return TRUE . = TRUE switch(tgui_modal_act(src, action, params)) diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm index 40e8152340..f775bed4c3 100644 --- a/code/game/machinery/computer/medical.dm +++ b/code/game/machinery/computer/medical.dm @@ -170,7 +170,7 @@ data["virus"] = list() for(var/ID in virusDB) var/datum/data/record/v = virusDB[ID] - data["virus"] += list(list("name" = v.fields["name"], "D" = v)) + data["virus"] += list(list("name" = v.fields["name"], "D" = "\ref[v]")) if(MED_DATA_MEDBOT) data["medbots"] = list() for(var/mob/living/bot/medbot/M in mob_list) @@ -198,7 +198,7 @@ /obj/machinery/computer/med_data/tgui_act(action, params) if(..()) - return + return TRUE if(!data_core.general.Find(active1)) active1 = null @@ -266,16 +266,9 @@ active2 = null if("vir") var/datum/data/record/v = locate(params["vir"]) - var/list/payload = list( - id = v.fields["id"], - name = v.fields["name"], - max_stages = "Unknown", - spread_text = v.fields["spread type"], - cure = v.fields["antigen"], - desc = v.fields["description"], - severity = "Unknown" - ); - tgui_modal_message(src, "virus", "", null, payload) + if(!istype(v)) + return FALSE + tgui_modal_message(src, "virus", "", null, v.fields["tgui_description"]) if("del_all") for(var/datum/data/record/R in data_core.medical) qdel(R) @@ -508,3 +501,6 @@ icon_screen = "medlaptop" circuit = /obj/item/weapon/circuitboard/med_data/laptop density = 0 + +#undef FIELD +#undef MED_FIELD diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm index d279a9773b..d822bcb739 100644 --- a/code/game/machinery/cryo.dm +++ b/code/game/machinery/cryo.dm @@ -141,7 +141,7 @@ /obj/machinery/atmospherics/unary/cryo_cell/tgui_act(action, params) if(..() || usr == occupant) - return + return TRUE . = TRUE switch(action) diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 8f1d7faed7..2ec9c0043b 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -1127,6 +1127,7 @@ About the new airlock wires panel: SetWeakened(5) var/turf/T = get_turf(src) T.add_blood(src) + return 1 /mob/living/carbon/airlock_crush(var/crush_damage) . = ..() diff --git a/code/game/machinery/doors/blast_door.dm b/code/game/machinery/doors/blast_door.dm index ebf1beceaf..06a4527f7e 100644 --- a/code/game/machinery/doors/blast_door.dm +++ b/code/game/machinery/doors/blast_door.dm @@ -10,6 +10,9 @@ // UPDATE 06.04.2018 // The emag thing wasn't working as intended, manually overwrote it. +#define BLAST_DOOR_CRUSH_DAMAGE 40 +#define SHUTTER_CRUSH_DAMAGE 0 // VOREStation Edit - Shutter damage 0. + /obj/machinery/door/blast name = "Blast Door" desc = "That looks like it doesn't open easily." @@ -24,6 +27,8 @@ var/icon_state_closing = null var/open_sound = 'sound/machines/blastdooropen.ogg' var/close_sound = 'sound/machines/blastdoorclose.ogg' + var/damage = BLAST_DOOR_CRUSH_DAMAGE + var/multiplier = 1 // The multiplier for how powerful our YEET is. closed_layer = ON_WINDOW_LAYER // Above airlocks when closed var/id = 1.0 @@ -61,9 +66,13 @@ SSradiation.resistance_cache.Remove(get_turf(src)) return -// Has to be in here, comment at the top is older than the emag_act code on doors proper +// Proc: emag_act() +// Description: Emag action to allow blast doors to double their yeet distance and speed. /obj/machinery/door/blast/emag_act() - return -1 + if(!emagged) + emagged = 1 + multiplier = 2 // Haha emag go yeet + return 1 // Blast doors are triggered remotely, so nobody is allowed to physically influence it. /obj/machinery/door/blast/allowed(mob/M) @@ -88,6 +97,10 @@ // Parameters: None // Description: Closes the door. No checks are done inside this proc. /obj/machinery/door/blast/proc/force_close() + // Blast door turf checks. We do this before the door closes to prevent it from failing after the door is closed, because obv a closed door will block any adjacency checks. + var/turf/T = get_turf(src) + var/list/yeet_turfs = T.CardinalTurfs(TRUE) + src.operating = 1 playsound(src, close_sound, 100, 1) src.layer = closed_layer @@ -98,6 +111,14 @@ src.set_opacity(1) sleep(15) src.operating = 0 + + // Blast door crushing. + for(var/turf/turf in locs) + for(var/atom/movable/AM in turf) + if(AM.airlock_crush(damage)) + if(LAZYLEN(yeet_turfs)) + AM.throw_at(get_edge_target_turf(src, get_dir(src, pick(yeet_turfs))), (rand(1,3) * multiplier), (rand(2,4) * multiplier)) // YEET. + take_damage(damage*0.2) // Proc: force_toggle() // Parameters: None @@ -300,3 +321,7 @@ obj/machinery/door/blast/regular/open icon_state_closed = "shutter1" icon_state_closing = "shutterc1" icon_state = "shutter1" + damage = SHUTTER_CRUSH_DAMAGE + +#undef BLAST_DOOR_CRUSH_DAMAGE +#undef SHUTTER_CRUSH_DAMAGE \ No newline at end of file diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm index 0d2c4e78e8..1afa9910f4 100644 --- a/code/game/machinery/machinery.dm +++ b/code/game/machinery/machinery.dm @@ -213,6 +213,13 @@ Class Procs: /obj/machinery/proc/inoperable(var/additional_flags = 0) return (stat & (NOPOWER | BROKEN | additional_flags)) +// Duplicate of below because we don't want to fuck around with CanUseTopic in TGUI +// TODO: Replace this with can_interact from /tg/ +/obj/machinery/tgui_status(mob/user) + if(!interact_offline && (stat & (NOPOWER | BROKEN))) + return STATUS_CLOSE + return ..() + /obj/machinery/CanUseTopic(var/mob/user) if(!interact_offline && (stat & (NOPOWER | BROKEN))) return STATUS_CLOSE diff --git a/code/game/mecha/combat/combat.dm b/code/game/mecha/combat/combat.dm index b5d3351b21..d618d276c3 100644 --- a/code/game/mecha/combat/combat.dm +++ b/code/game/mecha/combat/combat.dm @@ -17,6 +17,14 @@ max_special_equip = 1 cargo_capacity = 1 + starting_components = list( + /obj/item/mecha_parts/component/hull/durable, + /obj/item/mecha_parts/component/actuator, + /obj/item/mecha_parts/component/armor/reinforced, + /obj/item/mecha_parts/component/gas, + /obj/item/mecha_parts/component/electrical + ) + /* /obj/mecha/combat/range_action(target as obj|mob|turf) if(internal_damage&MECHA_INT_CONTROL_LOST) diff --git a/code/game/mecha/combat/durand.dm b/code/game/mecha/combat/durand.dm index 5ab43a93ee..aa5a9e000c 100644 --- a/code/game/mecha/combat/durand.dm +++ b/code/game/mecha/combat/durand.dm @@ -5,8 +5,8 @@ initial_icon = "durand" step_in = 4 dir_in = 1 //Facing North. - health = 400 - maxhealth = 400 //Don't forget to update the /old variant if you change this number. + health = 300 + maxhealth = 300 //Don't forget to update the /old variant if you change this number. deflect_chance = 20 damage_absorption = list("brute"=0.5,"fire"=1.1,"bullet"=0.65,"laser"=0.85,"energy"=0.9,"bomb"=0.8) max_temperature = 30000 @@ -23,6 +23,14 @@ max_universal_equip = 1 max_special_equip = 1 + starting_components = list( + /obj/item/mecha_parts/component/hull/durable, + /obj/item/mecha_parts/component/actuator, + /obj/item/mecha_parts/component/armor/military, + /obj/item/mecha_parts/component/gas, + /obj/item/mecha_parts/component/electrical + ) + defence_mode_possible = 1 /* @@ -73,5 +81,5 @@ /obj/mecha/combat/durand/old/New() ..() health = 25 - maxhealth = 350 //Just slightly worse. + maxhealth = 250 //Just slightly worse. cell.charge = rand(0, (cell.charge/2)) \ No newline at end of file diff --git a/code/game/mecha/combat/gygax.dm b/code/game/mecha/combat/gygax.dm index d74b96cf99..0323124c47 100644 --- a/code/game/mecha/combat/gygax.dm +++ b/code/game/mecha/combat/gygax.dm @@ -5,8 +5,8 @@ initial_icon = "gygax" step_in = 3 dir_in = 1 //Facing North. - health = 300 - maxhealth = 300 //Don't forget to update the /old variant if you change this number. + health = 250 + maxhealth = 250 //Don't forget to update the /old variant if you change this number. deflect_chance = 15 damage_absorption = list("brute"=0.75,"fire"=1,"bullet"=0.8,"laser"=0.7,"energy"=0.85,"bomb"=1) max_temperature = 25000 @@ -21,6 +21,14 @@ max_universal_equip = 1 max_special_equip = 1 + starting_components = list( + /obj/item/mecha_parts/component/hull/lightweight, + /obj/item/mecha_parts/component/actuator, + /obj/item/mecha_parts/component/armor/marshal, + /obj/item/mecha_parts/component/gas, + /obj/item/mecha_parts/component/electrical + ) + overload_possible = 1 //Not quite sure how to move those yet. @@ -58,17 +66,12 @@ max_universal_equip = 1 max_special_equip = 2 -/obj/mecha/combat/gygax/dark/Initialize() - ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/grenade/clusterbang - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/teleporter - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay - ME.attach(src) - return + starting_equipment = list( + /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot, + /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/grenade/clusterbang, + /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay, + /obj/item/mecha_parts/mecha_equipment/teleporter + ) /obj/mecha/combat/gygax/dark/add_cell(var/obj/item/weapon/cell/C=null) if(C) @@ -101,6 +104,14 @@ max_universal_equip = 1 max_special_equip = 1 + starting_components = list( + /obj/item/mecha_parts/component/hull, + /obj/item/mecha_parts/component/actuator, + /obj/item/mecha_parts/component/armor/lightweight, + /obj/item/mecha_parts/component/gas, + /obj/item/mecha_parts/component/electrical + ) + var/obj/item/clothing/glasses/hud/health/mech/hud /obj/mecha/combat/gygax/serenity/New() diff --git a/code/game/mecha/combat/marauder.dm b/code/game/mecha/combat/marauder.dm index a79dc3fda2..e2c0b9467a 100644 --- a/code/game/mecha/combat/marauder.dm +++ b/code/game/mecha/combat/marauder.dm @@ -5,8 +5,8 @@ icon_state = "marauder" initial_icon = "marauder" step_in = 5 - health = 500 - maxhealth = 500 //Don't forget to update the /old variant if you change this number. + health = 350 + maxhealth = 350 //Don't forget to update the /old variant if you change this number. deflect_chance = 25 damage_absorption = list("brute"=0.5,"fire"=0.7,"bullet"=0.45,"laser"=0.6,"energy"=0.7,"bomb"=0.7) max_temperature = 60000 @@ -29,6 +29,21 @@ zoom_possible = 1 thrusters_possible = 1 + starting_components = list( + /obj/item/mecha_parts/component/hull/durable, + /obj/item/mecha_parts/component/actuator, + /obj/item/mecha_parts/component/armor/military, + /obj/item/mecha_parts/component/gas, + /obj/item/mecha_parts/component/electrical + ) + + starting_equipment = list( + /obj/item/mecha_parts/mecha_equipment/weapon/energy/pulse, + /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/explosive, + /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay, + /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster + ) + /obj/mecha/combat/marauder/seraph desc = "Heavy-duty, command-type exosuit. This is a custom model, utilized only by high-ranking military personnel." name = "Seraph" @@ -37,12 +52,20 @@ initial_icon = "seraph" operation_req_access = list(access_cent_creed) step_in = 3 - health = 550 + health = 450 wreckage = /obj/effect/decal/mecha_wreckage/seraph internal_damage_threshold = 20 force = 55 max_equip = 5 + starting_equipment = list( + /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot, + /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/explosive, + /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay, + /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster, + /obj/item/mecha_parts/mecha_equipment/teleporter + ) + //Note that is the Mauler /obj/mecha/combat/marauder/mauler desc = "Heavy-duty, combat exosuit, developed off of the existing Marauder model." @@ -53,40 +76,6 @@ wreckage = /obj/effect/decal/mecha_wreckage/mauler mech_faction = MECH_FACTION_SYNDI -//Note that is the default Marauder -/obj/mecha/combat/marauder/Initialize() - ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/weapon/energy/pulse - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/explosive - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) - ME.attach(src) - return - -//Note that this is the seraph. -/obj/mecha/combat/marauder/seraph/Initialize() - ..()//Let it equip whatever is needed. - var/obj/item/mecha_parts/mecha_equipment/ME - if(equipment.len)//Now to remove it and equip anew. - for(ME in equipment) - ME.detach() - qdel(ME) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/explosive(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/teleporter(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/tesla_energy_relay(src) - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster(src) - ME.attach(src) - return - - //I'll break this down later /obj/mecha/combat/marauder/relaymove(mob/user,direction) if(user != src.occupant) //While not "realistic", this piece is player friendly. @@ -150,17 +139,10 @@ /obj/mecha/combat/marauder/old desc = "Heavy-duty, combat exosuit, developed after the Durand model. Rarely found among civilian populations. This one is particularly worn looking and likely isn't as sturdy." + starting_equipment = null + /obj/mecha/combat/marauder/old/New() ..() health = 25 - maxhealth = 400 //Just slightly worse. + maxhealth = 300 //Just slightly worse. cell.charge = rand(0, (cell.charge/2)) - -/obj/mecha/combat/marauder/old/Initialize() - ..() - var/obj/item/mecha_parts/mecha_equipment/ME - if(equipment.len) - for(ME in equipment) - ME.detach() - qdel(ME) - return \ No newline at end of file diff --git a/code/game/mecha/combat/phazon.dm b/code/game/mecha/combat/phazon.dm index 121bab34f9..17aea92345 100644 --- a/code/game/mecha/combat/phazon.dm +++ b/code/game/mecha/combat/phazon.dm @@ -25,15 +25,24 @@ max_universal_equip = 3 max_special_equip = 4 - phasing_possible = 1 - switch_dmg_type_possible = 1 + starting_components = list( + /obj/item/mecha_parts/component/hull/durable, + /obj/item/mecha_parts/component/actuator, + /obj/item/mecha_parts/component/armor/alien, + /obj/item/mecha_parts/component/gas, + /obj/item/mecha_parts/component/electrical + ) + + cloak_possible = TRUE + phasing_possible = TRUE + switch_dmg_type_possible = TRUE /obj/mecha/combat/phazon/equipped/Initialize() ..() - var/obj/item/mecha_parts/mecha_equipment/ME = new /obj/item/mecha_parts/mecha_equipment/tool/rcd - ME.attach(src) - ME = new /obj/item/mecha_parts/mecha_equipment/gravcatapult - ME.attach(src) + starting_equipment = list( + /obj/item/mecha_parts/mecha_equipment/tool/rcd, + /obj/item/mecha_parts/mecha_equipment/gravcatapult + ) return /* Leaving this until we are really sure we don't need it for reference. @@ -95,8 +104,9 @@ max_universal_equip = 2 max_special_equip = 2 - phasing_possible = 1 - switch_dmg_type_possible = 1 + phasing_possible = TRUE + switch_dmg_type_possible = TRUE + cloak_possible = FALSE /obj/mecha/combat/phazon/janus/take_damage(amount, type="brute") ..() diff --git a/code/game/mecha/components/_component.dm b/code/game/mecha/components/_component.dm new file mode 100644 index 0000000000..aadf390c61 --- /dev/null +++ b/code/game/mecha/components/_component.dm @@ -0,0 +1,155 @@ + +/obj/item/mecha_parts/component + name = "mecha component" + icon = 'icons/mecha/mech_component.dmi' + icon_state = "component" + w_class = ITEMSIZE_HUGE + origin_tech = list(TECH_DATA = 2, TECH_ENGINEERING = 2) + + var/component_type = null + + var/obj/mecha/chassis = null + var/start_damaged = FALSE + + var/emp_resistance = 0 // Amount of emp 'levels' removed. + + var/list/required_type = null // List, if it exists. Exosuits meant to use the component (Unique var changes / effects) + + var/integrity + var/integrity_danger_mod = 0.5 // Multiplier for comparison to max_integrity before problems start. + var/max_integrity = 100 + + var/step_delay = 0 + + var/relative_size = 30 // Percent chance for the component to be hit. + + var/internal_damage_flag // If set, the component will toggle the flag on or off if it is destroyed / severely damaged. + +/obj/item/mecha_parts/component/examine(mob/user) + . = ..() + var/show_integrity = round(integrity/max_integrity*100, 0.1) + switch(show_integrity) + if(85 to 100) + . += "It's fully intact." + if(65 to 85) + . += "It's slightly damaged." + if(45 to 65) + . += "It's badly damaged." + if(25 to 45) + . += "It's heavily damaged." + if(2 to 25) + . += "It's falling apart." + if(0 to 1) + . += "It is completely destroyed." + +/obj/item/mecha_parts/component/Initialize() + ..() + integrity = max_integrity + + if(start_damaged) + integrity = round(integrity * integrity_danger_mod) + +/obj/item/mecha_parts/component/Destroy() + detach() + return ..() + +// Damage code. + +/obj/item/mecha_parts/component/emp_act(var/severity = 4) + if(severity + emp_resistance > 4) + return + + severity = clamp(severity + emp_resistance, 1, 4) + + take_damage((4 - severity) * round(integrity * 0.1, 0.1)) + +/obj/item/mecha_parts/component/proc/adjust_integrity(var/amt = 0) + integrity = clamp(integrity + amt, 0, max_integrity) + return + +/obj/item/mecha_parts/component/proc/damage_part(var/dam_amt = 0, var/type = BRUTE) + if(dam_amt <= 0) + return FALSE + + adjust_integrity(-1 * dam_amt) + + if(chassis && internal_damage_flag) + if(get_efficiency() < 0.5) + chassis.check_for_internal_damage(list(internal_damage_flag), TRUE) + + return TRUE + +/obj/item/mecha_parts/component/proc/get_efficiency() + var/integ_limit = round(max_integrity * integrity_danger_mod) + + if(integrity < integ_limit) + var/int_percent = round(integrity / integ_limit, 0.1) + + return int_percent + + return 1 + +// Attach/Detach code. + +/obj/item/mecha_parts/component/proc/attach(var/obj/mecha/target, var/mob/living/user) + if(target) + if(!(component_type in target.internal_components)) + if(user) + to_chat(user, "\The [target] doesn't seem to have anywhere to put \the [src].") + return FALSE + if(target.internal_components[component_type]) + if(user) + to_chat(user, "\The [target] already has a [component_type] installed!") + return FALSE + chassis = target + if(user) + user.drop_from_inventory(src) + forceMove(target) + + if(internal_damage_flag) + if(integrity > (max_integrity * integrity_danger_mod)) + if(chassis.hasInternalDamage(internal_damage_flag)) + chassis.clearInternalDamage(internal_damage_flag) + + else + chassis.check_for_internal_damage(list(internal_damage_flag)) + + chassis.internal_components[component_type] = src + + if(user) + chassis.visible_message("[user] installs \the [src] in \the [chassis].") + return TRUE + return FALSE + +/obj/item/mecha_parts/component/proc/detach() + if(chassis) + chassis.internal_components[component_type] = null + + if(internal_damage_flag && chassis.hasInternalDamage(internal_damage_flag)) // If the module has been removed, it's kind of unfair to keep it causing problems by being damaged. It's nonfunctional either way. + chassis.clearInternalDamage(internal_damage_flag) + + forceMove(get_turf(chassis)) + chassis = null + return TRUE + + +/obj/item/mecha_parts/component/attackby(obj/item/weapon/W as obj, mob/user as mob) + if(istype(W,/obj/item/stack/nanopaste)) + var/obj/item/stack/nanopaste/NP = W + + if(integrity < max_integrity) + while(integrity < max_integrity && NP) + if(do_after(user, 1 SECOND, src) && NP.use(1)) + adjust_integrity(10) + + return + + return ..() + +// Various procs to handle different calls by Exosuits. IE, movement actions, damage actions, etc. + +/obj/item/mecha_parts/component/proc/get_step_delay() + return step_delay + +/obj/item/mecha_parts/component/proc/handle_move() + return diff --git a/code/game/mecha/components/actuators.dm b/code/game/mecha/components/actuators.dm new file mode 100644 index 0000000000..d814518629 --- /dev/null +++ b/code/game/mecha/components/actuators.dm @@ -0,0 +1,37 @@ + +/obj/item/mecha_parts/component/actuator + name = "mecha actuator" + icon = 'icons/mecha/mech_component.dmi' + icon_state = "motor" + w_class = ITEMSIZE_HUGE + origin_tech = list(TECH_DATA = 2, TECH_ENGINEERING = 2) + + component_type = MECH_ACTUATOR + + start_damaged = FALSE + + emp_resistance = 1 + + required_type = null // List, if it exists. Exosuits meant to use the component. + + integrity_danger_mod = 0.6 // Multiplier for comparison to max_integrity before problems start. + max_integrity = 50 + + internal_damage_flag = MECHA_INT_CONTROL_LOST + + var/strafing_multiplier = 1.5 + +/obj/item/mecha_parts/component/actuator/get_step_delay() + return step_delay + +/obj/item/mecha_parts/component/actuator/hispeed + name = "overclocked mecha actuator" + + step_delay = -1 + + emp_resistance = -1 + + integrity_danger_mod = 0.7 + max_integrity = 60 + + strafing_multiplier = 1.2 diff --git a/code/game/mecha/components/armor.dm b/code/game/mecha/components/armor.dm new file mode 100644 index 0000000000..b64c37bca5 --- /dev/null +++ b/code/game/mecha/components/armor.dm @@ -0,0 +1,238 @@ + +/obj/item/mecha_parts/component/armor + name = "mecha plating" + icon = 'icons/mecha/mech_component.dmi' + icon_state = "armor" + w_class = ITEMSIZE_HUGE + origin_tech = list(TECH_DATA = 1, TECH_ENGINEERING = 2) + + component_type = MECH_ARMOR + + start_damaged = FALSE + + emp_resistance = 4 + + required_type = null // List, if it exists. Exosuits meant to use the component. + + integrity_danger_mod = 0.4 // Multiplier for comparison to max_integrity before problems start. + max_integrity = 120 + + internal_damage_flag = MECHA_INT_TEMP_CONTROL + + step_delay = 1 + + var/deflect_chance = 10 + var/list/damage_absorption = list( + "brute"= 0.8, + "fire"= 1.2, + "bullet"= 0.9, + "laser"= 1, + "energy"= 1, + "bomb"= 1, + "bio"= 1, + "rad"= 1 + ) + + var/damage_minimum = 10 + var/minimum_penetration = 0 + var/fail_penetration_value = 0.66 + +/obj/item/mecha_parts/component/armor/mining + name = "blast-resistant mecha plating" + + step_delay = 2 + max_integrity = 80 + + damage_absorption = list( + "brute"=0.8, + "fire"=0.8, + "bullet"=1.2, + "laser"=1.2, + "energy"=1, + "bomb"=0.5, + "bio"=1, + "rad"=1 + ) + +/obj/item/mecha_parts/component/armor/lightweight + name = "lightweight mecha plating" + + max_integrity = 50 + step_delay = 0 + + damage_absorption = list( + "brute"=1, + "fire"=1.4, + "bullet"=1.1, + "laser"=1.2, + "energy"=1, + "bomb"=1, + "bio"=1, + "rad"=1 + ) + +/obj/item/mecha_parts/component/armor/reinforced + name = "reinforced mecha plating" + + step_delay = 4 + + max_integrity = 80 + + minimum_penetration = 10 + + damage_absorption = list( + "brute"=0.7, + "fire"=1, + "bullet"=0.7, + "laser"=0.85, + "energy"=1, + "bomb"=0.8 + ) + +/obj/item/mecha_parts/component/armor/military + name = "military grade mecha plating" + + step_delay = 6 + + max_integrity = 100 + + emp_resistance = 2 + + required_type = list(/obj/mecha/combat) + + damage_minimum = 15 + minimum_penetration = 25 + + damage_absorption = list( + "brute"=0.5, + "fire"=1.1, + "bullet"=0.65, + "laser"=0.85, + "energy"=0.9, + "bomb"=0.8 + ) + +/obj/item/mecha_parts/component/armor/military/attach(var/obj/mecha/target, var/mob/living/user) + . = ..() + if(.) + var/typepass = FALSE + for(var/type in required_type) + if(istype(chassis, type)) + typepass = TRUE + + if(typepass) + step_delay = 3 + else + step_delay = initial(step_delay) + +/obj/item/mecha_parts/component/armor/marshal + name = "marshal mecha plating" + + step_delay = 5 + + max_integrity = 100 + + emp_resistance = 3 + + deflect_chance = 15 + + minimum_penetration = 10 + + required_type = list(/obj/mecha/combat) + + damage_absorption = list( + "brute"=0.75, + "fire"=1, + "bullet"=0.8, + "laser"=0.7, + "energy"=0.85, + "bomb"=1 + ) + +/obj/item/mecha_parts/component/armor/marshal/attach(var/obj/mecha/target, var/mob/living/user) + . = ..() + if(.) + var/typepass = FALSE + for(var/type in required_type) + if(istype(chassis, type)) + typepass = TRUE + + if(typepass) + step_delay = 2 + else + step_delay = initial(step_delay) + +/obj/item/mecha_parts/component/armor/marshal/reinforced + name = "blackops mecha plating" + + step_delay = 5 + + damage_absorption = list( + "brute"=0.6, + "fire"=0.8, + "bullet"=0.6, + "laser"=0.5, + "energy"=0.65, + "bomb"=0.8 + ) + +/obj/item/mecha_parts/component/armor/military/marauder + name = "cutting edge mecha plating" + + step_delay = 4 + + max_integrity = 150 + + emp_resistance = 3 + + required_type = list(/obj/mecha/combat/marauder) + + deflect_chance = 25 + damage_minimum = 30 + minimum_penetration = 25 + + damage_absorption = list( + "brute"=0.5, + "fire"=0.7, + "bullet"=0.45, + "laser"=0.6, + "energy"=0.7, + "bomb"=0.7 + ) + +/obj/item/mecha_parts/component/armor/military/marauder/attach(var/obj/mecha/target, var/mob/living/user) + . = ..() + if(.) + var/typepass = FALSE + for(var/type in required_type) + if(istype(chassis, type)) + typepass = TRUE + + if(typepass) + step_delay = 1 + else + step_delay = initial(step_delay) + +/obj/item/mecha_parts/component/armor/alien + name = "strange mecha plating" + step_delay = 3 + damage_absorption = list( + "brute"=0.7, + "fire"=0.7, + "bullet"=0.7, + "laser"=0.7, + "energy"=0.7, + "bomb"=0.7 + ) + +/obj/item/mecha_parts/component/armor/alien/attach(var/obj/mecha/target, var/mob/living/user) + . = ..() + if(.) + if(istype(target, /obj/mecha/combat/phazon/janus)) + step_delay = -1 + + else if(istype(target, /obj/mecha/combat/phazon)) + step_delay = -3 + + else + step_delay = initial(step_delay) diff --git a/code/game/mecha/components/electrical.dm b/code/game/mecha/components/electrical.dm new file mode 100644 index 0000000000..de07c96c84 --- /dev/null +++ b/code/game/mecha/components/electrical.dm @@ -0,0 +1,31 @@ + +/obj/item/mecha_parts/component/electrical + name = "mecha electrical harness" + icon = 'icons/mecha/mech_component.dmi' + icon_state = "board" + w_class = ITEMSIZE_HUGE + origin_tech = list(TECH_DATA = 2, TECH_ENGINEERING = 2) + + component_type = MECH_ELECTRIC + + emp_resistance = 1 + + integrity_danger_mod = 0.4 + max_integrity = 40 + + step_delay = 0 + + relative_size = 20 + + internal_damage_flag = MECHA_INT_SHORT_CIRCUIT + + var/charge_cost_mod = 1 + +/obj/item/mecha_parts/component/electrical/high_current + name = "efficient mecha electrical harness" + + emp_resistance = 0 + max_integrity = 30 + + relative_size = 10 + charge_cost_mod = 0.6 diff --git a/code/game/mecha/components/hull.dm b/code/game/mecha/components/hull.dm new file mode 100644 index 0000000000..16d01ad92c --- /dev/null +++ b/code/game/mecha/components/hull.dm @@ -0,0 +1,33 @@ + +/obj/item/mecha_parts/component/hull + name = "mecha hull" + icon = 'icons/mecha/mech_component.dmi' + icon_state = "hull" + w_class = ITEMSIZE_HUGE + origin_tech = list(TECH_DATA = 2, TECH_ENGINEERING = 2) + + component_type = MECH_HULL + + emp_resistance = 0 // Amount of emp 'levels' removed. + + required_type = null // List, if it exists. Exosuits meant to use the component. + + integrity_danger_mod = 0.5 // Multiplier for comparison to max_integrity before problems start. + max_integrity = 50 + + internal_damage_flag = MECHA_INT_FIRE + + step_delay = 2 + +/obj/item/mecha_parts/component/hull/durable + name = "durable mecha hull" + + step_delay = 4 + integrity_danger_mod = 0.3 + max_integrity = 100 + +/obj/item/mecha_parts/component/hull/lightweight + name = "lightweight mecha hull" + + step_delay = 1 + integrity_danger_mod = 0.3 diff --git a/code/game/mecha/components/lifesupport.dm b/code/game/mecha/components/lifesupport.dm new file mode 100644 index 0000000000..d98cefda4d --- /dev/null +++ b/code/game/mecha/components/lifesupport.dm @@ -0,0 +1,28 @@ + +/obj/item/mecha_parts/component/gas + name = "mecha life-support" + icon = 'icons/mecha/mech_component.dmi' + icon_state = "lifesupport" + w_class = ITEMSIZE_HUGE + origin_tech = list(TECH_DATA = 2, TECH_ENGINEERING = 2) + + component_type = MECH_GAS + + emp_resistance = 1 + + integrity_danger_mod = 0.4 + max_integrity = 40 + + step_delay = 0 + + relative_size = 20 + + internal_damage_flag = MECHA_INT_TANK_BREACH + +/obj/item/mecha_parts/component/gas/reinforced + name = "reinforced mecha life-support" + + emp_resistance = 2 + max_integrity = 80 + + relative_size = 40 diff --git a/code/game/mecha/equipment/mecha_equipment.dm b/code/game/mecha/equipment/mecha_equipment.dm index 678ac64910..617af4849f 100644 --- a/code/game/mecha/equipment/mecha_equipment.dm +++ b/code/game/mecha/equipment/mecha_equipment.dm @@ -28,6 +28,8 @@ var/ready_sound = 'sound/mecha/mech_reload_default.ogg' //Sound to play once the fire delay passed. var/enable_special = FALSE // Will the tool do its special? + var/step_delay = 0 // Does the component slow/speed up the suit? + /obj/item/mecha_parts/mecha_equipment/proc/do_after_cooldown(target=1) sleep(equip_cooldown) set_ready_state(1) @@ -273,3 +275,6 @@ /obj/item/mecha_parts/mecha_equipment/proc/MoveAction() //Allows mech equipment to do an action upon the mech moving return + +/obj/item/mecha_parts/mecha_equipment/proc/get_step_delay() // Equipment returns its slowdown or speedboost. + return step_delay diff --git a/code/game/mecha/equipment/tools/armor_melee.dm b/code/game/mecha/equipment/tools/armor_melee.dm index 8390a2cc52..085148d938 100644 --- a/code/game/mecha/equipment/tools/armor_melee.dm +++ b/code/game/mecha/equipment/tools/armor_melee.dm @@ -9,6 +9,8 @@ var/deflect_coeff = 1.15 var/damage_coeff = 0.8 + step_delay = 1 + equip_type = EQUIP_HULL /obj/item/mecha_parts/mecha_equipment/anticcw_armor_booster/get_equip_info() diff --git a/code/game/mecha/equipment/tools/armor_ranged.dm b/code/game/mecha/equipment/tools/armor_ranged.dm index 4fb3aac32b..5b8d6d8172 100644 --- a/code/game/mecha/equipment/tools/armor_ranged.dm +++ b/code/game/mecha/equipment/tools/armor_ranged.dm @@ -9,6 +9,8 @@ var/deflect_coeff = 1.15 var/damage_coeff = 0.8 + step_delay = 2 + equip_type = EQUIP_HULL /obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster/handle_projectile_contact(var/obj/item/projectile/Proj, var/inc_damage) diff --git a/code/game/mecha/equipment/tools/repair_droid.dm b/code/game/mecha/equipment/tools/repair_droid.dm index f4f9696aa5..7cd8ebd9ab 100644 --- a/code/game/mecha/equipment/tools/repair_droid.dm +++ b/code/game/mecha/equipment/tools/repair_droid.dm @@ -11,6 +11,8 @@ var/icon/droid_overlay var/list/repairable_damage = list(MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH) + step_delay = 1 + equip_type = EQUIP_HULL /obj/item/mecha_parts/mecha_equipment/repair_droid/New() @@ -79,8 +81,23 @@ RD.chassis.clearInternalDamage(int_dam_flag) repaired = 1 break - if(health_boost<0 || RD.chassis.health < initial(RD.chassis.health)) + + var/obj/item/mecha_parts/component/AC = RD.chassis.internal_components[MECH_ARMOR] + var/obj/item/mecha_parts/component/HC = RD.chassis.internal_components[MECH_HULL] + + var/damaged_armor = AC.integrity < AC.max_integrity + + var/damaged_hull = HC.integrity < HC.max_integrity + + if(health_boost<0 || RD.chassis.health < initial(RD.chassis.health) || damaged_armor || damaged_hull) RD.chassis.health += min(health_boost, initial(RD.chassis.health)-RD.chassis.health) + + if(AC) + AC.adjust_integrity(round(health_boost * 0.5, 0.5)) + + if(HC) + HC.adjust_integrity(round(health_boost * 0.5, 0.5)) + repaired = 1 if(repaired) if(RD.chassis.use_power(RD.energy_drain)) diff --git a/code/game/mecha/equipment/tools/shield.dm b/code/game/mecha/equipment/tools/shield.dm index 1ceab07945..453c2ba9da 100644 --- a/code/game/mecha/equipment/tools/shield.dm +++ b/code/game/mecha/equipment/tools/shield.dm @@ -7,6 +7,8 @@ energy_drain = 20 range = 0 + step_delay = 1 + var/obj/item/shield_projector/line/exosuit/my_shield = null var/my_shield_type = /obj/item/shield_projector/line/exosuit var/icon/drone_overlay @@ -66,9 +68,11 @@ my_shield.attack_self(chassis.occupant) if(my_shield.active) set_ready_state(0) + step_delay = 4 log_message("Activated.") else set_ready_state(1) + step_delay = 1 log_message("Deactivated.") /obj/item/mecha_parts/mecha_equipment/combat_shield/Topic(href, href_list) diff --git a/code/game/mecha/equipment/tools/shield_omni.dm b/code/game/mecha/equipment/tools/shield_omni.dm index 1d230c4deb..aae68ef4cd 100644 --- a/code/game/mecha/equipment/tools/shield_omni.dm +++ b/code/game/mecha/equipment/tools/shield_omni.dm @@ -9,6 +9,8 @@ energy_drain = OMNI_SHIELD_DRAIN range = 0 + step_delay = 1 + var/obj/item/shield_projector/shields = null var/shield_type = /obj/item/shield_projector/rectangle/mecha @@ -42,9 +44,11 @@ shields.set_on(!shields.active) if(shields.active) set_ready_state(0) + step_delay = 4 log_message("Activated.") else set_ready_state(1) + step_delay = 1 log_message("Deactivated.") /obj/item/mecha_parts/mecha_equipment/omni_shield/Topic(href, href_list) diff --git a/code/game/mecha/equipment/tools/speedboost.dm b/code/game/mecha/equipment/tools/speedboost.dm index 6e7ad06f69..7ffbeaee38 100644 --- a/code/game/mecha/equipment/tools/speedboost.dm +++ b/code/game/mecha/equipment/tools/speedboost.dm @@ -7,6 +7,9 @@ equip_type = EQUIP_HULL + var/slowdown_multiplier = 0.75 // How much does the exosuit multiply its slowdown by if it's the proper type? + +/* /obj/item/mecha_parts/mecha_equipment/speedboost/attach(obj/mecha/M as obj) ..() if(enable_special) @@ -14,6 +17,13 @@ else chassis.step_in = 6 // Improper parts slow the mech down return +*/ + +/obj/item/mecha_parts/mecha_equipment/speedboost/get_step_delay() + if(enable_special) + return -1 + else + return 3 /obj/item/mecha_parts/mecha_equipment/speedboost/detach() chassis.step_in = initial(chassis.step_in) diff --git a/code/game/mecha/equipment/weapons/ballistic/mortar.dm b/code/game/mecha/equipment/weapons/ballistic/mortar.dm index 86928c9da5..c192d0fc9b 100644 --- a/code/game/mecha/equipment/weapons/ballistic/mortar.dm +++ b/code/game/mecha/equipment/weapons/ballistic/mortar.dm @@ -11,6 +11,8 @@ projectile = /obj/item/projectile/arc/fragmentation/mortar projectile_energy_cost = 600 + step_delay = 2 + origin_tech = list(TECH_MATERIAL = 4, TECH_COMBAT = 5, TECH_ILLEGAL = 3) /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/mortar/action_checks(atom/target) diff --git a/code/game/mecha/equipment/weapons/ballistic/shotgun.dm b/code/game/mecha/equipment/weapons/ballistic/shotgun.dm index d2232d0004..e6b12d8ebd 100644 --- a/code/game/mecha/equipment/weapons/ballistic/shotgun.dm +++ b/code/game/mecha/equipment/weapons/ballistic/shotgun.dm @@ -11,6 +11,8 @@ deviation = 0.7 projectile_energy_cost = 25 + step_delay = 2 + origin_tech = list(TECH_MATERIAL = 3, TECH_COMBAT = 4) /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/scattershot/rigged diff --git a/code/game/mecha/equipment/weapons/energy/laser.dm b/code/game/mecha/equipment/weapons/energy/laser.dm index 8bdcbcf71f..0dfcbd1328 100644 --- a/code/game/mecha/equipment/weapons/energy/laser.dm +++ b/code/game/mecha/equipment/weapons/energy/laser.dm @@ -52,6 +52,8 @@ projectile = /obj/item/projectile/beam/heavylaser fire_sound = 'sound/weapons/lasercannonfire.ogg' + step_delay = 2 + origin_tech = list(TECH_MATERIAL = 3, TECH_COMBAT = 4, TECH_MAGNET = 4) /obj/item/mecha_parts/mecha_equipment/weapon/energy/laser/heavy/rigged diff --git a/code/game/mecha/equipment/weapons/explosive/missile.dm b/code/game/mecha/equipment/weapons/explosive/missile.dm index 1c14a8c1dd..df8cf0c3bb 100644 --- a/code/game/mecha/equipment/weapons/explosive/missile.dm +++ b/code/game/mecha/equipment/weapons/explosive/missile.dm @@ -2,6 +2,8 @@ var/missile_speed = 2 var/missile_range = 30 + step_delay = 2 + /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/Fire(atom/movable/AM, atom/target, turf/aimloc) AM.throw_at(target,missile_range, missile_speed, chassis) @@ -19,6 +21,8 @@ missile_range = 15 required_type = /obj/mecha //Why restrict it to just mining or combat mechs? + step_delay = 0 + equip_type = EQUIP_UTILITY /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack/flare/Fire(atom/movable/AM, atom/target, turf/aimloc) diff --git a/code/game/mecha/equipment/weapons/fire/flamethrower.dm b/code/game/mecha/equipment/weapons/fire/flamethrower.dm index f97f598e24..532c35a6b4 100644 --- a/code/game/mecha/equipment/weapons/fire/flamethrower.dm +++ b/code/game/mecha/equipment/weapons/fire/flamethrower.dm @@ -7,6 +7,8 @@ energy_drain = 30 + step_delay = 2 + projectile = /obj/item/projectile/bullet/incendiary/flamethrower/large fire_sound = 'sound/weapons/towelwipe.ogg' diff --git a/code/game/mecha/equipment/weapons/fire/incendiary.dm b/code/game/mecha/equipment/weapons/fire/incendiary.dm index 3d3c174839..ff49d42016 100644 --- a/code/game/mecha/equipment/weapons/fire/incendiary.dm +++ b/code/game/mecha/equipment/weapons/fire/incendiary.dm @@ -16,3 +16,5 @@ projectile_energy_cost = 40 fire_cooldown = 3 origin_tech = list(TECH_MATERIAL = 4, TECH_COMBAT = 5, TECH_PHORON = 2, TECH_ILLEGAL = 1) + + step_delay = 1 diff --git a/code/game/mecha/equipment/weapons/weapons.dm b/code/game/mecha/equipment/weapons/weapons.dm index 355ce629ac..9dccf551c9 100644 --- a/code/game/mecha/equipment/weapons/weapons.dm +++ b/code/game/mecha/equipment/weapons/weapons.dm @@ -12,6 +12,8 @@ var/auto_rearm = 0 //Does the weapon reload itself after each shot? required_type = list(/obj/mecha/combat, /obj/mecha/working/hoverpod/combatpod) + step_delay = 1 + equip_type = EQUIP_WEAPON /obj/item/mecha_parts/mecha_equipment/weapon/action_checks(atom/target) diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index ca2bcab7b9..dec98745c8 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -7,6 +7,11 @@ #define MELEE 1 #define RANGED 2 +#define MECHA_OPERATING 0 +#define MECHA_BOLTS_SECURED 1 +#define MECHA_PANEL_LOOSE 2 +#define MECHA_CELL_OPEN 3 +#define MECHA_CELL_OUT 4 #define MECH_FACTION_NT "nano" #define MECH_FACTION_SYNDI "syndi" @@ -48,7 +53,7 @@ var/fail_penetration_value = 0.66 //By how much failing to penetrate reduces your shit. 66% by default. var/obj/item/weapon/cell/cell - var/state = 0 + var/state = MECHA_OPERATING var/list/log = new var/last_message = 0 var/add_req_access = 1 @@ -108,6 +113,24 @@ var/max_universal_equip = 2 var/max_special_equip = 1 + var/list/starting_equipment = null // List containing starting tools. + +// Mech Components, similar to Cyborg, but Bigger. + var/list/internal_components = list( + MECH_HULL = null, + MECH_ACTUATOR = null, + MECH_ARMOR = null, + MECH_GAS = null, + MECH_ELECTRIC = null + ) + var/list/starting_components = list( + /obj/item/mecha_parts/component/hull, + /obj/item/mecha_parts/component/actuator, + /obj/item/mecha_parts/component/armor, + /obj/item/mecha_parts/component/gas, + /obj/item/mecha_parts/component/electrical + ) + //Working exosuit vars var/list/cargo = list() var/cargo_capacity = 3 @@ -139,7 +162,7 @@ var/phasing_possible = 0 //This is to allow phasing. var/can_phase = TRUE //This is an internal check during the relevant procs. var/phasing_energy_drain = 200 - + var/switch_dmg_type_possible = 0 //Can you switch damage type? It is mostly for the Phazon and its children. var/smoke_possible = 0 @@ -148,6 +171,8 @@ var/smoke_cooldown = 100 //How long you have between uses. var/datum/effect/effect/system/smoke_spread/smoke_system = new + var/cloak_possible = FALSE // Can this exosuit innately cloak? + ////All of those are for the HUD buttons in the top left. See Grant and Remove procs in mecha_actions. var/datum/action/innate/mecha/mech_eject/eject_action = new @@ -164,10 +189,20 @@ var/datum/action/innate/mecha/mech_cycle_equip/cycle_action = new var/datum/action/innate/mecha/mech_switch_damtype/switch_damtype_action = new var/datum/action/innate/mecha/mech_toggle_phasing/phasing_action = new + var/datum/action/innate/mecha/mech_toggle_cloaking/cloak_action = new var/weapons_only_cycle = FALSE //So combat mechs don't switch to their equipment at times. +/obj/mecha/Initialize() + ..() + for(var/path in starting_components) + var/obj/item/mecha_parts/component/C = new path(src) + C.attach(src) + if(starting_equipment && LAZYLEN(starting_equipment)) + for(var/path in starting_equipment) + var/obj/item/mecha_parts/mecha_equipment/ME = new path(src) + ME.attach(src) /obj/mecha/drain_power(var/drain_check) @@ -245,6 +280,15 @@ else E.forceMove(loc) E.destroy() + + for(var/slot in internal_components) + var/obj/item/mecha_parts/component/C = internal_components[slot] + if(istype(C)) + C.damage_part(rand(10, 20)) + C.detach() + WR.crowbar_salvage += C + C.forceMove(WR) + if(cell) WR.crowbar_salvage += cell cell.forceMove(WR) @@ -256,6 +300,11 @@ for(var/obj/item/mecha_parts/mecha_equipment/E in equipment) E.detach(loc) E.destroy() + for(var/slot in internal_components) + var/obj/item/mecha_parts/component/C = internal_components[slot] + if(istype(C)) + C.detach() + qdel(C) if(cell) qdel(cell) if(internal_tank) @@ -351,6 +400,26 @@ /obj/mecha/examine(mob/user) . = ..() + + var/obj/item/mecha_parts/component/armor/AC = internal_components[MECH_ARMOR] + + var/obj/item/mecha_parts/component/hull/HC = internal_components[MECH_HULL] + + if(AC) + . += "It has [AC] attached. [AC.get_efficiency()<0.5?"It is severely damaged.":""]" + else + . += "It has no armor plating." + + if(HC) + if(!AC || AC.get_efficiency() < 0.7) + . += "It has [HC] attached. [HC.get_efficiency()<0.5?"It is severely damaged.":""]" + else + . += "You cannot tell what type of hull it has." + + else + . += "It does not seem to have a completed hull." + + var/integrity = health/initial(health)*100 switch(integrity) if(85 to 100) @@ -536,6 +605,12 @@ user.forceMove(get_turf(src)) to_chat(user, "You climb out from [src]") return 0 + + var/obj/item/mecha_parts/component/hull/HC = internal_components[MECH_HULL] + if(!HC) + occupant_message("You can't operate an exosuit that doesn't have a hull!") + return + if(connected_port) if(world.time - last_message > 20) src.occupant_message("Unable to move while connected to the air system port") @@ -562,6 +637,44 @@ return call((proc_res["dyndomove"]||src), "dyndomove")(direction) +/obj/mecha/proc/get_step_delay() + var/tally = 0 + + if(overload) + tally = min(1, round(step_in/2)) + + for(var/slot in internal_components) + var/obj/item/mecha_parts/component/C = internal_components[slot] + if(C && C.get_step_delay()) + tally += C.get_step_delay() + + for(var/obj/item/mecha_parts/mecha_equipment/ME in equipment) + if(ME.get_step_delay()) + tally += ME.get_step_delay() + + var/obj/item/mecha_parts/component/actuator/actuator = internal_components[MECH_ACTUATOR] + + if(!actuator) // Relying purely on hydraulic pumps. You're going nowhere fast. + tally = 2 SECONDS + + return tally + + tally += 0.5 SECONDS * (1 - actuator.get_efficiency()) // Damaged actuators run slower, slowing as damage increases beyond its threshold. + + if(strafing) + tally = round(tally * actuator.strafing_multiplier) + + for(var/obj/item/mecha_parts/mecha_equipment/ME in equipment) + if(istype(ME, /obj/item/mecha_parts/mecha_equipment/speedboost)) + var/obj/item/mecha_parts/mecha_equipment/speedboost/SB = ME + for(var/path in ME.required_type) + if(istype(src, path)) + tally = round(tally * SB.slowdown_multiplier) + break + break + + return max(1, round(tally)) + /obj/mecha/proc/dyndomove(direction) if(!can_move) return 0 @@ -600,7 +713,6 @@ health-- if(health < initial(health) - initial(health)/3) overload = 0 - step_in = initial(step_in) step_energy_drain = initial(step_energy_drain) src.occupant_message("Leg actuators damage threshold exceded. Disabling overload.") @@ -656,7 +768,7 @@ if(!src.check_for_support()) src.pr_inertial_movement.start(list(src,direction)) src.log_message("Movement control lost. Inertial movement started.") - if(do_after(step_in)) + if(do_after(get_step_delay())) can_move = 1 return 1 return 0 @@ -708,7 +820,7 @@ flick("[initial_icon]-phase", src) src.loc = get_step(src,src.dir) src.use_power(phasing_energy_drain) - sleep(step_in*3) + sleep(get_step_delay() * 3) can_phase = TRUE occupant_message("Phazed.") . = ..(obstacle) @@ -785,16 +897,57 @@ update_damage_alerts() if(amount) var/damage = absorbDamage(amount,type) + + damage = components_handle_damage(damage,type) + health -= damage + update_health() log_append_to_last("Took [damage] points of damage. Damage type: \"[type]\".",1) return +/obj/mecha/proc/components_handle_damage(var/damage, var/type = BRUTE) + var/obj/item/mecha_parts/component/armor/AC = internal_components[MECH_ARMOR] + + if(AC) + var/armor_efficiency = AC.get_efficiency() + var/damage_change = armor_efficiency * (damage * 0.5) * AC.damage_absorption[type] + AC.damage_part(damage_change, type) + damage -= damage_change + + var/obj/item/mecha_parts/component/hull/HC = internal_components[MECH_HULL] + + if(HC) + if(HC.integrity) + var/hull_absorb = round(rand(5, 10) / 10, 0.1) * damage + HC.damage_part(hull_absorb, type) + damage -= hull_absorb + + for(var/obj/item/mecha_parts/component/C in (internal_components - list(MECH_HULL, MECH_ARMOR))) + if(prob(C.relative_size)) + var/damage_part_amt = round(damage / 4, 0.1) + C.damage_part(damage_part_amt) + damage -= damage_part_amt + + return damage + +/obj/mecha/proc/get_damage_absorption() + var/obj/item/mecha_parts/component/armor/AC = internal_components[MECH_ARMOR] + + if(!istype(AC)) + return + + else + if(AC.get_efficiency() > 0.25) + return AC.damage_absorption + + return + /obj/mecha/proc/absorbDamage(damage,damage_type) return call((proc_res["dynabsorbdamage"]||src), "dynabsorbdamage")(damage,damage_type) /obj/mecha/proc/dynabsorbdamage(damage,damage_type) - return damage*(listgetindex(damage_absorption,damage_type) || 1) + return damage*(listgetindex(get_damage_absorption(),damage_type) || 1) /obj/mecha/airlock_crush(var/crush_damage) ..() @@ -814,14 +967,24 @@ if(user == occupant) show_radial_occupant(user) return - + user.setClickCooldown(user.get_attack_speed()) src.log_message("Attack by hand/paw. Attacker - [user].",1) + var/obj/item/mecha_parts/component/armor/ArmC = internal_components[MECH_ARMOR] + + var/temp_deflect_chance = deflect_chance + + if(!ArmC) + temp_deflect_chance = 1 + + else + temp_deflect_chance = round(ArmC.get_efficiency() * ArmC.deflect_chance + (defence_mode ? 25 : 0)) + if(istype(user,/mob/living/carbon/human)) var/mob/living/carbon/human/H = user if(H.species.can_shred(user)) - if(!prob(src.deflect_chance)) + if(!prob(temp_deflect_chance)) src.take_damage(15) //The take_damage() proc handles armor values if(prob(25)) //Why would they get free internal damage. At least make it a bit RNG. src.check_for_internal_damage(list(MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST)) @@ -838,7 +1001,7 @@ user.visible_message("\The [user] hits \the [src]. Nothing happens.","You hit \the [src] with no visible effect.") src.log_append_to_last("Armor saved.") return - else if ((HULK in user.mutations) && !prob(src.deflect_chance)) + else if ((HULK in user.mutations) && !prob(temp_deflect_chance)) src.take_damage(15) //The take_damage() proc handles armor values if(prob(25)) //Hulks punch hard but lets not give them consistent internal damage. src.check_for_internal_damage(list(MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST)) @@ -856,11 +1019,30 @@ //I think this is relative to throws. /obj/mecha/proc/dynhitby(atom/movable/A) + var/obj/item/mecha_parts/component/armor/ArmC = internal_components[MECH_ARMOR] + + var/temp_deflect_chance = deflect_chance + var/temp_damage_minimum = damage_minimum + var/temp_minimum_penetration = minimum_penetration + var/temp_fail_penetration_value = fail_penetration_value + + if(!ArmC) + temp_deflect_chance = 0 + temp_damage_minimum = 0 + temp_minimum_penetration = 0 + temp_fail_penetration_value = 1 + + else + temp_deflect_chance = round(ArmC.get_efficiency() * ArmC.deflect_chance + (defence_mode ? 25 : 0)) + temp_damage_minimum = round(ArmC.get_efficiency() * ArmC.damage_minimum) + temp_minimum_penetration = round(ArmC.get_efficiency() * ArmC.minimum_penetration) + temp_fail_penetration_value = round(ArmC.get_efficiency() * ArmC.fail_penetration_value) + if(istype(A, /obj/item/mecha_parts/mecha_tracking)) A.forceMove(src) src.visible_message("The [A] fastens firmly to [src].") return - if(prob(src.deflect_chance) || istype(A, /mob)) + if(prob(temp_deflect_chance) || istype(A, /mob)) src.occupant_message("\The [A] bounces off the armor.") src.visible_message("\The [A] bounces off \the [src] armor") src.log_append_to_last("Armor saved.") @@ -873,18 +1055,18 @@ var/pass_damage = O.throwforce var/pass_damage_reduc_mod - if(pass_damage <= damage_minimum)//Too little to go through. + if(pass_damage <= temp_damage_minimum)//Too little to go through. src.occupant_message("\The [A] bounces off the armor.") src.visible_message("\The [A] bounces off \the [src] armor") return - else if(O.armor_penetration < minimum_penetration) //If you don't have enough pen, you won't do full damage + else if(O.armor_penetration < temp_minimum_penetration) //If you don't have enough pen, you won't do full damage src.occupant_message("\The [A] struggles to bypass \the [src] armor.") src.visible_message("\The [A] struggles to bypass \the [src] armor") - pass_damage_reduc_mod = fail_penetration_value //This will apply to reduce damage to 2/3 or 66% by default + pass_damage_reduc_mod = temp_fail_penetration_value //This will apply to reduce damage to 2/3 or 66% by default else - src.occupant_message("\The [A] manages to pierces \the [src] armor.") - src.visible_message("\The [A] manages to pierces \the [src] armor") + src.occupant_message("\The [A] manages to pierce \the [src] armor.") +// src.visible_message("\The [A] manages to pierce \the [src] armor") pass_damage_reduc_mod = 1 @@ -911,7 +1093,26 @@ return /obj/mecha/proc/dynbulletdamage(var/obj/item/projectile/Proj) - if(prob(src.deflect_chance)) + var/obj/item/mecha_parts/component/armor/ArmC = internal_components[MECH_ARMOR] + + var/temp_deflect_chance = deflect_chance + var/temp_damage_minimum = damage_minimum + var/temp_minimum_penetration = minimum_penetration + var/temp_fail_penetration_value = fail_penetration_value + + if(!ArmC) + temp_deflect_chance = 0 + temp_damage_minimum = 0 + temp_minimum_penetration = 0 + temp_fail_penetration_value = 1 + + else + temp_deflect_chance = round(ArmC.get_efficiency() * ArmC.deflect_chance + (defence_mode ? 25 : 0)) + temp_damage_minimum = round(ArmC.get_efficiency() * ArmC.damage_minimum) + temp_minimum_penetration = round(ArmC.get_efficiency() * ArmC.minimum_penetration) + temp_fail_penetration_value = round(ArmC.get_efficiency() * ArmC.fail_penetration_value) + + if(prob(temp_deflect_chance)) src.occupant_message("The armor deflects incoming projectile.") src.visible_message("The [src.name] armor deflects the projectile") src.log_append_to_last("Armor saved.") @@ -930,19 +1131,19 @@ for(var/obj/item/mecha_parts/mecha_equipment/ME in equipment) pass_damage = ME.handle_projectile_contact(Proj, pass_damage) - if(pass_damage < damage_minimum)//too pathetic to really damage you. + if(pass_damage < temp_damage_minimum)//too pathetic to really damage you. src.occupant_message("The armor deflects incoming projectile.") src.visible_message("The [src.name] armor deflects\the [Proj]") return - else if(Proj.armor_penetration < minimum_penetration) //If you don't have enough pen, you won't do full damage + else if(Proj.armor_penetration < temp_minimum_penetration) //If you don't have enough pen, you won't do full damage src.occupant_message("\The [Proj] struggles to pierce \the [src] armor.") src.visible_message("\The [Proj] struggles to pierce \the [src] armor") - pass_damage_reduc_mod = fail_penetration_value //This will apply to reduce damage to 2/3 or 66% by default + pass_damage_reduc_mod = temp_fail_penetration_value //This will apply to reduce damage to 2/3 or 66% by default else //You go through completely because you use AP. Nice. src.occupant_message("\The [Proj] manages to pierce \the [src] armor.") - src.visible_message("\The [Proj] manages to pierce \the [src] armor") +// src.visible_message("\The [Proj] manages to pierce \the [src] armor") pass_damage_reduc_mod = 1 pass_damage = (pass_damage_reduc_mod*pass_damage)//Apply damage reduction before usage. @@ -961,7 +1162,7 @@ Proj.attack_mob(src.occupant, distance) hit_occupant = 0 else - if(pass_damage > internal_damage_minimum) //Only decently painful attacks trigger a chance of mech damage. + if(pass_damage > internal_damage_minimum) //Only decently painful attacks trigger a chance of mech damage. src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT), 1) Proj.penetrating-- @@ -974,24 +1175,34 @@ //This refer to whenever you are caught in an explosion. /obj/mecha/ex_act(severity) + var/obj/item/mecha_parts/component/armor/ArmC = internal_components[MECH_ARMOR] + + var/temp_deflect_chance = deflect_chance + + if(!ArmC) + temp_deflect_chance = 0 + + else + temp_deflect_chance = round(ArmC.get_efficiency() * ArmC.deflect_chance + (defence_mode ? 25 : 0)) + src.log_message("Affected by explosion of severity: [severity].",1) - if(prob(src.deflect_chance)) + if(prob(temp_deflect_chance)) severity++ src.log_append_to_last("Armor saved, changing severity to [severity].") switch(severity) if(1.0) - qdel(src) + src.take_damage(initial(src.health), "bomb") if(2.0) if (prob(30)) - qdel(src) + src.take_damage(initial(src.health), "bomb") else - src.take_damage(initial(src.health)/2) //The take_damage() proc handles armor values + src.take_damage(initial(src.health)/2, "bomb") src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT),1) if(3.0) if (prob(5)) qdel(src) else - src.take_damage(initial(src.health)/5) //The take_damage() proc handles armor values + src.take_damage(initial(src.health)/5, "bomb") src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT),1) return @@ -1038,20 +1249,39 @@ src.log_message("Attacked by [W]. Attacker - [user]") var/pass_damage_reduc_mod //Modifer for failing to bring AP. - if(prob(src.deflect_chance)) //Does your attack get deflected outright. + var/obj/item/mecha_parts/component/armor/ArmC = internal_components[MECH_ARMOR] + + var/temp_deflect_chance = deflect_chance + var/temp_damage_minimum = damage_minimum + var/temp_minimum_penetration = minimum_penetration + var/temp_fail_penetration_value = fail_penetration_value + + if(!ArmC) + temp_deflect_chance = 0 + temp_damage_minimum = 0 + temp_minimum_penetration = 0 + temp_fail_penetration_value = 1 + + else + temp_deflect_chance = round(ArmC.get_efficiency() * ArmC.deflect_chance + (defence_mode ? 25 : 0)) + temp_damage_minimum = round(ArmC.get_efficiency() * ArmC.damage_minimum) + temp_minimum_penetration = round(ArmC.get_efficiency() * ArmC.minimum_penetration) + temp_fail_penetration_value = round(ArmC.get_efficiency() * ArmC.fail_penetration_value) + + if(prob(temp_deflect_chance)) //Does your attack get deflected outright. src.occupant_message("\The [W] bounces off [src.name].") to_chat(user, "\The [W] bounces off [src.name].") src.log_append_to_last("Armor saved.") - else if(W.force < damage_minimum) //Is your attack too PATHETIC to do anything. 3 damage to a person shouldn't do anything to a mech. + else if(W.force < temp_damage_minimum) //Is your attack too PATHETIC to do anything. 3 damage to a person shouldn't do anything to a mech. src.occupant_message("\The [W] bounces off the armor.") src.visible_message("\The [W] bounces off \the [src] armor") return - else if(W.armor_penetration < minimum_penetration) //If you don't have enough pen, you won't do full damage + else if(W.armor_penetration < temp_minimum_penetration) //If you don't have enough pen, you won't do full damage src.occupant_message("\The [W] struggles to bypass \the [src] armor.") src.visible_message("\The [W] struggles to bypass \the [src] armor") - pass_damage_reduc_mod = fail_penetration_value //This will apply to reduce damage to 2/3 or 66% by default + pass_damage_reduc_mod = temp_fail_penetration_value //This will apply to reduce damage to 2/3 or 66% by default else pass_damage_reduc_mod = 1 //Just making sure. @@ -1080,6 +1310,11 @@ to_chat(user, "[src]-MMI interface initialization failed.") return + if(istype(W, /obj/item/device/robotanalyzer)) + var/obj/item/device/robotanalyzer/RA = W + RA.do_scan(src, user) + return + if(istype(W, /obj/item/mecha_parts/mecha_equipment)) var/obj/item/mecha_parts/mecha_equipment/E = W spawn() @@ -1090,6 +1325,20 @@ else to_chat(user, "You were unable to attach [W] to [src]") return + + if(istype(W, /obj/item/mecha_parts/component) && state == MECHA_CELL_OUT) + var/obj/item/mecha_parts/component/MC = W + spawn() + if(MC.attach(src)) + user.drop_item() + MC.forceMove(src) + user.visible_message("[user] installs \the [W] in \the [src]", "You install \the [W] in \the [src].") + return + + if(istype(W, /obj/item/weapon/card/robot)) + var/obj/item/weapon/card/robot/RoC = W + return attackby(RoC.dummy_card, user) + if(istype(W, /obj/item/weapon/card/id)||istype(W, /obj/item/device/pda)) if(add_req_access || maint_access) if(internals_access_allowed(usr)) @@ -1106,23 +1355,39 @@ else to_chat(user, "Maintenance protocols disabled by operator.") else if(W.is_wrench()) - if(state==1) - state = 2 + if(state==MECHA_BOLTS_SECURED) + state = MECHA_PANEL_LOOSE to_chat(user, "You undo the securing bolts.") - else if(state==2) - state = 1 + else if(state==MECHA_PANEL_LOOSE) + state = MECHA_BOLTS_SECURED to_chat(user, "You tighten the securing bolts.") return else if(W.is_crowbar()) - if(state==2) - state = 3 + if(state==MECHA_PANEL_LOOSE) + state = MECHA_CELL_OPEN to_chat(user, "You open the hatch to the power unit") - else if(state==3) - state=2 + else if(state==MECHA_CELL_OPEN) + state=MECHA_PANEL_LOOSE to_chat(user, "You close the hatch to the power unit") + else if(state==MECHA_CELL_OUT) + var/list/removable_components = list() + for(var/slot in internal_components) + var/obj/item/mecha_parts/component/MC = internal_components[slot] + if(istype(MC)) + removable_components[MC.name] = MC + else + to_chat(user, "\The [src] appears to be missing \the [slot].") + + var/remove = input(user, "Which component do you want to pry out?", "Remove Component") as null|anything in removable_components + if(!remove) + return + + var/obj/item/mecha_parts/component/RmC = removable_components[remove] + RmC.detach() + return else if(istype(W, /obj/item/stack/cable_coil)) - if(state == 3 && hasInternalDamage(MECHA_INT_SHORT_CIRCUIT)) + if(state >= MECHA_CELL_OPEN && hasInternalDamage(MECHA_INT_SHORT_CIRCUIT)) var/obj/item/stack/cable_coil/CC = W if(CC.use(2)) clearInternalDamage(MECHA_INT_SHORT_CIRCUIT) @@ -1134,19 +1399,19 @@ if(hasInternalDamage(MECHA_INT_TEMP_CONTROL)) clearInternalDamage(MECHA_INT_TEMP_CONTROL) to_chat(user, "You repair the damaged temperature controller.") - else if(state==3 && src.cell) + else if(state==MECHA_CELL_OPEN && src.cell) src.cell.forceMove(src.loc) src.cell = null - state = 4 + state = MECHA_CELL_OUT to_chat(user, "You unscrew and pry out the powercell.") src.log_message("Powercell removed") - else if(state==4 && src.cell) - state=3 + else if(state==MECHA_CELL_OUT && src.cell) + state=MECHA_CELL_OPEN to_chat(user, "You screw the cell in place") return else if(istype(W, /obj/item/device/multitool)) - if(state>=3 && src.occupant) + if(state>=MECHA_CELL_OPEN && src.occupant) to_chat(user, "You attempt to eject the pilot using the maintenance controls.") if(src.occupant.stat) src.go_out() @@ -1158,7 +1423,7 @@ return else if(istype(W, /obj/item/weapon/cell)) - if(state==4) + if(state==MECHA_CELL_OUT) if(!src.cell) to_chat(user, "You install the powercell") user.drop_item() @@ -1191,6 +1456,28 @@ user.visible_message("[user] attaches [W] to [src].", "You attach [W] to [src]") return + else if(istype(W,/obj/item/stack/nanopaste)) + if(state >= MECHA_PANEL_LOOSE) + var/obj/item/stack/nanopaste/NP = W + + for(var/slot in internal_components) + var/obj/item/mecha_parts/component/C = internal_components[slot] + + if(C) + + if(C.integrity < C.max_integrity) + while(C.integrity < C.max_integrity && NP && do_after(user, 1 SECOND, src)) + if(NP.use(1)) + C.adjust_integrity(10) + + to_chat(user, "You repair damage to \the [C].") + + return + + else + to_chat(user, "You can't reach \the [src]'s internal components.") + return + else call((proc_res["dynattackby"]||src), "dynattackby")(W,user) /* @@ -1299,7 +1586,8 @@ return /obj/mecha/remove_air(amount) - if(use_internal_tank) + var/obj/item/mecha_parts/component/gas/GC = internal_components[MECH_GAS] + if(use_internal_tank && (GC && prob(GC.get_efficiency() * 100))) return cabin_air.remove(amount) else var/turf/T = get_turf(src) @@ -1314,7 +1602,8 @@ /obj/mecha/proc/return_pressure() . = 0 - if(use_internal_tank) + var/obj/item/mecha_parts/component/gas/GC = internal_components[MECH_GAS] + if(use_internal_tank && (GC && prob(GC.get_efficiency() * 100))) . = cabin_air.return_pressure() else var/datum/gas_mixture/t_air = get_turf_air() @@ -1325,7 +1614,8 @@ //skytodo: //No idea what you want me to do here, mate. /obj/mecha/proc/return_temperature() . = 0 - if(use_internal_tank) + var/obj/item/mecha_parts/component/gas/GC = internal_components[MECH_GAS] + if(use_internal_tank && (GC && prob(GC.get_efficiency() * 100))) . = cabin_air.temperature else var/datum/gas_mixture/t_air = get_turf_air() @@ -1380,13 +1670,17 @@ set category = "Exosuit Interface" set src = usr.loc set popup_menu = 0 - + if(!occupant) return - + if(usr != occupant) return - + + var/obj/item/mecha_parts/component/gas/GC = internal_components[MECH_GAS] + if(!GC) + return + for(var/turf/T in locs) var/obj/machinery/atmospherics/portables_connector/possible_port = locate(/obj/machinery/atmospherics/portables_connector) in T if(possible_port) @@ -1407,13 +1701,13 @@ set category = "Exosuit Interface" set src = usr.loc set popup_menu = 0 - + if(!occupant) return - + if(usr != occupant) return - + if(disconnect()) occupant_message("[name] disconnects from the port.") verbs -= /obj/mecha/verb/disconnect_from_port @@ -1449,6 +1743,16 @@ /obj/mecha/proc/internal_tank() if(usr!=src.occupant) return + + var/obj/item/mecha_parts/component/gas/GC = internal_components[MECH_GAS] + if(!GC) + to_chat(occupant, "The life support systems don't seem to respond.") + return + + if(!prob(GC.get_efficiency() * 100)) + to_chat(occupant, "\The [GC] shudders and barks, before returning to how it was before.") + return + use_internal_tank = !use_internal_tank src.occupant_message("Now taking air from [use_internal_tank?"internal airtank":"environment"].") src.log_message("Now taking air from [use_internal_tank?"internal airtank":"environment"].") @@ -1579,6 +1883,8 @@ verbs -= /obj/mecha/verb/toggle_phasing if(!switch_dmg_type_possible) verbs -= /obj/mecha/verb/switch_damtype + if(!cloak_possible) + verbs -= /obj/mecha/verb/toggle_cloak occupant.in_enclosed_vehicle = 1 //Useful for when you need to know if someone is in a mecho. update_cell_alerts() @@ -1697,8 +2003,13 @@ /obj/mecha/proc/internals_access_allowed(mob/living/carbon/human/H) - for(var/atom/ID in list(H.get_active_hand(), H.wear_id, H.belt)) - if(src.check_access(ID,src.internals_req_access)) + if(istype(H)) + for(var/atom/ID in list(H.get_active_hand(), H.wear_id, H.belt)) + if(src.check_access(ID,src.internals_req_access)) + return 1 + else if(istype(H, /mob/living/silicon/robot)) + var/mob/living/silicon/robot/R = H + if(src.check_access(R.idcard,src.internals_req_access)) return 1 return 0 @@ -1799,9 +2110,15 @@ var/tank_pressure = internal_tank ? round(internal_tank.return_pressure(),0.01) : "None" var/tank_temperature = internal_tank ? internal_tank.return_temperature() : "Unknown" var/cabin_pressure = round(return_pressure(),0.01) + + var/obj/item/mecha_parts/component/hull/HC = internal_components[MECH_HULL] + var/obj/item/mecha_parts/component/armor/AC = internal_components[MECH_ARMOR] + var/output = {"[report_internal_damage()] + Armor Integrity: [AC?"[round(AC.integrity / AC.max_integrity * 100, 0.1)]%":"ARMOR MISSING"]
+ Hull Integrity: [HC?"[round(HC.integrity / HC.max_integrity * 100, 0.1)]%":"HULL MISSING"]
[integrity<30?"DAMAGE LEVEL CRITICAL
":null] - Integrity: [integrity]%
+ Chassis Integrity: [integrity]%
Powercell charge: [isnull(cell_charge)?"No powercell installed":"[cell.percent()]%"]
Air source: [use_internal_tank?"Internal Airtank":"Environment"]
Airtank pressure: [tank_pressure]kPa
@@ -1892,15 +2209,15 @@ output += "Micro Utility Module: [W.name] Detach
" for(var/obj/item/mecha_parts/mecha_equipment/W in micro_weapon_equipment) output += "Micro Weapon Module: [W.name] Detach
" - output += {"Available hull slots: [max_hull_equip-hull_equipment.len]
- Available weapon slots: [max_weapon_equip-weapon_equipment.len]
- Available micro weapon slots: [max_micro_weapon_equip-micro_weapon_equipment.len]
- Available utility slots: [max_utility_equip-utility_equipment.len]
- Available micro utility slots: [max_micro_utility_equip-micro_utility_equipment.len]
- Available universal slots: [max_universal_equip-universal_equipment.len]
- Available special slots: [max_special_equip-special_equipment.len]
- - "} + output += {"Available hull slots: [max_hull_equip-hull_equipment.len]
+ Available weapon slots: [max_weapon_equip-weapon_equipment.len]
+ Available micro weapon slots: [max_micro_weapon_equip-micro_weapon_equipment.len]
+ Available utility slots: [max_utility_equip-utility_equipment.len]
+ Available micro utility slots: [max_micro_utility_equip-micro_utility_equipment.len]
+ Available universal slots: [max_universal_equip-universal_equipment.len]
+ Available special slots: [max_special_equip-special_equipment.len]
+ + "} return output /obj/mecha/proc/get_equipment_list() //outputs mecha equipment list in html @@ -2109,15 +2426,15 @@ if(!in_range(src, usr)) return var/mob/user = top_filter.getMob("user") if(user) - if(state==0) - state = 1 + if(state==MECHA_OPERATING) + state = MECHA_BOLTS_SECURED to_chat(user, "The securing bolts are now exposed.") - else if(state==1) - state = 0 + else if(state==MECHA_BOLTS_SECURED) + state = MECHA_OPERATING to_chat(user, "The securing bolts are now hidden.") output_maintenance_dialog(top_filter.getObj("id_card"),user) return - if(href_list["set_internal_tank_valve"] && state >=1) + if(href_list["set_internal_tank_valve"] && state >=MECHA_BOLTS_SECURED) if(!in_range(src, usr)) return var/mob/user = top_filter.getMob("user") if(user) @@ -2125,7 +2442,7 @@ if(new_pressure) internal_tank_valve = new_pressure to_chat(user, "The internal pressure valve has been set to [internal_tank_valve]kPa.") - if(href_list["remove_passenger"] && state >= 1) + if(href_list["remove_passenger"] && state >= MECHA_BOLTS_SECURED) var/mob/user = top_filter.getMob("user") var/list/passengers = list() for (var/obj/item/mecha_parts/mecha_equipment/tool/passenger/P in contents) @@ -2284,6 +2601,13 @@ /obj/mecha/proc/dynusepower(amount) update_cell_alerts() + var/obj/item/mecha_parts/component/electrical/EC = internal_components[MECH_ELECTRIC] + + if(EC) + amount = amount * (2 - EC.get_efficiency()) * EC.charge_cost_mod + else + amount *= 5 + if(get_charge()) cell.use(amount) return 1 @@ -2291,6 +2615,13 @@ /obj/mecha/proc/give_power(amount) update_cell_alerts() + var/obj/item/mecha_parts/component/electrical/EC = internal_components[MECH_ELECTRIC] + + if(!EC) + amount /= 4 + else + amount *= EC.get_efficiency() + if(!isnull(get_charge())) cell.give(amount) return 1 @@ -2306,6 +2637,19 @@ //This is for mobs mostly. /obj/mecha/attack_generic(var/mob/user, var/damage, var/attack_message) + var/obj/item/mecha_parts/component/armor/ArmC = internal_components[MECH_ARMOR] + + var/temp_deflect_chance = deflect_chance + var/temp_damage_minimum = damage_minimum + + if(!ArmC) + temp_deflect_chance = 1 + temp_damage_minimum = 0 + + else + temp_deflect_chance = round(ArmC.get_efficiency() * ArmC.deflect_chance + (defence_mode ? 25 : 0)) + temp_damage_minimum = round(ArmC.get_efficiency() * ArmC.damage_minimum) + user.setClickCooldown(user.get_attack_speed()) if(!damage) return 0 @@ -2313,14 +2657,14 @@ src.log_message("Attacked. Attacker - [user].",1) user.do_attack_animation(src) - if(prob(src.deflect_chance))//Deflected + if(prob(temp_deflect_chance))//Deflected src.log_append_to_last("Armor saved.") src.occupant_message("\The [user]'s attack is stopped by the armor.") visible_message("\The [user] rebounds off [src.name]'s armor!") user.attack_log += text("\[[time_stamp()]\] attacked [src.name]") playsound(src, 'sound/weapons/slash.ogg', 50, 1, -1) - else if(damage < damage_minimum)//Pathetic damage levels just don't harm MECH. + else if(damage < temp_damage_minimum)//Pathetic damage levels just don't harm MECH. src.occupant_message("\The [user]'s doesn't dent \the [src] paint.") src.visible_message("\The [user]'s attack doesn't dent \the [src] armor") src.log_append_to_last("Armor saved.") diff --git a/code/game/mecha/mecha_actions.dm b/code/game/mecha/mecha_actions.dm index 33307c77d3..c70c3b9b83 100644 --- a/code/game/mecha/mecha_actions.dm +++ b/code/game/mecha/mecha_actions.dm @@ -3,7 +3,7 @@ //THIS FILE CONTAINS THE CODE TO ADD THE HUD BUTTONS AND THE MECH ACTIONS THEMSELVES. // // -// I better get some free food for this.. +// I better get some free food for this.. @@ -35,7 +35,9 @@ phasing_action.Grant(user, src) if(switch_dmg_type_possible) switch_damtype_action.Grant(user, src) - + if(cloak_possible) + cloak_action.Grant(user, src) + /obj/mecha/proc/RemoveActions(mob/living/user, human_occupant = 0) if(human_occupant) eject_action.Remove(user, src) @@ -51,8 +53,8 @@ thrusters_action.Remove(user, src) phasing_action.Remove(user, src) switch_damtype_action.Remove(user, src) - overload_action.Remove(user, src) - + overload_action.Remove(user, src) + cloak_action.Remove(user, src) // @@ -242,6 +244,15 @@ +/datum/action/innate/mecha/mech_toggle_cloaking + name = "Toggle Mech phasing" + button_icon_state = "mech_phasing_off" + +/datum/action/innate/mecha/mech_toggle_cloaking/Activate() + button_icon_state = "mech_phasing_[chassis.cloaked ? "off" : "on"]" + button.UpdateIcon() + chassis.toggle_cloaking() + ///// @@ -293,12 +304,10 @@ return if(overload) overload = 0 - step_in = initial(step_in) step_energy_drain = initial(step_energy_drain) src.occupant_message("You disable leg actuators overload.") else overload = 1 - step_in = min(1, round(step_in/2)) step_energy_drain = step_energy_drain*overload_coeff src.occupant_message("You enable leg actuators overload.") src.log_message("Toggled leg actuators overload.") @@ -324,7 +333,7 @@ if(smoke_ready) smoke_reserve-- //Remove ammo src.occupant_message("Smoke fired. [smoke_reserve] usages left.") - + var/datum/effect/effect/system/smoke_spread/smoke = new /datum/effect/effect/system/smoke_spread() smoke.attach(src) smoke.set_up(10, 0, usr.loc) @@ -422,6 +431,25 @@ return +/obj/mecha/verb/toggle_cloak() + set category = "Exosuit Interface" + set name = "Toggle cloaking" + set src = usr.loc + set popup_menu = 0 + toggle_cloaking() + +/obj/mecha/proc/toggle_cloaking() + if(usr!=src.occupant) + return + + if(cloaked) + uncloak() + else + cloak() + + src.occupant_message("En":"#f00\">Dis"]abled cloaking.") + return + /obj/mecha/verb/toggle_weapons_only_cycle() set category = "Exosuit Interface" set name = "Toggle weapons only cycling" @@ -435,4 +463,3 @@ weapons_only_cycle = !weapons_only_cycle src.occupant_message("En":"#f00\">Dis"]abled weapons only cycling.") return - diff --git a/code/game/mecha/medical/medical.dm b/code/game/mecha/medical/medical.dm index 6e9dee5047..973ddec3fc 100644 --- a/code/game/mecha/medical/medical.dm +++ b/code/game/mecha/medical/medical.dm @@ -9,6 +9,14 @@ cargo_capacity = 1 + starting_components = list( + /obj/item/mecha_parts/component/hull, + /obj/item/mecha_parts/component/actuator, + /obj/item/mecha_parts/component/armor/lightweight, + /obj/item/mecha_parts/component/gas, + /obj/item/mecha_parts/component/electrical + ) + /obj/mecha/medical/Initialize() . = ..() var/turf/T = get_turf(src) diff --git a/code/game/mecha/medical/odysseus.dm b/code/game/mecha/medical/odysseus.dm index 00be6d3c3f..dbba0cd251 100644 --- a/code/game/mecha/medical/odysseus.dm +++ b/code/game/mecha/medical/odysseus.dm @@ -1,4 +1,4 @@ -/obj/mecha/medical/odysseus/ +/obj/mecha/medical/odysseus desc = "These exosuits are developed and produced by Vey-Med. (© All rights reserved)." name = "Odysseus" catalogue_data = list( @@ -9,8 +9,8 @@ initial_icon = "odysseus" step_in = 2 max_temperature = 15000 - health = 120 - maxhealth = 120 + health = 70 + maxhealth = 70 wreckage = /obj/effect/decal/mecha_wreckage/odysseus internal_damage_threshold = 35 deflect_chance = 15 @@ -139,5 +139,5 @@ /obj/mecha/medical/odysseus/old/New() ..() health = 25 - maxhealth = 100 //Just slightly worse. + maxhealth = 50 //Just slightly worse. cell.charge = rand(0, (cell.charge/2)) \ No newline at end of file diff --git a/code/game/mecha/micro/micro.dm b/code/game/mecha/micro/micro.dm index 0b1e340487..97f86f7472 100644 --- a/code/game/mecha/micro/micro.dm +++ b/code/game/mecha/micro/micro.dm @@ -28,7 +28,8 @@ //operation_req_access = list(access_hos) damage_absorption = list("brute"=1,"fire"=1,"bullet"=1,"laser"=1,"energy"=1,"bomb"=1) var/am = "d3c2fbcadca903a41161ccc9df9cf948" - + damage_minimum = 0 //Incoming damage lower than this won't actually deal damage. Scrapes shouldn't be a real thing. + minimum_penetration = 0 //Incoming damage won't be fully applied if you don't have at least 20. Almost all AP clears this. /obj/mecha/micro/melee_action(target as obj|mob|turf) if(internal_damage&MECHA_INT_CONTROL_LOST) diff --git a/code/game/mecha/micro/security.dm b/code/game/mecha/micro/security.dm index 57bc57eed3..d6d054a4f5 100644 --- a/code/game/mecha/micro/security.dm +++ b/code/game/mecha/micro/security.dm @@ -30,6 +30,7 @@ max_equip = 3 max_micro_utility_equip = 0 max_micro_weapon_equip = 3 + damage_minimum = 5 //A teeny bit of armor /obj/effect/decal/mecha_wreckage/micro/sec/polecat name = "Polecat wreckage" diff --git a/code/game/mecha/working/ripley.dm b/code/game/mecha/working/ripley.dm index 5bfe3b4395..86405b6934 100644 --- a/code/game/mecha/working/ripley.dm +++ b/code/game/mecha/working/ripley.dm @@ -14,6 +14,14 @@ minimum_penetration = 10 + starting_components = list( + /obj/item/mecha_parts/component/hull/durable, + /obj/item/mecha_parts/component/actuator, + /obj/item/mecha_parts/component/armor/mining, + /obj/item/mecha_parts/component/gas, + /obj/item/mecha_parts/component/electrical + ) + /obj/mecha/working/ripley/Destroy() for(var/atom/movable/A in src.cargo) A.loc = loc diff --git a/code/game/objects/effects/decals/Cleanable/tracks.dm b/code/game/objects/effects/decals/Cleanable/tracks.dm index 0ad5ac13f8..934068f25b 100644 --- a/code/game/objects/effects/decals/Cleanable/tracks.dm +++ b/code/game/objects/effects/decals/Cleanable/tracks.dm @@ -46,6 +46,8 @@ var/global/list/image/fluidtrack_cache=list() var/coming_state="blood1" var/going_state="blood2" var/updatedtracks=0 + persistent = TRUE + generic_filth = FALSE // dir = id in stack var/list/setdirs=list( diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm index 179b996df9..2ef6582553 100644 --- a/code/game/objects/effects/decals/cleanable.dm +++ b/code/game/objects/effects/decals/cleanable.dm @@ -1,3 +1,10 @@ +/* +USAGE NOTE +For decals, the var Persistent = 'has already been saved', and is primarily used to prevent duplicate savings of generic filth (filth.dm). +This also means 'TRUE' can be used to define a decal as "Do not save at all, even as a generic replacement." if a dirt decal is considered 'too common' to save. +generic_filth = TRUE means when the decal is saved, it will be switched out for a generic green 'filth' decal. +*/ + /obj/effect/decal/cleanable plane = DIRTY_PLANE var/persistent = FALSE diff --git a/code/game/objects/effects/map_effects/perma_light.dm b/code/game/objects/effects/map_effects/perma_light.dm index 281c128fe6..a48e768fc7 100644 --- a/code/game/objects/effects/map_effects/perma_light.dm +++ b/code/game/objects/effects/map_effects/perma_light.dm @@ -14,4 +14,15 @@ light_range = 5 light_power = 3 - light_color = "#FFFFFF" \ No newline at end of file + light_color = "#FFFFFF" + +/obj/effect/map_effect/perma_light/concentrated + name = "permanent light (concentrated)" + + light_range = 2 + light_power = 5 + +/obj/effect/map_effect/perma_light/concentrated/incandescent + name = "permanent light (concentrated incandescent)" + + light_color = LIGHT_COLOR_INCANDESCENT_TUBE \ No newline at end of file diff --git a/code/game/objects/effects/map_effects/portal.dm b/code/game/objects/effects/map_effects/portal.dm new file mode 100644 index 0000000000..93cbd0c53f --- /dev/null +++ b/code/game/objects/effects/map_effects/portal.dm @@ -0,0 +1,344 @@ +GLOBAL_LIST_EMPTY(all_portal_masters) + +/* + +Portal map effects allow a mapper to join two distant places together, while looking somewhat seamlessly connected. +This can allow for very strange PoIs that twist and turn in what appear to be physically impossible ways. + +Portals do have some specific requirements when mapping them in; + - There must by one, and only one `/obj/effect/map_effect/portal/master` for each side of a portal. + - Both sides need to have matching `portal_id`s in order to link to each other. + - Each side must face opposite directions, e.g. if side A faces SOUTH, side B must face NORTH. + - Each side must have the same orientation, e.g. horizontal on both sides, or vertical on both sides. + - Portals can be made to be longer than 1x1 with `/obj/effect/map_effect/portal/line`s, + but both sides must have the same length. + - If portal lines are added, they must form a straight line and be next to a portal master or another portal line. + - If portal lines are used, both portal masters should be in the same relative position among the lines. + E.g. both being on the left most side on a horizontal row. + +Portals also have some limitations to be aware of when mapping. Some of these are not an issue if you're trying to make an 'obvious' portal; + - The objects seen through portals are purely visual, which has many implications, + such as simple_mob AIs being blind to mobs on the other side of portals. + - Objects on the other side of a portal can be interacted with if the interaction has no range limitation, + or the distance between the two portal sides happens to be less than the interaction max range. Examine will probably work, + while picking up an item that appears to be next to you will fail. + - Sounds currently are not carried across portals. + - Mismatched lighting between each portal end can make the portal look obvious. + - Portals look weird when observing as a ghost, or otherwise when able to see through walls. Meson vision will also spoil the illusion. + - Walls that change icons based on neightboring walls can give away that a portal is nearby if both sides don't have a similar transition. + - Projectiles that pass through portals will generally work as intended, however aiming and firing upon someone on the other side of a portal + will likely be weird due to the click targeting the real position of the thing clicked instead of the apparent position. + Thrown objects suffer a similar fate. + - The tiles that are visually shown across a portal are determined based on visibility at the time of portal initialization, + and currently don't update, meaning that opacity changes are not reflected, e.g. a wall is deconstructed, or an airlock is opened. + - There is currently a small but somewhat noticable pause in mob movement when moving across a portal, + as a result of the mob's glide animation being inturrupted by a teleport. + - Gas is not transferred through portals, and ZAS is oblivious to them. + +A lot of those limitations can potentially be solved with some more work. Otherwise, portals work best in static environments like Points of Interest, +when portals are shortly lived, or when portals are made to be obvious with special effects. +*/ + +/obj/effect/map_effect/portal + name = "portal subtype" + invisibility = 0 + opacity = TRUE + plane = TURF_PLANE + layer = ABOVE_TURF_LAYER + appearance_flags = PIXEL_SCALE|KEEP_TOGETHER // Removed TILE_BOUND so things not visible on the other side stay hidden from the viewer. + + var/obj/effect/map_effect/portal/counterpart = null // The portal line or master that this is connected to, on the 'other side'. + + // Information used to apply `pixel_[x|y]` offsets so that the visuals line up. + // Set automatically by `calculate_dimensions()`. + var/total_height = 0 // Measured in tiles. + var/total_width = 0 + + var/portal_distance_x = 0 // How far the portal is from the left edge, in tiles. + var/portal_distance_y = 0 // How far the portal is from the top edge. + +/obj/effect/map_effect/portal/Destroy() + vis_contents = null + if(counterpart) + counterpart.counterpart = null // Disconnect our counterpart from us + counterpart = null // Now disconnect us from them. + return ..() + +// Called when something touches the portal, and usually teleports them to the other side. +/obj/effect/map_effect/portal/Crossed(atom/movable/AM) + if(AM.is_incorporeal()) + return + ..() + if(!AM) + return + if(!counterpart) + return + + go_through_portal(AM) + + +/obj/effect/map_effect/portal/proc/go_through_portal(atom/movable/AM) + // TODO: Find a way to fake the glide or something. + if(isliving(AM)) + var/mob/living/L = AM + if(L.pulling) + var/atom/movable/pulled = L.pulling + L.stop_pulling() + // For some reason, trying to put the pulled object behind the person makes the drag stop and it doesn't even move to the other side. + // pulled.forceMove(get_turf(counterpart)) + pulled.forceMove(counterpart.get_focused_turf()) + L.forceMove(counterpart.get_focused_turf()) + L.start_pulling(pulled) + else + L.forceMove(counterpart.get_focused_turf()) + else + AM.forceMove(counterpart.get_focused_turf()) + +// 'Focused turf' is the turf directly in front of a portal, +// and it is used both as the destination when crossing, as well as the PoV for visuals. +/obj/effect/map_effect/portal/proc/get_focused_turf() + return get_step(get_turf(src), dir) + +// Determines the size of the block of turfs inside `vis_contents`, and where the portal is in relation to that. +/obj/effect/map_effect/portal/proc/calculate_dimensions() + var/highest_x = 0 + var/lowest_x = 0 + + var/highest_y = 0 + var/lowest_y = 0 + + // First pass is for finding the top right corner. + for(var/thing in vis_contents) + var/turf/T = thing + if(T.x > highest_x) + highest_x = T.x + if(T.y > highest_y) + highest_y = T.y + + lowest_x = highest_x + lowest_y = highest_y + + // Second one is for the bottom left corner. + for(var/thing in vis_contents) + var/turf/T = thing + if(T.x < lowest_x) + lowest_x = T.x + if(T.y < lowest_y) + lowest_y = T.y + + // Now calculate the dimensions. + total_width = (highest_x - lowest_x) + 1 + total_height = (highest_y - lowest_y) + 1 + + // Find how far the portal is from the edges. + var/turf/focused_T = counterpart.get_focused_turf() + portal_distance_x = lowest_x - focused_T.x + portal_distance_y = lowest_y - focused_T.y + + +// Portal masters manage everything else involving portals. +// This is the base type. Use `/side_a` or `/side_b` with matching IDs for actual portals. +/obj/effect/map_effect/portal/master + name = "portal master" + show_messages = TRUE // So portals can hear and see, and relay to the other side. + var/portal_id = "test" // For a portal to be made, both the A and B sides need to share the same ID value. + var/list/portal_lines = list() + +/obj/effect/map_effect/portal/master/Initialize() + GLOB.all_portal_masters += src + find_lines() + ..() + return INITIALIZE_HINT_LATELOAD + +/obj/effect/map_effect/portal/master/LateInitialize() + find_counterparts() + make_visuals() + apply_offset() + +/obj/effect/map_effect/portal/master/Destroy() + GLOB.all_portal_masters -= src + for(var/thing in portal_lines) + qdel(thing) + return ..() + +/obj/effect/map_effect/portal/master/proc/find_lines() + var/list/dirs_to_search = list( turn(dir, 90), turn(dir, -90) ) + + for(var/dir_to_search in dirs_to_search) + var/turf/current_T = get_turf(src) + while(current_T) + current_T = get_step(current_T, dir_to_search) + var/obj/effect/map_effect/portal/line/line = locate() in current_T + if(line) + portal_lines += line + line.my_master = src + else + break + +// Connects both sides of a portal together. +/obj/effect/map_effect/portal/master/proc/find_counterparts() + for(var/thing in GLOB.all_portal_masters) + var/obj/effect/map_effect/portal/master/M = thing + if(M == src) + continue + if(M.counterpart) + continue + + if(M.portal_id == src.portal_id) + counterpart = M + M.counterpart = src + if(portal_lines.len) + for(var/i = 1 to portal_lines.len) + var/obj/effect/map_effect/portal/line/our_line = portal_lines[i] + var/obj/effect/map_effect/portal/line/their_line = M.portal_lines[i] + our_line.counterpart = their_line + their_line.counterpart = our_line + break + + if(!counterpart) + crash_with("Portal master [type] ([x],[y],[z]) could not find another portal master with a matching portal_id ([portal_id]).") + +/obj/effect/map_effect/portal/master/proc/make_visuals() + var/list/observed_turfs = list() + for(var/thing in portal_lines + src) + var/obj/effect/map_effect/portal/P = thing + P.name = null + P.icon_state = null + + if(!P.counterpart) + return + + var/turf/T = P.counterpart.get_focused_turf() + P.vis_contents += T + + var/list/things = dview(world.view, T) + for(var/turf/turf in things) + if(get_dir(turf, T) & P.dir) + if(turf in observed_turfs) // Avoid showing the same turf twice or more for improved performance. + continue + + P.vis_contents += turf + observed_turfs += turf + + P.calculate_dimensions() + +// Shifts the portal's pixels in order to line up properly, as BYOND offsets the sprite when it holds multiple turfs inside `vis_contents`. +// This undos the shift that BYOND did. +/obj/effect/map_effect/portal/master/proc/apply_offset() + for(var/thing in portal_lines + src) + var/obj/effect/map_effect/portal/P = thing + + P.pixel_x = WORLD_ICON_SIZE * P.portal_distance_x + P.pixel_y = WORLD_ICON_SIZE * P.portal_distance_y + +// Allows portals to transfer emotes. +// Only portal masters do this to avoid flooding the other side with duplicate messages. +/obj/effect/map_effect/portal/master/see_emote(mob/M, text) + if(!counterpart) + return + var/turf/T = counterpart.get_focused_turf() + var/list/in_range = get_mobs_and_objs_in_view_fast(T, world.view, 0) + var/list/mobs_to_relay = in_range["mobs"] + + for(var/thing in mobs_to_relay) + var/mob/mob = thing + var/rendered = "[text]" + mob.show_message(rendered) + + ..() + +// Allows portals to transfer visible messages. +/obj/effect/map_effect/portal/master/show_message(msg, type, alt, alt_type) + if(!counterpart) + return + var/rendered = "[msg]" + var/turf/T = counterpart.get_focused_turf() + var/list/in_range = get_mobs_and_objs_in_view_fast(T, world.view, 0) + var/list/mobs_to_relay = in_range["mobs"] + + for(var/thing in mobs_to_relay) + var/mob/mob = thing + mob.show_message(rendered) + + ..() + +// Allows portals to transfer speech. +/obj/effect/map_effect/portal/master/hear_talk(mob/M, list/message_pieces, verb) + if(!counterpart) + return + var/turf/T = counterpart.get_focused_turf() + var/list/in_range = get_mobs_and_objs_in_view_fast(T, world.view, 0) + var/list/mobs_to_relay = in_range["mobs"] + + for(var/thing in mobs_to_relay) + var/mob/mob = thing + var/message = mob.combine_message(message_pieces, verb, M) + var/name_used = M.GetVoice() + var/rendered = null + rendered = "[name_used] [message]" + mob.show_message(rendered, 2) + + ..() + +// Returns the position that an atom that's hopefully on the other side of the portal would be if it were really there. +// Z levels not taken into account. +/obj/effect/map_effect/portal/master/proc/get_apparent_position(atom/A) + if(!counterpart) + return null + + var/turf/true_turf = get_turf(A) + var/obj/effect/map_effect/portal/master/other_master = counterpart + + var/in_vis_contents = FALSE + for(var/thing in other_master.portal_lines + other_master) + var/obj/effect/map_effect/portal/P = thing + if(P in true_turf.vis_locs) + in_vis_contents = TRUE + break + + if(!in_vis_contents) + return null // Not in vision of the other portal. + + var/turf/their_focus = counterpart.get_focused_turf() + var/turf/our_focus = get_focused_turf() + + var/relative_x = (true_turf.x - our_focus.x) + relative_x += SIGN(relative_x) + var/relative_y = (true_turf.y - our_focus.y) + relative_y += SIGN(relative_y) + + return new /datum/position(their_focus.x + relative_x, their_focus.y + relative_y, our_focus.z) + + +/obj/effect/map_effect/portal/master/side_a + name = "portal master A" + icon_state = "portal_side_a" +// color = "#00FF00" + +/obj/effect/map_effect/portal/master/side_b + name = "portal master B" + icon_state = "portal_side_b" +// color = "#FF0000" + + + +// Portal lines extend out from the sides of portal masters, +// They let portals be longer than 1x1. +// Both sides MUST be the same length, meaning if side A is 1x3, side B must also be 1x3. +/obj/effect/map_effect/portal/line + name = "portal line" + var/obj/effect/map_effect/portal/master/my_master = null + +/obj/effect/map_effect/portal/line/Destroy() + if(my_master) + my_master.portal_lines -= src + my_master = null + return ..() + +/obj/effect/map_effect/portal/line/side_a + name = "portal line A" + icon_state = "portal_line_side_a" + +/obj/effect/map_effect/portal/line/side_b + name = "portal line B" + icon_state = "portal_line_side_b" \ No newline at end of file diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm index 5ff1f46cde..c949884c6f 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners.dm @@ -136,10 +136,10 @@ HALOGEN COUNTER - Radcount on mobs for(var/A in C.reagents.reagent_list) var/datum/reagent/R = A if(R.scannable) - reagentdata["[R.id]"] = "\t[round(C.reagents.get_reagent_amount(R.id), 1)]u [R.name]
" + reagentdata["[R.id]"] = "\t[round(C.reagents.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.volume > R.overdose) ? " - Overdose" : ""]
" else unknown++ - unknownreagents["[R.id]"] = "\t[round(C.reagents.get_reagent_amount(R.id), 1)]u [R.name]
" + unknownreagents["[R.id]"] = "\t[round(C.reagents.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.volume > R.overdose) ? " - Overdose" : ""]
" if(reagentdata.len) dat += "Beneficial reagents detected in subject's blood:
" for(var/d in reagentdata) @@ -156,14 +156,14 @@ HALOGEN COUNTER - Radcount on mobs var/stomachreagentdata[0] var/stomachunknownreagents[0] for(var/B in C.ingested.reagent_list) - var/datum/reagent/T = B - if(T.scannable) - stomachreagentdata["[T.id]"] = "\t[round(C.ingested.get_reagent_amount(T.id), 1)]u [T.name]
" + var/datum/reagent/R = B + if(R.scannable) + stomachreagentdata["[R.id]"] = "\t[round(C.ingested.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.volume > R.overdose) ? " - Overdose" : ""]
" if (advscan == 0 || showadvscan == 0) - dat += "[T.name] found in subject's stomach.
" + dat += "[R.name] found in subject's stomach.
" else ++unknown - stomachunknownreagents["[T.id]"] = "\t[round(C.ingested.get_reagent_amount(T.id), 1)]u [T.name]
" + stomachunknownreagents["[R.id]"] = "\t[round(C.ingested.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.volume > R.overdose) ? " - Overdose" : ""]
" if(advscan >= 1 && showadvscan == 1) dat += "Beneficial reagents detected in subject's stomach:
" for(var/d in stomachreagentdata) @@ -180,14 +180,14 @@ HALOGEN COUNTER - Radcount on mobs var/touchreagentdata[0] var/touchunknownreagents[0] for(var/B in C.touching.reagent_list) - var/datum/reagent/T = B - if(T.scannable) - touchreagentdata["[T.id]"] = "\t[round(C.touching.get_reagent_amount(T.id), 1)]u [T.name]
" + var/datum/reagent/R = B + if(R.scannable) + touchreagentdata["[R.id]"] = "\t[round(C.touching.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.can_overdose_touch && R.volume > R.overdose) ? " - Overdose" : ""]
" if (advscan == 0 || showadvscan == 0) - dat += "[T.name] found in subject's dermis.
" + dat += "[R.name] found in subject's dermis.
" else ++unknown - touchunknownreagents["[T.id]"] = "\t[round(C.ingested.get_reagent_amount(T.id), 1)]u [T.name]
" + touchunknownreagents["[R.id]"] = "\t[round(C.ingested.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.can_overdose_touch && R.volume > R.overdose) ? " - Overdose" : ""]
" if(advscan >= 1 && showadvscan == 1) dat += "Beneficial reagents detected in subject's dermis:
" for(var/d in touchreagentdata) diff --git a/code/game/objects/items/devices/translocator_vr.dm b/code/game/objects/items/devices/translocator_vr.dm index aeda340ac0..7400b14ddb 100644 --- a/code/game/objects/items/devices/translocator_vr.dm +++ b/code/game/objects/items/devices/translocator_vr.dm @@ -289,6 +289,16 @@ if(!teleport_checks(target,user)) return //The checks proc can send them a message if it wants. + if(istype(target, /mob/living)) + var/mob/living/L = target + if(!L.stat) + if(L != user) + if(L.a_intent != I_HELP || L.has_AI()) + to_chat(user, "[L] is resisting your attempt to teleport them with \the [src].") + to_chat(L, " [user] is trying to teleport you with \the [src]!") + if(!do_after(user, 30, L)) + return + //Bzzt. ready = 0 power_source.use(charge_cost) diff --git a/code/game/objects/items/robot/robot_upgrades_vr.dm b/code/game/objects/items/robot/robot_upgrades_vr.dm index 2bc402b355..ad2556f810 100644 --- a/code/game/objects/items/robot/robot_upgrades_vr.dm +++ b/code/game/objects/items/robot/robot_upgrades_vr.dm @@ -28,4 +28,40 @@ return 0 R.verbs += /mob/living/proc/set_size - return 1 \ No newline at end of file + return 1 + +/obj/item/borg/upgrade/bellysizeupgrade + name = "robotic Hound process capacity upgrade Module" + desc = "Used to upgrade a hound belly capacity. This only affects total volume and such, you won't be able to support more than one patient. Usable once." + icon_state = "cyborg_upgrade2" + item_state = "cyborg_upgrade" + require_module = 1 + +/obj/item/borg/upgrade/bellysizeupgrade/action(var/mob/living/silicon/robot/R) + if(..()) return 0 + + if(!R.module || R.dogborg == FALSE)//can work + to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!") + to_chat(usr, "There's no mounting point for the module! Try upgrading another model.") + return 0 + + var/obj/item/device/dogborg/sleeper/T = locate() in R.module + if(!T) + T = locate() in R.module.contents + if(!T) + T = locate() in R.module.modules + if(!T) + to_chat(usr, "This robot has had its processor removed!") + return 0 + + if(T.upgraded_capacity)// == TRUE + to_chat(R, "Maximum capacity achieved for this hardpoint!") + to_chat(usr, "There's no room for another capacity upgrade!") + return 0 + else + var/X = T.max_item_count*2 + T.max_item_count = X //I couldn't do T = maxitem*2 for some reason. + to_chat(R, "Internal capacity doubled.") + to_chat(usr, "Internal capacity doubled.") + T.upgraded_capacity = TRUE + return 1 diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index cf0e777e5f..7c3661e3d7 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -425,6 +425,15 @@ return ..() +/obj/random/mech_toy + name = "Random Mech Toy" + desc = "This is a random mech toy." + icon = 'icons/obj/toy.dmi' + icon_state = "ripleytoy" + +/obj/random/mech_toy/item_to_spawn() + return pick(typesof(/obj/item/toy/prize)) + /obj/item/toy/prize/ripley name = "toy ripley" desc = "Mini-Mecha action figure! Collect them all! 1/11." diff --git a/code/game/objects/items/weapons/storage/boxes.dm b/code/game/objects/items/weapons/storage/boxes.dm index aafc0d5e98..61e67a4bb4 100644 --- a/code/game/objects/items/weapons/storage/boxes.dm +++ b/code/game/objects/items/weapons/storage/boxes.dm @@ -199,6 +199,16 @@ /obj/item/weapon/storage/box/empshells/large starts_with = list(/obj/item/ammo_casing/a12g/emp = 16) +/obj/item/weapon/storage/box/flechetteshells + name = "box of shotgun flechettes" + desc = "It has a picture of a gun and several warning symbols on the front.
WARNING: Live ammunition. Misuse may result in serious injury or death." + icon_state = "lethalslug_box" + item_state_slots = list(slot_r_hand_str = "syringe_kit", slot_l_hand_str = "syringe_kit") + starts_with = list(/obj/item/ammo_casing/a12g/flechette = 8) + +/obj/item/weapon/storage/box/flechetteshells/large + starts_with = list(/obj/item/ammo_casing/a12g/flechette = 16) + /obj/item/weapon/storage/box/sniperammo name = "box of 14.5mm shells" desc = "It has a picture of a gun and several warning symbols on the front.
WARNING: Live ammunition. Misuse may result in serious injury or death." diff --git a/code/game/objects/mob_spawner_vr.dm b/code/game/objects/mob_spawner_vr.dm index 23ec38c907..a3f01c5371 100644 --- a/code/game/objects/mob_spawner_vr.dm +++ b/code/game/objects/mob_spawner_vr.dm @@ -30,7 +30,7 @@ /obj/structure/mob_spawner/Destroy() STOP_PROCESSING(SSobj, src) for(var/mob/living/L in spawned_mobs) - L.source_spawner = null + L.nest = null spawned_mobs.Cut() return ..() @@ -57,7 +57,7 @@ if(!ispath(mob_path)) return 0 var/mob/living/L = new mob_path(get_turf(src)) - L.source_spawner = src + L.nest = src spawned_mobs.Add(L) last_spawn = world.time if(total_spawns > 0) diff --git a/code/game/objects/random/mob.dm b/code/game/objects/random/mob.dm index ac89272f52..bd720faed4 100644 --- a/code/game/objects/random/mob.dm +++ b/code/game/objects/random/mob.dm @@ -33,7 +33,9 @@ prob(10);/mob/living/simple_mob/animal/passive/mouse, prob(10);/mob/living/simple_mob/animal/passive/yithian, prob(10);/mob/living/simple_mob/animal/passive/tindalos, + prob(10);/mob/living/simple_mob/animal/passive/pillbug, prob(10);/mob/living/simple_mob/animal/passive/dog/tamaskan, + prob(10);/mob/living/simple_mob/animal/passive/dog/brittany, prob(3);/mob/living/simple_mob/animal/passive/bird/parrot, prob(1);/mob/living/simple_mob/animal/passive/crab) @@ -69,10 +71,12 @@ /obj/random/mob/sif/item_to_spawn() return pick(prob(30);/mob/living/simple_mob/animal/sif/diyaab, + prob(20);/mob/living/simple_mob/animal/passive/hare, prob(15);/mob/living/simple_mob/animal/passive/crab, prob(15);/mob/living/simple_mob/animal/passive/penguin, prob(15);/mob/living/simple_mob/animal/passive/mouse, prob(15);/mob/living/simple_mob/animal/passive/dog/tamaskan, + prob(10);/mob/living/simple_mob/animal/sif/siffet, prob(2);/mob/living/simple_mob/animal/giant_spider/frost, prob(1);/mob/living/simple_mob/animal/space/goose, prob(20);/mob/living/simple_mob/animal/passive/crab) @@ -88,6 +92,7 @@ /obj/random/mob/sif/peaceful/item_to_spawn() return pick(prob(30);/mob/living/simple_mob/animal/sif/diyaab, + prob(20);/mob/living/simple_mob/animal/passive/hare, prob(15);/mob/living/simple_mob/animal/passive/crab, prob(15);/mob/living/simple_mob/animal/passive/penguin, prob(15);/mob/living/simple_mob/animal/passive/mouse, @@ -102,6 +107,8 @@ /obj/random/mob/sif/hostile/item_to_spawn() return pick(prob(22);/mob/living/simple_mob/animal/sif/savik, prob(33);/mob/living/simple_mob/animal/giant_spider/frost, + prob(20);/mob/living/simple_mob/animal/sif/frostfly, + prob(10);/mob/living/simple_mob/animal/sif/tymisian, prob(45);/mob/living/simple_mob/animal/sif/shantak) /obj/random/mob/sif/kururak @@ -329,6 +336,12 @@ /mob/living/simple_mob/animal/sif/duck, /mob/living/simple_mob/animal/sif/duck ), + prob(15);list( + /mob/living/simple_mob/animal/passive/hare, + /mob/living/simple_mob/animal/passive/hare, + /mob/living/simple_mob/animal/passive/hare, + /mob/living/simple_mob/animal/passive/hare + ), prob(10);list( /mob/living/simple_mob/animal/sif/shantak/retaliate, /mob/living/simple_mob/animal/sif/shantak/retaliate, diff --git a/code/modules/ai/ai_holder_targeting.dm b/code/modules/ai/ai_holder_targeting.dm index cacfcf56de..240d782926 100644 --- a/code/modules/ai/ai_holder_targeting.dm +++ b/code/modules/ai/ai_holder_targeting.dm @@ -12,7 +12,7 @@ var/vision_range = 7 // How far the targeting system will look for things to kill. Note that values higher than 7 are 'offscreen' and might be unsporting. var/respect_alpha = TRUE // If true, mobs with a sufficently low alpha will be treated as invisible. - var/alpha_vision_threshold = 127 // Targets with an alpha less or equal to this will be considered invisible. Requires above var to be true. + var/alpha_vision_threshold = FAKE_INVIS_ALPHA_THRESHOLD // Targets with an alpha less or equal to this will be considered invisible. Requires above var to be true. var/lose_target_time = 0 // world.time when a target was lost. var/lose_target_timeout = 5 SECONDS // How long until a mob 'times out' and stops trying to find the mob that disappeared. diff --git a/code/modules/client/preference_setup/loadout/loadout_fluffitems_vr.dm b/code/modules/client/preference_setup/loadout/loadout_fluffitems_vr.dm index 21ebb11403..f8bffbbd90 100644 --- a/code/modules/client/preference_setup/loadout/loadout_fluffitems_vr.dm +++ b/code/modules/client/preference_setup/loadout/loadout_fluffitems_vr.dm @@ -183,12 +183,6 @@ ckeywhitelist = list("cockatricexl") character_name = list("James Holder") -/datum/gear/fluff/jasmine_implant - path = /obj/item/weapon/implanter/reagent_generator/jasmine - display_name = "Jasmine's Implant" - ckeywhitelist = list("cameron653") - character_name = list("Jasmine Lizden") - /datum/gear/fluff/diana_robe path = /obj/item/clothing/suit/fluff/purp_robes display_name = "Diana's Robes" @@ -310,11 +304,6 @@ allowed_roles = list("Explorer") // G CKEYS -/datum/gear/fluff/eldi_implant - path = /obj/item/weapon/implanter/reagent_generator/eldi - display_name = "Eldi's Implant" - ckeywhitelist = list("gowst") - character_name = list("Eldi Moljir") // H CKEYS /datum/gear/fluff/lauren_medal @@ -335,12 +324,6 @@ ckeywhitelist = list("hottokeeki") character_name = list("Belle Day") -/datum/gear/fluff/belle_implant - path = /obj/item/weapon/implanter/reagent_generator/belle - display_name = "Belle's Implant" - ckeywhitelist = list("hottokeeki") - character_name = list("Belle Day") - // I CKEYS /datum/gear/fluff/ruda_badge path = /obj/item/clothing/accessory/badge/holo/detective/ruda @@ -553,12 +536,6 @@ ckeywhitelist = list("kiwidaninja") character_name = list("Chakat Taiga") -/datum/gear/fluff/rischi_implant - path = /obj/item/weapon/implanter/reagent_generator/rischi - display_name = "Rischi's Implant" - ckeywhitelist = list("konabird") - character_name = list("Rischi") - /datum/gear/fluff/ashley_medal path = /obj/item/clothing/accessory/medal/nobel_science/fluff/ashley display_name = "Ashley's Medal" @@ -580,12 +557,6 @@ ckeywhitelist = list("luminescentring") character_name = list("Briana Moore") -/datum/gear/fluff/savannah_implant - path = /obj/item/weapon/implanter/reagent_generator/savannah - display_name = "Savannah's Implant" - ckeywhitelist = list("lycanthorph") - character_name = list("Savannah Dixon") - // M CKEYS /datum/gear/fluff/phi_box path = /obj/item/weapon/storage/box/fluff/phi @@ -842,12 +813,6 @@ ckeywhitelist = list("silvertalismen") character_name = list("Tasy Ruffles") -/datum/gear/fluff/evian_implant - path = /obj/item/weapon/implanter/reagent_generator/evian - display_name = "Evian's Implant" - ckeywhitelist = list("silvertalismen") - character_name = list("Evian") - /datum/gear/fluff/fortune_backpack path = /obj/item/weapon/storage/backpack/satchel/fluff/swat43bag display_name = "Fortune's Backpack" @@ -861,12 +826,6 @@ ckeywhitelist = list("stobarico") character_name = list("Alexis Bloise") -/datum/gear/fluff/roiz_implant - path = /obj/item/weapon/implanter/reagent_generator/roiz - display_name = "Roiz's Implant" - ckeywhitelist = list("spoopylizz") - character_name = list("Roiz Lizden") - /datum/gear/fluff/roiz_coat path = /obj/item/clothing/suit/storage/hooded/wintercoat/roiz display_name = "Roiz's Coat" @@ -1020,12 +979,6 @@ ckeywhitelist = list("vorrarkul") character_name = list("Theodora Lindt") -/datum/gear/fluff/theodora_implant - path = /obj/item/weapon/implanter/reagent_generator/vorrarkul - display_name = "Theodora's Implant" - ckeywhitelist = list("vorrarkul") - character_name = list("Theodora Lindt") - /datum/gear/fluff/kaitlyn_plush path = /obj/item/toy/plushie/mouse/fluff display_name = "Kaitlyn's Mouse Plush" @@ -1102,12 +1055,6 @@ ckeywhitelist = list("wickedtemp") character_name = list("Chakat Tempest Venosare") -/datum/gear/fluff/tempest_implant - path = /obj/item/weapon/implanter/reagent_generator/tempest - display_name = "Tempest's Implant" - ckeywhitelist = list("wickedtemp") - character_name = list("Chakat Tempest Venosare") - // X CKEYS /datum/gear/fluff/penelope_box path = /obj/item/weapon/storage/box/fluff/penelope diff --git a/code/modules/food/kitchen/cooking_machines/_appliance.dm b/code/modules/food/kitchen/cooking_machines/_appliance.dm index 61666aacff..d8c4e761ca 100644 --- a/code/modules/food/kitchen/cooking_machines/_appliance.dm +++ b/code/modules/food/kitchen/cooking_machines/_appliance.dm @@ -583,6 +583,7 @@ var/obj/item/thing var/delete = 1 var/status = CI.container.check_contents() + if (status == 1)//If theres only one object in a container then we extract that thing = locate(/obj/item) in CI.container delete = 0 @@ -596,6 +597,7 @@ qdel(CI) else CI.reset()//reset instead of deleting if the container is left inside + user.visible_message("\The [user] remove \the [thing] from \the [src].") /obj/machinery/appliance/proc/cook_mob(var/mob/living/victim, var/mob/user) return diff --git a/code/modules/food/kitchen/cooking_machines/container.dm b/code/modules/food/kitchen/cooking_machines/container.dm index 2ebb2ed7f0..d650a2266e 100644 --- a/code/modules/food/kitchen/cooking_machines/container.dm +++ b/code/modules/food/kitchen/cooking_machines/container.dm @@ -145,7 +145,7 @@ /obj/item/weapon/reagent_containers/cooking_container/oven name = "oven dish" shortname = "shelf" - desc = "Put ingredients in this; designed for use with an oven. Warranty void if used incorrectly." + desc = "Put ingredients in this; designed for use with an oven. Warranty void if used incorrectly. Alt click to remove contents." icon_state = "ovendish" max_space = 30 max_reagents = 120 @@ -162,11 +162,11 @@ /obj/item/weapon/reagent_containers/cooking_container/fryer name = "fryer basket" shortname = "basket" - desc = "Put ingredients in this; designed for use with a deep fryer. Warranty void if used incorrectly." + desc = "Put ingredients in this; designed for use with a deep fryer. Warranty void if used incorrectly. Alt click to remove contents." icon_state = "basket" /obj/item/weapon/reagent_containers/cooking_container/grill name = "grill rack" shortname = "rack" - desc = "Put ingredients 'in'/on this; designed for use with a grill. Warranty void if used incorrectly." + desc = "Put ingredients 'in'/on this; designed for use with a grill. Warranty void if used incorrectly. Alt click to remove contents." icon_state = "grillrack" \ No newline at end of file diff --git a/code/modules/food/kitchen/cooking_machines/fryer.dm b/code/modules/food/kitchen/cooking_machines/fryer.dm index 941c7d6b64..6c2e0d9b71 100644 --- a/code/modules/food/kitchen/cooking_machines/fryer.dm +++ b/code/modules/food/kitchen/cooking_machines/fryer.dm @@ -12,7 +12,7 @@ circuit = /obj/item/weapon/circuitboard/fryer appliancetype = FRYER active_power_usage = 12 KILOWATTS - heating_power = 12000 + heating_power = 12 KILOWATTS light_y = 15 @@ -24,7 +24,7 @@ // Power used to maintain temperature once it's heated. // Going with 25% of the active power. This is a somewhat arbitrary value. - resistance = 60000 // Approx. 10 minutes to heat up. + resistance = 10 KILOWATTS // Approx. 10 minutes to heat up. max_contents = 2 container_type = /obj/item/weapon/reagent_containers/cooking_container/fryer diff --git a/code/modules/food/kitchen/cooking_machines/oven.dm b/code/modules/food/kitchen/cooking_machines/oven.dm index df9394f599..3203ecdc51 100644 --- a/code/modules/food/kitchen/cooking_machines/oven.dm +++ b/code/modules/food/kitchen/cooking_machines/oven.dm @@ -9,10 +9,10 @@ can_burn_food = TRUE circuit = /obj/item/weapon/circuitboard/oven active_power_usage = 6 KILOWATTS - heating_power = 6000 + heating_power = 6 KILOWATTS //Based on a double deck electric convection oven - resistance = 30000 // Approx. 12 minutes to heat up. + resistance = 12 KILOWATTS // Approx. 12 minutes to heat up. idle_power_usage = 2 KILOWATTS //uses ~30% power to stay warm optimal_power = 0.8 // Oven cooks .2 faster than the default speed. @@ -86,6 +86,7 @@ cooking = FALSE playsound(src, 'sound/machines/hatch_open.ogg', 20, 1) + to_chat(user, "You [open? "close":"open"] the oven door") update_icon() /obj/machinery/appliance/cooker/oven/proc/manip(var/obj/item/I) diff --git a/code/modules/food/recipes_microwave_vr.dm b/code/modules/food/recipes_microwave_vr.dm index aba09d186c..8a4d88fc2a 100644 --- a/code/modules/food/recipes_microwave_vr.dm +++ b/code/modules/food/recipes_microwave_vr.dm @@ -20,27 +20,6 @@ ) result = /obj/item/weapon/reagent_containers/food/snacks/sliceable/sushi -/datum/recipe/chocroizegg - items = list( - /obj/item/weapon/reagent_containers/food/snacks/chocolatebar, - /obj/item/weapon/reagent_containers/food/snacks/egg/roiz - ) - result = /obj/item/weapon/reagent_containers/food/snacks/chocolateegg/roiz - -/datum/recipe/friedroizegg - reagents = list("sodiumchloride" = 1, "blackpepper" = 1) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/egg/roiz - ) - result = /obj/item/weapon/reagent_containers/food/snacks/friedegg/roiz - -/datum/recipe/boiledroizegg - reagents = list("water" = 5) - items = list( - /obj/item/weapon/reagent_containers/food/snacks/egg/roiz - ) - result = /obj/item/weapon/reagent_containers/food/snacks/boiledegg/roiz - /datum/recipe/lobster fruit = list("lemon" = 1, "cabbage" = 1) items = list( diff --git a/code/modules/holodeck/HolodeckControl.dm b/code/modules/holodeck/HolodeckControl.dm index e30a6f049c..5ba939fd8d 100644 --- a/code/modules/holodeck/HolodeckControl.dm +++ b/code/modules/holodeck/HolodeckControl.dm @@ -325,7 +325,7 @@ for(var/mob/living/M in mobs_in_area(linkedholodeck)) if(M.mind) - linkedholodeck.play_ambience(M) + linkedholodeck.play_ambience(M, initial = TRUE) linkedholodeck.sound_env = A.sound_env diff --git a/code/modules/mob/_modifiers/modifiers.dm b/code/modules/mob/_modifiers/modifiers.dm index 66afd732a2..56422efdf6 100644 --- a/code/modules/mob/_modifiers/modifiers.dm +++ b/code/modules/mob/_modifiers/modifiers.dm @@ -55,6 +55,14 @@ var/emp_modifier // Added to the EMP strength, which is an inverse scale from 1 to 4, with 1 being the strongest EMP. 5 is a nullification. var/explosion_modifier // Added to the bomb strength, which is an inverse scale from 1 to 3, with 1 being gibstrength. 4 is a nullification. + // Note that these are combined with the mob's real armor values additatively. You can also omit specific armor types. + var/list/armor_percent = null // List of armor values to add to the holder when doing armor calculations. This is for percentage based armor. E.g. 50 = half damage. + var/list/armor_flat = null // Same as above but only for flat armor calculations. E.g. 5 = 5 less damage (this comes after percentage). + // Unlike armor, this is multiplicative. Two 50% protection modifiers will be combined into 75% protection (assuming no base protection on the mob). + var/heat_protection = null // Modifies how 'heat' protection is calculated, like wearing a firesuit. 1 = full protection. + var/cold_protection = null // Ditto, but for cold, like wearing a winter coat. + var/siemens_coefficient = null // Similar to above two vars but 0 = full protection, to be consistant with siemens numbers everywhere else. + var/vision_flags // Vision flags to add to the mob. SEE_MOB, SEE_OBJ, etc. /datum/modifier/New(var/new_holder, var/new_origin) @@ -186,10 +194,14 @@ // Checks if the mob has a modifier type. /mob/living/proc/has_modifier_of_type(var/modifier_type) + return get_modifier_of_type(modifier_type) ? TRUE : FALSE + +// Gets the first instance of a specific modifier type or subtype. +/mob/living/proc/get_modifier_of_type(var/modifier_type) for(var/datum/modifier/M in modifiers) if(istype(M, modifier_type)) - return TRUE - return FALSE + return M + return null // This displays the actual 'numbers' that a modifier is doing. Should only be shown in OOC contexts. // When adding new effects, be sure to update this as well. diff --git a/code/modules/mob/_modifiers/modifiers_misc.dm b/code/modules/mob/_modifiers/modifiers_misc.dm index 2efa5c0ef9..3b9663d855 100644 --- a/code/modules/mob/_modifiers/modifiers_misc.dm +++ b/code/modules/mob/_modifiers/modifiers_misc.dm @@ -397,4 +397,33 @@ the artifact triggers the rage. /datum/modifier/outline_test/tick() animate(filter_instance, size = 3, time = 0.25 SECONDS) - animate(size = 1, 0.25 SECONDS) \ No newline at end of file + animate(size = 1, 0.25 SECONDS) + + +// Acts as a psuedo-godmode, yet probably is more reliable than the actual var for it nowdays. +// Can't protect from instantly killing things like singulos. +/datum/modifier/invulnerable + name = "invulnerable" + desc = "You are almost immune to harm, for a little while at least." + stacks = MODIFIER_STACK_EXTEND + + disable_duration_percent = 0 + incoming_damage_percent = 0 +// bleeding_rate_percent = 0 + pain_immunity = TRUE + armor_percent = list("melee" = 2000, "bullet" = 2000, "laser" = 2000, "bomb" = 2000, "energy" = 2000, "bio" = 2000, "rad" = 2000) + heat_protection = 1.0 + cold_protection = 1.0 + siemens_coefficient = 0.0 + +// Reduces resistance to "elements". +// Note that most things that do give resistance gives 100% protection, +// and due to multiplicitive stacking, this modifier won't do anything to change that. +/datum/modifier/elemental_vulnerability + name = "elemental vulnerability" + desc = "You're more vulnerable to extreme temperatures and electricity." + stacks = MODIFIER_STACK_EXTEND + + heat_protection = -0.5 + cold_protection = -0.5 + siemens_coefficient = 1.5 \ No newline at end of file diff --git a/code/modules/mob/_modifiers/traits_phobias.dm b/code/modules/mob/_modifiers/traits_phobias.dm index bd30891fbe..87f8ecc446 100644 --- a/code/modules/mob/_modifiers/traits_phobias.dm +++ b/code/modules/mob/_modifiers/traits_phobias.dm @@ -120,27 +120,32 @@ // People covered in blood is also bad. // Feel free to trim down if its too expensive CPU wise. - if(istype(thing, /mob/living/carbon/human)) - var/mob/living/carbon/human/H = thing - var/self_multiplier = H == holder ? 2 : 1 - var/human_blood_fear_amount = 0 - if(!H.gloves && H.bloody_hands && H.hand_blood_color != SYNTH_BLOOD_COLOUR) - human_blood_fear_amount += 1 - if(!H.shoes && H.feet_blood_color && H.feet_blood_color != SYNTH_BLOOD_COLOUR) - human_blood_fear_amount += 1 + if(isliving(thing)) + var/mob/living/L = thing + if(L.alpha <= FAKE_INVIS_ALPHA_THRESHOLD) // Can't fear something you can't (easily) see. + continue - // List of slots. Some slots like pockets are omitted due to not being visible, if H isn't the holder. - var/list/clothing_slots = list(H.back, H.wear_mask, H.l_hand, H.r_hand, H.wear_id, H.glasses, H.gloves, H.head, H.shoes, H.belt, H.wear_suit, H.w_uniform, H.s_store, H.l_ear, H.r_ear) - if(H == holder) - clothing_slots += list(H.l_store, H.r_store) - - for(var/obj/item/clothing/C in clothing_slots) - if(C.blood_DNA && C.blood_color && C.blood_color != SYNTH_BLOOD_COLOUR) + if(istype(thing, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = thing + var/self_multiplier = H == holder ? 2 : 1 + var/human_blood_fear_amount = 0 + if(!H.gloves && H.bloody_hands && H.hand_blood_color != SYNTH_BLOOD_COLOUR) + human_blood_fear_amount += 1 + if(!H.shoes && H.feet_blood_color && H.feet_blood_color != SYNTH_BLOOD_COLOUR) human_blood_fear_amount += 1 - // This is divided, since humans can wear so many items at once. - human_blood_fear_amount = round( (human_blood_fear_amount * self_multiplier) / 3, 1) - fear_amount += human_blood_fear_amount + // List of slots. Some slots like pockets are omitted due to not being visible, if H isn't the holder. + var/list/clothing_slots = list(H.back, H.wear_mask, H.l_hand, H.r_hand, H.wear_id, H.glasses, H.gloves, H.head, H.shoes, H.belt, H.wear_suit, H.w_uniform, H.s_store, H.l_ear, H.r_ear) + if(H == holder) + clothing_slots += list(H.l_store, H.r_store) + + for(var/obj/item/clothing/C in clothing_slots) + if(C.blood_DNA && C.blood_color && C.blood_color != SYNTH_BLOOD_COLOUR) + human_blood_fear_amount += 1 + + // This is divided, since humans can wear so many items at once. + human_blood_fear_amount = round( (human_blood_fear_amount * self_multiplier) / 3, 1) + fear_amount += human_blood_fear_amount // Bloody objects are also bad. if(istype(thing, /obj)) @@ -207,12 +212,18 @@ if(istype(thing, /obj/structure/snowman/spider)) //Snow spiders are also spooky so people can be assholes with those too. fear_amount += 1 - if(istype(thing, /mob/living/simple_mob/animal/giant_spider)) // Actual giant spiders are the scariest of them all. - var/mob/living/simple_mob/animal/giant_spider/S = thing - if(S.stat == DEAD) // Dead giant spiders are less scary than alive ones. - fear_amount += 4 - else - fear_amount += 8 + if(isliving(thing)) + var/mob/living/L = thing + if(L.alpha <= FAKE_INVIS_ALPHA_THRESHOLD) // Can't fear something you can't (easily) see. + continue + + if(istype(L, /mob/living/simple_mob/animal/giant_spider)) // Actual giant spiders are the scariest of them all. + var/mob/living/simple_mob/animal/giant_spider/S = L + + if(S.stat == DEAD) // Dead giant spiders are less scary than alive ones. + fear_amount += 4 + else + fear_amount += 8 return fear_amount @@ -425,25 +436,29 @@ if(istype(thing, /obj/item/clothing/head/collectable/slime)) // Some hats are spooky so people can be assholes with them. fear_amount += 1 - if(istype(thing, /mob/living/simple_mob/slime)) // An actual predatory specimen! - var/mob/living/simple_mob/slime/S = thing - if(S.stat == DEAD) // Dead slimes are somewhat less spook. - fear_amount += 4 - if(istype(S, /mob/living/simple_mob/slime/xenobio)) - var/mob/living/simple_mob/slime/xenobio/X = S - if(X.is_adult == TRUE) //big boy - fear_amount += 8 + if(isliving(thing)) + var/mob/living/L = thing + if(L.alpha <= FAKE_INVIS_ALPHA_THRESHOLD) // Can't fear something you can't (easily) see. + continue + if(istype(L, /mob/living/simple_mob/slime)) // An actual predatory specimen! + var/mob/living/simple_mob/slime/S = L + if(S.stat == DEAD) // Dead slimes are somewhat less spook. + fear_amount += 4 + if(istype(S, /mob/living/simple_mob/slime/xenobio)) + var/mob/living/simple_mob/slime/xenobio/X = S + if(X.is_adult == TRUE) //big boy + fear_amount += 8 + else + fear_amount += 6 else - fear_amount += 6 - else - fear_amount += 10 // It's huge and feral. + fear_amount += 10 // It's huge and feral. - if(istype(thing, /mob/living/carbon/human)) - var/mob/living/carbon/human/S = thing - if(istype(S.species, /datum/species/skrell)) //Skrell ARE slimey. - fear_amount += 1 - if(istype(S.species, /datum/species/shapeshifter/promethean)) - fear_amount += 4 + if(istype(L, /mob/living/carbon/human)) + var/mob/living/carbon/human/S = L + if(istype(S.species, /datum/species/skrell)) //Skrell ARE slimey. + fear_amount += 1 + if(istype(S.species, /datum/species/shapeshifter/promethean)) + fear_amount += 4 return fear_amount @@ -525,13 +540,17 @@ if(istype(thing, /obj/item/weapon/gun/launcher/syringe)) fear_amount += 6 - if(istype(thing, /mob/living/carbon/human)) - var/mob/living/carbon/human/H = thing - if(H.l_hand && istype(H.l_hand, /obj/item/weapon/reagent_containers/syringe) || H.r_hand && istype(H.r_hand, /obj/item/weapon/reagent_containers/syringe)) - fear_amount += 10 + if(isliving(thing)) + var/mob/living/L = thing + if(L.alpha <= FAKE_INVIS_ALPHA_THRESHOLD) // Can't fear something you can't (easily) see. + continue + if(istype(L, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = L + if(H.l_hand && istype(H.l_hand, /obj/item/weapon/reagent_containers/syringe) || H.r_hand && istype(H.r_hand, /obj/item/weapon/reagent_containers/syringe)) + fear_amount += 10 - if(H.l_ear && istype(H.l_ear, /obj/item/weapon/reagent_containers/syringe) || H.r_ear && istype(H.r_ear, /obj/item/weapon/reagent_containers/syringe)) - fear_amount +=10 + if(H.l_ear && istype(H.l_ear, /obj/item/weapon/reagent_containers/syringe) || H.r_ear && istype(H.r_ear, /obj/item/weapon/reagent_containers/syringe)) + fear_amount +=10 return fear_amount diff --git a/code/modules/mob/living/bot/mulebot.dm b/code/modules/mob/living/bot/mulebot.dm index 9bef4c0cbc..67fc8fd051 100644 --- a/code/modules/mob/living/bot/mulebot.dm +++ b/code/modules/mob/living/bot/mulebot.dm @@ -241,20 +241,20 @@ M.Weaken(5) ..() -/mob/living/bot/mulebot/proc/runOver(var/mob/living/carbon/human/H) - if(istype(H)) // No safety checks - WILL run over lying humans. Stop ERPing in the maint! - visible_message("[src] drives over [H]!") +/mob/living/bot/mulebot/proc/runOver(var/mob/living/M) + if(istype(M)) // At this point, MULEBot has somehow crossed over onto your tile with you still on it. CRRRNCH. + visible_message("[src] drives over [M]!") playsound(src, 'sound/effects/splat.ogg', 50, 1) var/damage = rand(5, 7) - H.apply_damage(2 * damage, BRUTE, BP_HEAD) - H.apply_damage(2 * damage, BRUTE, BP_TORSO) - H.apply_damage(0.5 * damage, BRUTE, BP_L_LEG) - H.apply_damage(0.5 * damage, BRUTE, BP_R_LEG) - H.apply_damage(0.5 * damage, BRUTE, BP_L_ARM) - H.apply_damage(0.5 * damage, BRUTE, BP_R_ARM) + M.apply_damage(2 * damage, BRUTE, BP_HEAD) + M.apply_damage(2 * damage, BRUTE, BP_TORSO) + M.apply_damage(0.5 * damage, BRUTE, BP_L_LEG) + M.apply_damage(0.5 * damage, BRUTE, BP_R_LEG) + M.apply_damage(0.5 * damage, BRUTE, BP_L_ARM) + M.apply_damage(0.5 * damage, BRUTE, BP_R_ARM) - blood_splatter(src, H, 1) + blood_splatter(src, M, 1) ..() /mob/living/bot/mulebot/relaymove(var/mob/user, var/direction) diff --git a/code/modules/mob/living/bot/mulebot_vr.dm b/code/modules/mob/living/bot/mulebot_vr.dm new file mode 100644 index 0000000000..aec8f30983 --- /dev/null +++ b/code/modules/mob/living/bot/mulebot_vr.dm @@ -0,0 +1,5 @@ +/mob/living/bot/mulebot/handle_micro_bump_helping() // Can't drive over micros or macros regardless of intent. + return 0 + +/mob/living/bot/mulebot/handle_micro_bump_other() // Can't drive over micros or macros regardless of intent. + return 0 \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 17c2e87969..1dbfcca9c8 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -253,20 +253,14 @@ return // called when something steps onto a human -// this handles mulebots and vehicles -// and now mobs on fire +// this handles mobs on fire - mulebot and vehicle code has been relocated to /mob/living/Crossed() /mob/living/carbon/human/Crossed(var/atom/movable/AM) if(AM.is_incorporeal()) return - if(istype(AM, /mob/living/bot/mulebot)) - var/mob/living/bot/mulebot/MB = AM - MB.runOver(src) - - if(istype(AM, /obj/vehicle)) - var/obj/vehicle/V = AM - V.RunOver(src) spread_fire(AM) + + ..() // call parent because we moved behavior to parent // Get rank from ID, ID inside PDA, PDA, ID in wallet, etc. /mob/living/carbon/human/proc/get_authentification_rank(var/if_no_id = "No id", var/if_no_job = "No job") diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 8e351372c9..0199d387ae 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -133,6 +133,12 @@ emp_act if(istype(C) && (C.body_parts_covered & def_zone.body_part)) // Is that body part being targeted covered? siemens_coefficient *= C.siemens_coefficient + // Modifiers. + for(var/thing in modifiers) + var/datum/modifier/M = thing + if(!isnull(M.siemens_coefficient)) + siemens_coefficient *= M.siemens_coefficient + return siemens_coefficient // Similar to above but is for the mob's overall protection, being the average of all slots. @@ -150,11 +156,11 @@ emp_act if(fire_stacks < 0) // Water makes you more conductive. siemens_value *= 1.5 - return (siemens_value/max(total, 1)) + return (siemens_value / max(total, 1)) // Returns a number between 0 to 1, with 1 being total protection. /mob/living/carbon/human/get_shock_protection() - return between(0, 1-get_siemens_coefficient_average(), 1) + return min(1 - get_siemens_coefficient_average(), 1) // Don't go above 1, but negatives are fine. // Returns a list of clothing that is currently covering def_zone. /mob/living/carbon/human/proc/get_clothing_list_organ(var/obj/item/organ/external/def_zone, var/type) @@ -173,6 +179,13 @@ emp_act var/list/protective_gear = def_zone.get_covering_clothing() for(var/obj/item/clothing/gear in protective_gear) protection += gear.armor[type] + + for(var/thing in modifiers) + var/datum/modifier/M = thing + var/modifier_armor = LAZYACCESS(M.armor_percent, type) + if(modifier_armor) + protection += modifier_armor + return protection /mob/living/carbon/human/proc/getsoak_organ(var/obj/item/organ/external/def_zone, var/type) @@ -182,6 +195,13 @@ emp_act var/list/protective_gear = def_zone.get_covering_clothing() for(var/obj/item/clothing/gear in protective_gear) soaked += gear.armorsoak[type] + + for(var/thing in modifiers) + var/datum/modifier/M = thing + var/modifier_armor = LAZYACCESS(M.armor_flat, type) + if(modifier_armor) + soaked += modifier_armor + return soaked // Checked in borer code diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index b29afe4da5..479874b185 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -717,7 +717,7 @@ cold_dam = COLD_DAMAGE_LEVEL_1 take_overall_damage(burn=cold_dam, used_weapon = "Low Body Temperature") - + else clear_alert("temp") // Account for massive pressure differences. Done by Polymorph @@ -836,7 +836,19 @@ /mob/living/carbon/human/get_heat_protection(temperature) //Temperature is the temperature you're being exposed to. var/thermal_protection_flags = get_heat_protection_flags(temperature) - return get_thermal_protection(thermal_protection_flags) + + . = get_thermal_protection(thermal_protection_flags) + . = 1 - . // Invert from 1 = immunity to 0 = immunity. + + // Doing it this way makes multiplicative stacking not get out of hand, so two modifiers that give 0.5 protection will be combined to 0.75 in the end. + for(var/thing in modifiers) + var/datum/modifier/M = thing + if(!isnull(M.heat_protection)) + . *= 1 - M.heat_protection + + // Code that calls this expects 1 = immunity so we need to invert again. + . = 1 - . + . = min(., 1.0) /mob/living/carbon/human/get_cold_protection(temperature) if(COLD_RESISTANCE in mutations) @@ -844,7 +856,20 @@ temperature = max(temperature, 2.7) //There is an occasional bug where the temperature is miscalculated in ares with a small amount of gas on them, so this is necessary to ensure that that bug does not affect this calculation. Space's temperature is 2.7K and most suits that are intended to protect against any cold, protect down to 2.0K. var/thermal_protection_flags = get_cold_protection_flags(temperature) - return get_thermal_protection(thermal_protection_flags) + + . = get_thermal_protection(thermal_protection_flags) + . = 1 - . // Invert from 1 = immunity to 0 = immunity. + + // Doing it this way makes multiplicative stacking not get out of hand, so two modifiers that give 0.5 protection will be combined to 0.75 in the end. + for(var/thing in modifiers) + var/datum/modifier/M = thing + if(!isnull(M.cold_protection)) + // Invert the modifier values so they align with the current working value. + . *= 1 - M.cold_protection + + // Code that calls this expects 1 = immunity so we need to invert again. + . = 1 - . + . = min(., 1.0) /mob/living/carbon/human/proc/get_thermal_protection(var/flags) .=0 diff --git a/code/modules/mob/living/carbon/human/species/station/seromi.dm b/code/modules/mob/living/carbon/human/species/station/seromi.dm index 8133cfbfbd..7e9e7c4efb 100644 --- a/code/modules/mob/living/carbon/human/species/station/seromi.dm +++ b/code/modules/mob/living/carbon/human/species/station/seromi.dm @@ -89,8 +89,16 @@ heat_discomfort_strings = list( "Your feathers prickle in the heat.", "You feel uncomfortably warm.", + "Your hands and feet feel hot as your body tries to regulate heat", ) cold_discomfort_level = 180 + cold_discomfort_strings = list( + "You feel a bit chilly.", + "You fluff up your feathers against the cold.", + "You move your arms closer to your body to shield yourself from the cold.", + "You press your ears against your head to conserve heat", + "You start to feel the cold on your skin", + ) minimum_breath_pressure = 12 //Smaller, so needs less air diff --git a/code/modules/mob/living/carbon/human/species/station/traits_vr/neutral.dm b/code/modules/mob/living/carbon/human/species/station/traits_vr/neutral.dm index 7342c7c443..b495533e31 100644 --- a/code/modules/mob/living/carbon/human/species/station/traits_vr/neutral.dm +++ b/code/modules/mob/living/carbon/human/species/station/traits_vr/neutral.dm @@ -146,6 +146,15 @@ YW change end */ H.verbs |= /mob/living/carbon/human/proc/succubus_drain_finalize H.verbs |= /mob/living/carbon/human/proc/succubus_drain_lethal +/datum/trait/feeder + name = "Feeder" + desc = "Allows you to feed your prey using your own body." + cost = 0 + +/datum/trait/feeder/apply(var/datum/species/S,var/mob/living/carbon/human/H) + ..(S,H) + H.verbs |= /mob/living/carbon/human/proc/slime_feed + /datum/trait/hard_vore name = "Brutal Predation" desc = "Allows you to tear off limbs & tear out internal organs." diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index 809b82af54..b33a5d3945 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -10,6 +10,11 @@ if(istype(nest, /obj/structure/blob/factory)) var/obj/structure/blob/factory/F = nest F.spores -= src + //VOREStation Edit Start + if(istype(nest, /obj/structure/mob_spawner)) + var/obj/structure/mob_spawner/S = nest + S.get_death_report(src) + //VOREStation Edit End nest = null for(var/s in owned_soul_links) diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index bf3d898b6c..857075fee3 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -96,7 +96,7 @@ if(world.time >= (lastareachange + 30 SECONDS)) // Every 30 seconds, we're going to run a 35% chance to play ambience. var/area/A = get_area(src) if(A) - A.play_ambience(src) + A.play_ambience(src, initial = FALSE) /mob/living/proc/update_pulling() if(pulling) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index e8e5fd3361..a472366b03 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -244,6 +244,19 @@ default behaviour is: return TRUE return ..() +// Called when something steps onto us. This allows for mulebots and vehicles to run things over. <3 +/mob/living/Crossed(var/atom/movable/AM) // Transplanting this from /mob/living/carbon/human/Crossed() + if(AM == src || AM.is_incorporeal()) // We're not going to run over ourselves or ghosts + return + + if(istype(AM, /mob/living/bot/mulebot)) + var/mob/living/bot/mulebot/MB = AM + MB.runOver(src) + + if(istype(AM, /obj/vehicle)) + var/obj/vehicle/V = AM + V.RunOver(src) + /mob/living/verb/succumb() set hidden = 1 if ((src.health < 0 && src.health > (5-src.getMaxHealth()))) // Health below Zero but above 5-away-from-death, as before, but variable diff --git a/code/modules/mob/living/living_defines_vr.dm b/code/modules/mob/living/living_defines_vr.dm index b8e8cf202d..966d354fec 100644 --- a/code/modules/mob/living/living_defines_vr.dm +++ b/code/modules/mob/living/living_defines_vr.dm @@ -3,7 +3,6 @@ /mob/living var/ooc_notes = null - var/obj/structure/mob_spawner/source_spawner = null appearance_flags = TILE_BOUND|PIXEL_SCALE|KEEP_TOGETHER var/hunger_rate = DEFAULT_HUNGER_FACTOR diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index 1278b59c76..3a995ba19d 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -40,6 +40,7 @@ "Fennec" = "pai-fen", "Type Zero" = "pai-typezero", "Raccoon" = "pai-raccoon", + "Raptor" = "pai-raptor", "Rat" = "rat", "Panther" = "panther" //VOREStation Addition End diff --git a/code/modules/mob/living/silicon/robot/analyzer.dm b/code/modules/mob/living/silicon/robot/analyzer.dm index f1b2d76eec..e946c7999c 100644 --- a/code/modules/mob/living/silicon/robot/analyzer.dm +++ b/code/modules/mob/living/silicon/robot/analyzer.dm @@ -34,6 +34,8 @@ scan_type = "robot" else if(istype(M, /mob/living/carbon/human)) scan_type = "prosthetics" + else if(istype(M, /obj/mecha)) + scan_type = "mecha" else to_chat(user, "You can't analyze non-robotic things!") return @@ -95,5 +97,37 @@ if(!organ_found) to_chat(user, "No prosthetics located.") + if("mecha") + + var/obj/mecha/Mecha = M + + var/integrity = Mecha.health/initial(Mecha.health)*100 + var/cell_charge = Mecha.get_charge() + var/tank_pressure = Mecha.internal_tank ? round(Mecha.internal_tank.return_pressure(),0.01) : "None" + var/tank_temperature = Mecha.internal_tank ? Mecha.internal_tank.return_temperature() : "Unknown" + var/cabin_pressure = round(Mecha.return_pressure(),0.01) + + var/output = {"Analyzing Results for \the [Mecha]:
+ Chassis Integrity: [integrity]%
+ Powercell charge: [isnull(cell_charge)?"No powercell installed":"[Mecha.cell.percent()]%"]
+ Air source: [Mecha.use_internal_tank?"Internal Airtank":"Environment"]
+ Airtank pressure: [tank_pressure]kPa
+ Airtank temperature: [tank_temperature]K|[tank_temperature - T0C]°C
+ Cabin pressure: [cabin_pressure>WARNING_HIGH_PRESSURE ? "[cabin_pressure]": cabin_pressure]kPa
+ Cabin temperature: [Mecha.return_temperature()]K|[Mecha.return_temperature() - T0C]°C
+ DNA Lock: [Mecha.dna?"Mecha.dna":"Not Found"]
+ "} + + to_chat(user, output) + to_chat(user, "
") + to_chat(user, "Internal Diagnostics:") + for(var/slot in Mecha.internal_components) + var/obj/item/mecha_parts/component/MC = Mecha.internal_components[slot] + to_chat(user, "[MC?"[slot]: [MC] [round((MC.integrity / MC.max_integrity) * 100, 0.1)]% integrity. [MC.get_efficiency() * 100] Operational capacity.":"[slot]: Component Not Found"]") + + to_chat(user, "
") + to_chat(user, "General Statistics:") + to_chat(user, "Movement Weight: [Mecha.get_step_delay()]
") + src.add_fingerprint(user) return diff --git a/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper_vr.dm b/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper_vr.dm index 570e60bb51..3ce9a40447 100644 --- a/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper_vr.dm +++ b/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper_vr.dm @@ -30,6 +30,7 @@ var/synced = FALSE var/startdrain = 500 var/max_item_count = 1 + var/upgraded_capacity = FALSE var/gulpsound = 'sound/vore/gulp.ogg' var/datum/matter_synth/metal = null var/datum/matter_synth/glass = null diff --git a/code/modules/mob/living/simple_mob/defense.dm b/code/modules/mob/living/simple_mob/defense.dm index d94d83c0a9..1a301b8bfd 100644 --- a/code/modules/mob/living/simple_mob/defense.dm +++ b/code/modules/mob/living/simple_mob/defense.dm @@ -140,7 +140,18 @@ // Cold stuff. /mob/living/simple_mob/get_cold_protection() - return cold_resist + . = cold_resist + . = 1 - . // Invert from 1 = immunity to 0 = immunity. + + // Doing it this way makes multiplicative stacking not get out of hand, so two modifiers that give 0.5 protection will be combined to 0.75 in the end. + for(var/thing in modifiers) + var/datum/modifier/M = thing + if(!isnull(M.cold_protection)) + . *= 1 - M.cold_protection + + // Code that calls this expects 1 = immunity so we need to invert again. + . = 1 - . + . = min(., 1.0) // Fire stuff. Not really exciting at the moment. @@ -154,7 +165,18 @@ return /mob/living/simple_mob/get_heat_protection() - return heat_resist + . = heat_resist + . = 1 - . // Invert from 1 = immunity to 0 = immunity. + + // Doing it this way makes multiplicative stacking not get out of hand, so two modifiers that give 0.5 protection will be combined to 0.75 in the end. + for(var/thing in modifiers) + var/datum/modifier/M = thing + if(!isnull(M.heat_protection)) + . *= 1 - M.heat_protection + + // Code that calls this expects 1 = immunity so we need to invert again. + . = 1 - . + . = min(., 1.0) // Electricity /mob/living/simple_mob/electrocute_act(var/shock_damage, var/obj/source, var/siemens_coeff = 1.0, var/def_zone = null) @@ -170,7 +192,18 @@ s.start() /mob/living/simple_mob/get_shock_protection() - return shock_resist + . = shock_resist + . = 1 - . // Invert from 1 = immunity to 0 = immunity. + + // Doing it this way makes multiplicative stacking not get out of hand, so two modifiers that give 0.5 protection will be combined to 0.75 in the end. + for(var/thing in modifiers) + var/datum/modifier/M = thing + if(!isnull(M.siemens_coefficient)) + . *= M.siemens_coefficient + + // Code that calls this expects 1 = immunity so we need to invert again. + . = 1 - . + . = min(., 1.0) // Shot with taser/stunvolver /mob/living/simple_mob/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null) @@ -218,17 +251,29 @@ // Armor /mob/living/simple_mob/getarmor(def_zone, attack_flag) var/armorval = armor[attack_flag] - if(!armorval) - return 0 - else - return armorval + if(isnull(armorval)) + armorval = 0 + + for(var/thing in modifiers) + var/datum/modifier/M = thing + var/modifier_armor = LAZYACCESS(M.armor_percent, attack_flag) + if(modifier_armor) + armorval += modifier_armor + + return armorval /mob/living/simple_mob/getsoak(def_zone, attack_flag) var/armorval = armor_soak[attack_flag] - if(!armorval) - return 0 - else - return armorval + if(isnull(armorval)) + armorval = 0 + + for(var/thing in modifiers) + var/datum/modifier/M = thing + var/modifier_armor = LAZYACCESS(M.armor_flat, attack_flag) + if(modifier_armor) + armorval += modifier_armor + + return armorval // Lightning /mob/living/simple_mob/lightning_act() diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/passive/crab.dm b/code/modules/mob/living/simple_mob/subtypes/animal/passive/crab.dm index 87c05b317d..9fe548ee61 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/passive/crab.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/passive/crab.dm @@ -54,4 +54,4 @@ /obj/item/weapon/reagent_containers/food/snacks/meat/crab name = "meat" desc = "A chunk of meat." - icon_state = "crustacean-meat" + icon_state = "crustacean-meat" \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/passive/mouse.dm b/code/modules/mob/living/simple_mob/subtypes/animal/passive/mouse.dm index 0918c1b8ae..dedac8e1e2 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/passive/mouse.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/passive/mouse.dm @@ -10,6 +10,8 @@ maxHealth = 5 health = 5 + melee_damage_lower = 1 + melee_damage_upper = 3 movement_cooldown = 1.5 diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/pets/dog.dm b/code/modules/mob/living/simple_mob/subtypes/animal/pets/dog.dm index 1084da4889..189216c32d 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/pets/dog.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/pets/dog.dm @@ -237,4 +237,14 @@ name = "Spice" real_name = "Spice" //Intended to hold the name without altering it. gender = FEMALE - desc = "It's a tamaskan, the name Spice can be found on its collar." \ No newline at end of file + desc = "It's a tamaskan, the name Spice can be found on its collar." + +// Brittany Spaniel + +/mob/living/simple_mob/animal/passive/dog/brittany + name = "brittany" + real_name = "brittany" + desc = "It's a brittany spaniel." + icon_state = "brittany" + icon_living = "brittany" + icon_dead = "brittany_dead" \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/frostfly.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/frostfly.dm index 5f7aaa479b..1010ab6eab 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/sif/frostfly.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/frostfly.dm @@ -169,3 +169,4 @@ holder.face_atom(A) F.energy = max(0, F.energy - 1) // The AI will eventually flee. + diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/hare.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/hare.dm new file mode 100644 index 0000000000..e796cd488b --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/hare.dm @@ -0,0 +1,68 @@ +// Complete chumps but a little bit hardier than mice. + +/datum/category_item/catalogue/fauna/hare + name = "Sivian Fauna - Ice Hare" + desc = "Classification: S Lepus petropellis\ +

\ + Hard-skinned, horned herbivores common on the glacial regions of Sif. \ + The Ice Hare lives in colonies of up to thirty individuals dug beneath thick ice sheets for protection from many burrowing predators. \ + Their diet consists of mostly moss and lichens, though this is supplemented with the consumption of hard mineral pebbles, which it swallows whole, \ + which form the small, hard, 'ice-like' scales of the animal. \ + The Ice Hare is almost completely harmless to sapients, with relatively blunt claws and a weak jaw. Its main forms of self-defense are its speed, \ + and two sharp head spikes whose 'ear-like' appearance gave the species its common name." + value = CATALOGUER_REWARD_EASY + +/mob/living/simple_mob/animal/passive/hare + name = "ice hare" + real_name = "ice hare" + desc = "A small horned herbivore with a tough 'ice-like' hide." + tt_desc = "S Lepus petropellis" //Sivian hare rockskin + catalogue_data = list(/datum/category_item/catalogue/fauna/hare) + + icon_state = "hare" + icon_living = "hare" + icon_dead = "hare_dead" + icon_rest = "hare_rest" + + maxHealth = 20 + health = 20 + + armor = list( + "melee" = 30, + "bullet" = 5, + "laser" = 5, + "energy" = 0, + "bomb" = 10, + "bio" = 0, + "rad" = 0 + ) + + armor_soak = list( + "melee" = 5, + "bullet" = 0, + "laser" = 0, + "energy" = 0, + "bomb" = 0, + "bio" = 0, + "rad" = 0 + ) + + movement_cooldown = 2 + + mob_size = MOB_SMALL + pass_flags = PASSTABLE + layer = MOB_LAYER + density = 0 + + response_help = "pets" + response_disarm = "nudges" + response_harm = "kicks" + + meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat + + say_list_type = /datum/say_list/hare + +/datum/say_list/hare + speak = list("Snrf...","Crk!") + emote_hear = list("crackles","sniffles") + emote_see = list("stomps the ground", "sniffs the air", "chews on something") \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/moth.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/moth.dm new file mode 100644 index 0000000000..d153540dd5 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/moth.dm @@ -0,0 +1,143 @@ +//Very similar to frostflies, but with a non-lethal gas and less damaging, but less easy to protect from, projectiles. + +/datum/category_item/catalogue/fauna/tymisian + name = "Binman Fauna - Tymisian Moth" + desc = "Classification: B Carabidae glacios \ +

\ + A meter-long fuzzy insect from the planet Binma. \ + A native of the Binman contintents of Telarus and Dalomee, the Tymisian Moth usually lives in communal \ + groups of upwards of thirty individuals, known as 'spuzzes', and typically breeds up to three times in its \ + fifteen year natural lifespan. \ +
\ + Though strictly herbivorous, the moth has an acute sense of smell which it uses to detect potential predators. \ + Impressively, the moth secretes an oily substance which it uses to coat collected material stored in 'soft pockets' \ + beneath each wing, which in turn attracts colonies of pungent bacteria. \ + When threatened, the moth will release this dust, which has a disorienting effect on most Binman species, as well as all known sapients. \ +
\ + The Tymisian Moth is considered an invasive species on Sif, and is believed to have been established from an illegally \ + released pet collection. Though less dangerous than in its native environment, the moth has nonetheless established a \ + similar symbiotic relationship with Sivian bacteria for its defense mechanism, and is still to be considered quite dangerous. \ +
\ + As an invasive species, individuals encountering the Tymisian Moth on Sif are requested to report the sighting to local wildlife \ + services, and remove or destroy the creature if it is safe to do so." + value = CATALOGUER_REWARD_MEDIUM + +/mob/living/simple_mob/animal/sif/tymisian + name = "Tymisian Moth" + desc = "A huge, fuzzy insect with a disorienting dust." + tt_desc = "B Lepidoptera cinereus" + catalogue_data = list(/datum/category_item/catalogue/fauna/tymisian) + + faction = "spiders" //Hostile to most mobs, not all. + + icon_state = "moth" + icon_living = "moth" + icon_dead = "moth_dead" + icon_rest = "moth_dead" + icon = 'icons/mob/animal.dmi' + + maxHealth = 80 + health = 80 + + hovering = TRUE + + movement_cooldown = 0.5 + + melee_damage_lower = 5 + melee_damage_upper = 10 + base_attack_cooldown = 1.5 SECONDS + attacktext = list("nipped", "bit", "pinched") + + projectiletype = /obj/item/projectile/energy/blob + + special_attack_cooldown = 10 SECONDS + special_attack_min_range = 0 + special_attack_max_range = 6 + + var/energy = 100 + var/max_energy = 100 + + var/datum/effect/effect/system/smoke_spread/mothspore/smoke_spore + + say_list_type = /datum/say_list/tymisian + ai_holder_type = /datum/ai_holder/simple_mob/ranged/kiting/threatening/frostfly //Uses frostfly AI, since so similar mechanically + +/datum/say_list/tymisian + speak = list("Zzzz.", "Rrr...", "Zzt?") + emote_see = list("grooms itself","sprinkles dust from its wings", "rubs its mandibles") + emote_hear = list("chitters", "clicks", "rattles") + + say_understood = list("Ssst.") + say_cannot = list("Zzrt.") + say_maybe_target = list("Rr?") + say_got_target = list("Rrrrt!") + say_threaten = list("Kszsz.","Kszzt...","Kzzi!") + say_stand_down = list("Sss.","Zt.","! clicks.") + say_escalate = list("Rszt!") + + threaten_sound = 'sound/effects/spray3.ogg' + stand_down_sound = 'sound/effects/squelch1.ogg' + + +/obj/effect/effect/smoke/elemental/mothspore + name = "spore cloud" + desc = "A dust cloud filled with disorienting bacterial spores." + color = "#80AB82" + +/obj/effect/effect/smoke/elemental/mothspore/affect(mob/living/L) //Similar to a very weak flash, but depends on breathing instead of eye protection. + if(iscarbon(L)) + var/mob/living/carbon/C = L + if(C.stat != DEAD) + if(C.needs_to_breathe()) + var/spore_strength = 5 + if(ishuman(C)) + var/mob/living/carbon/human/H = C + H.Confuse(spore_strength) + H.eye_blurry = max(H.eye_blurry, spore_strength) + H.adjustHalLoss(10 * (spore_strength / 5)) + +/datum/effect/effect/system/smoke_spread/mothspore + smoke_type = /obj/effect/effect/smoke/elemental/mothspore + +/mob/living/simple_mob/animal/sif/tymisian/do_special_attack(atom/A) + . = TRUE + switch(a_intent) + if(I_DISARM) + if(energy < 20) + return FALSE + + energy -= 20 + + if(smoke_spore) + smoke_spore.set_up(7,0,src) + smoke_spore.start() + return TRUE + + return FALSE + +/mob/living/simple_mob/animal/sif/tymisian/Initialize() + ..() + smoke_spore = new + verbs += /mob/living/proc/ventcrawl + verbs += /mob/living/proc/hide + +/mob/living/simple_mob/animal/sif/tymisian/handle_special() + ..() + + if(energy < max_energy) + energy++ + +/mob/living/simple_mob/animal/sif/tymisian/Stat() + ..() + if(client.statpanel == "Status") + statpanel("Status") + if(emergency_shuttle) + var/eta_status = emergency_shuttle.get_status_panel_eta() + if(eta_status) + stat(null, eta_status) + stat("Energy", energy) + +/mob/living/simple_mob/animal/sif/tymisian/should_special_attack(atom/A) + if(energy >= 20) + return TRUE + return FALSE diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/pillbug.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/pillbug.dm new file mode 100644 index 0000000000..f70ea218c3 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/pillbug.dm @@ -0,0 +1,51 @@ +/datum/category_item/catalogue/fauna/pillbug + name = "Sivian Fauna - Fire Bug" + desc = "Classification: S Armadillidiidae calidi \ +

\ + A 10 inch long, hard-shelled insect with a natural adaption to living around terrestrial lava vents. \ + The fire bug's hard shell offers extremely effective protection against most threats, \ + though the species is almost completely docile, and will prefer to continue grazing on its diet of volcanic micro-flora \ + rather than defend itself in most situations.\ +
\ + The fire bug is a curiosity to most on the frontier, offering little in the way of meaningful food or resources, \ + though at least one Sivian fashion designer has used their iridescent red shells to create striking, hand-made garments." + value = CATALOGUER_REWARD_EASY + +/mob/living/simple_mob/animal/passive/pillbug + name = "fire bug" + desc = "A tiny plated bug found in Sif's volcanic regions." + tt_desc = "S Armadillidiidae calidi" + catalogue_data = list(/datum/category_item/catalogue/fauna/pillbug) + + icon_state = "pillbug" + icon_living = "pillbug" + icon_dead = "pillbug_dead" + + health = 15 + maxHealth = 15 + mob_size = MOB_MINISCULE + + response_help = "gently touches" + response_disarm = "rolls over" + response_harm = "stomps on" + + armor = list( + "melee" = 30, + "bullet" = 10, + "laser" = 50, + "energy" = 50, + "bomb" = 30, + "bio" = 100, + "rad" = 100 + ) + + // The frostfly's body is incredibly cold at all times, natural resistance to things trying to burn it. + armor_soak = list( + "melee" = 10, + "bullet" = 0, + "laser" = 10, + "energy" = 10, + "bomb" = 0, + "bio" = 0, + "rad" = 0 + ) \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/siffet.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/siffet.dm new file mode 100644 index 0000000000..40ae65d3bc --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/siffet.dm @@ -0,0 +1,61 @@ +// Somewhere between a fox and a weasel. Doesn't mess with stuff significantly bigger than it, but you don't want to get on its bad side. + +/datum/category_item/catalogue/fauna/siffet + name = "Sivian Fauna - Siffet" + desc = "Classification: S Pruinaeictis velocis\ +

\ + The Siffet, or Sivian Frost Weasel is a small, solitary predator known for its striking ability to take down prey up to twice their size. \ + The majority of the Siffet's adult life is spent in isolation, prowling large territories in Sif's tundra regions, \ + only seeking out other individuals during the summer mating season, when deadly battles for dominance are common. \ + Though mostly docile towards adult humans and other large sapients, the Siffet has been known to target children and smaller species as prey, \ + and a provoked Siffet can be a danger to even the most experienced handler due to its quick movement and surprisingly powerful jaws. \ + The Siffet is sometimes hunted for its remarkably soft pelt, though most is obtained through fur farming." + value = CATALOGUER_REWARD_MEDIUM + +/mob/living/simple_mob/animal/sif/siffet + name = "siffet" + desc = "A small, solitary predator with silky fur. Despite its size, the Siffet is ferocious when provoked." + tt_desc = "S Pruinaeictis velocis" //Sivian frost weasel, fast + catalogue_data = list(/datum/category_item/catalogue/fauna/siffet) + + faction = "siffet" + + mob_size = MOB_SMALL + + icon_state = "siffet" + icon_living = "siffet" + icon_dead = "siffet_dead" + icon = 'icons/mob/animal.dmi' + + maxHealth = 60 + health = 60 + + movement_cooldown = 0 + + melee_damage_lower = 10 + melee_damage_upper = 15 + base_attack_cooldown = 1 SECOND + attack_sharp = 1 + attacktext = list("sliced", "snapped", "gnawed") + + say_list_type = /datum/say_list/siffet + ai_holder_type = /datum/ai_holder/simple_mob/siffet + +/datum/say_list/siffet + speak = list("Yap!", "Heh!", "Huff.") + emote_see = list("sniffs its surroundings","flicks its ears", "scratches the ground") + emote_hear = list("chatters", "huffs") + +/datum/ai_holder/simple_mob/siffet + hostile = TRUE + retaliate = TRUE + +/datum/ai_holder/simple_mob/siffet/post_melee_attack(atom/A) //Evasive + if(holder.Adjacent(A)) + holder.IMove(get_step(holder, pick(alldirs))) + holder.face_atom(A) + +/mob/living/simple_mob/animal/sif/siffet/IIsAlly(mob/living/L) + . = ..() + if(!. && L.mob_size > 10) //Attacks things it considers small enough to take on, otherwise only attacks if attacked. + return TRUE \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/horse.dm b/code/modules/mob/living/simple_mob/subtypes/vore/horse.dm index 8c9f08a471..1245278421 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/horse.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/horse.dm @@ -1,5 +1,5 @@ /mob/living/simple_mob/vore/horse - name = "horse" + name = "small horse" desc = "Don't look it in the mouth." tt_desc = "Equus ferus caballus" @@ -12,7 +12,7 @@ maxHealth = 60 health = 60 - movement_cooldown = 4 //horses are fast mkay. + movement_cooldown = 1.5 //horses are fast mkay. see_in_dark = 6 response_help = "pets" @@ -35,18 +35,44 @@ say_list_type = /datum/say_list/horse ai_holder_type = /datum/ai_holder/simple_mob/retaliate +/mob/living/simple_mob/vore/horse/big + name = "horse" + icon_state = "horse" + icon_living = "horse" + icon_dead = "horse-dead" + icon = 'icons/mob/vore64x64.dmi' + + maxHealth = 120 + health = 120 + + melee_damage_lower = 5 + melee_damage_upper = 15 + attacktext = list("kicked") + + meat_amount = 6 + + old_x = -16 + old_y = 0 + default_pixel_x = -16 + pixel_x = -16 + pixel_y = 0 + mount_offset_y = 22 + // Activate Noms! /mob/living/simple_mob/vore/horse vore_active = 1 vore_icons = SA_ICON_LIVING +/mob/living/simple_mob/vore/horse/big + vore_capacity = 2 + /mob/living/simple_mob/vore/horse/Login() . = ..() if(!riding_datum) riding_datum = new /datum/riding/simple_mob(src) verbs |= /mob/living/simple_mob/proc/animal_mount verbs |= /mob/living/proc/toggle_rider_reins - movement_cooldown = 3 + movement_cooldown = 1.5 /mob/living/simple_mob/vore/horse/MouseDrop_T(mob/living/M, mob/living/user) return diff --git a/code/modules/mob/new_player/sprite_accessories.dm b/code/modules/mob/new_player/sprite_accessories.dm index 2bf54878b2..a875b8a574 100644 --- a/code/modules/mob/new_player/sprite_accessories.dm +++ b/code/modules/mob/new_player/sprite_accessories.dm @@ -1126,23 +1126,23 @@ //Skrell 'hairstyles' skr_tentacle_veryshort - name = "Skrell Very Short Tentacles" - icon_state = "skrell_hair_veryshort" + name = "Skrell Short Tentacles" + icon_state = "skrell_hair_short" species_allowed = list(SPECIES_SKRELL) gender = MALE skr_tentacle_short - name = "Skrell Short Tentacles" - icon_state = "skrell_hair_short" - species_allowed = list(SPECIES_SKRELL) - - skr_tentacle_average name = "Skrell Average Tentacles" icon_state = "skrell_hair_average" species_allowed = list(SPECIES_SKRELL) - skr_tentacle_verylong + skr_tentacle_average name = "Skrell Long Tentacles" + icon_state = "skrell_hair_long" + species_allowed = list(SPECIES_SKRELL) + + skr_tentacle_verylong + name = "Skrell Very Long Tentacles" icon_state = "skrell_hair_verylong" species_allowed = list(SPECIES_SKRELL) gender = FEMALE diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm index 7a63d49576..9e011f442d 100644 --- a/code/modules/modular_computers/file_system/program.dm +++ b/code/modules/modular_computers/file_system/program.dm @@ -42,6 +42,9 @@ /datum/computer_file/program/nano_host() return computer.nano_host() +/datum/computer_file/program/tgui_host() + return computer.tgui_host() + /datum/computer_file/program/clone() var/datum/computer_file/program/temp = ..() temp.required_access = required_access diff --git a/code/modules/organs/blood.dm b/code/modules/organs/blood.dm index cc66674b87..49f9d5b6a4 100644 --- a/code/modules/organs/blood.dm +++ b/code/modules/organs/blood.dm @@ -360,7 +360,7 @@ proc/blood_splatter(var/target,var/datum/reagent/blood/source,var/large) drop.drips |= drips // If there's no data to copy, call it quits here. - if(!source) + if(!istype(source)) return B // Update appearance. diff --git a/code/modules/organs/internal/brain.dm b/code/modules/organs/internal/brain.dm index 094d3eb32a..0412ab1470 100644 --- a/code/modules/organs/internal/brain.dm +++ b/code/modules/organs/internal/brain.dm @@ -98,6 +98,7 @@ GLOBAL_LIST_BOILERPLATE(all_brain_organs, /obj/item/organ/internal/brain) brainmob.real_name = H.real_name brainmob.dna = H.dna.Clone() brainmob.timeofhostdeath = H.timeofdeath + brainmob.ooc_notes = H.ooc_notes //VOREStation Edit // Copy modifiers. for(var/datum/modifier/M in H.modifiers) @@ -178,6 +179,7 @@ GLOBAL_LIST_BOILERPLATE(all_brain_organs, /obj/item/organ/internal/brain) parent_organ = BP_TORSO clone_source = TRUE flags = OPENCONTAINER + var/list/owner_flavor_text = list() /obj/item/organ/internal/brain/slime/is_open_container() return 1 @@ -191,6 +193,11 @@ GLOBAL_LIST_BOILERPLATE(all_brain_organs, /obj/item/organ/internal/brain) H = owner color = rgb(min(H.r_skin + 40, 255), min(H.g_skin + 40, 255), min(H.b_skin + 40, 255)) +/obj/item/organ/internal/brain/slime/removed(var/mob/living/user) + if(istype(owner)) + owner_flavor_text = owner.flavor_texts.Copy() + ..() + /obj/item/organ/internal/brain/slime/proc/reviveBody() var/datum/dna2/record/R = new /datum/dna2/record() R.dna = brainmob.dna @@ -200,6 +207,8 @@ GLOBAL_LIST_BOILERPLATE(all_brain_organs, /obj/item/organ/internal/brain) R.types = DNA2_BUF_UI|DNA2_BUF_UE|DNA2_BUF_SE R.languages = brainmob.languages R.flavor = list() + if(islist(owner_flavor_text)) + R.flavor = owner_flavor_text.Copy() for(var/datum/modifier/mod in brainmob.modifiers) if(mod.flags & MODIFIER_GENETIC) R.genetic_modifiers.Add(mod.type) @@ -238,6 +247,7 @@ GLOBAL_LIST_BOILERPLATE(all_brain_organs, /obj/item/organ/internal/brain) if(!R.dna.real_name) //to prevent null names R.dna.real_name = "promethean ([rand(0,999)])" H.real_name = R.dna.real_name + H.ooc_notes = brainmob.ooc_notes // VOREStation Edit H.nutrition = 260 //Enough to try to regenerate ONCE. H.adjustBruteLoss(40) diff --git a/code/modules/paperwork/paper_sticky.dm b/code/modules/paperwork/paper_sticky.dm index e2904aba37..5c1efb2a28 100644 --- a/code/modules/paperwork/paper_sticky.dm +++ b/code/modules/paperwork/paper_sticky.dm @@ -1,6 +1,7 @@ /obj/item/sticky_pad name = "sticky note pad" desc = "A pad of densely packed sticky notes." + description_info = "Click to remove a sticky note from the pile. Click-drag to yourself to pick up the stack. Sticky notes stuck to surfaces/objects will persist for 50 rounds." color = COLOR_YELLOW icon = 'icons/obj/stickynotes.dmi' icon_state = "pad_full" @@ -50,24 +51,38 @@ . = ..() if(.) to_chat(user, SPAN_NOTICE("It has [papers] sticky note\s left.")) - to_chat(user, SPAN_NOTICE("You can click it on grab intent to pick it up.")) /obj/item/sticky_pad/attack_hand(var/mob/user) - if(user.a_intent == I_GRAB) - ..() + var/obj/item/weapon/paper/paper = new paper_type(get_turf(src)) + paper.set_content(written_text, "sticky note") + paper.last_modified_ckey = written_by + paper.color = color + written_text = null + user.put_in_hands(paper) + to_chat(user, SPAN_NOTICE("You pull \the [paper] off \the [src].")) + papers-- + if(papers <= 0) + qdel(src) else - var/obj/item/weapon/paper/paper = new paper_type(get_turf(src)) - paper.set_content(written_text, "sticky note") - paper.last_modified_ckey = written_by - paper.color = color - written_text = null - user.put_in_hands(paper) - to_chat(user, SPAN_NOTICE("You pull \the [paper] off \the [src].")) - papers-- - if(papers <= 0) - qdel(src) - else - update_icon() + update_icon() + +/obj/item/sticky_pad/MouseDrop(mob/user as mob) + if(user == usr && !(usr.restrained() || usr.stat) && (usr.contents.Find(src) || in_range(src, usr))) + if(!istype(usr, /mob/living/simple_mob)) + if( !usr.get_active_hand() ) //if active hand is empty + var/mob/living/carbon/human/H = user + var/obj/item/organ/external/temp = H.organs_by_name["r_hand"] + + if (H.hand) + temp = H.organs_by_name["l_hand"] + if(temp && !temp.is_usable()) + to_chat(user, "You try to move your [temp.name], but cannot!") + return + + to_chat(user, "You pick up the [src].") + user.put_in_hands(src) + + return /obj/item/sticky_pad/random/Initialize() . = ..() diff --git a/code/modules/paperwork/paperbin.dm b/code/modules/paperwork/paperbin.dm index f7a726d60d..69c14fe8b9 100644 --- a/code/modules/paperwork/paperbin.dm +++ b/code/modules/paperwork/paperbin.dm @@ -19,7 +19,7 @@ /obj/item/weapon/paper_bin/MouseDrop(mob/user as mob) - if((user == usr && (!( usr.restrained() ) && (!( usr.stat ) && (usr.contents.Find(src) || in_range(src, usr)))))) + if(user == usr && !(usr.restrained() || usr.stat) && (usr.contents.Find(src) || in_range(src, usr))) if(!istype(usr, /mob/living/simple_mob)) if( !usr.get_active_hand() ) //if active hand is empty var/mob/living/carbon/human/H = user diff --git a/code/modules/projectiles/ammunition.dm b/code/modules/projectiles/ammunition.dm index 65ec739d62..8c7e9a440b 100644 --- a/code/modules/projectiles/ammunition.dm +++ b/code/modules/projectiles/ammunition.dm @@ -29,8 +29,8 @@ set_dir(pick(cardinal)) //spin spent casings update_icon() -/obj/item/ammo_casing/attackby(obj/item/weapon/W as obj, mob/user as mob) - if(W.is_screwdriver()) +/obj/item/ammo_casing/attackby(obj/item/I as obj, mob/user as mob) + if(I.is_screwdriver()) if(!BB) to_chat(user, "There is no bullet in the casing to inscribe anything into.") return @@ -45,6 +45,39 @@ else to_chat(user, "You inscribe \"[label_text]\" into \the [initial(BB.name)].") BB.name = "[initial(BB.name)] (\"[label_text]\")" + else if(istype(I, /obj/item/ammo_magazine) && isturf(loc)) // Mass magazine reloading. + var/obj/item/ammo_magazine/box = I + if (!box.can_remove_ammo || box.reloading) + return ..() + + box.reloading = TRUE + var/boolets = 0 + var/turf/floor = loc + for(var/obj/item/ammo_casing/bullet in floor) + if(box.stored_ammo.len >= box.max_ammo) + break + if(box.caliber == bullet.caliber && bullet.BB) + if (boolets < 1) + to_chat(user, "You start collecting shells.") // Say it here so it doesn't get said if we don't find anything useful. + if(do_after(user,5,box)) + if(box.stored_ammo.len >= box.max_ammo) // Double check because these can change during the wait. + break + if(bullet.loc != floor) + continue + bullet.forceMove(box) + box.stored_ammo.Add(bullet) + box.update_icon() + boolets++ + else + break + + if(boolets > 0) + to_chat(user, "You collect [boolets] shell\s. [box] now contains [box.stored_ammo.len] shell\s.") + else + to_chat(user, "You fail to collect anything!") + box.reloading = FALSE + else + return ..() /obj/item/ammo_casing/update_icon() if(!BB) @@ -84,6 +117,7 @@ var/initial_ammo = null var/can_remove_ammo = TRUE // Can this thing have bullets removed one-by-one? As of first implementation, only affects smart magazines + var/reloading = FALSE // Is this magazine being reloaded, currently? - Currently only useful for automatic pickups, ignored by manual reloading. var/multiple_sprites = 0 //because BYOND doesn't support numbers as keys in associative lists @@ -115,7 +149,7 @@ to_chat(user, "[src] is full!") return user.remove_from_mob(C) - C.loc = src + C.forceMove(src) stored_ammo.Add(C) update_icon() if(istype(W, /obj/item/ammo_magazine/clip)) @@ -131,7 +165,7 @@ return var/obj/item/ammo_casing/AC = L.stored_ammo[1] //select the next casing. L.stored_ammo -= AC //Remove this casing from loaded list of the clip. - AC.loc = src + AC.forceMove(src) stored_ammo.Insert(1, AC) //add it to the head of our magazine's list L.update_icon() playsound(src, 'sound/weapons/flipblade.ogg', 50, 1) diff --git a/code/modules/projectiles/ammunition/magazines.dm b/code/modules/projectiles/ammunition/magazines.dm index 7ccdbfc9ba..1d08811bae 100644 --- a/code/modules/projectiles/ammunition/magazines.dm +++ b/code/modules/projectiles/ammunition/magazines.dm @@ -88,6 +88,10 @@ name = "magazine (.45 AP)" ammo_type = /obj/item/ammo_casing/a45/ap +/obj/item/ammo_magazine/m45/hp + name = "magazine (.45 HP)" + ammo_type = /obj/item/ammo_casing/a45/hp + /obj/item/ammo_magazine/box/emp/b45 name = "ammunition box (.45 haywire)" ammo_type = /obj/item/ammo_casing/a45/emp @@ -293,6 +297,11 @@ name = "top mounted magazine (9mm practice)" ammo_type = /obj/item/ammo_casing/a9mm/practice +/obj/item/ammo_magazine/m9mmt/ap + name = "top mounted magazine (9mm armor piercing)" + ammo_type = /obj/item/ammo_casing/a9mm/ap + matter = list(DEFAULT_WALL_MATERIAL = 1000, MAT_PLASTEEL = 2000) + /obj/item/ammo_magazine/m9mmp90 name = "large capacity top mounted magazine (9mm armor-piercing)" icon_state = "p90" diff --git a/code/modules/projectiles/ammunition/rounds.dm b/code/modules/projectiles/ammunition/rounds.dm index b83d7ad235..fa6c5d21aa 100644 --- a/code/modules/projectiles/ammunition/rounds.dm +++ b/code/modules/projectiles/ammunition/rounds.dm @@ -131,6 +131,7 @@ desc = "A .45 Armor-Piercing bullet casing." icon_state = "r-casing" projectile_type = /obj/item/projectile/bullet/pistol/medium/ap + matter = list(DEFAULT_WALL_MATERIAL = 50, MAT_PLASTEEL = 25) /obj/item/ammo_casing/a45/practice desc = "A .45 practice bullet casing." @@ -160,6 +161,7 @@ /obj/item/ammo_casing/a45/hp desc = "A .45 hollow-point bullet casing." projectile_type = /obj/item/projectile/bullet/pistol/medium/hp + matter = list(DEFAULT_WALL_MATERIAL = 60, MAT_PLASTIC = 15) /* * 10mm @@ -246,6 +248,14 @@ // projectile_type = /obj/item/projectile/bullet/shotgun/ion matter = list(DEFAULT_WALL_MATERIAL = 360, "uranium" = 240) +/obj/item/ammo_casing/a12g/flechette + name = "shotgun flechette" + desc = "A 12 gauge flechette cartidge, also known as nailshot." + icon_state = "slshell" + caliber = "12g" + projectile_type = /obj/item/projectile/scatter/flechette + matter = list(DEFAULT_WALL_MATERIAL = 360, MAT_PLASTEEL = 100) + /* * 7.62mm */ diff --git a/code/modules/projectiles/projectile/scatter.dm b/code/modules/projectiles/projectile/scatter.dm index 4b0511d0b4..b3900f8429 100644 --- a/code/modules/projectiles/projectile/scatter.dm +++ b/code/modules/projectiles/projectile/scatter.dm @@ -27,6 +27,10 @@ /obj/item/projectile/bullet/pellet/shotgun/flak = 3 ) +/* + * Energy + */ + /obj/item/projectile/scatter/laser damage = 40 @@ -61,6 +65,12 @@ /obj/item/projectile/bullet/shotgun/ion = 3 ) + +/* + * Flame + */ + + /obj/item/projectile/scatter/flamethrower damage = 5 submunition_spread_max = 100 @@ -70,3 +80,19 @@ submunitions = list( /obj/item/projectile/bullet/incendiary/flamethrower/tiny = 7 ) + + +/* + * Ballistic + */ + + +/obj/item/projectile/scatter/flechette + damage = 60 + + submunition_spread_max = 40 + submunition_spread_min = 10 + + submunitions = list( + /obj/item/projectile/bullet/magnetic/flechette/small = 4 + ) diff --git a/code/modules/reagents/Chemistry-Machinery.dm b/code/modules/reagents/Chemistry-Machinery.dm index 517385757b..97c81cf7cb 100644 --- a/code/modules/reagents/Chemistry-Machinery.dm +++ b/code/modules/reagents/Chemistry-Machinery.dm @@ -379,7 +379,7 @@ /obj/machinery/chem_master/tgui_act(action, params, datum/tgui/ui, datum/tgui_state/state) if(..()) - return + return TRUE if(tgui_act_modal(action, params, ui, state)) return TRUE @@ -667,6 +667,15 @@ if(length(holdingitems)) options["grind"] = radial_grind + if (usr.stat != 0) + return + if (!beaker) + return + beaker.loc = src.loc + beaker = null + visible_message("\The [usr] remove the container from \the [src].") + update_icon() + var/choice if(length(options) < 1) @@ -833,4 +842,4 @@ #undef MAX_MULTI_AMOUNT #undef MAX_UNITS_PER_PILL #undef MAX_UNITS_PER_PATCH -#undef MAX_CUSTOM_NAME_LEN \ No newline at end of file +#undef MAX_CUSTOM_NAME_LEN diff --git a/code/modules/reagents/Chemistry-Reagents.dm b/code/modules/reagents/Chemistry-Reagents.dm index 11986063a6..be747f821c 100644 --- a/code/modules/reagents/Chemistry-Reagents.dm +++ b/code/modules/reagents/Chemistry-Reagents.dm @@ -19,7 +19,7 @@ var/dose = 0 var/max_dose = 0 var/overdose = 0 //Amount at which overdose starts - var/overdose_mod = 2 //Modifier to overdose damage + var/overdose_mod = 1 //Modifier to overdose damage var/can_overdose_touch = FALSE // Can the chemical OD when processing on touch? var/scannable = 0 // Shows up on health analyzers. var/affects_dead = 0 @@ -146,7 +146,10 @@ if(ishuman(M)) var/mob/living/carbon/human/H = M overdose_mod *= H.species.chemOD_mod - M.adjustToxLoss(removed * overdose_mod) + // 6 damage per unit at minimum, scales with excessive reagents. Rounding should help keep damage consistent between ingest / inject, but isn't perfect. + // Hardcapped at 3.6 damage per tick, or 18 damage per unit at 0.2 metabolic rate so that you can't instakill people with overdoses by feeding them infinite periadaxon. + // Overall, max damage is slightly less effective than hydrophoron, and 1/5 as effective as cyanide. + M.adjustToxLoss(min(removed * overdose_mod * round(3 + 3 * volume / overdose), 3.6)) /datum/reagent/proc/initialize_data(var/newdata) // Called when the reagent is created. if(!isnull(newdata)) diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Core.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Core.dm index aa641df405..2a60a7d506 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Core.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Core.dm @@ -180,7 +180,17 @@ L.ExtinguishMob() L.water_act(amount / 25) // Div by 25, as water_act multiplies it by 5 in order to calculate firestack modification. remove_self(needed) - //YWedit start, readds promethean damage that was removed by vorestation. + // Put out cigarettes if splashed. + if(istype(L, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = L + if(H.wear_mask) + if(istype(H.wear_mask, /obj/item/clothing/mask/smokable)) + var/obj/item/clothing/mask/smokable/S = H.wear_mask + if(S.lit) + S.quench() + H.visible_message("[H]\'s [S.name] is put out.") + +//YWedit start, readds promethean damage that was removed by vorestation. /datum/reagent/water/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) if(alien == IS_SLIME) M.adjustToxLoss(6 * removed) diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm index e19ee6117b..425bf0d2b5 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm @@ -260,7 +260,7 @@ step(M, pick(cardinal)) if(prob(5)) M.emote(pick("twitch", "drool", "moan")) - M.adjustBrainLoss(0.1) + M.adjustBrainLoss(0.5 * removed) /datum/reagent/nitrogen name = "Nitrogen" diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm index fab198a14e..fcc56678df 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm @@ -48,6 +48,7 @@ reagent_state = LIQUID color = "#BF0000" overdose = REAGENTS_OVERDOSE + overdose_mod = 0.25 scannable = 1 /datum/reagent/bicaridine/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) @@ -214,6 +215,8 @@ reagent_state = LIQUID color = "#225722" scannable = 1 + overdose = REAGENTS_OVERDOSE * 0.5 + overdose_mod = 0 // Not used, but it shouldn't deal toxin damage anyways. Carth heals toxins! /datum/reagent/carthatoline/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) if(alien == IS_DIONA) @@ -234,6 +237,11 @@ if(alien == IS_SLIME) H.druggy = max(M.druggy, 5) +/datum/reagent/carthatoline/overdose(var/mob/living/carbon/M, var/alien, var/removed) + M.adjustHalLoss(2) + var/mob/living/carbon/human/H = M + H.internal_organs_by_name[O_STOMACH].take_damage(removed * 2) // Causes stomach contractions, makes sense for an overdose to make it much worse. + /datum/reagent/dexalin name = "Dexalin" id = "dexalin" @@ -267,6 +275,7 @@ reagent_state = LIQUID color = "#0040FF" overdose = REAGENTS_OVERDOSE * 0.5 + overdose_mod = 1.25 scannable = 1 /datum/reagent/dexalinp/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) @@ -441,7 +450,8 @@ taste_description = "bitterness" reagent_state = LIQUID color = "#C8A5DC" - overdose = 60 + overdose = REAGENTS_OVERDOSE * 2 + overdose_mod = 0.75 scannable = 1 metabolism = 0.02 mrate_static = TRUE @@ -465,7 +475,8 @@ taste_description = "sourness" reagent_state = LIQUID color = "#CB68FC" - overdose = 30 + overdose = REAGENTS_OVERDOSE + overdose_mod = 0.75 scannable = 1 metabolism = 0.02 mrate_static = TRUE @@ -489,6 +500,7 @@ reagent_state = LIQUID color = "#800080" overdose = 20 + overdose_mod = 0.75 scannable = 1 metabolism = 0.02 mrate_static = TRUE @@ -536,7 +548,7 @@ M.AdjustWeakened(-1) holder.remove_reagent("mindbreaker", 5) M.hallucination = max(0, M.hallucination - 10) - M.adjustToxLoss(5 * removed * chem_effective) // It used to be incredibly deadly due to an oversight. Not anymore! + M.adjustToxLoss(10 * removed * chem_effective) // It used to be incredibly deadly due to an oversight. Not anymore! M.add_chemical_effect(CE_PAINKILLER, 20 * chem_effective * M.species.chem_strength_pain) /datum/reagent/hyperzine @@ -547,6 +559,7 @@ reagent_state = LIQUID color = "#FF3300" overdose = REAGENTS_OVERDOSE * 0.5 + overdose_mod = 0.25 /datum/reagent/hyperzine/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) if(alien == IS_TAJARA) @@ -560,6 +573,13 @@ M.emote(pick("twitch", "blink_r", "shiver")) M.add_chemical_effect(CE_SPEEDBOOST, 1) +/datum/reagent/hyperzine/overdose(var/mob/living/carbon/M, var/alien, var/removed) + ..() + if(prob(5)) // 1 in 20 + var/mob/living/carbon/human/H = M + H.internal_organs_by_name[O_HEART].take_damage(1) + to_chat(M, "Huh... Is this what a heart attack feels like?") + /datum/reagent/alkysine name = "Alkysine" id = "alkysine" @@ -616,6 +636,7 @@ reagent_state = LIQUID color = "#561EC3" overdose = 10 + overdose_mod = 1.5 scannable = 1 /datum/reagent/peridaxon/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) @@ -635,6 +656,11 @@ if(prob(33)) H.Confuse(10) +/datum/reagent/peridaxon/overdose(var/mob/living/carbon/M, var/alien, var/removed) + ..() + M.adjustHalLoss(5) + M.hallucination = max(M.hallucination, 10) + /datum/reagent/osteodaxon name = "Osteodaxon" id = "osteodaxon" @@ -643,6 +669,7 @@ color = "#C9BCE3" metabolism = REM * 0.5 overdose = REAGENTS_OVERDOSE * 0.5 + overdose_mod = 1.5 scannable = 1 /datum/reagent/osteodaxon/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) @@ -665,6 +692,7 @@ color = "#4246C7" metabolism = REM * 0.5 overdose = REAGENTS_OVERDOSE * 0.5 + overdose_mod = 1.5 scannable = 1 var/repair_strength = 3 @@ -686,6 +714,23 @@ if(W.damage <= 0) O.wounds -= W +/datum/reagent/myelamine/overdose(var/mob/living/carbon/M, var/alien, var/removed) + // Copypaste of affect_blood with slight adjustment. Heals slightly faster at the cost of high toxins + ..() + if(ishuman(M)) + var/mob/living/carbon/human/H = M + var/wound_heal = removed * repair_strength / 2 + for(var/obj/item/organ/external/O in H.bad_external_organs) + for(var/datum/wound/W in O.wounds) + if(W.bleeding()) + W.damage = max(W.damage - wound_heal, 0) + if(W.damage <= 0) + O.wounds -= W + if(W.internal) + W.damage = max(W.damage - wound_heal, 0) + if(W.damage <= 0) + O.wounds -= W + /datum/reagent/respirodaxon name = "Respirodaxon" id = "respirodaxon" @@ -695,6 +740,7 @@ color = "#4444FF" metabolism = REM * 1.5 overdose = 10 + overdose_mod = 1.75 scannable = 1 /datum/reagent/respirodaxon/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) @@ -726,6 +772,7 @@ color = "#8B4513" metabolism = REM * 1.5 overdose = 10 + overdose_mod = 1.75 scannable = 1 /datum/reagent/gastirodaxon/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) @@ -757,6 +804,7 @@ color = "#D2691E" metabolism = REM * 1.5 overdose = 10 + overdose_mod = 1.75 scannable = 1 /datum/reagent/hepanephrodaxon/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) @@ -790,6 +838,7 @@ color = "#FF4444" metabolism = REM * 1.5 overdose = 10 + overdose_mod = 1.75 scannable = 1 /datum/reagent/cordradaxon/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) @@ -817,6 +866,7 @@ reagent_state = SOLID color = "#7B4D4F" overdose = 20 + overdose_mod = 1.5 scannable = 1 /datum/reagent/immunosuprizine/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) @@ -874,6 +924,7 @@ color = "#84B2B0" metabolism = REM * 0.75 overdose = 20 + overdose_mod = 1.5 scannable = 1 /datum/reagent/skrellimmuno/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) @@ -1032,6 +1083,7 @@ color = "#008000" metabolism = REM * 0.25 overdose = REAGENTS_OVERDOSE + overdose_mod = 1.25 scannable = 1 /datum/reagent/arithrazine/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) @@ -1080,6 +1132,7 @@ color = "#FFB0B0" mrate_static = TRUE overdose = 10 + overdose_mod = 1.5 scannable = 1 data = 0 @@ -1261,6 +1314,7 @@ reagent_state = SOLID color = "#669900" overdose = REAGENTS_OVERDOSE + overdose_mod = 2 scannable = 1 /datum/reagent/rezadone/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm index 240397c0e0..99b09caff5 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm @@ -423,6 +423,16 @@ if(prob(5)) M.vomit() +/datum/reagent/space_cleaner/touch_mob(var/mob/living/L, var/amount) + if(istype(L, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = L + if(H.wear_mask) + if(istype(H.wear_mask, /obj/item/clothing/mask/smokable)) + var/obj/item/clothing/mask/smokable/S = H.wear_mask + if(S.lit) + S.quench() // No smoking in my medbay! + H.visible_message("[H]\'s [S.name] is put out.") + /datum/reagent/lube // TODO: spraying on borgs speeds them up name = "Space Lube" id = "lube" diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm index b541d66b44..e18c2e56d9 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm @@ -46,6 +46,11 @@ color = "#792300" strength = 10 +/datum/reagent/toxin/amatoxin/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) + // Trojan horse. Waits until most of the toxin has gone through the body before dealing the bulk of it in one big strike. + if(volume < max_dose * 0.2) + M.adjustToxLoss(max_dose * strength * removed / (max_dose * 0.2)) + /datum/reagent/toxin/carpotoxin name = "Carpotoxin" id = "carpotoxin" @@ -55,6 +60,10 @@ color = "#003333" strength = 10 +/datum/reagent/toxin/carpotoxin/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) + ..() + M.adjustBrainLoss(strength / 4 * removed) + /datum/reagent/toxin/neurotoxic_protein name = "toxic protein" id = "neurotoxic_protein" @@ -212,6 +221,7 @@ color = "#d0583a" metabolism = REM * 3 overdose = 10 + overdose_mod = 0.5 strength = 3 /datum/reagent/toxin/stimm/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) @@ -225,6 +235,13 @@ M.take_organ_damage(6 * removed, 0) M.add_chemical_effect(CE_SPEEDBOOST, 1) +/datum/reagent/toxin/stimm/overdose(var/mob/living/carbon/M, var/alient, var/removed) + ..() + if(prob(10)) // 1 in 10. This thing's made with welder fuel and fertilizer, what do you expect? + var/mob/living/carbon/human/H = M + H.internal_organs_by_name[O_HEART].take_damage(1) + to_chat(M, "Huh... Is this what a heart attack feels like?") + /datum/reagent/toxin/potassium_chloride name = "Potassium Chloride" id = "potassium_chloride" @@ -658,7 +675,7 @@ metabolism = REM * 0.5 ingest_met = REM * 1.5 overdose = REAGENTS_OVERDOSE * 0.5 - overdose_mod = 5 //For that good, lethal feeling + overdose_mod = 2 //For that good, lethal feeling // Reduced with overdose changes. Slightly stronger than before /datum/reagent/chloralhydrate/affect_blood(var/mob/living/carbon/M, var/alien, var/removed) if(alien == IS_DIONA) diff --git a/code/modules/reagents/dispenser/dispenser2.dm b/code/modules/reagents/dispenser/dispenser2.dm index 1fdf6a24af..7f2fb13f40 100644 --- a/code/modules/reagents/dispenser/dispenser2.dm +++ b/code/modules/reagents/dispenser/dispenser2.dm @@ -156,7 +156,7 @@ /obj/machinery/chemical_dispenser/tgui_act(action, params) if(..()) - return + return TRUE . = TRUE switch(action) diff --git a/code/modules/recycling/disposal.dm b/code/modules/recycling/disposal.dm index fa19b46e1f..3437c8b582 100644 --- a/code/modules/recycling/disposal.dm +++ b/code/modules/recycling/disposal.dm @@ -260,18 +260,18 @@ /obj/machinery/disposal/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) if(..()) - return + return TRUE if(usr.loc == src) to_chat(usr, "You cannot reach the controls from inside.") - return + return TRUE if(mode==-1 && action != "eject") // If the mode is -1, only allow ejection to_chat(usr, "The disposal units power is disabled.") - return + return TRUE if(stat & BROKEN) - return + return TRUE add_fingerprint(usr) diff --git a/code/modules/research/circuitprinter.dm b/code/modules/research/circuitprinter.dm index 468c7ff6bb..d0aaebb7d8 100644 --- a/code/modules/research/circuitprinter.dm +++ b/code/modules/research/circuitprinter.dm @@ -16,7 +16,7 @@ using metal and glass, it uses glass and reagents (usually sulphuric acid). var/mat_efficiency = 1 var/speed = 1 - materials = list(DEFAULT_WALL_MATERIAL = 0, "glass" = 0, MAT_PLASTEEL = 0, "plastic" = 0, MAT_GRAPHITE, "gold" = 0, "silver" = 0, "osmium" = 0, MAT_LEAD = 0, "phoron" = 0, "uranium" = 0, "diamond" = 0, MAT_DURASTEEL = 0, MAT_VERDANTIUM = 0, MAT_MORPHIUM = 0, MAT_METALHYDROGEN = 0, MAT_SUPERMATTER = 0) + materials = list(DEFAULT_WALL_MATERIAL = 0, "glass" = 0, MAT_PLASTEEL = 0, "plastic" = 0, MAT_GRAPHITE = 0, "gold" = 0, "silver" = 0, "osmium" = 0, MAT_LEAD = 0, "phoron" = 0, "uranium" = 0, "diamond" = 0, MAT_DURASTEEL = 0, MAT_VERDANTIUM = 0, MAT_MORPHIUM = 0, MAT_METALHYDROGEN = 0, MAT_SUPERMATTER = 0) hidden_materials = list(MAT_PLASTEEL, MAT_DURASTEEL, MAT_GRAPHITE, MAT_VERDANTIUM, MAT_MORPHIUM, MAT_METALHYDROGEN, MAT_SUPERMATTER) diff --git a/code/modules/research/designs/circuits/circuits.dm b/code/modules/research/designs/circuits/circuits.dm index e2479a8e1c..425223ffe7 100644 --- a/code/modules/research/designs/circuits/circuits.dm +++ b/code/modules/research/designs/circuits/circuits.dm @@ -493,6 +493,8 @@ CIRCUITS BELOW name = "'Durand' central control" id = "durand_main" req_tech = list(TECH_DATA = 4) + materials = list("glass" = 2000, MAT_GRAPHITE = 1250) + chemicals = list("pacid" = 20) build_path = /obj/item/weapon/circuitboard/mecha/durand/main sort_string = "NAADA" @@ -500,6 +502,8 @@ CIRCUITS BELOW name = "'Durand' peripherals control" id = "durand_peri" req_tech = list(TECH_DATA = 4) + materials = list("glass" = 2000, MAT_GRAPHITE = 1250) + chemicals = list("pacid" = 20) build_path = /obj/item/weapon/circuitboard/mecha/durand/peripherals sort_string = "NAADB" @@ -507,6 +511,8 @@ CIRCUITS BELOW name = "'Durand' weapon control and targeting" id = "durand_targ" req_tech = list(TECH_DATA = 4, TECH_COMBAT = 2) + materials = list("glass" = 2000, MAT_GRAPHITE = 1250) + chemicals = list("pacid" = 20) build_path = /obj/item/weapon/circuitboard/mecha/durand/targeting sort_string = "NAADC" diff --git a/code/modules/research/designs/weapons.dm b/code/modules/research/designs/weapons.dm index e475d6752f..9ba1ce297f 100644 --- a/code/modules/research/designs/weapons.dm +++ b/code/modules/research/designs/weapons.dm @@ -100,15 +100,24 @@ sort_string = "MABBA" /datum/design/item/weapon/ballistic/ammo/stunshell - name = "stun shell" + name = "stun shells" desc = "A stunning shell for a shotgun." id = "stunshell" req_tech = list(TECH_COMBAT = 3, TECH_MATERIAL = 3) materials = list(DEFAULT_WALL_MATERIAL = 4000) - build_path = /obj/item/ammo_casing/a12g/stunshell + build_path = /obj/item/weapon/storage/box/stunshells sort_string = "MABBB" autolathe_build = 1 //Ywedit +/datum/design/item/weapon/ballistic/ammo/empshell + name = "emp shells" + desc = "An electromagnetic shell for a shotgun." + id = "empshell" + req_tech = list(TECH_COMBAT = 4, TECH_MAGNET = 3) + materials = list(DEFAULT_WALL_MATERIAL = 4000, MAT_URANIUM = 1000) + build_path = /obj/item/weapon/storage/box/empshells + sort_string = "MABBC" + // Phase weapons /datum/design/item/weapon/phase/AssembleDesignName() diff --git a/code/modules/research/mechfab_designs.dm b/code/modules/research/mechfab_designs.dm index 7316c42179..899b5fd9db 100644 --- a/code/modules/research/mechfab_designs.dm +++ b/code/modules/research/mechfab_designs.dm @@ -177,57 +177,57 @@ name = "Durand Chassis" id = "durand_chassis" build_path = /obj/item/mecha_parts/chassis/durand - time = 10 - materials = list(DEFAULT_WALL_MATERIAL = 18750) + time = 20 + materials = list(DEFAULT_WALL_MATERIAL = 18750, MAT_PLASTEEL = 20000) /datum/design/item/mechfab/durand/torso name = "Durand Torso" id = "durand_torso" build_path = /obj/item/mecha_parts/part/durand_torso - time = 30 - materials = list(DEFAULT_WALL_MATERIAL = 41250, "glass" = 15000, "silver" = 7500) + time = 60 + materials = list(DEFAULT_WALL_MATERIAL = 41250, MAT_PLASTEEL = 15000, "silver" = 7500) /datum/design/item/mechfab/durand/head name = "Durand Head" id = "durand_head" build_path = /obj/item/mecha_parts/part/durand_head - time = 20 + time = 40 materials = list(DEFAULT_WALL_MATERIAL = 18750, "glass" = 7500, "silver" = 2250) /datum/design/item/mechfab/durand/left_arm name = "Durand Left Arm" id = "durand_left_arm" build_path = /obj/item/mecha_parts/part/durand_left_arm - time = 20 + time = 40 materials = list(DEFAULT_WALL_MATERIAL = 26250, "silver" = 2250) /datum/design/item/mechfab/durand/right_arm name = "Durand Right Arm" id = "durand_right_arm" build_path = /obj/item/mecha_parts/part/durand_right_arm - time = 20 + time = 40 materials = list(DEFAULT_WALL_MATERIAL = 26250, "silver" = 2250) /datum/design/item/mechfab/durand/left_leg name = "Durand Left Leg" id = "durand_left_leg" build_path = /obj/item/mecha_parts/part/durand_left_leg - time = 20 + time = 40 materials = list(DEFAULT_WALL_MATERIAL = 30000, "silver" = 2250) /datum/design/item/mechfab/durand/right_leg name = "Durand Right Leg" id = "durand_right_leg" build_path = /obj/item/mecha_parts/part/durand_right_leg - time = 20 + time = 40 materials = list(DEFAULT_WALL_MATERIAL = 30000, "silver" = 2250) /datum/design/item/mechfab/durand/armour name = "Durand Armour Plates" id = "durand_armour" build_path = /obj/item/mecha_parts/part/durand_armour - time = 60 - materials = list(DEFAULT_WALL_MATERIAL = 37500, "uranium" = 7500) + time = 90 + materials = list(DEFAULT_WALL_MATERIAL = 27500, MAT_PLASTEEL = 10000, "uranium" = 7500) /datum/design/item/mechfab/janus category = "Janus" @@ -1038,4 +1038,151 @@ time = 20 req_tech = list(TECH_MATERIAL = 6, TECH_ENGINEERING = 5, TECH_PHORON = 3, TECH_MAGNET = 4, TECH_POWER = 6) materials = list(DEFAULT_WALL_MATERIAL = 10000, "glass" = 6000, "silver" = 4000) - \ No newline at end of file + +// Exosuit Internals + +/datum/design/item/mechfab/exointernal + category = "Exosuit Internals" + time = 120 + req_tech = list(TECH_MATERIAL = 3, TECH_ENGINEERING = 3) + +/datum/design/item/mechfab/exointernal/stan_armor + name = "Armor Plate (Standard)" + category = "Exosuit Internals" + id = "exo_int_armor_standard" + req_tech = list(TECH_MATERIAL = 2, TECH_ENGINEERING = 2) + materials = list(DEFAULT_WALL_MATERIAL = 10000) + build_path = /obj/item/mecha_parts/component/armor + +/datum/design/item/mechfab/exointernal/light_armor + name = "Armor Plate (Lightweight)" + category = "Exosuit Internals" + id = "exo_int_armor_lightweight" + req_tech = list(TECH_MATERIAL = 2, TECH_ENGINEERING = 3) + materials = list(DEFAULT_WALL_MATERIAL = 5000, MAT_PLASTIC = 3000) + build_path = /obj/item/mecha_parts/component/armor/lightweight + +/datum/design/item/mechfab/exointernal/reinf_armor + name = "Armor Plate (Reinforced)" + category = "Exosuit Internals" + id = "exo_int_armor_reinforced" + req_tech = list(TECH_MATERIAL = 4, TECH_ENGINEERING = 4) + materials = list(DEFAULT_WALL_MATERIAL = 20000, MAT_PLASTEEL = 10000) + build_path = /obj/item/mecha_parts/component/armor/reinforced + +/datum/design/item/mechfab/exointernal/mining_armor + name = "Armor Plate (Blast)" + category = "Exosuit Internals" + id = "exo_int_armor_blast" + req_tech = list(TECH_MATERIAL = 4, TECH_ENGINEERING = 4) + materials = list(DEFAULT_WALL_MATERIAL = 20000, MAT_PLASTEEL = 10000) + build_path = /obj/item/mecha_parts/component/armor/mining + +/datum/design/item/mechfab/exointernal/gygax_armor + name = "Armor Plate (Marshal)" + category = "Exosuit Internals" + id = "exo_int_armor_gygax" + req_tech = list(TECH_MATERIAL = 5, TECH_ENGINEERING = 4, TECH_COMBAT = 2) + materials = list(DEFAULT_WALL_MATERIAL = 40000, MAT_DIAMOND = 8000) + build_path = /obj/item/mecha_parts/component/armor/marshal + +/datum/design/item/mechfab/exointernal/darkgygax_armor + name = "Armor Plate (Blackops)" + category = "Exosuit Internals" + id = "exo_int_armor_dgygax" + req_tech = list(TECH_MATERIAL = 5, TECH_ENGINEERING = 5, TECH_COMBAT = 4, TECH_ILLEGAL = 2) + materials = list(MAT_PLASTEEL = 20000, MAT_DIAMOND = 10000, MAT_GRAPHITE = 20000) + build_path = /obj/item/mecha_parts/component/armor/marshal/reinforced + +/datum/design/item/mechfab/exointernal/durand_armour + name = "Armor Plate (Military)" + id = "exo_int_armor_durand" + req_tech = list(TECH_MATERIAL = 5, TECH_ENGINEERING = 4, TECH_COMBAT = 2) + materials = list(DEFAULT_WALL_MATERIAL = 40000, MAT_PLASTEEL = 9525, "uranium" = 8000) + build_path = /obj/item/mecha_parts/component/armor/military + +/datum/design/item/mechfab/exointernal/marauder_armour + name = "Armor Plate (Cutting Edge)" + id = "exo_int_armor_marauder" + req_tech = list(TECH_MATERIAL = 8, TECH_ENGINEERING = 7, TECH_COMBAT = 6, TECH_ILLEGAL = 4) + materials = list(MAT_DURASTEEL = 40000, MAT_GRAPHITE = 8000, MAT_OSMIUM = 8000) + build_path = /obj/item/mecha_parts/component/armor/military/marauder + +/datum/design/item/mechfab/exointernal/phazon_armour + name = "Armor Plate (Janus)" + id = "exo_int_armor_phazon" + req_tech = list(TECH_MATERIAL = 6, TECH_ENGINEERING = 6, TECH_COMBAT = 6, TECH_ILLEGAL = 4) + materials = list(MAT_MORPHIUM = 40000, MAT_DURASTEEL = 8000, MAT_OSMIUM = 8000) + build_path = /obj/item/mecha_parts/component/armor/alien + +/datum/design/item/mechfab/exointernal/stan_hull + name = "Hull (Standard)" + category = "Exosuit Internals" + id = "exo_int_hull_standard" + req_tech = list(TECH_MATERIAL = 2, TECH_ENGINEERING = 2) + materials = list(DEFAULT_WALL_MATERIAL = 10000) + build_path = /obj/item/mecha_parts/component/hull + +/datum/design/item/mechfab/exointernal/durable_hull + name = "Hull (Durable)" + category = "Exosuit Internals" + id = "exo_int_hull_durable" + req_tech = list(TECH_MATERIAL = 2, TECH_ENGINEERING = 2) + materials = list(DEFAULT_WALL_MATERIAL = 8000, MAT_PLASTEEL = 5000) + build_path = /obj/item/mecha_parts/component/hull/durable + +/datum/design/item/mechfab/exointernal/light_hull + name = "Hull (Lightweight)" + category = "Exosuit Internals" + id = "exo_int_hull_light" + req_tech = list(TECH_MATERIAL = 3, TECH_ENGINEERING = 4) + materials = list(DEFAULT_WALL_MATERIAL = 5000, MAT_PLASTIC = 3000) + build_path = /obj/item/mecha_parts/component/hull/lightweight + +/datum/design/item/mechfab/exointernal/stan_gas + name = "Life-Support (Standard)" + category = "Exosuit Internals" + id = "exo_int_lifesup_standard" + req_tech = list(TECH_MATERIAL = 2, TECH_ENGINEERING = 2) + materials = list(DEFAULT_WALL_MATERIAL = 10000) + build_path = /obj/item/mecha_parts/component/gas + +/datum/design/item/mechfab/exointernal/reinf_gas + name = "Life-Support (Reinforced)" + category = "Exosuit Internals" + id = "exo_int_lifesup_reinforced" + req_tech = list(TECH_MATERIAL = 4, TECH_ENGINEERING = 4) + materials = list(DEFAULT_WALL_MATERIAL = 8000, MAT_PLASTEEL = 8000, MAT_GRAPHITE = 1000) + build_path = /obj/item/mecha_parts/component/gas/reinforced + +/datum/design/item/mechfab/exointernal/stan_electric + name = "Electrical Harness (Standard)" + category = "Exosuit Internals" + id = "exo_int_electric_standard" + req_tech = list(TECH_POWER = 2, TECH_ENGINEERING = 2) + materials = list(DEFAULT_WALL_MATERIAL = 5000, MAT_PLASTIC = 1000) + build_path = /obj/item/mecha_parts/component/electrical + +/datum/design/item/mechfab/exointernal/efficient_electric + name = "Electrical Harness (High)" + category = "Exosuit Internals" + id = "exo_int_electric_efficient" + req_tech = list(TECH_POWER = 4, TECH_ENGINEERING = 4, TECH_DATA = 2) + materials = list(DEFAULT_WALL_MATERIAL = 5000, MAT_PLASTIC = 3000, MAT_SILVER = 3000) + build_path = /obj/item/mecha_parts/component/electrical/high_current + +/datum/design/item/mechfab/exointernal/stan_actuator + name = "Actuator Lattice (Standard)" + category = "Exosuit Internals" + id = "exo_int_actuator_standard" + req_tech = list(TECH_MATERIAL = 2, TECH_ENGINEERING = 2) + materials = list(DEFAULT_WALL_MATERIAL = 10000) + build_path = /obj/item/mecha_parts/component/actuator + +/datum/design/item/mechfab/exointernal/hispeed_actuator + name = "Actuator Lattice (Overclocked)" + category = "Exosuit Internals" + id = "exo_int_actuator_overclock" + req_tech = list(TECH_MATERIAL = 5, TECH_ENGINEERING = 4, TECH_POWER = 4) + materials = list(MAT_PLASTEEL = 10000, MAT_OSMIUM = 3000, MAT_GOLD = 5000) + build_path = /obj/item/mecha_parts/component/actuator/hispeed \ No newline at end of file diff --git a/code/modules/research/prosfab_designs_vr.dm b/code/modules/research/prosfab_designs_vr.dm index 02f39bd307..094d036c91 100644 --- a/code/modules/research/prosfab_designs_vr.dm +++ b/code/modules/research/prosfab_designs_vr.dm @@ -5,4 +5,11 @@ id = "borg_sizeshift_module" req_tech = list(TECH_BLUESPACE = 3, TECH_MATERIAL = 3, TECH_POWER = 2) materials = list(DEFAULT_WALL_MATERIAL = 4000, "glass" = 4000) - build_path = /obj/item/borg/upgrade/sizeshift \ No newline at end of file + build_path = /obj/item/borg/upgrade/sizeshift + +/datum/design/item/prosfab/robot_upgrade/bellysizeupgrade + name = "Size Alteration Module" + id = "borg_hound_capacity_module" + req_tech = list(TECH_BLUESPACE = 3, TECH_MATERIAL = 3, TECH_POWER = 2) + materials = list(DEFAULT_WALL_MATERIAL = 4000, "glass" = 4000) + build_path = /obj/item/borg/upgrade/bellysizeupgrade \ No newline at end of file diff --git a/code/modules/resleeving/computers.dm b/code/modules/resleeving/computers.dm index f6e11c5318..8a3d0867d9 100644 --- a/code/modules/resleeving/computers.dm +++ b/code/modules/resleeving/computers.dm @@ -97,6 +97,7 @@ active_br = new /datum/transhuman/body_record(brDisk.stored) // Loads a COPY! to_chat(user, "\The [src] loads the body record from \the [W] before ejecting it.") attack_hand(user) + view_b_rec("view_b_rec", list("ref" = "\ref[active_br]")) else ..() return @@ -195,7 +196,7 @@ /obj/machinery/computer/transhuman/resleeving/tgui_act(action, params) if(..()) - return + return TRUE . = TRUE switch(tgui_modal_act(src, action, params)) @@ -216,42 +217,7 @@ switch(action) if("view_b_rec") - var/ref = params["ref"] - if(!length(ref)) - return - active_br = locate(ref) - if(istype(active_br)) - if(isnull(active_br.ckey)) - qdel(active_br) - set_temp("Error: Record corrupt.", "danger") - else - var/can_grow_active = 1 - if(!synthetic_capable && active_br.synthetic) //Disqualified due to being synthetic in an organic only. - can_grow_active = 0 - set_temp("Error: Cannot grow [active_br.mydna.name] due to lack of synthfabs.", "danger") - else if(!organic_capable && !active_br.synthetic) //Disqualified for the opposite. - can_grow_active = 0 - set_temp("Error: Cannot grow [active_br.mydna.name] due to lack of cloners.", "danger") - else if(!synthetic_capable && !organic_capable) //What have you done?? - can_grow_active = 0 - set_temp("Error: Cannot grow [active_br.mydna.name] due to lack of synthfabs and cloners.", "danger") - else if(active_br.toocomplex) - can_grow_active = 0 - set_temp("Error: Cannot grow [active_br.mydna.name] due to species complexity.", "danger") - var/list/payload = list( - activerecord = "\ref[active_br]", - realname = sanitize(active_br.mydna.name), - species = active_br.speciesname ? active_br.speciesname : active_br.mydna.dna.species, - sex = active_br.bodygender, - mind_compat = active_br.locked ? "Low" : "High", - synthetic = active_br.synthetic, - oocnotes = active_br.body_oocnotes ? active_br.body_oocnotes : "None", - can_grow_active = can_grow_active, - ) - tgui_modal_message(src, action, "", null, payload) - else - active_br = null - set_temp("Error: Record missing.", "danger") + view_b_rec(action, params) if("view_m_rec") var/ref = params["ref"] if(!length(ref)) @@ -266,15 +232,12 @@ if(!LAZYLEN(sleevers)) can_sleeve_active = 0 set_temp("Error: Cannot sleeve due to no sleevers.", "danger") - return if(!selected_sleever) can_sleeve_active = 0 set_temp("Error: Cannot sleeve due to no selected sleever.", "danger") - return if(selected_sleever && !selected_sleever.occupant) can_sleeve_active = 0 set_temp("Error: Cannot sleeve due to lack of sleever occupant.", "danger") - return var/list/payload = list( activerecord = "\ref[active_mr]", realname = sanitize(active_mr.mindname), @@ -520,6 +483,40 @@ if(update_now) SStgui.update_uis(src) +/obj/machinery/computer/transhuman/resleeving/proc/view_b_rec(action, params) + var/ref = params["ref"] + if(!length(ref)) + return + active_br = locate(ref) + if(istype(active_br)) + var/can_grow_active = 1 + if(!synthetic_capable && active_br.synthetic) //Disqualified due to being synthetic in an organic only. + can_grow_active = 0 + set_temp("Error: Cannot grow [active_br.mydna.name] due to lack of synthfabs.", "danger") + else if(!organic_capable && !active_br.synthetic) //Disqualified for the opposite. + can_grow_active = 0 + set_temp("Error: Cannot grow [active_br.mydna.name] due to lack of cloners.", "danger") + else if(!synthetic_capable && !organic_capable) //What have you done?? + can_grow_active = 0 + set_temp("Error: Cannot grow [active_br.mydna.name] due to lack of synthfabs and cloners.", "danger") + else if(active_br.toocomplex) + can_grow_active = 0 + set_temp("Error: Cannot grow [active_br.mydna.name] due to species complexity.", "danger") + var/list/payload = list( + activerecord = "\ref[active_br]", + realname = sanitize(active_br.mydna.name), + species = active_br.speciesname ? active_br.speciesname : active_br.mydna.dna.species, + sex = active_br.bodygender, + mind_compat = active_br.locked ? "Low" : "High", + synthetic = active_br.synthetic, + oocnotes = active_br.body_oocnotes ? active_br.body_oocnotes : "None", + can_grow_active = can_grow_active, + ) + tgui_modal_message(src, action, "", null, payload) + else + active_br = null + set_temp("Error: Record missing.", "danger") + #undef MENU_MAIN #undef MENU_BODY #undef MENU_MIND \ No newline at end of file diff --git a/code/modules/tgui/external.dm b/code/modules/tgui/external.dm index 7e75f9343c..19f039869e 100644 --- a/code/modules/tgui/external.dm +++ b/code/modules/tgui/external.dm @@ -12,9 +12,10 @@ * * required user mob The mob who opened/is using the UI. * optional ui datum/tgui The UI to be updated, if it exists. + * optional parent_ui datum/tgui A parent UI that, when closed, closes this UI as well. */ -/datum/proc/tgui_interact(mob/user, datum/tgui/ui = null) +/datum/proc/tgui_interact(mob/user, datum/tgui/ui = null, datum/tgui/parent_ui = null) return FALSE // Not implemented. /** @@ -27,7 +28,7 @@ * * return list Data to be sent to the UI. */ -/datum/proc/tgui_data(mob/user) +/datum/proc/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state) return list() // Not implemented. /** diff --git a/code/modules/tgui/modules/_base.dm b/code/modules/tgui/modules/_base.dm index 23b12b8bd1..46dbc655ff 100644 --- a/code/modules/tgui/modules/_base.dm +++ b/code/modules/tgui/modules/_base.dm @@ -12,12 +12,15 @@ Code is pretty much ripped verbatim from nano modules, but with un-needed stuff var/list/using_access var/tgui_id + var/ntos = FALSE /datum/tgui_module/New(var/host) src.host = host + if(ntos) + tgui_id = "Ntos" + tgui_id /datum/tgui_module/tgui_host() - return host ? host : src + return host ? host.tgui_host() : src /datum/tgui_module/tgui_close(mob/user) if(host) @@ -43,4 +46,34 @@ Code is pretty much ripped verbatim from nano modules, but with un-needed stuff if(access in I.access) return 1 - return 0 \ No newline at end of file + return 0 + +/datum/tgui_module/tgui_static_data() + . = ..() + + var/obj/item/modular_computer/host = tgui_host() + if(istype(host)) + . += host.get_header_data() + +/datum/tgui_module/tgui_act(action, params) + if(..()) + return TRUE + + var/obj/item/modular_computer/host = tgui_host() + if(istype(host)) + if(action == "PC_exit") + host.kill_program() + return TRUE + if(action == "PC_shutdown") + host.shutdown_computer() + return TRUE + if(action == "PC_minimize") + host.minimize_program(usr) + return TRUE + +// Just a nice little default interact in case the subtypes don't need any special behavior here +/datum/tgui_module/tgui_interact(mob/user, datum/tgui/ui = null) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, tgui_id, name) + ui.open() \ No newline at end of file diff --git a/code/modules/tgui/modules/camera.dm b/code/modules/tgui/modules/camera.dm index b7305ca165..e1223fad74 100644 --- a/code/modules/tgui/modules/camera.dm +++ b/code/modules/tgui/modules/camera.dm @@ -144,7 +144,7 @@ return data /datum/tgui_module/camera/tgui_static_data(mob/user) - var/list/data = list() + var/list/data = ..() data["mapRef"] = map_name var/list/cameras = get_available_cameras(user) data["cameras"] = list() @@ -160,11 +160,11 @@ /datum/tgui_module/camera/tgui_act(action, params) if(..()) - return + return TRUE if(action && !issilicon(usr)) playsound(tgui_host(), "terminal_type", 50, 1) - + if(action == "switch_camera") var/c_tag = params["name"] var/list/cameras = get_available_cameras(usr) @@ -287,33 +287,7 @@ // If/when that is done, just move all the PC_ specific data and stuff to the modular computers themselves // instead of copying this approach here. /datum/tgui_module/camera/ntos - tgui_id = "NtosCameraConsole" - -/datum/tgui_module/camera/ntos/tgui_state() - return GLOB.tgui_ntos_state - -/datum/tgui_module/camera/ntos/tgui_static_data() - . = ..() - - var/datum/computer_file/program/host = tgui_host() - if(istype(host) && host.computer) - . += host.computer.get_header_data() - -/datum/tgui_module/camera/ntos/tgui_act(action, params) - if(..()) - return - - var/datum/computer_file/program/host = tgui_host() - if(istype(host) && host.computer) - if(action == "PC_exit") - host.computer.kill_program() - return TRUE - if(action == "PC_shutdown") - host.computer.shutdown_computer() - return TRUE - if(action == "PC_minimize") - host.computer.minimize_program(usr) - return TRUE + ntos = TRUE // ERT Version provides some additional networks. /datum/tgui_module/camera/ntos/ert diff --git a/code/modules/tgui/modules/crew_monitor.dm b/code/modules/tgui/modules/crew_monitor.dm index 247db8cb6c..86e039222c 100644 --- a/code/modules/tgui/modules/crew_monitor.dm +++ b/code/modules/tgui/modules/crew_monitor.dm @@ -24,7 +24,7 @@ return TRUE if("setZLevel") ui.set_map_z_level(params["mapZLevel"]) - SStgui.update_uis(src) + return TRUE /datum/tgui_module/crew_monitor/tgui_interact(mob/user, datum/tgui/ui = null) var/z = get_z(user) @@ -43,7 +43,7 @@ ui.open() -/datum/tgui_module/crew_monitor/tgui_data(mob/user, ui_key = "main", datum/tgui_state/state = GLOB.tgui_default_state) +/datum/tgui_module/crew_monitor/tgui_data(mob/user) var/data[0] data["isAI"] = isAI(user) @@ -59,33 +59,7 @@ return data /datum/tgui_module/crew_monitor/ntos - tgui_id = "NtosCrewMonitor" - -/datum/tgui_module/crew_monitor/ntos/tgui_state(mob/user) - return GLOB.tgui_ntos_state - -/datum/tgui_module/crew_monitor/ntos/tgui_static_data() - . = ..() - - var/datum/computer_file/program/host = tgui_host() - if(istype(host) && host.computer) - . += host.computer.get_header_data() - -/datum/tgui_module/crew_monitor/ntos/tgui_act(action, params) - if(..()) - return - - var/datum/computer_file/program/host = tgui_host() - if(istype(host) && host.computer) - if(action == "PC_exit") - host.computer.kill_program() - return TRUE - if(action == "PC_shutdown") - host.computer.shutdown_computer() - return TRUE - if(action == "PC_minimize") - host.computer.minimize_program(usr) - return TRUE + ntos = TRUE // Subtype for glasses_state /datum/tgui_module/crew_monitor/glasses diff --git a/code/modules/tgui/states/default.dm b/code/modules/tgui/states/default.dm index 9ccc9c751e..ba8d132e0b 100644 --- a/code/modules/tgui/states/default.dm +++ b/code/modules/tgui/states/default.dm @@ -36,13 +36,29 @@ GLOBAL_DATUM_INIT(tgui_default_state, /datum/tgui_state/default, new) return STATUS_DISABLED // Otherwise they can keep the UI open. /mob/living/silicon/ai/default_can_use_tgui_topic(src_object) - . = shared_tgui_interaction(src_object) - if(. < STATUS_INTERACTIVE) + . = shared_tgui_interaction() + if(. != STATUS_INTERACTIVE) return - // The AI can interact with anything it can see nearby, or with cameras while wireless control is enabled. - if(!control_disabled && can_see(src_object)) + // Prevents the AI from using Topic on admin levels (by for example viewing through the court/thunderdome cameras) + // unless it's on the same level as the object it's interacting with. + var/turf/T = get_turf(src_object) + if(!T || !(z == T.z || (T.z in using_map.player_levels))) + return STATUS_CLOSE + + // If an object is in view then we can interact with it + if(src_object in view(client.view, src)) return STATUS_INTERACTIVE + + // If we're installed in a chassi, rather than transfered to an inteliCard or other container, then check if we have camera view + if(is_in_chassis()) + //stop AIs from leaving windows open and using then after they lose vision + if(cameranet && !cameranet.checkTurfVis(get_turf(src_object))) + return STATUS_CLOSE + return STATUS_INTERACTIVE + else if(get_dist(src_object, src) <= client.view) // View does not return what one would expect while installed in an inteliCard + return STATUS_INTERACTIVE + return STATUS_CLOSE /mob/living/simple_animal/default_can_use_tgui_topic(src_object) diff --git a/code/modules/tgui/states/ntos.dm b/code/modules/tgui/states/ntos.dm deleted file mode 100644 index d6a5533e1e..0000000000 --- a/code/modules/tgui/states/ntos.dm +++ /dev/null @@ -1,15 +0,0 @@ - /** - * tgui state: ntos_state - * - * Checks a number of things -- mostly physical distance for humans and view for robots. - * This is basically the same as default, except instead of src_object, it uses the computer - * it's attached to. - **/ - -GLOBAL_DATUM_INIT(tgui_ntos_state, /datum/tgui_state/ntos, new) - -/datum/tgui_state/ntos/can_use_topic(src_object, mob/user) - var/datum/computer_file/program/P = src_object - if(!istype(P) || !P.computer) - return FALSE - return user.default_can_use_tgui_topic(P.computer) // Call the individual mob-overridden procs. diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index 9af1296ab0..191078f675 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -34,8 +34,12 @@ var/status = STATUS_INTERACTIVE /// Topic state used to determine status/interactability. var/datum/tgui_state/state = null - // The map z-level to display. + /// The map z-level to display. var/map_z_level = 1 + /// The Parent UI + var/datum/tgui/parent_ui + /// Children of this UI + var/list/children = list() /** * public @@ -46,12 +50,13 @@ * required src_object datum The object or datum which owns the UI. * required interface string The interface used to render the UI. * optional title string The title of the UI. + * optional parent_ui datum/tgui The parent of this UI. * optional ui_x int Deprecated: Window width. * optional ui_y int Deprecated: Window height. * * return datum/tgui The requested UI. */ -/datum/tgui/New(mob/user, datum/src_object, interface, title, ui_x, ui_y) +/datum/tgui/New(mob/user, datum/src_object, interface, title, datum/tgui/parent_ui, ui_x, ui_y) src.user = user src.src_object = src_object src.window_key = "[REF(src_object)]-main" @@ -59,6 +64,9 @@ if(title) src.title = title src.state = src_object.tgui_state() + src.parent_ui = parent_ui + if(parent_ui) + parent_ui.children += src // Deprecated if(ui_x && ui_y) src.window_size = list(ui_x, ui_y) @@ -100,10 +108,13 @@ * * Close the UI, and all its children. */ -/datum/tgui/proc/close(can_be_suspended = TRUE) +/datum/tgui/proc/close(can_be_suspended = TRUE, logout = FALSE) if(closing) return closing = TRUE + for(var/datum/tgui/child in children) + child.close() + children.Cut() // If we don't have window_id, open proc did not have the opportunity // to finish, therefore it's safe to skip this whole block. if(window) @@ -111,10 +122,11 @@ // and we want to keep them around, to allow user to read // the error message properly. window.release_lock() - window.close(can_be_suspended) + window.close(can_be_suspended, logout) src_object.tgui_close(user) SStgui.on_close(src) state = null + parent_ui = null qdel(src) /** @@ -209,7 +221,7 @@ "observer" = isobserver(user), ), ) - var/data = custom_data || with_data && src_object.tgui_data(user) + var/data = custom_data || with_data && src_object.tgui_data(user, src, state) if(data) json_data["data"] = data var/static_data = with_static_data && src_object.tgui_static_data(user) @@ -244,7 +256,7 @@ return // Update through a normal call to ui_interact if(status != STATUS_DISABLED && (autoupdate || force)) - src_object.tgui_interact(user, src) + src_object.tgui_interact(user, src, parent_ui) return // Update status only var/needs_update = process_status() @@ -262,6 +274,8 @@ /datum/tgui/proc/process_status() var/prev_status = status status = src_object.tgui_status(user, state) + if(parent_ui) + status = min(status, parent_ui.status) return prev_status != status /datum/tgui/proc/log_message(message) diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm index 26533447d4..c78eda86d3 100644 --- a/code/modules/tgui/tgui_window.dm +++ b/code/modules/tgui/tgui_window.dm @@ -133,14 +133,19 @@ * * optional can_be_suspended bool */ -/datum/tgui_window/proc/close(can_be_suspended = TRUE) +/datum/tgui_window/proc/close(can_be_suspended = TRUE, logout = FALSE) if(!client) return if(can_be_suspended && can_be_suspended()) log_tgui(client, "[id]/close: suspending") status = TGUI_WINDOW_READY send_message("suspend") - winset(client, null, "mapwindow.map.focus=true") + // You would think that BYOND would null out client or make it stop passing istypes or, y'know, ANYTHING during + // logout, but nope! It appears to be perfectly valid to call winset by every means we can measure in Logout, + // and yet it causes a bad client runtime. To avoid that happening, we just have to know if we're in Logout or + // not. + if(!logout && client) + winset(client, null, "mapwindow.map.focus=true") return log_tgui(client, "[id]/close") release_lock() @@ -150,7 +155,8 @@ // to read the error message. if(!fatally_errored) client << browse(null, "window=[id]") - winset(client, null, "mapwindow.map.focus=true") + if(!logout && client) + winset(client, null, "mapwindow.map.focus=true") /** * public * @@ -190,9 +196,9 @@ /datum/tgui_window/proc/send_asset(datum/asset/asset) if(!client || !asset) return - // if(istype(asset, /datum/asset/spritesheet)) - // var/datum/asset/spritesheet/spritesheet = asset - // send_message("asset/stylesheet", spritesheet.css_filename()) + if(istype(asset, /datum/asset/spritesheet)) + var/datum/asset/spritesheet/spritesheet = asset + send_message("asset/stylesheet", spritesheet.css_filename()) send_message("asset/mappings", asset.get_url_mappings()) sent_assets += list(asset) asset.send(client) diff --git a/code/modules/vehicles/Securitrain_vr.dm b/code/modules/vehicles/Securitrain_vr.dm index 81afed4177..22d1127bb2 100644 --- a/code/modules/vehicles/Securitrain_vr.dm +++ b/code/modules/vehicles/Securitrain_vr.dm @@ -166,28 +166,28 @@ else verbs += /obj/vehicle/train/security/engine/verb/stop_engine -/obj/vehicle/train/security/RunOver(var/mob/living/carbon/human/H) +/obj/vehicle/train/security/RunOver(var/mob/living/M) var/list/parts = list(BP_HEAD, BP_TORSO, BP_L_LEG, BP_R_LEG, BP_L_ARM, BP_R_ARM) - H.apply_effects(5, 5) + M.apply_effects(5, 5) for(var/i = 0, i < rand(1,3), i++) - H.apply_damage(rand(1,5), BRUTE, pick(parts)) + M.apply_damage(rand(1,5), BRUTE, pick(parts)) -/obj/vehicle/train/security/trolley/RunOver(var/mob/living/carbon/human/H) +/obj/vehicle/train/security/trolley/RunOver(var/mob/living/M) ..() - attack_log += text("\[[time_stamp()]\] ran over [H.name] ([H.ckey])") + attack_log += text("\[[time_stamp()]\] ran over [M.name] ([M.ckey])") -/obj/vehicle/train/security/engine/RunOver(var/mob/living/carbon/human/H) +/obj/vehicle/train/security/engine/RunOver(var/mob/living/M) ..() if(is_train_head() && istype(load, /mob/living/carbon/human)) var/mob/living/carbon/human/D = load - to_chat(D, "You ran over \the [H]!") - visible_message("\The [src] ran over \the [H]!") - add_attack_logs(D,H,"Ran over with [src.name]") - attack_log += text("\[[time_stamp()]\] ran over [H.name] ([H.ckey]), driven by [D.name] ([D.ckey])") + to_chat(D, "You ran over \the [M]!" + visible_message("\The [src] ran over \the [M]!") + add_attack_logs(D,M,"Ran over with [src.name]") + attack_log += text("\[[time_stamp()]\] ran over [M.name] ([M.ckey]), driven by [D.name] ([D.ckey])") else - attack_log += text("\[[time_stamp()]\] ran over [H.name] ([H.ckey])") + attack_log += text("\[[time_stamp()]\] ran over [M.name] ([M.ckey])") //------------------------------------------- diff --git a/code/modules/vehicles/cargo_train.dm b/code/modules/vehicles/cargo_train.dm index 94437458cf..a118b1c258 100644 --- a/code/modules/vehicles/cargo_train.dm +++ b/code/modules/vehicles/cargo_train.dm @@ -152,28 +152,28 @@ else verbs += /obj/vehicle/train/engine/verb/stop_engine -/obj/vehicle/train/RunOver(var/mob/living/carbon/human/H) +/obj/vehicle/train/RunOver(var/mob/living/M) var/list/parts = list(BP_HEAD, BP_TORSO, BP_L_LEG, BP_R_LEG, BP_L_ARM, BP_R_ARM) - H.apply_effects(5, 5) + M.apply_effects(5, 5) for(var/i = 0, i < rand(1,3), i++) - H.apply_damage(rand(1,5), BRUTE, pick(parts)) + M.apply_damage(rand(1,5), BRUTE, pick(parts)) -/obj/vehicle/train/trolley/RunOver(var/mob/living/carbon/human/H) +/obj/vehicle/train/trolley/RunOver(var/mob/living/M) ..() - attack_log += text("\[[time_stamp()]\] ran over [H.name] ([H.ckey])") + attack_log += text("\[[time_stamp()]\] ran over [M.name] ([M.ckey])") -/obj/vehicle/train/engine/RunOver(var/mob/living/carbon/human/H) +/obj/vehicle/train/engine/RunOver(var/mob/living/M) ..() if(is_train_head() && istype(load, /mob/living/carbon/human)) var/mob/living/carbon/human/D = load - to_chat(D, "You ran over [H]!") - visible_message("\The [src] ran over [H]!") - add_attack_logs(D,H,"Ran over with [src.name]") - attack_log += text("\[[time_stamp()]\] ran over [H.name] ([H.ckey]), driven by [D.name] ([D.ckey])") + to_chat(D, "You ran over [M]!") + visible_message("\The [src] ran over [M]!") + add_attack_logs(D,M,"Ran over with [src.name]") + attack_log += text("\[[time_stamp()]\] ran over [M.name] ([M.ckey]), driven by [D.name] ([D.ckey])") else - attack_log += text("\[[time_stamp()]\] ran over [H.name] ([H.ckey])") + attack_log += text("\[[time_stamp()]\] ran over [M.name] ([M.ckey])") //------------------------------------------- diff --git a/code/modules/vehicles/quad.dm b/code/modules/vehicles/quad.dm index 657d4976f4..3524895ba2 100644 --- a/code/modules/vehicles/quad.dm +++ b/code/modules/vehicles/quad.dm @@ -142,15 +142,15 @@ add_attack_logs(D,M,"Ran over with [src.name]") -/obj/vehicle/train/engine/quadbike/RunOver(var/mob/living/carbon/human/H) +/obj/vehicle/train/engine/quadbike/RunOver(var/mob/living/M) ..() var/list/throw_dirs = list(1, 2, 4, 8, 5, 6, 9, 10) if(!emagged) throw_dirs -= dir if(tow) - throw_dirs -= get_dir(H, tow) //Don't throw it at the trailer either. - var/turf/T = get_step(H, pick(throw_dirs)) - H.throw_at(T, 1, 1, src) + throw_dirs -= get_dir(M, tow) //Don't throw it at the trailer either. + var/turf/T = get_step(M, pick(throw_dirs)) + M.throw_at(T, 1, 1, src) /* * Trailer bits and bobs. diff --git a/code/modules/vehicles/rover_vr.dm b/code/modules/vehicles/rover_vr.dm index fa7e51ebd9..a3225eb4aa 100644 --- a/code/modules/vehicles/rover_vr.dm +++ b/code/modules/vehicles/rover_vr.dm @@ -164,28 +164,28 @@ else verbs += /obj/vehicle/train/rover/engine/verb/stop_engine -/obj/vehicle/train/rover/RunOver(var/mob/living/carbon/human/H) +/obj/vehicle/train/rover/RunOver(var/mob/living/M) var/list/parts = list(BP_HEAD, BP_TORSO, BP_L_LEG, BP_R_LEG, BP_L_ARM, BP_R_ARM) - H.apply_effects(5, 5) + M.apply_effects(5, 5) for(var/i = 0, i < rand(1,3), i++) - H.apply_damage(rand(1,5), BRUTE, pick(parts)) + M.apply_damage(rand(1,5), BRUTE, pick(parts)) -/obj/vehicle/train/rover/trolley/RunOver(var/mob/living/carbon/human/H) +/obj/vehicle/train/rover/trolley/RunOver(var/mob/living/M) ..() - attack_log += text("\[[time_stamp()]\] ran over [H.name] ([H.ckey])") + attack_log += text("\[[time_stamp()]\] ran over [M.name] ([M.ckey])") -/obj/vehicle/train/rover/engine/RunOver(var/mob/living/carbon/human/H) +/obj/vehicle/train/rover/engine/RunOver(var/mob/living/M) ..() if(is_train_head() && istype(load, /mob/living/carbon/human)) var/mob/living/carbon/human/D = load - to_chat(D, "You ran over \the [H]!") - visible_message("\The [src] ran over \the [H]!") - add_attack_logs(D,H,"Ran over with [src.name]") - attack_log += text("\[[time_stamp()]\] ran over [H.name] ([H.ckey]), driven by [D.name] ([D.ckey])") + to_chat(D, "You ran over \the [M]!") + visible_message("\The [src] ran over \the [M]!") + add_attack_logs(D,M,"Ran over with [src.name]") + attack_log += text("\[[time_stamp()]\] ran over [M.name] ([M.ckey]), driven by [D.name] ([D.ckey])") else - attack_log += text("\[[time_stamp()]\] ran over [H.name] ([H.ckey])") + attack_log += text("\[[time_stamp()]\] ran over [M.name] ([M.ckey])") //------------------------------------------- diff --git a/code/modules/vehicles/vehicle.dm b/code/modules/vehicles/vehicle.dm index e43228c4be..44be1f6bd3 100644 --- a/code/modules/vehicles/vehicle.dm +++ b/code/modules/vehicles/vehicle.dm @@ -292,7 +292,7 @@ cell = null powercheck() -/obj/vehicle/proc/RunOver(var/mob/living/carbon/human/H) +/obj/vehicle/proc/RunOver(var/mob/living/M) return //write specifics for different vehicles //------------------------------------------- diff --git a/code/modules/virus2/disease2.dm b/code/modules/virus2/disease2.dm index d62502084a..e163478b77 100644 --- a/code/modules/virus2/disease2.dm +++ b/code/modules/virus2/disease2.dm @@ -245,6 +245,25 @@ var/global/list/virusDB = list() return r +/datum/disease2/disease/proc/get_tgui_info() + . = list( + "name" = name(), + "spreadtype" = spreadtype, + "antigen" = antigens2string(antigen), + "rate" = stageprob * 10, + "resistance" = resistance, + "species" = jointext(affected_species, ", "), + "symptoms" = list(), + ) + + for(var/datum/disease2/effectholder/E in effects) + .["symptoms"].Add(list(list( + "stage" = E.stage, + "name" = E.effect.name, + "strength" = "[E.multiplier >= 3 ? "Severe" : E.multiplier > 1 ? "Above Average" : "Average"]", + "aggressiveness" = E.chance * 15, + ))) + /datum/disease2/disease/proc/addToDB() if ("[uniqueID]" in virusDB) return 0 @@ -252,6 +271,7 @@ var/global/list/virusDB = list() v.fields["id"] = uniqueID v.fields["name"] = name() v.fields["description"] = get_info() + v.fields["tgui_description"] = get_tgui_info() v.fields["antigen"] = antigens2string(antigen) v.fields["spread type"] = spreadtype virusDB["[uniqueID]"] = v diff --git a/code/modules/vore/eating/living_vr.dm b/code/modules/vore/eating/living_vr.dm index 76f11b8768..0570744028 100644 --- a/code/modules/vore/eating/living_vr.dm +++ b/code/modules/vore/eating/living_vr.dm @@ -43,7 +43,7 @@ M.verbs += /mob/living/proc/switch_scaling if(M.no_vore) //If the mob isn't supposed to have a stomach, let's not give it an insidepanel so it can make one for itself, or a stomach. return TRUE - M.vorePanel = new + M.vorePanel = new(M) M.verbs += /mob/living/proc/insidePanel //Tries to load prefs if a client is present otherwise gives freebie stomach diff --git a/code/modules/vore/eating/vorepanel_vr.dm b/code/modules/vore/eating/vorepanel_vr.dm index 709138c47e..de776e002b 100644 --- a/code/modules/vore/eating/vorepanel_vr.dm +++ b/code/modules/vore/eating/vorepanel_vr.dm @@ -17,1026 +17,804 @@ if(!vorePanel) log_debug("[src] ([type], \ref[src]) didn't have a vorePanel and tried to use the verb.") - vorePanel = new + vorePanel = new(src) - vorePanel.selected = vore_selected - vorePanel.show(src) + vorePanel.tgui_interact(src) /mob/living/proc/updateVRPanel() //Panel popup update call from belly events. - if(!vorePanel) - log_debug("[src] ([type], \ref[src]) didn't have a vorePanel and something tried to update it.") - vorePanel = new - - if(vorePanel.open) - vorePanel.selected = vore_selected - vorePanel.show(src) + SStgui.update_uis(vorePanel) // // Callback Handler for the Inside form // /datum/vore_look - var/datum/browser/popup - var/obj/belly/selected - var/show_interacts = 0 - var/open = FALSE + var/mob/living/host // Note, we do this in case we ever want to allow people to view others vore panels + var/unsaved_changes = FALSE -/datum/vore_look/Destroy() - selected = null - QDEL_NULL(popup) +/datum/vore_look/New(mob/living/new_host) + if(istype(new_host)) + host = new_host . = ..() -/datum/vore_look/Topic(href,href_list[]) - if(vp_interact(href, href_list) && popup) - popup.set_content(gen_ui(usr)) - usr << output(popup.get_content(), "insidePanel.browser") +/datum/vore_look/Destroy() + host = null + . = ..() -/datum/vore_look/proc/show(mob/living/user) - if(popup) - QDEL_NULL(popup) - popup = new(user, "insidePanel", "Inside!", 450, 700, src) - popup.set_content(gen_ui(user)) - popup.open() - open = TRUE +/datum/vore_look/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "VorePanel", "Inside!") + ui.open() -/datum/vore_look/proc/gen_ui(var/mob/living/user) - var/list/dat = list() +// This looks weird, but all tgui_host is used for is state checking +// So this allows us to use the self_state just fine. +/datum/vore_look/tgui_host(mob/user) + return host - var/atom/userloc = user.loc - if(isbelly(userloc)) - var/obj/belly/inside_belly = userloc - var/mob/living/eater = inside_belly.owner +// Note, in order to allow others to look at others vore panels, this state would need +// to be changed to tgui_always_state, and a custom tgui_status() implemented for true "rights" management. +/datum/vore_look/tgui_state(mob/user) + return GLOB.tgui_self_state - dat += "You are currently [user.absorbed ? "absorbed into " : "inside "][eater]'s[inside_belly.name]!

" +/datum/vore_look/var/static/list/nom_icons +/datum/vore_look/proc/cached_nom_icon(atom/target) + LAZYINITLIST(nom_icons) - if(inside_belly.desc) - dat += "[inside_belly.desc]

" //Extra br - - if(inside_belly.contents.len > 1) - dat += "You can see the following around you:
" - var/list/belly_contents = list() - for (var/atom/movable/O in inside_belly) - if(istype(O,/mob/living)) - var/mob/living/M = O - //That's just you - if(M == user) - continue - - //That's an absorbed person you're checking - if(M.absorbed) - if(user.absorbed) - belly_contents += "[O]" - continue - else - continue - - //Anything else - dat += "[O]​" - - //Zero-width space, for wrapping - dat += "​" - - dat += jointext(belly_contents, null) //Add in belly contents to main running list + var/key = "" + if(isobj(target)) + key = "[target.type]" + else if(ismob(target)) + var/mob/M = target + key = "\ref[target][M.real_name]" + if(nom_icons[key]) + . = nom_icons[key] else - dat += "You aren't inside anyone." + . = icon2base64(getFlatIcon(target,defdir=SOUTH,no_anim=TRUE)) + nom_icons[key] = . - var/list/belly_list = list("
    ") - for(var/belly in user.vore_organs) - var/obj/belly/B = belly - if(B == selected) - belly_list += "
  1. [B.name]" - else - belly_list += "
  2. [B.name]" - var/spanstyle - switch(B.digest_mode) - if(DM_HOLD) - spanstyle = "" - if(DM_DIGEST) - spanstyle = "color:red;" - if(DM_ABSORB) - spanstyle = "color:purple;" - if(DM_DRAIN) - spanstyle = "color:purple;" - if(DM_HEAL) - spanstyle = "color:green;" - if(DM_SHRINK) - spanstyle = "color:purple;" - if(DM_GROW) - spanstyle = "color:purple;" - if(DM_SIZE_STEAL) - spanstyle = "color:purple;" - if(DM_TRANSFORM_MALE) - spanstyle = "color:purple;" - if(DM_TRANSFORM_HAIR_AND_EYES) - spanstyle = "color:purple;" - if(DM_TRANSFORM_FEMALE) - spanstyle = "color:purple;" - if(DM_TRANSFORM_KEEP_GENDER) - spanstyle = "color:purple;" - if(DM_TRANSFORM_CHANGE_SPECIES_AND_TAUR) - spanstyle = "color:purple;" - if(DM_TRANSFORM_CHANGE_SPECIES_AND_TAUR_EGG) - spanstyle = "color:purple;" - if(DM_TRANSFORM_REPLICA) - spanstyle = "color:purple;" - if(DM_TRANSFORM_REPLICA_EGG) - spanstyle = "color:purple;" - if(DM_TRANSFORM_KEEP_GENDER_EGG) - spanstyle = "color:purple;" - if(DM_TRANSFORM_MALE_EGG) - spanstyle = "color:purple;" - if(DM_TRANSFORM_FEMALE_EGG) - spanstyle = "color:purple;" - if(DM_EGG) - spanstyle = "color:purple;" +/datum/vore_look/tgui_data(mob/user) + var/list/data = list() - belly_list += " ([B.contents.len])
  3. " + if(!host) + return data - if(user.vore_organs.len < BELLIES_MAX) - belly_list += "
  4. New+
  5. " - belly_list += "

" + data["unsaved_changes"] = unsaved_changes - dat += jointext(belly_list, null) //Add in belly list to main running list + data["inside"] = list() + var/atom/hostloc = host.loc + if(isbelly(hostloc)) + var/obj/belly/inside_belly = hostloc + var/mob/living/pred = inside_belly.owner - // Selected Belly (contents, configuration) - if(!selected) - dat += "No belly selected. Click one to select it." - else - var/list/belly_contents = list() - if(selected.contents.len) - belly_contents += "Contents: " - for(var/O in selected) - - //Mobs can be absorbed, so treat them separately from everything else - if(istype(O,/mob/living)) - var/mob/living/M = O - - //Absorbed gets special color OOoOOOOoooo - if(M.absorbed) - belly_contents += "[O]" - continue - - //Anything else - belly_contents += "[O]" - - //Zero-width space, for wrapping - belly_contents += "​" - - //If there's more than one thing, add an [All] button - if(selected.contents.len > 1) - belly_contents += "\[All\]" - - belly_contents += "
" - - if(belly_contents.len) - dat += jointext(belly_contents, null) - - //Belly Name Button - dat += "
Name: '[selected.name]'" - - //Belly Type button - dat += "
Is this belly fleshy: [selected.is_wet ? "Yes" : "No"]" - if(selected.is_wet) - dat += "
Internal loop for prey?: [selected.wet_loop ? "Yes" : "No"]" - - //Digest Mode Button - var/mode = selected.digest_mode - dat += "
Belly Mode: [mode]" - - //Mode addons button - var/list/flag_list = list() - for(var/flag_name in selected.mode_flag_list) - if(selected.mode_flags & selected.mode_flag_list[flag_name]) - flag_list += flag_name - if(flag_list.len) - dat += "
Mode Addons: [english_list(flag_list)]" - else - dat += "
Mode Addons: None" - - //Item Digest Mode Button - dat += "
Item Mode: [selected.item_digest_mode]" - - //Will it contaminate contents? - dat += "
Contaminates: [selected.contaminates ? "Yes" : "No"]" - - if(selected.contaminates) - //Contamination descriptors - dat += "
Contamination Flavor: [selected.contamination_flavor]" - //Contamination color - dat += "
Contamination Color: [selected.contamination_color]" - - //Belly verb - dat += "
Vore Verb: '[selected.vore_verb]'" - - //Inside flavortext - dat += "
Flavor Text: '[selected.desc]'" - - //Belly Sound Fanciness - dat += "
Use Fancy Sounds: [selected.fancy_vore ? "Yes" : "No"]" - - //Belly sound - dat += "
Vore Sound: [selected.vore_sound]Test" - - //Release sound - dat += "
Release Sound: [selected.release_sound]Test" - - //Belly messages - dat += "
Belly Messages" - - //Can belly taste? - dat += "
Can Taste: [selected.can_taste ? "Yes" : "No"]" - - //Nutritional percentage - dat += "
Nutritional Gain: [selected.nutrition_percent]%" - - //How much brute damage - dat += "
Digest Brute Damage: [selected.digest_brute]" - - //How much burn damage - dat += "
Digest Burn Damage: [selected.digest_burn]" - - //Minimum size prey must be to show up. - dat += "
Required examine size: [selected.bulge_size*100]%" - - //Size that prey will be grown/shrunk to. - dat += "
Shrink/Grow size: [selected.shrink_grow_size*100]%" - - //Belly escapability - dat += "
Belly Interactions ([selected.escapable ? "On" : "Off"])" - if(selected.escapable) - dat += "[show_interacts ? "Hide" : "Show"]" - - if(show_interacts && selected.escapable) - var/list/interacts = list() - interacts += "
" - interacts += "Interaction Settings ?" - interacts += "
Set Belly Escape Chance" - interacts += " [selected.escapechance]%" - - interacts += "
Set Belly Escape Time" - interacts += " [selected.escapetime/10]s" - - //Special
here to add a gap - interacts += "
" - interacts += "
Set Belly Transfer Chance" - interacts += " [selected.transferchance]%" - - interacts += "
Set Belly Transfer Location" - interacts += " [selected.transferlocation ? selected.transferlocation : "Disabled"]" - - //Special
here to add a gap - interacts += "
" - interacts += "
Set Belly Absorb Chance" - interacts += " [selected.absorbchance]%" - - interacts += "
Set Belly Digest Chance" - interacts += " [selected.digestchance]%" - interacts += "
" - dat += jointext(interacts, null) - - //Delete button - dat += "Delete Belly" - - dat += "
" - - var/list/nightmare_list = list() - switch(user.digestable) - if(TRUE) - nightmare_list += "Toggle Digestable (Currently: ON)" - if(FALSE) - nightmare_list += "Toggle Digestable (Currently: OFF)" - switch(user.devourable) - if(TRUE) - nightmare_list += "Toggle Devourable (Currently: ON)" - if(FALSE) - nightmare_list += "Toggle Devourable (Currently: OFF)" - switch(user.feeding) - if(TRUE) - nightmare_list += "
Toggle Feeding (Currently: ON)" - if(FALSE) - nightmare_list += "
Toggle Feeding (Currently: OFF)" - switch(user.absorbable) - if(TRUE) - nightmare_list += "Toggle Absorbtion Permission (Currently: ON)" - if(FALSE) - nightmare_list += "Toggle Absorbtion Permission (Currently: OFF)" - switch(user.digest_leave_remains) - if(TRUE) - nightmare_list += "Toggle Leaving Remains (Currently: ON)" - if(FALSE) - nightmare_list += "Toggle Leaving Remains (Currently: OFF)" - switch(user.allowmobvore) - if(TRUE) - nightmare_list += "
Toggle Mob Vore (Currently: ON)" - if(FALSE) - nightmare_list += "
Toggle Mob Vore (Currently: OFF)" - switch(user.permit_healbelly) - if(TRUE) - nightmare_list += "Toggle Healbelly Permission (Currently: ON)" - if(FALSE) - nightmare_list += "Toggle Healbelly Permission (Currently: OFF)" - - switch(user.can_be_drop_prey) - if(TRUE) - nightmare_list += "
Toggle Prey Spontaneous Vore (Currently: ON)" - if(FALSE) - nightmare_list += "
Toggle Prey Spontaneous Vore (Currently: OFF)" - - switch(user.can_be_drop_pred) - if(TRUE) - nightmare_list += "Toggle Pred Spontaneous Vore (Currently: ON)" - if(FALSE) - nightmare_list += "Toggle Pred Spontaneous Vore (Currently: OFF)" - - dat += jointext(nightmare_list, null) //AAAA - - dat += "
Set Your Taste" - dat += "
Set Your Smell" - dat += "
Toggle Hunger Noises" - - //Under the last HR, save and stuff. - dat += "
Save Prefs" - dat += "
Refresh" - dat += "
Reload Slot Prefs" - - //Returns the dat html to the vore_look - return jointext(dat, null) - -/datum/vore_look/proc/vp_interact(href, href_list) - var/mob/living/user = usr - if(href_list["close"]) - open = FALSE - QDEL_NULL(popup) - return - - if(href_list["show_int"]) - show_interacts = !show_interacts - return TRUE //Force update - - if(href_list["int_help"]) - alert("These control how your belly responds to someone using 'resist' while inside you. The percent chance to trigger each is listed below, \ - and you can change them to whatever you see fit. Setting them to 0% will disable the possibility of that interaction. \ - These only function as long as interactions are turned on in general. Keep in mind, the 'belly mode' interactions (digest/absorb) \ - will affect all prey in that belly, if one resists and triggers digestion/absorption. If multiple trigger at the same time, \ - only the first in the order of 'Escape > Transfer > Absorb > Digest' will occur.","Interactions Help") - return FALSE //Force update - - if(href_list["outsidepick"]) - var/atom/movable/tgt = locate(href_list["outsidepick"]) - var/obj/belly/OB = locate(href_list["outsidebelly"]) - if(!(tgt in OB)) //Aren't here anymore, need to update menu. - return TRUE - var/intent = "Examine" - - if(istype(tgt,/mob/living)) - var/mob/living/M = tgt - intent = alert("What do you want to do to them?","Query","Examine","Help Out","Devour") - switch(intent) - if("Examine") //Examine a mob inside another mob - var/list/results = M.examine(user) - if(!results || !results.len) - results = list("You were unable to examine that. Tell a developer!") - to_chat(user, jointext(results, "
")) - return FALSE - - if("Help Out") //Help the inside-mob out - if(user.stat || user.absorbed || M.absorbed) - to_chat(user,"You can't do that in your state!") - return TRUE - - to_chat(user,"You begin to push [M] to freedom!") - to_chat(M,"[usr] begins to push you to freedom!") - to_chat(M.loc,"Someone is trying to escape from inside you!") - sleep(50) - if(prob(33)) - OB.release_specific_contents(M) - to_chat(usr,"You manage to help [M] to safety!") - to_chat(M,"[user] pushes you free!") - to_chat(OB.owner,"[M] forces free of the confines of your body!") - else - to_chat(user,"[M] slips back down inside despite your efforts.") - to_chat(M," Even with [user]'s help, you slip back inside again.") - to_chat(OB.owner,"Your body efficiently shoves [M] back where they belong.") - - if("Devour") //Eat the inside mob - if(user.absorbed || user.stat) - to_chat(user,"You can't do that in your state!") - return TRUE - - if(!user.vore_selected) - to_chat(user,"Pick a belly on yourself first!") - return TRUE - - var/obj/belly/TB = user.vore_selected - to_chat(user,"You begin to [lowertext(TB.vore_verb)] [M] into your [lowertext(TB.name)]!") - to_chat(M,"[user] begins to [lowertext(TB.vore_verb)] you into their [lowertext(TB.name)]!") - to_chat(OB.owner,"Someone inside you is eating someone else!") - - sleep(TB.nonhuman_prey_swallow_time) //Can't do after, in a stomach, weird things abound. - if((user in OB) && (M in OB)) //Make sure they're still here. - to_chat(user,"You manage to [lowertext(TB.vore_verb)] [M] into your [lowertext(TB.name)]!") - to_chat(M,"[user] manages to [lowertext(TB.vore_verb)] you into their [lowertext(TB.name)]!") - to_chat(OB.owner,"Someone inside you has eaten someone else!") - TB.nom_mob(M) - - else if(istype(tgt,/obj/item)) - var/obj/item/T = tgt - if(!(tgt in OB)) - //Doesn't exist anymore, update. - return TRUE - intent = alert("What do you want to do to that?","Query","Examine","Use Hand") - switch(intent) - if("Examine") - var/list/results = T.examine(user) - if(!results || !results.len) - results = list("You were unable to examine that. Tell a developer!") - to_chat(user, jointext(results, "
")) - return FALSE - - if("Use Hand") - if(user.stat) - to_chat(user,"You can't do that in your state!") - return TRUE - - user.ClickOn(T) - sleep(5) //Seems to exit too fast for the panel to update - - if(href_list["insidepick"]) - var/intent - - //Handle the [All] choice. Ugh inelegant. Someone make this pretty. - if(href_list["pickall"]) - intent = alert("Eject all, Move all?","Query","Eject all","Cancel","Move all") - switch(intent) - if("Cancel") - return FALSE - - if("Eject all") - if(user.stat) - to_chat(user,"You can't do that in your state!") - return FALSE - - selected.release_all_contents() - - if("Move all") - if(user.stat) - to_chat(user,"You can't do that in your state!") - return FALSE - - var/obj/belly/choice = input("Move all where?","Select Belly") as null|anything in user.vore_organs - if(!choice) - return FALSE - - for(var/atom/movable/tgt in selected) - to_chat(tgt,"You're squished from [user]'s [lowertext(selected)] to their [lowertext(choice.name)]!") - selected.transfer_contents(tgt, choice, 1) - - var/atom/movable/tgt = locate(href_list["insidepick"]) - if(!(tgt in selected)) //Old menu, needs updating because they aren't really there. - return TRUE //Forces update - intent = "Examine" - intent = alert("Examine, Eject, Move? Examine if you want to leave this box.","Query","Examine","Eject","Move") - switch(intent) - if("Examine") - var/list/results = tgt.examine(user) - if(!results || !results.len) - results = list("You were unable to examine that. Tell a developer!") - to_chat(user, jointext(results, "
")) - return FALSE - - if("Eject") - if(user.stat) - to_chat(user,"You can't do that in your state!") - return FALSE - - selected.release_specific_contents(tgt) - - if("Move") - if(user.stat) - to_chat(user,"You can't do that in your state!") - return FALSE - - var/obj/belly/choice = input("Move [tgt] where?","Select Belly") as null|anything in user.vore_organs - if(!choice || !(tgt in selected)) - return FALSE - - to_chat(tgt,"You're squished from [user]'s [lowertext(selected.name)] to their [lowertext(choice.name)]!") - selected.transfer_contents(tgt, choice) - - if(href_list["newbelly"]) - if(user.vore_organs.len >= BELLIES_MAX) - return FALSE - - var/new_name = html_encode(input(usr,"New belly's name:","New Belly") as text|null) - - var/failure_msg - if(length(new_name) > BELLIES_NAME_MAX || length(new_name) < BELLIES_NAME_MIN) - failure_msg = "Entered belly name length invalid (must be longer than [BELLIES_NAME_MIN], no more than than [BELLIES_NAME_MAX])." - // else if(whatever) //Next test here. - else - for(var/belly in user.vore_organs) - var/obj/belly/B = belly - if(lowertext(new_name) == lowertext(B.name)) - failure_msg = "No duplicate belly names, please." - break - - if(failure_msg) //Something went wrong. - alert(user,failure_msg,"Error!") - return FALSE - - var/obj/belly/NB = new(user) - NB.name = new_name - selected = NB - - if(href_list["bellypick"]) - selected = locate(href_list["bellypick"]) - user.vore_selected = selected - - //// - //Please keep these the same order they are on the panel UI for ease of coding - //// - if(href_list["b_name"]) - var/new_name = html_encode(input(usr,"Belly's new name:","New Name") as text|null) - - var/failure_msg - if(length(new_name) > BELLIES_NAME_MAX || length(new_name) < BELLIES_NAME_MIN) - failure_msg = "Entered belly name length invalid (must be longer than [BELLIES_NAME_MIN], no more than than [BELLIES_NAME_MAX])." - // else if(whatever) //Next test here. - else - for(var/belly in user.vore_organs) - var/obj/belly/B = belly - if(lowertext(new_name) == lowertext(B.name)) - failure_msg = "No duplicate belly names, please." - break - - if(failure_msg) //Something went wrong. - alert(user,failure_msg,"Error!") - return FALSE - - selected.name = new_name - - if(href_list["b_wetness"]) - selected.is_wet = !selected.is_wet - - if(href_list["b_wetloop"]) - selected.wet_loop = !selected.wet_loop - - if(href_list["b_mode"]) - var/list/menu_list = selected.digest_modes.Copy() - if(istype(usr,/mob/living/carbon/human)) - menu_list += DM_TRANSFORM - - var/new_mode = input("Choose Mode (currently [selected.digest_mode])") as null|anything in menu_list - if(!new_mode) - return FALSE - - if(new_mode == DM_TRANSFORM) //Snowflek submenu - var/list/tf_list = selected.transform_modes - var/new_tf_mode = input("Choose TF Mode (currently [selected.digest_mode])") as null|anything in tf_list - if(!new_tf_mode) - return FALSE - selected.digest_mode = new_tf_mode - return - - selected.digest_mode = new_mode - //selected.items_preserved.Cut() //Re-evaltuate all items in belly on belly-mode change //Handled with item modes now - - if(href_list["b_addons"]) - var/list/menu_list = selected.mode_flag_list.Copy() - var/toggle_addon = input("Toggle Addon") as null|anything in menu_list - if(!toggle_addon) - return FALSE - selected.mode_flags ^= selected.mode_flag_list[toggle_addon] - selected.items_preserved.Cut() //Re-evaltuate all items in belly on addon toggle - - if(href_list["b_item_mode"]) - var/list/menu_list = selected.item_digest_modes.Copy() - - var/new_mode = input("Choose Mode (currently [selected.item_digest_mode])") as null|anything in menu_list - if(!new_mode) - return FALSE - - selected.item_digest_mode = new_mode - selected.items_preserved.Cut() //Re-evaltuate all items in belly on belly-mode change - - if(href_list["b_contaminates"]) - selected.contaminates = !selected.contaminates - - if(href_list["b_contamination_flavor"]) - var/list/menu_list = contamination_flavors.Copy() - var/new_flavor = input("Choose Contamination Flavor Text Type (currently [selected.contamination_flavor])") as null|anything in menu_list - if(!new_flavor) - return FALSE - selected.contamination_flavor = new_flavor - - if(href_list["b_contamination_color"]) - var/list/menu_list = contamination_colors.Copy() - var/new_color = input("Choose Contamination Color (currently [selected.contamination_color])") as null|anything in menu_list - if(!new_color) - return FALSE - selected.contamination_color = new_color - selected.items_preserved.Cut() //To re-contaminate for new color - - if(href_list["b_desc"]) - var/new_desc = html_encode(input(usr,"Belly Description ([BELLIES_DESC_MAX] char limit):","New Description",selected.desc) as message|null) - - if(new_desc) - new_desc = readd_quotes(new_desc) - if(length(new_desc) > BELLIES_DESC_MAX) - alert("Entered belly desc too long. [BELLIES_DESC_MAX] character limit.","Error") - return FALSE - selected.desc = new_desc - else //Returned null - return FALSE - - if(href_list["b_msgs"]) - var/list/messages = list( - "Digest Message (to prey)", - "Digest Message (to you)", - "Struggle Message (outside)", - "Struggle Message (inside)", - "Examine Message (when full)", - "Reset All To Default" + data["inside"] = list( + "absorbed" = host.absorbed, + "belly_name" = inside_belly.name, + "belly_mode" = inside_belly.digest_mode, + "desc" = inside_belly.desc || "No description.", + "pred" = pred, + "ref" = "\ref[inside_belly]", ) - alert(user,"Setting abusive or deceptive messages will result in a ban. Consider this your warning. Max 150 characters per message, max 10 messages per topic.","Really, don't.") - var/choice = input(user,"Select a type to modify. Messages from each topic are pulled at random when needed.","Pick Type") as null|anything in messages - var/help = " Press enter twice to separate messages. '%pred' will be replaced with your name. '%prey' will be replaced with the prey's name. '%belly' will be replaced with your belly's name." + data["inside"]["contents"] = list() + for(var/atom/movable/O in inside_belly) + if(O == host) + continue - switch(choice) - if("Digest Message (to prey)") - var/new_message = input(user,"These are sent to prey when they expire. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Digest Message (to prey)",selected.get_messages("dmp")) as message - if(new_message) - selected.set_messages(new_message,"dmp") + var/list/info = list( + "name" = "[O]", + "icon" = cached_nom_icon(O), + "absorbed" = FALSE, + "stat" = 0, + "ref" = "\ref[O]", + "outside" = FALSE, + ) + if(isliving(O)) + var/mob/living/M = O + info["stat"] = M.stat + if(M.absorbed) + info["absorbed"] = TRUE + data["inside"]["contents"].Add(list(info)) - if("Digest Message (to you)") - var/new_message = input(user,"These are sent to you when prey expires in you. Write them in 2nd person ('you feel X'). Avoid using %pred in this type."+help,"Digest Message (to you)",selected.get_messages("dmo")) as message - if(new_message) - selected.set_messages(new_message,"dmo") + data["our_bellies"] = list() + for(var/belly in host.vore_organs) + var/obj/belly/B = belly + data["our_bellies"].Add(list(list( + "selected" = (B == host.vore_selected), + "name" = B.name, + "ref" = "\ref[B]", + "digest_mode" = B.digest_mode, + "contents" = LAZYLEN(B.contents), + ))) - if("Struggle Message (outside)") - var/new_message = input(user,"These are sent to those nearby when prey struggles. Write them in 3rd person ('X's Y bulges')."+help,"Struggle Message (outside)",selected.get_messages("smo")) as message - if(new_message) - selected.set_messages(new_message,"smo") + data["selected"] = null + if(host.vore_selected) + var/obj/belly/selected = host.vore_selected + data["selected"] = list( + "belly_name" = selected.name, + "is_wet" = selected.is_wet, + "wet_loop" = selected.wet_loop, + "mode" = selected.digest_mode, + "item_mode" = selected.item_digest_mode, + "verb" = selected.vore_verb, + "desc" = selected.desc, + "fancy" = selected.fancy_vore, + "sound" = selected.vore_sound, + "release_sound" = selected.release_sound, + // "messages" // TODO + "can_taste" = selected.can_taste, + "nutrition_percent" = selected.nutrition_percent, + "digest_brute" = selected.digest_brute, + "digest_burn" = selected.digest_burn, + "bulge_size" = selected.bulge_size, + "shrink_grow_size" = selected.shrink_grow_size, + ) - if("Struggle Message (inside)") - var/new_message = input(user,"These are sent to prey when they struggle. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Struggle Message (inside)",selected.get_messages("smi")) as message - if(new_message) - selected.set_messages(new_message,"smi") + data["selected"]["addons"] = list() + for(var/flag_name in selected.mode_flag_list) + if(selected.mode_flags & selected.mode_flag_list[flag_name]) + data["selected"]["addons"].Add(flag_name) - if("Examine Message (when full)") - var/new_message = input(user,"These are sent to people who examine you when this belly has contents. Write them in 3rd person ('Their %belly is bulging')."+help,"Examine Message (when full)",selected.get_messages("em")) as message - if(new_message) - selected.set_messages(new_message,"em") + data["selected"]["contaminates"] = selected.contaminates + data["selected"]["contaminate_flavor"] = null + data["selected"]["contaminate_color"] = null + if(selected.contaminates) + data["selected"]["contaminate_flavor"] = selected.contamination_flavor + data["selected"]["contaminate_color"] = selected.contamination_color - if("Reset All To Default") - var/confirm = alert(user,"This will delete any custom messages. Are you sure?","Confirmation","DELETE","Cancel") - if(confirm == "DELETE") - selected.digest_messages_prey = initial(selected.digest_messages_prey) - selected.digest_messages_owner = initial(selected.digest_messages_owner) - selected.struggle_messages_outside = initial(selected.struggle_messages_outside) - selected.struggle_messages_inside = initial(selected.struggle_messages_inside) + data["selected"]["escapable"] = selected.escapable + data["selected"]["interacts"] = list() + if(selected.escapable) + data["selected"]["interacts"]["escapechance"] = selected.escapechance + data["selected"]["interacts"]["escapetime"] = selected.escapetime + data["selected"]["interacts"]["transferchance"] = selected.transferchance + data["selected"]["interacts"]["transferlocation"] = selected.transferlocation + data["selected"]["interacts"]["absorbchance"] = selected.absorbchance + data["selected"]["interacts"]["digestchance"] = selected.digestchance - if(href_list["b_verb"]) - var/new_verb = html_encode(input(usr,"New verb when eating (infinitive tense, e.g. nom or swallow):","New Verb") as text|null) + data["selected"]["contents"] = list() + for(var/O in selected) + var/list/info = list( + "name" = "[O]", + "icon" = cached_nom_icon(O), + "absorbed" = FALSE, + "stat" = 0, + "ref" = "\ref[O]", + "outside" = TRUE, + ) + if(isliving(O)) + var/mob/living/M = O + info["stat"] = M.stat + if(M.absorbed) + info["absorbed"] = TRUE + data["selected"]["contents"].Add(list(info)) - if(length(new_verb) > BELLIES_NAME_MAX || length(new_verb) < BELLIES_NAME_MIN) - alert("Entered verb length invalid (must be longer than [BELLIES_NAME_MIN], no longer than [BELLIES_NAME_MAX]).","Error") - return FALSE + data["prefs"] = list( + "digestable" = host.digestable, + "devourable" = host.devourable, + "feeding" = host.feeding, + "absorbable" = host.absorbable, + "digest_leave_remains" = host.digest_leave_remains, + "allowmobvore" = host.allowmobvore, + "permit_healbelly" = host.permit_healbelly, + "can_be_drop_prey" = host.can_be_drop_prey, + "can_be_drop_pred" = host.can_be_drop_pred, + "noisy" = host.noisy, + ) - selected.vore_verb = new_verb + return data - if(href_list["b_fancy_sound"]) - selected.fancy_vore = !selected.fancy_vore - selected.vore_sound = "Gulp" - selected.release_sound = "Splatter" - // defaults as to avoid potential bugs +/datum/vore_look/tgui_act(action, params) + if(..()) + return TRUE - if(href_list["b_release"]) - var/choice - if(selected.fancy_vore) - choice = input(user,"Currently set to [selected.release_sound]","Select Sound") as null|anything in fancy_release_sounds - else - choice = input(user,"Currently set to [selected.release_sound]","Select Sound") as null|anything in classic_release_sounds + switch(action) + if("int_help") + alert("These control how your belly responds to someone using 'resist' while inside you. The percent chance to trigger each is listed below, \ + and you can change them to whatever you see fit. Setting them to 0% will disable the possibility of that interaction. \ + These only function as long as interactions are turned on in general. Keep in mind, the 'belly mode' interactions (digest/absorb) \ + will affect all prey in that belly, if one resists and triggers digestion/absorption. If multiple trigger at the same time, \ + only the first in the order of 'Escape > Transfer > Absorb > Digest' will occur.","Interactions Help") + return TRUE - if(!choice) - return FALSE + // Host is inside someone else, and is trying to interact with something else inside that person. + if("pick_from_inside") + return pick_from_inside(usr, params) + + // Host is trying to interact with something in host's belly. + if("pick_from_outside") + return pick_from_outside(usr, params) - selected.release_sound = choice - - if(href_list["b_releasesoundtest"]) - var/sound/releasetest - if(selected.fancy_vore) - releasetest = fancy_release_sounds[selected.release_sound] - else - releasetest = classic_release_sounds[selected.release_sound] - - if(releasetest) - SEND_SOUND(user, releasetest) - - if(href_list["b_sound"]) - var/choice - if(selected.fancy_vore) - choice = input(user,"Currently set to [selected.vore_sound]","Select Sound") as null|anything in fancy_vore_sounds - else - choice = input(user,"Currently set to [selected.vore_sound]","Select Sound") as null|anything in classic_vore_sounds - - if(!choice) - return FALSE - - selected.vore_sound = choice - - if(href_list["b_soundtest"]) - var/sound/voretest - if(selected.fancy_vore) - voretest = fancy_vore_sounds[selected.vore_sound] - else - voretest = classic_vore_sounds[selected.vore_sound] - if(voretest) - SEND_SOUND(user, voretest) - - if(href_list["b_tastes"]) - selected.can_taste = !selected.can_taste - - if(href_list["b_bulge_size"]) - var/new_bulge = input(user, "Choose the required size prey must be to show up on examine, ranging from 25% to 200% Set this to 0 for no text on examine.", "Set Belly Examine Size.") as num|null - if(new_bulge == null) - return - if(new_bulge == 0) //Disable. - selected.bulge_size = 0 - to_chat(user,"Your stomach will not be seen on examine.") - else if (!ISINRANGE(new_bulge,25,200)) - selected.bulge_size = 0.25 //Set it to the default. - to_chat(user,"Invalid size.") - else if(new_bulge) - selected.bulge_size = (new_bulge/100) - - if(href_list["b_grow_shrink"]) - var/new_grow = input(user, "Choose the size that prey will be grown/shrunk to, ranging from 25% to 200%", "Set Growth Shrink Size.", selected.shrink_grow_size) as num|null - if (new_grow == null) - return - if (!ISINRANGE(new_grow,25,200)) - selected.shrink_grow_size = 1 //Set it to the default - to_chat(user,"Invalid size.") - else if(new_grow) - selected.shrink_grow_size = (new_grow*0.01) - - if(href_list["b_nutritionpercent"]) - var/new_damage = input(user, "Choose the nutrition gain percentage you will recieve per tick from prey. Ranges from 0.01 to 100.", "Set Nutrition Gain Percentage.", selected.digest_brute) as num|null - if(new_damage == null) - return - var/new_new_damage = CLAMP(new_damage, 0.01, 100) - selected.nutrition_percent = new_new_damage - - if(href_list["b_burn_dmg"]) - var/new_damage = input(user, "Choose the amount of burn damage prey will take per tick. Ranges from 0 to 6.", "Set Belly Burn Damage.", selected.digest_burn) as num|null - if(new_damage == null) - return - var/new_new_damage = CLAMP(new_damage, 0, 6) - selected.digest_burn = new_new_damage - - if(href_list["b_brute_dmg"]) - var/new_damage = input(user, "Choose the amount of brute damage prey will take per tick. Ranges from 0 to 6", "Set Belly Brute Damage.", selected.digest_brute) as num|null - if(new_damage == null) - return - var/new_new_damage = CLAMP(new_damage, 0, 6) - selected.digest_brute = new_new_damage - - if(href_list["b_escapable"]) - if(selected.escapable == 0) //Possibly escapable and special interactions. - selected.escapable = 1 - to_chat(usr,"Prey now have special interactions with your [lowertext(selected.name)] depending on your settings.") - else if(selected.escapable == 1) //Never escapable. - selected.escapable = 0 - to_chat(usr,"Prey will not be able to have special interactions with your [lowertext(selected.name)].") - show_interacts = 0 //Force the hiding of the panel - else - alert("Something went wrong. Your stomach will now not have special interactions. Press the button enable them again and tell a dev.","Error") //If they somehow have a varable that's not 0 or 1 - selected.escapable = 0 - show_interacts = 0 //Force the hiding of the panel - - if(href_list["b_escapechance"]) - var/escape_chance_input = input(user, "Set prey escape chance on resist (as %)", "Prey Escape Chance") as num|null - if(!isnull(escape_chance_input)) //These have to be 'null' because both cancel and 0 are valid, separate options - selected.escapechance = sanitize_integer(escape_chance_input, 0, 100, initial(selected.escapechance)) - - if(href_list["b_escapetime"]) - var/escape_time_input = input(user, "Set number of seconds for prey to escape on resist (1-60)", "Prey Escape Time") as num|null - if(!isnull(escape_time_input)) - selected.escapetime = sanitize_integer(escape_time_input*10, 10, 600, initial(selected.escapetime)) - - if(href_list["b_transferchance"]) - var/transfer_chance_input = input(user, "Set belly transfer chance on resist (as %). You must also set the location for this to have any effect.", "Prey Escape Time") as num|null - if(!isnull(transfer_chance_input)) - selected.transferchance = sanitize_integer(transfer_chance_input, 0, 100, initial(selected.transferchance)) - - if(href_list["b_transferlocation"]) - var/obj/belly/choice = input("Where do you want your [lowertext(selected.name)] to lead if prey resists?","Select Belly") as null|anything in (user.vore_organs + "None - Remove" - selected) - - if(!choice) //They cancelled, no changes - return FALSE - else if(choice == "None - Remove") - selected.transferlocation = null - else - selected.transferlocation = choice.name - - if(href_list["b_absorbchance"]) - var/absorb_chance_input = input(user, "Set belly absorb mode chance on resist (as %)", "Prey Absorb Chance") as num|null - if(!isnull(absorb_chance_input)) - selected.absorbchance = sanitize_integer(absorb_chance_input, 0, 100, initial(selected.absorbchance)) - - if(href_list["b_digestchance"]) - var/digest_chance_input = input(user, "Set belly digest mode chance on resist (as %)", "Prey Digest Chance") as num|null - if(!isnull(digest_chance_input)) - selected.digestchance = sanitize_integer(digest_chance_input, 0, 100, initial(selected.digestchance)) - - if(href_list["b_del"]) - var/alert = alert("Are you sure you want to delete your [lowertext(selected.name)]?","Confirmation","Delete","Cancel") - if(!(alert == "Delete")) - return FALSE - - var/failure_msg = "" - - var/dest_for //Check to see if it's the destination of another vore organ. - for(var/belly in user.vore_organs) - var/obj/belly/B = belly - if(B.transferlocation == selected) - dest_for = B.name - failure_msg += "This is the destiantion for at least '[dest_for]' belly transfers. Remove it as the destination from any bellies before deleting it. " - break - - if(selected.contents.len) - failure_msg += "You cannot delete bellies with contents! " //These end with spaces, to be nice looking. Make sure you do the same. - if(selected.immutable) - failure_msg += "This belly is marked as undeletable. " - if(user.vore_organs.len == 1) - failure_msg += "You must have at least one belly. " - - if(failure_msg) - alert(user,failure_msg,"Error!") - return FALSE - - qdel(selected) - selected = user.vore_organs[1] - user.vore_selected = user.vore_organs[1] - - if(href_list["saveprefs"]) - if(!user.save_vore_prefs()) - alert("ERROR: Virgo-specific preferences failed to save!","Error") - else - to_chat(user,"Virgo-specific preferences saved!") - - if(href_list["applyprefs"]) - var/alert = alert("Are you sure you want to reload character slot preferences? This will remove your current vore organs and eject their contents.","Confirmation","Reload","Cancel") - if(alert != "Reload") - return FALSE - if(!user.apply_vore_prefs()) - alert("ERROR: Virgo-specific preferences failed to apply!","Error") - else - to_chat(user,"Virgo-specific preferences applied from active slot!") - - if(href_list["setflavor"]) - var/new_flavor = html_encode(input(usr,"What your character tastes like (40ch limit). This text will be printed to the pred after 'X tastes of...' so just put something like 'strawberries and cream':","Character Flavor",user.vore_taste) as text|null) - if(!new_flavor) - return FALSE - - new_flavor = readd_quotes(new_flavor) - if(length(new_flavor) > FLAVOR_MAX) - alert("Entered flavor/taste text too long. [FLAVOR_MAX] character limit.","Error!") - return FALSE - user.vore_taste = new_flavor - - if(href_list["setsmell"]) - var/new_smell = html_encode(input(usr,"What your character smells like (40ch limit). This text will be printed to the pred after 'X smells of...' so just put something like 'strawberries and cream':","Character Smell",user.vore_smell) as text|null) - if(!new_smell) - return FALSE - - new_smell = readd_quotes(new_smell) - if(length(new_smell) > FLAVOR_MAX) - alert("Entered perfume/smell text too long. [FLAVOR_MAX] character limit.","Error!") - return FALSE - user.vore_smell = new_smell - - if(href_list["toggle_dropnom_pred"]) - var/choice = alert(user, "This toggle is for spontaneous, environment related vore as a predator, including drop-noms, teleporters, etc. You are currently [user.can_be_drop_pred ? " able to eat prey that you encounter by environmental actions." : "avoiding eating prey encountered in the environment."]", "", "Be Pred", "Cancel", "Don't be Pred") - switch(choice) - if("Cancel") + if("newbelly") + if(host.vore_organs.len >= BELLIES_MAX) return FALSE - if("Be Pred") - user.can_be_drop_pred = TRUE - if("Don't be Pred") - user.can_be_drop_pred = FALSE - if(href_list["toggle_dropnom_prey"]) - var/choice = alert(user, "This toggle is for spontaneous, environment related vore as a prey, including drop-noms, teleporters, etc. You are currently [user.can_be_drop_prey ? "able to be eaten by environmental actions." : "not able to be eaten by environmental actions."]", "", "Be Prey", "Cancel", "Don't Be Prey") - switch(choice) - if("Cancel") + var/new_name = html_encode(input(usr,"New belly's name:","New Belly") as text|null) + + var/failure_msg + if(length(new_name) > BELLIES_NAME_MAX || length(new_name) < BELLIES_NAME_MIN) + failure_msg = "Entered belly name length invalid (must be longer than [BELLIES_NAME_MIN], no more than than [BELLIES_NAME_MAX])." + // else if(whatever) //Next test here. + else + for(var/belly in host.vore_organs) + var/obj/belly/B = belly + if(lowertext(new_name) == lowertext(B.name)) + failure_msg = "No duplicate belly names, please." + break + + if(failure_msg) //Something went wrong. + alert(usr, failure_msg, "Error!") + return TRUE + + var/obj/belly/NB = new(host) + NB.name = new_name + host.vore_selected = NB + unsaved_changes = TRUE + return TRUE + + if("bellypick") + host.vore_selected = locate(params["bellypick"]) + return TRUE + + if("set_attribute") + return set_attr(usr, params) + + if("saveprefs") + if(!host.save_vore_prefs()) + alert("ERROR: Virgo-specific preferences failed to save!","Error") + else + to_chat(usr, "Virgo-specific preferences saved!") + unsaved_changes = FALSE + return TRUE + if("reloadprefs") + var/alert = alert("Are you sure you want to reload character slot preferences? This will remove your current vore organs and eject their contents.","Confirmation","Reload","Cancel") + if(alert != "Reload") return FALSE - if("Be Prey") - user.can_be_drop_prey = TRUE - if("Don't Be Prey") - user.can_be_drop_prey = FALSE - - if(href_list["toggledg"]) - var/choice = alert(user, "This button is for those who don't like being digested. It can make you undigestable. Digesting you is currently: [user.digestable ? "Allowed" : "Prevented"]", "", "Allow Digestion", "Cancel", "Prevent Digestion") - switch(choice) - if("Cancel") + if(!host.apply_vore_prefs()) + alert("ERROR: Virgo-specific preferences failed to apply!","Error") + else + to_chat(usr,"Virgo-specific preferences applied from active slot!") + unsaved_changes = FALSE + return TRUE + if("setflavor") + var/new_flavor = html_encode(input(usr,"What your character tastes like (40ch limit). This text will be printed to the pred after 'X tastes of...' so just put something like 'strawberries and cream':","Character Flavor",host.vore_taste) as text|null) + if(!new_flavor) return FALSE - if("Allow Digestion") - user.digestable = TRUE - if("Prevent Digestion") - user.digestable = FALSE - if(user.client.prefs_vr) - user.client.prefs_vr.digestable = user.digestable - - if(href_list["toggleddevour"]) - var/choice = alert(user, "This button is to toggle your ability to be devoured by others. Devouring is currently: [user.devourable ? "Allowed" : "Prevented"]", "", "Be Devourable", "Cancel", "Prevent being Devoured") - switch(choice) - if("Cancel") + new_flavor = readd_quotes(new_flavor) + if(length(new_flavor) > FLAVOR_MAX) + alert("Entered flavor/taste text too long. [FLAVOR_MAX] character limit.","Error!") return FALSE - if("Be Devourable") - user.devourable = TRUE - if("Prevent being Devoured") - user.devourable = FALSE - - if(user.client.prefs_vr) - user.client.prefs_vr.devourable = user.devourable - - if(href_list["toggledfeed"]) - var/choice = alert(user, "This button is to toggle your ability to be fed to or by others vorishly. Force Feeding is currently: [user.feeding ? "Allowed" : "Prevented"]", "", "Allow Feeding", "Cancel", "Prevent Feeding") - switch(choice) - if("Cancel") + host.vore_taste = new_flavor + unsaved_changes = TRUE + return TRUE + if("setsmell") + var/new_smell = html_encode(input(usr,"What your character smells like (40ch limit). This text will be printed to the pred after 'X smells of...' so just put something like 'strawberries and cream':","Character Smell",host.vore_smell) as text|null) + if(!new_smell) return FALSE - if("Allow Feeding") - user.feeding = TRUE - if("Prevent Feeding") - user.feeding = FALSE - if(user.client.prefs_vr) - user.client.prefs_vr.feeding = user.feeding - - if(href_list["toggleabsorbable"]) - var/choice = alert(user, "This button allows preds to know whether you prefer or don't prefer to be absorbed. Currently you are [user.absorbable? "" : "not"] giving permission.", "", "Allow absorption", "Cancel", "Disallow absorption") - switch(choice) - if("Cancel") + new_smell = readd_quotes(new_smell) + if(length(new_smell) > FLAVOR_MAX) + alert("Entered perfume/smell text too long. [FLAVOR_MAX] character limit.","Error!") return FALSE - if("Allow absorption") - user.absorbable = TRUE - if("Disallow absorption") - user.absorbable = FALSE + host.vore_smell = new_smell + unsaved_changes = TRUE + return TRUE + if("toggle_dropnom_pred") + host.can_be_drop_pred = !host.can_be_drop_pred + if(host.client.prefs_vr) + host.client.prefs_vr.can_be_drop_pred = host.can_be_drop_pred + unsaved_changes = TRUE + return TRUE + if("toggle_dropnom_prey") + host.can_be_drop_prey = !host.can_be_drop_prey + if(host.client.prefs_vr) + host.client.prefs_vr.can_be_drop_prey = host.can_be_drop_prey + unsaved_changes = TRUE + return TRUE + if("toggle_digest") + host.digestable = !host.digestable + if(host.client.prefs_vr) + host.client.prefs_vr.digestable = host.digestable + unsaved_changes = TRUE + return TRUE + if("toggle_devour") + host.devourable = !host.devourable + if(host.client.prefs_vr) + host.client.prefs_vr.devourable = host.devourable + unsaved_changes = TRUE + return TRUE + if("toggle_feed") + host.feeding = !host.feeding + if(host.client.prefs_vr) + host.client.prefs_vr.feeding = host.feeding + unsaved_changes = TRUE + return TRUE + if("toggle_absorbable") + host.absorbable = !host.absorbable + if(host.client.prefs_vr) + host.client.prefs_vr.absorbable = host.absorbable + unsaved_changes = TRUE + return TRUE + if("toggle_leaveremains") + host.digest_leave_remains = !host.digest_leave_remains + if(host.client.prefs_vr) + host.client.prefs_vr.digest_leave_remains = host.digest_leave_remains + unsaved_changes = TRUE + return TRUE + if("toggle_mobvore") + host.allowmobvore = !host.allowmobvore + if(host.client.prefs_vr) + host.client.prefs_vr.allowmobvore = host.allowmobvore + unsaved_changes = TRUE + return TRUE + if("toggle_healbelly") + host.permit_healbelly = !host.permit_healbelly + if(host.client.prefs_vr) + host.client.prefs_vr.permit_healbelly = host.permit_healbelly + unsaved_changes = TRUE + return TRUE + if("toggle_noisy") + host.noisy = !host.noisy + unsaved_changes = TRUE + return TRUE - if(user.client.prefs_vr) - user.client.prefs_vr.absorbable = user.absorbable +/datum/vore_look/proc/pick_from_inside(mob/user, params) + var/atom/movable/target = locate(params["pick"]) + var/obj/belly/OB = locate(params["belly"]) - if(href_list["toggledlm"]) - var/choice = alert(user, "This button allows preds to have your remains be left in their belly after you are digested. This will only happen if pred sets their belly to do so. Remains consist of skeletal parts. Currently you are [user.digest_leave_remains? "" : "not"] leaving remains.", "", "Allow Post-digestion Remains", "Cancel", "Disallow Post-digestion Remains") - switch(choice) + if(!(target in OB)) + return TRUE // Aren't here anymore, need to update menu + + var/intent = "Examine" + if(isliving(target)) + intent = alert("What do you want to do to them?","Query","Examine","Help Out","Devour") + + else if(istype(target, /obj/item)) + intent = alert("What do you want to do to that?","Query","Examine","Use Hand") + + switch(intent) + if("Examine") //Examine a mob inside another mob + var/list/results = target.examine(host) + if(!results || !results.len) + results = list("You were unable to examine that. Tell a developer!") + to_chat(user, jointext(results, "
")) + return TRUE + + if("Use Hand") + if(host.stat) + to_chat(user, "You can't do that in your state!") + return TRUE + + host.ClickOn(target) + return TRUE + + if(!isliving(target)) + return + + var/mob/living/M = target + switch(intent) + if("Help Out") //Help the inside-mob out + if(host.stat || host.absorbed || M.absorbed) + to_chat(user, "You can't do that in your state!") + return TRUE + + to_chat(user,"You begin to push [M] to freedom!") + to_chat(M,"[host] begins to push you to freedom!") + to_chat(M.loc,"Someone is trying to escape from inside you!") + sleep(50) + if(prob(33)) + OB.release_specific_contents(M) + to_chat(user,"You manage to help [M] to safety!") + to_chat(M,"[host] pushes you free!") + to_chat(OB.owner,"[M] forces free of the confines of your body!") + else + to_chat(user,"[M] slips back down inside despite your efforts.") + to_chat(M," Even with [host]'s help, you slip back inside again.") + to_chat(OB.owner,"Your body efficiently shoves [M] back where they belong.") + return TRUE + + if("Devour") //Eat the inside mob + if(host.absorbed || host.stat) + to_chat(user,"You can't do that in your state!") + return TRUE + + if(!host.vore_selected) + to_chat(user,"Pick a belly on yourself first!") + return TRUE + + var/obj/belly/TB = host.vore_selected + to_chat(user,"You begin to [lowertext(TB.vore_verb)] [M] into your [lowertext(TB.name)]!") + to_chat(M,"[host] begins to [lowertext(TB.vore_verb)] you into their [lowertext(TB.name)]!") + to_chat(OB.owner,"Someone inside you is eating someone else!") + + sleep(TB.nonhuman_prey_swallow_time) //Can't do after, in a stomach, weird things abound. + if((host in OB) && (M in OB)) //Make sure they're still here. + to_chat(user,"You manage to [lowertext(TB.vore_verb)] [M] into your [lowertext(TB.name)]!") + to_chat(M,"[host] manages to [lowertext(TB.vore_verb)] you into their [lowertext(TB.name)]!") + to_chat(OB.owner,"Someone inside you has eaten someone else!") + TB.nom_mob(M) + +/datum/vore_look/proc/pick_from_outside(mob/user, params) + var/intent + + //Handle the [All] choice. Ugh inelegant. Someone make this pretty. + if(params["pickall"]) + intent = alert("Eject all, Move all?","Query","Eject all","Cancel","Move all") + switch(intent) if("Cancel") + return TRUE + + if("Eject all") + if(host.stat) + to_chat(user,"You can't do that in your state!") + return TRUE + + host.vore_selected.release_all_contents() + return TRUE + + if("Move all") + if(host.stat) + to_chat(user,"You can't do that in your state!") + return TRUE + + var/obj/belly/choice = input("Move all where?","Select Belly") as null|anything in host.vore_organs + if(!choice) + return FALSE + + for(var/atom/movable/target in host.vore_selected) + to_chat(target,"You're squished from [host]'s [lowertext(host.vore_selected)] to their [lowertext(choice.name)]!") + host.vore_selected.transfer_contents(target, choice, 1) + return TRUE + return + + var/atom/movable/target = locate(params["pick"]) + if(!(target in host.vore_selected)) + return TRUE // Not in our X anymore, update UI + intent = "Examine" + intent = alert("Examine, Eject, Move? Examine if you want to leave this box.","Query","Examine","Eject","Move") + switch(intent) + if("Examine") + var/list/results = target.examine(host) + if(!results || !results.len) + results = list("You were unable to examine that. Tell a developer!") + to_chat(user, jointext(results, "
")) + return TRUE + + if("Eject") + if(host.stat) + to_chat(user,"You can't do that in your state!") + return TRUE + + host.vore_selected.release_specific_contents(target) + + if("Move") + if(host.stat) + to_chat(user,"You can't do that in your state!") + return TRUE + + var/obj/belly/choice = input("Move [target] where?","Select Belly") as null|anything in host.vore_organs + if(!choice || !(target in host.vore_selected)) + return TRUE + + to_chat(target,"You're squished from [host]'s [lowertext(host.vore_selected.name)] to their [lowertext(choice.name)]!") + host.vore_selected.transfer_contents(target, choice) + +/datum/vore_look/proc/set_attr(mob/user, params) + if(!host.vore_selected) + alert("No belly selected to modify.") + return FALSE + + var/attr = params["attribute"] + switch(attr) + if("b_name") + var/new_name = html_encode(input(usr,"Belly's new name:","New Name") as text|null) + + var/failure_msg + if(length(new_name) > BELLIES_NAME_MAX || length(new_name) < BELLIES_NAME_MIN) + failure_msg = "Entered belly name length invalid (must be longer than [BELLIES_NAME_MIN], no more than than [BELLIES_NAME_MAX])." + // else if(whatever) //Next test here. + else + for(var/belly in host.vore_organs) + var/obj/belly/B = belly + if(lowertext(new_name) == lowertext(B.name)) + failure_msg = "No duplicate belly names, please." + break + + if(failure_msg) //Something went wrong. + alert(user,failure_msg,"Error!") return FALSE - if("Allow Post-digestion Remains") - user.digest_leave_remains = TRUE - if("Disallow Post-digestion Remains") - user.digest_leave_remains = FALSE - if(user.client.prefs_vr) - user.client.prefs_vr.digest_leave_remains = user.digest_leave_remains + host.vore_selected.name = new_name + . = TRUE + if("b_wetness") + host.vore_selected.is_wet = !host.vore_selected.is_wet + . = TRUE + if("b_wetloop") + host.vore_selected.wet_loop = !host.vore_selected.wet_loop + . = TRUE + if("b_mode") + var/list/menu_list = host.vore_selected.digest_modes.Copy() + if(istype(usr,/mob/living/carbon/human)) + menu_list += DM_TRANSFORM - if(href_list["togglemv"]) - var/choice = alert(user, "This button is for those who don't like being eaten by mobs. Mobs are currently: [user.allowmobvore ? "Allowed to eat" : "Prevented from eating"] you.", "", "Allow Mob Predation", "Cancel", "Prevent Mob Predation") - switch(choice) - if("Cancel") + var/new_mode = input("Choose Mode (currently [host.vore_selected.digest_mode])") as null|anything in menu_list + if(!new_mode) return FALSE - if("Allow Mob Predation") - user.allowmobvore = TRUE - if("Prevent Mob Predation") - user.allowmobvore = FALSE - if(user.client.prefs_vr) - user.client.prefs_vr.allowmobvore = user.allowmobvore + if(new_mode == DM_TRANSFORM) //Snowflek submenu + var/list/tf_list = host.vore_selected.transform_modes + var/new_tf_mode = input("Choose TF Mode (currently [host.vore_selected.digest_mode])") as null|anything in tf_list + if(!new_tf_mode) + return FALSE + host.vore_selected.digest_mode = new_tf_mode + return - if(href_list["togglehealbelly"]) - var/choice = alert(user, "This button is for those who don't like healbelly used on them as a mechanic. It does not affect anything, but is displayed under mechanical prefs for ease of quick checks. You are currently: [user.allowmobvore ? "Okay" : "Not Okay"] with players using healbelly on you.", "", "Allow Healing Belly", "Cancel", "Disallow Healing Belly") - switch(choice) - if("Cancel") + host.vore_selected.digest_mode = new_mode + . = TRUE + if("b_addons") + var/list/menu_list = host.vore_selected.mode_flag_list.Copy() + var/toggle_addon = input("Toggle Addon") as null|anything in menu_list + if(!toggle_addon) return FALSE - if("Allow Healing Belly") - user.permit_healbelly = TRUE - if("Disallow Healing Belly") - user.permit_healbelly = FALSE + host.vore_selected.mode_flags ^= host.vore_selected.mode_flag_list[toggle_addon] + host.vore_selected.items_preserved.Cut() //Re-evaltuate all items in belly on + . = TRUE + if("b_item_mode") + var/list/menu_list = host.vore_selected.item_digest_modes.Copy() - if(user.client.prefs_vr) - user.client.prefs_vr.permit_healbelly = user.permit_healbelly - - if(href_list["togglenoisy"]) - var/choice = alert(user, "Toggle audible hunger noises. Currently: [user.noisy ? "Enabled" : "Disabled"]", "", "Enable audible hunger", "Cancel", "Disable audible hunger") - switch(choice) - if("Cancel") + var/new_mode = input("Choose Mode (currently [host.vore_selected.item_digest_mode])") as null|anything in menu_list + if(!new_mode) return FALSE - if("Enable audible hunger") - user.noisy = TRUE - if("Disable audible hunger") - user.noisy = FALSE - //Refresh when interacted with, returning 1 makes vore_look.Topic update - return TRUE + host.vore_selected.item_digest_mode = new_mode + host.vore_selected.items_preserved.Cut() //Re-evaltuate all items in belly on belly-mode change + . = TRUE + if("b_contaminates") + host.vore_selected.contaminates = !host.vore_selected.contaminates + . = TRUE + if("b_contamination_flavor") + var/list/menu_list = contamination_flavors.Copy() + var/new_flavor = input("Choose Contamination Flavor Text Type (currently [host.vore_selected.contamination_flavor])") as null|anything in menu_list + if(!new_flavor) + return FALSE + host.vore_selected.contamination_flavor = new_flavor + . = TRUE + if("b_contamination_color") + var/list/menu_list = contamination_colors.Copy() + var/new_color = input("Choose Contamination Color (currently [host.vore_selected.contamination_color])") as null|anything in menu_list + if(!new_color) + return FALSE + host.vore_selected.contamination_color = new_color + host.vore_selected.items_preserved.Cut() //To re-contaminate for new color + . = TRUE + if("b_desc") + var/new_desc = html_encode(input(usr,"Belly Description ([BELLIES_DESC_MAX] char limit):","New Description",host.vore_selected.desc) as message|null) + + if(new_desc) + new_desc = readd_quotes(new_desc) + if(length(new_desc) > BELLIES_DESC_MAX) + alert("Entered belly desc too long. [BELLIES_DESC_MAX] character limit.","Error") + return FALSE + host.vore_selected.desc = new_desc + . = TRUE + if("b_msgs") + alert(user,"Setting abusive or deceptive messages will result in a ban. Consider this your warning. Max 150 characters per message, max 10 messages per topic.","Really, don't.") + var/help = " Press enter twice to separate messages. '%pred' will be replaced with your name. '%prey' will be replaced with the prey's name. '%belly' will be replaced with your belly's name." + switch(params["msgtype"]) + if("dmp") + var/new_message = input(user,"These are sent to prey when they expire. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Digest Message (to prey)",host.vore_selected.get_messages("dmp")) as message + if(new_message) + host.vore_selected.set_messages(new_message,"dmp") + + if("dmo") + var/new_message = input(user,"These are sent to you when prey expires in you. Write them in 2nd person ('you feel X'). Avoid using %pred in this type."+help,"Digest Message (to you)",host.vore_selected.get_messages("dmo")) as message + if(new_message) + host.vore_selected.set_messages(new_message,"dmo") + + if("smo") + var/new_message = input(user,"These are sent to those nearby when prey struggles. Write them in 3rd person ('X's Y bulges')."+help,"Struggle Message (outside)",host.vore_selected.get_messages("smo")) as message + if(new_message) + host.vore_selected.set_messages(new_message,"smo") + + if("smi") + var/new_message = input(user,"These are sent to prey when they struggle. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Struggle Message (inside)",host.vore_selected.get_messages("smi")) as message + if(new_message) + host.vore_selected.set_messages(new_message,"smi") + + if("em") + var/new_message = input(user,"These are sent to people who examine you when this belly has contents. Write them in 3rd person ('Their %belly is bulging')."+help,"Examine Message (when full)",host.vore_selected.get_messages("em")) as message + if(new_message) + host.vore_selected.set_messages(new_message,"em") + + if("reset") + var/confirm = alert(user,"This will delete any custom messages. Are you sure?","Confirmation","DELETE","Cancel") + if(confirm == "DELETE") + host.vore_selected.digest_messages_prey = initial(host.vore_selected.digest_messages_prey) + host.vore_selected.digest_messages_owner = initial(host.vore_selected.digest_messages_owner) + host.vore_selected.struggle_messages_outside = initial(host.vore_selected.struggle_messages_outside) + host.vore_selected.struggle_messages_inside = initial(host.vore_selected.struggle_messages_inside) + . = TRUE + if("b_verb") + var/new_verb = html_encode(input(usr,"New verb when eating (infinitive tense, e.g. nom or swallow):","New Verb") as text|null) + + if(length(new_verb) > BELLIES_NAME_MAX || length(new_verb) < BELLIES_NAME_MIN) + alert("Entered verb length invalid (must be longer than [BELLIES_NAME_MIN], no longer than [BELLIES_NAME_MAX]).","Error") + return FALSE + + host.vore_selected.vore_verb = new_verb + . = TRUE + if("b_fancy_sound") + host.vore_selected.fancy_vore = !host.vore_selected.fancy_vore + host.vore_selected.vore_sound = "Gulp" + host.vore_selected.release_sound = "Splatter" + // defaults as to avoid potential bugs + . = TRUE + if("b_release") + var/choice + if(host.vore_selected.fancy_vore) + choice = input(user,"Currently set to [host.vore_selected.release_sound]","Select Sound") as null|anything in fancy_release_sounds + else + choice = input(user,"Currently set to [host.vore_selected.release_sound]","Select Sound") as null|anything in classic_release_sounds + + if(!choice) + return FALSE + + host.vore_selected.release_sound = choice + . = TRUE + if("b_releasesoundtest") + var/sound/releasetest + if(host.vore_selected.fancy_vore) + releasetest = fancy_release_sounds[host.vore_selected.release_sound] + else + releasetest = classic_release_sounds[host.vore_selected.release_sound] + + if(releasetest) + SEND_SOUND(user, releasetest) + . = TRUE + if("b_sound") + var/choice + if(host.vore_selected.fancy_vore) + choice = input(user,"Currently set to [host.vore_selected.vore_sound]","Select Sound") as null|anything in fancy_vore_sounds + else + choice = input(user,"Currently set to [host.vore_selected.vore_sound]","Select Sound") as null|anything in classic_vore_sounds + + if(!choice) + return FALSE + + host.vore_selected.vore_sound = choice + . = TRUE + if("b_soundtest") + var/sound/voretest + if(host.vore_selected.fancy_vore) + voretest = fancy_vore_sounds[host.vore_selected.vore_sound] + else + voretest = classic_vore_sounds[host.vore_selected.vore_sound] + if(voretest) + SEND_SOUND(user, voretest) + . = TRUE + if("b_tastes") + host.vore_selected.can_taste = !host.vore_selected.can_taste + . = TRUE + if("b_bulge_size") + var/new_bulge = input(user, "Choose the required size prey must be to show up on examine, ranging from 25% to 200% Set this to 0 for no text on examine.", "Set Belly Examine Size.") as num|null + if(new_bulge == null) + return FALSE + if(new_bulge == 0) //Disable. + host.vore_selected.bulge_size = 0 + to_chat(user,"Your stomach will not be seen on examine.") + else if (!ISINRANGE(new_bulge,25,200)) + host.vore_selected.bulge_size = 0.25 //Set it to the default. + to_chat(user,"Invalid size.") + else if(new_bulge) + host.vore_selected.bulge_size = (new_bulge/100) + . = TRUE + if("b_grow_shrink") + var/new_grow = input(user, "Choose the size that prey will be grown/shrunk to, ranging from 25% to 200%", "Set Growth Shrink Size.", host.vore_selected.shrink_grow_size) as num|null + if (new_grow == null) + return FALSE + if (!ISINRANGE(new_grow,25,200)) + host.vore_selected.shrink_grow_size = 1 //Set it to the default + to_chat(user,"Invalid size.") + else if(new_grow) + host.vore_selected.shrink_grow_size = (new_grow*0.01) + . = TRUE + if("b_nutritionpercent") + var/new_nutrition = input(user, "Choose the nutrition gain percentage you will recieve per tick from prey. Ranges from 0.01 to 100.", "Set Nutrition Gain Percentage.", host.vore_selected.nutrition_percent) as num|null + if(new_nutrition == null) + return FALSE + var/new_new_nutrition = CLAMP(new_nutrition, 0.01, 100) + host.vore_selected.nutrition_percent = new_new_nutrition + . = TRUE + if("b_burn_dmg") + var/new_damage = input(user, "Choose the amount of burn damage prey will take per tick. Ranges from 0 to 6.", "Set Belly Burn Damage.", host.vore_selected.digest_burn) as num|null + if(new_damage == null) + return FALSE + var/new_new_damage = CLAMP(new_damage, 0, 6) + host.vore_selected.digest_burn = new_new_damage + . = TRUE + if("b_brute_dmg") + var/new_damage = input(user, "Choose the amount of brute damage prey will take per tick. Ranges from 0 to 6", "Set Belly Brute Damage.", host.vore_selected.digest_brute) as num|null + if(new_damage == null) + return FALSE + var/new_new_damage = CLAMP(new_damage, 0, 6) + host.vore_selected.digest_brute = new_new_damage + . = TRUE + if("b_escapable") + if(host.vore_selected.escapable == 0) //Possibly escapable and special interactions. + host.vore_selected.escapable = 1 + to_chat(usr,"Prey now have special interactions with your [lowertext(host.vore_selected.name)] depending on your settings.") + else if(host.vore_selected.escapable == 1) //Never escapable. + host.vore_selected.escapable = 0 + to_chat(usr,"Prey will not be able to have special interactions with your [lowertext(host.vore_selected.name)].") + else + alert("Something went wrong. Your stomach will now not have special interactions. Press the button enable them again and tell a dev.","Error") //If they somehow have a varable that's not 0 or 1 + host.vore_selected.escapable = 0 + . = TRUE + if("b_escapechance") + var/escape_chance_input = input(user, "Set prey escape chance on resist (as %)", "Prey Escape Chance") as num|null + if(!isnull(escape_chance_input)) //These have to be 'null' because both cancel and 0 are valid, separate options + host.vore_selected.escapechance = sanitize_integer(escape_chance_input, 0, 100, initial(host.vore_selected.escapechance)) + . = TRUE + if("b_escapetime") + var/escape_time_input = input(user, "Set number of seconds for prey to escape on resist (1-60)", "Prey Escape Time") as num|null + if(!isnull(escape_time_input)) + host.vore_selected.escapetime = sanitize_integer(escape_time_input*10, 10, 600, initial(host.vore_selected.escapetime)) + . = TRUE + if("b_transferchance") + var/transfer_chance_input = input(user, "Set belly transfer chance on resist (as %). You must also set the location for this to have any effect.", "Prey Escape Time") as num|null + if(!isnull(transfer_chance_input)) + host.vore_selected.transferchance = sanitize_integer(transfer_chance_input, 0, 100, initial(host.vore_selected.transferchance)) + . = TRUE + if("b_transferlocation") + var/obj/belly/choice = input("Where do you want your [lowertext(host.vore_selected.name)] to lead if prey resists?","Select Belly") as null|anything in (host.vore_organs + "None - Remove" - host.vore_selected) + + if(!choice) //They cancelled, no changes + return FALSE + else if(choice == "None - Remove") + host.vore_selected.transferlocation = null + else + host.vore_selected.transferlocation = choice.name + . = TRUE + if("b_absorbchance") + var/absorb_chance_input = input(user, "Set belly absorb mode chance on resist (as %)", "Prey Absorb Chance") as num|null + if(!isnull(absorb_chance_input)) + host.vore_selected.absorbchance = sanitize_integer(absorb_chance_input, 0, 100, initial(host.vore_selected.absorbchance)) + . = TRUE + if("b_digestchance") + var/digest_chance_input = input(user, "Set belly digest mode chance on resist (as %)", "Prey Digest Chance") as num|null + if(!isnull(digest_chance_input)) + host.vore_selected.digestchance = sanitize_integer(digest_chance_input, 0, 100, initial(host.vore_selected.digestchance)) + . = TRUE + if("b_del") + var/alert = alert("Are you sure you want to delete your [lowertext(host.vore_selected.name)]?","Confirmation","Delete","Cancel") + if(!(alert == "Delete")) + return FALSE + + var/failure_msg = "" + + var/dest_for //Check to see if it's the destination of another vore organ. + for(var/belly in host.vore_organs) + var/obj/belly/B = belly + if(B.transferlocation == host.vore_selected) + dest_for = B.name + failure_msg += "This is the destiantion for at least '[dest_for]' belly transfers. Remove it as the destination from any bellies before deleting it. " + break + + if(host.vore_selected.contents.len) + failure_msg += "You cannot delete bellies with contents! " //These end with spaces, to be nice looking. Make sure you do the same. + if(host.vore_selected.immutable) + failure_msg += "This belly is marked as undeletable. " + if(host.vore_organs.len == 1) + failure_msg += "You must have at least one belly. " + + if(failure_msg) + alert(user,failure_msg,"Error!") + return FALSE + + qdel(host.vore_selected) + host.vore_selected = host.vore_organs[1] + . = TRUE + + if(.) + unsaved_changes = TRUE \ No newline at end of file diff --git a/code/modules/vore/fluffstuff/custom_boxes_vr.dm b/code/modules/vore/fluffstuff/custom_boxes_vr.dm index 0f4f4d0a37..4b16e1d66f 100644 --- a/code/modules/vore/fluffstuff/custom_boxes_vr.dm +++ b/code/modules/vore/fluffstuff/custom_boxes_vr.dm @@ -93,7 +93,6 @@ desc = "A small box containing Yonra's personal effects" has_items = list( /obj/item/weapon/melee/fluff/holochain/mass, - /obj/item/weapon/implanter/reagent_generator/yonra, /obj/item/clothing/accessory/medal/silver/unity) //ivymoomoo:Ivy Baladeva diff --git a/code/modules/vore/fluffstuff/custom_implants_vr.dm b/code/modules/vore/fluffstuff/custom_implants_vr.dm new file mode 100644 index 0000000000..8647c9d68f --- /dev/null +++ b/code/modules/vore/fluffstuff/custom_implants_vr.dm @@ -0,0 +1,547 @@ + +//WickedTempest: Chakat Tempest +/obj/item/weapon/implant/reagent_generator/tempest + generated_reagents = list("milk" = 2) + reagent_name = "milk" + usable_volume = 1000 + + empty_message = list("Your breasts are almost completely drained!") + full_message = list("Your teats feel heavy and swollen!") + emote_descriptor = list("squeezes milk", "tugs on Tempest's breasts, milking them") + self_emote_descriptor = list("squeeze") + random_emote = list("moos quietly") + verb_name = "Milk" + verb_desc = "Grab Tempest's nipples and milk them into a container! May cause blushing and groaning." + +/obj/item/weapon/implanter/reagent_generator/tempest + implant_type = /obj/item/weapon/implant/reagent_generator/tempest + + +//Hottokeeki: Belle Day +/obj/item/weapon/implant/reagent_generator/belle + generated_reagents = list("milk" = 2) + reagent_name = "milk" + usable_volume = 5000 + + empty_message = list("Your breasts and or udder feel almost completely drained!", "You're feeling a liittle on the empty side...") + full_message = list("You're due for a milking; your breasts and or udder feel heavy and swollen!", "Looks like you've got some full tanks!") + emote_descriptor = list("squeezes milk", "tugs on Belle's breasts/udders, milking them", "extracts milk") + self_emote_descriptor = list("squeeze", "extract") + random_emote = list("moos", "mrours", "groans softly") + verb_name = "Milk" + verb_desc = "Obtain Belle's milk and put it into a container! May cause blushing and groaning, or arousal." + +/obj/item/weapon/implanter/reagent_generator/belle + implant_type = /obj/item/weapon/implant/reagent_generator/belle + +//Gowst: Eldi Moljir +//Eldi iz coolest elf-dorf. +/obj/item/weapon/implant/reagent_generator/eldi + name = "lactation implant" + desc = "This is an implant that allows the user to lactate." + generated_reagents = list("milk" = 2) + reagent_name = "milk" + usable_volume = 1000 + + empty_message = list("Your breasts feel unusually empty.", "Your chest feels lighter - your milk supply is empty!", "Your milk reserves have run dry.", "Your grateful nipples ache as the last of your milk leaves them.") + full_message = list("Your breasts ache badly - they are swollen and feel fit to burst!", "You need to be milked! Your breasts feel bloated, eager for release.", "Your milky breasts are starting to leak...") + emote_descriptor = list("squeezes Eldi's nipples, milking them", "milks Eldi's breasts", "extracts milk") + self_emote_descriptor = list("squeeze out", "extract") + random_emote = list("surpresses a moan", "gasps sharply", "bites her lower lip") + verb_name = "Milk" + verb_desc = "Grab Eldi's breasts and milk her, storing her fresh, warm milk in a container. This will undoubtedly turn her on." + +/obj/item/weapon/implanter/reagent_generator/eldi + implant_type = /obj/item/weapon/implant/reagent_generator/eldi + +//Vorrarkul: Theodora Lindt +/obj/item/weapon/implant/reagent_generator/vorrarkul + generated_reagents = list("chocolate_milk" = 2) + reagent_name = "chocalate milk" + usable_volume = 1000 + + empty_message = list("Your nipples are sore from being milked!") + full_message = list("Your breasts are full, their sweet scent emanating from your chest!") + emote_descriptor = list("squeezes chocolate milk from Theodora", "tugs on Theodora's nipples, milking them", "kneads Theodora's breasts, milking them") + self_emote_descriptor = list("squeeze", "knead") + random_emote = list("moans softly", "gives an involuntary squeal") + verb_name = "Milk" + verb_desc = "Grab Theodora's breasts and extract delicious chocolate milk from them!" + +/obj/item/weapon/implanter/reagent_generator/vorrarkul + implant_type = /obj/item/weapon/implant/reagent_generator/vorrarkul + +//Lycanthorph: Savannah Dixon +/obj/item/weapon/implant/reagent_generator/savannah + generated_reagents = list("milk" = 2) + reagent_name = "milk" + usable_volume = 1000 + + empty_message = list("Your nipples are sore from being milked!", "Your breasts feel drained, milk is no longer leaking from your nipples!") + full_message = list("Your breasts are full, their sweet scent emanating from your chest!", "Your breasts feel full, milk is starting to leak from your nipples, filling the air with it's sweet scent!") + emote_descriptor = list("squeezes sweet milk from Savannah", "tugs on Savannah's nipples, milking them", "kneads Savannah's breasts, milking them") + self_emote_descriptor = list("squeeze", "knead") + random_emote = list("lets out a soft moan", "gives an involuntary squeal") + verb_name = "Milk" + verb_desc = "Grab Savannah's breasts and extract sweet milk from them!" + +/obj/item/weapon/implanter/reagent_generator/savannah + implant_type = /obj/item/weapon/implant/reagent_generator/savannah + +//SpoopyLizz: Roiz Lizden +//I made this! Woo! +//implant +//-------------------- +/obj/item/weapon/implant/reagent_generator/roiz + name = "egg laying implant" + desc = "This is an implant that allows the user to lay eggs." + generated_reagents = list("egg" = 2) + usable_volume = 500 + transfer_amount = 50 + + empty_message = list("Your lower belly feels smooth and empty. Sorry, we're out of eggs!", "The reduced pressure in your lower belly tells you there are no more eggs.") + full_message = list("Your lower belly looks swollen with irregular bumps, and it feels heavy.", "Your lower abdomen feels really heavy, making it a bit hard to walk.") + emote_descriptor = list("an egg right out of Roiz's lower belly!", "into Roiz' belly firmly, forcing him to lay an egg!", "Roiz really tight, who promptly lays an egg!") + var/verb_descriptor = list("squeezes", "pushes", "hugs") + var/self_verb_descriptor = list("squeeze", "push", "hug") + var/short_emote_descriptor = list("lays", "forces out", "pushes out") + self_emote_descriptor = list("lay", "force out", "push out") + random_emote = list("hisses softly with a blush on his face", "yelps in embarrassment", "grunts a little") + assigned_proc = /mob/living/carbon/human/proc/use_reagent_implant_roiz + +/obj/item/weapon/implant/reagent_generator/roiz/post_implant(mob/living/carbon/source) + START_PROCESSING(SSobj, src) + to_chat(source, "You implant [source] with \the [src].") + source.verbs |= assigned_proc + return 1 + +/obj/item/weapon/implanter/reagent_generator/roiz + implant_type = /obj/item/weapon/implant/reagent_generator/roiz + +/mob/living/carbon/human/proc/use_reagent_implant_roiz() + set name = "Lay Egg" + set desc = "Force Roiz to lay an egg by squeezing into his lower body! This makes the lizard extremely embarrassed, and it looks funny." + set category = "Object" + set src in view(1) + + //do_reagent_implant(usr) + if(!isliving(usr) || !usr.checkClickCooldown()) + return + + if(usr.incapacitated() || usr.stat > CONSCIOUS) + return + + var/obj/item/weapon/implant/reagent_generator/roiz/rimplant + for(var/obj/item/organ/external/E in organs) + for(var/obj/item/weapon/implant/I in E.implants) + if(istype(I, /obj/item/weapon/implant/reagent_generator)) + rimplant = I + break + if (rimplant) + if(rimplant.reagents.total_volume <= rimplant.transfer_amount) + to_chat(src, "[pick(rimplant.empty_message)]") + return + + new /obj/item/weapon/reagent_containers/food/snacks/egg/roiz(get_turf(src)) + + var/index = rand(0,3) + + if (usr != src) + var/emote = rimplant.emote_descriptor[index] + var/verb_desc = rimplant.verb_descriptor[index] + var/self_verb_desc = rimplant.self_verb_descriptor[index] + usr.visible_message("[usr] [verb_desc] [emote]", + "You [self_verb_desc] [emote]") + else + visible_message("[src] [pick(rimplant.short_emote_descriptor)] an egg.", + "You [pick(rimplant.self_emote_descriptor)] an egg.") + if(prob(15)) + visible_message("[src] [pick(rimplant.random_emote)].") // M-mlem. + + rimplant.reagents.remove_any(rimplant.transfer_amount) + +//Cameron653: Jasmine Lizden +/obj/item/weapon/implant/reagent_generator/jasmine + name = "egg laying implant" + desc = "This is an implant that allows the user to lay eggs." + generated_reagents = list("egg" = 2) + usable_volume = 500 + transfer_amount = 50 + + empty_message = list("Your lower belly feels flat, empty, and somewhat rough!", "Your lower belly feels completely empty, no more bulges visible... At least, for the moment!") + full_message = list("Your lower belly is stretched out, smooth,and heavy, small bulges visible from within!", "It takes considerably more effort to move yourself, the large bulges within your gut most likely the cause!") + emote_descriptor = list("an egg from Jasmine's tauric belly!", "into Jasmine's gut, forcing her to lay a considerably large egg!", "Jasmine with a considerable amount of force, causing an egg to slip right out of her!") + var/verb_descriptor = list("squeezes", "pushes", "hugs") + var/self_verb_descriptor = list("squeeze", "push", "hug") + var/short_emote_descriptor = list("lays", "forces out", "pushes out") + self_emote_descriptor = list("lay", "force out", "push out") + random_emote = list("hisses softly with a blush on her face", "bites down on her lower lip", "lets out a light huff") + assigned_proc = /mob/living/carbon/human/proc/use_reagent_implant_jasmine + +/obj/item/weapon/implant/reagent_generator/jasmine/post_implant(mob/living/carbon/source) + START_PROCESSING(SSobj, src) + to_chat(source, "You implant [source] with \the [src].") + source.verbs |= assigned_proc + return 1 + +/obj/item/weapon/implanter/reagent_generator/jasmine + implant_type = /obj/item/weapon/implant/reagent_generator/jasmine + +/mob/living/carbon/human/proc/use_reagent_implant_jasmine() + set name = "Lay Egg" + set desc = "Cause Jasmine to lay an egg by squeezing her tauric belly!" + set category = "Object" + set src in view(1) + + //do_reagent_implant(usr) + if(!isliving(usr) || !usr.checkClickCooldown()) + return + + if(usr.incapacitated() || usr.stat > CONSCIOUS) + return + + var/obj/item/weapon/implant/reagent_generator/jasmine/rimplant + for(var/obj/item/organ/external/E in organs) + for(var/obj/item/weapon/implant/I in E.implants) + if(istype(I, /obj/item/weapon/implant/reagent_generator)) + rimplant = I + break + if (rimplant) + if(rimplant.reagents.total_volume <= rimplant.transfer_amount) + to_chat(src, "[pick(rimplant.empty_message)]") + return + + new /obj/item/weapon/reagent_containers/food/snacks/egg/roiz(get_turf(src)) + + var/index = rand(0,3) + + if (usr != src) + var/emote = rimplant.emote_descriptor[index] + var/verb_desc = rimplant.verb_descriptor[index] + var/self_verb_desc = rimplant.self_verb_descriptor[index] + usr.visible_message("[usr] [verb_desc] [emote]", + "You [self_verb_desc] [emote]") + else + visible_message("[src] [pick(rimplant.short_emote_descriptor)] an egg.", + "You [pick(rimplant.self_emote_descriptor)] an egg.") + if(prob(15)) + visible_message("[src] [pick(rimplant.random_emote)].") + + rimplant.reagents.remove_any(rimplant.transfer_amount) + +//Draycu: Schae Yonra +/obj/item/weapon/implant/reagent_generator/yonra + name = "egg laying implant" + desc = "This is an implant that allows the user to lay eggs." + generated_reagents = list("egg" = 2) + usable_volume = 500 + transfer_amount = 50 + + empty_message = list("Your feathery lower belly feels smooth and empty. For now...", "The lack of clacking eggs in your abdomen lets you know you're free to continue your day as normal.", "The reduced pressure in your lower belly tells you there are no more eggs.", "With a soft sigh, you can feel your lower body is empty. You know it will only be a matter of time before another batch fills you up again, however.") + full_message = list("Your feathery lower belly looks swollen with irregular bumps, and feels very heavy.", "Your feathery covered lower abdomen feels really heavy, making it a bit hard to walk.", "The added weight from your collection of eggs constantly reminds you that you'll have to lay soon!", "The sounds of eggs clacking as you walk reminds you that you will have to lay soon!") + emote_descriptor = list("an egg right out of Yonra's feathery crotch!", "into Yonra's belly firmly, forcing her to lay an egg!", ", making Yonra gasp and softly moan while an egg slides out.") + var/verb_descriptor = list("squeezes", "pushes", "hugs") + var/self_verb_descriptor = list("squeeze", "push", "hug") + var/short_emote_descriptor = list("lays", "forces out", "pushes out") + self_emote_descriptor = list("lay", "force out", "push out") + random_emote = list("hisses softly with a blush on her face", "yelps in embarrassment", "grunts a little") + assigned_proc = /mob/living/carbon/human/proc/use_reagent_implant_yonra + +/obj/item/weapon/implant/reagent_generator/yonra/post_implant(mob/living/carbon/source) + START_PROCESSING(SSobj, src) + to_chat(source, "You implant [source] with \the [src].") + source.verbs |= assigned_proc + return 1 + +/obj/item/weapon/implanter/reagent_generator/yonra + implant_type = /obj/item/weapon/implant/reagent_generator/yonra + +/mob/living/carbon/human/proc/use_reagent_implant_yonra() + set name = "Lay Egg" + set desc = "Force Yonra to lay an egg by squeezing into her lower body! This makes the Teshari stop whatever she is doing at the time, greatly embarassing her." + set category = "Object" + set src in view(1) + + //do_reagent_implant(usr) + if(!isliving(usr) || !usr.checkClickCooldown()) + return + + if(usr.incapacitated() || usr.stat > CONSCIOUS) + return + + var/obj/item/weapon/implant/reagent_generator/yonra/rimplant + for(var/obj/item/organ/external/E in organs) + for(var/obj/item/weapon/implant/I in E.implants) + if(istype(I, /obj/item/weapon/implant/reagent_generator)) + rimplant = I + break + if (rimplant) + if(rimplant.reagents.total_volume <= rimplant.transfer_amount) + to_chat(src, "[pick(rimplant.empty_message)]") + return + + new /obj/item/weapon/reagent_containers/food/snacks/egg/teshari(get_turf(src)) + + var/index = rand(0,3) + + if (usr != src) + var/emote = rimplant.emote_descriptor[index] + var/verb_desc = rimplant.verb_descriptor[index] + var/self_verb_desc = rimplant.self_verb_descriptor[index] + usr.visible_message("[usr] [verb_desc] [emote]", + "You [self_verb_desc] [emote]") + else + visible_message("[src] [pick(rimplant.short_emote_descriptor)] an egg.", + "You [pick(rimplant.self_emote_descriptor)] an egg.") + if(prob(15)) + visible_message("[src] [pick(rimplant.random_emote)].") + + rimplant.reagents.remove_any(rimplant.transfer_amount) + +/obj/item/weapon/reagent_containers/food/snacks/egg/teshari + name = "teshari egg" + desc = "It's a large teshari egg." + icon = 'icons/vore/custom_items_vr.dmi' + icon_state = "tesh_egg" + filling_color = "#FDFFD1" + volume = 12 + +/obj/item/weapon/reagent_containers/food/snacks/egg/teshari/New() + ..() + reagents.add_reagent("egg", 10) + bitesize = 2 + +/obj/item/weapon/reagent_containers/food/snacks/egg/teshari/tesh2 + icon_state = "tesh_egg_2" + +//Konabird: Rischi +/obj/item/weapon/implant/reagent_generator/rischi + name = "egg laying implant" + desc = "This is an implant that allows the user to lay eggs." + generated_reagents = list("egg" = 2) + usable_volume = 3000 //They requested 1 egg every ~30 minutes. + transfer_amount = 3000 + + empty_message = list("Your abdomen feels normal and taught, like usual.", "The lack of eggs in your abdomen leaves your belly flat and smooth.", "The reduced pressure in your belly tells you there are no more eggs.", "With a soft sigh, you can feel your body is empty of eggs. You know it will only be a matter of time before an egg forms once again, however.") + full_message = list("Your lower abdomen feels a bit swollen", "You feel a pressure within your abdomen, and a broody mood slowly creeps over you.", "You can feel the egg inside of you shift as you move, the needy feeling to lay slowly growing stronger!", "You can feel the egg inside of you, swelling out your normally taught abdomen considerably. You'll definitely need to lay soon!") + emote_descriptor = list("Rischi, causing the small female to squeak and wriggle, an egg falling from between her legs!", "Rischi's midsection, forcing her to lay an egg!", "Rischi, the Teshari huffing and grunting as an egg is squeezed from her body!") + var/verb_descriptor = list("squeezes", "squashes", "hugs") + var/self_verb_descriptor = list("squeeze", "push", "hug") + var/short_emote_descriptor = list("lays", "forces out", "pushes out") + self_emote_descriptor = list("lay", "force out", "push out") + random_emote = list("trembles and huffs, panting from the exertion.", "sees what has happened and covers her face with both hands!", "whimpers softly, her legs shivering, knees pointed inward from the feeling.") + assigned_proc = /mob/living/carbon/human/proc/use_reagent_implant_rischi + +/obj/item/weapon/implant/reagent_generator/rischi/post_implant(mob/living/carbon/source) + START_PROCESSING(SSobj, src) + to_chat(source, "You implant [source] with \the [src].") + source.verbs |= assigned_proc + return 1 + +/obj/item/weapon/implanter/reagent_generator/rischi + implant_type = /obj/item/weapon/implant/reagent_generator/rischi + +/mob/living/carbon/human/proc/use_reagent_implant_rischi() + set name = "Lay Egg" + set desc = "Force Rischi to lay an egg by squeezing her! What a terribly rude thing to do!" + set category = "Object" + set src in view(1) + + //do_reagent_implant(usr) + if(!isliving(usr) || !usr.checkClickCooldown()) + return + + if(usr.incapacitated() || usr.stat > CONSCIOUS) + return + + var/obj/item/weapon/implant/reagent_generator/rischi/rimplant + for(var/obj/item/organ/external/E in organs) + for(var/obj/item/weapon/implant/I in E.implants) + if(istype(I, /obj/item/weapon/implant/reagent_generator)) + rimplant = I + break + if (rimplant) + if(rimplant.reagents.total_volume <= rimplant.transfer_amount) + to_chat(src, "[pick(rimplant.empty_message)]") + return + + new /obj/item/weapon/reagent_containers/food/snacks/egg/teshari/tesh2(get_turf(src)) + + var/index = rand(0,3) + + if (usr != src) + var/emote = rimplant.emote_descriptor[index] + var/verb_desc = rimplant.verb_descriptor[index] + var/self_verb_desc = rimplant.self_verb_descriptor[index] + usr.visible_message("[usr] [verb_desc] [emote]", + "You [self_verb_desc] [emote]") + else + visible_message("[src] falls to her knees as the urge to lay overwhelms her, letting out a whimper as she [pick(rimplant.short_emote_descriptor)] an egg from between her legs.", + "You fall to your knees as the urge to lay overwhelms you, letting out a whimper as you [pick(rimplant.self_emote_descriptor)] an egg from between your legs.") + if(prob(15)) + visible_message("[src] [pick(rimplant.random_emote)].") + + rimplant.reagents.remove_any(rimplant.transfer_amount) + +/* +/obj/item/weapon/implant/reagent_generator/pumila_nectar //Bugged. Two implants at once messes things up. + generated_reagents = list("honey" = 2) + reagent_name = "honey" + usable_volume = 5000 + + empty_message = list("You appear to be all out of nectar", "You feel as though you are lacking a majority of your nectar.") + full_message = list("You appear to be full of nectar.", "You feel as though you are full of nectar!") + emote_descriptor = list("squeezes nectar", "extracts nectar") + self_emote_descriptor = list("squeeze", "extract") + verb_name = "Extract Honey" + verb_desc = "Obtain pumila's nectar and put it into a container!" + +/obj/item/weapon/implanter/reagent_generator/pumila_nectar + implant_type = /obj/item/weapon/implant/reagent_generator/pumila_nectar +*/ +//Egg item +//------------- +/obj/item/weapon/reagent_containers/food/snacks/egg/roiz + name = "lizard egg" + desc = "It's a large lizard egg." + icon = 'icons/vore/custom_items_vr.dmi' + icon_state = "egg_roiz" + filling_color = "#FDFFD1" + volume = 12 + +/obj/item/weapon/reagent_containers/food/snacks/egg/roiz/New() + ..() + reagents.add_reagent("egg", 9) + bitesize = 2 + +/obj/item/weapon/reagent_containers/food/snacks/egg/roiz/attackby(obj/item/weapon/W as obj, mob/user as mob) + if(istype( W, /obj/item/weapon/pen/crayon )) + var/obj/item/weapon/pen/crayon/C = W + var/clr = C.colourName + + if(!(clr in list("blue","green","mime","orange","purple","rainbow","red","yellow"))) + to_chat(user, "The egg refuses to take on this color!") + return + + to_chat(user, "You color \the [src] [clr]") + icon_state = "egg_roiz_[clr]" + desc = "It's a large lizard egg. It has been colored [clr]!" + if (clr == "rainbow") + var/number = rand(1,4) + icon_state = icon_state + num2text(number, 0) + else + ..() + +/obj/item/weapon/reagent_containers/food/snacks/friedegg/roiz + name = "fried lizard egg" + desc = "A large, fried lizard egg, with a touch of salt and pepper. It looks rather chewy." + icon = 'icons/vore/custom_items_vr.dmi' + icon_state = "friedegg" + volume = 12 + +/obj/item/weapon/reagent_containers/food/snacks/friedegg/roiz/New() + ..() + reagents.add_reagent("protein", 9) + bitesize = 2 + +/obj/item/weapon/reagent_containers/food/snacks/boiledegg/roiz + name = "boiled lizard egg" + desc = "A hard boiled lizard egg. Be careful, a lizard detective may hatch!" + icon = 'icons/vore/custom_items_vr.dmi' + icon_state = "egg_roiz" + volume = 12 + +/obj/item/weapon/reagent_containers/food/snacks/boiledegg/roiz/New() + ..() + reagents.add_reagent("protein", 6) + bitesize = 2 + +/obj/item/weapon/reagent_containers/food/snacks/chocolateegg/roiz + name = "chocolate lizard egg" + desc = "Such huge, sweet, fattening food. You feel gluttonous just looking at it." + icon = 'icons/vore/custom_items_vr.dmi' + icon_state = "chocolateegg_roiz" + filling_color = "#7D5F46" + nutriment_amt = 3 + nutriment_desc = list("chocolate" = 5) + volume = 18 + +/obj/item/weapon/reagent_containers/food/snacks/chocolateegg/roiz/New() + ..() + reagents.add_reagent("sugar", 6) + reagents.add_reagent("coco", 6) + reagents.add_reagent("milk", 2) + bitesize = 2 + +//SilverTalisman: Evian +/obj/item/weapon/implant/reagent_generator/evian + emote_descriptor = list("an egg right out of Evian's lower belly!", "into Evian' belly firmly, forcing him to lay an egg!", "Evian really tight, who promptly lays an egg!") + var/verb_descriptor = list("squeezes", "pushes", "hugs") + var/self_verb_descriptor = list("squeeze", "push", "hug") + var/short_emote_descriptor = list("lays", "forces out", "pushes out") + self_emote_descriptor = list("lay", "force out", "push out") + random_emote = list("hisses softly with a blush on his face", "yelps in embarrassment", "grunts a little") + assigned_proc = /mob/living/carbon/human/proc/use_reagent_implant_evian + +/obj/item/weapon/implant/reagent_generator/evian/post_implant(mob/living/carbon/source) + START_PROCESSING(SSobj, src) + to_chat(source, "You implant [source] with \the [src].") + source.verbs |= assigned_proc + return 1 + +/obj/item/weapon/implanter/reagent_generator/evian + implant_type = /obj/item/weapon/implant/reagent_generator/evian + +/mob/living/carbon/human/proc/use_reagent_implant_evian() + set name = "Lay Egg" + set desc = "Force Evian to lay an egg by squeezing into his lower body! This makes the lizard extremely embarrassed, and it looks funny." + set category = "Object" + set src in view(1) + + //do_reagent_implant(usr) + if(!isliving(usr) || !usr.checkClickCooldown()) + return + + if(usr.incapacitated() || usr.stat > CONSCIOUS) + return + + var/obj/item/weapon/implant/reagent_generator/evian/rimplant + for(var/obj/item/organ/external/E in organs) + for(var/obj/item/weapon/implant/I in E.implants) + if(istype(I, /obj/item/weapon/implant/reagent_generator)) + rimplant = I + break + if (rimplant) + if(rimplant.reagents.total_volume <= rimplant.transfer_amount) + to_chat(src, "[pick(rimplant.empty_message)]") + return + + new /obj/item/weapon/reagent_containers/food/snacks/egg/roiz/evian(get_turf(src)) //Roiz/evian so it gets all the functionality + + var/index = rand(0,3) + + if (usr != src) + var/emote = rimplant.emote_descriptor[index] + var/verb_desc = rimplant.verb_descriptor[index] + var/self_verb_desc = rimplant.self_verb_descriptor[index] + usr.visible_message("[usr] [verb_desc] [emote]", + "You [self_verb_desc] [emote]") + else + visible_message("[src] [pick(rimplant.short_emote_descriptor)] an egg.", + "You [pick(rimplant.self_emote_descriptor)] an egg.") + if(prob(15)) + visible_message("[src] [pick(rimplant.random_emote)].") // M-mlem. + + rimplant.reagents.remove_any(rimplant.transfer_amount) + +/obj/item/weapon/reagent_containers/food/snacks/egg/roiz/evian + name = "dragon egg" + desc = "A quite large dragon egg!" + icon_state = "egg_roiz_yellow" + + +/obj/item/weapon/reagent_containers/food/snacks/egg/roiz/evian/attackby(obj/item/weapon/W as obj, mob/user as mob) + if(istype( W, /obj/item/weapon/pen/crayon)) //No coloring these ones! + return + else + ..() diff --git a/code/modules/vore/fluffstuff/custom_items_vr.dm b/code/modules/vore/fluffstuff/custom_items_vr.dm index ff25ac561d..fa0acd60d6 100644 --- a/code/modules/vore/fluffstuff/custom_items_vr.dm +++ b/code/modules/vore/fluffstuff/custom_items_vr.dm @@ -761,480 +761,6 @@ mid_length = 20 volume = 25 -//WickedTempest: Chakat Tempest -/obj/item/weapon/implant/reagent_generator/tempest - generated_reagents = list("milk" = 2) - reagent_name = "milk" - usable_volume = 1000 - - empty_message = list("Your breasts are almost completely drained!") - full_message = list("Your teats feel heavy and swollen!") - emote_descriptor = list("squeezes milk", "tugs on Tempest's breasts, milking them") - self_emote_descriptor = list("squeeze") - random_emote = list("moos quietly") - verb_name = "Milk" - verb_desc = "Grab Tempest's nipples and milk them into a container! May cause blushing and groaning." - -/obj/item/weapon/implanter/reagent_generator/tempest - implant_type = /obj/item/weapon/implant/reagent_generator/tempest - - -//Hottokeeki: Belle Day -/obj/item/weapon/implant/reagent_generator/belle - generated_reagents = list("milk" = 2) - reagent_name = "milk" - usable_volume = 5000 - - empty_message = list("Your breasts and or udder feel almost completely drained!", "You're feeling a liittle on the empty side...") - full_message = list("You're due for a milking; your breasts and or udder feel heavy and swollen!", "Looks like you've got some full tanks!") - emote_descriptor = list("squeezes milk", "tugs on Belle's breasts/udders, milking them", "extracts milk") - self_emote_descriptor = list("squeeze", "extract") - random_emote = list("moos", "mrours", "groans softly") - verb_name = "Milk" - verb_desc = "Obtain Belle's milk and put it into a container! May cause blushing and groaning, or arousal." - -/obj/item/weapon/implanter/reagent_generator/belle - implant_type = /obj/item/weapon/implant/reagent_generator/belle - -//Gowst: Eldi Moljir -//Eldi iz coolest elf-dorf. -/obj/item/weapon/implant/reagent_generator/eldi - name = "lactation implant" - desc = "This is an implant that allows the user to lactate." - generated_reagents = list("milk" = 2) - reagent_name = "milk" - usable_volume = 1000 - - empty_message = list("Your breasts feel unusually empty.", "Your chest feels lighter - your milk supply is empty!", "Your milk reserves have run dry.", "Your grateful nipples ache as the last of your milk leaves them.") - full_message = list("Your breasts ache badly - they are swollen and feel fit to burst!", "You need to be milked! Your breasts feel bloated, eager for release.", "Your milky breasts are starting to leak...") - emote_descriptor = list("squeezes Eldi's nipples, milking them", "milks Eldi's breasts", "extracts milk") - self_emote_descriptor = list("squeeze out", "extract") - random_emote = list("surpresses a moan", "gasps sharply", "bites her lower lip") - verb_name = "Milk" - verb_desc = "Grab Eldi's breasts and milk her, storing her fresh, warm milk in a container. This will undoubtedly turn her on." - -/obj/item/weapon/implanter/reagent_generator/eldi - implant_type = /obj/item/weapon/implant/reagent_generator/eldi - -//Vorrarkul: Theodora Lindt -/obj/item/weapon/implant/reagent_generator/vorrarkul - generated_reagents = list("chocolate_milk" = 2) - reagent_name = "chocalate milk" - usable_volume = 1000 - - empty_message = list("Your nipples are sore from being milked!") - full_message = list("Your breasts are full, their sweet scent emanating from your chest!") - emote_descriptor = list("squeezes chocolate milk from Theodora", "tugs on Theodora's nipples, milking them", "kneads Theodora's breasts, milking them") - self_emote_descriptor = list("squeeze", "knead") - random_emote = list("moans softly", "gives an involuntary squeal") - verb_name = "Milk" - verb_desc = "Grab Theodora's breasts and extract delicious chocolate milk from them!" - -/obj/item/weapon/implanter/reagent_generator/vorrarkul - implant_type = /obj/item/weapon/implant/reagent_generator/vorrarkul - -//Lycanthorph: Savannah Dixon -/obj/item/weapon/implant/reagent_generator/savannah - generated_reagents = list("milk" = 2) - reagent_name = "milk" - usable_volume = 1000 - - empty_message = list("Your nipples are sore from being milked!", "Your breasts feel drained, milk is no longer leaking from your nipples!") - full_message = list("Your breasts are full, their sweet scent emanating from your chest!", "Your breasts feel full, milk is starting to leak from your nipples, filling the air with it's sweet scent!") - emote_descriptor = list("squeezes sweet milk from Savannah", "tugs on Savannah's nipples, milking them", "kneads Savannah's breasts, milking them") - self_emote_descriptor = list("squeeze", "knead") - random_emote = list("lets out a soft moan", "gives an involuntary squeal") - verb_name = "Milk" - verb_desc = "Grab Savannah's breasts and extract sweet milk from them!" - -/obj/item/weapon/implanter/reagent_generator/savannah - implant_type = /obj/item/weapon/implant/reagent_generator/savannah - -//SpoopyLizz: Roiz Lizden -//I made this! Woo! -//implant -//-------------------- -/obj/item/weapon/implant/reagent_generator/roiz - name = "egg laying implant" - desc = "This is an implant that allows the user to lay eggs." - generated_reagents = list("egg" = 2) - usable_volume = 500 - transfer_amount = 50 - - empty_message = list("Your lower belly feels smooth and empty. Sorry, we're out of eggs!", "The reduced pressure in your lower belly tells you there are no more eggs.") - full_message = list("Your lower belly looks swollen with irregular bumps, and it feels heavy.", "Your lower abdomen feels really heavy, making it a bit hard to walk.") - emote_descriptor = list("an egg right out of Roiz's lower belly!", "into Roiz' belly firmly, forcing him to lay an egg!", "Roiz really tight, who promptly lays an egg!") - var/verb_descriptor = list("squeezes", "pushes", "hugs") - var/self_verb_descriptor = list("squeeze", "push", "hug") - var/short_emote_descriptor = list("lays", "forces out", "pushes out") - self_emote_descriptor = list("lay", "force out", "push out") - random_emote = list("hisses softly with a blush on his face", "yelps in embarrassment", "grunts a little") - assigned_proc = /mob/living/carbon/human/proc/use_reagent_implant_roiz - -/obj/item/weapon/implant/reagent_generator/roiz/post_implant(mob/living/carbon/source) - START_PROCESSING(SSobj, src) - to_chat(source, "You implant [source] with \the [src].") - source.verbs |= assigned_proc - return 1 - -/obj/item/weapon/implanter/reagent_generator/roiz - implant_type = /obj/item/weapon/implant/reagent_generator/roiz - -/mob/living/carbon/human/proc/use_reagent_implant_roiz() - set name = "Lay Egg" - set desc = "Force Roiz to lay an egg by squeezing into his lower body! This makes the lizard extremely embarrassed, and it looks funny." - set category = "Object" - set src in view(1) - - //do_reagent_implant(usr) - if(!isliving(usr) || !usr.checkClickCooldown()) - return - - if(usr.incapacitated() || usr.stat > CONSCIOUS) - return - - var/obj/item/weapon/implant/reagent_generator/roiz/rimplant - for(var/obj/item/organ/external/E in organs) - for(var/obj/item/weapon/implant/I in E.implants) - if(istype(I, /obj/item/weapon/implant/reagent_generator)) - rimplant = I - break - if (rimplant) - if(rimplant.reagents.total_volume <= rimplant.transfer_amount) - to_chat(src, "[pick(rimplant.empty_message)]") - return - - new /obj/item/weapon/reagent_containers/food/snacks/egg/roiz(get_turf(src)) - - var/index = rand(0,3) - - if (usr != src) - var/emote = rimplant.emote_descriptor[index] - var/verb_desc = rimplant.verb_descriptor[index] - var/self_verb_desc = rimplant.self_verb_descriptor[index] - usr.visible_message("[usr] [verb_desc] [emote]", - "You [self_verb_desc] [emote]") - else - visible_message("[src] [pick(rimplant.short_emote_descriptor)] an egg.", - "You [pick(rimplant.self_emote_descriptor)] an egg.") - if(prob(15)) - visible_message("[src] [pick(rimplant.random_emote)].") // M-mlem. - - rimplant.reagents.remove_any(rimplant.transfer_amount) - -//Cameron653: Jasmine Lizden -/obj/item/weapon/implant/reagent_generator/jasmine - name = "egg laying implant" - desc = "This is an implant that allows the user to lay eggs." - generated_reagents = list("egg" = 2) - usable_volume = 500 - transfer_amount = 50 - - empty_message = list("Your lower belly feels flat, empty, and somewhat rough!", "Your lower belly feels completely empty, no more bulges visible... At least, for the moment!") - full_message = list("Your lower belly is stretched out, smooth,and heavy, small bulges visible from within!", "It takes considerably more effort to move yourself, the large bulges within your gut most likely the cause!") - emote_descriptor = list("an egg from Jasmine's tauric belly!", "into Jasmine's gut, forcing her to lay a considerably large egg!", "Jasmine with a considerable amount of force, causing an egg to slip right out of her!") - var/verb_descriptor = list("squeezes", "pushes", "hugs") - var/self_verb_descriptor = list("squeeze", "push", "hug") - var/short_emote_descriptor = list("lays", "forces out", "pushes out") - self_emote_descriptor = list("lay", "force out", "push out") - random_emote = list("hisses softly with a blush on her face", "bites down on her lower lip", "lets out a light huff") - assigned_proc = /mob/living/carbon/human/proc/use_reagent_implant_jasmine - -/obj/item/weapon/implant/reagent_generator/jasmine/post_implant(mob/living/carbon/source) - START_PROCESSING(SSobj, src) - to_chat(source, "You implant [source] with \the [src].") - source.verbs |= assigned_proc - return 1 - -/obj/item/weapon/implanter/reagent_generator/jasmine - implant_type = /obj/item/weapon/implant/reagent_generator/jasmine - -/mob/living/carbon/human/proc/use_reagent_implant_jasmine() - set name = "Lay Egg" - set desc = "Cause Jasmine to lay an egg by squeezing her tauric belly!" - set category = "Object" - set src in view(1) - - //do_reagent_implant(usr) - if(!isliving(usr) || !usr.checkClickCooldown()) - return - - if(usr.incapacitated() || usr.stat > CONSCIOUS) - return - - var/obj/item/weapon/implant/reagent_generator/jasmine/rimplant - for(var/obj/item/organ/external/E in organs) - for(var/obj/item/weapon/implant/I in E.implants) - if(istype(I, /obj/item/weapon/implant/reagent_generator)) - rimplant = I - break - if (rimplant) - if(rimplant.reagents.total_volume <= rimplant.transfer_amount) - to_chat(src, "[pick(rimplant.empty_message)]") - return - - new /obj/item/weapon/reagent_containers/food/snacks/egg/roiz(get_turf(src)) - - var/index = rand(0,3) - - if (usr != src) - var/emote = rimplant.emote_descriptor[index] - var/verb_desc = rimplant.verb_descriptor[index] - var/self_verb_desc = rimplant.self_verb_descriptor[index] - usr.visible_message("[usr] [verb_desc] [emote]", - "You [self_verb_desc] [emote]") - else - visible_message("[src] [pick(rimplant.short_emote_descriptor)] an egg.", - "You [pick(rimplant.self_emote_descriptor)] an egg.") - if(prob(15)) - visible_message("[src] [pick(rimplant.random_emote)].") - - rimplant.reagents.remove_any(rimplant.transfer_amount) - -//Draycu: Schae Yonra -/obj/item/weapon/implant/reagent_generator/yonra - name = "egg laying implant" - desc = "This is an implant that allows the user to lay eggs." - generated_reagents = list("egg" = 2) - usable_volume = 500 - transfer_amount = 50 - - empty_message = list("Your feathery lower belly feels smooth and empty. For now...", "The lack of clacking eggs in your abdomen lets you know you're free to continue your day as normal.", "The reduced pressure in your lower belly tells you there are no more eggs.", "With a soft sigh, you can feel your lower body is empty. You know it will only be a matter of time before another batch fills you up again, however.") - full_message = list("Your feathery lower belly looks swollen with irregular bumps, and feels very heavy.", "Your feathery covered lower abdomen feels really heavy, making it a bit hard to walk.", "The added weight from your collection of eggs constantly reminds you that you'll have to lay soon!", "The sounds of eggs clacking as you walk reminds you that you will have to lay soon!") - emote_descriptor = list("an egg right out of Yonra's feathery crotch!", "into Yonra's belly firmly, forcing her to lay an egg!", ", making Yonra gasp and softly moan while an egg slides out.") - var/verb_descriptor = list("squeezes", "pushes", "hugs") - var/self_verb_descriptor = list("squeeze", "push", "hug") - var/short_emote_descriptor = list("lays", "forces out", "pushes out") - self_emote_descriptor = list("lay", "force out", "push out") - random_emote = list("hisses softly with a blush on her face", "yelps in embarrassment", "grunts a little") - assigned_proc = /mob/living/carbon/human/proc/use_reagent_implant_yonra - -/obj/item/weapon/implant/reagent_generator/yonra/post_implant(mob/living/carbon/source) - START_PROCESSING(SSobj, src) - to_chat(source, "You implant [source] with \the [src].") - source.verbs |= assigned_proc - return 1 - -/obj/item/weapon/implanter/reagent_generator/yonra - implant_type = /obj/item/weapon/implant/reagent_generator/yonra - -/mob/living/carbon/human/proc/use_reagent_implant_yonra() - set name = "Lay Egg" - set desc = "Force Yonra to lay an egg by squeezing into her lower body! This makes the Teshari stop whatever she is doing at the time, greatly embarassing her." - set category = "Object" - set src in view(1) - - //do_reagent_implant(usr) - if(!isliving(usr) || !usr.checkClickCooldown()) - return - - if(usr.incapacitated() || usr.stat > CONSCIOUS) - return - - var/obj/item/weapon/implant/reagent_generator/yonra/rimplant - for(var/obj/item/organ/external/E in organs) - for(var/obj/item/weapon/implant/I in E.implants) - if(istype(I, /obj/item/weapon/implant/reagent_generator)) - rimplant = I - break - if (rimplant) - if(rimplant.reagents.total_volume <= rimplant.transfer_amount) - to_chat(src, "[pick(rimplant.empty_message)]") - return - - new /obj/item/weapon/reagent_containers/food/snacks/egg/teshari(get_turf(src)) - - var/index = rand(0,3) - - if (usr != src) - var/emote = rimplant.emote_descriptor[index] - var/verb_desc = rimplant.verb_descriptor[index] - var/self_verb_desc = rimplant.self_verb_descriptor[index] - usr.visible_message("[usr] [verb_desc] [emote]", - "You [self_verb_desc] [emote]") - else - visible_message("[src] [pick(rimplant.short_emote_descriptor)] an egg.", - "You [pick(rimplant.self_emote_descriptor)] an egg.") - if(prob(15)) - visible_message("[src] [pick(rimplant.random_emote)].") - - rimplant.reagents.remove_any(rimplant.transfer_amount) - -/obj/item/weapon/reagent_containers/food/snacks/egg/teshari - name = "teshari egg" - desc = "It's a large teshari egg." - icon = 'icons/vore/custom_items_vr.dmi' - icon_state = "tesh_egg" - filling_color = "#FDFFD1" - volume = 12 - -/obj/item/weapon/reagent_containers/food/snacks/egg/teshari/New() - ..() - reagents.add_reagent("egg", 10) - bitesize = 2 - -/obj/item/weapon/reagent_containers/food/snacks/egg/teshari/tesh2 - icon_state = "tesh_egg_2" - -//Konabird: Rischi -/obj/item/weapon/implant/reagent_generator/rischi - name = "egg laying implant" - desc = "This is an implant that allows the user to lay eggs." - generated_reagents = list("egg" = 2) - usable_volume = 3000 //They requested 1 egg every ~30 minutes. - transfer_amount = 3000 - - empty_message = list("Your abdomen feels normal and taught, like usual.", "The lack of eggs in your abdomen leaves your belly flat and smooth.", "The reduced pressure in your belly tells you there are no more eggs.", "With a soft sigh, you can feel your body is empty of eggs. You know it will only be a matter of time before an egg forms once again, however.") - full_message = list("Your lower abdomen feels a bit swollen", "You feel a pressure within your abdomen, and a broody mood slowly creeps over you.", "You can feel the egg inside of you shift as you move, the needy feeling to lay slowly growing stronger!", "You can feel the egg inside of you, swelling out your normally taught abdomen considerably. You'll definitely need to lay soon!") - emote_descriptor = list("Rischi, causing the small female to squeak and wriggle, an egg falling from between her legs!", "Rischi's midsection, forcing her to lay an egg!", "Rischi, the Teshari huffing and grunting as an egg is squeezed from her body!") - var/verb_descriptor = list("squeezes", "squashes", "hugs") - var/self_verb_descriptor = list("squeeze", "push", "hug") - var/short_emote_descriptor = list("lays", "forces out", "pushes out") - self_emote_descriptor = list("lay", "force out", "push out") - random_emote = list("trembles and huffs, panting from the exertion.", "sees what has happened and covers her face with both hands!", "whimpers softly, her legs shivering, knees pointed inward from the feeling.") - assigned_proc = /mob/living/carbon/human/proc/use_reagent_implant_rischi - -/obj/item/weapon/implant/reagent_generator/rischi/post_implant(mob/living/carbon/source) - START_PROCESSING(SSobj, src) - to_chat(source, "You implant [source] with \the [src].") - source.verbs |= assigned_proc - return 1 - -/obj/item/weapon/implanter/reagent_generator/rischi - implant_type = /obj/item/weapon/implant/reagent_generator/rischi - -/mob/living/carbon/human/proc/use_reagent_implant_rischi() - set name = "Lay Egg" - set desc = "Force Rischi to lay an egg by squeezing her! What a terribly rude thing to do!" - set category = "Object" - set src in view(1) - - //do_reagent_implant(usr) - if(!isliving(usr) || !usr.checkClickCooldown()) - return - - if(usr.incapacitated() || usr.stat > CONSCIOUS) - return - - var/obj/item/weapon/implant/reagent_generator/rischi/rimplant - for(var/obj/item/organ/external/E in organs) - for(var/obj/item/weapon/implant/I in E.implants) - if(istype(I, /obj/item/weapon/implant/reagent_generator)) - rimplant = I - break - if (rimplant) - if(rimplant.reagents.total_volume <= rimplant.transfer_amount) - to_chat(src, "[pick(rimplant.empty_message)]") - return - - new /obj/item/weapon/reagent_containers/food/snacks/egg/teshari/tesh2(get_turf(src)) - - var/index = rand(0,3) - - if (usr != src) - var/emote = rimplant.emote_descriptor[index] - var/verb_desc = rimplant.verb_descriptor[index] - var/self_verb_desc = rimplant.self_verb_descriptor[index] - usr.visible_message("[usr] [verb_desc] [emote]", - "You [self_verb_desc] [emote]") - else - visible_message("[src] falls to her knees as the urge to lay overwhelms her, letting out a whimper as she [pick(rimplant.short_emote_descriptor)] an egg from between her legs.", - "You fall to your knees as the urge to lay overwhelms you, letting out a whimper as you [pick(rimplant.self_emote_descriptor)] an egg from between your legs.") - if(prob(15)) - visible_message("[src] [pick(rimplant.random_emote)].") - - rimplant.reagents.remove_any(rimplant.transfer_amount) - -/* -/obj/item/weapon/implant/reagent_generator/pumila_nectar //Bugged. Two implants at once messes things up. - generated_reagents = list("honey" = 2) - reagent_name = "honey" - usable_volume = 5000 - - empty_message = list("You appear to be all out of nectar", "You feel as though you are lacking a majority of your nectar.") - full_message = list("You appear to be full of nectar.", "You feel as though you are full of nectar!") - emote_descriptor = list("squeezes nectar", "extracts nectar") - self_emote_descriptor = list("squeeze", "extract") - verb_name = "Extract Honey" - verb_desc = "Obtain pumila's nectar and put it into a container!" - -/obj/item/weapon/implanter/reagent_generator/pumila_nectar - implant_type = /obj/item/weapon/implant/reagent_generator/pumila_nectar -*/ -//Egg item -//------------- -/obj/item/weapon/reagent_containers/food/snacks/egg/roiz - name = "lizard egg" - desc = "It's a large lizard egg." - icon = 'icons/vore/custom_items_vr.dmi' - icon_state = "egg_roiz" - filling_color = "#FDFFD1" - volume = 12 - -/obj/item/weapon/reagent_containers/food/snacks/egg/roiz/New() - ..() - reagents.add_reagent("egg", 9) - bitesize = 2 - -/obj/item/weapon/reagent_containers/food/snacks/egg/roiz/attackby(obj/item/weapon/W as obj, mob/user as mob) - if(istype( W, /obj/item/weapon/pen/crayon )) - var/obj/item/weapon/pen/crayon/C = W - var/clr = C.colourName - - if(!(clr in list("blue","green","mime","orange","purple","rainbow","red","yellow"))) - to_chat(user, "The egg refuses to take on this color!") - return - - to_chat(user, "You color \the [src] [clr]") - icon_state = "egg_roiz_[clr]" - desc = "It's a large lizard egg. It has been colored [clr]!" - if (clr == "rainbow") - var/number = rand(1,4) - icon_state = icon_state + num2text(number, 0) - else - ..() - -/obj/item/weapon/reagent_containers/food/snacks/friedegg/roiz - name = "fried lizard egg" - desc = "A large, fried lizard egg, with a touch of salt and pepper. It looks rather chewy." - icon = 'icons/vore/custom_items_vr.dmi' - icon_state = "friedegg" - volume = 12 - -/obj/item/weapon/reagent_containers/food/snacks/friedegg/roiz/New() - ..() - reagents.add_reagent("protein", 9) - bitesize = 2 - -/obj/item/weapon/reagent_containers/food/snacks/boiledegg/roiz - name = "boiled lizard egg" - desc = "A hard boiled lizard egg. Be careful, a lizard detective may hatch!" - icon = 'icons/vore/custom_items_vr.dmi' - icon_state = "egg_roiz" - volume = 12 - -/obj/item/weapon/reagent_containers/food/snacks/boiledegg/roiz/New() - ..() - reagents.add_reagent("protein", 6) - bitesize = 2 - -/obj/item/weapon/reagent_containers/food/snacks/chocolateegg/roiz - name = "chocolate lizard egg" - desc = "Such huge, sweet, fattening food. You feel gluttonous just looking at it." - icon = 'icons/vore/custom_items_vr.dmi' - icon_state = "chocolateegg_roiz" - filling_color = "#7D5F46" - nutriment_amt = 3 - nutriment_desc = list("chocolate" = 5) - volume = 18 - -/obj/item/weapon/reagent_containers/food/snacks/chocolateegg/roiz/New() - ..() - reagents.add_reagent("sugar", 6) - reagents.add_reagent("coco", 6) - reagents.add_reagent("milk", 2) - bitesize = 2 - //PontifexMinimus: Lucius/Lucia Null /obj/item/weapon/fluff/dragor_dot name = "supplemental battery" @@ -1443,80 +969,6 @@ name = "Malady's riding crop" desc = "An infernum made riding crop with Malady Blanche engraved in the shaft. It's a little worn from how many butts it has spanked." - -//SilverTalisman: Evian -/obj/item/weapon/implant/reagent_generator/evian - emote_descriptor = list("an egg right out of Evian's lower belly!", "into Evian' belly firmly, forcing him to lay an egg!", "Evian really tight, who promptly lays an egg!") - var/verb_descriptor = list("squeezes", "pushes", "hugs") - var/self_verb_descriptor = list("squeeze", "push", "hug") - var/short_emote_descriptor = list("lays", "forces out", "pushes out") - self_emote_descriptor = list("lay", "force out", "push out") - random_emote = list("hisses softly with a blush on his face", "yelps in embarrassment", "grunts a little") - assigned_proc = /mob/living/carbon/human/proc/use_reagent_implant_evian - -/obj/item/weapon/implant/reagent_generator/evian/post_implant(mob/living/carbon/source) - START_PROCESSING(SSobj, src) - to_chat(source, "You implant [source] with \the [src].") - source.verbs |= assigned_proc - return 1 - -/obj/item/weapon/implanter/reagent_generator/evian - implant_type = /obj/item/weapon/implant/reagent_generator/evian - -/mob/living/carbon/human/proc/use_reagent_implant_evian() - set name = "Lay Egg" - set desc = "Force Evian to lay an egg by squeezing into his lower body! This makes the lizard extremely embarrassed, and it looks funny." - set category = "Object" - set src in view(1) - - //do_reagent_implant(usr) - if(!isliving(usr) || !usr.checkClickCooldown()) - return - - if(usr.incapacitated() || usr.stat > CONSCIOUS) - return - - var/obj/item/weapon/implant/reagent_generator/evian/rimplant - for(var/obj/item/organ/external/E in organs) - for(var/obj/item/weapon/implant/I in E.implants) - if(istype(I, /obj/item/weapon/implant/reagent_generator)) - rimplant = I - break - if (rimplant) - if(rimplant.reagents.total_volume <= rimplant.transfer_amount) - to_chat(src, "[pick(rimplant.empty_message)]") - return - - new /obj/item/weapon/reagent_containers/food/snacks/egg/roiz/evian(get_turf(src)) //Roiz/evian so it gets all the functionality - - var/index = rand(0,3) - - if (usr != src) - var/emote = rimplant.emote_descriptor[index] - var/verb_desc = rimplant.verb_descriptor[index] - var/self_verb_desc = rimplant.self_verb_descriptor[index] - usr.visible_message("[usr] [verb_desc] [emote]", - "You [self_verb_desc] [emote]") - else - visible_message("[src] [pick(rimplant.short_emote_descriptor)] an egg.", - "You [pick(rimplant.self_emote_descriptor)] an egg.") - if(prob(15)) - visible_message("[src] [pick(rimplant.random_emote)].") // M-mlem. - - rimplant.reagents.remove_any(rimplant.transfer_amount) - -/obj/item/weapon/reagent_containers/food/snacks/egg/roiz/evian - name = "dragon egg" - desc = "A quite large dragon egg!" - icon_state = "egg_roiz_yellow" - - -/obj/item/weapon/reagent_containers/food/snacks/egg/roiz/evian/attackby(obj/item/weapon/W as obj, mob/user as mob) - if(istype( W, /obj/item/weapon/pen/crayon)) //No coloring these ones! - return - else - ..() - //jacknoir413:Areax Third /obj/item/weapon/melee/baton/fluff/stunstaff name = "Electrostaff" diff --git a/code/modules/vore/resizing/resize_vr.dm b/code/modules/vore/resizing/resize_vr.dm index 3db6dc4386..ea7ec4c5cd 100644 --- a/code/modules/vore/resizing/resize_vr.dm +++ b/code/modules/vore/resizing/resize_vr.dm @@ -251,7 +251,7 @@ var/const/RESIZE_A_SMALLTINY = (RESIZE_SMALL + RESIZE_TINY) / 2 now_pushing = 0 forceMove(tmob.loc) if(a_intent == I_GRAB || a_intent == I_DISARM) - if(tmob.a_intent = I_HELP) + if(tmob.a_intent == I_HELP) tmob.resting = 1 else tmob.Weaken(1) diff --git a/code/unit_tests/mob_tests.dm b/code/unit_tests/mob_tests.dm index c6b21e5d36..0c74449d2d 100644 --- a/code/unit_tests/mob_tests.dm +++ b/code/unit_tests/mob_tests.dm @@ -27,3 +27,120 @@ qdel(H) return 1 + + +/datum/modifier/unit_test + +/datum/unit_test/modifier + name = "modifier test template" + var/mob/living/subject = null + var/subject_type = /mob/living/carbon/human + var/list/inputs = list(1.00, 0.75, 0.50, 0.25, 0.00, -0.50, -1.0, -2.0) + var/list/expected_outputs = list(1.00, 0.75, 0.50, 0.25, 0.00, -0.50, -1.0, -2.0) + var/datum/modifier/test_modifier = null + var/issues = 0 + +/datum/unit_test/modifier/start_test() + // Arrange. + subject = new subject_type(get_standard_turf()) + subject.add_modifier(/datum/modifier/unit_test) + test_modifier = subject.get_modifier_of_type(/datum/modifier/unit_test) + + // Act, + for(var/i = 1 to inputs.len) + set_tested_variable(test_modifier, inputs[i]) + var/actual = round(get_test_value(subject), 0.01) // Rounding because floating point schannigans. + if(actual != expected_outputs[i]) + issues++ + log_bad("Input '[inputs[i]]' did not match expected output '[expected_outputs[i]]', but was instead '[actual]'.") + + // Assert. + if(issues) + fail("[issues] issues were found.") + else + pass("No issues found.") + qdel(subject) + return TRUE + +// Override for subtypes. +/datum/unit_test/modifier/proc/set_tested_variable(datum/modifier/M, new_value) + return + +/datum/unit_test/modifier/proc/get_test_value(mob/living/L) + return + + +/datum/unit_test/modifier/heat_protection + name = "MOB: human mob heat protection is calculated correctly" + +/datum/unit_test/modifier/heat_protection/set_tested_variable(datum/modifier/M, new_value) + M.heat_protection = new_value + +/datum/unit_test/modifier/heat_protection/get_test_value(mob/living/L) + return L.get_heat_protection(1000) + +/datum/unit_test/modifier/heat_protection/simple_mob + name = "MOB: simple mob heat protection is calculated correctly" + subject_type = /mob/living/simple_mob + + +/datum/unit_test/modifier/cold_protection + name = "MOB: human mob cold protection is calculated correctly" + +/datum/unit_test/modifier/cold_protection/set_tested_variable(datum/modifier/M, new_value) + M.cold_protection = new_value + +/datum/unit_test/modifier/cold_protection/get_test_value(mob/living/L) + return L.get_cold_protection(50) + +/datum/unit_test/modifier/cold_protection/simple_mob + name = "MOB: simple mob cold protection is calculated correctly" + subject_type = /mob/living/simple_mob + + +/datum/unit_test/modifier/shock_protection + name = "MOB: human mob shock protection is calculated correctly" + inputs = list(3.00, 2.00, 1.50, 1.00, 0.75, 0.50, 0.25, 0.00) + expected_outputs = list(-2.00, -1.00, -0.50, 0.00, 0.25, 0.50, 0.75, 1.00) + +/datum/unit_test/modifier/shock_protection/set_tested_variable(datum/modifier/M, new_value) + M.siemens_coefficient = new_value + +/datum/unit_test/modifier/shock_protection/get_test_value(mob/living/L) + return L.get_shock_protection() + +/datum/unit_test/modifier/shock_protection/simple_mob + name = "MOB: simple mob shock protection is calculated correctly" + subject_type = /mob/living/simple_mob + + +/datum/unit_test/modifier/percentage_armor + name = "MOB: human mob percentage armor is calculated correctly" + inputs = list(100, 75, 50, 25, 0) + expected_outputs = list(100, 75, 50, 25, 0) + +/datum/unit_test/modifier/percentage_armor/set_tested_variable(datum/modifier/M, new_value) + M.armor_percent = list("melee" = new_value) + +/datum/unit_test/modifier/percentage_armor/get_test_value(mob/living/L) + return L.getarmor(null, "melee") + +/datum/unit_test/modifier/percentage_armor/simple_mob + name = "MOB: simple mob percentage armor is calculated correctly" + subject_type = /mob/living/simple_mob + + +/datum/unit_test/modifier/percentage_flat + name = "MOB: human mob flat armor is calculated correctly" + inputs = list(100, 75, 50, 25, 0) + expected_outputs = list(100, 75, 50, 25, 0) + +/datum/unit_test/modifier/percentage_flat/set_tested_variable(datum/modifier/M, new_value) + M.armor_flat = list("melee" = new_value) + +/datum/unit_test/modifier/percentage_flat/get_test_value(mob/living/L) + return L.getsoak(null, "melee") + +/datum/unit_test/modifier/percentage_flat/simple_mob + name = "MOB: simple mob flat armor is calculated correctly" + subject_type = /mob/living/simple_mob diff --git a/html/changelog.html b/html/changelog.html index e3ea40755b..39c9522a1d 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -53,6 +53,52 @@ -->
+

06 August 2020

+

Cerebulon updated:

+ +

ForFoxSake updated:

+ +

Mechoid updated:

+ +

Rykka Stormheart updated:

+ + +

03 August 2020

+

Cerebulon updated:

+ +

Mechoid updated:

+ +

Rykka Stormheart updated:

+ +

21 June 2020

Arokha updated:

)); - ops.unshift(( -
{ - this.setSelected(null); - }}> - -- {placeholder} -- -
- )); + if (placeholder) { + ops.unshift(( +
{ + this.setSelected(null); + }}> + -- {placeholder} -- +
+ )); + } return ops; } @@ -73,6 +75,7 @@ export class Dropdown extends Component { noscroll, nochevron, width, + maxHeight, onClick, selected, disabled, @@ -92,6 +95,7 @@ export class Dropdown extends Component { tabIndex="-1" style={{ 'width': width, + 'max-height': maxHeight, }} className={classes([ noscroll && 'Dropdown__menu-noscroll' || 'Dropdown__menu', diff --git a/tgui/packages/tgui/components/NanoMap.js b/tgui/packages/tgui/components/NanoMap.js index 8ee21ef4a9..261fb8b1d3 100644 --- a/tgui/packages/tgui/components/NanoMap.js +++ b/tgui/packages/tgui/components/NanoMap.js @@ -2,16 +2,15 @@ import { Box, Icon, Tooltip } from '.'; import { Component } from 'inferno'; import { useBackend } from "../backend"; import { resolveAsset } from '../assets'; +import { logger } from '../logging'; export class NanoMap extends Component { constructor(props) { super(props); // Auto center based on window size - const Xcenter = (window.innerWidth / 2) - 256; - this.state = { - offsetX: Xcenter, + offsetX: 0, offsetY: 0, transform: 'none', dragging: false, @@ -42,8 +41,8 @@ export class NanoMap extends Component { const newOffsetX = e.screenX - state.originX; const newOffsetY = e.screenY - state.originY; if (prevState.dragging) { - state.offsetX += newOffsetX; - state.offsetY += newOffsetY; + state.offsetX += (newOffsetX / this.props.zoom); + state.offsetY += (newOffsetY / this.props.zoom); state.originX = e.screenX; state.originY = e.screenY; } else { @@ -71,11 +70,12 @@ export class NanoMap extends Component { const { offsetX, offsetY } = this.state; const { children, zoom, reset } = this.props; + let matrix + = `matrix(${zoom}, 0, 0, ${zoom}, ${offsetX * zoom}, ${offsetY * zoom})`; + const newStyle = { - width: '512px', - height: '512px', - "margin-top": offsetY + 'px', - "margin-left": offsetX + 'px', + width: '560px', + height: '560px', "overflow": "hidden", "position": "relative", "padding": "0px", @@ -83,8 +83,7 @@ export class NanoMap extends Component { "url("+config.map+"_nanomap_z"+config.mapZLevel+".png)", "background-size": "cover", "text-align": "center", - "transform-origin": "center center", - "transform": "scale(" + zoom + ")", + "transform": matrix, }; return ( @@ -110,32 +109,26 @@ const NanoMapMarker = (props, context) => { icon, tooltip, color, + onClick, } = props; - // Please note, the horrifying `3.65714285714` is just the ratio of the - // width/height of the minimap *element* (not image) - // to the actual turf size of the map - const rx = (-256 * (zoom - 1) - + (x * (3.65714285714 * zoom)) - - 1.5 * zoom - 3); - const ry = (512 * zoom - - (y * (3.65714285714 * zoom)) - + zoom - 1.5); + const rx = (x * 4) - 5; + const ry = (y * 4) - 4; + return ( -
- - - - -
+ + + + ); }; diff --git a/tgui/packages/tgui/components/Section.js b/tgui/packages/tgui/components/Section.js index fb090fa81e..c5df9f6b5d 100644 --- a/tgui/packages/tgui/components/Section.js +++ b/tgui/packages/tgui/components/Section.js @@ -29,6 +29,7 @@ export const Section = props => { 'Section', 'Section--level--' + level, fill && 'Section--fill', + scrollable && 'Section--scrollable', flexGrow && 'Section--flex', className, ...computeBoxClassName(rest), diff --git a/tgui/packages/tgui/components/Tooltip.js b/tgui/packages/tgui/components/Tooltip.js index c46f3c45f7..1cc6ed51cc 100644 --- a/tgui/packages/tgui/components/Tooltip.js +++ b/tgui/packages/tgui/components/Tooltip.js @@ -4,6 +4,7 @@ export const Tooltip = props => { const { content, position = 'bottom', + scale, } = props; // Empirically calculated length of the string, // at which tooltip text starts to overflow. @@ -14,6 +15,7 @@ export const Tooltip = props => { 'Tooltip', long && 'Tooltip--long', position && 'Tooltip--' + position, + scale && 'Tooltip--scale--' + Math.floor(scale), ])} data-tooltip={content} /> ); diff --git a/tgui/packages/tgui/components/index.js b/tgui/packages/tgui/components/index.js index 0d2b2ddab6..71fe763ef8 100644 --- a/tgui/packages/tgui/components/index.js +++ b/tgui/packages/tgui/components/index.js @@ -26,4 +26,4 @@ export { Section } from './Section'; export { Slider } from './Slider'; export { Table } from './Table'; export { Tabs } from './Tabs'; -export { Tooltip } from './Tooltip'; +export { Tooltip } from './Tooltip'; \ No newline at end of file diff --git a/tgui/packages/tgui/constants.js b/tgui/packages/tgui/constants.js index 20ba00cb75..c8155e1229 100644 --- a/tgui/packages/tgui/constants.js +++ b/tgui/packages/tgui/constants.js @@ -4,6 +4,10 @@ export const UI_UPDATE = 1; export const UI_DISABLED = 0; export const UI_CLOSE = -1; +// Atmospheric helpers +/** 0.0 Degrees Celsius in Kelvin */ +export const T0C = 273.15; + // All game related colors are stored here export const COLORS = { // Department colors @@ -48,77 +52,105 @@ export const CSS_COLORS = [ 'label', ]; + +// If you ever add a new radio channel, you can either manually update this, or +// go use /client/verb/generate_tgui_radio_constants() in communications.dm. export const RADIO_CHANNELS = [ { - name: 'Syndicate', - freq: 1213, - color: '#a52a2a', + "name": "Mercenary", + "freq": 1213, + "color": "#6D3F40", }, { - name: 'Red Team', - freq: 1215, - color: '#ff4444', + "name": "Raider", + "freq": 1277, + "color": "#6D3F40", }, { - name: 'Blue Team', - freq: 1217, - color: '#3434fd', + "name": "Special Ops", + "freq": 1341, + "color": "#5C5C8A", }, { - name: 'CentCom', - freq: 1337, - color: '#2681a5', + "name": "AI Private", + "freq": 1343, + "color": "#FF00FF", }, { - name: 'Supply', - freq: 1347, - color: '#b88646', + "name": "Response Team", + "freq": 1345, + "color": "#5C5C8A", }, { - name: 'Service', - freq: 1349, - color: '#6ca729', + "name": "Supply", + "freq": 1347, + "color": "#5F4519", }, { - name: 'Science', - freq: 1351, - color: '#c68cfa', + "name": "Service", + "freq": 1349, + "color": "#6eaa2c", }, { - name: 'Command', - freq: 1353, - color: '#5177ff', + "name": "Science", + "freq": 1351, + "color": "#993399", }, { - name: 'Medical', - freq: 1355, - color: '#57b8f0', + "name": "Command", + "freq": 1353, + "color": "#193A7A", }, { - name: 'Engineering', - freq: 1357, - color: '#f37746', + "name": "Medical", + "freq": 1355, + "color": "#008160", }, { - name: 'Security', - freq: 1359, - color: '#dd3535', + "name": "Engineering", + "freq": 1357, + "color": "#A66300", }, { - name: 'AI Private', - freq: 1447, - color: '#d65d95', + "name": "Security", + "freq": 1359, + "color": "#A30000", }, { - name: 'Common', - freq: 1459, - color: '#1ecc43', + "name": "Explorer", + "freq": 1361, + "color": "#555555", + }, + { + "name": "Talon", + "freq": 1363, + "color": "#555555", + }, + { + "name": "Common", + "freq": 1459, + "color": "#008000", + }, + { + "name": "Entertainment", + "freq": 1461, + "color": "#339966", + }, + { + "name": "Security(I)", + "freq": 1475, + "color": "#008000", + }, + { + "name": "Medical(I)", + "freq": 1485, + "color": "#008000", }, ]; const GASES = [ { - 'id': 'o2', + 'id': 'oxygen', 'name': 'Oxygen', 'label': 'Oâ‚‚', 'color': 'blue', @@ -130,15 +162,15 @@ const GASES = [ 'color': 'red', }, { - 'id': 'co2', + 'id': 'carbon dioxide', 'name': 'Carbon Dioxide', 'label': 'COâ‚‚', 'color': 'grey', }, { - 'id': 'plasma', - 'name': 'Plasma', - 'label': 'Plasma', + 'id': 'phoron', + 'name': 'Phoron', + 'label': 'Phoron', 'color': 'pink', }, { @@ -201,6 +233,24 @@ const GASES = [ 'label': 'Hâ‚‚', 'color': 'white', }, + { + 'id': 'other', + 'name': 'Other', + 'label': 'Other', + 'color': 'white', + }, + { + 'id': 'pressure', + 'name': 'Pressure', + 'label': 'Pressure', + 'color': 'average', + }, + { + 'id': 'temperature', + 'name': 'Temperature', + 'label': 'Temperature', + 'color': 'yellow', + }, ]; export const getGasLabel = (gasId, fallbackValue) => { diff --git a/tgui/packages/tgui/interfaces/BodyScanner.js b/tgui/packages/tgui/interfaces/BodyScanner.js index c7207eaca5..b250082473 100644 --- a/tgui/packages/tgui/interfaces/BodyScanner.js +++ b/tgui/packages/tgui/interfaces/BodyScanner.js @@ -4,10 +4,6 @@ import { useBackend } from "../backend"; import { AnimatedNumber, Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Table, Tooltip } from "../components"; import { Window } from "../layouts"; -import { createLogger } from '../logging'; - -const debugBodyScannerLogger = createLogger('debugBodyScanner'); - const stats = [ ['good', 'Alive'], ['average', 'Unconscious'], @@ -219,7 +215,11 @@ const BodyScannerMainReagents = props => { {reagent.name} - {reagent.amount} Units + {reagent.amount} Units { + reagent.overdose + ? OVERDOSING + : null + } ))} @@ -241,7 +241,11 @@ const BodyScannerMainReagents = props => { {reagent.name} - {reagent.amount} Units + {reagent.amount} Units { + reagent.overdose + ? OVERDOSING + : null + } ))} diff --git a/tgui/packages/tgui/interfaces/ChemMaster.js b/tgui/packages/tgui/interfaces/ChemMaster.js index 2ef1b6f963..6250b1852f 100644 --- a/tgui/packages/tgui/interfaces/ChemMaster.js +++ b/tgui/packages/tgui/interfaces/ChemMaster.js @@ -250,7 +250,6 @@ const ChemMasterProduction = (props, context) => { return (
{ color="label">
@@ -271,7 +271,7 @@ const ChemMasterProduction = (props, context) => { } return ( -
+
{!props.isCondiment ? ( ) : ( @@ -390,11 +390,18 @@ const ChemMasterCustomization = (props, context) => { disabled={!data.loaded_pill_bottle} icon="eject" content={data.loaded_pill_bottle - ? data.loaded_pill_bottle_name + ? ( + data.loaded_pill_bottle_name + + " (" + + data.loaded_pill_bottle_contents_len + + "/" + + data.loaded_pill_bottle_storage_slots + + ")" + ) : "None loaded"} mb="0.5rem" onClick={() => act('ejectp')} - />
+ />
); }; diff --git a/tgui/packages/tgui/interfaces/CrewMonitor.js b/tgui/packages/tgui/interfaces/CrewMonitor.js index e5d8dff2ba..1776aa065e 100644 --- a/tgui/packages/tgui/interfaces/CrewMonitor.js +++ b/tgui/packages/tgui/interfaces/CrewMonitor.js @@ -103,6 +103,9 @@ export const CrewMonitorContent = (props, context) => {
); } else if (tabIndex === 1) { + // Please note, if you ever change the zoom values, + // you MUST update styles/components/Tooltip.scss + // and change the @for scss to match. body = ( Zoom Level: @@ -113,7 +116,7 @@ export const CrewMonitorContent = (props, context) => { stepPixelSize="5" value={mapZoom} minValue={1} - maxValue={8} + maxValue={8} onChange={(e, value) => setZoom(value)} /> Z-Level: {data.map_levels diff --git a/tgui/packages/tgui/interfaces/MedicalRecords.js b/tgui/packages/tgui/interfaces/MedicalRecords.js index 0ece98c75e..c5da8480b5 100644 --- a/tgui/packages/tgui/interfaces/MedicalRecords.js +++ b/tgui/packages/tgui/interfaces/MedicalRecords.js @@ -6,6 +6,7 @@ import { Window } from "../layouts"; import { LoginInfo } from './common/LoginInfo'; import { LoginScreen } from './common/LoginScreen'; import { TemporaryNotice } from './common/TemporaryNotice'; +import { decodeHtmlEntities } from 'common/string'; const severities = { "Minor": "good", @@ -23,30 +24,45 @@ const doEdit = (context, field) => { }; const virusModalBodyOverride = (modal, context) => { + const { act } = useBackend(context); const virus = modal.args; return (
+ title={virus.name || "Virus"} + buttons={ +
@@ -76,7 +84,13 @@ const viewBodyRecordModalBodyOverride = (modal, context) => { level={2} m="-1rem" pb="1rem" - title={"Body Record (" + realname + ")"}> + title={"Body Record (" + realname + ")"} + buttons={ + + {thing.name} + + ))} + + ); +}; + +const VoreUserPreferences = (props, context) => { + const { act, data } = useBackend(context); + + const { + digestable, + devourable, + feeding, + absorbable, + digest_leave_remains, + allowmobvore, + permit_healbelly, + can_be_drop_prey, + can_be_drop_pred, + noisy, + } = data.prefs; + + return ( +
+ + +
+ + ); +}; \ No newline at end of file diff --git a/tgui/packages/tgui/interfaces/common/FullscreenNotice.js b/tgui/packages/tgui/interfaces/common/FullscreenNotice.js new file mode 100644 index 0000000000..124318d0fb --- /dev/null +++ b/tgui/packages/tgui/interfaces/common/FullscreenNotice.js @@ -0,0 +1,20 @@ +import { Flex, Section } from '../../components'; + +/** + * Just a generic wrapper for fullscreen notices. + */ +export const FullscreenNotice = (props, context) => { + const { + children, + title = 'Welcome', + } = props; + return ( +
+ + + {children} + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/common/LoginScreen.js b/tgui/packages/tgui/interfaces/common/LoginScreen.js index ada5c2d89d..441705e6a0 100644 --- a/tgui/packages/tgui/interfaces/common/LoginScreen.js +++ b/tgui/packages/tgui/interfaces/common/LoginScreen.js @@ -1,5 +1,6 @@ import { useBackend } from '../../backend'; import { Box, Button, Flex, Icon, Section } from '../../components'; +import { FullscreenNotice } from './FullscreenNotice'; /** * Displays a login screen that users can interact with @@ -30,55 +31,51 @@ export const LoginScreen = (_properties, context) => { isRobot, } = data; return ( -
- - - - - Guest - - - ID: -
+ + + + Guest + + + ID: +