diff --git a/code/__defines/faction.dm b/code/__defines/faction.dm index 0d46529297b..2d975b6cbb6 100644 --- a/code/__defines/faction.dm +++ b/code/__defines/faction.dm @@ -21,6 +21,7 @@ #define FACTION_ALTEVIAN "altevian" #define FACTION_CULT "cult" +#define FACTION_GLAMOUR "glamour" #define FACTION_PIRATE "pirate" #define FACTION_SHADEKIN "shadekin" #define FACTION_SPACE_GHOST "space ghost" diff --git a/code/__defines/jobs.dm b/code/__defines/jobs.dm index 40916e9ed40..6d625e96092 100644 --- a/code/__defines/jobs.dm +++ b/code/__defines/jobs.dm @@ -345,7 +345,7 @@ //___________________________________________________________ -// Talom jobs +// Talon jobs #define JOB_TALON_CAPTAIN "Talon Captain" // Talon Captain alt titles #define JOB_ALT_TALON_COMMANDER "Talon Commander" diff --git a/code/datums/underwear/top.dm b/code/datums/underwear/top.dm index 336bebd1cc5..a977717f425 100644 --- a/code/datums/underwear/top.dm +++ b/code/datums/underwear/top.dm @@ -78,6 +78,10 @@ name = "Binder" icon_state = "binder_s" +/datum/category_item/underwear/top/binder_hyper + name = "HYPER Binder" + icon_state = "hyper_binder" + /datum/category_item/underwear/top/straplessbinder name = "Binder Strapless" icon_state = "straplessbinder_s" diff --git a/code/game/jobs/access_datum.dm b/code/game/jobs/access_datum.dm index 32773362a01..d5d9e201392 100644 --- a/code/game/jobs/access_datum.dm +++ b/code/game/jobs/access_datum.dm @@ -493,6 +493,7 @@ var/const/access_pilot = 67 /var/const/access_syndicate = 150//General Syndicate Access /datum/access/syndicate id = access_syndicate + desc = "Syndicate" access_type = ACCESS_TYPE_SYNDICATE /******* @@ -507,11 +508,13 @@ var/const/access_pilot = 67 /var/const/access_crate_cash = 200 /datum/access/crate_cash id = access_crate_cash + desc = "Crate cash" access_type = ACCESS_TYPE_NONE /var/const/access_trader = 160//General Beruang Trader Access /datum/access/trader id = access_trader + desc = "Trader" access_type = ACCESS_TYPE_PRIVATE /var/const/access_alien = 300 // For things like crashed ships. diff --git a/code/game/jobs/job/civilian_vr.dm b/code/game/jobs/job/civilian_vr.dm index c7e2a2c7857..bd4093e4033 100644 --- a/code/game/jobs/job/civilian_vr.dm +++ b/code/game/jobs/job/civilian_vr.dm @@ -74,7 +74,7 @@ /datum/alt_title/supply_courier title = JOB_ALT_SUPPLY_COURIER - title_blurb = "A " + JOB_ALT_SUPPLY_COURIER + " is usually tasked with devlivering packages or cargo directly to whoever requires it." + title_blurb = "A " + JOB_ALT_SUPPLY_COURIER + " is usually tasked with delivering packages or cargo directly to whoever requires it." /datum/alt_title/cargo_loader title = JOB_ALT_CARGO_LOADER diff --git a/code/game/objects/micro_structures.dm b/code/game/objects/micro_structures.dm index ba05bf5724e..1f67efd6ed5 100644 --- a/code/game/objects/micro_structures.dm +++ b/code/game/objects/micro_structures.dm @@ -1,3 +1,5 @@ +var/global/list/micro_tunnels = list() + /obj/structure/micro_tunnel name = "mouse hole" desc = "A tiny little hole... where does it go?" @@ -18,6 +20,10 @@ /mob/living/simple_mob/slime ) +/obj/structure/micro_tunnel/New() + . = ..() + micro_tunnels.Add(src) + /obj/structure/micro_tunnel/Initialize() . = ..() if(name == initial(name)) @@ -34,6 +40,8 @@ thing.forceMove(get_turf(src.loc)) thing.cancel_camera() + micro_tunnels.Remove(src) + return ..() /obj/structure/micro_tunnel/set_dir(new_dir) @@ -63,7 +71,7 @@ if(myturf.z in P.expected_z_levels) planet = P else - for(var/obj/structure/micro_tunnel/t in world) + for(var/obj/structure/micro_tunnel/t in micro_tunnels) if(t == src) continue if(magic || t.magic) @@ -342,6 +350,8 @@ return if(QDELETED(src)) return + if(usr.loc != src) + return var/obj/our_choice = choice var/list/new_contained_mobs = list() diff --git a/code/game/turfs/simulated/water.dm b/code/game/turfs/simulated/water.dm index fc6f5b261e4..f6477e9764a 100644 --- a/code/game/turfs/simulated/water.dm +++ b/code/game/turfs/simulated/water.dm @@ -73,7 +73,7 @@ water_breath.adjust_gas(gasid, BREATH_MOLES) // They have no oxygen, but non-zero moles and temp water_breath.temperature = above_air.temperature return water_breath - if(L && L.is_bad_swimmer() && depth >= 2 && !L.buckled()) + if(L && L.is_bad_swimmer() && depth >= 2 && !L.buckled() && !L.flying) if(prob(10)) L.visible_message("[L] splashes wildly.","You struggle to keep your head above the water!") if(L.can_breathe_water()) diff --git a/code/modules/admin/modify_robot.dm b/code/modules/admin/modify_robot.dm new file mode 100644 index 00000000000..3c1682f40ee --- /dev/null +++ b/code/modules/admin/modify_robot.dm @@ -0,0 +1,641 @@ +/client/proc/modify_robot(var/mob/living/silicon/robot/target in silicon_mob_list) + set name = "Modify Robot" + set desc = "Allows to add or remove modules to/from robots." + set category = "Admin" + if(!check_rights(R_ADMIN|R_FUN|R_VAREDIT|R_EVENT)) + return + + var/datum/eventkit/modify_robot/modify_robot = new() + modify_robot.target = target + modify_robot.tgui_interact(src.mob) + +/datum/eventkit/modify_robot + var/mob/living/silicon/robot/target + var/mob/living/silicon/robot/source + var/ion_law = "IonLaw" + var/zeroth_law = "ZerothLaw" + var/inherent_law = "InherentLaw" + var/supplied_law = "SuppliedLaw" + var/supplied_law_position = MIN_SUPPLIED_LAW_NUMBER + var/list/datum/ai_laws/law_list + +/datum/eventkit/modify_robot/New() + . = ..() + log_and_message_admins("has used modify robot and is modifying [target]") + law_list = new() + init_subtypes(/datum/ai_laws, law_list) + law_list = dd_sortedObjectList(law_list) + +/datum/eventkit/modify_robot/tgui_close() + if(source) + qdel(source) + +/datum/eventkit/modify_robot/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ModifyRobot", "Modify Robot") + ui.open() + +/datum/eventkit/modify_robot/Destroy() + if(source) + qdel(source) + . = ..() + +/datum/eventkit/modify_robot/tgui_data(mob/user) + . = list() + // Target section for general data + if(target) + .["target"] = list() + .["target"]["name"] = target.name + .["target"]["ckey"] = target.ckey + .["target"]["module"] = target.module + .["target"]["crisis_override"] = target.crisis_override + .["target"]["active_restrictions"] = target.restrict_modules_to + var/list/possible_restrictions = list() + for(var/entry in robot_modules) + if(!target.restrict_modules_to.Find(entry)) + possible_restrictions += entry + .["target"]["possible_restrictions"] = possible_restrictions + // Target section for options once a module has been selected + if(target.module) + .["target"]["active"] = target.icon_selected + .["target"]["front"] = icon2base64(get_flat_icon(target,dir=SOUTH,no_anim=TRUE)) + .["target"]["side"] = icon2base64(get_flat_icon(target,dir=WEST,no_anim=TRUE)) + .["target"]["side_alt"] = icon2base64(get_flat_icon(target,dir=EAST,no_anim=TRUE)) + .["target"]["back"] = icon2base64(get_flat_icon(target,dir=NORTH,no_anim=TRUE)) + .["target"]["modules"] = get_target_items(user) + var/list/module_options = list() + for(var/module in robot_modules) + module_options += module + .["model_options"] = module_options + // Data for the upgrade options + .["target"] += get_upgrades() + var/obj/item/weapon/gun/energy/kinetic_accelerator/kin = locate() in target.module.modules + if(kin) + .["target"]["pka"] += get_pka(kin) + // Radio section + var/list/radio_channels = list() + for(var/channel in target.radio.channels) + radio_channels += channel + var/list/availalbe_channels = list() + for(var/channel in (radiochannels - target.radio.channels)) + availalbe_channels += channel + .["target"]["radio_channels"] = radio_channels + .["target"]["availalbe_channels"] = availalbe_channels + // Components + .["target"]["components"] = get_components() + .["cell"] = target.cell?.name + .["cell_options"] = get_cells() + // Access + .["id_icon"] = icon2html(target.idcard, user, sourceonly=TRUE) + var/list/active_access = list() + for(var/access in target.idcard?.GetAccess()) + active_access += list(list("id" = access, "name" = get_access_desc(access))) + .["target"]["active_access"] = active_access + var/list/access_options = list() + for(var/datum/access/acc) + if(acc.id in target.idcard?.GetAccess()) + continue + access_options += list(list("id" = acc.id, "name" = acc.desc)) + .["access_options"] = access_options + // Section for source data for the module we might want to salvage + if(source) + .["source"] += get_module_source(user) + var/list/all_robots = list() + for(var/mob/living/silicon/robot/R in silicon_mob_list) + if(!R.loc) + continue + all_robots += list(list("displayText" = "[R]", "value" = "\ref[R]")) + .["all_robots"] = all_robots + // Law data + .["ion_law_nr"] = ionnum() + .["ion_law"] = ion_law + .["zeroth_law"] = zeroth_law + .["inherent_law"] = inherent_law + .["supplied_law"] = supplied_law + .["supplied_law_position"] = supplied_law_position + + package_laws(., "zeroth_laws", list(target.laws.zeroth_law)) + package_laws(., "ion_laws", target.laws.ion_laws) + package_laws(., "inherent_laws", target.laws.inherent_laws) + package_laws(., "supplied_laws", target.laws.supplied_laws) + + .["isAI"] = isAI(target) + + var/list/channels = list() + for(var/ch_name in target.law_channels()) + channels[++channels.len] = list("channel" = ch_name) + .["channel"] = target.lawchannel + .["channels"] = channels + .["law_sets"] = package_multiple_laws(law_list) + + +/datum/eventkit/modify_robot/tgui_state(mob/user) + return GLOB.tgui_admin_state + +/datum/eventkit/modify_robot/tgui_act(action, params) + . = ..() + if(.) + return + switch(action) + if("rename") + target.name = params["new_name"] + target.custom_name = params["new_name"] + target.real_name = params["new_name"] + return TRUE + if("select_target") + target = locate(params["new_target"]) + log_and_message_admins("changed robot modifictation target to [target]") + return TRUE + if("toggle_crisis") + target.crisis_override = !target.crisis_override + return TRUE + if("add_restriction") + target.restrict_modules_to += params["new_restriction"] + return TRUE + if("remove_restriction") + target.restrict_modules_to -= params["rem_restriction"] + return TRUE + if("select_source") + if(source) + qdel(source) + source = new /mob/living/silicon/robot(null) + var/module_type = robot_modules[params["new_source"]] + source.modtype = params["new_source"] + var/obj/item/weapon/robot_module/robot/robot_type = new module_type(source) + source.sprite_datum = pick(SSrobot_sprites.get_module_sprites(source.modtype, source)) + source.update_icon() + source.emag_items = 1 + if(!istype(robot_type, /obj/item/weapon/robot_module/robot)) + QDEL_NULL(source) + return TRUE + return TRUE + if("reset_module") + target.module_reset(FALSE) + return TRUE + if("add_module") + var/obj/item/add_item = locate(params["module"]) + if(!add_item) + return TRUE + source.module.emag.Remove(add_item) + source.module.modules.Remove(add_item) + source.module.contents.Remove(add_item) + target.module.modules.Add(add_item) + target.module.contents.Add(add_item) + spawn(0) + SEND_SIGNAL(add_item, COMSIG_OBSERVER_MOVED) + target.hud_used.update_robot_modules_display() + if(istype(add_item, /obj/item/stack/)) + var/obj/item/stack/item_with_synth = add_item + for(var/synth in item_with_synth.synths) + var/found = target.module.synths.Find(synth) + if(!found) + source.module.synths.Remove(synth) + target.module.synths.Add(synth) + else + item_with_synth.synths = list(target.module.synths[found]) + return TRUE + if(istype(add_item, /obj/item/weapon/matter_decompiler/) || istype(add_item, /obj/item/device/dogborg/sleeper/compactor/decompiler/)) + var/obj/item/weapon/matter_decompiler/item_with_matter = add_item + if(item_with_matter.metal) + var/found = target.module.synths.Find(item_with_matter.metal) + if(!found) + source.module.synths.Remove(item_with_matter.metal) + target.module.synths.Add(item_with_matter.metal) + else + item_with_matter.metal = target.module.synths[found] + if(item_with_matter.glass) + var/found = target.module.synths.Find(item_with_matter.glass) + if(!found) + source.module.synths.Remove(item_with_matter.glass) + target.module.synths.Add(item_with_matter.glass) + else + item_with_matter.glass = target.module.synths[found] + if(item_with_matter.wood) + var/found = target.module.synths.Find(item_with_matter.wood) + if(!found) + source.module.synths.Remove(item_with_matter.wood) + target.module.synths.Add(item_with_matter.wood) + else + item_with_matter.wood = target.module.synths[found] + if(item_with_matter.plastic) + var/found = target.module.synths.Find(item_with_matter.plastic) + if(!found) + source.module.synths.Remove(item_with_matter.plastic) + target.module.synths.Add(item_with_matter.plastic) + else + item_with_matter.plastic = target.module.synths[found] + return TRUE + if("rem_module") + var/obj/item/rem_item = locate(params["module"]) + target.uneq_all() + target.hud_used.update_robot_modules_display(TRUE) + target.module.emag.Remove(rem_item) + target.module.modules.Remove(rem_item) + target.module.contents.Remove(rem_item) + qdel(rem_item) + return TRUE + if("swap_module") + if(!source) + return FALSE + var/mod_type = source.modtype + qdel(source.module) + var/module_type = robot_modules[target.modtype] + source.modtype = target.modtype + new module_type(source) + source.sprite_datum = target.sprite_datum + source.update_icon() + source.emag_items = 1 + // Target + target.uneq_all() + target.hud_used.update_robot_modules_display(TRUE) + qdel(target.module) + target.modtype = mod_type + module_type = robot_modules[mod_type] + target.transform_with_anim() + new module_type(target) + target.hands.icon_state = target.get_hud_module_icon() + target.hud_used.update_robot_modules_display() + return TRUE + if("ert_toggle") + target.crisis_override = !target.crisis_override + target.module_reset(FALSE) + return TRUE + if("add_compatibility") + target.module.supported_upgrades |= text2path(params["upgrade"]) + return TRUE + if("rem_compatibility") + target.module.supported_upgrades.Remove(text2path(params["upgrade"])) + return TRUE + if("add_upgrade") + var/new_upgrade = text2path(params["upgrade"]) + if(new_upgrade == /obj/item/borg/upgrade/utility/reset) + var/obj/item/borg/upgrade/utility/reset/rmodul = new_upgrade + if(tgui_alert(usr, "Are you sure that you want to install [initial(rmodul.name)] and reset the robot's module?","Confirm",list("Yes","No"))!="Yes") + return FALSE + var/obj/item/borg/upgrade/U = new new_upgrade(null) + if(new_upgrade == /obj/item/borg/upgrade/utility/rename) + var/obj/item/borg/upgrade/utility/rename/UN = U + var/new_name = sanitizeSafe(tgui_input_text(usr, "Enter new robot name", "Robot Reclassification", UN.heldname, MAX_NAME_LEN), MAX_NAME_LEN) + if(new_name) + UN.heldname = new_name + U = UN + if(istype(U, /obj/item/borg/upgrade/restricted)) + target.module.supported_upgrades |= new_upgrade + if(!U.action(target)) + return FALSE + U.loc = target + target.hud_used.update_robot_modules_display() + return TRUE + if("install_modkit") + var/new_modkit = text2path(params["modkit"]) + var/obj/item/weapon/gun/energy/kinetic_accelerator/kin = locate() in target.module.modules + var/obj/item/borg/upgrade/modkit/M = new new_modkit(null) + M.install(kin, target) + return TRUE + if("remove_modkit") + var/obj/item/weapon/gun/energy/kinetic_accelerator/kin = locate() in target.module.modules + var/obj/item/rem_kit = locate(params["modkit"]) + kin.modkits.Remove(rem_kit) + qdel(rem_kit) + return TRUE + if("add_channel") + var/selected_radio_channel = params["channel"] + if(selected_radio_channel == CHANNEL_SPECIAL_OPS) + target.radio.centComm = 1 + if(selected_radio_channel == CHANNEL_RAIDER) + qdel(target.radio.keyslot) + target.radio.keyslot = new /obj/item/device/encryptionkey/raider(target) + target.radio.syndie = 1 + if(selected_radio_channel == CHANNEL_MERCENARY) + qdel(target.radio.keyslot) + target.radio.keyslot = new /obj/item/device/encryptionkey/syndicate(target) + target.radio.syndie = 1 + target.module.channels += list("[selected_radio_channel]" = 1) + target.radio.channels[selected_radio_channel] += target.module.channels[selected_radio_channel] + target.radio.secure_radio_connections[selected_radio_channel] += radio_controller.add_object(target.radio, radiochannels[selected_radio_channel], RADIO_CHAT) + return TRUE + if("rem_channel") + var/selected_radio_channel = params["channel"] + if(selected_radio_channel == CHANNEL_SPECIAL_OPS) + target.radio.centComm = 0 + target.module.channels -= selected_radio_channel + if((selected_radio_channel == CHANNEL_MERCENARY || selected_radio_channel == CHANNEL_RAIDER) && !(target.module.channels[CHANNEL_RAIDER] || target.module.channels[CHANNEL_MERCENARY])) + qdel(target.radio.keyslot) + target.radio.keyslot = null + target.radio.syndie = 0 + target.radio.channels = list() + for(var/n_chan in target.module.channels) + target.radio.channels[n_chan] -= target.module.channels[n_chan] + radio_controller.remove_object(target.radio, radiochannels[selected_radio_channel]) + target.radio.secure_radio_connections -= selected_radio_channel + return TRUE + if("add_component") + var/datum/robot_component/C = locate(params["component"]) + if(C.wrapped) + qdel(C.wrapped) + if(istype(C, /datum/robot_component/actuator)) + C.wrapped = new /obj/item/robot_parts/robot_component/actuator(target) + else if(istype(C, /datum/robot_component/radio)) + C.wrapped = new /obj/item/robot_parts/robot_component/radio(target) + else if(istype(C, /datum/robot_component/cell)) + var/new_cell = text2path(params["cell"]) + target.cell = new new_cell(target) + C.wrapped = target.cell + else if(istype(C, /datum/robot_component/diagnosis_unit)) + C.wrapped = new /obj/item/robot_parts/robot_component/diagnosis_unit(target) + else if(istype(C, /datum/robot_component/camera)) + C.wrapped = new /obj/item/robot_parts/robot_component/camera(target) + else if(istype(C, /datum/robot_component/binary_communication)) + C.wrapped = new /obj/item/robot_parts/robot_component/binary_communication_device(target) + else if(istype(C, /datum/robot_component/armour)) + C.wrapped = new /obj/item/robot_parts/robot_component/armour(target) + C.brute_damage = 0 + C.electronics_damage = 0 + C.install() + C.installed = 1 + return TRUE + if("rem_component") + var/datum/robot_component/C = locate(params["component"]) + if(!C.wrapped) + return FALSE + C.uninstall() + C.brute_damage = 0 + C.electronics_damage = 0 + C.installed = 0 + qdel(C.wrapped) + C.wrapped = null + if(istype(C, /datum/robot_component/cell)) + target.cell = null + return TRUE + if("add_access") + target.idcard.access += text2num(params["access"]) + return TRUE + if("rem_access") + target.idcard.access -= text2num(params["access"]) + return TRUE + if("add_centcom") + target.idcard.access |= get_all_centcom_access() + return TRUE + if("rem_centcom") + target.idcard.access -= get_all_centcom_access() + return TRUE + if("add_station") + target.idcard.access |= get_all_station_access() + target.idcard.access |= access_synth + return TRUE + if("rem_station") + target.idcard.access -= get_all_station_access() + target.idcard.access -= access_synth + return TRUE + if("law_channel") + if(params["law_channel"] in target.law_channels()) + target.lawchannel = params["law_channel"] + return TRUE + if("state_law") + var/datum/ai_law/AL = locate(params["ref"]) in target.laws.all_laws() + if(AL) + var/state_law = text2num(params["state_law"]) + target.laws.set_state_law(AL, state_law) + return TRUE + if("add_zeroth_law") + if(zeroth_law && !target.laws.zeroth_law) + target.set_zeroth_law(zeroth_law) + target.lawsync() + return TRUE + if("add_ion_law") + if(ion_law) + target.add_ion_law(ion_law) + target.lawsync() + return TRUE + if("add_inherent_law") + if(inherent_law) + target.add_inherent_law(inherent_law) + target.lawsync() + return TRUE + if("add_supplied_law") + if(supplied_law && supplied_law_position >= 1 && MIN_SUPPLIED_LAW_NUMBER <= MAX_SUPPLIED_LAW_NUMBER) + target.add_supplied_law(supplied_law_position, supplied_law) + target.lawsync() + return TRUE + if("change_zeroth_law") + var/new_law = sanitize(params["val"]) + if(new_law && new_law != zeroth_law) + zeroth_law = new_law + target.lawsync() + return TRUE + if("change_ion_law") + var/new_law = sanitize(params["val"]) + if(new_law && new_law != ion_law) + ion_law = new_law + target.lawsync() + return TRUE + if("change_inherent_law") + var/new_law = sanitize(params["val"]) + if(new_law && new_law != inherent_law) + inherent_law = new_law + target.lawsync() + return TRUE + if("change_supplied_law") + var/new_law = sanitize(params["val"]) + if(new_law && new_law != supplied_law) + supplied_law = new_law + target.lawsync() + return TRUE + if("change_supplied_law_position") + var/new_position = tgui_input_number(usr, "Enter new supplied law position between 1 and [MAX_SUPPLIED_LAW_NUMBER], inclusive. Inherent laws at the same index as a supplied law will not be stated.", "Law Position", supplied_law_position, MAX_SUPPLIED_LAW_NUMBER, 1) + if(isnum(new_position)) + supplied_law_position = CLAMP(new_position, 1, MAX_SUPPLIED_LAW_NUMBER) + target.lawsync() + return TRUE + if("edit_law") + var/datum/ai_law/AL = locate(params["edit_law"]) in target.laws.all_laws() + if(AL) + var/new_law = sanitize(tgui_input_text(usr, "Enter new law. Leaving the field blank will cancel the edit.", "Edit Law", AL.law)) + if(new_law && new_law != AL.law) + AL.law = new_law + target.lawsync() + return TRUE + if("delete_law") + var/datum/ai_law/AL = locate(params["delete_law"]) in target.laws.all_laws() + if(AL) + target.delete_law(AL) + target.lawsync() + return TRUE + if("state_laws") + target.statelaws(target.laws) + return TRUE + if("state_law_set") + var/datum/ai_laws/ALs = locate(params["state_law_set"]) in law_list + if(ALs) + target.statelaws(ALs) + return TRUE + if("transfer_laws") + var/datum/ai_laws/ALs = locate(params["transfer_laws"]) in law_list + if(ALs) + ALs.sync(target, 0) + target.lawsync() + return TRUE + if("notify_laws") + to_chat(target, "Law Notice") + target.laws.show_laws(target) + if(isAI(target)) + var/mob/living/silicon/ai/AI = target + for(var/mob/living/silicon/robot/R in AI.connected_robots) + to_chat(R, "Law Notice") + R.laws.show_laws(R) + if(usr != target) + to_chat(usr, "Laws displayed.") + return TRUE + +/datum/eventkit/modify_robot/proc/get_target_items(var/mob/user) + var/list/target_items = list() + for(var/obj/item in target.module.modules) + target_items += list(list("name" = item.name, "ref" = "\ref[item]", "icon" = icon2html(item, user, sourceonly=TRUE), "desc" = item.desc)) + return target_items + +/datum/eventkit/modify_robot/proc/get_module_source(var/mob/user) + var/list/source_list = list() + source_list["model"] = source.module + source_list["front"] = icon2base64(get_flat_icon(source,dir=SOUTH,no_anim=TRUE)) + var/list/source_items = list() + for(var/obj/item in (source.module.modules | source.module.emag)) + var/exists + for(var/obj/has_item in (target.module.modules + target.module.emag)) + if(has_item.name == item.name) + exists = TRUE + break + if(exists) + continue + source_items += list(list("name" = item.name, "ref" = "\ref[item]", "icon" = icon2html(item, user, sourceonly=TRUE), "desc" = item.desc)) + source_list["modules"] = source_items + return source_list + +/datum/eventkit/modify_robot/proc/get_upgrades() + var/list/all_upgrades = list() + var/list/whitelisted_upgrades = list() + var/list/blacklisted_upgrades = list() + for(var/datum/design/item/prosfab/robot_upgrade/restricted/upgrade) + if(!upgrade.name) + continue + if(!(initial(upgrade.build_path) in target.module.supported_upgrades)) + whitelisted_upgrades += list(list("name" = initial(upgrade.name), "path" = "[initial(upgrade.build_path)]")) + else + blacklisted_upgrades += list(list("name" = initial(upgrade.name), "path" = "[initial(upgrade.build_path)]")) + all_upgrades["whitelisted_upgrades"] = whitelisted_upgrades + all_upgrades["blacklisted_upgrades"] = blacklisted_upgrades + var/list/utility_upgrades = list() + for(var/datum/design/item/prosfab/robot_upgrade/utility/upgrade) + if(!upgrade.name) + continue + if(!(target.has_upgrade(initial(upgrade.build_path)))) + utility_upgrades += list(list("name" = initial(upgrade.name), "path" = "[initial(upgrade.build_path)]")) + all_upgrades["utility_upgrades"] = utility_upgrades + var/list/basic_upgrades = list() + for(var/datum/design/item/prosfab/robot_upgrade/basic/upgrade) + if(!upgrade.name) + continue + if(!(target.has_upgrade(initial(upgrade.build_path)))) + basic_upgrades += list(list("name" = initial(upgrade.name), "path" = "[initial(upgrade.build_path)]", "installed" = 0)) + else + basic_upgrades += list(list("name" = initial(upgrade.name), "path" = "[initial(upgrade.build_path)]", "installed" = 1)) + all_upgrades["basic_upgrades"] = basic_upgrades + var/list/advanced_upgrades = list() + for(var/datum/design/item/prosfab/robot_upgrade/advanced/upgrade) + if(!upgrade.name) + continue + if(!(target.has_upgrade(initial(upgrade.build_path)))) + advanced_upgrades += list(list("name" = initial(upgrade.name), "path" = "[initial(upgrade.build_path)]", "installed" = 0)) + else + advanced_upgrades += list(list("name" = initial(upgrade.name), "path" = "[initial(upgrade.build_path)]", "installed" = 1)) + all_upgrades["advanced_upgrades"] = advanced_upgrades + var/list/restricted_upgrades = list() + for(var/datum/design/item/prosfab/robot_upgrade/restricted/upgrade) + if(!upgrade.name) + continue + if(!(target.has_upgrade(initial(upgrade.build_path)))) + if(!(initial(upgrade.build_path) in target.module.supported_upgrades)) + restricted_upgrades += list(list("name" = initial(upgrade.name), "path" = "[initial(upgrade.build_path)]", "installed" = 2)) + continue + restricted_upgrades += list(list("name" = initial(upgrade.name), "path" = "[initial(upgrade.build_path)]", "installed" = 0)) + else + restricted_upgrades += list(list("name" = initial(upgrade.name), "path" = "[initial(upgrade.build_path)]", "installed" = 1)) + all_upgrades["restricted_upgrades"] = restricted_upgrades + return all_upgrades + +/datum/eventkit/modify_robot/proc/get_pka(var/obj/item/weapon/gun/energy/kinetic_accelerator/kin) + var/list/pka = list() + pka["name"] = kin.name + var/list/installed_modkits = list() + for(var/obj/item/borg/upgrade/modkit/modkit in kin.modkits) + installed_modkits += list(list("name" = modkit.name, "ref" = "\ref[modkit]", "costs" = modkit.cost)) + pka["installed_modkits"] = installed_modkits + var/list/modkits = list() + for(var/modkit in typesof(/obj/item/borg/upgrade/modkit)) + var/obj/item/borg/upgrade/modkit/single_modkit = modkit + if(single_modkit == /obj/item/borg/upgrade/modkit) + continue + if(kin.get_remaining_mod_capacity() < initial(single_modkit.cost)) + modkits += list(list("name" = initial(single_modkit.name), "path" = single_modkit, "costs" = initial(single_modkit.cost), "denied" = TRUE, "denied_by" = "Insufficient capacity!")) + continue + if(initial(single_modkit.denied_type)) + var/number_of_denied = 0 + var/denied = FALSE + for(var/A in kin.get_modkits()) + var/obj/item/borg/upgrade/modkit/M = A + if(istype(M, initial(single_modkit.denied_type))) + number_of_denied++ + if(number_of_denied >= initial(single_modkit.maximum_of_type)) + var/obj/item/denied_type = initial(single_modkit.denied_type) + modkits += list(list("name" = initial(single_modkit.name), "path" = single_modkit, "costs" = initial(single_modkit.cost), "denied" = TRUE, "denied_by" = "[initial(denied_type.name)]")) + denied = TRUE + break + if(denied) + continue + modkits += list(list("name" = initial(single_modkit.name), "path" = single_modkit, "costs" = initial(single_modkit.cost))) + pka["modkits"] = modkits + pka["capacity"] = kin.get_remaining_mod_capacity() + pka["max_capacity"] = kin.max_mod_capacity + return pka + +/datum/eventkit/modify_robot/proc/get_cells() + var/list/cell_options = list() + for(var/cell in typesof(/obj/item/weapon/cell)) + var/obj/item/weapon/cell/C = cell + if(initial(C.name) == "power cell") + continue + if(ispath(C, /obj/item/weapon/cell/standin)) + continue + if(ispath(C, /obj/item/weapon/cell/device)) + continue + if(ispath(C, /obj/item/weapon/cell/mech)) + continue + if(cell_options[initial(C.name)]) // empty cells are defined after normal cells! + continue + cell_options += list(initial(C.name) = list("path" = "[C]", "charge" = initial(C.maxcharge), "max_charge" = initial(C.maxcharge), "charge_amount" = initial(C.charge_amount) , "self_charge" = initial(C.self_recharge))) // our cells do not have their charge predefined, they do it on init, so both maaxcharge for now + return cell_options + +/datum/eventkit/modify_robot/proc/get_components() + var/list/components = list() + for(var/entry in target.components) + var/datum/robot_component/C = target.components[entry] + components += list(list("name" = C.name, "ref" = "\ref[C]", "brute_damage" = C.brute_damage, "electronics_damage" = C.electronics_damage, "max_damage" = C.max_damage, "installed" = C.installed, "exists" = (C.wrapped ? TRUE : FALSE))) + return components + +/datum/eventkit/modify_robot/proc/package_laws(var/list/data, var/field, var/list/datum/ai_law/laws) + var/list/packaged_laws = list() + for(var/datum/ai_law/AL in laws) + packaged_laws[++packaged_laws.len] = list("law" = AL.law, "index" = AL.get_index(), "state" = target.laws.get_state_law(AL), "ref" = "\ref[AL]") + data[field] = packaged_laws + data["has_[field]"] = packaged_laws.len + +/datum/eventkit/modify_robot/proc/package_multiple_laws(var/list/datum/ai_laws/laws) + var/list/law_sets = list() + for(var/datum/ai_laws/ALs in laws) + var/list/packaged_laws = list() + package_laws(packaged_laws, "zeroth_laws", list(ALs.zeroth_law, ALs.zeroth_law_borg)) + package_laws(packaged_laws, "ion_laws", ALs.ion_laws) + package_laws(packaged_laws, "inherent_laws", ALs.inherent_laws) + package_laws(packaged_laws, "supplied_laws", ALs.supplied_laws) + law_sets[++law_sets.len] = list("name" = ALs.name, "header" = ALs.law_header, "ref" = "\ref[ALs]","laws" = packaged_laws) + return law_sets diff --git a/code/modules/admin/player_effects.dm b/code/modules/admin/player_effects.dm index b2d0a83d3de..1a77dc227a7 100644 --- a/code/modules/admin/player_effects.dm +++ b/code/modules/admin/player_effects.dm @@ -74,7 +74,7 @@ return Tar.adjust_fire_stacks(10) Tar.IgniteMob() - Tar.visible_message("[target] bursts into flames!") + Tar.visible_message(span_danger("[target] bursts into flames!")) if("lightning_strike") var/mob/living/carbon/human/Tar = target @@ -83,7 +83,7 @@ var/turf/T = get_step(get_step(target, NORTH), NORTH) T.Beam(target, icon_state="lightning[rand(1,12)]", time = 5) Tar.electrocute_act(75,def_zone = BP_HEAD) - target.visible_message("[target] is struck by lightning!") + target.visible_message(span_danger("[target] is struck by lightning!")) if("shadekin_attack") var/turf/Tt = get_turf(target) //Turf for target @@ -180,7 +180,7 @@ shadekin.ckey = target.ckey else //Permakin'd - to_chat(target,"You're carried off into The Dark by the [shadekin]. Who knows if you'll find your way back?") + to_chat(target,span_danger("You're carried off into The Dark by the [shadekin]. Who knows if you'll find your way back?")) target.ghostize() qdel(target) qdel(shadekin) @@ -202,7 +202,7 @@ if("peppernade") var/obj/item/weapon/grenade/chem_grenade/teargas/grenade = new /obj/item/weapon/grenade/chem_grenade/teargas grenade.loc = target.loc - to_chat(target,"GRENADE?!") + to_chat(target,span_warning("GRENADE?!")) grenade.detonate() if("spicerequest") @@ -248,6 +248,39 @@ target.ClearTransform() target.update_transform() + if("pie_splat") + new/obj/effect/decal/cleanable/pie_smudge(get_turf(target)) + playsound(target, 'sound/effects/slime_squish.ogg', 100, 1, get_rand_frequency(), falloff = 5) + target.Weaken(1) + target.visible_message(span_danger("[target] is struck by pie!")) + + if("spicy_air") + to_chat(target, span_warning("Spice spice baby!")) + target.eye_blurry = max(target.eye_blurry, 25) + target.Blind(10) + target.Stun(5) + target.Weaken(5) + playsound(target, 'sound/effects/spray2.ogg', 100, 1, get_rand_frequency(), falloff = 5) + + if("hot_dog") + playsound(target, 'sound/effects/whistle.ogg', 50, 1, get_rand_frequency(), falloff = 5) + sleep(2 SECONDS) + target.Stun(10) + if(!ishuman(target)) + return + var/mob/living/carbon/human/H = target + if(H.head) + H.unEquip(H.head) + if(H.wear_suit) + H.unEquip(H.wear_suit) + var/obj/item/clothing/suit = new /obj/item/clothing/suit/storage/hooded/foodcostume/hotdog + var/obj/item/clothing/hood = new /obj/item/clothing/head/hood_vr/hotdog_hood + H.equip_to_slot_if_possible(suit, slot_wear_suit, 0, 0, 1) + H.equip_to_slot_if_possible(hood, slot_head, 0, 0, 1) + sleep(5 SECONDS) + qdel(suit) + qdel(hood) + ////////MEDICAL////////////// if("appendicitis") @@ -528,10 +561,10 @@ return var/input_NIF if(!Tar.get_organ(BP_HEAD)) - to_chat(user,"Target is unsuitable.") + to_chat(user,span_warning("Target is unsuitable.")) return if(Tar.nif) - to_chat(user,"Target already has a NIF.") + to_chat(user,span_warning("Target already has a NIF.")) return if(Tar.species.flags & NO_SCAN) var/obj/item/device/nif/S = /obj/item/device/nif/bioadap diff --git a/code/modules/client/preference_setup/loadout/loadout_accessories.dm b/code/modules/client/preference_setup/loadout/loadout_accessories.dm index 81b6d810448..64e5e86cce3 100644 --- a/code/modules/client/preference_setup/loadout/loadout_accessories.dm +++ b/code/modules/client/preference_setup/loadout/loadout_accessories.dm @@ -569,3 +569,61 @@ Talon pin ) gear_tweaks += new/datum/gear_tweak/path(wristband_lists) gear_tweaks += gear_tweak_free_color_choice + +/datum/gear/accessory/plushie + display_name = "Plushie Selection" + description = "A single plushie of your selection!" + path = /obj/item/toy/plushie/nymph + +/datum/gear/accessory/plushie/New() + ..() + var/plushie = list( + "Nymph" = /obj/item/toy/plushie/nymph, + "Mouse" = /obj/item/toy/plushie/mouse, + "Kitten" = /obj/item/toy/plushie/kitten, + "Lizard" = /obj/item/toy/plushie/lizard, + "Black cat" = /obj/item/toy/plushie/black_cat, + "Black fox" = /obj/item/toy/plushie/black_fox, + "Blue fox" = /obj/item/toy/plushie/blue_fox, + "Carp plushie" = /obj/random/carp_plushie, + "Coffee fox" = /obj/item/toy/plushie/coffee_fox, + "Corgi" = /obj/item/toy/plushie/corgi, + "Crimson fox" = /obj/item/toy/plushie/crimson_fox, + "Deer" = /obj/item/toy/plushie/deer, + "Girly corgi" = /obj/item/toy/plushie/girly_corgi, + "Grey cat" = /obj/item/toy/plushie/grey_cat, + "Marble fox" = /obj/item/toy/plushie/marble_fox, + "Octopus" = /obj/item/toy/plushie/octopus, + "Orange cat" = /obj/item/toy/plushie/orange_cat, + "Orange fox" = /obj/item/toy/plushie/orange_fox, + "Pink fox" = /obj/item/toy/plushie/pink_fox, + "Purple fox" = /obj/item/toy/plushie/purple_fox, + "Red fox" = /obj/item/toy/plushie/red_fox, + "Robo corgi" = /obj/item/toy/plushie/robo_corgi, + "Siamese cat" = /obj/item/toy/plushie/siamese_cat, + "Spider" = /obj/item/toy/plushie/spider, + "Tabby cat" = /obj/item/toy/plushie/tabby_cat, + "Tuxedo cat" = /obj/item/toy/plushie/tuxedo_cat, + "White cat" = /obj/item/toy/plushie/white_cat, + "Lizard" = /obj/item/toy/plushie/lizardplushie, + "Kobold" = /obj/item/toy/plushie/lizardplushie/kobold, + "Resh" = /obj/item/toy/plushie/lizardplushie/resh, + "Slime" = /obj/item/toy/plushie/slimeplushie, + "Box" = /obj/item/toy/plushie/box, + "Robot" = /obj/item/toy/plushie/borgplushie, + "Medihound" = /obj/item/toy/plushie/borgplushie/medihound, + "Scrubpuppy" = /obj/item/toy/plushie/borgplushie/scrubpuppy, + "Foxbear" = /obj/item/toy/plushie/foxbear, + "Operative" = /obj/item/toy/plushie/nukeplushie, + "Otter" = /obj/item/toy/plushie/otter, + "Vox" = /obj/item/toy/plushie/vox, + "Shark" = /obj/item/toy/plushie/shark, + "Sec Drake" = /obj/item/toy/plushie/borgplushie/drake/sec, + "Med Drake" = /obj/item/toy/plushie/borgplushie/drake/med, + "Sci Drake" = /obj/item/toy/plushie/borgplushie/drake/sci, + "Jani Drake" = /obj/item/toy/plushie/borgplushie/drake/jani, + "Eng Drake" = /obj/item/toy/plushie/borgplushie/drake/eng, + "Mine Drake" = /obj/item/toy/plushie/borgplushie/drake/mine, + "Trauma Drake" = /obj/item/toy/plushie/borgplushie/drake/trauma, + ) + gear_tweaks += new/datum/gear_tweak/path(plushie) diff --git a/code/modules/eventkit/generic_objects/generic_item.dm b/code/modules/eventkit/generic_objects/generic_item.dm index 48432ee370a..02fa8e264c5 100644 --- a/code/modules/eventkit/generic_objects/generic_item.dm +++ b/code/modules/eventkit/generic_objects/generic_item.dm @@ -29,7 +29,8 @@ else icon = 'icons/obj/props/items.dmi' icon_state = icon_state_on - src.visible_message("[text_activated]") + if(user) + user.visible_message("[text_activated]") update_icon() if(effect == 1) var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread @@ -83,7 +84,8 @@ icon = icon_off else icon = 'icons/obj/props/items.dmi' - src.visible_message("[text_deactivated]") + if(user) + user.visible_message("[text_deactivated]") update_icon() return ..() diff --git a/code/modules/eventkit/generic_objects/generic_structure.dm b/code/modules/eventkit/generic_objects/generic_structure.dm index 402d21bbf08..cfd8cfd85fc 100644 --- a/code/modules/eventkit/generic_objects/generic_structure.dm +++ b/code/modules/eventkit/generic_objects/generic_structure.dm @@ -77,6 +77,14 @@ if(effect == 4) var/atom/o = new object(get_turf(src)) src.visible_message("[src] has produced [o]!") + if(effect == 5) + for (var/mob/O in viewers(src, null)) + if(get_dist(src, O) > 7) + continue + + if(istype(O, /mob/living/carbon/human)) + var/mob/living/carbon/human/H = O + H.fear = 200 if(sound_activated) playsound(src, sound_activated, 50, 1) else if(togglable) @@ -96,8 +104,8 @@ /obj/structure/generic_structure/attackby(obj/item/weapon/W as obj, mob/user as mob) if(wrenchable && W.has_tool_quality(TOOL_WRENCH)) add_fingerprint(user) - anchored = !anchored to_chat(user, "You [anchored? "un" : ""]secured \the [src]!") + anchored = !anchored /client/proc/generic_structure() set category = "EventKit" @@ -268,7 +276,7 @@ if(s_icon_state_on == "Upload Own Sprite") s_icon2 = input(usr, "Choose an image file to upload. Images that are not 32x32 will need to have their positions offset.","Upload Icon") as null|file s_delay = tgui_input_number(src, "Do you want it to take time to put turn on? Choose a number of deciseconds to activate, or 0 for instant.", "Delay") - var/check_effect = tgui_alert(src, "Produce an effect on activation?", "Effect?", list("No", "Spark", "Flicker Lights", "Flash", "Spawn Item", "Cancel")) + var/check_effect = tgui_alert(src, "Produce an effect on activation?", "Effect?", list("No", "Spark", "Flicker Lights", "Flash", "Spawn Item", "Fear", "Cancel")) if(!check_effect || check_effect == "Cancel") return if(check_effect == "No") @@ -282,6 +290,8 @@ if(check_effect == "Spawn Item") s_effect = 4 s_object = get_path_from_partial_text() + if(check_effect == "Fear") + s_effect = 5 var/check_sound = tgui_alert(src, "Play a sound when turning on?", "Sound", list("Yes", "No", "Cancel")) if(!check_sound || check_sound == "Cancel") return diff --git a/code/modules/events/event_dynamic.dm b/code/modules/events/event_dynamic.dm index 110e17421df..95612d2fd72 100644 --- a/code/modules/events/event_dynamic.dm +++ b/code/modules/events/event_dynamic.dm @@ -30,9 +30,9 @@ var/list/event_last_fired = list() var/minutes_passed = world.time/600 var/list/active_with_role = number_active_with_role() - //var/engineer_count = number_active_with_role(JOB_ENGINEER) - //var/security_count = number_active_with_role("Security") - //var/medical_count = number_active_with_role("Medical") + //var/engineer_count = number_active_with_role(DEPARTMENT_ENGINEERING) + //var/security_count = number_active_with_role(DEPARTMENT_SECURITY) + //var/medical_count = number_active_with_role(DEPARTMENT_MEDICAL) //var/AI_count = number_active_with_role(JOB_AI) //var/janitor_count = number_active_with_role(JOB_JANITOR) @@ -56,33 +56,33 @@ var/list/event_last_fired = list() possibleEvents[/datum/event/money_hacker] = max(min(25, player_list.len) * 4, 200) - possibleEvents[/datum/event/carp_migration] = 20 + 10 * active_with_role[JOB_ENGINEER] + possibleEvents[/datum/event/carp_migration] = 20 + 10 * active_with_role[DEPARTMENT_ENGINEERING] possibleEvents[/datum/event/brand_intelligence] = 20 + 25 * active_with_role[JOB_JANITOR] - possibleEvents[/datum/event/rogue_drone] = 5 + 25 * active_with_role[JOB_ENGINEER] + 25 * active_with_role["Security"] + possibleEvents[/datum/event/rogue_drone] = 5 + 25 * active_with_role[DEPARTMENT_ENGINEERING] + 25 * active_with_role[DEPARTMENT_SECURITY] possibleEvents[/datum/event/infestation] = 100 + 100 * active_with_role[JOB_JANITOR] - possibleEvents[/datum/event/communications_blackout] = 50 + 25 * active_with_role[JOB_AI] + active_with_role[JOB_SCIENTIST] * 25 - possibleEvents[/datum/event/ionstorm] = active_with_role[JOB_AI] * 25 + active_with_role[JOB_CYBORG] * 25 + active_with_role[JOB_ENGINEER] * 10 + active_with_role[JOB_SCIENTIST] * 5 - possibleEvents[/datum/event/grid_check] = 25 + 10 * active_with_role[JOB_ENGINEER] - possibleEvents[/datum/event/electrical_storm] = 15 * active_with_role[JOB_JANITOR] + 5 * active_with_role[JOB_ENGINEER] - possibleEvents[/datum/event/wallrot] = 30 * active_with_role[JOB_ENGINEER] + 50 * active_with_role[JOB_ALT_GARDENER] + possibleEvents[/datum/event/communications_blackout] = 50 + 25 * active_with_role[JOB_AI] + active_with_role[DEPARTMENT_RESEARCH] * 25 + possibleEvents[/datum/event/ionstorm] = active_with_role[JOB_AI] * 25 + active_with_role[JOB_CYBORG] * 25 + active_with_role[DEPARTMENT_ENGINEERING] * 10 + active_with_role[DEPARTMENT_RESEARCH] * 5 + possibleEvents[/datum/event/grid_check] = 25 + 10 * active_with_role[DEPARTMENT_ENGINEERING] + possibleEvents[/datum/event/electrical_storm] = 15 * active_with_role[JOB_JANITOR] + 5 * active_with_role[DEPARTMENT_ENGINEERING] + possibleEvents[/datum/event/wallrot] = 30 * active_with_role[DEPARTMENT_ENGINEERING] + 50 * active_with_role[JOB_ALT_GARDENER] if(!spacevines_spawned) - possibleEvents[/datum/event/spacevine] = 10 + 5 * active_with_role[JOB_ENGINEER] + possibleEvents[/datum/event/spacevine] = 10 + 5 * active_with_role[DEPARTMENT_ENGINEERING] if(minutes_passed >= 30) // Give engineers time to set up engine - possibleEvents[/datum/event/meteor_wave] = 10 * active_with_role[JOB_ENGINEER] - possibleEvents[/datum/event/blob] = 10 * active_with_role[JOB_ENGINEER] + possibleEvents[/datum/event/meteor_wave] = 10 * active_with_role[DEPARTMENT_ENGINEERING] + possibleEvents[/datum/event/blob] = 10 * active_with_role[DEPARTMENT_ENGINEERING] - if(active_with_role["Medical"] > 0) - possibleEvents[/datum/event/radiation_storm] = active_with_role["Medical"] * 10 - possibleEvents[/datum/event/spontaneous_appendicitis] = active_with_role["Medical"] * 10 + if(active_with_role[DEPARTMENT_MEDICAL] > 0) + possibleEvents[/datum/event/radiation_storm] = active_with_role[DEPARTMENT_MEDICAL] * 10 + possibleEvents[/datum/event/spontaneous_appendicitis] = active_with_role[DEPARTMENT_MEDICAL] * 10 - possibleEvents[/datum/event/prison_break] = active_with_role["Security"] * 50 - if(active_with_role["Security"] > 0) + possibleEvents[/datum/event/prison_break] = active_with_role[DEPARTMENT_SECURITY] * 50 + if(active_with_role[DEPARTMENT_SECURITY] > 0) if(!sent_spiders_to_station) - possibleEvents[/datum/event/spider_infestation] = max(active_with_role["Security"], 5) + 5 - possibleEvents[/datum/event/random_antag] = max(active_with_role["Security"], 5) + 2.5 + possibleEvents[/datum/event/spider_infestation] = max(active_with_role[DEPARTMENT_SECURITY], 5) + 5 + possibleEvents[/datum/event/random_antag] = max(active_with_role[DEPARTMENT_SECURITY], 5) + 2.5 for(var/event_type in event_last_fired) if(possibleEvents[event_type]) var/time_passed = world.time - event_last_fired[event_type] @@ -179,10 +179,10 @@ var/list/event_last_fired = list() // Note that this isn't sorted by department, because e.g. having a roboticist shouldn't make meteors spawn. /proc/number_active_with_role() var/list/active_with_role = list() - active_with_role[JOB_ENGINEER] = 0 - active_with_role["Medical"] = 0 - active_with_role["Security"] = 0 - active_with_role[JOB_SCIENTIST] = 0 + active_with_role[DEPARTMENT_ENGINEERING] = 0 + active_with_role[DEPARTMENT_MEDICAL] = 0 + active_with_role[DEPARTMENT_SECURITY] = 0 + active_with_role[DEPARTMENT_RESEARCH] = 0 active_with_role[JOB_AI] = 0 active_with_role[JOB_CYBORG] = 0 active_with_role[JOB_JANITOR] = 0 @@ -198,29 +198,29 @@ var/list/event_last_fired = list() var/mob/living/silicon/robot/R = M if(R.module) if(istype(R.module, /obj/item/weapon/robot_module/robot/engineering)) - active_with_role[JOB_ENGINEER]++ + active_with_role[DEPARTMENT_ENGINEERING]++ else if(istype(R.module, /obj/item/weapon/robot_module/robot/security)) - active_with_role["Security"]++ + active_with_role[DEPARTMENT_SECURITY]++ else if(istype(R.module, /obj/item/weapon/robot_module/robot/medical)) - active_with_role["Medical"]++ + active_with_role[DEPARTMENT_MEDICAL]++ else if(istype(R.module, /obj/item/weapon/robot_module/robot/research)) - active_with_role[JOB_SCIENTIST]++ + active_with_role[DEPARTMENT_RESEARCH]++ else if(istype(R.module, /obj/item/weapon/robot_module/robot/janitor)) active_with_role[JOB_JANITOR]++ else if(istype(R.module, /obj/item/weapon/robot_module/robot/clerical/butler)) active_with_role[JOB_BOTANIST]++ if(M.mind.assigned_role in SSjob.get_job_titles_in_department(DEPARTMENT_ENGINEERING)) - active_with_role[JOB_ENGINEER]++ + active_with_role[DEPARTMENT_ENGINEERING]++ if(M.mind.assigned_role in SSjob.get_job_titles_in_department(DEPARTMENT_MEDICAL)) - active_with_role["Medical"]++ + active_with_role[DEPARTMENT_MEDICAL]++ if(M.mind.assigned_role in SSjob.get_job_titles_in_department(DEPARTMENT_SECURITY)) - active_with_role["Security"]++ + active_with_role[DEPARTMENT_SECURITY]++ if(M.mind.assigned_role in SSjob.get_job_titles_in_department(DEPARTMENT_RESEARCH)) - active_with_role[JOB_SCIENTIST]++ + active_with_role[DEPARTMENT_RESEARCH]++ if(M.mind.assigned_role == JOB_AI) active_with_role[JOB_AI]++ diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm index 65b4039cddc..1239ddfc613 100644 --- a/code/modules/mob/living/carbon/human/human_movement.dm +++ b/code/modules/mob/living/carbon/human/human_movement.dm @@ -147,7 +147,7 @@ if(!T) return 0 - if(T.movement_cost) + if(T.movement_cost && !flying) //VOREStation Add: If you are flying you are probably not affected by the terrain on the ground. var/turf_move_cost = T.movement_cost if(istype(T, /turf/simulated/floor/water)) if(species.water_movement) diff --git a/code/modules/mob/living/carbon/human/species/lleill/hanner.dm b/code/modules/mob/living/carbon/human/species/lleill/hanner.dm index 8269104dab2..1a684a477b5 100644 --- a/code/modules/mob/living/carbon/human/species/lleill/hanner.dm +++ b/code/modules/mob/living/carbon/human/species/lleill/hanner.dm @@ -88,6 +88,7 @@ /mob/living/carbon/human/proc/shapeshifter_select_wings, /mob/living/carbon/human/proc/shapeshifter_select_tail, /mob/living/carbon/human/proc/shapeshifter_select_ears, + /mob/living/carbon/human/proc/shapeshifter_select_eye_colour, /mob/living/proc/set_size, // /mob/living/carbon/human/proc/lleill_contact, // /mob/living/carbon/human/proc/lleill_alchemy, diff --git a/code/modules/mob/living/silicon/robot/sprites/civilian.dm b/code/modules/mob/living/silicon/robot/sprites/civilian.dm index 18f371accfc..e390e389fce 100644 --- a/code/modules/mob/living/silicon/robot/sprites/civilian.dm +++ b/code/modules/mob/living/silicon/robot/sprites/civilian.dm @@ -121,7 +121,7 @@ /datum/robot_sprite/service/drone name = "AG Model-Serv" - sprite_icon_state = "drone-crisis" + sprite_icon_state = "drone" has_custom_open_sprites = TRUE /datum/robot_sprite/service/drone_hydro diff --git a/code/modules/mob/living/simple_mob/subtypes/glamour/homunculus.dm b/code/modules/mob/living/simple_mob/subtypes/glamour/homunculus.dm index 3a65edf7b6f..febdbe3dd61 100644 --- a/code/modules/mob/living/simple_mob/subtypes/glamour/homunculus.dm +++ b/code/modules/mob/living/simple_mob/subtypes/glamour/homunculus.dm @@ -33,3 +33,92 @@ /mob/living/simple_mob/homunculus/update_icons() return +/mob/living/simple_mob/homunculus/evil + var/seen_melee + var/seen_gun + var/transformed + var/seen_armour + maxHealth = 100 + health = 100 + + faction = FACTION_GLAMOUR + + melee_damage_lower = 5 + melee_damage_upper = 7 + + ai_holder_type = /datum/ai_holder/simple_mob/hostile + +/mob/living/simple_mob/homunculus/evil/Life() + handle_homunculus() + return ..() + +/mob/living/simple_mob/homunculus/evil/proc/handle_homunculus() + if(!transformed) + for(var/mob/living/carbon/human/H in range(8, src)) + if(H.stat == DEAD) + continue + name = H.name + desc = H.desc + icon = H.icon + icon_state = H.icon_state + copy_overlays(H, TRUE) + resize(H.size_multiplier, ignore_prefs = TRUE) + var/obj/item/A = H.get_active_hand() + var/obj/item/I = H.get_inactive_hand() + if(istype(A,/obj/item/weapon/material) || istype(A,/obj/item/weapon/melee) || istype(I,/obj/item/weapon/material) || istype(I,/obj/item/weapon/melee)) + seen_melee = 1 + melee_damage_lower = 20 + melee_damage_upper = 30 + if(istype(A,/obj/item/weapon/gun/projectile) || istype(I,/obj/item/weapon/gun/projectile)) + seen_gun = 1 + projectiletype = /obj/item/projectile/bullet/pistol/medium + projectilesound = 'sound/weapons/Gunshot_light.ogg' + if(istype(A,/obj/item/weapon/gun/projectile/shotgun) || istype(I,/obj/item/weapon/gun/projectile/shotgun)) + projectiletype = /obj/item/projectile/bullet/pellet/shotgun + projectilesound = 'sound/weapons/Gunshot_shotgun.ogg' + reload_time = 1.5 SECONDS + ranged_attack_delay = 1.5 SECONDS + projectile_dispersion = 8 + projectile_accuracy = -40 + if(istype(A,/obj/item/weapon/gun/projectile/automatic) || istype(I,/obj/item/weapon/gun/projectile/automatic)) + base_attack_cooldown = 5 + projectile_dispersion = 7 + projectile_accuracy = -20 + if(istype(A,/obj/item/weapon/gun/projectile/heavysniper) || istype(I,/obj/item/weapon/gun/projectile/heavysniper)) + projectiletype = /obj/item/projectile/bullet/rifle/a145/highvel // Do not get seen with a big ass sniper! + projectilesound = 'sound/weapons/Gunshot_cannon.ogg' + ranged_attack_delay = 2.5 SECONDS + reload_time = 5 SECONDS + projectile_accuracy = 75 + if(istype(A,/obj/item/weapon/gun/energy) || istype(I,/obj/item/weapon/gun/energy)) + seen_gun = 1 + projectiletype = /obj/item/projectile/beam/midlaser + projectilesound = 'sound/weapons/Laser.ogg' + projectile_dispersion = 5 + projectile_accuracy = -20 + if(istype(A,/obj/item/weapon/gun/energy/ionrifle) || istype(I,/obj/item/weapon/gun/energy/ionrifle)) + projectiletype = /obj/item/projectile/ion + if(istype(A,/obj/item/weapon/gun/energy/lasercannon) || istype(I,/obj/item/weapon/gun/energy/lasercannon)) + projectiletype = /obj/item/projectile/beam/heavylaser + ranged_attack_delay = 2.5 SECONDS + reload_time = 5 SECONDS + projectilesound = 'sound/weapons/lasercannonfire.ogg' + if(istype(A,/obj/item/weapon/gun/energy/sniperrifle) || istype(I,/obj/item/weapon/gun/energy/sniperrifle)) + projectiletype = /obj/item/projectile/beam/sniper // Do not get seen with a big ass sniper! + projectilesound = 'sound/weapons/gauss_shoot.ogg' + ranged_attack_delay = 2.5 SECONDS + reload_time = 5 SECONDS + projectile_accuracy = 75 + if(istype(A,/obj/item/weapon/gun/magnetic) || istype(I,/obj/item/weapon/gun/magnetic)) + seen_gun = 1 + projectiletype = /obj/item/projectile/bullet/magnetic/bore + projectilesound = 'sound/weapons/railgun.ogg' + ranged_attack_delay = 1.5 SECONDS + projectile_dispersion = 5 + projectile_accuracy = 20 + var/obj/item/clothing/suit/S = H.get_equipped_item(slot_wear_suit) + if(istype(S,/obj/item/clothing/suit/armor) || istype(S,/obj/item/clothing/suit/space/rig/)) + armor = list(melee = 40, bullet = 30, laser = 30, energy = 10, bomb = 10, bio = 100, rad = 100) + transformed = 1 + return TRUE + diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm b/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm index b9f58b315be..cd01e044cf6 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm @@ -184,3 +184,48 @@ /datum/ai_holder/simple_mob/melee/evasive/corrupthound violent_breakthrough = TRUE can_breakthrough = TRUE + +/mob/living/simple_mob/vore/aggressive/corrupthound/fire + icon_state = "badboi_fire" + icon_living = "badboi_fire" + icon_dead = "badboi_fire-dead" + icon_rest = "badboi_fire_rest" + + maxHealth = 150 + health = 150 + + ai_holder_type = /datum/ai_holder/simple_mob/ranged/aggressive + + projectiletype = /obj/item/projectile/scatter/flamethrower + ranged_attack_delay = 2 SECONDS + +/mob/living/simple_mob/vore/aggressive/corrupthound/laser + icon_state = "badboi_laser" + icon_living = "badboi_laser" + icon_dead = "badboi_laser-dead" + icon_rest = "badboi_laser_rest" + + maxHealth = 150 + health = 150 + + ai_holder_type = /datum/ai_holder/simple_mob/ranged + + ranged_attack_delay = 2 SECONDS + projectiletype = /obj/item/projectile/beam/midlaser + projectilesound = 'sound/weapons/Laser.ogg' + projectile_dispersion = 5 + projectile_accuracy = -20 + +/mob/living/simple_mob/vore/aggressive/corrupthound/fast + icon_state = "badboi_fast" + icon_living = "badboi_fast" + icon_dead = "badboi_fast-dead" + icon_rest = "badboi_fast_rest" + + maxHealth = 100 + health = 100 + + movement_cooldown = -1 + + melee_damage_lower = 7 + melee_damage_upper = 15 diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/meowl.dm b/code/modules/mob/living/simple_mob/subtypes/vore/meowl.dm index 62ab96e07ce..f7ec3a56686 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/meowl.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/meowl.dm @@ -137,6 +137,8 @@ /datum/ai_holder/simple_mob/vore/meowl var/last_friend_time = 0 + violent_breakthrough = FALSE + destructive = FALSE /datum/ai_holder/simple_mob/vore/meowl/engage_target() ai_log("engage_target() : Entering.", AI_LOG_DEBUG) diff --git a/code/modules/vore/eating/silicon_vr.dm b/code/modules/vore/eating/silicon_vr.dm index 2334e186e87..d6622039ede 100644 --- a/code/modules/vore/eating/silicon_vr.dm +++ b/code/modules/vore/eating/silicon_vr.dm @@ -100,3 +100,16 @@ if(master.ooc_notes) . += "OOC Notes: \[View\] - \[Print\]" + +// Allow dissipating ai holograms by attacking them +/obj/effect/overlay/aiholo/attack_hand(mob/living/user) + if(user.a_intent == I_HURT) + to_chat(user, span_attack("You dissipate [src].")) + master?.holo?.clear_holo(master) + return ..() + +/obj/effect/overlay/aiholo/attackby(obj/item/I, mob/user) + if(user.a_intent == I_HURT) + to_chat(user, span_attack("You dissipate [src] with [I].")) + master?.holo?.clear_holo(master) + return ..() diff --git a/icons/mob/human.dmi b/icons/mob/human.dmi index c98035743d5..1fc987773e6 100644 Binary files a/icons/mob/human.dmi and b/icons/mob/human.dmi differ diff --git a/icons/mob/vore64x32.dmi b/icons/mob/vore64x32.dmi index a530a26d00f..072c39795ab 100644 Binary files a/icons/mob/vore64x32.dmi and b/icons/mob/vore64x32.dmi differ diff --git a/maps/southern_cross/loadout/loadout_accessories.dm b/maps/southern_cross/loadout/loadout_accessories.dm index 4604d6354dd..9011a5c8493 100644 --- a/maps/southern_cross/loadout/loadout_accessories.dm +++ b/maps/southern_cross/loadout/loadout_accessories.dm @@ -1,34 +1,34 @@ /datum/gear/accessory/brown_vest display_name = "webbing, brown" path = /obj/item/clothing/accessory/storage/brown_vest - allowed_roles = list(JOB_ENGINEER,JOB_ATMOSPHERIC_TECHNICIAN,JOB_CHIEF_ENGINEER,JOB_SECURITY_OFFICER,JOB_DETECTIVE,JOB_HEAD_OF_SECURITY,JOB_WARDEN,JOB_PARAMEDIC,JOB_CHIEF_MEDICAL_OFFICER,JOB_MEDICAL_DOCTOR,"Search and Rescue","Explorer",JOB_SHAFT_MINER) + allowed_roles = list(JOB_ENGINEER,JOB_ATMOSPHERIC_TECHNICIAN,JOB_CHIEF_ENGINEER,JOB_SECURITY_OFFICER,JOB_DETECTIVE,JOB_HEAD_OF_SECURITY,JOB_WARDEN,JOB_ALT_SEARCH_AND_RESCUE,JOB_CHIEF_MEDICAL_OFFICER,JOB_MEDICAL_DOCTOR,JOB_PARAMEDIC,JOB_EXPLORER,JOB_SHAFT_MINER) /datum/gear/accessory/black_vest display_name = "webbing, black" path = /obj/item/clothing/accessory/storage/black_vest - allowed_roles = list(JOB_ENGINEER,JOB_ATMOSPHERIC_TECHNICIAN,JOB_CHIEF_ENGINEER,JOB_SECURITY_OFFICER,JOB_DETECTIVE,JOB_HEAD_OF_SECURITY,JOB_WARDEN,JOB_PARAMEDIC,JOB_CHIEF_MEDICAL_OFFICER,JOB_MEDICAL_DOCTOR,"Search and Rescue","Explorer",JOB_SHAFT_MINER) + allowed_roles = list(JOB_ENGINEER,JOB_ATMOSPHERIC_TECHNICIAN,JOB_CHIEF_ENGINEER,JOB_SECURITY_OFFICER,JOB_DETECTIVE,JOB_HEAD_OF_SECURITY,JOB_WARDEN,JOB_ALT_SEARCH_AND_RESCUE,JOB_CHIEF_MEDICAL_OFFICER,JOB_MEDICAL_DOCTOR,JOB_PARAMEDIC,JOB_EXPLORER,JOB_SHAFT_MINER) /datum/gear/accessory/white_vest display_name = "webbing, white" path = /obj/item/clothing/accessory/storage/white_vest - allowed_roles = list(JOB_ENGINEER,JOB_ATMOSPHERIC_TECHNICIAN,JOB_CHIEF_ENGINEER,JOB_SECURITY_OFFICER,JOB_DETECTIVE,JOB_HEAD_OF_SECURITY,JOB_WARDEN,JOB_PARAMEDIC,JOB_CHIEF_MEDICAL_OFFICER,JOB_MEDICAL_DOCTOR,"Search and Rescue","Explorer",JOB_SHAFT_MINER) + allowed_roles = list(JOB_ENGINEER,JOB_ATMOSPHERIC_TECHNICIAN,JOB_CHIEF_ENGINEER,JOB_SECURITY_OFFICER,JOB_DETECTIVE,JOB_HEAD_OF_SECURITY,JOB_WARDEN,JOB_ALT_SEARCH_AND_RESCUE,JOB_CHIEF_MEDICAL_OFFICER,JOB_MEDICAL_DOCTOR,JOB_PARAMEDIC,JOB_EXPLORER,JOB_SHAFT_MINER) /datum/gear/accessory/brown_drop_pouches display_name = "drop pouches, brown" path = /obj/item/clothing/accessory/storage/brown_drop_pouches - allowed_roles = list(JOB_ENGINEER,JOB_ATMOSPHERIC_TECHNICIAN,JOB_CHIEF_ENGINEER,JOB_SECURITY_OFFICER,JOB_DETECTIVE,JOB_HEAD_OF_SECURITY,JOB_WARDEN,JOB_PARAMEDIC,JOB_CHIEF_MEDICAL_OFFICER,JOB_MEDICAL_DOCTOR,"Search and Rescue","Explorer",JOB_SHAFT_MINER) + allowed_roles = list(JOB_ENGINEER,JOB_ATMOSPHERIC_TECHNICIAN,JOB_CHIEF_ENGINEER,JOB_SECURITY_OFFICER,JOB_DETECTIVE,JOB_HEAD_OF_SECURITY,JOB_WARDEN,JOB_ALT_SEARCH_AND_RESCUE,JOB_CHIEF_MEDICAL_OFFICER,JOB_MEDICAL_DOCTOR,JOB_PARAMEDIC,JOB_EXPLORER,JOB_SHAFT_MINER) /datum/gear/accessory/black_drop_pouches display_name = "drop pouches, black" path = /obj/item/clothing/accessory/storage/black_drop_pouches - allowed_roles = list(JOB_ENGINEER,JOB_ATMOSPHERIC_TECHNICIAN,JOB_CHIEF_ENGINEER,JOB_SECURITY_OFFICER,JOB_DETECTIVE,JOB_HEAD_OF_SECURITY,JOB_WARDEN,JOB_PARAMEDIC,JOB_CHIEF_MEDICAL_OFFICER,JOB_MEDICAL_DOCTOR,"Search and Rescue","Explorer",JOB_SHAFT_MINER) + allowed_roles = list(JOB_ENGINEER,JOB_ATMOSPHERIC_TECHNICIAN,JOB_CHIEF_ENGINEER,JOB_SECURITY_OFFICER,JOB_DETECTIVE,JOB_HEAD_OF_SECURITY,JOB_WARDEN,JOB_ALT_SEARCH_AND_RESCUE,JOB_CHIEF_MEDICAL_OFFICER,JOB_MEDICAL_DOCTOR,JOB_PARAMEDIC,JOB_EXPLORER,JOB_SHAFT_MINER) /datum/gear/accessory/white_drop_pouches display_name = "drop pouches, white" path = /obj/item/clothing/accessory/storage/white_drop_pouches - allowed_roles = list(JOB_ENGINEER,JOB_ATMOSPHERIC_TECHNICIAN,JOB_CHIEF_ENGINEER,JOB_SECURITY_OFFICER,JOB_DETECTIVE,JOB_HEAD_OF_SECURITY,JOB_WARDEN,JOB_PARAMEDIC,JOB_CHIEF_MEDICAL_OFFICER,JOB_MEDICAL_DOCTOR,"Search and Rescue","Explorer",JOB_SHAFT_MINER) + allowed_roles = list(JOB_ENGINEER,JOB_ATMOSPHERIC_TECHNICIAN,JOB_CHIEF_ENGINEER,JOB_SECURITY_OFFICER,JOB_DETECTIVE,JOB_HEAD_OF_SECURITY,JOB_WARDEN,JOB_ALT_SEARCH_AND_RESCUE,JOB_CHIEF_MEDICAL_OFFICER,JOB_MEDICAL_DOCTOR,JOB_PARAMEDIC,JOB_EXPLORER,JOB_SHAFT_MINER) /datum/gear/accessory/holster display_name = "holster selection (Security, CD, HoP)" path = /obj/item/clothing/accessory/holster - allowed_roles = list(JOB_SITE_MANAGER,JOB_HEAD_OF_PERSONNEL,JOB_SECURITY_OFFICER,JOB_WARDEN,JOB_HEAD_OF_SECURITY,JOB_DETECTIVE,"Explorer") + allowed_roles = list(JOB_SITE_MANAGER,JOB_HEAD_OF_PERSONNEL,JOB_SECURITY_OFFICER,JOB_WARDEN,JOB_HEAD_OF_SECURITY,JOB_DETECTIVE,JOB_PATHFINDER,JOB_EXPLORER) diff --git a/maps/southern_cross/loadout/loadout_head.dm b/maps/southern_cross/loadout/loadout_head.dm index bf35942c106..137f9ef8b42 100644 --- a/maps/southern_cross/loadout/loadout_head.dm +++ b/maps/southern_cross/loadout/loadout_head.dm @@ -1,4 +1,4 @@ /datum/gear/head/pilot display_name = "helmet, pilot (Pilot)" path = /obj/item/clothing/head/ompilot/alt //VOREStation Edit - allowed_roles = list("Pilot") \ No newline at end of file + allowed_roles = list(JOB_PILOT) diff --git a/tgui/packages/tgui-panel/package.json b/tgui/packages/tgui-panel/package.json index d435245e3fe..8a3c4b562c7 100644 --- a/tgui/packages/tgui-panel/package.json +++ b/tgui/packages/tgui-panel/package.json @@ -6,7 +6,7 @@ "@types/node": "^20.12.3", "@types/react": "^18.2.74", "common": "workspace:*", - "dompurify": "^2.4.9", + "dompurify": "^2.5.4", "react": "^18.2.0", "react-dom": "^18.2.0", "tgui": "workspace:*", diff --git a/tgui/packages/tgui/interfaces/Canvas.tsx b/tgui/packages/tgui/interfaces/Canvas.tsx index d54eb7e66cd..ad49add4333 100644 --- a/tgui/packages/tgui/interfaces/Canvas.tsx +++ b/tgui/packages/tgui/interfaces/Canvas.tsx @@ -1,8 +1,9 @@ +import { clamp } from 'common/math'; import { BooleanLike } from 'common/react'; -import { Component, createRef, RefObject } from 'react'; +import { Component, createRef, RefObject, useState } from 'react'; import { useBackend } from '../backend'; -import { Box, Button } from '../components'; +import { Box, Button, Stack } from '../components'; import { Window } from '../layouts'; const PX_PER_UNIT = 24; @@ -12,11 +13,16 @@ type PaintCanvasProps = Partial<{ value: string[][]; dotsize: number; res: number; + tool: Tool; }>; class PaintCanvas extends Component { canvasRef: RefObject; + mouseIsDown: boolean; + lastSuccessfulPaint: number; onCVClick: (x: number, y: number) => void; + lineStart: [number, number] | null; + lastHovered: [number, number] | null; constructor(props) { super(props); @@ -32,10 +38,128 @@ class PaintCanvas extends Component { this.drawCanvas(this.props); } + getLinePixels() { + if (!this.lineStart || !this.lastHovered) { + return; + } + + let [x0, y0] = this.lineStart; + let [x1, y1] = this.lastHovered; + + x0 = x0 - 1; + x1 = x1 - 1; + y0 = y0 - 1; + y1 = y1 - 1; + + let dx = Math.abs(x1 - x0); + let sx = x0 < x1 ? 1 : -1; + let dy = -Math.abs(y1 - y0); + let sy = y0 < y1 ? 1 : -1; + let error = dx + dy; + + let pixels: [number, number][] = []; + + while (true) { + pixels.push([x0, y0]); + // grid[x0][y0] = '#000'; + + if (x0 === x1 && y0 === y1) { + break; + } + + let e2 = error * 2; + if (e2 >= dy) { + error = error + dy; + x0 = x0 + sx; + } + if (e2 <= dx) { + error = error + dx; + y0 = y0 + sy; + } + } + + return pixels; + } + + drawLine(grid: string[][]) { + let pixels = this.getLinePixels(); + if (!pixels) { + return; + } + + for (let [x, y] of pixels) { + if (x < grid.length && y < grid[0].length) { + grid[x][y] = '#000000'; + } + } + } + + areaFill(grid: string[][]) { + if (!this.lastHovered) { + return; + } + + let [x_start, y_start] = this.lastHovered; + x_start = clamp(x_start - 1, 0, grid.length - 1); + y_start = clamp(y_start - 1, 0, grid[0].length - 1); + + const origPixelColor = grid[x_start][y_start]; + const inside = (x_c, y_c) => { + if (x_c < 0 || y_c < 0 || x_c >= grid.length || y_c >= grid[0].length) { + return false; + } + return grid[x_c][y_c] === origPixelColor; + }; + + let s: [number, number, number, number][] = []; + s.push([x_start, x_start, y_start, 1]); + s.push([x_start, x_start, y_start - 1, -1]); + + let pixels_touched: [number, number][] = []; + + while (s.length) { + // This can't fail because of our while condition + let [x1, x2, y, dy] = s.pop()!; + + let x = x1; + if (inside(x, y)) { + while (inside(x - 1, y)) { + grid[x - 1][y] = '#000000'; + pixels_touched.push([x - 1, y]); + x = x - 1; + } + if (x < x1) { + s.push([x, x1 - 1, y - dy, -dy]); + } + } + + while (x1 <= x2) { + while (inside(x1, y)) { + grid[x1][y] = '#000000'; + pixels_touched.push([x1, y]); + x1 = x1 + 1; + } + if (x1 > x) { + s.push([x, x1 - 1, y + dy, dy]); + } + if (x1 - 1 > x2) { + s.push([x2 + 1, x1 - 1, y - dy, -dy]); + } + x1 = x1 + 1; + while (x1 < x2 && !inside(x1, y)) { + x1 = x1 + 1; + } + x = x1; + } + } + + return pixels_touched; + } + drawCanvas(propSource: PaintCanvasProps) { const canvas = this.canvasRef.current!; const ctx = canvas.getContext('2d')!; - const grid = propSource.value; + const grid = JSON.parse(JSON.stringify(propSource.value)); if (!grid) { return; } @@ -48,6 +172,13 @@ class PaintCanvas extends Component { const y_scale = Math.round(canvas.height / y_size); ctx.save(); ctx.scale(x_scale, y_scale); + + if (this.mouseIsDown && propSource.tool === Tool.Line) { + this.drawLine(grid); + } else if (propSource.tool === Tool.Fill) { + this.areaFill(grid); + } + for (let x = 0; x < grid.length; x++) { const element = grid[x]; for (let y = 0; y < element.length; y++) { @@ -59,22 +190,96 @@ class PaintCanvas extends Component { ctx.restore(); } - clickwrapper(event: React.MouseEvent) { + getCoord( + event: React.MouseEvent, + ): [number, number] | null { const value = this.props.value; if (!value) { - return; + return null; } const x_size = value.length; if (!x_size) { - return; + return null; } + const y_size = this.props.value[0].length; const canvas = this.canvasRef.current!; const x_scale = canvas.width / x_size; const y_scale = canvas.height / y_size; const x = Math.floor(event.nativeEvent.offsetX / x_scale) + 1; const y = Math.floor(event.nativeEvent.offsetY / y_scale) + 1; - this.onCVClick(x, y); + + return [x, y]; + } + + mouseDown(event: React.MouseEvent) { + this.mouseIsDown = true; + + let coord = this.getCoord(event); + if (!coord) { + return; + } + + this.lineStart = coord; + this.lastHovered = coord; + + if (this.props.tool === Tool.Paintbrush) { + this.onCVClick(coord[0], coord[1]); + } else if (this.props.tool === Tool.Fill) { + let gridCopy = JSON.parse(JSON.stringify(this.props.value)); + let pixels_touched = this.areaFill(gridCopy); + if (pixels_touched) { + for (let [x, y] of pixels_touched) { + this.onCVClick(x + 1, y + 1); + } + } + } + } + + mouseUp(event: React.MouseEvent) { + this.mouseIsDown = false; + + let coord = this.getCoord(event); + if (!coord) { + return; + } + + this.lastHovered = coord; + + if (this.props.tool === Tool.Line) { + let line = this.getLinePixels(); + if (line) { + for (let [x, y] of line) { + this.onCVClick(x + 1, y + 1); + } + } + } + } + + mouseMove(event: React.MouseEvent) { + const coord = this.getCoord(event); + if (!coord) { + return; + } + + this.lastHovered = coord; + + let time = new Date().getTime(); + + if (this.lastSuccessfulPaint + 50 > time) { + return; + } + + if (this.mouseIsDown && this.props.tool === Tool.Paintbrush) { + this.onCVClick(coord[0], coord[1]); + this.lastSuccessfulPaint = time; + } else if (this.mouseIsDown && this.props.tool === Tool.Line) { + // Force redraw + this.drawCanvas(this.props); + this.lastSuccessfulPaint = time; + } else if (this.props.tool === Tool.Fill) { + this.drawCanvas(this.props); + } } render() { @@ -86,7 +291,10 @@ class PaintCanvas extends Component { width={width * dotsize || 300} height={height * dotsize || 300} {...rest} - onClick={(e) => this.clickwrapper(e)} + // onClick={(e) => this.clickwrapper(e)} + onMouseDown={(e) => this.mouseDown(e)} + onMouseMove={(e) => this.mouseMove(e)} + onMouseUp={(e) => this.mouseUp(e)} > Canvas failed to render. @@ -106,9 +314,17 @@ type Data = { finalized: BooleanLike; }; +enum Tool { + Paintbrush, + Line, + Fill, +} + export const Canvas = (props) => { const { act, data } = useBackend(); + const [tool, setTool] = useState(Tool.Paintbrush); + const dotsize = PX_PER_UNIT; const [width, height] = getImageSize(data.grid); return ( @@ -117,20 +333,56 @@ export const Canvas = (props) => { height={Math.min(700, height * dotsize + 72)} > + + + + + + + + + +
+ + + {target.active_restrictions.map((active_restriction, i) => { + return ( + + ); + })} + + +
+
+ +
+ + + {target.possible_restrictions.map((possible_restriction, i) => { + return ( + + ); + })} + + +
+
+ +
+ + ); +}; diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotAccess.tsx b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotAccess.tsx new file mode 100644 index 00000000000..a5780f93e3e --- /dev/null +++ b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotAccess.tsx @@ -0,0 +1,180 @@ +import { capitalize } from 'common/string'; +import { useState } from 'react'; +import { useBackend } from 'tgui/backend'; +import { + Button, + Divider, + Flex, + Icon, + Image, + Input, + Section, + Stack, +} from 'tgui/components'; + +import { NoSpriteWarning } from '../components'; +import { prepareSearch } from '../functions'; +import { Access, Target } from '../types'; + +export const ModifyRobotAccess = (props: { + target: Target; + tab_icon: string; + all_access: Access[]; +}) => { + const { act } = useBackend(); + const { target, tab_icon, all_access } = props; + const [searchAccessAll, setSearchAccessAll] = useState(''); + const [searchAccessActive, setSearchAccessActive] = useState(''); + + return ( + <> + {!target.active && } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +const AccessSection = (props: { + title: string; + searchText: string; + onSearchText: Function; + access: Access[]; + action: string; + buttonColor: string; + buttonIcon: string; +}) => { + const { act } = useBackend(); + const { + title, + searchText, + onSearchText, + access, + action, + buttonColor, + buttonIcon, + } = props; + return ( +
+ onSearchText(value)} + /> + + + + {prepareSearch(access, searchText).map((acc, i) => { + return ( + + ); + })} + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotComponent.tsx b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotComponent.tsx new file mode 100644 index 00000000000..4450c6009cd --- /dev/null +++ b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotComponent.tsx @@ -0,0 +1,301 @@ +import { capitalize } from 'common/string'; +import { useState } from 'react'; +import { useBackend } from 'tgui/backend'; +import { + Box, + Button, + Divider, + Dropdown, + Flex, + Icon, + Image, + Input, + Section, + Stack, +} from 'tgui/components'; + +import { NoSpriteWarning } from '../components'; +import { prepareSearch } from '../functions'; +import { Cell, Component, Target } from '../types'; + +export const ModifyRobotComponent = (props: { + target: Target; + cell: string | null; + cells: Record; +}) => { + const { target, cell, cells } = props; + const [searchComponentReplaceText, setSearchComponentReplaceText] = + useState(''); + const [searchComponentRemoveText, setSearchComponentRemoveText] = + useState(''); + const [selectedCell, setSelectedCell] = useState(cell || ''); + const cell_options = Object.keys(cells) as Array; + + return ( + <> + {!target.active && } + + + + + + + + + + +
+ Current cell:{' '} + {cell ? ( + capitalize(cell) + ) : ( + + No cell installed! + + )} + + + + Charge State: {cells[selectedCell]?.charge} /{' '} + {cells[selectedCell]?.max_charge} + + + Charge Rate: {cells[selectedCell]?.charge_amount} + + + Self Charge:{' '} + {cells[selectedCell]?.self_charge ? 'Yes' : 'No'} + + +
+
+
+
+ + + +
+ + ); +}; + +const ComponentSection = (props: { + title: string; + searchText: string; + onSearchText: Function; + components: Component[]; + action: string; + buttonColor: string; + buttonIcon: string; + celltype?: string; + selected_cell?: string; + cell?: string; +}) => { + const { act } = useBackend(); + const { + title, + searchText, + onSearchText, + components, + action, + buttonColor, + buttonIcon, + celltype, + selected_cell, + cell, + } = props; + return ( +
+ onSearchText(value)} + /> + + + + {prepareSearch(components, searchText).map((component, i) => { + return ( + + ); + })} + + +
+ ); +}; + +function checkDisabled( + component: Component, + action: string, + selected_cell: string | undefined, + cell: string | undefined, +): boolean { + switch (action) { + case 'rem_component': + return !component.exists; + case 'add_component': + if ( + selected_cell && + cell && + component.name === 'power cell' && + selected_cell !== cell + ) { + return false; + } + return ( + component.installed === 1 && + !!component.exists && + component.brute_damage === 0 && + component.electronics_damage === 0 + ); + } + return false; +} + +function getComponentTooltip( + component: Component, + action: string, + selected_cell: string | undefined, + cell: string | undefined, +): string { + switch (action) { + case 'add_component': + if (component.installed === 0 || !component.exists) { + return 'Component missing!'; + } + if ( + component.installed === -1 || + component.brute_damage + component.electronics_damage >= + component.max_damage + ) { + return 'Component destroyed!'; + } + if (checkDisabled(component, action, selected_cell, cell)) { + return 'Disabled due to fully intact component!'; + } + return ''; + case 'rem_component': + return component.exists ? '' : 'Disabled due to missing component!'; + } + return ''; +} + +function getComponentColor( + component: Component, + action: string, +): string | undefined { + switch (action) { + case 'add_component': + if ( + component.brute_damage + component.electronics_damage >= + component.max_damage || + component.installed !== 1 || + !component.exists + ) { + return 'black'; + } + if ( + (component.brute_damage + component.electronics_damage) / + component.max_damage > + 0.66 + ) { + return 'red'; + } + if ( + (component.brute_damage + component.electronics_damage) / + component.max_damage > + 0.33 + ) { + return 'orange'; + } + if ( + (component.brute_damage + component.electronics_damage) / + component.max_damage > + 0 + ) { + return 'yellow'; + } + return undefined; + case 'rem_component': + return undefined; + } + return ''; +} diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotModules.tsx b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotModules.tsx new file mode 100644 index 00000000000..67ed0ac3b78 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotModules.tsx @@ -0,0 +1,201 @@ +import { capitalize } from 'common/string'; +import { useState } from 'react'; +import { useBackend } from 'tgui/backend'; +import { + Box, + Button, + Divider, + Dropdown, + Flex, + Icon, + Image, + Input, + Section, + Stack, +} from 'tgui/components'; + +import { NoSpriteWarning } from '../components'; +import { prepareSearch } from '../functions'; +import { Module, Source, Target } from '../types'; + +export const ModifyRobotModules = (props: { + target: Target; + source: Source; + model_options: string[]; +}) => { + const { target, source, model_options } = props; + const { act } = useBackend(); + const [searchSourceText, setSearchSourceText] = useState(''); + const [searchModuleText, setSearchModulText] = useState(''); + + return ( + <> + {!target.active && } + + +
+ Robot to salvage + + act('select_source', { + new_source: value, + }) + } + /> + {!!source && ( + + )} +
+
+ + + + + act('swap_module')} + /> + + + act('ert_toggle')} + /> + + + + + +
+ {target ? target.module : ''} + act('reset_module')} + tooltip="Allows to reset the module back to default." + > + Reset Module + + + +
+
+
+ + ); +}; + +const SelectionField = (props: { + previewImage: string | undefined; + searchText: string; + onSearchText: Function; + action: string; + buttonIcon: string; + buttonColor: string; + modules: Module[]; +}) => { + const { act } = useBackend(); + const { + previewImage, + searchText, + onSearchText, + action, + modules, + buttonIcon, + buttonColor, + } = props; + + return ( + <> + + + + onSearchText(value)} + /> + + + + {prepareSearch(modules, searchText).map((modul_option, i) => { + return ( + + ); + })} + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotPKA.tsx b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotPKA.tsx new file mode 100644 index 00000000000..1662c8053ca --- /dev/null +++ b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotPKA.tsx @@ -0,0 +1,112 @@ +import { capitalize } from 'common/string'; +import { useState } from 'react'; +import { useBackend } from 'tgui/backend'; +import { + Box, + Button, + Divider, + Flex, + Image, + Input, + NoticeBox, +} from 'tgui/components'; + +import { NoSpriteWarning } from '../components'; +import { getModuleIcon, prepareSearch } from '../functions'; +import { Target } from '../types'; + +export const ModifyRobotPKA = (props: { target: Target }) => { + const { act } = useBackend(); + const { target } = props; + const [searchModkitText, setSearchModkitText] = useState(''); + const [searchInstalledtext, setSearchInstalledtext] = useState(''); + + return ( + <> + {!target.active && } + {!target.pka ? ( + {target.name} has no PKA installed. + ) : ( + + + + Remaining Capacity: {target.pka.capacity} + + setSearchModkitText(value)} + /> + + {prepareSearch(target.pka.modkits, searchModkitText).map( + (modkit, i) => { + return ( + + ); + }, + )} + + + + + + + + + + Used Capacity: {target.pka.max_capacity - target.pka.capacity} + + + setSearchInstalledtext(value)} + /> + + {prepareSearch( + target.pka.installed_modkits, + searchInstalledtext, + ).map((modkit, i) => { + return ( + + ); + })} + + + )} + + ); +}; diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotRadio.tsx b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotRadio.tsx new file mode 100644 index 00000000000..5af3a8cd063 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotRadio.tsx @@ -0,0 +1,126 @@ +import { capitalize } from 'common/string'; +import { useState } from 'react'; +import { useBackend } from 'tgui/backend'; +import { + Button, + Divider, + Flex, + Icon, + Image, + Input, + Section, + Stack, +} from 'tgui/components'; + +import { NoSpriteWarning } from '../components'; +import { prepareSearch } from '../functions'; +import { Target } from '../types'; + +export const ModifyRobotRadio = (props: { target: Target }) => { + const { target } = props; + const [searchChannelAddText, setSearchChannelAddText] = useState(''); + const [searchChannelRemoveText, setSearchChannelRemoveText] = + useState(''); + + return ( + <> + {!target.active && } + + + + + + + + + + + + + ); +}; + +const RadioSection = (props: { + title: string; + searchText: string; + onSearchText: Function; + channels: string[]; + action: string; + buttonColor: string; + buttonIcon: string; +}) => { + const { act } = useBackend(); + const { + title, + searchText, + onSearchText, + channels, + action, + buttonColor, + buttonIcon, + } = props; + return ( +
+ onSearchText(value)} + /> + + + + {prepareSearch(channels, searchText).map((channel, i) => { + return ( + + ); + })} + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotUpgrades.tsx b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotUpgrades.tsx new file mode 100644 index 00000000000..d34337828a2 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotUpgrades.tsx @@ -0,0 +1,154 @@ +import { capitalize } from 'common/string'; +import { useState } from 'react'; +import { useBackend } from 'tgui/backend'; +import { + Button, + Divider, + Flex, + Image, + Input, + Section, + Stack, +} from 'tgui/components'; + +import { NoSpriteWarning } from '../components'; +import { install2col } from '../constants'; +import { prepareSearch } from '../functions'; +import { Target, Upgrade } from '../types'; + +export const ModifyRobotUpgrades = (props: { target: Target }) => { + const { target } = props; + const [searchAddCompatibilityText, setSearchAddCompatibilityText] = + useState(''); + const [searchRemoveCompatibilityText, setSearchRemoveCompatibilityText] = + useState(''); + const [searchUtilityUpgradeText, setsearchUtilityUpgradeText] = + useState(''); + const [searchBasicUpgradeText, setSearchBasicUpgradeText] = + useState(''); + const [searchAdvancedUpgradeText, setSearchAdvancedUpgradeText] = + useState(''); + const [searchRestrictedUpgradeText, setSearchRestrictedUpgradeText] = + useState(''); + + return ( + <> + {!target.active && } + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +const UpgradeSection = (props: { + title: string; + searchText: string; + onSearchText: Function; + upgrades: Upgrade[]; + action: string; +}) => { + const { act } = useBackend(); + const { title, searchText, onSearchText, upgrades, action } = props; + return ( +
+ onSearchText(value)} + /> + + + + {prepareSearch(upgrades, searchText).map((upgrade, i) => { + return ( + + ); + })} + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/components.tsx b/tgui/packages/tgui/interfaces/ModifyRobot/components.tsx new file mode 100644 index 00000000000..60d3219e4db --- /dev/null +++ b/tgui/packages/tgui/interfaces/ModifyRobot/components.tsx @@ -0,0 +1,12 @@ +import { NoticeBox } from 'tgui/components'; + +export const NoSpriteWarning = (props: { name: string }) => { + const { name } = props; + + return ( + + Warning, {name} has not yet chosen a sprite. Functionality might be + limited. + + ); +}; diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/constants.ts b/tgui/packages/tgui/interfaces/ModifyRobot/constants.ts new file mode 100644 index 00000000000..ab554eb3c3e --- /dev/null +++ b/tgui/packages/tgui/interfaces/ModifyRobot/constants.ts @@ -0,0 +1,6 @@ +export const install2col = { + undefined: '', + 0: 'red', + 1: 'green', + 2: 'grey', +}; diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/functions.ts b/tgui/packages/tgui/interfaces/ModifyRobot/functions.ts new file mode 100644 index 00000000000..81386eb291f --- /dev/null +++ b/tgui/packages/tgui/interfaces/ModifyRobot/functions.ts @@ -0,0 +1,40 @@ +import { filter } from 'common/collections'; +import { flow } from 'common/fp'; +import { createSearch } from 'common/string'; + +import { Module } from './types'; + +type SearchObject = string | { name: string }; + +export function prepareSearch( + objects: T[], + searchText: string = '', +): T[] { + const testSearch = createSearch(searchText, (object: T): string => { + if (typeof object === 'string') { + return object; + } else { + return object['name']; + } + }); + return flow([ + (objects: T[]) => { + // Optional search term + if (!searchText) { + return objects as any; + } else { + return filter(objects, testSearch) as any; + } + }, + ])(objects); +} + +export function getModuleIcon(modules: Module[], name: string) { + if (modules) { + const module = modules.filter((module) => module.name === name); + if (module.length > 0) { + return module[0].icon; + } + } + return ''; +} diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/index.tsx b/tgui/packages/tgui/interfaces/ModifyRobot/index.tsx new file mode 100644 index 00000000000..dba66ce648a --- /dev/null +++ b/tgui/packages/tgui/interfaces/ModifyRobot/index.tsx @@ -0,0 +1,217 @@ +import { useEffect, useState } from 'react'; +import { useBackend } from 'tgui/backend'; +import { + Button, + Divider, + Dropdown, + Input, + LabeledList, + NoticeBox, + Section, + Stack, + Tabs, +} from 'tgui/components'; +import { Window } from 'tgui/layouts'; + +import { LawManagerLaws, LawManagerLawSets } from '../LawManager'; +import { ModifyRobotNoModule } from './ModifyRobotNoModule'; +import { ModifyRobotAccess } from './ModifyRobotTabs/ModifyRobotAccess'; +import { ModifyRobotComponent } from './ModifyRobotTabs/ModifyRobotComponent'; +import { ModifyRobotModules } from './ModifyRobotTabs/ModifyRobotModules'; +import { ModifyRobotPKA } from './ModifyRobotTabs/ModifyRobotPKA'; +import { ModifyRobotRadio } from './ModifyRobotTabs/ModifyRobotRadio'; +import { ModifyRobotUpgrades } from './ModifyRobotTabs/ModifyRobotUpgrades'; +import { Data } from './types'; + +export const ModifyRobot = (props) => { + const { act, data } = useBackend(); + + const { + target, + all_robots, + source, + model_options, + cell, + cell_options, + id_icon, + access_options, + ion_law_nr, + ion_law, + zeroth_law, + inherent_law, + supplied_law, + supplied_law_position, + zeroth_laws, + ion_laws, + inherent_laws, + supplied_laws, + has_zeroth_laws, + has_ion_laws, + has_inherent_laws, + has_supplied_laws, + isAI, + channel, + channels, + law_sets, + } = data; + + const [tab, setTab] = useState(0); + const [robotName, setRobotName] = useState(target ? target.name : ''); + const [searchLawName, setSearchLawName] = useState(''); + + useEffect(() => { + if (target?.name) { + setRobotName(target.name); + } + }, [target?.name]); + + const tabs: React.JSX.Element[] = []; + + tabs[0] = ( + + ); + tabs[1] = ; + tabs[2] = ; + tabs[3] = ; + tabs[4] = ( + + ); + tabs[5] = ( + + ); + tabs[6] = ( + + ); + tabs[7] = ( +
+ +
+ ); + + return ( + + + {target ? ( + + {target.name} + {!!target.ckey && ' played by ' + target.ckey}. + + ) : ( + No target selected. Please pick one. + )} + + + + + + act('select_target', { + new_target: value, + }) + } + /> + + {!!target?.module && ( + <> + + setRobotName(value)} + /> + + + + + + )} + + + + + {!!target && + (!target.module ? ( + + ) : ( + <> + + setTab(0)}> + Module Manager + + setTab(1)}> + Upgrade Manager + + setTab(2)}> + PKA + + setTab(3)}> + Radio Manager + + setTab(4)}> + Component Manager + + setTab(5)}> + Access Manager + + setTab(6)}> + Law Manager + + setTab(7)}> + Law Sets + + + {tabs[tab]} + + ))} + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/types.ts b/tgui/packages/tgui/interfaces/ModifyRobot/types.ts new file mode 100644 index 00000000000..02b840948ba --- /dev/null +++ b/tgui/packages/tgui/interfaces/ModifyRobot/types.ts @@ -0,0 +1,133 @@ +import { BooleanLike } from 'common/react'; + +export type Data = { + source: Source; + target: Target | null; + all_robots: DropdownEntry[]; + model_options: string[] | null; + cell: string | null; + cell_options: Record; + id_icon: string; + access_options: Access[] | undefined; + ion_law_nr: string; + ion_law: string; + zeroth_law: string; + inherent_law: string; + supplied_law: string; + supplied_law_position: number; + zeroth_laws: law[]; + ion_laws: law[]; + inherent_laws: law[]; + supplied_laws: law[]; + has_zeroth_laws: number; + has_ion_laws: number; + has_inherent_laws: number; + has_supplied_laws: number; + isAI: BooleanLike; + channel: string; + channels: { channel: string }[]; + law_sets: law_pack[]; +}; + +export type DropdownEntry = { + displayText: string; + value: string; +}; + +export type Target = { + name: string; + ckey: string; + module: string; + active: BooleanLike; + crisis_override: BooleanLike; + active_restrictions: string[]; + possible_restrictions: string[]; + front: string | undefined; + side: string | undefined; + side_alt: string | undefined; + back: string | undefined; + modules: Module[]; + whitelisted_upgrades: Upgrade[]; + blacklisted_upgrades: Upgrade[]; + utility_upgrades: Upgrade[]; + basic_upgrades: Upgrade[]; + advanced_upgrades: Upgrade[]; + restricted_upgrades: Upgrade[]; + radio_channels: string[]; + availalbe_channels: string[]; + pka: PKA | undefined; + components: Component[]; + active_access: Access[]; +}; + +export type Upgrade = { + name: string; + path: string; + installed: number | undefined; +}; + +export type Source = { + model: string; + front: string; + modules: Module[]; +} | null; + +export type Module = { name: string; ref: string; icon: string; desc: string }; + +export type Component = { + name: string; + ref: string; + brute_damage: number; + electronics_damage: number; + max_damage: number; + installed: number; + exists: BooleanLike; +}; + +export type PKA = { + name: string; + modkits: { + name: string; + path: string; + costs: number; + denied: BooleanLike; + denied_by: string; + }[]; + installed_modkits: { name: string; ref: string; costs: number }[]; + capacity: number; + max_capacity: number; +}; + +export type Cell = { + path: string; + charge: number; + max_charge: number; + charge_amount: number; + self_charge: BooleanLike; +}; + +export type Access = { id: number; name: string }; + +type law_pack = { + name: string; + header: string; + ref: string; + laws: { + zeroth_laws: law[]; + has_zeroth_laws: number; + ion_laws: law[]; + has_ion_laws: number; + inherent_laws: law[]; + has_inherent_laws: number; + supplied_laws: law[]; + has_supplied_laws: number; + }; +}; + +type law = { + law: string; + index: number; + state: number; + ref: string; + zero: boolean; // Local UI var +}; diff --git a/tgui/packages/tgui/interfaces/PlayerEffects/PlayerEffectsTabs/ControlSmites.tsx b/tgui/packages/tgui/interfaces/PlayerEffects/PlayerEffectsTabs/ControlSmites.tsx index 8334dfdd384..bfd83aa3e2e 100644 --- a/tgui/packages/tgui/interfaces/PlayerEffects/PlayerEffectsTabs/ControlSmites.tsx +++ b/tgui/packages/tgui/interfaces/PlayerEffects/PlayerEffectsTabs/ControlSmites.tsx @@ -54,6 +54,15 @@ export const ControlSmites = (props) => { + + + ); }; diff --git a/tgui/packages/tgui/interfaces/common/RankIcon.tsx b/tgui/packages/tgui/interfaces/common/RankIcon.tsx index 646a4e43ad7..76d5dd46b9f 100644 --- a/tgui/packages/tgui/interfaces/common/RankIcon.tsx +++ b/tgui/packages/tgui/interfaces/common/RankIcon.tsx @@ -1,103 +1,637 @@ import { Icon } from '../../components'; const rank2icon = { - // Command - 'Colony Director': 'user-tie', + // Command Site Manager 'Site Manager': 'user-tie', Overseer: 'user-tie', + 'Facility Director': 'user-tie', + 'Chief Supervisor': 'user-tie', + Captain: 'user-tie', + 'Colony Director': 'user-tie', + // HOP 'Head of Personnel': 'briefcase', 'Crew Resources Officer': 'briefcase', 'Deputy Director': 'briefcase', - 'Command Secretary': 'user-tie', - // Security + 'Staff Manager': 'briefcase', + 'Facility Steward': 'briefcase', + 'First Mate': 'briefcase', + 'Performance Management Supervisor': 'briefcase', + // Secretary + 'Command Secretary': 'address-card', + 'Command Liaison': 'address-card', + 'Command Assistant': 'address-card', + 'Command Intern': 'address-card', + 'Bridge Secretary': 'address-card', + 'Bridge Assistant': 'address-card', + 'Bridge Officer': 'address-card', + // Security HOS 'Head of Security': 'user-shield', 'Security Commander': 'user-shield', 'Chief of Security': 'user-shield', - Warden: ['city', 'shield-alt'], - Detective: 'search', - 'Forensic Technician': 'search', - 'Security Officer': 'user-shield', - 'Junior Officer': 'user-shield', - // Engineering - 'Chief Engineer': 'toolbox', + 'Security Managery': 'user-shield', + // Warden + Warden: ['city', 'shield-halved'], + 'Brig Sentry': ['city', 'shield-halved'], + 'Armory Superintendent': ['city', 'shield-halved'], + 'Master-at-Arms': ['city', 'shield-halved'], + // Detective + Detective: 'magnifying-glass', + Investigator: 'magnifying-glass', + 'Security Inspector': 'magnifying-glass', + 'Forensic Technician': 'magnifying-glass', + // Security Officer + 'Security Officer': 'shield', + 'Patrol Officer': 'shield', + 'Security Guard': 'shield', + 'Security Deputy': 'shield', + 'Junior Officer': 'shield', + 'Security Contractor': 'shield', + // Engineering CE + 'Chief Engineer': 'screwdriver-wrench', + 'Head Engineer': 'screwdriver-wrench', + Foreman: 'screwdriver-wrench', + 'Maintenance Manager': 'screwdriver-wrench', + // Atmospheric Technician 'Atmospheric Technician': 'wind', - 'Station Engineer': 'toolbox', + 'Atmospheric Engineer': 'wind', + 'Atmospheric Maintainer': 'wind', + 'Disposals Technician': 'wind', + 'Fuel Technician': 'wind', + // Engineer + Engineer: 'toolbox', 'Maintenance Technician': 'wrench', 'Engine Technician': 'toolbox', - Electrician: 'toolbox', - // Medical - 'Chief Medical Officer': 'user-md', + Electrician: 'screwdriver', + 'Construction Engineer': 'trowel-bricks', + 'Engineering Contractor': 'ruler', + // Medical CMO + 'Chief Medical Officer': 'user-doctor', + 'Chief Physician': 'user-doctor', + 'Medical Director': 'user-doctor', + 'Healthcare Manager': 'user-doctor', + // Chemist Chemist: 'mortar-pestle', Pharmacist: 'mortar-pestle', - 'Medical Doctor': 'user-md', - Surgeon: 'user-md', - 'Emergency Physician': 'user-md', - Nurse: 'user-md', + Pharmacologist: 'mortar-pestle', + // Medical Doctor + 'Medical Doctor': 'suitcase-medical', + Physician: 'suitcase-medical', + 'Medical Practitioner': 'suitcase-medical', + Surgeon: 'syringe', + 'Emergency Physician': 'suitcase-medical', + Nurse: 'user-nurse', + Orderly: 'book-medical', Virologist: 'disease', - Paramedic: 'ambulance', - 'Emergency Medical Technician': 'ambulance', + 'Medical Contractor': 'notes-medical', + // Paramedic + Paramedic: 'truck-medical', + 'Emergency Medical Technician': 'truck-medical', + 'Medical Responder': 'truck-medical', + 'Search and Rescue': 'truck-droplet', + // Psychiatrist Psychiatrist: 'couch', Psychologist: 'couch', - // Science + Psychoanalyst: 'couch', + Psychotherapist: 'couch', + // Genetecist + Geneticist: 'dna', + // Brig Physician + 'Brig Physician': ['kit-medical', 'shield-halved'], + 'Security Medic': ['kit-medical', 'shield-halved'], + 'Security Medic Care Unit': ['kit-medical', 'shield-halved'], + 'Penitentiary Medical Care Unit': ['kit-medical', 'shield-halved'], + 'Junior Brig Physician': ['kit-medical', 'shield-halved'], + 'Detention Health Officer': ['kit-medical', 'shield-halved'], + // Science RD 'Research Director': 'user-graduate', 'Research Supervisor': 'user-graduate', + 'Research Manager': 'user-graduate', + 'Head of Development': 'user-graduate', + 'Head Scientist': 'user-graduate', + // Roboticist Roboticist: 'robot', + 'Assembly Technician': 'screwdriver', 'Biomechanical Engineer': ['wrench', 'heartbeat'], 'Mechatronic Engineer': 'wrench', + // Scientist Scientist: 'flask', - Xenoarchaeologist: 'flask', - Anomalist: 'flask', - 'Phoron Researcher': 'flask', + Researcher: 'flask-vial', + 'Lab Assistant': 'flask', + Xenoarchaeologist: 'bone', + Xenopaleontologist: 'bone', + Anomalist: 'atom', + 'Phoron Researcher': 'vials', + 'Gas Physicist': 'microscope', 'Circuit Designer': 'car-battery', + 'Circuit Programmer': 'laptop-file', + // Xenobiologist Xenobiologist: 'meteor', + Xenozoologist: 'locust', + Xenoanthropologist: 'bugs', + // Xenobotanist Xenobotanist: ['biohazard', 'seedling'], - // Cargo + Xenohydroponicist: ['biohazard', 'droplet'], + Xenoflorist: ['biohazard', 'clover'], + // Cargo QM Quartermaster: 'box-open', 'Supply Chief': 'warehouse', - 'Cargo Technician': 'box-open', - 'Shaft Miner': 'hard-hat', - 'Drill Technician': 'hard-hat', - // Exploration + 'Logistics Manager': 'warehouse', + 'Cargo Supervisor': 'box-open', + // Cargo Technician + 'Cargo Technician': 'box', + 'Cargo Loader': 'dolly', + 'Cargo Handler': 'boxes-stacked', + 'Supply Courier': 'people-carry-box', + 'Disposals Sorter': 'recycle', + Mailman: 'envelopes-bulk', + // Shaft Miner + 'Shaft Miner': 'helmet-safety', + 'Deep Space Miner': 'bore-hole', + 'Drill Technician': 'oil-well', + Prospector: 'helmet-safety', + Excavator: 'bore-hole', + // Exploration Lead Pathfinder: 'binoculars', + 'Expedition Lead': 'binoculars', + 'Exploration Manager': 'binoculars', + // Explorer Explorer: 'user-astronaut', - 'Field Medic': ['user-md', 'user-astronaut'], - Pilot: 'space-shuttle', - // Civvies + Surveyor: 'user-astronaut', + 'Offsite Scout': 'user-astronaut', + 'Explorer Medic': ['user-astronaut', 'kit-medical'], + 'Explorer Technician': ['user-astronaut', 'screwdriver'], + // Field Medic + 'Field Medic': ['suitcase-medical', 'user-astronaut'], + 'Expedition Medic': ['suitcase-medical', 'user-astronaut'], + 'Offsite Medic': ['suitcase-medical', 'user-astronaut'], + // Pilot + Pilot: 'shuttle-space', + 'Co-Pilot': 'shuttle-space', + Navigator: 'shuttle-space', + Helmsman: 'shuttle-space', + // Barkeeper Bartender: 'glass-martini', + Barkeeper: 'wine-glass', + Barmaid: 'whiskey-glass', Barista: 'coffee', + Mixologist: 'martini-glass-citrus', + // Botanist Botanist: 'leaf', + Hydroponicist: 'droplet', Gardener: 'leaf', - Chaplain: 'place-of-worship', - Counselor: 'couch', + Cultivator: 'spa', + Farmer: 'plant-wilt', + Florist: 'spa', + Rancher: 'leaf', + // Chef Chef: 'utensils', + 'Sous-chef': 'spoon', Cook: 'utensils', + 'Kitchen Worker': 'kitchen-set', + // Chaplain + Chaplain: 'place-of-worship', + Counselor: 'cross', + Preacher: 'cross', + Missionary: 'cross', + Priest: 'cross', + Nun: 'church', + Monk: 'place-of-worship', + Guru: 'place-of-worship', + // Entertainer Entertainer: 'smile-beam', Performer: 'smile-beam', Musician: 'guitar', Stagehand: 'smile-beam', + Actor: 'face-laugh-wink', + Dancer: 'face-smile', + Singer: 'smusic', + Magician: 'wand-magic-sparkles', + Comedian: 'face-laugh-wink', + Tragedian: 'face-sad-tear', + Artist: 'smile-beam', + 'Game Master': 'dice', + // Entrepreneur + Entrepreneur: 'building', + Lawyer: 'gavel', + 'Private Eye': 'user-secret', + Bodyguard: 'person-military-pointing', + 'Personal Physician': 'star-of-life', + Dentist: 'teeth', + 'Fitness Instructor': 'dumbbell', + 'Yoga Teacher': 'person-walking', + Masseuse: 'bottle-droplet', + Tradesperson: 'money-bill-trend-up', + Streamer: 'desktop', + Influencer: 'computer', + 'Paranormal Investigator': 'magnifying-glass-arrow-right', + 'Personal Secretary': 'pen-to-square', + Stylist: 'hat-cowboy-side', + Fisher: 'fish-fins', + 'Fortune Teller': 'golf-ball-tee', + 'Spirit Healer': 'ghost', // All of the interns Intern: 'school', 'Apprentice Engineer': ['school', 'wrench'], - 'Medical Intern': ['school', 'user-md'], - 'Lab Assistant': ['school', 'flask'], - 'Security Cadet': ['school', 'shield-alt'], + 'Research Intern': ['school', 'flask'], + 'Security Cadet': ['school', 'shield-halved'], 'Jr. Cargo Tech': ['school', 'box'], 'Jr. Explorer': ['school', 'user-astronaut'], + Assistant: ['school', 'address-card'], Server: ['school', 'utensils'], - // Back to civvies + 'Technical Assistant': ['school', 'screwdriver'], + 'Medical Intern': ['school', 'user-nurse'], + 'Research Assistant"': ['school', 'flask'], + Visitor: 'user', + Resident: 'user', + // IAA 'Internal Affairs Agent': 'balance-scale', + 'Internal Affairs Liaison': 'balance-scale', + 'Internal Affairs Delegate': 'balance-scale', + 'Internal Affairs Investigator': 'balance-scale', + // Janitor Janitor: 'broom', Custodian: 'broom', 'Sanitation Technician': 'hand-sparkles', Maid: 'broom', + 'Garbage Collector': 'dumpster', + // Librarian Librarian: 'book', Journalist: 'newspaper', + Reporter: 'newspaper', Writer: 'book', Historian: 'chalkboard-teacher', + Archivist: 'book', Professor: 'chalkboard-teacher', - Visitor: 'user', + Academic: 'chalkboard-teacher', + Philosopher: 'book', + Curator: 'book', + // Off duty + 'Off-duty Officer': 'tree-city', + 'Off-duty Engineer': 'tree-city', + 'Off-duty Medic': 'tree-city', + 'Off-duty Scientist': 'tree-city', + 'Off-duty Cargo': 'tree-city', + 'Off-duty Explorer': 'tree-city', + 'Off-duty Worker': 'tree-city', + // AI / Robot + AI: 'display', + Cyborg: 'robot', + Robot: 'robot', + Drone: 'robot', + // Clown / Mime + Clown: 'bullhorn', + Jester: 'bullhorn', + Fool: 'bullhorn', + Mime: 'face-grin-tears', + Poseur: 'face-grin-tears', // Special roles 'Emergency Responder': 'fighter-jet', + // Talon + 'Talon Captain': ['location-arrow', 'user-tie'], + 'Talon Commander': ['location-arrow', 'user-tie'], + 'Talon Doctor': ['location-arrow', 'suitcase-medical'], + 'Talon Medic': ['location-arrow', 'suitcase-medical'], + 'Talon Engineer': ['location-arrow', 'wrench'], + 'Talon Technician': ['location-arrow', 'screwdriver'], + 'Talon Guard': ['location-arrow', 'shield'], + 'Talon Security': ['location-arrow', 'shield'], + 'Talon Marine': ['location-arrow', 'shield'], + 'Talon Pilot': ['location-arrow', 'shuttle-space'], + 'Talon Helmsman': ['location-arrow', 'shuttle-space'], + 'Talon Miner': ['location-arrow', 'helmet-safety'], + 'Talon Excavator': ['location-arrow', 'helmet-safety'], + // Robot Modules + Standard: 'robot', + Service: 'glass-martini', + Clerical: 'pen-to-square', + Research: 'flask', + Miner: 'helmet-safety', + Crisis: 'kit-medical', + Security: 'shield', + Combat: 'gun', + Engineering: 'wrench', + Gravekeeper: 'square-xmark', + Lost: 'location-crosshairs', + Protector: 'building-shield', + Mechanist: 'gears', + 'Combat Medic': 'x-ray', +}; + +const rank2color = { + // Command Site Manager + 'Site Manager': 'blue', + Overseer: 'blue', + 'Facility Director': 'blue', + 'Chief Supervisor': 'blue', + Captain: 'blue', + 'Colony Director': 'blue', + // HOP + 'Head of Personnel': 'blue', + 'Crew Resources Officer': 'blue', + 'Deputy Director': 'blue', + 'Staff Manager': 'blue', + 'Facility Steward': 'blue', + 'First Mate': 'blue', + 'Performance Management Supervisor': 'blue', + // Secretary + 'Command Secretary': 'blue', + 'Command Liaison': 'blue', + 'Command Assistant': 'blue', + 'Command Intern': 'blue', + 'Bridge Secretary': 'blue', + 'Bridge Assistant': 'blue', + 'Bridge Officer': 'blue', + // Security HOS + 'Head of Security': 'blue', + 'Security Commander': 'blue', + 'Chief of Security': 'blue', + 'Security Managery': 'blue', + // Warden + Warden: 'red', + 'Brig Sentry': 'red', + 'Armory Superintendent': 'red', + 'Master-at-Arms': 'red', + // Detective + Detective: 'red', + Investigator: 'red', + 'Security Inspector': 'red', + 'Forensic Technician': 'red', + // Security Officer + 'Security Officer': 'red', + 'Patrol Officer': 'red', + 'Security Guard': 'red', + 'Security Deputy': 'red', + 'Junior Officer': 'red', + 'Security Contractor': 'red', + // Engineering CE + 'Chief Engineer': 'blue', + 'Head Engineer': 'blue', + Foreman: 'blue', + 'Maintenance Manager': 'blue', + // Atmospheric Technician + 'Atmospheric Technician': 'orange', + 'Atmospheric Engineer': 'orange', + 'Atmospheric Maintainer': 'orange', + 'Disposals Technician': 'orange', + 'Fuel Technician': 'orange', + // Engineer + Engineer: 'orange', + 'Maintenance Technician': 'orange', + 'Engine Technician': 'orange', + Electrician: 'orange', + 'Construction Engineer': 'orange', + 'Engineering Contractor': 'orange', + // Medical CMO + 'Chief Medical Officer': 'blue', + 'Chief Physician': 'blue', + 'Medical Director': 'blue', + 'Healthcare Manager': 'blue', + // Chemist + Chemist: 'teal', + Pharmacist: 'teal', + Pharmacologist: 'teal', + // Medical Doctor + 'Medical Doctor': 'teal', + Physician: 'teal', + 'Medical Practitioner': 'teal', + Surgeon: 'teal', + 'Emergency Physician': 'teal', + Nurse: 'teal', + Orderly: 'teal', + Virologist: 'teal', + 'Medical Contractor': 'teal', + // Paramedic + Paramedic: 'teal', + 'Emergency Medical Technician': 'teal', + 'Medical Responder': 'teal', + 'Search and Rescue': 'teal', + // Psychiatrist + Psychiatrist: 'teal', + Psychologist: 'teal', + Psychoanalyst: 'teal', + Psychotherapist: 'teal', + // Genetecist + Geneticist: 'teal', + // Brig Physician + 'Brig Physician': 'teal', + 'Security Medic': 'teal', + 'Security Medic Care Unit': 'teal', + 'Penitentiary Medical Care Unit': 'teal', + 'Junior Brig Physician': 'teal', + 'Detention Health Officer': 'teal', + // Science RD + 'Research Director': 'blue', + 'Research Supervisor': 'blue', + 'Research Manager': 'blue', + 'Head of Development': 'blue', + 'Head Scientist': 'blue', + // Roboticist + Roboticist: 'purple', + 'Assembly Technician': 'purple', + 'Biomechanical Engineer': 'purple', + 'Mechatronic Engineer': 'purple', + // Scientist + Scientist: 'purple', + Researcher: 'purple', + 'Lab Assistant': 'purple', + Xenoarchaeologist: 'purple', + Xenopaleontologist: 'purple', + Anomalist: 'purple', + 'Phoron Researcher': 'purple', + 'Gas Physicist': 'purple', + 'Circuit Designer': 'purple', + 'Circuit Programmer': 'purple', + // Xenobiologist + Xenobiologist: 'purple', + Xenozoologist: 'purple', + Xenoanthropologist: 'purple', + // Xenobotanist + Xenobotanist: 'purple', + Xenohydroponicist: 'purple', + Xenoflorist: 'purple', + // Cargo QM + Quartermaster: 'brown', + 'Supply Chief': 'brown', + 'Logistics Manager': 'brown', + 'Cargo Supervisor': 'brown', + // Cargo Technician + 'Cargo Technician': 'brown', + 'Cargo Loader': 'brown', + 'Cargo Handler': 'brown', + 'Supply Courier': 'brown', + 'Disposals Sorter': 'brown', + Mailman: 'brown', + // Shaft Miner + 'Shaft Miner': 'brown', + 'Deep Space Miner': 'brown', + 'Drill Technician': 'brown', + Prospector: 'brown', + Excavator: 'brown', + // Exploration Lead + Pathfinder: 'blue', + 'Expedition Lead': 'blue', + 'Exploration Manager': 'blue', + // Explorer + Explorer: 'grey', + Surveyor: 'grey', + 'Offsite Scout': 'grey', + 'Explorer Medic': 'grey', + 'Explorer Technician': 'grey', + // Field Medic + 'Field Medic': 'grey', + 'Expedition Medic': 'grey', + 'Offsite Medic': 'grey', + // Pilot + Pilot: 'grey', + 'Co-Pilot': 'grey', + Navigator: 'grey', + Helmsman: 'grey', + // Barkeeper + Bartender: 'green', + Barkeeper: 'green', + Barmaid: 'green', + Barista: 'green', + Mixologist: 'green', + // Botanist + Botanist: 'green', + Hydroponicist: 'green', + Gardener: 'green', + Cultivator: 'green', + Farmer: 'green', + Florist: 'green', + Rancher: 'green', + // Chef + Chef: 'green', + 'Sous-chef': 'green', + Cook: 'green', + 'Kitchen Worker': 'green', + // Chaplain + Chaplain: 'green', + Counselor: 'green', + Preacher: 'green', + Missionary: 'green', + Priest: 'green', + Nun: 'green', + Monk: 'green', + Guru: 'green', + // Entertainer + Entertainer: 'green', + Performer: 'green', + Musician: 'green', + Stagehand: 'green', + Actor: 'green', + Dancer: 'green', + Singer: 'green', + Magician: 'green', + Comedian: 'green', + Tragedian: 'green', + Artist: 'green', + 'Game Master': 'green', + // Entrepreneur + Entrepreneur: 'green', + Lawyer: 'green', + 'Private Eye': 'green', + Bodyguard: 'green', + 'Personal Physician': 'green', + Dentist: 'green', + 'Fitness Instructor': 'green', + 'Yoga Teacher': 'green', + Masseuse: 'green', + Tradesperson: 'green', + Streamer: 'green', + Influencer: 'green', + 'Paranormal Investigator': 'green', + 'Personal Secretary': 'green', + Stylist: 'green', + Fisher: 'green', + 'Fortune Teller': 'green', + 'Spirit Healer': 'green', + // All of the interns + Intern: 'green', + 'Apprentice Engineer': 'green', + 'Research Intern': 'green', + 'Security Cadet': 'green', + 'Jr. Cargo Tech': 'green', + 'Jr. Explorer': 'green', + Assistant: 'green', + Server: 'green', + 'Technical Assistant': 'green', + 'Medical Intern': 'green', + 'Research Assistant"': 'green', + Visitor: 'green', + Resident: 'green', + // IAA + 'Internal Affairs Agent': 'blue', + 'Internal Affairs Liaison': 'blue', + 'Internal Affairs Delegate': 'blue', + 'Internal Affairs Investigator': 'blue', + // Janitor + Janitor: 'green', + Custodian: 'green', + 'Sanitation Technician': 'green', + Maid: 'green', + 'Garbage Collector': 'green', + // Librarian + Librarian: 'green', + Journalist: 'green', + Reporter: 'green', + Writer: 'green', + Historian: 'green', + Archivist: 'green', + Professor: 'green', + Academic: 'green', + Philosopher: 'green', + Curator: 'green', + // Off duty + 'Off-duty Officer': 'white', + 'Off-duty Engineer': 'white', + 'Off-duty Medic': 'white', + 'Off-duty Scientist': 'white', + 'Off-duty Cargo': 'white', + 'Off-duty Explorer': 'white', + 'Off-duty Worker': 'white', + // AI / Robot + AI: 'darkgrey', + Cyborg: 'darkgrey', + Robot: 'darkgrey', + Drone: 'darkgrey', + // Clown / Mime + Clown: 'green', + Jester: 'green', + Fool: 'green', + Mime: 'green', + Poseur: 'green', + // Special roles + 'Emergency Responder': 'yellow', + // Talon + 'Talon Captain': 'grey', + 'Talon Commander': 'grey', + 'Talon Doctor': 'grey', + 'Talon Medic': 'grey', + 'Talon Engineer': 'grey', + 'Talon Technician': 'grey', + 'Talon Guard': 'grey', + 'Talon Security': 'grey', + 'Talon Marine': 'grey', + 'Talon Pilot': 'grey', + 'Talon Helmsman': 'grey', + 'Talon Miner': 'grey', + 'Talon Excavator': 'grey', + // Robot Modules + Standard: 'grey', + Service: 'green', + Clerical: 'blue', + Research: 'purple', + Miner: 'brown', + Crisis: 'teal', + Security: 'red', + Combat: 'yellow', + Engineering: 'orange', + Gravekeeper: 'dark-grey', + Lost: 'grey', + Protector: 'darkred', + Mechanist: 'darkred', + 'Combat Medic': 'darkred', }; type rank_icon = { rank: string; color: string }; diff --git a/tgui/packages/tgui/package.json b/tgui/packages/tgui/package.json index efe74af8123..b933df189d7 100644 --- a/tgui/packages/tgui/package.json +++ b/tgui/packages/tgui/package.json @@ -8,7 +8,7 @@ "@types/react": "^18.2.74", "common": "workspace:*", "dateformat": "^4.6.3", - "dompurify": "^2.4.9", + "dompurify": "^2.5.4", "highlight.js": "^11.9.0", "jest": "^29.7.0", "js-yaml": "^4.1.0", diff --git a/tgui/yarn.lock b/tgui/yarn.lock index 3ec34d15025..c53812c4aec 100644 --- a/tgui/yarn.lock +++ b/tgui/yarn.lock @@ -3119,10 +3119,10 @@ __metadata: languageName: node linkType: hard -"dompurify@npm:^2.4.9": - version: 2.5.3 - resolution: "dompurify@npm:2.5.3" - checksum: 10c0/0ed513212cd7c51a14357cbf8135bbc58b13c8e7053cb0762ade33e60ba8ccd5ad5826734ee1d09cbd893fefcca2230b4c76f1f7742c863c691e64f51f75db54 +"dompurify@npm:^2.5.4": + version: 2.5.6 + resolution: "dompurify@npm:2.5.6" + checksum: 10c0/ee7e7d17982b1017a20982a2d57a0463d7fbb67f7b92a13ecf772e5e6acf0a529a19e3e31d725b05d5a2524d40e0aeb7ebc4be0aff396a6345bd6f2749fe560d languageName: node linkType: hard @@ -8255,7 +8255,7 @@ __metadata: "@types/node": "npm:^20.12.3" "@types/react": "npm:^18.2.74" common: "workspace:*" - dompurify: "npm:^2.4.9" + dompurify: "npm:^2.5.4" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" tgui: "workspace:*" @@ -8336,7 +8336,7 @@ __metadata: "@types/react": "npm:^18.2.74" common: "workspace:*" dateformat: "npm:^4.6.3" - dompurify: "npm:^2.4.9" + dompurify: "npm:^2.5.4" highlight.js: "npm:^11.9.0" jest: "npm:^29.7.0" js-yaml: "npm:^4.1.0" diff --git a/vorestation.dme b/vorestation.dme index 11313c0b1d0..9f8a730d4f0 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -1738,6 +1738,7 @@ #include "code\modules\admin\holder2.dm" #include "code\modules\admin\IsBanned.dm" #include "code\modules\admin\map_capture.dm" +#include "code\modules\admin\modify_robot.dm" #include "code\modules\admin\NewBan.dm" #include "code\modules\admin\news.dm" #include "code\modules\admin\persistence.dm" @@ -1814,7 +1815,6 @@ #include "code\modules\admin\verbs\lightning_strike.dm" #include "code\modules\admin\verbs\map_template_loadverb.dm" #include "code\modules\admin\verbs\mapping.dm" -#include "code\modules\admin\verbs\modify_robot.dm" #include "code\modules\admin\verbs\panicbunker.dm" #include "code\modules\admin\verbs\playsound.dm" #include "code\modules\admin\verbs\possess.dm"