From 3badc0651d11f794c9ef93b555841eda9e3b6697 Mon Sep 17 00:00:00 2001
From: dwasint <82520990+dwasint@users.noreply.github.com>
Date: Thu, 26 Oct 2023 23:37:04 -0400
Subject: [PATCH 01/79] early port
---
.../SpaceRuins/old_infiltrator.dmm | 3 -
_maps/RandomRuins/SpaceRuins/oldstation.dmm | 1 -
_maps/RandomZLevels/snowdin.dmm | 23 +-
.../map_files/Deltastation/DeltaStation2.dmm | 3 -
.../map_files/IceBoxStation/IceBoxStation.dmm | 2 -
_maps/map_files/KiloStation/KiloStation.dmm | 1 -
_maps/map_files/MetaStation/MetaStation.dmm | 102 ++-
.../maintenance_modules/barcargoupper_1.dmm | 1 -
.../maintenance_modules/barcargoupper_2.dmm | 1 -
.../barcargoupper_attachment_b_3.dmm | 1 -
.../barcargoupper_cave_1.dmm | 6 +-
.../barcargoupper_cave_3.dmm | 6 +-
.../cargoscilower_attachment_a_2.dmm | 6 +-
.../maintenance_modules/dormmedupper_3.dmm | 1 -
.../secbarupper_attachment_b_2.dmm | 1 -
.../secbarupper_cave_1.dmm | 6 +-
.../secservicelower_attachment_c_2.dmm | 1 -
.../servicecargolower_2.dmm | 1 -
.../servicecargolower_attachment_b_3.dmm | 7 +-
_maps/map_files/tramstation/tramstation.dmm | 30 +-
_maps/shuttles/whiteship_delta.dmm | 1 -
code/__DEFINES/artifact.dm | 41 +
code/__DEFINES/dcs/signals/signals_sticker.dm | 6 +
code/__DEFINES/logging.dm | 2 +-
code/__DEFINES/stickers.dm | 8 +
code/__DEFINES/traits.dm | 5 +
code/_globalvars/lists/maintenance_loot.dm | 4 +-
code/controllers/subsystem/artifacts.dm | 68 ++
code/datums/components/attached_sticker.dm | 84 ---
code/datums/elements/sticker.dm | 45 --
.../machines/machine_circuitboards.dm | 38 +-
code/game/objects/items/debug_items.dm | 17 +
code/game/objects/items/manuals.dm | 7 -
code/game/objects/items/sticker.dm | 117 ++-
.../items/storage/boxes/service_boxes.dm | 2 +-
code/game/turfs/closed/minerals.dm | 28 +
code/modules/admin/admin_investigate.dm | 2 +-
code/modules/admin/admin_verbs.dm | 1 +
code/modules/admin/verbs/artifacts.dm | 69 ++
code/modules/artsci/artifact.dm | 21 +
code/modules/artsci/artifact_datum.dm | 317 ++++++++
.../artsci/artifact_items/artifact_cell.dm | 40 +
.../artsci/artifact_items/artifact_gun.dm | 89 +++
.../artsci/artifact_items/artifact_melee.dm | 100 +++
.../artsci/artifact_objects/artifact_bomb.dm | 160 ++++
.../artsci/artifact_objects/artifact_bonk.dm | 37 +
.../artifact_objects/artifact_forcegen.dm | 63 ++
.../artsci/artifact_objects/artifact_heal.dm | 34 +
.../artifact_objects/artifact_injector.dm | 49 ++
.../artsci/artifact_objects/artifact_lamp.dm | 36 +
.../artifact_objects/artifact_powergen.dm | 101 +++
.../artifact_objects/artifact_repulsor.dm | 54 ++
.../artsci/artifact_objects/artifact_vomit.dm | 38 +
code/modules/artsci/artifact_origins.dm | 77 ++
code/modules/artsci/artifact_triggers.dm | 71 ++
.../artsci/testing_machines/analysis_form.dm | 222 ++++++
.../modules/artsci/testing_machines/heater.dm | 111 +++
code/modules/artsci/testing_machines/xray.dm | 155 ++++
.../modules/artsci/testing_machines/zapper.dm | 94 +++
code/modules/cargo/bounties/assistant.dm | 6 -
code/modules/cargo/bounties/science.dm | 15 -
code/modules/cargo/packs/exploration.dm | 7 +-
code/modules/cargo/packs/science.dm | 6 +
code/modules/cargo/universal_scanner.dm | 3 +
code/modules/events/artifact_spawn.dm | 39 +
.../experisci/experiment/experiments.dm | 3 -
.../research/designs/machine_designs.dm | 31 +-
code/modules/research/experimentor.dm | 712 ------------------
code/modules/research/techweb/all_nodes.dm | 3 +-
icons/effects/ore_visuals.dmi | Bin 22277 -> 27543 bytes
icons/obj/artifacts.dmi | Bin 0 -> 6254 bytes
icons/obj/machines/artifact_machines.dmi | Bin 0 -> 3225 bytes
.../obj/machines/atmospherics/heatingpad.dmi | Bin 0 -> 1153 bytes
icons/obj/service/bureaucracy.dmi | Bin 0 -> 28525 bytes
.../code/modules/cargo/crates/science.dm | 7 -
sound/misc/bonk.ogg | Bin 0 -> 7078 bytes
tgstation.dme | 28 +-
tgui/packages/tgui/interfaces/ArtifactForm.js | 71 ++
.../packages/tgui/interfaces/ArtifactPanel.js | 42 ++
tgui/packages/tgui/interfaces/ArtifactXray.js | 67 ++
.../tgui/interfaces/ArtifactZapper.js | 37 +
tools/UpdatePaths/79239_artsci.txt | 1 +
82 files changed, 2648 insertions(+), 1047 deletions(-)
create mode 100644 code/__DEFINES/artifact.dm
create mode 100644 code/__DEFINES/dcs/signals/signals_sticker.dm
create mode 100644 code/__DEFINES/stickers.dm
create mode 100644 code/controllers/subsystem/artifacts.dm
delete mode 100644 code/datums/components/attached_sticker.dm
delete mode 100644 code/datums/elements/sticker.dm
create mode 100644 code/modules/admin/verbs/artifacts.dm
create mode 100644 code/modules/artsci/artifact.dm
create mode 100644 code/modules/artsci/artifact_datum.dm
create mode 100644 code/modules/artsci/artifact_items/artifact_cell.dm
create mode 100644 code/modules/artsci/artifact_items/artifact_gun.dm
create mode 100644 code/modules/artsci/artifact_items/artifact_melee.dm
create mode 100644 code/modules/artsci/artifact_objects/artifact_bomb.dm
create mode 100644 code/modules/artsci/artifact_objects/artifact_bonk.dm
create mode 100644 code/modules/artsci/artifact_objects/artifact_forcegen.dm
create mode 100644 code/modules/artsci/artifact_objects/artifact_heal.dm
create mode 100644 code/modules/artsci/artifact_objects/artifact_injector.dm
create mode 100644 code/modules/artsci/artifact_objects/artifact_lamp.dm
create mode 100644 code/modules/artsci/artifact_objects/artifact_powergen.dm
create mode 100644 code/modules/artsci/artifact_objects/artifact_repulsor.dm
create mode 100644 code/modules/artsci/artifact_objects/artifact_vomit.dm
create mode 100644 code/modules/artsci/artifact_origins.dm
create mode 100644 code/modules/artsci/artifact_triggers.dm
create mode 100644 code/modules/artsci/testing_machines/analysis_form.dm
create mode 100644 code/modules/artsci/testing_machines/heater.dm
create mode 100644 code/modules/artsci/testing_machines/xray.dm
create mode 100644 code/modules/artsci/testing_machines/zapper.dm
create mode 100644 code/modules/events/artifact_spawn.dm
delete mode 100644 code/modules/research/experimentor.dm
create mode 100644 icons/obj/artifacts.dmi
create mode 100644 icons/obj/machines/artifact_machines.dmi
create mode 100644 icons/obj/machines/atmospherics/heatingpad.dmi
create mode 100644 icons/obj/service/bureaucracy.dmi
create mode 100644 sound/misc/bonk.ogg
create mode 100644 tgui/packages/tgui/interfaces/ArtifactForm.js
create mode 100644 tgui/packages/tgui/interfaces/ArtifactPanel.js
create mode 100644 tgui/packages/tgui/interfaces/ArtifactXray.js
create mode 100644 tgui/packages/tgui/interfaces/ArtifactZapper.js
create mode 100644 tools/UpdatePaths/79239_artsci.txt
diff --git a/_maps/RandomRuins/SpaceRuins/old_infiltrator.dmm b/_maps/RandomRuins/SpaceRuins/old_infiltrator.dmm
index 43640452bb24..dfcdf0852910 100644
--- a/_maps/RandomRuins/SpaceRuins/old_infiltrator.dmm
+++ b/_maps/RandomRuins/SpaceRuins/old_infiltrator.dmm
@@ -233,9 +233,6 @@
/area/ruin/space/unpowered)
"pl" = (
/obj/structure/table,
-/obj/item/relic{
- pixel_y = 5
- },
/turf/open/floor/mineral/plastitanium/red/airless,
/area/ruin/space/unpowered)
"qL" = (
diff --git a/_maps/RandomRuins/SpaceRuins/oldstation.dmm b/_maps/RandomRuins/SpaceRuins/oldstation.dmm
index b52d531e4613..cff5ce4e560e 100644
--- a/_maps/RandomRuins/SpaceRuins/oldstation.dmm
+++ b/_maps/RandomRuins/SpaceRuins/oldstation.dmm
@@ -7926,7 +7926,6 @@
/obj/structure/closet/crate/secure/science{
req_access = list("away_science")
},
-/obj/item/relic,
/obj/item/transfer_valve,
/obj/item/raw_anomaly_core/bluespace,
/obj/item/raw_anomaly_core/random,
diff --git a/_maps/RandomZLevels/snowdin.dmm b/_maps/RandomZLevels/snowdin.dmm
index 5ee092df524a..e8b02c27fbae 100644
--- a/_maps/RandomZLevels/snowdin.dmm
+++ b/_maps/RandomZLevels/snowdin.dmm
@@ -7266,8 +7266,6 @@
"yj" = (
/obj/machinery/light/directional/west,
/obj/structure/closet/crate,
-/obj/item/relic,
-/obj/item/relic,
/obj/effect/mapping_helpers/broken_floor,
/turf/open/floor/plating,
/area/awaymission/snowdin/post/mining_dock)
@@ -7346,9 +7344,6 @@
/area/awaymission/snowdin/outside)
"yx" = (
/obj/structure/closet/crate,
-/obj/item/relic,
-/obj/item/relic,
-/obj/item/relic,
/turf/open/floor/plating,
/area/awaymission/snowdin/post/mining_dock)
"yy" = (
@@ -10988,14 +10983,6 @@
slowdown = 1
},
/area/awaymission/snowdin/cave)
-"Pc" = (
-/obj/structure/closet/crate,
-/obj/item/relic,
-/obj/item/relic,
-/obj/item/relic,
-/obj/effect/turf_decal/tile/neutral/fourcorners,
-/turf/open/floor/iron,
-/area/awaymission/snowdin/post/mining_dock)
"Pd" = (
/turf/open/misc/asteroid/snow{
floor_variance = 0;
@@ -11697,12 +11684,6 @@
},
/turf/open/floor/iron,
/area/awaymission/snowdin/post/engineering)
-"SN" = (
-/obj/structure/closet/crate,
-/obj/item/relic,
-/obj/effect/turf_decal/tile/neutral/fourcorners,
-/turf/open/floor/iron,
-/area/awaymission/snowdin/post/mining_dock)
"SQ" = (
/obj/machinery/light/small/directional/west,
/obj/item/stack/rods{
@@ -62841,7 +62822,7 @@ wL
wL
WB
yj
-Pc
+Sm
xW
eJ
eJ
@@ -63097,7 +63078,7 @@ xy
Tn
xy
WB
-SN
+Sm
yx
wD
eJ
diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm
index fb586a584fc5..c66a4cb06bc5 100644
--- a/_maps/map_files/Deltastation/DeltaStation2.dmm
+++ b/_maps/map_files/Deltastation/DeltaStation2.dmm
@@ -9048,7 +9048,6 @@
/turf/open/floor/iron/dark,
/area/station/engineering/atmos)
"ccY" = (
-/obj/item/relic,
/obj/effect/turf_decal/tile/neutral/fourcorners,
/obj/effect/decal/cleanable/dirt,
/obj/structure/closet/crate/cardboard,
@@ -31039,7 +31038,6 @@
/obj/structure/table/reinforced,
/obj/structure/sign/poster/official/random/directional/south,
/obj/effect/turf_decal/tile/neutral/half/contrasted,
-/obj/item/relic,
/obj/effect/spawner/random/maintenance,
/turf/open/floor/iron,
/area/station/maintenance/department/science)
@@ -94251,7 +94249,6 @@
/obj/machinery/door/firedoor/border_only{
dir = 1
},
-/obj/item/relic,
/obj/item/assembly/signaler{
pixel_x = 6;
pixel_y = 5
diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm
index 936568a048c8..cc8011280571 100644
--- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm
+++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm
@@ -19505,7 +19505,6 @@
/area/station/tcommsat/computer)
"giR" = (
/obj/structure/table,
-/obj/item/relic,
/obj/effect/spawner/random/maintenance,
/obj/item/screwdriver,
/obj/effect/decal/cleanable/cobweb/cobweb2,
@@ -32550,7 +32549,6 @@
/obj/structure/rack,
/obj/item/poster/random_official,
/obj/effect/spawner/random/maintenance,
-/obj/item/relic,
/turf/open/floor/plating,
/area/station/maintenance/department/medical/morgue)
"kxp" = (
diff --git a/_maps/map_files/KiloStation/KiloStation.dmm b/_maps/map_files/KiloStation/KiloStation.dmm
index 1d51253a6d62..3b2982edbc0c 100644
--- a/_maps/map_files/KiloStation/KiloStation.dmm
+++ b/_maps/map_files/KiloStation/KiloStation.dmm
@@ -46293,7 +46293,6 @@
/obj/item/flashlight,
/obj/item/flashlight/flare,
/obj/machinery/light/small/directional/west,
-/obj/item/relic,
/obj/structure/sign/poster/contraband/random/directional/west,
/turf/open/floor/plating,
/area/station/maintenance/starboard)
diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm
index b2f793621147..12ef489c07ba 100644
--- a/_maps/map_files/MetaStation/MetaStation.dmm
+++ b/_maps/map_files/MetaStation/MetaStation.dmm
@@ -3498,7 +3498,13 @@
/area/station/maintenance/port/fore)
"bkM" = (
/obj/machinery/light/small/directional/south,
-/turf/open/floor/engine,
+/obj/machinery/atmospherics/components/unary/thermomachine/freezer{
+ dir = 1
+ },
+/obj/effect/turf_decal/trimline/purple/filled/warning{
+ dir = 10
+ },
+/turf/open/floor/iron/dark,
/area/station/science/explab)
"bkO" = (
/obj/machinery/iv_drip,
@@ -5154,9 +5160,8 @@
/turf/open/floor/iron,
/area/station/science/xenobiology)
"bRb" = (
-/obj/effect/decal/cleanable/dirt,
/obj/effect/landmark/start/scientist,
-/turf/open/floor/engine,
+/turf/open/floor/iron/white,
/area/station/science/explab)
"bRG" = (
/obj/machinery/shower/directional/west,
@@ -7312,10 +7317,6 @@
},
/turf/open/floor/iron/white,
/area/station/medical/medbay/central)
-"cKm" = (
-/obj/structure/training_machine,
-/turf/open/floor/engine,
-/area/station/science/explab)
"cKn" = (
/obj/structure/lattice/catwalk,
/turf/open/space/basic,
@@ -7367,6 +7368,7 @@
/area/station/engineering/atmos)
"cLu" = (
/obj/effect/turf_decal/tile/purple,
+/obj/effect/landmark/start/scientist,
/turf/open/floor/iron/white,
/area/station/science/explab)
"cLx" = (
@@ -15445,6 +15447,10 @@
},
/turf/open/floor/iron,
/area/station/command/teleporter)
+"fRE" = (
+/obj/machinery/artifact_xray,
+/turf/open/floor/iron/white,
+/area/station/science/explab)
"fRG" = (
/obj/machinery/firealarm/directional/east,
/obj/effect/decal/cleanable/dirt,
@@ -15731,7 +15737,9 @@
"fXm" = (
/obj/structure/window/reinforced/spawner/directional/west,
/obj/machinery/light/small/directional/south,
-/turf/open/floor/engine,
+/obj/structure/table,
+/obj/item/analysis_bin,
+/turf/open/floor/iron/white,
/area/station/science/explab)
"fXK" = (
/obj/effect/turf_decal/trimline/red/filled/corner{
@@ -17979,7 +17987,8 @@
/area/station/command/bridge)
"gNC" = (
/obj/machinery/light/small/directional/north,
-/turf/open/floor/engine,
+/obj/effect/artifact_spawner,
+/turf/open/floor/iron/white,
/area/station/science/explab)
"gND" = (
/obj/machinery/iv_drip,
@@ -22637,10 +22646,6 @@
},
/turf/open/floor/iron,
/area/station/engineering/break_room)
-"itn" = (
-/obj/effect/decal/cleanable/oil,
-/turf/open/floor/engine,
-/area/station/science/explab)
"itp" = (
/obj/structure/chair{
dir = 8
@@ -27322,7 +27327,8 @@
/turf/open/floor/iron,
/area/station/science/xenobiology)
"jSk" = (
-/turf/open/floor/engine,
+/obj/machinery/atmospherics/components/unary/artifact_heatingpad,
+/turf/open/floor/iron/white,
/area/station/science/explab)
"jSm" = (
/obj/structure/cable,
@@ -42129,7 +42135,7 @@
dir = 8
},
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
-/turf/open/floor/engine,
+/turf/open/floor/iron/white,
/area/station/science/explab)
"phz" = (
/obj/machinery/navbeacon{
@@ -46040,10 +46046,12 @@
/turf/open/floor/iron/white,
/area/station/science/ordnance/testlab)
"qBK" = (
-/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{
- dir = 8
+/obj/machinery/portable_atmospherics/canister/plasma,
+/obj/machinery/atmospherics/components/unary/portables_connector/visible,
+/obj/effect/turf_decal/trimline/purple/filled/warning{
+ dir = 9
},
-/turf/open/floor/engine,
+/turf/open/floor/iron/dark,
/area/station/science/explab)
"qCa" = (
/obj/structure/rack,
@@ -47890,8 +47898,12 @@
/turf/open/floor/iron/dark,
/area/station/security/checkpoint/medical)
"rkT" = (
-/obj/item/target/syndicate,
-/turf/open/floor/engine,
+/obj/structure/table,
+/obj/item/sticker/analysis_form{
+ pixel_y = -3;
+ pixel_x = -5
+ },
+/turf/open/floor/iron/white,
/area/station/science/explab)
"rla" = (
/obj/effect/turf_decal/stripes/line{
@@ -49396,7 +49408,13 @@
/area/station/cargo/miningoffice)
"rKB" = (
/obj/machinery/airalarm/directional/east,
-/turf/open/floor/engine,
+/obj/machinery/atmospherics/pipe/smart/manifold/yellow{
+ dir = 4
+ },
+/obj/effect/turf_decal/trimline/purple/filled/warning{
+ dir = 8
+ },
+/turf/open/floor/iron/dark,
/area/station/science/explab)
"rKG" = (
/obj/structure/cable,
@@ -52222,11 +52240,6 @@
},
/turf/open/floor/iron,
/area/station/hallway/primary/central)
-"sIW" = (
-/obj/effect/decal/cleanable/dirt,
-/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
-/turf/open/floor/engine,
-/area/station/science/explab)
"sIX" = (
/obj/machinery/atmospherics/pipe/smart/simple/purple/visible/layer2{
dir = 9
@@ -57751,7 +57764,11 @@
c_tag = "Science Firing Range";
network = list("ss13","rd")
},
-/turf/open/floor/engine,
+/obj/effect/artifact_spawner,
+/obj/effect/turf_decal/trimline/purple/filled/warning{
+ dir = 6
+ },
+/turf/open/floor/iron/dark,
/area/station/science/explab)
"uEn" = (
/obj/machinery/light/directional/west,
@@ -59107,10 +59124,6 @@
/obj/machinery/light/directional/south,
/turf/open/floor/iron/dark,
/area/station/science/ordnance/storage)
-"vdx" = (
-/obj/machinery/rnd/experimentor,
-/turf/open/floor/engine,
-/area/station/science/explab)
"vdJ" = (
/obj/structure/closet/crate/freezer,
/obj/item/reagent_containers/blood/random,
@@ -59196,7 +59209,10 @@
name = "Research Test Chamber";
req_access = list("science")
},
-/turf/open/floor/engine,
+/obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{
+ dir = 1
+ },
+/turf/open/floor/iron/white,
/area/station/science/explab)
"vfm" = (
/obj/effect/turf_decal/tile/bar,
@@ -59982,7 +59998,11 @@
"vrv" = (
/obj/structure/window/reinforced/spawner/directional/west,
/obj/machinery/light/small/directional/north,
-/turf/open/floor/engine,
+/obj/machinery/artifact_zapper{
+ dir = 4
+ },
+/obj/effect/turf_decal/trimline/purple/filled/warning,
+/turf/open/floor/iron/dark,
/area/station/science/explab)
"vrF" = (
/obj/structure/table/reinforced,
@@ -66774,7 +66794,7 @@
/area/station/hallway/primary/port)
"xOx" = (
/obj/structure/window/reinforced/spawner/directional/west,
-/turf/open/floor/engine,
+/turf/open/floor/iron/white,
/area/station/science/explab)
"xOF" = (
/obj/docking_port/stationary/laborcamp_home{
@@ -103178,10 +103198,10 @@ gwf
ucd
tVv
uDS
-vdx
-sIW
+bQs
+bQs
bRb
-cKm
+bQs
oWk
nYU
fPD
@@ -103434,9 +103454,9 @@ hXd
gwf
itC
tVv
-jSk
-itn
-sIW
+fRE
+bQs
+bQs
jSk
rkT
oWk
@@ -103692,7 +103712,7 @@ emh
fQo
tVv
gNC
-jSk
+bQs
qBK
rKB
bkM
diff --git a/_maps/map_files/tramstation/maintenance_modules/barcargoupper_1.dmm b/_maps/map_files/tramstation/maintenance_modules/barcargoupper_1.dmm
index eab9280b8fa7..098b8f5c276a 100644
--- a/_maps/map_files/tramstation/maintenance_modules/barcargoupper_1.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/barcargoupper_1.dmm
@@ -191,7 +191,6 @@
/obj/structure/closet/crate,
/obj/effect/spawner/random/engineering/material_cheap,
/obj/effect/spawner/random/engineering/tool,
-/obj/item/relic,
/turf/open/floor/plating,
/area/station/maintenance/department/cargo)
"yB" = (
diff --git a/_maps/map_files/tramstation/maintenance_modules/barcargoupper_2.dmm b/_maps/map_files/tramstation/maintenance_modules/barcargoupper_2.dmm
index ea1beb34cb0d..54ecd1927f2a 100644
--- a/_maps/map_files/tramstation/maintenance_modules/barcargoupper_2.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/barcargoupper_2.dmm
@@ -288,7 +288,6 @@
/obj/structure/closet,
/obj/effect/spawner/random/engineering/material_cheap,
/obj/effect/spawner/random/maintenance/three,
-/obj/item/relic,
/turf/open/floor/iron/smooth,
/area/station/maintenance/department/cargo)
"yM" = (
diff --git a/_maps/map_files/tramstation/maintenance_modules/barcargoupper_attachment_b_3.dmm b/_maps/map_files/tramstation/maintenance_modules/barcargoupper_attachment_b_3.dmm
index ed88b7cffa3e..5b2b3de14505 100644
--- a/_maps/map_files/tramstation/maintenance_modules/barcargoupper_attachment_b_3.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/barcargoupper_attachment_b_3.dmm
@@ -25,7 +25,6 @@
"r" = (
/obj/structure/table,
/obj/effect/decal/cleanable/dirt,
-/obj/item/relic,
/obj/item/pen{
pixel_x = -4
},
diff --git a/_maps/map_files/tramstation/maintenance_modules/barcargoupper_cave_1.dmm b/_maps/map_files/tramstation/maintenance_modules/barcargoupper_cave_1.dmm
index ae78ae6731f9..e1d1deeda0e1 100644
--- a/_maps/map_files/tramstation/maintenance_modules/barcargoupper_cave_1.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/barcargoupper_cave_1.dmm
@@ -153,10 +153,6 @@
/obj/item/stack/ore/iron,
/turf/open/misc/asteroid,
/area/station/asteroid)
-"M" = (
-/obj/item/relic,
-/turf/open/misc/asteroid/dug,
-/area/station/asteroid)
"O" = (
/obj/effect/turf_decal/sand/plating,
/obj/structure/barricade/wooden,
@@ -719,7 +715,7 @@ V
G
A
z
-M
+A
z
l
l
diff --git a/_maps/map_files/tramstation/maintenance_modules/barcargoupper_cave_3.dmm b/_maps/map_files/tramstation/maintenance_modules/barcargoupper_cave_3.dmm
index e110ecac03b6..ad673312a1fa 100644
--- a/_maps/map_files/tramstation/maintenance_modules/barcargoupper_cave_3.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/barcargoupper_cave_3.dmm
@@ -129,10 +129,6 @@
/obj/item/assembly/signaler,
/turf/open/misc/asteroid,
/area/station/asteroid)
-"D" = (
-/obj/item/relic,
-/turf/open/misc/asteroid/dug,
-/area/station/asteroid)
"F" = (
/turf/closed/wall,
/area/station/maintenance/department/cargo)
@@ -1073,7 +1069,7 @@ Y
Y
u
P
-D
+I
P
I
i
diff --git a/_maps/map_files/tramstation/maintenance_modules/cargoscilower_attachment_a_2.dmm b/_maps/map_files/tramstation/maintenance_modules/cargoscilower_attachment_a_2.dmm
index aa82b69f2e72..a153b4c84bd7 100644
--- a/_maps/map_files/tramstation/maintenance_modules/cargoscilower_attachment_a_2.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/cargoscilower_attachment_a_2.dmm
@@ -1,8 +1,4 @@
//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
-"a" = (
-/obj/item/relic,
-/turf/open/misc/asteroid/dug,
-/area/station/asteroid)
"b" = (
/turf/open/misc/asteroid/dug,
/area/station/asteroid)
@@ -150,7 +146,7 @@ n
"}
(9,1,1) = {"
b
-a
+b
b
i
V
diff --git a/_maps/map_files/tramstation/maintenance_modules/dormmedupper_3.dmm b/_maps/map_files/tramstation/maintenance_modules/dormmedupper_3.dmm
index 69b84841086c..beb484b0ff9d 100644
--- a/_maps/map_files/tramstation/maintenance_modules/dormmedupper_3.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/dormmedupper_3.dmm
@@ -435,7 +435,6 @@
/area/station/maintenance/department/crew_quarters/dorms)
"XV" = (
/obj/item/stack/ore/glass,
-/obj/item/relic,
/turf/open/misc/asteroid/dug,
/area/station/maintenance/department/crew_quarters/dorms)
"ZJ" = (
diff --git a/_maps/map_files/tramstation/maintenance_modules/secbarupper_attachment_b_2.dmm b/_maps/map_files/tramstation/maintenance_modules/secbarupper_attachment_b_2.dmm
index 52dd568eee62..bdf4c13f3604 100644
--- a/_maps/map_files/tramstation/maintenance_modules/secbarupper_attachment_b_2.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/secbarupper_attachment_b_2.dmm
@@ -32,7 +32,6 @@
"p" = (
/obj/effect/decal/cleanable/dirt,
/obj/structure/closet/crate,
-/obj/item/relic,
/turf/open/floor/plating,
/area/station/maintenance/department/security)
"r" = (
diff --git a/_maps/map_files/tramstation/maintenance_modules/secbarupper_cave_1.dmm b/_maps/map_files/tramstation/maintenance_modules/secbarupper_cave_1.dmm
index 3b99c265a1f4..b66a69d8837c 100644
--- a/_maps/map_files/tramstation/maintenance_modules/secbarupper_cave_1.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/secbarupper_cave_1.dmm
@@ -22,10 +22,6 @@
"s" = (
/turf/open/misc/asteroid/dug,
/area/station/asteroid)
-"v" = (
-/obj/item/relic,
-/turf/open/misc/asteroid/dug,
-/area/station/asteroid)
"w" = (
/turf/template_noop,
/area/template_noop)
@@ -135,7 +131,7 @@ w
"}
(5,1,1) = {"
F
-v
+s
A
T
T
diff --git a/_maps/map_files/tramstation/maintenance_modules/secservicelower_attachment_c_2.dmm b/_maps/map_files/tramstation/maintenance_modules/secservicelower_attachment_c_2.dmm
index cb12e658a1ab..f5669b654ceb 100644
--- a/_maps/map_files/tramstation/maintenance_modules/secservicelower_attachment_c_2.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/secservicelower_attachment_c_2.dmm
@@ -99,7 +99,6 @@
"J" = (
/obj/effect/decal/cleanable/dirt,
/obj/structure/closet/crate,
-/obj/item/relic,
/obj/effect/spawner/random/maintenance/two,
/turf/open/floor/plating,
/area/station/maintenance/port/central)
diff --git a/_maps/map_files/tramstation/maintenance_modules/servicecargolower_2.dmm b/_maps/map_files/tramstation/maintenance_modules/servicecargolower_2.dmm
index 02960cfadc51..72d9de95ca98 100644
--- a/_maps/map_files/tramstation/maintenance_modules/servicecargolower_2.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/servicecargolower_2.dmm
@@ -352,7 +352,6 @@
/area/station/maintenance/starboard/greater)
"ST" = (
/obj/item/stack/ore/glass,
-/obj/item/relic,
/turf/open/misc/asteroid/dug,
/area/station/asteroid)
"Ui" = (
diff --git a/_maps/map_files/tramstation/maintenance_modules/servicecargolower_attachment_b_3.dmm b/_maps/map_files/tramstation/maintenance_modules/servicecargolower_attachment_b_3.dmm
index 06409ce21b48..d42b045c7a31 100644
--- a/_maps/map_files/tramstation/maintenance_modules/servicecargolower_attachment_b_3.dmm
+++ b/_maps/map_files/tramstation/maintenance_modules/servicecargolower_attachment_b_3.dmm
@@ -5,11 +5,6 @@
"b" = (
/turf/closed/wall/rust,
/area/station/maintenance/starboard/greater)
-"d" = (
-/obj/item/relic,
-/obj/item/stack/ore/glass,
-/turf/open/misc/asteroid/dug,
-/area/station/maintenance/starboard/greater)
"i" = (
/obj/modular_map_root/tramstation{
key = "servicecargolower_attachment_b";
@@ -106,7 +101,7 @@ W
i
t
n
-d
+Q
n
"}
(7,1,1) = {"
diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm
index b55b930ccc1b..9aa08ebccc5e 100644
--- a/_maps/map_files/tramstation/tramstation.dmm
+++ b/_maps/map_files/tramstation/tramstation.dmm
@@ -4314,7 +4314,6 @@
"axO" = (
/obj/structure/table,
/obj/effect/decal/cleanable/dirt,
-/obj/item/relic,
/obj/item/pen{
pixel_x = -4
},
@@ -5249,7 +5248,6 @@
"aFk" = (
/obj/effect/decal/cleanable/dirt,
/obj/structure/closet/crate,
-/obj/item/relic,
/obj/effect/spawner/random/maintenance/two,
/turf/open/floor/plating,
/area/station/maintenance/central/greater)
@@ -17661,11 +17659,6 @@
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
/turf/open/floor/iron/dark,
/area/station/science/lower)
-"erw" = (
-/obj/item/stack/ore/glass,
-/obj/item/relic,
-/turf/open/misc/asteroid/dug,
-/area/station/asteroid)
"erR" = (
/obj/effect/decal/cleanable/dirt,
/obj/effect/decal/cleanable/food/pie_smudge,
@@ -21239,7 +21232,6 @@
/obj/structure/closet,
/obj/effect/spawner/random/engineering/material_cheap,
/obj/effect/spawner/random/maintenance/three,
-/obj/item/relic,
/turf/open/floor/iron/smooth,
/area/station/maintenance/department/cargo)
"fDk" = (
@@ -29661,7 +29653,6 @@
"iwi" = (
/obj/effect/decal/cleanable/dirt,
/obj/structure/closet/crate,
-/obj/item/relic,
/turf/open/floor/plating,
/area/station/maintenance/department/security)
"iwm" = (
@@ -35436,10 +35427,6 @@
/obj/effect/landmark/blobstart,
/turf/open/floor/iron,
/area/station/engineering/atmos)
-"kpo" = (
-/obj/item/relic,
-/turf/open/misc/asteroid/dug,
-/area/station/maintenance/department/cargo)
"kpq" = (
/obj/machinery/atmospherics/pipe/color_adapter,
/turf/closed/wall/r_wall,
@@ -54216,11 +54203,6 @@
},
/turf/open/floor/iron,
/area/station/engineering/atmos)
-"qsZ" = (
-/obj/item/relic,
-/obj/item/stack/ore/glass,
-/turf/open/misc/asteroid/dug,
-/area/station/maintenance/starboard/greater)
"qtb" = (
/obj/machinery/atmospherics/pipe/smart/manifold/violet/visible{
dir = 8
@@ -58016,10 +57998,6 @@
"rBT" = (
/turf/closed/wall/rock,
/area/station/engineering)
-"rBV" = (
-/obj/item/relic,
-/turf/open/misc/asteroid/dug,
-/area/station/maintenance/starboard/lesser)
"rBW" = (
/obj/structure/cable,
/turf/open/floor/iron/dark,
@@ -118981,7 +118959,7 @@ crh
kPE
kPE
vlD
-qsZ
+seu
vlD
acy
acx
@@ -120544,7 +120522,7 @@ psP
tHh
bcj
lQw
-rBV
+lQw
lQw
aWg
oPQ
@@ -122064,7 +122042,7 @@ wmK
eXL
alA
whL
-erw
+vfm
rvV
mbJ
jBC
@@ -175237,7 +175215,7 @@ apj
nIh
dbA
xaW
-kpo
+dbA
itB
hDd
itB
diff --git a/_maps/shuttles/whiteship_delta.dmm b/_maps/shuttles/whiteship_delta.dmm
index a62e34436770..b2e669767791 100644
--- a/_maps/shuttles/whiteship_delta.dmm
+++ b/_maps/shuttles/whiteship_delta.dmm
@@ -1821,7 +1821,6 @@
},
/obj/structure/closet/crate,
/obj/item/grenade/chem_grenade/metalfoam,
-/obj/item/relic,
/obj/item/t_scanner,
/obj/effect/spawner/random/maintenance/three,
/obj/effect/turf_decal/tile/neutral/fourcorners,
diff --git a/code/__DEFINES/artifact.dm b/code/__DEFINES/artifact.dm
new file mode 100644
index 000000000000..a4f643a9d5d0
--- /dev/null
+++ b/code/__DEFINES/artifact.dm
@@ -0,0 +1,41 @@
+
+//size
+#define ARTIFACT_SIZE_TINY 0 //items
+#define ARTIFACT_SIZE_SMALL 1 //big items
+#define ARTIFACT_SIZE_LARGE 2 //not items
+// stimuli
+#define STIMULUS_CARBON_TOUCH "carbontouch"
+#define STIMULUS_SILICON_TOUCH "silicontouch"
+#define STIMULUS_FORCE "force"
+#define STIMULUS_HEAT "heat" //also works for cold
+#define STIMULUS_SHOCK "electricity"
+#define STIMULUS_RADIATION "rads"
+#define STIMULUS_DATA "data"
+// origins
+#define ORIGIN_NARSIE "narnar"
+#define ORIGIN_SILICON "silicon"
+#define ORIGIN_WIZARD "wiznerd"
+// rarities
+#define ARTIFACT_COMMON 500
+#define ARTIFACT_UNCOMMON 400
+#define ARTIFACT_VERYUNCOMMON 300
+#define ARTIFACT_RARE 250
+#define ARTIFACT_VERYRARE 140
+
+//cuts down on boiler plate code
+#define ARTIFACT_SETUP(X,subsystem) ##X/Initialize(mapload, var/forced_origin = null){\
+ . = ..();\
+ START_PROCESSING(subsystem, src);\
+ assoc_comp = AddComponent(assoc_comp, forced_origin);\
+ RegisterSignal(src, COMSIG_PARENT_QDELETING, PROC_REF(on_delete));\
+} \
+##X/proc/on_delete(atom/source){\
+ SIGNAL_HANDLER;\
+ assoc_comp = null;\
+} \
+##X/process(){\
+ assoc_comp?.heat_from_turf(get_turf(src));\
+ if(assoc_comp?.active) {\
+ assoc_comp.effect_process();\
+ }\
+}
diff --git a/code/__DEFINES/dcs/signals/signals_sticker.dm b/code/__DEFINES/dcs/signals/signals_sticker.dm
new file mode 100644
index 000000000000..4b18efbff5b2
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_sticker.dm
@@ -0,0 +1,6 @@
+//Stickers
+
+/// Called on an object when a sticker is sticked to it (sticker item, user)
+#define COMSIG_STICKER_STICKED "comsig_sticker_stick"
+/// Called on an object when a sticker is unsticked from it (sticker item)
+#define COMSIG_STICKER_UNSTICKED "comsig_sticker_unstick"
diff --git a/code/__DEFINES/logging.dm b/code/__DEFINES/logging.dm
index e7f91f42aad2..7694ecfada7a 100644
--- a/code/__DEFINES/logging.dm
+++ b/code/__DEFINES/logging.dm
@@ -6,7 +6,6 @@
#define INVESTIGATE_CRAFTING "crafting"
#define INVESTIGATE_DEATHS "deaths"
#define INVESTIGATE_ENGINE "engine"
-#define INVESTIGATE_EXPERIMENTOR "experimentor"
#define INVESTIGATE_GRAVITY "gravity"
#define INVESTIGATE_HALLUCINATIONS "hallucinations"
#define INVESTIGATE_HYPERTORUS "hypertorus"
@@ -17,6 +16,7 @@
#define INVESTIGATE_RESEARCH "research"
#define INVESTIGATE_WIRES "wires"
#define INVESTIGATE_NANITES "nanites"
+#define INVESTIGATE_ARTIFACT "artifact"
// Logging types for log_message()
#define LOG_ATTACK (1 << 0)
diff --git a/code/__DEFINES/stickers.dm b/code/__DEFINES/stickers.dm
new file mode 100644
index 000000000000..9a420160d401
--- /dev/null
+++ b/code/__DEFINES/stickers.dm
@@ -0,0 +1,8 @@
+//sticker contraband values for spawning in boxes
+
+/// dont spawn in sticker kits
+#define STICKER_NOSPAWN 0
+/// spawn in cargo sticker kits
+#define STICKER_NORMAL 1
+/// spawn in syndicate sticker kit,not actually checked but good for keeping track or something
+#define STICKER_SYNDICATE 2
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 7ef7a0f10c38..237db7ad1c24 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -716,6 +716,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_POSTERBOY "poster_boy"
#define TRAIT_THROWINGARM "throwing_arm"
+///if the atom has a sticker attached to it
+#define TRAIT_STICKERED "stickered"
+
#define TRAIT_JAILBIRD "jailbird"
#define TRAIT_STOWAWAY "stowaway"
#define TRAIT_LOUD_ASS "loud_ass"
@@ -1174,6 +1177,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define SPEAKING_FROM_TONGUE "tongue"
///trait source that sign language should use
#define SPEAKING_FROM_HANDS "hands"
+/// this object cannot have its export value be shown by export scanner (shows as unknown)
+#define TRAIT_HIDDEN_EXPORT_VALUE "hiddenexportvalue"
///FOOD TRAITS
///Trait for Fire Burps
diff --git a/code/_globalvars/lists/maintenance_loot.dm b/code/_globalvars/lists/maintenance_loot.dm
index c95758d3f150..b6fdf4b87b32 100644
--- a/code/_globalvars/lists/maintenance_loot.dm
+++ b/code/_globalvars/lists/maintenance_loot.dm
@@ -197,8 +197,8 @@ GLOBAL_LIST_INIT(uncommon_loot, list(//uncommon: useful items
/obj/item/pen/screwdriver = 1,
) = 8,
- list(//strange objects
- /obj/item/relic = 5,
+ list(//artifacts
+ /obj/effect/artifact_spawner = 4,
) = 8,
list(//construction and crafting
diff --git a/code/controllers/subsystem/artifacts.dm b/code/controllers/subsystem/artifacts.dm
new file mode 100644
index 000000000000..40f721ccf5b9
--- /dev/null
+++ b/code/controllers/subsystem/artifacts.dm
@@ -0,0 +1,68 @@
+
+/proc/spawn_artifact(turf/loc, forced_origin)
+ if (!loc)
+ return
+
+ var/list/datum/component/artifact/weighted_list
+ if(forced_origin)
+ weighted_list = SSartifacts.artifact_rarities[forced_origin]
+ else
+ weighted_list = SSartifacts.artifact_rarities["all"]
+
+ var/type = initial(pick_weight(weighted_list).associated_object)
+ return new type(loc)
+
+
+/// Subsystem for managing artifacts.
+SUBSYSTEM_DEF(artifacts)
+ name = "Artifacts"
+
+ flags = SS_NO_FIRE | SS_NO_INIT
+
+ ///Currently existing artifacts with a component (key = obj artifact, value = component artifact)
+ var/list/artifacts = list()
+ /// typepaths of artifact components
+ var/list/datum/component/artifact/artifact_types = list()
+ /// names of all artifact subtype type_name
+ var/list/artifact_type_names = list()
+ /// artifact typepath from type_name
+ var/list/artifact_types_from_name = list()
+ /// instances of origins
+ var/list/artifact_origins = list()
+ /// assoc list of origin type name to instance
+ var/list/artifact_origins_by_typename = list()
+ /// assoc list of IC name to origin typename
+ var/list/artifact_origin_name_to_typename = list()
+ /// list of IC origin names
+ var/list/artifact_origins_names = list()
+ /// artifact rarities for weighted picking
+ var/list/artifact_rarities = list()
+ /// get an artifact trigger typepath by name
+ var/list/artifact_trigger_name_to_type = list()
+
+/datum/controller/subsystem/artifacts/New()
+ ..()
+ artifact_rarities["all"] = list()
+
+ // origin list
+ for (var/origin_type in subtypesof(/datum/artifact_origin))
+ var/datum/artifact_origin/origin = new origin_type
+ artifact_origins += origin
+ artifact_origins_names += origin.name
+ artifact_origin_name_to_typename[origin.name] = origin.type_name
+ artifact_origins_by_typename[origin.type_name] = origin
+ artifact_rarities[origin.type_name] = list()
+ for (var/datum/component/artifact/artifact_type as anything in subtypesof(/datum/component/artifact))
+ var/weight = initial(artifact_type.weight)
+ if(!weight)
+ continue
+ artifact_types += artifact_type
+ artifact_type_names += initial(artifact_type.type_name)
+ artifact_types_from_name[initial(artifact_type.type_name)] = artifact_type
+
+ artifact_rarities["all"][artifact_type] = weight
+ for (var/origin in artifact_rarities)
+ if(origin in initial(artifact_type.valid_origins))
+ artifact_rarities[origin][artifact_type] = weight
+ for (var/datum/artifact_trigger/trigger_type as anything in subtypesof(/datum/artifact_trigger))
+ artifact_trigger_name_to_type[initial(trigger_type.name)] = trigger_type
diff --git a/code/datums/components/attached_sticker.dm b/code/datums/components/attached_sticker.dm
deleted file mode 100644
index cb37382da968..000000000000
--- a/code/datums/components/attached_sticker.dm
+++ /dev/null
@@ -1,84 +0,0 @@
-// The attached sticker
-
-/datum/component/attached_sticker
- dupe_mode = COMPONENT_DUPE_ALLOWED
- ///The overlay we apply to things we stick to
- var/mutable_appearance/sticker_overlay
- ///The turf our COMSIG_TURF_EXPOSE is registered to, so we can unregister it later.
- var/turf/signal_turf
- ///Our physical sticker to drop
- var/obj/item/sticker
- ///Can we be washed off?
- var/washable = TRUE
-
-/datum/component/attached_sticker/Initialize(px, py, obj/stick, mob/living/user, cleanable=TRUE)
- if(!isatom(parent))
- return COMPONENT_INCOMPATIBLE
- washable = cleanable
- var/atom/atom_parent = parent
- sticker = stick
- sticker_overlay = mutable_appearance(stick.icon, stick.icon_state , layer = atom_parent.layer + 1, appearance_flags = RESET_COLOR | PIXEL_SCALE)
- sticker_overlay.pixel_x = px
- sticker_overlay.pixel_y = py
- atom_parent.add_overlay(sticker_overlay)
- if(isliving(parent) && user)
- var/mob/living/victim = parent
- if(victim.client)
- user.log_message("stuck [sticker] to [key_name(victim)]", LOG_ATTACK)
- victim.log_message("had [sticker] stuck to them by [key_name(user)]", LOG_ATTACK)
- else if(isturf(parent) && (sticker.resistance_flags & FLAMMABLE))
- //register signals on the users turf instead because we can assume they are on flooring sticking it to a wall so it should burn (otherwise it would fruitlessly check wall temperature)
- signal_turf = (user && isclosedturf(parent)) ? get_turf(user) : parent
- RegisterSignal(signal_turf, COMSIG_TURF_EXPOSE, PROC_REF(on_turf_expose))
- sticker.moveToNullspace()
-
-///Move sticker item from nullspace, delete this component, cut overlay
-/datum/component/attached_sticker/proc/peel(atom/source)
- SIGNAL_HANDLER
- if(!parent) // just in case
- return
- var/atom/as_atom = parent
- as_atom.cut_overlay(sticker_overlay)
- sticker_overlay = null
- if(sticker)
- sticker.forceMove(isturf(parent) ? parent : as_atom.drop_location())
- sticker.pixel_y = rand(-4,1)
- sticker.pixel_x = rand(-3,3)
- sticker = null
- qdel(src)
-
-/datum/component/attached_sticker/RegisterWithParent()
- if(sticker.resistance_flags & FLAMMABLE)
- RegisterSignal(parent, COMSIG_LIVING_IGNITED, PROC_REF(on_ignite))
- if(washable)
- RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(peel))
- RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(on_attached_qdel))
-
-/datum/component/attached_sticker/UnregisterFromParent()
- if(sticker.resistance_flags & FLAMMABLE)
- UnregisterSignal(parent, list(COMSIG_LIVING_IGNITED, COMSIG_PARENT_QDELETING))
- if(signal_turf)
- UnregisterSignal(signal_turf, COMSIG_TURF_EXPOSE)
- signal_turf = null
- if(washable)
- UnregisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT)
-
-///Signal handler for COMSIG_TURF_EXPOSE, deletes this sticker if the temperature is above 100C and it is flammable
-/datum/component/attached_sticker/proc/on_turf_expose(datum/source, datum/gas_mixture/air, exposed_temperature)
- SIGNAL_HANDLER
- if(exposed_temperature <= FIRE_MINIMUM_TEMPERATURE_TO_EXIST)
- return
- qdel(sticker)
- peel()
-
-///Signal handler for COMSIG_LIVING_IGNITED, deletes this sticker
-/datum/component/attached_sticker/proc/on_ignite(datum/source)
- SIGNAL_HANDLER
- qdel(sticker)
- peel()
-
-/// Signal handler for COMSIG_PARENT_QDELETING, deletes this sticker if the attached object is deleted
-/datum/component/attached_sticker/proc/on_attached_qdel(datum/source)
- SIGNAL_HANDLER
- qdel(sticker)
- peel()
diff --git a/code/datums/elements/sticker.dm b/code/datums/elements/sticker.dm
deleted file mode 100644
index d089d9944da9..000000000000
--- a/code/datums/elements/sticker.dm
+++ /dev/null
@@ -1,45 +0,0 @@
-/datum/element/sticker
- ///The typepath for our attached sticker component
- var/stick_type = /datum/component/attached_sticker
- ///If TRUE, our attached_sticker can be washed off
- var/washable = TRUE
-
-/datum/element/sticker/Attach(datum/target, sticker_type, cleanable=TRUE)
- . = ..()
- if(!isitem(target))
- return ELEMENT_INCOMPATIBLE
- RegisterSignal(target, COMSIG_ITEM_AFTERATTACK, PROC_REF(on_afterattack))
- RegisterSignal(target, COMSIG_MOVABLE_IMPACT, PROC_REF(on_throw_impact))
- if(sticker_type)
- stick_type = sticker_type
- washable = cleanable
-
-/datum/element/sticker/Detach(datum/source)
- . = ..()
- UnregisterSignal(source, list(COMSIG_ITEM_AFTERATTACK, COMSIG_MOVABLE_IMPACT))
-
-/datum/element/sticker/proc/on_afterattack(obj/item/source, atom/target, mob/living/user, prox, params)
- SIGNAL_HANDLER
- if(!prox)
- return
- if(!isatom(target))
- return
- var/list/parameters = params2list(params)
- if(!LAZYACCESS(parameters, ICON_X) || !LAZYACCESS(parameters, ICON_Y))
- return
- var/divided_size = world.icon_size / 2
- var/px = text2num(LAZYACCESS(parameters, ICON_X)) - divided_size
- var/py = text2num(LAZYACCESS(parameters, ICON_Y)) - divided_size
-
- user.do_attack_animation(target)
- do_stick(source, target, user, px, py)
-
-///Add our stick_type to the target with px and py as pixel x and pixel y respectively
-/datum/element/sticker/proc/do_stick(obj/item/source, atom/target, mob/living/user, px, py)
- target.AddComponent(stick_type, px, py, source, user, washable)
-
-/datum/element/sticker/proc/on_throw_impact(obj/item/source, atom/hit_atom, datum/thrownthing/throwingdatum)
- SIGNAL_HANDLER
- if(prob(50))
- do_stick(source, hit_atom, null, rand(-7,7), rand(-7,7))
- source.balloon_alert_to_viewers("the sticker lands on its sticky side!")
diff --git a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm
index 46fa94eff8c3..55546a1a5bc4 100644
--- a/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm
+++ b/code/game/objects/items/circuitboards/machines/machine_circuitboards.dm
@@ -944,15 +944,6 @@
/datum/stock_part/manipulator = 1,
/datum/stock_part/micro_laser = 1)
-/obj/item/circuitboard/machine/experimentor
- name = "E.X.P.E.R.I-MENTOR"
- greyscale_colors = CIRCUIT_COLOR_SCIENCE
- build_path = /obj/machinery/rnd/experimentor
- req_components = list(
- /datum/stock_part/scanning_module = 1,
- /datum/stock_part/manipulator = 2,
- /datum/stock_part/micro_laser = 2)
-
/obj/item/circuitboard/machine/mech_recharger
name = "Mechbay Recharger"
greyscale_colors = CIRCUIT_COLOR_SCIENCE
@@ -1477,6 +1468,35 @@
/obj/item/stock_parts/micro_laser = 1,
/obj/item/stack/sheet/glass = 1,
/obj/item/stock_parts/scanning_module = 1)
+
+/obj/item/circuitboard/machine/artifactxray
+ name = "Artifact X-Ray Machine"
+ greyscale_colors = CIRCUIT_COLOR_SCIENCE
+ build_path = /obj/machinery/artifact_xray
+ req_components = list(
+ /obj/item/stock_parts/capacitor = 1,
+ /datum/stock_part/scanning_module = 1,
+ /obj/item/stock_parts/micro_laser = 1)
+
+/obj/item/circuitboard/machine/artifactheater
+ name = "Artifact Heating Pad"
+ greyscale_colors = CIRCUIT_COLOR_SCIENCE
+ build_path = /obj/machinery/atmospherics/components/unary/artifact_heatingpad
+ req_components = list(
+ /datum/stock_part/matter_bin = 2,
+ /obj/item/stack/cable_coil = 1,
+ /obj/item/stack/sheet/glass = 1)
+
+/obj/item/circuitboard/machine/artifactzapper
+ name = "Artifact Zapper"
+ greyscale_colors = CIRCUIT_COLOR_SCIENCE
+ build_path = /obj/machinery/artifact_zapper
+ req_components = list(
+ /datum/stock_part/capacitor = 2,
+ /datum/stock_part/scanning_module = 1,
+ /obj/item/stack/cable_coil = 1,
+ /obj/item/stack/sheet/glass = 3)
+
/obj/item/circuitboard/machine/navbeacon
name = "Bot Navigational Beacon"
greyscale_colors = CIRCUIT_COLOR_SCIENCE
diff --git a/code/game/objects/items/debug_items.dm b/code/game/objects/items/debug_items.dm
index 4ab086d90c9b..3921a0e82454 100644
--- a/code/game/objects/items/debug_items.dm
+++ b/code/game/objects/items/debug_items.dm
@@ -147,3 +147,20 @@
for(var/spawn_atom in (choice == "No" ? typesof(path) : subtypesof(path)))
new spawn_atom(loc_turf)
+
+/obj/item/debug/artifact_activator
+ name = "artifact activation wand"
+ desc = "Aim at an artifact and click to activate it."
+ icon = 'icons/obj/weapons/guns/magic.dmi'
+ icon_state = "nothingwand"
+ inhand_icon_state = "wand"
+ lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/items_righthand.dmi'
+ w_class = WEIGHT_CLASS_SMALL
+
+/obj/item/debug/artifact_activator/afterattack(atom/target, mob/user, proximity)
+ ..()
+ var/datum/component/artifact/artifact = target.GetComponent(/datum/component/artifact)
+ if(isobj(target) && artifact)
+ artifact.Activate()
+
diff --git a/code/game/objects/items/manuals.dm b/code/game/objects/items/manuals.dm
index 9460848bbdcc..2cd93dc64070 100644
--- a/code/game/objects/items/manuals.dm
+++ b/code/game/objects/items/manuals.dm
@@ -361,13 +361,6 @@
starting_title = "Research and Development 101"
page_link = "Guide_to_Research_and_Development"
-/obj/item/book/manual/wiki/experimentor
- name = "Mentoring your Experiments"
- icon_state = "rdbook"
- starting_author = "Dr. H.P. Kritz"
- starting_title = "Mentoring your Experiments"
- page_link = "Experimentor"
-
/obj/item/book/manual/wiki/cooking_to_serve_man
name = "To Serve Man"
desc = "It's a cookbook!"
diff --git a/code/game/objects/items/sticker.dm b/code/game/objects/items/sticker.dm
index 512e64c4cbd3..a2560fac02b8 100644
--- a/code/game/objects/items/sticker.dm
+++ b/code/game/objects/items/sticker.dm
@@ -1,3 +1,5 @@
+#define MAX_ALLOWED_STICKERS 12
+
/// parent type for all other stickers. do not spawn directly
/obj/item/sticker
name = "sticker"
@@ -8,10 +10,16 @@
w_class = WEIGHT_CLASS_TINY
throw_range = 3
vis_flags = VIS_INHERIT_DIR | VIS_INHERIT_PLANE | VIS_INHERIT_LAYER
- ///If not null, pick an icon_state from this list
- var/icon_states
+ ///The overlay we apply to things we stick to
+ var/mutable_appearance/sticker_overlay
+ ///A list of icon_states to pick an icon_state on Initialize, provided it is not null.
+ var/list/icon_states
+ ///The thing we are attached to
+ var/atom/attached
+ ///The turf our COMSIG_TURF_EXPOSE is registered to, so we can unregister it later.
+ var/turf/signal_turf
/// If the sticker should be disincluded from normal sticker boxes.
- var/contraband = FALSE
+ var/contraband = STICKER_NORMAL
/obj/item/sticker/Initialize(mapload)
. = ..()
@@ -19,7 +27,104 @@
icon_state = pick(icon_states)
pixel_y = rand(-3,3)
pixel_x = rand(-3,3)
- AddElement(/datum/element/sticker)
+
+/obj/item/sticker/afterattack(atom/target, mob/living/user, prox, params)
+ . = ..()
+ if(!prox)
+ return
+ if(!isliving(target) && !isobj(target) && !isturf(target))
+ return
+ var/list/parameters = params2list(params)
+ if(!LAZYACCESS(parameters, ICON_X) || !LAZYACCESS(parameters, ICON_Y))
+ return
+ var/divided_size = world.icon_size / 2
+ var/px = text2num(LAZYACCESS(parameters, ICON_X)) - divided_size
+ var/py = text2num(LAZYACCESS(parameters, ICON_Y)) - divided_size
+ . |= AFTERATTACK_PROCESSED_ITEM
+ user.do_attack_animation(target)
+ stick(target,user,px,py)
+ return .
+
+///Sticks this sticker to the target, with the pixel offsets being px and py.
+/obj/item/sticker/proc/stick(atom/target, mob/living/user, px,py)
+ if(COUNT_TRAIT_SOURCES(target, TRAIT_STICKERED) >= MAX_ALLOWED_STICKERS)
+ target.balloon_alert(user, "sticker won't stick!")
+ return FALSE
+ sticker_overlay = mutable_appearance(icon, icon_state , layer = target.layer + 1, appearance_flags = RESET_COLOR | PIXEL_SCALE)
+ sticker_overlay.pixel_x = px
+ sticker_overlay.pixel_y = py
+ target.add_overlay(sticker_overlay)
+ attached = target
+ if(isliving(target) && user)
+ var/mob/living/victim = target
+ if(victim.client)
+ user.log_message("stuck [src] to [key_name(victim)]", LOG_ATTACK)
+ victim.log_message("had [src] stuck to them by [key_name(user)]", LOG_ATTACK)
+ register_signals(user)
+ moveToNullspace()
+ SEND_SIGNAL(attached, COMSIG_STICKER_STICKED, src, user)
+
+///Makes this sticker move from nullspace and cut the overlay from the object it is attached to, silent for no visible message.
+/obj/item/sticker/proc/peel(datum/source)
+ SIGNAL_HANDLER
+ if(!attached)
+ return
+ attached.cut_overlay(sticker_overlay)
+ sticker_overlay = null
+ forceMove(attached.drop_location())
+ pixel_y = rand(-4,1)
+ pixel_x = rand(-3,3)
+ unregister_signals()
+ SEND_SIGNAL(attached, COMSIG_STICKER_UNSTICKED, src)
+ attached = null
+
+///Registers signals to the object it is attached to
+/obj/item/sticker/proc/register_signals(mob/living/user)
+ if(isturf(attached))
+ //register signals on the users turf instead because we can assume they are on flooring sticking it to a wall so it should burn (otherwise it would fruitlessly check wall temperature)
+ signal_turf = (user && isclosedturf(attached)) ? get_turf(user) : attached
+ RegisterSignal(signal_turf, COMSIG_TURF_EXPOSE, PROC_REF(on_turf_expose))
+ RegisterSignal(attached, COMSIG_LIVING_IGNITED, PROC_REF(on_ignite))
+ RegisterSignal(attached, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(peel))
+ RegisterSignal(attached, COMSIG_PARENT_QDELETING, PROC_REF(on_attached_qdel))
+ ADD_TRAIT(attached, TRAIT_STICKERED, REF(src))
+
+//Unregisters signals from the object it is attached to
+/obj/item/sticker/proc/unregister_signals(datum/source)
+ SIGNAL_HANDLER
+ UnregisterSignal(attached, list(COMSIG_COMPONENT_CLEAN_ACT, COMSIG_LIVING_IGNITED, COMSIG_PARENT_QDELETING))
+ REMOVE_TRAIT(attached, TRAIT_STICKERED, REF(src))
+ if(signal_turf)
+ UnregisterSignal(signal_turf, COMSIG_TURF_EXPOSE)
+ signal_turf = null
+
+/obj/item/sticker/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
+ . = ..()
+ if(!. && prob(50))
+ stick(hit_atom,rand(-7,7),rand(-7,7))
+ attached.balloon_alert_to_viewers("the sticker lands on its sticky side!")
+
+///Signal handler for COMSIG_TURF_EXPOSE, deletes this sticker if the temperature is above 100C and it is flammable
+/obj/item/sticker/proc/on_turf_expose(datum/source, datum/gas_mixture/air, exposed_temperature)
+ SIGNAL_HANDLER
+ if(!(resistance_flags & FLAMMABLE) || exposed_temperature <= FIRE_MINIMUM_TEMPERATURE_TO_EXIST)
+ return
+ peel()
+ qdel(src)
+
+///Signal handler for COMSIG_LIVING_IGNITED, deletes this sticker, if it is flammable
+/obj/item/sticker/proc/on_ignite(datum/source)
+ SIGNAL_HANDLER
+ if(!(resistance_flags & FLAMMABLE))
+ return
+ peel()
+ qdel(src)
+
+/// Signal handler for COMSIG_QDELETING, deletes this sticker if the attached object is deleted
+/obj/item/sticker/proc/on_attached_qdel(datum/source)
+ SIGNAL_HANDLER
+ peel()
+ qdel(src)
/obj/item/sticker/smile
name = "smiley sticker"
@@ -96,7 +201,7 @@
/obj/item/sticker/syndicate
name = "syndicate sticker"
icon_state = "synd"
- contraband = TRUE
+ contraband = STICKER_SYNDICATE
/obj/item/sticker/syndicate/c4
name = "C-4 sticker"
@@ -129,3 +234,5 @@
/obj/item/sticker/syndicate/trap
name = "bear trap sticker"
icon_state = "trap"
+
+#undef MAX_ALLOWED_STICKERS
diff --git a/code/game/objects/items/storage/boxes/service_boxes.dm b/code/game/objects/items/storage/boxes/service_boxes.dm
index 3ff3480817e7..e830848231a0 100644
--- a/code/game/objects/items/storage/boxes/service_boxes.dm
+++ b/code/game/objects/items/storage/boxes/service_boxes.dm
@@ -211,7 +211,7 @@
/obj/item/storage/box/stickers/proc/generate_non_contraband_stickers_list()
. = list()
for(var/obj/item/sticker/sticker_type as anything in subtypesof(/obj/item/sticker))
- if(!initial(sticker_type.contraband))
+ if(initial(sticker_type.contraband) == STICKER_NORMAL)
. += sticker_type
return .
/obj/item/storage/box/stickers/PopulateContents()
diff --git a/code/game/turfs/closed/minerals.dm b/code/game/turfs/closed/minerals.dm
index 05ba6ea2edc5..4d8a30895c28 100644
--- a/code/game/turfs/closed/minerals.dm
+++ b/code/game/turfs/closed/minerals.dm
@@ -231,6 +231,7 @@
/obj/item/stack/ore/titanium = 11,
/obj/item/stack/ore/uranium = 5,
/turf/closed/mineral/gibtonite = 4,
+ /turf/closed/mineral/artifact = 1,
)
/turf/closed/mineral/random/Initialize(mapload)
@@ -352,6 +353,7 @@
/obj/item/stack/ore/titanium = 11,
/obj/item/stack/ore/uranium = 5,
/turf/closed/mineral/gibtonite/volcanic = 4,
+ /turf/closed/mineral/artifact/volcanic = 1,
)
/// A turf that can't we can't build openspace chasms on or spawn ruins in.
@@ -854,4 +856,30 @@
/turf/closed/mineral/strong/ex_act(severity, target)
return FALSE
+//Artifact spawning rock
+
+/turf/closed/mineral/artifact
+ mineralAmt = 1
+ //icon_state = "rock_Gibtonite_inactive"
+ scan_state = "rock_Artifact"
+
+/turf/closed/mineral/artifact/gets_drilled(mob/user, give_exp = FALSE, triggered_by_explosion = FALSE)
+ if(istype(user))
+ SEND_SIGNAL(user, COMSIG_MOB_MINED, src, give_exp)
+
+ if(!triggered_by_explosion) //if someone maxcaps lavaland and promptly unearths every single artifact thats gonna fuck up and activate some of them which is not good
+ new /obj/effect/artifact_spawner(src)
+
+ var/flags = NONE
+ if(defer_change)
+ flags = CHANGETURF_DEFER_CHANGE
+ var/turf/open/mined = ScrapeAway(null, flags)
+ mined.update_visuals()
+
+/turf/closed/mineral/artifact/volcanic
+ turf_type = /turf/open/misc/asteroid/basalt/lava_land_surface
+ baseturfs = /turf/open/misc/asteroid/basalt/lava_land_surface
+ initial_gas_mix = LAVALAND_DEFAULT_ATMOS
+ defer_change = TRUE
+
#undef MINING_MESSAGE_COOLDOWN
diff --git a/code/modules/admin/admin_investigate.dm b/code/modules/admin/admin_investigate.dm
index da88a92bbb49..3f4c041f387a 100644
--- a/code/modules/admin/admin_investigate.dm
+++ b/code/modules/admin/admin_investigate.dm
@@ -24,7 +24,7 @@
INVESTIGATE_CRAFTING,
INVESTIGATE_DEATHS,
INVESTIGATE_ENGINE,
- INVESTIGATE_EXPERIMENTOR,
+ INVESTIGATE_ARTIFACT,
INVESTIGATE_GRAVITY,
INVESTIGATE_HALLUCINATIONS,
INVESTIGATE_HYPERTORUS,
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index c01ab9e5b21d..bde9cc2f72c4 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -44,6 +44,7 @@ GLOBAL_PROTECT(admin_verbs_admin)
/datum/admins/proc/toggleoocdead, /*toggles ooc on/off for everyone who is dead*/
/datum/admins/proc/trophy_manager,
/datum/admins/proc/view_all_circuits,
+ /datum/admins/proc/open_artifactpanel,
/datum/verbs/menu/Admin/verb/playerpanel, /* It isn't /datum/admin but it fits no less */
// Client procs
/client/proc/admin_call_shuttle, /*allows us to call the emergency shuttle*/
diff --git a/code/modules/admin/verbs/artifacts.dm b/code/modules/admin/verbs/artifacts.dm
new file mode 100644
index 000000000000..b61ce71d7b22
--- /dev/null
+++ b/code/modules/admin/verbs/artifacts.dm
@@ -0,0 +1,69 @@
+/datum/artifactpanel
+ var/user
+
+/datum/admins/proc/open_artifactpanel()
+ set category = "Admin.Game"
+ set name = "Artifact Panel"
+ set desc = "Artifact panel"
+
+ if(!check_rights(R_ADMIN))
+ return
+
+ var/datum/artifactpanel/artifactpanel = new(usr)
+
+ artifactpanel.ui_interact(usr)
+
+/datum/artifactpanel/New(to_user, mob/living/silicon/robot/to_borg)
+ user = CLIENT_FROM_VAR(to_user)
+
+/datum/artifactpanel/ui_state(mob/user)
+ return GLOB.admin_state
+
+/datum/artifactpanel/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ArtifactPanel")
+ ui.open()
+
+/datum/artifactpanel/ui_data(mob/user)
+ . = list()
+ .["artifacts"] = list()
+ for(var/obj/art in SSartifacts.artifacts)
+ var/datum/component/artifact/component = SSartifacts.artifacts[art]
+ .["artifacts"] += list(list(
+ "name" = art.name,
+ "ref" = REF(art),
+ "loc" = "[AREACOORD(art)]",
+ "active" = component.active,
+ "typename" = component.type_name,
+ "lastprint" = "[art.fingerprintslast]",
+ ))
+
+/datum/artifactpanel/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ switch (action)
+ if ("delete")
+ var/atom/movable/to_delete = locate(params["ref"]) in SSartifacts.artifacts
+ if(isnull(to_delete))
+ return
+ var/ask = tgui_alert(usr, "Are you sure you want to delete that?", "Are you sure about that?", list("YEAH BABY LETS GO", "Naw"))
+ if(ask == "YEAH BABY LETS GO")
+ message_admins("[key_name_admin(user)] has deleted [to_delete] via Artifact Panel at [ADMIN_VERBOSEJMP(to_delete)].")
+ qdel(to_delete)
+ if ("toggle")
+ var/atom/movable/object = locate(params["ref"]) in SSartifacts.artifacts
+ if(isnull(object))
+ return
+ var/datum/component/artifact/component = SSartifacts.artifacts[object]
+ var/ask = tgui_alert(usr, "Do you want to do it silently?", "Silently?", list("Visible", "Silent"))
+ var/do_silently = FALSE
+ if(ask == "Silent")
+ do_silently = TRUE
+
+ message_admins("[key_name_admin(user)] has [component.active ? "deactivated" : "activated"] [object][ADMIN_FLW(object)] via Artifact Panel.")
+ if(component.active)
+ component.Deactivate(silent = do_silently)
+ else
+ component.Activate(silent = do_silently)
diff --git a/code/modules/artsci/artifact.dm b/code/modules/artsci/artifact.dm
new file mode 100644
index 000000000000..370b1df83528
--- /dev/null
+++ b/code/modules/artsci/artifact.dm
@@ -0,0 +1,21 @@
+/obj/structure/artifact
+ name = "Artifact"
+ desc = "Yell at coderbus."
+ icon = 'icons/obj/artifacts.dmi'
+ icon_state = "narnar-1" //for when something shits itself or a map editor
+ resistance_flags = LAVA_PROOF | ACID_PROOF | INDESTRUCTIBLE
+ anchored = FALSE
+ density = TRUE
+ var/datum/component/artifact/assoc_comp
+
+ARTIFACT_SETUP(/obj/structure/artifact, SSobj)
+
+/obj/effect/artifact_spawner
+ name = "Random Artifact Spawner"
+ icon = 'icons/obj/artifacts.dmi'
+ icon_state = "wiznerd-1"
+
+/obj/effect/artifact_spawner/Initialize(mapload)
+ . = ..()
+ spawn_artifact(loc)
+ qdel(src)
diff --git a/code/modules/artsci/artifact_datum.dm b/code/modules/artsci/artifact_datum.dm
new file mode 100644
index 000000000000..8704279e6457
--- /dev/null
+++ b/code/modules/artsci/artifact_datum.dm
@@ -0,0 +1,317 @@
+/datum/component/artifact
+ ///object related to this datum for spawning
+ var/obj/associated_object
+ ///actual specific object for this instance
+ var/obj/holder
+ ///list weight for picking this artifact datum (0 = never)
+ var/weight = 0
+ ///size class for visuals (ARTIFACT_SIZE_TINY,ARTIFACT_SIZE_SMALL,ARTIFACT_SIZE_LARGE)
+ var/artifact_size = ARTIFACT_SIZE_LARGE
+ ///type name for displaying on analysis forms
+ var/type_name = "coderbus moment"
+ /// fake name for when unanalyzed
+ var/fake_name
+ ///randomly generated names by origin for when it gets analyzed
+ var/list/names = list()
+ ///Is the artifact active?
+ var/active = FALSE
+ ///Triggers that activate the artifact
+ var/list/datum/artifact_trigger/triggers = list()
+ var/max_triggers = 2
+ ///Valid triggers to pick
+ var/list/valid_triggers = list(
+ /datum/artifact_trigger/carbon_touch,
+ /datum/artifact_trigger/silicon_touch,
+ /datum/artifact_trigger/force,
+ /datum/artifact_trigger/heat,
+ /datum/artifact_trigger/shock,
+ /datum/artifact_trigger/radiation,
+ /datum/artifact_trigger/data
+ )
+ ///origin datum
+ var/datum/artifact_origin/artifact_origin
+ ///origin datums to pick
+ var/list/valid_origins = list(
+ ORIGIN_NARSIE,
+ ORIGIN_WIZARD,
+ ORIGIN_SILICON
+ )
+ var/activation_message
+ var/activation_sound
+ var/deactivation_message
+ var/deactivation_sound
+ var/hint_text = "emits a faint noise.."
+ var/examine_hint
+ var/mutable_appearance/act_effect
+ /// Potency in percentage, used for making more strong artifacts need more stimulus. (1% - 100%) 100 is strongest.
+ var/potency = 1
+
+ ///structure description from x-ray machines
+ var/xray_result = "NONE"
+ ///we store our analysis form var here
+ var/obj/item/sticker/analysis_form/analysis
+
+/datum/component/artifact/Initialize(forced_origin = null)
+ . = ..()
+ if(!isobj(parent))
+ return COMPONENT_INCOMPATIBLE
+ holder = parent
+ SSartifacts.artifacts[holder] = src
+
+ if(forced_origin)
+ valid_origins = list(forced_origin)
+ artifact_origin = SSartifacts.artifact_origins_by_typename[pick(valid_origins)]
+ fake_name = "[pick(artifact_origin.adjectives)] [pick(isitem(holder) ? artifact_origin.nouns_small : artifact_origin.nouns_large)]"
+ for(var/datum/artifact_origin/og in SSartifacts.artifact_origins)
+ var/a_name = og.generate_name()
+ if(a_name)
+ names[og.type_name] = a_name
+ else
+ names[og.type_name] = "[pick(og.adjectives)] [pick(isitem(holder) ? og.nouns_small : og.nouns_large)]"
+ holder.name = fake_name
+ holder.desc = "You have absolutely no clue what this thing is or how it got here."
+
+ var/dat_icon
+ var/origin_name = artifact_origin.type_name
+ switch(artifact_size)
+ if(ARTIFACT_SIZE_LARGE)
+ dat_icon = "[origin_name]-[rand(1,artifact_origin.max_icons)]"
+ if(ARTIFACT_SIZE_SMALL)
+ dat_icon = "[origin_name]-item-[rand(1,artifact_origin.max_item_icons)]"
+ if(ARTIFACT_SIZE_TINY)
+ dat_icon = "[origin_name]-item-small-[rand(1,artifact_origin.max_item_icons)]"
+ holder.icon_state = dat_icon
+
+ act_effect = mutable_appearance(holder.icon, "[holder.icon_state]fx", offset_spokesman = holder, alpha = rand(artifact_origin.overlay_alpha_minimum, artifact_origin.overlay_alpha_maximum))
+ act_effect.color = rgb(rand(artifact_origin.overlay_red_minimum,artifact_origin.overlay_red_maximum),rand(artifact_origin.overlay_green_minimum,artifact_origin.overlay_green_maximum),rand(artifact_origin.overlay_blue_minimum,artifact_origin.overlay_blue_maximum))
+ act_effect.overlays += emissive_appearance(act_effect.icon, act_effect.icon_state, holder, alpha = act_effect.alpha)
+ activation_sound = pick(artifact_origin.activation_sounds)
+ if(LAZYLEN(artifact_origin.deactivation_sounds))
+ deactivation_sound = pick(artifact_origin.deactivation_sounds)
+
+ var/trigger_amount = rand(1,max_triggers)
+ while(trigger_amount>0)
+ var/selection = pick(valid_triggers)
+ valid_triggers -= selection
+ triggers += new selection()
+ trigger_amount--
+
+ ADD_TRAIT(holder, TRAIT_HIDDEN_EXPORT_VALUE, INNATE_TRAIT)
+ setup()
+ potency = clamp(potency, 0, 100)
+ for(var/datum/artifact_trigger/trigger in triggers)
+ trigger.amount = max(trigger.base_amount,trigger.base_amount + (trigger.max_amount - trigger.base_amount) * (potency/100))
+ trigger.range = trigger.amount + (trigger.hint_range * 2)
+
+/datum/component/artifact/proc/setup()
+ return
+
+/datum/component/artifact/RegisterWithParent()
+ RegisterSignals(parent, list(COMSIG_ATOM_DESTRUCTION, COMSIG_PARENT_QDELETING), PROC_REF(Artifact_Destroyed))
+ RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(Touched))
+ RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(attack_by))
+ RegisterSignal(parent, COMSIG_ATOM_ATTACK_ROBOT, PROC_REF(robot_attack))
+ RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, PROC_REF(emp_act))
+ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+ RegisterSignal(parent, COMSIG_ATOM_EX_ACT, PROC_REF(ex_act))
+ RegisterSignal(parent, COMSIG_STICKER_STICKED, PROC_REF(on_analysis))
+ RegisterSignal(parent, COMSIG_STICKER_UNSTICKED, PROC_REF(deanalyze))
+
+/datum/component/artifact/UnregisterFromParent()
+ SSartifacts.artifacts -= parent
+ UnregisterSignal(parent, list(COMSIG_ITEM_PICKUP,COMSIG_ATOM_ATTACK_HAND,COMSIG_ATOM_DESTRUCTION,COMSIG_PARENT_EXAMINE,COMSIG_ATOM_EMP_ACT,COMSIG_ATOM_EX_ACT,COMSIG_STICKER_STICKED,COMSIG_STICKER_UNSTICKED))
+
+/datum/component/artifact/proc/Activate(silent=FALSE)
+ if(active) //dont activate activated objects
+ return FALSE
+ if(activation_sound && !silent)
+ playsound(holder, activation_sound, 75, TRUE)
+ if(activation_message && !silent)
+ holder.visible_message(span_notice("[holder] [activation_message]"))
+ active = TRUE
+ holder.add_overlay(act_effect)
+ effect_activate(silent)
+ return TRUE
+
+/datum/component/artifact/proc/on_examine(atom/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+ if(examine_hint)
+ examine_list += examine_hint
+
+/datum/component/artifact/proc/Deactivate(silent=FALSE)
+ if(!active)
+ return
+ if(deactivation_sound && !silent)
+ playsound(holder, deactivation_sound, 75, TRUE)
+ if(deactivation_message && !silent)
+ holder.visible_message(span_notice("[holder] [deactivation_message]"))
+ active = FALSE
+ holder.cut_overlay(act_effect)
+ effect_deactivate(silent)
+
+/datum/component/artifact/proc/Artifact_Destroyed(atom/source, silent=FALSE)
+ SIGNAL_HANDLER
+ //UnregisterSignal(holder, COMSIG_IN_RANGE_OF_IRRADIATION)
+ if(!silent && !QDELETED(holder))
+ holder.loc.visible_message(span_warning("[holder] [artifact_origin.destroy_message]"))
+ Deactivate(silent=TRUE)
+ if(!QDELETED(holder))
+ qdel(holder) // if it isnt already...
+// Stimuli stuff
+/datum/component/artifact/proc/Stimulate(stimuli, severity = 0)
+ if(!stimuli || active)
+ return
+ for(var/datum/artifact_trigger/trigger in triggers)
+ if(active)
+ break
+ if(trigger.needed_stimulus == stimuli)
+ if(trigger.check_amount)
+ if(ISINRANGE(severity, trigger.amount, trigger.range))
+ Activate()
+ else if(hint_text && (trigger.hint_range > abs(severity - (trigger.hint_range + trigger.range)) || trigger.hint_range > abs(severity - trigger.hint_range)))
+ if(prob(trigger.hint_prob))
+ holder.visible_message(span_notice("[holder] [hint_text]"))
+ else
+ Activate()
+
+/datum/component/artifact/proc/Touched(atom/source, mob/living/user)
+ SIGNAL_HANDLER
+ if(!user.Adjacent(holder))
+ return
+ if(isAI(user) || isobserver(user)) //sanity
+ return
+ if(user.pulling && isliving(user.pulling))
+ if((user.istate & ISTATE_HARM) && user.pulling.Adjacent(holder) && user.grab_state > GRAB_PASSIVE)
+ holder.visible_message(span_warning("[user] forcefully shoves [user.pulling] against the [holder]!"))
+ Touched(user.pulling)
+ else if(!(user.istate & ISTATE_HARM))
+ holder.visible_message(span_notice("[user] gently pushes [user.pulling] against the [holder]."))
+ Stimulate(STIMULUS_CARBON_TOUCH)
+ return
+ if(artifact_size == ARTIFACT_SIZE_LARGE) //only large artifacts since the average spessman wouldnt notice)
+ user.visible_message(span_notice("[user] touches [holder]."))
+ if(ishuman(user))
+ var/mob/living/carbon/human/human = user
+ var/obj/item/bodypart/arm = human.get_active_hand()
+ if(arm.bodytype & BODYTYPE_ROBOTIC)
+ Stimulate(STIMULUS_SILICON_TOUCH)
+ else
+ Stimulate(STIMULUS_CARBON_TOUCH)
+ else if(iscarbon(user))
+ Stimulate(STIMULUS_CARBON_TOUCH)
+ else if(issilicon(user))
+ Stimulate(STIMULUS_SILICON_TOUCH)
+ Stimulate(STIMULUS_FORCE,1)
+ if(active)
+ effect_touched(user)
+ return
+ if(LAZYLEN(artifact_origin.touch_descriptors))
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(to_chat), user, span_notice("[pick(artifact_origin.touch_descriptors)]")), 0.5 SECONDS)
+
+/datum/component/artifact/proc/robot_attack(datum/source, mob/living/user)
+ SIGNAL_HANDLER
+ if(!user.Adjacent(holder))
+ return
+ Touched(null, user)
+
+//doesnt work
+/*/datum/artifact/proc/Irradiating(atom/source, datum/radiation_pulse_information/pulse_information, insulation_to_target)
+ SIGNAL_HANDLER
+ to_chat(world,"[get_perceived_radiation_danger(pulse_information,insulation_to_target)]")
+ if(!active)
+ Stimulate(STIMULUS_RADIATION, get_perceived_radiation_danger(pulse_information,insulation_to_target)*2)*/
+
+/datum/component/artifact/proc/attack_by(atom/source, obj/item/I, mob/user)
+ SIGNAL_HANDLER
+ if(istype(I,/obj/item/weldingtool))
+ if(I.use(1))
+ holder.visible_message(span_warning("[user] burns the artifact with the [I]!"))
+ Stimulate(STIMULUS_HEAT,800)
+ playsound(user,pick(I.usesound),50, TRUE)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+ if(istype(I, /obj/item/bodypart/arm))
+ var/obj/item/bodypart/arm/arm = I
+ holder.visible_message(span_notice("[user] presses the [arm] against the artifact.")) //pressing stuff against stuff isnt very severe so
+ if(arm.bodytype & BODYTYPE_ROBOTIC)
+ Stimulate(STIMULUS_SILICON_TOUCH)
+ else
+ Stimulate(STIMULUS_CARBON_TOUCH)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+ if(istype(I,/obj/item/assembly/igniter))
+ holder.visible_message(span_warning("[user] zaps the artifact with the [I]!"))
+ Stimulate(STIMULUS_HEAT, I.heat)
+ Stimulate(STIMULUS_SHOCK, 700)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+ if(istype(I, /obj/item/lighter))
+ var/obj/item/lighter/lighter = I
+ if(lighter.lit)
+ holder.visible_message(span_warning("[user] burns the artifact with the [I]!"))
+ Stimulate(STIMULUS_HEAT, lighter.heat)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+ if(I.tool_behaviour == TOOL_MULTITOOL)
+ holder.visible_message(span_warning("[user] shocks the artifact with the [I]!"))
+ Stimulate(STIMULUS_SHOCK, 1000)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+ if(istype(I,/obj/item/shockpaddles))
+ var/obj/item/shockpaddles/paddles = I
+ if(paddles.defib.deductcharge(2000))
+ playsound(user,'sound/machines/defib_zap.ogg', 50, TRUE, -1)
+ Stimulate(STIMULUS_SHOCK, 2000)
+ holder.visible_message(span_warning("[user] shocks the artifact with the [I]."))
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+ if(istype(I,/obj/item/disk/data) || istype(I,/obj/item/circuitboard))
+ Stimulate(STIMULUS_DATA)
+ holder.visible_message(span_notice("[user] touches the artifact with the [I]"))
+
+ if(I.force)
+ Stimulate(STIMULUS_FORCE,I.force)
+
+/datum/component/artifact/proc/ex_act(atom/source, severity)
+ SIGNAL_HANDLER
+ switch(severity)
+ if(EXPLODE_DEVASTATE)
+ Stimulate(STIMULUS_FORCE,100)
+ Stimulate(STIMULUS_HEAT,600)
+ if(EXPLODE_HEAVY)
+ Stimulate(STIMULUS_FORCE,50)
+ Stimulate(STIMULUS_HEAT,450)
+ if(EXPLODE_LIGHT)
+ Stimulate(STIMULUS_FORCE,25)
+ Stimulate(STIMULUS_HEAT,360)
+
+/datum/component/artifact/proc/emp_act(atom/source, severity)
+ SIGNAL_HANDLER
+ Stimulate(STIMULUS_SHOCK, 800 * severity)
+ Stimulate(STIMULUS_RADIATION, 2 * severity)
+
+/datum/component/artifact/proc/heat_from_turf(turf/target)
+ Stimulate(STIMULUS_HEAT, target.return_air().temperature)
+
+/datum/component/artifact/proc/on_analysis(atom/source, obj/item/sticker/sticker, mob/user)
+ SIGNAL_HANDLER
+ if(analysis)
+ to_chat(user, "You peel off [analysis], to make room for [sticker].")
+ sticker.peel()
+ if(!istype(sticker, /obj/item/sticker/analysis_form))
+ return
+ analysis = sticker
+
+/datum/component/artifact/proc/deanalyze(atom/source)
+ SIGNAL_HANDLER
+ analysis = null
+
+// Effects for subtypes
+/datum/component/artifact/proc/effect_activate(silent)
+ return
+/datum/component/artifact/proc/effect_deactivate(silent)
+ return
+/datum/component/artifact/proc/effect_touched(mob/living/user)
+ return
+/datum/component/artifact/proc/effect_process()
+ return
diff --git a/code/modules/artsci/artifact_items/artifact_cell.dm b/code/modules/artsci/artifact_items/artifact_cell.dm
new file mode 100644
index 000000000000..b6fe5c238eaa
--- /dev/null
+++ b/code/modules/artsci/artifact_items/artifact_cell.dm
@@ -0,0 +1,40 @@
+/obj/item/stock_parts/cell/artifact
+ icon = 'icons/obj/artifacts.dmi'
+ icon_state = "narnar-1"
+ resistance_flags = LAVA_PROOF | ACID_PROOF | INDESTRUCTIBLE
+ ratingdesc = FALSE
+ charge_light_type = null
+ var/datum/component/artifact/assoc_comp = /datum/component/artifact/cell
+
+ARTIFACT_SETUP(/obj/item/stock_parts/cell/artifact, SSobj)
+
+
+/datum/component/artifact/cell
+ associated_object = /obj/item/stock_parts/cell/artifact
+ artifact_size = ARTIFACT_SIZE_TINY
+ type_name = "Power Cell"
+ weight = ARTIFACT_UNCOMMON
+ xray_result = "SEGMENTED"
+ valid_triggers = list(/datum/artifact_trigger/heat, /datum/artifact_trigger/shock, /datum/artifact_trigger/radiation)
+
+/datum/component/artifact/cell/setup()
+ var/obj/item/stock_parts/cell/artifact/cell = holder
+ cell.corrupted = prob(10) //trolled
+ cell.maxcharge = rand(5000,80000) //2x of bluespace
+ cell.charge = cell.maxcharge / 2
+ cell.chargerate = rand(5000,round(cell.maxcharge * 0.4))
+ potency += cell.maxcharge / 900
+ potency += cell.chargerate / 4000
+
+/datum/component/artifact/cell/effect_activate()
+ var/obj/item/stock_parts/cell/artifact/cell = holder
+ cell.ratingdesc = TRUE
+
+/datum/component/artifact/cell/effect_deactivate()
+ var/obj/item/stock_parts/cell/artifact/cell = holder
+ cell.ratingdesc = FALSE
+
+/obj/item/stock_parts/cell/artifact/use(amount, force) //dont use power unless active
+ . = FALSE
+ if(assoc_comp.active)
+ return ..()
diff --git a/code/modules/artsci/artifact_items/artifact_gun.dm b/code/modules/artsci/artifact_items/artifact_gun.dm
new file mode 100644
index 000000000000..6427cee148be
--- /dev/null
+++ b/code/modules/artsci/artifact_items/artifact_gun.dm
@@ -0,0 +1,89 @@
+/obj/item/ammo_casing/magic/artifact
+ projectile_type = /obj/projectile/magic/artifact
+
+/obj/item/ammo_casing/magic/artifact/ready_proj(atom/target, mob/living/user, quiet, zone_override = "", atom/fired_from)
+ if(!loaded_projectile)
+ return
+ var/datum/component/artifact/gun/gun = fired_from.GetComponent(/datum/component/artifact/gun)
+ loaded_projectile.damage = gun.damage / pellets
+ loaded_projectile.icon_state = gun.projectile_icon
+ loaded_projectile.damage_type = gun.dam_type
+ loaded_projectile.ricochets_max = gun.ricochets_max
+ loaded_projectile.ricochet_chance = gun.ricochet_chance
+ loaded_projectile.ricochet_auto_aim_range = gun.ricochet_auto_aim_range
+ loaded_projectile.wound_bonus = gun.wound_bonus
+ loaded_projectile.sharpness = gun.sharpness
+ loaded_projectile.spread = gun.spread
+ return ..()
+
+/obj/projectile/magic/artifact
+ name = "incomprehensible energy"
+ antimagic_flags = null
+ ricochet_incidence_leeway = 0
+ ricochet_decay_chance = 0.9
+ hitsound_wall = SFX_RICOCHET
+ impact_effect_type = /obj/effect/temp_visual/impact_effect
+
+
+/obj/item/gun/magic/artifact
+ icon = 'icons/obj/artifacts.dmi'
+ icon_state = "narnar-item1"
+ resistance_flags = LAVA_PROOF | ACID_PROOF | INDESTRUCTIBLE
+ icon = 'icons/obj/artifacts.dmi'
+ inhand_icon_state = "plasmashiv"
+ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
+ ammo_type = /obj/item/ammo_casing/magic/artifact
+ school = SCHOOL_UNSET
+ max_charges = 8
+ pinless = TRUE
+ recharge_rate = 1
+ antimagic_flags = null
+ var/datum/component/artifact/assoc_comp = /datum/component/artifact/gun
+
+ARTIFACT_SETUP(/obj/item/gun/magic/artifact, SSobj)
+
+/obj/item/gun/magic/artifact/can_shoot()
+ return assoc_comp.active
+
+/obj/item/gun/magic/artifact/shoot_with_empty_chamber()
+ return
+
+/datum/component/artifact/gun
+ associated_object = /obj/item/gun/magic/artifact
+ artifact_size = ARTIFACT_SIZE_SMALL
+ type_name = "Ranged Weapon"
+ weight = ARTIFACT_VERYUNCOMMON //rare
+ xray_result = "COMPLEX"
+ valid_triggers = list(/datum/artifact_trigger/heat, /datum/artifact_trigger/shock, /datum/artifact_trigger/radiation)
+ var/damage
+ var/projectile_icon
+ var/dam_type
+ var/ricochets_max = 0
+ var/ricochet_chance = 0
+ var/ricochet_auto_aim_range = 0
+ var/wound_bonus = CANT_WOUND
+ var/sharpness = NONE
+ var/spread = 0
+
+/datum/component/artifact/gun/setup()
+ var/obj/item/gun/magic/artifact/our_wand = holder
+ var/obj/item/ammo_casing/casing = our_wand.chambered
+ //randomize our casing
+ casing.click_cooldown_override = rand(3,10)
+ if(prob(30))
+ casing.pellets = rand(1,3)
+ spread += 0.1
+
+ spread += prob(65) ? rand(0.0,0.2) : rand(0.3,1.0)
+ damage = rand(-5,25)
+ projectile_icon = pick("energy","scatterlaser", "toxin", "energy", "spell", "pulse1", "bluespace", "gauss","gaussweak","gaussstrong", "redtrac", "omnilaser", "heavylaser", "laser", "infernoshot", "cryoshot", "arcane_barrage")
+ dam_type = pick(BRUTE,BURN,TOX,STAMINA,BRAIN)
+ if(prob(30)) //bouncy
+ ricochets_max = rand(1,40)
+ ricochet_chance = rand(80,600) // will bounce off anything and everything, whether they like it or not
+ ricochet_auto_aim_range = rand(0,4)
+ if(prob(50))
+ wound_bonus = rand(CANT_WOUND,15)
+ if(prob(40))
+ sharpness = pick(SHARP_POINTY,SHARP_EDGED)
diff --git a/code/modules/artsci/artifact_items/artifact_melee.dm b/code/modules/artsci/artifact_items/artifact_melee.dm
new file mode 100644
index 000000000000..e254bf11221a
--- /dev/null
+++ b/code/modules/artsci/artifact_items/artifact_melee.dm
@@ -0,0 +1,100 @@
+#define SPECIAL_LAUNCH "launch"
+#define SPECIAL_IGNITE "ignite"
+#define SPECIAL_TELEPORT "teleport"
+
+/obj/item/melee/artifact
+ icon = 'icons/obj/artifacts.dmi'
+ icon_state = "narnar-1"
+ resistance_flags = LAVA_PROOF | ACID_PROOF | INDESTRUCTIBLE
+ icon = 'icons/obj/artifacts.dmi'
+ inhand_icon_state = "plasmashiv"
+ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
+ var/special_cooldown_time
+ var/special
+ var/datum/component/artifact/assoc_comp = /datum/component/artifact/melee
+ COOLDOWN_DECLARE(special_cooldown)
+
+ARTIFACT_SETUP(/obj/item/melee/artifact, SSobj)
+
+/obj/item/melee/artifact/afterattack(mob/living/victim, mob/user, proximity)
+ SIGNAL_HANDLER
+
+ if(!istype(victim) || !assoc_comp.active || !COOLDOWN_FINISHED(src,special_cooldown) || !special || !proximity)
+ return
+ . |= AFTERATTACK_PROCESSED_ITEM
+ switch(special)
+ if(SPECIAL_IGNITE)
+ victim.adjust_fire_stacks(5)
+ victim.ignite_mob(silent = TRUE)
+ if(victim.on_fire) //check to make sure they actually caught on fire, or if it was prevented cause they were wet.
+ victim.visible_message(span_warning("[victim] catches fire!"), ignored_mobs = victim)
+ to_chat(victim, span_userdanger("You feel a sudden wave of heat as you burst into flames!"))
+ if(SPECIAL_LAUNCH)
+ var/owner_turf = get_turf(user)
+ var/throwtarget = get_edge_target_turf(owner_turf, get_dir(owner_turf, get_step_away(victim, owner_turf)))
+ victim.safe_throw_at(throwtarget, rand(3,7), 1, force = MOVE_FORCE_VERY_STRONG)
+ if(SPECIAL_TELEPORT)
+ if(victim.move_resist < MOVE_FORCE_OVERPOWERING)
+ do_teleport(victim, get_turf(victim), 15, channel = TELEPORT_CHANNEL_BLUESPACE)
+ COOLDOWN_START(src,special_cooldown,special_cooldown_time)
+
+/datum/component/artifact/melee
+ associated_object = /obj/item/melee/artifact
+ artifact_size = ARTIFACT_SIZE_SMALL
+ type_name = "Melee Weapon"
+ weight = ARTIFACT_VERYUNCOMMON //rare
+ xray_result = "DENSE"
+ valid_triggers = list(/datum/artifact_trigger/silicon_touch,/datum/artifact_trigger/heat, /datum/artifact_trigger/shock, /datum/artifact_trigger/radiation)
+ var/active_force //force when active
+ var/active_reach
+ var/active_woundbonus = 0
+
+/datum/component/artifact/melee/setup() //RNG incarnate
+ var/obj/item/melee/artifact/weapon = holder
+ weapon.special_cooldown_time = rand(3,8) SECONDS
+ active_force = rand(-10,30)
+ weapon.demolition_mod = rand(-1.0, 2.0)
+ weapon.force = active_force / 3
+ weapon.throwforce = weapon.force
+ potency += abs(active_force)
+ if(prob(40))
+ weapon.sharpness = pick(SHARP_EDGED,SHARP_POINTY)
+ if(weapon.sharpness == SHARP_POINTY)
+ weapon.attack_verb_continuous = list("stabs", "shanks", "pokes")
+ weapon.attack_verb_simple = list("stab", "shank", "poke")
+ else
+ weapon.attack_verb_continuous = list("slashes", "slices", "cuts")
+ weapon.attack_verb_simple = list("slash", "slice", "cut")
+ weapon.hitsound = 'sound/weapons/bladeslice.ogg'
+ potency += 9
+ if(prob(30))
+ active_woundbonus = rand(3,20)
+ if(prob(30))
+ weapon.armour_penetration = rand(5,15)//this barely does anything inactive so its fine to have it always
+ if(prob(50))
+ weapon.damtype = pick(BRUTE, BURN, TOX, STAMINA)
+ if(prob(10))
+ active_reach = rand(1,3) // this CANT possibly backfire
+ potency += 20
+ if(prob(30))
+ potency += 15
+ weapon.special = pick(SPECIAL_LAUNCH, SPECIAL_IGNITE, SPECIAL_TELEPORT)
+
+/datum/component/artifact/melee/effect_activate()
+ var/obj/item/melee/artifact/weapon = holder
+ weapon.reach = active_reach
+ weapon.force = active_force
+ weapon.wound_bonus = active_woundbonus
+ weapon.throwforce = weapon.force
+
+/datum/component/artifact/melee/effect_deactivate()
+ var/obj/item/melee/artifact/weapon = holder
+ weapon.force = active_force / 3
+ weapon.throwforce = weapon.force
+ weapon.reach = 1
+ weapon.wound_bonus = 0
+
+#undef SPECIAL_LAUNCH
+#undef SPECIAL_IGNITE
+#undef SPECIAL_TELEPORT
diff --git a/code/modules/artsci/artifact_objects/artifact_bomb.dm b/code/modules/artsci/artifact_objects/artifact_bomb.dm
new file mode 100644
index 000000000000..262b8d41161e
--- /dev/null
+++ b/code/modules/artsci/artifact_objects/artifact_bomb.dm
@@ -0,0 +1,160 @@
+/datum/component/artifact/bomb
+ examine_hint = span_warning("It is covered in very conspicuous markings.")
+ valid_triggers = list(/datum/artifact_trigger/force, /datum/artifact_trigger/heat,/datum/artifact_trigger/shock,/datum/artifact_trigger/radiation)
+ deactivation_message = "sputters a bit, and falls silent once more."
+ xray_result = "COMPLEX"
+ var/dud = FALSE
+ var/dud_message = "sputters, failing to activate! Its a dud!"
+ var/initial_warning = "begins overloading, rattling violenty!"
+ var/explode_delay = 1 MINUTES // also delayed by finale_delay for fluff
+ var/explode_cooldown_time = 1 MINUTES
+ var/finale_delay = 6 SECONDS //delay before we actually deliver the payload for fluff
+ var/final_message = "reaches a catastrophic overload, cracks forming at its surface!"
+ var/sound/active_alarm = 'sound/effects/alert.ogg' // plays every alarm_cooldown_time when active
+ var/alarm_cooldown_time = 3 SECONDS
+ var/sound/final_sound = 'sound/misc/bloblarm.ogg'
+ COOLDOWN_DECLARE(activation_cooldown)
+ COOLDOWN_DECLARE(alarm_cooldown)
+ var/timer_id
+ var/do_alert = FALSE //do we send an announcement on activation
+
+/datum/component/artifact/bomb/setup()
+ if(prob(20))
+ dud = TRUE
+
+/datum/component/artifact/bomb/effect_activate()
+ if(!COOLDOWN_FINISHED(src,explode_cooldown_time))
+ holder.visible_message(span_warning("[holder] [deactivation_message]")) //rekt
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/component/artifact, Deactivate)), 1 SECONDS)
+ return
+ holder.visible_message(span_bolddanger("[holder] [initial_warning]"))
+ COOLDOWN_START(src,activation_cooldown,explode_cooldown_time)
+ timer_id = addtimer(CALLBACK(src, PROC_REF(finale)), explode_delay, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE)
+ if(do_alert && is_station_level(holder.z))
+ priority_announce("A highly unstable object of type [type_name] has been activated at [get_area(holder)]. It has been marked on GPS, The crew is advised to get rid of it IMMEDIATELY.", null, SSstation.announcer.get_rand_report_sound(), has_important_message = TRUE)
+ holder.AddComponent(/datum/component/gps, "Unstable Object")
+
+/datum/component/artifact/bomb/effect_deactivate()
+ deltimer(timer_id)
+
+/datum/component/artifact/bomb/effect_process()
+ . = ..()
+ if(active && COOLDOWN_FINISHED(src,alarm_cooldown) && (COOLDOWN_TIMELEFT(src,alarm_cooldown) <= finale_delay))
+ playsound(holder, active_alarm, 30, 1)
+ holder.Shake(duration = 1 SECONDS, shake_interval = 0.08 SECONDS)
+ COOLDOWN_START(src,alarm_cooldown, alarm_cooldown_time)
+
+/datum/component/artifact/bomb/proc/finale()
+ if(final_sound)
+ playsound(holder.loc, final_sound, 100, 1, -1)
+ if(finale_delay)
+ holder.visible_message(span_bolddanger("[holder] [final_message]"))
+ addtimer(CALLBACK(src, PROC_REF(payload)), finale_delay)
+ else
+ payload()
+
+/datum/component/artifact/bomb/Artifact_Destroyed(silent=FALSE)
+ . = ..()
+ if(active)
+ payload()
+ deltimer(timer_id)
+/datum/component/artifact/bomb/proc/payload()
+ . = TRUE
+ if(dud || !active)
+ holder.visible_message(span_notice("[holder] [dud_message]"))
+ Deactivate(silent=TRUE)
+ return FALSE
+
+/obj/structure/artifact/bomb
+ assoc_comp = /datum/component/artifact/bomb/explosive
+
+/datum/component/artifact/bomb/explosive
+ associated_object = /obj/structure/artifact/bomb
+ type_name = "Bomb (explosive)"
+ weight = ARTIFACT_RARE
+ var/devast
+ var/heavy
+ var/light
+
+/datum/component/artifact/bomb/explosive/New()
+ . = ..()
+ devast = rand(1,3)
+ heavy = rand(2,4)
+ light = rand(3,10)
+ potency = (light + heavy + devast) * 2
+
+/datum/component/artifact/bomb/explosive/payload()
+ if(!..())
+ return FALSE
+ explosion(holder, devast,heavy,light,light*1.5)
+ Artifact_Destroyed(silent=TRUE)
+
+/obj/structure/artifact/bomb/devastating
+ assoc_comp = /datum/component/artifact/bomb/explosive/devastating
+
+/datum/component/artifact/bomb/explosive/devastating
+ associated_object = /obj/structure/artifact/bomb/devastating
+ type_name = "Bomb (explosive, devastating)"
+ do_alert = TRUE
+ weight = ARTIFACT_VERYRARE
+ xray_result = "DENSE"
+ explode_delay = 2 MINUTES
+
+/datum/component/artifact/bomb/explosive/devastating/New()
+ ..()
+ devast = rand(3,7)
+ heavy = rand(7,12)
+ light = rand(10,25)
+ potency = (devast + heavy + light) * 2.25 // get real
+
+/* TODO
+/obj/structure/artifact/bomb/chemical
+ assoc_comp = /datum/component/artifact/bomb/chemical
+/datum/component/artifact/bomb/chemical
+ associated_object = /obj/structure/artifact/bomb/chemical
+ type_name = "Bomb (chemical)"
+ weight = ARTIFACT_RARE
+ explode_delay = 1 // so it dont complain
+ explode_cooldown_time = 5 MINUTES
+ finale_delay = 0
+ var/single_use = FALSE //true = destroy on payload
+ var/smoke = FALSE // if false deliver via foam instead
+/datum/component/artifact/bomb/chemical/setup()
+ . = ..()
+ single_use = prob(70)
+ smoke = prob(50)
+ initial_warning = "'s pores start releasing [smoke ? "a thick smoke!" : "foam!"]"
+*/
+
+/obj/structure/artifact/bomb/gas
+ assoc_comp = /datum/component/artifact/bomb/gas
+
+/datum/component/artifact/bomb/gas
+ associated_object = /obj/structure/artifact/bomb/gas
+ type_name = "Bomb (gas)"
+ weight = ARTIFACT_RARE
+ xray_result = "POROUS"
+ initial_warning = "begins rattling violenty!"
+ final_message = "reaches a critical pressure, cracks forming at its surface!"
+ var/datum/gas/payload_gas
+
+/datum/component/artifact/bomb/gas/setup()
+ . = ..()
+ payload_gas = pick(/datum/gas/plasma, /datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide, /datum/gas/tritium, /datum/gas/hydrogen)
+
+/datum/component/artifact/bomb/gas/payload()
+ if(!..())
+ Deactivate()
+ return FALSE
+ var/turf/open/O = get_turf(holder)
+ if(!isopenturf(O))
+ Deactivate()
+ return FALSE
+ var/datum/gas_mixture/merger = new
+ merger.assert_gas(payload_gas)
+ merger.assert_gas(/datum/gas/oxygen)
+ merger.gases[payload_gas][MOLES] = rand(150,2000)
+ merger.gases[/datum/gas/oxygen][MOLES] = 350
+ merger.temperature = rand(200,3000)
+ O.assume_air(merger)
+ qdel(holder)
diff --git a/code/modules/artsci/artifact_objects/artifact_bonk.dm b/code/modules/artsci/artifact_objects/artifact_bonk.dm
new file mode 100644
index 000000000000..1fe965bfa882
--- /dev/null
+++ b/code/modules/artsci/artifact_objects/artifact_bonk.dm
@@ -0,0 +1,37 @@
+/obj/structure/artifact/bonk
+ assoc_comp = /datum/component/artifact/bonk
+
+/datum/component/artifact/bonk
+ associated_object = /obj/structure/artifact/bonk
+ weight = ARTIFACT_UNCOMMON
+ type_name = "Slammer"
+ activation_message = "opens up!"
+ deactivation_message = "closes up."
+ valid_triggers = list(/datum/artifact_trigger/carbon_touch,/datum/artifact_trigger/silicon_touch)
+ ///force of the hit
+ var/hit_power = 1
+ COOLDOWN_DECLARE(bonk_cooldown)
+
+/datum/component/artifact/bonk/setup()
+ hit_power = rand(0,35)
+ potency += hit_power
+
+/datum/component/artifact/bonk/effect_touched(mob/living/user)
+ if(!COOLDOWN_FINISHED(src, bonk_cooldown))
+ return
+ if(iscarbon(user))
+ var/mob/living/carbon/carbon = user
+ if(!carbon.get_bodypart(BODY_ZONE_HEAD))
+ holder.say("My condolences to your missing head.") //they can speak uhh galactic common because alien tech idk
+ holder.visible_message(span_notice("[holder] shakes [user][p_s()] hands with an apparatus."))
+ playsound(get_turf(holder), 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1)
+ Deactivate()
+ return
+ else
+ carbon.apply_damage(hit_power, BRUTE, BODY_ZONE_HEAD, carbon.run_armor_check(BODY_ZONE_HEAD, MELEE))
+ holder.visible_message(span_danger("[holder] hits [carbon] over the head!"))
+ else
+ holder.visible_message(span_danger("[holder] slams [user]!"))
+ user.adjustBruteLoss(hit_power)
+ playsound(get_turf(holder), 'sound/misc/bonk.ogg', 80, FALSE)
+ COOLDOWN_START(src, bonk_cooldown, 1.5 SECONDS)
diff --git a/code/modules/artsci/artifact_objects/artifact_forcegen.dm b/code/modules/artsci/artifact_objects/artifact_forcegen.dm
new file mode 100644
index 000000000000..a8898bb0861a
--- /dev/null
+++ b/code/modules/artsci/artifact_objects/artifact_forcegen.dm
@@ -0,0 +1,63 @@
+/obj/structure/artifact_forcefield
+ name = "forcefield"
+ desc = "A glowing barrier, completely impenetratable to your means. Possibly made by some nearby object."
+ icon = 'icons/effects/effects.dmi'
+ layer = ABOVE_ALL_MOB_LAYER
+ plane = ABOVE_GAME_PLANE
+ anchored = TRUE
+ pass_flags_self = PASSGLASS
+ density = TRUE
+ mouse_opacity = MOUSE_OPACITY_OPAQUE
+ resistance_flags = INDESTRUCTIBLE
+ can_atmos_pass = ATMOS_PASS_DENSITY
+
+/obj/structure/artifact_forcefield/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0)
+ playsound(loc, 'sound/weapons/egloves.ogg', 80, TRUE)
+
+/obj/structure/artifact/forcegen
+ assoc_comp = /datum/component/artifact/forcegen
+
+/datum/component/artifact/forcegen
+ associated_object = /obj/structure/artifact/forcegen
+ weight = ARTIFACT_UNCOMMON
+ type_name = "Forcefield Generator"
+ activation_message = "springs to life and starts emitting a forcefield!"
+ deactivation_message = "shuts down, its forcefields shutting down with it."
+ valid_triggers = list(/datum/artifact_trigger/carbon_touch,/datum/artifact_trigger/silicon_touch,/datum/artifact_trigger/force)
+ var/cooldown_time //cooldown AFTER the shield lowers
+ var/shield_iconstate
+ var/list/projected_forcefields = list()
+ var/radius
+ var/shield_time
+ COOLDOWN_DECLARE(cooldown)
+
+/datum/component/artifact/forcegen/setup()
+ shield_iconstate = pick("shieldsparkles","empdisable","shield2","shield-old","shield-red","shield-green","shield-yellow")
+ activation_sound = pick('sound/mecha/mech_shield_drop.ogg')
+ deactivation_sound = pick('sound/mecha/mech_shield_raise.ogg','sound/magic/forcewall.ogg')
+ shield_time = rand(10,40) SECONDS
+ radius = rand(1,3)
+ cooldown_time = shield_time / 3
+ potency += radius * 3 + shield_time / 30
+
+/datum/component/artifact/forcegen/effect_activate()
+ if(!COOLDOWN_FINISHED(src,cooldown))
+ holder.visible_message(span_notice("[holder] wheezes, shutting down."))
+ Deactivate(silent=TRUE)
+ return
+ holder.anchored = TRUE
+ var/turf/our_turf = get_turf(holder)
+ for(var/turf/open/floor in range(radius,holder))
+ if(floor == our_turf)
+ continue
+ var/obj/field = new /obj/structure/artifact_forcefield(floor)
+ field.icon_state = shield_iconstate
+ projected_forcefields += field
+ addtimer(CALLBACK(src, TYPE_PROC_REF(/datum/component/artifact, Deactivate)), shield_time)
+ COOLDOWN_START(src,cooldown,shield_time + cooldown_time)
+
+/datum/component/artifact/forcegen/effect_deactivate()
+ holder.anchored = FALSE
+ for(var/obj/field in projected_forcefields)
+ projected_forcefields -= field
+ qdel(field)
diff --git a/code/modules/artsci/artifact_objects/artifact_heal.dm b/code/modules/artsci/artifact_objects/artifact_heal.dm
new file mode 100644
index 000000000000..98d112caf69e
--- /dev/null
+++ b/code/modules/artsci/artifact_objects/artifact_heal.dm
@@ -0,0 +1,34 @@
+/obj/structure/artifact/heal
+ assoc_comp = /datum/component/artifact/heal
+
+/datum/component/artifact/heal
+ associated_object = /obj/structure/artifact/heal
+ weight = ARTIFACT_VERYUNCOMMON
+ type_name = "Single Healer"
+ activation_message = "starts emitting a soothing aura!"
+ deactivation_message = "becomes silent."
+ valid_triggers = list(/datum/artifact_trigger/carbon_touch,/datum/artifact_trigger/silicon_touch)
+ ///list of damage types we heal, this is randomly removed from at setup
+ var/list/damage_types = list(BRUTE,BURN,TOX,OXY,BRAIN)
+ ///how much do we heal
+ var/heal_amount
+ COOLDOWN_DECLARE(heal_cooldown)
+
+/datum/component/artifact/heal/setup()
+ heal_amount = rand(1,15)
+ potency += heal_amount
+ var/type_amount = prob(75) ? 4 : rand(2,4) //75% to remove 4 types for 1 heal type or 25% for 2 or 4 types removed
+ while(type_amount)
+ type_amount--
+ damage_types -= pick(damage_types)
+ potency += 5 * (length(damage_types) - 1)
+
+/datum/component/artifact/heal/effect_touched(mob/living/user)
+ if(!COOLDOWN_FINISHED(src, heal_cooldown))
+ return
+ var/damage_length = length(damage_types)
+ for(var/dam_type in damage_types)
+ user.apply_damage_type( -(heal_amount / damage_length), dam_type)
+ to_chat(user, span_notice("You feel slightly refreshed!"))
+ new /obj/effect/temp_visual/heal(get_turf(user), COLOR_HEALING_CYAN)
+ COOLDOWN_START(src, heal_cooldown, 5 SECONDS)
diff --git a/code/modules/artsci/artifact_objects/artifact_injector.dm b/code/modules/artsci/artifact_objects/artifact_injector.dm
new file mode 100644
index 000000000000..be9742c302e9
--- /dev/null
+++ b/code/modules/artsci/artifact_objects/artifact_injector.dm
@@ -0,0 +1,49 @@
+/obj/structure/artifact/injector
+ assoc_comp = /datum/component/artifact/injector
+/datum/component/artifact/injector
+ associated_object = /obj/structure/artifact/injector
+ weight = ARTIFACT_UNCOMMON
+ type_name = "Injector"
+ activation_message = "opens up to reveal a large needle!"
+ deactivation_message = "pulls its needle inside, closing itself up."
+ xray_result = "SEGMENTED"
+ var/max_reagents // the total amount to dose the victim with
+ var/reagent_amount
+ var/list/reagent_datums = list()
+ var/cooldown_time = 10 SECONDS
+ COOLDOWN_DECLARE(activation_cooldown)
+
+/datum/component/artifact/injector/setup()
+ holder.create_reagents(200, NO_REACT | SEALED_CONTAINER)
+ reagent_amount = rand(10,25)
+ max_reagents = rand(1,2)
+ var/static/list/poisons = list()
+ if(!poisons.len) //mostly copied from reagents.dm but oh well
+ for(var/datum/reagent/reagent as anything in subtypesof(/datum/reagent/toxin))
+ if(initial(reagent.chemical_flags) & REAGENT_CAN_BE_SYNTHESIZED)
+ poisons += reagent
+ switch(artifact_origin.type_name)
+ if(ORIGIN_NARSIE)
+ for(var/i in 1 to max_reagents)
+ reagent_datums += pick(poisons) //cult likes killing people ok
+ if(ORIGIN_WIZARD)
+ max_reagents = rand(1,3)
+ reagent_amount = rand(1,50)
+ potency += 5
+ for(var/i in 1 to max_reagents)
+ reagent_datums += get_random_reagent_id() // funny
+ if(ORIGIN_SILICON)
+ var/list/silicon_reagents = list(/datum/reagent/uranium, /datum/reagent/silicon, /datum/reagent/fuel, /datum/reagent/cyborg_mutation_nanomachines, /datum/reagent/fuel/oil, /datum/reagent/toxin/leadacetate)
+ for(var/i in 1 to max_reagents)
+ reagent_datums += pick(silicon_reagents)
+ potency += reagent_amount + max_reagents
+
+/datum/component/artifact/injector/effect_touched(mob/living/user)
+ if(!ishuman(user) || !COOLDOWN_FINISHED(src,activation_cooldown))
+ holder.visible_message(span_smallnoticeital("[holder] does not react to [user]."))
+ return
+ for(var/reagent in reagent_datums)
+ holder.reagents.add_reagent(reagent, reagent_amount / reagent_datums.len)
+ holder.visible_message(span_danger("[holder] pricks [user] with its needle!"), span_userdanger("OW! You are pricked by [holder]!"))
+ holder.reagents.trans_to(user, holder.reagents.total_volume, transfered_by = holder, methods = INJECT)
+ COOLDOWN_START(src,activation_cooldown,cooldown_time)
diff --git a/code/modules/artsci/artifact_objects/artifact_lamp.dm b/code/modules/artsci/artifact_objects/artifact_lamp.dm
new file mode 100644
index 000000000000..958064e01d73
--- /dev/null
+++ b/code/modules/artsci/artifact_objects/artifact_lamp.dm
@@ -0,0 +1,36 @@
+/obj/structure/artifact/lamp
+ assoc_comp = /datum/component/artifact/lamp
+ light_system = MOVABLE_LIGHT
+ light_on = FALSE
+/datum/component/artifact/lamp
+ associated_object = /obj/structure/artifact/lamp
+ weight = ARTIFACT_COMMON
+ type_name = "Lamp"
+ activation_message = "starts shining!"
+ deactivation_message = "stops shining."
+
+/datum/component/artifact/lamp/setup()
+ var/power
+ var/color = pick(COLOR_RED, COLOR_BLUE, COLOR_YELLOW, COLOR_GREEN, COLOR_PURPLE, COLOR_ORANGE)
+ var/range
+ switch(rand(1,100))
+ if(1 to 75)
+ power = rand(2,5)
+ range = rand(2,5)
+ if(76 to 100)
+ range = rand(4,10)
+ power = rand(2,10) // the sun
+ if(artifact_origin.type_name == ORIGIN_NARSIE && prob(40))
+ color = COLOR_BLACK
+ holder.set_light_range_power_color(range,power,color)
+ potency += (range + power) * 2
+
+/datum/component/artifact/lamp/effect_touched(mob/user)
+ holder.set_light_on(!holder.light_on) //toggle
+ to_chat(user, span_hear("[holder] clicks."))
+
+/datum/component/artifact/lamp/effect_activate()
+ holder.set_light_on(TRUE)
+
+/datum/component/artifact/lamp/effect_deactivate()
+ holder.set_light_on(FALSE)
diff --git a/code/modules/artsci/artifact_objects/artifact_powergen.dm b/code/modules/artsci/artifact_objects/artifact_powergen.dm
new file mode 100644
index 000000000000..384aba873ade
--- /dev/null
+++ b/code/modules/artsci/artifact_objects/artifact_powergen.dm
@@ -0,0 +1,101 @@
+#define MAX_POSSIBLE_GEN 600 KW
+#define SIDEEFFECT_THRESHOLD 100 KW
+#define SHITFUCK_THRESHOLD 400 KW
+
+/obj/machinery/power/generator_artifact
+ icon = 'icons/obj/artifacts.dmi'
+ icon_state = "narnar-1"
+ resistance_flags = LAVA_PROOF | ACID_PROOF | INDESTRUCTIBLE
+ use_power = NO_POWER_USE
+ circuit = null
+ density = TRUE
+ anchored = FALSE
+ var/datum/component/artifact/assoc_comp = /datum/component/artifact/generator
+
+ARTIFACT_SETUP(/obj/machinery/power/generator_artifact, SSmachines)
+
+/datum/component/artifact/generator
+ associated_object = /obj/machinery/power/generator_artifact
+ type_name = "Power Generator"
+ weight = ARTIFACT_RARE
+ valid_triggers = list(/datum/artifact_trigger/heat, /datum/artifact_trigger/shock, /datum/artifact_trigger/radiation)
+ valid_origins = list(ORIGIN_WIZARD,ORIGIN_SILICON) //narnar doesnt need power
+ activation_message = "begins emitting a faint, droning hum."
+ deactivation_message = "shortcircuits!"
+ xray_result = "COMPLEX"
+ COOLDOWN_DECLARE(sideeffect_cooldown)
+
+ var/power_gen = 0
+ ///does the power output fluctuate
+ var/unstable_generation = FALSE
+
+/datum/component/artifact/generator/setup()
+ if(prob(65))
+ power_gen = rand(1 KW, MAX_POSSIBLE_GEN / 2)
+ else
+ power_gen = rand(1 KW, MAX_POSSIBLE_GEN)
+ unstable_generation = prob(40)
+ potency = power_gen / (6 KW) // 100 potency at 600kw generation
+
+/datum/component/artifact/generator/effect_touched(mob/living/user)
+ var/obj/machinery/power/generator_artifact/powerholder = holder
+ if(!powerholder.anchored && locate(/obj/structure/cable) in get_turf(powerholder))
+ powerholder.visible_message(span_warning("[holder] seems to snap to the cable!"))
+ playsound(get_turf(powerholder), 'sound/items/deconstruct.ogg', 50, TRUE)
+ powerholder.anchored = TRUE
+ powerholder.connect_to_network()
+ return
+ holder.Beam(user, icon_state="lightning[rand(1,12)]", time = 0.5 SECONDS)
+ playsound(get_turf(powerholder), 'sound/magic/lightningshock.ogg', 100, TRUE, extrarange = 5)
+ var/damage = user.electrocute_act(power_gen / 2 KW, powerholder, flags = SHOCK_NOSTUN)
+ to_chat(user, span_userdanger("You are hit by a burst of electricity from [holder]!"))
+ if(damage > 80)
+ var/turf/owner_turf = get_turf(holder)
+ var/throwtarget = get_edge_target_turf(get_turf(user), get_dir(owner_turf, get_step_away(user, owner_turf)))
+ user.safe_throw_at(throwtarget, power_gen / 38 KW, 1, force = MOVE_FORCE_EXTREMELY_STRONG)
+ if(damage > 400 && prob(50))
+ user.dust(just_ash = TRUE, drop_items = TRUE)
+ Deactivate() //shortcircuit
+
+ if(prob(20)) //try to get yourself shocked with insuls many times to shortcircuit it (in retrospect this sucks)
+ Deactivate()
+
+/datum/component/artifact/generator/effect_process() //todo add more
+ if(!holder.anchored)
+ return
+ var/obj/machinery/power/generator_artifact/powerholder = holder
+ powerholder.add_avail(power_gen * (unstable_generation ? rand(0.1, 1) : 1))
+ if(power_gen < SIDEEFFECT_THRESHOLD || !COOLDOWN_FINISHED(src,sideeffect_cooldown)) //sorry boss no can do
+ return
+ COOLDOWN_START(src,sideeffect_cooldown,rand(4,8) SECONDS)
+ //minor to medium side effects
+ if(power_gen >= (SHITFUCK_THRESHOLD / 3))
+ powerholder.visible_message(span_danger("\The [holder] lets out a shower of thunder!"), span_hear("You hear a loud electrical crack!"))
+ playsound(get_turf(powerholder), 'sound/magic/lightningshock.ogg', 100, TRUE, extrarange = 5)
+ tesla_zap(powerholder, rand(2,3), power_gen / 3500)
+
+ //SHIT IS FUCK
+
+ if(power_gen < SHITFUCK_THRESHOLD)
+ return
+
+ if(prob(50))
+ explosion(powerholder, flame_range = rand(1,2), adminlog = FALSE) //doesnt log to not spam
+ else
+ var/datum/gas_mixture/merger = new
+ merger.assert_gas(/datum/gas/carbon_dioxide)
+ merger.gases[/datum/gas/carbon_dioxide][MOLES] = rand(10,120)
+ merger.temperature = rand(200,1000)
+ var/turf/holder_turf = get_turf(holder)
+ holder_turf.assume_air(merger)
+
+
+/datum/component/artifact/generator/effect_deactivate()
+ var/obj/machinery/power/generator_artifact/powerholder = holder
+ powerholder.disconnect_from_network()
+ powerholder.anchored = FALSE
+ playsound(get_turf(powerholder), 'sound/items/deconstruct.ogg', 50, TRUE)
+
+#undef SHITFUCK_THRESHOLD
+#undef SIDEEFFECT_THRESHOLD
+#undef MAX_POSSIBLE_GEN
diff --git a/code/modules/artsci/artifact_objects/artifact_repulsor.dm b/code/modules/artsci/artifact_objects/artifact_repulsor.dm
new file mode 100644
index 000000000000..1933ac07b09b
--- /dev/null
+++ b/code/modules/artsci/artifact_objects/artifact_repulsor.dm
@@ -0,0 +1,54 @@
+/obj/structure/artifact/repulsor
+ assoc_comp = /datum/component/artifact/repulsor
+
+/datum/component/artifact/repulsor
+ associated_object = /obj/structure/artifact/repulsor
+ weight = ARTIFACT_UNCOMMON
+ type_name = "Repulsor/Impulsor"
+ activation_message = "opens up, a weird aura starts emitting from it!"
+ deactivation_message = "closes up."
+ xray_result = "SEGMENTED"
+ var/attract = FALSE //if FALSE, repulse, otherwise, attract
+ var/strength
+ var/range
+ var/cooldown_time
+ COOLDOWN_DECLARE(cooldown)
+
+/datum/component/artifact/repulsor/setup()
+ attract = prob(40)
+ range = rand(1,3)
+ cooldown_time = rand(10,40) SECONDS
+ strength = rand(MOVE_FORCE_DEFAULT,MOVE_FORCE_OVERPOWERING)
+ potency += cooldown_time / 4 + strength / 3000
+
+/datum/component/artifact/repulsor/effect_touched(mob/user)
+ pulse()
+
+/datum/component/artifact/repulsor/RegisterWithParent()
+ . = ..()
+ RegisterSignal(parent, COMSIG_ATOM_HITBY, PROC_REF(pulse))
+
+/datum/component/artifact/repulsor/UnregisterFromParent()
+ . = ..()
+ UnregisterSignal(parent, COMSIG_ATOM_HITBY)
+
+/datum/component/artifact/repulsor/proc/pulse(datum/source,atom/movable/thrown, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
+ SIGNAL_HANDLER
+ if(!active || !COOLDOWN_FINISHED(src,cooldown))
+ return
+ holder.visible_message(span_warning("[holder] emits a pulse of energy, throwing things [attract ? "towards it!" : "away from it!"]"))
+ var/owner_turf = get_turf(holder)
+ var/real_cooldown_time = cooldown_time
+ if(isnull(thrown))
+ for(var/atom/movable/throwee in oview(range,holder))
+ if(throwee.anchored)
+ continue
+ if(attract)
+ throwee.safe_throw_at(holder, strength / 3000, 1, force = strength)
+ else
+ var/throwtarget = get_edge_target_turf(get_turf(throwee), get_dir(owner_turf, get_step_away(throwee, owner_turf)))
+ throwee.safe_throw_at(throwtarget, strength / 3000, 1, force = strength)
+ else if(throwingdatum?.thrower)
+ real_cooldown_time = real_cooldown_time / 4
+ thrown.safe_throw_at(throwingdatum.thrower, get_dist(holder, throwingdatum.thrower), 1, force = strength)
+ COOLDOWN_START(src,cooldown,cooldown_time)
diff --git a/code/modules/artsci/artifact_objects/artifact_vomit.dm b/code/modules/artsci/artifact_objects/artifact_vomit.dm
new file mode 100644
index 000000000000..6d4d726580fa
--- /dev/null
+++ b/code/modules/artsci/artifact_objects/artifact_vomit.dm
@@ -0,0 +1,38 @@
+/obj/structure/artifact/vomit
+ assoc_comp = /datum/component/artifact/vomit
+/datum/component/artifact/vomit
+ associated_object = /obj/structure/artifact/vomit
+ weight = ARTIFACT_UNCOMMON
+ type_name = "Vomiting Inducer"
+ activation_message = "starts emitting disgusting imagery!"
+ deactivation_message = "falls silent, its aura dissipating!"
+ valid_origins = list(ORIGIN_NARSIE,ORIGIN_WIZARD) //silicons dont like organic stuff or something
+ var/range = 0
+ var/spew_range = 1
+ var/spew_organs = FALSE
+ var/bloody_vomit = FALSE
+ COOLDOWN_DECLARE(cooldown)
+
+/datum/component/artifact/vomit/setup()
+ switch(rand(1,100))
+ if(1 to 84)
+ range = rand(2,3)
+ if(85 to 100) //15%
+ range = rand(2,7)
+ if(prob(12))
+ spew_organs = TRUE //trolling
+ potency += 20
+ if(prob(40))
+ spew_range = rand(1,5)
+ potency += spew_range
+ bloody_vomit = prob(50)
+ potency += (range) * 4
+
+/datum/component/artifact/vomit/on_examine(atom/source, mob/user, list/examine_list)
+ . = ..()
+ var/mob/living/carbon/carbon = user
+ if(active && istype(carbon) && carbon.stat < UNCONSCIOUS)
+ examine_list += span_warning("It has an [spew_organs ? "extremely" : ""] disgusting aura! [prob(20) ? "..is that a felinid?" : ""]")
+ carbon.vomit(blood = bloody_vomit, stun = (spew_organs ? TRUE : prob(25)), distance = spew_range)
+ if(spew_organs && prob(40))
+ carbon.spew_organ()
diff --git a/code/modules/artsci/artifact_origins.dm b/code/modules/artsci/artifact_origins.dm
new file mode 100644
index 000000000000..6005991f87f4
--- /dev/null
+++ b/code/modules/artsci/artifact_origins.dm
@@ -0,0 +1,77 @@
+/datum/artifact_origin
+ var/type_name = "coder moment"
+ var/name = "unknown"
+ var/activation_sounds = list()
+ var/adjectives = list()
+ var/nouns_small = list()
+ var/nouns_large = list()
+ var/touch_descriptors = list()
+ var/destroy_message = ""
+ var/deactivation_sounds = list()
+ var/max_icons = 1 // amount of sprites we have for this origin
+ var/max_item_icons = 1 // amount of sprites we have for this origins items
+ var/overlay_red_minimum = 225
+ var/overlay_red_maximum = 255
+ var/overlay_green_minimum = 225
+ var/overlay_green_maximum = 255
+ var/overlay_blue_minimum = 225
+ var/overlay_blue_maximum = 255
+ var/overlay_alpha_minimum = 225
+ var/overlay_alpha_maximum = 255
+
+/datum/artifact_origin/proc/generate_name()
+ return FALSE
+
+/datum/artifact_origin/wizard
+ type_name = ORIGIN_WIZARD
+ name = "Wizard"
+ activation_sounds = list('sound/effects/stealthoff.ogg')
+ adjectives = list("imposing","regal","majestic","beautiful","shiny")
+ nouns_large = list("jewel","crystal","sculpture","statue","ornament")
+ nouns_small = list("staff","pearl","rod","cane","wand","trophy")
+ touch_descriptors = list("It feels warm.", "Its pleasant to touch.", "It feels smooth.")
+ destroy_message = "shatters, and disintegrates!"
+ overlay_red_minimum = 40
+ overlay_red_maximum = 130
+ overlay_green_minimum = 130
+ overlay_green_maximum = 255
+ overlay_blue_minimum = 130
+ overlay_blue_maximum = 255
+ overlay_alpha_minimum = 130
+ var/list/mats = list("stone", "pearl", "golden", "ruby", "sapphire", "opal")
+ var/list/object = list("crown","trophy","staff","boon","token","amulet")
+ var/list/aspect = list("Yendor","wonder","eminence","grace","plenty","mystery")
+
+/datum/artifact_origin/wizard/generate_name()
+ return "[pick(mats)] [pick(object)] of [pick(aspect)]"
+
+/datum/artifact_origin/narsie
+ type_name = ORIGIN_NARSIE
+ name = "Eldritch"
+ activation_sounds = list('sound/effects/curse3.ogg','sound/effects/curse1.ogg')
+ adjectives = list("imposing","sharp-edged","terrifying","jagged","dark")
+ nouns_large = list("obelisk","altar","sculpture","statue","ornament")
+ nouns_small = list("staff","pearl","rod","cane","wand","trophy")
+ touch_descriptors = list("It feels cold.", "Its rough to the touch.", "You prick yourself on its rough surface!")
+ destroy_message = "warps on itself, vanishing from sight!"
+ overlay_red_minimum = 40
+ overlay_red_maximum = 255
+ overlay_green_minimum = 40
+ overlay_green_maximum = 255
+ overlay_blue_minimum = 40
+ overlay_blue_maximum = 255
+
+/datum/artifact_origin/silicon
+ type_name = ORIGIN_SILICON
+ name = "Ancient"
+ activation_sounds = list('sound/items/modsuit/loader_charge.ogg')
+ adjectives = list("cold","smooth","humming","droning")
+ nouns_large = list("monolith","slab","obelisk","pylon")
+ nouns_small = list("implement","device", "apparatus","mechanism")
+ touch_descriptors = list("It feels cold.","Touching it makes you feel uneasy..","It feels smooth.")
+ destroy_message = "sputters violently, falling apart!"
+ max_icons = 3
+ max_item_icons = 3
+
+/datum/artifact_origin/silicon/generate_name()
+ return "Unit-[pick(GLOB.phonetic_alphabet)] [pick(GLOB.phonetic_alphabet)] [rand(0,9000)]"
diff --git a/code/modules/artsci/artifact_triggers.dm b/code/modules/artsci/artifact_triggers.dm
new file mode 100644
index 000000000000..54c113920bbe
--- /dev/null
+++ b/code/modules/artsci/artifact_triggers.dm
@@ -0,0 +1,71 @@
+/* WOE
+ Here are Stimuli for artifacts. They decide what way you need to activate them.
+ What applies what stimulus is actually handled by the artifact itself, these datums serve to give them a name, and all those cool variables
+ So far, artifacts should need higher tier parts for machines to be able to dish out higher stimuli.
+ Stimulus base_amount needed is multiplied by the artifact type, so more potent artifacts need higher stimuli.
+*/
+/datum/artifact_trigger
+ var/name = "Call coderbus!"
+ ///stimulus like STIMULUS_CARBON_TOUCH
+ var/needed_stimulus
+ var/check_amount = TRUE
+ ///base stimulus severity for math magic... base_amount + (max_amount - base_amount) * percentage...
+ var/base_amount = 0
+ var/max_amount = 0
+ ///stimulus severity needed to activate, changed after setup()..
+ var/amount = 0
+ ///stimulus severity range, needs to be between amount and range for activation, done on setup()
+ var/range = 0
+ ///Probability for a hint to be shown when the stimulus is hint_range close to the needed stimuli base_amount.
+ var/hint_range = 0
+ var/hint_prob = 35
+
+/datum/artifact_trigger/carbon_touch
+ name = "Carbon Touch"
+ needed_stimulus = STIMULUS_CARBON_TOUCH
+ check_amount = FALSE
+
+/datum/artifact_trigger/silicon_touch
+ name = "Silicon Touch"
+ needed_stimulus = STIMULUS_SILICON_TOUCH
+ check_amount = FALSE
+
+/datum/artifact_trigger/force
+ name = "Physical Force"
+ needed_stimulus = STIMULUS_FORCE
+ hint_range = 10
+ hint_prob = 75
+ max_amount = 35
+
+/datum/artifact_trigger/force/New()
+ base_amount = rand(2,15)
+
+/datum/artifact_trigger/heat
+ name = "Heat"
+ needed_stimulus = STIMULUS_HEAT
+ hint_range = 20
+ max_amount = 15000
+
+/datum/artifact_trigger/heat/New()
+ base_amount = rand(320,950)
+
+/datum/artifact_trigger/shock
+ name = "Electricity"
+ needed_stimulus = STIMULUS_SHOCK
+ max_amount = 10000
+ hint_range = 500
+
+/datum/artifact_trigger/shock/New()
+ base_amount = rand(400,1200)
+
+/datum/artifact_trigger/radiation
+ name = "Radiation"
+ needed_stimulus = STIMULUS_RADIATION
+ max_amount = 10
+ hint_range = 2
+ base_amount = 1
+
+/datum/artifact_trigger/data
+ name = "Data"
+ needed_stimulus = STIMULUS_DATA
+ check_amount = FALSE
diff --git a/code/modules/artsci/testing_machines/analysis_form.dm b/code/modules/artsci/testing_machines/analysis_form.dm
new file mode 100644
index 000000000000..e7aa970b5492
--- /dev/null
+++ b/code/modules/artsci/testing_machines/analysis_form.dm
@@ -0,0 +1,222 @@
+/datum/export/analyzed_artifact
+ cost = -CARGO_CRATE_VALUE
+ k_elasticity = 0
+ unit_name = "artifact"
+ allow_negative_cost = TRUE
+ export_types = list(/obj)
+
+/datum/export/analyzed_artifact/applies_to(obj/object, apply_elastic = TRUE)
+ if(object.GetComponent(/datum/component/artifact))
+ return TRUE
+ return ..()
+
+/datum/export/analyzed_artifact/get_cost(obj/object)
+ var/datum/component/artifact/art = object.GetComponent(/datum/component/artifact)
+ if(!art || !art.analysis)
+ return -CARGO_CRATE_VALUE
+ return art.analysis.get_export_value(art)
+
+/obj/item/sticker/analysis_form
+ name = "analysis form"
+ desc = "An analysis form for artifacts, has adhesive on the back."
+ gender = NEUTER
+ icon = 'icons/obj/service/bureaucracy.dmi'
+ icon_state = "analysisform"
+ inhand_icon_state = "paper"
+ throwforce = 0
+ throw_range = 1
+ throw_speed = 1
+ max_integrity = 50
+ drop_sound = 'sound/items/handling/paper_drop.ogg'
+ pickup_sound = 'sound/items/handling/paper_pickup.ogg'
+ contraband = STICKER_NOSPAWN
+ var/chosen_origin = ""
+ var/list/chosentriggers = list()
+ var/chosentype = ""
+
+/obj/item/sticker/analysis_form/attackby(obj/item/item, mob/living/user, params)
+ if(istype(item, /obj/item/pen))
+ ui_interact(user)
+ else
+ return ..()
+
+/obj/item/sticker/analysis_form/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ArtifactForm", name)
+ ui.open()
+
+/obj/item/sticker/analysis_form/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ if(!istype(usr.get_active_held_item(), /obj/item/pen))
+ to_chat(usr, span_notice("You need a pen to write on [src]!"))
+ return
+ switch(action)
+ if("origin")
+ chosen_origin = params["origin"]
+ if("type")
+ chosentype = params["type"]
+ if("trigger")
+ var/trig = params["trigger"]
+ if(trig in chosentriggers)
+ chosentriggers -= trig
+ else
+ chosentriggers += trig
+ if(attached)
+ analyze_attached()
+
+/obj/item/sticker/analysis_form/ui_static_data(mob/user)
+ . = ..()
+ .["allorigins"] = SSartifacts.artifact_origin_name_to_typename
+ .["alltypes"] = SSartifacts.artifact_type_names
+ .["alltriggers"] = SSartifacts.artifact_trigger_name_to_type
+ return
+
+/obj/item/sticker/analysis_form/ui_data(mob/user)
+ . = ..()
+ .["chosenorigin"] = chosen_origin
+ .["chosentype"] = chosentype
+ .["chosentriggers"] = chosentriggers
+ return .
+
+/obj/item/sticker/analysis_form/can_interact(mob/user)
+ if(attached && user.Adjacent(attached))
+ return TRUE
+ return ..()
+
+/obj/item/sticker/analysis_form/register_signals(mob/living/user)
+ . = ..()
+ RegisterSignal(attached, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
+
+/obj/item/sticker/analysis_form/unregister_signals(datum/source)
+ . = ..()
+ UnregisterSignal(attached, list(COMSIG_PARENT_EXAMINE))
+
+/obj/item/sticker/analysis_form/examine(mob/user)
+ . = ..()
+ if(!in_range(user, (attached ? attached : src)) && !isobserver(user))
+ return
+ ui_interact(user)
+
+/obj/item/sticker/analysis_form/proc/on_examine(atom/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+ examine_list += span_notice("It has an artifact analysis form attached to it...")
+ ui_interact(user)
+
+/obj/item/sticker/analysis_form/examine(mob/user)
+ . = ..()
+ if(!in_range(user, (attached ? attached : src)) && !isobserver(user))
+ return
+ ui_interact(user)
+
+/obj/item/sticker/analysis_form/ui_status(mob/user,/datum/ui_state/ui_state)
+ if(!in_range(user, (attached ? attached : src)) && !isobserver(user))
+ return UI_CLOSE
+ if(user.incapacitated(IGNORE_RESTRAINTS|IGNORE_GRAB) || (isobserver(user) && !isAdminGhostAI(user)))
+ return UI_UPDATE
+ if(user.is_blind())
+ to_chat(user, span_warning("You are blind!"))
+ return UI_CLOSE
+ if(!user.can_read(src))
+ return UI_CLOSE
+ if(attached && in_range(user, attached))
+ return UI_INTERACTIVE
+ return ..()
+//analysis
+
+/obj/item/sticker/analysis_form/stick(atom/target, mob/living/user, px,py)
+ ..()
+ analyze_attached()
+
+/obj/item/sticker/analysis_form/peel(atom/source)
+ SIGNAL_HANDLER
+ INVOKE_ASYNC(src, PROC_REF(deanalyze_attached))
+ ..()
+
+/obj/item/sticker/analysis_form/proc/analyze_attached()
+ var/datum/component/artifact/to_analyze = attached.GetComponent(/datum/component/artifact)
+ if(!to_analyze)
+ return
+ if(chosen_origin)
+ to_analyze.holder.name = to_analyze.names[chosen_origin]
+ if(chosentype)
+ to_analyze.holder.name += " ([chosentype])"
+
+/obj/item/sticker/analysis_form/proc/deanalyze_attached()
+ var/datum/component/artifact/to_analyze = attached.GetComponent(/datum/component/artifact)
+ if(!to_analyze)
+ return
+ to_analyze.holder.name = to_analyze.fake_name
+
+/obj/item/sticker/analysis_form/proc/get_export_value(datum/component/artifact/art)
+ var/correct = 0
+ var/total_guesses = 0
+
+ if(art.artifact_origin.type_name == chosen_origin)
+ correct += 1
+ if(chosen_origin)
+ total_guesses += 1
+ if(chosentype)
+ total_guesses += 1
+ if(art.type_name == chosentype)
+ correct += 1
+ for(var/name in chosentriggers)
+ total_guesses += 1
+ if(locate(SSartifacts.artifact_trigger_name_to_type[name]) in art.triggers)
+ correct += 1
+
+ var/incorrect = total_guesses - correct
+ return round((CARGO_CRATE_VALUE/4) * art.potency * (max((ARTIFACT_COMMON - art.weight) * 0.01, 0.01) * max(correct - incorrect, 0.01)))
+
+/obj/item/analysis_bin
+ name = "analysis bin"
+ desc = "A bin made out of material to resist adhesion, for artifact analysis forms."
+ icon = 'icons/obj/service/bureaucracy.dmi'
+ icon_state = "analysisbin1"
+ base_icon_state = "analysisbin"
+ inhand_icon_state = "sheet-metal"
+ lefthand_file = 'icons/mob/inhands/items/sheets_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/items/sheets_righthand.dmi'
+ w_class = WEIGHT_CLASS_NORMAL
+ var/forms = 15
+ var/form_type = /obj/item/sticker/analysis_form
+
+/obj/item/analysis_bin/Initialize(mapload)
+ . = ..()
+ interaction_flags_item &= ~INTERACT_ITEM_ATTACK_HAND_PICKUP
+ AddElement(/datum/element/drag_pickup)
+
+/obj/item/analysis_bin/update_icon_state()
+ icon_state = "[base_icon_state][forms > 0]"
+ return ..()
+
+/obj/item/analysis_bin/attack_hand(mob/user, list/modifiers)
+ if(isliving(user))
+ var/mob/living/living_mob = user
+ if(!(living_mob.mobility_flags & MOBILITY_PICKUP))
+ return
+ if(forms)
+ forms--
+ var/obj/item/form = new form_type
+ form.add_fingerprint(user)
+ form.forceMove(user.loc)
+ user.put_in_hands(form)
+ balloon_alert(user, "took form")
+ update_appearance()
+ else
+ balloon_alert(user, "empty!")
+ add_fingerprint(user)
+ return ..()
+
+/obj/item/analysis_bin/attackby(obj/item/item, mob/user, params)
+ if(istype(item, form_type))
+ if(!user.transferItemToLoc(item, src))
+ return
+ qdel(item)
+ balloon_alert(user, "form returned")
+ forms++
+ update_appearance()
+ else
+ return ..()
diff --git a/code/modules/artsci/testing_machines/heater.dm b/code/modules/artsci/testing_machines/heater.dm
new file mode 100644
index 000000000000..381439200feb
--- /dev/null
+++ b/code/modules/artsci/testing_machines/heater.dm
@@ -0,0 +1,111 @@
+/obj/machinery/atmospherics/components/unary/artifact_heatingpad //this is literally a fancy thermomachine
+ icon = 'icons/obj/machines/atmospherics/heatingpad.dmi'
+ icon_state = "pad_norm"
+
+ name = "Heating Pad"
+ desc = "Through some science bullcrap, this machine heats artifacts and people on top of it, without heating air, to the temperature of the gas contained. It will, in addition, heat its contents to 20C."
+ density = FALSE
+ max_integrity = 300
+ armor_type = /datum/armor/unary_thermomachine
+ layer = LOW_OBJ_LAYER
+ circuit = /obj/item/circuitboard/machine/artifactheater
+ hide = TRUE
+ move_resist = MOVE_RESIST_DEFAULT
+ vent_movement = NONE
+ pipe_flags = PIPING_ONE_PER_TURF
+ set_dir_on_move = FALSE
+
+ var/heat_capacity = 0
+
+/obj/machinery/atmospherics/components/unary/artifact_heatingpad/Initialize(mapload)
+ . = ..()
+ RefreshParts()
+ update_appearance()
+
+/obj/machinery/atmospherics/components/unary/artifact_heatingpad/update_icon_state()
+ var/datum/gas_mixture/port = airs[1]
+ if(!port?.total_moles())
+ icon_state = "pad_norm"
+ return ..()
+ var/state_to_use = ""
+ switch(port.temperature)
+ if(BODYTEMP_HEAT_WARNING_1 to INFINITY)
+ state_to_use = "pad_hot"
+ if(-INFINITY to BODYTEMP_COLD_WARNING_1)
+ state_to_use = "pad_cold"
+ else
+ state_to_use = "pad_norm"
+
+ if(panel_open)
+ icon_state = "pad_open"
+ return ..()
+ icon_state = state_to_use
+ return ..()
+
+/obj/machinery/atmospherics/components/unary/artifact_heatingpad/update_overlays()
+ . = ..()
+ if(!initial(icon))
+ return
+ var/mutable_appearance/pipe = new(initial(icon))
+ . += get_pipe_image(pipe, "pipe", dir, COLOR_LIME, piping_layer)
+
+/obj/machinery/atmospherics/components/unary/artifact_heatingpad/RefreshParts()
+ . = ..()
+ var/calculated_bin_rating = 0
+ for(var/datum/stock_part/matter_bin/bin in component_parts)
+ calculated_bin_rating += bin.tier
+ heat_capacity = 5000 * ((calculated_bin_rating - 1) ** 2) //pointless but uhh yeah
+
+/obj/machinery/atmospherics/components/unary/artifact_heatingpad/process_atmos()
+ if(panel_open)
+ return
+ var/turf/turf = get_turf(src)
+ var/datum/gas_mixture/port = airs[1]
+ if(!is_operational || !turf)
+ return
+ if(!port.total_moles())
+ return
+
+ var/port_capacity = port.heat_capacity()
+ var/delta = T20C - port.temperature //i dont think objs have temperature so
+ var/heat_amount = CALCULATE_CONDUCTION_ENERGY(delta, port_capacity, heat_capacity)
+ port.temperature = max(((port.temperature * port_capacity) + heat_amount) / port_capacity, TCMB)
+
+ for(var/atom/movable/content in turf.contents)
+ if(isliving(content)) // this so so will backfire but they can just walk off
+ var/mob/living/victim = content
+ if(victim.bodytemperature < port.temperature)
+ victim.adjust_bodytemperature(port.temperature * TEMPERATURE_DAMAGE_COEFFICIENT)
+ continue
+ else if(content in SSartifacts.artifacts) //this is an artifact, probably!
+ var/datum/component/artifact/pulled_artifact = SSartifacts.artifacts[content]
+ if(!istype(pulled_artifact))
+ return
+ pulled_artifact.Stimulate(STIMULUS_HEAT, port.temperature) //if its in the artifacts list it should have the component and if it doesnt shit is fuck
+ update_appearance()
+
+/obj/machinery/atmospherics/components/unary/artifact_heatingpad/screwdriver_act(mob/living/user, obj/item/tool)
+ if(default_deconstruction_screwdriver(user, "pad_open", "pad_norm", tool))
+ change_pipe_connection(panel_open)
+ return TOOL_ACT_TOOLTYPE_SUCCESS
+
+/obj/machinery/atmospherics/components/unary/artifact_heatingpad/wrench_act(mob/living/user, obj/item/tool)
+ return default_change_direction_wrench(user, tool)
+
+/obj/machinery/atmospherics/components/unary/artifact_heatingpad/crowbar_act(mob/living/user, obj/item/tool)
+ return default_deconstruction_crowbar(tool)
+
+/obj/machinery/atmospherics/components/unary/artifact_heatingpad/multitool_act(mob/living/user, obj/item/multitool/tool)
+ . = TOOL_ACT_TOOLTYPE_SUCCESS
+ if(!panel_open)
+ balloon_alert(user, "open panel!")
+ return
+ piping_layer = (piping_layer >= PIPING_LAYER_MAX) ? PIPING_LAYER_MIN : (piping_layer + 1)
+ update_appearance()
+
+/obj/machinery/atmospherics/components/unary/artifact_heatingpad/default_change_direction_wrench(mob/user, obj/item/item)
+ if(!..())
+ return FALSE
+ set_init_directions()
+ update_appearance()
+ return TRUE
diff --git a/code/modules/artsci/testing_machines/xray.dm b/code/modules/artsci/testing_machines/xray.dm
new file mode 100644
index 000000000000..fedd23180d5e
--- /dev/null
+++ b/code/modules/artsci/testing_machines/xray.dm
@@ -0,0 +1,155 @@
+/obj/machinery/artifact_xray
+ name = "artifact x-ray machine"
+ desc = "An x-ray machine, used to scan artifacts."
+ icon = 'icons/obj/machines/artifact_machines.dmi'
+ icon_state = "xray-0"
+ base_icon_state = "xray"
+ density = TRUE
+ circuit = /obj/item/circuitboard/machine/artifactxray
+ use_power = IDLE_POWER_USE
+ ///max radiation level
+ var/max_radiation = 3
+ ///chosen radiation level
+ var/chosen_level = 1
+ var/pulse_time = 4 SECONDS
+ var/pulse_cooldown_time = 3 SECONDS
+ var/list/last_results = list("NO DATA")
+ var/pulsing = FALSE
+ COOLDOWN_DECLARE(message_cooldown)
+ COOLDOWN_DECLARE(pulse_cooldown)
+
+/obj/machinery/artifact_xray/Initialize(mapload)
+ . = ..()
+ RefreshParts()
+
+/obj/machinery/artifact_xray/RefreshParts()
+ . = ..()
+ var/power_usage = 250
+ for(var/datum/stock_part/micro_laser/laser in component_parts)
+ max_radiation = round(2.5 * laser.tier)
+ for(var/datum/stock_part/capacitor/capac in component_parts)
+ power_usage -= 30 * capac.tier
+ update_mode_power_usage(ACTIVE_POWER_USE, power_usage)
+
+/obj/machinery/artifact_xray/update_icon_state()
+ icon_state = "[base_icon_state]-[state_open]"
+ return ..()
+
+/obj/machinery/artifact_xray/AltClick(mob/user)
+ . = ..()
+ if(!can_interact(user))
+ return
+ toggle_open()
+/obj/machinery/artifact_xray/proc/toggle_open()
+ if(!COOLDOWN_FINISHED(src,pulse_cooldown))
+ return
+ if(state_open)
+ flick("xray-closing", src)
+ close_machine()
+ else
+ flick("xray-opening", src)
+ open_machine()
+
+/obj/machinery/artifact_xray/attackby(obj/item/item, mob/living/user, params)
+ if(HAS_TRAIT(item, TRAIT_NODROP))
+ to_chat(user, span_warning("[item] is stuck to your hand, you can't put it inside [src]!"))
+ return
+ if(state_open && COOLDOWN_FINISHED(src,pulse_cooldown))
+ close_machine(item)
+ return
+ ..()
+/obj/machinery/artifact_xray/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ArtifactXray", name)
+ ui.open()
+
+/obj/machinery/artifact_xray/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("toggleopen")
+ toggle_open()
+ . = TRUE
+ return
+ if("change_rate")
+ chosen_level = clamp(params["target"], 0, max_radiation)
+ . = TRUE
+ return
+ if("pulse")
+ pulse()
+ return
+ update_appearance()
+
+/obj/machinery/artifact_xray/proc/pulse()
+ if(!COOLDOWN_FINISHED(src,pulse_cooldown) || pulsing || !occupant)
+ return
+ if(state_open)
+ return
+ if(isliving(occupant))
+ if(!(obj_flags & EMAGGED))
+ say("Cannot pulse with a living being inside!")
+ return
+ var/datum/component/artifact/component = occupant.GetComponent(/datum/component/artifact)
+ if(component)
+ component.Stimulate(STIMULUS_RADIATION, chosen_level)
+ else
+ if(!HAS_TRAIT(occupant, TRAIT_IRRADIATED) && SSradiation.can_irradiate_basic(occupant))
+ occupant.AddComponent(/datum/component/irradiated)
+ pulsing = TRUE
+ update_use_power(ACTIVE_POWER_USE)
+ addtimer(CALLBACK(src, PROC_REF(post_pulse), component), pulse_time)
+
+/obj/machinery/artifact_xray/proc/post_pulse(datum/component/artifact/artifact)
+ update_use_power(IDLE_POWER_USE)
+ playsound(loc, 'sound/machines/chime.ogg', 30, FALSE)
+ COOLDOWN_START(src,pulse_cooldown,pulse_cooldown_time)
+ pulsing = FALSE
+ if(artifact)
+ last_results = list("STRUCTURAL ABNORMALITY ANALYSIS: [artifact.xray_result]", "SIZE: [artifact.artifact_size < ARTIFACT_SIZE_LARGE ? "SMALL" : "LARGE" ]")
+ else
+ last_results = list("INCONCLUSIVE;", "NO SPECIAL PROPERTIES DETECTED")
+
+
+/obj/machinery/artifact_xray/ui_data(mob/user)
+ . = ..()
+ .["is_open"] = state_open
+ if(occupant)
+ .["artifact_name"] = occupant.name
+ .["pulsing"] = pulsing
+ .["current_strength"] = chosen_level
+ .["max_strength"] = max_radiation
+ .["results"] = last_results
+ return .
+
+/obj/machinery/artifact_xray/emag_act(mob/user)
+ if(obj_flags & EMAGGED)
+ return
+ obj_flags |= EMAGGED
+ to_chat(user,span_notice("You short out the safety sensors on the [src]."))
+ playsound(src, SFX_SPARKS, 75, TRUE, SILENCED_SOUND_EXTRARANGE)
+
+/obj/machinery/artifact_xray/relaymove(mob/living/user, direction)
+ if(user.stat)
+ if(COOLDOWN_FINISHED(src, message_cooldown))
+ COOLDOWN_START(src, message_cooldown, 4 SECONDS)
+ to_chat(user, span_warning("[src]'s door won't budge while it's processing!"))
+ return
+ open_machine()
+
+/obj/machinery/artifact_xray/can_be_occupant(atom/movable/occupant_atom)
+ . = ..()
+ if(isitem(occupant_atom))
+ return TRUE
+ else if(!occupant_atom.anchored)
+ return TRUE
+
+/obj/machinery/artifact_xray/screwdriver_act(mob/living/user, obj/item/tool)
+ if(pulsing)
+ return TOOL_ACT_SIGNAL_BLOCKING
+ . = default_deconstruction_screwdriver(user, base_icon_state, base_icon_state, tool)
+
+
+/obj/machinery/artifact_xray/crowbar_act(mob/living/user, obj/item/tool)
+ return pulsing ? TOOL_ACT_SIGNAL_BLOCKING : default_deconstruction_crowbar(tool)
diff --git a/code/modules/artsci/testing_machines/zapper.dm b/code/modules/artsci/testing_machines/zapper.dm
new file mode 100644
index 000000000000..75e55e0c7f2f
--- /dev/null
+++ b/code/modules/artsci/testing_machines/zapper.dm
@@ -0,0 +1,94 @@
+/obj/machinery/artifact_zapper
+ name = "artifact zapper"
+ desc = "A directed tesla coil, zaps the artifact that it is facing. VERY power-consuming."
+ icon = 'icons/obj/machines/artifact_machines.dmi'
+ icon_state = "zapper"
+ base_icon_state = "zapper"
+ density = TRUE
+ use_power = IDLE_POWER_USE
+ circuit = /obj/item/circuitboard/machine/artifactzapper
+ ///max shock level
+ var/max_shock = 100
+ ///chosen level
+ var/chosen_level = 100
+ var/pulse_cooldown_time = 4 SECONDS
+ COOLDOWN_DECLARE(pulse_cooldown)
+
+/obj/machinery/artifact_zapper/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/simple_rotation, ROTATION_REQUIRE_WRENCH)
+ RefreshParts()
+
+/obj/machinery/artifact_zapper/RefreshParts()
+ . = ..()
+ var/shock = 0
+ for(var/datum/stock_part/capacitor/capac in component_parts)
+ shock += round(1250 * capac.tier)
+ max_shock = shock
+
+ for(var/datum/stock_part/scanning_module/scan in component_parts)
+ pulse_cooldown_time = 4 SECONDS / scan.tier
+
+/obj/machinery/artifact_zapper/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ArtifactZapper", name)
+ ui.open()
+
+/obj/machinery/artifact_zapper/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("strength")
+ chosen_level = clamp(params["target"], 0, max_shock)
+ . = TRUE
+ active_power_usage = chosen_level * 5
+ return
+ if("shock")
+ shock()
+ return
+ update_appearance()
+
+/obj/machinery/artifact_zapper/proc/shock()
+ if(!COOLDOWN_FINISHED(src,pulse_cooldown))
+ return
+ var/turf/target_turf = get_step(src,dir)
+ var/datum/component/artifact/component
+ for(var/obj/object in target_turf)
+ component = object.GetComponent(/datum/component/artifact)
+ if(component)
+ break
+
+ if(!component)
+ return
+
+ Beam(component.parent, icon_state="lightning[rand(1,12)]", time = pulse_cooldown_time)
+ playsound(get_turf(src), 'sound/magic/lightningshock.ogg', 60, TRUE, extrarange = 2)
+ use_power(chosen_level)
+ component.Stimulate(STIMULUS_SHOCK, chosen_level)
+ COOLDOWN_START(src,pulse_cooldown, pulse_cooldown_time)
+
+
+/obj/machinery/artifact_zapper/ui_data(mob/user)
+ . = ..()
+ .["pulsing"] = !COOLDOWN_FINISHED(src,pulse_cooldown)
+ .["current_strength"] = chosen_level
+ .["max_strength"] = max_shock
+ return .
+
+/obj/machinery/artifact_zapper/emag_act(mob/user)
+ if(obj_flags & EMAGGED)
+ return
+ obj_flags |= EMAGGED
+ to_chat(user,span_notice("You short out the safety sensors on the [src]."))
+ playsound(src, SFX_SPARKS, 75, TRUE, SILENCED_SOUND_EXTRARANGE)
+
+/obj/machinery/artifact_zapper/screwdriver_act(mob/living/user, obj/item/tool)
+ if(!COOLDOWN_FINISHED(src,pulse_cooldown))
+ return TOOL_ACT_SIGNAL_BLOCKING
+ . = default_deconstruction_screwdriver(user, base_icon_state, base_icon_state, tool)
+
+
+/obj/machinery/artifact_zapper/crowbar_act(mob/living/user, obj/item/tool)
+ return !COOLDOWN_FINISHED(src,pulse_cooldown) ? TOOL_ACT_SIGNAL_BLOCKING : default_deconstruction_crowbar(tool)
diff --git a/code/modules/cargo/bounties/assistant.dm b/code/modules/cargo/bounties/assistant.dm
index 2e9c81d3bb42..b040ed55d47a 100644
--- a/code/modules/cargo/bounties/assistant.dm
+++ b/code/modules/cargo/bounties/assistant.dm
@@ -1,9 +1,3 @@
-/datum/bounty/item/assistant/strange_object
- name = "Strange Object"
- description = "Nanotrasen has taken an interest in strange objects. Find one in maintenance, and ship it off to CentCom right away."
- reward = CARGO_CRATE_VALUE * 2.4
- wanted_types = list(/obj/item/relic = TRUE)
-
/datum/bounty/item/assistant/scooter
name = "Scooter"
description = "Nanotrasen has determined walking to be wasteful. Ship a scooter to CentCom to speed operations up."
diff --git a/code/modules/cargo/bounties/science.dm b/code/modules/cargo/bounties/science.dm
index 1807c5ae56ff..294b2a49bd6a 100644
--- a/code/modules/cargo/bounties/science.dm
+++ b/code/modules/cargo/bounties/science.dm
@@ -1,18 +1,3 @@
-
-/datum/bounty/item/science/relic
- name = "E.X.P.E.R.I-MENTORially Discovered Devices"
- description = "Psst, hey. Don't tell the assistants, but we're undercutting them on the value of those 'strange objects' they've been finding. Fish one up and send us a discovered one by using the E.X.P.E.R.I-MENTOR."
- reward = CARGO_CRATE_VALUE * 8
- wanted_types = list(/obj/item/relic = TRUE)
-
-/datum/bounty/item/science/relic/applies_to(obj/O)
- if(!..())
- return FALSE
- var/obj/item/relic/experiment = O
- if(experiment.revealed)
- return TRUE
- return
-
/datum/bounty/item/science/bepis_disc
name = "Reformatted Tech Disk"
description = "It turns out the diskettes the BEPIS prints experimental nodes on are extremely space-efficient. Send us one of your spares when you're done with it."
diff --git a/code/modules/cargo/packs/exploration.dm b/code/modules/cargo/packs/exploration.dm
index 55b28094de29..b209f2bb2aca 100644
--- a/code/modules/cargo/packs/exploration.dm
+++ b/code/modules/cargo/packs/exploration.dm
@@ -8,9 +8,10 @@
name = "Scrapyard Crate"
desc = "Outsourced crate containing various junk."
cost = CARGO_CRATE_VALUE * 5
- contains = list(/obj/item/relic,
- /obj/item/broken_bottle,
- /obj/item/pickaxe/rusted)
+ contains = list(
+ /obj/item/broken_bottle,
+ /obj/item/pickaxe/rusted
+ )
crate_name = "scrapyard crate"
/datum/supply_pack/exploration/catering
diff --git a/code/modules/cargo/packs/science.dm b/code/modules/cargo/packs/science.dm
index 19a7710ffed7..db800f1cb9ec 100644
--- a/code/modules/cargo/packs/science.dm
+++ b/code/modules/cargo/packs/science.dm
@@ -177,3 +177,9 @@
contains = list(/obj/item/mod/core/standard = 3)
crate_name = "\improper MOD core crate"
crate_type = /obj/structure/closet/crate/secure/science
+
+/datum/supply_pack/science/analysis_bin
+ name = "Analysis bin Crate"
+ desc = "Two analysis bins, to analyze artifacts. For those who research a lot of artifacts."
+ cost = CARGO_CRATE_VALUE * 4
+ contains = list(/obj/item/analysis_bin = 2)
diff --git a/code/modules/cargo/universal_scanner.dm b/code/modules/cargo/universal_scanner.dm
index 7e352aca98f5..340132f9c841 100644
--- a/code/modules/cargo/universal_scanner.dm
+++ b/code/modules/cargo/universal_scanner.dm
@@ -169,6 +169,9 @@
/obj/item/universal_scanner/proc/export_scan(obj/target, mob/user)
// Before you fix it:
// yes, checking manifests is a part of intended functionality.
+ if(HAS_TRAIT(target, TRAIT_HIDDEN_EXPORT_VALUE))
+ to_chat(user, span_warning("Scanned [target], export value unknown."))
+ return
var/datum/export_report/ex = export_item_and_contents(target, dry_run = TRUE)
var/price = 0
for(var/x in ex.total_amount)
diff --git a/code/modules/events/artifact_spawn.dm b/code/modules/events/artifact_spawn.dm
new file mode 100644
index 000000000000..b2a299ea1a91
--- /dev/null
+++ b/code/modules/events/artifact_spawn.dm
@@ -0,0 +1,39 @@
+/datum/round_event_control/random_artifact
+ name = "Artifact Manifestation"
+ description = "Spawns a random artifact somewhere on the station"
+ typepath = /datum/round_event/random_artifact
+ weight = 10
+ max_occurrences = 3
+ min_players = 3
+ category = EVENT_CATEGORY_ANOMALIES
+ track = EVENT_TRACK_MODERATE
+ tags = list(TAG_SPOOKY)
+
+/datum/round_event/random_artifact
+ announce_when = 0
+ start_when = 1
+ var/datum/weakref/spawn_location
+
+/datum/round_event/random_artifact/setup()
+ spawn_location = WEAKREF(pick(GLOB.generic_event_spawns))
+
+ if(!spawn_location?.resolve())
+ return kill()
+
+/datum/round_event_control/random_artifact/can_spawn_event(players_amt, allow_magic = FALSE, fake_check = FALSE)
+ . = ..()
+ if(!.)
+ return
+ //just in case
+ if(!length(GLOB.generic_event_spawns))
+ return FALSE
+ else
+ return
+
+/datum/round_event/random_artifact/start()
+ var/marker = spawn_location.resolve()
+ if(!marker)
+ return
+ var/artifact = spawn_artifact(get_turf(marker))
+ do_sparks(4, FALSE, artifact)
+ announce_to_ghosts(artifact)
diff --git a/code/modules/experisci/experiment/experiments.dm b/code/modules/experisci/experiment/experiments.dm
index b2e2ef909ff3..e26dec095a8c 100644
--- a/code/modules/experisci/experiment/experiments.dm
+++ b/code/modules/experisci/experiment/experiments.dm
@@ -242,7 +242,6 @@
required_points = 10
required_atoms = list(
/obj/machinery/mecha_part_fabricator = 1,
- /obj/machinery/rnd/experimentor = 1,
/obj/machinery/dna_scannernew = 1,
/obj/machinery/microwave = 2,
/obj/machinery/deepfryer = 2,
@@ -273,7 +272,6 @@
required_points = 6
required_atoms = list(
/obj/machinery/dna_scannernew = 1,
- /obj/machinery/rnd/experimentor = 1,
/obj/machinery/medical_kiosk = 2,
/obj/machinery/piratepad/civilian = 2,
)
@@ -299,7 +297,6 @@
required_atoms = list(
/obj/machinery/mecha_part_fabricator = 1,
/obj/machinery/microwave = 1,
- /obj/machinery/rnd/experimentor = 1,
/obj/machinery/atmospherics/components/unary/thermomachine/freezer = 2,
/obj/machinery/power/emitter = 2,
/obj/machinery/chem_heater = 2,
diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm
index a4040dba92d8..3e22dc279489 100644
--- a/code/modules/research/designs/machine_designs.dm
+++ b/code/modules/research/designs/machine_designs.dm
@@ -338,16 +338,6 @@
)
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
-/datum/design/board/experimentor
- name = "E.X.P.E.R.I-MENTOR Board"
- desc = "The circuit board for an E.X.P.E.R.I-MENTOR."
- id = "experimentor"
- build_path = /obj/item/circuitboard/machine/experimentor
- category = list(
- RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_RESEARCH
- )
- departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
-
/datum/design/board/protolathe
name = "Protolathe Board"
desc = "The circuit board for a protolathe."
@@ -1105,3 +1095,24 @@
RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ROBOTICS
)
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING
+
+
+/datum/design/board/artifact_xray
+ name = "Artifact XRay Board"
+ desc = "The circuit board for a xray machine for artifacts"
+ id = "artifact_xray"
+ build_path = /obj/item/circuitboard/machine/artifactxray
+ category = list(
+ RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_RESEARCH
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
+
+/datum/design/board/artifact_heater
+ name = "Artifact Heating Pad Board"
+ desc = "The circuit board for a heating pad for artifact"
+ id = "artifact_heater"
+ build_path = /obj/item/circuitboard/machine/artifactheater
+ category = list(
+ RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_RESEARCH
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
diff --git a/code/modules/research/experimentor.dm b/code/modules/research/experimentor.dm
deleted file mode 100644
index 98abd12ee750..000000000000
--- a/code/modules/research/experimentor.dm
+++ /dev/null
@@ -1,712 +0,0 @@
-//this is designed to replace the destructive analyzer
-
-//NEEDS MAJOR CODE CLEANUP
-
-#define SCANTYPE_POKE 1
-#define SCANTYPE_IRRADIATE 2
-#define SCANTYPE_GAS 3
-#define SCANTYPE_HEAT 4
-#define SCANTYPE_COLD 5
-#define SCANTYPE_OBLITERATE 6
-#define SCANTYPE_DISCOVER 7
-
-#define EFFECT_PROB_VERYLOW 20
-#define EFFECT_PROB_LOW 35
-#define EFFECT_PROB_MEDIUM 50
-#define EFFECT_PROB_HIGH 75
-#define EFFECT_PROB_VERYHIGH 95
-
-#define FAIL 8
-/obj/machinery/rnd/experimentor
- name = "\improper E.X.P.E.R.I-MENTOR"
- desc = "A \"replacement\" for the destructive analyzer with a slight tendency to catastrophically fail."
- icon = 'icons/obj/machines/heavy_lathe.dmi'
- icon_state = "h_lathe"
- base_icon_state = "h_lathe"
- density = TRUE
- use_power = IDLE_POWER_USE
- circuit = /obj/item/circuitboard/machine/experimentor
- var/recentlyExperimented = 0
- /// Weakref to the first ian we can find at init
- var/datum/weakref/tracked_ian_ref
- /// Weakref to the first runtime we can find at init
- var/datum/weakref/tracked_runtime_ref
- ///Determines the probability of a malfunction.
- var/malfunction_probability_coeff = 0
- ///Keeps track of how many times we've had a critical reaction
- var/malfunction_probability_coeff_modifier = 0
- var/resetTime = 15
- var/cloneMode = FALSE
- var/list/item_reactions
- var/static/list/valid_items //valid items for special reactions like transforming
- var/list/critical_items_typecache //items that can cause critical reactions
-
-/obj/machinery/rnd/experimentor/proc/ConvertReqString2List(list/source_list)
- var/list/temp_list = params2list(source_list)
- for(var/O in temp_list)
- temp_list[O] = text2num(temp_list[O])
- return temp_list
-
-/obj/machinery/rnd/experimentor/proc/valid_items()
- RETURN_TYPE(/list)
-
- if (isnull(valid_items))
- generate_valid_items_and_item_reactions()
-
- return valid_items
-
-/obj/machinery/rnd/experimentor/proc/item_reactions()
- RETURN_TYPE(/list)
-
- if (isnull(item_reactions))
- generate_valid_items_and_item_reactions()
-
- return item_reactions
-
-/obj/machinery/rnd/experimentor/proc/generate_valid_items_and_item_reactions()
- var/static/list/banned_typecache = typecacheof(list(
- /obj/item/stock_parts/cell/infinite,
- /obj/item/grenade/chem_grenade/tuberculosis
- ))
-
- item_reactions = list()
- valid_items = list()
-
- for(var/I in typesof(/obj/item))
- if(ispath(I, /obj/item/relic))
- item_reactions["[I]"] = SCANTYPE_DISCOVER
- else
- item_reactions["[I]"] = pick(SCANTYPE_POKE,SCANTYPE_IRRADIATE,SCANTYPE_GAS,SCANTYPE_HEAT,SCANTYPE_COLD,SCANTYPE_OBLITERATE)
-
- if(is_type_in_typecache(I, banned_typecache))
- continue
-
- if(ispath(I, /obj/item/stock_parts) || ispath(I, /obj/item/grenade/chem_grenade) || ispath(I, /obj/item/knife))
- var/obj/item/tempCheck = I
- if(initial(tempCheck.icon_state) != null) //check it's an actual usable item, in a hacky way
- valid_items["[I]"] += 15
-
- if(ispath(I, /obj/item/food))
- var/obj/item/tempCheck = I
- if(initial(tempCheck.icon_state) != null) //check it's an actual usable item, in a hacky way
- valid_items["[I]"] += rand(1,4)
-
-/obj/machinery/rnd/experimentor/Initialize(mapload)
- . = ..()
-
- tracked_ian_ref = WEAKREF(locate(/mob/living/basic/pet/dog/corgi/ian) in GLOB.mob_living_list)
- tracked_runtime_ref = WEAKREF(locate(/mob/living/simple_animal/pet/cat/runtime) in GLOB.mob_living_list)
-
- critical_items_typecache = typecacheof(list(
- /obj/item/construction/rcd,
- /obj/item/grenade,
- /obj/item/aicard,
- /obj/item/storage/backpack/holding,
- /obj/item/slime_extract,
- /obj/item/onetankbomb,
- /obj/item/transfer_valve))
-
-/obj/machinery/rnd/experimentor/RefreshParts()
- . = ..()
- malfunction_probability_coeff = malfunction_probability_coeff_modifier
- resetTime = initial(resetTime)
- for(var/datum/stock_part/manipulator/manipulator in component_parts)
- resetTime = max(1, resetTime - manipulator.tier)
- for(var/datum/stock_part/scanning_module/scanning_module in component_parts)
- malfunction_probability_coeff += scanning_module.tier * 2
- for(var/datum/stock_part/micro_laser/micro_laser in component_parts)
- malfunction_probability_coeff += micro_laser.tier
-
-/obj/machinery/rnd/experimentor/examine(mob/user)
- . = ..()
- if(in_range(user, src) || isobserver(user))
- . += span_notice("The status display reads: Malfunction probability reduced by [malfunction_probability_coeff]%.
Cooldown interval between experiments at [resetTime*0.1] seconds.")
-
-/obj/machinery/rnd/experimentor/proc/checkCircumstances(obj/item/O)
- //snowflake check to only take "made" bombs
- if(istype(O, /obj/item/transfer_valve))
- var/obj/item/transfer_valve/T = O
- if(!T.tank_one || !T.tank_two || !T.attached_device)
- return FALSE
- return TRUE
-
-/obj/machinery/rnd/experimentor/Insert_Item(obj/item/O, mob/living/user)
- if(!(user.istate & ISTATE_HARM))
- . = 1
- if(!is_insertion_ready(user))
- return
- if(!user.transferItemToLoc(O, src))
- return
- loaded_item = O
- to_chat(user, span_notice("You add [O] to the machine."))
- flick("h_lathe_load", src)
-
-/obj/machinery/rnd/experimentor/default_deconstruction_crowbar(obj/item/O)
- ejectItem()
- . = ..(O)
-
-/obj/machinery/rnd/experimentor/ui_interact(mob/user)
- var/list/dat = list("
0^NfK$t^ zY17qQhb#uqIwjX{`9nJpkU40SRRHw_oX2XJHhw~7Z( fObSyIKI0s(zYF;z7J$1v>02e9ZHlyOkE{M7iUsZ5noXDQZa zzvwz7{SLDLCc&Mih2Ok=YiC-DU~K|o!`_1#z9!RB3;+HX1cT!0$4!?-%U6fHbr++? z;E3dJwc^N#*D1OJv`EZ*#UwR$kD2u5^#dR>=eM^JF+2gaTeH#mw5|b-=(fW*%-8~F zp*NvrE%*EC>{|$;h6u_2vJG2*p!~s6G=zxhBMV?!Rkj!@c)fY-uJD$$Rs3!AA&gC} z`$1T1XKkulPZa1qKUO$TvM&)-d$M*bJ2&@dNeR`W|7G#v>8!H~^2|+$2~U242HE zm2UFm>h2gc8`QP9s4DjoL 1>q1xaT?>q9NKDw7XZ|{jr*={YkaI+Gipsrg-t`9t3alXsL(|06YAtWRu0 7 z$)6sjSL!w3LeWT?oAv}BrKFeQ{g%(ex02u^$=lUqO6avkUC(utRRYxm;6X3MbyKOL zVPr*iaq1d@fsBkpD&4u`Rh4iF#H&z(dIH5| zQ2CY;4AfZK$0!!G+HxJ73NXA-QHlTdEo8Yftn c>*y%Z8iNUeNL zWqw%guz|pmQ)Ras8O5c8kTEKS9zRixRNp-Kom;NkR8`s2Np0# z^Woh0BP 57zouOOMC?=Vx9KY<$1E~UsTg00e18Uc^k>u-LczoG*)3R+{wngESx{$##gvSrXF z)z;JNTy`P!P@ GdX5TreCD?&*M7hkzLyH}NV;wnnSf)icws z=hE+oecI%%&U<_gRX;q>XxW&zj(@_*Ux09!uz0pGgPy$dE{tf#*O_+Q-B9VLdQgPv zAm^9eA!;^n$A2KA110`C)=2&y{Y&hUwU9u3G_96*m+*~F?pycpn$YdefK!~OWfYJ3 zygKx)$}WHM;~ev~zp|tYmRCe^dRO=XaINoVf&oIjrTp@`#w2P|51wzXdXQ3@x%RR! z_vrHAmL3?xH@JX+J&ZEZcH*~hnMMByu4%;sxZUIYOH=Cb#U&+K3h$M4JbcG8Js`7a zH$6T5J)F~=Hb5Adez>hpSb*ndi;g$OEmb2{GU|9ikY;7&;iw8f=v1h7TgX1~8x!=> zAeXHF`s~LoTS>ev?<+|?{dvIx8Hxu U5IQV#zC>VYw#HC*6}o7SF=UXi ztDb81f%yBbHiLK+ap&K_o0CX?c;0t{I&ix=FmT#$+?rJ1MF&N81`tEGLj#`-z=2r~ z hzej(Cil3jfgy$Y>xj%RzgWlva%9Yc2$2giAr()- oLAJPv?z0iDP(@|j;)SE *a`Z>?szXc9L4bRbjlESVOBiu z^whKqJ%qE%DMs~{m=s#{e-B6X&iFo#jgvt?D=aOI)5racld16IclZ{@kw10gB}z?S z)}@w-w^2c}+u|9VX8=f|oT8YuYZaJ;e_tfW$Wxt3j=C9;iT9#Q2MvY#MrS}b(xOw( zSXWPvMD)fFnrE&4t0&HgmqSB~*29_eI?%iI>KOl$R#Bt(V8!88Wl3$WLI_=1ZqF zk<#I7SHpr3xmmUf-g3z|Mz1}8*L3ZDa*T-n6r+f;c=pf(U#po`H%s%0S%D`$L4fc6 zfD4EUXgPZe1oR!gU-Gx0=fuVI*lmsGB#A?-F7_oyQNxSX4)RICX>VTz7mKUrq;?Kn zK5T8 OdF8q+u@rq=1zK7R z$M%l{9{g8N>uTl?Jv%BIe?~p!bjt|ZNo|vFomzT#LUiD@H0lez3jvMEyAy1AMX7Dt zo_-A_z0!mWlT-?izdXT@zt48xAP(>4^XC(_G=COuJfiXFAX7$B*KIp1yz=9-vtt1* z>95+Xq1msw%M_<>sH{ %51Fk2UbZ)>)J-}z zn=C%^7p4^EFPIA;3a@m`tVoic ~Fj^ep-H-$5tp0X^v+GzdP0+9q*I>+0(>9zJq(%#o?) zrNMo#t5JT_3a0FSEblEY`L~zwPjYZZB+a_DQ+ a-! zbqDVG0c`*}918e#xc7LF8r=9~nEZ-`;QM{S@C>%`6E2Zp)l5m!w%gMjbGI6X17wnd zs6!uI(T)GQ)eYQx!TJ%98av^V4pu<+QCU!Kex&Jnb&_1Gtx2lDV$oEHN dq7S$0tgkDcHzYA<>dO0h9isoqQDDMFbd zY*nw@+R8%}Hit8%nOWkpum?0iTMMT8{1QoCwbkm|uYc6#_oB7UV)W}GQ=gglcQeir zeD=^=AuiA=P+ RB_gK2L89cB%pjK3wfDUw3{j~D1rF@L9FHH|gX zs >Zt7d?Y#cl-=n-B#0}c(kzFB=bn79|`MIA67f5P+`^(JObrmJT?~! zD8e{gA&hGIcV@Fo^I_E}&~#KV8x6n0Jeke4_*>j6=%J^R(fcRY@ 8ffPv8!5 z+4F43%rL}Y>72x|u9s69@%J|Un4Oj1#Bq87imAgwo>4!NCbX3c<=OFGfzeBE+<7}V zC-|T-&e%EIa~BWra!}>+cR3}RdR247)3gqfD&a1Fc?v%0jb*Yyl(3DCN-Tj! lbDw?nLh?wj6pVr}w%CTcI6-`SsNlI9MidI?c3QJSj8xQ%A zvb%T=SeKj7J0P0%)02PJwJvNq)FqWSxaamky$<5?Y90n8BRh%;pc1&K!RDJnGM6O4 zljC$7RPUfSo&Eaa{T%-yj;3}@$K 7BAhI7N$i3gXV_iAKk}a{YBNA9YaA+ zvi2MS!@iw$GxlGjR9bAwHAj><$B#&}IAeB)ZX_xvq=qZ_5=8Z_k-3?z{KtK~<@n$` z2n7Wc7k9;3!7!zh6CVH;5a5doDq5N-73V|ki=S?oMr7(y^k%u1cAX>Go!$Lz*IekC zz;}xD-YpzBF-jtJ9<}%d@1n%&%noA4odsb{bZu 9_Sy-4qO_tNf3X5iCX5wuz zS>G;R^|Z+qAxxufxaYsKInw4{Y| *BPa5C_r*(1M)hQbg?>1EHZ_cvi45!$U!GLQM?_oLtmuQ%yB%eT=!AeOKGLcI#j z=?3GK4mb|=RA{ffTQkX!6#8ud#u+@)od*P1--~`%^h2PhoMZb&q?Xq2&|(;vYjBj@ zc&AmSV=Mb@88U5qetL#wt3(eNNvvae7MoA7JU`(IhA>JPJ*pbqf7cBve9gEQvn{m8 z`j@dbi<#1~BI7?ZTc|`XAC63U5h_z5GQoL5h1m^z{i6op40A63sHw(T YO9) zCX$%wN|&O;vb9oacklRj=B<$2^mhrn%RJw4@Z;lSf)9wKA=Gz>!p%jwBoOsP$}QM+ zZ|{r6;YxStdHD*epPXMzOzZ$}X8e$Z9R%Wbf{Z|6EQb3YvJiWhnuvxXlZWJR+&SVq zeB?t(1D3u2d y58s%*#7hCWBgBb3S=ja_K-f`RJuC6E-0SeQ zC0Juc4bu%NGvD)9ki_aB7HJuo>Es-#9OUMx{EGEMN({PzSR*ZvH{xnqNKK9pD(n40#t7`)@y2zA930HK{Xb|11$__9Ftv>95hQyOl1glVvftju z15ja*Rj@cJuMorD8!JLA&COdZeY#tE! dKRU z*ahiFN@k%6o4kn21=9j%!3RkfVrd7G>zP~Sm8R9Z7zl00{i}J*449MkI!pcbP+s5L z&lM=hb4~-t$C7+>Y@;9@D>`m@S%bqD(C}IE8AnVkFZn|YTILB>rv$G&XQ{OP8c9H{ zmp9cyu;{B5@sm +pB*GsZGc^R+)B{&WHK%SL~^FkU`Uk#whLH>R1 z=bG4b-l W`)ffiqb%L>qG%%0zJdocNbFU~;W_hw?~ z** DfOKlu4Qw&l3NYw`S`sW zO8^-Ce*W8)<#d11O!9Ik(*_|k@EIZLTPJ$ZkkDlQH3=EC%61X6wR?2o8>*j7a@9Em z&H}jf9_IJGPj{xv)f8STGV&T$-QOIIbR1eofbtFETb2}d`>8+}vWt^Oa;dBDYMEW# zul$R#O4bkD9+9C2rXvbF?Qo7iGSs@g235hhq`KH6YwzeNh=1EguoWmv+B`!J lO=59{_V7hDVdKV6`5N+w|zUuW-K{Lj!k} zIo6zNw@W@^k$gU$N4K~bqF5U`IeHakv91{hQF3x}(l{T)97Kkreu63XKG-BW^O|Di zpY_yre(2N}3gIXtGZ Na2&E5Y@`J<9OC=%f;N$p z5LlW)mL}567dfkA{J_O7+%kEL?<11y`A7DQT{`~9G$jUo_B5{F6x0@$!)ZfX^WK>% zaSr2?VoJV_-x@rQ;fcQkF~yRBM!SK|hcJfxX{ NpHW zEp=yM2C`}s#F2#Njqt!eC1qHwd~#sSvu=hua#jT^Dte4yrh(KN!KmUw_$xyn-)2XK z$D@*&Y_WL5fFu7l1PY_oH4O9EoXTWtDaNu>_EL;PpOC4}MkS$@st`9&w?W<`?xW zho0xE3sJBZ%*HX883c-%?6Z{eUuvxkI(&V-7cejzP)m^(&gDGR-IH!Y5N0V<1Kxrt z#LmG3x*{@@N-(bB)w~cs`z86(oXW3XYK{@9;gXGTQpMOzj=uhZTk#R?)rKWq=rv#I zAe<=$9~F=xjkL_{N&cmbl1uxb bS1M9o<4Fa?bMDKo4hofcBy*)?vx
?U$H){yHLAe!5}M~1DrGru-z=Weeqc27%XK4#OrAKEeCE@EZXzVXRv z*K{U6NN$c{#wujeIb!=l9fei|G~ji3$}B8p)}A5~9(& nqFs;GK2hCj>ew+o%YjO(!8FpY7qNg&ZDcq-2}k z7>G!r+#w#8BN#4Wn^|TAnYi6B<8bpZt+!3Esa2Y`RZ|poy8ZQShIQ+si7yuUT~anL zEA(NkelqDa=s>o77Q0>3_r<#@S^1!Ar`0SIKW)}b;8K-IzdP1I$cMLN^ASgV_Z__8 z$y~;A)C#|jdT0(~+dBbxM&$g+ZV7F~#mVQ2&?i;{Fr?N)bnjw%luyX+5s$M?B|L7w z>EdOwQGATJ0+^o|RTz(+^p_JtR?5&+vo(|``$n^sb@xMSJp6fXT{qqpBQA^4_LR9s z`A6I@zq==A3BryX8UN4ag8vON`jx%nWw`N&H#|?(9N;e3rr_Ei`f_>sD c@`T3%|cvnEvsf1;AYK)$5DEoLCV+sVOp_q?FY|!~q;i@ xglDQMEhc77acDqb6Fr`K55xWOfNDPC&UGg%76!3GMU)PaEGybpM zz5}YMrd>CHLZlZF6ha9C3QALogrYPR6a=Z#doNN#C-h!a1Ox;G>AiPCFVaDJgisWa z5UO+%?&kZy|DJR2Ip;t3uDjM{tt_&3vS)T?-g)Ppcb?~&5T}DdUd~EcZNz3(@I~tF zGhoZs0eNxvo~12D%JD-k(J-);10?Kdg~PjDBYsy00sJXenb=*Y(vkfzqu0lM>(AtI z#V7kj2M~bk6}?l2&$^L5ED!vO^!LIVRD+JA0P#xA{l$0Iv%z)0xkuzn*K7WU+zST3 zRv*@!7sGz0DIr~|C)B<|;s5wf4~(Efv6?NBrrBTmyfPF{01!)(R%Kj+K!nCty1U?m z&X7z9B!9&k=aH66PyH6Lco?qw_<6--4#zl9hZEYpv=(u;_AG~5IpM_aTW!;p2(i?O zr9EcM^zUoWflBike@@jR3<9TuoyJ|;GSx{ciWljD+IGWUI7(8B44*<}gX1n4IBEJ$ z00*w^qfA> 1+~qjJiF{tf{wmN_w-h4-?uP8R@{R9|>lrDsSLSZuQ41QN6!h~! z#Y?1n`5Dp$b-w#8;>Yg5XD$~28t<_fk8s%EMnK)qh4Q{#0^Lg)ucP~P2MO%9{I1^A zcLEk}GpxV_8`jBwKU^N7Dhebi9TeFGyrjxe*w*V_n1EOT>fjq>uIX8tuR+8cap_bp zmNq1pWT)nb48u<{uDp!8eC|A#_n~9^IatOUb0>wQhRK;69a_b9x7EM{27+L$k48w@ z;3&-!ucip?-C*-F#$Y&dH QneTJCT|0-*8`rj?sYNL&=`U+fBBtz~#{--{b-@Di6`eL})pH{lt_0W5R4Mm@gZh2g=$>T@a~ zy=i+*Ko|ln3EK!$CIbNrdSDHp;FCT%&;c-}fd2T4Zw7F=7URAK7?uV7lQ9MSf5Voo z7 G!Oe~vB9mlLuw)p>>V#1c TN0u&&Spp B(Ou@tz{zH$m1ZfL z0LFmZno<_PK-eg~p>Nwf1W*vmos|EQ3H?vk_J2&B{_k#Bye;aI{QZ?AMYi&`C43L3 zV$5LR!H&MT-2+lKxg?)_pdnvUpFB%W>(S94F#+f0lkqo?u1^%u$%;jA>VwwoCQo{a z5$k-PHpdh`f(ov86bNP9|BV||Ha&viYRahmV$?~O)RRXN^*okBdY-cSW<#WW7=iEE z+e^js_`B3@^jNP&KkK7TeDv~iRPF%V3okW&HE0`FlK#E|>^sz*97(^2`6Yih+wzeI zBmRs%wa4RBe#S~KSleiT2FM{aNPuheq?@fS@frI)?{2p0eGPh~knGdoGVR%C+0= e3$@9COAWzv{y-fC^27;g(tie z_Rz;{GEu*ZlP4*RGz&vX!KzbZK5ZA#|0^?ay-~LFsiys`u r; `)AH7hKEn*W`VjtY)ALftwFHObZ8k(G7QscX~yb2JR;D69k47WJ>(D;2%m zG~||4rOiO;EN#Mo?A0;1FnCU-P4Ao6mn 0JM%p}OADhBL6(4vEmWJ>Cd@ zSq%8sHp&88kqX5wdLmf1;!GD0jTZUlm#lsJ7!iic?@)v!@fgc4 *xsDB@N}r55!d`{%(WGHu4ToB<2Pv(CRLgBcf-{n YxnYqZN1>>AU50}g{D3p=zd z*28z`d6^=v1J#hr{KK}v@_*a54q)5t*{4B<@U_Tmjm0V-H(*jMujsob-$7TG+` zg;M}!NrOynJ`?oWBYX)57m)*Tk!d @Gml*1BmxXJG_H8A(zaIa*WRlq|1OlM zkWg@(j*$bS-SQfQ>C=p$(V+a4#2U}WOy|@xumM}+&LrTAO%tyOIGJ!Wf9L0Z?*z%% zELVNB$?}{h#L=pk8wS>(Y $L)zW?jzg@DOVPj!X{a;nwmGy6F{hu-Hd z3PG(h4Z)27=7lORr}GZyhuY@P9rg$Z>(qyTt%Cwq3B{~Og5?3m;NbtY%!+t^y1!W_ zW( ycHGqo~an;L+D0{i-yq`9UH$xd-o!LJIZRm{eGj-{P@ zI6RCFh(aOk#p37jnSK%j83fEhKt?gs5se&@T1dnY-4C;^W{1QL{1G?%lW%?aNcc=> zCmlAQcsgy6z%aSLaC)YwyAjU}tmYD>mj?$2HLrHfx1O7K_2SRF=sb)Dh-yQ3I4mV- zcPmJ0CHyCgv`}iIsvD?^^P6pN0T45#9=gg>rGKLCfsyXG+A#|vR+8Y}Kmm@U%q(Po zNZD4aS*=Ct@1VNp+}qq7z 1=A^ki+b#1reeP`%WK+IvNK&X5@BB}-A zku_20b@CL_X1nidI;fa#s$GxGa(vrkE^0jBUA9QYcLIwqDlDrcBKF@tN~Z*^&th8h zXOjXro_dowa5veb;EP}5q`RN8B5oaRS0Qh$meViU`n?FHU=k(VaS!4NZ-m3MQXhyA zfrzKcdsd@|)<5|9nCmLyhF%_!Q1z@1wI8hL;8m~g{O#&GmXYvCm2*~y?#Dg-9jtm- z&~(u&x7$gDr#vttHZd#Qyu*P0+sd25Hqailz}=0tvQw1vb%s_JSHtYI