diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index a1bb4122a8..42199174e7 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -290,7 +290,7 @@ #define COMSIG_LIVING_ACTIVE_BLOCK_START "active_block_start" //from base of mob/living/keybind_start_active_blocking(): (obj/item/blocking_item, list/backup_items) #define COMPONENT_PREVENT_BLOCK_START 1 -#define COMSIG_LIVING_ACTIVE_PARRY_START "active_parry_start" //from base of mob/living/initiate_parry_sequence(): (parrying_method, datum/parrying_item_mob_or_art, list/backup_items) +#define COMSIG_LIVING_ACTIVE_PARRY_START "active_parry_start" //from base of mob/living/initiate_parry_sequence(): (parrying_method, datum/parrying_item_mob_or_art, list/backup_items, list/override) #define COMPONENT_PREVENT_PARRY_START 1 //ALL OF THESE DO NOT TAKE INTO ACCOUNT WHETHER AMOUNT IS 0 OR LOWER AND ARE SENT REGARDLESS! diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index b7750556d5..3d041edfd2 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -197,6 +197,7 @@ #define TRAIT_EMPATH "empath" #define TRAIT_FRIENDLY "friendly" #define TRAIT_SNOB "snob" +#define TRAIT_MULTILINGUAL "multilingual" #define TRAIT_CULT_EYES "cult_eyes" #define TRAIT_AUTO_CATCH_ITEM "auto_catch_item" #define TRAIT_CLOWN_MENTALITY "clown_mentality" // The future is now, clownman. diff --git a/code/__HELPERS/priority_announce.dm b/code/__HELPERS/priority_announce.dm index d06912b701..374e89f715 100644 --- a/code/__HELPERS/priority_announce.dm +++ b/code/__HELPERS/priority_announce.dm @@ -10,7 +10,7 @@ announcement += "

[html_encode(title)]

" else if(type == "Captain") announcement += "

Captain Announces

