diff --git a/code/modules/antagonists/malf_ai/malf_ai.dm b/code/modules/antagonists/malf_ai/malf_ai.dm index 763ab99c4ce..b46b5420023 100644 --- a/code/modules/antagonists/malf_ai/malf_ai.dm +++ b/code/modules/antagonists/malf_ai/malf_ai.dm @@ -18,8 +18,6 @@ var/give_objectives = TRUE ///bool for giving codewords var/should_give_codewords = TRUE - ///since the module purchasing is built into the antag info, we need to keep track of its compact mode here - var/module_picker_compactmode = FALSE ///malf on_gain sound effect. Set here so Infected AI can override var/malf_sound = 'sound/music/antag/malf.ogg' @@ -165,15 +163,12 @@ /datum/antagonist/malf_ai/ui_data(mob/living/silicon/ai/malf_ai) var/list/data = list() data["processingTime"] = malf_ai.malf_picker.processing_time - data["compactMode"] = module_picker_compactmode data["hackedAPCs"] = malf_ai.hacked_apcs.len return data /datum/antagonist/malf_ai/ui_static_data(mob/living/silicon/ai/malf_ai) var/list/data = list() - //antag panel data - data["has_codewords"] = should_give_codewords if(should_give_codewords) data["phrases"] = jointext(SStraitor.syndicate_code_phrase, ", ") @@ -183,53 +178,8 @@ data["goal"] = malfunction_flavor["goal"] data["objectives"] = get_objectives() data["can_change_objective"] = can_assign_self_objectives - - //module picker data - - data["categories"] = list() - if(malf_ai.malf_picker) - for(var/category in malf_ai.malf_picker.possible_modules) - var/list/cat = list( - "name" = category, - "items" = (category == malf_ai.malf_picker.selected_cat ? list() : null)) - for(var/module in malf_ai.malf_picker.possible_modules[category]) - var/datum/ai_module/malf/mod = malf_ai.malf_picker.possible_modules[category][module] - cat["items"] += list(list( - "name" = mod.name, - "cost" = mod.cost, - "desc" = mod.description, - "minimum_apcs" = mod.minimum_apcs, - )) - data["categories"] += list(cat) - return data -/datum/antagonist/malf_ai/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) - . = ..() - if(.) - return - if(!isAI(usr)) - return - var/mob/living/silicon/ai/malf_ai = usr - switch(action) - //module picker actions - if("buy") - var/item_name = params["name"] - var/list/buyable_items = list() - for(var/category in malf_ai.malf_picker.possible_modules) - buyable_items += malf_ai.malf_picker.possible_modules[category] - for(var/key in buyable_items) - var/datum/ai_module/malf/valid_mod = buyable_items[key] - if(valid_mod.name == item_name) - malf_ai.malf_picker.purchase_module(malf_ai, valid_mod) - return TRUE - if("select") - malf_ai.malf_picker.selected_cat = params["category"] - return TRUE - if("compact_toggle") - module_picker_compactmode = !module_picker_compactmode - return TRUE - /datum/antagonist/malf_ai/roundend_report() var/list/result = list() diff --git a/code/modules/antagonists/malf_ai/malf_ai_module_picker.dm b/code/modules/antagonists/malf_ai/malf_ai_module_picker.dm index e99dd0e83d5..f51d10b5b9c 100644 --- a/code/modules/antagonists/malf_ai/malf_ai_module_picker.dm +++ b/code/modules/antagonists/malf_ai/malf_ai_module_picker.dm @@ -1,32 +1,20 @@ /// The datum and interface for the malf unlock menu, which lets them choose actions to unlock. /datum/module_picker var/name = "Malfunction Modules Menu" - var/selected_cat - var/compact_mode = FALSE var/processing_time = 50 var/list/possible_modules /datum/module_picker/New() possible_modules = get_malf_modules() -/proc/cmp_malfmodules_priority(datum/ai_module/A, datum/ai_module/B) - return B.cost - A.cost - /proc/get_malf_modules() - var/list/filtered_modules = list() - + var/list/modules = list() for(var/path in GLOB.malf_modules) var/datum/ai_module/AM = new path if((AM.power_type == /datum/action/innate/ai) && !AM.upgrade) continue - if(!filtered_modules[AM.category]) - filtered_modules[AM.category] = list() - filtered_modules[AM.category][AM] = AM - - for(var/category in filtered_modules) - sortTim(filtered_modules[category], GLOBAL_PROC_REF(cmp_malfmodules_priority)) - - return filtered_modules + modules += AM + return modules /datum/module_picker/ui_state(mob/user) return GLOB.always_state @@ -40,7 +28,6 @@ /datum/module_picker/ui_data(mob/user) var/list/data = list() data["processingTime"] = processing_time - data["compactMode"] = compact_mode if(isAI(user)) var/mob/living/silicon/ai/ai_user = user data["hackedAPCs"] = ai_user.hacked_apcs.len @@ -48,21 +35,28 @@ /datum/module_picker/ui_static_data(mob/user) var/list/data = list() - data["categories"] = list() - for(var/category in possible_modules) - var/list/cat = list( - "name" = category, - "items" = (category == selected_cat ? list() : null)) - for(var/module in possible_modules[category]) - var/datum/ai_module/AM = possible_modules[category][module] - cat["items"] += list(list( - "name" = AM.name, - "cost" = AM.cost, - "desc" = AM.description, - "minimum_apcs" = AM.minimum_apcs, - )) - data["categories"] += list(cat) + data["modules"] = list() + + for(var/datum/ai_module/module as anything in possible_modules) + var/icon_state = module.icon_state + var/icon = module.icon + if (!module.icon_state && !module.upgrade && module.power_type) + var/datum/action/innate/ai/active_ability = module.power_type + icon = active_ability.button_icon + icon_state = active_ability.button_icon_state + + data["modules"] += list(list( + "name" = module.name, + "icon" = icon, + "icon_state" = icon_state, + "cost" = module.cost, + "desc" = module.description, + "category" = module.category, + "minimumApcs" = module.minimum_apcs, + )) + if (!(module.category in data["categories"])) + data["categories"] += module.category return data @@ -75,20 +69,10 @@ switch(action) if("buy") var/item_name = params["name"] - var/list/buyable_items = list() - for(var/category in possible_modules) - buyable_items += possible_modules[category] - for(var/key in buyable_items) - var/datum/ai_module/AM = buyable_items[key] - if(AM.name == item_name) - purchase_module(usr, AM) + for(var/datum/ai_module/module as anything in possible_modules) + if(module.name == item_name) + purchase_module(usr, module) return TRUE - if("select") - selected_cat = params["category"] - return TRUE - if("compact_toggle") - compact_mode = !compact_mode - return TRUE /datum/module_picker/proc/purchase_module(mob/living/silicon/ai/AI, datum/ai_module/AM) if(!istype(AM)) @@ -99,11 +83,10 @@ return if(AM.minimum_apcs > AI.hacked_apcs.len) return - var/datum/action/innate/ai/action = locate(AM.power_type) in AI.actions // Give the power and take away the money. if(AM.upgrade) //upgrade and upgrade() are separate, be careful! AM.upgrade(AI) - possible_modules[AM.category] -= AM + possible_modules -= AM if(AM.unlock_text) to_chat(AI, AM.unlock_text) if(AM.unlock_sound) @@ -111,12 +94,13 @@ update_static_data(AI) else if(AM.power_type) + var/datum/action/innate/ai/action = locate(AM.power_type) in AI.actions if(!action) //Unlocking for the first time var/datum/action/AC = new AM.power_type AC.Grant(AI) AI.current_modules += new AM.type if(AM.one_purchase) - possible_modules[AM.category] -= AM + possible_modules -= AM update_static_data(AI) if(AM.unlock_text) to_chat(AI, AM.unlock_text) diff --git a/code/modules/antagonists/malf_ai/malf_ai_modules.dm b/code/modules/antagonists/malf_ai/malf_ai_modules.dm index 5f810a1e37c..9c2d376de9c 100644 --- a/code/modules/antagonists/malf_ai/malf_ai_modules.dm +++ b/code/modules/antagonists/malf_ai/malf_ai_modules.dm @@ -122,6 +122,9 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module/malf)) var/name = "generic module" var/category = "generic category" var/description = "generic description" + /// Icons for the module picker interface. If icon_state is not set, the interface will attempt to use the active ability's icon. + var/icon = 'icons/mob/actions/actions_AI.dmi' + var/icon_state = "" var/cost = 5 /// Minimum amount of APCs that has to be under the AI's control to purchase this module. var/minimum_apcs = 0 @@ -846,6 +849,8 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module/malf)) /datum/ai_module/malf/upgrade/upgrade_cameras name = "Upgrade Camera Network" description = "Install broad-spectrum scanning and electrical redundancy firmware to the camera network, enabling EMP-proofing and light-amplified X-ray vision. Upgrade is done immediately upon purchase." //I <3 pointless technobabble + icon = 'icons/obj/machines/camera.dmi' + icon_state = "xraycamera" //This used to have motion sensing as well, but testing quickly revealed that giving it to the whole cameranet is PURE HORROR. cost = 35 //Decent price for omniscience! upgrade = TRUE @@ -883,6 +888,8 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module/malf)) /datum/ai_module/malf/upgrade/upgrade_turrets name = "AI Turret Upgrade" description = "Improves the power and health of all AI turrets. This effect is permanent. Upgrade is done immediately upon purchase." + icon = 'icons/obj/weapons/turrets.dmi' + icon_state = "standard_off" cost = 30 upgrade = TRUE unlock_text = span_notice("You establish a power diversion to your turrets, upgrading their health and damage.") @@ -901,6 +908,8 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module/malf)) name = "Enhanced Surveillance" description = "Via a combination of hidden microphones and lip reading software, \ you are able to use your cameras to listen in on conversations. Upgrade is done immediately upon purchase." + icon = 'icons/obj/service/broadcast.dmi' + icon_state = "microphone" cost = 30 upgrade = TRUE unlock_text = span_notice("OTA firmware distribution complete! Cameras upgraded: Enhanced surveillance package online.") @@ -916,6 +925,8 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/ai_module/malf)) description = "Allows you to hack into a mech's onboard computer, shunting all processes into it and ejecting any occupants. \ Upgrade is done immediately upon purchase. Do not allow the mech to leave the station's vicinity or allow it to be destroyed. \ If your core is destroyed, you will be lose connection with the Doomsday Device and the countdown will cease." + icon = 'icons/mob/rideables/mecha.dmi' + icon_state = "durand" cost = 30 upgrade = TRUE unlock_text = span_notice("Virus package compiled. Select a target mech at any time. You must remain on the station at all times. \ diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 0849b20eed9..f8309987078 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -895,9 +895,8 @@ to_chat(src, "You are also capable of hacking APCs, which grants you more points to spend on your Malfunction powers. The drawback is that a hacked APC will give you away if spotted by the crew. Hacking an APC takes 60 seconds.") view_core() //A BYOND bug requires you to be viewing your core before your verbs update malf_picker = new /datum/module_picker - if(!IS_MALF_AI(src)) //antagonists have their modules built into their antag info panel. this is for adminbus and the combat upgrade - modules_action = new(malf_picker) - modules_action.Grant(src) + modules_action = new(malf_picker) + modules_action.Grant(src) /mob/living/silicon/ai/reset_perspective(atom/new_eye) SHOULD_CALL_PARENT(FALSE) // I hate you all diff --git a/tgui/packages/tgui/interfaces/AntagInfoMalf.tsx b/tgui/packages/tgui/interfaces/AntagInfoMalf.tsx index 0d589f041c6..4cb55e74320 100644 --- a/tgui/packages/tgui/interfaces/AntagInfoMalf.tsx +++ b/tgui/packages/tgui/interfaces/AntagInfoMalf.tsx @@ -1,10 +1,9 @@ -import { useState } from 'react'; -import { BlockQuote, Button, Section, Stack, Tabs } from 'tgui-core/components'; +import { BlockQuote, Button, Section, Stack } from 'tgui-core/components'; import type { BooleanLike } from 'tgui-core/react'; import { useBackend } from '../backend'; import { Window } from '../layouts'; -import { MalfAiModules } from './common/MalfAiModules'; + import { type Objective, ObjectivePrintout, @@ -178,63 +177,29 @@ function CodewordsSection(props) { ); } -enum Screen { - Intro, - Modules, -} export function AntagInfoMalf(props) { - const [antagInfoTab, setAntagInfoTab] = useState(Screen.Intro); - return ( - - - setAntagInfoTab(Screen.Intro)} - > - Information - - setAntagInfoTab(Screen.Modules)} - > - Malfunction Modules - - + + + + + + + + + + + + - {antagInfoTab === Screen.Intro ? ( - <> - - - - - - - - - - - - - - - ) : ( - -
- -
-
- )}
diff --git a/tgui/packages/tgui/interfaces/MalfunctionModulePicker.tsx b/tgui/packages/tgui/interfaces/MalfunctionModulePicker.tsx index 199bf699047..dcf818802a7 100644 --- a/tgui/packages/tgui/interfaces/MalfunctionModulePicker.tsx +++ b/tgui/packages/tgui/interfaces/MalfunctionModulePicker.tsx @@ -1,11 +1,54 @@ import { Window } from '../layouts'; -import { MalfAiModules } from './common/MalfAiModules'; +import { useBackend } from '../backend'; +import { GenericUplink, type Item } from './Uplink/GenericUplink'; + +type MalfModuleData = { + name: string, + icon: string, + icon_state: string, + cost: number, + desc: string, + category: string, + minimumApcs: number, +}; + +type Data = { + processingTime: number; + hackedAPCs: number; + categories: string[]; + modules: MalfModuleData[]; +}; export function MalfunctionModulePicker(props) { + const { act, data } = useBackend(); + const { processingTime, hackedAPCs, categories } = data; + + const items: Item[] = data.modules.map((module) => ({ + category: module.category, + cost: `${module.cost} PT`, + desc: + module.desc + + (module.minimumApcs > 0 + ? ` Requires at least ${module.minimumApcs} APCs hacked.` + : ''), + disabled: processingTime < module.cost || hackedAPCs < module.minimumApcs, + id: module.name, + name: module.name, + icon: module.icon, + icon_state: module.icon_state, + population_tooltip: '', + insufficient_population: false, + })) + return ( - + - + act('buy', { name: item.name })} + /> ); diff --git a/tgui/packages/tgui/interfaces/common/MalfAiModules.tsx b/tgui/packages/tgui/interfaces/common/MalfAiModules.tsx deleted file mode 100644 index c6fc61c7e7c..00000000000 --- a/tgui/packages/tgui/interfaces/common/MalfAiModules.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { useBackend } from '../../backend'; -import { GenericUplink, type Item } from '../Uplink/GenericUplink'; - -type Category = { - name: string; - items: MalfItem[]; -}; -/* This is shitcode, but someone used normal uplink so i feel justified */ -type MalfItem = Item & { - minimum_apcs: number; -}; - -type Data = { - processingTime: string; - hackedAPCs: number; - categories: Category[]; -}; - -/** Common ui for selecting malf ai modules */ -export function MalfAiModules(props) { - const { act, data } = useBackend(); - const { processingTime, hackedAPCs, categories = [] } = data; - - const categoriesList: string[] = []; - const items: MalfItem[] = []; - - for (let idx = 0; idx < categories.length; idx++) { - const category = categories[idx]; - categoriesList.push(category.name); - - for (let itemIndex = 0; itemIndex < category.items?.length; itemIndex++) { - const item = category.items[itemIndex]; - items.push({ - category: category.name, - cost: `${item.cost} PT`, - desc: - item.desc + - (item.minimum_apcs > 0 - ? ` Requires at least ${item.minimum_apcs} APCs hacked.` - : ''), - disabled: processingTime < item.cost || hackedAPCs < item.minimum_apcs, - icon_state: item.icon_state, - icon: item.icon, - id: item.name, - name: item.name, - population_tooltip: '', - insufficient_population: false, - minimum_apcs: item.minimum_apcs || 0, // Handle the case where minimum_apcs is not defined - }); - } - } - - return ( - act('buy', { name: item.name })} - /> - ); -}