diff --git a/code/_helpers/text.dm b/code/_helpers/text.dm
index 520b98d39ad..3357116d308 100644
--- a/code/_helpers/text.dm
+++ b/code/_helpers/text.dm
@@ -620,3 +620,8 @@
return json_decode(data)
catch
return null
+
+/// Removes all non-alphanumerics from the text, keep in mind this can lead to id conflicts
+/proc/sanitize_css_class_name(name)
+ var/static/regex/regex = new(@"[^a-zA-Z0-9]","g")
+ return replacetext(name, regex, "")
diff --git a/code/game/objects/items/antag_spawners.dm b/code/game/objects/items/antag_spawners.dm
index 8371f5b0175..28817531895 100644
--- a/code/game/objects/items/antag_spawners.dm
+++ b/code/game/objects/items/antag_spawners.dm
@@ -118,7 +118,6 @@
to speak with your team, and learn what their plan is for today."))
R.key = C.key
-// R.Namepick() // Apparnetly making someone a merc lets them pick a name, so this isn't needed.
spawn(1)
mercs.add_antagonist(R.mind, FALSE, TRUE, FALSE, FALSE, FALSE)
diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm
index 32af47b31af..2667b9d3f66 100644
--- a/code/game/objects/items/robot/robot_parts.dm
+++ b/code/game/objects/items/robot/robot_parts.dm
@@ -225,7 +225,6 @@
feedback_inc("cyborg_birth",1)
callHook("borgify", list(O))
- O.namepick()
qdel(src)
else
diff --git a/code/game/objects/structures/ghost_pods/silicon.dm b/code/game/objects/structures/ghost_pods/silicon.dm
index 64ac8f5674b..4f50784aed2 100644
--- a/code/game/objects/structures/ghost_pods/silicon.dm
+++ b/code/game/objects/structures/ghost_pods/silicon.dm
@@ -31,7 +31,6 @@
from the pod is not a crewmember."))
R.ckey = M.ckey
visible_message(span_warning("As \the [src] opens, the eyes of the robot flicker as it is activated."))
- R.namepick()
log_and_message_admins("successfully opened \a [src] and got a Lost Drone.")
..()
@@ -57,7 +56,6 @@
definiton of 'your gravesite' is where your pod is."))
R.ckey = M.ckey
visible_message(span_warning("As \the [src] opens, the eyes of the robot flicker as it is activated."))
- R.namepick()
..()
/obj/structure/ghost_pod/ghost_activated/swarm_drone
diff --git a/code/game/objects/structures/ghost_pods/silicon_vr.dm b/code/game/objects/structures/ghost_pods/silicon_vr.dm
index cfc3b781d2f..1039349f2e1 100644
--- a/code/game/objects/structures/ghost_pods/silicon_vr.dm
+++ b/code/game/objects/structures/ghost_pods/silicon_vr.dm
@@ -21,7 +21,6 @@
from the pod is not a crewmember."))
R.ckey = M.ckey
visible_message(span_warning("As \the [src] opens, the eyes of the robot flicker as it is activated."))
- R.namepick()
log_and_message_admins("successfully opened \a [src] and got a Lost Drone.")
used = TRUE
return TRUE
diff --git a/code/modules/admin/modify_robot.dm b/code/modules/admin/modify_robot.dm
index f2847943f35..64f486824d7 100644
--- a/code/modules/admin/modify_robot.dm
+++ b/code/modules/admin/modify_robot.dm
@@ -43,9 +43,16 @@
qdel(source)
. = ..()
+/datum/eventkit/modify_robot/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/robot_icons)
+ )
+
/datum/eventkit/modify_robot/tgui_data(mob/user)
. = list()
// Target section for general data
+ var/datum/asset/spritesheet/robot_icons/spritesheet = get_asset_datum(/datum/asset/spritesheet/robot_icons)
+
if(target)
.["target"] = list()
.["target"]["name"] = target.name
@@ -62,10 +69,8 @@
// 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"]["sprite"] = sanitize_css_class_name("[target.sprite_datum.type]")
+ .["target"]["sprite_size"] = spritesheet.icon_size_id(.["target"]["sprite"] + "S")
.["target"]["modules"] = get_target_items(user)
var/list/module_options = list()
for(var/module in robot_modules)
@@ -110,7 +115,7 @@
.["access_options"] = access_options
// Section for source data for the module we might want to salvage
if(source)
- .["source"] += get_module_source(user)
+ .["source"] += get_module_source(user, spritesheet)
var/list/all_robots = list()
for(var/mob/living/silicon/robot/R in silicon_mob_list)
if(!R.loc)
@@ -573,10 +578,11 @@
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)
+/datum/eventkit/modify_robot/proc/get_module_source(var/mob/user, var/datum/asset/spritesheet/robot_icons/spritesheet)
var/list/source_list = list()
source_list["model"] = source.module
- source_list["front"] = icon2base64(get_flat_icon(source,dir=SOUTH,no_anim=TRUE))
+ source_list["sprite"] = sanitize_css_class_name("[source.sprite_datum.type]")
+ source_list["sprite_size"] = spritesheet.icon_size_id(source_list["sprite"] + "S")
var/list/source_items = list()
for(var/obj/item in (source.module.modules | source.module.emag))
var/exists
diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm
index 572c1a5d26e..564b0879488 100644
--- a/code/modules/asset_cache/asset_list_items.dm
+++ b/code/modules/asset_cache/asset_list_items.dm
@@ -437,6 +437,45 @@
Insert("bottle-[i]", 'icons/obj/chemical.dmi', "bottle-[i]")
+// Robot UI sprites
+/datum/asset/spritesheet/robot_icons
+ name = "robot_icons"
+
+/datum/asset/spritesheet/robot_icons/create_spritesheets()
+ for(var/datum/robot_sprite/S as anything in typesof(/datum/robot_sprite))
+ if(!S.name || !S.sprite_icon_state) // snowflake out those customs... they suck
+ continue
+ var/icon/I_N = icon(S.sprite_icon, S.sprite_icon_state, NORTH)
+ var/icon/I_S = icon(S.sprite_icon, S.sprite_icon_state, SOUTH)
+ var/icon/I_W = icon(S.sprite_icon, S.sprite_icon_state, WEST)
+ var/icon/I_E = icon(S.sprite_icon, S.sprite_icon_state, EAST)
+ if(S.has_eye_sprites)
+ var/icon/I_NE = icon(S.sprite_icon, "[S.sprite_icon_state]-eyes", NORTH)
+ if(I_NE)
+ I_N.Blend(I_NE, ICON_OVERLAY)
+ if(S.has_eye_sprites)
+ var/icon/I_SE = icon(S.sprite_icon, "[S.sprite_icon_state]-eyes", SOUTH)
+ if(I_SE)
+ I_S.Blend(I_SE, ICON_OVERLAY)
+ if(S.has_eye_sprites)
+ var/icon/I_WE = icon(S.sprite_icon, "[S.sprite_icon_state]-eyes", WEST)
+ if(I_WE)
+ I_W.Blend(I_WE, ICON_OVERLAY)
+ if(S.has_eye_sprites)
+ var/icon/I_EE = icon(S.sprite_icon, "[S.sprite_icon_state]-eyes", EAST)
+ if(I_EE)
+ I_E.Blend(I_EE, ICON_OVERLAY)
+ var/imgid = sanitize_css_class_name("[S.type]")
+ I_N.Scale(120, 120)
+ I_S.Scale(120, 120)
+ I_W.Scale(120, 120)
+ I_E.Scale(120, 120)
+ Insert(imgid + "N", I_N)
+ Insert(imgid + "S", I_S)
+ Insert(imgid + "W", I_W)
+ Insert(imgid + "E", I_E)
+
+
//Cloning pod sprites for UIs
/datum/asset/simple/cloning
assets = list(
diff --git a/code/modules/mob/living/silicon/robot/drone/drone.dm b/code/modules/mob/living/silicon/robot/drone/drone.dm
index fd1132d05a8..c7c1a1a2fbc 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone.dm
@@ -203,9 +203,6 @@ var/list/mob_hat_cache = list()
can_pick_shell = FALSE
update_icon()
-/mob/living/silicon/robot/drone/choose_icon()
- return
-
/mob/living/silicon/robot/drone/pick_module()
return
diff --git a/code/modules/mob/living/silicon/robot/login.dm b/code/modules/mob/living/silicon/robot/login.dm
index 89a136223e2..ef25b1bece0 100644
--- a/code/modules/mob/living/silicon/robot/login.dm
+++ b/code/modules/mob/living/silicon/robot/login.dm
@@ -11,11 +11,6 @@
repick_laws()
// Forces synths to select an icon relevant to their module
- if(!icon_selected)
- icon_selection_tries = SSrobot_sprites.get_module_sprites_len(modtype, src) + 1
- choose_icon(icon_selection_tries)
-
- if(sprite_datum && module)
- sprite_datum.do_equipment_glamour(module)
+ pick_module()
plane_holder.set_vis(VIS_AUGMENTED, TRUE)
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index a79310c02be..f3161e0e090 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -31,8 +31,7 @@
//Icon stuff
var/datum/robot_sprite/sprite_datum // Sprite datum, holding all our sprite data
- var/icon_selected = 1 // If icon selection has been completed yet
- var/icon_selection_tries = 0 // Remaining attempts to select icon before a selection is forced
+ var/icon_selected = FALSE // If icon selection has been completed yet
var/list/sprite_extra_customization = list()
var/rest_style = "Default"
var/notransform
@@ -47,6 +46,9 @@
var/shown_robot_modules = 0 //Used to determine whether they have the module menu shown or not
var/obj/screen/robot_modules_background
+ var/ui_theme
+ var/selecting_module = FALSE
+
//3 Modules can be activated at any one time.
var/obj/item/robot_module/module = null
var/module_active = null
@@ -336,46 +338,17 @@
return module_sprites
*/
/mob/living/silicon/robot/proc/pick_module()
+ if(icon_selected)
+ return
if(module)
- return
- var/list/modules = list()
- //VOREStatation Edit Start: shell restrictions
- if(shell)
- if(restrict_modules_to.len > 0)
- modules.Add(restrict_modules_to)
- else
- modules.Add(shell_module_types)
- else
- if(restrict_modules_to.len > 0)
- modules.Add(restrict_modules_to)
- else
- modules.Add(robot_module_types)
- if(crisis || security_level == SEC_LEVEL_RED || crisis_override)
- to_chat(src, span_red("Crisis mode active. Combat module available."))
- modules |= emergency_module_types
- for(var/module_name in whitelisted_module_types)
- if(is_borg_whitelisted(src, module_name))
- modules |= module_name
- //VOREStatation Edit End: shell restrictions
- modtype = tgui_input_list(usr, "Please, select a module!", "Robot module", modules)
-
- if(module)
- return
- if(!(modtype in robot_modules))
- return
- if(!is_borg_whitelisted(src, modtype))
- return
-
- var/module_type = robot_modules[modtype]
- transform_with_anim() //VOREStation edit: sprite animation
- new module_type(src)
-
- hands.icon_state = get_hud_module_icon()
- feedback_inc("cyborg_[lowertext(modtype)]",1)
- updatename()
- hud_used.update_robot_modules_display()
- notify_ai(ROBOT_NOTIFICATION_NEW_MODULE, module.name)
- robotact?.update_static_data_for_all_viewers()
+ var/list/module_sprites = SSrobot_sprites.get_module_sprites(module, src)
+ if(module_sprites.len == 1 || !client)
+ sprite_datum = module_sprites[1]
+ sprite_datum.do_equipment_glamour(module)
+ return
+ if(!selecting_module)
+ var/datum/tgui_module/robot_ui_module/ui = new(src)
+ ui.tgui_interact(src)
/mob/living/silicon/robot/proc/update_braintype()
if(istype(mmi, /obj/item/mmi/digital/posibrain))
@@ -436,15 +409,12 @@
to_chat(usr, "You can't pick another custom name. [isshell(src) ? "" : "Go ask for a name change."]")
return 0
- spawn(0)
- var/newname
- newname = sanitizeSafe(tgui_input_text(src,"You are a robot. Enter a name, or leave blank for the default name.", "Name change","", MAX_NAME_LEN), MAX_NAME_LEN)
- if (newname)
- custom_name = newname
- sprite_name = newname
+ var/newname = sanitizeSafe(tgui_input_text(src,"You are a robot. Enter a name, or leave blank for the default name.", "Name change","", MAX_NAME_LEN), MAX_NAME_LEN)
+ if (newname)
+ custom_name = newname
+ sprite_name = newname
- updatename()
- update_icon()
+ updatename()
/mob/living/silicon/robot/verb/extra_customization()
set name = "Customize Appearance"
@@ -796,6 +766,7 @@
notify_ai(ROBOT_NOTIFICATION_MODULE_RESET, module.name)
module.Reset(src)
qdel(module)
+ icon_selected = FALSE
module = null
updatename("Default")
has_recoloured = FALSE
@@ -1088,60 +1059,6 @@
return
-/mob/living/silicon/robot/proc/choose_icon(var/triesleft)
- var/robot_species = null
- if(!SSrobot_sprites)
- to_chat(src, "Robot Sprites have not been initialized yet. How are you choosing a sprite? Harass a coder.")
- return
-
- var/list/module_sprites = SSrobot_sprites.get_module_sprites(modtype, src)
- if(!module_sprites || !module_sprites.len)
- to_chat(src, "Your module appears to have no sprite options. Harass a coder.")
- return
-
- icon_selected = 0
- icon_selection_tries = triesleft
- if(module_sprites.len == 1 || !client)
- if(!(sprite_datum in module_sprites))
- sprite_datum = module_sprites[1]
- else
- var/selection = tgui_input_list(src, "Select an icon! [triesleft ? "You have [triesleft] more chance\s." : "This is your last try."]", "Robot Icon", module_sprites)
- if(selection)
- sprite_datum = selection
- else
- sprite_datum = module_sprites[1]
- if(!istype(src,/mob/living/silicon/robot/drone))
- robot_species = sprite_datum.name
- if(notransform)
- to_chat(src, "Your current transformation has not finished yet!")
- choose_icon(icon_selection_tries)
- return
- else
- transform_with_anim()
-
- var/tempheight = vis_height
- update_icon()
- // This is bad but I dunno other way to 'reset' our resize offset based on vis_height changes other than resizing to normal and back.
- if(tempheight != vis_height)
- var/tempsize = size_multiplier
- resize(1)
- resize(tempsize)
-
-
- if (module_sprites.len > 1 && triesleft >= 1 && client)
- icon_selection_tries--
- var/choice = tgui_alert(usr, "Look at your icon - is this what you want?", "Icon Choice", list("Yes","No"))
- if(choice == "No")
- choose_icon(icon_selection_tries)
- return
-
- icon_selected = 1
- icon_selection_tries = 0
- sprite_type = robot_species
- if(hands)
- update_hud()
- to_chat(src, span_filter_notice("Your icon has been set. You now require a module reset to change it."))
-
/mob/living/silicon/robot/proc/set_default_module_icon()
if(!SSrobot_sprites)
return
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/station.dm b/code/modules/mob/living/silicon/robot/robot_modules/station.dm
index 8f8c0757e0c..bc54a72e638 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules/station.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules/station.dm
@@ -73,7 +73,7 @@ var/global/list/robot_modules = list(
R.radio.recalculateChannels()
R.set_default_module_icon()
- R.choose_icon(SSrobot_sprites.get_module_sprites_len(R.modtype, R) + 1)
+ R.pick_module()
if(!R.client)
R.icon_selected = FALSE // It wasnt a player selecting icon? Let them do it later!
@@ -93,7 +93,7 @@ var/global/list/robot_modules = list(
if(R.radio)
R.radio.recalculateChannels()
- R.choose_icon(0)
+ R.set_default_module_icon()
R.scrubbing = FALSE
diff --git a/code/modules/mob/living/silicon/robot/robot_ui.dm b/code/modules/mob/living/silicon/robot/robot_ui.dm
index 7262f45364a..11027cc20da 100644
--- a/code/modules/mob/living/silicon/robot/robot_ui.dm
+++ b/code/modules/mob/living/silicon/robot/robot_ui.dm
@@ -52,6 +52,10 @@
var/mob/living/silicon/robot/R = host
data["module_name"] = R.module ? "[R.module]" : null
+ if(R.emagged)
+ data["theme"] = "syndicate"
+ else if (R.ui_theme)
+ data["theme"] = R.ui_theme
if(!R.module)
return data
diff --git a/code/modules/mob/living/silicon/robot/robot_ui_module.dm b/code/modules/mob/living/silicon/robot/robot_ui_module.dm
new file mode 100644
index 00000000000..25b199b47c0
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot_ui_module.dm
@@ -0,0 +1,169 @@
+// Robot module selection
+/datum/tgui_module/robot_ui_module
+ name = "Robot Module Configurator"
+ tgui_id = "RobotChoose"
+ var/selected_module
+ var/newName
+ var/datum/robot_sprite/sprite_datum
+
+/datum/tgui_module/robot_ui_module/tgui_state(mob/user)
+ return GLOB.tgui_self_state
+
+/datum/tgui_module/robot_ui_module/tgui_close(mob/user)
+ . = ..()
+ if(isrobot(user))
+ var/mob/living/silicon/robot/R = user
+ R.selecting_module = FALSE
+
+/datum/tgui_module/robot_ui_module/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui)
+ . = ..()
+ if(isrobot(user) && ui)
+ var/mob/living/silicon/robot/R = user
+ R.selecting_module = TRUE
+
+/datum/tgui_module/robot_ui_module/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/robot_icons)
+ )
+
+/datum/tgui_module/robot_ui_module/tgui_static_data()
+ var/list/data = ..()
+
+ var/mob/living/silicon/robot/R = host
+
+ var/list/modules = list()
+ if(R.module)
+ modules = list(R.modtype)
+ selected_module = R.modtype
+ else
+ if(LAZYLEN(R.restrict_modules_to) > 0)
+ modules.Add(R.restrict_modules_to)
+ else if(R.shell)
+ modules.Add(shell_module_types)
+ else
+ modules.Add(robot_module_types)
+ if(R.crisis || security_level == SEC_LEVEL_RED || R.crisis_override)
+ to_chat(R, span_red("Crisis mode active. Combat module available."))
+ modules |= emergency_module_types
+ for(var/module_name in whitelisted_module_types)
+ if(is_borg_whitelisted(R, module_name))
+ modules |= module_name
+ data["possible_modules"] = modules
+ if(R.emagged)
+ data["theme"] = "syndicate"
+ else if (R.ui_theme)
+ data["theme"] = R.ui_theme
+
+ return data
+
+/datum/tgui_module/robot_ui_module/tgui_data()
+ var/list/data = ..()
+
+ var/mob/living/silicon/robot/R = host
+ var/datum/asset/spritesheet/robot_icons/spritesheet = get_asset_datum(/datum/asset/spritesheet/robot_icons)
+
+ data["currentName"] = newName ? newName : R.name
+ data["isDefaultName"] = !newName
+
+ if(selected_module)
+ data["selected_module"] = selected_module
+ if(!SSrobot_sprites)
+ to_chat(R, span_warning("Robot Sprites have not been initialized yet. How are you choosing a sprite? Harass a coder."))
+ selected_module = null
+ return
+
+ var/list/module_sprites = SSrobot_sprites.get_module_sprites(selected_module, R)
+ if(!module_sprites || !module_sprites.len)
+ to_chat(R, span_warning("Your module appears to have no sprite options. Harass a coder."))
+ selected_module = null
+ return
+ var/list/available_sprites = list()
+ for(var/datum/robot_sprite/S in module_sprites)
+ var/model_type = "def"
+ if(istype(S, /datum/robot_sprite/dogborg))
+ model_type = "wide"
+ if(istype(S, /datum/robot_sprite/dogborg/tall))
+ model_type = "tall"
+ available_sprites += list(list("sprite" = S.name, "belly" = S.has_vore_belly_sprites, "type" = model_type))
+
+ data["possible_sprites"] = available_sprites
+ data["sprite_datum"] = sprite_datum
+ data["sprite_datum_class"] = null
+ data["sprite_datum_size"] = null
+ if(sprite_datum)
+ data["sprite_datum_class"] = sanitize_css_class_name("[sprite_datum.type]")
+ data["sprite_datum_size"] = spritesheet.icon_size_id(data["sprite_datum_class"] + "S") // just get the south icon's size, the rest will be the same
+
+ return data
+
+/datum/tgui_module/robot_ui_module/tgui_act(action, params)
+ . = ..()
+ if(.)
+ return
+
+ var/mob/living/silicon/robot/R = host
+
+ switch(action)
+ if("pick_module")
+ if(R.module)
+ return
+ var/new_module = params["value"]
+ if(!(new_module in robot_modules))
+ return
+ if(!is_borg_whitelisted(R, new_module))
+ return
+ selected_module = new_module
+ var/new_datum
+ var/list/module_sprites = SSrobot_sprites.get_module_sprites(selected_module, R)
+ for(var/datum/robot_sprite/S in module_sprites)
+ if(S.name == sprite_datum.name)
+ new_datum = S
+ break
+ sprite_datum = new_datum
+ . = TRUE
+ if("pick_icon")
+ var/sprite = params["value"]
+ if(!sprite)
+ return
+ var/list/module_sprites = SSrobot_sprites.get_module_sprites(selected_module, R)
+ for(var/datum/robot_sprite/S in module_sprites)
+ if(S.name == sprite)
+ sprite_datum = S
+ break
+ . = TRUE
+ if("rename")
+ var/name = params["value"]
+ if(name)
+ newName = sanitizeSafe(name, MAX_NAME_LEN)
+ . = TRUE
+ if("confirm")
+ if(!R.custom_name)
+ if (newName)
+ R.custom_name = newName
+ R.sprite_name = newName
+ R.icon_selected = TRUE
+ var/module_type = robot_modules[selected_module]
+ R.modtype = selected_module
+ R.module = new module_type(R)
+ feedback_inc("cyborg_[lowertext(selected_module)]",1)
+ R.updatename()
+ R.hud_used.update_robot_modules_display()
+ R.notify_ai(ROBOT_NOTIFICATION_NEW_MODULE, R.module.name)
+ R.robotact?.update_static_data_for_all_viewers()
+ R.sprite_datum = sprite_datum
+ if(!istype(R,/mob/living/silicon/robot/drone))
+ R.sprite_type = sprite_datum.name
+ R.transform_with_anim()
+ var/tempheight = R.vis_height
+ R.update_icon()
+ // This is bad but I dunno other way to 'reset' our resize offset based on vis_height changes other than resizing to normal and back.
+ if(tempheight != R.vis_height)
+ var/tempsize = R.size_multiplier
+ R.resize(1)
+ R.resize(tempsize)
+ if(R.hands)
+ R.update_hud()
+ R.sprite_datum.do_equipment_glamour(R.module)
+ to_chat(R, span_filter_notice("Your icon has been set. You now require a module reset to change it."))
+ close_ui()
+ . = TRUE
diff --git a/code/modules/mob/living/silicon/robot/subtypes/gravekeeper.dm b/code/modules/mob/living/silicon/robot/subtypes/gravekeeper.dm
index 73357c10711..ccf1908c166 100644
--- a/code/modules/mob/living/silicon/robot/subtypes/gravekeeper.dm
+++ b/code/modules/mob/living/silicon/robot/subtypes/gravekeeper.dm
@@ -9,6 +9,7 @@
icon_selected = FALSE
can_be_antagged = FALSE
restrict_modules_to = list("Gravekeeper")
+ ui_theme = "malfunction"
/mob/living/silicon/robot/gravekeeper/init()
aiCamera = new/obj/item/camera/siliconcam/robot_camera(src)
diff --git a/code/modules/mob/living/silicon/robot/subtypes/lost_drone.dm b/code/modules/mob/living/silicon/robot/subtypes/lost_drone.dm
index 24e449c411c..9f0aa11446c 100644
--- a/code/modules/mob/living/silicon/robot/subtypes/lost_drone.dm
+++ b/code/modules/mob/living/silicon/robot/subtypes/lost_drone.dm
@@ -9,6 +9,7 @@
icon_selected = FALSE
restrict_modules_to = list("Lost")
var/law_retries = 5
+ ui_theme = "malfunction"
/mob/living/silicon/robot/lost/init()
aiCamera = new/obj/item/camera/siliconcam/robot_camera(src)
diff --git a/code/modules/mob/living/silicon/robot/subtypes/syndicate.dm b/code/modules/mob/living/silicon/robot/subtypes/syndicate.dm
index 5dd72332e44..15a8257a499 100644
--- a/code/modules/mob/living/silicon/robot/subtypes/syndicate.dm
+++ b/code/modules/mob/living/silicon/robot/subtypes/syndicate.dm
@@ -8,6 +8,7 @@
idcard_type = /obj/item/card/id/syndicate
icon_selected = FALSE
restrict_modules_to = list("Protector", "Mechanist", "Combat Medic")
+ ui_theme = "syndicate"
/mob/living/silicon/robot/syndicate/init()
aiCamera = new/obj/item/camera/siliconcam/robot_camera(src)
diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm
index a686e35de7b..5d018586f1d 100644
--- a/code/modules/mob/transform_procs.dm
+++ b/code/modules/mob/transform_procs.dm
@@ -190,7 +190,6 @@
O.custom_speech_bubble = B.custom_speech_bubble
callHook("borgify", list(O))
- O.namepick()
spawn(0) // Mobs still instantly del themselves, thus we need to spawn or O will never be returned
qdel(src)
diff --git a/icons/mob/robot/janitor.dmi b/icons/mob/robot/janitor.dmi
index 7410b353a75..3dbaa27a420 100644
Binary files a/icons/mob/robot/janitor.dmi and b/icons/mob/robot/janitor.dmi differ
diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotComponent.tsx b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotComponent.tsx
index 293db7da7de..165b95af2e7 100644
--- a/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotComponent.tsx
+++ b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotComponent.tsx
@@ -2,16 +2,17 @@ import { capitalize } from 'common/string';
import { useState } from 'react';
import { useBackend } from 'tgui/backend';
import {
+ Box,
Button,
Divider,
Flex,
Icon,
- Image,
Input,
Section,
Stack,
Tabs,
} from 'tgui/components';
+import { classes } from 'tgui-core/react';
import { NoSpriteWarning } from '../components';
import {
@@ -163,15 +164,18 @@ export const ModifyRobotComponent = (props: {
-
+
+
+
+
+
+
+
diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotModules.tsx b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotModules.tsx
index 045025ccd56..85ad5a6da4c 100644
--- a/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotModules.tsx
+++ b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotModules.tsx
@@ -13,6 +13,7 @@ import {
Section,
Stack,
} from 'tgui/components';
+import { classes } from 'tgui-core/react';
import { NoSpriteWarning } from '../components';
import { prepareSearch } from '../functions';
@@ -47,7 +48,8 @@ export const ModifyRobotModules = (props: {
/>
{!!source && (
-
+
+
+
+
+
+
+
{
/>
-
+
+
+
+
+
+
+
{
/>
-
+
+
+
+
+
+
+
{
+ const { act } = useBackend();
+ const { currentName, isDefaultName, sprite, size } = props;
+
+ return (
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/RobotChoose/ModuleSection.tsx b/tgui/packages/tgui/interfaces/RobotChoose/ModuleSection.tsx
new file mode 100644
index 00000000000..c90dbbe7be5
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/RobotChoose/ModuleSection.tsx
@@ -0,0 +1,47 @@
+import { createSearch } from 'common/string';
+import { useState } from 'react';
+import { Input, Section, Stack } from 'tgui-core/components';
+
+import { SelectorElement } from './SelectorElement';
+
+export const ModuleSection = (props: {
+ title: string;
+ sortable?: string[];
+ selected?: string;
+}) => {
+ const { title, sortable, selected } = props;
+
+ const [searchText, setSearchText] = useState('');
+
+ const searcher = createSearch(searchText, (element: string) => {
+ return element;
+ });
+
+ const filtered = sortable?.filter(searcher);
+
+ return (
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/RobotChoose/SelectorElement.tsx b/tgui/packages/tgui/interfaces/RobotChoose/SelectorElement.tsx
new file mode 100644
index 00000000000..3279ce78829
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/RobotChoose/SelectorElement.tsx
@@ -0,0 +1,34 @@
+import { useBackend } from 'tgui/backend';
+import { Button, Flex, Icon, Stack } from 'tgui-core/components';
+
+export const SelectorElement = (props: {
+ option: string;
+ action: string;
+ selected?: string | null;
+ belly?: boolean;
+}) => {
+ const { act } = useBackend();
+ const { option, action, selected, belly } = props;
+
+ return (
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/RobotChoose/SpriteSection.tsx b/tgui/packages/tgui/interfaces/RobotChoose/SpriteSection.tsx
new file mode 100644
index 00000000000..d287792d025
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/RobotChoose/SpriteSection.tsx
@@ -0,0 +1,80 @@
+import { useState } from 'react';
+import { Button, Input, Section, Stack } from 'tgui-core/components';
+
+import { robotSpriteSearcher } from './functions';
+import { SelectorElement } from './SelectorElement';
+import { spriteOption } from './types';
+
+export const SpriteSection = (props: {
+ title: string;
+ selected?: string | null;
+ sortable?: spriteOption[];
+}) => {
+ const { title, sortable, selected } = props;
+
+ const [searchText, setSearchText] = useState('');
+ const [includeDefault, setIncludeDefault] = useState(false);
+ const [includeWide, setInclideDog] = useState(false);
+ const [includeTall, setIncludeTall] = useState(false);
+
+ const filtered = robotSpriteSearcher(
+ searchText,
+ includeDefault,
+ includeWide,
+ includeTall,
+ sortable,
+ );
+ return (
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/RobotChoose/functions.ts b/tgui/packages/tgui/interfaces/RobotChoose/functions.ts
new file mode 100644
index 00000000000..db60717e88e
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/RobotChoose/functions.ts
@@ -0,0 +1,47 @@
+import { filter } from 'common/collections';
+import { flow } from 'common/fp';
+import { createSearch } from 'common/string';
+
+import { spriteOption } from './types';
+
+export function robotSpriteSearcher(
+ searchText: string,
+ includeDefault: boolean,
+ includeWide: boolean,
+ includeTall: boolean,
+ sprites?: spriteOption[],
+): spriteOption[] {
+ const testSearch = createSearch(
+ searchText,
+ (sprite: spriteOption) => sprite.sprite,
+ );
+ if (!sprites) {
+ return [];
+ }
+ let subtypes: string[] = [];
+ if (includeDefault) {
+ subtypes.push('def');
+ }
+ if (includeWide) {
+ subtypes.push('wide');
+ }
+ if (includeTall) {
+ subtypes.push('tall');
+ }
+ return flow([
+ (sprites: spriteOption[]) => {
+ if (!searchText) {
+ return sprites;
+ } else {
+ return filter(sprites, testSearch);
+ }
+ },
+ (sprites: spriteOption[]) => {
+ if (!subtypes.length) {
+ return sprites;
+ } else {
+ return filter(sprites, (sprite) => subtypes.includes(sprite.type));
+ }
+ },
+ ])(sprites);
+}
diff --git a/tgui/packages/tgui/interfaces/RobotChoose/index.tsx b/tgui/packages/tgui/interfaces/RobotChoose/index.tsx
new file mode 100644
index 00000000000..91d8dcb7d57
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/RobotChoose/index.tsx
@@ -0,0 +1,49 @@
+import { useBackend } from 'tgui/backend';
+import { Window } from 'tgui/layouts';
+import { Stack } from 'tgui-core/components';
+
+import { IconSection } from './IconSection';
+import { ModuleSection } from './ModuleSection';
+import { SpriteSection } from './SpriteSection';
+import { Data } from './types';
+
+export const RobotChoose = (props) => {
+ const { data } = useBackend();
+
+ const {
+ possible_modules,
+ possible_sprites,
+ selected_module,
+ sprite_datum,
+ theme,
+ currentName,
+ isDefaultName,
+ sprite_datum_class,
+ sprite_datum_size,
+ } = data;
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/RobotChoose/types.ts b/tgui/packages/tgui/interfaces/RobotChoose/types.ts
new file mode 100644
index 00000000000..06d691c5893
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/RobotChoose/types.ts
@@ -0,0 +1,13 @@
+export type Data = {
+ possible_modules: string[];
+ possible_sprites?: spriteOption[];
+ currentName: string;
+ isDefaultName: boolean;
+ theme?: string;
+ selected_module?: string;
+ sprite_datum?: string | null;
+ sprite_datum_class?: string | null;
+ sprite_datum_size?: string | null;
+};
+
+export type spriteOption = { sprite: string; belly: boolean; type: string };
diff --git a/tgui/packages/tgui/interfaces/Robotact/index.tsx b/tgui/packages/tgui/interfaces/Robotact/index.tsx
index 94b97253342..1ab5ea486aa 100644
--- a/tgui/packages/tgui/interfaces/Robotact/index.tsx
+++ b/tgui/packages/tgui/interfaces/Robotact/index.tsx
@@ -18,7 +18,7 @@ export const Robotact = (props) => {
}
return (
-
+
{content}
);
diff --git a/tgui/packages/tgui/interfaces/Robotact/types.ts b/tgui/packages/tgui/interfaces/Robotact/types.ts
index bd33676d99d..b4232ff6e43 100644
--- a/tgui/packages/tgui/interfaces/Robotact/types.ts
+++ b/tgui/packages/tgui/interfaces/Robotact/types.ts
@@ -31,6 +31,7 @@ export type Data = {
max_health: number;
light_color: string;
+ theme?: string;
// Modules
modules_static: Module[];
diff --git a/vorestation.dme b/vorestation.dme
index 5ef4cbce393..dbe63b1438f 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -3163,6 +3163,7 @@
#include "code\modules\mob\living\silicon\robot\robot_movement.dm"
#include "code\modules\mob\living\silicon\robot\robot_remote_control.dm"
#include "code\modules\mob\living\silicon\robot\robot_ui.dm"
+#include "code\modules\mob\living\silicon\robot\robot_ui_module.dm"
#include "code\modules\mob\living\silicon\robot\dogborg\dog_modules_vr.dm"
#include "code\modules\mob\living\silicon\robot\dogborg\dog_sleeper_vr.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone.dm"