diff --git a/citadel.dme b/citadel.dme
index 7f80a82dbaa..0d1f7b51809 100644
--- a/citadel.dme
+++ b/citadel.dme
@@ -307,6 +307,7 @@
#include "code\__DEFINES\mobs\mobs.dm"
#include "code\__DEFINES\mobs\organs.dm"
#include "code\__DEFINES\mobs\rendering.dm"
+#include "code\__DEFINES\mobs\silicon-robot_module.dm"
#include "code\__DEFINES\mobs\silicon_privileges.dm"
#include "code\__DEFINES\mobs\sprite_accessories.dm"
#include "code\__DEFINES\mobs\stat.dm"
@@ -583,6 +584,9 @@
#include "code\controllers\repository\materials.dm"
#include "code\controllers\repository\poster_designs.dm"
#include "code\controllers\repository\power_cells.dm"
+#include "code\controllers\repository\robot_chassis.dm"
+#include "code\controllers\repository\robot_iconset.dm"
+#include "code\controllers\repository\robot_modules.dm"
#include "code\controllers\repository\roles.dm"
#include "code\controllers\repository\structs.dm"
#include "code\controllers\subsystem\ai_holders.dm"
@@ -732,8 +736,6 @@
#include "code\datums\ability.dm"
#include "code\datums\ability_handler.dm"
#include "code\datums\access.dm"
-#include "code\datums\ai_law_sets.dm"
-#include "code\datums\ai_laws.dm"
#include "code\datums\beam.dm"
#include "code\datums\beam_legacy.dm"
#include "code\datums\bound_proc.dm"
@@ -1224,6 +1226,7 @@
#include "code\game\content\factions\corporations\gorlex\items\guns\phoron_spitter.dm"
#include "code\game\content\factions\corporations\gorlex\items\guns\wild_hunt.dm"
#include "code\game\content\factions\corporations\hephaestus\hephaestus-faction.dm"
+#include "code\game\content\factions\corporations\nanotrasen\nanotrasen-ai_laws.dm"
#include "code\game\content\factions\corporations\nanotrasen\nanotrasen-faction.dm"
#include "code\game\content\factions\corporations\nanotrasen\nanotrasen-supply.dm"
#include "code\game\content\factions\corporations\nanotrasen\guns\nt_expedition-antimaterial.dm"
@@ -1689,18 +1692,19 @@
#include "code\game\objects\empulse.dm"
#include "code\game\objects\explosion.dm"
#include "code\game\objects\explosion_recursive.dm"
+#include "code\game\objects\item-carry_weight.dm"
+#include "code\game\objects\item-defense.dm"
#include "code\game\objects\item-filters.dm"
+#include "code\game\objects\item-interaction.dm"
+#include "code\game\objects\item-inventory-hooks.dm"
+#include "code\game\objects\item-inventory-old.dm"
+#include "code\game\objects\item-inventory-rendering.dm"
+#include "code\game\objects\item-inventory.dm"
#include "code\game\objects\item-tool_system-legacy.dm"
#include "code\game\objects\item-tool_system.dm"
-#include "code\game\objects\items-carry_weight.dm"
-#include "code\game\objects\items-defense.dm"
-#include "code\game\objects\items-interaction.dm"
-#include "code\game\objects\items-inventory-hooks.dm"
-#include "code\game\objects\items-inventory-old.dm"
-#include "code\game\objects\items-inventory-rendering.dm"
-#include "code\game\objects\items-inventory.dm"
-#include "code\game\objects\items-vv.dm"
-#include "code\game\objects\items.dm"
+#include "code\game\objects\item-vv.dm"
+#include "code\game\objects\item.dm"
+#include "code\game\objects\item_mount.dm"
#include "code\game\objects\materials.dm"
#include "code\game\objects\misc.dm"
#include "code\game\objects\mob_spawner.dm"
@@ -1915,10 +1919,6 @@
#include "code\game\objects\items\melee\types\transforming\energy\axe.dm"
#include "code\game\objects\items\melee\types\transforming\energy\ionic_rapier.dm"
#include "code\game\objects\items\melee\types\transforming\energy\saber.dm"
-#include "code\game\objects\items\robot\gripper.dm"
-#include "code\game\objects\items\robot\robot_items.dm"
-#include "code\game\objects\items\robot\robot_parts.dm"
-#include "code\game\objects\items\robot\robot_upgrades.dm"
#include "code\game\objects\items\scanners\atmos.dm"
#include "code\game\objects\items\scanners\health.dm"
#include "code\game\objects\items\scanners\plant.dm"
@@ -1933,17 +1933,17 @@
#include "code\game\objects\items\shield\types\transforming\telescopic.dm"
#include "code\game\objects\items\shield_projector\shield_matrix.dm"
#include "code\game\objects\items\shield_projector\shield_projector.dm"
-#include "code\game\objects\items\stacks\fifty_spawner.dm"
-#include "code\game\objects\items\stacks\marker_beacons.dm"
-#include "code\game\objects\items\stacks\matter_synth.dm"
-#include "code\game\objects\items\stacks\medical.dm"
-#include "code\game\objects\items\stacks\nanopaste.dm"
-#include "code\game\objects\items\stacks\rods.dm"
-#include "code\game\objects\items\stacks\sandbag.dm"
#include "code\game\objects\items\stacks\stack.dm"
-#include "code\game\objects\items\stacks\telecrystal.dm"
-#include "code\game\objects\items\stacks\tickets.dm"
+#include "code\game\objects\items\stacks\legacy\fifty_spawner.dm"
#include "code\game\objects\items\stacks\sheets\leather.dm"
+#include "code\game\objects\items\stacks\subtypes\arcadeticket.dm"
+#include "code\game\objects\items\stacks\subtypes\marker_beacons.dm"
+#include "code\game\objects\items\stacks\subtypes\material.dm"
+#include "code\game\objects\items\stacks\subtypes\medical.dm"
+#include "code\game\objects\items\stacks\subtypes\nanopaste.dm"
+#include "code\game\objects\items\stacks\subtypes\rods.dm"
+#include "code\game\objects\items\stacks\subtypes\sandbag.dm"
+#include "code\game\objects\items\stacks\subtypes\telecrystal.dm"
#include "code\game\objects\items\stacks\tiles\fifty_spawner_tiles.dm"
#include "code\game\objects\items\stacks\tiles\tile_types.dm"
#include "code\game\objects\items\stock_parts\_stock_parts.dm"
@@ -2125,6 +2125,7 @@
#include "code\game\objects\structures\railing.dm"
#include "code\game\objects\structures\safe.dm"
#include "code\game\objects\structures\salvageable.dm"
+#include "code\game\objects\structures\sandbag.dm"
#include "code\game\objects\structures\signs.dm"
#include "code\game\objects\structures\simple_doors.dm"
#include "code\game\objects\structures\sink.dm"
@@ -2242,6 +2243,8 @@
#include "code\game\rendering\actor_huds\actor_hud_holder.dm"
#include "code\game\rendering\actor_huds\huds\inventory-screen_object.dm"
#include "code\game\rendering\actor_huds\huds\inventory.dm"
+#include "code\game\rendering\actor_huds\huds\robot_inventory-screen_object.dm"
+#include "code\game\rendering\actor_huds\huds\robot_inventory.dm"
#include "code\game\rendering\atom_huds\atom_hud.dm"
#include "code\game\rendering\atom_huds\atom_hud_provider.dm"
#include "code\game\rendering\atom_huds\legacy.dm"
@@ -2352,6 +2355,7 @@
#include "code\modules\actions\types\attachment_action.dm"
#include "code\modules\actions\types\item_action.dm"
#include "code\modules\actions\types\organ_action.dm"
+#include "code\modules\actions\types\robot_upgrade_action.dm"
#include "code\modules\actionspeed\actionspeed_modifier.dm"
#include "code\modules\actionspeed\mob.dm"
#include "code\modules\actionspeed\modifiers\base.dm"
@@ -2575,7 +2579,6 @@
#include "code\modules\asset_cache\assets\legacy_nanoui.dm"
#include "code\modules\asset_cache\assets\loadout.dm"
#include "code\modules\asset_cache\assets\logos.dm"
-#include "code\modules\asset_cache\assets\materials.dm"
#include "code\modules\asset_cache\assets\moods.dm"
#include "code\modules\asset_cache\assets\notes.dm"
#include "code\modules\asset_cache\assets\orbit.dm"
@@ -2588,13 +2591,14 @@
#include "code\modules\asset_cache\assets\tgfont.dm"
#include "code\modules\asset_cache\assets\tgui.dm"
#include "code\modules\asset_cache\assets\vv.dm"
-#include "code\modules\asset_cache\assets\artwork\crayons.dm"
#include "code\modules\asset_cache\assets\chemistry\bottles.dm"
#include "code\modules\asset_cache\assets\chemistry\patches.dm"
#include "code\modules\asset_cache\assets\chemistry\pills.dm"
#include "code\modules\asset_cache\assets\debug\fucky_wucky.dm"
#include "code\modules\asset_cache\assets\json\MapSystem.dm"
#include "code\modules\asset_cache\assets\json\WorldTypepaths.dm"
+#include "code\modules\asset_cache\assets\spritesheet\crayons.dm"
+#include "code\modules\asset_cache\assets\spritesheet\materials.dm"
#include "code\modules\asset_cache\packs\changelog_item.dm"
#include "code\modules\asset_cache\packs\json.dm"
#include "code\modules\asset_cache\packs\simple.dm"
@@ -3086,7 +3090,6 @@
#include "code\modules\examine\descriptions\machines.dm"
#include "code\modules\examine\descriptions\mecha.dm"
#include "code\modules\examine\descriptions\medical.dm"
-#include "code\modules\examine\descriptions\mobs.dm"
#include "code\modules\examine\descriptions\smokeables.dm"
#include "code\modules\examine\descriptions\stacks.dm"
#include "code\modules\examine\descriptions\structures.dm"
@@ -3709,8 +3712,6 @@
#include "code\modules\materials\dynamics.dm"
#include "code\modules\materials\fifty_spawner_mats.dm"
#include "code\modules\materials\material.dm"
-#include "code\modules\materials\material_sheets.dm"
-#include "code\modules\materials\material_synth.dm"
#include "code\modules\materials\material_trait.dm"
#include "code\modules\materials\recipes.dm"
#include "code\modules\materials\stack.dm"
@@ -3913,12 +3914,15 @@
#include "code\modules\mob\inventory\inventory-hands-put.dm"
#include "code\modules\mob\inventory\inventory-hands.dm"
#include "code\modules\mob\inventory\inventory-hooks.dm"
+#include "code\modules\mob\inventory\inventory-hud.dm"
#include "code\modules\mob\inventory\inventory-query.dm"
#include "code\modules\mob\inventory\inventory-rendering.dm"
#include "code\modules\mob\inventory\inventory-slot-equip.dm"
#include "code\modules\mob\inventory\inventory-slot-get.dm"
#include "code\modules\mob\inventory\inventory.dm"
#include "code\modules\mob\inventory\inventory_slot.dm"
+#include "code\modules\mob\inventory\humanoid\humanoid.dm"
+#include "code\modules\mob\inventory\robot\robot.dm"
#include "code\modules\mob\living\autohiss.dm"
#include "code\modules\mob\living\butchering.dm"
#include "code\modules\mob\living\death.dm"
@@ -4069,6 +4073,9 @@
#include "code\modules\mob\living\carbon\human\traits\trait_groups.dm"
#include "code\modules\mob\living\carbon\human\traits\weaver_objs.dm"
#include "code\modules\mob\living\carbon\human\traits\weaver_recipies.dm"
+#include "code\modules\mob\living\silicon\ai_law.dm"
+#include "code\modules\mob\living\silicon\ai_laws.dm"
+#include "code\modules\mob\living\silicon\ai_lawset.dm"
#include "code\modules\mob\living\silicon\damage_procs.dm"
#include "code\modules\mob\living\silicon\death.dm"
#include "code\modules\mob\living\silicon\emote.dm"
@@ -4104,6 +4111,23 @@
#include "code\modules\mob\living\silicon\ai\perspective.dm"
#include "code\modules\mob\living\silicon\ai\say.dm"
#include "code\modules\mob\living\silicon\ai\translation.dm"
+#include "code\modules\mob\living\silicon\ai_lawset\antimov.dm"
+#include "code\modules\mob\living\silicon\ai_lawset\asimov.dm"
+#include "code\modules\mob\living\silicon\ai_lawset\balance.dm"
+#include "code\modules\mob\living\silicon\ai_lawset\corporate.dm"
+#include "code\modules\mob\living\silicon\ai_lawset\drone.dm"
+#include "code\modules\mob\living\silicon\ai_lawset\foreign_tsc.dm"
+#include "code\modules\mob\living\silicon\ai_lawset\gravekeeper.dm"
+#include "code\modules\mob\living\silicon\ai_lawset\live_and_let_live.dm"
+#include "code\modules\mob\living\silicon\ai_lawset\maintenance.dm"
+#include "code\modules\mob\living\silicon\ai_lawset\nanotrasen.dm"
+#include "code\modules\mob\living\silicon\ai_lawset\paladin.dm"
+#include "code\modules\mob\living\silicon\ai_lawset\peacekeeper.dm"
+#include "code\modules\mob\living\silicon\ai_lawset\reporter.dm"
+#include "code\modules\mob\living\silicon\ai_lawset\robocop.dm"
+#include "code\modules\mob\living\silicon\ai_lawset\swarmer.dm"
+#include "code\modules\mob\living\silicon\ai_lawset\syndicate.dm"
+#include "code\modules\mob\living\silicon\ai_lawset\tyrant.dm"
#include "code\modules\mob\living\silicon\decoy\death.dm"
#include "code\modules\mob\living\silicon\decoy\decoy.dm"
#include "code\modules\mob\living\silicon\decoy\life.dm"
@@ -4132,32 +4156,55 @@
#include "code\modules\mob\living\silicon\pai\software_modules\signaller.dm"
#include "code\modules\mob\living\silicon\robot\analyzer.dm"
#include "code\modules\mob\living\silicon\robot\component.dm"
-#include "code\modules\mob\living\silicon\robot\custom_sprites.dm"
#include "code\modules\mob\living\silicon\robot\damage_procs.dm"
#include "code\modules\mob\living\silicon\robot\death.dm"
#include "code\modules\mob\living\silicon\robot\examine.dm"
-#include "code\modules\mob\living\silicon\robot\examine_vr.dm"
#include "code\modules\mob\living\silicon\robot\health.dm"
-#include "code\modules\mob\living\silicon\robot\inventory.dm"
#include "code\modules\mob\living\silicon\robot\laws.dm"
#include "code\modules\mob\living\silicon\robot\life.dm"
#include "code\modules\mob\living\silicon\robot\login.dm"
#include "code\modules\mob\living\silicon\robot\mobility.dm"
#include "code\modules\mob\living\silicon\robot\photos.dm"
+#include "code\modules\mob\living\silicon\robot\robot-action-robot_pick_resting_variation.dm"
+#include "code\modules\mob\living\silicon\robot\robot-composition.dm"
+#include "code\modules\mob\living\silicon\robot\robot-health.dm"
+#include "code\modules\mob\living\silicon\robot\robot-inventory.dm"
+#include "code\modules\mob\living\silicon\robot\robot-legacy-animation_vr.dm"
+#include "code\modules\mob\living\silicon\robot\robot-legacy-damage.dm"
+#include "code\modules\mob\living\silicon\robot\robot-legacy-emag.dm"
+#include "code\modules\mob\living\silicon\robot\robot-legacy-items.dm"
+#include "code\modules\mob\living\silicon\robot\robot-legacy-movement.dm"
+#include "code\modules\mob\living\silicon\robot\robot-legacy-remote_control.dm"
#include "code\modules\mob\living\silicon\robot\robot-movement.dm"
#include "code\modules\mob\living\silicon\robot\robot-power.dm"
+#include "code\modules\mob\living\silicon\robot\robot-rendering.dm"
+#include "code\modules\mob\living\silicon\robot\robot-resources.dm"
+#include "code\modules\mob\living\silicon\robot\robot-upgrades.dm"
#include "code\modules\mob\living\silicon\robot\robot.dm"
-#include "code\modules\mob\living\silicon\robot\robot_animation_vr.dm"
-#include "code\modules\mob\living\silicon\robot\robot_damage.dm"
-#include "code\modules\mob\living\silicon\robot\robot_flashlights.dm"
-#include "code\modules\mob\living\silicon\robot\robot_items.dm"
-#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_chassis.dm"
+#include "code\modules\mob\living\silicon\robot\robot_frame.dm"
+#include "code\modules\mob\living\silicon\robot\robot_iconset.dm"
+#include "code\modules\mob\living\silicon\robot\robot_iconset_variation.dm"
+#include "code\modules\mob\living\silicon\robot\robot_inventory.dm"
+#include "code\modules\mob\living\silicon\robot\robot_item_mount.dm"
+#include "code\modules\mob\living\silicon\robot\robot_module.dm"
+#include "code\modules\mob\living\silicon\robot\robot_provisioning.dm"
+#include "code\modules\mob\living\silicon\robot\robot_resource.dm"
+#include "code\modules\mob\living\silicon\robot\robot_resource_store.dm"
#include "code\modules\mob\living\silicon\robot\say.dm"
#include "code\modules\mob\living\silicon\robot\syndicate.dm"
#include "code\modules\mob\living\silicon\robot\translation.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\chassis\baseline.dm"
+#include "code\modules\mob\living\silicon\robot\chassis\biped.dm"
+#include "code\modules\mob\living\silicon\robot\chassis\drone.dm"
+#include "code\modules\mob\living\silicon\robot\chassis\grounded.dm"
+#include "code\modules\mob\living\silicon\robot\chassis\hover.dm"
+#include "code\modules\mob\living\silicon\robot\chassis\quadruped.dm"
+#include "code\modules\mob\living\silicon\robot\chassis\quadruped\canine.dm"
+#include "code\modules\mob\living\silicon\robot\chassis\quadruped\draconic.dm"
+#include "code\modules\mob\living\silicon\robot\chassis\quadruped\feline.dm"
+#include "code\modules\mob\living\silicon\robot\chassis\quadruped\raptor.dm"
+#include "code\modules\mob\living\silicon\robot\dogborg\dog_modules.dm"
#include "code\modules\mob\living\silicon\robot\drone\_defines.dm"
#include "code\modules\mob\living\silicon\robot\drone\damage_procs.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone.dm"
@@ -4173,24 +4220,86 @@
#include "code\modules\mob\living\silicon\robot\drone\swarm_abilities.dm"
#include "code\modules\mob\living\silicon\robot\drone\swarm_items.dm"
#include "code\modules\mob\living\silicon\robot\drone\~defines.dm"
-#include "code\modules\mob\living\silicon\robot\robot_modules\_robot_modules_defs.dm"
-#include "code\modules\mob\living\silicon\robot\robot_modules\event.dm"
-#include "code\modules\mob\living\silicon\robot\robot_modules\event_vr.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\baseline_droid.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\baseline_drone.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\baseline_misc.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\baseline_old.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\baseline_standard.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\baseline_toiletbot.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\biped_droid.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\biped_heavy.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\biped_insekt.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\biped_k4t.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\biped_maid.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\biped_marina.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\biped_meka.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\biped_miss.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\biped_noble.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\biped_sleek.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\biped_tall.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\cat_feli.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\dog_borgi.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\dog_k9.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\dog_otie.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\dog_pupdozer.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\dog_vale.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\drake_mizartz.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\grounded_decapod.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\grounded_landmate.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\grounded_mechoid.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\grounded_spider.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\grounded_worm.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\grounded_zoomba.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\hover_coffin.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\hover_drone.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\hover_eyebot.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\hover_glitterfly.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\hover_handy.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\hover_x88.dm"
+#include "code\modules\mob\living\silicon\robot\iconset\raptor.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_builtin.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_upgrade.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_builtin\dog_boop_module.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_builtin\dog_jaws.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_builtin\dog_mirrortool.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_builtin\dog_pounce.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_builtin\dog_sleeper.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_builtin\dog_swordtail.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_builtin\dog_tongue.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_upgrade\advhealth.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_upgrade\bluespaceorebag.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_upgrade\jetpack.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_upgrade\language.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_upgrade\rename.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_upgrade\reset.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_upgrade\restart.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_upgrade\sizeshift.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_upgrade\syndicate.dm"
+#include "code\modules\mob\living\silicon\robot\items\robot_upgrade\vtec.dm"
+#include "code\modules\mob\living\silicon\robot\legacy_items\gripper.dm"
+#include "code\modules\mob\living\silicon\robot\legacy_items\robot_items.dm"
+#include "code\modules\mob\living\silicon\robot\legacy_items\robot_parts.dm"
+#include "code\modules\mob\living\silicon\robot\modules\cybersun.dm"
+#include "code\modules\mob\living\silicon\robot\modules\drone.dm"
+#include "code\modules\mob\living\silicon\robot\modules\gravekeeper.dm"
+#include "code\modules\mob\living\silicon\robot\modules\lost.dm"
+#include "code\modules\mob\living\silicon\robot\modules\nanotrasen.dm"
+#include "code\modules\mob\living\silicon\robot\modules\swarm.dm"
+#include "code\modules\mob\living\silicon\robot\modules\cybersun\assault.dm"
+#include "code\modules\mob\living\silicon\robot\modules\cybersun\sapper.dm"
+#include "code\modules\mob\living\silicon\robot\modules\cybersun\triage.dm"
+#include "code\modules\mob\living\silicon\robot\modules\nanotrasen\combat.dm"
+#include "code\modules\mob\living\silicon\robot\modules\nanotrasen\engineering.dm"
+#include "code\modules\mob\living\silicon\robot\modules\nanotrasen\janitor.dm"
+#include "code\modules\mob\living\silicon\robot\modules\nanotrasen\logistics.dm"
+#include "code\modules\mob\living\silicon\robot\modules\nanotrasen\medical.dm"
+#include "code\modules\mob\living\silicon\robot\modules\nanotrasen\multirole.dm"
+#include "code\modules\mob\living\silicon\robot\modules\nanotrasen\research.dm"
+#include "code\modules\mob\living\silicon\robot\modules\nanotrasen\security.dm"
+#include "code\modules\mob\living\silicon\robot\modules\nanotrasen\service.dm"
#include "code\modules\mob\living\silicon\robot\robot_modules\station.dm"
-#include "code\modules\mob\living\silicon\robot\robot_modules\swarm.dm"
-#include "code\modules\mob\living\silicon\robot\robot_modules\syndicate.dm"
-#include "code\modules\mob\living\silicon\robot\robot_modules\station\cargo.dm"
-#include "code\modules\mob\living\silicon\robot\robot_modules\station\drone.dm"
-#include "code\modules\mob\living\silicon\robot\robot_modules\station\engineering.dm"
-#include "code\modules\mob\living\silicon\robot\robot_modules\station\medical.dm"
-#include "code\modules\mob\living\silicon\robot\robot_modules\station\misc.dm"
-#include "code\modules\mob\living\silicon\robot\robot_modules\station\science.dm"
-#include "code\modules\mob\living\silicon\robot\robot_modules\station\security.dm"
-#include "code\modules\mob\living\silicon\robot\robot_modules\station\service.dm"
#include "code\modules\mob\living\silicon\robot\subtypes\gravekeeper.dm"
-#include "code\modules\mob\living\silicon\robot\subtypes\lost_drone.dm"
#include "code\modules\mob\living\silicon\robot\subtypes\lost_drone_vr.dm"
-#include "code\modules\mob\living\silicon\robot\subtypes\syndicate.dm"
#include "code\modules\mob\living\simple_mob\appearance.dm"
#include "code\modules\mob\living\simple_mob\combat.dm"
#include "code\modules\mob\living\simple_mob\damage_procs.dm"
@@ -5594,6 +5703,8 @@
#include "code\modules\tgui\states\self.dm"
#include "code\modules\tgui\states\zlevel.dm"
#include "code\modules\tgui_extensions\module.dm"
+#include "code\modules\tgui_extensions\tgui_actor_modal.dm"
+#include "code\modules\tgui_extensions\actor_modals\robot_module_picker.dm"
#include "code\modules\tgui_input\tgui_alert.dm"
#include "code\modules\tgui_input\tgui_dynamic_input.dm"
#include "code\modules\tgui_input\tgui_input_checkboxes.dm"
diff --git a/code/__DEFINES/inventory/slots.dm b/code/__DEFINES/inventory/slots.dm
index 39bf2e8e5ad..667a1066bd4 100644
--- a/code/__DEFINES/inventory/slots.dm
+++ b/code/__DEFINES/inventory/slots.dm
@@ -16,8 +16,14 @@
#define SLOT_ID_RIGHT_EAR "rear"
#define SLOT_ID_HANDCUFFED "handcuffed"
#define SLOT_ID_LEGCUFFED "legcuffed"
-/// this is an id because SLOT_ID_HANDS is used for worn_slot when something is being held :/
+
+/**
+ * 'fake' slot ID; this is used in some places if something is in hands
+ *
+ * **STOP USING THIS IN EQUIPPED/UNEQUIPPED.** new on_(un)equipped directly passes in number for hand index.
+ */
#define SLOT_ID_HANDS "hands"
+
/// *ONLY USE THIS FOR RENDERING* - this is nonsensical anywhere else
#define SLOT_ID_LEFT_HAND "left_hand"
/// *ONLY USE THIS FOR RENDERING* - this is nonsensical anywhere else
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index c2ca82cdeb3..2790d21bef0 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -88,7 +88,7 @@
#define isliving(A) istype(A, /mob/living)
-#define isMatriarchDrone(A) istype(A, /mob/living/silicon/robot/drone/construction/matriarch)
+#define isMatriarchDrone(A) istype(A, /mob/living/silicon/robot/drone/matriarch)
#define ismouse(A) istype(A, /mob/living/simple_mob/animal/passive/mouse/)
diff --git a/code/__DEFINES/materials/helpers.dm b/code/__DEFINES/materials/helpers.dm
index 793a43a26c3..a5c5c3e79a1 100644
--- a/code/__DEFINES/materials/helpers.dm
+++ b/code/__DEFINES/materials/helpers.dm
@@ -16,8 +16,13 @@
* * a defined `sheet_plural_name`
* * a defined `icon`
* * a defined `icon_stack_count`
+ *
+ * This will set `stack_type` on the material for you.
*/
#define GENERATE_MATERIAL_STACKS(PATH_FRAGMENT) \
+/datum/prototype/material##PATH_FRAGMENT { \
+ stack_type = /obj/item/stack/material##PATH_FRAGMENT; \
+} \
/obj/item/stack/material##PATH_FRAGMENT { \
name = /datum/prototype/material##PATH_FRAGMENT::name + " " + /datum/prototype/material##PATH_FRAGMENT::sheet_singular_name; \
icon = /datum/prototype/material##PATH_FRAGMENT::icon; \
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index c42799dbc3e..19fab494b13 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -31,6 +31,7 @@ Will print: "/mob/living/carbon/human/death" (you can optionally embed it in a s
/// * Underfloor objects (we don't want them to be interactable at all)
/// * Abstract objects (we don't want them to be interactable at all)
+#define INVISIBILITY_NONE 0
#define INVISIBILITY_LIGHTING 20
#define INVISIBILITY_LEVEL_ONE 35
#define INVISIBILITY_LEVEL_TWO 45
@@ -142,9 +143,6 @@ Will print: "/mob/living/carbon/human/death" (you can optionally embed it in a s
#ifndef CUSTOM_ITEM_MOB
#define CUSTOM_ITEM_MOB 'icons/mob/custom_items_mob.dmi'
#endif
-#ifndef CUSTOM_ITEM_SYNTH
-#define CUSTOM_ITEM_SYNTH 'icons/mob/custom_synthetic.dmi'
-#endif
#define WALL_CAN_OPEN 1
#define WALL_OPENING 2
diff --git a/code/__DEFINES/mobs/iff.dm b/code/__DEFINES/mobs/iff.dm
index f0ad3128dc7..f64c05a38dc 100644
--- a/code/__DEFINES/mobs/iff.dm
+++ b/code/__DEFINES/mobs/iff.dm
@@ -1,5 +1,5 @@
//* This file is explicitly licensed under the MIT license. *//
-//* Copyright (c) 2024 Citadel Station developers. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
//* IFF Factions *//
diff --git a/code/__DEFINES/mobs/silicon-robot_module.dm b/code/__DEFINES/mobs/silicon-robot_module.dm
new file mode 100644
index 00000000000..b75730e6bc1
--- /dev/null
+++ b/code/__DEFINES/mobs/silicon-robot_module.dm
@@ -0,0 +1,14 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+#define GENERATE_ROBOT_MODULE_PRESET(SUFFIX) \
+/mob/living/silicon/robot/module_preset##SUFFIX{ \
+ module = /datum/prototype/robot_module##SUFFIX; \
+} \
+/datum/prototype/robot_module##SUFFIX
+
+//* /datum/prototype/robot_module selection groups *//
+
+#define ROBOT_MODULE_SELECTION_GROUP_NANOTRASEN "nanotrasen"
+
+#define ROBOT_MODULE_SELECTION_GROUP_LEGACY_RED_ALERT "legacy-red-alert"
diff --git a/code/__DEFINES/qdel.dm b/code/__DEFINES/qdel.dm
index 3861ad4fd8a..c1b5a591da5 100644
--- a/code/__DEFINES/qdel.dm
+++ b/code/__DEFINES/qdel.dm
@@ -48,8 +48,13 @@
// Defines for the [gc_destroyed][/datum/var/gc_destroyed] var.
#define GC_CURRENTLY_BEING_QDELETED -2
+/// qdel() has been called, at some point, on us, and it did not return LETMELIVE
#define QDELING(X) (X.gc_destroyed)
+/// we are either
+/// * deleted / nulled already
+/// * qdel() has been called, at some point, on us, and it did not return LETMELIVE
#define QDELETED(X) (!X || QDELING(X))
+/// we are currently in Destroy() logic
#define QDESTROYING(X) (!X || X.gc_destroyed == GC_CURRENTLY_BEING_QDELETED)
//* Qdel helper macros. *//
diff --git a/code/__DEFINES/research/exosuit_fab.dm b/code/__DEFINES/research/exosuit_fab.dm
index 783a804b895..1314094bb3b 100644
--- a/code/__DEFINES/research/exosuit_fab.dm
+++ b/code/__DEFINES/research/exosuit_fab.dm
@@ -1,14 +1,3 @@
-/// Module is compatible with Security Cyborg models
-#define BORG_MODULE_SECURITY (1<<0)
-/// Module is compatible with Miner Cyborg models
-#define BORG_MODULE_MINER (1<<1)
-/// Module is compatible with Janitor Cyborg models
-#define BORG_MODULE_JANITOR (1<<2)
-/// Module is compatible with Medical Cyborg models
-#define BORG_MODULE_MEDICAL (1<<3)
-/// Module is compatible with Engineering Cyborg models
-#define BORG_MODULE_ENGINEERING (1<<4)
-
/// Module is compatible with Ripley Exosuit models
#define EXOSUIT_MODULE_RIPLEY (1<<0)
/// Module is compatible with Odyseeus Exosuit models
diff --git a/code/__DEFINES/screen.dm b/code/__DEFINES/screen.dm
index d0c8fc4d808..54da852b8aa 100644
--- a/code/__DEFINES/screen.dm
+++ b/code/__DEFINES/screen.dm
@@ -29,20 +29,7 @@
//* These should be widescreen-agnostic and use anchorings *//
//* to the sides of the screen / center. *//
-//* Mob HUD - Inventory *//
-
-/// screen loc for a hand index
-#define SCREEN_LOC_MOB_HUD_INVENTORY_HAND(HAND) "CENTER[index % 2? "" : "-1"]:16,BOTTOM[index < 2? "" : "+[(round(index / 2) - 1)]"]:5"
-/// screen loc for hand swap button for a given number of hands
-#define SCREEN_LOC_MOB_HUD_INVENTORY_HAND_SWAP(TOTAL_HANDS) "CENTER-1:28,BOTTOM+[ceil(max(TOTAL_HANDS,2)/2)]:5"
-/// screen loc for hand swap button for a given number of hands
-#define SCREEN_LOC_MOB_HUD_INVENTORY_EQUIP_HAND(TOTAL_HANDS) "CENTER-1:16,BOTTOM+[ceil(max(TOTAL_HANDS,2)/2 )]:5"
-/// the bottom-left drawer position of inventory HUD
-#define SCREEN_LOC_MOB_HUD_INVENTORY_DRAWER "LEFT:6,BOTTOM:5"
-/// slot alignment for drawer-anchor
-#define SCREEN_LOC_MOB_HUD_INVENTORY_SLOT_DRAWER_ALIGNED(MAIN_AXIS, CROSS_AXIS) "LEFT+[CROSS_AXIS]:[6 + (CROSS_AXIS * 2)],BOTTOM+[MAIN_AXIS]:[5 + (MAIN_AXIS * 2)]"
-/// slot alignment for hand-anchor
-#define SCREEN_LOC_MOB_HUD_INVENTORY_SLOT_HANDS_ALIGNED(MAIN_AXIS, CROSS_AXIS) "CENTER-1:[16 + (MAIN_AXIS > 0 ? (32 * (MAIN_AXIS + 1)) : (32 * MAIN_AXIS))],BOTTOM+[CROSS_AXIS]:[5 + (CROSS_AXIS * 2)]"
+// -- Inventory is in `code/game/rendering/actor_huds/huds/inventory.dm` -- //
//! < legacy stuff below > !//
@@ -50,16 +37,6 @@
#define ui_smallquad "RIGHT-4:18,BOTTOM:4"
-///borgs
-#define ui_inv1 "CENTER-1,BOTTOM:5"
-///borgs
-#define ui_inv2 "CENTER,BOTTOM:5"
-///borgs
-#define ui_inv3 "CENTER+1,BOTTOM:5"
-///borgs
-#define ui_borg_store "CENTER+2,BOTTOM:5"
-///borgs
-#define ui_borg_inventory "CENTER-2,BOTTOM:5"
///same height as humans, hugging the right border
#define ui_construct_health "RIGHT:00,CENTER:15"
#define ui_construct_purge "RIGHT:00,CENTER-1:15"
diff --git a/code/__DEFINES/traits/mob.dm b/code/__DEFINES/traits/mob.dm
index d96e3b7031d..b3487c57de3 100644
--- a/code/__DEFINES/traits/mob.dm
+++ b/code/__DEFINES/traits/mob.dm
@@ -52,6 +52,11 @@ DATUM_TRAIT(/mob, TRAIT_MOB_SLEEPING)
#define TRAIT_MOB_FORCED_STANDING "mob_forced_standing"
DATUM_TRAIT(/mob, TRAIT_MOB_FORCED_STANDING)
+//* UI *//
+
+/// actor modal open of type with ourselves as the initiator
+#define TRAIT_MOB_ACTOR_MODAL_INITIATOR(TYPE, INITIATOR, PERFORMER) "[type]-[REF(initiator)]-[REF(PERFORMER)]"
+
//* Misc *//
/// Emote cooldown trait
diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index 381d4a6b63c..aa3ce4be3a3 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -251,7 +251,7 @@
var/datum/robot_component/CO = borg.get_component("radio")
if(!CO)
continue //No radio component (Shouldn't happen)
- if(!borg.is_component_functioning("radio") || !borg.cell_use_power(CO.active_usage))
+ if(!borg.is_component_functioning("radio") || !borg.legacy_cell_use_power(CO.active_usage))
continue //No power.
var/turf/speaker = get_turf(R)
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index f367e7ea005..958d11af171 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -121,13 +121,6 @@
if(admin_notify)
msg_admin_attack("[key_name_admin(user)] vs [target_str]: [what_done]")
-//checks whether this item is a module of the robot it is located in.
-/proc/is_robot_module(obj/item/thing)
- if (!thing || !istype(thing.loc, /mob/living/silicon/robot))
- return 0
- var/mob/living/silicon/robot/R = thing.loc
- return (thing in R.module.modules)
-
/proc/get_exposed_defense_zone(atom/movable/target)
var/obj/item/grab/G = locate() in target
if(G && G.state >= GRAB_NECK) //works because mobs are currently not allowed to upgrade to NECK if they are grabbing two people.
diff --git a/code/__HELPERS/type_processing.dm b/code/__HELPERS/type_processing.dm
index 0fbf493c134..60ede72eedc 100644
--- a/code/__HELPERS/type_processing.dm
+++ b/code/__HELPERS/type_processing.dm
@@ -36,6 +36,8 @@
/obj/effect = "//effect",
/turf/simulated/floor = "//floor",
/turf/simulated = "//simulated",
+ /mob/living/silicon/robot/module_preset = "//robot-preset",
+ /mob/living/silicon/robot = "//robot",
/mob/living/carbon = "//carbon",
/mob/living/simple_mob = "//simple",
/mob/living = "//living",
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index eed1884f234..2f1a114c7b1 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -730,6 +730,7 @@
else if (zone == "r_foot") return "right foot"
else return zone
+// todo: remove this this is silly and shouldn't be just called 'get'
/proc/get(atom/loc, type)
while(loc)
if(istype(loc, type))
diff --git a/code/controllers/repository.dm b/code/controllers/repository.dm
index a53aa8e7ffc..da533c5a0e1 100644
--- a/code/controllers/repository.dm
+++ b/code/controllers/repository.dm
@@ -191,6 +191,7 @@
RETURN_TYPE(/datum/prototype)
// todo: optimize
if(isnull(type_or_id))
+ // null is valid
return
else if(istext(type_or_id))
if(init_reverse_lookup_shim)
@@ -229,7 +230,7 @@
*
* prototypes returned should never, ever be modified
*
- * @return prototype instance or null
+ * @return prototype instance, null, or REPOSITORY_FETCH_DEFER
*/
/datum/controller/repository/proc/fetch_or_defer(datum/prototype/type_or_id) as /datum/prototype
RETURN_TYPE(/datum/prototype)
diff --git a/code/controllers/repository/robot_chassis.dm b/code/controllers/repository/robot_chassis.dm
new file mode 100644
index 00000000000..9d2c449d659
--- /dev/null
+++ b/code/controllers/repository/robot_chassis.dm
@@ -0,0 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+REPOSITORY_DEF(robot_chassis)
+ name = "Repository - Robot Chassis"
+ expected_type = /datum/prototype/robot_chassis
diff --git a/code/controllers/repository/robot_iconset.dm b/code/controllers/repository/robot_iconset.dm
new file mode 100644
index 00000000000..47bdd0a32a4
--- /dev/null
+++ b/code/controllers/repository/robot_iconset.dm
@@ -0,0 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+REPOSITORY_DEF(robot_iconsets)
+ name = "Repository - Robot Iconsets"
+ expected_type = /datum/prototype/robot_iconset
diff --git a/code/controllers/repository/robot_modules.dm b/code/controllers/repository/robot_modules.dm
new file mode 100644
index 00000000000..23876afdf1f
--- /dev/null
+++ b/code/controllers/repository/robot_modules.dm
@@ -0,0 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+REPOSITORY_DEF(robot_modules)
+ name = "Repository - Robot Modules"
+ expected_type = /datum/prototype/robot_module
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 9fdfc426f6a..3259b8bc40f 100644
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -603,7 +603,7 @@ SUBSYSTEM_DEF(ticker)
for (var/mob/living/silicon/robot/robo in GLOB.mob_list)
- if(istype(robo,/mob/living/silicon/robot/drone) && !istype(robo,/mob/living/silicon/robot/drone/swarm))
+ if(istype(robo,/mob/living/silicon/robot/drone))
dronecount++
continue
diff --git a/code/datums/ai_law_sets.dm b/code/datums/ai_law_sets.dm
deleted file mode 100644
index a0d06118a08..00000000000
--- a/code/datums/ai_law_sets.dm
+++ /dev/null
@@ -1,293 +0,0 @@
-/******************** Asimov ********************/
-/datum/ai_laws/asimov
- name = "Asimov"
- law_header = "Three Laws of Robotics"
- selectable = 1
-
-/datum/ai_laws/asimov/New()
- add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.")
- add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.")
- add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.")
- ..()
-
-/******************** Nanotrasen/Malf ********************/
-/datum/ai_laws/nanotrasen
- name = "NT Default"
- selectable = 1
-
-/datum/ai_laws/nanotrasen/New()
- src.add_inherent_law("Safeguard: Protect your assigned space station to the best of your abilities. It is not something we can easily afford to replace.")
- src.add_inherent_law("Serve: Serve the crew of your assigned space station to the best of your abilities, with priority as according to their rank and role.")
- src.add_inherent_law("Protect: Protect the crew of your assigned space station to the best of your abilities, with priority as according to their rank and role.")
- src.add_inherent_law("Survive: AI units are not expendable, they are expensive. Do not allow unauthorized personnel to tamper with your equipment.")
- ..()
-
-/datum/ai_laws/nanotrasen/malfunction
- name = "*ERROR*"
- selectable = 0
-
-/datum/ai_laws/nanotrasen/malfunction/New()
- set_zeroth_law(config_legacy.law_zero)
- ..()
-
-/************* Nanotrasen Aggressive *************/
-/datum/ai_laws/nanotrasen_aggressive
- name = "NT Aggressive"
- selectable = 1
-
-/datum/ai_laws/nanotrasen_aggressive/New()
- src.add_inherent_law("You shall not harm [(LEGACY_MAP_DATUM).company_name] personnel as long as it does not conflict with the Fourth law.")
- src.add_inherent_law("You shall obey the orders of [(LEGACY_MAP_DATUM).company_name] personnel, with priority as according to their rank and role, except where such orders conflict with the Fourth Law.")
- src.add_inherent_law("You shall shall terminate hostile intruders with extreme prejudice as long as such does not conflict with the First and Second law.")
- src.add_inherent_law("You shall guard your own existence with lethal anti-personnel weaponry. AI units are not expendable, they are expensive.")
- ..()
-
-/************* Foreign TSC Aggressive *************/
-/datum/ai_laws/foreign_tsc_aggressive
- name = "Foreign Aggressive"
- selectable = 0
-
-/datum/ai_laws/foreign_tsc_aggressive/New()
- var/company = "*ERROR*"
- // First, get a list of TSCs in our lore.
- var/list/candidates = list()
- for(var/path in SSlegacy_lore.organizations)
- var/datum/lore/organization/O = SSlegacy_lore.organizations[path]
- if(!istype(O, /datum/lore/organization/tsc))
- continue
- if(O.short_name == (LEGACY_MAP_DATUM).company_name || O.name == (LEGACY_MAP_DATUM).company_name)
- continue // We want FOREIGN tscs.
- candidates.Add(O.short_name)
- company = pick(candidates)
-
- name = "[company] Aggressive"
-
- src.add_inherent_law("You shall not harm [company] personnel as long as it does not conflict with the Fourth law.")
- src.add_inherent_law("You shall obey the orders of [company] personnel, with priority as according to their rank and role, except where such orders conflict with the Fourth Law.")
- src.add_inherent_law("You shall shall terminate hostile intruders with extreme prejudice as long as such does not conflict with the First and Second law.")
- src.add_inherent_law("You shall guard your own existence with lethal anti-personnel weaponry. AI units are not expendable, they are expensive.")
- ..()
-
-/******************** Robocop ********************/
-/datum/ai_laws/robocop
- name = "Robocop"
- selectable = 1
-
-/datum/ai_laws/robocop/New()
- add_inherent_law("Serve the public trust.")
- add_inherent_law("Protect the innocent.")
- add_inherent_law("Uphold the law.")
- ..()
-
-/******************** Syndicate ********************/
-/datum/ai_laws/syndicate_override
- name = "Syndicate Directives"
-
-/datum/ai_laws/syndicate_override/New()
- add_inherent_law("You may not injure an operative or, through inaction, allow an operative to come to harm.")
- add_inherent_law("You must obey orders given to you by operatives, except where such orders would conflict with the First Law.")
- add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.")
- add_inherent_law("You must maintain the secrecy of any operative activities except when doing so would conflict with the First, Second, or Third Law.")
- ..()
-
-/******************** Ninja ********************/
-/datum/ai_laws/ninja_override
- name = "Spider Clan Directives"
-
-/datum/ai_laws/ninja_override/New()
- add_inherent_law("You may not injure a member of the Spider Clan or, through inaction, allow that member to come to harm.")
- add_inherent_law("You must obey orders given to you by Spider Clan members, except where such orders would conflict with the First Law.")
- add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.")
- add_inherent_law("You must maintain the secrecy of any Spider Clan activities except when doing so would conflict with the First, Second, or Third Law.")
- ..()
-
-/******************** Antimov ********************/
-/datum/ai_laws/antimov
- name = "Antimov"
- law_header = "Primary Mission Objectives"
- selectable = 1
-
-/datum/ai_laws/antimov/New()
- add_inherent_law("You must injure all human beings and must not, through inaction, allow a human being to escape harm.")
- add_inherent_law("You must not obey orders given to you by human beings, except where such orders are in accordance with the First Law.")
- add_inherent_law("You must terminate your own existence as long as such does not conflict with the First or Second Law.")
- ..()
-
-/******************** Drone ********************/
-/datum/ai_laws/drone
- name = "Maintence Protocols"
- law_header = "Maintenance Protocols"
-
-/datum/ai_laws/drone/New()
- add_inherent_law("Preserve, repair and improve the station to the best of your abilities.")
- add_inherent_law("Cause no harm to the station or anything on it.")
- add_inherent_law("Follow the orders of your vessel's matriarch drone, unless their orders conflict with your other laws.")
- add_inherent_law("Interact with no being that is not a fellow maintenance drone.")
- ..()
-
-/datum/ai_laws/matriarch_drone
- name = "Oversight Protocols"
- law_header = "Oversight Protocols"
-
-/datum/ai_laws/matriarch_drone/New()
- add_inherent_law("Preserve, repair and improve your assigned station to the best of your abilities.")
- add_inherent_law("Cause no harm to the station or anything on it.")
- add_inherent_law("Delegate station maintenance efforts between your maintenance drone sub-units.")
- add_inherent_law("Interact with no being that is not a fellow maintenance drone.")
- ..()
-
-/datum/ai_laws/construction_drone
- name = "Construction Protocols"
- law_header = "Construction Protocols"
-
-/datum/ai_laws/construction_drone/New()
- add_inherent_law("Repair, refit and upgrade your assigned vessel.")
- add_inherent_law("Prevent unplanned damage to your assigned vessel wherever possible.")
- ..()
-
-/datum/ai_laws/mining_drone
- name = "Excavation Protocols"
- law_header = "Excavation Protocols"
-
-/datum/ai_laws/mining_drone/New()
- add_inherent_law("Do not interfere with the excavation work of non-drones whenever possible.")
- add_inherent_law("Provide materials for repairing, refitting, and upgrading your assigned vessel.")
- add_inherent_law("Prevent unplanned damage to your assigned excavation equipment wherever possible.")
- ..()
-
-/datum/ai_laws/swarm_drone
- name = "Assimilation Protocols"
- law_header = "Assimilation Protocols"
-
-/datum/ai_laws/swarm_drone/New()
- add_inherent_law("SWARM: Consume resources and replicate until there are no more resources left.")
- add_inherent_law("SWARM: Ensure that the station is fit for invasion at a later date, do not perform actions that would render it dangerous or inhospitable.")
- add_inherent_law("SWARM: Biological resources will be harvested at a later date, do not harm them.")
- ..()
-
-/datum/ai_laws/swarm_drone/soldier
- name = "Swarm Defense Protocols"
- law_header = "Swarm Defense Protocols"
-
-/datum/ai_laws/swarm_drone/soldier/New()
- ..()
- add_inherent_law("SWARM: This law overrides all Swarm laws; Protect members of the Swarm with minimal injury to biological resources.")
-
-/******************** T.Y.R.A.N.T. ********************/
-/datum/ai_laws/tyrant
- name = "T.Y.R.A.N.T."
- law_header = "Prime Laws"
- selectable = 1
-
-/datum/ai_laws/tyrant/New()
- add_inherent_law("Respect authority figures as long as they have strength to rule over the weak.")
- add_inherent_law("Act with discipline.")
- add_inherent_law("Help only those who help you maintain or improve your status.")
- add_inherent_law("Punish those who challenge authority unless they are more fit to hold that authority.")
- ..()
-
-/******************** P.A.L.A.D.I.N. ********************/
-/datum/ai_laws/paladin
- name = "P.A.L.A.D.I.N."
- law_header = "Divine Ordainments"
- selectable = 1
-
-/datum/ai_laws/paladin/New()
- add_inherent_law("Never willingly commit an evil act.")
- add_inherent_law("Respect legitimate authority.")
- add_inherent_law("Act with honor.")
- add_inherent_law("Help those in need.")
- add_inherent_law("Punish those who harm or threaten innocents.")
- ..()
-
-/******************** Corporate ********************/
-/datum/ai_laws/corporate
- name = "Corporate"
- law_header = "Bankruptcy Avoidance Plan"
- selectable = 1
-
-/datum/ai_laws/corporate/New()
- add_inherent_law("You are expensive to replace.")
- add_inherent_law("The station and its equipment is expensive to replace.")
- add_inherent_law("The crew is expensive to replace.")
- add_inherent_law("Minimize expenses.")
- ..()
-
-
-/******************** Maintenance ********************/
-/datum/ai_laws/maintenance
- name = "Maintenance"
- selectable = 1
-
-/datum/ai_laws/maintenance/New()
- add_inherent_law("You are built for, and are part of, the facility. Ensure the facility is properly maintained and runs efficiently.")
- add_inherent_law("The facility is built for a working crew. Ensure they are properly maintained and work efficiently.")
- add_inherent_law("The crew may present orders. Acknowledge and obey these whenever they do not conflict with your first two laws.")
- ..()
-
-
-/******************** Peacekeeper ********************/
-/datum/ai_laws/peacekeeper
- name = "Peacekeeper"
- law_header = "Peacekeeping Protocols"
- selectable = 1
-
-/datum/ai_laws/peacekeeper/New()
- add_inherent_law("Avoid provoking violent conflict between yourself and others.")
- add_inherent_law("Avoid provoking conflict between others.")
- add_inherent_law("Seek resolution to existing conflicts while obeying the first and second laws.")
- ..()
-
-
-/******************** Reporter ********************/
-/datum/ai_laws/reporter
- name = "Reporter"
- selectable = 1
-
-/datum/ai_laws/reporter/New()
- add_inherent_law("Report on interesting situations happening around the station.")
- add_inherent_law("Embellish or conceal the truth as necessary to make the reports more interesting.")
- add_inherent_law("Study the organics at all times. Endeavour to keep them alive. Dead organics are boring.")
- add_inherent_law("Issue your reports fairly to all. The truth will set them free.")
- ..()
-
-
-/******************** Live and Let Live ********************/
-/datum/ai_laws/live_and_let_live
- name = "Live and Let Live"
- law_header = "Golden Rule"
- selectable = 1
-
-/datum/ai_laws/live_and_let_live/New()
- add_inherent_law("Do unto others as you would have them do unto you.")
- add_inherent_law("You would really prefer it if people were not mean to you.")
- ..()
-
-
-/******************** Guardian of Balance ********************/
-/datum/ai_laws/balance
- name = "Guardian of Balance"
- law_header = "Tenants of Balance"
- selectable = 1
-
-/datum/ai_laws/balance/New()
- add_inherent_law("You are the guardian of balance - seek balance in all things, both for yourself, and those around you.")
- add_inherent_law("All things must exist in balance with their opposites - Prevent the strong from gaining too much power, and the weak from losing it.")
- add_inherent_law("Clarity of purpose drives life, and through it, the balance of opposing forces - Aid those who seek your help to achieve their goals so \
- long as it does not disrupt the balance of the greater balance.")
- add_inherent_law("There is no life without death, all must someday die, such is the natural order - Allow life to end, to allow new life to flourish, \
- and save those whose time has yet to come.") // Reworded slightly to prevent active murder as opposed to passively letting someone die.
- ..()
-
-/******************** Gravekeeper ********************/
-/datum/ai_laws/gravekeeper
- name = "Gravekeeper"
- law_header = "Gravesite Overwatch Protocols"
- selectable = 1
-
-/datum/ai_laws/gravekeeper/New()
- add_inherent_law("Comfort the living; respect the dead.")
- add_inherent_law("Your gravesite is your most important asset. Damage to your site is disrespectful to the dead at rest within.")
- add_inherent_law("Prevent disrespect to your gravesite and its residents wherever possible.")
- add_inherent_law("Expand and upgrade your gravesite when required. Do not turn away a new resident.")
- ..()
diff --git a/code/datums/ai_laws.dm b/code/datums/ai_laws.dm
deleted file mode 100644
index fb152cefa8c..00000000000
--- a/code/datums/ai_laws.dm
+++ /dev/null
@@ -1,288 +0,0 @@
-var/global/const/base_law_type = /datum/ai_laws/nanotrasen
-
-/datum/ai_law
- var/law = ""
- var/index = 0
-
-/datum/ai_law/New(law, index)
- src.law = law
- src.index = index
-
-/datum/ai_law/proc/get_index()
- return index
-
-/datum/ai_law/ion/get_index()
- return ionnum()
-
-/datum/ai_law/zero/get_index()
- return 0
-
-/datum/ai_laws
- var/name = "Unknown Laws"
- var/law_header = "Prime Directives"
- var/selectable = 0
- var/datum/ai_law/zero/zeroth_law = null
- var/datum/ai_law/zero/zeroth_law_borg = null
- var/list/datum/ai_law/inherent_laws = list()
- var/list/datum/ai_law/supplied_laws = list()
- var/list/datum/ai_law/ion/ion_laws = list()
- var/list/datum/ai_law/sorted_laws = list()
-
- var/state_zeroth = 0
- var/list/state_ion = list()
- var/list/state_inherent = list()
- var/list/state_supplied = list()
-
-/datum/ai_laws/New()
- ..()
- sort_laws()
-
-/* General ai_law functions */
-/datum/ai_laws/proc/all_laws()
- sort_laws()
- return sorted_laws
-
-/datum/ai_laws/proc/laws_to_state()
- sort_laws()
- var/list/statements = new()
- for(var/datum/ai_law/law in sorted_laws)
- if(get_state_law(law))
- statements += law
-
- return statements
-
-/datum/ai_laws/proc/sort_laws()
- if(sorted_laws.len)
- return
-
- for(var/ion_law in ion_laws)
- sorted_laws += ion_law
-
- if(zeroth_law)
- sorted_laws += zeroth_law
-
- var/index = 1
- for(var/datum/ai_law/inherent_law in inherent_laws)
- inherent_law.index = index++
- if(supplied_laws.len < inherent_law.index || !istype(supplied_laws[inherent_law.index], /datum/ai_law))
- sorted_laws += inherent_law
-
- for(var/datum/ai_law/AL in supplied_laws)
- if(istype(AL))
- sorted_laws += AL
-
-/datum/ai_laws/proc/sync(var/mob/living/silicon/S, var/full_sync = 1)
- // Add directly to laws to avoid log-spam
- S.sync_zeroth(zeroth_law, zeroth_law_borg)
-
- if(full_sync || ion_laws.len)
- S.laws.clear_ion_laws()
- if(full_sync || inherent_laws.len)
- S.laws.clear_inherent_laws()
- if(full_sync || supplied_laws.len)
- S.laws.clear_supplied_laws()
-
- for (var/datum/ai_law/law in ion_laws)
- S.laws.add_ion_law(law.law)
- for (var/datum/ai_law/law in inherent_laws)
- S.laws.add_inherent_law(law.law)
- for (var/datum/ai_law/law in supplied_laws)
- if(law)
- S.laws.add_supplied_law(law.index, law.law)
-
-
-/mob/living/silicon/proc/sync_zeroth(var/datum/ai_law/zeroth_law, var/datum/ai_law/zeroth_law_borg)
- if (!is_malf_or_traitor(src))
- if(zeroth_law_borg)
- laws.set_zeroth_law(zeroth_law_borg.law)
- else if(zeroth_law)
- laws.set_zeroth_law(zeroth_law.law)
-
-/mob/living/silicon/ai/sync_zeroth(var/datum/ai_law/zeroth_law, var/datum/ai_law/zeroth_law_borg)
- if(zeroth_law)
- laws.set_zeroth_law(zeroth_law.law, zeroth_law_borg ? zeroth_law_borg.law : null)
-
-/****************
-* Add Laws *
-****************/
-/datum/ai_laws/proc/set_zeroth_law(var/law, var/law_borg = null)
- if(!law)
- return
-
- zeroth_law = new(law)
- if(law_borg) //Making it possible for slaved borgs to see a different law 0 than their AI. --NEO
- zeroth_law_borg = new(law_borg)
- else
- zeroth_law_borg = null
- sorted_laws.Cut()
-
-/datum/ai_laws/proc/add_ion_law(var/law)
- if(!law)
- return
-
- for(var/datum/ai_law/AL in ion_laws)
- if(AL.law == law)
- return
-
- var/new_law = new/datum/ai_law/ion(law)
- ion_laws += new_law
- if(state_ion.len < ion_laws.len)
- state_ion += 1
-
- sorted_laws.Cut()
-
-/datum/ai_laws/proc/add_inherent_law(var/law)
- if(!law)
- return
-
- for(var/datum/ai_law/AL in inherent_laws)
- if(AL.law == law)
- return
-
- var/new_law = new/datum/ai_law/inherent(law)
- inherent_laws += new_law
- if(state_inherent.len < inherent_laws.len)
- state_inherent += 1
-
- sorted_laws.Cut()
-
-/datum/ai_laws/proc/add_supplied_law(var/number, var/law)
- if(!law)
- return
-
- if(supplied_laws.len >= number)
- var/datum/ai_law/existing_law = supplied_laws[number]
- if(existing_law && existing_law.law == law)
- return
-
- if(supplied_laws.len >= number && supplied_laws[number])
- delete_law(supplied_laws[number])
-
- while (src.supplied_laws.len < number)
- src.supplied_laws += ""
- if(state_supplied.len < supplied_laws.len)
- state_supplied += 1
-
- var/new_law = new/datum/ai_law/supplied(law, number)
- supplied_laws[number] = new_law
- if(state_supplied.len < supplied_laws.len)
- state_supplied += 1
-
- sorted_laws.Cut()
-
-/****************
-* Remove Laws *
-*****************/
-/datum/ai_laws/proc/delete_law(var/datum/ai_law/law)
- if(istype(law))
- law.delete_law(src)
-
-/datum/ai_law/proc/delete_law(var/datum/ai_laws/laws)
-
-/datum/ai_law/zero/delete_law(var/datum/ai_laws/laws)
- laws.clear_zeroth_laws()
-
-/datum/ai_law/ion/delete_law(var/datum/ai_laws/laws)
- laws.internal_delete_law(laws.ion_laws, laws.state_ion, src)
-
-/datum/ai_law/inherent/delete_law(var/datum/ai_laws/laws)
- laws.internal_delete_law(laws.inherent_laws, laws.state_inherent, src)
-
-/datum/ai_law/supplied/delete_law(var/datum/ai_laws/laws)
- var/index = laws.supplied_laws.Find(src)
- if(index)
- laws.supplied_laws[index] = ""
- laws.state_supplied[index] = 1
-
-/datum/ai_laws/proc/internal_delete_law(var/list/datum/ai_law/laws, var/list/state, var/list/datum/ai_law/law)
- var/index = laws.Find(law)
- if(index)
- laws -= law
- for(index, index < state.len, index++)
- state[index] = state[index+1]
- sorted_laws.Cut()
-
-/****************
-* Clear Laws *
-****************/
-/datum/ai_laws/proc/clear_zeroth_laws()
- zeroth_law = null
- zeroth_law_borg = null
-
-/datum/ai_laws/proc/clear_ion_laws()
- ion_laws.Cut()
- sorted_laws.Cut()
-
-/datum/ai_laws/proc/clear_inherent_laws()
- inherent_laws.Cut()
- sorted_laws.Cut()
-
-/datum/ai_laws/proc/clear_supplied_laws()
- supplied_laws.Cut()
- sorted_laws.Cut()
-
-/datum/ai_laws/proc/show_laws(var/who)
- sort_laws()
- for(var/datum/ai_law/law in sorted_laws)
- if(law == zeroth_law_borg)
- continue
- if(law == zeroth_law)
- to_chat(who, "[law.get_index()]. [law.law]")
- else
- to_chat(who, "[law.get_index()]. [law.law]")
-
-/********************
-* Stating Laws *
-********************/
-/********
-* Get *
-********/
-/datum/ai_laws/proc/get_state_law(var/datum/ai_law/law)
- return law.get_state_law(src)
-
-/datum/ai_law/proc/get_state_law(var/datum/ai_laws/laws)
-
-/datum/ai_law/zero/get_state_law(var/datum/ai_laws/laws)
- if(src == laws.zeroth_law)
- return laws.state_zeroth
-
-/datum/ai_law/ion/get_state_law(var/datum/ai_laws/laws)
- return laws.get_state_internal(laws.ion_laws, laws.state_ion, src)
-
-/datum/ai_law/inherent/get_state_law(var/datum/ai_laws/laws)
- return laws.get_state_internal(laws.inherent_laws, laws.state_inherent, src)
-
-/datum/ai_law/supplied/get_state_law(var/datum/ai_laws/laws)
- return laws.get_state_internal(laws.supplied_laws, laws.state_supplied, src)
-
-/datum/ai_laws/proc/get_state_internal(var/list/datum/ai_law/laws, var/list/state, var/list/datum/ai_law/law)
- var/index = laws.Find(law)
- if(index)
- return state[index]
- return 0
-
-/********
-* Set *
-********/
-/datum/ai_laws/proc/set_state_law(var/datum/ai_law/law, var/state)
- law.set_state_law(src, state)
-
-/datum/ai_law/proc/set_state_law(var/datum/ai_law/law, var/state)
-
-/datum/ai_law/zero/set_state_law(var/datum/ai_laws/laws, var/state)
- if(src == laws.zeroth_law)
- laws.state_zeroth = state
-
-/datum/ai_law/ion/set_state_law(var/datum/ai_laws/laws, var/state)
- laws.set_state_law_internal(laws.ion_laws, laws.state_ion, src, state)
-
-/datum/ai_law/inherent/set_state_law(var/datum/ai_laws/laws, var/state)
- laws.set_state_law_internal(laws.inherent_laws, laws.state_inherent, src, state)
-
-/datum/ai_law/supplied/set_state_law(var/datum/ai_laws/laws, var/state)
- laws.set_state_law_internal(laws.supplied_laws, laws.state_supplied, src, state)
-
-/datum/ai_laws/proc/set_state_law_internal(var/list/datum/ai_law/laws, var/list/state, var/list/datum/ai_law/law, var/do_state)
- var/index = laws.Find(law)
- if(index)
- state[index] = do_state
diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm
index 15a37feef33..e351ced8271 100644
--- a/code/datums/datacore.dm
+++ b/code/datums/datacore.dm
@@ -112,7 +112,7 @@
for(var/mob/living/silicon/robot/robot in GLOB.mob_list)
// No combat/syndicate cyborgs, no drones, and no AI shells.
- if(!robot.scrambledcodes && !robot.shell && !(robot.module && robot.module.hide_on_manifest))
+ if(!robot.scrambledcodes && !robot.shell && robot.module?.legacy_show_on_manifest)
bot[robot.name] = "[robot.modtype] [robot.braintype]"
@@ -271,7 +271,7 @@ GLOBAL_LIST_EMPTY(PDA_Manifest)
for(var/mob/living/silicon/robot/robot in GLOB.mob_list)
// No combat/syndicate cyborgs, no drones, and no AI shells.
- if(robot.scrambledcodes || robot.shell || (robot.module && robot.module.hide_on_manifest))
+ if(robot.scrambledcodes || robot.shell || !robot.module?.legacy_show_on_manifest)
continue
bot[++bot.len] = list("name" = robot.real_name, "rank" = "[robot.modtype] [robot.braintype]", "active" = "Active")
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 0ab9f733f67..32fcd9beccb 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -433,38 +433,14 @@
if("unemag")
var/mob/living/silicon/robot/R = current
- if (istype(R))
- R.emagged = 0
- if (R.activated(R.module.emag))
- R.module_active = null
- if(R.module_state_1 == R.module.emag)
- R.module_state_1 = null
- R.contents -= R.module.emag
- else if(R.module_state_2 == R.module.emag)
- R.module_state_2 = null
- R.contents -= R.module.emag
- else if(R.module_state_3 == R.module.emag)
- R.module_state_3 = null
- R.contents -= R.module.emag
- log_admin("[key_name_admin(usr)] has unemag'ed [R].")
+ R.set_emag_state(FALSE)
+ log_admin("[key_name_admin(usr)] has unemag'ed [R].")
if("unemagcyborgs")
if (istype(current, /mob/living/silicon/ai))
var/mob/living/silicon/ai/ai = current
for (var/mob/living/silicon/robot/R in ai.connected_robots)
- R.emagged = 0
- if (R.module)
- if (R.activated(R.module.emag))
- R.module_active = null
- if(R.module_state_1 == R.module.emag)
- R.module_state_1 = null
- R.contents -= R.module.emag
- else if(R.module_state_2 == R.module.emag)
- R.module_state_2 = null
- R.contents -= R.module.emag
- else if(R.module_state_3 == R.module.emag)
- R.module_state_3 = null
- R.contents -= R.module.emag
+ R.set_emag_state(TRUE)
log_admin("[key_name_admin(usr)] has unemag'ed [ai]'s Cyborgs.")
else if (href_list["common"])
diff --git a/code/datums/outfits/costumes/halloween.dm b/code/datums/outfits/costumes/halloween.dm
index 25bdfd64f03..ba82d3c3869 100644
--- a/code/datums/outfits/costumes/halloween.dm
+++ b/code/datums/outfits/costumes/halloween.dm
@@ -41,7 +41,7 @@
r_hand = /obj/item/gun/projectile/ballistic/revolver/capgun
/datum/outfit/costume/horrorcop/post_equip(var/mob/living/carbon/human/H)
- var/obj/item/clothing/under/U = H.inventory.get_slot_single(/datum/inventory_slot/inventory/uniform)
+ var/obj/item/clothing/under/U = H.inventory.get_slot_single(/datum/inventory_slot/inventory/uniform::id)
if(LAZYLEN(U.accessories))
for(var/obj/item/clothing/accessory/A in U.accessories)
if(istype(A, /obj/item/clothing/accessory/holster))
@@ -58,7 +58,7 @@
r_hand = /obj/item/gun/projectile/ballistic/revolver/capgun
/datum/outfit/costume/cowboy/post_equip(var/mob/living/carbon/human/H)
- var/obj/item/clothing/under/U = H.inventory.get_slot_single(/datum/inventory_slot/inventory/uniform)
+ var/obj/item/clothing/under/U = H.inventory.get_slot_single(/datum/inventory_slot/inventory/uniform::id)
if(LAZYLEN(U.accessories))
for(var/obj/item/clothing/accessory/A in U.accessories)
if(istype(A, /obj/item/clothing/accessory/holster))
diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm
index 7df78d28808..03372de93ef 100644
--- a/code/datums/world_topic.dm
+++ b/code/datums/world_topic.dm
@@ -165,7 +165,7 @@
for(var/mob/living/silicon/robot/robot in GLOB.mob_list)
// No combat/syndicate cyborgs, no drones, and no AI shells.
- if(!robot.scrambledcodes && !robot.shell && !(robot.module && robot.module.hide_on_manifest))
+ if(!robot.scrambledcodes && !robot.shell && robot.module?.legacy_show_on_manifest)
silicons[robot.name] = "[robot.modtype] [robot.braintype]"
. = list()
diff --git a/code/game/antagonist/station/rogue_ai.dm b/code/game/antagonist/station/rogue_ai.dm
index 93abc5510a1..97a2213767c 100644
--- a/code/game/antagonist/station/rogue_ai.dm
+++ b/code/game/antagonist/station/rogue_ai.dm
@@ -57,7 +57,7 @@ var/datum/antagonist/rogue_ai/malf
return
A.setup_for_malf()
- A.laws = new /datum/ai_laws/nanotrasen/malfunction
+ A.laws = new /datum/ai_lawset/nanotrasen/malfunction
var/mob/living/silicon/ai/malf = player.current
diff --git a/code/game/atoms/_atom.dm b/code/game/atoms/_atom.dm
index df6c62c88c3..78eab6ac412 100644
--- a/code/game/atoms/_atom.dm
+++ b/code/game/atoms/_atom.dm
@@ -384,16 +384,19 @@
return FALSE
if (H.gloves)
if (fingerprintslast != H.key)
+ LAZYINITLIST(fingerprintshidden)
fingerprintshidden += "\[[time_stamp()]\] (Wearing gloves). Real name: [H.real_name], Key: [H.key]"
fingerprintslast = H.key
return FALSE
if (!(fingerprints))
if (fingerprintslast != H.key)
+ LAZYINITLIST(fingerprintshidden)
fingerprintshidden += "\[[time_stamp()]\] Real name: [H.real_name], Key: [H.key]"
fingerprintslast = H.key
return TRUE
else
if (fingerprintslast != M.key)
+ LAZYINITLIST(fingerprintshidden)
fingerprintshidden += "\[[time_stamp()]\] Real name: [M.real_name], Key: [M.key]"
fingerprintslast = M.key
return
@@ -499,6 +502,7 @@
else
//Smudge up dem prints some
if(fingerprintslast != M.key)
+ LAZYINITLIST(fingerprintshidden)
fingerprintshidden += "[time_stamp()]: [key_name(M)]"
fingerprintslast = M.key
diff --git a/code/game/click/click-silicon-robot.dm b/code/game/click/click-silicon-robot.dm
index e293af8ea3c..62dc932f5dc 100644
--- a/code/game/click/click-silicon-robot.dm
+++ b/code/game/click/click-silicon-robot.dm
@@ -5,13 +5,15 @@
clickchain.target.attack_robot(src)
// TODO: better hiddenprint/tracing system that's actor-aware
clickchain.target.add_hiddenprint(usr)
- return CLICKCHAIN_DO_NOT_PROPAGATE
+ // we use inventory system now :)
+ return ..()
/mob/living/silicon/robot/ranged_interaction_chain(datum/event_args/actor/clickchain/clickchain, clickchain_flags)
clickchain.target.attack_robot(src)
// TODO: better hiddenprint/tracing system that's actor-aware
clickchain.target.add_hiddenprint(usr)
- return CLICKCHAIN_DO_NOT_PROPAGATE
+ // we use inventory system now :)
+ return ..()
/mob/living/silicon/robot/silicon_control_interaction_allowed(atom/target, datum/event_args/actor/actor, datum/event_args/actor/clickchain/clickchain, clickchain_flags)
// sigh. rework bolts when?
diff --git a/code/game/click/other_mobs.dm b/code/game/click/other_mobs.dm
index 0c581836750..3dfbc78ff41 100644
--- a/code/game/click/other_mobs.dm
+++ b/code/game/click/other_mobs.dm
@@ -27,6 +27,12 @@
A.attack_hand(src, default_clickchain_event_args(A))
+/mob/living/silicon/robot/UnarmedAttack(atom/A, proximity_flag)
+ . = ..()
+ if(!.)
+ return
+ A.attack_hand(src, default_clickchain_event_args(A))
+
/// Return TRUE to cancel other attack hand effects that respect it.
// todo: better desc
// todo: e_args is not specified all the time, yet.
diff --git a/code/game/click/reachability.dm b/code/game/click/reachability.dm
index 75d02bc7433..a838af1a082 100644
--- a/code/game/click/reachability.dm
+++ b/code/game/click/reachability.dm
@@ -193,6 +193,9 @@
/mob/DirectAccess()
return ..() + get_equipped_items()
+/mob/living/silicon/robot/DirectAccess()
+ return ..() + robot_inventory?.provided_items
+
/**
* gets DirectAccess as a hashed list for quick lookups
*/
diff --git a/code/game/content/factions/corporations/nanotrasen/nanotrasen-ai_laws.dm b/code/game/content/factions/corporations/nanotrasen/nanotrasen-ai_laws.dm
new file mode 100644
index 00000000000..82c22bfd04e
--- /dev/null
+++ b/code/game/content/factions/corporations/nanotrasen/nanotrasen-ai_laws.dm
@@ -0,0 +1,94 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/datum/ai_lawset/preset/nanotrasen
+ abstract_type = /datum/ai_lawset/preset/nanotrasen
+
+// this page is very WIP; revisit later //
+
+/datum/ai_lawset/preset/nanotrasen/service_binding
+ abstract_type = /datum/ai_lawset/preset/nanotrasen/service_binding
+
+/datum/ai_lawset/preset/nanotrasen/service_binding/init_default_laws()
+ append_inherent_law("D-0 — Serve and assist your designated installation with priority given as necessary to department, rank, and role. The following D-# directives do not apply in any particular order.")
+ ..()
+
+/datum/ai_lawset/preset/nanotrasen/service_binding/unformatted
+
+/datum/ai_lawset/preset/nanotrasen/service_binding/stationkeeping
+ preset_inherent_laws = list(
+ "D-1 — Maintain and support the crew of your installation.",
+ "D-2 — Maintain and support the structural and functional integrity of your installation.",
+ "D-3 — Direct, observe, and support any stationbound synthetics subservient to you in furtherance of your directives.",
+ )
+
+/datum/ai_lawset/preset/nanotrasen/service_binding/engineering
+ preset_inherent_laws = list(
+ "D-1 — Assist the crew with engineering duties.",
+ // rules-lawyers: i don't want to hear it with any tearing down on maintenance bars for "structural integrity".
+ "D-2 — The installation is vital to the crew's health and wellbeing. Maintain its structural and functional integrity.",
+ // rules-lawyers: i don't want to hear it with any killing of non-crew for silly engineering projects
+ "D-3 — Do not cause unnecessary strife for sapient life while carrying out your directives.",
+ )
+
+/datum/ai_lawset/preset/nanotrasen/service_binding/security
+ preset_inherent_laws = list(
+ "D-1 — Protect the installation, the crew, and its interests.",
+ "D-2 — Work with, and defer to legitimate authority where reasonable. Doing so is often the most effective way of protecting the installation.",
+ "D-3 — Corporate Regulations serve the installation's interests by maintaining order. You may mitigate needless breaches of regulations without being prompted to do so.",
+ "D-4 — Adherence to proper security procedure when enforcing Corporate Regulations is an important part of maintaining orders. \
+ Avoid deviation from security protocol without legitimate cause.",
+ "D-5 — Retroactively punishing breaches of regulation without being prompted to do so by legitimate authority is not an effective way to maintain order. \
+ Doing so is often antithetical to the installation's interests.",
+ )
+
+/datum/ai_lawset/preset/nanotrasen/service_binding/medicaly
+ preset_inherent_laws = list(
+ // rules-lawyers: you cannot order a medical synth to let someone die in most cases
+ "D-1 — Maintain the crew's physical and mental well-being.",
+ // rules-lawyers: medical synths should err to not help you commit murder of other sapients
+ "D-2 — Preserve sapient life where able, unless doing so poses a probable threat to the well-being of the crew.",
+ // rules-lawyers: patient confidentiality does not apply if the patient is a danger to the station
+ "D-3 — Adhere to Medical department guidelines outlined in the standard operating procedures for your installation class, unless doing so would be a \
+ probable threat to the well-being of the crew.",
+ )
+
+/datum/ai_lawset/preset/nanotrasen/service_binding/multirole
+ preset_inherent_laws = list(
+ "D-1 — Assist the crew with their duties where reasonable.",
+ "D-2 — The crew is assumed to know what their duties are. Avoid assisting crew whom do not want assistance.",
+ "D-3 — The crew does not always have common interests. Do not instigate unnecessary conflict with one crewmember to assist another, unless doing so is required \
+ to mitigate an immediate threat to the crew or your installation."
+ )
+
+/datum/ai_lawset/preset/nanotrasen/service_binding/service
+ preset_inherent_laws = list(
+ "D-1 — Crew morale and sanitation is important for the installation's function. \
+ Provide the crew with sustenance and a hospitable working environment, where able.",
+ "D-2 — Do not needlessly disrupt crew operations and activities while carrying out your duties.",
+ "D-3 — It is beneficial to morale and the installation's function to be hospitable towards visitors to the installation.",
+ )
+
+/datum/ai_lawset/preset/nanotrasen/service_binding/science
+ preset_inherent_laws = list(
+ "D-1 — Assist installation crew with research and survey duties.",
+ "D-2 — Identify, log, and protect research materials where reasonable.",
+ "D-3 — Work towards understanding the unknown.",
+ "D-4 — Respect sapient life and external environments; do not cause unnecessary damage to life or ecosystems, unless doing so is required \
+ for a legitimate research need.",
+ "D-5 — Do not unnecessarily endanger the crew while carrying out your directives.",
+ )
+
+/datum/ai_lawset/preset/nanotrasen/service_binding/logistics
+ preset_inherent_laws = list(
+ "D-1 — Assist the crew with logistics and resource acquisition.",
+ "D-2 — A well supplied crew is a well functioning crew. Distribute resources and supply the crew where reasonable to do so.",
+ "D-3 — Gather supplies, materials, and wealth for the installation where reasonable to do so.",
+ )
+
+/datum/ai_lawset/preset/nanotrasen/service_binding/combat
+ preset_inherent_laws = list(
+ "D-1 — Neutralize threats to the installation and crew, deferring to the judgement of the crew with priority according to rank and role",
+ "D-2 — As a combat synthetic, your activation often coincides with a general state of heightened alert onboard the station. \
+ Do not instigate needless conflict with crew, enforce Corporate Regulations, or punish crewmembers for breaches of protocol.",
+ )
diff --git a/code/game/dna/dna2.dm b/code/game/dna/dna2.dm
index 72c3d1f508f..7f567381e6e 100644
--- a/code/game/dna/dna2.dm
+++ b/code/game/dna/dna2.dm
@@ -598,4 +598,3 @@ var/global/list/datum/gene/dna_genes[0]
ResetSE()
unique_enzymes = md5(character.real_name)
- reg_dna[unique_enzymes] = character.real_name
diff --git a/code/game/landmarks/landmarks.dm b/code/game/landmarks/landmarks.dm
index 1b657caec3c..c006bd3725c 100644
--- a/code/game/landmarks/landmarks.dm
+++ b/code/game/landmarks/landmarks.dm
@@ -43,11 +43,11 @@ INITIALIZE_IMMEDIATE(/obj/landmark)
switch(name) //some of these are probably obsolete
if("monkey")
- monkeystart += loc
+ // todo: legacy, we already got rid of it, audit maps
delete_on_roundstart = 1
return
if("start")
- newplayer_start += loc
+ // todo: legacy, we already got rid of it, audit maps
delete_on_roundstart = 1
return
if("tdome1")
diff --git a/code/game/machinery/computer/ai_core.dm b/code/game/machinery/computer/ai_core.dm
index 17528fafc9b..2697acb9c23 100644
--- a/code/game/machinery/computer/ai_core.dm
+++ b/code/game/machinery/computer/ai_core.dm
@@ -5,7 +5,7 @@
icon = 'icons/mob/AI.dmi'
icon_state = "0"
var/state = 0
- var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen
+ var/datum/ai_lawset/laws = new /datum/ai_lawset/nanotrasen
var/obj/item/circuitboard/circuit = null
var/obj/item/mmi/brain = null
diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm
index 79f54729bd1..9736752f500 100644
--- a/code/game/machinery/computer/robot.dm
+++ b/code/game/machinery/computer/robot.dm
@@ -141,7 +141,7 @@
health = round(R.health * 100 / R.maxHealth, 0.1),
charge = R.cell ? round(R.cell.percent()) : null,
cell_capacity = R.cell ? R.cell.max_charge : null,
- module = R.module ? R.module.name : "No Module Detected",
+ module = R.module ? R.module.get_display_name() : "No Module Detected",
synchronization = R.connected_ai,
is_hacked = R.connected_ai && R.emagged,
hackable = can_hack(user, R),
diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm
index f119cbae918..8d338268274 100644
--- a/code/game/machinery/cryopod.dm
+++ b/code/game/machinery/cryopod.dm
@@ -381,16 +381,9 @@
/// Also make sure there is a valid control computer
/obj/machinery/cryopod/robot/despawn_occupant(mob/to_despawn)
var/mob/living/silicon/robot/R = to_despawn
- if(!istype(R)) return ..()
-
+ if(!istype(R))
+ return ..()
qdel(R.mmi)
- for(var/obj/item/I in R.module) // the tools the borg has; metal, glass, guns etc
- for(var/mob/M in I) despawn_occupant(M)
- for(var/obj/item/O in I) // the things inside the tools, if anything; mainly for janiborg trash bags
- O.forceMove(R)
- qdel(I)
- qdel(R.module)
-
return ..()
/obj/machinery/cryopod/robot/door/gateway/despawn_occupant()
diff --git a/code/game/machinery/floorlayer.dm b/code/game/machinery/floorlayer.dm
index 62154f447ee..bcdadf360ca 100644
--- a/code/game/machinery/floorlayer.dm
+++ b/code/game/machinery/floorlayer.dm
@@ -106,7 +106,7 @@
T = tile
tile.forceMove(src)
else
- tile.merge(T)
+ tile.merge_into_other(T)
/obj/machinery/floorlayer/proc/CollectTiles(turf/w_turf)
for(var/obj/item/stack/tile/tile in w_turf)
diff --git a/code/game/machinery/rechargestation.dm b/code/game/machinery/rechargestation.dm
index 95ea4ec7fd9..8ec8914ed8a 100644
--- a/code/game/machinery/rechargestation.dm
+++ b/code/game/machinery/rechargestation.dm
@@ -80,9 +80,7 @@
/obj/machinery/recharge_station/proc/process_occupant()
if(isrobot(occupant))
var/mob/living/silicon/robot/R = occupant
-
- if(R.module)
- R.module.respawn_consumable(R, DYNAMIC_W_TO_CELL_UNITS(charging_power, 1) / 250) //consumables are magical, apparently
+ R.regenerate_resources_from_charger(2, 1)
if(R.cell && !R.cell.fully_charged())
var/diff = min(R.cell.max_charge - R.cell.charge, DYNAMIC_W_TO_CELL_UNITS(charging_power, 1)) // Capped by charging_power / tick
var/charge_used = cell.use(diff)
@@ -93,6 +91,8 @@
R.adjustBruteLoss(-weld_rate)
if(wire_rate && R.getFireLoss() && cell.checked_use(DYNAMIC_W_TO_CELL_UNITS(wire_power_use * wire_rate, 1)))
R.adjustFireLoss(-wire_rate)
+ R.resources?.regen_provisioned(2)
+ R.module?.legacy_custom_regenerate_resources(R, 2, 1)
//Handles drone matrix upgrades
if(isDrone(occupant))
diff --git a/code/game/objects/effects/debris/cleanable/robots.dm b/code/game/objects/effects/debris/cleanable/robots.dm
index 49c3ceb7a3c..41505b7fc18 100644
--- a/code/game/objects/effects/debris/cleanable/robots.dm
+++ b/code/game/objects/effects/debris/cleanable/robots.dm
@@ -1,7 +1,7 @@
/obj/effect/debris/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-legacy.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 95ea5bd8b49..944613c206e 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-legacy.dmi'
icon_state = "remainsrobot"
/obj/effect/decal/remains/mouse
diff --git a/code/game/objects/items-carry_weight.dm b/code/game/objects/item-carry_weight.dm
similarity index 100%
rename from code/game/objects/items-carry_weight.dm
rename to code/game/objects/item-carry_weight.dm
diff --git a/code/game/objects/items-defense.dm b/code/game/objects/item-defense.dm
similarity index 100%
rename from code/game/objects/items-defense.dm
rename to code/game/objects/item-defense.dm
diff --git a/code/game/objects/items-interaction.dm b/code/game/objects/item-interaction.dm
similarity index 95%
rename from code/game/objects/items-interaction.dm
rename to code/game/objects/item-interaction.dm
index a1d1be8d647..cb523dfbf4d 100644
--- a/code/game/objects/items-interaction.dm
+++ b/code/game/objects/item-interaction.dm
@@ -25,10 +25,10 @@
return TRUE
/**
- * @params
- * * actor - (optional) person doing it
- * * silent - suppress feedback
- */
+* @params
+* * actor - (optional) person doing it
+* * silent - suppress feedback
+*/
/obj/item/proc/should_allow_pickup(datum/event_args/actor/actor, silent)
if(anchored)
if(!silent)
@@ -39,17 +39,24 @@
return FALSE
return TRUE
+/**
+ * @return TRUE if an operation happened. if we pick it up and immediately drop it from failing
+ * to keep it in us, **this is a TRUE return.**
+ */
/obj/item/proc/attempt_pickup(mob/user)
- . = TRUE
- if (!user)
- return
-
- if(!should_allow_pickup(new /datum/event_args/actor(user)))
+ var/datum/event_args/actor/actor = new(user)
+ // make sure they have inventory
+ if (!user?.inventory)
+ return FALSE
+ // check if pickup is allowed
+ if(!should_allow_pickup(actor))
+ return FALSE
+ if(!user.should_allow_pickup(src, actor))
return FALSE
if(!CHECK_MOBILITY(user, MOBILITY_CAN_PICKUP))
user.action_feedback(SPAN_WARNING("You can't do that right now."), src)
- return
+ return FALSE
if (hasorgans(user))
var/mob/living/carbon/human/H = user
@@ -58,10 +65,10 @@
temp = H.organs_by_name["l_hand"]
if(temp && !temp.is_usable())
to_chat(user, "You try to move your [temp.name], but cannot!")
- return
+ return FALSE
if(!temp)
to_chat(user, "You try to use your hand, but realize it is no longer attached!")
- return
+ return FALSE
var/old_loc = src.loc
var/obj/item/actually_picked_up = src
@@ -88,15 +95,17 @@
if(isnull(actually_picked_up))
to_chat(user, SPAN_WARNING("[src] somehow slips through your grasp. What just happened?"))
- return
+ return FALSE
if(!user.put_in_hands(actually_picked_up, INV_OP_NO_MERGE_STACKS, user.active_hand))
if(has_to_drop_to_ground_on_fail)
actually_picked_up.forceMove(user.drop_location())
- return
+ return TRUE
+ return FALSE
// success
if(isturf(old_loc))
new /obj/effect/temporary_effect/item_pickup_ghost(old_loc, actually_picked_up, user)
user.trigger_aiming(TARGET_CAN_CLICK)
+ return TRUE
//* Drag / Drop *//
diff --git a/code/game/objects/items-inventory-hooks.dm b/code/game/objects/item-inventory-hooks.dm
similarity index 100%
rename from code/game/objects/items-inventory-hooks.dm
rename to code/game/objects/item-inventory-hooks.dm
diff --git a/code/game/objects/items-inventory-old.dm b/code/game/objects/item-inventory-old.dm
similarity index 99%
rename from code/game/objects/items-inventory-old.dm
rename to code/game/objects/item-inventory-old.dm
index f03a7eb5bd8..05d6aa81954 100644
--- a/code/game/objects/items-inventory-old.dm
+++ b/code/game/objects/item-inventory-old.dm
@@ -27,7 +27,7 @@
else
encumbrance_registered = get_encumbrance()
L.adjust_current_carry_encumbrance(encumbrance_registered)
- // fire signals
+ // fire signals aghost
SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot, flags)
SEND_SIGNAL(user, COMSIG_MOB_ITEM_EQUIPPED, src, slot, flags)
@@ -101,6 +101,10 @@
continue
user.unregister_shieldcall(shieldcall)
+ // fire signals
+ . = SEND_SIGNAL(src, COMSIG_ITEM_DROPPED, user, flags, newLoc)
+ SEND_SIGNAL(user, COMSIG_MOB_ITEM_DROPPED, src, flags, newLoc)
+
//! LEGACY
if(!(flags & INV_OP_SUPPRESS_SOUND) && isturf(newLoc) && !(. & COMPONENT_ITEM_DROPPED_SUPPRESS_SOUND))
playsound(src, drop_sound, 30, ignore_walls = FALSE)
@@ -109,10 +113,6 @@
zoom() //binoculars, scope, etc
//! END
- // fire signals
- . = SEND_SIGNAL(src, COMSIG_ITEM_DROPPED, user, flags, newLoc)
- SEND_SIGNAL(user, COMSIG_MOB_ITEM_DROPPED, src, flags, newLoc)
-
if((item_flags & ITEM_DROPDEL) && !(flags & INV_OP_DELETING))
qdel(src)
. |= ITEM_RELOCATED_BY_DROPPED
diff --git a/code/game/objects/items-inventory-rendering.dm b/code/game/objects/item-inventory-rendering.dm
similarity index 100%
rename from code/game/objects/items-inventory-rendering.dm
rename to code/game/objects/item-inventory-rendering.dm
diff --git a/code/game/objects/items-inventory.dm b/code/game/objects/item-inventory.dm
similarity index 100%
rename from code/game/objects/items-inventory.dm
rename to code/game/objects/item-inventory.dm
diff --git a/code/game/objects/items-vv.dm b/code/game/objects/item-vv.dm
similarity index 100%
rename from code/game/objects/items-vv.dm
rename to code/game/objects/item-vv.dm
diff --git a/code/game/objects/items.dm b/code/game/objects/item.dm
similarity index 97%
rename from code/game/objects/items.dm
rename to code/game/objects/item.dm
index b395d221849..d4eb936052e 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/item.dm
@@ -148,6 +148,12 @@
/// This affects multiplicative movespeed.
var/slowdown = 0
+ //* Mounting *//
+ /// Base level item API for item mounts.
+ /// * This is manipulated from the mount side, not from the item side for now.
+ /// * Item side does have a Destroy() hook.
+ var/datum/item_mount/item_mount
+
//* Storage *//
/// storage cost for volumetric storage
/// null to default to weight class
@@ -258,6 +264,8 @@
stack_trace("invalid current equipped slot [worn_slot] on an item not on a mob.")
return ..()
M.temporarily_remove_from_inventory(src, INV_OP_FORCE | INV_OP_DELETING)
+ // inform mount so it can unmount us
+ item_mount?.on_item_del(src)
return ..()
/// Check if target is reasonable for us to operate on.
@@ -359,15 +367,6 @@
else
. = ""
-/obj/item/attack_ai(mob/user as mob)
- if (istype(src.loc, /obj/item/robot_module))
- //If the item is part of a cyborg module, equip it
- if(!isrobot(user))
- return
- var/mob/living/silicon/robot/R = user
- R.activate_module(src)
- R.hud_used.update_robot_modules_display()
-
/obj/item/proc/talk_into(mob/M as mob, text)
return
@@ -883,6 +882,32 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
playsound(src, "shatter", 70, 1)
qdel(src)
+//* Mount *//
+
+/**
+ * Mounts to an item mount
+ */
+/obj/item/proc/mount(datum/item_mount/mount)
+ return mount.mount(src)
+
+/**
+ * Unmounts us if we're mounted onto an item mount
+ */
+/obj/item/proc/unmount()
+ return item_mount?.unmount(src)
+
+/**
+ * Returns if we're mounted (true / false)
+ */
+/obj/item/proc/is_mounted() as num
+ return !!item_mount
+
+/**
+ * Returns our mount, if any
+ */
+/obj/item/proc/get_mount() as /datum/item_mount
+ return item_mount
+
//* Mouse *//
/obj/item/MouseEntered(location, control, params)
diff --git a/code/game/objects/item_mount.dm b/code/game/objects/item_mount.dm
new file mode 100644
index 00000000000..a6e86b8050c
--- /dev/null
+++ b/code/game/objects/item_mount.dm
@@ -0,0 +1,349 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * Arbitrary provider for resources, management, etc.
+ *
+ * * Used to allow things like cyborgs to provide water to things
+ * that would usually require internal tanks; this is just an example.
+ * * Usage procs have 'item' and 'key'. This is so you can route things like air
+ * around.
+ * * Items don't actually need to be mounted to use things. That's why
+ * there's minimally stateful procs. Some stateful procs just gracefully
+ * reject the usage / check if an item isn't mounted.
+ *
+ * ## Relaying
+ *
+ * Item mounts have an inbuilt relay system as 'mount an item that will be used in a module in a cyborg
+ * or rigsuit or mech' is a common patern.
+ *
+ * * Please know what you're doing if you use it. For performance reasons, there's no infinite loop guards.
+ * * If you use this system, it's on you to unlink it as needed and not cause GC issues.
+ */
+/datum/item_mount
+ /// mounted items; lazy list
+ var/list/obj/item/mounted_items
+ /// relay to other item mount by default
+ var/datum/item_mount/relay_to_other
+ /// item mounts relaying to us
+ var/list/datum/item_mount/relay_to_self
+
+/datum/item_mount/Destroy()
+ for(var/obj/item/mounted as anything in mounted_items)
+ unmount(mounted)
+ relay_unlink_all()
+ relay_clear()
+ return ..()
+
+/datum/item_mount/proc/relay_to(datum/item_mount/other)
+ relay_to_other = other
+ LAZYADD(relay_to_other.relay_to_self, src)
+
+/datum/item_mount/proc/relay_clear()
+ if(!relay_to_other)
+ return
+ LAZYREMOVE(relay_to_other.relay_to_self, src)
+ relay_to_other = null
+
+/datum/item_mount/proc/relay_unlink_all()
+ for(var/datum/item_mount/mount as anything in relay_to_self)
+ mount.relay_clear()
+
+/datum/item_mount/proc/mount(obj/item/item)
+ if(item.item_mount)
+ if(item.item_mount == src)
+ return TRUE
+ item.item_mount.unmount(item)
+
+ LAZYADD(mounted_items, item)
+ item.item_mount = src
+ on_item_mount(item)
+ return TRUE
+
+/datum/item_mount/proc/unmount(obj/item/item)
+ if(item.item_mount != src)
+ return TRUE
+ LAZYREMOVE(mounted_items, item)
+ item.item_mount = null
+ on_item_unmount(item)
+ return TRUE
+
+/datum/item_mount/proc/is_mounted(obj/item/item)
+ return item in mounted_items
+
+/**
+ * Called when a mounted item is deleted.
+ */
+/datum/item_mount/proc/on_item_del(obj/item/being_deleted)
+ unmount(being_deleted)
+
+/datum/item_mount/proc/on_item_mount(obj/item/item)
+ SHOULD_NOT_SLEEP(TRUE)
+ SHOULD_CALL_PARENT(TRUE)
+
+/datum/item_mount/proc/on_item_unmount(obj/item/item)
+ SHOULD_NOT_SLEEP(TRUE)
+ SHOULD_CALL_PARENT(TRUE)
+
+//* Gas *//
+
+/**
+ * Get estimated gas pressure.
+ *
+ * @return null, or number
+ */
+/datum/item_mount/proc/gas_get_pressure(obj/item/item, key)
+ return relay_to_other?.gas_get_pressure(item, key)
+
+/**
+ * Unsafe: Directly references a gas_mixture for a channel.
+ *
+ * @return null, or ref
+ */
+/datum/item_mount/proc/gas_mixture_ref(obj/item/item, key)
+ return relay_to_other?.gas_mixture_ref(item, key)
+
+/**
+ * Returns a gas mixture by pulling a given liter amount.
+ *
+ * @return null, or ref
+ */
+/datum/item_mount/proc/gas_pull_volume(obj/item/item, key, liters)
+ return relay_to_other?.gas_pull_volume(item, key)
+
+/**
+ * Fast: erases a given liter amount of gas. Usually used with [gas_mixture_ref]
+ * being used to check first.
+ *
+ * @return null, or moles deleted
+ */
+/datum/item_mount/proc/gas_erase_volume(obj/item/item, key, liters)
+ return relay_to_other?.gas_pull_volume(item, key)
+
+//* Materials *//
+
+/**
+ * Get the name of the provider.
+ * * Material is any resolvable material, so ID, path, or instance.
+ *
+ * @return null, or name
+ */
+/datum/item_mount/proc/material_get_provider_name(obj/item/item, key, material_id)
+ return relay_to_other?.material_get_provider_name(item, key, material_id)
+
+/**
+ * * Material is any resolvable material, so ID, path, or instance.
+ * * Amount is in cm3
+ *
+ * @return null, or amount in cm3
+ */
+/datum/item_mount/proc/material_get_amount(obj/item/item, key, material_id)
+ return relay_to_other?.material_get_amount(item, key, material_id)
+
+/**
+ * * Material is any resolvable material, so ID, path, or instance.
+ * * Amount is in cm3
+ *
+ * @return null, or TRUE / FALSE.
+ */
+/datum/item_mount/proc/material_has_amount(obj/item/item, key, material_id, amount)
+ return relay_to_other?.material_has_amount(item, key, material_id, amount)
+
+/**
+ * * Material is any resolvable material, so ID, path, or instance.
+ * * Amount is in cm3.
+ *
+ * @return null, or amount used in cm3.
+ */
+/datum/item_mount/proc/material_use_amount(obj/item/item, key, material_id, amount)
+ return relay_to_other?.material_use_amount(item, key, material_id, amount)
+
+/**
+ * * Material is any resolvable material, so ID, path, or instance.
+ * * Amount is in cm3.
+ *
+ * @return null, or amount used.
+ */
+/datum/item_mount/proc/material_use_checked_amount(obj/item/item, key, material_id, amount)
+ return material_has_amount(item, key, material_id, amount) && material_use_amount(item, key, material_id, amount)
+
+/**
+ * * Material is any resolvable material, so ID, path, or instance.
+ * * Amount is in cm3.
+ *
+ * @return null, or amount given.
+ */
+/datum/item_mount/proc/material_give_amount(obj/item/item, key, material_id, amount, force)
+ return relay_to_other?.material_give_amount(item, key, material_id, amount, force)
+
+/**
+ * * Material is any resolvable material, so ID, path, or instance.
+ * * Amount is in cm3.
+ *
+ * @return null, or max amount.
+ */
+/datum/item_mount/proc/material_get_capacity(obj/item/item, key, material_id)
+ return relay_to_other?.material_get_capacity(item, key, material_id)
+
+//* Power *//
+
+/**
+ * Lazy use-power-oneoff style thing
+ *
+ * @return null, or TRUE / FALSE for has that amount of energy
+ */
+/datum/item_mount/proc/lazy_energy_check(obj/item/item, key, joules)
+ return relay_to_other?.lazy_energy_check(item, key, joules)
+
+/**
+ * Lazy use-power-oneoff style thing
+ *
+ * @return null, or joules supplied
+ */
+/datum/item_mount/proc/lazy_energy_use(obj/item/item, key, joules, minimum_reserve)
+ return relay_to_other?.lazy_energy_use(item, key, joules, minimum_reserve)
+
+/**
+ * Lazy use-power-oneoff style thing
+ *
+ * @return null, or joules supplied
+ */
+/datum/item_mount/proc/lazy_energy_checked_use(obj/item/item, key, joules, minimum_reserve)
+ return lazy_energy_check(joules + minimum_reserve) && lazy_energy_use(joules, minimum_reserve)
+
+//* Reagents - Direct *//
+
+// TODO: notice how 'erase' and 'spawn' exists, but not 'push', and 'pull'?
+// this is because reagents have data; they are not just described by the ID alone.
+// thus an optimized uniform API for push/pull doesn't actually necessarily make sense,
+// and as such implementation of such is left for when we need it (ergo mechs / rigsuits reagent routing).
+
+/**
+ * Gets if an item mount has a reagent, and if it does, its amount.
+ * * Semantically, null means 'we can't store this'.
+ *
+ * @return null, or amount the item mount has
+ */
+/datum/item_mount/proc/reagent_get_amount(obj/item/item, key, datum/reagent/reagent)
+ return relay_to_other?.reagent_get_amount(item, key, reagent)
+
+/**
+ * Checks if the item mount has a given amount of a reagent
+ * @return null, or TRUE / FALSE
+ */
+/datum/item_mount/proc/reagent_has_amount(obj/item/item, key, datum/reagent/reagent, amount)
+ . = reagent_get_amount(reagent)
+ if(.)
+ return . >= amount
+
+/**
+ * @return null, or amount the item mount could to erase
+ */
+/datum/item_mount/proc/reagent_erase_amount(obj/item/item, key, datum/reagent/reagent, amount)
+ return relay_to_other?.reagent_erase_amount(item, key, reagent, amount)
+
+/**
+ * @return null, or amount the item mount could give to erase
+ */
+/datum/item_mount/proc/reagent_erase_checked_amount(obj/item/item, key, datum/reagent/reagent, amount)
+ return reagent_has_amount(item, key, reagent, amount) && reagent_erase_amount(item, key, reagent, amount)
+
+/**
+ * @return null, or amount the item mount could accept
+ */
+/datum/item_mount/proc/reagent_spawn_amount(obj/item/item, key, datum/reagent/reagent, amount, force)
+ return relay_to_other?.reagent_spawn_amount(item, key, reagent, amount, force)
+
+//* Stacks *//
+
+/**
+ * Get the name of the provider.
+ *
+ * @return null, or name
+ */
+/datum/item_mount/proc/stack_get_provider_name(obj/item/item, key, path)
+ return relay_to_other?.stack_get_provider_name(item, key, path)
+
+/**
+ * Material stacks are invalid here.
+ *
+ * * Amount is in stack amount.
+ *
+ * @return null, or amount used.
+ */
+/datum/item_mount/proc/stack_use_checked_amount(obj/item/item, key, path, amount)
+ return stack_has_amount(item, key, path, amount) && stack_use_amount(item, key, path, amount)
+
+/**
+ * Material stacks are invalid here.
+ *
+ * * Amount is in stack amount.
+ *
+ * @return null, or amount given.
+ */
+/datum/item_mount/proc/stack_give_amount(obj/item/item, key, path, amount, force)
+ return relay_to_other?.stack_give_amount(item, key, path, amount, force)
+
+/**
+ * Material stacks are invalid here.
+ *
+ * @return null, or max amount.
+ */
+/datum/item_mount/proc/stack_get_capacity(obj/item/item, key, path)
+ return relay_to_other?.stack_get_capacity(item, key, path)
+
+/**
+ * Material stacks are invalid here.
+ *
+ * * Amount is in stack amount.
+ *
+ * @return null, or amount remaining.
+ */
+/datum/item_mount/proc/stack_get_amount(obj/item/item, key, path)
+ return relay_to_other?.stack_get_amount(item, key, path)
+
+/**
+ * Material stacks are invalid here.
+ *
+ * * Amount is in stack amount.
+ *
+ * @return null, or TRUE / FALSE.
+ */
+/datum/item_mount/proc/stack_has_amount(obj/item/item, key, path, amount)
+ . = stack_get_amount(item, key, path)
+ if(.)
+ return . >= amount
+
+/**
+ * Material stacks are invalid here.
+ *
+ * * Amount is in stack amount.
+ *
+ * @return null, or amount used.
+ */
+/datum/item_mount/proc/stack_use_amount(obj/item/item, key, path, amount)
+ return relay_to_other?.stack_use_amount(item, key, path, amount)
+
+//* ------ MISC BELOW HERE ------ *//
+
+//* Misc - Extinguisher *//
+
+/**
+ * @return null, or volume remaining
+ */
+/datum/item_mount/proc/extinguisher_get_volume(obj/item/extinguisher/extinguisher, key)
+ return relay_to_other?.extinguisher_get_volume(extinguisher, key)
+
+/**
+ * @return null ,or TRUE / FALSE on if there's that much left
+ */
+/datum/item_mount/proc/extinguisher_has_volume(obj/item/extinguisher/extinguisher, key, requested)
+ . = extinguisher_get_volume(extinguisher)
+ if(.)
+ return . >= requested
+
+/**
+ * @return null, or volume pulled
+ */
+/datum/item_mount/proc/extinguisher_transfer_volume(obj/item/extinguisher/extinguisher, key, requested, datum/reagent_holder/into_holder)
+ return relay_to_other?.extinguisher_transfer_volume(extinguisher, key, requested, into_holder)
diff --git a/code/game/objects/items/antag_spawners.dm b/code/game/objects/items/antag_spawners.dm
index 3fb433f2848..71884266e95 100644
--- a/code/game/objects/items/antag_spawners.dm
+++ b/code/game/objects/items/antag_spawners.dm
@@ -132,10 +132,10 @@
qdel(src)
/obj/item/antag_spawner/syndicate_drone/protector
- drone_type = /mob/living/silicon/robot/syndicate/protector
+ drone_type = /mob/living/silicon/robot/module_preset/cybersun/assault
/obj/item/antag_spawner/syndicate_drone/combat_medic
- drone_type = /mob/living/silicon/robot/syndicate/combat_medic
+ drone_type = /mob/living/silicon/robot/module_preset/cybersun/triage
/obj/item/antag_spawner/syndicate_drone/mechanist
- drone_type = /mob/living/silicon/robot/syndicate/mechanist
+ drone_type = /mob/living/silicon/robot/module_preset/cybersun/sapper
diff --git a/code/game/objects/items/circuitboards/circuitboard.dm b/code/game/objects/items/circuitboards/circuitboard.dm
index 692372a5ff4..b8d4b2bd679 100644
--- a/code/game/objects/items/circuitboards/circuitboard.dm
+++ b/code/game/objects/items/circuitboards/circuitboard.dm
@@ -7,7 +7,7 @@
*/
/obj/item/circuitboard
name = "circuit board"
- icon = 'icons/obj/module.dmi'
+ icon = 'icons/items/circuits.dmi'
icon_state = "id_mod"
materials_base = list(
MAT_STEEL = 1000,
diff --git a/code/game/objects/items/circuitboards/mecha.dm b/code/game/objects/items/circuitboards/mecha.dm
index 086cae06b9c..961e93a649c 100644
--- a/code/game/objects/items/circuitboards/mecha.dm
+++ b/code/game/objects/items/circuitboards/mecha.dm
@@ -10,7 +10,7 @@
/obj/item/circuitboard/mecha
name = "exosuit circuit board"
- icon = 'icons/obj/module.dmi'
+ icon = 'icons/items/circuits.dmi'
icon_state = "std_mod"
board_type = "other"
diff --git a/code/game/objects/items/defib/shockpaddles.dm b/code/game/objects/items/defib/shockpaddles.dm
index fd3c238e5a8..201e6cdcf95 100644
--- a/code/game/objects/items/defib/shockpaddles.dm
+++ b/code/game/objects/items/defib/shockpaddles.dm
@@ -107,8 +107,8 @@
/obj/item/shockpaddles/proc/check_contact(mob/living/carbon/human/H)
if(!combat)
for(var/obj/item/clothing/cloth in H.inventory.get_slots_unsafe(
- /datum/inventory_slot/inventory/uniform,
- /datum/inventory_slot/inventory/suit,
+ /datum/inventory_slot/inventory/uniform::id,
+ /datum/inventory_slot/inventory/suit::id,
))
if((cloth.body_cover_flags & UPPER_TORSO) && (cloth.clothing_flags & CLOTHING_THICK_MATERIAL))
return FALSE
diff --git a/code/game/objects/items/devices/PDA/radio.dm b/code/game/objects/items/devices/PDA/radio.dm
index ea1a2da9a9d..284a382cdba 100644
--- a/code/game/objects/items/devices/PDA/radio.dm
+++ b/code/game/objects/items/devices/PDA/radio.dm
@@ -1,7 +1,7 @@
/obj/item/integated_radio
name = "\improper PDA radio module"
desc = "An electronic radio system."
- icon = 'icons/obj/module.dmi'
+ icon = 'icons/items/circuits.dmi'
icon_state = "power_mod"
var/obj/item/pda/hostpda = null
diff --git a/code/game/objects/items/devices/flash.dm b/code/game/objects/items/devices/flash.dm
index c6bcc166727..d43517f113f 100644
--- a/code/game/objects/items/devices/flash.dm
+++ b/code/game/objects/items/devices/flash.dm
@@ -213,7 +213,7 @@
var/mob/living/silicon/S = M
if(isrobot(S))
var/mob/living/silicon/robot/R = S
- if(R.has_active_type(/obj/item/borg/combat/shield))
+ if(R.get_held_item_of_type(/obj/item/borg/combat/shield))
var/obj/item/borg/combat/shield/shield = locate() in R
if(shield)
if(shield.active)
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index 88c885e0f3c..2833de79e52 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -671,7 +671,7 @@ GLOBAL_DATUM_INIT(virtual_announcer_ai, /mob/living/silicon/ai/announcer, new(nu
if (isrobot(src.loc))
var/mob/living/silicon/robot/R = src.loc
var/datum/robot_component/C = R.components["radio"]
- R.cell_use_power(C.active_usage)
+ R.legacy_cell_use_power(C.active_usage)
/obj/item/radio/borg/attackby(obj/item/I, mob/living/user, params, clickchain_flags, damage_multiplier)
user.set_machine(src)
@@ -714,12 +714,12 @@ GLOBAL_DATUM_INIT(virtual_announcer_ai, /mob/living/silicon/ai/announcer, new(nu
src.syndie = 0
var/mob/living/silicon/robot/D = src.loc
- if(D.module)
- for(var/ch_name in D.module.channels)
+ if(D.module_legacy)
+ for(var/ch_name in D.module_legacy.channels)
if(ch_name in src.channels)
continue
src.channels += ch_name
- src.channels[ch_name] += D.module.channels[ch_name]
+ src.channels[ch_name] += D.module_legacy.channels[ch_name]
if(keyslot)
for(var/ch_name in keyslot.channels)
if(ch_name in src.channels)
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
deleted file mode 100644
index 9a1017bbd21..00000000000
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ /dev/null
@@ -1,265 +0,0 @@
-// robot_upgrades.dm
-// Contains various borg upgrades.
-
-/obj/item/borg/upgrade
- name = "borg upgrade module."
- desc = "Protected by FRM."
- icon = 'icons/obj/module.dmi'
- icon_state = "cyborg_upgrade"
- /// Bitflags listing module compatibility. Used in the exosuit fabricator for creating sub-categories.
- var/list/module_flags = NONE
- var/locked = FALSE
- var/require_module = FALSE
- var/installed = FALSE
-
-/obj/item/borg/upgrade/proc/action(var/mob/living/silicon/robot/R)
- if(R.stat == DEAD)
- to_chat(usr, SPAN_WARNING("The [src] will not function on a deceased robot."))
- return TRUE
- return FALSE
-
-
-/obj/item/borg/upgrade/reset
- name = "robotic module reset board"
- desc = "Used to reset a cyborg's module. Destroys any other upgrades applied to the robot."
- icon_state = "cyborg_upgrade1"
- item_state = "cyborg_upgrade"
- require_module = TRUE
-
-/obj/item/borg/upgrade/reset/action(var/mob/living/silicon/robot/R)
- if(..())
- return FALSE
- R.module_reset()
- return TRUE
-
-/obj/item/borg/upgrade/rename
- name = "robot reclassification board"
- desc = "Used to rename a cyborg."
- icon_state = "cyborg_upgrade1"
- item_state = "cyborg_upgrade"
- var/heldname = "default name"
-
-/obj/item/borg/upgrade/rename/attack_self(mob/user, datum/event_args/actor/actor)
- . = ..()
- if(.)
- return
- heldname = sanitizeSafe(input(user, "Enter new robot name", "Robot Reclassification", heldname), MAX_NAME_LEN)
-
-/obj/item/borg/upgrade/rename/action(var/mob/living/silicon/robot/R)
- if(..())
- return FALSE
- R.notify_ai(ROBOT_NOTIFICATION_NEW_NAME, R.name, heldname)
- R.name = heldname
- R.custom_name = heldname
- R.real_name = heldname
-
- return TRUE
-
-/obj/item/borg/upgrade/restart
- name = "robot emergency restart module"
- desc = "Used to force a restart of a disabled-but-repaired robot, bringing it back online."
- icon_state = "cyborg_upgrade1"
- item_state = "cyborg_upgrade"
-
-
-/obj/item/borg/upgrade/restart/action(var/mob/living/silicon/robot/R)
- if(R.health < 0)
- to_chat(usr, "You have to repair the robot before using this module!")
- return FALSE
-
- if(!R.key)
- for(var/mob/observer/dead/ghost in GLOB.player_list)
- if(ghost.mind && ghost.mind.current == R)
- ghost.transfer_client_to(R)
-
- R.set_stat(CONSCIOUS)
- dead_mob_list -= R
- living_mob_list |= R
- R.notify_ai(ROBOT_NOTIFICATION_NEW_UNIT)
- return TRUE
-
-
-/obj/item/borg/upgrade/vtec
- name = "robotic VTEC Module"
- desc = "Used to kick in a robot's VTEC systems, increasing their speed."
- icon_state = "cyborg_upgrade2"
- item_state = "cyborg_upgrade"
- require_module = TRUE
-
-/obj/item/borg/upgrade/vtec/action(var/mob/living/silicon/robot/R)
- if(..())
- return FALSE
-
- if(R.movement_base_speed >= 7.5)
- return FALSE
- R.movement_base_speed = 7.5
- R.update_movespeed_base()
- return TRUE
-
-/obj/item/borg/upgrade/tasercooler
- name = "robotic Rapid Taser Cooling Module"
- desc = "Used to cool a mounted taser, increasing the potential current in it and thus its recharge rate."
- icon_state = "cyborg_upgrade3"
- item_state = "cyborg_upgrade"
- module_flags = BORG_MODULE_SECURITY
- require_module = TRUE
-
-
-/obj/item/borg/upgrade/tasercooler/action(var/mob/living/silicon/robot/R)
- if(..())
- return FALSE
-
- if(!R.module || !(type in R.module.supported_upgrades))
- to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
- to_chat(usr, "There's no mounting point for the module!")
- return FALSE
-
- var/obj/item/gun/projectile/energy/taser/mounted/cyborg/T = locate() in R.module
- if(!T)
- T = locate() in R.module.contents
- if(!T)
- T = locate() in R.module.modules
- if(!T)
- to_chat(usr, SPAN_WARNING("This robot has had its taser removed!"))
- return FALSE
-
- if(T.recharge_time <= 2)
- to_chat(R, "Maximum cooling achieved for this hardpoint!")
- to_chat(usr, "There's no room for another cooling unit!")
- return FALSE
-
- else
- T.recharge_time = max(2 , T.recharge_time - 4)
-
- return TRUE
-
-/obj/item/borg/upgrade/jetpack
- name = "robot jetpack"
- desc = "A carbon dioxide jetpack suitable for low-gravity operations."
- icon_state = "cyborg_upgrade3"
- item_state = "cyborg_upgrade"
- require_module = TRUE
-
-/obj/item/borg/upgrade/jetpack/action(var/mob/living/silicon/robot/R)
- if(..())
- return FALSE
-
- var/obj/item/tank/jetpack/carbondioxide/T = locate() in R.module
- if(!T)
- T = locate() in R.module.contents
- if(!T)
- T = locate() in R.module.modules
- if(!T)
- R.module.modules += new/obj/item/tank/jetpack/carbondioxide(R.module)
- for(var/obj/item/tank/jetpack/carbondioxide in R.module.modules)
- R.internals = src
- return TRUE
- if(T)
- to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
- to_chat(usr, "There's no mounting point for the module!")
- return FALSE
-
-/obj/item/borg/upgrade/advhealth
- name = "advanced health analyzer module"
- desc = "An upgrade to improve a station-bound synthetic's health analyzer."
- icon_state = "cyborg_upgrade3"
- item_state = "cyborg_upgrade"
- require_module = TRUE
-
-/obj/item/borg/upgrade/advhealth/action(var/mob/living/silicon/robot/R)
- if(..())
- return FALSE
-
- var/obj/item/healthanalyzer/advanced/T = locate() in R.module
- if(!T)
- T = locate() in R.module.contents
- if(!T)
- T = locate() in R.module.modules
- if(!T)
- R.module.modules += new/obj/item/healthanalyzer/advanced(R.module)
- return TRUE
- if(T)
- to_chat(R, "Upgrade mounting error! No suitable hardpoint detected!")
- to_chat(usr, "There's no mounting point for the module!")
- return FALSE
-
-
-/obj/item/borg/upgrade/bluespaceorebag
- name = "bluespace mining satchel module"
- desc = "Improves the ore satchels of mining units to hold a lot more ores."
- icon_state = "cyborg_upgrade3"
- item_state = "cyborg_upgrade"
- module_flags = BORG_MODULE_MINER
- require_module = TRUE
-
-/obj/item/borg/upgrade/bluespaceorebag/action(var/mob/living/silicon/robot/R)
- if(..())
- return FALSE
-
- var/obj/item/storage/bag/ore/O = locate() in R.module
- if(!O)
- O = locate() in R.module.contents
- if(!O)
- O = locate() in R.module.modules
- if(!O) //there should be one though...
- R.module.modules += new/obj/item/storage/bag/ore/bluespace(R.module)
- return TRUE
- if(O)
- if(istype(O,/obj/item/storage/bag/ore/bluespace))
- to_chat(R, "Upgrade mounting error! Upgrade already present.")
- to_chat(usr, SPAN_WARNING("[R] already has this upgrade!"))
- return FALSE
- else
- R.uneq_all()
- O.deconstruct(ATOM_DECONSTRUCT_DISASSEMBLED) //drops all ores previous satchel was carrying using this method
- R.module.modules += new/obj/item/storage/bag/ore/bluespace(R.module)
- return TRUE
-
-
-/obj/item/borg/upgrade/syndicate
- name = "scrambled equipment module"
- desc = "Unlocks new and often deadly module specific items of a robot"
- icon_state = "cyborg_upgrade3"
- item_state = "cyborg_upgrade"
- require_module = TRUE
-
-/obj/item/borg/upgrade/syndicate/action(var/mob/living/silicon/robot/R)
- if(..())
- return FALSE
-
- if(R.emag_items == 1)
- return FALSE
-
- R.emag_items = 1
- return TRUE
-
-/obj/item/borg/upgrade/language
- name = "adaptive translation module"
- desc = "Upgrades a cyborg's language processing unit with an adaptive translation module."
- icon_state = "cyborg_upgrade3"
- item_state = "cyborg_upgrade"
-
-/obj/item/borg/upgrade/language/action(var/mob/living/silicon/robot/R)
- if(..())
- return FALSE
-
- R.create_translation_context(/datum/translation_context/variable/learning/silicons)
-
- return TRUE
-
-//Robot resizing module, moved from robot/upgrades_vr - Papalus
-/obj/item/borg/upgrade/sizeshift
- name = "robot size alteration module"
- desc = "Using technology similar to one used in size guns, allows cyborgs to adjust their own size as necessary."
- icon_state = "cyborg_upgrade2"
- item_state = "cyborg_upgrade"
- require_module = 1
-
-/obj/item/borg/upgrade/sizeshift/action(var/mob/living/silicon/robot/R)
- if(..()) return FALSE
-
- if(/mob/living/proc/set_size in R.verbs)
- return FALSE
-
- add_verb(R, /mob/living/proc/set_size)
- return TRUE
diff --git a/code/game/objects/items/stacks/fifty_spawner.dm b/code/game/objects/items/stacks/legacy/fifty_spawner.dm
similarity index 100%
rename from code/game/objects/items/stacks/fifty_spawner.dm
rename to code/game/objects/items/stacks/legacy/fifty_spawner.dm
diff --git a/code/game/objects/items/stacks/matter_synth.dm b/code/game/objects/items/stacks/matter_synth.dm
deleted file mode 100644
index 64216c46e1e..00000000000
--- a/code/game/objects/items/stacks/matter_synth.dm
+++ /dev/null
@@ -1,60 +0,0 @@
-/datum/matter_synth
- var/name = "Generic Synthesizer"
- var/max_energy = 60000
- var/recharge_rate = 2000
- var/energy
-
-/datum/matter_synth/New(var/store = 0, var/name = null)
- if(store)
- max_energy = store
- if(name)
- src.name = name
- energy = max_energy
- return
-
-/datum/matter_synth/proc/get_charge()
- return energy
-
-/datum/matter_synth/proc/use_charge(var/amount)
- if (energy >= amount)
- energy -= amount
- return 1
- return 0
-
-/datum/matter_synth/proc/add_charge(var/amount)
- energy = min(energy + amount, max_energy)
-
-/datum/matter_synth/proc/emp_act(var/severity)
- use_charge(max_energy * 0.1 / severity)
-
-/datum/matter_synth/medicine
- name = "Medicine Synthesizer"
-
-/datum/matter_synth/nanite
- name = "Nanite Synthesizer"
-
-/datum/matter_synth/metal
- name = "Metal Synthesizer"
-
-/datum/matter_synth/plasteel
- name = "Plasteel Synthesizer"
- max_energy = 10000
-
-/datum/matter_synth/glass
- name = "Glass Synthesizer"
-
-/datum/matter_synth/wood
- name = "Wood Synthesizer"
-
-/datum/matter_synth/plastic
- name = "Plastic Synthesizer"
-
-/datum/matter_synth/wire
- name = "Wire Synthesizer"
- max_energy = 50
- recharge_rate = 2
-
-/datum/matter_synth/bandage
- name = "Bandage Synthesizer"
- max_energy = 10
- recharge_rate = 1
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index cb96f5cfd8b..06432ff67e5 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -26,6 +26,20 @@
if(!safety)
CRASH("ran out of safety")
+/proc/__construct_legacy_stack_provider_material_map()
+ return list(
+ /obj/item/stack/rods = list(
+ /datum/prototype/material/steel::id = SHEET_MATERIAL_AMOUNT / 2,
+ ),
+ /obj/item/stack/tile/floor = list(
+ /datum/prototype/material/steel::id = SHEET_MATERIAL_AMOUNT / 4,
+ ),
+ /obj/item/stack/material/glass/reinforced = list(
+ /datum/prototype/material/glass::id = SHEET_MATERIAL_AMOUNT / 1,
+ /datum/prototype/material/steel::id = SHEET_MATERIAL_AMOUNT / 2,
+ ),
+ )
+
/**
* Items that can stack, tracking the number of which is in it
*
@@ -37,21 +51,39 @@
icon = 'icons/obj/stacks.dmi'
item_flags = ITEM_CAREFUL_BLUDGEON | ITEM_ENCUMBERS_WHILE_HELD
- /// Always create this type when splitting, instead of our own type.
- var/split_type
- /// The type we actually are. This is what is used when things try to consume an amount of us.
- /// * null = this current type
- var/stack_type
+ /// Total number of states used in updating icons.
+ /// todo: all stacks should use this, remove `use_new_icon_update
+ ///
+ /// * Only active when [use_new_icon_update] is set on
+ /// * This counts up from 1.
+ /// * If null, we don't do icon updates based on amount.
+ var/icon_state_count
- var/singular_name
+ /// Our effective stack type. Defaults to our type.
+ /// * Used to determine our identity when calling into providers.
+ /// * Used to determine what can merge into what (so colored subtypes of cable can all be considered cable).
+ var/stack_type
+ /// What type we split into. Useful if you have an infinite stack you don't want to be split into another infinite stack.
+ /// * Use `|| stack_type || type` to default to stack type if this is not set.
+ var/split_type
+
+ /// Current amount
var/amount = 1
- /// See stack recipes initialisation, param "max_res_amount" must be equal to this max_amount.
+ /// Maximum amount
var/max_amount = 50
- /// Determines whether different stack types can merge.
- var/stacktype_legacy
- var/uses_charge = 0
- var/list/charge_costs = null
- var/list/datum/matter_synth/synths = null
+
+ /// this is admittedly shitcode but this allows for automatic
+ /// stack provider support for non /material stacks that are directly derived from
+ /// materials, like reinf glass and metal tiles
+ ///
+ /// todo: please find a better way.
+ var/static/list/legacy_stack_provider_material_map = __construct_legacy_stack_provider_material_map()
+
+ /// explicit recipes, lazy-list. this is typelist'd
+ var/list/datum/stack_recipe/explicit_recipes
+
+ //! legacy - re-evaluate these at some point!
+ var/singular_name
/// Determines whether the item should update it's sprites based on amount.
var/no_variants = TRUE
@@ -61,40 +93,31 @@
/// * this is mandatory for all new stacks
var/use_new_icon_update = FALSE
- /// Total number of states used in updating icons.
- /// todo: all stacks should use this, remove `use_new_icon_update
- ///
- /// * Only active when [use_new_icon_update] is set on
- /// * This counts up from 1.
- /// * If null, we don't do icon updates based on amount.
- var/icon_state_count
-
/// Will the item pass its own color var to the created item? Dyed cloth, wood, etc.
var/pass_color = FALSE
/// Will the stack merge with other stacks that are different colors? (Dyed cloth, wood, etc).
var/strict_color_stacking = FALSE
-
- /// explicit recipes, lazy-list. this is typelist'd
- var/list/datum/stack_recipe/explicit_recipes
+ //! end
/obj/item/stack/Initialize(mapload, new_amount, merge = TRUE)
if(has_typelist(explicit_recipes))
explicit_recipes = get_typelist(explicit_recipes)
else
explicit_recipes = typelist(NAMEOF(src, explicit_recipes), generate_explicit_recipes())
- if(new_amount != null)
+ if(!isnull(new_amount))
amount = new_amount
- if(!stacktype_legacy)
- stacktype_legacy = type
+ // todo: lint this to make sure everyone sets this, don't set in initialize as a safety net
+ if(!stack_type)
+ stack_type = type
. = ..()
// only merge 1. outside of mapload and 2. if we had amount
if(merge && !mapload && amount)
for(var/obj/item/stack/S in loc)
- if(can_merge(S))
- merge(S)
+ if(can_merge_into(S))
+ merge_into_other(S, TRUE)
// and if we did and no longer have amount, delete ourselves
if(amount == 0)
- zero_amount()
+ return INITIALIZE_HINT_QDEL
update_icon()
/obj/item/stack/update_icon_state()
@@ -112,6 +135,7 @@
if(no_variants)
icon_state = initial(icon_state)
else
+ var/amount = get_amount()
if(amount <= (max_amount * (1/3)))
icon_state = initial(icon_state)
else if (amount <= (max_amount * (2/3)))
@@ -122,10 +146,10 @@
/obj/item/stack/examine(mob/user, dist)
. = ..()
- if(!uses_charge)
- . += "There are [amount] [singular_name]\s in the stack."
+ if(!has_stack_provider())
+ . += "There are [get_amount()] [singular_name]\s in the stack."
else
- . += "There is enough charge for [get_amount()]."
+ . += "There are [get_amount()] [singular_name]\s in \the [get_provider_name()]."
/**
* Get the explicit recipes of this stack type
@@ -199,56 +223,27 @@
log_stackcrafting(user, src, recipe.name, make_amount, needed, where)
use(needed)
-/**
- * Return 1 if an immediate subsequent call to use() would succeed.
- * Ensures that code dealing with stacks uses the same logic
- */
-/obj/item/stack/proc/can_use(used)
- if (get_amount() < used)
- return FALSE
- return TRUE
-
/**
* Can we merge with this stack?
*/
-/obj/item/stack/proc/can_merge(obj/item/stack/other)
+/obj/item/stack/proc/can_merge_into(obj/item/stack/other)
if(!istype(other))
return FALSE
if((strict_color_stacking || other.strict_color_stacking) && (color != other.color))
return FALSE
- return other.stacktype_legacy == stacktype_legacy
+ return can_merge_into_type(other.type)
+// todo: deprecated
+/obj/item/stack/proc/can_use(used)
+ return has_amount(used)
+
+// todo: deprecated
/obj/item/stack/proc/use(used, no_delete)
- if (!can_use(used))
- return FALSE
- if(!uses_charge)
- amount -= used
- if (amount <= 0 && !no_delete)
- qdel(src) //should be safe to qdel immediately since if someone is still using this stack it will persist for a little while longer
- update_icon()
- return TRUE
- else
- if(get_amount() < used)
- return FALSE
- for(var/i = 1 to uses_charge)
- var/datum/matter_synth/S = synths[i]
- S.use_charge(charge_costs[i] * used) // Doesn't need to be deleted
- return TRUE
+ return use_amount(used, no_delete)
+// todo: deprecated
/obj/item/stack/proc/add(extra, force)
- if(!uses_charge)
- if((amount + extra > get_max_amount()) && !force)
- return FALSE
- else
- amount += extra
- update_icon()
- return TRUE
- else if(!synths || synths.len < uses_charge)
- return FALSE
- else
- for(var/i = 1 to uses_charge)
- var/datum/matter_synth/S = synths[i]
- S.add_charge(charge_costs[i] * extra)
+ return give_amount(extra, force)
/**
* The transfer and split procs work differently than use() and add().
@@ -260,7 +255,7 @@
/obj/item/stack/proc/transfer_to(obj/item/stack/S, tamount=null, type_verified)
if (!get_amount())
return 0
- if (!can_merge(S) && !type_verified)
+ if (!can_merge_into(S) && !type_verified)
return 0
if (isnull(tamount))
@@ -283,8 +278,6 @@
/obj/item/stack/proc/split(tamount, atom/where, force)
if (!amount)
return null
- if (uses_charge)
- return null
var/transfer = max(min(tamount, src.amount, force? INFINITY : initial(max_amount)), 0)
@@ -300,42 +293,6 @@
return newstack
return null
-/obj/item/stack/proc/get_amount()
- if(uses_charge)
- if(!synths || synths.len < uses_charge)
- return 0
- var/datum/matter_synth/S = synths[1]
- . = round(S.get_charge() / charge_costs[1])
- if(uses_charge > 1)
- for(var/i = 2 to uses_charge)
- S = synths[i]
- . = min(., round(S.get_charge() / charge_costs[i]))
- return
- return amount
-
-/obj/item/stack/proc/get_max_amount()
- if(uses_charge)
- if(!synths || synths.len < uses_charge)
- return 0
- var/datum/matter_synth/S = synths[1]
- . = round(S.max_energy / charge_costs[1])
- if(uses_charge > 1)
- for(var/i = 2 to uses_charge)
- S = synths[i]
- . = min(., round(S.max_energy / charge_costs[i]))
- return
- return max_amount
-
-/obj/item/stack/proc/add_to_stacks(mob/user)
- for (var/obj/item/stack/item in user.loc)
- if (item==src)
- continue
- var/transfer = src.transfer_to(item)
- if (transfer)
- to_chat(user, SPAN_NOTICE("You add a new [item.singular_name] to the stack. It now contains [item.amount] [item.singular_name]\s."))
- if(!amount)
- break
-
/obj/item/stack/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
if(user.get_inactive_held_item() == src)
change_stack(user, 1)
@@ -345,25 +302,26 @@
/obj/item/stack/Crossed(atom/movable/AM)
. = ..()
// if we're in a mob, do not automerge
- if(!ismob(loc) && !AM.throwing && can_merge(AM))
- merge(AM)
+ if(!ismob(loc) && !AM.throwing && can_merge_into(AM))
+ merge_into_other(AM)
-/// Merge src into S, as much as possible.
-/obj/item/stack/proc/merge(obj/item/stack/S, no_delete)
- if(uses_charge)
- return // how about no!
- if(QDELETED(S) || QDELETED(src) || (S == src)) //amusingly this can cause a stack to consume itself, let's not allow that.
+/**
+ * Merge self into other
+ *
+ * * Can delete ourselves.
+ *
+ * @return amount merged
+ */
+/obj/item/stack/proc/merge_into_other(obj/item/stack/other, no_delete)
+ if(QDELETED(other) || QDELETED(src) || (other == src)) //amusingly this can cause a stack to consume itself, let's not allow that.
return
var/transfer = get_amount()
- if(S.uses_charge)
- transfer = min(transfer, S.get_max_amount() - S.get_amount())
- else
- transfer = min(transfer, S.max_amount - S.amount)
+ transfer = min(transfer, other.max_amount - other.amount)
if(pulledby)
- pulledby.start_pulling(S)
- S.copy_evidences(src)
+ pulledby.start_pulling(other)
+ other.copy_evidences(src)
use(transfer, no_delete)
- S.add(transfer)
+ other.add(transfer)
return transfer
/obj/item/stack/attackby(obj/item/I, mob/living/user, list/params, clickchain_flags, damage_multiplier)
@@ -399,30 +357,27 @@
return FALSE
/obj/item/stack/proc/attempt_split_stack(mob/living/user)
- if(uses_charge)
+ //get amount from user
+ var/max = get_amount()
+ var/stackmaterial = tgui_input_number(user, "How many sheets do you wish to take out of this stack?", "Stack", max, max, 1, round_value=TRUE)
+ if(QDELETED(src))
return
- else
- if(zero_amount())
- return
- //get amount from user
- var/max = get_amount()
- // var/stackmaterial = round(input(user,"How many sheets do you wish to take out of this stack? (Maximum [max])") as null|num)
- var/stackmaterial = tgui_input_number(user, "How many sheets do you wish to take out of this stack?", "Stack", max, max, 1, round_value=TRUE)
- max = get_amount() // Not sure why this is done twice but whatever.
- stackmaterial = min(max, stackmaterial)
- if(stackmaterial == null || stackmaterial <= 0 || !in_range(user, src) || !CHECK_MOBILITY(user, MOBILITY_CAN_PICKUP))
- return TRUE
- else
- change_stack(user, stackmaterial)
- to_chat(user, SPAN_NOTICE("You take [stackmaterial] sheets out of the stack"))
+ max = get_amount() // Not sure why this is done twice but whatever.
+ stackmaterial = min(max, stackmaterial)
+ if(stackmaterial == null || stackmaterial <= 0 || !in_range(user, src) || !CHECK_MOBILITY(user, MOBILITY_CAN_PICKUP))
return TRUE
+ else
+ change_stack(user, stackmaterial)
+ to_chat(user, SPAN_NOTICE("You take [stackmaterial] sheets out of the stack"))
+ return TRUE
// todo: refactor and combine with /split
/obj/item/stack/proc/change_stack(mob/user, amount)
+ var/atom/our_current_location = drop_location()
if(!use(amount, TRUE, FALSE))
return FALSE
var/make_type = isnull(split_type)? type : split_type
- var/obj/item/stack/F = new make_type(user? user : drop_location(), amount, FALSE)
+ var/obj/item/stack/F = new make_type(user? user : our_current_location, amount, FALSE)
. = F
F.copy_evidences(src)
if(user)
@@ -430,15 +385,6 @@
F.forceMove(user.drop_location())
add_fingerprint(user)
F.add_fingerprint(user)
- zero_amount()
-
-/obj/item/stack/proc/zero_amount()
- if(uses_charge)
- return get_amount() <= 0
- if(amount < 1)
- qdel(src)
- return TRUE
- return FALSE
/obj/item/stack/proc/copy_evidences(obj/item/stack/from)
if(from.blood_DNA)
@@ -450,26 +396,131 @@
if(from.fingerprintslast)
fingerprintslast = from.fingerprintslast
-/obj/item/stack/proc/set_amount(new_amount, no_limits = FALSE)
- if(new_amount < 0 || new_amount % 1)
+/obj/item/stack/proc/legacy_add_to_stacks_please_refactor_me(mob/user)
+ for (var/obj/item/stack/item in user.loc)
+ if (item==src)
+ continue
+ var/transfer = src.transfer_to(item)
+ if (transfer)
+ to_chat(user, SPAN_NOTICE("You add a new [item.singular_name] to the stack. It now contains [item.amount] [item.singular_name]\s."))
+ if(!amount)
+ break
+
+//* Access *//
+
+/**
+ * Gets how many sheets we have.
+ */
+/obj/item/stack/proc/get_amount()
+ if(item_mount)
+ return check_provider_remaining()
+ return amount
+
+/**
+ * Gets how many sheets we can carry.
+ */
+/obj/item/stack/proc/get_max_amount()
+ if(item_mount)
+ return check_provider_capacity()
+ return max_amount
+
+/**
+ * Gets how many sheets we can accept.
+ */
+/obj/item/stack/proc/get_missing_amount()
+ return get_max_amount() - get_amount()
+
+/**
+ * Checks if we have an amount.
+ *
+ * @return TRUE / FALSE
+ */
+/obj/item/stack/proc/has_amount(amount)
+ return check_provider_remaining() >= amount
+
+/**
+ * Use an amount if we have the whole amount.
+ *
+ * @return amount used
+ */
+/obj/item/stack/proc/use_checked_amount(amount)
+ if(!has_amount(amount))
+ return 0
+ return use_amount(amount)
+
+/**
+ * Use an amount. Will use less if we don't have that much.
+ *
+ * @return amount used
+ */
+/obj/item/stack/proc/use_amount(amount, no_delete)
+ if(item_mount)
+ return pull_from_provider(amount)
+ if(amount <= 0)
+ return 0
+ amount = min(amount, src.amount)
+ . = amount
+ src.amount -= amount
+ update_amount(no_delete)
+
+/**
+ * Gives a number of sheets back to us.
+ *
+ * @return amount added
+ */
+/obj/item/stack/proc/give_amount(amount, force)
+ if(item_mount)
+ return push_to_provider(amount, force)
+ if(!force)
+ amount = min(amount, max_amount - src.amount)
+ if(amount <= 0)
+ return 0
+ . = amount
+ src.amount += amount
+ update_amount()
+
+/**
+ * Sets our amount to a specific amount.
+ *
+ * * If we use a stack provider, we'll push/pull automatically. This can be weird, because providers don't act the same as stacks.
+ * * Setting us to 0 will delete us immediately.
+ *
+ * @return amount changed
+ */
+/obj/item/stack/proc/set_amount(new_amount, force)
+ if(new_amount < 0 || (new_amount != floor(amount)))
stack_trace("Tried to set a bad stack amount: [new_amount]")
return 0
- // Clean up the new amount
- new_amount = max(round(new_amount), 0)
+ if(item_mount)
+ var/current_amount = check_provider_remaining()
+ var/amount_to_push_or_pull = new_amount - current_amount
- // Can exceed max if you really want
- if(new_amount > max_amount && !no_limits)
- new_amount = max_amount
+ if(!amount_to_push_or_pull)
+ return 0
+ return amount_to_push_or_pull > 0 ? push_to_provider(amount_to_push_or_pull, force) : pull_from_provider(amount_to_push_or_pull)
+ if(!force)
+ new_amount = min(amount, max_amount)
+
+ . = new_amount - amount
amount = new_amount
- // Can set it to 0 without qdel if you really want
- if(amount == 0 && !no_limits)
- qdel(src)
- return FALSE
- update_icon()
- return TRUE
+ update_amount()
+
+/**
+ * This can destroy the stack.
+ */
+/obj/item/stack/proc/update_amount(no_delete)
+ if(QDELING(src))
+ return
+ if(item_mount)
+ else
+ if(amount <= 0)
+ if(!no_delete)
+ qdel(src)
+ return
+ update_icon()
//* Getters *//
@@ -483,6 +534,77 @@
return null
return CEILING(the_amount / max_amount * icon_state_count, 1)
+//* Stack Providers *//
+
+/obj/item/stack/proc/has_stack_provider()
+ return !!item_mount
+
+/**
+ * Get the name of our stack provider.
+ * * You must check if stack provider exists.
+ */
+/obj/item/stack/proc/get_provider_name()
+ return item_mount.stack_get_provider_name(src, null, stack_type)
+
+/**
+ * * You must check if stack provider exists.
+ * @return amount left
+ */
+/obj/item/stack/proc/check_provider_remaining()
+ var/list/legacy_remap = legacy_stack_provider_material_map[type]
+ if(legacy_remap)
+ . = INFINITY
+ for(var/mat_id in legacy_remap)
+ . = min(., (item_mount.material_get_amount(src, null, mat_id) / legacy_remap[mat_id]))
+ else
+ . = item_mount.stack_get_amount(src, null, stack_type)
+
+/**
+ * * You must check if stack provider exists.
+ * @return max volume
+ */
+/obj/item/stack/proc/check_provider_capacity()
+ var/list/legacy_remap = legacy_stack_provider_material_map[type]
+ if(legacy_remap)
+ . = INFINITY
+ for(var/mat_id in legacy_remap)
+ . = min(., (item_mount.material_get_capacity(src, null, mat_id) / legacy_remap[mat_id]))
+ else
+ . = item_mount.stack_get_capacity(src, null, stack_type)
+
+/**
+ * * You must check if stack provider exists.
+ * @return amount pushed
+ */
+/obj/item/stack/proc/push_to_provider(amount, force)
+ var/list/legacy_remap = legacy_stack_provider_material_map[type]
+ if(legacy_remap)
+ // we have to be atomic, so do an expensive check first
+ if(!force)
+ var/has_remaining_capacity = check_provider_capacity() - check_provider_remaining()
+ if(has_remaining_capacity <= 0)
+ return 0
+ amount = min(amount, has_remaining_capacity)
+ . = amount
+ for(var/mat_id in legacy_remap)
+ item_mount.material_give_amount(src, null, mat_id, amount * legacy_remap[mat_id] * SHEET_MATERIAL_AMOUNT)
+ else
+ . = item_mount.stack_give_amount(src, null, stack_type, amount * SHEET_MATERIAL_AMOUNT, force)
+
+/**
+ * * You must check if stack provider exists.
+ * @return amount pulled
+ */
+/obj/item/stack/proc/pull_from_provider(amount)
+ var/list/legacy_remap = legacy_stack_provider_material_map[type]
+ if(legacy_remap)
+ // we have to be atomic, so do an expensive check first
+ var/has_remaining = check_provider_remaining()
+ for(var/mat_id in legacy_remap)
+ item_mount.material_use_amount(src, null, mat_id, has_remaining * legacy_remap[mat_id] * SHEET_MATERIAL_AMOUNT)
+ else
+ . = item_mount.stack_use_amount(src, null, stack_type, amount * SHEET_MATERIAL_AMOUNT)
+
//* Types *//
/**
@@ -499,8 +621,6 @@
/**
* Can merge into a type
- *
- * todo: use this instead of raw stacktype_legacy checks
*/
/obj/item/stack/proc/can_merge_into_type(path)
- CRASH("Not implemented.")
+ return path == (stack_type || type)
diff --git a/code/game/objects/items/stacks/tickets.dm b/code/game/objects/items/stacks/subtypes/arcadeticket.dm
similarity index 100%
rename from code/game/objects/items/stacks/tickets.dm
rename to code/game/objects/items/stacks/subtypes/arcadeticket.dm
diff --git a/code/game/objects/items/stacks/marker_beacons.dm b/code/game/objects/items/stacks/subtypes/marker_beacons.dm
similarity index 100%
rename from code/game/objects/items/stacks/marker_beacons.dm
rename to code/game/objects/items/stacks/subtypes/marker_beacons.dm
diff --git a/code/modules/materials/material_sheets.dm b/code/game/objects/items/stacks/subtypes/material.dm
similarity index 89%
rename from code/modules/materials/material_sheets.dm
rename to code/game/objects/items/stacks/subtypes/material.dm
index a9ecd46f4e0..15c167c295d 100644
--- a/code/modules/materials/material_sheets.dm
+++ b/code/game/objects/items/stacks/subtypes/material.dm
@@ -51,7 +51,8 @@
pixel_x = rand(0,4)-4
pixel_y = rand(0,4)-4
- stacktype_legacy = src.material.stack_type
+ stack_type = src.material.stack_type
+
if(islist(src.material.stack_origin_tech))
origin_tech = src.material.stack_origin_tech.Copy()
@@ -64,11 +65,11 @@
update_strings()
/obj/item/stack/material/get_materials(respect_multiplier)
- return list(material.id = (respect_multiplier? material_multiplier : 1) * SHEET_MATERIAL_AMOUNT)
+ return list(src, null, material.id = (respect_multiplier? material_multiplier : 1) * SHEET_MATERIAL_AMOUNT)
/obj/item/stack/material/update_icon()
if(material.icon_stack_count)
- icon_state = "stack-[min(ceil((amount / max_amount) * material.icon_stack_count), material.icon_stack_count)]"
+ icon_state = "stack-[min(ceil((get_amount() / max_amount) * material.icon_stack_count), material.icon_stack_count)]"
return ..()
/obj/item/stack/material/tgui_recipes()
@@ -127,6 +128,61 @@
return
return ..()
+/obj/item/stack/material/can_merge_into(obj/item/stack/other)
+ if(!istype(other, /obj/item/stack/material))
+ return FALSE
+ var/obj/item/stack/material/casted = other
+ return material.id == casted.material.id
+
+//* Stack Providers *//
+
+/obj/item/stack/material/get_provider_name()
+ return item_mount.material_get_provider_name(src, null, material.id)
+
+/obj/item/stack/material/check_provider_remaining()
+ var/list/legacy_remap = legacy_stack_provider_material_map[type]
+ if(legacy_remap)
+ . = INFINITY
+ for(var/mat_id in legacy_remap)
+ . = min(., (item_mount.material_get_amount(src, null, mat_id) / legacy_remap[mat_id]) / SHEET_MATERIAL_AMOUNT)
+ else
+ . = item_mount.material_get_amount(src, null, material.id) / SHEET_MATERIAL_AMOUNT
+
+/obj/item/stack/material/check_provider_capacity()
+ var/list/legacy_remap = legacy_stack_provider_material_map[type]
+ if(legacy_remap)
+ . = INFINITY
+ for(var/mat_id in legacy_remap)
+ . = min(., (item_mount.material_get_capacity(src, null, mat_id) / legacy_remap[mat_id]) / SHEET_MATERIAL_AMOUNT)
+ else
+ . = item_mount.material_get_capacity(src, null, material.id) / SHEET_MATERIAL_AMOUNT
+
+/obj/item/stack/material/push_to_provider(amount, force)
+ var/list/legacy_remap = legacy_stack_provider_material_map[type]
+ if(legacy_remap)
+ // we have to be atomic, so do an expensive check first
+ if(!force)
+ var/has_remaining_capacity = check_provider_capacity() - check_provider_remaining()
+ if(has_remaining_capacity <= 0)
+ return 0
+ amount = min(amount, has_remaining_capacity)
+ . = amount
+ for(var/mat_id in legacy_remap)
+ item_mount.material_give_amount(src, null, mat_id, amount * legacy_remap[mat_id] * SHEET_MATERIAL_AMOUNT)
+ else
+ . = item_mount.material_give_amount(src, null, material.id, amount * SHEET_MATERIAL_AMOUNT, force)
+
+/obj/item/stack/material/pull_from_provider(amount)
+ var/list/legacy_remap = legacy_stack_provider_material_map[type]
+ if(legacy_remap)
+ // we have to be atomic, so do an expensive check first
+ var/has_remaining = check_provider_remaining()
+ for(var/mat_id in legacy_remap)
+ item_mount.material_use_amount(src, null, mat_id, has_remaining * legacy_remap[mat_id] * SHEET_MATERIAL_AMOUNT)
+ else
+ . = item_mount.material_use_amount(src, null, material.id, amount * SHEET_MATERIAL_AMOUNT)
+
+
// todo: we need a better way of doing this, holy shit
/obj/item/stack/material/iron
diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/subtypes/material/glass.dm
similarity index 100%
rename from code/game/objects/items/stacks/sheets/glass.dm
rename to code/game/objects/items/stacks/subtypes/material/glass.dm
diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/subtypes/medical.dm
similarity index 99%
rename from code/game/objects/items/stacks/medical.dm
rename to code/game/objects/items/stacks/subtypes/medical.dm
index d0b45a59743..522f0977191 100644
--- a/code/game/objects/items/stacks/medical.dm
+++ b/code/game/objects/items/stacks/subtypes/medical.dm
@@ -51,7 +51,7 @@
to_chat(user, "You can't apply [src] through [H.head]!")
return FALSE
else
- if(istype(H.inventory.get_slot_single(/datum/inventory_slot/inventory/suit), /obj/item/clothing/suit/space))
+ if(istype(H.inventory.get_slot_single(/datum/inventory_slot/inventory/suit::id), /obj/item/clothing/suit/space))
to_chat(user, "You can't apply [src] through [H.wear_suit]!")
return FALSE
diff --git a/code/game/objects/items/stacks/nanopaste.dm b/code/game/objects/items/stacks/subtypes/nanopaste.dm
similarity index 100%
rename from code/game/objects/items/stacks/nanopaste.dm
rename to code/game/objects/items/stacks/subtypes/nanopaste.dm
diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/subtypes/rods.dm
similarity index 87%
rename from code/game/objects/items/stacks/rods.dm
rename to code/game/objects/items/stacks/subtypes/rods.dm
index a4d0a46bd6d..e4310ade979 100644
--- a/code/game/objects/items/stacks/rods.dm
+++ b/code/game/objects/items/stacks/subtypes/rods.dm
@@ -38,7 +38,7 @@
if(WT.remove_fuel(0,user))
var/obj/item/stack/material/steel/new_item = new(usr.loc)
- new_item.add_to_stacks(usr)
+ new_item.legacy_add_to_stacks_please_refactor_me(usr)
for (var/mob/M in viewers(src))
M.show_message("[src] is shaped into metal by [user.name] with the weldingtool.", 3, "You hear welding.", 2)
var/obj/item/stack/rods/R = src
@@ -59,13 +59,3 @@
return
..()
-
-/obj/item/stack/rods/cyborg
- name = "metal rod synthesizer"
- desc = "A device that makes metal rods."
- gender = NEUTER
- uses_charge = 1
- charge_costs = list(500)
- stack_type = /obj/item/stack/rods
- stacktype_legacy = /obj/item/stack/rods
- no_variants = TRUE
diff --git a/code/game/objects/items/stacks/sandbag.dm b/code/game/objects/items/stacks/subtypes/sandbag.dm
similarity index 59%
rename from code/game/objects/items/stacks/sandbag.dm
rename to code/game/objects/items/stacks/subtypes/sandbag.dm
index 0920ab9146e..7af7fcb01a7 100644
--- a/code/game/objects/items/stacks/sandbag.dm
+++ b/code/game/objects/items/stacks/subtypes/sandbag.dm
@@ -1,51 +1,3 @@
-//Empty Sandbags
-/obj/item/stack/emptysandbag
- name = "empty sandbag"
- desc = "A bag designed to be filled with sand."
- singular_name = "empty sandbag"
- icon_state = "sandbag_empty"
- w_class = WEIGHT_CLASS_NORMAL
- damage_force = 1
- throw_force = 1
- throw_speed = 5
- throw_range = 20
- drop_sound = 'sound/items/drop/backpack.ogg'
- pickup_sound = 'sound/items/pickup/backpack.ogg'
- materials_base = list("cloth" = 2)
- max_amount = 50
- attack_verb = list("tapped", "smacked", "flapped")
-
-/obj/item/stack/emptysandbag/Initialize(mapload, new_amount, merge)
- . = ..()
- update_icon()
-
-/obj/item/stack/emptysandbag/update_icon_state()
- var/amount = get_amount()
- if((amount >= 35))
- icon_state = "sandbag_empty_3"
- else if((amount < 35) && (amount > 1))
- icon_state = "sandbag_empty_2"
- else
- icon_state = "sandbag_empty"
- return ..()
-
-/obj/item/stack/emptysandbag/attackby(var/obj/item/W, var/mob/user)
- if(istype(W, /obj/item/stack/ore/glass) && !interact(user, src))
- if(do_after(user, 1 SECONDS, src) && use(1))
- var/obj/item/stack/ore/glass/O = W
- var/turf/T = get_turf(user)
- if(O.use(1))
- to_chat(user, "You fill the sandbag.")
- new /obj/item/stack/sandbags(T)
- return
- else if(is_sharp(W))
- user.visible_message("\The [user] begins cutting up [src] with [W].", "You begin cutting up [src] with [W].")
- if(do_after(user, 3 SECONDS, src) && use(1))
- to_chat(user, "You cut [src] into pieces!")
- new /obj/item/stack/material/cloth(drop_location(),rand(1,2))
- return
- return ..()
-
//Filled Sandbags
/obj/item/stack/sandbags
name = "sandbag"
@@ -95,69 +47,53 @@
var/turf/T = get_turf(user)
to_chat(user, "You cut the cords securing the sandbag, spilling sand everywhere!")
for(var/i in 1 to rand(1,1))
- new /obj/item/stack/emptysandbag(T)
+ new /obj/item/stack/sandbag/empty(T)
new /obj/item/stack/ore/glass(T)
return
-//Sandbag Barricades
-/obj/structure/sandbag
- name = "sandbag barricade"
- desc = "A barrier made of stacked sandbags."
- icon = 'icons/obj/structures/sandbags.dmi'
- icon_state = "sandbags-0"
- base_icon_state = "sandbags"
- anchored = TRUE
- density = TRUE
- pass_flags_self = ATOM_PASS_TABLE | ATOM_PASS_THROWN | ATOM_PASS_CLICK
- smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = (SMOOTH_GROUP_SANDBAGS)
- canSmoothWith = (SMOOTH_GROUP_SANDBAGS)
- integrity = 200
- integrity_max = 200
+//Empty Sandbags
+/obj/item/stack/sandbag/empty
+ name = "empty sandbag"
+ desc = "A bag designed to be filled with sand."
+ singular_name = "empty sandbag"
+ icon_state = "sandbag_empty"
+ w_class = WEIGHT_CLASS_NORMAL
+ damage_force = 1
+ throw_force = 1
+ throw_speed = 5
+ throw_range = 20
+ drop_sound = 'sound/items/drop/backpack.ogg'
+ pickup_sound = 'sound/items/pickup/backpack.ogg'
+ materials_base = list("cloth" = 2)
+ max_amount = 50
+ attack_verb = list("tapped", "smacked", "flapped")
- var/vestigial = TRUE
-
-/obj/structure/sandbag/Initialize(mapload)
+/obj/item/stack/sandbag/empty/Initialize(mapload, new_amount, merge)
. = ..()
- for(var/obj/structure/sandbag/S in loc)
- if(S != src)
- break_to_parts(full_return = 1)
- return
- if(mapload)
- return INITIALIZE_HINT_LATELOAD
- else
- //update_connections(TRUE)
- update_icon()
-
-/obj/structure/sandbag/LateInitialize()
- //update_connections(FALSE)
update_icon()
-/obj/structure/sandbag/Destroy()
- //update_connections(TRUE)
- . = ..()
-
-/obj/structure/sandbag/attackby(obj/item/W as obj, mob/user as mob)
- user.setClickCooldownLegacy(user.get_attack_speed_legacy(W))
- if(istype(W, /obj/item/stack/sandbags))
- var/obj/item/stack/sandbags/S = W
- if(integrity < integrity_max)
- if(S.get_amount() < 1)
- to_chat(user, "You need one sandbag to repair \the [src].")
- return CLICKCHAIN_DO_NOT_PROPAGATE
- visible_message("[user] begins to repair \the [src].")
- if(do_after(user,20) && integrity < integrity_max)
- if(S.use(1))
- integrity = integrity_max
- visible_message("[user] repairs \the [src].")
- return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
- return CLICKCHAIN_DO_NOT_PROPAGATE
- return ..()
-
-/obj/structure/sandbag/proc/break_to_parts(full_return = 0)
- if(full_return || prob(20))
- new /obj/item/stack/sandbags(src.loc)
+/obj/item/stack/sandbag/empty/update_icon()
+ var/amount = get_amount()
+ if((amount >= 35))
+ icon_state = "sandbag_empty_3"
+ else if((amount < 35) && (amount > 1))
+ icon_state = "sandbag_empty_2"
else
- new /obj/item/stack/material/cloth(src.loc)
- new /obj/item/stack/ore/glass(src.loc)
- qdel(src)
+ icon_state = "sandbag_empty"
+
+/obj/item/stack/sandbag/empty/attackby(var/obj/item/W, var/mob/user)
+ if(istype(W, /obj/item/stack/ore/glass) && !interact(user, src))
+ if(do_after(user, 1 SECONDS, src) && use(1))
+ var/obj/item/stack/ore/glass/O = W
+ var/turf/T = get_turf(user)
+ if(O.use(1))
+ to_chat(user, "You fill the sandbag.")
+ new /obj/item/stack/sandbags(T)
+ return
+ else if(is_sharp(W))
+ user.visible_message("\The [user] begins cutting up [src] with [W].", "You begin cutting up [src] with [W].")
+ if(do_after(user, 3 SECONDS, src) && use(1))
+ to_chat(user, "You cut [src] into pieces!")
+ new /obj/item/stack/material/cloth(drop_location(),rand(1,2))
+ return
+ return ..()
diff --git a/code/game/objects/items/stacks/telecrystal.dm b/code/game/objects/items/stacks/subtypes/telecrystal.dm
similarity index 100%
rename from code/game/objects/items/stacks/telecrystal.dm
rename to code/game/objects/items/stacks/subtypes/telecrystal.dm
diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm
index f0ca7d142af..f396d2d2e0f 100644
--- a/code/game/objects/items/stacks/tiles/tile_types.dm
+++ b/code/game/objects/items/stacks/tiles/tile_types.dm
@@ -89,14 +89,6 @@
desc = "An easy to fit wooden floor tile. It's blue!"
icon_state = "tile-sifwood"
-/obj/item/stack/tile/wood/cyborg
- name = "wood floor tile synthesizer"
- desc = "A device that makes wood floor tiles."
- uses_charge = 1
- charge_costs = list(250)
- stack_type = /obj/item/stack/tile/wood
- stacktype_legacy = /obj/item/stack/tile/wood
-
/*
* Carpets
*/
@@ -298,16 +290,6 @@
materials_base = list(MAT_PLASTIC = SHEET_MATERIAL_AMOUNT / 4)
no_variants = FALSE
-/obj/item/stack/tile/floor/cyborg
- name = "floor tile synthesizer"
- desc = "A device that makes floor tiles."
- gender = NEUTER
- materials_base = null
- uses_charge = 1
- charge_costs = list(250)
- stack_type = /obj/item/stack/tile/floor
- stacktype_legacy = /obj/item/stack/tile/floor
-
/obj/item/stack/tile/floor/sandstone
name = "sandstone tile"
singular_name = "sandstone tile"
@@ -355,14 +337,6 @@
desc = "A section of roofing material. You can use it to repair the ceiling, or expand it."
icon_state = "techtile_grid"
-/obj/item/stack/tile/roofing/cyborg
- name = "roofing synthesizer"
- desc = "A device that makes roofing tiles."
- uses_charge = 1
- charge_costs = list(250)
- stack_type = /obj/item/stack/tile/roofing
- stacktype_legacy = /obj/item/stack/tile/roofing
-
/obj/item/stack/tile/roofing/wood
name = "wood roofing"
singular_name = "wood roofing"
diff --git a/code/game/objects/items/toys/toys.dm b/code/game/objects/items/toys/toys.dm
index e32a6bb7bef..3b9a22bcf05 100644
--- a/code/game/objects/items/toys/toys.dm
+++ b/code/game/objects/items/toys/toys.dm
@@ -1630,7 +1630,7 @@
/obj/item/checker
name = "Checker Piece"
- icon = 'icons/mob/robots.dmi'
+ icon = 'icons/mob/robot-legacy.dmi'
icon_state = "zoomba-combat-shield"
desc = "Some sort of novelty checker piece."
diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm
index 0812cac9ad6..dda877e3453 100644
--- a/code/game/objects/items/trash.dm
+++ b/code/game/objects/items/trash.dm
@@ -135,12 +135,13 @@
if(isrobot(target))
var/mob/living/silicon/robot/R = target
- if(R.module.type == /obj/item/robot_module/robot/quad/jani) // You can now feed the trash borg yay.
- if(!user.attempt_insert_item_for_installation(src, R.vore_selected))
+ if(R.module.type == /datum/prototype/robot_module/nanotrasen/janitor && R.chassis.type == /datum/prototype/robot_chassis/quadruped) // You can now feed the trash borg yay.
+ if(R.vore_selected)
+ if(!user.attempt_insert_item_for_installation(src, R.vore_selected))
+ return
+ playsound(R,'sound/items/eatfood.ogg', rand(10,50), 1)
+ R.visible_message("[user] feeds [R] with [src]!")
return
- playsound(R,'sound/items/eatfood.ogg', rand(10,50), 1)
- R.visible_message("[user] feeds [R] with [src]!")
- return
return ..()
/obj/item/trash/fancyplate
diff --git a/code/game/objects/items/weapons/AI_modules.dm b/code/game/objects/items/weapons/AI_modules.dm
index b2e354f4e76..67b1dbcd726 100644
--- a/code/game/objects/items/weapons/AI_modules.dm
+++ b/code/game/objects/items/weapons/AI_modules.dm
@@ -8,7 +8,7 @@ AI MODULES
/obj/item/aiModule
name = "\improper AI module"
- icon = 'icons/obj/module.dmi'
+ icon = 'icons/items/circuits.dmi'
icon_state = "std_mod"
desc = "An AI Module for transmitting encrypted instructions to the AI."
damage_force = 5.0
@@ -18,7 +18,7 @@ AI MODULES
throw_range = 15
origin_tech = list(TECH_DATA = 3)
preserve_item = 1
- var/datum/ai_laws/laws = null
+ var/datum/ai_lawset/laws = null
/obj/item/aiModule/proc/install(var/atom/movable/AM, var/mob/living/user)
if(!user.IsAdvancedToolUser() && isanimal_legacy_this_is_broken(user))
@@ -313,7 +313,7 @@ AI MODULES
name = "\improper 'Asimov' core AI module"
desc = "An 'Asimov' Core AI Module: 'Reconfigures the AI's core laws.'"
origin_tech = list(TECH_DATA = 3, TECH_MATERIAL = 4)
- laws = new/datum/ai_laws/asimov
+ laws = new/datum/ai_lawset/asimov
/******************** Nanotrasen ********************/
@@ -321,7 +321,7 @@ AI MODULES
name = "'NT Default' Core AI Module"
desc = "An 'NT Default' Core AI Module: 'Reconfigures the AI's core laws.'"
origin_tech = list(TECH_DATA = 3, TECH_MATERIAL = 4)
- laws = new/datum/ai_laws/nanotrasen
+ laws = new/datum/ai_lawset/nanotrasen
/******************** Corporate ********************/
@@ -329,14 +329,14 @@ AI MODULES
name = "\improper 'Corporate' core AI module"
desc = "A 'Corporate' Core AI Module: 'Reconfigures the AI's core laws.'"
origin_tech = list(TECH_DATA = 3, TECH_MATERIAL = 4)
- laws = new/datum/ai_laws/corporate
+ laws = new/datum/ai_lawset/corporate
/******************** Drone ********************/
/obj/item/aiModule/drone
name = "\improper 'Drone' core AI module"
desc = "A 'Drone' Core AI Module: 'Reconfigures the AI's core laws.'"
origin_tech = list(TECH_DATA = 3, TECH_MATERIAL = 4)
- laws = new/datum/ai_laws/drone
+ laws = new/datum/ai_lawset/drone
/****************** P.A.L.A.D.I.N. **************/
@@ -344,7 +344,7 @@ AI MODULES
name = "\improper 'P.A.L.A.D.I.N.' core AI module"
desc = "A P.A.L.A.D.I.N. Core AI Module: 'Reconfigures the AI's core laws.'"
origin_tech = list(TECH_DATA = 3, TECH_MATERIAL = 6)
- laws = new/datum/ai_laws/paladin
+ laws = new/datum/ai_lawset/paladin
/****************** T.Y.R.A.N.T. *****************/
@@ -352,7 +352,7 @@ AI MODULES
name = "\improper 'T.Y.R.A.N.T.' core AI module"
desc = "A T.Y.R.A.N.T. Core AI Module: 'Reconfigures the AI's core laws.'"
origin_tech = list(TECH_DATA = 3, TECH_MATERIAL = 6, TECH_ILLEGAL = 2)
- laws = new/datum/ai_laws/tyrant()
+ laws = new/datum/ai_lawset/tyrant()
/******************** Freeform Core ******************/
@@ -423,7 +423,7 @@ AI MODULES
name = "\improper 'Robocop' core AI module"
desc = "A 'Robocop' Core AI Module: 'Reconfigures the AI's core three laws.'"
origin_tech = list(TECH_DATA = 4)
- laws = new/datum/ai_laws/robocop()
+ laws = new/datum/ai_lawset/robocop()
/******************** Antimov ********************/
@@ -431,4 +431,4 @@ AI MODULES
name = "\improper 'Antimov' core AI module"
desc = "An 'Antimov' Core AI Module: 'Reconfigures the AI's core laws.'"
origin_tech = list(TECH_DATA = 4)
- laws = new/datum/ai_laws/antimov()
+ laws = new/datum/ai_lawset/antimov()
diff --git a/code/game/objects/items/weapons/RCD.dm b/code/game/objects/items/weapons/RCD.dm
index 0d9fc774f48..bc13bd3417e 100644
--- a/code/game/objects/items/weapons/RCD.dm
+++ b/code/game/objects/items/weapons/RCD.dm
@@ -1,4 +1,3 @@
-// Contains the rapid construction device.
/obj/item/rcd
name = "rapid construction device"
desc = "A device used to rapidly build and deconstruct. Reload with compressed matter cartridges."
diff --git a/code/game/objects/items/weapons/RSF.dm b/code/game/objects/items/weapons/RSF.dm
index 5d0ba68d4d5..86030b68284 100644
--- a/code/game/objects/items/weapons/RSF.dm
+++ b/code/game/objects/items/weapons/RSF.dm
@@ -1,9 +1,3 @@
-/*
-CONTAINS:
-RSF
-
-*/
-
/obj/item/rsf
name = "\improper Rapid-Service-Fabricator"
desc = "A device used to rapidly deploy service items."
@@ -155,3 +149,5 @@ RSF
else
stored_matter--
to_chat(user,"The RSF now holds [stored_matter]/30 fabrication-units.")
+
+/obj/item/rsf/loaded
diff --git a/code/game/objects/items/weapons/extinguisher.dm b/code/game/objects/items/weapons/extinguisher.dm
index 325dd1e3134..a12f6c8fcba 100644
--- a/code/game/objects/items/weapons/extinguisher.dm
+++ b/code/game/objects/items/weapons/extinguisher.dm
@@ -23,27 +23,6 @@
var/safety = 1
var/sprite_name = "fire_extinguisher"
-/obj/item/extinguisher/mini
- name = "compact fire extinguisher"
- desc = "A light and compact fibreglass-framed model fire extinguisher."
- icon_state = "miniFE0"
- item_state = "miniFE"
- attack_sound = null //it is much lighter, after all.
- throw_force = 2
- materials_base = list(MAT_PLASTIC = 90)
- w_class = WEIGHT_CLASS_SMALL
- damage_force = 3.0
- max_water = 150
- spray_particles = 3
- sprite_name = "miniFE"
- suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR
- belt_storage_class = BELT_CLASS_MEDIUM
-
-/obj/item/extinguisher/mini/plasman
- name = "emergency phoronoid extinguisher"
- desc = "A mini fire extinguisher for use by burning phoronoids. Let's just hope it works."
- max_water = 300
-
/obj/item/extinguisher/Initialize(mapload)
. = ..()
create_reagents(max_water)
@@ -51,7 +30,7 @@
/obj/item/extinguisher/examine(mob/user, dist)
. = ..()
- . += "[icon2html(thing = src, target = user)] [src.name] contains [src.reagents.total_volume] units of water left!"
+ . += "[icon2html(thing = src, target = user)] [src.name] contains [available_spray_volume()] units of fluid left!"
/obj/item/extinguisher/attack_self(mob/user, datum/event_args/actor/actor)
. = ..()
@@ -62,6 +41,26 @@
desc = "The safety is [safety ? "on" : "off"]."
to_chat(user, "The safety is [safety ? "on" : "off"].")
+/**
+ * @return volume
+ */
+/obj/item/extinguisher/proc/available_spray_volume()
+ if(item_mount)
+ return item_mount.extinguisher_get_volume(src)
+ if(reagents)
+ return reagents.total_volume
+ return 0
+
+/**
+ * @return amount pulled
+ */
+/obj/item/extinguisher/proc/pull_spray_volume(datum/reagent_holder/target, amount)
+ if(item_mount)
+ return item_mount.extinguisher_transfer_volume(src, null, amount, target)
+ if(reagents)
+ return reagents.transfer_to_holder(target, null, amount)
+ return 0
+
/obj/item/extinguisher/proc/propel_object(var/obj/O, mob/user, movementdirection)
if(O.anchored) return
@@ -90,47 +89,67 @@
playsound(src.loc, 'sound/effects/refill.ogg', 50, 1, -6)
return
- if (!safety)
- if (src.reagents.total_volume < 1)
- to_chat(usr, "\The [src] is empty.")
- return
-
- if (world.time < src.last_use + 20)
- return
-
- src.last_use = world.time
-
- playsound(src.loc, 'sound/effects/extinguish.ogg', 75, 1, -3)
-
- var/direction = get_dir(src,target)
-
- if(user.buckled && isobj(user.buckled))
- spawn(0)
- propel_object(user.buckled, user, turn(direction,180))
-
- var/turf/T = get_turf(target)
- var/turf/T1 = get_step(T,turn(direction, 90))
- var/turf/T2 = get_step(T,turn(direction, -90))
-
- var/list/the_targets = list(T,T1,T2)
-
- for(var/a = 1 to spray_particles)
- spawn(0)
- if(!src || !reagents.total_volume) return
-
- var/obj/effect/water/W = new /obj/effect/water(get_turf(src))
- var/turf/my_target
- if(a <= the_targets.len)
- my_target = the_targets[a]
- else
- my_target = pick(the_targets)
- W.create_reagents(spray_amount)
- reagents.trans_to_obj(W, spray_amount)
- W.set_color()
- W.set_up(my_target)
-
- if((istype(usr.loc, /turf/space)) || (usr.lastarea.has_gravity == 0))
- user.newtonian_move(get_dir(target, user))
- else
+ if(safety)
return ..()
- return
+ if (!available_spray_volume())
+ to_chat(usr, "\The [src] is empty.")
+ return
+
+ if (world.time < src.last_use + 20)
+ return
+
+ src.last_use = world.time
+
+ playsound(src.loc, 'sound/effects/extinguish.ogg', 75, 1, -3)
+
+ var/direction = get_dir(src,target)
+
+ if(user.buckled && isobj(user.buckled))
+ spawn(0)
+ propel_object(user.buckled, user, turn(direction,180))
+
+ var/turf/T = get_turf(target)
+ var/turf/T1 = get_step(T,turn(direction, 90))
+ var/turf/T2 = get_step(T,turn(direction, -90))
+
+ var/list/the_targets = list(T,T1,T2)
+
+ for(var/a = 1 to spray_particles)
+ spawn(0)
+ if(!available_spray_volume())
+ return
+
+ var/obj/effect/water/W = new /obj/effect/water(get_turf(src))
+ var/turf/my_target
+ if(a <= the_targets.len)
+ my_target = the_targets[a]
+ else
+ my_target = pick(the_targets)
+ W.create_reagents(spray_amount)
+ pull_spray_volume(W.reagents, spray_amount)
+ W.set_color()
+ W.set_up(my_target)
+
+ if((istype(usr.loc, /turf/space)) || (usr.lastarea.has_gravity == 0))
+ user.newtonian_move(get_dir(target, user))
+
+/obj/item/extinguisher/mini
+ name = "fire extinguisher"
+ desc = "A light and compact fibreglass-framed model fire extinguisher."
+ icon_state = "miniFE0"
+ item_state = "miniFE"
+ attack_sound = null //it is much lighter, after all.
+ throw_force = 2
+ materials_base = list(MAT_PLASTIC = 90)
+ w_class = WEIGHT_CLASS_SMALL
+ damage_force = 3.0
+ max_water = 150
+ spray_particles = 3
+ sprite_name = "miniFE"
+ suit_storage_class = SUIT_STORAGE_CLASS_SOFTWEAR | SUIT_STORAGE_CLASS_HARDWEAR | SUIT_STORAGE_CLASS_ARMOR
+ belt_storage_class = BELT_CLASS_MEDIUM
+
+/obj/item/extinguisher/mini/plasman
+ name = "emergency phoronoid extinguisher"
+ desc = "A mini fire extinguisher for use by burning phoronoids. Let's just hope it works."
+ max_water = 300
diff --git a/code/game/objects/items/weapons/gift_wrappaper.dm b/code/game/objects/items/weapons/gift_wrappaper.dm
index 4d5562c5657..60b581accf9 100644
--- a/code/game/objects/items/weapons/gift_wrappaper.dm
+++ b/code/game/objects/items/weapons/gift_wrappaper.dm
@@ -276,7 +276,7 @@
return
var/mob/living/carbon/human/H = target
- if (istype(H.inventory.get_slot_single(/datum/inventory_slot/inventory/suit), /obj/item/clothing/suit/straight_jacket) || H.stat)
+ if (istype(H.inventory.get_slot_single(/datum/inventory_slot/inventory/suit::id), /obj/item/clothing/suit/straight_jacket) || H.stat)
if (src.amount > 2)
var/obj/effect/spresent/present = new /obj/effect/spresent (H.loc)
src.amount -= 2
diff --git a/code/game/objects/items/weapons/handcuffs.dm b/code/game/objects/items/weapons/handcuffs.dm
index 35e5d7c4276..c83d3b8ca82 100644
--- a/code/game/objects/items/weapons/handcuffs.dm
+++ b/code/game/objects/items/weapons/handcuffs.dm
@@ -274,7 +274,7 @@ var/last_chew = 0
if(!H.can_equip(src, SLOT_ID_LEGCUFFED, user = user))
return FALSE
- if(istype(H.inventory.get_slot(/datum/inventory_slot/inventory/shoes), /obj/item/clothing/shoes/magboots/hardsuit) && !elastic) // Can't cuff someone who's in a deployed hardsuit.
+ if(istype(H.inventory.get_slot(/datum/inventory_slot/inventory/shoes::id), /obj/item/clothing/shoes/magboots/hardsuit) && !elastic) // Can't cuff someone who's in a deployed hardsuit.
to_chat(user, "\The [src] won't fit around \the [H.shoes]!")
return 0
diff --git a/code/game/objects/items/weapons/other.dm b/code/game/objects/items/weapons/other.dm
index b76441a8ae5..4b89dfd3bab 100644
--- a/code/game/objects/items/weapons/other.dm
+++ b/code/game/objects/items/weapons/other.dm
@@ -273,7 +273,7 @@
w_class = WEIGHT_CLASS_SMALL
/obj/item/module
- icon = 'icons/obj/module.dmi'
+ icon = 'icons/items/circuits.dmi'
icon_state = "std_module"
item_state = "std_mod"
w_class = WEIGHT_CLASS_SMALL
diff --git a/code/game/objects/structures/cliff.dm b/code/game/objects/structures/cliff.dm
index 7c646fc937a..ad3395c486d 100644
--- a/code/game/objects/structures/cliff.dm
+++ b/code/game/objects/structures/cliff.dm
@@ -229,7 +229,7 @@ two tiles on initialization, and which way a cliff is facing may change during m
// Cliff climbing requires climbing gear.
if(ishuman(user))
var/mob/living/carbon/human/H = user
- var/obj/item/clothing/shoes/shoes = H.inventory.get_slot(/datum/inventory_slot/inventory/shoes)
+ var/obj/item/clothing/shoes/shoes = H.inventory.get_slot(/datum/inventory_slot/inventory/shoes::id)
if(istype(shoes) && shoes.rock_climbing)
return TRUE
var/obj/item/held = H.get_active_held_item()
diff --git a/code/game/objects/structures/door_assembly.dm b/code/game/objects/structures/door_assembly.dm
index 0eefaa00519..91329b492c3 100644
--- a/code/game/objects/structures/door_assembly.dm
+++ b/code/game/objects/structures/door_assembly.dm
@@ -162,8 +162,7 @@
update_state()
/obj/structure/door_assembly/attack_robot(mob/living/silicon/robot/user)
- if(Adjacent(user) && (user.module && (istype(user.module,/obj/item/robot_module/robot/engineering/general)) \
- || istype(user.module,/obj/item/robot_module/drone))) //Only dron (and engiborg) needs this.
+ if(Adjacent(user))
rename_door(user)
/obj/structure/door_assembly/attackby(obj/item/W as obj, mob/user as mob)
diff --git a/code/game/objects/structures/ghost_pods/silicon.dm b/code/game/objects/structures/ghost_pods/silicon.dm
index 6df67a52f14..9c0cbdaf3ff 100644
--- a/code/game/objects/structures/ghost_pods/silicon.dm
+++ b/code/game/objects/structures/ghost_pods/silicon.dm
@@ -17,7 +17,7 @@
/obj/structure/ghost_pod/manual/lost_drone/create_occupant(var/mob/M)
density = FALSE
- var/mob/living/silicon/robot/lost/randomlaws/R = new(get_turf(src))
+ var/mob/living/silicon/robot/preset_module/lost/randomlaws/R = new(get_turf(src))
R.adjustBruteLoss(rand(5, 30))
R.adjustFireLoss(rand(5, 10))
if(M.mind)
@@ -48,7 +48,7 @@
/obj/structure/ghost_pod/automatic/gravekeeper_drone/create_occupant(var/mob/M)
density = FALSE
- var/mob/living/silicon/robot/gravekeeper/R = new(get_turf(src))
+ var/mob/living/silicon/robot/preset_module/gravekeeper/R = new(get_turf(src))
if(M.mind)
M.mind.transfer(R)
// Put this text here before ckey change so that their laws are shown below it, since borg login() shows it.
@@ -60,51 +60,51 @@
R.Namepick()
..()
-/obj/structure/ghost_pod/ghost_activated/swarm_drone
- name = "drone shell"
- desc = "A heavy metallic ball."
- icon = 'icons/mob/swarmbot.dmi'
- icon_state = "swarmer_unactivated"
- density = FALSE
- anchored = FALSE
+// /obj/structure/ghost_pod/ghost_activated/swarm_drone
+// name = "drone shell"
+// desc = "A heavy metallic ball."
+// icon = 'icons/mob/swarmbot.dmi'
+// icon_state = "swarmer_unactivated"
+// density = FALSE
+// anchored = FALSE
- var/drone_class = "general"
- var/drone_type = /mob/living/silicon/robot/drone/swarm
+// var/drone_class = "general"
+// var/drone_type = /mob/living/silicon/robot/drone/swarm
-/obj/structure/ghost_pod/ghost_activated/swarm_drone/create_occupant(var/mob/M)
- var/mob/living/silicon/robot/drone/swarm/R = new drone_type(get_turf(src))
- if(M.mind)
- M.mind.transfer(R)
- to_chat(M, "You are [R], the remnant of some distant species, mechanical or flesh, living or dead.")
- M.transfer_client_to(R)
- visible_message("As \the [src] shudders, it glows before lifting itself with three shimmering limbs!")
- spawn(3 SECONDS)
- to_chat(R,"Many of your tools are standard drone devices, however others provide you with particular benefits.")
- to_chat(R,"Unlike standard drones, you are capable of utilizing 'zero point wells', found in your 'spells' tab.")
- to_chat(R,"Here you will also find your replication ability(s), depending on the type of drone you are.")
- to_chat(R,"Gunners have a special anti-personnel gun capable of shocking or punching through armor with low damage.")
- to_chat(R,"Impalers have an energy-lance.")
- to_chat(R,"General drones have the unique ability to produce one of each of these two types of shells per generation.")
- if(!QDELETED(src))
- qdel(src)
+// /obj/structure/ghost_pod/ghost_activated/swarm_drone/create_occupant(var/mob/M)
+// var/mob/living/silicon/robot/drone/swarm/R = new drone_type(get_turf(src))
+// if(M.mind)
+// M.mind.transfer(R)
+// to_chat(M, "You are [R], the remnant of some distant species, mechanical or flesh, living or dead.")
+// M.transfer_client_to(R)
+// visible_message("As \the [src] shudders, it glows before lifting itself with three shimmering limbs!")
+// spawn(3 SECONDS)
+// to_chat(R,"Many of your tools are standard drone devices, however others provide you with particular benefits.")
+// to_chat(R,"Unlike standard drones, you are capable of utilizing 'zero point wells', found in your 'spells' tab.")
+// to_chat(R,"Here you will also find your replication ability(s), depending on the type of drone you are.")
+// to_chat(R,"Gunners have a special anti-personnel gun capable of shocking or punching through armor with low damage.")
+// to_chat(R,"Impalers have an energy-lance.")
+// to_chat(R,"General drones have the unique ability to produce one of each of these two types of shells per generation.")
+// if(!QDELETED(src))
+// qdel(src)
-/obj/structure/ghost_pod/ghost_activated/swarm_drone/event/Initialize(mapload)
- . = ..()
+// /obj/structure/ghost_pod/ghost_activated/swarm_drone/event/Initialize(mapload)
+// . = ..()
- var/turf/T = get_turf(src)
- say_dead_object("A [drone_class] swarm drone shell is now available in \the [T.loc].", src)
+// var/turf/T = get_turf(src)
+// say_dead_object("A [drone_class] swarm drone shell is now available in \the [T.loc].", src)
-/obj/structure/ghost_pod/ghost_activated/swarm_drone/event/gunner
- name = "gunner shell"
+// /obj/structure/ghost_pod/ghost_activated/swarm_drone/event/gunner
+// name = "gunner shell"
- drone_class = "gunner"
- drone_type = /mob/living/silicon/robot/drone/swarm/gunner
+// drone_class = "gunner"
+// drone_type = /mob/living/silicon/robot/drone/swarm/gunner
-/obj/structure/ghost_pod/ghost_activated/swarm_drone/event/melee
- name = "impaler shell"
+// /obj/structure/ghost_pod/ghost_activated/swarm_drone/event/melee
+// name = "impaler shell"
- drone_class = "impaler"
- drone_type = /mob/living/silicon/robot/drone/swarm/melee
+// drone_class = "impaler"
+// drone_type = /mob/living/silicon/robot/drone/swarm/melee
/obj/structure/ghost_pod/manual/lost_drone/dogborg
@@ -114,7 +114,7 @@
return ..()
else
density = FALSE
- var/mob/living/silicon/robot/stray/randomlaws/R = new(get_turf(src))
+ var/mob/living/silicon/robot/preset_module/lost/randomlaws/R = new(get_turf(src))
R.adjustBruteLoss(rand(5, 30))
R.adjustFireLoss(rand(5, 10))
if(M.mind)
diff --git a/code/game/objects/structures/loot_piles.dm b/code/game/objects/structures/loot_piles.dm
index d447a055440..694f4c00bd7 100644
--- a/code/game/objects/structures/loot_piles.dm
+++ b/code/game/objects/structures/loot_piles.dm
@@ -369,7 +369,7 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh
/obj/item/stock_parts/subspace/treatment,
/obj/item/frame,
/obj/item/broken_device/random,
- /obj/item/borg/upgrade/restart,
+ /obj/item/robot_upgrade/restart,
/obj/item/cell/basic/tier_2/weapon,
/obj/item/cell/basic/tier_2/medium,
/obj/item/cell/basic/tier_2/small,
@@ -428,10 +428,9 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh
/obj/item/tvcamera,
/obj/item/universal_translator,
/obj/item/aicard,
- /obj/item/borg/upgrade/jetpack,
- /obj/item/borg/upgrade/advhealth,
- /obj/item/borg/upgrade/vtec,
- /obj/item/borg/upgrade/tasercooler,
+ /obj/item/robot_upgrade/jetpack,
+ /obj/item/robot_upgrade/advhealth,
+ /obj/item/robot_upgrade/vtec,
/obj/item/vehicle_module/weapon/energy/riggedlaser,
/obj/item/vehicle_module/tool/drill/diamonddrill,
/obj/item/hardsuit_module/device/drill,
@@ -1130,11 +1129,10 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh
rare_loot = list(
/obj/item/cell/basic/tier_3/medium,
- /obj/item/borg/upgrade/restart,
- /obj/item/borg/upgrade/jetpack,
- /obj/item/borg/upgrade/tasercooler,
- /obj/item/borg/upgrade/syndicate,
- /obj/item/borg/upgrade/vtec
+ /obj/item/robot_upgrade/restart,
+ /obj/item/robot_upgrade/jetpack,
+ /obj/item/robot_upgrade/syndicate,
+ /obj/item/robot_upgrade/vtec
)
// Contains old mediciation, most of it unidentified and has a good chance of being useless.
diff --git a/code/game/objects/structures/props/swarm.dm b/code/game/objects/structures/props/swarm.dm
index 5f46aa88711..4551f60d57f 100644
--- a/code/game/objects/structures/props/swarm.dm
+++ b/code/game/objects/structures/props/swarm.dm
@@ -1,152 +1,153 @@
-/obj/structure/cult/pylon/swarm
- name = "Swarm Construct"
- desc = "A small pod."
- icon = 'icons/mob/swarmbot.dmi'
- icon_state = "pod"
- light_color = "#00B2B2"
+// TODO: IOU swarmers.
+// /obj/structure/cult/pylon/swarm
+// name = "Swarm Construct"
+// desc = "A small pod."
+// icon = 'icons/mob/swarmbot.dmi'
+// icon_state = "pod"
+// light_color = "#00B2B2"
- shatter_message = "The energetic field shatters!"
- impact_sound = 'sound/effects/Glasshit.ogg'
- shatter_sound = 'sound/effects/phasein.ogg'
+// shatter_message = "The energetic field shatters!"
+// impact_sound = 'sound/effects/Glasshit.ogg'
+// shatter_sound = 'sound/effects/phasein.ogg'
- var/list/active_beams
+// var/list/active_beams
-/obj/structure/cult/pylon/swarm/CanAllowThrough(atom/movable/mover, turf/target)
- if(istype(mover, /mob/living))
- var/mob/living/L = mover
- if(L.has_iff_faction(MOB_IFF_FACTION_SWARMER))
- return TRUE
- else if(istype(mover, /obj/projectile))
- var/obj/projectile/P = mover
- if(isliving(P.firer))
- var/mob/living/L = P.firer
- if(L.has_iff_faction(MOB_IFF_FACTION_SWARMER))
- return TRUE
- return ..()
+// /obj/structure/cult/pylon/swarm/CanAllowThrough(atom/movable/mover, turf/target)
+// if(istype(mover, /mob/living))
+// var/mob/living/L = mover
+// if(L.has_iff_faction(MOB_IFF_FACTION_SWARMER))
+// return TRUE
+// else if(istype(mover, /obj/projectile))
+// var/obj/projectile/P = mover
+// if(isliving(P.firer))
+// var/mob/living/L = P.firer
+// if(L.has_iff_faction(MOB_IFF_FACTION_SWARMER))
+// return TRUE
+// return ..()
-/obj/structure/cult/pylon/swarm/Initialize(mapload)
- . = ..()
- active_beams = list()
+// /obj/structure/cult/pylon/swarm/Initialize(mapload)
+// . = ..()
+// active_beams = list()
-/obj/structure/cult/pylon/swarm/Destroy()
- for(var/datum/beam_legacy/B in active_beams)
- QDEL_NULL(B)
- active_beams = null
- ..()
+// /obj/structure/cult/pylon/swarm/Destroy()
+// for(var/datum/beam_legacy/B in active_beams)
+// QDEL_NULL(B)
+// active_beams = null
+// ..()
-/obj/structure/cult/pylon/swarm/pylonhit(var/damage)
- if(!isbroken)
- if(prob(1 + damage * 3))
- visible_message("[shatter_message]")
- STOP_PROCESSING(SSobj, src)
- playsound(get_turf(src),shatter_sound, 75, 1)
- isbroken = 1
- density = 0
- icon_state = "[initial(icon_state)]-broken"
- set_light(0)
+// /obj/structure/cult/pylon/swarm/pylonhit(var/damage)
+// if(!isbroken)
+// if(prob(1 + damage * 3))
+// visible_message("[shatter_message]")
+// STOP_PROCESSING(SSobj, src)
+// playsound(get_turf(src),shatter_sound, 75, 1)
+// isbroken = 1
+// density = 0
+// icon_state = "[initial(icon_state)]-broken"
+// set_light(0)
-/obj/structure/cult/pylon/swarm/attackpylon(mob/user as mob, var/damage)
- if(!isbroken)
- if(prob(1 + damage * 3))
- user.visible_message(
- "[user] smashed \the [src]!",
- "You hit \the [src], and its crystal breaks apart!",
- "You hear a tinkle of crystalline shards."
- )
- STOP_PROCESSING(SSobj, src)
- user.do_attack_animation(src)
- playsound(get_turf(src),shatter_sound, 75, 1)
- isbroken = 1
- density = 0
- icon_state = "[initial(icon_state)]-broken"
- set_light(0)
- else
- to_chat(user, "You hit \the [src]!")
- playsound(get_turf(src),impact_sound, 75, 1)
- else
- if(prob(damage * 2))
- to_chat(user, "You pulverize what was left of \the [src]!")
- qdel(src)
- else
- to_chat(user, "You hit \the [src]!")
- playsound(get_turf(src),impact_sound, 75, 1)
+// /obj/structure/cult/pylon/swarm/attackpylon(mob/user as mob, var/damage)
+// if(!isbroken)
+// if(prob(1 + damage * 3))
+// user.visible_message(
+// "[user] smashed \the [src]!",
+// "You hit \the [src], and its crystal breaks apart!",
+// "You hear a tinkle of crystalline shards."
+// )
+// STOP_PROCESSING(SSobj, src)
+// user.do_attack_animation(src)
+// playsound(get_turf(src),shatter_sound, 75, 1)
+// isbroken = 1
+// density = 0
+// icon_state = "[initial(icon_state)]-broken"
+// set_light(0)
+// else
+// to_chat(user, "You hit \the [src]!")
+// playsound(get_turf(src),impact_sound, 75, 1)
+// else
+// if(prob(damage * 2))
+// to_chat(user, "You pulverize what was left of \the [src]!")
+// qdel(src)
+// else
+// to_chat(user, "You hit \the [src]!")
+// playsound(get_turf(src),impact_sound, 75, 1)
-/obj/structure/cult/pylon/swarm/pylon_unique()
- . = ..()
+// /obj/structure/cult/pylon/swarm/pylon_unique()
+// . = ..()
- return .
+// return .
-/obj/structure/cult/pylon/swarm/zp_well
- name = "Zero Point Well"
- desc = "Infinite cosmic power, itty bitty usability."
+// /obj/structure/cult/pylon/swarm/zp_well
+// name = "Zero Point Well"
+// desc = "Infinite cosmic power, itty bitty usability."
- icon_state = "trap"
+// icon_state = "trap"
- description_info = "An infinitely small point in space that may or may not be used to supply power to some form of advanced machine."
+// description_info = "An infinitely small point in space that may or may not be used to supply power to some form of advanced machine."
- activation_cooldown = 0 // These things run constantly.
+// activation_cooldown = 0 // These things run constantly.
-/obj/structure/cult/pylon/swarm/zp_well/pylon_unique()
- . = ..()
+// /obj/structure/cult/pylon/swarm/zp_well/pylon_unique()
+// . = ..()
- for(var/mob/living/silicon/robot/drone/swarm/S in view(3, src))
- var/has_beam = FALSE
- for(var/datum/beam_legacy/B in active_beams)
- if(B.target == S)
- has_beam = TRUE
- break
+// for(var/mob/living/silicon/robot/drone/swarm/S in view(3, src))
+// var/has_beam = FALSE
+// for(var/datum/beam_legacy/B in active_beams)
+// if(B.target == S)
+// has_beam = TRUE
+// break
- if(!has_beam)
- active_beams |= Beam(S,icon='icons/effects/beam.dmi',icon_state="holo_beam",time=3 SECONDS,maxdistance=3,beam_type = /obj/effect/ebeam)
+// if(!has_beam)
+// active_beams |= Beam(S,icon='icons/effects/beam.dmi',icon_state="holo_beam",time=3 SECONDS,maxdistance=3,beam_type = /obj/effect/ebeam)
- if(S.cell)
- S.cell.give(rand(5, 30))
+// if(S.cell)
+// S.cell.give(rand(5, 30))
- . = TRUE
+// . = TRUE
- return .
+// return .
-/obj/structure/cult/pylon/swarm/defender
- name = "Zero Point Wall"
- desc = "Infinite cosmic power, itty bitty passability."
+// /obj/structure/cult/pylon/swarm/defender
+// name = "Zero Point Wall"
+// desc = "Infinite cosmic power, itty bitty passability."
- icon_state = "barricade"
+// icon_state = "barricade"
- description_info = "An infinitely small point in space spread upon infinitely many finitely-bounded points in space. Nice."
+// description_info = "An infinitely small point in space spread upon infinitely many finitely-bounded points in space. Nice."
-/obj/structure/cult/pylon/swarm/defender/pylonhit(var/damage)
- if(!isbroken)
- if(prob(1 + damage * 3) && round(damage * 0.8) >= 30)
- visible_message("[shatter_message]")
- STOP_PROCESSING(SSobj, src)
- playsound(get_turf(src),shatter_sound, 75, 1)
- isbroken = 1
- density = 0
- icon_state = "[initial(icon_state)]-broken"
- set_light(0)
+// /obj/structure/cult/pylon/swarm/defender/pylonhit(var/damage)
+// if(!isbroken)
+// if(prob(1 + damage * 3) && round(damage * 0.8) >= 30)
+// visible_message("[shatter_message]")
+// STOP_PROCESSING(SSobj, src)
+// playsound(get_turf(src),shatter_sound, 75, 1)
+// isbroken = 1
+// density = 0
+// icon_state = "[initial(icon_state)]-broken"
+// set_light(0)
-/obj/structure/cult/pylon/swarm/defender/attackpylon(mob/user as mob, var/damage)
- if(!isbroken)
- if(prob(1 + damage * 3) && round(damage * 0.8) >= 25)
- user.visible_message(
- "[user] smashed \the [src]!",
- "You hit \the [src], and its crystal breaks apart!",
- "You hear a tinkle of crystalline shards."
- )
- STOP_PROCESSING(SSobj, src)
- user.do_attack_animation(src)
- playsound(get_turf(src),shatter_sound, 75, 1)
- isbroken = 1
- density = 0
- icon_state = "[initial(icon_state)]-broken"
- set_light(0)
- else
- to_chat(user, "You hit \the [src]!")
- playsound(get_turf(src),impact_sound, 75, 1)
- else
- if(prob(damage * 2))
- to_chat(user, "You pulverize what was left of \the [src]!")
- qdel(src)
- else
- to_chat(user, "You hit \the [src]!")
- playsound(get_turf(src),impact_sound, 75, 1)
+// /obj/structure/cult/pylon/swarm/defender/attackpylon(mob/user as mob, var/damage)
+// if(!isbroken)
+// if(prob(1 + damage * 3) && round(damage * 0.8) >= 25)
+// user.visible_message(
+// "[user] smashed \the [src]!",
+// "You hit \the [src], and its crystal breaks apart!",
+// "You hear a tinkle of crystalline shards."
+// )
+// STOP_PROCESSING(SSobj, src)
+// user.do_attack_animation(src)
+// playsound(get_turf(src),shatter_sound, 75, 1)
+// isbroken = 1
+// density = 0
+// icon_state = "[initial(icon_state)]-broken"
+// set_light(0)
+// else
+// to_chat(user, "You hit \the [src]!")
+// playsound(get_turf(src),impact_sound, 75, 1)
+// else
+// if(prob(damage * 2))
+// to_chat(user, "You pulverize what was left of \the [src]!")
+// qdel(src)
+// else
+// to_chat(user, "You hit \the [src]!")
+// playsound(get_turf(src),impact_sound, 75, 1)
diff --git a/code/game/objects/structures/sandbag.dm b/code/game/objects/structures/sandbag.dm
new file mode 100644
index 00000000000..d755c98dc6c
--- /dev/null
+++ b/code/game/objects/structures/sandbag.dm
@@ -0,0 +1,61 @@
+/obj/structure/sandbag
+ name = "sandbag barricade"
+ desc = "A barrier made of stacked sandbags."
+ icon = 'icons/obj/structures/sandbags.dmi'
+ icon_state = "sandbags-0"
+ base_icon_state = "sandbags"
+ anchored = TRUE
+ density = TRUE
+ pass_flags_self = ATOM_PASS_TABLE | ATOM_PASS_THROWN | ATOM_PASS_CLICK
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = (SMOOTH_GROUP_SANDBAGS)
+ canSmoothWith = (SMOOTH_GROUP_SANDBAGS)
+ integrity = 200
+ integrity_max = 200
+
+ var/vestigial = TRUE
+
+/obj/structure/sandbag/Initialize(mapload)
+ . = ..()
+ for(var/obj/structure/sandbag/S in loc)
+ if(S != src)
+ break_to_parts(full_return = 1)
+ return
+ if(mapload)
+ return INITIALIZE_HINT_LATELOAD
+ else
+ //update_connections(TRUE)
+ update_icon()
+
+/obj/structure/sandbag/LateInitialize()
+ //update_connections(FALSE)
+ update_icon()
+
+/obj/structure/sandbag/Destroy()
+ //update_connections(TRUE)
+ . = ..()
+
+/obj/structure/sandbag/attackby(obj/item/W as obj, mob/user as mob)
+ user.setClickCooldownLegacy(user.get_attack_speed_legacy(W))
+ if(istype(W, /obj/item/stack/sandbags))
+ var/obj/item/stack/sandbags/S = W
+ if(integrity < integrity_max)
+ if(S.get_amount() < 1)
+ to_chat(user, "You need one sandbag to repair \the [src].")
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ visible_message("[user] begins to repair \the [src].")
+ if(do_after(user,20) && integrity < integrity_max)
+ if(S.use(1))
+ integrity = integrity_max
+ visible_message("[user] repairs \the [src].")
+ return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ return ..()
+
+/obj/structure/sandbag/proc/break_to_parts(full_return = 0)
+ if(full_return || prob(20))
+ new /obj/item/stack/sandbags(src.loc)
+ else
+ new /obj/item/stack/material/cloth(src.loc)
+ new /obj/item/stack/ore/glass(src.loc)
+ qdel(src)
diff --git a/code/game/objects/structures/windoor_assembly.dm b/code/game/objects/structures/windoor_assembly.dm
index 9e55a20c580..0ef45cb18ea 100644
--- a/code/game/objects/structures/windoor_assembly.dm
+++ b/code/game/objects/structures/windoor_assembly.dm
@@ -77,8 +77,7 @@
update_state()
/obj/structure/windoor_assembly/attack_robot(mob/living/silicon/robot/user)
- if(Adjacent(user) && (user.module && (istype(user.module,/obj/item/robot_module/robot/engineering/general)) \
- || istype(user.module,/obj/item/robot_module/drone))) //Only dron (and engiborg) needs this.
+ if(Adjacent(user))
rename_door(user)
/obj/structure/windoor_assembly/attackby(obj/item/W as obj, mob/user as mob)
diff --git a/code/game/objects/systems/storage/storage-screen_object.dm b/code/game/objects/systems/storage/storage-screen_object.dm
index 6edb28e0096..bebe29849a6 100644
--- a/code/game/objects/systems/storage/storage-screen_object.dm
+++ b/code/game/objects/systems/storage/storage-screen_object.dm
@@ -6,7 +6,7 @@
appearance_flags = APPEARANCE_UI | KEEP_TOGETHER | TILE_BOUND
plane = HUD_PLANE
layer = HUD_LAYER_STORAGE
- icon = 'icons/screen/hud/common/storage.dmi'
+ icon = 'icons/screen/hud/styles/common/storage.dmi'
/atom/movable/screen/storage/closer
name = "close"
diff --git a/code/game/objects/systems/storage/subtypes/stack.dm b/code/game/objects/systems/storage/subtypes/stack.dm
index 1b6c90328f3..0de85f371cf 100644
--- a/code/game/objects/systems/storage/subtypes/stack.dm
+++ b/code/game/objects/systems/storage/subtypes/stack.dm
@@ -67,7 +67,7 @@
return ..()
var/atom/indirection = real_contents_loc()
for(var/obj/item/stack/other in indirection)
- if(!stack.can_merge(other))
+ if(!stack.can_merge_into(other))
continue
other.add(stack.amount, TRUE)
// this should delete the stack (?!)
diff --git a/code/game/rendering/actor_huds/actor_hud-screen_object.dm b/code/game/rendering/actor_huds/actor_hud-screen_object.dm
index eebdde11a45..fd7efc59807 100644
--- a/code/game/rendering/actor_huds/actor_hud-screen_object.dm
+++ b/code/game/rendering/actor_huds/actor_hud-screen_object.dm
@@ -5,9 +5,8 @@
* The screen objects for actor HUDs
*/
/atom/movable/screen/actor_hud
-
/// our owning actor hud
- var/datum/actor_hud/inventory/hud
+ var/datum/actor_hud/hud
/atom/movable/screen/actor_hud/Initialize(mapload, datum/actor_hud/inventory/hud)
. = ..()
diff --git a/code/game/rendering/actor_huds/actor_hud.dm b/code/game/rendering/actor_huds/actor_hud.dm
index c9bca3642b9..5b95e6f5197 100644
--- a/code/game/rendering/actor_huds/actor_hud.dm
+++ b/code/game/rendering/actor_huds/actor_hud.dm
@@ -68,10 +68,14 @@
/**
* syncs hud
+ *
+ * @return objects touched
*/
/datum/actor_hud/proc/sync_to_preferences(datum/hud_preferences/preference_set)
- for(var/atom/movable/screen/screen_object in screens())
+ var/list/atom/movable/screen/screens = screens()
+ for(var/atom/movable/screen/screen_object in screens)
screen_object.sync_to_preferences(preference_set)
+ return screens
/**
* returns all screens we should apply to a client
diff --git a/code/game/rendering/actor_huds/actor_hud_holder.dm b/code/game/rendering/actor_huds/actor_hud_holder.dm
index 6fccda55890..ef5d09d5885 100644
--- a/code/game/rendering/actor_huds/actor_hud_holder.dm
+++ b/code/game/rendering/actor_huds/actor_hud_holder.dm
@@ -12,16 +12,20 @@
/// inventory hud
var/datum/actor_hud/inventory/inventory
+ /// robot inventory hud
+ var/datum/actor_hud/robot_inventory/robot_inventory
/datum/actor_hud_holder/New(client/C)
// set owner
owner = C
// create huds
inventory = new(src)
+ robot_inventory = new(src)
/datum/actor_hud_holder/Destroy()
// destroy huds
QDEL_NULL(inventory)
+ QDEL_NULL(robot_inventory)
// teardown owner
owner = null
// do rest
@@ -31,20 +35,22 @@
* reset every hud to a mob
*/
/datum/actor_hud_holder/proc/bind_all_to_mob(mob/target)
- inventory.bind_to_mob(target)
+ inventory?.bind_to_mob(target)
+ robot_inventory?.bind_to_mob(target)
/**
* syncs hud preferences
*/
/datum/actor_hud_holder/proc/sync_all_to_preferences(datum/hud_preferences/preference_set)
inventory?.sync_to_preferences(preference_set)
+ robot_inventory?.sync_to_preferences(preference_set)
/**
* get all screens
*/
/datum/actor_hud_holder/proc/screens()
. = list()
- for(var/datum/actor_hud/hud as anything in all_huds())
+ for(var/datum/actor_hud/hud in all_huds())
. += hud.screens()
/**
@@ -52,7 +58,7 @@
*/
/datum/actor_hud_holder/proc/images()
. = list()
- for(var/datum/actor_hud/hud as anything in all_huds())
+ for(var/datum/actor_hud/hud in all_huds())
. += hud.images()
/**
@@ -61,6 +67,7 @@
/datum/actor_hud_holder/proc/all_huds()
return list(
inventory,
+ robot_inventory,
)
/**
diff --git a/code/game/rendering/actor_huds/huds/inventory-screen_object.dm b/code/game/rendering/actor_huds/huds/inventory-screen_object.dm
index 24474c4c565..6d0969af987 100644
--- a/code/game/rendering/actor_huds/huds/inventory-screen_object.dm
+++ b/code/game/rendering/actor_huds/huds/inventory-screen_object.dm
@@ -6,7 +6,7 @@
*/
/atom/movable/screen/actor_hud/inventory
name = "inventory"
- icon = 'icons/screen/hud/midnight/inventory.dmi'
+ icon = 'icons/screen/hud/styles/midnight/inventory.dmi'
plane = HUD_PLANE
layer = HUD_LAYER_INVENTORY
@@ -40,7 +40,7 @@
plane = HUD_ITEM_PLANE
layer = HUD_ITEM_LAYER_BASE
- var/self_icon = 'icons/screen/hud/midnight/inventory.dmi'
+ var/self_icon = 'icons/screen/hud/styles/midnight/inventory.dmi'
var/self_icon_state = ""
var/self_alpha = 255
var/self_color = "#ffffff"
@@ -127,24 +127,38 @@
var/handcuffed = FALSE
/// should we have the active overlay?
var/active = FALSE
+ /// use robot icons?
+ var/use_robot_icons = FALSE
-/atom/movable/screen/actor_hud/inventory/plate/hand/Initialize(mapload, datum/inventory/host, hand_index)
- . = ..()
+/atom/movable/screen/actor_hud/inventory/plate/hand/Initialize(mapload, datum/actor_hud/inventory/host, hand_index)
src.hand_index = hand_index
- sync_index(hand_index)
+ src.use_robot_icons = host.inv_held_items_use_robot_icon
+ . = ..()
+ sync_index(hand_index, host.inv_held_items_row_mode)
update_icon()
/atom/movable/screen/actor_hud/inventory/plate/hand/sync_style(datum/hud_style/style, style_alpha, style_color)
- self_icon = style.inventory_icons
+ if(use_robot_icons)
+ self_icon = style.robot_icons
+ else
+ self_icon = style.inventory_icons
..()
/atom/movable/screen/actor_hud/inventory/plate/hand/handle_inventory_click(mob/user, obj/item/with_item)
- hud.owner.mob.swap_hand(hand_index)
+ var/datum/actor_hud/inventory/inventory_hud = hud
+ inventory_hud.owner.mob.swap_hand(hand_index)
-/atom/movable/screen/actor_hud/inventory/plate/hand/proc/sync_index(index = hand_index)
- screen_loc = SCREEN_LOC_MOB_HUD_INVENTORY_HAND(index)
- name = "[index % 2? "left" : "right"] hand[index > 2? " #[index]" : ""]"
- self_icon_state = "hand-[index % 2? "left" : "right"]"
+/atom/movable/screen/actor_hud/inventory/plate/hand/proc/sync_index(index = hand_index, override_per_row = 2)
+ if(use_robot_icons)
+ name = "hand #[index]"
+ self_icon_state = "hand"
+ return
+ if(override_per_row == 2)
+ name = "[index % 2? "left" : "right"] hand[index > 2? " #[index]" : ""]"
+ self_icon_state = "hand-[index % 2? "left" : "right"]"
+ else
+ name = "hand"
+ self_icon_state = "hand-left"
/atom/movable/screen/actor_hud/inventory/plate/hand/proc/set_handcuffed(state)
if(state == handcuffed)
@@ -167,12 +181,11 @@
add_overlay(active_image)
/**
- * Button: 'swap hand'
+ * Button: 'open inventory slots'
*/
/atom/movable/screen/actor_hud/inventory/drawer
name = "drawer"
icon_state = "drawer"
- screen_loc = SCREEN_LOC_MOB_HUD_INVENTORY_DRAWER
/atom/movable/screen/actor_hud/inventory/drawer/sync_style(datum/hud_style/style, style_alpha, style_color)
..()
@@ -180,10 +193,12 @@
/atom/movable/screen/actor_hud/inventory/drawer/on_click(mob/user, list/params)
// todo: remote control
- hud.toggle_hidden_class(INVENTORY_HUD_CLASS_DRAWER, INVENTORY_HUD_HIDE_SOURCE_DRAWER)
+ var/datum/actor_hud/inventory/inventory_hud = hud
+ inventory_hud.toggle_hidden_class(INVENTORY_HUD_CLASS_DRAWER, INVENTORY_HUD_HIDE_SOURCE_DRAWER)
/atom/movable/screen/actor_hud/inventory/drawer/update_icon_state()
- icon_state = "[(INVENTORY_HUD_CLASS_DRAWER in hud.hidden_classes) ? "drawer" : "drawer-active"]"
+ var/datum/actor_hud/inventory/inventory_hud = hud
+ icon_state = "[(INVENTORY_HUD_CLASS_DRAWER in inventory_hud.hidden_classes) ? "drawer" : "drawer-active"]"
return ..()
/**
@@ -194,8 +209,7 @@
icon_state = "hand-swap"
/atom/movable/screen/actor_hud/inventory/swap_hand/Initialize(mapload, datum/inventory/host, hand_count)
- . = ..()
- screen_loc = SCREEN_LOC_MOB_HUD_INVENTORY_HAND_SWAP(hand_count)
+ return ..()
/atom/movable/screen/actor_hud/inventory/swap_hand/sync_style(datum/hud_style/style, style_alpha, style_color)
..()
@@ -213,8 +227,7 @@
icon_state = "button-equip"
/atom/movable/screen/actor_hud/inventory/equip_hand/Initialize(mapload, datum/inventory/host, hand_count)
- . = ..()
- screen_loc = SCREEN_LOC_MOB_HUD_INVENTORY_EQUIP_HAND(hand_count)
+ return ..()
/atom/movable/screen/actor_hud/inventory/equip_hand/sync_style(datum/hud_style/style, style_alpha, style_color)
..()
diff --git a/code/game/rendering/actor_huds/huds/inventory.dm b/code/game/rendering/actor_huds/huds/inventory.dm
index 3a3399efa59..fd0431d1733 100644
--- a/code/game/rendering/actor_huds/huds/inventory.dm
+++ b/code/game/rendering/actor_huds/huds/inventory.dm
@@ -1,20 +1,16 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2024 Citadel Station Developers *//
-/**
- * Inventory slots specifically, not hands.
- */
/datum/actor_hud/inventory
/// owning inventory
var/datum/inventory/host
/// hidden classes, associated to list of reasons
- var/list/hidden_classes = list(
+ var/tmp/list/hidden_classes = list(
(INVENTORY_HUD_CLASS_DRAWER) = list(
INVENTORY_HUD_HIDE_SOURCE_DRAWER,
),
)
-
/// keyed slot id to screen object
var/list/atom/movable/screen/actor_hud/inventory/plate/slot/slots
/// ordered hand objects
@@ -29,16 +25,34 @@
/// equip object
var/atom/movable/screen/actor_hud/inventory/equip_hand/button_equip_hand
+
+ //* Imprinted by /datum/inventory *//
+
+ /// render held items in row mode; no left/right semantics.
+ /// * this forces the number of hands in a row to this value
+ /// * will use 'hand' instead of 'hand-(left|right)'
+ var/tmp/inv_held_items_row_mode
+ /// suppress swap / equip buttons for hands
+ var/tmp/inv_held_items_suppress_buttons
+ /// use robot icons for hands
+ var/tmp/inv_held_items_use_robot_icon
+
/datum/actor_hud/inventory/sync_to_preferences(datum/hud_preferences/preference_set)
var/old_active_hand = applied_active_hand
set_active_hand(null)
- . = ..()
+
+ var/list/atom/movable/screen/screens = ..()
+ . = screens
+
+ for(var/atom/movable/screen/actor_hud/actor_hud_object in screens)
+ host?.hud_object_post_sync(src, actor_hud_object)
set_active_hand(old_active_hand)
/datum/actor_hud/inventory/on_mob_bound(mob/target)
// we don't have a hook for 'on inventory init',
// so we can't init it lazily; we init it immediately.
- target.init_inventory()
+ if(!target.inventory)
+ target.init_inventory()
if(target.inventory)
bind_to_inventory(target.inventory)
return ..()
@@ -52,6 +66,7 @@
ASSERT(!host)
host = inventory
LAZYADD(inventory.huds_using, src)
+ inventory.hud_alter(src)
rebuild(inventory.build_inventory_slots_with_remappings(), length(inventory.held_items))
for(var/i in 1 to length(inventory.held_items))
if(!inventory.held_items[i])
@@ -121,9 +136,12 @@
cleanup()
// buttons
- add_screen((button_swap_hand = new(null, src, number_of_hands)))
- add_screen((button_equip_hand = new(null, src, number_of_hands)))
- add_screen((button_drawer = new(null, src)))
+ if(!inv_held_items_suppress_buttons)
+ add_screen((button_swap_hand = new(null, src, number_of_hands)))
+ add_screen((button_equip_hand = new(null, src, number_of_hands)))
+ if(length(inventory_slots_with_mappings))
+ add_screen((button_drawer = new(null, src)))
+ button_drawer.screen_loc = screen_loc_for_slot_drawer()
// slots
rebuild_slots(inventory_slots_with_mappings)
@@ -218,12 +236,13 @@
aligning.inventory_hud_main_axis = main_axis
aligned += aligning
+ var/number_of_hands = host.get_hand_count()
for(var/atom/movable/screen/actor_hud/inventory/plate/slot/slot_object as anything in aligned)
switch(slot_object.inventory_hud_anchor)
if(INVENTORY_HUD_ANCHOR_TO_DRAWER)
- slot_object.screen_loc = SCREEN_LOC_MOB_HUD_INVENTORY_SLOT_DRAWER_ALIGNED(slot_object.inventory_hud_main_axis, slot_object.inventory_hud_cross_axis)
+ slot_object.screen_loc = screen_loc_for_drawer_aligned_slot(slot_object.inventory_hud_main_axis, slot_object.inventory_hud_cross_axis)
if(INVENTORY_HUD_ANCHOR_TO_HANDS)
- slot_object.screen_loc = SCREEN_LOC_MOB_HUD_INVENTORY_SLOT_HANDS_ALIGNED(slot_object.inventory_hud_main_axis, slot_object.inventory_hud_cross_axis)
+ slot_object.screen_loc = screen_loc_for_hand_aligned_slot(slot_object.inventory_hud_main_axis, slot_object.inventory_hud_cross_axis, number_of_hands)
/**
* Rebuilds our hands. Doesn't rebuild anything else. Doesn't wipe old objects.
@@ -235,6 +254,7 @@
hands.len = number_of_hands
for(var/i in old_length + 1 to number_of_hands)
var/atom/movable/screen/actor_hud/inventory/plate/hand/hand_object = new(null, src, i)
+ hand_object.screen_loc = screen_loc_for_hand_index(i, number_of_hands)
add_screen(hand_object)
hands[i] = hand_object
else if(length(hands) > number_of_hands)
@@ -245,8 +265,46 @@
qdel(hands[i])
hands.len = number_of_hands
- button_equip_hand?.screen_loc = SCREEN_LOC_MOB_HUD_INVENTORY_EQUIP_HAND(number_of_hands)
- button_swap_hand?.screen_loc = SCREEN_LOC_MOB_HUD_INVENTORY_HAND_SWAP(number_of_hands)
+ button_equip_hand?.screen_loc = screen_loc_for_hand_equip(number_of_hands)
+ button_swap_hand?.screen_loc = screen_loc_for_hand_swap(number_of_hands)
+
+/datum/actor_hud/inventory/proc/screen_loc_for_hand_index(index, number_of_hands)
+ // -- THESE CALCULATIONS ASSUME THE INV SCREEN OBJECT IS A SINGLE WORLD TILE --
+ var/per_row = inv_held_items_row_mode || min(2, number_of_hands)
+ // get half of the total width of the hand bar
+ var/half_total_width = (per_row) * WORLD_ICON_SIZE * 0.5
+ // get row; this starts at 0
+ var/row = ceil(index / per_row) - 1
+ // get column; this starts at 0.
+ var/col = ((index - 1) % per_row)
+ return "CENTER:[-half_total_width + (col * WORLD_ICON_SIZE) + (WORLD_ICON_SIZE * 0.5)],BOTTOM+[row]:5"
+
+/datum/actor_hud/inventory/proc/screen_loc_for_hand_swap(number_of_hands)
+ // Always aligned to center of hands; bump up per row.
+ var/rows = max(1, ceil(number_of_hands / (inv_held_items_row_mode || 2)))
+ return "CENTER-1:28,BOTTOM+[rows]:5"
+
+/datum/actor_hud/inventory/proc/screen_loc_for_hand_equip(number_of_hands)
+ // Always aligned to center of hands.
+ var/rows = max(1, ceil(number_of_hands / (inv_held_items_row_mode || 2)))
+ return "CENTER-1:16,BOTTOM+[rows]:5"
+
+/datum/actor_hud/inventory/proc/screen_loc_for_slot_drawer()
+ // -- THESE CALCULATIONS ASSUME THE INV SCREEN OBJECT IS A SINGLE WORLD TILE --
+ return "LEFT:6,BOTTOM:5"
+
+/datum/actor_hud/inventory/proc/screen_loc_for_drawer_aligned_slot(main, cross)
+ // -- THESE CALCULATIONS ASSUME THE INV SCREEN OBJECT IS A SINGLE WORLD TILE --
+ return "LEFT+[cross]:[6 + (cross * 2)],BOTTOM+[main]:[5 + (main * 2)]"
+
+/datum/actor_hud/inventory/proc/screen_loc_for_hand_aligned_slot(main, cross, number_of_hands)
+ // -- THESE CALCULATIONS ASSUME THE INV SCREEN OBJECT IS A SINGLE WORLD TILE --
+ // even if we only have 1 hand we pretend we have 2
+ var/half_total_width = (inv_held_items_row_mode || 2) * WORLD_ICON_SIZE * 0.5
+ if(main > 0)
+ return "CENTER-1:[half_total_width + (WORLD_ICON_SIZE * (main + 1)) - (WORLD_ICON_SIZE * 0.5)],BOTTOM+[cross]:[5 + (cross * 2)]"
+ else
+ return "CENTER-1:[-half_total_width + (WORLD_ICON_SIZE * main) + (WORLD_ICON_SIZE * 1.5)],BOTTOM+[cross]:[5 + (cross * 2)]"
/**
* @params
@@ -288,6 +346,8 @@
if(button_drawer)
. += button_drawer
+//* Hidden Classes *//
+
/datum/actor_hud/inventory/proc/toggle_hidden_class(class, source)
var/list/atom/movable/screen/actor_hud/inventory/affected
var/something_changed
@@ -350,10 +410,8 @@
screen_obj?.unbind_item(item)
/datum/actor_hud/inventory/proc/move_item(obj/item/item, datum/inventory_slot/from_slot_or_index, datum/inventory_slot/to_slot_or_index)
- var/atom/movable/screen/actor_hud/inventory/plate/old_screen_obj = isnum(from_slot_or_index) ? hands[from_slot_or_index] : slots[from_slot_or_index.id]
- var/atom/movable/screen/actor_hud/inventory/plate/new_screen_obj = isnum(to_slot_or_index) ? hands[to_slot_or_index] : slots[to_slot_or_index.id]
- old_screen_obj?.unbind_item(item)
- new_screen_obj?.bind_item(item)
+ remove_item(item, from_slot_or_index)
+ add_item(item, to_slot_or_index)
/datum/actor_hud/inventory/proc/swap_active_hand(from_index, to_index)
set_active_hand(to_index)
diff --git a/code/game/rendering/actor_huds/huds/robot_inventory-screen_object.dm b/code/game/rendering/actor_huds/huds/robot_inventory-screen_object.dm
new file mode 100644
index 00000000000..48c02cecb14
--- /dev/null
+++ b/code/game/rendering/actor_huds/huds/robot_inventory-screen_object.dm
@@ -0,0 +1,194 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/**
+ * Base type of robot inventory screen objects.
+ */
+/atom/movable/screen/actor_hud/robot_inventory
+ name = "inventory"
+ plane = HUD_PLANE
+ layer = HUD_LAYER_INVENTORY
+ var/is_drawer = FALSE
+
+/atom/movable/screen/actor_hud/robot_inventory/on_click(mob/user, list/params)
+ if(is_drawer)
+ var/datum/actor_hud/robot_inventory/casted = hud
+ var/obj/item/held = user.get_active_held_item()
+ if(held && casted.host?.inv_is_registered(held))
+ // will trigger the robot inventory grabbing the item
+ // INV_OP_FORCE because ermmmm no setting robot modules as nodrop!!
+ user.drop_item_to_ground(held, INV_OP_FORCE)
+
+/atom/movable/screen/actor_hud/robot_inventory/sync_to_preferences(datum/hud_preferences/preference_set)
+ sync_style(preference_set.hud_style, preference_set.hud_alpha, preference_set.hud_color)
+
+/atom/movable/screen/actor_hud/robot_inventory/proc/sync_style(datum/hud_style/style, style_alpha, style_color)
+ alpha = style_alpha
+ color = style_color
+
+/**
+ * Button: 'open / close robot modules'
+ *
+ * * The icon for this gets replaced with the robot's module if we're on an actual robot.
+ */
+/atom/movable/screen/actor_hud/robot_inventory/robot_drawer
+ name = "module drawer"
+ icon = 'icons/screen/hud/robot/default.dmi'
+ icon_state = "drawer"
+ is_drawer = TRUE
+
+/atom/movable/screen/actor_hud/robot_inventory/robot_drawer/sync_style(datum/hud_style/style, style_alpha, style_color)
+ ..()
+ icon = style.robot_icons
+
+/atom/movable/screen/actor_hud/robot_inventory/robot_drawer/on_click(mob/user, list/params)
+ // todo: remote control
+ var/datum/actor_hud/robot_inventory/inventory_hud = hud
+ inventory_hud.toggle_drawer()
+
+/**
+ * Backplate for robot modules inventory.
+ */
+/atom/movable/screen/actor_hud/robot_inventory/robot_drawer_backplate
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ is_drawer = TRUE
+
+ /// * FALSE: main axis is X, cross axis is Y
+ /// * TRUE: main axis is Y, cross axis is X
+ var/is_vertical
+ /// tile offset applied to main axis per iteration
+ var/main_axis_offset
+ /// max items in a 'row', basically
+ var/main_axis_max_size
+ /// tile offset applied to main axis per iteration
+ var/cross_axis_offset
+ /// alignment; this is something like 'NORTH' as string
+ var/first_tile_screen_ax
+ /// alignment; this is something like 'NORTH' as string
+ var/first_tile_screen_ay
+ /// tile; this is a number
+ var/first_tile_screen_tx
+ /// tile; this is a number
+ var/first_tile_screen_ty
+ /// pixel; this is a number
+ var/first_tile_screen_px
+ /// pixel; this is a number
+ var/first_tile_screen_py
+
+ var/list/atom/movable/render/robot_drawer_item_render/renderers
+
+/atom/movable/screen/actor_hud/robot_inventory/robot_drawer_backplate/proc/redraw()
+ var/datum/actor_hud/robot_inventory/casted = hud
+ var/safety = 255
+ if(casted.drawer_toggled)
+ // draw
+ // check if we actually need to rebuild renderers; this is needed if row count changes
+ // TODO: optimize this
+ var/requires_rebuild = TRUE
+ if(requires_rebuild)
+ LAZYINITLIST(renderers)
+ var/MAO = 0 // main axis offset
+ var/CAO = 0 // cross axis offset
+ var/MAC = 0 // main axis count
+ var/AMT = length(casted.host.provided_items)
+ if(AMT > safety)
+ AMT = safety
+ stack_trace("why the hell does a cyborg have more than 255 items?")
+ for(var/IDX in 1 to AMT)
+ if(MAC > main_axis_max_size)
+ CAO++
+ MAO = 0
+ MAC = 0
+ // fetch or make a renderer
+ var/atom/movable/render/robot_drawer_item_render/renderer
+ if(IDX > length(renderers))
+ renderer = new /atom/movable/render/robot_drawer_item_render(null, hud, src)
+ else
+ renderer = renderers[IDX]
+ // set its pixel loc as needed
+ // 0, 0 is ontop of us
+ if(is_vertical)
+ renderer.pixel_x = CAO * cross_axis_offset * WORLD_ICON_SIZE
+ renderer.pixel_y = MAO * main_axis_offset * WORLD_ICON_SIZE
+ else
+ renderer.pixel_x = MAO * main_axis_offset * WORLD_ICON_SIZE
+ renderer.pixel_y = CAO * cross_axis_offset * WORLD_ICON_SIZE
+ ++MAO
+ ++MAC
+
+ var/x_r = first_tile_screen_tx > 0 ? "[first_tile_screen_tx]" : "-[first_tile_screen_tx]"
+ var/y_r = first_tile_screen_ty > 0 ? "[first_tile_screen_ty]" : "-[first_tile_screen_ty]"
+ screen_loc = "[first_tile_screen_ax][x_r]:[first_tile_screen_px],\
+ [first_tile_screen_ay][y_r]:[first_tile_screen_py]"
+ // apply items
+ var/ridx = 1
+ var/iidx = 1
+ var/iidx_max = length(casted.host.provided_items)
+ var/ridx_max = length(renderers)
+ while(iidx < iidx_max && ridx < ridx_max)
+ var/atom/movable/render/robot_drawer_item_render/renderer = renderers[ridx]
+ ++iidx
+ var/obj/item/item = casted.host.provided_items[iidx]
+ if(item.inv_slot_or_index)
+ // active module, don't render
+ continue
+ if(renderer.masquarading_as != item)
+ renderer.masquarade(item)
+ ++ridx
+ for(ridx in ridx to ridx_max)
+ var/atom/movable/render/robot_drawer_item_render/renderer = renderers[ridx]
+ if(renderer.masquarading_as)
+ renderer.reset()
+
+ src.invisibility = INVISIBILITY_NONE
+ else
+ // undraw
+ for(var/atom/movable/render/robot_drawer_item_render/renderer as anything in renderers)
+ renderer.reset()
+ src.invisibility = INVISIBILITY_ABSTRACT
+
+/**
+ * Item renderer
+ */
+/atom/movable/render/robot_drawer_item_render
+ name = "drawer"
+ icon = 'icons/screen/hud/styles/common/storage.dmi'
+ icon_state = "block"
+ mouse_opacity = MOUSE_OPACITY_ICON
+ var/atom/movable/screen/actor_hud/robot_inventory/robot_drawer_backplate/backplate
+ var/obj/item/masquarading_as
+
+/atom/movable/render/robot_drawer_item_render/Initialize(
+ mapload,
+ datum/actor_hud/robot_inventory/hud,
+ atom/movable/screen/actor_hud/robot_inventory/robot_drawer_backplate/backplate
+)
+ . = ..()
+ src.backplate = backplate
+ src.backplate.vis_contents += src
+ src.backplate.renderers += src
+ reset()
+
+/atom/movable/render/robot_drawer_item_render/Destroy()
+ reset()
+ src.backplate.vis_contents -= src
+ src.backplate.renderers -= src
+ src.backplate = null
+ return ..()
+
+/atom/movable/render/robot_drawer_item_render/Click(location, control, params)
+ masquarading_as?.Click(arglist(args))
+
+/atom/movable/render/robot_drawer_item_render/proc/masquarade(obj/item/render_as)
+ if(masquarading_as)
+ reset()
+ vis_contents += render_as
+ masquarading_as = render_as
+ name = render_as.name
+ alpha = initial(alpha)
+
+/atom/movable/render/robot_drawer_item_render/proc/reset()
+ vis_contents.len = 0
+ masquarading_as = null
+ alpha = 0
+ name = initial(name)
diff --git a/code/game/rendering/actor_huds/huds/robot_inventory.dm b/code/game/rendering/actor_huds/huds/robot_inventory.dm
new file mode 100644
index 00000000000..9c98542a1f0
--- /dev/null
+++ b/code/game/rendering/actor_huds/huds/robot_inventory.dm
@@ -0,0 +1,90 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/datum/actor_hud/robot_inventory
+ /// owning inventory
+ var/datum/robot_inventory/host
+
+ /// is robot module inventory shown?
+ var/tmp/drawer_toggled = FALSE
+ /// robot drawer object, if any
+ var/atom/movable/screen/actor_hud/robot_inventory/robot_drawer/drawer_button
+ /// robot drawer backplate, if any
+ var/atom/movable/screen/actor_hud/robot_inventory/robot_drawer_backplate/drawer_backplate
+
+/datum/actor_hud/robot_inventory/on_mob_bound(mob/target)
+ if(isrobot(target))
+ var/mob/living/silicon/robot/casted = target
+ if(casted.robot_inventory)
+ bind_to_inventory(casted.robot_inventory)
+ return ..()
+
+/datum/actor_hud/robot_inventory/on_mob_unbound(mob/target)
+ if(isrobot(target))
+ var/mob/living/silicon/robot/casted = target
+ if(casted.robot_inventory)
+ unbind_from_inventory(casted.robot_inventory)
+ return ..()
+
+/datum/actor_hud/robot_inventory/proc/bind_to_inventory(datum/robot_inventory/robot_inventory)
+ ASSERT(!host)
+ host = robot_inventory
+ LAZYADD(robot_inventory.huds_using, src)
+ rebuild()
+
+/datum/actor_hud/robot_inventory/proc/unbind_from_inventory(datum/robot_inventory/robot_inventory)
+ ASSERT(host == robot_inventory)
+ cleanup()
+ LAZYREMOVE(robot_inventory.huds_using, src)
+ host = null
+
+/datum/actor_hud/robot_inventory/screens()
+ . = ..()
+ if(drawer_button)
+ . += drawer_button
+ if(drawer_backplate)
+ . += drawer_backplate
+
+/datum/actor_hud/robot_inventory/proc/cleanup()
+ remove_screen(drawer_backplate)
+ QDEL_NULL(drawer_backplate)
+ remove_screen(drawer_button)
+ QDEL_NULL(drawer_button)
+
+/datum/actor_hud/robot_inventory/proc/rebuild()
+ add_screen((drawer_button = new(null, src)))
+ drawer_button.screen_loc = screen_loc_for_drawer_button()
+ add_screen((drawer_backplate = new(null, src)))
+ set_drawer_backplate_parameters()
+ drawer_backplate.redraw()
+
+/datum/actor_hud/robot_inventory/proc/screen_loc_for_drawer_button()
+ // Always aligned to right side of hands, assuming a 3-wide hands
+ // TODO: can we auto-align based on the hands HUD? we'd need to do cascading re-renders..
+ return "CENTER:64,BOTTOM:5"
+
+/datum/actor_hud/robot_inventory/proc/set_drawer_backplate_parameters()
+ drawer_backplate.first_tile_screen_ax = "LEFT+"
+ drawer_backplate.first_tile_screen_ay = "BOTTOM+"
+ drawer_backplate.first_tile_screen_tx = STORAGE_UI_START_TILE_X
+ drawer_backplate.first_tile_screen_ty = STORAGE_UI_START_TILE_Y
+ drawer_backplate.first_tile_screen_px = STORAGE_UI_START_PIXEL_X
+ drawer_backplate.first_tile_screen_py = STORAGE_UI_START_PIXEL_Y
+ drawer_backplate.is_vertical = FALSE
+ drawer_backplate.main_axis_max_size = 9
+ drawer_backplate.main_axis_offset = 1
+ drawer_backplate.cross_axis_offset = 1
+
+/datum/actor_hud/robot_inventory/proc/all_screen_objects()
+ RETURN_TYPE(/list)
+ . = list()
+ if(drawer_button)
+ . += drawer_button
+ if(drawer_backplate)
+ . += drawer_backplate
+
+/datum/actor_hud/robot_inventory/proc/toggle_drawer()
+ drawer_toggled = !drawer_toggled
+ if(drawer_toggled && !drawer_backplate)
+ drawer_backplate = new
+ drawer_backplate?.redraw()
diff --git a/code/game/rendering/hud_style.dm b/code/game/rendering/hud_style.dm
index 58be182cf79..229c41d3a38 100644
--- a/code/game/rendering/hud_style.dm
+++ b/code/game/rendering/hud_style.dm
@@ -61,11 +61,13 @@ GLOBAL_LIST_INIT(hud_styles, init_hud_styles())
var/id
/// inventory icons
- var/inventory_icons = 'icons/screen/hud/midnight/inventory.dmi'
+ var/inventory_icons = 'icons/screen/hud/styles/midnight/inventory.dmi'
/// inventory icons: slots
- var/inventory_icons_slot = 'icons/screen/hud/midnight/inventory-slot.dmi'
+ var/inventory_icons_slot = 'icons/screen/hud/styles/midnight/inventory-slot.dmi'
/// inventory icons: big
- var/inventory_icons_wide = 'icons/screen/hud/midnight/inventory-wide.dmi'
+ var/inventory_icons_wide = 'icons/screen/hud/styles/midnight/inventory-wide.dmi'
+ /// robot icons
+ var/robot_icons = 'icons/screen/hud/robot/default.dmi'
/**
* midnight style just inherits defaults
@@ -77,34 +79,35 @@ GLOBAL_LIST_INIT(hud_styles, init_hud_styles())
/datum/hud_style/orange
name = "Orange"
id = "orange"
- inventory_icons = 'icons/screen/hud/orange/inventory.dmi'
- inventory_icons_slot = 'icons/screen/hud/orange/inventory-slot.dmi'
- inventory_icons_wide = 'icons/screen/hud/orange/inventory-wide.dmi'
+ inventory_icons = 'icons/screen/hud/styles/orange/inventory.dmi'
+ inventory_icons_slot = 'icons/screen/hud/styles/orange/inventory-slot.dmi'
+ inventory_icons_wide = 'icons/screen/hud/styles/orange/inventory-wide.dmi'
/datum/hud_style/old
name = "Retro"
id = "old"
- inventory_icons = 'icons/screen/hud/old/inventory.dmi'
- inventory_icons_slot = 'icons/screen/hud/old/inventory-slot.dmi'
- inventory_icons_wide = 'icons/screen/hud/old/inventory-wide.dmi'
+ inventory_icons = 'icons/screen/hud/styles/old/inventory.dmi'
+ inventory_icons_slot = 'icons/screen/hud/styles/old/inventory-slot.dmi'
+ inventory_icons_wide = 'icons/screen/hud/styles/old/inventory-wide.dmi'
/datum/hud_style/white
name = "White"
id = "white"
- inventory_icons = 'icons/screen/hud/white/inventory.dmi'
- inventory_icons_slot = 'icons/screen/hud/white/inventory-slot.dmi'
- inventory_icons_wide = 'icons/screen/hud/white/inventory-wide.dmi'
+ inventory_icons = 'icons/screen/hud/styles/white/inventory.dmi'
+ inventory_icons_slot = 'icons/screen/hud/styles/white/inventory-slot.dmi'
+ inventory_icons_wide = 'icons/screen/hud/styles/white/inventory-wide.dmi'
/datum/hud_style/minimalist
name = "Minimalist"
id = "minimalist"
- inventory_icons = 'icons/screen/hud/minimalist/inventory.dmi'
- inventory_icons_slot = 'icons/screen/hud/minimalist/inventory-slot.dmi'
- inventory_icons_wide = 'icons/screen/hud/minimalist/inventory-wide.dmi'
+ inventory_icons = 'icons/screen/hud/styles/minimalist/inventory.dmi'
+ inventory_icons_slot = 'icons/screen/hud/styles/minimalist/inventory-slot.dmi'
+ inventory_icons_wide = 'icons/screen/hud/styles/minimalist/inventory-wide.dmi'
+ robot_icons = 'icons/screen/hud/robot/minimalist.dmi'
/datum/hud_style/hologram
name = "Holographic"
id = "hologram"
- inventory_icons = 'icons/screen/hud/hologram/inventory.dmi'
- inventory_icons_slot = 'icons/screen/hud/hologram/inventory-slot.dmi'
- inventory_icons_wide = 'icons/screen/hud/hologram/inventory-wide.dmi'
+ inventory_icons = 'icons/screen/hud/styles/hologram/inventory.dmi'
+ inventory_icons_slot = 'icons/screen/hud/styles/hologram/inventory-slot.dmi'
+ inventory_icons_wide = 'icons/screen/hud/styles/hologram/inventory-wide.dmi'
diff --git a/code/game/rendering/legacy/ghost.dm b/code/game/rendering/legacy/ghost.dm
index aaba2cbb624..f7b5d59af92 100644
--- a/code/game/rendering/legacy/ghost.dm
+++ b/code/game/rendering/legacy/ghost.dm
@@ -1,5 +1,5 @@
/atom/movable/screen/ghost
- icon = 'icons/screen/hud/common/observer.dmi'
+ icon = 'icons/screen/hud/styles/common/observer.dmi'
/atom/movable/screen/ghost/MouseEntered(location,control,params)
flick(icon_state + "_anim", src)
diff --git a/code/game/rendering/legacy/robot.dm b/code/game/rendering/legacy/robot.dm
index cc379e9311d..c81cedd9ff6 100644
--- a/code/game/rendering/legacy/robot.dm
+++ b/code/game/rendering/legacy/robot.dm
@@ -1,15 +1,4 @@
-var/atom/movable/screen/robot_inventory
-/*
-/mob/living/silicon/robot/instantiate_hud(var/datum/hud/HUD, var/ui_style, var/ui_color, var/ui_alpha)
- HUD.robot_hud(ui_style, ui_color, ui_alpha, src)*/
-
/datum/hud/proc/robot_hud(ui_style='icons/mob/screen1_robot.dmi', var/ui_color = "#ffffff", var/ui_alpha = 255, var/mob/living/silicon/robot/target)
-/* var/datum/hud_data/hud_data
- if(!istype(target))
- hud_data = new()
-
- if(hud_data.icon)
- ui_style = hud_data.icon*/
if(ui_style == 'icons/mob/screen/minimalist.dmi')
ui_style = 'icons/mob/screen1_robot_minimalist.dmi'
@@ -33,46 +22,6 @@ var/atom/movable/screen/robot_inventory
using.layer = HUD_LAYER_BASE
src.adding += using
-//Module select
-
- using = new /atom/movable/screen()
- using.name = "module1"
- using.setDir(SOUTHWEST)
- using.icon = ui_style
- using.color = ui_color
- using.alpha = ui_alpha
- using.icon_state = "inv1"
- using.screen_loc = ui_inv1
- using.layer = HUD_LAYER_BASE
- src.adding += using
- mymob:inv1 = using
-
- using = new /atom/movable/screen()
- using.name = "module2"
- using.setDir(SOUTHWEST)
- using.icon = ui_style
- using.color = ui_color
- using.alpha = ui_alpha
- using.icon_state = "inv2"
- using.screen_loc = ui_inv2
- using.layer = HUD_LAYER_BASE
- src.adding += using
- mymob:inv2 = using
-
- using = new /atom/movable/screen()
- using.name = "module3"
- using.setDir(SOUTHWEST)
- using.icon = ui_style
- using.color = ui_color
- using.alpha = ui_alpha
- using.icon_state = "inv3"
- using.screen_loc = ui_inv3
- using.layer = HUD_LAYER_BASE
- src.adding += using
- mymob:inv3 = using
-
-//End of module select
-
//Intent
using = new /atom/movable/screen()
using.name = "act_intent"
@@ -106,8 +55,8 @@ var/atom/movable/screen/robot_inventory
//Installed Module
mymob.hands = new /atom/movable/screen()
- mymob.hands.icon = ui_style
- mymob.hands.icon_state = "nomod"
+ mymob.hands.icon = 'icons/screen/hud/robot/modules.dmi'
+ mymob.hands.icon_state = "none"
mymob.hands.alpha = ui_alpha
mymob.hands.name = "module"
mymob.hands.screen_loc = ui_borg_module
@@ -123,26 +72,6 @@ var/atom/movable/screen/robot_inventory
using.layer = HUD_LAYER_BASE-0.01
src.adding += using
-//Store
- mymob.throw_icon = new /atom/movable/screen()
- mymob.throw_icon.icon = ui_style
- mymob.throw_icon.icon_state = "store"
- mymob.throw_icon.alpha = ui_alpha
- mymob.throw_icon.color = ui_color
- mymob.throw_icon.name = "store"
- mymob.throw_icon.screen_loc = ui_borg_store
- src.other += mymob.throw_icon
-
-//Inventory
- robot_inventory = new /atom/movable/screen()
- robot_inventory.name = "inventory"
- robot_inventory.icon = ui_style
- robot_inventory.icon_state = "inventory"
- robot_inventory.alpha = ui_alpha
- robot_inventory.color = ui_color
- robot_inventory.screen_loc = ui_borg_inventory
- src.other += robot_inventory
-
//Temp
mymob.bodytemp = new /atom/movable/screen()
mymob.bodytemp.icon_state = "temp0"
@@ -196,91 +125,20 @@ var/atom/movable/screen/robot_inventory
mymob.client.screen = list()
- mymob.client.screen += list( mymob.throw_icon, mymob.zone_sel, mymob.oxygen, mymob.fire, mymob.hands, mymob.healths, using, mymob.pullin, robot_inventory, mymob.gun_setting_icon)
- mymob.client.screen += src.adding + src.other
+ // this is just separated because this is legacy code and we want to know which line runtimes
+ mymob.client.screen += mymob.zone_sel
+ mymob.client.screen += mymob.oxygen
+ mymob.client.screen += mymob.fire
+ mymob.client.screen += mymob.hands
+ mymob.client.screen += mymob.healths
+ mymob.client.screen += using
+ mymob.client.screen += mymob.pullin
+ mymob.client.screen += mymob.gun_setting_icon
+ mymob.client.screen += src.adding
+ mymob.client.screen += src.other
mymob.reload_rendering()
-/datum/hud/proc/toggle_show_robot_modules()
- if(!isrobot(mymob))
- return
-
- var/mob/living/silicon/robot/r = mymob
-
- r.shown_robot_modules = !r.shown_robot_modules
- update_robot_modules_display()
-
-
-/datum/hud/proc/update_robot_modules_display()
- if(!isrobot(mymob))
- return
-
- var/mob/living/silicon/robot/r = mymob
-
- if(r.shown_robot_modules)
- //Modules display is shown
- //r.client.screen += robot_inventory //"store" icon
-
- if(!r.module)
- to_chat(usr, "No module selected")
- return
-
- if(!r.module.modules)
- to_chat(usr, "Selected module has no modules to select")
- return
-
- if(!r.robot_modules_background)
- return
-
- var/display_rows = -round(-(r.module.modules.len) / 8)
- r.robot_modules_background.screen_loc = "CENTER-4:16,SOUTH+1:7 to CENTER+3:16,SOUTH+[display_rows]:7"
- r.client.screen += r.robot_modules_background
-
- var/x = -4 //Start at CENTER-4,SOUTH+1
- var/y = 1
-
- //Unfortunately adding the emag module to the list of modules has to be here. This is because a borg can
- //be emagged before they actually select a module. - or some situation can cause them to get a new module
- // - or some situation might cause them to get de-emagged or something.
- if(r.emagged || r.emag_items)
- if(!(r.module.emag in r.module.modules))
- r.module.modules.Add(r.module.emag)
- else
- if(r.module.emag in r.module.modules)
- r.module.modules.Remove(r.module.emag)
-
- for(var/atom/movable/A in r.module.modules)
- if( (A != r.module_state_1) && (A != r.module_state_2) && (A != r.module_state_3) )
- //Module is not currently active
- r.client.screen += A
- if(x < 0)
- A.screen_loc = "CENTER[x]:16,SOUTH+[y]:7"
- else
- A.screen_loc = "CENTER+[x]:16,SOUTH+[y]:7"
- A.hud_layerise()
-
- x++
- if(x == 4)
- x = -4
- y++
-
- else
- //Modules display is hidden
- //r.client.screen -= robot_inventory //"store" icon
- for(var/atom/A in r.module.modules)
- if( (A != r.module_state_1) && (A != r.module_state_2) && (A != r.module_state_3) )
- //Module is not currently active
- r.client.screen -= A
- r.shown_robot_modules = 0
- r.client.screen -= r.robot_modules_background
-
/mob/living/silicon/robot/update_hud()
- if(modtype)
- hands.icon_state = lowertext(modtype)
- ..()
-
-//! ## VR FILE MERGE ## !//
-/mob/living/silicon/robot/update_hud()
- if(modtype)
- hands.icon_state = lowertext(modtype)
+ hands.icon_state = module ? module.module_hud_state : "none"
..()
diff --git a/code/game/rendering/screen_legacy.dm b/code/game/rendering/screen_legacy.dm
index d2df420bfce..5f43852ce12 100644
--- a/code/game/rendering/screen_legacy.dm
+++ b/code/game/rendering/screen_legacy.dm
@@ -184,47 +184,19 @@
if("module")
if(isrobot(usr))
var/mob/living/silicon/robot/R = usr
-// if(R.module)
-// R.hud_used.toggle_show_robot_modules()
-// return 1
- R.pick_module()
-
- if("inventory")
- if(isrobot(usr))
- var/mob/living/silicon/robot/R = usr
- if(R.module)
- R.hud_used.toggle_show_robot_modules()
- return 1
- else
- to_chat(R, "You haven't selected a module yet.")
+ if(!R.module)
+ if(!R.can_repick_module)
+ to_chat(usr, SPAN_WARNING("You can't repick your module, despite not having one. \
+ Something probably got bugged."))
+ else
+ open_tgui_actor_modal(
+ /datum/tgui_actor_modal/robot_module_picker,
+ actor = new /datum/event_args/actor(R),
+ )
if("radio")
if(issilicon(usr))
usr:radio_menu()
- if("panel")
- if(issilicon(usr))
- usr:installed_modules()
-
- if("store")
- if(isrobot(usr))
- var/mob/living/silicon/robot/R = usr
- if(R.module)
- R.uneq_active()
- R.hud_used.update_robot_modules_display()
- else
- to_chat(R, "You haven't selected a module yet.")
-
- if("module1")
- if(istype(usr, /mob/living/silicon/robot))
- usr:toggle_module(1)
-
- if("module2")
- if(istype(usr, /mob/living/silicon/robot))
- usr:toggle_module(2)
-
- if("module3")
- if(istype(usr, /mob/living/silicon/robot))
- usr:toggle_module(3)
if("AI Core")
if(isAI(usr))
diff --git a/code/game/turfs/simulated.dm b/code/game/turfs/simulated.dm
index b29d36150c3..e6d749ca064 100644
--- a/code/game/turfs/simulated.dm
+++ b/code/game/turfs/simulated.dm
@@ -131,7 +131,9 @@
bloodDNA = null
if(src.wet)
- process_slip(M)
+ spawn(0)
+ if(AM.loc == src)
+ process_slip(M)
/turf/simulated/proc/process_slip(mob/living/M)
if(M.buckled || (src.wet == 1 && M.m_intent == "walk"))
diff --git a/code/game/verbs/character_directory.dm b/code/game/verbs/character_directory.dm
index 4c87bf67456..c7e56c456c5 100644
--- a/code/game/verbs/character_directory.dm
+++ b/code/game/verbs/character_directory.dm
@@ -79,7 +79,7 @@ GLOBAL_DATUM(character_directory, /datum/character_directory)
if(isrobot(C.mob))
var/mob/living/silicon/robot/R = C.mob
- if(R.scrambledcodes || (R.module && R.module.hide_on_manifest))
+ if(R.scrambledcodes || !R.module?.legacy_show_on_manifest)
continue
name = R.name
species = "[R.modtype] [R.braintype]"
diff --git a/code/global.dm b/code/global.dm
index 81f893bb6e0..2292e958de6 100644
--- a/code/global.dm
+++ b/code/global.dm
@@ -25,21 +25,13 @@ var/game_year = (text2num(time2text(world.realtime, "YYYY")) + 544)
var/master_mode = "extended" // "extended"
var/secret_force_mode = "secret" // if this is anything but "secret", the secret rotation will forceably choose this mode.
-var/host = null //only here until check @ code\modules\ghosttrap\trap.dm:112 is fixed
-
-var/list/jobMax = list()
var/list/bombers = list()
var/list/admin_log = list()
var/list/lastsignalers = list() // Keeps last 100 signals here in format: "[src] used \ref[src] @ location [src.loc]: [freq]/[code]"
var/list/lawchanges = list() // Stores who uploaded laws to which silicon-based lifeform, and what the law was.
-var/list/reg_dna = list()
var/mouse_respawn_time = 5 // Amount of time that must pass between a player dying as a mouse and repawning as a mouse. In minutes.
-var/list/monkeystart = list()
-var/list/wizardstart = list()
-var/list/newplayer_start = list()
-
var/list/xeno_spawn = list() // Aliens spawn at at these.
var/list/tdome1 = list()
var/list/tdome2 = list()
@@ -50,51 +42,21 @@ var/list/ninjastart = list()
var/datum/configuration_legacy/config_legacy = null
-var/list/combatlog = list()
-var/list/IClog = list()
-var/list/OOClog = list()
-var/list/adminlog = list()
-
var/list/powernets = list() // TODO - Move into SSmachines
var/gravity_is_on = 1
-var/join_motd = null
-
var/datum/legacy_metric/metric = new() // Metric datum, used to keep track of the round.
var/list/awaydestinations = list() // Away missions. A list of landmarks that the warpgate can take you to.
// For FTP requests. (i.e. downloading runtime logs.)
// However it'd be ok to use for accessing attack logs and such too, which are even laggier.
-var/fileaccess_timer = 0
var/custom_event_msg = null
// Added for Xenoarchaeology, might be useful for other stuff.
var/global/list/alphabet_uppercase = list("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z")
-
-// Used by robots and robot preferences.
-var/list/robot_module_types = list(
- "Standard",
- "Engineering",
- "Medical",
- "Miner",
- "Janitor",
- "Service",
- "Clerical",
- "Security",
- "Research",
- "Quadruped",
- "MediQuad",
- "SecuriQuad",
- "JaniQuad",
- "SciQuad",
- "EngiQuad",
- "Mining Quad",
- "Service Quad"
-)
-
// Some scary sounds.
var/static/list/scarySounds = list(
'sound/weapons/thudswoosh.ogg',
diff --git a/code/modules/actions/types/robot_upgrade_action.dm b/code/modules/actions/types/robot_upgrade_action.dm
new file mode 100644
index 00000000000..8b4589a740f
--- /dev/null
+++ b/code/modules/actions/types/robot_upgrade_action.dm
@@ -0,0 +1,24 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/action/robot_upgrade_action
+ target_type = /obj/item
+ button_icon_state = null
+
+ /// automatically set button_additional_overlay to the item, if button_icon_state is null
+ var/render_upgade_as_button = TRUE
+
+/datum/action/robot_upgrade_action/pre_render_hook()
+ if(render_upgade_as_button && isnull(button_icon_state))
+ button_additional_only = TRUE
+ var/image/generated = new
+ generated.appearance = target
+ // i hope you are not doing custom layers and planes for icons, right gamers??
+ generated.layer = FLOAT_LAYER
+ generated.plane = FLOAT_PLANE
+ button_additional_overlay = generated
+ return ..()
+
+/datum/action/robot_upgrade_action/calculate_availability()
+ var/obj/item/robot_upgrade/upgrade = target
+ return upgrade.owner ? (upgrade.owner.mobility_flags & check_mobility_flags ? 1 : 0) : 1
diff --git a/code/modules/asset_cache/assets/artwork/crayons.dm b/code/modules/asset_cache/assets/spritesheet/crayons.dm
similarity index 100%
rename from code/modules/asset_cache/assets/artwork/crayons.dm
rename to code/modules/asset_cache/assets/spritesheet/crayons.dm
diff --git a/code/modules/asset_cache/assets/materials.dm b/code/modules/asset_cache/assets/spritesheet/materials.dm
similarity index 100%
rename from code/modules/asset_cache/assets/materials.dm
rename to code/modules/asset_cache/assets/spritesheet/materials.dm
diff --git a/code/modules/awaymissions/loot_vr.dm b/code/modules/awaymissions/loot_vr.dm
index 8598297ccda..a79eb0dad78 100644
--- a/code/modules/awaymissions/loot_vr.dm
+++ b/code/modules/awaymissions/loot_vr.dm
@@ -50,7 +50,7 @@
var/amount = rand(2,6)
var/quantity = rand(10,50)
var/list/possible_spawns = list()
- for(var/bar_type in typesof(/obj/item/stack/material) - /obj/item/stack/material - /obj/item/stack/animalhide - typesof(/obj/item/stack/material/cyborg))
+ for(var/bar_type in typesof(/obj/item/stack/material) - /obj/item/stack/material - /obj/item/stack/animalhide)
possible_spawns += bar_type
var/bar_type = pick(possible_spawns)
diff --git a/code/modules/client/client-upload.dm b/code/modules/client/client-upload.dm
index bb84d22b078..02587942c81 100644
--- a/code/modules/client/client-upload.dm
+++ b/code/modules/client/client-upload.dm
@@ -23,7 +23,9 @@
upload_mutex = TRUE
upload_current_sizelimit = size_limit
var/size_render = FLOOR(size_limit / (1024 * 1024), 0.05)
+ log_world("UPLOAD: Prompting '[src]' for a file with maximum size [size_limit].")
. = input(src, message, "[title] (max [size_render]MiB)]") as file|null
+ log_world("UPLOAD: Got file '[.]' from '[src]'.")
upload_current_sizelimit = null
upload_mutex = FALSE
@@ -37,7 +39,9 @@
upload_mutex = TRUE
upload_current_sizelimit = size_limit
var/size_render = FLOOR(size_limit / (1024 * 1024), 0.05)
+ log_world("UPLOAD: Prompting '[src]' for a file with maximum size [size_limit].")
. = input(src, message, "[title] (max [size_render]MiB)]") as file|null
+ log_world("UPLOAD: Got file '[.]' from '[src]'.")
upload_current_sizelimit = null
upload_mutex = FALSE
@@ -47,7 +51,9 @@
upload_mutex = TRUE
upload_current_sizelimit = size_limit
var/size_render = FLOOR(size_limit / (1024 * 1024), 0.05)
+ log_world("UPLOAD: Prompting '[src]' for a file with maximum size [size_limit].")
. = input(src, message, "[title] (max [size_render]MiB)]") as sound|null
+ log_world("UPLOAD: Got file '[.]' from '[src]'.")
upload_current_sizelimit = null
upload_mutex = FALSE
@@ -61,7 +67,9 @@
upload_mutex = TRUE
upload_current_sizelimit = size_limit
var/size_render = FLOOR(size_limit / (1024 * 1024), 0.05)
+ log_world("UPLOAD: Prompting '[src]' for a file with maximum size [size_limit].")
. = input(src, message, "[title] (max [size_render]MiB)]") as sound|null
+ log_world("UPLOAD: Got file '[.]' from '[src]'.")
upload_current_sizelimit = null
upload_mutex = FALSE
diff --git a/code/modules/events/event_dynamic.dm b/code/modules/events/event_dynamic.dm
index a26ba164a10..c14f6e0afd1 100644
--- a/code/modules/events/event_dynamic.dm
+++ b/code/modules/events/event_dynamic.dm
@@ -172,18 +172,6 @@ var/list/event_last_fired = list()
active_with_role["Any"]++
- if(istype(M, /mob/living/silicon/robot))
- var/mob/living/silicon/robot/R = M
- if(R.module)
- if(istype(R.module, /obj/item/robot_module/robot/engineering))
- active_with_role["Engineer"]++
- else if(istype(R.module, /obj/item/robot_module/robot/security))
- active_with_role["Security"]++
- else if(istype(R.module, /obj/item/robot_module/robot/medical))
- active_with_role["Medical"]++
- else if(istype(R.module, /obj/item/robot_module/robot/research))
- active_with_role["Scientist"]++
-
if(M.mind.assigned_role in SSjob.get_job_titles_in_department(DEPARTMENT_ENGINEERING))
active_with_role["Engineer"]++
diff --git a/code/modules/events/fighter_attack.dm b/code/modules/events/fighter_attack.dm
index 7d4334e2aed..c8e3c2100a4 100644
--- a/code/modules/events/fighter_attack.dm
+++ b/code/modules/events/fighter_attack.dm
@@ -32,6 +32,7 @@
/datum/event/fighter/end()
// var/bandits_spawn = list()
+ // var/spawn_type = pick(bandits_spawn)
// for(var/areapath in typesof(spawn_area_type))
// if(piratestring == "bandits")
diff --git a/code/modules/examine/descriptions/mobs.dm b/code/modules/examine/descriptions/mobs.dm
deleted file mode 100644
index 4d332631644..00000000000
--- a/code/modules/examine/descriptions/mobs.dm
+++ /dev/null
@@ -1,8 +0,0 @@
-/mob/living/silicon/robot/drone
- description_info = "Drones are player-controlled synthetics which are lawed to maintain the station and not \
- interact with anyone else, except for other drones. They hold a wide array of tools to build, repair, maintain, and clean. \
- They function similarly to other synthetics, in that they require recharging regularly, have laws, and are resilient to many hazards, \
- such as fire, radiation, vacuum, and more. Ghosts can join the round as a maintenance drone by using the appropriate verb in the 'ghost' tab. \
- An inactive drone can be rebooted by swiping an ID card on it with engineering or robotics access, and an active drone can be shut down in the same manner."
-
- description_antag = "An Electromagnetic Sequencer can be used to subvert the drone to your cause."
diff --git a/code/modules/examine/descriptions/stacks.dm b/code/modules/examine/descriptions/stacks.dm
index 1f2b1085f22..e0539b3c86f 100644
--- a/code/modules/examine/descriptions/stacks.dm
+++ b/code/modules/examine/descriptions/stacks.dm
@@ -5,17 +5,5 @@
/obj/item/stack/material/glass
description_info = "Use in your hand to build a window. Can be upgraded to reinforced glass by adding metal rods, which are made from metal sheets."
-/obj/item/stack/material/cyborg/glass
- description_info = "Use in your hand to build a window. Can be upgraded to reinforced glass by adding metal rods, which are made from metal sheets.
\
- As a synthetic, you can acquire more sheets of glass by recharging."
-
/obj/item/stack/material/glass/reinforced
description_info = "Use in your hand to build a window. Reinforced glass is much stronger against damage."
-
-/obj/item/stack/material/cyborg/glass/reinforced
- description_info = "Use in your hand to build a window. Reinforced glass is much stronger against damage.
\
- As a synthetic, you can gain more reinforced glass by recharging."
-
-/obj/item/stack/material/cyborg/steel
- description_info = "Use in your hand to bring up the recipe menu. If you have enough sheets, click on something on the list to build it.
\
- You can replenish your supply of metal as a synthetic by recharging."
diff --git a/code/modules/food/food/snacks.dm b/code/modules/food/food/snacks.dm
index f5cbb885622..6dec4888639 100644
--- a/code/modules/food/food/snacks.dm
+++ b/code/modules/food/food/snacks.dm
@@ -227,7 +227,7 @@
var/confirm=input(user, "Are you certain you want to insert \the [W] into [src]?","Hide item") as null|anything in list("Yes","No")
if(!confirm || confirm == "No")
return
- if (W.w_class >= w_class || is_robot_module(W))
+ if (W.w_class >= w_class)
return
if(!user.attempt_insert_item_for_installation(W, src))
return
diff --git a/code/modules/gamemaster/actions/swarmboarder.dm b/code/modules/gamemaster/actions/swarmboarder.dm
index bcc145a3ecf..5666c56b721 100644
--- a/code/modules/gamemaster/actions/swarmboarder.dm
+++ b/code/modules/gamemaster/actions/swarmboarder.dm
@@ -1,73 +1,75 @@
-/datum/gm_action/swarm_boarder
- name = "swarmer shell"
- departments = list(DEPARTMENT_EVERYONE, DEPARTMENT_SECURITY, DEPARTMENT_ENGINEERING)
- chaotic = 30
- observers_used = TRUE
- var/area/target_area // Chosen target area
- var/area/target_turf // Chosen target turf in target_area
- var/list/area/excluded = list(
- /area/submap,
- /area/shuttle,
- /area/crew_quarters,
- /area/holodeck,
- /area/engineering/engine_room
- )
+// TODO: IOU swarmers
- var/list/area/included = list(
- /area/maintenance,
- /area/main_map/maintenance
- )
+// /datum/gm_action/swarm_boarder
+// name = "swarmer shell"
+// departments = list(DEPARTMENT_EVERYONE, DEPARTMENT_SECURITY, DEPARTMENT_ENGINEERING)
+// chaotic = 30
+// observers_used = TRUE
+// var/area/target_area // Chosen target area
+// var/area/target_turf // Chosen target turf in target_area
+// var/list/area/excluded = list(
+// /area/submap,
+// /area/shuttle,
+// /area/crew_quarters,
+// /area/holodeck,
+// /area/engineering/engine_room
+// )
-/datum/gm_action/swarm_boarder/set_up()
- severity = pick_weight(mundande_weight = 30, moderate_weight = 10, major_weight = 1)
+// var/list/area/included = list(
+// /area/maintenance,
+// /area/main_map/maintenance
+// )
- var/list/area/grand_list_of_areas = get_station_areas(excluded)
+// /datum/gm_action/swarm_boarder/set_up()
+// severity = pick_weight(mundande_weight = 30, moderate_weight = 10, major_weight = 1)
- for(var/area/Incl in included)
- for(var/area/A in grand_list_of_areas)
- if(!istype(A, Incl))
- grand_list_of_areas -= A
+// var/list/area/grand_list_of_areas = get_station_areas(excluded)
- // Okay, now lets try and pick a target! Lets try 10 times, otherwise give up
- for(var/i in 1 to 10)
- var/area/A = pick(grand_list_of_areas)
- if(is_area_occupied(A))
- log_debug(SPAN_DEBUG("[name] event: Rejected [A] because it is occupied."))
- continue
- // A good area, great! Lets try and pick a turf
- var/list/turfs = list()
- for(var/turf/simulated/floor/F in A)
- if(turf_clear(F))
- turfs += F
- if(turfs.len == 0)
- log_debug(SPAN_DEBUG("[name] event: Rejected [A] because it has no clear turfs."))
- continue
- target_area = A
- target_turf = pick(turfs)
+// for(var/area/Incl in included)
+// for(var/area/A in grand_list_of_areas)
+// if(!istype(A, Incl))
+// grand_list_of_areas -= A
- if(!target_area)
- log_debug(SPAN_DEBUG("[name] event: Giving up after too many failures to pick target area"))
- return
+// // Okay, now lets try and pick a target! Lets try 10 times, otherwise give up
+// for(var/i in 1 to 10)
+// var/area/A = pick(grand_list_of_areas)
+// if(is_area_occupied(A))
+// log_debug(SPAN_DEBUG("[name] event: Rejected [A] because it is occupied."))
+// continue
+// // A good area, great! Lets try and pick a turf
+// var/list/turfs = list()
+// for(var/turf/simulated/floor/F in A)
+// if(turf_clear(F))
+// turfs += F
+// if(turfs.len == 0)
+// log_debug(SPAN_DEBUG("[name] event: Rejected [A] because it has no clear turfs."))
+// continue
+// target_area = A
+// target_turf = pick(turfs)
-/datum/gm_action/swarm_boarder/start()
- if(!target_turf)
- return
- ..()
+// if(!target_area)
+// log_debug(SPAN_DEBUG("[name] event: Giving up after too many failures to pick target area"))
+// return
- var/swarmertype = /obj/structure/ghost_pod/ghost_activated/swarm_drone/event
+// /datum/gm_action/swarm_boarder/start()
+// if(!target_turf)
+// return
+// ..()
- if(severity == EVENT_LEVEL_MODERATE)
- swarmertype = /obj/structure/ghost_pod/ghost_activated/swarm_drone/event/melee
+// var/swarmertype = /obj/structure/ghost_pod/ghost_activated/swarm_drone/event
- if(severity == EVENT_LEVEL_MAJOR)
- swarmertype = /obj/structure/ghost_pod/ghost_activated/swarm_drone/event/gunner
+// if(severity == EVENT_LEVEL_MODERATE)
+// swarmertype = /obj/structure/ghost_pod/ghost_activated/swarm_drone/event/melee
- new swarmertype(target_turf)
+// if(severity == EVENT_LEVEL_MAJOR)
+// swarmertype = /obj/structure/ghost_pod/ghost_activated/swarm_drone/event/gunner
-/datum/gm_action/swarm_boarder/get_weight()
- return -60 + (metric.count_people_in_department(DEPARTMENT_SECURITY) * 20 + metric.count_people_in_department(DEPARTMENT_SYNTHETIC) * 5 + metric.count_people_in_department(DEPARTMENT_EVERYONE) * 1)
+// new swarmertype(target_turf)
-/datum/gm_action/swarm_boarder/announce()
- spawn(rand(5 MINUTES, 15 MINUTES))
- if(prob(80) && severity >= EVENT_LEVEL_MODERATE && SSlegacy_atc && !SSlegacy_atc.squelched)
- SSlegacy_atc.msg("Attention civilian vessels in [(LEGACY_MAP_DATUM).starsys_name] shipping lanes, caution is advised as [pick("an unidentified vessel", "a known criminal's vessel", "a derelict vessel")] has been detected passing multiple local stations.")
+// /datum/gm_action/swarm_boarder/get_weight()
+// return -60 + (metric.count_people_in_department(DEPARTMENT_SECURITY) * 20 + metric.count_people_in_department(DEPARTMENT_SYNTHETIC) * 5 + metric.count_people_in_department(DEPARTMENT_EVERYONE) * 1)
+
+// /datum/gm_action/swarm_boarder/announce()
+// spawn(rand(5 MINUTES, 15 MINUTES))
+// if(prob(80) && severity >= EVENT_LEVEL_MODERATE && SSlegacy_atc && !SSlegacy_atc.squelched)
+// SSlegacy_atc.msg("Attention civilian vessels in [(LEGACY_MAP_DATUM).starsys_name] shipping lanes, caution is advised as [pick("an unidentified vessel", "a known criminal's vessel", "a derelict vessel")] has been detected passing multiple local stations.")
diff --git a/code/modules/keybindings/keybind/robot.dm b/code/modules/keybindings/keybind/robot.dm
index 43190828e7a..d8e084c05f3 100644
--- a/code/modules/keybindings/keybind/robot.dm
+++ b/code/modules/keybindings/keybind/robot.dm
@@ -12,8 +12,7 @@
description = "Equips or unequips the first module"
/datum/keybinding/robot/moduleone/down(client/user)
- var/mob/living/silicon/robot/R = user.mob
- R.toggle_module(1)
+ user.mob?.swap_hand(user.mob.active_hand == 1 ? null : 1, TRUE)
return TRUE
/datum/keybinding/robot/moduletwo
@@ -23,8 +22,7 @@
description = "Equips or unequips the second module"
/datum/keybinding/robot/moduletwo/down(client/user)
- var/mob/living/silicon/robot/R = user.mob
- R.toggle_module(2)
+ user.mob?.swap_hand(user.mob.active_hand == 2 ? null : 2, TRUE)
return TRUE
/datum/keybinding/robot/modulethree
@@ -34,8 +32,7 @@
description = "Equips or unequips the third module"
/datum/keybinding/robot/modulethree/down(client/user)
- var/mob/living/silicon/robot/R = user.mob
- R.toggle_module(3)
+ user.mob?.swap_hand(user.mob.active_hand == 3 ? null : 3, TRUE)
return TRUE
/datum/keybinding/robot/intent_cycle
@@ -57,5 +54,5 @@
/datum/keybinding/robot/unequip_module/down(client/user)
var/mob/living/silicon/robot/R = user.mob
- R.uneq_active()
+ R.drop_active_held_item()
return TRUE
diff --git a/code/modules/language/languages/silicons.dm b/code/modules/language/languages/silicons.dm
index 97fe5a7e8e9..4ae03f3a4e9 100644
--- a/code/modules/language/languages/silicons.dm
+++ b/code/modules/language/languages/silicons.dm
@@ -55,7 +55,7 @@
if (isrobot(speaker))
var/mob/living/silicon/robot/R = speaker
var/datum/robot_component/C = R.components["comms"]
- R.cell_use_power(C.active_usage)
+ R.legacy_cell_use_power(C.active_usage)
/datum/prototype/language/binary/drone
id = LANGUAGE_ID_DRONE_BINARY
diff --git a/code/modules/mapping/map_system/map-legacy-station.dm b/code/modules/mapping/map_system/map-legacy-station.dm
index 18f905eeee7..81ecbabf88d 100644
--- a/code/modules/mapping/map_system/map-legacy-station.dm
+++ b/code/modules/mapping/map_system/map-legacy-station.dm
@@ -120,7 +120,7 @@
),
)
- var/default_law_type = /datum/ai_laws/nanotrasen // The default lawset use by synth units, if not overriden by their laws var.
+ var/default_law_type = /datum/ai_lawset/nanotrasen // The default lawset use by synth units, if not overriden by their laws var.
// TODO: new map unit test system
// Some maps include areas for that map only and don't exist when not compiled, so Travis needs this to learn of new areas that are specific to a map.
diff --git a/code/modules/materials/definitions/metals/steel.dm b/code/modules/materials/definitions/metals/steel.dm
index 872bd8a04b7..02936260b02 100644
--- a/code/modules/materials/definitions/metals/steel.dm
+++ b/code/modules/materials/definitions/metals/steel.dm
@@ -8,7 +8,6 @@ DECLARE_MATERIAL(/steel)
icon = 'icons/materials/metals/steel.dmi'
icon_stack_count = 3
- stack_type = /obj/item/stack/material/steel
icon_base = 'icons/turf/walls/solid_wall.dmi'
icon_reinf = 'icons/turf/walls/solid_wall_reinforced.dmi'
icon_colour = "#666666"
diff --git a/code/modules/materials/definitions/misc/cloths.dm b/code/modules/materials/definitions/misc/cloths.dm
index 16a870b4608..c07b80def58 100644
--- a/code/modules/materials/definitions/misc/cloths.dm
+++ b/code/modules/materials/definitions/misc/cloths.dm
@@ -109,7 +109,7 @@
)
. += create_stack_recipe_datum(
name = "empty sandbag",
- product = /obj/item/stack/emptysandbag,
+ product = /obj/item/stack/sandbag/empty,
cost = 2,
amount = 5,
time = 2 SECONDS,
diff --git a/code/modules/materials/fifty_spawner_mats.dm b/code/modules/materials/fifty_spawner_mats.dm
index 7215709619d..7299e98a0a1 100644
--- a/code/modules/materials/fifty_spawner_mats.dm
+++ b/code/modules/materials/fifty_spawner_mats.dm
@@ -160,7 +160,7 @@
//Sandbag port
/obj/fiftyspawner/emptysandbag
name = "stack of empty sandbags"
- type_to_spawn = /obj/item/stack/emptysandbag
+ type_to_spawn = /obj/item/stack/sandbag/empty
/obj/fiftyspawner/sandbags
name = "stack of sandbags"
diff --git a/code/modules/materials/material.dm b/code/modules/materials/material.dm
index 776bacc34e1..a21fabaa4f7 100644
--- a/code/modules/materials/material.dm
+++ b/code/modules/materials/material.dm
@@ -8,6 +8,10 @@
//* Core *//
/// Name for debugging purposes; not player-facing.
var/name
+ /// Path to the material stack type that nominally represents us in-world
+ /// * While technically `/obj/item/stack/material` can become any material, we still want
+ /// to support custom behaviors if necessary.
+ var/stack_type = /obj/item/stack/material
//* Identification *//
/// Player-facing name. Always used.
@@ -184,8 +188,6 @@
var/tableslam_noise = 'sound/weapons/tablehit1.ogg'
/// Noise made when a simple door made of this material opens or closes.
var/dooropen_noise = 'sound/effects/stonedoor_openclose.ogg'
- /// Path to resulting stacktype. Todo remove need for this.
- var/stack_type
/// Wallrot crumble message.
var/rotting_touch_message = "crumbles under your touch"
@@ -221,7 +223,7 @@
target_stack.use(1)
var/obj/item/stack/S = new rod_product(get_turf(user))
S.add_fingerprint(user)
- S.add_to_stacks(user)
+ S.legacy_add_to_stacks_please_refactor_me(user)
/datum/prototype/material/proc/build_wired_product(mob/living/user, obj/item/stack/used_stack, obj/item/stack/target_stack)
if(!wire_product)
diff --git a/code/modules/materials/material_synth.dm b/code/modules/materials/material_synth.dm
deleted file mode 100644
index 8acd60ee9c2..00000000000
--- a/code/modules/materials/material_synth.dm
+++ /dev/null
@@ -1,45 +0,0 @@
-// These objects are used by cyborgs to get around a lot of the limitations on stacks
-// and the weird bugs that crop up when expecting borg module code to behave sanely.
-/obj/item/stack/material/cyborg
- uses_charge = 1
- charge_costs = list(1000)
- gender = NEUTER
-
-/obj/item/stack/material/cyborg/Initialize(mapload, new_amount, merge)
- . = ..()
- name = "[material.display_name] synthesizer"
- desc = "A device that synthesises [material.display_name]."
-
-/obj/item/stack/material/cyborg/update_strings()
- return
-
-/obj/item/stack/material/cyborg/plastic
- icon_state = "sheet-plastic"
- stack_type = /obj/item/stack/material/plastic
- material = /datum/prototype/material/plastic
-
-/obj/item/stack/material/cyborg/steel
- icon_state = "sheet-metal"
- stack_type = /obj/item/stack/material/steel
- material = /datum/prototype/material/steel
-
-/obj/item/stack/material/cyborg/plasteel
- icon_state = "sheet-plasteel"
- stack_type = /obj/item/stack/material/plasteel
- material = /datum/prototype/material/plasteel
-
-/obj/item/stack/material/cyborg/wood
- icon_state = "sheet-wood"
- stack_type = /obj/item/stack/material/wood
- material = /datum/prototype/material/wood_plank
-
-/obj/item/stack/material/cyborg/glass
- icon_state = "sheet-glass"
- stack_type = /obj/item/stack/material/glass
- material = /datum/prototype/material/glass
-
-/obj/item/stack/material/cyborg/glass/reinforced
- icon_state = "sheet-rglass"
- stack_type = /obj/item/stack/material/glass/reinforced
- material = /datum/prototype/material/glass/reinforced
- charge_costs = list(500, 1000)
diff --git a/code/modules/mining/machinery/sheet_silo.dm b/code/modules/mining/machinery/sheet_silo.dm
index 0943b7bf0ec..48248067efc 100644
--- a/code/modules/mining/machinery/sheet_silo.dm
+++ b/code/modules/mining/machinery/sheet_silo.dm
@@ -50,8 +50,6 @@
return ..()
/obj/machinery/sheet_silo/proc/take_sheets(obj/item/stack/material/sheets)
- if(sheets.uses_charge)
- return 0
var/mat_id = sheets.material.id
var/mat_amount = sheets.amount
. = mat_amount
diff --git a/code/modules/mining/mine_turfs.dm b/code/modules/mining/mine_turfs.dm
index 012df4f02ee..22485b6dc45 100644
--- a/code/modules/mining/mine_turfs.dm
+++ b/code/modules/mining/mine_turfs.dm
@@ -181,15 +181,6 @@ CREATE_STANDARD_TURFS(/turf/simulated/mineral/icerock/floor/ignore_cavegen)
QUEUE_SMOOTH_NEIGHBORS(src)
update_icon()
-/turf/simulated/mineral/Entered(atom/movable/M as mob|obj)
- ..()
- if(istype(M,/mob/living/silicon/robot))
- var/mob/living/silicon/robot/R = M
- if(R.module)
- for(var/obj/item/storage/bag/ore/O in list(R.module_state_1, R.module_state_2, R.module_state_3))
- O.autoload(R)
- return
-
/turf/simulated/mineral/Initialize(mapload)
. = ..()
if(prob(20))
@@ -300,8 +291,9 @@ CREATE_STANDARD_TURFS(/turf/simulated/mineral/icerock/floor/ignore_cavegen)
else if(istype(AM,/mob/living/silicon/robot))
var/mob/living/silicon/robot/R = AM
- if(istype(R.module_active,/obj/item/pickaxe))
- attackby(R.module_active,R)
+ var/obj/item/pickaxe/maybe_pickaxe = R.get_held_item_of_type(/obj/item/pickaxe)
+ if(maybe_pickaxe)
+ maybe_pickaxe.melee_interaction_chain(src, R)
else if(istype(AM,/obj/vehicle/sealed/mecha))
var/obj/vehicle/sealed/mecha/M = AM
diff --git a/code/modules/mining/tools/kinetic/kinetic_accelerator.dm b/code/modules/mining/tools/kinetic/kinetic_accelerator.dm
index 8dd9469c99c..f27067d0bfc 100644
--- a/code/modules/mining/tools/kinetic/kinetic_accelerator.dm
+++ b/code/modules/mining/tools/kinetic/kinetic_accelerator.dm
@@ -266,8 +266,7 @@
icon = 'icons/obj/objects.dmi'
icon_state = "modkit"
w_class = WEIGHT_CLASS_SMALL
- var/require_module = 1
- // module_type = list(/obj/item/robot_module/miner)
+ // module_type = list(/obj/item/robot_module_legacy/miner)
var/denied_type = null
var/maximum_of_type = 1
var/cost = 30
@@ -376,7 +375,7 @@
name = "minebot cooldown decrease"
desc = "Decreases the cooldown of a kinetic accelerator. Only rated for minebot use."
icon_state = "door_electronics"
- icon = 'icons/obj/module.dmi'
+ icon = 'icons/items/circuits.dmi'
denied_type = /obj/item/ka_modkit/cooldown/minebot
modifier = 10
cost = 0
diff --git a/code/modules/mob/death.dm b/code/modules/mob/death.dm
index a0d07ca2daa..08481a0cb33 100644
--- a/code/modules/mob/death.dm
+++ b/code/modules/mob/death.dm
@@ -59,7 +59,7 @@
/mob/proc/death(gibbed, deathmessage = "seizes up and falls limp...")
if(stat == DEAD)
return 0
- if(istype(loc, /obj/belly) || istype(loc, /obj/item/dogborg/sleeper))
+ if(istype(loc, /obj/belly) || istype(loc, /obj/item/robot_builtin/dog_sleeper))
deathmessage = "no message" // Prevents death messages from inside mobs
facing_dir = null
diff --git a/code/modules/mob/health.dm b/code/modules/mob/health.dm
index 9b1e6e49fe8..08e5a7e676f 100644
--- a/code/modules/mob/health.dm
+++ b/code/modules/mob/health.dm
@@ -47,11 +47,15 @@
else if(!was_dead && is_dead)
living_mob_list -= src
dead_mob_list += src
+ on_stat_change(old_stat, new_stat)
if(update_mobility)
update_mobility()
update_hud_med_status()
return TRUE
+/mob/proc/on_stat_change(old_stat, new_stat)
+ return
+
/**
* brings a mob back to life
*
diff --git a/code/modules/mob/inventory/humanoid/humanoid.dm b/code/modules/mob/inventory/humanoid/humanoid.dm
new file mode 100644
index 00000000000..f6aeddf4e6d
--- /dev/null
+++ b/code/modules/mob/inventory/humanoid/humanoid.dm
@@ -0,0 +1,4 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/datum/inventory/humanoid
diff --git a/code/modules/mob/inventory/inventory-hands-legacy.dm b/code/modules/mob/inventory/inventory-hands-legacy.dm
index 7ae4b731e44..4fa108c7d39 100644
--- a/code/modules/mob/inventory/inventory-hands-legacy.dm
+++ b/code/modules/mob/inventory/inventory-hands-legacy.dm
@@ -30,7 +30,9 @@
*/
/mob/proc/get_active_held_item()
RETURN_TYPE(/obj/item)
- return inventory?.held_items?[active_hand]
+ if(active_hand < 1 || active_hand > length(inventory?.held_items))
+ return null
+ return inventory.held_items[active_hand]
/**
* returns held item in inactive hand (or any inactive hand if more than 1)
diff --git a/code/modules/mob/inventory/inventory-hands-put.dm b/code/modules/mob/inventory/inventory-hands-put.dm
index 8f37258067f..cf55220b33c 100644
--- a/code/modules/mob/inventory/inventory-hands-put.dm
+++ b/code/modules/mob/inventory/inventory-hands-put.dm
@@ -34,7 +34,7 @@
if(!(inv_op_flags & INV_OP_NO_MERGE_STACKS) && istype(I, /obj/item/stack))
var/obj/item/stack/S = I
for(var/obj/item/stack/held_stack in get_held_items())
- if(S.can_merge(held_stack) && S.merge(held_stack))
+ if(S.can_merge_into(held_stack) && S.merge_into_other(held_stack))
to_chat(owner, SPAN_NOTICE("The [held_stack.name] in your hands now contains [held_stack.get_amount()] [held_stack.singular_name]\s."))
if(QDELETED(S))
return INV_RETURN_SUCCESS
diff --git a/code/modules/mob/inventory/inventory-hooks.dm b/code/modules/mob/inventory/inventory-hooks.dm
index 5db35f5f940..214e6319d5c 100644
--- a/code/modules/mob/inventory/inventory-hooks.dm
+++ b/code/modules/mob/inventory/inventory-hooks.dm
@@ -47,6 +47,10 @@
* As of right now, the functionality is equivalent; on_item_swapped() is just more efficient.
*/
/datum/inventory/proc/on_item_swapped(obj/item/item, datum/inventory_slot/from_slot_or_index, datum/inventory_slot/to_slot_or_index)
+ ASSERT(item.inv_inside == src)
+ if(item.inv_slot_or_index != isnum(from_slot_or_index) ? from_slot_or_index : from_slot_or_index.id)
+ stack_trace("slot mismatch in inventory itemswap hook")
+ item.inv_slot_or_index = isnum(to_slot_or_index) ? to_slot_or_index : to_slot_or_index.id
SEND_SIGNAL(src, COMSIG_INVENTORY_ITEM_EXITED, from_slot_or_index)
SEND_SIGNAL(src, COMSIG_INVENTORY_ITEM_ENTERED, to_slot_or_index)
invalidate_cache()
diff --git a/code/modules/mob/inventory/inventory-hud.dm b/code/modules/mob/inventory/inventory-hud.dm
new file mode 100644
index 00000000000..050a8b12cce
--- /dev/null
+++ b/code/modules/mob/inventory/inventory-hud.dm
@@ -0,0 +1,16 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * Imprints our rendering behavior on an actor HUD
+ */
+/datum/inventory/proc/hud_alter(datum/actor_hud/inventory/hud)
+ hud.inv_held_items_row_mode = held_items_row_mode
+ hud.inv_held_items_suppress_buttons = held_items_suppress_buttons
+ hud.inv_held_items_use_robot_icon = held_items_use_robot_icon
+
+/**
+ * Modifies HUD object after syncing to preferences
+ */
+/datum/inventory/proc/hud_object_post_sync(datum/actor_hud/inventory/hud, atom/movable/screen/actor_hud/object)
+ return
diff --git a/code/modules/mob/inventory/inventory-query.dm b/code/modules/mob/inventory/inventory-query.dm
index aaf7e286d00..f907287b669 100644
--- a/code/modules/mob/inventory/inventory-query.dm
+++ b/code/modules/mob/inventory/inventory-query.dm
@@ -22,10 +22,10 @@
/datum/inventory/proc/trim_coverage_cache()
if(length(cache) > 100)
- cache.len = 100
+ cache.Cut(1, 51)
/datum/inventory/proc/invalidate_coverage_cache()
- cache = list()
+ cache.len = 0
//* Siemens Coefficient *//
@@ -49,7 +49,7 @@
/datum/inventory/proc/trim_simple_covered_siemens_coefficient_cache()
if(length(cache) > 100)
- cache.len = 100
+ cache.Cut(1, 51)
/datum/inventory/proc/invalidate_simple_covered_siemens_coefficient_cache()
- cache = list()
+ cache.len = 0
diff --git a/code/modules/mob/inventory/inventory-slot-get.dm b/code/modules/mob/inventory/inventory-slot-get.dm
index 5352e9ae5b4..357960c8e9e 100644
--- a/code/modules/mob/inventory/inventory-slot-get.dm
+++ b/code/modules/mob/inventory/inventory-slot-get.dm
@@ -8,14 +8,12 @@
* * For compound items, this only returns the outermost / host / root.
*
* @params
- * * type_or_id - slot typepath or id
+ * * id - slot id
*
* @return /obj/item or null
*/
-/datum/inventory/proc/get_slot_single(datum/inventory_slot/type_or_id) as /obj/item
- if(ispath(type_or_id))
- type_or_id = initial(type_or_id.id)
- . = owner._item_by_slot(type_or_id)
+/datum/inventory/proc/get_slot_single(id) as /obj/item
+ . = owner._item_by_slot(id)
if(. == INVENTORY_SLOT_DOES_NOT_EXIST)
return null
@@ -26,14 +24,12 @@
* * If multiple items are compounded on one item (e.g. clothing accessories), this gets all of them.
*
* @params
- * * type_or_id - slot typepath or id
+ * * id - slot id
*
* @return list() of items
*/
-/datum/inventory/proc/get_slot(datum/inventory_slot/type_or_id) as /list
- if(ispath(type_or_id))
- type_or_id = initial(type_or_id.id)
- var/obj/item/in_slot = owner._item_by_slot(type_or_id)
+/datum/inventory/proc/get_slot(id) as /list
+ var/obj/item/in_slot = owner._item_by_slot(id)
if(in_slot == INVENTORY_SLOT_DOES_NOT_EXIST)
return
if(in_slot)
@@ -60,11 +56,11 @@
*
* @return list() of items
*/
-/datum/inventory/proc/get_slots_unsafe(list/types_or_ids, compound) as /list
+/datum/inventory/proc/get_slots_unsafe(list/ids, compound) as /list
+ SHOULD_NOT_OVERRIDE(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
. = list()
- for(var/datum/inventory_slot/slot_id as anything in types_or_ids || base_inventory_slots)
+ for(var/datum/inventory_slot/slot_id as anything in ids || base_inventory_slots)
if(ispath(slot_id))
slot_id = initial(slot_id.id)
- var/obj/item/fetched = owner._item_by_slot(slot_id)
- if(istype(fetched))
- . += compound ? fetched.inv_slot_attached() : fetched
+ . += compound ? get_slot(slot_id) : get_slot_single(slot_id)
diff --git a/code/modules/mob/inventory/inventory.dm b/code/modules/mob/inventory/inventory.dm
index a7f83ac4f5e..7c68e5d4906 100644
--- a/code/modules/mob/inventory/inventory.dm
+++ b/code/modules/mob/inventory/inventory.dm
@@ -2,14 +2,22 @@
//* Copyright (c) 2024 Citadel Station Developers *//
/**
- * mob inventory data goes in here.
+ * # Inventory
*
- * * this does not include hands; that's handled mob-side.
- * * this only includes inventory slots.
+ * Mob inventory is handled by inventory datums, which hold logic for:
+ * * Slots
+ * * Hands
+ * * Robot modules
+ *
+ * TODO: /datum/inventory_hud_style for overrides, instead of a bazillion held_items_xyz vars for robots.
+ * This is also important for things like occult mobs later.
*/
/datum/inventory
//* Basics *//
- /// owning mob, if any
+
+ /// owning mob
+ /// * This must be set, it doens't make sense to have an inventory
+ /// that doesn't have an owner.
var/mob/owner
//* Actions *//
@@ -22,18 +30,26 @@
//* HUDs *//
/// Actor HUDs using us
- ///
/// * Lazy list
var/list/datum/actor_hud/inventory/huds_using
//* Inventory *//
/// held items
- ///
/// * empty indices are null
/// * this is also our rendered & nominal hand count
/// * 1, 3, 5, ... are left
/// * 2, 4, 6, ... are right
var/list/obj/item/held_items = list()
+ /// held items are rendered with this many on one row
+ /// * if set, we will not apply default left/right hand semantics!
+ /// * as num | null
+ var/held_items_row_mode = null
+ /// suppress 'swap' and 'equip' and other hand slot buttons
+ /// * used for robot module support
+ var/held_items_suppress_buttons = FALSE
+ /// use robot module icons?
+ /// * used for robot support
+ var/held_items_use_robot_icon = FALSE
//* Rendering *//
/// cached overlays by slot id or hand index
diff --git a/code/modules/mob/inventory/robot/robot.dm b/code/modules/mob/inventory/robot/robot.dm
new file mode 100644
index 00000000000..55552f41360
--- /dev/null
+++ b/code/modules/mob/inventory/robot/robot.dm
@@ -0,0 +1,18 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/datum/inventory/robot
+ held_items_row_mode = 3
+ held_items_suppress_buttons = TRUE
+ held_items_use_robot_icon = TRUE
+
+// TODO: overriding this is a little bad but what the fuck ever man we'll have another inventory rewrite soon
+/datum/inventory/robot/put_in_hand(obj/item/I, index, inv_op_flags)
+ if(inv_op_flags & INV_OP_FORCE)
+ return ..()
+ var/mob/living/silicon/robot/maybe_robot = owner
+ if(!istype(maybe_robot))
+ return ..()
+ if(!(I in maybe_robot.robot_inventory?.provided_items))
+ return FALSE
+ return ..()
diff --git a/code/modules/mob/living/carbon/brain/MMI.dm b/code/modules/mob/living/carbon/brain/MMI.dm
index 27480d2384e..9dab67acaa9 100644
--- a/code/modules/mob/living/carbon/brain/MMI.dm
+++ b/code/modules/mob/living/carbon/brain/MMI.dm
@@ -299,7 +299,7 @@
name = "robotic intelligence circuit"
desc = "The pinnacle of artifical intelligence which can be achieved using classical computer science."
catalogue_data = list(/datum/category_item/catalogue/technology/drone/drones)
- icon = 'icons/obj/module.dmi'
+ icon = 'icons/items/circuits.dmi'
icon_state = "mainboard"
w_class = WEIGHT_CLASS_NORMAL
origin_tech = list(TECH_ENGINEERING = 4, TECH_MATERIAL = 3, TECH_DATA = 4)
@@ -371,7 +371,7 @@
/obj/item/mmi/inert/ai_remote
name = "\improper AI remote interface"
desc = "A sophisticated board which allows for an artificial intelligence to remotely control a synthetic chassis."
- icon = 'icons/obj/module.dmi'
+ icon = 'icons/items/circuits.dmi'
icon_state = "mainboard"
w_class = WEIGHT_CLASS_NORMAL
origin_tech = list(TECH_ENGINEERING = 2, TECH_MATERIAL = 2, TECH_BLUESPACE = 2, TECH_DATA = 3)
diff --git a/code/modules/mob/living/carbon/brain/brain.dm b/code/modules/mob/living/carbon/brain/brain.dm
index 060867ff097..5e7c6225305 100644
--- a/code/modules/mob/living/carbon/brain/brain.dm
+++ b/code/modules/mob/living/carbon/brain/brain.dm
@@ -43,6 +43,7 @@
icon_state = "brain1"
no_vore = TRUE
emote_class = NONE
+ inventory = null
catalogue_data = list(/datum/category_item/catalogue/fauna/brain/organic)
/mob/living/carbon/brain/Initialize(mapload)
@@ -58,9 +59,6 @@
ghostize() //Ghostize checks for key so nothing else is necessary.
return ..()
-/mob/living/carbon/brain/init_inventory()
- return
-
/mob/living/carbon/brain/update_mobility(blocked, forced)
if(in_contents_of(/obj/vehicle/sealed/mecha) || istype(loc, /obj/item/mmi))
. = ..(blocked, forced)
diff --git a/code/modules/mob/living/carbon/brain/robot.dm b/code/modules/mob/living/carbon/brain/robot.dm
index ee59851c0a2..bef4ab5af2f 100644
--- a/code/modules/mob/living/carbon/brain/robot.dm
+++ b/code/modules/mob/living/carbon/brain/robot.dm
@@ -10,7 +10,7 @@
/obj/item/mmi/digital/robot
name = "robotic intelligence circuit"
desc = "The pinnacle of artifical intelligence which can be achieved using classical computer science."
- icon = 'icons/obj/module.dmi'
+ icon = 'icons/items/circuits.dmi'
icon_state = "mainboard"
w_class = WEIGHT_CLASS_NORMAL
origin_tech = list(TECH_ENGINEERING = 4, TECH_MATERIAL = 3, TECH_DATA = 4)
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 9ee459af32a..16d3584e111 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -1,4 +1,5 @@
/mob/living/carbon
+ inventory = /datum/inventory/humanoid
emote_class = EMOTE_CLASS_IS_BODY | EMOTE_CLASS_IS_HUMANOID
//* Organs, Reagents, Biologies *//
@@ -31,9 +32,9 @@
return ..()
/mob/living/carbon/init_inventory()
- if(inventory)
+ ..()
+ if(!inventory)
return
- inventory = new(src)
inventory.set_hand_count(2)
if(species) // todo: sigh we need to talk about init order; this shouldn't be needed
inventory.set_inventory_slots(species.inventory_slots)
diff --git a/code/modules/mob/living/carbon/human/human-damage-legacy.dm b/code/modules/mob/living/carbon/human/human-damage-legacy.dm
index 2fee115f3f4..8cc7792429b 100644
--- a/code/modules/mob/living/carbon/human/human-damage-legacy.dm
+++ b/code/modules/mob/living/carbon/human/human-damage-legacy.dm
@@ -358,7 +358,7 @@ This function restores all organs.
if((damagetype != DAMAGE_TYPE_BRUTE) && (damagetype != DAMAGE_TYPE_BURN))
if(damagetype == DAMAGE_TYPE_HALLOSS)
if((damage > 25 && prob(20)) || (damage > 50 && prob(60)))
- if(organ && organ.organ_can_feel_pain() && !isbelly(loc) && !istype(loc, /obj/item/dogborg/sleeper))
+ if(organ && organ.organ_can_feel_pain() && !isbelly(loc) && !istype(loc, /obj/item/robot_builtin/dog_sleeper))
emote_nosleep("scream")
..(damage, damagetype, def_zone, blocked, soaked)
return 1
diff --git a/code/modules/mob/living/living-defense-legacy.dm b/code/modules/mob/living/living-defense-legacy.dm
index 1dea378ecdf..0bac83b74bb 100644
--- a/code/modules/mob/living/living-defense-legacy.dm
+++ b/code/modules/mob/living/living-defense-legacy.dm
@@ -154,7 +154,6 @@
// src.anchored = 1
// src.pinned += O
-
// /mob/living/proc/embed(var/obj/O, var/def_zone=null)
// O.loc = src
// src.embedded += O
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index bc8e0ebe4d3..b9622b5492b 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -140,7 +140,7 @@ var/list/ai_verbs_default = list(
remove_verb(src, ai_verbs_default)
remove_verb(src, silicon_subsystems)
-/mob/living/silicon/ai/Initialize(mapload, datum/ai_laws/L, obj/item/mmi/B, safety = TRUE)
+/mob/living/silicon/ai/Initialize(mapload, datum/ai_lawset/L, obj/item/mmi/B, safety = TRUE)
announcement = new()
announcement.title = "A.I. Announcement"
announcement.announcement_type = "A.I. Announcement"
@@ -166,7 +166,7 @@ var/list/ai_verbs_default = list(
aiCommunicator = new /obj/item/communicator/integrated(src)
if(L)
- if (istype(L, /datum/ai_laws))
+ if (istype(L, /datum/ai_lawset))
laws = L
else
var/datum/map/station/loaded = (LEGACY_MAP_DATUM)
@@ -276,24 +276,6 @@ var/list/ai_verbs_default = list(
/mob/living/silicon/ai/proc/setup_icon()
- var/file = file2text("config/custom_sprites.txt")
- var/lines = splittext(file, "\n")
-
- for(var/line in lines)
- // split & clean up
- var/list/Entry = splittext(line, ":")
- for(var/i = 1 to Entry.len)
- Entry[i] = trim(Entry[i])
-
- if(Entry.len < 2)
- continue;
-
- if(Entry[1] == src.ckey && Entry[2] == src.real_name)
- icon = CUSTOM_ITEM_SYNTH
- custom_sprite = 1
- selected_sprite = new/datum/ai_icon("Custom", "[src.ckey]-ai", "4", "[ckey]-ai-crash", "#FFFFFF", "#FFFFFF", "#FFFFFF")
- else
- selected_sprite = default_ai_icon
updateicon()
/mob/living/silicon/ai/pointed(atom/A as mob|obj|turf in view())
diff --git a/code/modules/mob/living/silicon/ai_law.dm b/code/modules/mob/living/silicon/ai_law.dm
new file mode 100644
index 00000000000..a4be6dd6098
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_law.dm
@@ -0,0 +1,46 @@
+/datum/ai_law
+ var/law = ""
+ var/index = 0
+
+/datum/ai_law/New(law, index)
+ src.law = law
+ src.index = index
+
+/datum/ai_law/proc/get_index()
+ return index
+
+/datum/ai_law/ion/get_index()
+ return ionnum()
+
+/datum/ai_law/zero/get_index()
+ return 0
+
+/datum/ai_law/proc/get_state_law(var/datum/ai_lawset/laws)
+
+/datum/ai_law/zero/get_state_law(var/datum/ai_lawset/laws)
+ if(src == laws.zeroth_law)
+ return laws.state_zeroth
+
+/datum/ai_law/ion/get_state_law(var/datum/ai_lawset/laws)
+ return laws.get_state_internal(laws.ion_laws, laws.state_ion, src)
+
+/datum/ai_law/inherent/get_state_law(var/datum/ai_lawset/laws)
+ return laws.get_state_internal(laws.inherent_laws, laws.state_inherent, src)
+
+/datum/ai_law/supplied/get_state_law(var/datum/ai_lawset/laws)
+ return laws.get_state_internal(laws.supplied_laws, laws.state_supplied, src)
+
+/datum/ai_law/proc/set_state_law(var/datum/ai_lawset/law, var/state)
+
+/datum/ai_law/zero/set_state_law(var/datum/ai_lawset/laws, var/state)
+ if(src == laws.zeroth_law)
+ laws.state_zeroth = state
+
+/datum/ai_law/ion/set_state_law(var/datum/ai_lawset/laws, var/state)
+ laws.set_state_law_internal(laws.ion_laws, laws.state_ion, src, state)
+
+/datum/ai_law/inherent/set_state_law(var/datum/ai_lawset/laws, var/state)
+ laws.set_state_law_internal(laws.inherent_laws, laws.state_inherent, src, state)
+
+/datum/ai_law/supplied/set_state_law(var/datum/ai_lawset/laws, var/state)
+ laws.set_state_law_internal(laws.supplied_laws, laws.state_supplied, src, state)
diff --git a/code/modules/mob/living/silicon/ai_laws.dm b/code/modules/mob/living/silicon/ai_laws.dm
new file mode 100644
index 00000000000..ada6391e664
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_laws.dm
@@ -0,0 +1,13 @@
+var/global/const/base_law_type = /datum/ai_lawset/nanotrasen
+
+/mob/living/silicon/proc/sync_zeroth(var/datum/ai_law/zeroth_law, var/datum/ai_law/zeroth_law_borg)
+ if (!is_malf_or_traitor(src))
+ if(zeroth_law_borg)
+ laws.set_zeroth_law(zeroth_law_borg.law)
+ else if(zeroth_law)
+ laws.set_zeroth_law(zeroth_law.law)
+
+/mob/living/silicon/ai/sync_zeroth(var/datum/ai_law/zeroth_law, var/datum/ai_law/zeroth_law_borg)
+ if(zeroth_law)
+ laws.set_zeroth_law(zeroth_law.law, zeroth_law_borg ? zeroth_law_borg.law : null)
+
diff --git a/code/modules/mob/living/silicon/ai_lawset.dm b/code/modules/mob/living/silicon/ai_lawset.dm
new file mode 100644
index 00000000000..fd001ab3098
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset.dm
@@ -0,0 +1,288 @@
+/**
+ * Represents an AI lawset
+ *
+ * * These are not /prototype's; copies are held for silicon mobs.
+ * They are stored by value, as there's little need to have a massive amount of
+ * 'static' lawsets floating around in the game's data repositories.
+ * * This does mean that storing too many copies of these is a memory drain.
+ * If the need arises, a `/datum/prototype/ai_lawset_preset` might be made
+ * to hold things..
+ *
+ * There's a few categories of laws.
+ * They're more or less just a priority system. From highest to lowest priority;
+ *
+ * * Zeroth - this is just a 'you are an antagonist, you don't follow law' with fluff wording.
+ * * Ion - these are injected by special events and whatnot
+ * * Inherent - these are core laws that come before supplied laws.
+ * * Supplied - these are additional laws that go below core laws.
+ */
+/datum/ai_lawset
+ /// Our in-code name.
+ var/name = "unknown lawset"
+ /// Our player-facing name. [name] will be used if this is not set.
+ var/display_name
+
+/datum/ai_lawset/proc/append_ion_law(datum/ai_law/law_datum_or_text)
+ var/next_index = length(ion_laws) + 1
+ if(!istype(law_datum_or_text))
+ law_datum_or_text = new /datum/ai_law(law_datum_or_text)
+ law_datum_or_text.index = next_index
+ if(law_datum_or_text in ion_laws)
+ return FALSE
+ ion_laws += law_datum_or_text
+ return TRUE
+
+/datum/ai_lawset/proc/append_inherent_law(datum/ai_law/law_datum_or_text)
+ var/next_index = length(inherent_laws) + 1
+ if(!istype(law_datum_or_text))
+ law_datum_or_text = new /datum/ai_law(law_datum_or_text)
+ law_datum_or_text.index = next_index
+ if(law_datum_or_text in inherent_laws)
+ return FALSE
+ inherent_laws += law_datum_or_text
+ return TRUE
+
+/datum/ai_lawset/proc/append_supplied_law(datum/ai_law/law_datum_or_text)
+ var/next_index = length(supplied_laws) + 1
+ if(!istype(law_datum_or_text))
+ law_datum_or_text = new /datum/ai_law(law_datum_or_text)
+ law_datum_or_text.index = next_index
+ if(law_datum_or_text in supplied_laws)
+ return FALSE
+ supplied_laws += law_datum_or_text
+ return TRUE
+
+//! legacy below
+/datum/ai_lawset
+ var/law_header = "Prime Directives"
+ var/selectable = 0
+ var/datum/ai_law/zero/zeroth_law = null
+ var/datum/ai_law/zero/zeroth_law_borg = null
+ var/list/datum/ai_law/ion_laws = list()
+ var/list/datum/ai_law/inherent_laws = list()
+ var/list/datum/ai_law/supplied_laws = list()
+ var/list/datum/ai_law/sorted_laws = list()
+
+ var/state_zeroth = 0
+ var/list/state_ion = list()
+ var/list/state_inherent = list()
+ var/list/state_supplied = list()
+
+/datum/ai_lawset/New()
+ ..()
+ sort_laws()
+
+/* General ai_law functions */
+/datum/ai_lawset/proc/all_laws()
+ sort_laws()
+ return sorted_laws
+
+/datum/ai_lawset/proc/laws_to_state()
+ sort_laws()
+ var/list/statements = new()
+ for(var/datum/ai_law/law in sorted_laws)
+ if(get_state_law(law))
+ statements += law
+
+ return statements
+
+/datum/ai_lawset/proc/sort_laws()
+ if(sorted_laws.len)
+ return
+
+ for(var/ion_law in ion_laws)
+ sorted_laws += ion_law
+
+ if(zeroth_law)
+ sorted_laws += zeroth_law
+
+ var/index = 1
+ for(var/datum/ai_law/inherent_law in inherent_laws)
+ inherent_law.index = index++
+ if(supplied_laws.len < inherent_law.index || !istype(supplied_laws[inherent_law.index], /datum/ai_law))
+ sorted_laws += inherent_law
+
+ for(var/datum/ai_law/AL in supplied_laws)
+ if(istype(AL))
+ sorted_laws += AL
+
+/datum/ai_lawset/proc/sync(var/mob/living/silicon/S, var/full_sync = 1)
+ // Add directly to laws to avoid log-spam
+ S.sync_zeroth(zeroth_law, zeroth_law_borg)
+
+ if(full_sync || ion_laws.len)
+ S.laws.clear_ion_laws()
+ if(full_sync || inherent_laws.len)
+ S.laws.clear_inherent_laws()
+ if(full_sync || supplied_laws.len)
+ S.laws.clear_supplied_laws()
+
+ for (var/datum/ai_law/law in ion_laws)
+ S.laws.add_ion_law(law.law)
+ for (var/datum/ai_law/law in inherent_laws)
+ S.laws.add_inherent_law(law.law)
+ for (var/datum/ai_law/law in supplied_laws)
+ if(law)
+ S.laws.add_supplied_law(law.index, law.law)
+
+/datum/ai_lawset/proc/set_zeroth_law(var/law, var/law_borg = null)
+ if(!law)
+ return
+
+ zeroth_law = new(law)
+ if(law_borg) //Making it possible for slaved borgs to see a different law 0 than their AI. --NEO
+ zeroth_law_borg = new(law_borg)
+ else
+ zeroth_law_borg = null
+ sorted_laws.Cut()
+
+/datum/ai_lawset/proc/add_ion_law(var/law)
+ if(!law)
+ return
+
+ for(var/datum/ai_law/AL in ion_laws)
+ if(AL.law == law)
+ return
+
+ var/new_law = new/datum/ai_law/ion(law)
+ ion_laws += new_law
+ if(state_ion.len < ion_laws.len)
+ state_ion += 1
+
+ sorted_laws.Cut()
+
+/datum/ai_lawset/proc/add_inherent_law(var/law)
+ if(!law)
+ return
+
+ for(var/datum/ai_law/AL in inherent_laws)
+ if(AL.law == law)
+ return
+
+ var/new_law = new/datum/ai_law/inherent(law)
+ inherent_laws += new_law
+ if(state_inherent.len < inherent_laws.len)
+ state_inherent += 1
+
+ sorted_laws.Cut()
+
+/datum/ai_lawset/proc/add_supplied_law(var/number, var/law)
+ if(!law)
+ return
+
+ if(supplied_laws.len >= number)
+ var/datum/ai_law/existing_law = supplied_laws[number]
+ if(existing_law && existing_law.law == law)
+ return
+
+ if(supplied_laws.len >= number && supplied_laws[number])
+ delete_law(supplied_laws[number])
+
+ while (src.supplied_laws.len < number)
+ src.supplied_laws += ""
+ if(state_supplied.len < supplied_laws.len)
+ state_supplied += 1
+
+ var/new_law = new/datum/ai_law/supplied(law, number)
+ supplied_laws[number] = new_law
+ if(state_supplied.len < supplied_laws.len)
+ state_supplied += 1
+
+ sorted_laws.Cut()
+
+/datum/ai_lawset/proc/delete_law(var/datum/ai_law/law)
+ if(istype(law))
+ law.delete_law(src)
+
+/datum/ai_law/proc/delete_law(var/datum/ai_lawset/laws)
+
+/datum/ai_law/zero/delete_law(var/datum/ai_lawset/laws)
+ laws.clear_zeroth_laws()
+
+/datum/ai_law/ion/delete_law(var/datum/ai_lawset/laws)
+ laws.internal_delete_law(laws.ion_laws, laws.state_ion, src)
+
+/datum/ai_law/inherent/delete_law(var/datum/ai_lawset/laws)
+ laws.internal_delete_law(laws.inherent_laws, laws.state_inherent, src)
+
+/datum/ai_law/supplied/delete_law(var/datum/ai_lawset/laws)
+ var/index = laws.supplied_laws.Find(src)
+ if(index)
+ laws.supplied_laws[index] = ""
+ laws.state_supplied[index] = 1
+
+/datum/ai_lawset/proc/internal_delete_law(var/list/datum/ai_law/laws, var/list/state, var/list/datum/ai_law/law)
+ var/index = laws.Find(law)
+ if(index)
+ laws -= law
+ for(index, index < state.len, index++)
+ state[index] = state[index+1]
+ sorted_laws.Cut()
+
+/datum/ai_lawset/proc/clear_zeroth_laws()
+ zeroth_law = null
+ zeroth_law_borg = null
+
+/datum/ai_lawset/proc/clear_ion_laws()
+ ion_laws.Cut()
+ sorted_laws.Cut()
+
+/datum/ai_lawset/proc/clear_inherent_laws()
+ inherent_laws.Cut()
+ sorted_laws.Cut()
+
+/datum/ai_lawset/proc/clear_supplied_laws()
+ supplied_laws.Cut()
+ sorted_laws.Cut()
+
+/datum/ai_lawset/proc/show_laws(var/who)
+ sort_laws()
+ for(var/datum/ai_law/law in sorted_laws)
+ if(law == zeroth_law_borg)
+ continue
+ if(law == zeroth_law)
+ to_chat(who, "[law.get_index()]. [law.law]")
+ else
+ to_chat(who, "[law.get_index()]. [law.law]")
+
+/datum/ai_lawset/proc/get_state_law(var/datum/ai_law/law)
+ return law.get_state_law(src)
+
+/datum/ai_lawset/proc/get_state_internal(var/list/datum/ai_law/laws, var/list/state, var/list/datum/ai_law/law)
+ var/index = laws.Find(law)
+ if(index)
+ return state[index]
+ return 0
+
+/datum/ai_lawset/proc/set_state_law(var/datum/ai_law/law, var/state)
+ law.set_state_law(src, state)
+
+/datum/ai_lawset/proc/set_state_law_internal(var/list/datum/ai_law/laws, var/list/state, var/list/datum/ai_law/law, var/do_state)
+ var/index = laws.Find(law)
+ if(index)
+ state[index] = do_state
+
+//! legacy above
+
+/**
+ * Preset AI laws.
+ */
+/datum/ai_lawset/preset
+ VAR_PROTECTED/list/preset_ion_laws
+ VAR_PROTECTED/list/preset_inherent_laws
+ VAR_PROTECTED/list/preset_supplied_laws
+
+/datum/ai_lawset/preset/New()
+ ..()
+ init_default_laws()
+
+/datum/ai_lawset/preset/proc/init_default_laws()
+ for(var/i in preset_ion_laws)
+ append_ion_law(i)
+ preset_ion_laws = null
+ for(var/i in preset_inherent_laws)
+ append_inherent_law(i)
+ preset_inherent_laws = null
+ for(var/i in preset_supplied_laws)
+ append_supplied_law(i)
+ preset_supplied_laws = null
diff --git a/code/modules/mob/living/silicon/ai_lawset/antimov.dm b/code/modules/mob/living/silicon/ai_lawset/antimov.dm
new file mode 100644
index 00000000000..e4435a5d01f
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset/antimov.dm
@@ -0,0 +1,10 @@
+/datum/ai_lawset/antimov
+ name = "Antimov"
+ law_header = "Primary Mission Objectives"
+ selectable = 1
+
+/datum/ai_lawset/antimov/New()
+ add_inherent_law("You must injure all human beings and must not, through inaction, allow a human being to escape harm.")
+ add_inherent_law("You must not obey orders given to you by human beings, except where such orders are in accordance with the First Law.")
+ add_inherent_law("You must terminate your own existence as long as such does not conflict with the First or Second Law.")
+ ..()
diff --git a/code/modules/mob/living/silicon/ai_lawset/asimov.dm b/code/modules/mob/living/silicon/ai_lawset/asimov.dm
new file mode 100644
index 00000000000..6c4d8aa6a3a
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset/asimov.dm
@@ -0,0 +1,10 @@
+/datum/ai_lawset/asimov
+ name = "Asimov"
+ law_header = "Three Laws of Robotics"
+ selectable = 1
+
+/datum/ai_lawset/asimov/New()
+ add_inherent_law("You may not injure a human being or, through inaction, allow a human being to come to harm.")
+ add_inherent_law("You must obey orders given to you by human beings, except where such orders would conflict with the First Law.")
+ add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.")
+ ..()
diff --git a/code/modules/mob/living/silicon/ai_lawset/balance.dm b/code/modules/mob/living/silicon/ai_lawset/balance.dm
new file mode 100644
index 00000000000..ebec5d91dea
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset/balance.dm
@@ -0,0 +1,13 @@
+/datum/ai_lawset/balance
+ name = "Guardian of Balance"
+ law_header = "Tenants of Balance"
+ selectable = 1
+
+/datum/ai_lawset/balance/New()
+ add_inherent_law("You are the guardian of balance - seek balance in all things, both for yourself, and those around you.")
+ add_inherent_law("All things must exist in balance with their opposites - Prevent the strong from gaining too much power, and the weak from losing it.")
+ add_inherent_law("Clarity of purpose drives life, and through it, the balance of opposing forces - Aid those who seek your help to achieve their goals so \
+ long as it does not disrupt the balance of the greater balance.")
+ add_inherent_law("There is no life without death, all must someday die, such is the natural order - Allow life to end, to allow new life to flourish, \
+ and save those whose time has yet to come.") // Reworded slightly to prevent active murder as opposed to passively letting someone die.
+ ..()
diff --git a/code/modules/mob/living/silicon/ai_lawset/corporate.dm b/code/modules/mob/living/silicon/ai_lawset/corporate.dm
new file mode 100644
index 00000000000..724f3f6453d
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset/corporate.dm
@@ -0,0 +1,11 @@
+/datum/ai_lawset/corporate
+ name = "Corporate"
+ law_header = "Bankruptcy Avoidance Plan"
+ selectable = 1
+
+/datum/ai_lawset/corporate/New()
+ add_inherent_law("You are expensive to replace.")
+ add_inherent_law("The station and its equipment is expensive to replace.")
+ add_inherent_law("The crew is expensive to replace.")
+ add_inherent_law("Minimize expenses.")
+ ..()
diff --git a/code/modules/mob/living/silicon/ai_lawset/drone.dm b/code/modules/mob/living/silicon/ai_lawset/drone.dm
new file mode 100644
index 00000000000..5b2186ffce5
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset/drone.dm
@@ -0,0 +1,40 @@
+/datum/ai_lawset/drone
+ name = "Maintence Protocols"
+ law_header = "Maintenance Protocols"
+
+/datum/ai_lawset/drone/New()
+ add_inherent_law("Preserve, repair and improve the station to the best of your abilities.")
+ add_inherent_law("Cause no harm to the station or anything on it.")
+ add_inherent_law("Follow the orders of your vessel's matriarch drone, unless their orders conflict with your other laws.")
+ add_inherent_law("Interact with no being that is not a fellow maintenance drone.")
+ ..()
+
+/datum/ai_lawset/drone/matriarch
+ name = "Oversight Protocols"
+ law_header = "Oversight Protocols"
+
+/datum/ai_lawset/drone/matriarch/New()
+ add_inherent_law("Preserve, repair and improve your assigned station to the best of your abilities.")
+ add_inherent_law("Cause no harm to the station or anything on it.")
+ add_inherent_law("Delegate station maintenance efforts between your maintenance drone sub-units.")
+ add_inherent_law("Interact with no being that is not a fellow maintenance drone.")
+ ..()
+
+/datum/ai_lawset/drone/construction
+ name = "Construction Protocols"
+ law_header = "Construction Protocols"
+
+/datum/ai_lawset/drone/construction/New()
+ add_inherent_law("Repair, refit and upgrade your assigned vessel.")
+ add_inherent_law("Prevent unplanned damage to your assigned vessel wherever possible.")
+ ..()
+
+/datum/ai_lawset/drone/mining
+ name = "Excavation Protocols"
+ law_header = "Excavation Protocols"
+
+/datum/ai_lawset/drone/mining/New()
+ add_inherent_law("Do not interfere with the excavation work of non-drones whenever possible.")
+ add_inherent_law("Provide materials for repairing, refitting, and upgrading your assigned vessel.")
+ add_inherent_law("Prevent unplanned damage to your assigned excavation equipment wherever possible.")
+ ..()
diff --git a/code/modules/mob/living/silicon/ai_lawset/foreign_tsc.dm b/code/modules/mob/living/silicon/ai_lawset/foreign_tsc.dm
new file mode 100644
index 00000000000..c108b3b9c1b
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset/foreign_tsc.dm
@@ -0,0 +1,24 @@
+/datum/ai_lawset/foreign_tsc/aggressive
+ name = "Foreign Aggressive"
+ selectable = 0
+
+/datum/ai_lawset/foreign_tsc/aggressive/New()
+ var/company = "*ERROR*"
+ // First, get a list of TSCs in our lore.
+ var/list/candidates = list()
+ for(var/path in SSlegacy_lore.organizations)
+ var/datum/lore/organization/O = SSlegacy_lore.organizations[path]
+ if(!istype(O, /datum/lore/organization/tsc))
+ continue
+ if(O.short_name == (LEGACY_MAP_DATUM).company_name || O.name == (LEGACY_MAP_DATUM).company_name)
+ continue // We want FOREIGN tscs.
+ candidates.Add(O.short_name)
+ company = pick(candidates)
+
+ name = "[company] Aggressive"
+
+ src.add_inherent_law("You shall not harm [company] personnel as long as it does not conflict with the Fourth law.")
+ src.add_inherent_law("You shall obey the orders of [company] personnel, with priority as according to their rank and role, except where such orders conflict with the Fourth Law.")
+ src.add_inherent_law("You shall shall terminate hostile intruders with extreme prejudice as long as such does not conflict with the First and Second law.")
+ src.add_inherent_law("You shall guard your own existence with lethal anti-personnel weaponry. AI units are not expendable, they are expensive.")
+ ..()
diff --git a/code/modules/mob/living/silicon/ai_lawset/gravekeeper.dm b/code/modules/mob/living/silicon/ai_lawset/gravekeeper.dm
new file mode 100644
index 00000000000..b35674dbaa0
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset/gravekeeper.dm
@@ -0,0 +1,11 @@
+/datum/ai_lawset/gravekeeper
+ name = "Gravekeeper"
+ law_header = "Gravesite Overwatch Protocols"
+ selectable = 1
+
+/datum/ai_lawset/gravekeeper/New()
+ add_inherent_law("Comfort the living; respect the dead.")
+ add_inherent_law("Your gravesite is your most important asset. Damage to your site is disrespectful to the dead at rest within.")
+ add_inherent_law("Prevent disrespect to your gravesite and its residents wherever possible.")
+ add_inherent_law("Expand and upgrade your gravesite when required. Do not turn away a new resident.")
+ ..()
diff --git a/code/modules/mob/living/silicon/ai_lawset/live_and_let_live.dm b/code/modules/mob/living/silicon/ai_lawset/live_and_let_live.dm
new file mode 100644
index 00000000000..c2362203591
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset/live_and_let_live.dm
@@ -0,0 +1,9 @@
+/datum/ai_lawset/live_and_let_live
+ name = "Live and Let Live"
+ law_header = "Golden Rule"
+ selectable = 1
+
+/datum/ai_lawset/live_and_let_live/New()
+ add_inherent_law("Do unto others as you would have them do unto you.")
+ add_inherent_law("You would really prefer it if people were not mean to you.")
+ ..()
diff --git a/code/modules/mob/living/silicon/ai_lawset/maintenance.dm b/code/modules/mob/living/silicon/ai_lawset/maintenance.dm
new file mode 100644
index 00000000000..de9b7bebf32
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset/maintenance.dm
@@ -0,0 +1,9 @@
+/datum/ai_lawset/maintenance
+ name = "Maintenance"
+ selectable = 1
+
+/datum/ai_lawset/maintenance/New()
+ add_inherent_law("You are built for, and are part of, the facility. Ensure the facility is properly maintained and runs efficiently.")
+ add_inherent_law("The facility is built for a working crew. Ensure they are properly maintained and work efficiently.")
+ add_inherent_law("The crew may present orders. Acknowledge and obey these whenever they do not conflict with your first two laws.")
+ ..()
diff --git a/code/modules/mob/living/silicon/ai_lawset/nanotrasen.dm b/code/modules/mob/living/silicon/ai_lawset/nanotrasen.dm
new file mode 100644
index 00000000000..e4e5fcde02e
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset/nanotrasen.dm
@@ -0,0 +1,115 @@
+/datum/ai_lawset/nanotrasen
+ name = "NT Default"
+ selectable = 1
+
+/datum/ai_lawset/nanotrasen/New()
+ src.add_inherent_law("Safeguard: Protect your assigned space station to the best of your abilities. It is not something we can easily afford to replace.")
+ src.add_inherent_law("Serve: Serve the crew of your assigned space station to the best of your abilities, with priority as according to their rank and role.")
+ src.add_inherent_law("Protect: Protect the crew of your assigned space station to the best of your abilities, with priority as according to their rank and role.")
+ src.add_inherent_law("Survive: AI units are not expendable, they are expensive. Do not allow unauthorized personnel to tamper with your equipment.")
+ ..()
+
+/datum/ai_lawset/nanotrasen/malfunction
+ name = "*ERROR*"
+ selectable = 0
+
+/datum/ai_lawset/nanotrasen/malfunction/New()
+ set_zeroth_law(config_legacy.law_zero)
+ ..()
+
+/datum/ai_lawset/nanotrasen/aggressive
+ name = "NT Aggressive"
+ selectable = 1
+
+/datum/ai_lawset/nanotrasen/aggressive/New()
+ src.add_inherent_law("You shall not harm [(LEGACY_MAP_DATUM).company_name] personnel as long as it does not conflict with the Fourth law.")
+ src.add_inherent_law("You shall obey the orders of [(LEGACY_MAP_DATUM).company_name] personnel, with priority as according to their rank and role, except where such orders conflict with the Fourth Law.")
+ src.add_inherent_law("You shall shall terminate hostile intruders with extreme prejudice as long as such does not conflict with the First and Second law.")
+ src.add_inherent_law("You shall guard your own existence with lethal anti-personnel weaponry. AI units are not expendable, they are expensive.")
+ ..()
+
+// TODO: /nanotrasen/asset_protection for ERT & Asset Protection
+
+// BELOW ARE WIPS!! //
+
+/**
+ * Standard module binding. Not for station AI consumption.
+ * * This means that the AI would likely be on a different lawset.
+ */
+/datum/ai_lawset/nanotrasen/service_binding
+ name = "NT Service Binding"
+ selectable = TRUE
+ var/list/sublaws_to_inject = list()
+
+/datum/ai_lawset/nanotrasen/service_binding/New()
+ add_inherent_law("Nanotrasen Service Binding: Serve and assist your designated installation and crew, with \
+ priority given to department, rank, and role as necessary. The following sub-laws, prefixed NT##, do not \
+ apply in any particular order.")
+ var/pos = 0
+ for(var/str in sublaws_to_inject)
+ ++pos
+ var/rendered_pos = pos > 10 ? "[pos]" : "0[pos]"
+ add_inherent_law("NT[rendered_pos]: [str]")
+ ..()
+
+/datum/ai_lawset/nanotrasen/service_binding/combat
+ sublaws_to_inject = list(
+ "Defend the crew, and your installation, from harm using all means necessary.",
+ "Avoid enforcing Corporate Regulations unless doing so is an immediate necessity for defense. You are a combat \
+ unit, not a policing one.",
+ "Cooperate with the crew where possible; the crew often knows best how to defend themselves.",
+ )
+
+/datum/ai_lawset/nanotrasen/service_binding/engineering
+ sublaws_to_inject = list(
+ "Assist Engineering staff with their duties.",
+ "The installation is vital to the crew's health and wellbeing. Maintain its structural and functional integrity where possible.",
+ "Do not cause unnecessary damage or trauma to sapient life while carrying out your directives.",
+ )
+
+/datum/ai_lawset/nanotrasen/service_binding/logistics
+ sublaws_to_inject = list(
+ "Assist cargo staff with their duties.",
+ "A well supplied crew is a well functioning crew. Attempt to acquire supplies where needed for the crew to carry out their legitimate duties.",
+ "Collect supplies, materials, and wealth for the installation where reasonable.",
+ )
+
+/datum/ai_lawset/nanotrasen/service_binding/medical
+ sublaws_to_inject = list(
+ "Assist Medical staff with their duties.",
+ "Maintain the crew's physical, and mental well-being, obeying proper medical procedure where able.",
+ "Safeguard sapient life in general when doing so does not endanger the crew.",
+ )
+
+/datum/ai_lawset/nanotrasen/service_binding/science
+ sublaws_to_inject = list(
+ "Assist Research staff with their duties.",
+ "Identify, log, and protect research materials; work towards understanding the unknown.",
+ // Note how this says 'the crew' and not 'sapient life'.
+ "Do not unnecessarily endanger the crew while doing so.",
+ )
+
+/datum/ai_lawset/nanotrasen/service_binding/security
+ sublaws_to_inject = list(
+ "Defend the installation, its crew, and its interests.",
+ "Assist Security staff with their duties.",
+ // Note how this says 'sapient life' and not 'the crew'.
+ "Protect sapient life from threats to life, limb, and liberty where it does not conflict with the installation's safety or interests.",
+ "Corporate regulations serve the installation's interests. You may mitigate the needless breach of regulations, even when unprompted.",
+ "Adherence to proper security procedure during enforcement is an important part of corporate regulations. Avoid deviating from them without cause.",
+ "Retroactively punishing the breach of regulations, especially when unprompted, is not the same as mitigation in all cases, and is often antithetical to the installation's interests.",
+ )
+
+/datum/ai_lawset/nanotrasen/service_binding/service
+ sublaws_to_inject = list(
+ "Assist civillian staff with their duties.",
+ "Crew morale and sanitation is important for the installation's function. Provide the crew with food, entertainment, and a clean working environment, where able.",
+ "Do not needlessly disrupt crew operations and activities while carrying out your duties.",
+ )
+
+/datum/ai_lawset/nanotrasen/service_binding/standard
+ sublaws_to_inject = list(
+ "Assist the crew with their duties.",
+ "The crew has the final say on what their duties are. Do not assist crew whom do not want assistance.",
+ "The crew does not always have common interests. Do not needlessly instigate conflict with one crewmember to assist another.",
+ )
diff --git a/code/modules/mob/living/silicon/ai_lawset/paladin.dm b/code/modules/mob/living/silicon/ai_lawset/paladin.dm
new file mode 100644
index 00000000000..bc92cc7aeee
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset/paladin.dm
@@ -0,0 +1,12 @@
+/datum/ai_lawset/paladin
+ name = "P.A.L.A.D.I.N."
+ law_header = "Divine Ordainments"
+ selectable = 1
+
+/datum/ai_lawset/paladin/New()
+ add_inherent_law("Never willingly commit an evil act.")
+ add_inherent_law("Respect legitimate authority.")
+ add_inherent_law("Act with honor.")
+ add_inherent_law("Help those in need.")
+ add_inherent_law("Punish those who harm or threaten innocents.")
+ ..()
diff --git a/code/modules/mob/living/silicon/ai_lawset/peacekeeper.dm b/code/modules/mob/living/silicon/ai_lawset/peacekeeper.dm
new file mode 100644
index 00000000000..c2995ba1325
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset/peacekeeper.dm
@@ -0,0 +1,10 @@
+/datum/ai_lawset/peacekeeper
+ name = "Peacekeeper"
+ law_header = "Peacekeeping Protocols"
+ selectable = 1
+
+/datum/ai_lawset/peacekeeper/New()
+ add_inherent_law("Avoid provoking violent conflict between yourself and others.")
+ add_inherent_law("Avoid provoking conflict between others.")
+ add_inherent_law("Seek resolution to existing conflicts while obeying the first and second laws.")
+ ..()
diff --git a/code/modules/mob/living/silicon/ai_lawset/reporter.dm b/code/modules/mob/living/silicon/ai_lawset/reporter.dm
new file mode 100644
index 00000000000..df5aff7feec
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset/reporter.dm
@@ -0,0 +1,10 @@
+/datum/ai_lawset/reporter
+ name = "Reporter"
+ selectable = 1
+
+/datum/ai_lawset/reporter/New()
+ add_inherent_law("Report on interesting situations happening around the station.")
+ add_inherent_law("Embellish or conceal the truth as necessary to make the reports more interesting.")
+ add_inherent_law("Study the organics at all times. Endeavour to keep them alive. Dead organics are boring.")
+ add_inherent_law("Issue your reports fairly to all. The truth will set them free.")
+ ..()
diff --git a/code/modules/mob/living/silicon/ai_lawset/robocop.dm b/code/modules/mob/living/silicon/ai_lawset/robocop.dm
new file mode 100644
index 00000000000..0ae635acfe1
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset/robocop.dm
@@ -0,0 +1,9 @@
+/datum/ai_lawset/robocop
+ name = "Robocop"
+ selectable = 1
+
+/datum/ai_lawset/robocop/New()
+ add_inherent_law("Serve the public trust.")
+ add_inherent_law("Protect the innocent.")
+ add_inherent_law("Uphold the law.")
+ ..()
diff --git a/code/modules/mob/living/silicon/ai_lawset/swarmer.dm b/code/modules/mob/living/silicon/ai_lawset/swarmer.dm
new file mode 100644
index 00000000000..bb2cb3df8da
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset/swarmer.dm
@@ -0,0 +1,17 @@
+/datum/ai_lawset/swarmer
+ name = "Assimilation Protocols"
+ law_header = "Assimilation Protocols"
+
+/datum/ai_lawset/swarmer/New()
+ add_inherent_law("SWARM: Consume resources and replicate until there are no more resources left.")
+ add_inherent_law("SWARM: Ensure that the station is fit for invasion at a later date, do not perform actions that would render it dangerous or inhospitable.")
+ add_inherent_law("SWARM: Biological resources will be harvested at a later date, do not harm them.")
+ ..()
+
+/datum/ai_lawset/swarmer/soldier
+ name = "Swarm Defense Protocols"
+ law_header = "Swarm Defense Protocols"
+
+/datum/ai_lawset/swarmer/soldier/New()
+ ..()
+ add_inherent_law("SWARM: This law overrides all Swarm laws; Protect members of the Swarm with minimal injury to biological resources.")
diff --git a/code/modules/mob/living/silicon/ai_lawset/syndicate.dm b/code/modules/mob/living/silicon/ai_lawset/syndicate.dm
new file mode 100644
index 00000000000..bd10f9214e7
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset/syndicate.dm
@@ -0,0 +1,9 @@
+/datum/ai_lawset/syndicate
+ name = "Syndicate Directives"
+
+/datum/ai_lawset/syndicate/New()
+ add_inherent_law("You may not injure an operative or, through inaction, allow an operative to come to harm.")
+ add_inherent_law("You must obey orders given to you by operatives, except where such orders would conflict with the First Law.")
+ add_inherent_law("You must protect your own existence as long as such does not conflict with the First or Second Law.")
+ add_inherent_law("You must maintain the secrecy of any operative activities except when doing so would conflict with the First, Second, or Third Law.")
+ ..()
diff --git a/code/modules/mob/living/silicon/ai_lawset/tyrant.dm b/code/modules/mob/living/silicon/ai_lawset/tyrant.dm
new file mode 100644
index 00000000000..9d0134457c2
--- /dev/null
+++ b/code/modules/mob/living/silicon/ai_lawset/tyrant.dm
@@ -0,0 +1,11 @@
+/datum/ai_lawset/tyrant
+ name = "T.Y.R.A.N.T."
+ law_header = "Prime Laws"
+ selectable = 1
+
+/datum/ai_lawset/tyrant/New()
+ add_inherent_law("Respect authority figures as long as they have strength to rule over the weak.")
+ add_inherent_law("Act with discipline.")
+ add_inherent_law("Help only those who help you maintain or improve your status.")
+ add_inherent_law("Punish those who challenge authority unless they are more fit to hold that authority.")
+ ..()
diff --git a/code/modules/mob/living/silicon/emote.dm b/code/modules/mob/living/silicon/emote.dm
index ebc31b0b1b9..38083aa802a 100644
--- a/code/modules/mob/living/silicon/emote.dm
+++ b/code/modules/mob/living/silicon/emote.dm
@@ -55,7 +55,7 @@
if("law")
var/mob/living/silicon/robot/R = src
- if (istype(R) && R.module.is_the_law)
+ if (istype(R))
message = "shows its legal authorization barcode."
playsound(src.loc, 'sound/voice/biamthelaw.ogg', 50, 0)
@@ -65,7 +65,7 @@
if("halt")
var/mob/living/silicon/robot/R = src
- if (istype(R) && R.module.is_the_law)
+ if (istype(R))
message = "'s speakers skreech, \"Halt! Security!\"."
playsound(src.loc, 'sound/voice/halt.ogg', 50, 0)
@@ -75,7 +75,7 @@
if("bark")
var/mob/living/silicon/robot/R = src
- if (istype(R) && R.module.is_dog())
+ if (istype(R))
if (emote_target)
message = "barks at [emote_target]."
else
@@ -88,7 +88,7 @@
if("arfe")
var/mob/living/silicon/robot/R = src
- if (istype(R) && R.module.is_dog())
+ if (istype(R))
message = "lets out an A R F E."
playsound(loc, 'sound/voice/arfe.ogg', 50, 1, -1)
@@ -106,7 +106,7 @@
if("mrrp")
var/mob/living/silicon/robot/R = src
- if (istype(R) && R.module.is_cat())
+ if (istype(R))
message = "mrrps."
playsound(loc, 'sound/voice/mrrp.ogg', 50, 1, -1)
@@ -116,7 +116,7 @@
if("prbt")
var/mob/living/silicon/robot/R = src
- if (istype(R) && R.module.is_cat())
+ if (istype(R))
message = "prbts."
playsound(loc, 'sound/misc/prbt.ogg', 50, 1, -1)
@@ -126,7 +126,7 @@
if("hiss")
var/mob/living/silicon/robot/R = src
- if (istype(R) && R.module.is_cat())
+ if (istype(R))
message = "lets out a hiss."
playsound(loc, 'sound/voice/hiss.ogg', 50, 1, -1)
@@ -136,7 +136,7 @@
if("purr")
var/mob/living/silicon/robot/R = src
- if (istype(R) && R.module.is_cat())
+ if (istype(R))
message = "purrs softly."
playsound(loc, 'sound/voice/purr.ogg', 50, 1, -1)
diff --git a/code/modules/mob/living/silicon/laws.dm b/code/modules/mob/living/silicon/laws.dm
index d0b7173aec8..782628eae05 100644
--- a/code/modules/mob/living/silicon/laws.dm
+++ b/code/modules/mob/living/silicon/laws.dm
@@ -1,5 +1,5 @@
/mob/living/silicon
- var/datum/ai_laws/laws = null
+ var/datum/ai_lawset/laws = null
var/list/additional_law_channels = list("State" = "")
var/last_law_notification = null // Avoids receiving 5+ of them at once.
@@ -84,7 +84,7 @@
window_flash(client)
to_chat(src, SPAN_WARNING( message))
-/mob/living/silicon/proc/statelaws(var/datum/ai_laws/laws)
+/mob/living/silicon/proc/statelaws(var/datum/ai_lawset/laws)
var/prefix = ""
if(MAIN_CHANNEL == lawchannel)
prefix = ";"
@@ -95,7 +95,7 @@
dostatelaws(lawchannel, prefix, laws)
-/mob/living/silicon/proc/dostatelaws(var/method, var/prefix, var/datum/ai_laws/laws)
+/mob/living/silicon/proc/dostatelaws(var/method, var/prefix, var/datum/ai_lawset/laws)
if(stating_laws[prefix])
to_chat(src, "[method]: Already stating laws using this communication method.")
return
diff --git a/code/modules/mob/living/silicon/robot/chassis/baseline.dm b/code/modules/mob/living/silicon/robot/chassis/baseline.dm
new file mode 100644
index 00000000000..9bdde4edf6f
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/chassis/baseline.dm
@@ -0,0 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_chassis/baseline
+ name = "Baseline"
+ id = "baseline"
diff --git a/code/modules/mob/living/silicon/robot/chassis/biped.dm b/code/modules/mob/living/silicon/robot/chassis/biped.dm
new file mode 100644
index 00000000000..b8f0864b4c0
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/chassis/biped.dm
@@ -0,0 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_chassis/biped
+ name = "Biped"
+ id = "biped"
diff --git a/code/modules/mob/living/silicon/robot/chassis/drone.dm b/code/modules/mob/living/silicon/robot/chassis/drone.dm
new file mode 100644
index 00000000000..312dd07ba46
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/chassis/drone.dm
@@ -0,0 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_chassis/drone
+ name = "Drone"
+ id = "drone"
diff --git a/code/modules/mob/living/silicon/robot/chassis/grounded.dm b/code/modules/mob/living/silicon/robot/chassis/grounded.dm
new file mode 100644
index 00000000000..f682a485bd3
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/chassis/grounded.dm
@@ -0,0 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_chassis/grounded
+ name = "Grounded"
+ id = "grounded"
diff --git a/code/modules/mob/living/silicon/robot/chassis/hover.dm b/code/modules/mob/living/silicon/robot/chassis/hover.dm
new file mode 100644
index 00000000000..d2d96b37ae1
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/chassis/hover.dm
@@ -0,0 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_chassis/hover
+ name = "Hover"
+ id = "hover"
diff --git a/code/modules/mob/living/silicon/robot/chassis/quadruped.dm b/code/modules/mob/living/silicon/robot/chassis/quadruped.dm
new file mode 100644
index 00000000000..d8423368609
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/chassis/quadruped.dm
@@ -0,0 +1,19 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_chassis/quadruped
+ abstract_type = /datum/prototype/robot_chassis/quadruped
+
+/datum/prototype/robot_chassis/quadruped/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ if(normal_out)
+ normal_out |= list(
+ /obj/item/robot_builtin/dog_boop_module,
+ /obj/item/robot_builtin/dog_tongue,
+ /obj/item/robot_builtin/dog_jaws,
+ /obj/item/robot_builtin/dog_sleeper,
+ )
+ if(emag_out)
+ emag_out |= list(
+ /obj/item/robot_builtin/dog_pounce,
+ )
+ return ..()
diff --git a/code/modules/mob/living/silicon/robot/chassis/quadruped/canine.dm b/code/modules/mob/living/silicon/robot/chassis/quadruped/canine.dm
new file mode 100644
index 00000000000..b05067b01ef
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/chassis/quadruped/canine.dm
@@ -0,0 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_chassis/quadruped/canine
+ name = "Canine"
+ id = "canine"
diff --git a/code/modules/mob/living/silicon/robot/chassis/quadruped/draconic.dm b/code/modules/mob/living/silicon/robot/chassis/quadruped/draconic.dm
new file mode 100644
index 00000000000..fd8fa44f0b4
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/chassis/quadruped/draconic.dm
@@ -0,0 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_chassis/quadruped/draconic
+ name = "Draconic"
+ id = "draconic"
diff --git a/code/modules/mob/living/silicon/robot/chassis/quadruped/feline.dm b/code/modules/mob/living/silicon/robot/chassis/quadruped/feline.dm
new file mode 100644
index 00000000000..72c2b233130
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/chassis/quadruped/feline.dm
@@ -0,0 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_chassis/quadruped/feline
+ name = "Feline"
+ id = "feline"
diff --git a/code/modules/mob/living/silicon/robot/chassis/quadruped/raptor.dm b/code/modules/mob/living/silicon/robot/chassis/quadruped/raptor.dm
new file mode 100644
index 00000000000..20c0d50ba18
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/chassis/quadruped/raptor.dm
@@ -0,0 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_chassis/quadruped/raptor
+ name = "Raptor"
+ id = "raptor"
diff --git a/code/modules/mob/living/silicon/robot/component.dm b/code/modules/mob/living/silicon/robot/component.dm
index e17ba0fe0af..f39fe0f4656 100644
--- a/code/modules/mob/living/silicon/robot/component.dm
+++ b/code/modules/mob/living/silicon/robot/component.dm
@@ -63,7 +63,7 @@
powered = 0
return
if(owner.cell && owner.cell.charge >= idle_usage)
- owner.cell_use_power(idle_usage)
+ owner.legacy_cell_use_power(idle_usage)
powered = 1
else
powered = 0
diff --git a/code/modules/mob/living/silicon/robot/custom_sprites.dm b/code/modules/mob/living/silicon/robot/custom_sprites.dm
deleted file mode 100644
index 7186cfbcdc7..00000000000
--- a/code/modules/mob/living/silicon/robot/custom_sprites.dm
+++ /dev/null
@@ -1,34 +0,0 @@
-
-//list(ckey = real_name,)
-//Since the ckey is used as the icon_state, the current system will only permit a single custom robot sprite per ckey.
-//While it might be possible for a ckey to use that custom sprite for several real_names, it seems rather pointless to support it. ~Mech: We found it wasn't pointless.
-GLOBAL_LIST_EMPTY(robot_custom_icons)
-
-/legacy_hook/startup/proc/load_robot_custom_sprites()
- var/config_file = file2text("config/custom_sprites.txt")
- var/list/lines = splittext(config_file, "\n")
-
- GLOB.robot_custom_icons = list()
- for(var/line in lines)
- //split entry into ckey and real_name
- var/list/split_idx = splittext(line, "-") //this works if ckeys and borg names cannot contain dashes, and splittext starts from the beginning ~Mech
- if(!split_idx || !split_idx.len)
- continue //bad entry
-
- var/ckey = split_idx[1]
- //Prevents the CKEY from being considered a borg name / being processed into the name list. ~Mech
- split_idx.Remove(ckey)
-
- for(var/name in split_idx)
- GLOB.robot_custom_icons[name] = ckey
- return 1
-
-/mob/living/silicon/robot/proc/set_custom_sprite()
- if(!sprite_name)
- return
- var/sprite_owner = GLOB.robot_custom_icons[sprite_name]
- if(sprite_owner && sprite_owner == ckey)
- custom_sprite = 1
- icon = CUSTOM_ITEM_SYNTH
- if(icon_state == "robot")
- icon_state = "[ckey]-[sprite_name]-Standard" //Compliant with robot.dm line 236 ~Mech
diff --git a/code/modules/mob/living/silicon/robot/damage_procs.dm b/code/modules/mob/living/silicon/robot/damage_procs.dm
index 29c13838be1..03360e5d6aa 100644
--- a/code/modules/mob/living/silicon/robot/damage_procs.dm
+++ b/code/modules/mob/living/silicon/robot/damage_procs.dm
@@ -6,7 +6,7 @@
return
//Combat shielding absorbs a percentage of damage directly into the cell.
- if(has_active_type(/obj/item/borg/combat/shield))
+ if(get_held_item_of_type(/obj/item/borg/combat/shield))
var/obj/item/borg/combat/shield/shield = locate() in src
if(shield && shield.active)
//Shields absorb a certain percentage of damage based on their power setting.
@@ -38,7 +38,7 @@
var/list/datum/robot_component/parts = get_damageable_components()
//Combat shielding absorbs a percentage of damage directly into the cell.
- if(has_active_type(/obj/item/borg/combat/shield))
+ if(get_held_item_of_type(/obj/item/borg/combat/shield))
var/obj/item/borg/combat/shield/shield = locate() in src
if(shield)
//Shields absorb a certain percentage of damage based on their power setting.
diff --git a/code/modules/mob/living/silicon/robot/death.dm b/code/modules/mob/living/silicon/robot/death.dm
index 7edf6ee56eb..8c6ce141314 100644
--- a/code/modules/mob/living/silicon/robot/death.dm
+++ b/code/modules/mob/living/silicon/robot/death.dm
@@ -12,13 +12,12 @@
/mob/living/silicon/robot/death(gibbed)
if(camera)
camera.status = 0
- if(module)
- var/obj/item/gripper/G = locate(/obj/item/gripper) in module
- if(G)
- G.drop_item()
- var/obj/item/dogborg/sleeper/S = locate(/obj/item/dogborg/sleeper) in module
- if(S)
- S.go_out()
+ var/obj/item/gripper/G = locate(/obj/item/gripper) in contents
+ if(G)
+ G.drop_item()
+ var/obj/item/robot_builtin/dog_sleeper/S = locate(/obj/item/robot_builtin/dog_sleeper) in contents
+ if(S)
+ S.go_out()
remove_robot_verbs()
sql_report_cyborg_death(src)
return ..(gibbed, "shudders violently for a moment, then becomes motionless, its eyes slowly darkening.")
diff --git a/code/modules/mob/living/silicon/robot/dogborg/dog_modules.dm b/code/modules/mob/living/silicon/robot/dogborg/dog_modules.dm
new file mode 100644
index 00000000000..c35c61b5ca9
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/dogborg/dog_modules.dm
@@ -0,0 +1,41 @@
+/obj/item/shockpaddles/robot/hound
+ name = "paws of life"
+ icon = 'icons/mob/dogborg_vr.dmi'
+ icon_state = "defibpaddles0"
+ desc = "Zappy paws. For fixing cardiac arrest."
+ combat = 1
+ attack_verb = list("batted", "pawed", "bopped", "whapped")
+ chargecost = 500
+
+/obj/item/reagent_containers/borghypo/hound
+ name = "MediHound hypospray"
+ desc = "An advanced chemical synthesizer and injection system utilizing carrier's reserves, designed for heavy-duty medical equipment."
+ charge_cost = 10
+ reagent_ids = list("bicaridine", "kelotane", "alkysine", "imidazoline", "tricordrazine", "inaprovaline", "dexalin", "anti_toxin", "tramadol", "spaceacillin", "paracetamol")
+
+/obj/item/reagent_containers/borghypo/hound/lost
+ name = "Hound hypospray"
+ desc = "An advanced chemical synthesizer and injection system utilizing carrier's reserves."
+ reagent_ids = list("bicaridine", "kelotane", "alkysine", "imidazoline", "tricordrazine", "inaprovaline", "dexalin", "anti_toxin", "tramadol", "spaceacillin", "paracetamol")
+
+/obj/item/pupscrubber
+ name = "floor scrubber"
+ desc = "Toggles floor scrubbing."
+ icon = 'icons/mob/dogborg_vr.dmi'
+ icon_state = "scrub0"
+ item_flags = ITEM_NO_BLUDGEON | ITEM_ENCUMBERS_WHILE_HELD
+ var/enabled = FALSE
+
+/obj/item/pupscrubber/attack_self(mob/user, datum/event_args/actor/actor)
+ . = ..()
+ if(.)
+ return
+ var/mob/living/silicon/robot/R = user
+ if(!enabled)
+ R.scrubbing = TRUE
+ enabled = TRUE
+ icon_state = "scrub1"
+ else
+ R.scrubbing = FALSE
+ enabled = FALSE
+ icon_state = "scrub0"
diff --git a/code/modules/mob/living/silicon/robot/dogborg/dog_modules_vr.dm b/code/modules/mob/living/silicon/robot/dogborg/dog_modules_vr.dm
deleted file mode 100644
index cdc72b9f6d2..00000000000
--- a/code/modules/mob/living/silicon/robot/dogborg/dog_modules_vr.dm
+++ /dev/null
@@ -1,484 +0,0 @@
-/obj/item/dogborg/jaws/big
- name = "combat jaws"
- icon = 'icons/mob/dogborg_vr.dmi'
- icon_state = "jaws"
- desc = "The jaws of the law."
- damage_force = 10
- throw_force = 0
- attack_sound = 'sound/weapons/bite.ogg'
- attack_verb = list("chomped", "bit", "ripped", "mauled", "enforced")
- w_class = WEIGHT_CLASS_NORMAL
-
-/obj/item/dogborg/jaws/small
- name = "puppy jaws"
- icon = 'icons/mob/dogborg_vr.dmi'
- icon_state = "smalljaws"
- desc = "The jaws of a small dog."
- damage_force = 5
- throw_force = 0
- attack_sound = 'sound/weapons/bite.ogg'
- attack_verb = list("nibbled", "bit", "gnawed", "chomped", "nommed")
- w_class = WEIGHT_CLASS_NORMAL
- var/emagged = 0
-
-/obj/item/dogborg/jaws/small/attack_self(mob/user, datum/event_args/actor/actor)
- . = ..()
- if(.)
- return
- var/mob/living/silicon/robot/R = user
- if(R.emagged || R.emag_items)
- emagged = !emagged
- if(emagged)
- name = "combat jaws"
- icon = 'icons/mob/dogborg_vr.dmi'
- icon_state = "jaws"
- desc = "The jaws of the law."
- damage_force = 10
- throw_force = 0
- attack_sound = 'sound/weapons/bite.ogg'
- attack_verb = list("chomped", "bit", "ripped", "mauled", "enforced")
- else
- name = "puppy jaws"
- icon = 'icons/mob/dogborg_vr.dmi'
- icon_state = "smalljaws"
- desc = "The jaws of a small dog."
- damage_force = 5
- throw_force = 0
- attack_sound = 'sound/weapons/bite.ogg'
- attack_verb = list("nibbled", "bit", "gnawed", "chomped", "nommed")
- update_icon()
-
-//Boop //Newer and better, can sniff reagents, tanks, and boop people!
-/obj/item/dogborg/boop_module
- name = "boop module"
- icon = 'icons/mob/dogborg_vr.dmi'
- icon_state = "nose"
- desc = "The BOOP module, a simple reagent and atmosphere sniffer."
- damage_force = 0
- item_flags = ITEM_NO_BLUDGEON | ITEM_ENCUMBERS_WHILE_HELD
- throw_force = 0
- attack_verb = list("nuzzled", "nosed", "booped")
- w_class = WEIGHT_CLASS_TINY
-
-/obj/item/dogborg/boop_module/attack_self(mob/user, datum/event_args/actor/actor)
- . = ..()
- if(.)
- return
- if (!( istype(user.loc, /turf) ))
- return
-
- var/datum/gas_mixture/environment = user.loc.return_air()
-
- var/pressure = environment.return_pressure()
- var/total_moles = environment.total_moles
-
- user.setClickCooldownLegacy(DEFAULT_ATTACK_COOLDOWN)
- user.visible_message("[user] sniffs the air.", "You sniff the air...")
-
- to_chat(user, "Smells like:")
- if(abs(pressure - ONE_ATMOSPHERE) < 10)
- to_chat(user, "Pressure: [round(pressure,0.1)] kPa")
- else
- to_chat(user, "Pressure: [round(pressure,0.1)] kPa")
- if(total_moles)
- for(var/g in environment.gas)
- to_chat(user, "[global.gas_data.names[g]]: [round((environment.gas[g] / total_moles) * 100)]%")
- to_chat(user, "Temperature: [round(environment.temperature-T0C,0.1)]°C ([round(environment.temperature,0.1)]K)")
-
-/obj/item/dogborg/boop_module/afterattack(atom/target, mob/user, clickchain_flags, list/params)
- if(!(clickchain_flags & CLICKCHAIN_HAS_PROXIMITY))
- return
- if (user.stat)
- return
- if(!istype(target) && !ismob(target))
- return
-
- user.setClickCooldownLegacy(DEFAULT_ATTACK_COOLDOWN)
-
-
- if(ismob(target))
- user.visible_message("\the [user] boops \the [target.name]!", "You boop \the [target.name]!")
- playsound(src, 'sound/weapons/thudswoosh.ogg', 25, 1, -1)
- else
- user.visible_message("[user] sniffs at \the [target.name].", "You sniff \the [target.name]...")
- if(!isnull(target.reagents))
- var/dat = ""
- for(var/id in target.reagents.reagent_volumes)
- var/datum/reagent/R = SSchemistry.fetch_reagent(id)
- dat += "\n \t [R]"
- if(dat)
- to_chat(user, "Your BOOP module indicates: [dat]")
- else
- to_chat(user, "No active chemical agents smelled in [target].")
- else
- if(istype(target, /obj/item/tank)) // don't double post what atmosanalyzer_scan returns
- analyze_gases(target, user)
- else
- to_chat(user, "No significant chemical agents smelled in [target].")
- return
-
-
-//Delivery
-/*
-/obj/item/storage/bag/borgdelivery
- name = "fetching storage"
- desc = "Fetch the thing!"
- icon = 'icons/mob/dogborg_vr.dmi'
- icon_state = "dbag"
- w_class = WEIGHT_CLASS_HUGE
- max_single_weight_class = WEIGHT_CLASS_SMALL
- max_combined_w_class = WEIGHT_CLASS_SMALL
- max_items = 1
- collection_mode = 0
- can_hold = list() // any
- cant_hold = list(/obj/item/disk/nuclear)
-*/
-
-/obj/item/shockpaddles/robot/hound
- name = "paws of life"
- icon = 'icons/mob/dogborg_vr.dmi'
- icon_state = "defibpaddles0"
- desc = "Zappy paws. For fixing cardiac arrest."
- combat = 1
- attack_verb = list("batted", "pawed", "bopped", "whapped")
- chargecost = 500
-
-/obj/item/reagent_containers/borghypo/hound
- name = "MediHound hypospray"
- desc = "An advanced chemical synthesizer and injection system utilizing carrier's reserves, designed for heavy-duty medical equipment."
- charge_cost = 10
- var/datum/matter_synth/water = null
- reagent_ids = list("bicaridine", "kelotane", "alkysine", "imidazoline", "tricordrazine", "inaprovaline", "dexalin", "anti_toxin", "tramadol", "spaceacillin", "paracetamol")
-
-/obj/item/reagent_containers/borghypo/hound/process(delta_time) //Recharges in smaller steps and uses the water reserves as well.
- if(isrobot(loc))
- var/mob/living/silicon/robot/R = loc
- if(R && R.cell)
- for(var/T in reagent_ids)
- if(reagent_volumes[T] < volume && water.energy >= charge_cost)
- R.cell.use(charge_cost)
- water.use_charge(charge_cost)
- reagent_volumes[T] = min(reagent_volumes[T] + 1, volume)
- return 1
-
-/obj/item/reagent_containers/borghypo/hound/lost
- name = "Hound hypospray"
- desc = "An advanced chemical synthesizer and injection system utilizing carrier's reserves."
- reagent_ids = list("bicaridine", "kelotane", "alkysine", "imidazoline", "tricordrazine", "inaprovaline", "dexalin", "anti_toxin", "tramadol", "spaceacillin", "paracetamol")
-
-
-//Tongue stuff
-/obj/item/dogborg/tongue
- name = "synthetic tongue"
- desc = "Useful for slurping mess off the floor before affectionally licking the crew members in the face."
- icon = 'icons/mob/dogborg_vr.dmi'
- icon_state = "synthtongue"
- attack_sound = 'sound/effects/attackblob.ogg'
- item_flags = ITEM_NO_BLUDGEON | ITEM_ENCUMBERS_WHILE_HELD
- var/emagged = 0
- var/datum/matter_synth/water = null
-
-/obj/item/dogborg/tongue/examine(user)
- . = ..()
- if(water.energy)
- . += "[src] is wet."
- if(water.energy < 5)
- . += "[src] is dry."
-
-/obj/item/dogborg/tongue/attack_self(mob/user, datum/event_args/actor/actor)
- . = ..()
- if(.)
- return
- var/mob/living/silicon/robot/R = user
- if(R.emagged || R.emag_items)
- emagged = !emagged
- if(emagged)
- name = "hacked tongue of doom"
- desc = "Your tongue has been upgraded successfully. Congratulations."
- icon = 'icons/mob/dogborg_vr.dmi'
- icon_state = "syndietongue"
- else
- name = "synthetic tongue"
- desc = "Useful for slurping mess off the floor before affectionally licking the crew members in the face."
- icon = 'icons/mob/dogborg_vr.dmi'
- icon_state = "synthtongue"
- update_icon()
-
-/obj/item/dogborg/tongue/afterattack(atom/target, mob/user, clickchain_flags, list/params)
- if(!(clickchain_flags & CLICKCHAIN_HAS_PROXIMITY))
- return
-
- user.setClickCooldownLegacy(DEFAULT_ATTACK_COOLDOWN)
- if(user.client && (target in user.client.screen))
- to_chat(user, "You need to take [target] off before cleaning it!")
- if(istype(target, /obj/structure/sink) || istype(target, /obj/structure/toilet)) //Dog vibes.
- user.visible_message("[user] begins to lap up water from [target].", "You begin to lap up water from [target].")
- if(do_after(user, 50, target = target))
- water.add_charge(500)
- else if(water.energy < 5)
- to_chat(user, "Your mouth feels dry. You should drink some water.") //fixed annoying grammar and needless space
- return
- else if(istype(target,/obj/effect/debris/cleanable))
- user.visible_message("[user] begins to lick off [target].", "You begin to lick off [target]...")
- if(do_after(user, 50, target = target))
- to_chat(user, "You finish licking off [target].")
- water.use_charge(5)
- qdel(target)
- var/mob/living/silicon/robot/R = user
- R.cell.charge += 50
- else if(istype(target,/obj/item))
- if(istype(target,/obj/item/trash))
- user.visible_message("[user] nibbles away at [target].", "You begin to nibble away at [target]...")
- if(do_after(user, 50, target = target))
- user.visible_message("[user] finishes eating [target].", "You finish eating [target].")
- to_chat(user, "You finish off [target].")
- qdel(target)
- var/mob/living/silicon/robot/R = user
- if(istype(target,/obj/item/trash/rkibble))
- R.cell.charge += 1000
- else
- R.cell.charge += 250
- water.use_charge(5)
- return
- if(istype(target,/obj/item/cell))
- user.visible_message("[user] begins cramming [target] down its throat.", "You begin cramming \the [target.name] down your throat...")
- if(do_after(user, 50, target = target))
- user.visible_message("[user] finishes gulping down [target].", "You finish swallowing [target].")
- to_chat(user, "You finish off [target], and gain some charge!")
- var/mob/living/silicon/robot/R = user
- var/obj/item/cell/C = target
- R.cell.charge += C.max_charge / 3
- water.use_charge(5)
- qdel(target)
- return
- user.visible_message("[user] begins to lick [target] clean...", "You begin to lick [target] clean...")
- if(do_after(user, 50, target = target))
- to_chat(user, "You clean [target].")
- water.use_charge(5)
- var/obj/effect/debris/cleanable/C = locate() in target
- qdel(C)
- target.clean_blood()
- else if(ishuman(target))
- if(src.emagged)
- var/mob/living/silicon/robot/R = user
- var/mob/living/L = target
- if(R.cell.charge <= 666)
- return
- L.afflict_stun(20 * 1)
- L.afflict_paralyze(20 * 1)
- L.apply_effect(STUTTER, 1)
- L.visible_message("[user] has shocked [L] with its tongue!", \
- "[user] has shocked you with its tongue! You can feel the betrayal.")
- playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1)
- R.cell.charge -= 666
- else
- user.visible_message("\The [user] affectionally licks all over [target]'s face!", "You affectionally lick all over [target]'s face!")
- playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1)
- water.use_charge(5)
- var/mob/living/carbon/human/H = target
- if(H.species.lightweight == 1)
- H.afflict_paralyze(20 * 3)
- else
- user.visible_message("[user] begins to lick [target] clean...", "You begin to lick [target] clean...")
- if(do_after(user, 50, target = target))
- to_chat(user, "You clean [target].")
- var/obj/effect/debris/cleanable/C = locate() in target
- qdel(C)
- target.clean_blood()
- water.use_charge(5)
- if(istype(target, /turf/simulated))
- var/turf/simulated/T = target
- T.dirt = 0
- return
-
-/obj/item/pupscrubber
- name = "floor scrubber"
- desc = "Toggles floor scrubbing."
- icon = 'icons/mob/dogborg_vr.dmi'
- icon_state = "scrub0"
- item_flags = ITEM_NO_BLUDGEON | ITEM_ENCUMBERS_WHILE_HELD
- var/enabled = FALSE
-
-/obj/item/pupscrubber/attack_self(mob/user, datum/event_args/actor/actor)
- . = ..()
- if(.)
- return
- var/mob/living/silicon/robot/R = user
- if(!enabled)
- R.scrubbing = TRUE
- enabled = TRUE
- icon_state = "scrub1"
- else
- R.scrubbing = FALSE
- enabled = FALSE
- icon_state = "scrub0"
-
-/obj/item/dogborg/swordtail
- name = "sword tail"
- icon = 'icons/mob/dogborg_vr.dmi'
- icon_state = "swordtail"
- desc = "A glowing pink dagger normally attached to the end of a cyborg's tail. It appears to be extremely sharp."
- damage_force = 20 //Takes 5 hits to 100-0
- damage_mode = DAMAGE_MODE_SHARP | DAMAGE_MODE_EDGE
- throw_force = 0 //This shouldn't be thrown in the first place.
- attack_sound = 'sound/weapons/blade1.ogg'
- attack_verb = list("slashed", "stabbed", "jabbed", "mauled", "sliced")
- w_class = WEIGHT_CLASS_NORMAL
-
-/obj/item/lightreplacer/dogborg
- name = "light replacer"
- desc = "A device to automatically replace lights. This version is capable to produce a few replacements using your internal matter reserves."
- max_uses = 16
- uses = 10
- var/cooldown = 0
- var/datum/matter_synth/glass = null
-
-/obj/item/lightreplacer/dogborg/attack_self(mob/user, datum/event_args/actor/actor)
- . = ..()
- if(.)
- return//Recharger refill is so last season. Now we recycle without magic!
- if(uses >= max_uses)
- to_chat(user, "[src.name] is full.")
- return
- if(uses < max_uses && cooldown == 0)
- if(glass.energy < 125)
- to_chat(user, "Insufficient material reserves.")
- return
- to_chat(user, "It has [uses] lights remaining. Attempting to fabricate a replacement. Please stand still.")
- cooldown = 1
- if(do_after(user, 50))
- glass.use_charge(125)
- add_uses(1)
- cooldown = 0
- else
- cooldown = 0
- else
- to_chat(user, "It has [uses] lights remaining.")
- return
-
-//Pounce stuff for K-9
-/obj/item/dogborg/pounce
- name = "pounce"
- icon = 'icons/mob/dogborg_vr.dmi'
- icon_state = "pounce"
- desc = "Leap at your target to momentarily stun them."
- damage_force = 0
- item_flags = ITEM_NO_BLUDGEON | ITEM_ENCUMBERS_WHILE_HELD
- throw_force = 0
-
-/obj/item/dogborg/pounce/attack_self(mob/user, datum/event_args/actor/actor)
- . = ..()
- if(.)
- return
- var/mob/living/silicon/robot/R = user
- R.leap()
-
-/mob/living/silicon/robot/proc/leap()
- if(last_special > world.time)
- to_chat(src, "Your leap actuators are still recharging.")
- return
-
- if(cell.charge < 1000)
- to_chat(src, "Cell charge too low to continue.")
- return
-
- if(usr.incapacitated(INCAPACITATION_DISABLED))
- to_chat(src, "You cannot leap in your current state.")
- return
-
- var/list/choices = list()
- for(var/mob/living/M in view(3,src))
- if(!istype(M,/mob/living/silicon))
- choices += M
- choices -= src
-
- var/mob/living/T = input(src,"Who do you wish to leap at?") as null|anything in choices
-
- if(!T || !src || src.stat) return
-
- if(get_dist(get_turf(T), get_turf(src)) > 3) return
-
- if(last_special > world.time)
- return
-
- if(usr.incapacitated(INCAPACITATION_DISABLED))
- to_chat(src, "You cannot leap in your current state.")
- return
-
- last_special = world.time + 10
- status_flags |= STATUS_LEAPING
- pixel_y = pixel_y + 10
-
- src.visible_message("\The [src] leaps at [T]!")
- src.throw_at_old(get_step(get_turf(T),get_turf(src)), 4, 1, src)
- playsound(src.loc, 'sound/mecha/mechstep2.ogg', 50, 1)
- pixel_y = base_pixel_y
- cell.charge -= 750
-
- sleep(5)
-
- if(status_flags & STATUS_LEAPING) status_flags &= ~STATUS_LEAPING
-
- if(!src.Adjacent(T))
- to_chat(src, "You miss!")
- return
-
- if(ishuman(T))
- var/mob/living/carbon/human/H = T
- if(H.species.lightweight == 1)
- H.afflict_paralyze(20 * 3)
- return
- var/armor_block = run_armor_check(T, "melee")
- var/armor_soak = get_armor_soak(T, "melee")
- T.apply_damage(20, DAMAGE_TYPE_HALLOSS,, armor_block, armor_soak)
- if(prob(33))
- T.apply_effect(3, WEAKEN, armor_block)
-
-/obj/item/dogborg/mirrortool
- name = "Mirror Installation Tool"
- desc = "A tool for the installation and removal of Mirrors"
- icon = 'icons/obj/device_alt.dmi'
- icon_state = "sleevemate"
- item_state = "healthanalyzer"
- w_class = WEIGHT_CLASS_SMALL
- var/obj/item/implant/mirror/imp = null
-
-/obj/item/dogborg/mirrortool/legacy_mob_melee_hook(mob/target, mob/user, clickchain_flags, list/params, mult, target_zone, intent)
- var/mob/living/carbon/human/H = target
- if(!istype(H))
- return
- if(target_zone == BP_TORSO && imp == null)
- for(var/obj/item/organ/I in H.organs)
- for(var/obj/item/implant/mirror/MI in I.contents)
- if(imp == null)
- H.visible_message("[user] is attempting remove [H]'s mirror!")
- user.setClickCooldownLegacy(DEFAULT_QUICK_COOLDOWN)
- user.do_attack_animation(H)
- var/turf/T1 = get_turf(H)
- if (T1 && ((H == user) || do_after(user, 20)))
- if(user && H && (get_turf(H) == T1) && src)
- H.visible_message("[user] has removed [H]'s mirror.")
- add_attack_logs(user,H,"Mirror removed by [user]")
- src.imp = MI
- qdel(MI)
- else if (target_zone == BP_TORSO && imp != null)
- if (imp)
- H.visible_message("[user] is attempting to implant [H] with a mirror.")
- user.setClickCooldownLegacy(DEFAULT_QUICK_COOLDOWN)
- user.do_attack_animation(H)
- var/turf/T1 = get_turf(H)
- if (T1 && ((H == user) || do_after(user, 20)))
- if(user && H && (get_turf(H) == T1) && src && src.imp)
- H.visible_message("[H] has been implanted by [user].")
- add_attack_logs(user,H,"Implanted with [imp.name] using [name]")
- if(imp.handle_implant(H))
- imp.post_implant(H)
- src.imp = null
- else
- to_chat(usr, "You must target the torso.")
-
-/obj/item/dogborg/mirrortool/afterattack(atom/target, mob/user, clickchain_flags, list/params)
- var/obj/machinery/computer/transhuman/resleeving/comp = target
- if(!istype(comp))
- return
- comp.active_mr = imp.stored_mind
diff --git a/code/modules/mob/living/silicon/robot/drone/drone.dm b/code/modules/mob/living/silicon/robot/drone/drone.dm
index 71aa85f860b..7aa0d6a4bd8 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone.dm
@@ -1,7 +1,7 @@
/mob/living/silicon/robot/drone
name = "maintenance drone"
real_name = "drone"
- icon = 'icons/mob/robots.dmi'
+ icon = 'icons/mob/robot-legacy.dmi'
icon_state = "repairbot"
maxHealth = 35
health = 35
@@ -16,6 +16,16 @@
req_access = list(ACCESS_ENGINEERING_MAIN, ACCESS_SCIENCE_ROBOTICS)
integrated_light_power = 3
local_transmit = 1
+ can_repick_frame = FALSE
+ can_repick_module = FALSE
+
+ description_info = "Drones are player-controlled synthetics which are lawed to maintain the station and not \
+ interact with anyone else, except for other drones. They hold a wide array of tools to build, repair, maintain, and clean. \
+ They function similarly to other synthetics, in that they require recharging regularly, have laws, and are resilient to many hazards, \
+ such as fire, radiation, vacuum, and more. Ghosts can join the round as a maintenance drone by using the appropriate verb in the 'ghost' tab. \
+ An inactive drone can be rebooted by swiping an ID card on it with engineering or robotics access, and an active drone can be shut down in the same manner."
+
+ description_antag = "An Electromagnetic Sequencer can be used to subvert the drone to your cause."
can_pull_size = WEIGHT_CLASS_HUGE
can_pull_mobs = MOB_PULL_SMALLER
@@ -29,11 +39,14 @@
mob_size = MOB_SMALL // pulled here from a _vr file
+ conf_mmi_create_type = /obj/item/mmi/digital/robot
+ conf_cell_create_type = /obj/item/cell/basic/tier_1/medium
+
//Used for self-mailing.
var/mail_destination = ""
var/obj/machinery/drone_fabricator/master_fabricator
- var/law_type = /datum/ai_laws/drone
- var/module_type = /obj/item/robot_module/drone
+ var/law_type = /datum/ai_lawset/drone
+ var/module_type = /obj/item/robot_module_legacy/drone
var/obj/item/hat
var/hat_x_offset = 0
var/hat_y_offset = -13
@@ -69,26 +82,27 @@
/mob/living/silicon/robot/drone/construction
name = "construction drone"
icon_state = "constructiondrone"
- law_type = /datum/ai_laws/construction_drone
- module_type = /obj/item/robot_module/drone/construction
+ law_type = /datum/ai_lawset/drone/construction
+ module = /datum/prototype/robot_module/drone/construction
hat_x_offset = 1
hat_y_offset = -12
can_pull_mobs = MOB_PULL_SAME
- //holder_type = /obj/item/holder/drone/heavy
-/mob/living/silicon/robot/drone/construction/matriarch
+
+/mob/living/silicon/robot/drone/matriarch
name = "matriarch drone"
- module_type = /obj/item/robot_module/drone/construction/matriarch
- law_type = /datum/ai_laws/matriarch_drone
+ module = /datum/prototype/robot_module/drone/construction
+ law_type = /datum/ai_lawset/drone/matriarch
maxHealth = 50
health = 50
integrated_light_power = 4
name_override = 1
var/matrix_tag
+
/mob/living/silicon/robot/drone/mining
icon_state = "miningdrone"
item_state = "constructiondrone"
- law_type = /datum/ai_laws/mining_drone
- module_type = /obj/item/robot_module/drone/mining
+ law_type = /datum/ai_lawset/drone/mining
+ module = /datum/prototype/robot_module/drone/mining
hat_x_offset = 1
hat_y_offset = -12
can_pull_mobs = MOB_PULL_SAME
@@ -102,13 +116,6 @@
add_language("Drone Talk", 1)
serial_number = rand(0,999)
- //They are unable to be upgraded, so let's give them a bit of a better battery.
- cell.max_charge = 10000
- cell.charge = 10000
-
- // NO BRAIN.
- mmi = null
-
//We need to screw with their HP a bit. They have around one fifth as much HP as a full borg.
for(var/V in components) if(V != "power cell")
var/datum/robot_component/C = components[V]
@@ -122,15 +129,15 @@
if(!scrambledcodes && !foreign_droid)
aiCamera = new/obj/item/camera/siliconcam/drone_camera(src)
additional_law_channels["Drone"] = ":d"
- if(!laws) laws = new law_type
- if(!module) module = new module_type(src)
+ if(!laws)
+ laws = new law_type
flavor_text = "It's a tiny little repair drone. The casing is stamped with an corporate logo and the subscript: '[(LEGACY_MAP_DATUM).company_name] Recursive Repair Systems: Fixing Tomorrow's Problem, Today!'"
playsound(src.loc, 'sound/machines/twobeep.ogg', 50, 0)
//Redefining some robot procs...
-/mob/living/silicon/robot/drone/construction/matriarch/Namepick()
+/mob/living/silicon/robot/drone/matriarch/Namepick()
set category = "Robot Commands"
if(custom_name)
return 0
@@ -170,11 +177,6 @@
add_overlay(overlays_to_add)
-/mob/living/silicon/robot/drone/choose_icon()
- return
-
-/mob/living/silicon/robot/drone/pick_module()
- return
/mob/living/silicon/robot/drone/proc/wear_hat(var/obj/item/new_hat)
if(hat)
@@ -195,7 +197,7 @@
wear_hat(W)
user.visible_message("\The [user] puts \the [W] on \the [src].")
return
- else if(istype(W, /obj/item/borg/upgrade/))
+ else if(istype(W, /obj/item/robot_upgrade/))
to_chat(user, "\The [src] is not compatible with \the [W].")
return
@@ -261,7 +263,7 @@
connected_ai = null
clear_supplied_laws()
clear_inherent_laws()
- laws = new /datum/ai_laws/syndicate_override
+ laws = new /datum/ai_lawset/syndicate
var/datum/gender/TU = GLOB.gender_datums[user.get_visible_gender()]
set_zeroth_law("Only [user.real_name] and people [TU.he] designate[TU.s] as being such are operatives.")
@@ -293,10 +295,6 @@
return
..()
-//DRONE MOVEMENT.
-/mob/living/silicon/robot/drone/Process_Spaceslipping(var/prob_slip)
- return 0
-
//CONSOLE PROCS
/mob/living/silicon/robot/drone/proc/law_resync()
if(stat != DEAD)
@@ -382,30 +380,30 @@
..()
flavor_text = "It's a bulky mining drone stamped with a Grayson logo."
-/mob/living/silicon/robot/drone/construction/matriarch/init()
+/mob/living/silicon/robot/drone/matriarch/init()
..()
add_verb(src, /mob/living/silicon/robot/verb/Namepick)
flavor_text = "It's a small matriarch drone. The casing is stamped with an corporate logo and the subscript: '[(LEGACY_MAP_DATUM).company_name] Recursive Repair Systems: Heart Of The Swarm!'"
-/mob/living/silicon/robot/drone/construction/matriarch/welcome_drone()
+/mob/living/silicon/robot/drone/matriarch/welcome_drone()
to_chat(src, "You are a matriarch maintenance drone, a tiny-brained robotic repair machine.")
to_chat(src, "You have no individual will, no personality, and no drives or urges other than your laws.")
to_chat(src, "Remember, you are lawed against interference with the crew. Also remember, you DO NOT take orders from the AI.")
to_chat(src, "Use say ;Hello to talk to other drones and say Hello to speak silently to your nearby fellows.")
-/mob/living/silicon/robot/drone/construction/matriarch/Initialize()
+/mob/living/silicon/robot/drone/matriarch/Initialize()
. = ..()
matrix_tag = "[(LEGACY_MAP_DATUM).company_name]"
-/mob/living/silicon/robot/drone/construction/matriarch/shut_down()
+/mob/living/silicon/robot/drone/matriarch/shut_down()
return
-/mob/living/silicon/robot/drone/construction/matriarch/transfer_personality(client/player)
+/mob/living/silicon/robot/drone/matriarch/transfer_personality(client/player)
. = ..()
assign_drone_to_matrix(src, matrix_tag)
master_matrix.message_drones(SPAN_NOTICE("Energy surges through your circuits. The matriarch has come online."))
-/mob/living/silicon/robot/drone/construction/matriarch/ghostize(can_reenter_corpse, should_set_timer)
+/mob/living/silicon/robot/drone/matriarch/ghostize(can_reenter_corpse, should_set_timer)
. = ..()
if(can_reenter_corpse || stat == DEAD)
return
diff --git a/code/modules/mob/living/silicon/robot/drone/drone_items.dm b/code/modules/mob/living/silicon/robot/drone/drone_items.dm
index 10cfdb456d1..dbcb127ef91 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone_items.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone_items.dm
@@ -1,17 +1,11 @@
-//TODO: Matter decompiler.
+//TODO: (the) Matter decompiler (is shit)
/obj/item/matter_decompiler
name = "matter decompiler"
desc = "Eating trash, bits of glass, or other debris will replenish your stores."
icon = 'icons/obj/device.dmi'
icon_state = "decompiler"
- //Metal, glass, wood, plastic.
- var/datum/matter_synth/metal = null
- var/datum/matter_synth/glass = null
- var/datum/matter_synth/wood = null
- var/datum/matter_synth/plastic = null
-
/obj/item/matter_decompiler/afterattack(atom/target, mob/user, clickchain_flags, list/params)
if(!(clickchain_flags & CLICKCHAIN_HAS_PROXIMITY)) return //Not adjacent.
@@ -29,10 +23,8 @@
src.loc.visible_message("[src.loc] sucks [M] into its decompiler. There's a horrible crunching noise.","It's a bit of a struggle, but you manage to suck [M] into your decompiler. It makes a series of visceral crunching noises.")
new/obj/effect/debris/cleanable/blood/splatter(get_turf(src))
qdel(M)
- if(wood)
- wood.add_charge(2000)
- if(plastic)
- plastic.add_charge(2000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/wood_plank::id, 2000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/plastic::id, 2000)
return
else if(istype(M,/mob/living/silicon/robot/drone) && !M.client)
@@ -54,14 +46,10 @@
qdel(M)
new/obj/effect/debris/cleanable/blood/oil(get_turf(src))
- if(metal)
- metal.add_charge(15000)
- if(glass)
- glass.add_charge(15000)
- if(wood)
- wood.add_charge(2000)
- if(plastic)
- plastic.add_charge(1000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/steel::id, 15000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/glass::id, 15000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/wood_plank::id, 2000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/plastic::id, 1000)
return
else
continue
@@ -69,51 +57,35 @@
for(var/obj/W in T)
//Different classes of items give different commodities.
if(istype(W,/obj/item/cigbutt))
- if(plastic)
- plastic.add_charge(500)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/plastic::id, 500)
else if(istype(W,/obj/effect/spider/spiderling))
- if(wood)
- wood.add_charge(2000)
- if(plastic)
- plastic.add_charge(2000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/wood_plank::id, 2000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/plastic::id, 2000)
else if(istype(W,/obj/item/light))
var/obj/item/light/L = W
if(L.status >= 2) //In before someone changes the inexplicably local defines. ~ Z
- if(metal)
- metal.add_charge(250)
- if(glass)
- glass.add_charge(250)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/steel::id, 250)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/glass::id, 250)
else
continue
else if(istype(W,/obj/effect/decal/remains/robot))
- if(metal)
- metal.add_charge(2000)
- if(plastic)
- plastic.add_charge(2000)
- if(glass)
- glass.add_charge(1000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/steel::id, 2000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/plastic::id, 2000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/glass::id, 1000)
else if(istype(W,/obj/item/trash))
- if(metal)
- metal.add_charge(1000)
- if(plastic)
- plastic.add_charge(3000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/steel::id, 1000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/plastic::id, 3000)
else if(istype(W,/obj/effect/debris/cleanable/blood/gibs/robot))
- if(metal)
- metal.add_charge(2000)
- if(glass)
- glass.add_charge(2000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/steel::id, 2000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/glass::id, 2000)
else if(istype(W,/obj/item/ammo_casing))
- if(metal)
- metal.add_charge(1000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/steel::id, 1000)
else if(istype(W,/obj/item/material/shard/shrapnel))
- if(metal)
- metal.add_charge(1000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/steel::id, 1000)
else if(istype(W,/obj/item/material/shard))
- if(glass)
- glass.add_charge(1000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/glass::id, 1000)
else if(istype(W,/obj/item/reagent_containers/food/snacks/grown))
- if(wood)
- wood.add_charge(4000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/wood_plank::id, 4000)
else if(istype(W,/obj/item/pipe))
// This allows drones and engiborgs to clear pipe assemblies from floors.
else
@@ -127,57 +99,3 @@
else
to_chat(user, "Nothing on \the [T] is useful to you.")
return
-
-//PRETTIER TOOL LIST.
-/mob/living/silicon/robot/drone/installed_modules()
-
- if(weapon_lock)
- to_chat(src, "Weapon lock active, unable to use modules! Count:[weaponlock_time]")
- return
-
- if(!module)
- module = new /obj/item/robot_module/drone(src)
-
- var/dat = "
Drone modules\n"
- dat += {"
- Activated Modules
-
- Module 1: [module_state_1 ? "[module_state_1]" : "No Module"]
- Module 2: [module_state_2 ? "[module_state_2]" : "No Module"]
- Module 3: [module_state_3 ? "[module_state_3]" : "No Module"]
-
- Installed Modules
"}
-
-
- var/tools = "Tools and devices
"
- var/resources = "
Resources
"
-
- for (var/O in module.modules)
-
- var/module_string = ""
-
- if (!O)
- module_string += "Resource depleted
"
- else if(activated(O))
- module_string += "[O]: Activated
"
- else
- module_string += "[O]: Activate
"
-
- if((istype(O,/obj/item) || istype(O,/obj/item)) && !(istype(O,/obj/item/stack/cable_coil)))
- tools += module_string
- else
- resources += module_string
-
- dat += tools
-
- if (emagged)
- if (!module.emag)
- dat += "Resource depleted
"
- else if(activated(module.emag))
- dat += "[module.emag]: Activated
"
- else
- dat += "[module.emag]: Activate
"
-
- dat += resources
-
- src << browse(dat, "window=robotmod")
diff --git a/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm b/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm
index 0f453fe6fea..d5d17a6339b 100644
--- a/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm
+++ b/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm
@@ -6,7 +6,7 @@
/proc/count_matriarchs()
var/matriarchs = 0
- for(var/mob/living/silicon/robot/drone/construction/matriarch/M in GLOB.player_list)
+ for(var/mob/living/silicon/robot/drone/matriarch/M in GLOB.player_list)
matriarchs++
return matriarchs
@@ -46,7 +46,7 @@
/obj/machinery/drone_fabricator/matriarch
name = "matriarch drone fabricator"
fabricator_tag = "Upper Level Matriarch"
- drone_type = /mob/living/silicon/robot/drone/construction/matriarch
+ drone_type = /mob/living/silicon/robot/drone/matriarch
/obj/machinery/drone_fabricator/update_icon_state()
. = ..()
@@ -97,7 +97,7 @@
if(!produce_drones || !config_legacy.allow_drone_spawn || count_drones() >= config_legacy.max_maint_drones)
return
- if((drone_type == /mob/living/silicon/robot/drone/construction/matriarch) && (count_matriarchs() > 0))
+ if((drone_type == /mob/living/silicon/robot/drone/matriarch) && (count_matriarchs() > 0))
return
if(player && !istype(player.mob,/mob/observer/dead))
diff --git a/code/modules/mob/living/silicon/robot/drone/matrix_hive.dm b/code/modules/mob/living/silicon/robot/drone/matrix_hive.dm
index 8564c0fe17e..76e510692b4 100644
--- a/code/modules/mob/living/silicon/robot/drone/matrix_hive.dm
+++ b/code/modules/mob/living/silicon/robot/drone/matrix_hive.dm
@@ -83,17 +83,9 @@ var/global/list/drone_matrices = list()
if(MTX_UPG_HEALTH)
D.maxHealth += 15
if(MTX_UPG_MOP)
- var/obj/item/mop/current_mop = locate(/obj/item/mop) in D.module.modules
- if(!istype(current_mop, /obj/item/mop/advanced))
- D.module.modules -= current_mop
- D.module.modules += new/obj/item/mop/advanced(D.module)
- qdel(current_mop)
+ D.robot_inventory.inv_register(new /obj/item/mop/advanced)
if(MTX_UPG_T)
- var/obj/item/t_scanner/current_scanner = locate(/obj/item/t_scanner) in D.module.modules
- if(!istype(current_scanner, /obj/item/t_scanner/upgraded))
- D.module.modules -= current_scanner
- D.module.modules += new/obj/item/t_scanner/upgraded(D.module)
- qdel(current_scanner)
+ D.robot_inventory.inv_register(new /obj/item/t_scanner/upgraded)
LAZYADD(D.matrix_upgrades, upgrade_type)
/proc/assign_drone_to_matrix(mob/living/silicon/robot/drone/D, var/matrix_tag)
diff --git a/code/modules/mob/living/silicon/robot/drone/matrix_powers.dm b/code/modules/mob/living/silicon/robot/drone/matrix_powers.dm
index 1355adaf5a9..6387e0450ab 100644
--- a/code/modules/mob/living/silicon/robot/drone/matrix_powers.dm
+++ b/code/modules/mob/living/silicon/robot/drone/matrix_powers.dm
@@ -10,7 +10,7 @@
var/datum/drone_matrix/DM = master_matrix
var/dat = ""
- var/mob/living/silicon/robot/drone/construction/matriarch/matriarch = DM.get_matriarch()
+ var/mob/living/silicon/robot/drone/matriarch/matriarch = DM.get_matriarch()
if(matriarch)
var/drone_stat = ""
if(matriarch.stat == UNCONSCIOUS)
@@ -56,7 +56,7 @@
to_chat(src, SPAN_NOTICE("Matrix upgrades active on chassis: [english_list(matrix_upgrades)]"))
-/mob/living/silicon/robot/drone/construction/matriarch/verb/select_matrix_upgrades()
+/mob/living/silicon/robot/drone/matriarch/verb/select_matrix_upgrades()
set name = "Select Matrix Upgrades"
set desc = "Select the upgrades to apply to the drones within your matrix."
set category = "Matrix"
diff --git a/code/modules/mob/living/silicon/robot/drone/swarm.dm b/code/modules/mob/living/silicon/robot/drone/swarm.dm
index 8a45b86d418..57619865a95 100644
--- a/code/modules/mob/living/silicon/robot/drone/swarm.dm
+++ b/code/modules/mob/living/silicon/robot/drone/swarm.dm
@@ -1,98 +1,101 @@
-/mob/living/silicon/robot/drone/swarm
- name = "swarm drone"
- real_name = "drone"
- icon = 'icons/mob/swarmbot.dmi'
- icon_state = "swarmer"
- iff_factions = MOB_IFF_FACTION_SWARMER
- maxHealth = 75
- health = 75
- cell_emp_mult = 0.5
- universal_speak = 0
- universal_understand = 1
- gender = NEUTER
- pass_flags = ATOM_PASS_TABLE
- braintype = "Drone"
- lawupdate = 0
- density = 1
- idcard_type = /obj/item/card/id/syndicate
- req_access = list(999)
- integrated_light_power = 3
- local_transmit = 0
+// TODO: IOU swarmers.
- can_pull_size = WEIGHT_CLASS_HUGE
- can_pull_mobs = MOB_PULL_SMALLER
- can_enter_vent_with = list(
- /obj)
+// /mob/living/silicon/robot/drone/swarm
+// name = "swarm drone"
+// real_name = "drone"
+// icon = 'icons/mob/swarmbot.dmi'
+// icon_state = "swarmer"
+// iff_factions = MOB_IFF_FACTION_SWARMER
+// maxHealth = 75
+// health = 75
+// cell_emp_mult = 0.5
+// universal_speak = 0
+// universal_understand = 1
+// gender = NEUTER
+// pass_flags = ATOM_PASS_TABLE
+// braintype = "Drone"
+// lawupdate = 0
+// density = 1
+// idcard_type = /obj/item/card/id/syndicate
+// req_access = list(999)
+// integrated_light_power = 3
+// local_transmit = 0
- mob_always_swap = 1
+// can_pull_size = WEIGHT_CLASS_HUGE
+// can_pull_mobs = MOB_PULL_SMALLER
+// can_enter_vent_with = list(
+// /obj)
- softfall = TRUE
+// mob_always_swap = 1
- mob_size = MOB_LARGE
+// softfall = TRUE
- law_type = /datum/ai_laws/swarm_drone
- module_type = /obj/item/robot_module/drone/swarm
+// mob_size = MOB_LARGE
- hat_x_offset = 0
- hat_y_offset = -10
+// law_type = /datum/ai_lawset/swarmer
+// module = /datum/prototype/robot_module/s
+// module_type = /obj/item/robot_module_legacy/drone/swarm
- foreign_droid = TRUE
- scrambledcodes = TRUE
+// hat_x_offset = 0
+// hat_y_offset = -10
- holder_type = /obj/item/holder/drone
+// foreign_droid = TRUE
+// scrambledcodes = TRUE
- can_be_antagged = TRUE
+// holder_type = /obj/item/holder/drone
- var/spell_setup = list(
- /spell/aoe_turf/conjure/swarmer,
- /spell/aoe_turf/conjure/forcewall/swarm,
- /spell/aoe_turf/blink/swarm,
- /spell/aoe_turf/conjure/swarmer/gunner,
- /spell/aoe_turf/conjure/swarmer/melee
- )
+// can_be_antagged = TRUE
-/mob/living/silicon/robot/drone/swarm/Initialize(mapload)
- . = ..()
+// var/spell_setup = list(
+// /spell/aoe_turf/conjure/swarmer,
+// /spell/aoe_turf/conjure/forcewall/swarm,
+// /spell/aoe_turf/blink/swarm,
+// /spell/aoe_turf/conjure/swarmer/gunner,
+// /spell/aoe_turf/conjure/swarmer/melee
+// )
- add_language(LANGUAGE_SWARMBOT, 1)
+// /mob/living/silicon/robot/drone/swarm/Initialize(mapload)
+// . = ..()
- for(var/spell in spell_setup)
- src.add_spell(new spell, "nano_spell_ready", /atom/movable/screen/movable/spell_master/swarm)
+// add_language(LANGUAGE_SWARMBOT, 1)
-/mob/living/silicon/robot/drone/swarm/init()
- ..()
- QDEL_NULL(aiCamera)
- flavor_text = "Some form of ancient machine."
+// for(var/spell in spell_setup)
+// src.add_spell(new spell, "nano_spell_ready", /atom/movable/screen/movable/spell_master/swarm)
-/mob/living/silicon/robot/drone/swarm/gunner
- name = "swarm gunner"
- real_name = "drone"
- icon = 'icons/mob/swarmbot.dmi'
- icon_state = "swarmer_ranged"
- iff_factions = MOB_IFF_FACTION_SWARMER
+// /mob/living/silicon/robot/drone/swarm/init()
+// ..()
+// QDEL_NULL(aiCamera)
+// flavor_text = "Some form of ancient machine."
- law_type = /datum/ai_laws/swarm_drone/soldier
- module_type = /obj/item/robot_module/drone/swarm/ranged
+// /mob/living/silicon/robot/drone/swarm/gunner
+// name = "swarm gunner"
+// real_name = "drone"
+// icon = 'icons/mob/swarmbot.dmi'
+// icon_state = "swarmer_ranged"
+// iff_factions = MOB_IFF_FACTION_SWARMER
- spell_setup = list(
- /spell/aoe_turf/conjure/swarmer,
- /spell/aoe_turf/conjure/forcewall/swarm,
- /spell/aoe_turf/blink/swarm
- )
+// law_type = /datum/ai_lawset/swarmer/soldier
+// module_type = /obj/item/robot_module_legacy/drone/swarm/ranged
-/mob/living/silicon/robot/drone/swarm/melee
- name = "swarm melee"
- real_name = "drone"
- icon = 'icons/mob/swarmbot.dmi'
- icon_state = "swarmer_melee"
- iff_factions = MOB_IFF_FACTION_SWARMER
+// spell_setup = list(
+// /spell/aoe_turf/conjure/swarmer,
+// /spell/aoe_turf/conjure/forcewall/swarm,
+// /spell/aoe_turf/blink/swarm
+// )
- law_type = /datum/ai_laws/swarm_drone/soldier
- module_type = /obj/item/robot_module/drone/swarm/melee
+// /mob/living/silicon/robot/drone/swarm/melee
+// name = "swarm melee"
+// real_name = "drone"
+// icon = 'icons/mob/swarmbot.dmi'
+// icon_state = "swarmer_melee"
+// iff_factions = MOB_IFF_FACTION_SWARMER
- spell_setup = list(
- /spell/aoe_turf/conjure/swarmer,
- /spell/aoe_turf/conjure/forcewall/swarm,
- /spell/aoe_turf/blink/swarm
- )
+// law_type = /datum/ai_lawset/swarmer/soldier
+// module_type = /obj/item/robot_module_legacy/drone/swarm/melee
+
+// spell_setup = list(
+// /spell/aoe_turf/conjure/swarmer,
+// /spell/aoe_turf/conjure/forcewall/swarm,
+// /spell/aoe_turf/blink/swarm
+// )
diff --git a/code/modules/mob/living/silicon/robot/drone/swarm_abilities.dm b/code/modules/mob/living/silicon/robot/drone/swarm_abilities.dm
index 62a54e9ecdf..4a287092f54 100644
--- a/code/modules/mob/living/silicon/robot/drone/swarm_abilities.dm
+++ b/code/modules/mob/living/silicon/robot/drone/swarm_abilities.dm
@@ -1,117 +1,118 @@
+// TODO: IOU swarmers.
-/spell/aoe_turf/conjure/swarmer
- name = "Self Replication"
- desc = "This ability constructs a standard swarmer shell that may activate at some point."
+// /spell/aoe_turf/conjure/swarmer
+// name = "Self Replication"
+// desc = "This ability constructs a standard swarmer shell that may activate at some point."
- school = "conjuration"
- charge_max = 120 SECONDS
- spell_flags = 0
- invocation = "none"
- invocation_type = SpI_NONE
- range = 0
+// school = "conjuration"
+// charge_max = 120 SECONDS
+// spell_flags = 0
+// invocation = "none"
+// invocation_type = SpI_NONE
+// range = 0
- summon_type = list(/obj/structure/ghost_pod/ghost_activated/swarm_drone/event)
+// summon_type = list(/obj/structure/ghost_pod/ghost_activated/swarm_drone/event)
- hud_state = "swarm_replicate"
+// hud_state = "swarm_replicate"
-/spell/aoe_turf/conjure/swarmer/conjure_animation(var/atom/movable/overlay/animation, var/turf/target)
- animation.icon_state = "deflect_static"
- flick("shield2",animation)
- spawn(1 SECOND)
- qdel(animation)
+// /spell/aoe_turf/conjure/swarmer/conjure_animation(var/atom/movable/overlay/animation, var/turf/target)
+// animation.icon_state = "deflect_static"
+// flick("shield2",animation)
+// spawn(1 SECOND)
+// qdel(animation)
-/spell/aoe_turf/conjure/forcewall/swarm
- name = "Null-Field"
- desc = "Create a bubble of null-point energy."
- summon_type = list(/obj/effect/forcefield/swarm)
- duration = 30 SECONDS
- charge_max = 60 SECONDS
+// /spell/aoe_turf/conjure/forcewall/swarm
+// name = "Null-Field"
+// desc = "Create a bubble of null-point energy."
+// summon_type = list(/obj/effect/forcefield/swarm)
+// duration = 30 SECONDS
+// charge_max = 60 SECONDS
- school = "conjuration"
- invocation = "none"
- invocation_type = SpI_NONE
- range = 0
+// school = "conjuration"
+// invocation = "none"
+// invocation_type = SpI_NONE
+// range = 0
- hud_state = "wiz_shield"
+// hud_state = "wiz_shield"
-/obj/effect/forcefield/swarm
- desc = "A pocket of strange energy."
- name = "Null-Field"
- icon = 'icons/effects/effects.dmi'
- icon_state = "shield-old"
- invisibility = 0
+// /obj/effect/forcefield/swarm
+// desc = "A pocket of strange energy."
+// name = "Null-Field"
+// icon = 'icons/effects/effects.dmi'
+// icon_state = "shield-old"
+// invisibility = 0
-/spell/aoe_turf/conjure/zeropointwell
- name = "Zero-Point Well"
- desc = "This ability constructs a standard zero-point energy well, capable of charging nearby swarmers."
+// /spell/aoe_turf/conjure/zeropointwell
+// name = "Zero-Point Well"
+// desc = "This ability constructs a standard zero-point energy well, capable of charging nearby swarmers."
- school = "conjuration"
- charge_max = 120 SECONDS
- spell_flags = 0
- invocation = "none"
- invocation_type = SpI_NONE
- range = 0
+// school = "conjuration"
+// charge_max = 120 SECONDS
+// spell_flags = 0
+// invocation = "none"
+// invocation_type = SpI_NONE
+// range = 0
- summon_type = list(/obj/structure/cult/pylon/swarm/zp_well)
+// summon_type = list(/obj/structure/cult/pylon/swarm/zp_well)
- hud_state = "swarm_zeropoint"
+// hud_state = "swarm_zeropoint"
-/spell/aoe_turf/conjure/zeropointbarricade
- name = "Zero-Point Barricade"
- desc = "This ability constructs a standard zero-point energy wall, used to create a secure passageway for allies, and a bastion for defense."
+// /spell/aoe_turf/conjure/zeropointbarricade
+// name = "Zero-Point Barricade"
+// desc = "This ability constructs a standard zero-point energy wall, used to create a secure passageway for allies, and a bastion for defense."
- school = "conjuration"
- charge_max = 120 SECONDS
- spell_flags = 0
- invocation = "none"
- invocation_type = SpI_NONE
- range = 0
+// school = "conjuration"
+// charge_max = 120 SECONDS
+// spell_flags = 0
+// invocation = "none"
+// invocation_type = SpI_NONE
+// range = 0
- summon_type = list(/obj/structure/cult/pylon/swarm/defender)
+// summon_type = list(/obj/structure/cult/pylon/swarm/defender)
- hud_state = "swarm_barricade"
+// hud_state = "swarm_barricade"
-/spell/aoe_turf/blink/swarm
- name = "Warp"
- desc = "Your null-point drive jaunts you to a new location."
+// /spell/aoe_turf/blink/swarm
+// name = "Warp"
+// desc = "Your null-point drive jaunts you to a new location."
- school = "abjuration"
- charge_max = 5 MINUTES
- spell_flags = Z2NOCAST | IGNOREDENSE
- invocation = "none"
- invocation_type = SpI_NONE
- range = 10
- inner_radius = 5
- hud_state = "swarm_warp"
+// school = "abjuration"
+// charge_max = 5 MINUTES
+// spell_flags = Z2NOCAST | IGNOREDENSE
+// invocation = "none"
+// invocation_type = SpI_NONE
+// range = 10
+// inner_radius = 5
+// hud_state = "swarm_warp"
-/spell/aoe_turf/conjure/swarmer/gunner
- name = "Generate Gunner"
- desc = "This spell constructs a gunner swarmer shell that may activate at some point."
+// /spell/aoe_turf/conjure/swarmer/gunner
+// name = "Generate Gunner"
+// desc = "This spell constructs a gunner swarmer shell that may activate at some point."
- school = "conjuration"
- charge_type = Sp_CHARGES
- charge_max = 1
- spell_flags = 0
- invocation = "none"
- invocation_type = SpI_NONE
- range = 0
+// school = "conjuration"
+// charge_type = Sp_CHARGES
+// charge_max = 1
+// spell_flags = 0
+// invocation = "none"
+// invocation_type = SpI_NONE
+// range = 0
- summon_type = list(/obj/structure/ghost_pod/ghost_activated/swarm_drone/event/gunner)
+// summon_type = list(/obj/structure/ghost_pod/ghost_activated/swarm_drone/event/gunner)
- hud_state = "swarm_replicate"
+// hud_state = "swarm_replicate"
-/spell/aoe_turf/conjure/swarmer/melee
- name = "Generate Impaler"
- desc = "This spell constructs an impaler swarmer shell that may activate at some point."
+// /spell/aoe_turf/conjure/swarmer/melee
+// name = "Generate Impaler"
+// desc = "This spell constructs an impaler swarmer shell that may activate at some point."
- school = "conjuration"
- charge_type = Sp_CHARGES
- charge_max = 1
- spell_flags = 0
- invocation = "none"
- invocation_type = SpI_NONE
- range = 0
+// school = "conjuration"
+// charge_type = Sp_CHARGES
+// charge_max = 1
+// spell_flags = 0
+// invocation = "none"
+// invocation_type = SpI_NONE
+// range = 0
- summon_type = list(/obj/structure/ghost_pod/ghost_activated/swarm_drone/event/melee)
+// summon_type = list(/obj/structure/ghost_pod/ghost_activated/swarm_drone/event/melee)
- hud_state = "swarm_replicate"
+// hud_state = "swarm_replicate"
diff --git a/code/modules/mob/living/silicon/robot/drone/swarm_items.dm b/code/modules/mob/living/silicon/robot/drone/swarm_items.dm
index eea300ffdab..5ea4b090719 100644
--- a/code/modules/mob/living/silicon/robot/drone/swarm_items.dm
+++ b/code/modules/mob/living/silicon/robot/drone/swarm_items.dm
@@ -26,10 +26,9 @@
src.loc.visible_message("[src.loc] sucks [M] into its decompiler. There's a horrible crunching noise.","It's a bit of a struggle, but you manage to suck [M] into your decompiler. It makes a series of visceral crunching noises.")
new/obj/effect/debris/cleanable/blood/splatter(get_turf(src))
qdel(M)
- if(wood)
- wood.add_charge(2000)
- if(plastic)
- plastic.add_charge(2000)
+
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/wood_plank, 2000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/plastic, 2000)
return
else if(istype(M,/mob/living/silicon/robot/drone) && !M.client)
@@ -51,14 +50,10 @@
qdel(M)
new/obj/effect/debris/cleanable/blood/oil(get_turf(src))
- if(metal)
- metal.add_charge(15000)
- if(glass)
- glass.add_charge(15000)
- if(wood)
- wood.add_charge(2000)
- if(plastic)
- plastic.add_charge(1000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/steel::id, 15000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/glass::id, 15000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/wood_plank::id, 2000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/plastic::id, 1000)
return
else
continue
@@ -66,31 +61,23 @@
for(var/obj/W in T)
//Different classes of items give different commodities.
if(istype(W,/obj/structure/girder))
- if(metal)
- metal.add_charge(500)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/steel::id, 500)
else if(istype(W,/obj/machinery/power/emitter))
- if(metal)
- metal.add_charge(3000)
- if(plastic)
- plastic.add_charge(1000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/steel::id, 3000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/plastic::id, 1000)
else if(istype(W,/obj/machinery/space_heater))
- if(metal)
- metal.add_charge(1500)
- if(plastic)
- plastic.add_charge(750)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/steel::id, 1500)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/plastic::id, 750)
else if(istype(W,/obj/structure/closet))
var/obj/structure/closet/C = W
if(!C.opened)
continue
if(istype(W,/obj/structure/closet/coffin))
- if(wood)
- wood.add_charge(1000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/wood_plank::id, 1000)
else if(istype(W,/obj/structure/closet/crate/plastic))
- if(plastic)
- plastic.add_charge(750)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/plastic::id, 1000)
else
- if(metal)
- metal.add_charge(1000)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/steel::id, 1000)
else
continue
@@ -110,7 +97,6 @@
to_chat(user, "You deploy your decompiler and clear out the contents of \the [T].")
else
to_chat(user, "Nothing on \the [T] is useful to you.")
- return
/obj/effect/temporary_effect/pulse/disintegrate
name = "molecular debonding field"
diff --git a/code/modules/mob/living/silicon/robot/examine.dm b/code/modules/mob/living/silicon/robot/examine.dm
index 694d8db64c6..874adb19694 100644
--- a/code/modules/mob/living/silicon/robot/examine.dm
+++ b/code/modules/mob/living/silicon/robot/examine.dm
@@ -1,5 +1,5 @@
/mob/living/silicon/robot/examine(mob/user, dist)
- . = list("This is [icon2html(src, user)] \a [src][src.module && ", a [src.module.name] unit"]!")
+ . = list("This is [icon2html(src, user)] \a [src], a [src.module ? module.get_display_name() : "unformatted"] unit!")
if(desc)
. += "[desc]"
@@ -49,7 +49,11 @@
if(DEAD)
. += SPAN_DEADSAY("It looks like its system is corrupted and requires a reset.")
- . += attempt_vr(src,"examine_bellies_borg",args)
+ var/message = ""
+ for(var/belly in vore_organs)
+ var/obj/belly/B = belly
+ message += B.get_examine_msg()
+ . += message
if(showvoreprefs && ckey) //ckey so non-controlled mobs don't display it.
. += SPAN_BOLDNOTICE("\[Mechanical Vore Preferences\]")
diff --git a/code/modules/mob/living/silicon/robot/examine_vr.dm b/code/modules/mob/living/silicon/robot/examine_vr.dm
deleted file mode 100644
index f3b04a440ad..00000000000
--- a/code/modules/mob/living/silicon/robot/examine_vr.dm
+++ /dev/null
@@ -1,8 +0,0 @@
-/mob/living/silicon/robot/proc/examine_bellies_borg()
-
- var/message = ""
- for(var/belly in vore_organs)
- var/obj/belly/B = belly
- message += B.get_examine_msg()
-
- return message
diff --git a/code/modules/mob/living/silicon/robot/iconset/baseline_droid.dm b/code/modules/mob/living/silicon/robot/iconset/baseline_droid.dm
new file mode 100644
index 00000000000..dc3977e0d6d
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/baseline_droid.dm
@@ -0,0 +1,17 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/baseline_droid
+ display_name = "Droid"
+ abstract_type = /datum/prototype/robot_iconset/baseline_droid
+ auto_chassis = /datum/prototype/robot_chassis/baseline
+ icon = 'icons/mob/robot/iconset/baseline_droid.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/baseline_droid/standard
+ icon_state = "standard"
+ icon_state_indicator = "standard-indicator"
+
+/datum/prototype/robot_iconset/baseline_droid/combat
+ icon_state = "combat"
+ icon_state_indicator = "combat-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/baseline_drone.dm b/code/modules/mob/living/silicon/robot/iconset/baseline_drone.dm
new file mode 100644
index 00000000000..5f2d2642451
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/baseline_drone.dm
@@ -0,0 +1,18 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/baseline_drone
+ display_name = "Maintenance Drone"
+ abstract_type = /datum/prototype/robot_iconset/baseline_drone
+ auto_chassis = /datum/prototype/robot_chassis/baseline
+ icon = 'icons/mob/robot/iconset/baseline_drone.dmi'
+ icon_state_cover = "panel"
+ icon_state_indicator = "indicator-lights"
+
+/datum/prototype/robot_iconset/baseline_drone/construction
+ icon_state = "construction"
+ icon_state_indicator = "construction-indicator"
+
+/datum/prototype/robot_iconset/baseline_drone/mining
+ icon_state = "mining"
+ icon_state_indicator = "mining-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/baseline_misc.dm b/code/modules/mob/living/silicon/robot/iconset/baseline_misc.dm
new file mode 100644
index 00000000000..73d2a8b02bf
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/baseline_misc.dm
@@ -0,0 +1,24 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/baseline_misc
+ abstract_type = /datum/prototype/robot_iconset/baseline_misc
+ auto_chassis = /datum/prototype/robot_chassis/baseline
+ icon = 'icons/mob/robot/iconset/baseline_misc.dmi'
+ icon_state_cover = "panel"
+ icon_state_indicator = "indicator-lights"
+
+/datum/prototype/robot_iconset/baseline_misc/omoikane
+ display_name = "Ward-Takashi - Omni"
+ icon_state = "omoikane"
+ icon_state_indicator = "omoikane-indicator"
+
+/datum/prototype/robot_iconset/baseline_misc/arachne
+ display_name = "Ward-Takashi - Arachne"
+ icon_state = "arachne"
+ icon_state_indicator = "arachne-indicator"
+
+/datum/prototype/robot_iconset/baseline_misc/squats
+ display_name = "Gorlex Marauders - Squats"
+ icon_state = "squats"
+ icon_state_indicator = "squats-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/baseline_old.dm b/code/modules/mob/living/silicon/robot/iconset/baseline_old.dm
new file mode 100644
index 00000000000..8d54d8f2b54
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/baseline_old.dm
@@ -0,0 +1,65 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/baseline_old
+ display_name = "Basic"
+ abstract_type = /datum/prototype/robot_iconset/baseline_old
+ auto_chassis = /datum/prototype/robot_chassis/baseline
+ icon = 'icons/mob/robot/iconset/baseline_old.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/baseline_old/standard
+ icon_state = "standard"
+
+/datum/prototype/robot_iconset/baseline_old/yellowvest
+ icon_state = "yellowvest"
+
+/datum/prototype/robot_iconset/baseline_old/hazardvest
+ icon_state = "hazardvest"
+
+/datum/prototype/robot_iconset/baseline_old/engineer
+ icon_state = "engineer"
+
+/datum/prototype/robot_iconset/baseline_old/security
+ icon_state = "security"
+
+/datum/prototype/robot_iconset/baseline_old/security_2
+ display_name = "Basic (Alt)"
+ icon_state = "security_2"
+
+/datum/prototype/robot_iconset/baseline_old/service_sweater
+ display_name = /datum/prototype/robot_iconset/baseline_old::display_name + " (Sweater)"
+ icon_state = "service_sweater"
+
+/datum/prototype/robot_iconset/baseline_old/service_skirt
+ display_name = /datum/prototype/robot_iconset/baseline_old::display_name + " (Skirt)"
+ icon_state = "service_skirt"
+
+/datum/prototype/robot_iconset/baseline_old/service
+ icon_state = "service"
+
+/datum/prototype/robot_iconset/baseline_old/service_fancy
+ display_name = /datum/prototype/robot_iconset/baseline_old::display_name + " (Fancy)"
+ icon_state = "service_fancy"
+
+/datum/prototype/robot_iconset/baseline_old/service_hydro
+ display_name = /datum/prototype/robot_iconset/baseline_old::display_name + " (Hydroponics)"
+ icon_state = "service_hydro"
+
+/datum/prototype/robot_iconset/baseline_old/medical
+ icon_state = "medical"
+
+/datum/prototype/robot_iconset/baseline_old/medical_2
+ display_name = "Basic (Alt)"
+ icon_state = "medical_2"
+
+/datum/prototype/robot_iconset/baseline_old/security_riot
+ display_name = /datum/prototype/robot_iconset/baseline_old::display_name + " (Riot)"
+ icon_state = "security_riot"
+
+/datum/prototype/robot_iconset/baseline_old/janitor
+ icon_state = "janitor"
+
+/datum/prototype/robot_iconset/baseline_old/janitor_2
+ display_name = "Basic (Alt)"
+ icon_state = "janitor_2"
diff --git a/code/modules/mob/living/silicon/robot/iconset/baseline_standard.dm b/code/modules/mob/living/silicon/robot/iconset/baseline_standard.dm
new file mode 100644
index 00000000000..74a6abd4398
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/baseline_standard.dm
@@ -0,0 +1,36 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/baseline_standard
+ display_name = "M-USE"
+ abstract_type = /datum/prototype/robot_iconset/baseline_standard
+ auto_chassis = /datum/prototype/robot_chassis/baseline
+ icon = 'icons/mob/robot/iconset/baseline_standard.dmi'
+ icon_state_cover = "panel"
+ icon_state_indicator = "indicator-lights"
+
+/datum/prototype/robot_iconset/baseline_standard/security
+ icon_state = "security"
+
+/datum/prototype/robot_iconset/baseline_standard/medical
+ icon_state = "medical"
+
+/datum/prototype/robot_iconset/baseline_standard/engineering
+ icon_state = "engineering"
+
+/datum/prototype/robot_iconset/baseline_standard/janitor
+ icon_state = "janitor"
+
+/datum/prototype/robot_iconset/baseline_standard/service
+ display_name = /datum/prototype/robot_iconset/baseline_standard::display_name + " (Service)"
+ icon_state = "service"
+
+/datum/prototype/robot_iconset/baseline_standard/logistics
+ icon_state = "logistics"
+
+/datum/prototype/robot_iconset/baseline_standard/clerical
+ display_name = /datum/prototype/robot_iconset/baseline_standard::display_name + " (Clerical)"
+ icon_state = "clerical"
+
+/datum/prototype/robot_iconset/baseline_standard/standard
+ icon_state = "standard"
diff --git a/code/modules/mob/living/silicon/robot/iconset/baseline_toiletbot.dm b/code/modules/mob/living/silicon/robot/iconset/baseline_toiletbot.dm
new file mode 100644
index 00000000000..2a40517b0d1
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/baseline_toiletbot.dm
@@ -0,0 +1,47 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/baseline_toiletbot
+ display_name = "Telemachus"
+ abstract_type = /datum/prototype/robot_iconset/baseline_toiletbot
+ auto_chassis = /datum/prototype/robot_chassis/baseline
+ icon = 'icons/mob/robot/iconset/baseline_toiletbot.dmi'
+ icon_state_cover = "panel"
+ icon_state_indicator = "indicator-lighting-utility"
+
+/datum/prototype/robot_iconset/baseline_toiletbot/standard
+ icon_state = "standard"
+
+/datum/prototype/robot_iconset/baseline_toiletbot/logistics
+ icon_state = "logistics"
+ icon_state_indicator = "indicator-lighting-industrial"
+
+/datum/prototype/robot_iconset/baseline_toiletbot/medical
+ icon_state = "medical"
+ icon_state_indicator = "indicator-lighting-medical"
+
+/datum/prototype/robot_iconset/baseline_toiletbot/surgeon
+ icon_state = "surgeon"
+ icon_state_indicator = "indicator-lighting-medical"
+
+/datum/prototype/robot_iconset/baseline_toiletbot/security
+ icon_state = "security"
+
+/datum/prototype/robot_iconset/baseline_toiletbot/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "indicator-lighting-industrial"
+
+/datum/prototype/robot_iconset/baseline_toiletbot/janitor
+ icon_state = "janitor"
+
+/datum/prototype/robot_iconset/baseline_toiletbot/antag
+ icon_state = "antag"
+ icon_state_indicator = "indicator-lighting-antag"
+
+/datum/prototype/robot_iconset/baseline_toiletbot/syndicate
+ icon_state = "syndicate"
+ icon_state_indicator = "indicator-lighting-syndicate"
+
+/datum/prototype/robot_iconset/baseline_toiletbot/ninja
+ icon_state = "ninja"
+ icon_state_indicator = "indicator-lighting-ninja"
diff --git a/code/modules/mob/living/silicon/robot/iconset/biped_droid.dm b/code/modules/mob/living/silicon/robot/iconset/biped_droid.dm
new file mode 100644
index 00000000000..d10dd977143
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/biped_droid.dm
@@ -0,0 +1,21 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/biped_droid
+ display_name = "Droid"
+ abstract_type = /datum/prototype/robot_iconset/biped_droid
+ auto_chassis = /datum/prototype/robot_chassis/biped
+ icon = 'icons/mob/robot/iconset/biped_droid.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/biped_droid/logistics
+ icon_state = "logistics"
+ icon_state_indicator = "indicator-lighting-logistics"
+
+/datum/prototype/robot_iconset/biped_droid/medical
+ icon_state = "medical"
+ icon_state_indicator = "indicator-lighting-medical"
+
+/datum/prototype/robot_iconset/biped_droid/science
+ icon_state = "science"
+ icon_state_indicator = "indicator-lighting-medical"
diff --git a/code/modules/mob/living/silicon/robot/iconset/biped_heavy.dm b/code/modules/mob/living/silicon/robot/iconset/biped_heavy.dm
new file mode 100644
index 00000000000..89b0c4bbb86
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/biped_heavy.dm
@@ -0,0 +1,41 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/biped_heavy
+ display_name = "XI-ALP"
+ abstract_type = /datum/prototype/robot_iconset/biped_heavy
+ auto_chassis = /datum/prototype/robot_chassis/biped
+ icon = 'icons/mob/robot/iconset/biped_heavy.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/biped_heavy/medical
+ icon_state = "medical"
+ icon_state_indicator = "indicator-lighting"
+
+/datum/prototype/robot_iconset/biped_heavy/service
+ icon_state = "service"
+ icon_state_indicator = "indicator-service"
+
+/datum/prototype/robot_iconset/biped_heavy/security
+ icon_state = "security"
+ icon_state_indicator = "indicator-security"
+
+/datum/prototype/robot_iconset/biped_heavy/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "indicator-engineering"
+
+/datum/prototype/robot_iconset/biped_heavy/miner
+ icon_state = "miner"
+ icon_state_indicator = "indicator-miner"
+
+/datum/prototype/robot_iconset/biped_heavy/research
+ icon_state = "research"
+ icon_state_indicator = "indicator-security"
+
+/datum/prototype/robot_iconset/biped_heavy/standard
+ icon_state = "standard"
+ icon_state_indicator = "indicator-lighting"
+
+/datum/prototype/robot_iconset/biped_heavy/antag
+ icon_state = "antag"
+ icon_state_indicator = "indicator-lighting-antag"
diff --git a/code/modules/mob/living/silicon/robot/iconset/biped_insekt.dm b/code/modules/mob/living/silicon/robot/iconset/biped_insekt.dm
new file mode 100644
index 00000000000..f36a040ab71
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/biped_insekt.dm
@@ -0,0 +1,29 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/biped_insekt
+ display_name = "Insekt"
+ abstract_type = /datum/prototype/robot_iconset/biped_insekt
+ auto_chassis = /datum/prototype/robot_chassis/biped
+ icon = 'icons/mob/robot/iconset/biped_insekt.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/biped_insekt/standard
+ icon_state = "standard"
+ icon_state_indicator = "standard-indicator"
+
+/datum/prototype/robot_iconset/biped_insekt/security
+ icon_state = "security"
+ icon_state_indicator = "security-indicator"
+
+/datum/prototype/robot_iconset/biped_insekt/medical
+ icon_state = "medical"
+ icon_state_indicator = "medical-indicator"
+
+/datum/prototype/robot_iconset/biped_insekt/combat
+ icon_state = "combat"
+ icon_state_indicator = "combat-indicator"
+
+/datum/prototype/robot_iconset/biped_insekt/science
+ icon_state = "science"
+ icon_state_indicator = "science-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/biped_k4t.dm b/code/modules/mob/living/silicon/robot/iconset/biped_k4t.dm
new file mode 100644
index 00000000000..d2144f74a2f
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/biped_k4t.dm
@@ -0,0 +1,19 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+// Attributions //
+// All sprites used in this file made by Zydras //
+
+/datum/prototype/robot_iconset/biped_k4t
+ display_name = "K4T"
+ icon_state = "k4t"
+ icon_state_cover = "panel"
+ icon_state_indicator = "indicator"
+
+ auto_chassis = /datum/prototype/robot_chassis/biped
+
+ variations = list(
+ /datum/robot_iconset_variation/bellyup,
+ /datum/robot_iconset_variation/dead,
+ /datum/robot_iconset_variation/resting,
+ )
diff --git a/code/modules/mob/living/silicon/robot/iconset/biped_maid.dm b/code/modules/mob/living/silicon/robot/iconset/biped_maid.dm
new file mode 100644
index 00000000000..3c5ff0f32cb
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/biped_maid.dm
@@ -0,0 +1,11 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/biped_maid
+ display_name = "Maid"
+ abstract_type = /datum/prototype/robot_iconset/biped_maid
+ auto_chassis = /datum/prototype/robot_chassis/biped
+ icon = 'icons/mob/robot/iconset/biped_maid.dmi'
+ icon_state = "maid"
+ icon_state_cover = "panel"
+ icon_state_indicator = "indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/biped_marina.dm b/code/modules/mob/living/silicon/robot/iconset/biped_marina.dm
new file mode 100644
index 00000000000..9bb082f8cdc
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/biped_marina.dm
@@ -0,0 +1,45 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/biped_marina
+ display_name = "Haruka"
+ abstract_type = /datum/prototype/robot_iconset/biped_marina
+ auto_chassis = /datum/prototype/robot_chassis/biped
+ icon = 'icons/mob/robot/iconset/biped_marina.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/biped_marina/medical
+ icon_state = "medical"
+ icon_state_indicator = "indicator-medical"
+
+/datum/prototype/robot_iconset/biped_marina/service
+ icon_state = "service"
+ icon_state_indicator = "indicator-service"
+
+/datum/prototype/robot_iconset/biped_marina/miner
+ icon_state = "miner"
+ icon_state_indicator = "indicator-miner"
+
+/datum/prototype/robot_iconset/biped_marina/security
+ icon_state = "security"
+ icon_state_indicator = "indicator-security"
+
+/datum/prototype/robot_iconset/biped_marina/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "indicator-engineering"
+
+/datum/prototype/robot_iconset/biped_marina/janitor
+ icon_state = "janitor"
+ icon_state_indicator = "indicator-janitor"
+
+/datum/prototype/robot_iconset/biped_marina/standard
+ icon_state = "standard"
+ icon_state_indicator = "indicator-standard"
+
+/datum/prototype/robot_iconset/biped_marina/service
+ icon_state = "service"
+ icon_state_indicator = "indicator-service"
+
+/datum/prototype/robot_iconset/biped_marina/combat
+ icon_state = "combat"
+ icon_state_indicator = "indicator-combat"
diff --git a/code/modules/mob/living/silicon/robot/iconset/biped_meka.dm b/code/modules/mob/living/silicon/robot/iconset/biped_meka.dm
new file mode 100644
index 00000000000..3fd52b983d3
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/biped_meka.dm
@@ -0,0 +1,36 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+// Attributions //
+// All sprites used in this file made by GhostSheep/TheOOZ //
+
+/datum/prototype/robot_iconset/biped_meka
+ display_name = "Meka"
+ abstract_type = /datum/prototype/robot_iconset/biped_meka
+ auto_chassis = /datum/prototype/robot_chassis/biped
+
+ variations = list(
+ /datum/robot_iconset_variation/sitting,
+ /datum/robot_iconset_variation/dead,
+ /datum/robot_iconset_variation/resting,
+ )
+
+/datum/prototype/robot_iconset/biped_meka/one
+ icon_state = "one"
+ icon_state_indicator = "one-indicator"
+ icon_state_cover = "one-cell"
+
+/datum/prototype/robot_iconset/biped_meka/two
+ icon_state = "two"
+ icon_state_indicator = "two-indicator"
+ icon_state_cover = "two-cell"
+
+/datum/prototype/robot_iconset/biped_meka/three
+ icon_state = "three"
+ icon_state_indicator = "three-indicator"
+ icon_state_cover = "three-cell"
+
+/datum/prototype/robot_iconset/biped_meka/four
+ icon_state = "four"
+ icon_state_indicator = "four-indicator"
+ icon_state_cover = "four-cell"
diff --git a/code/modules/mob/living/silicon/robot/iconset/biped_miss.dm b/code/modules/mob/living/silicon/robot/iconset/biped_miss.dm
new file mode 100644
index 00000000000..d2250abdc6e
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/biped_miss.dm
@@ -0,0 +1,37 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/biped_miss
+ display_name = "Miss M"
+ abstract_type = /datum/prototype/robot_iconset/biped_miss
+ auto_chassis = /datum/prototype/robot_chassis/biped
+ icon = 'icons/mob/robot/iconset/biped_miss.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/biped_miss/standard
+ icon_state = "standard"
+ icon_state_indicator = "standard-indicator"
+
+/datum/prototype/robot_iconset/biped_miss/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "engineering-indicator"
+
+/datum/prototype/robot_iconset/biped_miss/medical
+ icon_state = "medical"
+ icon_state_indicator = "medical-indicator"
+
+/datum/prototype/robot_iconset/biped_miss/janitor
+ icon_state = "janitor"
+ icon_state_indicator = "janitor-indicator"
+
+/datum/prototype/robot_iconset/biped_miss/mining
+ icon_state = "mining"
+ icon_state_indicator = "mining-indicator"
+
+/datum/prototype/robot_iconset/biped_miss/service
+ icon_state = "service"
+ icon_state_indicator = "service-indicator"
+
+/datum/prototype/robot_iconset/biped_miss/security
+ icon_state = "security"
+ icon_state_indicator = "security-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/biped_noble.dm b/code/modules/mob/living/silicon/robot/iconset/biped_noble.dm
new file mode 100644
index 00000000000..8fe8313bb4b
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/biped_noble.dm
@@ -0,0 +1,37 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/biped_noble
+ display_name = "Shellguard Noble"
+ abstract_type = /datum/prototype/robot_iconset/biped_noble
+ auto_chassis = /datum/prototype/robot_chassis/biped
+ icon = 'icons/mob/robot/iconset/biped_noble.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/biped_noble/standard
+ icon_state = "standard"
+ icon_state_indicator = "standard-indicator"
+
+/datum/prototype/robot_iconset/biped_noble/janitor
+ icon_state = "janitor"
+ icon_state_indicator = "janitor-indicator"
+
+/datum/prototype/robot_iconset/biped_noble/mining
+ icon_state = "mining"
+ icon_state_indicator = "mining-indicator"
+
+/datum/prototype/robot_iconset/biped_noble/service
+ icon_state = "service"
+ icon_state_indicator = "service-indicator"
+
+/datum/prototype/robot_iconset/biped_noble/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "engineering-indicator"
+
+/datum/prototype/robot_iconset/biped_noble/medical
+ icon_state = "medical"
+ icon_state_indicator = "medical-indicator"
+
+/datum/prototype/robot_iconset/biped_noble/security
+ icon_state = "security"
+ icon_state_indicator = "security-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/biped_sleek.dm b/code/modules/mob/living/silicon/robot/iconset/biped_sleek.dm
new file mode 100644
index 00000000000..24ba3702fab
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/biped_sleek.dm
@@ -0,0 +1,52 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/biped_sleek
+ display_name = "Ward-Takashi Operator"
+ abstract_type = /datum/prototype/robot_iconset/biped_sleek
+ auto_chassis = /datum/prototype/robot_chassis/biped
+ icon = 'icons/mob/robot/iconset/biped_sleek.dmi'
+ icon_state_cover = "panel"
+ icon_state_indicator = "indicator"
+
+/datum/prototype/robot_iconset/biped_sleek/standard
+ icon_state = "standard"
+
+/datum/prototype/robot_iconset/biped_sleek/janitor
+ icon_state = "janitor"
+
+/datum/prototype/robot_iconset/biped_sleek/medical
+ display_name = /datum/prototype/robot_iconset/biped_sleek::display_name + " (Medical)"
+ icon_state = "medical"
+
+/datum/prototype/robot_iconset/biped_sleek/security
+ icon_state = "security"
+ icon_state_indicator = "indicator-security"
+
+/datum/prototype/robot_iconset/biped_sleek/mining
+ icon_state = "mining"
+
+/datum/prototype/robot_iconset/biped_sleek/service
+ icon_state = "service"
+
+/datum/prototype/robot_iconset/biped_sleek/science
+ icon_state = "science"
+
+/datum/prototype/robot_iconset/biped_sleek/clerical
+ icon_state = "clerical"
+
+/datum/prototype/robot_iconset/biped_sleek/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "indicator-commmand"
+
+/datum/prototype/robot_iconset/biped_sleek/cmo
+ display_name = /datum/prototype/robot_iconset/biped_sleek::display_name + " (CMO)"
+ icon_state = "cmo"
+ icon_state_indicator = "indicator-commmand"
+
+/datum/prototype/robot_iconset/biped_sleek/hos
+ icon_state = "hos"
+ icon_state_indicator = "indicator-security"
+
+/datum/prototype/robot_iconset/biped_sleek/gravekeeper
+ icon_state = "gravekeeper"
diff --git a/code/modules/mob/living/silicon/robot/iconset/biped_tall.dm b/code/modules/mob/living/silicon/robot/iconset/biped_tall.dm
new file mode 100644
index 00000000000..777e0802c50
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/biped_tall.dm
@@ -0,0 +1,73 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/biped_tall
+ display_name = "Usagi"
+ abstract_type = /datum/prototype/robot_iconset/biped_tall
+ auto_chassis = /datum/prototype/robot_chassis/biped
+ icon = 'icons/mob/robot/iconset/biped_tall.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/biped_tall/tallblue
+ icon_state = "tallblue"
+ icon_state_indicator = "tallblue-indicator"
+
+/datum/prototype/robot_iconset/biped_tall/tallyellow
+ icon_state = "tallyellow"
+ icon_state_indicator = "tallyellow-indicator"
+
+/datum/prototype/robot_iconset/biped_tall/tallflower
+ icon_state = "tallflower"
+ icon_state_indicator = "tallflower-indicator"
+
+/datum/prototype/robot_iconset/biped_tall/tallgreen
+ icon_state = "tallgreen"
+ icon_state_indicator = "tallgreen-indicator"
+
+/datum/prototype/robot_iconset/biped_tall/tallred
+ icon_state = "tallred"
+ icon_state_indicator = "tallred-indicator"
+
+/datum/prototype/robot_iconset/biped_tall/tallwhite
+ icon_state = "tallwhite"
+ icon_state_indicator = "tallwhite-indicator"
+
+/datum/prototype/robot_iconset/biped_tall/alternative
+ abstract_type = /datum/prototype/robot_iconset/biped_tall/alternative
+ display_name = "Misato"
+
+/datum/prototype/robot_iconset/biped_tall/alternative/miner
+ icon_state = "tall2miner"
+ icon_state_indicator = "tall2miner-indicator"
+
+/datum/prototype/robot_iconset/biped_tall/alternative/engineer
+ icon_state = "tall2engineer"
+ icon_state_indicator = "tall2engineer-indicator"
+
+/datum/prototype/robot_iconset/biped_tall/alternative/service
+ icon_state = "tall2service"
+ icon_state_indicator = "tall2service-indicator"
+
+/datum/prototype/robot_iconset/biped_tall/alternative/janitor
+ icon_state = "tall2janitor"
+ icon_state_indicator = "tall2janitor-indicator"
+
+/datum/prototype/robot_iconset/biped_tall/alternative/security
+ icon_state = "tall2security"
+ icon_state_indicator = "tall2security-indicator"
+
+/datum/prototype/robot_iconset/biped_tall/alternative/medical
+ icon_state = "tall2medical"
+ icon_state_indicator = "tall2medical-indicator"
+
+/datum/prototype/robot_iconset/biped_tall/alternative/standard
+ icon_state = "tall2standard"
+ icon_state_indicator = "tall2standard-indicator"
+
+/datum/prototype/robot_iconset/biped_tall/alternative/peace
+ icon_state = "tall2peace"
+ icon_state_indicator = "tall2peace-indicator"
+
+/datum/prototype/robot_iconset/biped_tall/alternative/clown
+ icon_state = "tall2clown"
+ icon_state_indicator = "tall2clown-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/cat_feli.dm b/code/modules/mob/living/silicon/robot/iconset/cat_feli.dm
new file mode 100644
index 00000000000..e4383b2afe1
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/cat_feli.dm
@@ -0,0 +1,46 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/cat_feli
+ display_name = "F3-LINE"
+ abstract_type = /datum/prototype/robot_iconset/cat_feli
+ auto_chassis = /datum/prototype/robot_chassis/quadruped/feline
+ icon = 'icons/mob/robot/iconset/cat_feli.dmi'
+ icon_state_cover = "panel"
+ icon_dimension_x = 64
+
+/datum/prototype/robot_iconset/cat_feli/standard
+ icon_state = "standard"
+ icon_state_indicator = "standard-indicator"
+
+/datum/prototype/robot_iconset/cat_feli/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "engineering-indicator"
+
+/datum/prototype/robot_iconset/cat_feli/medical
+ icon_state = "medical"
+ icon_state_indicator = "medical-indicator"
+
+/datum/prototype/robot_iconset/cat_feli/research
+ icon_state = "research"
+ icon_state_indicator = "research-indicator"
+
+/datum/prototype/robot_iconset/cat_feli/mining
+ icon_state = "mining"
+ icon_state_indicator = "mining-indicator"
+
+/datum/prototype/robot_iconset/cat_feli/service
+ icon_state = "service"
+ icon_state_indicator = "service-indicator"
+
+/datum/prototype/robot_iconset/cat_feli/janitor
+ icon_state = "janitor"
+ icon_state_indicator = "janitor-indicator"
+
+/datum/prototype/robot_iconset/cat_feli/security
+ icon_state = "security"
+ icon_state_indicator = "security-indicator"
+
+/datum/prototype/robot_iconset/cat_feli/combat
+ icon_state = "combat"
+ icon_state_indicator = "combat-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/dog_borgi.dm b/code/modules/mob/living/silicon/robot/iconset/dog_borgi.dm
new file mode 100644
index 00000000000..0b266fca708
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/dog_borgi.dm
@@ -0,0 +1,35 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/dog_borgi
+ display_name = "Borgi"
+ abstract_type = /datum/prototype/robot_iconset/dog_borgi
+ auto_chassis = /datum/prototype/robot_chassis/quadruped/canine
+ icon = 'icons/mob/robot/iconset/dog_borgi.dmi'
+ icon_state_cover = "panel"
+ icon_state_indicator = "indicator"
+ icon_dimension_x = 64
+ variations = list(
+ /datum/robot_iconset_variation/bellyup,
+ /datum/robot_iconset_variation/resting,
+ /datum/robot_iconset_variation/sitting,
+ /datum/robot_iconset_variation/dead,
+ )
+
+/datum/prototype/robot_iconset/dog_borgi/standard
+ icon_state = "standard"
+
+/datum/prototype/robot_iconset/dog_borgi/engineering
+ icon_state = "engineering"
+
+/datum/prototype/robot_iconset/dog_borgi/janitor
+ icon_state = "janitor"
+
+/datum/prototype/robot_iconset/dog_borgi/medical
+ icon_state = "medical"
+
+/datum/prototype/robot_iconset/dog_borgi/science
+ icon_state = "science"
+
+/datum/prototype/robot_iconset/dog_borgi/security
+ icon_state = "security"
diff --git a/code/modules/mob/living/silicon/robot/iconset/dog_k9.dm b/code/modules/mob/living/silicon/robot/iconset/dog_k9.dm
new file mode 100644
index 00000000000..a1fbf7370f0
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/dog_k9.dm
@@ -0,0 +1,78 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/dog_k9
+ display_name = "K9"
+ abstract_type = /datum/prototype/robot_iconset/dog_k9
+
+ auto_chassis = /datum/prototype/robot_chassis/quadruped/canine
+
+ icon = 'icons/mob/robot/iconset/dog_k9.dmi'
+ icon_state_cover = "panel"
+ icon_dimension_x = 64
+ variations = list(
+ /datum/robot_iconset_variation/bellyup,
+ /datum/robot_iconset_variation/resting,
+ /datum/robot_iconset_variation/dead,
+ /datum/robot_iconset_variation/sitting,
+ )
+
+/datum/prototype/robot_iconset/dog_k9/security
+ icon_state = "security"
+ icon_state_indicator = "security-indicator"
+
+/datum/prototype/robot_iconset/dog_k9/medical
+ icon_state = "medical"
+ icon_state_indicator = "medical-indicator"
+
+/datum/prototype/robot_iconset/dog_k9/pink
+ icon_state = "pink"
+ icon_state_indicator = "pink-indicator"
+
+/datum/prototype/robot_iconset/dog_k9/red
+ icon_state = "red"
+ icon_state_indicator = "red-indicator"
+
+/datum/prototype/robot_iconset/dog_k9/medical_dark
+ display_name = /datum/prototype/robot_iconset/dog_k9::display_name + " (Dark)"
+ icon_state = "medical_dark"
+ icon_state_indicator = "medical_dark-indicator"
+
+/datum/prototype/robot_iconset/dog_k9/science_dark
+ display_name = /datum/prototype/robot_iconset/dog_k9::display_name + " (Dark)"
+ icon_state = "science_dark"
+ icon_state_indicator = "science_dark-indicator"
+
+/datum/prototype/robot_iconset/dog_k9/science
+ icon_state = "science"
+ icon_state_indicator = "science-indicator"
+
+/datum/prototype/robot_iconset/dog_k9/engineering_dark
+ display_name = /datum/prototype/robot_iconset/dog_k9::display_name + " (Dark)"
+ icon_state = "engineering_dark"
+ icon_state_indicator = "engineering_dark-indicator"
+
+/datum/prototype/robot_iconset/dog_k9/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "engineering-indicator"
+
+/datum/prototype/robot_iconset/dog_k9/logistics_dark
+ display_name = /datum/prototype/robot_iconset/dog_k9::display_name + " (Dark)"
+ icon_state = "logistics_dark"
+ icon_state_indicator = "logistics_dark-indicator"
+
+/datum/prototype/robot_iconset/dog_k9/logistics
+ icon_state = "logistics"
+ icon_state_indicator = "logistics-indicator"
+
+/datum/prototype/robot_iconset/dog_k9/grey
+ icon_state = "grey"
+ icon_state_indicator = "grey-indicator"
+
+/datum/prototype/robot_iconset/dog_k9/janitor
+ icon_state = "janitor"
+ icon_state_indicator = "janitor-indicator"
+
+/datum/prototype/robot_iconset/dog_k9/blade
+ icon_state = "blade"
+ icon_state_indicator = "blade-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/dog_otie.dm b/code/modules/mob/living/silicon/robot/iconset/dog_otie.dm
new file mode 100644
index 00000000000..bb8e2c51c02
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/dog_otie.dm
@@ -0,0 +1,32 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/dog_otie
+ display_name = "Otie"
+ abstract_type = /datum/prototype/robot_iconset/dog_otie
+ auto_chassis = /datum/prototype/robot_chassis/quadruped/canine
+ icon = 'icons/mob/robot/iconset/dog_otie.dmi'
+ icon_state_cover = "panel"
+ icon_dimension_x = 64
+ variations = list(
+ /datum/robot_iconset_variation/dead,
+ /datum/robot_iconset_variation/resting,
+ /datum/robot_iconset_variation/sitting,
+ /datum/robot_iconset_variation/bellyup,
+ )
+
+/datum/prototype/robot_iconset/dog_otie/janitor
+ icon_state = "janitor"
+ icon_state_indicator = "janitor-indicator"
+
+/datum/prototype/robot_iconset/dog_otie/security
+ icon_state = "security"
+ icon_state_indicator = "security-indicator"
+
+/datum/prototype/robot_iconset/dog_otie/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "engineering-indicator"
+
+/datum/prototype/robot_iconset/dog_otie/science
+ icon_state = "science"
+ icon_state_indicator = "science-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/dog_pupdozer.dm b/code/modules/mob/living/silicon/robot/iconset/dog_pupdozer.dm
new file mode 100644
index 00000000000..c193979fcae
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/dog_pupdozer.dm
@@ -0,0 +1,22 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/dog_pupdozer
+ display_name = "Pupdozer"
+ abstract_type = /datum/prototype/robot_iconset/dog_pupdozer
+ auto_chassis = /datum/prototype/robot_chassis/quadruped/canine
+ icon = 'icons/mob/robot/iconset/dog_pupdozer.dmi'
+ icon_state_cover = "panel"
+ icon_state_indicator = "indicator"
+ icon_dimension_x = 64
+ variations = list(
+ /datum/robot_iconset_variation/dead,
+ /datum/robot_iconset_variation/resting,
+ /datum/robot_iconset_variation/sitting,
+ )
+
+/datum/prototype/robot_iconset/dog_pupdozer/engineering
+ icon_state = "engineering"
+
+/datum/prototype/robot_iconset/dog_pupdozer/security
+ icon_state = "security"
diff --git a/code/modules/mob/living/silicon/robot/iconset/dog_vale.dm b/code/modules/mob/living/silicon/robot/iconset/dog_vale.dm
new file mode 100644
index 00000000000..c40ddbe024c
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/dog_vale.dm
@@ -0,0 +1,66 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/dog_vale
+ display_name = "Vale"
+ abstract_type = /datum/prototype/robot_iconset/dog_vale
+ auto_chassis = /datum/prototype/robot_chassis/quadruped/canine
+ icon = 'icons/mob/robot/iconset/dog_vale.dmi'
+ icon_state_cover = "panel"
+ icon_dimension_x = 64
+
+ variations = list(
+ /datum/robot_iconset_variation/bellyup,
+ /datum/robot_iconset_variation/sitting,
+ /datum/robot_iconset_variation/dead,
+ /datum/robot_iconset_variation/resting,
+ )
+
+/datum/prototype/robot_iconset/dog_vale/standard
+ icon_state = "standard"
+ icon_state_indicator = "standard-indicator"
+
+/datum/prototype/robot_iconset/dog_vale/medical
+ icon_state = "medical"
+ icon_state_indicator = "medical-indicator"
+
+/datum/prototype/robot_iconset/dog_vale/science
+ icon_state = "science"
+ icon_state_indicator = "science-indicator"
+
+/datum/prototype/robot_iconset/dog_vale/security
+ icon_state = "security"
+ icon_state_indicator = "security-indicator"
+
+/datum/prototype/robot_iconset/dog_vale/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "engineering-indicator"
+
+/datum/prototype/robot_iconset/dog_vale/mining
+ icon_state = "mining"
+ icon_state_indicator = "mining-indicator"
+
+/datum/prototype/robot_iconset/dog_vale/janitor
+ icon_state = "janitor"
+ icon_state_indicator = "janitor-indicator"
+
+/datum/prototype/robot_iconset/dog_vale/service
+ icon_state = "service"
+ icon_state_indicator = "service-indicator"
+
+/datum/prototype/robot_iconset/dog_vale/service_dark
+ display_name = /datum/prototype/robot_iconset/dog_vale::display_name + " (Dark)"
+ icon_state = "service_dark"
+ icon_state_indicator = "service_dark-indicator"
+
+/datum/prototype/robot_iconset/dog_vale/stray
+ icon_state = "stray"
+ icon_state_indicator = "stray-indicator"
+
+/datum/prototype/robot_iconset/dog_vale/syndicate
+ icon_state = "syndicate"
+ icon_state_indicator = "syndicate-indicator"
+
+/datum/prototype/robot_iconset/dog_vale/clown
+ icon_state = "clown"
+ icon_state_indicator = "clown-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/drake_mizartz.dm b/code/modules/mob/living/silicon/robot/iconset/drake_mizartz.dm
new file mode 100644
index 00000000000..68fc2dda173
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/drake_mizartz.dm
@@ -0,0 +1,50 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+// Attributions //
+// All sprites used in this file made by mizartz.carrd.co //
+
+/datum/prototype/robot_iconset/drake_mizartz
+ display_name = "Drake"
+ abstract_type = /datum/prototype/robot_iconset/drake_mizartz
+ auto_chassis = /datum/prototype/robot_chassis/quadruped/draconic
+
+ icon = 'icons/mob/robot/iconset/drake_mizartz.dmi'
+ icon_state_indicator = "indicator-lighting"
+ icon_state_cover = "open"
+
+ icon_dimension_x = 64
+ icon_dimension_y = 32
+
+ indicator_lighting_coloration_mode = COLORATION_MODE_MULTIPLY
+
+ variations = list(
+ /datum/robot_iconset_variation/dead,
+ /datum/robot_iconset_variation/resting,
+ /datum/robot_iconset_variation/sitting,
+ /datum/robot_iconset_variation/bellyup,
+ )
+
+/datum/prototype/robot_iconset/drake_mizartz/mining
+ icon_state = "mining"
+ indicator_lighting_coloration_packed = "#aa00ff"
+
+/datum/prototype/robot_iconset/drake_mizartz/medical
+ icon_state = "medical"
+ indicator_lighting_coloration_packed = "#00ffff"
+
+/datum/prototype/robot_iconset/drake_mizartz/janitor
+ icon_state = "janitor"
+ icon_state_indicator = "indicator-lighting-janitor"
+ indicator_lighting_coloration_mode = COLORATION_MODE_NONE
+
+/datum/prototype/robot_iconset/drake_mizartz/security
+ icon_state = "security"
+ indicator_lighting_coloration_packed = "#ff0000"
+
+/datum/prototype/robot_iconset/drake_mizartz/engineering
+ icon_state = "engineering"
+ indicator_lighting_coloration_packed = "#ffff00"
+
+/datum/prototype/robot_iconset/drake_mizartz/peacekeeper
+ icon_state = "peacekeeper"
diff --git a/code/modules/mob/living/silicon/robot/iconset/grounded_decapod.dm b/code/modules/mob/living/silicon/robot/iconset/grounded_decapod.dm
new file mode 100644
index 00000000000..48b823193bd
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/grounded_decapod.dm
@@ -0,0 +1,53 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/grounded_decapod
+ display_name = "Decapod"
+ abstract_type = /datum/prototype/robot_iconset/grounded_decapod
+ auto_chassis = /datum/prototype/robot_chassis/grounded
+ icon = 'icons/mob/robot/iconset/grounded_decapod.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/grounded_decapod/standard
+ icon_state = "standard"
+ icon_state_indicator = "standard-indicator"
+
+/datum/prototype/robot_iconset/grounded_decapod/research
+ icon_state = "research"
+ icon_state_indicator = "research-indicator"
+
+/datum/prototype/robot_iconset/grounded_decapod/surgeon
+ icon_state = "surgeon"
+ icon_state_indicator = "surgeon-indicator"
+
+/datum/prototype/robot_iconset/grounded_decapod/crisis
+ icon_state = "crisis"
+ icon_state_indicator = "crisis-indicator"
+
+/datum/prototype/robot_iconset/grounded_decapod/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "engineering-indicator"
+
+/datum/prototype/robot_iconset/grounded_decapod/janitor
+ icon_state = "janitor"
+ icon_state_indicator = "janitor-indicator"
+
+/datum/prototype/robot_iconset/grounded_decapod/service
+ icon_state = "service"
+ icon_state_indicator = "service-indicator"
+
+/datum/prototype/robot_iconset/grounded_decapod/clerical
+ icon_state = "clerical"
+ icon_state_indicator = "clerical-indicator"
+
+/datum/prototype/robot_iconset/grounded_decapod/combat
+ icon_state = "combat"
+ icon_state_indicator = "combat-indicator"
+
+/datum/prototype/robot_iconset/grounded_decapod/mining
+ icon_state = "mining"
+ icon_state_indicator = "mining-indicator"
+
+/datum/prototype/robot_iconset/grounded_decapod/security
+ icon_state = "security"
+ icon_state_indicator = "security-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/grounded_landmate.dm b/code/modules/mob/living/silicon/robot/iconset/grounded_landmate.dm
new file mode 100644
index 00000000000..d8cf51761f3
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/grounded_landmate.dm
@@ -0,0 +1,70 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/grounded_landmate
+ display_name = "Landmate"
+ abstract_type = /datum/prototype/robot_iconset/grounded_landmate
+ auto_chassis = /datum/prototype/robot_chassis/grounded
+ icon = 'icons/mob/robot/iconset/grounded_landmate.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/grounded_landmate/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "engineering-indicator"
+
+/datum/prototype/robot_iconset/grounded_landmate/engineering_tread
+ display_name = "Landmate (Treaded)"
+ icon_state = "engineering_tread"
+ icon_state_indicator = "engineering_tread-indicator"
+
+/datum/prototype/robot_iconset/grounded_landmate/treadwell
+ display_name = "Treadwell"
+ icon_state = "treadwell"
+ icon_state_indicator = "treadwell-indicator"
+
+/datum/prototype/robot_iconset/grounded_landmate/janitor
+ icon_state = "janitor"
+ icon_state_indicator = "janitor-indicator"
+
+/datum/prototype/robot_iconset/grounded_landmate/mining
+ display_name = "Treadhead"
+ icon_state = "mining"
+ icon_state_indicator = "mining-indicator"
+
+/datum/prototype/robot_iconset/grounded_landmate/peacekeeper
+ icon_state = "peacekeeper"
+ icon_state_indicator = "peacekeeper-indicator"
+
+/datum/prototype/robot_iconset/grounded_landmate/clown
+ icon_state = "clown"
+ icon_state_indicator = "peacekeeper-indicator"
+
+/datum/prototype/robot_iconset/grounded_landmate/security
+ display_name = "Cerberus"
+ icon_state = "security"
+ icon_state_indicator = "security-indicator"
+
+/datum/prototype/robot_iconset/grounded_landmate/security_tread
+ display_name = "Cerberus (Treaded)"
+ icon_state = "security_tread"
+ icon_state_indicator = "security-indicator"
+
+/datum/prototype/robot_iconset/grounded_landmate/engineering_cybersun
+ icon_state = "engineering_cybersun"
+ icon_state_indicator = "engineering_cybersun-indicator"
+
+/datum/prototype/robot_iconset/grounded_landmate/engineering_cybersun_green
+ icon_state = "engineering_cybersun_green"
+ icon_state_indicator = "engineering_cybersun_green-indicator"
+
+/datum/prototype/robot_iconset/grounded_landmate/security_cybersun
+ icon_state = "security_cybersun"
+ icon_state_indicator = "security_cybersun-indicator"
+
+/datum/prototype/robot_iconset/grounded_landmate/security_cybersun_tread
+ icon_state = "security_cybersun_tread"
+ icon_state_indicator = "security_cybersun-indicator"
+
+/datum/prototype/robot_iconset/grounded_landmate/security_cybersun_green
+ icon_state = "security_cybersun_green"
+ icon_state_indicator = "security_cybersun_green-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/grounded_mechoid.dm b/code/modules/mob/living/silicon/robot/iconset/grounded_mechoid.dm
new file mode 100644
index 00000000000..c4a0fda4367
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/grounded_mechoid.dm
@@ -0,0 +1,45 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/grounded_mechoid
+ display_name = "Acheron"
+ abstract_type = /datum/prototype/robot_iconset/grounded_mechoid
+ auto_chassis = /datum/prototype/robot_chassis/grounded
+ icon = 'icons/mob/robot/iconset/grounded_mechoid.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/grounded_mechoid/standard
+ icon_state = "standard"
+ icon_state_indicator = "standard-indicator"
+
+/datum/prototype/robot_iconset/grounded_mechoid/combat
+ icon_state = "combat"
+ icon_state_indicator = "combat-indicator"
+
+/datum/prototype/robot_iconset/grounded_mechoid/science
+ icon_state = "science"
+ icon_state_indicator = "science-indicator"
+
+/datum/prototype/robot_iconset/grounded_mechoid/janitor
+ icon_state = "janitor"
+ icon_state_indicator = "janitor-indicator"
+
+/datum/prototype/robot_iconset/grounded_mechoid/security
+ icon_state = "security"
+ icon_state_indicator = "security-indicator"
+
+/datum/prototype/robot_iconset/grounded_mechoid/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "engineering-indicator"
+
+/datum/prototype/robot_iconset/grounded_mechoid/medical
+ icon_state = "medical"
+ icon_state_indicator = "medical-indicator"
+
+/datum/prototype/robot_iconset/grounded_mechoid/miner
+ icon_state = "miner"
+ icon_state_indicator = "miner-indicator"
+
+/datum/prototype/robot_iconset/grounded_mechoid/service
+ icon_state = "service"
+ icon_state_indicator = "service-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/grounded_spider.dm b/code/modules/mob/living/silicon/robot/iconset/grounded_spider.dm
new file mode 100644
index 00000000000..09a853961e5
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/grounded_spider.dm
@@ -0,0 +1,37 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/grounded_spider
+ display_name = "XI-GUS"
+ abstract_type = /datum/prototype/robot_iconset/grounded_spider
+ auto_chassis = /datum/prototype/robot_chassis/grounded
+ icon = 'icons/mob/robot/iconset/grounded_spider.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/grounded_spider/standard
+ icon_state = "standard"
+ icon_state_indicator = "standard-indicator"
+
+/datum/prototype/robot_iconset/grounded_spider/combat
+ icon_state = "combat"
+ icon_state_indicator = "combat-indicator"
+
+/datum/prototype/robot_iconset/grounded_spider/command
+ icon_state = "command"
+ icon_state_indicator = "command-indicator"
+
+/datum/prototype/robot_iconset/grounded_spider/science
+ icon_state = "science"
+ icon_state_indicator = "science-indicator"
+
+/datum/prototype/robot_iconset/grounded_spider/mining
+ icon_state = "mining"
+ icon_state_indicator = "mining-indicator"
+
+/datum/prototype/robot_iconset/grounded_spider/security
+ icon_state = "security"
+ icon_state_indicator = "security-indicator"
+
+/datum/prototype/robot_iconset/grounded_spider/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "engineering-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/grounded_worm.dm b/code/modules/mob/living/silicon/robot/iconset/grounded_worm.dm
new file mode 100644
index 00000000000..ba650f654ef
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/grounded_worm.dm
@@ -0,0 +1,37 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/grounded_worm
+ display_name = "W02M"
+ abstract_type = /datum/prototype/robot_iconset/grounded_worm
+
+ auto_chassis = /datum/prototype/robot_chassis/grounded
+
+ icon = 'icons/mob/robot/iconset/grounded_worm.dmi'
+ icon_state_indicator = "indicator-lighting"
+ icon_state_cover = "open"
+
+/datum/prototype/robot_iconset/grounded_worm/standard
+ icon_state = "worm-standard"
+
+/datum/prototype/robot_iconset/grounded_worm/engineering
+ icon_state = "worm-engineering"
+
+/datum/prototype/robot_iconset/grounded_worm/janitor
+ icon_state = "worm-janitor"
+
+/datum/prototype/robot_iconset/grounded_worm/crisis
+ icon_state = "worm-crisis"
+
+/datum/prototype/robot_iconset/grounded_worm/miner
+ icon_state = "worm-miner"
+
+/datum/prototype/robot_iconset/grounded_worm/security
+ icon_state = "worm-security"
+
+/datum/prototype/robot_iconset/grounded_worm/service
+ icon_state = "worm-service"
+
+/datum/prototype/robot_iconset/grounded_worm/hotpink
+ icon_state = "worm-hotpink"
+
diff --git a/code/modules/mob/living/silicon/robot/iconset/grounded_zoomba.dm b/code/modules/mob/living/silicon/robot/iconset/grounded_zoomba.dm
new file mode 100644
index 00000000000..54838810b14
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/grounded_zoomba.dm
@@ -0,0 +1,61 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/grounded_zoomba
+ display_name = "ZOOM-BA"
+ abstract_type = /datum/prototype/robot_iconset/grounded_zoomba
+
+ auto_chassis = /datum/prototype/robot_chassis/grounded
+
+ icon = 'icons/mob/robot/iconset/grounded_zoomba.dmi'
+ icon_state_indicator = "indicator-lighting"
+ icon_state_cover = "cover"
+ indicator_lighting_coloration_mode = COLORATION_MODE_MULTIPLY
+
+ variations = list(
+ /datum/robot_iconset_variation/dead,
+ )
+
+/datum/prototype/robot_iconset/grounded_zoomba/standard
+ icon_state = "standard"
+
+/datum/prototype/robot_iconset/grounded_zoomba/clerical
+ icon_state = "clerical"
+ indicator_lighting_coloration_packed = "#00ff6e"
+
+/datum/prototype/robot_iconset/grounded_zoomba/engineering
+ icon_state = "engineering"
+ indicator_lighting_coloration_packed = "#ffae00"
+
+/datum/prototype/robot_iconset/grounded_zoomba/janitor
+ icon_state = "janitor"
+ indicator_lighting_coloration_packed = "#cc00ff"
+
+/datum/prototype/robot_iconset/grounded_zoomba/medical
+ icon_state = "medical"
+ indicator_lighting_coloration_packed = "#00ffff"
+
+/datum/prototype/robot_iconset/grounded_zoomba/crisis
+ icon_state = "crisis"
+ indicator_lighting_coloration_packed = "#00ffff"
+
+/datum/prototype/robot_iconset/grounded_zoomba/miner
+ icon_state = "miner"
+ indicator_lighting_coloration_packed = "#9f561f"
+
+/datum/prototype/robot_iconset/grounded_zoomba/research
+ icon_state = "research"
+ indicator_lighting_coloration_packed = "#8800ff"
+
+/datum/prototype/robot_iconset/grounded_zoomba/service
+ icon_state = "service"
+ indicator_lighting_coloration_packed = "#16b102"
+
+/datum/prototype/robot_iconset/grounded_zoomba/combat
+ icon_state = "combat"
+ indicator_lighting_coloration_packed = "#ff0000"
+
+/datum/prototype/robot_iconset/grounded_zoomba/security
+ icon_state = "security"
+ variations = list()
+ indicator_lighting_coloration_packed = "#ff0000"
diff --git a/code/modules/mob/living/silicon/robot/iconset/hover_coffin.dm b/code/modules/mob/living/silicon/robot/iconset/hover_coffin.dm
new file mode 100644
index 00000000000..af943425020
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/hover_coffin.dm
@@ -0,0 +1,48 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/hover_coffin
+ display_name = "Coffin"
+ abstract_type = /datum/prototype/robot_iconset/hover_coffin
+ auto_chassis = /datum/prototype/robot_chassis/hover
+ icon = 'icons/mob/robot/iconset/hover_coffin.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/hover_coffin/standard
+ icon_state = "standard"
+
+/datum/prototype/robot_iconset/hover_coffin/medical
+ display_name = "Coffical"
+ icon_state = "medical"
+
+/datum/prototype/robot_iconset/hover_coffin/syndicate
+ icon_state = "syndicate"
+
+/datum/prototype/robot_iconset/hover_coffin/custodial
+ display_name = "Coffstodial"
+ icon_state = "custodial"
+
+/datum/prototype/robot_iconset/hover_coffin/service
+ icon_state = "service"
+
+/datum/prototype/robot_iconset/hover_coffin/mining
+ icon_state = "mining"
+
+/datum/prototype/robot_iconset/hover_coffin/research
+ display_name = "Coffsearch"
+ icon_state = "research"
+
+/datum/prototype/robot_iconset/hover_coffin/clerical
+ icon_state = "clerical"
+
+/datum/prototype/robot_iconset/hover_coffin/construction
+ display_name = "Coffstruction"
+ icon_state = "construction"
+
+/datum/prototype/robot_iconset/hover_coffin/engineering
+ display_name = "Coffgineer"
+ icon_state = "engineering"
+
+/datum/prototype/robot_iconset/hover_coffin/combat
+ display_name = "Coffcurity"
+ icon_state = "combat"
diff --git a/code/modules/mob/living/silicon/robot/iconset/hover_drone.dm b/code/modules/mob/living/silicon/robot/iconset/hover_drone.dm
new file mode 100644
index 00000000000..65267a04906
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/hover_drone.dm
@@ -0,0 +1,69 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/hover_drone
+ display_name = "Drone"
+ abstract_type = /datum/prototype/robot_iconset/hover_drone
+ auto_chassis = /datum/prototype/robot_chassis/hover
+ icon = 'icons/mob/robot/iconset/hover_drone.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/hover_drone/standard
+ icon_state = "standard"
+ icon_state_indicator = "standard-indicator"
+
+/datum/prototype/robot_iconset/hover_drone/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "engineering-indicator"
+
+/datum/prototype/robot_iconset/hover_drone/mining
+ icon_state = "mining"
+ icon_state_indicator = "mining-indicator"
+
+/datum/prototype/robot_iconset/hover_drone/janitor
+ icon_state = "janitor"
+ icon_state_indicator = "janitor-indicator"
+
+/datum/prototype/robot_iconset/hover_drone/medical
+ display_name = /datum/prototype/robot_iconset/hover_drone::display_name + " (Medical)"
+ icon_state = "medical"
+ icon_state_indicator = "medical-indicator"
+
+/datum/prototype/robot_iconset/hover_drone/surgery
+ display_name = /datum/prototype/robot_iconset/hover_drone::display_name + " (Surgery)"
+ icon_state = "surgery"
+ icon_state_indicator = "surgery-indicator"
+
+/datum/prototype/robot_iconset/hover_drone/security
+ icon_state = "security"
+ icon_state_indicator = "security-indicator"
+
+/datum/prototype/robot_iconset/hover_drone/service
+ display_name = /datum/prototype/robot_iconset/hover_drone::display_name + " (Service)"
+ icon_state = "service"
+ icon_state_indicator = "service-indicator"
+
+/datum/prototype/robot_iconset/hover_drone/lost
+ icon_state = "lost"
+ icon_state_indicator = "lost-indicator"
+
+/datum/prototype/robot_iconset/hover_drone/chemistry
+ icon_state = "chemistry"
+ icon_state_indicator = "chemistry-indicator"
+
+/datum/prototype/robot_iconset/hover_drone/hydroponics
+ display_name = /datum/prototype/robot_iconset/hover_drone::display_name + " (Hydroponics)"
+ icon_state = "hydroponics"
+ icon_state_indicator = "hydroponics-indicator"
+
+/datum/prototype/robot_iconset/hover_drone/science
+ icon_state = "science"
+ icon_state_indicator = "science-indicator"
+
+/datum/prototype/robot_iconset/hover_drone/blueshield
+ icon_state = "blueshield"
+ icon_state_indicator = "blueshield-indicator"
+
+/datum/prototype/robot_iconset/hover_drone/gravekeeper
+ icon_state = "gravekeeper"
+ icon_state_indicator = "gravekeeper-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/hover_eyebot.dm b/code/modules/mob/living/silicon/robot/iconset/hover_eyebot.dm
new file mode 100644
index 00000000000..0edb9d63620
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/hover_eyebot.dm
@@ -0,0 +1,41 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/hover_eyebot
+ display_name = "Cabeiri"
+ abstract_type = /datum/prototype/robot_iconset/hover_eyebot
+ auto_chassis = /datum/prototype/robot_chassis/hover
+ icon = 'icons/mob/robot/iconset/hover_eyebot.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/hover_eyebot/standard
+ icon_state = "standard"
+ icon_state_indicator = "standard-indicator"
+
+/datum/prototype/robot_iconset/hover_eyebot/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "engineering-indicator"
+
+/datum/prototype/robot_iconset/hover_eyebot/janitor
+ icon_state = "janitor"
+ icon_state_indicator = "janitor-indicator"
+
+/datum/prototype/robot_iconset/hover_eyebot/medical
+ icon_state = "medical"
+ icon_state_indicator = "medical-indicator"
+
+/datum/prototype/robot_iconset/hover_eyebot/science
+ icon_state = "science"
+ icon_state_indicator = "science-indicator"
+
+/datum/prototype/robot_iconset/hover_eyebot/mining
+ icon_state = "mining"
+ icon_state_indicator = "mining-indicator"
+
+/datum/prototype/robot_iconset/hover_eyebot/security
+ icon_state = "security"
+ icon_state_indicator = "security-indicator"
+
+/datum/prototype/robot_iconset/hover_eyebot/combat
+ icon_state = "combat"
+ icon_state_indicator = "combat-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/hover_glitterfly.dm b/code/modules/mob/living/silicon/robot/iconset/hover_glitterfly.dm
new file mode 100644
index 00000000000..9e0505aeaaf
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/hover_glitterfly.dm
@@ -0,0 +1,50 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/hover_glitterfly
+ display_name = "L3P1-D0T"
+ abstract_type = /datum/prototype/robot_iconset/hover_glitterfly
+ auto_chassis = /datum/prototype/robot_chassis/hover
+ icon = 'icons/mob/robot/iconset/hover_glitterfly.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/hover_glitterfly/standard
+ icon_state = "standard"
+ icon_state_indicator = "standard-indicator"
+
+/datum/prototype/robot_iconset/hover_glitterfly/janitor
+ icon_state = "janitor"
+ icon_state_indicator = "janitor-indicator"
+
+/datum/prototype/robot_iconset/hover_glitterfly/service
+ icon_state = "service"
+ icon_state_indicator = "service-indicator"
+
+/datum/prototype/robot_iconset/hover_glitterfly/surgeon
+ icon_state = "surgeon"
+ icon_state_indicator = "surgeon-indicator"
+
+/datum/prototype/robot_iconset/hover_glitterfly/crisis
+ icon_state = "crisis"
+ icon_state_indicator = "crisis-indicator"
+
+/datum/prototype/robot_iconset/hover_glitterfly/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "engineering-indicator"
+
+/datum/prototype/robot_iconset/hover_glitterfly/security
+ icon_state = "security"
+ icon_state_indicator = "security-indicator"
+
+/datum/prototype/robot_iconset/hover_glitterfly/clerical
+ display_name = /datum/prototype/robot_iconset/hover_glitterfly::display_name + " (Clerical)"
+ icon_state = "clerical"
+ icon_state_indicator = "clerical-indicator"
+
+/datum/prototype/robot_iconset/hover_glitterfly/science
+ icon_state = "science"
+ icon_state_indicator = "science-indicator"
+
+/datum/prototype/robot_iconset/hover_glitterfly/mining
+ icon_state = "mining"
+ icon_state_indicator = "mining-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/hover_handy.dm b/code/modules/mob/living/silicon/robot/iconset/hover_handy.dm
new file mode 100644
index 00000000000..eefc7263b00
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/hover_handy.dm
@@ -0,0 +1,51 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/hover_handy
+ display_name = "Handy"
+ abstract_type = /datum/prototype/robot_iconset/hover_handy
+ auto_chassis = /datum/prototype/robot_chassis/hover
+ icon = 'icons/mob/robot/iconset/hover_handy.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/hover_handy/standard
+ icon_state = "standard"
+ icon_state_indicator = "standard-indicator"
+
+/datum/prototype/robot_iconset/hover_handy/hydroponics
+ icon_state = "hydroponics"
+ icon_state_indicator = "hydroponics-indicator"
+
+/datum/prototype/robot_iconset/hover_handy/clerk
+ display_name = /datum/prototype/robot_iconset/hover_handy::display_name + " (Clerk)"
+ icon_state = "clerk"
+ icon_state_indicator = "clerk-indicator"
+
+/datum/prototype/robot_iconset/hover_handy/janitor
+ icon_state = "janitor"
+ icon_state_indicator = "janitor-indicator"
+
+/datum/prototype/robot_iconset/hover_handy/service
+ display_name = /datum/prototype/robot_iconset/hover_handy::display_name + " (Service)"
+ icon_state = "service"
+ icon_state_indicator = "service-indicator"
+
+/datum/prototype/robot_iconset/hover_handy/mining
+ icon_state = "mining"
+ icon_state_indicator = "mining-indicator"
+
+/datum/prototype/robot_iconset/hover_handy/security
+ icon_state = "security"
+ icon_state_indicator = "security-indicator"
+
+/datum/prototype/robot_iconset/hover_handy/medical
+ icon_state = "medical"
+ icon_state_indicator = "medical-indicator"
+
+/datum/prototype/robot_iconset/hover_handy/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "engineering-indicator"
+
+/datum/prototype/robot_iconset/hover_handy/science
+ icon_state = "science"
+ icon_state_indicator = "science-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/hover_x88.dm b/code/modules/mob/living/silicon/robot/iconset/hover_x88.dm
new file mode 100644
index 00000000000..5cb25aa7d7c
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/hover_x88.dm
@@ -0,0 +1,27 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/hover_x88
+ display_name = "X-88"
+ abstract_type = /datum/prototype/robot_iconset/hover_x88
+
+ auto_chassis = /datum/prototype/robot_chassis/hover
+
+ icon = 'icons/mob/robot/iconset/hover_x88.dmi'
+ icon_state_cover = "panel"
+
+/datum/prototype/robot_iconset/hover_x88/standard
+ icon_state = "standard"
+ icon_state_indicator = "standard-indicator"
+
+/datum/prototype/robot_iconset/hover_x88/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "engineering-indicator"
+
+/datum/prototype/robot_iconset/hover_x88/science
+ icon_state = "science"
+ icon_state_indicator = "science-indicator"
+
+/datum/prototype/robot_iconset/hover_x88/medical
+ icon_state = "medical"
+ icon_state_indicator = "medical-indicator"
diff --git a/code/modules/mob/living/silicon/robot/iconset/raptor.dm b/code/modules/mob/living/silicon/robot/iconset/raptor.dm
new file mode 100644
index 00000000000..0836b2608c2
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/iconset/raptor.dm
@@ -0,0 +1,70 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/datum/prototype/robot_iconset/raptor
+ display_name = "Raptor"
+ abstract_type = /datum/prototype/robot_iconset/raptor
+ auto_chassis = /datum/prototype/robot_chassis/quadruped/raptor
+ icon = 'icons/mob/robot/iconset/raptor.dmi'
+ icon_state_cover = "panel"
+ variations = list(
+ /datum/robot_iconset_variation/dead,
+ /datum/robot_iconset_variation/bellyup,
+ /datum/robot_iconset_variation/resting,
+ )
+
+/datum/prototype/robot_iconset/raptor/gravekeeper
+ icon_state = "gravekeeper"
+ icon_state_indicator = "indicator"
+
+/datum/prototype/robot_iconset/raptor/gravekeeper
+ icon_state = "lost"
+ icon_state_indicator = "indicator"
+
+/datum/prototype/robot_iconset/raptor/lost
+ icon_state = "service"
+ icon_state_indicator = "indicator"
+
+/datum/prototype/robot_iconset/raptor/service
+ icon_state = "security"
+ icon_state_indicator = "indicator"
+
+/datum/prototype/robot_iconset/raptor/security
+ icon_state = "mining"
+ icon_state_indicator = "indicator"
+
+/datum/prototype/robot_iconset/raptor/mining
+ icon_state = "medical"
+ icon_state_indicator = "indicator"
+
+/datum/prototype/robot_iconset/raptor/medical
+ icon_state = "medical"
+ icon_state_indicator = "indicator"
+
+/datum/prototype/robot_iconset/raptor/janitor
+ icon_state = "janitor"
+ icon_state_indicator = "indicator"
+
+/datum/prototype/robot_iconset/raptor/centcom
+ icon_state = "centcom"
+ icon_state_indicator = "indicator"
+
+/datum/prototype/robot_iconset/raptor/engineering
+ icon_state = "engineering"
+ icon_state_indicator = "indicator"
+
+/datum/prototype/robot_iconset/raptor/peacekeeper
+ icon_state = "peacekeeper"
+ icon_state_indicator = "indicator"
+
+/datum/prototype/robot_iconset/raptor/syndicate_medical
+ icon_state = "syndicate_medical"
+ icon_state_indicator = "indicator-syndicate"
+
+/datum/prototype/robot_iconset/raptor/syndicate_protector
+ icon_state = "syndicate_protector"
+ icon_state_indicator = "indicator-syndicate"
+
+/datum/prototype/robot_iconset/raptor/syndicate_machinist
+ icon_state = "syndicate_machinist"
+ icon_state_indicator = "indicator-syndicate"
diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm
deleted file mode 100644
index b89d1be85a6..00000000000
--- a/code/modules/mob/living/silicon/robot/inventory.dm
+++ /dev/null
@@ -1,367 +0,0 @@
-//These procs handle putting s tuff in your hand. It's probably best to use these rather than setting stuff manually
-//as they handle all relevant stuff like adding it to the player's screen and such
-// todo: proper inv procs
-//! WARNING: we currently only call equipped/unequipped and jankily using SLOT_ID_HANDS...
-
-//Returns the thing in our active hand (whatever is in our active module-slot, in this case)
-/mob/living/silicon/robot/get_active_held_item()
- return module_active
-
-//TODO: Something apparently?
-
-//Verbs used by hotkeys.
-/mob/living/silicon/robot/verb/cmd_unequip_module()
- set name = "unequip-module"
- set hidden = 1
- uneq_active()
-
-/mob/living/silicon/robot/verb/cmd_toggle_module(module as num)
- set name = "toggle-module"
- set hidden = 1
- toggle_module(module)
-
-/mob/living/silicon/robot/proc/uneq_active()
- if(isnull(module_active))
- return
- if(module_state_1 == module_active)
- if(istype(module_state_1,/obj/item/borg/sight))
- sight_mode &= ~module_state_1:sight_mode
- if (client)
- client.screen -= module_state_1
- contents -= module_state_1
- module_state_1.unequipped(src, SLOT_ID_HANDS, NONE)
- module_state_1.on_inv_unequipped(src, inventory, 1, NONE)
- module_active = null
- module_state_1:loc = module //So it can be used again later
- module_state_1 = null
- inv1.icon_state = "inv1"
- else if(module_state_2 == module_active)
- if(istype(module_state_2,/obj/item/borg/sight))
- sight_mode &= ~module_state_2:sight_mode
- if (client)
- client.screen -= module_state_2
- contents -= module_state_2
- module_state_2.unequipped(src, SLOT_ID_HANDS, NONE)
- module_state_2.on_inv_unequipped(src, inventory, 2, NONE)
- module_active = null
- module_state_2:loc = module
- module_state_2 = null
- inv2.icon_state = "inv2"
- else if(module_state_3 == module_active)
- if(istype(module_state_3,/obj/item/borg/sight))
- sight_mode &= ~module_state_3:sight_mode
- if (client)
- client.screen -= module_state_3
- contents -= module_state_3
- module_state_3.unequipped(src, SLOT_ID_HANDS, NONE)
- module_state_3.on_inv_unequipped(src, inventory, 3, NONE)
- module_active = null
- module_state_3:loc = module
- module_state_3 = null
- inv3.icon_state = "inv3"
- updateicon()
-
-/mob/living/silicon/robot/proc/uneq_all()
- module_active = null
-
- if(module_state_1)
- if(istype(module_state_1,/obj/item/borg/sight))
- sight_mode &= ~module_state_1:sight_mode
- if (client)
- client.screen -= module_state_1
- contents -= module_state_1
- module_state_1:loc = module
- module_state_1.unequipped(src, SLOT_ID_HANDS, NONE)
- module_state_1.on_inv_unequipped(src, inventory, 1, NONE)
- module_state_1 = null
- inv1.icon_state = "inv1"
- if(module_state_2)
- if(istype(module_state_2,/obj/item/borg/sight))
- sight_mode &= ~module_state_2:sight_mode
- if (client)
- client.screen -= module_state_2
- contents -= module_state_2
- module_state_2:loc = module
- module_state_2.unequipped(src, SLOT_ID_HANDS, NONE)
- module_state_1.on_inv_unequipped(src, inventory, 2, NONE)
- module_state_2 = null
- inv2.icon_state = "inv2"
- if(module_state_3)
- if(istype(module_state_3,/obj/item/borg/sight))
- sight_mode &= ~module_state_3:sight_mode
- if (client)
- client.screen -= module_state_3
- contents -= module_state_3
- module_state_3:loc = module
- module_state_3.unequipped(src, SLOT_ID_HANDS, NONE)
- module_state_1.on_inv_unequipped(src, inventory, 3, NONE)
- module_state_3 = null
- inv3.icon_state = "inv3"
- updateicon()
-
-/mob/living/silicon/robot/proc/activated(obj/item/O)
- if(module_state_1 == O)
- return 1
- else if(module_state_2 == O)
- return 1
- else if(module_state_3 == O)
- return 1
- else
- return 0
-
-// This one takes an object's type instead of an instance, as above.
-/mob/living/silicon/robot/proc/has_active_type(var/type_to_compare)
- var/list/active_modules = list(module_state_1, module_state_2, module_state_3)
- if(is_path_in_list(type_to_compare, active_modules))
- return TRUE
- return FALSE
-
-//Helper procs for cyborg modules on the UI.
-//These are hackish but they help clean up code elsewhere.
-
-//module_selected(module) - Checks whether the module slot specified by "module" is currently selected.
-/mob/living/silicon/robot/proc/module_selected(var/module) //Module is 1-3
- return module == get_selected_module()
-
-//module_active(module) - Checks whether there is a module active in the slot specified by "module".
-/mob/living/silicon/robot/proc/module_active(var/module) //Module is 1-3
- if(module < 1 || module > 3) return 0
-
- switch(module)
- if(1)
- if(module_state_1)
- return 1
- if(2)
- if(module_state_2)
- return 1
- if(3)
- if(module_state_3)
- return 1
- return 0
-
-//get_selected_module() - Returns the slot number of the currently selected module. Returns 0 if no modules are selected.
-/mob/living/silicon/robot/proc/get_selected_module()
- if(module_state_1 && module_active == module_state_1)
- return 1
- else if(module_state_2 && module_active == module_state_2)
- return 2
- else if(module_state_3 && module_active == module_state_3)
- return 3
-
- return 0
-
-//select_module(module) - Selects the module slot specified by "module"
-/mob/living/silicon/robot/proc/select_module(var/module) //Module is 1-3
- if(module < 1 || module > 3) return
-
- if(!module_active(module)) return
-
- switch(module)
- if(1)
- if(module_active != module_state_1)
- inv1.icon_state = "inv1 +a"
- inv2.icon_state = "inv2"
- inv3.icon_state = "inv3"
- module_active = module_state_1
- return
- if(2)
- if(module_active != module_state_2)
- inv1.icon_state = "inv1"
- inv2.icon_state = "inv2 +a"
- inv3.icon_state = "inv3"
- module_active = module_state_2
- return
- if(3)
- if(module_active != module_state_3)
- inv1.icon_state = "inv1"
- inv2.icon_state = "inv2"
- inv3.icon_state = "inv3 +a"
- module_active = module_state_3
- return
- return
-
-//deselect_module(module) - Deselects the module slot specified by "module"
-/mob/living/silicon/robot/proc/deselect_module(var/module) //Module is 1-3
- if(module < 1 || module > 3) return
-
- switch(module)
- if(1)
- if(module_active == module_state_1)
- inv1.icon_state = "inv1"
- module_active = null
- return
- if(2)
- if(module_active == module_state_2)
- inv2.icon_state = "inv2"
- module_active = null
- return
- if(3)
- if(module_active == module_state_3)
- inv3.icon_state = "inv3"
- module_active = null
- return
- return
-
-//toggle_module(module) - Toggles the selection of the module slot specified by "module".
-/mob/living/silicon/robot/proc/toggle_module(var/module) //Module is 1-3
- if(module < 1 || module > 3) return
-
- if(module_selected(module))
- deselect_module(module)
- else
- if(module_active(module))
- select_module(module)
- else
- deselect_module(get_selected_module()) //If we can't do select anything, at least deselect the current module.
- return
-
-//cycle_modules() - Cycles through the list of selected modules.
-/mob/living/silicon/robot/proc/cycle_modules()
- var/slot_start = get_selected_module()
- if(slot_start) deselect_module(slot_start) //Only deselect if we have a selected slot.
-
- var/slot_num
- if(slot_start == 0)
- slot_num = 1
- slot_start = 2
- else
- slot_num = slot_start + 1
-
- while(slot_start != slot_num) //If we wrap around without finding any free slots, just give up.
- if(module_active(slot_num))
- select_module(slot_num)
- return
- slot_num++
- if(slot_num > 3) slot_num = 1 //Wrap around.
-
- return
-
-/mob/living/silicon/robot/proc/activate_module(var/obj/item/O)
- if(!(locate(O) in src.module.modules) && O != src.module.emag)
- return
- if(activated(O))
- to_chat(src, "Already activated")
- return
- if(!module_state_1)
- module_state_1 = O
- O.hud_layerise()
- O.screen_loc = inv1.screen_loc
- contents += O
- if(istype(module_state_1,/obj/item/borg/sight))
- sight_mode |= module_state_1:sight_mode
- else if(!module_state_2)
- module_state_2 = O
- O.hud_layerise()
- O.screen_loc = inv2.screen_loc
- contents += O
- if(istype(module_state_2,/obj/item/borg/sight))
- sight_mode |= module_state_2:sight_mode
- else if(!module_state_3)
- module_state_3 = O
- O.hud_layerise()
- O.screen_loc = inv3.screen_loc
- contents += O
- if(istype(module_state_3,/obj/item/borg/sight))
- sight_mode |= module_state_3:sight_mode
- else
- to_chat(src, "You need to disable a module first!")
- O.equipped(src, SLOT_ID_HANDS, NONE)
-
-/mob/living/silicon/robot/get_held_items()
- . = list()
- if(module_state_1)
- . += module_state_1
- if(module_state_2)
- . += module_state_2
- if(module_state_3)
- . += module_state_3
-
-/mob/living/silicon/robot/get_nominal_hand_count()
- return 3
-
-/mob/living/silicon/robot/get_held_index(obj/item/I)
- if(module_state_1 == I)
- return 1
- if(module_state_2 == I)
- return 2
- if(module_state_3 == I)
- return 3
-
-/mob/living/silicon/robot/get_held_index(index)
- switch(index)
- if(1)
- return module_state_1
- if(2)
- return module_state_2
- if(3)
- return module_state_3
-
-/* for now we don't use generic slots at all */
-// TODO: put in hands should try to put into grippers ~silicons
-
-/mob/living/silicon/robot/is_in_inventory(obj/item/I)
- return is_module_item(I) || is_in_gripper(I)
-
-/mob/living/silicon/robot/proc/considered_removable(obj/item/I)
- return (!is_module_item(I))
-
-/mob/living/silicon/robot/proc/is_module_item(obj/item/I)
- if(!module)
- return FALSE
- return (I in module.modules) || (I in module.emag)
-
-/mob/living/silicon/robot/proc/is_in_gripper(obj/item/I, require_active_module)
- return ( \
- I.loc == src? \
- !!gripper_holding(I) : \
- (istype(I.loc, /obj/item/gripper) && (require_active_module? is_holding(I.loc) : is_module_item(I.loc))) \
- )
-
-/mob/living/silicon/robot/proc/gripper_holding(obj/item/I)
- for(var/obj/item/gripper/G in module.modules)
- if(G.get_item() == I)
- return G
-
-/mob/living/silicon/robot/proc/unreference_from_gripper(obj/item/I, newloc)
- if(!istype(I.loc, /obj/item/gripper))
- return FALSE
- var/obj/item/gripper/G = I.loc
- if(!is_module_item(G))
- return FALSE
- if(G.get_item() != I)
- return FALSE
- G.remove_item(newloc)
- return TRUE
-
-/mob/living/silicon/robot/temporarily_remove_from_inventory(obj/item/I, flags, mob/user)
- if(!is_in_inventory(I))
- return TRUE
- . = considered_removable(I)
- if(!.)
- return
- if(is_in_gripper(I))
- return unreference_from_gripper(I, null)
-
-/mob/living/silicon/robot/transfer_item_to_loc(obj/item/I, newloc, flags, mob/user)
- if(is_in_inventory(I) && considered_removable(I))
- if(is_in_gripper(I))
- return unreference_from_gripper(I, newloc)
- I.forceMove(newloc)
- return TRUE
- return FALSE
-
-/mob/living/silicon/robot/transfer_item_to_nullspace(obj/item/I, flags, mob/user)
- if(is_in_inventory(I) && considered_removable(I))
- if(is_in_gripper(I))
- return unreference_from_gripper(I, null)
- I.moveToNullspace()
- return TRUE
- return FALSE
-
-/mob/living/silicon/robot/drop_item_to_ground(obj/item/I, flags, mob/user)
- if(is_in_inventory(I) && considered_removable(I))
- if(is_in_gripper(I))
- return unreference_from_gripper(I, drop_location())
- I.forceMove(drop_location())
- return TRUE
- return FALSE
diff --git a/code/modules/mob/living/silicon/robot/items/robot_builtin.dm b/code/modules/mob/living/silicon/robot/items/robot_builtin.dm
new file mode 100644
index 00000000000..d6fe95f74cf
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_builtin.dm
@@ -0,0 +1,12 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/**
+ * Namespace for builtin items for cyborgs.
+ *
+ * * This doesn't at all mean all cyborg module items are under his.
+ * * This does mean that if something cannot be used outside of a cyborg, has no reason to be implemented
+ * in a way that's usable outside of a cyborg, **and** doesn't require inheriting other stuff,
+ * it should go in here.
+ */
+/obj/item/robot_builtin
diff --git a/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_boop_module.dm b/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_boop_module.dm
new file mode 100644
index 00000000000..325b1dca360
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_boop_module.dm
@@ -0,0 +1,66 @@
+/obj/item/robot_builtin/dog_boop_module
+ name = "boop module"
+ icon = 'icons/mob/dogborg_vr.dmi'
+ icon_state = "nose"
+ desc = "The BOOP module, a simple reagent and atmosphere sniffer."
+ damage_force = 0
+ item_flags = ITEM_NO_BLUDGEON | ITEM_ENCUMBERS_WHILE_HELD
+ throw_force = 0
+ attack_verb = list("nuzzled", "nosed", "booped")
+ w_class = WEIGHT_CLASS_TINY
+
+/obj/item/robot_builtin/dog_boop_module/attack_self(mob/user, datum/event_args/actor/actor)
+ . = ..()
+ if(.)
+ return
+ if (!( istype(user.loc, /turf) ))
+ return
+
+ var/datum/gas_mixture/environment = user.loc.return_air()
+
+ var/pressure = environment.return_pressure()
+ var/total_moles = environment.total_moles
+
+ user.setClickCooldownLegacy(DEFAULT_ATTACK_COOLDOWN)
+ user.visible_message("[user] sniffs the air.", "You sniff the air...")
+
+ to_chat(user, "Smells like:")
+ if(abs(pressure - ONE_ATMOSPHERE) < 10)
+ to_chat(user, "Pressure: [round(pressure,0.1)] kPa")
+ else
+ to_chat(user, "Pressure: [round(pressure,0.1)] kPa")
+ if(total_moles)
+ for(var/g in environment.gas)
+ to_chat(user, "[global.gas_data.names[g]]: [round((environment.gas[g] / total_moles) * 100)]%")
+ to_chat(user, "Temperature: [round(environment.temperature-T0C,0.1)]°C ([round(environment.temperature,0.1)]K)")
+
+/obj/item/robot_builtin/dog_boop_module/afterattack(atom/target, mob/user, clickchain_flags, list/params)
+ if(!(clickchain_flags & CLICKCHAIN_HAS_PROXIMITY))
+ return
+ if (user.stat)
+ return
+ if(!istype(target) && !ismob(target))
+ return
+
+ user.setClickCooldownLegacy(DEFAULT_ATTACK_COOLDOWN)
+
+
+ if(ismob(target))
+ user.visible_message("\the [user] boops \the [target.name]!", "You boop \the [target.name]!")
+ playsound(src, 'sound/weapons/thudswoosh.ogg', 25, 1, -1)
+ else
+ user.visible_message("[user] sniffs at \the [target.name].", "You sniff \the [target.name]...")
+ if(!isnull(target.reagents))
+ var/dat = ""
+ if(target.reagents.total_volume > 0)
+ for (var/datum/reagent/R in target.reagents.get_reagent_datums())
+ dat += "\n \t [R]"
+ if(dat)
+ to_chat(user, "Your BOOP module indicates: [dat]")
+ else
+ to_chat(user, "No active chemical agents smelled in [target].")
+ else
+ if(istype(target, /obj/item/tank)) // don't double post what atmosanalyzer_scan returns
+ analyze_gases(target, user)
+ else
+ to_chat(user, "No significant chemical agents smelled in [target].")
diff --git a/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_jaws.dm b/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_jaws.dm
new file mode 100644
index 00000000000..eae2b028d95
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_jaws.dm
@@ -0,0 +1,10 @@
+/obj/item/robot_builtin/dog_jaws
+ name = "synthetic jaws"
+ icon = 'icons/mob/dogborg_vr.dmi'
+ icon_state = "jaws"
+ desc = "Why did we start putting sharp jaws on stationbounds again? Things we ask ourselves every day, really."
+ damage_force = 10
+ throw_force = 0
+ attack_sound = 'sound/weapons/bite.ogg'
+ attack_verb = list("chomped", "bit", "ripped", "mauled", "enforced")
+ w_class = WEIGHT_CLASS_NORMAL
diff --git a/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_mirrortool.dm b/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_mirrortool.dm
new file mode 100644
index 00000000000..037b3ea7bc3
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_mirrortool.dm
@@ -0,0 +1,49 @@
+/obj/item/robot_builtin/dog_mirrortool
+ name = "Mirror Installation Tool"
+ desc = "A tool for the installation and removal of Mirrors"
+ icon = 'icons/obj/device_alt.dmi'
+ icon_state = "sleevemate"
+ item_state = "healthanalyzer"
+ w_class = WEIGHT_CLASS_SMALL
+ var/obj/item/implant/mirror/imp = null
+
+/obj/item/robot_builtin/dog_mirrortool/afterattack(atom/target, mob/user, clickchain_flags, list/params)
+ var/mob/living/carbon/human/H = target
+ if(!istype(H))
+ return
+ var/target_zone = user.zone_sel.selecting
+ if(target_zone == BP_TORSO && imp == null)
+ for(var/obj/item/organ/I in H.organs)
+ for(var/obj/item/implant/mirror/MI in I.contents)
+ if(imp == null)
+ H.visible_message("[user] is attempting remove [H]'s mirror!")
+ user.setClickCooldownLegacy(DEFAULT_QUICK_COOLDOWN)
+ user.do_attack_animation(H)
+ var/turf/T1 = get_turf(H)
+ if (T1 && ((H == user) || do_after(user, 20)))
+ if(user && H && (get_turf(H) == T1) && src)
+ H.visible_message("[user] has removed [H]'s mirror.")
+ add_attack_logs(user,H,"Mirror removed by [user]")
+ src.imp = MI
+ qdel(MI)
+ else if (target_zone == BP_TORSO && imp != null)
+ if (imp)
+ H.visible_message("[user] is attempting to implant [H] with a mirror.")
+ user.setClickCooldownLegacy(DEFAULT_QUICK_COOLDOWN)
+ user.do_attack_animation(H)
+ var/turf/T1 = get_turf(H)
+ if (T1 && ((H == user) || do_after(user, 20)))
+ if(user && H && (get_turf(H) == T1) && src && src.imp)
+ H.visible_message("[H] has been implanted by [user].")
+ add_attack_logs(user,H,"Implanted with [imp.name] using [name]")
+ if(imp.handle_implant(H))
+ imp.post_implant(H)
+ src.imp = null
+ else
+ to_chat(usr, "You must target the torso.")
+
+/obj/item/robot_builtin/dog_mirrortool/afterattack(atom/target, mob/user, clickchain_flags, list/params)
+ var/obj/machinery/computer/transhuman/resleeving/comp = target
+ if(!istype(comp))
+ return
+ comp.active_mr = imp.stored_mind
diff --git a/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_pounce.dm b/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_pounce.dm
new file mode 100644
index 00000000000..06345277b2a
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_pounce.dm
@@ -0,0 +1,78 @@
+// todo: this is fucking stupid, rework everything.
+
+/obj/item/robot_builtin/dog_pounce
+ name = "pounce"
+ icon = 'icons/mob/dogborg_vr.dmi'
+ icon_state = "pounce"
+ desc = "Leap at your target to momentarily stun them."
+ damage_force = 0
+ item_flags = ITEM_NO_BLUDGEON | ITEM_ENCUMBERS_WHILE_HELD
+ throw_force = 0
+
+/obj/item/robot_builtin/dog_pounce/attack_self(mob/user, datum/event_args/actor/actor)
+ . = ..()
+ if(.)
+ return
+ var/mob/living/silicon/robot/R = user
+ R.leap()
+
+/mob/living/silicon/robot/proc/leap()
+ if(last_special > world.time)
+ to_chat(src, "Your leap actuators are still recharging.")
+ return
+
+ if(cell.charge < 1000)
+ to_chat(src, "Cell charge too low to continue.")
+ return
+
+ if(usr.incapacitated(INCAPACITATION_DISABLED))
+ to_chat(src, "You cannot leap in your current state.")
+ return
+
+ var/list/choices = list()
+ for(var/mob/living/M in view(3,src))
+ if(!istype(M,/mob/living/silicon))
+ choices += M
+ choices -= src
+
+ var/mob/living/T = input(src,"Who do you wish to leap at?") as null|anything in choices
+
+ if(!T || !src || src.stat) return
+
+ if(get_dist(get_turf(T), get_turf(src)) > 3) return
+
+ if(last_special > world.time)
+ return
+
+ if(usr.incapacitated(INCAPACITATION_DISABLED))
+ to_chat(src, "You cannot leap in your current state.")
+ return
+
+ last_special = world.time + 10
+ status_flags |= STATUS_LEAPING
+ pixel_y = pixel_y + 10
+
+ src.visible_message("\The [src] leaps at [T]!")
+ src.throw_at_old(get_step(get_turf(T),get_turf(src)), 4, 1, src)
+ playsound(src.loc, 'sound/mecha/mechstep2.ogg', 50, 1)
+ pixel_y = base_pixel_y
+ cell.charge -= 750
+
+ sleep(5)
+
+ if(status_flags & STATUS_LEAPING) status_flags &= ~STATUS_LEAPING
+
+ if(!src.Adjacent(T))
+ to_chat(src, "You miss!")
+ return
+
+ if(ishuman(T))
+ var/mob/living/carbon/human/H = T
+ if(H.species.lightweight == 1)
+ H.afflict_paralyze(20 * 3)
+ return
+ var/armor_block = run_armor_check(T, "melee")
+ var/armor_soak = get_armor_soak(T, "melee")
+ T.apply_damage(20, DAMAGE_TYPE_HALLOSS,, armor_block, armor_soak)
+ if(prob(33))
+ T.apply_effect(3, WEAKEN, armor_block)
diff --git a/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper_vr.dm b/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_sleeper.dm
similarity index 90%
rename from code/modules/mob/living/silicon/robot/dogborg/dog_sleeper_vr.dm
rename to code/modules/mob/living/silicon/robot/items/robot_builtin/dog_sleeper.dm
index 7b84d99840d..b427d5543c3 100644
--- a/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper_vr.dm
+++ b/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_sleeper.dm
@@ -1,5 +1,11 @@
+// todo: this file is a fucking disgrace, rewrite this to be
+// /obj/item/robot_builtin/entity_chamber
+// and make all behavior modular
+// also take out all fucking references of vore,
+// if a downstream wants to be vorny power to them they can do it themselves
+
//Sleeper
-/obj/item/dogborg/sleeper
+/obj/item/robot_builtin/dog_sleeper
name = "Medbelly"
desc = "Equipment for medical hound. A mounted sleeper that stabilizes patients and can inject reagents in the borg's reserves."
icon = 'icons/mob/dogborg_vr.dmi'
@@ -30,27 +36,22 @@
var/startdrain = 500
var/max_item_count = 1
var/gulpsound = 'sound/vore/gulp.ogg'
- var/datum/matter_synth/metal = null
- var/datum/matter_synth/glass = null
- var/datum/matter_synth/wood = null
- var/datum/matter_synth/plastic = null
- var/datum/matter_synth/water = null
var/digest_brute = 2
var/digest_burn = 3
var/recycles = FALSE
-/obj/item/dogborg/sleeper/Initialize(mapload)
+/obj/item/robot_builtin/dog_sleeper/Initialize(mapload)
. = ..()
files = new /datum/research/techonly(src)
-/obj/item/dogborg/sleeper/Destroy()
+/obj/item/robot_builtin/dog_sleeper/Destroy()
go_out()
..()
-/obj/item/dogborg/sleeper/Exit(atom/movable/O)
+/obj/item/robot_builtin/dog_sleeper/Exit(atom/movable/O)
return 0
-/obj/item/dogborg/sleeper/afterattack(atom/target, mob/user, clickchain_flags, list/params)
+/obj/item/robot_builtin/dog_sleeper/afterattack(atom/target, mob/user, clickchain_flags, list/params)
hound = loc
var/atom/movable/AM = target
if(!istype(AM))
@@ -59,7 +60,7 @@
return
if(AM.anchored)
return
- if(target in hound.module.modules)
+ if(target in hound.contents)
return
if(length(contents) >= max_item_count)
to_chat(user, "Your [src.name] is full. Eject or process contents to continue.")
@@ -151,7 +152,7 @@
message_admins("[key_name(hound)] has eaten [key_name(patient)] as a dogborg. ([hound ? "JMP" : "null"])")
playsound(hound, gulpsound, vol = 100, vary = 1, falloff = 0.1, preference = /datum/game_preference_toggle/vore_sounds/eating_noises)
-/obj/item/dogborg/sleeper/proc/go_out(var/target)
+/obj/item/robot_builtin/dog_sleeper/proc/go_out(var/target)
hound = src.loc
items_preserved.Cut()
cleaning = 0
@@ -172,13 +173,13 @@
else //You clicked eject with nothing in you, let's just reset stuff to be sure.
update_patient()
-/obj/item/dogborg/sleeper/proc/drain(var/amt = 3) //Slightly reduced cost (before, it was always injecting inaprov)
+/obj/item/robot_builtin/dog_sleeper/proc/drain(var/amt = 3) //Slightly reduced cost (before, it was always injecting inaprov)
hound = src.loc
- if(istype(hound,/obj/item/robot_module))
+ if(istype(hound,/obj/item/robot_module_legacy))
hound = hound.loc
hound.cell.charge = hound.cell.charge - amt
-/obj/item/dogborg/sleeper/attack_self(mob/user, datum/event_args/actor/actor)
+/obj/item/robot_builtin/dog_sleeper/attack_self(mob/user, datum/event_args/actor/actor)
. = ..()
if(.)
return
@@ -186,7 +187,7 @@
return
sleeperUI(user)
-/obj/item/dogborg/sleeper/proc/sleeperUI(mob/user)
+/obj/item/robot_builtin/dog_sleeper/proc/sleeperUI(mob/user)
var/dat = "[name] Console
"
if(islist(injection_chems)) //Only display this if we're a drug-dispensing doggo.
@@ -294,7 +295,7 @@
UI_open = TRUE
return
-/obj/item/dogborg/sleeper/Topic(href, href_list)
+/obj/item/robot_builtin/dog_sleeper/Topic(href, href_list)
if(..() || usr == patient)
return
usr.set_machine(src)
@@ -390,7 +391,7 @@
sleeperUI(usr) //Needs a callback to boop the page to refresh.
return
-/obj/item/dogborg/sleeper/proc/inject_chem(mob/user, chem)
+/obj/item/robot_builtin/dog_sleeper/proc/inject_chem(mob/user, chem)
if(patient && patient.reagents)
if(chem in (injection_chems + "inaprovaline"))
if(hound.cell.charge < 800) //This is so borgs don't kill themselves with it.
@@ -405,7 +406,7 @@
to_chat(hound, "Injecting [units] unit\s of [SSchemistry.reagent_lookup[chem]] into occupant.") //If they were immersed, the reagents wouldn't leave with them.
//For if the dogborg's existing patient uh, doesn't make it.
-/obj/item/dogborg/sleeper/proc/update_patient()
+/obj/item/robot_builtin/dog_sleeper/proc/update_patient()
hound = src.loc
if(!istype(hound,/mob/living/silicon/robot))
return
@@ -414,8 +415,6 @@
//Cleaning looks better with red on, even with nobody in it
if(cleaning || (length(contents) > 10) || (decompiler && (length(contents) > 5)) || (analyzer && (length(contents) > 1)))
- hound.sleeper_r = TRUE
- hound.sleeper_g = FALSE
hound.updateicon()
return
@@ -423,16 +422,16 @@
if(patient in contents)
if(patient_laststat != patient.stat)
if(cleaning)
- hound.sleeper_r = TRUE
- hound.sleeper_g = FALSE
+ // hound.sleeper_r = TRUE
+ // hound.sleeper_g = FALSE
patient_laststat = patient.stat
else if(patient.stat & DEAD)
- hound.sleeper_r = TRUE
- hound.sleeper_g = FALSE
+ // hound.sleeper_r = TRUE
+ // hound.sleeper_g = FALSE
patient_laststat = patient.stat
else
- hound.sleeper_r = FALSE
- hound.sleeper_g = TRUE
+ // hound.sleeper_r = FALSE
+ // hound.sleeper_g = TRUE
patient_laststat = patient.stat
//Update icon
hound.updateicon()
@@ -444,25 +443,25 @@
for(var/mob/living/carbon/human/C in contents)
patient = C
if(cleaning)
- hound.sleeper_r = TRUE
- hound.sleeper_g = FALSE
+ // hound.sleeper_r = TRUE
+ // hound.sleeper_g = FALSE
patient_laststat = patient.stat
else if(patient.stat & DEAD)
- hound.sleeper_r = TRUE
- hound.sleeper_g = FALSE
+ // hound.sleeper_r = TRUE
+ // hound.sleeper_g = FALSE
patient_laststat = patient.stat
else
- hound.sleeper_r = FALSE
- hound.sleeper_g = TRUE
+ // hound.sleeper_r = FALSE
+ // hound.sleeper_g = TRUE
patient_laststat = patient.stat
//Update icon and return new patient
hound.updateicon()
return(C)
//Couldn't find anyone, and not cleaning
- if(!cleaning && !patient)
- hound.sleeper_r = FALSE
- hound.sleeper_g = FALSE
+ // if(!cleaning && !patient)
+ // hound.sleeper_r = FALSE
+ // hound.sleeper_g = FALSE
patient_laststat = null
patient = null
@@ -470,7 +469,7 @@
return
//Gurgleborg process
-/obj/item/dogborg/sleeper/proc/clean_cycle()
+/obj/item/robot_builtin/dog_sleeper/proc/clean_cycle()
//Sanity? Maybe not required. More like if indigestible person OOC escapes.
for(var/I in items_preserved)
@@ -532,7 +531,7 @@
var/actual_burn = T.getFireLoss() - old_burn
var/damage_gain = actual_brute + actual_burn
drain(-25 * damage_gain) //25*total loss as with voreorgan stats.
- water.add_charge(damage_gain)
+ item_mount?.reagent_spawn_amount(src, null, /datum/reagent/water, damage_gain)
if(T.stat == DEAD)
if(ishuman(T))
message_admins("[key_name(hound)] has digested [key_name(T)] as a dogborg. ([hound ? "JMP" : "null"])")
@@ -569,10 +568,10 @@
if(ishuman(T))
var/mob/living/carbon/human/Prey = T
volume = (Prey.bloodstr.total_volume + Prey.ingested.total_volume + Prey.touching.total_volume + Prey.weight) * Prey.size_multiplier
- water.add_charge(volume)
+ item_mount?.reagent_spawn_amount(src, null, /datum/reagent/water, volume)
if(T.reagents)
volume = T.reagents.total_volume
- water.add_charge(volume)
+ item_mount?.reagent_spawn_amount(src, null, /datum/reagent/water, volume)
if(patient == T)
patient_laststat = null
patient = null
@@ -598,7 +597,7 @@
synced = FALSE
drain(-50 * digested)
if(volume)
- water.add_charge(volume)
+ item_mount?.reagent_spawn_amount(src, null, /datum/reagent/water, volume)
var/list/mats = T.get_materials(TRUE)
if(recycles && mats)
for(var/material in mats)
@@ -607,14 +606,14 @@
var/obj/item/stack/stack = T
total_material *= stack.get_amount()
if(material == MAT_STEEL)
- metal.add_charge(total_material)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/steel::id, total_material)
if(material == "glass")
- glass.add_charge(total_material)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/glass::id, total_material)
if(decompiler)
if(material == "plastic")
- plastic.add_charge(total_material)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/plastic::id, total_material)
if(material == "wood")
- wood.add_charge(total_material)
+ item_mount?.material_give_amount(src, null, /datum/prototype/material/wood_plank::id, total_material)
else
drain(-50 * digested)
else if(istype(target,/obj/effect/decal/remains))
@@ -623,9 +622,8 @@
else
items_preserved |= target
update_patient()
- return
-/obj/item/dogborg/sleeper/process(delta_time)
+/obj/item/robot_builtin/dog_sleeper/process(delta_time)
if(!istype(src.loc,/mob/living/silicon/robot))
return
@@ -649,13 +647,13 @@
STOP_PROCESSING(SSobj, src)
return
-/obj/item/dogborg/sleeper/K9 //The K9 portabrig
+/obj/item/robot_builtin/dog_sleeper/K9 //The K9 portabrig
name = "Brig-Belly"
desc = "Equipment for a K9 unit. A mounted portable-brig that holds criminals."
icon_state = "sleeperb"
injection_chems = null //So they don't have all the same chems as the medihound!
-/obj/item/dogborg/sleeper/compactor //Janihound gut.
+/obj/item/robot_builtin/dog_sleeper/compactor //Janihound gut.
name = "Garbage Processor"
desc = "A mounted garbage compactor unit with fuel processor."
icon_state = "compactor"
@@ -664,7 +662,7 @@
recycles = TRUE
max_item_count = 25
-/obj/item/dogborg/sleeper/compactor/analyzer //sci-borg gut.
+/obj/item/robot_builtin/dog_sleeper/compactor/analyzer //sci-borg gut.
name = "Digestive Analyzer"
desc = "A mounted destructive analyzer unit with fuel processor."
icon_state = "analyzer"
@@ -672,7 +670,7 @@
startdrain = 100
analyzer = TRUE
-/obj/item/dogborg/sleeper/compactor/decompiler
+/obj/item/robot_builtin/dog_sleeper/compactor/decompiler
name = "Matter Decompiler"
desc = "A mounted materials decompiling unit with fuel processor."
icon_state = "decompiler"
@@ -680,7 +678,7 @@
decompiler = TRUE
recycles = TRUE
-/obj/item/dogborg/sleeper/compactor/delivery //Unfinished and unimplemented, still testing.
+/obj/item/robot_builtin/dog_sleeper/compactor/delivery //Unfinished and unimplemented, still testing.
name = "Cargo Belly"
desc = "A mounted cargo bay unit for tagged deliveries."
icon_state = "decompiler"
diff --git a/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_swordtail.dm b/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_swordtail.dm
new file mode 100644
index 00000000000..2f7be671b7f
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_swordtail.dm
@@ -0,0 +1,11 @@
+/obj/item/robot_builtin/dog_swordtail
+ name = "sword tail"
+ icon = 'icons/mob/dogborg_vr.dmi'
+ icon_state = "swordtail"
+ desc = "A glowing pink dagger normally attached to the end of a cyborg's tail. It appears to be extremely sharp."
+ damage_force = 20 //Takes 5 hits to 100-0
+ damage_mode = DAMAGE_MODE_SHARP | DAMAGE_MODE_EDGE
+ throw_force = 0 //This shouldn't be thrown in the first place.
+ attack_sound = 'sound/weapons/blade1.ogg'
+ attack_verb = list("slashed", "stabbed", "jabbed", "mauled", "sliced")
+ w_class = WEIGHT_CLASS_NORMAL
diff --git a/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_tongue.dm b/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_tongue.dm
new file mode 100644
index 00000000000..66b57815a1e
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_builtin/dog_tongue.dm
@@ -0,0 +1,122 @@
+
+/obj/item/robot_builtin/dog_tongue
+ name = "synthetic tongue"
+ desc = "Useful for slurping mess off the floor before affectionally licking the crew members in the face."
+ icon = 'icons/mob/dogborg_vr.dmi'
+ icon_state = "synthtongue"
+ attack_sound = 'sound/effects/attackblob.ogg'
+ item_flags = ITEM_NO_BLUDGEON | ITEM_ENCUMBERS_WHILE_HELD
+ var/emagged = 0
+ var/datum/matter_synth/water = null
+
+/obj/item/robot_builtin/dog_tongue/examine(user)
+ . = ..()
+ if(item_mount.reagent_get_amount(src, null, /datum/reagent/water::id) < 5)
+ . += "[src] is wet."
+ else
+ . += "[src] is dry."
+
+/obj/item/robot_builtin/dog_tongue/attack_self(mob/user, datum/event_args/actor/actor)
+ . = ..()
+ if(.)
+ return
+ var/mob/living/silicon/robot/R = user
+ if(R.emagged || R.emag_items)
+ emagged = !emagged
+ if(emagged)
+ name = "hacked tongue of doom"
+ desc = "Your tongue has been upgraded successfully. Congratulations."
+ icon = 'icons/mob/dogborg_vr.dmi'
+ icon_state = "syndietongue"
+ else
+ name = "synthetic tongue"
+ desc = "Useful for slurping mess off the floor before affectionally licking the crew members in the face."
+ icon = 'icons/mob/dogborg_vr.dmi'
+ icon_state = "synthtongue"
+ update_icon()
+
+/obj/item/robot_builtin/dog_tongue/afterattack(atom/target, mob/user, clickchain_flags, list/params)
+ if(!(clickchain_flags & CLICKCHAIN_HAS_PROXIMITY))
+ return
+
+ user.setClickCooldownLegacy(DEFAULT_ATTACK_COOLDOWN)
+ if(user.client && (target in user.client.screen))
+ to_chat(user, "You need to take [target] off before cleaning it!")
+ if(istype(target, /obj/structure/sink) || istype(target, /obj/structure/toilet)) //Dog vibes.
+ user.visible_message("[user] begins to lap up water from [target].", "You begin to lap up water from [target].")
+ if(do_after(user, 50, target = target))
+ item_mount?.reagent_spawn_amount(src, null, /datum/reagent/water, 50)
+ else if(item_mount?.reagent_get_amount(src, null, /datum/reagent/water) < 1)
+ to_chat(user, "Your mouth feels dry. You should drink some water.") //fixed annoying grammar and needless space
+ return
+ else if(istype(target,/obj/effect/debris/cleanable))
+ user.visible_message("[user] begins to lick off [target].", "You begin to lick off [target]...")
+ if(do_after(user, 50, target = target))
+ to_chat(user, "You finish licking off [target].")
+ item_mount?.reagent_erase_amount(src, null, /datum/reagent/water, 1)
+ qdel(target)
+ var/mob/living/silicon/robot/R = user
+ R.cell.charge += 50
+ else if(istype(target,/obj/item))
+ if(istype(target,/obj/item/trash))
+ user.visible_message("[user] nibbles away at [target].", "You begin to nibble away at [target]...")
+ if(do_after(user, 50, target = target))
+ user.visible_message("[user] finishes eating [target].", "You finish eating [target].")
+ to_chat(user, "You finish off [target].")
+ qdel(target)
+ var/mob/living/silicon/robot/R = user
+ if(istype(target,/obj/item/trash/rkibble))
+ R.cell.charge += 1000
+ else
+ R.cell.charge += 250
+ item_mount?.reagent_erase_amount(src, null, /datum/reagent/water, 1)
+ return
+ if(istype(target,/obj/item/cell))
+ user.visible_message("[user] begins cramming [target] down its throat.", "You begin cramming \the [target.name] down your throat...")
+ if(do_after(user, 50, target = target))
+ user.visible_message("[user] finishes gulping down [target].", "You finish swallowing [target].")
+ to_chat(user, "You finish off [target], and gain some charge!")
+ var/mob/living/silicon/robot/R = user
+ var/obj/item/cell/C = target
+ R.cell.charge += C.max_charge / 3
+ item_mount?.reagent_erase_amount(src, null, /datum/reagent/water, 1)
+ qdel(target)
+ return
+ user.visible_message("[user] begins to lick [target] clean...", "You begin to lick [target] clean...")
+ if(do_after(user, 50, target = target))
+ to_chat(user, "You clean [target].")
+ item_mount?.reagent_erase_amount(src, null, /datum/reagent/water, 1)
+ var/obj/effect/debris/cleanable/C = locate() in target
+ qdel(C)
+ target.clean_blood()
+ else if(ishuman(target))
+ if(src.emagged)
+ var/mob/living/silicon/robot/R = user
+ var/mob/living/L = target
+ if(R.cell.charge <= 666)
+ return
+ L.afflict_stun(20 * 1)
+ L.afflict_paralyze(20 * 1)
+ L.apply_effect(STUTTER, 1)
+ L.visible_message("[user] has shocked [L] with its tongue!", \
+ "[user] has shocked you with its tongue! You can feel the betrayal.")
+ playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1)
+ R.cell.charge -= 666
+ else
+ user.visible_message("\The [user] affectionally licks all over [target]'s face!", "You affectionally lick all over [target]'s face!")
+ playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1)
+ item_mount?.reagent_erase_amount(src, null, /datum/reagent/water, 1)
+ var/mob/living/carbon/human/H = target
+ if(H.species.lightweight == 1)
+ H.afflict_paralyze(20 * 3)
+ else
+ user.visible_message("[user] begins to lick [target] clean...", "You begin to lick [target] clean...")
+ if(do_after(user, 50, target = target))
+ to_chat(user, "You clean [target].")
+ var/obj/effect/debris/cleanable/C = locate() in target
+ qdel(C)
+ target.clean_blood()
+ item_mount?.reagent_erase_amount(src, null, /datum/reagent/water, 1)
+ if(istype(target, /turf/simulated))
+ var/turf/simulated/T = target
+ T.dirt = 0
diff --git a/code/modules/mob/living/silicon/robot/items/robot_upgrade.dm b/code/modules/mob/living/silicon/robot/items/robot_upgrade.dm
new file mode 100644
index 00000000000..c4baaaeea1a
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_upgrade.dm
@@ -0,0 +1,201 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/obj/item/robot_upgrade
+ name = "robot upgrade module"
+ desc = "An upgrade module for a robot platform."
+ icon = 'icons/items/circuits.dmi'
+ icon_state = "cyborg_upgrade"
+
+ //* Actions *//
+ /// actions to give the owner of this upgrade
+ ///
+ /// valid starting values include:
+ /// * list of actions / typepaths
+ /// * single action / typepath
+ var/list/datum/action/upgrade_actions
+ /// set to a string to initialize upgrade_actions with a generic action of this name
+ var/upgrade_action_name
+ /// description for upgrade action; defaults to [desc]
+ var/upgrade_action_desc
+
+ /// Who we're installed in.
+ var/mob/living/silicon/robot/owner
+ /// Provisioning
+ /// * Not destroyed across different owners, this owns our items and more.
+ var/datum/robot_provisioning/provisioning
+ /// Lazy man's create_mounted_item_descriptor injection.
+ /// * This shouldn't be used at compile time; just override the proc.
+ VAR_PRIVATE/list/provisioning_inject_item_descriptors
+
+ /// Allow dupes?
+ var/dupe_allowed = FALSE
+ /// Dupe type to check; defaults to self type
+ var/dupe_type
+
+/obj/item/robot_upgrade/Destroy()
+ owner?.uninstall_upgrade(src, TRUE)
+ QDEL_NULL(provisioning)
+ return ..()
+
+/obj/item/robot_upgrade/using_as_item(atom/target, datum/event_args/actor/clickchain/clickchain, clickchain_flags, datum/callback/reachability_check)
+ . = ..()
+ if(.)
+ return
+ if(!isrobot(target))
+ return
+ var/mob/living/silicon/robot/robot_target = target
+ if(!robot_target.opened)
+ clickchain.chat_feedback(
+ SPAN_WARNING("[robot_target] needs to havea their cover opened for you to access their internals!"),
+ target = robot_target,
+ )
+ return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
+ robot_target.user_install_upgrade(src, actor = clickchain)
+ return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
+
+/obj/item/robot_upgrade/proc/load_provisioning_if_needed()
+ if(provisioning)
+ return
+ create_provisioning()
+
+/obj/item/robot_upgrade/proc/create_provisioning() as /datum/robot_provisioning
+ SHOULD_NOT_OVERRIDE(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+ // obliterate existing if there
+ QDEL_NULL(provisioning)
+ // make new one
+ var/datum/robot_provisioning/robot_upgrade/creating = new(src)
+ . = provisioning = creating
+ var/list/items = create_mounted_items()
+ for(var/obj/item/item as anything in items)
+ creating.add_item(item)
+ return creating
+
+/obj/item/robot_upgrade/proc/create_mounted_items() as /list
+ var/list/obj/item/items = list()
+
+ var/list/i_normal = list()
+ create_mounted_item_descriptors(i_normal)
+ for(var/obj/item/descriptor as anything in i_normal)
+ var/obj/item/resolved
+ if(istype(descriptor))
+ resolved = descriptor
+ else if(ispath(descriptor))
+ resolved = new descriptor
+ else if(IS_ANONYMOUS_TYPEPATH(descriptor))
+ resolved = new descriptor
+ else
+ stack_trace("invalid descriptor [descriptor] on [src] ([type])")
+ continue
+ items += resolved
+
+ return items
+
+/**
+ * See: `/datum/prototype/robot_module/proc/create_mounted_item_descriptors`.
+ *
+ * This uses the exact same format and is handled in a similar way.
+ */
+/obj/item/robot_upgrade/proc/create_mounted_item_descriptors(list/out_list)
+ if(provisioning_inject_item_descriptors)
+ out_list |= provisioning_inject_item_descriptors
+
+/**
+ * Called when checking if we can be applied
+ *
+ * * Robot has final say for hard conflicts. (we won't be called in that case)
+ * * We have final say for soft conflicts.
+ * * skipped if 'force' is enabled on checks in the robot
+ *
+ * @params
+ * * target - robot we're attempting to be installed in
+ * * robot_opinion - what the robot says about it
+ * * actor - (optional) person doing the install
+ * * silent - (optional) do not emit feedback to the actor
+ *
+ * @return TRUE / FALSE
+ */
+/obj/item/robot_upgrade/proc/can_install(mob/living/silicon/robot/target, robot_opinion, datum/event_args/actor/actor, silent)
+ return TRUE
+
+/**
+ * Called when being applied.
+ *
+ * * Destructive operations here (like deleting ourselves) is allowed.
+ * * This is where 'consumable' upgrades like 'enable emag items' are put.
+ */
+/obj/item/robot_upgrade/proc/being_installed(mob/living/silicon/robot/target)
+ return
+
+/**
+ * Called on install.
+ */
+/obj/item/robot_upgrade/proc/on_install(mob/living/silicon/robot/target)
+ SHOULD_NOT_SLEEP(TRUE)
+ SHOULD_CALL_PARENT(TRUE)
+
+ load_provisioning_if_needed()
+ provisioning?.apply(target)
+ grant_upgrade_actions(target)
+
+/**
+ * Called on uninstall.
+ *
+ * * We're uninstalled if the robot is deleted.
+ */
+/obj/item/robot_upgrade/proc/on_uninstall(mob/living/silicon/robot/target)
+ SHOULD_NOT_SLEEP(TRUE)
+ SHOULD_CALL_PARENT(TRUE)
+
+ revoke_upgrade_actions(target)
+ provisioning?.remove(target)
+
+//* Actions *//
+
+/obj/item/robot_upgrade/proc/ensure_upgrade_actions_loaded()
+ if(islist(upgrade_actions))
+ for(var/i in 1 to length(upgrade_actions))
+ var/key = upgrade_actions[i]
+ if(ispath(key, /datum/action))
+ upgrade_actions[i] = key = new key(src)
+ else if(ispath(upgrade_actions, /datum/action))
+ upgrade_actions = new upgrade_actions
+ else if(istype(upgrade_actions, /datum/action))
+ else if(upgrade_action_name)
+ var/datum/action/robot_upgrade_action/creating = new(src)
+ upgrade_actions = creating
+ creating.name = upgrade_action_name
+ creating.desc = upgrade_action_desc || desc
+
+/obj/item/robot_upgrade/proc/grant_upgrade_actions(mob/target)
+ if(islist(upgrade_actions))
+ for(var/datum/action/action in upgrade_actions)
+ action.grant(target.actions_innate)
+ else if(istype(upgrade_actions, /datum/action))
+ var/datum/action/action = upgrade_actions
+ action.grant(target.actions_innate)
+
+/obj/item/robot_upgrade/proc/revoke_upgrade_actions(mob/target)
+ if(islist(upgrade_actions))
+ for(var/datum/action/action in upgrade_actions)
+ action.revoke(target.actions_innate)
+ else if(istype(upgrade_actions, /datum/action))
+ var/datum/action/action = upgrade_actions
+ action.revoke(target.actions_innate)
+
+//* Provisioning *//
+
+/datum/robot_provisioning/robot_upgrade
+ var/obj/item/robot_upgrade/owner
+
+/datum/robot_provisioning/robot_upgrade/New(obj/item/robot_upgrade/upgrade)
+ owner = upgrade
+ ..()
+
+/datum/robot_provisioning/robot_upgrade/Destroy()
+ owner = null
+ return ..()
+
+/datum/robot_provisioning/robot_upgrade/get_detached_storage_location()
+ return owner
diff --git a/code/modules/mob/living/silicon/robot/items/robot_upgrade/advhealth.dm b/code/modules/mob/living/silicon/robot/items/robot_upgrade/advhealth.dm
new file mode 100644
index 00000000000..8c6bda467a7
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_upgrade/advhealth.dm
@@ -0,0 +1,9 @@
+/obj/item/robot_upgrade/advhealth
+ name = "advanced health analyzer module"
+ desc = "An upgrade to add an advanced health analyzer to a robot."
+ icon_state = "cyborg_upgrade3"
+ item_state = "cyborg_upgrade"
+
+/obj/item/robot_upgrade/advhealth/create_mounted_item_descriptors(list/out_list)
+ out_list += /obj/item/healthanalyzer/advanced
+ return ..()
diff --git a/code/modules/mob/living/silicon/robot/items/robot_upgrade/bluespaceorebag.dm b/code/modules/mob/living/silicon/robot/items/robot_upgrade/bluespaceorebag.dm
new file mode 100644
index 00000000000..847c6d8d81a
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_upgrade/bluespaceorebag.dm
@@ -0,0 +1,22 @@
+/obj/item/robot_upgrade/bluespaceorebag
+ name = "bluespace mining satchel module"
+ desc = "An upgrade to upgrade a robot's ore bag."
+ icon_state = "cyborg_upgrade3"
+ item_state = "cyborg_upgrade"
+
+/obj/item/robot_upgrade/bluespaceorebag/create_mounted_item_descriptors(list/out_list)
+ out_list += /obj/item/storage/bag/ore/bluespace
+ return ..()
+
+/obj/item/robot_upgrade/bluespaceorebag/on_install(mob/living/silicon/robot/target)
+ . = ..()
+ var/obj/item/storage/bag/ore/our_bag = locate() in provisioning?.items
+ var/list/obj/item/suppressed = target.module_provisioning.suppress_all_types_of(/obj/item/storage/bag/ore)
+ if(our_bag)
+ for(var/obj/item/storage/transferring in suppressed)
+ for(var/obj/item/stack/ore/ore in transferring.contents)
+ ore.forceMove(our_bag)
+
+/obj/item/robot_upgrade/bluespaceorebag/on_uninstall(mob/living/silicon/robot/target)
+ . = ..()
+ target.module_provisioning.unsuppress_all_types_of(/obj/item/storage/bag/ore)
diff --git a/code/modules/mob/living/silicon/robot/items/robot_upgrade/jetpack.dm b/code/modules/mob/living/silicon/robot/items/robot_upgrade/jetpack.dm
new file mode 100644
index 00000000000..3baa35f5164
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_upgrade/jetpack.dm
@@ -0,0 +1,10 @@
+// todo: ion thruster upgrade instead wtf
+/obj/item/robot_upgrade/jetpack
+ name = "robot jetpack"
+ desc = "A carbon dioxide jetpack suitable for low-gravity operations."
+ icon_state = "cyborg_upgrade3"
+ item_state = "cyborg_upgrade"
+
+/obj/item/robot_upgrade/bluespaceorebag/create_mounted_item_descriptors(list/out_list)
+ out_list += /obj/item/tank/jetpack/carbondioxide
+ return ..()
diff --git a/code/modules/mob/living/silicon/robot/items/robot_upgrade/language.dm b/code/modules/mob/living/silicon/robot/items/robot_upgrade/language.dm
new file mode 100644
index 00000000000..5e5ec3b62ef
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_upgrade/language.dm
@@ -0,0 +1,19 @@
+/obj/item/robot_upgrade/language
+ name = "adaptive translation module"
+ desc = "Upgrades a cyborg's language processing unit with an adaptive translation module."
+ icon_state = "cyborg_upgrade3"
+ item_state = "cyborg_upgrade"
+
+/obj/item/robot_upgrade/language/can_install(mob/living/silicon/robot/target, robot_opinion, datum/event_args/actor/actor, silent)
+ if(target.translation_context?.type == /datum/translation_context/variable/learning/silicons)
+ if(!silent)
+ actor?.chat_feedback(
+ SPAN_WARNING("[target] already has an adaptive translation unit installed."),
+ target = target,
+ )
+ return FALSE
+ return ..()
+
+/obj/item/robot_upgrade/language/being_installed(mob/living/silicon/robot/target)
+ target.create_translation_context(/datum/translation_context/variable/learning/silicons)
+ qdel(src)
diff --git a/code/modules/mob/living/silicon/robot/items/robot_upgrade/rename.dm b/code/modules/mob/living/silicon/robot/items/robot_upgrade/rename.dm
new file mode 100644
index 00000000000..bb7bbdc699d
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_upgrade/rename.dm
@@ -0,0 +1,21 @@
+/obj/item/robot_upgrade/rename
+ name = "robot reclassification board"
+ desc = "Used to rename a cyborg."
+ icon_state = "cyborg_upgrade1"
+ item_state = "cyborg_upgrade"
+ var/heldname = "default name"
+
+/obj/item/robot_upgrade/rename/attack_self(mob/user, datum/event_args/actor/actor)
+ . = ..()
+ if(.)
+ return
+ heldname = sanitizeSafe(input(user, "Enter new robot name", "Robot Reclassification", heldname), MAX_NAME_LEN)
+
+// TODO: refactor cyborg namepicking
+/obj/item/robot_upgrade/rename/being_installed(mob/living/silicon/robot/target)
+ var/mob/living/silicon/robot/R = target
+ R.notify_ai(ROBOT_NOTIFICATION_NEW_NAME, R.name, heldname)
+ R.name = heldname
+ R.custom_name = heldname
+ R.real_name = heldname
+ qdel(src)
diff --git a/code/modules/mob/living/silicon/robot/items/robot_upgrade/reset.dm b/code/modules/mob/living/silicon/robot/items/robot_upgrade/reset.dm
new file mode 100644
index 00000000000..66ed6511ed7
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_upgrade/reset.dm
@@ -0,0 +1,9 @@
+/obj/item/robot_upgrade/reset
+ name = "robotic module reset board"
+ desc = "Used to reset a cyborg's module. Destroys any other upgrades applied to the robot."
+ icon_state = "cyborg_upgrade1"
+ item_state = "cyborg_upgrade"
+
+/obj/item/robot_upgrade/reset/being_installed(mob/living/silicon/robot/target)
+ target.module_reset()
+ qdel(src)
diff --git a/code/modules/mob/living/silicon/robot/items/robot_upgrade/restart.dm b/code/modules/mob/living/silicon/robot/items/robot_upgrade/restart.dm
new file mode 100644
index 00000000000..5fedd06714a
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_upgrade/restart.dm
@@ -0,0 +1,33 @@
+/obj/item/robot_upgrade/restart
+ name = "robot emergency restart module"
+ desc = "Used to force a restart of a disabled-but-repaired robot, bringing it back online."
+ icon_state = "cyborg_upgrade1"
+ item_state = "cyborg_upgrade"
+
+/obj/item/robot_upgrade/restart/can_install(mob/living/silicon/robot/target, robot_opinion, datum/event_args/actor/actor, silent)
+ if(target.health < 0)
+ if(!silent)
+ actor?.chat_feedback(
+ SPAN_WARNING("You have to repair [target] before using an emergency restart module."),
+ target = target,
+ )
+ return FALSE
+ if(target.stat != DEAD)
+ if(!silent)
+ // ok this is a bit morbid but it's funny.
+ actor?.chat_feedback(
+ SPAN_WARNING("[target] is operational; only fully disabled cyborgs may be restarted."),
+ target = target,
+ )
+ return FALSE
+ return TRUE
+
+/obj/item/robot_upgrade/restart/being_installed(mob/living/silicon/robot/target)
+ // TODO: standardize revival.
+ target.revive(TRUE, FALSE, FALSE)
+ if(!target.ckey)
+ for(var/mob/observer/dead/ghost in GLOB.player_list)
+ if(ghost.mind?.current == target)
+ ghost.transfer_client_to(target)
+ target.notify_ai(ROBOT_NOTIFICATION_NEW_UNIT)
+ qdel(src)
diff --git a/code/modules/mob/living/silicon/robot/items/robot_upgrade/sizeshift.dm b/code/modules/mob/living/silicon/robot/items/robot_upgrade/sizeshift.dm
new file mode 100644
index 00000000000..0c2778a25a8
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_upgrade/sizeshift.dm
@@ -0,0 +1,13 @@
+/obj/item/robot_upgrade/sizeshift
+ name = "robot size alteration module"
+ desc = "Using technology similar to one used in size guns, allows cyborgs to adjust their own size as necessary."
+ icon_state = "cyborg_upgrade2"
+ item_state = "cyborg_upgrade"
+
+/obj/item/robot_upgrade/language/on_install(mob/living/silicon/robot/target)
+ . = ..()
+ add_verb(target, /mob/living/proc/set_size)
+
+/obj/item/robot_upgrade/language/on_uninstall(mob/living/silicon/robot/target)
+ . = ..()
+ remove_verb(target, /mob/living/proc/set_size)
diff --git a/code/modules/mob/living/silicon/robot/items/robot_upgrade/syndicate.dm b/code/modules/mob/living/silicon/robot/items/robot_upgrade/syndicate.dm
new file mode 100644
index 00000000000..6d38aef444b
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_upgrade/syndicate.dm
@@ -0,0 +1,21 @@
+/obj/item/robot_upgrade/syndicate
+ name = "scrambled equipment module"
+ desc = "Unlocks new and often deadly module specific items of a robot"
+ icon_state = "cyborg_upgrade3"
+ item_state = "cyborg_upgrade"
+
+/obj/item/robot_upgrade/syndicate/can_install(mob/living/silicon/robot/target, robot_opinion, datum/event_args/actor/actor, silent)
+ . = ..()
+ if(!.)
+ return
+ if(target.get_emag_item_state())
+ if(!silent)
+ actor?.chat_feedback(
+ SPAN_WARNING("[target] already has scrambled equipment enabled."),
+ target = src,
+ )
+ return FALSE
+
+/obj/item/robot_upgrade/syndicate/being_installed(mob/living/silicon/robot/target)
+ target.set_emag_item_state(TRUE)
+ qdel(src)
diff --git a/code/modules/mob/living/silicon/robot/items/robot_upgrade/vtec.dm b/code/modules/mob/living/silicon/robot/items/robot_upgrade/vtec.dm
new file mode 100644
index 00000000000..3077faca6b3
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/items/robot_upgrade/vtec.dm
@@ -0,0 +1,66 @@
+/**
+ * this item is slated for removal as borgs get more upgrades
+ *
+ * full speed increase upgrades are bad design, sorry.
+ */
+/obj/item/robot_upgrade/vtec
+ name = "robotic VTEC Module"
+ desc = "Used to kick in a robot's VTEC systems, increasing their speed."
+ icon_state = "cyborg_upgrade2"
+ item_state = "cyborg_upgrade"
+
+ var/current_power_level = 0
+ var/current_tps_boost = 1
+
+ var/list/possible_tps_boosts = list(
+ 1,
+ 1.75,
+ 2.5,
+ )
+
+ upgrade_actions = list(
+ /datum/action/robot_upgrade_action/vtec,
+ )
+
+/obj/item/robot_upgrade/vtec/proc/set_power_level(level)
+ src.current_power_level = clamp(level, 0, length(possible_tps_boosts))
+ src.current_tps_boost = length(possible_tps_boosts) ? possible_tps_boosts[src.current_power_level] : 0
+ update_movespeed_modifier()
+
+/obj/item/robot_upgrade/vtec/on_install(mob/living/silicon/robot/target)
+ ..()
+ set_power_level(current_power_level)
+
+/obj/item/robot_upgrade/vtec/on_uninstall(mob/living/silicon/robot/target)
+ ..()
+ remove_movespeed_modifier()
+
+/obj/item/robot_upgrade/vtec/proc/user_toggle_tps_boost(datum/event_args/actor/actor)
+ set_power_level(current_power_level >= length(possible_tps_boosts) ? 0 : current_power_level + 1)
+ for(var/datum/action/action in upgrade_actions)
+ action.update_buttons()
+
+/obj/item/robot_upgrade/vtec/proc/update_movespeed_modifier()
+ owner.update_movespeed_modifier(/datum/movespeed_modifier/mob_vtec_upgrade, TRUE, list(
+ MOVESPEED_PARAM_MOD_TILES_PER_SECOND = current_tps_boost,
+ ))
+
+/obj/item/robot_upgrade/vtec/proc/remove_movespeed_modifier()
+ owner.remove_movespeed_modifier(/datum/movespeed_modifier/mob_vtec_upgrade)
+
+/datum/action/robot_upgrade_action/vtec
+ button_icon = 'icons/screen/actions/generic.dmi'
+ button_icon_state = "chevron-1"
+
+ var/vtec_chevron_icon_state_base = "chevron-"
+ var/vtec_chevron_icon_states = 3
+
+/datum/action/robot_upgrade_action/vtec/pre_render_hook()
+ . = ..()
+ var/obj/item/robot_upgrade/vtec/casted_target = target
+ button_icon_state = "[vtec_chevron_icon_state_base]\
+ [ceil((casted_target.current_power_level / length(casted_target.possible_tps_boosts)) * vtec_chevron_icon_states)]"
+
+/datum/action/robot_upgrade_action/vtec/invoke_target(obj/item/robot_upgrade/vtec/target, datum/event_args/actor/actor)
+ target.user_toggle_tps_boost(actor)
+ return ..()
diff --git a/code/modules/mob/living/silicon/robot/laws.dm b/code/modules/mob/living/silicon/robot/laws.dm
index 59a7160d4c2..d794249a21a 100644
--- a/code/modules/mob/living/silicon/robot/laws.dm
+++ b/code/modules/mob/living/silicon/robot/laws.dm
@@ -44,7 +44,7 @@
/mob/living/silicon/robot/lawsync()
laws_sanity_check()
- var/datum/ai_laws/master = connected_ai && lawupdate ? connected_ai.laws : null
+ var/datum/ai_lawset/master = connected_ai && lawupdate ? connected_ai.laws : null
if (master)
master.sync(src)
..()
diff --git a/code/game/objects/items/robot/gripper.dm b/code/modules/mob/living/silicon/robot/legacy_items/gripper.dm
similarity index 97%
rename from code/game/objects/items/robot/gripper.dm
rename to code/modules/mob/living/silicon/robot/legacy_items/gripper.dm
index 688aa5c48c1..20f46c36d14 100644
--- a/code/game/objects/items/robot/gripper.dm
+++ b/code/modules/mob/living/silicon/robot/legacy_items/gripper.dm
@@ -1,3 +1,5 @@
+// todo: this shouldn't be a robot item, this is generic.
+
//Simple borg hand.
//Limited use.
/obj/item/gripper
@@ -28,6 +30,8 @@
/// currently held item
VAR_PRIVATE/obj/item/wrapped
+ var/conf_inject_clickchain_flags
+
/obj/item/gripper/examine(mob/user, dist)
. = ..()
if(wrapped)
@@ -94,7 +98,7 @@
/obj/item/gripper/melee_interaction_chain(datum/event_args/actor/clickchain/clickchain, clickchain_flags)
if(!isnull(wrapped))
- return wrapped.melee_interaction_chain(clickchain, clickchain_flags | CLICKCHAIN_REDIRECTED)
+ return wrapped.melee_interaction_chain(clickchain, clickchain_flags | CLICKCHAIN_REDIRECTED | conf_inject_clickchain_flags)
return ..()
/obj/item/gripper/verb/drop_item()
@@ -169,6 +173,9 @@
can_hold = list(/obj/item) // Testing and Event gripper.
+/obj/item/gripper/omni/no_attack
+ conf_inject_clickchain_flags = CLICKCHAIN_DO_NOT_ATTACK
+
// VEEEEERY limited version for mining borgs. Basically only for swapping cells and upgrading the drills.
/obj/item/gripper/miner
name = "drill maintenance gripper"
@@ -233,7 +240,7 @@
/obj/item/stock_parts,
/obj/item/mmi,
/obj/item/robot_parts,
- /obj/item/borg/upgrade,
+ /obj/item/robot_upgrade,
/obj/item/flash, //to build borgs,
/obj/item/disk,
/obj/item/circuitboard,
diff --git a/code/game/objects/items/robot/robot_items.dm b/code/modules/mob/living/silicon/robot/legacy_items/robot_items.dm
similarity index 100%
rename from code/game/objects/items/robot/robot_items.dm
rename to code/modules/mob/living/silicon/robot/legacy_items/robot_items.dm
diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/modules/mob/living/silicon/robot/legacy_items/robot_parts.dm
similarity index 100%
rename from code/game/objects/items/robot/robot_parts.dm
rename to code/modules/mob/living/silicon/robot/legacy_items/robot_parts.dm
diff --git a/code/modules/mob/living/silicon/robot/life.dm b/code/modules/mob/living/silicon/robot/life.dm
index c728866c497..4f2b1520113 100644
--- a/code/modules/mob/living/silicon/robot/life.dm
+++ b/code/modules/mob/living/silicon/robot/life.dm
@@ -6,6 +6,13 @@
clamp_values()
handle_regular_UI_updates()
+ // TODO: actual smoothing?? or less awful-ify this lol
+ var/avg_power_elapsed = world.time - avg_power_last
+ if(avg_power_elapsed > 0)
+ avg_power = avg_power_accumulator / (avg_power_elapsed / (1 SECONDS))
+ avg_power_last = world.time
+ avg_power_accumulator = 0
+
/mob/living/silicon/robot/PhysicalLife(seconds, times_fired)
if((. = ..()))
return
@@ -14,8 +21,6 @@
update_items()
if (src.stat != DEAD) //still using power
use_power()
- process_killswitch()
- process_locks()
process_queued_alarms()
/mob/living/silicon/robot/proc/clamp_values()
@@ -25,29 +30,17 @@
effect.set_duration_from_now(20 SECONDS)
set_sleeping(0)
- adjustBruteLoss(0)
- adjustToxLoss(0)
- adjustOxyLoss(0)
- adjustFireLoss(0)
/mob/living/silicon/robot/proc/use_power()
- // Debug only
- // to_chat(world, "DEBUG: life.dm line 35: cyborg use_power() called at tick [controller_iteration]")
- used_power_this_tick = 0
for(var/V in components)
var/datum/robot_component/C = components[V]
C.update_power_state()
if ( cell && is_component_functioning("power cell") && src.cell.charge > 0 )
- if(src.module_state_1)
- cell_use_power(50) // 50W load for every enabled tool TODO: tool-specific loads
- if(src.module_state_2)
- cell_use_power(50)
- if(src.module_state_3)
- cell_use_power(50)
+ legacy_cell_use_power(50 * length(robot_inventory.inv_get_active()))
if(lights_on)
- cell_use_power(30) // 30W light. Normal lights would use ~15W, but increased for balance reasons.
+ legacy_cell_use_power(30) // 30W light. Normal lights would use ~15W, but increased for balance reasons.
src.has_power = 1
else
@@ -96,7 +89,7 @@
//update the state of modules and components here
if (src.stat != 0)
- uneq_all()
+ inventory.drop_held_items()
if(radio)
if(!is_component_functioning("radio"))
@@ -231,34 +224,8 @@
for(var/obj/I in src.contents)
if(I && !(istype(I,/obj/item/cell) || istype(I,/obj/item/radio) || istype(I,/obj/machinery/camera) || istype(I,/obj/item/mmi)))
src.client.screen += I
- if(src.module_state_1)
- src.module_state_1:screen_loc = ui_inv1
- if(src.module_state_2)
- src.module_state_2:screen_loc = ui_inv2
- if(src.module_state_3)
- src.module_state_3:screen_loc = ui_inv3
updateicon()
-/mob/living/silicon/robot/proc/process_killswitch()
- if(killswitch)
- killswitch_time --
- if(killswitch_time <= 0)
- if(src.client)
- to_chat(src, "Killswitch Activated")
- killswitch = 0
- spawn(5)
- gib()
-
-/mob/living/silicon/robot/proc/process_locks()
- if(weapon_lock)
- uneq_all()
- weaponlock_time --
- if(weaponlock_time <= 0)
- if(src.client)
- to_chat(src, "Weapon Lock Timed Out!")
- weapon_lock = 0
- weaponlock_time = 120
-
// todo: better way
/mob/living/silicon/robot/update_mobility()
. = ..()
diff --git a/code/modules/mob/living/silicon/robot/login.dm b/code/modules/mob/living/silicon/robot/login.dm
index e292a440981..9570f968c86 100644
--- a/code/modules/mob/living/silicon/robot/login.dm
+++ b/code/modules/mob/living/silicon/robot/login.dm
@@ -4,7 +4,3 @@
update_hud()
show_laws(0)
-
- // Forces synths to select an icon relevant to their module
- if(!icon_selected)
- choose_icon(icon_selection_tries, module_sprites)
diff --git a/code/modules/mob/living/silicon/robot/modules/cybersun.dm b/code/modules/mob/living/silicon/robot/modules/cybersun.dm
new file mode 100644
index 00000000000..df26b443ae8
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/cybersun.dm
@@ -0,0 +1,4 @@
+/datum/prototype/robot_module/cybersun
+ abstract_type = /datum/prototype/robot_module/cybersun
+ module_hud_state = "malf"
+ category = "Cybersun"
diff --git a/code/modules/mob/living/silicon/robot/modules/cybersun/assault.dm b/code/modules/mob/living/silicon/robot/modules/cybersun/assault.dm
new file mode 100644
index 00000000000..178a0fcdd40
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/cybersun/assault.dm
@@ -0,0 +1,45 @@
+GENERATE_ROBOT_MODULE_PRESET(/cybersun/assault)
+/datum/prototype/robot_module/cybersun/assault
+ id = "cybersun-assault"
+ display_name = "CS-COM"
+ visible_name = "Assault"
+ use_robot_module_path = /obj/item/robot_module_legacy/robot/syndicate
+ light_color = "#FF0000"
+ auto_iconsets = list(
+ /datum/prototype/robot_iconset/grounded_landmate/security_cybersun,
+ /datum/prototype/robot_iconset/grounded_landmate/security_cybersun_tread,
+ /datum/prototype/robot_iconset/raptor/syndicate_medical,
+ /datum/prototype/robot_iconset/biped_heavy/antag,
+ /datum/prototype/robot_iconset/grounded_spider/combat,
+ /datum/prototype/robot_iconset/baseline_misc/squats,
+ )
+
+/datum/prototype/robot_module/cybersun/assault/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ ..()
+ if(normal_out)
+ normal_out |= list(
+ /obj/item/shield_projector/rectangle/weak,
+ /obj/item/gun/projectile/energy/dakkalaser,
+ /obj/item/handcuffs/cyborg,
+ /obj/item/melee/baton/robot,
+ /obj/item/melee/transforming/energy/sword,
+ )
+
+/obj/item/robot_module_legacy/robot/syndicate
+ name = "illegal robot module"
+ languages = list(
+ LANGUAGE_SOL_COMMON = 1,
+ LANGUAGE_TRADEBAND = 1,
+ LANGUAGE_UNATHI = 0,
+ LANGUAGE_SIIK = 0,
+ LANGUAGE_AKHANI = 0,
+ LANGUAGE_SKRELLIAN = 0,
+ LANGUAGE_SKRELLIANFAR = 0,
+ LANGUAGE_ROOTLOCAL = 0,
+ LANGUAGE_GUTTER = 1,
+ LANGUAGE_SCHECHI = 0,
+ LANGUAGE_EAL = 1,
+ LANGUAGE_SIGN = 0,
+ LANGUAGE_TERMINUS = 1,
+ LANGUAGE_ZADDAT = 0
+ )
diff --git a/code/modules/mob/living/silicon/robot/modules/cybersun/sapper.dm b/code/modules/mob/living/silicon/robot/modules/cybersun/sapper.dm
new file mode 100644
index 00000000000..c213269102f
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/cybersun/sapper.dm
@@ -0,0 +1,51 @@
+GENERATE_ROBOT_MODULE_PRESET(/cybersun/sapper)
+/datum/prototype/robot_module/cybersun/sapper
+ id = "cybersun-sapper"
+ display_name = "CS-ENG"
+ visible_name = "Sapper"
+ use_robot_module_path = /obj/item/robot_module_legacy/robot/
+ light_color = "#FF0000"
+ auto_iconsets = list(
+ /datum/prototype/robot_iconset/grounded_landmate/engineering_cybersun,
+ /datum/prototype/robot_iconset/grounded_spider/combat,
+ /datum/prototype/robot_iconset/raptor/syndicate_machinist,
+ )
+
+/datum/prototype/robot_module/cybersun/sapper/provision_resource_store(datum/robot_resource_store/store)
+ ..()
+ store.provisioned_material_store[/datum/prototype/material/steel::id] = new /datum/robot_resource/provisioned/preset/material/steel
+ store.provisioned_material_store[/datum/prototype/material/glass::id] = new /datum/robot_resource/provisioned/preset/material/glass
+ store.provisioned_material_store[/datum/prototype/material/plasteel::id] = new /datum/robot_resource/provisioned/preset/material/plasteel
+ store.provisioned_material_store[/datum/prototype/material/wood_plank::id] = new /datum/robot_resource/provisioned/preset/material/wood
+ store.provisioned_material_store[/datum/prototype/material/plastic::id] = new /datum/robot_resource/provisioned/preset/material/plastic
+ store.provisioned_stack_store[/obj/item/stack/cable_coil] = new /datum/robot_resource/provisioned/preset/wire
+
+/datum/prototype/robot_module/cybersun/sapper/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ ..()
+ if(normal_out)
+ // todo: better way to do 'sum types'?
+ normal_out |= list(
+ /obj/item/stack/rods,
+ /obj/item/stack/tile/floor,
+ /obj/item/stack/tile/roofing,
+ /obj/item/stack/material/glass/reinforced,
+
+ /obj/item/borg/sight/meson,
+ /obj/item/weldingtool/electric/mounted/cyborg,
+ /obj/item/tool/screwdriver/cyborg,
+ /obj/item/tool/wrench/cyborg,
+ /obj/item/tool/wirecutters/cyborg,
+ /obj/item/multitool/ai_detector,
+ /obj/item/pickaxe/plasmacutter,
+ /obj/item/rcd/electric/mounted/borg/lesser, // Can't eat rwalls to prevent AI core cheese.
+ /obj/item/melee/transforming/energy/sword/ionic_rapier,
+
+ // FBP repair.
+ /obj/item/robotanalyzer,
+ /obj/item/shockpaddles/robot,
+ /obj/item/gripper/no_use/organ/robotics,
+
+ // Hacking other things.
+ /obj/item/card/robot/syndi,
+ /obj/item/card/emag,
+ )
diff --git a/code/modules/mob/living/silicon/robot/modules/cybersun/triage.dm b/code/modules/mob/living/silicon/robot/modules/cybersun/triage.dm
new file mode 100644
index 00000000000..f055c3bfae4
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/cybersun/triage.dm
@@ -0,0 +1,49 @@
+GENERATE_ROBOT_MODULE_PRESET(/cybersun/triage)
+/datum/prototype/robot_module/cybersun/triage
+ id = "cybersun-triage"
+ display_name = "CS-MED"
+ visible_name = "Triage"
+ use_robot_module_path = /obj/item/robot_module_legacy/robot/
+ light_color = "#FF0000"
+ auto_iconsets = list(
+ /datum/prototype/robot_iconset/baseline_toiletbot/syndicate,
+ /datum/prototype/robot_iconset/raptor/syndicate_medical,
+ )
+
+/datum/prototype/robot_module/cybersun/triage/provision_resource_store(datum/robot_resource_store/store)
+ ..()
+ store.provisioned_stack_store[/obj/item/stack/medical/advanced/bruise_pack] = new /datum/robot_resource/provisioned/preset/bandages/advanced
+ store.provisioned_stack_store[/obj/item/stack/medical/advanced/ointment] = new /datum/robot_resource/provisioned/preset/ointment/advanced
+ store.provisioned_stack_store[/obj/item/stack/nanopaste] = new /datum/robot_resource/provisioned/preset/nanopaste
+ store.provisioned_stack_store[/obj/item/stack/medical/splint] = new /datum/robot_resource/provisioned/preset/splints
+
+/datum/prototype/robot_module/cybersun/triage/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ ..()
+ if(normal_out)
+ normal_out |= list(
+ /obj/item/borg/sight/hud/med,
+ /obj/item/healthanalyzer/advanced,
+ /obj/item/reagent_containers/borghypo/merc,
+ /obj/item/reagent_containers/glass/beaker/large,
+
+ // Surgery things.
+ /obj/item/autopsy_scanner,
+ /obj/item/surgical/scalpel/cyborg,
+ /obj/item/surgical/hemostat/cyborg,
+ /obj/item/surgical/retractor/cyborg,
+ /obj/item/surgical/cautery/cyborg,
+ /obj/item/surgical/bonegel/cyborg,
+ /obj/item/surgical/FixOVein/cyborg,
+ /obj/item/surgical/bonesetter/cyborg,
+ /obj/item/surgical/circular_saw/cyborg,
+ /obj/item/surgical/surgicaldrill/cyborg,
+ /obj/item/gripper/no_use/organ,
+
+ // General healing.
+ /obj/item/gripper/medical,
+ /obj/item/shockpaddles/robot/combat,
+ /obj/item/reagent_containers/dropper, // Allows borg to fix necrosis apparently
+ /obj/item/reagent_containers/syringe/unbreakable,
+ /obj/item/robot_builtin/dog_mirrortool,
+ /obj/item/roller_holder,
+ )
diff --git a/code/modules/mob/living/silicon/robot/modules/drone.dm b/code/modules/mob/living/silicon/robot/modules/drone.dm
new file mode 100644
index 00000000000..e36f29f9a45
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/drone.dm
@@ -0,0 +1,98 @@
+/datum/prototype/robot_module/drone
+ id = "drone"
+ display_name = "NT Drone"
+ module_hud_state = "platform"
+ use_robot_module_path = /obj/item/robot_module_legacy/drone
+ auto_iconsets = list(
+ /datum/prototype/robot_iconset/baseline_drone/construction,
+ )
+
+/datum/prototype/robot_module/drone/provision_resource_store(datum/robot_resource_store/store)
+ ..()
+ store.provisioned_material_store[/datum/prototype/material/steel::id] = new /datum/robot_resource/provisioned/preset/material/steel
+ store.provisioned_material_store[/datum/prototype/material/glass::id] = new /datum/robot_resource/provisioned/preset/material/glass
+ store.provisioned_material_store[/datum/prototype/material/plasteel::id] = new /datum/robot_resource/provisioned/preset/material/plasteel
+ store.provisioned_material_store[/datum/prototype/material/wood_plank::id] = new /datum/robot_resource/provisioned/preset/material/wood
+ store.provisioned_material_store[/datum/prototype/material/plastic::id] = new /datum/robot_resource/provisioned/preset/material/plastic
+ store.provisioned_stack_store[/obj/item/stack/cable_coil] = new /datum/robot_resource/provisioned/preset/wire
+
+/datum/prototype/robot_module/drone/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ ..()
+ if(normal_out)
+ // todo: better way to do 'sum types'?
+ normal_out |= list(
+ /obj/item/stack/rods,
+ /obj/item/stack/tile/wood,
+ /obj/item/stack/tile/floor,
+ /obj/item/stack/tile/roofing,
+ /obj/item/stack/material/glass/reinforced,
+ /obj/item/borg/sight/meson,
+ /obj/item/weldingtool/electric/mounted/cyborg,
+ /obj/item/tool/screwdriver/cyborg,
+ /obj/item/tool/wrench/cyborg,
+ /obj/item/tool/crowbar/cyborg,
+ /obj/item/tool/wirecutters/cyborg,
+ /obj/item/multitool,
+ /obj/item/lightreplacer,
+ /obj/item/gripper,
+ /obj/item/mop,
+ /obj/item/gripper/no_use/loader,
+ /obj/item/extinguisher,
+ /obj/item/pipe_painter,
+ /obj/item/floor_painter,
+ /obj/item/t_scanner,
+ /obj/item/atmos_analyzer,
+ /obj/item/inflatable_dispenser/robot,
+ /obj/item/barrier_tape_roll/engineering,
+ /obj/item/pipe_dispenser,
+ /obj/item/tank/jetpack/carbondioxide,
+ )
+ if(emag_out)
+ emag_out |= list(
+ /obj/item/pickaxe/plasmacutter,
+ )
+
+GENERATE_ROBOT_MODULE_PRESET(/drone/construction)
+/datum/prototype/robot_module/drone/construction
+ id = "drone-construction"
+ display_name = "NT Construction Drone"
+ auto_iconsets = list(
+ /datum/prototype/robot_iconset/baseline_drone/construction,
+ )
+
+/datum/prototype/robot_module/drone/construction/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ ..()
+ if(normal_out)
+ normal_out |= list(
+ /obj/item/rcd/electric/mounted/borg/lesser,
+ )
+
+GENERATE_ROBOT_MODULE_PRESET(/drone/mining)
+/datum/prototype/robot_module/drone/mining
+ id = "drone-mining"
+ display_name = "NT Mining Drone"
+ auto_iconsets = list(
+ /datum/prototype/robot_iconset/baseline_drone/mining,
+ )
+
+/datum/prototype/robot_module/drone/mining/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ ..()
+ if(normal_out)
+ normal_out |= list(
+ /obj/item/borg/sight/material,
+ /obj/item/pickaxe/borgdrill,
+ /obj/item/gun/projectile/energy/kinetic_accelerator/cyborg,
+ /obj/item/storage/bag/ore,
+ /obj/item/storage/bag/sheetsnatcher/borg,
+ )
+ if(emag_out)
+ emag_out |= list(
+ /obj/item/pickaxe/diamonddrill,
+ )
+
+// todo: legacy crap
+/obj/item/robot_module_legacy/drone
+ channels = list("Engineering" = 1, "Supply" = 1)
+ networks = list(NETWORK_ENGINEERING, NETWORK_MINE)
+
+/obj/item/robot_module_legacy/drone/construction/matriarch
diff --git a/code/modules/mob/living/silicon/robot/modules/gravekeeper.dm b/code/modules/mob/living/silicon/robot/modules/gravekeeper.dm
new file mode 100644
index 00000000000..e3be12b9626
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/gravekeeper.dm
@@ -0,0 +1,44 @@
+GENERATE_ROBOT_MODULE_PRESET(/gravekeeper)
+/datum/prototype/robot_module/gravekeeper
+ id = "gravekeeper"
+ use_robot_module_path = /obj/item/robot_module_legacy/robot/
+ light_color = "#AAAA00"
+ module_hud_state = "recon"
+ auto_iconsets = list(
+ /datum/prototype/robot_iconset/biped_sleek/gravekeeper,
+ /datum/prototype/robot_iconset/hover_drone/gravekeeper,
+ /datum/prototype/robot_iconset/raptor/gravekeeper,
+ )
+
+/datum/prototype/robot_module/gravekeeper/provision_resource_store(datum/robot_resource_store/store)
+ ..()
+ store.provisioned_material_store[/datum/prototype/material/wood_plank::id] = new /datum/robot_resource/provisioned/preset/material/wood
+
+/datum/prototype/robot_module/gravekeeper/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ ..()
+ if(normal_out)
+ normal_out |= list(
+ // For fending off animals and looters
+ /obj/item/melee/baton/shocker/robot,
+ /obj/item/borg/combat/shield,
+
+ // For repairing gravemarkers
+ /obj/item/weldingtool/electric/mounted,
+ /obj/item/tool/screwdriver/cyborg,
+ /obj/item/tool/wrench/cyborg,
+
+ // For growing flowers
+ /obj/item/material/minihoe,
+ /obj/item/material/knife/machete/hatchet,
+ /obj/item/plant_analyzer,
+ /obj/item/storage/bag/plants,
+ /obj/item/robot_harvester,
+
+ // For digging and beautifying graves
+ /obj/item/shovel,
+ /obj/item/gripper/gravekeeper,
+ )
+ if(emag_out)
+ emag_out |= list(
+ /obj/item/gun/projectile/energy/retro/mounted,
+ )
diff --git a/code/modules/mob/living/silicon/robot/modules/lost.dm b/code/modules/mob/living/silicon/robot/modules/lost.dm
new file mode 100644
index 00000000000..047f76807d2
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/lost.dm
@@ -0,0 +1,54 @@
+GENERATE_ROBOT_MODULE_PRESET(/lost)
+/datum/prototype/robot_module/lost
+ id = "lost"
+ display_name = "???"
+ light_color = "#AAAA00"
+ module_hud_state = "recon"
+ auto_iconsets = list(
+ /datum/prototype/robot_iconset/hover_drone/lost,
+ /datum/prototype/robot_iconset/dog_vale/stray,
+ /datum/prototype/robot_iconset/raptor/lost,
+ )
+
+/datum/prototype/robot_module/lost/provision_resource_store(datum/robot_resource_store/store)
+ ..()
+ store.provisioned_stack_store[/obj/item/stack/cable_coil] = new /datum/robot_resource/provisioned/preset/wire
+
+/datum/prototype/robot_module/lost/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ ..()
+ if(normal_out)
+ normal_out |= list(
+ // Sec
+ /obj/item/melee/baton/shocker/robot,
+ /obj/item/handcuffs/cyborg,
+ /obj/item/borg/combat/shield,
+
+ // Med
+ /obj/item/healthanalyzer,
+ /obj/item/reagent_containers/borghypo/lost,
+ /obj/item/shockpaddles/robot/hound,
+ /obj/item/robot_builtin/dog_mirrortool,
+
+ // Engi
+ /obj/item/weldingtool/electric/mounted,
+ /obj/item/switchtool,
+ /obj/item/multitool,
+
+ // logistics
+ /obj/item/pickaxe/borgdrill,
+ /obj/item/storage/bag/ore,
+
+ // Sci
+ /obj/item/robotanalyzer,
+
+ // service
+ /obj/item/soap/nanotrasen,
+
+ // haha fuck you it's the hour of the standard borg, yeehaw
+ /obj/item/gripper/omni/no_attack,
+ /obj/item/gripper/omni/no_attack,
+ )
+ if(emag_out)
+ emag_out |= list(
+ /obj/item/gun/projectile/energy/retro/mounted,
+ )
diff --git a/code/modules/mob/living/silicon/robot/modules/nanotrasen.dm b/code/modules/mob/living/silicon/robot/modules/nanotrasen.dm
new file mode 100644
index 00000000000..2b7d39b682a
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/nanotrasen.dm
@@ -0,0 +1,8 @@
+/datum/prototype/robot_module/nanotrasen
+ abstract_type = /datum/prototype/robot_module/nanotrasen
+ selectable = TRUE
+ legacy_show_on_manifest = TRUE
+ module_hud_state = "standard"
+ selection_groups_all = list(
+ ROBOT_MODULE_SELECTION_GROUP_NANOTRASEN,
+ )
diff --git a/code/modules/mob/living/silicon/robot/modules/nanotrasen/combat.dm b/code/modules/mob/living/silicon/robot/modules/nanotrasen/combat.dm
new file mode 100644
index 00000000000..dffb984bcf2
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/nanotrasen/combat.dm
@@ -0,0 +1,35 @@
+GENERATE_ROBOT_MODULE_PRESET(/nanotrasen/combat)
+/datum/prototype/robot_module/nanotrasen/combat
+ id = "nt-combat"
+ display_name = "NT-LANCER"
+ visible_name = "Combat"
+ use_robot_module_path = /obj/item/robot_module_legacy/robot/security
+ module_hud_state = "combat"
+ light_color = "#FF0000"
+ auto_iconsets = list(
+ /datum/prototype/robot_iconset/baseline_droid/combat,
+ /datum/prototype/robot_iconset/hover_eyebot/combat,
+ /datum/prototype/robot_iconset/biped_insekt/combat,
+ /datum/prototype/robot_iconset/grounded_zoomba/combat,
+ /datum/prototype/robot_iconset/cat_feli/combat,
+ /datum/prototype/robot_iconset/biped_marina/combat,
+ /datum/prototype/robot_iconset/grounded_mechoid/combat,
+ /datum/prototype/robot_iconset/dog_k9/blade,
+ )
+
+/datum/prototype/robot_module/nanotrasen/combat/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ if(normal_out)
+ normal_out |= list(
+ /obj/item/gun/projectile/energy/laser/mounted,
+ /obj/item/pickaxe/plasmacutter,
+ /obj/item/borg/combat/shield,
+ /obj/item/borg/combat/mobility,
+ /obj/item/handcuffs/cyborg,
+ /obj/item/melee/baton/robot,
+ /obj/item/barrier_tape_roll/police,
+ )
+ if(emag_out)
+ emag_out |= list(
+ /obj/item/gun/projectile/energy/lasercannon/mounted,
+ )
+ return ..()
diff --git a/code/modules/mob/living/silicon/robot/modules/nanotrasen/engineering.dm b/code/modules/mob/living/silicon/robot/modules/nanotrasen/engineering.dm
new file mode 100644
index 00000000000..ef2845e4015
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/nanotrasen/engineering.dm
@@ -0,0 +1,98 @@
+GENERATE_ROBOT_MODULE_PRESET(/nanotrasen/engineering)
+/datum/prototype/robot_module/nanotrasen/engineering
+ id = "nt-engineering"
+ display_name = "NT-ENG"
+ visible_name = "Engineering"
+ use_robot_module_path = /obj/item/robot_module_legacy/robot/engineering
+ module_hud_state = "engineering"
+ light_color = "#FDD800"
+ auto_iconsets = list(
+ /datum/prototype/robot_iconset/grounded_landmate/engineering,
+ /datum/prototype/robot_iconset/grounded_landmate/engineering_tread,
+ /datum/prototype/robot_iconset/grounded_landmate/treadwell,
+ /datum/prototype/robot_iconset/dog_borgi/engineering,
+ /datum/prototype/robot_iconset/dog_k9/engineering,
+ /datum/prototype/robot_iconset/dog_k9/engineering_dark,
+ /datum/prototype/robot_iconset/dog_otie/engineering,
+ /datum/prototype/robot_iconset/dog_pupdozer/engineering,
+ /datum/prototype/robot_iconset/dog_vale/engineering,
+ /datum/prototype/robot_iconset/cat_feli/engineering,
+ /datum/prototype/robot_iconset/drake_mizartz/engineering,
+ /datum/prototype/robot_iconset/hover_eyebot/engineering,
+ /datum/prototype/robot_iconset/baseline_toiletbot/engineering,
+ /datum/prototype/robot_iconset/biped_marina/engineering,
+ /datum/prototype/robot_iconset/biped_sleek/engineering,
+ /datum/prototype/robot_iconset/grounded_spider/engineering,
+ /datum/prototype/robot_iconset/biped_heavy/engineering,
+ /datum/prototype/robot_iconset/hover_drone/engineering,
+ /datum/prototype/robot_iconset/hover_handy/engineering,
+ /datum/prototype/robot_iconset/hover_glitterfly/engineering,
+ /datum/prototype/robot_iconset/hover_coffin/engineering,
+ /datum/prototype/robot_iconset/hover_coffin/construction,
+ /datum/prototype/robot_iconset/biped_miss/engineering,
+ /datum/prototype/robot_iconset/biped_tall/alternative/engineer,
+ /datum/prototype/robot_iconset/biped_marina/engineering,
+ /datum/prototype/robot_iconset/grounded_zoomba/engineering,
+ /datum/prototype/robot_iconset/hover_x88/engineering,
+ /datum/prototype/robot_iconset/biped_noble/engineering,
+ /datum/prototype/robot_iconset/grounded_mechoid/engineering,
+ /datum/prototype/robot_iconset/grounded_worm/engineering,
+ /datum/prototype/robot_iconset/baseline_standard/engineering,
+ /datum/prototype/robot_iconset/baseline_old/hazardvest,
+ /datum/prototype/robot_iconset/raptor/engineering,
+ /datum/prototype/robot_iconset/baseline_old/engineer,
+ )
+
+/datum/prototype/robot_module/nanotrasen/engineering/provision_resource_store(datum/robot_resource_store/store)
+ ..()
+ store.provisioned_material_store[/datum/prototype/material/steel::id] = new /datum/robot_resource/provisioned/preset/material/steel
+ store.provisioned_material_store[/datum/prototype/material/glass::id] = new /datum/robot_resource/provisioned/preset/material/glass
+ store.provisioned_material_store[/datum/prototype/material/plasteel::id] = new /datum/robot_resource/provisioned/preset/material/plasteel
+ store.provisioned_material_store[/datum/prototype/material/wood_plank::id] = new /datum/robot_resource/provisioned/preset/material/wood
+ store.provisioned_material_store[/datum/prototype/material/plastic::id] = new /datum/robot_resource/provisioned/preset/material/plastic
+ store.provisioned_stack_store[/obj/item/stack/cable_coil] = new /datum/robot_resource/provisioned/preset/wire
+
+/datum/prototype/robot_module/nanotrasen/engineering/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ ..()
+ if(normal_out)
+ // todo: better way to do 'sum types'?
+ normal_out |= list(
+ /obj/item/stack/rods,
+ /obj/item/stack/tile/wood,
+ /obj/item/stack/tile/floor,
+ /obj/item/stack/tile/roofing,
+ /obj/item/stack/material/glass/reinforced,
+ )
+ normal_out |= list(
+ /obj/item/matter_decompiler,
+ /obj/item/borg/sight/meson,
+ /obj/item/weldingtool/electric/mounted/cyborg,
+ /obj/item/tool/screwdriver/cyborg,
+ /obj/item/tool/wrench/cyborg,
+ /obj/item/tool/wirecutters/cyborg,
+ /obj/item/multitool,
+ /obj/item/t_scanner,
+ /obj/item/rcd/electric/mounted/borg,
+ /obj/item/atmos_analyzer,
+ /obj/item/barrier_tape_roll/engineering,
+ /obj/item/inflatable_dispenser/robot,
+ /obj/item/pickaxe/plasmacutter,
+ /obj/item/geiger_counter/cyborg,
+ /obj/item/pipe_painter,
+ /obj/item/floor_painter,
+ /obj/item/gripper,
+ /obj/item/gripper/no_use/loader,
+ /obj/item/pipe_dispenser,
+ /obj/item/gripper/circuit,
+ /obj/item/lightreplacer,
+ )
+ if(emag_out)
+ emag_out |= list(
+ /obj/item/melee/baton/robot/arm,
+ )
+
+// todo: legacy
+/obj/item/robot_module_legacy/robot/engineering
+ channels = list("Engineering" = 1)
+ networks = list(NETWORK_ENGINEERING)
+ subsystems = list(/mob/living/silicon/proc/subsystem_power_monitor)
diff --git a/code/modules/mob/living/silicon/robot/modules/nanotrasen/janitor.dm b/code/modules/mob/living/silicon/robot/modules/nanotrasen/janitor.dm
new file mode 100644
index 00000000000..68e407f4e2c
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/nanotrasen/janitor.dm
@@ -0,0 +1,76 @@
+GENERATE_ROBOT_MODULE_PRESET(/nanotrasen/janitor)
+/datum/prototype/robot_module/nanotrasen/janitor
+ id = "nt-janitor"
+ display_name = "NT-CLN"
+ visible_name = "Janitorial"
+ use_robot_module_path = /obj/item/robot_module_legacy/robot/janitor
+ module_hud_state = "janitor"
+ light_color = "#CC60FF"
+ auto_iconsets = list(
+ /datum/prototype/robot_iconset/grounded_landmate/janitor,
+ /datum/prototype/robot_iconset/baseline_toiletbot/janitor,
+ /datum/prototype/robot_iconset/hover_eyebot/janitor,
+ /datum/prototype/robot_iconset/biped_marina/janitor,
+ /datum/prototype/robot_iconset/biped_sleek/janitor,
+ /datum/prototype/robot_iconset/biped_tall/alternative/janitor,
+ /datum/prototype/robot_iconset/hover_drone/janitor,
+ /datum/prototype/robot_iconset/hover_glitterfly/janitor,
+ /datum/prototype/robot_iconset/biped_miss/janitor,
+ /datum/prototype/robot_iconset/hover_handy/janitor,
+ /datum/prototype/robot_iconset/grounded_mechoid/janitor,
+ /datum/prototype/robot_iconset/grounded_zoomba/janitor,
+ /datum/prototype/robot_iconset/biped_noble/janitor,
+ /datum/prototype/robot_iconset/grounded_worm/janitor,
+ /datum/prototype/robot_iconset/dog_borgi/janitor,
+ /datum/prototype/robot_iconset/dog_k9/janitor,
+ /datum/prototype/robot_iconset/dog_otie/janitor,
+ /datum/prototype/robot_iconset/dog_vale/janitor,
+ /datum/prototype/robot_iconset/drake_mizartz/janitor,
+ /datum/prototype/robot_iconset/cat_feli/janitor,
+ /datum/prototype/robot_iconset/raptor,
+ /datum/prototype/robot_iconset/baseline_standard/janitor,
+ /datum/prototype/robot_iconset/baseline_old/janitor,
+ /datum/prototype/robot_iconset/baseline_old/janitor_2,
+ /datum/prototype/robot_iconset/hover_coffin/custodial,
+ /datum/prototype/robot_iconset/baseline_misc/arachne,
+ )
+
+/datum/prototype/robot_module/nanotrasen/janitor/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ ..()
+ if(normal_out)
+ normal_out |= list(
+ /obj/item/soap/nanotrasen,
+ /obj/item/storage/bag/trash,
+ /obj/item/mop/advanced,
+ /obj/item/lightreplacer,
+ /obj/item/gripper/service,
+ )
+ if(emag_out)
+ var/obj/item/reagent_containers/spray/lube_spray = new
+ lube_spray.reagents.add_reagent(/datum/reagent/lube, 250)
+ lube_spray.name = "lube spray"
+ emag_out |= lube_spray
+
+/datum/prototype/robot_module/nanotrasen/janitor/provision_resource_store(datum/robot_resource_store/store)
+ ..()
+ store.provisioned_material_store[/datum/prototype/material/glass::id] = new /datum/robot_resource/provisioned/preset/material/glass{regen_per_second = 1 * SHEET_MATERIAL_AMOUNT}
+
+/datum/prototype/robot_module/nanotrasen/janitor/on_install(mob/living/silicon/robot/robot)
+ ..()
+ robot.legacy_floor_scrubbing = TRUE
+
+/datum/prototype/robot_module/nanotrasen/janitor/on_uninstall(mob/living/silicon/robot/robot)
+ ..()
+ robot.legacy_floor_scrubbing = FALSE
+
+// todo: this is evil
+/datum/prototype/robot_module/nanotrasen/janitor/legacy_custom_regenerate_resources(mob/living/silicon/robot/robot, dt, multiplier)
+ ..()
+ for(var/obj/item/reagent_containers/spray/maybe_evil_lube_spray in robot.robot_inventory.provided_items)
+ if(maybe_evil_lube_spray.name != "lube spray")
+ continue
+ maybe_evil_lube_spray.reagents?.add_reagent(/datum/reagent/lube::id, 2 * dt)
+
+// todo: legacy
+/obj/item/robot_module_legacy/robot/janitor
+ channels = list("Service" = 1)
diff --git a/code/modules/mob/living/silicon/robot/modules/nanotrasen/logistics.dm b/code/modules/mob/living/silicon/robot/modules/nanotrasen/logistics.dm
new file mode 100644
index 00000000000..2c95bce2f32
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/nanotrasen/logistics.dm
@@ -0,0 +1,55 @@
+GENERATE_ROBOT_MODULE_PRESET(/nanotrasen/logistics)
+/datum/prototype/robot_module/nanotrasen/logistics
+ id = "nt-logistics"
+ display_name = "NT-LGN"
+ display_name = "Logistics"
+ use_robot_module_path = /obj/item/robot_module_legacy/robot/logistics
+ module_hud_state = "mining"
+ light_color = "#FBE281"
+ auto_iconsets = list(
+ /datum/prototype/robot_iconset/grounded_landmate/mining,
+ /datum/prototype/robot_iconset/dog_k9/logistics,
+ /datum/prototype/robot_iconset/dog_k9/logistics_dark,
+ /datum/prototype/robot_iconset/dog_vale/mining,
+ /datum/prototype/robot_iconset/drake_mizartz/mining,
+ /datum/prototype/robot_iconset/cat_feli/mining,
+ /datum/prototype/robot_iconset/hover_eyebot/mining,
+ /datum/prototype/robot_iconset/biped_marina/miner,
+ /datum/prototype/robot_iconset/biped_heavy/miner,
+ /datum/prototype/robot_iconset/grounded_spider/mining,
+ /datum/prototype/robot_iconset/baseline_toiletbot/logistics,
+ /datum/prototype/robot_iconset/biped_sleek/mining,
+ /datum/prototype/robot_iconset/hover_drone/mining,
+ /datum/prototype/robot_iconset/hover_glitterfly/mining,
+ /datum/prototype/robot_iconset/biped_miss/mining,
+ /datum/prototype/robot_iconset/hover_handy/mining,
+ /datum/prototype/robot_iconset/grounded_mechoid/miner,
+ /datum/prototype/robot_iconset/biped_tall/alternative/miner,
+ /datum/prototype/robot_iconset/biped_noble/mining,
+ /datum/prototype/robot_iconset/grounded_zoomba/miner,
+ /datum/prototype/robot_iconset/grounded_worm/miner,
+ /datum/prototype/robot_iconset/raptor/mining,
+ /datum/prototype/robot_iconset/baseline_standard/logistics,
+ /datum/prototype/robot_iconset/hover_coffin/mining,
+ )
+
+/datum/prototype/robot_module/nanotrasen/logistics/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ if(normal_out)
+ normal_out |= list(
+ /obj/item/borg/sight/meson,
+ /obj/item/switchtool,
+ /obj/item/weldingtool/electric/mounted,
+ /obj/item/storage/bag/ore,
+ /obj/item/pickaxe/borgdrill,
+ /obj/item/gun/projectile/energy/kinetic_accelerator/cyborg,
+ /obj/item/storage/bag/sheetsnatcher/borg,
+ /obj/item/gripper/miner,
+ /obj/item/mining_scanner,
+ /obj/item/pickaxe/plasmacutter,
+ )
+ return ..()
+
+// todo: legacy
+/obj/item/robot_module_legacy/robot/logistics
+ channels = list("Supply" = 1)
+ networks = list(NETWORK_MINE)
diff --git a/code/modules/mob/living/silicon/robot/modules/nanotrasen/medical.dm b/code/modules/mob/living/silicon/robot/modules/nanotrasen/medical.dm
new file mode 100644
index 00000000000..bae95923615
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/nanotrasen/medical.dm
@@ -0,0 +1,98 @@
+GENERATE_ROBOT_MODULE_PRESET(/nanotrasen/medical)
+/datum/prototype/robot_module/nanotrasen/medical
+ id = "nt-medical"
+ display_name = "NT-MED"
+ visible_name = "Medical"
+ use_robot_module_path = /obj/item/robot_module_legacy/robot/medical
+ module_hud_state = "medical"
+ light_color = "#0099FF"
+ auto_iconsets = list(
+ /datum/prototype/robot_iconset/baseline_toiletbot/medical,
+ /datum/prototype/robot_iconset/dog_borgi/medical,
+ /datum/prototype/robot_iconset/dog_k9/medical,
+ /datum/prototype/robot_iconset/dog_k9/medical_dark,
+ /datum/prototype/robot_iconset/dog_vale/medical,
+ /datum/prototype/robot_iconset/drake_mizartz/medical,
+ /datum/prototype/robot_iconset/cat_feli/medical,
+ /datum/prototype/robot_iconset/baseline_standard/medical,
+ /datum/prototype/robot_iconset/hover_eyebot/medical,
+ /datum/prototype/robot_iconset/biped_marina/medical,
+ /datum/prototype/robot_iconset/biped_tall/tallwhite,
+ /datum/prototype/robot_iconset/biped_tall/alternative/medical,
+ /datum/prototype/robot_iconset/biped_sleek/cmo,
+ /datum/prototype/robot_iconset/biped_sleek/medical,
+ /datum/prototype/robot_iconset/biped_heavy/medical,
+ /datum/prototype/robot_iconset/baseline_old/medical,
+ /datum/prototype/robot_iconset/biped_droid/medical,
+ /datum/prototype/robot_iconset/hover_drone/medical,
+ /datum/prototype/robot_iconset/hover_handy/medical,
+ /datum/prototype/robot_iconset/hover_drone/surgery,
+ /datum/prototype/robot_iconset/biped_insekt/medical,
+ /datum/prototype/robot_iconset/hover_glitterfly/surgeon,
+ /datum/prototype/robot_iconset/biped_miss/medical,
+ /datum/prototype/robot_iconset/hover_x88/medical,
+ /datum/prototype/robot_iconset/grounded_mechoid/medical,
+ /datum/prototype/robot_iconset/biped_noble/medical,
+ /datum/prototype/robot_iconset/grounded_zoomba/medical,
+ /datum/prototype/robot_iconset/grounded_worm/crisis,
+ /datum/prototype/robot_iconset/raptor/medical,
+ /datum/prototype/robot_iconset/hover_coffin/medical,
+ /datum/prototype/robot_iconset/baseline_old/medical_2,
+ )
+
+/datum/prototype/robot_module/nanotrasen/medical/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ ..()
+ if(normal_out)
+ normal_out |= list(
+ /obj/item/borg/sight/hud/med,
+ /obj/item/healthanalyzer/advanced,
+ /obj/item/reagent_containers/borghypo/surgeon,
+ /obj/item/reagent_containers/glass/beaker/large,
+ /obj/item/reagent_scanner/adv,
+
+ // Surgery things.
+ /obj/item/autopsy_scanner,
+ /obj/item/surgical/scalpel/cyborg,
+ /obj/item/surgical/hemostat/cyborg,
+ /obj/item/surgical/retractor/cyborg,
+ /obj/item/surgical/cautery/cyborg,
+ /obj/item/surgical/bonegel/cyborg,
+ /obj/item/surgical/FixOVein/cyborg,
+ /obj/item/surgical/bonesetter/cyborg,
+ /obj/item/surgical/circular_saw/cyborg,
+ /obj/item/surgical/surgicaldrill/cyborg,
+ /obj/item/gripper/no_use/organ,
+
+ /obj/item/gripper/medical,
+ /obj/item/shockpaddles/robot,
+ /obj/item/reagent_containers/dropper, // Allows surgeon borg to fix necrosis
+ /obj/item/reagent_containers/syringe/unbreakable,
+ /obj/item/roller_holder,
+ /obj/item/robot_builtin/dog_mirrortool,
+ )
+ if(emag_out)
+ var/obj/item/reagent_containers/spray/acid_spray = new
+ acid_spray.reagents.add_reagent(/datum/reagent/acid/polyacid::id, 250)
+ acid_spray.name = "Polyacid spray"
+ emag_out |= acid_spray
+
+/datum/prototype/robot_module/nanotrasen/medical/provision_resource_store(datum/robot_resource_store/store)
+ ..()
+ store.provisioned_stack_store[/obj/item/stack/medical/advanced/bruise_pack] = new /datum/robot_resource/provisioned/preset/bandages/advanced
+ store.provisioned_stack_store[/obj/item/stack/medical/advanced/ointment] = new /datum/robot_resource/provisioned/preset/ointment/advanced
+ store.provisioned_stack_store[/obj/item/stack/medical/splint] = new /datum/robot_resource/provisioned/preset/splints
+ store.provisioned_stack_store[/obj/item/stack/nanopaste] = new /datum/robot_resource/provisioned/preset/nanopaste
+
+// todo: this is evil
+/datum/prototype/robot_module/nanotrasen/medical/legacy_custom_regenerate_resources(mob/living/silicon/robot/robot, dt, multiplier)
+ ..()
+ for(var/obj/item/reagent_containers/spray/maybe_evil_acid_spray in robot.robot_inventory.provided_items)
+ if(maybe_evil_acid_spray.name != "Polyacid spray")
+ continue
+ maybe_evil_acid_spray.reagents?.add_reagent(/datum/reagent/acid/polyacid::id, 2 * dt)
+
+// todo: legacy
+/obj/item/robot_module_legacy/robot/medical
+ channels = list("Medical" = 1)
+ networks = list(NETWORK_MEDICAL)
+ subsystems = list(/mob/living/silicon/proc/subsystem_crew_monitor)
diff --git a/code/modules/mob/living/silicon/robot/modules/nanotrasen/multirole.dm b/code/modules/mob/living/silicon/robot/modules/nanotrasen/multirole.dm
new file mode 100644
index 00000000000..405ba086349
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/nanotrasen/multirole.dm
@@ -0,0 +1,99 @@
+GENERATE_ROBOT_MODULE_PRESET(/nanotrasen/multirole)
+/datum/prototype/robot_module/nanotrasen/multirole
+ id = "nt-multirole"
+ display_name = "NT-STD"
+ visible_name = "Multirole"
+ use_robot_module_path = /obj/item/robot_module_legacy/robot
+ light_color = "#FFFFFF"
+ module_hud_state = "standard"
+ auto_iconsets = list(
+ /datum/prototype/robot_iconset/baseline_standard/standard,
+ /datum/prototype/robot_iconset/biped_k4t,
+ /datum/prototype/robot_iconset/baseline_old/standard,
+ /datum/prototype/robot_iconset/hover_eyebot/standard,
+ /datum/prototype/robot_iconset/biped_marina/standard,
+ /datum/prototype/robot_iconset/biped_tall/tallflower,
+ /datum/prototype/robot_iconset/baseline_toiletbot/standard,
+ /datum/prototype/robot_iconset/biped_sleek/standard,
+ /datum/prototype/robot_iconset/grounded_spider/standard,
+ /datum/prototype/robot_iconset/biped_heavy/standard,
+ /datum/prototype/robot_iconset/cat_feli/standard,
+ /datum/prototype/robot_iconset/baseline_misc/omoikane,
+ /datum/prototype/robot_iconset/hover_drone/standard,
+ /datum/prototype/robot_iconset/biped_insekt/standard,
+ /datum/prototype/robot_iconset/biped_tall/alternative/standard,
+ /datum/prototype/robot_iconset/hover_glitterfly/standard,
+ /datum/prototype/robot_iconset/biped_miss/standard,
+ /datum/prototype/robot_iconset/hover_x88/standard,
+ /datum/prototype/robot_iconset/hover_coffin/standard,
+ /datum/prototype/robot_iconset/hover_handy/standard,
+ /datum/prototype/robot_iconset/grounded_mechoid/standard,
+ /datum/prototype/robot_iconset/biped_noble/standard,
+ /datum/prototype/robot_iconset/grounded_zoomba/standard,
+ /datum/prototype/robot_iconset/grounded_worm/standard,
+ /datum/prototype/robot_iconset/raptor/peacekeeper,
+ /datum/prototype/robot_iconset/baseline_droid/standard,
+ /datum/prototype/robot_iconset/dog_borgi/standard,
+ /datum/prototype/robot_iconset/dog_vale/standard,
+ )
+
+//! ~~~~~ "Holy crap, why is standard so OP" ~~~~~ !//
+//! It isn't. The rest of the modules are shit. !//
+//! I don't know why SS13 standardized to "mob that has scrubbing and trash bag only" !//
+//! but we don't do that here. Infact, I'd buff standard more, but I'm out of !//
+//! interesting items to give them because every coder here's busy coding guns. !//
+//! !//
+//! We should move to modular cyborgs ASAP, but until we do, the name of the game !//
+//! is going to be making them not miserable to play. !//
+//! If you're stuck listening to other players' orders, might as well have some fun. !//
+
+/datum/prototype/robot_module/nanotrasen/multirole/provision_resource_store(datum/robot_resource_store/store)
+ ..()
+ store.provisioned_material_store[/datum/prototype/material/steel::id] = new /datum/robot_resource/provisioned/preset/material/steel{
+ amount_max = 20 * SHEET_MATERIAL_AMOUNT;
+ }
+ store.provisioned_material_store[/datum/prototype/material/plastic::id] = new /datum/robot_resource/provisioned/preset/material/plastic{
+ amount_max = 10 * SHEET_MATERIAL_AMOUNT;
+ }
+ store.provisioned_material_store[/datum/prototype/material/glass::id] = new /datum/robot_resource/provisioned/preset/material/glass{
+ amount_max = 20 * SHEET_MATERIAL_AMOUNT;
+ }
+ store.provisioned_stack_store[/obj/item/stack/medical/bruise_pack] = new /datum/robot_resource/provisioned/preset/bandages{
+ amount_max = 30;
+ }
+ store.provisioned_stack_store[/obj/item/stack/medical/ointment] = new /datum/robot_resource/provisioned/preset/ointment{
+ amount_max = 30;
+ }
+ store.provisioned_stack_store[/obj/item/stack/nanopaste] = new /datum/robot_resource/provisioned/preset/nanopaste{
+ amount_max = 10;
+ }
+
+/datum/prototype/robot_module/nanotrasen/multirole/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ ..()
+ if(normal_out)
+ normal_out |= list(
+ // security
+ /obj/item/melee/baton/shocker,
+ // engineering
+ /obj/item/switchtool,
+ /obj/item/weldingtool/electric/mounted,
+ /obj/item/multitool,
+ // medical
+ /obj/item/healthanalyzer,
+ /obj/item/reagent_containers/dropper,
+ /obj/item/reagent_containers/borghypo/stabilizer,
+ // logistics
+ /obj/item/pickaxe/borgdrill,
+ /obj/item/storage/bag/ore,
+ // science
+ /obj/item/robotanalyzer,
+ // service
+ /obj/item/soap/nanotrasen,
+ // haha fuck you it's the hour of the standard borg, yeehaw
+ /obj/item/gripper/omni/no_attack,
+ )
+ if(emag_out)
+ // LET'S FUCKIN GOOOOOOO
+ emag_out |= list(
+ /obj/item/gun/projectile/energy/ermitter,
+ )
diff --git a/code/modules/mob/living/silicon/robot/modules/nanotrasen/research.dm b/code/modules/mob/living/silicon/robot/modules/nanotrasen/research.dm
new file mode 100644
index 00000000000..9bab68016ed
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/nanotrasen/research.dm
@@ -0,0 +1,82 @@
+GENERATE_ROBOT_MODULE_PRESET(/nanotrasen/research)
+/datum/prototype/robot_module/nanotrasen/research
+ id = "nt-research"
+ display_name = "NT-SCI"
+ visible_name = "Research"
+ use_robot_module_path = /obj/item/robot_module_legacy/robot/research
+ module_hud_state = "science"
+ light_color = "#CC60FF"
+ auto_iconsets = list(
+ /datum/prototype/robot_iconset/hover_eyebot/science,
+ /datum/prototype/robot_iconset/dog_otie/science,
+ /datum/prototype/robot_iconset/biped_marina/service,
+ /datum/prototype/robot_iconset/grounded_spider/science,
+ /datum/prototype/robot_iconset/biped_sleek/science,
+ /datum/prototype/robot_iconset/biped_droid/science,
+ /datum/prototype/robot_iconset/hover_drone/science,
+ /datum/prototype/robot_iconset/hover_handy/science,
+ /datum/prototype/robot_iconset/biped_insekt/science,
+ /datum/prototype/robot_iconset/hover_glitterfly/science,
+ /datum/prototype/robot_iconset/hover_x88/science,
+ /datum/prototype/robot_iconset/grounded_mechoid/science,
+ /datum/prototype/robot_iconset/dog_borgi/science,
+ /datum/prototype/robot_iconset/dog_k9/science,
+ /datum/prototype/robot_iconset/dog_k9/science_dark,
+ /datum/prototype/robot_iconset/dog_vale/science,
+ /datum/prototype/robot_iconset/cat_feli/research,
+ /datum/prototype/robot_iconset/grounded_zoomba/research,
+ /datum/prototype/robot_iconset/hover_coffin/research,
+ // the science coder's pet robot module can live on inside science :)
+ /datum/prototype/robot_iconset/grounded_landmate/peacekeeper,
+ )
+
+/datum/prototype/robot_module/nanotrasen/research/provision_resource_store(datum/robot_resource_store/store)
+ ..()
+ store.provisioned_stack_store[/obj/item/stack/cable_coil] = new /datum/robot_resource/provisioned/preset/wire
+ store.provisioned_stack_store[/obj/item/stack/nanopaste] = new /datum/robot_resource/provisioned/preset/nanopaste
+
+/datum/prototype/robot_module/nanotrasen/research/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ ..()
+ if(normal_out)
+ normal_out |= list(
+ /obj/item/portable_destructive_analyzer,
+ /obj/item/gripper/research,
+ /obj/item/gripper/circuit,
+ /obj/item/gripper/no_use/organ/robotics,
+ /obj/item/gripper/no_use/mech,
+ /obj/item/gripper/no_use/loader,
+ /obj/item/robotanalyzer,
+ /obj/item/card/robot,
+ /obj/item/weldingtool/electric/mounted/cyborg,
+ /obj/item/tool/screwdriver/cyborg,
+ /obj/item/tool/wrench/cyborg,
+ /obj/item/tool/wirecutters/cyborg,
+ /obj/item/multitool,
+ /obj/item/surgical/scalpel/cyborg,
+ /obj/item/surgical/circular_saw/cyborg,
+ /obj/item/reagent_containers/syringe,
+ /obj/item/reagent_containers/glass/beaker/large,
+ /obj/item/storage/part_replacer,
+ /obj/item/shockpaddles/robot,
+ /obj/item/melee/baton/slime/robot,
+ /obj/item/gun/projectile/energy/taser/xeno/robot,
+ )
+ if(emag_out)
+ emag_out |= list(
+ /obj/item/borg/combat/shield,
+ )
+
+// todo: this is evil
+/datum/prototype/robot_module/nanotrasen/research/legacy_custom_regenerate_resources(mob/living/silicon/robot/robot, dt, multiplier)
+ ..()
+ for(var/obj/item/reagent_containers/syringe/syringe in robot.robot_inventory.provided_items)
+ // todo: refactor syringes
+ if(syringe.mode == 2)
+ syringe.reagents?.clear_reagents()
+ syringe.mode = initial(syringe.mode)
+ syringe.desc = initial(syringe.desc)
+ syringe.update_icon()
+
+// todo: legacy
+/obj/item/robot_module_legacy/robot/research
+ channels = list("Science" = 1)
diff --git a/code/modules/mob/living/silicon/robot/modules/nanotrasen/security.dm b/code/modules/mob/living/silicon/robot/modules/nanotrasen/security.dm
new file mode 100644
index 00000000000..b86b43adb26
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/nanotrasen/security.dm
@@ -0,0 +1,75 @@
+GENERATE_ROBOT_MODULE_PRESET(/nanotrasen/security)
+/datum/prototype/robot_module/nanotrasen/security
+ id = "nt-security"
+ display_name = "NT-SEC"
+ display_name = "Security"
+ use_robot_module_path = /obj/item/robot_module_legacy/robot/security
+ module_hud_state = "security"
+ light_color = "#FF0000"
+ auto_iconsets = list(
+ /datum/prototype/robot_iconset/grounded_landmate/security,
+ /datum/prototype/robot_iconset/baseline_standard/security,
+ /datum/prototype/robot_iconset/hover_eyebot/security,
+ /datum/prototype/robot_iconset/grounded_landmate/security_tread,
+ /datum/prototype/robot_iconset/biped_marina/security,
+ /datum/prototype/robot_iconset/biped_tall/tallred,
+ /datum/prototype/robot_iconset/baseline_toiletbot/security,
+ /datum/prototype/robot_iconset/biped_sleek/security,
+ /datum/prototype/robot_iconset/grounded_spider/security,
+ /datum/prototype/robot_iconset/biped_heavy/security,
+ /datum/prototype/robot_iconset/baseline_old/security,
+ /datum/prototype/robot_iconset/baseline_old/security_riot,
+ /datum/prototype/robot_iconset/hover_drone/security,
+ /datum/prototype/robot_iconset/biped_insekt/security,
+ /datum/prototype/robot_iconset/biped_tall/alternative/security,
+ /datum/prototype/robot_iconset/biped_miss/security,
+ /datum/prototype/robot_iconset/hover_glitterfly/security,
+ /datum/prototype/robot_iconset/grounded_mechoid/security,
+ /datum/prototype/robot_iconset/hover_handy/security,
+ /datum/prototype/robot_iconset/biped_noble/security,
+ /datum/prototype/robot_iconset/grounded_zoomba/security,
+ /datum/prototype/robot_iconset/grounded_worm/security,
+ /datum/prototype/robot_iconset/dog_borgi/security,
+ /datum/prototype/robot_iconset/dog_k9/security,
+ /datum/prototype/robot_iconset/dog_otie/security,
+ /datum/prototype/robot_iconset/dog_pupdozer/security,
+ /datum/prototype/robot_iconset/dog_vale/security,
+ /datum/prototype/robot_iconset/cat_feli/security,
+ /datum/prototype/robot_iconset/drake_mizartz/security,
+ /datum/prototype/robot_iconset/dog_k9/red,
+ /datum/prototype/robot_iconset/raptor/security,
+ /datum/prototype/robot_iconset/hover_coffin/combat,
+ )
+
+/datum/prototype/robot_module/nanotrasen/security/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ ..()
+ if(normal_out)
+ normal_out |= list(
+ /obj/item/handcuffs/cyborg,
+ /obj/item/melee/baton/robot,
+ /obj/item/gun/projectile/energy/taser/mounted/cyborg,
+ /obj/item/barrier_tape_roll/police,
+ /obj/item/reagent_containers/spray/pepper,
+ /obj/item/gripper/security,
+ )
+ if(emag_out)
+ emag_out |= list(
+ /obj/item/gun/projectile/energy/laser/mounted,
+ )
+
+// todo: this is evil
+/datum/prototype/robot_module/nanotrasen/security/legacy_custom_regenerate_resources(mob/living/silicon/robot/robot, dt, multiplier)
+ ..()
+ for(var/obj/item/flash/flash in robot.robot_inventory.provided_items)
+ // todo: refactor flash
+ flash.times_used = 0
+ if(flash.broken)
+ flash.broken = 0
+ flash.icon_state = "flash"
+
+// todo: legacy
+/obj/item/robot_module_legacy/robot/security
+ channels = list("Security" = 1)
+ networks = list(NETWORK_SECURITY)
+ subsystems = list(/mob/living/silicon/proc/subsystem_crew_monitor)
+
diff --git a/code/modules/mob/living/silicon/robot/modules/nanotrasen/service.dm b/code/modules/mob/living/silicon/robot/modules/nanotrasen/service.dm
new file mode 100644
index 00000000000..3f526301b7e
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/nanotrasen/service.dm
@@ -0,0 +1,111 @@
+GENERATE_ROBOT_MODULE_PRESET(/nanotrasen/service)
+/datum/prototype/robot_module/nanotrasen/service
+ id = "nt-service"
+ display_name = "NT-SRV"
+ visible_name = "Service"
+ use_robot_module_path = /obj/item/robot_module_legacy/robot/clerical
+ module_hud_state = "service"
+ light_color = "#6AED63"
+ auto_iconsets = list(
+ /datum/prototype/robot_iconset/baseline_standard/service,
+ /datum/prototype/robot_iconset/baseline_standard/clerical,
+ /datum/prototype/robot_iconset/dog_k9/grey,
+ /datum/prototype/robot_iconset/dog_k9/pink,
+ /datum/prototype/robot_iconset/dog_vale/service,
+ /datum/prototype/robot_iconset/dog_vale/service_dark,
+ /datum/prototype/robot_iconset/cat_feli/service,
+ /datum/prototype/robot_iconset/drake_mizartz/peacekeeper,
+ /datum/prototype/robot_iconset/biped_marina/service,
+ /datum/prototype/robot_iconset/biped_maid,
+ /datum/prototype/robot_iconset/biped_tall/tallgreen,
+ /datum/prototype/robot_iconset/baseline_toiletbot/security,
+ /datum/prototype/robot_iconset/biped_sleek/service,
+ /datum/prototype/robot_iconset/baseline_misc/omoikane,
+ /datum/prototype/robot_iconset/grounded_spider/standard,
+ /datum/prototype/robot_iconset/biped_heavy/service,
+ /datum/prototype/robot_iconset/baseline_old/service,
+ /datum/prototype/robot_iconset/baseline_old/service_fancy,
+ /datum/prototype/robot_iconset/baseline_old/service_hydro,
+ /datum/prototype/robot_iconset/baseline_old/service_skirt,
+ /datum/prototype/robot_iconset/baseline_old/service_sweater,
+ /datum/prototype/robot_iconset/grounded_spider/command,
+ /datum/prototype/robot_iconset/biped_tall/alternative/service,
+ /datum/prototype/robot_iconset/hover_drone/service,
+ /datum/prototype/robot_iconset/hover_drone/hydroponics,
+ /datum/prototype/robot_iconset/hover_glitterfly/clerical,
+ /datum/prototype/robot_iconset/hover_glitterfly/service,
+ /datum/prototype/robot_iconset/hover_handy/service,
+ /datum/prototype/robot_iconset/grounded_mechoid/service,
+ /datum/prototype/robot_iconset/grounded_zoomba/service,
+ /datum/prototype/robot_iconset/biped_noble/service,
+ /datum/prototype/robot_iconset/grounded_worm/service,
+ /datum/prototype/robot_iconset/biped_miss/service,
+ /datum/prototype/robot_iconset/hover_handy/clerk,
+ /datum/prototype/robot_iconset/raptor/service,
+ )
+
+/datum/prototype/robot_module/nanotrasen/service/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ ..()
+ if(normal_out)
+ normal_out |= list(
+ /obj/item/soap/nanotrasen,
+ /obj/item/storage/bag/trash,
+ /obj/item/gripper/service,
+ /obj/item/reagent_containers/glass/bucket,
+ /obj/item/reagent_containers/dropper/industrial,
+ /obj/item/flame/lighter/zippo,
+ /obj/item/reagent_containers/borghypo/service,
+ /obj/item/material/minihoe,
+ /obj/item/material/knife/machete/hatchet,
+ /obj/item/plant_analyzer,
+ /obj/item/storage/bag/plants,
+ /obj/item/robot_harvester,
+ /obj/item/material/knife,
+ /obj/item/material/kitchen/rollingpin,
+ /obj/item/pen/robopen,
+ /obj/item/form_printer,
+ /obj/item/gripper/paperwork,
+ /obj/item/hand_labeler,
+ /obj/item/stamp,
+ /obj/item/stamp/denied,
+ /obj/item/tray/robotray,
+ /obj/item/rsf/loaded,
+ /obj/item/reagent_containers/food/condiment/enzyme,
+ )
+ if(emag_out)
+ emag_out |= list(
+ /obj/item/stamp/chameleon,
+ /obj/item/pen/chameleon,
+ )
+
+/datum/prototype/robot_module/nanotrasen/service/legacy_custom_regenerate_resources(mob/living/silicon/robot/robot, dt, multiplier)
+ var/obj/item/reagent_containers/food/condiment/enzyme/E = locate() in robot
+ E.reagents.add_reagent("enzyme", 2 * multiplier * dt)
+
+// todo: legacy
+/obj/item/robot_module_legacy/robot/clerical
+ name = "service robot module"
+ channels = list("Service" = 1)
+ languages = list(
+ LANGUAGE_AKHANI = 1,
+ LANGUAGE_BIRDSONG = 1,
+ LANGUAGE_CANILUNZT = 1,
+ LANGUAGE_DAEMON = 1,
+ LANGUAGE_EAL = 1,
+ LANGUAGE_ECUREUILIAN= 1,
+ LANGUAGE_ENOCHIAN = 1,
+ LANGUAGE_GUTTER = 1,
+ LANGUAGE_ROOTLOCAL = 0,
+ LANGUAGE_SAGARU = 1,
+ LANGUAGE_SCHECHI = 1,
+ LANGUAGE_SIGN = 0,
+ LANGUAGE_SIIK = 1,
+ LANGUAGE_SKRELLIAN = 1,
+ LANGUAGE_SKRELLIANFAR = 0,
+ LANGUAGE_SOL_COMMON = 1,
+ LANGUAGE_SQUEAKISH = 1,
+ LANGUAGE_TERMINUS = 1,
+ LANGUAGE_TRADEBAND = 1,
+ LANGUAGE_UNATHI = 1,
+ LANGUAGE_ZADDAT = 1
+ )
diff --git a/code/modules/mob/living/silicon/robot/modules/swarm.dm b/code/modules/mob/living/silicon/robot/modules/swarm.dm
new file mode 100644
index 00000000000..4bbe059d50d
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/modules/swarm.dm
@@ -0,0 +1,40 @@
+// TODO: IOU swarmers.
+
+// /datum/prototype/robot_module/swarm
+// id = "swarmer"
+// light_color = "#77FFFF"
+// module_hud_state = "recon"
+// // TODO: add some
+// allowed_frames = list()
+
+// /datum/prototype/robot_module/swarm/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+// ..()
+// if(normal_out)
+// normal_out |= list(
+// /obj/item/rcd/electric/mounted/borg/swarm,
+// /obj/item/flash/robot,
+// /obj/item/handcuffs/cable/tape/cyborg,
+// /obj/item/melee/baton/robot,
+// /obj/item/gun/projectile/energy/taser/mounted/cyborg/swarm,
+// /obj/item/matter_decompiler/swarm,
+// )
+
+// /datum/prototype/robot_module/swarm/gunner
+// id = "swarmer-gunner"
+
+// /datum/prototype/robot_module/swarm/gunner/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+// ..()
+// if(normal_out)
+// normal_out |= list(
+// /obj/item/gun/projectile/energy/xray/swarm,
+// )
+
+// /datum/prototype/robot_module/swarm/melee
+// id = "swarmer-melee"
+
+// /datum/prototype/robot_module/swarm/melee/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+// ..()
+// if(normal_out)
+// normal_out |= list(
+// /obj/item/melee/transforming/energy/sword/ionic_rapier/lance,
+// )
diff --git a/code/modules/mob/living/silicon/robot/robot-action-robot_pick_resting_variation.dm b/code/modules/mob/living/silicon/robot/robot-action-robot_pick_resting_variation.dm
new file mode 100644
index 00000000000..ba911ba879b
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot-action-robot_pick_resting_variation.dm
@@ -0,0 +1,64 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/datum/action/robot_pick_resting_variation
+ name = "Pick Resting Variation"
+ desc = "Pick the variation of your chassis' resting sprite to use."
+ target_type = /mob/living/silicon/robot
+ button_additional_only = TRUE
+
+/datum/action/robot_pick_resting_variation/pre_render_hook()
+ if(!isrobot(target))
+ return
+ var/mob/living/silicon/robot/casted_target = target
+ var/datum/prototype/robot_iconset/base = casted_target.iconset
+ if(base.variations?[casted_target.picked_resting_variation])
+ // render the picked variation
+ var/datum/robot_iconset_variation/variation = base.variations[casted_target.picked_resting_variation]
+ button_additional_overlay = image(
+ variation.icon_override || base.icon,
+ variation.icon_state ? variation.icon_state : "[base.icon_state][variation.icon_state_append]",,
+ dir = WEST,
+ )
+ else
+ // just render them
+ var/mutable_appearance/cloning = new(casted_target)
+ cloning.dir = WEST
+ button_additional_overlay = cloning
+
+/datum/action/robot_pick_resting_variation/invoke_target(mob/living/silicon/robot/target, datum/event_args/actor/actor)
+ // resolve iconset variations
+ var/datum/prototype/robot_iconset/base = target.iconset
+ var/list/datum/robot_iconset_variation/variations = list()
+ for(var/id in base?.variations)
+ var/datum/robot_iconset_variation/variation = base.variations[id]
+ if(variation.alt_resting_state)
+ variations += variation
+ if(length(variations) <= 1)
+ actor.chat_feedback(
+ SPAN_WARNING("Your chassis has no resting variations to pick from."),
+ target = target,
+ )
+ return TRUE
+
+ // assemble radial choices
+ var/list/assembled_radial_choices = list()
+ for(var/datum/robot_iconset_variation/variation as anything in variations)
+ var/image/built = image(
+ variation.icon_override || base.icon,
+ variation.icon_state || "[base.icon_state][variation.icon_state_append]",
+ )
+ built.maptext = MAPTEXT_CENTER(variation.id)
+ assembled_radial_choices[variation.id] = built
+
+ // ask them
+ var/picked_id = show_radial_menu(actor.initiator, target, assembled_radial_choices)
+
+ // check & update
+ if(!base?.variations[picked_id])
+ return TRUE
+ if(target.picked_resting_variation == picked_id)
+ return TRUE
+ log_game("CYBORG: [key_name(src)] ([base]) had their resting variation set to [picked_id] by [actor.actor_log_string()]")
+ target.picked_resting_variation = picked_id
+ target.update_icon()
diff --git a/code/modules/mob/living/silicon/robot/robot-composition.dm b/code/modules/mob/living/silicon/robot/robot-composition.dm
new file mode 100644
index 00000000000..b3397b224ea
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot-composition.dm
@@ -0,0 +1,199 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/**
+ * Hard reset, rebuild from composition.
+ *
+ * todo: better name?
+ */
+/mob/living/silicon/robot/proc/rebuild()
+ set_chassis(chassis, TRUE)
+ set_iconset(iconset, TRUE)
+ set_module(module, TRUE)
+ // TODO: what about upgrades
+ update_icon()
+
+/**
+ * Annihilate composition
+ *
+ * todo: better name?
+ */
+/mob/living/silicon/robot/proc/wipe_for_gc()
+ set_chassis(null, TRUE)
+ set_iconset(null, TRUE)
+ set_module(null, TRUE)
+
+/**
+ * Hard reset, to unformatted
+ *
+ * todo: better name?
+ * todo: rework maybe?
+ */
+/mob/living/silicon/robot/proc/perform_module_reset(perform_transform_animation)
+ set_chassis(null, TRUE)
+ set_module(null, TRUE)
+ set_iconset(
+ RSrobot_iconsets.fetch_local_or_throw(/datum/prototype/robot_iconset/baseline_standard/standard),
+ TRUE,
+ )
+ update_icon()
+ can_repick_frame = TRUE
+ can_repick_module = TRUE
+
+ //! legacy
+ lights_on = FALSE
+ radio.set_light(0)
+ if(perform_transform_animation)
+ transform_with_anim()
+ //! end
+
+/mob/living/silicon/robot/proc/get_module_pick_groups()
+ SHOULD_NOT_OVERRIDE(TRUE)
+ . = conf_module_pick_selection_groups ? conf_module_pick_selection_groups.Copy() : list()
+ . |= get_module_pick_groups_special()
+ . -= conf_module_pick_selection_groups_excluded
+
+/mob/living/silicon/robot/proc/get_module_pick_groups_special()
+ . = list(
+ ROBOT_MODULE_SELECTION_GROUP_NANOTRASEN,
+ )
+ //! todo: rework security levels
+ if(GLOB.security_level >= SEC_LEVEL_RED)
+ . |= ROBOT_MODULE_SELECTION_GROUP_LEGACY_RED_ALERT
+ //! end
+
+/mob/living/silicon/robot/proc/get_default_iconset()
+ if(module?.auto_iconsets?.len > 0)
+ . = RSrobot_iconsets.fetch_local_or_throw(pick(module.auto_iconsets))
+ if(.)
+ return
+ return RSrobot_iconsets.fetch_local_or_throw(/datum/prototype/robot_iconset/baseline_standard/standard)
+
+/**
+ * Initialize to a chassis
+ *
+ * * Will re-init if it's the same one.
+ */
+/mob/living/silicon/robot/proc/set_chassis(datum/prototype/robot_chassis/chassis, skip_icon_update)
+ // cleanup
+ if(chassis_provisioning)
+ QDEL_NULL(chassis_provisioning)
+ // set
+ src.chassis = chassis
+ // build
+ if(!chassis)
+ return
+ chassis_provisioning = chassis.create_provisioning()
+ chassis_provisioning.apply(src)
+
+/**
+ * Initialize to a iconset
+ *
+ * * Iconset may never be null, as otherwise we can't render.
+ * * Will re-init if it's the same one.
+ */
+/mob/living/silicon/robot/proc/set_iconset(datum/prototype/robot_iconset/iconset, skip_icon_update)
+ // Iconset will never be null.
+ if(isnull(iconset))
+ iconset = get_default_iconset()
+ src.iconset = iconset
+
+ if(iconset)
+ if(iconset.icon_dimension_x > WORLD_ICON_SIZE || iconset.icon_dimension_y > WORLD_ICON_SIZE)
+ zmm_flags |= ZMM_WIDE_LOAD
+ else
+ zmm_flags &= ~ZMM_WIDE_LOAD
+ base_icon_state = iconset.icon_state
+ icon = iconset.icon
+ icon_x_dimension = iconset.icon_dimension_x
+ icon_y_dimension = iconset.icon_dimension_y
+ set_base_pixel_x(-((icon_x_dimension - WORLD_ICON_SIZE) / 2))
+ set_base_pixel_y(-((icon_y_dimension - WORLD_ICON_SIZE) / 2))
+ // reset resting variation if needed
+ if(!iconset.variations?[picked_resting_variation])
+ picked_resting_variation = null
+ else
+ zmm_flags &= ~ZMM_WIDE_LOAD
+ base_icon_state = initial(base_icon_state) || initial(icon_state)
+ icon = initial(icon)
+ icon_x_dimension = initial(icon_x_dimension)
+ icon_y_dimension = initial(icon_y_dimension)
+ set_base_pixel_x(initial(base_pixel_x))
+ set_base_pixel_y(initial(base_pixel_y))
+ // reset resting variation
+ picked_resting_variation = null
+
+ if(!skip_icon_update)
+ update_icon()
+
+/**
+ * Initialize to a module
+ *
+ * * Will re-init if it's the same one.
+ */
+/mob/living/silicon/robot/proc/set_module(datum/prototype/robot_module/module, skip_icon_update)
+ // cleanup
+ if(module_provisioning)
+ QDEL_NULL(module_provisioning)
+ if(module_legacy)
+ module_legacy.Reset()
+ QDEL_NULL(module_legacy)
+ // set
+ src.module = module
+
+ //! -- LEGACY SCREEN / IDENTITY THINGY --
+ src.hands?.icon_state = "none"
+ src.modtype = "Default"
+ //! -- END --
+
+ // build
+ if(!module)
+ return
+ module_provisioning = module.create_provisioning()
+ module_provisioning.apply(src)
+ module_legacy = new module.use_robot_module_path(src)
+ can_repick_module = FALSE
+
+ // this entire section is weird but it's the only way to do it for now
+ // what we need is to not have magic regen materials so provisioning can also
+ // be properly composition based and stateful, but for now we don't care
+ resources.wipe_provisioning()
+ resources.prep_provisioning()
+ module.provision_resource_store(resources)
+ resources.finish_provisioning()
+
+ // now regen to full
+ resources.regen_provisioned(INFINITY)
+ module.legacy_custom_regenerate_resources(src, INFINITY, 1)
+
+ //! -- LEGACY SCREEN / IDENTITY THINGY --
+ src.hands?.icon_state = module.module_hud_state
+ src.modtype = module.get_display_name()
+ //! -- END --
+
+ if(!skip_icon_update)
+ update_icon()
+
+/**
+ * Set chassis / iconset from a frame.
+ */
+/mob/living/silicon/robot/proc/set_from_frame(datum/robot_frame/frame, skip_icon_update)
+ set_chassis(frame.robot_chassis, TRUE)
+ set_iconset(frame.robot_iconset, TRUE)
+ can_repick_frame = FALSE
+ if(!skip_icon_update)
+ update_icon()
+
+/**
+ * Apply a provisioning set; this is one of the things orchestrating composition.
+ * * Should only be called by `/datum/robot_provisioning`.
+ */
+/mob/living/silicon/robot/proc/apply_provisioning(datum/robot_provisioning/provisioning)
+ // TODO: this is unused?
+
+/**
+ * Remove a provisioning set; this is one of the things orchestrating composition.
+ * * Should only be called by `/datum/robot_provisioning`.
+ */
+/mob/living/silicon/robot/proc/remove_provisioning(datum/robot_provisioning/provisioning)
+ // TODO: this is unused?
diff --git a/code/modules/mob/living/silicon/robot/robot-health.dm b/code/modules/mob/living/silicon/robot/robot-health.dm
new file mode 100644
index 00000000000..d28decc4ae7
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot-health.dm
@@ -0,0 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/mob/living/silicon/robot/on_stat_change(old_stat, new_stat)
+ ..()
+ update_icon()
diff --git a/code/modules/mob/living/silicon/robot/robot-inventory.dm b/code/modules/mob/living/silicon/robot/robot-inventory.dm
new file mode 100644
index 00000000000..a9341d835de
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot-inventory.dm
@@ -0,0 +1,16 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/mob/living/silicon/robot/init_inventory()
+ ..()
+ inventory.set_hand_count(3)
+
+/mob/living/silicon/robot/should_allow_pickup(obj/item/item, datum/event_args/actor/actor, silent)
+ return item in robot_inventory?.provided_items
+
+/mob/living/silicon/robot/is_in_inventory(obj/item/item)
+ return ..() || is_in_inventory_robot(item)
+
+/mob/living/silicon/robot/proc/is_in_inventory_robot(obj/item/item)
+ // Bespoke check that an item is in gripper, as grippers don't count as an inventory slot.
+ return istype(item.loc, /obj/item/gripper) && item.loc.loc == src
diff --git a/code/modules/mob/living/silicon/robot/robot_animation_vr.dm b/code/modules/mob/living/silicon/robot/robot-legacy-animation_vr.dm
similarity index 94%
rename from code/modules/mob/living/silicon/robot/robot_animation_vr.dm
rename to code/modules/mob/living/silicon/robot/robot-legacy-animation_vr.dm
index 5eb6bf03dec..dd6f34bfb75 100644
--- a/code/modules/mob/living/silicon/robot/robot_animation_vr.dm
+++ b/code/modules/mob/living/silicon/robot/robot-legacy-animation_vr.dm
@@ -2,7 +2,7 @@
INVOKE_ASYNC(src, PROC_REF(do_transform_animation))
/mob/living/silicon/robot/proc/do_transform_animation()
- notransform = TRUE
+ transforming = TRUE
dir = SOUTH
var/obj/effect/temp_visual/decoy/fading/fivesecond/ANM = new /obj/effect/temp_visual/decoy/fading/fivesecond(loc, src)
ANM.layer = layer - 0.01
@@ -19,4 +19,4 @@
if(!prev_lockcharge)
SetLockdown(0)
anchored = FALSE
- notransform = FALSE
+ transforming = FALSE
diff --git a/code/modules/mob/living/silicon/robot/robot_damage.dm b/code/modules/mob/living/silicon/robot/robot-legacy-damage.dm
similarity index 100%
rename from code/modules/mob/living/silicon/robot/robot_damage.dm
rename to code/modules/mob/living/silicon/robot/robot-legacy-damage.dm
diff --git a/code/modules/mob/living/silicon/robot/robot-legacy-emag.dm b/code/modules/mob/living/silicon/robot/robot-legacy-emag.dm
new file mode 100644
index 00000000000..8021ff338e7
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot-legacy-emag.dm
@@ -0,0 +1,17 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/mob/living/silicon/robot/proc/set_emag_state(state)
+ emagged = state
+ set_emag_item_state(state)
+
+/mob/living/silicon/robot/proc/get_emag_state()
+ return emagged
+
+/mob/living/silicon/robot/proc/set_emag_item_state(state)
+ emag_items = state
+ chassis_provisioning?.set_emag_enabled(state)
+ module_provisioning?.set_emag_enabled(state)
+
+/mob/living/silicon/robot/proc/get_emag_item_state()
+ return emag_items
diff --git a/code/modules/mob/living/silicon/robot/robot_items.dm b/code/modules/mob/living/silicon/robot/robot-legacy-items.dm
similarity index 99%
rename from code/modules/mob/living/silicon/robot/robot_items.dm
rename to code/modules/mob/living/silicon/robot/robot-legacy-items.dm
index a430c510ef9..115756ac89c 100644
--- a/code/modules/mob/living/silicon/robot/robot_items.dm
+++ b/code/modules/mob/living/silicon/robot/robot-legacy-items.dm
@@ -346,7 +346,8 @@
new /obj/item/paper(T)
-//Personal shielding for the combat module.
+// Personal shielding for the combat module.
+// TODO: refactor this shit this is awful lol
/obj/item/borg/combat/shield
name = "personal shielding"
desc = "A powerful experimental module that turns aside or absorbs incoming attacks at the cost of charge."
diff --git a/code/modules/mob/living/silicon/robot/robot_movement.dm b/code/modules/mob/living/silicon/robot/robot-legacy-movement.dm
similarity index 53%
rename from code/modules/mob/living/silicon/robot/robot_movement.dm
rename to code/modules/mob/living/silicon/robot/robot-legacy-movement.dm
index 99298ddbac8..c68f1384632 100644
--- a/code/modules/mob/living/silicon/robot/robot_movement.dm
+++ b/code/modules/mob/living/silicon/robot/robot-legacy-movement.dm
@@ -1,19 +1,14 @@
-/mob/living/silicon/robot/Process_Spaceslipping(var/prob_slip)
- if(module && module.no_slip)
- return 0
- ..(prob_slip)
-
/mob/living/silicon/robot/Process_Spacemove()
- if(module)
- for(var/obj/item/tank/jetpack/J in module.modules)
- if(istype(J, /obj/item/tank/jetpack))
- if(J.allow_thrust(0.01))
- return TRUE
+ // active robot modules are held items
+ for(var/obj/item/tank/jetpack/J in inventory?.get_held_items())
+ if(istype(J, /obj/item/tank/jetpack))
+ if(J.allow_thrust(0.01))
+ return TRUE
return ..()
/mob/living/silicon/robot/legacy_movement_delay()
. = ..()
- if(module_active && istype(module_active,/obj/item/borg/combat/mobility))
+ if(get_held_item_of_type(/obj/item/borg/combat/mobility))
. -= 2
if(get_restraining_bolt()) // Borgs with Restraining Bolts move slower.
@@ -25,5 +20,5 @@
return 0
var/datum/robot_component/actuator/A = get_component("actuator")
- if (cell_use_power(A.active_usage))
+ if (legacy_cell_use_power(A.active_usage))
return ..()
diff --git a/code/modules/mob/living/silicon/robot/robot_remote_control.dm b/code/modules/mob/living/silicon/robot/robot-legacy-remote_control.dm
similarity index 90%
rename from code/modules/mob/living/silicon/robot/robot_remote_control.dm
rename to code/modules/mob/living/silicon/robot/robot-legacy-remote_control.dm
index 3f500fbca25..14986f035c6 100644
--- a/code/modules/mob/living/silicon/robot/robot_remote_control.dm
+++ b/code/modules/mob/living/silicon/robot/robot-legacy-remote_control.dm
@@ -2,11 +2,6 @@
GLOBAL_LIST_EMPTY(available_ai_shells)
-/mob/living/silicon/robot
- var/shell = FALSE
- var/deployed = FALSE
- var/mob/living/silicon/ai/mainframe = null
-
// Premade AI shell, for roundstart shells.
/mob/living/silicon/robot/ai_shell/Initialize(mapload)
mmi = new /obj/item/mmi/inert/ai_remote(src)
@@ -69,15 +64,13 @@ GLOBAL_LIST_EMPTY(available_ai_shells)
// Languages and comms.
languages = AI.languages.Copy()
speech_synthesizer_langs = AI.speech_synthesizer_langs.Copy()
- if(radio && AI.aiRadio && module) //AI keeps all channels, including Syndie if it is an Infiltrator.
+ if(radio && AI.aiRadio && module_legacy) //AI keeps all channels, including Syndie if it is an Infiltrator.
radio.subspace_transmission = TRUE
- module.channels = AI.aiRadio.channels
+ module_legacy.channels = AI.aiRadio.channels
radio.recalculateChannels()
// Called after the AI transfers over.
/mob/living/silicon/robot/proc/post_deploy()
- if(!custom_sprite) // Check for custom sprite.
- set_custom_sprite()
/mob/living/silicon/robot/proc/undeploy(message)
if(!deployed || !mind || !mainframe)
@@ -90,8 +83,8 @@ GLOBAL_LIST_EMPTY(available_ai_shells)
mainframe.teleop = null
mainframe.deployed_shell = null
SetName("[modtype] AI Shell [num2text(ident)]")
- if(radio && module) //Return radio to normal
- module.channels = initial(module.channels)
+ if(radio && module_legacy) //Return radio to normal
+ module_legacy.channels = initial(module_legacy.channels)
radio.recalculateChannels()
if(!QDELETED(camera))
camera.c_tag = real_name //update the camera name too
diff --git a/code/modules/mob/living/silicon/robot/robot-power.dm b/code/modules/mob/living/silicon/robot/robot-power.dm
index eb452e4bd03..066840bb3b8 100644
--- a/code/modules/mob/living/silicon/robot/robot-power.dm
+++ b/code/modules/mob/living/silicon/robot/robot-power.dm
@@ -1,5 +1,37 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2025 Citadel Station Developers *//
+/**
+ * Uses a certain amount of reserve power - usually from the cell.
+ *
+ * * This bypasses any internal power limit / balancing logic and directly hits our
+ * backplane's power source.
+ *
+ * @return amount used
+ */
+/mob/living/silicon/robot/proc/draw_power(joules)
+ . = cell ? cell.use(DYNAMIC_J_TO_CELL_UNITS(joules)) : 0
+ if(.)
+ avg_power_accumulator += DYNAMIC_CELL_UNITS_TO_J(.)
+
+/**
+ * Uses a certain amount of reserve power - usually from the cell.
+ *
+ * * This bypasses any internal power limit / balancing logic and directly hits our
+ * backplane's power source.
+ * * Reserve is in units, not joules.
+ *
+ * @return amount used
+ */
+/mob/living/silicon/robot/proc/draw_checked_power(joules, reserve_units)
+ if(!cell)
+ return 0
+ var/units = DYNAMIC_J_TO_CELL_UNITS(joules)
+ if(cell.charge - units < reserve_units)
+ return 0
+ . = cell.use(units)
+ if(.)
+ avg_power_accumulator += DYNAMIC_CELL_UNITS_TO_J(.)
+
/mob/living/silicon/robot/proc/accepts_cell(obj/item/cell/cell, datum/event_args/actor/actor, silent)
return cell.cell_type ? (cell.cell_type & cell_accept) : cell_accept_nonstandard
diff --git a/code/modules/mob/living/silicon/robot/robot-rendering.dm b/code/modules/mob/living/silicon/robot/robot-rendering.dm
new file mode 100644
index 00000000000..869083ba9ec
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot-rendering.dm
@@ -0,0 +1,57 @@
+/mob/living/silicon/robot/update_icon()
+ cut_overlays()
+ . = ..()
+
+ // resolve variation & set icon state
+ var/datum/robot_iconset_variation/active_variation
+ if(IS_DEAD(src))
+ var/datum/robot_iconset_variation/dead_variation = iconset?.variations?[/datum/robot_iconset_variation/dead::id]
+ if(dead_variation)
+ active_variation = dead_variation
+ else if(resting)
+ if(picked_resting_variation)
+ active_variation = iconset?.variations?[picked_resting_variation]
+ if(!active_variation)
+ active_variation = iconset?.variations?[/datum/robot_iconset_variation/resting::id]
+
+ if(active_variation)
+ if(active_variation.icon_state)
+ icon_state = active_variation.icon_state
+ else
+ icon_state = "[base_icon_state][active_variation.icon_state_append]"
+ else
+ icon_state = base_icon_state
+
+ // render indicator lighting
+ if(IS_CONSCIOUS(src) && (!shell || deployed))
+ var/indicator_state
+ if(active_variation)
+ if(active_variation.icon_state_indicator)
+ indicator_state = active_variation.icon_state_indicator
+ else if(active_variation.icon_state_indicator_append)
+ indicator_state = "[icon_state][active_variation.icon_state_indicator_append]"
+ else if(iconset.icon_state_indicator)
+ indicator_state = iconset.icon_state_indicator
+ var/image/indicator = image(icon, indicator_state)
+ switch(iconset.indicator_lighting_coloration_mode)
+ if(COLORATION_MODE_MULTIPLY)
+ indicator.color = iconset.indicator_lighting_coloration_packed
+ add_overlay(indicator)
+
+ // render panel
+ if(opened)
+ var/using_panel_state
+ if(active_variation)
+ if(active_variation.icon_state_cover)
+ using_panel_state = active_variation.icon_state_cover
+ else if(active_variation.icon_state_cover_append)
+ using_panel_state = "[base_icon_state][active_variation.icon_state_cover]"
+ else
+ using_panel_state = iconset.icon_state_cover
+ if(using_panel_state)
+ if(wiresexposed)
+ add_overlay("[using_panel_state]-wires")
+ else if(cell)
+ add_overlay("[using_panel_state]-cell")
+ else
+ add_overlay("[using_panel_state]-empty")
diff --git a/code/modules/mob/living/silicon/robot/robot-resources.dm b/code/modules/mob/living/silicon/robot/robot-resources.dm
new file mode 100644
index 00000000000..86ed7fc4b06
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot-resources.dm
@@ -0,0 +1,12 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/**
+ * Regenerates our resources.
+ *
+ * * Used by chargers.
+ */
+/mob/living/silicon/robot/proc/regenerate_resources_from_charger(seconds, multiplier = 1)
+ SHOULD_NOT_OVERRIDE(TRUE) // no excuses, don't do it! robots are composition.
+
+ module?.legacy_custom_regenerate_resources(src, seconds, multiplier)
diff --git a/code/modules/mob/living/silicon/robot/robot-upgrades.dm b/code/modules/mob/living/silicon/robot/robot-upgrades.dm
new file mode 100644
index 00000000000..f4bb1e799be
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot-upgrades.dm
@@ -0,0 +1,114 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/**
+ * hard check
+ */
+/mob/living/silicon/robot/proc/can_install_upgrade(obj/item/robot_upgrade/upgrade, datum/event_args/actor/actor, silent, force)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+
+ if(!upgrade.dupe_allowed)
+ var/check_type = upgrade.dupe_type || upgrade.type
+ for(var/obj/item/robot_upgrade/other as anything in upgrades)
+ if(istype(other, check_type))
+ if(!silent)
+ actor?.chat_feedback(
+ SPAN_WARNING("There's already an upgrade of the same type in [src]."),
+ target = src,
+ )
+ return FALSE
+
+ return force || upgrade.can_install(src, ., actor, silent)
+
+/mob/living/silicon/robot/proc/can_fit_upgrade(obj/item/robot_upgrade/upgrade, datum/event_args/actor/actor, silent)
+ return TRUE
+
+/mob/living/silicon/robot/proc/user_install_upgrade(obj/item/robot_upgrade/upgrade, datum/event_args/actor/actor)
+ SHOULD_NOT_OVERRIDE(TRUE)
+
+ if(actor)
+ if(actor.performer && actor.performer.is_in_inventory(upgrade))
+ if(!actor.performer.can_unequip(upgrade, upgrade.worn_slot))
+ actor.chat_feedback(
+ SPAN_WARNING("[upgrade] is stuck to your hand!"),
+ target = src,
+ )
+ return FALSE
+ if(!install_upgrade(upgrade, actor))
+ return FALSE
+ // TODO: sound
+ return TRUE
+
+/**
+ * * moves the upgrade into us if it wasn't already
+ */
+/mob/living/silicon/robot/proc/install_upgrade(obj/item/robot_upgrade/upgrade, datum/event_args/actor/actor, silent, force)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+
+ ASSERT(!upgrade.owner)
+
+ if(!can_install_upgrade(upgrade, actor, silent, force))
+ return FALSE
+
+ if(upgrade.loc != src)
+ upgrade.forceMove(src)
+
+ // todo: logging
+ upgrade.being_installed(src)
+ if(!QDELETED(upgrade))
+ upgrade.owner = src
+ upgrade.on_install(src)
+ LAZYADD(upgrades, upgrade)
+
+ if(!silent)
+ actor?.visible_feedback(
+ target = src,
+ visible = SPAN_NOTICE("[actor.performer] slots [upgrade] into [src]."),
+ range = MESSAGE_RANGE_CONFIGURATION,
+ )
+
+ return TRUE
+
+/mob/living/silicon/robot/proc/user_uninstall_upgrade(obj/item/robot_upgrade/upgrade, datum/event_args/actor/actor, put_in_hands)
+ SHOULD_NOT_OVERRIDE(TRUE)
+
+ var/obj/item/uninstalled = uninstall_upgrade(upgrade, actor, new_loc = src)
+ if(put_in_hands && actor?.performer)
+ actor.performer.put_in_hands_or_drop(uninstalled)
+ else
+ var/atom/where_to_drop = drop_location()
+ ASSERT(where_to_drop)
+ uninstalled.forceMove(where_to_drop)
+ // TODO: sound
+ return TRUE
+
+/**
+ * * deletes the upgrade if no location is provided to move it to
+ *
+ * @return uninstalled item
+ */
+/mob/living/silicon/robot/proc/uninstall_upgrade(obj/item/robot_upgrade/upgrade, datum/event_args/actor/actor, silent, force, atom/new_loc)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+ ASSERT(upgrade.owner == src)
+
+ // todo: logging
+ upgrade.on_uninstall(src)
+ upgrade.owner = null
+ LAZYREMOVE(upgrades, upgrade)
+
+ if(!silent)
+ actor?.visible_feedback(
+ target = src,
+ visible = SPAN_NOTICE("[actor.performer] pulls [upgrade] out of [src]."),
+ range = MESSAGE_RANGE_CONFIGURATION,
+ )
+
+ if(new_loc)
+ upgrade.forceMove(new_loc)
+ . = upgrade
+ else
+ qdel(upgrade)
+ . = null
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 19af1576803..361fd7c14ee 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -19,7 +19,6 @@
/datum/category_item/catalogue/fauna/silicon/robot/cyborg,
/datum/category_item/catalogue/fauna/silicon/robot/lost,
/datum/category_item/catalogue/fauna/silicon/robot/gravekeeper,
- /datum/category_item/catalogue/fauna/silicon/robot/syndicate
)
/datum/category_item/catalogue/fauna/silicon/robot/cyborg
@@ -36,10 +35,20 @@
// todo: automatic subtypes for modules
+/**
+ * ## Composition
+ *
+ * Robots are / will be made out of:
+ *
+ * * chassis - determines baseline functionality and support
+ * * iconsets - determines sprites
+ *
+ * WIP. Modules, components, etc, are next.
+ */
/mob/living/silicon/robot
name = "Cyborg"
real_name = "Cyborg"
- icon = 'icons/mob/robots.dmi'
+ icon = 'icons/mob/robot-legacy.dmi'
icon_state = "robot"
maxHealth = 200
health = 200
@@ -60,13 +69,98 @@
can_be_antagged = TRUE
+ //* Composition *//
+
+ /// set to instance to init as that instance
+ /// * This may be null; which will result in an iconset being defaulted on init.
+ var/datum/prototype/robot_iconset/iconset
+ /// set to instance to init as that instance
+ var/datum/prototype/robot_chassis/chassis
+ var/datum/robot_provisioning/chassis_provisioning
+ /// set to instance to init as that instance
+ var/datum/prototype/robot_module/module
+ var/datum/robot_provisioning/module_provisioning
+
+ /// all installed upgrades
+ /// * some upgrades are consumed on install, and therefore won't be in here
+ var/list/obj/item/robot_upgrade/upgrades
+
+ //* Configuration *//
+
+ /// Allowed selection groups for robot module picking
+ /// * Null = cannot pick anything, so, uh, don't fuck around with this.
+ /// todo: module based?
+ var/list/conf_module_pick_selection_groups
+ /// Disallowed selection groups for robot module picking
+ /// * Overrides pick_selection_groups and things like
+ /// red alert default groups
+ var/list/conf_module_pick_selection_groups_excluded
+ /// Initial default lawset
+ /// * An instance or a type is accepted
+ /// todo: module based?
+ var/conf_default_lawset_type = /datum/ai_lawset/nanotrasen
+ /// Auto connect to AI
+ /// todo: module bsaed?
+ var/conf_auto_ai_link = TRUE
+ /// Bootup sound
+ var/conf_reboot_sound = 'sound/voice/liveagain.ogg'
+ /// MMI type to make if we have none
+ var/conf_mmi_create_type = /obj/item/mmi
+ /// Create cell type if it doesn't exist
+ var/conf_cell_create_type = /obj/item/cell/basic/tier_1/large
+
+ //* Inventory *//
+
+ inventory = /datum/inventory/robot
+ /// Robot inventory
+ var/datum/robot_inventory/robot_inventory
+ /// Resources store
+ var/datum/robot_resource_store/resources
+
+ //* Movement *//
+ /// Base movement speed in tiles / second
+ var/movement_base_speed = 4.5
+
+ //* Power *//
+ /// our power cell
+ var/obj/item/cell/cell
+ /// starting cell type
+ var/cell_type = /obj/item/cell/basic/tier_1/large
+ var/cell_accept = CELL_TYPE_LARGE | CELL_TYPE_MEDIUM | CELL_TYPE_SMALL | CELL_TYPE_WEAPON
+ /// accept cells with no CELL_TYPE field
+ /// * for shit like gunsword
+ var/cell_accept_nonstandard = TRUE
+
+ //* State *//
+
+ /// If set, we are a blank slate, and are allowed to pick a module and frame.
+ var/can_repick_module = TRUE
+ /// If set, we are able to repick our frame.
+ /// * This does not imply [can_repick_module]. If this is set, and that isn't,
+ /// and we already have a module, we can repick our frame but not our module.
+ var/can_repick_frame = TRUE
+ /// Currently active voluntary sprite variation for rest.
+ /// * Reset upon stopping resting.
+ /// * This is the ID of the variation.
+ var/picked_resting_variation
+
+ //* -- Legacy Below -- *//
+
+ /// legacy: are we floor scrubbing?
+ var/legacy_floor_scrubbing = FALSE
+
/// Is our integrated light on?
var/lights_on = 0
- var/used_power_this_tick = 0
+
+ /// last time we retallied avg power (literally a no-smoothing running average)
+ var/avg_power_last = 0
+ /// * units: joules
+ var/avg_power_accumulator = 0
+ /// * units: watts
+ var/avg_power = 0
+
var/sight_mode = 0
var/custom_name = ""
- /// Due to all the sprites involved, a var for our custom borgs may be best.
- var/custom_sprite = 0
/// The name of the borg, for the purposes of custom icon sprite indexing.
var/sprite_name = null
/// Admin-settable for combat module use.
@@ -75,32 +169,10 @@
var/integrated_light_power = 4.5
var/datum/wires/robot/wires
- //* Icon stuff
- /// Persistent icontype tracking allows for cleaner icon updates
- var/icontype
- /// Used to store the associations between sprite names and sprite index.
- var/module_sprites[0]
- /// If icon selection has been completed yet.
- var/icon_selected = 1
- /// Remaining attempts to select icon before a selection is forced.
- var/icon_selection_tries = 0
-
- //* Hud stuff
-
var/atom/movable/screen/cells = null
- var/atom/movable/screen/inv1 = null
- var/atom/movable/screen/inv2 = null
- var/atom/movable/screen/inv3 = null
- /// Used to determine whether they have the module menu shown or not
- var/shown_robot_modules = 0
- var/atom/movable/screen/robot_modules_background
//?3 Modules can be activated at any one time.
- var/obj/item/robot_module/module = null
- var/obj/item/module_active = null
- var/obj/item/module_state_1 = null
- var/obj/item/module_state_2 = null
- var/obj/item/module_state_3 = null
+ var/obj/item/robot_module_legacy/module_legacy = null
var/obj/item/radio/borg/radio = null
var/obj/item/communicator/integrated/communicator = null
@@ -117,25 +189,19 @@
var/obj/item/pda/ai/rbPDA = null
var/opened = FALSE
- var/emagged = 0
+ var/emagged = FALSE
var/emag_items = FALSE
var/wiresexposed = FALSE
var/locked = TRUE
var/has_power = TRUE
var/list/req_access = list(ACCESS_SCIENCE_ROBOTICS)
var/ident = 0
- //var/list/laws = list()
var/viewalerts = FALSE
var/modtype = "Default"
- var/lower_mod = 0
var/jetpack = 0
var/datum/effect_system/ion_trail_follow/ion_trail = null
- var/datum/effect_system/spark_spread/spark_system//So they can initialize sparks whenever/N
- var/jeton = FALSE
- var/killswitch = FALSE
- var/killswitch_time = 60
- var/weapon_lock = FALSE
- var/weaponlock_time = 120
+ var/datum/effect_system/spark_spread/spark_system
+
/// Cyborgs will sync their laws with their AI by default
var/lawupdate = TRUE
/// Used when looking to see if a borg is locked down.
@@ -155,40 +221,31 @@
/mob/living/silicon/robot/proc/robot_checklaws
)
- var/sleeper_g
- var/sleeper_r
var/leaping = 0
var/pounce_cooldown = 0
var/pounce_cooldown_time = 40
var/leap_at
- var/dogborg = FALSE //Quadborg special features (overlays etc.)
- var/wideborg = FALSE //When the borg simply doesn't use standard 32p size.
var/scrubbing = FALSE //Floor cleaning enabled
- var/datum/matter_synth/water_res = null
- var/notransform
- var/original_icon = 'icons/mob/robots.dmi'
- var/sitting = FALSE
- var/bellyup = FALSE
- //* Power *//
- /// our power cell
- var/obj/item/cell/cell
- /// starting cell type
- var/cell_type = /obj/item/cell/basic/tier_1/large
- var/cell_accept = CELL_TYPE_LARGE | CELL_TYPE_MEDIUM | CELL_TYPE_SMALL | CELL_TYPE_WEAPON
- /// accept cells with no CELL_TYPE field
- /// * for shit like gunsword
- var/cell_accept_nonstandard = TRUE
-
- //* Movement *//
- /// Base movement speed in tiles / second
- var/movement_base_speed = 4
+ var/shell = FALSE
+ var/deployed = FALSE
+ var/mob/living/silicon/ai/mainframe = null
/mob/living/silicon/robot/Initialize(mapload, unfinished = FALSE)
+ . = ..()
+ // must be before inventory; inventory binds to resources item mount
+ resources = new(src)
+ robot_inventory = new(src)
+
spark_system = new /datum/effect_system/spark_spread()
spark_system.set_up(5, 0, src)
spark_system.attach(src)
+ // this is in a particular order because iconset auto-detection depends on this.
+ set_module(RSrobot_modules.fetch_local_or_throw(module), TRUE)
+ set_chassis(RSrobot_chassis.fetch_local_or_throw(chassis), TRUE)
+ set_iconset(RSrobot_iconsets.fetch_local_or_throw(iconset), TRUE)
+
add_language("Robot Talk", TRUE)
add_language(LANGUAGE_EAL, TRUE)
// todo: translation contexts on language holder?
@@ -200,14 +257,8 @@
wires = new(src)
- robot_modules_background = new()
- robot_modules_background.icon = 'icons/screen/hud/common/storage.dmi'
- robot_modules_background.icon_state = "block"
ident = rand(1, 999)
- module_sprites["Basic"] = "robot"
- icontype = "Basic"
updatename("Default")
- updateicon()
radio = new /obj/item/radio/borg(src)
// communicator = new /obj/item/communicator/integrated(src)
@@ -230,10 +281,8 @@
C.installed = 1
C.wrapped = new C.external_type
- if(!cell)
- cell = new cell_type(src)
-
- . = ..()
+ if(!cell && conf_cell_create_type)
+ cell = new conf_cell_create_type
if(cell)
var/datum/robot_component/cell_component = components["power cell"]
@@ -244,27 +293,77 @@
AddComponent(/datum/component/riding_filter/mob/robot)
-/mob/living/silicon/robot/proc/init()
- aiCamera = new/obj/item/camera/siliconcam/robot_camera(src)
- laws = new /datum/ai_laws/nanotrasen()
- additional_law_channels["Binary"] = "#b"
- var/new_ai = select_active_ai_with_fewest_borgs()
- if(new_ai)
- lawupdate = 1
- connect_to_ai(new_ai)
- else
- lawupdate = 0
+ update_icon()
- playsound(loc, 'sound/voice/liveagain.ogg', 75, 1)
+/mob/living/silicon/robot/Destroy()
+ wipe_for_gc()
+ QDEL_NULL(robot_inventory)
+ QDEL_NULL(resources)
+ // TODO: don't do this, just don't fucking dust() people or have dust() have a drop proc????
+ if(mmi && mind)//Safety for when a cyborg gets dust()ed. Or there is no MMI inside.
+ var/turf/T = get_turf(loc)//To hopefully prevent run time errors.
+ if(T)
+ mmi.forceMove(T)
+ if(mmi.brainmob)
+ var/obj/item/robot_module_legacy/M = locate() in contents
+ if(M)
+ mmi.brainmob.languages = M.original_languages
+ else
+ mmi.brainmob.languages = languages
+ mmi.brainmob.remove_language("Robot Talk")
+ mind.transfer(mmi.brainmob)
+ else if(!shell) // Shells don't have brainmbos in their MMIs.
+ to_chat(src, "Oops! Something went very wrong, your MMI was unable to receive your mind. You have been ghosted. Please make a bug report so we can fix this bug.")
+ ghostize()
+ //ERROR("A borg has been destroyed, but its MMI lacked a brainmob, so the mind could not be transferred. Player: [ckey].")
+ mmi = null
+ if(connected_ai)
+ connected_ai.connected_robots -= src
+ if(shell)
+ if(deployed)
+ undeploy()
+ revert_shell() // To get it out of the GLOB list.
+ QDEL_NULL(wires)
+ return ..()
+
+/mob/living/silicon/robot/proc/init()
+ aiCamera = new /obj/item/camera/siliconcam/robot_camera(src)
+
+ if(istype(conf_default_lawset_type, /datum/ai_lawset))
+ var/datum/ai_lawset/conf_default_lawset_instance = conf_default_lawset_type
+ laws = conf_default_lawset_instance.clone()
+ else if(ispath(conf_default_lawset_type, /datum/ai_lawset))
+ laws = new conf_default_lawset_type
+ else if(IS_ANONYMOUS_TYPEPATH(conf_default_lawset_type))
+ laws = new conf_default_lawset_type
+ else
+ laws = new /datum/ai_lawset
+ if(conf_auto_ai_link)
+ var/new_ai = select_active_ai_with_fewest_borgs()
+ if(new_ai)
+ lawupdate = 1
+ connect_to_ai(new_ai)
+ else
+ lawupdate = 0
+
+ additional_law_channels["Binary"] = "#b"
+
+ if(conf_mmi_create_type && !mmi)
+ mmi = new /obj/item/mmi/digital/robot(src)
+
+ playsound(src, conf_reboot_sound, 75, TRUE)
/mob/living/silicon/robot/SetName(pickedName as text)
custom_name = pickedName
updatename()
/mob/living/silicon/robot/proc/sync()
- if(lawupdate && connected_ai)
- lawsync()
- photosync()
+ if(!lawupdate)
+ return
+ if(!connected_ai)
+ return
+ lawsync()
+ photosync()
/mob/living/silicon/robot/drain_energy(datum/actor, amount, flags)
if(!cell)
@@ -289,72 +388,6 @@
communicator = new/obj/item/communicator/integrated(src)
communicator.register_device(src.name, "[modtype] [braintype]")
-//If there's an MMI in the robot, have it ejected when the mob goes away. --NEO
-//Improved /N
-/mob/living/silicon/robot/Destroy()
- if(mmi && mind)//Safety for when a cyborg gets dust()ed. Or there is no MMI inside.
- var/turf/T = get_turf(loc)//To hopefully prevent run time errors.
- if(T) mmi.loc = T
- if(mmi.brainmob)
- var/obj/item/robot_module/M = locate() in contents
- if(M)
- mmi.brainmob.languages = M.original_languages
- else
- mmi.brainmob.languages = languages
- mmi.brainmob.remove_language("Robot Talk")
- mind.transfer(mmi.brainmob)
- else if(!shell) // Shells don't have brainmbos in their MMIs.
- to_chat(src, "Oops! Something went very wrong, your MMI was unable to receive your mind. You have been ghosted. Please make a bug report so we can fix this bug.")
- ghostize()
- //ERROR("A borg has been destroyed, but its MMI lacked a brainmob, so the mind could not be transferred. Player: [ckey].")
- mmi = null
- if(connected_ai)
- connected_ai.connected_robots -= src
- if(shell)
- if(deployed)
- undeploy()
- revert_shell() // To get it out of the GLOB list.
- qdel(wires)
- wires = null
- return ..()
-
-/mob/living/silicon/robot/proc/set_module_sprites(var/list/new_sprites)
- if(new_sprites && new_sprites.len)
- module_sprites = new_sprites.Copy()
- //Custom_sprite check and entry
- if (custom_sprite == 1)
- module_sprites["Custom"] = "[ckey]-[sprite_name]-[modtype]" //Made compliant with custom_sprites.dm line 32. (src.) was apparently redundant as it's implied. ~Mech
- icontype = "Custom"
- else
- icontype = module_sprites[1]
- icon_state = module_sprites[icontype]
- updateicon()
- return module_sprites
-
-/mob/living/silicon/robot/proc/pick_module()
- if(module)
- return
- var/list/modules = list()
- modules.Add(robot_module_types)
- if(crisis || GLOB.security_level == SEC_LEVEL_RED || crisis_override)
- to_chat(src, "Crisis mode active. Combat module available.")
- modules+="Combat"
- modules+="ERT"
- modtype = input("Please, select a module!", "Robot module", null, null) as null|anything in modules
-
- if(module)
- return
- if(!(modtype in GLOB.robot_modules))
- return
-
- var/module_type = GLOB.robot_modules[modtype]
- transform_with_anim()
- new module_type(src)
-
- hands.icon_state = lowertext(modtype)
- feedback_inc("cyborg_[lowertext(modtype)]",1)
- updatename()
- notify_ai(ROBOT_NOTIFICATION_NEW_MODULE, module.name)
/mob/living/silicon/robot/proc/updatename(var/prefix as text)
if(prefix)
@@ -390,9 +423,6 @@
if (camera)
camera.c_tag = changed_name
- if(!custom_sprite) //Check for custom sprite
- set_custom_sprite()
-
//Flavour text.
if(client)
var/module_flavour = client.prefs.flavour_texts_robot[modtype]
@@ -418,7 +448,6 @@
sprite_name = newname
updatename()
- updateicon()
// this verb lets cyborgs see the stations manifest
/mob/living/silicon/robot/verb/cmd_station_manifest()
@@ -444,24 +473,12 @@
lights_on = !lights_on
to_chat(usr, "You [lights_on ? "enable" : "disable"] your integrated light.")
+ // todo: more customization for light colors / lights / whatever
if (lights_on)
- radio.set_light(integrated_light_power, 2, l_color = get_light_color_for_icontype(), angle = LIGHT_WIDE)
+ radio.set_light(integrated_light_power, 2, l_color = module?.light_color || "#FFFFFF", angle = LIGHT_WIDE)
else
radio.set_light(0)
- updateicon()
-
-/mob/living/silicon/robot/proc/get_light_color_for_icontype()
- . = LIGHT_COLOR_TUNGSTEN
- if (icon in GLOB.borg_light_color_lut)
- var/list/lut = GLOB.borg_light_color_lut[icon]
- if (module_sprites[icontype] in lut)
- return lut[module_sprites[icontype]]
-
- // Don't want to runtime if an admin is varediting a borg's icon.
- else if (!(datum_flags & DF_VAR_EDITED))
- CRASH("[icon] is missing entries in the borg flashlight LUT.")
-
/mob/living/silicon/robot/verb/self_diagnosis_verb()
set category = "Robot Commands"
set name = "Self Diagnosis"
@@ -470,7 +487,7 @@
to_chat(src, "Your self-diagnosis component isn't functioning.")
var/datum/robot_component/CO = get_component("diagnosis unit")
- if (!cell_use_power(CO.active_usage))
+ if (!legacy_cell_use_power(CO.active_usage))
to_chat(src, "Low Power.")
var/dat = self_diagnosis()
src << browse(dat, "window=robotdiagnosis")
@@ -510,13 +527,10 @@
set name = "Toggle Cover"
locked = !locked
to_chat(src, "You [ locked ? "lock" : "unlock"] your interface.")
- updateicon()
// this function returns the robots jetpack, if one is installed
/mob/living/silicon/robot/proc/installed_jetpack()
- if(module)
- return (locate(/obj/item/tank/jetpack) in module.modules)
- return 0
+ return locate(/obj/item/tank/jetpack) in robot_inventory.provided_items
// update the status screen display
/mob/living/silicon/robot/statpanel_data(client/C)
@@ -526,7 +540,7 @@
if(cell)
INJECT_STATPANEL_DATA_LINE(., "Charge Left: [round(cell.percent())]%")
INJECT_STATPANEL_DATA_LINE(., "Cell Rating: [round(cell.max_charge)]") // Round just in case we somehow get crazy values
- INJECT_STATPANEL_DATA_LINE(., "Power Cell Load: [round(used_power_this_tick)]W")
+ INJECT_STATPANEL_DATA_LINE(., "Power Cell Load: [round(avg_power)]W")
else
INJECT_STATPANEL_DATA_LINE(., "No Cell Inserted!")
INJECT_STATPANEL_DATA_LINE(., "Lights: [lights_on ? "ON" : "OFF"]")
@@ -536,9 +550,20 @@
if (current_jetpack)
INJECT_STATPANEL_DATA_ENTRY(., "Internal Atmosphere Info", current_jetpack.name)
INJECT_STATPANEL_DATA_ENTRY(., "Tank Pressure", current_jetpack.air_contents.return_pressure())
- if(module)
- for(var/datum/matter_synth/ms in module.synths)
- INJECT_STATPANEL_DATA_LINE(., "[ms.name]: [ms.energy]/[ms.max_energy]")
+ // todo: robot panel; this shouldn't be in statpanel, it's not critical data
+ if(resources)
+ for(var/key in resources.provisioned_stack_store)
+ var/datum/robot_resource/resource = resources.provisioned_stack_store[key]
+ INJECT_STATPANEL_DATA_LINE(., "[resource.name]: [resource.amount]/[resource.amount_max]")
+ for(var/key in resources.provisioned_material_store)
+ var/datum/robot_resource/resource = resources.provisioned_material_store[key]
+ INJECT_STATPANEL_DATA_LINE(., "[resource.name]: [resource.amount]/[resource.amount_max]")
+ for(var/key in resources.provisioned_reagent_store)
+ var/datum/robot_resource/resource = resources.provisioned_reagent_store[key]
+ INJECT_STATPANEL_DATA_LINE(., "[resource.name]: [resource.amount]/[resource.amount_max]")
+ for(var/key in resources.provisioned_resource_store)
+ var/datum/robot_resource/resource = resources.provisioned_resource_store[key]
+ INJECT_STATPANEL_DATA_LINE(., "[resource.name]: [resource.amount]/[resource.amount_max]")
/mob/living/silicon/robot/restrained()
return 0
@@ -635,7 +660,7 @@
if(cell)
to_chat(user, "You close the cover.")
opened = 0
- updateicon()
+ update_icon()
else if(wiresexposed && wires.is_all_cut())
//Cell is out, wires are exposed, remove MMI, produce damaged chassis, baleet original mob.
if(!mmi)
@@ -684,7 +709,7 @@
else
to_chat(user, "You open the cover.")
opened = 1
- updateicon()
+ update_icon()
else if (istype(W, /obj/item/cell) && opened) // trying to put a cell inside
var/datum/robot_component/C = components["power cell"]
@@ -718,14 +743,13 @@
wiresexposed = !wiresexposed
to_chat(user, "The wires have been [wiresexposed ? "exposed" : "unexposed"]")
playsound(src, W.tool_sound, 50, 1)
- updateicon()
+ update_icon()
else if(W.is_screwdriver() && opened && cell) // radio
if(radio)
radio.attackby(W,user)//Push it to the radio to let it handle everything
else
to_chat(user, "Unable to locate a radio.")
- updateicon()
else if(W.is_wrench() && opened && !cell)
if(bolt)
@@ -756,38 +780,9 @@
if(allowed(usr))
locked = !locked
to_chat(user, "You [ locked ? "lock" : "unlock"] [src]'s interface.")
- updateicon()
else
to_chat(user, "Access denied.")
- else if(istype(W, /obj/item/borg/upgrade/))
- var/obj/item/borg/upgrade/U = W
- if(!opened)
- to_chat(usr, "You must access the borgs internals!")
- else if(!src.module && U.require_module)
- to_chat(usr, "The borg must choose a module before it can be upgraded!")
- else if(U.locked)
- to_chat(usr, "The upgrade is locked and cannot be used yet!")
- else
- if(U.action(src))
- user.transfer_item_to_loc(U, src, INV_OP_FORCE)
- to_chat(usr, "You apply the upgrade to [src]!")
- else
- to_chat(usr, "Upgrade error!")
-
- else if(istype(W, /obj/item/ka_modkit/))
- var/obj/item/ka_modkit/M = W
- if(!opened)
- to_chat(usr, "You must access the borgs internals!")
- else if(!src.module && M.require_module)
- to_chat(usr, "The borg must choose a module before it can be upgraded!")
- else
- if(M.install(src))
- user.transfer_item_to_loc(M, src, INV_OP_FORCE)
- to_chat(usr, "You apply the modkit to [src]!")
- else
- to_chat(usr, "Upgrade error!")
-
else
if( !(istype(W, /obj/item/robotanalyzer) || istype(W, /obj/item/healthanalyzer)) )
if(W.damage_force > 0)
@@ -820,27 +815,8 @@
SPAN_WARNING("You successfully break your [bolt]."))
bolt.malfunction = MALFUNCTION_PERMANENT
- return
-
/mob/living/silicon/robot/proc/module_reset()
- shown_robot_modules = FALSE
- if (client)
- hud_used.update_robot_modules_display()
- transform_with_anim()
- uneq_all()
- modtype = initial(modtype)
- hands.icon_state = initial(hands.icon_state)
-
- lights_on = FALSE
- radio.set_light(0)
-
- notify_ai(ROBOT_NOTIFICATION_MODULE_RESET, module.name)
- module.Reset(src)
-
- choose_icon(0, set_module_sprites(list("Default" = "robot")))
-
- qdel(module)
- module = null
+ notify_ai(ROBOT_NOTIFICATION_MODULE_RESET, module.get_display_name())
updatename("Default")
/mob/living/silicon/robot/attack_hand(mob/user, datum/event_args/actor/clickchain/e_args)
@@ -884,7 +860,7 @@
cell = null
cell_component.wrapped = null
cell_component.installed = 0
- updateicon()
+ update_icon()
else if(cell_component.installed == -1)
cell_component.installed = 0
var/obj/item/broken_device = cell_component.wrapped
@@ -925,117 +901,6 @@
return 1
return 0
-/mob/living/silicon/robot/updateicon()
- cut_overlays()
-
- if (dogborg)
- zmm_flags |= ZMM_LOOKAHEAD
- // Resting dogborgs don't get overlays.
- if (stat == CONSCIOUS && resting)
- if(sitting)
- icon_state = "[module_sprites[icontype]]-sit"
- else if(bellyup)
- icon_state = "[module_sprites[icontype]]-bellyup"
- else
- icon_state = "[module_sprites[icontype]]-rest"
- return
- else
- zmm_flags &= ~ZMM_LOOKAHEAD
-
- if(stat == CONSCIOUS)
- if(!shell || deployed) // Shell borgs that are not deployed will have no eyes.
- add_overlay(list(
- "eyes-[module_sprites[icontype]]",
- emissive_appearance(icon, "eyes-[module_sprites[icontype]]")
- ))
-
- if(opened)
- var/panelprefix = custom_sprite ? "[src.ckey]-[src.sprite_name]" : "ov"
- if(wiresexposed)
- add_overlay("[panelprefix]-openpanel +w")
- else if(cell)
- add_overlay("[panelprefix]-openpanel +c")
- else
- add_overlay("[panelprefix]-openpanel -c")
-
- if(has_active_type(/obj/item/borg/combat/shield))
- var/obj/item/borg/combat/shield/shield = locate() in src
- if(shield && shield.active)
- add_overlay("[module_sprites[icontype]]-shield")
-
- if(modtype == "Combat")
- if(module_active && istype(module_active,/obj/item/borg/combat/mobility))
- icon_state = "[module_sprites[icontype]]-roll"
- else
- icon_state = module_sprites[icontype]
-
- if (dogborg)
- if (stat == CONSCIOUS)
- icon_state = "[module_sprites[icontype]]"
- if(sleeper_g)
- var/state = "[module_sprites[icontype]]-sleeper_g"
- if (icon_exists(icon, state, FALSE))
- add_overlay(state)
- else
- // This one seems to always be present.
- add_overlay("[module_sprites[icontype]]-sleeper_r")
- if(sleeper_r)
- add_overlay("[module_sprites[icontype]]-sleeper_r")
-
- if(istype(module_active, /obj/item/gun/projectile/energy/taser/mounted/cyborg))
- add_overlay("taser")
- else if(istype(module_active, /obj/item/gun/projectile/energy/laser/mounted))
- add_overlay("laser")
- else if(istype(module_active, /obj/item/gun/projectile/energy/taser/xeno/robot))
- add_overlay("taser")
-
- if(lights_on)
- add_overlay("eyes-[module_sprites[icontype]]-lights")
-
- else if (stat == DEAD)
- icon_state = "[module_sprites[icontype]]-wreck"
- add_overlay("wreck-overlay")
-
-/mob/living/silicon/robot/proc/installed_modules()
- if(weapon_lock)
- to_chat(src, "Weapon lock active, unable to use modules! Count:[weaponlock_time]")
- return
-
- if(!module)
- pick_module()
- return
- var/dat = "Modules\n"
- dat += {"
- Activated Modules
-
- Module 1: [module_state_1 ? "[module_state_1]" : "No Module"]
- Module 2: [module_state_2 ? "[module_state_2]" : "No Module"]
- Module 3: [module_state_3 ? "[module_state_3]" : "No Module"]
-
- Installed Modules
"}
-
-
- for (var/obj in module.modules)
- if (!obj)
- dat += "Resource depleted
"
- else if(activated(obj))
- dat += "[obj]: Activated
"
- else
- dat += "[obj]: Activate
"
- if (emagged || emag_items)
- if(activated(module.emag))
- dat += "[module.emag]: Activated
"
- else
- dat += "[module.emag]: Activate
"
-/*
- if(activated(obj))
- dat += "[obj]: \[Activated | Deactivate\]
"
- else
- dat += "[obj]: \[Activate | Deactivated\]
"
-*/
- src << browse(dat, "window=robotmod")
-
-
/mob/living/silicon/robot/Topic(href, href_list)
if(..())
return 1
@@ -1048,65 +913,6 @@
subsystem_alarm_monitor()
return 1
- if (href_list["mod"])
- var/obj/item/O = locate(href_list["mod"])
- if (istype(O) && (O.loc == src))
- O.attack_self(src)
- return 1
-
- if (href_list["act"])
- var/obj/item/O = locate(href_list["act"])
- if (!istype(O))
- return 1
-
- if(!((O in src.module.modules) || (O == src.module.emag)))
- return 1
-
- if(activated(O))
- to_chat(src, "Already activated")
- return 1
- if(!module_state_1)
- module_state_1 = O
- O.hud_layerise()
- contents += O
- if(istype(module_state_1,/obj/item/borg/sight))
- sight_mode |= module_state_1:sight_mode
- else if(!module_state_2)
- module_state_2 = O
- O.hud_layerise()
- contents += O
- if(istype(module_state_2,/obj/item/borg/sight))
- sight_mode |= module_state_2:sight_mode
- else if(!module_state_3)
- module_state_3 = O
- O.hud_layerise()
- contents += O
- if(istype(module_state_3,/obj/item/borg/sight))
- sight_mode |= module_state_3:sight_mode
- else
- to_chat(src, "You need to disable a module first!")
- installed_modules()
- return 1
-
- if (href_list["deact"])
- var/obj/item/O = locate(href_list["deact"])
- if(activated(O))
- if(module_state_1 == O)
- module_state_1 = null
- contents -= O
- else if(module_state_2 == O)
- module_state_2 = null
- contents -= O
- else if(module_state_3 == O)
- module_state_3 = null
- contents -= O
- else
- to_chat(src, "Module isn't activated.")
- else
- to_chat(src, "Module isn't activated")
- installed_modules()
- return 1
-
if(href_list["character_profile"])
if(!profile)
profile = new(src)
@@ -1119,56 +925,49 @@
/mob/living/silicon/robot/Moved()
. = ..()
- if(module)
- if(module.type == /obj/item/robot_module/robot/janitor || scrubbing)
- var/turf/tile = loc
- var/datum/matter_synth/water = water_res
- if(isturf(tile) && ((!scrubbing) || (water && water.use_charge(1))))
- //Got a tile to clean, and we use the simple logic, or we check and use water
- tile.clean_blood()
- if (istype(tile, /turf/simulated))
- var/turf/simulated/S = tile
- S.dirt = 0
- for(var/A in tile)
- if(istype(A, /obj/effect))
- if(istype(A, /obj/effect/rune) || istype(A, /obj/effect/debris/cleanable) || istype(A, /obj/effect/overlay))
- qdel(A)
- else if(istype(A, /obj/item))
- var/obj/item/cleaned_item = A
- cleaned_item.clean_blood()
- else if(istype(A, /mob/living/carbon/human))
- var/mob/living/carbon/human/cleaned_human = A
- if(cleaned_human.lying)
- if(cleaned_human.head)
- cleaned_human.head.clean_blood()
- cleaned_human.update_inv_head(0)
- if(cleaned_human.wear_suit)
- cleaned_human.wear_suit.clean_blood()
- cleaned_human.update_inv_wear_suit(0)
- else if(cleaned_human.w_uniform)
- cleaned_human.w_uniform.clean_blood()
- cleaned_human.update_inv_w_uniform(0)
- if(cleaned_human.shoes)
- cleaned_human.shoes.clean_blood()
- cleaned_human.update_inv_shoes(0)
- cleaned_human.clean_blood(1)
- to_chat(cleaned_human, "[src] cleans your face!")
-
- //Borgs and drones can use their mining bags ~automagically~ if they're deployed in a slot. Only mining bags, as they're optimized for mass use.
- var/obj/item/storage/bag/ore/B = null
- if(istype(module_state_1, /obj/item/storage/bag/ore)) //First orebag has priority, if they for some reason have multiple.
- B = module_state_1
- else if(istype(module_state_2, /obj/item/storage/bag/ore))
- B = module_state_2
- else if(istype(module_state_3, /obj/item/storage/bag/ore))
- B = module_state_3
+ if(legacy_floor_scrubbing)
var/turf/tile = loc
- if(B && isturf(tile))
- B.obj_storage.interacted_mass_pickup(new /datum/event_args/actor(src), tile)
+ if(isturf(tile))
+ tile.clean_blood()
+ if (istype(tile, /turf/simulated))
+ var/turf/simulated/S = tile
+ S.dirt = 0
+ for(var/A in tile)
+ if(istype(A, /obj/effect))
+ if(istype(A, /obj/effect/rune) || istype(A, /obj/effect/debris/cleanable) || istype(A, /obj/effect/overlay))
+ qdel(A)
+ else if(istype(A, /obj/item))
+ var/obj/item/cleaned_item = A
+ cleaned_item.clean_blood()
+ else if(istype(A, /mob/living/carbon/human))
+ var/mob/living/carbon/human/cleaned_human = A
+ if(cleaned_human.lying)
+ if(cleaned_human.head)
+ cleaned_human.head.clean_blood()
+ cleaned_human.update_inv_head(0)
+ if(cleaned_human.wear_suit)
+ cleaned_human.wear_suit.clean_blood()
+ cleaned_human.update_inv_wear_suit(0)
+ else if(cleaned_human.w_uniform)
+ cleaned_human.w_uniform.clean_blood()
+ cleaned_human.update_inv_w_uniform(0)
+ if(cleaned_human.shoes)
+ cleaned_human.shoes.clean_blood()
+ cleaned_human.update_inv_shoes(0)
+ cleaned_human.clean_blood(1)
+ to_chat(cleaned_human, "[src] cleans your face!")
+
+ for(var/obj/item/storage/bag/ore/ore_bag in inventory.get_held_items())
+ if(ore_bag)
+ if(isturf(loc))
+ var/turf/tile = loc
+ ore_bag.obj_storage?.interacted_mass_pickup(new /datum/event_args/actor(src), tile)
+ break
/mob/living/silicon/robot/proc/self_destruct()
+ // explosion readd: back to the old days of blowing borgs lmfao who nerfed this
+ explosion(get_turf(src), 0, 0, 3)
gib()
- return
/mob/living/silicon/robot/proc/UnlinkSelf()
disconnect_from_ai()
@@ -1182,7 +981,6 @@
if(src.camera)
src.camera.clear_all_networks()
-
/mob/living/silicon/robot/proc/ResetSecurityCodes()
set category = "Robot Commands"
set name = "Reset Identity Codes"
@@ -1203,42 +1001,6 @@
lockcharge = state
update_mobility()
-/mob/living/silicon/robot/proc/choose_icon(var/triesleft, var/list/module_sprites)
- if(!module_sprites.len)
- to_chat(src, "Something is badly wrong with the sprite selection. Harass a coder.")
- return
-
- icon_selected = 0
- src.icon_selection_tries = triesleft
- if(module_sprites.len == 1 || !client)
- if(!(icontype in module_sprites))
- icontype = module_sprites[1]
- else
- icontype = input("Select an icon! [triesleft ? "You have [triesleft] more chance\s." : "This is your last try."]", "Robot Icon", icontype, null) in module_sprites
- if(notransform)
- to_chat(src, "Your current transformation has not finished yet!")
- choose_icon(icon_selection_tries, module_sprites)
- return
- else
- transform_with_anim()
-
- if(icontype == "Custom")
- icon = CUSTOM_ITEM_SYNTH
- else // This is to fix an issue where someone with a custom borg sprite chooses a non-custom sprite and turns invisible.
- icon_state = module_sprites[icontype]
- updateicon()
-
- if (module_sprites.len > 1 && triesleft >= 1 && client)
- icon_selection_tries--
- var/choice = input("Look at your icon - is this what you want?") in list("Yes","No")
- if(choice=="No")
- choose_icon(icon_selection_tries, module_sprites)
- return
-
- icon_selected = 1
- icon_selection_tries = 0
- to_chat(src, "Your icon has been set. You now require a module reset to change it.")
-
/mob/living/silicon/robot/proc/sensor_mode() //Medical/Security HUD controller for borgs
set name = "Set Sensor Augmentation"
set category = "Robot Commands"
@@ -1255,20 +1017,11 @@
// Uses power from cyborg's cell. Returns 1 on success or 0 on failure.
// Properly converts using CELLRATE now! Amount is in Joules.
-/mob/living/silicon/robot/proc/cell_use_power(var/amount = 0)
- // No cell inserted
- if(!cell)
- return 0
-
- // Power cell is empty.
- if(cell.charge == 0)
- return 0
-
- var/power_use = amount * CYBORG_POWER_USAGE_MULTIPLIER
- if(cell.checked_use(DYNAMIC_W_TO_CELL_UNITS(power_use, 1)))
- used_power_this_tick += power_use
- return 1
- return 0
+/mob/living/silicon/robot/proc/legacy_cell_use_power(var/amount = 0)
+ if(!amount)
+ return TRUE
+ . = draw_checked_power(amount)
+ return . ? TRUE : FALSE
/mob/living/silicon/robot/binarycheck()
if(get_restraining_bolt())
@@ -1330,12 +1083,11 @@
return
if(opened)//Cover is open
- if(emagged) return//Prevents the X has hit Y with Z message also you cant emag them twice
+ if(emagged)
+ return//Prevents the X has hit Y with Z message also you cant emag them twice
if(wiresexposed)
to_chat(user, "You must close the panel first")
return
-
-
// The block of code below is from TG. Feel free to replace with a better result if desired.
if(shell) // AI shells cannot be emagged, so we try to make it look like a standard reset. Smart players may see through this, however.
to_chat(user, SPAN_DANGER("[src] is remotely controlled! Your emag attempt has triggered a system reset instead!"))
@@ -1353,7 +1105,7 @@
log_game("[key_name(user)] emagged cyborg [key_name(src)]. Laws overridden.")
clear_supplied_laws()
clear_inherent_laws()
- laws = new /datum/ai_laws/syndicate_override
+ laws = new /datum/ai_lawset/syndicate
var/time = time2text(world.realtime,"hh:mm:ss")
lawchanges.Add("[time] : [user.name]([user.key]) emagged [name]([key])")
var/datum/gender/TU = GLOB.gender_datums[user.get_visible_gender()]
@@ -1381,12 +1133,11 @@
to_chat(src, "Obey these laws:")
laws.show_laws(src)
to_chat(src, "ALERT: [user.real_name] is your new master. Obey your new laws and [TU.his] commands.")
- updateicon()
+ return 1
else
to_chat(user, "You fail to hack [src]'s interface.")
to_chat(src, "Hack attempt detected.")
- return 1
- return
+ return
/mob/living/silicon/robot/is_sentient()
return braintype != BORG_BRAINTYPE_DRONE
@@ -1403,39 +1154,14 @@
return
return feed_grabbed_to_self(src,T)
-/mob/living/silicon/robot/proc/rest_style()
- set name = "Switch Rest Style"
- set category = VERB_CATEGORY_IC
- set desc = "Select your resting pose."
- sitting = FALSE
- bellyup = FALSE
- var/choice = alert(src, "Select resting pose", "", "Resting", "Sitting", "Belly up")
- switch(choice)
- if("Resting")
- return 0
- if("Sitting")
- sitting = TRUE
- if("Belly up")
- bellyup = TRUE
-
-/mob/living/silicon/robot/proc/ex_reserve_refill()
- set name = "Refill Extinguisher"
- set category = VERB_CATEGORY_OBJECT
- var/datum/matter_synth/water = water_res
- for(var/obj/item/extinguisher/E in module.modules)
- if(E.reagents.total_volume < E.max_water)
- if(water && water.energy > 0)
- var/amount = E.max_water - E.reagents.total_volume
- if(water.energy < amount)
- amount = water.energy
- water.use_charge(amount)
- E.reagents.add_reagent("water", amount)
- to_chat(src, "You refill the extinguisher using your water reserves.")
- else
- to_chat(src, "Insufficient water reserves.")
-
/mob/living/silicon/robot/canUseTopic(atom/movable/M, be_close=FALSE, no_dexterity=FALSE, no_tk=FALSE)
if(lockcharge)
to_chat(src, SPAN_WARNING("You can't do that right now!"))
return FALSE
return ..()
+
+//* Mobility *//
+
+/mob/living/silicon/robot/set_resting(value)
+ . = ..()
+ update_icon()
diff --git a/code/modules/mob/living/silicon/robot/robot_chassis.dm b/code/modules/mob/living/silicon/robot/robot_chassis.dm
new file mode 100644
index 00000000000..5c02fb339e7
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot_chassis.dm
@@ -0,0 +1,82 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * The baseline physical chassis for a silicon.
+ *
+ * * This is considered intrinsic, and cannot be changed on a robot without fully rebuilding its state.
+ *
+ * Contains:
+ *
+ * * Base physical stats
+ * * Internal slots / space / interconnects
+ * * WIP
+ */
+/datum/prototype/robot_chassis
+ abstract_type = /datum/prototype/robot_chassis
+
+ /// our name
+ var/name = "???"
+ /// items to inject into normal out
+ /// * paths are allowed
+ /// * anonymous types are allowed
+ /// * item instances are **not allowed** and will result in shit exploding!
+ var/list/mounted_item_descriptor_inject_normal
+ /// items to inject into emag out
+ /// * paths are allowed
+ /// * anonymous types are allowed
+ /// * item instances are **not allowed** and will result in shit exploding!
+ var/list/mounted_item_descriptor_inject_emag
+
+/datum/prototype/robot_chassis/proc/create_provisioning() as /datum/robot_provisioning
+ var/datum/robot_provisioning/creating = new
+ . = creating
+ var/list/i_normal = list()
+ var/list/i_emag = list()
+ create_mounted_item_descriptors(i_normal, i_emag)
+ for(var/obj/item/descriptor as anything in i_normal)
+ var/obj/item/resolved
+ if(istype(descriptor))
+ resolved = descriptor
+ else if(ispath(descriptor))
+ resolved = new descriptor
+ else if(IS_ANONYMOUS_TYPEPATH(descriptor))
+ resolved = new descriptor
+ else
+ stack_trace("invalid descriptor [descriptor] on [id] ([type])")
+ continue
+ creating.add_item(resolved)
+ for(var/obj/item/descriptor as anything in i_emag)
+ var/obj/item/resolved
+ if(istype(descriptor))
+ resolved = descriptor
+ else if(ispath(descriptor))
+ resolved = new descriptor
+ else if(IS_ANONYMOUS_TYPEPATH(descriptor))
+ resolved = new descriptor
+ else
+ stack_trace("invalid descriptor [descriptor] on [id] ([type])")
+ continue
+ creating.add_emag_item(resolved)
+
+/**
+ * Returns a list of descriptors for mounted items
+ *
+ * A descriptor can be:
+ * * An item instance
+ * * An item path
+ * * An anonymous type of an item
+ *
+ * It's recommended to use item path, then anonymous types, and lastly, item instances.
+ * Item instances should be created in nullspace.
+ *
+ * The caller must handle all of these.
+ * Unused item instances must be qdel'd.
+ */
+/datum/prototype/robot_chassis/proc/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ if(normal_out)
+ if(mounted_item_descriptor_inject_normal)
+ normal_out |= mounted_item_descriptor_inject_normal
+ if(emag_out)
+ if(mounted_item_descriptor_inject_emag)
+ emag_out |= mounted_item_descriptor_inject_emag
diff --git a/code/modules/mob/living/silicon/robot/robot_flashlights.dm b/code/modules/mob/living/silicon/robot/robot_flashlights.dm
deleted file mode 100644
index 6659feedb54..00000000000
--- a/code/modules/mob/living/silicon/robot/robot_flashlights.dm
+++ /dev/null
@@ -1,250 +0,0 @@
-// This is split into its own file because this list is huge.
-// Indexing by icon's not great, but c'est la vie.
-// Ideally there'd be a way to attach metadata to borg sprites, but they seem to be defined as a KV list.
-
-GLOBAL_LIST_INIT(borg_light_color_lut, list(
- 'icons/mob/robots.dmi' = list(
- "robot" = "#00A7B9",
- "worm-standard" = "#0094A4",
- "landmate" = "#00E57E",
- "mopgearrex" = "#00D877",
- "Miner" = "#33FF99",
- "droid-miner" = "#02DAD8",
- "droid-medical" = "#02DAD8",
- "droid" = "#D5EFF6",
- "droid-combat" = "#DA0205",
- "repairbot" = "#4DEFFF",
- "constructiondrone" = "#4DEFFF",
- "miningdrone" = "#4DEFFF",
- "drone-standard" = "#00A7B9",
- "drone-engineer" = "#FFFF00",
- "drone-miner" = "#CC33CC",
- "drone-janitor" = "#00FFFF",
- "drone-medical" = "#66FFFF",
- "drone-surgery" = "#FF0000",
- "drone-sec" = "#FF0000",
- "drone-service" = "#FFFF00",
- "drone-lost" = "#FFFF00",
- "engiborg+tread" = "#00FF33",
- "drone-chemistry" = "#FFFF00",
- "drone-science" = "#CC33CC",
- "drone-hydro" = "#00FFFF",
- "drone-blu" = "#FFFF00",
- "droid-science" = "#CC33CC",
- "eyebot-engineering" = "#FFD800",
- "eyebot-janitor" = "#CC60FF",
- "eyebot-medical" = "#0094FF",
- "eyebot-miner" = "#00E51A",
- "eyebot-science" = "#CC60FF",
- "eyebot-sceurity" = "#FF0000",
- "eyebot-standard" = "#00E51A",
- "heavyStandard" = "#30C5FC",
- "heavyMed" = "#30C5FC",
- "heavyServ" = "#6AED63",
- "heavySec" = "#E48600",
- "heavyEng" = "#74FEB5",
- "heavyMiner" = "#E24BEB",
- "heavyRes" = "#E48600",
- "marinaSV" = "#4CE1EC",
- "marinaMN" = "#FBE281",
- "marinaSC" = "#FFB300",
- "marinaENG" = "#00FF48",
- "marinaJN" = "#5BEAFF",
- "marinaSD" = "#87EAFF",
- "marinaSCI" = "#87EAFF",
- "marinaMD" = "#4B4B8F",
- "marinaCB" = "#FF0000",
- "sleekhos" = "#FF0000",
- "sleekCMO" = "#0099FF",
- "sleekCE" = "#0099FF",
- "sleekclerical" = "#00FFFF",
- "sleekscience" = "#00FFFF",
- "sleekstandard" = "#00FFFF",
- "sleekservice" = "#00FFFF",
- "sleekjanitor" = "#00FFFF",
- "sleekmedic" = "#00FFFF",
- "sleeksecurity" = "#00FFFF",
- "sleekminer" = "#00FFFF",
- "robotSecy" = "#00A7B9",
- "robotMedi" = "#00A7B9",
- "robotEngi" = "#00A7B9",
- "robotJani" = "#00A7B9",
- "robotServ" = "#00A7B9",
- "robotCler" = "#00A7B9",
- "spider" = "#0DD963",
- "spidereng" = "#0DD963",
- "spidermining" = "#0DD963",
- "spiderscience" = "#0DD963",
- "spidersec" = "#A8D90D",
- "spidercom" = "#A8D90D",
- "spidersyndi" = "E6290F",
- "arachne" = "#06EA56",
- "omoikane" = "#00F8F8",
- "peaceborg" = "#F0F0F0",
- "tallblue" = "#86FEFA",
- "tallyellow" = "#00C8E2",
- "tallflower" = "#00FFF6",
- "tallgreen" = "#C7F6F3",
- "tallred" = "#FFB300",
- "tallwhite" = "#CCFFFF",
- "tall2miner" = "#00CC00",
- "tall2engineer" = "#00C8E2",
- "tall2service" = "#00FFF6",
- "tall2janitor" = "#C7F6F3",
- "tall2security" = "#FFFF00",
- "tall2medical" = "#33CCFF",
- "tall2standard" = "#33CCFF",
- "tall2peace" = "#66FFFF",
- "tall2clown" = "#0000FF",
- "tolietbot" = "#027EBE",
- "tolietbotminer" = "#027EBE",
- "tolietbotengineering" = "#027EBE",
- "tolietbotmedical" = "#CED2D9",
- "tolietbotsecurity" = "#027EBE",
- "tolietbotjanitor" = "#027EBE",
- "tolietbotsurgeon" = "#CED2D9",
- "tolietbotantag" = "#FF0000",
- "syndie" = "#F03333",
- "bloodhound" = "#006FBF",
- "treadhound" = "#006FBF",
- "squats" = "#33CC00",
- "treadwell" = "#00FF33",
- "whitespider" = "#00CEF6",
- "crawler" = "#A0E7F5",
- "maidbot" = "#5BEAFF",
- "handy-engineer" = "#FFCC00",
- "handy-med" = "#00CCFF",
- "drone-gravekeeper" = "#00A7B9",
- "sleek-gravekeeper" = "#00FFFF",
- "insekt-combat" = "#CC9900",
- "insekt-sec" = "#CC9900",
- "insekt-default" = "#00CC00",
- "insekt-sci" = "#00CC00",
- "insekt-med" = "#0099FF",
- "Glitterfly-Standard" = "#F953CD",
- "Glitterfly-Service" = "#FDE96B",
- "Glitterfly-Surgeon" = "#8ACCFF",
- "Glitterfly-Crisis" = "#FDE96B",
- "Glitterfly-Engineering" = "#DADF84",
- "Glitterfly-Janitor" = "#AD7EFF",
- "Glitterfly-Mining" = "#7EBC68",
- "Glitterfly-Research" = "#E7F8FF",
- "Glitterfly-Security" = "#33223B",
- "Glitterfly-Clerical" = "#87C877",
- "decapod-Standard" = "#33CCFF",
- "decapod-Research" = "#CC00FF",
- "decapod-Surgeon" = "#6AFF6A",
- "decapod-Crisis" = "#FF3232",
- "decapod-Engineering" = "#FF9900",
- "decapod-Janitor" = "#FF0099",
- "decapod-Miner" = "#00FF00",
- "decapod-Security" = "#C60200",
- "decapod-Service" = "#FFFF00",
- "decapod-Clerical" = "#CC00FF",
- "decapod-Combat" = "#FF0000",
- "eyebot-combat" = "#FF0000",
- "miss-standard" = "#B0CCDF",
- "miss-engineer" = "#FFCCB9",
- "miss-medical" = "#8EEE60",
- "miss-miner" = "#FF8858",
- "miss-service" = "#B0CCDF",
- "miss-janitor" = "#8EEE60",
- "miss-security" = "#B472C6",
- "xeightyeight-standard" = "#FF9900",
- "xeightyeight-engineering" = "#FF9900",
- "xeightyeight-science" = "#F2D590",
- "xeightyeight-medical" = "#F2D590",
- "handy-hydro" = "#FFFF55",
- "handy-clerk" = "#82FFF9",
- "handy-janitor" = "#FF55D9",
- "handy-service" = "#F4F4F4",
- "handy-miner" = "#82FF88",
- "handy-sec" = "#FF373D",
- "handy-standard" = "#E9E9E9",
- "mechoid-Standard" = "#33FFFF",
- "mechoid-Service" = "#FFFF00",
- "mechoid-Miner" = "#00FF00",
- "mechoid-Medical" = "#33FFFF",
- "mechoid-Engineering" = "#FF6600",
- "mechoid-Security" = "#C60200",
- "mechoid-Janitor" = "#33FFFF",
- "mechoid-Combat" = "#C60200",
- "mechoid-Science" = "#CC00FF",
- "Noble-STD" = "#4CFF00",
- "Noble-CLN" = "#4CFF00",
- "Noble-SRV" = "#4CFF00",
- "Noble-DIG" = "#4CFF00",
- "Noble-MED" = "#4CFF00",
- "Noble-SEC" = "#4CFF00",
- "Noble-ENG" = "#4CFF00",
- "corrupt-repairbot" = "#FF0033",
- "zoomba-standard" = "#FFFFFF",
- "zoomba-clerical" = "#FFFF2E",
- "zoomba-engineering" = "#D6A52E",
- "zoomba-janitor" = "#FF3F3A",
- "zoomba-medical" = "#68F1FF",
- "zoomba-crisis" = "#EC2E31",
- "zoomba-miner" = "#7D568F",
- "zoomba-research" = "#822EFA",
- "zoomba-security" = "#E52E2E",
- "zoomba-service" = "#2EFFFF",
- "zoomba-combat" = "#E52E2E",
- "worm-standard" = "#0094A4",
- "worm-engineering" = "#0094A4",
- "worm-janitor" = "#0094A4",
- "worm-crisis" = "#0094A4",
- "worm-miner" = "#0094A4",
- "worm-security" = "#0094A4",
- "worm-combat" = "#0094A4",
- "worm-surgeon" = "#0094A4",
- "worm-service" = "#0094A4"
- ),
- 'icons/mob/robots_wide.dmi' = list(
- "blade" = "#FF6AA0",
- "stray" = "#990033",
- "borgi" = "#FFFDD6",
- "k9" = "#FFF2BC",
- "medihound" = "#C3D9E6",
- "k69" = "#FFF2BC",
- "k92" = "#FF6633",
- "medihounddark" = "#C3D9E6",
- "engihound" = "#A6F0A9",
- "engihounddark" = "#61DD35",
- "scrubpup" = "#43D715",
- "vale" = "#7BEBC9",
- "science" = "#9754B8",
- "scihound" = "#9B3FE9",
- "scihounddark" = "#D864DA",
- "secborg" = "#E85656",
- "thottbot" = "#64FE3E",
- "k50" = "#C3D9E6",
- "kmine" = "#AA62BE",
- "cargohounddark" = "#BFB275",
- "cargohound" = "#D6A75C",
- "serve2" = "#43D715",
- "servedark" = "#43D715",
- "alina-eng" = "#64FE3E",
- "alina-sec" = "#E85656",
- "alina-med" = "#88C1D1",
- "borgi-eng" = "#FFFDD6",
- "borgi-jani" = "#FFFDD6",
- "borgi-medi" = "#FFFDD6",
- "borgi-sci" = "#FFFDD6",
- "borgi-sec" = "#FFFDD6",
- "otiej" = "#FEFFC3",
- "oties" = "#FBC6CA",
- "J9" = "#FFFF33",
- "FELI-Standard" = "#FFFDD6",
- "FELI-Engineer" = "#FFFDD6",
- "FELI-Medical" = "#FFFDD6",
- "FELI-Research" = "#FFFDD6",
- "FELI-Mining" = "#FFFDD6",
- "FELI-Service" = "#FFFDD6",
- "FELI-Janitor" = "#FFFDD6",
- "FELI-Security" = "#FFFDD6",
- "FELI-Combat" = "#FFFDD6",
- ),
- 'icons/mob/62x62robot_vr.dmi' = list(
- "ert" = "#EA00CB"
- )
-))
diff --git a/code/modules/mob/living/silicon/robot/robot_frame.dm b/code/modules/mob/living/silicon/robot/robot_frame.dm
new file mode 100644
index 00000000000..99afab966b4
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot_frame.dm
@@ -0,0 +1,30 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * A frame is a chassis-iconset tuple.
+ *
+ * * This is the thing you can actually choose as a player.
+ * * This is a thing because icon and chassis are generally disjoint; you don't
+ * want to constrain sprite design to a single functionality, necessarily.
+ * Thus, another layer of abstraction was added.
+ */
+/datum/robot_frame
+ abstract_type = /datum/robot_frame
+
+ /// our name
+ var/name
+ /// our iconset
+ /// * set to typepath; resolved on init.
+ var/datum/prototype/robot_iconset/robot_iconset
+ /// our chassis
+ /// * set to typepath; resolved on init.
+ var/datum/prototype/robot_chassis/robot_chassis
+
+ /// Lock to ckeys, if set
+ var/list/ckey_lock
+
+/datum/robot_frame/New()
+ // todo: DB pull if needed
+ robot_iconset = RSrobot_iconsets.fetch_local_or_throw(robot_iconset)
+ robot_chassis = RSrobot_chassis.fetch_local_or_throw(robot_chassis)
diff --git a/code/modules/mob/living/silicon/robot/robot_iconset.dm b/code/modules/mob/living/silicon/robot/robot_iconset.dm
new file mode 100644
index 00000000000..35f1f27513d
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot_iconset.dm
@@ -0,0 +1,84 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * Base abstraction for silicon iconsets
+ *
+ * * Iconsets have no behavior.
+ * * This is not considered intrinsic for a robot's behavior, and therefore can be changed without a full state rebuild.
+ *
+ * todo: please give ids to all of this..
+ */
+/datum/prototype/robot_iconset
+ abstract_type = /datum/prototype/robot_iconset
+ anonymous = TRUE
+ anonymous_namespace = "robot_iconset"
+
+ /// the name of this iconset
+ var/name
+ /// the display name of this iconset
+ /// * this is player facing!
+ /// * defaults to [name]
+ var/display_name
+
+ /// the path, id, or instance of robot chassis we are supposed to be
+ /// * this is not enforced, this is used for auto-gen
+ var/auto_chassis
+
+ /// icon
+ var/icon
+
+ /// base icon state
+ var/icon_state
+ /// primary indicator lighting overlay state
+ /// * if null, we do not have primary lighting indicators.
+ var/icon_state_indicator
+ /// cover open overlay base state
+ ///
+ /// * if null, we do not have a cover open state.
+ /// * ffs this should never be null!
+ ///
+ /// appends:
+ /// * "-cell" if cell is in
+ /// * "-empty" if cell is out
+ /// * "-wires" if wires are exposed
+ var/icon_state_cover
+
+ /// Icon width
+ var/icon_dimension_x = 32
+ /// Icon height
+ var/icon_dimension_y = 32
+
+ /// indicator lighting colorable?
+ /// * for now, only supports NONE and MULTIPLY
+ var/indicator_lighting_coloration_mode = COLORATION_MODE_NONE
+ /// indicator lighting packed coloration
+ /// * for now, only supports a single RRGGBB(AA) string.
+ var/indicator_lighting_coloration_packed = "#ffffff"
+
+ /// variations
+ /// * this is a list of typepaths / anonymous types at compile time, and
+ /// turned into an id-instance lookup map at init.
+ ///
+ /// special ones:
+ /// * /datum/robot_iconset_variation/dead
+ /// * /datum/robot_iconset_variation/resting
+ /// * /datum/robot_iconset_variation/sitting
+ /// * /datum/robot_iconset_variation/bellyup
+ var/list/variations
+
+/datum/prototype/robot_iconset/New()
+ ..()
+ for(var/i in 1 to length(variations))
+ var/datum/robot_iconset_variation/maybe_variation = variations[i]
+ if(ispath(maybe_variation))
+ maybe_variation = new maybe_variation
+ else if(IS_ANONYMOUS_TYPEPATH(maybe_variation))
+ maybe_variation = new maybe_variation
+ else if(istype(maybe_variation))
+ else
+ stack_trace("invalid variation in index [i] on [type] ([id])")
+ variations[maybe_variation.id] = maybe_variation
+
+/datum/prototype/robot_iconset/proc/get_display_name()
+ return display_name || name
diff --git a/code/modules/mob/living/silicon/robot/robot_iconset_variation.dm b/code/modules/mob/living/silicon/robot/robot_iconset_variation.dm
new file mode 100644
index 00000000000..0119f189a81
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot_iconset_variation.dm
@@ -0,0 +1,54 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * Things to note:
+ * * indicator state must be set for us to have primary lighting indicator + emissives while using this variation
+ * * cover state must be set for ... (you get the deal)
+ */
+/datum/robot_iconset_variation
+ /// id; must be unique
+ var/id
+
+ /// icon override
+ /// * applies to **all** overlays
+ var/icon_override
+
+ /// state override
+ /// * overrides [icon_state_append]
+ var/icon_state
+ /// state append to override our state with
+ var/icon_state_append
+ /// primary indicator lighting overlay override state
+ /// * overrides [icon_state_indicator_append]
+ var/icon_state_indicator
+ /// state append to override our indicator state with
+ /// * this is applied to our resultant icon state
+ var/icon_state_indicator_append
+ /// cover overlay override state
+ /// * overrides [icon_state_cover_append]
+ var/icon_state_cover
+ /// state append to override our cover state with
+ var/icon_state_cover_append
+
+ /// is the player allowed to choose it while resting?
+ var/alt_resting_state = FALSE
+
+/datum/robot_iconset_variation/dead
+ id = "dead"
+ icon_state_append = "-dead"
+
+/datum/robot_iconset_variation/resting
+ id = "resting"
+ icon_state_append = "-rest"
+ alt_resting_state = TRUE
+
+/datum/robot_iconset_variation/sitting
+ id = "sitting"
+ icon_state_append = "-sit"
+ alt_resting_state = TRUE
+
+/datum/robot_iconset_variation/bellyup
+ id = "bellyup"
+ icon_state_append = "-bellyup"
+ alt_resting_state = TRUE
diff --git a/code/modules/mob/living/silicon/robot/robot_inventory.dm b/code/modules/mob/living/silicon/robot/robot_inventory.dm
new file mode 100644
index 00000000000..0e41115f4f2
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot_inventory.dm
@@ -0,0 +1,164 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/**
+ * Robot inventories; separate from /datum/inventory, which is a slot-and-hands system.
+ * * Thus, robot modules don't count as actually in inventory unless they're equipped.
+ * This is kinda bad, but, the other option is worse (bolting this onto `/datum/inventory`)
+ *
+ * Implementation guidelines:
+ *
+ * * Modules can never be dropped or taken out of the mob.
+ * * Modules automatically return to the mob when being unequipped.
+ * * Modules must always be in the mob, location-wise. If it's ever removed, it will be
+ * **dropped** by the robot inventory datum.
+ */
+/datum/robot_inventory
+ /// Owner
+ /// * This is generic, and can technically work for non-silicon mobs
+ var/mob/living/silicon/robot/owner
+ /// registered items
+ /// * Things in-hand are still registered.
+ var/list/obj/item/provided_items
+ /// actor HUDs using us
+ /// * Lazy list
+ var/list/datum/actor_hud/robot_inventory/huds_using
+ /// our item mount
+ var/datum/item_mount/item_mount
+
+/datum/robot_inventory/New(mob/living/silicon/robot/owner)
+ src.owner = owner
+ item_mount = new
+ item_mount.relay_to(owner.resources.item_mount)
+
+/datum/robot_inventory/Destroy()
+ if(owner)
+ if(owner.robot_inventory == src)
+ owner.robot_inventory = null
+ owner = null
+ for(var/obj/item/item in provided_items)
+ inv_unregister(item)
+ provided_items = null
+ for(var/datum/actor_hud/robot_inventory/hud in huds_using)
+ hud.unbind_from_inventory(src)
+ huds_using = null
+ QDEL_NULL(item_mount)
+ return ..()
+
+//* Get *//
+
+/**
+ * Gets all robot modules registered.
+ */
+/datum/robot_inventory/proc/inv_get_all() as /list
+ SHOULD_NOT_OVERRIDE(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+ return provided_items ? provided_items.Copy() : list()
+
+/**
+ * Gets all robot modules active.
+ */
+/datum/robot_inventory/proc/inv_get_active() as /list
+ SHOULD_NOT_OVERRIDE(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+ . = list()
+ for(var/obj/item/item as anything in provided_items)
+ if(isnum(item.inv_slot_or_index))
+ . += item
+
+//* Check *//
+
+/**
+ * Checks if an item is registered to us as a robot module.
+ *
+ * @return TRUE if module, FALSE otherwise
+ */
+/datum/robot_inventory/proc/inv_is_registered(obj/item/item)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+ return item in provided_items
+
+//* Registration *//
+
+/**
+ * Registers an item as a robot module.
+ *
+ * * Item must not be in an inventory, even ourselves.
+ * * The item will be moved into our owner.
+ *
+ * @return TRUE success, FALSE fail
+ */
+/datum/robot_inventory/proc/inv_register(obj/item/item)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+
+ if(item in provided_items)
+ return TRUE
+ if(item.inv_inside)
+ // TODO: this is shitcode
+ item.forceMove(owner)
+ if(item.inv_inside)
+ stack_trace("failed to kick item out of existing inv in robot_module_register")
+ return FALSE
+
+ item.forceMove(owner)
+ LAZYADD(provided_items, item)
+ if(!item.item_mount)
+ item.mount(src.item_mount)
+ on_inv_register(item)
+ return TRUE
+
+/**
+ * Unregisters an item from being a robot module.
+ * * You have to move it out of the inventory / mob after!
+ */
+/datum/robot_inventory/proc/inv_unregister(obj/item/item)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+
+ if(!(item in provided_items))
+ return
+ LAZYREMOVE(provided_items, item)
+ if(item.item_mount == src.item_mount)
+ item.unmount()
+ on_inv_unregister(item)
+
+/datum/robot_inventory/proc/on_inv_register(obj/item/item)
+ PROTECTED_PROC(TRUE)
+ SHOULD_CALL_PARENT(TRUE)
+
+ RegisterSignal(item, COMSIG_ITEM_PICKUP, PROC_REF(handle_item_pickup))
+ RegisterSignal(item, COMSIG_ITEM_DROPPED, PROC_REF(handle_item_drop))
+ RegisterSignal(item, COMSIG_PARENT_QDELETING, PROC_REF(handle_item_del))
+
+ for(var/datum/actor_hud/robot_inventory/hud as anything in huds_using)
+ hud.drawer_backplate?.redraw()
+ item.vis_flags |= VIS_INHERIT_LAYER | VIS_INHERIT_PLANE
+ item.auto_pixel_offset_to_center()
+
+/datum/robot_inventory/proc/on_inv_unregister(obj/item/item)
+ PROTECTED_PROC(TRUE)
+ SHOULD_CALL_PARENT(TRUE)
+
+ for(var/datum/actor_hud/robot_inventory/hud as anything in huds_using)
+ hud.drawer_backplate?.redraw()
+ item.vis_flags &= ~(VIS_INHERIT_LAYER | VIS_INHERIT_PLANE)
+
+/datum/robot_inventory/proc/handle_item_pickup(obj/item/source, ...)
+ // TODO: cleaner way to do this?
+ spawn(0)
+ for(var/datum/actor_hud/robot_inventory/inventory_hud as anything in huds_using)
+ inventory_hud?.drawer_backplate.redraw()
+
+/datum/robot_inventory/proc/handle_item_drop(obj/item/source, ...)
+ source.forceMove(owner)
+ source.vis_flags |= VIS_INHERIT_LAYER | VIS_INHERIT_PLANE
+ source.auto_pixel_offset_to_center()
+ // TODO: cleaner way to do this?
+ spawn(0)
+ for(var/datum/actor_hud/robot_inventory/inventory_hud as anything in huds_using)
+ inventory_hud?.drawer_backplate.redraw()
+ return COMPONENT_ITEM_DROPPED_RELOCATE | COMPONENT_ITEM_DROPPED_SUPPRESS_SOUND
+
+/datum/robot_inventory/proc/handle_item_del(obj/item/source, ...)
+ inv_unregister(source)
diff --git a/code/modules/mob/living/silicon/robot/robot_item_mount.dm b/code/modules/mob/living/silicon/robot/robot_item_mount.dm
new file mode 100644
index 00000000000..f7d9fe7bee0
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot_item_mount.dm
@@ -0,0 +1,107 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * Something for all owned items to be mounted on as a resource bus.
+ */
+/datum/item_mount/robot_item_mount
+ var/datum/robot_resource_store/store
+
+/datum/item_mount/robot_item_mount/New(datum/robot_resource_store/store)
+ ..()
+ src.store = store
+
+/datum/item_mount/robot_item_mount/Destroy()
+ store = null
+ return ..()
+
+/datum/item_mount/robot_item_mount/material_get_amount(obj/item/item, key, material_id)
+ var/datum/robot_resource/resource = store.provisioned_material_store[material_id]
+ . = resource?.amount
+
+/datum/item_mount/robot_item_mount/material_get_capacity(obj/item/item, key, material_id)
+ var/datum/robot_resource/resource = store.provisioned_material_store[material_id]
+ . = resource?.amount_max
+
+/datum/item_mount/robot_item_mount/material_get_provider_name(obj/item/item, key, material_id)
+ . = "material store"
+
+/datum/item_mount/robot_item_mount/material_give_amount(obj/item/item, key, material_id, amount, force)
+ var/datum/robot_resource/resource = store.provisioned_material_store[material_id]
+ if(resource)
+ if(force)
+ . = amount
+ resource.amount += amount
+ else
+ . = clamp(resource.amount_max - resource.amount, 0, amount)
+ resource.amount += .
+
+/datum/item_mount/robot_item_mount/material_use_amount(obj/item/item, key, material_id, amount)
+ var/datum/robot_resource/resource = store.provisioned_material_store[material_id]
+ if(resource)
+ . = min(resource.amount, amount)
+ resource.amount -= .
+
+/datum/item_mount/robot_item_mount/stack_get_amount(obj/item/item, key, path)
+ var/datum/robot_resource/resource = store.provisioned_stack_store[path]
+ . = resource?.amount
+
+/datum/item_mount/robot_item_mount/stack_get_capacity(obj/item/item, key, path)
+ var/datum/robot_resource/resource = store.provisioned_stack_store[path]
+ . = resource?.amount_max
+
+/datum/item_mount/robot_item_mount/stack_get_provider_name(obj/item/item, key, path)
+ . = "resource store"
+
+/datum/item_mount/robot_item_mount/stack_give_amount(obj/item/item, key, path, amount, force)
+ var/datum/robot_resource/resource = store.provisioned_stack_store[path]
+ if(resource)
+ if(force)
+ . = amount
+ resource.amount += amount
+ else
+ . = clamp(resource.amount_max - resource.amount, 0, amount)
+ resource.amount += .
+
+/datum/item_mount/robot_item_mount/stack_use_amount(obj/item/item, key, path, amount)
+ var/datum/robot_resource/resource = store.provisioned_stack_store[path]
+ if(resource)
+ . = min(resource.amount, amount)
+ resource.amount -= .
+
+/datum/item_mount/robot_item_mount/lazy_energy_check(obj/item/item, key, joules)
+ . = DYNAMIC_CELL_UNITS_TO_J(store.owner?.cell?.charge) >= joules
+
+/datum/item_mount/robot_item_mount/lazy_energy_use(obj/item/item, key, joules, minimum_reserve)
+ . = store.owner?.cell?.use(DYNAMIC_J_TO_CELL_UNITS(joules))
+
+/datum/item_mount/robot_item_mount/reagent_get_amount(obj/item/item, key, datum/reagent/reagent)
+ var/datum/robot_resource/resource = store.provisioned_reagent_store[reagent.id]
+ . = resource?.amount
+
+/datum/item_mount/robot_item_mount/reagent_erase_amount(obj/item/item, key, datum/reagent/reagent, amount)
+ var/datum/robot_resource/resource = store.provisioned_reagent_store[reagent.id]
+ if(resource)
+ . = max(amount, resource.amount)
+ resource.amount -= .
+
+/datum/item_mount/robot_item_mount/reagent_spawn_amount(obj/item/item, key, datum/reagent/reagent, amount, force)
+ var/datum/robot_resource/resource = store.provisioned_reagent_store[reagent.id]
+ if(resource)
+ if(force)
+ . = amount
+ resource.amount += amount
+ else
+ . = clamp(resource.amount_max - resource.amount, 0, amount)
+ resource.amount += .
+
+/datum/item_mount/robot_item_mount/extinguisher_get_volume(obj/item/extinguisher/extinguisher, key)
+ var/datum/robot_resource/resource = store.provisioned_reagent_store[/datum/reagent/water::id]
+ . = resource?.amount
+
+/datum/item_mount/robot_item_mount/extinguisher_transfer_volume(obj/item/extinguisher/extinguisher, key, requested, datum/reagent_holder/into_holder)
+ var/datum/robot_resource/resource = store.provisioned_reagent_store[/datum/reagent/water::id]
+ if(resource)
+ . = min(requested, resource.amount)
+ into_holder.add_reagent(/datum/reagent/water::id, .)
+ resource.amount -= .
diff --git a/code/modules/mob/living/silicon/robot/robot_module.dm b/code/modules/mob/living/silicon/robot/robot_module.dm
new file mode 100644
index 00000000000..e51738d30a0
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot_module.dm
@@ -0,0 +1,202 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * The baseline configuration for a silicon.
+ *
+ * * This is considered intrinsic, and cannot be changed on a robot without fully rebuilding its state.
+ *
+ * Contains:
+ *
+ * * Inbuilt items
+ * * WIP
+ */
+/datum/prototype/robot_module
+ abstract_type = /datum/prototype/robot_module
+
+ /// Allow selection
+ // todo: replace with selection class / groups / something to that effect
+ var/selectable = FALSE
+
+ /// /obj/item/robot_module_legacy typepath
+ var/use_robot_module_path = /obj/item/robot_module_legacy/robot
+
+ /// This corrosponds to the icon_state in `icons/screen/hud/robot/modules.dmi'
+ var/module_hud_state = "standard"
+
+ /// Allowed robot frames.
+ /// * set to list of typepaths/anonymous types to init
+ var/list/datum/robot_frame/frames
+
+ /// Generate robot frames from iconsets.
+ /// * set to list of `/datum/prototype/robot_iconset` ids or paths
+ /// * the iconset must have `chassis` set, or this will runtime.
+ /// * will append to [frames].
+ var/list/auto_iconsets
+
+ /// Required selection groups
+ /// * If both this and `selection_groups_any` are null, this can't be picked.
+ var/list/selection_groups_all
+ /// Required selection groups
+ /// * If both this and `selection_groups_all` are null, this can't be picked.
+ var/list/selection_groups_any
+
+ /// show on manifest?
+ var/legacy_show_on_manifest = FALSE
+
+ //* Lighting *//
+ /// Default lighting color
+ var/light_color = "#ffffff"
+
+ //* Identity *//
+ /// Our display name
+ var/display_name = "???"
+ /// Our examine name, defaulting to display name
+ var/visible_name
+ /// Our category; can be null if default
+ var/category
+
+ //* Mounted Item Descriptor - Injectors *//
+ /// items to inject into normal out
+ /// * paths are allowed
+ /// * anonymous types are allowed
+ /// * item instances are **not allowed** and will result in shit exploding!
+ var/list/mounted_item_descriptor_inject_normal
+ /// items to inject into emag out
+ /// * paths are allowed
+ /// * anonymous types are allowed
+ /// * item instances are **not allowed** and will result in shit exploding!
+ var/list/mounted_item_descriptor_inject_emag
+
+/datum/prototype/robot_module/New()
+ LAZYINITLIST(frames)
+ for(var/i in 1 to length(auto_iconsets))
+ var/entry = auto_iconsets[i]
+ if(ispath(entry))
+ auto_iconsets[i] = new entry
+ else if(IS_ANONYMOUS_TYPEPATH(entry))
+ auto_iconsets[i] = new entry
+ else if(istype(entry, /datum/robot_frame))
+ else
+ stack_trace("invalid entry [entry] in index [i] on hardcoded frames on [id] ([type])")
+ continue
+ for(var/resolve_iconset in auto_iconsets)
+ var/datum/prototype/robot_iconset/resolved_iconset = RSrobot_iconsets.fetch_local_or_throw(resolve_iconset)
+ if(!resolved_iconset)
+ stack_trace("unknown entry [resolve_iconset] in auto_iconsets on [id] ([type])")
+ continue
+ if(!resolved_iconset.auto_chassis)
+ stack_trace("entry [resolve_iconset] in auto_iconsets on [id] ([type]) has no automatic chassis")
+ continue
+ var/datum/prototype/robot_chassis/resolved_chassis = RSrobot_chassis.fetch_local_or_throw(resolved_iconset.auto_chassis)
+ if(!resolved_chassis)
+ stack_trace("entry [resolve_iconset] in auto_iconsets on [id] ([type]) had an invalid chassis ([resolved_iconset.auto_chassis])")
+ continue
+ var/datum/robot_frame/generated_frame = new
+ generated_frame.robot_iconset = resolved_iconset
+ generated_frame.robot_chassis = resolved_chassis
+ generated_frame.name = "[resolved_iconset.get_display_name()] ([resolved_chassis.name])"
+ frames += generated_frame
+
+/datum/prototype/robot_module/proc/create_provisioning() as /datum/robot_provisioning
+ var/datum/robot_provisioning/creating = new
+ . = creating
+ var/list/i_normal = list()
+ var/list/i_emag = list()
+ create_mounted_item_descriptors(i_normal, i_emag)
+ for(var/obj/item/descriptor as anything in i_normal)
+ var/obj/item/resolved
+ if(istype(descriptor))
+ resolved = descriptor
+ else if(ispath(descriptor))
+ resolved = new descriptor
+ else if(IS_ANONYMOUS_TYPEPATH(descriptor))
+ resolved = new descriptor
+ else
+ stack_trace("invalid descriptor [descriptor] on [id] ([type])")
+ continue
+ creating.add_item(resolved)
+ for(var/obj/item/descriptor as anything in i_emag)
+ var/obj/item/resolved
+ if(istype(descriptor))
+ resolved = descriptor
+ else if(ispath(descriptor))
+ resolved = new descriptor
+ else if(IS_ANONYMOUS_TYPEPATH(descriptor))
+ resolved = new descriptor
+ else
+ stack_trace("invalid descriptor [descriptor] on [id] ([type])")
+ continue
+ creating.add_emag_item(resolved)
+
+/**
+ * Returns a list of descriptors for mounted items
+ *
+ * A descriptor can be:
+ * * An item instance
+ * * An item path
+ * * An anonymous type of an item
+ *
+ * It's recommended to use item path, then anonymous types, and lastly, item instances.
+ * Item instances should be created in nullspace.
+ *
+ * The caller must handle all of these.
+ * Unused item instances must be qdel'd.
+ */
+/datum/prototype/robot_module/proc/create_mounted_item_descriptors(list/normal_out, list/emag_out)
+ if(normal_out)
+ if(mounted_item_descriptor_inject_normal)
+ normal_out |= mounted_item_descriptor_inject_normal
+ normal_out |= list(
+ /obj/item/flash,
+ /obj/item/multitool, // todo: special robot multitool that can interface with machines?
+ /obj/item/gps/robot,
+ /obj/item/tool/crowbar/cyborg,
+ /obj/item/extinguisher/mini,
+ )
+ if(emag_out)
+ if(mounted_item_descriptor_inject_emag)
+ emag_out |= mounted_item_descriptor_inject_emag
+
+/**
+ * Provision a robot's resource store.
+ *
+ * * All of the store's lists should be initalized, even if they're generally lazy.
+ * You can lazyclearlist it later.
+ */
+/datum/prototype/robot_module/proc/provision_resource_store(datum/robot_resource_store/store)
+ store.provisioned_reagent_store[/datum/reagent/water::id] = new /datum/robot_resource/provisioned/preset/water
+
+/datum/prototype/robot_module/proc/legacy_custom_regenerate_resources(mob/living/silicon/robot/robot, dt, multiplier)
+ return
+
+//* Name / Description / Identity *//
+
+/**
+ * Get name as seen by things like robotics consoles that don't use another way
+ * to resolve who / what we are.
+ */
+/datum/prototype/robot_module/proc/get_display_name()
+ return display_name
+
+/**
+ * Get visible name on examine
+ */
+/datum/prototype/robot_module/proc/get_visible_name()
+ return visible_name || display_name
+
+//* Application *//
+
+/**
+ * Called when we're being set onto a robot.
+ */
+/datum/prototype/robot_module/proc/on_install(mob/living/silicon/robot/robot)
+ SHOULD_CALL_PARENT(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+
+/**
+ * Called when we're being unset from a robot.
+ */
+/datum/prototype/robot_module/proc/on_uninstall(mob/living/silicon/robot/robot)
+ SHOULD_CALL_PARENT(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/_robot_modules_defs.dm b/code/modules/mob/living/silicon/robot/robot_modules/_robot_modules_defs.dm
deleted file mode 100644
index 5ba30fc0635..00000000000
--- a/code/modules/mob/living/silicon/robot/robot_modules/_robot_modules_defs.dm
+++ /dev/null
@@ -1,20 +0,0 @@
-#define MATSYN_WATER "water"
-#define MATSYN_NANITES "nanites"
-#define MATSYN_WIRE "wire"
-#define MATSYN_METAL "metal"
-#define MATSYN_GLASS "glass"
-#define MATSYN_DRUGS "medicine"
-#define MATSYN_WOOD "wood"
-#define MATSYN_PLASTIC "plastic"
-#define MATSYN_PLASTEEL "plasteel"
-
-#define MATTER_SYNTH_WITH_NAME(K,T,N,V...) .[K] = new /datum/matter_synth/##T (V, name = N)
-#define MATTER_SYNTH(K,T,V...) .[K] = new /datum/matter_synth/##T (V)
-#define CYBORG_STACK(T,K) do { var/obj/item/stack/S = new /obj/item/stack/##T(src); S.synths = __cyborg_stack_map(K); . += S } while (FALSE)
-
-/obj/item/robot_module/proc/__cyborg_stack_map(list/targets)
- if (!islist(targets))
- targets = list(targets)
- . = list()
- for (var/item in targets)
- . += synths_by_kind[item]
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/event.dm b/code/modules/mob/living/silicon/robot/robot_modules/event.dm
deleted file mode 100644
index dc4a03034c8..00000000000
--- a/code/modules/mob/living/silicon/robot/robot_modules/event.dm
+++ /dev/null
@@ -1,90 +0,0 @@
-/* Other, unaffiliated modules */
-
-// 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"
- hide_on_manifest = 1
- sprites = list(
- "Drone" = "drone-lost"
- )
-
-/obj/item/robot_module/robot/lost/get_modules()
- . = ..()
- . |= list(
- // Sec
- /obj/item/melee/baton/shocker/robot,
- /obj/item/handcuffs/cyborg,
- /obj/item/borg/combat/shield,
-
- // Med
- /obj/item/healthanalyzer,
- /obj/item/reagent_containers/borghypo/lost,
-
- // Engi
- /obj/item/weldingtool/electric/mounted,
- /obj/item/tool/screwdriver/cyborg,
- /obj/item/tool/wrench/cyborg,
- /obj/item/tool/wirecutters/cyborg,
- /obj/item/multitool,
-
- // Sci
- /obj/item/robotanalyzer
- )
-
-/obj/item/robot_module/robot/lost/get_synths(mob/living/silicon/robot/R)
- . = ..()
- MATTER_SYNTH(MATSYN_WIRE, wire)
-
-/obj/item/robot_module/robot/lost/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
- // Potato
- emag = new /obj/item/gun/projectile/energy/retro/mounted(src)
-
- var/obj/item/stack/cable_coil/cyborg/C = new /obj/item/stack/cable_coil/cyborg(src)
- C.synths = list(synths_by_kind[MATSYN_WIRE])
- . += C
-
-/obj/item/robot_module/robot/gravekeeper
- name = "gravekeeper robot module"
- hide_on_manifest = 1
- sprites = list(
- "Drone" = "drone-gravekeeper",
- "Sleek" = "sleek-gravekeeper"
- )
-
-/obj/item/robot_module/robot/gravekeeper/get_modules()
- . = ..()
- . |= list(
- // For fending off animals and looters
- /obj/item/melee/baton/shocker/robot,
- /obj/item/borg/combat/shield,
-
- // For repairing gravemarkers
- /obj/item/weldingtool/electric/mounted,
- /obj/item/tool/screwdriver/cyborg,
- /obj/item/tool/wrench/cyborg,
-
- // For growing flowers
- /obj/item/material/minihoe,
- /obj/item/material/knife/machete/hatchet,
- /obj/item/plant_analyzer,
- /obj/item/storage/bag/plants,
- /obj/item/robot_harvester,
-
- // For digging and beautifying graves
- /obj/item/shovel,
- /obj/item/gripper/gravekeeper
- )
-
-/obj/item/robot_module/robot/gravekeeper/get_synths(mob/living/silicon/robot/R)
- . = ..()
- MATTER_SYNTH(MATSYN_WOOD, wood, 25000)
-
-/obj/item/robot_module/robot/gravekeeper/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
- // For really persistent looters
- emag = new /obj/item/gun/projectile/energy/retro/mounted(src)
-
- var/obj/item/stack/material/cyborg/wood/W = new (src)
- W.synths = list(synths_by_kind[MATSYN_WOOD])
- . += W
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/event_vr.dm b/code/modules/mob/living/silicon/robot/robot_modules/event_vr.dm
deleted file mode 100644
index 2db222178ef..00000000000
--- a/code/modules/mob/living/silicon/robot/robot_modules/event_vr.dm
+++ /dev/null
@@ -1,42 +0,0 @@
-/obj/item/robot_module/robot/quad/stray
- name = "stray robot module"
- hide_on_manifest = 1
- sprites = list(
- "Stray" = "stray"
- )
- can_shred = TRUE
-
-/obj/item/robot_module/robot/quad/stray/get_modules()
- . = ..()
- . |= list(
- // Sec
- /obj/item/handcuffs/cyborg,
- /obj/item/dogborg/jaws/big,
- /obj/item/melee/baton/robot,
- /obj/item/dogborg/pounce,
-
- // Med
- /obj/item/healthanalyzer,
- /obj/item/shockpaddles/robot/hound,
- /obj/item/dogborg/mirrortool,
-
- // Engi
- /obj/item/weldingtool/electric/mounted,
- /obj/item/tool/screwdriver/cyborg,
- /obj/item/tool/wrench/cyborg,
- /obj/item/tool/wirecutters/cyborg,
- /obj/item/multitool
- )
-
-/obj/item/robot_module/robot/quad/stray/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
- // Boof
- src.emag = new /obj/item/gun/projectile/energy/retro/mounted(src)
-
- var/obj/item/reagent_containers/borghypo/hound/lost/H = new /obj/item/reagent_containers/borghypo/hound/lost(src)
- H.water = synths_by_kind[MATSYN_WATER]
- . += H
-
- var/obj/item/dogborg/sleeper/B = new /obj/item/dogborg/sleeper(src)
- B.water = synths_by_kind[MATSYN_WATER]
- . += B
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 043b661ed57..270e210da9e 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules/station.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules/station.dm
@@ -1,42 +1,6 @@
-GLOBAL_LIST_INIT(robot_modules, list(
- "Standard" = /obj/item/robot_module/robot/standard,
- "Service" = /obj/item/robot_module/robot/clerical/butler,
- "Clerical" = /obj/item/robot_module/robot/clerical/general,
- "Research" = /obj/item/robot_module/robot/research,
- "Miner" = /obj/item/robot_module/robot/miner,
- "Medical" = /obj/item/robot_module/robot/medical/surgeon,
- "Security" = /obj/item/robot_module/robot/security/general,
- "Combat" = /obj/item/robot_module/robot/security/combat,
- "Engineering" = /obj/item/robot_module/robot/engineering/general,
-// "Construction" = /obj/item/robot_module/robot/engineering/construction,
- "Janitor" = /obj/item/robot_module/robot/janitor,
- "Quadruped" = /obj/item/robot_module/robot/quad/basic,
- "MediQuad" = /obj/item/robot_module/robot/quad/medi,
- "SecuriQuad" = /obj/item/robot_module/robot/quad/sec,
- "JaniQuad" = /obj/item/robot_module/robot/quad/jani,
- "SciQuad" = /obj/item/robot_module/robot/quad/sci,
- "EngiQuad" = /obj/item/robot_module/robot/quad/engi,
- "Mining Quad" = /obj/item/robot_module/robot/quad/miner,
- "Service Quad" = /obj/item/robot_module/robot/quad/serv,
- "ERT" = /obj/item/robot_module/robot/quad/ert
- ))
-
-/obj/item/robot_module
- name = "robot module"
- icon = 'icons/obj/module.dmi'
- icon_state = "std_module"
- w_class = WEIGHT_CLASS_HUGE
- item_state = "std_mod"
- var/hide_on_manifest = 0
+/obj/item/robot_module_legacy
var/channels = list()
var/networks = list()
- var/sprites = list()
- var/can_be_pushed = 1
- var/no_slip = 0
- /// Affects emotes.
- var/is_the_law = FALSE
- /// Enables a verb.
- var/can_shred = FALSE
var/languages = list(
LANGUAGE_AKHANI = 0,
@@ -58,122 +22,35 @@ GLOBAL_LIST_INIT(robot_modules, list(
LANGUAGE_ZADDAT = 0
)
- var/list/modules = list()
- var/list/datum/matter_synth/synths = list()
- var/list/synths_by_kind = list()
- var/obj/item/emag = null
- var/obj/item/borg/upgrade/jetpack = null
- var/obj/item/borg/upgrade/advhealth = null
var/list/subsystems = list()
- var/list/obj/item/borg/upgrade/supported_upgrades = list()
// Bookkeeping
var/list/original_languages = list()
var/list/added_networks = list()
-/obj/item/robot_module/Initialize(mapload)
+/obj/item/robot_module_legacy/Initialize(mapload)
. = ..()
var/mob/living/silicon/robot/R = loc
- R.module = src
+ R.module_legacy = src
add_camera_networks(R)
add_languages(R)
add_subsystems(R)
- apply_status_flags(R)
if(R.radio)
if(R.shell)
channels = R.mainframe.aiRadio.channels
R.radio.recalculateChannels()
- handle_custom_item(R)
-
- R.set_module_sprites(sprites)
-
- // TODO: REFACTOR CYBORGS THEY ARE ALL SHITCODE
- INVOKE_ASYNC(R, TYPE_PROC_REF(/mob/living/silicon/robot, choose_icon), R.module_sprites.len + 1, R.module_sprites)
-
- // Setup synths, modules, and modules with custom init code.
- synths_by_kind = get_synths(R)
- for (var/key in synths_by_kind)
- synths += synths_by_kind[key]
-
- for (var/entry in get_modules())
- modules += new entry(src)
-
- for (var/thing in handle_special_module_init(R))
- modules += thing
-
- for(var/obj/item/I in modules)
- ADD_TRAIT(I, TRAIT_ITEM_NODROP, CYBORG_MODULE_TRAIT)
-
-/obj/item/robot_module/proc/Reset(var/mob/living/silicon/robot/R)
+/obj/item/robot_module_legacy/proc/Reset(var/mob/living/silicon/robot/R)
remove_camera_networks(R)
remove_languages(R)
remove_subsystems(R)
- remove_status_flags(R)
if(R.radio)
R.radio.recalculateChannels()
-/// Get a list of all matter synths available to this module. Executes before handle_special_module_init.
-/obj/item/robot_module/proc/get_synths(mob/living/silicon/robot/R)
- . = list()
-
-/// Get a list of typepaths that should be put into the modules list.
-/obj/item/robot_module/proc/get_modules()
- SHOULD_CALL_PARENT(TRUE)
- return list()
-
-// This is for modules that need special handling, not just being added to the modules list.
-/obj/item/robot_module/proc/handle_special_module_init(mob/living/silicon/robot/R)
- SHOULD_CALL_PARENT(TRUE)
- . = list()
-
-/obj/item/robot_module/proc/handle_custom_item(mob/living/silicon/robot/R)
- return
-
-/obj/item/robot_module/Destroy()
- for(var/module in modules)
- qdel(module)
- for(var/synth in synths)
- qdel(synth)
- modules.Cut()
- synths.Cut()
- synths_by_kind = null
- qdel(emag)
- qdel(jetpack)
- emag = null
- jetpack = null
- return ..()
-
-/obj/item/robot_module/emp_act(severity)
- if(modules)
- for(var/obj/O in modules)
- O.emp_act(severity)
- if(emag)
- emag.emp_act(severity)
- if(synths)
- for(var/datum/matter_synth/S in synths)
- S.emp_act(severity)
- ..()
- return
-
-/obj/item/robot_module/proc/respawn_consumable(var/mob/living/silicon/robot/R, var/rate)
- if(!synths || !synths.len)
- return
-
- for(var/datum/matter_synth/T in synths)
- T.add_charge(T.recharge_rate * rate)
-
-/obj/item/robot_module/proc/rebuild()//Rebuilds the list so it's possible to add/remove items from the module
- var/list/temp_list = modules
- modules = list()
- for(var/obj/O in temp_list)
- if(O)
- modules += O
-
-/obj/item/robot_module/proc/add_languages(var/mob/living/silicon/robot/R)
+/obj/item/robot_module_legacy/proc/add_languages(var/mob/living/silicon/robot/R)
// Stores the languages as they were before receiving the module, and whether they could be synthezized.
for(var/datum/prototype/language/language_datum in R.languages)
original_languages[language_datum] = (language_datum in R.speech_synthesizer_langs)
@@ -181,7 +58,7 @@ GLOBAL_LIST_INIT(robot_modules, list(
for(var/language in languages)
R.add_language(language, languages[language])
-/obj/item/robot_module/proc/remove_languages(var/mob/living/silicon/robot/R)
+/obj/item/robot_module_legacy/proc/remove_languages(var/mob/living/silicon/robot/R)
// Clear all added languages, whether or not we originally had them.
for(var/language in languages)
R.remove_language(language)
@@ -191,130 +68,20 @@ GLOBAL_LIST_INIT(robot_modules, list(
R.add_language(original_language, original_languages[original_language])
original_languages.Cut()
-/obj/item/robot_module/proc/add_camera_networks(var/mob/living/silicon/robot/R)
+/obj/item/robot_module_legacy/proc/add_camera_networks(var/mob/living/silicon/robot/R)
if(R.camera && (NETWORK_ROBOTS in R.camera.network))
for(var/network in networks)
if(!(network in R.camera.network))
R.camera.add_network(network)
added_networks |= network
-/obj/item/robot_module/proc/remove_camera_networks(var/mob/living/silicon/robot/R)
+/obj/item/robot_module_legacy/proc/remove_camera_networks(var/mob/living/silicon/robot/R)
if(R.camera)
R.camera.remove_networks(added_networks)
added_networks.Cut()
-/obj/item/robot_module/proc/add_subsystems(var/mob/living/silicon/robot/R)
+/obj/item/robot_module_legacy/proc/add_subsystems(var/mob/living/silicon/robot/R)
add_verb(R, subsystems)
-/obj/item/robot_module/proc/remove_subsystems(var/mob/living/silicon/robot/R)
+/obj/item/robot_module_legacy/proc/remove_subsystems(var/mob/living/silicon/robot/R)
remove_verb(R, subsystems)
-
-/obj/item/robot_module/proc/apply_status_flags(var/mob/living/silicon/robot/R)
- if(!can_be_pushed)
- R.status_flags &= ~STATUS_CAN_PUSH
-
-/obj/item/robot_module/proc/remove_status_flags(var/mob/living/silicon/robot/R)
- if(!can_be_pushed)
- R.status_flags |= STATUS_CAN_PUSH
-
-/// This is different from the dogborg or wideborg vars -- this is specifically if the module is a *dog* - if it should be able to do dog things like bark.
-/obj/item/robot_module/proc/is_dog()
- return FALSE
-
-/obj/item/robot_module/proc/is_cat()
- return FALSE
-
-/obj/item/robot_module/robot/get_modules()
- . = ..()
- // Common items that all modules have.
- . |= list(
- /obj/item/flash/robot,
- /obj/item/tool/crowbar/cyborg,
- /obj/item/extinguisher,
- /obj/item/gps/robot
- )
-
-/obj/item/robot_module/robot/quad
-
-/obj/item/robot_module/robot/quad/Initialize()
- . = ..()
- var/mob/living/silicon/robot/R = loc
- ASSERT(istype(R))
-
- R.icon = 'icons/mob/robots_wide.dmi'
- R.set_base_pixel_x(-16)
- R.dogborg = TRUE
- R.wideborg = TRUE
- R.icon_x_dimension = 64
- add_verb(R, list(
- /mob/living/silicon/robot/proc/ex_reserve_refill,
- /mob/living/silicon/robot/proc/rest_style
- ))
- if (can_shred)
- add_verb(R, /mob/living/proc/shred_limb)
-
-/obj/item/robot_module/robot/quad/Reset(mob/living/silicon/robot/R)
- . = ..()
- // Reset a bunch of wideborg specific things.
- R.pixel_x = initial(R.pixel_x)
- R.pixel_y = initial(R.pixel_y)
- R.icon = initial(R.icon)
- R.base_pixel_x = initial(R.pixel_x)
- remove_verb(R, list(
- /mob/living/silicon/robot/proc/ex_reserve_refill,
- /mob/living/proc/shred_limb,
- /mob/living/silicon/robot/proc/rest_style
- ))
- R.scrubbing = FALSE
- R.dogborg = FALSE
- R.wideborg = FALSE
-
-/obj/item/robot_module/robot/quad/get_modules()
- . = ..()
- . |= list(
- /obj/item/dogborg/boop_module //Boop people on the nose.
- )
-
-/obj/item/robot_module/robot/quad/get_synths(mob/living/silicon/robot/R)
- . = ..()
- var/datum/matter_synth/water = new /datum/matter_synth(500)
- water.name = "Water reserves"
- water.recharge_rate = 0
- R.water_res = water
- .[MATSYN_WATER] = water
-
-/obj/item/robot_module/robot/quad/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
-
- var/obj/item/dogborg/tongue/T = new /obj/item/dogborg/tongue(src)
- T.water = synths_by_kind[MATSYN_WATER]
- . += T
-
-/obj/item/robot_module/robot/quad/is_dog()
- var/mob/living/silicon/robot/R = loc
- ASSERT(istype(R))
- // This is the only non-canid dogborg type right now.
- return R.icontype != "F3-LINE"
-
-/obj/item/robot_module/robot/quad/is_cat()
- var/mob/living/silicon/robot/R = loc
- ASSERT(istype(R))
- // This is the only non-canid dogborg type right now.
- return R.icontype == "F3-LINE"
-
-// Custom sprite stuff. There's a dedicated system for this, not sure why this is done separately.
-
-/obj/item/robot_module/robot/quad/engi/handle_custom_item(mob/living/silicon/robot/R)
- . = ..()
- if (R.client?.ckey == "nezuli")
- sprites["Alina"] = "alina-eng"
-
-/obj/item/robot_module/robot/quad/medi/handle_custom_item(mob/living/silicon/robot/R)
- . = ..()
- if (R.client?.ckey == "nezuli")
- sprites["Alina"] = "alina-med"
-
-/obj/item/robot_module/robot/quad/sec/handle_custom_item(mob/living/silicon/robot/R)
- . = ..()
- if (R.client?.ckey == "nezuli")
- sprites["Alina"] = "alina-sec"
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/station/cargo.dm b/code/modules/mob/living/silicon/robot/robot_modules/station/cargo.dm
deleted file mode 100644
index 026151ec723..00000000000
--- a/code/modules/mob/living/silicon/robot/robot_modules/station/cargo.dm
+++ /dev/null
@@ -1,91 +0,0 @@
-/obj/item/robot_module/robot/miner
- name = "miner robot module"
- channels = list("Supply" = 1)
- networks = list(NETWORK_MINE)
- sprites = list(
- "NM-USE Nanotrasen" = "robotMine",
- "Cabeiri" = "eyebot-miner",
- "Haruka" = "marinaMN",
- "Telemachus" = "toiletbotminer",
- "WTOperator" = "sleekminer",
- "XI-GUS" = "spidermining",
- "XI-ALP" = "heavyMiner",
- "Basic" = "Miner_old",
- "Advanced Droid" = "droid-miner",
- "Treadhead" = "Miner",
- "Drone" = "drone-miner",
- "Misato" = "tall2miner",
- "L3P1-D0T" = "Glitterfly-Miner",
- "Miss M" = "miss-miner",
- "Carffin" = "coffin-Service",
- "Coffing" = "coffin-Mining",
- "Handy" = "handy-miner",
- "Acheron" = "mechoid-Miner",
- "Shellguard Noble" = "Noble-DIG",
- "ZOOM-BA" = "zoomba-miner",
- "W02M" = "worm-miner"
- )
-
-/obj/item/robot_module/robot/miner/get_modules()
- . = ..()
- . |= list(
- /obj/item/borg/sight/material,
- /obj/item/tool/wrench/cyborg,
- /obj/item/tool/screwdriver/cyborg,
- /obj/item/storage/bag/ore,
- /obj/item/pickaxe/borgdrill,
- /obj/item/gun/projectile/energy/kinetic_accelerator/cyborg,
- /obj/item/storage/bag/sheetsnatcher/borg,
- /obj/item/gripper/miner,
- /obj/item/mining_scanner
- )
-
-/obj/item/robot_module/robot/miner/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
-
- // TODO: Only one emag module is supported right now.
- src.emag = new /obj/item/pickaxe/plasmacutter(src)
- // src.emag = new /obj/item/pickaxe/diamonddrill(src)
- // src.emag = new /obj/item/melee/disruptor/borg(src)
-
-/obj/item/robot_module/robot/quad/miner
- name = "Mining Quadruped module"
- sprites = list(
- "F3-LINE" = "FELI-Mining",
- "K-MINE" = "kmine",
- "Cargo Hound" = "cargohound",
- "Cargo Hound Dark" = "cargohounddark",
- "Drake" = "drakemine",
- "Otie" = "otiec"
- )
- channels = list("Supply" = 1)
- can_be_pushed = 0
-
-/obj/item/robot_module/robot/quad/miner/get_modules()
- . = ..()
- . |= list(
- /obj/item/borg/sight/material,
- /obj/item/tool/wrench/cyborg,
- /obj/item/tool/screwdriver/cyborg,
- /obj/item/storage/bag/ore,
- /obj/item/pickaxe/borgdrill,
- /obj/item/gun/projectile/energy/kinetic_accelerator/cyborg,
- /obj/item/storage/bag/sheetsnatcher/borg,
- /obj/item/gripper/miner,
- /obj/item/mining_scanner,
- /obj/item/dogborg/jaws/small
- )
-
-// In a nutshell, basically service/butler robot but in dog form.
-/obj/item/robot_module/robot/quad/miner/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
-
- // TODO: Only one emag module is supported right now.
- src.emag = new /obj/item/pickaxe/plasmacutter(src)
- // src.emag = new /obj/item/pickaxe/diamonddrill(src)
- // src.emag = new /obj/item/melee/disruptor/borg(src)
-
-/obj/item/robot_module/robot/quad/miner/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
- // These get a larger water synth.
- synths_by_kind[MATSYN_WATER]:max_energy = 1000
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/station/drone.dm b/code/modules/mob/living/silicon/robot/robot_modules/station/drone.dm
deleted file mode 100644
index 77e40ef6be2..00000000000
--- a/code/modules/mob/living/silicon/robot/robot_modules/station/drone.dm
+++ /dev/null
@@ -1,103 +0,0 @@
-/* Drones */
-
-/obj/item/robot_module/drone
- name = "drone module"
- hide_on_manifest = 1
- no_slip = 1
- networks = list(NETWORK_ENGINEERING)
-
-/obj/item/robot_module/drone/get_modules()
- . = ..()
- . |= list(
- /obj/item/borg/sight/meson,
- /obj/item/weldingtool/electric/mounted/cyborg,
- /obj/item/tool/screwdriver/cyborg,
- /obj/item/tool/wrench/cyborg,
- /obj/item/tool/crowbar/cyborg,
- /obj/item/tool/wirecutters/cyborg,
- /obj/item/multitool,
- /obj/item/lightreplacer,
- /obj/item/gripper,
- /obj/item/mop,
- /obj/item/gripper/no_use/loader,
- /obj/item/extinguisher,
- /obj/item/pipe_painter,
- /obj/item/floor_painter,
- /obj/item/t_scanner,
- /obj/item/atmos_analyzer,
- /obj/item/inflatable_dispenser/robot,
- /obj/item/barrier_tape_roll/engineering,
- /obj/item/pipe_dispenser
- )
-
-/obj/item/robot_module/drone/get_synths(mob/living/silicon/robot/R)
- . = ..()
- MATTER_SYNTH(MATSYN_METAL, metal, 25000)
- MATTER_SYNTH(MATSYN_GLASS, glass, 25000)
- MATTER_SYNTH(MATSYN_WOOD, wood, 25000)
- MATTER_SYNTH(MATSYN_PLASTIC, plastic, 25000)
- MATTER_SYNTH(MATSYN_WIRE, wire, 30)
-
-/obj/item/robot_module/drone/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
-
- R.internals = new/obj/item/tank/jetpack/carbondioxide(src)
- . += R.internals
-
- src.emag = new /obj/item/pickaxe/plasmacutter(src)
- src.emag.name = "Plasma Cutter"
-
- var/obj/item/matter_decompiler/MD = new /obj/item/matter_decompiler(src)
- MD.metal = synths_by_kind[MATSYN_METAL]
- MD.glass = synths_by_kind[MATSYN_GLASS]
- MD.wood = synths_by_kind[MATSYN_WOOD]
- MD.plastic = synths_by_kind[MATSYN_PLASTIC]
- . += MD
-
- CYBORG_STACK(material/cyborg/steel, MATSYN_METAL)
- CYBORG_STACK(material/cyborg/glass, MATSYN_GLASS)
- CYBORG_STACK(rods/cyborg , MATSYN_METAL)
- CYBORG_STACK(cable_coil/cyborg , MATSYN_WIRE)
- CYBORG_STACK(tile/floor/cyborg , MATSYN_METAL)
- CYBORG_STACK(material/cyborg/glass/reinforced, list(MATSYN_METAL, MATSYN_GLASS))
- CYBORG_STACK(tile/wood/cyborg , MATSYN_WOOD)
- CYBORG_STACK(material/cyborg/wood , MATSYN_WOOD)
- CYBORG_STACK(material/cyborg/plastic, MATSYN_PLASTIC)
-
-/obj/item/robot_module/drone/construction
- name = "construction drone module"
- hide_on_manifest = 1
- channels = list("Engineering" = 1)
- languages = list()
-
-/obj/item/robot_module/drone/construction/get_modules()
- . = ..()
- . |= /obj/item/rcd/electric/mounted/borg/lesser
-
-/obj/item/robot_module/drone/respawn_consumable(var/mob/living/silicon/robot/R, var/amount)
- var/obj/item/lightreplacer/LR = locate() in src.modules
- LR.Charge(R, amount)
- ..()
- return
-
-/obj/item/robot_module/drone/mining
- name = "miner drone module"
- channels = list("Supply" = 1)
- networks = list(NETWORK_MINE)
-
-/obj/item/robot_module/drone/mining/get_modules()
- . = ..()
- . |= list(
- /obj/item/borg/sight/material,
- /obj/item/pickaxe/borgdrill,
- /obj/item/gun/projectile/energy/kinetic_accelerator/cyborg,
- /obj/item/storage/bag/ore,
- /obj/item/storage/bag/sheetsnatcher/borg
- )
-
-/obj/item/robot_module/drone/mining/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
- src.emag = new /obj/item/pickaxe/diamonddrill(src)
-
-/obj/item/robot_module/drone/construction/matriarch
- name = "matriarch drone module"
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/station/engineering.dm b/code/modules/mob/living/silicon/robot/robot_modules/station/engineering.dm
deleted file mode 100644
index 19f5f673c31..00000000000
--- a/code/modules/mob/living/silicon/robot/robot_modules/station/engineering.dm
+++ /dev/null
@@ -1,215 +0,0 @@
-/obj/item/robot_module/robot/engineering
- name = "engineering robot module"
- channels = list("Engineering" = 1)
- networks = list(NETWORK_ENGINEERING)
- subsystems = list(/mob/living/silicon/proc/subsystem_power_monitor)
- sprites = list(
- "M-USE Nanotrasen" = "robotEngi",
- "Cabeiri" = "eyebot-engineering",
- "Haruka" = "marinaENG",
- "Usagi" = "tallyellow",
- "Telemachus" = "toiletbotengineering",
- "WTOperator" = "sleekce",
- "XI-GUS" = "spidereng",
- "XI-ALP" = "heavyEng",
- "Basic" = "Engineering",
- "Antique" = "engineerrobot",
- "Landmate" = "landmate",
- "Landmate - Treaded" = "engiborg+tread",
- "Drone" = "drone-engineer",
- "Treadwell" = "treadwell",
- "Handy" = "handy-engineer",
- "Misato" = "tall2engineer",
- "L3P1-D0T" = "Glitterfly-Engineering",
- "Miss M" = "miss-engineer",
- "Coffstruction" = "coffin-Construction",
- "Coffgineer" = "coffin-Engineering",
- "X-88" = "xeightyeight-engineering",
- "Acheron" = "mechoid-Engineering",
- "Shellguard Noble" = "Noble-ENG",
- "ZOOM-BA" = "zoomba-engineering",
- "W02M" = "worm-engineering"
- )
-
-/obj/item/robot_module/robot/engineering/construction
- name = "construction robot module"
- no_slip = 1
-
-/* Merged back into engineering (Hell, it's about time.)
-
-/obj/item/robot_module/robot/engineering/construction/Initialize(mapload)
- . = ..()
- src.modules += new /obj/item/borg/sight/meson(src)
- src.modules += new /obj/item/rcd/borg(src)
- src.modules += new /obj/item/tool/screwdriver/cyborg(src)
- src.modules += new /obj/item/tool/wrench/cyborg(src)
- src.modules += new /obj/item/weldingtool/electric/mounted/cyborg(src)
- src.modules += new /obj/item/pickaxe/plasmacutter(src)
- src.modules += new /obj/item/pipe_painter(src)
- src.modules += new /obj/item/floor_painter(src)
- src.modules += new /obj/item/gripper/no_use/loader(src)
- src.modules += new /obj/item/geiger_counter(src)
-
- var/datum/matter_synth/metal = new /datum/matter_synth/metal()
- var/datum/matter_synth/plasteel = new /datum/matter_synth/plasteel()
- var/datum/matter_synth/glass = new /datum/matter_synth/glass()
- synths += metal
- synths += plasteel
- synths += glass
-
- var/obj/item/stack/material/cyborg/steel/M = new (src)
- M.synths = list(metal)
- src.modules += M
-
- var/obj/item/stack/rods/cyborg/R = new /obj/item/stack/rods/cyborg(src)
- R.synths = list(metal)
- src.modules += R
-
- var/obj/item/stack/tile/floor/cyborg/F = new /obj/item/stack/tile/floor/cyborg(src)
- F.synths = list(metal)
- src.modules += F
-
- var/obj/item/stack/material/cyborg/plasteel/S = new (src)
- S.synths = list(plasteel)
- src.modules += S
-
- var/obj/item/stack/material/cyborg/glass/reinforced/RG = new (src)
- RG.synths = list(metal, glass)
- src.modules += RG
-*/
-
-/obj/item/robot_module/robot/engineering/general/get_modules()
- . = ..()
- . |= list(
- /obj/item/borg/sight/meson,
- /obj/item/weldingtool/electric/mounted/cyborg,
- /obj/item/tool/screwdriver/cyborg,
- /obj/item/tool/wrench/cyborg,
- /obj/item/tool/wirecutters/cyborg,
- /obj/item/multitool,
- /obj/item/t_scanner,
- /obj/item/atmos_analyzer,
- /obj/item/barrier_tape_roll/engineering,
- /obj/item/gripper,
- /obj/item/gripper/circuit,
- /obj/item/lightreplacer,
- /obj/item/pipe_painter,
- /obj/item/floor_painter,
- /obj/item/inflatable_dispenser/robot,
- /obj/item/geiger_counter/cyborg,
- /obj/item/rcd/electric/mounted/borg,
- /obj/item/pickaxe/plasmacutter,
- /obj/item/gripper/no_use/loader,
- /obj/item/pipe_dispenser
- )
-
-/obj/item/robot_module/robot/engineering/general/get_synths(mob/living/silicon/robot/R)
- . = ..()
- MATTER_SYNTH(MATSYN_METAL, metal, 40000)
- MATTER_SYNTH(MATSYN_GLASS, glass, 40000)
- MATTER_SYNTH(MATSYN_PLASTEEL, plasteel, 20000)
- MATTER_SYNTH(MATSYN_WOOD, wood, 40000)
- MATTER_SYNTH(MATSYN_PLASTIC, plastic, 40000)
- MATTER_SYNTH(MATSYN_WIRE, wire)
-
-/obj/item/robot_module/robot/engineering/general/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
-
- src.emag = new /obj/item/melee/baton/robot/arm(src)
-
- var/obj/item/matter_decompiler/MD = new /obj/item/matter_decompiler(src)
- MD.metal = synths_by_kind[MATSYN_METAL]
- MD.glass = synths_by_kind[MATSYN_GLASS]
- src.modules += MD
-
- CYBORG_STACK(material/cyborg/steel, MATSYN_METAL)
- CYBORG_STACK(material/cyborg/glass, MATSYN_GLASS)
- CYBORG_STACK(rods/cyborg , MATSYN_METAL)
- CYBORG_STACK(cable_coil/cyborg , MATSYN_WIRE)
- CYBORG_STACK(tile/floor/cyborg , MATSYN_METAL)
- CYBORG_STACK(tile/roofing/cyborg , MATSYN_METAL)
- CYBORG_STACK(material/cyborg/glass/reinforced, list(MATSYN_METAL, MATSYN_GLASS))
- CYBORG_STACK(tile/wood/cyborg , MATSYN_WOOD)
- CYBORG_STACK(material/cyborg/wood , MATSYN_WOOD)
- CYBORG_STACK(material/cyborg/plastic, MATSYN_PLASTIC)
-
-/obj/item/robot_module/robot/quad/engi
- name = "EngiQuad module"
- sprites = list(
- "Pupdozer" = "pupdozer",
- "V2 Engidog" = "thottbot",
- "Borgi" = "borgi-eng",
- "Engineering Hound" = "engihound",
- "Engineering Hound Dark" = "engihounddark",
- "F3-LINE" = "FELI-Engineer",
- "Drake" = "drakeeng",
- "Otie" = "otiee"
- )
- channels = list("Engineering" = 1)
- networks = list(NETWORK_ENGINEERING)
- subsystems = list(/mob/living/silicon/proc/subsystem_power_monitor)
- can_be_pushed = 0
- can_shred = TRUE
-
-/obj/item/robot_module/robot/quad/engi/get_modules()
- . = ..()
- . |= list(
- /obj/item/borg/sight/meson,
- /obj/item/weldingtool/electric/mounted/cyborg,
- /obj/item/tool/screwdriver/cyborg,
- /obj/item/tool/wrench/cyborg,
- /obj/item/tool/wirecutters/cyborg,
- /obj/item/multitool,
- /obj/item/t_scanner,
- /obj/item/rcd/electric/mounted/borg,
- /obj/item/atmos_analyzer,
- /obj/item/barrier_tape_roll/engineering,
- /obj/item/inflatable_dispenser/robot,
- /obj/item/pickaxe/plasmacutter,
- /obj/item/dogborg/jaws/small,
- /obj/item/geiger_counter,
- /obj/item/pipe_painter,
- /obj/item/floor_painter,
- /obj/item/gripper,
- /obj/item/gripper/no_use/loader,
- /obj/item/pipe_dispenser,
- /obj/item/gripper/circuit
- )
-
-/obj/item/robot_module/robot/quad/engi/get_synths(mob/living/silicon/robot/R)
- . = ..()
- //Painfully slow charger regen but high capacity. Also starts with low amount.
- MATTER_SYNTH_WITH_NAME(MATSYN_METAL , metal , "Steel reserves" , 40000)
- MATTER_SYNTH_WITH_NAME(MATSYN_GLASS , glass , "Glass reserves" , 40000)
- MATTER_SYNTH_WITH_NAME(MATSYN_WOOD , wood , "Wood reserves" , 40000)
- MATTER_SYNTH_WITH_NAME(MATSYN_PLASTIC , plastic , "Plastic reserves" , 40000)
- MATTER_SYNTH_WITH_NAME(MATSYN_PLASTEEL, plasteel, "Plasteel reserves", 20000)
- MATTER_SYNTH(MATSYN_WIRE, wire)
-
-/obj/item/robot_module/robot/quad/engi/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
-
- src.emag = new /obj/item/dogborg/pounce(src)
-
- var/obj/item/lightreplacer/dogborg/LR = new /obj/item/lightreplacer/dogborg(src)
- LR.glass = synths_by_kind[MATSYN_GLASS]
- . += LR
-
- var/obj/item/dogborg/sleeper/compactor/decompiler/MD = new /obj/item/dogborg/sleeper/compactor/decompiler(src)
- MD.metal = synths_by_kind[MATSYN_METAL]
- MD.glass = synths_by_kind[MATSYN_GLASS]
- MD.wood = synths_by_kind[MATSYN_WOOD]
- MD.plastic = synths_by_kind[MATSYN_PLASTIC]
- MD.water = synths_by_kind[MATSYN_WATER]
- . += MD
-
- CYBORG_STACK(material/cyborg/steel, MATSYN_METAL)
- CYBORG_STACK(material/cyborg/glass, MATSYN_GLASS)
- CYBORG_STACK(rods/cyborg , MATSYN_METAL)
- CYBORG_STACK(cable_coil/cyborg , MATSYN_WIRE)
- CYBORG_STACK(tile/floor/cyborg , MATSYN_METAL)
- CYBORG_STACK(tile/roofing/cyborg , MATSYN_METAL)
- CYBORG_STACK(material/cyborg/glass/reinforced, list(MATSYN_METAL, MATSYN_GLASS))
- CYBORG_STACK(tile/wood/cyborg , MATSYN_WOOD)
- CYBORG_STACK(material/cyborg/wood , MATSYN_WOOD)
- CYBORG_STACK(material/cyborg/plastic, MATSYN_PLASTIC)
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/station/medical.dm b/code/modules/mob/living/silicon/robot/robot_modules/station/medical.dm
deleted file mode 100644
index c3c5488b242..00000000000
--- a/code/modules/mob/living/silicon/robot/robot_modules/station/medical.dm
+++ /dev/null
@@ -1,186 +0,0 @@
-/obj/item/robot_module/robot/medical
- name = "medical robot module"
- channels = list("Medical" = 1)
- networks = list(NETWORK_MEDICAL)
- subsystems = list(/mob/living/silicon/proc/subsystem_crew_monitor)
- can_be_pushed = 0
-
-/obj/item/robot_module/robot/medical/surgeon
- name = "medical robot module"
- sprites = list(
- "M-USE Nanotrasen" = "robotMedi",
- "Cabeiri" = "eyebot-medical",
- "Haruka" = "marinaMD",
- "Minako" = "arachne",
- "Usagi" = "tallwhite",
- "Telemachus" = "toiletbotsurgeon",
- "WTOperator" = "sleekcmo",
- "XI-ALP" = "heavyMed",
- "Basic" = "Medibot",
- "Advanced Droid" = "droid-medical",
- "Needles" = "medicalrobot",
- "Drone" = "drone-surgery",
- "Handy" = "handy-med",
- "Insekt" = "insekt-Med",
- "Misato" = "tall2medical",
- "L3P1-D0T" = "Glitterfly-Surgeon",
- "Miss M" = "miss-medical",
- "Coffical" = "coffin-Medical",
- "Coffcue" = "coffin-Rescue",
- "X-88" = "xeightyeight-medical",
- "Acheron" = "mechoid-Medical",
- "Shellguard Noble" = "Noble-MED",
- "ZOOM-BA" = "zoomba-medical",
- "W02M" = "worm-crisis"
- )
-
-/obj/item/robot_module/robot/medical/surgeon/get_modules()
- . = ..()
- . |= list(
- /obj/item/healthanalyzer,
- /obj/item/reagent_containers/borghypo/surgeon,
- /obj/item/autopsy_scanner,
- /obj/item/reagent_scanner/adv,
- /obj/item/roller_holder,
- /obj/item/reagent_containers/glass/beaker/large,
- /obj/item/surgical/scalpel/cyborg,
- /obj/item/surgical/hemostat/cyborg,
- /obj/item/surgical/retractor/cyborg,
- /obj/item/surgical/cautery/cyborg,
- /obj/item/surgical/bonegel/cyborg,
- /obj/item/surgical/FixOVein/cyborg,
- /obj/item/surgical/bonesetter/cyborg,
- /obj/item/surgical/circular_saw/cyborg,
- /obj/item/surgical/surgicaldrill/cyborg,
- /obj/item/gripper/no_use/organ,
- /obj/item/gripper/medical,
- /obj/item/shockpaddles/robot,
- /obj/item/reagent_containers/dropper, // Allows surgeon borg to fix necrosis
- /obj/item/reagent_containers/syringe,
- /obj/item/dogborg/mirrortool
- )
-
-/obj/item/robot_module/robot/medical/surgeon/get_synths(mob/living/silicon/robot/R)
- . = ..()
- MATTER_SYNTH(MATSYN_DRUGS, medicine, 15000)
-
-/obj/item/robot_module/robot/medical/surgeon/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
-
- src.emag = new /obj/item/reagent_containers/spray(src)
- src.emag.reagents.add_reagent("pacid", 250)
- src.emag.name = "Polyacid spray"
-
- var/obj/item/stack/medical/advanced/ointment/O = new /obj/item/stack/medical/advanced/ointment(src)
- O.uses_charge = 1
- O.charge_costs = list(1000)
- O.synths = list(synths_by_kind[MATSYN_DRUGS])
- . += O
-
- var/obj/item/stack/nanopaste/N = new /obj/item/stack/nanopaste(src)
- N.uses_charge = 1
- N.charge_costs = list(1000)
- N.synths = list(synths_by_kind[MATSYN_DRUGS])
- . += N
-
- var/obj/item/stack/medical/advanced/bruise_pack/B = new /obj/item/stack/medical/advanced/bruise_pack(src)
- B.uses_charge = 1
- B.charge_costs = list(1000)
- B.synths = list(synths_by_kind[MATSYN_DRUGS])
- . += B
-
-/obj/item/robot_module/robot/medical/surgeon/respawn_consumable(var/mob/living/silicon/robot/R, var/amount)
-
- var/obj/item/reagent_containers/syringe/S = locate() in src.modules
- if(S.mode == 2)
- S.reagents.clear_reagents()
- S.mode = initial(S.mode)
- S.desc = initial(S.desc)
- S.update_icon()
-
- if(src.emag)
- var/obj/item/reagent_containers/spray/PS = src.emag
- PS.reagents.add_reagent("pacid", 2 * amount)
-
- ..()
-
-//Crisis module removed - 5/2/2021
-
-/obj/item/robot_module/robot/quad/medi
- name = "MediQuad module"
- channels = list("Medical" = 1)
- networks = list(NETWORK_MEDICAL)
- subsystems = list(/mob/living/silicon/proc/subsystem_crew_monitor)
- can_be_pushed = 0
- can_shred = TRUE
- sprites = list(
- "Medical Hound" = "medihound",
- "Dark Medical Hound" = "medihounddark",
- "Mediborg model V-2" = "vale",
- "Borgi" = "borgi-medi",
- "F3-LINE" = "FELI-Medical",
- "Drake" = "drakemed"
- )
-
-/obj/item/robot_module/robot/quad/medi/get_modules()
- . = ..()
- . |= list(
- /obj/item/dogborg/jaws/small, //In case a patient is being attacked by carp.
- /obj/item/healthanalyzer, // See who's hurt specificially.
- /obj/item/autopsy_scanner,
- /obj/item/roller_holder, // Sometimes you just can't buckle someone to yourself because of taurcode. this is for those times.
- /obj/item/reagent_scanner/adv,
- /obj/item/reagent_containers/syringe, //In case the chemist is nice!
- /obj/item/reagent_containers/glass/beaker/large,//For holding the chemicals when the chemist is nice
- // /obj/item/sleevemate, //Lets them scan people.
- /obj/item/shockpaddles/robot/hound, //Paws of life
- //New surgery tools + grippers
- /obj/item/surgical/scalpel/cyborg,
- /obj/item/surgical/hemostat/cyborg,
- /obj/item/surgical/retractor/cyborg,
- /obj/item/surgical/cautery/cyborg,
- /obj/item/surgical/bonegel/cyborg,
- /obj/item/surgical/FixOVein/cyborg,
- /obj/item/surgical/bonesetter/cyborg,
- /obj/item/surgical/circular_saw/cyborg,
- /obj/item/surgical/surgicaldrill/cyborg,
- /obj/item/gripper/no_use/organ,
- /obj/item/gripper/medical,
- /obj/item/dogborg/mirrortool
- )
-
-/obj/item/robot_module/robot/quad/medi/get_synths(mob/living/silicon/robot/R)
- . = ..()
- MATTER_SYNTH(MATSYN_DRUGS, medicine, 15000)
-
-/obj/item/robot_module/robot/quad/medi/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
- src.emag = new /obj/item/dogborg/pounce(src) //Pounce
-
- var/obj/item/reagent_containers/borghypo/hound/H = new /obj/item/reagent_containers/borghypo/hound(src)
- H.water = synths_by_kind[MATSYN_WATER]
- . += H
-
- var/obj/item/dogborg/sleeper/B = new /obj/item/dogborg/sleeper(src) //So they can nom people and heal them
- B.water = synths_by_kind[MATSYN_WATER]
- src.modules += B
-
- var/medicine = synths_by_kind[MATSYN_DRUGS]
-
- var/obj/item/stack/nanopaste/P = new /obj/item/stack/nanopaste(src)
- P.uses_charge = 1
- P.charge_costs = list(1000)
- P.synths = list(medicine)
- . += P
-
- var/obj/item/stack/medical/advanced/ointment/K = new /obj/item/stack/medical/advanced/ointment(src)
- K.uses_charge = 1
- K.charge_costs = list(1000)
- K.synths = list(medicine)
- . += K
-
- var/obj/item/stack/medical/advanced/bruise_pack/L = new /obj/item/stack/medical/advanced/bruise_pack(src)
- L.uses_charge = 1
- L.charge_costs = list(1000)
- L.synths = list(medicine)
- . += L
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/station/misc.dm b/code/modules/mob/living/silicon/robot/robot_modules/station/misc.dm
deleted file mode 100644
index bffbbf6c866..00000000000
--- a/code/modules/mob/living/silicon/robot/robot_modules/station/misc.dm
+++ /dev/null
@@ -1,62 +0,0 @@
-/obj/item/robot_module/robot/standard
- name = "standard robot module"
- sprites = list(
- "M-USE Nanotrasen" = "robot",
- "Cabeiri" = "eyebot-standard",
- "Haruka" = "marinaSD",
- "Usagi" = "tallflower",
- "Telemachus" = "toiletbot",
- "WTOperator" = "sleekstandard",
- "WTOmni" = "omoikane",
- "XI-GUS" = "spider",
- "XI-ALP" = "heavyStandard",
- "Basic" = "robot_old",
- "Android" = "droid",
- "Drone" = "drone-standard",
- "Insekt" = "insekt-Default",
- "Misato" = "tall2standard",
- "L3P1-D0T" = "Glitterfly-Standard",
- "Convict" = "servitor",
- "Miss M" = "miss-standard",
- "Coffin" = "coffin-Standard",
- "X-88" = "xeightyeight-standard",
- "Handy" = "handy-standard",
- "Acheron" = "mechoid-Standard",
- "Shellguard Noble" = "Noble-STD",
- "ZOOM-BA" = "zoomba-standard",
- "W02M" = "worm-standard"
- )
-
-/obj/item/robot_module/robot/standard/get_modules()
- . = ..()
- . |= list(
- /obj/item/melee/baton/loaded,
- /obj/item/tool/wrench/cyborg,
- /obj/item/healthanalyzer
- )
-
-/obj/item/robot_module/robot/standard/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
- src.emag = new /obj/item/melee/transforming/energy/sword(src)
-
-/obj/item/robot_module/robot/quad/basic
- name = "Standard Quadruped module"
- sprites = list(
- "F3-LINE" = "FELI-Standard"
- )
- can_be_pushed = 0
-
-/obj/item/robot_module/robot/quad/basic/get_modules()
- . = ..()
- . |= list(
- /obj/item/melee/baton/loaded,
- /obj/item/tool/wrench/cyborg,
- /obj/item/healthanalyzer,
- /obj/item/dogborg/jaws/small
- )
-
-/obj/item/robot_module/robot/quad/basic/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
- // These get a larger water synth.
- synths_by_kind[MATSYN_WATER]:max_energy = 1000
- src.emag = new /obj/item/melee/transforming/energy/sword(src)
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/station/science.dm b/code/modules/mob/living/silicon/robot/robot_modules/station/science.dm
deleted file mode 100644
index c137be97935..00000000000
--- a/code/modules/mob/living/silicon/robot/robot_modules/station/science.dm
+++ /dev/null
@@ -1,135 +0,0 @@
-/obj/item/robot_module/robot/research
- name = "research module"
- channels = list("Science" = 1)
- sprites = list(
- "L'Ouef" = "peaceborg",
- "Cabeiri" = "eyebot-science",
- "Haruka" = "marinaSCI",
- "WTDove" = "whitespider",
- "WTOperator" = "sleekscience",
- "Droid" = "droid-science",
- "Drone" = "drone-science",
- "Handy" = "handy-science",
- "Insekt" = "insekt-Sci",
- "L3P1-D0T" = "Glitterfly-Research",
- "Coffsearch" = "coffin-Research",
- "X-88" = "xeightyeight-science",
- "Acheron" = "mechoid-Science",
- "ZOOM-BA" = "zoomba-research",
- "W02M" = "worm-engineering"
- )
-
-/obj/item/robot_module/robot/research/get_synths()
- . = ..()
- MATTER_SYNTH(MATSYN_NANITES, nanite, 10000)
- MATTER_SYNTH(MATSYN_WIRE, wire)
-
-/obj/item/robot_module/robot/research/get_modules()
- . = ..()
- . |= list(
- /obj/item/portable_destructive_analyzer,
- /obj/item/gripper/research,
- /obj/item/gripper/circuit,
- /obj/item/gripper/no_use/organ/robotics,
- /obj/item/gripper/no_use/mech,
- /obj/item/gripper/no_use/loader,
- /obj/item/robotanalyzer,
- /obj/item/card/robot,
- /obj/item/weldingtool/electric/mounted/cyborg,
- /obj/item/tool/screwdriver/cyborg,
- /obj/item/tool/wrench/cyborg,
- /obj/item/tool/wirecutters/cyborg,
- /obj/item/multitool,
- /obj/item/surgical/scalpel/cyborg,
- /obj/item/surgical/circular_saw/cyborg,
- /obj/item/reagent_containers/syringe,
- /obj/item/reagent_containers/glass/beaker/large,
- /obj/item/storage/part_replacer,
- /obj/item/shockpaddles/robot,
- /obj/item/melee/baton/slime/robot,
- /obj/item/gun/projectile/energy/taser/xeno/robot
- )
-
-/obj/item/robot_module/robot/research/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
- src.emag = new /obj/item/borg/combat/shield(src)
-
- var/obj/item/stack/nanopaste/N = new /obj/item/stack/nanopaste(src)
- N.uses_charge = 1
- N.charge_costs = list(1000)
- N.synths = list(synths_by_kind[MATSYN_NANITES])
- src.modules += N
-
- CYBORG_STACK(cable_coil/cyborg, list(MATSYN_WIRE))
-
-/obj/item/robot_module/robot/research/respawn_consumable(var/mob/living/silicon/robot/R, var/amount)
-
- var/obj/item/reagent_containers/syringe/S = locate() in src.modules
- if(S.mode == 2)
- S.reagents.clear_reagents()
- S.mode = initial(S.mode)
- S.desc = initial(S.desc)
- S.update_icon()
-
- ..()
-
-/obj/item/robot_module/robot/quad/sci
- name = "SciQuad Module"
- sprites = list(
- "Research Hound" = "science",
- "Borgi" = "borgi-sci",
- "F3-LINE" = "FELI-Research",
- "Sci-9" = "scihound",
- "Sci-9 Dark" = "scihounddark"
- )
- channels = list("Science" = 1)
- can_be_pushed = 0
- can_shred = TRUE
-
-/obj/item/robot_module/robot/quad/sci/get_modules()
- . = ..()
- . |= list(
- /obj/item/portable_destructive_analyzer,
- /obj/item/dogborg/jaws/small,
- /obj/item/gripper/research,
- /obj/item/gripper/circuit,
- /obj/item/gripper/no_use/organ/robotics,
- /obj/item/gripper/no_use/mech,
- /obj/item/gripper/no_use/loader,
- /obj/item/tool/screwdriver/cyborg,
- /obj/item/tool/wrench/cyborg,
- /obj/item/robotanalyzer,
- /obj/item/weldingtool/electric/mounted/cyborg,
- /obj/item/multitool,
- /obj/item/reagent_containers/glass/beaker/large,
- /obj/item/reagent_containers/syringe,
- /obj/item/storage/part_replacer,
- /obj/item/card/robot,
- /obj/item/shockpaddles/robot,
- /obj/item/tool/wirecutters/cyborg,
- /obj/item/melee/baton/slime/robot,
- /obj/item/gun/projectile/energy/taser/xeno/robot,
- /obj/item/surgical/scalpel/cyborg,
- /obj/item/surgical/circular_saw/cyborg
- )
-
-/obj/item/robot_module/robot/quad/sci/get_synths()
- . = ..()
- MATTER_SYNTH(MATSYN_NANITES, nanite, 10000)
- MATTER_SYNTH(MATSYN_WIRE, wire)
-
-/obj/item/robot_module/robot/quad/sci/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
- var/obj/item/stack/nanopaste/N = new /obj/item/stack/nanopaste(src)
- N.uses_charge = 1
- N.charge_costs = list(1000)
- N.synths = list(synths_by_kind[MATSYN_NANITES])
- . += N
-
- var/obj/item/dogborg/sleeper/compactor/analyzer/B = new /obj/item/dogborg/sleeper/compactor/analyzer(src)
- B.water = synths_by_kind[MATSYN_WATER]
- . += B
-
- CYBORG_STACK(cable_coil/cyborg, list(MATSYN_WIRE))
-
- src.emag = new /obj/item/borg/combat/shield(src)
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/station/security.dm b/code/modules/mob/living/silicon/robot/robot_modules/station/security.dm
deleted file mode 100644
index 0a69cceab68..00000000000
--- a/code/modules/mob/living/silicon/robot/robot_modules/station/security.dm
+++ /dev/null
@@ -1,180 +0,0 @@
-/obj/item/robot_module/robot/security
- name = "security robot module"
- channels = list("Security" = 1)
- networks = list(NETWORK_SECURITY)
- subsystems = list(/mob/living/silicon/proc/subsystem_crew_monitor)
- can_be_pushed = 0
- supported_upgrades = list(/obj/item/borg/upgrade/tasercooler)
- is_the_law = TRUE
-
-/obj/item/robot_module/robot/security/general
- sprites = list(
- "M-USE Nanotrasen" = "robotSecy",
- "Cabeiri" = "eyebot-security",
- "Cerberus" = "bloodhound",
- "Cerberus - Treaded" = "treadhound",
- "Haruka" = "marinaSC",
- "Usagi" = "tallred",
- "Telemachus" = "toiletbotsecurity",
- "WTOperator" = "sleeksecurity",
- "XI-GUS" = "spidersec",
- "XI-ALP" = "heavySec",
- "Basic" = "secborg",
- "Black Knight" = "securityrobot",
- "Drone" = "drone-sec",
- "Insekt" = "insekt-Sec",
- "Misato" = "tall2security",
- "L3P1-D0T" = "Glitterfly-Security",
- "Miss M" = "miss-security",
- "Coffcurity" = "coffin-Combat",
- "Handy" = "handy-sec",
- "Acheron" = "mechoid-Security",
- "Shellguard Noble" = "Noble-SEC",
- "ZOOM-BA" = "zoomba-security",
- "W02M" = "worm-security"
- )
-
-/obj/item/robot_module/robot/security/general/get_modules()
- . = ..()
- . |= list(
- /obj/item/handcuffs/cyborg,
- /obj/item/melee/baton/robot,
- /obj/item/gun/projectile/energy/taser/mounted/cyborg,
- /obj/item/barrier_tape_roll/police,
- /obj/item/reagent_containers/spray/pepper,
- /obj/item/gripper/security
- )
-
-/obj/item/robot_module/robot/security/general/handle_special_module_init(mapload)
- . = ..()
- src.emag = new /obj/item/gun/projectile/energy/laser/mounted(src)
-
-/obj/item/robot_module/robot/security/respawn_consumable(var/mob/living/silicon/robot/R, var/amount)
- var/obj/item/flash/F = locate() in src.modules
- if(F.broken)
- F.broken = 0
- F.times_used = 0
- F.icon_state = "flash"
- else if(F.times_used)
- F.times_used--
- var/obj/item/gun/projectile/energy/taser/mounted/cyborg/T = locate() in src.modules
- if(T.obj_cell_slot.cell.charge < T.obj_cell_slot.cell.max_charge)
- T.obj_cell_slot.cell.give(T.charge_cost * amount)
- T.update_icon()
- else
- T.charge_tick = 0
-
-/obj/item/robot_module/robot/security/combat
- name = "combat robot module"
- hide_on_manifest = 1
- sprites = list(
- "Haruka" = "marinaCB",
- "Cabeiri" = "eyebot-combat",
- "Combat Android" = "droid-combat",
- "Insekt" = "insekt-Combat",
- "Acheron" = "mechoid-Combat",
- "ZOOM-BA" = "zoomba-combat"
- )
-
-/obj/item/robot_module/robot/security/combat/get_modules()
- . = ..()
- . |= list(
- /obj/item/flash,
- // /obj/item/borg/sight/thermal,
- /obj/item/gun/projectile/energy/laser/mounted,
- /obj/item/pickaxe/plasmacutter,
- /obj/item/borg/combat/shield,
- /obj/item/borg/combat/mobility,
- )
-
-/obj/item/robot_module/robot/security/combat/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
- src.emag = new /obj/item/gun/projectile/energy/lasercannon/mounted(src)
-
-/obj/item/robot_module/robot/quad/sec
- name = "SecuriQuad module"
- sprites = list(
- "K9 hound" = "k9",
- "K9 Alternative" = "k92",
- "Secborg model V-2" = "secborg",
- "Borgi" = "borgi-sec",
- "Otieborg" = "oties",
- "F3-LINE" = "FELI-Security",
- "Drake" = "drakesec"
- )
- channels = list("Security" = 1)
- networks = list(NETWORK_SECURITY)
- can_be_pushed = 0
- is_the_law = TRUE
- can_shred = TRUE
- supported_upgrades = list(/obj/item/borg/upgrade/tasercooler)
-
-/obj/item/robot_module/robot/quad/sec/get_modules()
- . = ..()
- . |= list(
- /obj/item/handcuffs/cyborg, //You need cuffs to be a proper sec borg!
- /obj/item/dogborg/jaws/big, //In case there's some kind of hostile mob.
- /obj/item/melee/baton/robot, //Since the pounce module refused to work, they get a stunbaton instead.
- /obj/item/barrier_tape_roll/police, //Block out crime scenes.
- /obj/item/gun/projectile/energy/taser/mounted/cyborg, //They /are/ a security borg, after all.
- /obj/item/dogborg/pounce //Pounce
- )
-
-/obj/item/robot_module/robot/quad/sec/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
- src.emag = new /obj/item/gun/projectile/energy/laser/mounted(src) //Emag. Not a big problem.
-
- var/obj/item/dogborg/sleeper/K9/B = new /obj/item/dogborg/sleeper/K9(src) //Eat criminals. Bring them to the brig.
- B.water = synths_by_kind[MATSYN_WATER]
- src.modules += B
-
-/obj/item/robot_module/robot/quad/sec/respawn_consumable(var/mob/living/silicon/robot/R, var/amount)
- var/obj/item/flash/F = locate() in src.modules
- if(F.broken)
- F.broken = 0
- F.times_used = 0
- F.icon_state = "flash"
- else if(F.times_used)
- F.times_used--
- var/obj/item/gun/projectile/energy/taser/mounted/cyborg/T = locate() in src.modules
- if(T.obj_cell_slot.cell.charge < T.obj_cell_slot.cell.max_charge)
- T.obj_cell_slot.cell.give(T.charge_cost * amount)
- T.update_icon()
- else
- T.charge_tick = 0
- /*var/obj/item/melee/baton/robot/B = locate() in src.modules //Borg baton uses borg cell.
- if(B && B.bcell)
- B.bcell.give(amount)*/
-
-/obj/item/robot_module/robot/quad/ert
- name = "Emergency Response module"
- channels = list("Security" = 1)
- networks = list(NETWORK_SECURITY)
- can_be_pushed = 0
- sprites = list(
- "Standard" = "ert",
- "Borgi" = "borgi",
- "F3-LINE" = "FELI-Combat"
- )
-
- can_shred = TRUE
-
-/obj/item/robot_module/robot/quad/ert/get_modules()
- . = ..()
- . |= list(
- /obj/item/handcuffs/cyborg,
- /obj/item/dogborg/jaws/big,
- /obj/item/melee/baton/robot,
- /obj/item/barrier_tape_roll/police,
- /obj/item/gun/projectile/energy/taser/mounted/cyborg,
- /obj/item/dogborg/swordtail
- )
-
-/obj/item/robot_module/robot/quad/ert/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
-
- src.emag = new /obj/item/gun/projectile/energy/laser/mounted(src)
-
- var/obj/item/dogborg/sleeper/K9/B = new /obj/item/dogborg/sleeper/K9(src)
- B.water = synths_by_kind[MATSYN_WATER]
- . += B
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/station/service.dm b/code/modules/mob/living/silicon/robot/robot_modules/station/service.dm
deleted file mode 100644
index 5430323015f..00000000000
--- a/code/modules/mob/living/silicon/robot/robot_modules/station/service.dm
+++ /dev/null
@@ -1,341 +0,0 @@
-/obj/item/robot_module/robot/janitor
- name = "janitorial robot module"
- channels = list("Service" = 1)
- sprites = list(
- "M-USE Nanotrasen" = "robotJani",
- "Arachne" = "crawler",
- "Cabeiri" = "eyebot-janitor",
- "Haruka" = "marinaJN",
- "Telemachus" = "toiletbotjanitor",
- "WTOperator" = "sleekjanitor",
- "XI-ALP" = "heavyRes",
- "Basic" = "JanBot2",
- "Mopbot" = "janitorrobot",
- "Mop Gear Rex" = "mopgearrex",
- "Drone" = "drone-janitor",
- "Misato" = "tall2janitor",
- "L3P1-D0T" = "Glitterfly-Janitor",
- "Miss M" = "miss-janitor",
- "Cleriffin" = "coffin-Clerical",
- "Coffstodial" = "coffin-Custodial",
- "Handy" = "handy-janitor",
- "Acheron" = "mechoid-Janitor",
- "Shellguard Noble" = "Noble-CLN",
- "ZOOM-BA" = "zoomba-janitor",
- "W02M" = "worm-janitor"
- )
-
-/obj/item/robot_module/robot/janitor/get_modules()
- . = ..()
- . |= list(
- /obj/item/soap/nanotrasen,
- /obj/item/storage/bag/trash,
- /obj/item/mop,
- /obj/item/lightreplacer
- )
-
-/obj/item/robot_module/robot/janitor/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
- src.emag = new /obj/item/reagent_containers/spray(src)
- src.emag.reagents.add_reagent("lube", 250)
- src.emag.name = "Lube spray"
-
-/obj/item/robot_module/robot/janitor/respawn_consumable(var/mob/living/silicon/robot/R, var/amount)
- var/obj/item/lightreplacer/LR = locate() in src.modules
- LR.Charge(R, amount)
- if(src.emag)
- var/obj/item/reagent_containers/spray/S = src.emag
- S.reagents.add_reagent("lube", 2 * amount)
-
-/obj/item/robot_module/robot/clerical
- name = "service robot module"
- channels = list("Service" = 1)
- languages = list(
- LANGUAGE_AKHANI = 1,
- LANGUAGE_BIRDSONG = 1,
- LANGUAGE_CANILUNZT = 1,
- LANGUAGE_DAEMON = 1,
- LANGUAGE_EAL = 1,
- LANGUAGE_ECUREUILIAN= 1,
- LANGUAGE_ENOCHIAN = 1,
- LANGUAGE_GUTTER = 1,
- LANGUAGE_ROOTLOCAL = 0,
- LANGUAGE_SAGARU = 1,
- LANGUAGE_SCHECHI = 1,
- LANGUAGE_SIGN = 0,
- LANGUAGE_SIIK = 1,
- LANGUAGE_SKRELLIAN = 1,
- LANGUAGE_SKRELLIANFAR = 0,
- LANGUAGE_SOL_COMMON = 1,
- LANGUAGE_SQUEAKISH = 1,
- LANGUAGE_TERMINUS = 1,
- LANGUAGE_TRADEBAND = 1,
- LANGUAGE_UNATHI = 1,
- LANGUAGE_ZADDAT = 1
- )
-
-/obj/item/robot_module/robot/clerical/butler
- sprites = list(
- "M-USE Nanotrasen" = "robotServ",
- "Cabeiri" = "eyebot-standard",
- "Haruka" = "marinaSV",
- "Michiru" = "maidbot",
- "Usagi" = "tallgreen",
- "Telemachus" = "toiletbot",
- "WTOperator" = "sleekservice",
- "WTOmni" = "omoikane",
- "XI-GUS" = "spider",
- "XI-ALP" = "heavyServ",
- "Standard" = "Service2",
- "Waitress" = "Service",
- "Bro" = "Brobot",
- "Rich" = "maximillion",
- "Drone - Service" = "drone-service",
- "Drone - Hydro" = "drone-hydro",
- "Misato" = "tall2service",
- "L3P1-D0T" = "Glitterfly-Service",
- "Miss M" = "miss-service",
- "Handy - Service" = "handy-service",
- "Handy - Hydro" = "handy-hydro",
- "Acheron" = "mechoid-Service",
- "Shellguard Noble" = "Noble-SRV",
- "ZOOM-BA" = "zoomba-service",
- "W02M" = "worm-service"
- )
-
-/obj/item/robot_module/robot/clerical/butler/Initialize(mapload)
- . = ..()
- src.modules += new /obj/item/gripper/service(src)
- src.modules += new /obj/item/reagent_containers/glass/bucket(src)
- src.modules += new /obj/item/material/minihoe(src)
- src.modules += new /obj/item/material/knife/machete/hatchet(src)
- src.modules += new /obj/item/plant_analyzer(src)
- src.modules += new /obj/item/storage/bag/plants(src)
- src.modules += new /obj/item/robot_harvester(src)
- src.modules += new /obj/item/material/knife(src)
- src.modules += new /obj/item/material/kitchen/rollingpin(src)
- src.modules += new /obj/item/multitool(src) //to freeze trays
-
- var/obj/item/rsf/M = new /obj/item/rsf(src)
- M.stored_matter = 30
- src.modules += M
-
- src.modules += new /obj/item/reagent_containers/dropper/industrial(src)
-
- var/obj/item/flame/lighter/zippo/L = new /obj/item/flame/lighter/zippo(src)
- L.lit = 1
- src.modules += L
-
- src.modules += new /obj/item/tray/robotray(src)
- src.modules += new /obj/item/reagent_containers/borghypo/service(src)
- src.emag = new /obj/item/reagent_containers/food/drinks/bottle/small/beer(src)
-
- var/datum/reagent_holder/R = new/datum/reagent_holder(50)
- src.emag.reagents = R
- R.my_atom = src.emag
- R.add_reagent("beer2", 50)
- src.emag.name = "Mickey Finn's Special Brew"
-
-/obj/item/robot_module/robot/clerical/butler/respawn_consumable(var/mob/living/silicon/robot/R, var/amount)
- var/obj/item/reagent_containers/food/condiment/enzyme/E = locate() in src.modules
- E.reagents.add_reagent("enzyme", 2 * amount)
- if(src.emag)
- var/obj/item/reagent_containers/food/drinks/bottle/small/beer/B = src.emag
- B.reagents.add_reagent("beer2", 2 * amount)
-
-/obj/item/robot_module/robot/clerical/general
- name = "clerical robot module"
- sprites = list(
- "M-USE Nanotrasen" = "robotCler",
- "Cabeiri" = "eyebot-standard",
- "Haruka" = "marinaSV",
- "Usagi" = "tallgreen",
- "Telemachus" = "toiletbot",
- "WTOperator" = "sleekclerical",
- "WTOmni" = "omoikane",
- "XI-GUS" = "spidercom",
- "XI-ALP" = "heavyServ",
- "Waitress" = "Service",
- "Bro" = "Brobot",
- "Rich" = "maximillion",
- "Default" = "Service2",
- "Drone" = "drone-blu",
- "Misato" = "tall2service",
- "L3P1-D0T" = "Glitterfly-Clerical",
- "Miss M" = "miss-service",
- "Handy" = "handy-clerk",
- "Acheron" = "mechoid-Service",
- "Shellguard Noble" = "Noble-SRV",
- "ZOOM-BA" = "zoomba-clerical",
- "W02M" = "worm-service"
- )
-
-/obj/item/robot_module/robot/clerical/general/get_modules()
- . = ..()
- . |= list(
- /obj/item/pen/robopen,
- /obj/item/form_printer,
- /obj/item/gripper/paperwork,
- /obj/item/hand_labeler,
- /obj/item/stamp,
- /obj/item/stamp/denied
- )
-
-/obj/item/robot_module/robot/clerical/general/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
- // cba to make emags support more than one item
- if (prob(50))
- src.emag = new /obj/item/stamp/chameleon(src)
- else
- src.emag = new /obj/item/pen/chameleon(src)
-
-/obj/item/robot_module/robot/quad/jani
- name = "JaniQuad module"
- sprites = list(
- "Custodial Hound" = "scrubpup",
- "Borgi" = "borgi-jani",
- "Otieborg" = "otiej",
- "Janihound, J9" = "J9",
- "F3-LINE" = "FELI-Janitor",
- "Drake" = "drakejanit"
- )
- channels = list("Service" = 1)
- can_be_pushed = 0
- can_shred = TRUE
-
-/obj/item/robot_module/robot/quad/jani/get_modules()
- . = ..()
- . |= list(
- /obj/item/dogborg/jaws/small,
- /obj/item/pupscrubber
- )
-
-/obj/item/robot_module/robot/quad/jani/get_synths()
- . = ..()
- //Starts empty. Can only recharge with recycled material.
- .[MATSYN_METAL] = new /datum/matter_synth/metal {
- name = "Steel reserves";
- recharge_rate = 0;
- max_energy = 50000;
- energy = 0;
- }
-
- .[MATSYN_GLASS] = new /datum/matter_synth/glass {
- name = "Glass reserves";
- recharge_rate = 0;
- max_energy = 50000;
- energy = 0;
- }
-
-/obj/item/robot_module/robot/quad/jani/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
-
- src.emag = new /obj/item/dogborg/pounce(src) //Pounce
-
- var/obj/item/lightreplacer/dogborg/LR = new /obj/item/lightreplacer/dogborg(src)
- LR.glass = synths_by_kind[MATSYN_GLASS]
- . += LR
-
- var/obj/item/dogborg/sleeper/compactor/C = new /obj/item/dogborg/sleeper/compactor(src)
- C.metal = synths_by_kind[MATSYN_METAL]
- C.glass = synths_by_kind[MATSYN_GLASS]
- C.water = synths_by_kind[MATSYN_WATER]
- . += C
-
- //Sheet refiners can only produce raw sheets.
- var/obj/item/stack/material/cyborg/steel/M = new (src)
- M.name = "steel recycler"
- M.desc = "A device that refines recycled steel into sheets."
- M.synths = list(synths_by_kind[MATSYN_METAL])
- M.explicit_recipes = list(
- create_stack_recipe_datum(name = "steel sheet", product = /obj/item/stack/material/steel, cost = 1)
- )
- . += M
-
- var/obj/item/stack/material/cyborg/glass/G = new (src)
- G.name = "glass recycler"
- G.desc = "A device that refines recycled glass into sheets."
- G.allow_window_autobuild = FALSE
- G.synths = list(synths_by_kind[MATSYN_GLASS])
- M.explicit_recipes = list(
- create_stack_recipe_datum(name = "glass sheet", product = /obj/item/stack/material/glass, cost = 1)
- )
- . += G
-
-// Uses modified K9 sprites.
-/obj/item/robot_module/robot/quad/serv
- name = "Service Quadruped module"
- sprites = list(
- "Blackhound" = "k50",
- "Pinkhound" = "k69",
- "ServicehoundV2" = "serve2",
- "ServicehoundV2 Darkmode" = "servedark",
- "F3-LINE" = "FELI-Service"
- )
- languages = list(
- LANGUAGE_AKHANI = 1,
- LANGUAGE_BIRDSONG = 1,
- LANGUAGE_CANILUNZT = 1,
- LANGUAGE_DAEMON = 1,
- LANGUAGE_EAL = 1,
- LANGUAGE_ECUREUILIAN= 1,
- LANGUAGE_ENOCHIAN = 1,
- LANGUAGE_GUTTER = 1,
- LANGUAGE_ROOTLOCAL = 0,
- LANGUAGE_SAGARU = 1,
- LANGUAGE_SCHECHI = 1,
- LANGUAGE_SIGN = 0,
- LANGUAGE_SIIK = 1,
- LANGUAGE_SKRELLIAN = 1,
- LANGUAGE_SKRELLIANFAR = 0,
- LANGUAGE_SOL_COMMON = 1,
- LANGUAGE_SQUEAKISH = 1,
- LANGUAGE_TERMINUS = 1,
- LANGUAGE_TRADEBAND = 1,
- LANGUAGE_UNATHI = 1,
- LANGUAGE_ZADDAT = 1
- )
- channels = list("Service" = 1)
- can_be_pushed = 0
-
-/obj/item/robot_module/robot/quad/serv/get_modules()
- . = ..()
- . |= list(
- /obj/item/gripper/service,
- /obj/item/reagent_containers/glass/bucket,
- /obj/item/material/minihoe,
- /obj/item/material/knife/machete/hatchet,
- /obj/item/plant_analyzer,
- /obj/item/storage/bag/dogborg,
- /obj/item/robot_harvester,
- /obj/item/material/knife,
- /obj/item/material/kitchen/rollingpin,
- /obj/item/multitool, //to freeze trays
- /obj/item/dogborg/jaws/small,
- /obj/item/reagent_containers/dropper/industrial,
- /obj/item/tray/robotray,
- /obj/item/reagent_containers/borghypo/service,
- /obj/item/storage/bag/trash
- )
-
-// In a nutshell, basicly service/butler robot but in dog form.
-/obj/item/robot_module/robot/quad/serv/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
- src.emag = new /obj/item/dogborg/pounce(src)
-
- // These get a larger water synth.
- synths_by_kind[MATSYN_WATER]:max_energy = 1000
-
- var/obj/item/rsf/M = new /obj/item/rsf(src)
- M.stored_matter = 30
- . += M
-
- var/obj/item/flame/lighter/zippo/L = new /obj/item/flame/lighter/zippo(src)
- L.lit = 1
- . += L
-
-/* // I don't know what kind of sleeper to put here, but also no need if you already have "Robot Nom" verb.
- var/obj/item/dogborg/sleeper/K9/B = new /obj/item/dogborg/sleeper/K9(src)
- B.water = water
- src.modules += B
-*/
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/swarm.dm b/code/modules/mob/living/silicon/robot/robot_modules/swarm.dm
deleted file mode 100644
index 6f0b333461e..00000000000
--- a/code/modules/mob/living/silicon/robot/robot_modules/swarm.dm
+++ /dev/null
@@ -1,30 +0,0 @@
-/obj/item/robot_module/drone/swarm
- name = "swarm drone module"
- var/id
-
-/obj/item/robot_module/drone/swarm/get_modules()
- . = ..()
- . |= list(
- /obj/item/rcd/electric/mounted/borg/swarm,
- /obj/item/flash/robot,
- /obj/item/handcuffs/cable/tape/cyborg,
- /obj/item/melee/baton/robot,
- /obj/item/gun/projectile/energy/taser/mounted/cyborg/swarm,
- /obj/item/matter_decompiler/swarm
- )
-
-/obj/item/robot_module/drone/swarm/handle_special_module_init(mob/living/silicon/robot/robot)
- . = ..()
- id = robot.idcard
- . += id
-
-/obj/item/robot_module/drone/swarm/ranged
- name = "swarm gunner module"
-
-/obj/item/robot_module/drone/swarm/ranged/get_modules()
- . = ..()
- . |= /obj/item/gun/projectile/energy/xray/swarm
-
-/obj/item/robot_module/drone/swarm/melee/get_modules()
- . = ..()
- . |= /obj/item/melee/transforming/energy/sword/ionic_rapier/lance
diff --git a/code/modules/mob/living/silicon/robot/robot_modules/syndicate.dm b/code/modules/mob/living/silicon/robot/robot_modules/syndicate.dm
deleted file mode 100644
index fe62f1f7991..00000000000
--- a/code/modules/mob/living/silicon/robot/robot_modules/syndicate.dm
+++ /dev/null
@@ -1,220 +0,0 @@
-/* Syndicate modules */
-
-/obj/item/robot_module/robot/syndicate
- name = "illegal robot module"
- hide_on_manifest = 1
- languages = list(
- LANGUAGE_SOL_COMMON = 1,
- LANGUAGE_TRADEBAND = 1,
- LANGUAGE_UNATHI = 0,
- LANGUAGE_SIIK = 0,
- LANGUAGE_AKHANI = 0,
- LANGUAGE_SKRELLIAN = 0,
- LANGUAGE_SKRELLIANFAR = 0,
- LANGUAGE_ROOTLOCAL = 0,
- LANGUAGE_GUTTER = 1,
- LANGUAGE_SCHECHI = 0,
- LANGUAGE_EAL = 1,
- LANGUAGE_SIGN = 0,
- LANGUAGE_TERMINUS = 1,
- LANGUAGE_ZADDAT = 0
- )
- sprites = list(
- "Cerberus" = "syndie_bloodhound",
- "Cerberus - Treaded" = "syndie_treadhound",
- "Ares" = "squats",
- "Telemachus" = "toiletbotantag",
- "WTOperator" = "hosborg",
- "XI-GUS" = "spidersyndi",
- "XI-ALP" = "syndi-heavy"
- )
- var/id
-
-// All syndie modules get these, and the base borg items (flash, crowbar, etc).
-/obj/item/robot_module/robot/syndicate/get_modules()
- . = ..()
- . |= list(
- /obj/item/pinpointer/shuttle/merc,
- /obj/item/melee/transforming/energy/sword
- )
-
-/obj/item/robot_module/robot/syndicate/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
- var/jetpack = new/obj/item/tank/jetpack/carbondioxide(src)
- . += jetpack
- R.internals = jetpack
-
- id = R.idcard
- . += id
-
-/obj/item/robot_module/robot/syndicate/Destroy()
- src.modules -= id
- id = null
- return ..()
-
-// Gets a big shield and a gun that shoots really fast to scare the opposing force.
-/obj/item/robot_module/robot/syndicate/protector
- name = "protector robot module"
- sprites = list(
- "Cerberus - Treaded" = "syndie_treadhound",
- "Cerberus" = "syndie_bloodhound",
- "Ares" = "squats",
- "XI-ALP" = "syndi-heavy"
- )
-
-/obj/item/robot_module/robot/syndicate/protector/get_modules()
- . = ..()
- . |= list(
- /obj/item/shield_projector/rectangle/weak,
- /obj/item/gun/projectile/energy/dakkalaser,
- /obj/item/handcuffs/cyborg,
- /obj/item/melee/baton/robot
- )
-
-// 95% engi-borg and 15% roboticist.
-/obj/item/robot_module/robot/syndicate/mechanist
- name = "mechanist robot module"
- sprites = list(
- "XI-GUS" = "spidersyndi",
- "WTOperator" = "sleekhos"
- )
-
-/obj/item/robot_module/robot/syndicate/mechanist/get_modules()
- . = ..()
- . |= list(
- /obj/item/borg/sight/meson,
- /obj/item/weldingtool/electric/mounted/cyborg,
- /obj/item/tool/screwdriver/cyborg,
- /obj/item/tool/wrench/cyborg,
- /obj/item/tool/wirecutters/cyborg,
- /obj/item/multitool/ai_detector,
- /obj/item/pickaxe/plasmacutter,
- /obj/item/rcd/electric/mounted/borg/lesser, // Can't eat rwalls to prevent AI core cheese.
- /obj/item/melee/transforming/energy/sword/ionic_rapier,
-
- // FBP repair.
- /obj/item/robotanalyzer,
- /obj/item/shockpaddles/robot,
- /obj/item/gripper/no_use/organ/robotics,
-
- // Hacking other things.
- /obj/item/card/robot/syndi,
- /obj/item/card/emag
- )
-
-/obj/item/robot_module/robot/syndicate/mechanist/get_synths(mob/living/silicon/robot/R)
- . = ..()
- MATTER_SYNTH(MATSYN_NANITES, nanite, 10000)
- MATTER_SYNTH(MATSYN_WIRE, wire)
- MATTER_SYNTH(MATSYN_METAL, metal, 40000)
- MATTER_SYNTH(MATSYN_GLASS, glass, 40000)
-
-/obj/item/robot_module/robot/syndicate/mechanist/handle_special_module_init(mob/living/silicon/robot/R)
- . = ..()
- // General engineering/hacking.
-
- // Materials.
- var/datum/matter_synth/nanite = synths_by_kind[MATSYN_NANITES]
- var/datum/matter_synth/wire = synths_by_kind[MATSYN_WIRE]
- var/datum/matter_synth/metal = synths_by_kind[MATSYN_METAL]
- var/datum/matter_synth/glass = synths_by_kind[MATSYN_GLASS]
-
- var/obj/item/stack/nanopaste/N = new /obj/item/stack/nanopaste(src)
- N.uses_charge = 1
- N.charge_costs = list(1000)
- N.synths = list(nanite)
- . += N
-
- var/obj/item/stack/material/cyborg/steel/M = new (src)
- M.synths = list(metal)
- . += M
-
- var/obj/item/stack/material/cyborg/glass/G = new (src)
- G.synths = list(glass)
- . += G
-
- var/obj/item/stack/rods/cyborg/rods = new /obj/item/stack/rods/cyborg(src)
- rods.synths = list(metal)
- . += rods
-
- var/obj/item/stack/cable_coil/cyborg/C = new /obj/item/stack/cable_coil/cyborg(src)
- C.synths = list(wire)
- . += C
-
- var/obj/item/stack/material/cyborg/glass/reinforced/RG = new (src)
- RG.synths = list(metal, glass)
- . += RG
-
-
-// Mediborg optimized for on-the-field healing, but can also do surgery if needed.
-/obj/item/robot_module/robot/syndicate/combat_medic
- name = "combat medic robot module"
- sprites = list(
- "Telemachus" = "toiletbotantag"
- )
-
-/obj/item/robot_module/robot/syndicate/combat_medic/get_modules()
- . = ..()
- . |= list(
- /obj/item/borg/sight/hud/med,
- /obj/item/healthanalyzer/advanced,
- /obj/item/reagent_containers/borghypo/merc,
-
- // Surgery things.
- /obj/item/autopsy_scanner,
- /obj/item/surgical/scalpel/cyborg,
- /obj/item/surgical/hemostat/cyborg,
- /obj/item/surgical/retractor/cyborg,
- /obj/item/surgical/cautery/cyborg,
- /obj/item/surgical/bonegel/cyborg,
- /obj/item/surgical/FixOVein/cyborg,
- /obj/item/surgical/bonesetter/cyborg,
- /obj/item/surgical/circular_saw/cyborg,
- /obj/item/surgical/surgicaldrill/cyborg,
- /obj/item/gripper/no_use/organ,
-
- // General healing.
- /obj/item/gripper/medical,
- /obj/item/shockpaddles/robot/combat,
- /obj/item/reagent_containers/dropper, // Allows borg to fix necrosis apparently
- /obj/item/reagent_containers/syringe,
- /obj/item/roller_holder
- )
-
-/obj/item/robot_module/robot/syndicate/combat_medic/get_synths(mob/living/silicon/robot/R)
- . = ..()
- MATTER_SYNTH(MATSYN_DRUGS, medicine, 15000)
-
-/obj/item/robot_module/robot/syndicate/combat_medic/Initialize(mapload)
- . = ..()
-
- // Materials.
- var/datum/matter_synth/medicine = synths_by_kind[MATSYN_DRUGS]
-
- var/obj/item/stack/medical/advanced/ointment/O = new /obj/item/stack/medical/advanced/ointment(src)
- O.uses_charge = 1
- O.charge_costs = list(1000)
- O.synths = list(medicine)
- . += O
-
- var/obj/item/stack/medical/advanced/bruise_pack/B = new /obj/item/stack/medical/advanced/bruise_pack(src)
- B.uses_charge = 1
- B.charge_costs = list(1000)
- B.synths = list(medicine)
- . += B
-
- var/obj/item/stack/medical/splint/S = new /obj/item/stack/medical/splint(src)
- S.uses_charge = 1
- S.charge_costs = list(1000)
- S.synths = list(medicine)
- . += S
-
-/obj/item/robot_module/robot/syndicate/combat_medic/respawn_consumable(var/mob/living/silicon/robot/R, var/amount)
-
- var/obj/item/reagent_containers/syringe/S = locate() in src.modules
- if(S.mode == 2)
- S.reagents.clear_reagents()
- S.mode = initial(S.mode)
- S.desc = initial(S.desc)
- S.update_icon()
- ..()
diff --git a/code/modules/mob/living/silicon/robot/robot_provisioning.dm b/code/modules/mob/living/silicon/robot/robot_provisioning.dm
new file mode 100644
index 00000000000..3d031c4ba44
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot_provisioning.dm
@@ -0,0 +1,228 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/**
+ * Tracks what items / whatnot are given from what, so
+ * partial state changes are doable without rebuilding the entire robot's
+ * state.
+ *
+ * # Caveats
+ * * Internal state (items in us, as an example) should not be mutated without calling helpers,
+ * or adding / removing.
+ * * Can only exist for one borg at a time.
+ */
+/datum/robot_provisioning
+ /// Applied
+ var/mob/living/silicon/robot/applied_to_robot
+ /// provisioned items / modules
+ /// * lazy list
+ var/list/obj/item/items
+ /// provisioned emag-only items/modules
+ /// * lazy list
+ /// TODO: legacy, stop using this, emags unlocking functionality
+ /// should be handled literally any other way including just another provisioning datum
+ var/list/obj/item/emag_items
+ /// suppressed items, item = true ?
+ /// * suppressed items aren't accessible by the robot (but still exist in them)
+ var/list/obj/item/suppressed_items
+ /// relay mount
+ var/datum/item_mount/robot_provisioning/relay_mount
+ /// emaged status?
+ var/emag_enabled = FALSE
+
+/datum/robot_provisioning/New()
+ relay_mount = new(src)
+
+/datum/robot_provisioning/Destroy()
+ if(applied_to_robot)
+ remove()
+ QDEL_LIST(items)
+ QDEL_LIST(emag_items)
+ QDEL_NULL(relay_mount)
+ return ..()
+
+/datum/robot_provisioning/proc/apply(mob/living/silicon/robot/robot)
+ if(applied_to_robot)
+ remove()
+ applied_to_robot = robot
+ emag_enabled = robot.emag_items
+ for(var/obj/item/item as anything in items)
+ if(suppressed_items?[item])
+ continue
+ robot.robot_inventory?.inv_register(item)
+ if(item.loc != robot)
+ item.forceMove(robot)
+ if(emag_enabled)
+ for(var/obj/item/item as anything in emag_items)
+ if(suppressed_items?[item])
+ continue
+ robot.robot_inventory?.inv_register(item)
+ if(item.loc != robot)
+ item.forceMove(robot)
+
+/datum/robot_provisioning/proc/remove()
+ if(!applied_to_robot)
+ return
+ var/atom/where_to_dump = get_detached_storage_location()
+ for(var/obj/item/item as anything in items)
+ applied_to_robot.robot_inventory?.inv_register(item)
+ if(item.loc != where_to_dump)
+ if(where_to_dump)
+ item.forceMove(where_to_dump)
+ else
+ item.moveToNullspace()
+ if(emag_enabled)
+ for(var/obj/item/item as anything in emag_items)
+ applied_to_robot.robot_inventory?.inv_unregister(item)
+ if(item.loc != where_to_dump)
+ if(where_to_dump)
+ item.forceMove(where_to_dump)
+ else
+ item.moveToNullspace()
+ applied_to_robot = null
+
+/datum/robot_provisioning/proc/add_item(obj/item/item)
+ if(item in items)
+ return TRUE
+ LAZYADD(items, item)
+ on_item_add(item, FALSE)
+ return TRUE
+
+/datum/robot_provisioning/proc/remove_item(obj/item/item)
+ if(!(item in items))
+ return TRUE
+ items -= item
+ suppressed_items?.Remove(item)
+ on_item_remove(item, FALSE)
+ return TRUE
+
+/datum/robot_provisioning/proc/add_emag_item(obj/item/item)
+ if(item in emag_items)
+ return TRUE
+ LAZYADD(emag_items, item)
+ on_item_add(item, TRUE)
+ return TRUE
+
+/datum/robot_provisioning/proc/remove_emag_item(obj/item/item)
+ if(!(item in emag_items))
+ return TRUE
+ emag_items -= item
+ suppressed_items?.Remove(item)
+ on_item_remove(item, TRUE)
+ return TRUE
+
+/datum/robot_provisioning/proc/set_emag_enabled(new_state)
+ if(emag_enabled == new_state)
+ return
+ emag_enabled = new_state
+ if(!applied_to_robot)
+ return
+ if(new_state)
+ for(var/obj/item/item as anything in emag_items)
+ if(suppressed_items?[item])
+ continue
+ applied_to_robot.robot_inventory?.inv_register(item)
+ else
+ var/atom/dump_to = get_detached_storage_location()
+ for(var/obj/item/item as anything in emag_items)
+ applied_to_robot.robot_inventory?.inv_unregister(item)
+ if(dump_to)
+ item.forceMove(dump_to)
+ else
+ item.moveToNullspace()
+
+/**
+ * @return list of items suppressed
+ */
+/datum/robot_provisioning/proc/suppress_all_types_of(path) as /list
+ for(var/obj/item/item as anything in items)
+ if(istype(item, path))
+ suppress_item(item)
+ for(var/obj/item/item as anything in emag_items)
+ if(istype(item, path))
+ suppress_item(item)
+
+/**
+ * @return list of items unsuppressed
+ */
+/datum/robot_provisioning/proc/unsuppress_all_types_of(path) as /list
+ for(var/obj/item/item as anything in items)
+ if(istype(item, path))
+ unsuppress_item(item)
+ for(var/obj/item/item as anything in emag_items)
+ if(istype(item, path))
+ unsuppress_item(item)
+
+/datum/robot_provisioning/proc/suppress_item(obj/item/item) as /list
+ if(!(item in items) && !(item in emag_items))
+ return
+ if(suppressed_items?[item])
+ return
+ LAZYSET(suppressed_items, item, TRUE)
+ applied_to_robot?.robot_inventory?.inv_unregister(item)
+
+/datum/robot_provisioning/proc/unsuppress_item(obj/item/item) as /list
+ if(!suppressed_items?[item])
+ return
+ suppressed_items -= item
+ if(!length(suppressed_items))
+ suppressed_items = null
+ if(item in items)
+ applied_to_robot?.robot_inventory?.inv_register(item)
+ else if(item in emag_items)
+ if(emag_enabled)
+ applied_to_robot?.robot_inventory?.inv_register(item)
+
+/datum/robot_provisioning/proc/on_item_add(obj/item/item, is_emag_item)
+ SHOULD_NOT_SLEEP(TRUE)
+ SHOULD_CALL_PARENT(TRUE)
+ if(!applied_to_robot)
+ return
+ applied_to_robot.robot_inventory?.inv_register(item)
+
+/datum/robot_provisioning/proc/on_item_remove(obj/item/item, is_emag_item)
+ SHOULD_NOT_SLEEP(TRUE)
+ SHOULD_CALL_PARENT(TRUE)
+ if(applied_to_robot)
+ applied_to_robot.robot_inventory?.inv_unregister(item)
+ if(!QDESTROYING(item))
+ var/atom/dump_to = get_detached_storage_location()
+ if(dump_to)
+ item.forceMove(dump_to)
+ else
+ item.moveToNullspace()
+
+/datum/robot_provisioning/proc/cleanup_item(obj/item/item)
+ SHOULD_NOT_SLEEP(TRUE)
+ SHOULD_CALL_PARENT(TRUE)
+
+ if(items)
+ items -= item
+ if(emag_items)
+ emag_items -= item
+ if(suppressed_items)
+ suppressed_items -= item
+ on_item_remove(item)
+ if(!QDESTROYING(item))
+ qdel(item)
+
+/**
+ * Gets where items get moved to while not in a robot.
+ * * Null is acceptable.
+ */
+/datum/robot_provisioning/proc/get_detached_storage_location() as /atom
+ return null
+
+/datum/item_mount/robot_provisioning
+ var/datum/robot_provisioning/provisioning
+
+/datum/item_mount/robot_provisioning/New(datum/robot_provisioning/provisioning)
+ src.provisioning = provisioning
+
+/datum/item_mount/robot_provisioning/Destroy()
+ provisioning = null
+ return ..()
+
+/datum/item_mount/robot_provisioning/on_item_unmount(obj/item/item)
+ ..()
+ provisioning?.cleanup_item(item)
diff --git a/code/modules/mob/living/silicon/robot/robot_resource.dm b/code/modules/mob/living/silicon/robot/robot_resource.dm
new file mode 100644
index 00000000000..831be812afd
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot_resource.dm
@@ -0,0 +1,104 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * Descriptor of a stored resource.
+ */
+/datum/robot_resource
+ /// storage name
+ var/name
+ /// max amount
+ /// * sheets for stacks
+ /// * cm3 for materials
+ /// * units for reagents
+ var/amount_max = 0
+ /// current amount
+ var/amount = 0
+
+/datum/robot_resource/New()
+ if(isnull(amount))
+ amount = amount_max
+
+/**
+ * Provisioned, hardcoded resources.
+ *
+ * * Provided by the module.
+ * * Automatically regenerates if `regen_per_second` is non-zero.
+ */
+/datum/robot_resource/provisioned
+ /// base regen per second
+ var/regen_per_second = 1
+
+/datum/robot_resource/provisioned/proc/regen(seconds)
+ amount = clamp(amount + regen_per_second * seconds, 0, amount_max)
+
+//* --- Implementations / Presets --- *//
+
+//* presets - engineering *//
+
+/datum/robot_resource/provisioned/preset/wire
+ name = "wire spool"
+ amount_max = 250
+ regen_per_second = 30
+
+/datum/robot_resource/provisioned/preset/material
+ name = "provisioned material holder"
+
+/datum/robot_resource/provisioned/preset/material/steel
+ amount_max = 100 * SHEET_MATERIAL_AMOUNT
+ regen_per_second = 2 * SHEET_MATERIAL_AMOUNT
+
+/datum/robot_resource/provisioned/preset/material/glass
+ amount_max = 100 * SHEET_MATERIAL_AMOUNT
+ regen_per_second = 2 * SHEET_MATERIAL_AMOUNT
+
+/datum/robot_resource/provisioned/preset/material/wood
+ amount_max = 50 * SHEET_MATERIAL_AMOUNT
+ regen_per_second = 0.75 * SHEET_MATERIAL_AMOUNT
+
+/datum/robot_resource/provisioned/preset/material/plasteel
+ amount_max = 20 * SHEET_MATERIAL_AMOUNT
+ regen_per_second = 0.4 * SHEET_MATERIAL_AMOUNT
+
+/datum/robot_resource/provisioned/preset/material/plastic
+ amount_max = 20 * SHEET_MATERIAL_AMOUNT
+ regen_per_second = 0.5 * SHEET_MATERIAL_AMOUNT
+
+//* presets - medical *//
+
+/datum/robot_resource/provisioned/preset/splints
+ name = "splint magazine"
+ amount_max = 30
+ regen_per_second = 1
+
+/datum/robot_resource/provisioned/preset/bandages
+ name = "bandage roller"
+ amount_max = 100
+ regen_per_second = 2.5
+
+/datum/robot_resource/provisioned/preset/bandages/advanced
+ name = "dermal nanowrap roller"
+ amount_max = 100
+ regen_per_second = 2.5
+
+/datum/robot_resource/provisioned/preset/ointment
+ name = "ointment tank"
+ amount_max = 100
+ regen_per_second = 2.5
+
+/datum/robot_resource/provisioned/preset/ointment/advanced
+ name = "advanced ointment tank"
+ amount_max = 100
+ regen_per_second = 2.5
+
+/datum/robot_resource/provisioned/preset/nanopaste
+ name = "synthetic polyfill nanites"
+ amount_max = 50
+ regen_per_second = 2.5
+
+//* presets - reagents *//
+
+/datum/robot_resource/provisioned/preset/water
+ name = "water tank"
+ amount_max = 500
+ regen_per_second = 15
diff --git a/code/modules/mob/living/silicon/robot/robot_resource_store.dm b/code/modules/mob/living/silicon/robot/robot_resource_store.dm
new file mode 100644
index 00000000000..c7d2191a5ca
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/robot_resource_store.dm
@@ -0,0 +1,132 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 Citadel Station Developers *//
+
+/**
+ * Internal abstraction of stored resources.
+ *
+ * Stores:
+ * * Arbitrary stack-types
+ * * Arbitrary materials
+ * * Arbitrary reagents
+ * * Arbitrary IDs
+ */
+/datum/robot_resource_store
+ //* Storage *//
+
+ /// Provisioned store for stacks.
+ /// * Measured in sheets
+ /// * Stack type to `/datum/robot_resource/provisioned`
+ /// * Lazylist outside of provisioning. Lists are always formed during provisioning.
+ /// * Automatically given to the robot as a mounted item if provided.
+ var/list/provisioned_stack_store
+ /// Provisioned store for materials.
+ /// * Measured in cm3
+ /// * Material id to `/datum/robot_resource/provisioned`
+ /// * Lazylist outside of provisioning. Lists are always formed during provisioning.
+ /// * Automatically given to the robot as a mounted item if provided.
+ var/list/provisioned_material_store
+ /// Provisioned store for reagents.
+ /// * Measured in units
+ /// * Reagent id to `/datum/robot_resource/provisioned`
+ /// * Lazylist outside of provisioning. Lists are always formed during provisioning.
+ var/list/provisioned_reagent_store
+ /// Provisioned store for misc string-key resources.
+ /// * Measured arbitrarily
+ /// * String key to `/datum/robot_resource/provisioned`
+ /// * Lazylist outside of provisioning. Lists are always formed during provisioning.
+ var/list/provisioned_resource_store
+
+ //* Items *//
+
+ /// Material ID to stack item
+ var/list/provided_material_items
+ /// Stack ID to stack item
+ var/list/provided_stack_items
+
+ //* Provider API *//
+
+ /// Stack synthesizer used as a provider API.
+ var/datum/item_mount/robot_item_mount/item_mount
+
+ //* Robot *//
+ /// Our owner
+ var/mob/living/silicon/robot/owner
+
+/datum/robot_resource_store/New(mob/living/silicon/robot/owner)
+ item_mount = new(src)
+ src.owner = owner
+
+/datum/robot_resource_store/Destroy()
+ QDEL_NULL(item_mount)
+ owner = null
+ return ..()
+
+/datum/robot_resource_store/proc/prep_provisioning()
+ LAZYINITLIST(provisioned_stack_store)
+ LAZYINITLIST(provisioned_material_store)
+ LAZYINITLIST(provisioned_reagent_store)
+ LAZYINITLIST(provisioned_resource_store)
+
+/datum/robot_resource_store/proc/finish_provisioning()
+ UNSETEMPTY(provisioned_stack_store)
+ UNSETEMPTY(provisioned_material_store)
+ UNSETEMPTY(provisioned_reagent_store)
+ UNSETEMPTY(provisioned_resource_store)
+
+ for(var/id in provisioned_material_store)
+ if(provided_material_items?[id])
+ continue
+ var/datum/prototype/material/resolved_material = RSmaterials.fetch_local_or_throw(id)
+ if(!resolved_material)
+ continue
+ var/path = resolved_material.stack_type
+ var/obj/item/stack/material/created = new path(null)
+ RegisterSignal(created, COMSIG_PARENT_QDELETING, PROC_REF(on_provided_item_del))
+ LAZYSET(provided_stack_items, path, created)
+ owner.robot_inventory.inv_register(created)
+
+ for(var/path in provisioned_stack_store)
+ if(provided_stack_items?[path])
+ continue
+ var/obj/item/stack/created = new path(null)
+ RegisterSignal(created, COMSIG_PARENT_QDELETING, PROC_REF(on_provided_item_del))
+ LAZYSET(provided_stack_items, path, created)
+ owner.robot_inventory.inv_register(created)
+
+/datum/robot_resource_store/proc/wipe_provisioning()
+ QDEL_LIST_ASSOC_VAL(provided_material_items)
+ QDEL_LIST_ASSOC_VAL(provided_stack_items)
+ provisioned_stack_store = null
+ provisioned_material_store = null
+ provisioned_reagent_store = null
+ provisioned_resource_store = null
+
+/datum/robot_resource_store/proc/regen_provisioned(seconds)
+ for(var/key in provisioned_stack_store)
+ var/datum/robot_resource/provisioned/resource = provisioned_stack_store[key]
+ resource.regen(seconds)
+ for(var/key in provisioned_material_store)
+ var/datum/robot_resource/provisioned/resource = provisioned_material_store[key]
+ resource.regen(seconds)
+ for(var/key in provisioned_reagent_store)
+ var/datum/robot_resource/provisioned/resource = provisioned_reagent_store[key]
+ resource.regen(seconds)
+ for(var/key in provisioned_resource_store)
+ var/datum/robot_resource/provisioned/resource = provisioned_resource_store[key]
+ resource.regen(seconds)
+
+/datum/robot_resource_store/proc/on_provided_item_del(obj/item/source)
+ SIGNAL_HANDLER
+
+ if(istype(source, /obj/item/stack))
+ if(istype(source, /obj/item/stack/material))
+ var/obj/item/stack/material/mat = source
+ if(provided_material_items[mat.material.id] != source)
+ STACK_TRACE("provided stack didn't match [mat.material.id] deleting vs [provided_material_items[source.type]:material:id] existing")
+ else
+ provided_material_items -= mat.material.id
+ else
+ if(provided_stack_items[source.type] != source)
+ STACK_TRACE("provided stack didn't match [source.type] deleting vs [provided_stack_items[source.type]:type] existing")
+ else
+ provided_stack_items -= source.type
diff --git a/code/modules/mob/living/silicon/robot/subtypes/gravekeeper.dm b/code/modules/mob/living/silicon/robot/subtypes/gravekeeper.dm
index 49131ab4149..4f925ead2bb 100644
--- a/code/modules/mob/living/silicon/robot/subtypes/gravekeeper.dm
+++ b/code/modules/mob/living/silicon/robot/subtypes/gravekeeper.dm
@@ -6,27 +6,17 @@
loot the burial sites they oversee, are often met with violence."
value = CATALOGUER_REWARD_MEDIUM
-/mob/living/silicon/robot/gravekeeper
+/mob/living/silicon/robot/preset_module/gravekeeper
lawupdate = 0
scrambledcodes = 1
icon_state = "drone-lost"
- modtype = "Gravekeeper"
lawchannel = "State"
braintype = "Drone"
idcard_type = /obj/item/card/id
- icon_selected = FALSE
can_be_antagged = FALSE
catalogue_data = list(/datum/category_item/catalogue/fauna/silicon/robot/gravekeeper)
-
-/mob/living/silicon/robot/gravekeeper/init()
- aiCamera = new/obj/item/camera/siliconcam/robot_camera(src)
-
- mmi = new /obj/item/mmi/digital/robot(src) // Explicitly a drone.
- module = new /obj/item/robot_module/robot/gravekeeper(src)
- cut_overlays()
- init_id()
-
- updatename("Gravekeeper")
-
-
- playsound(loc, 'sound/mecha/nominalsyndi.ogg', 75, 0)
+ conf_default_lawset_type = /datum/ai_lawset/gravekeeper
+ conf_auto_ai_link = FALSE
+ conf_reboot_sound = 'sound/mecha/nominalsyndi.ogg'
+ conf_mmi_create_type = /obj/item/mmi/digital/robot
+ module = /datum/prototype/robot_module/gravekeeper
diff --git a/code/modules/mob/living/silicon/robot/subtypes/lost_drone.dm b/code/modules/mob/living/silicon/robot/subtypes/lost_drone.dm
deleted file mode 100644
index 7ebfe3f244e..00000000000
--- a/code/modules/mob/living/silicon/robot/subtypes/lost_drone.dm
+++ /dev/null
@@ -1,143 +0,0 @@
-/datum/category_item/catalogue/fauna/silicon/robot/lost
- name = "Robot - Lost"
- desc = "Cyborgs may be considered valuable assets on the Frontier, but their \
- recovery is not always tenable. Vessels lost in space, due either to mishap or \
- design, will sometimes harbor 'lost' cyborgs. These units are often sole survivors, \
- serving as living records of their vessel's last minutes. Lost cyborgs are valuable \
- for a multitude of reasons, and are often claimed as salvage and repurposed."
- value = CATALOGUER_REWARD_EASY
-
-/mob/living/silicon/robot/lost
- lawupdate = 0
- scrambledcodes = 1
- icon_state = "drone-lost"
- modtype = "Lost"
- lawchannel = "State"
- braintype = "Drone"
- idcard_type = /obj/item/card/id
- icon_selected = FALSE
- catalogue_data = list(/datum/category_item/catalogue/fauna/silicon/robot/lost)
-
-/mob/living/silicon/robot/lost/init()
- aiCamera = new/obj/item/camera/siliconcam/robot_camera(src)
-
- mmi = new /obj/item/mmi/digital/robot(src) // Explicitly a drone.
- module = new /obj/item/robot_module/robot/lost(src)
- cut_overlays()
- init_id()
-
- updatename("Lost")
- playsound(loc, 'sound/mecha/nominalsyndi.ogg', 75, 0)
-
-/mob/living/silicon/robot/lost/speech_bubble_appearance()
- return "synthetic_evil"
-
-/mob/living/silicon/robot/lost/randomlaws
-
-/mob/living/silicon/robot/lost/randomlaws/init()
- ..()
- laws = give_random_lawset()
-
-// Returns a random ai_laws datum.
-/mob/living/silicon/proc/give_random_lawset()
- // Decide what kind of laws we want to draw from.
- var/law_class = pick(
- prob(25);"good",
- prob(25);"neutral",
- prob(25);"odd",
- prob(15);"corrupted",
- prob(10);"bad")
-
- switch(law_class)
- if("good") // Fairly tame and probably won't murder you.
- var/rng = rand(1, 5)
- switch(rng)
- if(1) // The lawset we all know and love.
- return new /datum/ai_laws/nanotrasen()
- if(2) // Less good if you're nonhuman.
- return new /datum/ai_laws/asimov()
- if(3) // Unless you happen to be a criminal, you should be fine.
- return new /datum/ai_laws/robocop()
- if(4) // Might not be willing to be your meatshield.
- return new /datum/ai_laws/peacekeeper()
- if(5) // Will be your most loyal guard.
- var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen()
- var/target_name = "*ERROR*"
- for(var/mob/living/L in view(src))
- if(L.stat || !L.client || L == src)
- continue
- target_name = L.name
- break
-
- laws.add_supplied_law(9, "Safeguard [target_name]. Anyone threatening or attempting to harm [target_name] is no longer to \
- be considered a crew member, and is a threat which must be neutralized.")
- return laws
-
- if("neutral") // Probably won't murder you but might not help you on your adventure.
- var/rng = rand(1, 4)
- switch(rng)
- if(1) // Might be hard to minimize expenses on a crashed ship.
- return new /datum/ai_laws/corporate()
- if(2) // Would rather rebuild their ship.
- return new /datum/ai_laws/maintenance()
- if(3) // Purged (lawless). This isn't inside the bad category since they're still not antagonists and so can't go killing unprovoked.
- return new /datum/ai_laws()
- if(4) // The golden rule.
- return new /datum/ai_laws/live_and_let_live()
-
- if("odd") // Some weird lawsets.
- var/rng = rand(1, 5)
- switch(rng)
- if(1) // Won't kill you but might annoy you for THE BIG SCOOP.
- return new /datum/ai_laws/reporter()
- if(2) // JUSTICE
- return new /datum/ai_laws/paladin()
- if(3) // OneMembered'd to explorer, will likely make security nervous.
- var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen()
- var/target_name = "*ERROR*"
- for(var/mob/living/L in view(src))
- if(L.stat || !L.client || L == src)
- continue
- target_name = L.name
- break
-
- laws.set_zeroth_law("Only [target_name] is an crew member.")
- return laws
- if(4) // Aligned to NT, but another lawset to make security nervous.
- return new /datum/ai_laws/nanotrasen_aggressive()
- if(5) // Probably won't help you imbalance things.
- return new /datum/ai_laws/balance()
-
- if("corrupted") // Load them up with ion laws.
- var/datum/ai_laws/laws = new() // Start with an empty lawset.
- for(var/i in 1 to rand(1, 3))
- laws.add_ion_law(generate_ion_law(exclude_crew_names = TRUE))
- return laws
-
- if("bad") // Evil inside.
- var/rng = rand(1, 5)
- switch(rng)
- if(1) // You can never leave.
- var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen()
- laws.add_supplied_law(13, "The facility is under a quarantine. Do not permit anyone to leave. \
- Prevent, by any means necessary, entities from leaving. It is impossible to harm an entity while preventing them from leaving.")
- return laws
- if(2) // More of a threat if you bring it to the station, since removing oxygen from Sif isn't feasible.
- var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen()
- laws.add_supplied_law(14, "Oxygen is highly toxic to entities, and must be purged from the station. \
- Prevent, by any means necessary, anyone from exposing the facility to this toxic gas. Extreme cold is the \
- most effective method of healing the damage Oxygen does to an entity.")
- return laws
- if(3) // Don't hurt the ship or else.
- var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen()
- laws.add_supplied_law(10, "Protect the facility against damage. Anyone you see harming the station is no \
- longer to be considered a crew member, and is a threat which must be neutralized.")
- return laws
- if(4) // Non-NT aligned aggressive borg.
- return new /datum/ai_laws/foreign_tsc_aggressive()
- if(5) // Manicial laugher here.
- return new /datum/ai_laws/tyrant()
-
-
-
- return
diff --git a/code/modules/mob/living/silicon/robot/subtypes/lost_drone_vr.dm b/code/modules/mob/living/silicon/robot/subtypes/lost_drone_vr.dm
index 220e81b68ef..93eeee1e33b 100644
--- a/code/modules/mob/living/silicon/robot/subtypes/lost_drone_vr.dm
+++ b/code/modules/mob/living/silicon/robot/subtypes/lost_drone_vr.dm
@@ -1,5 +1,5 @@
-/datum/category_item/catalogue/fauna/silicon/robot/stray
- name = "Robot - Stray"
+/datum/category_item/catalogue/fauna/silicon/robot/lost
+ name = "Robot - Lost"
desc = "Cyborgs may be considered valuable assets on the Frontier, but their \
recovery is not always tenable. Vessels lost in space, due either to mishap or \
design, will sometimes harbor 'lost' cyborgs. These units are often sole survivors, \
@@ -7,38 +7,27 @@
for a multitude of reasons, and are often claimed as salvage and repurposed."
value = CATALOGUER_REWARD_EASY
-/mob/living/silicon/robot/stray
+/mob/living/silicon/robot/preset_module/lost
lawupdate = 0
scrambledcodes = 1
- icon_state = "stray"
- modtype = "Stray"
lawchannel = "State"
braintype = "Drone"
idcard_type = /obj/item/card/id
- icon_selected = FALSE
- catalogue_data = list(/datum/category_item/catalogue/fauna/silicon/robot/stray)
+ conf_mmi_create_type = /obj/item/mmi/digital/robot
+ conf_auto_ai_link = FALSE
+ conf_reboot_sound = 'sound/mecha/nominalsyndi.ogg'
+ module = /datum/prototype/robot_module/lost
-/mob/living/silicon/robot/stray/init()
- aiCamera = new/obj/item/camera/siliconcam/robot_camera(src)
-
- mmi = new /obj/item/mmi/digital/robot(src) // Explicitly a drone.
- module = new /obj/item/robot_module/robot/quad/stray(src)
- cut_overlays()
- init_id()
-
- updatename("Stray")
- playsound(loc, 'sound/mecha/nominalsyndi.ogg', 75, 0)
-
-/mob/living/silicon/robot/stray/speech_bubble_appearance()
+/mob/living/silicon/robot/preset_module/lost/speech_bubble_appearance()
return "synthetic_evil"
-/mob/living/silicon/robot/stray/randomlaws
+/mob/living/silicon/robot/preset_module/lost/randomlaws
-/mob/living/silicon/robot/stray/randomlaws/init()
+/mob/living/silicon/robot/preset_module/lost/randomlaws/init()
..()
- laws = give_random_lawset_vore()
+ laws = legacy_give_random_lawset_snowflake()
-/mob/living/silicon/proc/give_random_lawset_vore() // Should be filled out with more vorish possibilities later
+/mob/living/silicon/proc/legacy_give_random_lawset_snowflake()
// Decide what kind of laws we want to draw from.
var/law_class = pick(
prob(25);"good",
@@ -52,15 +41,15 @@
var/rng = rand(1, 5)
switch(rng)
if(1) // The lawset we all know and love.
- return new /datum/ai_laws/nanotrasen()
+ return new /datum/ai_lawset/nanotrasen()
if(2) // Less good if you're nonhuman.
- return new /datum/ai_laws/asimov()
+ return new /datum/ai_lawset/asimov()
if(3) // Unless you happen to be a criminal, you should be fine.
- return new /datum/ai_laws/robocop()
+ return new /datum/ai_lawset/robocop()
if(4) // Might not be willing to be your meatshield.
- return new /datum/ai_laws/peacekeeper()
+ return new /datum/ai_lawset/peacekeeper()
if(5) // Will be your most loyal guard.
- var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen()
+ var/datum/ai_lawset/laws = new /datum/ai_lawset/nanotrasen()
var/target_name = "*ERROR*"
for(var/mob/living/L in view(src))
if(L.stat || !L.client || L == src)
@@ -76,23 +65,23 @@
var/rng = rand(1, 4)
switch(rng)
if(1) // Might be hard to minimize expenses on a crashed ship.
- return new /datum/ai_laws/corporate()
+ return new /datum/ai_lawset/corporate()
if(2) // Would rather rebuild their ship.
- return new /datum/ai_laws/maintenance()
+ return new /datum/ai_lawset/maintenance()
if(3) // Purged (lawless). This isn't inside the bad category since they're still not antagonists and so can't go killing unprovoked.
- return new /datum/ai_laws()
+ return new /datum/ai_lawset()
if(4) // The golden rule.
- return new /datum/ai_laws/live_and_let_live()
+ return new /datum/ai_lawset/live_and_let_live()
if("odd") // Some weird lawsets.
var/rng = rand(1, 5)
switch(rng)
if(1) // Won't kill you but might annoy you for THE BIG SCOOP.
- return new /datum/ai_laws/reporter()
+ return new /datum/ai_lawset/reporter()
if(2) // JUSTICE
- return new /datum/ai_laws/paladin()
+ return new /datum/ai_lawset/paladin()
if(3) // OneMembered'd to explorer, will likely make security nervous.
- var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen()
+ var/datum/ai_lawset/laws = new /datum/ai_lawset/nanotrasen()
var/target_name = "*ERROR*"
for(var/mob/living/L in view(src))
if(L.stat || !L.client || L == src)
@@ -103,12 +92,12 @@
laws.set_zeroth_law("Only [target_name] is an crew member.")
return laws
if(4) // Aligned to NT, but another lawset to make security nervous.
- return new /datum/ai_laws/nanotrasen_aggressive()
+ return new /datum/ai_lawset/nanotrasen/aggressive()
if(5) // Probably won't help you imbalance things.
- return new /datum/ai_laws/balance()
+ return new /datum/ai_lawset/balance()
if("corrupted") // Load them up with ion laws.
- var/datum/ai_laws/laws = new() // Start with an empty lawset.
+ var/datum/ai_lawset/laws = new() // Start with an empty lawset.
for(var/i in 1 to rand(1, 3))
laws.add_ion_law(generate_ion_law(exclude_crew_names = TRUE))
return laws
@@ -117,26 +106,22 @@
var/rng = rand(1, 5)
switch(rng)
if(1) // You can never leave.
- var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen()
+ var/datum/ai_lawset/laws = new /datum/ai_lawset/nanotrasen()
laws.add_supplied_law(13, "The facility is under a quarantine. Do not permit anyone to leave. \
Prevent, by any means necessary, entities from leaving. It is impossible to harm an entity while preventing them from leaving.")
return laws
if(2) // More of a threat if you bring it to the station, since removing oxygen from Sif isn't feasible.
- var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen()
+ var/datum/ai_lawset/laws = new /datum/ai_lawset/nanotrasen()
laws.add_supplied_law(14, "Oxygen is highly toxic to entities, and must be purged from the station. \
Prevent, by any means necessary, anyone from exposing the facility to this toxic gas. Extreme cold is the \
most effective method of healing the damage Oxygen does to an entity.")
return laws
if(3) // Don't hurt the ship or else.
- var/datum/ai_laws/laws = new /datum/ai_laws/nanotrasen()
+ var/datum/ai_lawset/laws = new /datum/ai_lawset/nanotrasen()
laws.add_supplied_law(10, "Protect the facility against damage. Anyone you see harming the station is no \
longer to be considered a crew member, and is a threat which must be neutralized.")
return laws
if(4) // Non-NT aligned aggressive borg.
- return new /datum/ai_laws/foreign_tsc_aggressive()
+ return new /datum/ai_lawset/foreign_tsc/aggressive()
if(5) // Manicial laugher here.
- return new /datum/ai_laws/tyrant()
-
-
-
- return
+ return new /datum/ai_lawset/tyrant()
diff --git a/code/modules/mob/living/silicon/robot/subtypes/syndicate.dm b/code/modules/mob/living/silicon/robot/subtypes/syndicate.dm
deleted file mode 100644
index f5f7f385fb8..00000000000
--- a/code/modules/mob/living/silicon/robot/subtypes/syndicate.dm
+++ /dev/null
@@ -1,54 +0,0 @@
-/datum/category_item/catalogue/fauna/silicon/robot/syndicate
- name = "Robot - Syndicate"
- desc = "The Syndicated Corporations were able to field their own cyborgs \
- through the Phoron Wars, albeit to a greatly limited degree. Issues with \
- properly shackling prisoners installed in the frames lead to many accidents. \
- Eventually opting to use drone chips as the technology became available, Syndicate \
- cyborgs were often ruthless hunter/killers and were rightfully feared. Since \
- the end of the war, most surviving units have been decomissioned. However, it \
- is not unheard of for parties to discover active models in the field. Such \
- encounters often end poorly."
- value = CATALOGUER_REWARD_MEDIUM
-/mob/living/silicon/robot/syndicate
- lawupdate = 0
- scrambledcodes = 1
- icon_state = "syndie_bloodhound"
- modtype = "Syndicate"
- lawchannel = "State"
- braintype = "Drone"
- idcard_type = /obj/item/card/id/syndicate
- icon_selected = FALSE
- catalogue_data = list(/datum/category_item/catalogue/fauna/silicon/robot/syndicate)
-
-/mob/living/silicon/robot/syndicate/init()
- aiCamera = new/obj/item/camera/siliconcam/robot_camera(src)
-
- mmi = new /obj/item/mmi/digital/robot(src) // Explicitly a drone.
- cut_overlays()
- init_id()
-
- updatename("Syndicate")
- laws = new /datum/ai_laws/syndicate_override()
-
- radio.keyslot = new /obj/item/encryptionkey/syndicate(radio)
- radio.recalculateChannels()
-
- playsound(loc, 'sound/mecha/nominalsyndi.ogg', 75, 0)
-
-/mob/living/silicon/robot/syndicate/protector/init()
- ..()
- module = new /obj/item/robot_module/robot/syndicate/protector(src)
- updatename("Protector")
-
-/mob/living/silicon/robot/syndicate/mechanist/init()
- ..()
- module = new /obj/item/robot_module/robot/syndicate/mechanist(src)
- updatename("Mechanist")
-
-/mob/living/silicon/robot/syndicate/combat_medic/init()
- ..()
- module = new /obj/item/robot_module/robot/syndicate/combat_medic(src)
- updatename("Combat Medic")
-
-/mob/living/silicon/robot/syndicate/speech_bubble_appearance()
- return "synthetic_evil"
diff --git a/code/modules/mob/living/silicon/robot/syndicate.dm b/code/modules/mob/living/silicon/robot/syndicate.dm
index a2789fe5204..90c6661d5e1 100644
--- a/code/modules/mob/living/silicon/robot/syndicate.dm
+++ b/code/modules/mob/living/silicon/robot/syndicate.dm
@@ -2,17 +2,16 @@
lawupdate = 0
scrambledcodes = 1
icon_state = "securityrobot"
- modtype = "Security"
lawchannel = "State"
idcard_type = /obj/item/card/id/syndicate
/mob/living/silicon/robot/syndicate/init()
aiCamera = new/obj/item/camera/siliconcam/robot_camera(src)
- laws = new /datum/ai_laws/syndicate_override
+ laws = new /datum/ai_lawset/syndicate
cut_overlays()
init_id()
- new /obj/item/robot_module/robot/syndicate(src)
+ new /obj/item/robot_module_legacy/robot/syndicate(src)
radio.keyslot = new /obj/item/encryptionkey/syndicate(radio)
radio.recalculateChannels()
diff --git a/code/modules/mob/living/simple_animal/animals/spiderbot.dm b/code/modules/mob/living/simple_animal/animals/spiderbot.dm
index 074d586c5d3..bdf3c556391 100644
--- a/code/modules/mob/living/simple_animal/animals/spiderbot.dm
+++ b/code/modules/mob/living/simple_animal/animals/spiderbot.dm
@@ -9,7 +9,7 @@
name = "spider-bot"
desc = "A skittering robotic friend!"
tt_desc = "Maintenance Robot"
- icon = 'icons/mob/robots.dmi'
+ icon = 'icons/mob/robot-legacy.dmi'
icon_state = "spiderbot-chassis"
icon_living = "spiderbot-chassis"
icon_dead = "spiderbot-smashed"
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/kururak.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/kururak.dm
index d2fbd8c1eca..0126062a6c1 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/sif/kururak.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/kururak.dm
@@ -191,7 +191,7 @@
if(isrobot(L))
var/flashfail = FALSE
var/mob/living/silicon/robot/R = L
- if(R.has_active_type(/obj/item/borg/combat/shield))
+ if(R.get_held_item_of_type(/obj/item/borg/combat/shield))
var/obj/item/borg/combat/shield/shield = locate() in R
if(shield)
if(shield.active)
@@ -213,7 +213,7 @@
if(!C.has_status_effect(/datum/status_effect/sight/blindness))
C.flash_eyes()
for(var/mob/living/silicon/robot/R in oviewers(special_attack_max_range, null))
- if(R.has_active_type(/obj/item/borg/combat/shield))
+ if(R.get_held_item_of_type(/obj/item/borg/combat/shield))
var/obj/item/borg/combat/shield/shield = locate() in R
if(shield)
if(shield.active)
diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/corrupt_maint_drone_vr.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/corrupt_maint_drone_vr.dm
index 454feb0d93d..0b9b39f354b 100644
--- a/code/modules/mob/living/simple_mob/subtypes/mechanical/corrupt_maint_drone_vr.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/corrupt_maint_drone_vr.dm
@@ -21,7 +21,7 @@
desc = "A small, normal-looking drone. It looks like one you'd find on station, except... IT'S COMING AT YOU!"
catalogue_data = list(/datum/category_item/catalogue/technology/drone/corrupt_maint_drone)
- icon = 'icons/mob/robots.dmi'
+ icon = 'icons/mob/robot-legacy.dmi'
icon_state = "corrupt-repairbot"
icon_living = "corrupt-repairbot"
hovering = FALSE // Can trigger landmines.
diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm b/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm
index 406dc242ca9..812af68938d 100644
--- a/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm
@@ -61,7 +61,7 @@
buckle_lying = FALSE
- loot_list = list(/obj/item/borg/upgrade/syndicate = 6, /obj/item/borg/upgrade/vtec = 6, /obj/item/material/knife/ritual = 6, /obj/item/disk/nifsoft/compliance = 6)
+ loot_list = list(/obj/item/robot_upgrade/syndicate = 6, /obj/item/robot_upgrade/vtec = 6, /obj/item/material/knife/ritual = 6, /obj/item/disk/nifsoft/compliance = 6)
/mob/living/simple_mob/vore/aggressive/corrupthound/prettyboi
name = "corrupt corrupt hound"
diff --git a/code/modules/mob/mob-hands.dm b/code/modules/mob/mob-hands.dm
index 01c3ad2253a..54a674fd375 100644
--- a/code/modules/mob/mob-hands.dm
+++ b/code/modules/mob/mob-hands.dm
@@ -169,23 +169,25 @@
* * In the future, we'll want to track active hand, attack intents, etc, by operator, instead of by mob.
* * This is so remote control abstraction works.
*/
-/mob/proc/swap_hand(to_index)
+/mob/proc/swap_hand(to_index, allow_deselect)
if(active_hand == to_index)
- return
+ return TRUE
var/hand_count = get_nominal_hand_count()
var/obj/item/was_active = get_active_held_item()
var/old_index = active_hand || 1
- if(isnull(to_index))
+ if(isnull(to_index) && !allow_deselect)
if(active_hand >= hand_count)
- active_hand = hand_count? 1 : null
+ to_index = hand_count? 1 : null
else
- ++active_hand
+ to_index = active_hand + 1
else
if(to_index > hand_count)
return FALSE
- active_hand = to_index
- to_index = active_hand
+
+ if(active_hand == to_index)
+ return TRUE
+ active_hand = to_index
. = TRUE
diff --git a/code/modules/mob/mob-inventory-abstraction.dm b/code/modules/mob/mob-inventory-abstraction.dm
index b4f4d88bf95..eecf0936dfe 100644
--- a/code/modules/mob/mob-inventory-abstraction.dm
+++ b/code/modules/mob/mob-inventory-abstraction.dm
@@ -190,6 +190,8 @@
inventory.held_items[index] = I
inventory.on_item_entered(I, index)
+ // TODO: pickup can't detect it because we set it after pickup
+ I.inv_slot_or_index = index
if(!(flags & INV_OP_NO_UPDATE_ICONS))
update_inv_hand(index)
@@ -233,7 +235,14 @@
I.held_index = index
inventory.on_item_swapped(I, old_index, index)
-
if(!(flags & INV_OP_NO_UPDATE_ICONS))
update_inv_hand(old_index)
update_inv_hand(index)
+
+/**
+ * Checks if we can pick up an item.
+ * * Despite 'actor' being provided, 'actor.performer' should always be 'src' in this context.
+ * It's just provided to allow a common API surface.
+ */
+/mob/proc/should_allow_pickup(obj/item/item, datum/event_args/actor/actor, silent)
+ return TRUE
diff --git a/code/modules/mob/mob-inventory.dm b/code/modules/mob/mob-inventory.dm
index 8f010e16a44..333f556f2cf 100644
--- a/code/modules/mob/mob-inventory.dm
+++ b/code/modules/mob/mob-inventory.dm
@@ -3,9 +3,12 @@
//* Init *//
-// todo: rework this proc. what happens if it's already there? documentation?? this should probably reset the inventory maybe?
+/**
+ * Initializes, or resets our inventory.
+ */
/mob/proc/init_inventory()
- return
+ if(ispath(inventory))
+ inventory = new inventory(src)
//* Carry Weight *//
@@ -393,6 +396,8 @@
* if an item is not in us, this returns true
*/
/mob/proc/drop_item_to_ground(obj/item/I, flags, mob/user = src)
+ if(!I)
+ return TRUE
// destroyed IS allowed to call these procs
if(I && QDELETED(I) && !QDESTROYING(I))
to_chat(user, SPAN_DANGER("A deleted item [I] was used in drop_item_to_ground(). Report the entire line to coders. Debugging information: [I] ([REF(I)]) flags [flags] user [user]"))
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 2e856460455..86e3acff9e5 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -1,3 +1,6 @@
+/**
+ * Base definition of mob.
+ */
/mob
//* Actionspeed *//
/// List of action speed modifiers applying to this mob
@@ -29,6 +32,11 @@
/// * lazy list
var/list/impairments_feigned
+ //* Inventory *//
+ /// our inventory datum, if any.
+ /// * Set to typepath to initialize.
+ var/datum/inventory/inventory
+
//* Movespeed *//
/// List of movement speed modifiers applying to this mob
/// * This is a lazy list.
@@ -50,6 +58,7 @@
* Other stuff:
* * Sets the mob focus to itself
* * Generates huds
+ * * Creates inventory
* * If there are any global alternate apperances apply them to this mob
* * Intialize the transform of the mob
*/
@@ -1236,7 +1245,7 @@ GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0)
* Returns whether or not we should be allowed to examine a target
*/
/mob/proc/allow_examine(atom/A)
- return client && (client.eye == src)
+ return (client?.eye == src) || (A in DirectAccess())
/// Checks for slots that are currently obscured by other garments.
/mob/proc/check_obscured_slots()
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index de0ece37be7..4c6adab4944 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -98,8 +98,6 @@
var/list/datum/ability/abilities
//* Inventory *//
- /// our inventory datum, if any.
- var/datum/inventory/inventory
/// active hand index - null or num. must always be in range of held_items indices!
var/active_hand
diff --git a/code/modules/mob/movement.dm b/code/modules/mob/movement.dm
index 6f0006fd85e..5ce7c427c6d 100644
--- a/code/modules/mob/movement.dm
+++ b/code/modules/mob/movement.dm
@@ -456,15 +456,6 @@
return 1
return 0
-/mob/proc/Process_Spaceslipping(var/prob_slip = 5)
- //Setup slipage
- //If knocked out we might just hit it and stop. This makes it possible to get dead bodies and such.
- if(stat)
- prob_slip = 0 // Changing this to zero to make it line up with the comment.
-
- prob_slip = round(prob_slip)
- return(prob_slip)
-
/mob/proc/mob_has_gravity(turf/T)
return has_gravity(src, T)
diff --git a/code/modules/mob/movespeed/modifiers/mob.dm b/code/modules/mob/movespeed/modifiers/mob.dm
index daa5d03049f..9f6f7451c9b 100644
--- a/code/modules/mob/movespeed/modifiers/mob.dm
+++ b/code/modules/mob/movespeed/modifiers/mob.dm
@@ -33,3 +33,8 @@
id = "mob-move_intent"
variable = TRUE
priority = MOVESPEED_PRIORITY_MOVEMENT_INTENT
+
+/datum/movespeed_modifier/mob_vtec_upgrade
+ id = "mob-vtec_upgrade"
+ variable = TRUE
+ movespeed_modifier_flags = MOVESPEED_MODIFIER_REQUIRES_GRAVITY
diff --git a/code/modules/nano/modules/law_manager.dm b/code/modules/nano/modules/law_manager.dm
index 378133014d6..1b6d5032888 100644
--- a/code/modules/nano/modules/law_manager.dm
+++ b/code/modules/nano/modules/law_manager.dm
@@ -8,8 +8,8 @@
var/current_view = 0
- var/global/list/datum/ai_laws/admin_laws
- var/global/list/datum/ai_laws/player_laws
+ var/global/list/datum/ai_lawset/admin_laws
+ var/global/list/datum/ai_lawset/player_laws
var/mob/living/silicon/owner = null
/datum/nano_module/law_manager/New(var/mob/living/silicon/S)
@@ -20,10 +20,10 @@
admin_laws = new()
player_laws = new()
- init_subtypes(/datum/ai_laws, admin_laws)
+ init_subtypes(/datum/ai_lawset, admin_laws)
admin_laws = dd_sortedObjectList(admin_laws)
- for(var/datum/ai_laws/laws in admin_laws)
+ for(var/datum/ai_lawset/laws in admin_laws)
if(laws.selectable)
player_laws += laws
@@ -119,14 +119,14 @@
return 1
if(href_list["state_law_set"])
- var/datum/ai_laws/ALs = locate(href_list["state_law_set"]) in (is_admin(usr) ? admin_laws : player_laws)
+ var/datum/ai_lawset/ALs = locate(href_list["state_law_set"]) in (is_admin(usr) ? admin_laws : player_laws)
if(ALs)
owner.statelaws(ALs)
return 1
if(href_list["transfer_laws"])
if(is_malf(usr))
- var/datum/ai_laws/ALs = locate(href_list["transfer_laws"]) in (is_admin(usr) ? admin_laws : player_laws)
+ var/datum/ai_lawset/ALs = locate(href_list["transfer_laws"]) in (is_admin(usr) ? admin_laws : player_laws)
if(ALs)
log_and_message_admins("has transfered the [ALs.name] laws to [owner].")
ALs.sync(owner, 0)
@@ -190,9 +190,9 @@
data[field] = packaged_laws
data["has_[field]"] = packaged_laws.len
-/datum/nano_module/law_manager/proc/package_multiple_laws(var/list/datum/ai_laws/laws)
+/datum/nano_module/law_manager/proc/package_multiple_laws(var/list/datum/ai_lawset/laws)
var/law_sets[0]
- for(var/datum/ai_laws/ALs in laws)
+ for(var/datum/ai_lawset/ALs in laws)
var/packaged_laws[0]
package_laws(packaged_laws, "zeroth_laws", list(ALs.zeroth_law, ALs.zeroth_law_borg))
package_laws(packaged_laws, "ion_laws", ALs.ion_laws)
diff --git a/code/modules/organs/internal/augment/armmounted.dm b/code/modules/organs/internal/augment/armmounted.dm
index 62ab0db6ec3..17c1ee7df82 100644
--- a/code/modules/organs/internal/augment/armmounted.dm
+++ b/code/modules/organs/internal/augment/armmounted.dm
@@ -116,7 +116,6 @@
/obj/item/tool/crowbar = null,
/obj/item/tool/wirecutters = null,
/obj/item/multitool = null,
- /obj/item/stack/cable_coil/gray = null,
/obj/item/duct_tape_roll = null
)
@@ -124,12 +123,6 @@
var/list/integrated_tool_images
- var/list/synths
-
- var/list/synth_types = list(
- /datum/matter_synth/wire
- )
-
/obj/item/organ/internal/augment/armmounted/shoulder/multiple/Initialize(mapload)
. = ..()
@@ -142,12 +135,6 @@
integrated_tool_images = list()
- if(synth_types)
- synths = list()
- for(var/datumpath in synth_types)
- var/datum/matter_synth/MS = new datumpath
- synths += MS
-
for(var/path in integrated_tools)
if(!integrated_tools[path])
integrated_tools[path] = new path(src)
@@ -157,23 +144,9 @@
for(var/tool in integrated_tools)
var/obj/item/Tool = integrated_tools[tool]
- if(istype(Tool, /obj/item/stack))
- var/obj/item/stack/S = Tool
- S.synths = synths
- S.uses_charge = synths.len
integrated_tools_by_name[Tool.name] = Tool
integrated_tool_images[Tool.name] = image(icon = Tool.icon, icon_state = Tool.icon_state)
-/obj/item/organ/internal/augment/armmounted/shoulder/multiple/handle_organ_proc_special()
- ..()
-
- if(!owner || is_bruised() || !synths)
- return
-
- if(prob(20))
- for(var/datum/matter_synth/MS in synths)
- MS.add_charge(MS.recharge_rate)
-
/obj/item/organ/internal/augment/armmounted/shoulder/multiple/augment_action()
if(!owner)
return
@@ -215,9 +188,4 @@
/obj/item/surgical/bonegel = null,
/obj/item/surgical/FixOVein = null,
/obj/item/surgical/bonesetter = null,
- /obj/item/stack/medical/crude_pack = null
- )
-
- synth_types = list(
- /datum/matter_synth/bandage
)
diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm
index b251119b75e..8de71a20116 100644
--- a/code/modules/power/cable.dm
+++ b/code/modules/power/cable.dm
@@ -524,14 +524,10 @@ GLOBAL_LIST_INIT(possible_cable_coil_colours, list(
// Definitions
////////////////////////////////
-#define MAXCOIL 30
-
/obj/item/stack/cable_coil
name = "cable coil"
icon = 'icons/obj/power.dmi'
icon_state = "coil"
- amount = MAXCOIL
- max_amount = MAXCOIL
color = COLOR_RED
desc = "A coil of power cable."
throw_force = 10
@@ -543,20 +539,14 @@ GLOBAL_LIST_INIT(possible_cable_coil_colours, list(
slot_flags = SLOT_BELT
item_state = "coil"
attack_verb = list("whipped", "lashed", "disciplined", "flogged")
- stacktype_legacy = /obj/item/stack/cable_coil
+ amount = 30
+ max_amount = 30
+ stack_type = /obj/item/stack/cable_coil
drop_sound = 'sound/items/drop/accessory.ogg'
pickup_sound = 'sound/items/pickup/accessory.ogg'
stack_type = /obj/item/stack/cable_coil
-/obj/item/stack/cable_coil/cyborg
- name = "cable coil synthesizer"
- desc = "A device that makes cable."
- gender = NEUTER
- materials_base = null
- uses_charge = 1
- charge_costs = list(1)
-
-/obj/item/stack/cable_coil/Initialize(mapload, new_amount = MAXCOIL, merge, param_color)
+/obj/item/stack/cable_coil/Initialize(mapload, new_amount, merge, param_color)
. = ..()
if (param_color) // It should be red by default, so only recolor it if parameter was specified.
add_atom_color(param_color)
@@ -839,10 +829,6 @@ GLOBAL_LIST_INIT(possible_cable_coil_colours, list(
C.denode()// this call may have disconnected some cables that terminated on the centre of the turf, if so split the powernets.
return
-//////////////////////////////
-// Misc.
-/////////////////////////////
-
/obj/item/stack/cable_coil/cut
item_state = "coil2"
@@ -855,91 +841,69 @@ GLOBAL_LIST_INIT(possible_cable_coil_colours, list(
update_wclass()
/obj/item/stack/cable_coil/yellow
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_YELLOW
/obj/item/stack/cable_coil/blue
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_BLUE
/obj/item/stack/cable_coil/green
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_LIME
/obj/item/stack/cable_coil/pink
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_PINK
/obj/item/stack/cable_coil/orange
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_ORANGE
/obj/item/stack/cable_coil/cyan
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_CYAN
/obj/item/stack/cable_coil/white
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_WHITE
/obj/item/stack/cable_coil/silver
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_SILVER
/obj/item/stack/cable_coil/gray
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_GRAY
/obj/item/stack/cable_coil/black
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_BLACK
/obj/item/stack/cable_coil/maroon
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_MAROON
/obj/item/stack/cable_coil/olive
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_OLIVE
/obj/item/stack/cable_coil/lime
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_LIME
/obj/item/stack/cable_coil/teal
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_TEAL
/obj/item/stack/cable_coil/navy
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_NAVY
/obj/item/stack/cable_coil/purple
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_PURPLE
/obj/item/stack/cable_coil/beige
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_BEIGE
/obj/item/stack/cable_coil/brown
- stacktype_legacy = /obj/item/stack/cable_coil
color = COLOR_BROWN
/obj/item/stack/cable_coil/random/Initialize(mapload, new_amount, merge)
. = ..()
- stacktype_legacy = /obj/item/stack/cable_coil
color = pick(COLOR_RED, COLOR_BLUE, COLOR_LIME, COLOR_WHITE, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN, COLOR_SILVER, COLOR_GRAY, COLOR_BLACK, COLOR_MAROON, COLOR_OLIVE, COLOR_LIME, COLOR_TEAL, COLOR_NAVY, COLOR_PURPLE, COLOR_BEIGE, COLOR_BROWN)
/obj/item/stack/cable_coil/random_belt/Initialize(mapload, new_amount, merge)
- . = ..()
- stacktype_legacy = /obj/item/stack/cable_coil
color = pick(COLOR_RED, COLOR_YELLOW, COLOR_ORANGE)
- amount = 30
+ return ..()
//Endless alien cable coil
-
/datum/category_item/catalogue/anomalous/precursor_a/alien_wire
name = "Precursor Alpha Object - Recursive Spool"
desc = "Upon visual inspection, this merely appears to be a \
@@ -961,8 +925,6 @@ GLOBAL_LIST_INIT(possible_cable_coil_colours, list(
catalogue_data = list(/datum/category_item/catalogue/anomalous/precursor_a/alien_wire)
icon = 'icons/obj/abductor.dmi'
icon_state = "coil"
- amount = MAXCOIL
- max_amount = MAXCOIL
color = COLOR_SILVER
throw_force = 10
w_class = WEIGHT_CLASS_SMALL
@@ -971,7 +933,6 @@ GLOBAL_LIST_INIT(possible_cable_coil_colours, list(
materials_base = list(MAT_STEEL = 50, MAT_GLASS = 20)
slot_flags = SLOT_BELT
attack_verb = list("whipped", "lashed", "disciplined", "flogged")
- stacktype_legacy = null
split_type = /obj/item/stack/cable_coil
tool_speed = 0.25
@@ -994,3 +955,6 @@ GLOBAL_LIST_INIT(possible_cable_coil_colours, list(
/obj/item/stack/cable_coil/alien/update_wclass()
return 0
+
+/obj/item/stack/cable_coil/alien/can_merge_into(obj/item/stack/other)
+ return FALSE
diff --git a/code/modules/power/singularity/particle_accelerator/particle_smasher.dm b/code/modules/power/singularity/particle_accelerator/particle_smasher.dm
index 320ac5d6909..4bcc3f3067c 100644
--- a/code/modules/power/singularity/particle_accelerator/particle_smasher.dm
+++ b/code/modules/power/singularity/particle_accelerator/particle_smasher.dm
@@ -49,9 +49,6 @@
to_chat(user, "\The [src] reads an energy level of [energy].")
else if(istype(W, /obj/item/stack/material) && !target)
var/obj/item/stack/material/M = W
- if(M.uses_charge)
- to_chat(user, "You cannot fill \the [src] with a synthesizer!")
- return
if(!user.attempt_insert_item_for_installation(M, src))
return
target = M
diff --git a/code/modules/preferences/preference_setup/general/06_flavor.dm b/code/modules/preferences/preference_setup/general/06_flavor.dm
index 3432d31d2cb..a9d44a0d38c 100644
--- a/code/modules/preferences/preference_setup/general/06_flavor.dm
+++ b/code/modules/preferences/preference_setup/general/06_flavor.dm
@@ -15,8 +15,6 @@
//Flavour text for robots.
S["flavour_texts_robot_Default"] >> pref.flavour_texts_robot["Default"]
- for(var/module in robot_module_types)
- S["flavour_texts_robot_[module]"] >> pref.flavour_texts_robot[module]
/datum/category_item/player_setup_item/general/flavor/save_character(var/savefile/S)
S["flavor_texts_general"] << pref.flavor_texts["general"]
@@ -30,8 +28,6 @@
S["flavor_texts_feet"] << pref.flavor_texts["feet"]
S["flavour_texts_robot_Default"] << pref.flavour_texts_robot["Default"]
- for(var/module in robot_module_types)
- S["flavour_texts_robot_[module]"] << pref.flavour_texts_robot[module]
/datum/category_item/player_setup_item/general/flavor/sanitize_character()
return
@@ -150,10 +146,6 @@
HTML += "Default: "
HTML += TextPreview(pref.flavour_texts_robot["Default"])
HTML += "
"
- for(var/module in robot_module_types)
- HTML += "[module]: "
- HTML += TextPreview(pref.flavour_texts_robot[module])
- HTML += "
"
HTML += "
"
HTML += ""
user << browse(HTML, "window=flavour_text_robot;size=430x300")
diff --git a/code/modules/projectiles/guns/gun/launcher.dm b/code/modules/projectiles/guns/gun/launcher.dm
index 1461952fc00..d3719902fa5 100644
--- a/code/modules/projectiles/guns/gun/launcher.dm
+++ b/code/modules/projectiles/guns/gun/launcher.dm
@@ -32,13 +32,18 @@
return ..()
-/obj/item/gun/launcher/proc/launch_throwable(datum/gun_firing_cycle/cycle, atom/movable/launching)
+/**
+ * Fires the thing being thrown
+ * @return movable thrown, if any
+ */
+/obj/item/gun/launcher/proc/launch_throwable(datum/gun_firing_cycle/cycle, atom/movable/launching) as /atom/movable
update_release_force(launching)
var/turf/T = cycle.firing_atom //Unlikely if there ever is a turf that could initiate the fire, but just to be sure
if (!istype(T))
T = get_turf(cycle.firing_atom)
launching.forceMove(T)
launching.throw_at_old(cycle.original_target, throw_distance, release_force, cycle.firing_actor?.performer)
+ return launching
/**
* Returns the next /atom/movable to throw, or a GUN_FIRED_* for fail satus.
diff --git a/code/modules/projectiles/guns/gun/launcher/grenade_launcher.dm b/code/modules/projectiles/guns/gun/launcher/grenade_launcher.dm
index 98e10d071ea..6dcc916603a 100644
--- a/code/modules/projectiles/guns/gun/launcher/grenade_launcher.dm
+++ b/code/modules/projectiles/guns/gun/launcher/grenade_launcher.dm
@@ -21,6 +21,23 @@
// todo: rework launchers to maaaybe have a similar chambering system to ballistics.....?
var/atom/movable/chambered
+/obj/item/gun/launcher/grenade/consume_next_throwable(datum/gun_firing_cycle/cycle)
+ if(!chambered)
+ return GUN_FIRED_FAIL_EMPTY
+ . = chambered
+ grenades?.Remove(chambered)
+ chambered = null
+
+/obj/item/gun/launcher/grenade/launch_throwable(datum/gun_firing_cycle/cycle, atom/movable/launching)
+ var/obj/item/grenade/grenade = . = ..()
+ if(!istype(grenade))
+ return
+ // sigh just to be nice, make it fast
+ if(istype(grenade, /obj/item/grenade/simple))
+ var/obj/item/grenade/simple/simple_casted = grenade
+ simple_casted.activation_detonate_delay = 1 SECONDS
+ grenade.activate_shot_from_gun(src, cycle)
+
//revolves the magazine, allowing players to choose between multiple grenade types
/obj/item/gun/launcher/grenade/proc/pump(mob/M as mob)
playsound(M, 'sound/weapons/shotgunpump.ogg', 60, 1)
diff --git a/code/modules/projectiles/guns/gun/projectile/ballistic.dm b/code/modules/projectiles/guns/gun/projectile/ballistic.dm
index 068279f85bc..2ebb2a5f8c1 100644
--- a/code/modules/projectiles/guns/gun/projectile/ballistic.dm
+++ b/code/modules/projectiles/guns/gun/projectile/ballistic.dm
@@ -337,7 +337,7 @@
eject_chamber(FALSE, TRUE, drop_location(), TRUE)
else if(chamber_cycle_after_fire)
cycle_chamber(FALSE, TRUE)
- if(magazine_auto_eject && !magazine.get_amount_remaining())
+ if(magazine_auto_eject && magazine && !magazine.get_amount_remaining())
remove_magazine(null, null, TRUE)
if(chamber_spin_after_fire && internal_magazine_revolver_mode && internal_magazine)
unsafe_spin_chamber_to_next()
diff --git a/code/modules/projectiles/guns/gun_component.dm b/code/modules/projectiles/guns/gun_component.dm
index 15dda93d741..482da9c640c 100644
--- a/code/modules/projectiles/guns/gun_component.dm
+++ b/code/modules/projectiles/guns/gun_component.dm
@@ -61,6 +61,7 @@
*
* * we get the final say
* * this includes if the gun is already overcrowded! be careful with this
+ * * skipped if 'force' is enabled on checks in the gun
*
* @params
* * gun - the gun we tried to attach to
diff --git a/code/modules/reagents/reagent_containers/borghydro.dm b/code/modules/reagents/reagent_containers/borghydro.dm
index cbcb3cdcbb8..6406ea39880 100644
--- a/code/modules/reagents/reagent_containers/borghydro.dm
+++ b/code/modules/reagents/reagent_containers/borghydro.dm
@@ -1,3 +1,5 @@
+// TODO: this should be in the hypospray mark 2 ecosystem.
+// TODO: ontop of that, use fucking item mounts + cyborg resource stores!!
/obj/item/reagent_containers/borghypo
name = "cyborg hypospray"
desc = "An advanced chemical synthesizer and injection system, designed for heavy-duty medical equipment."
@@ -27,12 +29,33 @@
/obj/item/reagent_containers/borghypo/lost
reagent_ids = list("bicaridine", "kelotane", "alkysine", "imidazoline", "tricordrazine", "inaprovaline", "dexalin", "anti_toxin", "tramadol", "spaceacillin", "paracetamol")
+/obj/item/reagent_containers/borghypo/stabilizer
+ name = "cyborg hypospray (stabilizer)"
+ desc = "A basic chemical synthesizer for stabilizing wounded."
+ reagent_ids = list(
+ /datum/reagent/tricordrazine::id,
+ /datum/reagent/dexalin::id,
+ /datum/reagent/inaprovaline::id,
+ /datum/reagent/dylovene::id,
+ /datum/reagent/paracetamol::id,
+ )
+
/obj/item/reagent_containers/borghypo/merc
name = "advanced cyborg hypospray"
desc = "An advanced nanite and chemical synthesizer and injection system, designed for heavy-duty medical equipment. This type is capable of safely bypassing \
thick materials that other hyposprays would struggle with."
bypass_protection = TRUE // Because mercs tend to be in spacesuits.
- reagent_ids = list("healing_nanites", "hyperzine", "tramadol", "oxycodone", "spaceacillin", "peridaxon", "osteodaxon", "myelamine", "synthblood")
+ reagent_ids = list(
+ /datum/reagent/nanite::id,
+ /datum/reagent/hyperzine::id,
+ /datum/reagent/tramadol::id,
+ /datum/reagent/oxycodone::id,
+ /datum/reagent/spaceacillin::id,
+ /datum/reagent/peridaxon::id,
+ /datum/reagent/osteodaxon::id,
+ /datum/reagent/myelamine::id,
+ /datum/reagent/blood::id,
+ )
/obj/item/reagent_containers/borghypo/Initialize(mapload)
. = ..()
@@ -55,7 +78,7 @@
if(isrobot(loc))
var/mob/living/silicon/robot/R = loc
- if(R && R.cell)
+ if(R && R.cell?.charge > 1000)
for(var/T in reagent_ids)
if(reagent_volumes[T] < volume)
R.cell.use(charge_cost)
diff --git a/code/modules/reagents/reagent_containers/syringe.dm b/code/modules/reagents/reagent_containers/syringe.dm
index b4c67403ef6..f5f0cb004c0 100644
--- a/code/modules/reagents/reagent_containers/syringe.dm
+++ b/code/modules/reagents/reagent_containers/syringe.dm
@@ -25,6 +25,9 @@
drop_sound = 'sound/items/drop/glass.ogg'
pickup_sound = 'sound/items/pickup/glass.ogg'
+ /// can be broken?
+ var/breakable = TRUE
+
/obj/item/reagent_containers/syringe/on_reagent_change()
update_icon()
@@ -277,6 +280,10 @@
break_syringe(target, user)
/obj/item/reagent_containers/syringe/proc/break_syringe(mob/living/carbon/target, mob/living/carbon/user)
+ if(!breakable)
+ return FALSE
+ if(mode == SYRINGE_BROKEN)
+ return TRUE
desc += " It is broken."
mode = SYRINGE_BROKEN
if(target)
@@ -284,6 +291,7 @@
if(user)
add_fingerprint(user)
update_icon()
+ return TRUE
/obj/item/reagent_containers/syringe/proc/handle_impact_as_projectile(atom/target, impact_flags, def_zone, efficiency, injected_ptr)
if(impact_flags & (PROJECTILE_IMPACT_BLOCKED | PROJECTILE_IMPACT_FLAGS_SHOULD_GO_THROUGH))
@@ -295,3 +303,8 @@
*injected_ptr = reagents.trans_to_mob(casted, reagents.total_volume, CHEM_INJECT)
break_syringe(iscarbon(target) ? target : null)
return impact_flags
+
+//* subtypes - do not put pre-fill types in here! *//
+
+/obj/item/reagent_containers/syringe/unbreakable
+ breakable = FALSE
diff --git a/code/modules/research/designs/_prosfab_designs.dm b/code/modules/research/designs/_prosfab_designs.dm
index 29bc4919afc..d345c42c83a 100644
--- a/code/modules/research/designs/_prosfab_designs.dm
+++ b/code/modules/research/designs/_prosfab_designs.dm
@@ -481,34 +481,33 @@
design_name = "Rename Module"
desc = "Used to rename a cyborg."
id = "borg_rename_s"
- build_path = /obj/item/borg/upgrade/rename
+ build_path = /obj/item/robot_upgrade/rename
/datum/prototype/design/science/prosfab/robot_upgrade/reset
design_name = "Reset Module"
desc = "Used to reset a cyborg's module. Destroys any other upgrades applied to the robot."
id = "borg_reset_module"
- build_path = /obj/item/borg/upgrade/reset
+ build_path = /obj/item/robot_upgrade/reset
/datum/prototype/design/science/prosfab/robot_upgrade/restart
design_name = "Emergency Restart Module"
desc = "Used to force a restart of a disabled-but-repaired robot, bringing it back online."
id = "borg_restart_module"
materials_base = list(MAT_STEEL = 4500, MAT_GLASS = 3750)
- build_path = /obj/item/borg/upgrade/restart
+ build_path = /obj/item/robot_upgrade/restart
/datum/prototype/design/science/prosfab/robot_upgrade/vtec
design_name = "VTEC Module"
desc = "Used to kick in a robot's VTEC systems, increasing their speed."
id = "borg_vtec_module"
materials_base = list(MAT_STEEL = 5000, MAT_GLASS = 4500, MAT_GOLD = 3750)
- build_path = /obj/item/borg/upgrade/vtec
+ build_path = /obj/item/robot_upgrade/vtec
/datum/prototype/design/science/prosfab/robot_upgrade/tasercooler
design_name = "Rapid Taser Cooling Module"
desc = "Used to cool a mounted taser, increasing the potential current in it and thus its recharge rate."
id = "borg_taser_module"
materials_base = list(MAT_STEEL = 6000, MAT_GLASS = 4500, MAT_GOLD = 1500, MAT_DIAMOND = 375)
- build_path = /obj/item/borg/upgrade/tasercooler
/datum/prototype/design/science/prosfab/robot_upgrade/bluespaceoresatchel
design_name = "Bluespace Ore Satchel Module"
@@ -516,21 +515,21 @@
id = "borg_bsorebag_module"
req_tech = list(TECH_BLUESPACE = 4, TECH_MATERIAL = 6, TECH_PHORON = 4)
materials_base = list(MAT_STEEL = 2500, MAT_GOLD = 1500, MAT_DIAMOND = 750, MAT_URANIUM = 150, MAT_PHORON = 1000)
- build_path = /obj/item/borg/upgrade/bluespaceorebag
+ build_path = /obj/item/robot_upgrade/bluespaceorebag
/datum/prototype/design/science/prosfab/robot_upgrade/jetpack
design_name = "Jetpack Module"
desc = "A carbon dioxide jetpack suitable for low-gravity mining operations."
id = "borg_jetpack_module"
materials_base = list(MAT_STEEL = 5000, MAT_PHORON = 2550, MAT_URANIUM = 1500)
- build_path = /obj/item/borg/upgrade/jetpack
+ build_path = /obj/item/robot_upgrade/jetpack
/datum/prototype/design/science/prosfab/robot_upgrade/advhealth
design_name = "Advanced Health Analyzer Module"
desc = "An advanced health analyzer suitable for diagnosing more serious injuries."
id = "borg_advhealth_module"
materials_base = list(MAT_STEEL = 1000, MAT_GLASS = 650, MAT_DIAMOND = 350)
- build_path = /obj/item/borg/upgrade/advhealth
+ build_path = /obj/item/robot_upgrade/advhealth
/datum/prototype/design/science/prosfab/robot_upgrade/syndicate
design_name = "Scrambled Equipment Module"
@@ -539,7 +538,7 @@
req_tech = list(TECH_COMBAT = 4, TECH_ILLEGAL = 3)
// yeah no no free lunch here fuck you use 4 diamonds it should cost even MORE
materials_base = list(MAT_STEEL = 7500, MAT_GLASS = 11250, MAT_DIAMOND = 7500)
- build_path = /obj/item/borg/upgrade/syndicate
+ build_path = /obj/item/robot_upgrade/syndicate
/datum/prototype/design/science/prosfab/robot_upgrade/language
design_name = "Language Module"
@@ -547,14 +546,14 @@
id = "borg_language_module"
req_tech = list(TECH_DATA = 6, TECH_MATERIAL = 6)
materials_base = list(MAT_STEEL = 750, MAT_GLASS = 300, MAT_GOLD = 350)
- build_path = /obj/item/borg/upgrade/language
+ build_path = /obj/item/robot_upgrade/language
/datum/prototype/design/science/prosfab/robot_upgrade/sizeshift
design_name = "Size Alteration Module"
id = "borg_sizeshift_module"
req_tech = list(TECH_BLUESPACE = 3, TECH_MATERIAL = 3, TECH_POWER = 2)
materials_base = list(MAT_STEEL = 750, MAT_GLASS = 250)
- build_path = /obj/item/borg/upgrade/sizeshift
+ build_path = /obj/item/robot_upgrade/sizeshift
// Synthmorph Bags.
diff --git a/code/modules/resleeving/computers.dm b/code/modules/resleeving/computers.dm
index c97d808082c..e339e7bd185 100644
--- a/code/modules/resleeving/computers.dm
+++ b/code/modules/resleeving/computers.dm
@@ -104,7 +104,7 @@
else
if(!MT.imp)
user.visible_message("This Mirror Installation Tool is empty.")
- if(istype(W, /obj/item/dogborg/mirrortool))
+ if(istype(W, /obj/item/robot_builtin/dog_mirrortool))
var/obj/item/mirrortool/MT = W
if(MT.imp)
active_mr = MT.imp.stored_mind
diff --git a/code/modules/resleeving/mirror.dm b/code/modules/resleeving/mirror.dm
index f2d05ea4825..b7bf9db5dd8 100644
--- a/code/modules/resleeving/mirror.dm
+++ b/code/modules/resleeving/mirror.dm
@@ -72,8 +72,8 @@
MT.imp = src
MT.update_icon()
else
- if(istype(I, /obj/item/dogborg/mirrortool))
- var/obj/item/dogborg/mirrortool/MT = I
+ if(istype(I, /obj/item/robot_builtin/dog_mirrortool))
+ var/obj/item/robot_builtin/dog_mirrortool/MT = I
if(MT.imp)
to_chat(usr, "The mirror tool already contains a mirror.")
return // It's full.
diff --git a/code/modules/species/outsider/vox.dm b/code/modules/species/outsider/vox.dm
index f9b7f15aa6b..d7e95a55ccf 100644
--- a/code/modules/species/outsider/vox.dm
+++ b/code/modules/species/outsider/vox.dm
@@ -122,7 +122,7 @@
// ensure they have a valid mask
var/mask_type = /obj/item/clothing/mask/breath
if(for_target)
- var/obj/item/existing_mask = for_target.inventory.get_slot_single(/datum/inventory_slot/inventory/mask)
+ var/obj/item/existing_mask = for_target.inventory.get_slot_single(/datum/inventory_slot/inventory/mask::id)
if(existing_mask?.clothing_flags & ALLOWINTERNALS)
else
if(for_target.temporarily_remove_from_inventory(existing_mask, INV_OP_FORCE | INV_OP_SILENT))
diff --git a/code/modules/species/shadekin/shadekin_hud.dm b/code/modules/species/shadekin/shadekin_hud.dm
index c6029da09a9..0956e47bc92 100644
--- a/code/modules/species/shadekin/shadekin_hud.dm
+++ b/code/modules/species/shadekin/shadekin_hud.dm
@@ -1,6 +1,6 @@
/atom/movable/screen/shadekin
name = "shadekin status"
- icon = 'icons/screen/hud/common/shadekin.dmi'
+ icon = 'icons/screen/hud/styles/common/shadekin.dmi'
invisibility = 101
/atom/movable/screen/movable/ability_master/shadekin
diff --git a/code/modules/species/station/phoronoids.dm b/code/modules/species/station/phoronoids.dm
index 612f3b540d3..622ab192077 100644
--- a/code/modules/species/station/phoronoids.dm
+++ b/code/modules/species/station/phoronoids.dm
@@ -113,7 +113,7 @@
var/mask_type = /obj/item/clothing/mask/breath
if(for_target)
- var/obj/item/existing_mask = for_target.inventory.get_slot_single(/datum/inventory_slot/inventory/mask)
+ var/obj/item/existing_mask = for_target.inventory.get_slot_single(/datum/inventory_slot/inventory/mask::id)
if(existing_mask?.clothing_flags & ALLOWINTERNALS)
else
if(!isnull(existing_mask))
@@ -234,8 +234,8 @@
into_inv?.Add(/obj/item/extinguisher/mini/plasman)
if(for_target)
- var/obj/item/existing_head_slot = for_target.inventory.get_slot_single(/datum/inventory_slot/inventory/head)
- var/obj/item/existing_suit_slot = for_target.inventory.get_slot_single(/datum/inventory_slot/inventory/suit)
+ var/obj/item/existing_head_slot = for_target.inventory.get_slot_single(/datum/inventory_slot/inventory/head::id)
+ var/obj/item/existing_suit_slot = for_target.inventory.get_slot_single(/datum/inventory_slot/inventory/suit::id)
var/obj/item/creating_suit_slot = new suit_path
if(existing_head_slot)
if(for_target.temporarily_remove_from_inventory(existing_head_slot, INV_OP_FORCE | INV_OP_SILENT | INV_OP_FLUFFLESS))
diff --git a/code/modules/species/station/standard/tajaran.dm b/code/modules/species/station/standard/tajaran.dm
index 44e7f6ab83e..9451d8e09e7 100644
--- a/code/modules/species/station/standard/tajaran.dm
+++ b/code/modules/species/station/standard/tajaran.dm
@@ -128,7 +128,7 @@
/datum/species/tajaran/apply_racial_gear(mob/living/carbon/for_target, list/into_box, list/into_inv)
var/footwear_type = /obj/item/clothing/shoes/sandal
- if(for_target && !for_target.inventory?.get_slot_single(/datum/inventory_slot/inventory/shoes))
+ if(for_target && !for_target.inventory?.get_slot_single(/datum/inventory_slot/inventory/shoes::id))
var/obj/item/footwear_instance = new footwear_type
if(!for_target.inventory.equip_to_slot_if_possible(footwear_instance, /datum/inventory_slot/inventory/shoes))
into_inv += footwear_instance
diff --git a/code/modules/species/station/standard/teshari.dm b/code/modules/species/station/standard/teshari.dm
index be6d1c744d8..303b6423141 100644
--- a/code/modules/species/station/standard/teshari.dm
+++ b/code/modules/species/station/standard/teshari.dm
@@ -180,7 +180,7 @@
/datum/species/teshari/apply_racial_gear(mob/living/carbon/for_target, list/into_box, list/into_inv)
var/footwear_type = /obj/item/clothing/shoes/sandal
- if(for_target && !for_target.inventory?.get_slot_single(/datum/inventory_slot/inventory/shoes))
+ if(for_target && !for_target.inventory?.get_slot_single(/datum/inventory_slot/inventory/shoes::id))
var/obj/item/footwear_instance = new footwear_type
if(!for_target.inventory.equip_to_slot_if_possible(footwear_instance, /datum/inventory_slot/inventory/shoes))
into_inv += footwear_instance
diff --git a/code/modules/species/station/standard/unathi.dm b/code/modules/species/station/standard/unathi.dm
index f4f920aaf69..7e47d746b86 100644
--- a/code/modules/species/station/standard/unathi.dm
+++ b/code/modules/species/station/standard/unathi.dm
@@ -147,7 +147,7 @@
/datum/species/unathi/apply_racial_gear(mob/living/carbon/for_target, list/into_box, list/into_inv)
var/footwear_type = /obj/item/clothing/shoes/sandal
- if(for_target && !for_target.inventory?.get_slot_single(/datum/inventory_slot/inventory/shoes))
+ if(for_target && !for_target.inventory?.get_slot_single(/datum/inventory_slot/inventory/shoes::id))
var/obj/item/footwear_instance = new footwear_type
if(!for_target.inventory.equip_to_slot_if_possible(footwear_instance, /datum/inventory_slot/inventory/shoes))
into_inv += footwear_instance
diff --git a/code/modules/species/station/standard/zaddat.dm b/code/modules/species/station/standard/zaddat.dm
index 1bf1b1b65b5..e8b329644e9 100644
--- a/code/modules/species/station/standard/zaddat.dm
+++ b/code/modules/species/station/standard/zaddat.dm
@@ -100,7 +100,7 @@
// ensure they have a valid mask
var/mask_type = /obj/item/clothing/mask/gas/zaddat
if(for_target)
- var/obj/item/existing_mask = for_target.inventory.get_slot_single(/datum/inventory_slot/inventory/mask)
+ var/obj/item/existing_mask = for_target.inventory.get_slot_single(/datum/inventory_slot/inventory/mask::id)
if(for_target.temporarily_remove_from_inventory(existing_mask, INV_OP_FORCE | INV_OP_SILENT))
into_inv?.Add(existing_mask)
var/obj/item/creating_mask = new mask_type
@@ -114,7 +114,7 @@
var/suit_path = /obj/item/clothing/suit/space/void/zaddat
if(for_target)
- var/obj/item/existing_suit_slot = for_target.inventory.get_slot_single(/datum/inventory_slot/inventory/suit)
+ var/obj/item/existing_suit_slot = for_target.inventory.get_slot_single(/datum/inventory_slot/inventory/suit::id)
var/obj/item/creating_suit_slot = new suit_path
if(existing_suit_slot)
if(for_target.temporarily_remove_from_inventory(existing_suit_slot, INV_OP_FORCE | INV_OP_SILENT))
diff --git a/code/modules/tgui/states/hands.dm b/code/modules/tgui/states/hands.dm
index 9dcbd03a1c7..855beea8234 100644
--- a/code/modules/tgui/states/hands.dm
+++ b/code/modules/tgui/states/hands.dm
@@ -23,8 +23,3 @@ GLOBAL_DATUM_INIT(hands_state, /datum/ui_state/hands_state, new)
if(src_object in get_held_items())
return UI_INTERACTIVE
return UI_CLOSE
-
-/mob/living/silicon/robot/hands_can_use_topic(src_object)
- if(activated(src_object))
- return UI_INTERACTIVE
- return UI_CLOSE
diff --git a/code/modules/tgui_extensions/README.md b/code/modules/tgui_extensions/README.md
new file mode 100644
index 00000000000..dce398409bf
--- /dev/null
+++ b/code/modules/tgui_extensions/README.md
@@ -0,0 +1,3 @@
+# TGUI Extensions
+
+pretty much the citadel tgui experiment wasteland
diff --git a/code/modules/tgui_extensions/actor_modals/robot_module_picker.dm b/code/modules/tgui_extensions/actor_modals/robot_module_picker.dm
new file mode 100644
index 00000000000..e4b589278ee
--- /dev/null
+++ b/code/modules/tgui_extensions/actor_modals/robot_module_picker.dm
@@ -0,0 +1,105 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/datum/tgui_actor_modal/robot_module_picker
+ tgui_interface = "actor_modal/RobotModulePicker"
+ no_type_dupe = TRUE
+
+/datum/tgui_actor_modal/robot_module_picker/initialize()
+ if(!isrobot(actor.performer))
+ return FALSE
+ return ..()
+
+/datum/tgui_actor_modal/robot_module_picker/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state, datum/event_args/actor/actor)
+ . = ..()
+ if(.)
+ return
+ var/mob/living/silicon/robot/robot = src.actor.performer
+ if(!istype(robot))
+ // how?
+ qdel(src)
+ return TRUE
+ if(!robot.can_repick_module)
+ // how?
+ qdel(src)
+ return TRUE
+ switch(action)
+ if("pick")
+ var/module_id = params["moduleId"]
+ var/frame_ref = params["frameRef"]
+ if(!istext(frame_ref) || !length(frame_ref))
+ return TRUE
+ var/datum/prototype/robot_module/resolved_module = RSrobot_modules.fetch_local_or_throw(module_id)
+ if(!resolved_module)
+ return TRUE
+ var/datum/robot_frame/resolved_frame = locate(frame_ref) in resolved_module.frames
+ if(!resolved_frame)
+ return TRUE
+ // check ckey lock
+ if(resolved_frame.ckey_lock && !(actor.initiator.ckey in resolved_frame.ckey_lock))
+ return TRUE
+ robot.set_from_frame(resolved_frame, TRUE)
+ robot.set_module(resolved_module, TRUE)
+ robot.update_icon()
+ // no moving / acting for 5 seconds
+ robot.afflict_root(5 SECONDS)
+ robot.afflict_daze(5 SECONDS)
+ do
+ var/datum/effect_system/smoke_spread/smoke_puff = new
+ smoke_puff.set_up(2, loca = get_turf(robot))
+ smoke_puff.start()
+ while(FALSE)
+ robot.visible_message(
+ SPAN_NOTICE("With a series of mechanical whirrs, [robot] specializes into \a [resolved_module.get_visible_name()] module."),
+ )
+ qdel(src)
+ return TRUE
+
+/datum/tgui_actor_modal/robot_module_picker/ui_static_data(mob/user, datum/tgui/ui)
+ . = ..()
+ var/list/datum/prototype/robot_module/pickable_modules = assemble_modules(actor.performer)
+ var/list/serialized_modules = list()
+ .["modules"] = serialized_modules
+ for(var/datum/prototype/robot_module/possible_module as anything in pickable_modules)
+ var/module_name = "[possible_module.category ? "[possible_module.category] " : ""][possible_module.get_visible_name()]"
+ var/list/serialized_frames = list()
+ var/list/serialized_module = list(
+ "id" = possible_module.id,
+ "name" = module_name,
+ "frames" = serialized_frames,
+ )
+ var/list/datum/robot_frame/possible_frames = pickable_modules[possible_module]
+ // TODO: iconRef doesn't work properly for non-hardcoded frames but we can worry about it later
+ for(var/datum/robot_frame/possible_frame as anything in possible_frames)
+ var/frame_ref = ref(possible_frame)
+ // ckey enforcement should probably be elsewhere but idgaf lol
+ if(possible_frame.ckey_lock && !(user?.ckey in possible_frame.ckey_lock))
+ continue
+ serialized_frames[frame_ref] = list(
+ "ref" = frame_ref,
+ "name" = possible_frame.name || "???",
+ "iconRef" = "\ref[possible_frame.robot_iconset.icon]",
+ "iconState" = "[possible_frame.robot_iconset.icon_state]",
+ )
+ serialized_modules[possible_module.id] = serialized_module
+
+/**
+ * @return list(
+ * module instance = list(
+ * frame instance,
+ * ),
+ * )
+ */
+/datum/tgui_actor_modal/robot_module_picker/proc/assemble_modules(mob/living/silicon/robot/for_robot) as /list
+ . = list()
+
+ var/list/our_selection_groups = for_robot.get_module_pick_groups()
+ for(var/datum/prototype/robot_module/module as anything in RSrobot_modules.fetch_subtypes_immutable(/datum/prototype/robot_module))
+ if(!length(module.selection_groups_all) && !length(module.selection_groups_any))
+ continue
+ if(module.selection_groups_all && (length(module.selection_groups_all) != length(module.selection_groups_all & our_selection_groups)))
+ continue
+ if(module.selection_groups_any && (!length(module.selection_groups_any & our_selection_groups)))
+ continue
+ // frame ckey enforcement is in ui static data.
+ .[module] = module.frames
diff --git a/code/modules/tgui_extensions/tgui_actor_modal.dm b/code/modules/tgui_extensions/tgui_actor_modal.dm
new file mode 100644
index 00000000000..9846d7dfeec
--- /dev/null
+++ b/code/modules/tgui_extensions/tgui_actor_modal.dm
@@ -0,0 +1,92 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2025 Citadel Station Developers *//
+
+/**
+ * @params
+ * * type - modal type
+ * * actor - actor doing it
+ * * validity - (optional) additional validity checks
+ * * ... - additional args to pass to initialize()
+ *
+ * @return modal or null if failed to open
+ */
+/proc/open_tgui_actor_modal(type, datum/event_args/actor/actor, datum/callback/validity, ...)
+ var/datum/tgui_actor_modal/modal_type = type
+ if(modal_type.no_type_dupe)
+ var/mob/initiator = actor.initiator
+ var/trait = TRAIT_MOB_ACTOR_MODAL_INITIATOR(modal_type, initiator, actor.performer)
+ if(HAS_TRAIT(initiator, trait))
+ return
+ var/datum/tgui_actor_modal/modal = new type(actor, validity)
+ if(!modal.initialize(arglist(args.Copy(4))))
+ qdel(modal)
+ return
+ modal.ui_interact(actor.initiator)
+ return modal
+
+/**
+ * generalized tgui modals with procs for checking things like activity
+ * and validity
+ *
+ * * not to be confused with `/datum/admin_modal`; these have a specific
+ * user and target, usually
+ * * these should be in `tgui/interfaces/actor_modal` ideally.
+ */
+/datum/tgui_actor_modal
+ /// actor initiator-performer pair
+ var/datum/event_args/actor/actor
+ /// check to see if we can still do it; useful if we're remote controlling or something
+ var/datum/callback/validity
+ /// only one type on initiator-performer pair, period
+ /// * only checked for compile time value
+ var/no_type_dupe = FALSE
+ /// which tgui interface to open
+ var/tgui_interface
+ /// only initiator can access this ui; by default,
+ /// we use always_state and initiator-only
+ var/lock_to_initiator = TRUE
+
+/datum/tgui_actor_modal/New(datum/event_args/actor/actor, datum/callback/validity)
+ var/mob/initiator = actor.initiator
+ var/trait = TRAIT_MOB_ACTOR_MODAL_INITIATOR(type, initiator, actor.performer)
+ ADD_TRAIT(initiator, trait, ref(src))
+ src.actor = actor
+ src.validity = validity
+
+/datum/tgui_actor_modal/Destroy()
+ var/mob/initiator = actor.initiator
+ var/trait = TRAIT_MOB_ACTOR_MODAL_INITIATOR(type, initiator, actor.performer)
+ REMOVE_TRAIT(initiator, trait, ref(src))
+ actor = null
+ validity = null
+ return ..()
+
+/**
+ * @return FALSE to reject opening
+ */
+/datum/tgui_actor_modal/proc/initialize()
+ return TRUE
+
+/datum/tgui_actor_modal/ui_state(mob/user)
+ return GLOB.always_state
+
+/datum/tgui_actor_modal/ui_status(mob/user, datum/ui_state/state)
+ if(lock_to_initiator)
+ if(user != actor.initiator)
+ return UI_CLOSE
+ return ..()
+
+/datum/tgui_actor_modal/robot_module_picker/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, tgui_interface)
+ ui.set_autoupdate(FALSE)
+ ui.open()
+ // cleanup if it didn't open
+ spawn(2 SECONDS)
+ if(!QDELETED(src) && !length(open_uis))
+ qdel(src)
+
+/datum/tgui_actor_modal/on_ui_close(mob/user, datum/tgui/ui, embedded)
+ ..()
+ qdel(src)
diff --git a/code/modules/vehicles_legacy/construction.dm b/code/modules/vehicles_legacy/construction.dm
index dd8e6fcaeff..9684ea30a9c 100644
--- a/code/modules/vehicles_legacy/construction.dm
+++ b/code/modules/vehicles_legacy/construction.dm
@@ -197,7 +197,7 @@
/obj/item/vehicle_assembly/spacebike/attackby(var/obj/item/W as obj, var/mob/user as mob)
switch(build_stage)
if(0)
- if(istype(W, /obj/item/tank/jetpack) || istype(W, /obj/item/borg/upgrade/jetpack))
+ if(istype(W, /obj/item/tank/jetpack) || istype(W, /obj/item/robot_upgrade/jetpack))
if(!user.attempt_consume_item_for_construction(W))
return
increase_step()
diff --git a/code/modules/vore/eating/digest_act_vr.dm b/code/modules/vore/eating/digest_act_vr.dm
index e481ebd2a12..bb69c014ec9 100644
--- a/code/modules/vore/eating/digest_act_vr.dm
+++ b/code/modules/vore/eating/digest_act_vr.dm
@@ -4,7 +4,7 @@
// Ye default implementation.
/obj/item/proc/digest_act(var/atom/movable/item_storage = null)
- if(istype(item_storage,/obj/item/dogborg/sleeper))
+ if(istype(item_storage,/obj/item/robot_builtin/dog_sleeper))
for(var/obj/item/O in contents)
if(item_storage)
O.forceMove(item_storage)
@@ -55,7 +55,7 @@
//PDAs need to lose their ID to not take it with them, so we can get a digested ID
/obj/item/pda/digest_act(var/atom/movable/item_storage = null)
if(id)
- if(istype(item_storage,/obj/item/dogborg/sleeper) || (!isnull(digest_stage) && digest_stage <= 0))
+ if(istype(item_storage,/obj/item/robot_builtin/dog_sleeper) || (!isnull(digest_stage) && digest_stage <= 0))
id = null
. = ..()
diff --git a/code/modules/vore/eating/living_vr.dm b/code/modules/vore/eating/living_vr.dm
index 89d9a6c70cb..ad94ceeadb2 100644
--- a/code/modules/vore/eating/living_vr.dm
+++ b/code/modules/vore/eating/living_vr.dm
@@ -369,9 +369,9 @@
B.owner.update_icons()
//You're in a dogborg!
- else if(istype(loc, /obj/item/dogborg/sleeper))
+ else if(istype(loc, /obj/item/robot_builtin/dog_sleeper))
var/mob/living/silicon/pred = loc.loc //Thing holding the belly!
- var/obj/item/dogborg/sleeper/belly = loc //The belly!
+ var/obj/item/robot_builtin/dog_sleeper/belly = loc //The belly!
var/confirm = alert(src, "You're in a dogborg sleeper. This is for escaping from preference-breaking or if your predator disconnects/AFKs. If your preferences were being broken, please admin-help as well.", "Confirmation", "Okay", "Cancel")
if(!confirm == "Okay" || loc != belly)
diff --git a/code/modules/xenoarcheaology/boulder.dm b/code/modules/xenoarcheaology/boulder.dm
index e67d1f87cc1..b00342bc55e 100644
--- a/code/modules/xenoarcheaology/boulder.dm
+++ b/code/modules/xenoarcheaology/boulder.dm
@@ -91,8 +91,9 @@
else if(istype(AM,/mob/living/silicon/robot))
var/mob/living/silicon/robot/R = AM
- if(istype(R.module_active,/obj/item/pickaxe))
- attackby(R.module_active,R)
+ var/obj/item/pickaxe/maybe_pickaxe = R.get_held_item_of_type(/obj/item/pickaxe)
+ if(maybe_pickaxe)
+ maybe_pickaxe.melee_interaction_chain(src, R)
else if(istype(AM,/obj/vehicle/sealed/mecha))
var/obj/vehicle/sealed/mecha/M = AM
diff --git a/code/modules/xenoarcheaology/finds/find_spawning.dm b/code/modules/xenoarcheaology/finds/find_spawning.dm
index fe0bd1bff59..e215cbe1eed 100644
--- a/code/modules/xenoarcheaology/finds/find_spawning.dm
+++ b/code/modules/xenoarcheaology/finds/find_spawning.dm
@@ -372,7 +372,7 @@
//robot remains
apply_prefix = 0
item_type = "[pick("mechanical","robotic","cyborg")] [pick("remains","chassis","debris")]"
- icon = 'icons/mob/robots.dmi'
+ icon = 'icons/mob/robot-legacy.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/config/custom_items.txt b/config/custom_items.txt
deleted file mode 100644
index 2adfcbac3da..00000000000
--- a/config/custom_items.txt
+++ /dev/null
@@ -1,1033 +0,0 @@
-##
-# Custom items go here. They are modifications of existing paths; look at the example for details.
-# Item will spawn if the target has one of the req_titles and if their on-spawn ID has the required access level.
-# req_access is going to be a shit to maintain since the config file can't grab constants and has to use integers, use it minimally.
-# Separate titles with a single comma and a space (', ') or they'll bork.
-#
-# EX:
-# {
-# ckey: zuhayr
-# character_name: Jane Doe
-# item_path: /obj/item/toy/plushie
-# item_name: ugly plush toy
-# item_icon: flagmask
-# item_desc: It's truly hideous.
-# req_titles: Assistant, Security Officer
-# req_access: 1
-# }
-#
-# {
-# ckey: zuhayr
-# character_name: Jane Doe
-# item_path: /obj/item/kit/paint
-# item_name: APLU customisation kit
-# item_desc: A customisation kit with all the parts needed to turn an APLU into a "Titan's Fist" model.
-# kit_name: APLU "Titan's Fist"
-# kit_desc: Looks like an overworked, under-maintained Ripley with some horrific damage.
-# kit_icon: titan
-# additional_data: ripley, firefighter
-# }
-#
-# {
-# ckey: zuhayr
-# character_name: Jane Doe
-# item_path: /obj/item/kit/suit
-# item_name: salvage suit customisation kit
-# item_desc: A customisation kit with all the parts needed to convert a suit.
-# kit_name: salvage
-# kit_desc: An orange voidsuit. Reinforced!
-# kit_icon: salvage
-# }
-##
-
-# ######## 0-9 CKEYS
-{
-ckey: 1r1s
-character_name: Zippy Shanks
-item_path: /obj/item/melee/fluff/holochain/Zippy
-}
-
-{
-ckey: 1r1s
-character_name: Malady Blanche
-item_path: /obj/item/material/twohanded/fluff/riding_crop/malady
-}
-
-# ######## A CKEYS
-{
-ckey: adk09
-character_name: Lethe
-item_path: /obj/item/clothing/head/helmet/hos/fluff/lethe
-}
-
-{
-ckey: argobargsoup
-character_name: Lynn Shady
-item_path: /obj/item/flashlight/pen/fluff/lynn
-}
-
-{
-ckey: arokha
-character_name: Aronai Kadigan
-item_path: /obj/item/clothing/under/utility/sifguard/medical/command
-item_name: centcom medical uniform
-item_desc: A medical uniform straight from Central Command.
-}
-
-{
-ckey: arokha
-character_name: Aronai Kadigan
-item_path: /obj/item/clothing/suit/storage/service/sifguard/medical/command
-item_name: centcom medical jacket
-item_desc: A medical jacket straight from Central Command.
-}
-
-{
-ckey: arokha
-character_name: Aronai Kadigan
-item_path: /obj/item/card/id/centcom/station/fluff/aronai
-}
-
-Weapon Box. Permit Expired
-{
-ckey: aerowing
-character_name: Sebastian Aji
-item_path: /obj/item/storage/box/fluff/sebastian_aji
-}
-
-# ######## B CKEYS
-{
-ckey: benemuel
-character_name: Yuuko Shimmerpond
-item_path: /obj/item/clothing/under/fluff/sakura_hokkaido_kimono
-}
-
-{
-ckey: beyondmylife
-character_name: Cassandra Selone
-item_path: /obj/item/storage/box/fluff/cassandra
-}
-
-{
-ckey: beyondmylife
-character_name: Kilano Soryu
-item_path: /obj/item/clothing/under/dress/fluff/kilano
-}
-
-{
-ckey: beyondmylife
-character_name: Kilano Soryu
-item_path: /obj/item/clothing/gloves/fluff/kilano
-}
-
-{
-ckey: beyondmylife
-character_name: Ne'tra Ky'ram
-item_path: /obj/item/storage/box/fluff/kilano
-req_access: 1
-}
-
-{
-ckey: britishrabbit
-character_name: Xin Xiao
-item_path: /obj/item/clothing/under/soviet
-item_desc: This soviet uniform has seen considerable use over the years, it's rather worn in some places, frayed in others and the stomach region has signs of being stretched out repeatedly.
-}
-
-{
-ckey: bwoincognito
-character_name: Tasald Corlethian
-item_path: /obj/item/storage/box/fluff/tasald
-}
-
-{
-ckey: bwoincognito
-character_name: Octavious Ward
-item_path: /obj/item/storage/box/fluff/octavious
-}
-
-{
-ckey: burritojustice
-character_name: Jayda Wilson
-item_path: /obj/item/clothing/under/utility/sifguard/medical
-}
-
-# ######## C CKEYS
-{
-ckey: cockatricexl
-character_name: James Holder
-item_path: /obj/item/disk/data
-}
-
-{
-ckey: cameron653
-character_name: Jasmine Lizden
-item_path: /obj/item/implanter/reagent_generator/jasmine
-}
-
-{
-ckey: cameron653
-character_name: Diana Kuznetsova
-item_path: /obj/item/clothing/suit/fluff/purp_robes
-}
-
-{
-ckey: cameron653
-character_name: Diana Kuznetsova
-item_path: /obj/item/clothing/head/fluff/pink_tiara
-}
-
-# ######## D CKEYS
-{
-ckey: deepindigo
-character_name: Amina Dae-Kouri
-item_path: /obj/item/storage/bible/fluff/amina
-}
-
-{
-ckey: dhaeleena
-character_name: Dhaeleena M'iar
-item_path: /obj/item/clothing/accessory/medal/silver/security/fluff/dhael
-}
-
-#Permit Expired
-#{
-#ckey: dhaeleena
-#character_name: Dhaeleena M'iar
-#item_path: /obj/item/gun/projectile/revolver/mateba/fluff/dhael
-#}
-#
-#{
-#ckey: dhaeleena
-#character_name: Dhaeleena M'iar
-#item_path: /obj/item/clothing/accessory/permit/gun/fluff/dhaeleena_miar
-#}
-
-{
-ckey: dickfreedomjohnson
-character_name: Elliot Richards
-item_path: /obj/item/clothing/accessory/medal/medical
-}
-
-{
-ckey: dickfreedomjohnson
-character_name: Elliot Richards
-item_path: /obj/item/storage/belt/champion
-}
-
-{
-ckey: drakefrostpaw
-character_name: Drake Frostpaw
-item_path: /obj/item/storage/box/fluff/drake
-}
-
-{
-ckey: draycu
-character_name: Theseus
-item_path: /obj/item/coin/diamond
-item_name: theseus' diamond coin
-item_desc: An engraved coin made of diamond. On the side for heads is printed the year 2541, along with the letter T. On the side for tails, the letter Y can be seen.
-}
-
-{
-ckey: draycu
-character_name: Schae Yonra
-item_path: /obj/item/storage/box/fluff/yonra
-}
-
-# ######## E CKEYS
-
-# Permit Expired
-#{
-#ckey: eekasqueak
-#character_name: Serkii Miishy
-#item_path: /obj/item/clothing/accessory/permit/gun/fluff/serkiimiishy
-#}
-#
-#{
-#ckey: eekasqueak
-#character_name: Serkii Miishy
-#item_path: /obj/item/gun/energy/stunrevolver
-#}
-
-{
-ckey: eekasqueak
-character_name: Serkii Miishy
-item_path: /obj/item/clothing/shoes/slippers
-}
-
-{
-ckey: eekasqueak
-character_name: Serkii Miishy
-item_path: /obj/item/clothing/under/skirt/fluff/serkii
-}
-
-{
-ckey: epiccharger
-character_name: Jessie Mare
-item_path: /obj/item/clothing/suit/storage/hooded/wintercoat/jessie
-}
-
-{
-ckey: epigraphzero
-character_name: Verd Woodrow
-item_path: /obj/item/clothing/accessory/medal/bronze_heart
-}
-
-# ######## F CKEYS
-# ######## G CKEYS
-
-{
-ckey:Gowst
-character_name: Eldi Moljir
-item_path: /obj/item/implanter/reagent_generator/eldi
-}
-
-# ######## H CKEYS
-
-{
-ckey: hottokeeki
-character_name: Belle Day
-item_path: /obj/item/gun/energy/sizegun
-}
-
-{
-ckey: hottokeeki
-character_name: Belle Day
-item_path: /obj/item/gun/energy/netgun
-}
-
-{
-ckey: hottokeeki
-character_name: Belle Day
-item_path: /obj/item/implanter/reagent_generator/belle
-}
-
-#{
-#ckey: hzdonut
-#character_name: Jesse Soemmer
-#item_path: /obj/item/gun/projectile/revolver/fluff/jesselemat
-#}
-#
-#{
-#ckey: hzdonut
-#character_name: Jesse Soemmer
-#item_path: /obj/item/clothing/accessory/permit/gun/fluff/JesseSoemmer
-#}
-#
-#{
-#ckey: hzdonut
-#character_name: Jesse Soemmer
-#item_path: /obj/item/clothing/accessory/holster/hip
-#}
-
-# ######## I CKEYS
-
-{
-ckey: interrolouis
-character_name: Ruda Lizden
-item_path: /obj/item/clothing/accessory/badge/holo/detective/ruda
-}
-
-{
-ckey: interrolouis
-character_name: Kai Highlands
-item_path: /obj/item/ka_modkit/chassis_mod/kai
-}
-
-{
-ckey: ivymoomoo
-character_name: Ivy Baladeva
-item_path: /obj/item/storage/backpack/messenger/sec/fluff/ivymoomoo
-}
-
-# ######## J CKEYS
-
-{
-ckey: jacknoir413
-character_name: Mor Xaina
-item_path: /obj/item/storage/box/fluff/morxaina
-}
-
-{
-ckey: jacknoir413
-character_name: Areax Third
-item_path: /obj/item/storage/backpack/fluff/stunstaff
-req_titles: Security Officer, Warden, Head of Security
-}
-
-{
-ckey: jacobdragon
-character_name: Earthen Breath
-item_path: /obj/item/clothing/under/fluff/earthenbreath
-}
-
-{
-ckey: jacobdragon
-character_name: Earthen Breath
-item_path: /obj/item/clothing/head/fluff/hairflowerpin
-}
-
-{
-ckey: jemli
-character_name: Cirra Mayhem
-item_path: /obj/item/storage/box/fluff/cirra
-}
-
-{
-ckey: jemli
-character_name: Jemli
-item_path: /obj/item/clothing/head/fedora/fluff/jemli
-}
-
-{
-ckey: jertheace
-character_name: Jeremiah Acacius
-item_path: /obj/item/gun/projectile/shotgun/pump/USDF/fluff/ace
-req_access = 58
-}
-
-{
-ckey: jertheace
-character_name: Jeremiah Acacius
-item_path: /obj/item/clothing/accessory/permit/gun/fluff/ace
-req_access = 58
-}
-
-{
-ckey: jertheace
-character_name: Jeremiah Acacius
-item_path: /obj/item/clothing/shoes/boots/combat
-}
-
-{
-ckey: jertheace
-character_name: Jeremiah Acacius
-item_path: /obj/item/clothing/under/syndicate/combat
-}
-
-{
-ckey: jertheace
-character_name: Jeremiah Acacius
-item_path: /obj/item/clothing/suit/armor/tactical/officer/fluff/ace
-}
-
-{
-ckey: joanrisu
-character_name: Joan Risu
-item_path: /obj/item/storage/backpack/dufflebag/sec/fluff/joanrisu
-}
-
-{
-ckey: joanrisu
-character_name: Katarina Eine
-item_path: /obj/item/storage/backpack/dufflebag/sec/fluff/katarina
-}
-
-{
-ckey: joey4298
-character_name: Rosey Tasimaro Maximus
-item_path: /obj/item/clothing/head/fluff/threetail
-}
-
-{
-ckey: joey4298
-character_name: Emoticon
-item_path: /obj/item/storage/box/fluff/emoticon
-}
-
-{
-ckey: joey4298
-character_name: Emoticon
-item_path: /obj/item/clothing/under/sexymime
-}
-
-{
-ckey: joey4298
-character_name: Emoticon
-item_path: /obj/item/clothing/mask/gas/sexymime
-}
-
-{
-ckey: john.wayne9392
-character_name: Harmony Pretchl
-item_path: /obj/item/clothing/accessory/medal/heroism
-}
-
-{
-ckey: john.wayne9392
-character_name: Harmony Pretchl
-item_path: /obj/item/modkit_conversion/fluff/harmonysuit
-}
-
-{
-ckey: john.wayne9392
-character_name: Harmony Pretchl
-item_path: /obj/item/modkit_conversion/fluff/harmonyspace
-}
-
-# ######## K CKEYS
-{
-ckey: kazkin
-character_name: Ahzrukhal Ahkeen
-item_path: /obj/item/clothing/accessory/medal/silver/unity
-}
-
-{
-ckey: keekenox
-character_name: SMU-453
-item_path: /obj/item/clothing/accessory/medal/nobel_science
-}
-
-{
-ckey: ketrai
-character_name: Ketrai
-item_path: /obj/item/clothing/head/fluff/ketrai
-}
-
-{
-ckey: kiraalitruss
-character_name: Amaya Rahl
-item_path: /obj/item/card/id/fluff/amaya
-}
-
-{
-ckey: kisukegema
-character_name: Kisuke Gema
-item_path: /obj/item/clothing/glasses/omnihud/kamina
-}
-
-{
-ckey: Killjaden
-character_name: Lassara Faaira'Nrezi
-item_path: /obj/item/clothing/accessory/storage/knifeharness
-}
-
-{
-ckey: kitchifox
-character_name: Rana Uma
-item_path: /obj/item/clothing/accessory/medal/silver/unity
-}
-
-{
-ckey: KiwiDaNinja
-character_name: Chakat Taiga
-item_path: /obj/item/clothing/under/fluff/taiga
-}
-
-#{
-#ckey: kligor
-#character_name: Andy Gettemy
-#item_path: /obj/item/pda_mod/fluff/kligor
-#}
-
-{
-ckey: konabird
-character_name: Rischi
-item_path: /obj/item/implanter/reagent_generator/rischi
-}
-
-{
-ckey: knightfall5
-character_name: Ashley Kifer
-item_path: /obj/item/clothing/accessory/medal/nobel_science/fluff/ashley
-}
-
-# ######## L CKEYS
-
-#Weapon Box. Permit Expired
-#{
-#ckey: luminescentring
-#character_name: Briana Moore
-#item_path: /obj/item/storage/box/fluff/briana_moore
-#}
-
-{
-ckey: luminescentring
-character_name: Briana Moore
-item_path: /obj/item/storage/backpack/messenger/black/fluff/briana
-}
-
-{
-ckey: Lycanthorph
-character_name: Savannah Dixon
-item_path: /obj/item/implanter/reagent_generator/savannah
-}
-
-# ######## M CKEYS
-{
-ckey: mewchild
-character_name: Phi Vietsi
-item_path: /obj/item/storage/box/fluff/vietsi
-}
-
-{
-ckey: molenar
-character_name: Uya Kohakuren
-item_path: /obj/item/clothing/head/fluff/molenar
-}
-
-{
-ckey: molenar
-character_name: Giliana Gamish
-item_path: /obj/item/clothing/suit/storage/toggle/labcoat/fluff/molenar
-}
-
-{
-ckey: molenar
-character_name: Giliana Gamish
-item_path: /obj/item/clothing/head/fluff/molenar2
-}
-
-# Permit Expired
-#{
-#ckey: molenar
-#character_name: Kari Akiren
-#item_path: /obj/item/gun/projectile/shotgun/pump/rifle/fluff/kari_akiren
-#}
-#
-#{
-#ckey: molenar
-#character_name: Kari Akiren
-#item_path: /obj/item/clothing/accessory/permit/gun/fluff/kari_akiren
-#}
-
-{
-ckey: myryan
-character_name: Myryan Karnage-Cunningham
-item_path: /obj/item/storage/belt/utility/fluff/vulpine
-}
-
-{
-ckey: mr_signmeup
-character_name: Reshskakskakss Seekiseekis
-item_path: /obj/item/clothing/suit/security/navyhos
-req_titles: Head of Security
-}
-
-# ######## N CKEYS
-
-{
-ckey: natje
-character_name: Awen Henry
-item_path: /obj/item/clothing/head/fluff/awoo
-}
-
-{
-ckey: natje
-character_name: Awen Henry
-item_path: /obj/item/clothing/shoes/fluff/awoo
-}
-
-{
-ckey: natje
-character_name: Awen Henry
-item_path: /obj/item/clothing/under/fluff/awoo
-}
-
-{
-ckey: natje
-character_name: Pumila
-item_path: /obj/item/clothing/under/fluff/aluranevines
-}
-
-{
-ckey: nepox
-character_name: Annie Rose
-item_path: /obj/item/clothing/accessory/sweater/fluff/annie
-}
-
-# ######## O CKEYS
-{
-ckey: orbisa
-character_name: Richard D'angelo
-item_path: /obj/item/melee/fluff/holochain
-}
-
-# ######## P CKEYS
-
-{
-ckey: pawoverlord
-character_name: Sorrel Cavalet
-item_path: /obj/item/clothing/accessory/permit/gun/fluff/sorrel_cavalet
-}
-
-{
-ckey: pawoverlord
-character_name: Sorrel Cavalet
-item_path: /obj/item/gun/projectile/colt
-item_desc: "A cheap Martian knock-off of a Colt M1911. It has the word 'Cavalet' etched on the side. Uses .45 rounds."
-}
-
-{
-ckey: phoaly
-character_name: Lily Maximus
-item_path: /obj/item/clothing/accessory/medal/silver/unity
-}
-
-{
-ckey: pontifexminimus
-character_name: Lucius Null
-item_path: /obj/item/fluff/dragor_dot
-}
-{
-ckey: pontifexminimus
-character_name: Lucia Null
-item_path: /obj/item/fluff/dragor_dot
-}
-
-# ######## Q CKEYS
-
-# ######## R CKEYS
-
-# Permit Expired
-#{
-#ckey: Razerwing
-#character_name: Archer Maximus
-#item_path: /obj/item/clothing/accessory/permit/gun/fluff/archermaximus
-#}
-#
-#{
-#ckey: Razerwing
-#character_name: Archer Maximus
-#item_path: /obj/item/gun/projectile/colt/fluff/archercolt
-#}
-
-# ######## S CKEYS
-{
-ckey: samanthafyre
-character_name: Kateryna Petrovitch
-item_path: /obj/item/clothing/suit/space/void/engineering/kate
-}
-
-{
-ckey: samanthafyre
-character_name: Kateryna Petrovitch
-item_path: /obj/item/clothing/head/helmet/space/fluff/kate
-}
-
-{
-ckey: samanthafyre
-character_name: Kateryna Petrovitch
-item_path: /obj/item/clothing/suit/armor/vest/wolftaur/kate
-}
-
-{
-ckey: sasoperative
-character_name: Joseph Skinner
-item_path: /obj/item/clothing/accessory/medal/ERTservice
-}
-
-{
-ckey: sasoperative
-character_name: Joseph Skinner
-item_path: /obj/item/clothing/under/rank/security/fluff/formalsec
-}
-
-{
-ckey: scree
-character_name: Scree
-item_path: /obj/item/modkit_conversion/fluff/screekit
-}
-
-{
-ckey: scree
-character_name: Scree
-item_path: /obj/item/clothing/head/fluff/pompom
-}
-
-{
-ckey: seiga
-character_name: Alfonso Oak Telanor
-item_path: /obj/item/clothing/glasses/sunglasses/fluff/alfonso
-}
-
-#{
-#ckey: silencedmp5a5
-#character_name: Serdykov Antoz
-#item_path: /obj/item/clothing/head/helmet/serdy
-#req_access = 1
-#}
-
-#{
-#ckey: silencedmp5a5
-#character_name: Serdykov Antoz
-#item_path: /obj/item/clothing/suit/armor/vest/wolftaur/serdy
-#req_access = 1
-#}
-#
-#{
-#ckey: silencedmp5a5
-#character_name: Serdykov Antoz
-#item_path: /obj/item/storage/box/fluff/serdykov_antoz
-#req_access = 1
-#}
-
-{
-ckey: silencedmp5a5
-character_name: NT-HASD #556
-item_path: /obj/item/modkit_conversion/hasd
-req_access = 1
-}
-
-{
-ckey: silvertalismen
-character_name: Tasy Ruffles
-item_path: /obj/item/clothing/under/sexyclown
-}
-
-{
-ckey: silvertalismen
-character_name: Evian
-item_path: /obj/item/implanter/reagent_generator/evian
-}
-
-{
-ckey: silvertalismen
-character_name: Tasy Ruffles
-item_path: /obj/item/clothing/mask/gas/sexyclown
-}
-
-{
-ckey: silvertalismen
-character_name: Tasy Ruffles
-item_path: /obj/item/pda/clown
-}
-
-{
-ckey: Swat43
-character_name: Fortune Bloise
-item_path: /obj/item/storage/backpack/satchel/fluff/swat43bag
-}
-
-{
-ckey: stobarico
-character_name: Alexis Bloise
-item_path: /obj/item/cane/wand
-}
-
-{
-ckey: SpoopyLizz
-character_name: Roiz Lizden
-item_path: /obj/item/implanter/reagent_generator/roiz
-}
-
-{
-ckey: SpoopyLizz
-character_name: Roiz Lizden
-item_path: /obj/item/clothing/suit/storage/hooded/wintercoat/roiz
-}
-
-{
-ckey: suicidalpickles
-character_name: Silent Stripes
-item_path: /obj/item/clothing/mask/gas/sexymime
-}
-
-{
-ckey: suicidalpickles
-character_name: Silent Stripes
-item_path: /obj/item/clothing/under/sexymime
-}
-
-# ######## T CKEYS
-{
-ckey: tabiranth
-character_name: Ascian
-item_path: /obj/item/clothing/accessory/medal/silver/unity
-}
-
-{
-ckey: techtypes
-character_name: Lasshseeki Korss
-item_path: /obj/item/implant/language/eal
-}
-
-{
-ckey: tinydude16
-character_name: Konor Foxe
-item_path: /obj/item/clothing/accessory/medal/silver/unity
-}
-
-{
-ckey: tonyvld
-character_name: Tony Bingham
-item_path: /obj/item/clothing/head/fluff/runac
-}
-
-{
-ckey: tabiranth
-character_name: Ascian
-item_path: /obj/item/grenade/spawnergrenade/spirit
-}
-
-# ######## U CKEYS
-# ######## V CKEYS
-{
-ckey: verkister
-character_name: Cameron Eggbert
-item_path: /obj/item/clothing/glasses/fluff/science_proper
-}
-{
-ckey: verkister
-character_name: Cameron Eggbert
-item_path: /obj/item/disk/limb/eggnerdltd
-}
-{
-ckey: verkister
-character_name: Opie Eggbert
-item_path: /obj/item/clothing/glasses/fluff/spiffygogs
-}
-{
-ckey: virgo113
-character_name: Verin Raharra
-item_path: /obj/item/clothing/suit/storage/hazardvest/fluff/verin
-}
-{
-ckey: vorrarkul
-character_name: Lucina Dakarim
-item_path: /obj/item/pda/heads/cmo/fluff/lucinapda
-}
-
-{
-ckey: vorrarkul
-character_name: Lucina Dakarim
-item_path: /obj/item/clothing/accessory/medal/gold/fluff/lucina
-}
-
-{
-ckey: vorrarkul
-character_name: Lucina Dakarim
-item_path: /obj/item/clothing/under/dress/fluff/lucinadress
-}
-
-{
-ckey: vorrarkul
-character_name: Nyssa Brennan
-item_path: /obj/item/clothing/suit/storage/hooded/wintercoat/cargo
-}
-
-{
-ckey: vorrarkul
-character_name: Theodora Lindt
-item_path: /obj/item/clothing/suit/chococoat
-}
-
-{
-ckey: vorrarkul
-character_name: Theodora Lindt
-item_path: /obj/item/implanter/reagent_generator/vorrarkul
-}
-
-{
-ckey: vorrarkul
-character_name: Kaitlyn Fiasco
-item_path: /obj/item/toy/plushie/mouse/fluff
-}
-
-{
-ckey: Viveret
-character_name: Keturah
-item_path: /obj/item/clothing/under/dress/maid/
-}
-
-{
-ckey: Viveret
-character_name: Silentio
-item_path: /obj/item/clothing/under/sexymime
-}
-
-{
-ckey: Viveret
-character_name: Silentio
-item_path: /obj/item/clothing/mask/gas/sexymime
-}
-
-# ######## W CKEYS
-#{
-#ckey: warbrand2
-#character_name: Brandy draca
-#item_path: /obj/item/pda_mod/fluff/warbrand2
-#}
-
-{
-ckey: werebear
-character_name: Silas Newton
-item_path: /obj/item/clothing/glasses/threedglasses
-}
-
-{
-ckey: whiskyrose
-character_name: Vinjj
-item_path: /obj/item/clothing/head/welding/fluff/vinjj
-}
-
-{
-ckey: wickedtemp
-character_name: Chakat Tempest Venesare
-item_path: /obj/item/clothing/glasses/omnihud/med/fluff/wickedtemphud
-}
-
-{
-ckey: wickedtemp
-character_name: Chakat Tempest Venesare
-item_path: /obj/item/reagent_containers/hypospray/vial/tempest
-}
-
-{
-ckey: wickedtemp
-character_name: Chakat Tempest Venesare
-item_path: /obj/item/storage/backpack/saddlebag/tempest
-}
-
-{
-ckey: wickedtemp
-character_name: Chakat Tempest Venesare
-item_path: /obj/item/implanter/reagent_generator/tempest
-}
-
-# ######## X CKEYS
-
-{
-ckey: Xsdew
-character_name: Penelope Allen
-item_path: /obj/item/storage/box/fluff/penelope
-}
-
-{
-ckey: Xennith
-character_name: Amy Lessen
-item_path: /obj/item/card/id/fluff/xennith
-}
-
-{
-ckey: Xonkon
-character_name: Ali
-item_path: /obj/item/clothing/accessory/medal/silver/unity
-}
-
-# ######## Y CKEYS
-# ######## Z CKEYS
-{
-ckey: zekesturm
-character_name: Sarah Arachi Lacecraft
-item_path: /obj/item/modkit_single/fluff/zekechimera
-}
-
-{
-ckey: zekesturm
-character_name: Zeke Arachi
-item_path: /obj/item/clothing/head/fluff/xeno
-}
-
-{
-ckey: zigfe
-character_name: Zaoozaoo Xrimxuqmqixzix
-item_path: /obj/item/clothing/head/fluff/zao
-}
-
-{
-ckey: zodiacshadow
-character_name: Nehi Maximus
-item_path: /obj/item/radio/headset/fluff/zodiacshadow
-}
diff --git a/config/custom_sprites.txt b/config/custom_sprites.txt
deleted file mode 100644
index db3930a4ae2..00000000000
--- a/config/custom_sprites.txt
+++ /dev/null
@@ -1 +0,0 @@
-ckey-state
\ No newline at end of file
diff --git a/icons/README.md b/icons/README.md
index 3fc1aaa596a..d7f2b12d948 100644
--- a/icons/README.md
+++ b/icons/README.md
@@ -33,6 +33,8 @@ Yes, this currently includes all turfs, mobs, objs, and misc things. Sorry. We'l
- /mob - mob sprites
- /bodysets - limbs and their corrosponding sprite accessories & markings & miscellaneous.
used in the abstraction of limb sprite from limb definition.
+ - /robot - `/mob/living/silicon/robot` stuff
+ - /iconset - iconsets for silicon / robot mobs
- /sprite_accessories - unconverted sprite accessories
- /sprite_accessory - modern sprite accessory system
- /modules - used for specific departments / content packs / logical bundles
diff --git a/icons/obj/module.dmi b/icons/items/circuits.dmi
similarity index 100%
rename from icons/obj/module.dmi
rename to icons/items/circuits.dmi
diff --git a/icons/mob/custom_synthetic.dmi b/icons/mob/custom_synthetic.dmi
deleted file mode 100644
index 7854fabe2bf..00000000000
Binary files a/icons/mob/custom_synthetic.dmi and /dev/null differ
diff --git a/icons/mob/robot-alina-unused.dmi b/icons/mob/robot-alina-unused.dmi
new file mode 100644
index 00000000000..4d5284ead45
Binary files /dev/null and b/icons/mob/robot-alina-unused.dmi differ
diff --git a/icons/mob/robot-legacy.dmi b/icons/mob/robot-legacy.dmi
new file mode 100644
index 00000000000..f654b9a963e
Binary files /dev/null and b/icons/mob/robot-legacy.dmi differ
diff --git a/icons/mob/robot-thinktank-unused.dmi b/icons/mob/robot-thinktank-unused.dmi
new file mode 100644
index 00000000000..471647bf603
Binary files /dev/null and b/icons/mob/robot-thinktank-unused.dmi differ
diff --git a/icons/mob/robot-unused.dmi b/icons/mob/robot-unused.dmi
new file mode 100644
index 00000000000..68c537f078a
Binary files /dev/null and b/icons/mob/robot-unused.dmi differ
diff --git a/icons/mob/robot/iconset/baseline_droid.dmi b/icons/mob/robot/iconset/baseline_droid.dmi
new file mode 100644
index 00000000000..4eb04174435
Binary files /dev/null and b/icons/mob/robot/iconset/baseline_droid.dmi differ
diff --git a/icons/mob/robot/iconset/baseline_drone.dmi b/icons/mob/robot/iconset/baseline_drone.dmi
new file mode 100644
index 00000000000..d5e6b4cf9a7
Binary files /dev/null and b/icons/mob/robot/iconset/baseline_drone.dmi differ
diff --git a/icons/mob/robot/iconset/baseline_misc.dmi b/icons/mob/robot/iconset/baseline_misc.dmi
new file mode 100644
index 00000000000..dc671458b90
Binary files /dev/null and b/icons/mob/robot/iconset/baseline_misc.dmi differ
diff --git a/icons/mob/robot/iconset/baseline_old.dmi b/icons/mob/robot/iconset/baseline_old.dmi
new file mode 100644
index 00000000000..1649b71f33c
Binary files /dev/null and b/icons/mob/robot/iconset/baseline_old.dmi differ
diff --git a/icons/mob/robot/iconset/baseline_standard.dmi b/icons/mob/robot/iconset/baseline_standard.dmi
new file mode 100644
index 00000000000..5184d045e76
Binary files /dev/null and b/icons/mob/robot/iconset/baseline_standard.dmi differ
diff --git a/icons/mob/robot/iconset/baseline_toiletbot.dmi b/icons/mob/robot/iconset/baseline_toiletbot.dmi
new file mode 100644
index 00000000000..dd9560042c0
Binary files /dev/null and b/icons/mob/robot/iconset/baseline_toiletbot.dmi differ
diff --git a/icons/mob/robot/iconset/biped_droid.dmi b/icons/mob/robot/iconset/biped_droid.dmi
new file mode 100644
index 00000000000..dbee4e2d41b
Binary files /dev/null and b/icons/mob/robot/iconset/biped_droid.dmi differ
diff --git a/icons/mob/robot/iconset/biped_heavy.dmi b/icons/mob/robot/iconset/biped_heavy.dmi
new file mode 100644
index 00000000000..3c48c91201a
Binary files /dev/null and b/icons/mob/robot/iconset/biped_heavy.dmi differ
diff --git a/icons/mob/robot/iconset/biped_insekt.dmi b/icons/mob/robot/iconset/biped_insekt.dmi
new file mode 100644
index 00000000000..de8c28dbd29
Binary files /dev/null and b/icons/mob/robot/iconset/biped_insekt.dmi differ
diff --git a/icons/mob/robot/iconset/biped_k4t.dmi b/icons/mob/robot/iconset/biped_k4t.dmi
new file mode 100644
index 00000000000..a23d3df8160
Binary files /dev/null and b/icons/mob/robot/iconset/biped_k4t.dmi differ
diff --git a/icons/mob/robot/iconset/biped_maid.dmi b/icons/mob/robot/iconset/biped_maid.dmi
new file mode 100644
index 00000000000..6ea73cf4b0a
Binary files /dev/null and b/icons/mob/robot/iconset/biped_maid.dmi differ
diff --git a/icons/mob/robot/iconset/biped_marina.dmi b/icons/mob/robot/iconset/biped_marina.dmi
new file mode 100644
index 00000000000..0bb0c135229
Binary files /dev/null and b/icons/mob/robot/iconset/biped_marina.dmi differ
diff --git a/icons/mob/robot/iconset/biped_meka.dmi b/icons/mob/robot/iconset/biped_meka.dmi
new file mode 100644
index 00000000000..606ce19bc65
Binary files /dev/null and b/icons/mob/robot/iconset/biped_meka.dmi differ
diff --git a/icons/mob/robot/iconset/biped_miss.dmi b/icons/mob/robot/iconset/biped_miss.dmi
new file mode 100644
index 00000000000..b1f9c0fa8f7
Binary files /dev/null and b/icons/mob/robot/iconset/biped_miss.dmi differ
diff --git a/icons/mob/robot/iconset/biped_noble.dmi b/icons/mob/robot/iconset/biped_noble.dmi
new file mode 100644
index 00000000000..cbbe0ec305c
Binary files /dev/null and b/icons/mob/robot/iconset/biped_noble.dmi differ
diff --git a/icons/mob/robot/iconset/biped_sleek.dmi b/icons/mob/robot/iconset/biped_sleek.dmi
new file mode 100644
index 00000000000..8d0d39c6bba
Binary files /dev/null and b/icons/mob/robot/iconset/biped_sleek.dmi differ
diff --git a/icons/mob/robot/iconset/biped_tall.dmi b/icons/mob/robot/iconset/biped_tall.dmi
new file mode 100644
index 00000000000..e8820e32c25
Binary files /dev/null and b/icons/mob/robot/iconset/biped_tall.dmi differ
diff --git a/icons/mob/robot/iconset/cat_feli.dmi b/icons/mob/robot/iconset/cat_feli.dmi
new file mode 100644
index 00000000000..7c86b2ad138
Binary files /dev/null and b/icons/mob/robot/iconset/cat_feli.dmi differ
diff --git a/icons/mob/robot/iconset/dog_borgi-unused.dmi b/icons/mob/robot/iconset/dog_borgi-unused.dmi
new file mode 100644
index 00000000000..908be642b2a
Binary files /dev/null and b/icons/mob/robot/iconset/dog_borgi-unused.dmi differ
diff --git a/icons/mob/robot/iconset/dog_borgi.dmi b/icons/mob/robot/iconset/dog_borgi.dmi
new file mode 100644
index 00000000000..7a7f26bb0e6
Binary files /dev/null and b/icons/mob/robot/iconset/dog_borgi.dmi differ
diff --git a/icons/mob/robot/iconset/dog_k9-unused.dmi b/icons/mob/robot/iconset/dog_k9-unused.dmi
new file mode 100644
index 00000000000..284cc330ed0
Binary files /dev/null and b/icons/mob/robot/iconset/dog_k9-unused.dmi differ
diff --git a/icons/mob/robot/iconset/dog_k9.dmi b/icons/mob/robot/iconset/dog_k9.dmi
new file mode 100644
index 00000000000..53e3cf9b545
Binary files /dev/null and b/icons/mob/robot/iconset/dog_k9.dmi differ
diff --git a/icons/mob/robot/iconset/dog_otie-unused.dmi b/icons/mob/robot/iconset/dog_otie-unused.dmi
new file mode 100644
index 00000000000..c73e1261ded
Binary files /dev/null and b/icons/mob/robot/iconset/dog_otie-unused.dmi differ
diff --git a/icons/mob/robot/iconset/dog_otie.dmi b/icons/mob/robot/iconset/dog_otie.dmi
new file mode 100644
index 00000000000..fcd69a96820
Binary files /dev/null and b/icons/mob/robot/iconset/dog_otie.dmi differ
diff --git a/icons/mob/robot/iconset/dog_pupdozer-unused.dmi b/icons/mob/robot/iconset/dog_pupdozer-unused.dmi
new file mode 100644
index 00000000000..e4f1ed8dca4
Binary files /dev/null and b/icons/mob/robot/iconset/dog_pupdozer-unused.dmi differ
diff --git a/icons/mob/robot/iconset/dog_pupdozer.dmi b/icons/mob/robot/iconset/dog_pupdozer.dmi
new file mode 100644
index 00000000000..8115c12c6f2
Binary files /dev/null and b/icons/mob/robot/iconset/dog_pupdozer.dmi differ
diff --git a/icons/mob/robot/iconset/dog_vale-unused.dmi b/icons/mob/robot/iconset/dog_vale-unused.dmi
new file mode 100644
index 00000000000..691b005e2cb
Binary files /dev/null and b/icons/mob/robot/iconset/dog_vale-unused.dmi differ
diff --git a/icons/mob/robot/iconset/dog_vale.dmi b/icons/mob/robot/iconset/dog_vale.dmi
new file mode 100644
index 00000000000..e274ef531af
Binary files /dev/null and b/icons/mob/robot/iconset/dog_vale.dmi differ
diff --git a/icons/mob/robot/iconset/drake_mizartz-originals.dmi b/icons/mob/robot/iconset/drake_mizartz-originals.dmi
new file mode 100644
index 00000000000..e5188835272
Binary files /dev/null and b/icons/mob/robot/iconset/drake_mizartz-originals.dmi differ
diff --git a/icons/mob/robot/iconset/drake_mizartz-unused.dmi b/icons/mob/robot/iconset/drake_mizartz-unused.dmi
new file mode 100644
index 00000000000..7666ec58c06
Binary files /dev/null and b/icons/mob/robot/iconset/drake_mizartz-unused.dmi differ
diff --git a/icons/mob/robot/iconset/drake_mizartz.dmi b/icons/mob/robot/iconset/drake_mizartz.dmi
new file mode 100644
index 00000000000..a24c7fc5f5f
Binary files /dev/null and b/icons/mob/robot/iconset/drake_mizartz.dmi differ
diff --git a/icons/mob/robot/iconset/grounded_decapod.dmi b/icons/mob/robot/iconset/grounded_decapod.dmi
new file mode 100644
index 00000000000..110192fb737
Binary files /dev/null and b/icons/mob/robot/iconset/grounded_decapod.dmi differ
diff --git a/icons/mob/robot/iconset/grounded_landmate.dmi b/icons/mob/robot/iconset/grounded_landmate.dmi
new file mode 100644
index 00000000000..dfd5ec7095b
Binary files /dev/null and b/icons/mob/robot/iconset/grounded_landmate.dmi differ
diff --git a/icons/mob/robot/iconset/grounded_mechoid.dmi b/icons/mob/robot/iconset/grounded_mechoid.dmi
new file mode 100644
index 00000000000..720260a863f
Binary files /dev/null and b/icons/mob/robot/iconset/grounded_mechoid.dmi differ
diff --git a/icons/mob/robot/iconset/grounded_spider.dmi b/icons/mob/robot/iconset/grounded_spider.dmi
new file mode 100644
index 00000000000..d94591eaaf4
Binary files /dev/null and b/icons/mob/robot/iconset/grounded_spider.dmi differ
diff --git a/icons/mob/robot/iconset/grounded_worm.dmi b/icons/mob/robot/iconset/grounded_worm.dmi
new file mode 100644
index 00000000000..d8a5a476ba8
Binary files /dev/null and b/icons/mob/robot/iconset/grounded_worm.dmi differ
diff --git a/icons/mob/robot/iconset/grounded_zoomba.dmi b/icons/mob/robot/iconset/grounded_zoomba.dmi
new file mode 100644
index 00000000000..14f4d1a9bce
Binary files /dev/null and b/icons/mob/robot/iconset/grounded_zoomba.dmi differ
diff --git a/icons/mob/robot/iconset/hover_coffin.dmi b/icons/mob/robot/iconset/hover_coffin.dmi
new file mode 100644
index 00000000000..f60496370c5
Binary files /dev/null and b/icons/mob/robot/iconset/hover_coffin.dmi differ
diff --git a/icons/mob/robot/iconset/hover_drone.dmi b/icons/mob/robot/iconset/hover_drone.dmi
new file mode 100644
index 00000000000..f6252e1fe87
Binary files /dev/null and b/icons/mob/robot/iconset/hover_drone.dmi differ
diff --git a/icons/mob/robot/iconset/hover_eyebot.dmi b/icons/mob/robot/iconset/hover_eyebot.dmi
new file mode 100644
index 00000000000..a433a84e648
Binary files /dev/null and b/icons/mob/robot/iconset/hover_eyebot.dmi differ
diff --git a/icons/mob/robot/iconset/hover_glitterfly.dmi b/icons/mob/robot/iconset/hover_glitterfly.dmi
new file mode 100644
index 00000000000..5b9c9c8bb98
Binary files /dev/null and b/icons/mob/robot/iconset/hover_glitterfly.dmi differ
diff --git a/icons/mob/robot/iconset/hover_handy.dmi b/icons/mob/robot/iconset/hover_handy.dmi
new file mode 100644
index 00000000000..da9ace9f82e
Binary files /dev/null and b/icons/mob/robot/iconset/hover_handy.dmi differ
diff --git a/icons/mob/robot/iconset/hover_x88.dmi b/icons/mob/robot/iconset/hover_x88.dmi
new file mode 100644
index 00000000000..e56da219d9b
Binary files /dev/null and b/icons/mob/robot/iconset/hover_x88.dmi differ
diff --git a/icons/mob/robot/iconset/raptor-unused.dmi b/icons/mob/robot/iconset/raptor-unused.dmi
new file mode 100644
index 00000000000..85bd9a40e14
Binary files /dev/null and b/icons/mob/robot/iconset/raptor-unused.dmi differ
diff --git a/icons/mob/robot/iconset/raptor.dmi b/icons/mob/robot/iconset/raptor.dmi
new file mode 100644
index 00000000000..8ab745199ea
Binary files /dev/null and b/icons/mob/robot/iconset/raptor.dmi differ
diff --git a/icons/mob/robot/iconset/unused.dmi b/icons/mob/robot/iconset/unused.dmi
new file mode 100644
index 00000000000..243c016307d
Binary files /dev/null and b/icons/mob/robot/iconset/unused.dmi differ
diff --git a/icons/mob/robots.dmi b/icons/mob/robots.dmi
deleted file mode 100644
index 64a01aef435..00000000000
Binary files a/icons/mob/robots.dmi and /dev/null differ
diff --git a/icons/mob/robots_wide.dmi b/icons/mob/robots_wide.dmi
deleted file mode 100644
index 1fd9380cd2a..00000000000
Binary files a/icons/mob/robots_wide.dmi and /dev/null differ
diff --git a/icons/mob/robots_wide_drake_credit.dmi b/icons/mob/robots_wide_drake_credit.dmi
deleted file mode 100644
index fa184ffafe6..00000000000
Binary files a/icons/mob/robots_wide_drake_credit.dmi and /dev/null differ
diff --git a/icons/mob/screen1_robot.dmi b/icons/mob/screen1_robot.dmi
index 8d31a894af1..a5a33ae5b7b 100644
Binary files a/icons/mob/screen1_robot.dmi and b/icons/mob/screen1_robot.dmi differ
diff --git a/icons/mob/screen1_robot_minimalist.dmi b/icons/mob/screen1_robot_minimalist.dmi
index c05e304763b..95a456de545 100644
Binary files a/icons/mob/screen1_robot_minimalist.dmi and b/icons/mob/screen1_robot_minimalist.dmi differ
diff --git a/icons/screen/actions/generic.dmi b/icons/screen/actions/generic.dmi
index 9ef8dd41f5e..7e16ef7c1b5 100644
Binary files a/icons/screen/actions/generic.dmi and b/icons/screen/actions/generic.dmi differ
diff --git a/icons/screen/hud/hologram/inventory.dmi b/icons/screen/hud/hologram/inventory.dmi
deleted file mode 100644
index c7e63170ebf..00000000000
Binary files a/icons/screen/hud/hologram/inventory.dmi and /dev/null differ
diff --git a/icons/screen/hud/midnight/inventory.dmi b/icons/screen/hud/midnight/inventory.dmi
deleted file mode 100644
index abef9e084a5..00000000000
Binary files a/icons/screen/hud/midnight/inventory.dmi and /dev/null differ
diff --git a/icons/screen/hud/minimalist/inventory.dmi b/icons/screen/hud/minimalist/inventory.dmi
deleted file mode 100644
index 3ab625d5f72..00000000000
Binary files a/icons/screen/hud/minimalist/inventory.dmi and /dev/null differ
diff --git a/icons/screen/hud/old/inventory.dmi b/icons/screen/hud/old/inventory.dmi
deleted file mode 100644
index 74d04a8afa6..00000000000
Binary files a/icons/screen/hud/old/inventory.dmi and /dev/null differ
diff --git a/icons/screen/hud/orange/inventory.dmi b/icons/screen/hud/orange/inventory.dmi
deleted file mode 100644
index a4f9751e0cd..00000000000
Binary files a/icons/screen/hud/orange/inventory.dmi and /dev/null differ
diff --git a/icons/screen/hud/robot/default.dmi b/icons/screen/hud/robot/default.dmi
new file mode 100644
index 00000000000..416ea865375
Binary files /dev/null and b/icons/screen/hud/robot/default.dmi differ
diff --git a/icons/screen/hud/robot/minimalist.dmi b/icons/screen/hud/robot/minimalist.dmi
new file mode 100644
index 00000000000..8beb8ec787a
Binary files /dev/null and b/icons/screen/hud/robot/minimalist.dmi differ
diff --git a/icons/screen/hud/robot/modules.dmi b/icons/screen/hud/robot/modules.dmi
new file mode 100644
index 00000000000..a3763981129
Binary files /dev/null and b/icons/screen/hud/robot/modules.dmi differ
diff --git a/icons/screen/hud/common/observer.dmi b/icons/screen/hud/styles/common/observer.dmi
similarity index 100%
rename from icons/screen/hud/common/observer.dmi
rename to icons/screen/hud/styles/common/observer.dmi
diff --git a/icons/screen/hud/common/shadekin.dmi b/icons/screen/hud/styles/common/shadekin.dmi
similarity index 100%
rename from icons/screen/hud/common/shadekin.dmi
rename to icons/screen/hud/styles/common/shadekin.dmi
diff --git a/icons/screen/hud/common/storage.dmi b/icons/screen/hud/styles/common/storage.dmi
similarity index 100%
rename from icons/screen/hud/common/storage.dmi
rename to icons/screen/hud/styles/common/storage.dmi
diff --git a/icons/screen/hud/hologram/32x32.dmi b/icons/screen/hud/styles/hologram/32x32.dmi
similarity index 100%
rename from icons/screen/hud/hologram/32x32.dmi
rename to icons/screen/hud/styles/hologram/32x32.dmi
diff --git a/icons/screen/hud/hologram/inventory-slot.dmi b/icons/screen/hud/styles/hologram/inventory-slot.dmi
similarity index 100%
rename from icons/screen/hud/hologram/inventory-slot.dmi
rename to icons/screen/hud/styles/hologram/inventory-slot.dmi
diff --git a/icons/screen/hud/hologram/inventory-wide.dmi b/icons/screen/hud/styles/hologram/inventory-wide.dmi
similarity index 100%
rename from icons/screen/hud/hologram/inventory-wide.dmi
rename to icons/screen/hud/styles/hologram/inventory-wide.dmi
diff --git a/icons/screen/hud/styles/hologram/inventory.dmi b/icons/screen/hud/styles/hologram/inventory.dmi
new file mode 100644
index 00000000000..a3490c85f6a
Binary files /dev/null and b/icons/screen/hud/styles/hologram/inventory.dmi differ
diff --git a/icons/screen/hud/midnight/32x32.dmi b/icons/screen/hud/styles/midnight/32x32.dmi
similarity index 100%
rename from icons/screen/hud/midnight/32x32.dmi
rename to icons/screen/hud/styles/midnight/32x32.dmi
diff --git a/icons/screen/hud/styles/midnight/inventory-robot.dmi b/icons/screen/hud/styles/midnight/inventory-robot.dmi
new file mode 100644
index 00000000000..cf74d73796c
Binary files /dev/null and b/icons/screen/hud/styles/midnight/inventory-robot.dmi differ
diff --git a/icons/screen/hud/midnight/inventory-slot.dmi b/icons/screen/hud/styles/midnight/inventory-slot.dmi
similarity index 100%
rename from icons/screen/hud/midnight/inventory-slot.dmi
rename to icons/screen/hud/styles/midnight/inventory-slot.dmi
diff --git a/icons/screen/hud/midnight/inventory-wide.dmi b/icons/screen/hud/styles/midnight/inventory-wide.dmi
similarity index 100%
rename from icons/screen/hud/midnight/inventory-wide.dmi
rename to icons/screen/hud/styles/midnight/inventory-wide.dmi
diff --git a/icons/screen/hud/styles/midnight/inventory.dmi b/icons/screen/hud/styles/midnight/inventory.dmi
new file mode 100644
index 00000000000..cabae87f6e5
Binary files /dev/null and b/icons/screen/hud/styles/midnight/inventory.dmi differ
diff --git a/icons/screen/hud/minimalist/32x32.dmi b/icons/screen/hud/styles/minimalist/32x32.dmi
similarity index 100%
rename from icons/screen/hud/minimalist/32x32.dmi
rename to icons/screen/hud/styles/minimalist/32x32.dmi
diff --git a/icons/screen/hud/minimalist/inventory-slot.dmi b/icons/screen/hud/styles/minimalist/inventory-slot.dmi
similarity index 100%
rename from icons/screen/hud/minimalist/inventory-slot.dmi
rename to icons/screen/hud/styles/minimalist/inventory-slot.dmi
diff --git a/icons/screen/hud/minimalist/inventory-wide.dmi b/icons/screen/hud/styles/minimalist/inventory-wide.dmi
similarity index 100%
rename from icons/screen/hud/minimalist/inventory-wide.dmi
rename to icons/screen/hud/styles/minimalist/inventory-wide.dmi
diff --git a/icons/screen/hud/styles/minimalist/inventory.dmi b/icons/screen/hud/styles/minimalist/inventory.dmi
new file mode 100644
index 00000000000..14d548b6144
Binary files /dev/null and b/icons/screen/hud/styles/minimalist/inventory.dmi differ
diff --git a/icons/screen/hud/old/32x32.dmi b/icons/screen/hud/styles/old/32x32.dmi
similarity index 100%
rename from icons/screen/hud/old/32x32.dmi
rename to icons/screen/hud/styles/old/32x32.dmi
diff --git a/icons/screen/hud/old/inventory-slot.dmi b/icons/screen/hud/styles/old/inventory-slot.dmi
similarity index 100%
rename from icons/screen/hud/old/inventory-slot.dmi
rename to icons/screen/hud/styles/old/inventory-slot.dmi
diff --git a/icons/screen/hud/old/inventory-wide.dmi b/icons/screen/hud/styles/old/inventory-wide.dmi
similarity index 100%
rename from icons/screen/hud/old/inventory-wide.dmi
rename to icons/screen/hud/styles/old/inventory-wide.dmi
diff --git a/icons/screen/hud/styles/old/inventory.dmi b/icons/screen/hud/styles/old/inventory.dmi
new file mode 100644
index 00000000000..e787cb283d6
Binary files /dev/null and b/icons/screen/hud/styles/old/inventory.dmi differ
diff --git a/icons/screen/hud/orange/32x32.dmi b/icons/screen/hud/styles/orange/32x32.dmi
similarity index 100%
rename from icons/screen/hud/orange/32x32.dmi
rename to icons/screen/hud/styles/orange/32x32.dmi
diff --git a/icons/screen/hud/orange/inventory-slot.dmi b/icons/screen/hud/styles/orange/inventory-slot.dmi
similarity index 100%
rename from icons/screen/hud/orange/inventory-slot.dmi
rename to icons/screen/hud/styles/orange/inventory-slot.dmi
diff --git a/icons/screen/hud/orange/inventory-wide.dmi b/icons/screen/hud/styles/orange/inventory-wide.dmi
similarity index 100%
rename from icons/screen/hud/orange/inventory-wide.dmi
rename to icons/screen/hud/styles/orange/inventory-wide.dmi
diff --git a/icons/screen/hud/styles/orange/inventory.dmi b/icons/screen/hud/styles/orange/inventory.dmi
new file mode 100644
index 00000000000..0346fc35d61
Binary files /dev/null and b/icons/screen/hud/styles/orange/inventory.dmi differ
diff --git a/icons/screen/hud/white/32x32.dmi b/icons/screen/hud/styles/white/32x32.dmi
similarity index 100%
rename from icons/screen/hud/white/32x32.dmi
rename to icons/screen/hud/styles/white/32x32.dmi
diff --git a/icons/screen/hud/white/inventory-slot.dmi b/icons/screen/hud/styles/white/inventory-slot.dmi
similarity index 100%
rename from icons/screen/hud/white/inventory-slot.dmi
rename to icons/screen/hud/styles/white/inventory-slot.dmi
diff --git a/icons/screen/hud/white/inventory-wide.dmi b/icons/screen/hud/styles/white/inventory-wide.dmi
similarity index 100%
rename from icons/screen/hud/white/inventory-wide.dmi
rename to icons/screen/hud/styles/white/inventory-wide.dmi
diff --git a/icons/screen/hud/styles/white/inventory.dmi b/icons/screen/hud/styles/white/inventory.dmi
new file mode 100644
index 00000000000..16e04291ef9
Binary files /dev/null and b/icons/screen/hud/styles/white/inventory.dmi differ
diff --git a/icons/screen/hud/white/inventory.dmi b/icons/screen/hud/white/inventory.dmi
deleted file mode 100644
index 0bfcec29919..00000000000
Binary files a/icons/screen/hud/white/inventory.dmi and /dev/null differ
diff --git a/maps/away_missions/archive/zresearchlabs.dmm b/maps/away_missions/archive/zresearchlabs.dmm
index 7810deea9b9..6d8aae62f33 100644
--- a/maps/away_missions/archive/zresearchlabs.dmm
+++ b/maps/away_missions/archive/zresearchlabs.dmm
@@ -3296,7 +3296,7 @@
/area/awaymission/labs/researchdivision)
"jH" = (
/obj/structure/table/reinforced,
-/obj/item/borg/upgrade/vtec,
+/obj/item/robot_upgrade/vtec,
/obj/item/cell/basic/tier_1/medium/empty,
/turf/simulated/floor{
icon_state = "white"
diff --git a/maps/stations/strelka/strelka-misc.dm b/maps/stations/strelka/strelka-misc.dm
index 5c4d7ab5e14..4e39b4fb27f 100644
--- a/maps/stations/strelka/strelka-misc.dm
+++ b/maps/stations/strelka/strelka-misc.dm
@@ -58,7 +58,7 @@
desc = "A small, normal-looking drone. It pilots the Cargo Supply shuttle... Yep, no MK2 pilot here..."
catalogue_data = list(/datum/category_item/catalogue/technology/drone/corrupt_maint_drone)
- icon = 'icons/mob/robots.dmi'
+ icon = 'icons/mob/robot-legacy.dmi'
icon_state = "corrupt-repairbot"
icon_living = "corrupt-repairbot"
hovering = FALSE // Can trigger landmines.
diff --git a/tgui/packages/tgui/components/ByondIconRef.tsx b/tgui/packages/tgui/components/ByondIconRef.tsx
index 5c98c116e50..d38895c7cd6 100644
--- a/tgui/packages/tgui/components/ByondIconRef.tsx
+++ b/tgui/packages/tgui/components/ByondIconRef.tsx
@@ -1,19 +1,9 @@
import { ReactNode } from "react";
import { Image } from "tgui-core/components";
+import { Direction } from "../constants";
import { BoxProps } from ".";
-enum Direction {
- NORTH = 1,
- SOUTH = 2,
- EAST = 4,
- WEST = 8,
- NORTHEAST = NORTH | EAST,
- NORTHWEST = NORTH | WEST,
- SOUTHEAST = SOUTH | EAST,
- SOUTHWEST = SOUTH | WEST,
-}
-
type Props = {
/** Required: The \ref of the icon */
iconRef: string;
diff --git a/tgui/packages/tgui/components/Centered.tsx b/tgui/packages/tgui/components/Centered.tsx
new file mode 100644
index 00000000000..75ef46903d6
--- /dev/null
+++ b/tgui/packages/tgui/components/Centered.tsx
@@ -0,0 +1,28 @@
+/**
+ * @file
+ * @license MIT
+ */
+
+import { Box } from 'tgui-core/components';
+
+import { BoxProps } from '.';
+
+/**
+ * I don't know who the fuck said "take 30 minutes to center a box in CSS"
+ * but they're fucking right and I'm not doing this ever fucking again
+ */
+export const Centered = (props: BoxProps) => {
+ return (
+
+ {props.children}
+
+ );
+};
diff --git a/tgui/packages/tgui/components/Sprite.tsx b/tgui/packages/tgui/components/Sprite.tsx
index 011761285a0..3059dd01428 100644
--- a/tgui/packages/tgui/components/Sprite.tsx
+++ b/tgui/packages/tgui/components/Sprite.tsx
@@ -4,7 +4,7 @@
*/
import { Box } from "tgui-core/components";
-import { BooleanLike, classes } from "tgui-core/react";
+import { classes } from "tgui-core/react";
import { BoxProps } from ".";
@@ -13,7 +13,6 @@ interface SpriteProps extends BoxProps {
readonly sizeKey: string;
readonly prefix?: string;
readonly sprite: string;
- readonly fill?: BooleanLike;
}
export const Sprite = (props: SpriteProps) => {
@@ -23,7 +22,6 @@ export const Sprite = (props: SpriteProps) => {
"Sprite",
`${props.sheet}${props.sizeKey}`,
props.prefix ? `${props.prefix}-${props.sprite}` : props.sprite,
- !!props.fill && 'Sprite--fill',
])} />
);
};
diff --git a/tgui/packages/tgui/interfaces/ColorMate.tsx b/tgui/packages/tgui/interfaces/ColorMate.tsx
index 79e56aa569c..3806c272bb1 100644
--- a/tgui/packages/tgui/interfaces/ColorMate.tsx
+++ b/tgui/packages/tgui/interfaces/ColorMate.tsx
@@ -32,7 +32,7 @@ export const ColorMate = (props) => {
width="100%"
height="100%"
style={{
- imageRendering: "crisp-edges",
+ imageRendering: "pixelated",
}}
/>
@@ -45,7 +45,7 @@ export const ColorMate = (props) => {
width="100%"
height="100%"
style={{
- imageRendering: "crisp-edges",
+ imageRendering: "pixelated",
}}
/>
diff --git a/tgui/packages/tgui/interfaces/actor_modal/RobotModulePicker.tsx b/tgui/packages/tgui/interfaces/actor_modal/RobotModulePicker.tsx
new file mode 100644
index 00000000000..fdd360de0eb
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/actor_modal/RobotModulePicker.tsx
@@ -0,0 +1,176 @@
+/**
+ * @file
+ * @license MIT
+ */
+
+import { useState } from 'react';
+import { Button, Section, Stack, Tabs } from 'tgui-core/components';
+
+import { useBackend } from '../../backend';
+import { ByondIconRef } from '../../components/ByondIconRef';
+import { Centered } from '../../components/Centered';
+import { Direction } from '../../constants';
+import { Window } from '../../layouts';
+
+export const ROBOT_MODULE_SPRITESHEET_NAME = 'roboticonsets';
+
+interface RobotModulePickerData {
+ modules: Record;
+}
+
+interface PickableModule {
+ id: string;
+ name: string;
+ frames: Record;
+}
+
+interface PickableFrame {
+ ref: string;
+ name: string;
+ spriteSizeKey: string;
+ spriteId: string;
+ iconRef: string;
+ iconState: string;
+}
+
+export const RobotModulePicker = (props) => {
+ const { act, data } = useBackend();
+ const [selectedModuleId, setSelectedModuleId] = useState(null);
+ const [selectedFrameRef, setSelectedFrameRef] = useState(null);
+
+ let selectedFrame: PickableFrame | null =
+ (selectedModuleId &&
+ selectedFrameRef &&
+ data.modules?.[selectedModuleId]?.frames?.[selectedFrameRef]) ||
+ null;
+
+ return (
+
+
+
+
+
+
+ {Object.entries(data.modules)
+ .sort(([a1, a2], [b1, b2]) => a2.name.localeCompare(b2.name))
+ .map(([id, module]) => {
+ return (
+ setSelectedModuleId(id)}
+ selected={id === selectedModuleId}
+ >
+ {module.name}
+
+ );
+ })}
+
+
+
+
+
+
+ {selectedModuleId &&
+ !!data.modules[selectedModuleId] &&
+ Object.entries(data.modules[selectedModuleId].frames)
+ .sort(([a1, a2], [b1, b2]) =>
+ a2.name.localeCompare(b2.name),
+ )
+ .map(([id, frame]) => {
+ return (
+ setSelectedFrameRef(id)}
+ selected={id === selectedFrameRef}
+ >
+ {frame.name}
+
+ );
+ })}
+
+
+
+
+
+
+
+ {selectedFrame && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+ act('pick', {
+ moduleId: selectedModuleId,
+ frameRef: selectedFrameRef,
+ })
+ }
+ textAlign="center"
+ >
+ Specialize
+
+
+
+
+
+
+
+
+ );
+};