" - GLOB.news_network.SubmitArticle(text, "Captain's Announcement", "Station Announcements", null) + GLOB.news_network.SubmitArticle(html_encode(text), "Captain's Announcement", "Station Announcements", null) else if(!sender_override) diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 9f89920b46..71bbfe64fe 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -263,7 +263,7 @@ Turf and target are separate in case you want to teleport some distance from a t return . //Returns a list of all items of interest with their name -/proc/getpois(mobs_only=0,skip_mindless=0) +/proc/getpois(mobs_only = FALSE, skip_mindless = FALSE, specify_dead_role = TRUE) var/list/mobs = sortmobs() var/list/namecounts = list() var/list/pois = list() @@ -277,7 +277,7 @@ Turf and target are separate in case you want to teleport some distance from a t if(M.real_name && M.real_name != M.name) name += " \[[M.real_name]\]" - if(M.stat == DEAD) + if(M.stat == DEAD && specify_dead_role) if(isobserver(M)) name += " \[ghost\]" else diff --git a/code/datums/components/orbiter.dm b/code/datums/components/orbiter.dm index 49b19f767c..26f52f6ba5 100644 --- a/code/datums/components/orbiter.dm +++ b/code/datums/components/orbiter.dm @@ -146,9 +146,11 @@ if(!istype(A) || !get_turf(A) || A == src) return + orbit_target = A return A.AddComponent(/datum/component/orbiter, src, radius, clockwise, rotation_speed, rotation_segments, pre_rotation) /atom/movable/proc/stop_orbit(datum/component/orbiter/orbits) + orbit_target = null return // We're just a simple hook /atom/proc/transfer_observers_to(atom/target) diff --git a/code/datums/components/riding.dm b/code/datums/components/riding.dm index e9aa5afe92..e11eca2975 100644 --- a/code/datums/components/riding.dm +++ b/code/datums/components/riding.dm @@ -37,7 +37,7 @@ qdel(src) /datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/M, force) - handle_vehicle_offsets() + handle_vehicle_offsets(M.buckled?.dir) /datum/component/riding/proc/handle_vehicle_layer(dir) var/atom/movable/AM = parent diff --git a/code/datums/traits/_quirk.dm b/code/datums/traits/_quirk.dm index c6466fdd96..22a851da1d 100644 --- a/code/datums/traits/_quirk.dm +++ b/code/datums/traits/_quirk.dm @@ -11,6 +11,8 @@ var/antag_removal_text // Text will be given to the quirk holder if they get an antag that has it blacklisted. var/mood_quirk = FALSE //if true, this quirk affects mood and is unavailable if moodlets are disabled var/mob_trait //if applicable, apply and remove this mob trait + /// should we immediately call on_spawn or add a timer to trigger + var/on_spawn_immediate = TRUE var/mob/living/quirk_holder /datum/quirk/New(mob/living/quirk_mob, spawn_effects) @@ -26,7 +28,10 @@ START_PROCESSING(SSquirks, src) add() if(spawn_effects) - on_spawn() + if(on_spawn_immediate) + on_spawn() + else + addtimer(CALLBACK(src, .proc/on_spawn), 0) addtimer(CALLBACK(src, .proc/post_add), 30) /datum/quirk/Destroy() diff --git a/code/datums/traits/good.dm b/code/datums/traits/good.dm index 23fd75f982..4a4108f005 100644 --- a/code/datums/traits/good.dm +++ b/code/datums/traits/good.dm @@ -219,3 +219,19 @@ /datum/quirk/night_vision/on_spawn() var/mob/living/carbon/human/H = quirk_holder H.update_sight() + +/datum/quirk/multilingual + name = "Multi-Lingual" + desc = "You spent a portion of your life learning to understand an additional language. You may or may not be able to speak it based on your anatomy." + value = 1 + mob_trait = TRAIT_MULTILINGUAL + gain_text = "You've learned an extra language!" + lose_text = "You've forgotten your extra language." + +/datum/quirk/multilingual/add() + var/mob/living/carbon/human/H = quirk_holder + H.grant_language(H.client.prefs.language, TRUE, TRUE, LANGUAGE_MIND) + +/datum/quirk/multilingual/remove() + var/mob/living/carbon/human/H = quirk_holder + H.remove_language(H.client.prefs.language, TRUE, TRUE, LANGUAGE_MIND) diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm index 3cbf4b3cd2..cce138e82c 100644 --- a/code/datums/traits/negative.dm +++ b/code/datums/traits/negative.dm @@ -184,6 +184,7 @@ GLOBAL_LIST_EMPTY(family_heirlooms) gain_text = null // Handled by trauma. lose_text = null medical_record_text = "Patient has an untreatable impairment in motor function in the lower extremities." + on_spawn_immediate = FALSE /datum/quirk/paraplegic/add() var/datum/brain_trauma/severe/paralysis/paraplegic/T = new() diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 189640a1c6..1b64cca787 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -99,6 +99,9 @@ ///Mobs that are currently do_after'ing this atom, to be cleared from on Destroy() var/list/targeted_by + ///Reference to atom being orbited + var/atom/orbit_target + /** * Called when an atom is created in byond (built in engine proc) * diff --git a/code/game/machinery/cell_charger.dm b/code/game/machinery/cell_charger.dm index effd70e9ab..25445f0d1c 100644 --- a/code/game/machinery/cell_charger.dm +++ b/code/game/machinery/cell_charger.dm @@ -10,7 +10,7 @@ circuit = /obj/item/circuitboard/machine/cell_charger pass_flags = PASSTABLE var/obj/item/stock_parts/cell/charging = null - var/charge_rate = 500 + var/recharge_coeff = 1 /obj/machinery/cell_charger/update_overlays() . += ..() @@ -28,9 +28,10 @@ . = ..() . += "There's [charging ? "a" : "no"] cell in the charger." if(charging) - . += "Current charge: [round(charging.percent(), 1)]%." + var/obj/item/stock_parts/cell/C = charging.get_cell() + . += "Current charge: [C.percent()]%." if(in_range(user, src) || isobserver(user)) - . += "The status display reads: Charge rate at [charge_rate]J per cycle." + . += "The status display reads: Charge rate at [recharge_coeff*10]J per cycle." /obj/machinery/cell_charger/attackby(obj/item/W, mob/user, params) if(istype(W, /obj/item/stock_parts/cell) && !panel_open) @@ -122,17 +123,18 @@ charging.emp_act(severity) /obj/machinery/cell_charger/RefreshParts() - charge_rate = 500 for(var/obj/item/stock_parts/capacitor/C in component_parts) - charge_rate *= C.rating + recharge_coeff = C.rating /obj/machinery/cell_charger/process() if(!charging || !anchored || (stat & (BROKEN|NOPOWER))) return - if(charging.percent() >= 100) - return - use_power(charge_rate) - charging.give(charge_rate) //this is 2558, efficient batteries exist + if(charging) + var/obj/item/stock_parts/cell/C = charging.get_cell() + if(C) + if(C.charge < C.maxcharge) + C.give(C.chargerate * recharge_coeff) + use_power(250 * recharge_coeff) update_icon() diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index 939d77a1f0..015b8774ad 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -69,7 +69,8 @@ GLOBAL_LIST_INIT(channel_tokens, list( /obj/item/radio/headset/talk_into(mob/living/M, message, channel, list/spans,datum/language/language) if (!listening) return ITALICS | REDUCE_RANGE - return ..() + if (language != /datum/language/signlanguage) + return ..() /obj/item/radio/headset/can_receive(freq, level, AIuser) if(ishuman(src.loc)) diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index 4623a405de..3435a0d0f0 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -208,6 +208,8 @@ return if(!M.IsVocal()) return + if(language == /datum/language/signlanguage) + return if(use_command) spans |= commandspan diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm index 1277bb3d4e..517350c916 100644 --- a/code/game/objects/items/stacks/rods.dm +++ b/code/game/objects/items/stacks/rods.dm @@ -20,7 +20,7 @@ GLOBAL_LIST_INIT(rod_recipes, list ( \ custom_materials = list(/datum/material/iron=1000) max_amount = 50 attack_verb = list("hit", "bludgeoned", "whacked") - hitsound = 'sound/weapons/grenadelaunch.ogg' + hitsound = 'sound/items/trayhit1.ogg' embedding = list() novariants = TRUE diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 7cc8be800c..572c71b1e0 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -99,6 +99,8 @@ GLOBAL_LIST_EMPTY(preferences_datums) var/be_random_body = 0 //whether we'll have a random body every round var/gender = MALE //gender of character (well duh) var/age = 30 //age of character + var/language = "Random" //bonus language + var/choselanguage = "Random" //language appearance var/underwear = "Nude" //underwear type var/undie_color = "FFFFFF" var/undershirt = "Nude" //undershirt type @@ -308,6 +310,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) dat += "Gender: [gender == MALE ? "Male" : (gender == FEMALE ? "Female" : (gender == PLURAL ? "Non-binary" : "Object"))]
" dat += "Age: [age]
" + dat += "Language: [choselanguage]
" dat += "Special Names:
" var/old_group @@ -2315,6 +2318,28 @@ GLOBAL_LIST_EMPTY(preferences_datums) features["body_model"] = chosengender gender = chosengender + if("language") + choselanguage = input(user, "Select a language.", "Language", language) as null|anything in list("Beachtongue","Draconic","Dwarven", + "Chimpanzee","Space Sign Language","Random") + if(!choselanguage) + return + switch(choselanguage) + if("Rachidian") + language = /datum/language/arachnid + if("Beachtongue") + language = /datum/language/beachbum + if("Draconic") + language = /datum/language/draconic + if("Dwarven") + language = /datum/language/dwarf + if("Chimpanzee") + language = /datum/language/monkey + if("Space Sign Language") + language = /datum/language/signlanguage + if("Random") + language = pick(list("Rachidian", "Beachtongue","Draconic","Dwarven", + "Chimpanzee","Space Sign Language")) + if("body_size") var/new_body_size = input(user, "Choose your desired sprite size: (90-125%)\nWarning: This may make your character look distorted. Additionally, any size under 100% takes a 10% maximum health penalty", "Character Preference", features["body_size"]*100) as num|null if(new_body_size) diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index 0db9fbb66c..088f7753cf 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -604,6 +604,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car S["body_model"] >> features["body_model"] S["body_size"] >> features["body_size"] S["age"] >> age + S["language"] >> language + S["choselanguage"] >> choselanguage S["hair_color"] >> hair_color S["facial_hair_color"] >> facial_hair_color S["eye_type"] >> eye_type @@ -947,6 +949,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car WRITE_FILE(S["body_model"] , features["body_model"]) WRITE_FILE(S["body_size"] , features["body_size"]) WRITE_FILE(S["age"] , age) + WRITE_FILE(S["language"] , language) + WRITE_FILE(S["choselanguage"] , choselanguage) WRITE_FILE(S["hair_color"] , hair_color) WRITE_FILE(S["facial_hair_color"] , facial_hair_color) WRITE_FILE(S["eye_type"] , eye_type) diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm index c069649848..a558abbfe8 100644 --- a/code/modules/clothing/gloves/miscellaneous.dm +++ b/code/modules/clothing/gloves/miscellaneous.dm @@ -161,6 +161,68 @@ return NO_AUTO_CLICKDELAY_HANDLING | ATTACK_IGNORE_ACTION +/obj/item/clothing/gloves/fingerless/ablative + name = "ablative armwraps" + desc = "Armwraps made out of a highly durable, reflective metal. Has the side effect of absorbing shocks." + siemens_coefficient = 0 + icon_state = "ablative_armwraps" + item_state = "ablative_armwraps" + block_parry_data = /datum/block_parry_data/ablative_armwraps + var/wornonce = FALSE + +/obj/item/clothing/gloves/fingerless/ablative/proc/get_component_parry_data(datum/source, parrying_method, datum/parrying_item_mob_or_art, list/backup_items, list/override) + if(parrying_method && !(parrying_method == UNARMED_PARRY)) + return + override[src] = ITEM_PARRY + +/obj/item/clothing/gloves/fingerless/ablative/equipped(mob/user, slot) + . = ..() + if(current_equipped_slot == SLOT_GLOVES) + RegisterSignal(user, COMSIG_LIVING_ACTIVE_PARRY_START, .proc/get_component_parry_data) + wornonce = TRUE + +/obj/item/clothing/gloves/fingerless/ablative/dropped(mob/user) + . = ..() + if(wornonce) + UnregisterSignal(user, COMSIG_LIVING_ACTIVE_PARRY_START) + wornonce = FALSE + +/obj/item/clothing/gloves/fingerless/ablative/can_active_parry(mob/user) + var/mob/living/carbon/human/H = user + if(!istype(H)) + return FALSE + return src == H.gloves + +/obj/item/clothing/gloves/fingerless/ablative/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/block_return, parry_efficiency, parry_time) + . = ..() + if(parry_efficiency > 0) + owner.visible_message("[owner] deflects \the [object] with their armwraps!") + +/datum/block_parry_data/ablative_armwraps + parry_stamina_cost = 4 + parry_attack_types = ATTACK_TYPE_UNARMED | ATTACK_TYPE_PROJECTILE | ATTACK_TYPE_TACKLE | ATTACK_TYPE_THROWN | ATTACK_TYPE_MELEE + parry_flags = NONE + + parry_time_windup = 0 + parry_time_spindown = 0 + parry_time_active = 7.5 + + parry_time_perfect = 1 + parry_time_perfect_leeway = 7.5 + parry_imperfect_falloff_percent = 20 + parry_efficiency_perfect = 100 + parry_time_perfect_leeway_override = list( + TEXT_ATTACK_TYPE_MELEE = 1 + ) + + parry_efficiency_considered_successful = 0.01 + parry_efficiency_to_counterattack = INFINITY // no auto counter + parry_max_attacks = INFINITY + parry_failed_cooldown_duration = 2.25 SECONDS + parry_failed_stagger_duration = 2.25 SECONDS + parry_cooldown = 0 + parry_failed_clickcd_duration = 0 + /obj/item/clothing/gloves/botanic_leather name = "botanist's leather gloves" desc = "These leather gloves protect against thorns, barbs, prickles, spikes and other harmful objects of floral origin. They're also quite warm." diff --git a/code/modules/integrated_electronics/subtypes/weaponized.dm b/code/modules/integrated_electronics/subtypes/weaponized.dm index 96a732d08f..25fa7058fb 100644 --- a/code/modules/integrated_electronics/subtypes/weaponized.dm +++ b/code/modules/integrated_electronics/subtypes/weaponized.dm @@ -137,6 +137,10 @@ //Shooting Code: A.preparePixelProjectile(target, src) A.fire() + if(ismob(loc.loc)) + installed_gun.shoot_live_shot(loc.loc) + else + installed_gun.shoot_live_shot() //Shitcode, but we don't have much of a choice log_attack("[assembly] [REF(assembly)] has fired [installed_gun].") return A diff --git a/code/modules/language/signlanguage.dm b/code/modules/language/signlanguage.dm new file mode 100644 index 0000000000..97705d4a4c --- /dev/null +++ b/code/modules/language/signlanguage.dm @@ -0,0 +1,12 @@ +/datum/language/signlanguage + name = "Space Sign Language" + desc = "Those who cannot speak can learn this instead." + speech_verb = "signs" + whisper_verb = "gestures" + key = "9" + flags = TONGUELESS_SPEECH + + syllables = list(".") + + icon_state = "ssl" + default_priority = 90 diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm index 9c39601b4e..6af1c2118c 100644 --- a/code/modules/mining/lavaland/necropolis_chests.dm +++ b/code/modules/mining/lavaland/necropolis_chests.dm @@ -522,7 +522,7 @@ max_charges = 1 item_flags = NEEDS_PERMIT | NOBLUDGEON w_class = WEIGHT_CLASS_BULKY - force = 18 + force = 15 /obj/item/ammo_casing/magic/hook name = "hook" @@ -536,11 +536,11 @@ icon_state = "hook" icon = 'icons/obj/lavaland/artefacts.dmi' pass_flags = PASSTABLE - damage = 25 - armour_penetration = 100 + damage = 15 + armour_penetration = 10 + knockdown = 5 damage_type = BRUTE hitsound = 'sound/effects/splat.ogg' - knockdown = 30 var/chain /obj/item/projectile/hook/fire(setAngle) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index dc53f9487f..783f55d12d 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -556,7 +556,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp var/list/dest = list() //List of possible destinations (mobs) var/target = null //Chosen target. - dest += getpois(mobs_only=1) //Fill list, prompt user with list + dest += getpois(mobs_only = TRUE) //Fill list, prompt user with list target = input("Please, select a player!", "Jump to Mob", null, null) as null|anything in dest if (!target)//Make sure we actually have a target @@ -893,7 +893,9 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp if (!eye_name) return - var/mob/mob_eye = creatures[eye_name] + do_observe(creatures[eye_name]) + +/mob/dead/observer/proc/do_observe(mob/mob_eye) //Istype so we filter out points of interest that are not mobs if(client && mob_eye && istype(mob_eye)) client.eye = mob_eye diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm index 3aa5f8e302..26494dcb34 100644 --- a/code/modules/mob/dead/observer/orbit.dm +++ b/code/modules/mob/dead/observer/orbit.dm @@ -1,5 +1,6 @@ /datum/orbit_menu var/mob/dead/observer/owner + var/auto_observe = FALSE /datum/orbit_menu/New(mob/dead/observer/new_owner) if(!istype(new_owner)) @@ -10,6 +11,7 @@ return GLOB.observer_state /datum/orbit_menu/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) if (!ui) ui = new(user, src, "Orbit") ui.open() @@ -18,15 +20,35 @@ if (..()) return - if (action == "orbit") - var/ref = params["ref"] - var/atom/movable/poi = (locate(ref) in GLOB.mob_list) || (locate(ref) in GLOB.poi_list) - if (poi != null) + switch(action) + if ("orbit") + var/ref = params["ref"] + var/atom/movable/poi = (locate(ref) in GLOB.mob_list) || (locate(ref) in GLOB.poi_list) + if (poi == null) + . = TRUE + return owner.ManualFollow(poi) + owner.reset_perspective(null) + if (auto_observe) + owner.do_observe(poi) + . = TRUE + if ("refresh") + update_static_data(owner, ui) + . = TRUE + if ("toggle_observe") + auto_observe = !auto_observe + if (auto_observe && owner.orbit_target) + owner.do_observe(owner.orbit_target) + else + owner.reset_perspective(null) /datum/orbit_menu/ui_data(mob/user) var/list/data = list() + data["auto_observe"] = auto_observe + return data +/datum/orbit_menu/ui_static_data(mob/user) + var/list/data = list() var/list/alive = list() var/list/antagonists = list() var/list/dead = list() @@ -34,7 +56,7 @@ var/list/misc = list() var/list/npcs = list() - var/list/pois = getpois(skip_mindless = 1) + var/list/pois = getpois(skip_mindless = TRUE, specify_dead_role = FALSE) for (var/name in pois) var/list/serialized = list() serialized["name"] = name @@ -80,7 +102,7 @@ data["npcs"] = npcs return data - + /datum/orbit_menu/ui_assets() . = ..() || list() . += get_asset_datum(/datum/asset/simple/orbit) diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm index 7c80d1d426..f5970d9da2 100644 --- a/code/modules/mob/living/carbon/alien/alien.dm +++ b/code/modules/mob/living/carbon/alien/alien.dm @@ -11,6 +11,10 @@ bubble_icon = "alien" type_of_meat = /obj/item/reagent_containers/food/snacks/meat/slab/xeno + /// Whether they can ventcrawl; this is set individually for 'humanoid' and 'royal' types + /// 'royal' types (Praetorian, Queen) cannot ventcrawl + var/can_ventcrawl + /// How much brute damage without armor piercing they do against mobs in melee var/meleeSlashHumanPower = 20 /// How much power they have for DefaultCombatKnockdown when attacking humans @@ -38,7 +42,8 @@ create_internal_organs() - AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + if(can_ventcrawl) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) . = ..() diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm index d0addbab21..0d2a1c0c59 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm @@ -20,6 +20,8 @@ bodyparts = list(/obj/item/bodypart/chest/alien, /obj/item/bodypart/head/alien, /obj/item/bodypart/l_arm/alien, /obj/item/bodypart/r_arm/alien, /obj/item/bodypart/r_leg/alien, /obj/item/bodypart/l_leg/alien) + can_ventcrawl = TRUE + //This is fine right now, if we're adding organ specific damage this needs to be updated /mob/living/carbon/alien/humanoid/Initialize() diff --git a/code/modules/mob/living/carbon/alien/humanoid/queen.dm b/code/modules/mob/living/carbon/alien/humanoid/queen.dm index 42b62e37a0..bcc83f14f9 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/queen.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/queen.dm @@ -9,6 +9,7 @@ layer = LARGE_MOB_LAYER //above most mobs, but below speechbubbles pressure_resistance = 200 //Because big, stompy xenos should not be blown around like paper. butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/xeno = 20, /obj/item/stack/sheet/animalhide/xeno = 3) + can_ventcrawl = FALSE meleeKnockdownPower = 125 meleeSlashHumanPower = 30 diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index df7ed66169..f653c96cbc 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -938,43 +938,43 @@ admin_ticket_log(src, msg) /mob/living/carbon/human/MouseDrop_T(mob/living/target, mob/living/user) - if(pulling == target && grab_state >= GRAB_AGGRESSIVE && stat == CONSCIOUS) + var/GS_needed = istype(target, /mob/living/silicon/pai)? GRAB_PASSIVE : GRAB_AGGRESSIVE + if(pulling == target && grab_state >= GS_needed && stat == CONSCIOUS) //If they dragged themselves and we're currently aggressively grabbing them try to piggyback if(user == target && can_piggyback(target)) piggyback(target) return //If you dragged them to you and you're aggressively grabbing try to fireman carry them - else if(user != target) + else if(user == src) if(user.a_intent == INTENT_GRAB) fireman_carry(target) return . = ..() //src is the user that will be carrying, target is the mob to be carried -/mob/living/carbon/human/proc/can_piggyback(mob/living/carbon/target) - return (istype(target) && target.stat == CONSCIOUS) +/mob/living/carbon/human/proc/can_piggyback(mob/living/target) + return (iscarbon(target) || ispAI(target)) && target.stat == CONSCIOUS /mob/living/carbon/human/proc/can_be_firemanned(mob/living/carbon/target) - return (ishuman(target) && !CHECK_MOBILITY(target, MOBILITY_STAND)) + return (ishuman(target) && !CHECK_MOBILITY(target, MOBILITY_STAND)) || ispAI(target) /mob/living/carbon/human/proc/fireman_carry(mob/living/carbon/target) var/carrydelay = 50 //if you have latex you are faster at grabbing var/skills_space = "" //cobby told me to do this if(HAS_TRAIT(src, TRAIT_QUICKER_CARRY)) carrydelay = 30 - skills_space = "expertly" + skills_space = "expertly " else if(HAS_TRAIT(src, TRAIT_QUICK_CARRY)) carrydelay = 40 - skills_space = "quickly" + skills_space = "quickly " if(can_be_firemanned(target) && !incapacitated(FALSE, TRUE)) - visible_message("[src] starts [skills_space] lifting [target] onto their back..", + visible_message("[src] starts [skills_space]lifting [target] onto their back..", //Joe Medic starts quickly/expertly lifting Grey Tider onto their back.. - "[carrydelay < 35 ? "Using your gloves' nanochips, you" : "You"] [skills_space] start to lift [target] onto your back[carrydelay == 40 ? ", while assisted by the nanochips in your gloves.." : "..."]") + "[carrydelay < 35 ? "Using your gloves' nanochips, you" : "You"] [skills_space]start to lift [target] onto your back[carrydelay == 40 ? ", while assisted by the nanochips in your gloves.." : "..."]") //(Using your gloves' nanochips, you/You) ( /quickly/expertly) start to lift Grey Tider onto your back(, while assisted by the nanochips in your gloves../...) if(do_after(src, carrydelay, TRUE, target)) //Second check to make sure they're still valid to be carried if(can_be_firemanned(target) && !incapacitated(FALSE, TRUE)) - target.set_resting(FALSE, TRUE) buckle_mob(target, TRUE, TRUE, 90, 1, 0, TRUE) return visible_message("[src] fails to fireman carry [target]!") @@ -992,13 +992,13 @@ if(target.incapacitated(FALSE, TRUE) || incapacitated(FALSE, TRUE)) target.visible_message("[target] can't hang onto [src]!") return - buckle_mob(target, TRUE, TRUE, FALSE, 1, 2, FALSE) + buckle_mob(target, TRUE, TRUE, 0, 1, 2, FALSE) else visible_message("[target] fails to climb onto [src]!") else to_chat(target, "You can't piggyback ride [src] right now!") -/mob/living/carbon/human/buckle_mob(mob/living/target, force = FALSE, check_loc = TRUE, lying_buckle = FALSE, hands_needed = 0, target_hands_needed = 0, fireman = FALSE) +/mob/living/carbon/human/buckle_mob(mob/living/target, force = FALSE, check_loc = TRUE, lying_buckle = 0, hands_needed = 0, target_hands_needed = 0, fireman = FALSE) if(!force)//humans are only meant to be ridden through piggybacking and special cases return if(!is_type_in_typecache(target, can_ride_typecache)) @@ -1010,6 +1010,9 @@ riding_datum.ride_check_rider_restrained = TRUE if(buckled_mobs && ((target in buckled_mobs) || (buckled_mobs.len >= max_buckled_mobs)) || buckled) return + if(istype(target, /mob/living/silicon/pai)) + hands_needed = 1 + target_hands_needed = 0 var/equipped_hands_self var/equipped_hands_target if(hands_needed) diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index a82151cc1d..eedc59d9d5 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -71,7 +71,7 @@ var/list/datum/bioware = list() var/creamed = FALSE //to use with creampie overlays - var/static/list/can_ride_typecache = typecacheof(list(/mob/living/carbon/human, /mob/living/simple_animal/slime, /mob/living/simple_animal/parrot)) + var/static/list/can_ride_typecache = typecacheof(list(/mob/living/carbon/human, /mob/living/simple_animal/slime, /mob/living/simple_animal/parrot, /mob/living/silicon/pai)) var/lastpuke = 0 var/account_id var/last_fire_update diff --git a/code/modules/mob/living/carbon/human/species_types/dwarves.dm b/code/modules/mob/living/carbon/human/species_types/dwarves.dm index 7c35ade4f2..e0a9bcaa36 100644 --- a/code/modules/mob/living/carbon/human/species_types/dwarves.dm +++ b/code/modules/mob/living/carbon/human/species_types/dwarves.dm @@ -30,10 +30,7 @@ GLOBAL_LIST_INIT(dwarf_last, world.file2list("strings/names/dwarf_last.txt")) // /datum/species/dwarf/on_species_gain(mob/living/carbon/C, datum/species/old_species) . = ..() - var/dwarf_hair = pick("Beard (Dwarf)", "Beard (Very Long)", "Beard (Long)") //beard roullette var/mob/living/carbon/human/H = C - H.facial_hair_style = dwarf_hair - H.update_hair() H.AddElement(/datum/element/dwarfism, COMSIG_SPECIES_LOSS, src) RegisterSignal(C, COMSIG_MOB_SAY, .proc/handle_speech) //We register handle_speech is being used. diff --git a/code/modules/mob/living/carbon/say.dm b/code/modules/mob/living/carbon/say.dm index 27d0e9cbaf..d75843a3e5 100644 --- a/code/modules/mob/living/carbon/say.dm +++ b/code/modules/mob/living/carbon/say.dm @@ -10,6 +10,8 @@ /mob/living/carbon/can_speak_vocal(message) if(silent) return 0 + if(get_message_language(message) == /datum/language/signlanguage && (handcuffed || (!src.get_bodypart(BODY_ZONE_L_ARM) && !src.get_bodypart(BODY_ZONE_R_ARM)) || get_num_held_items() == held_items.len)) + return 0 return ..() /mob/living/carbon/could_speak_language(datum/language/language) diff --git a/code/modules/mob/living/living_active_parry.dm b/code/modules/mob/living/living_active_parry.dm index 8d1ee38de6..c0423286c1 100644 --- a/code/modules/mob/living/living_active_parry.dm +++ b/code/modules/mob/living/living_active_parry.dm @@ -2,7 +2,7 @@ /** * Determines if we can actively parry. */ -/obj/item/proc/can_active_parry() +/obj/item/proc/can_active_parry(mob/user) return block_parry_data && (item_flags & ITEM_CAN_PARRY) /** @@ -29,7 +29,7 @@ var/datum/block_parry_data/data var/datum/tool var/method - if(using_item?.can_active_parry()) + if(using_item?.can_active_parry(src)) data = using_item.block_parry_data method = ITEM_PARRY tool = using_item @@ -50,9 +50,20 @@ using_item = backup method = ITEM_PARRY var/list/other_items = list() - if(SEND_SIGNAL(src, COMSIG_LIVING_ACTIVE_PARRY_START, method, tool, other_items) & COMPONENT_PREVENT_PARRY_START) + var/list/override = list() + if(SEND_SIGNAL(src, COMSIG_LIVING_ACTIVE_PARRY_START, method, tool, other_items, override) & COMPONENT_PREVENT_PARRY_START) to_chat(src, "Something is preventing you from parrying!") return + if(length(override)) + var/datum/thing = override[1] + var/_method = override[thing] + if(_method == ITEM_PARRY) + using_item = thing + method = ITEM_PARRY + data = using_item.block_parry_data + else if(_method == UNARMED_PARRY) + method = UNARMED_PARRY + data = thing if(!using_item && !method && length(other_items)) using_item = other_items[1] method = ITEM_PARRY @@ -94,7 +105,7 @@ */ /mob/living/proc/find_backup_parry_item() for(var/obj/item/I in held_items - get_active_held_item()) - if(I.can_active_parry()) + if(I.can_active_parry(src)) return I /** @@ -231,7 +242,7 @@ var/efficiency = data.get_parry_efficiency(attack_type, get_parry_time()) switch(parrying) if(ITEM_PARRY) - if(!active_parry_item.can_active_parry()) + if(!active_parry_item.can_active_parry(src)) return BLOCK_NONE . = active_parry_item.on_active_parry(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, get_parry_time()) if(UNARMED_PARRY) @@ -243,6 +254,18 @@ if(efficiency <= 0) // Do not allow automatically handled/standardized parries that increase damage for now. return . |= BLOCK_SHOULD_PARTIAL_MITIGATE + if(efficiency >= data.parry_efficiency_perfect) + . |= data.perfect_parry_block_return_flags + if(data.perfect_parry_block_return_list) + return_list |= data.perfect_parry_block_return_list + else if(efficiency >= data.parry_efficiency_considered_successful) + . |= data.imperfect_parry_block_return_flags + if(data.imperfect_parry_block_return_list) + return_list |= data.imperfect_parry_block_return_list + else + . |= data.failed_parry_block_return_flags + if(data.failed_parry_block_return_list) + return_list |= data.failed_parry_block_return_list if(isnull(return_list[BLOCK_RETURN_MITIGATION_PERCENT])) // if one of the on_active_parry procs overrode. We don't have to worry about interference since parries are the first thing checked in the [do_run_block()] sequence. return_list[BLOCK_RETURN_MITIGATION_PERCENT] = clamp(efficiency, 0, 100) // do not allow > 100% or < 0% for now. if((return_list[BLOCK_RETURN_MITIGATION_PERCENT] >= 100) || (damage <= 0)) diff --git a/code/modules/mob/living/living_blocking_parrying.dm b/code/modules/mob/living/living_blocking_parrying.dm index e290956873..9e974177e5 100644 --- a/code/modules/mob/living/living_blocking_parrying.dm +++ b/code/modules/mob/living/living_blocking_parrying.dm @@ -157,6 +157,16 @@ GLOBAL_LIST_EMPTY(block_parry_data) /// Parry cooldown post-parry if failed. This is ADDED to parry_cooldown!!! var/parry_failed_cooldown_duration = 0 SECONDS + // Advanced + /// Flags added to return value + var/perfect_parry_block_return_flags = NONE + var/imperfect_parry_block_return_flags = NONE + var/failed_parry_block_return_flags = NONE + /// List appended to block return + var/perfect_parry_block_return_list + var/imperfect_parry_block_return_list + var/failed_parry_block_return_list + /** * Quirky proc to get average of flags in list that are in attack_type because why is attack_type a flag. */ diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 4c03e74d70..606daed0c5 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -329,7 +329,7 @@ GLOBAL_LIST_INIT(department_radio_keys, list( return 1 /mob/living/proc/can_speak_vocal(message) //Check AFTER handling of xeno and ling channels - if(HAS_TRAIT(src, TRAIT_MUTE)) + if(HAS_TRAIT(src, TRAIT_MUTE) && get_message_language(message) != /datum/language/signlanguage) return 0 if(is_muzzled()) diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index 2ab74664a3..5740f98e90 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -143,6 +143,10 @@ custom_holoform.Grant(src) emitter_next_use = world.time + 10 SECONDS +/mob/living/silicon/pai/deployed/Initialize() + . = ..() + fold_out(TRUE) + /mob/living/silicon/pai/ComponentInitialize() . = ..() if(possible_chassis[chassis]) diff --git a/code/modules/modular_computers/file_system/programs/card.dm b/code/modules/modular_computers/file_system/programs/card.dm index 6d45914add..3455547d20 100644 --- a/code/modules/modular_computers/file_system/programs/card.dm +++ b/code/modules/modular_computers/file_system/programs/card.dm @@ -175,7 +175,7 @@ if("PRG_edit") if(!computer || !authenticated || !target_id_card) return - var/new_name = params["name"] + var/new_name = reject_bad_name(params["name"]) if(!new_name) return target_id_card.registered_name = new_name @@ -190,7 +190,7 @@ return if(target == "Custom") - var/custom_name = params["custom_name"] + var/custom_name = reject_bad_name(params["custom_name"]) if(custom_name) target_id_card.assignment = custom_name target_id_card.update_label() diff --git a/code/modules/movespeed/modifiers/reagents.dm b/code/modules/movespeed/modifiers/reagents.dm index b6c2458670..1a03e8a602 100644 --- a/code/modules/movespeed/modifiers/reagents.dm +++ b/code/modules/movespeed/modifiers/reagents.dm @@ -12,3 +12,7 @@ /datum/movespeed_modifier/reagent/nitryl multiplicative_slowdown = -1 + +/datum/movespeed_modifier/reagent/meth + multiplicative_slowdown = -0.5 + absolute_max_tiles_per_second = 11 diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm index 00e59d09f8..e8d53ddeb8 100644 --- a/code/modules/projectiles/guns/ballistic/automatic.dm +++ b/code/modules/projectiles/guns/ballistic/automatic.dm @@ -12,6 +12,7 @@ name = "\improper Nanotrasen Saber SMG" desc = "A prototype three-round burst 9mm submachine gun, designated 'SABR'. Has a threaded barrel for suppressors." icon_state = "saber" + fire_sound = "sound/weapons/gunshot_smg_alt.ogg" mag_type = /obj/item/ammo_box/magazine/smgm9mm pin = null @@ -125,6 +126,7 @@ desc = "An outdated personal defence weapon. Uses 4.6x30mm rounds and is designated the WT-550 Semi-Automatic SMG." icon_state = "wt550" item_state = "arg" + fire_sound = "sound/weapons/gunshot_smg_alt.ogg" mag_type = /obj/item/ammo_box/magazine/wt550m9 can_suppress = FALSE burst_size = 2 @@ -138,6 +140,10 @@ . = ..() spread = 15 +/obj/item/gun/ballistic/automatic/wt550/afterattack() + . = ..() + empty_alarm() + /obj/item/gun/ballistic/automatic/wt550/disable_burst() . = ..() spread = 0 @@ -158,7 +164,7 @@ icon_state = "m90" item_state = "m90" mag_type = /obj/item/ammo_box/magazine/m556 - fire_sound = 'sound/weapons/gunshot_smg.ogg' + fire_sound = 'sound/weapons/rifleshot.ogg' can_suppress = FALSE automatic_burst_overlay = FALSE var/obj/item/gun/ballistic/revolver/grenadelauncher/underbarrel @@ -243,7 +249,7 @@ item_state = "arg" slot_flags = 0 mag_type = /obj/item/ammo_box/magazine/m556 - fire_sound = 'sound/weapons/gunshot_smg.ogg' + fire_sound = 'sound/weapons/rifleshot.ogg' can_suppress = FALSE burst_size = 3 burst_shot_delay = 1 @@ -258,7 +264,7 @@ w_class = WEIGHT_CLASS_NORMAL weapon_weight = WEAPON_MEDIUM mag_type = /obj/item/ammo_box/magazine/m12g - fire_sound = 'sound/weapons/gunshot.ogg' + fire_sound = 'sound/weapons/gunshotshotgunshot.ogg' automatic_burst_overlay = FALSE can_suppress = FALSE burst_size = 1 @@ -293,11 +299,11 @@ desc = "A heavily modified 1.95x129mm light machine gun, designated 'L6 SAW'. Has 'Aussec Armoury - 2531' engraved on the receiver below the designation." icon_state = "l6closed100" item_state = "l6closedmag" + fire_sound = "sound/weapons/lmgshot.ogg" w_class = WEIGHT_CLASS_HUGE slot_flags = 0 mag_type = /obj/item/ammo_box/magazine/mm195x129 weapon_weight = WEAPON_HEAVY - fire_sound = 'sound/weapons/gunshot_smg.ogg' var/cover_open = FALSE can_suppress = FALSE burst_size = 3 @@ -363,6 +369,7 @@ desc = "A long ranged weapon that does significant damage. No, you can't quickscope." icon_state = "sniper" item_state = "sniper" + fire_sound = "sound/weapons/noscope.ogg" recoil = 2 weapon_weight = WEAPON_HEAVY mag_type = /obj/item/ammo_box/magazine/sniper_rounds @@ -397,6 +404,7 @@ desc = "One of countless obsolete ballistic rifles that still sees use as a cheap deterrent. Uses 10mm ammo and its bulky frame prevents one-hand firing." icon_state = "surplus" item_state = "moistnugget" + fire_sound = 'sound/weapons/rifleshot.ogg' weapon_weight = WEAPON_HEAVY mag_type = /obj/item/ammo_box/magazine/m10mm/rifle fire_delay = 30 diff --git a/code/modules/projectiles/guns/ballistic/pistol.dm b/code/modules/projectiles/guns/ballistic/pistol.dm index 9ed9630f2f..98b654aadb 100644 --- a/code/modules/projectiles/guns/ballistic/pistol.dm +++ b/code/modules/projectiles/guns/ballistic/pistol.dm @@ -138,7 +138,7 @@ can_suppress = FALSE w_class = WEIGHT_CLASS_NORMAL actions_types = list() - fire_sound = 'sound/weapons/blastcannon.ogg' + fire_sound = 'sound/weapons/noscope.ogg' spread = 20 //damn thing has no rifling. automatic_burst_overlay = FALSE diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm index 3e564a9ace..06342937dc 100644 --- a/code/modules/projectiles/guns/ballistic/revolver.dm +++ b/code/modules/projectiles/guns/ballistic/revolver.dm @@ -3,6 +3,7 @@ desc = "A suspicious revolver. Uses .357 ammo." //usually used by syndicates icon_state = "revolver" mag_type = /obj/item/ammo_box/magazine/internal/cylinder + fire_sound = "sound/weapons/revolvershot.ogg" casing_ejector = FALSE /obj/item/gun/ballistic/revolver/Initialize() diff --git a/code/modules/projectiles/guns/ballistic/shotgun.dm b/code/modules/projectiles/guns/ballistic/shotgun.dm index 796e544603..8035440906 100644 --- a/code/modules/projectiles/guns/ballistic/shotgun.dm +++ b/code/modules/projectiles/guns/ballistic/shotgun.dm @@ -3,6 +3,7 @@ desc = "A traditional shotgun with wood furniture and a four-shell capacity underneath." icon_state = "shotgun" item_state = "shotgun" + fire_sound = "sound/weapons/gunshotshotgunshot.ogg" w_class = WEIGHT_CLASS_BULKY force = 10 flags_1 = CONDUCT_1 diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm index c489edf88e..fd6204a8e3 100644 --- a/code/modules/reagents/chemistry/holder.dm +++ b/code/modules/reagents/chemistry/holder.dm @@ -973,10 +973,8 @@ for(var/A in cached_reagents) var/datum/reagent/R = A if (R.type == reagent) - if((total_volume - amount) <= 0)//Because this can result in 0, I don't want it to crash. - pH = REAGENT_NORMAL_PH //In practice this is really confusing and players feel like it randomly melts their beakers, but I'm not sure how else to handle it. We'll see how it goes and I can remove this if it confuses people. - else if (!ignore_pH) + if(!ignore_pH) //if (((pH > R.pH) && (pH <= 7)) || ((pH < R.pH) && (pH >= 7))) pH = (((pH - R.pH) / total_volume) * amount) + pH if(istype(my_atom, /obj/item/reagent_containers/)) @@ -987,6 +985,8 @@ amount = clamp(amount, 0, R.volume) R.volume -= amount update_total() + if(total_volume <= 0)//Because this can result in 0, I don't want it to crash. + pH = REAGENT_NORMAL_PH if(!safety)//So it does not handle reactions when it need not to handle_reactions() if(my_atom) diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm index d1c18f0510..22aadc06f4 100644 --- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm +++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm @@ -24,9 +24,11 @@ circuit = /obj/item/circuitboard/machine/chem_dispenser var/obj/item/stock_parts/cell/cell var/powerefficiency = 0.0666666 + var/dispenceUnit = 5 var/amount = 30 var/recharge_amount = 10 var/recharge_counter = 0 + var/canStore = TRUE//If this can hold reagents or not var/mutable_appearance/beaker_overlay var/working_state = "dispenser_working" var/nopower_state = "dispenser_nopower" @@ -102,6 +104,7 @@ if(upgrade_reagents3) upgrade_reagents3 = sortList(upgrade_reagents3, /proc/cmp_reagents_asc) dispensable_reagents = sortList(dispensable_reagents, /proc/cmp_reagents_asc) + create_reagents(200, NO_REACT) update_icon() /obj/machinery/chem_dispenser/Destroy() @@ -190,24 +193,27 @@ data["amount"] = amount data["energy"] = cell.charge ? cell.charge * powerefficiency : "0" //To prevent NaN in the UI. data["maxEnergy"] = cell.maxcharge * powerefficiency + data["storedVol"] = reagents.total_volume + data["maxVol"] = reagents.maximum_volume data["isBeakerLoaded"] = beaker ? 1 : 0 + data["stepAmount"] = dispenceUnit + data["canStore"] = canStore var/beakerContents[0] var/beakerCurrentVolume = 0 if(beaker && beaker.reagents && beaker.reagents.reagent_list.len) for(var/datum/reagent/R in beaker.reagents.reagent_list) - beakerContents.Add(list(list("name" = R.name, "volume" = R.volume))) // list in a list because Byond merges the first list... + beakerContents.Add(list(list("name" = R.name, "id" = R.type, "volume" = round(R.volume, 0.01)))) // list in a list because Byond merges the first list... beakerCurrentVolume += R.volume data["beakerContents"] = beakerContents if (beaker) - data["beakerCurrentVolume"] = beakerCurrentVolume + data["beakerCurrentVolume"] = round(beakerCurrentVolume, 0.01) data["beakerMaxVolume"] = beaker.volume data["beakerTransferAmounts"] = beaker.possible_transfer_amounts - data["beakerCurrentpH"] = beaker.reagents.pH //pH accuracy for(var/obj/item/stock_parts/capacitor/C in component_parts) - data["partRating"]= 10**(C.rating-1) + data["beakerCurrentpH"] = round(beaker.reagents.pH, 10**-(C.rating+1)) else data["beakerCurrentVolume"] = null @@ -225,11 +231,17 @@ var/chemname = temp.name if(is_hallucinating && prob(5)) chemname = "[pick_list_replacements("hallucination.json", "chemicals")]" - chemicals.Add(list(list("title" = chemname, "id" = ckey(temp.name)))) + chemicals.Add(list(list("title" = chemname, "id" = ckey(temp.name), "pH" = temp.pH, "pHCol" = ConvertpHToCol(temp.pH)))) data["chemicals"] = chemicals data["recipes"] = saved_recipes data["recordingRecipe"] = recording_recipe + + var/storedContents[0] + if(reagents.total_volume) + for(var/datum/reagent/N in reagents.reagent_list) + storedContents.Add(list(list("name" = N.name, "id" = N.type, "volume" = N.volume))) + data["storedContents"] = storedContents return data /obj/machinery/chem_dispenser/ui_act(action, params) @@ -240,10 +252,9 @@ if(!is_operational() || QDELETED(beaker)) return var/target = text2num(params["target"]) - if(target in beaker.possible_transfer_amounts) - amount = target - work_animation() - . = TRUE + SetAmount(target) + work_animation() + . = TRUE if("dispense") if(!is_operational() || QDELETED(cell)) return @@ -269,10 +280,9 @@ if(!is_operational() || recording_recipe) return var/amount = text2num(params["amount"]) - if(beaker && (amount in beaker.possible_transfer_amounts)) - beaker.reagents.remove_all(amount) - work_animation() - . = TRUE + beaker.reagents.remove_all(amount) //This should be set correctly in "amount" + work_animation() + . = TRUE if("eject") replace_beaker(usr) . = TRUE @@ -350,6 +360,52 @@ recording_recipe = null . = TRUE + //Storing and unstoring reagents + if("store") + if(!is_operational() || QDELETED(cell)) + return + if(!beaker) + return + if(recording_recipe) + say("Cannot store while recording!") + return + if(beaker.reagents.fermiIsReacting) + say("Cannot store ongoing reactions!") + return + var/reagent = text2path(params["id"]) + var/datum/reagent/R = beaker.reagents.has_reagent(reagent) + var/potentialAmount = min(amount, R.volume) + if(reagents.total_volume+potentialAmount > reagents.maximum_volume) + say("Not enough storage space left!") + return + beaker.reagents.trans_id_to(src, R.type, potentialAmount) + work_animation() + . = TRUE + + if("unstore") + if(!is_operational() || QDELETED(cell)) + return + if(!beaker) + return + if(recording_recipe) + say("Cannot distribute while recording!") + return + var/reagent = text2path(params["id"]) + var/datum/reagent/R = reagents.has_reagent(reagent) + reagents.trans_id_to(beaker, R.type, amount) + work_animation() + . = TRUE + +/obj/machinery/chem_dispenser/proc/SetAmount(inputAmount) + if(inputAmount % 5 == 0) //Always allow 5u values + amount = inputAmount + return + inputAmount -= inputAmount % dispenceUnit + if(inputAmount == 0) //Prevent ghost entries in macros + amount = dispenceUnit + return + amount = inputAmount + /obj/machinery/chem_dispenser/attackby(obj/item/I, mob/user, params) if(default_unfasten_wrench(user, I)) return @@ -402,6 +458,8 @@ cell = P for(var/obj/item/stock_parts/matter_bin/M in component_parts) newpowereff += 0.0166666666*M.rating + if(reagents) + reagents.maximum_volume = 200*(M.rating) for(var/obj/item/stock_parts/capacitor/C in component_parts) recharge_amount *= C.rating for(var/obj/item/stock_parts/manipulator/M in component_parts) @@ -411,6 +469,15 @@ dispensable_reagents |= upgrade_reagents2 if(M.rating > 3) dispensable_reagents |= upgrade_reagents3 + switch(M.rating) + if(-INFINITY to 1) + dispenceUnit = 5 + if(2) + dispenceUnit = 3 + if(3) + dispenceUnit = 2 + if(4 to INFINITY) + dispenceUnit = 1 powerefficiency = round(newpowereff, 0.01) /obj/machinery/chem_dispenser/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker) @@ -421,6 +488,8 @@ user.put_in_hands(B) if(new_beaker) beaker = new_beaker + if(amount > beaker.reagents.maximum_volume) + amount = beaker.reagents.maximum_volume else beaker = null update_icon() @@ -439,6 +508,32 @@ replace_beaker(user) return TRUE +/obj/machinery/chem_dispenser/proc/ConvertpHToCol(pH) + switch(pH) + if(-INFINITY to 1) + return "red" + if(1 to 2) + return "orange" + if(2 to 3) + return "average" + if(3 to 4) + return "yellow" + if(4 to 5) + return "olive" + if(5 to 6) + return "good" + if(6 to 8) + return "green" + if(8 to 9.5) + return "teal" + if(9.5 to 11) + return "blue" + if(11 to 12.5) + return "violet" + if(12.5 to INFINITY) + return "purple" + + /obj/machinery/chem_dispenser/drinks/Initialize() . = ..() AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE) @@ -466,6 +561,7 @@ b_o.pixel_x = rand(-9, 9) return b_o + /obj/machinery/chem_dispenser/drinks name = "soda dispenser" desc = "Contains a large reservoir of soft drinks." @@ -479,6 +575,7 @@ working_state = null nopower_state = null pass_flags = PASSTABLE + canStore = FALSE dispensable_reagents = list( /datum/reagent/water, /datum/reagent/consumable/ice, @@ -611,12 +708,14 @@ dispensable_reagents = list(/datum/reagent/toxin/mutagen) upgrade_reagents = null emagged_reagents = list(/datum/reagent/toxin/plasma) + canStore = FALSE /obj/machinery/chem_dispenser/mutagensaltpeter name = "botanical chemical dispenser" desc = "Creates and dispenses chemicals useful for botany." flags_1 = NODECONSTRUCT_1 + canStore = FALSE dispensable_reagents = list( /datum/reagent/toxin/mutagen, @@ -739,6 +838,7 @@ working_state = "minidispenser_working" nopower_state = "minidispenser_nopower" circuit = /obj/item/circuitboard/machine/chem_dispenser/apothecary + canStore = FALSE powerefficiency = 0.0833333 dispensable_reagents = list( //radium and stable plasma moved to upgrade tier 1 and 2, they've little to do with most medicines anyway. /datum/reagent/hydrogen, diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm index 8c3b9c7f99..cd8ee2d986 100644 --- a/code/modules/reagents/chemistry/machinery/chem_heater.dm +++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm @@ -126,7 +126,7 @@ var beakerContents[0] if(beaker) for(var/datum/reagent/R in beaker.reagents.reagent_list) - beakerContents.Add(list(list("name" = R.name, "volume" = R.volume, "purity" = R.purity))) // list in a list because Byond merges the first list... + beakerContents.Add(list(list("name" = R.name, "volume" = round(R.volume, 0.01), "purity" = round(R.purity, 0.01)))) // list in a list because Byond merges the first list... data["beakerContents"] = beakerContents return data diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm index 89fdb52b2a..285ef70433 100644 --- a/code/modules/reagents/chemistry/machinery/chem_master.dm +++ b/code/modules/reagents/chemistry/machinery/chem_master.dm @@ -184,13 +184,13 @@ var/beakerContents[0] if(beaker) for(var/datum/reagent/R in beaker.reagents.reagent_list) - beakerContents.Add(list(list("name" = R.name, "id" = ckey(R.name), "volume" = R.volume))) // list in a list because Byond merges the first list... + beakerContents.Add(list(list("name" = R.name, "id" = R.type, "volume" = R.volume))) // list in a list because Byond merges the first list... data["beakerContents"] = beakerContents var/bufferContents[0] if(reagents.total_volume) for(var/datum/reagent/N in reagents.reagent_list) - bufferContents.Add(list(list("name" = N.name, "id" = ckey(N.name), "volume" = N.volume))) // ^ + bufferContents.Add(list(list("name" = N.name, "id" = N.type, "volume" = N.volume))) // ^ data["bufferContents"] = bufferContents //Calculated at init time as it never changes @@ -216,7 +216,7 @@ if(action == "transfer") if(!beaker) return FALSE - var/reagent = GLOB.name2reagent[params["id"]] + var/reagent = text2path(params["id"]) var/amount = text2num(params["amount"]) var/to_container = params["to"] // Custom amount @@ -386,7 +386,7 @@ if(action == "analyze") // var/datum/reagent/R = GLOB.name2reagent[params["id"]] - var/reagent = GLOB.name2reagent[params["id"]] + var/reagent = text2path(params["id"]) var/datum/reagent/R = GLOB.chemical_reagents_list[reagent] if(R) var/state = "Unknown" @@ -405,7 +405,7 @@ analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold), "purityF" = R.purity, "inverseRatioF" = initial(R.inverse_chem_val), "purityE" = initial(Rcr.PurityMin), "minTemp" = initial(Rcr.OptimalTempMin), "maxTemp" = initial(Rcr.OptimalTempMax), "eTemp" = initial(Rcr.ExplodeTemp), "pHpeak" = pHpeakCache) else fermianalyze = FALSE - analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold)) + analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold), "purityF" = R.purity) screen = "analyze" return TRUE diff --git a/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drink_reagents.dm index 1a8f6edcbe..0e1cd77793 100644 --- a/code/modules/reagents/chemistry/reagents/drink_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drink_reagents.dm @@ -504,6 +504,14 @@ glass_desc = "Don't cry, Don't raise your eye, It's only nuclear wasteland." value = REAGENT_VALUE_COMMON +/datum/reagent/consumable/nuka_cola/on_mob_metabolize(mob/living/carbon/M) + M.add_movespeed_modifier(/datum/movespeed_modifier/reagent/meth) + return ..() + +/datum/reagent/consumable/nuka_cola/on_mob_end_metabolize(mob/living/carbon/M) + M.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/meth) + return ..() + /datum/reagent/consumable/nuka_cola/on_mob_life(mob/living/carbon/M) M.Jitter(20) M.set_drugginess(30) diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm index 0b44c33926..675502c5fb 100644 --- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm @@ -174,11 +174,13 @@ ADD_TRAIT(L, TRAIT_IGNOREDAMAGESLOWDOWN, type) L.update_movespeed() ADD_TRAIT(L, TRAIT_TASED_RESISTANCE, type) + L.add_movespeed_modifier(/datum/movespeed_modifier/reagent/meth) /datum/reagent/drug/methamphetamine/on_mob_end_metabolize(mob/living/L) REMOVE_TRAIT(L, TRAIT_IGNOREDAMAGESLOWDOWN, type) L.update_movespeed() REMOVE_TRAIT(L, TRAIT_TASED_RESISTANCE, type) + L.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/meth) ..() /datum/reagent/drug/methamphetamine/on_mob_life(mob/living/carbon/M) diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm index d4d66af240..f2e9bd9e1a 100644 --- a/code/modules/reagents/chemistry/recipes/medicine.dm +++ b/code/modules/reagents/chemistry/recipes/medicine.dm @@ -116,7 +116,10 @@ holder.remove_reagent(id, added_volume*temp_ratio) if(St.purity < 1) St.volume *= St.purity + added_volume *= St.purity St.purity = 1 + if(!N) + return var/amount = clamp(0.002, 0, N.volume) N.volume -= amount St.data["grown_volume"] = St.data["grown_volume"] + added_volume diff --git a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm index c35f062739..f3e14993ed 100644 --- a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm +++ b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm @@ -469,6 +469,10 @@ qdel(src) return ..() +/datum/status_effect/stabilized/Destroy() + linked_extract = null + return ..() + /datum/status_effect/stabilized/null //This shouldn't ever happen, but just in case. id = "stabilizednull" @@ -884,7 +888,8 @@ /datum/status_effect/stabilized/oil/tick() if(owner.stat == DEAD) explosion(get_turf(owner),1,2,4,flame_range = 5) - owner.remove_status_effect(/datum/status_effect/stabilized/oil) + qdel(linked_extract) + return return ..() /datum/status_effect/stabilized/black diff --git a/code/modules/uplink/uplink_items/uplink_clothing.dm b/code/modules/uplink/uplink_items/uplink_clothing.dm index 6163e5722a..de15b16b68 100644 --- a/code/modules/uplink/uplink_items/uplink_clothing.dm +++ b/code/modules/uplink/uplink_items/uplink_clothing.dm @@ -104,3 +104,10 @@ desc = "An eyepatch that connects itself to your eye socket, enhancing your shooting to an impossible degree, allowing your bullets to ricochet far more often than usual." item = /obj/item/clothing/glasses/eyepatch/syndicate cost = 8 + +/datum/uplink_item/device_tools/ablative_armwraps + name = "Ablative Armwraps" + desc = "A pair of highly reinforced armwraps allowing the user to parry almost anything. Fully reflects projectiles, no downsides to failing, but is very hard to parry melee with." + cost = 6 + item = /obj/item/clothing/gloves/fingerless/ablative + exclude_modes = list(/datum/game_mode/nuclear) diff --git a/code/modules/uplink/uplink_items/uplink_stealth.dm b/code/modules/uplink/uplink_items/uplink_stealth.dm index 18a7707a88..1bd75fa2b1 100644 --- a/code/modules/uplink/uplink_items/uplink_stealth.dm +++ b/code/modules/uplink/uplink_items/uplink_stealth.dm @@ -102,6 +102,7 @@ along with slurred speech, aggression, and the ability to infect others with this agent." item = /obj/item/storage/box/syndie_kit/romerol cost = 25 + player_minimum = 25 cant_discount = TRUE exclude_modes = list(/datum/game_mode/nuclear) diff --git a/config/game_options.txt b/config/game_options.txt index 3c53d9fecb..7776d87d4e 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -675,3 +675,6 @@ TURF_DIRT_THRESHOLD 100 ## Default alpha of dirt on spawn DIRT_ALPHA_STARTING 127 + +## Allows pAI custom holoforms +PAI_CUSTOM_HOLOFORMS diff --git a/html/changelog.html b/html/changelog.html index 113a82f15d..999d4f3d64 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -50,6 +50,77 @@ -->
+

07 February 2021

+

Thalpy updated:

+ +

TyrianTyrell updated:

+ +

dzahlus updated:

+ +

silicons updated:

+ + +

05 February 2021

+

SmArtKar updated:

+ +

keronshb updated:

+ +

raspy-on-osu updated:

+ +

shellspeed1 updated:

+ +

timothyteakettle updated:

+ + +

03 February 2021

+

Hatterhat updated:

+ + +

02 February 2021

+

silicons updated:

+ +

31 January 2021

Putnam3145 updated: