[MIRROR] Moving robot module and icon selection to tgui (#9492)

Co-authored-by: Heroman3003 <31296024+Heroman3003@users.noreply.github.com>
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2024-11-17 14:56:51 -07:00
committed by GitHub
parent 3e9b15570f
commit 7f2914d757
34 changed files with 708 additions and 200 deletions

View File

@@ -628,3 +628,8 @@ GLOBAL_LIST_EMPTY(text_tag_cache)
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, "")

View File

@@ -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)

View File

@@ -225,7 +225,6 @@
feedback_inc("cyborg_birth",1)
callHook("borgify", list(O))
O.namepick()
qdel(src)
else

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -445,6 +445,45 @@
for(var/i = 1 to 4) // CHOMPedit
Insert("patch[i].png", 'icons/obj/chemical.dmi', "patch[i]") // CHOMPedit
// 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(

View File

@@ -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

View File

@@ -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)

View File

@@ -33,8 +33,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
@@ -49,6 +48,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
@@ -339,50 +341,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 //CHOMPstaton change to blacklist
if(shell)
if(restrict_modules_to.len > 0)
modules.Add(restrict_modules_to)
else
modules.Add(robot_module_types)
modules.Remove(GLOB.shell_module_blacklist) // CHOMPEdit - Managed Globals
//CHOMPedit Add
if(crisis || security_level == SEC_LEVEL_RED || crisis_override)
to_chat(src, span_red("Crisis mode active. Combat module available."))
modules |= emergency_module_types
//CHOMPedit end
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
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))
@@ -448,15 +417,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"
@@ -810,6 +776,7 @@
notify_ai(ROBOT_NOTIFICATION_MODULE_RESET, module.name)
module.Reset(src)
qdel(module)
icon_selected = FALSE
module = null
updatename("Default")
has_recoloured = FALSE
@@ -1126,82 +1093,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)
sprite_datum = selection
if(selection)
sprite_datum = selection
else
sprite_datum = module_sprites[1]
//CHOMPEdit Start, allow multi bellies
vore_icon_bellies = list() //Clear any belly options that may not exist now
vore_capacity_ex = list()
vore_fullness_ex = list()
if(sprite_datum.belly_capacity_list.len)
for(var/belly in sprite_datum.belly_capacity_list) //vore icons list only contains a list of names with no associated data
vore_capacity_ex[belly] = sprite_datum.belly_capacity_list[belly] //I dont know why but this wasnt working when I just
vore_fullness_ex[belly] = 0 //set the lists equal to the old lists
vore_icon_bellies += belly
for(var/belly in sprite_datum.belly_light_list)
vore_light_states[belly] = 0
else if(sprite_datum.has_vore_belly_sprites)
vore_capacity_ex = list("sleeper" = 1)
vore_fullness_ex = list("sleeper" = 0)
vore_icon_bellies = list("sleeper")
if(sprite_datum.has_sleeper_light_indicator)
vore_light_states = list("sleeepr" = 0)
sprite_datum.belly_light_list = list("sleeper")
update_fullness() //Set how full the newly defined bellies are, if they're already full
//CHOMPEdit End
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

View File

@@ -74,7 +74,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!
@@ -94,7 +94,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

View File

@@ -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

View File

