diff --git a/README.md b/README.md index 5218aca790e..67d5b8718e7 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,18 @@ For a basic setup, simply copy every file from config/example to config. For more advanced setups, setting the server `tick_lag` in the config as well as configuring SQL are good first steps. + +#### Permissions + +Permissions with file-based config are handled through `admin_ranks.json` +and `admins.txt`. To add yourself as the admin, simply find the rank with +the suitable permissions from the `admin_ranks.json` file, copy its `"name"` +field value, and input that into `admins.txt` like so: + +```cfg +myckeyhere - Head Admin/Dev +``` + --- ### SQL Setup diff --git a/SQL/migrate/V061__custom_items_database.sql b/SQL/migrate/V061__custom_items_database.sql new file mode 100644 index 00000000000..c33dd2d543f --- /dev/null +++ b/SQL/migrate/V061__custom_items_database.sql @@ -0,0 +1,19 @@ +-- +-- Implemented in PR TODO. +-- Adds a `ss13_characters_custom_items` table to replace the old custom item system. +-- + +CREATE TABLE `ss13_characters_custom_items` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `char_id` INT(11) NOT NULL, + `item_path` VARCHAR(255) NOT NULL COLLATE 'utf8mb4_unicode_ci', + `item_data` LONGTEXT NOT NULL COLLATE 'utf8mb4_bin', + `req_titles` LONGTEXT NULL DEFAULT NULL COLLATE 'utf8mb4_bin', + `additional_data` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci', + PRIMARY KEY (`id`) USING BTREE, + INDEX `FK_ss13_characters_custom_items_ss13_characters` (`char_id`) USING BTREE, + CONSTRAINT `FK_ss13_characters_custom_items_ss13_characters` FOREIGN KEY (`char_id`) REFERENCES `ss13_characters` (`id`) ON UPDATE CASCADE ON DELETE CASCADE +) +COLLATE='utf8mb4_unicode_ci' +ENGINE=InnoDB +; diff --git a/SQL/migrate/V062__hair_gradient_styles.sql b/SQL/migrate/V062__hair_gradient_styles.sql new file mode 100644 index 00000000000..4c992c47f68 --- /dev/null +++ b/SQL/migrate/V062__hair_gradient_styles.sql @@ -0,0 +1,8 @@ +-- +-- Implemented in PR #10718. +-- Adds a `Hair Gradient Style and Hair Gradient Color` columns for hair gradient preferences. +-- + +ALTER TABLE `ss13_characters` + ADD COLUMN `grad_colour` varchar(7) DEFAULT NULL AFTER `facial_colour`, + ADD COLUMN `gradient_style` varchar(32) DEFAULT NULL AFTER `facial_style`; diff --git a/aurorastation.dme b/aurorastation.dme index ce2ef797b85..515cd04bd48 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -1183,7 +1183,6 @@ #include "code\modules\admin\verbs\bluespacetech.dm" #include "code\modules\admin\verbs\BrokenInhands.dm" #include "code\modules\admin\verbs\buildmode.dm" -#include "code\modules\admin\verbs\check_customitem_activity.dm" #include "code\modules\admin\verbs\cinematic.dm" #include "code\modules\admin\verbs\clear_toxins.dm" #include "code\modules\admin\verbs\custom_event.dm" @@ -1378,6 +1377,7 @@ #include "code\modules\clothing\factions\gadpathur.dm" #include "code\modules\clothing\factions\goldendeep.dm" #include "code\modules\clothing\factions\himeo.dm" +#include "code\modules\clothing\factions\idris.dm" #include "code\modules\clothing\factions\konyang.dm" #include "code\modules\clothing\factions\vysoka.dm" #include "code\modules\clothing\glasses\glasses.dm" @@ -1661,6 +1661,7 @@ #include "code\modules\heavy_vehicle\mech_construction.dm" #include "code\modules\heavy_vehicle\mech_damage.dm" #include "code\modules\heavy_vehicle\mech_damage_immunity.dm" +#include "code\modules\heavy_vehicle\mech_helpers.dm" #include "code\modules\heavy_vehicle\mech_icon.dm" #include "code\modules\heavy_vehicle\mech_interaction.dm" #include "code\modules\heavy_vehicle\mech_life.dm" diff --git a/code/__defines/chemistry.dm b/code/__defines/chemistry.dm index 1044033e0d4..887ee9cf3f7 100644 --- a/code/__defines/chemistry.dm +++ b/code/__defines/chemistry.dm @@ -75,6 +75,7 @@ #define CE_BLOODRESTORE "bloodrestore" // Iron/nutriment #define CE_BRAIN_REGEN "brainfix" // Alkysine #define CE_OXYGENATED "oxygenated" // Dexalin +#define CE_BLOODCLOT "bloodclot" // Coagzolug // Deal damage #define CE_BREATHLOSS "breathloss" diff --git a/code/__defines/items_clothing.dm b/code/__defines/items_clothing.dm index 7e165d6c6a7..a7b6e91ab43 100644 --- a/code/__defines/items_clothing.dm +++ b/code/__defines/items_clothing.dm @@ -38,6 +38,7 @@ #define BLOCK_GAS_SMOKE_EFFECT 0x10 // Blocks the effect that chemical clouds would have on a mob -- glasses, mask and helmets ONLY! (NOTE: flag shared with ONESIZEFITSALL) #define FLEXIBLEMATERIAL 0x20 // At the moment, masks with this flag will not prevent eating even if they are covering your face. #define SOUNDPROTECTION 0x40 // whether wearing this item will protect you from loud noises such as flashbangs | this only works for ear slots or the head slot +#define LIGHTSTEP 0x80 // When applied to footwear, this makes it so that they don't trigger things like landmines and mouse traps // Flags for pass_flags. #define PASSTABLE 0x1 diff --git a/code/_helpers/global_lists.dm b/code/_helpers/global_lists.dm index e7b1a208cdd..cd3e8c5386c 100644 --- a/code/_helpers/global_lists.dm +++ b/code/_helpers/global_lists.dm @@ -49,6 +49,7 @@ var/list/obj/item/device/uplink/world_uplinks = list() var/global/list/hair_styles_list = list() //stores /datum/sprite_accessory/hair indexed by name var/global/list/hair_styles_male_list = list() var/global/list/hair_styles_female_list = list() +var/global/list/hair_gradient_styles_list = list() var/global/list/facial_hair_styles_list = list() //stores /datum/sprite_accessory/facial_hair indexed by name var/global/list/facial_hair_styles_male_list = list() var/global/list/facial_hair_styles_female_list = list() @@ -105,6 +106,14 @@ var/global/list/cloaking_devices = list() sortTim(hair_styles_male_list, /proc/cmp_text_asc) sortTim(hair_styles_female_list, /proc/cmp_text_asc) + //Gradients - Initialise all /datum/sprite_accessory/hair_gradients into an list indexed by hairgradient-style name + paths = subtypesof(/datum/sprite_accessory/hair_gradients) + for(var/path in paths) + var/datum/sprite_accessory/hair_gradients/H = new path() + hair_gradient_styles_list[H.name] = H + + sortTim(hair_gradient_styles_list, /proc/cmp_text_asc) + //Facial Hair - Initialise all /datum/sprite_accessory/facial_hair into an list indexed by facialhair-style name paths = subtypesof(/datum/sprite_accessory/facial_hair) for(var/path in paths) diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm index 0a2e73c5383..5c3b3f1d5cd 100644 --- a/code/_onclick/hud/_defines.dm +++ b/code/_onclick/hud/_defines.dm @@ -78,6 +78,7 @@ #define ui_fire "EAST-1:28,NORTH-3:25" #define ui_oxygen "EAST-1:28,NORTH-4:23" #define ui_pressure "EAST-1:28,NORTH-5:21" +#define ui_paralysis "EAST-1:28,NORTH-10:23" #define ui_alien_toxin "EAST-1:28,NORTH-2:25" #define ui_alien_fire "EAST-1:28,NORTH-3:25" diff --git a/code/_onclick/hud/action.dm b/code/_onclick/hud/action.dm index 712728aa861..4498c7406f1 100644 --- a/code/_onclick/hud/action.dm +++ b/code/_onclick/hud/action.dm @@ -22,6 +22,7 @@ var/obj/screen/movable/action_button/button = null var/button_icon = 'icons/obj/action_buttons/actions.dmi' var/button_icon_state = "default" + var/button_icon_color var/background_icon_state = "bg_default" var/mob/living/owner @@ -146,6 +147,8 @@ img = image(owner.button_icon,src,owner.button_icon_state) img.pixel_x = 0 img.pixel_y = 0 + if(owner.button_icon_color) + img.color = owner.button_icon_color add_overlay(img) if(!owner.IsAvailable()) diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm index e2363e3ba66..f14a04ba887 100644 --- a/code/_onclick/hud/human.dm +++ b/code/_onclick/hud/human.dm @@ -270,6 +270,13 @@ mymob.fire.screen_loc = ui_fire hud_elements |= mymob.fire + mymob.paralysis_indicator = new /obj/screen/paralysis() + mymob.paralysis_indicator.icon = 'icons/mob/status_indicators.dmi' + mymob.paralysis_indicator.icon_state = "paralysis0" + mymob.paralysis_indicator.name = "paralysis" + mymob.paralysis_indicator.screen_loc = ui_paralysis + hud_elements |= mymob.paralysis_indicator + mymob.healths = new /obj/screen() mymob.healths.icon = ui_style mymob.healths.icon_state = "health0" @@ -484,4 +491,11 @@ if(icon_state == "oxy0") to_chat(usr, SPAN_NOTICE("You are breathing easy.")) else - to_chat(usr, SPAN_DANGER("You cannot breathe!")) \ No newline at end of file + to_chat(usr, SPAN_DANGER("You cannot breathe!")) + +/obj/screen/paralysis/Click(var/location, var/control, var/params) + if(istype(usr) && usr.paralysis_indicator == src) + if(usr.paralysis) + to_chat(usr, SPAN_WARNING("You are completely paralyzed and cannot move!")) + else + to_chat(usr, SPAN_NOTICE("You are walking around completely fine.")) \ No newline at end of file diff --git a/code/controllers/subsystems/economy.dm b/code/controllers/subsystems/economy.dm index 40cdf948bb4..e4cbd716293 100644 --- a/code/controllers/subsystems/economy.dm +++ b/code/controllers/subsystems/economy.dm @@ -24,9 +24,8 @@ var/datum/controller/subsystem/economy/SSeconomy create_station_account() - for(var/department in station_departments) - create_department_account(department) - create_department_account("Vendor") + for(var/account in department_funds) + create_department_account(account) ..() @@ -77,7 +76,7 @@ var/datum/controller/subsystem/economy/SSeconomy department_account.account_number = next_account_number next_account_number += rand(1,500) department_account.remote_access_pin = rand(1111, 111111) - department_account.money = 5000 + department_account.money = department_funds[department] //create an entry in the account transaction log for when it was created var/datum/transaction/T = new() diff --git a/code/controllers/subsystems/ticker.dm b/code/controllers/subsystems/ticker.dm index f510921ff21..8b12065122f 100644 --- a/code/controllers/subsystems/ticker.dm +++ b/code/controllers/subsystems/ticker.dm @@ -363,6 +363,7 @@ var/datum/controller/subsystem/ticker/SSticker to_world("Welcome to the pre-game lobby!") to_world("Please, setup your character and select ready. Game will start in [pregame_timeleft] seconds.") + callHook("pregame_start") /datum/controller/subsystem/ticker/proc/setup() //Create and announce mode diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index be1b9db2254..4b9942f1dae 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -365,4 +365,7 @@ M.Turn(pick(-30, 30)) animate(I, alpha = 175, pixel_x = to_x, pixel_y = to_y, time = 3, transform = M, easing = CUBIC_EASING) sleep(1) - animate(I, alpha = 0, transform = matrix(), time = 1) \ No newline at end of file + animate(I, alpha = 0, transform = matrix(), time = 1) + +/atom/movable/proc/get_floating_chat_x_offset() + return 0 \ No newline at end of file diff --git a/code/game/machinery/CableLayer.dm b/code/game/machinery/CableLayer.dm index 112b469beae..5016f871fa6 100644 --- a/code/game/machinery/CableLayer.dm +++ b/code/game/machinery/CableLayer.dm @@ -12,42 +12,49 @@ /obj/machinery/cablelayer/Initialize() . = ..() cable = new(src) - cable.amount = 100 + cable.amount = max_cable /obj/machinery/cablelayer/Move(new_turf,M_Dir) ..() - layCable(new_turf,M_Dir) + if(on) + layCable(new_turf,M_Dir) -/obj/machinery/cablelayer/attack_hand(mob/user as mob) - if(!cable&&!on) - to_chat(user, "\The [src] doesn't have any cable loaded.") +/obj/machinery/cablelayer/attack_hand(mob/user) + if(!cable && !on) + to_chat(user, SPAN_WARNING("\The [src] doesn't have any cable loaded.")) return - on=!on - user.visible_message("\The [user] [!on?"dea":"a"]ctivates \the [src].", "You switch [src] [on? "on" : "off"]") + on = !on + user.visible_message("\The [user] [!on ? "de" : ""]activates \the [src].", SPAN_NOTICE("You switch \the [src] [on ? "on" : "off"].")) return -/obj/machinery/cablelayer/attackby(var/obj/item/O as obj, var/mob/user as mob) +/obj/machinery/cablelayer/attackby(var/obj/item/O, var/mob/user) if(O.iscoil()) - var/result = load_cable(O) if(!result) - to_chat(user, "\The [src]'s cable reel is full.") + to_chat(user, SPAN_WARNING("\The [src]'s cable reel is full.")) else - to_chat(user, "You load [result] lengths of cable into [src].") + to_chat(user, SPAN_NOTICE("You load [result] lengths of cable into \the [src].")) return if(O.iswirecutter()) if(cable && cable.amount) - var/m = round(input(usr,"Please specify the length of cable to cut","Cut cable",min(cable.amount,30)) as num, 1) + var/m = round(input(usr,"Please specify the length of cable to cut.", "Cut Cable",min(cable.amount,30)) as num, 1) m = min(m, cable.amount) m = min(m, 30) if(m) playsound(loc, 'sound/items/wirecutter.ogg', 50, 1) - use_cable(m) - var/obj/item/stack/cable_coil/CC = new (get_turf(src)) - CC.amount = m + var/cable_color = use_cable(m) + var/obj/item/stack/cable_coil/CC = new(get_turf(src), m, cable_color) + user.put_in_hands(CC) else - to_chat(usr, "There's no more cable on the reel.") + to_chat(user, SPAN_WARNING("There's no more cable on the reel.")) + return + + if(O.ismultitool()) + if(!cable) + to_chat(user, SPAN_WARNING("\The [src] doesn't have any cable loaded!")) + return + cable.attackby(O, user) /obj/machinery/cablelayer/examine(mob/user) ..() @@ -60,49 +67,43 @@ if(to_load) to_load = min(CC.amount, to_load) if(!cable) - cable = new(src) - cable.amount = 0 - cable.amount += to_load + cable = new(src, 0, CC.color) + cable.amount = to_load CC.use(to_load) return to_load else - return 0 - return + return FALSE /obj/machinery/cablelayer/proc/use_cable(amount) - if(!cable || cable.amount<1) - visible_message("A red light flashes on \the [src].") + if(!cable || cable.amount < 1) + on = FALSE + reset() + playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, TRUE) + visible_message(SPAN_WARNING("A red light flashes on \the [src].")) return + var/cable_color = cable.color cable.use(amount) if(QDELETED(cable)) cable = null - return 1 + return cable_color /obj/machinery/cablelayer/proc/reset() last_piece = null -/obj/machinery/cablelayer/proc/dismantleFloor(var/turf/new_turf) - if(istype(new_turf, /turf/simulated/floor)) - var/turf/simulated/floor/T = new_turf - if(!T.is_plating()) - T.make_plating(!(T.broken || T.burnt)) - return new_turf.is_plating() - /obj/machinery/cablelayer/proc/layCable(var/turf/new_turf,var/M_Dir) - if(!on) + if(!istype(new_turf)) return reset() - else - dismantleFloor(new_turf) - if(!istype(new_turf) || !dismantleFloor(new_turf)) + if(!new_turf.is_plating()) return reset() var/fdirn = turn(M_Dir,180) for(var/obj/structure/cable/LC in new_turf) // check to make sure there's not a cable there already if(LC.d1 == fdirn || LC.d2 == fdirn) return reset() - if(!use_cable(1)) + var/cable_color = use_cable(1) + if(!cable_color) return reset() var/obj/structure/cable/NC = new(new_turf) - NC.cableColor("red") + NC.cableColor(cable_color) NC.d1 = 0 NC.d2 = fdirn NC.update_icon() @@ -119,6 +120,5 @@ PN.add_cable(NC) NC.mergeConnectedNetworks(NC.d2) - //NC.mergeConnectedNetworksOnTurf() last_piece = NC - return 1 + return TRUE \ No newline at end of file diff --git a/code/game/machinery/body_scanner.dm b/code/game/machinery/body_scanner.dm index 8d19315ae8b..6b2b41ca9c2 100644 --- a/code/game/machinery/body_scanner.dm +++ b/code/game/machinery/body_scanner.dm @@ -306,7 +306,8 @@ for(var/obj/machinery/bodyscanner/C in orange(1,src)) connected = C break - connected.connected = src + if(connected) + connected.connected = src update_icon() /obj/machinery/body_scanconsole/attack_ai(var/mob/user) diff --git a/code/game/machinery/mech_recharger.dm b/code/game/machinery/mech_recharger.dm index 377497acde6..9468ffadf10 100644 --- a/code/game/machinery/mech_recharger.dm +++ b/code/game/machinery/mech_recharger.dm @@ -110,4 +110,22 @@ /obj/machinery/mech_recharger/proc/stop_charging() update_use_power(1) - charging = null \ No newline at end of file + charging = null + + +/obj/machinery/mech_recharger/hephaestus + name = "hephaestus exosuit dock" + desc = "A massive vehicle dock elevated slightly above the ground, constructed for equally massive charging speeds." + icon_state = "supermechcharger" + idle_power_usage = 400 + active_power_usage = 120 KILOWATTS + + base_charge_rate = 120 KILOWATTS + repair = 1 + + component_types = list( + /obj/item/circuitboard/mech_recharger/hephaestus, + /obj/item/stock_parts/capacitor = 3, + /obj/item/stock_parts/scanning_module = 2, + /obj/item/stock_parts/manipulator = 3 + ) diff --git a/code/game/objects/effects/decals/Cleanable/humans.dm b/code/game/objects/effects/decals/Cleanable/humans.dm index a3a9de78022..c39a791fd32 100644 --- a/code/game/objects/effects/decals/Cleanable/humans.dm +++ b/code/game/objects/effects/decals/Cleanable/humans.dm @@ -83,6 +83,8 @@ if(perp.shoes && !perp.buckled)//Adding blood to shoes var/obj/item/clothing/shoes/S = perp.shoes if(istype(S)) + if(S.item_flags & LIGHTSTEP) + return S.blood_color = basecolor S.track_footprint = max(amount, S.track_footprint) if(!S.blood_overlay) diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm index 80cae247231..f97ee9dfeed 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners.dm @@ -625,3 +625,78 @@ BREATH ANALYZER ++unknown if(unknown) to_chat(user,"Non-medical reagent[(unknown > 1)?"s":""] found in subject's respitory system.") + + +/obj/item/device/advanced_healthanalyzer + name = "zeng-hu body analyzer" + desc = "An expensive and varied-use health analyzer that prints full-body scans after a short scanning delay." + icon_state = "zh-analyzer" + item_state = "healthanalyzer" + slot_flags = SLOT_BELT + w_class = ITEMSIZE_NORMAL + origin_tech = list(TECH_MAGNET = 2, TECH_BIO = 3) + var/obj/machinery/body_scanconsole/internal_bodyscanner = null //this is used to print the date and to deal with extra + +/obj/item/device/advanced_healthanalyzer/Initialize() + . = ..() + if(!internal_bodyscanner) + var/obj/machinery/body_scanconsole/S = new (src) + S.forceMove(src) + S.use_power = FALSE + internal_bodyscanner = S + +/obj/item/device/advanced_healthanalyzer/Destroy() + if(internal_bodyscanner) + QDEL_NULL(internal_bodyscanner) + return ..() + +/obj/item/device/advanced_healthanalyzer/attack(mob/living/M, mob/living/user) + if(!internal_bodyscanner) + return + if(do_after(user, 7 SECONDS, TRUE)) + print_scan(M, user) + add_fingerprint(user) + +/obj/item/device/advanced_healthanalyzer/proc/print_scan(var/mob/M, var/mob/living/user) + var/obj/item/paper/R = new(user.loc) + R.color = "#eeffe8" + R.set_content_unsafe("Scan ([M.name])", internal_bodyscanner.format_occupant_data(get_medical_data(M))) + + if(ishuman(user) && !(user.l_hand && user.r_hand)) + user.put_in_hands(R) + user.visible_message("\The [src] spits out a piece of paper.") + +/obj/item/device/advanced_healthanalyzer/proc/get_medical_data(var/mob/living/carbon/human/H) + if (!ishuman(H)) + return + + var/list/medical_data = list( + "stationtime" = worldtime2text(), + "brain_activity" = H.get_brain_status(), + "blood_volume" = H.get_blood_volume(), + "blood_oxygenation" = H.get_blood_oxygenation(), + "blood_pressure" = H.get_blood_pressure(), + + "bruteloss" = get_severity(H.getBruteLoss(), TRUE), + "fireloss" = get_severity(H.getFireLoss(), TRUE), + "oxyloss" = get_severity(H.getOxyLoss(), TRUE), + "toxloss" = get_severity(H.getToxLoss(), TRUE), + "cloneloss" = get_severity(H.getCloneLoss(), TRUE), + + "rads" = H.total_radiation, + "paralysis" = H.paralysis, + "bodytemp" = H.bodytemperature, + "borer_present" = H.has_brain_worms(), + "inaprovaline_amount" = H.reagents.get_reagent_amount(/datum/reagent/inaprovaline), + "dexalin_amount" = H.reagents.get_reagent_amount(/datum/reagent/dexalin), + "stoxin_amount" = H.reagents.get_reagent_amount(/datum/reagent/soporific), + "bicaridine_amount" = H.reagents.get_reagent_amount(/datum/reagent/bicaridine), + "dermaline_amount" = H.reagents.get_reagent_amount(/datum/reagent/dermaline), + "blood_amount" = H.vessel.get_reagent_amount(/datum/reagent/blood), + "disabilities" = H.sdisabilities, + "lung_ruptured" = H.is_lung_ruptured(), + "external_organs" = H.organs.Copy(), + "internal_organs" = H.internal_organs.Copy(), + "species_organs" = H.species.has_organ + ) + return medical_data diff --git a/code/game/objects/items/glassjar.dm b/code/game/objects/items/glassjar.dm index 8db7ad4c609..acd82f78473 100644 --- a/code/game/objects/items/glassjar.dm +++ b/code/game/objects/items/glassjar.dm @@ -219,6 +219,8 @@ for(var/i = 1 to GUMBALL_MAX) var/obj/item/clothing/mask/chewable/candy/gum/gumball/medical/G = new(src) contained += G + + return INITIALIZE_HINT_LATELOAD #undef JAR_NOTHING #undef JAR_MONEY diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 542b646c1de..57190703374 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -503,7 +503,9 @@ /obj/item/toy/snappop/Crossed(H as mob|obj) if((ishuman(H))) //i guess carp and shit shouldn't set them off - var/mob/living/carbon/M = H + var/mob/living/carbon/human/M = H + if(M.shoes?.item_flags & LIGHTSTEP) + return if(M.m_intent == M_RUN) to_chat(M, SPAN_WARNING("You step on the snap pop!")) do_pop() diff --git a/code/game/objects/items/weapons/RFD.dm b/code/game/objects/items/weapons/RFD.dm index ed8dd7180cc..6445b5e260f 100644 --- a/code/game/objects/items/weapons/RFD.dm +++ b/code/game/objects/items/weapons/RFD.dm @@ -336,6 +336,7 @@ RFD Construction-Class /obj/item/rfd/construction/mounted/can_use(var/mob/user,var/turf/T) return (user.Adjacent(T) && !user.stat && !user.restrained()) + /* RFD Service-Class */ diff --git a/code/game/objects/items/weapons/circuitboards/machinery/mech_recharger.dm b/code/game/objects/items/weapons/circuitboards/machinery/mech_recharger.dm index a8614345b91..9d226321301 100644 --- a/code/game/objects/items/weapons/circuitboards/machinery/mech_recharger.dm +++ b/code/game/objects/items/weapons/circuitboards/machinery/mech_recharger.dm @@ -1,5 +1,5 @@ #ifndef T_BOARD -#error T_BOARD macro is not defined but we need it! +#error T_BOARD macro is not defined but we need it! #endif /obj/item/circuitboard/mech_recharger @@ -10,4 +10,13 @@ req_components = list( "/obj/item/stock_parts/capacitor" = 2, "/obj/item/stock_parts/scanning_module" = 1, - "/obj/item/stock_parts/manipulator" = 2) \ No newline at end of file + "/obj/item/stock_parts/manipulator" = 2) + +/obj/item/circuitboard/mech_recharger/hephaestus + name = T_BOARD("hephaestus mech recharger") + build_path = /obj/machinery/mech_recharger/hephaestus + origin_tech = list(TECH_DATA = 2, TECH_POWER = 3, TECH_ENGINEERING = 3) + req_components = list( + "/obj/item/stock_parts/capacitor" = 3, + "/obj/item/stock_parts/scanning_module" = 2, + "/obj/item/stock_parts/manipulator" = 3) \ No newline at end of file diff --git a/code/game/objects/items/weapons/landmines.dm b/code/game/objects/items/weapons/landmines.dm index d4a674ef475..420e9223839 100644 --- a/code/game/objects/items/weapons/landmines.dm +++ b/code/game/objects/items/weapons/landmines.dm @@ -57,6 +57,11 @@ /obj/item/landmine/Crossed(AM as mob|obj) if(deployed) + if(ishuman(AM)) + var/mob/living/carbon/human/H = AM + if(H.shoes?.item_flags & LIGHTSTEP) + ..() + return if(isliving(AM)) var/mob/living/L = AM if(L.mob_size >= 5) diff --git a/code/game/objects/items/weapons/material/caltrops.dm b/code/game/objects/items/weapons/material/caltrops.dm index 55724b94202..02b7d5b590d 100644 --- a/code/game/objects/items/weapons/material/caltrops.dm +++ b/code/game/objects/items/weapons/material/caltrops.dm @@ -22,9 +22,10 @@ var/damage_coef = 1 if(H.buckled) return - if(H.resting) return + if(H.shoes?.item_flags & LIGHTSTEP) + return to_chat(H, SPAN_DANGER("You step on \the [src]!")) playsound(get_turf(src), 'sound/effects/glass_step.ogg', 50, TRUE) diff --git a/code/game/objects/items/weapons/material/misc.dm b/code/game/objects/items/weapons/material/misc.dm index 4d560dd94ab..9e0cb3e7d79 100644 --- a/code/game/objects/items/weapons/material/misc.dm +++ b/code/game/objects/items/weapons/material/misc.dm @@ -82,6 +82,7 @@ ) sharp = 1 edge = 1 + force_divisor = 0.25 /obj/item/material/minihoe // -- Numbers name = "mini hoe" diff --git a/code/game/objects/items/weapons/policetape.dm b/code/game/objects/items/weapons/policetape.dm index f3ec465eaaf..4606c4c6390 100644 --- a/code/game/objects/items/weapons/policetape.dm +++ b/code/game/objects/items/weapons/policetape.dm @@ -71,8 +71,26 @@ var/list/tape_roll_applications = list() /obj/item/tape/engineering name = "engineering tape" desc = "A length of engineering tape. Better not cross it." - req_one_access = list(access_engine,access_atmospherics) + desc_info = "You can use a multitool on this tape to allow emergency shield generators to deploy shields on this tile." + req_one_access = list(access_engine, access_atmospherics) icon_base = "engineering" + var/shield_marker = FALSE + +/obj/item/tape/engineering/examine(mob/user, distance) + . = ..() + if(shield_marker) + to_chat(user, SPAN_NOTICE("This strip of tape has been modified to serve as a marker for emergency shield generators to lock onto.")) + +/obj/item/tape/engineering/attackby(obj/item/W, mob/user) + if(W.ismultitool()) + shield_marker = !shield_marker + to_chat(user, SPAN_NOTICE("You [shield_marker ? "" : "un"]designate \the [src] as a target for an emergency shield generator.")) + if(shield_marker) + animate(src, 1 SECOND, color = color_rotation(-60)) + else + animate(src, 1 SECOND, color = initial(color)) + return + return ..() /obj/item/taperoll/attack_self(mob/user as mob) if(icon_state == "[icon_base]_start") diff --git a/code/game/objects/items/weapons/soap.dm b/code/game/objects/items/weapons/soap.dm index b970d36e985..68450cb9aea 100644 --- a/code/game/objects/items/weapons/soap.dm +++ b/code/game/objects/items/weapons/soap.dm @@ -47,7 +47,11 @@ overlays += image('icons/obj/items.dmi', icon_state = "soap_key_overlay") /obj/item/soap/Crossed(AM as mob|obj) - if (istype(AM, /mob/living)) + if(isliving(AM)) + if(ishuman(AM)) + var/mob/living/carbon/human/H = AM + if(H.shoes?.item_flags & LIGHTSTEP) + return var/mob/living/M = AM M.slip("the [src.name]",3) diff --git a/code/game/objects/items/weapons/storage/backpack.dm b/code/game/objects/items/weapons/storage/backpack.dm index fc6ef02a400..51453d59cc7 100644 --- a/code/game/objects/items/weapons/storage/backpack.dm +++ b/code/game/objects/items/weapons/storage/backpack.dm @@ -513,3 +513,20 @@ canremove = 0 species_restricted = list(BODYTYPE_VAURCA_BREEDER) sprite_sheets = list(BODYTYPE_VAURCA_BREEDER = 'icons/mob/species/breeder/back.dmi') + +/obj/item/storage/backpack/service + name = "idris service backpack" + desc = "The infamously Idris Service Standard refers to this monstrous, self-stabilizing back-mounted utensil and service item holder, not anything professional." + icon_state = "idris_backpack" + storage_slots = 6 + max_w_class = ITEMSIZE_LARGE + can_hold = list( + /obj/item/tray, + /obj/item/material/kitchen/utensil/fork, + /obj/item/material/kitchen/utensil/knife, + /obj/item/material/kitchen/utensil/spoon, + /obj/item/material/knife, + /obj/item/material/hatchet/butch, + /obj/item/reagent_containers/food/drinks/drinkingglass, + /obj/item/storage/toolbox/lunchbox/nt + ) \ No newline at end of file diff --git a/code/game/objects/items/weapons/storage/firstaid.dm b/code/game/objects/items/weapons/storage/firstaid.dm index c7b2bc27e81..cdb0ccd0019 100644 --- a/code/game/objects/items/weapons/storage/firstaid.dm +++ b/code/game/objects/items/weapons/storage/firstaid.dm @@ -137,7 +137,8 @@ starts_with = list( /obj/item/stack/medical/bruise_pack = 2, /obj/item/stack/medical/advanced/bruise_pack = 2, - /obj/item/reagent_containers/pill/bicaridine = 2, + /obj/item/reagent_containers/pill/bicaridine = 1, + /obj/item/reagent_containers/hypospray/autoinjector/coagzolug = 1, /obj/item/device/healthanalyzer = 1 ) diff --git a/code/game/objects/items/weapons/storage/internal.dm b/code/game/objects/items/weapons/storage/internal.dm index 6d06d886c09..fd1dd16c733 100644 --- a/code/game/objects/items/weapons/storage/internal.dm +++ b/code/game/objects/items/weapons/storage/internal.dm @@ -84,3 +84,16 @@ /obj/item/storage/internal/Adjacent(var/atom/neighbor) return master_item.Adjacent(neighbor) + +/obj/item/storage/internal/skrell + name = "headtail storage" + icon = 'icons/obj/action_buttons/organs.dmi' + icon_state = "skrell_headpocket" + storage_slots = 1 + max_storage_space = 2 + max_w_class = ITEMSIZE_SMALL + use_sound = null + +/obj/item/storage/internal/skrell/Initialize() + . = ..() + name = initial(name) \ No newline at end of file diff --git a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm index c5263f700b7..700d6c2dd67 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/medical.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/medical.dm @@ -118,6 +118,7 @@ new /obj/item/storage/box/inhalers(src) new /obj/item/clothing/glasses/hud/health/aviator(src) new /obj/item/storage/box/fancy/keypouch/med(src) + new /obj/item/device/advanced_healthanalyzer(src) /obj/structure/closet/secure_closet/CMO2 name = "chief medical officer's attire" diff --git a/code/game/objects/structures/stool_bed_chair_nest/bed.dm b/code/game/objects/structures/stool_bed_chair_nest/bed.dm index 1e47b651d3d..e3b83955d9e 100644 --- a/code/game/objects/structures/stool_bed_chair_nest/bed.dm +++ b/code/game/objects/structures/stool_bed_chair_nest/bed.dm @@ -82,6 +82,11 @@ desc += padding_material ? " It's made of [material.use_name] and covered with [padding_material.use_name]." : " It's made of [material.use_name]." +/obj/structure/bed/forceMove(atom/dest) + . = ..() + if(buckled_mob) + buckled_mob.forceMove(dest) + /obj/structure/bed/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) if(istype(mover) && mover.checkpass(PASSTABLE)) return 1 diff --git a/code/global.dm b/code/global.dm index a442f7ebfb3..8985488586d 100644 --- a/code/global.dm +++ b/code/global.dm @@ -146,7 +146,17 @@ var/max_explosion_range = 14 // Announcer intercom, because too much stuff creates an intercom for one message then hard del()s it. var/global/obj/item/device/radio/intercom/global_announcer = new(null) -var/list/station_departments = list("Command", "Medical", "Engineering", "Science", "Security", "Cargo", "Civilian") +// the number next to it denotes how much money the department receives when its account is generated +var/list/department_funds = list( + "Command" = 10000, + "Medical" = 10000, + "Engineering" = 10000, + "Science" = 10000, + "Security" = 10000, + "Cargo" = 5000, + "Civilian" = 10000, + "Vendor" = 0 + ) //List of exosuit tracking beacons, to save performance -var/global/list/exo_beacons = list() +var/global/list/exo_beacons = list() \ No newline at end of file diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 0ede3b88592..ffd797bea64 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -1010,27 +1010,6 @@ proc/admin_notice(var/message, var/rights) item_to_spawn.spawn_item(get_turf(usr)) -/datum/admins/proc/check_custom_items() - - set category = "Debug" - set desc = "Check the custom item list." - set name = "Check Custom Items" - - if(!check_rights(R_SPAWN)) return - - if(!custom_items) - to_chat(usr, "Custom item list is null.") - return - - if(!custom_items.len) - to_chat(usr, "Custom item list not populated.") - return - - for(var/assoc_key in custom_items) - to_chat(usr, "[assoc_key] has:") - var/list/current_items = custom_items[assoc_key] - for(var/datum/custom_item/item in current_items) - to_chat(usr, "- name: [item.name] icon: [item.item_icon] path: [item.item_path] desc: [item.item_desc]") /datum/admins/proc/spawn_plant(seedtype in SSplants.seeds) set category = "Debug" diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index b9ad8e67677..cf412ea740c 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -72,7 +72,6 @@ var/list/admin_verbs_admin = list( /client/proc/toggleghostwriters, /client/proc/toggledrones, /datum/admins/proc/show_skills, - /client/proc/check_customitem_activity, /client/proc/man_up, /client/proc/global_man_up, /client/proc/response_team, // Response Teams admin verb, @@ -145,7 +144,6 @@ var/list/admin_verbs_spawn = list( /client/proc/game_panel, /datum/admins/proc/spawn_fruit, /datum/admins/proc/spawn_custom_item, - /datum/admins/proc/check_custom_items, /datum/admins/proc/spawn_plant, /datum/admins/proc/spawn_atom, // allows us to spawn instances, /client/proc/respawn_character, @@ -171,7 +169,6 @@ var/list/admin_verbs_server = list( /datum/admins/proc/toggle_round_spookyness, /datum/admins/proc/toggle_space_ninja, /client/proc/toggle_random_events, - /client/proc/check_customitem_activity, /client/proc/nanomapgen_DumpImage, /client/proc/toggle_recursive_explosions, /client/proc/restart_controller, @@ -350,7 +347,6 @@ var/list/admin_verbs_hideable = list( /datum/admins/proc/call_drop_pod, /datum/admins/proc/spawn_fruit, /datum/admins/proc/spawn_custom_item, - /datum/admins/proc/check_custom_items, /datum/admins/proc/spawn_plant, /client/proc/show_plant_genes, /datum/admins/proc/spawn_atom, @@ -400,7 +396,6 @@ var/list/admin_verbs_hideable = list( /client/proc/callproc, /client/proc/callproc_target, /client/proc/debug_controller, - /client/proc/check_customitem_activity, /client/proc/print_logout_report, /client/proc/edit_admin_permissions, /proc/possess, diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index a9b5e68d84a..683ff801313 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1460,10 +1460,6 @@ src.admincaster_screen = 20 src.access_news_network() - else if(href_list["populate_inactive_customitems"]) - if(check_rights(R_ADMIN|R_SERVER)) - populate_inactive_customitems_list(src.owner) - else if(href_list["vsc"]) if(check_rights(R_ADMIN|R_SERVER)) if(href_list["vsc"] == "airflow") diff --git a/code/modules/admin/verbs/check_customitem_activity.dm b/code/modules/admin/verbs/check_customitem_activity.dm deleted file mode 100644 index e9bbaded60b..00000000000 --- a/code/modules/admin/verbs/check_customitem_activity.dm +++ /dev/null @@ -1,85 +0,0 @@ -var/checked_for_inactives = 0 -var/inactive_keys = "None
" - -/client/proc/check_customitem_activity() - set category = "Admin" - set name = "Check activity of players with custom items" - - var/dat = "Inactive players with custom items
" - dat += "
" - dat += "The list below contains players with custom items that have not logged\ - in for the past two months, or have not logged in since this system was implemented.\ - This system requires the feedback SQL database to be properly setup and linked.
" - dat += "
" - dat += "Populating this list is done automatically, but must be manually triggered on a per\ - round basis. Populating the list may cause a lag spike, so use it sparingly.
" - dat += "
" - if(checked_for_inactives) - dat += inactive_keys - dat += "
" - dat += "This system was implemented on March 1 2013, and the database a few days before that. Root server access is required to add or disable access to specific custom items.
" - else - dat += "Populate list (requires an active database connection)
" - - usr << browse(dat, "window=inactive_customitems;size=600x480") - -/proc/populate_inactive_customitems_list(var/client/C) - set background = 1 - - if(checked_for_inactives) - return - - establish_db_connection(dbcon) - if(!dbcon.IsConnected()) - return - - //grab all ckeys associated with custom items - var/list/ckeys_with_customitems = list() - - var/file = file2text("config/custom_items.txt") - var/lines = text2list(file, "\n") - - for(var/line in lines) - // split & clean up - var/list/Entry = text2list(line, ":") - for(var/i = 1 to Entry.len) - Entry[i] = trim(Entry[i]) - - if(Entry.len < 1) - continue - - var/cur_key = Entry[1] - if(!ckeys_with_customitems.Find(cur_key)) - ckeys_with_customitems.Add(cur_key) - - //run a query to get all ckeys inactive for over 2 months - var/list/inactive_ckeys = list() - if(ckeys_with_customitems.len) - var/DBQuery/query_inactive = dbcon.NewQuery("SELECT ckey, lastseen FROM ss13_player WHERE datediff(Now(), lastseen) > 60") - query_inactive.Execute() - while(query_inactive.NextRow()) - var/cur_ckey = query_inactive.item[1] - //if the ckey has a custom item attached, output it - if(ckeys_with_customitems.Find(cur_ckey)) - ckeys_with_customitems.Remove(cur_ckey) - inactive_ckeys[cur_ckey] = "last seen on [query_inactive.item[2]]" - - //if there are ckeys left over, check whether they have a database entry at all - if(ckeys_with_customitems.len) - for(var/cur_ckey in ckeys_with_customitems) - var/DBQuery/query_inactive = dbcon.NewQuery("SELECT ckey FROM ss13_player WHERE ckey = '[cur_ckey]'") - query_inactive.Execute() - if(!query_inactive.RowCount()) - inactive_ckeys += cur_ckey - - if(inactive_ckeys.len) - inactive_keys = "" - for(var/cur_key in inactive_ckeys) - if(inactive_ckeys[cur_key]) - inactive_keys += "[cur_key] - [inactive_ckeys[cur_key]]
" - else - inactive_keys += "[cur_key] - no database entry
" - - checked_for_inactives = 1 - if(C) - C.check_customitem_activity() diff --git a/code/modules/admin/verbs/warning.dm b/code/modules/admin/verbs/warning.dm index 2774e382dad..dde65366889 100644 --- a/code/modules/admin/verbs/warning.dm +++ b/code/modules/admin/verbs/warning.dm @@ -281,6 +281,8 @@ */ /client/proc/fetch_unacked_warning_count() establish_db_connection(dbcon) + if (!dbcon) + return if (!dbcon.IsConnected()) return var/DBQuery/warning_count_query = dbcon.NewQuery("SELECT COUNT(*) FROM ss13_warnings WHERE (visible = 1 AND acknowledged = 0 AND expired = 0) AND (ckey = :ckey: OR computerid = :computer_id: OR ip = :address:)") diff --git a/code/modules/assembly/mousetrap.dm b/code/modules/assembly/mousetrap.dm index 87f6bab6be9..cbf721e2755 100644 --- a/code/modules/assembly/mousetrap.dm +++ b/code/modules/assembly/mousetrap.dm @@ -85,7 +85,12 @@ if(armed) if(israt(AM)) triggered(AM) - else if(istype(AM, /mob/living)) + else if(ishuman(AM)) + var/mob/living/carbon/human/H = AM + if(!(H.shoes?.item_flags & LIGHTSTEP)) + triggered(H) + H.visible_message(SPAN_WARNING("\The [H] accidentally steps on \the [src]."), SPAN_WARNING("You accidentally step on \the [src].")) + else if(isliving(AM)) var/mob/living/L = AM triggered(L) L.visible_message(SPAN_WARNING("\The [L] accidentally steps on \the [src]."), SPAN_WARNING("You accidentally step on \the [src].")) diff --git a/code/modules/client/asset_cache.dm b/code/modules/client/asset_cache.dm index 64585baf26d..08e3a9a1862 100644 --- a/code/modules/client/asset_cache.dm +++ b/code/modules/client/asset_cache.dm @@ -61,7 +61,7 @@ You can set verify to TRUE if you want send() to sleep until the client has the while(client && !client.completed_asset_jobs.Find(job) && t < timeout_time) // Reception is handled in Topic() sleep(1) // Lock up the caller until this is received. t++ - + if(t >= timeout_time) log_admin(SPAN_DANGER("Timeout time [timeout_time] exceeded for asset: [asset_name] for client [client]. Please notify a developer.")) @@ -175,25 +175,6 @@ var/list/asset_datums = list() /datum/asset/simple/send(client) send_asset_list(client,assets,verify) -/datum/asset/chem_master - var/list/bottle_sprites = list("bottle-1", "bottle-2", "bottle-3", "bottle-4") - var/max_pill_sprite = 20 - var/list/assets = list() - -/datum/asset/chem_master/register() - for (var/i = 1 to max_pill_sprite) - var/name = "pill[i].png" - register_asset(name, icon('icons/obj/chemical.dmi', "pill[i]")) - assets += name - - for (var/sprite in bottle_sprites) - var/name = "[sprite].png" - register_asset(name, icon('icons/obj/chemical.dmi', sprite)) - assets += name - -/datum/asset/chem_master/send(client) - send_asset_list(client, assets) - /datum/asset/group _abstract = /datum/asset/group var/list/children @@ -528,3 +509,16 @@ var/list/asset_datums = list() else Insert(imgid, I) return ..() + +/datum/asset/spritesheet/chem_master + name = "chemmaster" + var/list/bottle_sprites = list("bottle-1", "bottle-2", "bottle-3", "bottle-4") + var/max_pill_sprite = 20 + +/datum/asset/spritesheet/chem_master/register() + for (var/i = 1 to max_pill_sprite) + Insert("pill[i]", 'icons/obj/chemical.dmi', "pill[i]") + + for (var/sprite in bottle_sprites) + Insert(sprite, icon('icons/obj/chemical.dmi', sprite)) + return ..() diff --git a/code/modules/client/preference_setup/general/01_basic.dm b/code/modules/client/preference_setup/general/01_basic.dm index 765e6f7c3a0..dd26ddd1ba7 100644 --- a/code/modules/client/preference_setup/general/01_basic.dm +++ b/code/modules/client/preference_setup/general/01_basic.dm @@ -181,10 +181,22 @@ if (!isnull(raw_name) && CanUseTopic(user)) var/new_name = sanitize_name(raw_name, pref.species) if(new_name) + if(new_name == pref.real_name) + return TOPIC_NOACTION //If the name is the same do nothing + if(config.sql_saves) + //Check if the player already has a character with the same name. (We dont have to account for the current char in that query, as that is already handled by the condition above) + var/DBQuery/query = dbcon.NewQuery("SELECT COUNT(*) FROM ss13_characters WHERE ckey = :ckey: and name = :char_name:") + query.Execute(list("ckey" = user.client.ckey, "char_name" = new_name)) + query.NextRow() + var/count = text2num(query.item[1]) + if(count > 0) + to_chat(user, SPAN_WARNING("Invalid name. You have already used this name for another character. If you have deleted the character contact an admin to restore it.")) + return TOPIC_NOACTION + pref.real_name = new_name return TOPIC_REFRESH else - to_chat(user, "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z, -, ' and .") + to_chat(user, SPAN_WARNING("Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z, -, ' and .")) return TOPIC_NOACTION else if(href_list["namehelp"]) diff --git a/code/modules/client/preference_setup/general/03_body.dm b/code/modules/client/preference_setup/general/03_body.dm index 8d7092bea64..867d6ba294f 100644 --- a/code/modules/client/preference_setup/general/03_body.dm +++ b/code/modules/client/preference_setup/general/03_body.dm @@ -14,12 +14,16 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O S["facial_red"] >> pref.r_facial S["facial_green"] >> pref.g_facial S["facial_blue"] >> pref.b_facial + S["grad_red"] >> pref.r_grad + S["grad_green"] >> pref.g_grad + S["grad_blue"] >> pref.b_grad S["skin_tone"] >> pref.s_tone S["skin_red"] >> pref.r_skin S["skin_green"] >> pref.g_skin S["skin_blue"] >> pref.b_skin S["hair_style_name"] >> pref.h_style S["facial_style_name"] >> pref.f_style + S["grad_style_name"] >> pref.g_style S["eyes_red"] >> pref.r_eyes S["eyes_green"] >> pref.g_eyes S["eyes_blue"] >> pref.b_eyes @@ -36,12 +40,16 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O S["facial_red"] << pref.r_facial S["facial_green"] << pref.g_facial S["facial_blue"] << pref.b_facial + S["grad_red"] << pref.r_grad + S["grad_green"] << pref.g_grad + S["grad_blue"] << pref.b_grad S["skin_tone"] << pref.s_tone S["skin_red"] << pref.r_skin S["skin_green"] << pref.g_skin S["skin_blue"] << pref.b_skin S["hair_style_name"] << pref.h_style S["facial_style_name"] << pref.f_style + S["grad_style_name"] << pref.g_style S["eyes_red"] << pref.r_eyes S["eyes_green"] << pref.g_eyes S["eyes_blue"] << pref.b_eyes @@ -50,7 +58,7 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O S["organ_data"] << pref.organ_data S["rlimb_data"] << pref.rlimb_data S["body_markings"] << pref.body_markings - S["bgstate"] << pref.bgstate + S["bgstate"] << pref.bgstate /datum/category_item/player_setup_item/general/body/gather_load_query() return list( @@ -58,10 +66,12 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O "vars" = list( "hair_colour", "facial_colour", + "grad_colour", "skin_tone" = "s_tone", "skin_colour", "hair_style" = "h_style", "facial_style" = "f_style", + "gradient_style" = "g_style", "eyes_colour", "b_type", "disabilities", @@ -82,10 +92,12 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O "ss13_characters" = list( "hair_colour", "facial_colour", + "grad_colour", "skin_tone", "skin_colour", "hair_style", "facial_style", + "gradient_style", "eyes_colour", "b_type", "disabilities", @@ -102,10 +114,12 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O return list( "hair_colour" = rgb(pref.r_hair, pref.g_hair, pref.b_hair), "facial_colour" = rgb(pref.r_facial, pref.g_facial, pref.b_facial), + "grad_colour" = rgb(pref.r_grad, pref.g_grad, pref.b_grad), "skin_tone" = pref.s_tone, "skin_colour" = rgb(pref.r_skin, pref.g_skin, pref.b_skin) , "hair_style" = pref.h_style, "facial_style" = pref.f_style, + "gradient_style"= pref.g_style, "eyes_colour" = rgb(pref.r_eyes, pref.g_eyes, pref.b_eyes), "b_type" = pref.b_type, "disabilities" = json_encode(pref.disabilities), @@ -129,6 +143,11 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O pref.g_facial = GetGreenPart(pref.facial_colour) pref.b_facial = GetBluePart(pref.facial_colour) + pref.grad_colour = sanitize_hexcolor(pref.grad_colour) + pref.r_grad = GetRedPart(pref.grad_colour) + pref.g_grad = GetGreenPart(pref.grad_colour) + pref.b_grad = GetBluePart(pref.grad_colour) + pref.s_tone = text2num(pref.s_tone) pref.skin_colour = sanitize_hexcolor(pref.skin_colour) @@ -172,6 +191,7 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O pref.b_skin = sanitize_integer(pref.b_skin, 0, 255, initial(pref.b_skin)) pref.h_style = sanitize_inlist(pref.h_style, hair_styles_list, initial(pref.h_style)) pref.f_style = sanitize_inlist(pref.f_style, facial_hair_styles_list, initial(pref.f_style)) + pref.g_style = sanitize_inlist(pref.g_style, hair_gradient_styles_list, initial(pref.g_style)) pref.r_eyes = sanitize_integer(pref.r_eyes, 0, 255, initial(pref.r_eyes)) pref.g_eyes = sanitize_integer(pref.g_eyes, 0, 255, initial(pref.g_eyes)) pref.b_eyes = sanitize_integer(pref.b_eyes, 0, 255, initial(pref.b_eyes)) @@ -273,6 +293,11 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O out += "Change Color [HTML_RECT(rgb(pref.r_facial, pref.g_facial, pref.b_facial))] " out += " Style: [pref.f_style]
" + out += "Gradient
" + if(has_flag(mob_species, HAS_HAIR_COLOR)) + out += "Change Color [HTML_RECT(rgb(pref.r_grad, pref.g_grad, pref.b_grad))] " + out += " Style: [pref.g_style]
" + if(has_flag(mob_species, HAS_EYE_COLOR)) out += "
Eyes
" out += "Change Color [HTML_RECT(rgb(pref.r_eyes, pref.g_eyes, pref.b_eyes))]
" @@ -402,6 +427,16 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O pref.b_hair = GetBluePart(new_hair) return TOPIC_REFRESH_UPDATE_PREVIEW + else if(href_list["gradient_color"]) + if(!has_flag(mob_species, HAS_HAIR_COLOR)) + return TOPIC_NOACTION + var/new_grad = input(user, "Choose your character's secondary hair color:", "Character Preference", rgb(pref.r_grad, pref.g_grad, pref.b_grad)) as color|null + if(new_grad && has_flag(mob_species, HAS_HAIR_COLOR) && CanUseTopic(user)) + pref.r_grad = GetRedPart(new_grad) + pref.g_grad = GetGreenPart(new_grad) + pref.b_grad = GetBluePart(new_grad) + return TOPIC_REFRESH_UPDATE_PREVIEW + else if(href_list["hair_style"]) if(mob_species.bald) return @@ -419,6 +454,20 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O pref.h_style = new_h_style return TOPIC_REFRESH_UPDATE_PREVIEW + else if(href_list["gradient_style"]) + var/list/valid_gradients = list() + for(var/gradstyle in hair_gradient_styles_list) + var/datum/sprite_accessory/S = hair_gradient_styles_list[gradstyle] + if(!(mob_species.type in S.species_allowed)) + continue + + valid_gradients[gradstyle] = hair_gradient_styles_list[gradstyle] + + var/new_g_style = input(user, "Choose a color pattern for your hair:", "Character Preference", pref.g_style) as null|anything in valid_gradients + if(new_g_style && CanUseTopic(user)) + pref.g_style = new_g_style + return TOPIC_REFRESH_UPDATE_PREVIEW + else if(href_list["facial_color"]) if(!has_flag(mob_species, HAS_HAIR_COLOR)) return TOPIC_NOACTION diff --git a/code/modules/client/preference_setup/loadout/loadout_factions.dm b/code/modules/client/preference_setup/loadout/loadout_factions.dm index 4137a63c9b5..657f0718842 100644 --- a/code/modules/client/preference_setup/loadout/loadout_factions.dm +++ b/code/modules/client/preference_setup/loadout/loadout_factions.dm @@ -23,6 +23,12 @@ path = /obj/item/clothing/accessory/armband/idris faction = "Idris Incorporated" +/datum/gear/faction/idris_windbreaker + display_name = "idris jacket" + path = /obj/item/clothing/suit/storage/toggle/idris + slot = slot_wear_suit + faction = "Idris Incorporated" + /datum/gear/faction/zavodskoi_beret display_name = "black zavodskoi beret" path = /obj/item/clothing/head/beret/security/zavodskoi diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index db8928b2826..9f72ab436c1 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -49,6 +49,11 @@ datum/preferences var/r_hair = 0 //Hair color var/g_hair = 0 //Hair color var/b_hair = 0 //Hair color + var/g_style = "None" //Gradient style + var/grad_colour = "#000000" //Gradient colour hex value, for SQL loading + var/r_grad = 0 //Gradient color + var/g_grad = 0 //Gradient color + var/b_grad = 0 //Gradient color var/f_style = "Shaved" //Face hair type var/facial_colour = "#000000" //Facial colour hex value, for SQL loading var/r_facial = 0 //Face hair color @@ -436,15 +441,17 @@ datum/preferences character.g_facial = g_facial character.b_facial = b_facial + character.g_style = g_style + character.r_grad = r_grad + character.g_grad = g_grad + character.b_grad = b_grad + character.r_skin = r_skin character.g_skin = g_skin character.b_skin = b_skin character.s_tone = s_tone - character.h_style = h_style - character.f_style = f_style - character.citizenship = citizenship character.employer_faction = faction character.religion = religion diff --git a/code/modules/clothing/factions/idris.dm b/code/modules/clothing/factions/idris.dm new file mode 100644 index 00000000000..5960cf82733 --- /dev/null +++ b/code/modules/clothing/factions/idris.dm @@ -0,0 +1,53 @@ +//iru coats + +/obj/item/clothing/suit/armor/vest/idris + name = "black Idris Unit coat" + desc = "A coat worn by the Idris units, notorious across space." + icon = 'icons/clothing/suits/coats/idris_iru_coats.dmi' + icon_state = "iru_coat" + item_state = "iru_coat" + cold_protection = 0 + min_cold_protection_temperature = 0 + heat_protection = 0 + max_heat_protection_temperature = 0 + contained_sprite = TRUE + +/obj/item/clothing/suit/armor/vest/idris/brown + name = "brown Idris Unit coat" + desc = "A coat worn by the Idris units, notorious across space. This one is brown." + icon_state = "iru_coat_brown" + item_state = "iru_coat_brown" + +/obj/item/clothing/suit/armor/vest/idris/trenchcoat/black + name = "black Idris Unit trench coat" + desc = "A trench coat worn by the Idris units, notorious across space. This one is black." + icon_state = "iru_trench_black" + item_state = "iru_trench_black" + +/obj/item/clothing/suit/armor/vest/idris/trenchcoat/brown + name = "brown Idris Unit trench coat" + desc = "A trench coat worn by the Idris units, notorious across space. This one is brown." + icon_state = "iru_trench_brown" + item_state = "iru_trench_brown" + +/obj/item/clothing/suit/armor/vest/idris/duster/black + name = "black Idris Unit duster coat" + desc = "A duster coat worn by the Idris units, notorious across space. This one is black." + icon_state = "iru_duster_black" + item_state = "iru_duster_black" + +/obj/item/clothing/suit/armor/vest/idris/duster/brown + name = "brown Idris Unit duster coat" + desc = "A duster coat worn by the Idris units, notorious across space. This one is brown." + icon_state = "iru_duster_brown" + item_state = "iru_duster_brown" + +//windbreaker + +/obj/item/clothing/suit/storage/toggle/idris + name = "\improper Idris Incorporated jacket" + desc = "A comfortable windbreaker for Idris Incorporated investigations staff styled after the coats of Idris reclamation units. Many of the Idris patches and badges on the coat are holographic." + icon = 'icons/clothing/suits/coats/idris_windbreaker.dmi' + icon_state = "idris_windbreaker" + item_state = "idris_windbreaker" + contained_sprite = TRUE \ No newline at end of file diff --git a/code/modules/clothing/shoes/miscellaneous.dm b/code/modules/clothing/shoes/miscellaneous.dm index 663ea3009cd..65e8fa01c16 100644 --- a/code/modules/clothing/shoes/miscellaneous.dm +++ b/code/modules/clothing/shoes/miscellaneous.dm @@ -4,7 +4,7 @@ icon_state = "brown" item_state = "brown" permeability_coefficient = 0.05 - item_flags = NOSLIP + item_flags = NOSLIP|LIGHTSTEP origin_tech = list(TECH_ILLEGAL = 3) var/list/clothing_choices = list() siemens_coefficient = 0.75 diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm index f7eae55b202..8a109443abe 100644 --- a/code/modules/clothing/suits/armor.dm +++ b/code/modules/clothing/suits/armor.dm @@ -576,46 +576,6 @@ pockets.max_w_class = ITEMSIZE_SMALL pockets.max_storage_space = 8 -/obj/item/clothing/suit/armor/vest/idris - name = "black Idris Unit coat" - desc = "A coat worn by the Idris units, notorious across space." - icon_state = "iru_coat" - item_state = "iru_coat" - cold_protection = 0 - min_cold_protection_temperature = 0 - heat_protection = 0 - max_heat_protection_temperature = 0 - -/obj/item/clothing/suit/armor/vest/idris/brown - name = "brown Idris Unit coat" - desc = "A coat worn by the Idris units, notorious across space. This one is brown." - icon_state = "iru_coat_brown" - item_state = "iru_coat_brown" - -/obj/item/clothing/suit/armor/vest/idris/trenchcoat/black - name = "black Idris Unit trench coat" - desc = "A trench coat worn by the Idris units, notorious across space. This one is black." - icon_state = "iru_trench_black" - item_state = "iru_trench_black" - -/obj/item/clothing/suit/armor/vest/idris/trenchcoat/brown - name = "brown Idris Unit trench coat" - desc = "A trench coat worn by the Idris units, notorious across space. This one is brown." - icon_state = "iru_trench_brown" - item_state = "iru_trench_brown" - -/obj/item/clothing/suit/armor/vest/idris/duster/black - name = "black Idris Unit duster coat" - desc = "A duster coat worn by the Idris units, notorious across space. This one is black." - icon_state = "iru_duster_black" - item_state = "iru_duster_black" - -/obj/item/clothing/suit/armor/vest/idris/duster/brown - name = "brown Idris Unit duster coat" - desc = "A duster coat worn by the Idris units, notorious across space. This one is brown." - icon_state = "iru_duster_brown" - item_state = "iru_duster_brown" - /obj/item/clothing/suit/storage/vest/sol name = "sol heavy armor vest" desc = "A high-quality armor vest in a deep green. It is surprisingly flexible and light, even with the added webbing and armor plating." diff --git a/code/modules/customitems/item_spawning.dm b/code/modules/customitems/item_spawning.dm index 9a5978d3e1b..9d67cc5fcb1 100644 --- a/code/modules/customitems/item_spawning.dm +++ b/code/modules/customitems/item_spawning.dm @@ -1,40 +1,194 @@ -// Switch this out to use a database at some point. Each ckey is -// associated with a list of custom item datums. When the character -// spawns, the list is checked and all appropriate datums are spawned. -// See config/example/custom_items.txt for a more detailed overview -// of how the config system works. +//This is the custom items system. +//There are two main modes of operation: database-based and file-based +//The operating mode is decided by the config.sql_enabled parameter +// +/// File +// In File mode the system loads all the custom items from the custom_items.json into the custom_items list at roundstart +// There is also a fallback mode for the DEPRECATED custom_items.txt. This fallback mode will be removed at some point. +// You SHOULD migrate to json file (manually) or the db using the automatic migration feature described in the db section +// When the equip_custom_items() proc is called for a specified mob, the custom_items list is iteraed over to determine which +// custom items belong to that mob of the player. Afterwards the items are spawned and applied to the mob (if the role matches) +// +/// Database +// In the database mode the custom items are NOT preloaded but fetched on demand based on the character id. +// If the database is empty and one of the configuration files exists the configuration file is loaded into the custom_items list. +// Afterwards a attempt is made to migrate the custom items into the database. +// Once there are entries in the ss13_characters_custom_items table no more attempts to migrate the data are made. +// To make it easier to find the round when that migration occured, the feedback variables +// custom_item_migration_success and custom_item_migration_error are set. +// +/// Removed Features +// The kits have been removed without replacement +// The icon manipulation features have been removed without replacement +// The required access has been removed without replacement +// The name/desc field have been removed and replaced with the item_data system +// This allows to modify any (string/int) variable of a existing item via the custom_item system. +// The key in the item_data list is the name of the variable, and the value is the value of the variable. +// i.e. `item_data = list("name"="asdf")` would set the name of the item to asdf when its spawned in -// CUSTOM ITEM ICONS: -// Inventory icons must be in CUSTOM_ITEM_OBJ with state name [item_icon]. -// On-mob icons must be in CUSTOM_ITEM_MOB with state name [item_icon]. -// Inhands must be in CUSTOM_ITEM_MOB as [icon_state]_l and [icon_state]_r. - -// Kits must have mech icons in CUSTOM_ITEM_OBJ under [kit_icon]. -// Broken must be [kit_icon]-broken and open must be [kit_icon]-open. - -// Kits must also have hardsuit icons in CUSTOM_ITEM_MOB as [kit_icon]_suit -// and [kit_icon]_helmet, and in CUSTOM_ITEM_OBJ as [kit_icon]. - - -//ITEM_ICONS ARE DEPRECATED. USE CONTAINED SPRITES IN FUTURE /var/list/custom_items = list() +//Loads the custom items from the json file if the db backend is disabled +/hook/pregame_start/proc/load_custom_items() + var/load_from_file = 0 + + if (config.sql_enabled) + log_debug("Custom Items: Loading from SQL") + //If we have sql enabled we check if the db is empty. If so we migrate the json file to the db + //If the db is not empty we dont load the json file + if(!establish_db_connection(dbcon)) + log_debug("Custom Items: Unable to establish database connection. - Aborting") + return 1 + + var/DBQuery/query = dbcon.NewQuery("SELECT COUNT(*) FROM ss13_characters_custom_items") + query.Execute() + + if (!query.NextRow()) + log_debug("Custom Items: Unable to fetch custom item count from database. - Aborting") + return 1 + var/item_count = text2num(query.item[1]) + if(item_count > 0) + return 1 + //If there are no items in the db, migrate them + load_from_file = 2 + log_debug("Custom Items: No items found in the database - attempting migration") + else + //If we dont have a db, we need to load the json file + load_from_file = 1 + + if(load_from_file) + log_debug("Custom Items: Loading from File") + //Check if the json file exists + if(fexists("config/custom_items.json")) + log_debug("Custom Items: Loading from json") + var/list/loaded_items = list() + var/item_id = 0 + try + loaded_items = json_decode(return_file_text("config/custom_items.json")) + catch(var/exception/e) + log_debug("Custom Items: Failed to load custom_items.json: [e]") + + for(var/item in loaded_items) + //TODO: Check for existance of the vars first + item_id += 1 + var/datum/custom_item/ci = new() + ci.id = item_id + ci.usr_ckey = item["ckey"] + ci.usr_charname = item["character_name"] + ci.item_path = text2path(item["item_path"]) + if(item["item_data"]) + ci.item_data = item["item_data"] + if(item["item_name"]) + ci.item_data["name"] = item["item_name"] + if(item["item_desc"]) + ci.item_data["desc"] = item["item_desc"] + ci.additional_data = item["additional_data"] + ci.req_titles = item["req_titles"] + custom_items.Add(ci) + log_debug("Custom Items: Loaded [length(custom_items)] custom items") + else if(fexists("config/custom_items.txt")) //TODO: Retire that at some point down the line + log_debug("Custom Items: Loading from txt") + log_and_message_admins("The deprecated custom_items.txt file is used. Migrate to SQL or JSON.") + //If we dont have the json file, we might have the old file so lets try that + var/datum/custom_item/current_data + for(var/line in text2list(file2text("config/custom_items.txt"), "\n")) + line = trim(line) + if(line == "" || !line || findtext(line, "#", 1, 2)) + continue + + if(findtext(line, "{", 1, 2) || findtext(line, "}", 1, 2)) // New block! + if(current_data && current_data.usr_ckey && current_data.usr_charname) + custom_items.Add(current_data) + current_data = null + + var/split = findtext(line,":") + if(!split) + continue + var/field = trim(copytext(line,1,split)) + var/field_data = trim(copytext(line,(split+1))) + if(!field || !field_data) + continue + + if(!current_data) + current_data = new() + + switch(field) + if("ckey") + current_data.usr_ckey = ckey(field_data) + if("character_name") + current_data.usr_charname = lowertext(field_data) + if("item_path") + current_data.item_path = text2path(field_data) + if("item_name") + current_data.item_data["name"] = field_data + if("item_icon") + continue + if("inherit_inhands") + continue + if("item_desc") + current_data.item_data["desc"] = field_data + if("req_access") + continue + if("req_titles") + current_data.req_titles = text2list(field_data,", ") + if("kit_name") + continue + if("kit_desc") + continue + if("kit_icon") + continue + if("additional_data") + current_data.additional_data = field_data + if(load_from_file == 2 && length(custom_items)) //insert the item into the db + log_debug("Custom Items: Migrating custom_items to database") + var/success_count = 0 + var/error_count = 0 + for(var/item in custom_items) + var/datum/custom_item/ci = item + log_debug("Custom Items: Migrating Item for: [ci.usr_ckey] - [ci.usr_charname]") + + if(!ci.item_path || ci.item_path == "") + log_debug("Custom Items: Invalid Item path") + error_count += 1 + continue + + //Fetch the character id of the character + var/DBQuery/char_query = dbcon.NewQuery("SELECT id FROM ss13_characters WHERE ckey = :ckey: AND name = :name: AND deleted_at IS NULL ORDER BY id DESC") + char_query.Execute(list("ckey"=ckey(ci.usr_ckey),"name"=ci.usr_charname)) + if (!char_query.NextRow()) + log_debug("Custom Items: Unable to find matching character for: ckey: [ci.usr_ckey] name: [ci.usr_charname]") + error_count += 1 + continue + var/char_id = text2num(char_query.item[1]) + + try + var/DBQuery/item_insert_query = dbcon.NewQuery("INSERT INTO ss13_characters_custom_items (`char_id`, `item_path`, `item_data`, `req_titles`, `additional_data`) VALUES (:char_id:, :item_path:, :item_data:, :req_titles:, :additional_data:)") + item_insert_query.Execute(list("char_id"=char_id,"item_path"="[ci.item_path]","item_data"=json_encode(ci.item_data),"req_titles"=json_encode(ci.req_titles),"additional_data"=ci.additional_data)) + catch(var/exception/e) + log_debug("Custom Items: Failed to save item to db: [e]") + error_count += 1 + success_count += 1 + + feedback_set("custom_item_migration_success",success_count) + feedback_set("custom_item_migration_error",error_count) + return 1 + /datum/custom_item - var/assoc_key - var/character_name - var/inherit_inhands = 1 //if unset, and inhands are not provided, then the inhand overlays will be invisible. - var/item_icon - var/item_desc - var/name - var/item_path = /obj/item - var/req_access = 0 + var/id + //the character_id is used with the db setup + var/character_id + //the char_name/ckey is used with the disk based setup (and auto-generated from the char id for the db based setup) + var/usr_ckey + var/usr_charname + + var/item_path + var/list/item_data = list() + var/list/req_titles = list() - var/kit_name - var/kit_desc - var/kit_icon + var/additional_data -/datum/custom_item/proc/spawn_item(var/newloc) +/datum/custom_item/proc/spawn_item(var/newloc) //TODO: pass mob its spawned for as parameter var/obj/item/citem = new item_path(newloc) apply_to_item(citem) return citem @@ -42,207 +196,90 @@ /datum/custom_item/proc/apply_to_item(var/obj/item/item) if(!item) return - if(name) - item.name = name - if(item_desc) - item.desc = item_desc - if(item_icon) - if(!istype(item)) - item.icon = CUSTOM_ITEM_OBJ - item.icon_state = item_icon - return - else - if(inherit_inhands) - apply_inherit_inhands(item) - else - item.item_state_slots = null - item.item_icons = null - item.icon = CUSTOM_ITEM_OBJ - item.icon_state = item_icon - item.item_state = null - item.icon_override = CUSTOM_ITEM_MOB - - var/obj/item/clothing/under/U = item - if(istype(U)) - U.worn_state = U.icon_state - U.update_rolldown_status() - - // Kits are dumb so this is going to have to be hardcoded/snowflake. - if(istype(item, /obj/item/device/kit)) - var/obj/item/device/kit/K = item - K.new_name = kit_name - K.new_desc = kit_desc - K.new_icon = kit_icon - K.new_icon_file = CUSTOM_ITEM_OBJ - if(istype(item, /obj/item/device/kit/suit)) - var/obj/item/device/kit/suit/kit = item - kit.new_light_overlay = additional_data - kit.new_mob_icon_file = CUSTOM_ITEM_MOB + //Customize the item with the item_data + for(var/var_name in item_data) + try + item.vars[var_name] = item_data[var_name] + catch(var/exception/e) + log_debug("Custom Item: Bad variable name [var_name] in custom item with id [id]: [e]") // for snowflake implants - else if(istype(item, /obj/item/implanter/fluff)) + if(istype(item, /obj/item/implanter/fluff)) var/obj/item/implanter/fluff/L = item - L.allowed_ckey = assoc_key + L.allowed_ckey = usr_ckey L.implant_type = text2path(additional_data) L.create_implant() return item -/datum/custom_item/proc/apply_inherit_inhands(var/obj/item/item) - var/list/new_item_icons = list() - var/list/new_item_state_slots = list() - - var/list/available_states = icon_states(CUSTOM_ITEM_MOB) - - //If l_hand or r_hand are not present, preserve them using item_icons/item_state_slots - //Then use icon_override to make every other slot use the custom sprites by default. - //This has to be done before we touch any of item's vars - if(!("[item_icon]_l" in available_states)) - new_item_state_slots[slot_l_hand_str] = get_state(item, slot_l_hand_str, "_l") - new_item_icons[slot_l_hand_str] = get_icon(item, slot_l_hand_str, 'icons/mob/items/lefthand.dmi') - if(!("[item_icon]_r" in available_states)) - new_item_state_slots[slot_r_hand_str] = get_state(item, slot_r_hand_str, "_r") - new_item_icons[slot_r_hand_str] = get_icon(item, slot_r_hand_str, 'icons/mob/items/righthand.dmi') - - item.item_state_slots = new_item_state_slots - item.item_icons = new_item_icons - -//this has to mirror the way update_inv_*_hand() selects the state -/datum/custom_item/proc/get_state(var/obj/item/item, var/slot_str, var/hand_str) - var/t_state - if(item.item_state_slots && item.item_state_slots[slot_str]) - t_state = item.item_state_slots[slot_str] - else if(item.item_state) - t_state = item.item_state - else - t_state = item.icon_state - if(item.icon_override) - t_state += hand_str - return t_state - -//this has to mirror the way update_inv_*_hand() selects the icon -/datum/custom_item/proc/get_icon(var/obj/item/item, var/slot_str, var/icon/hand_icon) - var/icon/t_icon - if(item.icon_override) - t_icon = item.icon_override - else if(item.item_icons && (slot_str in item.item_icons)) - t_icon = item.item_icons[slot_str] - else - t_icon = hand_icon - return t_icon - -// Parses the config file into the custom_items list. -/hook/startup/proc/load_custom_items() - - var/datum/custom_item/current_data - for(var/line in text2list(file2text("config/custom_items.txt"), "\n")) - - line = trim(line) - if(line == "" || !line || findtext(line, "#", 1, 2)) - continue - - if(findtext(line, "{", 1, 2) || findtext(line, "}", 1, 2)) // New block! - if(current_data && current_data.assoc_key) - if(!custom_items[current_data.assoc_key]) - custom_items[current_data.assoc_key] = list() - var/list/L = custom_items[current_data.assoc_key] - L |= current_data - current_data = null - - var/split = findtext(line,":") - if(!split) - continue - var/field = trim(copytext(line,1,split)) - var/field_data = trim(copytext(line,(split+1))) - if(!field || !field_data) - continue - - if(!current_data) - current_data = new() - - switch(field) - if("ckey") - current_data.assoc_key = lowertext(field_data) - if("character_name") - current_data.character_name = lowertext(field_data) - if("item_path") - current_data.item_path = text2path(field_data) - if("item_name") - current_data.name = field_data - if("item_icon") - current_data.item_icon = field_data - if("inherit_inhands") - current_data.inherit_inhands = text2num(field_data) - if("item_desc") - current_data.item_desc = field_data - if("req_access") - current_data.req_access = text2num(field_data) - if("req_titles") - current_data.req_titles = text2list(field_data,", ") - if("kit_name") - current_data.kit_name = field_data - if("kit_desc") - current_data.kit_desc = field_data - if("kit_icon") - current_data.kit_icon = field_data - if("additional_data") - current_data.additional_data = field_data - return 1 - //gets the relevant list for the key from the listlist if it exists, check to make sure they are meant to have it and then calls the giving function -/proc/equip_custom_items(mob/living/carbon/human/M) - var/list/key_list = custom_items[M.ckey] - if(!key_list || key_list.len < 1) - return +/proc/equip_custom_items(var/mob/living/carbon/human/M) + //Fetch the custom items for the mob + if(config.sql_enabled) + if(!establish_db_connection(dbcon)) + log_debug("Custom Items: Unable to establish database connection while loading item. - Aborting") + return - for(var/datum/custom_item/citem in key_list) + var/DBQuery/char_item_query = dbcon.NewQuery("SELECT ss13_characters_custom_items.id, ss13_characters_custom_items.char_id, ss13_characters.ckey as usr_ckey, ss13_characters.name as usr_charname, item_path, item_data, req_titles, additional_data FROM ss13_characters_custom_items LEFT JOIN ss13_characters ON ss13_characters.id = ss13_characters_custom_items.char_id WHERE char_id = :char_id:") + char_item_query.Execute(list("char_id"=M.character_id)) + while(char_item_query.NextRow()) + CHECK_TICK + var/datum/custom_item/ci = new() + ci.id = text2num(char_item_query.item[1]) + ci.character_id = text2num(char_item_query.item[2]) + ci.usr_ckey = char_item_query.item[3] + ci.usr_charname = char_item_query.item[4] + ci.item_path = text2path(char_item_query.item[5]) + ci.item_data = json_decode(char_item_query.item[6]) //TODO: try/catch + ci.req_titles = json_decode(char_item_query.item[7]) //TODO: try/catch + ci.additional_data = char_item_query.item[8] - // Check for requisite ckey and character name. - if((lowertext(citem.assoc_key) != lowertext(M.ckey)) || (lowertext(citem.character_name) != lowertext(M.real_name))) - continue - - // Check for required access. - var/obj/item/I = M.wear_id - if(citem.req_access && citem.req_access > 0) - if(!(istype(I) && (citem.req_access in I.GetAccess()))) + equip_custom_item_to_mob(ci,M) + else + for(var/item in custom_items) + CHECK_TICK + var/datum/custom_item/ci = item + if(lowertext(ci.usr_ckey) != lowertext(M.ckey)) continue - - // Check for required job title. - if(citem.req_titles && citem.req_titles.len > 0) - var/has_title - var/current_title = M.mind.role_alt_title ? M.mind.role_alt_title : M.mind.assigned_role - for(var/title in citem.req_titles) - if(title == current_title) - has_title = 1 - break - if(!has_title) + if(lowertext(ci.usr_charname) != lowertext(M.real_name)) continue + equip_custom_item_to_mob(ci,M) - // ID cards and MCs are applied directly to the existing object rather than spawned fresh. - var/obj/item/existing_item - if(citem.item_path == /obj/item/card/id) - existing_item = locate(/obj/item/card/id) in M.get_contents() //TODO: Improve this ? - else if(citem.item_path == /obj/item/modular_computer) - existing_item = locate(/obj/item/modular_computer) in M.contents - // Spawn and equip the item. - if(existing_item) - citem.apply_to_item(existing_item) - else - place_custom_item(M,citem) +/proc/equip_custom_item_to_mob(var/datum/custom_item/citem, var/mob/living/carbon/human/M) + // Check for required job title. + if(citem.req_titles && length(citem.req_titles) > 0) + var/has_title + var/current_title = M.mind.role_alt_title ? M.mind.role_alt_title : M.mind.assigned_role + for(var/title in citem.req_titles) + if(title == current_title) + has_title = 1 + break + if(!has_title) + to_chat(M, "A custom item could not be equipped as you have joined with the wrong role.") + return FALSE -// Places the item on the target mob. -/proc/place_custom_item(mob/living/carbon/human/M, var/datum/custom_item/citem) + // ID cards and MCs are applied directly to the existing object rather than spawned fresh. + var/obj/item/existing_item + if(citem.item_path == /obj/item/card/id) + existing_item = locate(/obj/item/card/id) in M.get_contents() //TODO: Improve this ? + else if(citem.item_path == /obj/item/modular_computer) + existing_item = locate(/obj/item/modular_computer) in M.contents - if(!citem) return - var/obj/item/newitem = citem.spawn_item() + // Spawn and equip the item. + if(existing_item) + citem.apply_to_item(existing_item) + return TRUE + else + var/obj/item/newitem = citem.spawn_item() - if(M.equip_to_appropriate_slot(newitem)) - return newitem + if(M.equip_to_appropriate_slot(newitem)) + return TRUE - if(M.equip_to_storage(newitem)) - return newitem + if(M.equip_to_storage(newitem)) + return TRUE - newitem.forceMove(get_turf(M.loc)) - return newitem + newitem.forceMove(get_turf(M.loc)) + to_chat(M, "A custom item has been placed on the floor as there was no space for it on your mob.") + return TRUE diff --git a/code/modules/economy/cash.dm b/code/modules/economy/cash.dm index 01866af9762..f5e7af17f2e 100644 --- a/code/modules/economy/cash.dm +++ b/code/modules/economy/cash.dm @@ -168,7 +168,7 @@ proc/spawn_money(var/sum, spawnloc, mob/living/carbon/human/human_user as mob) return /obj/item/spacecash/ewallet - name = "Charge card" + name = "charge card" icon_state = "efundcard" desc = "A card that holds an amount of money." var/owner_name = "" //So the ATM can set it so the EFTPOS can put a valid name on transactions. diff --git a/code/modules/heavy_vehicle/interface/screen_objects.dm b/code/modules/heavy_vehicle/interface/screen_objects.dm index b76d3c2fc43..89a8ad697ad 100644 --- a/code/modules/heavy_vehicle/interface/screen_objects.dm +++ b/code/modules/heavy_vehicle/interface/screen_objects.dm @@ -6,6 +6,10 @@ var/mob/living/heavy_vehicle/owner maptext_y = 11 +/obj/screen/mecha/proc/notify_user(var/mob/user, var/text) + if(user.loc == owner) + to_chat(user, text) + /obj/screen/mecha/radio name = "radio" icon_state = "base" @@ -136,12 +140,12 @@ var/modifiers = params2list(params) if(modifiers["ctrl"]) if(owner.hardpoints_locked) - to_chat(usr, "Hardpoint ejection system is locked.") + notify_user(usr, SPAN_WARNING("The hardpoint ejection system is locked.")) return if(owner.remove_system(hardpoint_tag)) - to_chat(usr, "You disengage and discard the system mounted to your [hardpoint_tag] hardpoint.") + notify_user(usr, SPAN_NOTICE("You disengage and discard the system mounted to your [hardpoint_tag] hardpoint.")) else - to_chat(usr, "You fail to remove the system mounted to your [hardpoint_tag] hardpoint.") + notify_user(usr, SPAN_DANGER("You fail to remove the system mounted to your [hardpoint_tag] hardpoint.")) return if(owner.selected_hardpoint == hardpoint_tag) @@ -203,7 +207,7 @@ owner.use_air = toggled var/main_color = owner.use_air ? "#d1d1d1" : "#525252" maptext = "AIR" - to_chat(usr, "Auxiliary atmospheric system [owner.use_air ? "enabled" : "disabled"].") + notify_user(usr, SPAN_NOTICE("Auxiliary atmospheric system [owner.use_air ? "enabled" : "disabled"].")) /obj/screen/mecha/toggle/maint name = "toggle maintenance protocol" @@ -217,7 +221,7 @@ owner.maintenance_protocols = toggled var/main_color = owner.maintenance_protocols ? "#d1d1d1" : "#525252" maptext = "MAINT" - to_chat(usr, "Maintenance protocols [owner.maintenance_protocols ? "enabled" : "disabled"].") + notify_user(usr, SPAN_NOTICE("Maintenance protocols [owner.maintenance_protocols ? "enabled" : "disabled"].")) /obj/screen/mecha/toggle/hardpoint name = "toggle hardpoint lock" @@ -227,13 +231,13 @@ /obj/screen/mecha/toggle/hardpoint/toggled() if(owner.force_locked) - to_chat(usr, "The locking system cannot be operated due to software restriction. Contact the manufacturer for more details.") + notify_user(usr, SPAN_WARNING("The locking system cannot be operated due to software restriction. Contact the manufacturer for more details.")) return toggled = !toggled owner.hardpoints_locked = toggled var/main_color = owner.hardpoints_locked ? "#d1d1d1" : "#525252" maptext = "GEAR" - to_chat(usr, "Hardpoint system access is now [owner.hardpoints_locked ? "disabled" : "enabled"].") + notify_user(usr, SPAN_NOTICE("Hardpoint system access is now [owner.hardpoints_locked ? "disabled" : "enabled"].")) /obj/screen/mecha/toggle/hatch name = "toggle hatch lock" @@ -243,10 +247,10 @@ /obj/screen/mecha/toggle/hatch/toggled() if(!owner.hatch_locked && !owner.hatch_closed) - to_chat(usr, "You cannot lock the hatch while it is open.") + notify_user(usr, SPAN_WARNING("You cannot lock the hatch while it is open.")) return if(owner.force_locked) - to_chat(usr, "The locking system cannot be operated due to software restriction. Contact the manufacturer for more details.") + notify_user(usr, SPAN_WARNING("The locking system cannot be operated due to software restriction. Contact the manufacturer for more details.")) return toggled = !toggled owner.hatch_locked = toggled @@ -258,7 +262,7 @@ maptext = "LOCK" maptext_y = 11 maptext_x = 5 - to_chat(usr, "The [owner.body.hatch_descriptor] is [owner.hatch_locked ? "now" : "no longer" ] locked.") + notify_user(usr, SPAN_NOTICE("The [owner.body.hatch_descriptor] is [owner.hatch_locked ? "now" : "no longer" ] locked.")) /obj/screen/mecha/toggle/hatch_open name = "open or close hatch" @@ -268,13 +272,13 @@ /obj/screen/mecha/toggle/hatch_open/toggled() if(owner.hatch_locked && owner.hatch_closed) - to_chat(usr, "You cannot open the hatch while it is locked.") + notify_user(usr, SPAN_WARNING("You cannot open the hatch while it is locked.")) return toggled = !toggled owner.hatch_closed = toggled maptext = "[owner.hatch_closed ? "OPEN" : "CLOSE"]" maptext_x = owner.hatch_closed ? 4 : 3 - to_chat(usr, "The [owner.body.hatch_descriptor] is now [owner.hatch_closed ? "closed" : "open" ].") + notify_user(usr, SPAN_NOTICE("The [owner.body.hatch_descriptor] is now [owner.hatch_closed ? "closed" : "open" ].")) owner.update_icon() // This is basically just a holder for the updates the mech does. @@ -291,15 +295,15 @@ /obj/screen/mecha/toggle/sensor/toggled() if(!owner.head) - to_chat(usr, "I/O Error: Sensor systems not found.") + notify_user(usr, SPAN_WARNING("I/O Error: Sensor systems not found.")) return if(!owner.head.vision_flags) - to_chat(usr, "\The [owner.head] does not have any special sensor configurations.") + notify_user(usr, SPAN_WARNING("\The [owner.head] does not have any special sensor configurations.")) return toggled = !toggled owner.head.active_sensors = toggled var/main_color = owner.head.active_sensors ? "#d1d1d1" : "#525252" maptext = "SENSOR" - to_chat(usr, "[capitalize_first_letters(owner.head.name)] Advanced Sensor mode is [owner.head.active_sensors ? "now" : "no longer" ] active.") + notify_user(usr, SPAN_NOTICE("[capitalize_first_letters(owner.head.name)] Advanced Sensor mode is [owner.head.active_sensors ? "now" : "no longer" ] active.")) -#undef BAR_CAP +#undef BAR_CAP \ No newline at end of file diff --git a/code/modules/heavy_vehicle/mech_helpers.dm b/code/modules/heavy_vehicle/mech_helpers.dm new file mode 100644 index 00000000000..deb71d6aa0c --- /dev/null +++ b/code/modules/heavy_vehicle/mech_helpers.dm @@ -0,0 +1,74 @@ +/mob/living/heavy_vehicle/proc/can_move(var/mob/user) + . = 0 + if(world.time < next_mecha_move) + return + + if(incapacitated() || (user && user.incapacitated()) || lockdown) + return + + if(!legs) + if(user) + to_chat(user, "\The [src] has no means of propulsion!") + next_mecha_move = world.time + 3 // Just to stop them from getting spammed with messages. + return + + if(!legs.motivator || legs.total_damage > 45) + if(user) + to_chat(user, "Your motivators are damaged! You can't move!") + next_mecha_move = world.time + 15 + return + + next_mecha_move = world.time + legs.move_delay + + if(maintenance_protocols) + if(user) + to_chat(user, "Maintenance protocols are in effect.") + return + + var/obj/item/cell/C = get_cell() + if(!C || !C.check_charge(legs.power_use * CELLRATE)) + if(user) + to_chat(user, "The power indicator flashes briefly.") + return + + return TRUE + +/mob/living/heavy_vehicle/proc/toggle_maintenance_protocols() + var/obj/screen/mecha/toggle/maint/M = locate() in hud_elements + M.toggled() + return TRUE + +/mob/living/heavy_vehicle/proc/toggle_hatch() + var/obj/screen/mecha/toggle/hatch_open/H = locate() in hud_elements + H.toggled() + return TRUE + +/mob/living/heavy_vehicle/proc/toggle_lock() + var/obj/screen/mecha/toggle/hatch/L = locate() in hud_elements + L.toggled() + return TRUE + +/mob/living/heavy_vehicle/proc/can_listen() + return TRUE + +/mob/living/heavy_vehicle/proc/assign_leader(var/mob/living/carbon/human/H) + leader_name = H.name + leader = WEAKREF(H) + +/mob/living/heavy_vehicle/proc/unassign_leader() + leader = null + leader_name = null + +/mob/living/heavy_vehicle/proc/assign_following(var/mob/living/carbon/human/H) + following_name = H.name + following = WEAKREF(H) + +/mob/living/heavy_vehicle/proc/unassign_following() + following = null + following_name = null + +/mob/living/heavy_vehicle/proc/prepare_nickname(var/text) + text = replacemany(text, list("\"" = "", "." = "", "," = "")) + text = trim_left(text) + text = trim_right(text) + return text \ No newline at end of file diff --git a/code/modules/heavy_vehicle/mech_interaction.dm b/code/modules/heavy_vehicle/mech_interaction.dm index 21e84e03f27..c4474a7ab68 100644 --- a/code/modules/heavy_vehicle/mech_interaction.dm +++ b/code/modules/heavy_vehicle/mech_interaction.dm @@ -238,6 +238,7 @@ if(user.client) user.client.screen |= hud_elements LAZYDISTINCTADD(user.additional_vision_handlers, src) update_icon() + walk(src, 0) // stop it from auto moving when the pilot gets in return 1 /mob/living/heavy_vehicle/proc/sync_access() @@ -275,31 +276,7 @@ UNSETEMPTY(pilots) /mob/living/heavy_vehicle/relaymove(var/mob/living/user, var/direction) - if(world.time < next_mecha_move) - return 0 - - if(!user || incapacitated() || user.incapacitated() || lockdown) - return - - if(!legs) - to_chat(user, "\The [src] has no means of propulsion!") - next_mecha_move = world.time + 3 // Just to stop them from getting spammed with messages. - return - - if(!legs.motivator || legs.total_damage > 45) - to_chat(user, "Your motivators are damaged! You can't move!") - next_mecha_move = world.time + 15 - return - - next_mecha_move = world.time + legs.move_delay - - if(maintenance_protocols) - to_chat(user, "Maintenance protocols are in effect.") - return - - var/obj/item/cell/C = get_cell() - if(!C || !C.check_charge(legs.power_use * CELLRATE)) - to_chat(user, "The power indicator flashes briefly.") + if(!can_move(user)) return if(hallucination >= EMP_MOVE_DISRUPT && prob(30)) @@ -565,3 +542,154 @@ src.visible_message("\The [src] beeps loudly as its servos sieze up, and it enters lockdown mode!") else src.visible_message("\The [src] hums with life as it is released from its lockdown mode!") + +/mob/living/heavy_vehicle/get_floating_chat_x_offset() + return 8 + +/mob/living/heavy_vehicle/hear_say(var/message, var/verb = "says", var/datum/language/language = null, var/alt_name = "", var/italics = 0, var/mob/speaker = null, var/sound/speech_sound, var/sound_vol) + if(can_listen()) + addtimer(CALLBACK(src, .proc/handle_hear_say, speaker, message), 0.5 SECONDS) + return ..() + +// heavily commented so it doesn't look like one fat chunk of code, which it still does - Geeves +/mob/living/heavy_vehicle/proc/handle_hear_say(var/mob/speaker, var/text) + var/found_text = findtext(text, name) + if(!found_text) + found_text = findtext(text, nickname) + if(found_text) + text = copytext(text, found_text) // I'm trimming the text each time so only information stated after eachother is valid + + // a quick way to figure out the remote control status of the mech + if(findtext(text, "report diagnostics")) + var/has_leader = FALSE + if(leader) + var/mob/resolved_leader = leader.resolve() + if(!resolved_leader) + say("Error, leader not found. Unassigning...") + unassign_leader() + return + has_leader = TRUE + say("Currently [has_leader ? "paired with [leader_name]" : "unpaired"].") + if(following) + var/mob/resolved_following = following.resolve() + if(!resolved_following) + say("Error, follow target not found. Unassigning...") + unassign_following() + else + say("Currently following [resolved_following.name].") + if(nickname) + say("Nickname set to [nickname].") + say("Maintenance protocols, [maintenance_protocols ? "active" : "disabled"].") + return + + // Checking whether we have a leader or not + if(!leader) + if(!maintenance_protocols) // don't select a leader unless we have maintenance protocols set + return + // If we have no leader, we listen to the keywords 'listen to' + if(findtext(text, "listen to")) + text = copytext(text, found_text) + found_text = findtext(text, "me") // if they say listen to me, we listen to them + if(found_text) + assign_leader(speaker) + say("New paired leader, [leader_name], confirmed and added to temporary biometric database.") + return + // check for humans and their IDs + for(var/mob/living/carbon/human/H in view(world.view, src)) + var/obj/item/card/id/ID = H.GetIdCard(TRUE) + if(ID?.registered_name) // we ID people based on their... ID + if(findtext(text, ID.registered_name)) + assign_leader(H) + say("New paired leader, [ID.registered_name], confirmed and added to temporary biometric database.") + break + return + else + var/mob/resolved_leader = leader.resolve() + if(!resolved_leader) + say("Error, leader not found. Unassigning...") + unassign_leader() + return + if(speaker != resolved_leader || (speaker in pilots)) + return + + found_text = findtext(text, "set nickname to") + if(found_text) + text = copytext(text, found_text + 15) + text = prepare_nickname(text) + if(lowertext(text) == "null") + nickname = null + say("Nickname removed.") + else + nickname = text + say("Nickname set to [text].") + return + + // simply toggle maintenance protocols + if(findtext(text, "toggle maintenance protocols")) + if(toggle_maintenance_protocols()) + say("Maintenance protocols toggled [maintenance_protocols ? "on" : "off"].") + return + + // simply open or close the hatch + if(findtext(text, "toggle hatch")) + if(hatch_locked || force_locked) + say("Hatch locked, cannot toggle status.") + return + if(toggle_hatch()) + say("Hatch [hatch_closed ? "closed" : "opened"].") + return + + // simply toggle the lock status + if(findtext(text, "toggle lock")) + if(!hatch_closed) + say("Hatch lock cannot be toggled while the hatch is open.") + return + if(force_locked) + say("Hatch lock forced on, cannot override.") + return + if(toggle_lock()) + say("Hatch [hatch_locked ? "locked" : "unlocked"].") + return + + // unlink the leader to get a new one + if(findtext(text, "unlink")) + unassign_leader() + say("Leader dropped, awaiting new leader.") + return + + // stop following who you were assigned to follow + if(findtext(text, "stop")) + unassign_following() + walk(src, 0) + say("Holding position.") + return + + // set a follow range for the mecha, one to three, at which point it stops approaching + found_text = findtext(text, "follow range") + if(found_text) + text = copytext(text, found_text) + var/list/follow_range = list("one", "two", "three") + for(var/i = 1 to length(follow_range)) + if(findtext(text, follow_range[i])) + say("Follow range set to [follow_range[i]] units.") + follow_distance = i + break + return + + // set who it has to follow, broken into two steps to make it more versatile + found_text = findtext(text, "follow") + if(found_text) + text = copytext(text, found_text) + found_text = findtext(text, "me") + if(found_text) + assign_following(speaker) + say("Following [speaker.name].") + return + for(var/mob/living/carbon/human/H in view(world.view, src)) + var/obj/item/card/id/ID = H.GetIdCard(TRUE) + if(ID?.registered_name) // we ID people based on their... ID + if(findtext(text, ID.registered_name)) + assign_following(H) + say("Following [ID.registered_name].") + break + return \ No newline at end of file diff --git a/code/modules/heavy_vehicle/mech_life.dm b/code/modules/heavy_vehicle/mech_life.dm index 975ee16c16c..e7e22b28e4f 100644 --- a/code/modules/heavy_vehicle/mech_life.dm +++ b/code/modules/heavy_vehicle/mech_life.dm @@ -41,6 +41,25 @@ handle_vision() handle_hud_icons() +/mob/living/heavy_vehicle/think() + var/mob/resolved_following + if(following) + resolved_following = following.resolve() + + if(length(pilots)) + if(resolved_following && !(resolved_following in pilots)) // if the person we're following is our pilot, we keep following them, otherwise we drop them + unassign_following() + return + + if(following) + if(isturf(loc) && can_move()) + if(resolved_following) + walk_to(src, resolved_following, follow_distance, legs.move_delay) + else + unassign_following() + else + walk(src, 0) // this stops them from moving + /mob/living/heavy_vehicle/get_cell() RETURN_TYPE(/obj/item/cell) return body?.cell diff --git a/code/modules/heavy_vehicle/mecha.dm b/code/modules/heavy_vehicle/mecha.dm index c2dc0a19cac..f2f753a36b9 100644 --- a/code/modules/heavy_vehicle/mecha.dm +++ b/code/modules/heavy_vehicle/mecha.dm @@ -9,6 +9,7 @@ mob_size = MOB_LARGE mob_push_flags = ALLMOBS can_buckle = FALSE + accent = ACCENT_TTS var/decal var/emp_damage = 0 @@ -27,6 +28,16 @@ var/list/saved_access = list() var/sync_access = TRUE + // Mob we're currently paired with or following | the names are saved to prevent metagaming when returning diagnostics + var/datum/weakref/leader + var/leader_name + var/datum/weakref/following + var/following_name + + // Orders from our leader + var/nickname // we'll respond to our name or our nickname + var/follow_distance = 3 + // Mob currently piloting the mech. var/list/pilots var/list/pilot_overlays @@ -75,6 +86,8 @@ var/obj/screen/mecha/power/hud_power /mob/living/heavy_vehicle/Destroy() + unassign_leader() + unassign_following() selected_system = null @@ -199,6 +212,9 @@ // Build icon. update_icon() + add_language(LANGUAGE_TCB) + set_default_language(LANGUAGE_TCB) + . = INITIALIZE_HINT_LATELOAD /mob/living/heavy_vehicle/LateInitialize() diff --git a/code/modules/hydroponics/grown_inedible.dm b/code/modules/hydroponics/grown_inedible.dm index f11d960b309..cd680fdc80e 100644 --- a/code/modules/hydroponics/grown_inedible.dm +++ b/code/modules/hydroponics/grown_inedible.dm @@ -70,6 +70,10 @@ throw_range = 20 /obj/item/bananapeel/Crossed(AM as mob|obj) - if (istype(AM, /mob/living)) + if(isliving(AM)) + if(ishuman(AM)) + var/mob/living/carbon/human/H = AM + if(H.shoes?.item_flags & LIGHTSTEP) + return var/mob/living/M = AM - M.slip("the [src.name]",4) + M.slip("the [src.name]",4) \ No newline at end of file diff --git a/code/modules/mining/machine_processing.dm b/code/modules/mining/machine_processing.dm index 83b9fe9b0ee..9eb09918e6e 100644 --- a/code/modules/mining/machine_processing.dm +++ b/code/modules/mining/machine_processing.dm @@ -15,7 +15,7 @@ var/obj/machinery/mineral/processing_unit/machine var/show_all_ores = FALSE var/points = 0 - var/obj/item/card/id/inserted_id + var/datum/weakref/scanned_id var/list/ore/input_mats = list() var/list/material/output_mats = list() @@ -46,17 +46,25 @@ /obj/machinery/mineral/processing_unit_console/attack_hand(mob/user) add_fingerprint(user) + if(!scanned_id) + get_user_id(user) + else + var/obj/item/card/id/ID = scanned_id.resolve() + if(!ID) + scanned_id = null + get_user_id(user) + else + var/turf/id_turf = get_turf(ID) + if(!id_turf.Adjacent(loc)) + scanned_id = null + get_user_id(user) interact(user) -/obj/machinery/mineral/processing_unit_console/attackby(obj/item/I, mob/user) - if(istype(I,/obj/item/card/id)) - var/obj/item/card/id/C = user.get_active_hand() - if(istype(C) && !istype(inserted_id)) - user.drop_from_inventory(C, src) - inserted_id = C - interact(user) - else - ..() +/obj/machinery/mineral/processing_unit_console/proc/get_user_id(var/mob/user) + if(!scanned_id && !isDrone(user)) + var/obj/item/card/id/ID = user.GetIdCard() + if(ID) + scanned_id = WEAKREF(ID) /obj/machinery/mineral/processing_unit_console/interact(mob/user) if(..()) @@ -75,8 +83,9 @@ dat += "Current unclaimed points: [points]
" - if(istype(inserted_id)) - dat += "You have [inserted_id.mining_points] mining points collected. Eject ID.
" + var/obj/item/card/id/ID = scanned_id.resolve() + if(ID) + dat += "You have [ID.mining_points] mining points collected. Eject ID.
" dat += "Claim points.
" dat += "Print yield declaration.
" else @@ -121,15 +130,14 @@ src.add_fingerprint(usr) if(href_list["choice"]) - if(istype(inserted_id)) + var/obj/item/card/id/ID = scanned_id.resolve() + if(ID) if(href_list["choice"] == "eject") - inserted_id.forceMove(loc) - usr.put_in_hands(inserted_id) - inserted_id = null + scanned_id = null if(href_list["choice"] == "claim") - if(access_mining_station in inserted_id.access) + if(access_mining_station in ID.access) if(points >= 0) - inserted_id.mining_points += points + ID.mining_points += points if(points != 0) ping("\The [src] pings, \"Point transfer complete! Transaction total: [points] points!\"") points = 0 @@ -138,7 +146,7 @@ else to_chat(usr, SPAN_WARNING("Required access not found.")) if(href_list["choice"] == "print_report") - if(access_mining_station in inserted_id.access) + if(access_mining_station in ID.access) print_report(usr) else to_chat(usr, SPAN_WARNING("Required access not found.")) @@ -146,10 +154,7 @@ else if(href_list["choice"] == "insert") var/obj/item/card/id/I = usr.get_active_hand() if(istype(I)) - usr.drop_from_inventory(I,src) - inserted_id = I - else - to_chat(usr, SPAN_WARNING("No valid ID.")) + scanned_id = WEAKREF(I) if(href_list["toggle_smelting"]) var/choice = input("What setting do you wish to use for processing [href_list["toggle_smelting"]]?") as null|anything in list("Smelting","Compressing","Alloying","Nothing") @@ -183,7 +188,8 @@ return /obj/machinery/mineral/processing_unit_console/proc/print_report(var/mob/living/user) - if(!inserted_id) + var/obj/item/card/id/ID = scanned_id.resolve() + if(!ID) to_chat(user, SPAN_WARNING("No ID inserted. Cannot digitally sign.")) return if(!input_mats.len && !output_mats.len && !alloy_mats) @@ -270,8 +276,7 @@ input_mats = list() waste = 0 - if(ishuman(user) && !(user.l_hand && user.r_hand)) - user.put_in_hands(P) + user.put_in_hands(P) printing = FALSE return diff --git a/code/modules/mining/machine_vending.dm b/code/modules/mining/machine_vending.dm index e3c53f1b0bb..e4c1110ee25 100644 --- a/code/modules/mining/machine_vending.dm +++ b/code/modules/mining/machine_vending.dm @@ -66,7 +66,7 @@ var/global/list/minevendor_list = list( //keep in order of price icon_state = "mining" density = TRUE anchored = TRUE - var/obj/item/card/id/inserted_id + var/datum/weakref/scanned_id /datum/data/mining_equipment var/equipment_name = "generic" @@ -103,13 +103,32 @@ var/global/list/minevendor_list = list( //keep in order of price /obj/machinery/mineral/equipment_vendor/attack_hand(mob/user) if(..()) return + if(!scanned_id) + get_user_id(user) + else + var/obj/item/card/id/ID = scanned_id.resolve() + if(!ID) + scanned_id = null + get_user_id(user) + else + var/turf/id_turf = get_turf(ID) + if(!id_turf.Adjacent(loc)) + scanned_id = null + get_user_id(user) interact(user) +/obj/machinery/mineral/equipment_vendor/proc/get_user_id(var/mob/user) + if(!scanned_id && !isDrone(user)) + var/obj/item/card/id/ID = user.GetIdCard() + if(ID) + scanned_id = WEAKREF(ID) + /obj/machinery/mineral/equipment_vendor/interact(mob/user) var/dat dat +="
" - if(istype(inserted_id)) - dat += "You have [inserted_id.mining_points ? inserted_id.mining_points : 0] mining points collected. Eject ID.
" + var/obj/item/card/id/ID = scanned_id.resolve() + if(ID) + dat += "You have [ID.mining_points ? ID.mining_points : 0] mining points collected. Eject ID.
" else dat += "No ID inserted. Insert ID.
" dat += "
" @@ -132,30 +151,23 @@ var/global/list/minevendor_list = list( //keep in order of price if(..()) return if(href_list["choice"]) - if(istype(inserted_id)) + var/obj/item/card/id/ID = scanned_id.resolve() + if(ID) if(href_list["choice"] == "eject") - inserted_id.forceMove(loc) - if(ishuman(usr)) - if(!usr.get_active_hand()) - usr.put_in_hands(inserted_id) - else - inserted_id.forceMove(get_turf(src)) - inserted_id = null + scanned_id = null else if(href_list["choice"] == "insert") var/obj/item/card/id/I = usr.get_active_hand() if(istype(I)) - usr.drop_from_inventory(I,src) - inserted_id = I - else - to_chat(usr, SPAN_DANGER("No valid ID.")) + scanned_id = WEAKREF(I) if(href_list["purchase"]) - if(istype(inserted_id)) + var/obj/item/card/id/ID = scanned_id.resolve() + if(ID) var/datum/data/mining_equipment/prize = locate(href_list["purchase"]) if(!prize || !(prize in minevendor_list)) return if(prize.amount <= 0 && prize.amount != -1) return - if(prize.cost > inserted_id.mining_points) + if(prize.cost > ID.mining_points) else if(prize.shuttle) var/datum/shuttle/autodock/ferry/supply/shuttle = SScargo.shuttle @@ -185,7 +197,7 @@ var/global/list/minevendor_list = list( //keep in order of price var/turf/pickedloc = clear_turfs[i] if(pickedloc) - inserted_id.mining_points -= prize.cost + ID.mining_points -= prize.cost new prize.equipment_path(pickedloc) to_chat(usr, SPAN_NOTICE("Order passed. Your order has been placed on the next available supply shuttle.")) else @@ -195,7 +207,7 @@ var/global/list/minevendor_list = list( //keep in order of price to_chat(usr, SPAN_DANGER("{ERR Code: NO_SHUTTLE} Order failed! Please try again.")) return else - inserted_id.mining_points -= prize.cost + ID.mining_points -= prize.cost if(prize.amount != -1) prize.amount-- new prize.equipment_path(get_turf(src)) @@ -219,13 +231,6 @@ var/global/list/minevendor_list = list( //keep in order of price qdel(I) user.put_in_hands(dispensed_equipment) return - else if(istype(I,/obj/item/card/id)) - var/obj/item/card/id/C = usr.get_active_hand() - if(istype(C) && !istype(inserted_id)) - usr.drop_from_inventory(C,src) - inserted_id = C - interact(user) - return if(default_deconstruction_screwdriver(user, "mining-open", "mining", I)) updateUsrDialog() return diff --git a/code/modules/mob/abstract/new_player/sprite_accessories.dm b/code/modules/mob/abstract/new_player/sprite_accessories.dm index 04c8a4de958..67da95d71c4 100644 --- a/code/modules/mob/abstract/new_player/sprite_accessories.dm +++ b/code/modules/mob/abstract/new_player/sprite_accessories.dm @@ -1712,7 +1712,48 @@ Follow by example and make good judgement based on length which list to include length = 2 chatname = "ponytail" +/* +///////////////////////////////////// +/ =-----------------------------= / +/ == Hair Gradient Definitions == / +/ =-----------------------------= / +///////////////////////////////////// +*/ +/datum/sprite_accessory/hair_gradients + icon = 'icons/mob/hair_gradients.dmi' + + none + name = "None" + icon_state = "none" + + fade_up + name = "Fade (Up)" + icon_state = "fadeup" + + fade_down + name = "Fade (Down)" + icon_state = "fadedown" + + fade_right + name = "Fade (Right)" + icon_state = "faderight" + + fade_left + name = "Fade (Left)" + icon_state = "fadeleft" + + vertical_split_right + name = "Vertical Split (Right)" + icon_state = "vsplit_right" + + vertical_split_left + name = "Vertical Split (Left)" + icon_state = "vsplit_left" + + horizontal + name = "Horizontal Split" + icon_state = "hsplit" /* /////////////////////////////////// / =---------------------------= / @@ -3155,4 +3196,4 @@ Follow by example and make good judgement based on length which list to include xion_lights name = "Xion - Lights Color" icon_state = "xion_lights" - body_parts = list(BP_L_LEG,BP_R_LEG,BP_L_ARM,BP_R_ARM,BP_CHEST,BP_HEAD) \ No newline at end of file + body_parts = list(BP_L_LEG,BP_R_LEG,BP_L_ARM,BP_R_ARM,BP_CHEST,BP_HEAD) diff --git a/code/modules/mob/floating_messages.dm b/code/modules/mob/floating_messages.dm index 7778072ba12..311f9fac99c 100644 --- a/code/modules/mob/floating_messages.dm +++ b/code/modules/mob/floating_messages.dm @@ -51,7 +51,7 @@ var/list/floating_chat_colors = list() I.maptext_height = 64 I.plane = FLOAT_PLANE I.layer = HUD_LAYER - 0.01 - I.pixel_x = -round(I.maptext_width/2) + 16 + I.pixel_x = (-round(I.maptext_width/2) + 16) + holder.get_floating_chat_x_offset() I.appearance_flags = RESET_COLOR|RESET_ALPHA|RESET_TRANSFORM style = "font-family: 'Small Fonts'; -dm-text-outline: 1 black; font-size: [size]px; [style]" @@ -69,4 +69,4 @@ var/list/floating_chat_colors = list() /proc/remove_floating_text(atom/movable/holder, image/I) animate(I, 2, pixel_y = I.pixel_y + 10, alpha = 0) - LAZYREMOVE(holder.stored_chat_text, I) \ No newline at end of file + LAZYREMOVE(holder.stored_chat_text, I) diff --git a/code/modules/mob/living/carbon/brain/MMI.dm b/code/modules/mob/living/carbon/brain/MMI.dm index 25878efc4d8..50060bed34a 100644 --- a/code/modules/mob/living/carbon/brain/MMI.dm +++ b/code/modules/mob/living/carbon/brain/MMI.dm @@ -1,11 +1,12 @@ //This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:32 -/obj/item/device/mmi/digital/New() +/obj/item/device/mmi/digital/Initialize(mapload, ...) + . = ..() src.brainmob = new(src) + brainmob.add_language(LANGUAGE_EAL) src.brainmob.stat = CONSCIOUS src.brainmob.container = src src.brainmob.silent = 0 - ..() /obj/item/device/mmi/digital/transfer_identity(var/mob/living/carbon/H) brainmob.dna = H.dna diff --git a/code/modules/mob/living/carbon/brain/brain.dm b/code/modules/mob/living/carbon/brain/brain.dm index f0eb69c307c..a8a5fcbeeaa 100644 --- a/code/modules/mob/living/carbon/brain/brain.dm +++ b/code/modules/mob/living/carbon/brain/brain.dm @@ -8,6 +8,12 @@ use_me = 0 //Can't use the me verb, it's a freaking immobile brain icon = 'icons/obj/surgery.dmi' icon_state = "brain" + accent = ACCENT_TTS + +/mob/living/carbon/brain/Initialize() + . = ..() + add_language(LANGUAGE_TCB) + set_default_language(all_languages[LANGUAGE_TCB]) /mob/living/carbon/brain/Destroy() if(key) //If there is a mob connected to this thing. Have to check key twice to avoid false death reporting. @@ -17,34 +23,6 @@ container = null return ..() -/mob/living/carbon/brain/say_understands(var/other)//Goddamn is this hackish, but this say code is so odd - if (istype(other, /mob/living/silicon/ai)) - if(!(container && istype(container, /obj/item/device/mmi))) - return 0 - else - return 1 - else if (istype(other, /mob/living/silicon/decoy)) - if(!(container && istype(container, /obj/item/device/mmi))) - return 0 - else - return 1 - else if (istype(other, /mob/living/silicon/pai)) - if(!(container && istype(container, /obj/item/device/mmi))) - return 0 - else - return 1 - else if (istype(other, /mob/living/silicon/robot)) - if(!(container && istype(container, /obj/item/device/mmi))) - return 0 - else - return 1 - else if (istype(other, /mob/living/carbon/human)) - return 1 - else if (istype(other, /mob/living/carbon/slime)) - return 1 - - return ..() - /mob/living/carbon/brain/update_canmove() if(istype(loc, /obj/item/device/mmi)) canmove = 1 diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 2735ffd527f..faf50d6f395 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -405,6 +405,7 @@ dat += "
Remove accessory" dat += "
Remove splints" dat += "
Empty pockets" + dat += species.get_strip_info("\ref[src]") dat += "
Refresh" dat += "
Close" @@ -591,6 +592,9 @@ if(href_list["item"]) handle_strip(href_list["item"],usr) + if(href_list["species"]) + species.handle_strip(usr, src, href_list["species"]) + if(href_list["criminal"]) if(hasHUD(usr,"security")) diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index d66b4975cf4..df7d9f3fcd7 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -5,6 +5,12 @@ var/b_hair = 0 var/h_style = "Bald" + //Hair gradient color and style + var/r_grad = 0 + var/g_grad = 0 + var/b_grad = 0 + var/g_style = "None" + //Facial hair colour and style var/r_facial = 0 var/g_facial = 0 @@ -116,4 +122,4 @@ var/datum/unarmed_attack/default_attack //default unarmed attack var/datum/martial_art/primary_martial_art = null - var/list/datum/martial_art/known_martial_arts = null \ No newline at end of file + var/list/datum/martial_art/known_martial_arts = null diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm index 85861c8d008..af4e6a0dbfe 100644 --- a/code/modules/mob/living/carbon/human/human_movement.dm +++ b/code/modules/mob/living/carbon/human/human_movement.dm @@ -19,7 +19,7 @@ if(can_feel_pain()) if(get_shock() >= 10) - tally += (get_shock() / 10) //pain shouldn't slow you down if you can't even feel it + tally += (get_shock() / 30) //pain shouldn't slow you down if you can't even feel it tally += ClothesSlowdown() diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 9c41987da86..297c191663d 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -728,8 +728,10 @@ if(paralysis || sleeping || InStasis()) blinded = TRUE - stat = UNCONSCIOUS - adjustHalLoss(-3) + if(sleeping) + stat = UNCONSCIOUS + + adjustHalLoss(-7) if (species.tail) animate_tail_reset() if(prob(2) && is_asystole() && isSynthetic()) @@ -765,11 +767,11 @@ if(resting) dizziness = max(0, dizziness - 15) jitteriness = max(0, jitteriness - 15) - adjustHalLoss(-3) + adjustHalLoss(-5) else dizziness = max(0, dizziness - 3) jitteriness = max(0, jitteriness - 3) - adjustHalLoss(-1) + adjustHalLoss(-3) //Other handle_statuses() @@ -810,7 +812,7 @@ return if(stat != DEAD) - if(stat == UNCONSCIOUS && health < maxHealth / 2) + if((stat == UNCONSCIOUS && health < maxHealth / 2) || paralysis || InStasis()) //Critical damage passage overlay var/severity = 0 switch(health - maxHealth/2) @@ -824,6 +826,8 @@ if(-90 to -80) severity = 8 if(-95 to -90) severity = 9 if(-INFINITY to -95) severity = 10 + if(paralysis || InStasis()) + severity = max(severity, 8) overlay_fullscreen("crit", /obj/screen/fullscreen/crit, severity) else clear_fullscreen("crit") @@ -858,6 +862,12 @@ else clear_fullscreen("brute") + if(paralysis_indicator) + if(paralysis) + paralysis_indicator.icon_state = "paralysis1" + else + paralysis_indicator.icon_state = "paralysis0" + if(healths) healths.overlays.Cut() if (chem_effects[CE_PAINKILLER] > 100) diff --git a/code/modules/mob/living/carbon/human/say.dm b/code/modules/mob/living/carbon/human/say.dm index 1eed969ce7f..704c2386959 100644 --- a/code/modules/mob/living/carbon/human/say.dm +++ b/code/modules/mob/living/carbon/human/say.dm @@ -150,6 +150,9 @@ return returns /mob/living/carbon/human/handle_message_mode(message_mode, message, verb, speaking, used_radios, alt_name, successful_radio) + if(paralysis || InStasis()) + whisper_say(message, speaking, alt_name) + return TRUE switch(message_mode) if("intercom") for(var/obj/item/device/radio/intercom/I in view(1)) diff --git a/code/modules/mob/living/carbon/human/species/species.dm b/code/modules/mob/living/carbon/human/species/species.dm index fee31909504..b5784e080bc 100644 --- a/code/modules/mob/living/carbon/human/species/species.dm +++ b/code/modules/mob/living/carbon/human/species/species.dm @@ -236,6 +236,7 @@ var/default_h_style = "Bald" var/default_f_style = "Shaved" + var/default_g_style = "None" var/list/allowed_citizenships = list(CITIZENSHIP_BIESEL, CITIZENSHIP_SOL, CITIZENSHIP_COALITION, CITIZENSHIP_ELYRA, CITIZENSHIP_ERIDANI, CITIZENSHIP_DOMINIA) var/list/allowed_religions = list(RELIGION_NONE, RELIGION_OTHER, RELIGION_CHRISTIANITY, RELIGION_ISLAM, RELIGION_JUDAISM, RELIGION_HINDU, RELIGION_BUDDHISM, RELIGION_MOROZ, RELIGION_TRINARY, RELIGION_SCARAB, RELIGION_TAOISM) @@ -607,6 +608,7 @@ /datum/species/proc/set_default_hair(var/mob/living/carbon/human/H) H.h_style = H.species.default_h_style H.f_style = H.species.default_f_style + H.g_style = H.species.default_g_style H.update_hair() /datum/species/proc/get_species_tally(var/mob/living/carbon/human/H) @@ -633,6 +635,12 @@ /datum/species/proc/handle_despawn() return +/datum/species/proc/handle_strip(var/mob/user, var/mob/living/carbon/human/H, var/action) + return + +/datum/species/proc/get_strip_info(var/reference) + return "" + /datum/species/proc/get_pain_emote(var/mob/living/carbon/human/H, var/pain_power) if(flags & NO_PAIN) return diff --git a/code/modules/mob/living/carbon/human/species/station/skrell/skrell.dm b/code/modules/mob/living/carbon/human/species/station/skrell/skrell.dm index 1aea53b69a7..e5ee3267085 100644 --- a/code/modules/mob/living/carbon/human/species/station/skrell/skrell.dm +++ b/code/modules/mob/living/carbon/human/species/station/skrell/skrell.dm @@ -34,6 +34,20 @@ appearance_flags = HAS_HAIR_COLOR | HAS_LIPS | HAS_UNDERWEAR | HAS_SKIN_COLOR | HAS_SOCKS flags = NO_SLIP + has_limbs = list( + BP_CHEST = list("path" = /obj/item/organ/external/chest), + BP_GROIN = list("path" = /obj/item/organ/external/groin), + BP_HEAD = list("path" = /obj/item/organ/external/head/skrell), + BP_L_ARM = list("path" = /obj/item/organ/external/arm), + BP_R_ARM = list("path" = /obj/item/organ/external/arm/right), + BP_L_LEG = list("path" = /obj/item/organ/external/leg), + BP_R_LEG = list("path" = /obj/item/organ/external/leg/right), + BP_L_HAND = list("path" = /obj/item/organ/external/hand), + BP_R_HAND = list("path" = /obj/item/organ/external/hand/right), + BP_L_FOOT = list("path" = /obj/item/organ/external/foot), + BP_R_FOOT = list("path" = /obj/item/organ/external/foot/right) + ) + has_organ = list( BP_HEART = /obj/item/organ/internal/heart/skrell, BP_LUNGS = /obj/item/organ/internal/lungs/skrell, @@ -71,6 +85,25 @@ /datum/species/skrell/handle_post_spawn(mob/living/carbon/human/H) H.set_psi_rank(PSI_COERCION, PSI_RANK_OPERANT) +/datum/species/skrell/handle_strip(var/mob/user, var/mob/living/carbon/human/H, var/action) + switch(action) + if("headtail") + if(!H.head) + to_chat(user, SPAN_WARNING("\The [H] doesn't have a head!")) + return + user.visible_message(SPAN_WARNING("\The [user] is trying to remove something from \the [H]'s headtails!")) + if(do_after(usr, HUMAN_STRIP_DELAY, act_target = H)) + var/obj/item/storage/internal/skrell/S = locate() in H.head + var/obj/item/I = locate() in S + if(!I) + to_chat(usr, SPAN_WARNING("\The [H] had nothing in their headtail storage.")) + return + S.remove_from_storage(I, get_turf(H)) + return + +/datum/species/skrell/get_strip_info(var/reference) + return "
Empty Headtail Storage" + /datum/species/skrell/can_breathe_water() return TRUE diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index 6dce5109b36..9f9fd8f4fe1 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -370,7 +370,7 @@ There are several things that need to be remembered: // This proc generates & returns an icon representing a human's hair, using a cached icon from SSicon_cache if possible. // If `hair_is_visible` is FALSE, only facial hair will be drawn. /mob/living/carbon/human/proc/generate_hair_icon(hair_is_visible = TRUE) - var/cache_key = "[f_style ? "[f_style][r_facial][g_facial][b_facial]" : "nofacial"]_[(h_style && hair_is_visible) ? "[h_style][r_hair][g_hair][b_hair]" : "nohair"]" + var/cache_key = "[f_style ? "[f_style][r_facial][g_facial][b_facial]" : "nofacial"]_[(h_style && hair_is_visible) ? "[h_style][r_hair][g_hair][b_hair]" : "nohair"]_[(g_style && g_style != "None" && hair_is_visible) ? "[g_style][r_grad][g_grad][b_grad]" : "nograd"]" var/icon/face_standing = SSicon_cache.human_hair_cache[cache_key] if (!face_standing) // Not cached, generate it from scratch. @@ -387,11 +387,19 @@ There are several things that need to be remembered: // Hair. if(hair_is_visible) + var/icon/grad_s = null var/datum/sprite_accessory/hair_style = hair_styles_list[h_style] if(hair_style && (species.type in hair_style.species_allowed)) var/icon/hair_s = new/icon("icon" = hair_style.icon, "icon_state" = hair_style.icon_state) if(hair_style.do_colouration) + if(g_style) + var/datum/sprite_accessory/gradient_style = hair_gradient_styles_list[g_style] + grad_s = new/icon("icon" = gradient_style.icon, "icon_state" = gradient_style.icon_state) + grad_s.Blend(hair_s, ICON_AND) + grad_s.Blend(rgb(r_grad, g_grad, b_grad), ICON_MULTIPLY) hair_s.Blend(rgb(r_hair, g_hair, b_hair), hair_style.icon_blend_mode) + if(!isnull(grad_s)) + hair_s.Blend(grad_s, ICON_OVERLAY) face_standing.Blend(hair_s, ICON_OVERLAY) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index bf871c6e130..b6cc60dbe64 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -98,11 +98,29 @@ default behaviour is: return if(can_swap_with(tmob)) // mutual brohugs all around! - var/turf/oldloc = loc - forceMove(tmob.loc) - tmob.forceMove(oldloc) + var/turf/tmob_oldloc = tmob.loc + var/turf/src_oldloc = loc + if(pulling?.density) + tmob.forceMove(pulling.loc) + forceMove(tmob_oldloc) + pulling.forceMove(src_oldloc) + else if(tmob.pulling?.density) + forceMove(tmob.pulling.loc) + tmob.forceMove(src_oldloc) + tmob.pulling.forceMove(tmob_oldloc) + else + forceMove(tmob_oldloc) + if(pulling) + pulling.forceMove(src_oldloc) + tmob.forceMove(src_oldloc) + if(tmob.pulling) + tmob.pulling.forceMove(tmob_oldloc) + for(var/obj/item/grab/G in list(l_hand, r_hand)) + G.affecting.forceMove(loc) + for(var/obj/item/grab/G in list(tmob.l_hand, tmob.r_hand)) + G.affecting.forceMove(tmob.loc) now_pushing = FALSE - for(var/mob/living/carbon/slime/slime in view(1,tmob)) + for(var/mob/living/carbon/slime/slime in view(2, tmob)) if(slime.victim == tmob) slime.UpdateFeed() return @@ -191,6 +209,9 @@ default behaviour is: if(swap_density_check(tmob, src)) return 0 + if(pulling?.density && tmob.pulling?.density) // if both are pulling, don't shuffle + return FALSE + return can_move_mob(tmob, 1, 0) /mob/living/verb/succumb() diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm index dfa518eef2f..f1153a59c29 100644 --- a/code/modules/mob/logout.dm +++ b/code/modules/mob/logout.dm @@ -8,7 +8,8 @@ SSfeedback.update_status() if(admin_datums[src.ckey]) - if (SSticker.current_state == GAME_STATE_PLAYING) //Only report this stuff if we are currently playing. + var/datum/admins/A = admin_datums[src.ckey] + if (A.rights & (R_MOD|R_ADMIN) && SSticker.current_state == GAME_STATE_PLAYING) //Only report this stuff if we are currently playing. var/admins_number = 0 var/admins_number_afk = 0 for (var/client/C in clients) diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index fc5edb9e50f..5e8904fa0c2 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -17,6 +17,7 @@ var/obj/screen/purged = null var/obj/screen/internals/internals = null var/obj/screen/oxygen = null + var/obj/screen/paralysis_indicator = null var/obj/screen/i_select = null var/obj/screen/m_select = null var/obj/screen/toxin = null diff --git a/code/modules/modular_computers/NTNet/NTNRC/conversation.dm b/code/modules/modular_computers/NTNet/NTNRC/conversation.dm index 8e8c4867a7d..692451011d5 100644 --- a/code/modules/modular_computers/NTNet/NTNRC/conversation.dm +++ b/code/modules/modular_computers/NTNet/NTNRC/conversation.dm @@ -24,7 +24,7 @@ var/global/ntnrc_uid = 0 for(var/datum/computer_file/program/chatclient/C in clients) if(C.program_state > PROGRAM_STATE_KILLED) - C.computer.output_message("([get_title(C)]) [username]: [message] (Reply)", 0) + C.computer.output_message("([get_title(C)]) [username]: [message] (Reply)", 0) if(!C.silent && C.username != username && C.program_state == PROGRAM_STATE_BACKGROUND) playsound(C.computer, 'sound/machines/twobeep.ogg', 50, 1) C.computer.output_message("*[C.ringtone]*") diff --git a/code/modules/modular_computers/file_system/programs/civilian/cargo_delivery.dm b/code/modules/modular_computers/file_system/programs/civilian/cargo_delivery.dm index fa10dc2de34..1efa5929444 100644 --- a/code/modules/modular_computers/file_system/programs/civilian/cargo_delivery.dm +++ b/code/modules/modular_computers/file_system/programs/civilian/cargo_delivery.dm @@ -74,10 +74,22 @@ return TRUE if(program && program.computer && program.computer.card_slot && program.computer.network_card) - var/obj/item/card/id/id_card = program.computer.card_slot.stored_card + var/using_id = FALSE + var/obj/item/card/id/id_card + if(program.computer.card_slot?.stored_card) + using_id = TRUE + id_card = program.computer.card_slot.stored_card if(!id_card?.registered_name) + using_id = FALSE status_message = "Card Error: Invalid ID Card in Card Reader" - return TRUE + + var/obj/item/spacecash/ewallet/charge_card + if(!using_id) + if(isliving(usr)) + var/mob/living/L = usr + charge_card = L.get_active_hand() + if(!istype(charge_card)) + return TRUE //Check if a payment is required if(order_details["needs_payment"]) @@ -85,11 +97,19 @@ var/transaction_purpose = "Cargo Order #[order_details["order_id"]]" var/transaction_terminal = "Modular Computer #[program.computer.network_card.identification_id]" - var/status = SSeconomy.transfer_money(id_card.associated_account_number, SScargo.supply_account.account_number,transaction_purpose,transaction_terminal,transaction_amount,null,usr) - - if(status) - status_message = status - return TRUE + if(using_id) + var/status = SSeconomy.transfer_money(id_card.associated_account_number, SScargo.supply_account.account_number,transaction_purpose,transaction_terminal,transaction_amount,null,usr) + if(status) + status_message = status + return TRUE + else + if(charge_card.worth < transaction_amount) + status_message = "Insufficient Funds in Charge Card" + return TRUE + if(!SSeconomy.charge_to_account(SScargo.supply_account.account_number, charge_card.owner_name, transaction_purpose, transaction_terminal, transaction_amount)) + status_message = "Account Error: Failed to Deposit Credits into Cargo Account" + return TRUE + charge_card.worth -= transaction_amount playsound(program.computer, 'sound/machines/chime.ogg', 50, TRUE) diff --git a/code/modules/modular_computers/file_system/programs/generic/ntnrc_client.dm b/code/modules/modular_computers/file_system/programs/generic/ntnrc_client.dm index fb6ec082b13..7a2d7b68c4a 100644 --- a/code/modules/modular_computers/file_system/programs/generic/ntnrc_client.dm +++ b/code/modules/modular_computers/file_system/programs/generic/ntnrc_client.dm @@ -61,14 +61,15 @@ if(href_list["Reply"]) . = TRUE - if(!channel || channel.title != href_list["target"]) - to_chat(usr, SPAN_WARNING("The target chat isn't active on your program anymore!")) + var/datum/ntnet_conversation/C = locate(href_list["Reply"]) in ntnet_global.chat_channels + if(!istype(C)) + to_chat(usr, SPAN_WARNING("The target channel couldn't be found and has likely been deleted!")) return var/message = send_message() - if(!channel || channel.title != href_list["target"]) - to_chat(usr, SPAN_WARNING("The target chat isn't active on your program anymore!")) + if(!(C in ntnet_global.chat_channels)) + to_chat(usr, SPAN_WARNING("The target channel couldn't be found and has likely been deleted!")) return - add_message(message) + add_message(message, C) if(href_list["PRG_joinchannel"]) . = TRUE @@ -219,11 +220,16 @@ return return message -/datum/computer_file/program/chatclient/proc/add_message(var/message) +/datum/computer_file/program/chatclient/proc/add_message(var/message, var/datum/ntnet_conversation/specific_channel) if(!message) return - channel.add_message(message, username, usr) - message_dead(FONT_SMALL("([channel.get_dead_title()]) [username]: [message]")) + var/datum/ntnet_conversation/sent_channel + if(specific_channel) + sent_channel = specific_channel + else + sent_channel = channel + sent_channel.add_message(message, username, usr) + message_dead(FONT_SMALL("([sent_channel.get_dead_title()]) [username]: [message]")) /datum/computer_file/program/chatclient/proc/direct_message() var/clients = list() diff --git a/code/modules/organs/internal/heart.dm b/code/modules/organs/internal/heart.dm index 6863423ac26..f7715a8a955 100644 --- a/code/modules/organs/internal/heart.dm +++ b/code/modules/organs/internal/heart.dm @@ -139,7 +139,7 @@ var/blood_max = 0 var/open_wound var/list/do_spray = list() - for(var/obj/item/organ/external/temp in owner.organs) + for(var/obj/item/organ/external/temp in owner.bad_external_organs) if((temp.status & ORGAN_BLEEDING) && !BP_IS_ROBOTIC(temp)) for(var/datum/wound/W in temp.wounds) if(W.bleeding()) @@ -156,6 +156,8 @@ if(temp.status & ORGAN_ARTERY_CUT) var/bleed_amount = Floor(owner.vessel.total_volume / (temp.applied_pressure || !open_wound ? 450 : 250)) if(bleed_amount) + if(CE_BLOODCLOT in owner.chem_effects) + bleed_amount *= 0.8 // won't do much, but it'll help if(open_wound) blood_max += bleed_amount do_spray += "[temp.name]" @@ -170,7 +172,9 @@ if(PULSE_2FAST, PULSE_THREADY) blood_max *= 1.5 - if(CE_STABLE in owner.chem_effects) + if(CE_BLOODCLOT in owner.chem_effects) + blood_max *= 0.7 + else if(CE_STABLE in owner.chem_effects) blood_max *= 0.8 if(world.time >= next_blood_squirt && istype(owner.loc, /turf) && do_spray.len) diff --git a/code/modules/organs/pain.dm b/code/modules/organs/pain.dm index 94a24ec70d3..de90c3dd3a7 100644 --- a/code/modules/organs/pain.dm +++ b/code/modules/organs/pain.dm @@ -73,6 +73,7 @@ mob/var/next_pain_time = 0 if(maxdam > 10 && paralysis) paralysis = max(0, paralysis - round(maxdam / 10)) if(maxdam > 50 && prob(maxdam / 5)) + to_chat(src, SPAN_WARNING("A bolt of pain shoots through your body, causing your hands to spasm!")) drop_item() var/burning = damaged_organ.burn_dam > damaged_organ.brute_dam var/msg diff --git a/code/modules/organs/subtypes/skrell.dm b/code/modules/organs/subtypes/skrell.dm index e2edca8c0cb..25260267f43 100644 --- a/code/modules/organs/subtypes/skrell.dm +++ b/code/modules/organs/subtypes/skrell.dm @@ -16,3 +16,33 @@ /obj/item/organ/internal/brain/skrell icon_state = "brain_skrell" + +/obj/item/organ/external/head/skrell + var/obj/item/storage/internal/skrell/storage + action_button_name = "Headtail Pocket" + +/obj/item/organ/external/head/skrell/Initialize(mapload) + . = ..() + addtimer(CALLBACK(src, .proc/setup_storage), 3 SECONDS) + +/obj/item/organ/external/head/skrell/proc/setup_storage() + storage = new /obj/item/storage/internal/skrell(src) + if(owner) + storage.color = rgb(owner.r_hair, owner.g_hair, owner.b_hair) + refresh_action_button() + +/obj/item/organ/external/head/skrell/refresh_action_button() + . = ..() + if(. && storage) + action.button_icon_state = storage.icon_state + action.button_icon_color = storage.color + if(action.button) + action.button.update_icon() + +/obj/item/organ/external/head/skrell/removed() + . = ..() + for(var/thing in storage) + storage.remove_from_storage(thing, get_turf(src)) + +/obj/item/organ/external/head/skrell/attack_self(mob/user) + storage.open(user) \ No newline at end of file diff --git a/code/modules/projectiles/guns/energy/disrupter.dm b/code/modules/projectiles/guns/energy/disrupter.dm index 3a343d00805..f08d5eaf51b 100644 --- a/code/modules/projectiles/guns/energy/disrupter.dm +++ b/code/modules/projectiles/guns/energy/disrupter.dm @@ -15,7 +15,7 @@ matter = list(DEFAULT_WALL_MATERIAL = 2000) projectile_type = /obj/item/projectile/energy/disruptorstun secondary_projectile_type = /obj/item/projectile/energy/blaster - max_shots = 12 //12 shots stun, 8 shots lethal. + max_shots = 8 charge_cost = 150 fire_delay = 8 accuracy = 1 @@ -24,8 +24,8 @@ sel_mode = 1 var/selectframecheck = FALSE firemodes = list( - list(mode_name="stun", projectile_type=/obj/item/projectile/energy/disruptorstun, modifystate="disruptorpistolstun", charge_cost = 150, fire_sound = 'sound/weapons/gunshot/bolter.ogg'), - list(mode_name="lethal", projectile_type=/obj/item/projectile/energy/blaster, modifystate="disruptorpistolkill", recoil = 1, charge_cost = 225, fire_sound = 'sound/weapons/gunshot/bolter.ogg') + list(mode_name="stun", projectile_type=/obj/item/projectile/energy/disruptorstun, modifystate="disruptorpistolstun", fire_sound = 'sound/weapons/gunshot/bolter.ogg'), + list(mode_name="lethal", projectile_type=/obj/item/projectile/energy/blaster/disruptor, modifystate="disruptorpistolkill", recoil = 1, fire_sound = 'sound/weapons/gunshot/bolter.ogg') ) /obj/item/gun/energy/disruptorpistol/security @@ -35,7 +35,7 @@ name = "miniature disruptor pistol" desc = "A Nanotrasen designed blaster pistol with two settings: stun and lethal. This is the miniature version." icon = 'icons/obj/guns/disruptorpistol/disruptorpistolc.dmi' - max_shots = 7 + max_shots = 5 force = 3 slot_flags = SLOT_BELT|SLOT_HOLSTER|SLOT_POCKET w_class = ITEMSIZE_SMALL @@ -47,7 +47,7 @@ name = "magnum disruptor pistol" desc = "A Nanotrasen designed blaster pistol with two settings: stun and lethal. This is the magnum version." icon = 'icons/obj/guns/disruptorpistol/disruptorpistolm.dmi' - max_shots = 20 + max_shots = 12 force = 6 /obj/item/gun/energy/disruptorpistol/magnum/security diff --git a/code/modules/projectiles/projectile/energy.dm b/code/modules/projectiles/projectile/energy.dm index 437d2a50bd5..905b9f9471c 100644 --- a/code/modules/projectiles/projectile/energy.dm +++ b/code/modules/projectiles/projectile/energy.dm @@ -201,10 +201,14 @@ muzzle_type = /obj/effect/projectile/muzzle/bolt hit_effect = /obj/effect/temp_visual/blaster_effect +/obj/item/projectile/energy/blaster/disruptor + damage = 20 + pass_flags = PASSTABLE + /obj/item/projectile/energy/disruptorstun name = "disruptor bolt" icon_state = "blue_laser" - agony = 45 + agony = 25 speed = 0.4 damage_type = PAIN // Can't blow your own head off with a stunbolt. taser_effect = TRUE diff --git a/code/modules/reagents/Chemistry-Machinery.dm b/code/modules/reagents/Chemistry-Machinery.dm index 34b628897b7..54e1f8e1e2e 100644 --- a/code/modules/reagents/Chemistry-Machinery.dm +++ b/code/modules/reagents/Chemistry-Machinery.dm @@ -26,6 +26,7 @@ var/pillsprite = "1" var/max_pill_count = 20 flags = OPENCONTAINER + var/datum/asset/spritesheet/chem_master/chem_asset /obj/machinery/chem_master/Initialize() . = ..() @@ -202,16 +203,23 @@ var/obj/item/reagent_containers/food/condiment/P = new/obj/item/reagent_containers/food/condiment(get_turf(src)) reagents.trans_to_obj(P,50) else if(href_list["change_pill"]) - var/dat = "" + if(!chem_asset) + chem_asset = get_asset_datum(/datum/asset/spritesheet/chem_master) + var/dat = chem_asset.css_tag() + dat += "
" for(var/i = 1 to MAX_PILL_SPRITE) - dat += "" + var/pillicon = "pill[i]" + dat += "" dat += "
[chem_asset.icon_tag(pillicon)]
" usr << browse(dat, "window=chem_master") return else if(href_list["change_bottle"]) - var/dat = "" + if(!chem_asset) + chem_asset = get_asset_datum(/datum/asset/spritesheet/chem_master) + var/dat = chem_asset.css_tag() + dat += "
" for(var/sprite in BOTTLE_SPRITES) - dat += "" + dat += "" dat += "
[chem_asset.icon_tag(sprite)]
" usr << browse(dat, "window=chem_master") return @@ -233,10 +241,9 @@ return user.set_machine(src) - var/datum/asset/pill_icons = get_asset_datum(/datum/asset/chem_master) - pill_icons.send(user.client) - - var/dat = "" + if(!chem_asset) + chem_asset = get_asset_datum(/datum/asset/spritesheet/chem_master) + var/dat = chem_asset.css_tag() if(!beaker) dat = "Please insert beaker.
" if(src.loaded_pill_bottle) @@ -277,9 +284,9 @@ else dat += "Empty
" if(!condi) - dat += "

Create pill (60 units max)
" + dat += "

Create pill (60 units max)[chem_asset.icon_tag("pill[pillsprite]")]
" dat += "Create multiple pills
" - dat += "Create bottle (60 units max)" + dat += "Create bottle (60 units max)[chem_asset.icon_tag(bottlesprite)]" else dat += "Create bottle (50 units max)" if(!condi) diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm index 65dc41508e2..90a3694eb7a 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm @@ -2937,7 +2937,7 @@ caffeine = 0.3 taste_description = "giving up on peaceful coexistence" - glass_icon_state = "giscoffeeglass" + glass_icon_state = "fiscoffeeglass" glass_name = "glass of Fisanduhian coffee" glass_desc = "It's like an Irish coffee, but spicy and angry about Dominia." glass_center_of_mass = list("x"=15, "y"=10) diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm index b7d99b1f30d..8b1429a1533 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm @@ -1677,3 +1677,25 @@ /datum/reagent/rmt/overdose(var/mob/living/carbon/H, var/alien) if(prob(2)) to_chat(H, SPAN_WARNING(pick("Your muscles are stinging a bit.", "Your muscles ache."))) + +/datum/reagent/coagzolug + name = "Coagzolug" + description = "A medicine that was stumbled upon by accident, coagzolug encourages blood to clot and slow down bleeding. An overdose causes dangerous blood clots capable of harming the heart." + reagent_state = LIQUID + scannable = TRUE + color = "#bd5eb5" + overdose = 10 + metabolism = REM / 3.33 + taste_description = "throat-clenching sourness" + fallback_specific_heat = 1 + +/datum/reagent/coagzolug/affect_blood(mob/living/carbon/M, alien, removed) + . = ..() + M.add_chemical_effect(CE_BLOODCLOT) + M.make_dizzy(5) + +/datum/reagent/coagzolug/overdose(var/mob/living/carbon/H, var/alien) + if(prob(2)) + to_chat(H, SPAN_WARNING(pick("You feel a clot shoot through your heart!", "Your veins feel like they're being shredded!"))) + var/obj/item/organ/internal/heart/heart = H.internal_organs_by_name[BP_HEART] + heart.take_internal_damage(1, TRUE) \ No newline at end of file diff --git a/code/modules/reagents/Chemistry-Recipes.dm b/code/modules/reagents/Chemistry-Recipes.dm index cd81585e89b..93fc79f3418 100644 --- a/code/modules/reagents/Chemistry-Recipes.dm +++ b/code/modules/reagents/Chemistry-Recipes.dm @@ -478,6 +478,13 @@ required_reagents = list(/datum/reagent/sodiumchloride = 1, /datum/reagent/alcohol/ethanol = 1, /datum/reagent/radium = 1) result_amount = 3 +/datum/chemical_reaction/coagzolug + name = "Coagzolug" + id = "coagzolug" + result = /datum/reagent/coagzolug + required_reagents = list(/datum/reagent/tricordrazine = 1, /datum/reagent/coughsyrup = 1) + result_amount = 1 // result is 1. i imagine it's because of some whacky reaction + /datum/chemical_reaction/surfactant name = "Azosurfactant" id = "surfactant" diff --git a/code/modules/reagents/reagent_containers/hypospray.dm b/code/modules/reagents/reagent_containers/hypospray.dm index 93a17351abb..51ea1a7d0e1 100644 --- a/code/modules/reagents/reagent_containers/hypospray.dm +++ b/code/modules/reagents/reagent_containers/hypospray.dm @@ -177,6 +177,17 @@ . = ..() desc += " This auto-injector is to be used in emergencies. It contains a small amount of inaprovaline and dexalin." +/obj/item/reagent_containers/hypospray/autoinjector/coagzolug + name = "autoinjector (coagzolug)" + desc = "A rapid and safe way to administer small amounts of drugs by untrained or trained personnel. This one contains coagzolug, a quick-acting blood coagulant that will slow bleeding for as long as it's within the bloodstream." + volume = 5 + flags = 0 + +/obj/item/reagent_containers/hypospray/autoinjector/coagzolug/Initialize() + . = ..() + reagents.add_reagent(/datum/reagent/coagzolug, 5) + update_icon() + /obj/item/reagent_containers/hypospray/autoinjector/sideeffectbgone name = "sideeffects-be-gone! autoinjector" desc = "A special cocktail designed to counter the side-effects of various drugs. Has 2 uses." diff --git a/code/modules/research/designs/circuit/machine_circuits.dm b/code/modules/research/designs/circuit/machine_circuits.dm index 4202af83a46..ee9794afc13 100644 --- a/code/modules/research/designs/circuit/machine_circuits.dm +++ b/code/modules/research/designs/circuit/machine_circuits.dm @@ -56,6 +56,11 @@ req_tech = list(TECH_DATA = 2, TECH_POWER = 2, TECH_ENGINEERING = 2) build_path = /obj/item/circuitboard/mech_recharger +/datum/design/circuit/machine/heph_mech_recharger + name = "Hephaestus Mech Recharger" + req_tech = list(TECH_DATA = 3, TECH_POWER = 3, TECH_ENGINEERING = 4) + build_path = /obj/item/circuitboard/mech_recharger/hephaestus + /datum/design/circuit/machine/recharge_station name = "Cyborg Recharge Station" req_tech = list(TECH_DATA = 3, TECH_ENGINEERING = 2) diff --git a/code/modules/research/designs/protolathe/tool_designs.dm b/code/modules/research/designs/protolathe/tool_designs.dm index 0e2a6e78f08..3cc1067955f 100644 --- a/code/modules/research/designs/protolathe/tool_designs.dm +++ b/code/modules/research/designs/protolathe/tool_designs.dm @@ -137,4 +137,10 @@ datum/design/item/tool/advanced_light_replacer desc = "A heavily modified RFD, modified to construct pipes and piping accessories." req_tech = list(TECH_ENGINEERING = 5, TECH_MATERIAL = 5) materials = list(DEFAULT_WALL_MATERIAL = 3000, MATERIAL_GLASS = 2500, MATERIAL_SILVER = 2500) - build_path = /obj/item/rfd/piping \ No newline at end of file + build_path = /obj/item/rfd/piping + +/datum/design/item/tool/idris_backpack + desc = "The infamously Idris Service Standard refers to this monstrous, self-stabilizing back-mounted utensil and service item holder, not anything professional." + req_tech = list(TECH_ENGINEERING = 2, TECH_MATERIAL = 2) + materials = list(DEFAULT_WALL_MATERIAL = 1500, MATERIAL_GLASS = 1500) + build_path = /obj/item/storage/backpack/service \ No newline at end of file diff --git a/code/modules/security levels/keycard authentication.dm b/code/modules/security levels/keycard authentication.dm index 9d4bb64a4c6..411938c3f23 100644 --- a/code/modules/security levels/keycard authentication.dm +++ b/code/modules/security levels/keycard authentication.dm @@ -169,6 +169,12 @@ var/global/maint_all_access = 0 security_announcement.Announce("The maintenance access requirement has been readded on all maintenance airlocks.","Attention!") /obj/machinery/door/airlock/allowed(mob/M) - if(maint_all_access && src.check_access_list(list(access_maint_tunnels))) + var/obj/item/I = M.GetIdCard() + if(!I) + return ..(M) + var/list/A = I.GetAccess() + var/maint_sec_access = ((security_level > SEC_LEVEL_GREEN) && has_access(access_security, accesses = A)) + var/exceptional_circumstances = maint_all_access || maint_sec_access + if(exceptional_circumstances && src.check_access_list(list(access_maint_tunnels))) return 1 return ..(M) diff --git a/code/modules/shieldgen/emergency_shield.dm b/code/modules/shieldgen/emergency_shield.dm index c7761dc2ad0..62724f6412b 100644 --- a/code/modules/shieldgen/emergency_shield.dm +++ b/code/modules/shieldgen/emergency_shield.dm @@ -1,5 +1,5 @@ /obj/machinery/shield - name = "Emergency energy shield" + name = "emergency energy shield" desc = "An energy shield used to contain hull breaches." icon = 'icons/effects/effects.dmi' icon_state = "shield-old" @@ -8,22 +8,38 @@ anchored = TRUE unacidable = TRUE atmos_canpass = CANPASS_NEVER - var/const/max_health = 200 - var/health = max_health //The shield can only take so much beating (prevents perma-prisons) + var/health = 75 //The shield can only take so much beating (prevents perma-prisons) var/shield_generate_power = 2500 //how much power we use when regenerating var/shield_idle_power = 500 //how much power we use when just being sustained. /obj/machinery/shield/malfai name = "emergency forcefield" - desc = "A weak forcefield which seems to be projected by the station's emergency atmosphere containment field" - health = max_health/2 // Half health, it's not suposed to resist much. + desc = "A forcefield which seems to be projected by the station's emergency atmosphere containment field." + health = 100 /obj/machinery/shield/malfai/machinery_process() health -= 0.5 // Slowly lose integrity over time check_failure() /obj/machinery/shield/proc/check_failure() - if (src.health <= 0) + var/health_percentage = (health / initial(health)) * 100 + switch(health_percentage) + if(-INFINITY to 25) + if(alpha != 150) + animate(src, 1 SECOND, alpha = 150) + if(26 to 50) + if(alpha != 175) + animate(src, 1 SECOND, alpha = 175) + if(51 to 75) + if(alpha != 210) + animate(src, 1 SECOND, alpha = 210) + if(76 to 90) + if(alpha != 230) + animate(src, 1 SECOND, alpha = 230) + if(91 to INFINITY) + if(alpha != initial(alpha)) + animate(src, 1 SECOND, alpha = initial(alpha)) + if(health <= 0) visible_message("\The [src] dissipates!") qdel(src) return @@ -43,19 +59,18 @@ if(!height || air_group) return FALSE else return ..() -/obj/machinery/shield/attackby(obj/item/W as obj, mob/user as mob) - if(!istype(W)) return - +/obj/machinery/shield/attackby(obj/item/W, mob/user) + user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) + user.do_attack_animation(src, W) //Calculate damage var/aforce = W.force if(W.damtype == BRUTE || W.damtype == BURN) - src.health -= aforce + health -= aforce //Play a fitting sound playsound(src.loc, 'sound/effects/EMPulse.ogg', 75, 1) check_failure() - user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) ..() @@ -112,8 +127,9 @@ ..() return + /obj/machinery/shieldgen - name = "Emergency shield projector" + name = "emergency shield projector" desc = "Used to seal minor hull breaches." icon = 'icons/obj/machines/shielding.dmi' icon_state = "shieldoff" @@ -121,8 +137,7 @@ opacity = FALSE anchored = FALSE req_access = list(access_engine) - var/const/max_health = 100 - var/health = max_health + var/health = 100 var/active = FALSE var/malfunction = FALSE //Malfunction causes parts of the shield to slowly dissapate var/list/deployed_shields = list() @@ -161,12 +176,19 @@ update_use_power(FALSE) /obj/machinery/shieldgen/proc/create_shields() - for(var/turf/target_tile in range(2, src)) - if (istype(target_tile,/turf/space) || istype(target_tile,/turf/simulated/open) || istype(target_tile,/turf/unsimulated/floor/asteroid/ash) || istype(target_tile,/turf/simulated/floor/airless) && !(locate(/obj/machinery/shield) in target_tile)) - if (malfunction && prob(33) || !malfunction) - var/obj/machinery/shield/S = new /obj/machinery/shield(target_tile) - deployed_shields += S - use_power(S.shield_generate_power) + for(var/T in RANGE_TURFS(2, src)) + var/turf/target_tile = T + var/obj/item/tape/engineering/E = locate() in target_tile + if(E?.shield_marker) + deploy_shield(target_tile) + else if(istype(target_tile,/turf/space) || istype(target_tile,/turf/simulated/open) || istype(target_tile,/turf/unsimulated/floor/asteroid/ash) || istype(target_tile,/turf/simulated/floor/airless) && !(locate(/obj/machinery/shield) in target_tile)) + if(malfunction && prob(33) || !malfunction) + deploy_shield(target_tile) + +/obj/machinery/shieldgen/proc/deploy_shield(var/turf/T) + var/obj/machinery/shield/S = new /obj/machinery/shield(T) + deployed_shields += S + use_power(S.shield_generate_power) /obj/machinery/shieldgen/proc/collapse_shields() for(var/obj/machinery/shield/shield_tile in deployed_shields) @@ -286,7 +308,7 @@ //if(do_after(user, min(60, round( ((maxhealth/health)*10)+(malfunction*10) ))) //Take longer to repair heavier damage if(do_after(user, 30)) if (coil.use(1)) - health = max_health + health = initial(health) malfunction = FALSE to_chat(user, "You repair the [src]!") update_icon() diff --git a/config/example/admins.txt b/config/example/admins.txt index ae1ab106a51..7f61dd46c2a 100644 --- a/config/example/admins.txt +++ b/config/example/admins.txt @@ -2,7 +2,7 @@ # Basically, ckey goes first. Rank goes after the "-" # # Case is not important for ckey. # # Case IS important for the rank. However punctuation/spaces are not # -# Ranks can be anything defined in admin_ranks.txt ~Carn # +# Ranks can be anything defined in admin_ranks.json ~Carn # ###################################################################### -# not_a_user - Admin +# not_a_user - Head Admin/Dev diff --git a/config/example/custom_items.json b/config/example/custom_items.json new file mode 100644 index 00000000000..44e0428ed74 --- /dev/null +++ b/config/example/custom_items.json @@ -0,0 +1,30 @@ +[ + { + "ckey": "arrow768", + "character_name": "Jane Doe", + "item_path": "/obj/item/toy/plushie", + "item_data": {"name":"ugly plush toy","desc":"It's truly hideous."}, + "req_titles": ["Assistant", "Security Officer"] + }, + { + "ckey": "arrow768", + "character_name": "Jane Doe", + "item_path": "/obj/item/device/kit/paint", + "item_data": {"name":"APLU customisation kit","desc":"It's truly hideous."}, + "additional_data": "ripley, firefighter" + }, + { + "ckey": "arrow768", + "character_name": "Jane Doe", + "item_path": "/obj/item/device/kit/suit", + "item_data": {"name":"salvage suit customisation kit","desc":"A customisation kit with all the parts needed to convert a suit."} + }, + { + "ckey": "arrow768", + "character_name": "John Doe", + "item_path": "/obj/item/gun/energy/lawgiver", + "item_name":"Executioner", + "item_desc":"When you really need to motivate your crew." + } +] + diff --git a/config/example/custom_items.txt b/config/example/custom_items.txt deleted file mode 100644 index 79fb05a4c18..00000000000 --- a/config/example/custom_items.txt +++ /dev/null @@ -1,41 +0,0 @@ -## -# Custom items go here. They are modifications of existing paths; look at the example for details. -# Item will spawn if the target has one of the req_titles and if their on-spawn ID has the required access level. -# req_access is going to be a shit to maintain since the config file can't grab constants and has to use integers, use it minimally. -# Separate titles with a single comma and a space (', ') or they'll bork. -# -# EX: -# { -# ckey: zuhayr -# character_name: Jane Doe -# item_path: /obj/item/toy/plushie -# item_name: ugly plush toy -# item_icon: flagmask -# item_desc: It's truly hideous. -# req_titles: Assistant, Security Officer -# req_access: 1 -# } -# -# { -# ckey: zuhayr -# character_name: Jane Doe -# item_path: /obj/item/device/kit/paint -# item_name: APLU customisation kit -# item_desc: A customisation kit with all the parts needed to turn an APLU into a "Titan's Fist" model. -# kit_name: APLU "Titan's Fist" -# kit_desc: Looks like an overworked, under-maintained Ripley with some horrific damage. -# kit_icon: titan -# additional_data: ripley, firefighter -# } -# -# { -# ckey: zuhayr -# character_name: Jane Doe -# item_path: /obj/item/device/kit/suit -# item_name: salvage suit customisation kit -# item_desc: A customisation kit with all the parts needed to convert a suit. -# kit_name: salvage -# kit_desc: An orange voidsuit. Reinforced! -# kit_icon: salvage -# } -## diff --git a/html/changelog.html b/html/changelog.html index d853a0b9651..0eca3bd3a4b 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -35,10 +35,123 @@ -->
+

02 January 2021

+

Geeves updated:

+ + +

01 January 2021

+

Ferner updated:

+ +

Ferner, sekritsanter updated:

+ +

Geeves updated:

+ +

Sparky_hotdog updated:

+ + +

31 December 2020

+

Geeves updated:

+ +

Sparky_Hotdog updated:

+ + +

28 December 2020

+

Geeves updated:

+ +

MoondancerPony updated:

+ +

Skull132 updated:

+ +

Wowzewow (Wezzy) updated:

+ + +

27 December 2020

+

Alberyk, Kyres1 updated:

+ + +

26 December 2020

+

Geeves updated:

+ +

Karolis2011 updated:

+ + +

25 December 2020

+

Arrow768 updated:

+ +

Ferner updated:

+ +

Geeves updated:

+ +

24 December 2020

Alberyk updated:

Geeves updated: