diff --git a/code/__defines/~mods/~master_defines.dm b/code/__defines/~mods/~master_defines.dm index 8b77841503b83..5677b37adacd7 100644 --- a/code/__defines/~mods/~master_defines.dm +++ b/code/__defines/~mods/~master_defines.dm @@ -53,3 +53,7 @@ // LOADOUT_ITEMS - Start #define ACCESSORY_SLOT_OVER "Over" // LOADOUT_ITEMS - End + +// IPC_COOLING_UNIT - Start +#define BP_COOLING "cooling system" +// IPC_COOLING_UNIT - End diff --git a/mods/global_modpacks.dm b/mods/global_modpacks.dm index 227eca056369d..2d50f1543e86b 100644 --- a/mods/global_modpacks.dm +++ b/mods/global_modpacks.dm @@ -15,6 +15,7 @@ #include "jukebox_tapes/_jukebox_tapes.dme" #include "loadout_items/_loadout_items.dme" #include "lobbyscreen/_lobbyscreen.dme" +#include "ipc_cooling_unit/_ipc_cooling_unit.dme" #include "music_player/_music_player.dme" #include "ntnet/_ntnet.dme" #include "nyc_posters/_nyc_posters.dme" diff --git a/mods/ipc_cooling_unit/README.md b/mods/ipc_cooling_unit/README.md new file mode 100644 index 0000000000000..cc5d91047fc1c --- /dev/null +++ b/mods/ipc_cooling_unit/README.md @@ -0,0 +1,78 @@ + +#### Список PRов: + +- https://github.com/SierraBay/SierraBay12/pull/1782 + + + +## IPC Cooling organ + +ID мода: IPC_COOLING_UNIT + + +### Описание мода + +Добавляет орган охлаждения для ИПС и внешнее охлаждающее устройство для ИПС + + +### Изменения *кор кода* +- Отсутствуют + + + +### Оверрайды + +- `mods/ipc_cooling_unit/code/machine.dm`: + - `/datum/species/machine/passive_temp_gain` + - `/datum/species/machine/New()` + - `/obj/machinery/organ_printer/robot/New()` + - `/mob/living/carbon/human/Stat()` + - `/obj/item/organ/internal/cell/Process()` + + + +### Дефайны + +- `code/__defines/~mods/~master_defines.dm`: `BP_COOLING`, + +### Используемые файлы, не содержащиеся в модпаке +- Отсуствует + + + +### Авторы: + +Lexanx + diff --git a/mods/ipc_cooling_unit/_ipc_cooling_unit.dm b/mods/ipc_cooling_unit/_ipc_cooling_unit.dm new file mode 100644 index 0000000000000..fcea627b07451 --- /dev/null +++ b/mods/ipc_cooling_unit/_ipc_cooling_unit.dm @@ -0,0 +1,3 @@ +/singleton/modpack/ipc_cooling_unit + name = "Орган Охлаждения ИПС" + author = "Lexanx" diff --git a/mods/ipc_cooling_unit/_ipc_cooling_unit.dme b/mods/ipc_cooling_unit/_ipc_cooling_unit.dme new file mode 100644 index 0000000000000..6366838019bac --- /dev/null +++ b/mods/ipc_cooling_unit/_ipc_cooling_unit.dme @@ -0,0 +1,9 @@ +#ifndef MODPACK_IPC_COOLING_UNIT +#define MODPACK_IPC_COOLING_UNIT + +#include "_ipc_cooling_unit.dm" +#include "code/cooling_system.dm" +#include "code/external_cooling_device.dm" +#include "code/machine.dm" + +#endif diff --git a/mods/ipc_cooling_unit/code/cooling_system.dm b/mods/ipc_cooling_unit/code/cooling_system.dm new file mode 100644 index 0000000000000..c0ecff7f23a65 --- /dev/null +++ b/mods/ipc_cooling_unit/code/cooling_system.dm @@ -0,0 +1,139 @@ +/obj/item/organ/internal/cooling_system + name = "cooling system" + icon = 'mods/ipc_cooling_unit/icons/ipc_icons.dmi' + icon_state = "cooling0" + organ_tag = BP_COOLING + parent_organ = BP_GROIN + status = ORGAN_ROBOTIC + desc = "The internal liquid cooling system consists of a weighty humming cylinder and a small ribbed block connected by flexible tubes through which clear liquid flows." + var/refrigerant_max = 90 // Максимальное количество охладителя + var/refrigerant_rate = 5 // Чем больше это значение, тем сильнее будет идти нагрев владельца. + var/durability_factor = 30 // Чем больше это значение, тем сильнее будет идти нагрев владельца при повреждениях + var/safety = 1 + damage_reduction = 0.8 + max_damage = 50 + var/sprite_name = "cooling" + var/fresh_coolant = 0 + var/coolant_purity = 0 + var/datum/reagents/coolant_reagents + var/used_coolant = 0 + var/heating_modificator + var/list/coolant_reagents_efficiency = list() + var/coolant_reagent_water + +/obj/item/organ/internal/cooling_system/New() + robotize() + create_reagents(refrigerant_max) + coolant_reagents_efficiency[/datum/reagent/water] = 17 + coolant_reagents_efficiency[/datum/reagent/ethanol] = 10 + coolant_reagents_efficiency[/datum/reagent/space_cleaner] = 5 + coolant_reagents_efficiency[/datum/reagent/sterilizine] = 3 + coolant_reagents_efficiency[/datum/reagent/coolant] = 0.1 + reagents.add_reagent(/datum/reagent/coolant, 60) + reagents.add_reagent(/datum/reagent/water, 30) + ..() + +/obj/item/organ/internal/cooling_system/emp_act(severity) + damage += rand(15 - severity * 5, 20 - severity * 5) + ..() +// Коэффицент эффективности работы смеси +/obj/item/organ/internal/cooling_system/proc/coolant_purity() + var/total_purity = 0 + fresh_coolant = 0 + coolant_purity = 0 + for (var/datum/reagent/current_reagent in src.reagents.reagent_list) + if (!current_reagent) + continue + var/cur_purity = coolant_reagents_efficiency[current_reagent.type] + if(!cur_purity) + cur_purity = 25 + else if(cur_purity < 0.1) + cur_purity = 0.1 + total_purity += cur_purity * current_reagent.volume + fresh_coolant += current_reagent.volume + if(total_purity && fresh_coolant) + coolant_purity = total_purity / fresh_coolant + heating_modificator = coolant_purity + + + + +/obj/item/organ/internal/cooling_system/proc/get_coolant_drain() + var/damage_factor = (damage*durability_factor)/max_damage + return damage_factor + +/obj/item/organ/internal/cooling_system/Process() + + if(!owner || owner.stat == DEAD || owner.bodytemperature < 32) + return + coolant_purity() + handle_cooling() + ..() + +/obj/item/organ/internal/cooling_system/proc/handle_cooling() + + var/obj/item/organ/internal/cell/C = owner.internal_organs_by_name[BP_CELL] + refrigerant_rate = heating_modificator + if (C && C.get_charge() < 25) + return + if(reagents.total_volume >= 0) + var/bruised_cost = get_coolant_drain() + + if(is_bruised()) + var/reagents_remove = bruised_cost/durability_factor + reagents.remove_any(reagents_remove) + + if(is_damaged()) + get_coolant_drain() + refrigerant_rate += bruised_cost // Нагрев владельца при повреждениях высчитывается тут. + + if(reagents.get_reagent_amount(/datum/reagent/water) <= (0.3 * reagents.total_volume)) + var/need_more_water = ((refrigerant_max - reagents.get_reagent_amount(/datum/reagent/water))/100) + take_internal_damage(need_more_water) + + if(reagents.total_volume <= 0) + refrigerant_rate += 40 + +/obj/item/organ/internal/cooling_system/proc/get_tempgain() + if(owner.bodytemperature > 950 CELSIUS) + return 0 + if(refrigerant_rate > 0) + return refrigerant_rate + +/obj/item/organ/internal/cooling_system/proc/get_coolant_remaining() + if(status & ORGAN_DEAD) + return 0 + return round(reagents.total_volume) + +/obj/item/organ/internal/cooling_system/examine(mob/user, distance) + . = ..() + if(distance <= 0) + to_chat(user, text("[icon2html(src, viewers(get_turf(src)))] [] contains [] units of liquid left!", src, src.reagents.total_volume)) + +/obj/item/organ/internal/cooling_system/attack_self(mob/user as mob) + safety = !safety + src.icon_state = "[sprite_name][!safety]" + src.desc = "The injection is [safety ? "on" : "off"]." + to_chat(user, "The injection is [safety ? "on" : "off"].") + + +/obj/item/organ/internal/cooling_system/afterattack(atom/target, mob/user, flag) + var/obj/item/reagent_containers/glass/beaker = target + if (!flag || !istype(beaker)) + return ..() + + var/amount = reagents.get_free_space() + if (safety) + if (amount <= 0) + to_chat(user, SPAN_NOTICE("\The [src] is full.")) + return + if (beaker.reagents.total_volume <= 0) + to_chat(user, SPAN_NOTICE("\The [beaker] is empty.")) + return + amount = beaker.reagents.trans_to_obj(src, refrigerant_max) + to_chat(user, SPAN_NOTICE("You fill \the [src] with [amount] units from \the [beaker].")) + playsound(src.loc, 'sound/effects/pour.ogg', 25, 1) + else + amount = src.reagents.trans_to_obj(beaker, refrigerant_max) + to_chat(user, SPAN_NOTICE("You fill \the [beaker] with [amount] units from \the [src].")) + playsound(src.loc, 'sound/effects/pour.ogg', 25, 1) diff --git a/mods/ipc_cooling_unit/code/external_cooling_device.dm b/mods/ipc_cooling_unit/code/external_cooling_device.dm new file mode 100644 index 0000000000000..f00031e35d908 --- /dev/null +++ b/mods/ipc_cooling_unit/code/external_cooling_device.dm @@ -0,0 +1,230 @@ +/obj/machinery/external_cooling_device + name = "\improper External Cooling Device" + icon = 'mods/ipc_cooling_unit/icons/ipc_icons.dmi' + icon_state = "basepowered" + desc = "It's a bulky machine that delivers life-giving cold through a hose." + anchored = FALSE + density = TRUE + var/mob/living/carbon/human/attached + var/obj/item/cell/cell = /obj/item/cell/high + var/active = FALSE + var/closed = TRUE + var/set_temperature = T0C + +/obj/machinery/external_cooling_device/New() + ..() + if(ispath(cell)) + cell = new cell(src) + update_icon() + +/obj/machinery/external_cooling_device/examine(mob/user) + . = ..() + + to_chat(user, "The external cooling device is [active ? "on" : "off"] and the hatch is [!closed ? "open" : "closed"].") + if(!closed) + to_chat(user, "The power cell is [cell ? "installed" : "missing"].") + else + to_chat(user, "The charge meter reads [cell ? round(cell.percent(),1) : 0]%") + + +/obj/machinery/external_cooling_device/Topic(href, href_list, state = GLOB.physical_state) + if (..()) + return 1 + + switch(href_list["op"]) + + if("temp") + var/value = text2num(href_list["val"]) + + // limit to 0-90 degC + set_temperature = dd_range(T0C, T0C + 90, set_temperature + value) + + if("cellremove") + if(!closed && cell && !usr.get_active_hand()) + usr.visible_message( + SPAN_NOTICE("The [usr] removes \the [cell] from \the [src]."), + SPAN_NOTICE("You remove \the [cell] from \the [src].") + ) + cell.update_icon() + usr.put_in_hands(cell) + cell.add_fingerprint(usr) + cell = null + + if("cellinstall") + if(!closed && !cell) + var/obj/item/cell/C = usr.get_active_hand() + if(istype(C)) + if(!usr.unEquip(C, src)) + return TOPIC_NOACTION + cell = C + C.add_fingerprint(usr) + usr.visible_message( + SPAN_NOTICE("\The [usr] inserts \the [C] into \the [src]."), + SPAN_NOTICE("You insert \the [C] into \the [src].") + ) + + if("Power_On") + if(cell) + active = !active + + if("Power_Off") + active = !active + + update_icon() + updateDialog() + +/obj/machinery/external_cooling_device/emp_act(severity) + if(cell) + cell.emp_act(severity) + ..(severity) + + +/obj/machinery/external_cooling_device/interface_interact(mob/user) + interact(user) + return TRUE + +/obj/machinery/external_cooling_device/interact(mob/user) + var/list/dat = list() + dat += "Power cell: " + if(cell) + dat += "Installed
" + else + dat += "Removed
" + + if(!active) + dat += "Power On
" + else + dat += "Power Off
" + + dat += "Power Level: [cell ? round(cell.percent(),1) : 0]%

