diff --git a/.tgs4.yml b/.tgs4.yml
index 932a3a6672ff..d137f1dbc8a8 100644
--- a/.tgs4.yml
+++ b/.tgs4.yml
@@ -2,6 +2,7 @@ static_files:
- name: config
populate: true
- name: data
+ - name: tmp
linux_scripts:
PreCompile.sh: tools/tgs4_scripts/PreCompile.sh
windows_scripts:
diff --git a/README.md b/README.md
index 8b1f8b6807bb..b0228569fead 100644
--- a/README.md
+++ b/README.md
@@ -56,8 +56,12 @@ These are also the folders you are likely going to encounter while managing the
- /config: server configuration
- /legacy: legacy configuration data go in here
- /data: server persistent data
+ - /asset-cache - default location for caching of generated assets
+ - /asset-root - default location for generated assets to be served in webroot mode
- /logs: logs are dumped in here
- /players: player data, like saves and characters get dumped in here
+- /tmp: server scratch space
+ - /assets - for asset generation
You only need to make the top level folders (e.g. config, data) static folders in TGS4.
diff --git a/SQL/database_schema.sql b/SQL/database_schema.sql
index f7237e6ac2db..f1b50df6fd40 100644
--- a/SQL/database_schema.sql
+++ b/SQL/database_schema.sql
@@ -381,13 +381,6 @@ CREATE TABLE IF NOT EXISTS `%_PREFIX_%privacy` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-CREATE TABLE IF NOT EXISTS `%_PREFIX_%vr_player_hours` (
- `ckey` varchar(32) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL,
- `department` varchar(64) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL,
- `hours` double NOT NULL,
- PRIMARY KEY (`ckey`,`department`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-
CREATE TABLE IF NOT EXISTS `%_PREFIX_%death` (
`id` INT(11) NOT NULL AUTO_INCREMENT ,
`pod` TEXT NOT NULL COMMENT 'Place of death' ,
diff --git a/citadel.dme b/citadel.dme
index dc6f673517fc..decb8575d4d4 100644
--- a/citadel.dme
+++ b/citadel.dme
@@ -46,6 +46,7 @@
#include "code\__DEFINES\dna.dm"
#include "code\__DEFINES\event_args.dm"
#include "code\__DEFINES\fonts.dm"
+#include "code\__DEFINES\frames.dm"
#include "code\__DEFINES\gamemode.dm"
#include "code\__DEFINES\holidays.dm"
#include "code\__DEFINES\holomap.dm"
@@ -149,6 +150,7 @@
#include "code\__DEFINES\combat\shieldcall.dm"
#include "code\__DEFINES\controllers\_repositories.dm"
#include "code\__DEFINES\controllers\_subsystems.dm"
+#include "code\__DEFINES\controllers\assets.dm"
#include "code\__DEFINES\controllers\dbcore.dm"
#include "code\__DEFINES\controllers\persistence.dm"
#include "code\__DEFINES\controllers\processing.dm"
@@ -159,10 +161,10 @@
#include "code\__DEFINES\dcs\flags.dm"
#include "code\__DEFINES\dcs\helpers.dm"
#include "code\__DEFINES\dcs\components\riding.dm"
+#include "code\__DEFINES\dcs\signals\global_signals.dm"
#include "code\__DEFINES\dcs\signals\signals_area.dm"
#include "code\__DEFINES\dcs\signals\signals_datum.dm"
#include "code\__DEFINES\dcs\signals\signals_fish.dm"
-#include "code\__DEFINES\dcs\signals\signals_global.dm"
#include "code\__DEFINES\dcs\signals\signals_object.dm"
#include "code\__DEFINES\dcs\signals\signals_turf.dm"
#include "code\__DEFINES\dcs\signals\datums\signals_beam.dm"
@@ -255,6 +257,7 @@
#include "code\__DEFINES\mobs\mobility.dm"
#include "code\__DEFINES\mobs\mobs.dm"
#include "code\__DEFINES\mobs\organs.dm"
+#include "code\__DEFINES\mobs\rendering.dm"
#include "code\__DEFINES\mobs\silicon_privileges.dm"
#include "code\__DEFINES\mobs\sprite_accessories.dm"
#include "code\__DEFINES\mobs\stat.dm"
@@ -357,6 +360,7 @@
#include "code\__HELPERS\stoplag.dm"
#include "code\__HELPERS\storage.dm"
#include "code\__HELPERS\text.dm"
+#include "code\__HELPERS\thermal.dm"
#include "code\__HELPERS\time.dm"
#include "code\__HELPERS\turfs.dm"
#include "code\__HELPERS\type_processing.dm"
@@ -381,6 +385,7 @@
#include "code\__HELPERS\icons\flatten.dm"
#include "code\__HELPERS\icons\hologram.dm"
#include "code\__HELPERS\icons\metadata.dm"
+#include "code\__HELPERS\icons\render.dm"
#include "code\__HELPERS\lists\_string_lists.dm"
#include "code\__HELPERS\lists\asset_sorted.dm"
#include "code\__HELPERS\lists\associations.dm"
@@ -508,7 +513,6 @@
#include "code\controllers\subsystem\chat.dm"
#include "code\controllers\subsystem\dcs.dm"
#include "code\controllers\subsystem\DPC.dm"
-#include "code\controllers\subsystem\early_assets.dm"
#include "code\controllers\subsystem\early_init.dm"
#include "code\controllers\subsystem\emergency_shuttle.dm"
#include "code\controllers\subsystem\events.dm"
@@ -535,7 +539,6 @@
#include "code\controllers\subsystem\overmap.dm"
#include "code\controllers\subsystem\parallax.dm"
#include "code\controllers\subsystem\pathfinder.dm"
-#include "code\controllers\subsystem\persist_vr.dm"
#include "code\controllers\subsystem\photography.dm"
#include "code\controllers\subsystem\ping.dm"
#include "code\controllers\subsystem\planets.dm"
@@ -564,7 +567,7 @@
#include "code\controllers\subsystem\vis_overlays.dm"
#include "code\controllers\subsystem\vote.dm"
#include "code\controllers\subsystem\xenoarch.dm"
-#include "code\controllers\subsystem\zmimic.dm"
+#include "code\controllers\subsystem\zcopy.dm"
#include "code\controllers\subsystem\characters\_characters.dm"
#include "code\controllers\subsystem\characters\backgrounds.dm"
#include "code\controllers\subsystem\characters\character_species.dm"
@@ -770,7 +773,9 @@
#include "code\datums\design\designs\machines\parts.dm"
#include "code\datums\design\designs\medical\_medical.dm"
#include "code\datums\design\designs\medical\chemistry.dm"
+#include "code\datums\design\designs\medical\consumables.dm"
#include "code\datums\design\designs\medical\misc.dm"
+#include "code\datums\design\designs\medical\sterile_wear.dm"
#include "code\datums\design\designs\misc\_misc.dm"
#include "code\datums\design\designs\misc\drinking_glass.dm"
#include "code\datums\design\designs\tools\_tool.dm"
@@ -856,6 +861,7 @@
#include "code\datums\recipe\material_recipes\furniture.dm"
#include "code\datums\recipe\material_recipes\structures.dm"
#include "code\datums\recipe\material_recipes\tools.dm"
+#include "code\datums\recipe\stack_recipes\frame.dm"
#include "code\datums\recipe\stack_recipes\misc.dm"
#include "code\datums\recipe\stack_recipes\structures.dm"
#include "code\datums\repositories\ammomaterial.dm"
@@ -1334,6 +1340,7 @@
#include "code\game\machinery\embedded_controller\simple_docking_controller.dm"
#include "code\game\machinery\lathes\autolathe.dm"
#include "code\game\machinery\lathes\lathe.dm"
+#include "code\game\machinery\lathes\medical_lathe.dm"
#include "code\game\machinery\misc\bioscan_antenna.dm"
#include "code\game\machinery\pipe\construction.dm"
#include "code\game\machinery\pipe\pipe_dispenser.dm"
@@ -1520,6 +1527,7 @@
#include "code\game\objects\effects\decals\remains.dm"
#include "code\game\objects\effects\decals\warning_stripes.dm"
#include "code\game\objects\effects\decals\posters\bs12.dm"
+#include "code\game\objects\effects\decals\posters\citadel.dm"
#include "code\game\objects\effects\decals\posters\polarisposters.dm"
#include "code\game\objects\effects\map_effects\beam_point.dm"
#include "code\game\objects\effects\map_effects\effect_emitter.dm"
@@ -1542,7 +1550,6 @@
#include "code\game\objects\effects\temporary_visuals\projectiles\projectile_effects.dm"
#include "code\game\objects\effects\temporary_visuals\projectiles\tracer.dm"
#include "code\game\objects\items\antag_spawners.dm"
-#include "code\game\objects\items\apc_frame.dm"
#include "code\game\objects\items\balls.dm"
#include "code\game\objects\items\bells.dm"
#include "code\game\objects\items\blueprints.dm"
@@ -1811,6 +1818,7 @@
#include "code\game\objects\items\weapons\tanks\tank_types.dm"
#include "code\game\objects\items\weapons\tanks\tanks.dm"
#include "code\game\objects\random\_random.dm"
+#include "code\game\objects\random\books.dm"
#include "code\game\objects\random\guns_and_ammo.dm"
#include "code\game\objects\random\maintenance.dm"
#include "code\game\objects\random\mapping.dm"
@@ -1958,10 +1966,9 @@
#include "code\game\rendering\client.dm"
#include "code\game\rendering\mob.dm"
#include "code\game\rendering\screen.dm"
-#include "code\game\rendering\atom_huds\alternate_appearance.dm"
#include "code\game\rendering\atom_huds\atom_hud.dm"
-#include "code\game\rendering\atom_huds\data_huds.dm"
-#include "code\game\rendering\atom_huds\other_huds.dm"
+#include "code\game\rendering\atom_huds\atom_hud_provider.dm"
+#include "code\game\rendering\atom_huds\legacy.dm"
#include "code\game\rendering\clickcatcher\clickcatcher.dm"
#include "code\game\rendering\fullscreen\fullscreen.dm"
#include "code\game\rendering\legacy\ability_screen_objects.dm"
@@ -2003,8 +2010,6 @@
#include "code\game\turfs\turf_flick_animations.dm"
#include "code\game\turfs\turf_movement.dm"
#include "code\game\turfs\unsimulated.dm"
-#include "code\game\turfs\initialization\init.dm"
-#include "code\game\turfs\initialization\maintenance.dm"
#include "code\game\turfs\simulated\sky.dm"
#include "code\game\turfs\simulated\floor\floor.dm"
#include "code\game\turfs\simulated\floor\floor_acts.dm"
@@ -2277,14 +2282,15 @@
#include "code\modules\assembly\signaler.dm"
#include "code\modules\assembly\timer.dm"
#include "code\modules\assembly\voice.dm"
-#include "code\modules\asset_cache\asset_cache_client.dm"
-#include "code\modules\asset_cache\asset_cache_item.dm"
-#include "code\modules\asset_cache\asset_list.dm"
+#include "code\modules\asset_cache\asset_item.dm"
+#include "code\modules\asset_cache\asset_pack.dm"
+#include "code\modules\asset_cache\asset_transport.dm"
+#include "code\modules\asset_cache\client.dm"
#include "code\modules\asset_cache\assets\arcade.dm"
#include "code\modules\asset_cache\assets\chat.dm"
#include "code\modules\asset_cache\assets\common.dm"
#include "code\modules\asset_cache\assets\condiments.dm"
-#include "code\modules\asset_cache\assets\fish.dm"
+#include "code\modules\asset_cache\assets\fishing.dm"
#include "code\modules\asset_cache\assets\fontawesome.dm"
#include "code\modules\asset_cache\assets\genetics.dm"
#include "code\modules\asset_cache\assets\headers.dm"
@@ -2292,7 +2298,6 @@
#include "code\modules\asset_cache\assets\jquery.dm"
#include "code\modules\asset_cache\assets\legacy_nanomaps.dm"
#include "code\modules\asset_cache\assets\legacy_nanoui.dm"
-#include "code\modules\asset_cache\assets\legacy_vstation.dm"
#include "code\modules\asset_cache\assets\loadout.dm"
#include "code\modules\asset_cache\assets\materials.dm"
#include "code\modules\asset_cache\assets\moods.dm"
@@ -2312,8 +2317,13 @@
#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\transports\asset_transport.dm"
-#include "code\modules\asset_cache\transports\webroot_transport.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"
+#include "code\modules\asset_cache\packs\spritesheet.dm"
+#include "code\modules\asset_cache\packs\spritesheet\simple.dm"
+#include "code\modules\asset_cache\transports\browse_rsc.dm"
+#include "code\modules\asset_cache\transports\webroot.dm"
#include "code\modules\atmospherics\atmosphere\atmosphere.dm"
#include "code\modules\atmospherics\atmosphere\planet.dm"
#include "code\modules\atmospherics\environmental\atom.dm"
@@ -2424,6 +2434,34 @@
#include "code\modules\blob2\overmind\overmind.dm"
#include "code\modules\blob2\overmind\powers.dm"
#include "code\modules\blob2\overmind\types.dm"
+#include "code\modules\bodysets\sprite_accessories.dm"
+#include "code\modules\bodysets\organic\akula.dm"
+#include "code\modules\bodysets\organic\apidean.dm"
+#include "code\modules\bodysets\organic\krisitik.dm"
+#include "code\modules\bodysets\organic\monkey.dm"
+#include "code\modules\bodysets\organic\moth.dm"
+#include "code\modules\bodysets\organic\naramadi.dm"
+#include "code\modules\bodysets\organic\nevrean.dm"
+#include "code\modules\bodysets\organic\rapala.dm"
+#include "code\modules\bodysets\organic\shadekin.dm"
+#include "code\modules\bodysets\organic\tajaran.dm"
+#include "code\modules\bodysets\organic\teshari.dm"
+#include "code\modules\bodysets\organic\unathi.dm"
+#include "code\modules\bodysets\organic\vasilissan.dm"
+#include "code\modules\bodysets\organic\vulpkanin.dm"
+#include "code\modules\bodysets\organic\xenohybrid.dm"
+#include "code\modules\bodysets\organic\xenomorph.dm"
+#include "code\modules\bodysets\organic\zorren_flatlander.dm"
+#include "code\modules\bodysets\organic\zorren_highlander.dm"
+#include "code\modules\bodysets\synthetic\eggnerd.dm"
+#include "code\modules\bodysets\synthetic\eggnerd_red.dm"
+#include "code\modules\bodysets\synthetic\oss_akula.dm"
+#include "code\modules\bodysets\synthetic\oss_lizard.dm"
+#include "code\modules\bodysets\synthetic\oss_naramadi.dm"
+#include "code\modules\bodysets\synthetic\oss_nevrean.dm"
+#include "code\modules\bodysets\synthetic\oss_spider.dm"
+#include "code\modules\bodysets\synthetic\oss_tajaran.dm"
+#include "code\modules\bodysets\synthetic\oss_vulpkanin.dm"
#include "code\modules\busy_space\organizations.dm"
#include "code\modules\cargo\supplypacks\_supplypacks.dm"
#include "code\modules\cargo\supplypacks\atmospherics.dm"
@@ -2452,7 +2490,6 @@
#include "code\modules\client\client_procs.dm"
#include "code\modules\client\connection.dm"
#include "code\modules\client\cutscene.dm"
-#include "code\modules\client\legacy.dm"
#include "code\modules\client\perspective.dm"
#include "code\modules\client\security.dm"
#include "code\modules\client\spam_prevention.dm"
@@ -2518,6 +2555,7 @@
#include "code\modules\clothing\masks\_mask.dm"
#include "code\modules\clothing\masks\boxing.dm"
#include "code\modules\clothing\masks\breath.dm"
+#include "code\modules\clothing\masks\breath_warmer.dm"
#include "code\modules\clothing\masks\gasmask.dm"
#include "code\modules\clothing\masks\miscellaneous.dm"
#include "code\modules\clothing\masks\voice.dm"
@@ -2793,6 +2831,14 @@
#include "code\modules\food\machinery\appliance\grill.dm"
#include "code\modules\food\machinery\appliance\oven.dm"
#include "code\modules\food\structures\icecream_cart.dm"
+#include "code\modules\frames\frame.dm"
+#include "code\modules\frames\frame_stage.dm"
+#include "code\modules\frames\frame_step.dm"
+#include "code\modules\frames\item.dm"
+#include "code\modules\frames\structure.dm"
+#include "code\modules\frames\types\apc.dm"
+#include "code\modules\frames\types\fire_alarm.dm"
+#include "code\modules\frames\types\solar_panel.dm"
#include "code\modules\gamemaster\controller.dm"
#include "code\modules\gamemaster\defines.dm"
#include "code\modules\gamemaster\helpers.dm"
@@ -3124,6 +3170,7 @@
#include "code\modules\library\lib_machines.dm"
#include "code\modules\library\lib_readme.dm"
#include "code\modules\library\hardcode_library\_library.dm"
+#include "code\modules\library\hardcode_library\reference\xenomorph_facts.dm"
#include "code\modules\lighting\__lighting_docs.dm"
#include "code\modules\lighting\_lighting_defs.dm"
#include "code\modules\lighting\emissive_blocker.dm"
@@ -3235,6 +3282,8 @@
#include "code\modules\mapping\map_helpers\component_injector.dm"
#include "code\modules\mapping\map_helpers\engine_loader.dm"
#include "code\modules\mapping\map_helpers\paint.dm"
+#include "code\modules\mapping\map_helpers\access_helper\access_helper.dm"
+#include "code\modules\mapping\map_helpers\access_helper\airlock.dm"
#include "code\modules\mapping\map_helpers\network_builder\_network_builder.dm"
#include "code\modules\mapping\map_helpers\network_builder\power_cable.dm"
#include "code\modules\mapping\spawner\_spawner.dm"
@@ -3503,10 +3552,10 @@
#include "code\modules\mob\inventory\hands.dm"
#include "code\modules\mob\inventory\helpers.dm"
#include "code\modules\mob\inventory\inventory.dm"
+#include "code\modules\mob\inventory\inventory_slot.dm"
#include "code\modules\mob\inventory\items.dm"
#include "code\modules\mob\inventory\mobs.dm"
#include "code\modules\mob\inventory\rendering.dm"
-#include "code\modules\mob\inventory\slot_meta.dm"
#include "code\modules\mob\inventory\stripping.dm"
#include "code\modules\mob\living\autohiss.dm"
#include "code\modules\mob\living\butchering.dm"
@@ -3559,6 +3608,7 @@
#include "code\modules\mob\living\carbon\organs.dm"
#include "code\modules\mob\living\carbon\perspective.dm"
#include "code\modules\mob\living\carbon\physiology.dm"
+#include "code\modules\mob\living\carbon\rendering.dm"
#include "code\modules\mob\living\carbon\resist.dm"
#include "code\modules\mob\living\carbon\shock.dm"
#include "code\modules\mob\living\carbon\taste.dm"
@@ -3597,7 +3647,6 @@
#include "code\modules\mob\living\carbon\human\dummy.dm"
#include "code\modules\mob\living\carbon\human\emote.dm"
#include "code\modules\mob\living\carbon\human\examine.dm"
-#include "code\modules\mob\living\carbon\human\gradient.dm"
#include "code\modules\mob\living\carbon\human\health.dm"
#include "code\modules\mob\living\carbon\human\human.dm"
#include "code\modules\mob\living\carbon\human\human_attackhand.dm"
@@ -3620,6 +3669,7 @@
#include "code\modules\mob\living\carbon\human\movement.dm"
#include "code\modules\mob\living\carbon\human\npcs.dm"
#include "code\modules\mob\living\carbon\human\pain.dm"
+#include "code\modules\mob\living\carbon\human\rendering.dm"
#include "code\modules\mob\living\carbon\human\riding.dm"
#include "code\modules\mob\living\carbon\human\say.dm"
#include "code\modules\mob\living\carbon\human\status_procs.dm"
@@ -4214,9 +4264,7 @@
#include "code\modules\power\power.dm"
#include "code\modules\power\powernet.dm"
#include "code\modules\power\powersupply.dm"
-#include "code\modules\power\solar.dm"
#include "code\modules\power\terminal.dm"
-#include "code\modules\power\tracker.dm"
#include "code\modules\power\turbine.dm"
#include "code\modules\power\antimatter\containment_jar.dm"
#include "code\modules\power\antimatter\control.dm"
@@ -4265,6 +4313,9 @@
#include "code\modules\power\smes\coil.dm"
#include "code\modules\power\smes\smes.dm"
#include "code\modules\power\smes\smes_construction.dm"
+#include "code\modules\power\solar\solar.dm"
+#include "code\modules\power\solar\solar_control.dm"
+#include "code\modules\power\solar\tracker.dm"
#include "code\modules\power\supermatter\setup_supermatter.dm"
#include "code\modules\power\supermatter\supermatter.dm"
#include "code\modules\power\tesla\coil.dm"
@@ -4689,8 +4740,11 @@
#include "code\modules\spells\targeted\projectile\fireball.dm"
#include "code\modules\spells\targeted\projectile\magic_missile.dm"
#include "code\modules\spells\targeted\projectile\projectile.dm"
-#include "code\modules\sprite_accessories\sprite_accessories.dm"
-#include "code\modules\sprite_accessories\ears\_ears.dm"
+#include "code\modules\sprite_accessories\ears.dm"
+#include "code\modules\sprite_accessories\hair.dm"
+#include "code\modules\sprite_accessories\sprite_accessory.dm"
+#include "code\modules\sprite_accessories\tail.dm"
+#include "code\modules\sprite_accessories\wings.dm"
#include "code\modules\sprite_accessories\ears\antenna.dm"
#include "code\modules\sprite_accessories\ears\horns.dm"
#include "code\modules\sprite_accessories\ears\misc.dm"
@@ -4727,17 +4781,18 @@
#include "code\modules\sprite_accessories\facial_hair\unathi.dm"
#include "code\modules\sprite_accessories\facial_hair\vox.dm"
#include "code\modules\sprite_accessories\facial_hair\vulpkanin.dm"
-#include "code\modules\sprite_accessories\hair\_hair.dm"
-#include "code\modules\sprite_accessories\hair\sergal.dm"
-#include "code\modules\sprite_accessories\hair\shadekin.dm"
-#include "code\modules\sprite_accessories\hair\skrell.dm"
-#include "code\modules\sprite_accessories\hair\tajaran.dm"
-#include "code\modules\sprite_accessories\hair\teshari.dm"
-#include "code\modules\sprite_accessories\hair\unathi.dm"
-#include "code\modules\sprite_accessories\hair\unathi_digitigrade.dm"
-#include "code\modules\sprite_accessories\hair\vox.dm"
-#include "code\modules\sprite_accessories\hair\vulpkanin.dm"
-#include "code\modules\sprite_accessories\hair\xenomorph.dm"
+#include "code\modules\sprite_accessories\hair\hair.dm"
+#include "code\modules\sprite_accessories\hair\legacy.dm"
+#include "code\modules\sprite_accessories\hair\species\sergal.dm"
+#include "code\modules\sprite_accessories\hair\species\shadekin.dm"
+#include "code\modules\sprite_accessories\hair\species\skrell.dm"
+#include "code\modules\sprite_accessories\hair\species\tajaran.dm"
+#include "code\modules\sprite_accessories\hair\species\teshari.dm"
+#include "code\modules\sprite_accessories\hair\species\unathi.dm"
+#include "code\modules\sprite_accessories\hair\species\unathi_digitigrade.dm"
+#include "code\modules\sprite_accessories\hair\species\vox.dm"
+#include "code\modules\sprite_accessories\hair\species\vulpkanin.dm"
+#include "code\modules\sprite_accessories\hair\species\xenomorph.dm"
#include "code\modules\sprite_accessories\markings\_markings.dm"
#include "code\modules\sprite_accessories\markings\diona_stuff.dm"
#include "code\modules\sprite_accessories\markings\eye_stuff.dm"
@@ -4753,7 +4808,6 @@
#include "code\modules\sprite_accessories\markings\vulpkanin.dm"
#include "code\modules\sprite_accessories\markings\werebeast.dm"
#include "code\modules\sprite_accessories\markings\zorren.dm"
-#include "code\modules\sprite_accessories\tail\_tail.dm"
#include "code\modules\sprite_accessories\tail\bearlike.dm"
#include "code\modules\sprite_accessories\tail\bird.dm"
#include "code\modules\sprite_accessories\tail\bovine.dm"
@@ -4782,9 +4836,10 @@
#include "code\modules\sprite_accessories\tail\species\vulpkanin.dm"
#include "code\modules\sprite_accessories\tail\species\xenomorph.dm"
#include "code\modules\sprite_accessories\tail\species\zorren.dm"
-#include "code\modules\sprite_accessories\wings\_wings.dm"
#include "code\modules\sprite_accessories\wings\bat.dm"
#include "code\modules\sprite_accessories\wings\draconic_furries.dm"
+#include "code\modules\sprite_accessories\wings\feathered-angel.dm"
+#include "code\modules\sprite_accessories\wings\feathered-generic.dm"
#include "code\modules\sprite_accessories\wings\feathered.dm"
#include "code\modules\sprite_accessories\wings\insect_not_really_wings.dm"
#include "code\modules\sprite_accessories\wings\misc.dm"
@@ -4942,7 +4997,6 @@
#include "code\modules\vore\lick_wounds.dm"
#include "code\modules\vore\trycatch_vr.dm"
#include "code\modules\vore\appearance\spider_taur_powers_vr.dm"
-#include "code\modules\vore\appearance\update_icons_vr.dm"
#include "code\modules\vore\eating\belly_dat_vr.dm"
#include "code\modules\vore\eating\belly_obj_vr.dm"
#include "code\modules\vore\eating\bellymodes_tf_vr.dm"
@@ -5039,10 +5093,11 @@
#include "code\modules\xenobio\items\slimepotions.dm"
#include "code\modules\xenobio\items\weapons.dm"
#include "code\modules\xenobio\machinery\processor.dm"
-#include "donator\legacy.dm"
-#include "donator\djkouta\donator_cloak.dm"
-#include "donator\timothytea\donator_bedsheet.dm"
-#include "donator\unclebourbon\pmc_mask.dm"
+#include "fluff\donator\legacy.dm"
+#include "fluff\donator\djkouta\donator_cloak.dm"
+#include "fluff\donator\timothytea\donator_bedsheet.dm"
+#include "fluff\donator\unclebourbon\pmc_mask.dm"
+#include "fluff\plushies\plushies.dm"
#include "interface\interface.dm"
#include "interface\stylesheet.dm"
#include "interface\skin.dmf"
@@ -5065,6 +5120,7 @@
#include "maps\overmap\shuttles\pirateskiff.dm"
#include "maps\overmap\shuttles\skipjack.dm"
#include "maps\overmap\shuttles\specialops.dm"
+#include "maps\rift\engines.dm"
#include "maps\rift\misc.dm"
#include "maps\rift\rift.dm"
#include "maps\rift\shuttles.dm"
diff --git a/code/__DEFINES/_flags/obj_flags.dm b/code/__DEFINES/_flags/obj_flags.dm
index d002e89ea78e..d18c75ed8434 100644
--- a/code/__DEFINES/_flags/obj_flags.dm
+++ b/code/__DEFINES/_flags/obj_flags.dm
@@ -20,7 +20,9 @@
/// Materials have been initialized
#define OBJ_MATERIAL_INITIALIZED (1<<9)
/// no sculpting
-#define OBJ_NO_SCULPTING (1<<10)
+#define OBJ_NO_SCULPTING (1<<10)
+/// wall-mounted; facing *towards* the wall we're mounted on (e.g. be NORTH if we're shifted north)
+#define OBJ_WALL_MOUNTED (1<<11)
DEFINE_BITFIELD(obj_flags, list(
BITFIELD(OBJ_EMAGGED),
@@ -34,4 +36,26 @@ DEFINE_BITFIELD(obj_flags, list(
BITFIELD(OBJ_MATERIAL_PARTS_MODIFIED),
BITFIELD(OBJ_MATERIAL_INITIALIZED),
BITFIELD(OBJ_NO_SCULPTING),
+ BITFIELD_NAMED("Wall Mounted", OBJ_WALL_MOUNTED),
+))
+
+//* /obj/var/obj_rotation_flags
+
+/// obj rotation enabled; we'll go to context menu
+#define OBJ_ROTATION_ENABLED (1<<0)
+/// allow defaulting on context menu
+#define OBJ_ROTATION_DEFAULTING (1<<1)
+/// do not perform standard anchor check
+#define OBJ_ROTATION_NO_ANCHOR_CHECK (1<<2)
+/// rotate CCW
+#define OBJ_ROTATION_CCW (1<<3)
+/// give optiosn to rotate both directions
+#define OBJ_ROTATION_BIDIRECTIONAL (1<<4)
+
+DEFINE_BITFIELD(obj_rotation_flags, list(
+ BITFIELD_NAMED("Enabled", OBJ_ROTATION_ENABLED),
+ BITFIELD_NAMED("Defaulting", OBJ_ROTATION_DEFAULTING),
+ BITFIELD_NAMED("Allow Anchored", OBJ_ROTATION_NO_ANCHOR_CHECK),
+ BITFIELD_NAMED("Counterclockwise", OBJ_ROTATION_CCW),
+ BITFIELD_NAMED("Show Both Directions", OBJ_ROTATION_BIDIRECTIONAL),
))
diff --git a/code/__DEFINES/_protect.dm b/code/__DEFINES/_protect.dm
index c865bd363ed6..00d66d60dc06 100644
--- a/code/__DEFINES/_protect.dm
+++ b/code/__DEFINES/_protect.dm
@@ -1,8 +1,11 @@
-#define GENERAL_PROTECT_DATUM(Path)\
+/**
+ * Completely occludes a path from view variable interactions.
+ */
+#define VV_PROTECT(Path)\
##Path/can_vv_get(var_name){\
return FALSE;\
}\
-##Path/vv_edit_var(var_name, var_value){\
+##Path/vv_edit_var(var_name, var_value, mass_edit, raw_edit){\
return FALSE;\
}\
##Path/CanProcCall(procname){\
@@ -11,3 +14,16 @@
##Path/can_vv_mark(){\
return FALSE;\
}
+
+/**
+ * Makes a path read-only to view variables.
+ *
+ * * Does not prevent the path from being marked!
+ */
+#define VV_PROTECT_READONLY(Path)\
+##Path/vv_edit_var(var_name, var_value, mass_edit, raw_edit){\
+ return FALSE;\
+}\
+##Path/CanProcCall(procname){\
+ return FALSE;\
+}
diff --git a/code/__DEFINES/access.dm b/code/__DEFINES/access.dm
index 83a45bff88da..fa652ddb0ae6 100644
--- a/code/__DEFINES/access.dm
+++ b/code/__DEFINES/access.dm
@@ -162,6 +162,7 @@ STANDARD_ACCESS_DATUM(ACCESS_COMMAND_UPLOAD, station/command/upload, "AI Upload"
#define ACCESS_COMMAND_TELEPORTER 17
STANDARD_ACCESS_DATUM(ACCESS_COMMAND_TELEPORTER, station/command/teleporter, "Teleporter")
+// todo: rename / repath to general high-security storage for command
#define ACCESS_COMMAND_EVA 18
STANDARD_ACCESS_DATUM(ACCESS_COMMAND_EVA, station/command/eva, "EVA")
diff --git a/code/__DEFINES/controllers/_subsystems.dm b/code/__DEFINES/controllers/_subsystems.dm
index f4a3c6f21640..48416ad5c622 100644
--- a/code/__DEFINES/controllers/_subsystems.dm
+++ b/code/__DEFINES/controllers/_subsystems.dm
@@ -77,12 +77,28 @@ DEFINE_BITFIELD(runlevels, list(
// todo: tg init brackets
+// core security system, used by client/New()
#define INIT_ORDER_FAIL2TOPIC 200
+// core security system, used by client/New()
#define INIT_ORDER_IPINTEL 197
+
+// core timing system, used by almost everything
#define INIT_ORDER_TIMER 195
+// just about every feature on the server requires the database backend
+// for storage and durability of permeance.
#define INIT_ORDER_DBCORE 190
+// repository is just struct storage. its things depend on database,
+// but should depend on nothing else.
+//
+// for the rare occasion when a prototype requires asset registration,
+// it should be able to recognize if SSassets is ready,
+// and only queue an udpate if its asset is already loaded.
+#define INIT_ORDER_REPOSITORY 187
+// early init initializes what is basically expensive global variables. it needs to go before assets.
#define INIT_ORDER_EARLY_INIT 185
-#define INIT_ORDER_REPOSITORY 180
+// assets is loaded early because things hook into this to register *their* assets
+#define INIT_ORDER_ASSETS 180
+
#define INIT_ORDER_STATPANELS 170
#define INIT_ORDER_PREFERENCES 165
#define INIT_ORDER_INPUT 160
@@ -93,7 +109,6 @@ DEFINE_BITFIELD(runlevels, list(
#define INIT_ORDER_VIS 80
#define INIT_ORDER_SERVER_MAINT 65
#define INIT_ORDER_INSTRUMENTS 50
-#define INIT_ORDER_EARLY_ASSETS 48
#define INIT_ORDER_MEDIA_TRACKS 38
#define INIT_ORDER_CHEMISTRY 35
#define INIT_ORDER_MATERIALS 34
@@ -112,7 +127,6 @@ DEFINE_BITFIELD(runlevels, list(
#define INIT_ORDER_AIR -1
#define INIT_ORDER_PLANETS -2
#define INIT_ORDER_PERSISTENCE -3
-#define INIT_ORDER_ASSETS -4
#define INIT_ORDER_MISC_LATE -5
#define INIT_ORDER_HOLOMAPS -5
#define INIT_ORDER_NIGHTSHIFT -5
diff --git a/code/__DEFINES/controllers/assets.dm b/code/__DEFINES/controllers/assets.dm
new file mode 100644
index 000000000000..617da4790492
--- /dev/null
+++ b/code/__DEFINES/controllers/assets.dm
@@ -0,0 +1,8 @@
+//* asset loaded status
+
+/// not loaded at all
+#define ASSET_NOT_LOADED 1
+/// loading started, wait
+#define ASSET_IS_LOADING 2
+/// loading finished, go
+#define ASSET_FULLY_LOADED 3
diff --git a/code/__DEFINES/dcs/signals/global_signals.dm b/code/__DEFINES/dcs/signals/global_signals.dm
new file mode 100644
index 000000000000..c9beb6fd03a1
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/global_signals.dm
@@ -0,0 +1,12 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
+/**
+ * Use SEND_GLOBAL_SIGNAL to send, and
+ * RegisterGlobalSignal to receive.
+ */
+
+/// when a mob is in /Initialize(): (mob/creating)
+#define COMSIG_GLOBAL_MOB_NEW "!global-mob-new"
+/// when a mob is in /Destroy(): (mob/destroying)
+#define COMSIG_GLOBAL_MOB_DEL "!global-mob-del"
diff --git a/code/__DEFINES/dcs/signals/signals_NTNet.dm b/code/__DEFINES/dcs/signals/signals_NTNet.dm
deleted file mode 100644
index 7b258d299b4c..000000000000
--- a/code/__DEFINES/dcs/signals/signals_NTNet.dm
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- *! ## NTNet Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Called on an object by its NTNET connection component on receive. (data(datum/netdata))
-////#define COMSIG_COMPONENT_NTNET_RECEIVE "ntnet_receive"
-/// Called on an object by its NTNET connection component on a port update (hardware_id, port))
-////#define COMSIG_COMPONENT_NTNET_PORT_UPDATE "ntnet_port_update"
-/// Called when packet was accepted by the target (datum/netdata, error_code)
-////#define COMSIG_COMPONENT_NTNET_ACK "ntnet_ack"
-/// Called when packet was not acknoledged by the target (datum/netdata, error_code)
-////#define COMSIG_COMPONENT_NTNET_NAK "ntnet_nack"
-
-// !Some internal NTnet signals used on ports
-/// Called on an object by its NTNET connection component on a port distruction (port, list/data))
-////#define COMSIG_COMPONENT_NTNET_PORT_DESTROYED "ntnet_port_destroyed"
-/// Called on an object by its NTNET connection component on a port distruction (port, list/data))
-////#define COMSIG_COMPONENT_NTNET_PORT_UPDATED "ntnet_port_updated"
diff --git a/code/__DEFINES/dcs/signals/signals_adventure.dm b/code/__DEFINES/dcs/signals/signals_adventure.dm
deleted file mode 100644
index b2350bdbdf73..000000000000
--- a/code/__DEFINES/dcs/signals/signals_adventure.dm
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- *! ## Adventure Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Exoprobe adventure finished: (result) result is ADVENTURE_RESULT_??? values
-////#define COMSIG_ADVENTURE_FINISHED "adventure_done"
-
-/// Sent on initial adventure qualities generation from /datum/adventure/proc/initialize_qualities(): (list/quality_list)
-////#define COMSIG_ADVENTURE_QUALITY_INIT "adventure_quality_init"
-
-/// Sent on adventure node delay start: (delay_time, delay_message)
-////#define COMSIG_ADVENTURE_DELAY_START "adventure_delay_start"
-/// Sent on adventure delay finish: ()
-////#define COMSIG_ADVENTURE_DELAY_END "adventure_delay_end"
-
-/// Exoprobe status changed : ()
-////#define COMSIG_EXODRONE_STATUS_CHANGED "exodrone_status_changed"
-
-//! Scanner Controller Signals
-/// Sent on begingging of new scan : (datum/exoscan/new_scan)
-////#define COMSIG_EXOSCAN_STARTED "exoscan_started"
-/// Sent on successful finish of exoscan: (datum/exoscan/finished_scan)
-////#define COMSIG_EXOSCAN_FINISHED "exoscan_finished"
-
-//! Exosca Signals
-/// Sent on exoscan failure/manual interruption: ()
-////#define COMSIG_EXOSCAN_INTERRUPTED "exoscan_interrupted"
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm
index 094fcc593499..2e96afb7e7c9 100644
--- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm
@@ -46,3 +46,6 @@
/// When the transform or an atom is varedited through vv topic.
////#define COMSIG_ATOM_VV_MODIFY_TRANSFORM "atom_vv_modify_transform"
+
+/// called when compile_overlays() is ran. args: ()
+#define COMSIG_ATOM_COMPILED_OVERLAYS "atom_compiled_overlays"
diff --git a/code/__DEFINES/dcs/signals/signals_bot.dm b/code/__DEFINES/dcs/signals/signals_bot.dm
deleted file mode 100644
index f3c56b5caa0d..000000000000
--- a/code/__DEFINES/dcs/signals/signals_bot.dm
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- *! ## Bot Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Called in /obj/structure/moneybot/add_money(). (to_add)
-////#define COMSIG_MONEYBOT_ADD_MONEY "moneybot_add_money"
-
-/// Called in /obj/structure/dispenserbot/add_item(). (obj/item/to_add)
-////#define COMSIG_DISPENSERBOT_ADD_ITEM "moneybot_add_item"
-
-/// Called in /obj/structure/dispenserbot/remove_item(). (obj/item/to_remove)
-////#define COMSIG_DISPENSERBOT_REMOVE_ITEM "moneybot_remove_item"
diff --git a/code/__DEFINES/dcs/signals/signals_changeling.dm b/code/__DEFINES/dcs/signals/signals_changeling.dm
deleted file mode 100644
index 582e53e986df..000000000000
--- a/code/__DEFINES/dcs/signals/signals_changeling.dm
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- *! ## Changeling Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Called when a changeling uses its transform ability (source = carbon), from /datum/action/changeling/transform/sting_action(mob/living/carbon/human/user)
-////#define COMSIG_CHANGELING_TRANSFORM "changeling_transform"
diff --git a/code/__DEFINES/dcs/signals/signals_circuit.dm b/code/__DEFINES/dcs/signals/signals_circuit.dm
deleted file mode 100644
index 0f3752d2904a..000000000000
--- a/code/__DEFINES/dcs/signals/signals_circuit.dm
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- *! ## Circuit Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-//! Component signals
-/// Sent when the value of a port is set.
-////#define COMSIG_PORT_SET_VALUE "port_set_value"
-/// Sent when the type of a port is set.
-////#define COMSIG_PORT_SET_TYPE "port_set_type"
-/// Sent when a port disconnects from everything.
-////#define COMSIG_PORT_DISCONNECT "port_disconnect"
-
-/// Sent when a [/obj/item/circuit_component] is added to a circuit.
-////#define COMSIG_CIRCUIT_ADD_COMPONENT "circuit_add_component"
- /// Cancels adding the component to the circuit.
- ////#define COMPONENT_CANCEL_ADD_COMPONENT (1<<0)
-
-/// Sent when a [/obj/item/circuit_component] is added to a circuit manually, by putting the item inside directly.
-/// Accepts COMPONENT_CANCEL_ADD_COMPONENT.
-////#define COMSIG_CIRCUIT_ADD_COMPONENT_MANUALLY "circuit_add_component_manually"
-
-/// Sent when a circuit is removed from its shell
-////#define COMSIG_CIRCUIT_SHELL_REMOVED "circuit_shell_removed"
-
-/// Sent to [/obj/item/circuit_component] when it is removed from a circuit. (/obj/item/integrated_circuit)
-////#define COMSIG_CIRCUIT_COMPONENT_REMOVED "circuit_component_removed"
-
-/// Called when the integrated circuit's cell is set.
-////#define COMSIG_CIRCUIT_SET_CELL "circuit_set_cell"
-
-/// Called when the integrated circuit is turned on or off.
-////#define COMSIG_CIRCUIT_SET_ON "circuit_set_on"
-
-/// Called when the integrated circuit's shell is set.
-////#define COMSIG_CIRCUIT_SET_SHELL "circuit_set_shell"
-
-/// Called when the integrated circuit is locked.
-////#define COMSIG_CIRCUIT_SET_LOCKED "circuit_set_locked"
-
-/// Called before power is used in an integrated circuit (power_to_use)
-////#define COMSIG_CIRCUIT_PRE_POWER_USAGE "circuit_pre_power_usage"
- ////#define COMPONENT_OVERRIDE_POWER_USAGE (1<<0)
-
-/// Called right before the integrated circuit data is converted to json. Allows modification to the data right before it is returned.
-////#define COMSIG_CIRCUIT_PRE_SAVE_TO_JSON "circuit_pre_save_to_json"
-
-/// Called when the integrated circuit is loaded.
-////#define COMSIG_CIRCUIT_POST_LOAD "circuit_post_load"
-
-/// Sent to an atom when a [/obj/item/usb_cable] attempts to connect to something. (/obj/item/usb_cable/usb_cable, /mob/user)
-////#define COMSIG_ATOM_USB_CABLE_TRY_ATTACH "usb_cable_try_attach"
- /// Attaches the USB cable to the atom. If the USB cables moves away, it will disconnect.
- ////#define COMSIG_USB_CABLE_ATTACHED (1<<0)
-
- /// Attaches the USB cable to a circuit. Producers of this are expected to set the usb_cable's
- /// `attached_circuit` variable.
- ////#define COMSIG_USB_CABLE_CONNECTED_TO_CIRCUIT (1<<1)
-
- /// Cancels the attack chain, but without performing any other action.
- ////#define COMSIG_CANCEL_USB_CABLE_ATTACK (1<<2)
-
-/// Called when the circuit component is saved.
-////#define COMSIG_CIRCUIT_COMPONENT_SAVE "circuit_component_save"
-
-/// Called when an external object is loaded.
-////#define COMSIG_MOVABLE_CIRCUIT_LOADED "movable_circuit_loaded"
diff --git a/code/__DEFINES/dcs/signals/signals_clothing.dm b/code/__DEFINES/dcs/signals/signals_clothing.dm
deleted file mode 100644
index 7452fb837f82..000000000000
--- a/code/__DEFINES/dcs/signals/signals_clothing.dm
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- *! ## Clothing Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-//! /obj/item/clothing
-/// (/obj/item/clothing, visor_state) - When a clothing gets it's visor toggled.
-////#define COMSIG_CLOTHING_VISOR_TOGGLE "clothing_visor_toggle"
diff --git a/code/__DEFINES/dcs/signals/signals_container.dm b/code/__DEFINES/dcs/signals/signals_container.dm
deleted file mode 100644
index a3f42c4ddd26..000000000000
--- a/code/__DEFINES/dcs/signals/signals_container.dm
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- *! ## Container Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-//! /datum/component/container_item
-/// (atom/container, mob/user) - returns bool
-////#define COMSIG_CONTAINER_TRY_ATTACH "container_try_attach"
diff --git a/code/__DEFINES/dcs/signals/signals_customizable.dm b/code/__DEFINES/dcs/signals/signals_customizable.dm
deleted file mode 100644
index 13f167104a04..000000000000
--- a/code/__DEFINES/dcs/signals/signals_customizable.dm
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- *! ## Customizable Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Called when an atom with /datum/component/customizable_reagent_holder is customized (obj/item/I)
-////#define COMSIG_ATOM_CUSTOMIZED "atom_customized"
diff --git a/code/__DEFINES/dcs/signals/signals_cytology.dm b/code/__DEFINES/dcs/signals/signals_cytology.dm
deleted file mode 100644
index 05b632cc6d09..000000000000
--- a/code/__DEFINES/dcs/signals/signals_cytology.dm
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- *! ## Cytology Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Sent from /datum/biological_sample/proc/reset_sample
-////#define COMSIG_SAMPLE_GROWTH_COMPLETED "sample_growth_completed"
- ////#define SPARE_SAMPLE (1<<0)
diff --git a/code/__DEFINES/dcs/signals/signals_food.dm b/code/__DEFINES/dcs/signals/signals_food.dm
deleted file mode 100644
index 16fffde87779..000000000000
--- a/code/__DEFINES/dcs/signals/signals_food.dm
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- *! ## Food Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Called when an item is used as an ingredient: (atom/customized)
-////#define COMSIG_ITEM_USED_AS_INGREDIENT "item_used_as_ingredient"
-/// Called when an edible ingredient is added: (datum/component/edible/ingredient)
-////#define COMSIG_EDIBLE_INGREDIENT_ADDED "edible_ingredient_added"
-
-//! Food Signals
-/// From Edible component: (mob/living/eater, mob/feeder, bitecount, bitesize)
-////#define COMSIG_FOOD_EATEN "food_eaten"
-/// From base of datum/component/edible/on_entered: (mob/crosser, bitecount)
-////#define COMSIG_FOOD_CROSSED "food_crossed"
-
-/// From base of Component/edible/On_Consume: (mob/living/eater, mob/living/feeder)
-////#define COMSIG_FOOD_CONSUMED "food_consumed"
-
-////#define COMSIG_ITEM_FRIED "item_fried"
- ////#define COMSIG_FRYING_HANDLED (1<<0)
-
-//! Drink Signals
-/// From base of obj/item/reagent_containers/food/drinks/attack(): (mob/living/M, mob/user)
-////#define COMSIG_DRINK_DRANK "drink_drank"
-/// From base of obj/item/reagent_containers/glass/attack(): (mob/M, mob/user)
-////#define COMSIG_GLASS_DRANK "glass_drank"
diff --git a/code/__DEFINES/dcs/signals/signals_gib.dm b/code/__DEFINES/dcs/signals/signals_gib.dm
deleted file mode 100644
index 8e36cc36ffbe..000000000000
--- a/code/__DEFINES/dcs/signals/signals_gib.dm
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- *! ## Gibs Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// From base of /obj/effect/debris/cleanable/blood/gibs/streak(): (list/directions, list/diseases)
-////#define COMSIG_GIBS_STREAK "gibs_streak"
-/// Called on mobs when they step in blood. (blood_amount, blood_state, list/blood_DNA)
-////#define COMSIG_STEP_ON_BLOOD "step_on_blood"
diff --git a/code/__DEFINES/dcs/signals/signals_global.dm b/code/__DEFINES/dcs/signals/signals_global.dm
deleted file mode 100644
index 2a58521989ea..000000000000
--- a/code/__DEFINES/dcs/signals/signals_global.dm
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- *! ## Global Signals
- * * These are signals which can be listened to by any component on any parent.
- * * Start global signals with "!", this used to be necessary but now it's just a formatting choice.
- *
- *! ## Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument.
- */
-
-/// Called after a successful area creation by a mob: (area/created_area, area/old_area, mob/creator)
-////#define COMSIG_AREA_CREATED "!mob_created_area"
-/// Sent when a new z-level is created: (name, traits, z_type)
-#define COMSIG_GLOB_NEW_Z "!new_z"
-/// Sent after world.maxx and/or world.maxy are expanded: (has_exapnded_world_maxx, has_expanded_world_maxy)
-////#define COMSIG_GLOB_EXPANDED_WORLD_BOUNDS "!expanded_world_bounds"
-/// Called after a successful var edit somewhere in the world: (list/args)
-////#define COMSIG_GLOB_VAR_EDIT "!var_edit"
-/// Called after an explosion happened : (epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range)
-////#define COMSIG_GLOB_EXPLOSION "!explosion"
-/// Called from base of /mob/Initialise : (mob)
-////#define COMSIG_GLOB_MOB_CREATED "!mob_created"
-/// Mob died somewhere : (mob/living, gibbed)
-////#define COMSIG_GLOB_MOB_DEATH "!mob_death"
-/// Global living say plug - use sparingly: (mob/speaker , message)
-////#define COMSIG_GLOB_LIVING_SAY_SPECIAL "!say_special"
-/// Called by datum/cinematic/play() : (datum/cinematic/new_cinematic)
-////#define COMSIG_GLOB_PLAY_CINEMATIC "!play_cinematic"
- ////#define COMPONENT_GLOB_BLOCK_CINEMATIC (1<<0)
-/// Ingame button pressed (/obj/machinery/button/button)
-////#define COMSIG_GLOB_BUTTON_PRESSED "!button_pressed"
-/// Job subsystem has spawned and equipped a new mob
-////#define COMSIG_GLOB_JOB_AFTER_SPAWN "!job_after_spawn"
-/// Job datum has been called to deal with the aftermath of a latejoin spawn
-////#define COMSIG_GLOB_JOB_AFTER_LATEJOIN_SPAWN "!job_after_latejoin_spawn"
-/// Crewmember joined the game (mob/living, rank)
-////#define COMSIG_GLOB_CREWMEMBER_JOINED "!crewmember_joined"
-/// Random event is trying to roll. (/datum/round_event_control/random_event)
-/// Called by (/datum/round_event_control/preRunEvent).
-////#define COMSIG_GLOB_PRE_RANDOM_EVENT "!pre_random_event"
- ///? Do not allow this random event to continue.
- ////#define CANCEL_PRE_RANDOM_EVENT (1<<0)
-/// A person somewhere has thrown something : (mob/living/carbon/carbon_thrower, target)
-////#define COMSIG_GLOB_CARBON_THROW_THING "!throw_thing"
-/// A trapdoor remote has sent out a signal to link with a trapdoor
-////#define COMSIG_GLOB_TRAPDOOR_LINK "!trapdoor_link"
- ///? Successfully linked to a trapdoor!
- ////#define LINKED_UP (1<<0)
-/// An obj/item is created! (obj/item/created_item)
-////#define COMSIG_GLOB_NEW_ITEM "!new_item"
-/// A client (re)connected, after all /client/New() checks have passed : (client/connected_client)
-////#define COMSIG_GLOB_CLIENT_CONNECT "!client_connect"
-/// A weather event of some kind occured
-////#define COMSIG_WEATHER_TELEGRAPH(event_type) "!weather_telegraph [event_type]"
-////#define COMSIG_WEATHER_START(event_type) "!weather_start [event_type]"
-////#define COMSIG_WEATHER_WINDDOWN(event_type) "!weather_winddown [event_type]"
-////#define COMSIG_WEATHER_END(event_type) "!weather_end [event_type]"
-/// An alarm of some form was sent (datum/alarm_handler/source, alarm_type, area/source_area)
-////#define COMSIG_ALARM_FIRE(alarm_type) "!alarm_fire [alarm_type]"
-/// An alarm of some form was cleared (datum/alarm_handler/source, alarm_type, area/source_area)
-////#define COMSIG_ALARM_CLEAR(alarm_type) "!alarm_clear [alarm_type]"
-/// Global mob logged in signal! (/mob/added_player)
-////#define COMSIG_GLOB_MOB_LOGGED_IN "!mob_logged_in"
-
-/// Global signal sent when a nuclear device is armed (/obj/machinery/nuclearbomb/nuke/exploding_nuke)
-////#define COMSIG_GLOB_NUKE_DEVICE_ARMED "!nuclear_device_armed"
-/// Global signal sent when a nuclear device is disarmed (/obj/machinery/nuclearbomb/nuke/disarmed_nuke)
-////#define COMSIG_GLOB_NUKE_DEVICE_DISARMED "!nuclear_device_disarmed"
-
-/// Global signal sent when a light mechanism is completed (try_id)
-////#define COMSIG_GLOB_LIGHT_MECHANISM_COMPLETED "!light_mechanism_completed"
diff --git a/code/__DEFINES/dcs/signals/signals_global_object.dm b/code/__DEFINES/dcs/signals/signals_global_object.dm
deleted file mode 100644
index f14cc7e76fe4..000000000000
--- a/code/__DEFINES/dcs/signals/signals_global_object.dm
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- *! ## Globally Accessible Object Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// From SSJob when DivideOccupations is called
-////#define COMSIG_OCCUPATIONS_DIVIDED "occupations_divided"
-
-/// From SSsun when the sun changes position : (azimuth)
-////#define COMSIG_SUN_MOVED "sun_moved"
-
-/// From SSsecurity_level when the security level changes : (new_level)
-////#define COMSIG_SECURITY_LEVEL_CHANGED "security_level_changed"
-
-/// From SSshuttle when the supply shuttle starts spawning orders : ()
-////#define COMSIG_SUPPLY_SHUTTLE_BUY "supply_shuttle_buy"
diff --git a/code/__DEFINES/dcs/signals/signals_greyscale.dm b/code/__DEFINES/dcs/signals/signals_greyscale.dm
deleted file mode 100644
index 5362dd026582..000000000000
--- a/code/__DEFINES/dcs/signals/signals_greyscale.dm
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- *! ## Greyscale Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-////#define COMSIG_GREYSCALE_CONFIG_REFRESHED "greyscale_config_refreshed"
diff --git a/code/__DEFINES/dcs/signals/signals_heretic.dm b/code/__DEFINES/dcs/signals/signals_heretic.dm
deleted file mode 100644
index 5ca2d02e53db..000000000000
--- a/code/__DEFINES/dcs/signals/signals_heretic.dm
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- *! ## Heretic Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// From /obj/item/clothing/mask/madness_mask/process : (amount)
-////#define COMSIG_HERETIC_MASK_ACT "void_mask_act"
-
-/// From /obj/item/melee/touch_attack/mansus_fist/on_mob_hit : (mob/living/source, mob/living/target)
-////#define COMSIG_HERETIC_MANSUS_GRASP_ATTACK "mansus_grasp_attack"
- ///? Default behavior is to use a charge, so return this to blocks the mansus fist from being consumed after use.
- ////#define COMPONENT_BLOCK_CHARGE_USE (1<<0)
-/// From /obj/item/melee/touch_attack/mansus_fist/afterattack_secondary : (mob/living/source, atom/target)
-////#define COMSIG_HERETIC_MANSUS_GRASP_ATTACK_SECONDARY "mansus_grasp_attack_secondary"
- ///? Default behavior is to continue attack chain and do nothing else, so return this to use up a charge after use.
- ////#define COMPONENT_USE_CHARGE (1<<0)
-
-/// From /obj/item/melee/sickly_blade/afterattack (with proximity) : (mob/living/source, mob/living/target)
-////#define COMSIG_HERETIC_BLADE_ATTACK "blade_attack"
-/// From /obj/item/melee/sickly_blade/afterattack (without proximity) : (mob/living/source, mob/living/target)
-////#define COMSIG_HERETIC_RANGED_BLADE_ATTACK "ranged_blade_attack"
diff --git a/code/__DEFINES/dcs/signals/signals_hydroponic.dm b/code/__DEFINES/dcs/signals/signals_hydroponic.dm
deleted file mode 100644
index 65ed7cc39ae2..000000000000
--- a/code/__DEFINES/dcs/signals/signals_hydroponic.dm
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- *! ## Hydroponics Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-//! Plants / Plant Traits Signals
-/// Called when a plant with slippery skin is slipped on (mob/victim)
-////#define COMSIG_PLANT_ON_SLIP "plant_on_slip"
-/// Called when a plant with liquid contents is squashed on (atom/target)
-////#define COMSIG_PLANT_ON_SQUASH "plant_on_squash"
-/// Called when a plant backfires via the backfire element (mob/victim)
-////#define COMSIG_PLANT_ON_BACKFIRE "plant_on_backfire"
-/// Called when a seed grows in a tray (obj/machinery/hydroponics)
-////#define COMSIG_SEED_ON_GROW "plant_on_grow"
-/// Called when a seed is planted in a tray (obj/machinery/hydroponics)
-////#define COMSIG_SEED_ON_PLANTED "plant_on_plant"
-
-//! Hydro Tray Signals
-/// From base of /obj/machinery/hydroponics/set_seed() : (obj/item/new_seed)
-////#define COMSIG_HYDROTRAY_SET_SEED "hydrotray_set_seed"
-/// From base of /obj/machinery/hydroponics/set_self_sustaining() : (new_value)
-////#define COMSIG_HYDROTRAY_SET_SELFSUSTAINING "hydrotray_set_selfsustaining"
-/// From base of /obj/machinery/hydroponics/set_weedlevel() : (new_value)
-////#define COMSIG_HYDROTRAY_SET_WEEDLEVEL "hydrotray_set_weedlevel"
-/// From base of /obj/machinery/hydroponics/set_pestlevel() : (new_value)
-////#define COMSIG_HYDROTRAY_SET_PESTLEVEL "hydrotray_set_pestlevel"
-/// From base of /obj/machinery/hydroponics/set_waterlevel() : (new_value)
-////#define COMSIG_HYDROTRAY_SET_WATERLEVEL "hydrotray_set_waterlevel"
-/// From base of /obj/machinery/hydroponics/set_plant_health() : (new_value)
-////#define COMSIG_HYDROTRAY_SET_PLANT_HEALTH "hydrotray_set_plant_health"
-/// From base of /obj/machinery/hydroponics/set_toxic() : (new_value)
-////#define COMSIG_HYDROTRAY_SET_TOXIC "hydrotray_set_toxic"
-/// From base of /obj/machinery/hydroponics/set_plant_status() : (new_value)
-////#define COMSIG_HYDROTRAY_SET_PLANT_STATUS "hydrotray_set_plant_status"
-/// From base of /obj/machinery/hydroponics/update_tray() : (mob/user, product_count)
-////#define COMSIG_HYDROTRAY_ON_HARVEST "hydrotray_on_harvest"
-/// From base of /obj/machinery/hydroponics/plantdies()
-////#define COMSIG_HYDROTRAY_PLANT_DEATH "hydrotray_plant_death"
diff --git a/code/__DEFINES/dcs/signals/signals_janitor.dm b/code/__DEFINES/dcs/signals/signals_janitor.dm
deleted file mode 100644
index e624d4abadf6..000000000000
--- a/code/__DEFINES/dcs/signals/signals_janitor.dm
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- *! ## Janitor Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Called on an object to "clean it", such as removing blood decals/overlays, etc. The clean types bitfield is sent with it. Return TRUE if any cleaning was necessary and thus performed.
-////#define COMSIG_COMPONENT_CLEAN_ACT "clean_act"
- ///? Returned by cleanable components when they are cleaned.
- ////#define COMPONENT_CLEANED (1<<0)
-
-//! Vacuum signals
-/// Called on a bag being attached to a vacuum parent
-////#define COMSIG_VACUUM_BAG_ATTACH "comsig_vacuum_bag_attach"
-/// Called on a bag being detached from a vacuum parent
-////#define COMSIG_VACUUM_BAG_DETACH "comsig_vacuum_bag_detach"
-
-///(): Returns bitflags of wet values.
-////#define COMSIG_TURF_IS_WET "check_turf_wet"
-///(max_strength, immediate, duration_decrease = INFINITY): Returns bool.
-////#define COMSIG_TURF_MAKE_DRY "make_turf_try"
diff --git a/code/__DEFINES/dcs/signals/signals_ladder.dm b/code/__DEFINES/dcs/signals/signals_ladder.dm
deleted file mode 100644
index 2ddf0439c7ed..000000000000
--- a/code/__DEFINES/dcs/signals/signals_ladder.dm
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- *! ## Ladder Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Called on a mob attempting to use a ladder to go in either direction. (entrance_ladder, exit_ladder, going_up)
-////#define COMSIG_LADDER_TRAVEL "ladder-travel"
- ////#define LADDER_TRAVEL_BLOCK (1<<0)
diff --git a/code/__DEFINES/dcs/signals/signals_light_eater.dm b/code/__DEFINES/dcs/signals/signals_light_eater.dm
deleted file mode 100644
index 7a2fdeae5ff9..000000000000
--- a/code/__DEFINES/dcs/signals/signals_light_eater.dm
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- *! ## Light Eater Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-//! /datum/element/light_eater
-/// From base of [/datum/element/light_eater/proc/table_buffet]: (list/light_queue, datum/light_eater)
-////#define COMSIG_LIGHT_EATER_QUEUE "light_eater_queue"
-/// From base of [/datum/element/light_eater/proc/devour]: (datum/light_eater)
-////#define COMSIG_LIGHT_EATER_ACT "light_eater_act"
- ///? Prevents the default light eater behavior from running in case of immunity or custom behavior
- ////#define COMPONENT_BLOCK_LIGHT_EATER (1<<0)
-/// From base of [/datum/element/light_eater/proc/devour]: (atom/eaten_light)
-////#define COMSIG_LIGHT_EATER_DEVOUR "light_eater_devour"
diff --git a/code/__DEFINES/dcs/signals/signals_medical.dm b/code/__DEFINES/dcs/signals/signals_medical.dm
deleted file mode 100644
index 5c3dd0d2d5f9..000000000000
--- a/code/__DEFINES/dcs/signals/signals_medical.dm
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- *! ## Medical Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// From /datum/surgery/New(): (datum/surgery/surgery, surgery_location (body zone), obj/item/bodypart/targeted_limb)
-////#define COMSIG_MOB_SURGERY_STARTED "mob_surgery_started"
-
-/// From /datum/surgery_step/success(): (datum/surgery_step/step, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results)
-////#define COMSIG_MOB_SURGERY_STEP_SUCCESS "mob_surgery_step_success"
diff --git a/code/__DEFINES/dcs/signals/signals_mind.dm b/code/__DEFINES/dcs/signals/signals_mind.dm
deleted file mode 100644
index 5f1bfae786fe..000000000000
--- a/code/__DEFINES/dcs/signals/signals_mind.dm
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- *! ## Mind Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// From mind/transfer_to. Sent after the mind has been transferred: (mob/previous_body)
-////#define COMSIG_MIND_TRANSFERRED "mind_transferred"
-
-/// Called on the mind when an antagonist is being gained, after the antagonist list has updated (datum/antagonist/antagonist)
-////#define COMSIG_ANTAGONIST_GAINED "antagonist_gained"
-
-/// Called on the mind when an antagonist is being removed, after the antagonist list has updated (datum/antagonist/antagonist)
-////#define COMSIG_ANTAGONIST_REMOVED "antagonist_removed"
diff --git a/code/__DEFINES/dcs/signals/signals_modsuit.dm b/code/__DEFINES/dcs/signals/signals_modsuit.dm
deleted file mode 100644
index c0bc5050148b..000000000000
--- a/code/__DEFINES/dcs/signals/signals_modsuit.dm
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- *! ## MODsuit Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Called when a module is selected to be the active one from on_select(obj/item/mod/module/module)
-////#define COMSIG_MOD_MODULE_SELECTED "mod_module_selected"
-/// Called when a MOD activation is called from toggle_activate(mob/user)
-////#define COMSIG_MOD_ACTIVATE "mod_activate"
- ///? Cancels the suit's activation
- ////#define MOD_CANCEL_ACTIVATE (1 << 0)
-/// Called when a MOD is having modules removed from crowbar_act(mob/user, obj/crowbar)
-////#define COMSIG_MOD_MODULE_REMOVAL "mod_module_removal"
- ///? Cancels the removal of modules
- ////#define MOD_CANCEL_REMOVAL (1 << 0)
-/// Called when a module attempts to activate, however it does. At the end of checks so you can add some yourself, or work on trigger behavior (mob/user)
-////#define COMSIG_MODULE_TRIGGERED "mod_module_triggered"
- ///? Cancels activation, with no message. include feedback on your cancel.
- ////#define MOD_ABORT_USE (1<<0)
-/// Called when a module activates, after all checks have passed and cooldown started.
-////#define COMSIG_MODULE_ACTIVATED "mod_module_activated"
-/// Called when a module deactivates, after all checks have passed.
-////#define COMSIG_MODULE_DEACTIVATED "mod_module_deactivated"
-/// Called when a module is used, after all checks have passed and cooldown started.
-////#define COMSIG_MODULE_USED "mod_module_used"
-/// Called when the MODsuit wearer is set.
-////#define COMSIG_MOD_WEARER_SET "mod_wearer_set"
-/// Called when the MODsuit wearer is unset.
-////#define COMSIG_MOD_WEARER_UNSET "mod_wearer_unset"
diff --git a/code/__DEFINES/dcs/signals/signals_mood.dm b/code/__DEFINES/dcs/signals/signals_mood.dm
deleted file mode 100644
index c8eb6cb03b35..000000000000
--- a/code/__DEFINES/dcs/signals/signals_mood.dm
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- *! ## Mood Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Called when you send a mood event from anywhere in the code.
-////#define COMSIG_ADD_MOOD_EVENT "add_mood"
-/// Mood event that only RnD members listen for
-////#define COMSIG_ADD_MOOD_EVENT_RND "RND_add_mood"
-/// Called when you clear a mood event from anywhere in the code.
-////#define COMSIG_CLEAR_MOOD_EVENT "clear_mood"
diff --git a/code/__DEFINES/dcs/signals/signals_moveloop.dm b/code/__DEFINES/dcs/signals/signals_moveloop.dm
deleted file mode 100644
index 13e4c99c7155..000000000000
--- a/code/__DEFINES/dcs/signals/signals_moveloop.dm
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- *! ## Moveloop Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// From [/datum/move_loop/start_loop] ():
-////#define COMSIG_MOVELOOP_START "moveloop_start"
-/// From [/datum/move_loop/stop_loop] ():
-////#define COMSIG_MOVELOOP_STOP "moveloop_stop"
-/// From [/datum/move_loop/process] ():
-////#define COMSIG_MOVELOOP_PREPROCESS_CHECK "moveloop_preprocess_check"
- ////#define MOVELOOP_SKIP_STEP (1<<0)
-/// From [/datum/move_loop/process] (succeeded, visual_delay):
-////#define COMSIG_MOVELOOP_POSTPROCESS "moveloop_postprocess"
-/// From [/datum/move_loop/has_target/jps/recalculate_path] ():
-////#define COMSIG_MOVELOOP_JPS_REPATH "moveloop_jps_repath"
diff --git a/code/__DEFINES/dcs/signals/signals_movetype.dm b/code/__DEFINES/dcs/signals/signals_movetype.dm
deleted file mode 100644
index 4cce601d3486..000000000000
--- a/code/__DEFINES/dcs/signals/signals_movetype.dm
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- *! ## Movetype Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-//! /datum/element/movetype_handler signals
-/// Called when the floating anim has to be temporarily stopped and restarted later: (timer)
-////#define COMSIG_PAUSE_FLOATING_ANIM "pause_floating_anim"
-/// From base of datum/element/movetype_handler/on_movement_type_trait_gain: (flag, old_movement_type)
-////#define COMSIG_MOVETYPE_FLAG_ENABLED "movetype_flag_enabled"
-/// From base of datum/element/movetype_handler/on_movement_type_trait_loss: (flag, old_movement_type)
-////#define COMSIG_MOVETYPE_FLAG_DISABLED "movetype_flag_disabled"
diff --git a/code/__DEFINES/dcs/signals/signals_music.dm b/code/__DEFINES/dcs/signals/signals_music.dm
deleted file mode 100644
index ddbfdb6de529..000000000000
--- a/code/__DEFINES/dcs/signals/signals_music.dm
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- *! ## Music Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-//! /datum/song Signals
-/// Sent to the instrument when a song starts playing
-////#define COMSIG_INSTRUMENT_START "instrument_start"
-/// Sent to the instrument when a song stops playing
-////#define COMSIG_INSTRUMENT_END "instrument_end"
-/// Sent to the instrument on /should_stop_playing(): (atom/player). Return values can be found in DEFINES/song.dm
-////#define COMSIG_INSTRUMENT_SHOULD_STOP_PLAYING "instrument_should_stop_playing"
-/// Sent to the instrument (and player if available) when a song repeats (datum/song)
-////#define COMSIG_INSTRUMENT_REPEAT "instrument_repeat"
-/// Sent to the instrument when tempo changes, skipped on new. (datum/song)
-////#define COMSIG_INSTRUMENT_TEMPO_CHANGE "instrument_tempo_change"
diff --git a/code/__DEFINES/dcs/signals/signals_operating_computer.dm b/code/__DEFINES/dcs/signals/signals_operating_computer.dm
deleted file mode 100644
index 39d85d39b504..000000000000
--- a/code/__DEFINES/dcs/signals/signals_operating_computer.dm
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- *! ## Operating Computer Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-//! /obj/machinery/computer/operating Signals
-/// Fired when a dissection surgery completes.
-/// (mob/living/target)
-////#define COMSIG_OPERATING_COMPUTER_DISSECTION_COMPLETE "operating_computer_dissection_complete"
diff --git a/code/__DEFINES/dcs/signals/signals_painting.dm b/code/__DEFINES/dcs/signals/signals_painting.dm
deleted file mode 100644
index 6261670366f3..000000000000
--- a/code/__DEFINES/dcs/signals/signals_painting.dm
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- *! ## Painting Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-//! Signals for painting canvases, tools and the /datum/component/palette component
-/// From base of /item/proc/set_painting_tool_color(): (chosen_color)
-////#define COMSIG_PAINTING_TOOL_SET_COLOR "painting_tool_set_color"
-
-/// From base of /item/canvas/ui_data(mob/user, datum/tgui/ui)
-////#define COMSIG_PAINTING_TOOL_GET_ADDITIONAL_DATA "painting_tool_get_data"
diff --git a/code/__DEFINES/dcs/signals/signals_reagent.dm b/code/__DEFINES/dcs/signals/signals_reagent.dm
deleted file mode 100644
index 8283c2ca958a..000000000000
--- a/code/__DEFINES/dcs/signals/signals_reagent.dm
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- *! ## Reagent Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// From base of atom/expose_reagents(): (/list, /datum/reagents, methods, volume_modifier, show_message)
-////#define COMSIG_ATOM_EXPOSE_REAGENTS "atom_expose_reagents"
- ///? Prevents the atom from being exposed to reagents if returned on [COMSIG_ATOM_EXPOSE_REAGENTS]
- ////#define COMPONENT_NO_EXPOSE_REAGENTS (1<<0)
-/// From base of [/datum/reagent/proc/expose_atom]: (/datum/reagent, reac_volume)
-////#define COMSIG_ATOM_EXPOSE_REAGENT "atom_expose_reagent"
-/// From base of [/datum/reagent/proc/expose_atom]: (/atom, reac_volume)
-////#define COMSIG_REAGENT_EXPOSE_ATOM "reagent_expose_atom"
-/// From base of [/datum/reagent/proc/expose_atom]: (/obj, reac_volume)
-////#define COMSIG_REAGENT_EXPOSE_OBJ "reagent_expose_obj"
-/// From base of [/datum/reagent/proc/expose_atom]: (/mob/living, reac_volume, methods, show_message, touch_protection, /mob/camera/blob) // ovemind arg is only used by blob reagents.
-////#define COMSIG_REAGENT_EXPOSE_MOB "reagent_expose_mob"
-/// From base of [/datum/reagent/proc/expose_atom]: (/turf, reac_volume)
-////#define COMSIG_REAGENT_EXPOSE_TURF "reagent_expose_turf"
-
-/// From base of [/datum/controller/subsystem/materials/proc/InitializeMaterial]: (/datum/material)
-////#define COMSIG_MATERIALS_INIT_MAT "SSmaterials_init_mat"
-
-/// From base of [/datum/component/multiple_lives/proc/respawn]: (mob/respawned_mob, gibbed, lives_left)
-////#define COMSIG_ON_MULTIPLE_LIVES_RESPAWN "on_multiple_lives_respawn"
-
-/// From base of [/datum/reagents/proc/add_reagent] - Sent before the reagent is added: (reagenttype, amount, reagtemp, data, no_react)
-////#define COMSIG_REAGENTS_PRE_ADD_REAGENT "reagents_pre_add_reagent"
- ///? Prevents the reagent from being added.
- ////#define COMPONENT_CANCEL_REAGENT_ADD (1<<0)
-/// From base of [/datum/reagents/proc/add_reagent]: (/datum/reagent, amount, reagtemp, data, no_react)
-////#define COMSIG_REAGENTS_NEW_REAGENT "reagents_new_reagent"
-/// From base of [/datum/reagents/proc/add_reagent]: (/datum/reagent, amount, reagtemp, data, no_react)
-////#define COMSIG_REAGENTS_ADD_REAGENT "reagents_add_reagent"
-/// From base of [/datum/reagents/proc/del_reagent]: (/datum/reagent)
-////#define COMSIG_REAGENTS_DEL_REAGENT "reagents_del_reagent"
-/// From base of [/datum/reagents/proc/remove_reagent]: (/datum/reagent, amount)
-////#define COMSIG_REAGENTS_REM_REAGENT "reagents_rem_reagent"
-/// From base of [/datum/reagents/proc/clear_reagents]: ()
-////#define COMSIG_REAGENTS_CLEAR_REAGENTS "reagents_clear_reagents"
-/// From base of [/datum/reagents/proc/set_temperature]: (new_temp, old_temp)
-////#define COMSIG_REAGENTS_TEMP_CHANGE "reagents_temp_change"
-/// From base of [/datum/reagents/proc/handle_reactions]: (num_reactions)
-////#define COMSIG_REAGENTS_REACTED "reagents_reacted"
-/// From base of [/datum/reagents/proc/process]: (num_reactions)
-////#define COMSIG_REAGENTS_REACTION_STEP "reagents_time_step"
-/// From base of [/atom/proc/expose_reagents]: (/atom, /list, methods, volume_modifier, show_message)
-////#define COMSIG_REAGENTS_EXPOSE_ATOM "reagents_expose_atom"
-/// From base of [/obj/proc/expose_reagents]: (/obj, /list, methods, volume_modifier, show_message)
-////#define COMSIG_REAGENTS_EXPOSE_OBJ "reagents_expose_obj"
-/// From base of [/mob/living/proc/expose_reagents]: (/mob/living, /list, methods, volume_modifier, show_message, touch_protection)
-////#define COMSIG_REAGENTS_EXPOSE_MOB "reagents_expose_mob"
-/// From base of [/turf/proc/expose_reagents]: (/turf, /list, methods, volume_modifier, show_message)
-////#define COMSIG_REAGENTS_EXPOSE_TURF "reagents_expose_turf"
-/// From base of [/datum/component/personal_crafting/proc/del_reqs]: ()
-////#define COMSIG_REAGENTS_CRAFTING_PING "reagents_crafting_ping"
diff --git a/code/__DEFINES/dcs/signals/signals_restaurant.dm b/code/__DEFINES/dcs/signals/signals_restaurant.dm
deleted file mode 100644
index 7b63146f03b5..000000000000
--- a/code/__DEFINES/dcs/signals/signals_restaurant.dm
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- *! ## Restaurant Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// (customer, container) venue signal sent when a venue sells an item. source is the thing sold, which can be a datum, so we send container for location checks
-////#define COMSIG_ITEM_SOLD_TO_CUSTOMER "item_sold_to_customer"
diff --git a/code/__DEFINES/dcs/signals/signals_scangate.dm b/code/__DEFINES/dcs/signals/signals_scangate.dm
deleted file mode 100644
index 4db863999594..000000000000
--- a/code/__DEFINES/dcs/signals/signals_scangate.dm
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- *! ## Scangate Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Called when somebody passes through a scanner gate and it triggers
-////#define COMSIG_SCANGATE_PASS_TRIGGER "scangate_pass_trigger"
-
-/// Called when somebody passes through a scanner gate and it does not trigger
-////#define COMSIG_SCANGATE_PASS_NO_TRIGGER "scangate_pass_no_trigger"
-
-/// Called when something passes through a scanner gate shell
-////#define COMSIG_SCANGATE_SHELL_PASS "scangate_shell_pass"
diff --git a/code/__DEFINES/dcs/signals/signals_screentips.dm b/code/__DEFINES/dcs/signals/signals_screentips.dm
deleted file mode 100644
index f0a3ac01a391..000000000000
--- a/code/__DEFINES/dcs/signals/signals_screentips.dm
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- *! ## Screentip Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// A "Type-A" contextual screentip interaction.
-/// These are used for items that are defined by their behavior. They define their contextual text within *themselves*,
-/// not in their targets.
-/// Examples include syringes (LMB to inject, RMB to draw) and health analyzers (LMB to scan health/wounds, RMB for chems)
-/// Items can override `add_item_context()`, and call `register_item_context()` in order to easily connect to this.
-/// Called on /obj/item with a mutable screentip context list, the hovered target, and the mob hovering.
-/// A screentip context list is a list that has context keys (SCREENTIP_CONTEXT_*, from __DEFINES/screentips.dm)
-/// that map to the action as text.
-/// If you mutate the list in this signal, you must return CONTEXTUAL_SCREENTIP_SET.
-////#define COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET "item_requesting_context_for_target"
-
-/// A "Type-B" contextual screentip interaction.
-/// These are atoms that are defined by what happens *to* them. These should define contextual text within themselves, and
-/// not in their operating tools.
-/// Examples include construction objects (LMB with glass to put in screen for computers).
-/// Called on /atom with a mutable screentip context list, the item being used, and the mob hovering.
-/// A screentip context list is a list that has context keys (SCREENTIP_CONTEXT_*, from __DEFINES/screentips.dm)
-/// that map to the action as text.
-/// If you mutate the list in this signal, you must return CONTEXTUAL_SCREENTIP_SET.
-////#define COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM "atom_requesting_context_from_item"
-
-/// Tells the contextual screentips system that the list context was mutated.
-////#define CONTEXTUAL_SCREENTIP_SET (1 << 0)
diff --git a/code/__DEFINES/dcs/signals/signals_spatial_grid.dm b/code/__DEFINES/dcs/signals/signals_spatial_grid.dm
deleted file mode 100644
index 6a1e8d05b2e6..000000000000
--- a/code/__DEFINES/dcs/signals/signals_spatial_grid.dm
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- *! ## Spatial Grid Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Called from base of /datum/controller/subsystem/spatial_grid/proc/enter_cell: (/atom/movable)
-////#define SPATIAL_GRID_CELL_ENTERED(contents_type) "spatial_grid_cell_entered_[contents_type]"
-/// Called from base of /datum/controller/subsystem/spatial_grid/proc/exit_cell: (/atom/movable)
-////#define SPATIAL_GRID_CELL_EXITED(contents_type) "spatial_grid_cell_exited_[contents_type]"
diff --git a/code/__DEFINES/dcs/signals/signals_species.dm b/code/__DEFINES/dcs/signals/signals_species.dm
deleted file mode 100644
index 96e63bb7f2b7..000000000000
--- a/code/__DEFINES/dcs/signals/signals_species.dm
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- *! ## /datum/species Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// From datum/species/on_species_gain(): (datum/species/new_species, datum/species/old_species)
-////#define COMSIG_SPECIES_GAIN "species_gain"
-/// From datum/species/on_species_loss(): (datum/species/lost_species)
-////#define COMSIG_SPECIES_LOSS "species_loss"
diff --git a/code/__DEFINES/dcs/signals/signals_subsystem.dm b/code/__DEFINES/dcs/signals/signals_subsystem.dm
deleted file mode 100644
index 4f59bb7730e5..000000000000
--- a/code/__DEFINES/dcs/signals/signals_subsystem.dm
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- *! ## Subsystem Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-//! Subsystem signals
-/// From base of datum/controller/subsystem/Initialize: (start_timeofday)
-////#define COMSIG_SUBSYSTEM_POST_INITIALIZE "subsystem_post_initialize"
-/// Called when the ticker enters the pre-game phase
-////#define COMSIG_TICKER_ENTER_PREGAME "comsig_ticker_enter_pregame"
-/// Called when the ticker sets up the game for start
-////#define COMSIG_TICKER_ENTER_SETTING_UP "comsig_ticker_enter_setting_up"
-/// Called when the ticker fails to set up the game for start
-////#define COMSIG_TICKER_ERROR_SETTING_UP "comsig_ticker_error_setting_up"
-/// Called when the round has started, but before GAME_STATE_PLAYING
-////#define COMSIG_TICKER_ROUND_STARTING "comsig_ticker_round_starting"
-
-//! Point of interest signals
-/// Sent from base of /datum/controller/subsystem/points_of_interest/proc/on_poi_element_added : (atom/new_poi)
-////#define COMSIG_ADDED_POINT_OF_INTEREST "added_point_of_interest"
-/// Sent from base of /datum/controller/subsystem/points_of_interest/proc/on_poi_element_removed : (atom/old_poi)
-////#define COMSIG_REMOVED_POINT_OF_INTEREST "removed_point_of_interest"
diff --git a/code/__DEFINES/dcs/signals/signals_swab.dm b/code/__DEFINES/dcs/signals/signals_swab.dm
deleted file mode 100644
index 1d9376b29440..000000000000
--- a/code/__DEFINES/dcs/signals/signals_swab.dm
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- *! ## Swab Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-//! /datum/component/swabbing signals
-/// Called when you try to swab something using the swabable component, includes a mutable list of what has been swabbed so far so it can be modified.
-////#define COMSIG_SWAB_FOR_SAMPLES "swab_for_samples"
- ////#define COMPONENT_SWAB_FOUND (1<<0)
diff --git a/code/__DEFINES/dcs/signals/signals_techweb.dm b/code/__DEFINES/dcs/signals/signals_techweb.dm
deleted file mode 100644
index be78012e1704..000000000000
--- a/code/__DEFINES/dcs/signals/signals_techweb.dm
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- *! ## Techweb Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Called when a techweb design is researched (datum/design/researched_design, custom)
-////#define COMSIG_TECHWEB_ADD_DESIGN "techweb_add_design"
-
-/// Called when a techweb design is removed (datum/design/removed_design, custom)
-////#define COMSIG_TECHWEB_REMOVE_DESIGN "techweb_remove_design"
diff --git a/code/__DEFINES/dcs/signals/signals_traitor.dm b/code/__DEFINES/dcs/signals/signals_traitor.dm
deleted file mode 100644
index 16a8f964859a..000000000000
--- a/code/__DEFINES/dcs/signals/signals_traitor.dm
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- *! ## Traitor Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Called when the hack_comm_console objective is completed.
-////#define COMSIG_GLOB_TRAITOR_OBJECTIVE_COMPLETED "!traitor_objective_completed"
-
-/// Called whenever the uplink handler receives any sort of update. Used by uplinks to update their UI. No arguments passed
-////#define COMSIG_UPLINK_HANDLER_ON_UPDATE "uplink_handler_on_update"
-
-/// Called before the traitor objective is generated
-////#define COMSIG_TRAITOR_OBJECTIVE_PRE_GENERATE "traitor_objective_pre_generate"
- ////#define COMPONENT_TRAITOR_OBJECTIVE_ABORT_GENERATION (1<<0)
-/// Called whenever the traitor objective is completed
-////#define COMSIG_TRAITOR_OBJECTIVE_COMPLETED "traitor_objective_completed"
-/// Called whenever the traitor objective is failed
-////#define COMSIG_TRAITOR_OBJECTIVE_FAILED "traitor_objective_failed"
-
-/// Called when a traitor bug is planted in an area
-////#define COMSIG_TRAITOR_BUG_PLANTED_GROUND "traitor_bug_planted_area"
-/// Called when a traitor bug is planted
-////#define COMSIG_TRAITOR_BUG_PLANTED_OBJECT "traitor_bug_planted_object"
-/// Called before a traitor bug is planted, where it can still be overrided
-////#define COMSIG_TRAITOR_BUG_PRE_PLANTED_OBJECT "traitor_bug_planted_pre_object"
- ////#define COMPONENT_FORCE_PLACEMENT (1<<0)
- ////#define COMPONENT_FORCE_FAIL_PLACEMENT (1<<1)
diff --git a/code/__DEFINES/dcs/signals/signals_tram.dm b/code/__DEFINES/dcs/signals/signals_tram.dm
deleted file mode 100644
index 2448b97fbf87..000000000000
--- a/code/__DEFINES/dcs/signals/signals_tram.dm
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- *! ## Tram Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Sent from /obj/structure/industrial_lift/tram when its travelling status updates. (travelling)
-////#define COMSIG_TRAM_SET_TRAVELLING "tram_set_travelling"
-
-/// Sent from /obj/structure/industrial_lift/tram when it begins to travel. (obj/landmark/tram/from_where, obj/landmark/tram/to_where)
-////#define COMSIG_TRAM_TRAVEL "tram_travel"
diff --git a/code/__DEFINES/dcs/signals/signals_transform.dm b/code/__DEFINES/dcs/signals/signals_transform.dm
deleted file mode 100644
index 70e9a50b7f72..000000000000
--- a/code/__DEFINES/dcs/signals/signals_transform.dm
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- *! ## Transform Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// From /datum/component/transforming/proc/on_attack_self(obj/item/source, mob/user): (obj/item/source, mob/user, active)
-////#define COMSIG_TRANSFORMING_PRE_TRANSFORM "transforming_pre_transform"
- ///? Return COMPONENT_BLOCK_TRANSFORM to prevent the item from transforming.
- ////#define COMPONENT_BLOCK_TRANSFORM (1<<0)
-/// From /datum/component/transforming/proc/do_transform(obj/item/source, mob/user): (obj/item/source, mob/user, active)
-////#define COMSIG_TRANSFORMING_ON_TRANSFORM "transforming_on_transform"
- ///? Return COMPONENT_NO_DEFAULT_MESSAGE to prevent the transforming component from displaying the default transform message / sound.
- ////#define COMPONENT_NO_DEFAULT_MESSAGE (1<<0)
diff --git a/code/__DEFINES/dcs/signals/signals_vehicle.dm b/code/__DEFINES/dcs/signals/signals_vehicle.dm
deleted file mode 100644
index eec3f538ce1a..000000000000
--- a/code/__DEFINES/dcs/signals/signals_vehicle.dm
+++ /dev/null
@@ -1,10 +0,0 @@
-/**
- *! ## Vehicle Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Called on a mob when they start riding a vehicle (obj/vehicle)
-////#define COMSIG_VEHICLE_RIDDEN "vehicle-ridden"
- ///? Return this to signal that the mob should be removed from the vehicle
- ////#define EJECT_FROM_VEHICLE (1<<0)
diff --git a/code/__DEFINES/dcs/signals/signals_wash.dm b/code/__DEFINES/dcs/signals/signals_wash.dm
deleted file mode 100644
index 68127bafba2c..000000000000
--- a/code/__DEFINES/dcs/signals/signals_wash.dm
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- *! ## Washing Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-/// Called when you wash your face at a sink: (num/strength)
-////#define COMSIG_COMPONENT_CLEAN_FACE_ACT "clean_face_act"
diff --git a/code/__DEFINES/dcs/signals/signals_xeno_control.dm b/code/__DEFINES/dcs/signals/signals_xeno_control.dm
deleted file mode 100644
index 6eaeba74a800..000000000000
--- a/code/__DEFINES/dcs/signals/signals_xeno_control.dm
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- *! ## Xenobiology Signals. Format:
- * * When the signal is called: (signal arguments)
- * * All signals send the source datum of the signal as the first argument
- */
-
-//! Xenobio Hotkeys
-/// From slime CtrlClickOn(): (/mob)
-////#define COMSIG_XENO_SLIME_CLICK_CTRL "xeno_slime_click_ctrl"
-/// From slime AltClickOn(): (/mob)
-////#define COMSIG_XENO_SLIME_CLICK_ALT "xeno_slime_click_alt"
-/// From slime ShiftClickOn(): (/mob)
-////#define COMSIG_XENO_SLIME_CLICK_SHIFT "xeno_slime_click_shift"
-/// From turf ShiftClickOn(): (/mob)
-////#define COMSIG_XENO_TURF_CLICK_SHIFT "xeno_turf_click_shift"
-/// From turf AltClickOn(): (/mob)
-////#define COMSIG_XENO_TURF_CLICK_CTRL "xeno_turf_click_alt"
-/// From monkey CtrlClickOn(): (/mob)
-////#define COMSIG_XENO_MONKEY_CLICK_CTRL "xeno_monkey_click_ctrl"
diff --git a/code/__DEFINES/frames.dm b/code/__DEFINES/frames.dm
new file mode 100644
index 000000000000..cdf493afb4e9
--- /dev/null
+++ b/code/__DEFINES/frames.dm
@@ -0,0 +1,76 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
+//* request types
+
+/// use x of a specific stack type
+/// request is typepath of stack
+/// amount is stack amount used
+#define FRAME_REQUEST_TYPE_STACK "!STACK"
+/// use x of a material
+/// request is typepath of material
+/// amount is stack amount used
+#define FRAME_REQUEST_TYPE_MATERIAL "!MATERIAL"
+/// use x of an item
+/// request is typepath of item
+/// amount is items used
+#define FRAME_REQUEST_TYPE_ITEM "!ITEM"
+/// rely on proc
+#define FRAME_REQUEST_TYPE_PROC "!PROC"
+/// interact with hand
+/// amount is time needed
+#define FRAME_REQUEST_TYPE_INTERACT "!INTERACT"
+/// request is tool function
+/// amount is time needed
+#define FRAME_REQUEST_TYPE_TOOL "!TOOL"
+
+//* special stages
+
+/// deconstruct; usually default behavior on reverting past first stage
+#define FRAME_STAGE_DECONSTRUCT "!DEL"
+/// finish; usually default behavior on progressing past last stage
+#define FRAME_STAGE_FINISH "!FIN"
+
+//* special contexts
+
+/// automatic context for storing items put into the frame
+#define FRAME_CONTEXT_FOR_STAGE(N) "![N]-parts"
+
+//* special things
+
+#define AUTO_FRAME_DATUM(TYPEPATH, EXTENSION, ICON) \
+/obj/structure/frame2/##EXTENSION { \
+ icon = ##ICON; \
+ frame = ##TYPEPATH; \
+} \
+/obj/structure/frame2/##EXTENSION/unanchored { \
+ anchored = FALSE; \
+} \
+/obj/item/frame2/##EXTENSION { \
+ icon = ##ICON; \
+ frame = ##TYPEPATH; \
+} \
+##TYPEPATH { \
+ icon = ##ICON; \
+}
+
+#define AUTO_FRAME_DATUM_UNANCHORABLE(TYPEPATH, EXTENSION, ICON) \
+AUTO_FRAME_DATUM(TYPEPATH, EXTENSION, ICON); \
+/obj/structure/frame2/##EXTENSION/unanchored { \
+ anchored = FALSE; \
+}
+
+//* text template fragments
+
+/// "firstname lastname"
+#define FRAME_TEXT_TOKEN_PERFORMER "!performer"
+/// "the fire alarm frame"
+#define FRAME_TEXT_TOKEN_FRAME "!frame"
+/// "the fire alarm electronics" or "the welding torch"
+#define FRAME_TEXT_TOKEN_TOOL "!tool"
+/// pronoun for 'their'
+#define FRAME_TEXT_TOKEN_THEIR "!their"
+/// pronoun for 'them'
+#define FRAME_TEXT_TOKEN_THEM "!them"
+/// pronoun for 'they're' (he's, she's, etc)
+#define FRAME_TEXT_TOKEN_THEYRE "!theyre"
diff --git a/code/__DEFINES/inventory/procs.dm b/code/__DEFINES/inventory/procs.dm
index 2f6581457636..ee68a510c377 100644
--- a/code/__DEFINES/inventory/procs.dm
+++ b/code/__DEFINES/inventory/procs.dm
@@ -60,7 +60,7 @@
#define INVENTORY_SLOT_DOES_NOT_EXIST -1
//! return values for inv view/strip/access/panel procs:
-//? /datum/inventory_slot_meta/proc/check_strip_conceal()
+//? /datum/inventory_slot/proc/check_strip_conceal()
/// do not show slot
#define INV_VIEW_OBFUSCATE_HIDE_SLOT (1<<0)
/// do not allow operations
diff --git a/code/__DEFINES/inventory/slots.dm b/code/__DEFINES/inventory/slots.dm
index d6a73c831463..682ea7ec1cf1 100644
--- a/code/__DEFINES/inventory/slots.dm
+++ b/code/__DEFINES/inventory/slots.dm
@@ -141,7 +141,7 @@ GLOBAL_LIST_INIT(slot_equipment_priority, meta_slot_equipment_priority())
SLOT_ID_GLASSES,
SLOT_ID_BELT,
SLOT_ID_SUIT_STORAGE,
- /datum/inventory_slot_meta/abstract/attach_as_accessory,
+ /datum/inventory_slot/abstract/attach_as_accessory,
SLOT_ID_LEFT_POCKET,
SLOT_ID_RIGHT_POCKET
)
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index 469155b777ba..a815909c061b 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -27,7 +27,7 @@
//Datums
-#define isTaurTail(A) istype(A, /datum/sprite_accessory/tail/taur)
+#define isTaurTail(A) istype(A, /datum/sprite_accessory/tail/legacy_taur)
//Turfs
diff --git a/code/__DEFINES/mobs/mobs.dm b/code/__DEFINES/mobs/mobs.dm
index 506bbc3637a8..978368266703 100644
--- a/code/__DEFINES/mobs/mobs.dm
+++ b/code/__DEFINES/mobs/mobs.dm
@@ -1,81 +1,6 @@
// todo: if i haven't bad touched something in here, i probably need to & will ~silicons
-// These are used as the layers for the icons, as well as indexes in a list that holds onto them.
-// Technically the layers used are all -100+layer to make them FLOAT_LAYER overlays.
-//! Human Overlays Indexes/////////
-/// Mutations like fat, and lasereyes
-#define MUTATIONS_LAYER 1
-/// Skin things added by a call on species
-#define SKIN_LAYER 2
-/// Bloodied hands/feet/anything else
-#define BLOOD_LAYER 3
-/// Injury overlay sprites like open wounds
-#define DAMAGE_LAYER 4
-/// Overlays for open surgical sites
-#define SURGERY_LAYER 5
-/// Underwear/bras/etc
-#define UNDERWEAR_LAYER 6
-/// Shoe-slot item (when set to be under uniform via verb)
-#define SHOES_LAYER_ALT 7
-/// Uniform-slot item
-#define UNIFORM_LAYER 8
-/// ID-slot item
-#define ID_LAYER 9
-/// Shoe-slot item
-#define SHOES_LAYER 10
-/// Glove-slot item
-#define GLOVES_LAYER 11
-/// Belt-slot item
-#define BELT_LAYER 12
-/// Suit-slot item
-#define SUIT_LAYER 13
-/// Some species have tails to render
-#define TAIL_LAYER 14
-/// Eye-slot item
-#define GLASSES_LAYER 15
-/// Belt-slot item (when set to be above suit via verb)
-#define BELT_LAYER_ALT 16
-/// Suit storage-slot item
-#define SUIT_STORE_LAYER 17
-/// Back-slot item
-#define BACK_LAYER 18
-/// The human's hair
-#define HAIR_LAYER 19
-/// Both ear-slot items (combined image)
-#define EARS_LAYER 20
-/// Mob's eyes (used for glowing eyes)
-#define EYES_LAYER 21
-/// Mask-slot item
-#define FACEMASK_LAYER 22
-/// Head-slot item
-#define HEAD_LAYER 23
-/// Handcuffs, if the human is handcuffed, in a secret inv slot
-#define HANDCUFF_LAYER 24
-/// Same as handcuffs, for legcuffs
-#define LEGCUFF_LAYER 25
-/// Left-hand item
-#define L_HAND_LAYER 26
-/// Right-hand item
-#define R_HAND_LAYER 27
-/// Wing overlay layer.
-#define WING_LAYER 28
-/// Tail alt. overlay layer for fixing overlay issues.
-#define TAIL_LAYER_ALT 29
-/// Effects drawn by modifiers
-#define MODIFIER_EFFECTS_LAYER 30
-/// 'Mob on fire' overlay layer
-#define FIRE_LAYER 31
-/// 'Mob submerged' overlay layer
-#define MOB_WATER_LAYER 32
-/// 'Aimed at' overlay layer
-#define TARGETED_LAYER 33
-//! KEEP THIS UPDATED, should always equal the highest number here, used to initialize a list.
-#define TOTAL_LAYERS 33
-//! the offset used
-#define BODY_LAYER -100
-//////////////////////////////////
-
// Bitflags defining which status effects could be or are inflicted on a mob.
// todo: this is all terrible tbh
#define STATUS_CAN_STUN (1<<0) //! Can Stun()
diff --git a/code/__DEFINES/mobs/rendering.dm b/code/__DEFINES/mobs/rendering.dm
new file mode 100644
index 000000000000..0b854a1f52e7
--- /dev/null
+++ b/code/__DEFINES/mobs/rendering.dm
@@ -0,0 +1,94 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+//* human overlay enums for standing_overlays
+
+#define HUMAN_OVERLAY_SKIN "skin"
+#define HUMAN_OVERLAY_MUTATIONS "mutations"
+#define HUMAN_OVERLAY_MODIFIERS "mutations"
+#define HUMAN_OVERLAY_BLOOD "blood"
+#define HUMAN_OVERLAY_EYES "eyes"
+#define HUMAN_OVERLAY_DAMAGE "damage"
+#define HUMAN_OVERLAY_SURGERY "surgery"
+#define HUMAN_OVERLAY_UNDERWEAR "underwear"
+#define HUMAN_OVERLAY_FIRE "fire"
+#define HUMAN_OVERLAY_LIQUID "liquid"
+#define HUMAN_OVERLAY_RHAND "rhand"
+#define HUMAN_OVERLAY_LHAND "lhand"
+
+// todo: sprite accessories list system
+
+#define HUMAN_OVERLAY_HAIR "hair"
+#define HUMAN_OVERLAY_FACEHAIR "facehair"
+#define HUMAN_OVERLAY_TAIL "tail"
+#define HUMAN_OVERLAY_WINGS "wings"
+#define HUMAN_OVERLAY_HORNS "horns"
+#define HUMAN_OVERLAY_EARS "ears"
+
+//* human rendering layers
+//* human layers are via FLOAT_LAYER so all of these are negative
+//* higher (so smaller subtracted numbers after FLOAT_LAYER) is on-top
+
+/// layer used for on-fire visual
+#define HUMAN_LAYER_FIRE (FLOAT_LAYER - 25)
+/// layer used for water/acid/etc overlays when you're in a pool or liquid
+#define HUMAN_LAYER_LIQUID (FLOAT_LAYER - 50)
+//! legacy - modifier graphics
+#define HUMAN_LAYER_MODIFIERS (FLOAT_LAYER - 75)
+
+#define HUMAN_LAYER_SLOT_RHAND (FLOAT_LAYER - 324)
+#define HUMAN_LAYER_SLOT_LHAND (FLOAT_LAYER - 325)
+
+#define HUMAN_LAYER_SLOT_LEGCUFFED (FLOAT_LAYER - 370)
+#define HUMAN_LAYER_SLOT_HANDCUFFED (FLOAT_LAYER - 371)
+
+#define HUMAN_LAYER_SLOT_HEAD (FLOAT_LAYER - 375)
+#define HUMAN_LAYER_SLOT_MASK (FLOAT_LAYER - 400)
+#define HUMAN_LAYER_SLOT_EYES (FLOAT_LAYER - 425)
+#define HUMAN_LAYER_SLOT_EARS (FLOAT_LAYER - 450)
+
+#define HUMAN_LAYER_SPRITEACC_HORNS_FRONT (FLOAT_LAYER - 475)
+#define HUMAN_LAYER_SPRITEACC_EARS_FRONT (FLOAT_LAYER - 476)
+#define HUMAN_LAYER_SPRITEACC_HAIR_FRONT (FLOAT_LAYER - 477)
+#define HUMAN_LAYER_SPRITEACC_FACEHAIR_FRONT (FLOAT_LAYER - 478)
+#define HUMAN_LAYER_SPRITEACC_WINGS_FRONT (FLOAT_LAYER - 479)
+#define HUMAN_LAYER_SPRITEACC_TAIL_FRONT (FLOAT_LAYER - 480)
+
+#define HUMAN_LAYER_SLOT_BACKPACK (FLOAT_LAYER - 500)
+#define HUMAN_LAYER_SLOT_SUITSTORE (FLOAT_LAYER - 525)
+#define HUMAN_LAYER_SLOT_BELT_ALT (FLOAT_LAYER - 550)
+#define HUMAN_LAYER_SLOT_GLASSES (FLOAT_LAYER - 575)
+
+#define HUMAN_LAYER_SLOT_OVERSUIT (FLOAT_LAYER - 625)
+#define HUMAN_LAYER_SLOT_BELT (FLOAT_LAYER - 650)
+#define HUMAN_LAYER_SLOT_GLOVES (FLOAT_LAYER - 675)
+#define HUMAN_LAYER_SLOT_SHOES (FLOAT_LAYER - 700)
+#define HUMAN_LAYER_SLOT_IDSLOT (FLOAT_LAYER - 725)
+#define HUMAN_LAYER_SLOT_UNIFORM (FLOAT_LAYER - 750)
+#define HUMAN_LAYER_SLOT_SHOES_ALT (FLOAT_LAYER - 775)
+
+//! legacy - underwear clothing
+#define HUMAN_LAYER_UNDERWEAR (FLOAT_LAYER - 800)
+//! legacy - surgery overlays
+#define HUMAN_LAYER_SURGERY (FLOAT_LAYER - 825)
+//! legacy - damage on limbs
+#define HUMAN_LAYER_DAMAGE (FLOAT_LAYER - 850)
+//! legacy - blood on skin
+#define HUMAN_LAYER_BLOOD (FLOAT_LAYER - 875)
+//! legacy - species skin
+#define HUMAN_LAYER_SKIN (FLOAT_LAYER - 900)
+
+#define HUMAN_LAYER_SPRITEACC_HORNS_BEHIND (FLOAT_LAYER - 950)
+#define HUMAN_LAYER_SPRITEACC_EARS_BEHIND (FLOAT_LAYER - 951)
+#define HUMAN_LAYER_SPRITEACC_HAIR_BEHIND (FLOAT_LAYER - 952)
+#define HUMAN_LAYER_SPRITEACC_FACEHAIR_BEHIND (FLOAT_LAYER - 953)
+#define HUMAN_LAYER_SPRITEACC_WINGS_BEHIND (FLOAT_LAYER - 954)
+#define HUMAN_LAYER_SPRITEACC_TAIL_BEHIND (FLOAT_LAYER - 955)
+
+//! legacy - genetics
+#define HUMAN_LAYER_MUTATIONS (FLOAT_LAYER - 1000)
+
+//* Helpers *//
+
+/// end proc immediately if we're being deleted or transformed into something
+#define HUMAN_RENDER_ABORT_IF_DELETING if(QDELING(src) || transforming) return
diff --git a/code/__DEFINES/mobs/sprite_accessories.dm b/code/__DEFINES/mobs/sprite_accessories.dm
index f6ebc867f7eb..2920f14941f5 100644
--- a/code/__DEFINES/mobs/sprite_accessories.dm
+++ b/code/__DEFINES/mobs/sprite_accessories.dm
@@ -7,3 +7,43 @@ DEFINE_BITFIELD(hair_flags, list(
BITFIELD(HAIR_TIEABLE),
))
// Hair Defines
+
+//* /datum/sprite_accessory/var/icon_sidedness
+//? These must be numbers! They also must be consequetive from 1 as they are list indices for rendering!
+
+/// no additional states
+#define SPRITE_ACCESSORY_SIDEDNESS_NONE 1
+/// -front state, and -behind state, use different layers
+#define SPRITE_ACCESSORY_SIDEDNESS_FRONT_BEHIND 2
+
+// todo: DEFINE_ENUM
+
+//* /datum/sprite_accessory/var/icon_alignment
+
+/// for some asinine reason just ignore it
+#define SPRITE_ACCESSORY_ALIGNMENT_IGNORE "ignore"
+/// center it east/west but align it to bottom edge
+#define SPRITE_ACCESSORY_ALIGNMENT_BOTTOM "bottom"
+/// center it fully
+#define SPRITE_ACCESSORY_ALIGNMENT_CENTER "center"
+
+// todo: DEFINE_ENUM
+
+//* Sprite Accessory Slots
+
+#define SPRITE_ACCESSORY_SLOT_TAIL "tail"
+#define SPRITE_ACCESSORY_SLOT_WINGS "wings"
+#define SPRITE_ACCESSORY_SLOT_HORNS "horns"
+#define SPRITE_ACCESSORY_SLOT_EARS "ears"
+#define SPRITE_ACCESSORY_SLOT_HAIR "hair"
+#define SPRITE_ACCESSORY_SLOT_FACEHAIR "facehair"
+
+// todo: DEFINE_ENUM
+
+//* Sprite Accessory Variations (Standard)
+
+#define SPRITE_ACCESSORY_VARIATION_FLAPPING "Flapping"
+#define SPRITE_ACCESSORY_VARIATION_WAGGING "Wagging"
+#define SPRITE_ACCESSORY_VARIATION_SPREAD "Spread"
+
+// todo: DEFINE_ENUM
diff --git a/code/__DEFINES/rendering/atom_huds.dm b/code/__DEFINES/rendering/atom_huds.dm
index 87181812b025..ab051252598b 100644
--- a/code/__DEFINES/rendering/atom_huds.dm
+++ b/code/__DEFINES/rendering/atom_huds.dm
@@ -1,62 +1,10 @@
-// for secHUDs and medHUDs and variants. The number is the location of the image on the list hud_list
-// note: if you add more HUDs, even for non-human atoms, make sure to use unique numbers for the defines!
-// /datum/atom_hud expects these to be unique
-// these need to be strings in order to make them associative lists
-/// dead, alive, sick, health status
-#define BIOLOGY_HUD "1"
-/// a simple line rounding the mob's number health
-#define LIFE_HUD "2"
-/// the job asigned to your ID
-#define ID_HUD "3"
-/// wanted, released, parroled, security status
-#define WANTED_HUD "4"
-/// loyality implant
-#define IMPLOYAL_HUD "5"
-/// chemical implant
-#define IMPCHEM_HUD "6"
-/// tracking implant
-#define IMPTRACK_HUD "7"
-/// antag icon
-#define ANTAG_HUD "8"
-/// animal alt appearance
-#define WORLD_BENDER_ANIMAL_HUD "9"
-// todo: datum hud icons
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
-// constant list lookup of hud to icon
-GLOBAL_LIST_INIT(hud_icon_files, list(
- BIOLOGY_HUD = 'icons/screen/atom_hud/biology.dmi',
- LIFE_HUD = 'icons/screen/atom_hud/health.dmi',
- WANTED_HUD = 'icons/screen/atom_hud/security.dmi',
- IMPLOYAL_HUD = 'icons/screen/atom_hud/implant.dmi',
- IMPCHEM_HUD = 'icons/screen/atom_hud/implant.dmi',
- IMPTRACK_HUD = 'icons/screen/atom_hud/implant.dmi',
- ANTAG_HUD = 'icons/screen/atom_hud/antag.dmi',
- ID_HUD = 'icons/screen/atom_hud/job.dmi'
-))
+//* sources
-// constant list lookup of hud to layer
-GLOBAL_LIST_INIT(hud_icon_layers, list(
- BIOLOGY_HUD = 1 // render above default
-))
-
-//by default everything in the hud_list of an atom is an image
-//a value in hud_list with one of these will change that behavior
-#define HUD_LIST_LIST 1
-
-//data HUD (medhud, sechud) defines
-//Don't forget to update human/New() if you change these!
-#define DATA_HUD_SECURITY_BASIC 1
-#define DATA_HUD_SECURITY_ADVANCED 2
-#define DATA_HUD_MEDICAL 3
-#define DATA_HUD_ID_JOB 4
-
-#define HUD_ANTAG 5
-
-#define WORLD_BENDER_HUD_ANIMALS 6
-
-///cooldown for being shown the images for any particular data hud
-#define ADD_HUD_TO_COOLDOWN 20
-
-//alternate appearance flags
-#define AA_TARGET_SEE_APPEARANCE (1<<0)
-#define AA_MATCH_TARGET_OVERLAYS (1<<1)
+#define ATOM_HUD_SOURCE_SILICON_SENSOR_AUGMENT "silicon-sensor-augment"
+#define ATOM_HUD_SOURCE_FOR_HUD_GRANTER_ON_EQUIPMENT_SLOT(SLOT) "equip-[SLOT]-granter"
+#define ATOM_HUD_SOURCE_OBSERVER "observer"
+#define ATOM_HUD_SOURCE_NIF "nif"
+#define ATOM_HUD_SOURCE_AUTOGRANT "autogrant"
diff --git a/code/__DEFINES/rendering/zmimic.dm b/code/__DEFINES/rendering/zmimic.dm
index e2b44def296f..7ceaa96115e1 100644
--- a/code/__DEFINES/rendering/zmimic.dm
+++ b/code/__DEFINES/rendering/zmimic.dm
@@ -1,41 +1,61 @@
+#define ZM_DESTRUCTION_TIMER(TARGET) addtimer(CALLBACK(TARGET, TYPE_PROC_REF(/datum, qdel_self)), 10 SECONDS, TIMER_STOPPABLE)
#define TURF_IS_MIMICKING(T) (isturf(T) && (T:mz_flags & MZ_MIMIC_BELOW))
-#define CHECK_OO_EXISTENCE(OO) if (OO && !MOVABLE_IS_ON_ZTURF(OO) && !OO.destruction_timer) { OO.destruction_timer = addtimer(CALLBACK(OO, TYPE_PROC_REF(/datum, qdel_self)), 10 SECONDS, TIMER_STOPPABLE); }
+#define CHECK_OO_EXISTENCE(OO) if (OO && !MOVABLE_IS_ON_ZTURF(OO) && !OO.destruction_timer) { OO.destruction_timer = ZM_DESTRUCTION_TIMER(OO); }
#define UPDATE_OO_IF_PRESENT CHECK_OO_EXISTENCE(bound_overlay); if (bound_overlay) { update_above(); }
// I do not apologize.
-#define MOVABLE_IS_BELOW_ZTURF(M) (isturf(loc) && ((M:zmm_flags & ZMM_LOOKAHEAD) ? ((get_step(M, M:dir)?:above?:mz_flags & MZ_MIMIC_BELOW) || (loc:above?:mz_flags & MZ_MIMIC_BELOW) || (get_step(M, global.reverse_dir[M:dir])?:above?:mz_flags & MZ_MIMIC_BELOW)) : TURF_IS_MIMICKING(loc:above)))
-#define MOVABLE_IS_ON_ZTURF(M) (isturf(loc) && ((M:zmm_flags & ZMM_LOOKAHEAD) ? ((get_step(M, M:dir)?:mz_flags & MZ_MIMIC_BELOW) || (loc:mz_flags & MZ_MIMIC_BELOW) || (get_step(M, global.reverse_dir[M:dir])?:mz_flags & MZ_MIMIC_BELOW)) : TURF_IS_MIMICKING(loc:above)))
-//# Turf Multi-Z flags.
-#define MZ_MIMIC_BELOW (1<<0) //! If this turf should mimic the turf on the Z below.
-#define MZ_MIMIC_OVERWRITE (1<<1) //! If this turf is Z-mimicing, overwrite the turf's appearance instead of using a movable. This is faster, but means the turf cannot have its own appearance (say, edges or a translucent sprite).
-#define MZ_MIMIC_NO_AO (1<<2) //! If the turf shouldn't apply regular turf AO and only do Z-mimic AO.
-#define MZ_MIMIC_BASETURF (1<<3) //! Mimic baseturf instead of the below atom. Sometimes useful for elevators.
+// These aren't intended to be used anywhere else, they just can't be undef'd because DM is dum.
+#define ZM_INTERNAL_SCAN_LOOKAHEAD(M,VTR,F) ((get_step(M, M:dir)?:VTR & F) || (get_step(M, turn(M:dir, 180))?:VTR & F))
+#define ZM_INTERNAL_SCAN_LOOKBESIDE(M,VTR,F) ((get_step(M, turn(M:dir, 90))?:VTR & F) || (get_step(M, turn(M:dir, -90))?:VTR & F))
-#define MZ_ALLOW_LIGHTING (1<<4) //! If this turf should permit passage of lighting.
-#define MZ_NO_OCCLUDE (1<<5) //! Don't occlude below atoms if we're a non-mimic z-turf.
+/// Is this movable visible from a turf that is mimicking below? Note: this does not necessarily mean *directly* below.
+#define MOVABLE_IS_BELOW_ZTURF(M) (\
+ isturf(M:loc) && (TURF_IS_MIMICKING(M:loc:above) \
+ || ((M:zmm_flags & ZMM_LOOKAHEAD) && ZM_INTERNAL_SCAN_LOOKAHEAD(M, above?:mz_flags, MZ_MIMIC_BELOW)) \
+ || ((M:zmm_flags & ZMM_LOOKBESIDE) && ZM_INTERNAL_SCAN_LOOKBESIDE(M, above?:mz_flags, MZ_MIMIC_BELOW))) \
+)
+/// Is this movable located on a turf that is mimicking below? Note: this does not necessarily mean *directly* on.
+#define MOVABLE_IS_ON_ZTURF(M) (\
+ (TURF_IS_MIMICKING(M:loc) \
+ || ((M:zmm_flags & ZMM_LOOKAHEAD) && ZM_INTERNAL_SCAN_LOOKAHEAD(M, mz_flags, MZ_MIMIC_BELOW)) \
+ || ((M:zmm_flags & ZMM_LOOKBESIDE) && ZM_INTERNAL_SCAN_LOOKBESIDE(M, mz_flags, MZ_MIMIC_BELOW))) \
+)
+#define MOVABLE_SHALL_MIMIC(AM) (!(AM.zmm_flags & ZMM_IGNORE) && MOVABLE_IS_BELOW_ZTURF(AM))
+
+// Turf MZ flags.
+#define MZ_MIMIC_BELOW (1 << 0) //! If this turf should mimic the turf on the Z below.
+#define MZ_MIMIC_OVERWRITE (1 << 1) //! If this turf is Z-mimicking, overwrite the turf's appearance instead of using a movable. This is faster, but means the turf cannot have its own appearance (say, edges or a translucent sprite).
+#define MZ_ALLOW_LIGHTING (1 << 2) //! If this turf should permit passage of lighting.
+#define MZ_MIMIC_NO_AO (1 << 3) //! If the turf shouldn't apply regular turf AO and only do Z-mimic AO.
+#define MZ_NO_OCCLUDE (1 << 4) //! Don't occlude below atoms if we're a non-mimic z-turf.
+#define MZ_OVERRIDE (1 << 5) //! Copy only z_appearance or baseturf and bail, do not attempt to copy movables. This is significantly cheaper and allows you to override the mimic, but results in movables not being visible.
+#define MZ_NO_SHADOW (1 << 6) //! If this turf is being copied, hide the shadower.
+#define MZ_TERMINATOR (1 << 7) //! Consider this turf the terminus of a Z-group, like the bottom of a Z-group or a MZ_OVERRIDE turf.
-#define MZ_OPEN_UP (1<<6) //! Allow atom movement through top.
-#define MZ_OPEN_DOWN (1<<7) //! Allow atom movement through bottom.
+#define MZ_OPEN_UP (1 << 8) //! Allow atom movement through top.
+#define MZ_OPEN_DOWN (1 << 9) //! Allow atom movement through bottom.
-#define MZ_ATMOS_UP (1<<8) //! Allow atmos passage through top.
-#define MZ_ATMOS_DOWN (1<<9) //! Allow atmos passage through bottom.
+#define MZ_ATMOS_UP (1 << 10) //! Allow atmos passage through top.
+#define MZ_ATMOS_DOWN (1 << 11) //! Allow atmos passage through bottom.
-//# Convenience flags.
-#define MZ_MIMIC_DEFAULTS (MZ_MIMIC_BELOW|MZ_ALLOW_LIGHTING)
+
+// Convenience flags.
+#define MZ_MIMIC_DEFAULTS (MZ_MIMIC_BELOW|MZ_ALLOW_LIGHTING) //! Common defaults for zturfs.
#define MZ_ATMOS_BOTH (MZ_ATMOS_UP|MZ_ATMOS_DOWN)
#define MZ_OPEN_BOTH (MZ_OPEN_UP|MZ_OPEN_DOWN)
+#define ZMM_WIDE_LOAD (ZMM_LOOKAHEAD | ZMM_LOOKBESIDE) //! Atom is big and needs to scan one extra turf in both X and Y. This only extends the range by one turf. Cheap, but not free.
-/// For debug purposes, should contain the above defines in ascending order.
-// TODO: Make it just print mz_flags bitfield. @Zandario
-var/list/mimic_defines = list(
+// For debug purposes, should contain the above defines in ascending order.
+var/global/list/mimic_defines = list(
"MZ_MIMIC_BELOW",
"MZ_MIMIC_OVERWRITE",
- "MZ_MIMIC_NO_AO",
- "MZ_MIMIC_BASETURF",
-
"MZ_ALLOW_LIGHTING",
+ "MZ_MIMIC_NO_AO",
"MZ_NO_OCCLUDE",
+ "MZ_OVERRIDE",
+ "MZ_NO_SHADOW",
+ "MZ_TERMINATOR",
"MZ_OPEN_UP",
"MZ_OPEN_DOWN",
@@ -47,11 +67,12 @@ var/list/mimic_defines = list(
DEFINE_BITFIELD(mz_flags, list(
BITFIELD(MZ_MIMIC_BELOW),
BITFIELD(MZ_MIMIC_OVERWRITE),
- BITFIELD(MZ_MIMIC_NO_AO),
- BITFIELD(MZ_MIMIC_BASETURF),
-
BITFIELD(MZ_ALLOW_LIGHTING),
+ BITFIELD(MZ_MIMIC_NO_AO),
BITFIELD(MZ_NO_OCCLUDE),
+ BITFIELD(MZ_OVERRIDE),
+ BITFIELD(MZ_NO_SHADOW),
+ BITFIELD(MZ_TERMINATOR),
BITFIELD(MZ_OPEN_UP),
BITFIELD(MZ_OPEN_DOWN),
@@ -61,12 +82,13 @@ DEFINE_BITFIELD(mz_flags, list(
))
-//# Movable mz_flags.
-#define ZMM_IGNORE (1<<0) //! Do not copy this movable. Atoms with INVISIBILITY_ABSTRACT implicitly do not copy.
-#define ZMM_MANGLE_PLANES (1<<1) //! Check this movable's overlays/underlays for explicit plane use and mangle for compatibility with Z-Mimic. If you're using emissive overlays, you probably should be using this flag. Expensive, only use if necessary.
-#define ZMM_LOOKAHEAD (1<<2) //! Look one turf ahead and one turf back when considering z-turfs that might be seeing this atom. Cheap, but not free.
-#define ZMM_AUTOMANGLE_NRML (1<<3) //! Behaves the same as ZMM_MANGLE_PLANES, but is automatically applied by SSoverlays. Do not manually use.
-#define ZMM_AUTOMANGLE_PRI (1<<4) //! Behaves the same as ZMM_MANGLE_PLANES, but is automatically applied by SSoverlays. Do not manually use.
+// Movable flags.
+#define ZMM_IGNORE (1 << 0) //! Do not copy this movable. Atoms may be excluded from copy automatically regardless of this flag.
+#define ZMM_MANGLE_PLANES (1 << 1) //! Check this movable's overlays/underlays for explicit plane use and mangle for compatibility with Z-Mimic. If you're using emissive overlays, you probably should be using this flag. Expensive, only use if necessary.
+#define ZMM_LOOKAHEAD (1 << 2) //! Look one turf ahead and one turf back when considering z-turfs that might be seeing this atom. Cheap, but not free.
+#define ZMM_LOOKBESIDE (1 << 3) //! Look one turf beside (left/right) when considering z-turfs that might be seeing this atom. Cheap, but not free.
+#define ZMM_AUTOMANGLE_NRML (1 << 4) //! Behaves the same as ZMM_MANGLE_PLANES, but is automatically applied by SSoverlays. Do not manually use.
+#define ZMM_AUTOMANGLE_PRI (1 << 5) //! Behaves the same as ZMM_MANGLE_PLANES, but is automatically applied by SSoverlays. Do not manually use.
#define ZMM_AUTOMANGLE (ZMM_AUTOMANGLE_NRML|ZMM_AUTOMANGLE_PRI) // convenience
@@ -74,6 +96,7 @@ DEFINE_BITFIELD(zmm_flags, list(
BITFIELD(ZMM_IGNORE),
BITFIELD(ZMM_MANGLE_PLANES),
BITFIELD(ZMM_LOOKAHEAD),
+ BITFIELD(ZMM_LOOKBESIDE),
BITFIELD(ZMM_AUTOMANGLE_NRML),
BITFIELD(ZMM_AUTOMANGLE_PRI)
))
diff --git a/code/__DEFINES/tgs.config.dm b/code/__DEFINES/tgs.config.dm
index 8c0f5d3fa2c6..3a4d8caae101 100644
--- a/code/__DEFINES/tgs.config.dm
+++ b/code/__DEFINES/tgs.config.dm
@@ -9,4 +9,4 @@
#define TGS_ERROR_LOG(message) log_world("TGS Error: [##message]")
#define TGS_NOTIFY_ADMINS(event) message_admins(##event)
#define TGS_CLIENT_COUNT GLOB.clients.len
-#define TGS_PROTECT_DATUM(Path) GENERAL_PROTECT_DATUM(##Path)
+#define TGS_PROTECT_DATUM(Path) VV_PROTECT(##Path)
diff --git a/code/__DEFINES/tools/functionality.dm b/code/__DEFINES/tools/functionality.dm
index bdeeefc443d0..a3c33e90bc78 100644
--- a/code/__DEFINES/tools/functionality.dm
+++ b/code/__DEFINES/tools/functionality.dm
@@ -23,6 +23,31 @@
#define TOOL_GLASS_CUT "glasskit"
#define TOOL_BONESET "bonesetter"
+GLOBAL_REAL_VAR(all_tool_functions) = list(
+ // engineering
+ TOOL_CROWBAR,
+ TOOL_MULTITOOL,
+ TOOL_SCREWDRIVER,
+ TOOL_WIRECUTTER,
+ TOOL_WRENCH,
+ TOOL_WELDER,
+ TOOL_ANALYZER,
+ // mining
+ TOOL_MINING,
+ TOOL_SHOVEL,
+ // surgery
+ TOOL_RETRACTOR,
+ TOOL_HEMOSTAT,
+ TOOL_CAUTERY,
+ TOOL_DRILL,
+ TOOL_SCALPEL,
+ TOOL_SAW,
+ // glassworking
+ TOOL_BLOW,
+ TOOL_GLASS_CUT,
+ TOOL_BONESET,
+)
+
/// Yes, this is a real global. No, you shouldn't touch this for no reason.
/// Add tools to this when they get states in the default icon file for:
/// - neutral (no append)
@@ -31,7 +56,7 @@
GLOBAL_REAL_VAR(_dyntool_image_states) = list(
null = "unknown",
TOOL_CROWBAR = "crowbar",
- TOOL_SCREWDRIVER = "screwdriver"
+ TOOL_SCREWDRIVER = "screwdriver",
)
//? Tool usage flags
@@ -57,3 +82,9 @@ GLOBAL_REAL_VAR(_dyntool_image_states) = list(
#define TOOL_LOCKING_STATIC 2
/// automatically, if we only have one dynamic behavior, use that
#define TOOL_LOCKING_AUTO 3
+
+//? Tool directions - used as hints.
+
+#define TOOL_DIRECTION_FORWARDS "forwards"
+#define TOOL_DIRECTION_BACKWARDS "backwards"
+#define TOOL_DIRECTION_NEUTRAL "neutral"
diff --git a/code/__DEFINES/traits/mob.dm b/code/__DEFINES/traits/mob.dm
index f49ef16af28e..8e4d0986bbb9 100644
--- a/code/__DEFINES/traits/mob.dm
+++ b/code/__DEFINES/traits/mob.dm
@@ -46,6 +46,12 @@ DATUM_TRAIT(/mob, TRAIT_MOB_UNCONSCIOUS)
#define TRAIT_MOB_SLEEPING "mob_sleeping"
DATUM_TRAIT(/mob, TRAIT_MOB_SLEEPING)
+//* Stance *//
+
+/// cannot be set to resting, even by death.
+#define TRAIT_MOB_FORCED_STANDING "mob_forced_standing"
+DATUM_TRAIT(/mob, TRAIT_MOB_FORCED_STANDING)
+
//? misc
/// Tracks whether you're a mime or not.
diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm
index f1d5c0a4fe9c..708300c3ca42 100644
--- a/code/__DEFINES/traits/sources.dm
+++ b/code/__DEFINES/traits/sources.dm
@@ -17,6 +17,11 @@
/// From a flashbulb
#define FLASH_TRAIT "flash"
+//* Machinery *//
+
+/// cryotube
+#define CRYO_TUBE_TRAIT "cryotube"
+
//? Mob Sources
/// From species
diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index 7889e5416ba5..2110b7cd224d 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -803,12 +803,6 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0
obj_flags &= ~FROZEN
*/
-/// Generate a filename for this asset
-/// The same asset will always lead to the same asset name
-/// (Generated names do not include file extention.)
-/proc/generate_asset_name(file)
- return "asset.[md5(fcopy_rsc(file))]"
-
/**
* Converts an icon to base64. Operates by putting the icon in the iconCache savefile,
* exporting it as text, and then parsing the base64 from that.
@@ -831,8 +825,6 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0
return
// if(SSlag_switch.measures[DISABLE_USR_ICON2HTML] && usr && !HAS_TRAIT(usr, TRAIT_BYPASS_MEASURES))
// return
-
- var/key
var/icon/I = thing
if (!target)
@@ -850,14 +842,10 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0
if (!isicon(I))
if (isfile(thing)) //special snowflake
- var/name = SANITIZE_FILENAME("[generate_asset_name(thing)].png")
- if (!SSassets.cache[name])
- SSassets.transport.register_asset(name, thing)
- for (var/thing2 in targets)
- SSassets.transport.send_assets(thing2, name)
+ var/url = SSassets.send_anonymous_file(targets, I, "png")
if(sourceonly)
- return SSassets.transport.get_asset_url(name)
- return ""
+ return url
+ return ""
var/atom/A = thing
I = A.icon
@@ -885,14 +873,10 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0
I = icon(I, icon_state, dir, frame, moving)
- key = "[generate_asset_name(I)].png"
- if(!SSassets.cache[key])
- SSassets.transport.register_asset(key, I)
- for (var/thing2 in targets)
- SSassets.transport.send_assets(thing2, key)
+ var/url = SSassets.send_anonymous_file(targets, I, "png")
if(sourceonly)
- return SSassets.transport.get_asset_url(key)
- return ""
+ return url
+ return ""
/proc/icon2base64html(thing)
if (!thing)
diff --git a/code/__HELPERS/icons/flatten.dm b/code/__HELPERS/icons/flatten.dm
index 08859a18b4b9..395068be6ee7 100644
--- a/code/__HELPERS/icons/flatten.dm
+++ b/code/__HELPERS/icons/flatten.dm
@@ -250,9 +250,12 @@
// gather
for(copying as anything in A.overlays)
// todo: better handling
- if(copying.plane != FLOAT_PLANE && copying.plane != A.plane)
+ if(copying.plane != FLOAT_PLANE)
// we don't care probably HUD or something lol
continue
+ if(copying.appearance_flags & KEEP_APART)
+ // we don't care about HUD / similar; this is a good litmus test
+ continue
current_layer = copying.layer
// if it's float layer, shove it right above atom.
if(current_layer < 0)
@@ -274,9 +277,12 @@
for(copying as anything in A.underlays)
// todo: better handling
- if(copying.plane != FLOAT_PLANE && copying.plane != A.plane)
+ if(copying.plane != FLOAT_PLANE)
// we don't care probably HUD or something lol
continue
+ if(copying.appearance_flags & KEEP_APART)
+ // we don't care about HUD / similar; this is a good litmus test
+ continue
current_layer = copying.layer
// if it's float layer, shove it right below atom.
if(current_layer < 0)
diff --git a/code/__HELPERS/icons/render.dm b/code/__HELPERS/icons/render.dm
new file mode 100644
index 000000000000..4fe3457f17bc
--- /dev/null
+++ b/code/__HELPERS/icons/render.dm
@@ -0,0 +1,20 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/proc/render_compound_icon_with_client(target, client/C)
+ var/icon/generated
+
+ // do one first
+ var/mutable_appearance/rendering = new /mutable_appearance(target)
+ rendering.dir = SOUTH
+ generated = icon(C.RenderIcon(rendering.appearance))
+
+ // do rest
+ rendering.dir = EAST
+ generated.Insert(C.RenderIcon(rendering.appearance), dir = EAST)
+ rendering.dir = NORTH
+ generated.Insert(C.RenderIcon(rendering.appearance), dir = NORTH)
+ rendering.dir = WEST
+ generated.Insert(C.RenderIcon(rendering.appearance), dir = WEST)
+
+ return generated
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index a11b5d22fa07..a44dcb3256b6 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -37,7 +37,7 @@
var/list/valid = list()
for(var/id in GLOB.sprite_accessory_hair)
var/datum/sprite_accessory/hair/S = GLOB.sprite_accessory_hair[id]
- if(S.gender != NEUTER && gender != S.gender)
+ if(!isnull(S.random_generation_gender) && gender != S.random_generation_gender)
continue
if(S.apply_restrictions && !(species in S.species_allowed))
continue
@@ -48,7 +48,7 @@
var/list/valid = list()
for(var/id in GLOB.sprite_accessory_facial_hair)
var/datum/sprite_accessory/facial_hair/S = GLOB.sprite_accessory_facial_hair[id]
- if(S.gender != NEUTER && gender != S.gender)
+ if(!isnull(S.random_generation_gender) && gender != S.random_generation_gender)
continue
if(S.apply_restrictions && !(species in S.species_allowed))
continue
diff --git a/code/__HELPERS/thermal.dm b/code/__HELPERS/thermal.dm
new file mode 100644
index 000000000000..95b5f4eb5779
--- /dev/null
+++ b/code/__HELPERS/thermal.dm
@@ -0,0 +1,74 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyrigth (c) 2024 LordME *//
+
+/**
+ * Calculates the new temperature and returns it.
+ *
+ * @params
+ * * initial_temperature - the temperature before the adding of the specified energy
+ * * heat_capacity - The thermal energy capacity of the object, may NEVER be 0
+ * * amount_in_joules - the amount of energy to be injected into the object
+ *
+ * @return the new temperature
+ */
+/proc/add_thermal_energy(initial_temperature, heat_capacity, amount_in_joules)
+ . = initial_temperature
+ if(heat_capacity)
+ . += amount_in_joules / heat_capacity
+ else
+ STACK_TRACE("Called add_thermal_energy with heat_capacity = 0")
+
+/**
+ * Calculates the energy needed to change the temperature to target_temperature
+ *
+ * @params
+ * * current_temperature - the temperature before the adding of the specified energy
+ * * heat_capacity - The thermal energy capacity of the object, may NEVER be 0
+ * * target_temperature - the temperature target
+ *
+ * @return the energy in joules
+ */
+/proc/get_thermal_energy_needed(current_temperature, heat_capacity, target_temperature)
+ return (target_temperature - current_temperature) * heat_capacity
+
+/**
+ * Calculates shared new temperature of the two objects, assuming infinite amount of time for energy to transfer
+ *
+ * @params
+ * * temperature_1 - temperature of thermal storage 1
+ * * heat_capacity_1 - thermal energy capacity of thermal storage 1
+ * * temperature_2 - temperature of thermal storage 2
+ * * heat_capacity_2 - thermal energy capacity of thermal storage 2
+ *
+ * @return the new temperature of both
+ */
+/proc/share_thermal_energy(temperature_1, heat_capacity_1, temperature_2, heat_capacity_2)
+ if(heat_capacity_1 || heat_capacity_2)//We are just catching math errors
+ return (temperature_1 * heat_capacity_1 + temperature_2 * heat_capacity_2) / heat_capacity_1 + heat_capacity_2
+ CRASH("Called share_thermal_energy with invalid heat_capacity_1 and heat_capacity_2")
+
+/proc/share_thermal_energy_gas(temperature, heat_capacity, datum/gas_mixture/gas)
+ var/temp_2 = gas.temperature
+ var/heat_cap_2 = gas.heat_capacity()
+ return share_thermal_energy(temperature, heat_capacity, temp_2, heat_cap_2)
+/**
+ * Calculates shared new temperature of the two objects, assuming one second for energy to transfer
+ * takes conductivity into account, stepsize is 1 second
+ *
+ * @params
+ * * temperature_1 - temperature of thermal storage 1
+ * * heat_capacity_1 - thermal energy capacity of thermal storage 1
+ * * temperature_2 - temperature of thermal storage 2
+ * * heat_capacity_2 - thermal energy capacity of thermal storage 2
+ * * thermal_conductivity - the conductivity to be used for the transfer
+ *
+ * @return the new temperature of thermal object 1
+ */
+/proc/share_thermal_energy_conductivity(temperature_1, heat_capacity_1, temperature_2, heat_capacity_2, thermal_conductivity)
+ return (temperature_1 + thermal_conductivity * (temperature_1 - temperature_2) / heat_capacity_1)
+
+#define SHARE_THERMALS(t1, c1, t2, c2) share_thermal_energy(t1,c1,t2,c2)
+#define ADD_THERMALS(t, c, e) add_thermal_energy(t, c, e)
+#define GET_THERMALS(t, c, tt) get_thermal_energy_needed(t, c, tt)
+
+
diff --git a/code/__HELPERS/type2type/type2type.dm b/code/__HELPERS/type2type/type2type.dm
index cc4593683c1f..aaf3b72857b6 100644
--- a/code/__HELPERS/type2type/type2type.dm
+++ b/code/__HELPERS/type2type/type2type.dm
@@ -311,11 +311,6 @@
else //regex everything else (works for /proc too)
return lowertext(replacetext("[the_type]", "[type2parent(the_type)]/", ""))
-/// Return html to load a url.
-/// for use inside of browse() calls to html assets that might be loaded on a cdn.
-/proc/url2htmlloader(url)
- return {"
"}
-
// Converts a string into ascii then converts it into hexadecimal.
/proc/strtohex(str)
if(!istext(str)||!str)
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 1cda55518d3c..df7c75ef2559 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -1091,7 +1091,7 @@ var/list/WALLITEMS = list(
/obj/machinery/power/apc, /obj/machinery/air_alarm, /obj/item/radio/intercom, /obj/structure/frame,
/obj/structure/extinguisher_cabinet, /obj/structure/reagent_dispensers/peppertank,
/obj/machinery/status_display, /obj/machinery/requests_console, /obj/machinery/light_switch, /obj/structure/sign,
- /obj/machinery/newscaster, /obj/machinery/firealarm, /obj/structure/noticeboard, /obj/machinery/button/remote,
+ /obj/machinery/newscaster, /obj/machinery/fire_alarm, /obj/structure/noticeboard, /obj/machinery/button/remote,
/obj/machinery/computer/security/telescreen, /obj/machinery/embedded_controller/radio,
/obj/item/storage/secure/safe, /obj/machinery/door_timer, /obj/machinery/flasher, /obj/machinery/keycard_auth,
/obj/structure/mirror, /obj/structure/fireaxecabinet, /obj/machinery/computer/security/telescreen/entertainment,
diff --git a/code/___compile_options.dm b/code/___compile_options.dm
index 1421a138ce94..96e0b8ef458f 100644
--- a/code/___compile_options.dm
+++ b/code/___compile_options.dm
@@ -71,13 +71,6 @@
*/
// #define USE_BYOND_TRACY
-
-/**
- * If defined, we will NOT defer asset generation till later in the game, and will instead do it all at once, during initiialize.
- */
-//#define DO_NOT_DEFER_ASSETS
-
-
/**
* If this is uncommented, Autowiki will generate edits and shut down the server.
* Prefer the autowiki build target instead.
@@ -185,7 +178,12 @@
#define ZAS_BREAKPOINT_HOOKS
#endif
+// ## Assets
+/**
+ * If defined, we will NOT defer asset generation till later in the game, and will instead do it all at once, during initiialize.
+ */
+//#define DO_NOT_DEFER_ASSETS
// ## Overlays
/**
diff --git a/code/_globals/lists/mobs.dm b/code/_globals/lists/mobs.dm
index 487fd5c1d14e..ed0d635f6420 100644
--- a/code/_globals/lists/mobs.dm
+++ b/code/_globals/lists/mobs.dm
@@ -2,164 +2,3 @@
GLOBAL_LIST_EMPTY(mob_list)
/// all player mobs (not clients!)
GLOBAL_LIST_EMPTY(player_list)
-
-/// by id
-GLOBAL_LIST_INIT(sprite_accessory_hair, all_hair_styles())
-/// by id
-GLOBAL_LIST_INIT(sprite_accessory_ears, all_ear_styles())
-/// by id
-GLOBAL_LIST_INIT(sprite_accessory_tails, all_tail_styles())
-/// by id
-GLOBAL_LIST_INIT(sprite_accessory_wings, all_wing_styles())
-/// by id
-GLOBAL_LIST_INIT(sprite_accessory_facial_hair, all_facial_hair_styles())
-/// by id
-GLOBAL_LIST_INIT(sprite_accessory_markings, all_marking_styles())
-
-// todo: most uses of these should either be a direct ref under new marking system or
-// todo: an id to ref.
-// todo: however, there are some legitimate cases of needing fast name lookup,
-// todo: like non-tgui interfaces that let you choose markings
-// todo: do not blindly kill these lists, we'll deal with everything as we go.
-
-// by name
-GLOBAL_LIST(legacy_hair_lookup)
-// by id
-GLOBAL_LIST(legacy_ears_lookup)
-// by id
-GLOBAL_LIST(legacy_wing_lookup)
-// by id
-GLOBAL_LIST(legacy_tail_lookup)
-// by name
-GLOBAL_LIST(legacy_facial_hair_lookup)
-// by name
-GLOBAL_LIST(legacy_marking_lookup)
-
-/proc/all_hair_styles()
- . = list()
- var/list/by_name = list()
- for(var/path in subtypesof(/datum/sprite_accessory/hair))
- var/datum/sprite_accessory/S = path
- if(initial(S.abstract_type) == path)
- continue
- S = new path
- if(!S.id)
- stack_trace("no id on [path]")
- continue
- if(.[S.id])
- stack_trace("duplicate id [S.id] on [path] and [.[S.id]]")
- continue
- if(by_name[S.name])
- stack_trace("duplicate name [S.name] on [path]")
- continue
- .[S.id] = S
- by_name[S.name] = S
- tim_sort(by_name, GLOBAL_PROC_REF(cmp_text_asc), associative = FALSE)
- GLOB.legacy_hair_lookup = by_name
- tim_sort(., GLOBAL_PROC_REF(cmp_name_asc), associative = TRUE)
-
-/proc/all_ear_styles()
- . = list()
- var/list/by_type = list()
- for(var/path in subtypesof(/datum/sprite_accessory/ears))
- var/datum/sprite_accessory/S = path
- if(initial(S.abstract_type) == path)
- continue
- S = new path
- if(!S.id)
- stack_trace("no id on [path]")
- continue
- if(.[S.id])
- stack_trace("duplicate id [S.id] on [path] and [.[S.id]]")
- continue
- .[S.id] = S
- by_type[S.type] = S
- tim_sort(by_type, GLOBAL_PROC_REF(cmp_name_asc), associative = TRUE)
- GLOB.legacy_ears_lookup = by_type
- tim_sort(., GLOBAL_PROC_REF(cmp_name_asc), associative = TRUE)
-
-/proc/all_wing_styles()
- . = list()
- var/list/by_type = list()
- for(var/path in subtypesof(/datum/sprite_accessory/wing))
- var/datum/sprite_accessory/S = path
- if(initial(S.abstract_type) == path)
- continue
- S = new path
- if(!S.id)
- stack_trace("no id on [path]")
- continue
- if(.[S.id])
- stack_trace("duplicate id [S.id] on [path] and [.[S.id]]")
- continue
- .[S.id] = S
- by_type[S.type] = S
- tim_sort(by_type, GLOBAL_PROC_REF(cmp_name_asc), associative = TRUE)
- GLOB.legacy_wing_lookup = by_type
- tim_sort(., GLOBAL_PROC_REF(cmp_name_asc), associative = TRUE)
-
-/proc/all_tail_styles()
- . = list()
- var/list/by_type = list()
- for(var/path in subtypesof(/datum/sprite_accessory/tail))
- var/datum/sprite_accessory/S = path
- if(initial(S.abstract_type) == path)
- continue
- S = new path
- if(!S.id)
- stack_trace("no id on [path]")
- continue
- if(.[S.id])
- stack_trace("duplicate id [S.id] on [path] and [.[S.id]]")
- continue
- .[S.id] = S
- by_type[S.type] = S
- tim_sort(by_type, GLOBAL_PROC_REF(cmp_name_asc), associative = TRUE)
- GLOB.legacy_tail_lookup = by_type
- tim_sort(., GLOBAL_PROC_REF(cmp_name_asc), associative = TRUE)
-
-/proc/all_facial_hair_styles()
- . = list()
- var/list/by_name = list()
- for(var/path in subtypesof(/datum/sprite_accessory/facial_hair))
- var/datum/sprite_accessory/S = path
- if(initial(S.abstract_type) == path)
- continue
- S = new path
- if(!S.id)
- stack_trace("no id on [path]")
- continue
- if(.[S.id])
- stack_trace("duplicate id [S.id] on [path] and [.[S.id]]")
- continue
- if(by_name[S.name])
- stack_trace("duplicate name [S.name] on [path]")
- continue
- .[S.id] = S
- by_name[S.name] = S
- tim_sort(by_name, GLOBAL_PROC_REF(cmp_text_asc), associative = FALSE)
- GLOB.legacy_facial_hair_lookup = by_name
- tim_sort(., GLOBAL_PROC_REF(cmp_name_asc), associative = TRUE)
-
-/proc/all_marking_styles()
- . = list()
- var/list/by_name = list()
- for(var/path in subtypesof(/datum/sprite_accessory/marking))
- var/datum/sprite_accessory/S = path
- if(initial(S.abstract_type) == path)
- continue
- S = new path
- if(!S.id)
- stack_trace("no id on [path]")
- continue
- if(.[S.id])
- stack_trace("duplicate id [S.id] on [path] and [.[S.id]]")
- continue
- if(by_name[S.name])
- stack_trace("duplicate name [S.name] on [path]")
- continue
- .[S.id] = S
- by_name[S.name] = S
- tim_sort(by_name, GLOBAL_PROC_REF(cmp_text_asc), associative = FALSE)
- GLOB.legacy_marking_lookup = by_name
- tim_sort(., GLOBAL_PROC_REF(cmp_name_asc), associative = TRUE)
diff --git a/code/controllers/configuration/entries/resources.dm b/code/controllers/configuration/entries/resources.dm
index c839ccc078d4..be1ae7c1d7ac 100644
--- a/code/controllers/configuration/entries/resources.dm
+++ b/code/controllers/configuration/entries/resources.dm
@@ -14,8 +14,8 @@
/datum/config_entry/string/asset_cdn_webroot/ValidateAndSet(str_var)
if (!str_var || trim(str_var) == "")
return FALSE
- if (str_var && str_var[length(str_var)] != "/")
- str_var += "/"
+ if (str_var && str_var[length_char(str_var)] == "/")
+ str_var = copytext_char(str_var, 1, length_char(str_var))
return ..(str_var)
/datum/config_entry/string/asset_cdn_url
@@ -25,6 +25,6 @@
/datum/config_entry/string/asset_cdn_url/ValidateAndSet(str_var)
if (!str_var || trim(str_var) == "")
return FALSE
- if (str_var && str_var[length(str_var)] != "/")
- str_var += "/"
+ if (str_var && str_var[length_char(str_var)] == "/")
+ str_var = copytext_char(str_var, 1, length_char(str_var))
return ..(str_var)
diff --git a/code/controllers/configuration_old/configuration_vr.dm b/code/controllers/configuration_old/configuration_vr.dm
index 8fae7b7d57d9..3b61d5dd19dd 100644
--- a/code/controllers/configuration_old/configuration_vr.dm
+++ b/code/controllers/configuration_old/configuration_vr.dm
@@ -4,8 +4,6 @@
/datum/configuration_legacy
var/time_off = FALSE
- var/pto_job_change = FALSE
- var/pto_cap = 100 //Hours
/hook/startup/proc/read_vs_config()
var/list/Lines = world.file2list("config/legacy/config.txt")
@@ -38,10 +36,6 @@
config_legacy.chat_webhook_key = value
if ("items_survive_digestion")
config_legacy.items_survive_digestion = 1
- if ("pto_cap")
- config_legacy.pto_cap = text2num(value)
if ("time_off")
config_legacy.time_off = TRUE
- if ("pto_job_change")
- config_legacy.pto_job_change = TRUE
return 1
diff --git a/code/controllers/subsystem/asset_loading.dm b/code/controllers/subsystem/asset_loading.dm
index f51ac236352b..27400cf89f5d 100644
--- a/code/controllers/subsystem/asset_loading.dm
+++ b/code/controllers/subsystem/asset_loading.dm
@@ -6,25 +6,22 @@
SUBSYSTEM_DEF(asset_loading)
name = "Asset Loading"
priority = FIRE_PRIORITY_ASSET_LOADING
+ // todo: hibernation
subsystem_flags = SS_NO_INIT
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
- var/list/datum/asset/generate_queue = list()
-/datum/controller/subsystem/asset_loading/fire(resumed)
- while(length(generate_queue))
- var/datum/asset/to_load = generate_queue[generate_queue.len]
+ /// things waiting to be loaded
+ var/list/datum/asset_pack/loading = list()
- to_load.queued_generation()
+/datum/controller/subsystem/asset_loading/proc/queue_asset_pack(datum/asset_pack/asset)
+ if(asset.loaded != ASSET_NOT_LOADED)
+ return
+ loading += asset
+/datum/controller/subsystem/asset_loading/fire(resumed)
+ while(length(loading))
+ var/datum/asset_pack/instance = loading[loading.len]
+ loading.len--
+ instance.ensure_ready(TRUE)
if(MC_TICK_CHECK)
return
- generate_queue.len--
-
-/datum/controller/subsystem/asset_loading/proc/queue_asset(datum/asset/queue)
-#ifdef DO_NOT_DEFER_ASSETS
- stack_trace("We queued an instance of [queue.type] for lateloading despite not allowing it")
-#endif
- generate_queue += queue
-
-/datum/controller/subsystem/asset_loading/proc/dequeue_asset(datum/asset/queue)
- generate_queue -= queue
diff --git a/code/controllers/subsystem/assets.dm b/code/controllers/subsystem/assets.dm
index f467a934cb51..91d10a49f228 100644
--- a/code/controllers/subsystem/assets.dm
+++ b/code/controllers/subsystem/assets.dm
@@ -2,37 +2,229 @@ SUBSYSTEM_DEF(assets)
name = "Assets"
init_order = INIT_ORDER_ASSETS
subsystem_flags = SS_NO_FIRE
- var/list/datum/asset_cache_item/cache = list()
- var/list/preload = list()
- var/datum/asset_transport/transport = new()
+
+ /// asset packs by type; this is for hardcoded assets
+ ///
+ /// an asset is either registered by type, or id, never both.
+ var/static/list/asset_packs_by_type = list()
+ /// asset packs by id; this is for dynamic assets
+ ///
+ /// an asset is either registered by type, or id, never both.
+ var/static/list/asset_packs_by_id = list()
+ /// all asset packs
+ var/static/list/datum/asset_pack/asset_packs = list()
+ /// asset packs we're going to preload via native (browse_rsc) transport
+ var/list/datum/asset_pack/asset_packs_to_natively_preload = list()
+
+ /// all dynamic or standalone asset items that were registered with name (which is also their uid)
+ var/static/list/datum/asset_item/dynamic/dynamic_asset_items_by_name = list()
+
+ /// our active asset transport
+ var/datum/asset_transport/transport
+
+ // todo: cache system.
+
+ /// if non-null, this is our effective cache commit
+ // var/cache_commit
+ /// are we using cached data this round?
+ // var/cache_enabled = FALSE
+
+/datum/controller/subsystem/assets/Initialize(timeofday)
+ // detect_cache_worthiness()
+
+ for(var/datum/asset_pack/path as anything in typesof(/datum/asset_pack))
+ if(path == initial(path.abstract_type))
+ continue
+ var/datum/asset_pack/instance = new path
+ register_asset_pack(instance, TRUE)
+
+#ifndef DO_NOT_DEFER_ASSETS
+ if(initial(instance.load_deferred))
+ continue
+ else if(initial(instance.load_immediately))
+ instance.load()
+ else
+ SSasset_loading.queue_asset_pack(instance)
+#else
+ instance.load()
+#endif
+
+ return ..()
+
+/**
+ * register an asset pack to make it able to be resolved or loaded
+ *
+ * @params
+ * * pack - the pack
+ * * by_type - do not use this, only used for subsystem.
+ */
+/datum/controller/subsystem/assets/proc/register_asset_pack(datum/asset_pack/pack, by_type)
+ var/registered = FALSE
+ if(by_type)
+ ASSERT(!asset_packs_by_type[pack.type])
+ asset_packs_by_type[pack.type] = pack
+ registered = TRUE
+ if(pack.id)
+ ASSERT(!asset_packs_by_id[pack.id])
+ asset_packs_by_id[pack.id] = pack
+ registered = TRUE
+ if(!registered)
+ CRASH("couldn't register by type or id")
+ asset_packs += pack
+ if(should_preload_native_pack(pack))
+ asset_packs_to_natively_preload += pack
+
+/datum/controller/subsystem/assets/proc/should_preload_native_pack(datum/asset_pack/pack)
+ if(pack.do_not_preload)
+ return FALSE
+ return CONFIG_GET(flag/asset_simple_preload)
+
+/datum/controller/subsystem/assets/proc/immediately_ready_all_packs()
+ for(var/datum/asset_pack/pack as anything in asset_packs)
+ pack.ensure_ready()
+
+/**
+ * fetches an asset datum, **without** ensuring it's ready / loaded
+ *
+ * @return asset pack resolved
+ */
+/datum/controller/subsystem/assets/proc/resolve_asset_pack(identifier)
+ if(istype(identifier, /datum/asset_pack))
+ return identifier
+ if(ispath(identifier))
+ return asset_packs_by_type[identifier]
+ else
+ return asset_packs_by_id[identifier]
+
+/**
+ * fetches an asset datum, and ensures it's ready / loaded
+ *
+ * * This proc can block if something needs to generate.
+ *
+ * @return asset pack resolved
+ */
+/datum/controller/subsystem/assets/proc/ready_asset_pack(identifier)
+ var/datum/asset_pack/resolved = resolve_asset_pack(identifier)
+ resolved.ensure_ready()
+ return resolved
+
+/**
+ * ensures an asset has been sent to a client
+ *
+ * @params
+ * * target - a client or a list of clients
+ * * identifier - asset type, id, or instance
+ *
+ * @return TRUE if an asset had to be sent, FALSE if the client (is supposed to) already have it.
+ */
+/datum/controller/subsystem/assets/proc/send_asset_pack(client/target, identifier)
+ if(isnull(target))
+ return FALSE
+
+ var/datum/asset_pack/resolved = ready_asset_pack(identifier)
+
+ var/list/targets = islist(target)? target : list(target)
+
+ for(var/client/C as anything in targets)
+ transport.send_asset_pack(C, resolved)
+
+/**
+ * loads a file that we declare to not be necessary to keep around / store information on after
+ * the necessary clients have loaded it.
+ *
+ * * blocking proc
+ * * warning - this can clog a client's browse queue. use send_anonymous_files() to send multiple in a short period of time!
+ *
+ * @params
+ * * targets - client or list of clients to send it to
+ * * file - the file in question
+ * * ext - mandatory extension for the file. do not include the '.'
+ *
+ * @return url to load it with; this will usually be heavily mangled.
+ */
+/datum/controller/subsystem/assets/proc/send_anonymous_file(list/client/targets, file, ext)
+ ASSERT(ext)
+ return transport.send_anonymous_file(targets, file, ext)
+
+/**
+ * loads a set of files that we declare to not be necessary to keep around / store information on after
+ * the necessary clients have loaded it.
+ *
+ * * blocking proc
+ * * you should probably not use this if you can; it's a little janky.
+ *
+ * @params
+ * * targets - client or list of clients to send it to
+ * * files - the files in question, associated to their extensions
+ *
+ * @return list(urls) in same order as files.
+ */
+/datum/controller/subsystem/assets/proc/send_anonymous_files(list/client/targets, list/files)
+ // todo: optimize this proc
+ . = new /list(length(files))
+ for(var/i in 1 to length(files))
+ var/file = files[i]
+ .[i] = send_anonymous_file(targets, file, files[file])
+ stoplag(0)
+
+/**
+ * @params
+ * * file - a file
+ * * name - the name for the file. if files is a list, this is ignored
+ * * do_not_mangle - do not mangle / unique-ify the name of the file.
+ *
+ * @return /datum/asset_item/dynamic
+ */
+/datum/controller/subsystem/assets/proc/register_dynamic_item_by_name(file, name, do_not_mangle)
+ RETURN_TYPE(/datum/asset_item/dynamic)
+ if(dynamic_asset_items_by_name[name])
+ . = FALSE
+ CRASH("collision on [name]; automatically-updating dynamic items are not yet supported.")
+ var/datum/asset_item/dynamic/created = new(name, file, do_not_mangle = do_not_mangle)
+ return created
+
+/datum/controller/subsystem/assets/proc/send_dynamic_item_by_name(list/client/clients, list/names)
+ if(!islist(clients))
+ clients = list(clients)
+ for(var/name in names)
+ var/datum/asset_item/dynamic/item = dynamic_asset_items_by_name[name]
+ item?.send(clients)
+
+/datum/controller/subsystem/assets/proc/get_dynamic_item_url_by_name(name)
+ return dynamic_asset_items_by_name[name]?.get_url()
/datum/controller/subsystem/assets/OnConfigLoad()
- var/newtransporttype = /datum/asset_transport
+ var/newtransporttype = /datum/asset_transport/browse_rsc
switch (CONFIG_GET(string/asset_transport))
if ("webroot")
newtransporttype = /datum/asset_transport/webroot
- if (newtransporttype == transport.type)
+ if (newtransporttype == transport?.type)
return
- var/datum/asset_transport/newtransport = new newtransporttype ()
- if (newtransport.validate_config())
- transport = newtransport
- transport.Load()
+ var/datum/asset_transport/newtransport = new newtransporttype
+ if (!newtransport.validate_config())
+ stack_trace("failed to validate config; going back to browse_rsc")
+ qdel(newtransport)
+ newtransport = new /datum/asset_transport/browse_rsc
-/datum/controller/subsystem/assets/Initialize(timeofday)
- for(var/type in typesof(/datum/asset))
- var/datum/asset/A = type
- if(type == initial(A.abstract_type))
- continue
- if(initial(A.lazy))
- continue
- load_asset_datum(type)
+ set_transport_to(newtransport)
- transport.Initialize(cache)
+/datum/controller/subsystem/assets/proc/set_transport_to(datum/asset_transport/new_transport)
+ QDEL_NULL(transport)
+ transport = new_transport
- return ..()
+ // unload all asset packs
+ for(var/datum/asset_pack/pack in asset_packs)
+ pack.loaded_urls = null
+ // unload all dynamic items
+ for(var/name in dynamic_asset_items_by_name)
+ var/datum/asset_item/dynamic/item = dynamic_asset_items_by_name[name]
+ if(!istype(item))
+ continue
+ item.loaded_url = null
+
+ transport.initialize()
-/datum/controller/subsystem/assets/Recover()
- cache = SSassets.cache
- preload = SSassets.preload
+/datum/controller/subsystem/assets/proc/preload_client_assets(client/target)
+ transport.perform_native_preload(target, asset_packs_to_natively_preload)
diff --git a/code/controllers/subsystem/early_assets.dm b/code/controllers/subsystem/early_assets.dm
deleted file mode 100644
index b33f96126572..000000000000
--- a/code/controllers/subsystem/early_assets.dm
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * Initializes any assets that need to be loaded ASAP.
- * This houses preference menu assets, since they can be loaded at any time,
- * most dangerously before the atoms SS initializes.
- * Thus, we want it to fail consistently in CI as if it would've if a player
- * opened it up early.
- */
-SUBSYSTEM_DEF(early_assets)
- name = "Early Assets"
- init_order = INIT_ORDER_EARLY_ASSETS
- subsystem_flags = SS_NO_FIRE
-
-/datum/controller/subsystem/early_assets/Initialize(timeofday)
- for (var/datum/asset/asset_type as anything in subtypesof(/datum/asset))
- if (initial(asset_type.abstract_type) == asset_type)
- continue
-
- if (!initial(asset_type.early))
- continue
-
- if (!load_asset_datum(asset_type))
- stack_trace("Could not initialize early asset [asset_type]!")
-
- CHECK_TICK
-
- return ..()
diff --git a/code/controllers/subsystem/mapping/_mapping.dm b/code/controllers/subsystem/mapping/_mapping.dm
index 004da48cbb13..ed3ec7620909 100644
--- a/code/controllers/subsystem/mapping/_mapping.dm
+++ b/code/controllers/subsystem/mapping/_mapping.dm
@@ -19,6 +19,8 @@ SUBSYSTEM_DEF(mapping)
var/list/areas_in_z = list()
/datum/controller/subsystem/mapping/Initialize(timeofday)
+ TO_WORLD("Loading with Map: [src.loaded_station]")//This is just for IntTest logs
+
// load data
// todo: refactor
load_map_templates()
diff --git a/code/controllers/subsystem/mapping/maps.dm b/code/controllers/subsystem/mapping/maps.dm
index c96132739c73..d5a195c6a0d0 100644
--- a/code/controllers/subsystem/mapping/maps.dm
+++ b/code/controllers/subsystem/mapping/maps.dm
@@ -71,6 +71,7 @@
if(isnull(default))
stack_trace("no default map; world init is likely going to explode.")
#ifdef FORCE_MAP
+ #warn FORCE_MAP is enabled! Don't forget to disable this before pushing.
if(keyed_maps[FORCE_MAP])
next_map = keyed_maps[FORCE_MAP]
subsystem_log("loaded forced map [FORCE_MAP]")
diff --git a/code/controllers/subsystem/overlays.dm b/code/controllers/subsystem/overlays.dm
index e8ff2c7f96fc..b85f92d07306 100644
--- a/code/controllers/subsystem/overlays.dm
+++ b/code/controllers/subsystem/overlays.dm
@@ -109,11 +109,7 @@ SUBSYSTEM_DEF(overlays)
priority_overlays = null
our_overlays = null
- if (alternate_appearances)
- for(var/I in alternate_appearances)
- var/datum/atom_hud/alternate_appearance/AA = alternate_appearances[I]
- if(AA.transfer_overlays)
- AA.copy_overlays(src, TRUE)
+ SEND_SIGNAL(src, COMSIG_ATOM_COMPILED_OVERLAYS)
atom_flags &= ~ATOM_OVERLAY_QUEUED
diff --git a/code/controllers/subsystem/persist_vr.dm b/code/controllers/subsystem/persist_vr.dm
deleted file mode 100644
index dcb1b738d0bf..000000000000
--- a/code/controllers/subsystem/persist_vr.dm
+++ /dev/null
@@ -1,110 +0,0 @@
-////////////////////////////////
-//// Paid Leave Subsystem
-//// For tracking how much department PTO time players have accured
-////////////////////////////////
-
-SUBSYSTEM_DEF(persist)
- name = "Persist"
- priority = 20
- wait = 15 MINUTES
- subsystem_flags = SS_BACKGROUND|SS_NO_INIT|SS_KEEP_TIMING
- runlevels = RUNLEVEL_GAME|RUNLEVEL_POSTGAME
- var/list/currentrun = list()
-
-/datum/controller/subsystem/persist/fire(var/resumed = FALSE)
- update_department_hours(resumed)
-
-// Do PTO Accruals
-/datum/controller/subsystem/persist/proc/update_department_hours(var/resumed = FALSE)
- if(!config_legacy.time_off)
- return
-
- if(!SSdbcore.Connect())
- src.currentrun.Cut()
- return
- if(!resumed)
- src.currentrun = human_mob_list.Copy()
-
- //cache for sanic speed (lists are references anyways)
- var/list/currentrun = src.currentrun
- while (currentrun.len)
- var/mob/M = currentrun[currentrun.len]
- currentrun.len--
- if (QDELETED(M) || !istype(M) || !M.mind || !M.client || TICKS2DS(M.client.inactivity) > wait)
- continue
-
- // Try and detect job and department of mob
- var/datum/role/job/J = detect_job(M)
- if(!istype(J) || !J.pto_type || !J.timeoff_factor)
- if (MC_TICK_CHECK)
- return
- continue
-
- // Do not collect useless PTO
- var/department_earning = J.pto_type
- clear_unused_pto(M)
-
- // Determine special PTO types and convert properly
- if(department_earning == PTO_CYBORG)
- // CITADEL EDIT: Cyborg PTO disabled for now
- // if(isrobot(M))
- // var/mob/living/silicon/robot/C = M
- // if(C?.module?.pto_type)
- // department_earning = C.module.pto_type
- if(department_earning == PTO_CYBORG)
- if (MC_TICK_CHECK)
- return
- continue
-
- // Update client whatever
- var/client/C = M.client
- var/wait_in_hours = wait / (1 HOUR)
- var/pto_factored = wait_in_hours * J.timeoff_factor
- LAZYINITLIST(C.department_hours)
- var/dept_hours = C.department_hours
- if(isnum(dept_hours[department_earning]))
- dept_hours[department_earning] += pto_factored
- else
- dept_hours[department_earning] = pto_factored
-
- // Cap it
- dept_hours[department_earning] = clamp(dept_hours[department_earning], 0, config_legacy.pto_cap)
-
- // Okay we figured it out, lets update database!
- var/sql_ckey = sql_sanitize_text(C.ckey)
- var/sql_dpt = sql_sanitize_text(department_earning)
- var/sql_bal = text2num("[C.department_hours[department_earning]]")
- SSdbcore.RunQuery(
- "INSERT INTO [format_table_name("vr_player_hours")] (ckey, department, hours) VALUES (:ckey, :dept, :hours) ON DUPLICATE KEY UPDATE hours = VALUES(hours)",
- list(
- "ckey" = sql_ckey,
- "dept" = sql_dpt,
- "hours" = sql_bal
- )
- )
-
- if (MC_TICK_CHECK)
- return
-
-// This proc tries to find the job datum of an arbitrary mob.
-/datum/controller/subsystem/persist/proc/detect_job(var/mob/M)
- // Records are usually the most reliable way to get what job someone is.
- var/datum/data/record/R = find_general_record("name", M.real_name)
- if(R) // We found someone with a record.
- var/recorded_rank = R.fields["real_rank"]
- if(recorded_rank)
- . = SSjob.get_job(recorded_rank)
- if(.) return
-
- // They have a custom title, aren't crew, or someone deleted their record, so we need a fallback method.
- // Let's check the mind.
- if(M.mind && M.mind.assigned_role)
- . = SSjob.get_job(M.mind.assigned_role)
-
-// This proc tries makes sure old Command PTO doesn't linger
-/datum/controller/subsystem/persist/proc/clear_unused_pto(var/mob/M)
- var/client/C = M.client
- LAZYINITLIST(C.department_hours)
- if(C.department_hours[DEPARTMENT_COMMAND])
- C.department_hours[DEPARTMENT_COMMAND] = null
- C.department_hours.Remove(DEPARTMENT_COMMAND)
diff --git a/code/controllers/subsystem/preferences.dm b/code/controllers/subsystem/preferences.dm
index 08bdf079e1cb..cf35c223cf47 100644
--- a/code/controllers/subsystem/preferences.dm
+++ b/code/controllers/subsystem/preferences.dm
@@ -10,6 +10,9 @@ SUBSYSTEM_DEF(preferences)
/datum/controller/subsystem/preferences/Initialize()
init_preference_entries()
init_preference_toggles()
+ for(var/key in preferences_by_key)
+ var/datum/game_preferences/prefs = preferences_by_key[key]
+ prefs.initialize()
return ..()
/datum/controller/subsystem/preferences/proc/resolve_preference_entry(datum/game_preference_entry/entrylike)
@@ -64,8 +67,12 @@ SUBSYSTEM_DEF(preferences)
if(!istype(preferences_by_key[ckey], /datum/game_preferences))
var/datum/game_preferences/initializing = new(key, ckey)
preferences_by_key[ckey] = initializing
- initializing.initialize()
- return preferences_by_key[ckey]
+ if(initialized)
+ initializing.initialize()
+ var/datum/game_preferences/found = preferences_by_key[ckey]
+ if(initialized && !found.initialized)
+ found.initialize()
+ return found
/datum/controller/subsystem/preferences/on_sql_reconnect()
for(var/ckey in SSpreferences.preferences_by_key)
diff --git a/code/controllers/subsystem/research.dm b/code/controllers/subsystem/research.dm
index f6b1b5de2d86..4dda3ba6e3cd 100644
--- a/code/controllers/subsystem/research.dm
+++ b/code/controllers/subsystem/research.dm
@@ -14,6 +14,10 @@ SUBSYSTEM_DEF(research)
/// cached autolathe desgin ids
var/list/autolathe_design_ids
+ //? designs - caches
+ /// cached medical mini autolathe desgin ids
+ var/list/medical_mini_design_ids
+
/datum/controller/subsystem/research/Initialize()
build_designs()
return ..()
@@ -26,6 +30,7 @@ SUBSYSTEM_DEF(research)
/datum/controller/subsystem/research/proc/build_designs()
design_lookup = list()
autolathe_design_ids = list()
+ medical_mini_design_ids = list()
for(var/datum/design/path as anything in subtypesof(/datum/design))
if(initial(path.abstract_type) == path)
continue
@@ -49,7 +54,10 @@ SUBSYSTEM_DEF(research)
. = TRUE
design_lookup[registering.id] = registering
if((registering.lathe_type & LATHE_TYPE_AUTOLATHE) && (registering.design_unlock & DESIGN_UNLOCK_INTRINSIC))
- autolathe_design_ids += registering.id
+ LAZYDISTINCTADD(autolathe_design_ids, registering.id)
+ if(istype(registering, /datum/design/medical))
+ LAZYDISTINCTADD(medical_mini_design_ids, registering.id)
+
/**
* gets a design datum
diff --git a/code/controllers/subsystem/zmimic.dm b/code/controllers/subsystem/zcopy.dm
similarity index 69%
rename from code/controllers/subsystem/zmimic.dm
rename to code/controllers/subsystem/zcopy.dm
index 4eef5327f2d5..e508b36fad3b 100644
--- a/code/controllers/subsystem/zmimic.dm
+++ b/code/controllers/subsystem/zcopy.dm
@@ -1,6 +1,8 @@
-/**
- *! Here be dragons.
- */
+/*
+
+ Here be dragons.
+
+*/
#define OPENTURF_MAX_PLANE -70
/// The maxiumum number of planes deep we'll go before we just dump everything on the same plane.
@@ -11,8 +13,21 @@
#define SHADOWER_DARKENING_COLOR "#999999"
#define READ_BASETURF(T) (islist(T.baseturfs) ? T.baseturfs[length(T.baseturfs)] : T.baseturfs)
-SUBSYSTEM_DEF(zmimic)
- name = "Z-Mimic"
+
+#define ZM_RECORD_STATS
+
+#ifdef ZM_RECORD_STATS
+#define ZM_RECORD_START STAT_START_STOPWATCH
+#define ZM_RECORD_STOP STAT_STOP_STOPWATCH
+#define ZM_RECORD_WRITE(X...) STAT_LOG_ENTRY(##X)
+#else
+#define ZM_RECORD_START
+#define ZM_RECORD_STOP
+#define ZM_RECORD_WRITE(X...)
+#endif
+
+SUBSYSTEM_DEF(zcopy)
+ name = "Z-Copy"
wait = 1
init_order = INIT_ORDER_ZMIMIC
priority = FIRE_PRIORITY_ZMIMIC
@@ -27,8 +42,19 @@ SUBSYSTEM_DEF(zmimic)
var/openspace_turfs = 0
var/multiqueue_skips_turf = 0
+ var/multiqueue_skips_discovery = 0
var/multiqueue_skips_object = 0
+ var/total_updates_turf = 0
+ var/total_updates_discovery = 0
+ var/total_updates_object = 0
+
+#ifdef ZM_RECORD_STATS
+ var/list/turf_stats = list()
+ var/list/discovery_stats = list()
+ var/list/mimic_stats = list()
+#endif
+
// Highest Z level in a given Z-group for absolute layering.
// zstm[zlev] = group_max
var/list/zlev_maximums = list()
@@ -43,10 +69,10 @@ SUBSYSTEM_DEF(zmimic)
var/fixup_hit = 0
// for admin proc-call
-/datum/controller/subsystem/zmimic/proc/update_all()
+/datum/controller/subsystem/zcopy/proc/update_all()
// disable()
can_fire = FALSE
- log_debug("SSzmimic: update_all() invoked.")
+ log_debug("SSzcopy: update_all() invoked.")
var/turf/T // putting the declaration up here totally speeds it up, right?
var/num_upd = 0
@@ -71,7 +97,7 @@ SUBSYSTEM_DEF(zmimic)
CHECK_TICK
- log_debug("SSzmimic: [num_upd + num_amupd] turf updates queued ([num_upd] direct, [num_amupd] indirect), [num_del] orphans destroyed.")
+ log_debug("SSzcopy: [num_upd + num_amupd] turf updates queued ([num_upd] direct, [num_amupd] indirect), [num_del] orphans destroyed.")
// enable()
if (!can_fire)
@@ -79,10 +105,10 @@ SUBSYSTEM_DEF(zmimic)
can_fire = TRUE
// for admin proc-call
-/datum/controller/subsystem/zmimic/proc/hard_reset()
+/datum/controller/subsystem/zcopy/proc/hard_reset()
// disable()
can_fire = FALSE
- log_debug("SSzmimic: hard_reset() invoked.")
+ log_debug("SSzcopy: hard_reset() invoked.")
var/num_deleted = 0
var/num_turfs = 0
@@ -109,28 +135,30 @@ SUBSYSTEM_DEF(zmimic)
CHECK_TICK
- log_debug("SSzmimic: deleted [num_deleted] overlays, and queued [num_turfs] turfs for update.")
+ log_debug("SSzcopy: deleted [num_deleted] overlays, and queued [num_turfs] turfs for update.")
// enable()
if (!can_fire)
next_fire = world.time + wait
can_fire = TRUE
-/datum/controller/subsystem/zmimic/stat_entry()
+/datum/controller/subsystem/zcopy/stat_entry()
var/list/entries = list(
"", // newline
"ZSt: [build_zstack_display()]", // This is a human-readable list of the z-stacks known to ZM.
"ZMx: [zlev_maximums.Join(", ")]", // And this is the raw internal state.
// This one gets broken out from the below because it's more important.
"Q: { T: [queued_turfs.len - (qt_idex - 1)] O: [queued_overlays.len - (qo_idex - 1)] }",
- // In order: Total, Skipped
- "T: { T: [openspace_turfs] O: [openspace_overlays] } Sk: { T: [multiqueue_skips_turf] O: [multiqueue_skips_object] }",
- "F: { H: [fixup_hit] M: [fixup_miss] N: [fixup_noop] FC: [fixup_cache.len] FKG: [fixup_known_good.len] }", // Fixup stats.
+ // In order: Total, Queued, Skipped
+ "T(O): { T: [openspace_turfs] O: [openspace_overlays] }",
+ "T(U): { T: [total_updates_turf] D: [total_updates_discovery] O: [total_updates_object] }",
+ "Sk: { T: [multiqueue_skips_turf] D: [multiqueue_skips_discovery] O: [multiqueue_skips_object] }",
+ "F: { H: [fixup_hit] M: [fixup_miss] N: [fixup_noop] FC: [fixup_cache.len] FKG: [fixup_known_good.len] }"
)
return ..() + entries.Join(" ")
// 1, 2, 3..=7, 8
-/datum/controller/subsystem/zmimic/proc/build_zstack_display()
+/datum/controller/subsystem/zcopy/proc/build_zstack_display()
if (!zlev_maximums.len)
return ""
var/list/zmx = list()
@@ -148,14 +176,14 @@ SUBSYSTEM_DEF(zmimic)
while (idx <= zlev_maximums.len)
return jointext(zmx, ", ")
-/datum/controller/subsystem/zmimic/Initialize(timeofday)
+/datum/controller/subsystem/zcopy/Initialize(timeofday)
calculate_zstack_limits()
// Flush the queue.
fire(FALSE, TRUE)
return ..()
// If you add a new Zlevel or change Z-connections, call this.
-/datum/controller/subsystem/zmimic/proc/calculate_zstack_limits()
+/datum/controller/subsystem/zcopy/proc/calculate_zstack_limits()
zlev_maximums = new(world.maxz)
var/start_zlev = 1
for (var/z in 1 to world.maxz)
@@ -163,12 +191,12 @@ SUBSYSTEM_DEF(zmimic)
for (var/member_zlev in start_zlev to z)
zlev_maximums[member_zlev] = z
if (z - start_zlev > OPENTURF_MAX_DEPTH)
- log_subsystem("zmimic", "WARNING: Z-levels [start_zlev] through [z] exceed maximum depth of [OPENTURF_MAX_DEPTH]; layering may behave strangely in this Z-stack.")
+ log_subsystem("zcopy", "WARNING: Z-levels [start_zlev] through [z] exceed maximum depth of [OPENTURF_MAX_DEPTH]; layering may behave strangely in this Z-stack.")
else if (z - start_zlev > 1)
- log_subsystem("zmimic", "Found Z-Stack: [start_zlev] -> [z] = [z - start_zlev + 1] zl")
+ log_subsystem("zcopy", "Found Z-Stack: [start_zlev] -> [z] = [z - start_zlev + 1] zl")
start_zlev = z + 1
- log_subsystem("zmimic", "Z-Level maximums: [json_encode(zlev_maximums)]")
+ log_subsystem("zcopy", "Z-Level maximums: [json_encode(zlev_maximums)]")
// /datum/controller/subsystem/zmimic/StartLoadingMap()
// suspend()
@@ -177,7 +205,8 @@ SUBSYSTEM_DEF(zmimic)
// wake()
/// Fully reset Z-Mimic, rebuilding state from scratch. Use this if you change Z-stack mappings after Z-Mimic has initialized. Expensive.
-/datum/controller/subsystem/zmimic/proc/RebuildZState()
+/// WARNING: This is *completely unsupported*. It will probably irreversibly corrupt Z-lighting everywhere in the world. Use at own risk.
+/datum/controller/subsystem/zcopy/proc/RebuildZState()
// suspend()
UNTIL(state == SS_IDLE)
@@ -193,7 +222,7 @@ SUBSYSTEM_DEF(zmimic)
CHECK_TICK
// wake()
-/datum/controller/subsystem/zmimic/fire(resumed = FALSE, no_mc_tick = FALSE)
+/datum/controller/subsystem/zcopy/fire(resumed = FALSE, no_mc_tick = FALSE)
if (!resumed)
qt_idex = 1
qo_idex = 1
@@ -202,8 +231,16 @@ SUBSYSTEM_DEF(zmimic)
if (!no_mc_tick)
MC_SPLIT_TICK
+ tick_turfs(no_mc_tick)
+
+ if (!no_mc_tick)
+ MC_SPLIT_TICK
+
+ tick_mimic(no_mc_tick)
+
+// - Turf mimic -
+/datum/controller/subsystem/zcopy/proc/tick_turfs(no_mc_tick)
var/list/curr_turfs = queued_turfs
- var/list/curr_ov = queued_overlays
while (qt_idex <= curr_turfs.len)
var/turf/T = curr_turfs[qt_idex]
@@ -231,18 +268,23 @@ SUBSYSTEM_DEF(zmimic)
// Z-Turf on the bottom-most level, just fake-copy space (or baseturf).
// It's impossible for anything to be on the synthetic turf, so ignore the rest of the ZM machinery.
if (!T.below)
+ ZM_RECORD_START
flush_z_state(T)
- if (T.mz_flags & MZ_MIMIC_BASETURF)
+ if (T.mz_flags & MZ_OVERRIDE)
simple_appearance_copy(T, READ_BASETURF(T), OPENTURF_MAX_PLANE)
else
simple_appearance_copy(T, /turf/space)
T.z_generation += 1
T.z_queued -= 1
+ total_updates_turf += 1
if (T.above)
T.above.update_mimic()
+ ZM_RECORD_STOP
+ ZM_RECORD_WRITE(turf_stats, "Fake: [T.type] on [T.z]")
+
if (no_mc_tick)
CHECK_TICK
else if (MC_TICK_CHECK)
@@ -255,9 +297,12 @@ SUBSYSTEM_DEF(zmimic)
T.z_generation += 1
+ ZM_RECORD_START
+
// Get the bottom-most turf, the one we want to mimic.
+ // Baseturf mimics act as false bottoms of the stack.
var/turf/Td = T
- while (Td.below)
+ while (Td.below && !(Td.mz_flags & (MZ_OVERRIDE|MZ_TERMINATOR)))
Td = Td.below
// Depth must be the depth of the *visible* turf, not self.
@@ -267,37 +312,39 @@ SUBSYSTEM_DEF(zmimic)
var/t_target = OPENTURF_MAX_PLANE - turf_depth // This is where the turf (but not the copied atoms) gets put.
// Turf is set to mimic baseturf, handle that and bail.
- if (T.mz_flags & MZ_MIMIC_BASETURF)
+ if (T.mz_flags & MZ_OVERRIDE)
flush_z_state(T)
- simple_appearance_copy(T, READ_BASETURF(T), t_target)
+ simple_appearance_copy(T, Td.z_appearance || READ_BASETURF(T), t_target)
if (T.above)
T.above.update_mimic()
+ total_updates_turf += 1
T.z_queued -= 1
+ ZM_RECORD_STOP
+ ZM_RECORD_WRITE(turf_stats, "Simple: [T.type] on [T.z]")
+
if (no_mc_tick)
CHECK_TICK
else if (MC_TICK_CHECK)
break
continue
- // If we previously were MZ_MIMIC_BASETURF, there might be an orphaned proxy.
+ // If we previously were MZ_OVERRIDE, there might be an orphaned proxy.
else if (T.mimic_underlay)
QDEL_NULL(T.mimic_underlay)
-
// Handle space parallax & starlight.
if (T.below.z_eventually_space)
T.z_eventually_space = TRUE
- if ((T.below.mz_flags & MZ_MIMIC_OVERWRITE) || T.below.type == /turf/space)
- t_target = SPACE_PLANE
+ t_target = SPACE_PLANE
if (T.mz_flags & MZ_MIMIC_OVERWRITE)
// This openturf doesn't care about its icon, so we can just overwrite it.
if (T.below.mimic_proxy)
QDEL_NULL(T.below.mimic_proxy)
- T.appearance = T.below
+ T.appearance = Td.z_appearance || Td
T.name = initial(T.name)
T.desc = initial(T.desc)
T.gender = initial(T.gender)
@@ -308,7 +355,7 @@ SUBSYSTEM_DEF(zmimic)
if (!T.below.mimic_proxy)
T.below.mimic_proxy = new(T)
var/atom/movable/openspace/turf_proxy/TO = T.below.mimic_proxy
- TO.appearance = Td
+ TO.appearance = Td.z_appearance || Td
TO.name = T.name
TO.gender = T.gender // Need to grab this too so PLURAL works properly in examine.
TO.opacity = FALSE
@@ -333,75 +380,45 @@ SUBSYSTEM_DEF(zmimic)
// Handle below atoms.
- // Add everything below us to the update queue.
+ var/shadower_set = FALSE
+
+ // Add everything below us to the discovery queue.
for (var/thing in T.below)
var/atom/movable/object = thing
if (QDELETED(object) || (object.zmm_flags & ZMM_IGNORE) || object.loc != T.below || object.invisibility == INVISIBILITY_ABSTRACT)
- // Don't queue deleted stuff, stuff that's not visible, blacklisted stuff, or stuff that's centered on another tile but intersects ours.
+ /* Don't queue:
+ - (q)deleted objects
+ - Explicitly ignored objects
+ - Objects not rooted on this turf (multitiles)
+ - Always-invisible atoms
+ */
continue
// Special case: these are merged into the shadower to reduce memory usage.
if (object.type == /atom/movable/lighting_overlay)
- T.shadower.copy_lighting(object)
+ T.shadower.copy_lighting(object, !(T.below.mz_flags & MZ_NO_SHADOW))
continue
- if (!object.bound_overlay) // Generate a new overlay if the atom doesn't already have one.
- object.bound_overlay = new(T)
- object.bound_overlay.associated_atom = object
-
- var/override_depth
- var/original_type = object.type
- var/original_z = object.z
- var/have_performed_fixup = FALSE
-
- switch (object.type)
- // Layering for recursive mimic needs to be inherited.
- if (/atom/movable/openspace/mimic)
- var/atom/movable/openspace/mimic/OOO = object
- original_type = OOO.mimiced_type
- override_depth = OOO.override_depth
- original_z = OOO.original_z
- have_performed_fixup = OOO.have_performed_fixup
-
- // If this is a turf proxy (the mimic for a non-OVERWRITE turf), it needs to respect space parallax if relevant.
- if (/atom/movable/openspace/turf_proxy)
- if (T.z_eventually_space)
- // Yes, this is an awful hack; I don't want to add yet another override_* var.
- override_depth = OPENTURF_MAX_PLANE - SPACE_PLANE
-
- if (/atom/movable/openspace/turf_mimic)
- original_z += 1
-
- var/atom/movable/openspace/mimic/OO = object.bound_overlay
-
- // If the OO was queued for destruction but was claimed by another OT, stop the destruction timer.
- if (OO.destruction_timer)
- deltimer(OO.destruction_timer)
- OO.destruction_timer = null
-
- OO.depth = override_depth || min(zlev_maximums[T.z] - original_z, OPENTURF_MAX_DEPTH)
-
- // These types need to be pushed a layer down for bigturfs to function correctly.
- switch (original_type)
- if (/atom/movable/openspace/multiplier, /atom/movable/openspace/turf_proxy)
- if (OO.depth < OPENTURF_MAX_DEPTH)
- OO.depth += 1
-
- OO.mimiced_type = original_type
- OO.override_depth = override_depth
- OO.original_z = original_z
- OO.have_performed_fixup ||= have_performed_fixup
-
- // Multi-queue to maintain ordering of updates to these
- // queueing it multiple times will result in only the most recent
- // actually processing.
- OO.queued += 1
- queued_overlays += OO
+ // If an atom already has an overlay, we probably don't need to discover it again.
+ // ...but we need to force it if the object was salvaged from another zturf.
+ if (!object.bound_overlay || object.bound_overlay.destruction_timer)
+ discover_movable(object, T)
+
+ if (!shadower_set)
+ if (T.below.mz_flags & MZ_NO_SHADOW)
+ T.shadower.color = null
+ else
+ T.shadower.color = SHADOWER_DARKENING_COLOR
T.z_queued -= 1
if (T.above)
T.above.update_mimic()
+ total_updates_turf += 1
+
+ ZM_RECORD_STOP
+ ZM_RECORD_WRITE(turf_stats, "Complex: [T.type] on [T.z]")
+
if (no_mc_tick)
CHECK_TICK
else if (MC_TICK_CHECK)
@@ -411,9 +428,9 @@ SUBSYSTEM_DEF(zmimic)
curr_turfs.Cut(1, qt_idex)
qt_idex = 1
- if (!no_mc_tick)
- MC_SPLIT_TICK
-
+// - Phase: Mimic update -- actually update the mimics' appearance, order sensitive -
+/datum/controller/subsystem/zcopy/proc/tick_mimic(no_mc_tick)
+ var/list/curr_ov = queued_overlays
while (qo_idex <= curr_ov.len)
var/atom/movable/openspace/mimic/OO = curr_ov[qo_idex]
curr_ov[qo_idex] = null
@@ -426,8 +443,9 @@ SUBSYSTEM_DEF(zmimic)
break
continue
- if (QDELETED(OO.associated_atom)) // This shouldn't happen, but just in-case.
+ if (QDELETED(OO.associated_atom)) // This shouldn't happen.
qdel(OO)
+ log_debug("Z-Mimic: Received mimic with QDELETED parent ([OO.associated_atom || ""])")
if (no_mc_tick)
CHECK_TICK
@@ -446,22 +464,24 @@ SUBSYSTEM_DEF(zmimic)
break
continue
+ ZM_RECORD_START
+
// Actually update the overlay.
if (OO.dir != OO.associated_atom.dir)
- OO.setDir(OO.associated_atom.dir)
+ OO.dir = OO.associated_atom.dir // updates are propagated up another way, don't use set_dir
+ OO.appearance = OO.associated_atom
+ OO.zmm_flags = OO.associated_atom.zmm_flags
if (OO.particles != OO.associated_atom.particles)
OO.particles = OO.associated_atom.particles
- OO.appearance = OO.associated_atom
- OO.zmm_flags = OO.associated_atom.zmm_flags
OO.plane = OPENTURF_MAX_PLANE - OO.depth
OO.opacity = FALSE
OO.queued = 0
// If an atom has explicit plane sets on its overlays/underlays, we need to replace the appearance so they can be mangled to work with our planing.
- if (OO.zmm_flags & (ZMM_MANGLE_PLANES | ZMM_AUTOMANGLE))
+ if (OO.zmm_flags & ZMM_MANGLE_PLANES)
var/new_appearance = fixup_appearance_planes(OO.appearance)
if (new_appearance)
OO.appearance = new_appearance
@@ -470,6 +490,11 @@ SUBSYSTEM_DEF(zmimic)
if (OO.bound_overlay) // If we have a bound overlay, queue it too.
OO.update_above()
+ total_updates_object += 1
+
+ ZM_RECORD_STOP
+ ZM_RECORD_WRITE(mimic_stats, OO.mimiced_type)
+
if (no_mc_tick)
CHECK_TICK
else if (MC_TICK_CHECK)
@@ -479,17 +504,88 @@ SUBSYSTEM_DEF(zmimic)
curr_ov.Cut(1, qo_idex)
qo_idex = 1
-/datum/controller/subsystem/zmimic/proc/flush_z_state(turf/T)
+// return: is-invalid
+/datum/controller/subsystem/zcopy/proc/discover_movable(atom/movable/object)
+ ASSERT(!QDELETED(object))
+
+ var/turf/Tloc = object.loc
+ if (!isturf(Tloc) || !Tloc.above)
+ return TRUE
+
+ var/turf/T = Tloc.above
+
+ ZM_RECORD_START
+
+ if (!object.bound_overlay)
+ var/atom/movable/openspace/mimic/M = new(T)
+ object.bound_overlay = M
+ M.associated_atom = object
+ if (TURF_IS_MIMICKING(M.loc))
+ .(M)
+
+ var/override_depth
+ var/original_type = object.type
+ var/original_z = object.z
+
+ switch (object.type)
+ // Layering for recursive mimic needs to be inherited.
+ if (/atom/movable/openspace/mimic)
+ var/atom/movable/openspace/mimic/OOO = object
+ original_type = OOO.mimiced_type
+ override_depth = OOO.override_depth
+ original_z = OOO.original_z
+
+ // If this is a turf proxy (the mimic for a non-OVERWRITE turf), it needs to respect space parallax if relevant.
+ if (/atom/movable/openspace/turf_proxy)
+ if (T.z_eventually_space)
+ // Yes, this is an awful hack; I don't want to add yet another override_* var.
+ override_depth = OPENTURF_MAX_PLANE - SPACE_PLANE
+
+ var/atom/movable/openspace/mimic/OO = object.bound_overlay
+
+ // If the OO was queued for destruction but was claimed by another OT, stop the destruction timer.
+ if (OO.destruction_timer)
+ deltimer(OO.destruction_timer)
+ OO.destruction_timer = null
+
+ OO.depth = override_depth || min(zlev_maximums[T.z] - original_z, OPENTURF_MAX_DEPTH)
+
+ switch (original_type)
+ // These types need to be pushed a layer down for bigturfs to function correctly.
+ if (/atom/movable/openspace/turf_proxy, /atom/movable/openspace/turf_mimic)
+ OO.depth += 1
+ if (/atom/movable/openspace/multiplier)
+ OO.depth += 1
+
+ OO.mimiced_type = original_type
+ OO.override_depth = override_depth
+ OO.original_z = original_z
+
+ // Multi-queue to maintain ordering of updates to these
+ // queueing it multiple times will result in only the most recent
+ // actually processing.
+ OO.queued += 1
+ queued_overlays += OO
+
+ total_updates_discovery += 1
+
+ ZM_RECORD_STOP
+ ZM_RECORD_WRITE(discovery_stats, "Depth [OO.depth] on [OO.z]")
+
+ return FALSE
+
+/datum/controller/subsystem/zcopy/proc/flush_z_state(turf/T)
if (T.below) // Z-Mimic turfs aren't necessarily above another turf.
if (T.below.mimic_above_copy)
QDEL_NULL(T.below.mimic_above_copy)
if (T.below.mimic_proxy)
QDEL_NULL(T.below.mimic_proxy)
+
QDEL_NULL(T.mimic_underlay)
for (var/atom/movable/openspace/mimic/OO in T)
qdel(OO)
-/datum/controller/subsystem/zmimic/proc/simple_appearance_copy(turf/T, new_appearance, target_plane)
+/datum/controller/subsystem/zcopy/proc/simple_appearance_copy(turf/T, new_appearance, target_plane)
if (T.mz_flags & MZ_MIMIC_OVERWRITE)
T.appearance = new_appearance
T.name = initial(T.name)
@@ -504,7 +600,7 @@ SUBSYSTEM_DEF(zmimic)
var/atom/movable/openspace/turf_proxy/TO = T.mimic_underlay
TO.appearance = new_appearance
TO.name = T.name
- TO.gender = T.gender // Need to grab this too so PLURAL works properly in examine.
+ TO.gender = T.gender // Need to grab this too so PLURAL works properly in examine.
TO.mouse_opacity = initial(TO.mouse_opacity)
if (TO.plane == 0 && target_plane)
TO.plane = target_plane
@@ -513,7 +609,7 @@ SUBSYSTEM_DEF(zmimic)
// For each of overlay,underlay, call fixup_appearance_planes; if it returns a new appearance, replace self
/// Generate a new appearance from `appearance` with planes mangled to work with Z-Mimic. Do not pass a depth.
-/datum/controller/subsystem/zmimic/proc/fixup_appearance_planes(appearance, depth = 0)
+/datum/controller/subsystem/zcopy/proc/fixup_appearance_planes(appearance, depth = 0)
// Adding this to guard against a reported runtime - supposed to be impossible, so cause is unclear.
if(!appearance)
@@ -601,7 +697,7 @@ SUBSYSTEM_DEF(zmimic)
fixed_underlays[i] = fixed_appearance
if (mutated)
- for (var/i in 1 to fixed_overlays.len)
+ for (var/i in 1 to fixed_underlays.len)
if (fixed_underlays[i] == null)
fixed_underlays[i] = appearance:underlays[i]
@@ -633,6 +729,7 @@ SUBSYSTEM_DEF(zmimic)
return MA
#define FMT_DEPTH(X) (X == null ? "(null)" : X)
+#define FMT_OK(X) (X) ? "OK" : "MISMATCH"
// This is a dummy object used so overlays can be shown in the analyzer.
/atom/movable/openspace/debug
@@ -641,6 +738,7 @@ SUBSYSTEM_DEF(zmimic)
var/turf/parent
var/computed_depth
+// this is for debug display only -- fixed as in unchanging, not mangling
var/list/zmimic_fixed_planes = list(
"0" = "World plane (Non-Z)",
"-15" = "Cloaked plane (Non-Z)",
@@ -659,7 +757,7 @@ var/list/zmimic_fixed_planes = list(
var/real_update_count = 0
var/claimed_update_count = T.z_queued
- var/list/tq = SSzmimic.queued_turfs.Copy()
+ var/list/tq = SSzcopy.queued_turfs.Copy()
for (var/turf/Tu in tq)
if (Tu == T)
real_update_count += 1
@@ -673,7 +771,7 @@ var/list/zmimic_fixed_planes = list(
"",
"
[fmt_label("Mimic", A)] plane [A.plane], layer [A.layer], depth [FMT_DEPTH(OO.depth)]"
if (QDELETED(OO.associated_atom)) // This shouldn't happen, but can if the deletion hook is not working.
return "[base] - [OO.type] copying ([OO.mimiced_type]) - ORPHANED
"
@@ -793,7 +891,7 @@ var/list/zmimic_fixed_planes = list(
else if (A.type == /atom/movable/openspace/debug/turf)
var/atom/movable/openspace/debug/turf/VTO = A
- return "
"
for (var/thing in things)
@@ -816,4 +914,49 @@ var/list/zmimic_fixed_planes = list(
out += "No atoms."
#undef FMT_DEPTH
-#undef READ_BASETURF
+#undef FMT_OK
+#undef ZM_RECORD_START
+#undef ZM_RECORD_STOP
+#undef ZM_RECORD_WRITE
+
+#ifdef ZM_RECORD_STATS
+/client/proc/zms_display_turf()
+ set name = "ZM Stats - 1Turf"
+ set category = "Debug"
+
+ if(!check_rights(R_DEBUG))
+ return
+
+ if (!length(SSzcopy.turf_stats))
+ alert("No stats.")
+ return
+
+ render_stats(SSzcopy.turf_stats, src)
+
+/client/proc/zms_display_discovery()
+ set name = "ZM Stats - 2Discovery"
+ set category = "Debug"
+
+ if(!check_rights(R_DEBUG))
+ return
+
+ if (!length(SSzcopy.discovery_stats))
+ alert("No stats.")
+ return
+
+ render_stats(SSzcopy.discovery_stats, src)
+
+/client/proc/zms_display_mimic()
+ set name = "ZM Stats - 3Mimic"
+ set category = "Debug"
+
+ if(!check_rights(R_DEBUG))
+ return
+
+ if (!length(SSzcopy.mimic_stats))
+ alert("No stats.")
+ return
+
+ render_stats(SSzcopy.mimic_stats, src)
+
+#endif
diff --git a/code/datums/browser/_browser.dm b/code/datums/browser/_browser.dm
index 634e2f572cb2..e06f9902c92a 100644
--- a/code/datums/browser/_browser.dm
+++ b/code/datums/browser/_browser.dm
@@ -12,8 +12,7 @@
var/body_elements
var/head_content = ""
var/content = ""
- var/static/datum/asset/simple/namespaced/common/common_asset = get_asset_datum(/datum/asset/simple/namespaced/common)
-
+ var/datum/asset_pack/simple/common/common_asset
/datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, atom/nref = null)
@@ -28,6 +27,8 @@
if (nref)
ref = nref
+ common_asset = SSassets.ready_asset_pack(/datum/asset_pack/simple/common)
+
/datum/browser/proc/add_head_content(nhead_content)
head_content = nhead_content
@@ -38,20 +39,14 @@
//title_image = ntitle_image
/datum/browser/proc/add_stylesheet(name, file)
- if(istype(name, /datum/asset/spritesheet))
- var/datum/asset/spritesheet/sheet = name
- stylesheets["spritesheet_[sheet.name].css"] = "data/spritesheets/[sheet.name]"
- else
- var/asset_name = "[name].css"
-
- stylesheets[asset_name] = file
-
- if (!SSassets.cache[asset_name])
- SSassets.transport.register_asset(asset_name, file)
+ name = "[sanitize_filename(name)].css"
+ stylesheets |= name
+ SSassets.register_dynamic_item_by_name(file, name)
/datum/browser/proc/add_script(name, file)
- scripts["[ckey(name)].js"] = file
- SSassets.transport.register_asset("[ckey(name)].js", file)
+ name = "[sanitize_filename(name)].js"
+ scripts |= "[name]"
+ SSassets.register_dynamic_item_by_name("[name]", name)
/datum/browser/proc/set_content(ncontent)
content = ncontent
@@ -61,13 +56,13 @@
/datum/browser/proc/get_header()
var/file
- head_content += ""
+ head_content += ""
for (file in stylesheets)
- head_content += ""
+ head_content += ""
for (file in scripts)
- head_content += ""
+ head_content += ""
return {"
@@ -105,11 +100,9 @@
var/window_size = ""
if(width && height)
window_size = "size=[width]x[height];"
- common_asset.send(user)
- if(stylesheets.len)
- SSassets.transport.send_assets(user, stylesheets)
- if(scripts.len)
- SSassets.transport.send_assets(user, scripts)
+ SSassets.send_asset_pack(user, common_asset)
+ SSassets.send_dynamic_item_by_name(user, stylesheets)
+ SSassets.send_dynamic_item_by_name(user, scripts)
user << browse(get_content(), "window=[window_id];[window_size][window_options]")
if(use_onclose)
setup_onclose()
diff --git a/code/datums/changelog.dm b/code/datums/changelog.dm
index 910c72186f2d..b2929b6368ba 100644
--- a/code/datums/changelog.dm
+++ b/code/datums/changelog.dm
@@ -15,11 +15,12 @@
if(.)
return
if(action == "get_month")
- var/datum/asset/changelog_item/changelog_item = changelog_items[params["date"]]
+ var/datum/asset_pack/changelog_item/changelog_item = changelog_items[params["date"]]
if (!changelog_item)
- changelog_item = new /datum/asset/changelog_item(params["date"])
+ changelog_item = new /datum/asset_pack/changelog_item(params["date"])
changelog_items[params["date"]] = changelog_item
- return ui.send_asset(changelog_item)
+ ui.send_asset(changelog_item)
+ return TRUE
/datum/changelog/ui_static_data(mob/user, datum/tgui/ui)
var/list/data = list( "dates" = list() )
diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm
index d46b995f7577..77b96d1f00bc 100644
--- a/code/datums/components/_component.dm
+++ b/code/datums/components/_component.dm
@@ -208,6 +208,19 @@
for (var/signal_type in signal_types)
RegisterSignal(target, signal_type, proctype, override)
+/**
+ * RegisterSignal on SSdcs to listen to global signals.
+ */
+/datum/proc/RegisterGlobalSignal(signal_type, proctype, override = FALSE)
+ RegisterSignal(SSdcs, signal_type, proctype, override)
+
+/**
+ * RegisterSignal on SSdcs to listen to global signals.
+ */
+/datum/proc/RegisterGlobalSignals(list/signal_types, proctype, override = FALSE)
+ for (var/signal_type in signal_types)
+ RegisterGlobalSignal(SSdcs, signal_type, proctype, override)
+
/**
* Stop listening to a given signal from target
*
@@ -254,6 +267,12 @@
if(!signal_procs[target].len)
signal_procs -= target
+/**
+ * RegisterSignal on SSdcs to listen to global signals.
+ */
+/datum/proc/UnregisterGlobalSignal(sig_type_or_types)
+ UnregisterSignal(SSdcs, sig_type_or_types)
+
/**
* Called on a component when a component of the same type was added to the same parent
*
diff --git a/code/datums/design/designs/medical/chemistry.dm b/code/datums/design/designs/medical/chemistry.dm
index 29b9abe6440f..9a95e95a553b 100644
--- a/code/datums/design/designs/medical/chemistry.dm
+++ b/code/datums/design/designs/medical/chemistry.dm
@@ -31,18 +31,8 @@
design_unlock = DESIGN_UNLOCK_INTRINSIC
build_path = /obj/item/reagent_containers/glass/hypovial/large
-/datum/design/medical/chemistry/syringe
- id = "ChemistrySyringe"
+/datum/design/medical/pillbottle
+ id = "ChemistryPillbottle"
lathe_type = LATHE_TYPE_AUTOLATHE
design_unlock = DESIGN_UNLOCK_INTRINSIC
- build_path = /obj/item/reagent_containers/syringe
-
-/datum/design/medical/chemistry/autoinjector
- id = "ChemistryAutoinjector"
- lathe_type = LATHE_TYPE_AUTOLATHE
- design_unlock = DESIGN_UNLOCK_INTRINSIC
- build_path = /obj/item/reagent_containers/hypospray/autoinjector/empty
- materials_base = list(
- MAT_STEEL = 250,
- MAT_GLASS = 250,
- )
+ build_path = /obj/item/storage/pill_bottle
diff --git a/code/datums/design/designs/medical/consumables.dm b/code/datums/design/designs/medical/consumables.dm
new file mode 100644
index 000000000000..32a565efcc2e
--- /dev/null
+++ b/code/datums/design/designs/medical/consumables.dm
@@ -0,0 +1,11 @@
+/datum/design/medical/chemistry/syringe
+ id = "MedicalSyringe"
+ lathe_type = LATHE_TYPE_AUTOLATHE
+ design_unlock = DESIGN_UNLOCK_INTRINSIC
+ build_path = /obj/item/reagent_containers/syringe
+
+/datum/design/medical/chemistry/autoinjector
+ id = "MedicalAutoinjector"
+ lathe_type = LATHE_TYPE_AUTOLATHE
+ design_unlock = DESIGN_UNLOCK_INTRINSIC
+ build_path = /obj/item/reagent_containers/hypospray/autoinjector/empty
diff --git a/code/datums/design/designs/medical/sterile_wear.dm b/code/datums/design/designs/medical/sterile_wear.dm
new file mode 100644
index 000000000000..7b608f722f01
--- /dev/null
+++ b/code/datums/design/designs/medical/sterile_wear.dm
@@ -0,0 +1,17 @@
+/datum/design/medical/latexgloves
+ id = "MedicalLatexGloves"
+ lathe_type = LATHE_TYPE_AUTOLATHE
+ design_unlock = DESIGN_UNLOCK_INTRINSIC
+ build_path = /obj/item/clothing/gloves/sterile/latex
+
+/datum/design/medical/nitrilgloves
+ id = "MedicalNitrilGloves"
+ lathe_type = LATHE_TYPE_AUTOLATHE
+ design_unlock = DESIGN_UNLOCK_INTRINSIC
+ build_path = /obj/item/clothing/gloves/sterile/nitrile
+
+/datum/design/medical/facemask
+ id = "MedicalFaceMask"
+ lathe_type = LATHE_TYPE_AUTOLATHE
+ design_unlock = DESIGN_UNLOCK_INTRINSIC
+ build_path = /obj/item/clothing/mask/surgical
diff --git a/code/datums/elements/items/hud_granter.dm b/code/datums/elements/items/hud_granter.dm
index 3782bc758600..fd2c0c9d66e1 100644
--- a/code/datums/elements/items/hud_granter.dm
+++ b/code/datums/elements/items/hud_granter.dm
@@ -28,12 +28,10 @@
if(!(slot in slots))
return
for(var/hud in huds)
- var/datum/atom_hud/H = GLOB.huds[hud]
- H.add_hud_to(M)
+ M.self_perspective.add_atom_hud(hud, ATOM_HUD_SOURCE_FOR_HUD_GRANTER_ON_EQUIPMENT_SLOT(slot))
/datum/element/hud_granter/proc/on_unequip(datum/source, mob/M, slot)
if(!(slot in slots))
return
for(var/hud in huds)
- var/datum/atom_hud/H = GLOB.huds[hud]
- H.remove_hud_from(M)
+ M.self_perspective.remove_atom_hud(hud, ATOM_HUD_SOURCE_FOR_HUD_GRANTER_ON_EQUIPMENT_SLOT(slot))
diff --git a/code/datums/event_args/actor.dm b/code/datums/event_args/actor.dm
index c5186ef2ed96..38f5117d96be 100644
--- a/code/datums/event_args/actor.dm
+++ b/code/datums/event_args/actor.dm
@@ -11,6 +11,11 @@
src.performer = performer
src.initiator = isnull(initiator)? performer : initiator
+/datum/event_args/actor/clone(include_contents)
+ return new /datum/event_args/actor(performer, initiator)
+
+// todo: reowrk these awful ass feedback/message procs wtf
+
/datum/event_args/actor/proc/chat_feedback(msg, atom/target)
performer.action_feedback(msg, target)
if(performer != initiator)
diff --git a/code/datums/event_args/clickchain.dm b/code/datums/event_args/clickchain.dm
index 4f350e9cae3c..93510a6352a1 100644
--- a/code/datums/event_args/clickchain.dm
+++ b/code/datums/event_args/clickchain.dm
@@ -1,12 +1,14 @@
/**
- * used to hold data about a click action
+ * used to hold data about a click (melee/ranged/other) action
+ *
+ * the click may be real or fake.
*/
/datum/event_args/actor/clickchain
- /// a_intent
+ /// optional: a_intent
var/intent
- /// click params
+ /// optional: click params
var/list/params
- /// target atom
+ /// optional: target atom
var/atom/target
/datum/event_args/actor/clickchain/New(mob/performer, mob/initiator, atom/target, intent, list/params)
@@ -14,3 +16,6 @@
src.target = target
src.intent = isnull(intent)? performer.a_intent : intent
src.params = isnull(params)? list() : params
+
+/datum/event_args/actor/clickchain/clone()
+ return new /datum/event_args/actor/clickchain(performer, initiator, target, intent, params)
diff --git a/code/datums/outfits/horror_killers.dm b/code/datums/outfits/horror_killers.dm
index 54f0fb68ace1..d28dfa48ff33 100644
--- a/code/datums/outfits/horror_killers.dm
+++ b/code/datums/outfits/horror_killers.dm
@@ -59,7 +59,7 @@
sec_briefcase.contents += new /obj/item/gun/ballistic/revolver/mateba
sec_briefcase.contents += new /obj/item/ammo_magazine/s357
sec_briefcase.contents += new /obj/item/plastique
- H.equip_to_slot_or_del(sec_briefcase, /datum/inventory_slot_meta/abstract/hand/left)
+ H.equip_to_slot_or_del(sec_briefcase, /datum/inventory_slot/abstract/hand/left)
/datum/outfit/samurai
name = "Vengeful Samurai"
diff --git a/code/datums/outfits/outfit.dm b/code/datums/outfits/outfit.dm
index 616f8af012c8..8b8ab2b73edb 100644
--- a/code/datums/outfits/outfit.dm
+++ b/code/datums/outfits/outfit.dm
@@ -32,7 +32,7 @@
var/r_hand = null
var/l_hand = null
// In the list(path=count,otherpath=count) format
- var/list/uniform_accessories = list() // webbing, armbands etc - fits in /datum/inventory_slot_meta/abstract/attach_as_accessory
+ var/list/uniform_accessories = list() // webbing, armbands etc - fits in /datum/inventory_slot/abstract/attach_as_accessory
var/list/backpack_contents = list()
var/id_type
@@ -88,7 +88,7 @@
for(var/path in backpack_contents)
var/number = backpack_contents[path]
for(var/i=0,i:t")
var/obj/item/encryptionkey/syndicate/encrypt_key = new(null)
- traitor_mob.equip_to_slot_or_del(encrypt_key, /datum/inventory_slot_meta/abstract/put_in_backpack)
+ traitor_mob.equip_to_slot_or_del(encrypt_key, /datum/inventory_slot/abstract/put_in_backpack)
else
var/obj/item/encryptionkey/syndicate/encrypt_key = new(null)
if(R.keyslot1 && R.keyslot2) // No room.
to_chat(traitor_mob, "Unfortunately, your headset cannot accept anymore encryption keys. You have been given an encryption key \
to put into a headset after making some room instead. Once that is done, you can talk to your team using :t")
- traitor_mob.equip_to_slot_or_del(encrypt_key, /datum/inventory_slot_meta/abstract/put_in_backpack)
+ traitor_mob.equip_to_slot_or_del(encrypt_key, /datum/inventory_slot/abstract/put_in_backpack)
else
if(R.keyslot1)
R.keyslot2 = encrypt_key
diff --git a/code/game/antagonist/station/renegade.dm b/code/game/antagonist/station/renegade.dm
index aae1115347e0..de6b319b2a62 100644
--- a/code/game/antagonist/station/renegade.dm
+++ b/code/game/antagonist/station/renegade.dm
@@ -87,7 +87,7 @@ var/datum/antagonist/renegade/renegades
var/obj/item/gun = new gun_type(player)
// Attempt to put into a container.
- if(player.equip_to_slot_if_possible(gun, /datum/inventory_slot_meta/abstract/put_in_storage, INV_OP_FLUFFLESS | INV_OP_SILENT))
+ if(player.equip_to_slot_if_possible(gun, /datum/inventory_slot/abstract/put_in_storage, INV_OP_FLUFFLESS | INV_OP_SILENT))
return
// If that failed, attempt to put into any valid non-handslot
diff --git a/code/game/area/Ship_Station_Areas.dm b/code/game/area/Ship_Station_Areas.dm
index 5a428eeeffa4..24fdc7ec3c14 100644
--- a/code/game/area/Ship_Station_Areas.dm
+++ b/code/game/area/Ship_Station_Areas.dm
@@ -200,10 +200,8 @@
/area/main_map/maintenance
area_flags = AREA_RAD_SHIELDED
sound_env = TUNNEL_ENCLOSED
- turf_initializer = new /datum/turf_initializer/maintenance()
ambience = AMBIENCE_MAINTENANCE
-
/area/main_map/maintenance/engineering
name = "Engineering Maintenance"
icon_state = "maint_engineering"
diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm
index 1571fb01b9e9..033a449212db 100644
--- a/code/game/area/Space Station 13 areas.dm
+++ b/code/game/area/Space Station 13 areas.dm
@@ -763,7 +763,6 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/maintenance
area_flags = AREA_RAD_SHIELDED
sound_env = TUNNEL_ENCLOSED
- turf_initializer = new /datum/turf_initializer/maintenance()
ambience = AMBIENCE_MAINTENANCE
/area/maintenance/aft
@@ -2667,7 +2666,6 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
name = "\improper Research Outpost Maintenance"
area_flags = AREA_RAD_SHIELDED
sound_env = TUNNEL_ENCLOSED
- turf_initializer = new /datum/turf_initializer/maintenance()
ambience = AMBIENCE_MAINTENANCE
/area/rnd/outpost/underground
diff --git a/code/game/area/area.dm b/code/game/area/area.dm
index 675cf7864d9c..5647b81db13b 100644
--- a/code/game/area/area.dm
+++ b/code/game/area/area.dm
@@ -363,8 +363,9 @@
if(!all_arfgs)
return
for(var/obj/machinery/atmospheric_field_generator/E in all_arfgs)
- E.disable_field()
- E.wasactive = FALSE
+ if(!E.alwaysactive)
+ E.disable_field()
+ E.wasactive = FALSE
/area/proc/fire_alert()
diff --git a/code/game/area/rift_areas.dm b/code/game/area/rift_areas.dm
index 5d2e26a5e763..df539b26a18e 100644
--- a/code/game/area/rift_areas.dm
+++ b/code/game/area/rift_areas.dm
@@ -434,7 +434,6 @@
name = "\improper Outpost Maintenance"
area_flags = AREA_RAD_SHIELDED
sound_env = TUNNEL_ENCLOSED
- turf_initializer = new /datum/turf_initializer/maintenance()
ambience = AMBIENCE_MAINTENANCE
/area/outpost/mining_main/outpost/maintenance/south
diff --git a/code/game/area/station/shuttle_area.dm b/code/game/area/station/shuttle_area.dm
new file mode 100644
index 000000000000..9b5e194e530c
--- /dev/null
+++ b/code/game/area/station/shuttle_area.dm
@@ -0,0 +1,4 @@
+///Civilian Century Shuttle
+/area/shuttle/oldcentury
+ requires_power = 1
+ icon_state = "shuttle2"
diff --git a/code/game/atoms/appearance.dm b/code/game/atoms/appearance.dm
index 2713e8e2537a..4078f0f1a768 100644
--- a/code/game/atoms/appearance.dm
+++ b/code/game/atoms/appearance.dm
@@ -1,3 +1,5 @@
+// todo: rethink everything about how to do signals for these.
+
/**
* Updates the appearence of the icon
*
diff --git a/code/game/atoms/atom.dm b/code/game/atoms/atom.dm
index 8b2ce9012b33..c25551238662 100644
--- a/code/game/atoms/atom.dm
+++ b/code/game/atoms/atom.dm
@@ -86,11 +86,9 @@
/// flags for resistances
var/integrity_flags = NONE
- //? HUDs
- /// This atom's HUD (med/sec, etc) images. Associative list.
- var/list/image/hud_list = null
- /// HUD images that this atom can provide.
- var/list/hud_possible
+ //* HUDs (Atom)
+ /// atom hud typepath to image
+ var/list/image/atom_huds
//? Icon Smoothing
/// Icon-smoothing behavior.
@@ -239,7 +237,7 @@
world.preloader_load(src)
if(datum_flags & DF_USE_TAG)
- GenerateTag()
+ generate_tag()
var/do_initialize = SSatoms.initialized
if(do_initialize != INITIALIZATION_INSSATOMS)
@@ -336,10 +334,8 @@
* * clears the light object
*/
/atom/Destroy(force)
- if(alternate_appearances)
- for(var/current_alternate_appearance in alternate_appearances)
- var/datum/atom_hud/alternate_appearance/selected_alternate_appearance = alternate_appearances[current_alternate_appearance]
- selected_alternate_appearance.remove_from_hud(src)
+ for(var/hud_provider in atom_huds)
+ remove_atom_hud_provider(src, hud_provider)
if(reagents)
QDEL_NULL(reagents)
@@ -851,7 +847,23 @@
/atom/proc/get_nametag_desc(mob/user)
return "" //Desc itself is often too long to use
-/atom/proc/GenerateTag()
+/**
+ * generates our locate() tag
+ *
+ * why would we use tags?
+ * i'm glad you asked!
+ *
+ * some atoms / datums have special needs of 'too critical to allow shared text refs to wreak havoc'
+ * yes, usually, people need to be gc-aware and not allow text ref reuse to break things
+ * unfortunately this is still going to be an issue for legacy code
+ *
+ * so we don't allow things like /mobs to ever share the same reference used for REF(),
+ * because the chances of a collision is just too high
+ *
+ * not only that, this is currently the way things like mobs can generate things like their render source/target UIDs
+ * in the future we'll need to change that to a better UID system for each system, but, for now, this is why.
+ */
+/atom/proc/generate_tag()
return
/**
diff --git a/code/game/atoms/movable/movement.dm b/code/game/atoms/movable/movement.dm
index f12fb2c6a239..ae07c7c6068b 100644
--- a/code/game/atoms/movable/movement.dm
+++ b/code/game/atoms/movable/movement.dm
@@ -311,6 +311,31 @@
move_speed = world.time - l_move_time
l_move_time = world.time
+// Hooks for foreign code.
+/atom/movable/Move(...)
+ var/old_loc = loc
+ . = ..()
+ if (!.)
+ return
+
+ if (light_source_solo)
+ light_source_solo.source_atom.update_light()
+ else if (light_source_multi)
+ var/datum/light_source/L
+ var/thing
+ for (thing in light_source_multi)
+ L = thing
+ L.source_atom.update_light()
+
+ // Z-Mimic.
+ if (bound_overlay)
+ // The overlay will handle cleaning itself up on non-openspace turfs.
+ bound_overlay.forceMove(get_step(src, UP))
+ if (bound_overlay && bound_overlay.dir != dir)
+ bound_overlay.setDir(dir)
+ else if (isturf(loc) && (!old_loc || !TURF_IS_MIMICKING(old_loc)) && MOVABLE_SHALL_MIMIC(src))
+ SSzcopy.discover_movable(src)
+
//! WARNING WARNING THIS IS SHITCODE
/atom/movable/proc/handle_buckled_mob_movement(newloc, direct, glide_size_override, forcemoving)
for(var/mob/M as anything in buckled_mobs)
diff --git a/code/game/dna/dna2.dm b/code/game/dna/dna2.dm
index a141b25c5ea1..72c3d1f508ff 100644
--- a/code/game/dna/dna2.dm
+++ b/code/game/dna/dna2.dm
@@ -93,8 +93,8 @@ var/global/list/datum/gene/dna_genes[0]
#define GENE_ALWAYS_ACTIVATE 1
/datum/dna
-//! READ-ONLY, GETS OVERWRITTEN
-//! DO NOT FUCK WITH THESE OR BYOND WILL EAT YOUR FACE
+ // READ-ONLY, GETS OVERWRITTEN
+ // DO NOT FUCK WITH THESE OR BYOND WILL EAT YOUR FACE
/// Encoded UI.
var/uni_identity = ""
@@ -107,8 +107,8 @@ var/global/list/datum/gene/dna_genes[0]
var/dirtyUI = 0
var/dirtySE = 0
-//! Okay to read, but you're an idiot if you do.
-//! BLOCK = VALUE
+ // Okay to read, but you're an idiot if you do.
+ // BLOCK = VALUE
var/list/SE[DNA_SE_LENGTH]
var/list/UI[DNA_UI_LENGTH]
@@ -298,7 +298,7 @@ var/global/list/datum/gene/dna_genes[0]
SetUIValueRange(DNA_UI_BEARD_STYLE, beard, GLOB.legacy_facial_hair_lookup.len,1)
body_markings.Cut()
-
+
for(var/obj/item/organ/external/E in character.organs)
E.s_base = s_base
if(E.markings.len)
diff --git a/code/game/machinery/_frame.dm b/code/game/machinery/_frame.dm
index 171763dd9dd9..248dd4a0b924 100644
--- a/code/game/machinery/_frame.dm
+++ b/code/game/machinery/_frame.dm
@@ -1,6 +1,8 @@
/var/global/list/construction_frame_wall
/var/global/list/construction_frame_floor
+
+
// TODO: MAKE FRAMES NOT AWFUL
// WHY IN THE NAME OF THE SEVEN HELLS ARE THEY NOT STATIC DATUMS AT THIS POINT?!
// WHY ARE VARIABLES HARD TYPECHECKED?
@@ -384,10 +386,7 @@
circuit.after_construct(new_machine)
for(var/obj/O in components)
- if(circuit.contain_parts)
- O.loc = new_machine
- else
- O.loc = null
+ O.loc = new_machine
new_machine.component_parts += O
circuit.loc = null
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index bf90d6cc3e5a..2ad91d1ff304 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -161,6 +161,7 @@
var/interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_SET_MACHINE
+// todo: from_frame? arg for frame to pass in context..
/obj/machinery/Initialize(mapload, newdir)
if(newdir)
setDir(newdir)
diff --git a/code/game/machinery/biogenerator.dm b/code/game/machinery/biogenerator.dm
index 6e870364fb62..71e82af85ab0 100644
--- a/code/game/machinery/biogenerator.dm
+++ b/code/game/machinery/biogenerator.dm
@@ -126,6 +126,7 @@
beaker = inserted_beaker
update_appearance()
+ SStgui.update_uis(src)
/*
* Eject the current stored beaker either into the user's hands or onto the ground.
@@ -263,13 +264,7 @@
if(default_unfasten_wrench(user, O, 40))
return
if(istype(O, /obj/item/reagent_containers/glass))
- if(beaker)
- to_chat(user, SPAN_NOTICE("\The [src] is already loaded."))
- else
- if(!user.attempt_insert_item_for_installation(O, src))
- return
- beaker = FALSE
- SStgui.update_uis(src)
+ insert_beaker(user, O)
else if(processing)
to_chat(user, SPAN_NOTICE("\The [src] is currently processing."))
else if(istype(O, /obj/item/storage/bag))
@@ -280,7 +275,8 @@
to_chat(user, SPAN_NOTICE("\The [src] is already full! Activate it."))
else
for(var/obj/item/reagent_containers/food/snacks/grown/G in O.contents)
- G.loc = src
+ O.obj_storage.remove(G)
+ G.forceMove(src)
i++
if(i >= 10)
to_chat(user, SPAN_NOTICE("You fill \the [src] to its capacity."))
diff --git a/code/game/machinery/computer/arcade/orion.dm b/code/game/machinery/computer/arcade/orion.dm
index 395a339eeec5..c1c365ded1b6 100644
--- a/code/game/machinery/computer/arcade/orion.dm
+++ b/code/game/machinery/computer/arcade/orion.dm
@@ -151,10 +151,9 @@ GLOBAL_LIST_INIT(orion_events, generate_orion_events())
ui = new(user, src, "OrionGame", name)
ui.open()
-/obj/machinery/computer/arcade/orion_trail/ui_assets(mob/user)
- return list(
- get_asset_datum(/datum/asset/spritesheet/moods),
- )
+/obj/machinery/computer/arcade/orion_trail/ui_asset_injection(datum/tgui/ui, list/immediate, list/deferred)
+ immediate += /datum/asset_pack/spritesheet/moods
+ return ..()
/obj/machinery/computer/arcade/orion_trail/ui_data(mob/user, datum/tgui/ui)
var/list/data = list()
diff --git a/code/game/machinery/computer/timeclock_vr.dm b/code/game/machinery/computer/timeclock_vr.dm
index a9aa2308ff38..79bc9bbfb951 100644
--- a/code/game/machinery/computer/timeclock_vr.dm
+++ b/code/game/machinery/computer/timeclock_vr.dm
@@ -81,8 +81,6 @@
var/list/data = ..()
// Okay, data for showing the user's OWN PTO stuff
- if(user.client)
- data["department_hours"] = SANITIZE_LIST(user.client.department_hours)
data["user_name"] = "[user]"
// Data about the card that we put into it.
@@ -101,12 +99,12 @@
"departments" = english_list(job.departments),
"selection_color" = job.selection_color,
"economic_modifier" = job.get_economic_payscale(),
- "timeoff_factor" = job.timeoff_factor,
- "pto_department" = job.pto_type
+ "pto_department" = job.pto_type,
+ "is_off_duty" = job.is_off_duty,
)
- if(config_legacy.time_off && config_legacy.pto_job_change)
+ if(config_legacy.time_off)
data["allow_change_job"] = TRUE
- if(job && job.timeoff_factor < 0) // Currently are Off Duty, so gotta lookup what on-duty jobs are open
+ if(job?.is_off_duty) // Currently are Off Duty, so gotta lookup what on-duty jobs are open
data["job_choices"] = getOpenOnDutyJobs(user, job.pto_type)
return data
@@ -175,7 +173,7 @@
&& job.player_old_enough(user.client) \
&& job.pto_type == department \
&& !job.disallow_jobhop \
- && job.timeoff_factor > 0 \
+ && !job.is_off_duty \
&& (job.check_mob_availability_one(user) == ROLE_AVAILABLE)
/obj/machinery/computer/timeclock/proc/makeOnDuty(var/newrank, var/newassignment)
@@ -209,7 +207,7 @@
var/new_dept = foundjob.pto_type || PTO_CIVILIAN
var/datum/role/job/ptojob = null
for(var/datum/role/job/job in SSjob.occupations)
- if(job.pto_type == new_dept && job.timeoff_factor < 0)
+ if(job.pto_type == new_dept && job.is_off_duty)
ptojob = job
break
if(ptojob)
diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm
index 722f0a30da3e..b6ff02a2f0bf 100644
--- a/code/game/machinery/constructable_frame.dm
+++ b/code/game/machinery/constructable_frame.dm
@@ -113,16 +113,10 @@
src.circuit.construct(new_machine)
for(var/obj/O in src)
- if(circuit.contain_parts) // things like disposal don't want their parts in them
- O.loc = new_machine
- else
- O.loc = null
+ O.loc = new_machine
new_machine.component_parts += O
- if(circuit.contain_parts)
- circuit.loc = new_machine
- else
- circuit.loc = null
+ circuit.loc = new_machine
new_machine.RefreshParts()
qdel(src)
diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm
index eb4321638e1f..237dd9408a77 100644
--- a/code/game/machinery/cryo.dm
+++ b/code/game/machinery/cryo.dm
@@ -1,5 +1,6 @@
///249840 J/K, for a 72 kg person.
-#define HEAT_CAPACITY_HUMAN 100
+// #define HEAT_CAPACITY_HUMAN 100
+#define HEAT_CAPACITY_HUMAN 5
/obj/machinery/atmospherics/component/unary/cryo_cell
name = "cryo cell"
icon = 'icons/obj/medical/cryogenics.dmi' // map only
@@ -227,15 +228,20 @@
if(occupant)
if(occupant.stat >= DEAD)
return
- occupant.bodytemperature += 2*(air_contents.temperature - occupant.bodytemperature)*current_heat_capacity/(current_heat_capacity + air_contents.heat_capacity())
- occupant.bodytemperature = max(occupant.bodytemperature, air_contents.temperature) // this is so ugly i'm sorry for doing it i'll fix it later i promise
- occupant.set_stat(UNCONSCIOUS)
- occupant.dir = SOUTH
+ // todo :kill bodyetmperature and rewrite it from scratch this is not real holy shit
+ var/cooling_power = clamp(
+ -(4 + ((occupant.nominal_bodytemperature() - occupant.bodytemperature) / BODYTEMP_AUTORECOVERY_DIVISOR)),
+ (air_contents.temperature - occupant.bodytemperature),
+ (air_contents.temperature - occupant.bodytemperature) * 0.5,
+ )
+ occupant.adjust_bodytemperature(cooling_power)
+ occupant.setDir(src.dir)
if(occupant.bodytemperature < T0C)
occupant.afflict_sleeping(20 * max(5, (1/occupant.bodytemperature)*2000))
occupant.afflict_unconscious(20 * max(5, (1/occupant.bodytemperature)*3000))
if(air_contents.gas[GAS_ID_OXYGEN] > 2)
- if(occupant.getOxyLoss()) occupant.adjustOxyLoss(-1)
+ if(occupant.getOxyLoss())
+ occupant.adjustOxyLoss(-1)
else
occupant.adjustOxyLoss(-1)
//severe damage should heal waaay slower without proper chemicals
@@ -277,12 +283,15 @@
vis_contents -= occupant
occupant.pixel_x = occupant.base_pixel_x
occupant.pixel_y = occupant.base_pixel_y
- occupant.forceMove(get_step(loc, SOUTH)) //this doesn't account for walls or anything, but i don't forsee that being a problem.
if(occupant.bodytemperature < 261 && occupant.bodytemperature >= 70) //Patch by Aranclanos to stop people from taking burn damage after being ejected
- occupant.bodytemperature = 261 // Changed to 70 from 140 by Zuhayr due to reoccurance of bug.
+ occupant.set_bodytemperature(261) // Changed to 70 from 140 by Zuhayr due to reoccurance of bug.
occupant.forceMove(loc)
occupant.update_perspective()
occupant = null
+
+ REMOVE_TRAIT(occupant, TRAIT_MOB_FORCED_STANDING, CRYO_TUBE_TRAIT)
+ occupant.update_mobility() // make them rest again if needed
+
current_heat_capacity = initial(current_heat_capacity)
update_use_power(USE_POWER_IDLE)
return
@@ -314,6 +323,11 @@
occupant.update_perspective()
vis_contents |= occupant
occupant.pixel_y += 19
+
+ ADD_TRAIT(occupant, TRAIT_MOB_FORCED_STANDING, CRYO_TUBE_TRAIT)
+ occupant.setDir(src.dir)
+ occupant.set_resting(FALSE)
+
current_heat_capacity = HEAT_CAPACITY_HUMAN
update_use_power(USE_POWER_ACTIVE)
// M.metabslow = 1
diff --git a/code/game/machinery/doors/airlock/airlock_subtypes.dm b/code/game/machinery/doors/airlock/airlock_subtypes.dm
index 4c7756360015..fd617414b25f 100644
--- a/code/game/machinery/doors/airlock/airlock_subtypes.dm
+++ b/code/game/machinery/doors/airlock/airlock_subtypes.dm
@@ -477,7 +477,13 @@
name = "alien airlock"
desc = "You're fairly sure this is a door."
catalogue_data = list(/datum/category_item/catalogue/anomalous/precursor_a/alien_airlock)
- icon = 'icons/obj/doors/Dooralien.dmi'
+ door_color = "none"
+ icon = 'icons/obj/doors/alien/door.dmi'
+ bolts_file = 'icons/obj/doors/alien/lights_bolts.dmi'
+ lights_file = 'icons/obj/doors/alien/lights_green.dmi'
+ sparks_damaged_file = 'icons/obj/doors/alien/sparks_damaged.dmi'
+ sparks_broken_file = 'icons/obj/doors/alien/sparks_broken.dmi'
+ welded_file = 'icons/obj/doors/alien/welded.dmi'
explosion_resistance = 20
secured_wires = TRUE
hackProof = TRUE
@@ -485,7 +491,6 @@
req_one_access = list(ACCESS_FACTION_ALIEN)
/obj/machinery/door/airlock/alien/locked
- icon_state = "door_locked"
locked = TRUE
/obj/machinery/door/airlock/alien/public // Entry to UFO.
diff --git a/code/game/machinery/doors/airlock/airlock_vr.dm b/code/game/machinery/doors/airlock/airlock_vr.dm
index 114dec9f567e..8d625ceabe85 100644
--- a/code/game/machinery/doors/airlock/airlock_vr.dm
+++ b/code/game/machinery/doors/airlock/airlock_vr.dm
@@ -5,7 +5,9 @@
name = "hybrid airlock"
desc = "You're fairly sure this is a door."
catalogue_data = list(/datum/category_item/catalogue/anomalous/precursor_a/alien_airlock)
- icon = 'icons/obj/doors/Dooralien_blue.dmi'
+ icon = 'icons/obj/doors/alien/door_blue.dmi'
+ bolts_file = 'icons/obj/doors/alien/lights_bolts_blue.dmi'
+ lights_file = 'icons/obj/doors/alien/lights_green_blue.dmi'
explosion_resistance = 20
secured_wires = TRUE
hackProof = TRUE
@@ -13,7 +15,6 @@
req_one_access = list()
/obj/machinery/door/airlock/alien/blue/locked
- icon_state = "door_locked"
locked = TRUE
/obj/machinery/door/airlock/alien/blue/public // Entry to UFO.
diff --git a/code/game/machinery/embedded_controller/airlock_program.dm b/code/game/machinery/embedded_controller/airlock_program.dm
index 1c971c264659..a0927b31421d 100644
--- a/code/game/machinery/embedded_controller/airlock_program.dm
+++ b/code/game/machinery/embedded_controller/airlock_program.dm
@@ -210,6 +210,7 @@
"tag" = tag,
"sigtype" = "command",
"power" = "[power]",
+ "expand" = "[1]",
)
post_signal(signal)
diff --git a/code/game/machinery/fire_alarm.dm b/code/game/machinery/fire_alarm.dm
index 101465e9cf45..c700d38d22b8 100644
--- a/code/game/machinery/fire_alarm.dm
+++ b/code/game/machinery/fire_alarm.dm
@@ -8,21 +8,21 @@ CREATE_WALL_MOUNTING_TYPES_SHIFTED(/obj/machinery/fire_alarm, 21)
icon = 'icons/obj/firealarm.dmi'
icon_state = "casing"
plane = TURF_PLANE
- layer = ABOVE_TURF_LAYER
- var/detecting = TRUE
- var/working = TRUE
- var/time = 10
- var/timing = 0
- var/lockdownbyai = FALSE
+ layer = ABOVE_WINDOW_LAYER
anchored = TRUE
use_power = TRUE
idle_power_usage = 2
active_power_usage = 6
power_channel = ENVIRON
- var/last_process = 0
panel_open = FALSE
- var/seclevel
circuit = /obj/item/circuitboard/firealarm
+ var/detecting = TRUE
+ var/working = TRUE
+ var/time = 10
+ var/timing = 0
+ var/lockdownbyai = FALSE
+ var/last_process = 0
+ var/seclevel
/// If the alarms from this machine are visible on consoles.
var/alarms_hidden = FALSE
@@ -34,7 +34,7 @@ CREATE_WALL_MOUNTING_TYPES_SHIFTED(/obj/machinery/fire_alarm/alarms_hidden, 21)
dir = turn(dir, -preloader.turn_angle)
return FALSE
-/obj/machinery/fire_alarm/Initialize(mapload)
+/obj/machinery/fire_alarm/Initialize(mapload, dir = src.dir)
. = ..()
if(z in (LEGACY_MAP_DATUM).contact_levels)
set_security_level(GLOB.security_level ? get_security_level() : "green")
@@ -155,7 +155,6 @@ CREATE_WALL_MOUNTING_TYPES_SHIFTED(/obj/machinery/fire_alarm/alarms_hidden, 21)
return
alarm()
- return
/obj/machinery/fire_alarm/process()//Note: this processing was mostly phased out due to other code, and only runs when needed
if(machine_stat & (NOPOWER|BROKEN))
@@ -268,117 +267,8 @@ CREATE_WALL_MOUNTING_TYPES_SHIFTED(/obj/machinery/fire_alarm/alarms_hidden, 21)
fire_alarm.triggerAlarm(loc, FA, duration, hidden = alarms_hidden)
update_icon()
playsound(src.loc, 'sound/machines/airalarm.ogg', 25, 0, 4)
- return
/obj/machinery/fire_alarm/proc/set_security_level(var/newlevel)
if(seclevel != newlevel)
seclevel = newlevel
update_icon()
-
-/*
-FIRE ALARM CIRCUIT
-Just a object used in constructing fire alarms
-/obj/item/firealarm_electronics
- name = "fire alarm electronics"
- icon = 'icons/obj/doors/door_assembly.dmi'
- icon_state = "door_electronics"
- desc = "A circuit. It has a label on it, it says \"Can handle heat levels up to 40 degrees celsius!\""
- w_class = WEIGHT_CLASS_SMALL
- materials_base = list(MAT_STEEL = 50, MAT_GLASS = 50)
-*/
-/obj/machinery/partyalarm
- name = "\improper PARTY BUTTON"
- desc = "Cuban Pete is in the house!"
- icon = 'icons/obj/monitors.dmi'
- icon_state = "fire0"
- var/detecting = TRUE
- var/working = TRUE
- var/time = 10
- var/timing = 0
- var/lockdownbyai = FALSE
- anchored = TRUE
- use_power = TRUE
- idle_power_usage = 2
- active_power_usage = 6
-
-/obj/machinery/partyalarm/attack_hand(mob/user, list/params)
- if(user.stat || machine_stat & (NOPOWER|BROKEN))
- return
-
- user.machine = src
- var/area/A = get_area(src)
- ASSERT(isarea(A))
- var/d1
- var/d2
- if(istype(user, /mob/living/carbon/human) || istype(user, /mob/living/silicon/ai))
-
- if(A.party)
- d1 = "No Party :("
- else
- d1 = "PARTY!!!"
- if(timing)
- d2 = "Stop Time Lock"
- else
- d2 = "Initiate Time Lock"
- var/second = time % 60
- var/minute = (time - second) / 60
- var/dat = "Party Button [d1]\n\nTimer System: [d2] \nTime Left: [(minute ? "[minute]:" : null)][second] --++\n"
-
- user << browse(dat, "window=partyalarm")
- onclose(user, "partyalarm")
- else
- if(A.fire)
- d1 = "[stars("No Party :(")]"
- else
- d1 = "[stars("PARTY!!!")]"
- if(timing)
- d2 = "[stars("Stop Time Lock")]"
- else
- d2 = "[stars("Initiate Time Lock")]"
- var/second = time % 60
- var/minute = (time - second) / 60
- var/dat = "[stars("Party Button")] [d1]\n\nTimer System: [d2] \nTime Left: [(minute ? "[minute]:" : null)][second] --++\n"
-
- user << browse(dat, "window=partyalarm")
- onclose(user, "partyalarm")
- return
-
-/obj/machinery/partyalarm/proc/reset()
- if(!(working))
- return
- var/area/A = get_area(src)
- ASSERT(isarea(A))
- A.partyreset()
- return
-
-/obj/machinery/partyalarm/proc/alarm()
- if(!(working))
- return
- var/area/A = get_area(src)
- ASSERT(isarea(A))
- A.partyalert()
- return
-
-/obj/machinery/partyalarm/Topic(href, href_list)
- ..()
- if(usr.stat || machine_stat & (BROKEN|NOPOWER))
- return
- if((usr.contents.Find(src) || ((get_dist(src, usr) <= 1) && istype(loc, /turf))) || (istype(usr, /mob/living/silicon/ai)))
- usr.machine = src
- if(href_list["reset"])
- reset()
- else if(href_list["alarm"])
- alarm()
- else if(href_list["time"])
- timing = text2num(href_list["time"])
- else if(href_list["tp"])
- var/tp = text2num(href_list["tp"])
- time += tp
- time = min(max(round(time), 0), 120)
- updateUsrDialog()
-
- add_fingerprint(usr)
- else
- usr << browse(null, "window=partyalarm")
- return
- return
diff --git a/code/game/machinery/lathes/medical_lathe.dm b/code/game/machinery/lathes/medical_lathe.dm
new file mode 100644
index 000000000000..42a0ca11f675
--- /dev/null
+++ b/code/game/machinery/lathes/medical_lathe.dm
@@ -0,0 +1,32 @@
+/obj/item/circuitboard/machine/lathe/medi_mini
+ name = T_BOARD("medical MiniLathe")
+ build_path = /obj/machinery/lathe/medical
+
+/obj/machinery/lathe/medical
+ name = "medical MiniLathe"
+ desc = "A standard minilathe, this one seems to print medical consumables."
+ lathe_type = LATHE_TYPE_AUTOLATHE
+
+ has_interface = TRUE
+
+ icon = 'icons/machinery/lathe/microlathe.dmi'
+ active_icon_state = "active"
+ print_icon_state = "print"
+ paused_icon_state = "pause"
+
+ idle_power_usage = POWER_USAGE_LATHE_IDLE / 2
+ active_power_usage = POWER_USAGE_LATHE_ACTIVE_SCALE(1) / 2
+ circuit = /obj/item/circuitboard/machine/lathe/medi_mini
+ design_holder = /datum/design_holder/lathe/medi_mini_lathe
+
+/datum/design_holder/lathe/medi_mini_lathe/available_ids()
+ return SSresearch.medical_mini_design_ids | ..()
+
+/obj/machinery/lathe/medical/stocked/Initialize(mapload)
+ . = ..()
+ LAZYINITLIST(stored_materials.stored)
+ stored_materials.stored[MAT_STEEL] = 5 * SHEET_MATERIAL_AMOUNT
+ stored_materials.stored[MAT_GLASS] = 5 * SHEET_MATERIAL_AMOUNT
+ stored_materials.stored[MAT_PLASTIC] = 1 * SHEET_MATERIAL_AMOUNT
+ stored_materials.stored[MAT_WOOD] = 1 * SHEET_MATERIAL_AMOUNT
+
diff --git a/code/game/machinery/pipe/pipe_dispenser.dm b/code/game/machinery/pipe/pipe_dispenser.dm
index d9adc6922ebc..5677ee85cd0a 100644
--- a/code/game/machinery/pipe/pipe_dispenser.dm
+++ b/code/game/machinery/pipe/pipe_dispenser.dm
@@ -16,10 +16,9 @@
return
ui_interact(user)
-/obj/machinery/pipedispenser/ui_assets(mob/user)
- return list(
- get_asset_datum(/datum/asset/spritesheet/pipes),
- )
+/obj/machinery/pipedispenser/ui_asset_injection(datum/tgui/ui, list/immediate, list/deferred)
+ immediate += /datum/asset_pack/spritesheet/pipes
+ return ..()
/obj/machinery/pipedispenser/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
diff --git a/code/game/machinery/suit_storage/suit_storage_prepared.dm b/code/game/machinery/suit_storage/suit_storage_prepared.dm
index 4c91db73c271..00cb8bcafc18 100644
--- a/code/game/machinery/suit_storage/suit_storage_prepared.dm
+++ b/code/game/machinery/suit_storage/suit_storage_prepared.dm
@@ -24,8 +24,8 @@
boots_stored_TYPE = /obj/item/clothing/shoes/magboots
/obj/machinery/suit_storage_unit/search_and_rescue
- suit_stored_TYPE = /obj/item/clothing/suit/space/void/medical
- helmet_stored_TYPE = /obj/item/clothing/head/helmet/space/void/medical
+ suit_stored_TYPE = /obj/item/clothing/suit/space/void/exploration/alt
+ helmet_stored_TYPE = /obj/item/clothing/head/helmet/space/void/exploration/alt
mask_stored_TYPE = /obj/item/clothing/mask/breath
boots_stored_TYPE = /obj/item/clothing/shoes/magboots
diff --git a/code/game/machinery/vending/loadout.dm b/code/game/machinery/vending/loadout.dm
index b80c1f89bac5..03debaedc09c 100644
--- a/code/game/machinery/vending/loadout.dm
+++ b/code/game/machinery/vending/loadout.dm
@@ -171,6 +171,8 @@
/obj/item/clothing/accessory/sweater/flowersweater = 5,
/obj/item/clothing/accessory/sweater/redneck = 5,
/obj/item/clothing/accessory/sweater/virgin = 5,
+ /obj/item/clothing/accessory/sweater/milk = 5,
+ /obj/item/clothing/accessory/sweater/milk/male = 5,
/obj/item/clothing/accessory/tie = 5,
/obj/item/clothing/accessory/tie/horrible = 5,
/obj/item/clothing/accessory/tie/white = 5,
@@ -224,6 +226,7 @@
/obj/item/clothing/mask/bandana/green = 5,
/obj/item/clothing/mask/bandana/red = 5,
/obj/item/clothing/mask/surgical = 5,
+ /obj/item/clothing/mask/warmer = 5,
)
premium = list(/obj/item/bedsheet/rainbow = 1)
contraband = list(/obj/item/clothing/mask/gas/clown_hat = 1)
diff --git a/code/game/machinery/vending/vending.dm b/code/game/machinery/vending/vending.dm
index f7506da156bd..a47f2cf8da65 100644
--- a/code/game/machinery/vending/vending.dm
+++ b/code/game/machinery/vending/vending.dm
@@ -11,18 +11,18 @@
// todo: temporary, as this is unbuildable
integrity_flags = INTEGRITY_INDESTRUCTIBLE
-//! ## Icons
+ //* Icons *//
/// Icon_state when vending.
var/icon_vend
/// Icon_state when denying access.
var/icon_deny
-//! ## Power
+ //* Power *//
use_power = USE_POWER_IDLE
idle_power_usage = 10
var/vend_power_usage = 150 //actuators and stuff
-//! ## Vending-related
+ //* Vending *//
/// No sales pitches if off!
var/active = TRUE
/// Are we ready to vend?? Is it time??
diff --git a/code/game/mecha/combat/combat.dm b/code/game/mecha/combat/combat.dm
index 7159c35fef70..a9e84b974790 100644
--- a/code/game/mecha/combat/combat.dm
+++ b/code/game/mecha/combat/combat.dm
@@ -82,7 +82,7 @@
H.stun_effect_act(1, force / 2, BP_TORSO, src)
else
return
- if(update) H.UpdateDamageIcon()
+ if(update) H.update_damage_overlay()
H.update_health()
else
diff --git a/code/game/mecha/mech_fabricator.dm b/code/game/mecha/mech_fabricator.dm
index 8d2e3af026bd..1a6fe4513e80 100644
--- a/code/game/mecha/mech_fabricator.dm
+++ b/code/game/mecha/mech_fabricator.dm
@@ -478,10 +478,9 @@
/obj/machinery/mecha_part_fabricator/proc/get_construction_time_w_coeff(construction_time, roundto = 1) //aran
return round(construction_time * time_coeff, roundto)
-/obj/machinery/mecha_part_fabricator/ui_assets(mob/user)
- return list(
- get_asset_datum(/datum/asset/spritesheet/materials)
- )
+/obj/machinery/mecha_part_fabricator/ui_asset_injection(datum/tgui/ui, list/immediate, list/deferred)
+ immediate += /datum/asset_pack/spritesheet/materials
+ return ..()
/obj/machinery/mecha_part_fabricator/attack_hand(mob/user, list/params)
if(..())
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index 0b274a4ae479..4706b69b485b 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -171,7 +171,7 @@
var/static/image/radial_image_lighttoggle = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_light")
var/static/image/radial_image_statpanel = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_examine2")
-//! Mech actions
+ //* Legacy - Actions *//
var/datum/mini_hud/mech/minihud
/// re we strafing or not?
var/strafing = 0
diff --git a/code/game/mecha/micro/micro.dm b/code/game/mecha/micro/micro.dm
index d2f478a8b86b..6399d7cac31f 100644
--- a/code/game/mecha/micro/micro.dm
+++ b/code/game/mecha/micro/micro.dm
@@ -66,7 +66,7 @@
H.reagents.add_reagent("cryptobiolin", force)
else
return
- if(update) H.UpdateDamageIcon()
+ if(update) H.update_damage_overlay()
H.update_health()
else
diff --git a/code/game/objects/effects/debris/cleanable/blood.dm b/code/game/objects/effects/debris/cleanable/blood.dm
index f87cbc9d5348..095f46374693 100644
--- a/code/game/objects/effects/debris/cleanable/blood.dm
+++ b/code/game/objects/effects/debris/cleanable/blood.dm
@@ -184,9 +184,8 @@ var/global/list/image/splatter_cache=list()
if(random_icon_states.len)
for(var/obj/effect/debris/cleanable/blood/writing/W in loc)
random_icon_states.Remove(W.icon_state)
- if(!LAZYLEN(random_icon_states))//If all iconstates are already in used by someone else on our tile, delete yourself
- qdel(src)
- return
+ if(!length(random_icon_states))
+ return INITIALIZE_HINT_QDEL
icon_state = pick(random_icon_states)
else
icon_state = "writing1"
diff --git a/code/game/objects/effects/decals/posters/citadel.dm b/code/game/objects/effects/decals/posters/citadel.dm
new file mode 100644
index 000000000000..172d40c47e5d
--- /dev/null
+++ b/code/game/objects/effects/decals/posters/citadel.dm
@@ -0,0 +1,4 @@
+/datum/poster/cit_1
+ icon_state="citposter1"
+ name = "I Blame Kynde"
+ desc = "A poster blaming the corporation Kynde Group Pharmaceuticals for something. It doesn't specify what."
diff --git a/code/game/objects/effects/particles/_particles.dm b/code/game/objects/effects/particles/_particles.dm
index 3f63047e3c81..fff2fa9a9c90 100644
--- a/code/game/objects/effects/particles/_particles.dm
+++ b/code/game/objects/effects/particles/_particles.dm
@@ -18,7 +18,7 @@
* After the datum is created, it can be assigned to an obj or mob using their particles var. The particles will appear on the map wherever that obj or mob appears.
*/
/particles
-//! ## Particle vars that affect the entire set (generators are not allowed for these)
+ //* Particle vars that affect the entire set (generators are not allowed for these) *//
/**
* Width size of the particle image in pixels.
@@ -74,7 +74,7 @@
*/
transform = null
-//! ## Vars that apply when a particle spawns
+ //* Vars that apply when a particle spawns *//
/**
* Maximum life of the particle, in ticks.
@@ -94,8 +94,8 @@
*/
fadein = 0
-//? The icon and icon_state values are special in that they can't be assigned a generator, but they
-//? can be assigned a constant icon or string, respectively, or a list of possible values to choose from.
+ //? The icon and icon_state values are special in that they can't be assigned a generator, but they
+ //? can be assigned a constant icon or string, respectively, or a list of possible values to choose from.
/**
* Icon to use, if any; no icon means this particle will be a dot.
@@ -165,7 +165,7 @@
*/
friction = null
-//! ## Vars that are evalulated every tick.
+ //* Vars that are evalulated every tick. *//
/**
* Added acceleration every tick; e.g. a circle or sphere generator can be applied to produce snow or ember effects.
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 8f96673b39a5..94bf8beecc7c 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -931,7 +931,7 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
if(!isnull(obj_storage) && obj_storage.allow_quick_empty && obj_storage.allow_quick_empty_via_attack_self)
var/turf/turf = get_turf(e_args.performer)
obj_storage.auto_handle_interacted_mass_dumping(e_args, turf)
- return CLICKCHAIN_DO_NOT_PROPAGATE
+ return TRUE
return FALSE
/**
diff --git a/code/game/objects/items/apc_frame.dm b/code/game/objects/items/apc_frame.dm
deleted file mode 100644
index a906bc6ddec0..000000000000
--- a/code/game/objects/items/apc_frame.dm
+++ /dev/null
@@ -1,44 +0,0 @@
-// APC HULL
-
-/obj/item/frame/apc
- name = "\improper APC frame"
- desc = "Used for repairing or building APCs"
- icon = 'icons/obj/apc_repair.dmi'
- icon_state = "apc_frame"
- refund_amt = 2
- refund_type = /obj/item/stack/material/steel
-
-/obj/item/frame/apc/attackby(obj/item/W as obj, mob/user as mob)
- ..()
- if (W.is_wrench())
- new /obj/item/stack/material/steel( get_turf(src.loc), 2 )
- qdel(src)
-
-/obj/item/frame/apc/try_build(turf/on_wall, mob/user as mob)
- if (get_dist(on_wall, user)>1)
- return
- var/ndir = get_dir(user, on_wall)
- if (!(ndir in GLOB.cardinal))
- return
- var/turf/loc = get_turf(user)
- var/area/A = loc.loc
- if (!istype(loc, /turf/simulated/floor))
- to_chat(user, "APC cannot be placed on this spot.")
- return
- if (A.requires_power == 0 || istype(A, /area/space))
- to_chat(user, "APC cannot be placed in this area.")
- return
- if (A.get_apc())
- to_chat(user, "This area already has an APC.")
- return //only one APC per area
- for(var/obj/machinery/power/terminal/T in loc)
- if (T.master)
- to_chat(user, "There is another network terminal here.")
- return
- else
- var/obj/item/stack/cable_coil/C = new /obj/item/stack/cable_coil(loc)
- C.amount = 10
- to_chat(user, "You cut the cables and disassemble the unused power terminal.")
- qdel(T)
- new /obj/machinery/power/apc(loc, ndir, 1)
- qdel(src)
diff --git a/code/game/objects/items/circuitboards/circuitboard.dm b/code/game/objects/items/circuitboards/circuitboard.dm
index ce82992e42f9..c18140f7309d 100644
--- a/code/game/objects/items/circuitboards/circuitboard.dm
+++ b/code/game/objects/items/circuitboards/circuitboard.dm
@@ -14,11 +14,9 @@
MAT_GLASS = 250,
)
origin_tech = list(TECH_DATA = 2)
- density = FALSE
- anchored = FALSE
w_class = WEIGHT_CLASS_SMALL
- damage_force = 5.0
- throw_force = 5.0
+ damage_force = 5
+ throw_force = 5
throw_speed = 3
throw_range = 15
var/build_path = null
@@ -35,8 +33,6 @@
*/
var/list/def_components
- var/contain_parts = TRUE
-
/**
* called when we are used to construct something.
*
diff --git a/code/game/objects/items/circuitboards/frame.dm b/code/game/objects/items/circuitboards/frame.dm
index 87b2cc6b2140..b473bcc0d1e6 100644
--- a/code/game/objects/items/circuitboards/frame.dm
+++ b/code/game/objects/items/circuitboards/frame.dm
@@ -46,7 +46,7 @@
/obj/item/circuitboard/firealarm
name = T_BOARD("fire alarm")
- build_path = /obj/machinery/firealarm
+ build_path = /obj/machinery/fire_alarm
board_type = new /datum/frame/frame_types/fire_alarm
materials_base = list(MAT_STEEL = 50, MAT_GLASS = 50)
diff --git a/code/game/objects/items/id_cards/cards.dm b/code/game/objects/items/id_cards/cards.dm
index 62997abe8a8f..e6bd11dd4d72 100644
--- a/code/game/objects/items/id_cards/cards.dm
+++ b/code/game/objects/items/id_cards/cards.dm
@@ -75,3 +75,114 @@
desc = "This card contains coordinates to the fabled Clown Planet. Handle with care."
function = "teleporter"
data = "Clown Land"
+
+
+/// FLUFF PERMIT
+
+/obj/item/card_fluff
+ name = "fluff card"
+ desc = "A tiny plaque of plastic. Purely decorative?"
+ description_fluff = "This permit was not issued by any branch of NanoTrasen, and as such it is not formally recognized at any NanoTrasen-operated installations. The bearer is not - under any circumstances - entitled to ownership of any items or allowed to perform any acts that would normally be restricted or illegal for their current position, regardless of what they or this permit may claim."
+ icon = 'icons/obj/card_fluff.dmi'
+ item_state = "card-id"
+ item_state_slots = list(
+ SLOT_ID_WORN_ID = "id"
+ )
+ w_class = WEIGHT_CLASS_TINY
+ slot_flags = SLOT_EARS
+
+ var/list/initial_sprite_stack = list("")
+ var/base_icon = 'icons/obj/card_fluff.dmi'
+ var/list/sprite_stack = list("")
+
+ drop_sound = 'sound/items/drop/card.ogg'
+ pickup_sound = 'sound/items/pickup/card.ogg'
+
+/obj/item/card_fluff/proc/reset_icon()
+ sprite_stack = list("")
+ update_icon()
+
+/obj/item/card_fluff/update_icon()
+ if(!sprite_stack || !istype(sprite_stack) || sprite_stack == list(""))
+ icon = base_icon
+ icon_state = initial(icon_state)
+
+ var/icon/I = null
+ for(var/iconstate in sprite_stack)
+ if(!iconstate)
+ iconstate = icon_state
+ if(I)
+ var/icon/IC = new(base_icon, iconstate)
+ I.Blend(IC, ICON_OVERLAY)
+ else
+ I = new/icon(base_icon, iconstate)
+ if(I)
+ icon = I
+
+/obj/item/card_fluff/attack_self()
+
+ var/choice = tgui_input_list(usr, "What element would you like to customize?", "Customize Card", list("Band","Stamp","Reset"))
+ if(!choice) return
+
+ if(choice == "Band")
+ var/bandchoice = tgui_input_list(usr, "Select colour", "Band colour", list("red","orange","green","dark green","medical blue","dark blue","purple","tan","pink","gold","white","black"))
+ if(!bandchoice) return
+
+ if(bandchoice == "red")
+ sprite_stack.Add("bar-red")
+ else if(bandchoice == "orange")
+ sprite_stack.Add("bar-orange")
+ else if(bandchoice == "green")
+ sprite_stack.Add("bar-green")
+ else if(bandchoice == "dark green")
+ sprite_stack.Add("bar-darkgreen")
+ else if(bandchoice == "medical blue")
+ sprite_stack.Add("bar-medblue")
+ else if(bandchoice == "dark blue")
+ sprite_stack.Add("bar-blue")
+ else if(bandchoice == "purple")
+ sprite_stack.Add("bar-purple")
+ else if(bandchoice == "ran")
+ sprite_stack.Add("bar-tan")
+ else if(bandchoice == "pink")
+ sprite_stack.Add("bar-pink")
+ else if(bandchoice == "gold")
+ sprite_stack.Add("bar-gold")
+ else if(bandchoice == "white")
+ sprite_stack.Add("bar-white")
+ else if(bandchoice == "black")
+ sprite_stack.Add("bar-black")
+
+ update_icon()
+ return
+ else if(choice == "Stamp")
+ var/stampchoice = tgui_input_list(usr, "Select image", "Stamp image", list("ship","cross","big ears","shield","circle-cross","target","smile","frown","peace","exclamation"))
+ if(!stampchoice) return
+
+ if(stampchoice == "ship")
+ sprite_stack.Add("stamp-starship")
+ else if(stampchoice == "cross")
+ sprite_stack.Add("stamp-cross")
+ else if(stampchoice == "big ears")
+ sprite_stack.Add("stamp-bigears") //get 'em outta the caption, wiseguy!!
+ else if(stampchoice == "shield")
+ sprite_stack.Add("stamp-shield")
+ else if(stampchoice == "circle-cross")
+ sprite_stack.Add("stamp-circlecross")
+ else if(stampchoice == "target")
+ sprite_stack.Add("stamp-target")
+ else if(stampchoice == "smile")
+ sprite_stack.Add("stamp-smile")
+ else if(stampchoice == "frown")
+ sprite_stack.Add("stamp-frown")
+ else if(stampchoice == "peace")
+ sprite_stack.Add("stamp-peace")
+ else if(stampchoice == "exclamation")
+ sprite_stack.Add("stamp-exclaim")
+
+ update_icon()
+ return
+ else if(choice == "Reset")
+ reset_icon()
+ return
+ return
diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm
index 6c93e5816e05..e39b4b3fb6c8 100644
--- a/code/game/objects/items/stacks/medical.dm
+++ b/code/game/objects/items/stacks/medical.dm
@@ -56,7 +56,7 @@
to_chat(user, "You apply the [src], but it seems to have no effect...")
use(1)
return FALSE
- H.UpdateDamageIcon()
+ H.update_damage_overlay()
else
C.heal_organ_damage((src.heal_brute/2), (src.heal_burn/2))
user.visible_message( \
diff --git a/code/game/objects/items/storage/backpack.dm b/code/game/objects/items/storage/backpack.dm
index 6b5652a0277d..0ad96126f933 100644
--- a/code/game/objects/items/storage/backpack.dm
+++ b/code/game/objects/items/storage/backpack.dm
@@ -590,29 +590,29 @@
if(!.)
return FALSE
var/mob/living/carbon/human/H = M
- var/datum/sprite_accessory/tail/taur/TT = H.tail_style
- if(istype(H) && istype(TT, /datum/sprite_accessory/tail/taur/horse))
+ var/datum/sprite_accessory/tail/legacy_taur/TT = H.tail_style
+ if(istype(H) && istype(TT, /datum/sprite_accessory/tail/legacy_taur/horse))
item_state = "[icon_base]_horse"
return 1
- if(istype(H) && istype(TT, /datum/sprite_accessory/tail/taur/wolf))
+ if(istype(H) && istype(TT, /datum/sprite_accessory/tail/legacy_taur/wolf))
item_state = "[icon_base]_wolf"
return 1
- if(istype(H) && istype(TT, /datum/sprite_accessory/tail/taur/cow))
+ if(istype(H) && istype(TT, /datum/sprite_accessory/tail/legacy_taur/cow))
item_state = "[icon_base]_cow"
return 1
- if(istype(H) && istype(TT, /datum/sprite_accessory/tail/taur/lizard))
+ if(istype(H) && istype(TT, /datum/sprite_accessory/tail/legacy_taur/lizard))
item_state = "[icon_base]_lizard"
return 1
- if(istype(H) && istype(TT, /datum/sprite_accessory/tail/taur/feline))
+ if(istype(H) && istype(TT, /datum/sprite_accessory/tail/legacy_taur/feline))
item_state = "[icon_base]_feline"
return 1
- if(istype(H) && istype(TT, /datum/sprite_accessory/tail/taur/drake))
+ if(istype(H) && istype(TT, /datum/sprite_accessory/tail/legacy_taur/drake))
item_state = "[icon_base]_drake"
return 1
- if(istype(H) && istype(TT, /datum/sprite_accessory/tail/taur/otie))
+ if(istype(H) && istype(TT, /datum/sprite_accessory/tail/legacy_taur/otie))
item_state = "[icon_base]_otie"
return 1
- if(istype(H) && istype(TT, /datum/sprite_accessory/tail/taur/deer))
+ if(istype(H) && istype(TT, /datum/sprite_accessory/tail/legacy_taur/deer))
item_state = "[icon_base]_deer"
return 1
else
diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm
index 37aa58512548..3c11d0611c8b 100644
--- a/code/game/objects/items/storage/belt.dm
+++ b/code/game/objects/items/storage/belt.dm
@@ -25,7 +25,7 @@
update_icon()
// todo: this bad lol
-/obj/item/storage/belt/render_apply_overlays(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot_meta/slot_meta, icon_used)
+/obj/item/storage/belt/render_apply_overlays(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot/slot_meta, icon_used)
. = ..()
var/static/icon/funny_belt_icon = 'icons/mob/clothing/belt.dmi'
for(var/obj/item/I in contents)
diff --git a/code/game/objects/items/storage/medical/firstaid.dm b/code/game/objects/items/storage/medical/firstaid.dm
index 74be1b9dc196..7196fb1ea805 100644
--- a/code/game/objects/items/storage/medical/firstaid.dm
+++ b/code/game/objects/items/storage/medical/firstaid.dm
@@ -172,6 +172,8 @@
sfx_open = null
max_combined_volume = WEIGHT_VOLUME_TINY * 14
max_single_weight_class = WEIGHT_CLASS_TINY
+ materials_base = list(MAT_PLASTIC = 80)
+ item_flags = ITEM_CAREFUL_BLUDGEON | ITEM_ENCUMBERS_WHILE_HELD | ITEM_EASY_LATHE_DECONSTRUCT
var/label_text = ""
var/labeled = 0
diff --git a/code/game/objects/items/weapons/RPD.dm b/code/game/objects/items/weapons/RPD.dm
index 1ac9261686b7..816efe7b5867 100644
--- a/code/game/objects/items/weapons/RPD.dm
+++ b/code/game/objects/items/weapons/RPD.dm
@@ -112,10 +112,9 @@
return
ui_interact(user)
-/obj/item/pipe_dispenser/ui_assets(mob/user)
- return list(
- get_asset_datum(/datum/asset/spritesheet/pipes),
- )
+/obj/item/pipe_dispenser/ui_asset_injection(datum/tgui/ui, list/immediate, list/deferred)
+ immediate += /datum/asset_pack/spritesheet/pipes
+ return ..()
/obj/item/pipe_dispenser/ui_state()
return GLOB.inventory_state
diff --git a/code/game/objects/items/weapons/handcuffs.dm b/code/game/objects/items/weapons/handcuffs.dm
index 96578cddd247..8faf835e2a90 100644
--- a/code/game/objects/items/weapons/handcuffs.dm
+++ b/code/game/objects/items/weapons/handcuffs.dm
@@ -128,7 +128,7 @@ var/last_chew = 0
add_attack_logs(H,H,"chewed own [O.name]")
if(O.take_damage(3,0,1,1,"teeth marks"))
- H:UpdateDamageIcon()
+ H:update_damage_overlay()
last_chew = world.time
*/
diff --git a/code/game/objects/items/weapons/implants/implantaugment.dm b/code/game/objects/items/weapons/implants/implantaugment.dm
index da26f72efa9e..cdbb56e3b955 100644
--- a/code/game/objects/items/weapons/implants/implantaugment.dm
+++ b/code/game/objects/items/weapons/implants/implantaugment.dm
@@ -118,29 +118,29 @@
if(O_AUG_R_HAND)
I.organ_tag = O_AUG_R_HAND
I.parent_organ = BP_R_HAND
- I.target_slot = /datum/inventory_slot_meta/abstract/hand/right
+ I.target_slot = /datum/inventory_slot/abstract/hand/right
if(O_AUG_L_HAND)
I.organ_tag = O_AUG_L_HAND
I.parent_organ = BP_L_HAND
- I.target_slot = /datum/inventory_slot_meta/abstract/hand/left
+ I.target_slot = /datum/inventory_slot/abstract/hand/left
if(O_AUG_R_FOREARM)
I.organ_tag = O_AUG_R_FOREARM
I.parent_organ = BP_R_ARM
- I.target_slot = /datum/inventory_slot_meta/abstract/hand/right
+ I.target_slot = /datum/inventory_slot/abstract/hand/right
if(O_AUG_L_FOREARM)
I.organ_tag = O_AUG_L_FOREARM
I.parent_organ = BP_L_ARM
- I.target_slot = /datum/inventory_slot_meta/abstract/hand/left
+ I.target_slot = /datum/inventory_slot/abstract/hand/left
if(O_AUG_R_UPPERARM)
I.organ_tag = O_AUG_R_UPPERARM
I.parent_organ = BP_R_ARM
- I.target_slot = /datum/inventory_slot_meta/abstract/hand/right
+ I.target_slot = /datum/inventory_slot/abstract/hand/right
if(O_AUG_L_UPPERARM)
I.organ_tag = O_AUG_L_UPPERARM
I.parent_organ = BP_L_ARM
- I.target_slot = /datum/inventory_slot_meta/abstract/hand/left
+ I.target_slot = /datum/inventory_slot/abstract/hand/left
. = H.get_organ(I.parent_organ)
diff --git a/code/game/objects/items/weapons/material/armor.dm b/code/game/objects/items/weapons/material/armor.dm
index f6b0c6fe7eb9..fcbfb38b4bc8 100644
--- a/code/game/objects/items/weapons/material/armor.dm
+++ b/code/game/objects/items/weapons/material/armor.dm
@@ -1,7 +1,7 @@
// todo: this is awful and need to all be refactored
// Putting these at /clothing/ level saves a lot of code duplication in armor/helmets/gauntlets/etc
/obj/item/clothing
- material_parts = MATERIAL_DEFAULT_NONE
+ material_parts = MATERIAL_DEFAULT_DISABLED
material_costs = 4000
material_primary = MATERIAL_PART_DEFAULT
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index dec2d74f8d80..9cc2b6640501 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -111,6 +111,10 @@
/// * this variable is not visible and should not be edited in the map editor.
var/tmp/obj_persist_dynamic_status = NONE
+ //* Rotation
+ /// rotation behavior flags
+ var/obj_rotation_flags = NONE
+
//? Sounds
/// volume when breaking out using resist process
var/breakout_sound = 'sound/effects/grillehit.ogg'
@@ -514,7 +518,7 @@
to_chat(H, "You land heavily!")
H.adjustBruteLoss(damage)
- H.UpdateDamageIcon()
+ H.update_damage_overlay()
H.update_health()
*/
@@ -528,6 +532,33 @@
if(obj_storage?.allow_open_via_context_click)
var/image/rendered = image(src)
.["obj_storage"] = atom_context_tuple("open storage", rendered, mobility = MOBILITY_CAN_STORAGE, defaultable = TRUE)
+ if(obj_rotation_flags & OBJ_ROTATION_ENABLED)
+ if(obj_rotation_flags & OBJ_ROTATION_BIDIRECTIONAL)
+ var/image/rendered = image(src) // todo: sprite
+ .["rotate_cw"] = atom_context_tuple(
+ "Rotate Clockwise",
+ rendered,
+ 1,
+ MOBILITY_CAN_USE,
+ !!(obj_rotation_flags & OBJ_ROTATION_DEFAULTING),
+ )
+ rendered = image(src) // todo: sprite
+ .["rotate_ccw"] = atom_context_tuple(
+ "Rotate Counterclockwise",
+ rendered,
+ 1,
+ MOBILITY_CAN_USE,
+ !!(obj_rotation_flags & OBJ_ROTATION_DEFAULTING),
+ )
+ else
+ var/image/rendered = image(src) // todo: sprite
+ .["rotate_[obj_rotation_flags & OBJ_ROTATION_CCW? "ccw" : "cw"]"] = atom_context_tuple(
+ "Rotate [obj_rotation_flags & OBJ_ROTATION_CCW? "Counterclockwise" : "Clockwise"]",
+ rendered,
+ 1,
+ MOBILITY_CAN_USE,
+ !!(obj_rotation_flags & OBJ_ROTATION_DEFAULTING),
+ )
/obj/context_act(datum/event_args/actor/e_args, key)
switch(key)
@@ -563,6 +594,10 @@
return TRUE
obj_storage?.auto_handle_interacted_open(e_args)
return TRUE
+ if("rotate_cw", "rotate_ccw")
+ var/clockwise = key == "rotate_cw"
+ handle_rotation(e_args, clockwise)
+ return TRUE
return ..()
//* EMP *//
@@ -650,6 +685,9 @@
. += "Its [key] is made out of [mat.display_name]"
if((obj_persist_dynamic_id || obj_persist_static_id) && !(obj_persist_status & OBJ_PERSIST_STATUS_NO_EXAMINE))
. += SPAN_BOLDNOTICE("This entity is a persistent entity; it may be preserved into future rounds.")
+ // todo: context + construction (tool) examines at some point need a better system
+ if(obj_rotation_flags & OBJ_ROTATION_ENABLED)
+ . += SPAN_NOTICE("This entity can be rotated[(obj_rotation_flags & OBJ_ROTATION_NO_ANCHOR_CHECK)? "" : " while unanchored"] via context menu (alt click while adjacent).")
/obj/proc/examine_integrity(mob/user)
. = list()
@@ -775,9 +813,42 @@
animate(src, transform=turn(matrix(), 8*shake_dir), pixel_x=init_px + 2*shake_dir, time=1)
animate(transform=null, pixel_x=init_px, time=6, easing=ELASTIC_EASING)
+//* Rotation *//
+
+/obj/proc/allow_rotation(datum/event_args/actor/actor, clockwise, silent)
+ if(!(obj_rotation_flags & OBJ_ROTATION_NO_ANCHOR_CHECK) && anchored)
+ if(!silent)
+ actor.chat_feedback(
+ SPAN_WARNING("[src] is anchored to the ground!"),
+ target = src,
+ )
+ return FALSE
+ if(!(obj_rotation_flags & OBJ_ROTATION_BIDIRECTIONAL) && (clockwise ^ !(obj_rotation_flags & OBJ_ROTATION_CCW)))
+ if(!silent)
+ actor.chat_feedback(
+ SPAN_WARNING("[src] doesn't rotate in that direction."),
+ target = src,
+ )
+ return FALSE
+ return TRUE
+
+/obj/proc/handle_rotation(datum/event_args/actor/actor, clockwise, silent, suppressed)
+ if(!allow_rotation(actor, clockwise, silent))
+ return FALSE
+ setDir(turn(dir, clockwise? -90 : 90))
+ if(!suppressed)
+ actor.visible_feedback(
+ target = src,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = SPAN_NOTICE("[actor.performer] rotates [src]."),
+ audible = SPAN_NOTICE("You hear something being pivoted."),
+ visible_self = SPAN_NOTICE("You spin [src] [clockwise? "clockwise" : "counterclockwise"]."),
+ )
+ return TRUE
+
//* Tool System *//
-/obj/dynamic_tool_query(obj/item/I, datum/event_args/actor/clickchain/e_args, list/hint_images = list())
+/obj/dynamic_tool_query(obj/item/I, datum/event_args/actor/clickchain/e_args)
if(isnull(obj_cell_slot) || !obj_cell_slot.remove_tool_behavior || !obj_cell_slot.interaction_active(e_args.performer))
return ..()
. = list()
diff --git a/code/game/objects/random/books.dm b/code/game/objects/random/books.dm
new file mode 100644
index 000000000000..e85ef0d1ff39
--- /dev/null
+++ b/code/game/objects/random/books.dm
@@ -0,0 +1,8 @@
+/obj/random/lore_book
+ name = "random lore book"
+ desc = "This is a random book"
+ icon = 'icons/obj/library.dmi'
+ icon_state = "book"
+
+/obj/random/lore_book/item_to_spawn()
+ return pick(/obj/item/book/lore/xenomorph_castes)
diff --git a/code/game/objects/random/guns_and_ammo.dm b/code/game/objects/random/guns_and_ammo.dm
index b6e632b7f037..8e41e4311d0a 100644
--- a/code/game/objects/random/guns_and_ammo.dm
+++ b/code/game/objects/random/guns_and_ammo.dm
@@ -95,6 +95,8 @@
prob(1);/obj/item/gun/ballistic/shotgun/pump/rifle/taj,
prob(1);/obj/item/gun/ballistic/shotgun/pump/rifle/lever,
prob(1);/obj/item/gun/ballistic/shotgun/pump/rifle/lever/win1895,
+ prob(1);/obj/item/gun/ballistic/caseless/phoron_spitter,
+ prob(1);/obj/item/gun/ballistic/caseless/wild_hunt,
prob(2);/obj/item/gun/ballistic/silenced)
/obj/random/projectile/sec
@@ -188,39 +190,43 @@
/obj/random/multiple/gun/projectile/smg/item_to_spawn()
return pick(
- prob(3);list(
+ prob(6);list(
/obj/item/gun/ballistic/automatic/wt550,
/obj/item/ammo_magazine/m9mmt,
/obj/item/ammo_magazine/m9mmt
),
- prob(3);list(
+ prob(6);list(
/obj/item/gun/ballistic/automatic/wt274,
/obj/item/ammo_magazine/m45uzi/wt274,
/obj/item/ammo_magazine/m45uzi/wt274
),
- prob(1);list(
+ prob(2);list(
/obj/item/gun/ballistic/automatic/mini_uzi,
/obj/item/ammo_magazine/m45uzi,
/obj/item/ammo_magazine/m45uzi
),
- prob(1);list(
+ prob(2);list(
/obj/item/gun/ballistic/automatic/tommygun,
/obj/item/ammo_magazine/m45tommy,
/obj/item/ammo_magazine/m45tommy
),
- prob(2);list(
+ prob(4);list(
/obj/item/gun/ballistic/automatic/c20r,
/obj/item/ammo_magazine/m10mm,
/obj/item/ammo_magazine/m10mm
),
- prob(1);list(
+ prob(2);list(
/obj/item/gun/ballistic/automatic/p90,
/obj/item/ammo_magazine/m57x28mmp90
),
- prob(1);list(
+ prob(2);list(
/obj/item/gun/ballistic/automatic/mini_uzi/taj,
/obj/item/ammo_magazine/m45uzi,
/obj/item/ammo_magazine/m45uzi
+ ),
+ prob(1);list(
+ /obj/item/gun/ballistic/caseless/phoron_spitter,
+ /obj/item/ammo_magazine/mphoronshot
)
)
@@ -234,36 +240,41 @@
/obj/random/multiple/gun/projectile/rifle/item_to_spawn()
return pick(
- prob(2);list(
+ prob(4);list(
/obj/item/gun/ballistic/automatic/sts35,
/obj/item/ammo_magazine/m556,
/obj/item/ammo_magazine/m556
),
- prob(2);list(
+ prob(4);list(
/obj/item/gun/ballistic/automatic/z8,
/obj/item/ammo_magazine/m762,
/obj/item/ammo_magazine/m762
),
- prob(4);list(
+ prob(8);list(
/obj/item/gun/ballistic/shotgun/pump/rifle,
/obj/item/ammo_magazine/clip/c762,
/obj/item/ammo_magazine/clip/c762
),
- prob(1);list(
+ prob(2);list(
/obj/item/gun/ballistic/shotgun/pump/rifle/lever/win1895,
/obj/item/ammo_magazine/clip/c762,
/obj/item/ammo_magazine/clip/c762
),
- prob(1);list(
+ prob(2);list(
/obj/item/gun/ballistic/automatic/bullpup,
/obj/item/ammo_magazine/m762,
/obj/item/ammo_magazine/m762
),
- prob(4);list(
+ prob(8);list(
/obj/item/gun/ballistic/shotgun/pump/rifle/taj,
/obj/item/ammo_magazine/clip/c762,
/obj/item/ammo_magazine/clip/c762
- )
+ ),
+ prob(1);list(
+ /obj/item/gun/ballistic/caseless/wild_hunt,
+ /obj/item/ammo_magazine/mfiftycalcaseless,
+ /obj/item/ammo_magazine/mfiftycalcaseless
+ ),
)
/obj/random/multiple/gun/projectile/handgun
diff --git a/code/game/objects/random/mapping.dm b/code/game/objects/random/mapping.dm
index 02803fa76d91..a173fa671a69 100644
--- a/code/game/objects/random/mapping.dm
+++ b/code/game/objects/random/mapping.dm
@@ -569,7 +569,7 @@
prob(10);list(
/obj/item/module/power_control,
/obj/item/stack/cable_coil,
- /obj/item/frame/apc,
+ /obj/item/frame2/apc,
/obj/item/cell/high,
/obj/structure/closet/crate/corporate/focalpoint //FOCAL APC
),
diff --git a/code/game/objects/structures/crates_lockers/__closet.dm b/code/game/objects/structures/crates_lockers/__closet.dm
index 8a5aa5a5d508..ec2b2d197dd8 100644
--- a/code/game/objects/structures/crates_lockers/__closet.dm
+++ b/code/game/objects/structures/crates_lockers/__closet.dm
@@ -170,6 +170,7 @@
playsound(src, open_sound, 15, 1, -3)
if(initial(density))
density = !density
+ update_icon()
return 1
/obj/structure/closet/proc/close()
@@ -195,6 +196,7 @@
playsound(src, close_sound, 15, 1, -3)
if(initial(density))
density = !density
+ update_icon()
return 1
//Cham Projector Exception
@@ -250,7 +252,6 @@
if(!(opened ? close() : open()))
to_chat(user, "It won't budge!")
return
- update_icon()
// this should probably use dump_contents()
/obj/structure/closet/legacy_ex_act(severity)
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index cb54ee6530fd..8c70b3be3e93 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -50,6 +50,7 @@
icon_state = icon_opened
src.opened = 1
shake_climbers()
+ update_icon()
return 1
/obj/structure/closet/crate/close()
@@ -74,6 +75,7 @@
icon_state = icon_closed
src.opened = 0
+ update_icon()
return 1
/obj/structure/closet/crate/attackby(obj/item/W as obj, mob/user as mob)
@@ -351,7 +353,7 @@
//closet_appearance = /singleton/closet_appearance/crate/engineering
starts_with = list(
- /obj/item/solar_assembly = 21,
+ /obj/item/frame2/solar_panel = 2,
/obj/item/circuitboard/solar_control,
/obj/item/tracker_electronics,
/obj/item/paper/solar)
diff --git a/code/game/rendering/atom_huds/README.md b/code/game/rendering/atom_huds/README.md
new file mode 100644
index 000000000000..48aaf2b62d30
--- /dev/null
+++ b/code/game/rendering/atom_huds/README.md
@@ -0,0 +1,13 @@
+# atom hud system
+
+atom HUDs are ultimately a way to attach /image's to specific /atom's, and manage showing/hiding them from consumers.
+
+atom HUDs consist of 2 parts
+
+## /datum/atom_hud_provider
+
+provides icons to atom huds. tracks and processes updates.
+
+## /datum/atom_hud
+
+this is a hud that draws from a group of providers to provide those images to **perspectives** that use it.
diff --git a/code/game/rendering/atom_huds/alternate_appearance.dm b/code/game/rendering/atom_huds/alternate_appearance.dm
deleted file mode 100644
index c2232b760757..000000000000
--- a/code/game/rendering/atom_huds/alternate_appearance.dm
+++ /dev/null
@@ -1,190 +0,0 @@
-GLOBAL_LIST_EMPTY(active_alternate_appearances)
-
-
-/atom/var/list/alternate_appearances
-
-/atom/proc/remove_alt_appearance(key)
- if(alternate_appearances)
- for(var/K in alternate_appearances)
- var/datum/atom_hud/alternate_appearance/AA = alternate_appearances[K]
- if(AA.appearance_key == key)
- AA.remove_from_hud(src)
- break
-
-/atom/proc/add_alt_appearance(type, key, ...)
- if(!type || !key)
- return
- if(alternate_appearances && alternate_appearances[key])
- return
- var/list/arguments = args.Copy(2)
- new type(arglist(arguments))
-
-/datum/atom_hud/alternate_appearance
- var/appearance_key
- var/transfer_overlays = FALSE
-
-/datum/atom_hud/alternate_appearance/New(key)
- ..()
- GLOB.active_alternate_appearances += src
- appearance_key = key
-
-/datum/atom_hud/alternate_appearance/Destroy()
- GLOB.active_alternate_appearances -= src
- return ..()
-
-/datum/atom_hud/alternate_appearance/proc/onNewMob(mob/M)
- if(mobShouldSee(M))
- add_hud_to(M)
-
-/datum/atom_hud/alternate_appearance/proc/mobShouldSee(mob/M)
- return FALSE
-
-/datum/atom_hud/alternate_appearance/add_to_hud(atom/A, image/I)
- . = ..()
- if(.)
- LAZYINITLIST(A.alternate_appearances)
- A.alternate_appearances[appearance_key] = src
-
-/datum/atom_hud/alternate_appearance/remove_from_hud(atom/A)
- . = ..()
- if(.)
- LAZYREMOVE(A.alternate_appearances, appearance_key)
-
-/datum/atom_hud/alternate_appearance/proc/copy_overlays(atom/other, cut_old)
- return
-
-//an alternate appearance that attaches a single image to a single atom
-/datum/atom_hud/alternate_appearance/basic
- var/atom/target
- var/image/theImage
- var/add_ghost_version = FALSE
- var/ghost_appearance
-
-/datum/atom_hud/alternate_appearance/basic/New(key, image/I, options = AA_TARGET_SEE_APPEARANCE)
- ..()
- transfer_overlays = options & AA_MATCH_TARGET_OVERLAYS
- theImage = I
- target = I.loc
- if(transfer_overlays)
- I.copy_overlays(target)
- hud_icons = list(appearance_key)
- add_to_hud(target, I)
- if((options & AA_TARGET_SEE_APPEARANCE) && ismob(target))
- add_hud_to(target)
- if(add_ghost_version)
- var/image/ghost_image = image(icon = I.icon , icon_state = I.icon_state, loc = I.loc)
- ghost_image.override = FALSE
- ghost_image.alpha = 128
- ghost_appearance = new /datum/atom_hud/alternate_appearance/basic/observers(key + "_observer", ghost_image, FALSE)
-
-/datum/atom_hud/alternate_appearance/basic/Destroy()
- . = ..()
- if(ghost_appearance)
- QDEL_NULL(ghost_appearance)
-
-/datum/atom_hud/alternate_appearance/basic/add_to_hud(atom/A)
- LAZYINITLIST(A.hud_list)
- A.hud_list[appearance_key] = theImage
- . = ..()
-
-/datum/atom_hud/alternate_appearance/basic/remove_from_hud(atom/A)
- . = ..()
- A.hud_list -= appearance_key
- if(. && !QDELETED(src))
- qdel(src)
-
-/datum/atom_hud/alternate_appearance/basic/copy_overlays(atom/other, cut_old)
- theImage.copy_overlays(other, cut_old)
-
-/datum/atom_hud/alternate_appearance/basic/everyone
- add_ghost_version = TRUE
-
-/datum/atom_hud/alternate_appearance/basic/everyone/New()
- ..()
- for(var/mob in GLOB.mob_list)
- if(mobShouldSee(mob))
- add_hud_to(mob)
-
-/datum/atom_hud/alternate_appearance/basic/everyone/mobShouldSee(mob/M)
- return !isobserver(M)
-
-/datum/atom_hud/alternate_appearance/basic/silicons
-
-/datum/atom_hud/alternate_appearance/basic/silicons/New()
- ..()
- for(var/mob in silicon_mob_list)
- if(mobShouldSee(mob))
- add_hud_to(mob)
-
-/datum/atom_hud/alternate_appearance/basic/silicons/mobShouldSee(mob/M)
- if(issilicon(M))
- return TRUE
- return FALSE
-
-/datum/atom_hud/alternate_appearance/basic/observers
- add_ghost_version = FALSE //just in case, to prevent infinite loops
-
-/datum/atom_hud/alternate_appearance/basic/observers/New()
- ..()
- for(var/mob in dead_mob_list)
- if(mobShouldSee(mob))
- add_hud_to(mob)
-
-/datum/atom_hud/alternate_appearance/basic/observers/mobShouldSee(mob/M)
- return isobserver(M)
-
-/datum/atom_hud/alternate_appearance/basic/noncult
-
-/datum/atom_hud/alternate_appearance/basic/noncult/New()
- ..()
- for(var/mob in GLOB.player_list)
- if(mobShouldSee(mob))
- add_hud_to(mob)
-
-/datum/atom_hud/alternate_appearance/basic/noncult/mobShouldSee(mob/M)
- if(!iscultist(M))
- return TRUE
- return FALSE
-
-/datum/atom_hud/alternate_appearance/basic/cult
-
-/datum/atom_hud/alternate_appearance/basic/cult/New()
- ..()
- for(var/mob in GLOB.player_list)
- if(mobShouldSee(mob))
- add_hud_to(mob)
-
-/datum/atom_hud/alternate_appearance/basic/cult/mobShouldSee(mob/M)
- if(iscultist(M))
- return TRUE
- return FALSE
-
-/datum/atom_hud/alternate_appearance/basic/blessedAware
-
-/datum/atom_hud/alternate_appearance/basic/blessedAware/New()
- ..()
- for(var/mob in GLOB.mob_list)
- if(mobShouldSee(mob))
- add_hud_to(mob)
-
-/datum/atom_hud/alternate_appearance/basic/blessedAware/mobShouldSee(mob/M)
- if(M.mind && (M.mind.assigned_role == "Chaplain"))
- return TRUE
- // if (istype(M, /mob/living/simple_animal/hostile/construct/wraith))
- // return TRUE
- // if(isrevenant(M) || iseminence(M) || iswizard(M))
- // return TRUE
- return FALSE
-
-/datum/atom_hud/alternate_appearance/basic/onePerson
- var/mob/seer
-
-/datum/atom_hud/alternate_appearance/basic/onePerson/mobShouldSee(mob/M)
- if(M == seer)
- return TRUE
- return FALSE
-
-/datum/atom_hud/alternate_appearance/basic/onePerson/New(key, image/I, mob/living/M)
- ..(key, I, FALSE)
- seer = M
- add_hud_to(seer)
diff --git a/code/game/rendering/atom_huds/atom_hud.dm b/code/game/rendering/atom_huds/atom_hud.dm
index 2a99d4da7794..3afd5ffe95f0 100644
--- a/code/game/rendering/atom_huds/atom_hud.dm
+++ b/code/game/rendering/atom_huds/atom_hud.dm
@@ -1,114 +1,107 @@
-/* HUD DATUMS */
-
-GLOBAL_LIST_EMPTY(all_huds)
-
-//GLOBAL HUD LIST
-GLOBAL_LIST_INIT(huds, list(
- DATA_HUD_SECURITY_BASIC = new /datum/atom_hud/data/human/security/basic,
- DATA_HUD_SECURITY_ADVANCED = new /datum/atom_hud/data/human/security/advanced,
- DATA_HUD_MEDICAL = new /datum/atom_hud/data/human/medical,
- DATA_HUD_ID_JOB = new /datum/atom_hud/data/human/job_id,
- HUD_ANTAG = new /datum/atom_hud/antag,
- WORLD_BENDER_HUD_ANIMALS = new /datum/atom_hud/world_bender/animals,
- ))
-
-/proc/get_atom_hud(id)
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+GLOBAL_LIST_INIT(atom_huds, initialize_atom_huds())
+
+/proc/initialize_atom_huds()
+ . = list()
+ for(var/datum/atom_hud/hud_type as anything in subtypesof(/datum/atom_hud))
+ if(initial(hud_type.abstract_type) == hud_type)
+ continue
+ var/datum/atom_hud/hud = new hud_type
+ .[hud_type] = hud
+ if(hud.id != hud_type)
+ ASSERT(!.[hud.id])
+ .[hud.id] = hud
+
+/**
+ * instantiate a custom hud
+ */
+/proc/initialize_atom_hud(path, list/hud_providers, id)
+ ASSERT(!GLOB.atom_huds[id])
+ var/datum/atom_hud/hud = new path(id, hud_providers)
+ GLOB.atom_huds[id] = hud
+
+/proc/fetch_atom_hud(datum/atom_hud/hudlike)
RETURN_TYPE(/datum/atom_hud)
- return GLOB.huds[id]
-
-// TODO: atom_huds on mob with hud sources
-// TODO: /datum/hud_supplier for image metadata/hud list metadata
-// TODO: atom huds using hud supplier id lists, more id usage in general for dynamic gen
+ if(istype(hudlike))
+ return hudlike
+ return GLOB.atom_huds[hudlike]
/datum/atom_hud
- var/list/atom/hudatoms = list() //list of all atoms which display this hud
- var/list/hudusers = list() //list with all mobs who can see the hud
- var/list/hud_icons = list() //these will be the indexes for the atom's hud_list
-
- var/list/next_time_allowed = list() //mobs associated with the next time this hud can be added to them
- var/list/queued_to_see = list() //mobs that have triggered the cooldown and are queued to see the hud, but do not yet
-
-/datum/atom_hud/New()
- GLOB.all_huds += src
-
-/datum/atom_hud/Destroy()
- for(var/v in hudusers)
- remove_hud_from(v)
- for(var/v in hudatoms)
- remove_from_hud(v)
- GLOB.all_huds -= src
- return ..()
-
-/datum/atom_hud/proc/remove_hud_from(mob/M)
- if(!M || !hudusers[M])
- return
- if (!--hudusers[M])
- hudusers -= M
- if(queued_to_see[M])
- queued_to_see -= M
- else
- for(var/atom/A in hudatoms)
- remove_from_single_hud(M, A)
-
-/datum/atom_hud/proc/remove_from_hud(atom/A)
- if(!A)
- return FALSE
- for(var/mob/M in hudusers)
- remove_from_single_hud(M, A)
- hudatoms -= A
- return TRUE
-
-/datum/atom_hud/proc/remove_from_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client
- if(!M || !M.client || !A)
- return
- for(var/i in hud_icons)
- M.client.images -= A.hud_list[i]
-
-/datum/atom_hud/proc/add_hud_to(mob/M)
- if(!M)
- return
- if(!hudusers[M])
- hudusers[M] = 1
- if(next_time_allowed[M] > world.time)
- if(!queued_to_see[M])
- addtimer(CALLBACK(src, PROC_REF(show_hud_images_after_cooldown), M), next_time_allowed[M] - world.time)
- queued_to_see[M] = TRUE
- else
- next_time_allowed[M] = world.time + ADD_HUD_TO_COOLDOWN
- for(var/atom/A in hudatoms)
- add_to_single_hud(M, A)
- else
- hudusers[M]++
-
-/datum/atom_hud/proc/show_hud_images_after_cooldown(M)
- if(queued_to_see[M])
- queued_to_see -= M
- next_time_allowed[M] = world.time + ADD_HUD_TO_COOLDOWN
- for(var/atom/A in hudatoms)
- add_to_single_hud(M, A)
-
-/datum/atom_hud/proc/add_to_hud(atom/A)
- if(!A)
- return FALSE
- hudatoms |= A
- for(var/mob/M in hudusers)
- if(!queued_to_see[M])
- add_to_single_hud(M, A)
- return TRUE
-
-/datum/atom_hud/proc/add_to_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client
- if(!M || !M.client || !A)
+ /// id; if exists, we register with id too
+ var/id
+ /// list of typepaths or ids of providers
+ var/list/providers = list()
+
+ /// DO NOT CHANGE THIS VALUE IN RUNTIME!
+ /// If set, we will automatically grant ourselves to mob's self-perspectives
+ /// if necessary.
+ var/auto_registration = FALSE
+
+/datum/atom_hud/New(id, list/hud_providers)
+ if(!isnull(id))
+ src.id = id
+ ASSERT(src.id)
+ else if(isnull(src.id))
+ src.id = type
+ if(!isnull(hud_providers))
+ src.providers = hud_providers
+ if(auto_registration)
+ RegisterGlobalSignal(COMSIG_GLOBAL_MOB_NEW, PROC_REF(on_global_mob_new))
+
+/datum/atom_hud/proc/resolve_providers()
+ . = list()
+ for(var/id in providers)
+ . += GLOB.atom_hud_providers[id]
+
+/datum/atom_hud/proc/on_global_mob_new(mob/source)
+ if(!should_auto_register_on(source))
return
- for(var/i in hud_icons)
- if(A.hud_list[i])
- M.client.images |= A.hud_list[i]
-
-//MOB PROCS
-/mob/proc/reload_huds()
- for(var/datum/atom_hud/hud in GLOB.all_huds)
- if(hud && hud.hudusers[src])
- for(var/atom/A in hud.hudatoms)
- hud.add_to_single_hud(src, A)
-
-/mob/new_player/reload_huds()
- return
+ source.ensure_self_perspective()
+ source.self_perspective.add_atom_hud(src, ATOM_HUD_SOURCE_AUTOGRANT)
+
+/**
+ * if [auto_registration] is set, this is called to determine if we should
+ * be on a mob's self_perspective.
+ */
+/datum/atom_hud/proc/should_auto_register_on(mob/target)
+ return FALSE
+
+//* Implementations - split off into their other files later. *//
+
+/datum/atom_hud/data/human/medical
+ providers = list(
+ /datum/atom_hud_provider/medical_biology,
+ /datum/atom_hud_provider/medical_health,
+ )
+
+/datum/atom_hud/data/human/job_id
+ providers = list(
+ /datum/atom_hud_provider/security_job,
+ )
+
+/datum/atom_hud/data/human/security
+
+/datum/atom_hud/data/human/security/basic
+ providers = list(
+ /datum/atom_hud_provider/security_job,
+ /datum/atom_hud_provider/security_status,
+ )
+
+/datum/atom_hud/data/human/security/advanced
+ providers = list(
+ /datum/atom_hud_provider/security_job,
+ /datum/atom_hud_provider/security_status,
+ /datum/atom_hud_provider/security_implant,
+ )
+
+/datum/atom_hud/antag
+ providers = list(
+ /datum/atom_hud_provider/special_role,
+ )
+
+/datum/atom_hud/world_bender
+ providers = list(
+ /datum/atom_hud_provider/overriding/world_bender_animals,
+ )
diff --git a/code/game/rendering/atom_huds/atom_hud_provider.dm b/code/game/rendering/atom_huds/atom_hud_provider.dm
new file mode 100644
index 000000000000..c32ed4e3511d
--- /dev/null
+++ b/code/game/rendering/atom_huds/atom_hud_provider.dm
@@ -0,0 +1,290 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+GLOBAL_LIST_INIT(atom_hud_providers, initialize_atom_hud_providers())
+
+/proc/initialize_atom_hud_providers()
+ . = list()
+ for(var/datum/atom_hud_provider/provider_type as anything in subtypesof(/datum/atom_hud_provider))
+ if(initial(provider_type.abstract_type) == provider_type)
+ continue
+ var/datum/atom_hud_provider/provider = new provider_type
+ if(.[provider.id])
+ stack_trace("dupe id [provider.id] between [.[provider.id]:type] and [provider_type]")
+ continue
+ .[provider.id] = provider
+
+/**
+ * instantiate a custom hud provider
+ */
+/proc/initialize_atom_hud_provider(path, icon/icon, id)
+ ASSERT(!GLOB.atom_hud_providers[id])
+ var/datum/atom_hud_provider/provider = new path
+ provider.icon = icon
+ provider.id = id
+ GLOB.atom_hud_providers[id] = provider
+
+/proc/remove_atom_hud_provider(atom/A, hud_path)
+ var/datum/atom_hud_provider/provider = GLOB.atom_hud_providers[hud_path]
+ provider.queue_remove(A)
+
+/proc/add_atom_hud_provider(atom/A, hud_path)
+ return update_atom_hud_provider(A, hud_path)
+
+/proc/update_atom_hud_provider(atom/A, hud_path)
+ var/datum/atom_hud_provider/provider = GLOB.atom_hud_providers[hud_path]
+ provider.queue_add_or_update(A)
+
+#define ATOM_HUD_QUEUED_FOR_UPDATE "update"
+#define ATOM_HUD_QUEUED_FOR_REMOVE "remove"
+
+/datum/atom_hud_provider
+ /// id; if exists, we register with ID too
+ var/id
+ /// our icon file
+ var/icon
+ /// layer bias: higher is above. this should not be above 100.
+ var/layer_bias = 0
+
+ /// atoms with us on it
+ var/list/atom/atoms = list()
+ /// images - this is so things are fast when adding/remove people from/to the hud.
+ var/list/image/images = list()
+ /// perspectives with this provider enabled
+ var/list/datum/perspective/using_perspectives = list()
+ /// clients with this provider enabled
+ var/list/client/using_clients = list()
+
+ /// queued to update
+ var/list/atom/queued_for_update = list()
+ /// is an update queued?
+ var/update_queued = FALSE
+
+/datum/atom_hud_provider/New()
+ if(isnull(id))
+ id = type
+ ASSERT(layer_bias <= 100 && layer_bias >= 0)
+
+/datum/atom_hud_provider/proc/add_perspective(datum/perspective/perspective)
+ using_perspectives += perspective
+ perspective.add_image(images)
+
+/datum/atom_hud_provider/proc/remove_perspective(datum/perspective/perspective)
+ using_perspectives -= perspective
+ perspective.remove_image(images)
+
+/datum/atom_hud_provider/proc/add_client(client/user)
+ using_clients += user
+ user.images += images
+
+/datum/atom_hud_provider/proc/remove_client(client/user)
+ using_clients -= user
+ user.images -= images
+
+/datum/atom_hud_provider/proc/remove(atom/A)
+ var/image/hud_image = A.atom_huds[type]
+ images -= hud_image
+ atoms -= A
+ for(var/datum/perspective/perspective as anything in using_perspectives)
+ perspective.remove_image(hud_image)
+ for(var/client/user as anything in using_clients)
+ user.images -= hud_image
+
+/datum/atom_hud_provider/proc/add_or_update(atom/A)
+ LAZYINITLIST(A.atom_huds)
+ atoms += A
+ var/image/hud_image = A.atom_huds[type]
+ if(isnull(hud_image))
+ A.atom_huds[type] = hud_image = create_image(A)
+ images += hud_image
+ for(var/datum/perspective/perspective as anything in using_perspectives)
+ perspective.add_image(hud_image)
+ for(var/client/user as anything in using_clients)
+ user.images += hud_image
+ update(A, hud_image)
+
+/datum/atom_hud_provider/proc/update(atom/A, image/plate)
+ return
+
+/datum/atom_hud_provider/proc/queue_remove(atom/A)
+ if(!update_queued)
+ queue_update()
+ queued_for_update[A] = ATOM_HUD_QUEUED_FOR_REMOVE
+
+/datum/atom_hud_provider/proc/queue_add_or_update(atom/A)
+ if(!update_queued)
+ queue_update()
+ LAZYINITLIST(A.atom_huds)
+ var/image/hud_image = A.atom_huds[type]
+ // todo: should we queue the add operations instead of duping them..?
+ if(isnull(hud_image))
+ A.atom_huds[type] = hud_image = create_image(A)
+ images += hud_image
+ for(var/datum/perspective/perspective as anything in using_perspectives)
+ perspective.add_image(hud_image)
+ queued_for_update[A] = ATOM_HUD_QUEUED_FOR_UPDATE
+
+/datum/atom_hud_provider/proc/queue_update()
+ update_queued = TRUE
+ addtimer(CALLBACK(src, PROC_REF(process_queue)), 0)
+
+/datum/atom_hud_provider/proc/process_queue()
+ for(var/atom/A as anything in queued_for_update)
+ var/opcode = queued_for_update[A]
+ switch(opcode)
+ if(ATOM_HUD_QUEUED_FOR_UPDATE)
+ update(A, A.atom_huds[type])
+ if(ATOM_HUD_QUEUED_FOR_REMOVE)
+ remove(A)
+ update_queued = FALSE
+ queued_for_update = list()
+
+/datum/atom_hud_provider/proc/create_image(atom/target)
+ var/image/creating = image(icon, target, "")
+ creating.layer = FLOAT_LAYER + 100 + layer_bias
+ creating.plane = FLOAT_PLANE
+ creating.appearance_flags = RESET_COLOR | RESET_TRANSFORM | KEEP_APART
+ return creating
+
+/**
+ * sets up image with override = TRUE
+ */
+/datum/atom_hud_provider/overriding
+ abstract_type = /datum/atom_hud_provider/overriding
+
+/datum/atom_hud_provider/overriding/create_image(atom/target)
+ var/image/creating = ..()
+ creating.override = TRUE
+ return creating
+
+#undef ATOM_HUD_QUEUED_FOR_UPDATE
+#undef ATOM_HUD_QUEUED_FOR_REMOVE
+
+//* Implementations - split off into their other files later. *//
+
+/datum/atom_hud_provider/security_status
+ icon = 'icons/screen/atom_hud/security.dmi'
+
+/datum/atom_hud_provider/security_status/update(atom/A, image/plate)
+ if(!ishuman(A))
+ return
+ var/mob/living/carbon/human/H = A
+ var/image/holder = plate
+ holder.icon_state = ""
+ var/perpname = H.name
+ if( H.wear_id)
+ var/obj/item/card/id/I = H.wear_id.GetID()
+ if(I)
+ perpname = I.registered_name
+
+ for(var/datum/data/record/E in data_core.general)
+ if(E.fields["name"] == perpname)
+ for (var/datum/data/record/R in data_core.security)
+ if((R.fields["id"] == E.fields["id"]) && (R.fields["criminal"] == "*Arrest*"))
+ holder.icon_state = "hudwanted"
+ break
+ else if((R.fields["id"] == E.fields["id"]) && (R.fields["criminal"] == "Incarcerated"))
+ holder.icon_state = "hudincarcerated"
+ break
+ else if((R.fields["id"] == E.fields["id"]) && (R.fields["criminal"] == "Parolled"))
+ holder.icon_state = "hudparolled"
+ break
+ else if((R.fields["id"] == E.fields["id"]) && (R.fields["criminal"] == "Released"))
+ holder.icon_state = "huddischarged"
+ break
+
+/datum/atom_hud_provider/security_job
+ icon = 'icons/screen/atom_hud/job.dmi'
+
+/datum/atom_hud_provider/security_job/update(atom/A, image/plate)
+ if(!ishuman(A))
+ return
+ var/mob/living/carbon/human/H = A
+ var/image/holder = plate
+ if(H.wear_id)
+ var/obj/item/card/id/I = H.wear_id.GetID()
+ if(I)
+ holder.icon_state = "[ckey(I.GetJobName())]" // ckey() serves as a formating help, not actually related to the ckey of the mob
+ else
+ holder.icon_state = "unknown"
+ else
+ holder.icon_state = "unknown"
+
+/datum/atom_hud_provider/medical_biology
+ icon = 'icons/screen/atom_hud/biology.dmi'
+
+/datum/atom_hud_provider/medical_biology/update(atom/A, image/plate)
+ if(!isliving(A))
+ return
+ var/mob/living/L = A
+ var/image/holder = plate
+ var/foundVirus = L.check_viruses()
+ if(L.isSynthetic())
+ holder.icon_state = "robo"
+ else if(L.stat == DEAD)
+ holder.icon_state = "dead"
+ else if(foundVirus)
+ holder.icon_state = "ill1"
+ else if(L.has_brain_worms())
+ var/mob/living/simple_mob/animal/borer/B = L.has_brain_worms()
+ holder.icon_state = B.controlling? "brainworm" : "healthy"
+ else
+ holder.icon_state = "healthy"
+
+/datum/atom_hud_provider/medical_health
+ icon = 'icons/screen/atom_hud/health.dmi'
+
+/datum/atom_hud_provider/medical_health/update(atom/A, image/plate)
+ if(!isliving(A))
+ return
+ var/mob/living/M = A
+ var/image/I = plate
+ if(M.stat == DEAD)
+ I.icon_state = "-100"
+ else
+ I.icon_state = RoundHealth((M.health-config_legacy.health_threshold_crit)/(M.getMaxHealth()-config_legacy.health_threshold_crit)*100)
+
+/datum/atom_hud_provider/security_implant
+ icon = 'icons/screen/atom_hud/implant.dmi'
+
+/datum/atom_hud_provider/security_implant/update(atom/A, image/plate)
+ if(!ismob(A))
+ return
+ var/mob/M = A
+ plate.overlays = list()
+ for(var/obj/item/implant/I in M)
+ if(!I.implanted)
+ continue
+ if(I.malfunction)
+ continue
+ if(istype(I, /obj/item/implant/tracking))
+ plate.overlays |= "tracking"
+ if(istype(I, /obj/item/implant/loyalty))
+ plate.overlays |= "loyal"
+ if(istype(I, /obj/item/implant/chem))
+ plate.overlays |= "chem"
+
+/datum/atom_hud_provider/special_role
+ icon = 'icons/screen/atom_hud/antag.dmi'
+
+/datum/atom_hud_provider/special_role/update(atom/A, image/plate)
+ if(!ismob(A))
+ return
+ var/mob/M = A
+ var/image/holder = plate
+ holder.icon_state = ""
+ if(M.mind?.special_role)
+ // ANTAG DATUM REFACTOR WHEN AUHGAOUSHGODHGHOAD
+ if(hud_icon_reference[M.mind.special_role])
+ holder.icon_state = hud_icon_reference[M.mind.special_role]
+ else
+ holder.icon_state = "syndicate"
+
+/datum/atom_hud_provider/overriding/world_bender_animals
+ icon = 'icons/mob/animal.dmi'
+
+/datum/atom_hud_provider/overriding/world_bender_animals/create_image(atom/target)
+ var/image/creating = ..()
+ var/animal = pick("cow","chicken_brown", "chicken_black", "chicken_white", "chick", "mouse_brown", "mouse_gray", "mouse_white", "lizard", "cat2", "goose", "penguin")
+ creating.icon_state = animal
+ return creating
diff --git a/code/game/rendering/atom_huds/data_huds.dm b/code/game/rendering/atom_huds/data_huds.dm
deleted file mode 100644
index f4555ee4e394..000000000000
--- a/code/game/rendering/atom_huds/data_huds.dm
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Data HUDs have been rewritten in a more generic way.
- * In short, they now use an observer-listener pattern.
- * See code/datum/hud.dm for the generic hud datum.
- * Update the HUD icons when needed with the appropriate hook. (see below)
- */
-
-/* DATA HUD DATUMS */
-
-/atom/proc/add_to_all_human_data_huds()
- for(var/datum/atom_hud/data/human/hud in GLOB.huds)
- hud.add_to_hud(src)
-
-/atom/proc/remove_from_all_data_huds()
- for(var/datum/atom_hud/data/hud in GLOB.huds)
- hud.remove_from_hud(src)
-
-/datum/atom_hud/data
-
-/datum/atom_hud/data/human/medical
- hud_icons = list(BIOLOGY_HUD, LIFE_HUD)
-
-/datum/atom_hud/data/human/job_id
- hud_icons = list(ID_HUD)
-
-/datum/atom_hud/data/human/security
-
-/datum/atom_hud/data/human/security/basic
- hud_icons = list(ID_HUD, WANTED_HUD)
-
-/datum/atom_hud/data/human/security/advanced
- hud_icons = list(ID_HUD, IMPTRACK_HUD, IMPLOYAL_HUD, IMPCHEM_HUD, WANTED_HUD)
-
-/datum/atom_hud/antag
- hud_icons = list(ANTAG_HUD)
-
-// one of these days we'll make /datum/hud_supplier
-// and we can standardize everything
-// today is not that day for i have no patience or motivation left
-
-/mob/proc/update_hud_med_health()
- return
-
-/mob/living/update_hud_med_health()
- . = ..()
- var/image/I = hud_list[LIFE_HUD]
- if(!I)
- return
- if(stat == DEAD)
- I.icon_state = "-100"
- else
- I.icon_state = RoundHealth((health-config_legacy.health_threshold_crit)/(getMaxHealth()-config_legacy.health_threshold_crit)*100)
-
-/mob/proc/update_hud_med_status()
- var/image/holder = hud_list[BIOLOGY_HUD]
- if(!holder)
- return
- var/foundVirus = check_viruses()
- if(isSynthetic())
- holder.icon_state = "robo"
- else if(stat == DEAD)
- holder.icon_state = "dead"
- else if(foundVirus)
- holder.icon_state = "ill1"
- else if(has_brain_worms())
- var/mob/living/simple_mob/animal/borer/B = has_brain_worms()
- holder.icon_state = B.controlling? "brainworm" : "healthy"
- else
- holder.icon_state = "healthy"
-
-/mob/proc/update_hud_med_all()
- update_hud_med_health()
- update_hud_med_status()
-
-/mob/proc/update_hud_sec_implants()
- var/image/Itrack = hud_list[IMPTRACK_HUD]
- var/image/Ichem = hud_list[IMPCHEM_HUD]
- var/image/Iloyal = hud_list[IMPLOYAL_HUD]
- Itrack?.icon_state = ""
- Ichem?.icon_state = ""
- Iloyal?.icon_state = ""
- for(var/obj/item/implant/I in src)
- if(!I.implanted)
- continue
- if(I.malfunction)
- continue
- if(istype(I, /obj/item/implant/tracking))
- Itrack?.icon_state = "tracking"
- if(istype(I, /obj/item/implant/loyalty))
- Iloyal?.icon_state = "loyal"
- if(istype(I, /obj/item/implant/chem))
- Ichem?.icon_state = "chem"
-
-/mob/proc/update_hud_sec_job()
- return
-
-/mob/living/carbon/human/update_hud_sec_job()
- . = ..()
- var/image/holder = hud_list[ID_HUD]
- if(!holder)
- return
- if(wear_id)
- var/obj/item/card/id/I = wear_id.GetID()
- if(I)
- holder.icon_state = "[ckey(I.GetJobName())]"
- else
- holder.icon_state = "unknown"
- else
- holder.icon_state = "unknown"
-
-/mob/proc/update_hud_sec_status()
- var/image/holder = hud_list[WANTED_HUD]
- holder?.icon_state = ""
-
-/mob/living/carbon/human/update_hud_sec_status()
- var/image/holder = hud_list[WANTED_HUD]
- if(!holder)
- return
- holder.icon_state = ""
- var/perpname = name
- if(wear_id)
- var/obj/item/card/id/I = wear_id.GetID()
- if(I)
- perpname = I.registered_name
-
- for(var/datum/data/record/E in data_core.general)
- if(E.fields["name"] == perpname)
- for (var/datum/data/record/R in data_core.security)
- if((R.fields["id"] == E.fields["id"]) && (R.fields["criminal"] == "*Arrest*"))
- holder.icon_state = "hudwanted"
- break
- else if((R.fields["id"] == E.fields["id"]) && (R.fields["criminal"] == "Incarcerated"))
- holder.icon_state = "hudincarcerated"
- break
- else if((R.fields["id"] == E.fields["id"]) && (R.fields["criminal"] == "Parolled"))
- holder.icon_state = "hudparolled"
- break
- else if((R.fields["id"] == E.fields["id"]) && (R.fields["criminal"] == "Released"))
- holder.icon_state = "huddischarged"
- break
-
-/mob/proc/update_hud_antag()
- var/image/holder = hud_list[ANTAG_HUD]
- if(!holder)
- return
- holder.icon_state = ""
- if(mind?.special_role)
- // ANTAG DATUM REFACTOR WHEN AUHGAOUSHGODHGHOAD
- if(hud_icon_reference[mind.special_role])
- holder.icon_state = hud_icon_reference[mind.special_role]
- else
- holder.icon_state = "syndicate"
-
-/proc/RoundHealth(health, icon = GLOB.hud_icon_files[LIFE_HUD])
- var/list/icon_states = icon_states(icon)
- for(var/icon_state in icon_states)
- if(health >= text2num(icon_state))
- return icon_state
- return icon_states[icon_states.len] // If we had no match, return the last element
-
-/mob/proc/check_viruses()
- return FALSE
-
-/mob/living/carbon/human/check_viruses()
- return !!length(virus2 & virusDB)
diff --git a/code/game/rendering/atom_huds/legacy.dm b/code/game/rendering/atom_huds/legacy.dm
new file mode 100644
index 000000000000..daf2f2e71581
--- /dev/null
+++ b/code/game/rendering/atom_huds/legacy.dm
@@ -0,0 +1,43 @@
+/*
+ * Data HUDs have been rewritten in a more generic way.
+ * In short, they now use an observer-listener pattern.
+ * See code/datum/hud.dm for the generic hud datum.
+ * Update the HUD icons when needed with the appropriate hook. (see below)
+ */
+
+/* DATA HUD DATUMS */
+
+/mob/proc/update_hud_med_health()
+ update_atom_hud_provider(src, /datum/atom_hud_provider/medical_health)
+
+/mob/proc/update_hud_med_status()
+ update_atom_hud_provider(src, /datum/atom_hud_provider/medical_biology)
+
+/mob/proc/update_hud_med_all()
+ update_hud_med_health()
+ update_hud_med_status()
+
+/mob/proc/update_hud_sec_implants()
+ update_atom_hud_provider(src, /datum/atom_hud_provider/security_implant)
+
+/mob/proc/update_hud_sec_job()
+ update_atom_hud_provider(src, /datum/atom_hud_provider/security_job)
+
+/mob/proc/update_hud_sec_status()
+ update_atom_hud_provider(src, /datum/atom_hud_provider/security_status)
+
+/mob/proc/update_hud_antag()
+ update_atom_hud_provider(src, /datum/atom_hud_provider/special_role)
+
+/proc/RoundHealth(health, icon = 'icons/screen/atom_hud/health.dmi')
+ var/list/icon_states = icon_states(icon)
+ for(var/icon_state in icon_states)
+ if(health >= text2num(icon_state))
+ return icon_state
+ return icon_states[icon_states.len] // If we had no match, return the last element
+
+/mob/proc/check_viruses()
+ return FALSE
+
+/mob/living/carbon/human/check_viruses()
+ return !!length(virus2 & virusDB)
diff --git a/code/game/rendering/atom_huds/other_huds.dm b/code/game/rendering/atom_huds/other_huds.dm
deleted file mode 100644
index 3f41e5909b84..000000000000
--- a/code/game/rendering/atom_huds/other_huds.dm
+++ /dev/null
@@ -1,2 +0,0 @@
-/datum/atom_hud/world_bender/animals
- hud_icons = list(WORLD_BENDER_ANIMAL_HUD)
diff --git a/code/game/rendering/perspectives/README.md b/code/game/rendering/perspectives/README.md
new file mode 100644
index 000000000000..32eb117a985c
--- /dev/null
+++ b/code/game/rendering/perspectives/README.md
@@ -0,0 +1,9 @@
+# Perspectives
+
+The Perspectives system is used to encapsulate graphics, visuals, and rendering 'experienced' by an entity.
+
+This way, shifting people's view around is easier and more standardized.
+
+As an example, something like a helmet camera has its own perspective because the perspective of the mob wearing it is what the mob's player sees, and not what someone merely connected to a helmet would see.
+
+Meanwhile, an observer viewing a mob's perspective can see the world through their eyes, literally.
diff --git a/code/game/rendering/perspectives/perspective.dm b/code/game/rendering/perspectives/perspective.dm
index 8d2291356e52..a7a18f0020aa 100644
--- a/code/game/rendering/perspectives/perspective.dm
+++ b/code/game/rendering/perspectives/perspective.dm
@@ -69,6 +69,10 @@
/// see_invisible
var/see_invisible = SEE_INVISIBLE_LIVING
+ //* HUDs *//
+ /// active atom HUD providers associated to a list of ids or paths of atom huds that's providing it.
+ var/list/datum/atom_hud_provider/atom_hud_providers
+
//? planes
/// planes
var/datum/plane_holder/mob_perspective/planes
@@ -124,6 +128,7 @@
/datum/perspective/Destroy()
clear_clients()
clear_mobs()
+ clear_atom_hud_providers()
QDEL_NULL(darksight_fov_overlay)
QDEL_NULL(darksight_occlusion_overlay)
images = null
@@ -285,7 +290,6 @@
if(!change)
images = list()
images |= I
- change = images.len - change
if(images.len != change)
for(var/client/C as anything in clients)
// |=, not +=, because we don't check dupes.
@@ -370,6 +374,77 @@
for(var/mob/M as anything in mobs)
M.see_in_dark = clamp(see_in_dark, 0, 255)
+//* Atom HUDs *//
+
+/**
+ * accepts:
+ * * path
+ * * string id
+ * * instance
+ *
+ * currently will runtime if it's not a valid hud!
+ */
+/datum/perspective/proc/add_atom_hud(datum/atom_hud/hud, source)
+ ASSERT(istext(source))
+ hud = fetch_atom_hud(hud)
+ if(isnull(atom_hud_providers))
+ atom_hud_providers = list()
+ var/list/datum/atom_hud_provider/providers = hud.resolve_providers()
+ for(var/datum/atom_hud_provider/provider as anything in providers)
+ var/already_there = atom_hud_providers[provider]
+ if(already_there)
+ atom_hud_providers[provider] |= source
+ else
+ atom_hud_providers[provider] = list(source)
+ provider.add_perspective(src)
+
+/**
+ * accepts:
+ * * path
+ * * string id
+ * * instance
+ *
+ * currently will runtime if it's not a valid hud!
+ */
+/datum/perspective/proc/remove_atom_hud(datum/atom_hud/hud, source)
+ ASSERT(istext(source))
+ if(!length(atom_hud_providers))
+ return
+ if(!hud)
+ // remove all of source
+ for(var/datum/atom_hud_provider/provider as anything in atom_hud_providers)
+ if(!(source in atom_hud_providers[provider]))
+ continue
+ atom_hud_providers[provider] -= source
+ if(!length(atom_hud_providers[provider]))
+ atom_hud_providers -= provider
+ provider.remove_perspective(src)
+ return
+ hud = fetch_atom_hud(hud)
+ var/list/datum/atom_hud_provider/providers = hud.resolve_providers()
+ for(var/datum/atom_hud_provider/provider as anything in providers)
+ if(!length(atom_hud_providers[provider]))
+ continue
+ atom_hud_providers[provider] -= source
+ if(!length(atom_hud_providers[provider]))
+ atom_hud_providers -= provider
+ provider.remove_perspective(src)
+
+// todo: add_atom_hud_provider, remove_atom_hud_provider
+
+/**
+ * clears all atom hud providers
+ *
+ * * seriously don't use this unless you know what you're doing
+ * * "why are huds broken" --> 9 / 10 times it's because someone used this when they shouldn't
+ * * remember that /datum/perspective does NOT state track HUD registration for you; you have to ensure things are added/removed as necessary
+ * * hint: if it's not on /silicon/robot, /simple_mob, or /dead/observer (or you don't know what those words are), you shouldn't be using this!
+ */
+/datum/perspective/proc/clear_atom_hud_providers()
+ for(var/datum/atom_hud_provider/provider as anything in atom_hud_providers)
+ provider.remove_perspective(src)
+ atom_hud_providers = null
+
//? Eye
/datum/perspective/proc/set_eye_mode(perspective)
diff --git a/code/game/rendering/screen.dm b/code/game/rendering/screen.dm
index bfd26030a128..31b02865d09e 100644
--- a/code/game/rendering/screen.dm
+++ b/code/game/rendering/screen.dm
@@ -285,7 +285,7 @@
if(!istype(tanks[index], /obj/item/tank))
continue
C.internal = tanks[index]
- to_chat(C, "You are now running on internals from [tanks[index]] on your [locnames[index]]")
+ to_chat(C, "You are now running on internals from [tanks[index]] [locnames[index]]")
if(C.internals)
C.internals.icon_state = "internal1"
return
diff --git a/code/game/sound.dm b/code/game/sound.dm
index f05434c775d6..e4c78834f896 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -249,7 +249,7 @@ GLOBAL_VAR_INIT(sound_distance_offscreen, 7)
soundin = pick('sound/weapons/hf_machete/hfmachete_throw_hit01.ogg', 'sound/weapons/hf_machete/hfmachete_throw_hit02.ogg', 'sound/weapons/hf_machete/hfmachete_throw_hit03.ogg')
-//! ## VORE SOUNDS
+ // VORE SOUNDS
if ("hunger_sounds") soundin = pick('sound/vore/growl1.ogg','sound/vore/growl2.ogg','sound/vore/growl3.ogg','sound/vore/growl4.ogg','sound/vore/growl5.ogg')
if("classic_digestion_sounds") soundin = pick(
diff --git a/code/game/turfs/initialization/init.dm b/code/game/turfs/initialization/init.dm
deleted file mode 100644
index 6c2d8ead2952..000000000000
--- a/code/game/turfs/initialization/init.dm
+++ /dev/null
@@ -1,11 +0,0 @@
-/datum/turf_initializer/proc/InitializeTurf(var/turf/T)
- return
-
-/area
- var/datum/turf_initializer/turf_initializer = null
-
-/area/LateInitialize()
- . = ..()
- if(turf_initializer)
- for(var/turf/simulated/T in src)
- turf_initializer.InitializeTurf(T)
diff --git a/code/game/turfs/initialization/maintenance.dm b/code/game/turfs/initialization/maintenance.dm
deleted file mode 100644
index 329bb2e97074..000000000000
--- a/code/game/turfs/initialization/maintenance.dm
+++ /dev/null
@@ -1,64 +0,0 @@
-/datum/turf_initializer/maintenance/InitializeTurf(var/turf/simulated/T)
- if(T.density)
- return
- // Quick and dirty check to avoid placing things inside windows
- if(locate(/obj/structure/grille, T))
- return
-
- var/cardinal_turfs = T.CardinalTurfs()
-
- T.dirt = rand(10, 50) + rand(0, 50)
- // If a neighbor is dirty, then we get dirtier.
- var/how_dirty = dirty_neighbors(cardinal_turfs)
- for(var/i = 0; i < how_dirty; i++)
- T.dirt += rand(0,10)
- T.update_dirt()
-
- if(prob(2))
- var/type = junk()
- var/obj/junk = new type(T)
- junk.obj_persist_status |= OBJ_PERSIST_STATUS_NO_THANK_YOU
- if(prob(2))
- var/obj/effect/debris/cleanable/blood/oil/oil = new(T)
- oil.obj_persist_status |= OBJ_PERSIST_STATUS_NO_THANK_YOU
- if(prob(25)) // Keep in mind that only "corners" get any sort of web
- attempt_web(T, cardinal_turfs)
-
-var/global/list/random_junk
-/datum/turf_initializer/maintenance/proc/junk()
- if(prob(25))
- return /obj/effect/debris/cleanable/generic
- if(!random_junk)
- random_junk = subtypesof(/obj/item/trash)
- random_junk += typesof(/obj/item/cigbutt)
- random_junk += /obj/effect/debris/cleanable/spiderling_remains
- random_junk += /obj/effect/decal/remains/mouse
- random_junk += /obj/effect/decal/remains/robot
- random_junk -= /obj/item/trash/plate
- random_junk -= /obj/item/trash/snack_bowl
- random_junk -= /obj/item/trash/syndi_cakes
- random_junk -= /obj/item/trash/tray
- return pick(random_junk)
-
-/datum/turf_initializer/maintenance/proc/dirty_neighbors(var/list/cardinal_turfs)
- var/how_dirty = 0
- for(var/turf/simulated/T in cardinal_turfs)
- // Considered dirty if more than halfway to visible dirt
- if(T.dirt > 25)
- how_dirty++
- return how_dirty
-
-/datum/turf_initializer/maintenance/proc/attempt_web(var/turf/simulated/T)
- var/turf/north_turf = get_step(T, NORTH)
- if(!north_turf || !north_turf.density)
- return
-
- for(var/dir in list(WEST, EAST)) // For the sake of efficiency, west wins over east in the case of 1-tile valid spots, rather than doing pick()
- var/turf/neighbour = get_step(T, dir)
- if(neighbour && neighbour.density)
- if(dir == WEST)
- var/obj/effect/debris/cleanable/cobweb/w1 = new(T)
- w1.obj_persist_status |= OBJ_PERSIST_STATUS_NO_THANK_YOU
- if(dir == EAST)
- var/obj/effect/debris/cleanable/cobweb2/w2 = new(T)
- w2.obj_persist_status |= OBJ_PERSIST_STATUS_NO_THANK_YOU
diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm
index bb6ddac50bc4..03bc1efd7a4e 100644
--- a/code/game/turfs/space/space.dm
+++ b/code/game/turfs/space/space.dm
@@ -30,7 +30,8 @@
SHOULD_CALL_PARENT(FALSE)
atom_flags |= ATOM_INITIALIZED
- icon_state = SPACE_ICON_STATE(x, y, z)
+ // we have parallax and don't need this anymore
+ // icon_state = SPACE_ICON_STATE(x, y, z)
// We might be an edge
if(y == world.maxy || forced_dirs & NORTH)
@@ -46,18 +47,23 @@
if (CONFIG_GET(flag/starlight))
update_starlight()
- var/turf/below = below()
- if(isnull(below))
- return INITIALIZE_HINT_NORMAL
+ // todo: audit all this again
+ // tl;dr given we load maps at runtime now, the maploader will do changeturfing, which means
+ // we don't need to manually check all this in initialize
+ return INITIALIZE_HINT_NORMAL
- if(isspaceturf(below))
- return INITIALIZE_HINT_NORMAL
+ // var/turf/below = below()
+ // if(isnull(below))
+ // return INITIALIZE_HINT_NORMAL
- var/area/A = below.loc
- if(!below.density && (A.area_flags & AREA_FLAG_EXTERNAL))
- return INITIALIZE_HINT_NORMAL
+ // if(isspaceturf(below))
+ // return INITIALIZE_HINT_NORMAL
- return INITIALIZE_HINT_NORMAL
+ // var/area/A = below.loc
+ // if(!below.density && (A.area_flags & AREA_FLAG_EXTERNAL))
+ // return INITIALIZE_HINT_NORMAL
+
+ // return INITIALIZE_HINT_NORMAL
// todo: wtf happened there..?
// return INITIALIZE_HINT_LATELOAD // oh no! we need to switch to being a different kind of turf!
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index a9cc652653d3..091dad0c4530 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -284,6 +284,10 @@
step(user.pulling, get_dir(user.pulling.loc, src))
return 1
+/turf/attack_ai(mob/user as mob) //this feels like a bad idea ultimately but this is the cheapest way to let cyborgs nudge things they're pulling around
+ . = ..()
+ attack_hand(user, list("siliconattack" = TRUE))
+
/turf/attackby(obj/item/I, mob/user, list/params, clickchain_flags, damage_multiplier)
if(I.obj_storage?.allow_mass_gather && I.obj_storage.allow_mass_gather_via_click)
I.obj_storage.auto_handle_interacted_mass_pickup(new /datum/event_args/actor(user), src)
diff --git a/code/game/world.dm b/code/game/world.dm
index f11f51e3fbb6..e4de216b30f5 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -313,7 +313,7 @@ GLOBAL_LIST(topic_status_cache)
/world/Del()
var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL")
if (debug_server)
- call(debug_server, "auxtools_shutdown")()
+ call_ext(debug_server, "auxtools_shutdown")()
. = ..()
/hook/startup/proc/loadMode()
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 9090799bf10b..1efda3b08d04 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -223,7 +223,15 @@ var/global/floorIsLava = 0
if (!istype(src,/datum/admins))
to_chat(usr, "Error: you are not an admin!")
return
- PlayerNotesPage(1)
+ //PlayerNotesPage(1)
+ tgui_notes()
+
+/datum/admins/proc/tgui_notes()
+ var/savefile/S=new("data/player_notes.sav")
+ var/list/note_keys
+ S >> note_keys
+ var/selected_ckey = tgui_input_list(usr, "Select the ckey you want the notes off:", "Ckey Select",sortList(note_keys))
+ show_player_info(selected_ckey)
/datum/admins/proc/PlayerNotesPage(page)
var/dat = "Player notes"
diff --git a/code/modules/admin/permissionverbs/permissionedit.dm b/code/modules/admin/permissionverbs/permissionedit.dm
index 18b7e3aa9025..fad215244819 100644
--- a/code/modules/admin/permissionverbs/permissionedit.dm
+++ b/code/modules/admin/permissionverbs/permissionedit.dm
@@ -9,14 +9,17 @@
/datum/admins/proc/edit_admin_permissions()
if(!check_rights(R_PERMISSIONS))
return
- var/datum/asset/asset_cache_datum = get_asset_datum(/datum/asset/group/permissions)
- asset_cache_datum.send(usr)
+
+ var/datum/asset_pack/asset_cache_datum = SSassets.ready_asset_pack(/datum/asset_pack/simple/permissions)
+ SSassets.send_asset_pack(usr, asset_cache_datum)
+
+ SSassets.send_asset_pack(usr, /datum/asset_pack/simple/common)
var/output = {"
Permissions Panel
-
+
diff --git a/code/modules/admin/verbs/debug/fucky_wucky.dm b/code/modules/admin/verbs/debug/fucky_wucky.dm
index dc9959fba211..9937a62482c1 100644
--- a/code/modules/admin/verbs/debug/fucky_wucky.dm
+++ b/code/modules/admin/verbs/debug/fucky_wucky.dm
@@ -3,8 +3,13 @@
set category = "Debug"
set name = "Fucky Wucky"
set desc = "Inform the players that the code monkeys at our headquarters are working very hard to fix this."
+
+ log_and_message_admins("[key_name(src)] made a fucky wucky.")
+
+ var/datum/asset_pack/assets = SSassets.ready_asset_pack(/datum/asset_pack/simple/fuckywucky)
+ var/img_src = assets.get_url("fuckywucky.png")
+ SSassets.send_asset_pack(GLOB.player_list, assets)
+
for(var/victim as anything in GLOB.player_list)
- var/datum/asset/fuckywucky = get_asset_datum(/datum/asset/simple/fuckywucky)
- fuckywucky.send(victim)
SEND_SOUND(victim, 'sound/misc/fuckywucky.ogg')
- to_chat(victim, "")
+ to_chat(victim, "")
diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm
index 0e4a167d9315..cc983dee8639 100644
--- a/code/modules/admin/verbs/mapping.dm
+++ b/code/modules/admin/verbs/mapping.dm
@@ -138,6 +138,9 @@ GLOBAL_LIST_EMPTY(dirty_vars)
var/list/debug_verbs = list (
/client/proc/analyze_openturf,
+ /client/proc/zms_display_turf,
+ /client/proc/zms_display_discovery,
+ /client/proc/zms_display_mimic,
/client/proc/atmos_toggle_debug,
/client/proc/atmosscan,
/client/proc/camera_view,
diff --git a/code/modules/artwork/items/crayon.dm b/code/modules/artwork/items/crayon.dm
index 7a27151f08cb..b3317c1283af 100644
--- a/code/modules/artwork/items/crayon.dm
+++ b/code/modules/artwork/items/crayon.dm
@@ -92,9 +92,9 @@
.["graffitiPickedAngle"] = current_graffiti_angle
.["graffitiPickedColor"] = crayon_color
-/obj/item/pen/crayon/ui_assets(mob/user)
- . = ..()
- . += get_asset_datum(/datum/asset/spritesheet/crayons)
+/obj/item/pen/crayon/ui_asset_injection(datum/tgui/ui, list/immediate, list/deferred)
+ immediate += /datum/asset_pack/spritesheet/crayons
+ return ..()
/obj/item/pen/crayon/ui_act(action, list/params, datum/tgui/ui)
. = ..()
diff --git a/code/modules/asset_cache/asset_cache_client.dm b/code/modules/asset_cache/asset_cache_client.dm
deleted file mode 100644
index a30fcaf5dfe4..000000000000
--- a/code/modules/asset_cache/asset_cache_client.dm
+++ /dev/null
@@ -1,49 +0,0 @@
-
-/client/proc/warn_if_no_asset_cache_browser()
- if(!winexists(src, "asset_cache_browser")) // The client is using a custom skin, tell them.
- to_chat(src, "Unable to access asset cache browser, if you are using a custom skin file, please allow DS to download the updated version, if you are not, then make a bug report. This is not a critical issue but can cause issues with resource downloading, as it is impossible to know when extra resources arrived to you.")
-
-/// Process asset cache client topic calls for `"asset_cache_confirm_arrival=[INT]"`
-/client/proc/asset_cache_confirm_arrival(job_id)
- var/asset_cache_job = round(text2num(job_id))
- //because we skip the limiter, we have to make sure this is a valid arrival and not somebody tricking us into letting them append to a list without limit.
- if (asset_cache_job > 0 && asset_cache_job <= last_asset_job && !(completed_asset_jobs["[asset_cache_job]"]))
- completed_asset_jobs["[asset_cache_job]"] = TRUE
- last_completed_asset_job = max(last_completed_asset_job, asset_cache_job)
- else
- return asset_cache_job || TRUE
-
-
-/// Process asset cache client topic calls for `"asset_cache_preload_data=[HTML+JSON_STRING]"`
-/client/proc/asset_cache_preload_data(data)
- var/json = data
- var/list/preloaded_assets = json_decode(json)
-
- for (var/preloaded_asset in preloaded_assets)
- if (copytext(preloaded_asset, findlasttext(preloaded_asset, ".")+1) in list("js", "jsm", "htm", "html"))
- preloaded_assets -= preloaded_asset
- continue
- sent_assets |= preloaded_assets
-
-
-/// Updates the client side stored json file used to keep track of what assets the client has between restarts/reconnects.
-/client/proc/asset_cache_update_json()
- if (world.time - connection_time < 10 SECONDS) //don't override the existing data file on a new connection
- return
-
- src << browse(json_encode(sent_assets), "file=asset_data.json&display=0")
-
-/// Blocks until all currently sending browse and browse_rsc assets have been sent.
-/// Due to byond limitations, this proc will sleep for 1 client round trip even if the client has no pending asset sends.
-/// This proc will return an untrue value if it had to return before confirming the send, such as timeout or the client going away.
-/client/proc/browse_queue_flush(timeout = 50)
- var/job = ++last_asset_job
- var/t = 0
- var/timeout_time = timeout
- src << browse({""}, "window=asset_cache_browser&file=asset_cache_send_verify.htm")
-
- while(!completed_asset_jobs["[job]"] && t < timeout_time) // Reception is handled in Topic()
- stoplag(1) // Lock up the caller until this is received.
- t++
- if (t < timeout_time)
- return TRUE
diff --git a/code/modules/asset_cache/asset_cache_item.dm b/code/modules/asset_cache/asset_cache_item.dm
deleted file mode 100644
index 72d976bf11f1..000000000000
--- a/code/modules/asset_cache/asset_cache_item.dm
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * # asset_cache_item
- *
- * An internal datum containing info on items in the asset cache. Mainly used to cache md5 info for speed.
- */
-/datum/asset_cache_item
- var/name
- var/hash
- var/resource
- var/ext = ""
- /// Should this file also be sent via the legacy browse_rsc system
- /// when cdn transports are enabled?
- var/legacy = FALSE
- /// Used by the cdn system to keep legacy css assets with their parent
- /// css file. (css files resolve urls relative to the css file, so the
- /// legacy system can't be used if the css file itself could go out over
- /// the cdn)
- var/namespace = null
- /// True if this is the parent css or html file for an asset's namespace
- var/namespace_parent = FALSE
- /// TRUE for keeping local asset names when browse_rsc backend is used
- var/keep_local_name = FALSE
-
-/datum/asset_cache_item/New(name, file)
- if (!isfile(file))
- file = fcopy_rsc(file)
-
- hash = md5asfile(file) //icons sent to the rsc sometimes md5 incorrectly
- if (!hash)
- CRASH("invalid asset sent to asset cache")
- src.name = name
- var/extstart = findlasttext(name, ".")
- if (extstart)
- ext = ".[copytext(name, extstart+1)]"
- resource = file
-
-/datum/asset_cache_item/vv_edit_var(var_name, var_value)
- return FALSE
-
-/datum/asset_cache_item/CanProcCall(procname)
- return FALSE
diff --git a/code/modules/asset_cache/asset_item.dm b/code/modules/asset_cache/asset_item.dm
new file mode 100644
index 000000000000..0a5782e982a2
--- /dev/null
+++ b/code/modules/asset_cache/asset_item.dm
@@ -0,0 +1,83 @@
+VV_PROTECT_READONLY(/datum/asset_item)
+/**
+ * A datum used to store data on an asset in the asset cache.
+ *
+ * The asset cache system, at its core, is just a way to get files from the server to the
+ * client in a reproducible and efficient manner.
+ *
+ * Each asset item is an item in the cache.
+ */
+/datum/asset_item
+ /// filename
+ var/name
+ /// our hash
+ var/hash
+ /// the actual file handle
+ ///
+ /// * this should always be something in data/, as this needs to be available during the whole round!
+ var/file
+ /// our extension, excluding the .
+ var/ext
+
+ /// force browse_rsc?
+ var/always_browse_rsc = FALSE
+ /// do not mangle our name
+ var/do_not_mangle = FALSE
+ /// the asset pack loaded the file from cache instead of generate()ing it again.
+ /// todo: caching; we might want to replace this var with the git commit or something, anything to track when it was cached.
+ // var/restored_from_cache = FALSE
+
+ /// if set, we are shoved in a namespace of this id if not browse_rsc()'d
+ var/namespace_id
+
+ /// actual filename to use
+ var/mangled_name
+
+/datum/asset_item/New(name, file, do_not_mangle, always_browse_rsc, restored_from_cache)
+ // set name
+ src.name = name
+ ASSERT(length(src.name))
+ // set options
+ src.do_not_mangle = do_not_mangle
+ src.always_browse_rsc = always_browse_rsc
+ // todo: caching
+ // src.restored_from_cache = restored_from_cache
+ // set file; always load them into resource cache if they aren't already
+ // todo: this kind of ruins the point if we're trying to flush state,
+ // todo: but we have to anyways unless TGS stops swapping A/B out from under Live during the round.
+ src.file = isfile(file)? file : fcopy_rsc(file)
+ // get hash; allegedly there's an issue that causes incorrect hashes if we do it directly
+ src.hash = md5asfile(file)
+ // set extension
+ var/extstart = findlasttext(name, ".")
+ if (extstart)
+ ext = "[copytext(name, extstart+1)]"
+ mangled_name = do_not_mangle? name : mangle_name()
+
+/datum/asset_item/proc/mangle_name()
+ return "asset.[hash].[ext]"
+
+/**
+ * dynamic asset items
+ *
+ * aka "i overengineered asset_pack, what if you just want to send one file?"
+ *
+ * well this is how.
+ */
+/datum/asset_item/dynamic
+ /// loaded url provided by transport; we are accessible from this by every client.
+ var/loaded_url
+
+/datum/asset_item/dynamic/proc/get_url()
+ return ensure_loaded()
+
+/datum/asset_item/dynamic/proc/send(list/client/clients)
+ ensure_loaded()
+ for(var/client/C as anything in clients)
+ SSassets.transport.send_asset_items(C, src)
+
+/datum/asset_item/dynamic/proc/ensure_loaded()
+ if(!isnull(loaded_url))
+ return loaded_url
+ loaded_url = SSassets.transport.load_asset_item(src)
+ return loaded_url
diff --git a/code/modules/asset_cache/asset_list.dm b/code/modules/asset_cache/asset_list.dm
deleted file mode 100644
index 0c66b93fc260..000000000000
--- a/code/modules/asset_cache/asset_list.dm
+++ /dev/null
@@ -1,600 +0,0 @@
-#define ASSET_CROSS_ROUND_CACHE_DIRECTORY "tmp/assets"
-
-//These datums are used to populate the asset cache, the proc "register()" does this.
-//Place any asset datums you create in asset_list_items.dm
-
-//all of our asset datums, used for referring to these later
-GLOBAL_LIST_EMPTY(asset_datums)
-
-/// Get an assetdatum or make a new one.
-/proc/get_asset_datum(type)
- var/datum/asset/loaded_asset = GLOB.asset_datums[type] || new type()
- return loaded_asset.ensure_ready()
-
-/**
- * Get an assetdatum or make a new one.
- *! But does NOT ensure it's filled, if you want that use get_asset_datum()
- */
-/proc/load_asset_datum(type)
- return GLOB.asset_datums[type] || new type()
-
-/datum/asset
- abstract_type = /datum/asset
- /// lazyloaded? this means we lateload when someone fetches us, rather than at init
- var/lazy = FALSE
-
- var/cached_serialized_url_mappings
- var/cached_serialized_url_mappings_transport_type
-
- /// Whether or not this asset should be loaded in the "early assets" SS
- var/early = FALSE
-
- /**
- * Whether or not this asset can be cached across rounds of the same commit under the `CACHE_ASSETS` config.
- * This is not a *guarantee* the asset will be cached. Not all asset subtypes respect this field, and the
- * config can, of course, be disabled.
- */
- var/cross_round_cachable = FALSE
-
-/**
- * Stub that allows us to react to something trying to get us.
- * Not useful here, more handy for sprite sheets.
- */
-/datum/asset/proc/ensure_ready()
- return src
-
-/// Stub to hook into if your asset is having its generation queued by SSasset_loading
-/datum/asset/proc/queued_generation()
- CRASH("[type] inserted into SSasset_loading despite not implementing /proc/queued_generation")
-
-
-/datum/asset/New()
- GLOB.asset_datums[type] = src
- register()
-
-/datum/asset/proc/get_url_mappings()
- return list()
-
-/// Returns a cached tgui message of URL mappings
-/datum/asset/proc/get_serialized_url_mappings()
- if (isnull(cached_serialized_url_mappings) || cached_serialized_url_mappings_transport_type != SSassets.transport.type)
- cached_serialized_url_mappings = TGUI_CREATE_MESSAGE("asset/mappings", get_url_mappings())
- cached_serialized_url_mappings_transport_type = SSassets.transport.type
-
- return cached_serialized_url_mappings
-
-/datum/asset/proc/register()
- return
-
-/datum/asset/proc/send(client)
- return
-
-/// Returns whether or not the asset should attempt to read from cache
-/datum/asset/proc/should_refresh()
- return !cross_round_cachable || !CONFIG_GET(flag/cache_assets)
-
-/// If you don't need anything complicated.
-/datum/asset/simple
- abstract_type = /datum/asset/simple
- /**
- * List of assets for this datum in the form of:
- * * asset_filename = asset_file.
- * At runtime the asset_file will be converted into a asset_cache datum.
- */
- var/assets = list()
- /**
- * Set to true to have this asset also be sent via the legacy browse_rsc
- * system when cdn transports are enabled?
- */
- var/legacy = FALSE
- /// TRUE for keeping local asset names when browse_rsc backend is used
- var/keep_local_name = FALSE
-
-/datum/asset/simple/register()
- for(var/asset_name in assets)
- var/datum/asset_cache_item/ACI = SSassets.transport.register_asset(asset_name, assets[asset_name])
- if (!ACI)
- log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
- continue
- if (legacy)
- ACI.legacy = legacy
- if (keep_local_name)
- ACI.keep_local_name = keep_local_name
- assets[asset_name] = ACI
-
-/datum/asset/simple/send(client)
- . = SSassets.transport.send_assets(client, assets)
-
-/datum/asset/simple/get_url_mappings()
- . = list()
- for (var/asset_name in assets)
- .[asset_name] = SSassets.transport.get_asset_url(asset_name, assets[asset_name])
-
-// For registering or sending multiple others at once
-/datum/asset/group
- abstract_type = /datum/asset/group
- var/list/children
-
-/datum/asset/group/register()
- for(var/type in children)
- load_asset_datum(type)
-
-/datum/asset/group/send(client/C)
- for(var/type in children)
- var/datum/asset/A = get_asset_datum(type)
- . = A.send(C) || .
-
-/datum/asset/group/get_url_mappings()
- . = list()
- for(var/type in children)
- var/datum/asset/A = get_asset_datum(type)
- . += A.get_url_mappings()
-
-/**
- * spritesheet implementation - coalesces various icons into a single .png file
- * and uses CSS to select icons out of that file - saves on transferring some
- * 1400-odd individual PNG files
- *
- * To use, use classes of "[name][size_key]" and the state name used in Insert().
- * If you used InsertAll(), don't forget the prefix.
- *
- * Example:
- * In tgui, usually would be clsasName={classes(['sheetmaterials32x32', 'glass-3'])}
- */
-#define SPR_SIZE 1
-#define SPR_IDX 2
-
-#define SPRSZ_COUNT 1
-#define SPRSZ_ICON 2
-#define SPRSZ_STRIPPED 3
-
-/datum/asset/spritesheet
- abstract_type = /datum/asset/spritesheet
- var/name
- /**
- * List of arguments to pass into queuedInsert.
- * Exists so we can queue icon insertion, mostly for stuff like preferences.
- */
- var/list/to_generate = list()
- /// "32x32" -> list(10, icon/normal, icon/stripped)
- var/list/sizes = list()
- /// "foo_bar" -> list("32x32", 5)
- var/list/sprites = list()
- var/list/cached_spritesheets_needed
- var/generating_cache = FALSE
- var/fully_generated = FALSE
- /**
- * If this asset should be fully loaded on new
- * Defaults to false so we can process this stuff nicely
- */
- var/load_immediately = FALSE
-
-/datum/asset/spritesheet/should_refresh()
- if (..())
- return TRUE
-
- /// Static so that the result is the same, even when the files are created, for this run.
- var/static/should_refresh = null
-
- if (isnull(should_refresh))
- // `fexists` seems to always fail on static-time
- should_refresh = !fexists("[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].css")
-
- return should_refresh
-
-/datum/asset/spritesheet/register()
- SHOULD_NOT_OVERRIDE(TRUE)
-
- if (!name)
- CRASH("spritesheet [type] cannot register without a name")
-
- if (!should_refresh() && read_from_cache())
- return
-
- /// If it's cached, may as well load it now, while the loading is cheap
- if(CONFIG_GET(flag/cache_assets) && cross_round_cachable)
- load_immediately = TRUE
-
- create_spritesheets()
- if(load_immediately)
- realize_spritesheets(yield = FALSE)
- else
- SSasset_loading.generate_queue += src
-
-/datum/asset/spritesheet/proc/realize_spritesheets(yield)
- if(fully_generated)
- return
- while(length(to_generate))
- var/list/stored_args = to_generate[to_generate.len]
- to_generate.len--
- queuedInsert(arglist(stored_args))
- if(yield && TICK_CHECK)
- return
-
- ensure_stripped()
- for(var/size_id in sizes)
- var/size = sizes[size_id]
- SSassets.transport.register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED])
- var/res_name = "spritesheet_[name].css"
- var/fname = "data/spritesheets/[res_name]"
- fdel(fname)
- text2file(generate_css(), fname)
- SSassets.transport.register_asset(res_name, fcopy_rsc(fname))
- fdel(fname)
-
- if (CONFIG_GET(flag/cache_assets) && cross_round_cachable)
- write_to_cache()
-
- fully_generated = TRUE
- // If we were ever in there, remove ourselves
- SSasset_loading.generate_queue -= src
-
-/datum/asset/spritesheet/queued_generation()
- realize_spritesheets(yield = TRUE)
-
-/datum/asset/spritesheet/ensure_ready()
- if(!fully_generated)
- realize_spritesheets(yield = FALSE)
- return ..()
-
-/datum/asset/spritesheet/send(client/client)
- if (!name)
- return
-
- if (!should_refresh())
- return send_from_cache(client)
-
- var/all = list("spritesheet_[name].css")
- for(var/size_id in sizes)
- all += "[name]_[size_id].png"
- . = SSassets.transport.send_assets(client, all)
-
-/datum/asset/spritesheet/get_url_mappings()
- if (!name)
- return
-
- if (!should_refresh())
- return get_cached_url_mappings()
-
- . = list("spritesheet_[name].css" = SSassets.transport.get_asset_url("spritesheet_[name].css"))
- for(var/size_id in sizes)
- .["[name]_[size_id].png"] = SSassets.transport.get_asset_url("[name]_[size_id].png")
-
-/datum/asset/spritesheet/proc/ensure_stripped(sizes_to_strip = sizes)
- for(var/size_id in sizes_to_strip)
- var/size = sizes[size_id]
- if (size[SPRSZ_STRIPPED])
- continue
-
- // save flattened version
- var/fname = "data/spritesheets/[name]_[size_id].png"
- fcopy(size[SPRSZ_ICON], fname)
- var/error = rustg_dmi_strip_metadata(fname)
- if(length(error))
- stack_trace("Failed to strip [name]_[size_id].png: [error]")
- size[SPRSZ_STRIPPED] = icon(fname)
- fdel(fname)
-
-/datum/asset/spritesheet/proc/generate_css()
- var/list/out = list()
-
- for (var/size_id in sizes)
- var/size = sizes[size_id]
- var/icon/tiny = size[SPRSZ_ICON]
- out += ".[name][size_id]{display:inline-block;width:[tiny.Width()]px;height:[tiny.Height()]px;background:url('[get_background_url("[name]_[size_id].png")]') no-repeat;}"
-
- for (var/sprite_id in sprites)
- var/sprite = sprites[sprite_id]
- var/size_id = sprite[SPR_SIZE]
- var/idx = sprite[SPR_IDX]
- var/size = sizes[size_id]
-
- var/icon/tiny = size[SPRSZ_ICON]
- var/icon/big = size[SPRSZ_STRIPPED]
- var/per_line = big.Width() / tiny.Width()
- var/x = (idx % per_line) * tiny.Width()
- var/y = round(idx / per_line) * tiny.Height()
-
- out += ".[name][size_id].[sprite_id]{background-position:-[x]px -[y]px;}"
-
- return out.Join("\n")
-
-/datum/asset/spritesheet/proc/read_from_cache()
- var/replaced_css = file2text("[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].css")
-
- var/regex/find_background_urls = regex(@"background:url\('%(.+?)%'\)", "g")
- while (find_background_urls.Find(replaced_css))
- var/asset_id = find_background_urls.group[1]
- var/asset_cache_item = SSassets.transport.register_asset(asset_id, "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[asset_id]")
- var/asset_url = SSassets.transport.get_asset_url(asset_cache_item = asset_cache_item)
- replaced_css = replacetext(replaced_css, find_background_urls.match, "background:url('[asset_url]')")
- LAZYADD(cached_spritesheets_needed, asset_id)
-
- var/replaced_css_filename = "data/spritesheets/spritesheet_[name].css"
- rustg_file_write(replaced_css, replaced_css_filename)
- SSassets.transport.register_asset("spritesheet_[name].css", replaced_css_filename)
-
- fdel(replaced_css_filename)
-
- return TRUE
-
-/datum/asset/spritesheet/proc/send_from_cache(client/client)
- if (isnull(cached_spritesheets_needed))
- stack_trace("cached_spritesheets_needed was null when sending assets from [type] from cache")
- cached_spritesheets_needed = list()
-
- return SSassets.transport.send_assets(client, cached_spritesheets_needed + "spritesheet_[name].css")
-
-/// Returns the URL to put in the background:url of the CSS asset
-/datum/asset/spritesheet/proc/get_background_url(asset)
- if (generating_cache)
- return "%[asset]%"
- else
- return SSassets.transport.get_asset_url(asset)
-
-/datum/asset/spritesheet/proc/write_to_cache()
- for (var/size_id in sizes)
- fcopy(SSassets.cache["[name]_[size_id].png"].resource, "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name]_[size_id].png")
-
- generating_cache = TRUE
- var/mock_css = generate_css()
- generating_cache = FALSE
-
- rustg_file_write(mock_css, "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].css")
-
-/datum/asset/spritesheet/proc/get_cached_url_mappings()
- var/list/mappings = list()
- mappings["spritesheet_[name].css"] = SSassets.transport.get_asset_url("spritesheet_[name].css")
-
- for (var/asset_name in cached_spritesheets_needed)
- mappings[asset_name] = SSassets.transport.get_asset_url(asset_name)
-
- return mappings
-
-/**
- *! Override this in order to start the creation of the spritehseet.
- *! This is where all your Insert, InsertAll, etc calls should be inside.
- */
-/datum/asset/spritesheet/proc/create_spritesheets()
- SHOULD_CALL_PARENT(FALSE)
- CRASH("create_spritesheets() not implemented for [type]!")
-/**
- * neither prefixes nor states may have spaces!
- */
-/datum/asset/spritesheet/proc/Insert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE)
- if(load_immediately)
- queuedInsert(sprite_name, I, icon_state, dir, frame, moving)
- else
- to_generate += list(args.Copy())
-
-/**
- * LEMON NOTE
- * A GOON CODER SAYS BAD ICON ERRORS CAN BE THROWN BY THE "ICON CACHE"
- * APPARENTLY IT MAKES ICONS IMMUTABLE
- * LOOK INTO USING THE MUTABLE APPEARANCE PATTERN HERE
- *
- * neither prefixes nor states may have spaces!
- */
-/datum/asset/spritesheet/proc/queuedInsert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE)
- I = icon(I, icon_state=icon_state, dir=dir, frame=frame, moving=moving)
- if (!I || !length(icon_states(I))) // That direction or state doesn't exist!
- return
- // Any sprite modifications we want to do (aka, coloring a greyscaled asset)
- I = ModifyInserted(I)
- var/size_id = "[I.Width()]x[I.Height()]"
- var/size = sizes[size_id]
-
- if (sprites[sprite_name])
- CRASH("duplicate sprite \"[sprite_name]\" in sheet [name] ([type])")
-
- if (size)
- var/position = size[SPRSZ_COUNT]++
- var/icon/sheet = size[SPRSZ_ICON]
- var/icon/sheet_copy = icon(sheet)
- size[SPRSZ_STRIPPED] = null
- sheet_copy.Insert(I, icon_state=sprite_name)
- size[SPRSZ_ICON] = sheet_copy
- sprites[sprite_name] = list(size_id, position)
- else
- sizes[size_id] = size = list(1, I, null)
- sprites[sprite_name] = list(size_id, 0)
-
-/**
- * A simple proc handing the Icon for you to modify before it gets turned into an asset.
- *
- * Arguments:
- * * I: icon being turned into an asset
- */
-/datum/asset/spritesheet/proc/ModifyInserted(icon/pre_asset)
- return pre_asset
-
-/**
- * neither prefixes nor states may have spaces!
- */
-/datum/asset/spritesheet/proc/InsertAll(prefix, icon/I, list/directions)
- if (length(prefix))
- prefix = "[prefix]-"
-
- if (!directions)
- directions = list(SOUTH)
-
- for (var/icon_state_name in icon_states(I))
- for (var/direction in directions)
- var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]-" : ""
- Insert("[prefix][prefix2][icon_state_name]", I, icon_state=icon_state_name, dir=direction)
-
-/datum/asset/spritesheet/proc/css_tag()
- return {""}
-
-/datum/asset/spritesheet/proc/css_filename()
- return SSassets.transport.get_asset_url("spritesheet_[name].css")
-
-/datum/asset/spritesheet/proc/icon_tag(sprite_name)
- var/sprite = sprites[sprite_name]
- if (!sprite)
- return null
- var/size_id = sprite[SPR_SIZE]
- return {""}
-
-/datum/asset/spritesheet/proc/icon_class_name(sprite_name)
- var/sprite = sprites[sprite_name]
- if (!sprite)
- return null
- var/size_id = sprite[SPR_SIZE]
- return {"[name][size_id] [sprite_name]"}
-
-/**
- * Returns the size class (ex design32x32) for a given sprite's icon
- *
- * Arguments:
- * * sprite_name - The sprite to get the size of
- */
-/datum/asset/spritesheet/proc/icon_size_id(sprite_name)
- var/sprite = sprites[sprite_name]
- if (!sprite)
- return null
- var/size_id = sprite[SPR_SIZE]
- return "[name][size_id]"
-
-#undef SPR_SIZE
-#undef SPR_IDX
-#undef SPRSZ_COUNT
-#undef SPRSZ_ICON
-#undef SPRSZ_STRIPPED
-
-
-/datum/asset/changelog_item
- abstract_type = /datum/asset/changelog_item
- var/item_filename
-
-/datum/asset/changelog_item/New(date)
- item_filename = SANITIZE_FILENAME("[date].yml")
- SSassets.transport.register_asset(item_filename, file("html/changelogs/archive/" + item_filename))
-
-/datum/asset/changelog_item/send(client)
- if (!item_filename)
- return
- . = SSassets.transport.send_assets(client, item_filename)
-
-/datum/asset/changelog_item/get_url_mappings()
- if (!item_filename)
- return
- . = list("[item_filename]" = SSassets.transport.get_asset_url(item_filename))
-
-/datum/asset/spritesheet/simple
- abstract_type = /datum/asset/spritesheet/simple
- var/list/assets
-
-/datum/asset/spritesheet/simple/create_spritesheets()
- for (var/key in assets)
- Insert(key, assets[key])
-
-//Generates assets based on iconstates of a single icon
-/datum/asset/simple/icon_states
- abstract_type = /datum/asset/simple/icon_states
- var/icon
- var/list/directions = list(SOUTH)
- var/frame = 1
- var/movement_states = FALSE
-
- var/prefix = "default" //asset_name = "[prefix].[icon_state_name].png"
- var/generic_icon_names = FALSE //generate icon filenames using generate_asset_name() instead the above format
-
-/datum/asset/simple/icon_states/register(_icon = icon)
- for(var/icon_state_name in icon_states(_icon))
- for(var/direction in directions)
- var/asset = icon(_icon, icon_state_name, direction, frame, movement_states)
- if (!asset)
- continue
- asset = fcopy_rsc(asset) //dedupe
- var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]." : ""
- var/asset_name = SANITIZE_FILENAME("[prefix].[prefix2][icon_state_name].png")
- if (generic_icon_names)
- asset_name = "[generate_asset_name(asset)].png"
-
- SSassets.transport.register_asset(asset_name, asset)
-
-/datum/asset/simple/icon_states/multiple_icons
- abstract_type = /datum/asset/simple/icon_states/multiple_icons
- var/list/icons
-
-/datum/asset/simple/icon_states/multiple_icons/register()
- for(var/i in icons)
- ..(i)
-
-/// Namespace'ed assets (for static css and html files)
-/// When sent over a cdn transport, all assets in the same asset datum will exist in the same folder, as their plain names.
-/// Used to ensure css files can reference files by url() without having to generate the css at runtime, both the css file and the files it depends on must exist in the same namespace asset datum. (Also works for html)
-/// For example `blah.css` with asset `blah.png` will get loaded as `namespaces/a3d..14f/f12..d3c.css` and `namespaces/a3d..14f/blah.png`. allowing the css file to load `blah.png` by a relative url rather then compute the generated url with get_url_mappings().
-/// The namespace folder's name will change if any of the assets change. (excluding parent assets)
-/datum/asset/simple/namespaced
- abstract_type = /datum/asset/simple/namespaced
- /// parents - list of the parent asset or assets (in name = file assoicated format) for this namespace.
- /// parent assets must be referenced by their generated url, but if an update changes a parent asset, it won't change the namespace's identity.
- var/list/parents = list()
-
-/datum/asset/simple/namespaced/register()
- if (legacy)
- assets |= parents
- var/list/hashlist = list()
- var/list/sorted_assets = sortList(assets)
-
- for (var/asset_name in sorted_assets)
- var/datum/asset_cache_item/ACI = new(asset_name, sorted_assets[asset_name])
- if (!ACI?.hash)
- log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
- continue
- hashlist += ACI.hash
- sorted_assets[asset_name] = ACI
- var/namespace = md5(hashlist.Join())
-
- for (var/asset_name in parents)
- var/datum/asset_cache_item/ACI = new(asset_name, parents[asset_name])
- if (!ACI?.hash)
- log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
- continue
- ACI.namespace_parent = TRUE
- sorted_assets[asset_name] = ACI
-
- for (var/asset_name in sorted_assets)
- var/datum/asset_cache_item/ACI = sorted_assets[asset_name]
- if (!ACI?.hash)
- log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
- continue
- ACI.namespace = namespace
-
- assets = sorted_assets
- ..()
-
-/// Get a html string that will load a html asset.
-/// Needed because byond doesn't allow you to browse() to a url.
-/datum/asset/simple/namespaced/proc/get_htmlloader(filename)
- return url2htmlloader(SSassets.transport.get_asset_url(filename, assets[filename]))
-
-/// A subtype to generate a JSON file from a list
-/datum/asset/json
- abstract_type = /datum/asset/json
- /// The filename, will be suffixed with ".json"
- var/name
-
-/datum/asset/json/send(client)
- return SSassets.transport.send_assets(client, "[name].json")
-
-/datum/asset/json/get_url_mappings()
- return list(
- "[name].json" = SSassets.transport.get_asset_url("[name].json"),
- )
-
-/datum/asset/json/register()
- var/filename = "data/[name].json"
- fdel(filename)
- text2file(json_encode(generate()), filename)
- SSassets.transport.register_asset("[name].json", fcopy_rsc(filename))
- fdel(filename)
-
-/// Returns the data that will be JSON encoded
-/datum/asset/json/proc/generate()
- SHOULD_CALL_PARENT(FALSE)
- CRASH("generate() not implemented for [type]!")
-
-#undef ASSET_CROSS_ROUND_CACHE_DIRECTORY
diff --git a/code/modules/asset_cache/asset_pack.dm b/code/modules/asset_cache/asset_pack.dm
new file mode 100644
index 000000000000..daf9a942686a
--- /dev/null
+++ b/code/modules/asset_cache/asset_pack.dm
@@ -0,0 +1,205 @@
+/**
+ * Effectively, a set of files that can be sent to clients.
+ *
+ * Or more specifically, a way to either package (just use /simple),
+ * or generate a set of files that can be sent to clients that
+ * provide specific information (like /spritesheet does).
+ *
+ * Provides inbuilt early and deferred generation capabilities to spread
+ * expensive generation operations across 'idle' ticks.
+ */
+/datum/asset_pack
+ abstract_type = /datum/asset_pack
+
+ /// if set, we are registering by ID instead of by type
+ /// thus, don't set it in a prototype,
+ /// because 99% of assets are hardcoded and shouldn't do that.
+ ///
+ /// ids should be round-unique, but not to prevent collisions.
+ /// our md5'ing system does that.
+ /// what id uniqueness provides is *persistence* collisions.
+ ///
+ /// though honestly, i'd question why assets should ever be persisted in that way
+ /// as opposed to a volatile-append-supported asset datum being added to
+ /// as things load in.
+ var/id
+
+ /// if set, we immediately load via SSassets, rather than being pushed
+ /// onto SSasset_loading for loading during pregame
+ var/load_immediately = FALSE
+ /// if set, we don't load at all until something asks for us
+ var/load_deferred = FALSE
+ /// are we fully loaded / generated?
+ var/loaded = ASSET_NOT_LOADED
+
+ /// /datum/asset_item list
+ var/tmp/list/datum/asset_item/packed_items
+ /// a record of filename = asset_item datums
+ /// these are transport-agnostic.
+ var/tmp/list/item_lookup
+ /// a record of filename = url when we're loaded and pushed to a transport.
+ /// if is null, we are not pushed to the transport
+ var/tmp/list/loaded_urls
+
+ /// having this on will force something to be sent via
+ /// browse_rsc(), ensuring it always exists in the cache folder
+ /// that byond browsers are ran out of
+ ///
+ /// * implies do_not_mangle
+ /// * implies do_not_separate
+ var/absolute = FALSE
+
+ /// do not mutate filenames on the remote side
+ /// used when html pages rely on static bindings / aren't otherwise
+ /// dynamically generated.
+ var/do_not_mangle = FALSE
+ /// do not allow files to be split up between different folders on the remote side
+ /// this means everything in this pack can access each other by a direct, relative link
+ /// and doesn't need to 'find' where the others are through some convoluted process
+ ///
+ /// * this does incur a cost as everything being sent now has to be md5'd with each other.
+ var/do_not_separate = FALSE
+ /// do not preload if determined to be loading via browse_rsc().
+ var/do_not_preload = FALSE
+
+ /// allow caching cross-rounds, if the server is under a singular commit
+ /// requires configuration to be enabled too.
+ var/allow_cached_generation = FALSE
+
+/datum/asset_pack/New(id)
+ if(!isnull(id))
+ src.id = id
+
+/**
+ * ensures we are loaded
+ */
+/datum/asset_pack/proc/ensure_loaded(push_to_transport)
+ switch(loaded)
+ if(ASSET_FULLY_LOADED)
+ // do nothing, we're good
+ if(ASSET_NOT_LOADED)
+ INVOKE_ASYNC(src, PROC_REF(load))
+ UNTIL(loaded != ASSET_IS_LOADING)
+ if(ASSET_IS_LOADING)
+ UNTIL(loaded != ASSET_IS_LOADING)
+ . = loaded == ASSET_FULLY_LOADED
+ if(. && push_to_transport)
+ ensure_ready()
+
+/**
+ * loads / generates whatever is in us
+ *
+ * also pushes us to the transport if necessary.
+ */
+/datum/asset_pack/proc/load()
+ loaded = ASSET_IS_LOADING
+ var/results = generate()
+ var/list/files = register(results)
+ var/list/datum/asset_item/items = pack(files)
+ src.item_lookup = items
+ src.packed_items = list_values(items)
+ loaded = ASSET_FULLY_LOADED
+
+/**
+ * ensures we are pushed to a transport
+ */
+/datum/asset_pack/proc/ensure_ready()
+ if(is_pushed_to_transport())
+ return TRUE
+ ensure_loaded()
+ var/datum/asset_transport/active_transport = SSassets.transport
+ if(isnull(active_transport))
+ return FALSE
+ loaded_urls = active_transport.load_asset_items_from_pack(src)
+
+/**
+ * checks if we're pushed to a transport
+ */
+/datum/asset_pack/proc/is_pushed_to_transport()
+ return !isnull(loaded_urls)
+
+/**
+ * generates whatever needs to be generated
+ *
+ * this must be idempotent and hopefully efficient,
+ * because this is called again if the transport is changed mid-round.
+ *
+ * @return return value is passed to register().
+ */
+/datum/asset_pack/proc/generate()
+ return
+
+/**
+ * packs a list of filename = file's into /datum/asset_item's
+ *
+ * @return filename = /datum/asset_item
+ */
+/datum/asset_pack/proc/pack(list/files)
+ . = list()
+ for(var/filename in files)
+ var/file = files[filename]
+ var/datum/asset_item/item = new(filename, file, do_not_mangle, absolute, FALSE)
+ .[filename] = item
+ if(do_not_separate && !absolute) // if absolute, it's pointless to generate namespace as we'll be in the same folder anyways.
+ // generate their namespace and assign
+ var/namespace = generate_namespace(.)
+ for(var/fname in .)
+ var/datum/asset_item/item = .[fname]
+ item.namespace_id = namespace
+
+/**
+ * things must be packed!
+ */
+/datum/asset_pack/proc/generate_namespace(list/datum/asset_item/items)
+ var/list/joining = list()
+ for(var/fname in items)
+ var/datum/asset_item/item = items[fname]
+ joining += item.hash
+ return md5(jointext(joining, "-"))
+
+/**
+ * registers our content to the asset system's transport
+ *
+ * returns a list of filenames associated to files,
+ * because that's what the asset items / cache is actually dealing with.
+ *
+ * all filenames must be *globally unique*.
+ * while this is technically able to be circumvented on the modern asset loading system
+ * used by tgui windows, it is nonetheless still kept and enforced
+ * to make debugging / development less awful.
+ *
+ * any temporary files should go into ""
+ *
+ * @params
+ * * generation - output of generate().
+ *
+ * @return list("" = file, ...)
+ */
+/datum/asset_pack/proc/register(generation)
+ return list()
+
+/datum/asset_pack/proc/get_url(filename)
+ . = loaded_urls[filename]
+ if(isnull(.))
+ CRASH("failed to get url for [filename] on [type] ([id || "compile-time"]). something is terribly wrong!")
+
+//* below is tg stuff *//
+
+// todo: i honestly don't yet know what this does / why we use custom loaders
+// but when i do i'll probably touch up on it ~silicons
+
+/datum/asset_pack
+
+ var/cached_serialized_url_mappings
+ var/cached_serialized_url_mappings_transport_type
+
+/datum/asset_pack/proc/get_url_mappings()
+ return loaded_urls
+
+/// Returns a cached tgui message of URL mappings
+/datum/asset_pack/proc/get_serialized_url_mappings()
+ if (isnull(cached_serialized_url_mappings) || cached_serialized_url_mappings_transport_type != SSassets.transport.type)
+ cached_serialized_url_mappings = TGUI_CREATE_MESSAGE("asset/mappings", get_url_mappings())
+ cached_serialized_url_mappings_transport_type = SSassets.transport.type
+
+ return cached_serialized_url_mappings
diff --git a/code/modules/asset_cache/asset_transport.dm b/code/modules/asset_cache/asset_transport.dm
new file mode 100644
index 000000000000..c104ca62ae42
--- /dev/null
+++ b/code/modules/asset_cache/asset_transport.dm
@@ -0,0 +1,148 @@
+/**
+ * Pluggable transports used for sending assets to clients.
+ *
+ * todo: if sending too many resources, we need a way to tell the client they're loading. maybe a loading bar graphic?
+ */
+/datum/asset_transport
+ /// name of the transport
+ var/name = "asset-transport: ???"
+
+ /// non-ephemeral items registered; *mangled filename* = instance
+ /// the reason we still use filename assoc list is so filename uniqueness is still enforced
+ /// incase we ever need to go back to native (browse_rsc).
+ var/list/loaded_items = list()
+
+/**
+ * called when we're loaded into SSassets
+ */
+/datum/asset_transport/proc/initialize()
+ return
+
+/datum/asset_transport/proc/load_asset_items_from_pack(datum/asset_pack/pack)
+ var/list/filename_to_url = list()
+ for(var/filename in pack.item_lookup)
+ var/datum/asset_item/item = pack.item_lookup[filename]
+ filename_to_url[filename] = load_asset_item(item)
+ return filename_to_url
+
+/**
+ * @return url to use after a client is sent the asset
+ */
+/datum/asset_transport/proc/load_asset_item(datum/asset_item/item)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if(item.always_browse_rsc)
+ . = load_item_native(item)
+ else
+ . = load_item(item)
+ if(!.)
+ CRASH("failed to load an item")
+ if(loaded_items[.])
+ var/datum/asset_item/existing = loaded_items[.]
+ if(existing.hash != item.hash)
+ CRASH("collision between [existing] and [item] on [.]")
+ loaded_items[.] = item
+
+/**
+ * this is a blocking proc
+ */
+/datum/asset_transport/proc/send_asset_pack(client/target, datum/asset_pack/pack)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if(pack.absolute)
+ return send_asset_item_native(target, pack.packed_items)
+ return send_asset_items(target, pack.packed_items)
+
+/**
+ * this is a blocking proc
+ */
+/datum/asset_transport/proc/send_asset_items(client/target, list/datum/asset_item/items)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if(!islist(items))
+ items = list(items)
+ var/list/datum/asset_item/native_sends = list()
+ for(var/datum/asset_item/item as anything in items)
+ if(!item.always_browse_rsc)
+ continue
+ items -= item
+ native_sends += item
+ if(length(native_sends))
+ send_asset_item_native(target, native_sends)
+ return send_items(target, items)
+
+//* not sure where to put or what to do with these yet
+
+/**
+ * automatically preload all native asset packs to a client, one by one.
+ */
+/datum/asset_transport/proc/perform_native_preload(client/victim, list/datum/asset_pack/native_packs)
+ victim.asset_cache_native_preload(native_packs)
+
+//* Abstraction - Subtypes must override these
+
+/// Check the config is valid to load this transport
+/// Returns TRUE or FALSE
+/datum/asset_transport/proc/validate_config()
+ CRASH("abstract proc unimplemented")
+
+/**
+ * @return URL
+ */
+/datum/asset_transport/proc/send_anonymous_file(list/client/targets, file, ext)
+ CRASH("abstract proc unimplemented")
+
+/**
+ * this proc must be idempotent.
+ *
+ * @return URL to use.
+ */
+/datum/asset_transport/proc/load_item(datum/asset_item/item)
+ CRASH("abstract proc unimplemented")
+
+/datum/asset_transport/proc/send_items(client/target, list/datum/asset_item/items)
+ CRASH("abstract proc unimplemented")
+
+//* Native - common behavior used to allow browse_rsc() usage, either as a fallback or as an alternative loader
+
+/datum/asset_transport/proc/send_anonymous_file_native(list/client/targets, file, ext)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ // we don't even need a hash, just the url
+ // only stuff like webroot cares about hash, for CDN caching purposes
+ var/static/notch = 0
+ // you're not going to send more than a million files a tick, are you now?
+ if(notch >= (1024 * 1024))
+ notch = 0
+ var/mangled_name = "[rand(1, 1000)]-[world.time]-[++notch]"
+ if(ext)
+ mangled_name = "[mangled_name].[ext]"
+ for(var/client/target as anything in targets)
+ target << browse_rsc(file, mangled_name)
+ return mangled_name
+
+/datum/asset_transport/proc/send_asset_item_native(list/client/targets, list/datum/asset_item/items)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ if(!islist(targets))
+ targets = list(targets)
+ if(!islist(items))
+ items = list(items)
+ for(var/client/target as anything in targets)
+ if(ismob(target))
+ target = target:client
+ var/list/datum/asset_item/to_send = list()
+ for(var/datum/asset_item/item as anything in items)
+ var/existing = target.asset_native_received[item.mangled_name]
+ if(existing)
+ if(existing != item.hash)
+ stack_trace("colliding hash when sending a native item to a client [target] <- [item] on mangled name [item.mangled_name].")
+ continue
+ to_send += item
+ for(var/datum/asset_item/sending as anything in to_send)
+ target.asset_native_received[sending.mangled_name] = sending.hash
+ target << browse_rsc(sending.file, sending.mangled_name)
+
+/**
+ * this proc must be idempotent.
+ *
+ * @return URL to use.
+ */
+/datum/asset_transport/proc/load_item_native(datum/asset_item/item)
+ SHOULD_NOT_OVERRIDE(TRUE)
+ return item.mangled_name
diff --git a/code/modules/asset_cache/assets/arcade.dm b/code/modules/asset_cache/assets/arcade.dm
index bcd23919f979..195d00e2b2bb 100644
--- a/code/modules/asset_cache/assets/arcade.dm
+++ b/code/modules/asset_cache/assets/arcade.dm
@@ -1,4 +1,4 @@
-/datum/asset/simple/arcade
+/datum/asset_pack/simple/arcade
assets = list(
"boss1.gif" = 'icons/ui_icons/arcade/boss1.gif',
"boss2.gif" = 'icons/ui_icons/arcade/boss2.gif',
diff --git a/code/modules/asset_cache/assets/artwork/crayons.dm b/code/modules/asset_cache/assets/artwork/crayons.dm
index 0967d5f4a609..317120b89375 100644
--- a/code/modules/asset_cache/assets/artwork/crayons.dm
+++ b/code/modules/asset_cache/assets/artwork/crayons.dm
@@ -1,6 +1,6 @@
-/datum/asset/spritesheet/crayons
+/datum/asset_pack/spritesheet/crayons
name = "crayon-graffiti"
-/datum/asset/spritesheet/crayons/create_spritesheets()
+/datum/asset_pack/spritesheet/crayons/generate()
for(var/datum/crayon_decal_meta/crayon_data in GLOB.crayon_data)
- InsertAll(crayon_data.name, crayon_data.icon_ref)
+ insert_all(crayon_data.name, crayon_data.icon_ref)
diff --git a/code/modules/asset_cache/assets/chat.dm b/code/modules/asset_cache/assets/chat.dm
index f33421e279ca..c587b39430b3 100644
--- a/code/modules/asset_cache/assets/chat.dm
+++ b/code/modules/asset_cache/assets/chat.dm
@@ -1,12 +1,12 @@
-/datum/asset/spritesheet/chat
+/datum/asset_pack/spritesheet/chat
name = "chat"
-/datum/asset/spritesheet/chat/create_spritesheets()
- InsertAll("emoji", EMOJI_SET)
- InsertAll("emoji", EMOJI32_SET)
+/datum/asset_pack/spritesheet/chat/generate()
+ insert_all("emoji", EMOJI_SET)
+ insert_all("emoji", EMOJI32_SET)
/*
// pre-loading all lanugage icons also helps to avoid meta
- InsertAll("language", 'icons/misc/language.dmi')
+ insert_all("language", 'icons/misc/language.dmi')
// catch languages which are pulling icons from another file
for(var/path in typesof(/datum/language))
var/datum/language/L = path
diff --git a/code/modules/asset_cache/assets/chemistry/bottles.dm b/code/modules/asset_cache/assets/chemistry/bottles.dm
index f875dbf8e58f..2597e1c5931d 100644
--- a/code/modules/asset_cache/assets/chemistry/bottles.dm
+++ b/code/modules/asset_cache/assets/chemistry/bottles.dm
@@ -1,4 +1,4 @@
-/datum/asset/spritesheet/simple/bottles
+/datum/asset_pack/spritesheet/simple/bottles
name = "bottles"
assets = list(
"bottle1" = 'icons/runtime/assets/medical/bottles/bottle1.png',
diff --git a/code/modules/asset_cache/assets/chemistry/patches.dm b/code/modules/asset_cache/assets/chemistry/patches.dm
index 4483167c263c..36724a9521cd 100644
--- a/code/modules/asset_cache/assets/chemistry/patches.dm
+++ b/code/modules/asset_cache/assets/chemistry/patches.dm
@@ -1,4 +1,4 @@
-/datum/asset/spritesheet/simple/patches
+/datum/asset_pack/spritesheet/simple/patches
name = "patches"
assets = list(
"patch" = 'icons/runtime/assets/medical/patches/patch.png',
diff --git a/code/modules/asset_cache/assets/chemistry/pills.dm b/code/modules/asset_cache/assets/chemistry/pills.dm
index 9c852b6e31b3..937cbb094ffb 100644
--- a/code/modules/asset_cache/assets/chemistry/pills.dm
+++ b/code/modules/asset_cache/assets/chemistry/pills.dm
@@ -1,4 +1,4 @@
-/datum/asset/spritesheet/simple/pills
+/datum/asset_pack/spritesheet/simple/pills
name = "pills"
assets = list(
"pill1" = 'icons/runtime/assets/medical/pills/pill1.png',
diff --git a/code/modules/asset_cache/assets/circuits.dm b/code/modules/asset_cache/assets/circuits.dm
index df9aa1fa6d89..eb2c46428f80 100644
--- a/code/modules/asset_cache/assets/circuits.dm
+++ b/code/modules/asset_cache/assets/circuits.dm
@@ -1,4 +1,4 @@
-/datum/asset/simple/circuit_assets
+/datum/asset_pack/simple/circuit_assets
assets = list(
"grid_background.png" = 'icons/ui_icons/tgui/grid_background.png'
)
diff --git a/code/modules/asset_cache/assets/common.dm b/code/modules/asset_cache/assets/common.dm
index 1b0fb301a192..8e0e2d1b8434 100644
--- a/code/modules/asset_cache/assets/common.dm
+++ b/code/modules/asset_cache/assets/common.dm
@@ -1,3 +1,8 @@
-/datum/asset/simple/namespaced/common
- assets = list("padlock.png" = 'icons/ui_icons/common/padlock.png')
- parents = list("common.css" = 'html/browser/common.css')
+/datum/asset_pack/simple/common
+ assets = list(
+ "padlock.png" = 'icons/ui_icons/common/padlock.png',
+ "common.css" = 'html/browser/common.css',
+ )
+ do_not_separate = TRUE
+ do_not_mangle = TRUE
+ absolute = TRUE
diff --git a/code/modules/asset_cache/assets/condiments.dm b/code/modules/asset_cache/assets/condiments.dm
index 58a9f2e60727..aea473817a38 100644
--- a/code/modules/asset_cache/assets/condiments.dm
+++ b/code/modules/asset_cache/assets/condiments.dm
@@ -1,4 +1,4 @@
-/datum/asset/spritesheet/simple/condiments
+/datum/asset_pack/spritesheet/simple/condiments
name = "condiments"
assets = list(
CONDIMASTER_STYLE_FALLBACK = 'icons/runtime/assets/condiments/emptycondiment.png',
diff --git a/code/modules/asset_cache/assets/contracts.dm b/code/modules/asset_cache/assets/contracts.dm
index 6ac1a9cb678e..d780f64a9ce9 100644
--- a/code/modules/asset_cache/assets/contracts.dm
+++ b/code/modules/asset_cache/assets/contracts.dm
@@ -1,4 +1,4 @@
-/datum/asset/simple/contracts
+/datum/asset_pack/simple/contracts
assets = list(
"bluespace.png" = 'icons/ui_icons/contracts/bluespace.png',
"destruction.png" = 'icons/ui_icons/contracts/destruction.png',
diff --git a/code/modules/asset_cache/assets/debug/fucky_wucky.dm b/code/modules/asset_cache/assets/debug/fucky_wucky.dm
index 53bdceb56e37..f5350bcb7e31 100644
--- a/code/modules/asset_cache/assets/debug/fucky_wucky.dm
+++ b/code/modules/asset_cache/assets/debug/fucky_wucky.dm
@@ -1,5 +1,5 @@
-/datum/asset/simple/fuckywucky
- keep_local_name = TRUE
+/datum/asset_pack/simple/fuckywucky
+ do_not_mangle = TRUE
assets = list(
"fuckywucky.png" = 'icons/runtime/assets/debug/fuckywucky.png',
)
diff --git a/code/modules/asset_cache/assets/fish.dm b/code/modules/asset_cache/assets/fishing.dm
similarity index 74%
rename from code/modules/asset_cache/assets/fish.dm
rename to code/modules/asset_cache/assets/fishing.dm
index 954690d9b56e..5be596cbafb5 100644
--- a/code/modules/asset_cache/assets/fish.dm
+++ b/code/modules/asset_cache/assets/fishing.dm
@@ -1,6 +1,6 @@
-/datum/asset/spritesheet/fish
+/datum/asset_pack/spritesheet/fish
name = "fish"
-/datum/asset/spritesheet/fish/create_spritesheets()
+/datum/asset_pack/spritesheet/fish/generate()
for (var/path in subtypesof(/obj/item/fish))
var/obj/item/fish/fish_type = path
var/fish_icon = initial(fish_type.icon)
@@ -8,10 +8,10 @@
var/id = sanitize_css_class_name("[fish_icon][fish_icon_state]")
if(sprites[id]) //no dupes
continue
- Insert(id, fish_icon, fish_icon_state)
+ insert(id, fish_icon, fish_icon_state)
-/datum/asset/simple/fishing_minigame
+/datum/asset_pack/simple/fishing_minigame
assets = list(
"fishing_background_default" = 'icons/interface/fishing/default.png',
"fishing_background_lavaland" = 'icons/interface/fishing/lavaland.png'
diff --git a/code/modules/asset_cache/assets/fontawesome.dm b/code/modules/asset_cache/assets/fontawesome.dm
index bc761a35c6d9..04e5f961956a 100644
--- a/code/modules/asset_cache/assets/fontawesome.dm
+++ b/code/modules/asset_cache/assets/fontawesome.dm
@@ -1,9 +1,11 @@
-/datum/asset/simple/namespaced/fontawesome
+/datum/asset_pack/simple/fontawesome
assets = list(
"fa-regular-400.eot" = 'html/font-awesome/webfonts/fa-regular-400.eot',
"fa-regular-400.woff" = 'html/font-awesome/webfonts/fa-regular-400.woff',
"fa-solid-900.eot" = 'html/font-awesome/webfonts/fa-solid-900.eot',
"fa-solid-900.woff" = 'html/font-awesome/webfonts/fa-solid-900.woff',
"v4shim.css" = 'html/font-awesome/css/v4-shims.min.css',
+ "font-awesome.css" = 'html/font-awesome/css/all.min.css',
)
- parents = list("font-awesome.css" = 'html/font-awesome/css/all.min.css')
+ do_not_separate = TRUE
+ do_not_mangle = TRUE
diff --git a/code/modules/asset_cache/assets/genetics.dm b/code/modules/asset_cache/assets/genetics.dm
index d74f10f631bb..e50e3284c02e 100644
--- a/code/modules/asset_cache/assets/genetics.dm
+++ b/code/modules/asset_cache/assets/genetics.dm
@@ -1,4 +1,4 @@
-/datum/asset/simple/genetics
+/datum/asset_pack/simple/genetics
assets = list(
"dna_discovered.gif" = 'icons/ui_icons/dna/dna_discovered.gif',
"dna_undiscovered.gif" = 'icons/ui_icons/dna/dna_undiscovered.gif',
diff --git a/code/modules/asset_cache/assets/headers.dm b/code/modules/asset_cache/assets/headers.dm
index 8ba6b3b51295..e43f0c0c90fc 100644
--- a/code/modules/asset_cache/assets/headers.dm
+++ b/code/modules/asset_cache/assets/headers.dm
@@ -1,4 +1,4 @@
-/datum/asset/simple/headers
+/datum/asset_pack/simple/headers
assets = list(
"alarm_green.gif" = 'icons/program_icons/alarm_green.gif',
"alarm_red.gif" = 'icons/program_icons/alarm_red.gif',
@@ -29,3 +29,4 @@
"borg_mon.gif" = 'icons/program_icons/borg_mon.gif',
"robotact.gif" = 'icons/program_icons/robotact.gif'
)
+ absolute = TRUE
diff --git a/code/modules/asset_cache/assets/inventory.dm b/code/modules/asset_cache/assets/inventory.dm
index a63fc45620f0..9b1cc6bdf3e0 100644
--- a/code/modules/asset_cache/assets/inventory.dm
+++ b/code/modules/asset_cache/assets/inventory.dm
@@ -1,4 +1,4 @@
-/datum/asset/simple/inventory
+/datum/asset_pack/simple/inventory
assets = list(
"inventory-glasses.png" = 'icons/ui_icons/inventory/glasses.png',
"inventory-head.png" = 'icons/ui_icons/inventory/head.png',
diff --git a/code/modules/asset_cache/assets/jquery.dm b/code/modules/asset_cache/assets/jquery.dm
index b7241fdb61d5..a21337fa5776 100644
--- a/code/modules/asset_cache/assets/jquery.dm
+++ b/code/modules/asset_cache/assets/jquery.dm
@@ -1,5 +1,5 @@
-/datum/asset/simple/jquery
- legacy = TRUE
+/datum/asset_pack/simple/jquery
+ absolute = TRUE
assets = list(
"jquery.min.js" = 'html/jquery/jquery.min.js',
)
diff --git a/code/modules/asset_cache/assets/language.dm b/code/modules/asset_cache/assets/language.dm
index 595f753f98f1..33830124c706 100644
--- a/code/modules/asset_cache/assets/language.dm
+++ b/code/modules/asset_cache/assets/language.dm
@@ -1,7 +1,7 @@
//this exists purely to avoid meta by pre-loading all language icons.
-/datum/asset/language
+/datum/asset_pack/language
-/datum/asset/language/register()
+/datum/asset_pack/language/register()
set waitfor = FALSE
for(var/path in typesof(/datum/language))
diff --git a/code/modules/asset_cache/assets/legacy_nanomaps.dm b/code/modules/asset_cache/assets/legacy_nanomaps.dm
index 63bbf58cd8b2..823a3b9fda8b 100644
--- a/code/modules/asset_cache/assets/legacy_nanomaps.dm
+++ b/code/modules/asset_cache/assets/legacy_nanomaps.dm
@@ -1,4 +1,4 @@
-/datum/asset/simple/nanomaps
+/datum/asset_pack/simple/nanomaps
// It REALLY doesnt matter too much if these arent up to date
// They are relatively big
assets = list(
diff --git a/code/modules/asset_cache/assets/legacy_nanoui.dm b/code/modules/asset_cache/assets/legacy_nanoui.dm
index 029e468fac43..27ec5b71d29c 100644
--- a/code/modules/asset_cache/assets/legacy_nanoui.dm
+++ b/code/modules/asset_cache/assets/legacy_nanoui.dm
@@ -1,7 +1,9 @@
-/datum/asset/simple/namespaced/nanoui
- keep_local_name = TRUE
+/datum/asset_pack/simple/nanoui
+ absolute = TRUE
+ do_not_mangle = TRUE
+ do_not_separate = TRUE
-/datum/asset/simple/namespaced/nanoui/register()
+/datum/asset_pack/simple/nanoui/generate()
var/list/dirs = list(
"nano/css/",
"nano/images/",
@@ -22,4 +24,4 @@
for(var/path in directory_walk_exts(dirs, exts, 0))
var/fname = filepath_extract_name(path)
assets[fname] = file(path)
- ..()
+ return ..()
diff --git a/code/modules/asset_cache/assets/legacy_vstation.dm b/code/modules/asset_cache/assets/legacy_vstation.dm
deleted file mode 100644
index 32f2b8a858ae..000000000000
--- a/code/modules/asset_cache/assets/legacy_vstation.dm
+++ /dev/null
@@ -1,29 +0,0 @@
-/datum/asset/simple/vstation_misc
- legacy = TRUE
- assets = list(
- "loading.gif" = 'html/images/loading.gif',
- "ntlogo.png" = 'html/images/ntlogo.png',
- "sglogo.png" = 'html/images/sglogo.png',
- "talisman.png" = 'html/images/talisman.png',
- "paper_bg.png" = 'html/images/paper_bg.png',
- "no_image32.png" = 'html/images/no_image32.png'
- )
-
-/datum/asset/simple/vstation_spideros
- legacy = TRUE
- assets = list(
- "sos_1.png" = 'icons/spideros_icons/sos_1.png',
- "sos_2.png" = 'icons/spideros_icons/sos_2.png',
- "sos_3.png" = 'icons/spideros_icons/sos_3.png',
- "sos_4.png" = 'icons/spideros_icons/sos_4.png',
- "sos_5.png" = 'icons/spideros_icons/sos_5.png',
- "sos_6.png" = 'icons/spideros_icons/sos_6.png',
- "sos_7.png" = 'icons/spideros_icons/sos_7.png',
- "sos_8.png" = 'icons/spideros_icons/sos_8.png',
- "sos_9.png" = 'icons/spideros_icons/sos_9.png',
- "sos_10.png" = 'icons/spideros_icons/sos_10.png',
- "sos_11.png" = 'icons/spideros_icons/sos_11.png',
- "sos_12.png" = 'icons/spideros_icons/sos_12.png',
- "sos_13.png" = 'icons/spideros_icons/sos_13.png',
- "sos_14.png" = 'icons/spideros_icons/sos_14.png'
- )
diff --git a/code/modules/asset_cache/assets/loadout.dm b/code/modules/asset_cache/assets/loadout.dm
index c0fe2af6c178..813d7f989398 100644
--- a/code/modules/asset_cache/assets/loadout.dm
+++ b/code/modules/asset_cache/assets/loadout.dm
@@ -1,7 +1,7 @@
-/datum/asset/spritesheet/loadout
+/datum/asset_pack/spritesheet/loadout
name = "loadout"
-/datum/asset/spritesheet/loadout/create_spritesheets()
+/datum/asset_pack/spritesheet/loadout/generate()
for(var/name in gear_datums)
var/datum/loadout_entry/entry = gear_datums[name]
var/item_id = entry.legacy_get_id()
@@ -13,4 +13,4 @@
var/obj/item/item_casted = item_path
var/item_icon = initial(item_casted.icon)
var/item_icon_state = initial(item_casted.icon_state)
- Insert(item_id, item_icon, item_icon_state)
+ insert(item_id, item_icon, item_icon_state)
diff --git a/code/modules/asset_cache/assets/materials.dm b/code/modules/asset_cache/assets/materials.dm
index 791ead24e31d..7034413302d9 100644
--- a/code/modules/asset_cache/assets/materials.dm
+++ b/code/modules/asset_cache/assets/materials.dm
@@ -1,5 +1,5 @@
-/datum/asset/spritesheet/materials
+/datum/asset_pack/spritesheet/materials
name = "sheetmaterials"
-/datum/asset/spritesheet/materials/create_spritesheets()
- InsertAll("stack", 'icons/interface/materials.dmi')
+/datum/asset_pack/spritesheet/materials/generate()
+ insert_all("stack", 'icons/interface/materials.dmi')
diff --git a/code/modules/asset_cache/assets/moods.dm b/code/modules/asset_cache/assets/moods.dm
index ebe3301c0856..4dcc3c3b8272 100644
--- a/code/modules/asset_cache/assets/moods.dm
+++ b/code/modules/asset_cache/assets/moods.dm
@@ -1,14 +1,15 @@
-/datum/asset/spritesheet/moods
+/datum/asset_pack/spritesheet/moods
name = "moods"
var/iconinserted = 1
-/datum/asset/spritesheet/moods/create_spritesheets()
+/datum/asset_pack/spritesheet/moods/generate()
+ . = ..()
for(var/i in 1 to 9)
var/target_to_insert = "mood"+"[iconinserted]"
- Insert(target_to_insert, 'icons/screen/screen_gen.dmi', target_to_insert)
+ insert(target_to_insert, 'icons/screen/screen_gen.dmi', target_to_insert)
iconinserted++
-/datum/asset/spritesheet/moods/ModifyInserted(icon/pre_asset)
+/datum/asset_pack/spritesheet/moods/modify_inserted(icon/pre_asset)
var/blended_color
switch(iconinserted)
if(1)
diff --git a/code/modules/asset_cache/assets/notes.dm b/code/modules/asset_cache/assets/notes.dm
index aec6838f9679..dfa5d0d791ec 100644
--- a/code/modules/asset_cache/assets/notes.dm
+++ b/code/modules/asset_cache/assets/notes.dm
@@ -1,4 +1,4 @@
-/datum/asset/simple/notes
+/datum/asset_pack/simple/notes
assets = list(
"high_button.png" = 'icons/ui_icons/notes/high_button.png',
"medium_button.png" = 'icons/ui_icons/notes/medium_button.png',
diff --git a/code/modules/asset_cache/assets/orbit.dm b/code/modules/asset_cache/assets/orbit.dm
index 7d0e0d98a0e6..f461db24d5a4 100644
--- a/code/modules/asset_cache/assets/orbit.dm
+++ b/code/modules/asset_cache/assets/orbit.dm
@@ -1,4 +1,4 @@
-/datum/asset/simple/orbit
+/datum/asset_pack/simple/orbit
assets = list(
"ghost.png" = 'icons/ui_icons/orbit/ghost.png'
)
diff --git a/code/modules/asset_cache/assets/paper.dm b/code/modules/asset_cache/assets/paper.dm
index 8cf50b8f3dc0..63d2b9b029ea 100644
--- a/code/modules/asset_cache/assets/paper.dm
+++ b/code/modules/asset_cache/assets/paper.dm
@@ -1,4 +1,4 @@
-/datum/asset/spritesheet/simple/paper
+/datum/asset_pack/spritesheet/simple/paper
name = "paper"
assets = list(
"stamp-clown" = 'icons/stamp_icons/large_stamp-clown.png',
@@ -17,3 +17,6 @@
"stamp-centcom" = 'icons/stamp_icons/large_stamp-centcom.png',
"stamp-syndicate" = 'icons/stamp_icons/large_stamp-syndicate.png'
)
+ do_not_separate = TRUE
+ absolute = TRUE
+ do_not_mangle = TRUE
diff --git a/code/modules/asset_cache/assets/pda.dm b/code/modules/asset_cache/assets/pda.dm
index 8207cef98680..206f34741373 100644
--- a/code/modules/asset_cache/assets/pda.dm
+++ b/code/modules/asset_cache/assets/pda.dm
@@ -1,4 +1,4 @@
-/datum/asset/spritesheet/simple/pda
+/datum/asset_pack/spritesheet/simple/pda
name = "pda"
assets = list(
"atmos" = 'icons/pda_icons/pda_atmos.png',
@@ -32,3 +32,6 @@
"emoji" = 'icons/pda_icons/pda_emoji.png',
"droneblacklist" = 'icons/pda_icons/pda_droneblacklist.png',
)
+ do_not_separate = TRUE
+ absolute = TRUE
+ do_not_mangle = TRUE
diff --git a/code/modules/asset_cache/assets/permission.dm b/code/modules/asset_cache/assets/permission.dm
index 6cc6c0e754bb..da5f1572af17 100644
--- a/code/modules/asset_cache/assets/permission.dm
+++ b/code/modules/asset_cache/assets/permission.dm
@@ -1,11 +1,5 @@
-/datum/asset/simple/permissions
+/datum/asset_pack/simple/permissions
assets = list(
"search.js" = 'html/admin/search.js',
"panels.css" = 'html/admin/panels.css'
)
-
-/datum/asset/group/permissions
- children = list(
- /datum/asset/simple/permissions,
- /datum/asset/simple/namespaced/common
- )
diff --git a/code/modules/asset_cache/assets/pipes.dm b/code/modules/asset_cache/assets/pipes.dm
index 2fe92ccadba4..e3610806f318 100644
--- a/code/modules/asset_cache/assets/pipes.dm
+++ b/code/modules/asset_cache/assets/pipes.dm
@@ -1,7 +1,7 @@
-/datum/asset/spritesheet/pipes
+/datum/asset_pack/spritesheet/pipes
name = "pipes"
-/datum/asset/spritesheet/pipes/create_spritesheets()
+/datum/asset_pack/spritesheet/pipes/generate()
for(var/each in list('icons/obj/pipe-item.dmi', 'icons/obj/pipes/disposal.dmi'))
////for (var/each in list('icons/obj/atmospherics/pipes/pipe_item.dmi', 'icons/obj/atmospherics/pipes/disposal.dmi', 'icons/obj/atmospherics/pipes/transit_tube.dmi', 'icons/obj/plumbing/fluid_ducts.dmi'))
- InsertAll("", each, GLOB.alldirs)
+ insert_all("", each, GLOB.alldirs)
diff --git a/code/modules/asset_cache/assets/portraits.dm b/code/modules/asset_cache/assets/portraits.dm
index 81bb0e058a63..dad5993e4304 100644
--- a/code/modules/asset_cache/assets/portraits.dm
+++ b/code/modules/asset_cache/assets/portraits.dm
@@ -1,7 +1,7 @@
-/datum/asset/simple/portraits
+/datum/asset_pack/simple/portraits
assets = list()
-/datum/asset/simple/portraits/New()
+/datum/asset_pack/simple/portraits/New()
if(!length(SSpersistent_paintings.paintings))
return
for(var/datum/painting/portrait as anything in SSpersistent_paintings.paintings)
diff --git a/code/modules/asset_cache/assets/radar.dm b/code/modules/asset_cache/assets/radar.dm
index cef2679a92dc..06cc1e6056db 100644
--- a/code/modules/asset_cache/assets/radar.dm
+++ b/code/modules/asset_cache/assets/radar.dm
@@ -1,4 +1,4 @@
-/datum/asset/simple/radar_assets
+/datum/asset_pack/simple/radar_assets
assets = list(
"ntosradarbackground.png" = 'icons/ui_icons/tgui/ntosradar_background.png',
"ntosradarpointer.png" = 'icons/ui_icons/tgui/ntosradar_pointer.png',
diff --git a/code/modules/asset_cache/assets/research_designs.dm b/code/modules/asset_cache/assets/research_designs.dm
index c2c26affbb54..88f02da28b54 100644
--- a/code/modules/asset_cache/assets/research_designs.dm
+++ b/code/modules/asset_cache/assets/research_designs.dm
@@ -1,8 +1,8 @@
// Representative icons for each research design
-/datum/asset/spritesheet/research_designs
+/datum/asset_pack/spritesheet/research_designs
name = "design"
-/datum/asset/spritesheet/research_designs/create_spritesheets()
+/datum/asset_pack/spritesheet/research_designs/generate()
for (var/path in subtypesof(/datum/design))
var/datum/design/D = path
diff --git a/code/modules/asset_cache/assets/safe.dm b/code/modules/asset_cache/assets/safe.dm
index b1d6ba9a8aac..60417650a271 100644
--- a/code/modules/asset_cache/assets/safe.dm
+++ b/code/modules/asset_cache/assets/safe.dm
@@ -1,4 +1,4 @@
-/datum/asset/simple/safe
+/datum/asset_pack/simple/safe
assets = list(
"safe_dial.png" = 'icons/ui_icons/safe/safe_dial.png'
)
diff --git a/code/modules/asset_cache/assets/supplypods.dm b/code/modules/asset_cache/assets/supplypods.dm
index fd4c961f103e..eca2766798cd 100644
--- a/code/modules/asset_cache/assets/supplypods.dm
+++ b/code/modules/asset_cache/assets/supplypods.dm
@@ -1,7 +1,7 @@
-/datum/asset/spritesheet/supplypods
+/datum/asset_pack/spritesheet/supplypods
name = "supplypods"
-/datum/asset/spritesheet/supplypods/create_spritesheets()
+/datum/asset_pack/spritesheet/supplypods/generate()
for (var/style in 1 to length(GLOB.podstyles))
if (style == STYLE_SEETHROUGH)
Insert("pod_asset[style]", icon('icons/obj/supplypods.dmi' , "seethrough-icon"))
diff --git a/code/modules/asset_cache/assets/tgfont.dm b/code/modules/asset_cache/assets/tgfont.dm
index efa98e22e2c0..9157e7924324 100644
--- a/code/modules/asset_cache/assets/tgfont.dm
+++ b/code/modules/asset_cache/assets/tgfont.dm
@@ -1,8 +1,7 @@
-/datum/asset/simple/namespaced/tgfont
+/datum/asset_pack/simple/tgfont
assets = list(
"tgfont.eot" = file("tgui/packages/tgfont/static/tgfont.eot"),
"tgfont.woff2" = file("tgui/packages/tgfont/static/tgfont.woff2"),
- )
- parents = list(
"tgfont.css" = file("tgui/packages/tgfont/static/tgfont.css"),
)
+ do_not_separate = TRUE
diff --git a/code/modules/asset_cache/assets/tgui.dm b/code/modules/asset_cache/assets/tgui.dm
index 9c79925602c7..4f89ff984fbb 100644
--- a/code/modules/asset_cache/assets/tgui.dm
+++ b/code/modules/asset_cache/assets/tgui.dm
@@ -1,12 +1,12 @@
-/datum/asset/simple/tgui
- keep_local_name = TRUE
+/datum/asset_pack/simple/tgui
+ // keep_local_name = TRUE
assets = list(
"tgui.bundle.js" = file("tgui/public/tgui.bundle.js"),
"tgui.bundle.css" = file("tgui/public/tgui.bundle.css"),
)
-/datum/asset/simple/tgui_panel
- keep_local_name = TRUE
+/datum/asset_pack/simple/tgui_panel
+ // keep_local_name = TRUE
assets = list(
"tgui-panel.bundle.js" = file("tgui/public/tgui-panel.bundle.js"),
"tgui-panel.bundle.css" = file("tgui/public/tgui-panel.bundle.css"),
diff --git a/code/modules/asset_cache/assets/uplink.dm b/code/modules/asset_cache/assets/uplink.dm
index 24f6e2f49b5a..e8356350c898 100644
--- a/code/modules/asset_cache/assets/uplink.dm
+++ b/code/modules/asset_cache/assets/uplink.dm
@@ -1,8 +1,8 @@
/// Sends information needed for uplinks
-/datum/asset/json/uplink
+/datum/asset_pack/json/uplink
name = "uplink"
-/datum/asset/json/uplink/generate()
+/datum/asset_pack/json/uplink/generate()
var/list/data = list()
var/list/categories = list()
var/list/items = list()
diff --git a/code/modules/asset_cache/assets/vending.dm b/code/modules/asset_cache/assets/vending.dm
index 87fddfc1132c..b21a46090fe2 100644
--- a/code/modules/asset_cache/assets/vending.dm
+++ b/code/modules/asset_cache/assets/vending.dm
@@ -1,7 +1,7 @@
-/datum/asset/spritesheet/vending
+/datum/asset_pack/spritesheet/vending
name = "vending"
-/datum/asset/spritesheet/vending/create_spritesheets()
+/datum/asset_pack/spritesheet/vending/generate()
for (var/k in GLOB.vending_products)
var/atom/item = k
if (!ispath(item, /atom))
diff --git a/code/modules/asset_cache/assets/vv.dm b/code/modules/asset_cache/assets/vv.dm
index 0dd974c7c794..ecfe99c84801 100644
--- a/code/modules/asset_cache/assets/vv.dm
+++ b/code/modules/asset_cache/assets/vv.dm
@@ -1,4 +1,4 @@
-/datum/asset/simple/vv
+/datum/asset_pack/simple/vv
assets = list(
"view_variables.css" = 'html/admin/view_variables.css'
)
diff --git a/code/modules/asset_cache/client.dm b/code/modules/asset_cache/client.dm
new file mode 100644
index 000000000000..c5e7a0d506cd
--- /dev/null
+++ b/code/modules/asset_cache/client.dm
@@ -0,0 +1,90 @@
+/client
+ /// asset cache: filename = md5, for things already sent to the client
+ /// this is only for browse_rsc()'d assets.
+ var/list/asset_native_received = list()
+ /// used for browse_queue_fluhs()
+ var/list/asset_flush_jobs = list()
+ /// last flush job id
+ var/asset_flush_last_id
+
+/client/on_new_hook_stability_checks()
+ // ensure asset cache is there
+ INVOKE_ASYNC(src, PROC_REF(warn_if_no_asset_cache_browser))
+ return ..()
+
+/client/proc/warn_if_no_asset_cache_browser()
+ if(!winexists(src, "asset_cache_browser")) // The client is using a custom skin, tell them.
+ to_chat(src, "Unable to access asset cache browser, if you are using a custom skin file, please allow DS to download the updated version, if you are not, then make a bug report. This is not a critical issue but can cause issues with resource downloading, as it is impossible to know when extra resources arrived to you.")
+
+/client/on_topic_hook(raw_href, list/href_list, raw_src)
+ . = ..()
+ if(.)
+ return
+
+ if(href_list["asset_cache_confirm_arrival"])
+ if(asset_cache_confirm_arrival(href_list["asset_cache_confirm_arrival"]))
+ return TRUE
+ else
+ if(!isnull(asset_flush_jobs))
+ // it's a valid job, it might be byond bug ID:2256651
+ to_chat(src, "An error has been detected in how your client is receiving resources. Attempting to correct.... (If you keep seeing these messages you might want to close byond and reconnect)")
+ src << browse("...", "window=asset_cache_browser")
+ return TRUE
+ else
+ // what the fuck are they doing?
+ security_kick("A fatal issue occurred during asset send, or your client kept spamming receive confirmations \
+ after acknowledgement. Please reconnect after clearing your cache.", TRUE, TRUE)
+ return TRUE
+
+/**
+ * Process asset cache client topic calls for `"asset_cache_confirm_arrival=[INT]"`
+ *
+ * @return TRUE if it was a valid arrival
+ */
+/client/proc/asset_cache_confirm_arrival(job_id)
+ var/asset_cache_job = round(text2num(job_id))
+ //because we skip the limiter, we have to make sure this is a valid arrival and not somebody tricking us into letting them append to a list without limit.
+ if(!isnull(asset_flush_jobs["[asset_cache_job]"]))
+ asset_flush_jobs["[asset_cache_job]"] = TRUE
+ return TRUE
+ return FALSE
+
+/// Blocks until all currently sending browse and browse_rsc assets have been sent.
+/// Due to byond limitations, this proc will sleep for 1 client round trip even if the client has no pending asset sends.
+/// This proc will return an untrue value if it had to return before confirming the send, such as timeout or the client going away.
+/client/proc/asset_cache_flush_browse_queue(timeout = 50)
+ var/job = ++asset_flush_last_id
+ if(asset_flush_last_id >= SHORT_REAL_LIMIT)
+ asset_flush_last_id = 0
+ var/timeout_time = world.time + timeout
+ src << browse({""}, "window=asset_cache_browser&file=asset_cache_send_verify.htm")
+
+ asset_flush_jobs["[job]"] = FALSE
+ while(!asset_flush_jobs["[job]"] && world.time < timeout_time) // Reception is handled in Topic()
+ stoplag(1) // Lock up the caller until this is received.
+
+ return asset_flush_jobs["[job]"]
+
+/**
+ * sends a list of asset packs intelligently without locking up brwose queue
+ *
+ * used for preloading
+ *
+ * this is on client for gc optimizations as src will be set to ourselves
+ *
+ * * do not use unless you know what you're doing
+ */
+/client/proc/asset_cache_native_preload(list/datum/asset_pack/packs, flush_on_how_many_packs = 3)
+ var/datum/asset_transport/cached_transport = SSassets.transport
+ var/packs_before_flush = flush_on_how_many_packs
+ for(var/datum/asset_pack/pack as anything in packs)
+ if(SSassets.transport != cached_transport)
+ return
+ if(!pack.is_pushed_to_transport())
+ continue
+ cached_transport.send_asset_item_native(src, pack.packed_items)
+ stoplag(0) // do not lock up browse queue
+ if(!(--packs_before_flush))
+ packs_before_flush = flush_on_how_many_packs
+ if(!asset_cache_flush_browse_queue(5 SECONDS))
+ stack_trace("aborted native preload for [src] because they timed out on browse queue flush. did something break, or do they just have bad internet?")
diff --git a/code/modules/asset_cache/packs/changelog_item.dm b/code/modules/asset_cache/packs/changelog_item.dm
new file mode 100644
index 000000000000..495434a65fbe
--- /dev/null
+++ b/code/modules/asset_cache/packs/changelog_item.dm
@@ -0,0 +1,12 @@
+
+/datum/asset_pack/changelog_item
+ abstract_type = /datum/asset_pack/changelog_item
+ var/item_filename
+
+/datum/asset_pack/changelog_item/New(date)
+ item_filename = SANITIZE_FILENAME("[date].yml")
+
+/datum/asset_pack/changelog_item/register()
+ return list(
+ item_filename = file("html/changelogs/archive/" + item_filename),
+ )
diff --git a/code/modules/asset_cache/packs/json.dm b/code/modules/asset_cache/packs/json.dm
new file mode 100644
index 000000000000..d849cd8a242f
--- /dev/null
+++ b/code/modules/asset_cache/packs/json.dm
@@ -0,0 +1,14 @@
+
+/// A subtype to generate a JSON file from a list
+/datum/asset_pack/json
+ abstract_type = /datum/asset_pack/json
+ /// The filename, will be suffixed with ".json"
+ var/name
+
+/datum/asset_pack/json/register(generation)
+ var/filename = "tmp/assets/[name].json"
+ fdel(filename)
+ text2file(json_encode(generate()), filename)
+ return list(
+ "[name].json" = file(filename),
+ )
diff --git a/code/modules/asset_cache/packs/simple.dm b/code/modules/asset_cache/packs/simple.dm
new file mode 100644
index 000000000000..fe614c5acb9f
--- /dev/null
+++ b/code/modules/asset_cache/packs/simple.dm
@@ -0,0 +1,24 @@
+
+/// If you don't need anything complicated.
+/datum/asset_pack/simple
+ abstract_type = /datum/asset_pack/simple
+ /**
+ * List of assets for this datum in the form of:
+ * * filename = file
+ * At runtime the asset file will be converted into a asset_item datum.
+ */
+ var/assets = list()
+
+/datum/asset_pack/simple/New(id, list/assets)
+ ..(id)
+ if(!isnull(assets))
+ src.assets = assets.Copy()
+
+/datum/asset_pack/simple/register()
+ . = list()
+ for(var/key in assets)
+ var/value = assets[key]
+ if(isfile(value))
+ .[key] = value
+ else
+ .[key] = file(value)
diff --git a/code/modules/asset_cache/packs/spritesheet.dm b/code/modules/asset_cache/packs/spritesheet.dm
new file mode 100644
index 000000000000..7b83ff7c6a4b
--- /dev/null
+++ b/code/modules/asset_cache/packs/spritesheet.dm
@@ -0,0 +1,198 @@
+
+/**
+ * spritesheet implementation - coalesces various icons into a single .png file
+ * and uses CSS to select icons out of that file - saves on transferring some
+ * 1400-odd individual PNG files
+ *
+ * To use, use classes of "[name][size_key]" and the state name used in insert().
+ * If you used insert_all(), don't forget the prefix.
+ *
+ * Example:
+ * In tgui, usually would be className={classes(['sheetmaterials32x32', 'glass-3'])}
+ */
+#define SPR_SIZE 1
+#define SPR_IDX 2
+
+#define SPRSZ_COUNT 1
+#define SPRSZ_ICON 2
+#define SPRSZ_STRIPPED 3
+
+/datum/asset_pack/spritesheet
+ abstract_type = /datum/asset_pack/spritesheet
+ do_not_separate = TRUE
+ /// so unfortunately we can't mangle it due to the fact that
+ /// * the .css needs to know where the image is
+ /// * we don't know what to name the image until the entire pack is encoded
+ /// * so the easiest way is to set do_not_mangle to TRUE for now, and deal with it later
+ /// todo: completely unnecessarily overengineer some more and fix this in 2025 ~silicons
+ do_not_mangle = TRUE
+ /// the spritesheet's name
+ var/name
+
+ /**
+ * List of arguments to pass into do_insert.
+ * Exists so we can queue icon insertion, mostly for stuff like preferences.
+ */
+ var/list/to_generate = list()
+
+ /// "32x32" -> list(10, icon/normal, icon/stripped)
+ var/list/sizes = list()
+ /// "foo_bar" -> list("32x32", 5)
+ var/list/sprites = list()
+
+/datum/asset_pack/spritesheet/register(generation)
+ return construct()
+
+/datum/asset_pack/spritesheet/proc/get_css_url()
+ return get_url("spritesheet_[name].css")
+
+/datum/asset_pack/spritesheet/proc/construct()
+ . = list()
+
+ while(length(to_generate))
+ var/list/stored_args = to_generate[to_generate.len]
+ to_generate.len--
+ do_insert(arglist(stored_args))
+ CHECK_TICK
+
+ ensure_stripped()
+ for(var/size_id in sizes)
+ var/size = sizes[size_id]
+ .["[name]_[size_id].png"] = size[SPRSZ_STRIPPED]
+
+ var/fname = "tmp/assets/spritesheets/[name].css"
+ fdel(fname)
+ text2file(generate_css(), fname)
+ var/res_name = "spritesheet_[name].css"
+ .[res_name] = fname
+
+/datum/asset_pack/spritesheet/proc/ensure_stripped(sizes_to_strip = sizes)
+ for(var/size_id in sizes_to_strip)
+ var/size = sizes[size_id]
+ if (size[SPRSZ_STRIPPED])
+ continue
+
+ // save flattened version
+ var/fname = "tmp/assets/spritesheets/[name]_[size_id].png"
+ fdel(fname)
+ fcopy(size[SPRSZ_ICON], fname)
+ var/error = rustg_dmi_strip_metadata(fname)
+ if(length(error))
+ stack_trace("Failed to strip [name]_[size_id].png: [error]")
+ size[SPRSZ_STRIPPED] = icon(fname)
+
+/datum/asset_pack/spritesheet/proc/generate_css()
+ var/list/out = list()
+
+ for (var/size_id in sizes)
+ var/size = sizes[size_id]
+ var/icon/tiny = size[SPRSZ_ICON]
+ out += ".[name][size_id]{display:inline-block;width:[tiny.Width()]px;height:[tiny.Height()]px;background:url('[get_background_url("[name]_[size_id].png")]') no-repeat;}"
+
+ for (var/sprite_id in sprites)
+ var/sprite = sprites[sprite_id]
+ var/size_id = sprite[SPR_SIZE]
+ var/idx = sprite[SPR_IDX]
+ var/size = sizes[size_id]
+
+ var/icon/tiny = size[SPRSZ_ICON]
+ var/icon/big = size[SPRSZ_STRIPPED]
+ var/per_line = big.Width() / tiny.Width()
+ var/x = (idx % per_line) * tiny.Width()
+ var/y = round(idx / per_line) * tiny.Height()
+
+ out += ".[name][size_id].[sprite_id]{background-position:-[x]px -[y]px;}"
+
+ return out.Join("\n")
+
+/// Returns the URL to put in the background:url of the CSS asset
+/datum/asset_pack/spritesheet/proc/get_background_url(image_name)
+ return image_name
+
+/**
+ * LEMON NOTE
+ * A GOON CODER SAYS BAD ICON ERRORS CAN BE THROWN BY THE "ICON CACHE"
+ * APPARENTLY IT MAKES ICONS IMMUTABLE
+ * LOOK INTO USING THE MUTABLE APPEARANCE PATTERN HERE
+ *
+ * neither prefixes nor states may have spaces!
+ */
+/datum/asset_pack/spritesheet/proc/do_insert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE)
+ I = icon(I, icon_state=icon_state, dir=dir, frame=frame, moving=moving)
+ if (!I || !length(icon_states(I))) // That direction or state doesn't exist!
+ return
+ // Any sprite modifications we want to do (aka, coloring a greyscaled asset)
+ I = modify_inserted(I)
+ var/size_id = "[I.Width()]x[I.Height()]"
+ var/size = sizes[size_id]
+
+ if (sprites[sprite_name])
+ CRASH("duplicate sprite \"[sprite_name]\" in sheet [name] ([type])")
+
+ if (size)
+ var/position = size[SPRSZ_COUNT]++
+ var/icon/sheet = size[SPRSZ_ICON]
+ var/icon/sheet_copy = icon(sheet)
+ size[SPRSZ_STRIPPED] = null
+ sheet_copy.Insert(I, icon_state=sprite_name)
+ size[SPRSZ_ICON] = sheet_copy
+ sprites[sprite_name] = list(size_id, position)
+ else
+ sizes[size_id] = size = list(1, I, null)
+ sprites[sprite_name] = list(size_id, 0)
+
+/**
+ * neither prefixes nor states may have spaces!
+ */
+/datum/asset_pack/spritesheet/proc/insert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE)
+ to_generate += list(args.Copy())
+
+/**
+ * neither prefixes nor states may have spaces!
+ */
+/datum/asset_pack/spritesheet/proc/insert_all(prefix, icon/I, list/directions)
+ if (length(prefix))
+ prefix = "[prefix]-"
+
+ if (!directions)
+ directions = list(SOUTH)
+
+ for (var/icon_state_name in icon_states(I))
+ for (var/direction in directions)
+ var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]-" : ""
+ insert("[prefix][prefix2][icon_state_name]", I, icon_state=icon_state_name, dir=direction)
+
+/**
+ * A simple proc handing the Icon for you to modify before it gets turned into an asset.
+ *
+ * Arguments:
+ * * I: icon being turned into an asset
+ */
+/datum/asset_pack/spritesheet/proc/modify_inserted(icon/pre_asset)
+ return pre_asset
+
+/**
+ * todo: deprecated; logic should be tgui-side.
+ */
+/datum/asset_pack/spritesheet/proc/icon_tag(sprite_name)
+ var/sprite = sprites[sprite_name]
+ if (!sprite)
+ return null
+ var/size_id = sprite[SPR_SIZE]
+ return {""}
+
+/**
+ * todo: deprecated; logic should be tgui-side.
+ */
+/datum/asset_pack/spritesheet/proc/icon_class_name(sprite_name)
+ var/sprite = sprites[sprite_name]
+ if (!sprite)
+ return null
+ var/size_id = sprite[SPR_SIZE]
+ return {"[name][size_id] [sprite_name]"}
+
+#undef SPR_SIZE
+#undef SPR_IDX
+#undef SPRSZ_COUNT
+#undef SPRSZ_ICON
+#undef SPRSZ_STRIPPED
diff --git a/code/modules/asset_cache/packs/spritesheet/simple.dm b/code/modules/asset_cache/packs/spritesheet/simple.dm
new file mode 100644
index 000000000000..18ed7c3fc808
--- /dev/null
+++ b/code/modules/asset_cache/packs/spritesheet/simple.dm
@@ -0,0 +1,15 @@
+/datum/asset_pack/spritesheet/simple
+ abstract_type = /datum/asset_pack/spritesheet/simple
+ /**
+ * key = value list of icon_state = icon
+ *
+ * e.g.
+ * "enzyme" = 'icons/runtime/assets/condiments/enzyme.png'
+ *
+ * keys must be unique, obviously.
+ */
+ var/list/assets
+
+/datum/asset_pack/spritesheet/simple/generate()
+ for (var/key in assets)
+ insert(key, assets[key])
diff --git a/code/modules/asset_cache/transports/asset_transport.dm b/code/modules/asset_cache/transports/asset_transport.dm
deleted file mode 100644
index 82375ce1a6fd..000000000000
--- a/code/modules/asset_cache/transports/asset_transport.dm
+++ /dev/null
@@ -1,154 +0,0 @@
-/// When sending mutiple assets, how many before we give the client a quaint little sending resources message
-#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8
-
-/// Base browse_rsc asset transport
-/datum/asset_transport
- var/name = "Simple browse_rsc asset transport"
- var/static/list/preload
- /// Don't mutate the filename of assets when sending via browse_rsc.
- /// This is to make it easier to debug issues with assets, and allow server operators to bypass issues that make it to production.
- /// If turning this on fixes asset issues, something isn't using get_asset_url and the asset isn't marked legacy, fix one of those.
- var/dont_mutate_filenames = FALSE
-
-/// Called when the transport is loaded by the config controller, not called on the default transport unless it gets loaded by a config change.
-/datum/asset_transport/proc/Load()
- if (CONFIG_GET(flag/asset_simple_preload))
- for(var/client/C in GLOB.clients)
- addtimer(CALLBACK(src, PROC_REF(send_assets_slow), C, preload), 1 SECONDS)
-
-/// Initialize - Called when SSassets initializes.
-/datum/asset_transport/proc/Initialize(list/assets)
- preload = assets.Copy()
- if (!CONFIG_GET(flag/asset_simple_preload))
- return
- for(var/client/C in GLOB.clients)
- addtimer(CALLBACK(src, PROC_REF(send_assets_slow), C, preload), 1 SECONDS)
-
-
-/// Register a browser asset with the asset cache system
-/// asset_name - the identifier of the asset
-/// asset - the actual asset file (or an asset_cache_item datum)
-/// returns a /datum/asset_cache_item.
-/// mutiple calls to register the same asset under the same asset_name return the same datum
-/datum/asset_transport/proc/register_asset(asset_name, asset)
- var/datum/asset_cache_item/ACI = asset
- if (!istype(ACI))
- ACI = new(asset_name, asset)
- if (!ACI || !ACI.hash)
- CRASH("ERROR: Invalid asset: [asset_name]:[asset]:[ACI]")
- if (SSassets.cache[asset_name])
- var/datum/asset_cache_item/OACI = SSassets.cache[asset_name]
- OACI.legacy = ACI.legacy = (ACI.legacy|OACI.legacy)
- OACI.namespace_parent = ACI.namespace_parent = (ACI.namespace_parent | OACI.namespace_parent)
- OACI.namespace = OACI.namespace || ACI.namespace
- if (OACI.hash != ACI.hash)
- var/error_msg = "ERROR: new asset added to the asset cache with the same name as another asset: [asset_name] existing asset hash: [OACI.hash] new asset hash:[ACI.hash]"
- stack_trace(error_msg)
- log_asset(error_msg)
- else
- if (length(ACI.namespace))
- return ACI
- return OACI
-
- SSassets.cache[asset_name] = ACI
- return ACI
-
-
-/// Returns a url for a given asset.
-/// asset_name - Name of the asset.
-/// asset_cache_item - asset cache item datum for the asset, optional, overrides asset_name
-/datum/asset_transport/proc/get_asset_url(asset_name, datum/asset_cache_item/asset_cache_item)
- if (!istype(asset_cache_item))
- asset_cache_item = SSassets.cache[asset_name]
- // To ensure code that breaks on cdns breaks in local testing, we only
- // use the normal filename on legacy assets and name space assets.
- var/keep_local_name = dont_mutate_filenames \
- || asset_cache_item.legacy \
- || asset_cache_item.keep_local_name \
- || (asset_cache_item.namespace && !asset_cache_item.namespace_parent)
- if (keep_local_name)
- return url_encode(asset_cache_item.name)
- return url_encode("asset.[asset_cache_item.hash][asset_cache_item.ext]")
-
-
-/// Sends a list of browser assets to a client
-/// client - a client or mob
-/// asset_list - A list of asset filenames to be sent to the client. Can optionally be assoicated with the asset's asset_cache_item datum.
-/// Returns TRUE if any assets were sent.
-/datum/asset_transport/proc/send_assets(client/client, list/asset_list)
- if (!istype(client))
- if (ismob(client))
- var/mob/M = client
- if (M.client)
- client = M.client
- else //no stacktrace because this will mainly happen because the client went away
- return
- else
- CRASH("Invalid argument: client: `[client]`")
- if (!islist(asset_list))
- asset_list = list(asset_list)
- var/list/unreceived = list()
-
- for (var/asset_name in asset_list)
- var/datum/asset_cache_item/ACI = asset_list[asset_name]
- if (!istype(ACI) && !(ACI = SSassets.cache[asset_name]))
- log_asset("ERROR: can't send asset `[asset_name]`: unregistered or invalid state: `[ACI]`")
- continue
- var/asset_file = ACI.resource
- if (!asset_file)
- log_asset("ERROR: can't send asset `[asset_name]`: invalid registered resource: `[ACI.resource]`")
- continue
-
- var/asset_hash = ACI.hash
- var/new_asset_name = asset_name
- var/keep_local_name = dont_mutate_filenames \
- || ACI.legacy \
- || ACI.keep_local_name \
- || (ACI.namespace && !ACI.namespace_parent)
- if (!keep_local_name)
- new_asset_name = "asset.[ACI.hash][ACI.ext]"
- if (client.sent_assets[new_asset_name] == asset_hash)
- if (GLOB.Debug2)
- log_asset("DEBUG: Skipping send of `[asset_name]` (as `[new_asset_name]`) for `[client]` because it already exists in the client's sent_assets list")
- continue
- unreceived[asset_name] = ACI
-
- if (unreceived.len)
- if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT)
- to_chat(client, SPAN_INFOPLAIN("Sending Resources..."))
-
- for (var/asset_name in unreceived)
- var/new_asset_name = asset_name
- var/datum/asset_cache_item/ACI = unreceived[asset_name]
- var/keep_local_name = dont_mutate_filenames \
- || ACI.legacy \
- || ACI.keep_local_name \
- || (ACI.namespace && !ACI.namespace_parent)
- if (!keep_local_name)
- new_asset_name = "asset.[ACI.hash][ACI.ext]"
- log_asset("Sending asset `[asset_name]` to client `[client]` as `[new_asset_name]`")
- client << browse_rsc(ACI.resource, new_asset_name)
-
- client.sent_assets[new_asset_name] = ACI.hash
-
- addtimer(CALLBACK(client, TYPE_PROC_REF(/client, asset_cache_update_json)), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE)
- return TRUE
- return FALSE
-
-
-/// Precache files without clogging up the browse() queue, used for passively sending files on connection start.
-/datum/asset_transport/proc/send_assets_slow(client/client, list/files, filerate = 6)
- var/startingfilerate = filerate
- for (var/file in files)
- if (!client)
- break
- if (send_assets(client, file))
- if (!(--filerate))
- filerate = startingfilerate
- client.browse_queue_flush()
- stoplag(0) //queuing calls like this too quickly can cause issues in some client versions
-
-/// Check the config is valid to load this transport
-/// Returns TRUE or FALSE
-/datum/asset_transport/proc/validate_config(log = TRUE)
- return TRUE
diff --git a/code/modules/asset_cache/transports/browse_rsc.dm b/code/modules/asset_cache/transports/browse_rsc.dm
new file mode 100644
index 000000000000..5b4b8d715f31
--- /dev/null
+++ b/code/modules/asset_cache/transports/browse_rsc.dm
@@ -0,0 +1,17 @@
+/**
+ * Basic asset transport, via browse_rsc()
+ */
+/datum/asset_transport/browse_rsc
+ name = "asset-transport: browse_rsc()"
+
+/datum/asset_transport/browse_rsc/validate_config()
+ return TRUE
+
+/datum/asset_transport/browse_rsc/send_anonymous_file(list/client/targets, file)
+ return send_anonymous_file_native(targets, file)
+
+/datum/asset_transport/browse_rsc/load_item(datum/asset_item/item)
+ return load_item_native(item)
+
+/datum/asset_transport/browse_rsc/send_items(client/target, list/datum/asset_item/items)
+ return send_asset_item_native(target, items)
diff --git a/code/modules/asset_cache/transports/webroot.dm b/code/modules/asset_cache/transports/webroot.dm
new file mode 100644
index 000000000000..60f3712aed9e
--- /dev/null
+++ b/code/modules/asset_cache/transports/webroot.dm
@@ -0,0 +1,54 @@
+/// CDN Webroot asset transport.
+/datum/asset_transport/webroot
+ name = "CDN Webroot asset transport"
+
+ var/webroot
+ var/url
+
+/datum/asset_transport/webroot/initialize()
+ . = ..()
+ webroot = CONFIG_GET(string/asset_cdn_webroot)
+ url = CONFIG_GET(string/asset_cdn_url)
+
+/datum/asset_transport/webroot/validate_config()
+ if (!CONFIG_GET(string/asset_cdn_url))
+ log_asset("ERROR: [type]: Invalid Config: ASSET_CDN_URL")
+ return FALSE
+ if (!CONFIG_GET(string/asset_cdn_webroot))
+ log_asset("ERROR: [type]: Invalid Config: ASSET_CDN_WEBROOT")
+ return FALSE
+ return TRUE
+
+/datum/asset_transport/webroot/send_items(client/target, list/datum/asset_item/items)
+ return TRUE
+
+/datum/asset_transport/webroot/send_anonymous_file(list/client/targets, file, ext)
+ return save_anonymous_file_to_webroot(file, ext)
+
+/datum/asset_transport/webroot/load_item(datum/asset_item/item)
+ return save_asset_item_to_webroot(item)
+
+/datum/asset_transport/webroot/proc/save_asset_item_to_webroot(datum/asset_item/item)
+ return save_to_webroot(item.file, item.mangled_name, item.namespace_id, item.hash)
+
+/datum/asset_transport/webroot/proc/save_anonymous_file_to_webroot(file, ext)
+ var/md5_of_file = md5asfile(file)
+ var/filename = "[md5_of_file].[ext]"
+ return save_to_webroot(file, filename, null, md5_of_file)
+
+/datum/asset_transport/webroot/proc/save_to_webroot(file, filename, namespace, hash)
+ var/path
+ if(namespace)
+ path = "namespaces/[copytext(namespace, 1, 3)]/[namespace]/[filename]"
+ else
+ path = "[get_webroot_path(hash, filename)]"
+ var/save_path = "[webroot]/[path]"
+ if(!fexists(save_path) && !fexists("[save_path].gz")) // sometimes web servers auto-compress text files
+ fcopy(file, "[save_path]")
+ return "[url]/[path]"
+
+/datum/asset_transport/webroot/proc/get_webroot_path(hash, filename)
+ return "[copytext(hash, 1, 3)]/[filename]"
+
+/datum/asset_transport/webroot/perform_native_preload(client/victim, list/datum/asset_pack/native_packs)
+ return // preloading? what's that? sounds like something done by sane people who don't use IIS!
diff --git a/code/modules/asset_cache/transports/webroot_transport.dm b/code/modules/asset_cache/transports/webroot_transport.dm
deleted file mode 100644
index e3cb33b8fabf..000000000000
--- a/code/modules/asset_cache/transports/webroot_transport.dm
+++ /dev/null
@@ -1,87 +0,0 @@
-/// CDN Webroot asset transport.
-/datum/asset_transport/webroot
- name = "CDN Webroot asset transport"
-
-/datum/asset_transport/webroot/Load()
- if (validate_config(log = FALSE))
- load_existing_assets()
-
-/// Processes thru any assets that were registered before we were loaded as a transport.
-/datum/asset_transport/webroot/proc/load_existing_assets()
- for (var/asset_name in SSassets.cache)
- var/datum/asset_cache_item/ACI = SSassets.cache[asset_name]
- save_asset_to_webroot(ACI)
-
-/// Register a browser asset with the asset cache system
-/// We also save it to the CDN webroot at this step instead of waiting for send_assets()
-/// asset_name - the identifier of the asset
-/// asset - the actual asset file or an asset_cache_item datum.
-/datum/asset_transport/webroot/register_asset(asset_name, asset)
- . = ..()
- var/datum/asset_cache_item/ACI = .
-
- if (istype(ACI) && ACI.hash)
- save_asset_to_webroot(ACI)
-
-/// Saves the asset to the webroot taking into account namespaces and hashes.
-/datum/asset_transport/webroot/proc/save_asset_to_webroot(datum/asset_cache_item/ACI)
- var/webroot = CONFIG_GET(string/asset_cdn_webroot)
- var/newpath = "[webroot][get_asset_suffex(ACI)]"
- if (fexists(newpath))
- return
- if (fexists("[newpath].gz")) //its a common pattern in webhosting to save gzip'ed versions of text files and let the webserver serve them up as gzip compressed normal files, sometimes without keeping the original version.
- return
- return fcopy(ACI.resource, newpath)
-
-/// Returns a url for a given asset.
-/// asset_name - Name of the asset.
-/// asset_cache_item - asset cache item datum for the asset, optional, overrides asset_name
-/datum/asset_transport/webroot/get_asset_url(asset_name, datum/asset_cache_item/asset_cache_item)
- if (!istype(asset_cache_item))
- asset_cache_item = SSassets.cache[asset_name]
- var/url = CONFIG_GET(string/asset_cdn_url) //config loading will handle making sure this ends in a /
- return "[url][get_asset_suffex(asset_cache_item)]"
-
-/datum/asset_transport/webroot/proc/get_asset_suffex(datum/asset_cache_item/asset_cache_item)
- var/base = "[copytext(asset_cache_item.hash, 1, 3)]/"
- var/filename = "asset.[asset_cache_item.hash][asset_cache_item.ext]"
- if (length(asset_cache_item.namespace))
- base = "namespaces/[copytext(asset_cache_item.namespace, 1, 3)]/[asset_cache_item.namespace]/"
- if (!asset_cache_item.namespace_parent)
- filename = "[asset_cache_item.name]"
- return base + filename
-
-
-/// webroot asset sending - does nothing unless passed legacy assets
-/datum/asset_transport/webroot/send_assets(client/client, list/asset_list)
- . = FALSE
- var/list/legacy_assets = list()
- if (!islist(asset_list))
- asset_list = list(asset_list)
- for (var/asset_name in asset_list)
- var/datum/asset_cache_item/ACI = asset_list[asset_name]
- if (!istype(ACI))
- ACI = SSassets.cache[asset_name]
- if (!ACI)
- legacy_assets += asset_name //pass it on to base send_assets so it can output an error
- continue
- if (ACI.legacy)
- legacy_assets[asset_name] = ACI
- if (length(legacy_assets))
- . = ..(client, legacy_assets)
-
-
-/// webroot slow asset sending - does nothing.
-/datum/asset_transport/webroot/send_assets_slow(client/client, list/files, filerate)
- return FALSE
-
-/datum/asset_transport/webroot/validate_config(log = TRUE)
- if (!CONFIG_GET(string/asset_cdn_url))
- if (log)
- log_asset("ERROR: [type]: Invalid Config: ASSET_CDN_URL")
- return FALSE
- if (!CONFIG_GET(string/asset_cdn_webroot))
- if (log)
- log_asset("ERROR: [type]: Invalid Config: ASSET_CDN_WEBROOT")
- return FALSE
- return TRUE
diff --git a/code/modules/asset_cache/validate_assets.html b/code/modules/asset_cache/validate_assets.html
deleted file mode 100644
index 0338f9514ce2..000000000000
--- a/code/modules/asset_cache/validate_assets.html
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/code/modules/atmospherics/machinery/_atmospherics_helpers.dm b/code/modules/atmospherics/machinery/_atmospherics_helpers.dm
index 4f4fbdb81ba9..7943f831e68d 100644
--- a/code/modules/atmospherics/machinery/_atmospherics_helpers.dm
+++ b/code/modules/atmospherics/machinery/_atmospherics_helpers.dm
@@ -425,3 +425,28 @@
if(connect_types & CONNECT_TYPE_HE)
dat += "HE"
return dat.Join("|")
+
+//Generalized gas pumping proc.
+//Moves gas from one gas_mixture to another and returns the amount of power needed (assuming 1 second), or -1 if no gas was pumped.
+//transfer_moles - Limits the amount of moles to transfer. The actual amount of gas moved may also be limited by available_power, if given.
+//available_power - the maximum amount of power that may be used when moving gas. If null then the transfer is not limited by power.
+/proc/pump_heat(target_temp, datum/gas_mixture/source, datum/gas_mixture/sink, available_power = null)
+ var/efficiency = get_thermal_efficiency(target_temp, source, sink)
+ CACHE_VSC_PROP(atmos_vsc, /atmos/heatpump/performance_factor, performance_factor)
+
+ var/actual_performance_factor = performance_factor*efficiency
+
+ var/max_energy_transfer = actual_performance_factor*available_power
+
+ if(abs(sink.temperature - target_temp) < 0.001) // don't want wild swings and too much power use
+ return
+ //only adds the energy actually removed from air one to air two(- infront of air1 because energy was removed)
+ var/energy_transfered = -source.adjust_thermal_energy(-clamp(sink.get_thermal_energy_change(target_temp),-max_energy_transfer,max_energy_transfer))
+ energy_transfered=abs(sink.adjust_thermal_energy(energy_transfered))
+ return abs(energy_transfered/actual_performance_factor)
+
+/proc/get_thermal_efficiency(target_temp, datum/gas_mixture/source, datum/gas_mixture/sink)
+ if((target_temp < sink.temperature))
+ return clamp((sink.temperature / source.temperature), 0, 1)
+ else if((target_temp > sink.temperature))
+ return clamp((source.temperature / sink.temperature), 0, 1)
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/heat_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/heat_pump.dm
index c82a1c20ac79..a391234ada30 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/heat_pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/heat_pump.dm
@@ -1,9 +1,6 @@
/***
* Heat pumps, binary devices that pump heat between both ends
*/
-#define EFFICIENCY_MULT 1
-#define EFFICIENCY_LIMIT_MULT 1
-
/obj/machinery/atmospherics/component/binary/heat_pump
name = "heat pump"
desc = "A heat pump, used to transfer heat between two pipe systems."
@@ -139,24 +136,9 @@
if((air1.temperature < 1) || (air2.temperature < 1))
return
- //Now we are at the point where we need to actively pump
- efficiency = get_thermal_efficiency()
- CACHE_VSC_PROP(atmos_vsc, /atmos/heatpump/performance_factor, performance_factor)
-
- var/actual_performance_factor = performance_factor*efficiency
-
- var/max_energy_transfer = actual_performance_factor*power_rating
-
- var/datum/gas_mixture/sample_air = air2
- if(length(network2.line_members)==1)
- sample_air=network2.line_members[1].air
+ efficiency = get_thermal_efficiency(target_temp, air1, air2)
+ var/power_draw = pump_heat(target_temp, air1, air2, power_rating)
- if(abs(sample_air.temperature - target_temp) < 0.001) // don't want wild swings and too much power use
- return
- //only adds the energy actually removed from air one to air two(- infront of air1 because energy was removed)
- var/energy_transfered = -air1.adjust_thermal_energy(-clamp(sample_air.get_thermal_energy_change(target_temp),-max_energy_transfer,max_energy_transfer))
- energy_transfered=abs(air2.adjust_thermal_energy(energy_transfered))
- var/power_draw = abs(energy_transfered/actual_performance_factor)
if (power_draw >= 0)
last_power_draw_legacy = power_draw
use_power(power_draw)
@@ -165,12 +147,6 @@
if(network2)
network2.update = 1
-/obj/machinery/atmospherics/component/binary/heat_pump/proc/get_thermal_efficiency()
- if((target_temp < air2.temperature))
- return clamp((air2.temperature / air1.temperature) * EFFICIENCY_MULT, 0, 1 * EFFICIENCY_LIMIT_MULT)
- else if((target_temp > air2.temperature))
- return clamp((air1.temperature / air2.temperature) * EFFICIENCY_MULT, 0, 1 * EFFICIENCY_LIMIT_MULT)
-
/obj/machinery/atmospherics/component/binary/heat_pump/proc/handle_passive_flow()
var/air_heat_capacity = air1.heat_capacity()
var/other_air_heat_capacity = air2.heat_capacity()
@@ -229,7 +205,3 @@
. = TRUE
if(.)
target_temp = max(newValue,lowest_temp)
-
-
-#undef EFFICIENCY_MULT
-#undef EFFICIENCY_LIMIT_MULT
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/massive_heat_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/massive_heat_pump.dm
index 78d367089246..6704cfe860e7 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/massive_heat_pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/massive_heat_pump.dm
@@ -81,16 +81,10 @@
if((air1.temperature < 1) || (air2.temperature < 1))
return
- var/power_draw = -1
- //Now we are at the point where we need to actively pump
- efficiency = get_thermal_efficiency()
- var/energy_transfered = 0
- CACHE_VSC_PROP(atmos_vsc, /atmos/heatpump/performance_factor, performance_factor)
-
- energy_transfered = clamp(air2.get_thermal_energy_change(target_temp),performance_factor*power_rating,-performance_factor*power_rating)
- power_draw = abs(energy_transfered/performance_factor)
- air2.adjust_thermal_energy(-air1.adjust_thermal_energy(-energy_transfered*efficiency))//only adds the energy actually removed from air one to air two(- infront of air1 because energy was removed)
+ //Now we are at the point where we need to actively pump
+ efficiency = get_thermal_efficiency(target_temp, air1, air2)
+ var/power_draw = pump_heat(target_temp, air1, air2, power_rating)
if (power_draw >= 0)
last_power_draw_legacy = power_draw
@@ -104,12 +98,6 @@
return 1
-/obj/machinery/atmospherics/component/binary/massive_heat_pump/proc/get_thermal_efficiency()
- if((target_temp < air2.temperature))
- return clamp((air2.temperature / air1.temperature) * EFFICIENCY_MULT, 0, 1 * EFFICIENCY_LIMIT_MULT)
- else if((target_temp > air2.temperature))
- return clamp((air1.temperature / air2.temperature) * EFFICIENCY_MULT, 0, 1 * EFFICIENCY_LIMIT_MULT)
-
/obj/machinery/atmospherics/component/binary/massive_heat_pump/proc/handle_passive_flow()
var/air_heat_capacity = air1.heat_capacity()
var/other_air_heat_capacity = air2.heat_capacity()
diff --git a/code/modules/atmospherics/machinery/components/unary/env_heat_pump.dm b/code/modules/atmospherics/machinery/components/unary/env_heat_pump.dm
index 464ae92e3270..357985acec34 100644
--- a/code/modules/atmospherics/machinery/components/unary/env_heat_pump.dm
+++ b/code/modules/atmospherics/machinery/components/unary/env_heat_pump.dm
@@ -74,32 +74,13 @@
env_temp = env.temperature
//Now we are at the point where we need to actively pump
- var/efficiency = get_thermal_efficiency(air_contents, env) * efficiency_multiplier
- CACHE_VSC_PROP(atmos_vsc, /atmos/heatpump/performance_factor, performance_factor)
+ var/power_draw = pump_heat(target_temp, air_contents, env, power_rating)
- var/actual_performance_factor = performance_factor*efficiency
-
- var/max_energy_transfer = actual_performance_factor*power_rating
-
- if(abs(env.temperature - target_temp) < 0.001) // don't want wild swings and too much power use
- use_power = USE_POWER_IDLE //We cant operate so we might as well turn off
- return
- //only adds the energy actually removed from air one to air two(- infront of air_contents because energy was removed)
- var/energy_transfered = -air_contents.adjust_thermal_energy(-clamp(env.get_thermal_energy_change(target_temp),-max_energy_transfer,max_energy_transfer))
- energy_transfered=abs(env.adjust_thermal_energy(energy_transfered))
- var/power_draw = abs(energy_transfered/actual_performance_factor)
if (power_draw >= 0)
last_power_draw_legacy = power_draw
use_power(power_draw)
network?.update = 1
-/obj/machinery/atmospherics/component/unary/env_heat_pump/proc/get_thermal_efficiency(var/datum/gas_mixture/air1, var/datum/gas_mixture/air2)
- if((target_temp < air2.temperature))
- return clamp((air2.temperature / air1.temperature), 0, 1)
- else if((target_temp > air2.temperature))
- return clamp((air1.temperature / air2.temperature), 0, 1)
-
-
/obj/machinery/atmospherics/component/unary/env_heat_pump/receive_signal(datum/signal/signal, receive_method, receive_param)
if(machine_stat & (NOPOWER|BROKEN))
return
diff --git a/code/modules/atmospherics/machinery/components/unary/heat_exchanger.dm b/code/modules/atmospherics/machinery/components/unary/heat_exchanger.dm
index e21a782c5cb7..5b0b62bdf6a3 100644
--- a/code/modules/atmospherics/machinery/components/unary/heat_exchanger.dm
+++ b/code/modules/atmospherics/machinery/components/unary/heat_exchanger.dm
@@ -4,7 +4,7 @@
icon = 'icons/obj/atmospherics/heat_exchanger.dmi'
icon_state = "intact"
pipe_state = "heunary"
- density = FALSE
+ density = TRUE
var/obj/machinery/atmospherics/component/unary/heat_exchanger/partner = null
var/update_cycle
diff --git a/code/modules/atmospherics/machinery/components/unary/vent_scrubber.dm b/code/modules/atmospherics/machinery/components/unary/vent_scrubber.dm
index ba665cace6f7..0b39402a8f48 100644
--- a/code/modules/atmospherics/machinery/components/unary/vent_scrubber.dm
+++ b/code/modules/atmospherics/machinery/components/unary/vent_scrubber.dm
@@ -62,6 +62,14 @@
var/radio_filter_out
var/radio_filter_in
+/obj/machinery/atmospherics/component/unary/vent_scrubber/high_power
+ name = "High power air scrubber"
+ power_rating = 15000
+ scrub_volume = 5000
+ expanded_power = 15000
+ expanded_scrub = 5000
+ scrub_boost = 100
+
/obj/machinery/atmospherics/component/unary/vent_scrubber/Initialize(mapload)
. = ..()
if(isnull(siphoning))
diff --git a/code/modules/bodysets/organic/akula.dm b/code/modules/bodysets/organic/akula.dm
new file mode 100644
index 000000000000..5502069cb1c9
--- /dev/null
+++ b/code/modules/bodysets/organic/akula.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/akula
+ name = "akula tail"
+ id = "tail-bodyset-akula"
+ icon = 'icons/mob/bodysets/organic/akula/sprite_accessories.dmi'
+ icon_state = "tail"
+ do_colouration = FALSE
diff --git a/code/modules/bodysets/organic/apidean.dm b/code/modules/bodysets/organic/apidean.dm
new file mode 100644
index 000000000000..dbb72dcdb823
--- /dev/null
+++ b/code/modules/bodysets/organic/apidean.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/apidean
+ name = "apidean tail"
+ id = "tail-bodyset-apidean"
+ icon = 'icons/mob/bodysets/organic/apidean/sprite_accessories.dmi'
+ icon_state = "tail"
+ do_colouration = FALSE
diff --git a/code/modules/bodysets/organic/krisitik.dm b/code/modules/bodysets/organic/krisitik.dm
new file mode 100644
index 000000000000..2732f48d9fd9
--- /dev/null
+++ b/code/modules/bodysets/organic/krisitik.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/krisitik
+ name = "krisitik tail"
+ id = "tail-bodyset-krisitik"
+ icon = 'icons/mob/bodysets/organic/krisitik/sprite_accessories.dmi'
+ icon_state = "tail"
+ do_colouration = FALSE
diff --git a/code/modules/bodysets/organic/monkey.dm b/code/modules/bodysets/organic/monkey.dm
new file mode 100644
index 000000000000..0ce6dd0dc755
--- /dev/null
+++ b/code/modules/bodysets/organic/monkey.dm
@@ -0,0 +1,26 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/monkey
+ abstract_type = /datum/sprite_accessory/tail/bodyset/monkey
+
+/datum/sprite_accessory/tail/bodyset/monkey/chimp
+ name = "chimpanzee tail"
+ id = "tail-bodyset-chimp"
+ icon = 'icons/mob/bodysets/organic/monkey/sprite_accessories.dmi'
+ icon_state = "tail-chimp"
+ do_colouration = FALSE
+
+/datum/sprite_accessory/tail/bodyset/monkey/stok
+ name = "stok tail"
+ id = "tail-bodyset-stok"
+ icon = 'icons/mob/bodysets/organic/monkey/sprite_accessories.dmi'
+ icon_state = "tail-stok"
+ do_colouration = FALSE
+
+/datum/sprite_accessory/tail/bodyset/monkey/farwa
+ name = "farwa tail"
+ id = "tail-bodyset-farwa"
+ icon = 'icons/mob/bodysets/organic/monkey/sprite_accessories.dmi'
+ icon_state = "tail-farwa"
+ do_colouration = FALSE
diff --git a/code/modules/bodysets/organic/moth.dm b/code/modules/bodysets/organic/moth.dm
new file mode 100644
index 000000000000..11a895fa6341
--- /dev/null
+++ b/code/modules/bodysets/organic/moth.dm
@@ -0,0 +1,16 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/wing/bodyset/moth
+ name = "moth wings"
+ id = "wings-bodyset-moth"
+ icon = 'icons/mob/bodysets/organic/moth/sprite_accessories_64x32.dmi'
+ icon_state = "wings"
+ do_colouration = FALSE
+
+/datum/sprite_accessory/ears/bodyset/moth
+ name = "moth fluff & antenna"
+ id = "ears-bodyset-moth"
+ icon = 'icons/mob/bodysets/organic/moth/sprite_accessories.dmi'
+ icon_state = "antenna"
+ do_colouration = FALSE
diff --git a/code/modules/bodysets/organic/naramadi.dm b/code/modules/bodysets/organic/naramadi.dm
new file mode 100644
index 000000000000..cfdee0fe16fc
--- /dev/null
+++ b/code/modules/bodysets/organic/naramadi.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/naramadi
+ name = "naramadi tail"
+ icon = 'icons/mob/bodysets/organic/naramadi/sprite_accessories.dmi'
+ icon_state = "tail"
+ id = "tail-bodyset-naramadi"
+ do_colouration = TRUE
diff --git a/code/modules/bodysets/organic/nevrean.dm b/code/modules/bodysets/organic/nevrean.dm
new file mode 100644
index 000000000000..1618a60cf251
--- /dev/null
+++ b/code/modules/bodysets/organic/nevrean.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/nevrean
+ name = "nevrean tail"
+ icon = 'icons/mob/bodysets/organic/nevrean/sprite_accessories.dmi'
+ icon_state = "tail"
+ id = "tail-bodyset-nevrean"
+ do_colouration = TRUE
diff --git a/code/modules/bodysets/organic/rapala.dm b/code/modules/bodysets/organic/rapala.dm
new file mode 100644
index 000000000000..df41456b9f76
--- /dev/null
+++ b/code/modules/bodysets/organic/rapala.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/rapala
+ name = "rapala tail"
+ icon = 'icons/mob/bodysets/organic/rapala/sprite_accessories.dmi'
+ icon_state = "tail"
+ id = "tail-bodyset-rapala"
+ do_colouration = TRUE
diff --git a/code/modules/bodysets/organic/shadekin.dm b/code/modules/bodysets/organic/shadekin.dm
new file mode 100644
index 000000000000..3203e71c3f31
--- /dev/null
+++ b/code/modules/bodysets/organic/shadekin.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/shadekin
+ name = "shadekin tail"
+ icon = 'icons/mob/bodysets/organic/shadekin/sprite_accessories.dmi'
+ icon_state = "tail"
+ id = "tail-bodyset-shadekin"
+ do_colouration = TRUE
diff --git a/code/modules/bodysets/organic/tajaran.dm b/code/modules/bodysets/organic/tajaran.dm
new file mode 100644
index 000000000000..82551aa04635
--- /dev/null
+++ b/code/modules/bodysets/organic/tajaran.dm
@@ -0,0 +1,12 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/tajaran
+ name = "tajaran tail"
+ icon = 'icons/mob/bodysets/organic/tajaran/sprite_accessories.dmi'
+ icon_state = "tail"
+ variations = list(
+ SPRITE_ACCESSORY_VARIATION_WAGGING = "tail-wag",
+ )
+ id = "tail-bodyset-tajaran"
+ do_colouration = TRUE
diff --git a/code/modules/bodysets/organic/teshari.dm b/code/modules/bodysets/organic/teshari.dm
new file mode 100644
index 000000000000..4879075fd10f
--- /dev/null
+++ b/code/modules/bodysets/organic/teshari.dm
@@ -0,0 +1,19 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/teshari
+ name = "teshari tail"
+ icon = 'icons/mob/bodysets/organic/teshari/sprite_accessories.dmi'
+ icon_state = "tail"
+ id = "tail-bodyset-teshari"
+ do_colouration = TRUE
+
+/datum/sprite_accessory/tail/bodyset/teshari/render(mob/for_whom, list/colors, layer_front, layer_behind, layer_side, with_base_state, with_variation, flattened)
+ var/list/image/layers = ..()
+ for(var/image/built as anything in layers)
+ var/image/hair = image(icon, "tail-hair")
+ if(ishuman(for_whom))
+ var/mob/living/carbon/human/casted = for_whom
+ hair.color = rgb(casted.r_hair, casted.g_hair, casted.b_hair)
+ built.overlays += hair
+ return layers
diff --git a/code/modules/bodysets/organic/unathi.dm b/code/modules/bodysets/organic/unathi.dm
new file mode 100644
index 000000000000..a6376f58ca01
--- /dev/null
+++ b/code/modules/bodysets/organic/unathi.dm
@@ -0,0 +1,12 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/unathi
+ name = "unathi tail"
+ icon = 'icons/mob/bodysets/organic/unathi/sprite_accessories.dmi'
+ icon_state = "tail"
+ variations = list(
+ SPRITE_ACCESSORY_VARIATION_WAGGING = "tail-wag",
+ )
+ id = "tail-bodyset-unathi"
+ do_colouration = TRUE
diff --git a/code/modules/bodysets/organic/vasilissan.dm b/code/modules/bodysets/organic/vasilissan.dm
new file mode 100644
index 000000000000..eac8eb089e9f
--- /dev/null
+++ b/code/modules/bodysets/organic/vasilissan.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/vasilissan
+ name = "vasilissan tail"
+ icon = 'icons/mob/bodysets/organic/vasilissan/sprite_accessories.dmi'
+ icon_state = "tail"
+ do_colouration = TRUE
+ id = "tail-bodyset-vasilissan"
diff --git a/code/modules/bodysets/organic/vulpkanin.dm b/code/modules/bodysets/organic/vulpkanin.dm
new file mode 100644
index 000000000000..a7dce7edd7cc
--- /dev/null
+++ b/code/modules/bodysets/organic/vulpkanin.dm
@@ -0,0 +1,12 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/vulpkanin
+ name = "vulpkanin tail"
+ icon = 'icons/mob/bodysets/organic/vulpkanin/sprite_accessories.dmi'
+ icon_state = "tail"
+ variations = list(
+ SPRITE_ACCESSORY_VARIATION_WAGGING = "tail-wag",
+ )
+ do_colouration = TRUE
+ id = "tail-bodyset-vulpkanin"
diff --git a/code/modules/bodysets/organic/xenohybrid.dm b/code/modules/bodysets/organic/xenohybrid.dm
new file mode 100644
index 000000000000..f7e63211c01c
--- /dev/null
+++ b/code/modules/bodysets/organic/xenohybrid.dm
@@ -0,0 +1,12 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/xenohybrid
+ name = "xenohybrid tail"
+ icon = 'icons/mob/bodysets/organic/xenohybrid/sprite_accessories.dmi'
+ icon_state = "tail"
+ variations = list(
+ SPRITE_ACCESSORY_VARIATION_WAGGING = "tail-wag",
+ )
+ do_colouration = TRUE
+ id = "tail-bodyset-xenohybrid"
diff --git a/code/modules/bodysets/organic/xenomorph.dm b/code/modules/bodysets/organic/xenomorph.dm
new file mode 100644
index 000000000000..a545b5d7cb03
--- /dev/null
+++ b/code/modules/bodysets/organic/xenomorph.dm
@@ -0,0 +1,33 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/xenomorph
+ abstract_type = /datum/sprite_accessory/tail/bodyset/xenomorph
+
+/datum/sprite_accessory/tail/bodyset/xenomorph/hunter
+ name = "xenomorph hunter tail"
+ icon = 'icons/mob/bodysets/organic/xenomorph/sprite_accessories.dmi'
+ icon_state = "tail-hunter"
+ do_colouration = FALSE
+ id = "tail-bodyset-xenomorph-hunter"
+
+/datum/sprite_accessory/tail/bodyset/xenomorph/drone
+ name = "xenomorph drone tail"
+ icon = 'icons/mob/bodysets/organic/xenomorph/sprite_accessories.dmi'
+ icon_state = "tail-drone"
+ do_colouration = FALSE
+ id = "tail-bodyset-xenomorph-drone"
+
+/datum/sprite_accessory/tail/bodyset/xenomorph/queen
+ name = "xenomorph queen tail"
+ icon = 'icons/mob/bodysets/organic/xenomorph/sprite_accessories.dmi'
+ icon_state = "tail-queen"
+ do_colouration = FALSE
+ id = "tail-bodyset-xenomorph-queen"
+
+/datum/sprite_accessory/tail/bodyset/xenomorph/sentinel
+ name = "xenomorph sentinel tail"
+ icon = 'icons/mob/bodysets/organic/xenomorph/sprite_accessories.dmi'
+ icon_state = "tail-sentinel"
+ do_colouration = FALSE
+ id = "tail-bodyset-xenomorph-sentinel"
diff --git a/code/modules/bodysets/organic/zorren_flatlander.dm b/code/modules/bodysets/organic/zorren_flatlander.dm
new file mode 100644
index 000000000000..6a8149cbce32
--- /dev/null
+++ b/code/modules/bodysets/organic/zorren_flatlander.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/zorren_flatlander
+ name = "zorren_flatlander tail"
+ icon = 'icons/mob/bodysets/organic/zorren_flatlander/sprite_accessories.dmi'
+ icon_state = "tail"
+ id = "tail-bodyset-zorren-flatlander"
+ do_colouration = TRUE
diff --git a/code/modules/bodysets/organic/zorren_highlander.dm b/code/modules/bodysets/organic/zorren_highlander.dm
new file mode 100644
index 000000000000..5edd304a2a15
--- /dev/null
+++ b/code/modules/bodysets/organic/zorren_highlander.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/zorren_highlander
+ name = "zorren_highlander tail"
+ icon = 'icons/mob/bodysets/organic/zorren_highlander/sprite_accessories.dmi'
+ icon_state = "tail"
+ id = "tail-bodyset-zorren-highlander"
+ do_colouration = TRUE
diff --git a/code/modules/bodysets/sprite_accessories.dm b/code/modules/bodysets/sprite_accessories.dm
new file mode 100644
index 000000000000..b582c9a545b4
--- /dev/null
+++ b/code/modules/bodysets/sprite_accessories.dm
@@ -0,0 +1,22 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/hair/bodyset
+ abstract_type = /datum/sprite_accessory/hair/bodyset
+ selectable = FALSE
+
+/datum/sprite_accessory/facial_hair/bodyset
+ abstract_type = /datum/sprite_accessory/facial_hair/bodyset
+ selectable = FALSE
+
+/datum/sprite_accessory/ears/bodyset
+ abstract_type = /datum/sprite_accessory/ears/bodyset
+ selectable = FALSE
+
+/datum/sprite_accessory/tail/bodyset
+ abstract_type = /datum/sprite_accessory/tail/bodyset
+ selectable = FALSE
+
+/datum/sprite_accessory/wing/bodyset
+ abstract_type = /datum/sprite_accessory/wing/bodyset
+ selectable = FALSE
diff --git a/code/modules/bodysets/synthetic/eggnerd.dm b/code/modules/bodysets/synthetic/eggnerd.dm
new file mode 100644
index 000000000000..c98ff6193935
--- /dev/null
+++ b/code/modules/bodysets/synthetic/eggnerd.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/eggnerd
+ name = "eggnerd synthetic tail"
+ icon = 'icons/mob/bodysets/synthetic/eggnerd/sprite_accessories.dmi'
+ icon_state = "tail"
+ do_colouration = FALSE
+ id = "tail-bodyset-eggnerd"
diff --git a/code/modules/bodysets/synthetic/eggnerd_red.dm b/code/modules/bodysets/synthetic/eggnerd_red.dm
new file mode 100644
index 000000000000..dc33d369a0b5
--- /dev/null
+++ b/code/modules/bodysets/synthetic/eggnerd_red.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/eggnerd_red
+ name = "eggnerd (red) synthetic tail"
+ icon = 'icons/mob/bodysets/synthetic/eggnerd_red/sprite_accessories.dmi'
+ icon_state = "tail"
+ do_colouration = FALSE
+ id = "tail-bodyset-eggnerd_red"
diff --git a/code/modules/bodysets/synthetic/oss_akula.dm b/code/modules/bodysets/synthetic/oss_akula.dm
new file mode 100644
index 000000000000..e3ab5bee7048
--- /dev/null
+++ b/code/modules/bodysets/synthetic/oss_akula.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/oss_akula
+ name = "OSS synthetic akula tail"
+ icon = 'icons/mob/bodysets/synthetic/oss_akula/sprite_accessories.dmi'
+ icon_state = "tail"
+ do_colouration = TRUE
+ id = "tail-bodyset-oss_akula"
diff --git a/code/modules/bodysets/synthetic/oss_lizard.dm b/code/modules/bodysets/synthetic/oss_lizard.dm
new file mode 100644
index 000000000000..47eb81ee9c97
--- /dev/null
+++ b/code/modules/bodysets/synthetic/oss_lizard.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/oss_lizard
+ name = "OSS synthetic lizard tail"
+ icon = 'icons/mob/bodysets/synthetic/oss_lizard/sprite_accessories.dmi'
+ icon_state = "tail"
+ do_colouration = TRUE
+ id = "tail-bodyset-oss_lizard"
diff --git a/code/modules/bodysets/synthetic/oss_naramadi.dm b/code/modules/bodysets/synthetic/oss_naramadi.dm
new file mode 100644
index 000000000000..a35e29042bf8
--- /dev/null
+++ b/code/modules/bodysets/synthetic/oss_naramadi.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/oss_naramadi
+ name = "OSS synthetic naramadi tail"
+ icon = 'icons/mob/bodysets/synthetic/oss_naramadi/sprite_accessories.dmi'
+ icon_state = "tail"
+ do_colouration = TRUE
+ id = "tail-bodyset-oss_naramadi"
diff --git a/code/modules/bodysets/synthetic/oss_nevrean.dm b/code/modules/bodysets/synthetic/oss_nevrean.dm
new file mode 100644
index 000000000000..108beb0f0075
--- /dev/null
+++ b/code/modules/bodysets/synthetic/oss_nevrean.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/oss_nevrean
+ name = "OSS synthetic nevrean tail"
+ icon = 'icons/mob/bodysets/synthetic/oss_nevrean/sprite_accessories.dmi'
+ icon_state = "tail"
+ do_colouration = TRUE
+ id = "tail-bodyset-oss_nevrean"
diff --git a/code/modules/bodysets/synthetic/oss_spider.dm b/code/modules/bodysets/synthetic/oss_spider.dm
new file mode 100644
index 000000000000..3ef5d1fdb36c
--- /dev/null
+++ b/code/modules/bodysets/synthetic/oss_spider.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/oss_spider
+ name = "OSS synthetic spider tail"
+ icon = 'icons/mob/bodysets/synthetic/oss_spider/sprite_accessories.dmi'
+ icon_state = "tail"
+ do_colouration = TRUE
+ id = "tail-bodyset-oss_spider"
diff --git a/code/modules/bodysets/synthetic/oss_tajaran.dm b/code/modules/bodysets/synthetic/oss_tajaran.dm
new file mode 100644
index 000000000000..3b1202302113
--- /dev/null
+++ b/code/modules/bodysets/synthetic/oss_tajaran.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/oss_tajaran
+ name = "OSS synthetic tajaran tail"
+ icon = 'icons/mob/bodysets/synthetic/oss_tajaran/sprite_accessories.dmi'
+ icon_state = "tail"
+ do_colouration = TRUE
+ id = "tail-bodyset-oss_tajaran"
diff --git a/code/modules/bodysets/synthetic/oss_vulpkanin.dm b/code/modules/bodysets/synthetic/oss_vulpkanin.dm
new file mode 100644
index 000000000000..4387887c58b8
--- /dev/null
+++ b/code/modules/bodysets/synthetic/oss_vulpkanin.dm
@@ -0,0 +1,9 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/datum/sprite_accessory/tail/bodyset/oss_vulpkanin
+ name = "OSS synthetic vulpkanin tail"
+ icon = 'icons/mob/bodysets/synthetic/oss_vulpkanin/sprite_accessories.dmi'
+ icon_state = "tail"
+ do_colouration = TRUE
+ id = "tail-bodyset-oss_vulpkanin"
diff --git a/code/modules/cargo/supplypacks/engineering.dm b/code/modules/cargo/supplypacks/engineering.dm
index eecdb6f29222..d5ebeb32a387 100644
--- a/code/modules/cargo/supplypacks/engineering.dm
+++ b/code/modules/cargo/supplypacks/engineering.dm
@@ -78,7 +78,7 @@
/datum/supply_pack/eng/solar
name = "Solar Pack crate"
contains = list(
- /obj/item/solar_assembly = 21,
+ /obj/item/frame2/solar_panel = 21,
/obj/item/circuitboard/solar_control,
/obj/item/tracker_electronics,
/obj/item/paper/solar
diff --git a/code/modules/client/client.dm b/code/modules/client/client.dm
index 3e753a8add94..1c00546b4427 100644
--- a/code/modules/client/client.dm
+++ b/code/modules/client/client.dm
@@ -56,6 +56,10 @@
/// open context menu
var/datum/radial_menu/context_menu/context_menu
+ //* HUDs *//
+ /// active atom HUD providers associated to a list of ids or paths of atom huds that's providing it.
+ var/list/datum/atom_hud_provider/atom_hud_providers
+
//? Rendering
/// Click catcher
var/atom/movable/screen/click_catcher/click_catcher
@@ -187,8 +191,6 @@
////////////////////////////////////
//things that require the database//
////////////////////////////////////
- ///Track hours of leave accured for each department.
- var/list/department_hours = list()
preload_rsc = PRELOAD_RSC
@@ -202,14 +204,6 @@
///Used for limiting the rate of clicks sends by the client to avoid abuse
var/list/clicklimiter
- ///List of all asset filenames sent to this client by the asset cache, along with their assoicated md5s
- var/list/sent_assets = list()
- ///List of all completed blocking send jobs awaiting acknowledgement by send_asset
- var/list/completed_asset_jobs = list()
- ///Last asset send job id.
- var/last_asset_job = 0
- var/last_completed_asset_job = 0
-
///world.time they connected
var/connection_time
///world.realtime they connected
@@ -233,6 +227,9 @@
return TRUE
return ..()
+
+//* Is-rank helpers *//
+
/**
* are we a guest account?
*/
@@ -250,3 +247,49 @@
*/
/client/proc/is_staff()
return !isnull(holder)
+
+//* Atom HUDs *//
+
+/client/proc/add_atom_hud(datum/atom_hud/hud, source)
+ ASSERT(istext(source))
+ if(isnull(atom_hud_providers))
+ atom_hud_providers = list()
+ var/list/datum/atom_hud_provider/providers = hud.resolve_providers()
+ for(var/datum/atom_hud_provider/provider as anything in providers)
+ var/already_there = atom_hud_providers[provider]
+ if(already_there)
+ atom_hud_providers[provider] |= source
+ else
+ atom_hud_providers[provider] = list(source)
+ provider.add_client(src)
+
+/client/proc/remove_atom_hud(datum/atom_hud/hud, source)
+ ASSERT(istext(source))
+ if(!length(atom_hud_providers))
+ return
+ if(!hud)
+ // remove all of source
+ for(var/datum/atom_hud_provider/provider as anything in atom_hud_providers)
+ if(!(source in atom_hud_providers[provider]))
+ continue
+ atom_hud_providers[provider] -= source
+ if(!length(atom_hud_providers[provider]))
+ atom_hud_providers -= provider
+ provider.remove_client(src)
+ return
+ hud = fetch_atom_hud(hud)
+ var/list/datum/atom_hud_provider/providers = hud.resolve_providers()
+ for(var/datum/atom_hud_provider/provider as anything in providers)
+ if(!length(atom_hud_providers[provider]))
+ continue
+ atom_hud_providers[provider] -= source
+ if(!length(atom_hud_providers[provider]))
+ atom_hud_providers -= provider
+ provider.remove_client(src)
+
+// todo: add_atom_hud_provider, remove_atom_hud_provider
+
+/client/proc/clear_atom_hud_providers()
+ for(var/datum/atom_hud_provider/provider as anything in atom_hud_providers)
+ provider.remove_client(src)
+ atom_hud_providers = null
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 15e8c0243c7c..66e132b7e2e7 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -12,6 +12,25 @@
#define CURRENT_MINUTE 3
#define MINUTE_COUNT 4
#define ADMINSWARNED_AT 5
+
+/**
+ * A 'hijack' / intercept proc.
+ * Override this and return TRUE if you handled it.
+ * This allows for clients to not have an overloaded Topic().
+ *
+ * The downside is additional overhead is generated due to the proc call overheads.
+ *
+ * * Logging & topic spam prevention is ran before this proc.
+ *
+ * @params
+ * * raw_href - the raw ?querystring that's sent by the client
+ * * href_list - the decoded, key-value query sent by client
+ * * raw_src - the raw src resolved by BYOND.
+ */
+/client/proc/on_topic_hook(raw_href, list/href_list, raw_src)
+ return FALSE
+
+
/*
When somebody clicks a link in game, this Topic is called first.
It does the stuff in this proc and then is redirected to the Topic() proc for the src=[0xWhatever]
@@ -32,13 +51,6 @@
if(!usr || usr != mob) //stops us calling Topic for somebody else's client. Also helps prevent usr=null
return
- // asset_cache
- var/asset_cache_job
- if(href_list["asset_cache_confirm_arrival"])
- asset_cache_job = asset_cache_confirm_arrival(href_list["asset_cache_confirm_arrival"])
- if (!asset_cache_job)
- return
-
// Rate limiting
var/mtl = config_legacy.minute_topic_limit //CONFIG_GET(number/minute_topic_limit)
if (!holder && mtl)
@@ -72,7 +84,6 @@
to_chat(src, "Your previous action was ignored because you've done too many in a second")
return
-
// Tgui Topic middleware
if(tgui_topic(href_list))
if(CONFIG_GET(flag/emergency_tgui_logging))
@@ -84,20 +95,15 @@
// Log
log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]")
+ // Run normal hooks.
+ if(on_topic_hook(href, href_list, hsrc))
+ return
+
// Route statpanel
if(href_list["statpanel"])
_statpanel_act(href_list["statpanel"], href_list)
return
- //byond bug ID:2256651
- if (asset_cache_job && (asset_cache_job in completed_asset_jobs))
- to_chat(src, "An error has been detected in how your client is receiving resources. Attempting to correct.... (If you keep seeing these messages you might want to close byond and reconnect)")
- src << browse("...", "window=asset_cache_browser")
- return
- if (href_list["asset_cache_preload_data"])
- asset_cache_preload_data(href_list["asset_cache_preload_data"])
- return
-
//Admin PM
if(href_list["priv_msg"])
var/client/C = locate(href_list["priv_msg"])
@@ -202,8 +208,6 @@
player.log_connect()
//* Resolve preferences
preferences = SSpreferences.resolve_game_preferences(key, ckey)
- preferences.active = src
- preferences.on_reconnect()
//? WARNING: SHITCODE ALERT ?//
// We allow a client/New sleep because preferences is currently required for
// everything else to work
@@ -212,16 +216,24 @@
security_kick("A fatal error occurred while attempting to load: preferences not initialized. Please notify a coder.")
stack_trace("we just kicked a client due to prefs not loading; something is horribly wrong!")
return
+ // we wait until it inits to do this
+ // todo: is there a better way this is kind of awful
+ preferences.active = src
+ preferences.on_reconnect()
//? END ?//
//* Setup user interface
// todo: move top level menu here, for now it has to be under prefs.
// Instantiate statpanel
- addtimer(CALLBACK(src, PROC_REF(statpanel_boot)), 0)
+ spawn(1)
+ statpanel_boot()
// Instantiate tgui panel
tgui_panel = new(src, "browseroutput")
// Instantiate cutscene system
- addtimer(CALLBACK(src, PROC_REF(init_cutscene_system)), 0)
+ spawn(1)
+ init_cutscene_system()
+ // instantiate tooltips
+ tooltips = new(src)
//* Setup admin tooling
GLOB.ahelp_tickets.ClientLogin(src)
@@ -278,8 +290,6 @@
//* therefore, DO NOT PUT ANYTHING YOU WILL RELY ON LATER IN THIS PROC IN LOGIN!
. = ..() //calls mob.Login()
- handle_legacy_connection_whatevers()
-
//* Connection Security
// start caching it immediately
INVOKE_ASYNC(SSipintel, TYPE_PROC_REF(/datum/controller/subsystem/ipintel, vpn_connection_check), address, ckey)
@@ -347,8 +357,8 @@
// if(config_legacy.aggressive_changelog)
// changelog_async()
- // ensure asset cache is there
- INVOKE_ASYNC(src, PROC_REF(warn_if_no_asset_cache_browser))
+ // run post-init 'lint'-like checks
+ on_new_hook_stability_checks()
// todo: fuck you voreprefs
prefs_vr = new /datum/vore_preferences(src)
@@ -370,6 +380,17 @@
// update our hub label
SSserver_maint.UpdateHubStatus()
+/**
+ * Called in the middle of new, after everything critical
+ * is loaded / initialized.
+ *
+ * This proc should all be async; it is where you hook to ensure things are properly
+ * loaded.
+ */
+/client/proc/on_new_hook_stability_checks()
+ SHOULD_CALL_PARENT(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+
//////////////
//DISCONNECT//
//////////////
@@ -415,10 +436,12 @@
active_mousedown_item = null
SSping.currentrun -= src
- //* cleanup mob-side stuff
+ //* cleanup rendering
// clear perspective
if(using_perspective)
set_perspective(null)
+ // clear HUDs
+ clear_atom_hud_providers()
//* cleanup UI
// cleanup statbrowser
@@ -427,6 +450,8 @@
cleanup_cutscene_system()
// cleanup tgui panel
QDEL_NULL(tgui_panel)
+ // cleanup tooltips
+ QDEL_NULL(tooltips)
. = ..() //Even though we're going to be hard deleted there are still some things that want to know the destroy is happening
return QDEL_HINT_HARDDEL_NOW
@@ -545,6 +570,7 @@ GLOBAL_VAR_INIT(log_clicks, FALSE)
//send resources to the client. It's here in its own proc so we can move it around easiliy if need be
/client/proc/send_resources()
+ // force them to download rsc's from configured paths if needed
#if (PRELOAD_RSC == 0)
var/static/next_external_rsc = 0
var/list/external_rsc_urls = CONFIG_GET(keyed_list/external_rsc_urls)
@@ -553,22 +579,8 @@ GLOBAL_VAR_INIT(log_clicks, FALSE)
preload_rsc = external_rsc_urls[next_external_rsc]
#endif
- spawn (10) //removing this spawn causes all clients to not get verbs.
-
- //load info on what assets the client has
- src << browse('code/modules/asset_cache/validate_assets.html', "window=asset_cache_browser")
-
- //Precache the client with all other assets slowly, so as to not block other browse() calls
- if (CONFIG_GET(flag/asset_simple_preload))
- addtimer(CALLBACK(SSassets.transport, TYPE_PROC_REF(/datum/asset_transport, send_assets_slow), src, SSassets.transport.preload), 5 SECONDS)
-/* // We don't have vox_sounds atm
- #if (PRELOAD_RSC == 0)
- for (var/name in GLOB.vox_sounds)
- var/file = GLOB.vox_sounds[name]
- Export("##action=load_rsc", file)
- stoplag()
- #endif
-*/
+ INVOKE_ASYNC(SSassets, TYPE_PROC_REF(/datum/controller/subsystem/assets, preload_client_assets), src)
+
//Hook, override it to run code when dir changes
//Like for /atoms, but clients are their own snowflake FUCK
/client/proc/setDir(newdir)
diff --git a/code/modules/client/legacy.dm b/code/modules/client/legacy.dm
deleted file mode 100644
index 0a5de4780511..000000000000
--- a/code/modules/client/legacy.dm
+++ /dev/null
@@ -1,15 +0,0 @@
-/client/proc/handle_legacy_connection_whatevers()
- if(is_guest())
- return
-
- // Department Hours
- if(config_legacy.time_off)
- var/datum/db_query/query_hours = SSdbcore.RunQuery(
- "SELECT department, hours FROM [format_table_name("vr_player_hours")] WHERE ckey = :ckey",
- list(
- "ckey" = ckey
- )
- )
- while(query_hours.NextRow())
- LAZYINITLIST(department_hours)
- department_hours[query_hours.item[1]] = text2num(query_hours.item[2])
diff --git a/code/modules/clothing/accessories/halo.dm b/code/modules/clothing/accessories/halo.dm
index 926a95291bcb..28646511a0b0 100644
--- a/code/modules/clothing/accessories/halo.dm
+++ b/code/modules/clothing/accessories/halo.dm
@@ -40,7 +40,7 @@
SIGNAL_HANDLER
update_worn_icon()
-/obj/item/clothing/accessory/halo_projector/render_apply_custom(mob/M, mutable_appearance/MA, bodytype, inhands, datum/inventory_slot_meta/slot_meta, icon_used, align_y)
+/obj/item/clothing/accessory/halo_projector/render_apply_custom(mob/M, mutable_appearance/MA, bodytype, inhands, datum/inventory_slot/slot_meta, icon_used, align_y)
. = ..()
if(inhands)
return
@@ -54,7 +54,7 @@
MA.pixel_y -= align_y
MA.transform = tform
-/obj/item/clothing/accessory/halo_projector/render_additional(mob/M, icon/icon_used, state_used, layer_used, dim_x, dim_y, align_y, bodytype, inhands, datum/inventory_slot_meta/slot_meta)
+/obj/item/clothing/accessory/halo_projector/render_additional(mob/M, icon/icon_used, state_used, layer_used, dim_x, dim_y, align_y, bodytype, inhands, datum/inventory_slot/slot_meta)
. = ..()
if(inhands)
return
diff --git a/code/modules/clothing/clothing_accessories.dm b/code/modules/clothing/clothing_accessories.dm
index cab51c659379..40fc520530b5 100644
--- a/code/modules/clothing/clothing_accessories.dm
+++ b/code/modules/clothing/clothing_accessories.dm
@@ -97,7 +97,7 @@
for(var/obj/item/I as anything in accessories)
I.dropped(user, flags | INV_OP_IS_ACCESSORY, newLoc)
-/obj/item/clothing/render_additional(mob/M, icon/icon_used, state_used, layer_used, dim_x, dim_y, align_y, bodytype, inhands, datum/inventory_slot_meta/slot_meta)
+/obj/item/clothing/render_additional(mob/M, icon/icon_used, state_used, layer_used, dim_x, dim_y, align_y, bodytype, inhands, datum/inventory_slot/slot_meta)
. = ..()
var/list/accessory_overlays = render_worn_accessories(M, inhands, slot_meta, layer_used, bodytype)
if(!isnull(accessory_overlays))
@@ -112,7 +112,7 @@
* * slot_meta - slot
* * bodytype - bodytype
*/
-/obj/item/clothing/proc/render_worn_accessories(mob/M, inhands, datum/inventory_slot_meta/slot_meta, layer_used, bodytype)
+/obj/item/clothing/proc/render_worn_accessories(mob/M, inhands, datum/inventory_slot/slot_meta, layer_used, bodytype)
RETURN_TYPE(/list)
if(!length(accessories))
return
@@ -144,16 +144,16 @@
/**
* Renders mob appearance for us as an accessory. Returns an image, or list of images.
*/
-/obj/item/clothing/proc/render_accessory_worn(mob/M, inhands, datum/inventory_slot_meta/slot_meta, layer_used, bodytype)
+/obj/item/clothing/proc/render_accessory_worn(mob/M, inhands, datum/inventory_slot/slot_meta, layer_used, bodytype)
if(accessory_render_legacy)
var/mutable_appearance/old
if(istype(src, /obj/item/clothing/accessory))
var/obj/item/clothing/accessory/A = src
old = A.get_mob_overlay()
if(!isnull(old) && old.plane == FLOAT_PLANE)
- old.layer = layer_used + BODY_LAYER + 0.1
+ old.layer = layer_used + 0.1
return old
- var/list/mutable_appearance/rendered = render_mob_appearance(M, accessory_render_specific? resolve_inventory_slot_meta(/datum/inventory_slot_meta/abstract/use_one_for_accessory) : slot_meta, bodytype)
+ var/list/mutable_appearance/rendered = render_mob_appearance(M, accessory_render_specific? resolve_inventory_slot(/datum/inventory_slot/abstract/use_one_for_accessory) : slot_meta, bodytype)
// sigh, fixup
if(isnull(rendered))
@@ -164,7 +164,7 @@
for(var/mutable_appearance/MA in rendered)
// fixup layer, but only if it's attached to mob; this is shitcode but the auril players have snipers outside my house, i'll refactor this later.
if(MA.plane == FLOAT_PLANE)
- MA.layer = layer_used + BODY_LAYER + 0.1 // ughhh, need way to override later.
+ MA.layer = layer_used + 0.1 // ughhh, need way to override later.
// sigh, legacy shit
if(istype(accessory_host, /obj/item/clothing/suit))
var/obj/item/clothing/suit/S = accessory_host
diff --git a/code/modules/clothing/clothing_icons.dm b/code/modules/clothing/clothing_icons.dm
index 6dafb2857626..22f6df307432 100644
--- a/code/modules/clothing/clothing_icons.dm
+++ b/code/modules/clothing/clothing_icons.dm
@@ -1,4 +1,4 @@
-/obj/item/clothing/render_apply_blood(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot_meta/slot_meta, icon_used)
+/obj/item/clothing/render_apply_blood(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot/slot_meta, icon_used)
. = ..()
if(inhands)
return
@@ -9,7 +9,7 @@
MA.add_overlay(bloodsies)
//HELMET: May have a lighting overlay
-/obj/item/clothing/head/render_apply_overlays(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot_meta/slot_meta, icon_used)
+/obj/item/clothing/head/render_apply_overlays(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot/slot_meta, icon_used)
. = ..()
if(on && (slot_meta.id == SLOT_ID_HEAD))
var/bodytype_str = bodytype_to_string(bodytype)
@@ -18,7 +18,7 @@
MA.add_overlay(I)
//SUIT: Blood state is slightly different
-/obj/item/clothing/suit/render_apply_blood(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot_meta/slot_meta, icon_used)
+/obj/item/clothing/suit/render_apply_blood(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot/slot_meta, icon_used)
. = ..()
if(inhands)
return
diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm
index 44e29ba1ec6c..155685319c68 100644
--- a/code/modules/clothing/glasses/_glasses.dm
+++ b/code/modules/clothing/glasses/_glasses.dm
@@ -524,7 +524,7 @@ BLIND // can't see anything
/obj/item/clothing/glasses/sunglasses/sechud/Initialize(mapload)
. = ..()
- AddElement(/datum/element/hud_granter, list(DATA_HUD_SECURITY_ADVANCED), list(SLOT_ID_GLASSES))
+ AddElement(/datum/element/hud_granter, list(/datum/atom_hud/data/human/security/advanced), list(SLOT_ID_GLASSES))
/obj/item/clothing/glasses/sunglasses/sechud/tactical
name = "tactical HUD"
@@ -582,7 +582,7 @@ BLIND // can't see anything
/obj/item/clothing/glasses/sunglasses/medhud/Initialize(mapload)
. = ..()
- AddElement(/datum/element/hud_granter, list(DATA_HUD_MEDICAL), list(SLOT_ID_GLASSES))
+ AddElement(/datum/element/hud_granter, list(/datum/atom_hud/data/human/medical), list(SLOT_ID_GLASSES))
/obj/item/clothing/glasses/thermal
name = "optical thermal scanner"
diff --git a/code/modules/clothing/glasses/hud.dm b/code/modules/clothing/glasses/hud.dm
index 0c98a49321e3..da9152b45692 100644
--- a/code/modules/clothing/glasses/hud.dm
+++ b/code/modules/clothing/glasses/hud.dm
@@ -13,7 +13,7 @@
/obj/item/clothing/glasses/hud/health/Initialize(mapload)
. = ..()
- AddElement(/datum/element/hud_granter, list(DATA_HUD_MEDICAL), list(SLOT_ID_GLASSES))
+ AddElement(/datum/element/hud_granter, list(/datum/atom_hud/data/human/medical), list(SLOT_ID_GLASSES))
/obj/item/clothing/glasses/hud/health/prescription
name = "Prescription Health Scanner HUD"
@@ -31,7 +31,7 @@
/obj/item/clothing/glasses/hud/security/Initialize(mapload)
. = ..()
- AddElement(/datum/element/hud_granter, list(DATA_HUD_SECURITY_ADVANCED), list(SLOT_ID_GLASSES))
+ AddElement(/datum/element/hud_granter, list(/datum/atom_hud/data/human/security/advanced), list(SLOT_ID_GLASSES))
/obj/item/clothing/glasses/hud/security/prescription
name = "Prescription Security HUD"
@@ -66,7 +66,7 @@
/obj/item/clothing/glasses/omnihud/Initialize(mapload)
. = ..()
- AddElement(/datum/element/hud_granter, list(DATA_HUD_ID_JOB), list(SLOT_ID_GLASSES))
+ AddElement(/datum/element/hud_granter, list(/datum/atom_hud/data/human/job_id), list(SLOT_ID_GLASSES))
/obj/item/clothing/glasses/omnihud/Initialize(mapload)
. = ..()
@@ -144,7 +144,7 @@
/obj/item/clothing/glasses/omnihud/med/Initialize(mapload)
. = ..()
- AddElement(/datum/element/hud_granter, list(DATA_HUD_ID_JOB, DATA_HUD_MEDICAL), list(SLOT_ID_GLASSES))
+ AddElement(/datum/element/hud_granter, list(/datum/atom_hud/data/human/job_id, /datum/atom_hud/data/human/medical), list(SLOT_ID_GLASSES))
/obj/item/clothing/glasses/omnihud/med/ar_interact(var/mob/living/carbon/human/user)
if(tgarscreen)
@@ -163,7 +163,7 @@
/obj/item/clothing/glasses/omnihud/sec/Initialize(mapload)
. = ..()
- AddElement(/datum/element/hud_granter, list(DATA_HUD_SECURITY_ADVANCED), list(SLOT_ID_GLASSES))
+ AddElement(/datum/element/hud_granter, list(/datum/atom_hud/data/human/security/advanced), list(SLOT_ID_GLASSES))
/obj/item/clothing/glasses/omnihud/sec/ar_interact(var/mob/living/carbon/human/user)
if(arscreen)
@@ -262,7 +262,7 @@
/obj/item/clothing/glasses/omnihud/all/Initialize(mapload)
. = ..()
- AddElement(/datum/element/hud_granter, list(DATA_HUD_SECURITY_ADVANCED, DATA_HUD_MEDICAL), list(SLOT_ID_GLASSES))
+ AddElement(/datum/element/hud_granter, list(/datum/atom_hud/data/human/security/advanced, /datum/atom_hud/data/human/medical), list(SLOT_ID_GLASSES))
/obj/item/clothing/glasses/hud/security/eyepatch
name = "Security Hudpatch"
diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm
index f8acad27a13a..5d528f9f8ec8 100644
--- a/code/modules/clothing/gloves/miscellaneous.dm
+++ b/code/modules/clothing/gloves/miscellaneous.dm
@@ -65,6 +65,7 @@
fingerprint_chance = 25
drop_sound = 'sound/items/drop/rubber.ogg'
pickup_sound = 'sound/items/pickup/rubber.ogg'
+ materials_base = list(MAT_PLASTIC = 100)
// var/balloonPath = /obj/item/latexballon
//TODO: Make inflating gloves a thing
diff --git a/code/modules/clothing/masks/_mask.dm b/code/modules/clothing/masks/_mask.dm
index f897284e5bd8..8dfbc89a2d95 100644
--- a/code/modules/clothing/masks/_mask.dm
+++ b/code/modules/clothing/masks/_mask.dm
@@ -12,5 +12,14 @@
var/list/say_messages
var/list/say_verbs
-/obj/item/clothing/mask/proc/filter_air(datum/gas_mixture/air)
+// gets one gas_mixture
+// that mixture is modified (and then used for breathing)
+// additionally removed gases can be returned to be passed to atmos
+/obj/item/clothing/mask/proc/process_air(datum/gas_mixture/air)
return
+
+//gets one gas_mixture (the exhale)
+//returns what should be passed to the environment
+/obj/item/clothing/mask/proc/process_exhale(datum/gas_mixture/air)
+ return
+
diff --git a/code/modules/clothing/masks/breath_warmer.dm b/code/modules/clothing/masks/breath_warmer.dm
new file mode 100644
index 000000000000..4b4c27f9bb7d
--- /dev/null
+++ b/code/modules/clothing/masks/breath_warmer.dm
@@ -0,0 +1,18 @@
+/obj/item/clothing/mask/warmer
+ name = "warming mask"
+ desc = "A mask that warms your breath, making the cold more bearable."
+ icon = 'icons/obj/clothing/ties.dmi'
+ icon_state = "gaiter_snow_up"
+ item_state_slots = list(SLOT_ID_RIGHT_HAND = "sterile", SLOT_ID_LEFT_HAND = "sterile")
+ w_class = WEIGHT_CLASS_SMALL
+ body_cover_flags = FACE
+ clothing_flags = FLEXIBLEMATERIAL
+
+//Warms with 1000 watts
+/obj/item/clothing/mask/warmer/process_air(datum/gas_mixture/air)
+ var/mob/living/carbon/wearer = src.loc
+ if(istype(wearer))
+ var/energy_to_warm = air.get_thermal_energy_change(wearer.bodytemperature)
+ air.adjust_thermal_energy(clamp(energy_to_warm, 0, 1000))
+
+
diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm
index 672a584a7648..05f1ea8c2a4b 100644
--- a/code/modules/clothing/masks/gasmask.dm
+++ b/code/modules/clothing/masks/gasmask.dm
@@ -14,7 +14,7 @@
var/gas_filter_strength = 1 //For gas mask filters
var/list/filtered_gases = list(GAS_ID_PHORON, GAS_ID_NITROUS_OXIDE)
-/obj/item/clothing/mask/gas/filter_air(datum/gas_mixture/air)
+/obj/item/clothing/mask/gas/process_air(datum/gas_mixture/air)
var/datum/gas_mixture/gas_filtered = new
for(var/g in filtered_gases)
diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm
index a2eb72240e5f..fa2f3fdbdd92 100644
--- a/code/modules/clothing/masks/miscellaneous.dm
+++ b/code/modules/clothing/masks/miscellaneous.dm
@@ -36,6 +36,7 @@
gas_transfer_coefficient = 0.90
permeability_coefficient = 0.01
armor_type = /datum/armor/mask/surgical
+ materials_base = list(MAT_PLASTIC = 100, MAT_WOOD = 20)
var/hanging = 0
/obj/item/clothing/mask/surgical/proc/adjust_mask(mob_user)
diff --git a/code/modules/clothing/suits/_suit.dm b/code/modules/clothing/suits/_suit.dm
index 621a3809bb3d..9bd314644efb 100644
--- a/code/modules/clothing/suits/_suit.dm
+++ b/code/modules/clothing/suits/_suit.dm
@@ -27,8 +27,8 @@
if(!taurized && slot == SLOT_ID_SUIT && ishuman(user))
var/mob/living/carbon/human/H = user
if(isTaurTail(H.tail_style))
- var/datum/sprite_accessory/tail/taur/taurtail = H.tail_style
- var/list/resolved = resolve_worn_assets(user, resolve_inventory_slot_meta(/datum/inventory_slot_meta/inventory/suit), FALSE, H.species.get_effective_bodytype(user, src, SLOT_ID_SUIT))
+ var/datum/sprite_accessory/tail/legacy_taur/taurtail = H.tail_style
+ var/list/resolved = resolve_worn_assets(user, resolve_inventory_slot(/datum/inventory_slot/inventory/suit), FALSE, H.species.get_effective_bodytype(user, src, SLOT_ID_SUIT))
if(taurtail.suit_sprites && (resolved[2] in icon_states(taurtail.suit_sprites)))
icon_override = taurtail.suit_sprites
normalize = FALSE
@@ -39,8 +39,8 @@
icon_override = initial(icon_override)
taurized = FALSE
-/obj/item/clothing/suit/render_apply_custom(mob/M, mutable_appearance/MA, bodytype, inhands, datum/inventory_slot_meta/slot_meta, icon_used, align_y)
+/obj/item/clothing/suit/render_apply_custom(mob/M, mutable_appearance/MA, bodytype, inhands, datum/inventory_slot/slot_meta, icon_used, align_y)
. = ..()
if(taurized)
MA.pixel_x = -16
- MA.layer = TAIL_LAYER + 1 // kick it over tail
+ MA.layer = HUMAN_LAYER_SPRITEACC_TAIL_FRONT + 1 // kick it over tail
diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm
index 8fbd9cf8d92b..1b2129eb61c8 100644
--- a/code/modules/clothing/suits/armor.dm
+++ b/code/modules/clothing/suits/armor.dm
@@ -814,7 +814,7 @@
if(!.)
return FALSE
var/mob/living/carbon/human/H
- if(istype(H) && istype(H.tail_style, /datum/sprite_accessory/tail/taur/wolf))
+ if(istype(H) && istype(H.tail_style, /datum/sprite_accessory/tail/legacy_taur/wolf))
return
else
to_chat(H,"You need to have a wolf-taur half to wear this.")
diff --git a/code/modules/clothing/suits/labcoat.dm b/code/modules/clothing/suits/labcoat.dm
index 53c0abab1e92..e25c4624379e 100644
--- a/code/modules/clothing/suits/labcoat.dm
+++ b/code/modules/clothing/suits/labcoat.dm
@@ -115,7 +115,7 @@
/obj/item/clothing/suit/storage/toggle/labcoat/blue
name = "blue-edged labcoat"
desc = "A suit that protects against minor chemical spills. This one has blue trim."
- icon_state = "blue_edge_labcoat"
+ icon_state = "labcoat_gen"
/obj/item/clothing/suit/storage/toggle/labcoat/ossnecro
name = "OSS&NECRO Labcoat"
diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm
index 092bf13f18dc..72aae1fd7516 100644
--- a/code/modules/clothing/under/_under.dm
+++ b/code/modules/clothing/under/_under.dm
@@ -151,7 +151,7 @@
//! Rendering
// todo : NUKE THIS SHIT FROM ORBIT ~silicons
//UNIFORM: Always appends "_s" to iconstate, stupidly.
-/obj/item/clothing/under/resolve_legacy_state(mob/M, datum/inventory_slot_meta/slot_meta, inhands, bodytype)
+/obj/item/clothing/under/resolve_legacy_state(mob/M, datum/inventory_slot/slot_meta, inhands, bodytype)
if(snowflake_worn_state && (slot_meta.id == SLOT_ID_UNIFORM))
return snowflake_worn_state + "_s"
return ..()
@@ -212,11 +212,11 @@
update_worn_icon()
/obj/item/clothing/under/proc/autodetect_rolldown(bodytype)
- var/datum/inventory_slot_meta/inventory/uniform/wow_this_sucks = resolve_inventory_slot_meta(SLOT_ID_UNIFORM)
+ var/datum/inventory_slot/inventory/uniform/wow_this_sucks = resolve_inventory_slot(SLOT_ID_UNIFORM)
return wow_this_sucks.check_rolldown_cache(bodytype, resolve_legacy_state(null, wow_this_sucks, FALSE, bodytype))
/obj/item/clothing/under/proc/autodetect_rollsleeve(bodytype)
- var/datum/inventory_slot_meta/inventory/uniform/wow_this_sucks = resolve_inventory_slot_meta(SLOT_ID_UNIFORM)
+ var/datum/inventory_slot/inventory/uniform/wow_this_sucks = resolve_inventory_slot(SLOT_ID_UNIFORM)
return wow_this_sucks.check_rollsleeve_cache(bodytype, resolve_legacy_state(null, wow_this_sucks, FALSE, bodytype))
//! Examine
diff --git a/code/modules/clothing/under/accessories/accessory.dm b/code/modules/clothing/under/accessories/accessory.dm
index 88bd2fcfd6a2..bb7aa1ecff1b 100644
--- a/code/modules/clothing/under/accessories/accessory.dm
+++ b/code/modules/clothing/under/accessories/accessory.dm
@@ -406,6 +406,30 @@
name = "neck gaiter (gray)"
icon_state = "gaiter_gray"
+/obj/item/clothing/accessory/gaiter/green
+ name = "neck gaiter (green)"
+ icon_state = "gaiter_green"
+
+/obj/item/clothing/accessory/gaiter/blue
+ name = "neck gaiter (blue)"
+ icon_state = "gaiter_blue"
+
+/obj/item/clothing/accessory/gaiter/purple
+ name = "neck gaiter (purple)"
+ icon_state = "gaiter_purple"
+
+/obj/item/clothing/accessory/gaiter/orange
+ name = "neck gaiter (orange)"
+ icon_state = "gaiter_orange"
+
+/obj/item/clothing/accessory/gaiter/charcoal
+ name = "neck gaiter (charcoal)"
+ icon_state = "gaiter_charcoal"
+
+/obj/item/clothing/accessory/gaiter/snow
+ name = "neck gaiter (white)"
+ icon_state = "gaiter_snow"
+
/obj/item/clothing/accessory/halfcape
name = "half cape"
desc = "A tasteful half-cape, suitible for European nobles and retro anime protagonists."
diff --git a/code/modules/clothing/under/accessories/clothing.dm b/code/modules/clothing/under/accessories/clothing.dm
index bbfad157117a..06220e3b8476 100644
--- a/code/modules/clothing/under/accessories/clothing.dm
+++ b/code/modules/clothing/under/accessories/clothing.dm
@@ -553,6 +553,16 @@
desc = "A white long sweater with a modest string to keep the otherwise immodest front piece from falling off. Compatible with a variety of chest sizes. It seems like it's made of a soft material."
icon_state = "virgin_sweater"
+/obj/item/clothing/accessory/sweater/milk
+ name = "Mega Milk sweater (f)"
+ desc = "A white shirt with blue sleeves. The words 'Mega Milk' have been written in black around the chest area. The shirt itself is quite well-fitting, accentuating the curves, with additional material to fit the wearer's chest."
+ icon_state = "milk_sweater_f"
+
+/obj/item/clothing/accessory/sweater/milk/male
+ name = "Mega Milk sweater (m)"
+ desc = "A white shirt with blue sleeves. The words 'Mega Milk' have been written in black around the chest area. The shirt itself is quite well-fitting, accentuating the curves."
+ icon_state = "milk_sweater_m"
+
//***
// End of sweaters
//***
@@ -685,3 +695,51 @@
name = "antediluvian armband"
desc = "A small, fake blue gem placed neatly into an otherwise cloth armband with thin metal outlines."
icon_state = "ante_armband"
+
+// ranger ponchos
+
+/obj/item/clothing/accessory/poncho/roles/ranger
+ name = "red ranger poncho"
+ desc = "A rugged all-weather poncho, perfectly coloured to match a popular line of neck gaiters. You could probably use it as a tent in a pinch!"
+ icon_state = "rangerponcho_red"
+ item_state = "rangerponcho_red"
+
+/obj/item/clothing/accessory/poncho/roles/ranger/tan
+ name = "tan ranger poncho"
+ icon_state = "rangerponcho_tan"
+ item_state = "rangerponcho_tan"
+
+/obj/item/clothing/accessory/poncho/roles/ranger/gray
+ name = "gray ranger poncho"
+ icon_state = "rangerponcho_gray"
+ item_state = "rangerponcho_gray"
+
+/obj/item/clothing/accessory/poncho/roles/ranger/green
+ name = "green ranger poncho"
+ icon_state = "rangerponcho_green"
+ item_state = "rangerponcho_green"
+
+/obj/item/clothing/accessory/poncho/roles/ranger/blue
+ name = "blue ranger poncho"
+ icon_state = "rangerponcho_blue"
+ item_state = "rangerponcho_blue"
+
+/obj/item/clothing/accessory/poncho/roles/ranger/purple
+ name = "purple ranger poncho"
+ icon_state = "rangerponcho_purple"
+ item_state = "rangerponcho_purple"
+
+/obj/item/clothing/accessory/poncho/roles/ranger/orange
+ name = "orange ranger poncho"
+ icon_state = "rangerponcho_orange"
+ item_state = "rangerponcho_orange"
+
+/obj/item/clothing/accessory/poncho/roles/ranger/charcoal
+ name = "charcoal ranger poncho"
+ icon_state = "rangerponcho_charcoal"
+ item_state = "rangerponcho_charcoal"
+
+/obj/item/clothing/accessory/poncho/roles/ranger/snow
+ name = "white ranger poncho"
+ icon_state = "rangerponcho_snow"
+ item_state = "rangerponcho_snow"
diff --git a/code/modules/customitems/item_spawning.dm b/code/modules/customitems/item_spawning.dm
index 2b26a723ec91..7e863ee7afb5 100644
--- a/code/modules/customitems/item_spawning.dm
+++ b/code/modules/customitems/item_spawning.dm
@@ -232,7 +232,7 @@
if(M.equip_to_appropriate_slot(newitem, INV_OP_SILENT))
return newitem
- if(M.equip_to_slot_if_possible(newitem, /datum/inventory_slot_meta/abstract/put_in_storage, INV_OP_SILENT))
+ if(M.equip_to_slot_if_possible(newitem, /datum/inventory_slot/abstract/put_in_storage, INV_OP_SILENT))
return newitem
newitem.forceMove(M.drop_location())
diff --git a/code/modules/detectivework/tools/rag.dm b/code/modules/detectivework/tools/rag.dm
index 95562c440abc..4b2741e467bf 100644
--- a/code/modules/detectivework/tools/rag.dm
+++ b/code/modules/detectivework/tools/rag.dm
@@ -13,6 +13,22 @@
/obj/item/clothing/shoes/
var/track_blood = 0
+/obj/item/reagent_containers/glass/rag/sponge
+ name = "sponge"
+ desc = "Scrub scrub scrub!"
+ icon = 'icons/obj/sponge.dmi'
+ icon_state = "sponge"
+
+/obj/item/reagent_containers/glass/rag/sponge/update_icon()
+ if(on_fire)
+ icon_state = "spongelit"
+ else
+ icon_state = "sponge"
+
+ var/obj/item/reagent_containers/food/drinks/bottle/B = loc
+ if(istype(B))
+ B.update_icon()
+
/obj/item/reagent_containers/glass/rag
name = "rag"
desc = "For cleaning up messes, you suppose."
diff --git a/code/modules/emoji/emoji_parse.dm b/code/modules/emoji/emoji_parse.dm
index 5e47e31df08c..2fb8e63b2ce7 100644
--- a/code/modules/emoji/emoji_parse.dm
+++ b/code/modules/emoji/emoji_parse.dm
@@ -1,3 +1,4 @@
+// todo: redo this, it should be tgui-native
/proc/emoji_parse(text) //turns :ai: into an emoji in text.
. = text
if(!CONFIG_GET(flag/emojis))
@@ -15,7 +16,7 @@
if(search)
emoji = lowertext(copytext(text, pos + length(text[pos]), search))
var/isthisapath = (emoji[1] == "/") && text2path(emoji)
- var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/chat)
+ var/datum/asset_pack/spritesheet/sheet = SSassets.ready_asset_pack(/datum/asset_pack/spritesheet/chat)
var/tag = sheet.icon_tag("emoji-[emoji]")
if(tag)
parsed += SPAN_TOOLTIP(":[emoji]:", "[tag]") //evil way of enforcing 16x16
diff --git a/code/modules/fishing/equipment/catalog.dm b/code/modules/fishing/equipment/catalog.dm
index d8ce1d083f77..c74b95e220a6 100644
--- a/code/modules/fishing/equipment/catalog.dm
+++ b/code/modules/fishing/equipment/catalog.dm
@@ -104,7 +104,6 @@
.["traits"] = trait_descriptions
return .
-/obj/item/book/fish_catalog/ui_assets(mob/user)
- return list(
- get_asset_datum(/datum/asset/spritesheet/fish)
- )
+/obj/item/book/fish_catalog/ui_asset_injection(datum/tgui/ui, list/immediate, list/deferred)
+ immediate += /datum/asset_pack/spritesheet/fish
+ return ..()
diff --git a/code/modules/fishing/equipment/rod.dm b/code/modules/fishing/equipment/rod.dm
index 017c8a110f98..627f0e2a50bc 100644
--- a/code/modules/fishing/equipment/rod.dm
+++ b/code/modules/fishing/equipment/rod.dm
@@ -270,7 +270,7 @@
bait_state = real_bait.rod_overlay_icon_state
. += bait_state
-/obj/item/fishing_rod/render_apply_overlays(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot_meta/slot_meta, icon_used)
+/obj/item/fishing_rod/render_apply_overlays(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot/slot_meta, icon_used)
var/slot_key = slot_meta.render_key
var/line_color = line?.line_color || default_line_color
var/mutable_appearance/reel_overlay = mutable_appearance(icon_used, "reel_[slot_key]")
diff --git a/code/modules/fishing/fish_source.dm b/code/modules/fishing/fish_source.dm
index 5ec0e90a9e69..fb1451e3b761 100644
--- a/code/modules/fishing/fish_source.dm
+++ b/code/modules/fishing/fish_source.dm
@@ -31,7 +31,7 @@ GLOBAL_LIST_EMPTY(cached_fish_sources)
var/fishing_difficulty = FISHING_DEFAULT_DIFFICULTY
/// How the spot type is described in fish catalog section about fish sources, will be skipped if null
var/catalog_description
- /// Background image name from /datum/asset/simple/fishing_minigame
+ /// Background image name from /datum/asset_pack/simple/fishing_minigame
var/background = "fishing_background_default"
/// Can we fish in this spot at all. Returns DENIAL_REASON or null if we're good to go
diff --git a/code/modules/fishing/minigame/fishing_minigame.dm b/code/modules/fishing/minigame/fishing_minigame.dm
index 78a914fd5211..ddd907e6728b 100644
--- a/code/modules/fishing/minigame/fishing_minigame.dm
+++ b/code/modules/fishing/minigame/fishing_minigame.dm
@@ -30,7 +30,7 @@
var/obj/item/fishing_rod/used_rod
/// Lure visual
var/obj/effect/fishing_lure/lure
- /// Background image from /datum/asset/simple/fishing_minigame
+ /// Background image from /datum/asset_pack/simple/fishing_minigame
var/background = "default"
/// Max distance we can move from the spot
@@ -171,8 +171,9 @@
.["special_effects"] = special_effects
.["background_image"] = background
-/datum/fishing_challenge/ui_assets(mob/user)
- return list(get_asset_datum(/datum/asset/simple/fishing_minigame)) //preset screens
+/datum/fishing_challenge/ui_asset_injection(datum/tgui/ui, list/immediate, list/deferred)
+ immediate += /datum/asset_pack/simple/fishing_minigame
+ return ..()
/datum/fishing_challenge/ui_status(mob/user, datum/ui_state/state)
return min(
diff --git a/code/modules/frames/frame.dm b/code/modules/frames/frame.dm
new file mode 100644
index 000000000000..395c5beada8c
--- /dev/null
+++ b/code/modules/frames/frame.dm
@@ -0,0 +1,537 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
+GLOBAL_LIST_INIT(frame_datum_lookup, init_frame_datums())
+
+/proc/init_frame_datums()
+ var/list/constructed = list()
+ for(var/datum/frame2/frame_path as anything in subtypesof(/datum/frame2))
+ if(initial(frame_path.abstract_type) == frame_path)
+ continue
+ var/datum/frame2/made = new frame_path
+ constructed[frame_path] = made
+ return constructed
+
+/proc/fetch_frame_datum(datum/frame2/framelike)
+ if(istype(framelike))
+ else
+ framelike = GLOB.frame_datum_lookup[framelike]
+ return framelike
+
+/**
+ * arbitrary construction framework
+ *
+ * ### how state machines work here
+ *
+ * frames operate off two principles, and are effectively a state machine:
+ * * stage - *usually* linear, 1 to n. stepping back from 1 deconstructs the frame. stepping forwards from n finishes the frame.
+ * * context - arbitrary data storage list
+ *
+ * ### how construction/deconstruction stage lists works:
+ *
+ * * set 'key' = /datum/frame_stage typepath
+ * * you'll probably want anonymous types.
+ * * please see examples.
+ *
+ * ### special things about the stage list
+ * * if there are no stages, we immediately finish() with stage null with no context when someone tries to place the item/structure.
+ *
+ * ### special things in general
+ * * wall frames should face away from the wall. most wall machinery face **away** from the wall.
+ *
+ * todo: no support for multiple 'interact' stages yet
+ * todo: /datum/frame_context so it can hold entities / data inside (e.g. can drop stuff if needed)
+ * todo: similarly, we need a way to store items inserted as part of a step, maybe as a part of context? so it can be dropped later.
+ */
+/datum/frame2
+ /// frame name
+ var/name = "construction frame"
+ /// sheet metal cost
+ var/material_cost = 1
+ // todo: non-steel support
+ /// for future use: set to TRUE to allow all materials
+ var/material_unlocked = FALSE
+ /// can we be built?
+ var/material_buildable = TRUE
+
+ /// deconstructs into item frame, or materials?
+ var/deconstruct_into_item = TRUE
+ /// deconstruct: **if and only if** steps_backwards does not specify a first step,
+ /// default to this tool
+ var/deconstruct_default_tool = TOOL_WRENCH
+ /// default deconstruction time
+ var/deconstruct_default_time = 3 SECONDS
+ /// default deconstruction cost multiplier
+ var/deconstruct_default_cost = 1
+
+ /// construction stages
+ /// see /datum/frame2 readme (so up above in this file) for how to do this
+ var/list/stages = list()
+ /// stage that a new construct starts at
+ /// defaults to stages[1]
+ var/stage_starting
+
+ // todo: implement anchor shit.
+
+ /// is this frame freely un/anchorable?
+ var/freely_anchorable = FALSE
+ /// do we need to be anchored to finish? we will not allow progression past last stage if so.
+ var/requires_anchored_to_finish = TRUE
+ /// requires anchored to do anything
+ var/requires_anchored = TRUE
+ /// starts anchored; null = [requires_anchored]
+ var/starts_anchored
+ /// anchoring time
+ var/anchor_time = 2 SECONDS
+ /// anchoring tool
+ var/anchor_tool = TOOL_WRENCH
+
+ /// are we a dense frame object? if it's a wall mount, the answer should probably be no.
+ var/has_density = FALSE
+
+ /// check for all dense objects on turf if we're dense
+ var/check_turf_content_density_dynamic = TRUE
+ /// still check for any density
+ var/check_turf_content_density_always = TRUE
+ /// check for another frame of this type on turf
+ var/check_turf_frame_duplicate = TRUE
+ /// check for another frame of this class on turf
+ /// * wallframes in same direction
+ /// * other non-wall-frames
+ var/check_turf_frame_collision = TRUE
+
+ /// is this frame a wall-frame?
+ var/wall_frame = FALSE
+ /// does the wall frame.. require a wall? please put yes.
+ var/wall_frame_requires_wall = TRUE
+ /// default pixel x offset for wall-frames; positive if right, negative if left
+ /// can set to list of "[NORTH]" = number, as well.
+ var/wall_pixel_x = 16
+ /// default pixel y offset for wall-frames; positive if up, negative if down
+ /// can set to list of "[NORTH]" = number, as well.
+ var/wall_pixel_y = 16
+
+ /// are we climbable if we're dense?
+ var/climb_allowed = TRUE
+ /// climb delay
+ var/climb_delay = 2 SECONDS
+
+ /// our structure's depth, if not a wall mount
+ var/depth_level = 16
+ /// does our structure project depth?
+ var/depth_projected = TRUE
+
+ /// our icon
+ var/icon
+ /// do we append -stage to structure?
+ /// structure preview state will always be "structure"
+ var/has_structure_stage_states = FALSE
+ /// do we append -stage to items?
+ /// item preview state will always be "item"
+ // todo: currently unused given lack of support for storing state
+ // todo: in the future, we will want this. for now, everything is just 'item'.
+ var/has_item_stage_states = FALSE
+
+ /// weight class of item
+ var/item_weight_class = WEIGHT_CLASS_NORMAL
+ /// weight volume of item
+ var/item_weight_volume = WEIGHT_VOLUME_NORMAL
+ /// what tool, if any to deconstruct item into materials
+ var/item_recycle_tool
+ /// time to decon for tool as item
+ var/item_recycle_time = 0 SECONDS
+ /// cost mult to decon for tool as item
+ var/item_recycle_cost = 1
+ /// what tool, if any, to attempt to set us up in a location
+ var/item_deploy_tool = TOOL_WRENCH
+ /// is tool required for deployment?
+ var/item_deploy_requires_tool = FALSE
+ /// deployment time
+ var/item_deploy_time = 0 SECONDS
+ /// deployment tool cost multiplier
+ var/item_deploy_cost = 1
+
+/datum/frame2/New()
+ for(var/i in 1 to length(stages))
+ var/key = stages[i]
+ var/value = stages[key]
+ if(istype(key, /datum/frame_stage))
+ continue
+ else if(istext(key))
+ stages[key] = new value
+ if(isnull(stage_starting) && length(stages))
+ stage_starting = stages[1]
+
+/datum/frame2/proc/apply_to_frame(obj/structure/frame2/frame)
+ frame.set_density(has_density)
+ frame.icon = icon
+ frame.update_appearance()
+
+ if(wall_frame)
+ // wall frames face away from walls :/
+ if(islist(wall_pixel_y))
+ frame.set_base_pixel_x(wall_pixel_x["[frame.dir]"] || 0)
+ else
+ frame.set_base_pixel_x(frame.dir & EAST? -wall_pixel_x : (frame.dir & WEST? wall_pixel_x : 0))
+ if(islist(wall_pixel_y))
+ frame.set_base_pixel_y(wall_pixel_y["[frame.dir]"] || 0)
+ else
+ frame.set_base_pixel_y(frame.dir & NORTH? -wall_pixel_y : (frame.dir & SOUTH? wall_pixel_y : 0))
+ frame.climb_allowed = FALSE
+ frame.climb_delay = 0
+ frame.depth_level = 0
+ frame.depth_projected = FALSE
+ else
+ frame.climb_allowed = climb_allowed
+ frame.climb_delay = climb_delay
+ frame.depth_level = depth_level
+ frame.depth_projected = depth_projected
+
+/**
+ * @return finished product
+ */
+/datum/frame2/proc/finish_frame(obj/structure/frame2/frame, datum/event_args/actor/actor, destroy_structure = TRUE)
+ ASSERT(isturf(frame.loc))
+ . = instance_product(frame)
+ if(destroy_structure)
+ qdel(frame)
+
+/**
+ * todo: /instance_from_frame()? we certainly can't have this be on /Initialize level at /obj, which sucks.. oh well.
+ *
+ * @return finished product
+ */
+/datum/frame2/proc/instance_product(obj/structure/frame2/frame)
+ CRASH("abstract proc called.")
+
+/**
+ * makes frame structure from item
+ *
+ * @return /obj/structure/frame, **if** we have stages. If not, we just finish it immediately.
+ */
+/datum/frame2/proc/deploy_frame(obj/item/frame2/frame_item, datum/event_args/actor/actor, atom/location, dir, destroy_item = TRUE)
+ var/obj/structure/frame2/creating_frame = new(location, dir, src)
+ creating_frame.set_anchored(isnull(starts_anchored)? requires_anchored : starts_anchored)
+
+ if(destroy_item)
+ qdel(frame_item)
+
+ if(!length(stages))
+ return // return nothing
+ return creating_frame
+
+/**
+ * @params
+ * * frame - the frame
+ * * actor - the person doing it, if any
+ * * put_in_hand_if_possible - put in user's hand instead of put it on the floor if possible
+ * * override_slice_to_parts - if non null, TRUE = deconstruct(), FALSE = collapse to item
+ *
+ * @return frame item dropped, if any.
+ */
+/datum/frame2/proc/deconstruct_frame(obj/structure/frame2/frame, datum/event_args/actor/actor, put_in_hand_if_possible = TRUE, override_slice_to_parts)
+ var/breaking_to_parts = isnull(override_slice_to_parts)? !deconstruct_into_item : override_slice_to_parts
+ if(breaking_to_parts)
+ frame.deconstruct(ATOM_DECONSTRUCT_DISASSEMBLED)
+ else
+ var/obj/item/frame2/collapsed
+ if(actor?.performer && put_in_hand_if_possible)
+ collapsed = new(actor.performer, src)
+ actor.performer.put_in_hand_or_drop(collapsed)
+ else
+ collapsed = new(frame.drop_location(), src)
+ return collapsed
+
+/**
+ * @params
+ * * method - ATOM_DECONSTRUCT_* define
+ * * where - atom to drop stuff at
+ * * stage_key - stage id
+ * * context - context list
+ */
+/datum/frame2/proc/drop_deconstructed_products(method, atom/where, stage_key, list/context)
+ // todo: drop all other stored things from steps!!
+ new /obj/item/stack/material/steel(where, material_cost)
+
+/**
+ * always use this proc, it's guarded against race conditions.
+ *
+ * If trying to deconstruct or finish the frame, you *must* do:
+ * * FRAME_STAGE_DECONSTRUCT
+ * * FRAME_STAGE_FINISH
+ *
+ * @params
+ * * frame - the frame being operated on
+ * * from_stage - move from this stage; if current stage key doesn't match expected, we abort as it might be a race condition.
+ * * to_stage - move to this stage
+ * * actor - actor data
+ *
+ * @return TRUE / FALSE success / fail
+ */
+/datum/frame2/proc/move_frame_to(obj/structure/frame2/frame, from_stage, to_stage, datum/event_args/actor/actor)
+ if(frame.stage != from_stage)
+ return FALSE
+
+ var/not_obliterating = FALSE
+ switch(to_stage)
+ if(FRAME_STAGE_DECONSTRUCT)
+ if(FRAME_STAGE_FINISH)
+ else
+ if(isnull(stages[to_stage]))
+ // check your fucking inputs
+ CRASH("attempted to go to invalid stage!")
+ if(from_stage == to_stage)
+ // check your fucking inputs
+ CRASH("attempted to move from the same state to the same state. why?")
+ not_obliterating = TRUE
+
+ frame.stage = to_stage
+ on_frame_step(frame, from_stage, to_stage)
+
+ if(not_obliterating)
+ frame.update_appearance()
+
+ log_construction(actor, frame, "mov [from_stage] -> [to_stage]")
+
+ switch(to_stage)
+ if(FRAME_STAGE_DECONSTRUCT)
+ deconstruct_frame(frame, actor)
+ if(FRAME_STAGE_FINISH)
+ finish_frame(frame, actor)
+
+/**
+ * Called when we transition stage.
+ * * called before update_icon() / re-renders
+ * * called before deconstruction/finish
+ */
+/datum/frame2/proc/on_frame_step(obj/structure/frame2/frame, from_stage, to_stage)
+ return
+
+/datum/frame2/proc/on_examine(obj/structure/frame2/frame, datum/event_args/actor/actor, list/examine_list, distance)
+ var/datum/frame_stage/stage = stages[frame.stage]
+ examine_list += stage.on_examine(frame, actor, examine_list, distance)
+ examine_list += instruction_steps(frame, actor, distance)
+ examine_list += instruction_special(frame, actor, distance)
+
+/**
+ * @return string or list of strings
+ */
+/datum/frame2/proc/instruction_steps(obj/structure/frame2/frame, datum/event_args/actor/actor, distance)
+ var/datum/frame_stage/stage = stages[frame.stage]
+ if(isnull(stage))
+ return list()
+ var/list/datum/frame_step/steps = stage.steps
+ if(!length(steps))
+ return list()
+ . = list()
+ for(var/datum/frame_step/step as anything in steps)
+ . += step.examine(frame, actor)
+
+/**
+ * @return list(string, ...)
+ */
+/datum/frame2/proc/instruction_special(obj/structure/frame2/frame, datum/event_args/actor/actor, distance)
+ return list()
+
+/**
+ * @return TRUE if handled
+ */
+/datum/frame2/proc/on_item(obj/structure/frame2/frame, obj/item/item, datum/event_args/actor/actor)
+ // todo: support for multiple possible steps
+ var/datum/frame_step/step_to_take
+ var/datum/frame_stage/current_stage = stages[frame.stage]
+ for(var/datum/frame_step/potential_step as anything in current_stage.steps)
+ if(!potential_step.valid_interaction(actor, item, src, frame))
+ continue
+ step_to_take = potential_step
+ break
+ if(!step_to_take)
+ return FALSE
+ var/time_needed = step_to_take.time
+ . = TRUE
+ standard_progress_step(frame, actor, item, step_to_take, time_needed)
+
+/**
+ * @return TRUE if handled
+ */
+/datum/frame2/proc/on_tool(obj/structure/frame2/frame, obj/item/tool, datum/event_args/actor/actor, function, flags, hint)
+ var/datum/frame_step/step_to_take
+ var/datum/frame_stage/current_stage = stages[frame.stage]
+ for(var/datum/frame_step/potential_step as anything in current_stage.steps)
+ if(!potential_step.valid_interaction(actor, tool, src, frame))
+ continue
+ if(hint && (potential_step.name != hint))
+ continue
+ step_to_take = potential_step
+ break
+ if(isnull(step_to_take))
+ return FALSE
+ // tool speed is handled by use_tool
+ var/time_needed = step_to_take.time
+ return standard_progress_step(frame, actor, tool, step_to_take, time_needed)
+
+/**
+ * @return list
+ */
+/datum/frame2/proc/on_tool_query(obj/structure/frame2/frame, obj/item/tool, datum/event_args/actor/clickchain/click)
+ . = list()
+ var/datum/frame_stage/stage = stages[frame.stage]
+ for(var/datum/frame_step/step as anything in stage.steps)
+ if(step.request_type != FRAME_REQUEST_TYPE_TOOL)
+ continue
+ if(isnull(.[step.request]))
+ .[step.request] = list()
+ .[step.request][step.name || "yell at coders"] = step.tool_image()
+
+/**
+ * @return TRUE if handled
+ */
+/datum/frame2/proc/on_interact(obj/structure/frame2/frame, datum/event_args/actor/actor)
+ // todo: support for multiple possible steps
+ var/datum/frame_step/step_to_take
+ var/datum/frame_stage/current_stage = stages[frame.stage]
+ for(var/datum/frame_step/potential_step as anything in current_stage.steps)
+ if(!potential_step.valid_interaction(actor, null, src, frame))
+ continue
+ step_to_take = potential_step
+ break
+ if(isnull(step_to_take))
+ return FALSE
+ var/time_needed = step_to_take.time
+ return standard_progress_step(frame, actor, null, step_to_take, time_needed)
+
+/**
+ * handles the do after, item manipulation, logging, and whatnot
+ *
+ * @return TRUE / FALSE
+ */
+/datum/frame2/proc/standard_progress_step(obj/structure/frame2/frame, datum/event_args/actor/actor, obj/item/using_item, datum/frame_step/frame_step, time_needed)
+ var/stage_we_were_in = frame.stage
+ if(frame_step.stage == FRAME_STAGE_FINISH && !frame.anchored && requires_anchored_to_finish)
+ actor.chat_feedback(SPAN_WARNING("[frame] needs to be anchored to be finished."), frame)
+ return FALSE
+ if((isnull(frame_step.requires_anchored)? requires_anchored : frame_step.requires_anchored) && !frame.anchored)
+ var/rendered = frame_step.action_descriptor || "<[frame_step.name]>"
+ actor.chat_feedback(SPAN_WARNING("[frame] needs to be anchored in order for you to [rendered]."), frame)
+ return FALSE
+ if(!frame_step.check_consumption(actor, using_item, src, frame))
+ return FALSE
+ if(frame_step.stage == FRAME_STAGE_FINISH)
+ if(!completion_checks(frame, frame.loc, frame.dir, actor))
+ return FALSE
+ frame_step.feedback_begin(
+ actor,
+ src,
+ frame,
+ using_item,
+ time_needed,
+ )
+ if(!frame_step.perform_usage(actor, using_item, src, frame, time_needed))
+ return FALSE
+ if(frame.stage != stage_we_were_in)
+ return FALSE
+ if(!frame_step.handle_consumption(actor, using_item, src, frame))
+ return FALSE
+ if(frame_step.stage == FRAME_STAGE_FINISH)
+ if(!completion_checks(frame, frame.loc, frame.dir, actor))
+ return FALSE
+ frame_step.feedback_finish(
+ actor,
+ src,
+ frame,
+ using_item,
+ time_needed,
+ )
+ frame_step.on_finish(src, frame, actor, using_item)
+ move_frame_to(frame, stage_we_were_in, frame_step.stage, actor)
+ return TRUE
+
+/**
+ * ran only on deployment, not completion
+ * * also ran when anchoring a frame down
+ *
+ * regarding direction
+ * * is in direction of machine that will be built if non-wall
+ * * will be in the direction of the wall from the tile if wall
+ *
+ * @return TRUE / FALSE
+ */
+/datum/frame2/proc/deployment_checks(obj/item/frame2/frame, turf/location, dir, datum/event_args/actor/actor, silent)
+ if(wall_frame && wall_frame_requires_wall)
+ var/turf/checking = get_step(location, turn(dir, 180))
+ if(!checking.get_wallmount_anchor())
+ actor.chat_feedback(
+ SPAN_WARNING("[checking] isn't a valid anchor for this wall mount!"),
+ target = frame,
+ )
+ return FALSE
+ if((check_turf_content_density_dynamic && has_density) || check_turf_content_density_always)
+ for(var/obj/thing in location)
+ if(thing.density)
+ return FALSE
+ if(check_turf_frame_collision || check_turf_frame_duplicate)
+ for(var/obj/structure/frame2/other_frame in location)
+ if(other_frame.frame == src && check_turf_frame_duplicate)
+ return FALSE
+ if(!other_frame.frame.wall_frame)
+ if(!wall_frame)
+ return FALSE
+ else
+ if(other_frame.dir == frame.dir)
+ return FALSE
+ return valid_location(frame, location, dir, actor, silent)
+
+/**
+ * ran on deployment as well as completion
+ *
+ * @return FALSE if obstructed
+ */
+/datum/frame2/proc/completion_checks(obj/structure/frame2/frame, turf/location, dir, datum/event_args/actor/actor, silent)
+ return valid_location(location, dir, actor, silent)
+
+/**
+ * checks if we semantically should even be able to be built here at all;
+ * ran during both deployment and completion
+ *
+ * used for stuff like APCs rejecting areas that shouldn't be powerable/etc
+ *
+ * @params
+ * * entity - either the item or structure frame. this is generic, and is only really used for chat feedback object name purposes.
+ * * location - where it's being built/placed
+ * * dir - direction that it's being built/placed in
+ * * actor - (optional) person doing it
+ * * silent - don't emit chat feedback
+ */
+/datum/frame2/proc/valid_location(obj/entity, turf/location, dir, datum/event_args/actor/actor, silent)
+ return TRUE
+
+/**
+ * gets a list of managed overlays to apply to a frame
+ */
+/datum/frame2/proc/get_overlays(obj/structure/frame/frame)
+ return list()
+
+/**
+ * template action text
+ */
+/datum/frame2/proc/template_action_string(list/tokens, mob/performer, obj/structure/frame2/frame, obj/item/tool)
+ if(isnull(tokens))
+ return // null = null
+ // i wish this was typescript so i could just return tokens.map((t) => ...) :confounded:
+ // pov i'm losing my mcfucking mind
+ . = tokens.Copy()
+ for(var/i in 1 to length(.))
+ switch(.[i])
+ if(FRAME_TEXT_TOKEN_PERFORMER)
+ .[i] = "[performer]"
+ if(FRAME_TEXT_TOKEN_FRAME)
+ .[i] = "[frame]"
+ if(FRAME_TEXT_TOKEN_TOOL)
+ .[i] = "[tool]"
+ if(FRAME_TEXT_TOKEN_THEIR)
+ .[i] = "[performer.p_their()]"
+ if(FRAME_TEXT_TOKEN_THEM)
+ .[i] = "[performer.p_them()]"
+ if(FRAME_TEXT_TOKEN_THEYRE)
+ .[i] = "[performer.p_theyre()]"
+ return jointext(., "")
diff --git a/code/modules/frames/frame_stage.dm b/code/modules/frames/frame_stage.dm
new file mode 100644
index 000000000000..cc78fef7f2c3
--- /dev/null
+++ b/code/modules/frames/frame_stage.dm
@@ -0,0 +1,49 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
+/**
+ * a discrete stage in a construction frame
+ */
+/datum/frame_stage
+ /// name; for vv. defaults to key
+ var/name
+ /// key; autoset from key if associating via frames.
+ var/key
+ /// list of step datums
+ /// set to typepaths to init
+ /// anonymous typepaths are allowed and encouraged.
+ var/list/datum/frame_step/steps = list()
+ /// name prepend; if existing, will be prepended with a space
+ var/name_prepend
+ /// name append; if existing, will be appended with a space
+ /// * the (xyz) format e.g. "(wired)" is recommended, resulting in a render of "frame (wired)"
+ var/name_append
+ /// name override; if existing, will override base name (prepend/append are still used)
+ /// * the bare format e.g. "wired" is recommended, resulting in a render of "wired frame"
+ var/name_override
+ /// "the [name] [descriptor]" on examine
+ var/descriptor
+ /// default require anchor for steps; if null, default to frame
+ /// * this is for steps going from us, not steps going to us!
+ var/requires_anchored
+ /// allow unanchor while in this stage
+ /// if null, defaults to frame not being requires_anchored **or** us being the first stage.
+ var/allow_unanchor
+
+/datum/frame_stage/New(set_key)
+ if(!isnull(set_key))
+ key = set_key
+ if(isnull(name))
+ name = key
+ for(var/i in 1 to length(steps))
+ var/datum/frame_step/step_casted = steps[i]
+ if(istype(step_casted))
+ continue
+ var/datum/frame_step/creating = new step_casted
+ if(isnull(creating.requires_anchored))
+ creating.requires_anchored = src.requires_anchored
+ steps[i] = creating
+
+/datum/frame_stage/proc/on_examine(obj/structure/frame2/frame, datum/event_args/actor/actor, list/examine_list, distance)
+ if(descriptor)
+ examine_list += SPAN_NOTICE("[frame] [descriptor]")
diff --git a/code/modules/frames/frame_step.dm b/code/modules/frames/frame_step.dm
new file mode 100644
index 000000000000..b9d17192dc86
--- /dev/null
+++ b/code/modules/frames/frame_step.dm
@@ -0,0 +1,372 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
+/**
+ * a transition from one stage to another
+ */
+/datum/frame_step
+ /// step name for tool radials & more
+ var/name
+ /// "use a [whatever] on [frame] to [action_descriptor: "unscrew the panel"]"
+ var/action_descriptor
+ /// "you start [x -> 'unscrewing the panel on'][src] ..."
+ /// add a space after!
+ /// please don't put pronouns in this, it doesn't get interpolated properly.
+ var/action_text_leading
+ /// "you start ... [src][x -> ', removing the screws in the process.']"
+ /// don't forget punctuation at the end.
+ /// please don't put pronouns in this, it doesn't get interpolated properly.
+ var/action_text_trailing
+ /// stage key this moves us to
+ /// * [STAGE_DECONSTRUCT] to deconstruct
+ /// * [STAGE_FINISH] to finish
+ var/stage
+ /// direction: [TOOL_DIRECTION_FORWARDS] or [TOOL_DIRECTION_BACKWARDS] or [TOOL_DIRECTION_NEUTRAL]
+ /// * this is used as a hint for tool graphics
+ /// * this is used as a hint for other visual / textual feedback
+ var/direction = TOOL_DIRECTION_NEUTRAL
+
+ /// FRAME_REQUEST_TYPE_* define
+ var/request_type
+ /// ergo: stack type, item type, tool function, etc. what this is depends on [step_type]
+ /// limited autodetection is allowed.
+ var/request
+ /// * stacks: amount
+ /// * items: amount; if 0, we just apply the item to it
+ /// * rest: unused.
+ var/request_amount
+ /// * tools: this is cost
+ /// * rest: unused
+ var/request_cost
+
+ /// time needed to do this
+ /// note that for tool steps, this might impact the total cost!
+ var/time = 0 SECONDS
+
+ /// requires anchored; if null, defaults to stage.
+ var/requires_anchored
+
+ // todo: request_store: null for default, context key to store under context
+
+ /// what to drop when undertaking this step
+ /// can either be:
+ /// * /obj/item/stack typepath
+ /// * /datum/material typepath
+ /// * /obj/item typepath
+ /// todo: text for 'drop context key'
+ var/drop
+ /// amount to drop
+ var/drop_amount = 1
+
+ /// use custom text?
+ var/use_custom_feedback = FALSE
+ /// list of tokens to concat into a string to display when beginning the step.
+ /// will not be shown if the step is fast enough.
+ /// null for default.
+ ///
+ /// FRAME_TEXT_TOKEN_* defines are allowed, and will be automatically replaced during execution to the relevant text.
+ var/list/visible_text_begin
+ /// list of tokens to concat into a string to display when beginning the step.
+ /// will not be shown if the step is fast enough.
+ /// null for default.
+ ///
+ /// FRAME_TEXT_TOKEN_* defines are allowed, and will be automatically replaced during execution to the relevant text.
+ var/list/audible_text_begin
+ /// list of tokens to concat into a string to display when beginning the step.
+ /// will not be shown if the step is fast enough.
+ /// null for default.
+ ///
+ /// FRAME_TEXT_TOKEN_* defines are allowed, and will be automatically replaced during execution to the relevant text.
+ var/list/self_text_begin
+ /// list of tokens to concat into a string to display when finishing the step.
+ /// null for default.
+ ///
+ /// FRAME_TEXT_TOKEN_* defines are allowed, and will be automatically replaced during execution to the relevant text.
+ var/list/visible_text_end
+ /// list of tokens to concat into a string to display when finishing the step.
+ /// null for default.
+ ///
+ /// FRAME_TEXT_TOKEN_* defines are allowed, and will be automatically replaced during execution to the relevant text.
+ var/list/audible_text_end
+ /// list of tokens to concat into a string to display when finishing the step.
+ /// null for default.
+ ///
+ /// FRAME_TEXT_TOKEN_* defines are allowed, and will be automatically replaced during execution to the relevant text.
+ var/list/self_text_end
+
+/datum/frame_step/New()
+ if(isnull(request_type))
+ // autodetect
+ var/detected
+ if(ispath(request, /datum/material))
+ request_type = FRAME_REQUEST_TYPE_MATERIAL
+ else if(ispath(request, /obj/item/stack))
+ request_type = FRAME_REQUEST_TYPE_STACK
+ else if(ispath(request, /obj/item))
+ request_type = FRAME_REQUEST_TYPE_ITEM
+ else if(istext(request))
+ if(request in global.all_tool_functions)
+ request_type = FRAME_REQUEST_TYPE_TOOL
+ detected = TRUE
+ else if(!detected)
+ CRASH("failed to autodetect request")
+
+/datum/frame_step/proc/examine(obj/structure/frame2/frame, datum/event_args/actor/actor)
+ var/rendered_action = action_descriptor || SPAN_BOLD(name)
+ switch(request_type)
+ if(FRAME_REQUEST_TYPE_INTERACT)
+ . = "Interact with [frame] using an empty hand to [rendered_action]."
+ if(FRAME_REQUEST_TYPE_ITEM)
+ var/rendered_item
+ var/obj/item/casted = request
+ rendered_item = initial(casted.name)
+ // todo: support amounts
+ . = "Use an [rendered_item] on [frame] to [rendered_action]."
+ if(FRAME_REQUEST_TYPE_MATERIAL)
+ var/rendered_material
+ var/rendered_stack_name
+ var/datum/material/resolved = SSmaterials.resolve_material(request)
+ rendered_material = resolved.display_name
+ rendered_stack_name = resolved.sheet_plural_name
+ . = "Apply [request_amount || 0] [rendered_stack_name] of [rendered_material] to [rendered_action]."
+ if(FRAME_REQUEST_TYPE_PROC)
+ if(FRAME_REQUEST_TYPE_STACK)
+ var/rendered_stack
+ var/rendered_stack_name
+ var/obj/item/stack/casted = request
+ rendered_stack = initial(casted.name)
+ rendered_stack_name = initial(casted.name) || "sheets"
+ . = "Use [request_amount || 0] [rendered_stack_name] of [rendered_stack] to [rendered_action]."
+ if(FRAME_REQUEST_TYPE_TOOL)
+ var/rendered_tool = request
+ . = "Use a [rendered_tool] to [rendered_action]."
+ return SPAN_NOTICE(.)
+
+/datum/frame_step/proc/tool_image()
+ switch(direction)
+ if(TOOL_DIRECTION_BACKWARDS)
+ return dyntool_image_backward(request)
+ if(TOOL_DIRECTION_FORWARDS)
+ return dyntool_image_forward(request)
+ if(TOOL_DIRECTION_NEUTRAL)
+ return dyntool_image_neutral(request)
+
+/**
+ * checks if a given person, using a given tool, can undertake this step.
+ */
+/datum/frame_step/proc/valid_interaction(datum/event_args/actor/actor, obj/item/using_tool, datum/frame2/frame_datum, obj/structure/frame2/frame)
+ switch(request_type)
+ if(FRAME_REQUEST_TYPE_INTERACT)
+ return TRUE
+ if(FRAME_REQUEST_TYPE_ITEM)
+ return using_tool?.type == request
+ if(FRAME_REQUEST_TYPE_STACK)
+ return using_tool?.type == request
+ if(FRAME_REQUEST_TYPE_PROC)
+ return FALSE // override this proc
+ if(FRAME_REQUEST_TYPE_TOOL)
+ return using_tool?.tool_check(request, actor, frame, TOOL_OP_SILENT)
+ if(FRAME_REQUEST_TYPE_MATERIAL)
+ var/obj/item/stack/material/material_stack = using_tool
+ return istype(material_stack) && (ispath(request, /datum/material)? material_stack.material.type == request : material_stack.material.id == request)
+ return FALSE
+
+/**
+ * This proc may assume the item is already type filtered to be the valid item / type / stack / whatever.
+ * If it isn't, do not istype(); allow the runtime to happen so we can yell at those responsible.
+ */
+/datum/frame_step/proc/check_consumption(datum/event_args/actor/actor, obj/item/using_tool, datum/frame2/frame_datum, obj/structure/frame2/frame)
+ switch(request_type)
+ if(FRAME_REQUEST_TYPE_STACK)
+ var/obj/item/stack/stack = using_tool
+ if(stack.amount < request_amount)
+ return FALSE
+ return TRUE
+ if(FRAME_REQUEST_TYPE_MATERIAL)
+ var/obj/item/stack/material/material_stack = using_tool
+ if(material_stack.amount < request_amount)
+ return FALSE
+ return TRUE
+ return TRUE
+
+/**
+ * we take in time_needed, as computed by /datum/frame2.
+ *
+ * todo: should we be taking in time needed? this seems like the right option for now.
+ */
+/datum/frame_step/proc/perform_usage(datum/event_args/actor/actor, obj/item/using_tool, datum/frame2/frame_datum, obj/structure/frame2/frame, time_needed)
+ switch(request_type)
+ if(FRAME_REQUEST_TYPE_TOOL)
+ return frame.use_tool(
+ request,
+ using_tool,
+ actor,
+ delay = time_needed,
+ cost = request_cost,
+ )
+ else
+ return do_after(
+ actor.performer,
+ time_needed,
+ frame,
+ mobility_flags = MOBILITY_CAN_USE,
+ max_distance = using_tool?.reach || 1,
+ )
+
+/datum/frame_step/proc/handle_consumption(datum/event_args/actor/actor, obj/item/using_tool, datum/frame2/frame_datum, obj/structure/frame2/frame)
+ switch(request_type)
+ if(FRAME_REQUEST_TYPE_ITEM)
+ if(!actor.performer.attempt_void_item_for_installation(using_tool))
+ actor.chat_feedback(
+ SPAN_WARNING("[using_tool] is stuck to your hand!"),
+ target = frame,
+ )
+ return FALSE
+ qdel(using_tool)
+ return TRUE
+ if(FRAME_REQUEST_TYPE_TOOL)
+ // we do the usage in perform_usainge
+ return TRUE
+ if(FRAME_REQUEST_TYPE_STACK)
+ var/obj/item/stack/stack = using_tool
+ return stack.use(request_amount)
+ if(FRAME_REQUEST_TYPE_MATERIAL)
+ var/obj/item/stack/material/material_stack = using_tool
+ return material_stack.use(request_amount)
+ return TRUE
+
+/**
+ * called before frame is moved to new stage
+ */
+/datum/frame_step/proc/on_finish(datum/frame2/frame_datum, obj/structure/frame2/frame, datum/event_args/actor/actor, obj/item/using_item)
+ if(drop)
+ var/atom/drop_where = frame.drop_location()
+ if(ispath(drop, /obj/item/stack))
+ var/safety = 50
+ var/left = drop_amount
+ var/obj/item/stack/casted_stack = drop
+ do
+ var/dropping = min(left, initial(casted_stack.max_amount))
+ new drop(drop_where, dropping)
+ left -= dropping
+ while(--safety > 0 && left > 0)
+ else if(ispath(drop, /datum/material))
+ var/safety = 50
+ var/left = drop_amount
+ var/datum/material/resolved_material = SSmaterials.resolve_material(drop)
+ do
+ var/dropping = min(left, 50)
+ // todo: /datum/material based max stacks.
+ resolved_material.place_sheet(drop_where, dropping)
+ left -= dropping
+ while(--safety > 0 && left > 0)
+ else if(ispath(drop, /obj/item))
+ for(var/i in 1 to min(50, drop_amount))
+ new drop(drop_where)
+
+/datum/frame_step/proc/feedback_begin(datum/event_args/actor/actor, datum/frame2/frame_datum, obj/structure/frame2/frame, obj/item/tool, time_needed)
+ // don't bother if it's that fast
+ if(time_needed <= 0.5 SECONDS)
+ return
+ if(use_custom_feedback)
+ // custom
+ actor.visible_feedback(
+ target = frame,
+ visible = frame_datum.template_action_string(visible_text_begin, actor.performer, frame, tool),
+ audible = frame_datum.template_action_string(audible_text_begin, actor.performer, frame, tool),
+ otherwise_self = frame_datum.template_action_string(self_text_begin, actor.performer, frame, tool),
+ )
+ else
+ // default
+ standard_feedback_handling(actor, frame_datum, frame, tool, time_needed, TRUE)
+
+/datum/frame_step/proc/feedback_finish(datum/event_args/actor/actor, datum/frame2/frame_datum, obj/structure/frame2/frame, obj/item/tool, time_taken)
+ if(use_custom_feedback)
+ // custom
+ actor.visible_feedback(
+ target = frame,
+ visible = frame_datum.template_action_string(visible_text_end, actor.performer, frame, tool),
+ audible = frame_datum.template_action_string(audible_text_end, actor.performer, frame, tool),
+ otherwise_self = frame_datum.template_action_string(self_text_end, actor.performer, frame, tool),
+ )
+ else
+ // default
+ standard_feedback_handling(actor, frame_datum, frame, tool, time_taken, FALSE)
+
+/datum/frame_step/proc/standard_feedback_handling(datum/event_args/actor/actor, datum/frame2/frame_datum, obj/structure/frame2/frame, obj/item/tool, time, beginning)
+ switch(request_type)
+ if(FRAME_REQUEST_TYPE_INTERACT)
+ if(beginning)
+ actor.visible_feedback(
+ target = frame,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = "[actor.performer] starts [action_text_leading || "tinkering with "][frame][action_text_trailing || "."]",
+ audible = "You hear something being tinkered with.",
+ otherwise_self = "You start [action_text_leading || "tinkering with "][frame][action_text_trailing || "."]",
+ )
+ else
+ actor.visible_feedback(
+ target = frame,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = "[actor.performer] finishes [action_text_leading || "tinkering with "][frame][action_text_trailing || "."]",
+ audible = "You hear something being tinkered with.",
+ otherwise_self = "You finish [action_text_leading || "tinkering with "][frame][action_text_trailing || "."]",
+ )
+ if(FRAME_REQUEST_TYPE_ITEM)
+ if(beginning)
+ actor.visible_feedback(
+ target = frame,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = "[actor.performer] starts [action_text_leading || "tinkering with "][frame][action_text_trailing || " using [tool]."]",
+ audible = "You hear something being tinkered with.",
+ otherwise_self = "You start [action_text_leading || "tinkering with "][frame][action_text_trailing || " using [tool]."]",
+ )
+ else
+ actor.visible_feedback(
+ target = frame,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = "[actor.performer] finishes [action_text_leading || "tinkering with "][frame][action_text_trailing || " using [tool]."]",
+ audible = "You hear something being tinkered with.",
+ otherwise_self = "You finish [action_text_leading || "tinkering with "][frame][action_text_trailing || " using [tool]."]",
+ )
+ if(FRAME_REQUEST_TYPE_MATERIAL, FRAME_REQUEST_TYPE_STACK)
+ var/name_to_use
+ if(request_type == FRAME_REQUEST_TYPE_MATERIAL)
+ var/datum/material/resolved_material = SSmaterials.resolve_material(request)
+ name_to_use = "[resolved_material.name || resolved_material.display_name] [resolved_material.sheet_plural_name]"
+ else
+ var/obj/item/stack/casted_stack = request
+ name_to_use = casted_stack.name
+ if(beginning)
+ actor.visible_feedback(
+ target = frame,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = "[actor.performer] starts [action_text_leading || "inserting some [name_to_use] into "][frame][action_text_trailing || "."]",
+ audible = "You hear something being tinkered with.",
+ otherwise_self = "You start [action_text_leading || "inserting some [name_to_use] into "][frame][action_text_trailing || "."]",
+ )
+ else
+ actor.visible_feedback(
+ target = frame,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = "[actor.performer] finishes [action_text_leading || "inserting some [name_to_use] into "][frame][action_text_trailing || "."]",
+ audible = "You hear something being tinkered with.",
+ otherwise_self = "You finish [action_text_leading || "inserting some [name_to_use] into "][frame][action_text_trailing || "."]",
+ )
+ if(FRAME_REQUEST_TYPE_TOOL)
+ if(beginning)
+ actor.visible_feedback(
+ target = frame,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = "[actor.performer] starts [action_text_leading || "tinkering with "][frame][action_text_trailing || " with [tool]."]",
+ audible = "You hear something being tinkered with.",
+ otherwise_self = "You start [action_text_leading || "tinkering with "][frame][action_text_trailing || " with [tool]."]",
+ )
+ else
+ actor.visible_feedback(
+ target = frame,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = "[actor.performer] finishes [action_text_leading || "tinkering with "][frame][action_text_trailing || " with [tool]."]",
+ audible = "You hear something being tinkered with.",
+ otherwise_self = "You finish [action_text_leading || "tinkering with "][frame][action_text_trailing || " with [tool]."]",
+ )
diff --git a/code/modules/frames/item.dm b/code/modules/frames/item.dm
new file mode 100644
index 000000000000..7233ee693f76
--- /dev/null
+++ b/code/modules/frames/item.dm
@@ -0,0 +1,304 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
+/obj/item/frame2
+ name = "entity frame"
+ desc = "Why do you see this? Contact a coder."
+ icon = 'icons/modules/frames/base.dmi'
+ icon_state = "item"
+
+ item_flags = ITEM_NOBLUDGEON
+ obj_rotation_flags = OBJ_ROTATION_ENABLED | OBJ_ROTATION_DEFAULTING
+
+ /// frame datum - set to typepath for initialization
+ var/datum/frame2/frame
+ /// our cached image for hover
+ var/image/hover_image
+ /// viewing clients
+ var/list/client/viewing
+
+/obj/item/frame2/Initialize(mapload, datum/frame2/frame)
+ . = ..()
+ if(!isnull(frame))
+ src.frame = frame
+ if(ispath(src.frame))
+ src.frame = fetch_frame_datum(src.frame)
+ sync_frame(src.frame)
+
+/obj/item/frame2/Destroy()
+ for(var/client/C as anything in viewing)
+ hide_frame_image(C)
+ return ..()
+
+/obj/item/frame2/proc/sync_frame(datum/frame2/frame)
+ name = "[frame.name]"
+ icon = frame.icon
+ icon_state = "item"
+ w_class = frame.item_weight_class
+ weight_volume = frame.item_weight_volume
+
+/obj/item/frame2/examine(mob/user, dist)
+ . = ..()
+ if(!frame.item_deploy_requires_tool)
+ if(frame.wall_frame)
+ . += SPAN_NOTICE("Use it on a wall, window, or other 'wall-like' object to attach the frame.")
+ else
+ . += SPAN_NOTICE("Use it in hand to deploy it in the direction you are looking at.")
+ if(frame.item_deploy_tool)
+ . += SPAN_NOTICE("Use a [frame.item_deploy_tool] on it to deploy it in its current direction.")
+ if(frame.item_recycle_tool)
+ . += SPAN_NOTICE("Use a [frame.item_recycle_tool] on it to deconstruct it back into material sheets.")
+
+/obj/item/frame2/MouseEntered(location, control, params)
+ ..()
+ if(!usr?.client)
+ return
+ show_frame_image(usr.client)
+
+/obj/item/frame2/MouseExited(location, control, params)
+ ..()
+ if(!usr?.client)
+ return
+ hide_frame_image(usr.client)
+
+/obj/item/frame2/proc/show_frame_image(client/C)
+ LAZYDISTINCTADD(viewing, C)
+ C.images += get_hover_image()
+ RegisterSignal(C, COMSIG_PARENT_QDELETING, PROC_REF(on_client_delete))
+
+/obj/item/frame2/proc/hide_frame_image(client/C)
+ LAZYREMOVE(viewing, C)
+ C.images -= get_hover_image()
+ UnregisterSignal(C, COMSIG_PARENT_QDELETING)
+
+ if(!length(viewing))
+ hover_image = null
+
+/obj/item/frame2/proc/on_client_delete(datum/source)
+ hide_frame_image(source)
+
+/obj/item/frame2/proc/get_hover_image()
+ if(isnull(hover_image))
+ // todo: big/multi-tile frame support
+ hover_image = image('icons/modules/frames/base.dmi', "arrow")
+ hover_image.loc = src
+ hover_image.filters = list(
+ filter(type = "outline", size = 1, color = "#aaffaa77"),
+ )
+ update_hover_image()
+ return hover_image
+
+/obj/item/frame2/proc/update_hover_image()
+ if(isnull(hover_image))
+ return
+ hover_image.pixel_x = 0
+ hover_image.pixel_y = 0
+ switch(dir)
+ if(NORTH)
+ hover_image.pixel_y = 12
+ if(SOUTH)
+ hover_image.pixel_y = -12
+ if(EAST)
+ hover_image.pixel_x = 12
+ if(WEST)
+ hover_image.pixel_x = -12
+
+/obj/item/frame2/setDir(ndir)
+ . = ..()
+ if(!.)
+ return
+ update_hover_image()
+
+/obj/item/frame2/on_attack_self(datum/event_args/actor/e_args)
+ . = ..()
+ if(.)
+ return
+ if(frame.item_deploy_requires_tool)
+ e_args.chat_feedback(SPAN_WARNING("[src] requires the use of a [frame.item_deploy_tool] to be deployed!"), src)
+ return TRUE
+ var/use_dir = e_args.performer.dir
+ //! shitcode for wallmounts
+ if(frame.wall_frame)
+ use_dir = turn(use_dir, 180)
+ //! end
+ if(!can_deploy(e_args, use_dir, e_args.performer.loc))
+ return TRUE
+ if(frame.item_deploy_time)
+ e_args.visible_feedback(
+ target = src,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = SPAN_WARNING("[e_args.performer] starts to deploy [src]."),
+ audible = SPAN_WARNING("You hear something being assembled."),
+ otherwise_self = SPAN_WARNING("You start to deploy [src]."),
+ )
+ log_construction(e_args, src, "started deploying")
+ if(!do_after(e_args.performer, frame.item_deploy_time, src, mobility_flags = MOBILITY_CAN_USE))
+ return TRUE
+ if(!attempt_deploy(e_args, use_dir = use_dir, use_loc = e_args.performer.loc))
+ return TRUE
+ e_args.visible_feedback(
+ target = src,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = SPAN_WARNING("[e_args.performer] deploys [src]."),
+ audible = SPAN_WARNING("You hear something finish being assembled."),
+ otherwise_self = SPAN_WARNING("You deploy [src]."),
+ )
+ return TRUE
+
+/obj/item/frame2/afterattack(atom/target, mob/user, clickchain_flags, list/params)
+ if(!frame.wall_frame)
+ return ..()
+ if(!user.Adjacent(target))
+ return ..()
+ var/use_dir = get_dir(user, target)
+ var/datum/event_args/actor/e_args = new(user)
+ if(IS_DIAGONAL(use_dir))
+ e_args.chat_feedback(SPAN_WARNING("You must be standing cardinally to [target] to attempt a deployment there!"), src)
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ //! shitcode for wallmounts
+ if(frame.wall_frame)
+ use_dir = turn(use_dir, 180)
+ //! end
+ if(frame.item_deploy_requires_tool)
+ e_args.chat_feedback(SPAN_WARNING("[src] requires the use of a [frame.item_deploy_tool] to be deployed!"), src)
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ if(!can_deploy(e_args, use_dir, e_args.performer.loc))
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ if(frame.item_deploy_time)
+ e_args.visible_feedback(
+ target = src,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = SPAN_WARNING("[e_args.performer] starts to deploy [src]."),
+ audible = SPAN_WARNING("You hear something being assembled."),
+ otherwise_self = SPAN_WARNING("You start to deploy [src]."),
+ )
+ log_construction(e_args, src, "started deploying")
+ if(!do_after(e_args.performer, frame.item_deploy_time, src, mobility_flags = MOBILITY_CAN_USE))
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ if(!attempt_deploy(e_args, use_dir, use_loc = e_args.performer.loc))
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ e_args.visible_feedback(
+ target = src,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = SPAN_WARNING("[e_args.performer] deploys [src]."),
+ audible = SPAN_WARNING("You hear something finish being assembled."),
+ otherwise_self = SPAN_WARNING("You deploy [src]."),
+ )
+ return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
+
+/obj/item/frame2/proc/can_deploy(datum/event_args/actor/e_args, use_dir = src.dir, use_loc = src.loc, silent)
+ if(!isturf(use_loc))
+ if(!silent)
+ e_args?.chat_feedback(SPAN_WARNING("[src] must be on the floor to be deployed!"), src)
+ return FALSE
+ if(!frame.deployment_checks(src, use_loc, use_dir, e_args))
+ return FALSE
+ return TRUE
+
+/obj/item/frame2/proc/attempt_deploy(datum/event_args/actor/e_args, use_dir = src.dir, use_loc = src.loc, silent)
+ if(!can_deploy(e_args, use_dir, use_loc, silent))
+ return FALSE
+ return deploy(e_args, use_dir, use_loc)
+
+/obj/item/frame2/proc/deploy(datum/event_args/actor/e_args, use_dir = src.dir, use_loc = src.loc)
+ if(!isturf(use_loc))
+ CRASH("non turf?")
+ frame.deploy_frame(src, e_args, use_loc, use_dir)
+ log_construction(e_args, src, "deployed")
+
+/obj/item/frame2/context_query(datum/event_args/actor/e_args)
+ . = ..()
+ .["deploy-frame"] = atom_context_tuple("deploy", image(src), 1, MOBILITY_CAN_USE)
+
+/obj/item/frame2/context_act(datum/event_args/actor/e_args, key)
+ . = ..()
+ if(.)
+ return
+ switch(key)
+ if("deploy-frame")
+ if(!e_args.performer.Reachability(src))
+ e_args.chat_feedback(SPAN_WARNING("You can't reach [src] right now!"), src)
+ return TRUE
+ var/use_dir = src.dir
+ //! shitcode for wallmounts
+ if(frame.wall_frame)
+ use_dir = turn(use_dir, 180)
+ //! end
+ if(frame.item_deploy_requires_tool)
+ e_args.chat_feedback(SPAN_WARNING("[src] requires the use of a [frame.item_deploy_tool] to be deployed!"), src)
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ if(!can_deploy(e_args, use_dir, loc))
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ if(frame.item_deploy_time)
+ e_args.visible_feedback(
+ target = src,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = SPAN_WARNING("[e_args.performer] starts to deploy [src]."),
+ audible = SPAN_WARNING("You hear something being assembled."),
+ otherwise_self = SPAN_WARNING("You start to deploy [src]."),
+ )
+ log_construction(e_args, src, "started deploying")
+ if(!do_after(e_args.performer, frame.item_deploy_time, src, mobility_flags = MOBILITY_CAN_USE))
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ attempt_deploy(e_args, use_dir = use_dir)
+ return TRUE
+
+/obj/item/frame2/drop_products(method, atom/where)
+ . = ..()
+ frame.drop_deconstructed_products(method, where, null, list())
+
+/obj/item/frame2/tool_act(obj/item/I, datum/event_args/actor/clickchain/e_args, function, flags, hint)
+ if(function == frame.item_recycle_tool)
+ if(frame.item_recycle_time)
+ e_args.visible_feedback(
+ target = src,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = SPAN_WARNING("[e_args.performer] starts to recycle [src]."),
+ audible = SPAN_WARNING("You hear someone starting to disassemble something."),
+ otherwise_self = SPAN_WARNING("You start to recycle [src]."),
+ )
+ log_construction(e_args, src, "started recycling")
+ if(!use_tool(function, I, e_args, flags, frame.item_recycle_time, frame.item_recycle_cost))
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ e_args.visible_feedback(
+ target = src,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = SPAN_WARNING("[e_args.performer] disassembles [src] back into raw material."),
+ audible = SPAN_WARNING("You hear something being disassembled back into raw material."),
+ otherwise_self = SPAN_WARNING("You recycle [src] back into raw material."),
+ )
+ log_construction(e_args, src, "recycled")
+ deconstruct(ATOM_DECONSTRUCT_DISASSEMBLED)
+ qdel(src)
+ return CLICKCHAIN_DID_SOMETHING | CLICKCHAIN_DO_NOT_PROPAGATE
+ if(function == frame.item_deploy_tool)
+ if(frame.item_deploy_time)
+ e_args.visible_feedback(
+ target = src,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = SPAN_WARNING("[e_args.performer] starts to deploy [src]."),
+ audible = SPAN_WARNING("You hear something being assembled."),
+ otherwise_self = SPAN_WARNING("You start to deploy [src]."),
+ )
+ log_construction(e_args, src, "started deploying")
+ if(!use_tool(function, I, e_args, flags, frame.item_deploy_time, frame.item_deploy_cost))
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ if(!attempt_deploy(e_args))
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ e_args.visible_feedback(
+ target = src,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = SPAN_WARNING("[e_args.performer] deploys [src]."),
+ audible = SPAN_WARNING("You hear something finish being assembled."),
+ otherwise_self = SPAN_WARNING("You deploy [src]."),
+ )
+ return CLICKCHAIN_DID_SOMETHING | CLICKCHAIN_DO_NOT_PROPAGATE
+ return ..()
+
+/obj/item/frame2/dynamic_tool_query(obj/item/I, datum/event_args/actor/clickchain/e_args, list/hint_images)
+ . = list()
+ if(frame.item_recycle_tool)
+ LAZYSET(.[frame.item_recycle_tool], "recycle", dyntool_image_neutral(frame.item_recycle_tool))
+ if(frame.item_deploy_tool)
+ LAZYSET(.[frame.item_deploy_tool], "deploy", dyntool_image_neutral(frame.item_deploy_tool))
+ return merge_double_lazy_assoc_list(., ..())
diff --git a/code/modules/frames/structure.dm b/code/modules/frames/structure.dm
new file mode 100644
index 000000000000..342508026769
--- /dev/null
+++ b/code/modules/frames/structure.dm
@@ -0,0 +1,123 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
+// todo: rename to just 'frame' from 'frame2' after all old frames are converted.
+/obj/structure/frame2
+ name = "construction frame"
+ desc = "if you see this, yell at coders"
+ icon = 'icons/modules/frames/base.dmi'
+ icon_state = "structure"
+
+ climb_knockable = TRUE
+ obj_rotation_flags = OBJ_ROTATION_ENABLED
+
+ /// frame datum; set to typepath to default to that on init
+ var/datum/frame2/frame
+
+ /// current stage
+ var/stage
+ /// current context
+ // todo: frame context system proper?
+ var/list/context
+
+/obj/structure/frame2/Initialize(mapload, dir, datum/frame2/set_frame_to, stage_id, list/context)
+ var/datum/frame2/applying_frame = fetch_frame_datum(set_frame_to || src.frame)
+ src.context = context || list()
+ setDir(dir)
+ if(!length(applying_frame.stages))
+ applying_frame.finish_frame(src)
+ return INITIALIZE_HINT_QDEL
+ src.stage = stage_id || applying_frame.stage_starting || stack_trace("no stage...")
+ src.frame = applying_frame
+ src.frame.apply_to_frame(src)
+ return ..()
+
+/obj/structure/frame2/proc/set_context(key, value)
+ LAZYSET(context, key, value)
+
+/obj/structure/frame2/proc/get_context(key)
+ return context?[key]
+
+/obj/structure/frame2/update_icon_state()
+ icon_state = "structure[frame.has_structure_stage_states? "-[stage]" : ""]"
+ return ..()
+
+/obj/structure/frame2/update_overlays()
+ . = ..()
+ . += frame.get_overlays(src)
+
+/obj/structure/frame2/update_name()
+ var/datum/frame_stage/frame_stage = frame.stages[stage]
+ name = "[frame_stage.name_prepend && "[frame_stage.name_prepend] "][frame_stage.name_override || frame.name][frame_stage.name_append && " [frame_stage.name_append]"]"
+ return ..()
+
+/obj/structure/frame2/drop_products(method, atom/where)
+ . = ..()
+ frame.drop_deconstructed_products(method, where, stage, context)
+
+/obj/structure/frame2/examine(mob/user, dist)
+ . = ..()
+ frame.on_examine(src, new /datum/event_args/actor(user), ., dist)
+
+/obj/structure/frame2/dynamic_tool_query(obj/item/I, datum/event_args/actor/clickchain/e_args)
+ // please don't hurt me lohikar
+ . = list()
+ if(frame.freely_anchorable && frame.anchor_tool)
+ .[frame.anchor_tool] = list(
+ "[anchored? "unanchor" : "anchor"]" = anchored? dyntool_image_backward(frame.anchor_tool) : dyntool_image_forward(frame.anchor_tool),
+ )
+ . = merge_double_lazy_assoc_list(frame.on_tool_query(src, I, e_args), .)
+ . = merge_double_lazy_assoc_list(., ..())
+
+/obj/structure/frame2/proc/still_anchored(anchorvalue)
+ return anchored == anchorvalue
+
+/obj/structure/frame2/tool_act(obj/item/I, datum/event_args/actor/clickchain/e_args, function, flags, hint)
+ if(frame.freely_anchorable && frame.anchor_tool == function)
+ var/datum/frame_stage/current_stage = frame.stages[stage]
+ // if anchored, and either: current stage allow_unanchor is set to FALSE (not null) OR it's to null
+ // and the frame is requiring anchored and the frame's not on its starting stage, do not allow unanchoring.
+ if(anchored && (isnull(current_stage.allow_unanchor)? (frame.requires_anchored? frame.stage_starting != stage : FALSE) : !current_stage.allow_unanchor))
+ e_args.chat_feedback(
+ SPAN_WARNING("[src] cannot be unanchored while in this stage!"),
+ target = src,
+ )
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ if(frame.anchor_time)
+ e_args.visible_feedback(
+ target = src,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = SPAN_NOTICE("[e_args.performer] starts to [anchored? "unbolt" : "bolt"] [src] [anchored? "from" : "to"] the floor."),
+ otherwise_self = SPAN_NOTICE("You begin to [anchored? "unbolt" : "bolt"] [src] [anchored? "from" : "to"] the floor."),
+ )
+ log_construction(e_args, src, "started [anchored? "unanchoring" : "anchoring"]")
+ if(!use_tool(function, I, e_args, flags, frame.anchor_time))
+ return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
+ set_anchored(!anchored)
+ log_construction(e_args, src, "[anchored? "anchored" : "unanchored"]")
+ e_args.visible_feedback(
+ target = src,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = SPAN_NOTICE("[e_args.performer] [anchored? "bolts" : "unbolts"] [src] [anchored? "to" : "from"] the floor."),
+ audible = SPAN_WARNING("You hear a set of bolts being [anchored? "fastened" : "undone"]."),
+ otherwise_self = SPAN_NOTICE("You [anchored? "bolt" : "unbolt"] [src] [anchored? "to" : "from"] the floor."),
+ )
+ return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
+ if(frame.on_tool(src, I, e_args, function, flags, hint))
+ // todo: did something might be sent even if we .. didn't do anything successfully.
+ return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
+ return ..()
+
+/obj/structure/frame2/on_attack_hand(datum/event_args/actor/clickchain/e_args)
+ . = ..()
+ if(.)
+ return
+ if(frame.on_interact(src, e_args))
+ return TRUE
+
+/obj/structure/frame2/attackby(obj/item/I, mob/user, list/params, clickchain_flags, damage_multiplier)
+ if(user.a_intent == INTENT_HARM)
+ return ..()
+ if(frame.on_item(src, I, new /datum/event_args/actor/clickchain(user)))
+ return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
+ return ..()
diff --git a/code/modules/frames/types/apc.dm b/code/modules/frames/types/apc.dm
new file mode 100644
index 000000000000..f5896d4908e7
--- /dev/null
+++ b/code/modules/frames/types/apc.dm
@@ -0,0 +1,50 @@
+AUTO_FRAME_DATUM(/datum/frame2/apc, apc, 'icons/machinery/power/apc.dmi')
+/datum/frame2/apc
+ name = "APC frame"
+ material_cost = 2
+ // we immediately form the entity on place; no stages
+ stages = list()
+ wall_frame = TRUE
+ wall_pixel_x = 24
+ wall_pixel_y = 24
+
+/datum/frame2/apc/instance_product(obj/structure/frame/frame)
+ return new /obj/machinery/power/apc(frame.loc, frame.dir, TRUE)
+
+/datum/frame2/apc/valid_location(obj/entity, turf/location, dir, datum/event_args/actor/actor, silent)
+ if(!istype(location, /turf/simulated))
+ if(!silent)
+ actor.chat_feedback(
+ SPAN_WARNING("[entity] must be placed on normal flooring."),
+ target = entity,
+ )
+ return FALSE
+ var/area/area = location.loc
+ if(!area)
+ if(!silent)
+ actor.chat_feedback(
+ SPAN_WARNING("Missing area. Report this to coders with a screenshot of your screen. How did you get here?"),
+ target = entity,
+ )
+ return FALSE
+ if(!area.requires_power || area.always_unpowered)
+ if(!silent)
+ actor.chat_feedback(
+ SPAN_WARNING("[location] doesn't require power, or is externally powered."),
+ target = entity,
+ )
+ return FALSE
+ if(area.get_apc())
+ if(!silent)
+ actor.chat_feedback(
+ SPAN_WARNING("[location] is part of an area that already has an APC."),
+ target = entity,
+ )
+ return FALSE
+ for(var/obj/machinery/power/terminal/T in location)
+ actor.chat_feedback(
+ SPAN_WARNING("There is another powernet terminal here."),
+ target = entity,
+ )
+ return FALSE
+ return ..()
diff --git a/code/modules/frames/types/fire_alarm.dm b/code/modules/frames/types/fire_alarm.dm
new file mode 100644
index 000000000000..03f5797e8e8b
--- /dev/null
+++ b/code/modules/frames/types/fire_alarm.dm
@@ -0,0 +1,85 @@
+AUTO_FRAME_DATUM(/datum/frame2/fire_alarm, fire_alarm, 'icons/machinery/fire_alarm.dmi')
+/datum/frame2/fire_alarm
+ name = "fire alarm frame"
+ wall_pixel_y = 24
+ wall_pixel_x = 24
+ wall_frame = TRUE
+ material_cost = 2
+ stages = list(
+ "frame" = /datum/frame_stage{
+ steps = list(
+ /datum/frame_step{
+ request = /obj/item/circuitboard/firealarm;
+ name = "insert circuit";
+ stage = "circuit";
+ direction = TOOL_DIRECTION_FORWARDS;
+ },
+ /datum/frame_step{
+ request = TOOL_WRENCH;
+ time = 1 SECONDS;
+ name = "detach frame";
+ stage = FRAME_STAGE_DECONSTRUCT;
+ direction = TOOL_DIRECTION_BACKWARDS;
+ },
+ );
+ descriptor = "is currently an empty shell.";
+ },
+ "circuit" = /datum/frame_stage{
+ steps = list(
+ /datum/frame_step{
+ request = TOOL_SCREWDRIVER;
+ name = "secure circuit";
+ stage = "secured";
+ direction = TOOL_DIRECTION_FORWARDS;
+ },
+ /datum/frame_step{
+ request_type = FRAME_REQUEST_TYPE_INTERACT;
+ name = "remove circuit";
+ stage = "frame";
+ drop = /obj/item/circuitboard/firealarm;
+ direction = TOOL_DIRECTION_BACKWARDS;
+ },
+ );
+ descriptor = "has the circuit installed";
+ },
+ "secured" = /datum/frame_stage{
+ steps = list(
+ /datum/frame_step{
+ request = /obj/item/stack/cable_coil;
+ name = "unsecure circuit";
+ request_amount = 1;
+ stage = "wired";
+ direction = TOOL_DIRECTION_FORWARDS;
+ },
+ /datum/frame_step{
+ request = TOOL_SCREWDRIVER;
+ name = "unsecure circuit";
+ stage = "circuit";
+ direction = TOOL_DIRECTION_FORWARDS;
+ },
+ );
+ descriptor = "has the circuit secured.";
+ },
+ "wired" = /datum/frame_stage{
+ steps = list(
+ /datum/frame_step{
+ request = TOOL_SCREWDRIVER;
+ name = "secure panel";
+ stage = FRAME_STAGE_FINISH;
+ direction = TOOL_DIRECTION_FORWARDS;
+ },
+ /datum/frame_step{
+ request = TOOL_WIRECUTTER;
+ name = "remove wiring";
+ stage = "secured";
+ direction = TOOL_DIRECTION_BACKWARDS;
+ },
+ );
+ descriptor = "has its wiring installed.";
+ name_append = "(wired)";
+ },
+ )
+
+
+/datum/frame2/fire_alarm/instance_product(obj/structure/frame/frame)
+ return new /obj/machinery/fire_alarm(frame.loc, frame.dir)
diff --git a/code/modules/frames/types/solar_panel.dm b/code/modules/frames/types/solar_panel.dm
new file mode 100644
index 000000000000..f3802813a4f8
--- /dev/null
+++ b/code/modules/frames/types/solar_panel.dm
@@ -0,0 +1,69 @@
+AUTO_FRAME_DATUM(/datum/frame2/solar_panel, solar_panel, 'icons/machinery/power/solar/panel.dmi')
+/datum/frame2/solar_panel
+ name = "solar assembly"
+ material_buildable = FALSE
+ has_density = TRUE
+ freely_anchorable = TRUE
+ stages = list(
+ "frame" = /datum/frame_stage{
+ steps = list(
+ /datum/frame_step{
+ name = "finish panel";
+ request = /datum/material/glass;
+ request_amount = 1;
+ direction = TOOL_DIRECTION_FORWARDS;
+ stage = FRAME_STAGE_FINISH;
+ },
+ );
+ },
+ )
+
+ has_structure_stage_states = FALSE
+
+/datum/frame2/solar_panel/on_item(obj/structure/frame2/frame, obj/item/item, datum/event_args/actor/clickchain/click)
+ . = ..()
+ if(.)
+ return
+ if(istype(item, /obj/item/tracker_electronics))
+ if(frame.get_context("tracker"))
+ click.chat_feedback(
+ SPAN_WARNING("[frame] already has tracker electronics installed."),
+ target = frame,
+ )
+ return TRUE
+ if(!click.performer.attempt_consume_item_for_construction(item))
+ return TRUE
+ click.visible_feedback(
+ target = src,
+ range = MESSAGE_RANGE_CONSTRUCTION,
+ visible = SPAN_NOTICE("[click.performer] inserts [item] into [frame].")
+ )
+ // todo: context system proper?
+ frame.set_context("tracker", TRUE)
+ return TRUE
+
+/datum/frame2/solar_panel/instance_product(obj/structure/frame2/frame)
+ // todo: context system proper?
+ if(frame.get_context("tracker"))
+ return new /obj/machinery/power/tracker(frame.loc)
+ else
+ return new /obj/machinery/power/solar(frame.loc)
+
+/datum/frame2/solar_panel/instruction_special(obj/structure/frame2/frame, datum/event_args/actor/clickchain/click)
+ . = ..()
+ // todo: context system proper?
+ if(!frame.get_context("tracker"))
+ . += SPAN_NOTICE("Add tracker electronics to make this a solar tracker assembly.")
+ else
+ . += SPAN_NOTICE("This assembly is wired to be a solar tracker.")
+
+/obj/structure/frame2/solar_panel/anchored
+ anchored = TRUE
+
+/obj/structure/frame2/solar_panel/tracker
+ context = list(
+ "tracker" = TRUE,
+ )
+
+/obj/structure/frame2/solar_panel/tracker/anchored
+ anchored = TRUE
diff --git a/code/modules/holidays/christmas_loadout.dm b/code/modules/holidays/christmas_loadout.dm
index 3abdbace15d2..4837efc51649 100644
--- a/code/modules/holidays/christmas_loadout.dm
+++ b/code/modules/holidays/christmas_loadout.dm
@@ -101,5 +101,5 @@
/datum/loadout_entry/seasonal/halloween/costumes/accessory
name = "Costume Accessories - Cueball Man"
path = /obj/item/clothing/accessory/wcoat
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
*/
diff --git a/code/modules/holidays/halloween_loadout.dm b/code/modules/holidays/halloween_loadout.dm
index 9447b646b597..e195efdda4cd 100644
--- a/code/modules/holidays/halloween_loadout.dm
+++ b/code/modules/holidays/halloween_loadout.dm
@@ -472,7 +472,7 @@
/datum/loadout_entry/seasonal/halloween/costumes/accessory
name = "Costume Accessories - Cueball Man"
path = /obj/item/clothing/accessory/wcoat
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
/datum/loadout_entry/seasonal/halloween/costumes/accessory/broom
name = "Costume Accessories - Witch Broom"
diff --git a/code/modules/hydroponics/seed_datums.dm b/code/modules/hydroponics/seed_datums.dm
index 227313e13da3..f1d8aec35cd8 100644
--- a/code/modules/hydroponics/seed_datums.dm
+++ b/code/modules/hydroponics/seed_datums.dm
@@ -1272,7 +1272,7 @@
seed_name = "grass"
display_name = "grass"
kitchen_tag = "grass"
- mutants = "carpet"
+ mutants = list("carpet")
chems = list("nutriment" = list(1,20))
/datum/seed/grass/New()
diff --git a/code/modules/hydroponics/spreading/spreading_growth.dm b/code/modules/hydroponics/spreading/spreading_growth.dm
index 5a1b38fc86fb..6ca1df00a692 100644
--- a/code/modules/hydroponics/spreading/spreading_growth.dm
+++ b/code/modules/hydroponics/spreading/spreading_growth.dm
@@ -105,7 +105,13 @@
for(var/mob/living/M in neighbor)
if(seed.get_trait(TRAIT_SPREAD) >= 2 && (M.lying || prob(round(seed.get_trait(TRAIT_POTENCY)))))
entangle(M)
-
+ if(is_mature() && seed.get_trait(TRAIT_CARNIVOROUS))
+ for(var/obj/effect/plant/thing in loc)
+ if(thing != src)
+ if(thing.seed != seed)
+ thing.vine_overrun(src,src)
+ else
+ thing.die_off(TRUE) // let's just make sure there's only one
if(is_mature() && neighbors.len && prob(spread_chance))
//spread to 1-3 adjacent turfs depending on yield trait.
var/max_spread = between(1, round(seed.get_trait(TRAIT_YIELD)*3/14), 3)
diff --git a/code/modules/integrated_electronics/core/assemblies.dm b/code/modules/integrated_electronics/core/assemblies.dm
index ce6f8a5c68f5..3f7f21f6d6c3 100644
--- a/code/modules/integrated_electronics/core/assemblies.dm
+++ b/code/modules/integrated_electronics/core/assemblies.dm
@@ -55,7 +55,7 @@
/// Cost set to default during init if unset.
var/cost = 0
-/obj/item/electronic_assembly/GenerateTag()
+/obj/item/electronic_assembly/generate_tag()
tag = "assembly_[next_assembly_id++]"
/obj/item/electronic_assembly/examine(mob/user, dist)
diff --git a/code/modules/jobs/job.dm b/code/modules/jobs/job.dm
index ae99a808d2a8..f7060336faa4 100644
--- a/code/modules/jobs/job.dm
+++ b/code/modules/jobs/job.dm
@@ -23,6 +23,10 @@
/// With minimal access off, this gets added
var/list/additional_access = list()
+ //* Off-Duty
+ /// are we an off duty role?
+ var/is_off_duty = FALSE
+
//? Unsorted
/// Bitflags for the job.
var/flag = NONE
@@ -74,9 +78,6 @@
// Requires a ckey to be whitelisted in jobwhitelist.txt
var/whitelist_only = 0
- // Every hour playing this role gains this much time off. (Can be negative for off duty jobs!)
- var/timeoff_factor = 3
-
// What type of PTO is that job earning?
var/pto_type
@@ -118,8 +119,6 @@
. |= ROLE_UNAVAILABLE_WHITELIST
if(!slots_remaining())
. |= ROLE_UNAVAILABLE_SLOTS_FULL
- if(!player_has_enough_pto(C))
- . |= ROLE_UNAVAILABLE_PTO
if(jobban_isbanned(C.mob, title))
. |= ROLE_UNAVAILABLE_BANNED
if(!player_old_enough(C))
@@ -151,8 +150,6 @@
return ROLE_UNAVAILABLE_WHITELIST
else if(latejoin && !slots_remaining(TRUE))
return ROLE_UNAVAILABLE_SLOTS_FULL
- else if(!player_has_enough_pto(C))
- return ROLE_UNAVAILABLE_PTO
else if(jobban_isbanned(C.mob, title))
return ROLE_UNAVAILABLE_BANNED
else if(!player_old_enough(C))
@@ -431,10 +428,6 @@
if(mannequin.back)
qdel(mannequin.back)
-/// Check client-specific availability rules.
-/datum/role/job/proc/player_has_enough_pto(client/C)
- return timeoff_factor >= 0 || (C && LAZYACCESS(C.department_hours, pto_type) > 0)
-
/datum/role/job/proc/equip_backpack(mob/living/carbon/human/H)
switch(H.backbag)
if(2)
diff --git a/code/modules/jobs/job_types/station/civillian/assistant.dm b/code/modules/jobs/job_types/station/civillian/assistant.dm
index 55e2c25531d7..7537c89fc28d 100644
--- a/code/modules/jobs/job_types/station/civillian/assistant.dm
+++ b/code/modules/jobs/job_types/station/civillian/assistant.dm
@@ -9,7 +9,6 @@
spawn_positions = -1
supervisors = "Nobody! You don't work here."
selection_color = "#515151"
- timeoff_factor = 0
outfit_type = /datum/outfit/job/station/assistant
alt_titles = list(
diff --git a/code/modules/jobs/job_types/station/offduty/_offduty.dm b/code/modules/jobs/job_types/station/offduty/_offduty.dm
index 105212f81f33..638b883713d5 100644
--- a/code/modules/jobs/job_types/station/offduty/_offduty.dm
+++ b/code/modules/jobs/job_types/station/offduty/_offduty.dm
@@ -1,7 +1,7 @@
/datum/role/job/station/off_duty
abstract_type = /datum/role/job/station/off_duty
join_types = JOB_LATEJOIN
- timeoff_factor = -1
total_positions = -1
departments = list(DEPARTMENT_OFFDUTY)
+ is_off_duty = TRUE
supervisors = "nobody! Enjoy your time off"
diff --git a/code/modules/jobs/job_types/station/offduty/command.dm b/code/modules/jobs/job_types/station/offduty/command.dm
index d2da5731b935..fbed3c6df07a 100644
--- a/code/modules/jobs/job_types/station/offduty/command.dm
+++ b/code/modules/jobs/job_types/station/offduty/command.dm
@@ -1,7 +1,6 @@
/datum/role/job/station/off_duty/command
id = JOB_ID_OFFDUTY_COMMAND
title = "Off-duty Command"
- timeoff_factor = -1
total_positions = -1
selection_color = "#9b633e"
minimal_access = list(
diff --git a/code/modules/jobs/job_types/station/science/roboticist.dm b/code/modules/jobs/job_types/station/science/roboticist.dm
index cc625a69f125..9a7e9be4d2e3 100644
--- a/code/modules/jobs/job_types/station/science/roboticist.dm
+++ b/code/modules/jobs/job_types/station/science/roboticist.dm
@@ -27,7 +27,8 @@
alt_titles = list(
"Biomechanical Engineer" = /datum/prototype/struct/alt_title/biomech,
"Mechatronic Engineer" = /datum/prototype/struct/alt_title/mech_tech,
- "Artificer-Biotechnicist" = /datum/prototype/struct/alt_title/artificer_biotechnicist
+ "Artificer-Biotechnicist" = /datum/prototype/struct/alt_title/artificer_biotechnicist,
+ "Junior Roboticist" = /datum/prototype/struct/alt_title/junior_roboticist,
)
/datum/prototype/struct/alt_title/artificer_biotechnicist
@@ -37,6 +38,11 @@
)
background_enforce = TRUE
+/datum/prototype/struct/alt_title/junior_roboticist
+ title = "Junior Roboticist"
+ title_blurb = "A Junior Roboticist is someone still learning the field of robotics and \
+ should seek guidance from other roboticists and the research seniors and lead."
+
/datum/prototype/struct/alt_title/biomech
title = "Biomechanical Engineer"
title_blurb = "A Biomechanical Engineer primarily works on prosthetics, and the organic parts attached to them. They may have some \
diff --git a/code/modules/jobs/job_types/station/supply/shaft_miner.dm b/code/modules/jobs/job_types/station/supply/shaft_miner.dm
index 6dec92615a2f..36d58b582419 100644
--- a/code/modules/jobs/job_types/station/supply/shaft_miner.dm
+++ b/code/modules/jobs/job_types/station/supply/shaft_miner.dm
@@ -27,7 +27,9 @@
alt_titles = list(
"Drill Technician" = /datum/prototype/struct/alt_title/miner/drill_tech,
"Belt Miner" = /datum/prototype/struct/alt_title/miner/belt,
- "Salvage Technician" = /datum/prototype/struct/alt_title/salvage
+ "Salvage Technician" = /datum/prototype/struct/alt_title/salvage,
+ "Apprentice Miner" = /datum/prototype/struct/alt_title/miner/apprenticemine,
+ "Apprentice Salvager" = /datum/prototype/struct/alt_title/miner/apprenticesalv
)
/datum/prototype/struct/alt_title/miner
@@ -40,6 +42,14 @@
/datum/prototype/struct/alt_title/miner/belt
title = "Belt Miner"
+/datum/prototype/struct/alt_title/miner/apprenticemine
+ title = "Apprentice Miner"
+ title_blurb = "An Apprentice Miner is still learning about the typical grind of a miner, and should seek the guidance of other miners and salvagers for direction."
+
+/datum/prototype/struct/alt_title/miner/apprenticesalv
+ title = "Apprentice Salvager"
+ title_blurb = "An Apprentice Salvager is still learning about the typical grind of a salvager, and should seek the guidance of other miners and salvagers for direction."
+
/datum/prototype/struct/alt_title/salvage
title = "Salvage Technician"
title_blurb = "A Salvage Technician specialized in traveling to wrecks and stripping them of useful items and materials."
diff --git a/code/modules/keybindings/keybind/human.dm b/code/modules/keybindings/keybind/human.dm
index 546b33cb4715..4de1ce6a37c3 100644
--- a/code/modules/keybindings/keybind/human.dm
+++ b/code/modules/keybindings/keybind/human.dm
@@ -24,7 +24,7 @@
/datum/keybinding/human/quick_equipbelt/down(client/user)
var/mob/living/carbon/human/H = user.mob
- H.auto_held_insert_or_draw_via_slot(/datum/inventory_slot_meta/inventory/belt)
+ H.auto_held_insert_or_draw_via_slot(/datum/inventory_slot/inventory/belt)
return TRUE
/datum/keybinding/human/bag_equip
@@ -35,5 +35,5 @@
/datum/keybinding/human/bag_equip/down(client/user)
var/mob/living/carbon/human/H = user.mob
- H.auto_held_insert_or_draw_via_slot(/datum/inventory_slot_meta/inventory/back)
+ H.auto_held_insert_or_draw_via_slot(/datum/inventory_slot/inventory/back)
return TRUE
diff --git a/code/modules/keybindings/keybind/mob.dm b/code/modules/keybindings/keybind/mob.dm
index 8472f2d4ef7f..ea741423a499 100644
--- a/code/modules/keybindings/keybind/mob.dm
+++ b/code/modules/keybindings/keybind/mob.dm
@@ -195,7 +195,7 @@
description = "Does a subtle emote that's invisible to ghosts."
/datum/keybinding/mob/subtler/down(client/user)
- user.mob.subtle_wrapper()
+ user.mob.subtler_wrapper()
return TRUE
/datum/keybinding/mob/drop_item
diff --git a/code/modules/library/hardcode_library/reference/xenomorph_facts.dm b/code/modules/library/hardcode_library/reference/xenomorph_facts.dm
new file mode 100644
index 000000000000..4aaa1d638a48
--- /dev/null
+++ b/code/modules/library/hardcode_library/reference/xenomorph_facts.dm
@@ -0,0 +1,117 @@
+/obj/item/book/lore/xenomorph_castes
+ name = "About the life of a xenomorph Part 1"
+ icon_state ="book13"
+ author = "Dr. xbio. Peter Steffans"
+ title = "About the life of a xenomorph Part 1"
+
+
+/obj/item/book/lore/xenomorph_castes/Initialize(mapload)
+ . = ..()
+ dat = {"
+
+
+
+
+
About the life of a xenomorph Part 1, by Dr. xbio. Peter Steffans
+
A simplification of the lifecycle of xenomorphs, written for laymen with a general interest in Xenomorphs.
+
Birth
+
Xenomorph Larvae have two primary ways to come into existance.
+
Facehuggers and Hatching
+
The Xenomorph Queen lays eggs, these eggs mature and from them, a Face hugger can be harvested. Movement around the egg can cause the facehugger to emerge on its own.
+ The Facehugger has a quite descriptive name, as its only purpose in life seems to be to jump onto, and hug a face. It is to note here, that the proportions seem to be mostly adapted for human, and similar creatures. While humanoid species with snouts, or beaks are less easily ensnared by the facehuggers many legs (They vary from 6 to 12), they are not immune to the attack.
+ While latched onto the Face the Facehugger forces a very small larvae into its victim, before dying off, leaving the victim with a unwelcome passenger.
+ The Facehugger itself dies of after leaving the egg for to long, survival durations can vary, between a few minutes up to a couple days (when the specimen was suspended in sugarwater). There have been reports of Facehuggers surviving for a couple hours moving around on their own, before ambushing crew members.
+ Inside the host, the larvae goes on a rampage, consuming interal organs, and other tissues almost indiscriminately, growing rapidly, before eating its way out of the hosts torso, creating a gaping hole leading, which is certainly lethal, if the destroyed internals werent already.
+ Larvae that hatched this way often called red larvae, due to the blood coating them after hatching.
+
'Spawn' pool
+
Decently established hives can create one or more pools of a acidic biomass slurry.
+ These pools are a waste dump for any biomass the hive has no other use for, from hatched corpses to plant matter, depending on the situation they can discard still clothed bodies into the pools, but leftover equipment can then be found in all depths of the pool, and sometimes grown into larvae, which usually leads to an early death of the affected creature.
+ Larvae from a 'spawn' pool are called green larvae(due to the coloration of the pool)
+
Larvae
+
Most of the time larvae just patrole the inner hive, feeding on weeds until they can evolve into a proper xenomorph.
+ There are how ever reports of larvae just rushing at attacking forces(be it marines, colonists or PMC). It is uncertain if such behaviour comes from a lack of connection to the hive in those larvae or a different defect.
+ Unconfirmed reports suggest that larvae might also function as scouts/observers, utilizing their small and flexible form to reach places other xenomorphs can not reach.
+
First evolution step
+
After just a few minutes(sometimes just seconds) the larvae evolves and takes up a role in the hive.
+ There is 4 main types of xenomorphs at this level:
+
Drone
+
Drones serve primary as maintainers of the hive, their build resin structures, look after hosts, remove corpses, feed the spawn pools.
+ They arent particular fast, but comparatively strong and dexterous, making them decent kidnappers.
+ In most cases the follow up queens of a hive. Reports of hybrids and field teams support this, but there are exceptions.
+
Runner
+
Runner are the hives shock troopers, incredible fast, not particular durable, deliver not nessecary lethal, but slowing cuts. They try to flank and haress attackers.
+ They also serve as scouts, and occasionally kidnappers
+
Defender
+
Defenders are bulky, strong, and resilient.
+ They have the thickest chitin of the four.
+ They can take a fair bit of bullets and still break a soldiers arm/leg/rib, not as fast as the others.
+
Sentinel
+
Sentinels are a quick and weak xenomorph, that focuses on defending the hive, rarely seen during xenomorph attacks.
+ Their primary means of attack is a neurotoxin which seems to work almost species independent.
+ They can deliver the toxin either by spit (leading to a small fog cloud on impact, inhilation of that cloud is sufficent!), or sting (usually done by ambush, or to soldiers dragged away by drones/runners)
+
Further evolution steps
+
While the interlectual community is split about names and exact properties of specific further evolution steps, and Xenomorph Hybrids being unable to vocalize names for the different evolution steps, the JSDF has created a visual guide for their Marines to help them know what to expect from a particular xenomorph.
+
Specialists
+
Generally evolving from Drones, these xenomorphs continue to maintain the hive primarily.
+
'Hivelord'
+
Larger and more bulky than drones these xenomorph are capable of creating large amounts of resin in short amounts of time, and reinforcing existing structures to take longer to take down.
+
'Carrier'
+
Alot taller than a drone, but in exchange capable of carrying and sustaining so called facehuggers for extended periods of time, also seen to carry smaller wounded xenomorphs away, or captured soldiers.
+
'Burrower'
+
If Carriers are the hives caseevac then burrowers are the hives Pioneers. They create underground tunnels, sometimes even through bedrock, and use their digging claws to destroy fortifications.
+
Frontliner
+
While Defenders are the common 'infantry' of the hive, these evolutions lean further into different parts of its combat capability.
+
'Warrior'
+
Taking a more bowed forward stance, the Warrior uses its thick skull plate and mandibles to ram and slash its targets, while using the proximity to single out individual soldiers.
+
'Crusher'
+
Utilizing speed, thick metalrich chitin plates, and alot of muscle the crusher tramples soldiers and fortifications alike. Particular old crushers have been seen pushing and sometimes flipping Tanks around.
+
Shock-Trooper
+
Furthering the hit and run, or ambush capabilities of the Runner.
+
'Lurker'
+
Lurker emit a particular enzyme on their chitin that allows there grown weeds to take on quite peculiar optical properties giving the Lurker very good active camoflage.
+
'Wraith'
+
As if almost invisible Xenomorphs werent bad enough, the wraith is capable of short range translocation, and 'phaseshift'(exact physics behind this are unknown). Making the Wraith a not so lethal but very chaotic element on the battlefield.
+
'Ravager'
+
Quite a massive creature primarily wielding two sythe shaped claws that can, will, and have cut through spaceship grade armored bulkheads like their are butter(Cold butter but still far to easily.)
+
Chemical
+
When spitting and injecting Neurotoxin isnt enough to deal with the threat, these Evolutions can be encountered.
+
'Spitter'
+
Giving up the ability to sting, the Spitter focuses on delivering Neurotoxin, or acid through a focused glob of either toxic or corrosive goo.
+
'Defiler'
+
Prefering to sting people that spit at them, the defiler can choose from an array of interesting compounds to deliver through a surprisingly sharp needle. From neurotoxin, to xenomorph larvae.
+
'Boiler'
+
The xenomorph version of a mortar. Delivering clouds of toxic or corrosive gas behind barricades, into chokepoints or where ever the hive wants it.
+
'Praetorian'
+
Praetorians are quite graceful as they walk over the battlefield, usually accompanying the queen. Their tactics are much less so, usually being a spray and pray style, delivering large amounts of either acid or neurotoxin in its direct surroundings, while using the chaos to get some slices with thier claws in.
+
and many more
+
There are about eight to eleven hundred more subcategorizations of xenomorphs, that we have autopsy and sighting reports off, but those mentioned above cover those that are frequent enough. And minor variations in toxin composition or claw length would simply strech this book beyond the limits of my publisher.
+
The Queen
+
At the core of every hive found around the frontier is a Queen. These massive and gigantic variation of xenomorph have some quite unique abilities.
+ As mentioned above, they are capable of laying eggs, containing facehuggers, they are also capable of dominating the xenomorph hivemind, making them the defacto leader of the hive.
+ Their psychic abilities are potent enough that they are capable of installing primal messages into the thought process of other species. This 'psychic whisper' seems to be based on such a basic level that it is a biologic communication, unbound of language or vocabulary, with the receiving brain trying and making the most sense of the whispered message. The message appears inside the recipient's mind in its preferred language.
+ Their size give the queen sufficient surface area and mass to emit shrieks, capable of piercing ear protection and often times pushing soldiers off balance.
+ Overall the queen combines features of all four types of xenomorph, having a spit attack, sharp claws, the ability to build and maintain resin structures, and many more situational capabilities
+
'Shrike'
+
Smaller but not less terrifying than a queen.
+ Shrikes can be seen leading small outgrowths of the hive.
+ Still capable of laying eggs, even though at lower quantity, shrikes are often found leading 'underdeveloped' hives, usually these 'hives' are just a 'scouting party' to assess the local situation.
+ There have been situations where there was just the one hive with a shrike at its head on the planet, but most sightings of a shrike were indicators for a larger hive somewhere else on the planet.
+
Capabilities
+
The shrike serves as a herald for the queen, seen at the frontlines much more often than the queen herself. It utilizes its smaller frame for flinging small groups of soldiers around, before letting the other xenomorphs finish them off.
+ Its name the Shrike has earned for its affinity to impale soldiers on its Tail.
+
'King'
+
Not really an alternate version of the queen, the king is probably the most capable combat variation of the xenomorphs.
+ It seems to have an even more devastating shrieks than the queen, shattering freshly set bones, and toppling even braced soldiers.
+ Its claws rend flesh and bones apart, and it is capable of localized EMP pulses.
+ "}
+
+
+
diff --git a/code/modules/library/lib_items.dm b/code/modules/library/lib_items.dm
index 2928ad4b4c34..28d1d9450f14 100644
--- a/code/modules/library/lib_items.dm
+++ b/code/modules/library/lib_items.dm
@@ -193,6 +193,21 @@
else
icon_state = "legalbook-5"
+/obj/structure/bookcase/lore
+ name = "reviewed Books bookcase"
+
+/obj/structure/bookcase/lore/New()
+ ..()
+ var/list/obj/item/book/lore/types = typesof(/obj/item/book/lore)
+ LAZYREMOVE(types, /obj/item/book/lore)
+ for(var/i = 5; i>= 0; i--)
+ var/t_picked = pick(types)
+ new t_picked(src)
+ LAZYREMOVE(types, t_picked)
+ if(length(types) <= 0)
+ break
+ update_icon()
+
/*
* Barcode Scanner
*/
diff --git a/code/modules/loadout/accessories/collar.dm b/code/modules/loadout/accessories/collar.dm
index 58006bfcf30d..b3edf8d2ad81 100644
--- a/code/modules/loadout/accessories/collar.dm
+++ b/code/modules/loadout/accessories/collar.dm
@@ -1,13 +1,13 @@
/datum/loadout_entry/choker // A colorable choker
name = "Choker"
path = /obj/item/clothing/accessory/choker
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
category = LOADOUT_CATEGORY_ACCESSORIES
/datum/loadout_entry/collar
name = "Collar - Silver"
path = /obj/item/clothing/accessory/collar/silver
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
category = LOADOUT_CATEGORY_ACCESSORIES
/datum/loadout_entry/collar/New()
diff --git a/code/modules/loadout/loadout_accessories.dm b/code/modules/loadout/loadout_accessories.dm
index 67bf6a47bae7..70ad28adb128 100644
--- a/code/modules/loadout/loadout_accessories.dm
+++ b/code/modules/loadout/loadout_accessories.dm
@@ -1,6 +1,6 @@
/datum/loadout_entry/accessory
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
category = LOADOUT_CATEGORY_ACCESSORIES
abstract_type = /datum/loadout_entry/accessory
path = /obj/item/clothing/accessory
@@ -232,12 +232,12 @@
/datum/loadout_entry/accessory/buttonup
name = "Button Up Shirt"
path = /obj/item/clothing/accessory/buttonup
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
/datum/loadout_entry/accessory/buttonup/untucked
name = "Button Up Shirt - Untucked"
path = /obj/item/clothing/accessory/buttonup/untucked
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
/datum/loadout_entry/accessory/antediluvian
name = "Antediluvian Loincloth"
diff --git a/code/modules/loadout/loadout_general.dm b/code/modules/loadout/loadout_general.dm
index 57637c3f035f..184016ad4886 100644
--- a/code/modules/loadout/loadout_general.dm
+++ b/code/modules/loadout/loadout_general.dm
@@ -1,6 +1,7 @@
/datum/loadout_entry/cane
name = "Cane"
path = /obj/item/cane
+ slot = SLOT_ID_HANDS
/datum/loadout_entry/cane/white
name = "Cane - White"
@@ -57,9 +58,13 @@
/datum/loadout_entry/plushie/New()
..()
var/list/plushies = list()
- for(var/plushie in subtypesof(/obj/item/toy/plushie/) - /obj/item/toy/plushie/therapy)
+ for(var/plushie in subtypesof(/obj/item/toy/plushie/) - list(/obj/item/toy/plushie/therapy, /obj/item/toy/plushie/snowflake))
var/obj/item/toy/plushie/plushie_type = plushie
- plushies[initial(plushie_type.name)] = plushie_type
+ var/entry_name = initial(plushie_type.name)
+ var/obj/item/toy/plushie/snowflake/snowflake = plushie
+ if(ispath(snowflake, /obj/item/toy/plushie/snowflake))
+ entry_name = "[entry_name] - ([initial(snowflake.player_name)])"
+ plushies[entry_name] = plushie_type
tweaks += new/datum/loadout_tweak/path(tim_sort(plushies, GLOBAL_PROC_REF(cmp_text_asc)))
/datum/loadout_entry/flask
@@ -131,3 +136,8 @@
var/obj/item/storage/box/fluff/swimsuit/swimsuit_type = swimsuit
swimsuits[initial(swimsuit_type.name)] = swimsuit_type
tweaks += new/datum/loadout_tweak/path(tim_sort(swimsuits, GLOBAL_PROC_REF(cmp_text_asc)))
+
+/datum/loadout_entry/customizable_permit
+ name = "Customizable Permit"
+ description = "A customizable permit you can use for... just about anything! Be sure to customize the name and description. It is meant to represent generic driver's or pilot's licenses, and similar fluff items. It includes an irremovable disclaimer and may be freely confiscated or revoked at the discretion of Security and/or Command if you attempt to abuse it!"
+ path = /obj/item/card_fluff
diff --git a/code/modules/loadout/loadout_role_restricted.dm b/code/modules/loadout/loadout_role_restricted.dm
index 38fb3f28f24c..6f0761b38298 100644
--- a/code/modules/loadout/loadout_role_restricted.dm
+++ b/code/modules/loadout/loadout_role_restricted.dm
@@ -32,7 +32,7 @@
slot = SLOT_ID_SHOES
/datum/loadout_entry/restricted/security/accessory
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
//Medical
/datum/loadout_entry/restricted/medical
@@ -58,7 +58,7 @@
slot = SLOT_ID_SHOES
/datum/loadout_entry/restricted/medical/accessory
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
//Engineering
/datum/loadout_entry/restricted/engineering
@@ -84,7 +84,7 @@
slot = SLOT_ID_SHOES
/datum/loadout_entry/restricted/engineering/accessory
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
//Command
/datum/loadout_entry/restricted/command
@@ -107,7 +107,7 @@
slot = SLOT_ID_SHOES
/datum/loadout_entry/restricted/command/accessory
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
//Science
/datum/loadout_entry/restricted/science
@@ -133,7 +133,7 @@
slot = SLOT_ID_SHOES
/datum/loadout_entry/restricted/science/accessory
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
//Supply
/datum/loadout_entry/restricted/supply
@@ -153,14 +153,14 @@
slot = SLOT_ID_SHOES
/datum/loadout_entry/restricted/supply/accessory
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
//Service
/datum/loadout_entry/restricted/service
allowed_roles = list("Head of Personnel", "Bartender", "Botanist", "Janitor", "Chef", "Librarian", "Chaplain")
/datum/loadout_entry/restricted/service/accessory
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
/datum/loadout_entry/restricted/service/suit
slot = SLOT_ID_SUIT
@@ -184,7 +184,7 @@
slot = SLOT_ID_SHOES
/datum/loadout_entry/restricted/misc/accessory
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
//*Multi-Department Combinations (Aka Multi-Department Drifting)
//Security + Command
diff --git a/code/modules/loadout/loadout_suit.dm b/code/modules/loadout/loadout_suit.dm
index 2803d9e9d0d9..7714e0553cfb 100644
--- a/code/modules/loadout/loadout_suit.dm
+++ b/code/modules/loadout/loadout_suit.dm
@@ -684,3 +684,23 @@
//Signalis Armor Accessories (no armor stats)//
+//Ranger Ponchos
+/datum/loadout_entry/suit/ranger_poncho
+ name = "Ranger poncho selection"
+ description = "A selection of colourful ponchos that match the common gaiter masks."
+ path = /obj/item/clothing/accessory/poncho/roles/ranger
+
+/datum/loadout_entry/suit/ranger_poncho/New()
+ ..()
+ var/list/ranger_ponchos = list(
+ "red ranger poncho" = /obj/item/clothing/accessory/poncho/roles/ranger,
+ "tan ranger poncho" = /obj/item/clothing/accessory/poncho/roles/ranger/tan,
+ "gray ranger poncho" = /obj/item/clothing/accessory/poncho/roles/ranger/gray,
+ "green ranger poncho" = /obj/item/clothing/accessory/poncho/roles/ranger/green,
+ "blue ranger poncho" = /obj/item/clothing/accessory/poncho/roles/ranger/blue,
+ "purple ranger poncho" = /obj/item/clothing/accessory/poncho/roles/ranger/purple,
+ "orange ranger poncho" = /obj/item/clothing/accessory/poncho/roles/ranger/orange,
+ "charcoal ranger poncho" = /obj/item/clothing/accessory/poncho/roles/ranger/charcoal,
+ "white ranger poncho" = /obj/item/clothing/accessory/poncho/roles/ranger/snow
+ )
+ tweaks += new/datum/loadout_tweak/path(tim_sort(ranger_ponchos, GLOBAL_PROC_REF(cmp_text_asc)))
diff --git a/code/modules/loadout/loadout_xeno.dm b/code/modules/loadout/loadout_xeno.dm
index 7772af391ec7..3e961c1bb9d0 100644
--- a/code/modules/loadout/loadout_xeno.dm
+++ b/code/modules/loadout/loadout_xeno.dm
@@ -9,7 +9,7 @@
category = LOADOUT_CATEGORY_XENOWEAR
/datum/loadout_entry/xeno/accessories
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
/datum/loadout_entry/xeno/shoes
slot = SLOT_ID_SHOES
@@ -41,7 +41,7 @@
legacy_species_lock = SPECIES_TAJ
/datum/loadout_entry/xeno/tajaran/accessories
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
/datum/loadout_entry/xeno/tajaran/shoes
slot = SLOT_ID_SHOES
@@ -66,7 +66,7 @@
legacy_species_lock = SPECIES_PROMETHEAN
/datum/loadout_entry/xeno/promethean/accessories
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
/datum/loadout_entry/xeno/promethean/shoes
slot = SLOT_ID_SHOES
@@ -90,7 +90,7 @@
/datum/loadout_entry/xeno/teshari
/datum/loadout_entry/xeno/teshari/accessories
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
/datum/loadout_entry/xeno/teshari/shoes
slot = SLOT_ID_SHOES
@@ -122,7 +122,7 @@
legacy_species_lock = SPECIES_PHORONOID
/datum/loadout_entry/xeno/phoronoid/accessories
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
/datum/loadout_entry/xeno/phoronoid/shoes
slot = SLOT_ID_SHOES
@@ -156,7 +156,7 @@
// legacy_species_lock = SPECIES_SKRELL
/datum/loadout_entry/xeno/skrell/accessories
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
/datum/loadout_entry/xeno/skrell/shoes
slot = SLOT_ID_SHOES
@@ -189,7 +189,7 @@
legacy_species_lock = SPECIES_UNATHI
/datum/loadout_entry/xeno/unathi/accessories
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
/datum/loadout_entry/xeno/unathi/shoes
slot = SLOT_ID_SHOES
@@ -222,7 +222,7 @@
legacy_species_lock = SPECIES_VOX
/datum/loadout_entry/xeno/vox/accessories
- slot = /datum/inventory_slot_meta/abstract/attach_as_accessory
+ slot = /datum/inventory_slot/abstract/attach_as_accessory
/datum/loadout_entry/xeno/vox/shoes
slot = SLOT_ID_SHOES
diff --git a/code/modules/logging/logging.dm b/code/modules/logging/logging.dm
index dd3ca429729d..9e9ea53f135b 100644
--- a/code/modules/logging/logging.dm
+++ b/code/modules/logging/logging.dm
@@ -4,6 +4,7 @@
//! New action logging file. Global helpers for things like attack, construction, say, etc, will go in here. !//
// todo: flesh this file out
+// todo: redo everything again lmao we need structured logging
/**
* Logs a construction action / step
@@ -26,7 +27,8 @@
* todo: log initiator
*/
/proc/log_construction(datum/event_args/actor/e_args, atom/target, message)
- log_game("CONSTRUCTION: [key_name(e_args.performer)] [COORD(e_args.performer)] -> [target] [COORD(target)]: [message]")
+ // todo: better handling
+ log_game("CONSTRUCTION: [key_name(e_args?.performer)] [COORD(e_args?.performer)] -> [target] [COORD(target)]: [message]")
/**
* log click - context menu
diff --git a/code/modules/lore_hardcoded/citizenship.dm b/code/modules/lore_hardcoded/citizenship.dm
index 02204660bcdc..73c0d8bb8562 100644
--- a/code/modules/lore_hardcoded/citizenship.dm
+++ b/code/modules/lore_hardcoded/citizenship.dm
@@ -69,6 +69,13 @@
desc = "Exiles from Unathi Clans. They are unwelcome in Unathi society by and large, and often resort to crime. Those who are not killed \
often flee to the Frontier, where they may find opportunities for a new life."
+/datum/lore/character_background/citizenship/vulpunitedstar
+ name = "Vulpkanin United Star"
+ id = "vulpunitedstar"
+ desc = "The Vulpkanin United Star is the galactic voice of all Vulpkanin from the Vazzend and other claimed systems. It is a relative newcomer \
+ on the galactic stage, having been conceived only a couple of centuries ago. Though it doesn't enjoy as much power or influence as the neighboring \
+ Vikara Combine, the VUS advocates for peace and open borders with most other galactic nations, and invests plenty of resources into the sciences."
+
/datum/lore/character_background/citizenship/custom
name = "Other"
id = "custom"
diff --git a/code/modules/lore_hardcoded/faction.dm b/code/modules/lore_hardcoded/faction.dm
index 930904e076bf..cd8a5420e6cf 100644
--- a/code/modules/lore_hardcoded/faction.dm
+++ b/code/modules/lore_hardcoded/faction.dm
@@ -106,6 +106,7 @@
job_whitelist = list(
JOB_ID_QUARTERMASTER,
JOB_ID_CARGO_TECHNICIAN,
+ JOB_ID_SHAFT_MINER,
JOB_ID_STATION_ENGINEER,
JOB_ID_PARAMEDIC,
JOB_ID_PILOT,
diff --git a/code/modules/lore_hardcoded/origin.dm b/code/modules/lore_hardcoded/origin.dm
index 7840da9eb124..32a54e739197 100644
--- a/code/modules/lore_hardcoded/origin.dm
+++ b/code/modules/lore_hardcoded/origin.dm
@@ -8,6 +8,12 @@
return
return ..()
+/datum/lore/character_background/origin/altam
+ name = "Altam"
+ id = "altam"
+ desc = "Altam, located within the Vazzend system, is a somewhat-chilly planet and the seat of the Vulpkanin United Star. Its population is predominantly Vulpkanin, though migrant races are welcome in most areas, from the chilling polar tundras to the temperate equatorial forests."
+ innate_languages = list(LANGUAGE_ID_VULPKANIN)
+
/datum/lore/character_background/origin/qerrbalak
name = "Qerr'Balak"
id = "qerrbalak"
diff --git a/code/modules/lore_hardcoded/religion.dm b/code/modules/lore_hardcoded/religion.dm
index 74c8186fd6e7..a4d1b1570e03 100644
--- a/code/modules/lore_hardcoded/religion.dm
+++ b/code/modules/lore_hardcoded/religion.dm
@@ -8,6 +8,12 @@
return
return ..()
+/datum/lore/character_background/religion/alverrtcheff
+ name = "Alverrtcheff"
+ id = "alverrtcheff"
+ desc = "Alverrtcheff, or The Grand Founder, is the name of both the deity and the religion associated with it. A type of deism, it was founded by the Vulpkanin under the belief that Alverrtcheff created the universe but does not meddle in it."
+ category = "Misc"
+
/datum/lore/character_background/religion/custom
name = "Other"
id = "custom"
diff --git a/code/modules/mapping/map_helpers/_map_helpers.dm b/code/modules/mapping/map_helpers/_map_helpers.dm
index 90d95e803305..74d339cc77b7 100644
--- a/code/modules/mapping/map_helpers/_map_helpers.dm
+++ b/code/modules/mapping/map_helpers/_map_helpers.dm
@@ -1,4 +1,6 @@
-//Landmarks and other helpers which speed up the mapping process and reduce the number of unique instances/subtypes of items/turf/ect
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
/obj/map_helper
icon = 'icons/mapping/helpers/mapping_helpers.dmi'
icon_state = ""
diff --git a/code/modules/mapping/map_helpers/access_helper/access_helper.dm b/code/modules/mapping/map_helpers/access_helper/access_helper.dm
new file mode 100644
index 000000000000..0e8f452d65e3
--- /dev/null
+++ b/code/modules/mapping/map_helpers/access_helper/access_helper.dm
@@ -0,0 +1,17 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/obj/map_helper/access_helper
+ icon = 'icons/mapping/helpers/access_helpers.dmi'
+
+/obj/map_helper/access_helper/Initialize(mapload)
+ var/detected = detect()
+ if(!isnull(detected))
+ apply(detected)
+ return ..()
+
+/obj/map_helper/access_helper/proc/apply(to_what)
+ return
+
+/obj/map_helper/access_helper/proc/detect()
+ return
diff --git a/code/modules/mapping/map_helpers/access_helper/airlock.dm b/code/modules/mapping/map_helpers/access_helper/airlock.dm
new file mode 100644
index 000000000000..84e00bbd2e24
--- /dev/null
+++ b/code/modules/mapping/map_helpers/access_helper/airlock.dm
@@ -0,0 +1,444 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/obj/map_helper/access_helper/airlock
+
+/obj/map_helper/access_helper/airlock/apply(obj/machinery/door/airlock/to_what)
+ // our lists aren't cached, luckily
+ // if's are just shameless and unnecessary memory optimizing for like 5 bytes each lmao
+ if(to_what.req_access != src.req_access)
+ to_what.req_access = src.req_access
+ if(to_what.req_one_access != req_one_access)
+ to_what.req_one_access = src.req_one_access
+
+/obj/map_helper/access_helper/airlock/detect()
+ return locate(/obj/machinery/door/airlock) in loc
+
+/obj/map_helper/access_helper/airlock/station
+ icon_state = "station"
+
+/obj/map_helper/access_helper/airlock/station/maintenance
+ req_access = list(
+ ACCESS_ENGINEERING_MAINT,
+ )
+ icon_state = "engineering"
+
+/obj/map_helper/access_helper/airlock/station/external_airlock
+ req_access = list(
+ ACCESS_ENGINEERING_AIRLOCK,
+ )
+ icon_state = "engineering"
+
+/**
+ * for pilots
+ */
+/obj/map_helper/access_helper/airlock/station/flight_crew
+ req_one_access = list(
+ ACCESS_GENERAL_PILOT,
+ ACCESS_COMMAND_BRIDGE,
+ )
+
+/**
+ * for external engineering storage usable by other departments
+ */
+/obj/map_helper/access_helper/airlock/station/technical_storage
+ req_access = list(
+ ACCESS_ENGINEERING_TECHSTORAGE,
+ )
+ icon_state = "engineering"
+
+/**
+ * for general EVA / stationkeeping storage
+ */
+/obj/map_helper/access_helper/airlock/station/eva_storage
+ req_one_access = list(
+ ACCESS_COMMAND_EVA,
+ ACCESS_ENGINEERING_AIRLOCK,
+ )
+ icon_state = "command"
+
+/**
+ * 'general station access' for use later in some maps
+ * if we want the map to share room with say, offmaps, but
+ * not let offmaps into foyers
+ */
+/obj/map_helper/access_helper/airlock/station/public
+ req_access = list()
+ icon_state = "station"
+
+/obj/map_helper/access_helper/airlock/station/security
+ icon_state = "security"
+
+/obj/map_helper/access_helper/airlock/station/security/department
+ req_one_access = list(
+ ACCESS_SECURITY_BRIG,
+ ACCESS_SECURITY_EQUIPMENT,
+ ACCESS_SECURITY_MAIN,
+ )
+
+/obj/map_helper/access_helper/airlock/station/security/equipment
+ req_access = list(
+ ACCESS_SECURITY_EQUIPMENT,
+ )
+
+/obj/map_helper/access_helper/airlock/station/security/armory
+ req_access = list(
+ ACCESS_SECURITY_ARMORY,
+ )
+
+/obj/map_helper/access_helper/airlock/station/security/control
+ req_access = list(
+ ACCESS_SECURITY_ARMORY,
+ )
+
+/obj/map_helper/access_helper/airlock/station/security/forensics
+ req_access = list(
+ ACCESS_SECURITY_FORENSICS,
+ )
+
+/obj/map_helper/access_helper/airlock/station/security/brig
+ req_one_access = list(
+ ACCESS_SECURITY_BRIG,
+ )
+
+/obj/map_helper/access_helper/airlock/station/engineering
+ icon_state = "engineering"
+
+/obj/map_helper/access_helper/airlock/station/engineering/department
+ req_one_access = list(
+ ACCESS_ENGINEERING_MAIN,
+ ACCESS_ENGINEERING_ATMOS,
+ ACCESS_ENGINEERING_ENGINE,
+ )
+
+/**
+ * for internal department storage
+ */
+/obj/map_helper/access_helper/airlock/station/engineering/storage
+ req_one_access = list(
+ ACCESS_ENGINEERING_ENGINE,
+ ACCESS_ENGINEERING_ATMOS,
+ )
+
+/obj/map_helper/access_helper/airlock/station/engineering/telecomms
+ req_access = list(
+ ACCESS_ENGINEERING_TELECOMMS,
+ )
+
+/obj/map_helper/access_helper/airlock/station/engineering/construction
+ req_access = list(
+ ACCESS_ENGINEERING_CONSTRUCTION,
+ )
+
+/obj/map_helper/access_helper/airlock/station/engineering/engine
+ req_access = list(
+ ACCESS_ENGINEERING_ENGINE,
+ )
+
+/obj/map_helper/access_helper/airlock/station/engineering/atmospherics
+ req_access = list(
+ ACCESS_ENGINEERING_ATMOS,
+ )
+
+/obj/map_helper/access_helper/airlock/station/medical
+ icon_state = "medical"
+
+/obj/map_helper/access_helper/airlock/station/medical/department
+ req_one_access = list(
+ ACCESS_MEDICAL_MAIN,
+ )
+
+/obj/map_helper/access_helper/airlock/station/medical/chemistry
+ req_access = list(
+ ACCESS_MEDICAL_CHEMISTRY,
+ )
+
+/obj/map_helper/access_helper/airlock/station/medical/virology
+ req_access = list(
+ ACCESS_MEDICAL_VIROLOGY,
+ )
+
+/obj/map_helper/access_helper/airlock/station/medical/surgery
+ req_access = list(
+ ACCESS_MEDICAL_SURGERY,
+ )
+
+/obj/map_helper/access_helper/airlock/station/medical/psychiatry
+ req_access = list(
+ ACCESS_MEDICAL_PSYCH,
+ )
+
+/obj/map_helper/access_helper/airlock/station/medical/equipment
+ req_access = list(
+ ACCESS_MEDICAL_EQUIPMENT,
+ )
+
+/obj/map_helper/access_helper/airlock/station/service
+ icon_state = "service"
+
+/obj/map_helper/access_helper/airlock/station/service/bar
+ req_access = list(
+ ACCESS_GENERAL_BAR,
+ )
+
+/obj/map_helper/access_helper/airlock/station/service/kitchen
+ req_access = list(
+ ACCESS_GENERAL_KITCHEN,
+ )
+
+/obj/map_helper/access_helper/airlock/station/service/library
+ req_access = list(
+ ACCESS_GENERAL_LIBRARY,
+ )
+
+/obj/map_helper/access_helper/airlock/station/service/botany
+ req_access = list(
+ ACCESS_GENERAL_BOTANY,
+ )
+
+/obj/map_helper/access_helper/airlock/station/service/janitor
+ req_access = list(
+ ACCESS_GENERAL_JANITOR,
+ )
+
+/obj/map_helper/access_helper/airlock/station/service/chapel
+ req_access = list(
+ ACCESS_GENERAL_CHAPEL,
+ )
+
+/obj/map_helper/access_helper/airlock/station/service/chapel/cremator
+ req_access = list(
+ ACCESS_GENERAL_CREMATOR,
+ )
+
+/obj/map_helper/access_helper/airlock/station/service/entertainer
+ req_one_access = list(
+ ACCESS_GENERAL_CLOWN,
+ ACCESS_GENERAL_MIME,
+ ACCESS_GENERAL_ENTERTAINMENT,
+ ACCESS_GENERAL_TOMFOOLERY,
+ )
+
+/obj/map_helper/access_helper/airlock/station/supply
+ icon_state = "supply"
+
+/obj/map_helper/access_helper/airlock/station/supply/cargo_bay
+ req_access = list(
+ ACCESS_SUPPLY_BAY,
+ )
+
+/obj/map_helper/access_helper/airlock/station/supply/department
+ req_access = list(
+ ACCESS_SUPPLY_MAIN,
+ )
+
+/**
+ * for all of onstation mining
+ */
+/obj/map_helper/access_helper/airlock/station/supply/mining
+ req_access = list(
+ ACCESS_SUPPLY_MINE,
+ )
+
+/**
+ * for transit to and doors within offstation areas
+ */
+/obj/map_helper/access_helper/airlock/station/supply/mining_outpost
+ req_access = list(
+ ACCESS_SUPPLY_MINE_OUTPOST,
+ )
+
+/obj/map_helper/access_helper/airlock/station/science
+ icon_state = "science"
+
+/obj/map_helper/access_helper/airlock/station/science/department
+ req_access = list(
+ ACCESS_SCIENCE_MAIN,
+ )
+
+/obj/map_helper/access_helper/airlock/station/science/fabrication
+ req_access = list(
+ ACCESS_SCIENCE_FABRICATION,
+ )
+
+/**
+ * so, what toxins is becoming.
+ *
+ * slated for potential engi-sci cluster.
+ */
+/obj/map_helper/access_helper/airlock/station/science/material_science
+ req_one_access = list(
+ ACCESS_SCIENCE_TOXINS,
+ )
+
+/**
+ * slated for potential med-sci cluster.
+ */
+/obj/map_helper/access_helper/airlock/station/science/xenobiology
+ req_access = list(
+ ACCESS_SCIENCE_XENOBIO,
+ )
+
+/**
+ * slated for potential med-sci cluster.
+ */
+/obj/map_helper/access_helper/airlock/station/science/xenobotany
+ req_access = list(
+ ACCESS_SCIENCE_XENOBOTANY,
+ )
+
+/obj/map_helper/access_helper/airlock/station/science/xenoarcheology
+ req_access = list(
+ ACCESS_SCIENCE_XENOARCH,
+ )
+
+/**
+ * slated for potential med-sci cluster.
+ */
+/obj/map_helper/access_helper/airlock/station/science/genetics
+ req_one_access = list(
+ ACCESS_SCIENCE_XENOBOTANY,
+ ACCESS_SCIENCE_XENOBIO,
+ )
+
+/**
+ * slated for potential engi-sci cluster.
+ *
+ * use for mech lab specifically
+ */
+/obj/map_helper/access_helper/airlock/station/science/mechatronics
+ req_access = list(
+ ACCESS_SCIENCE_ROBOTICS,
+ )
+
+/**
+ * use for unified robotics labs
+ * use for prosthetics/augments/robots robotics labs
+ */
+/obj/map_helper/access_helper/airlock/station/science/robotics
+ req_access = list(
+ ACCESS_SCIENCE_ROBOTICS,
+ )
+
+/obj/map_helper/access_helper/airlock/station/exploration
+ icon_state = "science"
+
+/obj/map_helper/access_helper/airlock/station/exploration/explorer
+ req_access = list(
+ ACCESS_GENERAL_EXPLORER,
+ )
+
+/**
+ * 'aux' exploration areas like the SAR bay, shuttle bay, etc
+ */
+/obj/map_helper/access_helper/airlock/station/exploration/auxillery
+ req_one_access = list(
+ ACCESS_GENERAL_EXPLORER,
+ )
+
+/obj/map_helper/access_helper/airlock/station/exploration/shuttle
+ req_one_access = list(
+ ACCESS_GENERAL_PILOT,
+ ACCESS_GENERAL_EXPLORER,
+ )
+
+/obj/map_helper/access_helper/airlock/station/command
+ icon_state = "command"
+
+/obj/map_helper/access_helper/airlock/station/command/bridge
+ req_access = list(
+ ACCESS_COMMAND_BRIDGE,
+ )
+
+/obj/map_helper/access_helper/airlock/station/command/ai_upload
+ req_access = list(
+ ACCESS_COMMAND_UPLOAD,
+ )
+
+/obj/map_helper/access_helper/airlock/station/command/ai_core
+ req_access = list(
+ ACCESS_COMMAND_UPLOAD,
+ )
+
+/obj/map_helper/access_helper/airlock/station/command/teleporter
+ req_access = list(
+ ACCESS_COMMAND_TELEPORTER,
+ )
+
+/obj/map_helper/access_helper/airlock/station/command/storage
+ req_access = list(
+ ACCESS_COMMAND_EVA,
+ )
+
+/obj/map_helper/access_helper/airlock/station/command/vault
+ req_access = list(
+ ACCESS_COMMAND_VAULT,
+ )
+
+/obj/map_helper/access_helper/airlock/station/centcom
+ icon_state = "centcom"
+
+/**
+ * special offices are under this
+ *
+ * for future readers: this doesn't mean that pathfinder/quartermaster/similar are official heads of staff
+ *
+ * i'm just putting this here as an extra 'fuck you' for whoever tries to argue server politics using code pathing.
+ */
+/obj/map_helper/access_helper/airlock/station/head_office
+ icon_state = "command"
+
+/obj/map_helper/access_helper/airlock/station/head_office/head_of_personnel
+ req_access = list(
+ ACCESS_COMMAND_HOP,
+ )
+
+/obj/map_helper/access_helper/airlock/station/head_office/captain
+ req_access = list(
+ ACCESS_COMMAND_CAPTAIN,
+ )
+
+/obj/map_helper/access_helper/airlock/station/head_office/blueshield
+ req_access = list(
+ ACCESS_COMMAND_BLUESHIELD,
+ )
+
+/obj/map_helper/access_helper/airlock/station/head_office/internal_affairs
+ req_access = list(
+ ACCESS_COMMAND_IAA,
+ )
+
+/obj/map_helper/access_helper/airlock/station/head_office/head_of_security
+ req_access = list(
+ ACCESS_SECURITY_HOS,
+ )
+
+/obj/map_helper/access_helper/airlock/station/head_office/research_director
+ req_access = list(
+ ACCESS_SCIENCE_RD,
+ )
+
+/obj/map_helper/access_helper/airlock/station/head_office/chief_engineer
+ req_access = list(
+ ACCESS_ENGINEERING_CE,
+ )
+
+/obj/map_helper/access_helper/airlock/station/head_office/quartermaster
+ req_access = list(
+ ACCESS_SUPPLY_QM,
+ )
+
+/obj/map_helper/access_helper/airlock/station/head_office/chief_medical_officer
+ req_access = list(
+ ACCESS_MEDICAL_CMO,
+ )
+
+/obj/map_helper/access_helper/airlock/station/head_office/pathfinder
+ req_access = list(
+ ACCESS_GENERAL_PATHFINDER,
+ )
+
+/obj/map_helper/access_helper/airlock/offmap
+ icon_state = "offmap"
+
+
diff --git a/code/modules/mapping/minimaps.dm b/code/modules/mapping/minimaps.dm
index 43efab7aa5e9..298e304390f5 100644
--- a/code/modules/mapping/minimaps.dm
+++ b/code/modules/mapping/minimaps.dm
@@ -73,15 +73,19 @@
overlay_icon = new(map_icon)
overlay_icon.Scale(16, 16)
//we're done baking, now we ship it.
- if (!SSassets.cache["minimap-[id].png"])
- SSassets.transport.register_asset("minimap-[id].png", map_icon)
- if (!SSassets.cache["minimap-[id]-meta.png"])
- SSassets.transport.register_asset("minimap-[id]-meta.png", meta_icon)
+ var/datum/asset_pack/simple/packed = new /datum/asset_pack/simple(
+ "minimap-[id]",
+ list(
+ "minimap-[id].png" = map_icon,
+ "minimap-[id]-meta.png" = meta_icon,
+ ),
+ )
+ SSassets.register_asset_pack(packed)
/datum/minimap/proc/send(mob/user)
if(!id)
CRASH("ERROR: send called, but the minimap id is null/missing. ID: [id]")
- SSassets.transport.send_assets(user, list("minimap-[id].png" = map_icon, "minimap-[id]-meta.png" = meta_icon))
+ SSassets.send_asset_pack(user, "minimap-[id]")
/datum/minimap_group
var/list/minimaps = list()
@@ -105,14 +109,15 @@
for(var/i in 1 to length(minimaps))// OLD: for(var/i in 1 to length(minimaps))
var/datum/minimap/M = minimaps[i]
+ var/datum/asset_pack/pack = SSassets.ready_asset_pack("minimap-[M.id]")
var/map_name = "minimap-[M.id].png"
var/meta_name = "minimap-[M.id]-meta.png"
M.send(user)
info += {"
-
-
+
+
diff --git a/code/modules/maps/overmap/planets/_lythios43c.dm b/code/modules/maps/overmap/planets/_lythios43c.dm
index 96caffd475fb..571be8e583ea 100644
--- a/code/modules/maps/overmap/planets/_lythios43c.dm
+++ b/code/modules/maps/overmap/planets/_lythios43c.dm
@@ -23,6 +23,7 @@
"Courser Scouting Vessel" = list("rift_courser_hangar"),
"Hammerhead Patrol Barge" = list("rift_hammerhead_hangar"),
"Civilian Transport" = list("rift_civvie_pad"),
+ "Civilian Century Shuttle" = list("rift_oldcentury_pad"),
"Dart EMT Shuttle" = list("rift_emt_pad"),
"Beruang Trade Ship" = list("rift_trade_dock"),
"NDV Quicksilver" = list("rift_specops_dock"),
diff --git a/code/modules/maps/overmap/planets/_virgo3b.dm b/code/modules/maps/overmap/planets/_virgo3b.dm
index 543ccd7b8f52..11fa22c6c5b3 100644
--- a/code/modules/maps/overmap/planets/_virgo3b.dm
+++ b/code/modules/maps/overmap/planets/_virgo3b.dm
@@ -1,8 +1,8 @@
/datum/atmosphere/planet/virgo3b
base = list(
- /datum/gas/nitrogen = 0.16,
- /datum/gas/phoron = 0.72,
+ /datum/gas/nitrogen = 0.36,
+ /datum/gas/phoron = 0.52,
/datum/gas/carbon_dioxide = 0.12,
)
pressure_low = 82.4
diff --git a/code/modules/maps/overmap/space/trade_station/trade_station.dm b/code/modules/maps/overmap/space/trade_station/trade_station.dm
index 789889039e68..fa2eacd230a7 100644
--- a/code/modules/maps/overmap/space/trade_station/trade_station.dm
+++ b/code/modules/maps/overmap/space/trade_station/trade_station.dm
@@ -9,22 +9,29 @@
color = "#8F6E4C"
initial_generic_waypoints = list(
- "nebula_pad_1",
- "nebula_pad_2",
- "nebula_space_SW",
- "nebula_pad_3",
+ "nebula_pad_1a",
+ "nebula_pad_1b",
+ "nebula_pad_2a",
+ "nebula_pad_2b",
+ "nebula_pad_3a",
+ "nebula_pad_3b",
+ "nebula_pad_3c",
+ "nebula_pad_3d",
"nebula_pad_4a",
"nebula_pad_4b",
"nebula_pad_4c",
- "nebula_pad_5",
- "nebula_pad_6",
+ "nebula_pad_4d",
+ "nebula_pad_5a",
+ "nebula_pad_5b",
+ "nebula_pad_6a",
+ "nebula_pad_6b",
"nebula_space_SE",
"nebula_space_S",
"nebula_space_SW"
)
initial_restricted_waypoints = list(
- "Beruang Trade Ship" = list("tradeport_hangar"), "Beluga Passenger Liner" = list("nebula_pad_3")
+ "Beruang Trade Ship" = list("tradeport_hangar")
)
/* // Old Restricted list. Leaving commented out for reference - Bloop
initial_restricted_waypoints = list(
diff --git a/code/modules/maps/templates/misc_presets/rift_shuttle_landmarks.dm b/code/modules/maps/templates/misc_presets/rift_shuttle_landmarks.dm
index 774b2cfea978..2507b0199ec5 100644
--- a/code/modules/maps/templates/misc_presets/rift_shuttle_landmarks.dm
+++ b/code/modules/maps/templates/misc_presets/rift_shuttle_landmarks.dm
@@ -25,6 +25,13 @@ Need to turn all of these into proper initializers like this:
base_turf = /turf/simulated/floor/reinforced/lythios43c
base_area = /area/rift/surfacebase/outside/outside3
+/obj/effect/shuttle_landmark/rift/deck3/oldcentury
+ name = "NSB Atlas - Secondary Civilian Transport Pad"
+ landmark_tag = "rift_oldcentury_pad"
+ docking_controller = "rift_oldcentury_dock"
+ base_turf = /turf/simulated/floor/reinforced/lythios43c
+ base_area = /area/rift/surfacebase/outside/outside3
+
/obj/effect/shuttle_landmark/rift/deck3/emt
name = "NSB Atlas - EMT Shuttle Pad"
landmark_tag = "rift_emt_pad"
@@ -129,6 +136,10 @@ Need to turn all of these into proper initializers like this:
name = "In transit"
landmark_tag = "nav_transit_civvie"
+/obj/effect/shuttle_landmark/transit/rift/oldcentury
+ name = "In transit"
+ landmark_tag = "nav_transit_oldcentury"
+
/obj/effect/shuttle_landmark/transit/rift/trade
name = "In transit"
landmark_tag = "nav_transit_trade"
diff --git a/code/modules/materials/definitions/metals/steel.dm b/code/modules/materials/definitions/metals/steel.dm
index e433859ba4f4..ce337ac483f0 100644
--- a/code/modules/materials/definitions/metals/steel.dm
+++ b/code/modules/materials/definitions/metals/steel.dm
@@ -100,6 +100,18 @@
cost = 5,
time = 2 SECONDS,
)
+ for(var/datum/frame2/frame_datum as anything in GLOB.frame_datum_lookup)
+ if(!frame_datum.material_buildable)
+ continue
+ . += create_stack_recipe_datum(
+ category = "frames",
+ cost = frame_datum.material_cost,
+ name = frame_datum.name,
+ recipe_type = /datum/stack_recipe/frame,
+ recipe_args = list(
+ frame_datum.type,
+ ),
+ )
. += new /datum/stack_recipe/railing
. += create_stack_recipe_datum(category = "sofas", cost = 1, name = "sofa middle", product = /obj/structure/bed/chair/sofa, exclusitivity = /obj/structure/bed)
. += create_stack_recipe_datum(category = "sofas", cost = 1, name = "sofa left", product = /obj/structure/bed/chair/sofa/left, exclusitivity = /obj/structure/bed)
@@ -113,13 +125,6 @@
cost = 2,
)
// todo: frame rework
- . += create_stack_recipe_datum(
- category = "frames",
- name = "apc frame",
- product = /obj/item/frame/apc,
- cost = 2,
- )
- // todo: frame rework
. += create_stack_recipe_datum(
category = "frames",
name = "mirror frame",
diff --git a/code/modules/mining/machinery/sheet_silo.dm b/code/modules/mining/machinery/sheet_silo.dm
index a8e964ac6390..2fa0a3fd5cd9 100644
--- a/code/modules/mining/machinery/sheet_silo.dm
+++ b/code/modules/mining/machinery/sheet_silo.dm
@@ -80,9 +80,9 @@
. = ..()
sheets_by_material = data["stored"]
-/obj/machinery/sheet_silo/ui_assets(mob/user)
- . = ..()
- . += get_asset_datum(/datum/asset/spritesheet/materials)
+/obj/machinery/sheet_silo/ui_asset_injection(datum/tgui/ui, list/immediate, list/deferred)
+ immediate += /datum/asset_pack/spritesheet/materials
+ return ..()
/obj/machinery/sheet_silo/ui_data(mob/user, datum/tgui/ui)
. = ..()
diff --git a/code/modules/mining/ore_redemption_machine/equipment_vendor.dm b/code/modules/mining/ore_redemption_machine/equipment_vendor.dm
index f926dd84dab4..2b765fa5120a 100644
--- a/code/modules/mining/ore_redemption_machine/equipment_vendor.dm
+++ b/code/modules/mining/ore_redemption_machine/equipment_vendor.dm
@@ -17,9 +17,12 @@
new /datum/data/mining_equipment("30 Marker Beacons", /obj/item/stack/marker_beacon/thirty, 300),
new /datum/data/mining_equipment("Whiskey", /obj/item/reagent_containers/food/drinks/bottle/whiskey, 125),
new /datum/data/mining_equipment("Absinthe", /obj/item/reagent_containers/food/drinks/bottle/absinthe, 125),
+ new /datum/data/mining_equipment("Hard Root Beer", /obj/item/reagent_containers/food/drinks/bottle/small/alcsassafras, 70),
new /datum/data/mining_equipment("Special Blend Whiskey", /obj/item/reagent_containers/food/drinks/bottle/specialwhiskey, 250),
new /datum/data/mining_equipment("Random Booze", /obj/random/alcohol, 125),
new /datum/data/mining_equipment("Cigar", /obj/item/clothing/mask/smokable/cigarette/cigar/havana, 150),
+ new /datum/data/mining_equipment("Root Beer", /obj/item/reagent_containers/food/drinks/bottle/small/sassafras, 50),
+ new /datum/data/mining_equipment("Sarsaparilla", /obj/item/reagent_containers/food/drinks/bottle/small/sarsaparilla, 50),
new /datum/data/mining_equipment("Soap", /obj/item/soap/nanotrasen, 200),
new /datum/data/mining_equipment("Laser Pointer", /obj/item/laser_pointer, 900),
new /datum/data/mining_equipment("Geiger Counter", /obj/item/geiger_counter, 750),
diff --git a/code/modules/mob/_modifiers/modifiers.dm b/code/modules/mob/_modifiers/modifiers.dm
index f7f3db4dc91b..5a6ff0d75b82 100644
--- a/code/modules/mob/_modifiers/modifiers.dm
+++ b/code/modules/mob/_modifiers/modifiers.dm
@@ -40,8 +40,8 @@
/// If set, the client will have wires replaced by the given replacement list. For colorblindness.
var/wire_colors_replace = null
-//! Now for all the different effects.
-//! Percentage modifiers are expressed as a multipler. (e.g. +25% damage should be written as 1.25)
+ //* Now for all the different effects.
+ //* Percentage modifiers are expressed as a multipler. (e.g. +25% damage should be written as 1.25)
/// Adjusts max health by a flat (e.g. +20) amount. Note this is added to base health.
var/max_health_flat
diff --git a/code/modules/mob/gender.dm b/code/modules/mob/gender.dm
index 10d119bba55c..2bf07e8d9917 100644
--- a/code/modules/mob/gender.dm
+++ b/code/modules/mob/gender.dm
@@ -30,6 +30,7 @@ GLOBAL_LIST_INIT(gender_select_list, gender_selection())
var/himself = "themselves"
var/s = ""
var/hes = "they're"
+ var/Hes = "They're"
/datum/gender/male
key = "male"
@@ -47,6 +48,7 @@ GLOBAL_LIST_INIT(gender_select_list, gender_selection())
himself = "himself"
s = "s"
hes = "he's"
+ Hes = "He's"
/datum/gender/female
key = "female"
@@ -64,6 +66,7 @@ GLOBAL_LIST_INIT(gender_select_list, gender_selection())
himself = "herself"
s = "s"
hes = "she's"
+ Hes = "She's"
/datum/gender/neuter
key = "neuter"
@@ -81,6 +84,7 @@ GLOBAL_LIST_INIT(gender_select_list, gender_selection())
himself = "itself"
s = "s"
hes = "it's"
+ Hes = "It's"
/datum/gender/herm
key = "herm"
@@ -98,6 +102,7 @@ GLOBAL_LIST_INIT(gender_select_list, gender_selection())
himself = "hirself"
s = "s"
hes = "shi's"
+ Hes = "Shi's"
/mob/proc/p_they()
var/datum/gender/G = GLOB.gender_datums[gender]
@@ -122,3 +127,11 @@ GLOBAL_LIST_INIT(gender_select_list, gender_selection())
/mob/proc/p_Their()
var/datum/gender/G = GLOB.gender_datums[gender]
return G.His
+
+/mob/proc/p_theyre()
+ var/datum/gender/G = GLOB.gender_datums[gender]
+ return G.hes
+
+/mob/proc/p_Theyre()
+ var/datum/gender/G = GLOB.gender_datums[gender]
+ return G.Hes
diff --git a/code/modules/mob/holder.dm b/code/modules/mob/holder.dm
index 546223655788..13d7a7147657 100644
--- a/code/modules/mob/holder.dm
+++ b/code/modules/mob/holder.dm
@@ -249,7 +249,7 @@
holder_slot_icons[cache_entry] = holder_mob_icon_cache[cache_key]
return ..()
-/obj/item/holder/human/resolve_worn_assets(mob/M, datum/inventory_slot_meta/slot_meta, inhands, bodytype)
+/obj/item/holder/human/resolve_worn_assets(mob/M, datum/inventory_slot/slot_meta, inhands, bodytype)
var/list/generated = list()
generated.len = WORN_DATA_LIST_SIZE
generated[WORN_DATA_ICON] = holder_slot_icons[slot_meta.id]
diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm
index 73d6ca3cf182..de100a4abc1d 100644
--- a/code/modules/mob/inventory.dm
+++ b/code/modules/mob/inventory.dm
@@ -21,7 +21,7 @@
/mob/proc/smart_equip(obj/item/I)
if(equip_to_appropriate_slot(I, INV_OP_SUPPRESS_WARNING))
return TRUE
- if(equip_to_slot_if_possible(I, /datum/inventory_slot_meta/abstract/put_in_storage_try_active, INV_OP_SUPPRESS_WARNING))
+ if(equip_to_slot_if_possible(I, /datum/inventory_slot/abstract/put_in_storage_try_active, INV_OP_SUPPRESS_WARNING))
return TRUE
to_chat(src, SPAN_WARNING("You have nowhere to put [I]!"))
return FALSE
diff --git a/code/modules/mob/inventory/helpers.dm b/code/modules/mob/inventory/helpers.dm
index ef843afeac39..64bd6e13dd10 100644
--- a/code/modules/mob/inventory/helpers.dm
+++ b/code/modules/mob/inventory/helpers.dm
@@ -100,12 +100,12 @@
/**
* Attempt to shove an item being held into a storage in a given slot
*/
-/mob/proc/attempt_put_held_item_into_storage_in_slot(obj/item/inserting, datum/inventory_slot_meta/slot_like, silent, mob/initiator = src)
+/mob/proc/attempt_put_held_item_into_storage_in_slot(obj/item/inserting, datum/inventory_slot/slot_like, silent, mob/initiator = src)
if(isnull(inserting))
inserting = get_active_held_item()
if(!is_holding(inserting))
return
- slot_like = resolve_inventory_slot_meta(slot_like)
+ slot_like = resolve_inventory_slot(slot_like)
var/obj/item/equipped = item_by_slot_id(slot_like.id)
if(isnull(equipped))
if(!silent)
@@ -122,8 +122,8 @@
/**
* Attempt to grab an item from a given slot into hand.
*/
-/mob/proc/attempt_grab_item_out_of_storage_in_slot(datum/inventory_slot_meta/slot_like, silent, mob/initiator = src)
- slot_like = resolve_inventory_slot_meta(slot_like)
+/mob/proc/attempt_grab_item_out_of_storage_in_slot(datum/inventory_slot/slot_like, silent, mob/initiator = src)
+ slot_like = resolve_inventory_slot(slot_like)
var/obj/item/equipped = item_by_slot_id(slot_like.id)
if(isnull(equipped))
if(!silent)
@@ -159,8 +159,8 @@
* Automatically either put in hand object into a storage in a given slot, or
* draw an item from that storage into hand.
*/
-/mob/proc/auto_held_insert_or_draw_via_slot(datum/inventory_slot_meta/slot_like, silent, mob/initiator = src)
- slot_like = resolve_inventory_slot_meta(slot_like)
+/mob/proc/auto_held_insert_or_draw_via_slot(datum/inventory_slot/slot_like, silent, mob/initiator = src)
+ slot_like = resolve_inventory_slot(slot_like)
var/obj/item/holding = get_active_held_item()
var/obj/item/in_slot = item_by_slot_id(slot_like.id)
if(isnull(in_slot) || isnull(in_slot.obj_storage))
diff --git a/code/modules/mob/inventory/inventory.dm b/code/modules/mob/inventory/inventory.dm
index 25e905dca3da..db10b451347d 100644
--- a/code/modules/mob/inventory/inventory.dm
+++ b/code/modules/mob/inventory/inventory.dm
@@ -2,13 +2,18 @@
* mob inventory data goes in here.
*/
/datum/inventory
- //? basics
+ //* Basics *//
/// owning mob
var/mob/owner
- //? slots
+ //* Inventory *//
- //? caches
+ //* Caches *//
+ /// cached overlays by slot id
+ var/list/rendered_normal_overlays = list()
+ /// cached overlays by slot id
+ // todo: emissives
+ // var/list/rendered_emissive_overlays = list()
/datum/inventory/New(mob/M)
if(!istype(M))
@@ -19,6 +24,95 @@
owner = null
return ..()
+//* Rendering *//
+
+/datum/inventory/proc/remove_slot_renders()
+ var/list/transformed = list()
+ for(var/slot_id in rendered_normal_overlays)
+ transformed += rendered_normal_overlays[slot_id]
+ owner.cut_overlay(transformed)
+
+/datum/inventory/proc/reapply_slot_renders()
+ // try not to dupe
+ remove_slot_renders()
+ var/list/transformed = list()
+ for(var/slot_id in rendered_normal_overlays)
+ transformed += rendered_normal_overlays[slot_id]
+ owner.add_overlay(transformed)
+
+/**
+ * just update if a slot is visible
+ */
+/datum/inventory/proc/update_slot_visible(slot_id, cascade = TRUE)
+ // resolve item
+ var/obj/item/target = owner.item_by_slot_id(slot_id)
+
+ // first, cascade incase we early-abort later
+ if(cascade)
+ var/datum/inventory_slot/slot = resolve_inventory_slot(slot_id)
+ slot.cascade_render_visibility(owner, target)
+
+ // check existing
+ if(isnull(rendered_normal_overlays[slot_id]))
+ return
+
+ // remove overlay first incase it's already there
+ owner.cut_overlay(rendered_normal_overlays[slot_id])
+
+ // check if slot should render
+ var/datum/inventory_slot/slot = resolve_inventory_slot(slot_id)
+ if(!slot.should_render(owner, target))
+ return
+
+ // add overlay if it should
+ owner.add_overlay(rendered_normal_overlays[slot_id])
+
+/**
+ * redo a slot's render
+ */
+/datum/inventory/proc/update_slot_render(slot_id, cascade = TRUE)
+ var/datum/inventory_slot/slot = resolve_inventory_slot(slot_id)
+ var/obj/item/target = owner.item_by_slot_id(slot_id)
+
+ // first, cascade incase we early-abort later
+ if(cascade)
+ slot.cascade_render_visibility(owner, target)
+
+ if(!slot.should_render(owner, target))
+ remove_slot_render(slot_id)
+ return
+
+ if(isnull(target))
+ remove_slot_render(slot_id)
+ return
+
+ var/bodytype = BODYTYPE_DEFAULT
+
+ if(ishuman(owner))
+ var/mob/living/carbon/human/casted_human = owner
+ bodytype = casted_human.species.get_effective_bodytype(casted_human, target, slot_id)
+
+ var/rendering_results = slot.render(owner, target, bodytype)
+ if(islist(rendering_results)? !length(rendering_results) : isnull(rendering_results))
+ remove_slot_render(slot_id)
+ return
+
+ set_slot_render(slot_id, rendering_results)
+
+/datum/inventory/proc/remove_slot_render(slot_id)
+ if(isnull(rendered_normal_overlays[slot_id]))
+ return
+ owner.cut_overlay(rendered_normal_overlays[slot_id])
+ rendered_normal_overlays -= slot_id
+
+/datum/inventory/proc/set_slot_render(slot_id, overlay)
+ if(!isnull(rendered_normal_overlays[slot_id]))
+ owner.cut_overlay(rendered_normal_overlays[slot_id])
+ rendered_normal_overlays[slot_id] = overlay
+ owner.add_overlay(overlay)
+
+//* Queries *//
+
/**
* returns list() of items with body_cover_flags
*/
@@ -39,32 +133,32 @@
* @return true/false based on if it worked
*/
/mob/proc/handle_abstract_slot_insertion(obj/item/I, slot, flags)
- if(!ispath(slot, /datum/inventory_slot_meta/abstract))
- slot = resolve_inventory_slot_meta(slot)?.type
- if(!ispath(slot, /datum/inventory_slot_meta/abstract))
+ if(!ispath(slot, /datum/inventory_slot/abstract))
+ slot = resolve_inventory_slot(slot)?.type
+ if(!ispath(slot, /datum/inventory_slot/abstract))
stack_trace("invalid slot: [slot]")
- else if(slot != /datum/inventory_slot_meta/abstract/put_in_hands)
+ else if(slot != /datum/inventory_slot/abstract/put_in_hands)
stack_trace("attempted usage of slot id in abstract insertion converted successfully")
. = FALSE
switch(slot)
- if(/datum/inventory_slot_meta/abstract/hand/left)
+ if(/datum/inventory_slot/abstract/hand/left)
return put_in_left_hand(I, flags)
- if(/datum/inventory_slot_meta/abstract/hand/right)
+ if(/datum/inventory_slot/abstract/hand/right)
return put_in_right_hand(I, flags)
- if(/datum/inventory_slot_meta/abstract/put_in_belt)
+ if(/datum/inventory_slot/abstract/put_in_belt)
var/obj/item/held = item_by_slot_id(SLOT_ID_BELT)
if(flags & INV_OP_FORCE)
return held?.obj_storage?.insert(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_SOUND)
return held?.obj_storage?.auto_handle_interacted_insertion(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_WARNING, flags & INV_OP_SUPPRESS_SOUND)
- if(/datum/inventory_slot_meta/abstract/put_in_backpack)
+ if(/datum/inventory_slot/abstract/put_in_backpack)
var/obj/item/held = item_by_slot_id(SLOT_ID_BACK)
if(flags & INV_OP_FORCE)
return held?.obj_storage?.insert(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_SOUND)
return held?.obj_storage?.auto_handle_interacted_insertion(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_WARNING, flags & INV_OP_SUPPRESS_SOUND)
- if(/datum/inventory_slot_meta/abstract/put_in_hands)
+ if(/datum/inventory_slot/abstract/put_in_hands)
return put_in_hands(I, flags)
- if(/datum/inventory_slot_meta/abstract/put_in_storage, /datum/inventory_slot_meta/abstract/put_in_storage_try_active)
- if(slot == /datum/inventory_slot_meta/abstract/put_in_storage_try_active)
+ if(/datum/inventory_slot/abstract/put_in_storage, /datum/inventory_slot/abstract/put_in_storage_try_active)
+ if(slot == /datum/inventory_slot/abstract/put_in_storage_try_active)
// todo: redirection
if(flags & INV_OP_FORCE)
if(active_storage?.insert(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_WARNING))
@@ -86,7 +180,7 @@
return held.obj_storage.insert(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_SOUND)
return held.obj_storage.auto_handle_interacted_insertion(I, new /datum/event_args/actor(src), flags & INV_OP_SUPPRESS_WARNING, flags & INV_OP_SUPPRESS_SOUND)
return FALSE
- if(/datum/inventory_slot_meta/abstract/attach_as_accessory)
+ if(/datum/inventory_slot/abstract/attach_as_accessory)
for(var/obj/item/clothing/C in get_equipped_items())
if(C.attempt_attach_accessory(I))
return TRUE
@@ -215,7 +309,7 @@
if(!(flags & INV_OP_FORCE) && HAS_TRAIT(I, TRAIT_ITEM_NODROP))
if(!(flags & INV_OP_SUPPRESS_WARNING))
- var/datum/inventory_slot_meta/slot_meta = resolve_inventory_slot_meta(slot)
+ var/datum/inventory_slot/slot_meta = resolve_inventory_slot(slot)
to_chat(user, SPAN_WARNING("[I] is stubbornly stuck [slot_meta.display_preposition] your [slot_meta.display_name]!"))
return FALSE
@@ -254,7 +348,7 @@
to_chat(user, SPAN_DANGER("can_equip will now attempt to prevent the deleted item from being equipped. There should be no glitches."))
return FALSE
- var/datum/inventory_slot_meta/slot_meta = resolve_inventory_slot_meta(slot)
+ var/datum/inventory_slot/slot_meta = resolve_inventory_slot(slot)
var/self_equip = user == src
if(!slot_meta)
. = FALSE
@@ -263,17 +357,17 @@
if(slot_meta.inventory_slot_flags & INV_SLOT_IS_ABSTRACT)
// special handling: make educated guess, defaulting to yes
switch(slot_meta.type)
- if(/datum/inventory_slot_meta/abstract/hand/left)
+ if(/datum/inventory_slot/abstract/hand/left)
return (flags & INV_OP_FORCE) || !get_left_held_item()
- if(/datum/inventory_slot_meta/abstract/hand/right)
+ if(/datum/inventory_slot/abstract/hand/right)
return (flags & INV_OP_FORCE) || !get_right_held_item()
- if(/datum/inventory_slot_meta/abstract/put_in_backpack)
+ if(/datum/inventory_slot/abstract/put_in_backpack)
var/obj/item/thing = item_by_slot_id(SLOT_ID_BACK)
return thing?.obj_storage?.can_be_inserted(I, new /datum/event_args/actor(user), TRUE)
- if(/datum/inventory_slot_meta/abstract/put_in_belt)
+ if(/datum/inventory_slot/abstract/put_in_belt)
var/obj/item/thing = item_by_slot_id(SLOT_ID_BACK)
return thing?.obj_storage?.can_be_inserted(I, new /datum/event_args/actor(user), TRUE)
- if(/datum/inventory_slot_meta/abstract/put_in_hands)
+ if(/datum/inventory_slot/abstract/put_in_hands)
return (flags & INV_OP_FORCE) || !hands_full()
return TRUE
@@ -432,9 +526,9 @@
*
* return TRUE if conflicting, otherwise FALSE
*/
-/mob/proc/inventory_slot_semantic_conflict(obj/item/I, datum/inventory_slot_meta/slot, mob/user)
+/mob/proc/inventory_slot_semantic_conflict(obj/item/I, datum/inventory_slot/slot, mob/user)
. = FALSE
- slot = resolve_inventory_slot_meta(slot)
+ slot = resolve_inventory_slot(slot)
return slot._equip_check(I, src, user)
/**
@@ -455,7 +549,7 @@
return FALSE
// resolve slot
- var/datum/inventory_slot_meta/slot_meta = resolve_inventory_slot_meta(slot)
+ var/datum/inventory_slot/slot_meta = resolve_inventory_slot(slot)
if(slot_meta.inventory_slot_flags & INV_SLOT_IS_ABSTRACT)
// if it's abstract, we go there directly - do not use can_equip as that will just guess.
return handle_abstract_slot_insertion(I, slot, flags)
@@ -579,7 +673,7 @@
* handles adding an item or updating an item to our hud
*/
/mob/proc/_handle_inventory_hud_update(obj/item/I, slot)
- var/datum/inventory_slot_meta/meta = resolve_inventory_slot_meta(slot)
+ var/datum/inventory_slot/meta = resolve_inventory_slot(slot)
I.screen_loc = meta.hud_position
if(client)
client.screen |= I
diff --git a/code/modules/mob/inventory/slot_meta.dm b/code/modules/mob/inventory/inventory_slot.dm
similarity index 75%
rename from code/modules/mob/inventory/slot_meta.dm
rename to code/modules/mob/inventory/inventory_slot.dm
index 13753a7d158c..f465688ef9aa 100644
--- a/code/modules/mob/inventory/slot_meta.dm
+++ b/code/modules/mob/inventory/inventory_slot.dm
@@ -8,8 +8,8 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
. = list()
GLOB.inventory_slot_meta = .
GLOB.inventory_slot_type_cache = list()
- for(var/path in subtypesof(/datum/inventory_slot_meta))
- var/datum/inventory_slot_meta/M = path
+ for(var/path in subtypesof(/datum/inventory_slot))
+ var/datum/inventory_slot/M = path
if(initial(M.abstract_type) == path)
continue
M = new path
@@ -27,7 +27,7 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
for(var/id in GLOB.inventory_slot_meta)
. += id
-/proc/cmp_inventory_slot_meta_dsc(datum/inventory_slot_meta/a, datum/inventory_slot_meta/b)
+/proc/cmp_inventory_slot_meta_dsc(datum/inventory_slot/a, datum/inventory_slot/b)
return b.sort_order - a.sort_order
/**
@@ -35,8 +35,8 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
*
* String IDs are not automatically converted to paths for speed.
*/
-/proc/resolve_inventory_slot_meta(datum/inventory_slot_meta/id)
- RETURN_TYPE(/datum/inventory_slot_meta)
+/proc/resolve_inventory_slot(datum/inventory_slot/id)
+ RETURN_TYPE(/datum/inventory_slot)
if(istype(id))
return id
else if(ispath(id))
@@ -46,8 +46,8 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
/**
* returns inventory slot render key for an id
*/
-/proc/resolve_inventory_slot_render_key(datum/inventory_slot_meta/id)
- return resolve_inventory_slot_meta(id)?.render_key
+/proc/resolve_inventory_slot_render_key(datum/inventory_slot/id)
+ return resolve_inventory_slot(id)?.render_key
/**
* get inventory slot meta of a typepath
@@ -57,7 +57,7 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
if(.)
return
for(var/id in GLOB.inventory_slot_meta)
- var/datum/inventory_slot_meta/slot = GLOB.inventory_slot_meta[id]
+ var/datum/inventory_slot/slot = GLOB.inventory_slot_meta[id]
if(slot.type != type)
continue
GLOB.inventory_slot_meta[type] = . = slot
@@ -77,11 +77,11 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
* They only work on equips - can_equip, and anything unrelating to unequips, cannot check for it well.
* Can equip supports some abstract slots but not others.
*/
-/datum/inventory_slot_meta
+/datum/inventory_slot
/// abstract type
- abstract_type = /datum/inventory_slot_meta
+ abstract_type = /datum/inventory_slot
- //! Intrinsics
+ //* Intrinsics
/// slot name
var/name = "unknown"
/// id
@@ -93,11 +93,11 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
/// display order - higher is upper. a is applied on 0.
var/sort_order = 0
- //! HUD
+ //* HUD
/// our screen loc
var/hud_position
- //! Grammar
+ //* Grammar
/// player friendly name
var/display_name = "unknown"
/// player friendly preposition
@@ -105,7 +105,7 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
/// is this a "plural" slot?
var/display_plural = FALSE
- //! Equip Checks
+ //* Equip Checks
/// equip checks to use
var/slot_equip_checks = NONE
/// slot flags required to have if checking
@@ -113,13 +113,13 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
/// slot flags forbidden to have if checking
var/slot_flags_forbidden = NONE
- //! Stripping
+ //* Stripping
/// always show on strip/force equip menu, or only show when full
var/always_show_on_strip_menu = TRUE
/// default INV_VIEW flags for stripping
var/default_strip_inv_view_flags = NONE
- //! Rendering
+ //* Rendering
/// rendering slot key
var/render_key
/// rendering plural slot key - only set on base type of plural slots
@@ -136,14 +136,26 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
VAR_PRIVATE/list/render_dim_y_cache
/// fallback states; if set for a bodytype, that bodytype converts to this if not in worn_bodytypes, rather than defaulted.
VAR_PROTECTED/list/render_fallback
+ /// cascade renders to these slot ids
+ // todo: this should be flag-based; for a future refactor via /datum/inventory, not hardcoded!
+ VAR_PROTECTED/list/legacy_visibility_sensitive_slots = list()
-/datum/inventory_slot_meta/New()
+/datum/inventory_slot/New()
if(!id && (inventory_slot_flags & INV_SLOT_ALLOW_RANDOM_ID))
id = "[++id_next]"
+ // resolve typepaths to ids
+ for(var/i in 1 to length(legacy_visibility_sensitive_slots))
+ var/what = legacy_visibility_sensitive_slots[i]
+ if(ispath(what, /datum/inventory_slot))
+ var/datum/inventory_slot/resolving = what
+ if(!initial(resolving.id))
+ stack_trace("no id on [resolving]; cascade render slot targets should have hardcoded ids at this point in time")
+ legacy_visibility_sensitive_slots[i] = initial(resolving.id)
+
rebuild_rendering_caches()
-/datum/inventory_slot_meta/proc/_equip_check(obj/item/I, mob/wearer, mob/user, flags)
+/datum/inventory_slot/proc/_equip_check(obj/item/I, mob/wearer, mob/user, flags)
if(slot_equip_checks & SLOT_EQUIP_CHECK_USE_FLAGS)
if(!(flags & INV_OP_FORCE))
if(!CHECK_MULTIPLE_BITFIELDS(I.slot_flags, slot_flags_required))
@@ -158,16 +170,30 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
/**
* checked if slot_equip_checks specifies to use proc
*/
-/datum/inventory_slot_meta/proc/allow_equip(obj/item/I, mob/wearer, mob/user, force)
+/datum/inventory_slot/proc/allow_equip(obj/item/I, mob/wearer, mob/user, force)
return TRUE
/**
* checks for obfuscation when making the strip menu
*/
-/datum/inventory_slot_meta/proc/strip_obfuscation_check(obj/item/equipped, mob/wearer, mob/user)
+/datum/inventory_slot/proc/strip_obfuscation_check(obj/item/equipped, mob/wearer, mob/user)
return default_strip_inv_view_flags
-/datum/inventory_slot_meta/proc/rebuild_rendering_caches()
+//* Rendering *//
+
+// todo: shouldn't have to specify bodytype
+// todo: yes we should? otherwise we can't tell it to render differently against a mob's default bodytype.
+/datum/inventory_slot/proc/render(mob/wearer, obj/item/item, bodytype)
+ return item.render_mob_appearance(wearer, id, bodytype)
+
+/datum/inventory_slot/proc/should_render(mob/wearer, obj/item/item)
+ return TRUE
+
+/datum/inventory_slot/proc/cascade_render_visibility(mob/wearer, obj/item/item)
+ for(var/slot_id in legacy_visibility_sensitive_slots)
+ wearer.inventory.update_slot_visible(slot_id, FALSE)
+
+/datum/inventory_slot/proc/rebuild_rendering_caches()
PROTECTED_PROC(TRUE) // if you think you need this outside you should rethink
render_state_cache = list()
render_dim_x_cache = list()
@@ -193,7 +219,7 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
/**
* returns (icon, dim_x, dim_y) if found in defaults, null if not
*/
-/datum/inventory_slot_meta/proc/resolve_default_assets(bodytype, state, mob/wearer, obj/item/equipped, inhand_domain)
+/datum/inventory_slot/proc/resolve_default_assets(bodytype, state, mob/wearer, obj/item/equipped, inhand_domain)
var/bodytype_str = bodytype_to_string(bodytype)
if(!render_state_cache[bodytype_str]?[state])
return
@@ -202,7 +228,7 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
/**
* returns layer
*/
-/datum/inventory_slot_meta/proc/resolve_default_layer(bodytype, mob/wearer, obj/item/equipped, inhand_domain)
+/datum/inventory_slot/proc/resolve_default_layer(bodytype, mob/wearer, obj/item/equipped, inhand_domain)
if(!islist(render_layer))
return render_layer
var/index = 1
@@ -214,7 +240,7 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
index = (B.show_above_suit == 1)? 2 : 1
return render_layer[clamp(index, 1, length(render_layer))]
-/datum/inventory_slot_meta/proc/handle_worn_fallback(bodytype, list/worn_data)
+/datum/inventory_slot/proc/handle_worn_fallback(bodytype, list/worn_data)
var/bodytype_str = bodytype_to_string(bodytype)
if(!render_fallback[bodytype_str])
return FALSE
@@ -224,11 +250,13 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
worn_data[WORN_DATA_STATE] = render_fallback[bodytype_str]
return TRUE
-/datum/inventory_slot_meta/inventory
- abstract_type = /datum/inventory_slot_meta/inventory
+//? Implementations ?//
+
+/datum/inventory_slot/inventory
+ abstract_type = /datum/inventory_slot/inventory
inventory_slot_flags = INV_SLOT_IS_RENDERED | INV_SLOT_IS_INVENTORY | INV_SLOT_IS_STRIPPABLE | INV_SLOT_HUD_REQUIRES_EXPAND | INV_SLOT_CONSIDERED_WORN
-/datum/inventory_slot_meta/inventory/back
+/datum/inventory_slot/inventory/back
name = "back"
render_key = "back"
id = SLOT_ID_BACK
@@ -248,9 +276,9 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
render_fallback = list(
BODYTYPE_STRING_TESHARI = "_fallback_"
)
- render_layer = BACK_LAYER
+ render_layer = HUMAN_LAYER_SLOT_BACKPACK
-/datum/inventory_slot_meta/inventory/uniform
+/datum/inventory_slot/inventory/uniform
name = "uniform"
render_key = "under"
id = SLOT_ID_UNIFORM
@@ -272,7 +300,10 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
BODYTYPE_STRING_TESHARI = "_fallback_",
BODYTYPE_STRING_VOX = "_fallback_",
)
- render_layer = UNIFORM_LAYER
+ render_layer = HUMAN_LAYER_SLOT_UNIFORM
+ legacy_visibility_sensitive_slots = list(
+ SLOT_ID_SHOES,
+ )
/// list of rolldown icons; must DIRECTLY corrospond to default icons.
var/list/render_rolldown_icons = list(
@@ -290,7 +321,7 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
/// list of rollsleeve states
var/list/render_rollsleeve_states
-/datum/inventory_slot_meta/inventory/uniform/rebuild_rendering_caches()
+/datum/inventory_slot/inventory/uniform/rebuild_rendering_caches()
. = ..()
render_rolldown_states = list()
for(var/bodytype_str in render_rolldown_icons)
@@ -317,7 +348,7 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
for(var/state in render_rollsleeve_states[bodytype_str])
render_rollsleeve_states[bodytype_str][state] = TRUE
-/datum/inventory_slot_meta/inventory/uniform/resolve_default_assets(bodytype, state, mob/wearer, obj/item/equipped, inhand_domain)
+/datum/inventory_slot/inventory/uniform/resolve_default_assets(bodytype, state, mob/wearer, obj/item/equipped, inhand_domain)
if(!istype(equipped, /obj/item/clothing/under))
return ..()
var/obj/item/clothing/under/U = equipped
@@ -331,13 +362,47 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
else
return ..()
-/datum/inventory_slot_meta/inventory/uniform/proc/check_rolldown_cache(bodytype, state)
+/datum/inventory_slot/inventory/uniform/proc/check_rolldown_cache(bodytype, state)
return render_rolldown_states[bodytype_to_string(bodytype)]?[state]
-/datum/inventory_slot_meta/inventory/uniform/proc/check_rollsleeve_cache(bodytype, state)
+/datum/inventory_slot/inventory/uniform/proc/check_rollsleeve_cache(bodytype, state)
return render_rollsleeve_states[bodytype_to_string(bodytype)]?[state]
-/datum/inventory_slot_meta/inventory/head
+/datum/inventory_slot/inventory/uniform/should_render(mob/wearer, obj/item/item)
+ if(!ishuman(wearer))
+ return ..()
+ var/mob/living/carbon/human/casted_human = wearer
+ if((casted_human.wear_suit?.inv_hide_flags) & HIDEJUMPSUIT)
+ return FALSE
+ return ..()
+
+
+/datum/inventory_slot/inventory/uniform/render(mob/wearer, obj/item/item, bodytype)
+ . = ..()
+ if(!ishuman(wearer))
+ return
+ var/mob/living/carbon/human/human = wearer
+ var/icon/clipmask = human.tail_style?.clip_mask
+ if(!clipmask)
+ return
+ var/tail_rendered = !!human.standing_overlays[HUMAN_OVERLAY_TAIL]
+ if(!tail_rendered)
+ return
+ if(istype(item, /obj/item/clothing/suit))
+ var/obj/item/clothing/suit/suit = item
+ if(suit.inv_hide_flags & HIDETAIL)
+ return
+ if(suit.taurized)
+ return
+ var/list/mutating
+ if(!islist(.))
+ mutating = list(.)
+ else
+ mutating = .
+ for(var/image/appearancelike as anything in mutating)
+ appearancelike.filters += filter(arglist(alpha_mask_filter(icon = clipmask)))
+
+/datum/inventory_slot/inventory/head
name = "head"
render_key = "head"
id = SLOT_ID_HEAD
@@ -367,9 +432,12 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
BODYTYPE_STRING_ZORREN_HIGH = 'icons/mob/clothing/species/fox/helmet.dmi',
BODYTYPE_STRING_UNATHI_DIGI = 'icons/mob/clothing/species/unathidigi/head.dmi',
)
- render_layer = HEAD_LAYER
+ render_layer = HUMAN_LAYER_SLOT_HEAD
+ legacy_visibility_sensitive_slots = list(
+ SLOT_ID_HEAD,
+ )
-/datum/inventory_slot_meta/inventory/suit
+/datum/inventory_slot/inventory/suit
name = "outerwear"
render_key = "suit"
id = SLOT_ID_SUIT
@@ -401,9 +469,44 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
BODYTYPE_STRING_ZADDAT = 'icons/mob/clothing/species/zaddat/suits.dmi',
BODYTYPE_STRING_UNATHI_DIGI = 'icons/mob/clothing/species/unathidigi/suits.dmi',
)
- render_layer = SUIT_LAYER
+ render_layer = HUMAN_LAYER_SLOT_OVERSUIT
+ legacy_visibility_sensitive_slots = list(
+ SLOT_ID_UNIFORM,
+ SLOT_ID_SHOES,
+ )
-/datum/inventory_slot_meta/inventory/belt
+/datum/inventory_slot/inventory/suit/render(mob/wearer, obj/item/item, bodytype)
+ . = ..()
+ if(!ishuman(wearer))
+ return
+ var/mob/living/carbon/human/human = wearer
+ var/icon/clipmask = human.tail_style?.clip_mask
+ if(!clipmask)
+ return
+ var/tail_rendered = !!human.standing_overlays[HUMAN_OVERLAY_TAIL]
+ if(!tail_rendered)
+ return
+ if(istype(item, /obj/item/clothing/suit))
+ var/obj/item/clothing/suit/suit = item
+ if(suit.taurized)
+ return
+ var/list/mutating
+ if(!islist(.))
+ mutating = list(.)
+ else
+ mutating = .
+ for(var/image/appearancelike as anything in mutating)
+ appearancelike.filters += filter(arglist(alpha_mask_filter(icon = clipmask)))
+
+/datum/inventory_slot/inventory/suit/cascade_render_visibility(mob/wearer, obj/item/item)
+ . = ..()
+ var/mob/living/carbon/human/casted = wearer
+ if(!istype(casted))
+ return
+ casted.render_spriteacc_tail()
+ casted.render_spriteacc_wings()
+
+/datum/inventory_slot/inventory/belt
name = "belt"
render_key = "belt"
id = SLOT_ID_BELT
@@ -420,16 +523,16 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
BODYTYPE_STRING_WEREBEAST = 'icons/mob/clothing/species/werebeast/belt.dmi',
BODYTYPE_STRING_UNATHI_DIGI = 'icons/mob/clothing/species/unathidigi/belt.dmi',
)
- render_layer = list(BELT_LAYER, BELT_LAYER_ALT)
+ render_layer = list(HUMAN_LAYER_SLOT_BELT, HUMAN_LAYER_SLOT_BELT_ALT)
-/datum/inventory_slot_meta/inventory/pocket
- abstract_type = /datum/inventory_slot_meta/inventory/pocket
+/datum/inventory_slot/inventory/pocket
+ abstract_type = /datum/inventory_slot/inventory/pocket
sort_order = 2000
inventory_slot_flags = INV_SLOT_IS_INVENTORY | INV_SLOT_IS_STRIPPABLE
slot_equip_checks = SLOT_EQUIP_CHECK_USE_PROC
default_strip_inv_view_flags = INV_VIEW_OBFUSCATE_HIDE_ITEM_NAME | INV_VIEW_STRIP_FUMBLE_ON_FAILURE | INV_VIEW_STRIP_IS_SILENT
-/datum/inventory_slot_meta/inventory/pocket/allow_equip(obj/item/I, mob/wearer, mob/user, force)
+/datum/inventory_slot/inventory/pocket/allow_equip(obj/item/I, mob/wearer, mob/user, force)
. = ..()
if(I.slot_flags & SLOT_DENYPOCKET)
return FALSE
@@ -437,21 +540,21 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
return TRUE
return I.w_class <= WEIGHT_CLASS_SMALL
-/datum/inventory_slot_meta/inventory/pocket/left
+/datum/inventory_slot/inventory/pocket/left
name = "left pocket"
id = SLOT_ID_LEFT_POCKET
display_name = "left pocket"
display_preposition = "in"
hud_position = ui_storage1
-/datum/inventory_slot_meta/inventory/pocket/right
+/datum/inventory_slot/inventory/pocket/right
name = "right pocket"
id = SLOT_ID_RIGHT_POCKET
display_name = "right pocket"
display_preposition = "in"
hud_position = ui_storage2
-/datum/inventory_slot_meta/inventory/id
+/datum/inventory_slot/inventory/id
name = "id"
render_key = "id"
id = SLOT_ID_WORN_ID
@@ -470,9 +573,19 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
render_fallback = list(
BODYTYPE_STRING_TESHARI = "_fallback_"
)
- render_layer = ID_LAYER
+ render_layer = HUMAN_LAYER_SLOT_IDSLOT
+
+/datum/inventory_slot/inventory/id/should_render(mob/wearer, obj/item/item)
+ if(ishuman(wearer))
+ var/mob/living/carbon/human/H = wearer
+ // todo: this should be a trait system.
+ if(istype(H.w_uniform, /obj/item/clothing/under))
+ var/obj/item/clothing/under/uni = H.w_uniform
+ if(!uni.displays_id)
+ return FALSE
+ return ..()
-/datum/inventory_slot_meta/inventory/shoes
+/datum/inventory_slot/inventory/shoes
name = "shoes"
render_key = "shoes"
id = SLOT_ID_SHOES
@@ -494,9 +607,21 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
render_fallback = list(
BODYTYPE_STRING_TESHARI = "_fallback_" // this doesn't actually exist, so item becomes invis
)
- render_layer = list(SHOES_LAYER, SHOES_LAYER_ALT)
+ render_layer = list(HUMAN_LAYER_SLOT_SHOES, HUMAN_LAYER_SLOT_SHOES_ALT)
-/datum/inventory_slot_meta/inventory/gloves
+/datum/inventory_slot/inventory/shoes/should_render(mob/wearer, obj/item/item)
+ if(!ishuman(wearer))
+ return ..()
+ var/mob/living/carbon/human/casted_human = wearer
+ if((casted_human.wear_suit?.inv_hide_flags | casted_human.w_uniform?.inv_hide_flags) & HIDESHOES)
+ return FALSE
+ for(var/bodypart in list(BP_L_FOOT, BP_R_FOOT))
+ var/obj/item/organ/external/foot/foot = casted_human.get_organ(bodypart)
+ if(istype(foot) && foot.is_hidden_by_tail())
+ return FALSE
+ return ..()
+
+/datum/inventory_slot/inventory/gloves
name = "gloves"
render_key = "gloves"
id = SLOT_ID_GLOVES
@@ -517,9 +642,9 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
render_fallback = list(
BODYTYPE_STRING_TESHARI = "_fallback_"
)
- render_layer = GLOVES_LAYER
+ render_layer = HUMAN_LAYER_SLOT_GLOVES
-/datum/inventory_slot_meta/inventory/glasses
+/datum/inventory_slot/inventory/glasses
name = "glasses"
render_key = "glasses"
id = SLOT_ID_GLASSES
@@ -540,9 +665,9 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
render_fallback = list(
BODYTYPE_STRING_TESHARI = "_fallback_"
)
- render_layer = GLASSES_LAYER
+ render_layer = HUMAN_LAYER_SLOT_GLASSES
-/datum/inventory_slot_meta/inventory/suit_storage
+/datum/inventory_slot/inventory/suit_storage
name = "suit storage"
render_key = "suit-store"
id = SLOT_ID_SUIT_STORAGE
@@ -552,9 +677,20 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
hud_position = ui_sstore1
slot_equip_checks = SLOT_EQUIP_CHECK_USE_PROC
inventory_slot_flags = INV_SLOT_IS_RENDERED | INV_SLOT_IS_INVENTORY | INV_SLOT_IS_STRIPPABLE
- render_layer = SUIT_STORE_LAYER
+ render_layer = HUMAN_LAYER_SLOT_SUITSTORE
-/datum/inventory_slot_meta/inventory/suit_storage/allow_equip(obj/item/I, mob/wearer, mob/user, force)
+/datum/inventory_slot/inventory/suit_storage/render(mob/wearer, obj/item/item, bodytype)
+ var/mob/living/carbon/human/casted_human = wearer
+ if(!istype(casted_human))
+ return
+ var/datum/species/species = casted_human.species
+ return image(
+ icon = species.suit_storage_icon,
+ icon_state = item.item_state || item.icon_state,
+ layer = HUMAN_LAYER_SLOT_SUITSTORE,
+ )
+
+/datum/inventory_slot/inventory/suit_storage/allow_equip(obj/item/I, mob/wearer, mob/user, force)
. = ..()
var/obj/item/suit_item = wearer.item_by_slot_id(SLOT_ID_SUIT)
if(!suit_item)
@@ -564,9 +700,9 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
return TRUE
return FALSE
-/datum/inventory_slot_meta/inventory/ears
+/datum/inventory_slot/inventory/ears
sort_order = 9500
- abstract_type = /datum/inventory_slot_meta/inventory/ears
+ abstract_type = /datum/inventory_slot/inventory/ears
inventory_slot_flags = INV_SLOT_IS_RENDERED | INV_SLOT_IS_INVENTORY | INV_SLOT_IS_STRIPPABLE | INV_SLOT_CONSIDERED_WORN | INV_SLOT_HUD_REQUIRES_EXPAND
render_default_icons = list(
BODYTYPE_STRING_DEFAULT = 'icons/mob/clothing/ears.dmi',
@@ -578,10 +714,10 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
render_fallback = list(
BODYTYPE_STRING_TESHARI = "_fallback_"
)
- render_layer = EARS_LAYER
+ render_layer = HUMAN_LAYER_SLOT_EARS
render_key_plural = "ears"
-/datum/inventory_slot_meta/inventory/ears/left
+/datum/inventory_slot/inventory/ears/left
name = "left ear"
render_key = "ear-l"
id = SLOT_ID_LEFT_EAR
@@ -591,7 +727,7 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
slot_equip_checks = SLOT_EQUIP_CHECK_USE_FLAGS
slot_flags_required = SLOT_EARS
-/datum/inventory_slot_meta/inventory/ears/right
+/datum/inventory_slot/inventory/ears/right
name = "right ear"
render_key = "ear-r"
id = SLOT_ID_RIGHT_EAR
@@ -600,9 +736,9 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
hud_position = ui_r_ear
slot_equip_checks = SLOT_EQUIP_CHECK_USE_FLAGS
slot_flags_required = SLOT_EARS
- render_layer = EARS_LAYER
+ render_layer = HUMAN_LAYER_SLOT_EARS
-/datum/inventory_slot_meta/inventory/mask
+/datum/inventory_slot/inventory/mask
name = "mask"
render_key = "mask"
id = SLOT_ID_MASK
@@ -631,15 +767,22 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
render_fallback = list(
BODYTYPE_STRING_TESHARI = "_fallback_",
)
- render_layer = FACEMASK_LAYER
+ render_layer = HUMAN_LAYER_SLOT_MASK
+
+/datum/inventory_slot/inventory/mask/should_render(mob/wearer, obj/item/item)
+ if(ishuman(wearer))
+ var/mob/living/carbon/human/casted_human = wearer
+ if(casted_human.head?.inv_hide_flags & HIDEMASK)
+ return FALSE
+ return ..()
-/datum/inventory_slot_meta/restraints
+/datum/inventory_slot/restraints
sort_order = -250
always_show_on_strip_menu = FALSE
- abstract_type = /datum/inventory_slot_meta/restraints
+ abstract_type = /datum/inventory_slot/restraints
inventory_slot_flags = INV_SLOT_IS_RENDERED | INV_SLOT_IS_STRIPPABLE | INV_SLOT_STRIP_ONLY_REMOVES | INV_SLOT_STRIP_SIMPLE_LINK
-/datum/inventory_slot_meta/restraints/handcuffs
+/datum/inventory_slot/restraints/handcuffs
name = "handcuffed"
render_key = "handcuffs"
id = SLOT_ID_HANDCUFFED
@@ -651,12 +794,12 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
BODYTYPE_STRING_TESHARI = 'icons/mob/clothing/species/teshari/handcuffs.dmi',
BODYTYPE_STRING_UNATHI_DIGI = 'icons/mob/clothing/species/unathidigi/handcuffs.dmi',
)
- render_layer = HANDCUFF_LAYER
+ render_layer = HUMAN_LAYER_SLOT_HANDCUFFED
-/datum/inventory_slot_meta/restraints/handcuffs/allow_equip(obj/item/I, mob/wearer, mob/user, force)
+/datum/inventory_slot/restraints/handcuffs/allow_equip(obj/item/I, mob/wearer, mob/user, force)
return istype(I, /obj/item/handcuffs) && !istype(I, /obj/item/handcuffs/legcuffs)
-/datum/inventory_slot_meta/restraints/legcuffs
+/datum/inventory_slot/restraints/legcuffs
name = "legcuffed"
render_key = "legcuffs"
id = SLOT_ID_LEGCUFFED
@@ -668,42 +811,42 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
BODYTYPE_STRING_TESHARI = 'icons/mob/clothing/species/teshari/handcuffs.dmi',
BODYTYPE_STRING_UNATHI_DIGI = 'icons/mob/clothing/species/unathidigi/handcuffs.dmi'
)
- render_layer = LEGCUFF_LAYER
+ render_layer = HUMAN_LAYER_SLOT_LEGCUFFED
-/datum/inventory_slot_meta/restraints/legcuffs/allow_equip(obj/item/I, mob/wearer, mob/user, force)
+/datum/inventory_slot/restraints/legcuffs/allow_equip(obj/item/I, mob/wearer, mob/user, force)
return istype(I, /obj/item/handcuffs/legcuffs)
/**
* these have no excuse to be accessed by id
* they will have randomized ids
*/
-/datum/inventory_slot_meta/abstract
+/datum/inventory_slot/abstract
inventory_slot_flags = INV_SLOT_IS_ABSTRACT | INV_SLOT_ALLOW_RANDOM_ID
- abstract_type = /datum/inventory_slot_meta/abstract
+ abstract_type = /datum/inventory_slot/abstract
-/datum/inventory_slot_meta/abstract/put_in_hands
+/datum/inventory_slot/abstract/put_in_hands
name = "put in hands"
id = SLOT_ID_HANDS
display_name = "hands"
display_preposition = "in"
display_plural = TRUE
-/datum/inventory_slot_meta/abstract/attach_as_accessory
+/datum/inventory_slot/abstract/attach_as_accessory
name = "attach as accessory"
display_name = "clothes"
display_preposition = "clipped to"
-/datum/inventory_slot_meta/abstract/put_in_backpack
+/datum/inventory_slot/abstract/put_in_backpack
name = "put in backpack"
display_name = "backpack"
display_preposition = "in"
-/datum/inventory_slot_meta/abstract/put_in_belt
+/datum/inventory_slot/abstract/put_in_belt
name = "put in belt"
display_name = "belt"
display_preposition = "in"
-/datum/inventory_slot_meta/abstract/put_in_storage
+/datum/inventory_slot/abstract/put_in_storage
name = "put in storage"
display_name = "storage"
display_preposition = "in"
@@ -711,31 +854,31 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
/**
* like put in storage, but prioritizes active, even if it's not on you.
*/
-/datum/inventory_slot_meta/abstract/put_in_storage_try_active
+/datum/inventory_slot/abstract/put_in_storage_try_active
name = "put in storage (active storage)"
display_name = "storage"
display_name = "in"
-/datum/inventory_slot_meta/abstract/hand
- abstract_type = /datum/inventory_slot_meta/abstract/hand
+/datum/inventory_slot/abstract/hand
+ abstract_type = /datum/inventory_slot/abstract/hand
// our render default icons are based on inhand type
// this hijacks render_default_icons SO much but i don't care!
/**
* returns (icon, dim_x, dim_y) if found in defaults, null if not
*/
-/datum/inventory_slot_meta/abstract/hand/resolve_default_assets(bodytype, state, mob/wearer, obj/item/equipped, inhand_domain)
+/datum/inventory_slot/abstract/hand/resolve_default_assets(bodytype, state, mob/wearer, obj/item/equipped, inhand_domain)
if(!render_state_cache[inhand_domain]?[state])
return
return list(render_default_icons[inhand_domain], render_dim_x_cache[inhand_domain], render_dim_y_cache[inhand_domain])
-/datum/inventory_slot_meta/abstract/hand/left
+/datum/inventory_slot/abstract/hand/left
name = "put in left hand"
display_name = "left hand"
display_preposition = "in"
id = SLOT_ID_LEFT_HAND
render_key = "left"
- render_layer = L_HAND_LAYER
+ render_layer = HUMAN_LAYER_SLOT_LHAND
render_default_icons = list(
INHAND_DEFAULT_ICON_BALLS = 'icons/mob/items/lefthand_balls.dmi',
INHAND_DEFAULT_ICON_BOOKS = 'icons/mob/items/lefthand_books.dmi',
@@ -757,13 +900,13 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
INHAND_DEFAULT_ICON_64X64 = 'icons/mob/items/64x64_lefthand.dmi',
)
-/datum/inventory_slot_meta/abstract/hand/right
+/datum/inventory_slot/abstract/hand/right
name = "put in right hand"
display_name = "right hand"
display_preposition = "in"
id = SLOT_ID_RIGHT_HAND
render_key = "right"
- render_layer = R_HAND_LAYER
+ render_layer = HUMAN_LAYER_SLOT_RHAND
render_default_icons = list(
INHAND_DEFAULT_ICON_BALLS = 'icons/mob/items/righthand_balls.dmi',
INHAND_DEFAULT_ICON_BOOKS = 'icons/mob/items/righthand_books.dmi',
@@ -785,8 +928,8 @@ GLOBAL_LIST_EMPTY(inventory_slot_type_cache)
INHAND_DEFAULT_ICON_64X64 = 'icons/mob/items/64x64_righthand.dmi',
)
-/datum/inventory_slot_meta/abstract/use_one_for_accessory
+/datum/inventory_slot/abstract/use_one_for_accessory
render_key = "acc"
-/datum/inventory_slot_meta/abstract/use_one_for_all
+/datum/inventory_slot/abstract/use_one_for_all
render_key = "all"
diff --git a/code/modules/mob/inventory/items.dm b/code/modules/mob/inventory/items.dm
index c05f784e2137..a63fbf7a26a2 100644
--- a/code/modules/mob/inventory/items.dm
+++ b/code/modules/mob/inventory/items.dm
@@ -387,7 +387,7 @@
/obj/item/proc/is_being_worn()
if(!worn_slot)
return FALSE
- var/datum/inventory_slot_meta/slot_meta = resolve_inventory_slot_meta(worn_slot)
+ var/datum/inventory_slot/slot_meta = resolve_inventory_slot(worn_slot)
return slot_meta.inventory_slot_flags & INV_SLOT_CONSIDERED_WORN
/**
diff --git a/code/modules/mob/inventory/rendering.dm b/code/modules/mob/inventory/rendering.dm
index 164aa61eb697..25f9983f65ec 100644
--- a/code/modules/mob/inventory/rendering.dm
+++ b/code/modules/mob/inventory/rendering.dm
@@ -245,24 +245,26 @@
*
* @params
* * M - the mob we're rendering
+ * * slot_id_or_hand_index - the slot ID or numerical held index we're in
+ * * bodytype - the effective bodytype
*/
/obj/item/proc/render_mob_appearance(mob/M, slot_id_or_hand_index, bodytype = BODYTYPE_DEFAULT)
// SHOULD_NOT_OVERRIDE(TRUE) // if you think you need to, rethink.
// todo: eh reevaluate later
// determine if in hands
var/inhands = isnum(slot_id_or_hand_index)? slot_id_or_hand_index : null
- var/datum/inventory_slot_meta/slot_meta
+ var/datum/inventory_slot/slot_meta
// resolve slot
if(inhands)
- slot_meta = resolve_inventory_slot_meta((slot_id_or_hand_index % 2)? /datum/inventory_slot_meta/abstract/hand/left : /datum/inventory_slot_meta/abstract/hand/right)
+ slot_meta = resolve_inventory_slot((slot_id_or_hand_index % 2)? /datum/inventory_slot/abstract/hand/left : /datum/inventory_slot/abstract/hand/right)
else
- slot_meta = resolve_inventory_slot_meta(slot_id_or_hand_index)
+ slot_meta = resolve_inventory_slot(slot_id_or_hand_index)
var/list/resolved = resolve_worn_assets(M, slot_meta, inhands, bodytype)
return _render_mob_appearance(M, slot_meta, inhands, bodytype, resolved[WORN_DATA_ICON], resolved[WORN_DATA_STATE], resolved[WORN_DATA_LAYER], resolved [WORN_DATA_SIZE_X], resolved[WORN_DATA_SIZE_Y], resolved[WORN_DATA_ALIGN_Y])
-/obj/item/proc/_render_mob_appearance(mob/M, datum/inventory_slot_meta/slot_meta, inhands, bodytype, icon_used, state_used, layer_used, dim_x, dim_y, align_y)
+/obj/item/proc/_render_mob_appearance(mob/M, datum/inventory_slot/slot_meta, inhands, bodytype, icon_used, state_used, layer_used, dim_x, dim_y, align_y)
SHOULD_NOT_OVERRIDE(TRUE) // if you think you need to, rethink.
PRIVATE_PROC(TRUE) // if you think you need to call this, rethink.
var/list/additional = render_additional(M, icon_used, state_used, layer_used, dim_x, dim_y, align_y, bodytype, inhands, slot_meta)
@@ -273,7 +275,7 @@
// worn_state_guard makes us not render if we'd render the same as in-inventory icon.
if(no_render) // don't bother
return additional
- MA = mutable_appearance(icon_used, state_used, BODY_LAYER + layer_used, FLOAT_PLANE)
+ MA = mutable_appearance(icon_used, state_used, layer_used, FLOAT_PLANE)
// temporary - until coloration
MA.color = color
MA = center_appearance(MA, dim_x, dim_y)
@@ -288,7 +290,7 @@
*
* icon/icon state/layer information is included in the mutable appearance
*/
-/obj/item/proc/render_apply_custom(mob/M, mutable_appearance/MA, bodytype, inhands, datum/inventory_slot_meta/slot_meta, icon_used, align_y)
+/obj/item/proc/render_apply_custom(mob/M, mutable_appearance/MA, bodytype, inhands, datum/inventory_slot/slot_meta, icon_used, align_y)
return MA
/**
@@ -296,13 +298,13 @@
*
* icon/icon state/layer information is included in the mutable appearance
*/
-/obj/item/proc/render_apply_blood(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot_meta/slot_meta, icon_used)
+/obj/item/proc/render_apply_blood(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot/slot_meta, icon_used)
return MA
/**
* override to apply overlays to our current mutable appearance; called first
*/
-/obj/item/proc/render_apply_overlays(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot_meta/slot_meta, icon_used)
+/obj/item/proc/render_apply_overlays(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot/slot_meta, icon_used)
if(addblends)
var/mutable_appearance/adding = mutable_appearance(icon = MA.icon, icon_state = addblends)
adding.blend_mode = BLEND_ADD
@@ -312,7 +314,7 @@
/**
* override to include additional appearances while rendering
*/
-/obj/item/proc/render_additional(mob/M, icon/icon_used, state_used, layer_used, dim_x, dim_y, align_y, bodytype, inhands, datum/inventory_slot_meta/slot_meta)
+/obj/item/proc/render_additional(mob/M, icon/icon_used, state_used, layer_used, dim_x, dim_y, align_y, bodytype, inhands, datum/inventory_slot/slot_meta)
RETURN_TYPE(/list)
return list()
@@ -325,9 +327,9 @@
* - inhands - if we're going to inhands
* - bodytype - bodytype in question
*/
-/obj/item/proc/resolve_worn_assets(mob/M, datum/inventory_slot_meta/slot_meta, inhands, bodytype)
+/obj/item/proc/resolve_worn_assets(mob/M, datum/inventory_slot/slot_meta, inhands, bodytype)
if(istext(slot_meta))
- slot_meta = resolve_inventory_slot_meta(slot_meta)
+ slot_meta = resolve_inventory_slot(slot_meta)
var/list/data = new /list(WORN_DATA_LIST_SIZE)
//? state ; item_state_slots --> (worn_state | inhand_state) --> item_state --> icon_state
@@ -422,20 +424,20 @@
/obj/item/proc/debug_worn_assets(slot_or_id, mob/M = worn_mob(), bodytype)
var/mob/living/carbon/human/H = ishuman(M)? M : null
- var/datum/inventory_slot_meta/slot_meta
+ var/datum/inventory_slot/slot_meta
if(isnull(slot_or_id))
slot_or_id = worn_slot
if(isnum(slot_or_id))
- slot_meta = resolve_inventory_slot_meta((slot_or_id % 2)? /datum/inventory_slot_meta/abstract/hand/left : /datum/inventory_slot_meta/abstract/hand/right)
+ slot_meta = resolve_inventory_slot((slot_or_id % 2)? /datum/inventory_slot/abstract/hand/left : /datum/inventory_slot/abstract/hand/right)
else
- slot_meta = resolve_inventory_slot_meta(slot_or_id)
+ slot_meta = resolve_inventory_slot(slot_or_id)
if(isnull(bodytype) && H)
bodytype = H.species.get_effective_bodytype(H, src, slot_meta)
. = resolve_worn_assets(M, slot_meta, isnum(slot_or_id), bodytype)
.[WORN_DATA_ICON] = "[.[WORN_DATA_ICON]]"
// todo: remove, aka get rid of fucking uniform _s state
-/obj/item/proc/resolve_legacy_state(mob/M, datum/inventory_slot_meta/slot_meta, inhands, bodytype)
+/obj/item/proc/resolve_legacy_state(mob/M, datum/inventory_slot/slot_meta, inhands, bodytype)
return (item_state_slots?[slot_meta.id]) || (inhands? inhand_state : worn_state) || item_state || icon_state
/obj/item/proc/resolve_worn_state(inhands, slot_key, bodytype)
diff --git a/code/modules/mob/inventory/stripping.dm b/code/modules/mob/inventory/stripping.dm
index ce9ed20c094b..20691482912a 100644
--- a/code/modules/mob/inventory/stripping.dm
+++ b/code/modules/mob/inventory/stripping.dm
@@ -33,7 +33,7 @@
var/last_order
for(var/id in slot_ids)
// todo: optimize
- var/datum/inventory_slot_meta/meta = resolve_inventory_slot_meta(id)
+ var/datum/inventory_slot/meta = resolve_inventory_slot(id)
var/remove_only = meta.inventory_slot_flags & INV_SLOT_STRIP_ONLY_REMOVES
var/obj/item/I = _item_by_slot(id)
if(remove_only && !I)
@@ -97,7 +97,7 @@
if(!strip_interaction_prechecks(user))
return FALSE
- var/datum/inventory_slot_meta/slot_meta = resolve_inventory_slot_meta(slot_id)
+ var/datum/inventory_slot/slot_meta = resolve_inventory_slot(slot_id)
if(!slot_meta)
return FALSE
@@ -158,9 +158,9 @@
var/view_flags = NONE
- var/datum/inventory_slot_meta/slot_meta = resolve_inventory_slot_meta(slot_id_or_index)
+ var/datum/inventory_slot/slot_meta = resolve_inventory_slot(slot_id_or_index)
if(!isnum(slot_id_or_index))
- slot_meta = resolve_inventory_slot_meta(slot_id_or_index)
+ slot_meta = resolve_inventory_slot(slot_id_or_index)
view_flags = slot_meta.strip_obfuscation_check(ours, src, user)
if(view_flags & (INV_VIEW_OBFUSCATE_HIDE_SLOT))
return FALSE // how are you seeing this
@@ -252,7 +252,7 @@
return
var/slot = I.worn_slot
if(slot != SLOT_ID_HANDS)
- var/datum/inventory_slot_meta/slot_meta = resolve_inventory_slot_meta(slot)
+ var/datum/inventory_slot/slot_meta = resolve_inventory_slot(slot)
var/view_flags = slot_meta.strip_obfuscation_check(I, src, user)
if(view_flags & (INV_VIEW_OBFUSCATE_DISALLOW_INTERACT | INV_VIEW_OBFUSCATE_HIDE_ITEM_EXISTENCE | INV_VIEW_OBFUSCATE_HIDE_SLOT))
return // how tf are you gonna interact with it huh
diff --git a/code/modules/mob/living/carbon/breathe.dm b/code/modules/mob/living/carbon/breathe.dm
index 5e7ead6578f4..5213fe1a9b7e 100644
--- a/code/modules/mob/living/carbon/breathe.dm
+++ b/code/modules/mob/living/carbon/breathe.dm
@@ -72,9 +72,9 @@
if(breath)
//handle mask filtering
- if(istype(wear_mask, /obj/item/clothing/mask) && breath)
+ if(istype(wear_mask, /obj/item/clothing/mask))
var/obj/item/clothing/mask/M = wear_mask
- var/datum/gas_mixture/gas_filtered = M.filter_air(breath)
+ var/datum/gas_mixture/gas_filtered = M.process_air(breath)
loc.assume_air(gas_filtered)
return breath
return null
@@ -96,4 +96,8 @@
/mob/living/carbon/proc/handle_post_breath(datum/gas_mixture/breath)
if(breath)
- loc?.assume_air(breath) //by default, exhale
+ if(istype(wear_mask, /obj/item/clothing/mask))
+ var/obj/item/clothing/mask/M = wear_mask
+ loc.assume_air(M.process_exhale(breath))
+ else
+ loc?.assume_air(breath) //by default, exhale
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index 1eab6fae097d..2db93426db2b 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -4,6 +4,12 @@
return null
..()
+/mob/living/carbon/emp_act(severity)
+ . = ..()
+ // tODO: REFACTOR THIS DUMB SHIT
+ for(var/obj/item/organ/organ in organs | internal_organs)
+ organ.emp_act(severity)
+
/mob/living/carbon/standard_weapon_hit_effects(obj/item/I, mob/living/user, var/effective_force, var/blocked, var/soaked, var/hit_zone)
if(!effective_force || blocked >= 100)
return 0
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index 8cea5e39817e..d3eb1024b1d8 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -1,13 +1,13 @@
/**
* The root type of complex mobs.
- *
+ *
* These mobs have:
* * organ / bodypart simulation
* * reagent metabolism
* * species
* * virus simulation
* * and more.
- *
+ *
* todo: this should be carbon.dm at some point
*/
/mob/living/carbon
@@ -18,6 +18,12 @@
/// species - datumized handling of racial intrinsics like health, environmental, breathing, etc. set using set_species() **only**
var/datum/species/species
+ //* Rendering *//
+ /// keyed overlay storage list
+ /// must map key to an overlay or a list of overlays
+ /// the overlay in question must be an image or a mutable appearance
+ var/list/standing_overlays = list()
+
var/list/stomach_contents = list()
///var/list/datum/disease2/disease/virus2 = list()
var/list/antibodies = list()
diff --git a/code/modules/mob/living/carbon/carbon_movement.dm b/code/modules/mob/living/carbon/carbon_movement.dm
index a8bbd6f793e2..059deedb94f7 100644
--- a/code/modules/mob/living/carbon/carbon_movement.dm
+++ b/code/modules/mob/living/carbon/carbon_movement.dm
@@ -5,12 +5,9 @@
if(src.nutrition && src.stat != 2)
if(ishuman(src))
var/mob/living/carbon/human/M = src
- if(M.stat != 2 && M.nutrition > 0)
- M.nutrition -= M.species.hunger_factor/10
- if(M.m_intent == "run")
- M.nutrition -= M.species.hunger_factor/10
- if(M.nutrition < 0)
- M.nutrition = 0
+ if(M.stat != 2 && M.nutrition > 0 && M.m_intent == MOVE_INTENT_RUN)
+ // Only running takes nutrition now, still as much as before
+ M.nutrition = max(0, M.nutrition - M.species.hunger_factor/5)
else
src.nutrition -= DEFAULT_HUNGER_FACTOR/10
if(src.m_intent == "run")
diff --git a/code/modules/mob/living/carbon/damage_procs.dm b/code/modules/mob/living/carbon/damage_procs.dm
index 0f6d2d9f2c61..3aa39ee701a8 100644
--- a/code/modules/mob/living/carbon/damage_procs.dm
+++ b/code/modules/mob/living/carbon/damage_procs.dm
@@ -27,7 +27,7 @@
if(!defer_updates && .)
update_health()
- UpdateDamageIcon()
+ update_damage_overlay()
/mob/living/carbon/take_overall_damage(brute, burn, damage_mode, weapon_descriptor, defer_updates)
if(status_flags & STATUS_GODMODE)
@@ -43,4 +43,4 @@
if(!defer_updates && .)
update_health()
- UpdateDamageIcon()
+ update_damage_overlay()
diff --git a/code/modules/mob/living/carbon/health.dm b/code/modules/mob/living/carbon/health.dm
index 60324390e38f..90f260a9ffdb 100644
--- a/code/modules/mob/living/carbon/health.dm
+++ b/code/modules/mob/living/carbon/health.dm
@@ -18,3 +18,5 @@
ingested.clear_reagents()
touching.clear_reagents()
// todo: organs
+ // redo graphics
+ rebuild_standing_overlays()
diff --git a/code/modules/mob/living/carbon/human/ai_controlled/ai_controlled.dm b/code/modules/mob/living/carbon/human/ai_controlled/ai_controlled.dm
index 51608c1e1e80..c43d9c05500a 100644
--- a/code/modules/mob/living/carbon/human/ai_controlled/ai_controlled.dm
+++ b/code/modules/mob/living/carbon/human/ai_controlled/ai_controlled.dm
@@ -81,10 +81,10 @@
equip_to_slot_or_del(new to_wear_back(src), SLOT_ID_BACK)
if(to_wear_l_hand)
- equip_to_slot_or_del(new to_wear_l_hand(src), /datum/inventory_slot_meta/abstract/hand/left)
+ equip_to_slot_or_del(new to_wear_l_hand(src), /datum/inventory_slot/abstract/hand/left)
if(to_wear_r_hand)
- equip_to_slot_or_del(new to_wear_r_hand(src), /datum/inventory_slot_meta/abstract/hand/right)
+ equip_to_slot_or_del(new to_wear_r_hand(src), /datum/inventory_slot/abstract/hand/right)
if(to_wear_id_type)
var/obj/item/card/id/W = new to_wear_id_type(src)
diff --git a/code/modules/mob/living/carbon/human/appearance.dm b/code/modules/mob/living/carbon/human/appearance.dm
index c845bd03d641..e213076aa78f 100644
--- a/code/modules/mob/living/carbon/human/appearance.dm
+++ b/code/modules/mob/living/carbon/human/appearance.dm
@@ -75,7 +75,7 @@
grad_wingstyle = wing_gradient
- update_wing_showing()
+ render_spriteacc_wings()
return 1
/mob/living/carbon/human/proc/change_facial_hair(var/facial_hair_style)
@@ -212,9 +212,9 @@
var/datum/sprite_accessory/S = GLOB.legacy_hair_lookup[hairstyle]
if(check_gender && gender != NEUTER)
- if(gender == MALE && S.gender == FEMALE)
+ if(gender == MALE && S.random_generation_gender == FEMALE)
continue
- else if(gender == FEMALE && S.gender == MALE)
+ else if(gender == FEMALE && S.random_generation_gender == MALE)
continue
if(S.apply_restrictions && !(use_species in S.species_allowed))
@@ -233,11 +233,8 @@
for(var/facialhairstyle in GLOB.legacy_facial_hair_lookup)
var/datum/sprite_accessory/S = GLOB.legacy_facial_hair_lookup[facialhairstyle]
- if(gender != NEUTER)
- if(gender == MALE && S.gender == FEMALE)
- continue
- else if(gender == FEMALE && S.gender == MALE)
- continue
+ if(!isnull(S.random_generation_gender) && gender != S.random_generation_gender)
+ continue
if(S.apply_restrictions && !(use_species in S.species_allowed))
continue
diff --git a/code/modules/mob/living/carbon/human/blood.dm b/code/modules/mob/living/carbon/human/blood.dm
index b7b1dbcfd8c7..1e02d8efeafe 100644
--- a/code/modules/mob/living/carbon/human/blood.dm
+++ b/code/modules/mob/living/carbon/human/blood.dm
@@ -45,6 +45,15 @@ var/const/CE_STABLE_THRESHOLD = 0.5
B.color = B.data["blood_colour"]
B.name = B.data["blood_name"]
+/mob/living/carbon/human/proc/fixblood_if_broken()
+ if(species.species_flags & NO_BLOOD)
+ return
+ if(!should_have_organ(O_HEART))
+ return
+ if(!vessel.has_reagent("blood"))
+ vessel.add_reagent("blood", 0.1)
+ fixblood()
+
// Takes care blood loss and regeneration
/mob/living/carbon/human/handle_blood()
if(inStasisNow())
@@ -211,7 +220,7 @@ var/const/CE_STABLE_THRESHOLD = 0.5
if(!amt)
return 0
- if(amt > vessel.get_reagent_amount("blood"))
+ if(amt >= vessel.get_reagent_amount("blood"))
amt = vessel.get_reagent_amount("blood") - 1 // Bit of a safety net; it's impossible to add blood if there's not blood already in the vessel.
return vessel.remove_reagent("blood",amt * (src.mob_size/MOB_MEDIUM))
@@ -260,6 +269,9 @@ var/const/CE_STABLE_THRESHOLD = 0.5
if(vessel.get_reagent_amount("blood") < amount)
return null
+ if(amount >= vessel.get_reagent_amount("blood"))
+ amount = vessel.get_reagent_amount("blood") - 1 // Bit of a safety net; it's impossible to add blood if there's not blood already in the vessel.
+
. = ..()
vessel.remove_reagent("blood",amount) // Removes blood if human
@@ -286,6 +298,8 @@ var/const/CE_STABLE_THRESHOLD = 0.5
reagents.add_reagent("blood", amount, injected.data)
reagents.update_total()
return
+
+ fixblood_if_broken()
var/datum/reagent/blood/our = get_blood(vessel)
diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm
index dc6a4f7a171a..457416f29466 100644
--- a/code/modules/mob/living/carbon/human/emote.dm
+++ b/code/modules/mob/living/carbon/human/emote.dm
@@ -354,13 +354,13 @@
m_type = 1
if("ara")
- message = "aras"
+ message = "aras."
var/use_sound
use_sound = pick('sound/voice/ara_ara1.ogg','sound/voice/ara_ara2.ogg')
playsound(src.loc, use_sound, 50, 0)
if("uwu")
- message = "lets out a devious noise"
+ message = "lets out a devious noise."
playsound(src.loc, 'sound/voice/uwu.ogg', 50, 0)
if ("drool")
@@ -444,7 +444,8 @@
robotic = 1
if(!robotic)
message = "coughs up a small amount of blood!"
- BloodyMouth()
+ //! disabled for shitcode reasons
+ // BloodyMouth()
if(get_gender() == FEMALE)
if(species.female_cough_sounds)
playsound(src, pick(species.female_cough_sounds), 120)
@@ -961,10 +962,12 @@
src.animate_tail_once()
if("wag", "sway")
- src.animate_tail_start()
+ src.toggle_tail_vr()
+ // src.animate_tail_start()
if("qwag", "fastsway")
- src.animate_tail_fast()
+ src.toggle_tail_vr()
+ // src.animate_tail_fast()
if("swag", "stopsway")
src.animate_tail_stop()
@@ -1035,10 +1038,10 @@
return
if ("help")
- to_chat(src, "nyaha, awoo, bark, blink, blink_r, blush, bow-(none)/mob, burp, chirp, choke, chuckle, clap, collapse, cough, cry, custom, deathgasp, drool, eyebrow, fastsway/qwag, \
- flip, frown, gasp, giggle, glare-(none)/mob, grin, groan, grumble, handshake, hiss, hug-(none)/mob, laugh, look-(none)/mob, merp, moan, mumble, nod, nya, pale, peep, point-atom, \
+ to_chat(src, "nyaha, ara, awoo, bark, bleat, blink, blink_r, blush, bow-(none)/mob, burp, chirp, choke, chuckle, clap, collapse, cough, cry, custom, deathgasp, drool, eyebrow, fastsway/qwag, \
+ flip, frown, gasp, giggle, glare-(none)/mob, grin, groan, grumble, handshake, hiss, hug-(none)/mob, laugh, look-(none)/mob, mar, merp, moan, mrrp, mumble, nod, nya, pale, peep, point-atom, prbt, \
raise, roll, salute, fullsalute, scream, sneeze, shake, shiver, shrug, sigh, signal-#1-10, slap-(none)/mob, smile, sneeze, sniff, snore, stare-(none)/mob, stopsway/swag, squeak, sway/wag, swish, tremble, twitch, \
- twitch_v, vomit, weh, whimper, wink, yawn. Moth: mchitter, mlaugh, mscream, msqueak. Synthetics: beep, buzz, buzz2, chime, die, dwoop, error, honk, no, ping, rcough, rsneeze, scary, \
+ twitch_v, uwu, vomit, weh, whimper, wink, yawn. Moth: mchitter, mlaugh, mscream, msqueak. Synthetics: beep, buzz, buzz2, chime, die, dwoop, error, honk, no, ping, rcough, rsneeze, scary, \
shutdown, startup, warn, ye, yes. Vox: shriekshort, shriekloud")
else
@@ -1110,19 +1113,19 @@
if ("vwag")
if(toggle_tail_vr(message = 1))
m_type = 1
- message = "[wagging ? "starts" : "stops"] wagging their tail."
+ message = "[get_sprite_accessory_variation(SPRITE_ACCESSORY_SLOT_TAIL, SPRITE_ACCESSORY_VARIATION_WAGGING) ? "starts" : "stops"] wagging their tail."
else
return 1
if ("vflap")
if(toggle_wing_vr(message = 1))
m_type = 1
- message = "[flapping ? "starts" : "stops"] flapping their wings."
+ message = "[get_sprite_accessory_variation(SPRITE_ACCESSORY_SLOT_WINGS, SPRITE_ACCESSORY_VARIATION_FLAPPING) ? "starts" : "stops"] flapping their wings."
else
return 1
if ("vspread")
if(toggle_wing_spread(message = 1))
m_type = 1
- message = "[spread ? "extends" : "retracts"] their wings."
+ message = "[get_sprite_accessory_variation(SPRITE_ACCESSORY_SLOT_WINGS, SPRITE_ACCESSORY_VARIATION_SPREAD) ? "extends" : "retracts"] their wings."
else
return 1
if ("mlem")
@@ -1161,7 +1164,7 @@
playsound(src.loc, 'sound/misc/prbt.ogg', 50, 1, -1)
m_type = 2
if ("mrrp")
- message = "mrrps"
+ message = "mrrps."
m_type = 2
playsound(src.loc, "sound/voice/mrrp.ogg", 50, 1, -1)
if ("weh")
@@ -1172,6 +1175,10 @@
message = "lets out a merp."
m_type = 2
playsound(loc, 'sound/voice/merp.ogg', 50, 1, -1)
+ if ("bleat")
+ message = "bleats!"
+ m_type = 2
+ playsound(loc, pick(list('sound/voice/baa.ogg','sound/voice/baa2.ogg')), 50, 1, -1)
if ("bark")
message = "lets out a bark."
m_type = 2
@@ -1187,7 +1194,7 @@
if("mar")
message = "lets out a mar."
m_type = 2
- playsound(loc, 'sound/voice/mar.ogg', 50, 1, -1)
+ playsound(loc, 'sound/voice/mar.ogg', 50, 1, -1)
if ("nsay")
nsay()
return TRUE
@@ -1259,51 +1266,6 @@
/mob/living/carbon/human/proc/spam_flag_false() //used for addtimer
spam_flag = FALSE
-/mob/living/carbon/human/proc/toggle_tail_vr(var/setting,var/message = 0)
- if(!tail_style || !tail_style.ani_state)
- if(message)
- to_chat(src, "You don't have a tail that supports this.")
- return 0
-
- var/new_wagging = isnull(setting) ? !wagging : setting
- if(new_wagging != wagging)
- wagging = new_wagging
- update_tail_showing()
- return 1
-
-/mob/living/carbon/human/proc/toggle_wing_vr(var/setting,var/message = 0)
- if(!wing_style || !wing_style.ani_state)
- if(message)
- to_chat(src, "You don't have wings that support this.")
- return 0
-
- var/new_flapping = isnull(setting) ? !flapping : setting
- if(new_flapping != flapping)
- flapping = setting
- if(flapping)
- spread = FALSE
- update_wing_showing()
- return 1
-
-/mob/living/carbon/human/proc/toggle_wing_spread(var/folded,var/message = 0)
- if(!wing_style)
- if(message)
- to_chat(src, "You don't have wings!")
- return 0
-
- if(!wing_style.spr_state)
- if(message)
- to_chat(src, "You don't have wings that support this.")
- return 0
-
- var/new_spread = isnull(folded) ? !spread : folded
- if(new_spread != spread)
- spread = new_spread
- if(spread)
- flapping = FALSE
- update_wing_showing()
- return 1
-
/mob/living/carbon/human/verb/toggle_gender_identity_vr()
set name = "Set Gender Identity"
set desc = "Sets the pronouns when examined and performing an emote."
@@ -1313,10 +1275,3 @@
return 0
change_gender_identity(new_gender_identity)
return 1
-
-/mob/living/carbon/human/verb/switch_tail_layer()
- set name = "Switch tail layer"
- set category = VERB_CATEGORY_IC
- set desc = "Switch tail layer on top."
- tail_alt = !tail_alt
- update_tail_showing()
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index f8976ba73c60..906f80f456a3 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -290,8 +290,7 @@
if(suiciding)
. += SPAN_WARNING("[T.He] appears to have commited suicide... There is no hope of recovery.")
-
-//! I hate this. Though it's better than what it was before. -Zandario
+ //* I hate this. Though it's better than what it was before. -Zandario
var/message = FALSE
var/weight_examine = round(weight)
if(show_pudge()) //Some clothing or equipment can hide this.
@@ -323,7 +322,7 @@
else
message = SPAN_DANGER("[T.He] [T.is] so morbidly obese, you wonder how [T.he] can even stand, let alone waddle around the station.")
. += message
-//! End of the bs
+ //* End of the bs
if(attempt_vr(src,"examine_bellies",args))
. += attempt_vr(src,"examine_bellies",args)
diff --git a/code/modules/mob/living/carbon/human/gradient.dm b/code/modules/mob/living/carbon/human/gradient.dm
deleted file mode 100644
index 4b0352fdb3cc..000000000000
--- a/code/modules/mob/living/carbon/human/gradient.dm
+++ /dev/null
@@ -1,19 +0,0 @@
-GLOBAL_LIST_INIT(hair_gradients, list(
- "None" = "none",
- "Fade (Up)" = "fadeup",
- "Fade Low (Up)" = "fadeup_low",
- "Fade (Down)" = "fadedown",
- "Fade Low (Down)" = "fadedown_low",
- "Fade High (Down)" = "fadedown_high",
- "Reflected (Center)"= "reflected",
- "Reflected Inverse (Center)"= "reflected_inverse",
- "Vertical Split (Back)"= "vsplit_back",
- "Vertical Split" = "vsplit",
- "Vertical Split (Back, Front Effect)"= "vsplit_dual",
- "Bottom (Flat)" = "bottomflat",
- "Shoulders (Flat)" = "shouldersflat",
- "Wavy" = "wavy",
- "Wavy Spiked" = "wavy_spiked",
- "Striped" = "striped",
- "Striped (Vertical)"= "striped_vertical"
- ))
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 59c9e65f598f..389205a497bf 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -44,8 +44,6 @@
dna.real_name = real_name
sync_organ_dna()
- init_world_bender_hud()
-
if(mapload)
return INITIALIZE_HINT_LATELOAD
@@ -65,19 +63,8 @@
qdel(organ)
QDEL_NULL(nif)
QDEL_LIST_NULL(vore_organs)
- cleanup_world_bender_hud()
return ..()
-/mob/living/carbon/human/prepare_data_huds()
- //Update med hud images...
- . = ..()
- //...sec hud images...
- update_hud_sec_implants()
- update_hud_sec_job()
- update_hud_sec_status()
- //...and display them.
- add_to_all_human_data_huds()
-
/mob/living/carbon/human/statpanel_data(client/C)
. = ..()
if(C.statpanel_tab("Status"))
@@ -1115,6 +1102,7 @@
return
var/datum/species/S
+ var/datum/species/old_species = species
// provided? if so, set
// (and hope to god the provider isn't stupid and didn't quantum entangle a datum)
@@ -1148,6 +1136,36 @@
reload_rendering()
update_vision()
+ //! FUCK FUCK FUCK FUCK FUCK FUCK FUCK
+ for(var/key in species.sprite_accessory_defaults)
+ var/datum/sprite_accessory/accessory = species.sprite_accessory_defaults[key]
+ var/datum/sprite_accessory/existing = get_sprite_accessory(key)
+ if(existing && old_species?.sprite_accessory_defaults?[key] != existing)
+ continue
+ switch(key)
+ if(SPRITE_ACCESSORY_SLOT_EARS)
+ ear_style = accessory
+ r_ears = r_skin
+ g_ears = g_skin
+ b_ears = b_skin
+ if(SPRITE_ACCESSORY_SLOT_FACEHAIR)
+ if(SPRITE_ACCESSORY_SLOT_HAIR)
+ if(SPRITE_ACCESSORY_SLOT_HORNS)
+ horn_style = accessory
+ r_horn = r_skin
+ g_horn = g_skin
+ b_horn = b_skin
+ if(SPRITE_ACCESSORY_SLOT_TAIL)
+ tail_style = accessory
+ r_tail = r_skin
+ g_tail = g_skin
+ b_tail = b_skin
+ if(SPRITE_ACCESSORY_SLOT_WINGS)
+ wing_style = accessory
+ r_wing = r_skin
+ g_wing = g_skin
+ b_wing = b_skin
+
// skip the rest
if(skip)
return
@@ -1260,6 +1278,7 @@
W.add_fingerprint(src)
/mob/living/carbon/human/emp_act(severity)
+ . = ..()
if(isSynthetic())
switch(severity)
if(1)
@@ -1279,8 +1298,6 @@
to_chat(src, "*BZZZT*")
to_chat(src, "Warning: Electromagnetic pulse detected.")
to_chat(src, "Warning: Navigation systems offline. Restarting...")
- ..()
-
/mob/living/carbon/human/can_inject(var/mob/user, var/error_msg, var/target_zone, var/ignore_thickness = FALSE)
. = 1
@@ -1586,21 +1603,6 @@
msg += get_display_species()
return msg
-//Crazy alternate human stuff
-/mob/living/carbon/human/proc/init_world_bender_hud()
- var/animal = pick("cow","chicken_brown", "chicken_black", "chicken_white", "chick", "mouse_brown", "mouse_gray", "mouse_white", "lizard", "cat2", "goose", "penguin")
- var/image/img = image('icons/mob/animal.dmi', src, animal)
- // hud refactor when
- img.override = TRUE
- LAZYINITLIST(hud_list)
- hud_list[WORLD_BENDER_ANIMAL_HUD] = img
- var/datum/atom_hud/world_bender/animals/A = GLOB.huds[WORLD_BENDER_HUD_ANIMALS]
- A.add_to_hud(src)
-
-/mob/living/carbon/human/proc/cleanup_world_bender_hud()
- var/datum/atom_hud/world_bender/animals/A = GLOB.huds[WORLD_BENDER_HUD_ANIMALS]
- A.remove_from_hud(src)
-
/mob/living/carbon/human/get_mob_riding_slots()
return list(back, head, wear_suit)
diff --git a/code/modules/mob/living/carbon/human/human_damage.dm b/code/modules/mob/living/carbon/human/human_damage.dm
index 7a74efb5c0c0..7e6078e372d8 100644
--- a/code/modules/mob/living/carbon/human/human_damage.dm
+++ b/code/modules/mob/living/carbon/human/human_damage.dm
@@ -292,7 +292,7 @@
if(!parts.len) return
var/obj/item/organ/external/picked = pick(parts)
if(picked.heal_damage(brute,burn))
- UpdateDamageIcon()
+ update_damage_overlay()
update_health()
@@ -316,7 +316,7 @@
parts -= picked
update_health()
if(update)
- UpdateDamageIcon()
+ update_damage_overlay()
////////////////////////////////////////////
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index e400818df377..b87a9859e57f 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -33,54 +33,57 @@
/// Throws byond:tm: errors if placed in human/emote, but not here.
var/spam_flag = FALSE
- hud_possible = list(
- LIFE_HUD,
- BIOLOGY_HUD,
- ID_HUD,
- WANTED_HUD,
- IMPLOYAL_HUD,
- IMPTRACK_HUD,
- IMPCHEM_HUD,
- ANTAG_HUD,
+ atom_huds_to_initialize = list(
+ /datum/atom_hud_provider/medical_biology,
+ /datum/atom_hud_provider/medical_health,
+ /datum/atom_hud_provider/security_implant,
+ /datum/atom_hud_provider/security_job,
+ /datum/atom_hud_provider/security_status,
+ /datum/atom_hud_provider/special_role,
+ /datum/atom_hud_provider/overriding/world_bender_animals,
)
//! Buckling - For riding.dm
buckle_allowed = TRUE
buckle_flags = BUCKLING_NO_DEFAULT_BUCKLE // Custom procs handle that.
-//! ## Hair colour and style
+ //! LEGACY, TODO TO REMOVE
+ /// current head overlay pixel y offset
+ var/head_spriteacc_offset = 0
+
+ //* Hair colour and style
var/h_style = "Bald"
var/r_hair = 0
var/g_hair = 0
var/b_hair = 0
-//! ## Hair gradients
+ //* Hair gradients
var/grad_style = "None"
var/r_grad = 0
var/g_grad = 0
var/b_grad = 0
-//! ## Facial hair colour and style
+ //* Facial hair colour and style
var/f_style = "Shaved"
var/r_facial = 0
var/g_facial = 0
var/b_facial = 0
-//! ## Eye colour
+ //* Eye colour
var/r_eyes = 0
var/g_eyes = 0
var/b_eyes = 0
/// Skin tone
var/s_tone = 0
-//! ## Skin colour
+ //* Skin colour
/// Skin flag
var/skin_state = SKIN_NORMAL
var/r_skin = 238 // TODO: Set defaults for other races.
var/g_skin = 206
var/b_skin = 179
-//! ## ears, horns, tails, wings and custom species.
+ //* ears, horns, tails, wings and custom species.
var/datum/sprite_accessory/ears/ear_style = null
var/r_ears = 30
var/g_ears = 30
@@ -113,12 +116,16 @@
var/r_tail3 = 30
var/g_tail3 = 30
var/b_tail3 = 30
+ // pain
+ var/legacy_tail_variation
var/datum/sprite_accessory/wing/wing_style = null
var/grad_wingstyle = "None"
var/r_gradwing = 0
var/g_gradwing = 0
var/b_gradwing = 0
+ // pain
+ var/legacy_wing_variation
var/r_wing = 30
var/g_wing = 30
@@ -130,9 +137,6 @@
var/g_wing3 = 30
var/b_wing3 = 30
- var/wagging = 0 //UGH.
- var/flapping = 0
- var/spread = 0
/// What's my status?
var/vantag_pref = VANTAG_NONE
// todo: REOMVE THIS FOR SPECIES VAR CHANGES
@@ -147,7 +151,7 @@
var/custom_species
-//! ## Synth colors
+ //* Synth colors
/// Lets normally uncolorable synth parts be colorable.
var/synth_color = 0
// Used with synth_color to color synth parts that normaly can't be colored.
@@ -183,7 +187,7 @@
/// Which PDA type the player has chosen.
var/pdachoice = 1
-//! ## General information
+ //* General information
var/home_system = ""
var/citizenship = ""
var/personal_faction = ""
@@ -191,7 +195,7 @@
var/antag_faction = ""
var/antag_vis = ""
-//! ## Equipment slots
+ //* Equipment slots
var/obj/item/wear_suit = null
var/obj/item/w_uniform = null
var/obj/item/shoes = null
diff --git a/code/modules/mob/living/carbon/human/human_organs.dm b/code/modules/mob/living/carbon/human/human_organs.dm
index 17e661de0b5a..4d9e0db3c5fa 100644
--- a/code/modules/mob/living/carbon/human/human_organs.dm
+++ b/code/modules/mob/living/carbon/human/human_organs.dm
@@ -105,11 +105,12 @@
// Canes and crutches help you stand (if the latter is ever added)
// One cane mitigates a broken leg+foot, or a missing foot.
- // Two canes are needed for a lost leg. If you are missing both legs, canes aren't gonna help you.
+ var/cane_help=4
if (l_hand && istype(l_hand, /obj/item/cane))
- stance_damage -= 2
+ stance_damage -= cane_help
+ cane_help-=1
if (r_hand && istype(r_hand, /obj/item/cane))
- stance_damage -= 2
+ stance_damage -= cane_help
// standing is poor
if(stance_damage >= 4 || (stance_damage >= 2 && prob(5)))
diff --git a/code/modules/mob/living/carbon/human/human_powers.dm b/code/modules/mob/living/carbon/human/human_powers.dm
index 3eeafaaa6b1f..292a64e66136 100644
--- a/code/modules/mob/living/carbon/human/human_powers.dm
+++ b/code/modules/mob/living/carbon/human/human_powers.dm
@@ -398,7 +398,7 @@
return
hiding_tail = !hiding_tail
to_chat(usr, SPAN_SMALLNOTICE("You are now [hiding_tail ? "hiding" : "showing"] your tail."))
- update_tail_showing()
+ render_spriteacc_tail()
/mob/living/carbon/human/proc/hide_wings()
set name = "Toggle Hide Wings"
@@ -409,7 +409,7 @@
return
hiding_wings = !hiding_wings
to_chat(usr, SPAN_SMALLNOTICE("You are now [hiding_wings ? "hiding" : "showing"] your wings."))
- update_wing_showing()
+ render_spriteacc_wings()
/mob/living/carbon/human/proc/hide_horns()
set name = "Toggle Hide Horns"
diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm
index 32d7e8a521be..8c67c1da4092 100644
--- a/code/modules/mob/living/carbon/human/inventory.dm
+++ b/code/modules/mob/living/carbon/human/inventory.dm
@@ -266,7 +266,7 @@
. = ..()
if(!.)
return
- var/datum/inventory_slot_meta/slot_meta = resolve_inventory_slot_meta(id)
+ var/datum/inventory_slot/slot_meta = resolve_inventory_slot(id)
if(!slot_meta)
return FALSE
return !(slot_meta.inventory_slot_flags & INV_SLOT_IS_INVENTORY) || !species || (id in species.hud.gear)
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index 8b77562499d7..e7b3034586a0 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -775,6 +775,9 @@
MOB_BODYTEMP_EQUALIZATION_MAX_UNFAVORABLE \
)
+ if(istype(loc, /obj/machinery/atmospherics/component/unary/cryo_cell))
+ adjust = min(adjust, 2) // snowflake patch for cryo i fucking hate this aoguhwagoehs8ry0u34ajwioer
+
if(is_stabilizing)
adjust_bodytemperature(adjust)
else if(difference < 0)
diff --git a/code/modules/mob/living/carbon/human/movement.dm b/code/modules/mob/living/carbon/human/movement.dm
index 9ed5dcd925eb..d57738dc5b29 100644
--- a/code/modules/mob/living/carbon/human/movement.dm
+++ b/code/modules/mob/living/carbon/human/movement.dm
@@ -33,7 +33,7 @@
tally += (halloss / 45) //halloss shouldn't slow you down if you can't even feel it
var/hungry = (500 - nutrition)/5 // So overeat would be 100 and default level would be 80
- if (hungry >= 70)
+ if (m_intent == MOVE_INTENT_RUN && hungry >= 70)//You can walk while hungry, but you cant run so fast while hungry
tally += hungry/50
if(reagents.has_reagent("numbenzyme"))
diff --git a/code/modules/mob/living/carbon/human/rendering.dm b/code/modules/mob/living/carbon/human/rendering.dm
new file mode 100644
index 000000000000..1e601df9b654
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/rendering.dm
@@ -0,0 +1,862 @@
+/mob/living/carbon/human/flatten_standing_overlays()
+ . = ..()
+ render_spriteacc_ears(TRUE)
+ render_spriteacc_facehair(TRUE)
+ render_spriteacc_hair(TRUE)
+ render_spriteacc_tail(TRUE)
+ render_spriteacc_wings(TRUE)
+ render_spriteacc_horns(TRUE)
+ reapply_standing_overlays()
+
+/mob/living/carbon/human/proc/render_spriteacc_ears(flatten)
+ if((head?.inv_hide_flags | wear_mask?.inv_hide_flags) & (BLOCKHEADHAIR | BLOCKHAIR))
+ remove_standing_overlay(HUMAN_OVERLAY_EARS)
+ return
+ var/datum/sprite_accessory/rendering = get_sprite_accessory(SPRITE_ACCESSORY_SLOT_EARS)
+ if(isnull(rendering))
+ remove_standing_overlay(HUMAN_OVERLAY_EARS)
+ return
+ var/obj/item/organ/external/head/head_organ = get_organ(BP_HEAD)
+ if(!head_organ || head_organ.is_stump())
+ remove_standing_overlay(HUMAN_OVERLAY_EARS)
+ return
+ var/rendered = rendering.render(
+ src,
+ list(
+ rgb(r_ears, g_ears, b_ears),
+ rgb(r_ears2, g_ears2, b_ears2),
+ rgb(r_ears3, g_ears3, b_ears3),
+ ),
+ HUMAN_LAYER_SPRITEACC_EARS_FRONT,
+ HUMAN_LAYER_SPRITEACC_EARS_BEHIND,
+ 0, // TODO
+ null,
+ flattened = flatten,
+ )
+ // todo: this is awful
+ if(islist(rendered))
+ for(var/image/I as anything in rendered)
+ I.pixel_y += head_spriteacc_offset
+ I.alpha = head_organ.hair_opacity
+ else
+ var/image/I = rendered
+ I.pixel_y += head_spriteacc_offset
+ I.alpha = head_organ.hair_opacity
+
+ . = rendered
+ set_standing_overlay(HUMAN_OVERLAY_EARS, rendered)
+
+/mob/living/carbon/human/proc/render_spriteacc_horns(flatten)
+ if((head?.inv_hide_flags | wear_mask?.inv_hide_flags) & (BLOCKHEADHAIR | BLOCKHAIR))
+ remove_standing_overlay(HUMAN_OVERLAY_HORNS)
+ return
+ var/datum/sprite_accessory/rendering = get_sprite_accessory(SPRITE_ACCESSORY_SLOT_HORNS)
+ if(isnull(rendering))
+ remove_standing_overlay(HUMAN_OVERLAY_HORNS)
+ return
+ if(hiding_horns && rendering.can_be_hidden)
+ //! legacy code
+ remove_standing_overlay(HUMAN_OVERLAY_HORNS)
+ return
+ var/obj/item/organ/external/head/head_organ = get_organ(BP_HEAD)
+ if(!head_organ || head_organ.is_stump())
+ remove_standing_overlay(HUMAN_OVERLAY_HORNS)
+ return
+ var/rendered = rendering.render(
+ src,
+ list(
+ rgb(r_horn, g_horn, b_horn),
+ rgb(r_horn2, g_horn2, b_horn2),
+ rgb(r_horn3, g_horn3, b_horn3),
+ ),
+ HUMAN_LAYER_SPRITEACC_HORNS_FRONT,
+ HUMAN_LAYER_SPRITEACC_HORNS_BEHIND,
+ 0, // TODO
+ null,
+ flattened = flatten,
+ )
+ // todo: this is awful
+ if(islist(rendered))
+ for(var/image/I as anything in rendered)
+ I.pixel_y += head_spriteacc_offset
+ I.alpha = head_organ.hair_opacity
+ else
+ var/image/I = rendered
+ I.pixel_y += head_spriteacc_offset
+ I.alpha = head_organ.hair_opacity
+
+ . = rendered
+ set_standing_overlay(HUMAN_OVERLAY_HORNS, rendered)
+
+/mob/living/carbon/human/proc/render_spriteacc_facehair(flatten)
+ if((head?.inv_hide_flags | wear_mask?.inv_hide_flags) & BLOCKHAIR)
+ remove_standing_overlay(HUMAN_OVERLAY_FACEHAIR)
+ return
+ var/datum/sprite_accessory/rendering = get_sprite_accessory(SPRITE_ACCESSORY_SLOT_FACEHAIR)
+ if(isnull(rendering))
+ remove_standing_overlay(HUMAN_OVERLAY_FACEHAIR)
+ return
+ var/obj/item/organ/external/head/head_organ = get_organ(BP_HEAD)
+ if(!head_organ || head_organ.is_stump())
+ remove_standing_overlay(HUMAN_OVERLAY_FACEHAIR)
+ return
+ var/rendered = rendering.render(
+ src,
+ list(
+ rgb(r_facial, g_facial, b_facial),
+ ),
+ HUMAN_LAYER_SPRITEACC_FACEHAIR_FRONT,
+ HUMAN_LAYER_SPRITEACC_FACEHAIR_BEHIND,
+ 0, // TODO
+ null,
+ flattened = flatten,
+ )
+ // todo: this is awful
+ if(islist(rendered))
+ for(var/image/I as anything in rendered)
+ I.pixel_y += head_spriteacc_offset
+ I.alpha = head_organ.hair_opacity
+ else
+ var/image/I = rendered
+ I.pixel_y += head_spriteacc_offset
+ I.alpha = head_organ.hair_opacity
+
+ . = rendered
+ set_standing_overlay(HUMAN_OVERLAY_FACEHAIR, rendered)
+
+/mob/living/carbon/human/proc/render_spriteacc_hair(flatten)
+ if((head?.inv_hide_flags | wear_mask?.inv_hide_flags) & (BLOCKHEADHAIR | BLOCKHAIR))
+ remove_standing_overlay(HUMAN_OVERLAY_HAIR)
+ return
+ var/datum/sprite_accessory/rendering = get_sprite_accessory(SPRITE_ACCESSORY_SLOT_HAIR)
+ if(isnull(rendering))
+ remove_standing_overlay(HUMAN_OVERLAY_HAIR)
+ return
+ var/obj/item/organ/external/head/head_organ = get_organ(BP_HEAD)
+ if(!head_organ || head_organ.is_stump())
+ remove_standing_overlay(HUMAN_OVERLAY_HAIR)
+ return
+ // todo: what is this for?
+ // if(head && (head.inv_hide_flags & BLOCKHEADHAIR))
+ // if(!(hair_style.hair_flags & HAIR_VERY_SHORT))
+ // hair_style = GLOB.legacy_hair_lookup["Short Hair"]
+ var/rendered = rendering.render(
+ src,
+ list(
+ rgb(r_hair, g_hair, b_hair),
+ ),
+ HUMAN_LAYER_SPRITEACC_HAIR_FRONT,
+ HUMAN_LAYER_SPRITEACC_HAIR_BEHIND,
+ 0, // TODO
+ null,
+ flattened = flatten,
+ )
+ // todo: this is awful
+ if(islist(rendered))
+ for(var/image/I as anything in rendered)
+ I.pixel_y += head_spriteacc_offset
+ I.alpha = head_organ.hair_opacity
+ else
+ var/image/I = rendered
+ I.pixel_y += head_spriteacc_offset
+ I.alpha = head_organ.hair_opacity
+
+ . = rendered
+ set_standing_overlay(HUMAN_OVERLAY_HAIR, rendered)
+
+/mob/living/carbon/human/proc/render_spriteacc_wings(flatten)
+ var/datum/sprite_accessory/wing/rendering = get_sprite_accessory(SPRITE_ACCESSORY_SLOT_WINGS)
+ if(isnull(rendering))
+ remove_standing_overlay(HUMAN_OVERLAY_WINGS)
+ return
+ if(hiding_wings && rendering.can_be_hidden)
+ //! legacy code
+ remove_standing_overlay(HUMAN_OVERLAY_WINGS)
+ return
+ var/rendered = rendering.render(
+ src,
+ list(
+ rgb(r_wing, g_wing, b_wing),
+ rgb(r_wing2, g_wing2, b_wing2),
+ rgb(r_wing3, g_wing3, b_wing3),
+ ),
+ HUMAN_LAYER_SPRITEACC_WINGS_FRONT,
+ HUMAN_LAYER_SPRITEACC_WINGS_BEHIND,
+ 0, // TODO
+ null,
+ legacy_wing_variation,
+ flattened = flatten,
+ )
+
+ . = rendered
+ set_standing_overlay(HUMAN_OVERLAY_WINGS, rendered)
+
+
+/mob/living/carbon/human/proc/render_spriteacc_tail(flatten)
+ var/datum/sprite_accessory/tail/rendering = get_sprite_accessory(SPRITE_ACCESSORY_SLOT_TAIL)
+ if(isnull(rendering))
+ remove_standing_overlay(HUMAN_OVERLAY_TAIL)
+ return
+ if(hiding_tail && rendering.can_be_hidden)
+ //! legacy code
+ remove_standing_overlay(HUMAN_OVERLAY_TAIL)
+ return
+ var/rendered = rendering.render(
+ src,
+ list(
+ rgb(r_tail, g_tail, b_tail),
+ rgb(r_tail2, g_tail2, b_tail2),
+ rgb(r_tail3, g_tail3, b_tail3),
+ ),
+ HUMAN_LAYER_SPRITEACC_TAIL_FRONT,
+ HUMAN_LAYER_SPRITEACC_TAIL_BEHIND,
+ 0, // TODO,
+ null,
+ legacy_tail_variation,
+ flattened = flatten,
+ )
+
+ . = rendered
+ set_standing_overlay(HUMAN_OVERLAY_TAIL, rendered)
+
+/mob/living/carbon/human/proc/set_wing_variation(variation)
+ var/datum/sprite_accessory/wing/rendering = get_sprite_accessory(SPRITE_ACCESSORY_SLOT_WINGS)
+ if(!rendering?.variations?[variation] && !isnull(variation))
+ return
+ legacy_wing_variation = variation
+ render_spriteacc_wings()
+
+/mob/living/carbon/human/proc/set_tail_variation(variation)
+ var/datum/sprite_accessory/tail/rendering = get_sprite_accessory(SPRITE_ACCESSORY_SLOT_TAIL)
+ // todo: legacy sihtcode lol
+ if(istype(rendering) && rendering.ani_state && variation == SPRITE_ACCESSORY_VARIATION_WAGGING)
+ else
+ if(!rendering?.variations?[variation] && !isnull(variation))
+ return
+ legacy_tail_variation = variation
+ render_spriteacc_tail()
+
+/mob/living/carbon/proc/get_sprite_accessory(slot)
+ return
+
+/mob/living/carbon/human/get_sprite_accessory(slot)
+ switch(slot)
+ if(SPRITE_ACCESSORY_SLOT_TAIL)
+ // . = GLOB.sprite_accessory_tails[tail_style]
+ . = tail_style
+ if(SPRITE_ACCESSORY_SLOT_HAIR)
+ . = GLOB.legacy_hair_lookup[h_style]
+ if(SPRITE_ACCESSORY_SLOT_FACEHAIR)
+ . = GLOB.legacy_facial_hair_lookup[f_style]
+ if(SPRITE_ACCESSORY_SLOT_WINGS)
+ // . = GLOB.sprite_accessory_wings[wing_style]
+ . = wing_style
+ if(SPRITE_ACCESSORY_SLOT_HORNS)
+ // . = GLOB.sprite_accessory_ears[horn_style]
+ . = horn_style
+ if(SPRITE_ACCESSORY_SLOT_EARS)
+ // . = GLOB.sprite_accessory_ears[ear_style]
+ . = ear_style
+ if(slot == SPRITE_ACCESSORY_SLOT_TAIL && !.)
+ var/datum/robolimb/limb = isSynthetic()
+ if(istype(limb))
+ . = limb?.legacy_includes_tail
+ if(.)
+ //! :skull_emoji:
+ // todo: better defaulting system, this is horrible.
+ r_tail = r_skin
+ g_tail = g_skin
+ b_tail = b_skin
+
+/mob/living/carbon/proc/render_sprite_accessory(slot)
+ return
+
+/mob/living/carbon/human/render_sprite_accessory(slot)
+ switch(slot)
+ if(SPRITE_ACCESSORY_SLOT_TAIL)
+ render_spriteacc_tail()
+ if(SPRITE_ACCESSORY_SLOT_HAIR)
+ render_spriteacc_hair()
+ if(SPRITE_ACCESSORY_SLOT_FACEHAIR)
+ render_spriteacc_facehair()
+ if(SPRITE_ACCESSORY_SLOT_WINGS)
+ render_spriteacc_wings()
+ if(SPRITE_ACCESSORY_SLOT_HORNS)
+ render_spriteacc_horns()
+ if(SPRITE_ACCESSORY_SLOT_EARS)
+ render_spriteacc_ears()
+
+/mob/living/carbon/proc/set_sprite_accessory_variation(slot, variation)
+ return
+
+/mob/living/carbon/human/set_sprite_accessory_variation(slot, variation)
+ switch(slot)
+ if(SPRITE_ACCESSORY_SLOT_HAIR)
+ if(SPRITE_ACCESSORY_SLOT_FACEHAIR)
+ if(SPRITE_ACCESSORY_SLOT_HORNS)
+ if(SPRITE_ACCESSORY_SLOT_EARS)
+ if(SPRITE_ACCESSORY_SLOT_WINGS)
+ set_wing_variation(variation)
+ if(SPRITE_ACCESSORY_SLOT_TAIL)
+ set_tail_variation(variation)
+
+/mob/living/carbon/proc/has_sprite_accessory_variation(slot, variation)
+ return
+
+/mob/living/carbon/human/has_sprite_accessory_variation(slot, variation)
+ var/datum/sprite_accessory/resolved = get_sprite_accessory(slot)
+ if(istype(resolved, /datum/sprite_accessory/tail) && variation == SPRITE_ACCESSORY_VARIATION_WAGGING)
+ var/datum/sprite_accessory/tail/tail = resolved
+ if(tail.ani_state)
+ return TRUE
+ return (resolved?.variations?[variation])? TRUE : FALSE
+
+/mob/living/carbon/proc/get_sprite_accessory_variation(slot)
+ return
+
+/mob/living/carbon/human/get_sprite_accessory_variation(slot)
+ var/datum/sprite_accessory/resolved = get_sprite_accessory(slot)
+ var/variation
+ switch(slot)
+ if(SPRITE_ACCESSORY_SLOT_HAIR)
+ if(SPRITE_ACCESSORY_SLOT_FACEHAIR)
+ if(SPRITE_ACCESSORY_SLOT_HORNS)
+ if(SPRITE_ACCESSORY_SLOT_EARS)
+ if(SPRITE_ACCESSORY_SLOT_TAIL)
+ variation = legacy_tail_variation
+ if(SPRITE_ACCESSORY_SLOT_WINGS)
+ variation = legacy_wing_variation
+ if(istype(resolved, /datum/sprite_accessory/tail) && variation == SPRITE_ACCESSORY_VARIATION_WAGGING)
+ var/datum/sprite_accessory/tail/tail = resolved
+ if(tail.ani_state)
+ return SPRITE_ACCESSORY_VARIATION_WAGGING
+ return (resolved.variations?[variation])? variation : null
+
+//! old code below
+
+//Not really once, since BYOND can't do that.
+//Update this if the ability to flick() images or make looping animation start at the first frame is ever added.
+//You can sort of flick images now with flick_overlay -Aro
+/mob/living/carbon/human/proc/animate_tail_once()
+ var/datum/sprite_accessory/accessory = get_sprite_accessory(SPRITE_ACCESSORY_SLOT_TAIL)
+ var/time = accessory.variation_animation_times?[SPRITE_ACCESSORY_VARIATION_WAGGING] || accessory.variation_animation_time
+ if(!set_sprite_accessory_variation(SPRITE_ACCESSORY_SLOT_TAIL, SPRITE_ACCESSORY_VARIATION_WAGGING))
+ return
+ addtimer(CALLBACK(src, PROC_REF(animate_tail_reset)), time, TIMER_CLIENT_TIME)
+
+/mob/living/carbon/human/proc/toggle_tail_vr(var/setting,var/message = 0)
+ if(!has_sprite_accessory_variation(SPRITE_ACCESSORY_SLOT_TAIL, SPRITE_ACCESSORY_VARIATION_WAGGING))
+ if(message)
+ to_chat(src, "You don't have a tail that supports this.")
+ return 0
+
+ if(!set_sprite_accessory_variation(
+ SPRITE_ACCESSORY_SLOT_TAIL,
+ get_sprite_accessory_variation(SPRITE_ACCESSORY_SLOT_TAIL) == SPRITE_ACCESSORY_VARIATION_WAGGING? null : SPRITE_ACCESSORY_VARIATION_WAGGING,
+ ))
+ return 0
+ return 1
+
+/mob/living/carbon/human/proc/animate_tail_start()
+ set_sprite_accessory_variation(SPRITE_ACCESSORY_SLOT_TAIL, SPRITE_ACCESSORY_VARIATION_WAGGING)
+ // set_tail_state("[species.get_tail(src)]_slow[rand(0,9)]")
+
+/mob/living/carbon/human/proc/animate_tail_fast()
+ set_sprite_accessory_variation(SPRITE_ACCESSORY_SLOT_TAIL, SPRITE_ACCESSORY_VARIATION_WAGGING)
+ // set_tail_state("[species.get_tail(src)]_loop[rand(0,9)]")
+
+/mob/living/carbon/human/proc/animate_tail_reset()
+ set_sprite_accessory_variation(SPRITE_ACCESSORY_SLOT_TAIL, null)
+ // if(stat != DEAD)
+ // set_tail_state("[species.get_tail(src)]_idle[rand(0,9)]")
+ // else
+ // set_tail_state("[species.get_tail(src)]_static")
+ // toggle_tail_vr(FALSE) // So tails stop when someone dies. TODO - Fix this hack ~Leshana
+
+/mob/living/carbon/human/proc/animate_tail_stop()
+ set_sprite_accessory_variation(SPRITE_ACCESSORY_SLOT_TAIL, null)
+
+/mob/living/carbon/human/proc/toggle_wing_vr(var/setting,var/message = 0)
+ if(!has_sprite_accessory_variation(SPRITE_ACCESSORY_SLOT_WINGS, SPRITE_ACCESSORY_VARIATION_FLAPPING))
+ if(message)
+ to_chat(src, "You don't have wings that support this.")
+ return 0
+ if(!set_sprite_accessory_variation(
+ SPRITE_ACCESSORY_SLOT_WINGS,
+ get_sprite_accessory_variation(SPRITE_ACCESSORY_SLOT_WINGS) == SPRITE_ACCESSORY_VARIATION_FLAPPING? null : SPRITE_ACCESSORY_VARIATION_FLAPPING,
+ ))
+ return 0
+ return 1
+
+/mob/living/carbon/human/proc/toggle_wing_spread(var/folded,var/message = 0)
+ if(!has_sprite_accessory_variation(SPRITE_ACCESSORY_SLOT_WINGS, SPRITE_ACCESSORY_VARIATION_SPREAD))
+ if(message)
+ to_chat(src, "You don't have wings!")
+ return 0
+ if(!set_sprite_accessory_variation(
+ SPRITE_ACCESSORY_SLOT_WINGS,
+ get_sprite_accessory_variation(SPRITE_ACCESSORY_SLOT_WINGS) == SPRITE_ACCESSORY_VARIATION_SPREAD? null : SPRITE_ACCESSORY_VARIATION_SPREAD,
+ ))
+ return 0
+ return 1
+
+//? FUCK FUCK FUCK FUCK FUCK-
+
+/mob/living/carbon/human/update_hair()
+ update_eyes() //Pirated out of here, for glowing eyes.
+
+ var/obj/item/organ/external/head/head_organ = get_organ(BP_HEAD)
+ if(istype(head_organ,/obj/item/organ/external/head/vr))
+ var/obj/item/organ/external/head/vr/head_organ_vr = head_organ
+ head_spriteacc_offset = head_organ_vr.head_offset
+ else
+ head_spriteacc_offset = 0
+
+ render_spriteacc_ears()
+ render_spriteacc_horns()
+ render_spriteacc_hair()
+ render_spriteacc_facehair()
+
+//? Body
+
+/mob/living/carbon/human/update_damage_overlay()
+ // first check whether something actually changed about damage appearance
+ var/damage_appearance = ""
+
+ for(var/obj/item/organ/external/O in organs)
+ if(isnull(O) || O.is_stump())
+ continue
+ damage_appearance += O.damage_state
+
+ if(damage_appearance == previous_damage_appearance)
+ // nothing to do here
+ return
+
+ previous_damage_appearance = damage_appearance
+
+ var/image/standing_image = image(icon = species.damage_overlays, icon_state = "00", layer = HUMAN_LAYER_DAMAGE)
+
+ // blend the individual damage states with our icons
+ for(var/obj/item/organ/external/O in organs)
+ if(isnull(O) || O.is_stump())
+ continue
+
+ O.update_icon()
+ if(O.damage_state == "00") continue
+ var/icon/DI
+ var/cache_index = "[O.damage_state]/[O.icon_name]/[species.get_blood_colour(src)]/[species.get_bodytype_legacy(src)]"
+ if(GLOB.damage_icon_parts[cache_index] == null)
+ DI = icon(species.get_damage_overlays(src), O.damage_state) // the damage icon for whole human
+ DI.Blend(icon(species.get_damage_mask(src), O.icon_name), ICON_MULTIPLY) // mask with this organ's pixels
+ DI.Blend(species.get_blood_colour(src), ICON_MULTIPLY)
+ GLOB.damage_icon_parts[cache_index] = DI
+ else
+ DI = GLOB.damage_icon_parts[cache_index]
+
+ standing_image.add_overlay(DI)
+
+ set_standing_overlay(HUMAN_OVERLAY_DAMAGE, standing_image)
+
+//BASE MOB SPRITE
+/mob/living/carbon/human/update_icons_body()
+ var/husk_color_mod = rgb(96,88,80)
+ var/hulk_color_mod = rgb(48,224,40)
+
+ var/husk = (MUTATION_HUSK in src.mutations)
+ var/fat = (MUTATION_FAT in src.mutations)
+ var/hulk = (MUTATION_HULK in src.mutations)
+ var/skeleton = (MUTATION_SKELETON in src.mutations)
+
+ robolimb_count = 0 //TODO, here, really tho?
+ robobody_count = 0
+
+ //CACHING: Generate an index key from visible bodyparts.
+ //0 = destroyed, 1 = normal, 2 = robotic, 3 = necrotic.
+
+ //Create a new, blank icon for our mob to use.
+ var/icon/stand_icon = new(species.icon_template ? species.icon_template : 'icons/mob/human.dmi', icon_state = "blank")
+
+ var/g = gender == FEMALE ? "f" : "m"
+ /* This was the prior code before the above line. It was faulty and has been commented out.
+ var/g = "male"
+ if(gender == FEMALE)
+ g = "female"
+ */
+
+ var/icon_key = "[species.get_race_key(src)][s_base][g][s_tone][r_skin][g_skin][b_skin]"
+ if(lip_style)
+ icon_key += "[lip_style]"
+ else
+ icon_key += "nolips"
+ var/obj/item/organ/internal/eyes/eyes = internal_organs_by_name[O_EYES]
+ if(eyes)
+ icon_key += "[rgb(eyes.eye_colour[1], eyes.eye_colour[2], eyes.eye_colour[3])]"
+ else
+ icon_key += "[r_eyes], [g_eyes], [b_eyes]"
+
+ var/obj/item/organ/external/head/head = organs_by_name[BP_HEAD]
+ if(head)
+ if(!istype(head, /obj/item/organ/external/stump))
+ icon_key += "[head.eye_icon]"
+ for(var/organ_tag in species.has_limbs)
+ var/obj/item/organ/external/part = organs_by_name[organ_tag]
+ // Allowing tails to prevent bodyparts rendering, granting more spriter freedom for taur/digitigrade stuff.
+ if(isnull(part) || part.is_stump() || part.is_hidden_by_tail())
+ icon_key += "0"
+ continue
+ if(part)
+ icon_key += "[part.name]"
+ icon_key += "[part.species.get_race_key(part.owner)]"
+ icon_key += "[part.dna.GetUIState(DNA_UI_GENDER)]"
+ icon_key += "[part.s_tone]"
+ if(part.s_col && part.s_col.len >= 3)
+ icon_key += "[rgb(part.s_col[1],part.s_col[2],part.s_col[3])]"
+ if(part.body_hair && part.h_col && part.h_col.len >= 3)
+ icon_key += "[rgb(part.h_col[1],part.h_col[2],part.h_col[3])]"
+ if(species.color_force_greyscale)
+ icon_key += "_ags"
+ if(species.color_mult)
+ icon_key += "[ICON_MULTIPLY]"
+ else
+ icon_key += "[ICON_ADD]"
+ else
+ icon_key += "#000000"
+ for(var/M in part.markings)
+ icon_key += "[M][part.markings[M]["color"]]"
+
+ if(part.robotic >= ORGAN_ROBOT)
+ icon_key += "2[part.model ? "-[part.model]": ""]"
+ robolimb_count++
+ if((part.robotic == ORGAN_ROBOT || part.robotic == ORGAN_LIFELIKE || part.robotic == ORGAN_NANOFORM) && (part.organ_tag == BP_HEAD || part.organ_tag == BP_TORSO || part.organ_tag == BP_GROIN))
+ robobody_count ++
+ else if(part.status & ORGAN_DEAD)
+ icon_key += "3"
+ else
+ icon_key += "1"
+ if(part.transparent)
+ icon_key += "_t"
+
+ icon_key = "[icon_key][husk ? 1 : 0][fat ? 1 : 0][hulk ? 1 : 0][skeleton ? 1 : 0]"
+
+ var/icon/base_icon
+ if(GLOB.human_icon_cache[icon_key])
+ base_icon = GLOB.human_icon_cache[icon_key]
+ else
+ //BEGIN CACHED ICON GENERATION.
+ var/obj/item/organ/external/chest = get_organ(BP_TORSO)
+ base_icon = chest.get_icon()
+
+ for(var/obj/item/organ/external/part in organs)
+ if(isnull(part) || part.is_stump() || part.is_hidden_by_tail())
+ continue
+ var/icon/temp = part.get_icon(skeleton)
+ //That part makes left and right legs drawn topmost and lowermost when human looks WEST or EAST
+ //And no change in rendering for other parts (they icon_position is 0, so goes to 'else' part)
+ if(part.icon_position & (LEFT | RIGHT))
+ var/icon/temp2 = new(species.icon_template ? species.icon_template : 'icons/mob/human.dmi', icon_state = "blank")
+ temp2.Insert(new/icon(temp,dir=NORTH),dir=NORTH)
+ temp2.Insert(new/icon(temp,dir=SOUTH),dir=SOUTH)
+ if(!(part.icon_position & LEFT))
+ temp2.Insert(new/icon(temp,dir=EAST),dir=EAST)
+ if(!(part.icon_position & RIGHT))
+ temp2.Insert(new/icon(temp,dir=WEST),dir=WEST)
+ base_icon.Blend(temp2, ICON_OVERLAY)
+ if(part.icon_position & LEFT)
+ temp2.Insert(new/icon(temp,dir=EAST),dir=EAST)
+ if(part.icon_position & RIGHT)
+ temp2.Insert(new/icon(temp,dir=WEST),dir=WEST)
+ base_icon.Blend(temp2, ICON_UNDERLAY)
+ else if(part.icon_position & UNDER)
+ base_icon.Blend(temp, ICON_UNDERLAY)
+ else
+ base_icon.Blend(temp, ICON_OVERLAY)
+
+ if(!skeleton)
+ if(husk)
+ base_icon.ColorTone(husk_color_mod)
+ else if(hulk)
+ var/list/tone = ReadRGB(hulk_color_mod)
+ base_icon.MapColors(rgb(tone[1],0,0),rgb(0,tone[2],0),rgb(0,0,tone[3]))
+
+ // Handle husk overlay.
+ if(husk)
+ var/husk_icon = species.get_husk_icon(src)
+ if(husk_icon)
+ var/icon/mask = new(base_icon)
+ var/icon/husk_over = new(species.husk_icon,"")
+ mask.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0)
+ husk_over.Blend(mask, ICON_ADD)
+ base_icon.Blend(husk_over, ICON_OVERLAY)
+
+ GLOB.human_icon_cache[icon_key] = base_icon
+
+ //END CACHED ICON GENERATION.
+ stand_icon.Blend(base_icon,ICON_OVERLAY)
+ icon = stand_icon
+
+ //tail
+ render_spriteacc_tail()
+ //wing
+ render_spriteacc_wings()
+
+/mob/living/carbon/human/proc/update_skin()
+ var/image/skin = species.update_skin(src)
+ set_standing_overlay(HUMAN_OVERLAY_SKIN, skin)
+
+/mob/living/carbon/human/proc/update_bloodied()
+ if(!blood_DNA && !feet_blood_DNA)
+ remove_standing_overlay(HUMAN_OVERLAY_BLOOD)
+ return
+
+ var/image/both = image(icon = 'icons/effects/effects.dmi', icon_state = "nothing", layer = HUMAN_LAYER_BLOOD)
+
+ //Bloody hands
+ if(blood_DNA)
+ var/image/bloodsies = image(icon = species.get_blood_mask(src), icon_state = "bloodyhands", layer = HUMAN_LAYER_BLOOD)
+ bloodsies.color = hand_blood_color
+ both.add_overlay(bloodsies)
+
+ //Bloody feet
+ if(feet_blood_DNA)
+ var/image/bloodsies = image(icon = species.get_blood_mask(src), icon_state = "shoeblood", layer = HUMAN_LAYER_BLOOD)
+ bloodsies.color = feet_blood_color
+ both.add_overlay(bloodsies)
+
+ set_standing_overlay(HUMAN_OVERLAY_BLOOD, both)
+
+
+//UNDERWEAR OVERLAY
+/mob/living/carbon/human/proc/update_underwear()
+ if(!(species.species_appearance_flags & HAS_UNDERWEAR))
+ remove_standing_overlay(HUMAN_OVERLAY_UNDERWEAR)
+ return
+ var/list/setting = list()
+ // SHITCODE WARNING
+ // MANUAL UNDERWEAR SORT
+ for(var/key in list(
+ "Underwear, bottom",
+ "Socks",
+ "Underwear, top",
+ "Undershirt",
+ ))
+ var/existing = all_underwear[key]
+ all_underwear -= key
+ if(existing)
+ all_underwear[key] = existing
+ // END
+
+ for(var/category in all_underwear)
+ if(hide_underwear[category])
+ continue
+ var/datum/category_item/underwear/UWI = all_underwear[category]
+ var/image/wear = UWI.generate_image(all_underwear_metadata[category], layer = HUMAN_LAYER_UNDERWEAR)
+ setting += wear
+ set_standing_overlay(HUMAN_OVERLAY_UNDERWEAR, setting)
+
+/mob/living/carbon/human/update_eyes()
+ var/obj/item/organ/internal/eyes/eyes = internal_organs_by_name[O_EYES]
+ if(eyes)
+ eyes.update_colour()
+ update_icons_body()
+
+ //TODO: Probably redo this. I know I wrote it, but...
+
+ //This is ONLY for glowing eyes for now. Boring flat eyes are done by the head's own proc.
+ if(!species.has_glowing_eyes)
+ remove_standing_overlay(HUMAN_OVERLAY_EYES)
+ return
+
+ //Our glowy eyes should be hidden if some equipment hides them.
+ if(!should_have_organ(O_EYES) || (head && (head.inv_hide_flags & BLOCKHAIR)) || (wear_mask && (wear_mask.inv_hide_flags & BLOCKHAIR)))
+ remove_standing_overlay(HUMAN_OVERLAY_EYES)
+ return
+
+ //Get the head, we'll need it later.
+ var/obj/item/organ/external/head/head_organ = get_organ(BP_HEAD)
+ if(!head_organ || head_organ.is_stump() )
+ remove_standing_overlay(HUMAN_OVERLAY_EYES)
+ return
+
+ //The eyes store the color themselves, funny enough.
+ if(!head_organ.eye_icon)
+ remove_standing_overlay(HUMAN_OVERLAY_EYES)
+ return
+
+ var/icon/eyes_icon = new/icon(head_organ.eye_icon_location, head_organ.eye_icon)
+ if(eyes)
+ eyes_icon.Blend(rgb(eyes.eye_colour[1], eyes.eye_colour[2], eyes.eye_colour[3]), ICON_ADD)
+ else
+ eyes_icon.Blend(rgb(128,0,0), ICON_ADD)
+
+ var/image/eyes_image = image(eyes_icon)
+ eyes_image.plane = ABOVE_LIGHTING_PLANE
+
+ set_standing_overlay(HUMAN_OVERLAY_EYES, eyes_image)
+
+/mob/living/carbon/human/update_mutations()
+ if(!LAZYLEN(mutations))
+ remove_standing_overlay(HUMAN_OVERLAY_MUTATIONS)
+ return //No mutations, no icons.
+
+ //TODO: THIS PROC???
+ var/fat
+ if(MUTATION_FAT in mutations)
+ fat = "fat"
+
+ var/image/standing = image(icon = 'icons/effects/genetics.dmi', layer = HUMAN_LAYER_MUTATIONS)
+ var/g = gender == FEMALE ? "f" : "m"
+
+ for(var/datum/gene/gene in dna_genes)
+ if(!gene.block)
+ continue
+ if(gene.is_active(src))
+ var/underlay = gene.OnDrawUnderlays(src,g,fat)
+ if(underlay)
+ standing.underlays += underlay
+
+ for(var/mut in mutations)
+ if(mut == MUTATION_LASER)
+ standing.overlays += "lasereyes_s" //TODO
+
+ set_standing_overlay(HUMAN_OVERLAY_MUTATIONS, standing)
+
+//? Misc
+
+/mob/living/carbon/human/update_modifier_visuals()
+ if(!LAZYLEN(modifiers))
+ remove_standing_overlay(HUMAN_OVERLAY_MODIFIERS)
+ return //No modifiers, no effects.
+
+ var/image/effects = new()
+ for(var/datum/modifier/M in modifiers)
+ if(M.mob_overlay_state)
+ var/image/I = image(icon = 'icons/mob/modifier_effects.dmi', icon_state = M.mob_overlay_state)
+ effects.add_overlay(I) //TODO, this compositing is annoying.
+
+ set_standing_overlay(HUMAN_OVERLAY_MODIFIERS, effects)
+
+/mob/living/carbon/human/update_fire()
+ if(!on_fire)
+ remove_standing_overlay(HUMAN_OVERLAY_FIRE)
+ return
+ set_standing_overlay(
+ HUMAN_OVERLAY_FIRE,
+ image(icon = 'icons/mob/OnFire.dmi', icon_state = get_fire_icon_state(), layer = HUMAN_LAYER_FIRE),
+ )
+
+/mob/living/carbon/human/update_water()
+ var/depth = check_submerged()
+ if(!depth || lying)
+ remove_standing_overlay(HUMAN_OVERLAY_LIQUID)
+ return
+ var/image/applying
+ if(depth < 3)
+ applying = image(icon = 'icons/mob/submerged.dmi', icon_state = "human_swimming_[depth]", layer = HUMAN_LAYER_LIQUID) //TODO: Improve
+ if(depth == 4)
+ applying = image(icon = 'icons/mob/submerged.dmi', icon_state = "hacid_1", layer = HUMAN_LAYER_LIQUID)
+ if(depth == 5)
+ applying = image(icon = 'icons/mob/submerged.dmi', icon_state = "hacid_2", layer = HUMAN_LAYER_LIQUID)
+ if(depth == 6)
+ applying = image(icon = 'icons/mob/submerged.dmi', icon_state = "hblood_1", layer = HUMAN_LAYER_LIQUID)
+ if(depth == 7)
+ applying = image(icon = 'icons/mob/submerged.dmi', icon_state = "hblood_2", layer = HUMAN_LAYER_LIQUID)
+ set_standing_overlay(HUMAN_OVERLAY_LIQUID, applying)
+
+// todo: burn with fire
+/mob/living/carbon/human/update_acidsub()
+ var/depth = check_submerged()
+ if(!depth || lying)
+ remove_standing_overlay(HUMAN_OVERLAY_LIQUID)
+ return
+
+ set_standing_overlay(
+ HUMAN_OVERLAY_LIQUID,
+ image(icon = 'icons/mob/submerged.dmi', icon_state = "hacid_[depth]", layer = HUMAN_LAYER_LIQUID),
+ )
+
+// todo: burn with fire
+/mob/living/carbon/human/update_bloodsub()
+ var/depth = check_submerged()
+ if(!depth || lying)
+ remove_standing_overlay(HUMAN_OVERLAY_LIQUID)
+ return
+
+ set_standing_overlay(
+ HUMAN_OVERLAY_LIQUID,
+ image(icon = 'icons/mob/submerged.dmi', icon_state = "hblood_[depth]", layer = HUMAN_LAYER_LIQUID),
+ )
+
+/mob/living/carbon/human/proc/update_surgery()
+ var/image/total = new
+ for(var/obj/item/organ/external/E in organs)
+ if(E.open)
+ var/image/I = image(icon = 'icons/mob/surgery.dmi', icon_state = "[E.icon_name][round(E.open)]", layer = HUMAN_LAYER_SURGERY)
+ total.add_overlay(I) //TODO: This compositing is annoying
+
+ if(length(total.overlays))
+ set_standing_overlay(HUMAN_OVERLAY_SURGERY, total)
+ else
+ remove_standing_overlay(HUMAN_OVERLAY_SURGERY)
+
+//? Inventory
+
+/mob/living/carbon/human/update_inv_w_uniform()
+ inventory.update_slot_render(SLOT_ID_UNIFORM)
+
+/mob/living/carbon/human/update_inv_wear_id()
+ inventory.update_slot_render(SLOT_ID_WORN_ID)
+
+/mob/living/carbon/human/update_inv_gloves()
+ inventory.update_slot_render(SLOT_ID_GLOVES)
+
+/mob/living/carbon/human/update_inv_glasses()
+ inventory.update_slot_render(SLOT_ID_GLASSES)
+
+/mob/living/carbon/human/update_inv_ears()
+ inventory.update_slot_render(SLOT_ID_LEFT_EAR)
+ inventory.update_slot_render(SLOT_ID_RIGHT_EAR)
+
+/mob/living/carbon/human/update_inv_shoes()
+ inventory.update_slot_render(SLOT_ID_SHOES)
+
+/mob/living/carbon/human/update_inv_s_store()
+ inventory.update_slot_render(SLOT_ID_SUIT_STORAGE)
+
+/mob/living/carbon/human/update_inv_head()
+ inventory.update_slot_render(SLOT_ID_HEAD)
+
+/mob/living/carbon/human/update_inv_belt()
+ inventory.update_slot_render(SLOT_ID_BELT)
+
+/mob/living/carbon/human/update_inv_wear_suit()
+ inventory.update_slot_render(SLOT_ID_SUIT)
+
+/mob/living/carbon/human/update_inv_wear_mask()
+ inventory.update_slot_render(SLOT_ID_MASK)
+
+/mob/living/carbon/human/update_inv_back()
+ inventory.update_slot_render(SLOT_ID_BACK)
+
+/mob/living/carbon/human/update_inv_handcuffed()
+ inventory.update_slot_render(SLOT_ID_HANDCUFFED)
+
+/mob/living/carbon/human/update_inv_legcuffed()
+ inventory.update_slot_render(SLOT_ID_LEGCUFFED)
+
+/mob/living/carbon/human/update_inv_r_hand()
+ if(isnull(r_hand))
+ remove_standing_overlay(HUMAN_OVERLAY_RHAND)
+ return
+ set_standing_overlay(
+ HUMAN_OVERLAY_RHAND,
+ r_hand.render_mob_appearance(src, 2, BODYTYPE_DEFAULT),
+ )
+
+/mob/living/carbon/human/update_inv_l_hand()
+ if(isnull(l_hand))
+ remove_standing_overlay(HUMAN_OVERLAY_LHAND)
+ return
+ set_standing_overlay(
+ HUMAN_OVERLAY_LHAND,
+ l_hand.render_mob_appearance(src, 1, BODYTYPE_DEFAULT),
+ )
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index dad9fe7f973d..7f5ab6620904 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -7,57 +7,7 @@ GLOBAL_LIST_EMPTY(tail_icon_cache)
GLOBAL_LIST_EMPTY(light_overlay_cache)
GLOBAL_LIST_EMPTY(damage_icon_parts)
-////////////////////////////////////////////////////////////////////////////////////////////////
-// # Human Icon Updating System
-//
-// This system takes care of the "icon" for human mobs. Of course humans don't just have a single
-// icon+icon_state, but a combination of dozens of little sprites including including the body,
-// clothing, equipment, in-universe HUD images, etc.
-//
-// # Basic Operation
-// Whenever you do something that should update the on-mob appearance of a worn or held item, You
-// will need to call the relevant update_inv_* proc. All of these are named after the variable they
-// update from. They are defined at the /mob level so you don't even need to cast to carbon/human.
-//
-// The new system leverages SSoverlays to actually add/remove the overlays from mob.overlays
-// Since SSoverlays already manages batching updates to reduce apperance churn etc, we don't need
-// to worry about that. (In short, you can call add/cut overlay as many times as you want, it will
-// only get assigned to the mob once per tick.)
-// As a corrolary, this means users of this system do NOT need to tell the system when you're done
-// making changes.
-//
-// There are also these special cases:
-// update_icons_body() //Handles updating your mob's icon to reflect their gender/race/complexion etc
-// UpdateDamageIcon() //Handles damage overlays for brute/burn damage //(will rename this when I geta round to it) ~Carn
-// update_skin() //Handles updating skin for species that have a skin overlay.
-// update_bloodied() //Handles adding/clearing the blood overlays for hands & feet. Call when bloodied or cleaned.
-// update_underwear() //Handles updating the sprite for underwear.
-// update_hair() //Handles updating your hair and eyes overlay
-// update_mutations() //Handles updating your appearance for certain mutations. e.g MUTATION_TELEKINESIS head-glows
-// update_fire() //Handles overlay from being on fire.
-// update_water() //Handles overlay from being submerged.
-// update_surgery() //Handles overlays from open external organs.
-//
-// # History (i.e. I'm used to the old way, what is different?)
-// You used to have to call update_icons(FALSE) if you planned to make more changes, and call update_icons(TRUE)
-// on the final update. All that is gone, just call update_inv_whatever() and it handles the rest.
-//
-////////////////////////////////////////////////////////////////////////////////////////////////
-
-//Add an entry to overlays, assuming it exists
-/mob/living/carbon/human/proc/apply_layer(cache_index)
- if((. = overlays_standing[cache_index]))
- add_overlay(.)
-
-//Remove an entry from overlays, and from the list
-/mob/living/carbon/human/proc/remove_layer(cache_index)
- var/I = overlays_standing[cache_index]
- if(I)
- cut_overlay(I)
- overlays_standing[cache_index] = null
-
/mob/living/carbon/human
- var/list/overlays_standing[TOTAL_LAYERS]
var/previous_damage_appearance // store what the body last looked like, so we only have to update it if something changed
//UPDATES OVERLAYS FROM OVERLAYS_LYING/OVERLAYS_STANDING
@@ -79,6 +29,23 @@ GLOBAL_LIST_EMPTY(damage_icon_parts)
/mob/living/carbon/human/update_icons_huds()
stack_trace("CANARY: Old human update_icons_huds was called.")
+//TODO: Carbon procs in my human update_icons??
+/mob/living/carbon/human/update_hud() //TODO: do away with this if possible
+ // todo: this is utterly shitcode and fucking stupid ~silicons
+ // todo: the rest of hud code here ain't much better LOL
+ var/list/obj/item/relevant = get_equipped_items(TRUE, TRUE)
+ if(hud_used)
+ for(var/obj/item/I as anything in relevant)
+ position_hud_item(I, slot_id_by_item(I))
+ if(client)
+ client.screen |= relevant
+
+//update whether handcuffs appears on our hud.
+/mob/living/carbon/proc/update_hud_handcuffed()
+ if(hud_used && hud_used.l_hand_hud_object && hud_used.r_hand_hud_object)
+ hud_used.l_hand_hud_object.update_icon()
+ hud_used.r_hand_hud_object.update_icon()
+
/mob/living/carbon/human/update_transform()
var/matrix/old_matrix = transform
var/matrix/M = matrix()
@@ -93,6 +60,16 @@ GLOBAL_LIST_EMPTY(damage_icon_parts)
M.Scale(desired_scale_x, desired_scale_y)
M.Translate(0, 16 * (desired_scale_y - 1))
+ // Mark atom as wide/long for ZM.
+ if (desired_scale_x > 1)
+ zmm_flags |= ZMM_LOOKAHEAD
+ else
+ zmm_flags &= ~ZMM_LOOKAHEAD
+ if (desired_scale_y > 1)
+ zmm_flags |= ZMM_LOOKBESIDE
+ else
+ zmm_flags &= ~ZMM_LOOKBESIDE
+
// handle turning
M.Turn(lying)
// extremely lazy heuristic to see if we should shift down to appear to be, well, down.
@@ -108,422 +85,6 @@ GLOBAL_LIST_EMPTY(damage_icon_parts)
update_icon_special() //May contain transform-altering things
update_ssd_overlay()
-//DAMAGE OVERLAYS
-//constructs damage icon for each organ from mask * damage field and saves it in our overlays_ lists
-/mob/living/carbon/human/UpdateDamageIcon()
- if(QDESTROYING(src))
- return
-
- remove_layer(DAMAGE_LAYER)
-
- // first check whether something actually changed about damage appearance
- var/damage_appearance = ""
-
- for(var/obj/item/organ/external/O in organs)
- if(isnull(O) || O.is_stump())
- continue
- damage_appearance += O.damage_state
-
- if(damage_appearance == previous_damage_appearance)
- // nothing to do here
- return
-
- previous_damage_appearance = damage_appearance
-
- var/image/standing_image = image(icon = species.damage_overlays, icon_state = "00", layer = BODY_LAYER+DAMAGE_LAYER)
-
- // blend the individual damage states with our icons
- for(var/obj/item/organ/external/O in organs)
- if(isnull(O) || O.is_stump())
- continue
-
- O.update_icon()
- if(O.damage_state == "00") continue
- var/icon/DI
- var/cache_index = "[O.damage_state]/[O.icon_name]/[species.get_blood_colour(src)]/[species.get_bodytype_legacy(src)]"
- if(GLOB.damage_icon_parts[cache_index] == null)
- DI = icon(species.get_damage_overlays(src), O.damage_state) // the damage icon for whole human
- DI.Blend(icon(species.get_damage_mask(src), O.icon_name), ICON_MULTIPLY) // mask with this organ's pixels
- DI.Blend(species.get_blood_colour(src), ICON_MULTIPLY)
- GLOB.damage_icon_parts[cache_index] = DI
- else
- DI = GLOB.damage_icon_parts[cache_index]
-
- standing_image.add_overlay(DI)
-
- overlays_standing[DAMAGE_LAYER] = standing_image
- apply_layer(DAMAGE_LAYER)
-
-//BASE MOB SPRITE
-/mob/living/carbon/human/update_icons_body()
- if(QDESTROYING(src) || !(atom_flags & ATOM_INITIALIZED))
- return
-
- var/husk_color_mod = rgb(96,88,80)
- var/hulk_color_mod = rgb(48,224,40)
-
- var/husk = (MUTATION_HUSK in src.mutations)
- var/fat = (MUTATION_FAT in src.mutations)
- var/hulk = (MUTATION_HULK in src.mutations)
- var/skeleton = (MUTATION_SKELETON in src.mutations)
-
- robolimb_count = 0 //TODO, here, really tho?
- robobody_count = 0
-
- //CACHING: Generate an index key from visible bodyparts.
- //0 = destroyed, 1 = normal, 2 = robotic, 3 = necrotic.
-
- //Create a new, blank icon for our mob to use.
- var/icon/stand_icon = new(species.icon_template ? species.icon_template : 'icons/mob/human.dmi', icon_state = "blank")
-
- var/g = gender == FEMALE ? "f" : "m"
- /* This was the prior code before the above line. It was faulty and has been commented out.
- var/g = "male"
- if(gender == FEMALE)
- g = "female"
- */
-
- var/icon_key = "[species.get_race_key(src)][s_base][g][s_tone][r_skin][g_skin][b_skin]"
- if(lip_style)
- icon_key += "[lip_style]"
- else
- icon_key += "nolips"
- var/obj/item/organ/internal/eyes/eyes = internal_organs_by_name[O_EYES]
- if(eyes)
- icon_key += "[rgb(eyes.eye_colour[1], eyes.eye_colour[2], eyes.eye_colour[3])]"
- else
- icon_key += "[r_eyes], [g_eyes], [b_eyes]"
-
- var/obj/item/organ/external/head/head = organs_by_name[BP_HEAD]
- if(head)
- if(!istype(head, /obj/item/organ/external/stump))
- icon_key += "[head.eye_icon]"
- for(var/organ_tag in species.has_limbs)
- var/obj/item/organ/external/part = organs_by_name[organ_tag]
- // Allowing tails to prevent bodyparts rendering, granting more spriter freedom for taur/digitigrade stuff.
- if(isnull(part) || part.is_stump() || part.is_hidden_by_tail())
- icon_key += "0"
- continue
- if(part)
- icon_key += "[part.name]"
- icon_key += "[part.species.get_race_key(part.owner)]"
- icon_key += "[part.dna.GetUIState(DNA_UI_GENDER)]"
- icon_key += "[part.s_tone]"
- if(part.s_col && part.s_col.len >= 3)
- icon_key += "[rgb(part.s_col[1],part.s_col[2],part.s_col[3])]"
- if(part.body_hair && part.h_col && part.h_col.len >= 3)
- icon_key += "[rgb(part.h_col[1],part.h_col[2],part.h_col[3])]"
- if(species.color_force_greyscale)
- icon_key += "_ags"
- if(species.color_mult)
- icon_key += "[ICON_MULTIPLY]"
- else
- icon_key += "[ICON_ADD]"
- else
- icon_key += "#000000"
- for(var/M in part.markings)
- icon_key += "[M][part.markings[M]["color"]]"
-
- if(part.robotic >= ORGAN_ROBOT)
- icon_key += "2[part.model ? "-[part.model]": ""]"
- robolimb_count++
- if((part.robotic == ORGAN_ROBOT || part.robotic == ORGAN_LIFELIKE || part.robotic == ORGAN_NANOFORM) && (part.organ_tag == BP_HEAD || part.organ_tag == BP_TORSO || part.organ_tag == BP_GROIN))
- robobody_count ++
- else if(part.status & ORGAN_DEAD)
- icon_key += "3"
- else
- icon_key += "1"
- if(part.transparent)
- icon_key += "_t"
-
- icon_key = "[icon_key][husk ? 1 : 0][fat ? 1 : 0][hulk ? 1 : 0][skeleton ? 1 : 0]"
-
- var/icon/base_icon
- if(GLOB.human_icon_cache[icon_key])
- base_icon = GLOB.human_icon_cache[icon_key]
- else
- //BEGIN CACHED ICON GENERATION.
- var/obj/item/organ/external/chest = get_organ(BP_TORSO)
- base_icon = chest.get_icon()
-
- for(var/obj/item/organ/external/part in organs)
- if(isnull(part) || part.is_stump() || part.is_hidden_by_tail())
- continue
- var/icon/temp = part.get_icon(skeleton)
- //That part makes left and right legs drawn topmost and lowermost when human looks WEST or EAST
- //And no change in rendering for other parts (they icon_position is 0, so goes to 'else' part)
- if(part.icon_position & (LEFT | RIGHT))
- var/icon/temp2 = new(species.icon_template ? species.icon_template : 'icons/mob/human.dmi', icon_state = "blank")
- temp2.Insert(new/icon(temp,dir=NORTH),dir=NORTH)
- temp2.Insert(new/icon(temp,dir=SOUTH),dir=SOUTH)
- if(!(part.icon_position & LEFT))
- temp2.Insert(new/icon(temp,dir=EAST),dir=EAST)
- if(!(part.icon_position & RIGHT))
- temp2.Insert(new/icon(temp,dir=WEST),dir=WEST)
- base_icon.Blend(temp2, ICON_OVERLAY)
- if(part.icon_position & LEFT)
- temp2.Insert(new/icon(temp,dir=EAST),dir=EAST)
- if(part.icon_position & RIGHT)
- temp2.Insert(new/icon(temp,dir=WEST),dir=WEST)
- base_icon.Blend(temp2, ICON_UNDERLAY)
- else if(part.icon_position & UNDER)
- base_icon.Blend(temp, ICON_UNDERLAY)
- else
- base_icon.Blend(temp, ICON_OVERLAY)
-
- if(!skeleton)
- if(husk)
- base_icon.ColorTone(husk_color_mod)
- else if(hulk)
- var/list/tone = ReadRGB(hulk_color_mod)
- base_icon.MapColors(rgb(tone[1],0,0),rgb(0,tone[2],0),rgb(0,0,tone[3]))
-
- // Handle husk overlay.
- if(husk)
- var/husk_icon = species.get_husk_icon(src)
- if(husk_icon)
- var/icon/mask = new(base_icon)
- var/icon/husk_over = new(species.husk_icon,"")
- mask.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0)
- husk_over.Blend(mask, ICON_ADD)
- base_icon.Blend(husk_over, ICON_OVERLAY)
-
- GLOB.human_icon_cache[icon_key] = base_icon
-
- //END CACHED ICON GENERATION.
- stand_icon.Blend(base_icon,ICON_OVERLAY)
- icon = stand_icon
-
- //tail
- update_tail_showing()
- //wing
- update_wing_showing()
-
-/mob/living/carbon/human/proc/update_skin()
- if(QDESTROYING(src))
- return
-
- remove_layer(SKIN_LAYER)
-
- var/image/skin = species.update_skin(src)
- if(skin)
- skin.layer = BODY_LAYER+SKIN_LAYER
- overlays_standing[SKIN_LAYER] = skin
- apply_layer(SKIN_LAYER)
-
-/mob/living/carbon/human/proc/update_bloodied()
- if(QDESTROYING(src))
- return
-
- remove_layer(BLOOD_LAYER)
- if(!blood_DNA && !feet_blood_DNA)
- return
-
- var/image/both = image(icon = 'icons/effects/effects.dmi', icon_state = "nothing", layer = BODY_LAYER+BLOOD_LAYER)
-
- //Bloody hands
- if(blood_DNA)
- var/image/bloodsies = image(icon = species.get_blood_mask(src), icon_state = "bloodyhands", layer = BODY_LAYER+BLOOD_LAYER)
- bloodsies.color = hand_blood_color
- both.add_overlay(bloodsies)
-
- //Bloody feet
- if(feet_blood_DNA)
- var/image/bloodsies = image(icon = species.get_blood_mask(src), icon_state = "shoeblood", layer = BODY_LAYER+BLOOD_LAYER)
- bloodsies.color = feet_blood_color
- both.add_overlay(bloodsies)
-
- overlays_standing[BLOOD_LAYER] = both
-
- apply_layer(BLOOD_LAYER)
-
-/mob/living/carbon/human/proc/BloodyMouth()
-
- var/image/both = image(icon = 'icons/effects/effects.dmi', icon_state = "nothing", layer = BODY_LAYER+BLOOD_LAYER)
-
- //"lol", said the scorpion, "lmao"
- var/image/bloodsies = image(icon = species.get_blood_mask(src), icon_state = "redwings", layer = BODY_LAYER+BLOOD_LAYER)
- bloodsies.color = src.species.blood_color
- both.add_overlay(bloodsies)
-
- overlays_standing[BLOOD_LAYER] = both
-
- apply_layer(BLOOD_LAYER)
-
-
-//UNDERWEAR OVERLAY
-/mob/living/carbon/human/proc/update_underwear()
- if(QDESTROYING(src))
- return
-
- remove_layer(UNDERWEAR_LAYER)
-
- if(species.species_appearance_flags & HAS_UNDERWEAR)
- overlays_standing[UNDERWEAR_LAYER] = list()
- for(var/category in all_underwear)
- if(hide_underwear[category])
- continue
- var/datum/category_item/underwear/UWI = all_underwear[category]
- var/image/wear = UWI.generate_image(all_underwear_metadata[category], layer = BODY_LAYER+UNDERWEAR_LAYER)
- overlays_standing[UNDERWEAR_LAYER] += wear
-
- apply_layer(UNDERWEAR_LAYER)
-
-//HAIR OVERLAY
-/mob/living/carbon/human/update_hair()
- if(QDESTROYING(src))
- return
-
- //Reset our hair
- remove_layer(HAIR_LAYER)
- update_eyes() //Pirated out of here, for glowing eyes.
-
- var/obj/item/organ/external/head/head_organ = get_organ(BP_HEAD)
- if(!head_organ || head_organ.is_stump() )
- return
-
- //masks and helmets can obscure our hair.
- if( (head && (head.inv_hide_flags & BLOCKHAIR)) || (wear_mask && (wear_mask.inv_hide_flags & BLOCKHAIR)))
- return
-
- //base icons
- var/icon/face_standing = icon(icon = 'icons/mob/human_face.dmi', icon_state = "bald_s")
-
- if(f_style)
- var/datum/sprite_accessory/facial_hair_style = GLOB.legacy_facial_hair_lookup[f_style]
- if(facial_hair_style && (!facial_hair_style.apply_restrictions || (src.species.get_bodytype_legacy(src) in facial_hair_style.species_allowed)))
- var/icon/facial_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_s")
- if(facial_hair_style.do_colouration)
- facial_s.Blend(rgb(r_facial, g_facial, b_facial), facial_hair_style.color_blend_mode)
-
- face_standing.Blend(facial_s, ICON_OVERLAY)
-
- if(h_style)
- var/datum/sprite_accessory/hair/hair_style = GLOB.legacy_hair_lookup[h_style]
- if(head && (head.inv_hide_flags & BLOCKHEADHAIR))
- if(!(hair_style.hair_flags & HAIR_VERY_SHORT))
- hair_style = GLOB.legacy_hair_lookup["Short Hair"]
-
- if(hair_style && (!hair_style.apply_restrictions || (src.species.get_bodytype_legacy(src) in hair_style.species_allowed)))
- var/icon/grad_s
- var/icon/hair_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_s")
- var/icon/hair_s_add
- if(hair_style.icon_add)
- hair_s_add = new/icon("icon" = hair_style.icon_add, "icon_state" = "[hair_style.icon_state]_s")
- if(hair_style.do_colouration)
- if(grad_style)
- grad_s = new/icon("icon" = 'icons/mob/hair_gradients.dmi', "icon_state" = GLOB.hair_gradients[grad_style])
- grad_s.Blend(hair_s, ICON_AND)
- grad_s.Blend(rgb(r_grad, g_grad, b_grad), ICON_MULTIPLY)
- hair_s.Blend(rgb(r_hair, g_hair, b_hair), ICON_MULTIPLY)
- if(hair_s_add)
- hair_s.Blend(hair_s_add, ICON_ADD)
- if(grad_s)
- hair_s.Blend(grad_s, ICON_OVERLAY)
-
- face_standing.Blend(hair_s, ICON_OVERLAY)
-
- var/icon/ears_s = get_ears_overlay()
- if(ears_s)
- if(ears_s.Height() > face_standing.Height())
- face_standing.Crop(1, 1, face_standing.Width(), ears_s.Height())
- face_standing.Blend(ears_s, ICON_OVERLAY)
- if(istype(head_organ,/obj/item/organ/external/head/vr))
- var/obj/item/organ/external/head/vr/head_organ_vr = head_organ
- overlays_standing[HAIR_LAYER] = image(face_standing, layer = BODY_LAYER+HAIR_LAYER, "pixel_y" = head_organ_vr.head_offset)
- apply_layer(HAIR_LAYER)
- return
-
- var/icon/horns_s = get_horns_overlay()
- if(horns_s && (!hiding_horns && horn_style.can_be_hidden))
- if(horns_s.Height() > face_standing.Height())
- face_standing.Crop(1, 1, face_standing.Width(), horns_s.Height())
- face_standing.Blend(horns_s, ICON_OVERLAY)
- if(istype(head_organ,/obj/item/organ/external/head/vr))
- var/obj/item/organ/external/head/vr/head_organ_vr = head_organ
- overlays_standing[HAIR_LAYER] = image(face_standing, layer = BODY_LAYER+HAIR_LAYER, "pixel_y" = head_organ_vr.head_offset)
- apply_layer(HAIR_LAYER)
- return
-
- face_standing += rgb(,,,head_organ.hair_opacity)
-
- overlays_standing[HAIR_LAYER] = image(face_standing, layer = BODY_LAYER+HAIR_LAYER)
- apply_layer(HAIR_LAYER)
-
-/mob/living/carbon/human/update_eyes()
- if(QDESTROYING(src))
- return
-
- var/obj/item/organ/internal/eyes/eyes = internal_organs_by_name[O_EYES]
- if(eyes)
- eyes.update_colour()
- update_icons_body()
-
- //Reset our eyes
- remove_layer(EYES_LAYER)
-
- //TODO: Probably redo this. I know I wrote it, but...
-
- //This is ONLY for glowing eyes for now. Boring flat eyes are done by the head's own proc.
- if(!species.has_glowing_eyes)
- return
-
- //Our glowy eyes should be hidden if some equipment hides them.
- if(!should_have_organ(O_EYES) || (head && (head.inv_hide_flags & BLOCKHAIR)) || (wear_mask && (wear_mask.inv_hide_flags & BLOCKHAIR)))
- return
-
- //Get the head, we'll need it later.
- var/obj/item/organ/external/head/head_organ = get_organ(BP_HEAD)
- if(!head_organ || head_organ.is_stump() )
- return
-
- //The eyes store the color themselves, funny enough.
- if(!head_organ.eye_icon)
- return
-
- var/icon/eyes_icon = new/icon(head_organ.eye_icon_location, head_organ.eye_icon)
- if(eyes)
- eyes_icon.Blend(rgb(eyes.eye_colour[1], eyes.eye_colour[2], eyes.eye_colour[3]), ICON_ADD)
- else
- eyes_icon.Blend(rgb(128,0,0), ICON_ADD)
-
- var/image/eyes_image = image(eyes_icon)
- eyes_image.plane = ABOVE_LIGHTING_PLANE
-
- overlays_standing[EYES_LAYER] = eyes_image
- apply_layer(EYES_LAYER)
-
-/mob/living/carbon/human/update_mutations()
- if(QDESTROYING(src))
- return
-
- remove_layer(MUTATIONS_LAYER)
-
- if(!LAZYLEN(mutations))
- return //No mutations, no icons.
-
- //TODO: THIS PROC???
- var/fat
- if(MUTATION_FAT in mutations)
- fat = "fat"
-
- var/image/standing = image(icon = 'icons/effects/genetics.dmi', layer = BODY_LAYER+MUTATIONS_LAYER)
- var/g = gender == FEMALE ? "f" : "m"
-
- for(var/datum/gene/gene in dna_genes)
- if(!gene.block)
- continue
- if(gene.is_active(src))
- var/underlay = gene.OnDrawUnderlays(src,g,fat)
- if(underlay)
- standing.underlays += underlay
-
- for(var/mut in mutations)
- if(mut == MUTATION_LASER)
- standing.overlays += "lasereyes_s" //TODO
-
- overlays_standing[MUTATIONS_LAYER] = standing
- apply_layer(MUTATIONS_LAYER)
/* --------------------------------------- */
//Recomputes every icon on the mob. Expensive.
@@ -531,11 +92,8 @@ GLOBAL_LIST_EMPTY(damage_icon_parts)
//other drastic body-shape change, but otherwise avoid.
/mob/living/carbon/human/regenerate_icons()
..()
- if(transforming || QDELETED(src))
- return
-
update_icons_body()
- UpdateDamageIcon()
+ update_damage_overlay()
update_mutations()
update_skin()
update_underwear()
@@ -563,630 +121,6 @@ GLOBAL_LIST_EMPTY(damage_icon_parts)
update_bloodsub()
update_surgery()
-/* --------------------------------------- */
-//vvvvvv UPDATE_INV PROCS vvvvvv
-
-/mob/living/carbon/human/update_inv_w_uniform()
- if(QDESTROYING(src))
- return
-
- remove_layer(UNIFORM_LAYER)
-
- //Shoes can be affected by uniform being drawn onto them
- update_inv_shoes()
-
- if(!w_uniform)
- return
-
- if(wear_suit && (wear_suit.inv_hide_flags & HIDEJUMPSUIT) && !istype(wear_suit, /obj/item/clothing/suit/space/hardsuit))
- return //Wearing a suit that prevents uniform rendering
-
- //Build a uniform sprite
- var/icon/c_mask = tail_style?.clip_mask
- if(c_mask)
- var/obj/item/clothing/suit/S = wear_suit
- if((wear_suit?.inv_hide_flags & HIDETAIL) || (istype(S) && S.taurized)) // Reasons to not mask: 1. If you're wearing a suit that hides the tail or if you're wearing a taurized suit.
- c_mask = null
- var/list/MA_or_list = w_uniform.render_mob_appearance(src, SLOT_ID_UNIFORM, species.get_effective_bodytype(src, w_uniform, SLOT_ID_UNIFORM))
-
- if(c_mask)
- if(islist(MA_or_list))
- for(var/mutable_appearance/MA2 as anything in MA_or_list)
- MA2.filters += filter(type = "alpha", icon = c_mask)
- else
- var/mutable_appearance/MA = MA_or_list
- MA.filters += filter(type = "alpha", icon = c_mask)
- overlays_standing[UNIFORM_LAYER] = MA_or_list
-
- apply_layer(UNIFORM_LAYER)
-
-/mob/living/carbon/human/update_inv_wear_id()
- if(QDESTROYING(src))
- return
-
- remove_layer(ID_LAYER)
-
- if(!wear_id)
- return //Not wearing an ID
-
- //Only draw the ID on the mob if the uniform allows for it
- if(w_uniform && istype(w_uniform, /obj/item/clothing/under))
- var/obj/item/clothing/under/U = w_uniform
- if(U.displays_id)
- overlays_standing[ID_LAYER] = wear_id.render_mob_appearance(src, SLOT_ID_WORN_ID, species.get_effective_bodytype(src, wear_id, SLOT_ID_WORN_ID))
-
- apply_layer(ID_LAYER)
-
-/mob/living/carbon/human/update_inv_gloves()
- if(QDESTROYING(src))
- return
-
- remove_layer(GLOVES_LAYER)
-
- if(!gloves)
- return //No gloves, no reason to be here.
-
- overlays_standing[GLOVES_LAYER] = gloves.render_mob_appearance(src, SLOT_ID_GLOVES, species.get_effective_bodytype(src, gloves, SLOT_ID_GLOVES))
-
- apply_layer(GLOVES_LAYER)
-
-/mob/living/carbon/human/update_inv_glasses()
- if(QDESTROYING(src))
- return
-
- remove_layer(GLASSES_LAYER)
-
- if(!glasses)
- return //Not wearing glasses, no need to update anything.
-
- overlays_standing[GLASSES_LAYER] = glasses.render_mob_appearance(src, SLOT_ID_GLASSES, species.get_effective_bodytype(src, glasses, SLOT_ID_GLASSES))
-
- apply_layer(GLASSES_LAYER)
-
-/mob/living/carbon/human/update_inv_ears()
- if(QDESTROYING(src))
- return
-
- remove_layer(EARS_LAYER)
-
- if((head && head.inv_hide_flags & (BLOCKHAIR | BLOCKHEADHAIR)) || (wear_mask && wear_mask.inv_hide_flags & (BLOCKHAIR | BLOCKHEADHAIR)))
- return //Ears are blocked (by hair being blocked, overloaded)
-
- if(!l_ear && !r_ear)
- return //Why bother, if no ear sprites
-
- // Blank image upon which to layer left & right overlays.
- var/list/mutable_appearance/both = list()
- if(l_ear)
- both += l_ear.render_mob_appearance(src, SLOT_ID_LEFT_EAR, species.get_effective_bodytype(src, l_ear, SLOT_ID_LEFT_EAR))
- if(r_ear)
- both += r_ear.render_mob_appearance(src, SLOT_ID_RIGHT_EAR, species.get_effective_bodytype(src, r_ear, SLOT_ID_RIGHT_EAR))
-
- overlays_standing[EARS_LAYER] = both
- apply_layer(EARS_LAYER)
-
-/mob/living/carbon/human/update_inv_shoes()
- if(QDESTROYING(src))
- return
-
- remove_layer(SHOES_LAYER)
- remove_layer(SHOES_LAYER_ALT) //Dumb alternate layer for shoes being under the uniform.
-
- if(!shoes || (wear_suit && wear_suit.inv_hide_flags & HIDESHOES) || (w_uniform && w_uniform.inv_hide_flags & HIDESHOES))
- return //Either nothing to draw, or it'd be hidden.
-
- for(var/f in list(BP_L_FOOT, BP_R_FOOT))
- var/obj/item/organ/external/foot/foot = get_organ(f)
- if(istype(foot) && foot.is_hidden_by_tail()) //If either foot is hidden by the tail, don't render footwear.
- return
-
- //Allow for shoe layer toggle nonsense
- var/shoe_layer = SHOES_LAYER
- if(istype(shoes, /obj/item/clothing/shoes))
- var/obj/item/clothing/shoes/ushoes = shoes
- if(ushoes.shoes_under_pants == 1)
- shoe_layer = SHOES_LAYER_ALT
-
- //NB: the use of a var for the layer on this one
- overlays_standing[shoe_layer] = shoes.render_mob_appearance(src, SLOT_ID_SHOES, species.get_effective_bodytype(src, shoes, SLOT_ID_SHOES))
-
- apply_layer(SHOES_LAYER)
- apply_layer(SHOES_LAYER_ALT)
-
-/mob/living/carbon/human/update_inv_s_store()
- if(QDESTROYING(src))
- return
-
- remove_layer(SUIT_STORE_LAYER)
-
- if(!s_store)
- return //Why bother, nothing there.
-
- //TODO, this is unlike the rest of the things
- //Basically has no variety in slot icon choices at all. WHY SPECIES ONLY??
- var/t_state = s_store.item_state
- if(!t_state)
- t_state = s_store.icon_state
- overlays_standing[SUIT_STORE_LAYER] = image(icon = species.suit_storage_icon, icon_state = t_state, layer = BODY_LAYER+SUIT_STORE_LAYER)
-
- apply_layer(SUIT_STORE_LAYER)
-
-/mob/living/carbon/human/update_inv_head()
- if(QDESTROYING(src))
- return
-
- remove_layer(HEAD_LAYER)
-
- if(!head)
- return //No head item, why bother.
-
- overlays_standing[HEAD_LAYER] = head.render_mob_appearance(src, SLOT_ID_HEAD, species.get_effective_bodytype(src, head, SLOT_ID_HEAD))
-
- apply_layer(HEAD_LAYER)
-
-/mob/living/carbon/human/update_inv_belt()
- if(QDESTROYING(src))
- return
-
- remove_layer(BELT_LAYER)
- remove_layer(BELT_LAYER_ALT) //Because you can toggle belt layer with a verb
-
- if(!belt)
- return //No belt, why bother.
-
- //Toggle for belt layering with uniform
- var/belt_layer = BELT_LAYER
- if(istype(belt, /obj/item/storage/belt))
- var/obj/item/storage/belt/ubelt = belt
- if(ubelt.show_above_suit)
- belt_layer = BELT_LAYER_ALT
-
- //NB: this uses a var from above
- overlays_standing[belt_layer] = belt.render_mob_appearance(src, SLOT_ID_BELT, species.get_effective_bodytype(src, belt, SLOT_ID_BELT))
-
- apply_layer(belt_layer)
-
-/mob/living/carbon/human/update_inv_wear_suit()
- if(QDESTROYING(src))
- return
-
- remove_layer(SUIT_LAYER)
-
- //Hide/show other layers if necessary
- update_inv_w_uniform()
- update_inv_shoes()
- update_tail_showing()
- update_wing_showing()
-
- if(!wear_suit)
- return //No point, no suit.
-
- // Part of splitting the suit sprites up
-
- var/icon/c_mask = null
- var/tail_is_rendered = (overlays_standing[TAIL_LAYER] || overlays_standing[TAIL_LAYER_ALT])
- var/valid_clip_mask = tail_style?.clip_mask
-
- var/obj/item/clothing/suit/S = wear_suit
- if(tail_is_rendered && valid_clip_mask && !(istype(S) && S.taurized)) //Clip the lower half of the suit off using the tail's clip mask for taurs since taur bodies aren't hidden.
- c_mask = valid_clip_mask
- var/list/MA_or_list = wear_suit.render_mob_appearance(src, SLOT_ID_SUIT, species.get_effective_bodytype(src, wear_suit, SLOT_ID_SUIT))
- if(c_mask)
- if(islist(MA_or_list))
- for(var/mutable_appearance/MA2 as anything in MA_or_list)
- MA2.filters += filter(type = "alpha", icon = c_mask)
- else
- var/mutable_appearance/MA = MA_or_list
- MA.filters += filter(type = "alpha", icon = c_mask)
- overlays_standing[SUIT_LAYER] = MA_or_list
-
- apply_layer(SUIT_LAYER)
-
-/mob/living/carbon/human/update_inv_wear_mask()
- if(QDESTROYING(src))
- return
-
- remove_layer(FACEMASK_LAYER)
-
- if(!wear_mask || (head && head.inv_hide_flags & HIDEMASK))
- return //Why bother, nothing in mask slot.
-
- overlays_standing[FACEMASK_LAYER] = wear_mask.render_mob_appearance(src, SLOT_ID_MASK, species.get_effective_bodytype(src, wear_mask, SLOT_ID_MASK))
-
- apply_layer(FACEMASK_LAYER)
-
-/mob/living/carbon/human/update_inv_back()
- if(QDESTROYING(src))
- return
-
- remove_layer(BACK_LAYER)
-
- if(!back)
- return //Why do anything
-
- overlays_standing[BACK_LAYER] = back.render_mob_appearance(src, SLOT_ID_BACK, species.get_effective_bodytype(src, back, SLOT_ID_BACK))
-
- apply_layer(BACK_LAYER)
-
-//TODO: Carbon procs in my human update_icons??
-/mob/living/carbon/human/update_hud() //TODO: do away with this if possible
- if(QDESTROYING(src))
- return
- // todo: this is utterly shitcode and fucking stupid ~silicons
- // todo: the rest of hud code here ain't much better LOL
- var/list/obj/item/relevant = get_equipped_items(TRUE, TRUE)
- if(hud_used)
- for(var/obj/item/I as anything in relevant)
- position_hud_item(I, slot_id_by_item(I))
- if(client)
- client.screen |= relevant
-
-//update whether handcuffs appears on our hud.
-/mob/living/carbon/proc/update_hud_handcuffed()
- if(QDESTROYING(src))
- return
-
- if(hud_used && hud_used.l_hand_hud_object && hud_used.r_hand_hud_object)
- hud_used.l_hand_hud_object.update_icon()
- hud_used.r_hand_hud_object.update_icon()
-
-/mob/living/carbon/human/update_inv_handcuffed()
- if(QDESTROYING(src))
- return
-
- remove_layer(HANDCUFF_LAYER)
- update_hud_handcuffed() //TODO
-
- if(!handcuffed)
- return //Not cuffed, why bother
-
- overlays_standing[HANDCUFF_LAYER] = handcuffed.render_mob_appearance(src, SLOT_ID_HANDCUFFED, species.get_effective_bodytype(src, handcuffed, SLOT_ID_HANDCUFFED))
-
- apply_layer(HANDCUFF_LAYER)
-
-/mob/living/carbon/human/update_inv_legcuffed()
- if(QDESTROYING(src))
- return
-
- remove_layer(LEGCUFF_LAYER)
-
- if(!legcuffed)
- return //Not legcuffed, why bother.
-
- overlays_standing[LEGCUFF_LAYER] = legcuffed.render_mob_appearance(src, SLOT_ID_LEGCUFFED, species.get_effective_bodytype(src, legcuffed, SLOT_ID_LEGCUFFED))
-
- apply_layer(LEGCUFF_LAYER)
-
-/mob/living/carbon/human/update_inv_r_hand()
- if(QDESTROYING(src))
- return
-
- remove_layer(R_HAND_LAYER)
-
- if(!r_hand)
- return //No hand, no bother.
-
- overlays_standing[R_HAND_LAYER] = r_hand.render_mob_appearance(src, 2, BODYTYPE_DEFAULT)
-
- apply_layer(R_HAND_LAYER)
-
-/mob/living/carbon/human/update_inv_l_hand()
- if(QDESTROYING(src))
- return
-
- remove_layer(L_HAND_LAYER)
-
- if(!l_hand)
- return //No hand, no bother.
-
- overlays_standing[L_HAND_LAYER] = l_hand.render_mob_appearance(src, 1, BODYTYPE_DEFAULT)
-
- apply_layer(L_HAND_LAYER)
-
-/mob/living/carbon/human/proc/update_tail_showing()
- if(QDESTROYING(src))
- return
-
- remove_layer(TAIL_LAYER)
- remove_layer(TAIL_LAYER_ALT)
-
- if(hiding_tail && tail_style.can_be_hidden)
- return
-
- var/used_tail_layer = tail_alt ? TAIL_LAYER_ALT : TAIL_LAYER
-
- var/list/image/tail_images = list()
-
- var/image/rendering
- rendering = get_tail_image(TRUE)
- if(rendering)
- rendering.layer = BODY_LAYER + used_tail_layer
- tail_images += rendering
-
- if(tail_style?.front_behind_system)
- rendering = get_tail_image(FALSE)
- rendering.layer = BODY_LAYER - used_tail_layer
- tail_images += rendering
-
- if(length(tail_images))
- overlays_standing[used_tail_layer] = tail_images
- apply_layer(used_tail_layer)
- return
-
- var/species_tail = species.get_tail(src) // Species tail icon_state prefix.
-
- //This one is actually not that bad I guess.
- if(species_tail && !(wear_suit && wear_suit.inv_hide_flags & HIDETAIL))
- var/icon/tail_s = get_tail_icon()
- overlays_standing[used_tail_layer] = image(icon = tail_s, icon_state = "[species_tail]_s", layer = BODY_LAYER+used_tail_layer)
- animate_tail_reset()
-
-//TODO: Is this the appropriate place for this, and not on species...?
-/mob/living/carbon/human/proc/get_tail_icon()
- var/icon_key = "[species.get_race_key(src)][r_skin][g_skin][b_skin][r_hair][g_hair][b_hair]"
- var/icon/tail_icon = GLOB.tail_icon_cache[icon_key]
- if(!tail_icon)
- //generate a new one
- var/species_tail_anim = species.get_tail_animation(src)
- if(!species_tail_anim && species.icobase_tail)
- species_tail_anim = species.icobase // Allow override of file for non-animated tails
- if(!species_tail_anim)
- species_tail_anim = 'icons/effects/species.dmi'
- tail_icon = new/icon(species_tail_anim)
- tail_icon.Blend(rgb(r_skin, g_skin, b_skin), species.color_mult ? ICON_MULTIPLY : ICON_ADD)
- // The following will not work with animated tails.
- var/use_species_tail = species.get_tail_hair(src)
- if(use_species_tail)
- var/icon/hair_icon = icon('icons/effects/species.dmi', "[species.get_tail(src)]_[use_species_tail]_s") // Suffix icon state string with '_s' to compensate for diff in .dmi b/w us & Polaris. //TODO: No.
- if(species.color_force_greyscale)
- hair_icon.MapColors(arglist(color_matrix_greyscale()))
- hair_icon.Blend(rgb(r_hair, g_hair, b_hair), species.color_mult ? ICON_MULTIPLY : ICON_ADD) // Check for species color_mult
- tail_icon.Blend(hair_icon, ICON_OVERLAY)
- GLOB.tail_icon_cache[icon_key] = tail_icon
-
- return tail_icon
-
-/mob/living/carbon/human/proc/set_tail_state(var/t_state)
- var/used_tail_layer = tail_alt ? TAIL_LAYER_ALT : TAIL_LAYER
- var/list/image/tail_overlays = overlays_standing[used_tail_layer]
-
- remove_layer(TAIL_LAYER)
- remove_layer(TAIL_LAYER_ALT)
-
- if(!tail_overlays || hiding_tail)
- return
- if(islist(tail_overlays))
- for(var/image/tail_overlay as anything in tail_overlays)
- if(species.get_tail_animation(src))
- tail_overlay.icon_state = t_state
- overlays_standing[used_tail_layer] = tail_overlays
- else
- var/image/tail_overlay = tail_overlays
- if(species.get_tail_animation(src))
- tail_overlay.icon_state = t_state
- . = tail_overlay
- overlays_standing[used_tail_layer] = tail_overlay
-
- apply_layer(used_tail_layer)
-
-//Not really once, since BYOND can't do that.
-//Update this if the ability to flick() images or make looping animation start at the first frame is ever added.
-//You can sort of flick images now with flick_overlay -Aro
-/mob/living/carbon/human/proc/animate_tail_once()
- if(QDESTROYING(src))
- return
-
- var/t_state = "[species.get_tail(src)]_once"
- var/used_tail_layer = tail_alt ? TAIL_LAYER_ALT : TAIL_LAYER
-
- var/image/tail_overlay = overlays_standing[used_tail_layer]
- if(tail_overlay && tail_overlay.icon_state == t_state)
- return //let the existing animation finish
-
- tail_overlay = set_tail_state(t_state) // Calls remove_layer & apply_layer
- if(tail_overlay)
- spawn(20)
- //check that the animation hasn't changed in the meantime
- if(overlays_standing[used_tail_layer] == tail_overlay && tail_overlay.icon_state == t_state)
- animate_tail_stop()
-
-/mob/living/carbon/human/proc/animate_tail_start()
- if(QDESTROYING(src))
- return
-
- set_tail_state("[species.get_tail(src)]_slow[rand(0,9)]")
-
-/mob/living/carbon/human/proc/animate_tail_fast()
- if(QDESTROYING(src))
- return
-
- set_tail_state("[species.get_tail(src)]_loop[rand(0,9)]")
-
-/mob/living/carbon/human/proc/animate_tail_reset()
- if(QDESTROYING(src))
- return
-
- if(stat != DEAD)
- set_tail_state("[species.get_tail(src)]_idle[rand(0,9)]")
- else
- set_tail_state("[species.get_tail(src)]_static")
- toggle_tail_vr(FALSE) // So tails stop when someone dies. TODO - Fix this hack ~Leshana
-
-/mob/living/carbon/human/proc/animate_tail_stop()
- if(QDESTROYING(src))
- return
-
- set_tail_state("[species.get_tail(src)]_static")
-
-/// Wings! See update_icons_vr.dm for more wing procs
-/mob/living/carbon/human/proc/update_wing_showing()
- if(QDESTROYING(src))
- return
-
- remove_layer(WING_LAYER)
-
- if(hiding_wings && wing_style.can_be_hidden)
- return
-
- overlays_standing[WING_LAYER] = list()
-
- var/image/vr_wing_image = get_wing_image(TRUE)
- if(vr_wing_image)
- vr_wing_image.layer = BODY_LAYER+WING_LAYER
- overlays_standing[WING_LAYER] += vr_wing_image
-
- if(wing_style?.front_behind_system)
- var/image/vr_wing_image_2 = get_wing_image(FALSE)
- vr_wing_image_2.layer = BODY_LAYER - WING_LAYER
- overlays_standing[WING_LAYER] += vr_wing_image_2
-
- apply_layer(WING_LAYER)
-
-/mob/living/carbon/human/update_modifier_visuals()
- if(QDESTROYING(src))
- return
-
- remove_layer(MODIFIER_EFFECTS_LAYER)
-
- if(!LAZYLEN(modifiers))
- return //No modifiers, no effects.
-
- var/image/effects = new()
- for(var/datum/modifier/M in modifiers)
- if(M.mob_overlay_state)
- var/image/I = image(icon = 'icons/mob/modifier_effects.dmi', icon_state = M.mob_overlay_state)
- effects.add_overlay(I) //TODO, this compositing is annoying.
-
- overlays_standing[MODIFIER_EFFECTS_LAYER] = effects
-
- apply_layer(MODIFIER_EFFECTS_LAYER)
-
-/mob/living/carbon/human/update_fire()
- if(QDESTROYING(src))
- return
-
- remove_layer(FIRE_LAYER)
-
- if(!on_fire)
- return
-
- overlays_standing[FIRE_LAYER] = image(icon = 'icons/mob/OnFire.dmi', icon_state = get_fire_icon_state(), layer = BODY_LAYER+FIRE_LAYER)
-
- apply_layer(FIRE_LAYER)
-
-/mob/living/carbon/human/update_water()
- if(QDESTROYING(src))
- return
-
- remove_layer(MOB_WATER_LAYER)
-
- var/depth = check_submerged()
- if(!depth || lying)
- return
- if(depth < 3)
- overlays_standing[MOB_WATER_LAYER] = image(icon = 'icons/mob/submerged.dmi', icon_state = "human_swimming_[depth]", layer = BODY_LAYER+MOB_WATER_LAYER) //TODO: Improve
- apply_layer(MOB_WATER_LAYER)
- if(depth == 4)
- overlays_standing[MOB_WATER_LAYER] = image(icon = 'icons/mob/submerged.dmi', icon_state = "hacid_1", layer = BODY_LAYER+MOB_WATER_LAYER)
- apply_layer(MOB_WATER_LAYER)
- if(depth == 5)
- overlays_standing[MOB_WATER_LAYER] = image(icon = 'icons/mob/submerged.dmi', icon_state = "hacid_2", layer = BODY_LAYER+MOB_WATER_LAYER)
- apply_layer(MOB_WATER_LAYER)
- if(depth == 6)
- overlays_standing[MOB_WATER_LAYER] = image(icon = 'icons/mob/submerged.dmi', icon_state = "hblood_1", layer = BODY_LAYER+MOB_WATER_LAYER)
- apply_layer(MOB_WATER_LAYER)
- if(depth == 7)
- overlays_standing[MOB_WATER_LAYER] = image(icon = 'icons/mob/submerged.dmi', icon_state = "hblood_2", layer = BODY_LAYER+MOB_WATER_LAYER)
- apply_layer(MOB_WATER_LAYER)
-
-/mob/living/carbon/human/update_acidsub()
- if(QDESTROYING(src))
- return
-
- remove_layer(MOB_WATER_LAYER)
-
- var/depth = check_submerged()
- if(!depth || lying)
- return
-
- overlays_standing[MOB_WATER_LAYER] = image(icon = 'icons/mob/submerged.dmi', icon_state = "hacid_[depth]", layer = BODY_LAYER+MOB_WATER_LAYER) //TODO: Improve
-
- apply_layer(MOB_WATER_LAYER)
-
-/mob/living/carbon/human/update_bloodsub()
- if(QDESTROYING(src))
- return
-
- remove_layer(MOB_WATER_LAYER)
-
- var/depth = check_submerged()
- if(!depth || lying)
- return
-
- overlays_standing[MOB_WATER_LAYER] = image(icon = 'icons/mob/submerged.dmi', icon_state = "hblood_[depth]", layer = BODY_LAYER+MOB_WATER_LAYER) //TODO: Improve
-
- apply_layer(MOB_WATER_LAYER)
-
-/mob/living/carbon/human/proc/update_surgery()
- if(QDESTROYING(src))
- return
-
- remove_layer(SURGERY_LAYER)
-
- var/image/total = new
- for(var/obj/item/organ/external/E in organs)
- if(E.open)
- var/image/I = image(icon = 'icons/mob/surgery.dmi', icon_state = "[E.icon_name][round(E.open)]", layer = BODY_LAYER+SURGERY_LAYER)
- total.add_overlay(I) //TODO: This compositing is annoying
-
- if(total.overlays.len)
- overlays_standing[SURGERY_LAYER] = total
- apply_layer(SURGERY_LAYER)
-
-/mob/living/carbon/human/proc/get_wing_image(front) //redbull gives you wings
- var/icon/grad_swing
- if(QDESTROYING(src))
- return
-
- //If you are FBP with wing style and didn't set a custom one
- if(synthetic && synthetic.includes_wing && !wing_style)
- var/icon/wing_s = new/icon("icon" = synthetic.icon, "icon_state" = "wing") //I dunno. If synths have some custom wing?
- wing_s.Blend(rgb(src.r_skin, src.g_skin, src.b_skin), species.color_mult ? ICON_MULTIPLY : ICON_ADD)
- return image(wing_s)
-
- //If you have custom wings selected
- if(wing_style && (!(wear_suit && wear_suit.inv_hide_flags & HIDETAIL) || !wing_style.clothing_can_hide))
- var/icon/wing_s = new/icon("icon" = wing_style.icon, "icon_state" = spread ? wing_style.spr_state : (flapping && wing_style.ani_state ? wing_style.ani_state : (wing_style.front_behind_system? (wing_style.icon_state + (front? "_FRONT" : "_BEHIND")) : wing_style.icon_state)))
- if(wing_style.do_colouration)
- if(grad_wingstyle)
- grad_swing = new/icon("icon" = 'icons/mob/wing_gradients.dmi', "icon_state" = GLOB.hair_gradients[grad_wingstyle])
- grad_swing.Blend(wing_s, ICON_AND)
- grad_swing.Blend(rgb(r_gradwing, g_gradwing, b_gradwing), ICON_MULTIPLY)
- wing_s.Blend(rgb(src.r_wing, src.g_wing, src.b_wing), wing_style.color_blend_mode)
- if(grad_swing)
- wing_s.Blend(grad_swing, ICON_OVERLAY)
- if(wing_style.extra_overlay)
- var/icon/overlay = new/icon("icon" = wing_style.icon, "icon_state" = wing_style.extra_overlay)
- overlay.Blend(rgb(src.r_wing2, src.g_wing2, src.b_wing2), wing_style.color_blend_mode)
- wing_s.Blend(overlay, ICON_OVERLAY)
- qdel(overlay)
-
- if(wing_style.extra_overlay2)
- var/icon/overlay = new/icon("icon" = wing_style.icon, "icon_state" = wing_style.extra_overlay2)
- if(wing_style.ani_state)
- overlay = new/icon("icon" = wing_style.icon, "icon_state" = wing_style.extra_overlay2_w)
- overlay.Blend(rgb(src.r_wing3, src.g_wing3, src.b_wing3), wing_style.color_blend_mode)
- wing_s.Blend(overlay, ICON_OVERLAY)
- qdel(overlay)
- else
- overlay.Blend(rgb(src.r_wing3, src.g_wing3, src.b_wing3), wing_style.color_blend_mode)
- wing_s.Blend(overlay, ICON_OVERLAY)
- qdel(overlay)
-
- if(wing_style.center)
- center_appearance(wing_s, wing_style.dimension_x, wing_style.dimension_y)
- return image(wing_s, "pixel_x" = -16)
-
// TODO - Move this to where it should go ~Leshana
/mob/proc/stop_flying()
if(QDESTROYING(src))
@@ -1196,7 +130,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts)
/mob/living/carbon/human/stop_flying()
if((. = ..()))
- update_wing_showing()
+ render_spriteacc_wings()
//Stolen from bay for shifting equipment by default and not having to resprite it
/proc/overlay_image(icon,icon_state,color,flags)
diff --git a/code/modules/mob/living/carbon/lick_wounds.dm b/code/modules/mob/living/carbon/lick_wounds.dm
index a775613fe514..9b3718c9196d 100644
--- a/code/modules/mob/living/carbon/lick_wounds.dm
+++ b/code/modules/mob/living/carbon/lick_wounds.dm
@@ -49,7 +49,7 @@
if(affecting.brute_dam > 20 || affecting.burn_dam > 20)
to_chat(src, "The wounds on [M]'s [affecting.name] are too severe to treat with just licking.")
return
-
+
else
visible_message("\The [src] starts licking the wounds on [M]'s [affecting.name] clean.", \
"You start licking the wounds on [M]'s [affecting.name] clean." )
@@ -65,10 +65,10 @@
if(affecting.is_bandaged() && affecting.is_salved()) // We do a second check after the delay, in case it was bandaged after the first check.
to_chat(src, "The wounds on [M]'s [affecting.name] have already been treated.")
- return
+ return
else
- visible_message("\The [src] [pick("slathers \a [W.desc] on [M]'s [affecting.name] with their spit.",
+ visible_message("\The [src] [pick("slathers \a [W.desc] on [M]'s [affecting.name] with their spit.",
"drags their tongue across \a [W.desc] on [M]'s [affecting.name].",
"drips saliva onto \a [W.desc] on [M]'s [affecting.name].",
"uses their tongue to disinfect \a [W.desc] on [M]'s [affecting.name].",
@@ -78,5 +78,5 @@
W.salve()
W.bandage()
W.disinfect()
- H.UpdateDamageIcon()
+ H.update_damage_overlay()
playsound(src.loc, 'sound/effects/ointment.ogg', 25)
diff --git a/code/modules/mob/living/carbon/rendering.dm b/code/modules/mob/living/carbon/rendering.dm
new file mode 100644
index 000000000000..66d3a9e291e6
--- /dev/null
+++ b/code/modules/mob/living/carbon/rendering.dm
@@ -0,0 +1,66 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2024 silicons *//
+
+/**
+ * flatten all keyed overlays
+ */
+/mob/living/carbon/proc/flatten_standing_overlays()
+ return
+
+/**
+ * rebuild all keyed overlays
+ */
+/mob/living/carbon/proc/rebuild_standing_overlays(dump_all_overlays = TRUE)
+ regenerate_icons()
+ // todo: rebuild inventory
+ reapply_standing_overlays(dump_all_overlays)
+
+/**
+ * reapply all keyed overlays
+ *
+ * * inventory overlays *are* included in this
+ *
+ * @params
+ * * dump_all_overlays - drop all non-priority overlays
+ */
+/mob/living/carbon/proc/reapply_standing_overlays(dump_all_overlays = TRUE)
+ if(dump_all_overlays)
+ cut_overlays()
+ for(var/key in standing_overlays)
+ add_overlay(standing_overlays[key])
+ inventory.reapply_slot_renders()
+
+/**
+ * obliterate a keyed overlay or list of keyed overlays from our overlays
+ *
+ * * inventory overlays are not handled by this; check /datum/inventory
+ */
+/mob/living/carbon/proc/remove_standing_overlay(key, do_not_update)
+ . = standing_overlays[key]
+
+ standing_overlays -= key
+
+ if(!do_not_update)
+ cut_overlay(.)
+
+/**
+ * set a keyed overlay or list of keyed overlays to our overlays
+ *
+ * * inventory overlays are not handled by this; check /datum/inventory
+ */
+/mob/living/carbon/proc/set_standing_overlay(key, list/overlay_or_list, do_not_update)
+ . = standing_overlays[key]
+
+ if(isnull(overlay_or_list) || (islist(overlay_or_list) && !length(overlay_or_list)))
+ standing_overlays -= key
+ // unreference so we don't try to add overlay it
+ overlay_or_list = null
+ else
+ standing_overlays[key] = overlay_or_list
+
+ if(!do_not_update)
+ if(!isnull(.))
+ cut_overlay(.)
+ if(!isnull(overlay_or_list))
+ add_overlay(overlay_or_list)
+
diff --git a/code/modules/mob/living/health.dm b/code/modules/mob/living/health.dm
index edf3f14bf9de..f5bcd9cf190f 100644
--- a/code/modules/mob/living/health.dm
+++ b/code/modules/mob/living/health.dm
@@ -101,7 +101,7 @@
* set body temperature
*/
/mob/living/proc/set_bodytemperature(amt)
- bodytemperature = amt
+ bodytemperature = max(TCMB, amt)
/**
* adjust body temperature
*/
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 51241bb0c7e2..88bb83a1dec7 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -5,13 +5,6 @@
selected_image = image(icon = 'icons/mob/screen1.dmi', loc = src, icon_state = "centermarker")
-/mob/living/prepare_huds()
- ..()
- prepare_data_huds()
-
-/mob/living/proc/prepare_data_huds()
- update_hud_med_all()
-
/mob/living/Destroy()
if(nest) //Ew.
if(istype(nest, /obj/structure/prop/nest))
@@ -393,7 +386,7 @@ default behaviour is:
. += iterating
var/list/returned = iterating.return_inventory()
. += returned
-
+
/mob/living/proc/can_inject(var/mob/user, var/error_msg, var/target_zone, var/ignore_thickness = FALSE)
return 1
@@ -421,9 +414,6 @@ default behaviour is:
/mob/living/proc/restore_all_organs()
return
-/mob/living/proc/UpdateDamageIcon()
- return
-
/mob/living/proc/Examine_OOC()
set name = "Examine Meta-Info (OOC)"
set category = VERB_CATEGORY_OOC
diff --git a/code/modules/mob/living/mobility.dm b/code/modules/mob/living/mobility.dm
index 806aff17988d..9f0697f85618 100644
--- a/code/modules/mob/living/mobility.dm
+++ b/code/modules/mob/living/mobility.dm
@@ -29,6 +29,8 @@
* does not set other mobility flags or update mobility.
*/
/mob/living/proc/set_resting(value)
+ if(HAS_TRAIT(src, TRAIT_MOB_FORCED_STANDING))
+ value = FALSE
if(resting == value)
return
resting = value
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index fa5eb18cd5c0..03cde2178079 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -85,7 +85,7 @@ var/list/ai_verbs_default = list(
/// Used to prevent dummy AIs from spawning with communicators.
var/is_dummy = FALSE
-//! ## MALF VARIABLES
+ //* MALF VARIABLES
/// Master var that determines if AI is malfunctioning.
var/malfunctioning = FALSE
/// Installed piece of hardware.
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index f5ea6663dc93..7991a9fba01c 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -75,7 +75,7 @@
var/integrated_light_power = 4.5
var/datum/wires/robot/wires
-//! ## Icon stuff
+ //* Icon stuff
/// Persistent icontype tracking allows for cleaner icon updates
var/icontype
/// Used to store the associations between sprite names and sprite index.
@@ -85,7 +85,7 @@
/// Remaining attempts to select icon before a selection is forced.
var/icon_selection_tries = 0
-//! ## Hud stuff
+ //* Hud stuff
var/atom/movable/screen/cells = null
var/atom/movable/screen/inv1 = null
@@ -916,6 +916,7 @@
cut_overlays()
if (dogborg)
+ zmm_flags |= ZMM_LOOKAHEAD
// Resting dogborgs don't get overlays.
if (stat == CONSCIOUS && resting)
if(sitting)
@@ -925,6 +926,8 @@
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.
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index 7932630f2010..1d4231208d11 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -267,17 +267,14 @@
var/sensor_type = input("Please select sensor type.", "Sensor Integration", null) as null|anything in list("Security","Medical","Disable")
if(isnull(sensor_type))
return
- switch(hudmode)
- if("Security")
- get_atom_hud(DATA_HUD_SECURITY_ADVANCED).remove_hud_from(src)
- if("Medical")
- get_atom_hud(DATA_HUD_MEDICAL).remove_hud_from(src)
+
+ self_perspective.remove_atom_hud(source = ATOM_HUD_SOURCE_SILICON_SENSOR_AUGMENT)
switch(sensor_type)
if ("Security")
- get_atom_hud(DATA_HUD_SECURITY_ADVANCED).add_hud_to(src)
+ self_perspective.add_atom_hud(/datum/atom_hud/data/human/security/advanced, ATOM_HUD_SOURCE_SILICON_SENSOR_AUGMENT)
to_chat(src,"Security records overlay enabled.")
if ("Medical")
- get_atom_hud(DATA_HUD_MEDICAL).add_hud_to(src)
+ self_perspective.add_atom_hud(/datum/atom_hud/data/human/medical, ATOM_HUD_SOURCE_SILICON_SENSOR_AUGMENT)
to_chat(src,"Life signs monitor overlay enabled.")
if ("Disable")
to_chat(src,"Sensor augmentations disabled.")
diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm
index 3af0ca9ee2b0..092fdf357d27 100644
--- a/code/modules/mob/login.dm
+++ b/code/modules/mob/login.dm
@@ -64,24 +64,12 @@
update_client_color()
- //Reload alternate appearances
- for(var/v in GLOB.active_alternate_appearances)
- if(!v)
- continue
- var/datum/atom_hud/alternate_appearance/AA = v
- AA.onNewMob(src)
-
- if(!client.tooltips)
- client.tooltips = new(client)
-
var/turf/T = get_turf(src)
if(isturf(T))
update_client_z(T.z)
SEND_SIGNAL(src, COMSIG_MOB_CLIENT_LOGIN, client)
- reload_huds()
-
// reset perspective to using
reset_perspective(no_optimizations = TRUE)
// load rendering onto client's screen
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index cc40541682f3..5360221ecc45 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -23,15 +23,12 @@
// physiology
init_physiology()
// atom HUDs
- set_key_focus(src)
prepare_huds()
- for(var/v in GLOB.active_alternate_appearances)
- if(!v)
- continue
- var/datum/atom_hud/alternate_appearance/AA = v
- AA.onNewMob(src)
+ set_key_focus(src)
// todo: remove hooks
hook_vr("mob_new",list(src))
+ // signal
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOBAL_MOB_NEW, src)
// abilities
init_abilities()
// inventory
@@ -81,6 +78,8 @@
else
// mind is not ours, null it out
mind = null
+ // signal
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOBAL_MOB_DEL, src)
// abilities
dispose_abilities()
// this kicks out client
@@ -127,7 +126,7 @@
*
* This is simply "mob_"+ a global incrementing counter that goes up for every mob
*/
-/mob/GenerateTag()
+/mob/generate_tag()
tag = "mob_[++next_mob_id]"
/**
@@ -135,20 +134,15 @@
*
* Goes through hud_possible list and adds the images to the hud_list variable (if not already
* cached)
+ *
+ * todo: this should be atom level but uhh lmao lol
*/
-/atom/proc/prepare_huds()
- hud_list = list()
- for(var/hud in hud_possible)
- var/hint = hud_possible[hud]
- switch(hint)
- if(HUD_LIST_LIST)
- hud_list[hud] = list()
- else
- var/image/I = image(GLOB.hud_icon_files[hud] || 'icons/screen/atom_hud/misc.dmi', src, "")
- I.plane = FLOAT_PLANE
- I.layer = FLOAT_LAYER + 100 + (GLOB.hud_icon_layers[hud] || 0)
- I.appearance_flags = RESET_COLOR|RESET_TRANSFORM|KEEP_APART
- hud_list[hud] = I
+/mob/proc/prepare_huds()
+ if(!atom_huds_to_initialize)
+ return
+ for(var/hud in atom_huds_to_initialize)
+ update_atom_hud_provider(src, hud)
+ atom_huds_to_initialize = null
/mob/proc/remove_screen_obj_references()
hands = null
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index b0512526e191..98bb8ff77031 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -50,8 +50,11 @@
/// Atom we're buckl**ing** to. Used to stop stuff like lava from incinerating those who are mid buckle.
var/atom/movable/buckling
- //* HUD
+ //* HUD (Atom)
+ /// HUDs to initialize, typepaths
+ var/list/atom_huds_to_initialize
+ //* HUD
/// active, opened storage
// todo: doesn't clear from clients properly on logout, relies on login clearing screne.
// todo: we'll eventually need a system to handle ckey transfers properly.
@@ -246,7 +249,7 @@
var/timeofdeath = 0 //?Living
// todo: go to carbon, simple mobs don't need environmental stabilization
- var/bodytemperature = 310.055 //98.7 F
+ var/bodytemperature = 310.055 //98.7 F or 36,905 C
var/drowsyness = 0 //?Carbon
var/nutrition = 400 //?Carbon
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index ece66f8d706c..de5a862f371e 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -605,7 +605,7 @@ var/list/global/organ_rel_size = list(
return
// They may have hidden the icons in the bottom left with the hide button.
- if(!hud_used.inventory_shown && !held && (resolve_inventory_slot_meta(slot)?.inventory_slot_flags & INV_SLOT_HUD_REQUIRES_EXPAND))
+ if(!hud_used.inventory_shown && !held && (resolve_inventory_slot(slot)?.inventory_slot_flags & INV_SLOT_HUD_REQUIRES_EXPAND))
item.screen_loc = null
return
diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm
index 1627c58d1cb9..ec71b233cd4d 100644
--- a/code/modules/mob/new_player/new_player.dm
+++ b/code/modules/mob/new_player/new_player.dm
@@ -478,7 +478,6 @@
// Moving wheelchair if they have one
if(character.buckled && istype(character.buckled, /obj/structure/bed/chair/wheelchair))
character.buckled.forceMove(character.loc)
- character.buckled.setDir(character.dir)
SSticker.mode.latespawn(character)
diff --git a/code/modules/mob/observer/dead/dead.dm b/code/modules/mob/observer/dead/dead.dm
index f92102db5cd9..8341927ae1de 100644
--- a/code/modules/mob/observer/dead/dead.dm
+++ b/code/modules/mob/observer/dead/dead.dm
@@ -112,14 +112,15 @@ GLOBAL_LIST_EMPTY(observer_list)
var/mob/living/carbon/human/H = body
icon = H.icon
icon_state = H.icon_state
- if(H.overlays_standing)
- for(var/i in 1 to length(H.overlays_standing))
- if(!H.overlays_standing[i])
- continue
- add_overlay(H.overlays_standing[i])
+ // todo: fixup (get rid of other planes)
+ for(var/key in H.standing_overlays)
+ add_overlay(H.standing_overlays[key])
+ for(var/slot_id in H.inventory.rendered_normal_overlays)
+ add_overlay(H.inventory.rendered_normal_overlays[slot_id])
else
icon = body.icon
icon_state = body.icon_state
+ // todo: fixup (get rid of other planes)
add_overlay(body.overlays)
gender = body.gender
@@ -142,12 +143,6 @@ GLOBAL_LIST_EMPTY(observer_list)
T = locate(1,1,1)
forceMove(T)
- for(var/v in GLOB.active_alternate_appearances)
- if(!v)
- continue
- var/datum/atom_hud/alternate_appearance/AA = v
- AA.onNewMob(src)
-
if(!name) //To prevent nameless ghosts
name = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names))
real_name = name
@@ -305,9 +300,9 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
medHUD = !medHUD
if(medHUD)
- get_atom_hud(DATA_HUD_MEDICAL).add_hud_to(src)
+ self_perspective.add_atom_hud(/datum/atom_hud/data/human/medical, ATOM_HUD_SOURCE_OBSERVER)
else
- get_atom_hud(DATA_HUD_MEDICAL).remove_hud_from(src)
+ self_perspective.remove_atom_hud(/datum/atom_hud/data/human/medical, ATOM_HUD_SOURCE_OBSERVER)
to_chat(src,"Medical HUD [medHUD ? "Enabled" : "Disabled"]")
/mob/observer/dead/verb/toggle_antagHUD()
@@ -330,11 +325,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
has_enabled_antagHUD = TRUE
antagHUD = !antagHUD
- var/datum/atom_hud/H = GLOB.huds[ANTAG_HUD]
if(antagHUD)
- H.add_hud_to(src)
+ self_perspective.add_atom_hud(/datum/atom_hud/antag, ATOM_HUD_SOURCE_OBSERVER)
else
- H.remove_hud_from(src)
+ self_perspective.remove_atom_hud(/datum/atom_hud/antag, ATOM_HUD_SOURCE_OBSERVER)
to_chat(src,"AntagHUD [antagHUD ? "Enabled" : "Disabled"]")
/mob/observer/dead/proc/dead_tele(var/area/A in GLOB.sortedAreas)
diff --git a/code/modules/mob/observer/dead/orbit.dm b/code/modules/mob/observer/dead/orbit.dm
index e3099ce8591f..804cc45f1b3e 100644
--- a/code/modules/mob/observer/dead/orbit.dm
+++ b/code/modules/mob/observer/dead/orbit.dm
@@ -97,6 +97,6 @@
return data
-/datum/orbit_menu/ui_assets(mob/user)
- . = ..() || list()
- . += get_asset_datum(/datum/asset/simple/orbit)
+/datum/orbit_menu/ui_asset_injection(datum/tgui/ui, list/immediate, list/deferred)
+ immediate += /datum/asset_pack/simple/orbit
+ return ..()
diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm
index 85bc9b61c77c..a9451d4e3046 100644
--- a/code/modules/mob/say.dm
+++ b/code/modules/mob/say.dm
@@ -12,6 +12,11 @@
if(message)
me_verb_subtle(message)
+/mob/proc/subtler_wrapper()
+ var/message = input("","subtler (text)") as message|null
+ if(message)
+ subtler_anti_ghost(message)
+
/mob/verb/whisper(message as text)
set name = "Whisper"
set category = VERB_CATEGORY_IC
diff --git a/code/modules/mob/update_icons.dm b/code/modules/mob/update_icons.dm
index adb10c898cdf..a5e110a93bbc 100644
--- a/code/modules/mob/update_icons.dm
+++ b/code/modules/mob/update_icons.dm
@@ -20,6 +20,9 @@
// End obsolete
+/mob/proc/update_damage_overlay()
+ return
+
/mob/proc/update_hud()
return
diff --git a/code/modules/modular_computers/computers/modular_computer/ui.dm b/code/modules/modular_computers/computers/modular_computer/ui.dm
index 2ae564ead147..97989cb91a73 100644
--- a/code/modules/modular_computers/computers/modular_computer/ui.dm
+++ b/code/modules/modular_computers/computers/modular_computer/ui.dm
@@ -1,8 +1,7 @@
// Operates TGUI
-/obj/item/modular_computer/ui_assets(mob/user)
- return list(
- get_asset_datum(/datum/asset/simple/headers)
- )
+/obj/item/modular_computer/ui_asset_injection(datum/tgui/ui, list/immediate, list/deferred)
+ immediate += /datum/asset_pack/simple/headers
+ return ..()
/obj/item/modular_computer/ui_interact(mob/user, datum/tgui/ui)
if(!screen_on || !enabled)
diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm
index ff1c41b2f6ba..b1e018908fa7 100644
--- a/code/modules/modular_computers/file_system/program.dm
+++ b/code/modules/modular_computers/file_system/program.dm
@@ -151,10 +151,9 @@
QDEL_NULL(TM)
return 1
-/datum/computer_file/program/ui_assets(mob/user)
- return list(
- get_asset_datum(/datum/asset/simple/headers)
- )
+/datum/computer_file/program/ui_asset_injection(datum/tgui/ui, list/immediate, list/deferred)
+ immediate += /datum/asset_pack/simple/headers
+ return ..()
/datum/computer_file/program/ui_interact(mob/user, datum/tgui/ui)
if(program_state != PROGRAM_STATE_ACTIVE)
diff --git a/code/modules/modular_computers/file_system/programs/generic/game.dm b/code/modules/modular_computers/file_system/programs/generic/game.dm
index 5fe42f33b9d1..f3f5c1cd2d79 100644
--- a/code/modules/modular_computers/file_system/programs/generic/game.dm
+++ b/code/modules/modular_computers/file_system/programs/generic/game.dm
@@ -82,10 +82,9 @@
* UI assets define a list of asset datums to be sent with the UI.
* In this case, it's a bunch of cute enemy sprites.
*/
-/datum/computer_file/program/game/ui_assets(mob/user)
- return list(
- get_asset_datum(/datum/asset/simple/arcade),
- )
+/datum/computer_file/program/game/ui_asset_injection(datum/tgui/ui, list/immediate, list/deferred)
+ immediate += /datum/asset_pack/simple/arcade
+ return ..()
/**
* This provides all of the relevant data to the UI in a list().
diff --git a/code/modules/multiz/turf.dm b/code/modules/multiz/turf.dm
index f3b45acfb6e9..a4261ffa6b4a 100644
--- a/code/modules/multiz/turf.dm
+++ b/code/modules/multiz/turf.dm
@@ -116,13 +116,6 @@
/turf/check_impact(atom/movable/falling_atom)
return TRUE
-/turf/smooth_icon()
- . = ..()
- if(SSzmimic.initialized)
- var/turf/simulated/open/above = above()
- if(istype(above))
- above.queue()
-
//* lookups
/turf/proc/above()
diff --git a/code/modules/multiz/turfs/open.dm b/code/modules/multiz/turfs/open.dm
index 33e1ad1d90f5..2a629990ab9e 100644
--- a/code/modules/multiz/turfs/open.dm
+++ b/code/modules/multiz/turfs/open.dm
@@ -16,7 +16,6 @@
. = ..()
icon_state = ""
ASSERT(!isnull(below()))
- queue()
/turf/simulated/open/Entered(atom/movable/mover)
..()
@@ -29,11 +28,6 @@
if(AM.movement_type & MOVEMENT_GROUND)
AM.fall()
-/turf/simulated/open/proc/queue()
- if(smoothing_flags & SMOOTH_QUEUED)
- return
- smoothing_flags |= SMOOTH_QUEUED
-
//! We hijack smoothing flags.
/turf/simulated/open/smooth_icon()
return // Nope.amv
diff --git a/code/modules/multiz/zmimic/mimic_movable.dm b/code/modules/multiz/zmimic/mimic_movable.dm
index 43a9c075e8a6..9c3bf5afb619 100644
--- a/code/modules/multiz/zmimic/mimic_movable.dm
+++ b/code/modules/multiz/zmimic/mimic_movable.dm
@@ -4,39 +4,18 @@
/// Movable-level Z-Mimic flags. This uses ZMM_* flags, not ZM_* flags.
var/zmm_flags = NONE
-/atom/movable/doMove(atom/destination)
- . = ..(destination)
+/atom/movable/doMove(atom/dest)
+ . = ..(dest)
if (. && bound_overlay)
// The overlay will handle cleaning itself up on non-openspace turfs.
- if (isturf(destination))
- var/turf/current = get_turf(src)
- bound_overlay.doMove(current.above)
+ if (isturf(dest))
+ // If you feel a need to change this from get_step(), don't. Any change that would require that is illegal with Z-Mimic's layering model.
+ bound_overlay.doMove(get_step(src, UP))
if (bound_overlay && dir != bound_overlay.dir)
bound_overlay.setDir(dir)
else // Not a turf, so we need to destroy immediately instead of waiting for the destruction timer to proc.
qdel(bound_overlay)
-/atom/movable/Move(...)
- . = ..()
- if (!.)
- return
-
- if (bound_overlay)
- var/turf/current = get_turf(src)
- bound_overlay.doMove(current.above)
- // forceMove could've deleted our overlay
- if (bound_overlay && bound_overlay.dir != dir)
- bound_overlay.setDir(dir)
-
- if (light_source_solo)
- light_source_solo.source_atom.update_light()
- else if (light_source_multi)
- var/datum/light_source/L
- var/thing
- for (thing in light_source_multi)
- L = thing
- L.source_atom.update_light()
-
/atom/movable/setDir(ndir)
. = ..()
if (. && bound_overlay)
@@ -47,10 +26,10 @@
return
if (MOVABLE_IS_BELOW_ZTURF(src))
- SSzmimic.queued_overlays += bound_overlay
+ SSzcopy.queued_overlays += bound_overlay
bound_overlay.queued += 1
- else if (bound_overlay && !bound_overlay.destruction_timer)
- bound_overlay.destruction_timer = addtimer(CALLBACK(bound_overlay, TYPE_PROC_REF(/datum, qdel_self)), 10 SECONDS, TIMER_STOPPABLE)
+ else
+ qdel(bound_overlay)
// Grabs a list of every openspace object that's directly or indirectly mimicking this object. Returns an empty list if none found.
/atom/movable/proc/get_above_oo()
@@ -67,7 +46,7 @@
atom_flags = ATOM_ABSTRACT
anchored = TRUE
mouse_opacity = FALSE
- abstract_type = /atom/movable/openspace // unsure if this is valid, check with Lohi
+ abstract_type = /atom/movable/openspace
/atom/movable/openspace/can_fall()
return FALSE
@@ -119,34 +98,36 @@
var/turf/myturf = loc
if (istype(myturf))
myturf.shadower = null
+
return ..()
-/atom/movable/openspace/multiplier/proc/copy_lighting(atom/movable/lighting_overlay/LO)
+/atom/movable/openspace/multiplier/proc/copy_lighting(atom/movable/lighting_overlay/LO, use_shadower_mult = TRUE)
appearance = LO
layer = MIMICED_LIGHTING_LAYER_MAIN
plane = OPENTURF_MAX_PLANE
blend_mode = BLEND_MULTIPLY
invisibility = 0
- if (icon_state == LIGHTING_BASE_ICON_STATE)
- // We're using a color matrix, so just darken the colors across the board.
- var/list/c_list = color
- c_list[CL_MATRIX_RR] *= SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_RG] *= SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_RB] *= SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_GR] *= SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_GG] *= SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_GB] *= SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_BR] *= SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_BG] *= SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_BB] *= SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_AR] *= SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_AG] *= SHADOWER_DARKENING_FACTOR
- c_list[CL_MATRIX_AB] *= SHADOWER_DARKENING_FACTOR
- color = c_list
- else
- // Not a color matrix, so we can just use the color var ourselves.
- color = SHADOWER_DARKENING_COLOR
+ if (use_shadower_mult)
+ if (icon_state == LIGHTING_BASE_ICON_STATE)
+ // We're using a color matrix, so just darken the colors across the board.
+ var/list/c_list = color
+ c_list[CL_MATRIX_RR] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_RG] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_RB] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_GR] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_GG] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_GB] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_BR] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_BG] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_BB] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_AR] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_AG] *= SHADOWER_DARKENING_FACTOR
+ c_list[CL_MATRIX_AB] *= SHADOWER_DARKENING_FACTOR
+ color = c_list
+ else
+ // Not a color matrix, so we can just use the color var ourselves.
+ color = SHADOWER_DARKENING_COLOR
if (our_overlays || priority_overlays)
compile_overlays()
@@ -154,9 +135,9 @@
// compile_overlays() calls update_above().
update_above()
-//! -- OPENSPACE MIMIC --
+// -- OPENSPACE MIMIC --
-/// Object used to hold a mimiced atom's appearance.
+// Object used to hold a mimiced atom's appearance.
/atom/movable/openspace/mimic
plane = OPENTURF_MAX_PLANE
var/atom/movable/associated_atom
@@ -170,10 +151,10 @@
/atom/movable/openspace/mimic/New()
atom_flags |= ATOM_INITIALIZED
- SSzmimic.openspace_overlays += 1
+ SSzcopy.openspace_overlays += 1
/atom/movable/openspace/mimic/Destroy()
- SSzmimic.openspace_overlays -= 1
+ SSzcopy.openspace_overlays -= 1
queued = 0
if (associated_atom)
@@ -188,26 +169,40 @@
/atom/movable/openspace/mimic/attackby(obj/item/W, mob/user)
to_chat(user, SPAN_NOTICE("\The [src] is too far away."))
-/atom/movable/openspace/mimic/attack_hand(mob/user, list/params)
+/atom/movable/openspace/mimic/attack_hand(mob/user)
to_chat(user, SPAN_NOTICE("You cannot reach \the [src] from here."))
/atom/movable/openspace/mimic/examine(...)
SHOULD_CALL_PARENT(FALSE)
. = associated_atom.examine(arglist(args)) // just pass all the args to the copied atom
-/atom/movable/openspace/mimic/doMove(atom/destination)
+/atom/movable/openspace/mimic/doMove(turf/dest)
+ var/atom/old_loc = loc
. = ..()
- if (MOVABLE_IS_BELOW_ZTURF(associated_atom))
+ if (TURF_IS_MIMICKING(dest))
if (destruction_timer)
deltimer(destruction_timer)
destruction_timer = null
+ if (old_loc.z != loc.z)
+ reset_internal_layering()
else if (!destruction_timer)
- destruction_timer = addtimer(CALLBACK(src, TYPE_PROC_REF(/datum, qdel_self)), 10 SECONDS, TIMER_STOPPABLE)
+ destruction_timer = ZM_DESTRUCTION_TIMER(src)
// Called when the turf we're on is deleted/changed.
/atom/movable/openspace/mimic/proc/owning_turf_changed()
if (!destruction_timer)
- destruction_timer = addtimer(CALLBACK(src, TYPE_PROC_REF(/datum, qdel_self)), 10 SECONDS, TIMER_STOPPABLE)
+ destruction_timer = ZM_DESTRUCTION_TIMER(src)
+
+/atom/movable/openspace/mimic/proc/reset_internal_layering()
+ if (bound_overlay?.override_depth)
+ depth = bound_overlay.override_depth
+ else if (isturf(associated_atom.loc))
+ depth = min(SSzcopy.zlev_maximums[associated_atom.z] - associated_atom.z, OPENTURF_MAX_DEPTH)
+ override_depth = depth
+
+ plane = OPENTURF_MAX_PLANE - depth
+
+ bound_overlay?.reset_internal_layering()
// -- TURF PROXY --
diff --git a/code/modules/multiz/zmimic/mimic_turf.dm b/code/modules/multiz/zmimic/mimic_turf.dm
index f4a025cac965..adb5472e5f9e 100644
--- a/code/modules/multiz/zmimic/mimic_turf.dm
+++ b/code/modules/multiz/zmimic/mimic_turf.dm
@@ -1,4 +1,3 @@
-
/turf
/// The z-turf above us, if present.
var/tmp/turf/above
@@ -16,8 +15,10 @@
var/tmp/z_queued = 0
/// If this Z-turf leads to space, uninterrupted.
var/tmp/z_eventually_space = FALSE
+ /// Use this appearance for our appearance instead of `appearance`. If MZ_OVERRIDE is set, *only* this will be visible, no movables will be copied.
+ var/z_appearance
- //debug
+ // debug
var/tmp/z_depth
var/tmp/z_generation = 0
@@ -29,7 +30,7 @@
if(mz_flags & MZ_MIMIC_BELOW)
z_queued += 1
// This adds duplicates for a reason. Do not change this unless you understand how ZM queues work.
- SSzmimic.queued_turfs += src
+ SSzcopy.queued_turfs += src
/// Enables Z-mimic for a turf that didn't already have it enabled.
/turf/proc/enable_zmimic(additional_flags = 0)
@@ -54,7 +55,7 @@
if (shadower)
CRASH("Attempt to enable Z-mimic on already-enabled turf!")
shadower = new(src)
- SSzmimic.openspace_turfs += 1
+ SSzcopy.openspace_turfs += 1
var/turf/under = below()
if (under)
below = under
@@ -67,7 +68,7 @@
/// Cleans up Z-mimic objects for this turf. You shouldn't call this directly 99% of the time.
/turf/proc/cleanup_zmimic()
- SSzmimic.openspace_turfs -= 1
+ SSzcopy.openspace_turfs -= 1
// Don't remove ourselves from the queue, the subsystem will explode. We'll naturally fall out of the queue.
z_queued = 0
@@ -87,9 +88,3 @@
if (below)
below.above = null
below = null
-
-/turf/Entered(atom/movable/thing, atom/oldLoc)
- ..()
- if (thing.bound_overlay || (thing.zmm_flags & ZMM_IGNORE) || thing.invisibility == INVISIBILITY_ABSTRACT || !TURF_IS_MIMICKING(above))
- return
- above.update_mimic()
diff --git a/code/modules/nano/nanoui.dm b/code/modules/nano/nanoui.dm
index a505448584dc..ce088950e478 100644
--- a/code/modules/nano/nanoui.dm
+++ b/code/modules/nano/nanoui.dm
@@ -58,8 +58,6 @@
var/list/datum/nanoui/children = list()
var/datum/topic_state/state = null
- var/static/datum/asset/simple/namespaced/nanoui/nano_asset
-
/**
* Create a new nanoui instance.
*
@@ -75,8 +73,6 @@
* @return /nanoui new nanoui object
*/
/datum/nanoui/New(mob/nuser, nsrc_object, nui_key, ntemplate_filename, ntitle, nwidth, nheight, atom/nref, datum/nanoui/master_ui, datum/topic_state/state = default_state)
- if(!istype(nano_asset))
- nano_asset = get_asset_datum(/datum/asset/simple/namespaced/nanoui)
user = nuser
src_object = nsrc_object
ui_key = nui_key
@@ -100,8 +96,8 @@
ref = nref
add_common_assets()
- if (nuser?.client)
- nano_asset.send(nuser.client) //ship it
+
+ SSassets.send_asset_pack(nuser.client, /datum/asset_pack/simple/nanoui)
/**
* Use this proc to add assets which are common to (and required by) all nano uis
@@ -347,17 +343,19 @@
var/head_content = ""
+ var/datum/asset_pack/nanoui_pack = SSassets.ready_asset_pack(/datum/asset_pack/simple/nanoui)
+
for(var/filename in scripts)
- head_content += ""
+ head_content += ""
for(var/filename in stylesheets)
- head_content += ""
+ head_content += ""
var/template_data_json = "{}" // An empty JSON object
if (templates.len > 0)
// transform urls
for(var/key in templates)
- templates[key] = SSassets.transport.get_asset_url(templates[key])
+ templates[key] = nanoui_pack.get_url(templates[key])
template_data_json = json_encode(templates)
var/list/send_data = get_send_data(initial_data)
@@ -420,6 +418,7 @@
if(status == UI_CLOSE)
return
+ SSassets.send_asset_pack(user.client, /datum/asset_pack/simple/nanoui)
user << browse(get_html(), "window=[window_id];[window_size][window_options]")
winset(user, "mapwindow.map", "focus=true") // return keyboard focus to map
on_close_winset()
diff --git a/code/modules/nifsoft/software/01_vision.dm b/code/modules/nifsoft/software/01_vision.dm
index 9dcaf7c17040..a33feae1eb1a 100644
--- a/code/modules/nifsoft/software/01_vision.dm
+++ b/code/modules/nifsoft/software/01_vision.dm
@@ -9,15 +9,15 @@
if(.)
// i'd refactor nifsofts but i have a personal goddamn vendetta against nifs
for(var/i in data_huds)
- var/datum/atom_hud/H = GLOB.huds[i]
- H.add_hud_to(nif.human)
+ var/datum/atom_hud/hud = GLOB.atom_huds[i]
+ nif.human.self_perspective.add_atom_hud(hud, ATOM_HUD_SOURCE_NIF)
/datum/nifsoft/hud/deactivate(force)
. = ..()
if(.)
for(var/i in data_huds)
- var/datum/atom_hud/H = GLOB.huds[i]
- H.remove_hud_from(nif.human)
+ var/datum/atom_hud/hud = GLOB.atom_huds[i]
+ nif.human.self_perspective.remove_atom_hud(hud, ATOM_HUD_SOURCE_NIF)
/datum/nifsoft/hud/ar_civ
name = "AR Overlay (Civ)"
@@ -27,7 +27,7 @@
a_drain = 0.01
planes_enabled = list(/atom/movable/screen/plane_master/augmented)
vision_flags = (NIF_V_AR_CIVILIAN)
- data_huds = list(DATA_HUD_ID_JOB)
+ data_huds = list(/datum/atom_hud/data/human/job_id)
incompatible_with = list(NIF_MEDICAL_AR,NIF_SECURITY_AR,NIF_ENGINE_AR,NIF_SCIENCE_AR,NIF_OMNI_AR)
/datum/nifsoft/hud/ar_med
@@ -38,7 +38,7 @@
access = ACCESS_MEDICAL_MAIN
a_drain = 0.01
planes_enabled = list(/atom/movable/screen/plane_master/augmented)
- data_huds = list(DATA_HUD_MEDICAL)
+ data_huds = list(/datum/atom_hud/data/human/medical)
vision_flags = (NIF_V_AR_MEDICAL)
incompatible_with = list(NIF_CIVILIAN_AR,NIF_SECURITY_AR,NIF_ENGINE_AR,NIF_SCIENCE_AR,NIF_OMNI_AR)
@@ -49,7 +49,7 @@
cost = 150
access = ACCESS_SECURITY_EQUIPMENT
a_drain = 0.01
- data_huds = list(DATA_HUD_SECURITY_ADVANCED)
+ data_huds = list(/datum/atom_hud/data/human/security/advanced)
planes_enabled = list(/atom/movable/screen/plane_master/augmented)
vision_flags = (NIF_V_AR_SECURITY)
incompatible_with = list(NIF_CIVILIAN_AR,NIF_MEDICAL_AR,NIF_ENGINE_AR,NIF_SCIENCE_AR,NIF_OMNI_AR)
@@ -61,7 +61,7 @@
cost = 150
access = ACCESS_ENGINEERING_MAIN
a_drain = 0.01
- data_huds = list(DATA_HUD_ID_JOB)
+ data_huds = list(/datum/atom_hud/data/human/job_id)
planes_enabled = list(/atom/movable/screen/plane_master/augmented)
vision_flags = (NIF_V_AR_ENGINE)
incompatible_with = list(NIF_CIVILIAN_AR,NIF_MEDICAL_AR,NIF_SECURITY_AR,NIF_SCIENCE_AR,NIF_OMNI_AR)
@@ -73,7 +73,7 @@
cost = 50
access = ACCESS_SCIENCE_MAIN
a_drain = 0.01
- data_huds = list(DATA_HUD_ID_JOB)
+ data_huds = list(/datum/atom_hud/data/human/job_id)
planes_enabled = list(/atom/movable/screen/plane_master/augmented)
vision_flags = (NIF_V_AR_SCIENCE)
incompatible_with = list(NIF_CIVILIAN_AR,NIF_MEDICAL_AR,NIF_SECURITY_AR,NIF_ENGINE_AR,NIF_OMNI_AR)
@@ -85,7 +85,7 @@
cost = 250
access = ACCESS_COMMAND_CAPTAIN
a_drain = 0.01
- data_huds = list(DATA_HUD_SECURITY_ADVANCED, DATA_HUD_MEDICAL)
+ data_huds = list(/datum/atom_hud/data/human/security/advanced, /datum/atom_hud/data/human/medical)
planes_enabled = list(/atom/movable/screen/plane_master/augmented)
vision_flags = (NIF_V_AR_OMNI)
incompatible_with = list(NIF_CIVILIAN_AR,NIF_MEDICAL_AR,NIF_SECURITY_AR,NIF_ENGINE_AR,NIF_SCIENCE_AR)
diff --git a/code/modules/nifsoft/software/05_health.dm b/code/modules/nifsoft/software/05_health.dm
index 86c87cb8c36a..783504558917 100644
--- a/code/modules/nifsoft/software/05_health.dm
+++ b/code/modules/nifsoft/software/05_health.dm
@@ -114,7 +114,7 @@
W.heal_damage(0.1)
EO.update_damages()
if(EO.update_icon())
- nif.human.UpdateDamageIcon(1)
+ nif.human.update_damage_overlay(1)
nif.use_charge(0.1)
return TRUE //Return entirely, we only heal one at a time.
else if(mode == 1)
diff --git a/code/modules/nifsoft/software/13_soulcatcher.dm b/code/modules/nifsoft/software/13_soulcatcher.dm
index e418080f2800..fa1d267a3bfb 100644
--- a/code/modules/nifsoft/software/13_soulcatcher.dm
+++ b/code/modules/nifsoft/software/13_soulcatcher.dm
@@ -544,8 +544,6 @@
dummy.compile_overlays()
dummy.alpha = 192
- // remove hudlist
- dummy.cut_overlay(dummy.hud_list)
// appearance clone immediately
appearance = dummy.appearance
plane = AUGMENTED_PLANE
diff --git a/code/modules/nifsoft/software/15_misc.dm b/code/modules/nifsoft/software/15_misc.dm
index 3a30b55eef1e..d36438d51420 100644
--- a/code/modules/nifsoft/software/15_misc.dm
+++ b/code/modules/nifsoft/software/15_misc.dm
@@ -173,13 +173,9 @@
if((. = ..()))
if((. = ..()))
var/mob/living/carbon/human/H = nif.human
- var/datum/atom_hud/world_bender/animals/A = GLOB.huds[WORLD_BENDER_HUD_ANIMALS]
- if(A && H)
- A.add_hud_to(H)
+ H.self_perspective.add_atom_hud(/datum/atom_hud/world_bender, ATOM_HUD_SOURCE_NIF)
/datum/nifsoft/worldbend/deactivate(var/force = FALSE)
if((. = ..()))
var/mob/living/carbon/human/H = nif.human
- var/datum/atom_hud/world_bender/animals/A = GLOB.huds[WORLD_BENDER_HUD_ANIMALS]
- if(A && H)
- A.remove_hud_from(H)
+ H.self_perspective.remove_atom_hud(/datum/atom_hud/world_bender, ATOM_HUD_SOURCE_NIF)
diff --git a/code/modules/organs/external/external.dm b/code/modules/organs/external/external.dm
index 2c498aea62a7..4b7282d15d9b 100644
--- a/code/modules/organs/external/external.dm
+++ b/code/modules/organs/external/external.dm
@@ -27,14 +27,14 @@
/// Number of wounds we have - some wounds like bruises will collate into one datum representing all of them.
var/wound_tally
-//! ## STRINGS
+ //* ## STRINGS
/// Fracture description if any.
var/broken_description
/// Modifier used for generating the on-mob damage overlay for this limb.
var/damage_state = "00"
-//! ## DAMAGE VARS
+ //* ## DAMAGE VARS
/// Multiplier for incoming brute damage.
var/brute_mod = 1
/// As above for burn.
@@ -59,7 +59,7 @@
var/base_miss_chance = 20
-//! ## APPEARANCE VARS
+ //* ## APPEARANCE VARS
/// Snowflake warning, reee. Used for slime limbs.
var/nonsolid
/// Also for slimes. Used for transparent limbs.
@@ -92,7 +92,7 @@
/// Markings (body_markings) to apply to the icon
var/list/markings = list()
-//! ## STRUCTURAL VARS
+ //* ## STRUCTURAL VARS
/// Master-limb.
var/obj/item/organ/external/parent
/// Sub-limbs.
@@ -105,7 +105,7 @@
var/organ_rel_size = 25
var/atom/movable/splinted
-//! ## JOINT/STATE VARS
+ //* ## JOINT/STATE VARS
/// It would be more appropriate if these two were named "affects_grasp" and "affects_stand" at this point
var/can_grasp
/// Modifies stance tally/ability to stand.
@@ -128,7 +128,7 @@
var/encased
-//! ## SURGERY VARS
+ //* ## SURGERY VARS
var/open = FALSE
var/stage = FALSE
var/cavity = FALSE
@@ -186,13 +186,13 @@
var/burn_damage = 0
switch(severity)
if (1)
- burn_damage += rand(10, 16)
+ burn_damage += rand(10, 14)
if (2)
- burn_damage += rand(8, 12)
+ burn_damage += rand(7, 8.5)
if(3)
burn_damage += rand(4, 8)
if(4)
- burn_damage += rand(2, 6)
+ burn_damage += rand(2, 5)
if(burn_damage)
inflict_bodypart_damage(
@@ -357,6 +357,9 @@
// todo: this is awful
var/sharp = damage_mode & DAMAGE_MODE_SHARP
var/edge = damage_mode & DAMAGE_MODE_EDGE
+ // cache owner incase we get detached
+ // todo: this is awful
+ var/mob/living/carbon/owner = src.owner
// todo: lol this is shit
// legacy: organ damage on high damage
@@ -379,7 +382,7 @@
if(weapon_descriptor)
add_autopsy_data(weapon_descriptor, brute + burn)
- //! LEGACY BELOW
+ //* LEGACY BELOW
var/can_cut = (sharp) && (robotic < ORGAN_ROBOT)
@@ -399,7 +402,10 @@
else if(!(damage_mode & DAMAGE_MODE_NO_OVERFLOW))
var/overflow_brute = brute - can_inflict_brute
// keep allowing it, but, diminishing returns
- var/damage_anyways_brute = brute * min(1, 1 / ((brute_dam + damage_softcap_intensifier) / (damage_softcap_intensifier + max_damage)))
+ var/damage_anyways_brute = !(damage_mode & DAMAGE_MODE_NO_OVERFLOW) && ( \
+ overflow_brute * min(1, 1 / ((max(brute_dam, max_damage) + damage_softcap_intensifier) / (damage_softcap_intensifier + max_damage))) \
+ )
+ overflow_brute -= damage_anyways_brute
if(can_cut)
if(sharp && !edge)
create_wound( PIERCE, damage_anyways_brute )
@@ -413,11 +419,13 @@
var/can_inflict_burn = max(0, max_damage - burn_dam)
if(can_inflict_burn >= burn)
create_wound( BURN, burn )
- else if(!(damage_mode & DAMAGE_MODE_NO_OVERFLOW))
+ else
var/overflow_burn = burn - can_inflict_burn
- // keep allowing it, but, diminishing returns
- var/damage_anyways_burn = burn * min(1, 1 / ((burn_dam + damage_softcap_intensifier) / (damage_softcap_intensifier + max_damage)))
- create_wound( BURN, damage_anyways_burn )
+ var/damage_anyways_burn = !(damage_mode & DAMAGE_MODE_NO_OVERFLOW) && ( \
+ overflow_burn * min(1, 1 / ((max(burn_dam, max_damage) + damage_softcap_intensifier) / (damage_softcap_intensifier + max_damage))) \
+ )
+ overflow_burn -= damage_anyways_burn
+ create_wound(BURN, damage_anyways_burn + can_inflict_burn)
// rest goes into shock
owner.shock_stage += overflow_burn * 0.33
@@ -438,8 +446,8 @@
// and the brute damage dealt exceeds the tearoff threshold, the organ is torn off.
//Check edge eligibility
- //! edge eligibility disabled; organs should optimally not reqiure the item reference and should instead
- //! get descriptors (damage, damage mode, etc) of the inbound attack.
+ //* edge eligibility disabled; organs should optimally not reqiure the item reference and should instead
+ //* get descriptors (damage, damage mode, etc) of the inbound attack.
var/edge_eligible = edge
// var/edge_eligible = 0
// if(edge)
@@ -462,7 +470,7 @@
droplimb(0, DROPLIMB_BLUNT)
else if(brute >= max_damage / DROPLIMB_THRESHOLD_TEAROFF && prob(brute*0.33))
droplimb(0, DROPLIMB_EDGE)
- //! damage spreading disabled; the attacking weapon should handle this if necessary.
+ //* damage spreading disabled; the attacking weapon should handle this if necessary.
// else if(spread_dam && owner && parent && (brute_overflow || burn_overflow) && (brute_overflow >= 5 || burn_overflow >= 5) && !permutation) //No infinite damage loops.
// var/brute_third = brute_overflow * 0.33
// var/burn_third = burn_overflow * 0.33
@@ -475,7 +483,7 @@
// C.take_damage(brute_on_children, burn_on_children, 0, 0, null, forbidden_limbs, 1) //Splits the damage to each individual 'child', incase multiple exist.
// parent.take_damage(brute_third, burn_third, 0, 0, null, forbidden_limbs, 1)
- //! LEGACY ABOVE
+ //* LEGACY ABOVE
if(!defer_host_updates)
owner?.update_health()
@@ -776,7 +784,7 @@ Note that amputating the affected organ does in fact remove the infection from t
wounds -= W //TODO: robot wounds for robot limbs
src.update_damages()
if (update_icon())
- owner.UpdateDamageIcon(1)
+ owner.update_damage_overlay(1)
return
for(var/datum/wound/W as anything in wounds)
@@ -820,7 +828,7 @@ Note that amputating the affected organ does in fact remove the infection from t
// sync the organ's damage with its wounds
src.update_damages()
if (update_icon())
- owner.UpdateDamageIcon(1)
+ owner.update_damage_overlay(1)
//Updates brute_damn and burn_damn from wound damages. Updates BLEEDING status.
/obj/item/organ/external/proc/update_damages()
@@ -956,7 +964,7 @@ Note that amputating the affected organ does in fact remove the infection from t
spawn(1)
if(istype(victim))
victim.update_health()
- victim.UpdateDamageIcon()
+ victim.update_damage_overlay()
victim.update_icons_body()
else
victim.update_icons()
@@ -1158,7 +1166,7 @@ Note that amputating the affected organ does in fact remove the infection from t
return 0
/obj/item/organ/external/robotize(var/company, var/skip_prosthetics = 0, var/keep_organs = 0, force)
- //! SHITCODE ALERT: REFACTOR ORGANS ASAP; FORCE IS JUST SO PREFS WORK.
+ //* SHITCODE ALERT: REFACTOR ORGANS ASAP; FORCE IS JUST SO PREFS WORK.
if(robotic >= ORGAN_ROBOT && !force)
return
@@ -1212,7 +1220,7 @@ Note that amputating the affected organ does in fact remove the infection from t
owner.refresh_modular_limb_verbs()
return 1
-//! ## VIRGO HOOK, TODO: Integrate this.
+ //* ## VIRGO HOOK, TODO: Integrate this.
//Sideways override for nanoform limbs (ugh)
/obj/item/organ/external/robotize(var/company, var/skip_prosthetics = FALSE, var/keep_organs = FALSE, force)
var/original_robotic = robotic
diff --git a/code/modules/organs/external/external_icons.dm b/code/modules/organs/external/external_icons.dm
index 4358a54f8d34..1af4e4b43306 100644
--- a/code/modules/organs/external/external_icons.dm
+++ b/code/modules/organs/external/external_icons.dm
@@ -100,7 +100,7 @@ GLOBAL_LIST_EMPTY(limb_icon_cache)
for(var/M in markings)
var/datum/sprite_accessory/marking/mark_style = markings[M]["datum"]
var/icon/mark_s = new/icon("icon" = mark_style.icon, "icon_state" = "[mark_style.icon_state]-[organ_tag]")
- mark_s.Blend(markings[M]["color"], mark_style.color_blend_mode)
+ mark_s.Blend(markings[M]["color"], mark_style.legacy_use_additive_color_matrix? ICON_ADD : ICON_MULTIPLY)
add_overlay(mark_s) //So when it's not on your body, it has icons
mob_icon.Blend(mark_s, ICON_OVERLAY) //So when it's on your body, it has icons
icon_cache_key += "[M][markings[M]["color"]]"
@@ -111,38 +111,11 @@ GLOBAL_LIST_EMPTY(limb_icon_cache)
mob_icon.Blend(eyecon, ICON_OVERLAY)
icon_cache_key += "[eye_icon]"
- add_overlay(get_hair_icon())
+ add_overlay(owner.render_spriteacc_facehair())
+ add_overlay(owner.render_spriteacc_hair())
return mob_icon
-/obj/item/organ/external/head/proc/get_hair_icon()
- var/image/res = image('icons/mob/human_face.dmi',"bald_s")
- //Facial hair
- if(owner.f_style)
- var/datum/sprite_accessory/facial_hair_style = GLOB.legacy_facial_hair_lookup[owner.f_style]
- if(facial_hair_style && (!facial_hair_style.apply_restrictions || (species.get_bodytype_legacy(owner) in facial_hair_style.species_allowed)))
- var/icon/facial_s = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_s")
- if(facial_hair_style.do_colouration)
- facial_s.Blend(rgb(owner.r_facial, owner.g_facial, owner.b_facial), facial_hair_style.color_blend_mode)
- res.add_overlay(facial_s)
-
- //Head hair
- if(owner.h_style)
- var/style = owner.h_style
- var/datum/sprite_accessory/hair/hair_style = GLOB.legacy_hair_lookup[style]
- if(owner.head && (owner.head.inv_hide_flags & BLOCKHEADHAIR))
- if(!(hair_style.hair_flags & HAIR_VERY_SHORT))
- hair_style = GLOB.legacy_hair_lookup["Short Hair"]
- if(hair_style && (!hair_style.apply_restrictions || (species.get_bodytype_legacy(owner) in hair_style.species_allowed)))
- var/icon/hair_s = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_s")
- var/icon/hair_s_add = new/icon("icon" = hair_style.icon_add, "icon_state" = "[hair_style.icon_state]_s")
- if(hair_style.do_colouration && islist(h_col) && h_col.len >= 3)
- hair_s.Blend(rgb(h_col[1], h_col[2], h_col[3]), ICON_MULTIPLY)
- hair_s.Blend(hair_s_add, ICON_ADD)
- res.add_overlay(hair_s)
-
- return res
-
/obj/item/organ/external/proc/get_icon(var/skeletal)
if(owner && ishuman(owner))
@@ -181,7 +154,7 @@ GLOBAL_LIST_EMPTY(limb_icon_cache)
for(var/M in markings)
var/datum/sprite_accessory/marking/mark_style = markings[M]["datum"]
var/icon/mark_s = new/icon("icon" = mark_style.icon, "icon_state" = "[mark_style.icon_state]-[organ_tag]")
- mark_s.Blend(markings[M]["color"], mark_style.color_blend_mode)
+ mark_s.Blend(markings[M]["color"], mark_style.legacy_use_additive_color_matrix? ICON_ADD : ICON_MULTIPLY)
add_overlay(mark_s) //So when it's not on your body, it has icons
mob_icon.Blend(mark_s, ICON_OVERLAY) //So when it's on your body, it has icons
icon_cache_key += "[M][markings[M]["color"]]"
@@ -201,7 +174,7 @@ GLOBAL_LIST_EMPTY(limb_icon_cache)
for(var/M in markings)
var/datum/sprite_accessory/marking/mark_style = markings[M]["datum"]
var/icon/mark_s = new/icon("icon" = mark_style.icon, "icon_state" = "[mark_style.icon_state]-[organ_tag]")
- mark_s.Blend(markings[M]["color"], mark_style.color_blend_mode)
+ mark_s.Blend(markings[M]["color"], mark_style.legacy_use_additive_color_matrix? ICON_ADD : ICON_MULTIPLY)
add_overlay(mark_s) //So when it's not on your body, it has icons
mob_icon.Blend(mark_s, ICON_OVERLAY) //So when it's on your body, it has icons
icon_cache_key += "[M][markings[M]["color"]]"
diff --git a/code/modules/organs/external/robolimbs.dm b/code/modules/organs/external/robolimbs.dm
index 06be0ff2db94..d56ed840b16a 100644
--- a/code/modules/organs/external/robolimbs.dm
+++ b/code/modules/organs/external/robolimbs.dm
@@ -71,6 +71,10 @@ var/const/cyberbeast_monitor_styles= "blank=cyber_blank;\
if(species_company in GLOB.all_robolimbs)
R.species_alternates[species] = GLOB.all_robolimbs[species_company]
+/datum/sprite_accessory/tail/legacy_robolimb
+ do_colouration = FALSE
+ abstract_type = /datum/sprite_accessory/tail/legacy_robolimb
+
/datum/robolimb
/// Shown when selecting the limb.
var/company = "Unbranded"
@@ -108,10 +112,6 @@ var/const/cyberbeast_monitor_styles= "blank=cyber_blank;\
var/robo_brute_mod = 1
/// Multiplier for incoming burn damage.
var/robo_burn_mod = 1
- /// Cyberlimbs dmi includes a tail sprite to wear.
- var/includes_tail
- /// Cyberlimbs dmi includes a wing sprite to wear.
- var/includes_wing
/// If it should make the torso a species
var/suggested_species = SPECIES_HUMAN
/// Species in this list cannot take these prosthetics.
@@ -121,6 +121,16 @@ var/const/cyberbeast_monitor_styles= "blank=cyber_blank;\
/// List of ckeys that are allowed to pick this in charsetup.
var/list/whitelisted_to
+ /// typepath or id of sprite accessory to default for, for tail
+ var/datum/sprite_accessory/legacy_includes_tail
+
+/datum/robolimb/New()
+ if(ispath(legacy_includes_tail))
+ var/datum/sprite_accessory/casted = legacy_includes_tail
+ legacy_includes_tail = initial(casted.id)
+ if(istext(legacy_includes_tail))
+ legacy_includes_tail = GLOB.sprite_accessory_tails[legacy_includes_tail]
+
/datum/robolimb/unbranded_monitor
company = "Unbranded Monitor"
desc = "A generic unbranded interpretation of a popular prosthetic head model. It looks rudimentary and cheaply constructed."
@@ -622,7 +632,7 @@ var/const/cyberbeast_monitor_styles= "blank=cyber_blank;\
desc = "This limb has a slight salvaged handicraft vibe to it. The CE-marking on it is definitely not the standardized one, it looks more like a hand-written sharpie monogram."
icon = 'icons/mob/cyberlimbs/_fluff_vr/rahboop.dmi'
blood_color = "#5e280d"
- includes_tail = 1
+ legacy_includes_tail = /datum/sprite_accessory/tail/bodyset/eggnerd
unavailable_to_build = TRUE
/obj/item/disk/limb/eggnerdltd
@@ -671,7 +681,7 @@ var/const/cyberbeast_monitor_styles= "blank=cyber_blank;\
desc = "A slightly more refined limb variant from Eggnerd Prototyping. Its got red plating instead of orange."
icon = 'icons/mob/cyberlimbs/rahboopred/rahboopred.dmi'
blood_color = "#5e280d"
- includes_tail = 1
+ legacy_includes_tail = /datum/sprite_accessory/tail/bodyset/eggnerd_red
unavailable_to_build = TRUE
/obj/item/disk/limb/eggnerdltdred
@@ -688,8 +698,8 @@ var/const/cyberbeast_monitor_styles= "blank=cyber_blank;\
icon = 'icons/mob/cyberlimbs/DSITajaran/dsi_tajaran.dmi'
blood_color = "#ffe2ff"
lifelike = 1
+ legacy_includes_tail = /datum/sprite_accessory/tail/bodyset/oss_tajaran
unavailable_to_build = TRUE
- includes_tail = 1
skin_tone = 1
suggested_species = SPECIES_TAJ
speech_bubble_appearance = "normal"
@@ -705,7 +715,7 @@ var/const/cyberbeast_monitor_styles= "blank=cyber_blank;\
blood_color = "#ffe2ff"
lifelike = 1
unavailable_to_build = TRUE
- includes_tail = 1
+ legacy_includes_tail = /datum/sprite_accessory/tail/bodyset/oss_lizard
skin_tone = 1
suggested_species = SPECIES_UNATHI
speech_bubble_appearance = "normal"
@@ -721,7 +731,7 @@ var/const/cyberbeast_monitor_styles= "blank=cyber_blank;\
blood_color = "#ffe2ff"
lifelike = 1
unavailable_to_build = TRUE
- includes_tail = 1
+ legacy_includes_tail = /datum/sprite_accessory/tail/bodyset/oss_naramadi
skin_tone = 1
suggested_species = SPECIES_SERGAL
speech_bubble_appearance = "normal"
@@ -737,7 +747,7 @@ var/const/cyberbeast_monitor_styles= "blank=cyber_blank;\
blood_color = "#ffe2ff"
lifelike = 1
unavailable_to_build = TRUE
- includes_tail = 1
+ legacy_includes_tail = /datum/sprite_accessory/tail/bodyset/oss_nevrean
skin_tone = 1
suggested_species = SPECIES_NEVREAN
speech_bubble_appearance = "normal"
@@ -753,7 +763,7 @@ var/const/cyberbeast_monitor_styles= "blank=cyber_blank;\
blood_color = "#ffe2ff"
lifelike = 1
unavailable_to_build = TRUE
- includes_tail = 1
+ legacy_includes_tail = /datum/sprite_accessory/tail/bodyset/oss_vulpkanin
skin_tone = 1
suggested_species = SPECIES_VULPKANIN
speech_bubble_appearance = "normal"
@@ -768,8 +778,8 @@ var/const/cyberbeast_monitor_styles= "blank=cyber_blank;\
icon = 'icons/mob/cyberlimbs/DSIAkula/dsi_akula.dmi'
blood_color = "#ffe2ff"
lifelike = 1
+ legacy_includes_tail = /datum/sprite_accessory/tail/bodyset/oss_akula
unavailable_to_build = TRUE
- includes_tail = 1
skin_tone = 1
suggested_species = SPECIES_AKULA
speech_bubble_appearance = "normal"
@@ -785,7 +795,7 @@ var/const/cyberbeast_monitor_styles= "blank=cyber_blank;\
blood_color = "#ffe2ff"
lifelike = 1
unavailable_to_build = TRUE
- includes_tail = 1
+ legacy_includes_tail = /datum/sprite_accessory/tail/bodyset/oss_spider
skin_tone = 1
suggested_species = SPECIES_VASILISSAN
speech_bubble_appearance = "normal"
diff --git a/code/modules/organs/external/species/slime.dm b/code/modules/organs/external/species/slime.dm
index a4ad1b4c5f0f..3afd45779469 100644
--- a/code/modules/organs/external/species/slime.dm
+++ b/code/modules/organs/external/species/slime.dm
@@ -1,61 +1,61 @@
/obj/item/organ/external/chest/unbreakable/slime
nonsolid = 1
- max_damage = 50
+ max_damage = 200
encased = 0
spread_dam = 1
transparent = 1
/obj/item/organ/external/groin/unbreakable/slime
nonsolid = 1
- max_damage = 30
+ max_damage = 75
spread_dam = 1
transparent = 1
/obj/item/organ/external/arm/unbreakable/slime
nonsolid = 1
- max_damage = 20
+ max_damage = 25
spread_dam = 1
transparent = 1
/obj/item/organ/external/arm/right/unbreakable/slime
nonsolid = 1
- max_damage = 20
+ max_damage = 25
spread_dam = 1
transparent = 1
/obj/item/organ/external/leg/unbreakable/slime
nonsolid = 1
- max_damage = 20
+ max_damage = 25
spread_dam = 1
transparent = 1
/obj/item/organ/external/leg/right/unbreakable/slime
nonsolid = 1
- max_damage = 20
+ max_damage = 25
spread_dam = 1
transparent = 1
/obj/item/organ/external/foot/unbreakable/slime
nonsolid = 1
- max_damage = 20
+ max_damage = 25
spread_dam = 1
transparent = 1
/obj/item/organ/external/foot/right/unbreakable/slime
nonsolid = 1
- max_damage = 20
+ max_damage = 25
spread_dam = 1
transparent = 1
/obj/item/organ/external/hand/unbreakable/slime
nonsolid = 1
- max_damage = 20
+ max_damage = 25
spread_dam = 1
transparent = 1
/obj/item/organ/external/hand/right/unbreakable/slime
nonsolid = 1
- max_damage = 20
+ max_damage = 25
spread_dam = 1
transparent = 1
@@ -63,7 +63,7 @@
nonsolid = 1
cannot_gib = 0
vital = 0
- max_damage = 30
+ max_damage = 25
encased = 0
spread_dam = 1
transparent = 1
diff --git a/code/modules/organs/external/subtypes/standard.dm b/code/modules/organs/external/subtypes/standard.dm
index 34dd3b361d97..deab9e72497b 100644
--- a/code/modules/organs/external/subtypes/standard.dm
+++ b/code/modules/organs/external/subtypes/standard.dm
@@ -276,8 +276,7 @@
if(owner)
if(iscarbon(owner))
name = "[owner.real_name]'s head"
- spawn(1)
- owner.update_hair()
+ owner.update_hair()
get_icon()
..()
diff --git a/code/modules/organs/external/subtypes/standard_vr.dm b/code/modules/organs/external/subtypes/standard_vr.dm
index cf5323858073..da6c65e433ba 100644
--- a/code/modules/organs/external/subtypes/standard_vr.dm
+++ b/code/modules/organs/external/subtypes/standard_vr.dm
@@ -12,7 +12,7 @@
for(var/M in markings)
var/datum/sprite_accessory/marking/mark_style = markings[M]["datum"]
var/icon/mark_s = new/icon("icon" = mark_style.icon, "icon_state" = "[mark_style.icon_state]-[organ_tag]")
- mark_s.Blend(markings[M]["color"], mark_style.color_blend_mode)
+ mark_s.Blend(markings[M]["color"], mark_style.legacy_use_additive_color_matrix? ICON_ADD : ICON_MULTIPLY)
overlays_to_add.Add(mark_s) //So when it's not on your body, it has icons
mob_icon.Blend(mark_s, ICON_OVERLAY) //So when it's on your body, it has icons
icon_cache_key += "[M][markings[M]["color"]]"
diff --git a/code/modules/organs/internal/augment/armmounted.dm b/code/modules/organs/internal/augment/armmounted.dm
index f76ff37047dd..b6e32224a72a 100644
--- a/code/modules/organs/internal/augment/armmounted.dm
+++ b/code/modules/organs/internal/augment/armmounted.dm
@@ -9,7 +9,7 @@
w_class = WEIGHT_CLASS_BULKY
organ_tag = O_AUG_L_FOREARM
parent_organ = BP_L_ARM
- target_slot = /datum/inventory_slot_meta/abstract/hand/left
+ target_slot = /datum/inventory_slot/abstract/hand/left
target_parent_classes = list(ORGAN_FLESH, ORGAN_ASSISTED)
integrated_object_type = /obj/item/gun/energy/laser/mounted/augment
@@ -20,11 +20,11 @@
if(O_AUG_L_FOREARM)
organ_tag = O_AUG_R_FOREARM
parent_organ = BP_R_ARM
- target_slot = /datum/inventory_slot_meta/abstract/hand/left
+ target_slot = /datum/inventory_slot/abstract/hand/left
if(O_AUG_R_FOREARM)
organ_tag = O_AUG_L_FOREARM
parent_organ = BP_L_ARM
- target_slot = /datum/inventory_slot_meta/abstract/hand/right
+ target_slot = /datum/inventory_slot/abstract/hand/right
to_chat(user, "You swap \the [src]'s servos to install neatly into \the lower [parent_organ] mount.")
return
@@ -58,11 +58,11 @@
if(O_AUG_L_HAND)
organ_tag = O_AUG_R_HAND
parent_organ = BP_R_HAND
- target_slot = /datum/inventory_slot_meta/abstract/hand/left
+ target_slot = /datum/inventory_slot/abstract/hand/left
if(O_AUG_R_HAND)
organ_tag = O_AUG_L_HAND
parent_organ = BP_L_HAND
- target_slot = /datum/inventory_slot_meta/abstract/hand/right
+ target_slot = /datum/inventory_slot/abstract/hand/right
to_chat(user, "You swap \the [src]'s servos to install neatly into \the upper [parent_organ] mount.")
return
@@ -90,11 +90,11 @@
if(O_AUG_L_UPPERARM)
organ_tag = O_AUG_R_UPPERARM
parent_organ = BP_R_ARM
- target_slot = /datum/inventory_slot_meta/abstract/hand/left
+ target_slot = /datum/inventory_slot/abstract/hand/left
if(O_AUG_R_UPPERARM)
organ_tag = O_AUG_L_UPPERARM
parent_organ = BP_L_ARM
- target_slot = /datum/inventory_slot_meta/abstract/hand/right
+ target_slot = /datum/inventory_slot/abstract/hand/right
to_chat(user, "You swap \the [src]'s servos to install neatly into \the upper [parent_organ] mount.")
return
diff --git a/code/modules/organs/internal/subtypes/augment.dm b/code/modules/organs/internal/subtypes/augment.dm
index 851229b75b91..bbe4e3311c7a 100644
--- a/code/modules/organs/internal/subtypes/augment.dm
+++ b/code/modules/organs/internal/subtypes/augment.dm
@@ -218,7 +218,7 @@
to_chat(M, SPAN_NOTICE("You cannot use your augments when restrained."))
return FALSE
- if((slot == /datum/inventory_slot_meta/abstract/hand/left && l_hand) || (slot == /datum/inventory_slot_meta/abstract/hand/right && r_hand))
+ if((slot == /datum/inventory_slot/abstract/hand/left && l_hand) || (slot == /datum/inventory_slot/abstract/hand/right && r_hand))
to_chat(M, SPAN_WARNING("Your hand is full. Drop something first."))
return FALSE
diff --git a/code/modules/organs/internal/subtypes/liver.dm b/code/modules/organs/internal/subtypes/liver.dm
index 8fa79107371f..e1669431959d 100644
--- a/code/modules/organs/internal/subtypes/liver.dm
+++ b/code/modules/organs/internal/subtypes/liver.dm
@@ -37,7 +37,7 @@
// Do some reagent processing.
if(owner.chem_effects[CE_ALCOHOL_TOXIC])
- take_damage(owner.chem_effects[CE_ALCOHOL_TOXIC] * 0.1 * (dt * 5), prob(1)) // Chance to warn them
+ take_damage(owner.chem_effects[CE_ALCOHOL_TOXIC] * 0.1 * (dt * 5), prob(99)) // Chance to warn them
if(filter_effect < 2) //Liver is badly damaged, you're drinking yourself to death
owner.adjustToxLoss(owner.chem_effects[CE_ALCOHOL_TOXIC] * 0.2 * (dt * 5))
if(filter_effect < 3)
diff --git a/code/modules/organs/organ.dm b/code/modules/organs/organ.dm
index 07dcf906ae5e..dc8e7c6d4f13 100644
--- a/code/modules/organs/organ.dm
+++ b/code/modules/organs/organ.dm
@@ -5,14 +5,14 @@
drop_sound = 'sound/items/drop/flesh.ogg'
pickup_sound = 'sound/items/pickup/flesh.ogg'
-//! ## STRINGS VARS
+ //* ## STRINGS VARS
/// Unique identifier.
var/organ_tag = "organ"
/// The organ holding this object.
var/parent_organ = BP_TORSO
-//! STATUS VARS
+ //* STATUS VARS
/// Various status flags
var/status = 0
/**
@@ -29,7 +29,7 @@
var/stapled_nerves = FALSE
-//! ##REFERENCE VARS
+ //* ##REFERENCE VARS
/// Current mob owning the organ.
var/mob/living/carbon/human/owner
/// Transplant match data.
@@ -45,7 +45,7 @@
var/s_base
-//! ## DAMAGE VARS
+ //* ## DAMAGE VARS
/// Damage before considered bruised
var/min_bruised_damage = 10
/// Damage before becoming broken
@@ -62,12 +62,12 @@
/// decay rate
var/decay_rate = ORGAN_DECAY_PER_SECOND_DEFAULT
-//! ## LANGUAGE VARS - For organs that assist with certain languages.
+ //* ## LANGUAGE VARS - For organs that assist with certain languages.
var/list/will_assist_languages = list()
var/list/datum/language/assists_languages = list()
-//! ## VERB VARS
+ //* ## VERB VARS
/// Verbs added by the organ when present in the body.
var/list/organ_verbs
/// Is the parent supposed to be organic, robotic, assisted?
diff --git a/code/modules/organs/subtypes/nano.dm b/code/modules/organs/subtypes/nano.dm
index 02cdd22195e2..e466cc56a486 100644
--- a/code/modules/organs/subtypes/nano.dm
+++ b/code/modules/organs/subtypes/nano.dm
@@ -2,7 +2,7 @@
/obj/item/organ/external/chest/unbreakable/nano
robotic = ORGAN_NANOFORM
encased = FALSE
- max_damage = 100
+ max_damage = 200
min_broken_damage = 1000
vital = TRUE
emp_mod = 7
diff --git a/code/modules/overmap/legacy/ships/computers/sensors.dm b/code/modules/overmap/legacy/ships/computers/sensors.dm
index b8da4f51c558..553ddbf1fb1e 100644
--- a/code/modules/overmap/legacy/ships/computers/sensors.dm
+++ b/code/modules/overmap/legacy/ships/computers/sensors.dm
@@ -118,9 +118,8 @@
if(istype(O) && !QDELETED(O) && (O in view(7,linked)))
var/obj/item/paper/P = new /obj/item/paper(get_turf(src))
P.name = "paper (Sensor Scan - [O])"
- P.info = O.get_scan_data(usr)
- // TODO: strangle whoever made this, DO NOT MANUALLY CALL INIT
- P.Initialize() // has to be called because the scanner desc uses a combination of html and markdown for some reason
+ P.info = replacetext(html_encode(O.get_scan_data(usr)), "\n", " ")
+ P.init_parsepencode(P.info)
playsound(src, "sound/machines/printer.ogg", 30, 1)
. = TRUE
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index e154c1b46026..92209ce67943 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -698,7 +698,7 @@ CREATE_WALL_MOUNTING_TYPES_SHIFTED(/obj/machinery/power/apc, 22)
"You disassembled the broken APC frame.",\
"You hear welding.")
else
- new /obj/item/frame/apc(loc)
+ new /obj/item/frame2/apc(loc)
user.visible_message(\
"[src] has been cut from the wall by [user.name] with the [WT.name].",\
"You cut the APC frame from the wall.",\
@@ -706,7 +706,7 @@ CREATE_WALL_MOUNTING_TYPES_SHIFTED(/obj/machinery/power/apc, 22)
qdel(src)
return
else if (opened && ((machine_stat & BROKEN) || hacker || emagged))
- if (istype(W, /obj/item/frame/apc) && (machine_stat & BROKEN))
+ if (istype(W, /obj/item/frame2/apc) && (machine_stat & BROKEN))
if(cell)
to_chat(user, "You need to remove the power cell first.")
return
diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm
index 980695395cb0..9757c4173c31 100644
--- a/code/modules/power/cell.dm
+++ b/code/modules/power/cell.dm
@@ -228,7 +228,7 @@
var/mob/living/silicon/robot/R = loc
severity *= R.cell_emp_mult
- charge -= charge / severity
+ charge -= charge / (severity + 1)
if (charge < 0)
charge = 0
diff --git a/code/modules/power/fusion/core/core_control.dm b/code/modules/power/fusion/core/core_control.dm
index 802a509693f7..08f267e39747 100644
--- a/code/modules/power/fusion/core/core_control.dm
+++ b/code/modules/power/fusion/core/core_control.dm
@@ -50,7 +50,7 @@
Device ident '[cur_viewed_device.id_tag]' [cur_viewed_device.owned_field ? "active" : "inactive"]. Power status: [cur_viewed_device.avail()]/[cur_viewed_device.active_power_usage * 0.001] kW
- Bring field [cur_viewed_device.owned_field ? "offline" : "online"].
+ Bring field [cur_viewed_device.owned_field ? "offline, which will cause it to explode" : "online"]. Field power density (W.m-3): ----
diff --git a/code/modules/power/solar.dm b/code/modules/power/solar.dm
deleted file mode 100644
index ecbf71a212d2..000000000000
--- a/code/modules/power/solar.dm
+++ /dev/null
@@ -1,548 +0,0 @@
-#define SOLAR_MAX_DIST 40
-/// Will never start itself.
-#define SOLAR_AUTO_START_NO 0
-/// Will always start itself.
-#define SOLAR_AUTO_START_YES 1
-/// Will start itself if config allows it (default is no).
-#define SOLAR_AUTO_START_CONFIG 2
-GLOBAL_VAR_INIT(solar_gen_rate, 1500)
-GLOBAL_LIST_EMPTY(solars_list)
-
-/obj/machinery/power/solar
- name = "solar panel"
- desc = "A solar electrical generator."
- icon = 'icons/obj/power.dmi'
- icon_state = "sp_base"
- anchored = 1
- density = 1
- use_power = USE_POWER_OFF
- idle_power_usage = 0
- active_power_usage = 0
- integrity = 100
- integrity_max = 100
-
- var/id = 0
- var/obscured = 0
- var/sunfrac = 0
- var/adir = SOUTH // actual dir
- var/ndir = SOUTH // target dir
- var/turn_angle = 0
- var/obj/machinery/power/solar_control/control = null
-
-/obj/machinery/power/solar/can_drain_energy(datum/actor, flags)
- return FALSE
-
-/obj/machinery/power/solar/Initialize(mapload, obj/item/solar_assembly/S)
- . = ..()
- Make(S)
- connect_to_network()
-
-/obj/machinery/power/solar/Destroy()
- unset_control() //remove from control computer
- ..()
-
-//set the control of the panel to a given computer if closer than SOLAR_MAX_DIST
-/obj/machinery/power/solar/proc/set_control(var/obj/machinery/power/solar_control/SC)
- if(SC && (get_dist(src, SC) > SOLAR_MAX_DIST))
- return 0
- control = SC
- return 1
-
-//set the control of the panel to null and removes it from the control list of the previous control computer if needed
-/obj/machinery/power/solar/proc/unset_control()
- if(control)
- control.connected_panels.Remove(src)
- control = null
-
-/obj/machinery/power/solar/proc/Make(var/obj/item/solar_assembly/S)
- if(!S)
- S = new /obj/item/solar_assembly(src)
- S.glass_type = /obj/item/stack/material/glass
- else
- S.forceMove(src)
- if(S.glass_type == /obj/item/stack/material/glass/reinforced) //if the panel is in reinforced glass
- set_max_integrity(integrity_max * 2)
- set_integrity(integrity * 2)
- update_icon()
-
-/obj/machinery/power/solar/attackby(obj/item/I, mob/living/user, list/params, clickchain_flags, damage_multiplier)
- // todo: tool act
- if(I.is_crowbar())
- playsound(src.loc, 'sound/machines/click.ogg', 50, 1)
- user.visible_message("[user] begins to take the glass off the solar panel.")
- if(do_after(user, 50))
- var/obj/item/solar_assembly/S = locate() in src
- if(S)
- S.loc = src.loc
- S.give_glass()
- playsound(src.loc, 'sound/items/Deconstruct.ogg', 50, 1)
- user.visible_message("[user] takes the glass off the solar panel.")
- new /obj/item/solar_assembly(get_turf(src))
- qdel(src)
- return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
- return CLICKCHAIN_DO_NOT_PROPAGATE
-
-/obj/machinery/solar/drop_products(method, atom/where)
- . = ..()
- switch(method)
- if(ATOM_DECONSTRUCT_DISASSEMBLED)
- new /obj/item/stack/material/glass(where, 2)
- else
- for(var/i in 1 to 2)
- new /obj/item/material/shard(where)
-
-/obj/machinery/power/solar/update_icon()
- ..()
- cut_overlays()
- if(machine_stat & BROKEN)
- add_overlay(image('icons/obj/power.dmi', icon_state = "solar_panel-b", layer = FLY_LAYER))
- else
- add_overlay(image('icons/obj/power.dmi', icon_state = "solar_panel", layer = FLY_LAYER))
- setDir(angle2dir(adir))
- return
-
-//calculates the fraction of the SSsun.sunlight that the panel recieves
-/obj/machinery/power/solar/proc/update_solar_exposure()
- if(!SSsun.sun)
- return
- if(obscured)
- sunfrac = 0
- return
-
- //find the smaller angle between the direction the panel is facing and the direction of the SSsun.sun (the sign is not important here)
- var/p_angle = min(abs(adir - SSsun.sun.angle), 360 - abs(adir - SSsun.sun.angle))
-
- if(p_angle > 90) // if facing more than 90deg from SSsun.sun, zero output
- sunfrac = 0
- return
-
- sunfrac = cos(p_angle) ** 2
- //isn't the power recieved from the incoming light proportionnal to cos(p_angle) (Lambert's cosine law) rather than cos(p_angle)^2 ?
-
-/obj/machinery/power/solar/process(delta_time)//TODO: remove/add this from machines to save on processing as needed ~Carn PRIORITY
- if(machine_stat & BROKEN)
- return
- if(!SSsun.sun || !control) //if there's no SSsun.sun or the panel is not linked to a solar control computer, no need to proceed
- return
-
- if(powernet)
- if(powernet == control.powernet)//check if the panel is still connected to the computer
- if(obscured) //get no light from the SSsun.sun, so don't generate power
- return
- var/sgen = GLOB.solar_gen_rate * sunfrac
- add_avail(sgen * 0.001)
- control.gen += sgen
- else //if we're no longer on the same powernet, remove from control computer
- unset_control()
-
-/obj/machinery/power/solar/atom_break()
- . = ..()
- machine_stat |= BROKEN
- unset_control()
- update_icon()
-
-/obj/machinery/power/solar/atom_fix()
- . = ..()
- machine_stat &= ~(BROKEN)
- update_icon()
-
-/obj/machinery/power/solar/fake/Initialize(mapload, obj/item/solar_assembly/S)
- . = ..(mapload, S, FALSE)
-
-/obj/machinery/power/solar/fake/process(delta_time)
- return PROCESS_KILL
-
-//trace towards SSsun.sun to see if we're in shadow
-/obj/machinery/power/solar/proc/occlusion()
-
- var/ax = x // start at the solar panel
- var/ay = y
- var/turf/T = null
-
- for(var/i = 1 to 20) // 20 steps is enough
- ax += SSsun.sun.dx // do step
- ay += SSsun.sun.dy
-
- T = locate( round(ax,0.5),round(ay,0.5),z)
-
- if(!T || T.x == 1 || T.x==world.maxx || T.y==1 || T.y==world.maxy) // not obscured if we reach the edge
- break
-
- if(T.opacity) // if we hit a solid turf, panel is obscured
- obscured = 1
- return
-
- obscured = 0 // if hit the edge or stepped 20 times, not obscured
- update_solar_exposure()
-
-
-//
-// Solar Assembly - For construction of solar arrays.
-//
-
-/obj/item/solar_assembly
- name = "solar panel assembly"
- desc = "A solar panel assembly kit, allows constructions of a solar panel, or with a tracking circuit board, a solar tracker"
- icon = 'icons/obj/power.dmi'
- icon_state = "sp_base"
- item_state = "camera"
- w_class = WEIGHT_CLASS_BULKY // Pretty big!
- anchored = 0
- var/tracker = 0
- var/glass_type = null
-
-/obj/item/solar_assembly/attack_hand(mob/user, list/params)
- if(!anchored || !isturf(loc)) // You can't pick it up
- ..()
-
-// Give back the glass type we were supplied with
-/obj/item/solar_assembly/proc/give_glass()
- if(glass_type)
- var/obj/item/stack/material/S = new glass_type(src.loc)
- S.amount = 2
- glass_type = null
-
-
-/obj/item/solar_assembly/attackby(var/obj/item/W, var/mob/user)
- if (!isturf(loc))
- return 0
- if(!anchored)
- if(W.is_wrench())
- anchored = 1
- user.visible_message("[user] wrenches the solar assembly into place.")
- playsound(src, W.tool_sound, 75, 1)
- return 1
- else
- if(W.is_wrench())
- anchored = 0
- user.visible_message("[user] unwrenches the solar assembly from it's place.")
- playsound(src, W.tool_sound, 75, 1)
- return 1
-
- // todo: phoronglass solars
- if(istype(W, /obj/item/stack/material) && istype(W.get_primary_material(), /datum/material/glass))
- var/obj/item/stack/material/S = W
- if(S.use(2))
- glass_type = W.type
- playsound(src.loc, 'sound/machines/click.ogg', 50, 1)
- user.visible_message("[user] places the glass on the solar assembly.")
- if(tracker)
- new /obj/machinery/power/tracker(get_turf(src), src)
- else
- new /obj/machinery/power/solar(get_turf(src), src)
- qdel(src)
- else
- to_chat(user, "You need two sheets of glass to put them into a solar panel.")
- return
- return 1
-
- if(!tracker)
- if(istype(W, /obj/item/tracker_electronics))
- if(!user.attempt_consume_item_for_construction(W))
- return
- tracker = 1
- user.visible_message("[user] inserts the electronics into the solar assembly.")
- return 1
- else
- if(W.is_crowbar())
- new /obj/item/tracker_electronics(src.loc)
- tracker = 0
- user.visible_message("[user] takes out the electronics from the solar assembly.")
- return 1
- ..()
-
-//
-// Solar Control Computer
-//
-
-/obj/machinery/power/solar_control
- name = "solar panel control"
- desc = "A controller for solar panel arrays."
- icon = 'icons/obj/computer.dmi'
- icon_state = "solar"
- anchored = 1
- density = 1
- use_power = USE_POWER_IDLE
- idle_power_usage = 250
- var/id = 0
- var/cdir = 0
- var/targetdir = 0 // target angle in manual tracking (since it updates every game minute)
- var/gen = 0
- var/lastgen = 0
- var/track = 0 // 0= off 1=timed 2=auto (tracker)
- var/trackrate = 600 // 300-900 seconds
- var/nexttime = 0 // time for a panel to rotate of 1� in manual tracking
- var/obj/machinery/power/tracker/connected_tracker = null
- var/list/connected_panels = list()
- var/auto_start = SOLAR_AUTO_START_NO
-
-// Used for mapping in solar arrays which automatically start itself.
-// Generally intended for far away and remote locations, where player intervention is rare.
-// In the interest of backwards compatability, this isn't named auto_start, as doing so might break downstream maps.
-/obj/machinery/power/solar_control/autostart
- auto_start = SOLAR_AUTO_START_YES
-
-// Similar to above but controlled by the configuration file.
-// Intended to be used for the main solar arrays, so individual servers can choose to have them start automatically or require manual intervention.
-/obj/machinery/power/solar_control/config_start
- auto_start = SOLAR_AUTO_START_CONFIG
-
-/obj/machinery/power/solar_control/Initialize(mapload)
- . = ..()
- connect_to_network()
- set_panels(cdir)
-
-/obj/machinery/power/solar_control/Destroy()
- for(var/obj/machinery/power/solar/M in connected_panels)
- M.unset_control()
- if(connected_tracker)
- connected_tracker.unset_control()
- return ..()
-
-/obj/machinery/power/solar_control/proc/auto_start(forced = FALSE)
- // Automatically sets the solars, if allowed.
- if(forced || auto_start == SOLAR_AUTO_START_YES || (auto_start == SOLAR_AUTO_START_CONFIG && config_legacy.autostart_solars) )
- track = 2 // Auto tracking mode.
- search_for_connected()
- if(connected_tracker)
- connected_tracker.set_angle(SSsun.sun.angle)
- set_panels(cdir)
-
-// This would use LateInitialize(), however the powernet does not appear to exist during that time.
-/hook/roundstart/proc/auto_start_solars()
- for(var/a in GLOB.solars_list)
- var/obj/machinery/power/solar_control/SC = a
- SC.auto_start()
- return TRUE
-
-/obj/machinery/power/solar_control/can_drain_energy(datum/actor, flags)
- return FALSE
-
-/obj/machinery/power/solar_control/disconnect_from_network()
- ..()
- GLOB.solars_list.Remove(src)
-
-/obj/machinery/power/solar_control/connect_to_network()
- var/to_return = ..()
- if(powernet) //if connected and not already in solar_list...
- GLOB.solars_list |= src //... add it
- return to_return
-
-/// Search for unconnected panels and trackers in the computer powernet and connect them
-/obj/machinery/power/solar_control/proc/search_for_connected()
- if(powernet)
- for(var/obj/machinery/power/M in powernet.nodes)
- if(istype(M, /obj/machinery/power/solar))
- var/obj/machinery/power/solar/S = M
- if(!S.control) //i.e unconnected
- S.set_control(src)
- connected_panels |= S
- else if(istype(M, /obj/machinery/power/tracker))
- if(!connected_tracker) //if there's already a tracker connected to the computer don't add another
- var/obj/machinery/power/tracker/T = M
- if(!T.control) //i.e unconnected
- connected_tracker = T
- T.set_control(src)
-
-/// Called by the SSsun.sun controller, update the facing angle (either manually or via tracking) and rotates the panels accordingly
-/obj/machinery/power/solar_control/proc/update()
- if(machine_stat & (NOPOWER | BROKEN))
- return
-
- switch(track)
- if(1)
- if(trackrate) //we're manual tracking. If we set a rotation speed...
- cdir = targetdir //...the current direction is the targetted one (and rotates panels to it)
- if(2) // auto-tracking
- if(connected_tracker)
- connected_tracker.set_angle(SSsun.sun.angle)
-
- set_panels(cdir)
- updateDialog()
-
-/obj/machinery/power/solar_control/update_icon()
- cut_overlays()
- if(machine_stat & BROKEN)
- icon_state = "broken"
- return
- if(machine_stat & NOPOWER)
- icon_state = "c_unpowered"
- return
- icon_state = "solar"
- if(cdir > -1)
- add_overlay(image('icons/obj/computer.dmi', "solcon-o", FLY_LAYER, angle2dir(cdir)))
- return
-
-/obj/machinery/power/solar_control/attack_hand(mob/user, list/params)
- if(!..())
- interact(user)
-
-/obj/machinery/power/solar_control/interact(mob/user)
-
- var/t = "Generated power : [round(lastgen)] W "
- t += "Star Orientation: [SSsun.sun.angle]° ([angle2text(SSsun.sun.angle)]) "
- t += "Array Orientation: [rate_control(src,"cdir","[cdir]°",1,15)] ([angle2text(cdir)]) "
- t += "Tracking:
"
- switch(track)
- if(0)
- t += "OffTimedAuto "
- if(1)
- t += "OffTimedAuto "
- if(2)
- t += "OffTimedAuto "
-
- t += "Tracking Rate: [rate_control(src,"tdir","[trackrate] deg/h ([trackrate<0 ? "CCW" : "CW"])",1,30,180)]
"
-
- t += "Connected devices:
"
-
- t += "Search for devices "
- t += "Solar panels : [connected_panels.len] connected "
- t += "Solar tracker : [connected_tracker ? "Found" : "Not found"]
"
-
- t += "Close"
-
- var/datum/browser/popup = new(user, "solar", name)
- popup.set_content(t)
- popup.open()
-
- return
-
-/obj/machinery/power/solar_control/attackby(obj/item/I, user as mob)
- if(I.is_screwdriver())
- playsound(src, I.tool_sound, 50, 1)
- if(do_after(user, 20))
- if (src.machine_stat & BROKEN)
- to_chat(user, "The broken glass falls out.")
- var/obj/structure/frame/A = new /obj/structure/frame/computer( src.loc )
- new /obj/item/material/shard( src.loc )
- var/obj/item/circuitboard/solar_control/M = new /obj/item/circuitboard/solar_control( A )
- for (var/obj/C in src)
- C.loc = src.loc
- A.circuit = M
- A.state = 3
- A.icon_state = "computer_3"
- A.anchored = 1
- qdel(src)
- else
- to_chat(user, "You disconnect the monitor.")
- var/obj/structure/frame/A = new /obj/structure/frame/computer( src.loc )
- var/obj/item/circuitboard/solar_control/M = new /obj/item/circuitboard/solar_control( A )
- for (var/obj/C in src)
- C.loc = src.loc
- A.circuit = M
- A.state = 4
- A.icon_state = "computer_4"
- A.anchored = 1
- qdel(src)
- else
- src.attack_hand(user)
- return
-
-/obj/machinery/power/solar_control/process(delta_time)
- lastgen = gen
- gen = 0
-
- if(machine_stat & (NOPOWER | BROKEN))
- return
-
- if(connected_tracker) //NOTE : handled here so that we don't add trackers to the processing list
- if(connected_tracker.powernet != powernet)
- connected_tracker.unset_control()
-
- if(track==1 && trackrate) //manual tracking and set a rotation speed
- if(nexttime <= world.time) //every time we need to increase/decrease the angle by 1�...
- targetdir = (targetdir + trackrate/abs(trackrate) + 360) % 360 //... do it
- nexttime += 36000/abs(trackrate) //reset the counter for the next 1�
-
- updateDialog()
-
-/obj/machinery/power/solar_control/Topic(href, href_list)
- if(..())
- usr << browse(null, "window=solcon")
- usr.unset_machine()
- return 0
- if(href_list["close"] )
- usr << browse(null, "window=solcon")
- usr.unset_machine()
- return 0
-
- if(href_list["rate control"])
- if(href_list["cdir"])
- src.cdir = clamp((360+src.cdir+text2num(href_list["cdir"]))%360, 0, 359)
- src.targetdir = src.cdir
- if(track == 2) //manual update, so losing auto-tracking
- track = 0
- spawn(1)
- set_panels(cdir)
- if(href_list["tdir"])
- src.trackrate = clamp(src.trackrate+text2num(href_list["tdir"]), -7200, 7200)
- if(src.trackrate) nexttime = world.time + 36000/abs(trackrate)
-
- if(href_list["track"])
- track = text2num(href_list["track"])
- if(track == 2)
- if(connected_tracker)
- connected_tracker.set_angle(SSsun.sun.angle)
- set_panels(cdir)
- else if (track == 1) //begin manual tracking
- src.targetdir = src.cdir
- if(src.trackrate) nexttime = world.time + 36000/abs(trackrate)
- set_panels(targetdir)
-
- if(href_list["search_connected"])
- src.search_for_connected()
- if(connected_tracker && track == 2)
- connected_tracker.set_angle(SSsun.sun.angle)
- src.set_panels(cdir)
-
- interact(usr)
- return 1
-
-//rotates the panel to the passed angle
-/obj/machinery/power/solar_control/proc/set_panels(var/cdir)
-
- for(var/obj/machinery/power/solar/S in connected_panels)
- S.adir = cdir //instantly rotates the panel
- S.occlusion()//and
- S.update_icon() //update it
-
- update_icon()
-
-
-/obj/machinery/power/solar_control/power_change()
- ..()
- update_icon()
-
-
-/obj/machinery/power/solar_control/proc/broken()
- machine_stat |= BROKEN
- update_icon()
-
-
-/obj/machinery/power/solar_control/legacy_ex_act(severity)
- switch(severity)
- if(1.0)
- //SN src = null
- qdel(src)
- return
- if(2.0)
- if (prob(50))
- broken()
- if(3.0)
- if (prob(25))
- broken()
- return
-
-//
-// MISC
-//
-
-/obj/item/paper/solar
- name = "paper- 'Going green! Setup your own solar array instructions.'"
- info = "
Welcome
At greencorps we love the environment, and space. With this package you are able to help mother nature and produce energy without any usage of fossil fuel or phoron! Singularity energy is dangerous while solar energy is safe, which is why it's better. Now here is how you setup your own solar array.
You can make a solar panel by wrenching the solar assembly onto a cable node. Adding a glass panel, reinforced or regular glass will do, will finish the construction of your solar panel. It is that easy!
Now after setting up 19 more of these solar panels you will want to create a solar tracker to keep track of our mother nature's gift, the SSsun.sun. These are the same steps as before except you insert the tracker equipment circuit into the assembly before performing the final step of adding the glass. You now have a tracker! Now the last step is to add a computer to calculate the SSsun.sun's movements and to send commands to the solar panels to change direction with the SSsun.sun. Setting up the solar computer is the same as setting up any computer, so you should have no trouble in doing that. You do need to put a wire node under the computer, and the wire needs to be connected to the tracker.
Congratulations, you should have a working solar array. If you are having trouble, here are some tips. Make sure all solar equipment are on a cable node, even the computer. You can always deconstruct your creations if you make a mistake.
That's all to it, be safe, be green!
"
-
-/proc/rate_control(var/S, var/V, var/C, var/Min=1, var/Max=5, var/Limit=null) //How not to name vars
- var/href = "-[href]=-[Min]'>- [(C?C : 0)] [href]=[Min]'>+[href]=[Max]'>+"
- if(Limit) return "[href]=-[Limit]'>-"+rate+"[href]=[Limit]'>+"
- return rate
diff --git a/code/modules/power/solar/solar.dm b/code/modules/power/solar/solar.dm
new file mode 100644
index 000000000000..0fd562b271b7
--- /dev/null
+++ b/code/modules/power/solar/solar.dm
@@ -0,0 +1,135 @@
+#define SOLAR_MAX_DIST 40
+/// Will start itself if config allows it (default is no).
+#define SOLAR_AUTO_START_CONFIG 2
+GLOBAL_VAR_INIT(solar_gen_rate, 1500)
+GLOBAL_LIST_EMPTY(solars_list)
+
+/obj/machinery/power/solar
+ name = "solar panel"
+ desc = "A solar electrical generator."
+ icon = 'icons/obj/power.dmi'
+ icon_state = "sp_base"
+ anchored = 1
+ density = 1
+ use_power = USE_POWER_OFF
+ idle_power_usage = 0
+ active_power_usage = 0
+ integrity = 100
+ integrity_max = 100
+
+ var/id = 0
+ var/sunfrac = 0
+ var/adir = SOUTH // actual dir
+ var/ndir = SOUTH // target dir
+ var/turn_angle = 0
+ var/obj/machinery/power/solar_control/control = null
+
+/obj/machinery/power/solar/Initialize(mapload)
+ . = ..()
+ connect_to_network()
+ update_icon()
+
+/obj/machinery/power/solar/Destroy()
+ unset_control() //remove from control computer
+ return ..()
+
+//set the control of the panel to a given computer if closer than SOLAR_MAX_DIST
+/obj/machinery/power/solar/proc/set_control(var/obj/machinery/power/solar_control/SC)
+ if(SC && (get_dist(src, SC) > SOLAR_MAX_DIST))
+ return 0
+ control = SC
+ return 1
+
+//set the control of the panel to null and removes it from the control list of the previous control computer if needed
+/obj/machinery/power/solar/proc/unset_control()
+ if(control)
+ control.connected_panels.Remove(src)
+ control = null
+
+/obj/machinery/power/solar/attackby(obj/item/I, mob/living/user, list/params, clickchain_flags, damage_multiplier)
+ // todo: tool act
+ if(I.is_crowbar())
+ playsound(src.loc, 'sound/machines/click.ogg', 50, 1)
+ user.visible_message("[user] begins to take the glass off the solar panel.")
+ if(do_after(user, 50))
+ playsound(src.loc, 'sound/items/Deconstruct.ogg', 50, 1)
+ user.visible_message("[user] takes the glass off the solar panel.")
+ deconstruct(ATOM_DECONSTRUCT_DISASSEMBLED)
+ return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ return ..()
+
+/obj/machinery/solar/drop_products(method, atom/where)
+ . = ..()
+ switch(method)
+ if(ATOM_DECONSTRUCT_DISASSEMBLED)
+ drop_product(method, new /obj/structure/frame2/solar_panel/anchored, where)
+ else
+ for(var/i in 1 to 2)
+ new /obj/item/material/shard(where)
+
+/obj/machinery/power/solar/update_icon()
+ ..()
+ cut_overlays()
+ if(machine_stat & BROKEN)
+ add_overlay(image('icons/obj/power.dmi', icon_state = "solar_panel-b", layer = FLY_LAYER))
+ else
+ add_overlay(image('icons/obj/power.dmi', icon_state = "solar_panel", layer = FLY_LAYER))
+ setDir(angle2dir(adir))
+ return
+
+//calculates the fraction of the SSsun.sunlight that the panel recieves
+/obj/machinery/power/solar/proc/update_solar_exposure()
+ var/p_angle = 180
+ var/solar_brightness = 1
+
+ var/datum/planet/our_planet = null
+ var/turf/T = get_turf(src)
+ if(T.outdoors && (T.z <= SSplanets.z_to_planet.len))
+ our_planet = SSplanets.z_to_planet[z]
+
+ if(our_planet && istype(our_planet))
+ solar_brightness = our_planet.sun_apparent_brightness * 1.3
+ var/time_num = text2num(our_planet.current_time.show_time("hh")) + text2num(our_planet.current_time.show_time("mm")) / 60
+ var/hours_in_day = our_planet.current_time.seconds_in_day / (1 HOURS)
+ var/sunangle_by_time = (time_num / hours_in_day) * 360 // day as progress from 0 to 1 * 360
+ p_angle = abs(adir - sunangle_by_time)
+ else
+ if(!SSsun.sun)
+ return
+ //find the smaller angle between the direction the panel is facing and the direction of the SSsun.sun (the sign is not important here)
+ p_angle = min(abs(adir - SSsun.sun.angle), 360 - abs(adir - SSsun.sun.angle))
+
+ sunfrac = max(cos(p_angle), 0) * solar_brightness
+
+/obj/machinery/power/solar/process(delta_time)//TODO: remove/add this from machines to save on processing as needed ~Carn PRIORITY
+ if(machine_stat & BROKEN)
+ return
+ if(!control) //if there's the panel is not linked to a solar control computer, no need to proceed
+ return
+
+ if(!sunfrac) //Not getting any sun, so why process
+ return
+
+ if(powernet)
+ if(powernet == control.powernet)//check if the panel is still connected to the computer
+ var/sgen = GLOB.solar_gen_rate * sunfrac
+ add_avail(sgen * 0.001)
+ control.gen += sgen
+ else //if we're no longer on the same powernet, remove from control computer
+ unset_control()
+
+/obj/machinery/power/solar/atom_break()
+ . = ..()
+ machine_stat |= BROKEN
+ unset_control()
+ update_icon()
+
+/obj/machinery/power/solar/atom_fix()
+ . = ..()
+ machine_stat &= ~(BROKEN)
+ update_icon()
+
+/obj/item/paper/solar
+ name = "paper- 'Going green! Setup your own solar array instructions.'"
+ info = "
Welcome
At greencorps we love the environment, and space. With this package you are able to help mother nature and produce energy without any usage of fossil fuel or phoron! Singularity energy is dangerous while solar energy is safe, which is why it's better. Now here is how you setup your own solar array.
You can make a solar panel by wrenching the solar assembly onto a cable node. Adding a glass panel, reinforced or regular glass will do, will finish the construction of your solar panel. It is that easy!
Now after setting up 19 more of these solar panels you will want to create a solar tracker to keep track of our mother nature's gift, the SSsun.sun. These are the same steps as before except you insert the tracker equipment circuit into the assembly before performing the final step of adding the glass. You now have a tracker! Now the last step is to add a computer to calculate the SSsun.sun's movements and to send commands to the solar panels to change direction with the SSsun.sun. Setting up the solar computer is the same as setting up any computer, so you should have no trouble in doing that. You do need to put a wire node under the computer, and the wire needs to be connected to the tracker.
Congratulations, you should have a working solar array. If you are having trouble, here are some tips. Make sure all solar equipment are on a cable node, even the computer. You can always deconstruct your creations if you make a mistake.
That's all to it, be safe, be green!
"
diff --git a/code/modules/power/solar/solar_control.dm b/code/modules/power/solar/solar_control.dm
new file mode 100644
index 000000000000..cd503e9daa74
--- /dev/null
+++ b/code/modules/power/solar/solar_control.dm
@@ -0,0 +1,300 @@
+/obj/machinery/power/solar_control
+ name = "solar panel control"
+ desc = "A controller for solar panel arrays."
+ icon = 'icons/obj/computer.dmi'
+ icon_state = "solar"
+ anchored = 1
+ density = 1
+ use_power = USE_POWER_IDLE
+ idle_power_usage = 250
+ var/id = 0
+ var/cdir = 0
+ var/targetdir = 0 // target angle in manual tracking (since it updates every game minute)
+ var/gen = 0
+ var/lastgen = 0
+ var/track = 0 // 0= off 1=timed 2=auto (tracker)
+ var/trackrate = 600 // 300-900 seconds
+ var/nexttime = 0 // time for a panel to rotate of 1� in manual tracking
+ var/obj/machinery/power/tracker/connected_tracker = null
+ var/list/connected_panels = list()
+ var/auto_start = FALSE
+
+// Used for mapping in solar arrays which automatically start itself.
+// Generally intended for far away and remote locations, where player intervention is rare.
+// In the interest of backwards compatability, this isn't named auto_start, as doing so might break downstream maps.
+/obj/machinery/power/solar_control/autostart
+ auto_start = TRUE
+
+// Similar to above but controlled by the configuration file.
+// Intended to be used for the main solar arrays, so individual servers can choose to have them start automatically or require manual intervention.
+/obj/machinery/power/solar_control/config_start
+ auto_start = SOLAR_AUTO_START_CONFIG
+
+/obj/machinery/power/solar_control/Initialize(mapload)
+ . = ..()
+ connect_to_network()
+ set_panels(cdir)
+
+/obj/machinery/power/solar_control/Destroy()
+ for(var/obj/machinery/power/solar/M in connected_panels)
+ M.unset_control()
+ if(connected_tracker)
+ connected_tracker.unset_control()
+ return ..()
+
+/obj/machinery/power/solar_control/proc/auto_start(forced = FALSE)
+ // Automatically sets the solars, if allowed.
+ if(forced || auto_start == TRUE || (auto_start == SOLAR_AUTO_START_CONFIG && config_legacy.autostart_solars) )
+ track = 2 // Auto tracking mode.
+ search_for_connected()
+ if(connected_tracker)
+ set_tracker_angle()
+ set_panels(cdir)
+
+// This would use LateInitialize(), however the powernet does not appear to exist during that time.
+/hook/roundstart/proc/auto_start_solars()
+ for(var/a in GLOB.solars_list)
+ var/obj/machinery/power/solar_control/SC = a
+ SC.auto_start()
+ return TRUE
+
+/obj/machinery/power/solar_control/can_drain_energy(datum/actor, flags)
+ return FALSE
+
+/obj/machinery/power/solar_control/disconnect_from_network()
+ ..()
+ GLOB.solars_list.Remove(src)
+
+/obj/machinery/power/solar_control/connect_to_network()
+ var/to_return = ..()
+ if(powernet) //if connected and not already in solar_list...
+ GLOB.solars_list |= src //... add it
+ return to_return
+
+/// Search for unconnected panels and trackers in the computer powernet and connect them
+/obj/machinery/power/solar_control/proc/search_for_connected()
+ if(powernet)
+ for(var/obj/machinery/power/M in powernet.nodes)
+ if(istype(M, /obj/machinery/power/solar))
+ var/obj/machinery/power/solar/S = M
+ if(!S.control) //i.e unconnected
+ S.set_control(src)
+ connected_panels |= S
+ else if(istype(M, /obj/machinery/power/tracker))
+ if(!connected_tracker) //if there's already a tracker connected to the computer don't add another
+ var/obj/machinery/power/tracker/T = M
+ if(!T.control) //i.e unconnected
+ connected_tracker = T
+ T.set_control(src)
+
+/// Called by the SSsun.sun controller, update the facing angle (either manually or via tracking) and rotates the panels accordingly
+/obj/machinery/power/solar_control/proc/update()
+ if(machine_stat & (NOPOWER | BROKEN))
+ return
+
+ switch(track)
+ if(1)
+ if(trackrate) //we're manual tracking. If we set a rotation speed...
+ cdir = targetdir //...the current direction is the targetted one (and rotates panels to it)
+ if(2) // auto-tracking
+ if(connected_tracker)
+ set_tracker_angle()
+
+ set_panels(cdir)
+ updateDialog()
+
+/obj/machinery/power/solar_control/update_icon()
+ cut_overlays()
+ if(machine_stat & BROKEN)
+ icon_state = "broken"
+ return
+ if(machine_stat & NOPOWER)
+ icon_state = "c_unpowered"
+ return
+ icon_state = "solar"
+ if(cdir > -1)
+ add_overlay(image('icons/obj/computer.dmi', "solcon-o", FLY_LAYER, angle2dir(cdir)))
+ return
+
+/obj/machinery/power/solar_control/attack_hand(mob/user, list/params)
+ if(!..())
+ interact(user)
+
+/obj/machinery/power/solar_control/interact(mob/user)
+ var/t = "Generated power : [round(lastgen)] W "
+ t += "Star Orientation: [cdir]° ([angle2text(cdir)]) "
+ t += "Array Orientation: [rate_control(src,"cdir","[cdir]°",1,15)] ([angle2text(cdir)]) "
+ t += "Tracking:
"
+ switch(track)
+ if(0)
+ t += "OffTimedAuto "
+ if(1)
+ t += "OffTimedAuto "
+ if(2)
+ t += "OffTimedAuto "
+
+ t += "Tracking Rate: [rate_control(src,"tdir","[trackrate] deg/h ([trackrate<0 ? "CCW" : "CW"])",1,30,180)]
"
+
+ t += "Connected devices:
"
+
+ t += "Search for devices "
+ t += "Solar panels : [connected_panels.len] connected "
+ t += "Solar tracker : [connected_tracker ? "Found" : "Not found"]