diff --git a/_maps/map_files/Birdshot/birdshot.dmm b/_maps/map_files/Birdshot/birdshot.dmm index 0e8e75395e737d..3bf5636a5a1422 100644 --- a/_maps/map_files/Birdshot/birdshot.dmm +++ b/_maps/map_files/Birdshot/birdshot.dmm @@ -1313,8 +1313,10 @@ /area/station/security/office) "aAD" = ( /obj/effect/decal/cleanable/dirt, -/obj/structure/table, -/obj/item/book/manual/wiki/tcomms, +/obj/machinery/computer/telecomms/server{ + dir = 8; + network = "tcommsat" + }, /turf/open/floor/circuit, /area/station/tcommsat/server) "aAL" = ( @@ -12665,6 +12667,7 @@ /obj/item/pen, /obj/machinery/airalarm/directional/west, /obj/item/paper/monitorkey, +/obj/item/book/manual/wiki/tcomms, /turf/open/floor/iron/grimy, /area/station/tcommsat/server) "eQa" = ( @@ -16010,11 +16013,10 @@ /obj/effect/turf_decal/siding/wood{ dir = 4 }, -/obj/machinery/computer/telecomms/server{ - dir = 8; - network = "tcommsat" - }, /obj/item/radio/intercom/directional/east, +/obj/machinery/computer/telecomms/traffic{ + dir = 8 + }, /turf/open/floor/iron/grimy, /area/station/tcommsat/server) "fZG" = ( @@ -56382,6 +56384,7 @@ /obj/structure/chair/office{ dir = 4 }, +/obj/effect/landmark/start/telecomms_specialist, /turf/open/floor/wood/tile, /area/station/tcommsat/server) "top" = ( diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm index ffb2192c4d0cd1..9b4794b1857bd5 100644 --- a/_maps/map_files/Deltastation/DeltaStation2.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2.dmm @@ -11037,10 +11037,12 @@ /turf/open/floor/iron/dark, /area/station/hallway/secondary/entry) "cGR" = ( -/obj/structure/table/wood, -/obj/item/folder/blue, -/obj/item/pen, /obj/item/radio/intercom/directional/north, +/obj/structure/table/wood, +/obj/item/book/manual/wiki/tcomms, +/obj/item/radio{ + pixel_y = 5 + }, /turf/open/floor/iron/grimy, /area/station/tcommsat/computer) "cHb" = ( @@ -12281,6 +12283,12 @@ /obj/effect/spawner/structure/window/reinforced, /turf/open/floor/plating, /area/station/maintenance/department/medical/morgue) +"cYj" = ( +/obj/item/folder/blue, +/obj/item/pen, +/obj/structure/table/wood, +/turf/open/floor/iron/grimy, +/area/station/tcommsat/computer) "cYk" = ( /obj/structure/closet/l3closet/virology, /obj/effect/turf_decal/bot, @@ -13497,6 +13505,7 @@ /area/station/medical/chemistry) "dol" = ( /obj/structure/cable, +/obj/item/kirbyplants/random, /turf/open/floor/iron/grimy, /area/station/tcommsat/computer) "dor" = ( @@ -21463,10 +21472,6 @@ /obj/effect/turf_decal/tile/neutral/fourcorners, /turf/open/floor/iron, /area/station/hallway/secondary/entry) -"fnM" = ( -/obj/structure/chair/office, -/turf/open/floor/iron/grimy, -/area/station/tcommsat/computer) "foh" = ( /obj/machinery/atmospherics/components/binary/volume_pump{ name = "Ports to Distro" @@ -28195,6 +28200,7 @@ /obj/structure/chair/office{ dir = 4 }, +/obj/effect/landmark/start/telecomms_specialist, /turf/open/floor/iron/grimy, /area/station/tcommsat/computer) "gUZ" = ( @@ -30326,9 +30332,10 @@ /turf/open/floor/iron, /area/station/security/execution/transfer) "hzA" = ( -/obj/item/kirbyplants/random, /obj/machinery/power/apc/auto_name/directional/north, /obj/structure/cable, +/obj/item/paper_bin, +/obj/structure/table/wood, /turf/open/floor/iron/grimy, /area/station/tcommsat/computer) "hzC" = ( @@ -45566,9 +45573,8 @@ /turf/open/floor/iron/smooth, /area/station/maintenance/department/science/xenobiology) "lqj" = ( -/obj/structure/table/wood, -/obj/item/paper_bin, /obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/structure/table/wood, /turf/open/floor/iron/dark, /area/station/tcommsat/computer) "lql" = ( @@ -50403,12 +50409,9 @@ /turf/open/floor/iron, /area/station/commons/locker) "mCM" = ( -/obj/structure/table/wood, -/obj/item/book/manual/wiki/tcomms, -/obj/item/radio{ - pixel_y = 5 - }, /obj/item/radio/intercom/directional/north, +/obj/structure/table/wood, +/obj/item/flashlight/lamp, /turf/open/floor/iron/grimy, /area/station/tcommsat/computer) "mCZ" = ( @@ -51370,6 +51373,7 @@ network = list("ss13","tcomms") }, /obj/machinery/light_switch/directional/north, +/obj/item/kirbyplants/random, /turf/open/floor/iron/grimy, /area/station/tcommsat/computer) "mOo" = ( @@ -81610,10 +81614,11 @@ /turf/open/floor/wood, /area/station/command/heads_quarters/hop) "usZ" = ( -/obj/item/flashlight/lamp, /obj/machinery/airalarm/directional/east, -/obj/structure/table/wood, /obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/machinery/computer/telecomms/monitor{ + dir = 8 + }, /turf/open/floor/iron/dark, /area/station/tcommsat/computer) "utj" = ( @@ -88748,11 +88753,11 @@ /turf/open/floor/iron, /area/station/maintenance/port) "wiw" = ( -/obj/machinery/computer/telecomms/monitor{ - dir = 8 - }, /obj/machinery/status_display/evac/directional/east, /obj/effect/turf_decal/tile/neutral/fourcorners, +/obj/machinery/computer/telecomms/traffic{ + dir = 8 + }, /turf/open/floor/iron/dark, /area/station/tcommsat/computer) "wiA" = ( @@ -135404,7 +135409,7 @@ dTp hEI sxr eVl -xDU +cYj gnA jHb kgs @@ -136691,7 +136696,7 @@ ipQ eVl mCM gUW -fnM +jHb hpv lkL jDi diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm index b88647d4934a00..a86c8b7f17713e 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -23943,6 +23943,12 @@ "hfI" = ( /obj/machinery/light/directional/north, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, +/obj/structure/table, +/obj/item/paper_bin, +/obj/item/pen, +/obj/item/pen/blue{ + pixel_y = 5 + }, /turf/open/floor/iron/dark, /area/station/tcommsat/computer) "hfL" = ( @@ -33986,17 +33992,14 @@ /turf/open/floor/carpet, /area/station/command/meeting_room) "kiR" = ( -/obj/structure/table, /obj/structure/cable, /obj/machinery/power/apc/auto_name/directional/north, -/obj/item/paper_bin, -/obj/item/pen/blue{ - pixel_y = 5 - }, -/obj/item/pen, /obj/effect/turf_decal/tile/yellow/anticorner/contrasted{ dir = 1 }, +/obj/machinery/computer/message_monitor{ + dir = 4 + }, /turf/open/floor/iron/dark, /area/station/tcommsat/computer) "kiY" = ( @@ -51365,6 +51368,7 @@ dir = 8 }, /obj/structure/cable, +/obj/effect/landmark/start/telecomms_specialist, /turf/open/floor/iron/dark, /area/station/tcommsat/computer) "ppK" = ( @@ -59665,12 +59669,12 @@ /turf/open/floor/iron/white, /area/station/medical/virology) "rHQ" = ( -/obj/machinery/computer/message_monitor{ - dir = 4 - }, /obj/effect/turf_decal/tile/yellow/half/contrasted{ dir = 8 }, +/obj/machinery/computer/telecomms/traffic{ + dir = 4 + }, /turf/open/floor/iron/dark, /area/station/tcommsat/computer) "rHR" = ( diff --git a/_maps/map_files/KiloStation/KiloStation.dmm b/_maps/map_files/KiloStation/KiloStation.dmm index 6d16e9a1f20f9c..549e4d4fdbe8a0 100644 --- a/_maps/map_files/KiloStation/KiloStation.dmm +++ b/_maps/map_files/KiloStation/KiloStation.dmm @@ -3787,6 +3787,9 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/effect/turf_decal/tile/neutral/anticorner/contrasted, +/obj/item/kirbyplants{ + icon_state = "plant-05" + }, /turf/open/floor/iron/dark/corner, /area/station/hallway/primary/central/fore) "bpn" = ( @@ -3953,7 +3956,6 @@ /turf/open/floor/plating, /area/station/maintenance/port/fore) "bsc" = ( -/obj/structure/table, /obj/item/storage/toolbox/mechanical, /obj/item/multitool, /obj/machinery/camera/directional/west{ @@ -3971,6 +3973,7 @@ /obj/effect/mapping_helpers/requests_console/announcement, /obj/effect/mapping_helpers/requests_console/assistance, /obj/effect/mapping_helpers/requests_console/information, +/obj/structure/table, /turf/open/floor/iron/dark, /area/station/tcommsat/computer) "bso" = ( @@ -7936,10 +7939,6 @@ /area/space/nearstation) "cJO" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/obj/structure/chair/office{ - dir = 1 - }, -/obj/effect/landmark/start/telecomms_specialist, /turf/open/floor/engine, /area/station/tcommsat/computer) "cKd" = ( @@ -12726,9 +12725,6 @@ /turf/open/floor/wood, /area/station/command/heads_quarters/hop) "epA" = ( -/obj/item/kirbyplants{ - icon_state = "plant-05" - }, /obj/machinery/power/apc/auto_name/directional/north, /obj/structure/cable, /obj/effect/turf_decal/tile/yellow/half/contrasted{ @@ -12740,7 +12736,7 @@ /obj/effect/turf_decal/tile/red{ dir = 1 }, -/turf/open/floor/iron, +/turf/closed/wall/r_wall, /area/station/hallway/primary/central/fore) "epI" = ( /obj/effect/landmark/start/scientist, @@ -29438,15 +29434,15 @@ /turf/open/floor/iron, /area/station/hallway/secondary/exit/departure_lounge) "jEd" = ( -/obj/structure/table, /obj/machinery/firealarm/directional/west, +/obj/effect/decal/cleanable/cobweb, +/obj/effect/turf_decal/tile/neutral, +/obj/structure/table, /obj/item/clipboard{ pixel_x = 4 }, -/obj/effect/decal/cleanable/cobweb, /obj/item/book/manual/wiki/tcomms, /obj/item/radio, -/obj/effect/turf_decal/tile/neutral, /turf/open/floor/iron/dark, /area/station/tcommsat/computer) "jEp" = ( @@ -37311,8 +37307,9 @@ }, /obj/machinery/light/small/directional/east, /obj/item/radio/intercom/directional/east, -/obj/machinery/status_display/ai/directional/south, /obj/machinery/computer/security/telescreen/tcomms/directional/east, +/obj/structure/chair/office, +/obj/effect/landmark/start/telecomms_specialist, /turf/open/floor/engine, /area/station/tcommsat/computer) "mfY" = ( @@ -45190,6 +45187,17 @@ }, /turf/open/floor/plating, /area/station/cargo/warehouse) +"oTj" = ( +/obj/effect/turf_decal/tile/neutral/half/contrasted{ + dir = 1 + }, +/obj/effect/turf_decal/bot, +/obj/machinery/status_display/ai/directional/east, +/obj/machinery/computer/telecomms/traffic{ + dir = 1 + }, +/turf/open/floor/iron/dark, +/area/station/tcommsat/computer) "oTt" = ( /obj/structure/sign/painting/library{ pixel_x = 32 @@ -105587,7 +105595,7 @@ shk orT aQB mfU -vqw +oTj vqw imo tEE diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index 484f0b6e21c6b3..5b699ea6076777 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -2675,7 +2675,6 @@ /turf/open/floor/iron/dark, /area/station/tcommsat/server) "aVZ" = ( -/obj/structure/chair/office, /obj/structure/cable, /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{ dir = 8 @@ -12430,9 +12429,9 @@ /turf/open/floor/plating, /area/station/maintenance/starboard/lesser) "eBz" = ( -/obj/structure/table/wood, /obj/machinery/status_display/ai/directional/north, /obj/item/flashlight/lamp, +/obj/structure/table/wood, /turf/open/floor/iron/grimy, /area/station/tcommsat/computer) "eBO" = ( @@ -20251,6 +20250,7 @@ /obj/structure/chair/office{ dir = 4 }, +/obj/effect/landmark/start/telecomms_specialist, /turf/open/floor/iron/grimy, /area/station/tcommsat/computer) "hpi" = ( @@ -21878,14 +21878,9 @@ "hSe" = ( /obj/machinery/light/small/directional/east, /obj/item/radio/intercom/directional/north, -/obj/structure/table/wood, -/obj/item/phone{ - pixel_x = -3; - pixel_y = 3 - }, -/obj/item/cigbutt/cigarbutt{ - pixel_x = 5; - pixel_y = -1 +/obj/machinery/computer/telecomms/monitor{ + dir = 8; + network = "tcommsat" }, /turf/open/floor/iron/grimy, /area/station/tcommsat/computer) @@ -30820,7 +30815,11 @@ /turf/open/floor/iron, /area/station/hallway/primary/starboard) "kRk" = ( -/obj/machinery/announcement_system, +/obj/structure/table/wood, +/obj/item/cigbutt/cigarbutt{ + pixel_x = 5; + pixel_y = -1 + }, /turf/open/floor/iron/grimy, /area/station/tcommsat/computer) "kRA" = ( @@ -32932,6 +32931,7 @@ /obj/structure/showcase/cyborg/old{ pixel_y = 20 }, +/obj/machinery/announcement_system, /turf/open/floor/iron/grimy, /area/station/tcommsat/computer) "lJm" = ( @@ -38723,11 +38723,15 @@ /area/station/medical/medbay/central) "nCu" = ( /obj/structure/chair/office{ - dir = 1 + dir = 8 }, /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{ dir = 4 }, +/obj/item/phone{ + pixel_x = -3; + pixel_y = 3 + }, /turf/open/floor/iron/grimy, /area/station/tcommsat/computer) "nCw" = ( @@ -40436,9 +40440,8 @@ /area/station/science/robotics/lab) "okp" = ( /obj/machinery/computer/security/telescreen/tcomms/directional/east, -/obj/machinery/computer/telecomms/monitor{ - dir = 8; - network = "tcommsat" +/obj/machinery/computer/telecomms/traffic{ + dir = 8 }, /turf/open/floor/iron/grimy, /area/station/tcommsat/computer) @@ -44335,12 +44338,11 @@ /turf/open/floor/iron/dark, /area/station/command/bridge) "pDe" = ( -/obj/machinery/computer/telecomms/server{ - dir = 8; - network = "tcommsat" - }, /obj/structure/cable, /obj/machinery/power/apc/auto_name/directional/east, +/obj/machinery/computer/message_monitor{ + dir = 8 + }, /turf/open/floor/iron/grimy, /area/station/tcommsat/computer) "pDl" = ( @@ -66207,10 +66209,11 @@ /turf/open/floor/plating, /area/station/maintenance/port/fore) "xml" = ( -/obj/machinery/computer/message_monitor{ - dir = 4 - }, /obj/machinery/airalarm/directional/west, +/obj/machinery/computer/telecomms/server{ + dir = 4; + network = "tcommsat" + }, /turf/open/floor/iron/grimy, /area/station/tcommsat/computer) "xmD" = ( diff --git a/_maps/map_files/NSVBlueshift/Blueshift.dmm b/_maps/map_files/NSVBlueshift/Blueshift.dmm index 537943f66e9e71..287fbae324b2d5 100644 --- a/_maps/map_files/NSVBlueshift/Blueshift.dmm +++ b/_maps/map_files/NSVBlueshift/Blueshift.dmm @@ -29427,8 +29427,10 @@ /turf/open/floor/plating, /area/station/maintenance/department/security/greater) "fBZ" = ( -/obj/structure/table/reinforced, -/obj/item/paper_bin, +/obj/machinery/computer/telecomms/server{ + dir = 1; + network = "tcommsat" + }, /turf/open/floor/iron/dark, /area/station/tcommsat/computer) "fCd" = ( @@ -36775,12 +36777,13 @@ /turf/open/floor/iron, /area/station/science/ordnance) "gZw" = ( -/obj/item/kirbyplants/organic/plant21, /obj/machinery/camera{ c_tag = "Engineering - Tcomms Controll 2"; dir = 10; name = "engineering camera" }, +/obj/structure/table/reinforced, +/obj/item/paper_bin, /turf/open/floor/iron/dark, /area/station/tcommsat/computer) "gZx" = ( @@ -49003,7 +49006,7 @@ /area/station/command/gateway) "jsT" = ( /obj/machinery/computer/telecomms/monitor{ - dir = 4; + dir = 1; network = "tcommsat" }, /turf/open/floor/iron/dark, @@ -81377,7 +81380,6 @@ "pEp" = ( /obj/effect/turf_decal/tile/neutral/fourcorners, /obj/structure/chair/office/light, -/obj/effect/landmark/start/telecomms_specialist, /turf/open/floor/iron/dark, /area/station/tcommsat/computer) "pEq" = ( @@ -96819,9 +96821,8 @@ /turf/open/floor/plating, /area/station/cargo/storage) "szl" = ( -/obj/machinery/computer/telecomms/server{ - dir = 8; - network = "tcommsat" +/obj/machinery/computer/telecomms/traffic{ + dir = 1 }, /turf/open/floor/iron/dark, /area/station/tcommsat/computer) @@ -103365,6 +103366,7 @@ /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{ dir = 4 }, +/obj/item/kirbyplants/organic/plant21, /turf/open/floor/iron/dark/side{ dir = 8 }, @@ -113274,7 +113276,8 @@ "vEE" = ( /obj/structure/chair/office/light, /obj/machinery/light/floor, -/obj/machinery/computer/security/telescreen/tcomms/directional/south, +/obj/effect/landmark/start/telecomms_specialist, +/obj/machinery/computer/security/telescreen/tcomms/directional/east, /turf/open/floor/iron, /area/station/tcommsat/computer) "vEF" = ( @@ -152119,8 +152122,8 @@ nds nds nds gZw -jsT xdB +jsT nds bNr ani @@ -152377,7 +152380,7 @@ pFN jas pFN vEE -tzO +szl nds tku dbY @@ -152633,7 +152636,7 @@ nds nds nds iBL -szl +tzO fBZ nds aju diff --git a/_maps/map_files/NorthStar/north_star.dmm b/_maps/map_files/NorthStar/north_star.dmm index 6e289e29cbe9b4..d61f9c8a0c1941 100644 --- a/_maps/map_files/NorthStar/north_star.dmm +++ b/_maps/map_files/NorthStar/north_star.dmm @@ -13156,14 +13156,13 @@ /turf/closed/wall, /area/station/maintenance/floor1/port/aft) "dqB" = ( -/obj/item/clothing/mask/breath{ - pixel_x = -4 - }, -/obj/item/clothing/mask/breath, -/obj/item/clothing/mask/breath{ - pixel_x = 4 - }, /obj/structure/table, +/obj/item/book/manual/wiki/tcomms, +/obj/item/storage/toolbox/electrical, +/obj/item/multitool, +/obj/item/radio/off{ + pixel_y = 4 + }, /turf/open/floor/iron/smooth, /area/station/tcommsat/computer) "dqF" = ( @@ -15088,6 +15087,9 @@ /area/station/hallway/floor1/fore) "dRE" = ( /obj/machinery/holopad, +/obj/machinery/computer/telecomms/traffic{ + dir = 1 + }, /turf/open/floor/iron/smooth, /area/station/tcommsat/computer) "dRI" = ( @@ -43535,6 +43537,14 @@ /area/station/maintenance/floor3/port/aft) "llt" = ( /obj/machinery/firealarm/directional/north, +/obj/structure/table, +/obj/item/clothing/mask/breath{ + pixel_x = -4 + }, +/obj/item/clothing/mask/breath, +/obj/item/clothing/mask/breath{ + pixel_x = 4 + }, /turf/open/floor/iron/smooth, /area/station/tcommsat/computer) "llv" = ( @@ -50994,6 +51004,7 @@ /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2{ dir = 1 }, +/obj/effect/landmark/start/telecomms_specialist, /turf/open/floor/iron/smooth_large, /area/station/tcommsat/computer) "nfv" = ( @@ -52597,13 +52608,6 @@ /obj/effect/turf_decal/tile/red/fourcorners, /turf/open/floor/iron/dark, /area/station/security/brig) -"nzb" = ( -/obj/machinery/computer/telecomms/monitor{ - dir = 4; - network = "tcommsat" - }, -/turf/open/floor/iron/smooth, -/area/station/tcommsat/computer) "nzd" = ( /obj/structure/closet/crate/hydroponics, /obj/item/wirecutters, @@ -89206,14 +89210,11 @@ }, /area/station/hallway/secondary/exit/escape_pod) "wUB" = ( -/obj/item/book/manual/wiki/tcomms, -/obj/item/radio/off{ - pixel_y = 4 - }, -/obj/structure/table, -/obj/item/multitool, -/obj/item/storage/toolbox/electrical, /obj/machinery/light/small/directional/east, +/obj/machinery/computer/telecomms/monitor{ + dir = 1; + network = "tcommsat" + }, /turf/open/floor/iron/smooth, /area/station/tcommsat/computer) "wUF" = ( @@ -130414,7 +130415,7 @@ jKt bMI xWl nPN -nzb +jIG mNh ngH snO diff --git a/_maps/map_files/PubbyStation/PubbyStation.dmm b/_maps/map_files/PubbyStation/PubbyStation.dmm index ef65853ce232ee..52b990f6521fb2 100644 --- a/_maps/map_files/PubbyStation/PubbyStation.dmm +++ b/_maps/map_files/PubbyStation/PubbyStation.dmm @@ -29183,6 +29183,13 @@ dir = 4 }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, +/obj/structure/table, +/obj/item/book/manual/wiki/tcomms, +/obj/item/paper_bin{ + pixel_x = -2; + pixel_y = 5 + }, +/obj/item/pen, /turf/open/floor/iron, /area/station/tcommsat/computer) "cmh" = ( @@ -29208,8 +29215,8 @@ /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, -/obj/effect/mapping_helpers/airlock/access/all/engineering/tcoms, -/obj/effect/mapping_helpers/airlock/access/all/engineering/ce, +/obj/effect/mapping_helpers/airlock/access/any/command/general, +/obj/effect/mapping_helpers/airlock/access/any/engineering/tcoms, /turf/open/floor/iron, /area/station/tcommsat/computer) "cmk" = ( @@ -29266,6 +29273,7 @@ /obj/effect/turf_decal/tile/yellow{ dir = 8 }, +/obj/effect/landmark/start/telecomms_specialist, /turf/open/floor/iron, /area/station/tcommsat/computer) "cms" = ( @@ -29291,13 +29299,6 @@ /obj/machinery/status_display/evac{ pixel_x = -32 }, -/obj/structure/table, -/obj/item/book/manual/wiki/tcomms, -/obj/item/paper_bin{ - pixel_x = -2; - pixel_y = 5 - }, -/obj/item/pen, /obj/effect/turf_decal/tile/yellow{ dir = 1 }, @@ -29305,6 +29306,10 @@ /obj/effect/turf_decal/tile/yellow{ dir = 8 }, +/obj/machinery/computer/telecomms/server{ + dir = 1; + network = "tcommsat" + }, /turf/open/floor/iron, /area/station/tcommsat/computer) "cmv" = ( @@ -29340,10 +29345,6 @@ /turf/open/floor/iron/dark, /area/station/tcommsat/computer) "cmx" = ( -/obj/machinery/computer/telecomms/server{ - dir = 1; - network = "tcommsat" - }, /obj/effect/turf_decal/tile/yellow{ dir = 1 }, @@ -29351,6 +29352,9 @@ /obj/effect/turf_decal/tile/yellow{ dir = 8 }, +/obj/machinery/computer/telecomms/traffic{ + dir = 1 + }, /turf/open/floor/iron, /area/station/tcommsat/computer) "cmy" = ( @@ -47544,7 +47548,6 @@ "nwR" = ( /obj/structure/chair/office, /obj/effect/turf_decal/tile/yellow, -/obj/effect/landmark/start/telecomms_specialist, /turf/open/floor/iron, /area/station/tcommsat/computer) "nxG" = ( diff --git a/_maps/map_files/VoidRaptor/VoidRaptor.dmm b/_maps/map_files/VoidRaptor/VoidRaptor.dmm index 59c408cbba65c2..3286a24cee983e 100644 --- a/_maps/map_files/VoidRaptor/VoidRaptor.dmm +++ b/_maps/map_files/VoidRaptor/VoidRaptor.dmm @@ -10052,14 +10052,10 @@ /turf/open/floor/iron/smooth, /area/station/maintenance/port/fore) "dau" = ( -/obj/structure/table, -/obj/item/storage/toolbox/mechanical, -/obj/item/multitool, /obj/machinery/airalarm/directional/south, /obj/machinery/atmospherics/pipe/smart/manifold4w/general/hidden, -/obj/item/storage/box/bandages{ - pixel_x = 4; - pixel_y = 5 +/obj/machinery/computer/message_monitor{ + dir = 1 }, /turf/open/floor/engine, /area/station/tcommsat/computer) @@ -27607,10 +27603,6 @@ /turf/open/floor/iron/white/small, /area/station/common/pool) "hYR" = ( -/obj/machinery/computer/telecomms/server{ - dir = 4; - network = "tcommsat" - }, /obj/machinery/atmospherics/pipe/smart/manifold4w/general/hidden, /obj/effect/turf_decal/bot, /turf/open/floor/engine, @@ -31265,6 +31257,12 @@ "iZs" = ( /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, +/obj/structure/table, +/obj/item/book/manual/wiki/tcomms, +/obj/item/clipboard{ + pixel_x = 4 + }, +/obj/item/radio, /turf/open/floor/engine, /area/station/tcommsat/computer) "iZx" = ( @@ -55512,14 +55510,12 @@ /turf/open/floor/iron/grimy, /area/station/security/detectives_office) "pzK" = ( -/obj/structure/table, -/obj/item/clipboard{ - pixel_x = 4 - }, -/obj/item/book/manual/wiki/tcomms, -/obj/item/radio, /obj/item/radio/intercom/directional/south, /obj/machinery/atmospherics/pipe/smart/manifold4w/general/hidden, +/obj/machinery/computer/telecomms/server{ + dir = 1; + network = "tcommsat" + }, /turf/open/floor/engine, /area/station/tcommsat/computer) "pzL" = ( @@ -58786,6 +58782,13 @@ name = "telecomms camera"; network = list("ss13","tcomms") }, +/obj/structure/table, +/obj/item/storage/toolbox/mechanical, +/obj/item/storage/box/bandages{ + pixel_x = 4; + pixel_y = 5 + }, +/obj/item/multitool, /turf/open/floor/engine, /area/station/tcommsat/computer) "qso" = ( @@ -61467,11 +61470,11 @@ }, /area/station/hallway/primary/central/aft) "rfR" = ( -/obj/machinery/computer/message_monitor{ - dir = 4 - }, /obj/effect/turf_decal/bot, /obj/machinery/atmospherics/pipe/smart/manifold4w/general/hidden, +/obj/machinery/computer/telecomms/traffic{ + dir = 4 + }, /turf/open/floor/engine, /area/station/tcommsat/computer) "rfZ" = ( diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm index eb3032f5f44759..21da3f31f0ce08 100644 --- a/_maps/map_files/tramstation/tramstation.dmm +++ b/_maps/map_files/tramstation/tramstation.dmm @@ -11080,6 +11080,7 @@ dir = 5 }, /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2, +/obj/machinery/announcement_system, /turf/open/floor/iron, /area/station/tcommsat/computer) "cNr" = ( @@ -21907,13 +21908,12 @@ /turf/open/floor/iron/dark, /area/station/security/courtroom) "gMr" = ( -/obj/machinery/computer/telecomms/server{ - dir = 1; - network = "tcommsat" - }, /obj/effect/turf_decal/trimline/yellow/filled/line{ dir = 1 }, +/obj/machinery/computer/telecomms/traffic{ + dir = 1 + }, /turf/open/floor/iron/dark, /area/station/tcommsat/computer) "gMH" = ( @@ -36620,7 +36620,6 @@ /turf/open/floor/glass/reinforced, /area/station/security/brig) "lSp" = ( -/obj/structure/filingcabinet/chestdrawer, /obj/effect/turf_decal/trimline/yellow/filled/line{ dir = 10 }, @@ -48765,6 +48764,7 @@ }, /obj/machinery/power/apc/auto_name/directional/north, /obj/structure/cable, +/obj/structure/filingcabinet/chestdrawer, /turf/open/floor/iron, /area/station/tcommsat/computer) "qjF" = ( @@ -49515,6 +49515,7 @@ "qxZ" = ( /obj/structure/chair/office, /obj/effect/turf_decal/trimline/yellow/filled/line, +/obj/effect/landmark/start/telecomms_specialist, /turf/open/floor/iron, /area/station/tcommsat/computer) "qya" = ( @@ -66362,10 +66363,13 @@ /turf/open/floor/iron, /area/station/tcommsat/computer) "wnq" = ( -/obj/machinery/announcement_system, /obj/effect/turf_decal/trimline/yellow/filled/line{ dir = 1 }, +/obj/machinery/computer/telecomms/server{ + dir = 1; + network = "tcommsat" + }, /turf/open/floor/iron/dark, /area/station/tcommsat/computer) "wnK" = ( diff --git a/_maps/map_files/wawastation/wawastation.dmm b/_maps/map_files/wawastation/wawastation.dmm index afb613a9e91356..c470f9b0f3017c 100644 --- a/_maps/map_files/wawastation/wawastation.dmm +++ b/_maps/map_files/wawastation/wawastation.dmm @@ -12420,6 +12420,13 @@ /obj/machinery/airalarm/directional/west, /turf/open/floor/plating, /area/station/maintenance/solars/port/aft) +"euo" = ( +/obj/machinery/holopad, +/obj/machinery/computer/telecomms/traffic{ + dir = 8 + }, +/turf/open/floor/circuit/green/telecomms/mainframe, +/area/station/tcommsat/server) "eus" = ( /obj/structure/stairs/east, /obj/structure/railing{ @@ -41242,6 +41249,7 @@ "oHd" = ( /obj/effect/turf_decal/tile/neutral/fourcorners, /obj/structure/cable/layer3, +/obj/effect/landmark/start/telecomms_specialist, /turf/open/floor/iron/dark/telecomms, /area/station/tcommsat/server) "oHf" = ( @@ -105096,7 +105104,7 @@ dxA dPf dPf ieI -nDC +euo dPf dPf dPf diff --git a/code/__DEFINES/access.dm b/code/__DEFINES/access.dm index 6200a86da624e7..090545989d3ef3 100644 --- a/code/__DEFINES/access.dm +++ b/code/__DEFINES/access.dm @@ -352,10 +352,11 @@ ACCESS_MINISAT, \ ACCESS_RC_ANNOUNCE, \ ACCESS_TCOMMS, \ + ACCESS_TCOMMS_ADMIN, \ ACCESS_TECH_STORAGE, \ ACCESS_TELEPORTER, \ ACCESS_VAULT, \ -) +) // TANNHAUSER ADDITION -- NTSL -- added: ACCESS_TCOMMS_ADMIN up there /// Private head of staff offices, usually only granted to most cards by trimming. Do not use direct, access via SSid_access.get_flag_access_list(ACCESS_FLAG_PRV_COMMAND) #define PRIVATE_COMMAND_ACCESS list( \ @@ -495,8 +496,9 @@ ACCESS_MECH_ENGINE, \ ACCESS_MINISAT, \ ACCESS_TCOMMS, \ + ACCESS_TCOMMS_ADMIN, \ ACCESS_TECH_STORAGE, \ -) +) // TANNHAUSER ADDITION -- NTSL -- added: ACCESS_TCOMMS_ADMIN up there /// Name for the Supply region. #define REGION_SUPPLY "Supply" /// Used to seed the accesses_by_region list in SSid_access. A list of all cargo regional accesses that are overseen by the HoP. diff --git a/code/__DEFINES/logging.dm b/code/__DEFINES/logging.dm index c2f0999a34f8cd..0e935c94fa3ff7 100644 --- a/code/__DEFINES/logging.dm +++ b/code/__DEFINES/logging.dm @@ -51,6 +51,7 @@ #define LOG_RADIO_EMOTE (1 << 20) #define LOG_SPEECH_INDICATORS (1 << 21) #define LOG_TRANSPORT (1 << 22) +#define LOG_NTSL (1 << 23) // TANNHAUSER ADDITION -- NTSL (could be put into tannhauser NTSL defines, but here is safer) //Individual logging panel pages #define INDIVIDUAL_GAME_LOG (LOG_GAME) @@ -59,7 +60,8 @@ #define INDIVIDUAL_EMOTE_LOG (LOG_EMOTE | LOG_RADIO_EMOTE) #define INDIVIDUAL_COMMS_LOG (LOG_PDA | LOG_CHAT | LOG_COMMENT | LOG_TELECOMMS) #define INDIVIDUAL_OOC_LOG (LOG_OOC | LOG_ADMIN) -#define INDIVIDUAL_SHOW_ALL_LOG (LOG_ATTACK | LOG_SAY | LOG_WHISPER | LOG_EMOTE | LOG_RADIO_EMOTE | LOG_DSAY | LOG_PDA | LOG_CHAT | LOG_COMMENT | LOG_TELECOMMS | LOG_OOC | LOG_ADMIN | LOG_OWNERSHIP | LOG_GAME | LOG_ADMIN_PRIVATE | LOG_ASAY | LOG_MECHA | LOG_VIRUS | LOG_SHUTTLE | LOG_ECON | LOG_VICTIM | LOG_SPEECH_INDICATORS) +#define INDIVIDUAL_SHOW_ALL_LOG (LOG_ATTACK | LOG_SAY | LOG_WHISPER | LOG_EMOTE | LOG_RADIO_EMOTE | LOG_DSAY | LOG_PDA | LOG_CHAT | LOG_COMMENT | LOG_TELECOMMS | LOG_OOC | LOG_ADMIN | LOG_OWNERSHIP | LOG_GAME | LOG_ADMIN_PRIVATE | LOG_ASAY | LOG_MECHA | LOG_VIRUS | LOG_SHUTTLE | LOG_ECON | LOG_VICTIM | LOG_SPEECH_INDICATORS | LOG_NTSL) +// TANNHAUSER ADDITION -- NTSL -- added LOG_NTSL in the upper line #define LOGSRC_CKEY "Ckey" #define LOGSRC_MOB "Mob" diff --git a/code/__DEFINES/~tannhauser_defines/NTSL.dm b/code/__DEFINES/~tannhauser_defines/NTSL.dm new file mode 100644 index 00000000000000..bf7c69c39566c2 --- /dev/null +++ b/code/__DEFINES/~tannhauser_defines/NTSL.dm @@ -0,0 +1,25 @@ +// TANNHAUSER ADDITION -- NTSL + +// Achievements (code/__DEFINES/achievements.dm) +#define MEDAL_GOOD_BIRD "Embrace The Bird" +#define MEDAL_BAD_BIRD "Silence Bird" + +// Job display priority (code/__DEFINES/jobs.dm) +#define JOB_SIGNAL_TECHNICIAN "Signal Technician" +#define JOB_DISPLAY_ORDER_SIGNAL_TECHNICIAN 17.5 + +// Access to the NTSL console. (code/__DEFINES/access.dm) +// There are still NTSL files in the access directory, because it needed to be put into the access lists there +// It's important because only the signal technician and CE should have it. +#define ACCESS_TCOMMS_ADMIN "tcomms_admin" + +// Logging stuff (code/__DEFINES/logging.dm) +// there are still NTSL files in the logging directory, in case a new logging thingy gets added upstream and it'd break the thing here +#define LOG_CATEGORY_NTSL "ntsl" + +// Actual NTSL coding defines below +#define SERVER_LOG_STORAGE_MAX 400 // Number of chat logs the telecomms servers will store before they start deleting the older ones. +#define TELECOMMS_SCAN_RANGE 25 // The range at which the telecomms computers can scan for telecomm servers. + +/// If something is an 'object' to scripting. +#define IS_OBJECT(thing) (istype(thing, /datum) || istype(thing, /list) || istype(thing, /savefile) || istype(thing, /client) || (thing==world)) diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm index ba2b91e08fa0af..ffe67fe77429b5 100644 --- a/code/datums/chatmessage.dm +++ b/code/datums/chatmessage.dm @@ -320,6 +320,11 @@ speaker = v.source spans |= "virtual-speaker" + // TANNHAUSER ADDITION START -- NTSL -- NTSL doesn't pass a speaker when you do broadcast() since technically nothing is actually speaking. + if(!speaker) + return + // TANNHAUSER ADDITION END + // Ignore virtual speaker (most often radio messages) from ourselves if (originalSpeaker != src && speaker == src) return diff --git a/code/datums/id_trim/jobs.dm b/code/datums/id_trim/jobs.dm index b5893380fde6f2..ac9acde2783ae8 100644 --- a/code/datums/id_trim/jobs.dm +++ b/code/datums/id_trim/jobs.dm @@ -347,6 +347,7 @@ ACCESS_MINISAT, ACCESS_RC_ANNOUNCE, ACCESS_TCOMMS, + ACCESS_TCOMMS_ADMIN, // TANNHAUSER ADDITION -- NTSL ACCESS_TECH_STORAGE, ) minimal_wildcard_access = list( diff --git a/code/game/machinery/telecomms/broadcasting.dm b/code/game/machinery/telecomms/broadcasting.dm index 2c31dcbd989555..f1f9a0a6cf8b1a 100644 --- a/code/game/machinery/telecomms/broadcasting.dm +++ b/code/game/machinery/telecomms/broadcasting.dm @@ -79,6 +79,7 @@ message, // the text content of the message spans, // the list of spans applied to the message list/message_mods, // the list of modification applied to the message. Whispering, singing, ect + lvls = null, // TANNHAUSER ADDITION -- NTSL -- what z-levels is this message broadcast to? ) src.source = source src.frequency = frequency @@ -94,7 +95,8 @@ "spans" = spans, "mods" = message_mods, ) - levels = SSmapping.get_connected_levels(get_turf(source)) + // levels = SSmapping.get_connected_levels(get_turf(source)) -- TANNHAUSER EDIT OLD + levels = lvls != null ? lvls : SSmapping.get_connected_levels(get_turf(source)) // TANNHAUSER EDIT NEW #undef COMPRESSION_VOCAL_SIGNAL_MIN #undef COMPRESSION_VOCAL_SIGNAL_MAX diff --git a/code/game/machinery/telecomms/machines/server.dm b/code/game/machinery/telecomms/machines/server.dm index 0c87a6101d182f..6eb771c155ff46 100644 --- a/code/game/machinery/telecomms/machines/server.dm +++ b/code/game/machinery/telecomms/machines/server.dm @@ -34,6 +34,8 @@ if (log_entries.len >= MAX_LOG_ENTRIES) log_entries.Cut(1, 2) + signal.data["server"] = src; // TANNHAUSER ADDITION -- NTSL + // Don't create a log if the frequency is banned from being logged if(!(signal.frequency in banned_frequencies)) var/datum/comm_log_entry/log = new @@ -57,6 +59,11 @@ log.name = "data packet ([md5(identifier)])" log_entries.Add(log) + // TANNHAUSER ADDITION START -- NTSL -- Run the damn NTSL code + if(Compiler && autoruncode) + Compiler.Run(signal) + // TANNHAUSER ADDITION END + var/can_send = relay_information(signal, /obj/machinery/telecomms/hub) if(!can_send) relay_information(signal, /obj/machinery/telecomms/broadcaster) diff --git a/code/game/machinery/telecomms/telecomunications.dm b/code/game/machinery/telecomms/telecomunications.dm index ee71ca0233db23..5e0477aed1ff95 100644 --- a/code/game/machinery/telecomms/telecomunications.dm +++ b/code/game/machinery/telecomms/telecomunications.dm @@ -49,6 +49,11 @@ GLOBAL_LIST_EMPTY(telecomms_list) if(!on) return + // TANNHAUSER ADDITION START -- NTSL -- Make sure the NTSL actually has a path + if(filter && !ispath(filter)) + CRASH("relay_information() was given a path filter that wasn't actually a path!") + // TANNHAUSER ADDITION END + if(!filter || !ispath(filter, /obj/machinery/telecomms)) CRASH("null or non /obj/machinery/telecomms typepath given as the filter argument! given typepath: [filter]") diff --git a/code/game/say.dm b/code/game/say.dm index ee54ef4a8f7bf3..524a286ce9da7a 100644 --- a/code/game/say.dm +++ b/code/game/say.dm @@ -154,9 +154,11 @@ GLOBAL_LIST_INIT(freqtospan, list( var/list/stored_name = list(null) SEND_SIGNAL(speaker, COMSIG_MOVABLE_MESSAGE_GET_NAME_PART, stored_name, visible_name) namepart = stored_name[NAME_PART_INDEX] || "[speaker.GetVoice()]" + var/realnamepart = stored_name[NAME_PART_INDEX] || "[speaker.GetVoice(TRUE)]" // TANNHAUSER ADDITION -- NTSL //End name span. - var/endspanpart = "" +// var/endspanpart = "" // TANNHAUSER EDIT OLD -- NTSL + var/endspanpart = "" // TANNHAUSER EDIT NEW //Message var/messagepart @@ -172,7 +174,11 @@ GLOBAL_LIST_INIT(freqtospan, list( messagepart = " [say_emphasis(messagepart)]" - return "[spanpart1][spanpart2][freqpart][languageicon][compose_track_href(speaker, namepart)][namepart][compose_job(speaker, message_language, raw_message, radio_freq)][endspanpart][messagepart]" +// TANNHAUSER EDIT OLD -- NTSL down there +// return "[spanpart1][spanpart2][freqpart][languageicon][compose_track_href(speaker, namepart)][namepart][compose_job(speaker, message_language, raw_message, radio_freq)][endspanpart][messagepart]" +// TANNHAUSER EDIT NEW -- NTSL down there + return "[spanpart1][spanpart2][freqpart][languageicon][compose_track_href(speaker, realnamepart)][namepart][compose_job(speaker, message_language, raw_message, radio_freq)][endspanpart][messagepart]" + /atom/movable/proc/compose_track_href(atom/movable/speaker, message_langs, raw_message, radio_freq) return "" @@ -313,6 +319,7 @@ INITIALIZE_IMMEDIATE(/atom/movable/virtualspeaker) source = M if(istype(M)) name = radio.anonymize ? "Unknown" : M.GetVoice() + realvoice = name // TANNHAUSER ADDITION -- NTSL verb_say = M.get_default_say_verb() verb_ask = M.verb_ask verb_exclaim = M.verb_exclaim diff --git a/code/modules/mob/living/silicon/ai/ai_say.dm b/code/modules/mob/living/silicon/ai/ai_say.dm index 45c3fb26a391e0..e34d761b191985 100644 --- a/code/modules/mob/living/silicon/ai/ai_say.dm +++ b/code/modules/mob/living/silicon/ai/ai_say.dm @@ -1,5 +1,6 @@ /mob/living/silicon/ai/compose_track_href(atom/movable/speaker, namepart) - var/mob/M = speaker.GetSource() +// var/mob/M = speaker.GetSource() // TANNHAUSER EDIT OLD + var/mob/M = speaker.GetJob() // TANNHAUSER EDIT NEW -- NTSL if(M) return "" return "" diff --git a/code/modules/research/techweb/nodes/engi_nodes.dm b/code/modules/research/techweb/nodes/engi_nodes.dm index 949b880d1f23a6..d1d509eebcf4a3 100644 --- a/code/modules/research/techweb/nodes/engi_nodes.dm +++ b/code/modules/research/techweb/nodes/engi_nodes.dm @@ -104,6 +104,7 @@ "s_filter", "s_transmitter", "s_treatment", + "s_traffic", // TANNHAUSER ADDITION -- NTSL -- The board to actually program in NTSL ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS) diff --git a/config/ntsl_filter.txt b/config/ntsl_filter.txt new file mode 100644 index 00000000000000..7c7509595c31cf --- /dev/null +++ b/config/ntsl_filter.txt @@ -0,0 +1,50 @@ +# This file contains filters required for replacements in supported speech systems. +# Each line contains one pattern and one replacement. The patterns used are regular +# expression patterns. +# +# Examples: +# +# fuck=duck +# This would replace all instances of "fuck" with duck. So "fucking" would become "ducking". +# +# fu+ck=duck +# This would do the same as above, except it would accept one or more "u". So "fuuuuuck" would become "duck". +# +# Activate the ([\\S]+?)=use $1 +# This would match a group, and use it in a pattern. So "Activate the probulator" would become "use probulator". +# +# \\b[f]+[u]+\\b=fun you +# This would make any word with combinations of "f" followed by "u" be replaced with fun you. So "ffffffffuuuuuuuuuuu" becomes "fun you". + +#prettyfiltertest=SUCCESS + +\bf[4a]*g+=BAN ME ADMINS! +f[4a]*gg+[0o]*t=BAN ME ADMINS! + +#Regex for le epic nword that if i type out will get the repo yeeted, in multiple variations, even capturing against simple replacement tricks (like "n.w.o.r.d" or "n!ord") +#Case sensitivity precautions added due to known regex bug in 512.14XX. Feel free to remove when Lummox fixes it. +\b[Nn]+[\W_]*[!iI\/?1\\]+[\W_]*[qgbQGB]+[\W_]*[qgbQGB]?[\W_]*(?:[eE3]+[\W_]*[Rr]+|[Aa]+)(?![iI][Aa])=BAN ME ADMINS! + +#Regex for le epic lword +#Case sensitivity precautions added due to known regex bug in 512.14XX. Feel free to remove when Lummox fixes it. +\b[Ll]+[\W_]*[!iI\/?1\\]+[\W_]*[qgQG]+[\W_]*[qgQG]?[\W_]*(?:[eE3]+[\W_]*[Rr]+|[Aa]+)\b=BAN ME ADMINS! + +#Regex for le epic nword but ending in an o: +#Case sensitivity precautions added due to known regex bug in 512.14XX. Feel free to remove when Lummox fixes it. +\b[nlNL][\W_]*[eE3]+[\W_]*[Gg]+[\W_]*[Rr]+[\W_]*[0Oo\*]+(?:\b|[iI!\/\\]+[Dd]|[Ee]+[Ss]|[Ss])\b=BAN ME ADMINS! + +\bnig\b=BAN ME ADMINS! + +#Regex for the first 4 letters of le epic nword and the combinaison of le epic nword and piglet: +n[i1!\\\/]+g(?:g|l+[e3]+t)=BAN ME ADMINS! + +\bligg=BAN ME ADMINS! + +One day while Andy was masturbating=BAN ME ADMINS! +XEzwgBD=BAN ME ADMINS! + +#Regex for tyranny without the y +tra+n+y+=BAN ME ADMINS! + +\b(?:https?:\/\/)?(?:www|i)?\.?(?!yogstation\.net|github\.com)\w{4,128}\.\w{2}\.?\w{0,2}\b\S*=[Link Removed] +[^ -ÿ]+=​ diff --git a/icons/ui/achievements/achievements.dmi b/icons/ui/achievements/achievements.dmi index 740759467e0ccb..6a3cf2a081de7a 100644 Binary files a/icons/ui/achievements/achievements.dmi and b/icons/ui/achievements/achievements.dmi differ diff --git a/modular_skyrat/modules/telecomms_specialist/telecomms_specialist.dm b/modular_skyrat/modules/telecomms_specialist/telecomms_specialist.dm index f4aa24cdfe2cca..64c9768fcb7a3a 100644 --- a/modular_skyrat/modules/telecomms_specialist/telecomms_specialist.dm +++ b/modular_skyrat/modules/telecomms_specialist/telecomms_specialist.dm @@ -104,6 +104,7 @@ ACCESS_NETWORK, //Sysadmin gets network access, of course ACCESS_RC_ANNOUNCE, //to use the requests console announcement found in telecomms ACCESS_TCOMMS, + ACCESS_TCOMMS_ADMIN, // TANNHAUSER ADDITION -- NTSL ACCESS_TECH_STORAGE, ) extra_access = list( diff --git a/modular_tannhauser/modules/NTSL/code/achievements.dm b/modular_tannhauser/modules/NTSL/code/achievements.dm new file mode 100644 index 00000000000000..065648c9449c8f --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/achievements.dm @@ -0,0 +1,11 @@ +/datum/award/achievement/jobs/Poly_silent + name = "Silence Bird!" + desc = "As a signal technician create a script that mutes poly" + database_id = MEDAL_BAD_BIRD + icon_state = "bird_silent" + +/datum/award/achievement/jobs/Poly_loud + name = "Embrace The Bird!" + desc = "As a signal technician create a script that makes poly LOUD" + database_id = MEDAL_GOOD_BIRD + icon_state = "bird_loud" diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/AST/ast_nodes.dm b/modular_tannhauser/modules/NTSL/code/coding_language/AST/ast_nodes.dm new file mode 100644 index 00000000000000..95da86e798c259 --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/AST/ast_nodes.dm @@ -0,0 +1,166 @@ +/** + * An abstract syntax tree (AST) is a representation of source code in a computer-friendly format. It is composed of nodes, + * each of which represents a certain part of the source code. For example, an node represents an if statement in the + * script's source code. Because it is a representation of the source code in memory, it is independent of any specific scripting language. + * This allows a script in any language for which a parser exists to be run by the interpreter. + * + * The AST is produced by an object. It consists of a with an arbitrary amount of statements. These statements are + * run in order by an object. A statement may in turn run another block (such as an if statement might if its condition is + * met). + * + * Articles: + * - + */ + +/** + * Macros: Operator Precedence + * The higher the value, the lower the priority in the precedence. + */ +#define OOP_ASSIGN 0 +///Logical or || +#define OOP_OR 1 +///Logical and && +#define OOP_AND 2 +///Bitwise operations &, | +#define OOP_BIT 3 +///Equality checks ==, != +#define OOP_EQUAL 4 +///Greater than, less than, etc >, <, >=, <= +#define OOP_COMPARE 5 +///Addition and subtraction + - +#define OOP_ADD 6 +///Multiplication and division * / % +#define OOP_MULTIPLY 7 +///Exponents ^ +#define OOP_POW 8 +///Unary Operators ! +#define OOP_UNARY 9 +///Parenthesis () +#define OOP_GROUP 10 + +/** + * Node + */ +/datum/node + ///Returns line number information + var/datum/token/token + +/datum/node/proc/ToString() + return "[type]" + +/* + * identifier + */ +/datum/node/identifier + var/id_name + +/datum/node/identifier/New(id, token) + . = ..() + src.id_name = id + src.token = token + +/datum/node/identifier/ToString() + return id_name + +/* + * expression + */ +/datum/node/expression +/* + * operator + * See and for subtypes. + */ +/datum/node/expression/expression_operator + var/datum/node/expression/exp + var/tmp/name + var/tmp/precedence + +/datum/node/expression/expression_operator/New(token, exp) + . = ..() + if(!name) + name = "[type]" + src.token = token + src.exp = exp + +/datum/node/expression/expression_operator/ToString() + return "operator: [name]" + +/datum/node/expression/member + var/datum/node/expression/object + var/tmp/temp_object // so you can pre-eval it, used for function calls and assignments + +/datum/node/expression/member/New(token) + src.token = token + return ..() + +/datum/node/expression/member/dot + var/datum/node/identifier/id + +/datum/node/expression/member/brackets + var/datum/node/expression/index + var/tmp/temp_index + + +/* + * FunctionCall + */ +/datum/node/expression/FunctionCall + //Function calls can also be expressions or statements. + var/datum/node/expression/function + var/list/parameters = list() + +/datum/node/expression/FunctionCall/New(token) + . = ..() + src.token = token + +/* + * literal + */ +/datum/node/expression/value/literal + var/value + +/datum/node/expression/value/literal/New(value) + . = ..() + src.value = value + +/datum/node/expression/value/literal/ToString() + return value + +/* + * Variable + */ +/datum/node/expression/value/variable + ///Either a node/identifier or another node/expression/value/variable which points to the object + var/datum/node/object + var/datum/node/identifier/id + +/datum/node/expression/value/variable/New(datum/node/identifier/ident, datum/token/token) + . = ..() + src.token = token + src.id = ident + if(istext(id)) + src.id = new(id) + +/datum/node/expression/value/variable/ToString() + return id.ToString() + +/datum/node/expression/value/list_init + var/list/init_list + +/datum/node/expression/value/list_init/New(datum/token/token) + . = ..() + src.token = token + +/** + * Reference + */ +/datum/node/expression/value/reference + var/datum/value + +/datum/node/expression/value/reference/New(value, datum/token/token) + . = ..() + src.token = token + src.value = value + +/datum/node/expression/value/reference/ToString() + return "ref: [value] ([value.type])" diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/AST/block.dm b/modular_tannhauser/modules/NTSL/code/coding_language/AST/block.dm new file mode 100644 index 00000000000000..6c898974208dd2 --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/AST/block.dm @@ -0,0 +1,42 @@ +/* + * BlockDefinition + * An object representing a set of actions to perform independently from the rest of the script. Blocks are basically just + * lists of statements to execute which also contain some local variables and methods. Note that since functions are local to a block, + * it is possible to have a function definition inside of any type of block (such as in an if statement or another function), + * and not just in the global scope as in many languages. + */ +/datum/node/BlockDefinition + var/list/statements = new + var/list/functions = new + var/list/initial_variables = new + +/** + * SetVar + * Defines a permanent variable. The variable will not be deleted when it goes out of scope. + * + * Notes: + * Since all pre-existing temporary variables are deleted, + * it is not generally desirable to use this proc after the interpreter has been instantiated. + * Instead, use . + * + * See Also: + * - + */ +/datum/node/BlockDefinition/proc/SetVar(name, value) + initial_variables[name] = value + +/** + * Globalblock + * A block object representing the global scope + */ +/datum/node/BlockDefinition/GlobalBlock + +/datum/node/BlockDefinition/GlobalBlock/New() + initial_variables["null"] = null + return ..() + +/** + * FunctionBlock + * A block representing a function body. + */ +/datum/node/BlockDefinition/FunctionBlock diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/AST/operators/binary_operators.dm b/modular_tannhauser/modules/NTSL/code/coding_language/AST/operators/binary_operators.dm new file mode 100644 index 00000000000000..1475c6cdd1ab88 --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/AST/operators/binary_operators.dm @@ -0,0 +1,166 @@ +/* + * binary + * Represents a binary operator in the AST. A binary operator takes two operands (ie x and y) and returns a value. + */ +/datum/node/expression/expression_operator/binary + var/datum/node/expression/exp2 + +/** + * Equal + * Returns TRUE if x == y + */ +/datum/node/expression/expression_operator/binary/Equal + precedence = OOP_EQUAL + +/** + * NotEqual + * + * Returns TRUE if x != y + */ +/datum/node/expression/expression_operator/binary/NotEqual + precedence = OOP_EQUAL + +/** + * Greater + * Returns TRUE if x > y + */ +/datum/node/expression/expression_operator/binary/Greater + precedence = OOP_COMPARE + +/** + * Less + * Returns TRUE if x < y + */ +/datum/node/expression/expression_operator/binary/Less + precedence = OOP_COMPARE + +/** + * GreaterOrEqual + * Returns TRUE if x >= y + */ +/datum/node/expression/expression_operator/binary/GreaterOrEqual + precedence = OOP_COMPARE + +/** + * LessOrEqual + * Returns TRUE if x <= y + */ +/datum/node/expression/expression_operator/binary/LessOrEqual + precedence = OOP_COMPARE + + +/** + * LogicalAnd + * Returns TRUE if x and y are TRUE + */ +/datum/node/expression/expression_operator/binary/LogicalAnd + precedence = OOP_AND + +/** + * LogicalOr + * Returns TRUE if x and/or y are TRUE + */ +/datum/node/expression/expression_operator/binary/LogicalOr + precedence = OOP_OR + +/** + * LogicalXor + * Returns TRUE if either x or y are TRUE, but not both. + * Not implemented in nS + */ +/datum/node/expression/expression_operator/binary/LogicalXor + precedence = OOP_OR + + +/** + * BitwiseAnd + * Performs a Bitwise operation + * + * Example: 011 & 110 = 010 + */ +/datum/node/expression/expression_operator/binary/BitwiseAnd + precedence = OOP_BIT + +/** + * BitwiseOr + * Performs a bitwise or operation + * + * Example: 011 | 110 = 111 + */ +/datum/node/expression/expression_operator/binary/BitwiseOr + precedence = OOP_BIT + +/** + * BitwiseXor + * Performs a bitwise exclusive or operation + * + * Example: 011 xor 110 = 101 + */ +/datum/node/expression/expression_operator/binary/BitwiseXor + precedence = OOP_BIT + +/** + * Add + * Returns the sum of x and y + */ +/datum/node/expression/expression_operator/binary/Add + precedence = OOP_ADD + +/** + * Substract + * Returns the difference of x and y + */ +/datum/node/expression/expression_operator/binary/Subtract + precedence = OOP_ADD + +/** + * Multiply + * Returns the product of x and y + */ +/datum/node/expression/expression_operator/binary/Multiply + precedence = OOP_MULTIPLY + +/** + * Divide + * Returns the quotient of x and y + */ +/datum/node/expression/expression_operator/binary/Divide + precedence = OOP_MULTIPLY + +/** + * Power + * Returns x raised to the power of y + */ +/datum/node/expression/expression_operator/binary/Power + precedence = OOP_POW + +/** + * Modulo + * Returns the remainder of x / y + */ +/datum/node/expression/expression_operator/binary/Modulo + precedence = OOP_MULTIPLY + +/** + * Assign + */ +/datum/node/expression/expression_operator/binary/Assign + precedence = OOP_ASSIGN + +/datum/node/expression/expression_operator/binary/Assign/BitwiseAnd + +/datum/node/expression/expression_operator/binary/Assign/BitwiseOr + +/datum/node/expression/expression_operator/binary/Assign/BitwiseXor + +/datum/node/expression/expression_operator/binary/Assign/Add + +/datum/node/expression/expression_operator/binary/Assign/Subtract + +/datum/node/expression/expression_operator/binary/Assign/Multiply + +/datum/node/expression/expression_operator/binary/Assign/Divide + +/datum/node/expression/expression_operator/binary/Assign/Power + +/datum/node/expression/expression_operator/binary/Assign/Modulo diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/AST/operators/unary_operators.dm b/modular_tannhauser/modules/NTSL/code/coding_language/AST/operators/unary_operators.dm new file mode 100644 index 00000000000000..b3927f35b01068 --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/AST/operators/unary_operators.dm @@ -0,0 +1,41 @@ +/** + * Unary Operators + * + * Unary + * Represents a Unary operator in the AST. + * Unary operators take a single operand (referred to as 'x' below) and returns a value. + */ +/datum/node/expression/expression_operator/unary + precedence = OOP_UNARY + +/** + * LogicalNot + * Returns !x + * + * Ex: !TRUE = FALSE and !FALSE = TRUE + */ +/datum/node/expression/expression_operator/unary/LogicalNot + name = "logical not" + +/** + * BitwiseNot + * Returns the value of a bitwise not operation performed on x + * + * Ex: ~10 (decimal 2) = 01 (decimal 1) + */ +/datum/node/expression/expression_operator/unary/BitwiseNot + name = "bitwise not" + +/** + * Minus + * Returns -x + */ +/datum/node/expression/expression_operator/unary/Minus + name = "minus" + +/** + * Group + * A special unary operator representing a value in parentheses. + */ +/datum/node/expression/expression_operator/unary/group + precedence = OOP_GROUP diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/AST/statement.dm b/modular_tannhauser/modules/NTSL/code/coding_language/AST/statement.dm new file mode 100644 index 00000000000000..22002f5228b46e --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/AST/statement.dm @@ -0,0 +1,95 @@ +/* + * Statement + * An object representing a single instruction run by an interpreter. + */ +/datum/node/statement + +/datum/node/statement/New(datum/token/token) + . = ..() + src.token = token + +/* + * FunctionDefinition + * Defines a function. + */ +/datum/node/statement/FunctionDefinition + var/func_name + var/list/parameters = new + var/datum/node/BlockDefinition/FunctionBlock/block + +/* + * VariableAssignment + * Sets a variable in an accessible scope to the given value if one exists, otherwise initializes a new local variable to the given value. + * + * Notes: + * If a variable with the same name exists in a higher block, the value will be assigned to it. If not, + * a new variable is created in the current block. To force creation of a new variable, use . + * + * See Also: + * - + */ +/datum/node/statement/VariableAssignment + var/datum/node/identifier/object + var/datum/node/identifier/var_name + var/datum/node/expression/value + +/* + * VariableDeclaration + * Intializes a local variable to a null value. + * + * See Also: + * - + */ +/datum/node/statement/VariableDeclaration + var/datum/node/identifier/object + var/datum/node/identifier/var_name + +/** + * IfStatement + */ +/datum/node/statement/IfStatement + var/skip = 0 + var/datum/node/BlockDefinition/block + var/datum/node/BlockDefinition/else_block //can be null + var/datum/node/expression/cond + var/datum/node/statement/else_if + +/datum/node/statement/IfStatement/ElseIf + + +/** + * WhileLoop + * Loops while a given condition is TRUE. + */ +/datum/node/statement/WhileLoop + var/datum/node/BlockDefinition/block + var/datum/node/expression/cond + +/* + * ForLoop + * Loops while test is true, initializing a variable, increasing the variable + */ +/datum/node/statement/ForLoop + var/datum/node/BlockDefinition/block + var/datum/node/expression/test + var/datum/node/expression/init + var/datum/node/expression/increment + +/* + * BreakStatement + * Ends a loop. + */ +/datum/node/statement/BreakStatement + +/* + * ContinueStatement + * Skips to the next iteration of a loop. + */ +/datum/node/statement/ContinueStatement + +/* + * ReturnStatement + * Ends the function and returns a value. + */ +/datum/node/statement/ReturnStatement + var/datum/node/expression/value diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/errors.dm b/modular_tannhauser/modules/NTSL/code/coding_language/errors.dm new file mode 100644 index 00000000000000..af50ce124a45fe --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/errors.dm @@ -0,0 +1,183 @@ +/datum/scriptError + ///A message describing the problem. + var/message + +/datum/scriptError/New(msg) + if(msg) + message = msg + +/datum/scriptError/BadToken + message = "Unexpected token: " + var/datum/token/token + +/datum/scriptError/BadToken/New(datum/token/token) + src.token = token + + if(token && token.line) + message = "[token.line]: [message]" + if(istype(token)) + message += "[token.value]" + else + message += "[token]" + +/datum/scriptError/InvalidID + parent_type = /datum/scriptError/BadToken + message = "Invalid identifier name: " + +/datum/scriptError/ReservedWord + parent_type = /datum/scriptError/BadToken + message = "Identifer using reserved word: " + +/datum/scriptError/BadNumber + parent_type = /datum/scriptError/BadToken + message = "Bad number: " + +/datum/scriptError/BadReturn + message = "Unexpected return statement outside of a function." + var/datum/token/token + +/datum/scriptError/BadReturn/New(datum/token/token) + src.token = token + +/datum/scriptError/EndOfFile + message = "Unexpected end of file." + +/datum/scriptError/ExpectedToken + message = "Expected: " + +/datum/scriptError/ExpectedToken/New(id, datum/token/token) + if(token) + message = "[token.line ? token.line : "???"]: [message]'[id]'. Found '[token.value]'." + else + message = "???: [message]'[id]'. Token did not exist!" + +/datum/scriptError/UnterminatedComment + message = "Unterminated multi-line comment statement: expected */" + +/datum/scriptError/DuplicateFunction + +/datum/scriptError/DuplicateFunction/New(name, datum/token/token) + message = "Function '[name]' defined twice." + +/datum/scriptError/ParameterFunction + message = "You cannot use a function inside a parameter." + +/datum/scriptError/ParameterFunction/New(datum/token/token) + var/line = "???" + if(token) + line = token.line + message = "[line]: [message]" + +/datum/scriptError/InvalidAssignment + message = "Left side of assignment cannot be assigned to." + +/datum/scriptError/OutdatedScript + message = "Your script looks like it was for an older version of NTSL! Your script may not work as intended. See documentation for new syntax and API." + +/* + * Runtime Error + * An error thrown by the interpreter in running the script. + */ +/datum/runtimeError + var/name + ///A basic description as to what went wrong. + var/message + var/datum/scope/scope + var/datum/token/token + +///Returns a description of the error suitable for showing to the user. +/datum/runtimeError/proc/ToString() + . = "[name]: [message]" + if(!scope) + return + var/last_line + var/last_col + if(token) + last_line = token.line + last_col = token.column + var/datum/scope/cur_scope = scope + while(cur_scope) + if(cur_scope.function) + . += "\n\tat [cur_scope.function.func_name]([last_line]:[last_col])" + if(cur_scope.call_node && cur_scope.call_node.token) + last_line = cur_scope.call_node.token.line + last_col = cur_scope.call_node.token.column + else + last_line = null + last_col = null + cur_scope = cur_scope.parent + if(last_line) + . += "\n\tat \[global]([last_line]:[last_col])" + else + . += "\n\tat \[internal]" + +/datum/runtimeError/TypeMismatch + name = "TypeMismatchError" + +/datum/runtimeError/TypeMismatch/New(op, a, b) + if(isnull(a)) + a = "NULL" + if(isnull(b)) + b = "NULL" + message = "Type mismatch: '[a]' [op] '[b]'" + +/datum/runtimeError/UnexpectedReturn + name = "UnexpectedReturnError" + message = "Unexpected return statement." + +/datum/runtimeError/UnknownInstruction + name = "UnknownInstructionError" + +/datum/runtimeError/UnknownInstruction/New(datum/node/op) + message = "Unknown instruction type '[op.type]'. This may be due to incompatible compiler and interpreter versions or a lack of implementation." + +/datum/runtimeError/UndefinedVariable + name = "UndefinedVariableError" + +/datum/runtimeError/UndefinedVariable/New(variable) + message = "Variable '[variable]' has not been declared." + +/datum/runtimeError/IndexOutOfRange + name = "IndexOutOfRangeError" + +/datum/runtimeError/IndexOutOfRange/New(obj, idx) + message = "Index [obj]\[[idx]] is out of range." + +/datum/runtimeError/UndefinedFunction + name = "UndefinedFunctionError" + +/datum/runtimeError/UndefinedFunction/New(function) + message = "Function '[function]()' has not been defined." + +/datum/runtimeError/DuplicateVariableDeclaration + name = "DuplicateVariableError" + +/datum/runtimeError/DuplicateVariableDeclaration/New(variable) + message = "Variable '[variable]' was already declared." + +/datum/runtimeError/IterationLimitReached + name = "MaxIterationError" + message = "A loop has reached its maximum number of iterations." + +/datum/runtimeError/RecursionLimitReached + name = "MaxRecursionError" + message = "The maximum amount of recursion has been reached." + +/datum/runtimeError/DivisionByZero + name = "DivideByZeroError" + message = "Division by zero (or a NULL value) attempted." + +/datum/runtimeError/InvalidAssignment + message = "Left side of assignment cannot be assigned to." + +/datum/runtimeError/MaxCPU + name = "MaxComputationalUse" + +/datum/runtimeError/MaxCPU/New(maxcycles) + message = "Maximum amount of computational cycles reached (>= [maxcycles])." + +/datum/runtimeError/Internal + name = "InternalError" + +/datum/runtimeError/Internal/New(exception/exception_message) + message = exception_message.name diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/implementations/logic.dm b/modular_tannhauser/modules/NTSL/code/coding_language/implementations/logic.dm new file mode 100644 index 00000000000000..48298fd153a1a1 --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/implementations/logic.dm @@ -0,0 +1,405 @@ +// Script -> BYOND code procs +#define SCRIPT_MAX_REPLACEMENTS_ALLOWED 200 + +/** + * List operations (lists known as vectors in n_script) + */ +/// Picks one random item from the list +/datum/n_function/default/pick + name = "pick" + +/datum/n_function/default/pick/execute(this_obj, list/params) + var/list/finalpick = list() + for(var/e in params) + if(IS_OBJECT(e)) + if(istype(e, /list)) + var/list/sublist = e + for(var/sube in sublist) + finalpick.Add(sube) + continue + finalpick.Add(e) + + return pick(finalpick) + +/** + * String methods + */ +///If list, finds a value in it, if text, finds a substring in it +/datum/n_function/default/find + name = "find" + +/datum/n_function/default/find/execute(this_obj, list/params) + var/haystack = length(params) >= 1 ? params[1] : null + var/needle = length(params) >= 2 ? params[2] : null + var/start = length(params) >= 3 ? params[3] : 1 + var/end = length(params) >= 4 ? params[4] : 0 + if(!haystack || !needle) + return + if(IS_OBJECT(haystack)) + if(!istype(haystack, /list)) + return + if(length(haystack) >= end && start > 0) + var/list/listhaystack = haystack + return listhaystack.Find(needle, start, end) + else if(istext(haystack)) + if(length(haystack) >= end && start > 0) + return findtext(haystack, needle, start, end) + +///Returns a substring of the string +/datum/n_function/default/substr + name = "substr" + +/datum/n_function/default/substr/execute(this_obj, list/params) + var/string = length(params) >= 1 ? params[1] : null + var/start = length(params) >= 2 ? params[2] : 1 + var/end = length(params) >= 3 ? params[3] : 0 + if(istext(string) && isnum(start) && isnum(end)) + if(start > 0) + return copytext(string, start, end) + +///Returns the length of the string or list +/datum/n_function/default/length + name = "length" + +/datum/n_function/default/length/execute(this_obj, list/params) + var/container = length(params) >= 1 ? params[1] : null + if(container) + if(istype(container, /list) || istext(container)) + return length(container) + return FALSE + +///Lowercase all characters +/datum/n_function/default/lower + name = "lower" + +/datum/n_function/default/lower/execute(this_obj, list/params) + var/string = length(params) >= 1 ? params[1] : null + if(istext(string)) + return lowertext(string) + +///Uppercase all characters +/datum/n_function/default/upper + name = "upper" + +/datum/n_function/default/upper/execute(this_obj, list/params) + var/string = length(params) >= 1 ? params[1] : null + if(istext(string)) + return uppertext(string) + +///Converts a string to a list +/datum/n_function/default/explode + name = "explode" + +/datum/n_function/default/explode/execute(this_obj, list/params) + var/string = length(params) >= 1 ? params[1] : null + var/separator = length(params) >= 2 ? params[2] : "" + if(istext(string) && (istext(separator) || isnull(separator))) + return splittext(string, separator) + +///Converts a list to a string +/datum/n_function/default/implode + name = "implode" + +/datum/n_function/default/implode/execute(this_obj, list/params) + var/list/li = LAZYACCESS(params, 1) + var/separator = LAZYACCESS(params, 2) + if(istype(li) && (istext(separator) || isnull(separator))) + return jointext(li, separator) + +///Repeats the string x times +/datum/n_function/default/repeat + name = "repeat" + +/datum/n_function/default/repeat/execute(this_obj, list/params) + var/string = length(params) >= 1 ? params[1] : null + var/amount = length(params) >= 2 ? params[2] : null + if(istext(string) && isnum(amount)) + var/i + var/newstring = "" + if(length(newstring) * amount >= 1000) + return + for(i = 0, i <= amount, i++) + if(i >= 1000) + break + newstring = newstring + string + + return newstring + +///Reverses the order of the string. "Clown" becomes "nwolC" +/datum/n_function/default/reverse + name = "reverse" + +/datum/n_function/default/reverse/execute(this_obj, list/params) + var/string = length(params) >= 1 ? params[1] : null + if(istext(string)) + var/newstring = "" + var/i + for(i = length(string), i > 0, i--) + if(i >= 1000) + break + newstring = newstring + copytext(string, i, i+1) + + return newstring + +//Turns a String into a Number +/datum/n_function/default/tonum + name = "tonum" + +/datum/n_function/default/tonum/execute(this_obj, list/params) + var/string = length(params) >= 1 ? params[1] : null + if(istext(string)) + return text2num(string) + +/datum/n_function/default/proper + name = "proper" + +/datum/n_function/default/proper/execute(this_obj, list/params) + var/string = length(params) >= 1 ? params[1] : null + if(!istext(string)) + return "" + + return text("[][]", uppertext(copytext(string, 1, 2)), lowertext(copytext(string, 2))) + +/** + * Number methods + */ + +///Returns the highest value of the arguments +///Need custom functions here cause byond's min and max runtimes if you give them a string or list. +/datum/n_function/default/max + name = "max" + +/datum/n_function/default/max/execute(this_obj, list/params) + if(!length(params)) + return FALSE + + var/max = params[1] + for(var/e in params) + if(isnum(e) && e > max) + max = e + + return max + +///Returns the lowest value of the arguments +/datum/n_function/default/min + name = "min" + +/datum/n_function/default/min/execute(this_obj, list/params) + if(!length(params)) + return FALSE + + var/min = params[1] + for(var/e in params) + if(isnum(e) && e < min) + min = e + + return min + +/datum/n_function/default/prob + name = "prob" + +/datum/n_function/default/prob/execute(this_obj, list/params) + var/chance = length(params) >= 1 ? params[1] : null + return prob(chance) + +/datum/n_function/default/randseed + name = "randseed" + +/datum/n_function/default/randseed/execute(this_obj, list/params) + //var/seed = length(params) >= 1 ? params[1] : null + //rand_seed(seed) + +/datum/n_function/default/rand + name = "rand" + +/datum/n_function/default/rand/execute(this_obj, list/params) + var/low = length(params) >= 1 ? params[1] : null + var/high = length(params) >= 2 ? params[2] : null + if(isnull(low) && isnull(high)) + return rand() + + return rand(low, high) + +///Turns a Number into a String +/datum/n_function/default/tostring + name = "tostring" + +/datum/n_function/default/tostring/execute(this_obj, list/params) + var/num = length(params) >= 1 ? params[1] : null + if(isnum(num)) + return num2text(num) + +// Squareroot +/datum/n_function/default/sqrt + name = "sqrt" + +/datum/n_function/default/sqrt/execute(this_obj, list/params) + var/num = length(params) >= 1 ? params[1] : null + if(isnum(num)) + return sqrt(num) + +///Magnitude of a Number +/datum/n_function/default/abs + name = "abs" + +/datum/n_function/default/abs/execute(this_obj, list/params) + var/num = length(params) >= 1 ? params[1] : null + if(isnum(num)) + return abs(num) + +///Rounds a number down +/datum/n_function/default/floor + name = "floor" + +/datum/n_function/default/floor/execute(this_obj, list/params) + var/num = length(params) >= 1 ? params[1] : null + if(isnum(num)) + return round(num) + +///Rounds a number up +/datum/n_function/default/ceil + name = "ceil" + +/datum/n_function/default/ceil/execute(this_obj, list/params) + var/num = length(params) >= 1 ? params[1] : null + if(isnum(num)) + return round(num)+1 + +///Rounds a number to its nearest integer +/datum/n_function/default/round + name = "round" + +/datum/n_function/default/round/execute(this_obj, list/params) + var/num = length(params) >= 1 ? params[1] : null + if(isnum(num)) + if(num-round(num) < 0.5) + return round(num) + return round(num) + 1 + +/// Clamps a number between min and max +/datum/n_function/default/clamp + name = "clamp" + +/datum/n_function/default/clamp/execute(this_obj, list/params) + var/num = length(params) >= 1 ? params[1] : null + var/min = length(params) >= 2 ? params[2] : -1 + var/max = length(params) >= 3 ? params[3] : 1 + if(isnum(num) && isnum(min) && isnum(max)) + if(num <= min) + return min + if(num >= max) + return max + return num + +///Returns TRUE if a number is inbetween Min and Max +/datum/n_function/default/inrange + name = "inrange" + +/datum/n_function/default/inrange/execute(this_obj, list/params) + var/num = length(params) >= 1 ? params[1] : null + var/min = length(params) >= 2 ? params[2] : -1 + var/max = length(params) >= 3 ? params[3] : 1 + if(isnum(num)&&isnum(min)&&isnum(max)) + return ((min <= num) && (num <= max)) + +///Returns the sine of a number +/datum/n_function/default/sin + name = "sin" + +/datum/n_function/default/sin/execute(this_obj, list/params) + var/num = length(params) >= 1 ? params[1] : null + if(isnum(num)) + return sin(num) + +///Returns the cosine of a number +/datum/n_function/default/cos + name = "cos" + +/datum/n_function/default/cos/execute(this_obj, list/params) + var/num = length(params) >= 1 ? params[1] : null + if(isnum(num)) + return cos(num) + +///Returns the arcsine of a number +/datum/n_function/default/asin + name = "asin" + +/datum/n_function/default/asin/execute(this_obj, list/params) + var/num = length(params) >= 1 ? params[1] : null + if(isnum(num) && -1 <= num && num <= 1) + return arcsin(num) + +///Returns the arccosine of a number +/datum/n_function/default/acos + name = "acos" + +/datum/n_function/default/acos/execute(this_obj, list/params) + var/num = length(params) >= 1 ? params[1] : null + if(isnum(num) && -1 <= num && num <= 1) + return arccos(num) + +///Returns the natural log of a number +/datum/n_function/default/log + name = "log" + +/datum/n_function/default/log/execute(this_obj, list/params) + var/num = length(params) >= 1 ? params[1] : null + if(isnum(num) && 0 < num) + return log(num) + +///Replaces text +/datum/n_function/default/replace + name = "replace" + +/datum/n_function/default/replace/execute(this_obj, list/params) + var/text = length(params) >= 1 ? params[1] : null + var/find = length(params) >= 2 ? params[2] : null + var/replacement = length(params) >= 3 ? params[3] : null + if(!istext(text) || !istext(find) || !istext(replacement)) + return + var/find_len = length(find) + if(!find_len) + return text + + var/max_count + if(length(params) >= 4 && isnum(params[4])) + max_count = min(params[4], SCRIPT_MAX_REPLACEMENTS_ALLOWED) + else + max_count = SCRIPT_MAX_REPLACEMENTS_ALLOWED + + . = "" + var/last_found = 1 + for(var/count = 0; count < max_count; ++count) + var/found = findtext(text, find, last_found, 0) + if(!found) + break + . += copytext(text, last_found, found) + . += replacement + last_found = found + find_len + return . + copytext(text, last_found) + +/** + * Miscellaneous functions + */ + +/datum/n_function/default/time + name = "time" + +/datum/n_function/default/time/execute(this_obj, list/params) + return world.timeofday + +///Clone of BYOND's sleep() +/datum/n_function/default/sleeps + name = "sleep" + +/datum/n_function/default/sleeps/execute(this_obj, list/params) + var/time = length(params) >= 1 ? params[1] : null + sleep(time) + +/datum/n_function/default/timestamp + name = "timestamp" + +/datum/n_function/default/timestamp/execute(this_obj, list/params) + return gameTimestamp(arglist(params)) + +#undef SCRIPT_MAX_REPLACEMENTS_ALLOWED diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/implementations/telecomms_translator.dm b/modular_tannhauser/modules/NTSL/code/coding_language/implementations/telecomms_translator.dm new file mode 100644 index 00000000000000..60ccdc2f91e3f0 --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/implementations/telecomms_translator.dm @@ -0,0 +1,506 @@ +/** + * Nanotrasen TCS Language - Made by Doohl, ported to Yogs by Altoids + */ +#define HUMAN (1<<0) +#define MONKEY (1<<1) +#define ROBOT (1<<2) +#define DRACONIC (1<<3) +#define BEACHTONGUE (1<<4) +#define SYLVAN (1<<5) +#define ETHEREAN (1<<6) +#define BONE (1<<7) +#define MOTH (1<<8) +#define CAT (1<<9) +#define ENGLISH (1<<10) + +///Span classes that players are allowed to set in a radio transmission. +GLOBAL_LIST_INIT(allowed_custom_spans, list( + SPAN_ROBOT, + SPAN_YELL, + SPAN_ITALICS, + SPAN_SANS, + SPAN_COMMAND, + SPAN_CLOWN, +)) + +///Language datums that players are allowed to translate to in a radio transmission. +///This is fucking broken. +GLOBAL_LIST_INIT(allowed_translations, list( + /datum/language/common, + /datum/language/machine, + /datum/language/draconic, +)) + +/datum/n_Interpreter/TCS_Interpreter + var/datum/TCS_Compiler/Compiler + +/datum/n_Interpreter/TCS_Interpreter/HandleError(datum/runtimeError/e) + Compiler.Holder.add_entry(e.ToString(), "Execution Error") + +/datum/n_Interpreter/TCS_Interpreter/garbage_collect() + . = ..() + Compiler = null + +/datum/TCS_Compiler + var/datum/n_Interpreter/TCS_Interpreter/interpreter + ///The telecomms server that is running the code. + var/obj/machinery/telecomms/server/Holder + ///Boolean on whether it's ready to run code. + var/ready = TRUE + +/** + * Set ourselves to Garbage Collect. + */ +/datum/TCS_Compiler/proc/garbage_collect() + Holder = null + if(interpreter) + interpreter.garbage_collect() +/** + * Compile a raw block of text. + */ +/datum/TCS_Compiler/proc/Compile(code as message) + var/datum/n_scriptOptions/options = new() + var/datum/n_Scanner/nS_Scanner/scanner = new(code, options) + var/list/datum/token/tokens = scanner.Scan() + var/datum/n_Parser/nS_Parser/parser = new(tokens, options) + var/datum/node/BlockDefinition/GlobalBlock/program = parser.Parse() + + var/list/returnerrors = list() + + returnerrors += scanner.errors + returnerrors += parser.errors + + if(length(returnerrors)) + return returnerrors + + interpreter = new(program) + interpreter.persist = TRUE + interpreter.Compiler = src + interpreter.container = src + + interpreter.SetVar("PI", 3.141592653) // value of pi + interpreter.SetVar("E" , 2.718281828) // value of e + interpreter.SetVar("SQURT2", 1.414213562) // value of the square root of 2 + interpreter.SetVar("FALSE", 0) // boolean shortcut to 0 + interpreter.SetVar("false", 0) // boolean shortcut to 0 + interpreter.SetVar("TRUE", 1) // boolean shortcut to 1 + interpreter.SetVar("true", 1) // boolean shortcut to 1 + + interpreter.SetVar("NORTH", NORTH) // NORTH (1) + interpreter.SetVar("SOUTH", SOUTH) // SOUTH (2) + interpreter.SetVar("EAST", EAST) // EAST (4) + interpreter.SetVar("WEST", WEST) // WEST (8) + + // Channel macros + // Common server is the one that handles the AI Private Channel, btw. + interpreter.SetVar( + "channels", new /datum/n_enum(list( + "common" = FREQ_COMMON, + "science" = FREQ_SCIENCE, + "command" = FREQ_COMMAND, + "medical" = FREQ_MEDICAL, + "engineering" = FREQ_ENGINEERING, + "security" = FREQ_SECURITY, + "supply" = FREQ_SUPPLY, + "service" = FREQ_SERVICE, + "centcom" = FREQ_CENTCOM, + "aiprivate" = FREQ_AI_PRIVATE, + )) + ) + + interpreter.SetVar( + "filter_types", new /datum/n_enum(list( + "robot" = SPAN_ROBOT, + "loud" = SPAN_YELL, + "emphasis" = SPAN_ITALICS, + "wacky" = SPAN_SANS, + "commanding" = SPAN_COMMAND, + )) + ) + + /** + * Current allowed span classes + * + * Language bitflags + * (Following comment written 26 Jan 2019) + * So, language doesn't work with bitflags anymore + * But having them be bitflags inside of NTSL makes more sense in its context + * So, when we get the signal back from NTSL, if the language has been altered, we'll set it to a new language datum, + * based on the bitflag the guy used. + * + * However, I think the signal can only have one language + * So, the lowest bit set within $language overrides any higher ones that are set. + */ + interpreter.SetVar("languages", new /datum/n_enum(list( + "human" = HUMAN, + "monkey" = MONKEY, + "robot" = ROBOT, + "draconic" = DRACONIC, + "beachtounge" = BEACHTONGUE, + "sylvan" = SYLVAN, + "etherean" = ETHEREAN, + "bonespeak" = BONE, + "mothian" = MOTH, + "cat" = CAT, + "english" = ENGLISH, + ))) + + interpreter.Run() // run the thing + + if(Holder) + Holder.compile_warnings = parser.warnings || list() + if(!interpreter.ProcExists("process_signal")) // yell at the user if they need to update their scripts + Holder.compile_warnings += new /datum/scriptError/OutdatedScript() + + return returnerrors + +/** + * Executes the Compiled code on an incoming signal. + */ +/datum/TCS_Compiler/proc/Run(datum/signal/subspace/vocal/signal) + if(!ready) + return + + if(!interpreter || !interpreter.ProcExists("process_signal")) + return + + var/datum/language/oldlang = signal.language + var/oldlangbits + switch(oldlang) + if(/datum/language/common) + oldlangbits = HUMAN + if(/datum/language/monkey) + oldlangbits = MONKEY + if(/datum/language/machine) + oldlangbits = ROBOT + if(/datum/language/draconic) + oldlangbits = DRACONIC + if(/datum/language/beachbum) + oldlangbits = BEACHTONGUE + if(/datum/language/sylvan) + oldlangbits = SYLVAN + if(/datum/language/voltaic) + oldlangbits = ETHEREAN + if(/datum/language/calcic) + oldlangbits = BONE + if(/datum/language/moffic) + oldlangbits = MOTH + if(/datum/language/nekomimetic) + oldlangbits = CAT + + // Signal data + var/datum/n_struct/signal/script_signal = new(list( + "content" = html_decode(signal.data["message"]), + "freq" = signal.frequency, + "source" = signal.data["name"], + "uuid" = signal.data["name"], + "sector" = signal.levels, + "job" = signal.data["job"], + "pass" = !(signal.data["reject"]), + "filters" = signal.data["spans"], + "language" = oldlangbits, + "say" = signal.virt.verb_say, + "ask" = signal.virt.verb_ask, + "yell" = signal.virt.verb_yell, + "exclaim" = signal.virt.verb_exclaim, + )) + + // Run the compiled code + script_signal = interpreter.CallProc("process_signal", list(script_signal)) + if(!istype(script_signal)) + signal.data["reject"] = TRUE + return + + // Backwards-apply variables onto signal data + /* sanitize EVERYTHING. fucking players can't be trusted with SHIT */ + + var/msg = script_signal.get_clean_property("content", signal.data["message"]) + if(isnum(msg)) + msg = "[msg]" + else if(!msg) + msg = "*beep*" + signal.data["message"] = msg + + + signal.frequency = script_signal.get_clean_property("freq", signal.frequency) + + var/setname = script_signal.get_clean_property("source", signal.data["name"]) + + if(signal.data["name"] != setname) + signal.data["realname"] = signal.data["name"] + signal.virt.name = setname + signal.data["name"] = setname + //signal.data["uuid"] = script_signal.get_clean_property("$uuid", signal.data["uuid"]) + signal.levels = script_signal.get_clean_property("sector", signal.levels) + signal.data["job"] = script_signal.get_clean_property("job", signal.data["job"]) + signal.data["reject"] = !(script_signal.get_clean_property("pass")) // set reject to the opposite of $pass + signal.virt.verb_say = script_signal.get_clean_property("say") + signal.virt.verb_ask = script_signal.get_clean_property("ask") + signal.virt.verb_yell = script_signal.get_clean_property("yell") + signal.virt.verb_exclaim = script_signal.get_clean_property("exclaim") + var/newlang = LangBit2Datum(script_signal.get_clean_property("language")) + if(newlang != oldlang)// makes sure that we only clean out unallowed languages when a translation is taking place otherwise we run an unnecessary proc to filter newlang on foreign untranslated languages. + if(!(LAZYFIND(GLOB.allowed_translations, oldlang))) // cleans out any unallowed translations by making sure the new language is on the allowed translation list. Tcomms powergaming is dead! - Hopek + newlang = oldlang + signal.language = newlang || oldlang + signal.data["language"] = newlang || oldlang + var/list/setspans = script_signal.get_clean_property("filters") //Save the span vector/list to a holder list + if(islist(setspans)) //Players cannot be trusted with ANYTHING. At all. Ever. + setspans &= GLOB.allowed_custom_spans //Prune out any illegal ones. Go ahead, comment this line out. See the horror you can unleash! + signal.data["spans"] = setspans //Apply new span to the signal only if it is a valid list, made using $filters & vector() in the script. + else + signal.data["spans"] = list() + + // If the message is invalid, just don't broadcast it! + if(signal.data["message"] == "" || !signal.data["message"]) + signal.data["reject"] = TRUE + +/datum/n_struct/signal + +/datum/n_struct/signal/New(list/property_list) + properties = property_list | list( + "content" = "", + "freq" = 1459, + "source" = "", + "uuid" = "", + "sector" = list(), + "job" = "", + "pass" = TRUE, + "filters" = list(), + "language" = HUMAN, + "say" = "says", + "ask" = "asks", + "yell" = "yells", + "exclaim" = "exclaims", + ) + +// makes a new signal object + +// arguments: message, freq, source, job +// if you want to change anything else do it yourself +/datum/n_function/default/signal + name = "signal" + interp_type = /datum/n_Interpreter/TCS_Interpreter + +/datum/n_function/default/signal/execute(this_obj, list/params) + var/datum/n_struct/signal/S = new + if(length(params) >= 1) + S.properties["content"] = params[1] + if(length(params) >= 2) + S.properties["freq"] = params[2] + if(length(params) >= 3) + S.properties["source"] = params[3] + if(length(params) >= 4) + S.properties["job"] = params[4] + return S + + +/* -- Actual language proc code -- */ + +#define SIGNAL_COOLDOWN 20 // 2 seconds +#define MAX_MEM_VARS 500 // The maximum number of variables that can be stored by NTSL via mem() + +/proc/LangBit2Datum(langbits) // Takes in the set language bits, returns the datum to use + if(istype(langbits, /datum/language)) + return langbits + + switch(langbits) + if(HUMAN) + return /datum/language/common + if(MONKEY) + return /datum/language/monkey + if(ROBOT) + return /datum/language/machine + if(DRACONIC) + return /datum/language/draconic + if(BEACHTONGUE) + return /datum/language/beachbum + if(SYLVAN) + return /datum/language/sylvan + if(ETHEREAN) + return /datum/language/voltaic + if(BONE) + return /datum/language/calcic + if(MOTH) + return /datum/language/moffic + if(CAT) + return /datum/language/nekomimetic + +/datum/n_function/default/mem + name = "mem" + interp_type = /datum/n_Interpreter/TCS_Interpreter + +/datum/n_function/default/mem/execute(this_obj, list/params, datum/scope/scope, datum/n_Interpreter/TCS_Interpreter/interp) + var/address = length(params) >= 1 ? params[1] : null + var/value = length(params) >= 2 ? params[2] : null + if(istext(address)) + var/obj/machinery/telecomms/server/S = interp.Compiler.Holder + + if(params.len == 1) // Getting the value + return S.memory[address] + else if(value == null) // setting it to null? You must be trying to remove it! Since altoids added this fancy ass memory thing might as well + S.memory -= address + return TRUE + else // Setting the value + if(length(S.memory) >= MAX_MEM_VARS) + if(!(address in S.memory)) + return FALSE + S.memory[address] = value + return TRUE + +/datum/n_function/default/clearmem + name = "clearmem" + interp_type = /datum/n_Interpreter/TCS_Interpreter + +/datum/n_function/default/clearmem/execute(this_obj, list/params, datum/scope/scope, datum/n_Interpreter/TCS_Interpreter/interp) + var/obj/machinery/telecomms/server/S = interp.Compiler.Holder + S.memory = list() + return TRUE + +/datum/n_function/default/remote_signal + name = "remote_signal" + interp_type = /datum/n_Interpreter/TCS_Interpreter + +/datum/n_function/default/remote_signal/execute(this_obj, list/params, datum/scope/scope, datum/n_Interpreter/TCS_Interpreter/interp) + var/freq = length(params) >= 1 ? params[1] : 1459 + var/code = length(params) >= 2 ? params[2] : 30 + + if(isnum(freq) && isnum(code)) + + var/obj/machinery/telecomms/server/S = interp.Compiler.Holder + + if(S.last_signal + SIGNAL_COOLDOWN > world.timeofday && S.last_signal < MIDNIGHT_ROLLOVER) + return + S.last_signal = world.timeofday + + if(findtext(num2text(freq), ".")) // if the frequency has been set as a decimal + freq *= 10 // shift the decimal one place + // "But wait, wouldn't floating point mess this up?" You ask. + // Nah. That actually can't happen when you multiply by a whole number. + // Think about it. + + freq = sanitize_frequency(freq) + + var/datum/radio_frequency/connection = SSradio.return_frequency(freq) + + code = round(code) + code = clamp(code, 0, 100) + + var/datum/signal/signal = new + signal.source = S + signal.data["code"] = code + signal.data["message"] = "ACTIVATE" + + connection.post_signal(S, signal) + + message_admins("Telecomms server \"[S.id]\" sent a signal command, which was triggered by NTSL: [format_frequency(freq)]/[code]") + +/datum/n_function/default/broadcast + name = "broadcast" + interp_type = /datum/n_Interpreter/TCS_Interpreter + +/datum/n_function/default/broadcast/execute(this_obj, list/params, datum/scope/scope, datum/n_Interpreter/TCS_Interpreter/interp) + if(length(params) < 1) + return + var/datum/n_struct/signal/script_signal = params[1] + if(!istype(script_signal)) + return + + var/message = script_signal.get_clean_property("content") + var/freq = script_signal.get_clean_property("freq") + var/source = script_signal.get_clean_property("source") + var/job = script_signal.get_clean_property("job") + var/spans = script_signal.get_clean_property("filters") + var/say = script_signal.get_clean_property("say") + var/ask = script_signal.get_clean_property("ask") + var/yell = script_signal.get_clean_property("yell") + var/exclaim = script_signal.get_clean_property("exclaim") + var/language = script_signal.get_clean_property("language") + + + var/obj/machinery/telecomms/server/S = interp.Compiler.Holder + var/obj/item/radio/server/hradio = S.server_radio + + if(!hradio) + throw EXCEPTION("tcombroadcast(): signal has no radio") + return + //First lets do some checks for bad input + if(isnum(message)) // Allows for setting $content to a number value + message = "[message]" + if((!message) && message != 0) + message = "*beep*" + if(!source) + source = "[html_encode(uppertext(S.id))]" + //hradio = new // sets the hradio as a radio intercom + if(!job) + job = "Unknown" + if(!freq || (!isnum(freq) && text2num(freq) == null)) + freq = 1459 + if(!isnum(freq)) + freq = text2num(freq) + if(findtext(num2text(freq), ".")) // if the frequency has been set as a decimal + freq *= 10 // shift the decimal one place + // "But wait, wouldn't floating point mess this up?" You ask. + // Nah. That actually can't happen when you multiply by a whole number. + // Think about it. + if(isnum(language)) // If the language was a lang bit instead of a datum + language = LangBit2Datum(language) + if(!islist(spans)) + spans = list() + else + spans &= GLOB.allowed_custom_spans //Removes any spans not on the allowed list. Comment this out if want to let players use ANY span in stylesheet.dm! + + //SAY REWRITE RELATED CODE. + //This code is a little hacky, but it *should* work. Even though it'll result in a virtual speaker referencing another virtual speaker. vOv + var/atom/movable/virtualspeaker/virt = new + virt.name = source + virt.job = job + virt.verb_say = say + virt.verb_ask = ask + virt.verb_exclaim = exclaim + virt.verb_yell = yell + + var/datum/signal/subspace/vocal/newsign = new(hradio, freq, virt, language, message, spans, list(), list(S.z)) + /* + virt.languages_spoken = language + virt.languages_understood = virt.languages_spoken //do not remove this or everything turns to jibberish + */ + //END SAY REWRITE RELATED CODE. + + //Now we set up the signal + newsign.data["mob"] = virt + newsign.data["mobtype"] = /mob/living/carbon/human + newsign.data["name"] = source + newsign.data["realname"] = newsign.data["name"] + newsign.data["uuid"] = source + newsign.data["job"] = "[job]" + newsign.data["compression"] = 0 + newsign.data["message"] = message + //newsign.data["type"] = BROADCAST_ARTIFICIAL // artificial broadcast + newsign.data["spans"] = spans + newsign.data["language"] = language + newsign.frequency = freq + newsign.data["radio"] = hradio + newsign.data["vmessage"] = message + newsign.data["vname"] = source + newsign.data["vmask"] = 0 + + + var/pass = S.relay_information(newsign, /obj/machinery/telecomms/hub) + if(!pass) // If we're not sending this to the hub (i.e. we're running a basic tcomms or something) + pass = S.relay_information(newsign, /obj/machinery/telecomms/broadcaster) // send this message to broadcasters directly + return pass // Returns, as of Jan 23 2019, the number of machines that received this broadcast's message. + +#undef SIGNAL_COOLDOWN +#undef MAX_MEM_VARS +#undef HUMAN +#undef MONKEY +#undef ROBOT +#undef DRACONIC +#undef BEACHTONGUE +#undef SYLVAN +#undef ETHEREAN +#undef BONE +#undef MOTH +#undef CAT +#undef ENGLISH diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/interpreter/evaluation.dm b/modular_tannhauser/modules/NTSL/code/coding_language/interpreter/evaluation.dm new file mode 100644 index 00000000000000..beeb0bba7fc120 --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/interpreter/evaluation.dm @@ -0,0 +1,262 @@ +/datum/n_Interpreter/proc/Eval(datum/node/expression/exp, datum/scope/scope) + if(istype(exp, /datum/node/expression/FunctionCall)) + . = RunFunction(exp, scope) + else if(istype(exp, /datum/node/expression/expression_operator)) + . = EvalOperator(exp, scope) + else if(istype(exp, /datum/node/expression/value/literal)) + var/datum/node/expression/value/literal/lit = exp + . = lit.value + else if(istype(exp, /datum/node/expression/value/reference)) + var/datum/node/expression/value/reference/ref = exp + . = ref.value + else if(istype(exp, /datum/node/expression/value/variable)) + var/datum/node/expression/value/variable/v = exp + . = scope.get_var(v.id.id_name, src, v) + else if(istype(exp, /datum/node/expression/value/list_init)) + var/datum/node/expression/value/list_init/list_exp = exp + . = list() + for(var/key in list_exp.init_list) + var/key_eval = Eval(key, scope) + var/val = list_exp.init_list[key] + if(val) + set_index(., key_eval, Eval(val, scope), scope, key) + else + . += list(key_eval) + else if(istype(exp, /datum/node/expression/member/dot)) + var/datum/node/expression/member/dot/D = exp + var/object = D.temp_object || Eval(D.object, scope) + D.temp_object = null + . = get_property(object, D.id.id_name, scope) + else if(istype(exp, /datum/node/expression/member/brackets)) + var/datum/node/expression/member/brackets/B = exp + var/object = B.temp_object || Eval(B.object, scope) + B.temp_object = null + var/index = B.temp_index || Eval(B.index, scope) + . = get_index(object, index, scope) + else if(istype(exp, /datum/node/expression)) + RaiseError(new /datum/runtimeError/UnknownInstruction(exp), scope, exp) + else + . = exp + + return Trim(.) + +/datum/n_Interpreter/proc/EvalOperator(datum/node/expression/expression_operator/exp, datum/scope/scope) + if(istype(exp, /datum/node/expression/expression_operator/binary/Assign)) + var/datum/node/expression/expression_operator/binary/Assign/ass = exp + var/member_obj + var/member_idx + if(istype(ass.exp, /datum/node/expression/value/variable)) + var/datum/node/expression/value/variable/var_exp = ass.exp + if(!scope.get_scope(var_exp.id.id_name)) + scope.init_var(var_exp.id.id_name, null, src, var_exp) + else if(istype(ass.exp, /datum/node/expression/member)) + var/datum/node/expression/member/M = ass.exp + member_obj = Eval(M.object, scope) + if(istype(M, /datum/node/expression/member/brackets)) + var/datum/node/expression/member/brackets/B = M + member_idx = Eval(B.index, scope) + var/out_value + var/in_value + if(ass.type != /datum/node/expression/expression_operator/binary/Assign) + if(istype(ass.exp, /datum/node/expression/member)) + var/datum/node/expression/member/M = ass.exp + M.temp_object = member_obj + if(istype(M, /datum/node/expression/member/brackets)) + var/datum/node/expression/member/brackets/B = M + B.temp_index = member_idx + in_value = Eval(ass.exp, scope) + if(islist(in_value)) + out_value = in_value + switch(ass.type) + if(/datum/node/expression/expression_operator/binary/Assign/BitwiseAnd) + in_value &= Eval(ass.exp2, scope) + if(/datum/node/expression/expression_operator/binary/Assign/BitwiseOr) + in_value |= Eval(ass.exp2, scope) + if(/datum/node/expression/expression_operator/binary/Assign/BitwiseXor) + in_value ^= Eval(ass.exp2, scope) + if(/datum/node/expression/expression_operator/binary/Assign/Add) + in_value += Eval(ass.exp2, scope) + if(/datum/node/expression/expression_operator/binary/Assign/Subtract) + in_value -= Eval(ass.exp2, scope) + else + out_value = null + if(!out_value) + switch(ass.type) + if(/datum/node/expression/expression_operator/binary/Assign) + out_value = Eval(ass.exp2, scope) + if(/datum/node/expression/expression_operator/binary/Assign/BitwiseAnd) + out_value = BitwiseAnd(in_value, Eval(ass.exp2, scope), scope, ass) + if(/datum/node/expression/expression_operator/binary/Assign/BitwiseOr) + out_value = BitwiseOr(in_value, Eval(ass.exp2, scope), scope, ass) + if(/datum/node/expression/expression_operator/binary/Assign/BitwiseXor) + out_value = BitwiseXor(in_value, Eval(ass.exp2, scope), scope, ass) + if(/datum/node/expression/expression_operator/binary/Assign/Add) + out_value = Add(in_value, Eval(ass.exp2, scope), scope, ass) + if(/datum/node/expression/expression_operator/binary/Assign/Subtract) + out_value = Subtract(in_value, Eval(ass.exp2, scope), scope, ass) + if(/datum/node/expression/expression_operator/binary/Assign/Multiply) + out_value = Multiply(in_value, Eval(ass.exp2, scope), scope, ass) + if(/datum/node/expression/expression_operator/binary/Assign/Divide) + out_value = Divide(in_value, Eval(ass.exp2, scope), scope, ass) + if(/datum/node/expression/expression_operator/binary/Assign/Power) + out_value = Power(in_value, Eval(ass.exp2, scope), scope, ass) + if(/datum/node/expression/expression_operator/binary/Assign/Modulo) + out_value = Modulo(in_value, Eval(ass.exp2, scope), scope, ass) + else + RaiseError(new /datum/runtimeError/UnknownInstruction(ass), scope, ass) + // write it to the var + if(istype(ass.exp, /datum/node/expression/value/variable)) + var/datum/node/expression/value/variable/var_exp = ass.exp + scope.set_var(var_exp.id.id_name, out_value, src, var_exp) + else if(istype(ass.exp, /datum/node/expression/member/dot)) + var/datum/node/expression/member/dot/dot_exp = ass.exp + set_property(member_obj, dot_exp.id.id_name, out_value, scope) + else if(istype(ass.exp, /datum/node/expression/member/brackets)) + set_index(member_obj, member_idx, out_value, scope) + else + RaiseError(new /datum/runtimeError/InvalidAssignment(), scope, ass) + return out_value + else if(istype(exp, /datum/node/expression/expression_operator/binary)) + var/datum/node/expression/expression_operator/binary/bin = exp + switch(bin.type) + if(/datum/node/expression/expression_operator/binary/Equal) + return Equal(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + if(/datum/node/expression/expression_operator/binary/NotEqual) + return NotEqual(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + if(/datum/node/expression/expression_operator/binary/Greater) + return Greater(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + if(/datum/node/expression/expression_operator/binary/Less) + return Less(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + if(/datum/node/expression/expression_operator/binary/GreaterOrEqual) + return GreaterOrEqual(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + if(/datum/node/expression/expression_operator/binary/LessOrEqual) + return LessOrEqual(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + if(/datum/node/expression/expression_operator/binary/LogicalAnd) + return LogicalAnd(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + if(/datum/node/expression/expression_operator/binary/LogicalOr) + return LogicalOr(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + if(/datum/node/expression/expression_operator/binary/LogicalXor) + return LogicalXor(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + if(/datum/node/expression/expression_operator/binary/BitwiseAnd) + return BitwiseAnd(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + if(/datum/node/expression/expression_operator/binary/BitwiseOr) + return BitwiseOr(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + if(/datum/node/expression/expression_operator/binary/BitwiseXor) + return BitwiseXor(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + if(/datum/node/expression/expression_operator/binary/Add) + return Add(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + if(/datum/node/expression/expression_operator/binary/Subtract) + return Subtract(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + if(/datum/node/expression/expression_operator/binary/Multiply) + return Multiply(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + if(/datum/node/expression/expression_operator/binary/Divide) + return Divide(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + if(/datum/node/expression/expression_operator/binary/Power) + return Power(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + if(/datum/node/expression/expression_operator/binary/Modulo) + return Modulo(Eval(bin.exp, scope), Eval(bin.exp2, scope), scope, bin) + else + RaiseError(new /datum/runtimeError/UnknownInstruction(bin), scope, bin) + else + switch(exp.type) + if(/datum/node/expression/expression_operator/unary/Minus) + return Minus(Eval(exp.exp, scope), scope, exp) + if(/datum/node/expression/expression_operator/unary/LogicalNot) + return LogicalNot(Eval(exp.exp, scope), scope, exp) + if(/datum/node/expression/expression_operator/unary/BitwiseNot) + return BitwiseNot(Eval(exp.exp, scope), scope, exp) + if(/datum/node/expression/expression_operator/unary/group) + return Eval(exp.exp, scope) + else + RaiseError(new /datum/runtimeError/UnknownInstruction(exp), scope, exp) + +/datum/n_Interpreter/proc/Equal(a, b) + return a == b + +/datum/n_Interpreter/proc/NotEqual(a, b) + return a != b //LogicalNot(Equal(a, b)) + +/datum/n_Interpreter/proc/Greater(a, b) + return a > b + +/datum/n_Interpreter/proc/Less(a, b) + return a < b + +/datum/n_Interpreter/proc/GreaterOrEqual(a, b) + return a >= b + +/datum/n_Interpreter/proc/LessOrEqual(a, b) + return a <= b + +/datum/n_Interpreter/proc/LogicalAnd(a, b) + return a && b + +/datum/n_Interpreter/proc/LogicalOr(a, b) + return a || b + +/datum/n_Interpreter/proc/LogicalXor(a, b) + return (a || b) && !(a && b) + +/datum/n_Interpreter/proc/BitwiseAnd(a, b) + return a & b + +/datum/n_Interpreter/proc/BitwiseOr(a, b) + return a | b + +/datum/n_Interpreter/proc/BitwiseXor(a, b) + return a^b + +/datum/n_Interpreter/proc/Add(a, b, scope, node) + if(istext(a) && !istext(b)) + b = "[b]" + else if(istext(b) && !istext(a) && !islist(a)) + a = "[a]" + if(isnull(a) || isnull(b)) + RaiseError(new /datum/runtimeError/TypeMismatch("+", a, b), scope, node) + return null + return a+b + +/datum/n_Interpreter/proc/Subtract(a, b, scope, node) + if(isnull(a) || isnull(b)) + RaiseError(new /datum/runtimeError/TypeMismatch("-", a, b), scope, node) + return null + return a-b + +/datum/n_Interpreter/proc/Divide(a, b, scope, node) + if(isnull(a) || isnull(b)) + RaiseError(new /datum/runtimeError/TypeMismatch("/", a, b), scope, node) + return null + if(b) + return a/b + // If $b is 0 or Null or whatever, then the above if statement fails, + // and we got a divison by zero. + RaiseError(new /datum/runtimeError/DivisionByZero(), scope, node) + //ReleaseSingularity() + return null + +/datum/n_Interpreter/proc/Multiply(a, b, scope, node) + if(isnull(a) || isnull(b)) + RaiseError(new /datum/runtimeError/TypeMismatch("*", a, b), scope, node) + return null + return a*b + +/datum/n_Interpreter/proc/Modulo(a, b, scope, node) + if(isnull(a) || isnull(b)) + RaiseError(new /datum/runtimeError/TypeMismatch("%", a, b), scope, node) + return null + return a%b + +/datum/n_Interpreter/proc/Power(a, b, scope, node) + if(isnull(a) || isnull(b)) + RaiseError(new /datum/runtimeError/TypeMismatch("**", a, b), scope, node) + return null + return a**b + +/datum/n_Interpreter/proc/Minus(a) + return -a + +/datum/n_Interpreter/proc/LogicalNot(a) + return !a + +/datum/n_Interpreter/proc/BitwiseNot(a) + return ~a diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/interpreter/interaction.dm b/modular_tannhauser/modules/NTSL/code/coding_language/interpreter/interaction.dm new file mode 100644 index 00000000000000..f10be299a38ffd --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/interpreter/interaction.dm @@ -0,0 +1,106 @@ +///Runs the Script. +/datum/n_Interpreter/proc/Run() + cur_recursion = 0 // reset recursion + cur_statements = 0 // reset CPU tracking + + ASSERT(program) + . = RunBlock(program) + +/** + * SetVar + * Defines a global variable for the duration of the next execution of a script. + * Notes: + * This differs from in that variables set using this procedure only last for the session, + * while those defined from the block object persist if it is ran multiple times. + * + * See Also: + * - + */ +/datum/n_Interpreter/proc/SetVar(name, value) + if(!istext(name)) + //CRASH("Invalid variable name") + return + globalScope.variables[name] = value + +/* + * SetProc + * Defines a procedure to be available to the script. + * + * Arguments: + * name - The name of the procedure as exposed to the script. + * path - The typepath of a proc to be called when the function call is read by the interpreter, or, if object is specified, a string representing the procedure's name. + * object - (Optional) An object which will the be target of a function call. + * params - Only required if object is not null, a list of the names of parameters the proc takes. + */ +/datum/n_Interpreter/proc/SetProc(name, path, object = null, list/params = null) + if(!istext(name)) + //CRASH("Invalid function name") + return + +/* + * VarExists + * Checks whether a global variable with the specified name exists. + */ +/datum/n_Interpreter/proc/VarExists(name) + return globalScope.variables.Find(name) //convert to 1/0 first? + +/* + * Proc: ProcExists + * Checks whether a global function with the specified name exists. + */ +/datum/n_Interpreter/proc/ProcExists(name) + return istype(globalScope.get_var(name), /datum/n_function) + +/* + * GetVar + * Returns the value of a global variable in the script. Remember to ensure that the variable exists before calling this procedure. + * + * See Also: + * - + */ +/datum/n_Interpreter/proc/GetVar(name) + if(!VarExists(name)) + //CRASH("No variable named '[name]'.") + return + var/x = globalScope.variables[name] + return x + +/* + * Proc: GetCleanVar + * Returns the value of a global variable in the script and cleans it (sanitizes). + */ +/datum/n_Interpreter/proc/GetCleanVar(name, compare) + var/x = GetVar(name) + if(istext(x) && compare && x != compare) // Was changed + x = sanitize(x) + if(isnotpretty(x)) // Pretty filter stuff + var/log_message = "An NTSL script just tripped the pretty filter, setting variable [name] from [compare] to value [x]!" + message_admins(log_message) + logger.Log(LOG_NTSL, "[key_name(src)] [log_message] [loc_name(src)]") + return FALSE + return x + +/* + * Proc: CallProc + * Calls a global function defined in the script and, amazingly enough, returns its return value. Remember to ensure that the function + * exists before calling this procedure. + * + * See Also: + * - + */ +/datum/n_Interpreter/proc/CallProc(name, list/params) + var/datum/n_function/func = globalScope.get_var(name) + if(istype(func)) + cur_recursion = 0 // reset recursion + cur_statements = 0 // reset CPU tracking + return func.execute(null, params, new /datum/scope(program, null), src) + //CRASH("Unknown function type '[name]'.") + +/* + * Event: HandleError + * Called when the interpreter throws a runtime error. + * + * See Also: + * - + */ +/datum/n_Interpreter/proc/HandleError(datum/runtimeError/e) diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/interpreter/interpreter.dm b/modular_tannhauser/modules/NTSL/code/coding_language/interpreter/interpreter.dm new file mode 100644 index 00000000000000..173eed56602939 --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/interpreter/interpreter.dm @@ -0,0 +1,251 @@ +/* + * Macros: Status Macros + */ +///Indicates that the current function is returning a value. +#define RETURNING (1<<0) +///Indicates that the current loop is being terminated. +#define BREAKING (1<<2) +///Indicates that the rest of the current iteration of a loop is being skipped. +#define CONTINUING (1<<3) +///Indicates that we are entering a new function and the allowed_status var should be cleared +#define RESET_STATUS (1<<4) + +/* + * Macros: Maximums + * MAX_STATEMENTS fuckin'... holds the maximum statements. I'unno, dude, I'm not the guy who made NTSL, + * I don't do fuckin verbose-ass comments line this. + * Figure it out yourself, fuckface. + * + * very helpful comment ^ ty + */ +///Maximum amount of statements that can be called in one execution. this is to prevent massive crashes and exploitation +#define MAX_STATEMENTS 900 +///Max number of uninterrupted loops possible +#define MAX_ITERATIONS 100 +///. + var/status = FALSE + var/returnVal + + /// current amount of statements called + var/cur_statements = FALSE + //Boolean on wheteher admins should be notified of anymore issues. + var/alertadmins = FALSE + ///Current amount of recursion + var/cur_recursion = 0 + ///Boolean that will reset global variables after Run() finishes. + var/persist = TRUE + var/paused = FALSE + +/datum/n_Interpreter/New(datum/node/BlockDefinition/GlobalBlock/program) + . = ..() + if(program) + Load(program) + +/** + * Load + * Loads a 'compiled' script into Memory. + * program - A object which represents the script's global scope. + * + * Parameters: + * program - A object which represents the script's global scope. + */ +/datum/n_Interpreter/proc/Load(datum/node/BlockDefinition/GlobalBlock/program) + ASSERT(program) + src.program = program + CreateGlobalScope() + alertadmins = FALSE + +///Trims strings and vectors down to an acceptable size, to prevent runaway memory usage +/datum/n_Interpreter/proc/Trim(value) + if(istext(value) && (length(value) > MAX_STRINGLEN)) + value = copytext(value, 1, MAX_STRINGLEN + 1) + else if(islist(value) && (length(value) > MAX_LISTLEN)) + var/list/L = value + value = L.Copy(1, MAX_LISTLEN + 1) + return value + +///Sets ourselves to Garbage Collect. +/datum/n_Interpreter/proc/garbage_collect() + container = null + +///Raises a Runtime error. +/datum/n_Interpreter/proc/RaiseError(datum/runtimeError/e, datum/scope/scope, datum/token/token) + e.scope = scope + if(istype(token)) + e.token = token + else if(istype(token, /datum/node)) + var/datum/node/N = token + e.token = N.token + HandleError(e) + +/datum/n_Interpreter/proc/CreateGlobalScope() + var/datum/scope/S = new(program, null) + globalScope = S + for(var/functype in subtypesof(/datum/n_function/default)) + var/datum/n_function/default/god_damn_it_byond = functype + if(!istype(src, initial(god_damn_it_byond.interp_type))) + continue + var/datum/n_function/default/func = new functype() + globalScope.init_var(func.name, func) + for(var/alias in func.aliases) + globalScope.init_var(alias, func) + return S + +///Alerts the admins of a script that is bad. +/datum/n_Interpreter/proc/AlertAdmins() + if(!container || alertadmins) + return + if(!istype(container, /datum/TCS_Compiler)) + return + var/datum/TCS_Compiler/Compiler = container + var/obj/machinery/telecomms/server/Holder = Compiler.Holder + var/message = "Potential crash-inducing NTSL script detected at telecommunications server [Compiler.Holder] ([Holder.x], [Holder.y], [Holder.z])." + alertadmins = TRUE + message_admins(message) + +///Runs each statement in a block of code. +/datum/n_Interpreter/proc/RunBlock(datum/node/BlockDefinition/Block, datum/scope/scope = globalScope) + if(cur_statements >= MAX_STATEMENTS) + return + for(var/datum/node/S in Block.statements) + while(paused) + sleep(1 SECONDS) + + cur_statements++ + if(cur_statements >= MAX_STATEMENTS) + RaiseError(new /datum/runtimeError/MaxCPU(MAX_STATEMENTS), scope, S) + AlertAdmins() + break + + if(istype(S, /datum/node/expression)) + . = Eval(S, scope) + else if(istype(S, /datum/node/statement/VariableDeclaration)) + //VariableDeclaration nodes are used to forcibly declare a local variable so that one in a higher scope isn't used by default. + var/datum/node/statement/VariableDeclaration/dec = S + scope.init_var(dec.var_name.id_name, src, S) + else if(istype(S, /datum/node/statement/FunctionDefinition)) + var/datum/node/statement/FunctionDefinition/dec = S + scope.init_var(dec.func_name, new /datum/n_function/defined(dec, scope, src), src, S) + else if(istype(S, /datum/node/statement/WhileLoop)) + . = RunWhile(S, scope) + else if(istype(S, /datum/node/statement/ForLoop)) + . = RunFor(S, scope) + else if(istype(S, /datum/node/statement/IfStatement)) + . = RunIf(S, scope) + else if(istype(S, /datum/node/statement/ReturnStatement)) + if(!(scope.allowed_status & RETURNING)) + RaiseError(new /datum/runtimeError/UnexpectedReturn(), scope, S) + continue + scope.status |= RETURNING + . = (scope.return_val = Eval(S:value, scope)) + break + else if(istype(S, /datum/node/statement/BreakStatement)) + if(!(scope.allowed_status & BREAKING)) + //RaiseError(new /datum/runtimeError/UnexpectedReturn()) + continue + scope.status |= BREAKING + break + else if(istype(S, /datum/node/statement/ContinueStatement)) + if(!(scope.allowed_status & CONTINUING)) + //RaiseError(new /datum/runtimeError/UnexpectedReturn()) + continue + scope.status |= CONTINUING + break + else + RaiseError(new /datum/runtimeError/UnknownInstruction(S), scope, S) + if(scope.status) + break + +///Runs a function block or a proc with the arguments specified in the script. +/datum/n_Interpreter/proc/RunFunction(datum/node/expression/FunctionCall/stmt, datum/scope/scope) + var/datum/n_function/func + var/this_obj + if(istype(stmt.function, /datum/node/expression/member)) + var/datum/node/expression/member/M = stmt.function + this_obj = M.temp_object = Eval(M.object, scope) + func = Eval(M, scope) + else + func = Eval(stmt.function, scope) + if(!istype(func)) + RaiseError(new /datum/runtimeError/UndefinedFunction("[stmt.function.ToString()]"), scope, stmt) + return + var/list/params = list() + for(var/datum/node/expression/P in stmt.parameters) + params += list(Eval(P, scope)) + + try + return func.execute(this_obj, params, scope, src, stmt) + catch(var/exception/E) + RaiseError(new /datum/runtimeError/Internal(E), scope, stmt) + +///Checks a condition and runs either the if block or else block. +/datum/n_Interpreter/proc/RunIf(datum/node/statement/IfStatement/stmt, datum/scope/scope) + if(!stmt.skip) + scope = scope.push(stmt.block) + if(Eval(stmt.cond, scope)) + . = RunBlock(stmt.block, scope) + // Loop through the if else chain and tell them to be skipped. + var/datum/node/statement/IfStatement/i = stmt.else_if + var/fail_safe = 800 + while(i && fail_safe) + fail_safe -= 1 + i.skip = 1 + i = i.else_if + + else if(stmt.else_block) + . = RunBlock(stmt.else_block, scope) + scope = scope.pop() + // We don't need to skip you anymore. + stmt.skip = FALSE + +///Runs a while loop. +/datum/n_Interpreter/proc/RunWhile(datum/node/statement/WhileLoop/stmt, datum/scope/scope) + var/i = 1 + scope = scope.push(stmt.block, allowed_status = CONTINUING | BREAKING) + while(Eval(stmt.cond, scope) && Iterate(stmt.block, scope, i++)) + continue + scope = scope.pop(RETURNING) + +/datum/n_Interpreter/proc/RunFor(datum/node/statement/ForLoop/stmt, datum/scope/scope) + var/i = 1 + scope = scope.push(stmt.block) + Eval(stmt.init, scope) + while(Eval(stmt.test, scope)) + if(Iterate(stmt.block, scope, i++)) + Eval(stmt.increment, scope) + else + break + scope = scope.pop(RETURNING) + +///Runs a single iteration of a loop. Returns a value indicating whether or not to continue looping. +/datum/n_Interpreter/proc/Iterate(datum/node/BlockDefinition/block, datum/scope/scope, count) + RunBlock(block, scope) + if(MAX_ITERATIONS > 0 && count >= MAX_ITERATIONS) + RaiseError(new /datum/runtimeError/IterationLimitReached(), scope, block) + return FALSE + if(status & (BREAKING | RETURNING)) + return FALSE + status &= ~CONTINUING + return TRUE + +#undef MAX_STATEMENTS +#undef MAX_ITERATIONS +#undef MAX_RECURSION +#undef MAX_STRINGLEN +#undef MAX_LISTLEN diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/interpreter/objects.dm b/modular_tannhauser/modules/NTSL/code/coding_language/interpreter/objects.dm new file mode 100644 index 00000000000000..841b25392c9e40 --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/interpreter/objects.dm @@ -0,0 +1,236 @@ +GLOBAL_LIST_EMPTY(ntsl_methods) + +/datum/n_Interpreter/proc/get_property(object, key, datum/scope/scope, node) //fun fact: node is always null here. + if(islist(object)) + var/list/L = object + switch(key) + if("len") + return length(L) + if("Copy") + return ntsl_method(/list, /datum/n_function/list_copy) + if("Cut") + return ntsl_method(/list, /datum/n_function/list_cut) + if("Find") + return ntsl_method(/list, /datum/n_function/list_find) + if("Insert") + return ntsl_method(/list, /datum/n_function/list_insert) + if("Join") + return ntsl_method(/list, /datum/n_function/list_join) + if("Remove") + return ntsl_method(/list, /datum/n_function/list_remove) + if("Swap") + return ntsl_method(/list, /datum/n_function/list_swap) + else if(istext(object)) + if(key == "len") + return length(object) + else if(istype(object, /datum)) + var/datum/D = object + return D.ntsl_get(key, scope, src, node) + RaiseError(new /datum/runtimeError/UndefinedVariable("[object].[key]"), scope, node) + +/datum/n_Interpreter/proc/set_property(object, key, val, datum/scope/scope, node) + if(istype(object, /datum)) + var/datum/D = object + D.ntsl_set(key, val, scope, src, node) + return + RaiseError(new /datum/runtimeError/UndefinedVariable("[object].[key]"), scope, node) + +/datum/n_Interpreter/proc/get_index(object, index, datum/scope/scope, node) + if(islist(object)) + var/list/L = object + if(!isnum(index) || (index <= length(L) && index >= 1)) + return L[index] + else if(istext(object)) + if(isnum(index) && index >= 1 && index <= length(object)) + return object[index] + RaiseError(new /datum/runtimeError/IndexOutOfRange(object, index), scope, node) + +/datum/n_Interpreter/proc/set_index(object, index, val, datum/scope/scope, node) + if(islist(object)) + var/list/L = object + if(!isnum(index) || (index <= length(L) && index >= 1)) + L[index] = val + return + RaiseError(new /datum/runtimeError/IndexOutOfRange(object, index), scope, node) + +/datum/proc/ntsl_get(key, datum/scope/scope, datum/n_Interpreter/interp, node) //fun fact: node is always null here. + interp.RaiseError(new /datum/runtimeError/UndefinedVariable("[src].[key]"), scope, node) + return + +/datum/proc/ntsl_set(key, val, datum/scope/scope, datum/n_Interpreter/interp, node) + interp.RaiseError(new /datum/runtimeError/UndefinedVariable("[src].[key]"), scope, node) + return + +/datum/n_enum + var/list/entries + +/datum/n_enum/New(list/entry_list) + src.entries = entry_list + +/datum/n_enum/ntsl_get(key, datum/scope/scope, datum/n_Interpreter/interp, node) + if(entries.Find(key)) + return entries[key] + return ..() + +/datum/n_struct + var/list/properties + +/datum/n_struct/New(list/property_list) + src.properties = property_list + +/datum/n_struct/proc/get_clean_property(name, compare) + var/x = properties[name] + if(istext(x) && compare && x != compare) // Was changed + x = sanitize(x) + if(isnotpretty(x)) // Pretty filter stuff + var/log_message = "An NTSL script just tripped the pretty filter, setting variable [name] from [compare] to value [x]!" + message_admins(log_message) + logger.Log(LOG_NTSL, "[key_name(src)] [log_message] [loc_name(src)]") + return FALSE + return x + +/datum/n_struct/ntsl_get(key, datum/scope/scope, datum/n_Interpreter/interp, node) + if(properties.Find(key)) + return properties[key] + return ..() + +/datum/n_struct/ntsl_set(key, val) + if(properties.Find(key)) + properties[key] = val + return + return ..() + +/datum/n_function + var/name = "" + +/datum/n_function/proc/execute(this_obj, list/params, datum/scope/scope, datum/n_Interpreter/interp) + return + +/datum/n_function/defined + var/datum/n_Interpreter/context + var/datum/scope/closure + var/datum/node/statement/FunctionDefinition/function_def + +/datum/n_function/defined/New(datum/node/statement/FunctionDefinition/function_def, datum/scope/closure, datum/n_Interpreter/context) + src.function_def = function_def + src.closure = closure + src.context = context + +/datum/n_function/defined/Destroy() + function_def = null + closure = null + context = null + return ..() + +/datum/n_function/defined/execute(this_obj, list/params, datum/scope/scope, datum/n_Interpreter/interp, datum/node/node) + if(scope.recursion >= 10) + interp.AlertAdmins() + interp.RaiseError(new /datum/runtimeError/RecursionLimitReached(), scope, node) + return FALSE + scope = scope.push(function_def.block, closure, RESET_STATUS | RETURNING) + scope.recursion++ + scope.function = function_def + if(node) + scope.call_node = node + for(var/i = 1 to length(function_def.parameters)) + var/val + if(length(params) >= i) + val = params[i] + //else + // unspecified param + scope.init_var(function_def.parameters[i], val, interp, node) + scope.init_var("src", this_obj, interp, node); + interp.RunBlock(function_def.block, scope) + //Handle return value + . = scope.return_val + scope = scope.pop(0) // keep nothing + +/datum/n_function/default + // functions included on compilation + var/interp_type = /datum/n_Interpreter // include this function in this kind of interpreter. + var/list/aliases // in case you want to give it multiple "names" + +/proc/ntsl_method(path, proc_ref, N) + var/list/path_methods = GLOB.ntsl_methods[path] + if(!path_methods) + path_methods = list() + GLOB.ntsl_methods[path] = path_methods + var/datum/n_function/func = path_methods[proc_ref] + if(func) + return func + if(ispath(proc_ref, /datum/n_function) && !ispath(path, /datum/n_function)) + func = new proc_ref() + else + func = new /datum/n_function/default_method(path, proc_ref, N) + path_methods[proc_ref] = func + return func + +/datum/n_function/default_method + var/obj_type + var/proc_ref + +/datum/n_function/default_method/New(path, ref, name) + src.obj_type = path + src.proc_ref = ref + src.name = name + +/datum/n_function/default_method/execute(this_obj, list/params, datum/scope/scope, datum/n_Interpreter/interp) + if(!istype(this_obj, obj_type)) + return + return call(this_obj, proc_ref)(params, scope, interp) + +/datum/n_function/list_add + name = "Add" + +/datum/n_function/list_add/execute(list/this_obj, list/params) + for(var/param in params) + this_obj.Add(param) + +/datum/n_function/list_copy + name = "Copy" + +/datum/n_function/list_copy/execute(list/this_obj, list/params) + return this_obj.Copy(length(params) >= 1 ? params[1] : 1, length(params) >= 2 ? params[2] : 0) + +/datum/n_function/list_cut + name = "Cut" + +/datum/n_function/list_cut/execute(list/this_obj, list/params) + this_obj.Cut(length(params) >= 1 ? params[1] : 1, length(params) >= 2 ? params[2] : 0) + +/datum/n_function/list_find + name = "Find" + +/datum/n_function/list_find/execute(list/this_obj, list/params) + return this_obj.Find(params[1], length(params) >= 2 ? params[2] : 1, length(params) >= 3 ? params[3] : 0) + +/datum/n_function/list_insert + name = "Insert" + +/datum/n_function/list_insert/execute(list/this_obj, list/params) + if(length(params) >= 2) + if(params[1] == 0) + for(var/I in 2 to length(params)) + this_obj.Add(params[I]) + else + for(var/I = length(params); I >= 2; I--) + this_obj.Insert(params[1], params[I]) + +/datum/n_function/list_join + name = "Join" + +/datum/n_function/list_join/execute(list/this_obj, list/params) + return this_obj.Join(params[1], length(params) >= 2 ? params[2] : 1, length(params) >= 3 ? params[3] : 0) + +/datum/n_function/list_remove + name = "Remove" + +/datum/n_function/list_remove/execute(list/this_obj, list/params) + for(var/param in params) + this_obj.Remove(param) + +/datum/n_function/list_swap + name = "Swap" + +/datum/n_function/list_swap/execute(list/this_obj, list/params) + this_obj.Swap(params[1], params[2]) diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/interpreter/scope.dm b/modular_tannhauser/modules/NTSL/code/coding_language/interpreter/scope.dm new file mode 100644 index 00000000000000..36b1a64b337a38 --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/interpreter/scope.dm @@ -0,0 +1,90 @@ +/* + * Scope + * A runtime instance of a block. Used internally by the interpreter. + */ +/datum/scope + var/datum/scope/parent + var/datum/scope/variables_parent + var/datum/node/BlockDefinition/block + var/list/functions + var/list/variables + var/status = 0 + var/allowed_status = 0 + var/recursion = 0 + var/datum/node/statement/FunctionDefinition/function + var/datum/node/expression/FunctionCall/call_node + var/return_val + +/datum/scope/New(datum/node/BlockDefinition/B, datum/scope/parent, datum/scope/variables_parent, allowed_status = 0) + src.block = B + src.parent = parent + src.variables_parent = variables_parent || parent + if(B) + src.variables = B.initial_variables.Copy() + src.functions = B.functions.Copy() + else + src.variables = list() + src.functions = list() + if(parent) + src.status = parent.status + src.recursion = parent.recursion + + if(allowed_status & RESET_STATUS || !parent) + src.allowed_status = allowed_status & ~RESET_STATUS + else + src.allowed_status = allowed_status | parent.allowed_status + return ..() + +/datum/scope/Destroy() + parent = null + variables_parent = null + block = null + functions = null + variables = null + function = null + call_node = null + return ..() + +/datum/scope/proc/get_scope(name) + var/datum/scope/S = src + while(S) + if(S.variables.Find(name)) + return S + S = S.variables_parent + +/datum/scope/proc/push(datum/node/BlockDefinition/B, datum/scope/variables_parent = src, allowed_status = 0) as /datum/scope + return new /datum/scope(B, src, variables_parent, allowed_status) + +/datum/scope/proc/pop(keep_status = (BREAKING | CONTINUING | RETURNING)) // keep_status is which flags you want to copy to the parent. + parent.status = (parent.status & ~keep_status) | (status & keep_status) + if(parent.status & RETURNING) + parent.return_val = return_val + return parent + +/datum/scope/proc/get_var(name, datum/n_Interpreter/interp, datum/node/node) + var/datum/scope/S = get_scope(name) + if(S) + return S.variables[name] + else if(interp) + interp.RaiseError(new /datum/runtimeError/UndefinedVariable(name), src, node) + +/datum/scope/proc/get_function(name) + var/datum/scope/S = src + while(S) + . = S.functions[name] + if(.) + return + S = S.variables_parent + +/datum/scope/proc/set_var(name, val, datum/n_Interpreter/interp, datum/node/node) + var/datum/scope/S = get_scope(name) + if(S) + S.variables[name] = val + else + init_var(name, val, interp, node) + return val + +/datum/scope/proc/init_var(name, val, datum/n_Interpreter/interp, datum/node/node) + if(variables.Find(name) && interp) + interp.RaiseError(new /datum/runtimeError/DuplicateVariableDeclaration(name), src, node) + variables[name] = val diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/options.dm b/modular_tannhauser/modules/NTSL/code/coding_language/options.dm new file mode 100644 index 00000000000000..b61b26f4f4f12c --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/options.dm @@ -0,0 +1,119 @@ +#define ascii_A 65 +#define ascii_Z 90 +#define ascii_a 97 +#define ascii_z 122 +#define ascii_DOLLAR 36 // $ +#define ascii_ZERO 48 +#define ascii_NINE 57 +#define ascii_UNDERSCORE 95 // _ + +/* + * n_scriptOptions + */ +/datum/n_scriptOptions + ///scanner - Characters that can be in symbols + var/list/symbols = list( + "(", + ")", + "\[", + "]", + ";", + ",", + "{", + "}", + ".", + ) + /* + * Var: keywords + * An associative list used by the parser to parse keywords. Indices are strings which will trigger the keyword when parsed and the + * associated values are types of which the proc will be called. + */ + var/list/keywords = list( + "if" = /datum/n_Keyword/nS_Keyword/kwIf, + "else" = /datum/n_Keyword/nS_Keyword/kwElse, + "elseif" = /datum/n_Keyword/nS_Keyword/kwElseIf, + "while" = /datum/n_Keyword/nS_Keyword/kwWhile, + "break" = /datum/n_Keyword/nS_Keyword/kwBreak, + "continue" = /datum/n_Keyword/nS_Keyword/kwContinue, + "for" = /datum/n_Keyword/nS_Keyword/kwFor, + "return" = /datum/n_Keyword/nS_Keyword/kwReturn, + "def"= /datum/n_Keyword/nS_Keyword/kwDef, + ) + var/list/unary_operators = list( + "!" = /datum/node/expression/expression_operator/unary/LogicalNot, + "~" = /datum/node/expression/expression_operator/unary/BitwiseNot, + "-" = /datum/node/expression/expression_operator/unary/Minus, + ) + + var/list/binary_operators = list( + "==" = /datum/node/expression/expression_operator/binary/Equal, + "!=" = /datum/node/expression/expression_operator/binary/NotEqual, + ">" = /datum/node/expression/expression_operator/binary/Greater, + "<"= /datum/node/expression/expression_operator/binary/Less, + ">=" = /datum/node/expression/expression_operator/binary/GreaterOrEqual, + "<=" = /datum/node/expression/expression_operator/binary/LessOrEqual, + "&&" = /datum/node/expression/expression_operator/binary/LogicalAnd, + "||" = /datum/node/expression/expression_operator/binary/LogicalOr, + "&" = /datum/node/expression/expression_operator/binary/BitwiseAnd, + "|"= /datum/node/expression/expression_operator/binary/BitwiseOr, + "`" = /datum/node/expression/expression_operator/binary/BitwiseXor, + "+"= /datum/node/expression/expression_operator/binary/Add, + "-" = /datum/node/expression/expression_operator/binary/Subtract, + "*"= /datum/node/expression/expression_operator/binary/Multiply, + "/" = /datum/node/expression/expression_operator/binary/Divide, + "^"= /datum/node/expression/expression_operator/binary/Power, + "%" = /datum/node/expression/expression_operator/binary/Modulo, + "=" = /datum/node/expression/expression_operator/binary/Assign, + "&=" = /datum/node/expression/expression_operator/binary/Assign/BitwiseAnd, + "|=" = /datum/node/expression/expression_operator/binary/Assign/BitwiseOr, + "`=" = /datum/node/expression/expression_operator/binary/Assign/BitwiseXor, + "+=" = /datum/node/expression/expression_operator/binary/Assign/Add, + "-=" = /datum/node/expression/expression_operator/binary/Assign/Subtract, + "*=" = /datum/node/expression/expression_operator/binary/Assign/Multiply, + "/=" = /datum/node/expression/expression_operator/binary/Assign/Divide, + "^=" = /datum/node/expression/expression_operator/binary/Assign/Power, + "%=" = /datum/node/expression/expression_operator/binary/Assign/Modulo, + ) + +/datum/n_scriptOptions/New() + . = ..() + for(var/operators in binary_operators + unary_operators) + if(!symbols.Find(operators)) + symbols += operators + +///Returns TRUE if the character can start a variable, function, or keyword name (by default letters or an underscore) +/datum/n_scriptOptions/proc/CanStartID(char) + if(!isnum(char)) + char = text2ascii(char) + return (char in ascii_A to ascii_Z) || (char in ascii_a to ascii_z) || char == ascii_UNDERSCORE || char == ascii_DOLLAR + +///Returns TRUE if the character can be in the body of a variable, function, or keyword name (by default letters, numbers, and underscore) +/datum/n_scriptOptions/proc/IsValidIDChar(char) + if(!isnum(char)) + char = text2ascii(char) + return CanStartID(char) || IsDigit(char) + +/datum/n_scriptOptions/proc/IsDigit(char) + if(!isnum(char)) + char = text2ascii(char) + return char in ascii_ZERO to ascii_NINE + +///Returns TRUE if all the characters in the string are okay to be in an identifier name. +/datum/n_scriptOptions/proc/IsValidID(id) + if(!CanStartID(id)) //don't need to grab first char in id, since text2ascii does it automatically + return FALSE + if(length(id) == 1) + return TRUE + for(var/i = 2 to length(id)) + if(!IsValidIDChar(copytext(id, i, i+1))) + return FALSE + return TRUE + +#undef ascii_A +#undef ascii_Z +#undef ascii_a +#undef ascii_z +#undef ascii_DOLLAR +#undef ascii_ZERO +#undef ascii_NINE +#undef ascii_UNDERSCORE diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/parser/expressions.dm b/modular_tannhauser/modules/NTSL/code/coding_language/parser/expressions.dm new file mode 100644 index 00000000000000..33f24ba7c9f275 --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/parser/expressions.dm @@ -0,0 +1,341 @@ +/* + * Macros: Expression Macros + */ +///A value indicating the parser currently expects a binary operator. +#define OPERATOR 1 +///A value indicating the parser currently expects a value. +#define VALUE 2 +///Tells the parser to push the current operator onto the stack. +#define SHIFT 0 +///Tells the parser to reduce the stack. +#define REDUCE 1 + +/datum/n_Parser/nS_Parser + ///A variable which keeps track of whether an operator or value is expected. + ///It should be either or . See for more information. + var/expecting = VALUE + +///Compares two operators, decides which is higher in the order of operations, and returns or . +/datum/n_Parser/nS_Parser/proc/Precedence(datum/node/expression/expression_operator/top, datum/node/expression/expression_operator/input) + if(istype(top)) + top = top.precedence + if(istype(input)) + input = input.precedence + if(top >= input) + return REDUCE + return SHIFT + +///Takes a token expected to represent a value and returns an node. +/datum/n_Parser/nS_Parser/proc/GetExpression(datum/token/T) as /datum/node/expression + if(!T) + return + if(istype(T, /datum/node/expression)) + return T + switch(T.type) + if(/datum/token/word) + return new /datum/node/expression/value/variable(T.value, T) + if(/datum/token/number, /datum/token/string) + return new /datum/node/expression/value/literal(T.value, T) + +/* + * GetOperator + * Gets a path related to a token or string and returns an instance of the given type. + * This is used to get an instance of either a binary or unary operator from a token. + * + * Arguments: + * O - The input value. If this is a token, O is reset to the token's value. + * When O is a string and is in L, its associated value is used as the path to instantiate. + * type - The desired type of the returned object. + * L - The list in which to search for O. + * + * See Also: + * - + * - + */ +/datum/n_Parser/nS_Parser/proc/GetOperator(O, type = /datum/node/expression/expression_operator, L[]) + var/datum/token/input_token + if(istype(O, type)) + return O + if(istype(O, /datum/token)) + input_token = O + O = input_token.value + if(istext(O)) + if(L.Find(O)) + O = L[O] + else + return null + if(input_token) + O = new O(input_token) + else + return null + return O + +/* + * GetBinaryOperator + * Uses to search for an instance of a binary operator type with which the given string is associated. For example, if + * O is set to "+", an node is returned. + * + * See Also: + * - + * - + */ +/datum/n_Parser/nS_Parser/proc/GetBinaryOperator(O) + return GetOperator(O, /datum/node/expression/expression_operator/binary, options.binary_operators) + +/* + * Proc: GetUnaryOperator + * Uses to search for an instance of a unary operator type with which the given string is associated. For example, if + * O is set to "!", a node is returned. + * + * See Also: + * - + * - + */ +/datum/n_Parser/nS_Parser/proc/GetUnaryOperator(O) + return GetOperator(O, /datum/node/expression/expression_operator/unary, options.unary_operators) + +/* + * Reduce + * Takes the operator on top of the opr stack and assigns its operand(s). Then this proc pushes the value of that operation to the top + * of the val stack. + */ +/datum/n_Parser/nS_Parser/proc/Reduce(datum/stack/opr, datum/stack/val, check_assignments = 1) + var/datum/node/expression/expression_operator/O = opr.Pop() + if(!O) return + if(!istype(O)) + errors += new /datum/scriptError("Error reducing expression - invalid operator.") + return + //Take O and assign its operands, popping one or two values from the val stack + //depending on whether O is a binary or unary operator. + if(istype(O, /datum/node/expression/expression_operator/binary)) + var/datum/node/expression/expression_operator/binary/B = O + B.exp2 = val.Pop() + B.exp = val.Pop() + val.Push(B) + if(check_assignments && istype(B, /datum/node/expression/expression_operator/binary/Assign) && !istype(B.exp, /datum/node/expression/value/variable) && !istype(B.exp, /datum/node/expression/member)) + errors += new /datum/scriptError/InvalidAssignment() + else + O.exp = val.Pop() + val.Push(O) + +/* + * EndOfExpression + * Returns true if the current token represents the end of an expression. + * + * Arguments: + * end - A list of values to compare the current token to. + */ +/datum/n_Parser/nS_Parser/proc/EndOfExpression(end[]) + if(!curToken) + return TRUE + if(istype(curToken, /datum/token/symbol) && end.Find(curToken.value)) + return TRUE + if(istype(curToken, /datum/token/end) && end.Find(/datum/token/end)) + return TRUE + return FALSE + +/* + * ParseExpression + * Uses the Shunting-yard algorithm to parse expressions. + * + * Notes: + * - When an opening parenthesis is found, then is called to handle it. + * - The variable helps distinguish unary operators from binary operators (for cases like the - operator, which can be either). + * + * Articles: + * - + * - + * + * See Also: + * - + * - + * - + */ +/datum/n_Parser/nS_Parser/proc/ParseExpression(list/end = list(/datum/token/end), list/ErrChars = list("{", "}"), check_functions = 0, check_assignments = 1) + var/datum/stack/opr = new + var/datum/stack/val = new + + expecting = VALUE + var/loop = 0 + while(TRUE) + loop++ + if(loop > 800) + errors += new /datum/scriptError("Too many nested tokens.") + return + + if(EndOfExpression(end)) + break + if(istype(curToken, /datum/token/symbol) && ErrChars.Find(curToken.value)) + errors += new /datum/scriptError/BadToken(curToken) + break + + + if(index > length(tokens)) //End of File + errors += new /datum/scriptError/EndOfFile() + break + var/datum/token/ntok + if(index + 1 <= length(tokens)) + ntok = tokens[index + 1] + + if(istype(curToken, /datum/token/symbol) && curToken.value == "(") //Parse parentheses expression + if(expecting == VALUE) + val.Push(ParseParenExpression()) + else + // you can call *anything*! You can even call "2()". It'll runtime though so just don't please. + val.Push(ParseFunctionExpression(val.Pop())) + expecting = OPERATOR + else if(istype(curToken, /datum/token/symbol) && curToken.value == "." && ntok && istype(ntok, /datum/token/word)) + if(expecting == VALUE) + errors += new /datum/scriptError/ExpectedToken("expression", curToken) + NextToken() + continue + var/datum/node/expression/member/dot/E = new(curToken) + E.object = val.Pop() + NextToken() + E.id = new(curToken.value, curToken) + val.Push(E) + else if(istype(curToken, /datum/token/symbol) && curToken.value == "\[") + if(expecting == VALUE) + errors += new /datum/scriptError/ExpectedToken("expression", curToken) + NextToken() + continue + var/datum/node/expression/member/brackets/B = new(curToken) + B.object = val.Pop() + NextToken() + B.index = ParseExpression(list("]")) + val.Push(B) + else if(istype(curToken, /datum/token/symbol)) //Operator found. + var/datum/node/expression/expression_operator/curOperator //Figure out whether it is unary or binary and get a new instance. + if(expecting == OPERATOR) + curOperator = GetBinaryOperator(curToken) + if(!curOperator) + errors += new /datum/scriptError/ExpectedToken("operator", curToken) + NextToken() + continue + else + curOperator = GetUnaryOperator(curToken) + if(!curOperator) //given symbol isn't a unary operator + errors += new /datum/scriptError/ExpectedToken("expression", curToken) + NextToken() + continue + + if(opr.Top() && Precedence(opr.Top(), curOperator)== REDUCE) //Check order of operations and reduce if necessary + Reduce(opr, val, check_assignments) + continue + opr.Push(curOperator) + expecting = VALUE + else if(istype(curToken, /datum/token/word) && curToken.value == "list" && ntok && ntok.value == "(" && expecting == VALUE) + val.Push(ParseListExpression()) + else if(istype(curToken, /datum/token/keyword)) //inline keywords + var/datum/n_Keyword/kw = options.keywords[curToken.value] + kw = new kw(inline = 1) + if(kw) + if(!kw.Parse(src)) + return + else + errors += new /datum/scriptError/BadToken(curToken) + + else if(istype(curToken, /datum/token/end)) //semicolon found where it wasn't expected + errors += new /datum/scriptError/BadToken(curToken) + NextToken() + continue + else + if(expecting != VALUE) + errors += new /datum/scriptError/ExpectedToken("operator", curToken) + NextToken() + continue + val.Push(GetExpression(curToken)) + expecting = OPERATOR + + NextToken() + + while(opr.Top()) + Reduce(opr, val, check_assignments) //Reduce the value stack completely + . = val.Pop() //Return what should be the last value on the stack + if(val.Top()) + var/datum/node/N = val.Pop() + errors += new /datum/scriptError("Error parsing expression. Unexpected value left on stack: [N.ToString()].") + return null + +///Parses a function call inside of an expression. (See also ) +/datum/n_Parser/nS_Parser/proc/ParseFunctionExpression(func_exp) as /datum/node/expression/FunctionCall + var/datum/node/expression/FunctionCall/exp = new(curToken) + exp.function = func_exp + NextToken() //skip open parenthesis, already found + var/loops = 0 + + while(TRUE) + loops++ + if(loops >= 800) + errors += new /datum/scriptError("Too many nested expressions.") + break + //CRASH("Something TERRIBLE has gone wrong in ParseFunctionExpression ;__;") + + if(istype(curToken, /datum/token/symbol) && curToken.value == ")") + return exp + exp.parameters += ParseParamExpression() + if(length(errors)) + return exp + if(curToken.value == "," && istype(curToken, /datum/token/symbol)) + NextToken() //skip comma + if(istype(curToken, /datum/token/end)) //Prevents infinite loop... + errors += new /datum/scriptError/ExpectedToken(")") + return exp + +/datum/n_Parser/nS_Parser/proc/ParseListExpression() as /datum/node/expression/value/list_init + var/datum/node/expression/value/list_init/exp = new(curToken) + exp.init_list = list() + NextToken() // skip the "list" word + NextToken() // skip the open parenthesis + var/loops = 0 + while(TRUE) + loops++ + if(loops >= 800) + errors += new /datum/scriptError("Too many nested expressions.") + break + + if(istype(curToken, /datum/token/symbol) && curToken.value == ")") + return exp + var/datum/node/expression/E = ParseParamExpression(check_assignments = FALSE) + if(E.type == /datum/node/expression/expression_operator/binary/Assign) + var/datum/node/expression/expression_operator/binary/Assign/A = E + exp.init_list[A.exp] = A.exp2 + else + exp.init_list += E + if(length(errors)) + return exp + if(curToken.value == "," && istype(curToken, /datum/token/symbol)) + NextToken() //skip comma + if(istype(curToken, /datum/token/end)) //Prevents infinite loop... + errors += new /datum/scriptError/ExpectedToken(")") + return exp + +/* + * ParseParenExpression + * Parses an expression that ends with a close parenthesis. This is used for parsing expressions inside of parentheses. + * + * See Also: + * - + */ +/datum/n_Parser/nS_Parser/proc/ParseParenExpression() as /datum/node/expression/expression_operator/unary/group + var/group_token = curToken + if(!CheckToken("(", /datum/token/symbol)) + return + return new /datum/node/expression/expression_operator/unary/group(group_token, ParseExpression(list(")"))) + +/* + * Proc: ParseParamExpression + * Parses an expression that ends with either a comma or close parenthesis. This is used for parsing the parameters passed to a function call. + * + * See Also: + * - + */ +/datum/n_Parser/nS_Parser/proc/ParseParamExpression(check_functions = 0, check_assignments = 1) + var/cf = check_functions + var/ca = check_assignments + return ParseExpression(list(",", ")"), check_functions = cf, check_assignments = ca) + +#undef OPERATOR +#undef VALUE +#undef SHIFT +#undef REDUCE diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/parser/keywords.dm b/modular_tannhauser/modules/NTSL/code/coding_language/parser/keywords.dm new file mode 100644 index 00000000000000..90262a4709ad2a --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/parser/keywords.dm @@ -0,0 +1,222 @@ +/* + * File: Keywords + */ +#define KW_FAIL 0 //Fatal error; stop parsing entire script. +#define KW_PASS 1 //OK +#define KW_ERR 2 //Non-fatal error, keyword couldn't be handled properly. Ignore keyword but continue on. +#define KW_WARN 3 //Warning + +/* + * n_Keyword + * Represents a special statement in the code triggered by a keyword. + */ +/datum/n_Keyword + ///Boolean if the keyword is in an expression (e.g. the new keyword in many languages). + var/inline + +/datum/n_Keyword/New(inline = 0) + src.inline = inline + return ..() + +/* + * Parse + * Called when the parser finds a keyword in the code. + * + * Arguments: + * parser - The parser that created this object. + * You can use the parameter to manipulate the parser in order to add statements and blocks to its AST. + */ +/datum/n_Keyword/proc/Parse(datum/n_Parser/parser) + +/* + * nS_Keyword + * A keyword in n_Script. By default these include return, if, else, while, and def. To enable or disable a keyword, change the + * list. + * + * Behavior: + * When a parser is expecting a new statement, and a keyword listed in is found, it will call the keyword's + * proc. + */ +/datum/n_Keyword/nS_Keyword + +/datum/n_Keyword/nS_Keyword/New(inline = 0) + if(inline) + qdel(src) + +/datum/n_Keyword/nS_Keyword/kwReturn + +/datum/n_Keyword/nS_Keyword/kwReturn/Parse(datum/n_Parser/nS_Parser/parser) + . = KW_PASS + if(istype(parser.curBlock, /datum/node/BlockDefinition/GlobalBlock)) // Exit out of the program by setting the tokens list size to the same as index. + parser.tokens.len = parser.index + return + var/datum/node/statement/ReturnStatement/stmt = new(parser.curToken) + parser.NextToken() //skip 'return' token + stmt.value = parser.ParseExpression() + parser.curBlock.statements += stmt + +/datum/n_Keyword/nS_Keyword/kwIf + +/datum/n_Keyword/nS_Keyword/kwIf/Parse(datum/n_Parser/nS_Parser/parser) + . = KW_PASS + var/datum/node/statement/IfStatement/stmt = new(parser.curToken) + parser.NextToken() //skip 'if' token + stmt.cond = parser.ParseParenExpression() + if(!parser.CheckToken(")", /datum/token/symbol)) + return KW_FAIL + if(!parser.CheckToken("{", /datum/token/symbol, skip = 0)) //datum/token needs to be preserved for parse loop, so skip = 0 + return KW_ERR + parser.curBlock.statements += stmt + stmt.block = new + parser.AddBlock(stmt.block) + +/datum/n_Keyword/nS_Keyword/kwElseIf + +/datum/n_Keyword/nS_Keyword/kwElseIf/Parse(datum/n_Parser/nS_Parser/parser) + . = KW_PASS + var/list/L = parser.curBlock.statements + var/datum/node/statement/IfStatement/ifstmt + + if(L && length(L)) + ifstmt = L[length(L)] //Get the last statement in the current block + if(!ifstmt || !istype(ifstmt) || ifstmt.else_if) + parser.errors += new /datum/scriptError/ExpectedToken("if statement", parser.curToken) + return KW_FAIL + + var/datum/node/statement/IfStatement/ElseIf/stmt = new(parser.curToken) + parser.NextToken() //skip 'if' token + stmt.cond = parser.ParseParenExpression() + if(!parser.CheckToken(")", /datum/token/symbol)) + return KW_FAIL + if(!parser.CheckToken("{", /datum/token/symbol, skip = FALSE)) //datum/token needs to be preserved for parse loop, so skip = 0 + return KW_ERR + parser.curBlock.statements += stmt + stmt.block = new + ifstmt.else_if = stmt + parser.AddBlock(stmt.block) + + +/datum/n_Keyword/nS_Keyword/kwElse + +/datum/n_Keyword/nS_Keyword/kwElse/Parse(datum/n_Parser/nS_Parser/parser) + . = KW_PASS + var/list/L = parser.curBlock.statements + var/datum/node/statement/IfStatement/stmt + if(L && length(L)) stmt = L[length(L)] //Get the last statement in the current block + if(!stmt || !istype(stmt) || stmt.else_block) //Ensure that it is an if statement + parser.errors += new /datum/scriptError/ExpectedToken("if statement",parser.curToken) + return KW_FAIL + parser.NextToken() //skip 'else' token + if(!parser.CheckToken("{", /datum/token/symbol, skip = 0)) + return KW_ERR + stmt.else_block = new() + parser.AddBlock(stmt.else_block) + +/datum/n_Keyword/nS_Keyword/kwWhile + +/datum/n_Keyword/nS_Keyword/kwWhile/Parse(datum/n_Parser/nS_Parser/parser) + . = KW_PASS + var/datum/node/statement/WhileLoop/stmt = new(parser.curToken) + parser.NextToken() //skip 'while' token + stmt.cond = parser.ParseParenExpression() + if(!parser.CheckToken(")", /datum/token/symbol)) + return KW_FAIL + if(!parser.CheckToken("{", /datum/token/symbol, skip = 0)) + return KW_ERR + parser.curBlock.statements += stmt + stmt.block = new + parser.AddBlock(stmt.block) + +/datum/n_Keyword/nS_Keyword/kwFor + +/datum/n_Keyword/nS_Keyword/kwFor/Parse(datum/n_Parser/nS_Parser/parser) + . = KW_PASS + var/datum/node/statement/ForLoop/stmt = new(parser.curToken) + parser.NextToken() + if(!parser.CheckToken("(", /datum/token/symbol)) + return KW_FAIL + stmt.init = parser.ParseExpression() + if(!parser.CheckToken(";", /datum/token/end)) + return KW_FAIL + stmt.test = parser.ParseExpression() + if(!parser.CheckToken(";", /datum/token/end)) + return KW_FAIL + stmt.increment = parser.ParseExpression(list(")")) + if(!parser.CheckToken(")", /datum/token/symbol)) + return KW_FAIL + if(!parser.CheckToken("{", /datum/token/symbol, skip = 0)) + return KW_ERR + parser.curBlock.statements += stmt + stmt.block = new + parser.AddBlock(stmt.block) + +/datum/n_Keyword/nS_Keyword/kwBreak + +/datum/n_Keyword/nS_Keyword/kwBreak/Parse(datum/n_Parser/nS_Parser/parser) + . = KW_PASS + if(istype(parser.curBlock, /datum/node/BlockDefinition/GlobalBlock)) + parser.errors += new /datum/scriptError/BadToken(parser.curToken) + . = KW_WARN + var/datum/node/statement/BreakStatement/stmt = new(parser.curToken) + parser.NextToken() //skip 'break' token + parser.curBlock.statements += stmt + +/datum/n_Keyword/nS_Keyword/kwContinue + +/datum/n_Keyword/nS_Keyword/kwContinue/Parse(datum/n_Parser/nS_Parser/parser) + . = KW_PASS + if(istype(parser.curBlock, /datum/node/BlockDefinition/GlobalBlock)) + parser.errors += new /datum/scriptError/BadToken(parser.curToken) + . = KW_WARN + var/datum/node/statement/ContinueStatement/stmt = new(parser.curToken) + parser.NextToken() //skip 'break' token + parser.curBlock.statements += stmt + +/datum/n_Keyword/nS_Keyword/kwDef + +/datum/n_Keyword/nS_Keyword/kwDef/Parse(datum/n_Parser/nS_Parser/parser) + . = KW_PASS + var/datum/node/statement/FunctionDefinition/def = new(parser.curToken) + parser.NextToken() //skip 'def' token + if(!parser.options.IsValidID(parser.curToken.value)) + parser.errors += new /datum/scriptError/InvalidID(parser.curToken) + return KW_FAIL + def.func_name = parser.curToken.value + parser.NextToken() + if(!parser.CheckToken("(", /datum/token/symbol)) + return KW_FAIL + while(TRUE) //for now parameters can be separated by whitespace - they don't need a comma in between + if(istype(parser.curToken, /datum/token/symbol)) + switch(parser.curToken.value) + if(",") + parser.NextToken() + if(")") + break + else + parser.errors += new /datum/scriptError/BadToken(parser.curToken) + return KW_ERR + + else if(istype(parser.curToken, /datum/token/word)) + def.parameters += parser.curToken.value + parser.NextToken() + else + parser.errors += new /datum/scriptError/InvalidID(parser.curToken) + return KW_ERR + if(!parser.CheckToken(")", /datum/token/symbol)) + return KW_FAIL + + if(istype(parser.curToken, /datum/token/end)) //Function prototype + parser.curBlock.statements += def + else if(parser.curToken.value == "{" && istype(parser.curToken, /datum/token/symbol)) + def.block = new + parser.curBlock.statements.Insert(1, def) // insert into the beginning so that all functions are defined first + parser.curBlock.functions[def.func_name] = def + parser.AddBlock(def.block) + else + parser.errors += new /datum/scriptError/BadToken(parser.curToken) + return KW_FAIL + +#undef KW_FAIL +#undef KW_PASS +#undef KW_ERR +#undef KW_WARN diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/parser/parser.dm b/modular_tannhauser/modules/NTSL/code/coding_language/parser/parser.dm new file mode 100644 index 00000000000000..349309fbf34343 --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/parser/parser.dm @@ -0,0 +1,99 @@ +/* + * n_Parser + * An object that reads tokens and produces an AST (abstract syntax tree). + */ +/datum/n_Parser + ///The parser's current position in the token's list. + var/index = 1 + ///The token at in . + var/datum/token/curToken + var/datum/stack/blocks = new + var/datum/node/BlockDefinition/GlobalBlock/global_block = new + var/datum/node/BlockDefinition/curBlock + ///A list of tokens in the source code generated by a scanner. + var/list/tokens = new + ///A list of fatal errors found by the parser. If there are any items in this list, then it is not safe to run the returned AST. + var/list/errors = new + ////A list of non-fatal problems in the script. + var/list/warnings = new + +/datum/n_Parser/Destroy(force) + curToken = null + blocks = null + global_block = null + curBlock = null + return ..() + +///Reads the tokens and returns the AST's node. Be sure to populate the tokens list before calling this procedure. +/datum/n_Parser/proc/Parse() + +///Sets to the next token in the list, or null if there are no more tokens. +/datum/n_Parser/proc/NextToken() + if(index >= length(tokens)) + curToken = null + else + curToken = tokens[++index] + return curToken + +/* + * nS_Parser + * An implmentation of a parser for n_Script. + */ +/datum/n_Parser/nS_Parser + var/datum/n_scriptOptions/options + +/datum/n_Parser/nS_Parser/New(tokens[], datum/n_scriptOptions/options) + src.tokens = tokens + src.options = options + src.curBlock = global_block + return ..() + +/datum/n_Parser/nS_Parser/Destroy(force) + options = null + return ..() + +/datum/n_Parser/nS_Parser/Parse() + ASSERT(tokens) + for(,index <= length(tokens), index++) + curToken = tokens[index] + switch(curToken.type) + if(/datum/token/keyword) + var/datum/n_Keyword/kw = options.keywords[curToken.value] + kw = new kw() + if(kw) + if(!kw.Parse(src)) + return + continue + if(/datum/token/symbol) + if(curToken.value == "}") + if(!EndBlock()) + errors += new /datum/scriptError/BadToken(curToken) + continue + continue + if(/datum/token/end) + continue + curBlock.statements += ParseExpression() + if(!istype(curToken, /datum/token/end)) + errors += new /datum/scriptError/ExpectedToken(";", curToken) + continue + + return global_block + +/datum/n_Parser/nS_Parser/proc/CheckToken(val, type, err = 1, skip = 1) + if(!curToken || !istype(curToken, type) || curToken.value != val) + if(err) + errors += new /datum/scriptError/ExpectedToken(val, curToken) + return FALSE + if(skip) + NextToken() + return TRUE + +/datum/n_Parser/nS_Parser/proc/AddBlock(datum/node/BlockDefinition/B) + blocks.Push(curBlock) + curBlock = B + +/datum/n_Parser/nS_Parser/proc/EndBlock() + if(curBlock == global_block) + return FALSE + curBlock = blocks.Pop() + return TRUE diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/scanner/scanner.dm b/modular_tannhauser/modules/NTSL/code/coding_language/scanner/scanner.dm new file mode 100644 index 00000000000000..d427e0c1ecca57 --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/scanner/scanner.dm @@ -0,0 +1,206 @@ +#define COLUMN_LOCATION (codepos-linepos) + +/* + * n_Scanner + * An object responsible for breaking up source code into tokens for use by the parser. + */ +/datum/n_Scanner + var/code + ///A list of fatal errors found by the scanner. If there are any items in this list, then it is not safe to parse the returned tokens. + var/list/errors = new + ///A list of non-fatal problems in the source code found by the scanner. + var/list/warnings = new + +///Loads source code. +/datum/n_Scanner/proc/LoadCode(c) + code = c + +///Gets the code from a file and calls LoadCode() +/datum/n_Scanner/proc/LoadCodeFromFile(f) + LoadCode(file2text(f)) + +///Runs the scanner and returns the resulting list of tokens. Ensure that has been called first. +/datum/n_Scanner/proc/Scan() + +/* + * nS_Scanner + * A scanner implementation for n_Script. + */ +/datum/n_Scanner/nS_Scanner + + ///The scanner's position in the source code. + var/codepos = 1 + var/line = 1 + var/linepos = 0 + var/datum/n_scriptOptions/options + + ///List of characters that end a statement. Each item may only be one character long. Default is a semicolon. + var/list/end_stmt = list(";") + ///List of characters that are ignored by the scanner. Default is whitespace. + var/list/ignore = list(" ", "\t", "\n") + ///List of characters that can start and end strings. Default is double and single quotes. + var/list/string_delim = list("\"", "'") + ///List of characters that denote the start of a new token. This list is automatically populated. + var/list/delim = new + + +/datum/n_Scanner/nS_Scanner/New(code, datum/n_scriptOptions/options) + . = ..() + src.options = options + src.ignore += ascii2text(13) //Carriage return + src.delim += ignore + options.symbols + end_stmt + string_delim + LoadCode(code) + +/datum/n_Scanner/nS_Scanner/Destroy(force) + options = null + return ..() + +/datum/n_Scanner/nS_Scanner/Scan() + var/list/datum/token/tokens = new + for(, codepos <= length(code), codepos++) + var/char = copytext(code, codepos, codepos + 1) + var/twochar = copytext(code, codepos, codepos + 2) // For finding comment syntax + if(char == "\n") + line++ + linepos = codepos + + if(ignore.Find(char)) + continue + else if(twochar == "//" || twochar == "/*") + ReadComment() + else if(end_stmt.Find(char)) + tokens += new /datum/token/end(char, line, COLUMN_LOCATION) + else if(string_delim.Find(char)) + codepos++ //skip string delimiter + tokens += ReadString(char) + else if(options.CanStartID(char)) + tokens += ReadWord() + else if(options.IsDigit(char)) + tokens += ReadNumber() + else if(options.symbols.Find(char)) + tokens += ReadSymbol() + + codepos = initial(codepos) + line = initial(line) + linepos = initial(linepos) + return tokens + +/** + * ReadString + * Reads a string in the source code into a token. + * Arguments: + * start - The character used to start the string. + */ +/datum/n_Scanner/nS_Scanner/proc/ReadString(start) + var/buf + for(, codepos <= length(code), codepos++)//codepos to length(code)) + var/char = copytext(code, codepos, codepos + 1) + switch(char) + if("\\") //Backslash (\) encountered in string + codepos++ //Skip next character in string, since it was escaped by a backslash + char = copytext(code, codepos, codepos + 1) + switch(char) + if("\\") //Double backslash + buf += "\\" + if("n") //\n Newline + buf += "\n" + else + if(char == start) //\" Doublequote + buf += start + else //Unknown escaped text + buf += char + if("\n") + . = new /datum/token/string(buf, line, COLUMN_LOCATION) + errors += new /datum/scriptError("Unterminated string. Newline reached.", .) + line++ + linepos = codepos + break + else + if(char == start) //string delimiter found, end string + break + else + buf += char //Just a normal character in a string + if(!.) + return new /datum/token/string(buf, line, COLUMN_LOCATION) + +///Reads characters separated by an item in into a token. +/datum/n_Scanner/nS_Scanner/proc/ReadWord() + var/char = copytext(code, codepos, codepos + 1) + var/buf + + while(!delim.Find(char) && codepos <= length(code)) + buf += char + char = copytext(code, ++codepos, codepos + 1) + codepos-- //allow main Scan() proc to read the delimiter + if(options.keywords.Find(buf)) + return new /datum/token/keyword(buf, line, COLUMN_LOCATION) + else + return new /datum/token/word(buf, line, COLUMN_LOCATION) + +///Reads a symbol into a token. +/datum/n_Scanner/nS_Scanner/proc/ReadSymbol() + var/char = copytext(code, codepos, codepos + 1) + var/buf + + while(options.symbols.Find(buf+char)) + buf += char + if(++codepos > length(code)) + break + char = copytext(code, codepos, codepos + 1) + + codepos-- //allow main Scan() proc to read the next character + return new /datum/token/symbol(buf, line, COLUMN_LOCATION) + +///Reads a number into a token. +/datum/n_Scanner/nS_Scanner/proc/ReadNumber() + var/char = copytext(code, codepos, codepos + 1) + var/buf + var/dec = 0 + + while(options.IsDigit(char) || (char == "." && !dec)) + if(char == ".") + dec = 1 + buf += char + codepos++ + char = copytext(code, codepos, codepos + 1) + var/datum/token/number/T = new(buf, line, COLUMN_LOCATION) + if(isnull(text2num(buf))) + errors += new /datum/scriptError("Bad number: ", T) + T.value = 0 + codepos-- //allow main Scan() proc to read the next character + return T + +/* + * ReadComment + * Reads a comment. Wow. + * I'm glad I wrote this proc description for you to explain that. + * Unlike the other Read functions, this one doesn't have to return any tokens, + * since it's just "reading" comments. + * All it does is just pass var/codepos through the comments until it reaches the end of'em. + */ +/datum/n_Scanner/nS_Scanner/proc/ReadComment() + // Remember that we still have that $codepos "pointer" variable to use. + var/longeur = length(code) // So I don't call for var/code's length every while loop + + if(copytext(code, codepos, codepos + 2) == "//") // If line comment + ++codepos // Eat the current comment start, halfway + while(++codepos <= longeur) // Second half of the eating, on the first eval + if(copytext(code, codepos, codepos + 1) == "\n") // then stop on the newline + line++ + linepos = codepos + return + else // If long comment + ++codepos // Eat the current comment start, halfway + while(++codepos <= longeur) // Ditto, on the first eval + if(copytext(code, codepos, codepos + 2) == "*/") // then stop on any */ 's' + ++codepos // Eat the comment end + //but not all of it, because the for-loop this is in + //will increment it again later. + return + else if(copytext(code, codepos, codepos + 1) == "\n") // We still have to count line numbers! + line++ + linepos = codepos + //Else if the longcomment didn't end, do an error + errors += new /datum/scriptError/UnterminatedComment() + +#undef COLUMN_LOCATION diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/scanner/token.dm b/modular_tannhauser/modules/NTSL/code/coding_language/scanner/token.dm new file mode 100644 index 00000000000000..834e15e967d24e --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/scanner/token.dm @@ -0,0 +1,32 @@ +/* + * Token + * Represents an entity and position in the source code. + */ +/datum/token + var/value + var/line + var/column + +/datum/token/New(value, line = 0, column = 0) + src.value = value + src.line = line + src.column = column + +/datum/token/string + +/datum/token/symbol + +/datum/token/word + +/datum/token/keyword + +/datum/token/number + +/datum/token/number/New() + . = ..() + if(isnum(value)) + return + src.value = text2num(value) + ASSERT(!isnull(value)) + +/datum/token/end diff --git a/modular_tannhauser/modules/NTSL/code/coding_language/stack.dm b/modular_tannhauser/modules/NTSL/code/coding_language/stack.dm new file mode 100644 index 00000000000000..7ced5a8d1cf65b --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/coding_language/stack.dm @@ -0,0 +1,25 @@ +/datum/stack + var/list/contents = new + +/datum/stack/proc/Push(value) + contents += value + +/datum/stack/proc/Pop() + if(!length(contents)) + return null + . = contents[length(contents)] + contents.len-- + +///returns the item on the top of the stack without removing it +/datum/stack/proc/Top() + if(!length(contents)) + return null + return contents[length(contents)] + +/datum/stack/proc/Copy() as /datum/stack + var/datum/stack/new_stack = new() + new_stack.contents = contents.Copy() + return new_stack + +/datum/stack/proc/Clear() + contents.Cut() diff --git a/modular_tannhauser/modules/NTSL/code/filter.dm b/modular_tannhauser/modules/NTSL/code/filter.dm new file mode 100644 index 00000000000000..3b6a0a7f2af5ca --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/filter.dm @@ -0,0 +1,85 @@ +/** + * Yogstation's preety filter, in a smaller package restricted only to NTSL + * It has no effect on anything thats not NTSL code + */ + +/proc/setup_pretty_filter(path = "config/ntsl_filter.txt") + var/list/filter_lines = world.file2list(path) + for(var/line in filter_lines) + add_pretty_filter_line(line) + +// Add a filter pair +/proc/add_pretty_filter_line(line) + if(findtextEx(line,"#",1,2) || length(line) == 0) + return + + //Split the line at every "=" + var/list/parts = splittext(line, "=") + if(!length(parts)) + return FALSE + + //pattern is before the first "=" + var/pattern = parts[1] + if(!pattern) + return FALSE + + //replacement follows the first "=" + var/replacement = "" + if(length(parts) >= 2) + var/index = 2 + for(index = 2; index <= length(parts); index++) + replacement += parts[index] + if(index < length(parts)) + replacement += "=" + + if(!replacement) + return FALSE + + GLOB.pretty_filter_items.Add(line) + return TRUE + +/proc/isnotpretty(text) // A simpler version of pretty_filter(), where all it returns is whether it had to replace something or not. + //Useful for the "You fumble your words..." business. + for(var/line in GLOB.pretty_filter_items) + var/list/parts = splittext(line, "=") + var/pattern = parts[1] + var/regex/R = new(pattern, "ig") + if(R.Find(text)) //If found + return TRUE // Yes, it isn't pretty. + return FALSE // No, it is pretty. + +//Returns null if there is any bad text in the string +/proc/reject_bad_ntsl_text(text, max_length = 512, ascii_only = TRUE, require_pretty = TRUE, allow_newline = FALSE, allow_code = FALSE) + if(require_pretty && isnotpretty(text)) + return + var/char_count = 0 + var/non_whitespace = FALSE + var/lenbytes = length(text) + var/char = "" + for(var/i = 1, i <= lenbytes, i += length(char)) + char = text[i] + char_count++ + if(char_count > max_length) + return + switch(text2ascii(char)) + if(9, 62, 60, 92, 47) // tab, <, >, \, / + if(!allow_code) + return + if(10, 13) //Carriage returns (CR) and newline (NL) + if(!allow_newline) + return + if(0 to 8) + return + if(11, 12) + return + if(14 to 31) + return + if(32) + continue + if(127 to INFINITY) + if(ascii_only) + return + else + non_whitespace = TRUE + if(non_whitespace) + return text //only accepts the text if it has some non-spaces diff --git a/modular_tannhauser/modules/NTSL/code/global.dm b/modular_tannhauser/modules/NTSL/code/global.dm new file mode 100644 index 00000000000000..ae356095f2a55a --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/global.dm @@ -0,0 +1,2 @@ +GLOBAL_LIST_EMPTY(traffic_comps) +GLOBAL_LIST_EMPTY(pretty_filter_items) diff --git a/modular_tannhauser/modules/NTSL/code/logging.dm b/modular_tannhauser/modules/NTSL/code/logging.dm new file mode 100644 index 00000000000000..b9c671f08d0a04 --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/logging.dm @@ -0,0 +1,6 @@ +/datum/log_category/log_ntsl + category = LOG_CATEGORY_NTSL + master_category = /datum/log_category/game + config_flag = /datum/config_entry/flag/log_ntsl + +/datum/config_entry/flag/log_ntsl diff --git a/modular_tannhauser/modules/NTSL/code/machinery/overrides.dm b/modular_tannhauser/modules/NTSL/code/machinery/overrides.dm new file mode 100644 index 00000000000000..13641721cd0c4a --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/machinery/overrides.dm @@ -0,0 +1,7 @@ +/obj/machinery/computer/telecomms/server/Initialize(mapload) + .=..() + light_color = LIGHT_COLOR_GREEN + +/obj/machinery/computer/telecomms/monitor/Initialize(mapload) + .=..() + light_color = LIGHT_COLOR_GREEN diff --git a/modular_tannhauser/modules/NTSL/code/machinery/server.dm b/modular_tannhauser/modules/NTSL/code/machinery/server.dm new file mode 100644 index 00000000000000..e1d141a1227940 --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/machinery/server.dm @@ -0,0 +1,95 @@ +GLOBAL_LIST_EMPTY(tcomms_servers) + +/obj/item/radio/server + +/obj/item/radio/server/can_receive(frequency,levels) + return FALSE // The server's radio isn't for receiving, it's for outputting. For now. + +/obj/machinery/telecomms/server + //NTSL-related stuffs + var/datum/TCS_Compiler/Compiler // the compiler that compiles and runs the code + var/autoruncode = FALSE // 1 if the code is set to run every time a signal is picked up + var/list/memory = list() // stored memory, for mem() in NTSL + var/rawcode = "" // the code to compile (raw-ass text) + var/compiledcode = "" //the last compiled code (also raw-ass text) + var/obj/item/radio/server/server_radio // Allows the server to talk on the radio, via broadcast() in NTSL + var/last_signal = 0 // Marks the last time an NTSL script called signal() from this server, to stop spam. + var/list/compile_warnings = list() + //End-NTSL + COOLDOWN_DECLARE(compile_cooldown) + +//NTSL-related procs +/obj/machinery/telecomms/server/Initialize(mapload) + Compiler = new() + Compiler.Holder = src + server_radio = new() + GLOB.tcomms_servers += src + return ..() + +/obj/machinery/telecomms/server/Destroy() + QDEL_NULL(Compiler) + QDEL_NULL(server_radio) + memory = null + GLOB.tcomms_servers -= src + return ..() + +/obj/machinery/telecomms/server/proc/update_logs() + if(length(log_entries) >= 400) // If so, start deleting at least, hopefully, one log entry + log_entries.Cut(1, 2) + /* + for(var/i = 1, i <= length(log_entries), i++) // locate the first garbage collectable log entry and remove it + var/datum/comm_log_entry/L = log_entries[i] + if(L.garbage_collector) + log_entries.Remove(L) + break + */ + +/obj/machinery/telecomms/server/proc/add_entry(content, input) + var/datum/comm_log_entry/log = new + var/identifier = num2text( rand(-1000,1000) + world.time ) + log.name = "[input] ([md5(identifier)])" + log.input_type = input + log.parameters["message"] = content + log_entries.Add(log) + update_logs() + +/obj/machinery/telecomms/server/proc/compile(mob/user = usr) as /list + if(is_banned_from(user.ckey, JOB_TELECOMMS_SPECIALIST)) + to_chat(user, span_warning("You are banned from using NTSL.")) + return "Unauthorized access." + + if(QDELETED(Compiler)) + return + + if(!reject_bad_ntsl_text(rawcode, 20000, require_pretty = FALSE, allow_newline = TRUE, allow_code = TRUE)) + rawcode = null + return "Please use galactic common characters only." + if(!COOLDOWN_FINISHED(src, compile_cooldown)) + return "Servers are recharging, please wait." + var/list/compileerrors = Compiler.Compile(rawcode) + COOLDOWN_START(src, compile_cooldown, 2 SECONDS) + if(!length(compileerrors) && (compiledcode != rawcode)) + user.log_message(rawcode, LOG_NTSL) + compiledcode = rawcode + if(user.mind.assigned_role == JOB_TELECOMMS_SPECIALIST) //achivement description says only Signal Technician gets the achivement + var/freq = length(freq_listening[1]) ? freq_listening[1] : 1459 + var/atom/movable/M = new() + var/atom/movable/virtualspeaker/speaker = new(null, M, server_radio) + speaker.name = "Poly" + speaker.job = "" + var/datum/signal/subspace/vocal/signal = new(src, freq, speaker, /datum/language/common, "test", list(), ) + signal.data["server"] = src + Compiler.Run(signal) + if(signal.data["reject"] == TRUE) + signal.data["name"] = "" + signal.data["reject"] = FALSE + Compiler.Run(signal) + if(!signal.data["reject"] == FALSE) + user.client.give_award(/datum/award/achievement/jobs/Poly_silent, user) + else + for(var/sample in signal.data["spans"]) + if(sample == SPAN_COMMAND) + user.client.give_award(/datum/award/achievement/jobs/Poly_loud, user) + break // Not having this break leaves us open to a potential DoS attack. + return compileerrors +//end-NTSL diff --git a/modular_tannhauser/modules/NTSL/code/machinery/traffic_control.dm b/modular_tannhauser/modules/NTSL/code/machinery/traffic_control.dm new file mode 100644 index 00000000000000..ae759415f8b37b --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/machinery/traffic_control.dm @@ -0,0 +1,231 @@ +/obj/item/circuitboard/computer/comm_traffic + name = "Telecommunications Traffic Control" + build_path = /obj/machinery/computer/telecomms/traffic + +/datum/design/board/traffic + name = "Computer Design (Traffic Console)" + desc = "Allows for the construction of Traffic Control Console." + id = "s_traffic" + build_path = /obj/item/circuitboard/computer/comm_traffic + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_TELECOMMS + ) + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_SCIENCE + +/obj/machinery/computer/telecomms/traffic + name = "traffic control computer" + desc = "A computer used to interface with the programming of communication servers." + + /// The servers located by the computer + var/list/obj/machinery/telecomms/server/servers = list() + /// The network to probe + var/network = "NULL" + /// The current code being used + var/storedcode = "" + /// The ID currently inserted + var/obj/item/card/id/inserted_id + /// The name and job on the ID used to log in + var/user_name = "" + /// Logs for users logging in/out or compiling code + var/list/access_log = list() + /// Output from compiling to the servers + var/list/compiler_output = list() + + var/unlimited_range = FALSE + + circuit = /obj/item/circuitboard/computer/comm_traffic + + req_access = list(ACCESS_TCOMMS_ADMIN) + +/obj/machinery/computer/telecomms/traffic/Initialize(mapload) + ..() + GLOB.traffic_comps += src + if(length(GLOB.pretty_filter_items) == 0) + setup_pretty_filter() + if(mapload) + unlimited_range = TRUE + return INITIALIZE_HINT_LATELOAD + +/obj/machinery/computer/telecomms/traffic/post_machine_initialize() + . = ..() + refresh_servers() + for(var/obj/machinery/telecomms/server/new_server in servers) + new_server.autoruncode = TRUE + +/obj/machinery/computer/telecomms/traffic/Destroy() + GLOB.traffic_comps -= src + servers = null + if(!isnull(inserted_id)) + inserted_id.forceMove(drop_location()) + inserted_id = null + return ..() + +/obj/machinery/computer/telecomms/traffic/proc/create_log(entry) + if(!user_name) + CRASH("[type] tried to create a log with no user_name!") + access_log += "\[[get_timestamp()]\] [user_name] [entry]" + +/obj/machinery/computer/telecomms/traffic/ui_interact(mob/user, datum/tgui/ui) + if(is_banned_from(user.ckey, JOB_TELECOMMS_SPECIALIST)) + to_chat(user, span_warning("You are banned from using the NTSL console")) + return "You are banned from using NTSL." + + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "NTSLCoding", name) + ui.open() + +/obj/machinery/computer/telecomms/traffic/ui_data(mob/user) + var/list/data = list() + var/list/server_data = list() + for(var/obj/machinery/telecomms/server/server in servers) + server_data.Add(list(list( + "server" = REF(server), + "server_id" = server.id, + "server_name" = server.name, + "run_code" = server.autoruncode, + ))) + data["server_data"] = server_data + data["stored_code"] = storedcode + data["network"] = network + data["user_name"] = user_name + data["has_access"] = inserted_id + data["access_log"] = access_log.Copy() + data["compiler_output"] = compiler_output.Copy() + data["emagged"] = ((obj_flags & EMAGGED) > 0) + + data["admin_view"] = FALSE + if(user.client.holder) // enables an admin-only button in case of severe grief + data["admin_view"] = TRUE + + return data + +/obj/machinery/computer/telecomms/traffic/ui_act(action, list/params) + . = ..() + if(action == "admin_reset") // something to note, this will runtime when clicked on by an admin ghost. But still works. + if(!usr.client.holder) + message_admins("[key_name_admin(usr)] has attempted to call \"admin_reset\" on a traffic console, this should not be possible as a non-admin and could have been an attempted javascript injection.") + return + network = "tcommsat" + refresh_servers() + for(var/obj/machinery/telecomms/server/server as anything in servers) + server.rawcode = "def process_signal(sig){ return sig;" // bare minimum + qdel(compiler_output) + compiler_output = compile_all(usr) + var/message = "[key_name_admin(usr)] has completelly cleared the NTSL console of code and re-compiled as an admin, this should only be done in severe rule infractions." + message_admins(message) + logger.Log(LOG_NTSL, "[key_name(src)] [message] [loc_name(src)]") + access_log += "\[[get_timestamp()]\] ERR !NTSL REMOTELLY CLEARED BY NANOTRASEN STAFF!" + return TRUE + if(.) + return + + playsound(src, "terminal_type", 15, FALSE) + switch(action) + if("refresh_servers") + refresh_servers() + return TRUE + if("toggle_code_execution") + var/obj/machinery/telecomms/server/selected_server = locate(params["selected_server"]) + selected_server.autoruncode = !selected_server.autoruncode + return TRUE + if("save_code") + storedcode = params["saved_code"] + return TRUE + if("compile_code") + if(!user_name) + message_admins("[key_name_admin(usr)] attempted compiling NTSL without being logged in.") // tell admins that someone tried a javascript injection + return + for(var/obj/machinery/telecomms/server/server as anything in servers) + if(storedcode && istext(storedcode)) + server.rawcode = storedcode + qdel(compiler_output) + compiler_output = compile_all(usr) + return TRUE + if("set_network") + if(!user_name) + return + network = params["new_network"] + return TRUE + if("log_in") + var/mob/living/usr_mob = usr + if(usr_mob.has_unlimited_silicon_privilege) + user_name = "System Administrator" + else if(check_access(inserted_id)) + user_name = "[inserted_id?.registered_name] ([inserted_id?.assignment])" + else + var/obj/item/card/id/user_id = usr_mob.get_idcard(TRUE) + if(!check_access(user_id)) + return + user_name = "[user_id?.registered_name] ([user_id?.assignment])" + create_log("has logged in.") + return TRUE + if("log_out") + if(obj_flags & EMAGGED) + return TRUE + create_log("has logged out.") + user_name = null + return TRUE + if("clear_logs") + if(!user_name) + message_admins("[key_name_admin(usr)] attempted clearing NTSL logs without being logged in.") + return + access_log.Cut() + create_log("cleared access logs.") + return TRUE + +/obj/machinery/computer/telecomms/traffic/proc/refresh_servers() + servers.Cut() + for(var/obj/machinery/telecomms/server/new_server as anything in GLOB.tcomms_servers) + if(new_server.network != network) + continue + if(!unlimited_range && get_dist(src, new_server) > 15) + continue + servers.Add(new_server) + +/obj/machinery/computer/telecomms/traffic/proc/compile_all(mob/user) + if(is_banned_from(user.ckey, JOB_TELECOMMS_SPECIALIST)) + return list("You are banned from using NTSL.") + if(!length(servers)) + return list("No servers detected.") + for(var/obj/machinery/telecomms/server/server as anything in servers) + var/list/compile_errors = server.compile(user) + if(!compile_errors) + return list("A fatal error has occured. Please contact your local network adminstrator.") + if(istext(compile_errors)) + return splittext(compile_errors, "\n") + var/list/text_list = list() + for(var/datum/scriptError/error in compile_errors) + text_list.Add(error.message) + if(length(text_list)) + return text_list + create_log("compiled to all linked servers on [network].") + return list("Compiling finished.") + +/obj/machinery/computer/telecomms/traffic/attackby(obj/item/item, mob/user, params) + if(istype(item, /obj/item/card/id) && check_access(item) && user.transferItemToLoc(item, src)) + inserted_id = item + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) + return + return ..() + +/obj/machinery/computer/telecomms/traffic/emag_act(mob/user, obj/item/card/emag/emag_card) + if(obj_flags & EMAGGED) + return FALSE + obj_flags |= EMAGGED + user_name = "System Administrator" + create_log("has logged in.") + playsound(src.loc, 'sound/effects/sparks4.ogg', 75, 1) + to_chat(user, span_notice("You bypass the console's security protocols.")) + +/obj/machinery/computer/telecomms/traffic/click_alt_secondary(mob/user) + if(!user.can_perform_action(src, NEED_DEXTERITY) || !iscarbon(user)) + return + + var/mob/living/carbon/carbon_user = user + if(inserted_id) + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) + inserted_id.forceMove(drop_location()) + carbon_user.put_in_hands(inserted_id) + inserted_id = null diff --git a/modular_tannhauser/modules/NTSL/code/object_vars.dm b/modular_tannhauser/modules/NTSL/code/object_vars.dm new file mode 100644 index 00000000000000..f964a868c34190 --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/object_vars.dm @@ -0,0 +1,9 @@ +// most code in: (code/game/say.dm) +/atom/movable/virtualspeaker + var/realvoice + +/atom/movable/virtualspeaker/GetVoice(bool) + if(bool && realvoice) + return realvoice + else + return "[src]" diff --git a/modular_tannhauser/modules/NTSL/code/signal_technician/clothing.dm b/modular_tannhauser/modules/NTSL/code/signal_technician/clothing.dm new file mode 100644 index 00000000000000..1227888c70d25e --- /dev/null +++ b/modular_tannhauser/modules/NTSL/code/signal_technician/clothing.dm @@ -0,0 +1,22 @@ +/obj/item/clothing/under/rank/engineering/signal_tech + name = "signal technician's jumpsuit" + desc = "It's an orange high visibility jumpsuit with green strips worn by signal technicians. Made from fire resistant materials." + icon = 'modular_tannhauser/modules/NTSL/icons/clothing_object.dmi' + worn_icon = 'modular_tannhauser/modules/NTSL/icons/clothing_mob.dmi' + icon_state = "signal_tech" + +/obj/item/clothing/head/helmet/space/plasmaman/signal_tech + name = "signal technician plasma envirosuit helmet" + desc = "A space-worthy helmet specially designed for signal technician plasmamen, the usual purple stripes being replaced by a unique bright green." + icon = 'modular_tannhauser/modules/NTSL/icons/clothing_object.dmi' + worn_icon = 'modular_tannhauser/modules/NTSL/icons/clothing_mob.dmi' + icon_state = "signal_tech_envirohelm" + inhand_icon_state = null + armor_type = /datum/armor/space_plasmaman/engineering_atmos + +/obj/item/clothing/under/plasmaman/engineering/signal_tech + name = "signal technician plasma envirosuit" + desc = "An air-tight suit designed to be used by plasmamen employed as signal technicians, the usual purple stripes being replaced by a unique bright green. It protects the user from fire and acid damage." + icon = 'modular_tannhauser/modules/NTSL/icons/clothing_object.dmi' + worn_icon = 'modular_tannhauser/modules/NTSL/icons/clothing_mob.dmi' + icon_state = "signal_tech_envirosuit" diff --git a/modular_tannhauser/modules/NTSL/icons/clothing_mob.dmi b/modular_tannhauser/modules/NTSL/icons/clothing_mob.dmi new file mode 100644 index 00000000000000..e69344985ba72a Binary files /dev/null and b/modular_tannhauser/modules/NTSL/icons/clothing_mob.dmi differ diff --git a/modular_tannhauser/modules/NTSL/icons/clothing_object.dmi b/modular_tannhauser/modules/NTSL/icons/clothing_object.dmi new file mode 100644 index 00000000000000..2f3f5083b6b623 Binary files /dev/null and b/modular_tannhauser/modules/NTSL/icons/clothing_object.dmi differ diff --git a/modular_tannhauser/modules/NTSL/readme.md b/modular_tannhauser/modules/NTSL/readme.md new file mode 100644 index 00000000000000..462a26bbb364e9 --- /dev/null +++ b/modular_tannhauser/modules/NTSL/readme.md @@ -0,0 +1,42 @@ +https://github.com/Monkestation/Monkestation2.0/pull/2199 + +## \ + +MODULE ID: NTSL + +### Description: + +Allows people to change how comms work via NTSL +for example, adding in their job after their name + +### TG Proc/File Changes: + +- code\datums\chatmessage.dm -- Added a if(!speaker); return; due to NTSL code not passing a speaker when you use broadcast() +- code\datums\id_trim\jobs.dm -- Added the ACCESS_TCOMMS_ADMIN to the CE's trim + +- code\game\say.dm -- Adds a to the end of endspanpart, also a lot of stuff for AI tracking +- code\game\machinery\telecomms\telecomunications.dm -- Added some logging if there's a wrong filter path +- code\game\machinery\telecomms\broadcasting.dm -- Added a lvls var to the signal, needed for broadcast() on comms to work +- code\game\machinery\telecomms\machines\server.dm -- Added stuff to make the servers actually compile NTSL + +- code\modules\research\techweb\all_nodes.dm -- Added the programming console thingy to the telecomms techweb + +- icons\ui\achievements\achievements.dmi -- Added the achievement icon for loud and silent birb + +### Included files that are not contained in this module: + +- modular_skyrat\modules\telecomms_specialist\telecomms_specialist.dm +- tgui\packages\tgui\interfaces\NTSLCoding.js -- Interface for the traffic console + +### Defines: + +- code\__DEFINES\~tannhauser_defines\NTSL.dm + +- code\__DEFINES\access.dm +- code\__DEFINES\logging.dm -- Added NTSL log stuff + +### Credits: + +- Altoids1 -- Original author in 2019 +- JohnFulpWillard -- Doing a lot of stuff apparently +- Gboster-0 -- Porting to Tannhauser, fixes diff --git a/tgstation.dme b/tgstation.dme index 8f36dbaa0be0d9..aab83951733544 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -488,6 +488,7 @@ #include "code\__DEFINES\~skyrat_defines\signals\signals_human.dm" #include "code\__DEFINES\~skyrat_defines\traits\declarations.dm" #include "code\__DEFINES\~tannhauser_defines\barsigns.dm" +#include "code\__DEFINES\~tannhauser_defines\NTSL.dm" #include "code\__HELPERS\_auxtools_api.dm" #include "code\__HELPERS\_dreamluau.dm" #include "code\__HELPERS\_lists.dm" @@ -8575,6 +8576,35 @@ #include "modular_tannhauser\modules\CitOwOChems\code\mob\organs.dm" #include "modular_tannhauser\modules\CitOwOChems\code\mob\plushie.dm" #include "modular_tannhauser\modules\CitOwOChems\code\obj\plushies.dm" +#include "modular_tannhauser\modules\NTSL\code\achievements.dm" +#include "modular_tannhauser\modules\NTSL\code\filter.dm" +#include "modular_tannhauser\modules\NTSL\code\global.dm" +#include "modular_tannhauser\modules\NTSL\code\logging.dm" +#include "modular_tannhauser\modules\NTSL\code\object_vars.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\errors.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\options.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\stack.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\AST\ast_nodes.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\AST\block.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\AST\statement.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\AST\operators\binary_operators.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\AST\operators\unary_operators.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\implementations\logic.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\implementations\telecomms_translator.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\interpreter\evaluation.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\interpreter\interaction.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\interpreter\interpreter.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\interpreter\objects.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\interpreter\scope.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\parser\expressions.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\parser\keywords.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\parser\parser.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\scanner\scanner.dm" +#include "modular_tannhauser\modules\NTSL\code\coding_language\scanner\token.dm" +#include "modular_tannhauser\modules\NTSL\code\machinery\overrides.dm" +#include "modular_tannhauser\modules\NTSL\code\machinery\server.dm" +#include "modular_tannhauser\modules\NTSL\code\machinery\traffic_control.dm" +#include "modular_tannhauser\modules\NTSL\code\signal_technician\clothing.dm" #include "modular_tannhauser\modules\Shuttles\tourist_shuttle.dm" #include "modular_tannhauser\modules\TannhauserEmotes\code\emote.dm" #include "modular_tannhauser\modules\ZestyStatue\code\zesty.dm" diff --git a/tgui/packages/tgui/interfaces/NTSLCoding.tsx b/tgui/packages/tgui/interfaces/NTSLCoding.tsx new file mode 100644 index 00000000000000..17f14ae267dac2 --- /dev/null +++ b/tgui/packages/tgui/interfaces/NTSLCoding.tsx @@ -0,0 +1,284 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend, useLocalState } from '../backend'; +import { + Box, + Button, + Divider, + Input, + Section, + Stack, + Tabs, + TextArea, +} from '../components'; +import { RADIO_CHANNELS } from '../constants'; +import { Window } from '../layouts'; + +type Data = { + admin_view: BooleanLike; + emagged: BooleanLike; + stored_code: string; + user_name: string; + network: string; + compiler_output: string[]; + access_log: string[]; + server_data: Server_Data[]; +}; + +type Server_Data = { + run_code: BooleanLike; + server: string; + server_name: string; +}; + +export const NTSLCoding = (props) => { + // Make sure we don't start larger than 50%/80% of screen width/height. + const winWidth = Math.min(900, window.screen.availWidth * 0.5); + const winHeight = Math.min(600, window.screen.availHeight * 0.8); + + return ( + + + + + + + + + + + + + ); +}; + +const ScriptEditor = (props) => { + const { act, data } = useBackend(); + const { stored_code, user_name } = data; + return ( + + {user_name ? ( +