@@ -0,0 +1,186 @@
// Robot module selection
/datum/tgui_module/robot_ui_module
name = "Robot Module Configurator"
tgui_id = "RobotChoose"
var/selected_module
var/new_name
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)
// CHOMPAdd Start, shell blacklist and crisis mode for shells
modules.Remove(GLOB.shell_module_blacklist)
if(R.crisis || security_level == SEC_LEVEL_RED || R.crisis_override)
to_chat(src, span_red("Crisis mode active. Combat module available."))
modules |= emergency_module_types
// CHOMPAdd End
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"] = new_name ? new_name : R.name
data["isDefaultName"] = !new_name
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
if(sprite_datum)
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)
new_name = sanitizeSafe(name, MAX_NAME_LEN)
. = TRUE
if("confirm")
R.apply_name(new_name)
R.apply_module(sprite_datum, selected_module)
R.update_multibelly() // CHOMPAdd Multibelly
R.transform_module()
close_ui()
. = TRUE
/mob/living/silicon/robot/proc/apply_name(var/new_name)
if(!custom_name)
if (new_name)
custom_name = new_name
sprite_name = new_name
/mob/living/silicon/robot/proc/apply_module(var/datum/robot_sprite/new_datum, var/new_module)
icon_selected = TRUE
var/module_type = robot_modules[new_module]
modtype = new_module
module = new module_type(src)
feedback_inc("cyborg_[lowertext(new_module)]",1)
updatename()
hud_used.update_robot_modules_display()
notify_ai(ROBOT_NOTIFICATION_NEW_MODULE, module.name)
robotact?.update_static_data_for_all_viewers()
sprite_datum = new_datum
if(!istype(src,/mob/living/silicon/robot/drone))
sprite_type = sprite_datum.name
/mob/living/silicon/robot/proc/transform_module()
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(hands)
update_hud()
sprite_datum.do_equipment_glamour(module)
to_chat(src, span_filter_notice("Your icon has been set. You now require a module reset to change it."))

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -194,7 +194,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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -34,6 +34,26 @@
else
to_chat(src, span_filter_notice("Insufficient water reserves."))
/mob/living/silicon/robot/proc/update_multibelly()
vore_icon_bellies = list() //Clear any belly options that may not exist now
vore_capacity_ex = list()
vore_fullness_ex = list()
if(sprite_datum.belly_capacity_list.len)
for(var/belly in sprite_datum.belly_capacity_list) //vore icons list only contains a list of names with no associated data
vore_capacity_ex[belly] = sprite_datum.belly_capacity_list[belly] //I dont know why but this wasnt working when I just
vore_fullness_ex[belly] = 0 //set the lists equal to the old lists
vore_icon_bellies += belly
for(var/belly in sprite_datum.belly_light_list)
vore_light_states[belly] = 0
else if(sprite_datum.has_vore_belly_sprites)
vore_capacity_ex = list("sleeper" = 1)
vore_fullness_ex = list("sleeper" = 0)
vore_icon_bellies = list("sleeper")
if(sprite_datum.has_sleeper_light_indicator)
vore_light_states = list("sleeepr" = 0)
sprite_datum.belly_light_list = list("sleeper")
update_fullness() //Set how full the newly defined bellies are, if they're already full
/mob/living/silicon/robot/proc/reset_belly_lights(var/b_class)
if(sprite_datum.belly_light_list.len && sprite_datum.belly_light_list.Find(b_class))
vore_light_states[b_class] = 0

View File

