diff --git a/code/game/objects/effects/decals/Cleanable/robots.dm b/code/game/objects/effects/decals/Cleanable/robots.dm index 16ea69c868a..b3e27786298 100644 --- a/code/game/objects/effects/decals/Cleanable/robots.dm +++ b/code/game/objects/effects/decals/Cleanable/robots.dm @@ -1,7 +1,7 @@ /obj/effect/decal/cleanable/blood/gibs/robot name = "robot debris" desc = "It's a useless heap of junk... or is it?" - icon = 'icons/mob/robots.dmi' + icon = 'icons/mob/robot_gibs.dmi' icon_state = "gib1" basecolor = SYNTH_BLOOD_COLOUR random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7") diff --git a/code/game/objects/effects/decals/remains.dm b/code/game/objects/effects/decals/remains.dm index 14360092cd4..3266ab07320 100644 --- a/code/game/objects/effects/decals/remains.dm +++ b/code/game/objects/effects/decals/remains.dm @@ -14,7 +14,7 @@ /obj/effect/decal/remains/robot desc = "They look like the remains of something mechanical. They have a strange aura about them." - icon = 'icons/mob/robots.dmi' + icon = 'icons/mob/robot_gibs.dmi' icon_state = "remainsrobot" /obj/effect/decal/remains/mouse diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index a9065fb7455..315890eb95d 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -3,7 +3,7 @@ /mob/living/silicon/robot name = "Cyborg" real_name = "Cyborg" - icon = 'icons/mob/robots.dmi' + icon = 'icons/mob/robots/robots_grounded.dmi' icon_state = "robot" maxHealth = 200 health = 200 diff --git a/code/modules/mob/living/silicon/robot/robot_modules/_module.dm b/code/modules/mob/living/silicon/robot/robot_modules/_module.dm index 7a7bb22d274..bfd9b1e2398 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules/_module.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules/_module.dm @@ -154,11 +154,19 @@ R.radio.recalculateChannels() R.choose_icon(0, R.set_module_sprites(list("Default" = "robot"))) +// This can qdel before init if spawned outside a mob, so +// Destroy() needs to be a bit nuanced to avoid runtimes. /obj/item/robot_module/Destroy() - QDEL_NULL_LIST(modules) - QDEL_NULL_LIST(synths) - QDEL_NULL(emag) - QDEL_NULL(jetpack) + for(var/datum/thing in modules) + qdel(thing) + modules = null + for(var/datum/thing in synths) + qdel(thing) + synths = null + if(istype(emag)) + QDEL_NULL(emag) + if(istype(jetpack)) + QDEL_NULL(jetpack) return ..() /obj/item/robot_module/emp_act(severity) diff --git a/code/modules/mob/living/silicon/robot/robot_modules/event.dm b/code/modules/mob/living/silicon/robot/robot_modules/event.dm index f92ff1afb41..09811bdb004 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules/event.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules/event.dm @@ -3,6 +3,7 @@ // The module that borgs on the surface have. Generally has a lot of useful tools in exchange for questionable loyalty to the crew. /obj/item/robot_module/robot/lost name = "lost robot module" + module_category = ROBOT_MODULE_TYPE_FLYING unavailable_by_default = TRUE hide_on_manifest = TRUE sprites = list( @@ -35,11 +36,11 @@ /obj/item/robot_module/robot/gravekeeper name = "gravekeeper robot module" + display_name = "Gravekeeper" unavailable_by_default = TRUE hide_on_manifest = TRUE sprites = list( - "Drone" = "drone-gravekeeper", - "Sleek" = "sleek-gravekeeper" + "Gravekeeper" = "sleek-gravekeeper" ) modules = list( /obj/item/melee/baton/shocker/robot, 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 b79212b7e93..c5aa88af455 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules/station.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules/station.dm @@ -14,7 +14,6 @@ "Android" = "droid", "Insekt" = "insekt-Default", "Usagi-II" = "tall2standard", - "Pyralis" = "Glitterfly-Standard", "Decapod" = "decapod-Standard", "Pneuma" = "pneuma-Standard", "Tower" = "drider-Standard" @@ -30,6 +29,8 @@ module_category = ROBOT_MODULE_TYPE_FLYING can_be_pushed = TRUE sprites = list( + "Drone" = "drone-standard", + "Pyralis" = "Glitterfly-Standard", "Cabeiri" = "eyebot-standard" ) @@ -55,10 +56,8 @@ "Basic" = "Medbot", "Advanced Droid" = "droid-medical", "Needles" = "medicalrobot", - "Handy" = "handy-med", "Insekt" = "insekt-Med", "Usagi-II" = "tall2medical", - "Pyralis" = "Glitterfly-Surgeon", "Decapod" = "decapod-Surgeon", "Pneuma" = "pneuma-Surgeon", "Tower" = "drider-Surgeon" @@ -93,8 +92,10 @@ module_category = ROBOT_MODULE_TYPE_FLYING can_be_pushed = TRUE sprites = list( - "Drone" = "drone-medical", - "Eyebot" = "eyebot-medical" + "Handy" = "handy-med", + "Drone" = "drone-medical", + "Eyebot" = "eyebot-medical", + "Pyralis" = "Glitterfly-Surgeon" ) /obj/item/robot_module/robot/medical/surgeon/finalize_emag() @@ -145,7 +146,6 @@ "Needles" = "medicalrobot", "Insekt" = "insekt-Med", "Usagi-II" = "tall2medical", - "Pyralis" = "Glitterfly-Crisis", "Decapod" = "decapod-Crisis", "Pneuma" = "pneuma-Crisis", "Tower" = "drider-Crisis" @@ -174,8 +174,9 @@ module_category = ROBOT_MODULE_TYPE_FLYING can_be_pushed = TRUE sprites = list( - "Drone" = "drone-medical", - "Eyebot" = "eyebot-medical" + "Drone" = "drone-medical", + "Eyebot" = "eyebot-medical", + "Pyralis" = "Glitterfly-Crisis" ) /obj/item/robot_module/robot/medical/crisis/finalize_emag() @@ -233,9 +234,7 @@ "Landmate" = "landmate", "Landmate - Treaded" = "engiborg+tread", "Treadwell" = "treadwell", - "Handy" = "handy-engineer", "Usagi-II" = "tall2engineer", - "Pyralis" = "Glitterfly-Engineering", "Decapod" = "decapod-Engineering", "Pneuma" = "pneuma-Engineering", "Tower" = "drider-Engineering" @@ -322,8 +321,10 @@ module_category = ROBOT_MODULE_TYPE_FLYING can_be_pushed = TRUE sprites = list( - "Drone" = "drone-engineer", - "Eyebot" = "eyebot-engineering" + "Handy" = "handy-engineer", + "Drone" = "drone-engineer", + "Eyebot" = "eyebot-engineering", + "Pyralis" = "Glitterfly-Engineering", ) /obj/item/robot_module/robot/security @@ -358,7 +359,6 @@ "Black Knight" = "securityrobot", "Insekt" = "insekt-Sec", "Usagi-II" = "tall2security", - "Pyralis" = "Glitterfly-Security", "Decapod" = "decapod-Security", "Pneuma" = "pneuma-Security", "Tower" = "drider-Security" @@ -368,8 +368,9 @@ module_category = ROBOT_MODULE_TYPE_FLYING can_be_pushed = TRUE sprites = list( - "Drone" = "drone-sec", - "Eyebot" = "eyebot-security" + "Pyralis" = "Glitterfly-Security", + "Drone" = "drone-sec", + "Eyebot" = "eyebot-security" ) /obj/item/robot_module/robot/security/respawn_consumable(var/mob/living/silicon/robot/R, var/amount) @@ -402,7 +403,6 @@ "Mopbot" = "janitorrobot", "Mop Gear Rex" = "mopgearrex", "Usagi-II" = "tall2janitor", - "Pyralis" = "Glitterfly-Janitor", "Decapod" = "decapod-Janitor", "Pneuma" = "pneuma-Janitor", "Tower" = "drider-Janitor" @@ -419,7 +419,9 @@ module_category = ROBOT_MODULE_TYPE_FLYING can_be_pushed = TRUE sprites = list( - "Cabeiri" = "eyebot-janitor", + "Drone" = "drone-janitor", + "Pyralis" = "Glitterfly-Janitor", + "Cabeiri" = "eyebot-janitor" ) /obj/item/robot_module/robot/janitor/finalize_emag() @@ -475,7 +477,6 @@ "Bro" = "Brobot", "Rich" = "maximillion", "Usagi-II" = "tall2service", - "Pyralis" = "Glitterfly-Service", "Decapod" = "decapod-Service", "Pneuma" = "pneuma-Service", "Tower" = "drider-Service" @@ -503,6 +504,7 @@ module_category = ROBOT_MODULE_TYPE_FLYING can_be_pushed = TRUE sprites = list( + "Pyralis" = "Glitterfly-Service", "Eyebot" = "eyebot-standard", "Service Drone" = "drone-service", "Hydroponics Drone" = "drone-hydro" @@ -541,7 +543,6 @@ "Rich" = "maximillion", "Default" = "Service2", "Usagi-II" = "tall2service", - "Pyralis" = "Glitterfly-Clerical", "Decapod" = "decapod-Clerical", "Pneuma" = "pneuma-Clerical", "Tower" = "drider-Clerical" @@ -562,7 +563,8 @@ sprites = list( "Eyebot" = "eyebot-standard", "Service Drone" = "drone-service", - "Hydroponics Drone" = "drone-hydro" + "Hydroponics Drone" = "drone-hydro", + "Pyralis" = "Glitterfly-Clerical" ) /obj/item/robot_module/general/butler/respawn_consumable(var/mob/living/silicon/robot/R, var/amount) @@ -588,7 +590,6 @@ "Advanced Droid" = "droid-miner", "Treadhead" = "Miner", "Usagi-II" = "tall2miner", - "Pyralis" = "Glitterfly-Miner", "Decapod" = "decapod-Miner", "Pneuma" = "pneuma-Miner", "Tower" = "drider-Miner" @@ -609,7 +610,9 @@ module_category = ROBOT_MODULE_TYPE_FLYING can_be_pushed = TRUE sprites = list( - "Cabeiri" = "eyebot-miner" + "Drone" = "drone-miner", + "Cabeiri" = "eyebot-miner", + "Pyralis" = "Glitterfly-Miner" ) /obj/item/robot_module/robot/research @@ -622,10 +625,8 @@ "WTDove" = "whitespider", "WTOperator" = "sleekscience", "Droid" = "droid-science", - "Handy" = "handy-science", "Insekt" = "insekt-Sci", "Usagi-II" = "tall2peace", - "Pyralis" = "Glitterfly-Research", "Decapod" = "decapod-Research", "Pneuma" = "pneuma-Research", "Tower" = "drider-Research" @@ -668,6 +669,9 @@ module_category = ROBOT_MODULE_TYPE_FLYING can_be_pushed = TRUE sprites = list( + "Handy" = "handy-science", + "Drone" = "drone-science", + "Pyralis" = "Glitterfly-Research", "Cabeiri" = "eyebot-science" ) diff --git a/code/modules/mob/living/silicon/robot/subtypes/flying.dm b/code/modules/mob/living/silicon/robot/subtypes/flying.dm index c44765ec390..cfcd3ea2660 100644 --- a/code/modules/mob/living/silicon/robot/subtypes/flying.dm +++ b/code/modules/mob/living/silicon/robot/subtypes/flying.dm @@ -1,6 +1,6 @@ /mob/living/silicon/robot/flying desc = "A utility robot with an anti-gravity hover unit and a lightweight frame." - icon = 'icons/mob/robots_flying.dmi' + icon = 'icons/mob/robots/robots_flying.dmi' icon_state = "drone-standard" module_category = ROBOT_MODULE_TYPE_FLYING dismantle_type = /obj/item/robot_parts/frame/flyer diff --git a/code/modules/mob/living/silicon/robot/subtypes/gravekeeper.dm b/code/modules/mob/living/silicon/robot/subtypes/gravekeeper.dm index 3706f212c97..a71cd78a44c 100644 --- a/code/modules/mob/living/silicon/robot/subtypes/gravekeeper.dm +++ b/code/modules/mob/living/silicon/robot/subtypes/gravekeeper.dm @@ -11,17 +11,12 @@ /mob/living/silicon/robot/gravekeeper/init() aiCamera = new/obj/item/camera/siliconcam/robot_camera(src) - mmi = new /obj/item/mmi/digital/robot(src) module = new /obj/item/robot_module/robot/gravekeeper(src) cut_overlays() init_id() - updatename("Gravekeeper") - if(!cell) cell = new /obj/item/cell/high(src) // 15k cell, as recharging stations are a lot more rare on the Surface. - laws = new /datum/ai_laws/gravekeeper() - playsound(src, 'sound/mecha/nominalsyndi.ogg', 75, 0) diff --git a/code/modules/mob/living/silicon/robot/subtypes/thinktank/_thinktank.dm b/code/modules/mob/living/silicon/robot/subtypes/thinktank/_thinktank.dm index 169cabaeea8..c860b949775 100644 --- a/code/modules/mob/living/silicon/robot/subtypes/thinktank/_thinktank.dm +++ b/code/modules/mob/living/silicon/robot/subtypes/thinktank/_thinktank.dm @@ -1,7 +1,7 @@ /mob/living/silicon/robot/platform name = "support platform" desc = "A large quadrupedal AI platform, colloquially known as a 'think-tank' due to the flexible onboard intelligence." - icon = 'icons/mob/robots_thinktank.dmi' + icon = 'icons/mob/robots/robots_platform.dmi' icon_state = "tachi" color = "#68a2f2" diff --git a/code/modules/mob/living/silicon/robot/subtypes/thinktank/thinktank_module.dm b/code/modules/mob/living/silicon/robot/subtypes/thinktank/thinktank_module.dm index d966d0b59ba..8e2ea9d50a0 100644 --- a/code/modules/mob/living/silicon/robot/subtypes/thinktank/thinktank_module.dm +++ b/code/modules/mob/living/silicon/robot/subtypes/thinktank/thinktank_module.dm @@ -1,6 +1,5 @@ /obj/item/robot_module/robot/platform - hide_on_manifest = TRUE module_category = ROBOT_MODULE_TYPE_PLATFORM unavailable_by_default = TRUE @@ -8,7 +7,7 @@ var/base_color = COLOR_WHITE var/eye_color = COLOR_BEIGE var/armor_color = "#68a2f2" - var/user_icon = 'icons/mob/robots_thinktank.dmi' + var/user_icon = 'icons/mob/robots/robots_platform.dmi' var/user_icon_state = "tachi" var/list/decals diff --git a/code/modules/xenoarcheaology/finds/find_spawning.dm b/code/modules/xenoarcheaology/finds/find_spawning.dm index 134fcd6243b..7789923b445 100644 --- a/code/modules/xenoarcheaology/finds/find_spawning.dm +++ b/code/modules/xenoarcheaology/finds/find_spawning.dm @@ -424,7 +424,7 @@ //robot remains apply_prefix = FALSE item_type = "[pick("mechanical","robotic","cyborg")] [pick("remains","chassis","debris")]" - icon = 'icons/mob/robots.dmi' + icon = 'icons/mob/robot_gibs.dmi' icon_state = "remainsrobot" additional_desc = pick("Almost mistakeable for the remains of a modern cyborg.",\ "They are barely recognisable as anything other than a pile of waste metals.",\ diff --git a/code/unit_tests/icon_tests.dm b/code/unit_tests/icon_tests.dm new file mode 100644 index 00000000000..787ef54a3b0 --- /dev/null +++ b/code/unit_tests/icon_tests.dm @@ -0,0 +1,124 @@ +/datum/unit_test/robot_module_icons_shall_be_valid + name = "ICONS: Robot module icons shall be valid" + var/list/check_module_categories = list( + ROBOT_MODULE_TYPE_GROUNDED, + ROBOT_MODULE_TYPE_FLYING + ) + var/list/panel_overlays = list( + "ov-openpanel +w", + "ov-openpanel +c", + "ov-openpanel -c" + ) + var/list/gear_to_check = list( + /obj/item/borg/combat/shield = "-shield", + /obj/item/borg/combat/mobility = "-roll" + ) + +/datum/unit_test/robot_module_icons_shall_be_valid/start_test() + + var/list/failures = list() + // fetch our icon states to check against + var/list/icon_state_cache = list( + ROBOT_MODULE_TYPE_GROUNDED = icon_states('icons/mob/robots/robots_grounded.dmi'), + ROBOT_MODULE_TYPE_FLYING = icon_states('icons/mob/robots/robots_flying.dmi') + ) + + // We need a robot to properly initialize the module, which is somewhat unfortunate. + var/list/found_states = list() // Keep track of this for checking for unused states later. + for(var/module_type in typesof(/obj/item/robot_module)) + + // Skip abstract modules and think-tanks as they do icon gen differently. + var/obj/item/robot_module/module = module_type + if(!initial(module.display_name) || !(initial(module.module_category) in check_module_categories)) + continue + + module = new module // this will automatically qdelete, but we just want the sprites. + + // Check that the expected states are actually in the icon file. + var/check_states = icon_state_cache[module.module_category] + for(var/sprite in module.sprites) + + // Basic sprite. + var/check_state = module.sprites[sprite] + if(check_state in check_states) + LAZYDISTINCTADD(found_states[module.module_category], check_state) + else + failures += "missing base state '[check_state]' for [module.display_name] ([module.module_category])" + + // Eyes overlay. + var/eye_check_state = "eyes-[check_state]" + if(eye_check_state in check_states) + LAZYDISTINCTADD(found_states[module.module_category], eye_check_state) + else + failures += "missing eyes state '[eye_check_state]' for [module.display_name] ([module.module_category])" + + // Equipment overlays. + for(var/geartype in gear_to_check) + var/suffix = gear_to_check[geartype] + for(var/gear in module.modules) + if(!istype(gear, geartype)) + continue + var/gear_check_state = "[check_state][suffix]" + if(gear_check_state in check_states) + LAZYDISTINCTADD(found_states[module.module_category], gear_check_state) + else + failures += "missing gear state '[gear_check_state]' for [module.display_name] ([module.module_category])" + break + + // Check for missing panel states. + for(var/module_category in icon_state_cache) + var/list/check_states = icon_state_cache[module_category] + for(var/panel_state in panel_overlays) + if(panel_state in check_states) + LAZYDISTINCTADD(found_states[module_category], panel_state) + else + failures += "missing panel state '[panel_state]' for [module_category]" + + // Now we can do tachikoma sprites. + var/list/tachikoma_icon_states = list() + for(var/module_type in typesof(/obj/item/robot_module/robot/platform)) + // Skip abstract modules. + var/obj/item/robot_module/robot/platform/module = module_type + if(!initial(module.display_name) || !initial(module.module_category) || !initial(module.user_icon)) + continue + module = new module_type + if(!tachikoma_icon_states[module.user_icon]) + tachikoma_icon_states[module.user_icon] = icon_states(module.user_icon) + LAZYDISTINCTADD(icon_state_cache[ROBOT_MODULE_TYPE_PLATFORM], tachikoma_icon_states[module.user_icon]) + + var/list/states_to_check = list( + module.user_icon_state, + "[module.user_icon_state]-open", + "[module.user_icon_state]-wires", + "[module.user_icon_state]-cell", + "[module.user_icon_state]-nowires" + ) + if(module.armor_color) + states_to_check += "[module.user_icon_state]_armour" + for(var/decal in module.decals) + states_to_check += "[module.user_icon_state]_[decal]" + if(module.eye_color) + states_to_check += "[module.user_icon_state]_eyes" + if(module.pupil_color) + states_to_check += "[module.user_icon_state]_pupils" + + for(var/check_state in states_to_check) + if(check_state in tachikoma_icon_states[module.user_icon]) + found_states[ROBOT_MODULE_TYPE_PLATFORM] |= check_state + else + failures += "missing platform state '[check_state]' in icon file [module.user_icon]" + + // Check that there aren't any unexpected states. + for(var/module_category in icon_state_cache) + var/list/check_found_states = LAZYACCESS(found_states, module_category) + for(var/check_state in icon_state_cache[module_category]) + if(check_state in check_found_states) + continue + failures += "unexpected state '[check_state]' for [module_category]" + + + if(length(failures)) + fail("Some robot module sprites are invalid:\n" + failures.Join("\n")) + else + pass("All robot module sprites are valid.") + return 1 diff --git a/icons/mob/robot_gibs.dmi b/icons/mob/robot_gibs.dmi new file mode 100644 index 00000000000..79ccbf73703 Binary files /dev/null and b/icons/mob/robot_gibs.dmi differ diff --git a/icons/mob/robots.dmi b/icons/mob/robots.dmi deleted file mode 100644 index bd622976411..00000000000 Binary files a/icons/mob/robots.dmi and /dev/null differ diff --git a/icons/mob/robots/robot_unused.dmi b/icons/mob/robots/robot_unused.dmi new file mode 100644 index 00000000000..17dde9de5c9 Binary files /dev/null and b/icons/mob/robots/robot_unused.dmi differ diff --git a/icons/mob/robots/robots_flying.dmi b/icons/mob/robots/robots_flying.dmi new file mode 100644 index 00000000000..21c99eda3ed Binary files /dev/null and b/icons/mob/robots/robots_flying.dmi differ diff --git a/icons/mob/robots/robots_grounded.dmi b/icons/mob/robots/robots_grounded.dmi new file mode 100644 index 00000000000..2637ed83597 Binary files /dev/null and b/icons/mob/robots/robots_grounded.dmi differ diff --git a/icons/mob/robots_thinktank.dmi b/icons/mob/robots/robots_platform.dmi similarity index 100% rename from icons/mob/robots_thinktank.dmi rename to icons/mob/robots/robots_platform.dmi diff --git a/icons/mob/robots_flying.dmi b/icons/mob/robots_flying.dmi deleted file mode 100644 index 45cdf4b77d5..00000000000 Binary files a/icons/mob/robots_flying.dmi and /dev/null differ diff --git a/polaris.dme b/polaris.dme index 8778127a6ea..89e903b9631 100644 --- a/polaris.dme +++ b/polaris.dme @@ -3317,6 +3317,7 @@ #include "code\modules\xgm\xgm_gas_data.dm" #include "code\modules\xgm\xgm_gas_mixture.dm" #include "code\unit_tests\decl_tests.dm" +#include "code\unit_tests\icon_tests.dm" #include "code\unit_tests\language_tests.dm" #include "code\unit_tests\loadout_tests.dm" #include "code\unit_tests\map_tests.dm"