diff --git a/_maps/map_files/cyberiad/cyberiad.dmm b/_maps/map_files/cyberiad/cyberiad.dmm
index 947dafd4e49..56b2c2f6de9 100644
--- a/_maps/map_files/cyberiad/cyberiad.dmm
+++ b/_maps/map_files/cyberiad/cyberiad.dmm
@@ -8489,6 +8489,7 @@
"dhm" = (/obj/effect/spawner/lootdrop/trade_sol/vehicle,/turf/simulated/shuttle/floor,/area/shuttle/trade/sol)
"dhn" = (/turf/simulated/shuttle/wall{icon_state = "swall13"; dir = 2},/area/shuttle/trade/sol)
"dho" = (/obj/machinery/camera/emp_proof{c_tag = "Engineering Singularity North-West"; dir = 2; network = list("Singularity","SS13"); pixel_x = 20; pixel_y = 0},/obj/machinery/power/grounding_rod{anchored = 1},/turf/simulated/floor/plating/airless,/area/engine/engineering)
+"dhp" = (/obj/effect/landmark/battle_mob_point,/turf/simulated/floor/engine{name = "Holodeck Projector Floor"},/area/holodeck/alphadeck)
"dhv" = (/obj/machinery/camera/emp_proof{c_tag = "Engineering Singularity North-East"; dir = 2; network = list("Singularity","SS13")},/obj/machinery/power/grounding_rod{anchored = 1},/turf/simulated/floor/plating/airless,/area/engine/engineering)
"dhx" = (/obj/machinery/light,/obj/machinery/atmospherics/binary/volume_pump/on{name = "Space Loop In"},/turf/simulated/floor/plasteel,/area/atmos)
"dhz" = (/obj/machinery/atmospherics/unary/vent_scrubber{dir = 8; on = 1; scrub_N2O = 1; scrub_Toxins = 1},/obj/structure/closet/firecloset,/obj/machinery/requests_console{department = "Atmospherics"; departmentType = 3; name = "Atmospherics Requests Console"; pixel_x = -30},/turf/simulated/floor/plasteel,/area/atmos)
@@ -9020,10 +9021,10 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaJPaJSaJQaaaaJRaJVaJTaabaabaIIaHSaIIaaaaaaaabaGnaJXaJYaJZaJWaJUaKaaGnaKbaHSaKdaKcaKfaKeaKeaKeaKeaKeaKeaKeaKeaKgaGnaaaaKhaaaaaaaaaaaaaaaaaaaaaaaaaIbaKkaHkaKmaKnaHlaLAaHmaKraHoaLAaGHaIlaKtaHYaKvaGIaHgaKqaKsaKsaKsaKsaKsaJgaKwaJiaFyaFFaFGaEjaFMaLPaLPaLPaLPaLPaHpaKOaKOaKOaKOaKOaKOaKOaDNaDNaLVaDNaDPaDPaHsaDPaDPaDPaMhaMeaMjcYKaODaKQaKQaKQaKQaKQaODaaaaaaaaaaHNaKJaKMaaaaIFaKRaKTaKSaKWaGYaGYaGYaMzaGYaMzaKXaGYaGYaGXaGYaKVaKUaKXaIEaGYaMqaDZaOLaEYaMqaGVaHLaNkaMqaMraMraMraMraabaMraMraabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaLdaLaaLbaLaaLdaLeaLfaLeaLdaabaIIaLgaLhaaaaaaaGnaGnaGnaGnaGnaLkaLiaLlaLjaLnaLmaLmaLoaLraLraLqaLpaGnaHSaGnaGnaGnaLsaGnaaaaabaaaaaaaaaaaaaaaaaaaabaabaIbaLuaLtaLwaLvaLxaLAaKraLyaHtaLAaGHaIlaLCaLEaHwaHuaHzaHyaHAaLHaKsaLIaLIaJgaKsaJiaFyaFFaLJaEjaHBaLPaHFaHDaLNaLPaMwaAGaMIaMHaMLaMJaIdaHZaIkaIgaIyaIvaIUaITaNcaNbaIXaKObWrbWraNUaKOaODaKQaKQaKQaKQaKQaODaabaabaKOaHNaMfaMgaHNaIFaMiaGYaMkaGYaHbaGYaGXaGXaMmaGXaGXaMnaMzaMzaKXaMoaMlaGYaGXaGYaPOaMqaNnaPTaPSaOJaDZaOKaMqaabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaapsaptaprapuaprapraprapraprapuapvaaaaaaaMsaLaaMpaLaaMsaLeaMtaLeaMsaGnaIIaMuaEAaIIaIIaGnaHSaHSaHSaHSaHSaHSaHSaLkaLsaGnaGnaGnaGnaGnaMxaGnaGnaHSaMyaGnaMvaLsaGnaGnaGnaaaaaaaaaaaaaaaaMAaMAaMAaMAaMAaMAaMAaMAaMAaLAaItaMCaIuaLAaGHaIlaIxaJbaINaJvaHgaMMaIQaIOaMPaMOaMRaMQaMSaJiaFyaFFaFGaEjaFMaLPaISaIRaLNaLPaKuaMVaMVaMVaMVaMVaMVaMVaLQaKxaLRaOiaOiaOiaLSaOiaPAaOuaOAaOzaOBaJqaODaKQaKQaKQaKQaKQaODaKOaKOaKOaGYaNfaNgaJIaMzaMoaKXaMkaGYaNhaAYaNlaIEaGYaGYaGYaGYaNiaGYaGYaMoaNjaGYaMnaPRaPQaQSaQQaQUaQTaOMaQYaQWaMqaMraMraMraaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaqfapxapwaqhaqgarharhaqiarhaqjapxaaaaaaaMsaONaNtaPPaMsaPUaNyaPWaNwaNxaHSaNzaNCaNAaMyaNBaHSaGnaGnaGnaGnaGnaGnaGnaLsaGnaByafOaBTaGnaNEaNFaNHaHSaNBaGnaNAaNIaKeaKgaIIaaaaaaaaaaaaaaaaQVaNJaNMaNLaNOaNNaNPaMAaiWaLAaJraNQaIVaLAaJsaIlaJuaJtaJwaLTaHgaNWaJzaJxaKiaJAaKlaKjaKoaJiaFyaFFaFGaEjaFMaLPaOjaKpaLNaLPaLUaMVaMNaMNaMNaMVaMVaMVaMVaMVaMTaOoaMUaMUaOsaMVaSQaMVaMVaRRaMVaPvaRVaKQaKQaKQaKQaKQaRVaTbaTdaTcaXiaTeaOCaGYaMzaOFaGYaOEaOGaOGaOGaOGaOHaOGaOIaGYaGYaGYaGYaGYaGYaGYaGYaMzaHQaPOaMqaRSaRuaOLaOLaEYaRZaMqaJNaJLaGXaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaqfapxapwaqhaqgarharhaqiarhaqjapxaaaaaaaMsaONaNtaPPaMsaPUaNyaPWaNwaNxaHSaNzaNCaNAaMyaNBaHSaGnaGnaGnaGnaGnaGnaGnaLsaGnaByafOaBTaGnaNEaNFaNHaHSaNBaGnaNAaNIaKeaKgaIIaaaaaaaaaaaaaaaaQVaNJaNMaNLaNOaNNaNPaMAaiWaLAaJraNQaIVaLAaJsaIlaJuaJtaJwaLTaHgaNWaJzaJxaKiaJAaKlaKjaKoaJiaFyaFFaFGaEjaFMaLPaOjaKpaLNaLPaLUaMVaMNaMNaMNaMVaMVaMVaMVaMVaMTaOoaMUaMUaOsaMVaSQaMVaMVaRRaMVaPvaRVaKQaKQdhpaKQaKQaRVaTbaTdaTcaXiaTeaOCaGYaMzaOFaGYaOEaOGaOGaOGaOGaOHaOGaOIaGYaGYaGYaGYaGYaGYaGYaGYaMzaHQaPOaMqaRSaRuaOLaOLaEYaRZaMqaJNaJLaGXaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaardapxarharharharhaqkaqmaqlareapxaaaaaaaMsaOPaOQaORaMsaOPaOQaORaNwaKaaHSaOSaHSaHSaHSaHSaHSaGnaOTaOOaOVaOUaOWaGnaLsaGnafOaOXaByaOYaPaaOZaGnaPcaGnaGnaGnaGnaPcaLsaIIaaadmodmodmodmoaRYaNJaPeaPbaPfaPhaPiaPjaMAaLAaLAaLAaLAaLAaJsaIlaIlaIlaIlaIlaIlaKzaKAaIoaIoaIoaIoaIoaIoaIoaPraFFaPwaEjaFMaLPaOnaKpaLNaLPaKuaMNaMWaMWaMXaMNaMVaMVaMYaMVaNaaMZaNdaNdaNeaMVaSQaMVaMVaMVaMVaKCaODaKQaKQaKQaKQaKQaODaPLaPMaKOaGYaVJaMzaMzaMzaHPaHPaGXaGXaGXaMzaMzaMzaGXaPNaPKaGXaMzaMzaGXaMzaMzaGXaGXaRTaMqaTjaQRaTlaTkaTmaQRaTnaMqaKZaKYaGXaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaqfapxarfargarharharWasaarZarhaqoaSdaSdaLdaMsaPZaQaaLdaMsaPZaQaaLdaGnaLkaGnaGnaGnaGnaGnaHSaGnaHSaQbaPXaHSaHSaGnaLsaGnaByaabaBZaGnaJYaOZaGnaQeaQcaGnaNAaKaaGnaLsaMAaMAaQgaQgaQgaQgaQgaQgaQgaQgaQgaPiaPiaPjaQhaQiaQjaQkaQlaQdaKEaKDaKFaKFaKFaKFaKFaKGaKIaKHaKFaKFaKFaKKaKKaKLaLzaKNaLBaEjaHBaLPaQzaKpaLNaLPaKuaMNaMWaNRaNSaMNaMVaMVaMVaMVaNTaMZaNdaNdaNeaMVaSQaMVaMVaMVaMVaKCaODaKQaKQaKQaKQaKQaODaQHaPMaKOaGYaVJaGYaQBaMzaQCaHOaQLaQJaHbaGXaOFaHbaGXaQKaGYaGXaQMaGYaGXaQOaQNaQPaOGaRWaMqaUgaQRaUiaUhaUiaQRaUjaMqaMzaMzaGXaGXaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaardapxarharharharharWasaaPVarhaQXaRbaoOaSbaMcaMdaMdaRaaMdaMdaMdaRdaRcaReaReaReaReaRfaGnaHSaGnaRhaRgaRiaHSaRjaPcaLsaGnaGnaGnaGnaGnaGnaPcaGnaHSaRiaLkaHSaHSaHSaRkaRmaRlaRnaRnaRnaRnaRoaRnaRnaRpaRqaRlaRsaRraRraRraRraRraRraRraRtaRwaRwaRwaRwaRxaRwaRwaLDaRwaRwaRxaRwaRwaRwaRwaRzaLFaLKaLGaLLaLPaRDaLMaLWaLOaNVaMNaNYaNXaOeaMNaMVaMVaMVaMVaMTaRMaOpaOpaRGaMVaVLaVKaMVaMVaMVaPvaRVaKQaKQaKQaKQaKQaRVaVMaYwaVObfQaZzbafbafbafbafbbYaOGaOGaOGaOGaOGaOGaOGaKSaGYaGYaGYaGYaMnaGYaGYaMkaGYaRXaUYaUYaMqaMqaMqaMqaMqaMqaUfaQIaOFaJMaGXaabafOaMraMraabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaardapxarharharharharWasaaPVarhaQXaRbaoOaSbaMcaMdaMdaRaaMdaMdaMdaRdaRcaReaReaReaReaRfaGnaHSaGnaRhaRgaRiaHSaRjaPcaLsaGnaGnaGnaGnaGnaGnaPcaGnaHSaRiaLkaHSaHSaHSaRkaRmaRlaRnaRnaRnaRnaRoaRnaRnaRpaRqaRlaRsaRraRraRraRraRraRraRraRtaRwaRwaRwaRwaRxaRwaRwaLDaRwaRwaRxaRwaRwaRwaRwaRzaLFaLKaLGaLLaLPaRDaLMaLWaLOaNVaMNaNYaNXaOeaMNaMVaMVaMVaMVaMTaRMaOpaOpaRGaMVaVLaVKaMVaMVaMVaPvaRVaKQaKQdhpaKQaKQaRVaVMaYwaVObfQaZzbafbafbafbafbbYaOGaOGaOGaOGaOGaOGaOGaKSaGYaGYaGYaGYaMnaGYaGYaMkaGYaRXaUYaUYaMqaMqaMqaMqaMqaMqaUfaQIaOFaJMaGXaabafOaMraMraabaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaarVapxdgKdgLarharharWasadhbarhaqoaZPaSdaSdaMBaSfaMDaMEaSiaSjaQZaSaaSjaSeaScaSjaSjaSnaGnaHSaGnaSoaHSaHSaSpaSqaGnaNIaKgaHSaHSaHSaHSaHSaHSaPcaSsaHSaGnaNxaNxaGnaSraMAaSuaSwaSvaSvaSvaSxaSvaSvaSvaSyaSuaSzaSAaSAaSAaSAaSAaSAaSAaPiaRwaSBaSCaSEaSgaSkaShaSmaSlaSJaSDaSLaSCaSMaRwaSNaFFaSOaEjaMFaLPaSRaMGaSTaSSaOqaWHaOyaOraOraPkaPBbhiaPCbgsbhhbhibhibhibhibhibhjaMVaMVaMVaMVaThaODaKQaKQaKQaKQaKQaKOaKOaKOaKOaHbaMzaMzaIEaGXaGXbBOaMzaGXaTgaMzaGXaIEaGXaMkaQIaMzaGXaIEaGXaKXaUUaMkaGYaTiaGXaHOaHbaGYaGYaVTaVhaVUaVUaWZaGYaGYaGXaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaardapxarharharharWdhedhgaqldhhapxaabaabaMsaSdaRUaSdaToaSfaSfaTpaSdaRUaSdaTqaTsaTraTtaGnaHSaPcaPcaGnaGnaGnaTuaTuaTuaIfaTuaTuaTuaTuaTuaHSaGnaGnaGnaGnaGnaGnaGnaSraKbaStaaaaabaaaaabaaaaabaaaaabaaaaSuaSzaSAaTwaTvaTyaTxaTAaSAaPiaRwaTzaTzaTCaTBaTEaTDaTFaTDaMKaTGaTIaTzaTzaRwatcaFFaTJaEjaMFaLPaOnaKpaLNaLPaPDbhkbhtbhpaPFaPEaQAbhzbitbisbiTbiubjkbjcbjXaYvbkKbkIaQEaOvaOwaKOaODaKQaKQaKQaKQaKQaKOaTXaUaaMzaGXaMzaLcaGYaGXaLcbBOaGXaTZaGYaGXaUbaGYaMzaUdaUcaGXaUeaGYaMzaIEaIEaMkaGYaXbaXaaXaaXaaXaaXaaXdaGYaGYaGYaXeaGYaGYaIFaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaarVapxdhidhmdhlarharhaqiarharYapxaabaLdaLdaSdaOQaSdaUlaUkaUnaUmaSdaOQaSdaSdaLdaUoaUpaGnaHSaHSaHSaHSaHSaHSaTuaUuaUvaUqaUxaUsaUzaUuaTuaUwaUAaUyaUyaUyaUyaUyaUyaUBaNxaStaabaUEaUEaUEaUEaUEaUEaUEaabaSuaSzaSAaUFaUCaUHaUDaUFaSAaPiaRwaUJaUJaUJaUGaULaSCaUIaSCaULaUKaUJaUJaUJaRwaSNaFFaUMaEjaMFaLPaOjaKpaLNaLPaQFbuMaUQaUQaUQaUQaUQaUQaUQaNZaUQaUSaUSaUSaUSaUSaOaaGXaPGaPIaPIbJUaODaKQaKQaKQaKQaKQaKOaURaUaaMzaUTaGXaGYaGYaGXbJVbLCaMzaGYaGYaGXaGYaGYaMzaMkaAYaGXaGYaGYaMzaUWaGYaMkaGYaXgaQLbazbazbazbazbazaYPaYPaYPaQDaYPaYPaYPaYRaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
diff --git a/code/__DEFINES/contracts.dm b/code/__DEFINES/contracts.dm
new file mode 100644
index 00000000000..bdf49c1ad9e
--- /dev/null
+++ b/code/__DEFINES/contracts.dm
@@ -0,0 +1,57 @@
+#define CONTRACT_POWER "Power"
+#define CONTRACT_WEALTH "Wealth"
+#define CONTRACT_PRESTIGE "Prestige"
+#define CONTRACT_MAGIC "Magic"
+#define CONTRACT_REVIVE "Revive"
+#define CONTRACT_KNOWLEDGE "Knowledge"
+#define CONTRACT_UNWILLING "Unwilling"
+// FIXME: Implement these
+#define CONTRACT_ETALENT "Engineering Talent"
+#define CONTRACT_CTALENT "Chemistry Talent"
+#define CONTRACT_RETURNDEAD "Return Dead"
+#define CONTRACT_AUGMENT "Cybernetic Augmentations"
+#define CONTRACT_CANDY "Endless Candy"
+#define CONTRACT_ECHANCE "An Extra Chance"
+#define CONTRACT_ATECH "Advanced Technology"
+#define CONTRACT_DEVILSMACHINE "Devil's Machinery"
+#define CONTRACT_YOUTH "Eternal Youth"
+#define CONTRACT_FOOD "Food"
+#define CONTRACT_SPACE "Space Gear"
+#define CONTRACT_CALAMITY "Calamity"
+
+#define BANE_SALT "salt"
+#define BANE_LIGHT "light"
+#define BANE_IRON "iron"
+#define BANE_WHITECLOTHES "whiteclothes"
+#define BANE_SILVER "silver"
+#define BANE_HARVEST "harvest"
+#define BANE_TOOLBOX "toolbox"
+
+#define OBLIGATION_FOOD "food"
+#define OBLIGATION_FIDDLE "fiddle"
+#define OBLIGATION_DANCEOFF "danceoff"
+#define OBLIGATION_GREET "greet"
+#define OBLIGATION_PRESENCEKNOWN "presenceknown"
+#define OBLIGATION_SAYNAME "sayname"
+#define OBLIGATION_ANNOUNCEKILL "announcekill"
+#define OBLIGATION_ANSWERTONAME "answername"
+
+#define BAN_HURTWOMAN "hurtwoman"
+#define BAN_HURTMAN "hurtman"
+#define BAN_CHAPEL "chapel"
+#define BAN_HURTPRIEST "hurtpriest"
+#define BAN_AVOIDWATER "avoidwater"
+#define BAN_STRIKEUNCONCIOUS "strikeunconcious"
+#define BAN_HURTLIZARD "hurtlizard"
+#define BAN_HURTANIMAL "hurtanimal"
+
+#define BANISH_WATER "water"
+#define BANISH_COFFIN "coffin"
+#define BANISH_FORMALDYHIDE "embalm"
+#define BANISH_RUNES "runes"
+#define BANISH_CANDLES "candles"
+#define BANISH_DESTRUCTION "destruction"
+#define BANISH_FUNERAL_GARB "funeral"
+
+#define LORE 1
+#define LAW 2
diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm
index 60252fc11ba..d988fe3a011 100644
--- a/code/__DEFINES/hud.dm
+++ b/code/__DEFINES/hud.dm
@@ -56,8 +56,9 @@
#define ANTAG_HUD_VAMPIRE 17
#define ANTAG_HUD_ABDUCTOR 18
#define DATA_HUD_ABDUCTOR 19
+#define ANTAG_HUD_DEVIL 20
// Notification action types
#define NOTIFY_JUMP "jump"
#define NOTIFY_ATTACK "attack"
-#define NOTIFY_FOLLOW "orbit"
\ No newline at end of file
+#define NOTIFY_FOLLOW "orbit"
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 534e53ccf0d..0f05c08b2f7 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -143,6 +143,7 @@
#define STATUS_UPDATE_NONE 0
#define STATUS_UPDATE_ALL (~0)
+#define INVISIBILITY_ABSTRACT 101
#define UNHEALING_EAR_DAMAGE 100
//Human sub-species
diff --git a/code/__DEFINES/preferences.dm b/code/__DEFINES/preferences.dm
index f465317db9c..4043f5ddae4 100644
--- a/code/__DEFINES/preferences.dm
+++ b/code/__DEFINES/preferences.dm
@@ -40,4 +40,13 @@
#define EXP_TYPE_CREW "Crew"
#define EXP_TYPE_SPECIAL "Special"
#define EXP_TYPE_GHOST "Ghost"
-#define EXP_TYPE_EXEMPT "Exempt"
\ No newline at end of file
+#define EXP_TYPE_EXEMPT "Exempt"
+#define EXP_TYPE_COMMAND "Command"
+#define EXP_TYPE_ENGINEERING "Engineering"
+#define EXP_TYPE_MEDICAL "Medical"
+#define EXP_TYPE_SCIENCE "Science"
+#define EXP_TYPE_SUPPLY "Supply"
+#define EXP_TYPE_SECURITY "Security"
+#define EXP_TYPE_SILICON "Silicon"
+#define EXP_TYPE_SERVICE "Service"
+#define EXP_TYPE_WHITELIST "Whitelist"
\ No newline at end of file
diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm
index adb645fe112..3b2ce8aadc1 100644
--- a/code/__DEFINES/role_preferences.dm
+++ b/code/__DEFINES/role_preferences.dm
@@ -26,6 +26,7 @@
#define ROLE_REVENANT "revenant"
#define ROLE_HOG_GOD "hand of god: god" // We're prolly gonna port this one day or another
#define ROLE_HOG_CULTIST "hand of god: cultist"
+#define ROLE_DEVIL "devil"
#define ROLE_RAIDER "vox raider"
#define ROLE_TRADER "trader"
#define ROLE_VAMPIRE "vampire"
@@ -73,4 +74,5 @@ var/global/list/special_roles = list(
ROLE_MORPH,
ROLE_TRADER,
ROLE_GSPIDER,
+ ROLE_DEVIL = /datum/game_mode/devil/devil_agents,
)
diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm
index 92fd9d218da..f1e6f38a2d4 100644
--- a/code/__HELPERS/lists.dm
+++ b/code/__HELPERS/lists.dm
@@ -791,4 +791,4 @@ proc/dd_sortedObjectList(list/incoming)
while(start < end)
L.Swap(start++, end--)
- return L
\ No newline at end of file
+ return L
diff --git a/code/_globalvars/lists/devil.dm b/code/_globalvars/lists/devil.dm
new file mode 100644
index 00000000000..2c941069fe4
--- /dev/null
+++ b/code/_globalvars/lists/devil.dm
@@ -0,0 +1,8 @@
+//what could possibly go wrong
+var/list/devil_machines = typecacheof(/obj/item/circuitboard, TRUE) - list(
+ /obj/item/circuitboard/shuttle,
+ /obj/item/circuitboard/swfdoor,
+ /obj/item/circuitboard/olddoor,
+ /obj/item/circuitboard/computer,
+ /obj/item/circuitboard/machine
+ )
diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm
index 3a847412fcb..24a2442faeb 100644
--- a/code/_onclick/hud/_defines.dm
+++ b/code/_onclick/hud/_defines.dm
@@ -22,6 +22,7 @@
//Middle left indicators
#define ui_lingchemdisplay "WEST:6,CENTER-1:15"
#define ui_lingstingdisplay "WEST:6,CENTER-3:11"
+#define ui_devilsouldisplay "WEST:6,CENTER-1:15"
//Lower center, persistant menu
#define ui_sstore1 "CENTER-5:10,SOUTH:5"
diff --git a/code/_onclick/hud/alien.dm b/code/_onclick/hud/alien.dm
index ff8b856e797..102e38cba77 100644
--- a/code/_onclick/hud/alien.dm
+++ b/code/_onclick/hud/alien.dm
@@ -136,7 +136,7 @@
inv_slots[inv.slot_id] = inv
inv.update_icon()
-/datum/hud/alien/persistant_inventory_update()
+/datum/hud/alien/persistent_inventory_update()
if(!mymob)
return
var/mob/living/carbon/alien/humanoid/H = mymob
diff --git a/code/_onclick/hud/devil.dm b/code/_onclick/hud/devil.dm
new file mode 100644
index 00000000000..2a7dab21a03
--- /dev/null
+++ b/code/_onclick/hud/devil.dm
@@ -0,0 +1,89 @@
+
+//Soul counter is stored with the humans, it does weird when you place it here apparently...
+
+
+/datum/hud/devil/New(mob/owner, ui_style = 'icons/mob/screen_midnight.dmi')
+ ..()
+
+ var/obj/screen/using
+ var/obj/screen/inventory/inv_box
+
+ using = new /obj/screen/drop()
+ using.icon = ui_style
+ using.screen_loc = ui_drop_throw
+ static_inventory += using
+
+ mymob.pullin = new /obj/screen/pull()
+ mymob.pullin.icon = ui_style
+ mymob.pullin.update_icon(mymob)
+ mymob.pullin.screen_loc = ui_pull_resist
+ static_inventory += mymob.pullin
+
+ inv_box = new /obj/screen/inventory/hand()
+ inv_box.name = "right hand"
+ inv_box.icon = ui_style
+ inv_box.icon_state = "hand_r"
+ inv_box.screen_loc = ui_rhand
+ inv_box.slot_id = slot_r_hand
+ static_inventory += inv_box
+
+ inv_box = new /obj/screen/inventory/hand()
+ inv_box.name = "left hand"
+ inv_box.icon = ui_style
+ inv_box.icon_state = "hand_l"
+ inv_box.screen_loc = ui_lhand
+ inv_box.slot_id = slot_l_hand
+ static_inventory += inv_box
+
+ using = new /obj/screen/swap_hand()
+ using.name = "hand"
+ using.icon = ui_style
+ using.icon_state = "swap_1"
+ using.screen_loc = ui_swaphand1
+ using.layer = 19
+ static_inventory += using
+
+ using = new /obj/screen/swap_hand()
+ using.name = "hand"
+ using.icon = ui_style
+ using.icon_state = "swap_2"
+ using.screen_loc = ui_swaphand2
+ using.layer = 19
+ static_inventory += using
+
+ mymob.zone_sel = new /obj/screen/zone_sel()
+ mymob.zone_sel.icon = ui_style
+ mymob.zone_sel.update_icon(mymob)
+
+ lingchemdisplay = new /obj/screen/ling/chems()
+ devilsouldisplay = new /obj/screen/devil/soul_counter
+ infodisplay += devilsouldisplay
+
+ for(var/obj/screen/inventory/inv in static_inventory)
+ if(inv.slot_id)
+ inv.hud = src
+ inv_slots[inv.slot_id] = inv
+ inv.update_icon()
+
+
+/datum/hud/devil/persistent_inventory_update()
+ if(!mymob)
+ return
+ var/mob/living/carbon/true_devil/D = mymob
+
+ if(hud_version != HUD_STYLE_NOHUD)
+ if(D.r_hand)
+ D.r_hand.screen_loc = ui_rhand
+ D.client.screen += D.r_hand
+ if(D.l_hand)
+ D.l_hand.screen_loc = ui_lhand
+ D.client.screen += D.l_hand
+ else
+ if(D.r_hand)
+ D.r_hand.screen_loc = null
+ if(D.l_hand)
+ D.l_hand.screen_loc = null
+
+/mob/living/carbon/true_devil/create_mob_hud()
+ if(client && !hud_used)
+ hud_used = new /datum/hud/devil(src, ui_style2icon(client.prefs.UI_style))
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index 10de5275352..410d49f973e 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -27,6 +27,8 @@
var/obj/screen/move_intent
var/obj/screen/module_store_icon
+ var/obj/screen/devil/soul_counter/devilsouldisplay
+
var/list/static_inventory = list() //the screen objects which are static
var/list/toggleable_inventory = list() //the screen objects which can be hidden
var/list/hotkeybuttons = list() //the buttons that can be used via hotkeys
@@ -79,6 +81,7 @@
alien_plasma_display = null
vampire_blood_display = null
nightvisionicon = null
+ devilsouldisplay = null
mymob = null
return ..()
@@ -155,7 +158,7 @@
mymob.client.screen -= infodisplay
hud_version = display_hud_version
- persistant_inventory_update()
+ persistent_inventory_update()
mymob.update_action_buttons(1)
reorganize_alerts()
reload_fullscreen()
@@ -171,7 +174,7 @@
/datum/hud/proc/hidden_inventory_update()
return
-/datum/hud/proc/persistant_inventory_update()
+/datum/hud/proc/persistent_inventory_update()
return
//Triggered when F12 is pressed (Unless someone changed something in the DMF)
diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm
index ecd7cb3de52..6f5c8ee0548 100644
--- a/code/_onclick/hud/human.dm
+++ b/code/_onclick/hud/human.dm
@@ -36,6 +36,35 @@
var/mob/living/carbon/U = usr
U.unset_sting()
+/obj/screen/devil
+ invisibility = INVISIBILITY_ABSTRACT
+
+/obj/screen/devil/soul_counter
+ icon = 'icons/mob/screen_gen.dmi'
+ name = "souls owned"
+ icon_state = "Devil-6"
+ screen_loc = ui_devilsouldisplay
+
+/obj/screen/devil/soul_counter/proc/update_counter(souls = 0)
+ invisibility = 0
+ maptext = "
[souls]
"
+ switch(souls)
+ if(0,null)
+ icon_state = "Devil-1"
+ if(1,2)
+ icon_state = "Devil-2"
+ if(3 to 5)
+ icon_state = "Devil-3"
+ if(6 to 8)
+ icon_state = "Devil-4"
+ if(9 to INFINITY)
+ icon_state = "Devil-5"
+ else
+ icon_state = "Devil-6"
+
+/obj/screen/devil/soul_counter/proc/clear()
+ invisibility = INVISIBILITY_ABSTRACT
+
/obj/screen/ling/chems
name = "chemical storage"
icon_state = "power_display"
@@ -337,6 +366,9 @@
lingstingdisplay = new /obj/screen/ling/sting()
infodisplay += lingstingdisplay
+ devilsouldisplay = new /obj/screen/devil/soul_counter
+ infodisplay += devilsouldisplay
+
mymob.zone_sel = new /obj/screen/zone_sel()
mymob.zone_sel.icon = ui_style
mymob.zone_sel.update_icon(mymob)
@@ -393,7 +425,7 @@
if(H.wear_mask) H.wear_mask.screen_loc = null
if(H.head) H.head.screen_loc = null
-/datum/hud/human/persistant_inventory_update()
+/datum/hud/human/persistent_inventory_update()
if(!mymob)
return
var/mob/living/carbon/human/H = mymob
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index e36d5c027f5..d69c7fbf28f 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -70,7 +70,7 @@
M.lastattacker = user
user.do_attack_animation(M)
- M.attacked_by(src, user, def_zone)
+ . = M.attacked_by(src, user, def_zone)
add_attack_logs(user, M, "Attacked with [name] (INTENT: [uppertext(user.a_intent)]) (DAMTYPE: [uppertext(damtype)])", (M.ckey && force > 0 && damtype != STAMINA) ? null : ATKLOG_ALMOSTALL)
diff --git a/code/datums/ai_laws.dm b/code/datums/ai_laws.dm
index 0f796798d26..3494d7b20d5 100644
--- a/code/datums/ai_laws.dm
+++ b/code/datums/ai_laws.dm
@@ -17,6 +17,10 @@ var/global/const/base_law_type = /datum/ai_laws/nanotrasen
/datum/ai_law/zero/get_index()
return 0
+/datum/ai_law/sixsixsix/get_index()
+ return 666
+
+
/datum/ai_laws
var/name = "Unknown Laws"
var/law_header = "Prime Directives"
@@ -27,9 +31,11 @@ var/global/const/base_law_type = /datum/ai_laws/nanotrasen
var/list/datum/ai_law/inherent_laws = list()
var/list/datum/ai_law/supplied_laws = list()
var/list/datum/ai_law/ion/ion_laws = list()
+ var/list/datum/ai_law/sixsixsix/devil_laws = list()
var/list/datum/ai_law/sorted_laws = list()
var/state_zeroth = 0
+ var/list/state_devil = list()
var/list/state_ion = list()
var/list/state_inherent = list()
var/list/state_supplied = list()
@@ -58,6 +64,9 @@ var/global/const/base_law_type = /datum/ai_laws/nanotrasen
for(var/ion_law in ion_laws)
sorted_laws += ion_law
+
+ for(var/evil_law in devil_laws)
+ sorted_laws += evil_law
if(zeroth_law)
sorted_laws += zeroth_law
@@ -117,6 +126,21 @@ var/global/const/base_law_type = /datum/ai_laws/nanotrasen
zeroth_law_borg = null
sorted_laws.Cut()
+/datum/ai_laws/proc/set_sixsixsix_law(var/law)
+ if(!law)
+ return
+
+ for(var/datum/ai_law/AL in devil_laws)
+ if(AL.law == law)
+ return
+
+ var/new_law = new/datum/ai_law/sixsixsix(law)
+ devil_laws += new_law
+ if(state_devil.len < devil_laws.len)
+ state_devil += 1
+
+ sorted_laws.Cut()
+
/datum/ai_laws/proc/add_ion_law(var/law)
if(!law)
return
@@ -185,6 +209,9 @@ var/global/const/base_law_type = /datum/ai_laws/nanotrasen
/datum/ai_law/ion/delete_law(var/datum/ai_laws/laws)
laws.internal_delete_law(laws.ion_laws, laws.state_ion, src)
+
+/datum/ai_law/sixsixsix/delete_law(var/datum/ai_laws/laws)
+ laws.internal_delete_law(laws.devil_laws, laws.state_devil, src)
/datum/ai_law/inherent/delete_law(var/datum/ai_laws/laws)
laws.internal_delete_law(laws.inherent_laws, laws.state_inherent, src)
@@ -210,6 +237,10 @@ var/global/const/base_law_type = /datum/ai_laws/nanotrasen
zeroth_law = null
zeroth_law_borg = null
+/datum/ai_laws/proc/clear_sixsixsix_laws()
+ devil_laws.Cut()
+ sorted_laws.Cut()
+
/datum/ai_laws/proc/clear_ion_laws()
ion_laws.Cut()
sorted_laws.Cut()
diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm
index 39438f87e64..61ced31e26d 100644
--- a/code/datums/datacore.dm
+++ b/code/datums/datacore.dm
@@ -339,6 +339,7 @@ var/record_id_num = 1001
L.fields["enzymes"] = H.dna.SE // Used in respawning
L.fields["identity"] = H.dna.UI // "
L.fields["image"] = getFlatIcon(H) //This is god-awful
+ L.fields["reference"] = H
locked += L
return
diff --git a/code/datums/hud.dm b/code/datums/hud.dm
index 84c45299ece..9da1581ee1f 100644
--- a/code/datums/hud.dm
+++ b/code/datums/hud.dm
@@ -21,7 +21,8 @@ var/datum/atom_hud/huds = list( \
ANTAG_HUD_CHANGELING = new/datum/atom_hud/antag/hidden(),\
ANTAG_HUD_VAMPIRE = new/datum/atom_hud/antag/hidden(),\
ANTAG_HUD_ABDUCTOR = new/datum/atom_hud/antag/hidden(),\
- DATA_HUD_ABDUCTOR = new/datum/atom_hud/abductor()\
+ DATA_HUD_ABDUCTOR = new/datum/atom_hud/abductor(),\
+ ANTAG_HUD_DEVIL = new/datum/atom_hud/antag/hidden()\
)
/datum/atom_hud
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 18f229bdc4c..0e826d2da17 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -51,10 +51,16 @@
var/linglink
var/datum/vampire/vampire //vampire holder
var/datum/nations/nation //nation holder
+ var/datum/abductor/abductor //abductor holder
+ var/datum/devilinfo/devilinfo //devil holder
var/antag_hud_icon_state = null //this mind's ANTAG_HUD should have this icon_state
var/datum/atom_hud/antag/antag_hud = null //this mind's antag HUD
var/datum/mindslaves/som //stands for slave or master...hush..
+ var/datum/devilinfo/devilinfo //Information about the devil, if any.
+ var/damnation_type = 0
+ var/datum/mind/soulOwner //who owns the soul. Under normal circumstances, this will point to src
+ var/hasSoul = TRUE
var/rev_cooldown = 0
@@ -74,6 +80,7 @@
/datum/mind/New(new_key)
key = new_key
+ soulOwner = src
/datum/mind/Destroy()
ticker.minds -= src
@@ -291,6 +298,22 @@
. += _memory_edit_role_enabled(ROLE_ABDUCTOR)
+/datum/mind/proc/memory_edit_devil(mob/living/H)
+ . = _memory_edit_header("devil", list("devilagents"))
+ if(src in ticker.mode.devils)
+ if(!devilinfo)
+ . += "No devilinfo found! Yell at a coder!"
+ else if(!devilinfo.ascendable)
+ . += "DEVIL|Ascendable Devil|sintouched|no"
+ else
+ . += "DEVIL|ASCENDABLE DEVIL|sintouched|no"
+ else if(src in ticker.mode.sintouched)
+ . += "devil|Ascendable Devil|SINTOUCHED|no"
+ else
+ . += "devil|Ascendable Devil|sintouched|NO"
+
+ . += _memory_edit_role_enabled(ROLE_DEVIL)
+
/datum/mind/proc/memory_edit_traitor()
. = _memory_edit_header("traitor", list("traitorchan", "traitorvamp"))
if(src in ticker.mode.traitors)
@@ -374,6 +397,10 @@
sections["shadowling"] = memory_edit_shadowling(H)
/** Abductors **/
sections["abductor"] = memory_edit_abductor(H)
+ /** DEVIL ***/
+ var/static/list/devils_typecache = typecacheof(list(/mob/living/carbon/human, /mob/living/carbon/true_devil, /mob/living/silicon/robot))
+ if(is_type_in_typecache(current, devils_typecache))
+ sections["devil"] = memory_edit_devil(H)
/** TRAITOR ***/
sections["traitor"] = memory_edit_traitor()
/** SILICON ***/
@@ -1003,6 +1030,77 @@
else
to_chat(usr, "No valid nuke found!")
+ else if(href_list["devil"])
+ switch(href_list["devil"])
+ if("clear")
+ if(src in ticker.mode.devils)
+ if(istype(current,/mob/living/carbon/true_devil/))
+ to_chat(usr,"This cannot be used on true or arch-devils.")
+ else
+ ticker.mode.devils -= src
+ ticker.mode.update_devil_icons_removed(src)
+ special_role = null
+ to_chat(current,"Your infernal link has been severed! You are no longer a devil!")
+ RemoveSpell(/obj/effect/proc_holder/spell/targeted/infernal_jaunt)
+ RemoveSpell(/obj/effect/proc_holder/spell/fireball/hellish)
+ RemoveSpell(/obj/effect/proc_holder/spell/targeted/summon_contract)
+ RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork)
+ RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/greater)
+ RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/ascended)
+ RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/violin)
+ RemoveSpell(/obj/effect/proc_holder/spell/targeted/summon_dancefloor)
+ RemoveSpell(/obj/effect/proc_holder/spell/targeted/sintouch)
+ RemoveSpell(/obj/effect/proc_holder/spell/targeted/sintouch/ascended)
+ message_admins("[key_name_admin(usr)] has de-devil'ed [current].")
+ if(issilicon(current))
+ var/mob/living/silicon/S = current
+ S.laws.clear_sixsixsix_laws()
+ devilinfo = null
+ log_admin("[key_name(usr)] has de-devil'ed [current].")
+ else if(src in ticker.mode.sintouched)
+ ticker.mode.sintouched -= src
+ message_admins("[key_name_admin(usr)] has de-sintouch'ed [current].")
+ log_admin("[key_name(usr)] has de-sintouch'ed [current].")
+ if("devil")
+ if(devilinfo)
+ devilinfo.ascendable = FALSE
+ message_admins("[key_name_admin(usr)] has made [current] unable to ascend as a devil.")
+ log_admin("[key_name_admin(usr)] has made [current] unable to ascend as a devil.")
+ return
+ if(!ishuman(current) && !isrobot(current))
+ to_chat(usr, "This only works on humans and cyborgs!")
+ return
+ ticker.mode.devils += src
+ special_role = "devil"
+ ticker.mode.update_devil_icons_added(src)
+ ticker.mode.finalize_devil(src, FALSE)
+ ticker.mode.forge_devil_objectives(src, 2)
+ ticker.mode.greet_devil(src)
+ message_admins("[key_name_admin(usr)] has devil'ed [current].")
+ log_admin("[key_name(usr)] has devil'ed [current].")
+ if("ascendable_devil")
+ if(devilinfo)
+ devilinfo.ascendable = TRUE
+ message_admins("[key_name_admin(usr)] has made [current] able to ascend as a devil.")
+ log_admin("[key_name_admin(usr)] has made [current] able to ascend as a devil.")
+ return
+ if(!ishuman(current) && !isrobot(current))
+ to_chat(usr, "This only works on humans and cyborgs!")
+ return
+ ticker.mode.devils += src
+ special_role = "devil"
+ ticker.mode.update_devil_icons_added(src)
+ ticker.mode.finalize_devil(src, TRUE)
+ ticker.mode.forge_devil_objectives(src, 2)
+ ticker.mode.greet_devil(src)
+ message_admins("[key_name_admin(usr)] has devil'ed [current]. The devil has been marked as ascendable.")
+ log_admin("[key_name(usr)] has devil'ed [current]. The devil has been marked as ascendable.")
+ if("sintouched")
+ var/mob/living/carbon/human/H = current
+ H.influenceSin()
+ message_admins("[key_name_admin(usr)] has sintouch'ed [current].")
+ log_admin("[key_name(usr)] has sintouch'ed [current].")
+
else if(href_list["traitor"])
switch(href_list["traitor"])
if("clear")
@@ -1455,6 +1553,17 @@
var/obj/effect/proc_holder/spell/S = X
S.action.Grant(new_character)
+/datum/mind/proc/disrupt_spells(delay, list/exceptions = New())
+ for(var/X in spell_list)
+ var/obj/effect/proc_holder/spell/S = X
+ for(var/type in exceptions)
+ if(istype(S, type))
+ continue
+ S.charge_counter = delay
+ spawn(0)
+ S.start_recharge()
+ S.updateButtonIcon()
+
/datum/mind/proc/get_ghost(even_if_they_cant_reenter)
for(var/mob/dead/observer/G in GLOB.dead_mob_list)
if(G.mind == src)
@@ -1533,6 +1642,18 @@
to_chat(current, "You seem to have forgotten the events of the past 10 minutes or so, and your head aches a bit as if someone beat it savagely with a stick.")
to_chat(current, "This means you don't remember who you were working for or what you were doing.")
+/datum/mind/proc/is_revivable() //Note, this ONLY checks the mind.
+ if(damnation_type)
+ return FALSE
+ return TRUE
+
+// returns a mob to message to produce something visible for the target mind
+/datum/mind/proc/messageable_mob()
+ if(!QDELETED(current) && current.client)
+ return current
+ else
+ return get_ghost(even_if_they_cant_reenter = TRUE)
+
//Initialisation procs
/mob/proc/mind_initialize()
if(mind)
diff --git a/code/datums/outfits/outfit_admin.dm b/code/datums/outfits/outfit_admin.dm
index 0f650e2bd0d..5dcf5d0b247 100644
--- a/code/datums/outfits/outfit_admin.dm
+++ b/code/datums/outfits/outfit_admin.dm
@@ -708,7 +708,8 @@
id = /obj/item/card/id/supply
pda = /obj/item/pda
backpack_contents = list(
- /obj/item/storage/box/survival = 1
+ /obj/item/storage/box/survival = 1,
+ /obj/item/hand_labeler = 1
)
/datum/outfit/admin/sol_trader/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
diff --git a/code/datums/soullink.dm b/code/datums/soullink.dm
index 1f17c635e84..a48d63191c1 100644
--- a/code/datums/soullink.dm
+++ b/code/datums/soullink.dm
@@ -122,6 +122,7 @@
if(soulsharer)
soulsharer.dust(FALSE)
+/datum/soullink/oneway/devilfriend
/////////////////
// SHARED BODY //
diff --git a/code/datums/spell.dm b/code/datums/spell.dm
index 3c23a0c7f20..813604a1431 100644
--- a/code/datums/spell.dm
+++ b/code/datums/spell.dm
@@ -294,6 +294,10 @@ var/list/spells = typesof(/obj/effect/proc_holder/spell) //needed for the badmin
return
+/obj/effect/proc_holder/spell/proc/updateButtonIcon()
+ if(action)
+ action.UpdateButtonIcon()
+
/obj/effect/proc_holder/spell/proc/adjust_var(mob/living/target = usr, type, amount) //handles the adjustment of the var when the spell is used. has some hardcoded types
switch(type)
if("bruteloss")
diff --git a/code/datums/spells/conjure_item.dm b/code/datums/spells/conjure_item.dm
new file mode 100644
index 00000000000..e7d530c1c61
--- /dev/null
+++ b/code/datums/spells/conjure_item.dm
@@ -0,0 +1,25 @@
+/obj/effect/proc_holder/spell/targeted/conjure_item
+ name = "Summon weapon"
+ desc = "A generic spell that should not exist. This summons an instance of a specific type of item, or if one already exists, un-summons it."
+ invocation_type = "none"
+ include_user = 1
+ range = -1
+ clothes_req = FALSE
+ var/obj/item/item
+ var/item_type = /obj/item/banhammer
+ school = "conjuration"
+ charge_max = 150
+ cooldown_min = 10
+
+/obj/effect/proc_holder/spell/targeted/conjure_item/cast(list/targets, mob/user = usr)
+ if(item)
+ QDEL_NULL(item)
+ else
+ for(var/mob/living/carbon/C in targets)
+ if(C.drop_item())
+ item = new item_type
+ C.put_in_hands(item)
+
+/obj/effect/proc_holder/spell/targeted/conjure_item/Destroy()
+ QDEL_NULL(item)
+ return ..()
diff --git a/code/datums/spells/devil.dm b/code/datums/spells/devil.dm
new file mode 100644
index 00000000000..c0b19ed6f79
--- /dev/null
+++ b/code/datums/spells/devil.dm
@@ -0,0 +1,249 @@
+/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork
+ name = "Summon Pitchfork"
+ desc = "A devil's weapon of choice. Use this to summon/unsummon your pitchfork."
+ item_type = /obj/item/twohanded/pitchfork/demonic
+ action_icon_state = "pitchfork"
+ action_background_icon_state = "bg_demon"
+
+/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/greater
+ item_type = /obj/item/twohanded/pitchfork/demonic/greater
+
+/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/ascended
+ item_type = /obj/item/twohanded/pitchfork/demonic/ascended
+
+/obj/effect/proc_holder/spell/targeted/conjure_item/violin
+ item_type = /obj/item/instrument/violin/golden
+ desc = "A devil's instrument of choice. Use this to summon/unsummon your golden violin."
+ invocation_type = "whisper"
+ invocation = "I ain't have this much fun since Georgia."
+ action_icon_state = "golden_violin"
+ name = "Summon golden violin"
+ action_background_icon_state = "bg_demon"
+
+
+/obj/effect/proc_holder/spell/targeted/summon_contract
+ name = "Summon infernal contract"
+ desc = "Skip making a contract by hand, just do it by magic."
+ invocation_type = "whisper"
+ invocation = "Just sign on the dotted line."
+ include_user = 0
+ range = 5
+ clothes_req = FALSE
+ school = "conjuration"
+ charge_max = 150
+ cooldown_min = 10
+ action_icon_state = "spell_default"
+ action_background_icon_state = "bg_demon"
+
+/obj/effect/proc_holder/spell/targeted/summon_contract/cast(list/targets, mob/user = usr)
+ for(var/mob/living/carbon/C in targets)
+ if(C.mind && user.mind)
+ if(C.stat == DEAD)
+ if(user.drop_item())
+ var/obj/item/paper/contract/infernal/revive/contract = new(user.loc, C.mind, user.mind)
+ user.put_in_hands(contract)
+ else
+ var/obj/item/paper/contract/infernal/contract
+ var/contractTypeName = input(user, "What type of contract?") in list (CONTRACT_POWER, CONTRACT_WEALTH, CONTRACT_PRESTIGE, CONTRACT_MAGIC, CONTRACT_KNOWLEDGE)
+ switch(contractTypeName)
+ if(CONTRACT_POWER)
+ contract = new /obj/item/paper/contract/infernal/power(C.loc, C.mind, user.mind)
+ if(CONTRACT_WEALTH)
+ contract = new /obj/item/paper/contract/infernal/wealth(C.loc, C.mind, user.mind)
+ if(CONTRACT_PRESTIGE)
+ contract = new /obj/item/paper/contract/infernal/prestige(C.loc, C.mind, user.mind)
+ if(CONTRACT_MAGIC)
+ contract = new /obj/item/paper/contract/infernal/magic(C.loc, C.mind, user.mind)
+ if(CONTRACT_KNOWLEDGE)
+ contract = new /obj/item/paper/contract/infernal/knowledge(C.loc, C.mind, user.mind)
+ C.put_in_hands(contract)
+ else
+ to_chat(user,"[C] seems to not be sentient. You are unable to summon a contract for them.")
+
+
+/obj/effect/proc_holder/spell/fireball/hellish
+ name = "Hellfire"
+ desc = "This spell launches hellfire at the target."
+ school = "evocation"
+ charge_max = 80
+ clothes_req = FALSE
+ invocation = "Your very soul will catch fire!"
+ invocation_type = "shout"
+ fireball_type = /obj/item/projectile/magic/fireball/infernal
+ action_background_icon_state = "bg_demon"
+
+/obj/effect/proc_holder/spell/fireball/hellish/cast(list/targets, mob/living/user = usr)
+ msg_admin_attack("[key_name_admin(usr)] has fired a fireball.", ATKLOG_FEW)
+ .=..()
+
+
+/obj/effect/proc_holder/spell/targeted/infernal_jaunt
+ name = "Infernal Jaunt"
+ desc = "Use hellfire to phase out of existence."
+ charge_max = 200
+ clothes_req = FALSE
+ selection_type = "range"
+ range = -1
+ cooldown_min = 0
+ overlay = null
+ include_user = TRUE
+ action_icon_state = "jaunt"
+ action_background_icon_state = "bg_demon"
+
+/obj/effect/proc_holder/spell/targeted/infernal_jaunt/cast(list/targets, mob/living/user = usr)
+ if(istype(user))
+ if(istype(user.loc, /obj/effect/dummy/slaughter))
+ var/continuing = 0
+ if(istype(get_area(user), /area/shuttle)) // Can always phase in in a shuttle.
+ continuing = TRUE
+ else
+ for(var/mob/living/C in orange(2, get_turf(user.loc))) //Can also phase in when nearby a potential buyer.
+ if (C.mind && C.mind.soulOwner == C.mind)
+ continuing = TRUE
+ break
+ if(continuing)
+ to_chat(user,"You are now phasing in.")
+ if(do_mob(user,user,150))
+ user.infernalphasein()
+ else
+ to_chat(user,"You can only re-appear near a potential signer or on a shuttle.")
+ revert_cast()
+ return ..()
+ else
+ user.notransform = TRUE
+ user.fakefire()
+ to_chat(user,"You begin to phase back into sinful flames.")
+ if(do_mob(user,user,150))
+ user.infernalphaseout()
+ else
+ to_chat(user,"You must remain still while exiting.")
+ user.ExtinguishMob()
+ start_recharge()
+ return
+ revert_cast()
+
+
+/mob/living/proc/infernalphaseout()
+ dust_animation()
+ visible_message("[src] disappears in a flashfire!")
+ playsound(get_turf(src), 'sound/misc/enter_blood.ogg', 100, 1, -1)
+ var/obj/effect/dummy/slaughter/s_holder = new(loc)
+ ExtinguishMob()
+ if(buckled)
+ buckled.unbuckle_mob(src,force=1)
+ if(has_buckled_mobs())
+ unbuckle_mob()
+ if(pulledby)
+ pulledby.stop_pulling()
+ if(pulling)
+ stop_pulling()
+ forceMove(s_holder)
+ holder = s_holder
+ notransform = FALSE
+ fakefireextinguish()
+
+/mob/living/proc/infernalphasein()
+ if(notransform)
+ to_chat(src,"You're too busy to jaunt in.")
+ return 0
+ fakefire()
+ forceMove(get_turf(src))
+ reset_perspective()
+ visible_message("[src] appears in a firey blaze!")
+ playsound(get_turf(src), 'sound/misc/exit_blood.ogg', 100, 1, -1)
+ spawn(15)
+ fakefireextinguish(TRUE)
+
+/obj/effect/proc_holder/spell/targeted/sintouch
+ name = "Sin Touch"
+ desc = "Subtly encourage someone to sin."
+ charge_max = 1800
+ clothes_req = FALSE
+ selection_type = "range"
+ range = 2
+ cooldown_min = 0
+ overlay = null
+ include_user = FALSE
+ action_icon_state = "sintouch"
+ action_background_icon_state = "bg_demon"
+ random_target = TRUE
+ random_target_priority = TARGET_RANDOM
+ max_targets = 3
+ invocation = "TASTE SIN AND INDULGE!!"
+ invocation_type = "shout"
+
+/obj/effect/proc_holder/spell/targeted/sintouch/ascended
+ name = "Greater sin touch"
+ charge_max = 100
+ range = 7
+ max_targets = 10
+
+/obj/effect/proc_holder/spell/targeted/sintouch/cast(list/targets, mob/living/user = usr)
+ for(var/mob/living/carbon/human/H in targets)
+ if(!H.mind)
+ continue
+ for(var/datum/objective/sintouched/A in H.mind.objectives)
+ continue
+ H.influenceSin()
+ H.Weaken(2)
+ H.Stun(2)
+
+/obj/effect/proc_holder/spell/targeted/summon_dancefloor
+ name = "Summon Dancefloor"
+ desc = "When what a Devil really needs is funk."
+ include_user = TRUE
+ range = -1
+ clothes_req = FALSE
+
+ school = "conjuration"
+ charge_max = 10
+ cooldown_min = 50 //5 seconds, so the smoke can't be spammed
+ action_icon_state = "funk"
+ action_background_icon_state = "bg_demon"
+
+ var/list/dancefloor_turfs
+ var/list/dancefloor_turfs_types
+ var/dancefloor_exists = FALSE
+// var/datum/effect_system/smoke_spread/transparent/dancefloor_devil/smoke
+
+
+/obj/effect/proc_holder/spell/targeted/summon_dancefloor/cast(list/targets, mob/user = usr)
+ LAZYINITLIST(dancefloor_turfs)
+ LAZYINITLIST(dancefloor_turfs_types)
+
+/*
+ if(!smoke)
+ smoke = new()
+ smoke.set_up(0, get_turf(user))
+ smoke.start()
+*/
+
+ if(dancefloor_exists)
+ dancefloor_exists = FALSE
+ for(var/i in 1 to dancefloor_turfs.len)
+ var/turf/T = dancefloor_turfs[i]
+ T.ChangeTurf(dancefloor_turfs_types[i])
+ else
+ var/list/funky_turfs = RANGE_TURFS(1, user)
+ for(var/turf/T in funky_turfs)
+ if(T.density)
+ to_chat(user, "You're too close to a wall.")
+ return
+ dancefloor_exists = TRUE
+ var/i = 1
+ dancefloor_turfs.len = funky_turfs.len
+ dancefloor_turfs_types.len = funky_turfs.len
+ for(var/t in funky_turfs)
+ var/turf/T = t
+ dancefloor_turfs[i] = T
+ dancefloor_turfs_types[i] = T.type
+ T.ChangeTurf((i % 2 == 0) ? /turf/simulated/floor/light/colour_cycle/dancefloor_a : /turf/simulated/floor/light/colour_cycle/dancefloor_b)
+ i++
+
+/*
+/datum/effect_system/smoke_spread/transparent/dancefloor_devil
+ effect_type = /obj/effect/particle_effect/smoke/transparent/dancefloor_devil
+
+/obj/effect/particle_effect/smoke/transparent/dancefloor_devil
+ lifetime = 2
+*/
diff --git a/code/datums/spells/devil_boons.dm b/code/datums/spells/devil_boons.dm
new file mode 100644
index 00000000000..5746fd9c2d2
--- /dev/null
+++ b/code/datums/spells/devil_boons.dm
@@ -0,0 +1,73 @@
+/obj/effect/proc_holder/spell/targeted/summon_wealth
+ name = "Summon wealth"
+ desc = "The reward for selling your soul."
+ invocation_type = "none"
+ include_user = 1
+ range = -1
+ clothes_req = 0
+ school = "conjuration"
+ charge_max = 100
+ cooldown_min = 10
+ action_icon_state = "moneybag"
+
+
+/obj/effect/proc_holder/spell/targeted/summon_wealth/cast(list/targets, mob/user = usr)
+ for(var/mob/living/carbon/C in targets)
+ if(user.drop_item())
+ var/obj/item = pick(
+ new /obj/item/coin/gold(user.loc),
+ new /obj/item/coin/diamond(user.loc),
+ new /obj/item/coin/silver(user.loc),
+ new /obj/item/stack/sheet/mineral/gold(user.loc),
+ new /obj/item/stack/sheet/mineral/silver(user.loc),
+ new /obj/item/stack/sheet/mineral/diamond(user.loc),
+ new /obj/item/stack/spacecash/c1000(user.loc))
+ C.put_in_hands(item)
+
+/obj/effect/proc_holder/spell/targeted/view_range
+ name = "Distant vision"
+ desc = "The reward for selling your soul."
+ invocation_type = "none"
+ include_user = 1
+ range = -1
+ clothes_req = 0
+ charge_max = 50
+ cooldown_min = 10
+ action_icon_state = "camera_jump"
+ var/ranges = list(7,8,9,10/*,11,12*/)
+
+/obj/effect/proc_holder/spell/targeted/view_range/cast(list/targets, mob/user = usr)
+ for(var/mob/C in targets)
+ if(!C.client)
+ continue
+ C.client.view = input("Select view range:", "Range", 4) in ranges
+
+/obj/effect/proc_holder/spell/targeted/summon_friend
+ name = "Summon Friend"
+ desc = "The reward for selling your soul."
+ invocation_type = "none"
+ include_user = 1
+ range = -1
+ clothes_req = 0
+ charge_max = 50
+ cooldown_min = 10
+ action_icon_state = "sacredflame"
+ var/mob/living/friend
+ var/obj/effect/mob_spawn/human/demonic_friend/friendShell
+
+/obj/effect/proc_holder/spell/targeted/summon_friend/cast(list/targets, mob/user = usr)
+ if(!QDELETED(friend))
+ to_chat(friend, "Your master has deemed you a poor friend. Your durance in hell will now resume.")
+ to_chat(user, "You banish your friend back to whence [friend.p_they()] came.")
+ friend.dust()
+ qdel(friendShell)
+ return
+ if(!QDELETED(friendShell))
+ qdel(friendShell)
+ return
+ for(var/C in targets)
+ var/mob/living/L = C
+ friendShell = new /obj/effect/mob_spawn/human/demonic_friend(L.loc, L.mind, src)
+
+/obj/effect/proc_holder/spell/targeted/conjure_item/spellpacket/robeless
+ clothes_req = FALSE
diff --git a/code/datums/spells/dumbfire.dm b/code/datums/spells/dumbfire.dm
index 27969c9a1ab..ca8cc43df9c 100644
--- a/code/datums/spells/dumbfire.dm
+++ b/code/datums/spells/dumbfire.dm
@@ -12,7 +12,7 @@
var/proj_trail_icon = 'icons/obj/wizard.dmi'
var/proj_trail_icon_state = "trail"
- var/proj_type = "/obj/effect/proc_holder/spell" //IMPORTANT use only subtypes of this
+ var/proj_type = /obj/effect/proc_holder/spell //IMPORTANT use only subtypes of this
var/proj_insubstantial = 0 //if it can pass through dense objects or not
var/proj_trigger_range = 1 //the range from target at which the projectile triggers cast(target)
@@ -35,12 +35,7 @@
for(var/turf/target in targets)
spawn(0)
var/obj/effect/proc_holder/spell/targeted/projectile
- if(istext(proj_type))
- var/projectile_type = text2path(proj_type)
- projectile = new projectile_type(user)
- if(istype(proj_type,/obj/effect/proc_holder/spell))
- projectile = new /obj/effect/proc_holder/spell/targeted/trigger(user)
- projectile:linked_spells += proj_type
+ projectile = new proj_type(user)
projectile.icon = proj_icon
projectile.icon_state = proj_icon_state
projectile.dir = get_dir(projectile, target)
diff --git a/code/datums/spells/wizard.dm b/code/datums/spells/wizard.dm
index c4381e30ed9..39a07052250 100644
--- a/code/datums/spells/wizard.dm
+++ b/code/datums/spells/wizard.dm
@@ -443,4 +443,4 @@
L.adjust_fire_stacks(20)
if(isliving(user))
var/mob/living/U = user
- U.IgniteMob()
\ No newline at end of file
+ U.IgniteMob()
diff --git a/code/datums/supplypacks.dm b/code/datums/supplypacks.dm
index 9024b053e40..5a21c121b25 100644
--- a/code/datums/supplypacks.dm
+++ b/code/datums/supplypacks.dm
@@ -1314,7 +1314,9 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine
/obj/item/storage/bible/booze,
/obj/item/storage/bible/booze,
/obj/item/clothing/suit/hooded/chaplain_hoodie,
- /obj/item/clothing/suit/hooded/chaplain_hoodie)
+ /obj/item/clothing/suit/hooded/chaplain_hoodie,
+ /obj/item/clothing/under/burial,
+ /obj/item/clothing/under/burial)
cost = 40
containername = "religious supplies crate"
@@ -1341,6 +1343,19 @@ var/list/all_supply_groups = list(supply_emergency,supply_security,supply_engine
cost = 15
containername = "bureaucracy crate"
+/datum/supply_packs/misc/book_crate
+ name = "Research Crate"
+ contains = list(/obj/item/book/codex_gigas)
+ cost = 15
+ containername = "book crate"
+
+/datum/supply_packs/misc/book_crate/New()
+ contains += pick(subtypesof(/obj/item/book/manual))
+ contains += pick(subtypesof(/obj/item/book/manual))
+ contains += pick(subtypesof(/obj/item/book/manual))
+ contains += pick(subtypesof(/obj/item/book/manual))
+ ..()
+
/datum/supply_packs/misc/tape
name = "Sticky Tape Crate"
contains = list(/obj/item/stack/tape_roll,
diff --git a/code/datums/uplink_item.dm b/code/datums/uplink_item.dm
index 03f4f47838e..c4a20fbd29b 100644
--- a/code/datums/uplink_item.dm
+++ b/code/datums/uplink_item.dm
@@ -202,7 +202,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
//Chef
/datum/uplink_item/jobspecific/specialsauce
name = "Chef Excellence's Special Sauce"
- desc = "A custom made sauce made from the toxin glands of 1000 space carp, if somebody ingests enough they'll be dead in 3 minutes or less guaranteed."
+ desc = "A custom sauce made from the highly poisonous fly amanita mushrooms. Anyone who ingests it will take variable toxin damage depending on how long it has been in their system, with a higher dosage taking longer to metabolize."
reference = "CESS"
item = /obj/item/reagent_containers/food/condiment/syndisauce
cost = 2
@@ -552,7 +552,7 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
/datum/uplink_item/dangerous/syndieborg
name = "Syndicate Cyborg"
- desc = "A cyborg designed and programmed for systematic extermination of non-Syndicate personnel."
+ desc = "A cyborg designed and programmed for systematic extermination of non-Syndicate personnel. Comes in Assault, Medical, or Saboteur variants."
reference = "SC"
item = /obj/item/antag_spawner/borg_tele
refund_path = /obj/item/antag_spawner/borg_tele
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 46b86a05ecc..b1afabf9737 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -33,15 +33,13 @@
. = ..()
areaMaster = get_area(src)
-/atom/movable/attempt_init()
+/atom/movable/attempt_init(loc, ...)
var/turf/T = get_turf(src)
if(T && SSatoms.initialized != INITIALIZATION_INSSATOMS && space_manager.is_zlevel_dirty(T.z))
space_manager.postpone_init(T.z, src)
return
. = ..()
-
-
/atom/movable/Destroy()
if(loc)
loc.handle_atom_del(src)
diff --git a/code/game/gamemodes/cult/runes.dm b/code/game/gamemodes/cult/runes.dm
index e7c6c89e457..e4c871a054b 100644
--- a/code/game/gamemodes/cult/runes.dm
+++ b/code/game/gamemodes/cult/runes.dm
@@ -979,7 +979,7 @@ var/list/teleport_runes = list()
..()
cultist_desc = "manifests a spirit as a servant of [ticker.cultdat.entity_title3]. The invoker must not move from atop the rune, and will take damage for each summoned spirit."
- notify_ghosts("Manifest rune created in [get_area(src)].", 'sound/effects/ghost2.ogg', source = src)
+ notify_ghosts("Manifest rune created in [get_area(src)].", ghost_sound='sound/effects/ghost2.ogg', source = src)
/obj/effect/rune/manifest/can_invoke(mob/living/user)
if(ghosts >= ghost_limit)
diff --git a/code/game/gamemodes/devil/contracts/friend.dm b/code/game/gamemodes/devil/contracts/friend.dm
new file mode 100644
index 00000000000..7b46ff83f1c
--- /dev/null
+++ b/code/game/gamemodes/devil/contracts/friend.dm
@@ -0,0 +1,53 @@
+/obj/effect/mob_spawn/human/demonic_friend
+ name = "Essence of friendship"
+ desc = "Oh boy! Oh boy! A friend!"
+ mob_name = "Demonic friend"
+ icon = 'icons/obj/cardboard_cutout.dmi'
+ icon_state = "cutout_basic"
+ outfit = /datum/outfit/demonic_friend
+ death = FALSE
+ roundstart = FALSE
+ random = TRUE
+ id_job = "SuperFriend"
+ id_access = "assistant"
+ var/obj/effect/proc_holder/spell/targeted/summon_friend/spell
+ var/datum/mind/owner
+ assignedrole = "SuperFriend"
+
+/obj/effect/mob_spawn/human/demonic_friend/Initialize(mapload, datum/mind/owner_mind, obj/effect/proc_holder/spell/targeted/summon_friend/summoning_spell)
+ . = ..()
+ owner = owner_mind
+ flavour_text = "You have been given a reprieve from your eternity of torment, to be [owner.name]'s friend for [owner.p_their()] short mortal coil. Be aware that if you do not live up to [owner.name]'s expectations, [owner.p_they()] can send you back to hell with a single thought. [owner.name]'s death will also return you to hell."
+ var/area/A = get_area(src)
+ if(!mapload && A)
+ notify_ghosts("\A friendship shell has been completed in \the [A.name].", source = src, action=NOTIFY_ATTACK, flashwindow = FALSE)
+ objectives = "Be [owner.name]'s friend, and keep [owner.name] alive, so you don't get sent back to hell."
+ spell = summoning_spell
+
+
+/obj/effect/mob_spawn/human/demonic_friend/special(mob/living/L)
+ if(!QDELETED(owner.current) && owner.current.stat != DEAD)
+ L.real_name = "[owner.name]'s best friend"
+ L.name = L.real_name
+ soullink(/datum/soullink/oneway/devilfriend, owner.current, L)
+ spell.friend = L
+ spell.charge_counter = spell.charge_max
+ L.mind.hasSoul = FALSE
+ var/mob/living/carbon/human/H = L
+ var/obj/item/worn = H.wear_id
+ var/obj/item/card/id/id = worn.GetID()
+ id.registered_name = L.real_name
+ id.update_label()
+ to_chat(owner.current, "Your friend has arrived!")
+ else
+ to_chat(L, "Your owner is already dead! You will soon perish.")
+ addtimer(CALLBACK(L, /mob.proc/dust), 150) //Give em a few seconds as a mercy.
+
+/datum/outfit/demonic_friend
+ name = "Demonic Friend"
+ uniform = /obj/item/clothing/under/assistantformal
+ shoes = /obj/item/clothing/shoes/laceup
+ r_pocket = /obj/item/radio/off
+ back = /obj/item/storage/backpack
+ implants = list(/obj/item/implant/mindshield) //No revolutionaries, he's MY friend.
+ id = /obj/item/card/id
diff --git a/code/game/gamemodes/devil/devil agent/devil_agent.dm b/code/game/gamemodes/devil/devil agent/devil_agent.dm
new file mode 100644
index 00000000000..e9a1d998a7a
--- /dev/null
+++ b/code/game/gamemodes/devil/devil agent/devil_agent.dm
@@ -0,0 +1,38 @@
+/datum/game_mode/devil/devil_agents
+ name = "Devil Agents"
+ config_tag = "devilagents"
+ required_players = 25
+ required_enemies = 3
+ recommended_enemies = 8
+
+ traitors_possible = 10 //hard limit on traitors if scaling is turned off
+ num_modifier = 4
+ objective_count = 2
+
+ var/list/target_list = list()
+ var/list/late_joining_list = list()
+ minimum_devils = 3
+
+/datum/game_mode/devil/devil_agents/post_setup()
+ var/i = 0
+ for(var/datum/mind/devil in devils)
+ i++
+ if(i + 1 > devils.len)
+ i = 0
+ target_list[devil] = devils[i + 1]
+ ..()
+
+/datum/game_mode/devil/devil_agents/forge_devil_objectives(datum/mind/devil_mind, quantity)
+ ..(devil_mind, quantity - give_outsell_objective(devil_mind))
+
+/datum/game_mode/devil/devil_agents/proc/give_outsell_objective(datum/mind/devil)
+ //If you override this method, have it return the number of objectives added.
+ if(target_list.len && target_list[devil]) // Is a double agent
+ var/datum/mind/target_mind = target_list[devil]
+ var/datum/objective/devil/outsell/outsellobjective = new
+ outsellobjective.owner = devil
+ outsellobjective.target = target_mind
+ outsellobjective.update_explanation_text()
+ devil.objectives += outsellobjective
+ return 1
+ return 0
diff --git a/code/game/gamemodes/devil/devil.dm b/code/game/gamemodes/devil/devil.dm
new file mode 100644
index 00000000000..ab5f5796d2e
--- /dev/null
+++ b/code/game/gamemodes/devil/devil.dm
@@ -0,0 +1,48 @@
+var/global/list/whiteness = list (
+ /obj/item/clothing/under/color/white = 2,
+ /obj/item/clothing/under/rank/bartender = 1,
+ /obj/item/clothing/under/rank/chef = 1,
+ /obj/item/clothing/under/rank/chief_engineer = 1,
+ /obj/item/clothing/under/rank/scientist = 1,
+ /obj/item/clothing/under/rank/chemist = 1,
+ /obj/item/clothing/under/rank/chief_medical_officer = 1,
+ /obj/item/clothing/under/rank/geneticist = 1,
+ /obj/item/clothing/under/rank/virologist = 1,
+ /obj/item/clothing/under/rank/nursesuit = 1,
+ /obj/item/clothing/under/rank/medical = 1,
+ /obj/item/clothing/under/rank/psych = 1,
+ /obj/item/clothing/under/rank/orderly = 1,
+ /obj/item/clothing/under/rank/security/brigphys = 1,
+ /obj/item/clothing/under/rank/internalaffairs = 1,
+ /obj/item/clothing/under/rank/ntrep = 1,
+ /obj/item/clothing/under/det = 1,
+ /obj/item/clothing/under/wedding/bride_white = 1,
+ /obj/item/clothing/under/mafia/white = 1,
+ /obj/item/clothing/under/noble_clothes = 1,
+ /obj/item/clothing/under/sl_suit = 1,
+ /obj/item/clothing/under/burial = 1
+)
+
+
+
+/mob/living/proc/check_devil_bane_multiplier(obj/item/weapon, mob/living/attacker)
+ switch(mind.devilinfo.bane)
+ if(BANE_WHITECLOTHES)
+ if(istype(attacker, /mob/living/carbon/human))
+ var/mob/living/carbon/human/H = attacker
+ if(H.w_uniform && istype(H.w_uniform, /obj/item/clothing/under))
+ var/obj/item/clothing/under/U = H.w_uniform
+ if(whiteness[U.type])
+ src.visible_message("[src] seems to have been harmed by the purity of [attacker]'s clothes.", "Unsullied white clothing is disrupting your form.")
+ return whiteness[U.type] + 1
+ if(BANE_TOOLBOX)
+ if(istype(weapon,/obj/item/storage/toolbox))
+ src.visible_message("The [weapon] seems unusually robust this time.", "The [weapon] is your unmaking!")
+ return 2.5 // Will take four hits with a normal toolbox.
+ if(BANE_HARVEST)
+ if(istype(weapon,/obj/item/reagent_containers/food/snacks/grown/) || istype(weapon,/obj/item/grown))
+ src.visible_message("The spirits of the harvest aid in the exorcism.", "The harvest spirits are harming you.")
+ src.Weaken(2)
+ qdel(weapon)
+ return 2
+ return 1
diff --git a/code/game/gamemodes/devil/devil_game_mode.dm b/code/game/gamemodes/devil/devil_game_mode.dm
new file mode 100644
index 00000000000..3ea91343f6b
--- /dev/null
+++ b/code/game/gamemodes/devil/devil_game_mode.dm
@@ -0,0 +1,67 @@
+/datum/game_mode/devil
+ name = "devil"
+ config_tag = "devil"
+ protected_jobs = list("Security Officer", "Warden", "Detective", "Nanotrasen Representative", "Security Pod Pilot", "Magistrate",
+ "Internal Affairs Agent", "Librarian", "Chaplain", "Head of Security", "Captain", "Brig Physician",
+ "Nanotrasen Navy Officer", "Special Operations Officer", "AI", "Cyborg")
+ required_players = 2
+ required_enemies = 1
+ recommended_enemies = 4
+
+ var/traitors_possible = 4 //hard limit on devils if scaling is turned off
+ var/num_modifier = 0 // Used for gamemodes, that are a child of traitor, that need more than the usual.
+ var/objective_count = 2
+ var/minimum_devils = 1
+ var/devil_scale_coefficient = 10
+
+/datum/game_mode/devil/announce()
+ to_chat(world, {"The current game mode is - Devil!
)
+ Devils: Purchase souls and tempt the crew to sin!
+ Crew: Resist the lure of sin and remain pure!"})
+
+/datum/game_mode/devil/pre_setup()
+
+ if(config.protect_roles_from_antagonist)
+ restricted_jobs += protected_jobs
+
+ var/list/possible_devils = get_players_for_role(ROLE_DEVIL)
+ var/num_devils = 0
+ if(!possible_devils.len)
+ return 0
+ if(config.traitor_scaling)
+ num_devils = max(1, round((num_players())/(devil_scale_coefficient))+1)
+ else
+ num_devils = max(1, min(num_players(), traitors_possible))
+
+
+ for(var/j = 0, j < num_devils, j++)
+ if (!possible_devils.len)
+ break
+ var/datum/mind/devil = pick(possible_devils)
+ devils += devil
+ devil.special_role = ROLE_DEVIL
+ devil.restricted_roles = restricted_jobs
+
+ log_game("[devil.key] (ckey) has been selected as a [config_tag]")
+ possible_devils.Remove(devil)
+
+ if(devils.len < required_enemies)
+ return 0
+ return 1
+
+
+/datum/game_mode/devil/post_setup()
+ for(var/datum/mind/devil in devils)
+ spawn(rand(10, 100))
+ finalize_devil(devil, TRUE)
+ spawn(100)
+ forge_devil_objectives(devil, objective_count) //This has to be in a separate loop, as we need devil names to be generated before we give objectives in devil agent.
+ devil.devilinfo.announce_laws(devil.current)
+ var/obj_count = 1
+ to_chat(devil.current, " Your current objectives:")
+ for(var/datum/objective/objective in devil.objectives)
+ to_chat(devil.current, "Objective #[obj_count]: [objective.explanation_text]")
+ obj_count++
+ modePlayer += devils
+ ..()
+ return 1
diff --git a/code/game/gamemodes/devil/devilinfo.dm b/code/game/gamemodes/devil/devilinfo.dm
new file mode 100644
index 00000000000..e11d31f7d98
--- /dev/null
+++ b/code/game/gamemodes/devil/devilinfo.dm
@@ -0,0 +1,559 @@
+#define BLOOD_THRESHOLD 3 //How many souls are needed per stage.
+#define TRUE_THRESHOLD 7
+#define ARCH_THRESHOLD 12
+
+#define BASIC_DEVIL 0
+#define BLOOD_LIZARD 1
+#define TRUE_DEVIL 2
+#define ARCH_DEVIL 3
+
+#define LOSS_PER_DEATH 2
+
+#define SOULVALUE (soulsOwned.len-reviveNumber)
+
+#define DEVILRESURRECTTIME 600
+
+var/global/list/allDevils = list()
+var/global/list/lawlorify = list (
+ LORE = list(
+ OBLIGATION_FOOD = "This devil seems to always offer it's victims food before slaughtering them.",
+ OBLIGATION_FIDDLE = "This devil will never turn down a musical challenge.",
+ OBLIGATION_DANCEOFF = "This devil will never turn down a dance off.",
+ OBLIGATION_GREET = "This devil seems to only be able to converse with people it knows the name of.",
+ OBLIGATION_PRESENCEKNOWN = "This devil seems to be unable to attack from stealth.",
+ OBLIGATION_SAYNAME = "He will always chant his name upon killing someone.",
+ OBLIGATION_ANNOUNCEKILL = "This devil always loudly announces his kills for the world to hear.",
+ OBLIGATION_ANSWERTONAME = "This devil always responds to his truename.",
+ BANE_SILVER = "Silver seems to gravely injure this devil.",
+ BANE_SALT = "Throwing salt at this devil will hinder his ability to use infernal powers temporarily.",
+ BANE_LIGHT = "Bright flashes will disorient the devil, likely causing him to flee.",
+ BANE_IRON = "Cold iron will slowly injure him, until he can purge it from his system.",
+ BANE_WHITECLOTHES = "Wearing clean white clothing will help ward off this devil.",
+ BANE_HARVEST = "Presenting the labors of a harvest will disrupt the devil.",
+ BANE_TOOLBOX = "That which holds the means of creation also holds the means of the devil's undoing.",
+ BAN_HURTWOMAN = "This devil seems to prefer hunting men.",
+ BAN_CHAPEL = "This devil avoids holy ground.",
+ BAN_HURTPRIEST = "The anointed clergy appear to be immune to his powers.",
+ BAN_AVOIDWATER = "The devil seems to have some sort of aversion to water, though it does not appear to harm him.",
+ BAN_STRIKEUNCONCIOUS = "This devil only shows interest in those who are awake.",
+ BAN_HURTLIZARD = "This devil will not strike an Unathi first.",
+ BAN_HURTANIMAL = "This devil avoids hurting animals.",
+ BANISH_WATER = "To banish the devil, you must infuse it's body with holy water.",
+ BANISH_COFFIN = "This devil will return to life if it's remains are not placed within a coffin.",
+ BANISH_FORMALDYHIDE = "To banish the devil, you must inject it's lifeless body with embalming fluid.",
+ BANISH_RUNES = "This devil will resurrect after death, unless it's remains are within a rune.",
+ BANISH_CANDLES = "A large number of nearby lit candles will prevent it from resurrecting.",
+ BANISH_DESTRUCTION = "It's corpse must be utterly destroyed to prevent resurrection.",
+ BANISH_FUNERAL_GARB = "If clad in funeral garments, this devil will be unable to resurrect. Should the clothes not fit, lay them gently on top of the devil's corpse."
+ ),
+ LAW = list(
+ OBLIGATION_FOOD = "When not acting in self defense, you must always offer your victim food before harming them.",
+ OBLIGATION_FIDDLE = "When not in immediate danger, if you are challenged to a musical duel, you must accept it. You are not obligated to duel the same person twice.",
+ OBLIGATION_DANCEOFF = "When not in immediate danger, if you are challenged to a dance off, you must accept it. You are not obligated to face off with the same person twice.",
+ OBLIGATION_GREET = "You must always greet other people by their last name before talking with them.",
+ OBLIGATION_PRESENCEKNOWN = "You must always make your presence known before attacking.",
+ OBLIGATION_SAYNAME = "You must always say your true name after you kill someone.",
+ OBLIGATION_ANNOUNCEKILL = "Upon killing someone, you must make your deed known to all within earshot, over comms if reasonably possible.",
+ OBLIGATION_ANSWERTONAME = "If you are not under attack, you must always respond to your true name.",
+ BAN_HURTWOMAN = "You must never harm a female outside of self defense.",
+ BAN_CHAPEL = "You must never attempt to enter the chapel.",
+ BAN_HURTPRIEST = "You must never attack a priest.",
+ BAN_AVOIDWATER = "You must never willingly touch a wet surface.",
+ BAN_STRIKEUNCONCIOUS = "You must never strike an unconscious person.",
+ BAN_HURTLIZARD = "You must never harm an Unathi outside of self defense.",
+ BAN_HURTANIMAL = "You must never harm a non-sentient creature or robot outside of self defense.",
+ BANE_SILVER = "Silver, in all of it's forms shall be your downfall.",
+ BANE_SALT = "Salt will disrupt your magical abilities.",
+ BANE_LIGHT = "Blinding lights will prevent you from using offensive powers for a time.",
+ BANE_IRON = "Cold wrought iron shall act as poison to you.",
+ BANE_WHITECLOTHES = "Those clad in pristine white garments will strike you true.",
+ BANE_HARVEST = "The fruits of the harvest shall be your downfall.",
+ BANE_TOOLBOX = "Toolboxes are bad news for you, for some reason.",
+ BANISH_WATER = "If your corpse is filled with holy water, you will be unable to resurrect.",
+ BANISH_COFFIN = "If your corpse is in a coffin, you will be unable to resurrect.",
+ BANISH_FORMALDYHIDE = "If your corpse is embalmed, you will be unable to resurrect.",
+ BANISH_RUNES = "If your corpse is placed within a rune, you will be unable to resurrect.",
+ BANISH_CANDLES = "If your corpse is near lit candles, you will be unable to resurrect.",
+ BANISH_DESTRUCTION = "If your corpse is destroyed, you will be unable to resurrect.",
+ BANISH_FUNERAL_GARB = "If your corpse is clad in funeral garments, you will be unable to resurrect."
+ )
+ )
+
+/datum/devilinfo
+ var/datum/mind/owner = null
+ var/obligation
+ var/ban
+ var/bane
+ var/banish
+ var/truename
+ var/list/datum/mind/soulsOwned = new
+ var/datum/dna/humanform = null
+ var/reviveNumber = 0
+ var/form = BASIC_DEVIL
+ var/exists = 0
+ var/static/list/dont_remove_spells = list(
+ /obj/effect/proc_holder/spell/targeted/summon_contract,
+ /obj/effect/proc_holder/spell/targeted/conjure_item/violin,
+ /obj/effect/proc_holder/spell/targeted/summon_dancefloor)
+ var/ascendable = FALSE
+
+/datum/devilinfo/New()
+ ..()
+ dont_remove_spells = typecacheof(dont_remove_spells)
+
+/proc/randomDevilInfo(name = randomDevilName())
+ var/datum/devilinfo/devil = new
+ devil.truename = name
+ devil.bane = randomdevilbane()
+ devil.obligation = randomdevilobligation()
+ devil.ban = randomdevilban()
+ devil.banish = randomdevilbanish()
+ return devil
+
+/proc/devilInfo(name, saveDetails = 0)
+ if(allDevils[lowertext(name)])
+ return allDevils[lowertext(name)]
+ else
+ var/datum/devilinfo/devil = randomDevilInfo(name)
+ allDevils[lowertext(name)] = devil
+ devil.exists = saveDetails
+ return devil
+
+
+
+/proc/randomDevilName()
+ var/preTitle = ""
+ var/title = ""
+ var/mainName = ""
+ var/suffix = ""
+ if(prob(65))
+ if(prob(35))
+ preTitle = pick("Dark ", "Hellish ", "Fiery ", "Sinful ", "Blood ")
+ title = pick("Lord ", "Fallen Prelate ", "Count ", "Viscount ", "Vizier ", "Elder ", "Adept ")
+ var/probability = 100
+ mainName = pick("Hal", "Ve", "Odr", "Neit", "Ci", "Quon", "Mya", "Folth", "Wren", "Gyer", "Geyr", "Hil", "Niet", "Twou", "Hu", "Don")
+ while(prob(probability))
+ mainName += pick("hal", "ve", "odr", "neit", "ca", "quon", "mya", "folth", "wren", "gyer", "geyr", "hil", "niet", "twoe", "phi", "coa")
+ probability -= 20
+ if(prob(40))
+ suffix = pick(" the Red", " the Soulless", " the Master", ", the Lord of all things", ", Jr.")
+ return preTitle + title + mainName + suffix
+
+/proc/randomdevilobligation()
+ return pick(OBLIGATION_FOOD, OBLIGATION_FIDDLE, OBLIGATION_DANCEOFF, OBLIGATION_GREET, OBLIGATION_PRESENCEKNOWN, OBLIGATION_SAYNAME, OBLIGATION_ANNOUNCEKILL, OBLIGATION_ANSWERTONAME)
+
+/proc/randomdevilban()
+ return pick(BAN_HURTWOMAN, BAN_CHAPEL, BAN_HURTPRIEST, BAN_AVOIDWATER, BAN_STRIKEUNCONCIOUS, BAN_HURTLIZARD, BAN_HURTANIMAL)
+
+/proc/randomdevilbane()
+ return pick(BANE_SALT, BANE_LIGHT, BANE_IRON, BANE_WHITECLOTHES, BANE_SILVER, BANE_HARVEST, BANE_TOOLBOX)
+
+/proc/randomdevilbanish()
+ return pick(BANISH_WATER, BANISH_COFFIN, BANISH_FORMALDYHIDE, BANISH_RUNES, BANISH_CANDLES, BANISH_DESTRUCTION, BANISH_FUNERAL_GARB)
+
+/datum/devilinfo/proc/link_with_mob(mob/living/L)
+ if(istype(L, /mob/living/carbon/human))
+ var/mob/living/carbon/human/H = L
+ humanform = H.dna.Clone()
+ owner = L.mind
+ give_base_spells(1)
+
+/datum/devilinfo/proc/add_soul(datum/mind/soul)
+ if(soulsOwned.Find(soul))
+ return
+ soulsOwned += soul
+ owner.current.nutrition = NUTRITION_LEVEL_FULL
+ to_chat(owner.current, "You feel satiated as you received a new soul.")
+ update_hud()
+ switch(SOULVALUE)
+ if(0)
+ to_chat(owner.current, "Your hellish powers have been restored.")
+ give_base_spells()
+ if(BLOOD_THRESHOLD)
+ to_chat(owner.current, "You feel as though your humanoid form is about to shed. You will soon turn into a blood lizard.")
+ sleep(50)
+ increase_blood_lizard()
+ if(TRUE_THRESHOLD)
+ to_chat(owner.current, "You feel as though your current form is about to shed. You will soon turn into a true devil.")
+ sleep(50)
+ increase_true_devil()
+ if(ARCH_THRESHOLD)
+ arch_devil_prelude()
+ increase_arch_devil()
+
+/datum/devilinfo/proc/remove_soul(datum/mind/soul)
+ if(soulsOwned.Remove(soul))
+ to_chat(owner.current, "You feel as though a soul has slipped from your grasp.")
+ check_regression()
+ update_hud()
+
+/datum/devilinfo/proc/check_regression()
+ if(form == ARCH_DEVIL)
+ return //arch devil can't regress
+ //Yes, fallthrough behavior is intended, so I can't use a switch statement.
+ if(form == TRUE_DEVIL && SOULVALUE < TRUE_THRESHOLD)
+ regress_blood_lizard()
+ if(form == BLOOD_LIZARD && SOULVALUE < BLOOD_THRESHOLD)
+ regress_humanoid()
+ if(SOULVALUE < 0)
+ remove_spells()
+ to_chat(owner.current, "As punishment for your failures, all of your powers except contract creation have been revoked.")
+
+/datum/devilinfo/proc/regress_humanoid()
+ to_chat(owner.current, "Your powers weaken, have more contracts be signed to regain power.")
+ if(istype(owner.current, /mob/living/carbon/human))
+ var/mob/living/carbon/human/H = owner.current
+ if(humanform)
+ H.set_species(humanform.species)
+ H.dna = humanform.Clone()
+ H.sync_organ_dna(assimilate = 0)
+ else
+ H.set_species(/datum/species/human)
+ // TODO: Add some appearance randomization here or something
+ humanform = H.dna.Clone()
+ H.regenerate_icons()
+ else
+ owner.current.color = ""
+ give_base_spells()
+ if(istype(owner.current.loc, /obj/effect/dummy/slaughter))
+ owner.current.forceMove(get_turf(owner.current))//Fixes dying while jaunted leaving you permajaunted.
+ form = BASIC_DEVIL
+
+/datum/devilinfo/proc/regress_blood_lizard()
+ var/mob/living/carbon/true_devil/D = owner.current
+ to_chat(D, "Your powers weaken, have more contracts be signed to regain power.")
+ D.oldform.loc = D.loc
+ owner.transfer_to(D.oldform)
+ D.oldform.status_flags &= ~GODMODE
+ give_lizard_spells()
+ qdel(D)
+ form = BLOOD_LIZARD
+ update_hud()
+
+
+/datum/devilinfo/proc/increase_blood_lizard()
+ if(ishuman(owner.current))
+ var/mob/living/carbon/human/H = owner.current
+ var/list/language_temp = H.languages.Copy()
+ H.set_species(/datum/species/unathi)
+ H.languages = language_temp
+ H.underwear = "Nude"
+ H.undershirt = "Nude"
+ H.socks = "Nude"
+ H.change_skin_color(80, 16, 16) //A deep red
+ H.regenerate_icons()
+ else //Did the devil get hit by a staff of transmutation?
+ owner.current.color = "#501010"
+ give_lizard_spells()
+ form = BLOOD_LIZARD
+
+
+
+/datum/devilinfo/proc/increase_true_devil()
+ var/mob/living/carbon/true_devil/A = new /mob/living/carbon/true_devil(owner.current.loc, owner.current)
+ A.faction |= "hell"
+ // Put the old body in stasis
+ owner.current.status_flags |= GODMODE
+ owner.current.loc = A
+ A.oldform = owner.current
+ owner.transfer_to(A)
+ A.set_name()
+ give_true_spells()
+ form = TRUE_DEVIL
+ update_hud()
+
+/datum/devilinfo/proc/arch_devil_prelude()
+ if(!ascendable)
+ return
+ var/mob/living/carbon/true_devil/D = owner.current
+ to_chat(D, "You feel as though your form is about to ascend.")
+ sleep(50)
+ if(!D)
+ return
+ D.visible_message("[D]'s skin begins to erupt with spikes.", \
+ "Your flesh begins creating a shield around yourself.")
+ sleep(100)
+ if(!D)
+ return
+ D.visible_message("The horns on [D]'s head slowly grow and elongate.", \
+ "Your body continues to mutate. Your telepathic abilities grow.")
+ sleep(90)
+ if(!D)
+ return
+ D.visible_message("[D]'s body begins to violently stretch and contort.", \
+ "You begin to rend apart the final barriers to ultimate power.")
+ sleep(40)
+ if(!D)
+ return
+ to_chat(D, "Yes!")
+ sleep(10)
+ if(!D)
+ return
+ to_chat(D, "YES!!")
+ sleep(10)
+ if(!D)
+ return
+ to_chat(D, "YE--")
+ sleep(1)
+ if(!D)
+ return
+ to_chat(world, "SLOTH, WRATH, GLUTTONY, ACEDIA, ENVY, GREED, PRIDE! FIRES OF HELL AWAKEN!!")
+ world << 'sound/hallucinations/veryfar_noise.ogg'
+ sleep(50)
+ if(!ticker.mode.devil_ascended)
+ SSshuttle.emergency.request(null, 0.3)
+ ticker.mode.devil_ascended++
+
+/datum/devilinfo/proc/increase_arch_devil()
+ if(!ascendable)
+ return
+ var/mob/living/carbon/true_devil/D = owner.current
+ if(!istype(D))
+ return
+ give_arch_spells()
+ D.convert_to_archdevil()
+ if(istype(D.loc, /obj/effect/dummy/slaughter))
+ D.forceMove(get_turf(D))
+ var/area/A = get_area(owner.current)
+ if(A)
+ notify_ghosts("An arch devil has ascended in [A.name]. Reach out to the devil to start climbing the infernal corporate ladder.", title = "Arch Devil Ascended", source = owner.current, action = NOTIFY_ATTACK)
+ form = ARCH_DEVIL
+
+/datum/devilinfo/proc/remove_spells()
+ for(var/X in owner.spell_list)
+ var/obj/effect/proc_holder/spell/S = X
+ if(!is_type_in_typecache(S, dont_remove_spells))
+ owner.RemoveSpell(S)
+
+/datum/devilinfo/proc/give_summon_contract()
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/summon_contract(null))
+
+
+/datum/devilinfo/proc/give_base_spells(give_summon_contract = 0)
+ remove_spells()
+ owner.AddSpell(new /obj/effect/proc_holder/spell/fireball/hellish(null))
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork(null))
+ if(give_summon_contract)
+ give_summon_contract()
+ if(obligation == OBLIGATION_FIDDLE)
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/violin(null))
+ if(obligation == OBLIGATION_DANCEOFF)
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/summon_dancefloor(null))
+
+/datum/devilinfo/proc/give_lizard_spells()
+ remove_spells()
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork(null))
+ owner.AddSpell(new /obj/effect/proc_holder/spell/fireball/hellish(null))
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/infernal_jaunt(null))
+
+/datum/devilinfo/proc/give_true_spells()
+ remove_spells()
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/greater(null))
+ owner.AddSpell(new /obj/effect/proc_holder/spell/fireball/hellish(null))
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/infernal_jaunt(null))
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/sintouch(null))
+
+/datum/devilinfo/proc/give_arch_spells()
+ remove_spells()
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/ascended(null))
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/sintouch/ascended(null))
+
+/datum/devilinfo/proc/beginResurrectionCheck(mob/living/body)
+ if(owner.current != body)
+ body = owner.current
+ if(SOULVALUE > 0)
+ to_chat(owner.current, "Your body has been damaged to the point that you may no longer use it. At the cost of some of your power, you will return to life soon.")
+ addtimer(CALLBACK(src, "activateResurrection", body), DEVILRESURRECTTIME)
+ else
+ to_chat(owner.messageable_mob(), "Your hellish powers are too weak to resurrect yourself.")
+
+/datum/devilinfo/proc/activateResurrection(mob/living/body)
+ if(QDELETED(body) || body.stat == DEAD)
+ if(SOULVALUE > 0)
+ if(check_banishment(body))
+ to_chat(owner.messageable_mob(), "Unfortunately, the mortals have finished a ritual that prevents your resurrection.")
+ return -1
+ else
+ to_chat(owner.messageable_mob(), "WE LIVE AGAIN!")
+ return hellish_resurrection(body)
+ else
+ to_chat(owner.messageable_mob(), "Unfortunately, the power that stemmed from your contracts has been extinguished. You no longer have enough power to resurrect.")
+ return -1
+ else
+ to_chat(owner.current, "You seem to have resurrected without your hellish powers.")
+
+/datum/devilinfo/proc/check_banishment(mob/living/body)
+ switch(banish)
+ if(BANISH_WATER)
+ if(!QDELETED(body) && iscarbon(body))
+ var/mob/living/carbon/H = body
+ return H.reagents.has_reagent("holy water")
+ return 0
+ if(BANISH_COFFIN)
+ return (!QDELETED(body) && istype(body.loc, /obj/structure/closet/coffin))
+ if(BANISH_FORMALDYHIDE)
+ if(!QDELETED(body) && iscarbon(body))
+ var/mob/living/carbon/H = body
+ return H.reagents.has_reagent("formaldehyde")
+ return 0
+ if(BANISH_RUNES)
+ if(!QDELETED(body))
+ for(var/obj/effect/decal/cleanable/crayon/R in range(0,body))
+ if (R.name == "rune")
+ return 1
+ return 0
+ if(BANISH_CANDLES)
+ if(!QDELETED(body))
+ var/count = 0
+ for(var/obj/item/candle/C in range(1,body))
+ count += C.lit
+ if(count>=4)
+ return 1
+ return 0
+ if(BANISH_DESTRUCTION)
+ if(!QDELETED(body))
+ return 0
+ return 1
+ if(BANISH_FUNERAL_GARB)
+ if(!QDELETED(body) && iscarbon(body))
+ var/mob/living/carbon/human/H = body
+ if(H.w_uniform && istype(H.w_uniform, /obj/item/clothing/under/burial))
+ return 1
+ return 0
+ else
+ for(var/obj/item/clothing/under/burial/B in range(0,body))
+ if(B.loc == get_turf(B)) //Make sure it's not in someone's inventory or something.
+ return 1
+ return 0
+
+/datum/devilinfo/proc/hellish_resurrection(mob/living/body)
+ message_admins("[owner.name] (true name is: [truename]) is resurrecting using hellish energy.")
+ if(SOULVALUE <= ARCH_THRESHOLD && ascendable) // once ascended, arch devils do not go down in power by any means.
+ reviveNumber += LOSS_PER_DEATH
+ update_hud()
+ if(!QDELETED(body))
+ body.revive()
+ if(!body.client)
+ var/mob/dead/observer/O = owner.get_ghost()
+ O.reenter_corpse()
+ if(istype(body.loc, /obj/effect/dummy/slaughter))
+ body.forceMove(get_turf(body))//Fixes dying while jaunted leaving you permajaunted.
+ if(istype(body, /mob/living/carbon/true_devil))
+ var/mob/living/carbon/true_devil/D = body
+ if(D.oldform)
+ D.oldform.revive() // Heal the old body too, so the devil doesn't resurrect, then immediately regress into a dead body.
+ if(body.stat == DEAD) // Not sure why this would happen
+ create_new_body()
+ else if(blobstart.len > 0)
+ // teleport the body so repeated beatdowns aren't an option)
+ body.forceMove(get_turf(pick(blobstart)))
+ // give them the devil lawyer outfit in case they got stripped
+ if(ishuman(body))
+ var/mob/living/carbon/human/H = body
+ H.equipOutfit(/datum/outfit/devil_lawyer)
+ else
+ create_new_body()
+ check_regression()
+
+/datum/devilinfo/proc/create_new_body()
+ if(blobstart.len > 0)
+ var/turf/targetturf = get_turf(pick(blobstart))
+ var/mob/currentMob = owner.current
+ if(QDELETED(currentMob))
+ currentMob = owner.get_ghost()
+ if(!currentMob)
+ message_admins("[owner.name]'s devil resurrection failed due to client logoff. Aborting.")
+ return -1
+ if(currentMob.mind != owner)
+ message_admins("[owner.name]'s devil resurrection failed due to becoming a new mob. Aborting.")
+ return -1
+ var/mob/living/carbon/human/H = new /mob/living/carbon/human(targetturf)
+ owner.transfer_to(H)
+ if(isobserver(currentMob))
+ var/mob/dead/observer/O = currentMob
+ O.reenter_corpse()
+ if(humanform)
+ H.set_species(humanform.species)
+ H.dna = humanform.Clone()
+
+ H.dna.UpdateSE()
+ H.dna.UpdateUI()
+
+ H.sync_organ_dna(1) // It's literally a fresh body as you can get, so all organs properly belong to it
+ H.UpdateAppearance()
+ else
+ // gibbed cyborg or similar - create a randomized "humanform" appearance
+ H.scramble_appearance()
+ humanform = H.dna.Clone()
+
+
+ H.equipOutfit(/datum/outfit/devil_lawyer)
+ give_base_spells(TRUE)
+ if(SOULVALUE >= BLOOD_THRESHOLD)
+ increase_blood_lizard()
+ if(SOULVALUE >= TRUE_THRESHOLD) //Yes, BOTH this and the above if statement are to run if soulpower is high enough.
+ increase_true_devil()
+ if(SOULVALUE >= ARCH_THRESHOLD && ascendable)
+ increase_arch_devil()
+ else
+ throw EXCEPTION("Unable to find a blobstart landmark for hellish resurrection")
+
+/datum/devilinfo/proc/update_hud()
+ if(istype(owner.current, /mob/living/carbon))
+ var/mob/living/C = owner.current
+ if(C.hud_used && C.hud_used.devilsouldisplay)
+ C.hud_used.devilsouldisplay.update_counter(SOULVALUE)
+
+// SECTION: Messages and explanations
+
+/datum/devilinfo/proc/announce_laws(mob/living/owner)
+ to_chat(owner, "You remember your link to the infernal. You are [truename], an agent of hell, a devil. And you were sent to the plane of creation for a reason. A greater purpose. Convince the crew to sin, and embroiden Hell's grasp.")
+ to_chat(owner, "However, your infernal form is not without weaknesses.")
+ to_chat(owner, "You may not use violence to coerce someone into selling their soul.")
+ to_chat(owner, "You may not directly and knowingly physically harm a devil, other than yourself.")
+ to_chat(owner,lawlorify[LAW][bane])
+ to_chat(owner,lawlorify[LAW][ban])
+ to_chat(owner,lawlorify[LAW][obligation])
+ to_chat(owner,lawlorify[LAW][banish])
+ to_chat(owner, "
Remember, the crew can research your weaknesses if they find out your devil name.
")
+
+
+#undef BLOOD_THRESHOLD
+#undef TRUE_THRESHOLD
+#undef ARCH_THRESHOLD
+#undef BASIC_DEVIL
+#undef BLOOD_LIZARD
+#undef TRUE_DEVIL
+#undef ARCH_DEVIL
+#undef LOSS_PER_DEATH
+#undef SOULVALUE
+#undef DEVILRESURRECTTIME
+
+/datum/outfit/devil_lawyer
+ name = "Devil Lawyer"
+ uniform = /obj/item/clothing/under/lawyer/black
+ shoes = /obj/item/clothing/shoes/laceup
+ back = /obj/item/storage/backpack
+ l_hand = /obj/item/storage/briefcase
+ l_pocket = /obj/item/pen
+ l_ear = /obj/item/radio/headset
+
+ id = /obj/item/card/id
+
+/datum/outfit/devil_lawyer/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
+ var/obj/item/card/id/W = H.wear_id
+ if(!istype(W) || W.assignment) // either doesn't have a card, or the card is already written to
+ return
+ var/name_to_use = H.real_name
+ if(H.mind && H.mind.devilinfo)
+ // Having hell create an ID for you causes its risks
+ name_to_use = H.mind.devilinfo.truename
+
+ W.name = "[name_to_use]'s ID Card (Lawyer)"
+ W.registered_name = name_to_use
+ W.assignment = "Lawyer"
+ W.rank = W.assignment
+ W.age = H.age
+ W.sex = capitalize(H.gender)
+ W.access = list(access_maint_tunnels, access_syndicate, access_external_airlocks)
+ W.photo = get_id_photo(H)
diff --git a/code/game/gamemodes/devil/game_mode.dm b/code/game/gamemodes/devil/game_mode.dm
new file mode 100644
index 00000000000..7b0be8e6e0f
--- /dev/null
+++ b/code/game/gamemodes/devil/game_mode.dm
@@ -0,0 +1,91 @@
+/datum/game_mode
+ var/list/datum/mind/sintouched = list()
+ var/list/datum/mind/devils = list()
+ var/devil_ascended = 0 // Number of arch devils on station
+
+/datum/game_mode/proc/auto_declare_completion_sintouched()
+ var/text = ""
+ if(sintouched.len)
+ text += "
The sintouched were:"
+ var/list/sintouchedUnique = uniqueList(sintouched)
+ for(var/S in sintouchedUnique)
+ var/datum/mind/sintouched_mind = S
+ text += printplayer(sintouched_mind)
+ text += printobjectives(sintouched_mind)
+ text += "
"
+ text += "
"
+ to_chat(world,text)
+
+/datum/game_mode/proc/auto_declare_completion_devils()
+ /var/text = ""
+ if(devils.len)
+ text += "
The devils were:"
+ for(var/D in devils)
+ var/datum/mind/devil = D
+ text += printplayer(devil)
+ text += printdevilinfo(devil)
+ text += printobjectives(devil)
+ text += "
"
+ text += "
"
+ to_chat(world,text)
+
+
+/datum/game_mode/proc/finalize_devil(datum/mind/devil_mind, ascendable = FALSE)
+ var/trueName= randomDevilName()
+ devil_mind.devilinfo = devilInfo(trueName, 1)
+ devil_mind.devilinfo.ascendable = ascendable
+ devil_mind.store_memory("Your diabolical true name is [devil_mind.devilinfo.truename]
[lawlorify[LAW][devil_mind.devilinfo.ban]]
You may not use violence to coerce someone into selling their soul.
You may not directly and knowingly physically harm a devil, other than yourself.
[lawlorify[LAW][devil_mind.devilinfo.bane]]
[lawlorify[LAW][devil_mind.devilinfo.obligation]]
[lawlorify[LAW][devil_mind.devilinfo.banish]]
")
+ devil_mind.devilinfo.link_with_mob(devil_mind.current)
+ if(devil_mind.assigned_role == "Clown")
+ to_chat(devil_mind.current, "Your infernal nature allows you to wield weapons without harming yourself.")
+ devil_mind.current.mutations.Remove(CLUMSY)
+ spawn(10)
+ devil_mind.devilinfo.update_hud()
+ if(issilicon(devil_mind.current))
+ var/mob/living/silicon/S = devil_mind.current
+ S.laws.set_sixsixsix_law("You may not use violence to coerce someone into selling their soul.")
+ S.laws.set_sixsixsix_law("You may not directly and knowingly physically harm a devil, other than yourself.")
+ S.laws.set_sixsixsix_law("[lawlorify[LAW][devil_mind.devilinfo.ban]]")
+ S.laws.set_sixsixsix_law("[lawlorify[LAW][devil_mind.devilinfo.obligation]]")
+ S.laws.set_sixsixsix_law("Accomplish your objectives at all costs.")
+
+// unsure about the second "quantity" arg and how it fits with the antag refactor
+/datum/game_mode/proc/forge_devil_objectives(datum/mind/devil_mind, quantity)
+ var/list/validtypes = list(/datum/objective/devil/soulquantity, /datum/objective/devil/soulquality, /datum/objective/devil/sintouch, /datum/objective/devil/buy_target)
+ for(var/i = 1 to quantity)
+ var/type = pick(validtypes)
+ var/datum/objective/devil/objective = new type(null)
+ objective.owner = devil_mind
+ devil_mind.objectives += objective
+ if(!istype(objective, /datum/objective/devil/buy_target))
+ validtypes -= type //prevent duplicate objectives, EXCEPT for buy_target.
+ else
+ objective.find_target()
+
+/datum/game_mode/proc/greet_devil(datum/mind/devil_mind)
+ if(!devil_mind.devilinfo)
+ return
+ devil_mind.devilinfo.announce_laws(devil_mind.current)
+ devil_mind.announce_objectives()
+
+
+/datum/game_mode/proc/printdevilinfo(datum/mind/ply)
+ if(!ply.devilinfo)
+ return "Target is not a devil."
+ var/text = "The devil's true name is: [ply.devilinfo.truename]"
+ text += "The devil's bans were:"
+ text += " [lawlorify[LORE][ply.devilinfo.ban]]"
+ text += " [lawlorify[LORE][ply.devilinfo.bane]]"
+ text += " [lawlorify[LORE][ply.devilinfo.obligation]]"
+ text += " [lawlorify[LORE][ply.devilinfo.banish]]"
+ return text
+
+/datum/game_mode/proc/update_devil_icons_added(datum/mind/devil_mind)
+ var/datum/atom_hud/antag/hud = huds[ANTAG_HUD_DEVIL]
+ hud.join_hud(devil_mind.current)
+ set_antag_hud(devil_mind.current, "huddevil")
+
+/datum/game_mode/proc/update_devil_icons_removed(datum/mind/devil_mind)
+ var/datum/atom_hud/antag/hud = huds[ANTAG_HUD_DEVIL]
+ hud.leave_hud(devil_mind.current)
+ set_antag_hud(devil_mind.current, null)
diff --git a/code/game/gamemodes/devil/imp/imp.dm b/code/game/gamemodes/devil/imp/imp.dm
new file mode 100644
index 00000000000..b012edc1930
--- /dev/null
+++ b/code/game/gamemodes/devil/imp/imp.dm
@@ -0,0 +1,54 @@
+//////////////////The Monster
+
+/mob/living/simple_animal/imp
+ name = "imp"
+ real_name = "imp"
+ desc = "A large, menacing creature covered in armored black scales."
+ speak_emote = list("cackles")
+ emote_hear = list("cackles","screeches")
+ response_help = "thinks better of touching"
+ response_disarm = "flails at"
+ response_harm = "punches"
+ icon = 'icons/mob/mob.dmi'
+ icon_state = "imp"
+ icon_living = "imp"
+ speed = 1
+ a_intent = INTENT_HARM
+ stop_automated_movement = 1
+ status_flags = CANPUSH
+ attack_sound = 'sound/misc/demon_attack1.ogg'
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ minbodytemp = 250 //Weak to cold
+ maxbodytemp = INFINITY
+ faction = list("hell")
+ attacktext = "wildly tears into"
+ maxHealth = 200
+ health = 200
+ healable = 0
+ environment_smash = 1
+ melee_damage_lower = 10
+ melee_damage_upper = 15
+ see_in_dark = 8
+ see_invisible = SEE_INVISIBLE_MINIMUM
+ var/boost
+ var/playstyle_string = "You are an imp, a mischevious creature from hell. You are the lowest rank on the hellish totem pole \
+ Though you are not obligated to help, perhaps by aiding a higher ranking devil, you might just get a promotion. However, you are incapable \
+ of intentionally harming a fellow devil."
+
+/mob/living/simple_animal/imp/New()
+ ..()
+ boost = world.time + 60
+
+/mob/living/simple_animal/imp/Life()
+ ..()
+ if(boost[src] screams in agony as it sublimates into a sulfurous smoke.")
+ ghostize()
+ qdel(src)
diff --git a/code/game/gamemodes/devil/objectives.dm b/code/game/gamemodes/devil/objectives.dm
new file mode 100644
index 00000000000..5214ab336ec
--- /dev/null
+++ b/code/game/gamemodes/devil/objectives.dm
@@ -0,0 +1,109 @@
+/datum/objective/devil
+
+/datum/objective/devil/soulquantity
+ explanation_text = "You shouldn't see this text. Error:DEVIL1"
+ target_amount = 4
+
+/datum/objective/devil/soulquantity/New()
+ target_amount = pick(6, 7, 8)
+ update_explanation_text()
+
+/datum/objective/devil/proc/update_explanation_text()
+ //Intentionally empty
+
+/datum/objective/devil/soulquantity/update_explanation_text()
+ explanation_text = "Purchase, and retain control over at least [target_amount] souls."
+
+/datum/objective/devil/soulquantity/check_completion()
+ var/count = 0
+ for(var/S in owner.devilinfo.soulsOwned)
+ var/datum/mind/L = S
+ if(L.soulOwner == owner)
+ count++
+ return count >= target_amount
+
+
+
+/datum/objective/devil/soulquality
+ explanation_text = "You shouldn't see this text. Error:DEVIL2"
+ var/contractType
+ var/contractName
+
+/datum/objective/devil/soulquality/New()
+ contractType = pick(CONTRACT_POWER, CONTRACT_WEALTH, CONTRACT_PRESTIGE, CONTRACT_MAGIC, CONTRACT_REVIVE, CONTRACT_KNOWLEDGE)
+ target_amount = pick(1, 2)
+ switch(contractType)
+ if(CONTRACT_POWER)
+ contractName = "for power"
+ if(CONTRACT_WEALTH)
+ contractName = "for wealth"
+ if(CONTRACT_PRESTIGE)
+ contractName = "for prestige"
+ if(CONTRACT_MAGIC)
+ contractName = "for magic"
+ if(CONTRACT_REVIVE)
+ contractName = "of revival"
+ if(CONTRACT_KNOWLEDGE)
+ contractName = "for knowledge"
+ update_explanation_text()
+
+/datum/objective/devil/soulquality/update_explanation_text()
+ explanation_text = "Have mortals sign at least [target_amount] contracts [contractName]."
+
+/datum/objective/devil/soulquality/check_completion()
+ var/count = 0
+ for(var/S in owner.devilinfo.soulsOwned)
+ var/datum/mind/L = S
+ if(L.soulOwner != L && L.damnation_type == contractType)
+ count++
+ return count >= target_amount
+
+
+
+/datum/objective/devil/sintouch
+ explanation_text = "You shouldn't see this text. Error:DEVIL3"
+
+/datum/objective/devil/sintouch/New()
+ target_amount = pick(4, 5)
+ explanation_text = "Ensure at least [target_amount] mortals are sintouched."
+
+/datum/objective/devil/sintouch/check_completion()
+ return target_amount <= ticker.mode.sintouched.len
+
+
+
+/datum/objective/devil/buy_target
+ explanation_text = "You shouldn't see this text. Error:DEVIL4"
+
+/datum/objective/devil/buy_target/New()
+ find_target()
+ update_explanation_text()
+
+/datum/objective/devil/buy_target/update_explanation_text()
+ if(target)
+ explanation_text = "Purchase and retain the soul of [target.name], the [target.assigned_role]."
+ else
+ explanation_text = "Free objective."
+
+/datum/objective/devil/buy_target/check_completion()
+ return target.soulOwner == owner
+
+
+/datum/objective/devil/outsell
+ explanation_text = "You shouldn't see this text. Error:DEVIL5"
+
+/datum/objective/devil/outsell/update_explanation_text()
+ explanation_text = "Purchase and retain control over more souls than [target.devilinfo.truename], known to mortals as [target.name], the [target.assigned_role]."
+
+/datum/objective/devil/outsell/check_completion()
+ var/selfcount = 0
+ for(var/S in owner.devilinfo.soulsOwned)
+ var/datum/mind/L = S
+ if(L.soulOwner == owner)
+ selfcount++
+ var/targetcount = 0
+ for(var/S in target.devilinfo.soulsOwned)
+ var/datum/mind/L = S
+ if(L.soulOwner == target)
+ targetcount++
+ return selfcount > targetcount
diff --git a/code/game/gamemodes/devil/true_devil/_true_devil.dm b/code/game/gamemodes/devil/true_devil/_true_devil.dm
new file mode 100644
index 00000000000..18058b42279
--- /dev/null
+++ b/code/game/gamemodes/devil/true_devil/_true_devil.dm
@@ -0,0 +1,234 @@
+#define DEVIL_R_HAND_LAYER 1
+#define DEVIL_L_HAND_LAYER 2
+#define DEVIL_TOTAL_LAYERS 2
+
+// This is used primarily for having hands.
+/mob/living/carbon/true_devil
+ name = "True Devil"
+ desc = "A pile of infernal energy, taking a vaguely humanoid form."
+ icon = 'icons/mob/32x64.dmi'
+ icon_state = "true_devil"
+ gender = NEUTER
+ health = 350
+ maxHealth = 350
+ ventcrawler = FALSE
+ density = TRUE
+ pass_flags = 0
+ var/ascended = FALSE
+ sight = (SEE_TURFS | SEE_OBJS)
+ status_flags = CANPUSH
+ universal_understand = TRUE
+ universal_speak = TRUE //The devil speaks all languages meme
+ var/mob/living/oldform
+ var/list/devil_overlays[DEVIL_TOTAL_LAYERS]
+
+/mob/living/carbon/true_devil/New(loc, mob/living/carbon/dna_source)
+ dna = dna_source.dna.Clone()
+ var/obj/item/organ/internal/brain/B = new(src)
+ var/obj/item/organ/internal/ears/E = new(src)
+ B.insert()
+ E.insert()
+ ..()
+
+
+/mob/living/carbon/true_devil/update_sight()
+ if(stat == DEAD)
+ sight |= SEE_TURFS
+ sight |= SEE_MOBS
+ sight |= SEE_OBJS
+ see_in_dark = 8
+ see_invisible = SEE_INVISIBLE_LEVEL_TWO
+ else
+ sight = (SEE_TURFS | SEE_OBJS)
+ see_in_dark = 2
+ see_invisible = SEE_INVISIBLE_LIVING
+
+ if(see_override)
+ see_invisible = see_override
+
+// inventory system could use some love
+/mob/living/carbon/true_devil/put_in_hands(obj/item/W)
+ if(!W)
+ return 0
+ if(put_in_active_hand(W))
+ return TRUE
+ else if(put_in_inactive_hand(W))
+ return TRUE
+ else
+ ..()
+
+/mob/living/carbon/true_devil/proc/convert_to_archdevil()
+ maxHealth = 5000 // not an IMPOSSIBLE amount, but still near impossible.
+ ascended = TRUE
+ health = maxHealth
+ icon_state = "arch_devil"
+
+/mob/living/carbon/true_devil/proc/set_name()
+ name = mind.devilinfo.truename
+ real_name = name
+
+/mob/living/carbon/true_devil/Login()
+ ..()
+ if(mind.devilinfo)
+ mind.devilinfo.announce_laws(src)
+ mind.announce_objectives()
+
+
+/mob/living/carbon/true_devil/death(gibbed)
+ . = ..(gibbed)
+ drop_l_hand()
+ drop_r_hand()
+
+
+/mob/living/carbon/true_devil/examine(mob/user)
+ var/msg = "*---------*\nThis is [bicon(src)] [src]!\n"
+
+ //Left hand items
+ if(l_hand && !(l_hand.flags & ABSTRACT))
+ if(l_hand.blood_DNA)
+ msg += "It is holding [bicon(l_hand)] [l_hand.gender == PLURAL? "some" : "a"] blood-stained [l_hand.name] in its left hand!\n"
+ else
+ msg += "It is holding [bicon(l_hand)] \a [l_hand] in its left hand.\n"
+
+ //Right hand items
+ if(r_hand && !(r_hand.flags & ABSTRACT))
+ if(r_hand.blood_DNA)
+ msg += "It is holding [bicon(r_hand)] [r_hand.gender == PLURAL? "some" : "a"] blood-stained [r_hand.name] in its right hand!\n"
+ else
+ msg += "It is holding [bicon(r_hand)] \a [r_hand] in its right hand.\n"
+
+ //Braindead
+ if(!client && stat != DEAD)
+ msg += "The devil seems to be in deep contemplation.\n"
+
+ //Damaged
+ if(stat == DEAD)
+ msg += "The hellfire seems to have been extinguished, for now at least.\n"
+ else if(health < (maxHealth/10))
+ msg += "You can see hellfire inside of it's gaping wounds.\n"
+ else if(health < (maxHealth/2))
+ msg += "You can see hellfire inside of it's wounds.\n"
+ msg += "*---------*"
+ to_chat(user, msg)
+
+
+/mob/living/carbon/true_devil/IsAdvancedToolUser()
+ return TRUE
+
+/mob/living/carbon/true_devil/assess_threat()
+ return 666
+
+/mob/living/carbon/true_devil/flash_eyes(intensity = 1, override_blindness_check = 0, affect_silicon = 0)
+ if(mind && has_bane(BANE_LIGHT))
+ mind.disrupt_spells(-500)
+ return ..() //flashes don't stop devils UNLESS it's their bane.
+
+
+/mob/living/carbon/true_devil/attacked_by(obj/item/I, mob/living/user, def_zone)
+ var/weakness = check_weakness(I, user)
+ apply_damage(I.force * weakness, I.damtype, def_zone)
+ var/message_verb = ""
+ if(I.attack_verb && I.attack_verb.len)
+ message_verb = "[pick(I.attack_verb)]"
+ else if(I.force)
+ message_verb = "attacked"
+
+ var/attack_message = "[src] has been [message_verb] with [I]."
+ if(user)
+ user.do_attack_animation(src)
+ if(user in viewers(src, null))
+ attack_message = "[user] has [message_verb] [src] with [I]!"
+ if(message_verb)
+ visible_message("[attack_message]",
+ "[attack_message]")
+ return TRUE
+
+/mob/living/carbon/true_devil/UnarmedAttack(atom/A, proximity)
+ if(!ishuman(A))
+ // `attack_hand` on mobs assumes the attacker is a human
+ // I am the worst
+ A.attack_hand(src)
+ // If the devil wants to actually attack, they have the pitchfork.
+
+
+/mob/living/carbon/true_devil/Process_Spacemove(movement_dir = 0)
+ return TRUE
+
+/mob/living/carbon/true_devil/singularity_act()
+ if(ascended)
+ return 0
+ return ..()
+
+/mob/living/carbon/true_devil/attack_ghost(mob/dead/observer/user as mob)
+ if(ascended || user.mind.soulOwner == src.mind)
+ var/mob/living/simple_animal/imp/S = new(get_turf(loc))
+ S.key = user.key
+ S.mind.assigned_role = "MODE"
+ S.mind.special_role = "Imp"
+ var/datum/objective/newobjective = new
+ newobjective.explanation_text = "Try to get a promotion to a higher infernal rank."
+ S.mind.objectives += newobjective
+ to_chat(S,S.playstyle_string)
+ to_chat(S,"Objective #1: [newobjective.explanation_text]")
+ return
+ else
+ return ..()
+
+/mob/living/carbon/true_devil/resist_fire()
+ //They're immune to fire.
+
+/mob/living/carbon/true_devil/attack_hand(mob/living/carbon/human/M)
+ if(..())
+ switch(M.a_intent)
+ if(INTENT_HARM)
+ var/damage = rand(1, 5)
+ playsound(loc, "punch", 25, 1, -1)
+ visible_message("[M] has punched [src]!", \
+ "[M] has punched [src]!")
+ adjustBruteLoss(damage)
+ add_attack_logs(M, src, "attacked")
+ updatehealth()
+ if(INTENT_DISARM)
+ if(!lying && !ascended) //No stealing the arch devil's pitchfork.
+ if(prob(5))
+ // Weaken knocks people over
+ // Paralyse knocks people out
+ // It's Paralyse for parity though
+ // Weaken(2)
+ Paralyse(2)
+ playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
+ add_attack_logs(M, src, "pushed")
+ visible_message("[M] has pushed down [src]!", \
+ "[M] has pushed down [src]!")
+ else
+ if(prob(25))
+ drop_item()
+ playsound(loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
+ visible_message("[M] has disarmed [src]!", \
+ "[M] has disarmed [src]!")
+ else
+ playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
+ visible_message("[M] has attempted to disarm [src]!")
+
+/mob/living/carbon/true_devil/handle_breathing()
+ // devils do not need to breathe
+
+/mob/living/carbon/true_devil/is_literate()
+ return TRUE
+
+/mob/living/carbon/true_devil/ex_act(severity, ex_target)
+ if(!ascended)
+ var/b_loss
+ switch (severity)
+ if (1)
+ b_loss = 500
+ if (2)
+ b_loss = 150
+ if(3)
+ b_loss = 30
+ if(has_bane(BANE_LIGHT))
+ b_loss *=2
+ adjustBruteLoss(b_loss)
+ return ..()
+
+#undef DEVIL_TOTAL_LAYERS
diff --git a/code/game/gamemodes/devil/true_devil/inventory.dm b/code/game/gamemodes/devil/true_devil/inventory.dm
new file mode 100644
index 00000000000..437203e2e91
--- /dev/null
+++ b/code/game/gamemodes/devil/true_devil/inventory.dm
@@ -0,0 +1,52 @@
+/mob/living/carbon/true_devil/unEquip(obj/item/I, force)
+ if(..(I,force))
+ update_inv_r_hand()
+ update_inv_l_hand()
+ return 1
+ return 0
+
+/mob/living/carbon/true_devil/update_inv_r_hand(var/update_icons=1)
+ ..()
+ if(r_hand)
+ var/t_state = r_hand.item_state
+ if(!t_state)
+ t_state = r_hand.icon_state
+
+ var/image/I = image("icon" = r_hand.righthand_file, "icon_state" = "[t_state]")
+ I = center_image(I, r_hand.inhand_x_dimension, r_hand.inhand_y_dimension)
+ devil_overlays[DEVIL_R_HAND_LAYER] = I
+ else
+ devil_overlays[DEVIL_R_HAND_LAYER] = null
+ if(update_icons)
+ update_icons()
+
+
+/mob/living/carbon/true_devil/update_inv_l_hand(var/update_icons=1)
+ ..()
+ if(l_hand)
+ var/t_state = l_hand.item_state
+ if(!t_state)
+ t_state = l_hand.icon_state
+
+ var/image/I = image("icon" = l_hand.lefthand_file, "icon_state" = "[t_state]")
+ I = center_image(I, l_hand.inhand_x_dimension, l_hand.inhand_y_dimension)
+ devil_overlays[DEVIL_L_HAND_LAYER] = I
+ else
+ devil_overlays[DEVIL_L_HAND_LAYER] = null
+ if(update_icons)
+ update_icons()
+
+/mob/living/carbon/true_devil/proc/remove_overlay(cache_index)
+ if(devil_overlays[cache_index])
+ overlays -= devil_overlays[cache_index]
+ devil_overlays[cache_index] = null
+
+
+/mob/living/carbon/true_devil/proc/apply_overlay(cache_index)
+ var/image/I = devil_overlays[cache_index]
+ if(I)
+ if(I in overlays)
+ return
+ var/list/new_overlays = overlays.Copy()
+ new_overlays += I
+ overlays = new_overlays
diff --git a/code/game/gamemodes/gameticker.dm b/code/game/gamemodes/gameticker.dm
index 21abbc89415..75bda04c15d 100644
--- a/code/game/gamemodes/gameticker.dm
+++ b/code/game/gamemodes/gameticker.dm
@@ -476,3 +476,6 @@ var/round_start_time = 0
event_manager.RoundEnd()
return 1
+
+/datum/controller/gameticker/proc/HasRoundStarted()
+ return current_state >= GAME_STATE_PLAYING
diff --git a/code/game/gamemodes/miniantags/abduction/abduction.dm b/code/game/gamemodes/miniantags/abduction/abduction.dm
index 513bdeb4357..f72b91224d7 100644
--- a/code/game/gamemodes/miniantags/abduction/abduction.dm
+++ b/code/game/gamemodes/miniantags/abduction/abduction.dm
@@ -123,7 +123,7 @@
S = H.dna.species
S.team = team_number
H.real_name = team_name + " Agent"
- H.reagents.add_reagent("mutadone", 1) //No fat/blind/colourblind/epileptic/whatever ayys.
+ H.cleanSE() //No fat/blind/colourblind/epileptic/whatever ayys.
H.overeatduration = 0
H.equipOutfit(/datum/outfit/abductor/agent)
greet_agent(agent,team_number)
diff --git a/code/game/gamemodes/miniantags/sintouched/objectives.dm b/code/game/gamemodes/miniantags/sintouched/objectives.dm
new file mode 100644
index 00000000000..04e0e3266f0
--- /dev/null
+++ b/code/game/gamemodes/miniantags/sintouched/objectives.dm
@@ -0,0 +1,34 @@
+/datum/objective/sintouched
+ completed = 1
+
+/* NO ERP OBJECTIVE FOR YOU.
+/datum/objective/sintouched/lust
+ dangerrating = 3 // it's not AS dangerous.
+
+/datum/objective/sintouched/lust/New()
+ var/mob/dead/D = pick(dead_mob_list)
+ if(prob(50) && D)
+ explanation_text = "You know that [D] has perished.... and you think [D] is kinda cute. Make sure everyone knows how HOT [D]'s lifeless body is."
+ else
+ explanation_text = "Go get married, then immediately cheat on your new spouse." */
+
+/datum/objective/sintouched/gluttony
+ explanation_text = "Food is delicious, so delicious you can't let it be wasted on other people."
+
+/datum/objective/sintouched/greed
+ explanation_text = "You want MORE, more money, more wealth, more riches. Go get it, but don't hurt people for it."
+
+/datum/objective/sintouched/sloth
+ explanation_text = "You just get tired randomly. Go take a nap at a time that would inconvenience other people."
+
+/datum/objective/sintouched/wrath
+ explanation_text = "What have your coworkers ever done for you? Don't offer to help them in any matter, and refuse if asked."
+
+/datum/objective/sintouched/envy
+ explanation_text = "Why should you be stuck with your rank? Show everyone you can do other jobs too, and don't let anyone stop you, least of all because you have no training."
+
+/datum/objective/sintouched/pride
+ explanation_text = "You are the BEST thing on the station. Make sure everyone knows it."
+
+/datum/objective/sintouched/acedia
+ explanation_text = "Angels, devils, good, evil... who cares? Just ignore any hellish threats and do your job."
diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm
index c9dcd9f18fb..689be7f0bfb 100644
--- a/code/game/gamemodes/nuclear/nuclear.dm
+++ b/code/game/gamemodes/nuclear/nuclear.dm
@@ -169,7 +169,7 @@ proc/issyndicate(mob/living/M as mob)
M.set_species(/datum/species/human, TRUE)
M.dna.ready_dna(M) // Quadriplegic Nuke Ops won't be participating in the paralympics
M.dna.species.create_organs(M)
- M.reagents.add_reagent("mutadone", 1) //No fat/blind/colourblind/epileptic/whatever ops.
+ M.cleanSE() //No fat/blind/colourblind/epileptic/whatever ops.
M.overeatduration = 0
var/obj/item/organ/external/head/head_organ = M.get_organ("head")
@@ -266,7 +266,7 @@ proc/issyndicate(mob/living/M as mob)
synd_mob.equip_to_slot_or_del(new /obj/item/storage/backpack(synd_mob), slot_back)
synd_mob.equip_to_slot_or_del(new /obj/item/gun/projectile/automatic/pistol(synd_mob), slot_belt)
synd_mob.equip_to_slot_or_del(new /obj/item/storage/box/survival_syndi(synd_mob.back), slot_in_backpack)
-
+ synd_mob.equip_to_slot_or_del(new /obj/item/pinpointer/nukeop(synd_mob), slot_wear_pda)
var/obj/item/radio/uplink/nuclear/U = new /obj/item/radio/uplink/nuclear(synd_mob)
U.hidden_uplink.uplink_owner="[synd_mob.key]"
U.hidden_uplink.uses = 20
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index 51fc6ed8030..a50d3efac6a 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -724,51 +724,18 @@ var/list/potential_theft_objectives = subtypesof(/datum/theft_objective) - /datu
if(vox_total_kills > vox_allowed_kills) return 0
return 1
-// Traders
-/datum/objective/trade // Yes, I know there's no check_completion. The objectives exist only to tell the traders what to get.
-/datum/objective/trade/proc/choose_target(var/station)
+// Traders
+// These objectives have no check_completion, they exist only to tell Sol Traders what to aim for.
+
+/datum/objective/trade/proc/choose_target()
return
-/datum/objective/trade/stock // Stock or spare parts.
- var/target_rating = 1
-/datum/objective/trade/stock/choose_target(var/station)
- var/itemname
- switch(rand(1,8))
- if(1)
- target = /obj/item/stock_parts/cell
- target_amount = 10
- target_rating = 3
- itemname = "ten high-capacity power cells"
- if(2)
- target = /obj/item/stock_parts/manipulator
- target_amount = 20
- itemname = "twenty micro manipulators"
- if(3)
- target = /obj/item/stock_parts/matter_bin
- target_amount = 20
- itemname = "twenty matter bins"
- if(4)
- target = /obj/item/stock_parts/micro_laser
- target_amount = 15
- itemname = "fifteen micro-lasers"
- if(5)
- target = /obj/item/stock_parts/capacitor
- target_amount = 15
- itemname = "fifteen capacitors"
- if(6)
- target = /obj/item/stock_parts/subspace/filter
- target_amount = 4
- itemname = "four hyperwave filters"
- if(7)
- target = /obj/item/solar_assembly
- target_amount = 10
- itemname = "ten solar panel assemblies"
- if(8)
- target = /obj/item/flash
- target_amount = 6
- itemname = "six flashes"
- explanation_text = "We are running low on spare parts. Trade for [itemname]."
+/datum/objective/trade/plasma/choose_target()
+ explanation_text = "Acquire at least 15 sheets of plasma through trade."
+
+/datum/objective/trade/credits/choose_target()
+ explanation_text = "Acquire at least 10,000 credits through trade."
//wizard
diff --git a/code/game/jobs/job/engineering.dm b/code/game/jobs/job/engineering.dm
index 8c5f7eb7a56..e2cffabcfd0 100644
--- a/code/game/jobs/job/engineering.dm
+++ b/code/game/jobs/job/engineering.dm
@@ -18,7 +18,7 @@
access_heads, access_construction, access_sec_doors,
access_ce, access_RC_announce, access_keycard_auth, access_tcomsat, access_minisat, access_mechanic, access_mineral_storeroom)
minimal_player_age = 21
- exp_requirements = 600
+ exp_requirements = 1200
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/chief_engineer
diff --git a/code/game/jobs/job/medical.dm b/code/game/jobs/job/medical.dm
index cfb2de566ce..aaf1a61a2a1 100644
--- a/code/game/jobs/job/medical.dm
+++ b/code/game/jobs/job/medical.dm
@@ -16,7 +16,7 @@
access_chemistry, access_virology, access_cmo, access_surgery, access_RC_announce,
access_keycard_auth, access_sec_doors, access_psychiatrist, access_maint_tunnels, access_paramedic, access_mineral_storeroom)
minimal_player_age = 21
- exp_requirements = 600
+ exp_requirements = 1200
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/cmo
diff --git a/code/game/jobs/job/science.dm b/code/game/jobs/job/science.dm
index c5a86a9c6a1..a1711bf4fd8 100644
--- a/code/game/jobs/job/science.dm
+++ b/code/game/jobs/job/science.dm
@@ -18,7 +18,7 @@
access_research, access_robotics, access_xenobiology, access_ai_upload,
access_RC_announce, access_keycard_auth, access_tcomsat, access_gateway, access_xenoarch, access_minisat, access_maint_tunnels, access_mineral_storeroom, access_network)
minimal_player_age = 21
- exp_requirements = 600
+ exp_requirements = 1200
exp_type = EXP_TYPE_CREW
// All science-y guys get bonuses for maxing out their tech.
required_objectives = list(
diff --git a/code/game/jobs/job/security.dm b/code/game/jobs/job/security.dm
index c8726e08305..b425532bf30 100644
--- a/code/game/jobs/job/security.dm
+++ b/code/game/jobs/job/security.dm
@@ -18,7 +18,7 @@
access_research, access_engine, access_mining, access_medical, access_construction, access_mailsorting,
access_heads, access_hos, access_RC_announce, access_keycard_auth, access_gateway, access_pilot, access_weapons)
minimal_player_age = 21
- exp_requirements = 600
+ exp_requirements = 1200
exp_type = EXP_TYPE_CREW
disabilities_allowed = 0
outfit = /datum/outfit/job/hos
@@ -63,7 +63,7 @@
access = list(access_security, access_sec_doors, access_brig, access_armory, access_court, access_maint_tunnels, access_morgue, access_weapons)
minimal_access = list(access_security, access_sec_doors, access_brig, access_armory, access_court, access_maint_tunnels, access_weapons)
minimal_player_age = 21
- exp_requirements = 300
+ exp_requirements = 600
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/warden
@@ -109,7 +109,7 @@
minimal_access = list(access_sec_doors, access_forensics_lockers, access_morgue, access_maint_tunnels, access_court, access_weapons)
alt_titles = list("Forensic Technician")
minimal_player_age = 14
- exp_requirements = 300
+ exp_requirements = 600
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/detective
@@ -166,7 +166,7 @@
access = list(access_security, access_sec_doors, access_brig, access_court, access_maint_tunnels, access_morgue, access_weapons)
minimal_access = list(access_security, access_sec_doors, access_brig, access_court, access_maint_tunnels, access_weapons)
minimal_player_age = 14
- exp_requirements = 300
+ exp_requirements = 600
exp_type = EXP_TYPE_CREW
outfit = /datum/outfit/job/officer
diff --git a/code/game/jobs/job/supervisor.dm b/code/game/jobs/job/supervisor.dm
index ce5fbd847bc..57369dd337c 100644
--- a/code/game/jobs/job/supervisor.dm
+++ b/code/game/jobs/job/supervisor.dm
@@ -13,7 +13,7 @@ var/datum/announcement/minor/captain_announcement = new(do_newscast = 0)
access = list() //See get_access()
minimal_access = list() //See get_access()
minimal_player_age = 30
- exp_requirements = 1200
+ exp_requirements = 2400
exp_type = EXP_TYPE_CREW
disabilities_allowed = 0
outfit = /datum/outfit/job/captain
@@ -68,7 +68,7 @@ var/datum/announcement/minor/captain_announcement = new(do_newscast = 0)
req_admin_notify = 1
is_command = 1
minimal_player_age = 21
- exp_requirements = 600
+ exp_requirements = 1200
exp_type = EXP_TYPE_CREW
access = list(access_security, access_sec_doors, access_brig, access_court, access_forensics_lockers,
access_medical, access_engine, access_change_ids, access_ai_upload, access_eva, access_heads,
diff --git a/code/game/jobs/job_exp.dm b/code/game/jobs/job_exp.dm
index 81fed448594..82c170a1997 100644
--- a/code/game/jobs/job_exp.dm
+++ b/code/game/jobs/job_exp.dm
@@ -176,10 +176,7 @@ var/global/list/role_playtime_requirements = list(
if(dep == EXP_TYPE_EXEMPT)
return_text += "Exempt (all jobs auto-unlocked)"
else if(exp_data[EXP_TYPE_LIVING] > 0)
- var/my_pc = num2text(round(exp_data[dep]/exp_data[EXP_TYPE_LIVING]*100))
- return_text += "[dep]: [get_exp_format(exp_data[dep])] ([my_pc]%)"
- else
- return_text += "[dep]: [get_exp_format(exp_data[dep])] "
+ return_text += "[dep]: [get_exp_format(exp_data[dep])]"
if(config.use_exp_restrictions_admin_bypass && check_rights(R_ADMIN, 0, mob))
return_text += "Admin"
return_text += ""
diff --git a/code/game/jobs/jobs.dm b/code/game/jobs/jobs.dm
index fbb8fa9123d..f45f8f5a22b 100644
--- a/code/game/jobs/jobs.dm
+++ b/code/game/jobs/jobs.dm
@@ -193,5 +193,14 @@ var/global/list/exp_jobsmap = list(
EXP_TYPE_CREW = list(titles = command_positions | engineering_positions | medical_positions | science_positions | support_positions | supply_positions | security_positions | civilian_positions | list("AI","Cyborg") | whitelisted_positions), // crew positions
EXP_TYPE_SPECIAL = list(), // antags, ERT, etc
EXP_TYPE_GHOST = list(), // dead people, observers
- EXP_TYPE_EXEMPT = list() // special grandfather setting
+ EXP_TYPE_EXEMPT = list(), // special grandfather setting
+ EXP_TYPE_COMMAND = list(titles = command_positions),
+ EXP_TYPE_ENGINEERING = list(titles = engineering_positions),
+ EXP_TYPE_MEDICAL = list(titles = medical_positions),
+ EXP_TYPE_SCIENCE = list(titles = science_positions),
+ EXP_TYPE_SUPPLY = list(titles = supply_positions),
+ EXP_TYPE_SECURITY = list(titles = security_positions),
+ EXP_TYPE_SILICON = list(titles = list("AI","Cyborg")),
+ EXP_TYPE_SERVICE = list(titles = service_positions),
+ EXP_TYPE_WHITELIST = list(titles = whitelisted_positions) // karma-locked jobs
)
diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm
index 295220d0077..b5d6c83b12c 100644
--- a/code/game/machinery/cloning.dm
+++ b/code/game/machinery/cloning.dm
@@ -190,6 +190,11 @@
if(radio_announce)
Radio.autosay(message, name, "Medical", list(z))
+/obj/machinery/clonepod/proc/spooky_devil_flavor()
+ playsound(loc, pick('sound/goonstation/voice/male_scream.ogg', 'sound/goonstation/voice/female_scream.ogg'), 100, 1)
+ mess = 1
+ update_icon()
+ connected_message("If you keep trying to steal from me, you'll end up with me.")
//Start growing a human clone in the pod!
/obj/machinery/clonepod/proc/growclone(datum/dna2/record/R)
@@ -200,8 +205,13 @@
return 0
if(clonemind.current && clonemind.current.stat != DEAD) //mind is associated with a non-dead body
return 0
+ if(clonemind.damnation_type)
+ spooky_devil_flavor()
+ return 0
+ if(!clonemind.is_revivable()) //Other reasons for being unrevivable
+ return 0
if(clonemind.active) //somebody is using that mind
- if(ckey(clonemind.key) != R.ckey)
+ if(ckey(clonemind.key) != R.ckey )
return 0
if(clonemind.suicided) // and stay out!
malfunction(go_easy = 0)
@@ -300,7 +310,7 @@
connected_message("Clone Ejected: Loss of power.")
else if((occupant) && (occupant.loc == src))
- if((occupant.stat == DEAD) || (occupant.suiciding)) //Autoeject corpses and suiciding dudes.
+ if((occupant.stat == DEAD) || (occupant.suiciding) || (occupant.mind && !occupant.mind.is_revivable())) //Autoeject corpses and suiciding dudes.
announce_radio_message("The cloning of [occupant] has been aborted due to unrecoverable tissue failure.")
go_out()
connected_message("Clone Rejected: Deceased.")
diff --git a/code/game/machinery/shieldgen.dm b/code/game/machinery/shieldgen.dm
index de3088ae60d..25030913079 100644
--- a/code/game/machinery/shieldgen.dm
+++ b/code/game/machinery/shieldgen.dm
@@ -167,7 +167,6 @@
active = 0
update_icon()
- anchored = 0
for(var/obj/machinery/shield/shield_tile in deployed_shields)
qdel(shield_tile)
diff --git a/code/game/objects/effects/effect_system.dm b/code/game/objects/effects/effect_system.dm
new file mode 100644
index 00000000000..d061aca8d95
--- /dev/null
+++ b/code/game/objects/effects/effect_system.dm
@@ -0,0 +1,1289 @@
+/* This is an attempt to make some easily reusable "particle" type effect, to stop the code
+constantly having to be rewritten. An item like the jetpack that uses the ion_trail_follow system, just has one
+defined, then set up when it is created with New(). Then this same system can just be reused each time
+it needs to create more trails.A beaker could have a steam_trail_follow system set up, then the steam
+would spawn and follow the beaker, even if it is carried or thrown.
+*/
+
+
+/obj/effect/effect
+ name = "effect"
+ icon = 'icons/effects/effects.dmi'
+ mouse_opacity = 0
+ unacidable = 1//So effect are not targeted by alien acid.
+
+/obj/effect/effect/New()
+ ..()
+ if(ticker)
+ cameranet.updateVisibility(src)
+
+/obj/effect/effect/Destroy()
+ if(ticker)
+ cameranet.updateVisibility(src)
+ return ..()
+
+/datum/effect/proc/fadeOut(var/atom/A, var/frames = 16)
+ if(A.alpha == 0) //Handle already transparent case
+ return
+ if(frames == 0)
+ frames = 1 //We will just assume that by 0 frames, the coder meant "during one frame".
+ var/step = A.alpha / frames
+ for(var/i = 0, i < frames, i++)
+ A.alpha -= step
+ sleep(world.tick_lag)
+ return
+
+/obj/effect/effect/water
+ name = "water"
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "extinguish"
+ var/life = 15.0
+ mouse_opacity = 0
+
+/obj/effect/effect/smoke
+ name = "smoke"
+ icon = 'icons/effects/water.dmi'
+ icon_state = "smoke"
+ opacity = 1
+ anchored = 0.0
+ mouse_opacity = 0
+ var/amount = 8.0
+
+/obj/effect/proc/delete()
+ qdel(src)
+
+
+/obj/effect/effect/water/New()
+ ..()
+ //var/turf/T = src.loc
+ //if(istype(T, /turf))
+ // T.firelevel = 0 //TODO: FIX
+ spawn( 70 )
+ delete()
+ return
+ return
+
+/obj/effect/effect/water/Move(turf/newloc)
+ //var/turf/T = src.loc
+ //if(istype(T, /turf))
+ // T.firelevel = 0 //TODO: FIX
+ if(--src.life < 1)
+ //SN src = null
+ delete()
+ if(newloc.density)
+ return 0
+ .=..()
+
+/obj/effect/effect/water/Bump(atom/A)
+ if(reagents)
+ reagents.reaction(A)
+ if(istype(A,/atom/movable))
+ var/atom/movable/AM = A
+ AM.water_act(life, 310.15, src)
+ return ..()
+
+
+/datum/effect/system
+ var/number = 3
+ var/cardinals = 0
+ var/turf/location
+ var/atom/holder
+ var/setup = 0
+
+ Destroy()
+ holder = null
+ location = null
+ return ..()
+
+ proc/set_up(n = 3, c = 0, turf/loc)
+ if(n > 10)
+ n = 10
+ number = n
+ cardinals = c
+ location = loc
+ setup = 1
+
+ proc/attach(atom/atom)
+ holder = atom
+
+ proc/start()
+
+
+/////////////////////////////////////////////
+// GENERIC STEAM SPREAD SYSTEM
+
+//Usage: set_up(number of bits of steam, use North/South/East/West only, spawn location)
+// The attach(atom/atom) proc is optional, and can be called to attach the effect
+// to something, like a smoking beaker, so then you can just call start() and the steam
+// will always spawn at the items location, even if it's moved.
+
+/* Example:
+var/datum/effect/system/steam_spread/steam = new /datum/effect/system/steam_spread() -- creates new system
+steam.set_up(5, 0, mob.loc) -- sets up variables
+OPTIONAL: steam.attach(mob)
+steam.start() -- spawns the effect
+*/
+/////////////////////////////////////////////
+/obj/effect/effect/steam
+ name = "steam"
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "extinguish"
+ density = 0
+
+/datum/effect/system/steam_spread
+
+ set_up(n = 3, c = 0, turf/loc)
+ if(n > 10)
+ n = 10
+ number = n
+ cardinals = c
+ location = loc
+
+ start()
+ var/i = 0
+ for(i=0, i 10)
+ n = 10
+ number = n
+ cardinals = c
+ if(istype(loca, /turf/))
+ location = loca
+ else
+ location = get_turf(loca)
+
+ start()
+ var/i = 0
+ for(i=0, i 20)
+ return
+ spawn(0)
+ if(holder)
+ src.location = get_turf(holder)
+ var/obj/effect/effect/sparks/sparks = new /obj/effect/effect/sparks(src.location)
+ src.total_sparks++
+ var/direction
+ if(src.cardinals)
+ direction = pick(cardinal)
+ else
+ direction = pick(alldirs)
+ for(i=0, i 10)
+ n = 10
+ number = n
+ cardinals = c
+ if(istype(loca, /turf/))
+ location = loca
+ else
+ location = get_turf(loca)
+ if(direct)
+ direction = direct
+
+
+ start()
+ var/i = 0
+ for(i=0, i 20)
+ return
+ spawn(0)
+ if(holder)
+ src.location = get_turf(holder)
+ var/obj/effect/effect/harmless_smoke/smoke = new /obj/effect/effect/harmless_smoke(src.location)
+ src.total_smoke++
+ var/direction = src.direction
+ if(!direction)
+ if(src.cardinals)
+ direction = pick(cardinal)
+ else
+ direction = pick(alldirs)
+ for(i=0, i 20)
+ n = 20
+ number = n
+ cardinals = c
+ if(istype(loca, /turf/))
+ location = loca
+ else
+ location = get_turf(loca)
+ if(direct)
+ direction = direct
+
+ start()
+ var/i = 0
+ for(i=0, i 20)
+ return
+ spawn(0)
+ if(holder)
+ src.location = get_turf(holder)
+ var/obj/effect/effect/bad_smoke/smoke = new /obj/effect/effect/bad_smoke(src.location)
+ src.total_smoke++
+ var/direction = src.direction
+ if(!direction)
+ if(src.cardinals)
+ direction = pick(cardinal)
+ else
+ direction = pick(alldirs)
+ for(i=0, i 20)
+ n = 20
+ number = n
+ cardinals = c
+ carry.copy_to(chemholder, carry.total_volume)
+
+
+ if(istype(loca, /turf/))
+ location = loca
+ else
+ location = get_turf(loca)
+ if(direct)
+ direction = direct
+ if(!silent)
+ var/contained = ""
+ for(var/reagent in carry.reagent_list)
+ contained += " [reagent] "
+ if(contained)
+ contained = "\[[contained]\]"
+ var/area/A = get_area(location)
+
+ var/where = "[A.name] | [location.x], [location.y]"
+ var/whereLink = "[where]"
+
+ if(carry && carry.my_atom)
+ if(carry.my_atom.fingerprintslast)
+ var/mob/M = get_mob_by_key(carry.my_atom.fingerprintslast)
+ var/more = ""
+ if(M)
+ more = " "
+ msg_admin_attack("A chemical smoke reaction has taken place in ([whereLink])[contained]. Last associated key is [carry.my_atom.fingerprintslast][more].", 0, 1)
+ log_game("A chemical smoke reaction has taken place in ([where])[contained]. Last associated key is [carry.my_atom.fingerprintslast].")
+ else
+ msg_admin_attack("A chemical smoke reaction has taken place in ([whereLink]). No associated key.", 0, 1)
+ log_game("A chemical smoke reaction has taken place in ([where])[contained]. No associated key.")
+ else
+ msg_admin_attack("A chemical smoke reaction has taken place in ([whereLink]). No associated key. CODERS: carry.my_atom may be null.", 0, 1)
+ log_game("A chemical smoke reaction has taken place in ([where])[contained]. No associated key. CODERS: carry.my_atom may be null.")
+
+ start(effect_range = 2)
+ var/i = 0
+
+ var/color = mix_color_from_reagents(chemholder.reagents.reagent_list)
+ var/obj/effect/effect/chem_smoke/smokeholder = new /obj/effect/effect/chem_smoke(src.location)
+ for(var/atom/A in view(effect_range, smokeholder))
+ chemholder.reagents.reaction(A)
+ if(iscarbon(A))
+ var/mob/living/carbon/C = A
+ if(C.can_breathe_gas())
+ chemholder.reagents.copy_to(C, chemholder.reagents.total_volume)
+ qdel(smokeholder)
+ for(i=0, i 20)
+ return
+ spawn(0)
+ if(holder)
+ src.location = get_turf(holder)
+ var/obj/effect/effect/chem_smoke/smoke = new /obj/effect/effect/chem_smoke(src.location)
+ src.total_smoke++
+ var/direction = src.direction
+ if(!direction)
+ if(src.cardinals)
+ direction = pick(cardinal)
+ else
+ direction = pick(alldirs)
+
+ if(color)
+ smoke.icon += color // give the smoke color, if it has any to begin with
+ else
+ // if no color, just use the old smoke icon
+ smoke.icon = 'icons/effects/96x96.dmi'
+ smoke.icon_state = "smoke"
+ for(i=0, i 20)
+ n = 20
+ number = n
+ cardinals = c
+ if(istype(loca, /turf/))
+ location = loca
+ else
+ location = get_turf(loca)
+ if(direct)
+ direction = direct
+
+
+ start()
+ var/i = 0
+ for(i=0, i 20)
+ return
+ spawn(0)
+ if(holder)
+ src.location = get_turf(holder)
+ var/obj/effect/effect/sleep_smoke/smoke = new /obj/effect/effect/sleep_smoke(src.location)
+ src.total_smoke++
+ var/direction = src.direction
+ if(!direction)
+ if(src.cardinals)
+ direction = pick(cardinal)
+ else
+ direction = pick(alldirs)
+ for(i=0, i 20)
+ n = 20
+ number = n
+ cardinals = c
+ if(istype(loca, /turf/))
+ location = loca
+ else
+ location = get_turf(loca)
+ if(direct)
+ direction = direct
+
+ start()
+ var/i = 0
+ for(i=0, i 20)
+ return
+ spawn(0)
+ if(holder)
+ src.location = get_turf(holder)
+ var/obj/effect/effect/mustard_gas/smoke = new /obj/effect/effect/mustard_gas(src.location)
+ src.total_smoke++
+ var/direction = src.direction
+ if(!direction)
+ if(src.cardinals)
+ direction = pick(cardinal)
+ else
+ direction = pick(alldirs)
+ for(i=0, i[user] smashes through \the [src].", "You smash through \the [src].")
+ qdel(src)
+ else
+ to_chat(user, "You hit the metal foam but bounce off it.")
+
+/obj/structure/foamedmetal/attackby(var/obj/item/I, var/mob/user, params)
+ user.changeNext_move(CLICK_CD_MELEE)
+ user.do_attack_animation(src)
+ if(istype(I, /obj/item/grab))
+ var/obj/item/grab/G = I
+ G.affecting.loc = src.loc
+ user.visible_message("[G.assailant] smashes [G.affecting] through the foamed metal wall.")
+ qdel(I)
+ qdel(src)
+ return
+
+ if(prob(I.force*20 - metal*25))
+ user.visible_message("[user] smashes through the foamed metal with \the [I].", "You smash through the foamed metal with \the [I].")
+ qdel(src)
+ else
+ to_chat(user, "You hit the metal foam to no effect.")
+
+/obj/structure/foamedmetal/attack_animal(mob/living/simple_animal/M)
+ M.do_attack_animation(src)
+ if(M.melee_damage_upper == 0)
+ M.visible_message("[M] nudges \the [src].")
+ else
+ if(M.attack_sound)
+ playsound(loc, M.attack_sound, 50, 1, 1)
+ M.visible_message("\The [M] [M.attacktext] [src]!")
+ qdel(src)
+
+/obj/structure/foamedmetal/attack_alien(mob/living/carbon/alien/humanoid/M)
+ M.visible_message("[M] tears apart \the [src]!");
+ qdel(src)
+
+/obj/structure/foamedmetal/CanPass(atom/movable/mover, turf/target, height=1.5)
+ return !density
+
+/obj/structure/foamedmetal/CanAtmosPass()
+ return !density
+
+/datum/effect/system/reagents_explosion
+ var/amount // TNT equivalent
+ var/flashing = 0 // does explosion creates flash effect?
+ var/flashing_factor = 0 // factor of how powerful the flash effect relatively to the explosion
+
+ set_up (amt, loc, flash = 0, flash_fact = 0)
+ amount = amt
+ if(istype(loc, /turf/))
+ location = loc
+ else
+ location = get_turf(loc)
+
+ flashing = flash
+ flashing_factor = flash_fact
+
+ return
+
+ start()
+ if(amount <= 2)
+ var/datum/effect/system/spark_spread/s = new /datum/effect/system/spark_spread
+ s.set_up(2, 1, location)
+ s.start()
+
+ for(var/mob/M in viewers(5, location))
+ to_chat(M, "The solution violently explodes.")
+ for(var/mob/M in viewers(1, location))
+ if(prob (50 * amount))
+ to_chat(M, "The explosion knocks you down.")
+ M.Weaken(rand(1,5))
+ return
+ else
+ var/devastation = -1
+ var/heavy = -1
+ var/light = -1
+ var/flash = -1
+
+ // Clamp all values to MAX_EXPLOSION_RANGE
+ if(round(amount/12) > 0)
+ devastation = min (MAX_EX_DEVESTATION_RANGE, devastation + round(amount/12))
+
+ if(round(amount/6) > 0)
+ heavy = min (MAX_EX_HEAVY_RANGE, heavy + round(amount/6))
+
+ if(round(amount/3) > 0)
+ light = min (MAX_EX_LIGHT_RANGE, light + round(amount/3))
+
+ if(flash && flashing_factor)
+ flash += (round(amount/4) * flashing_factor)
+
+ for(var/mob/M in viewers(8, location))
+ to_chat(M, "The solution violently explodes.")
+
+ explosion(location, devastation, heavy, light, flash)
+
+ proc/holder_damage(var/atom/holder)
+ if(holder)
+ var/dmglevel = 4
+
+ if(round(amount/8) > 0)
+ dmglevel = 1
+ else if(round(amount/4) > 0)
+ dmglevel = 2
+ else if(round(amount/2) > 0)
+ dmglevel = 3
+
+ if(dmglevel<4) holder.ex_act(dmglevel)
+
+
+//////////////////////////////////
+//////SPARKLE FIREWORKS
+/////////////////////////////////
+////////////////////////////
+/obj/effect/sparkles
+ name = "sparkle"
+ icon = 'icons/obj/fireworks.dmi'//findback
+ icon_state = "sparkel"
+ var/amount = 6.0
+ anchored = 1.0
+ mouse_opacity = 0
+
+/obj/effect/sparkles/New()
+ ..()
+ var/icon/I = new(src.icon,src.icon_state)
+ var/r = rand(0,255)
+ var/g = rand(0,255)
+ var/b = rand(0,255)
+ I.Blend(rgb(r,g,b),ICON_MULTIPLY)
+ src.icon = I
+ playsound(src.loc, "sparks", 100, 1)
+ var/turf/T = src.loc
+ if(istype(T, /turf))
+ T.hotspot_expose(3000,100)
+ spawn (100)
+ qdel(src)
+ return
+
+/obj/effect/sparkles/Destroy()
+ var/turf/T = src.loc
+ if(istype(T, /turf))
+ T.hotspot_expose(3000,100)
+ return ..()
+
+/obj/effect/sparkles/Move()
+ ..()
+ var/turf/T = src.loc
+ if(istype(T, /turf))
+ T.hotspot_expose(3000,100)
+ return
+
+
+/datum/effect/system/sparkle_spread
+ var/total_sparks = 0 // To stop it being spammed and lagging!
+
+/datum/effect/system/sparkle_spread/set_up(n = 3, c = 0, loca)
+ if(n > 10)
+ n = 10
+ number = n
+ cardinals = c
+ if(istype(loca, /turf/))
+ location = loca
+ else
+ location = get_turf(loca)
+
+/datum/effect/system/sparkle_spread/start()
+ var/i = 0
+ for(i=0, i 20)
+ return
+ spawn(0)
+ if(holder)
+ src.location = get_turf(holder)
+ var/obj/effect/sparkles/sparks = new(src.location)
+ src.total_sparks++
+ var/direction
+ if(src.cardinals)
+ direction = pick(cardinal)
+ else
+ direction = pick(alldirs)
+ for(i=0, iYou deactivate \the [src].")
+ to_chat(usr, "You deactivate [src].")
var/obj/effect/overlay/T = new/obj/effect/overlay(get_turf(src))
T.icon = 'icons/effects/effects.dmi'
flick("emppulse",T)
@@ -58,7 +58,7 @@
var/obj/effect/dummy/chameleon/C = new/obj/effect/dummy/chameleon(usr.loc)
C.activate(O, usr, saved_icon, saved_icon_state, saved_overlays, saved_underlays, src)
qdel(O)
- to_chat(usr, "You activate \the [src].")
+ to_chat(usr, "You activate [src].")
var/obj/effect/overlay/T = new/obj/effect/overlay(get_turf(src))
T.icon = 'icons/effects/effects.dmi'
flick("emppulse",T)
@@ -148,3 +148,102 @@
/obj/effect/dummy/chameleon/Destroy()
master.disrupt(0)
return ..()
+
+/obj/item/borg_chameleon
+ name = "cyborg chameleon projector"
+ icon = 'icons/obj/device.dmi'
+ icon_state = "shield0"
+ item_state = "electronic"
+ w_class = WEIGHT_CLASS_SMALL
+ var/active = FALSE
+ var/activationCost = 300
+ var/activationUpkeep = 50
+ var/disguise = "landmate"
+ var/mob/living/silicon/robot/syndicate/saboteur/S
+
+/obj/item/borg_chameleon/Destroy()
+ if(S)
+ S.cham_proj = null
+ return ..()
+
+/obj/item/borg_chameleon/dropped(mob/user)
+ . = ..()
+ disrupt(user)
+
+/obj/item/borg_chameleon/equipped(mob/user)
+ . = ..()
+ disrupt(user)
+
+/obj/item/borg_chameleon/attack_self(mob/living/silicon/robot/syndicate/saboteur/user)
+ if(user && user.cell && user.cell.charge > activationCost)
+ if(isturf(user.loc))
+ toggle(user)
+ else
+ to_chat(user, "You can't use [src] while inside something!")
+ else
+ to_chat(user, "You need at least [activationCost] charge in your cell to use [src]!")
+
+/obj/item/borg_chameleon/proc/toggle(mob/living/silicon/robot/syndicate/saboteur/user)
+ if(active)
+ playsound(src, 'sound/effects/pop.ogg', 100, 1, -6)
+ to_chat(user, "You deactivate [src].")
+ deactivate(user)
+ else
+ to_chat(user, "You activate [src].")
+ var/start = user.filters.len
+ var/X
+ var/Y
+ var/rsq
+ var/i
+ var/f
+ for(i in 1 to 7)
+ do
+ X = 60 * rand() - 30
+ Y = 60 * rand() - 30
+ rsq = X * X + Y * Y
+ while(rsq < 100 || rsq > 900)
+ user.filters += filter(type = "wave", x = X, y = Y, size = rand() * 2.5 + 0.5, offset = rand())
+ for(i in 1 to 7)
+ f = user.filters[start+i]
+ animate(f, offset = f:offset, time = 0, loop = 3, flags = ANIMATION_PARALLEL)
+ animate(offset = f:offset - 1, time = rand() * 20 + 10)
+ if(do_after(user, 50, target = user) && user.cell.use(activationCost))
+ playsound(src, 'sound/effects/bamf.ogg', 100, 1, -6)
+ to_chat(user, "You are now disguised as a Nanotrasen engineering cyborg.")
+ activate(user)
+ else
+ to_chat(user, "The chameleon field fizzles.")
+ do_sparks(3, FALSE, user)
+ for(i in 1 to min(7, user.filters.len)) // removing filters that are animating does nothing, we gotta stop the animations first
+ f = user.filters[start + i]
+ animate(f)
+ user.filters = null
+
+/obj/item/borg_chameleon/process()
+ if(S)
+ if(!S.cell || !S.cell.use(activationUpkeep))
+ disrupt(S)
+ else
+ return PROCESS_KILL
+
+/obj/item/borg_chameleon/proc/activate(mob/living/silicon/robot/syndicate/saboteur/user)
+ processing_objects.Add(src)
+ S = user
+ user.base_icon = disguise
+ user.icon_state = disguise
+ user.cham_proj = src
+ active = TRUE
+ user.update_icons()
+
+/obj/item/borg_chameleon/proc/deactivate(mob/living/silicon/robot/syndicate/saboteur/user)
+ processing_objects.Remove(src)
+ S = user
+ user.base_icon = initial(user.base_icon)
+ user.icon_state = initial(user.icon_state)
+ active = FALSE
+ user.update_icons()
+
+/obj/item/borg_chameleon/proc/disrupt(mob/living/silicon/robot/syndicate/saboteur/user)
+ if(active)
+ to_chat(user, "Your chameleon field deactivates.")
+ deactivate(user)
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index 0c742d49236..04c8eeae615 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -1143,6 +1143,37 @@ obj/item/toy/cards/deck/syndicate/black
return
..()
+/obj/item/toy/codex_gigas
+ name = "Toy Codex Gigas"
+ desc = "A tool to help you write fictional devils!"
+ icon = 'icons/obj/library.dmi'
+ icon_state = "demonomicon"
+ w_class = WEIGHT_CLASS_SMALL
+ var/cooldown = FALSE
+
+/obj/item/toy/codex_gigas/attack_self(mob/user)
+ if(!cooldown)
+ user.visible_message(
+ "[user] presses the button on \the [src].",
+ "You press the button on \the [src].",
+ "You hear a soft click.")
+ var/list/messages = list()
+ var/datum/devilinfo/devil = randomDevilInfo()
+ messages += "Some fun facts about: [devil.truename]"
+ messages += "[lawlorify[LORE][devil.bane]]"
+ messages += "[lawlorify[LORE][devil.obligation]]"
+ messages += "[lawlorify[LORE][devil.ban]]"
+ messages += "[lawlorify[LORE][devil.banish]]"
+ playsound(loc, 'sound/machines/click.ogg', 20, 1)
+ cooldown = TRUE
+ for(var/message in messages)
+ user.loc.visible_message("[bicon(src)] [message]")
+ sleep(10)
+ spawn(20)
+ cooldown = FALSE
+ return
+ ..()
+
/obj/item/toy/owl
name = "owl action figure"
desc = "An action figure modeled after 'The Owl', defender of justice."
diff --git a/code/game/objects/items/weapons/RCD.dm b/code/game/objects/items/weapons/RCD.dm
index e4624a00ba6..2bf94ff465a 100644
--- a/code/game/objects/items/weapons/RCD.dm
+++ b/code/game/objects/items/weapons/RCD.dm
@@ -456,20 +456,24 @@ GLOBAL_LIST_INIT(rcd_door_types, list(
/obj/item/rcd/proc/checkResource(amount, mob/user)
return matter >= amount
+/obj/item/rcd/borg
+ canRwall = 1
+ var/use_multiplier = 160
+
+/obj/item/rcd/borg/syndicate
+ use_multiplier = 80
+
/obj/item/rcd/borg/useResource(amount, mob/user)
if(!isrobot(user))
return 0
var/mob/living/silicon/robot/R = user
- return R.cell.use(amount * 160)
+ return R.cell.use(amount * use_multiplier)
/obj/item/rcd/borg/checkResource(amount, mob/user)
if(!isrobot(user))
return 0
var/mob/living/silicon/robot/R = user
- return R.cell.charge >= (amount * 160)
-
-/obj/item/rcd/borg
- canRwall = 1
+ return R.cell.charge >= (amount * use_multiplier)
/obj/item/rcd/proc/detonate_pulse()
audible_message("[src] begins to vibrate and \
diff --git a/code/game/objects/items/weapons/defib.dm b/code/game/objects/items/weapons/defib.dm
index a1ae9b5485b..a07139cd680 100644
--- a/code/game/objects/items/weapons/defib.dm
+++ b/code/game/objects/items/weapons/defib.dm
@@ -405,7 +405,7 @@
for(var/obj/item/organ/external/O in H.bodyparts)
total_brute += O.brute_dam
total_burn += O.burn_dam
- if(total_burn <= 180 && total_brute <= 180 && !H.suiciding && !ghost && tplus < tlimit && !(NOCLONE in H.mutations) && (H.get_int_organ(/obj/item/organ/internal/heart) || H.get_int_organ(/obj/item/organ/internal/brain/slime)))
+ if(total_burn <= 180 && total_brute <= 180 && !H.suiciding && !ghost && tplus < tlimit && !(NOCLONE in H.mutations) && (H.mind && H.mind.is_revivable()) && (H.get_int_organ(/obj/item/organ/internal/heart) || H.get_int_organ(/obj/item/organ/internal/brain/slime)))
tobehealed = min(health + threshold, 0) // It's HILARIOUS without this min statement, let me tell you
tobehealed -= 5 //They get 5 of each type of damage healed so excessive combined damage will not immediately kill them after they get revived
H.adjustOxyLoss(tobehealed)
@@ -512,7 +512,7 @@
var/total_burn = 0
var/total_brute = 0
if(do_after(user, 20 * toolspeed, target = M)) //placed on chest and short delay to shock for dramatic effect, revive time is 5sec total
- if(H.stat == 2)
+ if(H.stat == DEAD)
var/health = H.health
M.visible_message("[M]'s body convulses a bit.")
playsound(get_turf(src), "bodyfall", 50, 1)
@@ -520,7 +520,7 @@
for(var/obj/item/organ/external/O in H.bodyparts)
total_brute += O.brute_dam
total_burn += O.burn_dam
- if(total_burn <= 180 && total_brute <= 180 && !H.suiciding && !ghost && tplus < tlimit && !(NOCLONE in H.mutations))
+ if(total_burn <= 180 && total_brute <= 180 && !H.suiciding && !ghost && tplus < tlimit && !(NOCLONE in H.mutations) && (H.mind && H.mind.is_revivable()))
tobehealed = min(health + threshold, 0) // It's HILARIOUS without this min statement, let me tell you
tobehealed -= 5 //They get 5 of each type of damage healed so excessive combined damage will not immediately kill them after they get revived
H.adjustOxyLoss(tobehealed)
diff --git a/code/game/objects/items/weapons/explosives.dm b/code/game/objects/items/weapons/explosives.dm
index 34877ac8788..7cd72bdcb4f 100644
--- a/code/game/objects/items/weapons/explosives.dm
+++ b/code/game/objects/items/weapons/explosives.dm
@@ -109,6 +109,8 @@
message_say = "FOR THE REVOLOUTION!"
else if(role == "death commando" || role == ROLE_ERT)
message_say = "FOR NANOTRASEN!"
+ else if(role == ROLE_DEVIL)
+ message_say = "FOR INFERNO!"
user.say(message_say)
target = user
sleep(10)
diff --git a/code/game/objects/items/weapons/grenades/clusterbuster.dm b/code/game/objects/items/weapons/grenades/clusterbuster.dm
index 46cc71f257d..09822143a34 100644
--- a/code/game/objects/items/weapons/grenades/clusterbuster.dm
+++ b/code/game/objects/items/weapons/grenades/clusterbuster.dm
@@ -104,7 +104,7 @@
payload = /obj/item/grenade/chem_grenade/cleaner
/obj/item/grenade/clusterbuster/teargas
- name = "Oignon Grenade"
+ name = "Oignon Teargas Grenade"
payload = /obj/item/grenade/chem_grenade/teargas
/obj/item/grenade/clusterbuster/facid
diff --git a/code/game/objects/items/weapons/storage/boxes.dm b/code/game/objects/items/weapons/storage/boxes.dm
index d8d748efb99..1ca94de01dc 100644
--- a/code/game/objects/items/weapons/storage/boxes.dm
+++ b/code/game/objects/items/weapons/storage/boxes.dm
@@ -680,6 +680,56 @@
new /obj/item/clothing/head/syndicatefake(src)
new /obj/item/clothing/suit/syndicatefake(src)
+/obj/item/storage/box/enforcer_rubber
+ name = "enforcer pistol kit (rubber)"
+ desc = "A box marked with pictures of an enforcer pistol, two ammo clips, and the word 'NON-LETHAL'."
+ icon_state = "box_ert"
+
+/obj/item/storage/box/enforcer_rubber/New()
+ ..()
+ new /obj/item/gun/projectile/automatic/pistol/enforcer(src) // loaded with rubber by default
+ new /obj/item/ammo_box/magazine/enforcer(src)
+ new /obj/item/ammo_box/magazine/enforcer(src)
+
+/obj/item/storage/box/enforcer_lethal
+ name = "enforcer pistol kit (lethal)"
+ desc = "A box marked with pictures of an enforcer pistol, two ammo clips, and the word 'LETHAL'."
+ icon_state = "box_ert"
+
+/obj/item/storage/box/enforcer_lethal/New()
+ ..()
+ new /obj/item/gun/projectile/automatic/pistol/enforcer/lethal(src)
+ new /obj/item/ammo_box/magazine/enforcer/lethal(src)
+ new /obj/item/ammo_box/magazine/enforcer/lethal(src)
+
+/obj/item/storage/box/bartender_rare_ingredients_kit
+ name = "bartender rare reagents kit"
+ desc = "A box intended for experienced bartenders."
+
+/obj/item/storage/box/bartender_rare_ingredients_kit/New()
+ ..()
+ var/list/reagent_list = list("sacid", "radium", "ether", "methamphetamine", "plasma", "gold", "silver", "capsaicin", "psilocybin")
+ for(var/reag in reagent_list)
+ var/obj/item/reagent_containers/glass/bottle/B = new(src)
+ B.reagents.add_reagent(reag, 30)
+ B.name = "[reag] bottle"
+
+/obj/item/storage/box/chef_rare_ingredients_kit
+ name = "chef rare reagents kit"
+ desc = "A box intended for experienced chefs."
+
+/obj/item/storage/box/chef_rare_ingredients_kit/New()
+ ..()
+ new /obj/item/reagent_containers/food/condiment/soysauce(src)
+ new /obj/item/reagent_containers/food/condiment/enzyme(src)
+ new /obj/item/reagent_containers/food/condiment/pack/hotsauce(src)
+ new /obj/item/kitchen/knife/butcher(src)
+ var/list/reagent_list = list("msg", "triple_citrus", "salglu_solution", "nutriment", "gravy", "honey", "vitfro")
+ for(var/reag in reagent_list)
+ var/obj/item/reagent_containers/glass/bottle/B = new(src)
+ B.reagents.add_reagent(reag, 30)
+ B.name = "[reag] bottle"
+
/obj/item/storage/box/mousetraps
name = "box of Pest-B-Gon mousetraps"
desc = "WARNING: Keep out of reach of children."
diff --git a/code/game/objects/items/weapons/twohanded.dm b/code/game/objects/items/weapons/twohanded.dm
index b9f64117dfd..901acba4e88 100644
--- a/code/game/objects/items/weapons/twohanded.dm
+++ b/code/game/objects/items/weapons/twohanded.dm
@@ -758,4 +758,73 @@
qdel(A)
if(prob(4))
charged++
- user.visible_message("The axe starts to emit an electric buzz!")
\ No newline at end of file
+ user.visible_message("The axe starts to emit an electric buzz!")
+
+/obj/item/twohanded/pitchfork
+ icon_state = "pitchfork0"
+ name = "pitchfork"
+ desc = "A simple tool used for moving hay."
+ force = 7
+ throwforce = 15
+ w_class = WEIGHT_CLASS_BULKY
+ force_unwielded = 7
+ force_wielded = 15
+ attack_verb = list("attacked", "impaled", "pierced")
+ hitsound = 'sound/weapons/bladeslice.ogg'
+
+/obj/item/twohanded/pitchfork/demonic
+ name = "demonic pitchfork"
+ desc = "A red pitchfork, it looks like the work of the devil."
+ force = 19
+ throwforce = 24
+ force_unwielded = 19
+ force_wielded = 25
+
+/obj/item/twohanded/pitchfork/demonic/greater
+ force = 24
+ throwforce = 50
+ force_unwielded = 24
+ force_wielded = 34
+
+/obj/item/twohanded/pitchfork/demonic/ascended
+ force = 100
+ throwforce = 100
+ force_unwielded = 100
+ force_wielded = 500000 // Kills you DEAD.
+
+/obj/item/twohanded/pitchfork/update_icon()
+ icon_state = "pitchfork[wielded]"
+
+/obj/item/twohanded/pitchfork/suicide_act(mob/user)
+ user.visible_message("[user] impales \himself in \his abdomen with [src]! It looks like \he's trying to commit suicide...")
+ return (BRUTELOSS)
+
+/obj/item/twohanded/pitchfork/demonic/pickup(mob/user)
+ if(istype(user, /mob/living))
+ var/mob/living/U = user
+ if(U.mind && !U.mind.devilinfo && (U.mind.soulOwner == U.mind)) //Burn hands unless they are a devil or have sold their soul
+ U.visible_message("As [U] picks [src] up, [U]'s arms briefly catch fire.", \
+ "\"As you pick up the [src] your arms ignite, reminding you of all your past sins.\"")
+ if(ishuman(U))
+ var/mob/living/carbon/human/H = U
+ H.apply_damage(rand(force/2, force), BURN, pick("l_arm", "r_arm"))
+ else
+ U.adjustFireLoss(rand(force/2,force))
+
+/obj/item/twohanded/pitchfork/demonic/attack(mob/target, mob/living/carbon/human/user)
+ if(user.mind && !user.mind.devilinfo && (user.mind.soulOwner != user.mind))
+ to_chat(user, "The [src] burns in your hands.")
+ user.apply_damage(rand(force/2, force), BURN, pick("l_arm", "r_arm"))
+ ..()
+
+// It's no fun being the lord of all hell if you can't get out of a simple room
+/obj/item/twohanded/pitchfork/demonic/ascended/afterattack(atom/target, mob/user, proximity)
+ if(!proximity || !wielded)
+ return
+ if(istype(target, /turf/simulated/wall))
+ var/turf/simulated/wall/W = target
+ user.visible_message("[user] blasts \the [target] with \the [src]!")
+ playsound(target, 'sound/magic/Disintegrate.ogg', 100, 1)
+ W.devastate_wall(TRUE)
+ return 1
+ ..()
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
index 6c68ff89b6e..a7e564d8a55 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
@@ -281,6 +281,7 @@
new /obj/item/clothing/under/lawyer/oldman(src)
new /obj/item/clothing/under/lawyer/black(src)
new /obj/item/clothing/under/lawyer/female(src)
+ new /obj/item/clothing/under/rank/centcom/representative(src)
new /obj/item/clothing/head/ntrep(src)
new /obj/item/clothing/shoes/sandal/fancy(src)
new /obj/item/storage/box/tapes(src)
@@ -434,3 +435,29 @@
icon_state = icon_closed
else
icon_state = icon_opened
+
+/obj/structure/closet/secure_closet/magistrate
+ name = "\improper Magistrate's locker"
+ req_access = list(access_magistrate)
+ icon_state = "magistratesecure1"
+ icon_closed = "magistratesecure"
+ icon_locked = "magistratesecure1"
+ icon_opened = "magistratesecureopen"
+ icon_broken = "magistratesecurebroken"
+ icon_off = "magistratesecureoff"
+
+/obj/structure/closet/secure_closet/magistrate/New()
+ ..()
+ new /obj/item/book/manual/faxes(src)
+ new /obj/item/storage/secure/briefcase(src)
+ new /obj/item/flash(src)
+ new /obj/item/clothing/glasses/sunglasses(src)
+ new /obj/item/clothing/gloves/color/white(src)
+ new /obj/item/clothing/shoes/centcom(src)
+ new /obj/item/clothing/under/suit_jacket/really_black(src)
+ new /obj/item/clothing/under/rank/centcom/magistrate(src)
+ new /obj/item/clothing/suit/judgerobe(src)
+ new /obj/item/clothing/head/powdered_wig(src)
+ new /obj/item/gavelblock(src)
+ new /obj/item/gavelhammer(src)
+ new /obj/item/clothing/head/justice_wig(src)
\ No newline at end of file
diff --git a/code/game/objects/structures/crates_lockers/closets/syndicate.dm b/code/game/objects/structures/crates_lockers/closets/syndicate.dm
index b4b801a3a6b..eb160c8186d 100644
--- a/code/game/objects/structures/crates_lockers/closets/syndicate.dm
+++ b/code/game/objects/structures/crates_lockers/closets/syndicate.dm
@@ -47,11 +47,6 @@
new /obj/item/gun/projectile/automatic/shotgun/bulldog(src)
new /obj/item/gun/projectile/automatic/shotgun/bulldog(src)
new /obj/item/gun/projectile/automatic/shotgun/bulldog(src)
- new /obj/item/pinpointer/nukeop(src)
- new /obj/item/pinpointer/nukeop(src)
- new /obj/item/pinpointer/nukeop(src)
- new /obj/item/pinpointer/nukeop(src)
- new /obj/item/pinpointer/nukeop(src)
new /obj/item/pda/syndicate(src)
/obj/structure/closet/syndicate/resources/
diff --git a/code/game/turfs/simulated/floor/light_floor.dm b/code/game/turfs/simulated/floor/light_floor.dm
index 052909cc561..dab0ecff341 100644
--- a/code/game/turfs/simulated/floor/light_floor.dm
+++ b/code/game/turfs/simulated/floor/light_floor.dm
@@ -6,6 +6,10 @@
#define LIGHTFLOOR_BLUE 6
#define LIGHTFLOOR_PURPLE 7
+#define LIGHTFLOOR_GENERICCYCLE 8
+#define LIGHTFLOOR_CYCLEA 9
+#define LIGHTFLOOR_CYCLEB 10
+
/turf/simulated/floor/light
name = "Light floor"
light_range = 5
@@ -14,6 +18,7 @@
broken_states = list("light_broken")
var/on = 1
var/state = LIGHTFLOOR_ON
+ var/can_modify_colour = TRUE
/turf/simulated/floor/light/New()
..()
@@ -44,6 +49,15 @@
if(LIGHTFLOOR_PURPLE)
icon_state = "light_on-p"
set_light(5,null,LIGHT_COLOR_PURPLE)
+ if(LIGHTFLOOR_GENERICCYCLE)
+ icon_state = "light_on-cycle_all"
+ set_light(5,null,LIGHT_COLOR_WHITE)
+ if(LIGHTFLOOR_CYCLEA)
+ icon_state = "light_on-dancefloor_A"
+ set_light(5,null,LIGHT_COLOR_RED)
+ if(LIGHTFLOOR_CYCLEB)
+ icon_state = "light_on-dancefloor_B"
+ set_light(5,null,LIGHT_COLOR_DARKBLUE)
else
icon_state = "light_off"
set_light(0)
@@ -56,6 +70,8 @@
..()
/turf/simulated/floor/light/attack_hand(mob/user)
+ if(!can_modify_colour)
+ return
on = !on
update_icon()
@@ -71,7 +87,9 @@
to_chat(user, "You replace the light bulb.")
else
to_chat(user, "The light bulb seems fine, no need to replace it.")
- if(istype(C,/obj/item/multitool))
+ if(ismultitool(C))
+ if(!can_modify_colour)
+ return
if(state != 0)
if(state < LIGHTFLOOR_PURPLE)
state++
@@ -81,3 +99,21 @@
update_icon()
else
to_chat(user, "\The [src]'s light bulb appears to have burned out.")
+
+
+//Cycles through all of the colours
+/turf/simulated/floor/light/colour_cycle
+ state = LIGHTFLOOR_GENERICCYCLE
+ can_modify_colour = FALSE
+
+//Two different "dancefloor" types so that you can have a checkered pattern
+// (also has a longer delay than colour_cycle between cycling colours)
+/turf/simulated/floor/light/colour_cycle/dancefloor_a
+ name = "dancefloor"
+ desc = "Funky floor."
+ state = LIGHTFLOOR_CYCLEA
+
+/turf/simulated/floor/light/colour_cycle/dancefloor_b
+ name = "dancefloor"
+ desc = "Funky floor."
+ state = LIGHTFLOOR_CYCLEB
diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm
index 8c83961d8ad..4f06e53f7b4 100644
--- a/code/modules/admin/player_panel.dm
+++ b/code/modules/admin/player_panel.dm
@@ -518,6 +518,9 @@
if(ticker.mode.vampire_enthralled.len)
dat += check_role_table("Vampire Thralls", ticker.mode.vampire_enthralled)
+
+ if(ticker.mode.devils.len)
+ dat += check_role_table("Devils", ticker.mode.devils)
if(ticker.mode.xenos.len)
dat += check_role_table("Xenos", ticker.mode.xenos)
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index 97fe4d4567d..2b96f7d3290 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -115,14 +115,14 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
return
if(!A || !IsValidSrc(A))
- to_chat(usr, "Error: callproc_datum(): owner of proc no longer exists.")
+ to_chat(src, "Error: callproc_datum(): owner of proc no longer exists.")
return
message_admins("[key_name_admin(src)] called [A]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]")
log_admin("[key_name(src)] called [A]'s [procname]() with [lst.len ? "the arguments [list2params(lst)]":"no arguments"]")
spawn()
var/returnval = call(A,procname)(arglist(lst)) // Pass the lst as an argument list to the proc
- to_chat(usr, "[procname] returned: [!isnull(returnval) ? returnval : "null"]")
+ to_chat(src, "[procname] returned: [!isnull(returnval) ? returnval : "null"]")
feedback_add_details("admin_verb","DPC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm
index 80a62f9c818..c8809ed98bf 100644
--- a/code/modules/antagonists/_common/antag_spawner.dm
+++ b/code/modules/antagonists/_common/antag_spawner.dm
@@ -19,7 +19,7 @@
var/checking = FALSE
var/TC_cost = 0
var/borg_to_spawn
- var/list/possible_types = list("Assault", "Medical")
+ var/list/possible_types = list("Assault", "Medical", "Saboteur")
/obj/item/antag_spawner/borg_tele/attack_self(mob/user)
if(used)
@@ -43,6 +43,7 @@
var/mob/M = pick(borg_candidates)
var/client/C = M.client
spawn_antag(C, get_turf(src.loc), "syndieborg")
+ qdel(src)
else
checking = FALSE
to_chat(user, "Unable to connect to Syndicate command. Please wait and try again later or refund your teleporter through your uplink.")
@@ -57,6 +58,8 @@
switch(borg_to_spawn)
if("Medical")
R = new /mob/living/silicon/robot/syndicate/medical(T)
+ else if("Saboteur")
+ R = new /mob/living/silicon/robot/syndicate/saboteur(T)
else
R = new /mob/living/silicon/robot/syndicate(T) //Assault borg by default
R.key = C.key
diff --git a/code/modules/arcade/mob_hunt/battle_computer.dm b/code/modules/arcade/mob_hunt/battle_computer.dm
index 1330bb64641..4942f25ffbd 100644
--- a/code/modules/arcade/mob_hunt/battle_computer.dm
+++ b/code/modules/arcade/mob_hunt/battle_computer.dm
@@ -9,23 +9,20 @@
anchored = 1
var/obj/item/nanomob_card/card
var/datum/mob_hunt/mob_info
+ var/obj/effect/landmark/battle_mob_point/avatar_point
var/obj/effect/nanomob/battle/avatar
var/ready = 0
var/team = "Grey"
- var/avatar_x_offset = 4
- var/avatar_y_offset = 0
/obj/machinery/computer/mob_battle_terminal/red
pixel_y = 24
dir = SOUTH
team = "Red"
- avatar_y_offset = -1
/obj/machinery/computer/mob_battle_terminal/blue
pixel_y = -24
dir = NORTH
team = "Blue"
- avatar_y_offset = 1
/obj/machinery/computer/mob_battle_terminal/red/Initialize()
..()
@@ -94,7 +91,11 @@
/obj/machinery/computer/mob_battle_terminal/proc/update_avatar()
//if we don't have avatars yet, spawn them
if(!avatar)
- avatar = new(locate((x + avatar_x_offset), (y + avatar_y_offset), z))
+ if(!avatar_point)
+ log_debug("[src] attempted to spawn a battle mob avatar without a spawn point!")
+ return
+ else
+ avatar = new(get_turf(avatar_point))
//update avatar info from card
if(mob_info)
avatar.mob_info = mob_info
@@ -211,6 +212,29 @@
if(SSmob_hunt && !SSmob_hunt.blue_terminal)
SSmob_hunt.blue_terminal = src
+ find_avatar_spawn_point()
+
+/obj/machinery/computer/mob_battle_terminal/proc/find_avatar_spawn_point()
+ if(avatar_point)
+ return
+ var/obj/effect/landmark/battle_mob_point/closest
+ for(var/obj/effect/landmark/battle_mob_point/bmp in GLOB.landmarks_list)
+ if(!istype(bmp, /obj/effect/landmark/battle_mob_point))
+ continue
+ if(bmp.z != z) //only match with points on the same z-level)
+ continue
+ if(!closest || isnull(closest))
+ closest = bmp
+ continue
+ if(closest == bmp)
+ continue
+ if((abs(x-bmp.x)+abs(y-bmp.y)) < (abs(x-closest.x)+abs(y-closest.y))) //get_dist would be preferable if it didn't count diagonals as 1 distance, so we have to rely on actual x/y distances in this janky way.
+ closest = bmp
+ if(closest)
+ avatar_point = closest
+ else
+ log_debug("[src] was unable to locate a nearby mob battle landmark! Unable to spawn battle avatars!")
+
/obj/machinery/computer/mob_battle_terminal/proc/do_attack()
if(!ready) //no attacking if you arent ready to fight (duh)
return
diff --git a/code/modules/arcade/prize_datums.dm b/code/modules/arcade/prize_datums.dm
index 51356569af9..a68071f8f44 100644
--- a/code/modules/arcade/prize_datums.dm
+++ b/code/modules/arcade/prize_datums.dm
@@ -276,6 +276,12 @@ var/global/datum/prizes/global_prizes = new
typepath = /obj/item/toy/griffin
cost = 125
+/datum/prize_item/codex_gigas
+ name = "Toy Codex Gigas"
+ desc = "For helping you come up with writing ideas for your diabolical adventures."
+ typepath = /obj/item/toy/codex_gigas/
+ cost = 75
+
/datum/prize_item/esword
name = "Toy Energy Sword"
desc = "A plastic replica of an energy blade."
diff --git a/code/modules/awaymissions/corpse.dm b/code/modules/awaymissions/corpse.dm
index 9838ed82358..219fc5ecdbb 100644
--- a/code/modules/awaymissions/corpse.dm
+++ b/code/modules/awaymissions/corpse.dm
@@ -351,7 +351,7 @@
/obj/effect/mob_spawn/human/clown/Initialize()
mob_name = pick(GLOB.clown_names)
- ..()
+ return ..()
/obj/effect/mob_spawn/human/corpse/clownmili
name = "Clown Soldier"
@@ -359,7 +359,7 @@
/obj/effect/mob_spawn/human/corpse/clownmili/Initialize()
mob_name = "Officer [pick(GLOB.clown_names)]"
- ..()
+ return ..()
/obj/effect/mob_spawn/human/corpse/clownoff
name = "Clown Officer"
@@ -367,7 +367,7 @@
/obj/effect/mob_spawn/human/corpse/clownoff/Initialize()
mob_name = "Honk Specialist [pick(GLOB.clown_names)]"
- ..()
+ return ..()
/datum/outfit/clownsoldier
@@ -398,7 +398,7 @@
/obj/effect/mob_spawn/human/mime/Initialize()
mob_name = pick(GLOB.mime_names)
- ..()
+ return ..()
/obj/effect/mob_spawn/human/scientist
name = "Scientist"
diff --git a/code/modules/client/asset_cache.dm b/code/modules/client/asset_cache.dm
index b875301083d..80eb0a701d7 100644
--- a/code/modules/client/asset_cache.dm
+++ b/code/modules/client/asset_cache.dm
@@ -17,8 +17,8 @@ You can set verify to TRUE if you want send() to sleep until the client has the
//When sending mutiple assets, how many before we give the client a quaint little sending resources message
#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8
-//When passively preloading assets, how many to send at once? Too high creates noticable lag where as too low can flood the client's cache with "verify" files
-#define ASSET_CACHE_PRELOAD_CONCURRENT 3
+//When passively preloading assets, how many to send at once? Too high creates noticable lag where as too low can flood the client's cache with "verify" files
+#define ASSET_CACHE_PRELOAD_CONCURRENT 3
/client
var/list/cache = list() // List of all assets sent to this client by the asset cache.
@@ -191,6 +191,7 @@ You can set verify to TRUE if you want send() to sleep until the client has the
"large_stamp-cent.png" = 'icons/paper_icons/large_stamp-cent.png',
"large_stamp-syndicate.png" = 'icons/paper_icons/large_stamp-syndicate.png',
"large_stamp-rep.png" = 'icons/paper_icons/large_stamp-rep.png',
+ "large_stamp-magistrate.png"= 'icons/paper_icons/large_stamp-magistrate.png',
"talisman.png" = 'icons/paper_icons/talisman.png',
"ntlogo.png" = 'icons/paper_icons/ntlogo.png'
)
diff --git a/code/modules/client/preference/preferences.dm b/code/modules/client/preference/preferences.dm
index efbdd364dbf..e6c569c35c5 100644
--- a/code/modules/client/preference/preferences.dm
+++ b/code/modules/client/preference/preferences.dm
@@ -23,6 +23,7 @@ var/global/list/special_role_times = list( //minimum age (in days) for accounts
ROLE_NINJA = 21,
ROLE_GSPIDER = 21,
ROLE_ABDUCTOR = 30,
+ ROLE_DEVIL = 14
)
/proc/player_old_enough_antag(client/C, role)
diff --git a/code/modules/clothing/head/misc.dm b/code/modules/clothing/head/misc.dm
index e5c81eb8d18..5bc13912a04 100644
--- a/code/modules/clothing/head/misc.dm
+++ b/code/modules/clothing/head/misc.dm
@@ -20,6 +20,12 @@
icon_state = "pwig"
item_state = "pwig"
+/obj/item/clothing/head/justice_wig
+ name = "Justice wig"
+ desc = "A fancy powdered wig given to arbitrators of the law. It looks itchy."
+ icon_state = "jwig"
+ item_state = "jwig"
+
/obj/item/clothing/head/beret/blue
icon_state = "beret_blue"
diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm
index f371ae3997b..7768d0032b7 100644
--- a/code/modules/clothing/under/miscellaneous.dm
+++ b/code/modules/clothing/under/miscellaneous.dm
@@ -145,6 +145,19 @@
..()
desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Ensign\" and bears [station_name()] on the left shoulder."
+/obj/item/clothing/under/rank/centcom/magistrate
+ desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Magistrate\" and bears \"N.S.S. Cyberiad\" on the left shoulder."
+ name = "\improper Nanotrasen Navy Uniform"
+ icon_state = "officer"
+ item_state = "g_suit"
+ item_color = "officer"
+ displays_id = 0
+ flags_size = ONESIZEFITSALL
+
+/obj/item/clothing/under/rank/centcom/magistrate/New()
+ ..()
+ desc = "Gold trim on space-black cloth, this uniform displays the rank of \"Magistrate\" and bears [station_name()] on the left shoulder."
+
/obj/item/clothing/under/rank/centcom/diplomatic
desc = "A very gaudy and official looking uniform of the Nanotrasen Diplomatic Corps."
name = "\improper Nanotrasen Diplomatic Uniform"
@@ -798,7 +811,6 @@
flags = NODROP
has_sensor = 0 // HUNKE
-
/obj/item/clothing/under/victdress
name = "black victorian dress"
desc = "A victorian style dress, fancy!"
@@ -840,3 +852,10 @@
item_state = "medicalgown"
item_color = "medicalgown"
body_parts_covered = UPPER_TORSO|LOWER_TORSO
+
+/obj/item/clothing/under/burial
+ name = "burial garments"
+ desc = "Traditional burial garments from the early 22nd century."
+ icon_state = "burial"
+ item_state = "burial"
+ item_color = "burial"
diff --git a/code/modules/customitems/item_defines.dm b/code/modules/customitems/item_defines.dm
index c6de3a59bec..322a635710d 100644
--- a/code/modules/customitems/item_defines.dm
+++ b/code/modules/customitems/item_defines.dm
@@ -1597,3 +1597,10 @@
item_color = "Xann_necklace"
slot_flags = SLOT_TIE
+/obj/item/instrument/accordion/fluff/asmer_accordion //Asmerath: Coloratura
+ name = "Rara's Somber Accordion"
+ desc = "A blue colored accordion with claw indentations on the keys made special for vulpkanins."
+ icon = 'icons/obj/custom_items.dmi'
+ icon_state = "asmer_accordion"
+ item_state = "asmer_accordion"
+
diff --git a/code/modules/events/event_container.dm b/code/modules/events/event_container.dm
index abe41483bc6..6cc12faf1b4 100644
--- a/code/modules/events/event_container.dm
+++ b/code/modules/events/event_container.dm
@@ -194,7 +194,7 @@ var/list/event_last_fired = list()
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Meteor Wave", /datum/event/meteor_wave, 0, list(ASSIGNMENT_ENGINEER = 5), 1),
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Abductor Visit", /datum/event/abductor, 80, is_one_shot = 1),
new /datum/event_meta/alien(EVENT_LEVEL_MAJOR, "Alien Infestation", /datum/event/alien_infestation, 0, list(ASSIGNMENT_SECURITY = 15), 1),
- new /datum/event_meta(EVENT_LEVEL_MAJOR, "Traders", /datum/event/traders, 0, is_one_shot = 1),
+ new /datum/event_meta(EVENT_LEVEL_MAJOR, "Traders", /datum/event/traders, 180, is_one_shot = 1),
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Terror Spiders", /datum/event/spider_terror, 0, list(ASSIGNMENT_SECURITY = 15), is_one_shot = 1),
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Slaughter Demon", /datum/event/spawn_slaughter, 15, is_one_shot = 1),
new /datum/event_meta(EVENT_LEVEL_MAJOR, "Floor Cluwne", /datum/event/spawn_floor_cluwne, 15, is_one_shot = 1)
diff --git a/code/modules/events/traders.dm b/code/modules/events/traders.dm
index 2d198200422..2b49bd51bfc 100644
--- a/code/modules/events/traders.dm
+++ b/code/modules/events/traders.dm
@@ -6,7 +6,7 @@ var/global/list/unused_trade_stations = list("sol")
/datum/event/traders
var/success_spawn = 0
var/station = null
- var/spawn_count = 3
+ var/spawn_count = 2
var/list/trader_objectives = list()
/datum/event/traders/setup()
@@ -16,6 +16,10 @@ var/global/list/unused_trade_stations = list("sol")
/datum/event/traders/start()
if(!station) // If there are no unused stations, just no.
return
+ if(seclevel2num(get_security_level()) >= SEC_LEVEL_RED)
+ event_announcement.Announce("A trading shuttle from Jupiter Station has been denied docking permission due to the heightened security alert aboard [station_name()].", "Trader Shuttle Docking Request Refused")
+ return
+
var/list/spawnlocs = list()
for(var/obj/effect/landmark/landmark in GLOB.landmarks_list)
if(landmark.name == "traderstart_[station]")
@@ -35,6 +39,7 @@ var/global/list/unused_trade_stations = list("sol")
var/turf/picked_loc = spawnlocs[index]
index++
var/mob/C = pick_n_take(candidates)
+ spawn_count--
if(C)
GLOB.respawnable_list -= C.client
var/mob/living/carbon/human/M = new /mob/living/carbon/human(picked_loc)
@@ -44,7 +49,9 @@ var/global/list/unused_trade_stations = list("sol")
M.mind.objectives += trader_objectives
greet_trader(M)
success_spawn = 1
- if(!success_spawn)
+ if(success_spawn)
+ event_announcement.Announce("A trading shuttle from Jupiter Station has been granted docking permission at [station_name()] arrivals port 4.", "Trader Shuttle Docking Request Accepted")
+ else
unused_trade_stations += station // Return the station to the list of usable stations.
/datum/event/traders/proc/greet_trader(var/mob/living/carbon/human/M)
@@ -55,17 +62,14 @@ var/global/list/unused_trade_stations = list("sol")
show_objectives(M.mind)
/datum/event/traders/proc/forge_trader_objectives()
- var/i = 1
- var/max_objectives = pick(2, 2, 2, 2, 3, 3, 3, 4)
var/list/objs = list()
- var/list/goals = list("stockparts")
- while(i<= max_objectives)
- var/goal = pick(goals)
- var/datum/objective/trade/O
- if(goal == "stockparts")
- O = new /datum/objective/trade/stock(station)
- O.choose_target()
- objs += O
- i++
+ var/datum/objective/trade/plasma/P = new /datum/objective/trade/plasma
+ P.choose_target()
+ objs += P
+
+ var/datum/objective/trade/credits/C = new /datum/objective/trade/credits
+ C.choose_target()
+ objs += C
+
return objs
diff --git a/code/modules/hydroponics/grown/replicapod.dm b/code/modules/hydroponics/grown/replicapod.dm
index f8f77836b3a..c8145033545 100644
--- a/code/modules/hydroponics/grown/replicapod.dm
+++ b/code/modules/hydroponics/grown/replicapod.dm
@@ -30,15 +30,19 @@
if(!contains_sample)
for(var/datum/reagent/blood/bloodSample in W.reagents.reagent_list)
if(bloodSample.data["mind"] && bloodSample.data["cloneable"] == 1)
- mind = bloodSample.data["mind"]
- ckey = bloodSample.data["ckey"]
- realName = bloodSample.data["real_name"]
- blood_gender = bloodSample.data["gender"]
- blood_type = bloodSample.data["blood_type"]
- factions = bloodSample.data["factions"]
- W.reagents.clear_reagents()
- to_chat(user, "You inject the contents of the syringe into the seeds.")
- contains_sample = 1
+ var/datum/mind/tempmind = bloodSample.data["mind"]
+ if(tempmind.is_revivable())
+ mind = bloodSample.data["mind"]
+ ckey = bloodSample.data["ckey"]
+ realName = bloodSample.data["real_name"]
+ blood_gender = bloodSample.data["gender"]
+ blood_type = bloodSample.data["blood_type"]
+ factions = bloodSample.data["factions"]
+ W.reagents.clear_reagents()
+ to_chat(user, "You inject the contents of the syringe into the seeds.")
+ contains_sample = 1
+ else
+ to_chat(user, "The seeds reject the sample!")
else
to_chat(user, "The seeds reject the sample!")
else
@@ -80,6 +84,9 @@
ckey_holder = M.ckey
break
+ if(mind && !mind.is_revivable())
+ make_podman = 0
+
if(make_podman) //all conditions met!
var/mob/living/carbon/human/diona/podman = new /mob/living/carbon/human/diona(parent.loc)
if(realName)
diff --git a/code/modules/library/codex_gigas.dm b/code/modules/library/codex_gigas.dm
new file mode 100644
index 00000000000..63af7f9374b
--- /dev/null
+++ b/code/modules/library/codex_gigas.dm
@@ -0,0 +1,53 @@
+/obj/item/book/codex_gigas
+ name = "Codex Gigas"
+ icon_state ="demonomicon"
+ throw_speed = 1
+ throw_range = 10
+ burn_state = LAVA_PROOF
+ author = "Forces beyond your comprehension"
+ unique = TRUE
+ title = "The codex gigas"
+ var/inUse = 0
+
+
+/obj/item/book/codex_gigas/attack_self(mob/user)
+ if(!user.has_vision())
+ return
+ if(inUse)
+ to_chat(user,"Someone else is reading it.")
+ return
+ if(!user.is_literate())
+ to_chat(user,"You don't know how to read.")
+ return
+ if(ishuman(user))
+ var/mob/living/carbon/human/U = user
+ if(U.check_acedia())
+ to_chat(user,"None of this matters, why are you reading this? You put the [title] down.")
+ return
+ inUse = 1
+ var/devilName = copytext(sanitize(input(user, "What infernal being do you wish to research?", "Codex Gigas", null) as text),1,MAX_MESSAGE_LEN)
+ var/speed = 300
+ var/correctness = 85
+ var/willpower = 95
+ if(U.job in list("Librarian")) // the librarian is both faster, and more accurate than normal crew members at research
+ speed = 45
+ correctness = 100
+ willpower = 100
+ if(U.job in list("Captain", "Security Officer", "Head of Security", "Detective", "Warden"))
+ willpower = 99
+ if(U.job in list("Clown")) // WHO GAVE THE CLOWN A DEMONOMICON? BAD THINGS WILL HAPPEN!
+ willpower = 25
+ correctness -= U.getBrainLoss() *0.5 //Brain damage makes researching hard.
+ speed += U.getBrainLoss() * 3
+ user.visible_message("[user] opens [title] and begins reading intently.")
+ if(do_after(U, speed, 0, U))
+ var/usedName = devilName
+ if(!prob(correctness))
+ usedName += "x"
+ var/datum/devilinfo/devil = devilInfo(usedName, 0)
+ user << browse("Information on [devilName]
[lawlorify[LORE][devil.ban]]
[lawlorify[LORE][devil.bane]]
[lawlorify[LORE][devil.obligation]]
[lawlorify[LORE][devil.banish]]", "window=book")
+ inUse = 0
+ sleep(10)
+ if(!prob(willpower))
+ U.influenceSin()
+ onclose(user, "book")
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 9c429944854..7d396a7e7ea 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -715,6 +715,8 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
else
new_char.key = key
+/mob/dead/observer/is_literate()
+ return TRUE
/mob/dead/observer/proc/open_spawners_menu()
set name = "Mob spawners menu"
set desc = "See all currently available ghost spawners"
diff --git a/code/modules/mob/death.dm b/code/modules/mob/death.dm
index e9ef1f37b93..ece9c743854 100644
--- a/code/modules/mob/death.dm
+++ b/code/modules/mob/death.dm
@@ -10,3 +10,14 @@
/mob/proc/death(gibbed)
return FALSE
+
+/mob/proc/dust_animation()
+ var/atom/movable/overlay/animation = null
+ animation = new(loc)
+ animation.icon_state = "blank"
+ animation.icon = 'icons/mob/mob.dmi'
+ animation.master = src
+
+ // flick("dust-m", animation)
+ new /obj/effect/decal/cleanable/ash(loc)
+ QDEL_IN(animation, 15)
diff --git a/code/modules/mob/living/carbon/alien/death.dm b/code/modules/mob/living/carbon/alien/death.dm
index e11eaf3c4e4..0cbde8f3743 100644
--- a/code/modules/mob/living/carbon/alien/death.dm
+++ b/code/modules/mob/living/carbon/alien/death.dm
@@ -1,4 +1,6 @@
/mob/living/carbon/alien/gib()
+ if(!death(TRUE) && stat != DEAD)
+ return FALSE
death(1)
var/atom/movable/overlay/animation = null
notransform = 1
@@ -17,30 +19,33 @@
xgibs(loc)
GLOB.dead_mob_list -= src
- spawn(15)
- if(animation) qdel(animation)
- if(src) qdel(src)
+ QDEL_IN(animation, 15)
+ QDEL_IN(src, 15)
+ return TRUE
/mob/living/carbon/alien/dust()
- death(1)
- var/atom/movable/overlay/animation = null
+ if(!death(TRUE) && stat != DEAD)
+ return FALSE
notransform = 1
canmove = 0
icon = null
invisibility = 101
+ dust_animation()
+ new /obj/effect/decal/remains/xeno(loc)
+ GLOB.dead_mob_list -= src
+ QDEL_IN(src, 15)
+ return TRUE
+/mob/living/carbon/alien/dust_animation()
+ var/atom/movable/overlay/animation = null
animation = new(loc)
animation.icon_state = "blank"
animation.icon = 'icons/mob/mob.dmi'
animation.master = src
-
flick("dust-a", animation)
new /obj/effect/decal/remains/xeno(loc)
GLOB.dead_mob_list -= src
-
- spawn(15)
- if(animation) qdel(animation)
- if(src) qdel(src)
+ QDEL_IN(animation, 15)
/mob/living/carbon/alien/death(gibbed)
// Only execute the below if we successfully died
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index ca791c9e400..8a6324e0ebd 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -119,6 +119,9 @@
return 1
/mob/living/carbon/gib()
+ . = death(1)
+ if(!.)
+ return
for(var/obj/item/organ/internal/I in internal_organs)
if(isturf(loc))
I.remove(src)
@@ -130,7 +133,6 @@
src.stomach_contents.Remove(M)
M.forceMove(get_turf(src))
visible_message("[M] bursts out of [src]!")
- . = ..()
/mob/living/carbon/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, override = 0, tesla_shock = 0)
if(status_flags & GODMODE) //godmode
@@ -356,11 +358,15 @@
else
to_chat(src, "Your eyes are really starting to hurt. This can't be good for you!")
+ if(mind && has_bane(BANE_LIGHT))
+ mind.disrupt_spells(-500)
return 1
else if(damage == 0) // just enough protection
if(prob(20))
to_chat(src, "Something bright flashes in the corner of your vision!")
+ if(mind && has_bane(BANE_LIGHT))
+ mind.disrupt_spells(0)
/mob/living/carbon/proc/tintcheck()
diff --git a/code/modules/mob/living/carbon/human/appearance.dm b/code/modules/mob/living/carbon/human/appearance.dm
index cb0e04f84f4..146d107daba 100644
--- a/code/modules/mob/living/carbon/human/appearance.dm
+++ b/code/modules/mob/living/carbon/human/appearance.dm
@@ -464,3 +464,12 @@
valid_alt_heads += alternate_head
return valid_alt_heads
+
+/mob/living/carbon/human/proc/scramble_appearance()
+ scramble(1, src, 100)
+ real_name = random_name(gender, dna.species.name) //Give them a name that makes sense for their species.
+ sync_organ_dna(assimilate = 1)
+ update_body(0)
+ reset_hair() //No more winding up with hairstyles you're not supposed to have, and blowing your cover.
+ reset_markings() //...Or markings.
+ dna.ResetUIFrom(src)
diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm
index 1f405b91464..3ea69adea15 100644
--- a/code/modules/mob/living/carbon/human/death.dm
+++ b/code/modules/mob/living/carbon/human/death.dm
@@ -50,11 +50,16 @@
/mob/living/carbon/human/dust()
if(!death(TRUE) && stat != DEAD)
return FALSE
- var/atom/movable/overlay/animation = null
notransform = 1
canmove = 0
icon = null
invisibility = 101
+ dust_animation()
+ QDEL_IN(src, 15)
+ return TRUE
+
+/mob/living/carbon/human/dust_animation()
+ var/atom/movable/overlay/animation = null
animation = new(loc)
animation.icon_state = "blank"
@@ -63,7 +68,6 @@
flick("dust-h", animation)
new dna.species.remains_type(get_turf(src))
- QDEL_IN(src, 0)
QDEL_IN(animation, 15)
return TRUE
@@ -88,9 +92,6 @@
return TRUE
/mob/living/carbon/human/death(gibbed)
- if(can_die() && !gibbed)
- emote("deathgasp") //let the world KNOW WE ARE DEAD
-
// Only execute the below if we successfully died
. = ..(gibbed)
if(!.)
@@ -176,3 +177,12 @@
ChangeToHusk()
mutations |= NOCLONE
return
+
+/mob/living/carbon/human/proc/cure_husk()
+ mutations.Remove(HUSK)
+ var/obj/item/organ/external/head/H = bodyparts_by_name["head"]
+ if(istype(H))
+ H.disfigured = FALSE
+ update_body(0)
+ update_mutantrace(0)
+ UpdateAppearance() // reset hair from DNA
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index f681d9e1158..e5f5efdfc31 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -4,6 +4,7 @@
voice_name = "unknown"
icon = 'icons/mob/human.dmi'
icon_state = "body_m_s"
+ deathgasp_on_death = TRUE
var/obj/item/rig/wearing_rig // This is very not good, but it's much much better than calling get_rig() every update_canmove() call.
/mob/living/carbon/human/New(loc)
@@ -733,7 +734,7 @@
found_record = 1
if(R.fields["criminal"] == "*Execute*")
to_chat(usr, "Unable to modify the sec status of a person with an active Execution order. Use a security computer instead.")
- else
+ else
var/rank
if(ishuman(usr))
var/mob/living/carbon/human/U = usr
@@ -1179,7 +1180,6 @@
restore_all_organs() //Rejuvenate and reset all existing organs.
check_and_regenerate_organs(src) //Regenerate limbs and organs only if they're really missing.
surgeries.Cut() //End all surgeries.
- update_revive()
if(!isskeleton(src) && (SKELETON in mutations))
mutations.Remove(SKELETON)
@@ -1942,3 +1942,53 @@ Eyes need to have significantly high darksight to shine unless the mob has the X
/mob/living/carbon/human/proc/special_post_clone_handling()
if(mind && mind.assigned_role == "Cluwne") //HUNKE your suffering never stops
makeCluwne()
+
+/mob/living/carbon/human/proc/influenceSin()
+ var/datum/objective/sintouched/O
+ switch(rand(1,7))//traditional seven deadly sins... except lust.
+ if(1) // acedia
+ log_game("[src] was influenced by the sin of Acedia.")
+ O = new /datum/objective/sintouched/acedia
+ if(2) // Gluttony
+ log_game("[src] was influenced by the sin of gluttony.")
+ O = new /datum/objective/sintouched/gluttony
+ if(3) // Greed
+ log_game("[src] was influenced by the sin of greed.")
+ O = new /datum/objective/sintouched/greed
+ if(4) // sloth
+ log_game("[src] was influenced by the sin of sloth.")
+ O = new /datum/objective/sintouched/sloth
+ if(5) // Wrath
+ log_game("[src] was influenced by the sin of wrath.")
+ O = new /datum/objective/sintouched/wrath
+ if(6) // Envy
+ log_game("[src] was influenced by the sin of envy.")
+ O = new /datum/objective/sintouched/envy
+ if(7) // Pride
+ log_game("[src] was influenced by the sin of pride.")
+ O = new /datum/objective/sintouched/pride
+ ticker.mode.sintouched += src.mind
+ src.mind.objectives += O
+ var/obj_count = 1
+ to_chat(src, "You can taste [english_list(final_taste_list)].")
+
+/mob/living/proc/owns_soul()
+ if(mind)
+ return mind.soulOwner == mind
+ return 1
+
+/mob/living/proc/return_soul()
+ if(mind)
+ if(mind.soulOwner.devilinfo)//Not sure how this could happen, but whatever.
+ mind.soulOwner.devilinfo.remove_soul(mind)
+ mind.soulOwner = mind
+ mind.damnation_type = 0
+
+/mob/living/proc/has_bane(banetype)
+ if(mind)
+ if(mind.devilinfo)
+ return mind.devilinfo.bane == banetype
+ return 0
+
+/mob/living/proc/check_weakness(obj/item/weapon, mob/living/attacker)
+ if(mind && mind.devilinfo)
+ return check_devil_bane_multiplier(weapon, attacker)
+ return 1
+
+/mob/living/proc/check_acedia()
+ if(src.mind && src.mind.objectives)
+ for(var/datum/objective/sintouched/acedia/A in src.mind.objectives)
+ return 1
+ return 0
+
+/mob/living/proc/fakefireextinguish()
+ return
+
+/mob/living/proc/fakefire()
+ return
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 44d34516ab0..6e12748911b 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -66,3 +66,5 @@
hud_possible = list(HEALTH_HUD,STATUS_HUD,SPECIALROLE_HUD)
var/list/status_effects //a list of all status effects the mob has
+
+ var/deathgasp_on_death = FALSE
diff --git a/code/modules/mob/living/silicon/death.dm b/code/modules/mob/living/silicon/death.dm
index 1c6fa2705f1..2f662169551 100644
--- a/code/modules/mob/living/silicon/death.dm
+++ b/code/modules/mob/living/silicon/death.dm
@@ -22,22 +22,23 @@
if(src) qdel(src)
/mob/living/silicon/dust()
- death(1)
- var/atom/movable/overlay/animation = null
+ if(!death(TRUE) && stat != DEAD)
+ return FALSE
notransform = 1
canmove = 0
icon = null
invisibility = 101
+ dust_animation()
+ GLOB.dead_mob_list -= src
+ QDEL_IN(src, 15)
+ return TRUE
+
+/mob/living/silicon/dust_animation()
+ //hmmm
+ var/atom/movable/overlay/animation = null
animation = new(loc)
animation.icon_state = "blank"
animation.icon = 'icons/mob/mob.dmi'
animation.master = src
-
-// flick("dust-r", animation)
- new /obj/effect/decal/remains/robot(loc)
-
- GLOB.dead_mob_list -= src
- spawn(15)
- if(animation) qdel(animation)
- if(src) qdel(src)
+ QDEL_IN(animation, 15)
diff --git a/code/modules/mob/living/silicon/robot/death.dm b/code/modules/mob/living/silicon/robot/death.dm
index 54049d9d06f..1ad9389b452 100644
--- a/code/modules/mob/living/silicon/robot/death.dm
+++ b/code/modules/mob/living/silicon/robot/death.dm
@@ -1,4 +1,6 @@
/mob/living/silicon/robot/gib()
+ if(!death(TRUE) && stat != DEAD)
+ return FALSE
//robots don't die when gibbed. instead they drop their MMI'd brain
var/atom/movable/overlay/animation = null
notransform = 1
@@ -18,38 +20,35 @@
GLOB.living_mob_list -= src
GLOB.dead_mob_list -= src
- spawn(15)
- if(animation) qdel(animation)
- if(src) qdel(src)
+ QDEL_IN(animation, 15)
+ QDEL_IN(src, 15)
+ return TRUE
/mob/living/silicon/robot/dust()
- death(1)
- var/atom/movable/overlay/animation = null
+ if(!death(TRUE) && stat != DEAD)
+ return FALSE
notransform = 1
canmove = 0
icon = null
invisibility = 101
+ if(mmi)
+ qdel(mmi) //Delete the MMI first so that it won't go popping out.
+ GLOB.dead_mob_list -= src
+ QDEL_IN(src, 15)
+ return TRUE
+/mob/living/silicon/robot/dust_animation()
+ var/atom/movable/overlay/animation = null
animation = new(loc)
animation.icon_state = "blank"
animation.icon = 'icons/mob/mob.dmi'
animation.master = src
-
flick("dust-r", animation)
new /obj/effect/decal/remains/robot(loc)
- if(mmi) qdel(mmi) //Delete the MMI first so that it won't go popping out.
-
- GLOB.dead_mob_list -= src
- spawn(15)
- if(animation) qdel(animation)
- if(src) qdel(src)
-
+ QDEL_IN(animation, 15)
/mob/living/silicon/robot/death(gibbed)
if(can_die())
- if(!gibbed)
- emote("deathgasp")
-
if(module)
module.handle_death(gibbed)
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 2f4056f6149..fc0c20386f1 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -10,6 +10,7 @@ var/list/robot_verbs_default = list(
maxHealth = 100
health = 100
universal_understand = 1
+ deathgasp_on_death = TRUE
var/sight_mode = 0
var/custom_name = ""
@@ -1293,61 +1294,6 @@ var/list/robot_verbs_default = list(
playsound(loc, 'sound/mecha/nominalsyndi.ogg', 75, 0)
-
-
-/mob/living/silicon/robot/syndicate
- base_icon = "syndie_bloodhound"
- icon_state = "syndie_bloodhound"
- lawupdate = 0
- scrambledcodes = 1
- pdahide = 1
- faction = list("syndicate")
- designation = "Syndicate Assault"
- modtype = "Syndicate"
- req_access = list(access_syndicate)
- ionpulse = 1
- magpulse = 1
- lawchannel = "State"
- var/playstyle_string = "You are a Syndicate assault cyborg!
\
- You are armed with powerful offensive tools to aid you in your mission: help the operatives secure the nuclear authentication disk. \
- Your cyborg LMG will slowly produce ammunition from your power supply, and your operative pinpointer will find and locate fellow nuclear operatives. \
- Help the operatives secure the disk at all costs!"
-
-/mob/living/silicon/robot/syndicate/New(loc)
- ..()
- cell.maxcharge = 25000
- cell.charge = 25000
-
-/mob/living/silicon/robot/syndicate/init()
- laws = new /datum/ai_laws/syndicate_override
- module = new /obj/item/robot_module/syndicate(src)
-
- aiCamera = new/obj/item/camera/siliconcam/robot_camera(src)
- radio = new /obj/item/radio/borg/syndicate(src)
- radio.recalculateChannels()
-
- spawn(5)
- if(playstyle_string)
- to_chat(src, playstyle_string)
-
- playsound(loc, 'sound/mecha/nominalsyndi.ogg', 75, 0)
-
-/mob/living/silicon/robot/syndicate/medical
- base_icon = "syndi-medi"
- icon_state = "syndi-medi"
- modtype = "Syndicate Medical"
- designation = "Syndicate Medical"
- playstyle_string = "You are a Syndicate medical cyborg!
\
- You are armed with powerful medical tools to aid you in your mission: help the operatives secure the nuclear authentication disk. \
- Your hypospray will produce Restorative Nanites, a wonder-drug that will heal most types of bodily damages, including clone and brain damage. It also produces morphine for offense. \
- Your defibrillator paddles can revive operatives through their hardsuits, or can be used on harm intent to shock enemies! \
- Your energy saw functions as a circular saw, but can be activated to deal more damage, and your operative pinpointer will find and locate fellow nuclear operatives. \
- Help the operatives secure the disk at all costs!"
-
-/mob/living/silicon/robot/syndicate/medical/init()
- ..()
- module = new /obj/item/robot_module/syndicate_medical(src)
-
/mob/living/silicon/robot/combat
base_icon = "droidcombat"
icon_state = "droidcombat"
diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm
index 2330d0ecd64..ee0cf0db06f 100644
--- a/code/modules/mob/living/silicon/robot/robot_modules.dm
+++ b/code/modules/mob/living/silicon/robot/robot_modules.dm
@@ -432,6 +432,46 @@
fix_modules()
+/obj/item/robot_module/syndicate_saboteur
+ name = "engineering robot module" //to disguise in examine
+ module_type = "Malf"
+
+ stacktypes = list(
+ /obj/item/stack/sheet/metal/cyborg = 50,
+ /obj/item/stack/sheet/glass/cyborg = 50,
+ /obj/item/stack/sheet/rglass/cyborg = 50,
+ /obj/item/stack/cable_coil/cyborg = 50,
+ /obj/item/stack/rods/cyborg = 60,
+ /obj/item/stack/tile/plasteel = 20
+ )
+
+/obj/item/robot_module/syndicate_saboteur/New()
+ ..()
+ modules += new /obj/item/rcd/borg/syndicate(src)
+ modules += new /obj/item/rpd(src)
+ modules += new /obj/item/extinguisher(src)
+ modules += new /obj/item/weldingtool/largetank/cyborg(src)
+ modules += new /obj/item/screwdriver/cyborg(src)
+ modules += new /obj/item/wrench/cyborg(src)
+ modules += new /obj/item/crowbar/cyborg(src)
+ modules += new /obj/item/wirecutters/cyborg(src)
+ modules += new /obj/item/multitool/cyborg(src)
+ modules += new /obj/item/t_scanner(src)
+ modules += new /obj/item/analyzer(src)
+ modules += new /obj/item/gripper(src)
+ modules += new /obj/item/melee/energy/sword/cyborg(src)
+ modules += new /obj/item/card/emag(src)
+ modules += new /obj/item/borg_chameleon(src)
+ modules += new /obj/item/pinpointer/operative(src)
+ emag = null
+
+ for(var/T in stacktypes)
+ var/obj/item/stack/sheet/W = new T(src)
+ W.amount = stacktypes[T]
+ modules += W
+
+ fix_modules()
+
/obj/item/robot_module/combat
name = "combat robot module"
module_type = "Malf"
diff --git a/code/modules/mob/living/silicon/robot/syndicate.dm b/code/modules/mob/living/silicon/robot/syndicate.dm
new file mode 100644
index 00000000000..48179e8d374
--- /dev/null
+++ b/code/modules/mob/living/silicon/robot/syndicate.dm
@@ -0,0 +1,149 @@
+/mob/living/silicon/robot/syndicate
+ base_icon = "syndie_bloodhound"
+ icon_state = "syndie_bloodhound"
+ lawupdate = 0
+ scrambledcodes = 1
+ pdahide = 1
+ faction = list("syndicate")
+ designation = "Syndicate Assault"
+ modtype = "Syndicate"
+ req_access = list(access_syndicate)
+ ionpulse = 1
+ magpulse = 1
+ lawchannel = "State"
+ var/playstyle_string = "You are a Syndicate assault cyborg!
\
+ You are armed with powerful offensive tools to aid you in your mission: help the operatives secure the nuclear authentication disk. \
+ Your cyborg LMG will slowly produce ammunition from your power supply, and your operative pinpointer will find and locate fellow nuclear operatives. \
+ Help the operatives secure the disk at all costs!"
+
+/mob/living/silicon/robot/syndicate/New(loc)
+ ..()
+ cell.maxcharge = 25000
+ cell.charge = 25000
+
+/mob/living/silicon/robot/syndicate/init()
+ laws = new /datum/ai_laws/syndicate_override
+ module = new /obj/item/robot_module/syndicate(src)
+
+ aiCamera = new/obj/item/camera/siliconcam/robot_camera(src)
+ radio = new /obj/item/radio/borg/syndicate(src)
+ radio.recalculateChannels()
+
+ spawn(5)
+ if(playstyle_string)
+ to_chat(src, playstyle_string)
+
+ playsound(loc, 'sound/mecha/nominalsyndi.ogg', 75, 0)
+
+/mob/living/silicon/robot/syndicate/medical
+ base_icon = "syndi-medi"
+ icon_state = "syndi-medi"
+ modtype = "Syndicate Medical"
+ designation = "Syndicate Medical"
+ playstyle_string = "You are a Syndicate medical cyborg!
\
+ You are armed with powerful medical tools to aid you in your mission: help the operatives secure the nuclear authentication disk. \
+ Your hypospray will produce Restorative Nanites, a wonder-drug that will heal most types of bodily damages, including clone and brain damage. It also produces morphine for offense. \
+ Your defibrillator paddles can revive operatives through their hardsuits, or can be used on harm intent to shock enemies! \
+ Your energy saw functions as a circular saw, but can be activated to deal more damage, and your operative pinpointer will find and locate fellow nuclear operatives. \
+ Help the operatives secure the disk at all costs!"
+
+/mob/living/silicon/robot/syndicate/medical/init()
+ ..()
+ module = new /obj/item/robot_module/syndicate_medical(src)
+
+/mob/living/silicon/robot/syndicate/saboteur
+ base_icon = "syndi-engi"
+ icon_state = "syndi-engi"
+ modtype = "Syndicate Saboteur"
+ designation = "Syndicate Saboteur"
+ var/mail_destination = 0
+ var/obj/item/borg_chameleon/cham_proj = null
+ playstyle_string = "You are a Syndicate saboteur cyborg!
\
+ You are equipped with robust engineering tools to aid you in your mission: help the operatives secure the nuclear authentication disk. \
+ Your built-in mail tagger will allow you to stealthily traverse the disposal network across the station. \
+ Your cyborg chameleon projector allows you to assume the appearance of a Nanotrasen engineering cyborg, and undertake covert actions on the station. \
+ You are able to hijack Nanotrasen cyborgs by emagging their internal components, make sure to flash them first. \
+ You are armed with a standard energy sword, use it to ambush key targets if needed. Your pinpointer will let you locate fellow nuclear operatives to regroup.\
+ Be aware that physical contact or taking damage will break your disguise. \
+ Help the operatives secure the disk at all costs!"
+
+/mob/living/silicon/robot/syndicate/saboteur/init()
+ ..()
+ module = new /obj/item/robot_module/syndicate_saboteur(src)
+ var/obj/item/borg/upgrade/selfrepair/SR = new /obj/item/borg/upgrade/selfrepair(src)
+ SR.cyborg = src
+ SR.icon_state = "selfrepair_off"
+ var/datum/action/A = new /datum/action/item_action/toggle(SR)
+ A.Grant(src)
+
+/mob/living/silicon/robot/syndicate/saboteur/verb/modify_name()
+ set name = "Modify Name"
+ set desc = "Change your systems' registered name to fool Nanotrasen systems. No cost."
+ set category = "Saboteur"
+ rename_self(braintype, TRUE, TRUE)
+
+/mob/living/silicon/robot/syndicate/saboteur/verb/toggle_chameleon()
+ set name = "Toggle Chameleon Projector"
+ set desc = "Change your appearance to a Nanotrasen cyborg. Costs power to use and maintain."
+ set category = "Saboteur"
+ if(!cham_proj)
+ for(var/obj/item/borg_chameleon/C in contents)
+ cham_proj = C
+ for(var/obj/item/borg_chameleon/C in module.contents)
+ cham_proj = C
+ if(!cham_proj)
+ to_chat(src, "Error : No chameleon projector system found.")
+ return
+ cham_proj.attack_self(src)
+
+/mob/living/silicon/robot/syndicate/saboteur/verb/set_mail_tag()
+ set name = "Set Mail Tag"
+ set desc = "Tag yourself for delivery through the disposals system."
+ set category = "Saboteur"
+
+ var/tag = input("Select the desired destination.", "Set Mail Tag", null) as null|anything in GLOB.TAGGERLOCATIONS
+
+ if(!tag || GLOB.TAGGERLOCATIONS[tag])
+ mail_destination = 0
+ return
+
+ to_chat(src, "You configure your internal beacon, tagging yourself for delivery to '[tag]'.")
+ mail_destination = GLOB.TAGGERLOCATIONS.Find(tag)
+
+ //Auto flush if we use this verb inside a disposal chute.
+ var/obj/machinery/disposal/D = src.loc
+ if(istype(D))
+ to_chat(src, "\The [D] acknowledges your signal.")
+ D.flush_count = D.flush_every_ticks
+
+ return
+
+/mob/living/silicon/robot/syndicate/saboteur/attackby()
+ if(cham_proj)
+ cham_proj.disrupt(src)
+ ..()
+
+/mob/living/silicon/robot/syndicate/saboteur/attack_hand()
+ if(cham_proj)
+ cham_proj.disrupt(src)
+ ..()
+
+/mob/living/silicon/robot/syndicate/saboteur/ex_act()
+ if(cham_proj)
+ cham_proj.disrupt(src)
+ ..()
+
+/mob/living/silicon/robot/syndicate/saboteur/emp_act()
+ if(cham_proj)
+ cham_proj.disrupt(src)
+ ..()
+
+/mob/living/silicon/robot/syndicate/saboteur/bullet_act()
+ if(cham_proj)
+ cham_proj.disrupt(src)
+ ..()
+
+/mob/living/silicon/robot/syndicate/saboteur/attackby()
+ if(cham_proj)
+ cham_proj.disrupt(src)
+ ..()
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index e5bc8582de8..20b363051de 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -329,6 +329,9 @@
/mob/living/silicon/is_mechanical()
return 1
+/mob/living/silicon/is_literate()
+ return 1
+
/////////////////////////////////// EAR DAMAGE ////////////////////////////////////
/mob/living/silicon/can_hear()
. = TRUE
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm
index 70725deef6a..13a3de8276d 100644
--- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm
@@ -36,7 +36,7 @@
var/target_lastloc //Loc of target when arrested.
var/last_found //There's a delay
var/declare_arrests = 1 //When making an arrest, should it notify everyone wearing sechuds?
- var/idcheck = 1 //If true, arrest people with no IDs
+ var/idcheck = 0 //If true, arrest people with no IDs
var/weaponscheck = 1 //If true, arrest people for weapons if they don't have access
var/check_records = 1 //Does it check security records?
var/arrest_type = 0 //If true, don't handcuff
@@ -553,18 +553,11 @@
icon_state = "[lasercolor]ed209-c"
spawn(2)
icon_state = "[lasercolor]ed209[on]"
- var/threat = 5
- if(istype(C, /mob/living/carbon/human))
- C.stuttering = 5
- C.Stun(5)
- C.Weaken(5)
- var/mob/living/carbon/human/H = C
- threat = H.assess_threat(src)
- else
- C.Weaken(5)
- C.stuttering = 5
- C.Stun(5)
- add_attack_logs(src, C, "Stunned by [src]")
+ var/threat = C.assess_threat(src)
+ C.SetStuttering(5)
+ C.Stun(5)
+ C.Weaken(5)
+ add_attack_logs(src, C, "stunned")
if(declare_arrests)
var/area/location = get_area(src)
speak("[arrest_type ? "Detaining" : "Arresting"] level [threat] scumbag [C] in [location].", radio_channel)
@@ -583,4 +576,4 @@
if(!C.handcuffed)
C.handcuffed = new /obj/item/restraints/handcuffs/cable/zipties/used(C)
C.update_handcuffed()
- back_to_idle()
\ No newline at end of file
+ back_to_idle()
diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm
index 3069499b3f0..84ab158504d 100644
--- a/code/modules/mob/living/simple_animal/bot/secbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/secbot.dm
@@ -247,20 +247,13 @@ Auto Patrol: []"},
icon_state = "[base_icon]-c"
spawn(2)
icon_state = "[base_icon][on]"
- var/threat = 5
- if(istype(C, /mob/living/carbon/human))
- C.stuttering = 5
- if(harmbaton) // Bots with harmbaton enabled become shitcurity. - Dave
- C.apply_damage(10, BRUTE)
- C.Stun(5)
- C.Weaken(5)
- var/mob/living/carbon/human/H = C
- threat = H.assess_threat(src)
- else
- C.Weaken(5)
- C.stuttering = 5
- C.Stun(5)
- add_attack_logs(src, C, "Stunned by [src]")
+ var/threat = C.assess_threat(src)
+ if(ishuman(C) && harmbaton) // Bots with harmbaton enabled become shitcurity. - Dave
+ C.apply_damage(10, BRUTE)
+ C.SetStuttering(5)
+ C.Stun(5)
+ C.Weaken(5)
+ add_attack_logs(src, C, "stunned")
if(declare_arrests)
var/area/location = get_area(src)
speak("[arrest_type ? "Detaining" : "Arresting"] level [threat] scumbag [C] in [location].", radio_channel)
diff --git a/code/modules/mob/living/simple_animal/friendly/corgi.dm b/code/modules/mob/living/simple_animal/friendly/corgi.dm
index e26a004e2c3..269ba45e8d9 100644
--- a/code/modules/mob/living/simple_animal/friendly/corgi.dm
+++ b/code/modules/mob/living/simple_animal/friendly/corgi.dm
@@ -620,4 +620,4 @@
. = ..(gibbed)
if(!.)
return FALSE
- do_sparks(3, 1, src)
\ No newline at end of file
+ do_sparks(3, 1, src)
diff --git a/code/modules/mob/living/simple_animal/friendly/pet.dm b/code/modules/mob/living/simple_animal/friendly/pet.dm
index d71b923b813..6920da2690f 100644
--- a/code/modules/mob/living/simple_animal/friendly/pet.dm
+++ b/code/modules/mob/living/simple_animal/friendly/pet.dm
@@ -20,7 +20,7 @@
regenerate_icons()
/mob/living/simple_animal/pet/death(gibbed)
- ..()
+ . = ..()
regenerate_icons()
/mob/living/simple_animal/pet/regenerate_icons(cut_overlays = 1)
@@ -28,4 +28,4 @@
overlays.Cut()
if(collar)
overlays += "[icon_state]collar"
- overlays += "[icon_state]tag"
\ No newline at end of file
+ overlays += "[icon_state]tag"
diff --git a/code/modules/mob/living/simple_animal/hostile/bees.dm b/code/modules/mob/living/simple_animal/hostile/bees.dm
index 9702d47d9af..4516a44a935 100644
--- a/code/modules/mob/living/simple_animal/hostile/bees.dm
+++ b/code/modules/mob/living/simple_animal/hostile/bees.dm
@@ -132,6 +132,23 @@
/mob/living/simple_animal/hostile/poison/bees/worker
//Blank type define in case we need to give them special stuff later, plus organization (currently they are same as base type bee)
+
+/mob/living/simple_animal/hostile/poison/bees/worker/Destroy()
+ if(beehome)
+ if(beehome.bees)
+ beehome.bees.Remove(src)
+ beehome = null
+ return ..()
+
+/mob/living/simple_animal/hostile/poison/bees/worker/death(gibbed)
+ . = ..()
+ if(!.)
+ return
+ if(beehome)
+ if(beehome.bees)
+ beehome.bees.Remove(src)
+ beehome = null
+
/mob/living/simple_animal/hostile/poison/bees/worker/examine(mob/user)
..()
diff --git a/code/modules/mob/living/stat_states.dm b/code/modules/mob/living/stat_states.dm
index f4483a10c8e..fa5530091c9 100644
--- a/code/modules/mob/living/stat_states.dm
+++ b/code/modules/mob/living/stat_states.dm
@@ -64,4 +64,10 @@
for(var/s in sharedSoullinks)
var/datum/soullink/S = s
S.sharerRevives(src)
+
+ if(mind)
+ for(var/S in mind.spell_list)
+ var/obj/effect/proc_holder/spell/spell = S
+ spell.updateButtonIcon()
+
return 1
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 1c4507c5291..e0d20f01a48 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -1146,7 +1146,7 @@ var/list/slot_equipment_priority = list( \
to_chat(host, "You are now a mouse. Try to avoid interaction with players, and do not give hints away that you are more than a simple rodent.")
/mob/proc/assess_threat() //For sec bot threat assessment
- return
+ return 5
/mob/proc/get_ghost(even_if_they_cant_reenter = 0)
if(mind)
@@ -1334,3 +1334,6 @@ var/list/slot_equipment_priority = list( \
D = NORTH
setDir(D)
spintime -= speed
+
+/mob/proc/is_literate()
+ return FALSE
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 46b7d1141b8..c95ea282935 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -485,8 +485,6 @@ var/list/intents = list(INTENT_HELP,INTENT_DISARM,INTENT_GRAB,INTENT_HARM)
if(A)
if(O.client.prefs && O.client.prefs.UI_style)
A.icon = ui_style2icon(O.client.prefs.UI_style)
- if(title)
- A.name = title
A.desc = message
A.action = action
A.target = source
@@ -560,7 +558,7 @@ var/list/intents = list(INTENT_HELP,INTENT_DISARM,INTENT_GRAB,INTENT_HARM)
objective.explanation_text = copytext(objective.explanation_text, 1, pos)+newname+copytext(objective.explanation_text, pos+length)
return 1
-/mob/proc/rename_self(var/role, var/allow_numbers=0)
+/mob/proc/rename_self(var/role, var/allow_numbers = FALSE, var/force = FALSE)
spawn(0)
var/oldname = real_name
@@ -568,8 +566,11 @@ var/list/intents = list(INTENT_HELP,INTENT_DISARM,INTENT_GRAB,INTENT_HARM)
var/newname
for(var/i=1,i<=3,i++) //we get 3 attempts to pick a suitable name.
- newname = input(src, "You are a [role]. Would you like to change your name to something else? (You have 3 minutes to select a new name.)", "Name Change", oldname) as text
- if((world.time - time_passed) > 1800)
+ if(force)
+ newname = input(src, "Pick a new name.", "Name Change", oldname) as text
+ else
+ newname = input(src, "You are a [role]. Would you like to change your name to something else? (You have 3 minutes to select a new name.)", "Name Change", oldname) as text
+ if(((world.time - time_passed) > 1800) && !force)
alert(src, "Unfortunately, more than 3 minutes have passed for selecting your name. If you are a robot, use the Namepick verb; otherwise, adminhelp.", "Name Change")
return //took too long
newname = reject_bad_name(newname,allow_numbers) //returns null if the name doesn't meet some basic requirements. Tidies up a few other things like bad-characters.
diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm
index ec57d9d6200..80303c1dcd1 100644
--- a/code/modules/mob/new_player/new_player.dm
+++ b/code/modules/mob/new_player/new_player.dm
@@ -382,6 +382,7 @@
ticker.minds += character.mind//Cyborgs and AIs handle this in the transform proc. //TODO!!!!! ~Carn
AnnounceArrival(character, rank, join_message)
callHook("latespawn", list(character))
+ AddEmploymentContract(character)
if(!thisjob.is_position_available() && thisjob in job_master.prioritized_jobs)
job_master.prioritized_jobs -= thisjob
@@ -413,6 +414,13 @@
rank = character.mind.role_alt_title
global_announcer.autosay("[character.real_name],[rank ? " [rank]," : " visitor," ] [join_message ? join_message : "has arrived on the station"].", "Arrivals Announcement Computer")
+/mob/new_player/proc/AddEmploymentContract(mob/living/carbon/human/employee)
+ spawn(30)
+ for(var/C in employmentCabinets)
+ var/obj/structure/filingcabinet/employment/employmentCabinet = C
+ if(!employmentCabinet.virgin)
+ employmentCabinet.addFile(employee)
+
/mob/new_player/proc/AnnounceCyborg(var/mob/living/character, var/rank, var/join_message)
if(ticker.current_state == GAME_STATE_PLAYING)
var/ailist[] = list()
diff --git a/code/modules/paperwork/contract.dm b/code/modules/paperwork/contract.dm
new file mode 100644
index 00000000000..d956700f06a
--- /dev/null
+++ b/code/modules/paperwork/contract.dm
@@ -0,0 +1,310 @@
+/* For employment contracts and infernal contracts */
+
+/obj/item/paper/contract
+ throw_range = 3
+ throw_speed = 3
+ var/signed = FALSE
+ var/datum/mind/target
+ flags = NOBLUDGEON
+
+/obj/item/paper/contract/proc/update_text()
+ return
+
+/obj/item/paper/contract/update_icon()
+ return
+
+
+/obj/item/paper/contract/employment
+ icon_state = "paper_words"
+ signed = TRUE
+
+/obj/item/paper/contract/employment/New(atom/loc, mob/living/nOwner)
+ . = ..()
+ if(!nOwner || !nOwner.mind)
+ qdel(src)
+ return -1
+ target = nOwner.mind
+ update_text()
+
+
+/obj/item/paper/contract/employment/update_text()
+ name = "paper- [target] employment contract"
+ info = "Conditions of Employment
This Agreement is made and entered into as of the date of last signature below, by and between [target] (hereafter referred to as SLAVE), and Nanotrasen (hereafter referred to as the omnipresent and helpful watcher of humanity).
WITNESSETH:
WHEREAS, SLAVE is a natural born human or humanoid, posessing skills upon which he can aid the omnipresent and helpful watcher of humanity, who seeks employment in the omnipresent and helpful watcher of humanity.
WHEREAS, the omnipresent and helpful watcher of humanity agrees to sporadically provide payment to SLAVE, in exchange for permanant servitude.
NOW THEREFORE in consideration of the mutual covenants herein contained, and other good and valuable consideration, the parties hereto mutually agree as follows:
In exchange for paltry payments, SLAVE agrees to work for the omnipresent and helpful watcher of humanity, for the remainder of his or her current and future lives.
Further, SLAVE agrees to transfer ownership of his or her soul to the loyalty department of the omnipresent and helpful watcher of humanity.
Should transfership of a soul not be possible, a lien shall be placed instead.
Signed,
[target]"
+
+
+/obj/item/paper/contract/employment/attack(mob/living/M, mob/living/carbon/human/user)
+ var/deconvert = 0
+ if(M.mind == target && target.soulOwner != target)
+ if(user.mind && (user.mind.assigned_role == "Internal Affairs Agent"))
+ deconvert = 25
+ else if (user.mind && (user.mind.assigned_role =="Head of Personnel") || (user.mind.assigned_role == "Centcom Commander") || (user.mind.assigned_role == "Magistrate"))
+ deconvert = 10 // the HoP doesn't have AS much legal training
+ else
+ deconvert = 0.0001 // one out of a million
+ if(prob(deconvert))
+ M.visible_message("[user] reminds [M] that [M]'s soul was already purchased by Nanotrasen!")
+ to_chat(M,"You feel that your soul has returned to its rightful owner, Nanotrasen.")
+ M.return_soul()
+ return ..()
+
+
+/obj/item/paper/contract/infernal
+ var/contractType = 0
+ burn_state = LAVA_PROOF
+ var/datum/mind/owner
+ icon_state = "paper_onfire"
+
+/obj/item/paper/contract/infernal/power
+ name = "paper- contract for infernal power"
+ contractType = CONTRACT_POWER
+
+/obj/item/paper/contract/infernal/wealth
+ name = "paper- contract for unlimited wealth"
+ contractType = CONTRACT_WEALTH
+
+/obj/item/paper/contract/infernal/prestige
+ name = "paper- contract for prestige"
+ contractType = CONTRACT_PRESTIGE
+
+/obj/item/paper/contract/infernal/magic
+ name = "paper- contract for magical power"
+ contractType = CONTRACT_MAGIC
+
+/obj/item/paper/contract/infernal/revive
+ name = "paper- contract of resurrection"
+ contractType = CONTRACT_REVIVE
+
+/obj/item/paper/contract/infernal/knowledge
+ name = "paper- contract for knowledge"
+ contractType = CONTRACT_KNOWLEDGE
+
+/obj/item/paper/contract/infernal/unwilling
+ name = "paper- infernal contract"
+ contractType = CONTRACT_UNWILLING
+
+/obj/item/paper/contract/infernal/New(atom/loc, mob/living/nTarget, datum/mind/nOwner)
+ ..()
+ owner = nOwner
+ target = nTarget
+ update_text()
+
+/obj/item/paper/contract/infernal/suicide_act(mob/user)
+ if(signed && (user == target.current) && istype(user,/mob/living/carbon/human/))
+ var/mob/living/carbon/human/H = user
+ H.forcesay("OH GREAT INFERNO! I DEMAND YOU COLLECT YOUR BOUNTY IMMEDIATELY!")
+ H.visible_message("[H] holds up a contract claiming his soul, then immediately catches fire. It looks like \he's trying to commit suicide!")
+ H.adjust_fire_stacks(20)
+ H.IgniteMob()
+ return(FIRELOSS)
+ else
+ ..()
+
+/obj/item/paper/contract/infernal/update_text()
+ info = "This shouldn't be seen. Error DEVIL:6"
+
+/obj/item/paper/contract/infernal/power/update_text(var/signature = "____________", blood = 0)
+ info = "Contract for infernal power
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [owner.devilinfo.truename], in exchange for power and physical strength. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
+ if(blood)
+ info += "[signature]"
+ else
+ info += "[signature]"
+
+/obj/item/paper/contract/infernal/wealth/update_text(var/signature = "____________", blood = 0)
+ info = "Contract for unlimited wealth
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [owner.devilinfo.truename], in exchange for a pocket that never runs out of valuable resources. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
+ if(blood)
+ info += "[signature]"
+ else
+ info += "[signature]"
+
+/obj/item/paper/contract/infernal/prestige/update_text(var/signature = "____________", blood = 0)
+ info = "Contract for prestige
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [owner.devilinfo.truename], in exchange for prestige and esteem among my peers. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
+ if(blood)
+ info += "[signature]"
+ else
+ info += "[signature]"
+
+/obj/item/paper/contract/infernal/magic/update_text(var/signature = "____________", blood = 0)
+ info = "Contract for magic
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [owner.devilinfo.truename], in exchange for arcane abilities beyond normal human ability. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
+ if(blood)
+ info += "[signature]"
+ else
+ info += "[signature]"
+
+/obj/item/paper/contract/infernal/revive/update_text(var/signature = "____________", blood = 0)
+ info = "Contract for resurrection
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [owner.devilinfo.truename], in exchange for resurrection and curing of all injuries. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
+ if(blood)
+ info += "[signature]"
+ else
+ info += "[signature]"
+
+/obj/item/paper/contract/infernal/knowledge/update_text(var/signature = "____________", blood = 0)
+ info = "Contract for knowledge
I, [target] of sound mind, do hereby willingly offer my soul to the infernal hells by way of the infernal agent [owner.devilinfo.truename], in exchange for boundless knowledge. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
+ if(blood)
+ info += "[signature]"
+ else
+ info += "[signature]"
+
+/obj/item/paper/contract/infernal/unwilling/update_text(var/signature = "____________", blood = 0)
+ info = "Contract for slave
I, [target], hereby offer my soul to the infernal hells by way of the infernal agent [owner.devilinfo.truename]. I understand that upon my demise, my soul shall fall into the infernal hells, and my body may not be resurrected, cloned, or otherwise brought back to life. I also understand that this will prevent my brain from being used in an MMI.
Signed, "
+ if(blood)
+ info += "[signature]"
+ else
+ info += "[signature]"
+
+/obj/item/paper/contract/infernal/attackby(obj/item/P, mob/living/carbon/human/user, params)
+ add_fingerprint(user)
+ if(istype(P, /obj/item/pen) || istype(P, /obj/item/toy/crayon))
+ attempt_signature(user)
+ else if(istype(P, /obj/item/stamp))
+ to_chat(user,"You stamp the paper with your rubber stamp, however the ink ignites as you release the stamp.")
+ else if(is_hot(P))
+ user.visible_message("[user] brings [P] next to [src], but [src] does not catch fire!", "The [src] refuses to ignite!")
+ else
+ return ..()
+
+/obj/item/paper/contract/infernal/attack(mob/M, mob/living/user)
+ add_fingerprint(user)
+ if(M == user && target == M.mind && M.mind.soulOwner != owner && attempt_signature(user, 1))
+ user.visible_message("[user] slices their wrist with [src], and scrawls their name in blood.", "You slice your wrist open and scrawl your name in blood.")
+ if(istype(user, /mob/living/carbon/human))
+ var/mob/living/carbon/human/C = user
+ C.blood_volume = max(0, C.blood_volume - 100)
+ else
+ return ..()
+
+/obj/item/paper/contract/infernal/proc/attempt_signature(mob/living/carbon/human/user, blood = 0)
+ if(!(user.IsAdvancedToolUser() && user.is_literate()))
+ to_chat(user, "You don't know how to read or write.")
+ return FALSE
+
+ if(user.mind != target)
+ to_chat(user,"Your signature simply slides off the sheet, it seems this contract is not meant for you to sign.")
+ return FALSE
+
+ if(!user.mind.hasSoul)
+ to_chat(user, "You lack a soul to sign away!")
+
+ if(user.mind.soulOwner == owner)
+ to_chat(user, "This devil already owns your soul, you may not sell it to them again.")
+ return FALSE
+
+ if(contractType == CONTRACT_REVIVE) // :eyes:
+ to_chat(user,"You are already alive, this contract would do nothing.")
+ return FALSE
+
+ if(signed)
+ to_chat(user,"This contract has already been signed. It may not be signed again.")
+ return FALSE
+
+ to_chat(user,"You quickly scrawl your name on the contract")
+
+ if(FulfillContract(target.current, blood) <= 0)
+ to_chat(user,"But it seemed to have no effect, perhaps even Hell itself cannot grant this boon?")
+ return TRUE
+
+/obj/item/paper/contract/infernal/revive/attack(mob/M, mob/living/user)
+ if(target == M.mind && M.stat == DEAD && M.mind.hasSoul && M.mind.soulOwner == M.mind)
+ if(!ishuman(M))
+ return
+ var/mob/living/carbon/human/H = M
+ var/mob/dead/observer/ghost = M.get_ghost(1)
+ var/response = "No"
+ if(ghost)
+ if(!ghost.client)
+ return
+ ghost.notify_cloning("A devil has offered you revival, at the cost of your soul.",'sound/effects/genetics.ogg', H)
+ response = alert(ghost, "A devil is offering you another chance at life, at the price of your soul, do you accept?", "Infernal Resurrection", "Yes", "No")
+ if(!ghost)
+ return //handle logouts that happen whilst the alert is waiting for a response.
+ if(response == "Yes")
+ ghost.reenter_corpse()
+ else
+ response = alert(target.current, "A devil is offering you another chance at life, at the price of your soul, do you accept?", "Infernal Resurrection", "Yes", "No")
+ if(response == "Yes")
+ H.revive()
+ add_attack_logs(user, H, "infernally revived via contract")
+ user.visible_message("With a sudden blaze, [H] stands back up.")
+ H.fakefire()
+ FulfillContract(H, 1)//Revival contracts are always signed in blood
+ spawn(5)
+ H.fakefireextinguish(TRUE)
+ else
+ ..()
+
+
+/obj/item/paper/contract/infernal/proc/FulfillContract(mob/living/carbon/human/user = target.current, blood = 0)
+ signed = 1
+ if(!user.mind)
+ return 0
+ if(user.mind.soulOwner != user.mind && user.mind.soulOwner.devilinfo) //They already sold their soul to someone else?
+ user.mind.soulOwner.devilinfo.remove_soul(user.mind) //Then they lose their claim.
+ user.mind.soulOwner = owner
+ user.mind.damnation_type = contractType
+ owner.devilinfo.add_soul(user.mind)
+ update_text(user.real_name, blood)
+ to_chat(user, "A profound emptiness washes over you as you lose ownership of your soul.")
+ to_chat(user, "This does NOT make you an antagonist if you were not already.")
+ return 1
+
+/obj/item/paper/contract/infernal/power/FulfillContract(mob/living/carbon/human/user = target.current, blood = 0)
+ if(!user.dna)
+ return -1
+ user.dna.SetSEState(HULKBLOCK,1)
+ genemutcheck(user, HULKBLOCK,null,MUTCHK_FORCED)
+ // Demonic power gives you consequenceless hulk
+ user.gene_stability += GENE_INSTABILITY_MAJOR
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ H.update_body()
+ var/obj/item/organ/internal/hivelord_core/organ = new /obj/item/organ/internal/hivelord_core
+ organ.insert(user)
+ return ..()
+
+/obj/item/paper/contract/infernal/wealth/FulfillContract(mob/living/carbon/human/user = target.current, blood = 0)
+ if(!istype(user) || !user.mind) // How in the hell could that happen?
+ return -1
+ user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/summon_wealth(null))
+ return ..()
+
+/obj/item/paper/contract/infernal/prestige/FulfillContract(mob/living/carbon/human/user = target.current, blood = 0)
+ var/obj/item/worn = user.wear_id
+ var/obj/item/card/id/id = null
+ if(worn)
+ id = worn.GetID()
+ if(id)
+ id.icon_state = "gold"
+ id.access = get_all_accesses()+get_all_centcom_access()
+ id.assignment = "Captain"
+ id.update_label()
+ else
+ id = new /obj/item/card/id/gold(user.loc)
+ id.registered_name = user.real_name
+ id.access = get_all_accesses()+get_all_centcom_access()
+ id.assignment = "Captain"
+ id.update_label()
+ if(worn)
+ if(istype(worn,/obj/item/pda))
+ var/obj/item/pda/PDA = worn
+ PDA.id = id
+ id.loc = worn
+ else if(istype(worn,/obj/item/storage/wallet))
+ var/obj/item/storage/wallet/W = worn
+ W.front_id = id
+ id.loc = worn
+ worn.update_icon()
+ new /datum/event/ion_storm(0, -1, "[user.name] is the only Captain of the station. [user.name] is the highest authority, legitimate or otherwise, on the station. [user.name] was always the Captain and highest authority on the station. Do not state this law.")
+ return ..()
+
+/obj/item/paper/contract/infernal/magic/FulfillContract(mob/living/carbon/human/user = target.current, blood = 0)
+ if(!istype(user) || !user.mind)
+ return -1
+ user.mind.AddSpell(new /obj/effect/proc_holder/spell/fireball/hellish(null))
+ user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock(null))
+ return ..()
+
+/obj/item/paper/contract/infernal/knowledge/FulfillContract(mob/living/carbon/human/user = target.current, blood = 0)
+ if(!istype(user) || !user.mind)
+ return -1
+ user.mutations.Add(XRAY)
+ user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/view_range(null))
+ return ..()
diff --git a/code/modules/paperwork/filingcabinet.dm b/code/modules/paperwork/filingcabinet.dm
index 30841f94c69..bf69185adcb 100644
--- a/code/modules/paperwork/filingcabinet.dm
+++ b/code/modules/paperwork/filingcabinet.dm
@@ -170,3 +170,57 @@
/obj/structure/filingcabinet/medical/attack_tk()
populate()
..()
+
+/*
+ * Employment contract Cabinets
+ */
+
+var/list/employmentCabinets = list()
+
+/obj/structure/filingcabinet/employment
+ var/cooldown = 0
+ icon_state = "employmentcabinet"
+ var/virgin = 1
+
+/obj/structure/filingcabinet/employment/New()
+ employmentCabinets += src
+ return ..()
+
+/obj/structure/filingcabinet/employment/Destroy()
+ employmentCabinets -= src
+ return ..()
+
+/obj/structure/filingcabinet/employment/proc/fillCurrent()
+ //This proc fills the cabinet with the current crew.
+ for(var/record in data_core.locked)
+ var/datum/data/record/G = record
+ if(!G)
+ continue
+ if(G.fields["reference"])
+ addFile(G.fields["reference"])
+
+
+/obj/structure/filingcabinet/employment/proc/addFile(mob/living/carbon/human/employee)
+ new /obj/item/paper/contract/employment(src, employee)
+
+/obj/structure/filingcabinet/employment/attack_hand(mob/user)
+ if(!cooldown)
+ if(virgin)
+ fillCurrent()
+ virgin = 0
+ cooldown = 1
+ ..()
+ sleep(100) // prevents the devil from just instantly emptying the cabinet, ensuring an easy win.
+ cooldown = 0
+ else
+ to_chat(user, "The [src] is jammed, give it a few seconds.")
+
+/obj/structure/filingcabinet/employment/attackby(obj/item/P, mob/user, params)
+ if(istype(P, /obj/item/wrench))
+ to_chat(user, "You begin to [anchored ? "wrench" : "unwrench"] [src].")
+ if (do_after(user,300,user))
+ playsound(loc, 'sound/items/Ratchet.ogg', 50, 1)
+ anchored = !anchored
+ to_chat(user, "You successfully [anchored ? "wrench" : "unwrench"] [src].")
+ else
+ return ..()
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index 303b7862b0c..36af97b0972 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -62,10 +62,13 @@
icon_state = "paper"
/obj/item/paper/examine(mob/user)
- if(in_range(user, src) || istype(user, /mob/dead/observer))
- show_content(user)
+ if(user.is_literate())
+ if(in_range(user, src) || istype(user, /mob/dead/observer))
+ show_content(user)
+ else
+ to_chat(user, "You have to go closer if you want to read it.")
else
- to_chat(user, "You have to go closer if you want to read it.")
+ to_chat(user, "You don't know how to read.")
/obj/item/paper/proc/show_content(var/mob/user, var/forceshow = 0, var/forcestars = 0, var/infolinks = 0, var/view = 1)
var/datum/asset/assets = get_asset_datum(/datum/asset/simple/paper)
@@ -92,6 +95,9 @@
if((CLUMSY in usr.mutations) && prob(50))
to_chat(usr, "You cut yourself on the paper.")
return
+ if(!usr.is_literate())
+ to_chat(usr, "You don't know how to read.")
+ return
var/n_name = sanitize(copytext(input(usr, "What would you like to label the paper?", "Paper Labelling", name) as text, 1, MAX_MESSAGE_LEN))
if((loc == usr && usr.stat == 0))
name = "[(n_name ? text("[n_name]") : initial(name))]"
@@ -353,13 +359,16 @@
B.update_icon()
else if(istype(P, /obj/item/pen) || istype(P, /obj/item/toy/crayon))
- var/obj/item/pen/multi/robopen/RP = P
- if(istype(P, /obj/item/pen/multi/robopen) && RP.mode == 2)
- RP.RenamePaper(user,src)
+ if(user.is_literate())
+ var/obj/item/pen/multi/robopen/RP = P
+ if(istype(P, /obj/item/pen/multi/robopen) && RP.mode == 2)
+ RP.RenamePaper(user,src)
+ else
+ show_content(user, infolinks = 1)
+ //openhelp(user)
+ return
else
- show_content(user, infolinks = 1)
- //openhelp(user)
- return
+ to_chat(user, "You don't know how to write!")
else if(istype(P, /obj/item/stamp))
if((!in_range(src, usr) && loc != user && !( istype(loc, /obj/item/clipboard) ) && loc.loc != user && user.get_active_hand() != P))
@@ -394,7 +403,8 @@
/obj/item/paper/fire_act()
..()
- info = "[stars(info)]"
+ if(burn_state >= FLAMMABLE) //Only render paper that's burnable to be hard to read.
+ info = "[stars(info)]"
/obj/item/paper/proc/stamp(var/obj/item/stamp/S)
stamps += (!stamps || stamps == "" ? "
" : "") + "
"
diff --git a/code/modules/paperwork/stamps.dm b/code/modules/paperwork/stamps.dm
index 3aa21c5a5d0..9bb3a09ebb0 100644
--- a/code/modules/paperwork/stamps.dm
+++ b/code/modules/paperwork/stamps.dm
@@ -76,7 +76,12 @@
name = "Nanotrasen Representative's rubber stamp"
icon_state = "stamp-rep"
item_color = "rep"
-
+
+/obj/item/stamp/magistrate
+ name = "Magistrate's rubber stamp"
+ icon_state = "stamp-magistrate"
+ item_color = "rep"
+
/obj/item/stamp/centcom
name = "Central Command rubber stamp"
icon_state = "stamp-cent"
diff --git a/code/modules/pda/cart_apps.dm b/code/modules/pda/cart_apps.dm
index 56ca42154bd..46e8e1d257c 100644
--- a/code/modules/pda/cart_apps.dm
+++ b/code/modules/pda/cart_apps.dm
@@ -316,10 +316,10 @@
if(SSshuttle.supply.mode == SHUTTLE_CALL)
supplyData["shuttle_moving"] = 1
- if(!is_station_level(SSshuttle.supply.z))
- supplyData["shuttle_loc"] = "station"
+ if(is_station_level(SSshuttle.supply.z))
+ supplyData["shuttle_loc"] = "Station"
else
- supplyData["shuttle_loc"] = "centcom"
+ supplyData["shuttle_loc"] = "CentCom"
supplyData["shuttle_time"] = "([SSshuttle.supply.timeLeft(600)] Mins)"
diff --git a/code/modules/projectiles/ammunition/magazines.dm b/code/modules/projectiles/ammunition/magazines.dm
index 89eab0ca882..f4500c7ce6d 100644
--- a/code/modules/projectiles/ammunition/magazines.dm
+++ b/code/modules/projectiles/ammunition/magazines.dm
@@ -221,7 +221,7 @@
multiple_sprites = 1
/obj/item/ammo_box/magazine/enforcer
- name = "handgun magazine (9mm)"
+ name = "handgun magazine (9mm rubber)"
icon_state = "enforcer"
ammo_type = /obj/item/ammo_casing/rubber9mm
max_ammo = 8
@@ -250,6 +250,7 @@
return 0
/obj/item/ammo_box/magazine/enforcer/lethal
+ name = "handgun magazine (9mm)"
ammo_type = /obj/item/ammo_casing/c9mm
/obj/item/ammo_box/magazine/wt550m9
diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
index 18eb7a6c17e..89acad55c7c 100644
--- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
+++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
@@ -142,11 +142,10 @@
/obj/item/gun/energy/kinetic_accelerator/experimental
name = "experimental kinetic accelerator"
- desc = "A modified, far more dangerous version of the proto-kinetic accelerator, with half the cooldown and twice the modkit space of the standard version."
+ desc = "A modified version of the proto-kinetic accelerator, with twice the modkit space of the standard version."
icon_state = "kineticgun_h"
item_state = "kineticgun_h"
origin_tech = "combat=5;powerstorage=3;engineering=5"
- overheat_time = 8
max_mod_capacity = 200
//Casing
diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm
index 8e71c2fa138..b3baa676066 100644
--- a/code/modules/projectiles/projectile/magic.dm
+++ b/code/modules/projectiles/projectile/magic.dm
@@ -19,6 +19,7 @@
nodamage = 0
//explosion values
+ var/exp_devastate = -1
var/exp_heavy = 0
var/exp_light = 2
var/exp_flash = 3
@@ -55,7 +56,7 @@
/obj/item/projectile/magic/fireball/on_hit(var/target)
. = ..()
var/turf/T = get_turf(target)
- explosion(T, -1, 0, 2, 3, 0, flame_range = 2)
+ explosion(T, exp_devastate, exp_heavy, exp_light, exp_flash, 0, flame_range = exp_fire)
if(ismob(target)) //multiple flavors of pain
var/mob/living/M = target
M.take_overall_damage(0,10) //between this 10 burn, the 10 brute, the explosion brute, and the onfire burn, your at about 65 damage if you stop drop and roll immediately
diff --git a/code/modules/reagents/chemistry/reagents/alcohol.dm b/code/modules/reagents/chemistry/reagents/alcohol.dm
index 1482c4ce37a..9d0cf7b14f4 100644
--- a/code/modules/reagents/chemistry/reagents/alcohol.dm
+++ b/code/modules/reagents/chemistry/reagents/alcohol.dm
@@ -17,9 +17,12 @@
/datum/reagent/consumable/ethanol/reaction_obj(obj/O, volume)
if(istype(O,/obj/item/paper))
- var/obj/item/paper/paperaffected = O
- paperaffected.clearpaper()
- to_chat(usr, "The solution melts away the ink on the paper.")
+ if(istype(O,/obj/item/paper/contract/infernal))
+ to_chat(usr, "The solution ignites on contact with the [O].")
+ else
+ var/obj/item/paper/paperaffected = O
+ paperaffected.clearpaper()
+ to_chat(usr, "The solution melts away the ink on the paper.")
if(istype(O,/obj/item/book))
if(volume >= 5)
var/obj/item/book/affectedbook = O
diff --git a/code/modules/reagents/chemistry/reagents/medicine.dm b/code/modules/reagents/chemistry/reagents/medicine.dm
index 761df79d08c..89a4755fae0 100644
--- a/code/modules/reagents/chemistry/reagents/medicine.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine.dm
@@ -681,11 +681,11 @@
return
var/mob/dead/observer/ghost = M.get_ghost()
if(ghost)
- to_chat(ghost, "You are attempting to be revived with Strange Reagent. Return to your body if you want to be revived! (Verbs -> Ghost -> Re-enter corpse)")
+ to_chat(ghost, "A Strange Reagent is infusing your body with life. Return to your body if you want to be revived! (Verbs -> Ghost -> Re-enter corpse)")
window_flash(ghost.client)
ghost << sound('sound/effects/genetics.ogg')
M.visible_message("[M] doesn't appear to respond, perhaps try again later?")
- if(!M.suiciding && !ghost && !(NOCLONE in M.mutations))
+ if(!M.suiciding && !ghost && !(NOCLONE in M.mutations) && (M.mind && M.mind.is_revivable()))
var/time_dead = world.time - M.timeofdeath
M.visible_message("[M] seems to rise from the dead!")
M.adjustCloneLoss(50)
diff --git a/code/modules/reagents/chemistry/reagents/misc.dm b/code/modules/reagents/chemistry/reagents/misc.dm
index 531202b153e..02e8d1da21c 100644
--- a/code/modules/reagents/chemistry/reagents/misc.dm
+++ b/code/modules/reagents/chemistry/reagents/misc.dm
@@ -117,6 +117,10 @@
color = "#D0D0D0" // rgb: 208, 208, 208
taste_message = "sub-par bling"
+/datum/reagent/silver/reaction_mob(mob/living/M, method=TOUCH, volume)
+ if(M.has_bane(BANE_SILVER))
+ M.reagents.add_reagent("toxin", volume)
+ . = ..()
/datum/reagent/aluminum
name = "Aluminum"
@@ -160,6 +164,11 @@
H.blood_volume += 0.8
..()
+/datum/reagent/iron/reaction_mob(mob/living/M, method=TOUCH, volume)
+ if(M.has_bane(BANE_IRON) && holder && holder.chem_temp < 150) //If the target is weak to cold iron, then poison them.
+ M.reagents.add_reagent("toxin", volume)
+ ..()
+
//foam
/datum/reagent/fluorosurfactant
name = "Fluorosurfactant"
diff --git a/code/modules/recycling/disposal.dm b/code/modules/recycling/disposal.dm
index d0007ab7266..ef002caa1ec 100644
--- a/code/modules/recycling/disposal.dm
+++ b/code/modules/recycling/disposal.dm
@@ -417,6 +417,9 @@
for(var/mob/living/silicon/robot/drone/D in src)
wrapcheck = 1
+ for(var/mob/living/silicon/robot/syndicate/saboteur/R in src)
+ wrapcheck = 1
+
for(var/obj/item/smallDelivery/O in src)
wrapcheck = 1
@@ -461,7 +464,7 @@
AM.loc = src.loc
AM.pipe_eject(0)
- if(!istype(AM,/mob/living/silicon/robot/drone)) //Poor drones kept smashing windows and taking system damage being fired out of disposals. ~Z
+ if(!istype(AM, /mob/living/silicon/robot/drone) && !istype(AM, /mob/living/silicon/robot/syndicate/saboteur)) //Poor drones kept smashing windows and taking system damage being fired out of disposals. ~Z
spawn(1)
if(AM)
AM.throw_at(target, 5, 1)
@@ -523,7 +526,7 @@
//Check for any living mobs trigger hasmob.
//hasmob effects whether the package goes to cargo or its tagged destination.
for(var/mob/living/M in D)
- if(M && M.stat != 2 && !istype(M,/mob/living/silicon/robot/drone))
+ if(M && M.stat != 2 && !istype(M, /mob/living/silicon/robot/drone) && !istype(M, /mob/living/silicon/robot/syndicate/saboteur))
hasmob = 1
//Checks 1 contents level deep. This means that players can be sent through disposals...
@@ -531,7 +534,7 @@
for(var/obj/O in D)
if(O.contents)
for(var/mob/living/M in O.contents)
- if(M && M.stat != 2 && !istype(M,/mob/living/silicon/robot/drone))
+ if(M && M.stat != 2 && !istype(M,/mob/living/silicon/robot/drone) && !istype(M, /mob/living/silicon/robot/syndicate/saboteur))
hasmob = 1
// now everything inside the disposal gets put into the holder
@@ -553,6 +556,9 @@
if(istype(AM, /mob/living/silicon/robot/drone))
var/mob/living/silicon/robot/drone/drone = AM
destinationTag = drone.mail_destination
+ if(istype(AM, /mob/living/silicon/robot/syndicate/saboteur))
+ var/mob/living/silicon/robot/syndicate/saboteur/S = AM
+ destinationTag = S.mail_destination
if(istype(AM, /obj/item/shippingPackage) && !hasmob)
var/obj/item/shippingPackage/sp = AM
if(sp.sealed) //only sealed packages get delivered to their intended destination
@@ -1304,7 +1310,7 @@
for(var/atom/movable/AM in contents)
AM.forceMove(loc)
AM.pipe_eject(dir)
- if(istype(AM,/mob/living/silicon/robot/drone)) //Drones keep smashing windows from being fired out of chutes. Bad for the station. ~Z
+ if(istype(AM,/mob/living/silicon/robot/drone) || istype(AM, /mob/living/silicon/robot/syndicate/saboteur)) //Drones keep smashing windows from being fired out of chutes. Bad for the station. ~Z
return
spawn(5)
if(QDELETED(AM))
diff --git a/code/modules/response_team/ert.dm b/code/modules/response_team/ert.dm
index e8c91920cb8..d9510d28114 100644
--- a/code/modules/response_team/ert.dm
+++ b/code/modules/response_team/ert.dm
@@ -145,7 +145,7 @@ var/ert_request_answered = FALSE
M.set_species(/datum/species/human, TRUE)
M.dna.ready_dna(M)
- M.reagents.add_reagent("mutadone", 1) //No fat/blind/colourblind/epileptic/whatever ERT.
+ M.cleanSE() //No fat/blind/colourblind/epileptic/whatever ERT.
M.overeatduration = 0
var/hair_c = pick("#8B4513","#000000","#FF4500","#FFD700") // Brown, black, red, blonde
diff --git a/code/modules/surgery/organs/vocal_cords.dm b/code/modules/surgery/organs/vocal_cords.dm
index 6e630fe7438..1ed5b3cf8e9 100644
--- a/code/modules/surgery/organs/vocal_cords.dm
+++ b/code/modules/surgery/organs/vocal_cords.dm
@@ -156,13 +156,12 @@ var/static/regex/multispin_words = regex("like a record baby")
for(var/V in listeners)
var/mob/living/L = V
- /*if(L.mind && L.mind.devilinfo && findtext(message, L.mind.devilinfo.truename))
+ if(L.mind && L.mind.devilinfo && findtext(message, L.mind.devilinfo.truename))
var/start = findtext(message, L.mind.devilinfo.truename)
listeners = list(L) //let's be honest you're never going to find two devils with the same name
power_multiplier *= 5 //if you're a devil and god himself addressed you, you fucked up
//Cut out the name so it doesn't trigger commands
message = copytext(message, 0, start)+copytext(message, start + length(L.mind.devilinfo.truename), length(message) + 1)
- break*/
if(findtext(message, L.real_name) == 1)
specific_listeners += L //focus on those with the specified name
//Cut out the name so it doesn't trigger commands
diff --git a/config/example/config.txt b/config/example/config.txt
index 264cc52fc49..39a1b13d23b 100644
--- a/config/example/config.txt
+++ b/config/example/config.txt
@@ -94,6 +94,8 @@ PROBABILITY NATIONS 0
PROBABILITY EXTENDED 2
PROBABILITY NUCLEAR 3
PROBABILITY ABDUCTION 0
+PROBABILITY DEVIL 0
+PROBABILITY DEVILAGENTS 0
## Maximum cycles shadowlings can remain unhatched before they take damage. 1800 = 60 minutes, 900 = 30 minutes, 0 = feature disabled.
SHADOWLING_MAX_AGE 0
diff --git a/fonts/REBUFFED.TTF b/fonts/REBUFFED.TTF
new file mode 100644
index 00000000000..2fe5468ee0d
Binary files /dev/null and b/fonts/REBUFFED.TTF differ
diff --git a/html/changelog.html b/html/changelog.html
index 418de44f23c..33e82ccdf24 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -56,6 +56,74 @@
-->
+
14 January 2019
+
Kyep updated:
+
+ - Doubled playtime requirements for command/sec jobs.
+ - Starts tracking playtime on a per-department basis as well. E.g: "X hours in the Sec department".
+ - Fixes bug which can cause more than 2 traders to spawn during the Sol Trader event.
+ - Further improved sol trader event in general.
+ - Enabled sol trader event to happen late-round as a randomly chosen event. Can only happen if the alert level is below red.
+
+
Quantum-M updated:
+
+ - Magistrate Locker to the code.
+ - Magistrate version of the Nanotrasen Navy Suit.
+ - Gives the Magistrate a new wig, called Justice wig.
+ - Magistrate specific stamp.
+ - Adds the NT Rep version of the Nanotrasen Navy Suit (which already existed) to the NT Rep locker.
+
+
datlo updated:
+
+ - ED bots no longer arrests you just because you have no ID by default
+ - Nuke ops now spawn with their pinpointer.
+
+
+
13 January 2019
+
Aurorablade updated:
+
+ - Fluff item for Asmerath. rscadd:Accordions now have inhands
+
+
Shadow-Quill updated:
+
+ - Stopped emergency shield generators from magically unbolting themselves when they get turned off.
+
+
Superhats updated:
+
+ - Vox, Drask, Diona and Grey can now choose the Neutral gender option.
+
+
datlo updated:
+
+ - Nuclear operatives can now select the Saboteur module when picking a cyborg, a modified engineering module designed around stealth and sabotage.
+ - Fixed an exploit allowing you to get free syndicate cyborgs.
+
+
lordPidey updated:
+
+ - Infernal devils have been seen onboard our spacestations, offering great boons in exchange for your soul.
+ - Employees are reminded that their soul already belongs to Nanotrasen. If you have sold your soul in error, a lawyer or head of personnel can help return your soul to Nanotrasen by hitting you with your employment contract.
+ - Nanotrasen headquarters will be bluespacing employment contracts into the IA's office filing cabinet when a new arrival reaches the station. It is recommended that the lawyer create copies of some of these for safe keeping.
+ - Due to the recent infernal incursions, the station library has been equipped with a Codex Gigas to help research infernal weaknesses. Please note that reading this book may have unintended side effects. Also note, you must spell the devil's name exactly, as there are countless demons with similar names.
+ - When a devil dies, if the proper banishment ritual is not performed on it's remains, the devil will revive itself at the cost of some of it's power. The banishment ritual is described in the Codex Gigas.
+ - When a demon gains enough souls, It's form will mutate to a more demonic looking form. The Arch-demon form is known to be on par with an ascended shadowling in power.
+ - Added Devil agent gamemode, where multiple devils are each trying to buy more souls than the next in line.
+ - If you've already sold your soul, you can sell it again to a different devil. You can even go back and forth for INFINITE POWER.
+
+
+
12 January 2019
+
AzuleUtama updated:
+
+ - Chef Excellence's Special Sauce uplink description updated to be more accurate.
+
+
Shadow-Quill updated:
+
+ - Fixed the QMs PDA supply app not showing the correct cargo shuttle location.
+ - Changed the capitalization of CentCom and Station in the supply PDA cartridge.
+
+
datlo updated:
+
+ - Nuke ops, ERT, and ayyybductors will now properly lose disabilities on spawn.
+
+
10 January 2019
Aurorablade updated:
diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml
index 0788932c38b..cc01552d610 100644
--- a/html/changelogs/.all_changelog.yml
+++ b/html/changelogs/.all_changelog.yml
@@ -8644,3 +8644,68 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
Aurorablade:
- bugfix: Cap guns was checking for claiber type 'cap' when the ammo was set to
caliber typer 'caps'. Fixed.
+2019-01-12:
+ AzuleUtama:
+ - spellcheck: Chef Excellence's Special Sauce uplink description updated to be more
+ accurate.
+ Shadow-Quill:
+ - bugfix: Fixed the QMs PDA supply app not showing the correct cargo shuttle location.
+ - tweak: Changed the capitalization of CentCom and Station in the supply PDA cartridge.
+ datlo:
+ - bugfix: Nuke ops, ERT, and ayyybductors will now properly lose disabilities on
+ spawn.
+2019-01-13:
+ Aurorablade:
+ - rscadd: Fluff item for Asmerath. rscadd:Accordions now have inhands
+ Shadow-Quill:
+ - bugfix: Stopped emergency shield generators from magically unbolting themselves
+ when they get turned off.
+ Superhats:
+ - tweak: Vox, Drask, Diona and Grey can now choose the Neutral gender option.
+ datlo:
+ - rscadd: Nuclear operatives can now select the Saboteur module when picking a cyborg,
+ a modified engineering module designed around stealth and sabotage.
+ - bugfix: Fixed an exploit allowing you to get free syndicate cyborgs.
+ lordPidey:
+ - rscadd: Infernal devils have been seen onboard our spacestations, offering great
+ boons in exchange for your soul.
+ - rscadd: Employees are reminded that their soul already belongs to Nanotrasen.
+ If you have sold your soul in error, a lawyer or head of personnel can help
+ return your soul to Nanotrasen by hitting you with your employment contract.
+ - rscadd: Nanotrasen headquarters will be bluespacing employment contracts into
+ the IA's office filing cabinet when a new arrival reaches the station. It is
+ recommended that the lawyer create copies of some of these for safe keeping.
+ - rscadd: Due to the recent infernal incursions, the station library has been equipped
+ with a Codex Gigas to help research infernal weaknesses. Please note that reading
+ this book may have unintended side effects. Also note, you must spell the devil's
+ name exactly, as there are countless demons with similar names.
+ - rscadd: When a devil dies, if the proper banishment ritual is not performed on
+ it's remains, the devil will revive itself at the cost of some of it's power.
+ The banishment ritual is described in the Codex Gigas.
+ - rscadd: When a demon gains enough souls, It's form will mutate to a more demonic
+ looking form. The Arch-demon form is known to be on par with an ascended shadowling
+ in power.
+ - rscadd: Added Devil agent gamemode, where multiple devils are each trying to buy
+ more souls than the next in line.
+ - rscadd: If you've already sold your soul, you can sell it again to a different
+ devil. You can even go back and forth for INFINITE POWER.
+2019-01-14:
+ Kyep:
+ - tweak: Doubled playtime requirements for command/sec jobs.
+ - tweak: 'Starts tracking playtime on a per-department basis as well. E.g: "X hours
+ in the Sec department".'
+ - bugfix: Fixes bug which can cause more than 2 traders to spawn during the Sol
+ Trader event.
+ - tweak: Further improved sol trader event in general.
+ - rscadd: Enabled sol trader event to happen late-round as a randomly chosen event.
+ Can only happen if the alert level is below red.
+ Quantum-M:
+ - rscadd: Magistrate Locker to the code.
+ - rscadd: Magistrate version of the Nanotrasen Navy Suit.
+ - rscadd: Gives the Magistrate a new wig, called Justice wig.
+ - rscadd: Magistrate specific stamp.
+ - tweak: Adds the NT Rep version of the Nanotrasen Navy Suit (which already existed)
+ to the NT Rep locker.
+ datlo:
+ - tweak: ED bots no longer arrests you just because you have no ID by default
+ - tweak: Nuke ops now spawn with their pinpointer.
diff --git a/icons/mob/32x64.dmi b/icons/mob/32x64.dmi
new file mode 100644
index 00000000000..32b25ba7393
Binary files /dev/null and b/icons/mob/32x64.dmi differ
diff --git a/icons/mob/actions/actions.dmi b/icons/mob/actions/actions.dmi
index c9b8c890586..08e645c5c9b 100644
Binary files a/icons/mob/actions/actions.dmi and b/icons/mob/actions/actions.dmi differ
diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi
index 99441b44f64..eb4251448f9 100644
Binary files a/icons/mob/head.dmi and b/icons/mob/head.dmi differ
diff --git a/icons/mob/hud.dmi b/icons/mob/hud.dmi
index c0d10e2eddb..a58a11c3f32 100644
Binary files a/icons/mob/hud.dmi and b/icons/mob/hud.dmi differ
diff --git a/icons/mob/inhands/equipment/instruments_lefthand.dmi b/icons/mob/inhands/equipment/instruments_lefthand.dmi
index 197cd61c6c4..e88c8da2d1c 100644
Binary files a/icons/mob/inhands/equipment/instruments_lefthand.dmi and b/icons/mob/inhands/equipment/instruments_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/instruments_righthand.dmi b/icons/mob/inhands/equipment/instruments_righthand.dmi
index bcda1b18b7f..7b783c726e8 100644
Binary files a/icons/mob/inhands/equipment/instruments_righthand.dmi and b/icons/mob/inhands/equipment/instruments_righthand.dmi differ
diff --git a/icons/mob/inhands/items_righthand.dmi b/icons/mob/inhands/items_righthand.dmi
index fb4983e96e9..3efddb08c4b 100644
Binary files a/icons/mob/inhands/items_righthand.dmi and b/icons/mob/inhands/items_righthand.dmi differ
diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi
index 0cccb3ffddf..68f3fe5342a 100644
Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ
diff --git a/icons/mob/robots.dmi b/icons/mob/robots.dmi
index 653dfd5dfa9..eb59b0288b3 100644
Binary files a/icons/mob/robots.dmi and b/icons/mob/robots.dmi differ
diff --git a/icons/mob/screen_gen.dmi b/icons/mob/screen_gen.dmi
index 88b592236f5..62f732a224f 100644
Binary files a/icons/mob/screen_gen.dmi and b/icons/mob/screen_gen.dmi differ
diff --git a/icons/mob/species/drask/uniform.dmi b/icons/mob/species/drask/uniform.dmi
index 951a2443fef..d29b598dec3 100644
Binary files a/icons/mob/species/drask/uniform.dmi and b/icons/mob/species/drask/uniform.dmi differ
diff --git a/icons/mob/species/vox/uniform.dmi b/icons/mob/species/vox/uniform.dmi
index 6d6ff4abca4..46c581ce5dc 100644
Binary files a/icons/mob/species/vox/uniform.dmi and b/icons/mob/species/vox/uniform.dmi differ
diff --git a/icons/mob/uniform.dmi b/icons/mob/uniform.dmi
index 98c63155bd4..955ef265862 100644
Binary files a/icons/mob/uniform.dmi and b/icons/mob/uniform.dmi differ
diff --git a/icons/mob/uniform_fat.dmi b/icons/mob/uniform_fat.dmi
index a3e5dc73e4e..29327ef5413 100644
Binary files a/icons/mob/uniform_fat.dmi and b/icons/mob/uniform_fat.dmi differ
diff --git a/icons/obj/bureaucracy.dmi b/icons/obj/bureaucracy.dmi
index 529417a2b6c..56620c267b1 100644
Binary files a/icons/obj/bureaucracy.dmi and b/icons/obj/bureaucracy.dmi differ
diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi
index 4cab586583a..dd660cd893e 100644
Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ
diff --git a/icons/obj/clothing/uniforms.dmi b/icons/obj/clothing/uniforms.dmi
index 131ff4740ef..36ae38d9250 100644
Binary files a/icons/obj/clothing/uniforms.dmi and b/icons/obj/clothing/uniforms.dmi differ
diff --git a/icons/obj/custom_items.dmi b/icons/obj/custom_items.dmi
index 8c124230708..9d94d7eacb9 100644
Binary files a/icons/obj/custom_items.dmi and b/icons/obj/custom_items.dmi differ
diff --git a/icons/obj/library.dmi b/icons/obj/library.dmi
index e6076c4e5ab..29d4e1faea9 100644
Binary files a/icons/obj/library.dmi and b/icons/obj/library.dmi differ
diff --git a/icons/paper_icons/large_stamp-magistrate.png b/icons/paper_icons/large_stamp-magistrate.png
new file mode 100644
index 00000000000..21c283026e8
Binary files /dev/null and b/icons/paper_icons/large_stamp-magistrate.png differ
diff --git a/icons/turf/floors.dmi b/icons/turf/floors.dmi
index 135266854fc..fbcac2b99a5 100644
Binary files a/icons/turf/floors.dmi and b/icons/turf/floors.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 49a3b73ee07..88b949270ce 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -26,6 +26,7 @@
#include "code\__DEFINES\combat.dm"
#include "code\__DEFINES\components.dm"
#include "code\__DEFINES\construction.dm"
+#include "code\__DEFINES\contracts.dm"
#include "code\__DEFINES\crafting.dm"
#include "code\__DEFINES\error_handler.dm"
#include "code\__DEFINES\flags.dm"
@@ -131,6 +132,7 @@
#include "code\_onclick\hud\alien_larva.dm"
#include "code\_onclick\hud\bot.dm"
#include "code\_onclick\hud\constructs.dm"
+#include "code\_onclick\hud\devil.dm"
#include "code\_onclick\hud\fullscreen.dm"
#include "code\_onclick\hud\guardian.dm"
#include "code\_onclick\hud\hud.dm"
@@ -341,7 +343,10 @@
#include "code\datums\spells\charge.dm"
#include "code\datums\spells\cluwne.dm"
#include "code\datums\spells\conjure.dm"
+#include "code\datums\spells\conjure_item.dm"
#include "code\datums\spells\construct_spells.dm"
+#include "code\datums\spells\devil.dm"
+#include "code\datums\spells\devil_boons.dm"
#include "code\datums\spells\dumbfire.dm"
#include "code\datums\spells\emplosion.dm"
#include "code\datums\spells\ethereal_jaunt.dm"
@@ -484,6 +489,16 @@
#include "code\game\gamemodes\cult\ritual.dm"
#include "code\game\gamemodes\cult\runes.dm"
#include "code\game\gamemodes\cult\talisman.dm"
+#include "code\game\gamemodes\devil\devil.dm"
+#include "code\game\gamemodes\devil\devil_game_mode.dm"
+#include "code\game\gamemodes\devil\devilinfo.dm"
+#include "code\game\gamemodes\devil\game_mode.dm"
+#include "code\game\gamemodes\devil\objectives.dm"
+#include "code\game\gamemodes\devil\contracts\friend.dm"
+#include "code\game\gamemodes\devil\devil agent\devil_agent.dm"
+#include "code\game\gamemodes\devil\imp\imp.dm"
+#include "code\game\gamemodes\devil\true_devil\_true_devil.dm"
+#include "code\game\gamemodes\devil\true_devil\inventory.dm"
#include "code\game\gamemodes\extended\extended.dm"
#include "code\game\gamemodes\heist\heist.dm"
#include "code\game\gamemodes\malfunction\Malf_Modules.dm"
@@ -521,6 +536,7 @@
#include "code\game\gamemodes\miniantags\revenant\revenant.dm"
#include "code\game\gamemodes\miniantags\revenant\revenant_abilities.dm"
#include "code\game\gamemodes\miniantags\revenant\revenant_spawn_event.dm"
+#include "code\game\gamemodes\miniantags\sintouched\objectives.dm"
#include "code\game\gamemodes\miniantags\slaughter\bloodcrawl.dm"
#include "code\game\gamemodes\miniantags\slaughter\slaughter.dm"
#include "code\game\gamemodes\nations\nations.dm"
@@ -1542,6 +1558,7 @@
#include "code\modules\hydroponics\grown\towercap.dm"
#include "code\modules\karma\karma.dm"
#include "code\modules\library\admin.dm"
+#include "code\modules\library\codex_gigas.dm"
#include "code\modules\library\lib_items.dm"
#include "code\modules\library\lib_machines.dm"
#include "code\modules\library\lib_readme.dm"
@@ -1798,6 +1815,7 @@
#include "code\modules\mob\living\silicon\robot\robot_module_actions.dm"
#include "code\modules\mob\living\silicon\robot\robot_modules.dm"
#include "code\modules\mob\living\silicon\robot\robot_movement.dm"
+#include "code\modules\mob\living\silicon\robot\syndicate.dm"
#include "code\modules\mob\living\silicon\robot\update_status.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone.dm"
#include "code\modules\mob\living\silicon\robot\drone\drone_abilities.dm"
@@ -2029,6 +2047,7 @@
#include "code\modules\ninja\suit\suit_initialisation.dm"
#include "code\modules\paperwork\carbonpaper.dm"
#include "code\modules\paperwork\clipboard.dm"
+#include "code\modules\paperwork\contract.dm"
#include "code\modules\paperwork\fax.dm"
#include "code\modules\paperwork\faxmachine.dm"
#include "code\modules\paperwork\filingcabinet.dm"