" + + dat += "Set Temperature: " + + dat += "-" + + dat += " [set_temperature]K ([set_temperature-T0C]°C)" + dat += "+
" + + var/datum/browser/popup = new(usr, "spaceheater", "External Cooling Device Control Panel") + popup.set_content(jointext(dat, null)) + popup.set_title_image(usr.browse_rsc_icon(src.icon, "sheater-standby")) + popup.open() + + +/obj/machinery/external_cooling_device/on_update_icon(rebuild_overlay = 1) + if(!cell) + icon_state = "base" + else + icon_state = "basepowered" + + if(rebuild_overlay) + ClearOverlays() + if(attached) + AddOverlays("o_h") + if(!closed) + AddOverlays("o_m") + if(active && cell) + AddOverlays("o_w") + + +/obj/machinery/external_cooling_device/MouseDrop(over_object, src_location, over_location) + if(!CanMouseDrop(over_object)) + return + if(attached) + cooling_detach() + else if(ishuman(over_object)) + hook_up(over_object, usr) + +/obj/machinery/external_cooling_device/use_tool(obj/item/W, mob/living/user, list/click_params) + + if(istype(W, /obj/item/screwdriver)) + closed = !closed + playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1) + to_chat(user, SPAN_NOTICE("You [closed ? "tighten" : "unscrew"] ECD panel")) + on_update_icon() + if(!closed) + if (istype(W, /obj/item/cell)) + if(!isnull(src.cell)) + USE_FEEDBACK_FAILURE("There is already a cell loaded!") + return + if(!user.unEquip(W, src)) + return + cell = W + to_chat(user, "You attach \the [W] to \the [src].") + on_update_icon() + else + return ..() + + +/obj/machinery/external_cooling_device/Destroy() + STOP_PROCESSING(SSobj,src) + attached = null + QDEL_NULL(cell) + . = ..() + +/obj/machinery/external_cooling_device/Process() + + if(!cell) + return + + if(attached) + if(!Adjacent(attached)) + rip_out() + return + if(active) + if(!attached) + return + if(attached.bodytemperature > set_temperature) + attached.bodytemperature -= 20 + queue_icon_update() + cell.use(5) + +/obj/machinery/external_cooling_device/verb/cooling_detach() + set category = "Object" + set name = "Detach cooling device" + set src in range(1) + + if(!attached) + return + + if(!CanPhysicallyInteractWith(usr, src)) + to_chat(usr, SPAN_WARNING("You're in no condition to do that!")) + return + + if(!usr.skill_check(SKILL_DEVICES, SKILL_BASIC)) + rip_out() + else + visible_message(SPAN_NOTICE("\The [attached] is taken off \the [src].")) + attached = null + update_icon() + + +/obj/machinery/external_cooling_device/proc/rip_out() + visible_message(SPAN_WARNING("\The tube is ripped out of \the [src.attached]!")) + attached.apply_damage(1, DAMAGE_BRUTE, pick(BP_GROIN, BP_CHEST), damage_flags=DAMAGE_FLAG_SHARP) + attached = null + update_icon() + +/obj/machinery/external_cooling_device/proc/hook_up(mob/living/carbon/human/target, mob/user) + if(do_ECD_hookup(target, user, src)) + attached = target + update_icon() + +/obj/machinery/external_cooling_device/proc/do_ECD_hookup(mob/living/carbon/human/target, mob/user, obj/ECD) + to_chat(user, SPAN_NOTICE("You start to hook up \the [target] to \the [ECD].")) + if(!user.do_skilled(2 SECONDS, SKILL_DEVICES, target)) + return FALSE + + if(prob(user.skill_fail_chance(SKILL_DEVICES, 40, SKILL_MIN))) + user.visible_message( + SPAN_WARNING("\The [user] fails while trying to hook \the [target] up to \the [ECD], stabbing them instead!"), + SPAN_WARNING("You fail while trying to hook \the [target] up to \the [ECD], stabbing yourself instead!") + ) + target.apply_damage(5, DAMAGE_BRUTE, pick(BP_GROIN, BP_CHEST), damage_flags=DAMAGE_FLAG_SHARP) + return FALSE + + user.visible_message( + SPAN_NOTICE("\The [user] hooks \the [target] up to \the [ECD]."), + SPAN_NOTICE("You hook \the [target] up to \the [ECD]") + ) + return TRUE diff --git a/mods/ipc_cooling_unit/code/machine.dm b/mods/ipc_cooling_unit/code/machine.dm new file mode 100644 index 0000000000000..e820bb1ea56b6 --- /dev/null +++ b/mods/ipc_cooling_unit/code/machine.dm @@ -0,0 +1,40 @@ +/datum/species/machine + passive_temp_gain = 0 // This should cause IPCs to stabilize at ~80 C in a 20 C environment.(5 is default without organ) + + +/datum/species/machine/New() + LAZYINITLIST(has_organ) + has_organ[BP_COOLING] = /obj/item/organ/internal/cooling_system + ..() + +// ROBOT ORGAN PRINTER +/obj/machinery/organ_printer/robot/New() + LAZYINITLIST(products) + products[BP_COOLING] = list(/obj/item/organ/internal/cooling_system, 35) + . = ..() + + +/mob/living/carbon/human/Stat() + . = ..() + if(statpanel("Status")) + var/obj/item/organ/internal/cell/potato = internal_organs_by_name[BP_CELL] + var/obj/item/organ/internal/cooling_system/coolant = internal_organs_by_name[BP_COOLING] + if(potato && potato.cell) + stat("Coolant remaining:","[coolant.get_coolant_remaining()]/[coolant.refrigerant_max]") + +/obj/item/organ/internal/cell/Process() + ..() + var/cost = get_power_drain() + if(!checked_use(cost) && owner.isSynthetic()) + if(owner.species.name == SPECIES_IPC) + owner.species.passive_temp_gain = 0 + if(owner.species.name == SPECIES_IPC) + var/obj/item/organ/internal/cooling_system/cooling_organ = owner.internal_organs_by_name[BP_COOLING] + var/normal_passive_temp_gain = 30 + if(!cooling_organ) + if(owner.bodytemperature > 950 CELSIUS) + owner.species.passive_temp_gain = 0 + else + owner.species.passive_temp_gain = normal_passive_temp_gain + else + owner.species.passive_temp_gain = cooling_organ.get_tempgain() diff --git a/mods/ipc_cooling_unit/icons/ipc_icons.dmi b/mods/ipc_cooling_unit/icons/ipc_icons.dmi new file mode 100644 index 0000000000000..921934a57e5f9 Binary files /dev/null and b/mods/ipc_cooling_unit/icons/ipc_icons.dmi differ