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/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/tgui/packages/tgui/interfaces/LawManager.tsx b/tgui/packages/tgui/interfaces/LawManager.tsx index 3632166b60f..305e6a81a4e 100644 --- a/tgui/packages/tgui/interfaces/LawManager.tsx +++ b/tgui/packages/tgui/interfaces/LawManager.tsx @@ -1,4 +1,7 @@ +import { filter } from 'common/collections'; +import { flow } from 'common/fp'; import { BooleanLike } from 'common/react'; +import { createSearch } from 'common/string'; import { useBackend, useSharedState } from '../backend'; import { @@ -76,30 +79,12 @@ export const LawManager = (props) => { }; const LawManagerContent = (props) => { + const { data } = useBackend(); const [tabIndex, setTabIndex] = useSharedState('lawsTabIndex', 0); - - const tab: React.JSX.Element[] = []; - - tab[0] = ; - tab[1] = ; - - return ( - <> - - setTabIndex(0)}> - Law Management - - setTabIndex(1)}> - Law Sets - - - {tab[tabIndex]} - + const [searchLawName, setSearchLawName] = useSharedState( + 'searchLawName', + '', ); -}; - -const LawManagerLaws = (props) => { - const { act, data } = useBackend(); const { ion_law_nr, @@ -121,9 +106,109 @@ const LawManagerLaws = (props) => { isAdmin, channel, channels, + law_sets, } = data; - let allLaws = zeroth_laws + const tab: React.JSX.Element[] = []; + + tab[0] = ( + + ); + tab[1] = ( + + ); + + return ( + <> + + setTabIndex(0)}> + Law Management + + setTabIndex(1)}> + Law Sets + + + {tab[tabIndex]} + + ); +}; + +export const LawManagerLaws = (props: { + 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; + isMalf: BooleanLike; + isAdmin: BooleanLike; + channel: string; + channels: { channel: string }[]; + hasScroll?: boolean; + sectionHeight?: string; +}) => { + const { act } = useBackend(); + const { + ion_law_nr, + ion_law, + zeroth_law, + inherent_law, + supplied_law, + supplied_law_position, + zeroth_laws, + has_zeroth_laws, + ion_laws, + has_ion_laws, + inherent_laws, + has_inherent_laws, + supplied_laws, + has_supplied_laws, + isAI, + isMalf, + isAdmin, + channel, + channels, + hasScroll, + sectionHeight, + } = props; + + const allLaws = zeroth_laws .map((law) => { law.zero = true; return law; @@ -131,19 +216,37 @@ const LawManagerLaws = (props) => { .concat(inherent_laws); return ( -
+
{has_ion_laws ? ( - + ) : ( '' )} {has_zeroth_laws || has_inherent_laws ? ( - + ) : ( '' )} {has_supplied_laws ? ( - + ) : ( '' )} @@ -280,15 +383,16 @@ const LawManagerLaws = (props) => { }; const LawsTable = (props: { + laws: law[]; title: string; noButtons?: BooleanLike; [rest: string]: any; + isMalf: BooleanLike; + isAdmin: BooleanLike; }) => { - const { act, data } = useBackend(); + const { act } = useBackend(); - const { isMalf, isAdmin } = data; - - const { laws, title, noButtons, ...rest } = props; + const { laws, title, noButtons, isMalf, isAdmin, ...rest } = props; return (
@@ -360,10 +464,24 @@ const LawsTable = (props: { ); }; -const LawManagerLawSets = (props) => { - const { act, data } = useBackend(); +export const LawManagerLawSets = (props: { + law_sets: law_pack[]; + ion_law_nr: string; + searchLawName: string; + onSearchLawName: Function; + isAdmin: BooleanLike; + isMalf: BooleanLike; +}) => { + const { act } = useBackend(); - const { isMalf, law_sets, ion_law_nr } = data; + const { + law_sets, + ion_law_nr, + searchLawName, + onSearchLawName, + isMalf, + isAdmin, + } = props; return ( <> @@ -371,8 +489,14 @@ const LawManagerLawSets = (props) => { Remember: Stating laws other than those currently loaded may be grounds for decommissioning! - NanoTrasen + onSearchLawName(value)} + /> {law_sets.length - ? law_sets.map((laws) => ( + ? prepareSearch(law_sets, searchLawName).map((laws) => (
{ noButtons laws={laws.laws.ion_laws} title={ion_law_nr + ' Laws:'} + isAdmin={isAdmin} + isMalf={isMalf} /> ) : ( '' @@ -412,6 +538,8 @@ const LawManagerLawSets = (props) => { noButtons laws={laws.laws.zeroth_laws.concat(laws.laws.inherent_laws)} title={laws.header} + isAdmin={isAdmin} + isMalf={isMalf} /> ) : ( '' @@ -421,6 +549,8 @@ const LawManagerLawSets = (props) => { noButtons laws={laws.laws.supplied_laws} title="Supplied Laws" + isAdmin={isAdmin} + isMalf={isMalf} /> ) : ( '' @@ -431,3 +561,23 @@ const LawManagerLawSets = (props) => { ); }; + +const prepareSearch = ( + laws: law_pack[], + searchText: string = '', +): law_pack[] => { + const testSearch = createSearch( + searchText, + (law: law_pack) => law.name + law.header, + ); + return flow([ + (laws: law_pack[]) => { + // Optional search term + if (!searchText) { + return laws; + } else { + return filter(laws, testSearch); + } + }, + ])(laws); +}; diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotNoModule.tsx b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotNoModule.tsx new file mode 100644 index 00000000000..364bd2ff2d6 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotNoModule.tsx @@ -0,0 +1,109 @@ +import { useBackend } from 'tgui/backend'; +import { + Button, + Divider, + Flex, + Icon, + NoticeBox, + Section, + Stack, +} from 'tgui/components'; + +import { RankIcon } from '../common/RankIcon'; +import { Target } from './types'; + +export const ModifyRobotNoModule = (props: { target: Target }) => { + const { target } = props; + const { act } = useBackend(); + + return ( + <> + + Target has no active module. Limited options available. + + + + + + + +
+ + + {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/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/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"