@@ -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: {
<Flex.Item width="30%" fill>
<Stack vertical fill>
<Stack.Item>
<Image
src={'data:image/jpeg;base64, ' + target.side_alt}
style={{
display: 'block',
marginLeft: 'auto',
marginRight: 'auto',
width: '200px',
}}
/>
<Flex>
<Flex.Item grow />
<Flex.Item>
<Box
className={classes([
target.sprite_size,
target.sprite + 'E',
])}
/>
</Flex.Item>
<Flex.Item grow />
</Flex>
</Stack.Item>
<Stack.Item>
<Tabs>

View File

@@ -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 && (
<SelectionField
previewImage={source.front}
previewImage={source.sprite}
previewImageSize={source.sprite_size}
searchText={searchSourceText}
onSearchText={setSearchSourceText}
action="add_module"
@@ -106,7 +108,8 @@ export const ModifyRobotModules = (props: {
</Button.Confirm>
<Divider />
<SelectionField
previewImage={target.front}
previewImage={target.sprite}
previewImageSize={target.sprite_size}
searchText={searchModuleText}
onSearchText={setSearchModulText}
action="rem_module"
@@ -123,6 +126,7 @@ export const ModifyRobotModules = (props: {
const SelectionField = (props: {
previewImage: string | undefined;
previewImageSize: string | undefined;
searchText: string;
onSearchText: Function;
action: string;
@@ -133,6 +137,7 @@ const SelectionField = (props: {
const { act } = useBackend();
const {
previewImage,
previewImageSize,
searchText,
onSearchText,
action,
@@ -144,15 +149,13 @@ const SelectionField = (props: {
return (
<>
<Divider />
<Image
src={'data:image/jpeg;base64, ' + previewImage}
style={{
display: 'block',
marginLeft: 'auto',
marginRight: 'auto',
height: '200px',
}}
/>
<Flex>
<Flex.Item grow />
<Flex.Item>
<Box className={classes([previewImageSize, previewImage + 'S'])} />
</Flex.Item>
<Flex.Item grow />
</Flex>
<Divider />
<Input
fluid

View File

@@ -2,15 +2,16 @@ import { capitalize } from 'common/string';
import { useState } from 'react';
import { useBackend } from 'tgui/backend';
import {
Box,
Button,
Divider,
Flex,
Icon,
Image,
Input,
Section,
Stack,
} from 'tgui/components';
import { classes } from 'tgui-core/react';
import { NoSpriteWarning } from '../components';
import { prepareSearch } from '../functions';
@@ -38,15 +39,15 @@ export const ModifyRobotRadio = (props: { target: Target }) => {
/>
</Flex.Item>
<Flex.Item width="40%">
<Image
src={'data:image/jpeg;base64, ' + target.back}
style={{
display: 'block',
marginLeft: 'auto',
marginRight: 'auto',
width: '200px',
}}
/>
<Flex>
<Flex.Item grow />
<Flex.Item>
<Box
className={classes([target.sprite_size, target.sprite + 'N'])}
/>
</Flex.Item>
<Flex.Item grow />
</Flex>
</Flex.Item>
<Flex.Item width="30%" fill>
<RadioSection

View File

@@ -2,14 +2,15 @@ import { capitalize } from 'common/string';
import { useState } from 'react';
import { useBackend } from 'tgui/backend';
import {
Box,
Button,
Divider,
Flex,
Image,
Input,
Section,
Stack,
} from 'tgui/components';
import { classes } from 'tgui-core/react';
import { NoSpriteWarning } from '../components';
import { install2col } from '../constants';
@@ -45,15 +46,15 @@ export const ModifyRobotUpgrades = (props: { target: Target }) => {
/>
</Flex.Item>
<Flex.Item width="40%">
<Image
src={'data:image/jpeg;base64, ' + target.side}
style={{
display: 'block',
marginLeft: 'auto',
marginRight: 'auto',
width: '200px',
}}
/>
<Flex>
<Flex.Item grow />
<Flex.Item>
<Box
className={classes([target.sprite_size, target.sprite + 'W'])}
/>
</Flex.Item>
<Flex.Item grow />
</Flex>
</Flex.Item>
<Flex.Item width="30%" fill>
<UpgradeSection

View File

@@ -54,10 +54,8 @@ export type Target = {
crisis_override: BooleanLike;
active_restrictions: string[];
possible_restrictions: string[];
front: string | undefined;
side: string | undefined;
side_alt: string | undefined;
back: string | undefined;
sprite: string | undefined;
sprite_size: string | undefined;
modules: Module[];
whitelisted_upgrades: Upgrade[];
blacklisted_upgrades: Upgrade[];
@@ -80,7 +78,8 @@ export type Upgrade = {
export type Source = {
model: string;
front: string;
sprite: string | undefined;
sprite_size: string | undefined;
modules: Module[];
} | null;

View File

@@ -0,0 +1,88 @@
import { useBackend } from 'tgui/backend';
import { Box, Button, Flex, Input, Section, Stack } from 'tgui-core/components';
import { classes } from 'tgui-core/react';
import { Tooltip } from '../../components';
export const IconSection = (props: {
currentName: string;
isDefaultName: boolean;
sprite?: string | null;
size?: string | null;
}) => {
const { act } = useBackend();
const { currentName, isDefaultName, sprite, size } = props;
return (
<Section
title="Sprite"
fill
scrollable
width="40%"
buttons={
<Button disabled={!sprite} onClick={() => act('confirm')}>
Confirm
</Button>
}
>
<Stack.Item>
<Stack fill>
<Stack.Item>
<Box>Name: </Box>
</Stack.Item>
<Stack.Item basis="100%">
<Tooltip content="Adjust your name">
<Input
fluid
value={currentName}
onChange={(e, value) => act('rename', { value })}
maxLength={52}
textColor={isDefaultName ? 'red' : undefined}
/>
</Tooltip>
</Stack.Item>
</Stack>
</Stack.Item>
{!!sprite && !!size && (
<>
<Stack.Item>
<Flex>
<Flex.Item grow />
<Flex.Item>
<Box className={classes([size, sprite + 'N'])} />
</Flex.Item>
<Flex.Item grow />
</Flex>
</Stack.Item>
<Stack.Item>
<Flex>
<Flex.Item grow />
<Flex.Item>
<Box className={classes([size, sprite + 'S'])} />
</Flex.Item>
<Flex.Item grow />
</Flex>
</Stack.Item>
<Stack.Item>
<Flex>
<Flex.Item grow />
<Flex.Item>
<Box className={classes([size, sprite + 'W'])} />
</Flex.Item>
<Flex.Item grow />
</Flex>
</Stack.Item>
<Stack.Item>
<Flex>
<Flex.Item grow />
<Flex.Item>
<Box className={classes([size, sprite + 'E'])} />
</Flex.Item>
<Flex.Item grow />
</Flex>
</Stack.Item>
</>
)}
</Section>
);
};

View File

@@ -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<string>('');
const searcher = createSearch(searchText, (element: string) => {
return element;
});
const filtered = sortable?.filter(searcher);
return (
<Section title={title} fill scrollable width="30%">
<Stack.Item mb={'10px'}>
<Input
fluid
value={searchText}
placeholder="Search for modules..."
onInput={(e, value: string) => setSearchText(value)}
/>
</Stack.Item>
<Stack.Item>
<Stack vertical fill>
{!!filtered &&
filtered.map((filter) => (
<SelectorElement
key={filter}
option={filter}
action="pick_module"
selected={selected}
/>
))}
</Stack>
</Stack.Item>
</Section>
);
};

View File

@@ -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 (
<Stack.Item>
<Button
fluid
selected={option === selected}
onClick={() => act(action, { value: option })}
>
<Flex>
<Flex.Item>{option}</Flex.Item>
{!!belly && (
<>
<Flex.Item grow />
<Flex.Item>
<Icon name="utensils" />
</Flex.Item>
</>
)}
</Flex>
</Button>
</Stack.Item>
);
};

View File

@@ -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<string>('');
const [includeDefault, setIncludeDefault] = useState<boolean>(false);
const [includeWide, setInclideDog] = useState<boolean>(false);
const [includeTall, setIncludeTall] = useState<boolean>(false);
const filtered = robotSpriteSearcher(
searchText,
includeDefault,
includeWide,
includeTall,
sortable,
);
return (
<Section
title={title}
fill
scrollable
width="30%"
buttons={
<>
<Button.Checkbox
checked={includeDefault}
onClick={() => setIncludeDefault(!includeDefault)}
>
Def
</Button.Checkbox>
<Button.Checkbox
checked={includeWide}
onClick={() => setInclideDog(!includeWide)}
>
Wide
</Button.Checkbox>
<Button.Checkbox
checked={includeTall}
onClick={() => setIncludeTall(!includeTall)}
>
Tall
</Button.Checkbox>
</>
}
>
<Stack.Item mb={'10px'}>
<Input
fluid
value={searchText}
placeholder="Search for sprites..."
onInput={(e, value: string) => setSearchText(value)}
/>
</Stack.Item>
<Stack.Item>
<Stack vertical>
{!!filtered &&
filtered.map((filter) => (
<SelectorElement
key={filter.sprite}
option={filter.sprite}
action="pick_icon"
selected={selected}
belly={filter.belly}
/>
))}
</Stack>
</Stack.Item>
</Section>
);
};

View File

@@ -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);
}

View File

@@ -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<Data>();
const {
possible_modules,
possible_sprites,
selected_module,
sprite_datum,
theme,
currentName,
isDefaultName,
sprite_datum_class,
sprite_datum_size,
} = data;
return (
<Window width={800} height={600} theme={theme || 'ntos'}>
<Window.Content>
<Stack fill>
<ModuleSection
title="Modules"
sortable={possible_modules}
selected={selected_module}
/>
<SpriteSection
title="Sprites"
sortable={possible_sprites}
selected={sprite_datum}
/>
<IconSection
currentName={currentName}
isDefaultName={isDefaultName}
sprite={sprite_datum_class}
size={sprite_datum_size}
/>
</Stack>
</Window.Content>
</Window>
);
};

View File

@@ -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 };

View File

@@ -18,7 +18,7 @@ export const Robotact = (props) => {
}
return (
<Window width={800} height={600} theme="ntos">
<Window width={800} height={600} theme={data.theme || 'ntos'}>
<Window.Content>{content}</Window.Content>
</Window>
);

View File

@@ -31,6 +31,7 @@ export type Data = {
max_health: number;
light_color: string;
theme?: string;
// Modules
modules_static: Module[];

View File

@@ -3362,6 +3362,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_modules_yw.dm"
#include "code\modules\mob\living\silicon\robot\dogborg\dog_sleeper_vr.dm"