diff --git a/.github/workflows/run_integration_tests.yml b/.github/workflows/run_integration_tests.yml index 7b544fef2a66c..3485b90d47489 100644 --- a/.github/workflows/run_integration_tests.yml +++ b/.github/workflows/run_integration_tests.yml @@ -46,9 +46,9 @@ jobs: - name: Install rust-g run: | bash tools/ci/install_rust_g.sh - - name: Install auxlua + - name: Install dreamluau run: | - bash tools/ci/install_auxlua.sh + bash tools/ci/install_dreamluau.sh - name: Configure version run: | echo "BYOND_MAJOR=${{ inputs.major }}" >> $GITHUB_ENV diff --git a/.github/workflows/update_tgs_dmapi.yml b/.github/workflows/update_tgs_dmapi.yml index 15d45b7935f05..8dfdee90e36a0 100644 --- a/.github/workflows/update_tgs_dmapi.yml +++ b/.github/workflows/update_tgs_dmapi.yml @@ -28,8 +28,8 @@ jobs: - name: Commit and Push continue-on-error: true run: | - git config user.name tgstation-server - git config user.email tgstation-server@users.noreply.github.com + git config user.name "tgstation-server-ci[bot]" + git config user.email "161980869+tgstation-server-ci[bot]@users.noreply.github.com" git add . git commit -m 'Update TGS DMAPI' git push -f -u origin tgs-dmapi-update diff --git a/.gitignore b/.gitignore index c70ebf608b965..8ef9946abc935 100644 --- a/.gitignore +++ b/.gitignore @@ -214,7 +214,7 @@ Temporary Items # Built auxtools libraries and intermediate files aux*.dll libaux*.so -aux*.pdb +*.pdb # byond-tracy, we intentionally do not ship this and do not want to maintain it # https://github.com/mafemergency/byond-tracy/ diff --git a/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm b/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm index 64e7be0705b6b..21b981191aeb3 100644 --- a/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm +++ b/_maps/RandomRuins/SpaceRuins/infested_frigate.dmm @@ -856,7 +856,7 @@ icon_state = "floor5-old" }, /obj/item/ammo_casing/spent, -/obj/item/gun/ballistic/automatic/plastikov, +/obj/item/gun/ballistic/automatic/smartgun, /obj/effect/mob_spawn/corpse/human/syndicatepilot, /turf/open/floor/mineral/plastitanium/red, /area/ruin/space/has_grav/infested_frigate) @@ -2790,7 +2790,7 @@ icon_state = "plastitaniumtiny" }, /mob/living/basic/alien/queen/large{ - loot = list(/obj/effect/gibspawner/xeno,/obj/item/ammo_box/magazine/plastikov9mm,/obj/effect/mob_spawn/corpse/human/syndicatecommando/soft_suit); + loot = list(/obj/effect/gibspawner/xeno,/obj/item/ammo_box/magazine/smartgun,/obj/effect/mob_spawn/corpse/human/syndicatecommando/soft_suit); desc = "What you saw in your dreams last night."; faction = list("syndicate","xenomorph") }, diff --git a/_maps/RandomZLevels/SnowCabin.dmm b/_maps/RandomZLevels/SnowCabin.dmm index 3b5c32345c0e3..bfbc5a18c56a1 100644 --- a/_maps/RandomZLevels/SnowCabin.dmm +++ b/_maps/RandomZLevels/SnowCabin.dmm @@ -598,7 +598,7 @@ /turf/open/floor/iron/freezer, /area/awaymission/cabin) "cH" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowcabin, /obj/structure/cable, /turf/open/floor/wood, /area/awaymission/cabin) @@ -660,7 +660,7 @@ /turf/open/floor/iron/white, /area/awaymission/cabin) "cQ" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowcabin, /obj/structure/cable, /turf/open/floor/carpet, /area/awaymission/cabin) @@ -2904,7 +2904,7 @@ /area/awaymission/cabin/snowforest) "rk" = ( /obj/machinery/light/directional/south, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowcabin, /obj/structure/sign/poster/official/report_crimes/directional/south, /obj/structure/cable, /turf/open/floor/carpet, diff --git a/_maps/RandomZLevels/TheBeach.dmm b/_maps/RandomZLevels/TheBeach.dmm index c7a097a02ad17..64cdcbb6d362e 100644 --- a/_maps/RandomZLevels/TheBeach.dmm +++ b/_maps/RandomZLevels/TheBeach.dmm @@ -1688,7 +1688,7 @@ "vq" = ( /obj/effect/decal/cleanable/dirt, /obj/effect/mapping_helpers/broken_floor, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/beach, /turf/open/floor/plating, /area/awaymission/beach) "vx" = ( @@ -3586,7 +3586,7 @@ /area/awaymission/beach) "SB" = ( /obj/effect/decal/cleanable/dirt, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/beach, /turf/open/floor/plating, /area/awaymission/beach) "SI" = ( diff --git a/_maps/RandomZLevels/caves.dmm b/_maps/RandomZLevels/caves.dmm index c64aa99d1aab6..a21d64d505b63 100644 --- a/_maps/RandomZLevels/caves.dmm +++ b/_maps/RandomZLevels/caves.dmm @@ -480,7 +480,7 @@ /turf/open/floor/plating, /area/awaymission/caves/research) "cR" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/caves, /turf/open/floor/plating, /area/awaymission/caves/research) "cS" = ( @@ -610,7 +610,7 @@ "dw" = ( /obj/structure/bed, /obj/item/bedsheet, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/caves, /turf/open/floor/iron, /area/awaymission/caves/bmp_asteroid/level_two) "dx" = ( @@ -653,7 +653,7 @@ "dH" = ( /obj/structure/bed, /obj/item/bedsheet, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/caves, /turf/open/floor/wood, /area/awaymission/caves/northblock) "dI" = ( @@ -705,7 +705,7 @@ /obj/structure/bed, /obj/item/bedsheet, /obj/effect/decal/cleanable/cobweb/cobweb2, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/caves, /turf/open/floor/wood, /area/awaymission/caves/northblock) "ea" = ( @@ -714,7 +714,7 @@ /area/awaymission/caves/northblock) "ed" = ( /obj/structure/bed, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/caves, /turf/open/floor/wood, /area/awaymission/caves/northblock) "ee" = ( @@ -849,7 +849,7 @@ /turf/open/floor/iron, /area/awaymission/caves/listeningpost) "eO" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/caves, /turf/open/floor/iron, /area/awaymission/caves/listeningpost) "eP" = ( @@ -1584,7 +1584,7 @@ }, /area/awaymission/caves/bmp_asteroid/level_two) "Bs" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/caves, /turf/open/misc/asteroid/basalt{ initial_gas_mix = "n2=23;o2=14;TEMP=2.7" }, diff --git a/_maps/RandomZLevels/moonoutpost19.dmm b/_maps/RandomZLevels/moonoutpost19.dmm index 3a4e4c8affed6..a8f52784b343a 100644 --- a/_maps/RandomZLevels/moonoutpost19.dmm +++ b/_maps/RandomZLevels/moonoutpost19.dmm @@ -1903,14 +1903,14 @@ /turf/open/floor/mineral/titanium/yellow, /area/awaymission/moonoutpost19/arrivals) "ms" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/moonoutpost, /turf/open/floor/mineral/titanium/blue, /area/awaymission/moonoutpost19/arrivals) "mt" = ( /obj/structure/chair{ dir = 8 }, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/moonoutpost, /turf/open/floor/mineral/titanium/blue, /area/awaymission/moonoutpost19/arrivals) "mu" = ( @@ -1955,7 +1955,7 @@ /turf/open/floor/mineral/titanium/yellow, /area/awaymission/moonoutpost19/arrivals) "mI" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/moonoutpost, /turf/open/floor/mineral/titanium/yellow, /area/awaymission/moonoutpost19/arrivals) "mJ" = ( @@ -1965,7 +1965,7 @@ icon_state = "beacon"; name = "tracking beacon" }, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/moonoutpost, /turf/open/floor/mineral/titanium/yellow, /area/awaymission/moonoutpost19/arrivals) "mK" = ( diff --git a/_maps/RandomZLevels/museum.dmm b/_maps/RandomZLevels/museum.dmm index 2937250b1f9ba..afd0cd888fdcd 100644 --- a/_maps/RandomZLevels/museum.dmm +++ b/_maps/RandomZLevels/museum.dmm @@ -1426,7 +1426,7 @@ /turf/open/indestructible/plating, /area/awaymission/museum) "lz" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/museum, /obj/structure/flora/bush/sparsegrass/style_random, /turf/open/floor/grass, /area/awaymission/museum) @@ -3095,7 +3095,7 @@ /turf/open/floor/iron/smooth_large, /area/awaymission/museum) "zd" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/museum, /turf/open/floor/grass, /area/awaymission/museum) "zg" = ( @@ -4503,7 +4503,7 @@ }, /area/awaymission/museum) "KN" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/museum, /obj/effect/turf_decal/siding/wood{ dir = 1 }, diff --git a/_maps/RandomZLevels/research.dmm b/_maps/RandomZLevels/research.dmm index 1e84014fa0e1b..ae1814b7d7096 100644 --- a/_maps/RandomZLevels/research.dmm +++ b/_maps/RandomZLevels/research.dmm @@ -389,7 +389,7 @@ /turf/open/floor/iron/dark, /area/awaymission/research/interior/gateway) "bR" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/research, /turf/open/floor/iron/dark, /area/awaymission/research/interior/gateway) "bS" = ( @@ -438,7 +438,7 @@ /turf/open/floor/iron/dark, /area/awaymission/research/interior/gateway) "bZ" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/research, /obj/effect/turf_decal/stripes/line, /obj/structure/cable, /turf/open/floor/iron/dark, @@ -481,12 +481,12 @@ /area/awaymission/research/interior/gateway) "cj" = ( /obj/structure/window/reinforced/spawner/directional/south, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/research, /turf/open/floor/iron/dark, /area/awaymission/research/interior/gateway) "ck" = ( /obj/machinery/door/window/right/directional/south, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/research, /obj/structure/cable, /turf/open/floor/iron/dark, /area/awaymission/research/interior/gateway) @@ -1761,7 +1761,7 @@ /obj/structure/toilet{ dir = 8 }, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/research, /obj/machinery/light/small/directional/east, /turf/open/floor/iron/freezer, /area/awaymission/research/interior/bathroom) @@ -2007,7 +2007,7 @@ "jU" = ( /obj/structure/bed, /obj/item/bedsheet/blue, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/research, /turf/open/floor/wood, /area/awaymission/research/interior/dorm) "jV" = ( @@ -2505,7 +2505,7 @@ "lT" = ( /obj/structure/bed, /obj/item/bedsheet/patriot, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/research, /turf/open/floor/wood, /area/awaymission/research/interior/dorm) "lX" = ( @@ -2976,7 +2976,7 @@ /area/awaymission/research/interior) "sM" = ( /obj/structure/window/reinforced/spawner/directional/south, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/research, /obj/structure/cable, /turf/open/floor/iron/dark, /area/awaymission/research/interior/gateway) diff --git a/_maps/RandomZLevels/snowdin.dmm b/_maps/RandomZLevels/snowdin.dmm index d66be7f5614ff..6f565143e790e 100644 --- a/_maps/RandomZLevels/snowdin.dmm +++ b/_maps/RandomZLevels/snowdin.dmm @@ -181,7 +181,7 @@ /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4{ dir = 8 }, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowdin, /obj/item/bedsheet/purple, /turf/open/floor/wood, /area/awaymission/snowdin/post/dorm) @@ -213,7 +213,7 @@ /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4{ dir = 8 }, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowdin, /turf/open/floor/wood, /area/awaymission/snowdin/post/dorm) "aY" = ( @@ -239,7 +239,7 @@ /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4{ dir = 8 }, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowdin, /obj/item/paper/crumpled/ruins/snowdin/dontdeadopeninside, /obj/item/bedsheet/green, /turf/open/floor/wood, @@ -414,7 +414,7 @@ /turf/open/floor/iron/freezer, /area/awaymission/snowdin/post/kitchen) "bM" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowdin, /obj/effect/decal/cleanable/blood/old, /turf/open/floor/iron/freezer, /area/awaymission/snowdin/post/kitchen) @@ -1092,7 +1092,7 @@ "eh" = ( /obj/structure/bed, /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowdin, /obj/item/bedsheet/red, /obj/effect/mapping_helpers/broken_floor, /turf/open/floor/wood, @@ -2073,12 +2073,12 @@ /area/awaymission/snowdin/post/gateway) "hR" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowdin, /obj/effect/decal/cleanable/dirt, /turf/open/floor/iron, /area/awaymission/snowdin/post/gateway) "hS" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowdin, /obj/effect/turf_decal/loading_area, /obj/effect/decal/cleanable/dirt, /obj/structure/cable, @@ -2086,7 +2086,7 @@ /area/awaymission/snowdin/post/gateway) "hT" = ( /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer4, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowdin, /obj/effect/decal/cleanable/dirt, /turf/open/floor/iron, /area/awaymission/snowdin/post/gateway) @@ -2302,7 +2302,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 5 }, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowdin, /turf/open/floor/iron, /area/awaymission/snowdin/post/gateway) "iB" = ( @@ -2310,7 +2310,7 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{ dir = 10 }, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowdin, /obj/machinery/holopad, /obj/effect/decal/cleanable/dirt, /obj/structure/cable, @@ -2318,7 +2318,7 @@ /area/awaymission/snowdin/post/gateway) "iC" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowdin, /turf/open/floor/iron, /area/awaymission/snowdin/post/gateway) "iD" = ( @@ -2579,14 +2579,14 @@ /turf/open/floor/iron/white, /area/awaymission/snowdin/post/minipost) "ju" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowdin, /obj/effect/turf_decal/stripes/line, /turf/open/floor/iron, /area/awaymission/snowdin/post/gateway) "jv" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowdin, /obj/effect/turf_decal/stripes/line, /obj/effect/decal/cleanable/dirt, /obj/structure/cable, @@ -2650,7 +2650,7 @@ /obj/structure/chair{ dir = 4 }, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowdin, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4, /turf/open/floor/iron, @@ -3523,7 +3523,7 @@ /obj/structure/chair{ dir = 8 }, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowdin, /obj/effect/decal/cleanable/dirt, /turf/open/floor/iron, /area/awaymission/snowdin/post/garage) @@ -7044,7 +7044,7 @@ }, /area/awaymission/snowdin/cave) "Be" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowdin, /obj/effect/turf_decal/tile/neutral/anticorner/contrasted{ dir = 8 }, @@ -7500,7 +7500,7 @@ /obj/structure/bed{ dir = 4 }, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/snowdin, /obj/item/bedsheet/nanotrasen{ dir = 4 }, diff --git a/_maps/RandomZLevels/undergroundoutpost45.dmm b/_maps/RandomZLevels/undergroundoutpost45.dmm index 7d8b1cd028d46..16cc8924aae9b 100644 --- a/_maps/RandomZLevels/undergroundoutpost45.dmm +++ b/_maps/RandomZLevels/undergroundoutpost45.dmm @@ -101,14 +101,14 @@ /turf/open/floor/iron/dark, /area/awaymission/undergroundoutpost45/central) "ax" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/underground, /turf/open/floor/iron, /area/awaymission/undergroundoutpost45/central) "ay" = ( /obj/structure/chair/comfy/beige{ dir = 4 }, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/underground, /turf/open/floor/iron/grimy, /area/awaymission/undergroundoutpost45/central) "az" = ( @@ -118,7 +118,7 @@ icon_state = "beacon"; name = "tracking beacon" }, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/underground, /obj/effect/turf_decal/tile/neutral/half/contrasted{ dir = 4 }, @@ -126,7 +126,7 @@ /area/awaymission/undergroundoutpost45/central) "aB" = ( /obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer2, -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/underground, /obj/effect/turf_decal/tile/neutral/half/contrasted{ dir = 4 }, @@ -218,7 +218,7 @@ /turf/open/floor/iron, /area/awaymission/undergroundoutpost45/central) "aT" = ( -/obj/effect/landmark/awaystart, +/obj/effect/landmark/awaystart/underground, /turf/open/floor/iron/grimy, /area/awaymission/undergroundoutpost45/central) "aU" = ( diff --git a/auxlua.dll b/auxlua.dll deleted file mode 100644 index 4f712c26d82ee..0000000000000 Binary files a/auxlua.dll and /dev/null differ diff --git a/code/__DEFINES/_flags.dm b/code/__DEFINES/_flags.dm index 55e706ce06d03..bf1f4a354bd4d 100644 --- a/code/__DEFINES/_flags.dm +++ b/code/__DEFINES/_flags.dm @@ -136,6 +136,8 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 #define VIRTUAL_SAFE_AREA (1<<16) /// This area does not allow the Binary channel #define BINARY_JAMMING (1<<17) +/// This area prevents Bag of Holding rifts from being opened. +#define NO_BOH (1<<18) /* These defines are used specifically with the atom/pass_flags bitmask diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm index 72159bde0540e..5a95d4f77d7d2 100644 --- a/code/__DEFINES/colors.dm +++ b/code/__DEFINES/colors.dm @@ -435,3 +435,8 @@ GLOBAL_LIST_INIT(cable_colors, list( )) #define HUSK_COLOR_TONE rgb(96, 88, 80) + +#define CM_COLOR_SAT_MIN 0.6 +#define CM_COLOR_SAT_MAX 0.7 +#define CM_COLOR_LUM_MIN 0.65 +#define CM_COLOR_LUM_MAX 0.75 diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm index 8be5b1fdb64aa..dfbfe68ad52cd 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm @@ -243,5 +243,8 @@ /// from /mob/proc/slip(): (knockdown_amonut, obj/slipped_on, lube_flags [mobs.dm], paralyze, force_drop) #define COMSIG_MOB_SLIPPED "mob_slipped" +/// From the base of /datum/component/callouts/proc/callout_picker(mob/user, atom/clicked_atom): (datum/callout_option/callout, atom/target) +#define COMSIG_MOB_CREATED_CALLOUT "mob_created_callout" + /// from /mob/proc/key_down(): (key, client/client, full_key) #define COMSIG_MOB_KEYDOWN "mob_key_down" diff --git a/code/__DEFINES/maps.dm b/code/__DEFINES/maps.dm index c76ba60911355..3c87195e99076 100644 --- a/code/__DEFINES/maps.dm +++ b/code/__DEFINES/maps.dm @@ -222,3 +222,13 @@ Always compile, always use that verb, and always make sure that it works for wha /// Checks the job changes in the map config for the passed change key. #define CHECK_MAP_JOB_CHANGE(job, change) SSmapping.config.job_changes?[job]?[change] + +///Identifiers for away mission spawnpoints +#define AWAYSTART_BEACH "AWAYSTART_BEACH" +#define AWAYSTART_MUSEUM "AWAYSTART_MUSEUM" +#define AWAYSTART_RESEARCH "AWAYSTART_RESEARCH" +#define AWAYSTART_CAVES "AWAYSTART_CAVES" +#define AWAYSTART_MOONOUTPOST "AWAYSTART_MOONOUTPOST" +#define AWAYSTART_SNOWCABIN "AWAYSTART_SNOWCABIN" +#define AWAYSTART_SNOWDIN "AWAYSTART_SNOWDIN" +#define AWAYSTART_UNDERGROUND "AWAYSTART_UNDERGROUND" diff --git a/code/__DEFINES/projectiles.dm b/code/__DEFINES/projectiles.dm index ed4c66b799c59..28b7b6f3d1be5 100644 --- a/code/__DEFINES/projectiles.dm +++ b/code/__DEFINES/projectiles.dm @@ -14,8 +14,10 @@ #define CALIBER_A7MM "a7mm" /// The caliber used by the [security auto-rifle][/obj/item/gun/ballistic/automatic/wt550]. #define CALIBER_46X30MM "4.6x30mm" -/// The caliber used by the Nanotrasen Saber SMG, PP-95 SMG and Type U3 Uzi. Also used as the default caliber for pistols but only the stechkin APS machine pistol doesn't override it. +/// The caliber used by the Nanotrasen Saber SMG and Type U3 Uzi. Also used as the default caliber for pistols but only the stechkin APS machine pistol doesn't override it. #define CALIBER_9MM "9mm" +/// The caliber used by smart SMG ammunition +#define CALIBER_160SMART ".160 Smart" /// The caliber used as the default for ballistic guns. Only not overridden for the [surplus rifle][/obj/item/gun/ballistic/automatic/surplus]. #define CALIBER_10MM "10mm" /// The caliber used by most revolver variants. diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 5293298457b08..3a47fe1ca0ae1 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -747,6 +747,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_GREAT_QUALITY_BAIT "great_quality_bait" /// Baits with this trait will ignore bait preferences and related fish traits. #define TRAIT_OMNI_BAIT "omni_bait" +/// The bait won't be consumed when used +#define TRAIT_BAIT_UNCONSUMABLE "bait_unconsumabe" /// Plants that were mutated as a result of passive instability, not a mutation threshold. #define TRAIT_PLANT_WILDMUTATE "wildmutation" /// If you hit an APC with exposed internals with this item it will try to shock you @@ -1214,11 +1216,13 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai ///Trait given to a turf that should not be allowed to be terraformed, such as turfs holding ore vents. #define TRAIT_NO_TERRAFORM "no_terraform" +///Mobs with these trait do not get italicized/quiet speech when speaking in low pressure +#define TRAIT_SPEECH_BOOSTER "speech_booster" + /// Given to a mob that can throw to make them not able to throw #define TRAIT_NO_THROWING "no_throwing" ///Trait which allows mobs to parry mining mob projectiles #define TRAIT_MINING_PARRYING "mining_parrying" - // END TRAIT DEFINES diff --git a/code/__HELPERS/_auxtools_api.dm b/code/__HELPERS/_auxtools_api.dm index 0117ded4c5195..a907be8ecf8fb 100644 --- a/code/__HELPERS/_auxtools_api.dm +++ b/code/__HELPERS/_auxtools_api.dm @@ -1,38 +1,3 @@ -#define AUXTOOLS_FULL_INIT 2 -#define AUXTOOLS_PARTIAL_INIT 1 - -GLOBAL_LIST_EMPTY(auxtools_initialized) -GLOBAL_PROTECT(auxtools_initialized) - -#define AUXTOOLS_CHECK(LIB)\ - if (!CONFIG_GET(flag/auxtools_enabled)) {\ - CRASH("Auxtools is not enabled in config!");\ - }\ - if (GLOB.auxtools_initialized[LIB] != AUXTOOLS_FULL_INIT) {\ - if (fexists(LIB)) {\ - var/string = call_ext(LIB,"auxtools_init")();\ - if(findtext(string, "SUCCESS")) {\ - GLOB.auxtools_initialized[LIB] = AUXTOOLS_FULL_INIT;\ - } else {\ - CRASH(string);\ - }\ - } else {\ - CRASH("No file named [LIB] found!")\ - }\ - }\ - -#define AUXTOOLS_SHUTDOWN(LIB)\ - if (GLOB.auxtools_initialized[LIB] == AUXTOOLS_FULL_INIT && fexists(LIB)){\ - call_ext(LIB,"auxtools_shutdown")();\ - GLOB.auxtools_initialized[LIB] = AUXTOOLS_PARTIAL_INIT;\ - }\ - -#define AUXTOOLS_FULL_SHUTDOWN(LIB)\ - if (GLOB.auxtools_initialized[LIB] && fexists(LIB)){\ - call_ext(LIB,"auxtools_full_shutdown")();\ - GLOB.auxtools_initialized[LIB] = FALSE;\ - } - /proc/auxtools_stack_trace(msg) CRASH(msg) diff --git a/code/__HELPERS/_dreamluau.dm b/code/__HELPERS/_dreamluau.dm new file mode 100644 index 0000000000000..76d38effd19a2 --- /dev/null +++ b/code/__HELPERS/_dreamluau.dm @@ -0,0 +1,295 @@ +#define DREAMLUAU (world.system_type == MS_WINDOWS ? "dreamluau.dll" : "libdreamluau.so") + +#define DREAMLUAU_CALL(func) call_ext(DREAMLUAU, "byond:[#func]") + +/** + * All of the following functions will return a string if the underlying rust code returns an error or a wrapped panic. + * The return values specified for each function are what they will return if successful. + */ + +/** + * As of 515.1631, byondapi does not provide direct access to `usr`. + * Use this function to pass `usr` into the dreamluau binary so that luau scripts can retrieve it. + * + * @return null on success + */ +#define DREAMLUAU_SET_USR DREAMLUAU_CALL(set_usr)(usr) + + +/** + * Sets the execution limit, in milliseconds. + * + * @param limit the new execution limit + * + * @return null on success + */ +#define DREAMLUAU_SET_EXECUTION_LIMIT_MILLIS(limit) DREAMLUAU_CALL(set_execution_limit_millis)((limit)) + +/** + * Sets the execution limit, in seconds. + * + * @param limit the new execution limit + * + * @return null on success + */ +#define DREAMLUAU_SET_EXECUTION_LIMIT_SECS(limit) DREAMLUAU_CALL(set_execution_limit_secs)((limit)) + +/** + * Clears the execution limit, allowing scripts to run as long as they need to. + * + * WARNING: This allows infinite loops to block Dream Daemon indefinitely, with no safety checks. + * Do not use this if you have no reason for scripts to run arbitrarily long. + * + * @return null on success + */ +#define DREAMLUAU_CLEAR_EXECUTION_LIMIT DREAMLUAU_CALL(clear_execution_limit) + +//Wrapper setters/clearers + +/** + * Set the wrapper for instancing new datums with `dm.new`. + * Clears it if the argument is null. + * If unset, the object will be instantiated using the default `new` instruction. + * + * The wrapper must be a proc with the signature `(type as path, list/arguments)`. + * + * @param wrapper the path to the proc to use as the new wrapper + * + * @return null on success + */ +#define DREAMLUAU_SET_NEW_WRAPPER(wrapper) DREAMLUAU_CALL(set_new_wrapper)((wrapper)) + +/** + * Set the wrapper for reading the vars of an object. + * Clears it if the argument is null. + * If unset, the var will be read directly, without any safety checks. + * + * The wrapper must be a proc with the signature `(target, var)`. + * + * @param wrapper the path to the proc to use as the new wrapper + * + * @return null on success + */ +#define DREAMLUAU_SET_VAR_GET_WRAPPER(wrapper) DREAMLUAU_CALL(set_var_get_wrapper)((wrapper)) + +/** + * Set the wrapper for writing the vars of an object. + * Clears it if the argument is null. + * If unset, the var will be modified directly, without any safety checks. + * + * The wrapper must be a proc with the signature `(target, var, value)`. + * + * @param wrapper the path to the proc to use as the new wrapper + * + * @return null on success + */ +#define DREAMLUAU_SET_VAR_SET_WRAPPER(wrapper) DREAMLUAU_CALL(set_var_set_wrapper)((wrapper)) + +/** + * Set the wrapper for calling a proc on an object. + * Clears it if the argument is null. + * If unset, the proc will be called directly, without any safety checks. + * + * The wrapper must be a proc with the signature `(target, procname as text, list/arguments)`. + * + * @param wrapper the path to the proc to use as the new wrapper + * + * @return null on success + */ +#define DREAMLUAU_SET_OBJECT_CALL_WRAPPER(wrapper) DREAMLUAU_CALL(set_object_call_wrapper)((wrapper)) + +/** + * Set the wrapper for calling a global proc. + * Clears it if the argument is null. + * If unset, the proc will be called directly, without any safety checks. + * + * The wrapper must be a proc with the signature `(procname as text, list/arguments)`. + * + * @param wrapper the path to the proc to use as the new wrapper + * + * @return null on success + */ +#define DREAMLUAU_SET_GLOBAL_CALL_WRAPPER(wrapper) DREAMLUAU_CALL(set_global_call_wrapper)((wrapper)) + +/** + * Set the wrapper for printing with the `print` function. + * Clears it if the argument is null. + * If unset, `print` will raise an error. + * + * The wrapper must be a proc with the signature `(list/arguments)`. + * + * @param wrapper the path to the proc to use as the new wrapper + * + * @return null on success + */ +#define DREAMLUAU_SET_PRINT_WRAPPER(wrapper) DREAMLUAU_CALL(set_print_wrapper)((wrapper)) + + + +/** + * Create a new luau state. + * + * @return a handle to the created state. + */ +#define DREAMLUAU_NEW_STATE DREAMLUAU_CALL(new_state) + +/** + * Some of the following functions return values that cannot be cleanly converted from luau to DM. + * To account for this, these functions also return a list of variant specifiers, equivalent to + * an array of objects of the type described beloe: + * ``` + * type Variants = { + * key?: "error"|Array + * value?: "error"|Array + * } + * ``` + */ + +/** + * The following 4 functions execute luau code and return + * an associative list containing information about the result. + * This list has the following params. + * + * - "status": either "finished", "sleep", "yield", or "error" + * - "return_values": if "status" is "finished" or "yield", contains a list of the return values + * - "variants": a list of variant specifiers for the "return_values" param + * - "message": if "status" is "error", contains the error message + * - "name": the name of the executed code, according to the `what` field of `debug.getinfo` + */ + +/** + * Load and execute a luau script. + * + * @param state the handle to the state + * @param code the source code of the script to run + * @param name an optional name to give to the script, for debugging purposes + * + * @return an associative list containing result information as specified above + */ +#define DREAMLUAU_LOAD DREAMLUAU_CALL(load) + +/** + * Awaken the thread at the front of the specified state's sleeping thread queue. + * + * @param state the handle to the state + * + * @return an associative list containing result information as specified above + */ +#define DREAMLUAU_AWAKEN(state) DREAMLUAU_CALL(awaken)((state)) + +/** + * Resume one of the state's yielded threads. + * + * @param state the handle to the state + * @param index the index of the thread in the state's yielded threads list + * @param ...arguments arguments that will be returned by the `coroutine.yield` that yielded the thread + * + * @return an associative list containing result information as specified above + */ +#define DREAMLUAU_RESUME DREAMLUAU_CALL(resume) + +/** + * Call a function accessible from the global table. + * + * @param state the handle to the state + * @param function a list of nested indices from the global table to the specified function + * @param ...arguments arguments to pass to the function + * + * @return an associative list containing result information as specified above + */ +#define DREAMLUAU_CALL_FUNCTION DREAMLUAU_CALL(call_function) + +// State information collection functions + +/** + * Obtain a copy of the state's global table, converted to DM. + * + * @param state the handle to the state + * + * @return an associative list with the follwing entries: + * - "values": The actual values of the global table + * - "variants": Variant specifiers for "values" + */ +#define DREAMLUAU_GET_GLOBALS(state) DREAMLUAU_CALL(get_globals)((state)) + +/** + * List the names of all sleeping or yielded threads for the state. + * + * @param state the handle to the state + * + * @return an associative list with the following entries: + * - "sleeps": A list of sleeping threads + * - "yields": A list of yielded threads + */ +#define DREAMLUAU_LIST_THREADS(state) DREAMLUAU_CALL(list_threads)((state)) + +// Cleanup functions + +/** + * Run garbage collection on the state. + * + * This may be necessary to prevent hanging references, as some + * hard references may persist in unreachable luau objects that + * would be collected after a garbage collection cycle or two. + * + * @param state the handle to the state + * + * @return null on success + */ +#define DREAMLUAU_COLLECT_GARBAGE(state) DREAMLUAU_CALL(collect_garbage)((state)) + +/** + * Remove a sleeping thread from the sleep queue, without executing it. + * + * @param state the handle to the state + * @param thread the index in the sleep queue to the target thread + * + * @return null on success + */ +#define DREAMLUAU_KILL_SLEEPING_THREAD(state, thread) DREAMLUAU_CALL(kill_sleeping_thread)((state), (thread)) + +/** + * Remove a yielded thread from the yield table, without executing it. + * + * @param state the handle to the state + * @param thread the index in the yield table to the target thread + * + * @return null on success + */ +#define DREAMLUAU_KILL_YIELDED_THREAD(state, thread) DREAMLUAU_CALL(kill_yielded_thread)((state), (thread)) + +/** + * Delete a state. The state's handle will be freed for any new states created afterwards. + * + * @param state the handle to the state + * + * @return null on success + */ +#define DREAMLUAU_KILL_STATE(state) DREAMLUAU_CALL(kill_state)((state)) + +/** + * Retrieve lua traceback info, containing every lua stack frame between the lua entrypoint and the re-entry to dm code. + * + * @param level the level of lua execution to get the traceback for, + * with 1 being the lua code that executed the dm code that called this function, + * 2 being the lua code that executed the dm code that executed the lua code + * that executed the dm code that called this function, etc. + * + * @return the callstack of the specified lua level if valid, null if invalid + */ +#define DREAMLUAU_GET_TRACEBACK(index) DREAMLUAU_CALL(get_traceback)((index)) + +/** + * Luau userdata corresponding to a ref-counted DM type counts as a hard reference for BYOND's garbage collector. + * If you need to delete a DM object, and you cannot be certain that there are no references to it in any luau state, + * call this function before deleting that object to disassociate it from any userdata in any luau state. + * + * Hard deleting an object without clearing userdata corresponding to it leaves the userdata to become associated with + * the next DM object to receive the old object's reference ID, which may be undesirable behavior. + * + * @param object the object to disassociate from userdata. + * + * @return null on success + */ +#define DREAMLUAU_CLEAR_REF_USERDATA(object) DREAMLUAU_CALL(clear_ref_userdata)((object)) + diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index 157a17012a883..f8a0dc6d7d551 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -978,53 +978,57 @@ else return element -/// Returns a copy of the list where any element that is a datum or the world is converted into a ref -/proc/refify_list(list/target_list, list/visited, path_accumulator = "list") +/** + * Intermediate step for preparing lists to be passed into the lua editor tgui. + * Resolves weakrefs, converts some values without a standard textual representation to text, + * and can handle self-referential lists and potential duplicate output keys. + */ +/proc/prepare_lua_editor_list(list/target_list, list/visited) if(!visited) visited = list() var/list/ret = list() - visited[target_list] = path_accumulator + visited[target_list] = ret + var/list/duplicate_keys = list() for(var/i in 1 to target_list.len) var/key = target_list[i] var/new_key = key if(isweakref(key)) var/datum/weakref/ref = key - var/resolved = ref.resolve() - if(resolved) - new_key = "[resolved] [REF(resolved)]" - else - new_key = "null weakref [REF(key)]" - else if(isdatum(key)) - new_key = "[key] [REF(key)]" + new_key = ref.resolve() || "null weakref" else if(key == world) - new_key = "world [REF(world)]" + new_key = world.name + else if(ref(key) == "\[0xe000001\]") + new_key = "global" else if(islist(key)) - if(visited.Find(key)) + if(visited[key]) new_key = visited[key] else - new_key = refify_list(key, visited, path_accumulator + "\[[i]\]") + new_key = prepare_lua_editor_list(key, visited) var/value - if(istext(key) || islist(key) || ispath(key) || isdatum(key) || key == world) + if(!isnull(key) && !isnum(key)) value = target_list[key] if(isweakref(value)) var/datum/weakref/ref = value - var/resolved = ref.resolve() - if(resolved) - value = "[resolved] [REF(resolved)]" - else - value = "null weakref [REF(key)]" - else if(isdatum(value)) - value = "[value] [REF(value)]" - else if(value == world) - value = "world [REF(world)]" + value = ref.resolve() || "null weakref" + if(value == world) + value = "world" + else if(ref(value) == "\[0xe000001\]") + value = "global" else if(islist(value)) - if(visited.Find(value)) + if(visited[value]) value = visited[value] else - value = refify_list(value, visited, path_accumulator + "\[[key]\]") - var/list/to_add = list(new_key) - if(value) - to_add[new_key] = value + value = prepare_lua_editor_list(value, visited) + var/list/to_add = list() + if(!isnull(value)) + var/final_key = new_key + while(duplicate_keys[final_key]) + duplicate_keys[new_key]++ + final_key = "[new_key] ([duplicate_keys[new_key]])" + duplicate_keys[final_key] = 1 + to_add[final_key] = value + else + to_add += list(new_key) ret += to_add if(i < target_list.len) CHECK_TICK @@ -1033,29 +1037,31 @@ /** * Converts a list into a list of assoc lists of the form ("key" = key, "value" = value) * so that list keys that are themselves lists can be fully json-encoded + * and that unique objects with the same string representation do not + * produce duplicate keys that are clobbered by the standard JavaScript JSON.parse function */ -/proc/kvpify_list(list/target_list, depth = INFINITY, list/visited, path_accumulator = "list") +/proc/kvpify_list(list/target_list, depth = INFINITY, list/visited) if(!visited) visited = list() var/list/ret = list() - visited[target_list] = path_accumulator + visited[target_list] = ret for(var/i in 1 to target_list.len) var/key = target_list[i] var/new_key = key if(islist(key) && depth) - if(visited.Find(key)) + if(visited[key]) new_key = visited[key] else - new_key = kvpify_list(key, depth-1, visited, path_accumulator + "\[[i]\]") + new_key = kvpify_list(key, depth-1, visited) var/value - if(istext(key) || islist(key) || ispath(key) || isdatum(key) || key == world) + if(!isnull(key) && !isnum(key)) value = target_list[key] if(islist(value) && depth) - if(visited.Find(value)) + if(visited[value]) value = visited[value] else - value = kvpify_list(value, depth-1, visited, path_accumulator + "\[[key]\]") - if(value) + value = kvpify_list(value, depth-1, visited) + if(!isnull(value)) ret += list(list("key" = new_key, "value" = value)) else ret += list(list("key" = i, "value" = new_key)) @@ -1065,12 +1071,12 @@ /// Compares 2 lists, returns TRUE if they are the same /proc/deep_compare_list(list/list_1, list/list_2) - if(!islist(list_1) || !islist(list_2)) - return FALSE - if(list_1 == list_2) return TRUE + if(!islist(list_1) || !islist(list_2)) + return FALSE + if(list_1.len != list_2.len) return FALSE @@ -1093,11 +1099,11 @@ return TRUE /// Returns a copy of the list where any element that is a datum is converted into a weakref -/proc/weakrefify_list(list/target_list, list/visited, path_accumulator = "list") +/proc/weakrefify_list(list/target_list, list/visited) if(!visited) visited = list() var/list/ret = list() - visited[target_list] = path_accumulator + visited[target_list] = ret for(var/i in 1 to target_list.len) var/key = target_list[i] var/new_key = key @@ -1107,62 +1113,19 @@ if(visited.Find(key)) new_key = visited[key] else - new_key = weakrefify_list(key, visited, path_accumulator + "\[[i]\]") + new_key = weakrefify_list(key, visited) var/value - if(istext(key) || islist(key) || ispath(key) || isdatum(key) || key == world) + if(!isnull(key) && !isnum(key)) value = target_list[key] if(isdatum(value)) value = WEAKREF(value) else if(islist(value)) - if(visited.Find(value)) + if(visited[value]) value = visited[value] else - value = weakrefify_list(value, visited, path_accumulator + "\[[key]\]") - var/list/to_add = list(new_key) - if(value) - to_add[new_key] = value - ret += to_add - if(i < target_list.len) - CHECK_TICK - return ret - -/// Returns a copy of a list where text values (except assoc-keys and string representations of lua-only values) are -/// wrapped in quotes and existing quote marks are escaped, -/// and nulls are replaced with the string "null" -/proc/encode_text_and_nulls(list/target_list, list/visited) - var/static/regex/lua_reference_regex - if(!lua_reference_regex) - lua_reference_regex = regex(@"^((function)|(table)|(thread)|(userdata)): 0x[0-9a-fA-F]+$") - if(!visited) - visited = list() - var/list/ret = list() - visited[target_list] = TRUE - for(var/i in 1 to target_list.len) - var/key = target_list[i] - var/new_key = key - if(istext(key) && !target_list[key] && !lua_reference_regex.Find(key)) - new_key = "\"[replacetext(key, "\"", "\\\"")]\"" - else if(islist(key)) - var/found_index = visited.Find(key) - if(found_index) - new_key = visited[found_index] - else - new_key = encode_text_and_nulls(key, visited) - else if(isnull(key)) - new_key = "null" - var/value - if(istext(key) || islist(key) || ispath(key) || isdatum(key) || key == world) - value = target_list[key] - if(istext(value) && !lua_reference_regex.Find(value)) - value = "\"[replacetext(value, "\"", "\\\"")]\"" - else if(islist(value)) - var/found_index = visited.Find(value) - if(found_index) - value = visited[found_index] - else - value = encode_text_and_nulls(value, visited) + value = weakrefify_list(value, visited) var/list/to_add = list(new_key) - if(value) + if(!isnull(value)) to_add[new_key] = value ret += to_add if(i < target_list.len) @@ -1188,3 +1151,155 @@ if("x" in coords) return locate(coords["x"], coords["y"], coords["z"]) return locate(coords[1], coords[2], coords[3]) + +/** + * Given a list and a list of its variant hints, appends variants that aren't explicitly required by dreamluau, + * but are required by the lua editor tgui. + */ +/proc/add_lua_editor_variants(list/values, list/variants, list/visited, path = "") + if(!islist(visited)) + visited = list() + visited[values] = "\[\]" + if(!islist(values) || !islist(variants)) + return + if(values.len != variants.len) + CRASH("values and variants must be the same length") + for(var/i in 1 to variants.len) + var/pair = variants[i] + var/pair_modified = FALSE + if(isnull(pair)) + pair = list("key", "value") + var/key = values[i] + if(islist(key)) + if(visited[key]) + pair["key"] = list("cycle", visited[key]) + else + var/list/key_variants = pair["key"] + var/new_path = path + "\[[i], \"key\"\]," + visited[key] = new_path + add_lua_editor_variants(key, key_variants, visited, new_path) + visited -= key + pair["key"] = list("list", key_variants) + pair_modified = TRUE + else if(isdatum(key) || key == world || ref(key) == "\[0xe000001\]") + pair["key"] = list("ref", ref(key)) + pair_modified = TRUE + var/value + if(!isnull(key) && !isnum(key)) + value = values[key] + if(islist(value)) + if(visited[value]) + pair["value"] = list("cycle", visited[value]) + else + var/list/value_variants = pair["value"] + var/new_path = path + "\[[i], \"value\"\]," + visited[value] = new_path + add_lua_editor_variants(value, value_variants, visited, new_path) + visited -= value + pair["value"] = list("list", value_variants) + pair_modified = TRUE + else if(isdatum(value) || value == world || ref(value) == "\[0xe000001\]") + pair["value"] = list("ref", ref(value)) + pair_modified = TRUE + if(pair_modified && pair != variants[i]) + variants[i] = pair + if(i < variants.len) + CHECK_TICK + +/proc/add_lua_return_value_variants(list/values, list/variants) + if(!islist(values) || !islist(variants)) + return + if(values.len != variants.len) + CRASH("values and variants must be the same length") + for(var/i in 1 to values.len) + var/value = values[i] + if(islist(value)) + add_lua_editor_variants(value, variants[i]) + else if(isdatum(value) || value == world || ref(value) == "\[0xe000001\]") + variants[i] = list("ref", ref(value)) + +/proc/deep_copy_without_cycles(list/values, list/visited) + if(!islist(visited)) + visited = list() + if(!islist(values)) + return values + var/list/ret = list() + var/cycle_count = 0 + visited[values] = TRUE + for(var/i in 1 to values.len) + var/key = values[i] + var/out_key = key + if(islist(key)) + if(visited[key]) + do + out_key = "\[cyclical reference[cycle_count ? " (i)" : ""]\]" + cycle_count++ + while(values.Find(out_key)) + else + visited[key] = TRUE + out_key = deep_copy_without_cycles(key, visited) + visited -= key + var/value + if(!isnull(key) && !isnum(key)) + value = values[key] + var/out_value = value + if(islist(value)) + if(visited[value]) + out_value = "\[cyclical reference\]" + else + visited[value] = TRUE + out_value = deep_copy_without_cycles(value, visited) + visited -= value + var/list/to_add = list(out_key) + if(!isnull(out_value)) + to_add[out_key] = out_value + ret += to_add + if(i < values.len) + CHECK_TICK + return ret + +/** + * Given a list and a list of its variant hints, removes any list key/values that are represent lua values that could not be directly converted to DM. + */ +/proc/remove_non_dm_variants(list/return_values, list/variants, list/visited) + if(!islist(visited)) + visited = list() + if(!islist(return_values) || !islist(variants) || visited[return_values]) + return + visited[return_values] = TRUE + if(return_values.len != variants.len) + CRASH("return_values and variants must be the same length") + for(var/i in 1 to variants.len) + var/pair = variants[i] + if(!islist(variants)) + continue + var/key = return_values[i] + if(pair["key"]) + if(!islist(pair["key"])) + return_values[i] = null + continue + remove_non_dm_variants(key, pair["key"], visited) + if(pair["value"]) + if(!islist(pair["value"])) + return_values[key] = null + continue + remove_non_dm_variants(return_values[key], pair["value"], visited) + +/proc/compare_lua_logs(list/log_1, list/log_2) + if(log_1 == log_2) + return TRUE + for(var/field in list("status", "name", "message", "chunk")) + if(log_1[field] != log_2[field]) + return FALSE + switch(log_1["status"]) + if("finished", "yield") + return deep_compare_list( + recursive_list_resolve(log_1["return_values"]), + recursive_list_resolve(log_2["return_values"]) + ) && deep_compare_list(log_1["variants"], log_2["variants"]) + if("runtime") + return log_1["file"] == log_2["file"]\ + && log_1["line"] == log_2["line"]\ + && deep_compare_list(log_1["stack"], log_2["stack"]) + else + return TRUE diff --git a/code/__HELPERS/colors.dm b/code/__HELPERS/colors.dm index 9c70cef798eac..3a20e5ad60c09 100644 --- a/code/__HELPERS/colors.dm +++ b/code/__HELPERS/colors.dm @@ -101,5 +101,50 @@ return output +/** + * Gets a color for a name, will return the same color for a given string consistently within a round.atom + * + * Note that this proc aims to produce pastel-ish colors using the HSL colorspace. These seem to be favorable for displaying on the map. + * + * Arguments: + * * name - The name to generate a color for + * * sat_shift - A value between 0 and 1 that will be multiplied against the saturation + * * lum_shift - A value between 0 and 1 that will be multiplied against the luminescence + */ +/proc/colorize_string(name, sat_shift = 1, lum_shift = 1) + // seed to help randomness + var/static/rseed = rand(1,26) + + // get hsl using the selected 6 characters of the md5 hash + var/hash = copytext(md5(name + GLOB.round_id), rseed, rseed + 6) + var/h = hex2num(copytext(hash, 1, 3)) * (360 / 255) + var/s = (hex2num(copytext(hash, 3, 5)) >> 2) * ((CM_COLOR_SAT_MAX - CM_COLOR_SAT_MIN) / 63) + CM_COLOR_SAT_MIN + var/l = (hex2num(copytext(hash, 5, 7)) >> 2) * ((CM_COLOR_LUM_MAX - CM_COLOR_LUM_MIN) / 63) + CM_COLOR_LUM_MIN + + // adjust for shifts + s = clamp(s * sat_shift, 0, 1) + l = clamp(l * lum_shift, 0, 1) + + // convert to rgb + var/h_int = round(h/60) // mapping each section of H to 60 degree sections + var/c = (1 - abs(2 * l - 1)) * s + var/x = c * (1 - abs((h / 60) % 2 - 1)) + var/m = l - c * 0.5 + x = (x + m) * 255 + c = (c + m) * 255 + m *= 255 + switch(h_int) + if(0) + return "#[num2hex(c, 2)][num2hex(x, 2)][num2hex(m, 2)]" + if(1) + return "#[num2hex(x, 2)][num2hex(c, 2)][num2hex(m, 2)]" + if(2) + return "#[num2hex(m, 2)][num2hex(c, 2)][num2hex(x, 2)]" + if(3) + return "#[num2hex(m, 2)][num2hex(x, 2)][num2hex(c, 2)]" + if(4) + return "#[num2hex(x, 2)][num2hex(m, 2)][num2hex(c, 2)]" + if(5) + return "#[num2hex(c, 2)][num2hex(m, 2)][num2hex(x, 2)]" #define RANDOM_COLOUR (rgb(rand(0,255),rand(0,255),rand(0,255))) diff --git a/code/__HELPERS/spawns.dm b/code/__HELPERS/spawns.dm index 2c93e4ba19e56..c72c5a555de5a 100644 --- a/code/__HELPERS/spawns.dm +++ b/code/__HELPERS/spawns.dm @@ -29,7 +29,13 @@ if(!ispath(path)) path.forceMove(pod) else - path = new path(pod) + var/amount_to_spawn = paths_to_spawn[path] || 1 + if(!isnum(amount_to_spawn)) + stack_trace("amount to spawn for path \"[path]\" is not a number, defaulting to 1") + amount_to_spawn = 1 + + for(var/item_number in 1 to amount_to_spawn) + new path(pod) //remove non var edits from specifications specifications -= "target" diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index 3d9bab22285f2..50f7cded126ee 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -1228,6 +1228,13 @@ GLOBAL_LIST_INIT(binary, list("0","1")) var/input_length = LAZYLEN(ending) return !!findtext(input_text, ending, -input_length) +/// Returns TRUE if the input_text starts with any of the beginnings +/proc/starts_with_any(input_text, list/beginnings) + for(var/beginning in beginnings) + if(!!findtext(input_text, beginning, 1, LAZYLEN(beginning)+1)) + return TRUE + return FALSE + /// Generate a grawlix string of length of the text argument. /proc/grawlix(text) var/grawlix = "" diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index ea1b16d71cef8..4c1518252e3a5 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -64,6 +64,7 @@ DEFINE_BITFIELD(area_flags, list( "UNIQUE_AREA" = UNIQUE_AREA, "VALID_TERRITORY" = VALID_TERRITORY, "XENOBIOLOGY_COMPATIBLE" = XENOBIOLOGY_COMPATIBLE, + "NO_BOH" = NO_BOH, )) DEFINE_BITFIELD(turf_flags, list( diff --git a/code/_globalvars/lists/ambience.dm b/code/_globalvars/lists/ambience.dm index 6dd4935817fea..12a389cf081f8 100644 --- a/code/_globalvars/lists/ambience.dm +++ b/code/_globalvars/lists/ambience.dm @@ -127,6 +127,11 @@ GLOBAL_LIST_INIT(maint_ambience,list( 'sound/ambience/ambimaint5.ogg', 'sound/ambience/ambimaint6.ogg', 'sound/ambience/ambimaint7.ogg', + 'sound/ambience/ambimaint8.ogg', + 'sound/ambience/ambimaint9.ogg', + 'sound/ambience/ambimaint10.ogg', + 'sound/ambience/ambimaint11.ogg', + 'sound/ambience/ambimaint12.ogg', 'sound/ambience/ambitech2.ogg', 'sound/voice/lowHiss1.ogg', 'sound/voice/lowHiss2.ogg', diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index d187593220ca0..3c831c4a68140 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -529,10 +529,12 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_XENO_IMMUNE" = TRAIT_XENO_IMMUNE, "TRAIT_XRAY_HEARING" = TRAIT_XRAY_HEARING, "TRAIT_XRAY_VISION" = TRAIT_XRAY_VISION, + "TRAIT_SPEECH_BOOSTER" = TRAIT_SPEECH_BOOSTER, "TRAIT_MINING_PARRYING" = TRAIT_MINING_PARRYING, ), /obj/item = list( "TRAIT_APC_SHOCKING" = TRAIT_APC_SHOCKING, + "TRAIT_BAIT_UNCONSUMABLE" = TRAIT_BAIT_UNCONSUMABLE, "TRAIT_BAKEABLE" = TRAIT_BAKEABLE, "TRAIT_BASIC_QUALITY_BAIT" = TRAIT_BASIC_QUALITY_BAIT, "TRAIT_BLIND_TOOL" = TRAIT_BLIND_TOOL, diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm index 6c7377c382543..dc425083ad896 100644 --- a/code/_onclick/hud/radial.dm +++ b/code/_onclick/hud/radial.dm @@ -7,6 +7,7 @@ GLOBAL_LIST_EMPTY(radial_menus) icon = 'icons/hud/radial.dmi' plane = ABOVE_HUD_PLANE vis_flags = VIS_INHERIT_PLANE + var/click_on_hover = FALSE var/datum/radial_menu/parent /atom/movable/screen/radial/proc/set_parent(new_value) @@ -39,6 +40,8 @@ GLOBAL_LIST_EMPTY(radial_menus) icon_state = "[parent.radial_slice_icon]_focus" if(tooltips) openToolTip(usr, src, params, title = name) + if (click_on_hover && !isnull(usr) && !isnull(parent)) + Click(location, control, params) /atom/movable/screen/radial/slice/MouseExited(location, control, params) . = ..() @@ -146,7 +149,7 @@ GLOBAL_LIST_EMPTY(radial_menus) starting_angle = 180 ending_angle = 45 -/datum/radial_menu/proc/setup_menu(use_tooltips, set_page = 1) +/datum/radial_menu/proc/setup_menu(use_tooltips, set_page = 1, click_on_hover = FALSE) if(ending_angle > starting_angle) zone = ending_angle - starting_angle else @@ -183,18 +186,26 @@ GLOBAL_LIST_EMPTY(radial_menus) page_data[page] = current pages = page current_page = clamp(set_page, 1, pages) - update_screen_objects(anim = entry_animation) + update_screen_objects(entry_animation, click_on_hover) -/datum/radial_menu/proc/update_screen_objects(anim = FALSE) +/datum/radial_menu/proc/update_screen_objects(anim = FALSE, click_on_hover = FALSE) var/list/page_choices = page_data[current_page] var/angle_per_element = round(zone / page_choices.len) for(var/i in 1 to elements.len) - var/atom/movable/screen/radial/E = elements[i] + var/atom/movable/screen/radial/element = elements[i] var/angle = WRAP(starting_angle + (i - 1) * angle_per_element,0,360) if(i > page_choices.len) - HideElement(E) + HideElement(element) + element.click_on_hover = FALSE else - SetElement(E,page_choices[i],angle,anim = anim,anim_order = i) + SetElement(element,page_choices[i],angle,anim = anim,anim_order = i) + // Only activate click on hover after the animation plays + if (!click_on_hover) + continue + if (anim) + addtimer(VARSET_CALLBACK(element, click_on_hover, TRUE), i * 0.5) + else + element.click_on_hover = TRUE /datum/radial_menu/proc/HideElement(atom/movable/screen/radial/slice/E) E.cut_overlays() @@ -272,7 +283,7 @@ GLOBAL_LIST_EMPTY(radial_menus) /datum/radial_menu/proc/get_next_id() return "c_[choices.len]" -/datum/radial_menu/proc/set_choices(list/new_choices, use_tooltips, set_page = 1) +/datum/radial_menu/proc/set_choices(list/new_choices, use_tooltips, click_on_hover = FALSE, set_page = 1) if(choices.len) Reset() for(var/E in new_choices) @@ -286,7 +297,7 @@ GLOBAL_LIST_EMPTY(radial_menus) if (istype(new_choices[E], /datum/radial_menu_choice)) choice_datums[id] = new_choices[E] - setup_menu(use_tooltips, set_page) + setup_menu(use_tooltips, set_page, click_on_hover) /datum/radial_menu/proc/extract_image(to_extract_from) if (istype(to_extract_from, /datum/radial_menu_choice)) @@ -345,7 +356,7 @@ GLOBAL_LIST_EMPTY(radial_menus) Choices should be a list where list keys are movables or text used for element names and return value and list values are movables/icons/images used for element icons */ -/proc/show_radial_menu(mob/user, atom/anchor, list/choices, uniqueid, radius, datum/callback/custom_check, require_near = FALSE, tooltips = FALSE, no_repeat_close = FALSE, radial_slice_icon = "radial_slice", autopick_single_option = TRUE) +/proc/show_radial_menu(mob/user, atom/anchor, list/choices, uniqueid, radius, datum/callback/custom_check, require_near = FALSE, tooltips = FALSE, no_repeat_close = FALSE, radial_slice_icon = "radial_slice", autopick_single_option = TRUE, entry_animation = TRUE, click_on_hover = FALSE) if(!user || !anchor || !length(choices)) return @@ -362,6 +373,7 @@ GLOBAL_LIST_EMPTY(radial_menus) return var/datum/radial_menu/menu = new + menu.entry_animation = entry_animation GLOB.radial_menus[uniqueid] = menu if(radius) menu.radius = radius @@ -370,7 +382,7 @@ GLOBAL_LIST_EMPTY(radial_menus) menu.anchor = anchor menu.radial_slice_icon = radial_slice_icon menu.check_screen_border(user) //Do what's needed to make it look good near borders or on hud - menu.set_choices(choices, tooltips) + menu.set_choices(choices, tooltips, click_on_hover) menu.show_to(user) menu.wait(user, anchor, require_near) var/answer = menu.selected_choice diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm index 05489241c6a0b..72df7ceeb9777 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -266,7 +266,7 @@ /datum/config_entry/flag/roundstart_away //Will random away mission be loaded. /datum/config_entry/number/gateway_delay //How long the gateway takes before it activates. Default is half an hour. Only matters if roundstart_away is enabled. - default = 18000 + default = 30 MINUTES integer = FALSE min_val = 0 @@ -275,6 +275,16 @@ min_val = 0 max_val = 100 +///An override to gateway_delay for specific maps or start points +/datum/config_entry/keyed_list/gateway_delays_by_id + default = list( + AWAYSTART_BEACH = 5 MINUTES, //Chill RP zone + AWAYSTART_MUSEUM = 12 MINUTES, //Chill place with some cool puzzles and effects. + ) + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_NUM + lowercase_key = FALSE //The macros are written the exact same way as their values, only without the quotation marks. + /datum/config_entry/flag/ghost_interaction /datum/config_entry/flag/near_death_experience //If carbons can hear ghosts when unconscious and very close to death diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm index 706b4e51d690f..ad1b9e4132fed 100644 --- a/code/controllers/subsystem/garbage.dm +++ b/code/controllers/subsystem/garbage.dm @@ -346,6 +346,7 @@ SUBSYSTEM_DEF(garbage) /// Datums passed to this will be given a chance to clean up references to allow the GC to collect them. /proc/qdel(datum/to_delete, force = FALSE) if(!istype(to_delete)) + DREAMLUAU_CLEAR_REF_USERDATA(to_delete) del(to_delete) return diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm index 697beaee19b39..b1629237dc6bf 100644 --- a/code/controllers/subsystem/job.dm +++ b/code/controllers/subsystem/job.dm @@ -741,9 +741,11 @@ SUBSYSTEM_DEF(job) if(!spawn_turf) SendToLateJoin(living_mob) else - var/obj/structure/closet/supplypod/centcompod/toLaunch = new() - living_mob.forceMove(toLaunch) - new /obj/effect/pod_landingzone(spawn_turf, toLaunch) + podspawn(list( + "target" = spawn_turf, + "path" = /obj/structure/closet/supplypod/centcompod, + "spawn" = living_mob + )) /// Returns a list of minds of all heads of staff who are alive /datum/controller/subsystem/job/proc/get_living_heads() diff --git a/code/controllers/subsystem/lighting.dm b/code/controllers/subsystem/lighting.dm index 59ff294e959a2..24d871d2f09c4 100644 --- a/code/controllers/subsystem/lighting.dm +++ b/code/controllers/subsystem/lighting.dm @@ -26,6 +26,19 @@ SUBSYSTEM_DEF(lighting) return SS_INIT_SUCCESS + +/datum/controller/subsystem/lighting/proc/create_all_lighting_objects() + for(var/area/area as anything in GLOB.areas) + if(!area.static_lighting) + continue + for (var/list/zlevel_turfs as anything in area.get_zlevel_turf_lists()) + for(var/turf/area_turf as anything in zlevel_turfs) + if(area_turf.space_lit) + continue + new /datum/lighting_object(area_turf) + CHECK_TICK + CHECK_TICK + /datum/controller/subsystem/lighting/fire(resumed, init_tick_checks) MC_SPLIT_TICK_INIT(3) if(!init_tick_checks) diff --git a/code/controllers/subsystem/lua.dm b/code/controllers/subsystem/lua.dm index 1ab88a01746b7..99df8cf335490 100644 --- a/code/controllers/subsystem/lua.dm +++ b/code/controllers/subsystem/lua.dm @@ -2,7 +2,6 @@ SUBSYSTEM_DEF(lua) name = "Lua Scripting" runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT wait = 0.1 SECONDS - flags = SS_OK_TO_FAIL_INIT /// A list of all lua states var/list/datum/lua_state/states = list() @@ -18,31 +17,19 @@ SUBSYSTEM_DEF(lua) var/list/current_run = list() var/list/current_states_run = list() - /// Protects return values from getting GCed before getting converted to lua values - /// Gets cleared every tick. - var/list/gc_guard = list() + var/list/needs_gc_cycle = list() /datum/controller/subsystem/lua/Initialize() - if(!CONFIG_GET(flag/auxtools_enabled)) - warning("SSlua requires auxtools to be enabled to run.") - return SS_INIT_NO_NEED - - try - // Initialize the auxtools library - AUXTOOLS_CHECK(AUXLUA) - - // Set the wrappers for setting vars and calling procs - __lua_set_set_var_wrapper("/proc/wrap_lua_set_var") - __lua_set_datum_proc_call_wrapper("/proc/wrap_lua_datum_proc_call") - __lua_set_global_proc_call_wrapper("/proc/wrap_lua_global_proc_call") - __lua_set_print_wrapper("/proc/wrap_lua_print") - return SS_INIT_SUCCESS - catch(var/exception/e) - // Something went wrong, best not allow the subsystem to run - var/crash_message = "Error initializing SSlua: [e.name]" - initialization_failure_message = crash_message - warning(crash_message) - return SS_INIT_FAILURE + DREAMLUAU_SET_EXECUTION_LIMIT_SECS(5) + // Set wrappers to ensure that lua scripts are subject to the same safety restrictions as other admin tooling + DREAMLUAU_SET_NEW_WRAPPER("/proc/_new") + DREAMLUAU_SET_VAR_GET_WRAPPER("/proc/wrap_lua_get_var") + DREAMLUAU_SET_VAR_SET_WRAPPER("/proc/wrap_lua_set_var") + DREAMLUAU_SET_OBJECT_CALL_WRAPPER("/proc/wrap_lua_datum_proc_call") + DREAMLUAU_SET_GLOBAL_CALL_WRAPPER("/proc/wrap_lua_global_proc_call") + // Set the print wrapper, as otherwise, the print function is meaningless + DREAMLUAU_SET_PRINT_WRAPPER("/proc/wrap_lua_print") + return SS_INIT_SUCCESS /datum/controller/subsystem/lua/OnConfigLoad() // Read the paths from the config file @@ -52,9 +39,6 @@ SUBSYSTEM_DEF(lua) lua_path += path world.SetConfig("env", "LUAU_PATH", jointext(lua_path, ";")) -/datum/controller/subsystem/lua/Shutdown() - AUXTOOLS_FULL_SHUTDOWN(AUXLUA) - /datum/controller/subsystem/lua/proc/queue_resume(datum/lua_state/state, index, arguments) if(!initialized) return @@ -64,36 +48,33 @@ SUBSYSTEM_DEF(lua) arguments = list() else if(!islist(arguments)) arguments = list(arguments) + else + var/list/args_list = arguments + arguments = args_list.Copy() resumes += list(list("state" = state, "index" = index, "arguments" = arguments)) -/datum/controller/subsystem/lua/proc/kill_task(datum/lua_state/state, list/task_info) +/datum/controller/subsystem/lua/proc/kill_task(datum/lua_state/state, is_sleep, index) if(!istype(state)) return - if(!islist(task_info)) - return - if(!(istext(task_info["name"]) && istext(task_info["status"]) && isnum(task_info["index"]))) - return - switch(task_info["status"]) - if("sleep") - var/task_index = task_info["index"] - var/state_index = 1 - - // Get the nth sleep in the sleep list corresponding to the target state - for(var/i in 1 to length(sleeps)) - var/datum/lua_state/sleeping_state = sleeps[i] - if(sleeping_state == state) - if(state_index == task_index) - sleeps.Cut(i, i+1) - break - state_index++ - if("yield") - // Remove the resumt from the resumt list - for(var/i in 1 to length(resumes)) - var/resume = resumes[i] - if(resume["state"] == state && resume["index"] == task_info["index"]) - resumes.Cut(i, i+1) + if(is_sleep) + var/state_index = 1 + + // Get the nth sleep in the sleep list corresponding to the target state + for(var/i in 1 to length(sleeps)) + var/datum/lua_state/sleeping_state = sleeps[i] + if(sleeping_state == state) + if(state_index == index) + sleeps.Cut(i, i+1) break - state.kill_task(task_info) + state_index++ + else + // Remove the resumt from the resumt list + for(var/i in 1 to length(resumes)) + var/resume = resumes[i] + if(resume["state"] == state && resume["index"] == index) + resumes.Cut(i, i+1) + break + state.kill_task(is_sleep, index) /datum/controller/subsystem/lua/fire(resumed) // Each fire of SSlua awakens every sleeping task in the order they slept, @@ -104,7 +85,6 @@ SUBSYSTEM_DEF(lua) sleeps.Cut() resumes.Cut() - gc_guard.Cut() var/list/current_sleeps = current_run["sleeps"] var/list/affected_states = list() while(length(current_sleeps)) @@ -147,6 +127,32 @@ SUBSYSTEM_DEF(lua) if(MC_TICK_CHECK) break + while(length(needs_gc_cycle)) + var/datum/lua_state/state = needs_gc_cycle[needs_gc_cycle.len] + needs_gc_cycle.len-- + state.collect_garbage() + // Update every lua editor TGUI open for each state that had a task awakened or resumed for(var/datum/lua_state/state in affected_states) INVOKE_ASYNC(state, TYPE_PROC_REF(/datum/lua_state, update_editors)) + +/datum/controller/subsystem/lua/proc/log_involved_runtime(exception/runtime, list/desclines, list/lua_stacks) + var/list/json_data = list("status" = "runtime", "file" = runtime.file, "line" = runtime.line, "message" = runtime.name, "stack" = list()) + var/level = 1 + for(var/line in desclines) + line = copytext(line, 3) + if(starts_with_any(line, list( + "/datum/lua_state (/datum/lua_state): load script", + "/datum/lua_state (/datum/lua_state): call function", + "/datum/lua_state (/datum/lua_state): awaken", + "/datum/lua_state (/datum/lua_state): resume" + ))) + json_data["stack"] += lua_stacks[level] + level++ + json_data["stack"] += line + for(var/datum/weakref/state_ref as anything in GLOB.lua_state_stack) + var/datum/lua_state/state = state_ref.resolve() + if(!state) + continue + state.log_result(json_data) + return diff --git a/code/controllers/subsystem/market.dm b/code/controllers/subsystem/market.dm index 81d96d331c71d..0c134d5691570 100644 --- a/code/controllers/subsystem/market.dm +++ b/code/controllers/subsystem/market.dm @@ -95,9 +95,11 @@ SUBSYSTEM_DEF(market) qdel(purchase) if(SHIPPING_METHOD_SUPPLYPOD) - var/obj/structure/closet/supplypod/back_to_station/pod = new() - purchase.entry.spawn_item(pod, purchase) - new /obj/effect/pod_landingzone(get_turf(purchase.uplink), pod) + var/obj/structure/closet/supplypod/spawned_pod = podspawn(list( + "target" = get_turf(purchase.uplink), + "path" = /obj/structure/closet/supplypod/back_to_station, + )) + purchase.entry.spawn_item(spawned_pod, purchase) to_chat(buyer, span_notice("[purchase.uplink] flashes a message noting the order is being launched at your location. Right here, right now!")) qdel(purchase) diff --git a/code/controllers/subsystem/minor_mapping.dm b/code/controllers/subsystem/minor_mapping.dm index 6acbbc1894e25..4aed29be350b6 100644 --- a/code/controllers/subsystem/minor_mapping.dm +++ b/code/controllers/subsystem/minor_mapping.dm @@ -49,6 +49,8 @@ SUBSYSTEM_DEF(minor_mapping) ///List of areas where satchels should not be placed. var/list/blacklisted_area_types = list( /area/station/holodeck, + /area/space/nearstation, + /area/station/solars, ) while(turfs.len && satchel_amount > 0) diff --git a/code/datums/callback.dm b/code/datums/callback.dm index 35103fbc901f3..c2941c9202986 100644 --- a/code/datums/callback.dm +++ b/code/datums/callback.dm @@ -111,7 +111,7 @@ else calling_arguments = args if(datum_flags & DF_VAR_EDITED) - if(usr != GLOB.AdminProcCallHandler && !usr?.client?.ckey) //This happens when a timer or the MC invokes a callback + if(usr != GLOB.AdminProcCallHandler && !(usr && usr?.client?.ckey)) //This happens when a timer or the MC invokes a callback return HandleUserlessProcCall(usr, object, delegate, calling_arguments) return WrapAdminProcCall(object, delegate, calling_arguments) if (object == GLOBAL_PROC) @@ -148,7 +148,7 @@ else calling_arguments = args if(datum_flags & DF_VAR_EDITED) - if(usr != GLOB.AdminProcCallHandler && !usr?.client?.ckey) //This happens when a timer or the MC invokes a callback + if(usr != GLOB.AdminProcCallHandler && !(usr && usr?.client?.ckey)) //This happens when a timer or the MC invokes a callback return HandleUserlessProcCall(usr, object, delegate, calling_arguments) return WrapAdminProcCall(object, delegate, calling_arguments) if (object == GLOBAL_PROC) diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm index d300fcc371349..998c10a3b9b44 100644 --- a/code/datums/chatmessage.dm +++ b/code/datums/chatmessage.dm @@ -324,59 +324,6 @@ else new /datum/chatmessage(raw_message, speaker, src, message_language, spans) -// Tweak these defines to change the available color ranges -#define CM_COLOR_SAT_MIN 0.6 -#define CM_COLOR_SAT_MAX 0.7 -#define CM_COLOR_LUM_MIN 0.65 -#define CM_COLOR_LUM_MAX 0.75 - -/** - * Gets a color for a name, will return the same color for a given string consistently within a round.atom - * - * Note that this proc aims to produce pastel-ish colors using the HSL colorspace. These seem to be favorable for displaying on the map. - * - * Arguments: - * * name - The name to generate a color for - * * sat_shift - A value between 0 and 1 that will be multiplied against the saturation - * * lum_shift - A value between 0 and 1 that will be multiplied against the luminescence - */ -/datum/chatmessage/proc/colorize_string(name, sat_shift = 1, lum_shift = 1) - // seed to help randomness - var/static/rseed = rand(1,26) - - // get hsl using the selected 6 characters of the md5 hash - var/hash = copytext(md5(name + GLOB.round_id), rseed, rseed + 6) - var/h = hex2num(copytext(hash, 1, 3)) * (360 / 255) - var/s = (hex2num(copytext(hash, 3, 5)) >> 2) * ((CM_COLOR_SAT_MAX - CM_COLOR_SAT_MIN) / 63) + CM_COLOR_SAT_MIN - var/l = (hex2num(copytext(hash, 5, 7)) >> 2) * ((CM_COLOR_LUM_MAX - CM_COLOR_LUM_MIN) / 63) + CM_COLOR_LUM_MIN - - // adjust for shifts - s *= clamp(sat_shift, 0, 1) - l *= clamp(lum_shift, 0, 1) - - // convert to rgb - var/h_int = round(h/60) // mapping each section of H to 60 degree sections - var/c = (1 - abs(2 * l - 1)) * s - var/x = c * (1 - abs((h / 60) % 2 - 1)) - var/m = l - c * 0.5 - x = (x + m) * 255 - c = (c + m) * 255 - m *= 255 - switch(h_int) - if(0) - return "#[num2hex(c, 2)][num2hex(x, 2)][num2hex(m, 2)]" - if(1) - return "#[num2hex(x, 2)][num2hex(c, 2)][num2hex(m, 2)]" - if(2) - return "#[num2hex(m, 2)][num2hex(c, 2)][num2hex(x, 2)]" - if(3) - return "#[num2hex(m, 2)][num2hex(x, 2)][num2hex(c, 2)]" - if(4) - return "#[num2hex(x, 2)][num2hex(m, 2)][num2hex(c, 2)]" - if(5) - return "#[num2hex(c, 2)][num2hex(m, 2)][num2hex(x, 2)]" - - #undef CHAT_LAYER_MAX_Z #undef CHAT_LAYER_Z_STEP #undef CHAT_MESSAGE_APPROX_LHEIGHT @@ -388,7 +335,3 @@ #undef CHAT_MESSAGE_LIFESPAN #undef CHAT_MESSAGE_SPAWN_TIME #undef CHAT_MESSAGE_WIDTH -#undef CM_COLOR_LUM_MAX -#undef CM_COLOR_LUM_MIN -#undef CM_COLOR_SAT_MAX -#undef CM_COLOR_SAT_MIN diff --git a/code/datums/components/callouts.dm b/code/datums/components/callouts.dm new file mode 100644 index 0000000000000..24e7f081fbe78 --- /dev/null +++ b/code/datums/components/callouts.dm @@ -0,0 +1,177 @@ +#define CALLOUT_TIME (5 SECONDS) +#define CALLOUT_COOLDOWN 3 SECONDS + +/// Component that allows its owner/owner's wearer to use callouts system - their pointing is replaced with a fancy radial which allows them to summon glowing markers +/datum/component/callouts + /// If parent is clothing, slot on which this component activates + var/item_slot + /// If we are currently active + var/active = TRUE + /// Current user of this component + var/mob/cur_user + /// Whenever the user should shout the voiceline + var/voiceline = FALSE + /// If voiceline is true, what prefix the user should use + var/radio_prefix = null + /// List of all callout options + var/static/list/callout_options = typecacheof(subtypesof(/datum/callout_option)) + /// Text displayed when parent is examined + var/examine_text = null + /// Cooldown for callouts + COOLDOWN_DECLARE(callout_cooldown) + +/datum/component/callouts/Initialize(item_slot = null, voiceline = FALSE, radio_prefix = null, examine_text = null) + if (!isitem(parent) && !ismob(parent)) + return COMPONENT_INCOMPATIBLE + src.item_slot = item_slot + src.voiceline = voiceline + src.radio_prefix = radio_prefix + src.examine_text = examine_text + + if (ismob(parent)) + cur_user = parent + return + + var/atom/atom_parent = parent + + if (!ismob(atom_parent.loc)) + return + + var/mob/user = atom_parent.loc + if (!isnull(item_slot) && user.get_item_by_slot(item_slot) != parent) + return + + RegisterSignal(atom_parent.loc, COMSIG_MOB_CLICKON, PROC_REF(on_click)) + cur_user = atom_parent.loc + +/datum/component/callouts/Destroy(force) + cur_user = null + . = ..() + +/datum/component/callouts/RegisterWithParent() + RegisterSignal(parent, COMSIG_MOB_CLICKON, PROC_REF(on_click)) + RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equipped)) + RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(on_dropped)) + RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examines)) + RegisterSignal(parent, COMSIG_CLICK_CTRL, PROC_REF(on_ctrl_click)) + +/datum/component/callouts/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_MOB_CLICKON, COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED, COMSIG_ATOM_EXAMINE, COMSIG_CLICK_CTRL)) + +/datum/component/callouts/proc/on_ctrl_click(datum/source, mob/living/user) + SIGNAL_HANDLER + + if(!isitem(parent)) + return + + var/obj/item/item_parent = parent + active = !active + item_parent.balloon_alert(user, active ? "callouts enabled" : "callouts disabled") + +/datum/component/callouts/proc/on_equipped(datum/source, mob/equipper, slot) + SIGNAL_HANDLER + + if (item_slot & slot) + RegisterSignal(equipper, COMSIG_MOB_CLICKON, PROC_REF(on_click)) + cur_user = equipper + else if (cur_user == equipper) + UnregisterSignal(cur_user, COMSIG_MOB_CLICKON, PROC_REF(on_click)) + cur_user = null + +/datum/component/callouts/proc/on_dropped(datum/source, mob/user) + SIGNAL_HANDLER + + if (cur_user == user) + UnregisterSignal(cur_user, COMSIG_MOB_CLICKON, PROC_REF(on_click)) + cur_user = null + +/datum/component/callouts/proc/on_examines(mob/source, mob/user, list/examine_list) + SIGNAL_HANDLER + if (!isnull(examine_text)) + examine_list += examine_text + +/datum/component/callouts/proc/on_click(mob/user, atom/clicked_atom, list/modifiers) + SIGNAL_HANDLER + + if (!LAZYACCESS(modifiers, SHIFT_CLICK) || !LAZYACCESS(modifiers, MIDDLE_CLICK)) + return + + if (!active) + return + + if (!COOLDOWN_FINISHED(src, callout_cooldown)) + clicked_atom.balloon_alert(user, "callout is on cooldown!") + return COMSIG_MOB_CANCEL_CLICKON + + INVOKE_ASYNC(src, PROC_REF(callout_picker), user, clicked_atom) + return COMSIG_MOB_CANCEL_CLICKON + +/datum/component/callouts/proc/callout_picker(mob/user, atom/clicked_atom) + var/list/callout_items = list() + for(var/datum/callout_option/callout_option as anything in callout_options) + callout_items[callout_option] = image(icon = 'icons/hud/radial.dmi', icon_state = callout_option::icon_state) + + var/datum/callout_option/selection = show_radial_menu(user, get_turf(clicked_atom), callout_items, entry_animation = FALSE, click_on_hover = TRUE) + if (!selection) + return + + COOLDOWN_START(src, callout_cooldown, CALLOUT_COOLDOWN) + new /obj/effect/temp_visual/callout(get_turf(user), user, selection, clicked_atom) + SEND_SIGNAL(user, COMSIG_MOB_CREATED_CALLOUT, selection, clicked_atom) + if (voiceline) + user.say((!isnull(radio_prefix) ? radio_prefix : "") + selection::voiceline, forced = src) + +/obj/effect/temp_visual/callout + name = "callout" + icon = 'icons/effects/callouts.dmi' + icon_state = "point" + plane = ABOVE_LIGHTING_PLANE + duration = CALLOUT_TIME + +/obj/effect/temp_visual/callout/Initialize(mapload, mob/creator, datum/callout_option/callout, atom/target) + . = ..() + if (isnull(creator)) + return + icon_state = callout::icon_state + color = colorize_string(creator.GetVoice(), 2, 0.9) + update_appearance() + var/turf/target_loc = get_turf(target) + animate(src, pixel_x = (target_loc.x - loc.x) * world.icon_size + target.pixel_x, pixel_y = (target_loc.y - loc.y) * world.icon_size + target.pixel_y, time = 0.2 SECONDS, easing = EASE_OUT) + +/datum/callout_option + var/name = "ERROR" + var/icon_state = "point" + var/voiceline = "Something has gone wrong!" + +/datum/callout_option/point + name = "Point" + icon_state = "point" + voiceline = "Here!" + +/datum/callout_option/danger + name = "Danger" + icon_state = "danger" + voiceline = "Danger there!" + +/datum/callout_option/guard + name = "Guard" + icon_state = "guard" + voiceline = "Hold this position!" + +/datum/callout_option/attack + name = "Attack" + icon_state = "attack" + voiceline = "Attack there!" + +/datum/callout_option/mine + name = "Mine" + icon_state = "mine" + voiceline = "Dig here!" + +/datum/callout_option/move + name = "Move" + icon_state = "move" + voiceline = "Reposition there!" + +#undef CALLOUT_TIME +#undef CALLOUT_COOLDOWN diff --git a/code/datums/components/crafting/misc.dm b/code/datums/components/crafting/misc.dm index 606cf1fc29262..52c66253e824b 100644 --- a/code/datums/components/crafting/misc.dm +++ b/code/datums/components/crafting/misc.dm @@ -35,6 +35,17 @@ tool_paths = list(/obj/item/stamp/head/captain) category = CAT_MISC +/datum/crafting_recipe/clipboard + name = "Clipboard" + result = /obj/item/clipboard + time = 3 SECONDS + reqs = list( + /obj/item/stack/sheet/mineral/wood = 1, + /obj/item/stack/rods = 1, + ) + tool_behaviors = list(TOOL_WIRECUTTER) + category = CAT_MISC + /datum/crafting_recipe/cardboard_id name = "Cardboard ID Card" tool_behaviors = list(TOOL_WIRECUTTER) diff --git a/code/datums/components/pet_commands/pet_command.dm b/code/datums/components/pet_commands/pet_command.dm index 6ae249d2340aa..a8db88d3a44ef 100644 --- a/code/datums/components/pet_commands/pet_command.dm +++ b/code/datums/components/pet_commands/pet_command.dm @@ -18,6 +18,8 @@ var/radial_icon_state /// Speech strings to listen out for var/list/speech_commands = list() + /// Callout that triggers this command + var/callout_type /// Shown above the mob's head when it hears you var/command_feedback /// How close a mob needs to be to a target to respond to a command @@ -31,10 +33,11 @@ /datum/pet_command/proc/add_new_friend(mob/living/tamer) RegisterSignal(tamer, COMSIG_MOB_SAY, PROC_REF(respond_to_command)) RegisterSignal(tamer, COMSIG_MOB_AUTOMUTE_CHECK, PROC_REF(waive_automute)) + RegisterSignal(tamer, COMSIG_MOB_CREATED_CALLOUT, PROC_REF(respond_to_callout)) /// Stop listening to a guy /datum/pet_command/proc/remove_friend(mob/living/unfriended) - UnregisterSignal(unfriended, list(COMSIG_MOB_SAY, COMSIG_MOB_AUTOMUTE_CHECK)) + UnregisterSignal(unfriended, list(COMSIG_MOB_SAY, COMSIG_MOB_AUTOMUTE_CHECK, COMSIG_MOB_CREATED_CALLOUT)) /// Stop the automute from triggering for commands (unless the spoken text is suspiciously longer than the command) /datum/pet_command/proc/waive_automute(mob/living/speaker, client/client, last_message, mute_type) @@ -59,6 +62,34 @@ try_activate_command(speaker) +/// Respond to a callout +/datum/pet_command/proc/respond_to_callout(mob/living/caller, datum/callout_option/callout, atom/target) + SIGNAL_HANDLER + + if (isnull(callout_type) || !ispath(callout, callout_type)) + return + + var/mob/living/parent = weak_parent.resolve() + if (!parent) + return + + if (!valid_callout_target(caller, callout, target)) + var/found_new_target = FALSE + for (var/atom/new_target in range(2, target)) + if (valid_callout_target(caller, callout, new_target)) + target = new_target + found_new_target = TRUE + + if (!found_new_target) + return + + if (try_activate_command(caller)) + look_for_target(parent, target) + +/// Does this callout with this target trigger this command? +/datum/pet_command/proc/valid_callout_target(mob/living/caller, datum/callout_option/callout, atom/target) + return TRUE + /** * Returns true if we find any of our spoken commands in the text. * if check_verbosity is true, skip the match if there spoken_text is way longer than the match @@ -76,14 +107,35 @@ /datum/pet_command/proc/try_activate_command(mob/living/commander) var/mob/living/parent = weak_parent.resolve() if (!parent) - return + return FALSE if (!parent.ai_controller) // We stopped having a brain at some point - return + return FALSE if (IS_DEAD_OR_INCAP(parent)) // Probably can't hear them if we're dead - return + return FALSE if (parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] == src) // We're already doing it - return + return FALSE set_command_active(parent, commander) + return TRUE + +/// Target the pointed atom for actions +/datum/pet_command/proc/look_for_target(mob/living/friend, atom/pointed_atom) + var/mob/living/parent = weak_parent.resolve() + if (!parent) + return FALSE + if (!parent.ai_controller) + return FALSE + if (IS_DEAD_OR_INCAP(parent)) + return FALSE + if (parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] != src) // We're not listening right now + return FALSE + if (parent.ai_controller.blackboard[BB_CURRENT_PET_TARGET] == pointed_atom) // That's already our target + return FALSE + if (!can_see(parent, pointed_atom, sense_radius)) + return FALSE + + parent.ai_controller.CancelActions() + set_command_target(parent, pointed_atom) + return TRUE /// Activate the command, extend to add visible messages and the like /datum/pet_command/proc/set_command_active(mob/living/parent, mob/living/commander) @@ -134,33 +186,22 @@ /datum/pet_command/point_targeting/add_new_friend(mob/living/tamer) . = ..() - RegisterSignal(tamer, COMSIG_MOB_POINTED, PROC_REF(look_for_target)) + RegisterSignal(tamer, COMSIG_MOB_POINTED, PROC_REF(on_point)) /datum/pet_command/point_targeting/remove_friend(mob/living/unfriended) . = ..() UnregisterSignal(unfriended, COMSIG_MOB_POINTED) /// Target the pointed atom for actions -/datum/pet_command/point_targeting/proc/look_for_target(mob/living/friend, atom/pointed_atom) +/datum/pet_command/point_targeting/proc/on_point(mob/living/friend, atom/pointed_atom) SIGNAL_HANDLER var/mob/living/parent = weak_parent.resolve() if (!parent) return FALSE - if (!parent.ai_controller) - return FALSE - if (IS_DEAD_OR_INCAP(parent)) - return FALSE - if (parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] != src) // We're not listening right now - return FALSE - if (parent.ai_controller.blackboard[BB_CURRENT_PET_TARGET] == pointed_atom) // That's already our target - return FALSE - if (!can_see(parent, pointed_atom, sense_radius)) - return FALSE parent.ai_controller.CancelActions() - // Deciding if they can actually do anything with this target is the behaviour's job - if(set_command_target(parent, pointed_atom)) - // These are usually hostile actions so should have a record in chat - parent.visible_message(span_warning("[parent] follows [friend]'s gesture towards [pointed_atom][pointed_reaction ? " [pointed_reaction]" : ""]!")) - return TRUE + if (look_for_target(friend, pointed_atom) && set_command_target(parent, pointed_atom)) + parent.visible_message(span_warning("[parent] follows [friend]'s gesture towards [pointed_atom] [pointed_reaction]!")) + return TRUE + return FALSE diff --git a/code/datums/components/pet_commands/pet_commands_basic.dm b/code/datums/components/pet_commands/pet_commands_basic.dm index ad48bba0ffd07..9f4dda9cca394 100644 --- a/code/datums/components/pet_commands/pet_commands_basic.dm +++ b/code/datums/components/pet_commands/pet_commands_basic.dm @@ -41,6 +41,7 @@ radial_icon = 'icons/testing/turf_analysis.dmi' radial_icon_state = "red_arrow" speech_commands = list("heel", "follow") + callout_type = /datum/callout_option/move ///the behavior we use to follow var/follow_behavior = /datum/ai_behavior/pet_follow_friend @@ -124,6 +125,7 @@ radial_icon = 'icons/effects/effects.dmi' radial_icon_state = "bite" + callout_type = /datum/callout_option/attack speech_commands = list("attack", "sic", "kill") command_feedback = "growl" pointed_reaction = "and growls" @@ -220,6 +222,7 @@ command_name = "Protect owner" command_desc = "Your pet will run to your aid." hidden = TRUE + callout_type = /datum/callout_option/guard ///the range our owner needs to be in for us to protect him var/protect_range = 9 ///the behavior we will use when he is attacked @@ -250,6 +253,9 @@ . = ..() set_command_target(parent, victim) +/datum/pet_command/protect_owner/valid_callout_target(mob/living/caller, datum/callout_option/callout, atom/target) + return target == caller || get_dist(caller, target) <= 1 + /datum/pet_command/protect_owner/proc/set_attacking_target(atom/source, mob/living/attacker) SIGNAL_HANDLER diff --git a/code/datums/components/profound_fisher.dm b/code/datums/components/profound_fisher.dm index 1bc10b8ac303b..4485115db06e6 100644 --- a/code/datums/components/profound_fisher.dm +++ b/code/datums/components/profound_fisher.dm @@ -58,5 +58,6 @@ /obj/item/fishing_rod/mob_fisher display_fishing_line = FALSE line = /obj/item/fishing_line/reinforced + bait = /obj/item/food/bait/doughball/synthetic/unconsumable diff --git a/code/datums/datum.dm b/code/datums/datum.dm index f4fd0190b2ab9..a79397fe336ba 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -111,6 +111,8 @@ tag = null datum_flags &= ~DF_USE_TAG //In case something tries to REF us weak_reference = null //ensure prompt GCing of weakref. + DREAMLUAU_CLEAR_REF_USERDATA(vars) // vars ceases existing when src does, so we need to clear any lua refs to it that exist. + DREAMLUAU_CLEAR_REF_USERDATA(src) if(_active_timers) var/list/timers = _active_timers diff --git a/code/datums/skills/fishing.dm b/code/datums/skills/fishing.dm index cfd14a4ce3ba6..fac1855c98dbc 100644 --- a/code/datums/skills/fishing.dm +++ b/code/datums/skills/fishing.dm @@ -6,7 +6,7 @@ name = "Fishing" title = "Angler" desc = "How empty and alone you are on this barren Earth." - modifiers = list(SKILL_VALUE_MODIFIER = list(1, 1, 0, -1, -2, -4, -6)) + modifiers = list(SKILL_VALUE_MODIFIER = list(1, 0, -1, -3, -5, -7, -10)) skill_item_path = /obj/item/clothing/head/soft/fishing_hat /datum/skill/fishing/New() diff --git a/code/datums/station_traits/positive_traits.dm b/code/datums/station_traits/positive_traits.dm index 1af74533775fa..945fbe06934bc 100644 --- a/code/datums/station_traits/positive_traits.dm +++ b/code/datums/station_traits/positive_traits.dm @@ -20,15 +20,23 @@ COOLDOWN_START(src, party_cooldown, rand(PARTY_COOLDOWN_LENGTH_MIN, PARTY_COOLDOWN_LENGTH_MAX)) - var/area/area_to_spawn_in = pick(GLOB.bar_areas) - var/turf/T = pick(area_to_spawn_in.contents) - - var/obj/structure/closet/supplypod/centcompod/toLaunch = new() - var/obj/item/pizzabox/pizza_to_spawn = pick(list(/obj/item/pizzabox/margherita, /obj/item/pizzabox/mushroom, /obj/item/pizzabox/meat, /obj/item/pizzabox/vegetable, /obj/item/pizzabox/pineapple)) - new pizza_to_spawn(toLaunch) - for(var/i in 1 to 6) - new /obj/item/reagent_containers/cup/glass/bottle/beer(toLaunch) - new /obj/effect/pod_landingzone(T, toLaunch) + var/pizza_type_to_spawn = pick(list( + /obj/item/pizzabox/margherita, + /obj/item/pizzabox/mushroom, + /obj/item/pizzabox/meat, + /obj/item/pizzabox/vegetable, + /obj/item/pizzabox/pineapple + )) + + var/area/bar_area = pick(GLOB.bar_areas) + podspawn(list( + "target" = pick(bar_area.contents), + "path" = /obj/structure/closet/supplypod/centcompod, + "spawn" = list( + pizza_type_to_spawn, + /obj/item/reagent_containers/cup/glass/bottle/beer = 6 + ) + )) #undef PARTY_COOLDOWN_LENGTH_MIN #undef PARTY_COOLDOWN_LENGTH_MAX diff --git a/code/datums/storage/subtypes/bag_of_holding.dm b/code/datums/storage/subtypes/bag_of_holding.dm index aa812f5d1e007..4028d4f789c6b 100644 --- a/code/datums/storage/subtypes/bag_of_holding.dm +++ b/code/datums/storage/subtypes/bag_of_holding.dm @@ -16,6 +16,7 @@ return ..() /datum/storage/bag_of_holding/proc/recursive_insertion(obj/item/to_insert, mob/living/user) + var/area/bag_area = get_area(user) var/safety = tgui_alert(user, "Doing this will have extremely dire consequences for the station and its crew. Be sure you know what you're doing.", "Put in [to_insert.name]?", list("Proceed", "Abort")) if(safety != "Proceed" \ || QDELETED(to_insert) \ @@ -24,6 +25,7 @@ || QDELETED(user) \ || !user.can_perform_action(parent, NEED_DEXTERITY) \ || !can_insert(to_insert, user) \ + || (bag_area.area_flags & NO_BOH) \ ) return diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm index 669409099530a..ee70febb6c794 100644 --- a/code/game/atom/_atom.dm +++ b/code/game/atom/_atom.dm @@ -186,6 +186,12 @@ if(smoothing_flags & SMOOTH_QUEUED) SSicon_smooth.remove_from_queues(src) + // These lists cease existing when src does, so we need to clear any lua refs to them that exist. + DREAMLUAU_CLEAR_REF_USERDATA(contents) + DREAMLUAU_CLEAR_REF_USERDATA(filters) + DREAMLUAU_CLEAR_REF_USERDATA(overlays) + DREAMLUAU_CLEAR_REF_USERDATA(underlays) + return ..() /atom/proc/handle_ricochet(obj/projectile/ricocheting_projectile) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index b53c4d1a13cf6..5778f2be1a401 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -234,6 +234,10 @@ LAZYNULL(client_mobs_in_contents) + // These lists cease existing when src does, so we need to clear any lua refs to them that exist. + DREAMLUAU_CLEAR_REF_USERDATA(vis_contents) + DREAMLUAU_CLEAR_REF_USERDATA(vis_locs) + . = ..() for(var/movable_content in contents) diff --git a/code/game/machinery/roulette_machine.dm b/code/game/machinery/roulette_machine.dm index fc443c247f1d8..2a8dc8bb49b22 100644 --- a/code/game/machinery/roulette_machine.dm +++ b/code/game/machinery/roulette_machine.dm @@ -451,11 +451,12 @@ addtimer(CALLBACK(src, PROC_REF(launch_payload)), 4 SECONDS) /obj/item/roulette_wheel_beacon/proc/launch_payload() - var/obj/structure/closet/supplypod/centcompod/toLaunch = new() - - new /obj/machinery/roulette(toLaunch) - - new /obj/effect/pod_landingzone(drop_location(), toLaunch) + podspawn(list( + "target" = drop_location(), + "path" = /obj/structure/closet/supplypod/centcompod, + "spawn" = /obj/machinery/roulette + )) + qdel(src) #undef ROULETTE_DOZ_COL_PAYOUT diff --git a/code/game/objects/items/clown_items.dm b/code/game/objects/items/clown_items.dm index 3d817d24ccf2b..32a81d1f75f9c 100644 --- a/code/game/objects/items/clown_items.dm +++ b/code/game/objects/items/clown_items.dm @@ -89,6 +89,13 @@ worn_icon_state = "soapsyndie" cleanspeed = 0.5 SECONDS //faster than mops so it's useful for traitors who want to clean crime scenes +/obj/item/soap/drone + name = "\improper integrated soap module" + inhand_icon_state = "soapnt" + worn_icon_state = "soapnt" + cleanspeed = 0.5 SECONDS //can be changed if someone isn't happy + uses = INFINITY + /obj/item/soap/omega name = "\improper Omega soap" desc = "The most advanced soap known to mankind. The beginning of the end for germs." diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index 1f2cd37a5ccef..edf24b0d942d4 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -299,7 +299,7 @@ GLOBAL_LIST_INIT(channel_tokens, list( /obj/item/radio/headset/headset_cargo/mining name = "mining radio headset" - desc = "Headset used by shaft miners." + desc = "Headset used by shaft miners. It has a mining network uplink which allows the user to quickly transmit commands to their comrades and amplifies their voice in low-pressure environments." icon_state = "mine_headset" worn_icon_state = "mine_headset" // "puts the antenna down" while the headset is off @@ -307,6 +307,19 @@ GLOBAL_LIST_INIT(channel_tokens, list( overlay_mic_idle = "headset_up" keyslot = /obj/item/encryptionkey/headset_mining +/obj/item/radio/headset/headset_cargo/mining/Initialize(mapload) + . = ..() + AddComponent(/datum/component/callouts, ITEM_SLOT_EARS, examine_text = span_info("Use ctrl-click to enable or disable callouts.")) + +/obj/item/radio/headset/headset_cargo/mining/equipped(mob/living/carbon/human/user, slot) + . = ..() + if(slot & ITEM_SLOT_EARS) + ADD_TRAIT(user, TRAIT_SPEECH_BOOSTER, CLOTHING_TRAIT) + +/obj/item/radio/headset/headset_cargo/mining/dropped(mob/living/carbon/human/user) + . = ..() + REMOVE_TRAIT(user, TRAIT_SPEECH_BOOSTER, CLOTHING_TRAIT) + /obj/item/radio/headset/headset_srv name = "service radio headset" desc = "Headset used by the service staff, tasked with keeping the station full, happy and clean." diff --git a/code/game/objects/items/food/bait.dm b/code/game/objects/items/food/bait.dm index 047a8a7cd58ce..41b50c181f287 100644 --- a/code/game/objects/items/food/bait.dm +++ b/code/game/objects/items/food/bait.dm @@ -66,3 +66,9 @@ /obj/item/food/bait/doughball/synthetic/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_OMNI_BAIT, INNATE_TRAIT) + +/obj/item/food/bait/doughball/syntethic/unconsumable + +/obj/item/food/bait/doughball/synthetic/unconsumable/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_BAIT_UNCONSUMABLE, INNATE_TRAIT) diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm index 0337698106c38..855484c3066d4 100644 --- a/code/game/objects/items/melee/misc.dm +++ b/code/game/objects/items/melee/misc.dm @@ -167,7 +167,7 @@ /obj/item/melee/parsnip_sabre name = "parsnip sabre" - desc = "An elegant weapon, if weird weapon. Suprisingly sharp for being made from a parsnip." + desc = "A weird, yet elegant weapon. Suprisingly sharp for something made from a parsnip." icon = 'icons/obj/weapons/sword.dmi' icon_state = "parsnip_sabre" inhand_icon_state = "parsnip_sabre" diff --git a/code/game/objects/items/spear.dm b/code/game/objects/items/spear.dm index 96fbde554b79e..53e307e31d6e0 100644 --- a/code/game/objects/items/spear.dm +++ b/code/game/objects/items/spear.dm @@ -221,7 +221,7 @@ icon_state = "military_spear0" base_icon_state = "military_spear0" icon_prefix = "military_spear" - name = "military Javelin" + name = "military javelin" desc = "A stick with a seemingly blunt spearhead on its end. Looks like it might break bones easily." attack_verb_continuous = list("attacks", "pokes", "jabs") attack_verb_simple = list("attack", "poke", "jab") diff --git a/code/game/objects/items/stacks/sheets/leather.dm b/code/game/objects/items/stacks/sheets/leather.dm index f02a2af11f835..1f13ec34a764c 100644 --- a/code/game/objects/items/stacks/sheets/leather.dm +++ b/code/game/objects/items/stacks/sheets/leather.dm @@ -5,9 +5,8 @@ inhand_icon_state = null novariants = TRUE merge_type = /obj/item/stack/sheet/animalhide - - pickup_sound = null - drop_sound = null + pickup_sound = 'sound/items/skin_pick_up.ogg' + drop_sound = 'sound/items/skin_drop.ogg' /obj/item/stack/sheet/animalhide/human name = "human skin" @@ -194,6 +193,8 @@ GLOBAL_LIST_INIT(carp_recipes, list ( \ icon_state = "sheet-leather" inhand_icon_state = null merge_type = /obj/item/stack/sheet/leather + pickup_sound = 'sound/items/skin_pick_up.ogg' + drop_sound = 'sound/items/skin_drop.ogg' GLOBAL_LIST_INIT(leather_recipes, list ( \ new/datum/stack_recipe("wallet", /obj/item/storage/wallet, 1, crafting_flags = NONE, category = CAT_CONTAINERS), \ diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index 8cf393b3e065c..425a624984550 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -680,6 +680,8 @@ GLOBAL_LIST_INIT(cardboard_recipes, list ( \ merge_type = /obj/item/stack/sheet/cardboard grind_results = list(/datum/reagent/cellulose = 10) material_type = /datum/material/cardboard + pickup_sound = 'sound/items/cardboard_pick_up.ogg' + drop_sound = 'sound/items/cardboard_drop.ogg' /obj/item/stack/sheet/cardboard/Initialize(mapload, new_amount, merge, list/mat_override, mat_amt) . = ..() diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index 3c09dc3926eec..8bf2e0b74aa25 100644 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -857,7 +857,7 @@ /obj/item/storage/belt/grass_sabre name = "sabre sheath" - desc = "An simple grass sheath designed to hold a sabre of... some sorts. Actual metal one might be too sharp, though..." + desc = "An simple grass sheath designed to hold a sabre of... some sort. Actual metal one might be too sharp, though..." icon_state = "grass_sheath" inhand_icon_state = "grass_sheath" worn_icon_state = "grass_sheath" @@ -871,7 +871,7 @@ atom_storage.max_slots = 1 atom_storage.rustle_sound = FALSE atom_storage.max_specific_storage = WEIGHT_CLASS_BULKY - atom_storage.set_holdable(/obj/item/melee/sabre) + atom_storage.set_holdable(/obj/item/melee/parsnip_sabre) atom_storage.click_alt_open = FALSE /obj/item/storage/belt/grass_sabre/examine(mob/user) diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm index 7c5bc74e07550..905f2e40f47a7 100644 --- a/code/game/objects/items/storage/toolbox.dm +++ b/code/game/objects/items/storage/toolbox.dm @@ -348,6 +348,11 @@ weapon_to_spawn = /obj/item/gun/ballistic/automatic/c20r extra_to_spawn = /obj/item/ammo_box/magazine/smgm45 +/obj/item/storage/toolbox/guncase/smartgun + name = "adielle smartgun case" + weapon_to_spawn = /obj/item/gun/ballistic/automatic/smartgun + extra_to_spawn = /obj/item/ammo_box/magazine/smartgun + /obj/item/storage/toolbox/guncase/clandestine name = "clandestine gun case" weapon_to_spawn = /obj/item/gun/ballistic/automatic/pistol/clandestine @@ -442,12 +447,6 @@ weapon_to_spawn = /obj/effect/spawner/random/sakhno extra_to_spawn = /obj/effect/spawner/random/sakhno/ammo -/obj/item/storage/toolbox/guncase/soviet/plastikov - name = "ancient surplus gun case" - desc = "A gun case. Has the symbol of the Third Soviet Union stamped on the side." - weapon_to_spawn = /obj/item/gun/ballistic/automatic/plastikov - extra_to_spawn = /obj/item/food/rationpack //sorry comrade, cannot get you more ammo, here, have lunch - /obj/item/storage/toolbox/guncase/monkeycase name = "monkey gun case" desc = "Everything a monkey needs to truly go ape-shit. There's a paw-shaped hand scanner lock on the front of the case." diff --git a/code/game/turfs/open/floor/misc_floor.dm b/code/game/turfs/open/floor/misc_floor.dm index 7c4428c4823ec..a3915e14cabb8 100644 --- a/code/game/turfs/open/floor/misc_floor.dm +++ b/code/game/turfs/open/floor/misc_floor.dm @@ -17,12 +17,17 @@ /turf/open/floor/circuit/Initialize(mapload) SSmapping.nuke_tiles += src RegisterSignal(loc, COMSIG_AREA_POWER_CHANGE, PROC_REF(handle_powerchange)) - handle_powerchange(loc) + var/area/cur_area = get_area(src) + if (!isnull(cur_area)) + handle_powerchange(cur_area, TRUE) . = ..() /turf/open/floor/circuit/Destroy() SSmapping.nuke_tiles -= src UnregisterSignal(loc, COMSIG_AREA_POWER_CHANGE) + var/area/cur_area = get_area(src) + if(on && !isnull(cur_area)) + cur_area.removeStaticPower(CIRCUIT_FLOOR_POWERUSE, AREA_USAGE_STATIC_LIGHT) return ..() /turf/open/floor/circuit/update_appearance(updates) @@ -47,7 +52,7 @@ handle_powerchange(new_area) /// Enables/disables our lighting based off our source area -/turf/open/floor/circuit/proc/handle_powerchange(area/source) +/turf/open/floor/circuit/proc/handle_powerchange(area/source, mapload = FALSE) SIGNAL_HANDLER var/old_on = on if(always_off) @@ -59,7 +64,7 @@ if(on) source.addStaticPower(CIRCUIT_FLOOR_POWERUSE, AREA_USAGE_STATIC_LIGHT) - else + else if (!mapload) source.removeStaticPower(CIRCUIT_FLOOR_POWERUSE, AREA_USAGE_STATIC_LIGHT) update_appearance() diff --git a/code/game/world.dm b/code/game/world.dm index 9e57dbba343c5..b7de61e8c93e5 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -339,7 +339,6 @@ GLOBAL_VAR(restart_counter) #endif /world/proc/auxcleanup() - AUXTOOLS_FULL_SHUTDOWN(AUXLUA) var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL") if (debug_server) call_ext(debug_server, "auxtools_shutdown")() @@ -472,6 +471,7 @@ GLOBAL_VAR(restart_counter) /world/proc/on_tickrate_change() SStimer?.reset_buckets() + DREAMLUAU_SET_EXECUTION_LIMIT_MILLIS(tick_lag * 100) /world/proc/init_byond_tracy() var/library diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm b/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm index bc74347475ae9..1305e5a660d6e 100644 --- a/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm +++ b/code/modules/admin/verbs/SDQL2/SDQL_2_wrappers.dm @@ -51,6 +51,9 @@ /proc/_get_step(Ref, Dir) return get_step(Ref, Dir) +/proc/_hascall(object, procname) + return hascall(object, procname) + /proc/_hearers(Depth = world.view, Center = usr) return hearers(Depth, Center) diff --git a/code/modules/admin/verbs/lua/README.md b/code/modules/admin/verbs/lua/README.md index 707184d4d772b..9b9bfbe05f93f 100644 --- a/code/modules/admin/verbs/lua/README.md +++ b/code/modules/admin/verbs/lua/README.md @@ -1,150 +1,225 @@ -# Auxlua +# Objects ---- +Datums, lists, typepaths, static appearances, and some other objects are represented in Luau as userdata. Certain operations can be performed on these types of objects. -## Datums +## Common metamethods -DM datums are treated as lua userdata, and can be stored in fields. Due to fundamental limitations in lua, userdata is inherently truthy. Since datum userdata can correspond to a deleted datum, which would evaluate to `null` in DM, the function [`datum:is_null()`](#datumisnull) is provided to offer a truthiness test consistent with DM. +The following metamethods are defined for all objects. -Keep in mind that BYOND can't see that a datum is referenced in a lua field, and will garbage collect it if it is not referenced anywhere in DM. +### \_\_tostring(): string -### datum:get_var(var) +Returns the string representation of the object. This uses BYOND's internal string conversion function. -Equivalent to DM's `datum.var` +### \_\_eq(other: any): boolean -### datum:set_var(var, value) +Compare the equality of two objects. While passing the same object into luau twice will return two references to the same userdata, some DM projects may override the equality operator using an `__operator==` proc definition. -Equivalent to DM's `datum.var = value` +## Datum-like Objects -### datum:call_proc(procName, ...) +Datum-like objects include datums themselves, clients (if they have not been redefined to be children of `/datum`), static appearances, and the world. -Equivalent to DM's `datum.procName(...)` +### \_\_index(index: string): any -### datum:is_null() +Access the member specified by `index`. -This function is used to evaluate the truthiness of a DM var. The lua statement `if datum:is_null() then` is equivalent to the DM statement `if(datum)`. +If `index` is a valid var for the object, the index operation will return that var's value. +If the var getting wrapper proc is set, the operation will instead call that proc with the arguments `(object, index)`. -### datum.vars +For objects other than static appearances, if `index` is a valid proc for the object, the operation will return a wrapper for that proc that can be invoked using call syntax (e.g. `object:proc(...arguments)`). If the object proc calling wrapper is set, calling the returned function will instead call the wrapper proc with the arguments `(object, proc, {...arguments})`. Note that vars will be shadowed by procs with the same name. To work around this, use the `dm.get_var` function. -Returns a userdatum that allows you to access and modifiy the vars of a DM datum by index. `datum.vars.foo` is equivalent to `datum:get_var("foo")`, while `datum.vars.foo = bar` is equivalent to `datum:set_var("foo", bar)` +### \_\_newindex(index: string, value: any): () ---- +Set the var specified by `index` to `value`, if that var exists on the object. + +If the var setting wrapper proc is set, the operation will instead call that proc with the arguments `(object, index, value)`. ## Lists -In order to allow lists to be modified in-place across the DM-to-lua language barrier, lists are treated as userdata. Whenever running code that expects a DM value, auxlua will attempt to convert tables into lists. +Lists are syntactically similar to tables, with one crucial difference. +Unlike tables, numeric indices must be non-zero integers within the bounds of the list. + +### \_\_index(index: any): any + +Read the list at `index`. This works both for numeric indices and assoc keys. +Vars lists cannot be directly read this way if the var getting wrapper proc is set. + +### \_\_newindex(index: any, value: any): any + +Write `value` to the list at `index`. This works both for writing numeric indices and assoc keys. +Vars lists cannot be directly written this way if the var setting wrapper proc is set. + +### \_\_len(): integer + +Returns the length of the list, similarly to the `length` builtin in DM. + +### Iteration + +Lists support Luau's generalized iteration. Iteration this way returns pairs of numeric indices and list values. +For example, the statement `for _, v in L do` is logically equivalent to the DM statement `for(var/v in L)`. + +# Global Fields and Modules + +In addition to the full extent of Luau's standard library modules, some extra functions and modules have been added. + +## Global-Level Fields + +### sleep(): () + +Yields the active thread, without worrying about passing data into or out of the state. + +Threads yielded this way are placed at the end of a queue. Call the `awaken` hook function from DM to execute the thread at the front of the queue. + +### loadstring(code: string): function + +Luau does not inherently include the `loadstring` function common to a number of other versions of lua. This is an effective reimplementation of `loadstring`. + +### print(...any): () + +Calls the print wrapper with the passed in arguments. +Raises an error if no print wrapper is set, as that means there is nothing to print with. + +### \_state_id: integer + +The handle to the underlying luau state in the dreamluau binary. + +## \_exec + +The `_exec` module includes volatile fields related to the current execution context. + +### \_next_yield_index: integer + +When yielding a thread with `coroutine.yield`, it will be inserted into an internal table at the first open integer index. +This field corresponds to that first open integer index. + +### \_limit: integer? + +If set, the execution limit, rounded to the nearest millisecond. + +### \_time: integer + +The length of successive time luau code has been executed, including recursive calls to DM and back into luau, rounded to the nearest millisecond. + +## dm -List references are subject to the same limitations as datum userdata, but you are less likely to encounter these limitations for regular lists. +The `dm` module includes fields and functions for basic interaction with DM. -Some lists (`vars`, `contents`, `overlays`, `underlays`, `vis_contents`, and `vis_locs`) are inherently attached to datums, and as such, their corresponding userdata contains a weak reference to the containing datum. Use [`list:is_null`](#listisnull) to validate these types of lists. +### world: userdata -### list.len +A static reference to the DM `world`. -Equivalent to DM's `list.len` +### global_vars: userdata -### list:get(index) +A static reference that functions like the DM keyword `global`. This can be indexed to read/write global vars. -Equivalent to DM's `list[index]` +### global_procs: table -### list:set(index, value) +A table that can be indexed by string for functions that wrap global procs. -Equivalent to DM's `list[index] = value` +Due to BYOND limitations, attempting to index an invalid proc returns a function logically equivalent to a no-op. -### list:add(value) +### get_var(object: userdata, var: string): function -Equivalent to DM's `list.Add(value)` +Reads the var `var` on `object`. This function can be used to get vars that are shadowed by procs declared with the same name. -### list:remove(value) +### new(path: string, ...any): userdata -Equivalent to DM's `list.Remove(value)` +Creates an instance of the object specified by `path`, with `...` as its arguments. +If the "new" wrapper is set, that proc will be called instead, with the arguments `(path, {...})`. -### list:to_table() +### is_valid_ref(ref: any): boolean -Converts a DM list into a lua table. +Returns true if the value passed in corresponds to a valid reference-counted DM object. -### list:of_type(type_path) +### usr: userdata? -Will extract only values of type `type_path`. +Corresponds to the DM var `usr`. -### list:is_null() +## list -A similar truthiness test to [`datum:is_null()`](#datumisnull). This function only has the possibility of returning `false` for lists that are inherently attached to a datum (`vars`, `contents`, `overlays`, `underlays`, `vis_contents`, and `vis_locs`). +The `list` module contains wrappers for the builtin list procs, along with several other utility functions for working with lists. -### list.entries +### add(list: userdata, ...any): () -Returns a userdatum that allows you to access and modifiy the entries of the list by index. `list.entries.foo` is equivalent to `list:get("foo")`, while `list.entries.foo = bar` is equivalent to `list:set("foo", bar)` +Logically equivalent to the DM statement `list.Add(...)`. ---- +### copy(list: userdata, start?: integer, end?: integer): userdata -## The dm table +Logically equivalent to the DM statement `list.Copy(start, end)`. -The `dm` table consists of the basic hooks into the DM language. +### cut(list: userdata, start?: integer, end?: integer): userdata -### dm.state_id +Logically equivalent to the DM statement `list.Cut(start, end)`. -The address of the lua state in memory. This is a copy of the internal value used by auxlua to locate the lua state in a global hash map. `state_id` is a registry value that is indirectly obtained using the `dm` table's `__index` metamethod. +### find(list: userdata, item: any, start?: integer, end?: integer): integer -### dm.global_proc(proc, ...) -Calls the global proc `/proc/[proc]` with `...` as its arguments. +Logically equivalent to the DM statement `list.Find(item, start, end)`. -### dm.world -A reference to DM's `world`, in the form of datum userdata. This reference is always valid, since `world` always exists. +### insert(list: userdata, index: integer, ...any): integer -Due to limitations inherent in the wrapper functions used on tgstation, `world:set_var` and `world:call_proc` will raise an error. +Logically equivalent to the DM statement `list.Insert(item, ...)`. -### dm.global_vars -A reference to DM's `global`, in the form of datum userdata. Subject to the same limitations as `dm.world` +### join(list: userdata, glue: string, start?: integer, end?: integer): string -### dm.usr -A weak reference to DM's `usr`. As a rule of thumb, this is a reference to the mob of the client who triggered the chain of procs leading to the execution of Lua code. The following is a list of what `usr` is for the most common ways of executing Lua code: -- For resumes and awakens, which are generally executed by the MC, `usr` is (most likely) null. -- `SS13.wait` queues a resume, which gets executed by the MC. Therefore, `usr` is null after `SS13.wait` finishes. -- For chunk loads, `usr` is generally the current mob of the admin that loaded that chunk. -- For function calls done from the Lua editor, `usr` is the current mob of the admin calling the function. -- `SS13.register_signal` creates a `/datum/callback` that gets executed by the `SEND_SIGNAL` macro for the corresponding signal. As such, `usr` is the mob that triggered the chain of procs leading to the invocation of `SEND_SIGNAL`. +Logically equivalent to the statement `list.Join(glue, start, end)`. ---- +### remove(list: userdata, ...any): integer -## Execution Limit +Logically equivalent to the DM statement `list.Remove(...)`. -In order to prevent freezing the server with infinite loops, auxlua enforces an execution limit, defaulting to 100ms. When a single lua state has been executing for longer than this limit, it will eventually stop and produce an error. +### remove_all(list: userdata, ...any): integer -To avoid exceeding the execution limit, call `sleep()` or `coroutine.yield()` before the execution limit is reached. +Logically equivalent to the DM statement `list.RemoveAll(...)`. -### over_exec_usage(fraction = 0.95) +### splice(list: userdata, start?: integer, end?: integer, ...any): () -This function returns whether the current run of the Lua VM has executed for longer than the specified fraction of the execution limit. You can use this function to branch to a call to `sleep()` or `coroutine.yield()` to maximize the amount of work done in a single run of the Lua VM. If nil, `fraction` will default to 0.95, otherwise, it will be clamped to the range \[0, 1\]. +Logically equivalent to the DM statement `list.Splice(start, end, ...)`. ---- +### swap(list: userdata, index_1: integer, index_2: integer): () -## Task management -The Lua Scripting subsystem manages the execution of tasks for each Lua state. A single fire of the subsystem behaves as follows: -- All tasks that slept since the last fire are resumed in the order they slept. -- For each queued resume, the corresponding task is resumed. +Logically equivalent to the DM statement `list.Swap(index_1, index_2)`. -### sleep() -Yields the current thread, scheduling it to be resumed during the next fire of SSlua. Use this function to prevent your Lua code from exceeding its allowed execution duration. Under the hood, `sleep` performs the following: +### to_table(list: userdata, deep?: boolean): table -- Sets the [`sleep_flag`](#sleep_flag) -- Calls `coroutine.yield()` -- Clears the sleep flag when determining whether the task slept or yielded -- Ignores the return values of `coroutine.yield()` once resumed +Creates a table that is a copy of `list`. If `deep` is true, `to_table` will be called on any lists inside that list. ---- +### from_table(table: table): userdata -## The SS13 package +Creates a list that is a copy of `table`. This is not strictly necessary, as tables are automatically converted to lists when passed back into DM, using the same internal logic as `from_table`. + +### filter(list: userdata, path: string): userdata + +Returns a copy of `list`, containing only elements that are objects descended from `path`. + +## pointer + +The `pointer` module contains utility functions for interacting with pointers. +Keep in mind that passing DM pointers into luau and manipulating them in this way can bypass wrapper procs. + +### read(pointer: userdata): any + +Gets the underlying data the pointer references. + +### write(pointer: userdata, value: any): () + +Writes `value` to the underlying data the pointer references. + +### unwrap(possible_pointer: any): any + +If `possible_pointer` is a pointer, reads it. Otherwise, it is returned as-is. + +# The SS13 package The `SS13` package contains various helper functions that use code specific to tgstation. -### SS13.state +## SS13.state A reference to the state datum (`/datum/lua_state`) handling this Lua state. -### SS13.get_runner_ckey() +## SS13.get_runner_ckey() The ckey of the user who ran the lua script in the current context. Can be unreliable if accessed after sleeping. -### SS13.get_runner_client() +## SS13.get_runner_client() Returns the client of the user who ran the lua script in the current context. Can be unreliable if accessed after sleeping. -### SS13.global_proc +## SS13.global_proc A wrapper for the magic string used to tell `WrapAdminProcCall` to call a global proc. For instance, `/datum/callback` must be instantiated with `SS13.global_proc` as its first argument to specify that it will be invoking a global proc. The following example declares a callback which will execute the global proc `to_chat`: @@ -152,25 +227,18 @@ The following example declares a callback which will execute the global proc `to local callback = SS13.new("/datum/callback", SS13.global_proc, "to_chat", dm.world, "Hello World") ``` -### SS13.istype(thing, type) +## SS13.istype(thing, type) Equivalent to the DM statement `istype(thing, text2path(type))`. -### SS13.new(type, ...) -Instantiates a datum of type `type` with `...` as the arguments passed to `/proc/_new` -The following example spawns a singularity at the caller's current turf: -```lua -SS13.new("/obj/singularity", dm.global_proc("_get_step", dm.usr, 0)) -``` - -### SS13.new_untracked(type, ...) -Works exactly like SS13.new but it does not store the value to the lua state's `references` list variable. This means that the variable could end up deleted if nothing holds a reference to it. +## SS13.new(type, ...) +An alias for `dm.new` -### SS13.is_valid(datum) +## SS13.is_valid(datum) Can be used to determine if the datum passed is not nil, not undefined and not qdel'd all in one. A helper function that allows you to check the validity from only one function. Example usage: ```lua local datum = SS13.new("/datum") -dm.global_proc("qdel", datum) +dm.global_procs.qdel(datum) print(SS13.is_valid(datum)) -- false local null = nil @@ -180,13 +248,13 @@ local datum = SS13.new("/datum") print(SS13.is_valid(datum)) -- true ``` -### SS13.type(string) -Converts a string into a type. Equivalent to doing `dm.global_proc("_text2path", "/path/to/type")` +## SS13.type(string) +Converts a string into a typepath. Equivalent to doing `dm.global_proc("_text2path", "/path/to/type")` -### SS13.qdel(datum) +## SS13.qdel(datum) Deletes a datum. You shouldn't try to reference it after calling this function. Equivalent to doing `dm.global_proc("qdel", datum)` -### SS13.await(thing_to_call, proc_to_call, ...) +## SS13.await(thing_to_call, proc_to_call, ...) Calls `proc_to_call` on `thing_to_call`, with `...` as its arguments, and sleeps until that proc returns. Returns two return values - the first is the return value of the proc, and the second is the message of any runtime exception thrown by the called proc. The following example calls and awaits the return of `poll_ghost_candidates`: @@ -194,59 +262,59 @@ The following example calls and awaits the return of `poll_ghost_candidates`: local ghosts, runtime = SS13.await(SS13.global_proc, "poll_ghost_candidates", "Would you like to be considered for something?") ``` -### SS13.wait(time, timer) +## SS13.wait(time, timer) Waits for a number of **seconds** specified with the `time` argument. You can optionally specify a timer subsystem using the `timer` argument. Internally, this function creates a timer that will resume the current task after `time` seconds, then yields the current task by calling `coroutine.yield` with no arguments and ignores the return values. If the task is prematurely resumed, the timer will be safely deleted. -### SS13.register_signal(datum, signal, func, make_easy_clear_function) +## SS13.register_signal(datum, signal, func) Registers the Lua function `func` as a handler for `signal` on `datum`. Like with signal handlers written in DM, Lua signal handlers should not sleep (either by calling `sleep` or `coroutine.yield`). -If `make_easy_clear_function` is truthy, a member function taking no arguments will be created in the `SS13` table to easily unregister the signal handler. - -This function returns the `/datum/callback` created to call `func` from DM. +This function returns whether the signal registration was successful. The following example defines a function which will register a signal that makes `target` make a honking sound any time it moves: ```lua function honk(target) SS13.register_signal(target, "movable_moved", function(source) - dm.global_proc("playsound", target, "sound/items/bikehorn.ogg", 100, true) + dm.global_procs.playsound(target, "sound/items/bikehorn.ogg", 100, true) end) end ``` -### SS13.unregister_signal(datum, signal, callback) -Unregister a signal previously registered using `SS13.register_signal`. `callback` should be a `datum/callback` previously returned by `SS13.register_signal`. If `callback` is not specified, **ALL** signal handlers registered on `datum` for `signal` will be unregistered. +NOTE: if `func` is an anonymous function declared inside the call to `SS13.register_signal`, it cannot be referenced in order to unregister that signal with `SS13.unregister_signal` -### SS13.set_timeout(time, func) +## SS13.unregister_signal(datum, signal, func) +Unregister a signal previously registered using `SS13.register_signal`. `func` must be a function for which a handler for the specified signal has already been registered. If `func` is `nil`, all handlers for that signal will be unregistered. + +## SS13.set_timeout(time, func) Creates a timer which will execute `func` after `time` **seconds**. `func` should not expect to be passed any arguments, as it will not be passed any. Unlike `SS13.wait`, `SS13.set_timeout` does not yield or sleep the current task, making it suitable for use in signal handlers for `SS13.register_signal` The following example will output a message to chat after 5 seconds: ```lua SS13.set_timeout(5, function() - dm.global_proc("to_chat", dm.world, "Hello World!") + dm.global_procs.to_chat(dm.world, "Hello World!") end) ``` -### SS13.start_loop(time, amount, func) +## SS13.start_loop(time, amount, func) Creates a timer which will execute `func` after `time` **seconds**. `func` should not expect to be passed any arguments, as it will not be passed any. Works exactly the same as `SS13.set_timeout` except it will loop the timer `amount` times. If `amount` is set to -1, it will loop indefinitely. Returns a number value, which represents the timer's id. Can be stopped with `SS13.end_loop` Returns a number, the timer id, which is needed to stop indefinite timers. The following example will output a message to chat every 5 seconds, repeating 10 times: ```lua SS13.start_loop(5, 10, function() - dm.global_proc("to_chat", dm.world, "Hello World!") + dm.global_procs.to_chat(dm.world, "Hello World!") end) ``` The following example will output a message to chat every 5 seconds, until `SS13.end_loop(timerid)` is called: ```lua local timerid = SS13.start_loop(5, -1, function() - dm.global_proc("to_chat", dm.world, "Hello World!") + dm.global_proc.to_chat(dm.world, "Hello World!") end) ``` -### SS13.end_loop(id) +## SS13.end_loop(id) Prematurely ends a loop that hasn't ended yet, created with `SS13.start_loop`. Silently fails if there is no started loop with the specified id. The following example will output a message to chat every 5 seconds and delete it after it has repeated 20 times: ```lua @@ -254,7 +322,7 @@ local repeated_amount = 0 -- timerid won't be in the looping function's scope if declared before the function is declared. local timerid timerid = SS13.start_loop(5, -1, function() - dm.global_proc("to_chat", dm.world, "Hello World!") + dm.global_procs.to_chat(dm.world, "Hello World!") repeated_amount += 1 if repeated_amount >= 20 then SS13.end_loop(timerid) @@ -262,35 +330,6 @@ timerid = SS13.start_loop(5, -1, function() end) ``` -### SS13.stop_all_loops() +## SS13.stop_all_loops() Stops all current running loops that haven't ended yet. Useful in case you accidentally left a indefinite loop running without storing the id anywhere. - -### SS13.stop_tracking(datum) -Stops tracking a datum that was created via `SS13.new` so that it can be garbage collected and deleted without having to qdel. Should be used for things like callbacks and other such datums where the reference to the variable is no longer needed. - ---- - -## Internal globals - -Auxlua defines several registry values for each state. Note that there is no way to access registry values from lua code. - -### sleep_flag - -This flag is used to designate that a yielding task should be put in the sleep queue instead of the yield table. Once auxlua determines that a task should sleep, `sleep_flag` is cleared. - -### sleep_queue - -A sequence of threads, each corresponding to a task that has slept. When calling `/proc/__lua_awaken`, auxlua will dequeue the first thread from the sequence and resume it. - -### yield_table - -A table of threads, each corresponding to a coroutine that has yielded. When calling `/proc/__lua_resume`, auxlua will look for a thread at the index specified in the `index` argument, and resume it with the arguments specified in the `arguments` argument. - -### task_info - -A table of key-value-pairs, where the keys are threads, and the values are tables consisting of the following fields: - -- name: A string containing the name of the task -- status: A string, either "sleep" or "yield" -- index: The task's index in `sleep_queue` or `yield_table` diff --git a/code/modules/admin/verbs/lua/_hooks.dm b/code/modules/admin/verbs/lua/_hooks.dm deleted file mode 100644 index a092947e06ec9..0000000000000 --- a/code/modules/admin/verbs/lua/_hooks.dm +++ /dev/null @@ -1,239 +0,0 @@ -/datum - var/__auxtools_weakref_id //used by auxtools for weak references - -/** - * Sets a global proc to call in place of just outright setting a datum's var to a given value - * - * The proc will be called with the arguments (datum/datum_to_modify, var_name, value) - * - * required wrapper text the name of the proc to use as the wrapper - */ -/proc/__lua_set_set_var_wrapper(wrapper) - CRASH("auxlua not loaded") - -/** - * Sets a global proc to call in place of just outright calling a given proc on a datum - * - * The proc will be called with the arguments (datum/thing_to_call, proc_to_call, list/arguments) - * - * required wrapper text the name of the proc to use as the wrapper - */ -/proc/__lua_set_datum_proc_call_wrapper(wrapper) - CRASH("auxlua not loaded") - -/** - * Sets a global proc to call in place of just outright calling a given global proc - * - * The proc will be called with the arguments (proc_to_call, list/arguments) - * - * required wrapper text the name of the proc to use as the wrapper - */ -/proc/__lua_set_global_proc_call_wrapper(wrapper) - CRASH("auxlua not loaded") - -/** - * Sets a global proc as a wrapper for lua's print function - * - * The proc will be called with the arguments (state_id, list/arguments) - * - * required wrapper text the name of the proc to use as the wrapper - */ -/proc/__lua_set_print_wrapper(wrapper) - CRASH("auxlua not loaded") - -/** - * Sets the maximum amount of time a lua chunk or function can execute without sleeping or yielding. - * Chunks/functions that exceed this duration will produce an error. - * - * required limit number the execution limit, in milliseconds - */ -/proc/__lua_set_execution_limit(limit) - CRASH("auxlua not loaded") - -/** - * Creates a new lua state. - * - * return text a pointer to the created state. - */ -/proc/__lua_new_state() - CRASH("auxlua not loaded") - -/** - * Loads a chunk of lua source code and executes it - * - * required state text a pointer to the state - * in which to execute the code - * required script text the lua source code to execute - * optional name text a name to give to the chunk - * - * return list|text a list of lua return information - * or an error message if the state was corrupted - * - * Lua return information is formatted as followed: - * - ["status"]: How the chunk or function stopped code execution - * - "sleeping": The chunk or function called dm.sleep, - * placing it in the sleep queue. Items in the sleep - * queue can be resumed using /proc/__lua_awaken - * - "yielded": The chunk or function called coroutine.yield, - * placing it in the yield table. Items in the yield - * table can can be resumed by passing their index - * to /proc/__lua_resume - * - "finished": The chunk or function finished - * - "errored": The chunk or function produced an error - * - "bad return": The chunk or function yielded or finished, - * but its return value could not be converted to DM values - * - ["param"]: Depends on status. - * - "sleeping": null - * - "yielded" or "finished": The return/yield value(s) - * - "errored" or "bad return": The error message - * - ["yield_index"]: The index in the yield table where the - * chunk or function is located, for calls to __lua_resume - * - ["name"]: The name of the chunk or function, for logging - */ -/proc/__lua_load(state, script, name) - CRASH("auxlua not loaded") - -/** - * Calls a lua function - * - * required state text a pointer to the state - * in which to call the function - * required function text the name of the function to call - * optional arguments list arguments to pass to the function - * - * return list|text a list of lua return information - * or an error message if the state was corrupted - * - * Lua return information is formatted as followed: - * - ["status"]: How the chunk or function stopped code execution - * - "sleeping": The chunk or function called dm.sleep, - * placing it in the sleep queue. Items in the sleep - * queue can be resumed using /proc/__lua_awaken - * - "yielded": The chunk or function called coroutine.yield, - * placing it in the yield table. Items in the yield - * table can can be resumed by passing their index - * to /proc/__lua_resume - * - "finished": The chunk or function finished - * - "errored": The chunk or function produced an error - * - "bad return": The chunk or function yielded or finished, - * but its return value could not be converted to DM values - * - ["param"]: Depends on status. - * - "sleeping": null - * - "yielded" or "finished": The return/yield value(s) - * - "errored" or "bad return": The error message - * - ["yield_index"]: The index in the yield table where the - * chunk or function is located, for calls to __lua_resume - * - ["name"]: The name of the chunk or function, for logging - */ -/proc/__lua_call(state, function, arguments) - CRASH("auxlua not loaded") - -/** - * Dequeues the task at the front of the sleep queue and resumes it - * - * required state text a pointer to the state in which - * to resume a task - * - * return list|text|null a list of lua return information, - * an error message if the state is corrupted, - * or null if the sleep queue is empty - * - * Lua return information is formatted as followed: - * - ["status"]: How the chunk or function stopped code execution - * - "sleeping": The chunk or function called dm.sleep, - * placing it in the sleep queue. Items in the sleep - * queue can be resumed using /proc/__lua_awaken - * - "yielded": The chunk or function called coroutine.yield, - * placing it in the yield table. Items in the yield - * table can can be resumed by passing their index - * to /proc/__lua_resume - * - "finished": The chunk or function finished - * - "errored": The chunk or function produced an error - * - "bad return": The chunk or function yielded or finished, - * but its return value could not be converted to DM values - * - ["param"]: Depends on status. - * - "sleeping": null - * - "yielded" or "finished": The return/yield value(s) - * - "errored" or "bad return": The error message - * - ["yield_index"]: The index in the yield table where the - * chunk or function is located, for calls to __lua_resume - * - ["name"]: The name of the chunk or function, for logging - */ -/proc/__lua_awaken(state) - CRASH("auxlua not loaded") - -/** - * Removes the task at the specified index from the yield table - * and resumes it - * - * required state text a pointer to the state in which to - * resume a task - * required index number the index in the yield table of the - * task to resume - * optional arguments list the arguments to resume the task with - * - * return list|text|null a list of lua return information, - * an error message if the state is corrupted, - * or null if there is no task at the specified index - * - * Lua return information is formatted as followed: - * - ["status"]: How the chunk or function stopped code execution - * - "sleeping": The chunk or function called dm.sleep, - * placing it in the sleep queue. Items in the sleep - * queue can be resumed using /proc/__lua_awaken - * - "yielded": The chunk or function called coroutine.yield, - * placing it in the yield table. Items in the yield - * table can can be resumed by passing their index - * to /proc/__lua_resume - * - "finished": The chunk or function finished - * - "errored": The chunk or function produced an error - * - "bad return": The chunk or function yielded or finished, - * but its return value could not be converted to DM values - * - ["param"]: Depends on status. - * - "sleeping": null - * - "yielded" or "finished": The return/yield value(s) - * - "errored" or "bad return": The error message - * - ["yield_index"]: The index in the yield table where the - * chunk or function is located, for calls to __lua_resume - * - ["name"]: The name of the chunk or function, for logging - */ -/proc/__lua_resume(state, index, arguments) - CRASH("auxlua not loaded") - -/** - * Get the variables within a state's environment. - * Values not convertible to DM values are substituted - * for their types as text - * - * required state text a pointer to the state - * to get the variables from - * - * return list the variables of the state's environment - */ -/proc/__lua_get_globals(state) - CRASH("auxlua not loaded") - -/** - * Get a list of all tasks currently in progress within a state - * - * required state text a pointer to the state - * to get the tasks from - * - * return list a list of the state's tasks, formatted as follows: - * - name: The name of the task - * - status: Whether the task is sleeping or yielding - * - index: The index of the task in the sleep queue - * or yield table, whichever is applicable - */ -/proc/__lua_get_tasks(state) - CRASH("auxlua not loaded") - -/** - * Kills a task in progress - * - * required state text a pointer to the state - * in which to kill a task - * required info list the task info - */ -/proc/__lua_kill_task(state, info) - CRASH("auxlua not loaded") diff --git a/code/modules/admin/verbs/lua/_wrappers.dm b/code/modules/admin/verbs/lua/_wrappers.dm index 8e05453d29d5d..d516f064f847f 100644 --- a/code/modules/admin/verbs/lua/_wrappers.dm +++ b/code/modules/admin/verbs/lua/_wrappers.dm @@ -1,3 +1,12 @@ +/proc/wrap_lua_get_var(datum/thing, var_name) + SHOULD_NOT_SLEEP(TRUE) + if(thing == world) + return world.vars[var_name] + if(ref(thing) == "\[0xe000001\]") //This weird fucking thing is like global.vars, but it's not a list and vars is not a valid index for it and I really don't fucking know. + return global.vars[var_name] + if(thing.can_vv_get(var_name)) + return thing.vars[var_name] + /proc/wrap_lua_set_var(datum/thing_to_set, var_name, value) SHOULD_NOT_SLEEP(TRUE) thing_to_set.vv_edit_var(var_name, value) @@ -11,8 +20,6 @@ ret = WrapAdminProcCall(thing_to_call, proc_name, arguments) else ret = HandleUserlessProcCall("lua", thing_to_call, proc_name, arguments) - if(isdatum(ret)) - SSlua.gc_guard += ret return ret /proc/wrap_lua_global_proc_call(proc_name, list/arguments) @@ -24,8 +31,6 @@ ret = WrapAdminProcCall(GLOBAL_PROC, proc_name, arguments) else ret = HandleUserlessProcCall("lua", GLOBAL_PROC, proc_name, arguments) - if(isdatum(ret)) - SSlua.gc_guard += ret return ret /proc/wrap_lua_print(state_id, list/arguments) @@ -38,6 +43,6 @@ if(!target_state) return var/print_message = jointext(arguments, "\t") - var/result = list("status" = "print", "param" = print_message) + var/result = list("status" = "print", "message" = print_message) INVOKE_ASYNC(target_state, TYPE_PROC_REF(/datum/lua_state, log_result), result, TRUE) log_lua("[target_state]: [print_message]") diff --git a/code/modules/admin/verbs/lua/helpers.dm b/code/modules/admin/verbs/lua/helpers.dm index 66b7c835e9ab1..c3072f15e74cd 100644 --- a/code/modules/admin/verbs/lua/helpers.dm +++ b/code/modules/admin/verbs/lua/helpers.dm @@ -3,27 +3,27 @@ #define PROMISE_REJECTED 2 /** - * Auxtools hooks act as "set waitfor = 0" procs. This means that whenever - * a proc directly called from auxtools sleeps, the hook returns with whatever + * Byondapi hooks act as "set waitfor = 0" procs. This means that whenever + * a proc directly called from an external library sleeps, the hook returns with whatever * the called proc had as its return value at the moment it slept. This may not * be desired behavior, so this datum exists to wrap these procs. * * Some procs that don't sleep could take longer than the execution limit would * allow for. We can wrap these in a promise as well. */ -/datum/auxtools_promise +/datum/promise var/datum/callback/callback var/return_value var/runtime_message var/status = PROMISE_PENDING -/datum/auxtools_promise/New(...) +/datum/promise/New(...) + if(!usr) + usr = GLOB.lua_usr callback = CALLBACK(arglist(args)) - perform() + INVOKE_ASYNC(src, PROC_REF(perform)) -/datum/auxtools_promise/proc/perform() - set waitfor = 0 - sleep() //In case we have to call a super-expensive non-sleeping proc (like getFlatIcon) +/datum/promise/proc/perform() try return_value = callback.Invoke() status = PROMISE_RESOLVED diff --git a/code/modules/admin/verbs/lua/lua_editor.dm b/code/modules/admin/verbs/lua/lua_editor.dm index d4a4bc2ee50b7..c0b37fd87c1ec 100644 --- a/code/modules/admin/verbs/lua/lua_editor.dm +++ b/code/modules/admin/verbs/lua/lua_editor.dm @@ -16,6 +16,12 @@ /// If set, we will force the editor to look at this chunk var/force_view_chunk + /// If set, we will force the script input to be this + var/force_input + + /// If set, the latest code execution performed from the editor raised an error, and this is the message from that error + var/last_error + /datum/lua_editor/New(state, _quick_log_index) . = ..() if(state) @@ -37,37 +43,51 @@ /datum/lua_editor/ui_state(mob/user) return GLOB.debug_state -/datum/lua_editor/ui_static_data(mob/user) - var/list/data = list() - data["documentation"] = file2text('code/modules/admin/verbs/lua/README.md') - data["auxtools_enabled"] = CONFIG_GET(flag/auxtools_enabled) - data["ss_lua_init"] = SSlua.initialized - return data - /datum/lua_editor/ui_data(mob/user) var/list/data = list() - if(!CONFIG_GET(flag/auxtools_enabled) || !SSlua.initialized) + data["ss_lua_init"] = SSlua.initialized + if(!SSlua.initialized) return data data["noStateYet"] = !current_state data["showGlobalTable"] = show_global_table if(current_state) if(current_state.log) - data["stateLog"] = kvpify_list(refify_list(current_state.log.Copy((page*50)+1, min((page+1)*50+1, current_state.log.len+1)))) + var/list/logs = current_state.log.Copy((page*50)+1, min((page+1)*50+1, current_state.log.len+1)) + for(var/i in 1 to logs.len) + var/list/log = logs[i] + log = log.Copy() + if(log["return_values"]) + log["return_values"] = kvpify_list(prepare_lua_editor_list(deep_copy_without_cycles(log["return_values"]))) + logs[i] = log + data["stateLog"] = logs data["page"] = page data["pageCount"] = CEILING(current_state.log.len/50, 1) data["tasks"] = current_state.get_tasks() if(show_global_table) current_state.get_globals() - data["globals"] = kvpify_list(refify_list(current_state.globals)) - data["states"] = SSlua.states - data["callArguments"] = kvpify_list(refify_list(arguments)) + var/list/values = current_state.globals["values"] + values = deep_copy_without_cycles(values) + values = prepare_lua_editor_list(values) + values = kvpify_list(values) + var/list/variants = current_state.globals["variants"] + data["globals"] = list("values" = values, "variants" = variants) + if(last_error) + data["lastError"] = last_error + last_error = null + data["states"] = list() + for(var/datum/lua_state/state as anything in SSlua.states) + data["states"] += state.display_name + data["callArguments"] = kvpify_list(prepare_lua_editor_list(deep_copy_without_cycles(arguments))) if(force_modal) data["forceModal"] = force_modal force_modal = null if(force_view_chunk) data["forceViewChunk"] = force_view_chunk force_view_chunk = null + if(force_input) + data["force_input"] = force_input + force_input = null return data /datum/lua_editor/proc/traverse_list(list/path, list/root, traversal_depth_offset = 0) @@ -99,6 +119,15 @@ else return root +/datum/lua_editor/proc/run_code(code) + var/ckey = usr.ckey + current_state.ckey_last_runner = ckey + var/result = current_state.load_script(code) + var/index_with_result = current_state.log_result(result) + if(result["status"] == "error") + last_error = result["message"] + message_admins("[key_name(usr)] executed [length(code)] bytes of lua code. [ADMIN_LUAVIEW_CHUNK(current_state, index_with_result)]") + /datum/lua_editor/ui_act(action, list/params) . = ..() if(.) @@ -116,6 +145,8 @@ if(!length(state_name)) return TRUE var/datum/lua_state/new_state = new(state_name) + if(QDELETED(new_state)) + return SSlua.states += new_state LAZYREMOVEASSOC(SSlua.editors, text_ref(current_state), src) current_state = new_state @@ -130,11 +161,14 @@ page = 0 return TRUE if("runCode") - var/code = params["code"] - current_state.ckey_last_runner = usr.ckey - var/result = current_state.load_script(code) - var/index_with_result = current_state.log_result(result) - message_admins("[key_name(usr)] executed [length(code)] bytes of lua code. [ADMIN_LUAVIEW_CHUNK(current_state, index_with_result)]") + run_code(params["code"]) + return TRUE + if("runFile") + var/code_file = input(usr, "Select a script to run.", "Lua") as file|null + if(!code_file) + return TRUE + var/code = file2text(code_file) + run_code(code) return TRUE if("moveArgUp") var/list/path = params["path"] @@ -168,27 +202,31 @@ return TRUE if("callFunction") var/list/recursive_indices = params["indices"] - var/list/current_list = kvpify_list(current_state.globals) + var/list/current_list = kvpify_list(current_state.globals["values"]) + var/list/current_variants = current_state.globals["variants"] var/function = list() while(LAZYLEN(recursive_indices)) var/index = popleft(recursive_indices) var/list/element = current_list[index] var/key = element["key"] var/value = element["value"] - if(!(istext(key) || isnum(key))) - to_chat(usr, span_warning("invalid key \[[key]] for function call (expected text or num)")) + var/list/variant_pair = current_variants[index] + var/key_variant = variant_pair["key"] + if(key_variant == "function" || key_variant == "thread" || key_variant == "userdata" || key_variant == "error_as_value") + to_chat(usr, span_warning("invalid table key \[[key]] for function call (expected text, num, path, list, or ref, got [key_variant])")) return function += key if(islist(value)) current_list = value + current_variants = variant_pair["value"] else - var/regex/function_regex = regex("^function: 0x\[0-9a-fA-F]+$") - if(function_regex.Find(value)) - break - to_chat(usr, span_warning("invalid path element \[[value]] for function call (expected list or text matching [function_regex])")) - return + if(variant_pair["value"] != "function") + to_chat(usr, span_warning("invalid value \[[value]] for function call (expected list or function)")) + return var/result = current_state.call_function(arglist(list(function) + arguments)) current_state.log_result(result) + if(result["status"] == "error") + last_error = result["message"] arguments.Cut() return TRUE if("resumeTask") @@ -197,20 +235,21 @@ arguments.Cut() return TRUE if("killTask") - var/task_info = params["info"] - SSlua.kill_task(current_state, task_info) + var/is_sleep = params["is_sleep"] + var/index = params["index"] + SSlua.kill_task(current_state, is_sleep, index) return TRUE if("vvReturnValue") var/log_entry_index = params["entryIndex"] var/list/log_entry = current_state.log[log_entry_index] - var/thing_to_debug = traverse_list(params["tableIndices"], log_entry["param"]) + var/thing_to_debug = traverse_list(params["indices"], log_entry["return_values"]) if(isweakref(thing_to_debug)) var/datum/weakref/ref = thing_to_debug thing_to_debug = ref.resolve() INVOKE_ASYNC(usr.client, TYPE_PROC_REF(/client, debug_variables), thing_to_debug) return FALSE if("vvGlobal") - var/thing_to_debug = traverse_list(params["indices"], current_state.globals) + var/thing_to_debug = traverse_list(params["indices"], current_state.globals["values"]) if(isweakref(thing_to_debug)) var/datum/weakref/ref = thing_to_debug thing_to_debug = ref.resolve() @@ -228,6 +267,9 @@ if("previousPage") page = max(page-1, 0) return TRUE + if("nukeLog") + current_state.log.Cut() + return TRUE /datum/lua_editor/ui_close(mob/user) . = ..() diff --git a/code/modules/admin/verbs/lua/lua_state.dm b/code/modules/admin/verbs/lua/lua_state.dm index bf2bcbd5a9003..b3ede12238444 100644 --- a/code/modules/admin/verbs/lua/lua_state.dm +++ b/code/modules/admin/verbs/lua/lua_state.dm @@ -1,15 +1,15 @@ #define MAX_LOG_REPEAT_LOOKBACK 5 -GLOBAL_VAR_INIT(IsLuaCall, FALSE) -GLOBAL_PROTECT(IsLuaCall) - GLOBAL_DATUM(lua_usr, /mob) GLOBAL_PROTECT(lua_usr) +GLOBAL_LIST_EMPTY_TYPED(lua_state_stack, /datum/weakref) +GLOBAL_PROTECT(lua_state_stack) + /datum/lua_state - var/name + var/display_name - /// The internal ID of the lua state stored in auxlua's global map + /// The internal ID of the lua state stored in dreamluau's state list var/internal_id /// A log of every return, yield, and error for each chunk execution and function call @@ -18,9 +18,6 @@ GLOBAL_PROTECT(lua_usr) /// A list of all the variables in the state's environment var/list/globals = list() - /// A list in which to store datums and lists instantiated in lua, ensuring that they don't get garbage collected - var/list/references = list() - /// Ckey of the last user who ran a script on this lua state. var/ckey_last_runner = "" @@ -39,55 +36,65 @@ GLOBAL_PROTECT(lua_usr) if(SSlua.initialized != TRUE) qdel(src) return - name = _name - internal_id = __lua_new_state() + display_name = _name + internal_id = DREAMLUAU_NEW_STATE() + if(!isnum(internal_id)) + stack_trace(internal_id) + qdel(src) /datum/lua_state/proc/check_if_slept(result) - if(result["status"] == "sleeping") + if(result["status"] == "sleep") SSlua.sleeps += src /datum/lua_state/proc/log_result(result, verbose = TRUE) if(!islist(result)) return - if(!verbose && result["status"] != "errored" && result["status"] != "bad return" \ - && !(result["name"] == "input" && (result["status"] == "finished" || length(result["param"])))) + var/status = result["status"] + if(!verbose && status != "error" && status != "panic" && status != "runtime" && !(result["name"] == "input" && (status == "finished" || length(result["return_values"])))) return var/append_to_log = TRUE var/index_of_log if(log.len) for(var/index in log.len to max(log.len - MAX_LOG_REPEAT_LOOKBACK, 1) step -1) var/list/entry = log[index] - if(entry["status"] == result["status"] \ - && entry["chunk"] == result["chunk"] \ - && entry["name"] == result["name"] \ - && ((entry["param"] == result["param"]) || deep_compare_list(entry["param"], result["param"]))) - if(!entry["repeats"]) - entry["repeats"] = 0 - index_of_log = index - entry["repeats"]++ - append_to_log = FALSE - break + if(!compare_lua_logs(entry, result)) + continue + if(!entry["repeats"]) + entry["repeats"] = 0 + index_of_log = index + entry["repeats"]++ + append_to_log = FALSE + break if(append_to_log) - if(islist(result["param"])) - result["param"] = weakrefify_list(encode_text_and_nulls(result["param"])) + if(islist(result["return_values"])) + add_lua_return_value_variants(result["return_values"], result["variants"]) + result["return_values"] = weakrefify_list(result["return_values"]) log += list(result) index_of_log = log.len INVOKE_ASYNC(src, TYPE_PROC_REF(/datum/lua_state, update_editors)) return index_of_log +/datum/lua_state/proc/parse_error(message, name) + if(copytext(message, 1, 7) == "PANIC:") + return list("status" = "panic", "message" = copytext(message, 7), "name" = name) + else + return list("status" = "error", "message" = message, "name" = name) + /datum/lua_state/proc/load_script(script) - GLOB.IsLuaCall = TRUE var/tmp_usr = GLOB.lua_usr GLOB.lua_usr = usr - var/result = __lua_load(internal_id, script) - GLOB.IsLuaCall = FALSE + DREAMLUAU_SET_USR + GLOB.lua_state_stack += WEAKREF(src) + var/result = DREAMLUAU_LOAD(internal_id, script, "input") + SSlua.needs_gc_cycle |= src + pop(GLOB.lua_state_stack) GLOB.lua_usr = tmp_usr // Internal errors unrelated to the code being executed are returned as text rather than lists if(isnull(result)) - result = list("status" = "errored", "param" = "__lua_load returned null (it may have runtimed - check the runtime logs)", "name" = "input") + result = list("status" = "error", "message" = "load returned null (it may have runtimed - check the runtime logs)", "name" = "input") if(istext(result)) - result = list("status" = "errored", "param" = result, "name" = "input") + result = parse_error(result, "input") result["chunk"] = script check_if_slept(result) @@ -109,67 +116,106 @@ GLOBAL_PROTECT(lua_usr) if(islist(function)) var/list/new_function_path = list() for(var/path_element in function) - new_function_path += path_element + if(isweakref(path_element)) + var/datum/weakref/weak_ref = path_element + var/resolved = weak_ref.hard_resolve() + if(!resolved) + return list("status" = "error", "message" = "Weakref in function path ([weak_ref] [text_ref(weak_ref)]) resolved to null.", "name" = jointext(function, ".")) + new_function_path += resolved + else + new_function_path += path_element function = new_function_path + else + function = list(function) var/tmp_usr = GLOB.lua_usr GLOB.lua_usr = usr - GLOB.IsLuaCall = TRUE - var/result = __lua_call(internal_id, function, call_args) - GLOB.IsLuaCall = FALSE + DREAMLUAU_SET_USR + GLOB.lua_state_stack += WEAKREF(src) + var/result = DREAMLUAU_CALL_FUNCTION(internal_id, function, call_args) + SSlua.needs_gc_cycle |= src + pop(GLOB.lua_state_stack) GLOB.lua_usr = tmp_usr if(isnull(result)) - result = list("status" = "errored", "param" = "__lua_call returned null (it may have runtimed - check the runtime logs)", "name" = islist(function) ? jointext(function, ".") : function) + result = list("status" = "error", "message" = "call_function returned null (it may have runtimed - check the runtime logs)", "name" = jointext(function, ".")) if(istext(result)) - result = list("status" = "errored", "param" = result, "name" = islist(function) ? jointext(function, ".") : function) + result = parse_error(result, jointext(function, ".")) check_if_slept(result) return result /datum/lua_state/proc/call_function_return_first(function, ...) + SHOULD_NOT_SLEEP(TRUE) // This function is meant to be used for signal handlers. var/list/result = call_function(arglist(args)) - log_result(result, verbose = FALSE) + INVOKE_ASYNC(src, PROC_REF(log_result), deep_copy_list(result), /*verbose = */FALSE) if(length(result)) - if(islist(result["param"]) && length(result["param"])) - return result["param"][1] + if(islist(result["return_values"]) && length(result["return_values"])) + var/return_value = result["return_values"][1] + var/variant = (islist(result["variants"]) && length(result["variants"])) && result["variants"][1] + if(islist(return_value) && islist(variant)) + remove_non_dm_variants(return_value, variant) + return return_value /datum/lua_state/proc/awaken() - GLOB.IsLuaCall = TRUE - var/result = __lua_awaken(internal_id) - GLOB.IsLuaCall = FALSE + DREAMLUAU_SET_USR + GLOB.lua_state_stack += WEAKREF(src) + var/result = DREAMLUAU_AWAKEN(internal_id) + SSlua.needs_gc_cycle |= src + pop(GLOB.lua_state_stack) if(isnull(result)) - result = list("status" = "errored", "param" = "__lua_awaken returned null (it may have runtimed - check the runtime logs)", "name" = "An attempted awaken") + result = list("status" = "error", "message" = "awaken returned null (it may have runtimed - check the runtime logs)", "name" = "An attempted awaken") if(istext(result)) - result = list("status" = "errored", "param" = result, "name" = "An attempted awaken") + result = parse_error(result, "An attempted awaken") check_if_slept(result) return result /// Prefer calling SSlua.queue_resume over directly calling this /datum/lua_state/proc/resume(index, ...) var/call_args = length(args) > 1 ? args.Copy(2) : list() - var/msg = "[key_name(usr)] resumed a lua coroutine with arguments: [english_list(call_args)]" - log_lua(msg) - GLOB.IsLuaCall = TRUE - var/result = __lua_resume(internal_id, index, call_args) - GLOB.IsLuaCall = FALSE + DREAMLUAU_SET_USR + GLOB.lua_state_stack += WEAKREF(src) + var/result = DREAMLUAU_RESUME(internal_id, index, call_args) + SSlua.needs_gc_cycle |= src + pop(GLOB.lua_state_stack) if(isnull(result)) - result = list("status" = "errored", "param" = "__lua_resume returned null (it may have runtimed - check the runtime logs)", "name" = "An attempted resume") + result = list("status" = "error", "param" = "resume returned null (it may have runtimed - check the runtime logs)", "name" = "An attempted resume") if(istext(result)) - result = list("status" = "errored", "param" = result, "name" = "An attempted resume") + result = parse_error(result, "An attempted resumt") check_if_slept(result) return result /datum/lua_state/proc/get_globals() - globals = weakrefify_list(encode_text_and_nulls(__lua_get_globals(internal_id))) + var/result = DREAMLUAU_GET_GLOBALS(internal_id) + if(isnull(result)) + CRASH("get_globals returned null") + if(istext(result)) + CRASH(result) + var/list/new_globals = result + var/list/values = new_globals["values"] + var/list/variants = new_globals["variants"] + add_lua_editor_variants(values, variants) + globals = list("values" = weakrefify_list(values), "variants" = variants) /datum/lua_state/proc/get_tasks() - return __lua_get_tasks(internal_id) + var/result = DREAMLUAU_LIST_THREADS(internal_id) + if(isnull(result)) + CRASH("list_threads returned null") + if(istext(result)) + CRASH(result) + return result + +/datum/lua_state/proc/kill_task(is_sleep, index) + var/result = is_sleep ? DREAMLUAU_KILL_SLEEPING_THREAD(internal_id, index) : DREAMLUAU_KILL_YIELDED_THREAD(internal_id, index) + SSlua.needs_gc_cycle |= src + return result -/datum/lua_state/proc/kill_task(task_info) - __lua_kill_task(internal_id, task_info) +/datum/lua_state/proc/collect_garbage() + var/result = DREAMLUAU_COLLECT_GARBAGE(internal_id) + if(!isnull(result)) + CRASH(result) /datum/lua_state/proc/update_editors() var/list/editor_list = LAZYACCESS(SSlua.editors, text_ref(src)) @@ -177,18 +223,4 @@ GLOBAL_PROTECT(lua_usr) for(var/datum/lua_editor/editor as anything in editor_list) SStgui.update_uis(editor) -/// Called by lua scripts when they add an atom to var/list/references so that it gets cleared up on delete. -/datum/lua_state/proc/clear_on_delete(datum/to_clear) - RegisterSignal(to_clear, COMSIG_QDELETING, PROC_REF(on_delete)) - -/// Called by lua scripts when an atom they've added should soft delete and this state should stop tracking it. -/// Needs to unregister all signals. -/datum/lua_state/proc/let_soft_delete(datum/to_clear) - UnregisterSignal(to_clear, COMSIG_QDELETING, PROC_REF(on_delete)) - references -= to_clear - -/datum/lua_state/proc/on_delete(datum/to_clear) - SIGNAL_HANDLER - references -= to_clear - #undef MAX_LOG_REPEAT_LOOKBACK diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm index 16c8bde219c8e..6cab092280000 100644 --- a/code/modules/antagonists/heretic/heretic_knowledge.dm +++ b/code/modules/antagonists/heretic/heretic_knowledge.dm @@ -640,7 +640,6 @@ /obj/item/restraints/handcuffs/cable/zipties, /obj/item/circular_saw, /obj/item/scalpel, - /obj/item/binoculars, /obj/item/clothing/gloves/color/yellow, /obj/item/melee/baton/security, /obj/item/clothing/glasses/sunglasses, diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm index bc6cb750b219a..2952eb1daed8c 100644 --- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm +++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm @@ -87,7 +87,7 @@ GLOBAL_LIST_EMPTY(heretic_sacrifice_landmarks) has_gravity = STANDARD_GRAVITY ambience_index = AMBIENCE_SPOOKY sound_environment = SOUND_ENVIRONMENT_CAVE - area_flags = UNIQUE_AREA | NOTELEPORT | HIDDEN_AREA | BLOCK_SUICIDE + area_flags = UNIQUE_AREA | NOTELEPORT | HIDDEN_AREA | BLOCK_SUICIDE | NO_BOH /area/centcom/heretic_sacrifice/Initialize(mapload) if(!ambientsounds) diff --git a/code/modules/antagonists/nukeop/outfits.dm b/code/modules/antagonists/nukeop/outfits.dm index 0d122cff0c061..4f7b8804f8a8e 100644 --- a/code/modules/antagonists/nukeop/outfits.dm +++ b/code/modules/antagonists/nukeop/outfits.dm @@ -102,8 +102,8 @@ name = "Syndicate Operative - Reinforcement" tc = 0 backpack_contents = list( - /obj/item/gun/ballistic/automatic/plastikov = 1, - /obj/item/ammo_box/magazine/plastikov9mm = 2, + /obj/item/gun/ballistic/automatic/smartgun = 1, + /obj/item/ammo_box/magazine/smartgun = 2, ) var/faction = "The Syndicate" diff --git a/code/modules/antagonists/voidwalker/voidwalker_loot.dm b/code/modules/antagonists/voidwalker/voidwalker_loot.dm index 8d3420d0a5278..73ed9c7cd2207 100644 --- a/code/modules/antagonists/voidwalker/voidwalker_loot.dm +++ b/code/modules/antagonists/voidwalker/voidwalker_loot.dm @@ -6,6 +6,9 @@ icon = 'icons/obj/weapons/voidwalker_items.dmi' icon_state = "cosmic_skull_charged" + light_on = TRUE + light_color = "#CC00CC" + light_range = 3 /// Icon state for when drained var/drained_icon_state = "cosmic_skull_drained" /// How many uses does it have left? @@ -37,3 +40,4 @@ uses-- if(uses <= 0 ) icon_state = drained_icon_state + light_on = FALSE diff --git a/code/modules/awaymissions/zlevel.dm b/code/modules/awaymissions/zlevel.dm index 0fdc5093511f2..b72a6b59dde36 100644 --- a/code/modules/awaymissions/zlevel.dm +++ b/code/modules/awaymissions/zlevel.dm @@ -32,15 +32,51 @@ GLOBAL_LIST_INIT(potentialConfigRandomZlevels, generate_map_list_from_directory( current = D if(!current) current = new + current.name = name current.id = id if(delay) - current.wait = CONFIG_GET(number/gateway_delay) + var/list/waits_by_id = CONFIG_GET(keyed_list/gateway_delays_by_id) + var/wait_to_use = !isnull(waits_by_id[id]) ? waits_by_id[id] : CONFIG_GET(number/gateway_delay) + current.wait = wait_to_use GLOB.gateway_destinations += current current.target_turfs += get_turf(src) + return INITIALIZE_HINT_QDEL /obj/effect/landmark/awaystart/nodelay delay = FALSE +/obj/effect/landmark/awaystart/beach + name = "beach away spawn" + id = AWAYSTART_BEACH + +/obj/effect/landmark/awaystart/museum + name = "buseum away spawn" + id = AWAYSTART_MUSEUM + +/obj/effect/landmark/awaystart/research + name = "research outpost away spawn" + id = AWAYSTART_RESEARCH + +/obj/effect/landmark/awaystart/caves + name = "caves away spawn" + id = AWAYSTART_CAVES + +/obj/effect/landmark/awaystart/moonoutpost + name = "Moon Outpost 19 away spawn" + id = AWAYSTART_MOONOUTPOST + +/obj/effect/landmark/awaystart/snowcabin + name = "snow cabin away spawn" + id = AWAYSTART_SNOWCABIN + +/obj/effect/landmark/awaystart/snowdin + name = "Snowdin away spawn" + id = AWAYSTART_SNOWDIN + +/obj/effect/landmark/awaystart/underground + name = "Underground Outpost 45 away spawn" + id = AWAYSTART_UNDERGROUND + /proc/generateMapList(filename) . = list() filename = "[global.config.directory]/[SANITIZE_FILENAME(filename)]" diff --git a/code/modules/error_handler/error_handler.dm b/code/modules/error_handler/error_handler.dm index a6841d3975444..1cf617ce4513e 100644 --- a/code/modules/error_handler/error_handler.dm +++ b/code/modules/error_handler/error_handler.dm @@ -101,6 +101,12 @@ GLOBAL_VAR_INIT(total_runtimes_skipped, 0) // The proceeding mess will almost definitely break if error messages are ever changed var/list/splitlines = splittext(E.desc, "\n") var/list/desclines = list() + var/list/state_stack = GLOB.lua_state_stack + var/is_lua_call = length(state_stack) + var/list/lua_stacks = list() + if(is_lua_call) + for(var/level in 1 to state_stack.len) + lua_stacks += list(splittext(DREAMLUAU_GET_TRACEBACK(level), "\n")) if(LAZYLEN(splitlines) > ERROR_USEFUL_LEN) // If there aren't at least three lines, there's no info for(var/line in splitlines) if(LAZYLEN(line) < 3 || findtext(line, "source file:") || findtext(line, "usr.loc:")) @@ -110,13 +116,14 @@ GLOBAL_VAR_INIT(total_runtimes_skipped, 0) desclines.Add(usrinfo) usrinfo = null continue // Our usr info is better, replace it - if(copytext(line, 1, 3) != " ")//3 == length(" ") + 1 desclines += (" " + line) // Pad any unpadded lines, so they look pretty else desclines += line if(usrinfo) //If this info isn't null, it hasn't been added yet desclines.Add(usrinfo) + if(is_lua_call) + SSlua.log_involved_runtime(E, desclines, lua_stacks) if(silencing) desclines += " (This error will now be silenced for [DisplayTimeText(configured_error_silence_time)])" if(GLOB.error_cache) diff --git a/code/modules/fishing/admin.dm b/code/modules/fishing/admin.dm index 0a9cfc9d51dd9..ba5c29a7fd172 100644 --- a/code/modules/fishing/admin.dm +++ b/code/modules/fishing/admin.dm @@ -48,7 +48,7 @@ ADMIN_VERB(fishing_calculator, R_DEBUG, "Fishing Calculator", "A calculator... f if(hook_type) temporary_rod.set_slot(new hook_type(temporary_rod), ROD_SLOT_HOOK) if(line_type) - temporary_rod.set_slot(new line_type(temporary_rod), ROD_SLOT_HOOK) + temporary_rod.set_slot(new line_type(temporary_rod), ROD_SLOT_LINE) var/result_table = list() var/modified_table = spot.get_modified_fish_table(temporary_rod,user) diff --git a/code/modules/fishing/aquarium/aquarium.dm b/code/modules/fishing/aquarium/aquarium.dm index 97ce717b4c524..5afbf4b72d9a9 100644 --- a/code/modules/fishing/aquarium/aquarium.dm +++ b/code/modules/fishing/aquarium/aquarium.dm @@ -13,7 +13,7 @@ name = "aquarium" desc = "A vivarium in which aquatic fauna and flora are usually kept and displayed." density = TRUE - anchored = TRUE + anchored = FALSE icon = 'icons/obj/aquarium/tanks.dmi' icon_state = "aquarium_map" @@ -35,13 +35,13 @@ var/last_feeding /// Can fish reproduce in this quarium. - var/allow_breeding = FALSE + var/allow_breeding = TRUE //This is the area where fish can swim var/aquarium_zone_min_px = 2 var/aquarium_zone_max_px = 31 var/aquarium_zone_min_py = 10 - var/aquarium_zone_max_py = 24 + var/aquarium_zone_max_py = 28 var/list/fluid_types = list(AQUARIUM_FLUID_SALTWATER, AQUARIUM_FLUID_FRESHWATER, AQUARIUM_FLUID_SULPHWATEVER, AQUARIUM_FLUID_AIR) @@ -64,7 +64,7 @@ RegisterSignal(src, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked)) create_reagents(6, SEALED_CONTAINER) RegisterSignal(reagents, COMSIG_REAGENTS_NEW_REAGENT, PROC_REF(start_autofeed)) - AddComponent(/datum/component/plumbing/aquarium) + AddComponent(/datum/component/plumbing/aquarium, start = anchored) if(current_beauty) AddElement(/datum/element/beauty, current_beauty) ADD_KEEP_TOGETHER(src, INNATE_TRAIT) @@ -390,6 +390,9 @@ #undef AQUARIUM_BORDERS_LAYER #undef AQUARIUM_BELOW_GLASS_LAYER +/obj/structure/aquarium/lawyer + anchored = TRUE + /obj/structure/aquarium/lawyer/Initialize(mapload) . = ..() @@ -400,6 +403,9 @@ reagents.add_reagent(/datum/reagent/consumable/nutriment, 2) +/obj/structure/aquarium/prefilled + anchored = TRUE + /obj/structure/aquarium/prefilled/Initialize(mapload) . = ..() diff --git a/code/modules/fishing/fish/fish_types.dm b/code/modules/fishing/fish/fish_types.dm index 91c7bba5f3d9e..11f9ab317d25b 100644 --- a/code/modules/fishing/fish/fish_types.dm +++ b/code/modules/fishing/fish/fish_types.dm @@ -252,7 +252,7 @@ sprite_height = 9 sprite_width = 8 stable_population = 4 - feeding_frequency = 15 MINUTES + feeding_frequency = 10 MINUTES random_case_rarity = FISH_RARITY_RARE fillet_type = /obj/item/food/meat/slab/rawcrab required_temperature_min = MIN_AQUARIUM_TEMP+9 @@ -277,7 +277,7 @@ ///The lobstrosity type this matures into var/lob_type = /mob/living/basic/mining/lobstrosity/juvenile/lava ///at which rate the crab gains maturation - var/growth_rate = 100 / (12 MINUTES) * 10 + var/growth_rate = 100 / (10 MINUTES) * 10 ///A chasm crab growth speed is determined by its initial weight and size, ergo bigger crabs for faster lobstrosities /obj/item/fish/chasm_crab/update_size_and_weight(new_size = average_size, new_weight = average_weight) @@ -451,7 +451,7 @@ average_size = 20 average_weight = 400 health = 50 - breeding_timeout = 5 MINUTES + breeding_timeout = 2.5 MINUTES fish_traits = list(/datum/fish_trait/parthenogenesis, /datum/fish_trait/no_mating) required_temperature_min = MIN_AQUARIUM_TEMP+10 required_temperature_max = MIN_AQUARIUM_TEMP+40 diff --git a/code/modules/fishing/fishing_minigame.dm b/code/modules/fishing/fishing_minigame.dm index 0a6b6b7cc87a9..9c70d474a62d7 100644 --- a/code/modules/fishing/fishing_minigame.dm +++ b/code/modules/fishing/fishing_minigame.dm @@ -20,13 +20,15 @@ /// The minimum velocity required for the bait to bounce #define BAIT_MIN_VELOCITY_BOUNCE 150 /// The extra deceleration of velocity that happens when the bait switches direction -#define BAIT_DECELERATION_MULT 1.5 +#define BAIT_DECELERATION_MULT 1.8 /// Reduce initial completion rate depending on difficulty #define MAX_FISH_COMPLETION_MALUS 15 /// The window of time between biting phase and back to baiting phase #define BITING_TIME_WINDOW 4 SECONDS +/// The multiplier of how much the difficulty negatively impacts the bait height +#define BAIT_HEIGHT_DIFFICULTY_MALUS 1.3 ///Defines to know how the bait is moving on the minigame slider. #define REELING_STATE_IDLE 0 @@ -36,7 +38,7 @@ ///The pixel height of the minigame bar #define MINIGAME_SLIDER_HEIGHT 76 ///The standard pixel height of the bait -#define MINIGAME_BAIT_HEIGHT 24 +#define MINIGAME_BAIT_HEIGHT 27 ///The standard pixel height of the fish (minus a pixel on each direction for the sake of a better looking sprite) #define MINIGAME_FISH_HEIGHT 4 @@ -82,7 +84,7 @@ /// How much space the fish takes on the minigame slider var/fish_height = 50 /// How much space the bait takes on the minigame slider - var/bait_height = 320 + var/bait_height = 360 /// The height in pixels of the bait bar var/bait_pixel_height = MINIGAME_BAIT_HEIGHT /// The height in pixels of the fish @@ -182,14 +184,16 @@ if(rod.hook.fishing_hook_traits & FISHING_HOOK_KILL) special_effects |= FISHING_MINIGAME_RULE_KILL + completion_loss += user.mind?.get_skill_modifier(/datum/skill/fishing, SKILL_VALUE_MODIFIER)/5 + if(special_effects & FISHING_MINIGAME_RULE_KILL && ispath(reward_path,/obj/item/fish)) RegisterSignal(comp.fish_source, COMSIG_FISH_SOURCE_REWARD_DISPENSED, PROC_REF(hurt_fish)) difficulty += comp.fish_source.calculate_difficulty(reward_path, rod, user, src) - difficulty = clamp(round(difficulty), 1, 100) + difficulty = clamp(round(difficulty), FISHING_EASY_DIFFICULTY - 5, 100) if(difficulty > FISHING_EASY_DIFFICULTY) - completion -= round(MAX_FISH_COMPLETION_MALUS * (difficulty/100), 1) + completion -= MAX_FISH_COMPLETION_MALUS * (difficulty * 0.01) if(HAS_MIND_TRAIT(user, TRAIT_REVEAL_FISH)) fish_icon = GLOB.specific_fish_icons[reward_path] || "fish" @@ -211,7 +215,7 @@ else long_jump_chance *= difficulty - bait_height -= difficulty + bait_height -= round(difficulty * BAIT_HEIGHT_DIFFICULTY_MALUS) bait_pixel_height = round(MINIGAME_BAIT_HEIGHT * (bait_height/initial(bait_height)), 1) /datum/fishing_challenge/Destroy(force) @@ -277,11 +281,11 @@ //You need to be holding the rod to use it. if(LAZYACCESS(modifiers, SHIFT_CLICK) || LAZYACCESS(modifiers, CTRL_CLICK) || LAZYACCESS(modifiers, ALT_CLICK)) return - if(!source.get_active_held_item(used_rod) && !HAS_TRAIT(source, TRAIT_PROFOUND_FISHER)) + if(!HAS_TRAIT(source, TRAIT_PROFOUND_FISHER) && source.get_active_held_item() != used_rod) return - if(phase == WAIT_PHASE) //Reset wait + if(phase == WAIT_PHASE) send_alert("miss!") - start_baiting_phase(TRUE) + start_baiting_phase(TRUE) //Add in another 3 to 5 seconds for that blunder. else if(phase == BITING_PHASE) start_minigame_phase() return COMSIG_MOB_CANCEL_CLICKON @@ -329,7 +333,7 @@ if(penalty) wait_time = min(timeleft(next_phase_timer) + rand(3 SECONDS, 5 SECONDS), 30 SECONDS) else - wait_time = rand(1 SECONDS, 30 SECONDS) + wait_time = rand(3 SECONDS, 25 SECONDS) if(special_effects & FISHING_MINIGAME_AUTOREEL && wait_time >= 15 SECONDS) wait_time = max(wait_time - 7.5 SECONDS, 15 SECONDS) deltimer(next_phase_timer) @@ -409,7 +413,6 @@ completion *= 1.2 if(BITING_TIME_WINDOW - 0.5 SECONDS to BITING_TIME_WINDOW) completion *= 1.4 - completion = round(completion, 1) if(!prepare_minigame_hud()) return phase = MINIGAME_PHASE @@ -736,6 +739,8 @@ #undef MINIGAME_BAIT_HEIGHT #undef MINIGAME_FISH_HEIGHT +#undef BAIT_HEIGHT_DIFFICULTY_MALUS + #undef REELING_STATE_IDLE #undef REELING_STATE_UP #undef REELING_STATE_DOWN diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm index e6ac4bac8abae..c36ea48b15c02 100644 --- a/code/modules/fishing/fishing_rod.dm +++ b/code/modules/fishing/fishing_rod.dm @@ -101,7 +101,7 @@ /obj/item/fishing_rod/proc/consume_bait(atom/movable/reward) // catching things that aren't fish or alive mobs doesn't consume baits. - if(isnull(reward) || isnull(bait)) + if(isnull(reward) || isnull(bait) || HAS_TRAIT(bait, TRAIT_BAIT_UNCONSUMABLE)) return if(isliving(reward)) var/mob/living/caught_mob = reward @@ -536,7 +536,7 @@ ui_description = "This rod has an infinite supply of synth-bait. Also doubles as an Experi-Scanner for fish." icon_state = "fishing_rod_science" reel_overlay = "reel_science" - bait = /obj/item/food/bait/doughball/synthetic + bait = /obj/item/food/bait/doughball/synthetic/unconsumable /obj/item/fishing_rod/tech/Initialize(mapload) . = ..() @@ -558,9 +558,6 @@ . = ..() . += span_notice("Alt-Click to access the Experiment Configuration UI") -/obj/item/fishing_rod/tech/consume_bait(atom/movable/reward) - return - /obj/item/fishing_rod/tech/use_slot(slot, mob/user, obj/item/new_item) if(slot == ROD_SLOT_BAIT) return @@ -586,7 +583,9 @@ if(owner.hook) icon_state = owner.hook.icon_state transform = transform.Scale(1, -1) - return ..() + . = ..() + if(!QDELETED(src)) + our_line = owner.create_fishing_line(src) /obj/projectile/fishing_cast/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() @@ -594,10 +593,6 @@ QDEL_NULL(our_line) //we need to delete the old beam datum, otherwise it won't let you fish. owner.hook_hit(target) -/obj/projectile/fishing_cast/fire(angle, atom/direct_target) - . = ..() - our_line = owner.create_fishing_line(src) - /obj/projectile/fishing_cast/Destroy() QDEL_NULL(our_line) owner?.casting = FALSE diff --git a/code/modules/fishing/sources/_fish_source.dm b/code/modules/fishing/sources/_fish_source.dm index 602975ed75a00..feaa93424f9a8 100644 --- a/code/modules/fishing/sources/_fish_source.dm +++ b/code/modules/fishing/sources/_fish_source.dm @@ -97,7 +97,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, zebra_typecacheof(list( . += EXPERT_FISHER_DIFFICULTY_MOD // Difficulty modifier added by the fisher's skill level - if(!challenge || !(challenge.special_effects & FISHING_MINIGAME_RULE_NO_EXP)) + if(!(challenge?.special_effects & FISHING_MINIGAME_RULE_NO_EXP)) . += fisherman.mind?.get_skill_modifier(/datum/skill/fishing, SKILL_VALUE_MODIFIER) // Difficulty modifier added by the rod @@ -259,6 +259,9 @@ GLOBAL_LIST(fishing_property_cache) ///Multiplier used to make fishes more common compared to everything else. var/result_multiplier = 1 + + var/list/final_table = fish_table.Copy() + if(bait) if(HAS_TRAIT(bait, TRAIT_GREAT_QUALITY_BAIT)) result_multiplier = 9 @@ -269,10 +272,10 @@ GLOBAL_LIST(fishing_property_cache) else if(HAS_TRAIT(bait, TRAIT_BASIC_QUALITY_BAIT)) result_multiplier = 2 leveling_exponent = 0.1 + final_table -= FISHING_DUD var/list/fish_list_properties = collect_fish_properties() - var/list/final_table = fish_table.Copy() for(var/result in final_table) final_table[result] *= rod.hook?.get_hook_bonus_multiplicative(result) final_table[result] += rod.hook?.get_hook_bonus_additive(result)//Decide on order here so it can be multiplicative diff --git a/code/modules/fishing/sources/source_types.dm b/code/modules/fishing/sources/source_types.dm index 583aa3e3e7ea9..dd5602b12f99d 100644 --- a/code/modules/fishing/sources/source_types.dm +++ b/code/modules/fishing/sources/source_types.dm @@ -45,6 +45,7 @@ catalog_description = "Beach dimension (Fishing portal generator)" radial_name = "Beach" radial_state = "palm_beach" + overlay_state = "portal_beach" /datum/fish_source/portal/chasm background = "background_lavaland" diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index 46865f5bd9b9f..ed17254fa9b05 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -798,15 +798,7 @@ if(isnull(particles)) particles = new /particles/pollen() if(myseed.instability >= 20 && prob(70) && length(T.myseed.reagents_add)) - var/list/datum/plant_gene/reagent/possible_reagents = list() - for(var/datum/plant_gene/reagent/reag in T.myseed.genes) - possible_reagents += reag - var/datum/plant_gene/reagent/reagent_gene = pick(possible_reagents) //Let this serve as a lession to delete your WIP comments before merge. - if(reagent_gene.can_add(myseed)) - if(!reagent_gene.try_upgrade_gene(myseed)) - myseed.genes += reagent_gene.Copy() - myseed.reagents_from_genes() - continue + myseed.perform_reagent_pollination(T.myseed) if(!any_adjacent) particles = null diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm index 13a49b3518905..101239d8d31aa 100644 --- a/code/modules/hydroponics/seeds.dm +++ b/code/modules/hydroponics/seeds.dm @@ -616,3 +616,27 @@ /obj/item/grown/get_plant_seed() return seed + +/obj/item/seeds/proc/perform_reagent_pollination(obj/item/seeds/donor) + var/list/datum/plant_gene/reagent/valid_reagents = list() + for(var/datum/plant_gene/reagent/donor_reagent in donor.genes) + var/repeated = FALSE + for(var/datum/plant_gene/reagent/receptor_reagent in genes) + if(donor_reagent.reagent_id == receptor_reagent.reagent_id) + if(receptor_reagent.rate < donor_reagent.rate) + receptor_reagent.rate = donor_reagent.rate + // sucessful pollination/upgrade, we stop here. + reagents_from_genes() + return + else + repeated = TRUE + break + + if(!repeated) + valid_reagents += donor_reagent + + if(length(valid_reagents)) + // pick a valid reagent that our receptor seed don't have and add the gene to it + var/datum/plant_gene/reagent/selected_reagent = pick(valid_reagents) + genes += selected_reagent + reagents_from_genes() diff --git a/code/modules/lighting/lighting_setup.dm b/code/modules/lighting/lighting_setup.dm deleted file mode 100644 index c148530d1cd86..0000000000000 --- a/code/modules/lighting/lighting_setup.dm +++ /dev/null @@ -1,12 +0,0 @@ - -/proc/create_all_lighting_objects() - for(var/area/area as anything in GLOB.areas) - if(!area.static_lighting) - continue - for (var/list/zlevel_turfs as anything in area.get_zlevel_turf_lists()) - for(var/turf/area_turf as anything in zlevel_turfs) - if(area_turf.space_lit) - continue - new /datum/lighting_object(area_turf) - CHECK_TICK - CHECK_TICK diff --git a/code/modules/mob/living/basic/drone/drone_tools.dm b/code/modules/mob/living/basic/drone/drone_tools.dm index 1fa3aa7884b2a..32ec1bb152848 100644 --- a/code/modules/mob/living/basic/drone/drone_tools.dm +++ b/code/modules/mob/living/basic/drone/drone_tools.dm @@ -20,10 +20,11 @@ /obj/item/pipe_dispenser, /obj/item/t_scanner, /obj/item/analyzer, + /obj/item/soap/drone, ) atom_storage.max_total_storage = 40 atom_storage.max_specific_storage = WEIGHT_CLASS_NORMAL - atom_storage.max_slots = 9 + atom_storage.max_slots = 10 atom_storage.rustle_sound = FALSE atom_storage.set_holdable(drone_builtins) @@ -39,7 +40,7 @@ builtintools += new /obj/item/pipe_dispenser(src) builtintools += new /obj/item/t_scanner(src) builtintools += new /obj/item/analyzer(src) - + builtintools += new /obj/item/soap/drone(src) for(var/obj/item/tool as anything in builtintools) tool.AddComponent(/datum/component/holderloving, src, TRUE) diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm index a06ecb9e9b040..031ec3d45256c 100644 --- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm @@ -160,7 +160,7 @@ /mob/living/basic/mining/lobstrosity/juvenile/Initialize(mapload) . = ..() - var/growth_step = 1000/(8 MINUTES) //It should take 8 minutes if you keep the happiness above 40% and at most 14 + var/growth_step = 1000/(7 MINUTES) //It should take 7-ish minutes if you keep the happiness above 40% and at most 12 AddComponent(\ /datum/component/growth_and_differentiation,\ growth_path = grow_type,\ diff --git a/code/modules/mob/living/basic/lavaland/mining.dm b/code/modules/mob/living/basic/lavaland/mining.dm index ca84e0d1d20c7..37b7dd118f4d9 100644 --- a/code/modules/mob/living/basic/lavaland/mining.dm +++ b/code/modules/mob/living/basic/lavaland/mining.dm @@ -36,6 +36,9 @@ drop_immediately = basic_mob_flags & DEL_ON_DEATH,\ ) RegisterSignal(src, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(check_ashwalker_peace_violation)) + // We add this to ensure that mobs will actually receive the above signal, as some will lack AI + // handling for retaliation and attack special cases + AddElement(/datum/element/relay_attackers) /mob/living/basic/mining/proc/add_ranged_armour(list/vulnerable_projectiles) AddElement(\ diff --git a/code/modules/mob/living/basic/minebots/minebot_ai.dm b/code/modules/mob/living/basic/minebots/minebot_ai.dm index 62aeaf3aa7923..31fed0ec1f32c 100644 --- a/code/modules/mob/living/basic/minebots/minebot_ai.dm +++ b/code/modules/mob/living/basic/minebots/minebot_ai.dm @@ -284,6 +284,10 @@ radial_icon = 'icons/obj/mining.dmi' radial_icon_state = "pickaxe" speech_commands = list("mine") + callout_type = /datum/callout_option/mine + +/datum/pet_command/automate_mining/valid_callout_target(mob/living/caller, datum/callout_option/callout, atom/target) + return ismineralturf(target) /datum/pet_command/automate_mining/execute_action(datum/ai_controller/controller) controller.set_blackboard_key(BB_AUTOMATED_MINING, TRUE) diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index bb3bf8f5c36b9..5e1bdf4282686 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -1514,6 +1514,14 @@ GLOBAL_LIST_EMPTY(features_by_species) /datum/species/proc/get_cry_sound(mob/living/carbon/human/human) return +/// Returns the species' sigh sound. +/datum/species/proc/get_sigh_sound(mob/living/carbon/human/human) + return + +/// Returns the species' sniff sound. +/datum/species/proc/get_sniff_sound(mob/living/carbon/human/human) + return + /// Returns the species' cough sound. /datum/species/proc/get_cough_sound(mob/living/carbon/human/human) return diff --git a/code/modules/mob/living/carbon/human/species_types/felinid.dm b/code/modules/mob/living/carbon/human/species_types/felinid.dm index bd8063b744ee6..b26bd476b4bc4 100644 --- a/code/modules/mob/living/carbon/human/species_types/felinid.dm +++ b/code/modules/mob/living/carbon/human/species_types/felinid.dm @@ -99,6 +99,17 @@ return 'sound/voice/human/female_sneeze1.ogg' return 'sound/voice/human/male_sneeze1.ogg' +/datum/species/human/felinid/get_sigh_sound(mob/living/carbon/human/felinid) + if(felinid.physique == FEMALE) + return 'sound/voice/human/female_sigh.ogg' + return 'sound/voice/human/male_sigh.ogg' + +/datum/species/human/felinid/get_sniff_sound(mob/living/carbon/human/felinid) + if(felinid.physique == FEMALE) + return 'sound/voice/human/female_sniff.ogg' + return 'sound/voice/human/male_sniff.ogg' + + /proc/mass_purrbation() for(var/mob in GLOB.human_list) diff --git a/code/modules/mob/living/carbon/human/species_types/humans.dm b/code/modules/mob/living/carbon/human/species_types/humans.dm index 98a4518c4fa2e..ef8140c7d82ef 100644 --- a/code/modules/mob/living/carbon/human/species_types/humans.dm +++ b/code/modules/mob/living/carbon/human/species_types/humans.dm @@ -79,6 +79,16 @@ 'sound/voice/human/manlaugh2.ogg', ) +/datum/species/human/get_sigh_sound(mob/living/carbon/human/human) + if(human.physique == FEMALE) + return 'sound/voice/human/female_sigh.ogg' + return 'sound/voice/human/male_sigh.ogg' + +/datum/species/human/get_sniff_sound(mob/living/carbon/human/human) + if(human.physique == FEMALE) + return 'sound/voice/human/female_sniff.ogg' + return 'sound/voice/human/male_sniff.ogg' + /datum/species/human/get_species_description() return "Humans are the dominant species in the known galaxy. \ Their kind extend from old Earth to the edges of known space." diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm index 2a83efbda3e7e..6b71a234d9b61 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -101,6 +101,16 @@ /datum/species/lizard/get_laugh_sound(mob/living/carbon/human/lizard) return 'sound/voice/lizard/lizard_laugh1.ogg' +/datum/species/lizard/get_sigh_sound(mob/living/carbon/human/lizard) + if(lizard.physique == FEMALE) + return 'sound/voice/human/female_sigh.ogg' + return 'sound/voice/human/male_sigh.ogg' + +/datum/species/lizard/get_sniff_sound(mob/living/carbon/human/lizard) + if(lizard.physique == FEMALE) + return 'sound/voice/human/female_sniff.ogg' + return 'sound/voice/human/male_sniff.ogg' + /datum/species/lizard/get_physical_attributes() return "Lizardpeople can withstand slightly higher temperatures than most species, but they are very vulnerable to the cold \ and can't regulate their body-temperature internally, making the vacuum of space extremely deadly to them." diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm index 7b621971e1819..0caa0d996ba1b 100644 --- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm +++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm @@ -98,6 +98,16 @@ /datum/species/moth/get_laugh_sound(mob/living/carbon/human/moth) return 'sound/voice/moth/moth_laugh1.ogg' +/datum/species/moth/get_sigh_sound(mob/living/carbon/human/moth) + if(moth.physique == FEMALE) + return 'sound/voice/human/female_sigh.ogg' + return 'sound/voice/human/male_sigh.ogg' + +/datum/species/moth/get_sniff_sound(mob/living/carbon/human/moth) + if(moth.physique == FEMALE) + return 'sound/voice/human/female_sniff.ogg' + return 'sound/voice/human/male_sniff.ogg' + /datum/species/moth/get_physical_attributes() return "Moths have large and fluffy wings, which help them navigate the station if gravity is offline by pushing the air around them. \ Due to that, it isn't of much use out in space. Their eyes are very sensitive." diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm index b007da827da14..62030cf1eba59 100644 --- a/code/modules/mob/living/emote.dm +++ b/code/modules/mob/living/emote.dm @@ -395,6 +395,7 @@ message = "sighs." message_mime = "acts out an exaggerated silent sigh." emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE + vary = TRUE /datum/emote/living/sigh/run_emote(mob/living/user, params, type_override, intentional) . = ..() @@ -403,6 +404,11 @@ var/image/emote_animation = image('icons/mob/human/emote_visuals.dmi', user, "sigh") flick_overlay_global(emote_animation, GLOB.clients, 2.0 SECONDS) +/datum/emote/living/sigh/get_sound(mob/living/carbon/human/user) + if(!istype(user)) + return + return user.dna.species.get_sigh_sound(user) + /datum/emote/living/sit key = "sit" key_third_person = "sits" @@ -424,6 +430,13 @@ message = "sniffs." message_mime = "sniffs silently." emote_type = EMOTE_VISIBLE | EMOTE_AUDIBLE + vary = TRUE + +/datum/emote/living/sniff/get_sound(mob/living/carbon/human/user) + if(!istype(user)) + return + return user.dna.species.get_sniff_sound(user) + /datum/emote/living/snore key = "snore" diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm index 1d7f447493e07..459410d0a026a 100644 --- a/code/modules/mob/living/living_say.dm +++ b/code/modules/mob/living/living_say.dm @@ -251,7 +251,7 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( if(pressure < SOUND_MINIMUM_PRESSURE && !HAS_TRAIT(src, TRAIT_SIGN_LANG)) message_range = 1 - if(pressure < ONE_ATMOSPHERE*0.4) //Thin air, let's italicise the message + if(pressure < ONE_ATMOSPHERE * (HAS_TRAIT(src, TRAIT_SPEECH_BOOSTER) ? 0.1 : 0.4)) //Thin air, let's italicise the message unless we have a loud low pressure speech trait and not in vacuum spans |= SPAN_ITALICS send_speech(message, message_range, src, bubble_type, spans, language, message_mods, tts_message = tts_message, tts_filter = tts_filter)//roughly 58% of living/say()'s total cost diff --git a/code/modules/mod/mod_theme.dm b/code/modules/mod/mod_theme.dm index b3b0df28ed5d7..1699fc4e2e38e 100644 --- a/code/modules/mod/mod_theme.dm +++ b/code/modules/mod/mod_theme.dm @@ -533,7 +533,7 @@ ), ) -/datum/mod_theme/loader/New() +/datum/mod_theme/mining/New() .=..() allowed_suit_storage = GLOB.mining_suit_allowed diff --git a/code/modules/mod/modules/modules_general.dm b/code/modules/mod/modules/modules_general.dm index 18c89600573cf..e14fc483c0b9d 100644 --- a/code/modules/mod/modules/modules_general.dm +++ b/code/modules/mod/modules/modules_general.dm @@ -860,16 +860,10 @@ return ..() /obj/item/mod/module/recycler/on_activation() - . = ..() - if(!.) - return connector = AddComponent(/datum/component/connect_loc_behalf, mod.wearer, loc_connections) RegisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED, PROC_REF(on_wearer_moved)) /obj/item/mod/module/recycler/on_deactivation(display_message, deleting = FALSE) - . = ..() - if(!.) - return QDEL_NULL(connector) UnregisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED, PROC_REF(on_wearer_moved)) diff --git a/code/modules/mod/modules/modules_ninja.dm b/code/modules/mod/modules/modules_ninja.dm index 518e3683abf72..cfa95fb8a591e 100644 --- a/code/modules/mod/modules/modules_ninja.dm +++ b/code/modules/mod/modules/modules_ninja.dm @@ -72,15 +72,9 @@ cooldown_time = 3 SECONDS /obj/item/mod/module/stealth/ninja/on_activation() - . = ..() - if(!.) - return ADD_TRAIT(mod.wearer, TRAIT_SILENT_FOOTSTEPS, MOD_TRAIT) /obj/item/mod/module/stealth/ninja/on_deactivation(display_message = TRUE, deleting = FALSE) - . = ..() - if(!.) - return REMOVE_TRAIT(mod.wearer, TRAIT_SILENT_FOOTSTEPS, MOD_TRAIT) ///Camera Vision - Prevents flashes, blocks tracking. diff --git a/code/modules/mod/modules/modules_security.dm b/code/modules/mod/modules/modules_security.dm index 703cf197dc76d..752273fa0748c 100644 --- a/code/modules/mod/modules/modules_security.dm +++ b/code/modules/mod/modules/modules_security.dm @@ -331,9 +331,6 @@ RegisterSignal(dampening_field, COMSIG_DAMPENER_RELEASE, PROC_REF(release_projectile)) /obj/item/mod/module/projectile_dampener/on_deactivation(display_message, deleting = FALSE) - . = ..() - if(!.) - return QDEL_NULL(dampening_field) /obj/item/mod/module/projectile_dampener/proc/dampen_projectile(datum/source, obj/projectile/projectile) diff --git a/code/modules/projectiles/ammunition/ballistic/pistol.dm b/code/modules/projectiles/ammunition/ballistic/pistol.dm index a2f55f797bdb5..bc25970e7c364 100644 --- a/code/modules/projectiles/ammunition/ballistic/pistol.dm +++ b/code/modules/projectiles/ammunition/ballistic/pistol.dm @@ -26,7 +26,7 @@ desc = "A 10mm reaper bullet casing." projectile_type = /obj/projectile/bullet/c10mm/reaper -// 9mm (Makarov, Stechkin APS, PP-95) +// 9mm (Makarov, Stechkin APS) /obj/item/ammo_casing/c9mm name = "9mm bullet casing" @@ -49,7 +49,6 @@ desc = "A 9mm incendiary bullet casing." projectile_type = /obj/projectile/bullet/incendiary/c9mm - // .50AE (Desert Eagle) /obj/item/ammo_casing/a50ae @@ -57,3 +56,21 @@ desc = "A .50AE bullet casing." caliber = CALIBER_50AE projectile_type = /obj/projectile/bullet/a50ae + +// .160 Smart (Abielle smartgun) + +/obj/item/ammo_casing/c160smart + name = ".160 smart bullet casing" + desc = "A .160 smart bullet with a small charge of booster propellant at the bottom." + icon_state = "smartgun_casing" + caliber = CALIBER_160SMART + projectile_type = /obj/projectile/bullet/c160smart + +/obj/item/ammo_casing/c160smart/Initialize(mapload) + . = ..() + AddElement(/datum/element/caseless) + +/obj/item/ammo_casing/c160smart/ready_proj(atom/target, mob/living/user, quiet, zone_override, atom/fired_from) + . = ..() + if(!isturf(target)) + loaded_projectile.set_homing_target(target) diff --git a/code/modules/projectiles/boxes_magazines/external/smg.dm b/code/modules/projectiles/boxes_magazines/external/smg.dm index 3ebb459ed9319..40837d9ddbc4c 100644 --- a/code/modules/projectiles/boxes_magazines/external/smg.dm +++ b/code/modules/projectiles/boxes_magazines/external/smg.dm @@ -30,18 +30,16 @@ . = ..() icon_state = "[base_icon_state]-[round(ammo_count(), 4)]" -/obj/item/ammo_box/magazine/plastikov9mm - name = "PP-95 magazine (9mm)" - icon_state = "9x19-50" - base_icon_state = "9x19" - ammo_type = /obj/item/ammo_casing/c9mm - caliber = CALIBER_9MM +/obj/item/ammo_box/magazine/smartgun + name = "Abielle magazine (.160 Smart)" + icon_state = "smartgun" + base_icon_state = "smartgun" + ammo_type = /obj/item/ammo_casing/c160smart + multiple_sprites = AMMO_BOX_FULL_EMPTY + multiple_sprite_use_base = TRUE + caliber = CALIBER_160SMART max_ammo = 50 -/obj/item/ammo_box/magazine/plastikov9mm/update_icon_state() - . = ..() - icon_state = "[base_icon_state]-[ammo_count() ? 50 : 0]" - /obj/item/ammo_box/magazine/uzim9mm name = "uzi magazine (9mm)" icon_state = "uzi9mm-32" diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm index b86e2a9938995..a895cbe88e44f 100644 --- a/code/modules/projectiles/guns/ballistic/automatic.dm +++ b/code/modules/projectiles/guns/ballistic/automatic.dm @@ -86,20 +86,33 @@ /obj/item/gun/ballistic/automatic/wt550/add_bayonet_point() AddComponent(/datum/component/bayonet_attachable, offset_x = 25, offset_y = 12) -/obj/item/gun/ballistic/automatic/plastikov - name = "\improper PP-95 SMG" - desc = "An ancient 9mm submachine gun pattern updated and simplified to lower costs, though perhaps simplified too much." - icon_state = "plastikov" - inhand_icon_state = "plastikov" - accepted_magazine_type = /obj/item/ammo_box/magazine/plastikov9mm - burst_size = 5 - spread = 25 - can_suppress = FALSE +/obj/item/gun/ballistic/automatic/smartgun + name = "\improper Abielle Smart-SMG" + desc = "An old experiment in smart-weapon technology that guides bullets towards the target the gun was aimed at when fired. \ + While the tracking functions worked fine, the gun is prone to insanely wide spread thanks to it's practically non-existant barrel." + icon_state = "smartgun" + inhand_icon_state = "smartgun" + accepted_magazine_type = /obj/item/ammo_box/magazine/smartgun + burst_size = 4 + fire_delay = 1 + spread = 40 + dual_wield_spread = 20 actions_types = list() - projectile_damage_multiplier = 0.35 //It's like 10.5 damage per bullet, it's close enough to 10 shots + bolt_type = BOLT_TYPE_LOCKING + can_suppress = FALSE mag_display = TRUE empty_indicator = TRUE - fire_sound = 'sound/weapons/gun/smg/shot_alt.ogg' + click_on_low_ammo = FALSE + /// List of the possible firing sounds + var/list/firing_sound_list = list( + 'sound/weapons/gun/smartgun/smartgun_shoot_1.ogg', + 'sound/weapons/gun/smartgun/smartgun_shoot_2.ogg', + 'sound/weapons/gun/smartgun/smartgun_shoot_3.ogg', + ) + +/obj/item/gun/ballistic/automatic/smartgun/fire_sounds() + var/picked_fire_sound = pick(firing_sound_list) + playsound(src, picked_fire_sound, fire_sound_volume, vary_fire_sound) /obj/item/gun/ballistic/automatic/mini_uzi name = "\improper Type U3 Uzi" diff --git a/code/modules/projectiles/projectile/bullets/pistol.dm b/code/modules/projectiles/projectile/bullets/pistol.dm index 6bd355219f950..63e491e2f290d 100644 --- a/code/modules/projectiles/projectile/bullets/pistol.dm +++ b/code/modules/projectiles/projectile/bullets/pistol.dm @@ -71,3 +71,25 @@ impact_light_intensity = 5 impact_light_range = 1 impact_light_color_override = LIGHT_COLOR_DIM_YELLOW + +// .160 Smart + +/obj/projectile/bullet/c160smart + name = ".160 smart bullet" + icon_state = "smartgun" + damage = 10 + embed_type = /datum/embed_data/bullet_c160smart + speed = 2 + homing_turn_speed = 5 + homing_inaccuracy_min = 4 + homing_inaccuracy_max = 10 + +/datum/embed_data/bullet_c160smart + embed_chance = 10 + fall_chance = 5 + jostle_chance = 3 + ignore_throwspeed_threshold = TRUE + pain_stam_pct = 0.25 + pain_mult = 3 + jostle_pain_mult = 6 + rip_time = 5 diff --git a/code/modules/shuttle/supply.dm b/code/modules/shuttle/supply.dm index 01657d2ebb976..72096f7bb9bfb 100644 --- a/code/modules/shuttle/supply.dm +++ b/code/modules/shuttle/supply.dm @@ -81,12 +81,11 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( if(!length(stuff_to_send_home)) return FALSE - var/obj/structure/closet/supplypod/centcompod/et_go_home = new() - - for(var/atom/movable/et as anything in stuff_to_send_home) - et.forceMove(et_go_home) - - new /obj/effect/pod_landingzone(get_turf(home), et_go_home) + podspawn(list( + "target" = get_turf(home), + "path" = /obj/structure/closet/supplypod/centcompod, + "spawn" = stuff_to_send_home, + )) return stuff_to_send_home diff --git a/code/modules/surgery/cavity_implant.dm b/code/modules/surgery/cavity_implant.dm index ac8c69610c350..3ec68f8db8858 100644 --- a/code/modules/surgery/cavity_implant.dm +++ b/code/modules/surgery/cavity_implant.dm @@ -9,6 +9,8 @@ /datum/surgery_step/handle_cavity, /datum/surgery_step/close) +GLOBAL_LIST_INIT(heavy_cavity_implants, typecacheof(list(/obj/item/transfer_valve))) + //handle cavity /datum/surgery_step/handle_cavity name = "implant item" @@ -49,7 +51,7 @@ /datum/surgery_step/handle_cavity/success(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool, datum/surgery/surgery = FALSE) var/obj/item/bodypart/chest/target_chest = target.get_bodypart(BODY_ZONE_CHEST) if(tool) - if(item_for_cavity || tool.w_class > WEIGHT_CLASS_NORMAL || HAS_TRAIT(tool, TRAIT_NODROP) || isorgan(tool)) + if(item_for_cavity || ((tool.w_class > WEIGHT_CLASS_NORMAL) && !is_type_in_typecache(tool, GLOB.heavy_cavity_implants)) || HAS_TRAIT(tool, TRAIT_NODROP) || isorgan(tool)) to_chat(user, span_warning("You can't seem to fit [tool] in [target]'s [target_zone]!")) return FALSE else diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm b/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm index e4ab654cb5e41..4a7dcab80f259 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm @@ -2,7 +2,7 @@ name = "arm-mounted implant" desc = "You shouldn't see this! Adminhelp and report this as an issue on github!" zone = BODY_ZONE_R_ARM - icon_state = "implant-toolkit" + icon_state = "toolkit_generic" w_class = WEIGHT_CLASS_SMALL actions_types = list(/datum/action/item_action/organ_action/toggle) ///A ref for the arm we're taking up. Mostly for the unregister signal upon removal @@ -264,6 +264,7 @@ /obj/item/organ/internal/cyberimp/arm/toolset name = "integrated toolset implant" desc = "A stripped-down version of the engineering cyborg toolset, designed to be installed on subject's arm. Contain advanced versions of every tool." + icon_state = "toolkit_engineering" actions_types = list(/datum/action/item_action/organ_action/toggle/toolkit) items_to_create = list( /obj/item/screwdriver/cyborg, @@ -349,6 +350,7 @@ /obj/item/organ/internal/cyberimp/arm/surgery name = "surgical toolset implant" desc = "A set of surgical tools hidden behind a concealed panel on the user's arm." + icon_state = "toolkit_surgical" actions_types = list(/datum/action/item_action/organ_action/toggle/toolkit) items_to_create = list( /obj/item/retractor/augment, diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm b/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm index 046c84200d1eb..060499936d53d 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm @@ -1,15 +1,12 @@ /obj/item/organ/internal/cyberimp/chest name = "cybernetic torso implant" desc = "Implants for the organs in your torso." - icon_state = "chest_implant" - implant_overlay = "chest_implant_overlay" zone = BODY_ZONE_CHEST /obj/item/organ/internal/cyberimp/chest/nutriment - name = "Nutriment pump implant" + name = "nutriment pump implant" desc = "This implant will synthesize and pump into your bloodstream a small amount of nutriment when you are starving." - icon_state = "chest_implant" - implant_color = "#00AA00" + icon_state = "nutriment_implant" var/hunger_threshold = NUTRITION_LEVEL_STARVING var/synthesizing = 0 var/poison_amount = 5 @@ -37,18 +34,16 @@ /obj/item/organ/internal/cyberimp/chest/nutriment/plus - name = "Nutriment pump implant PLUS" + name = "nutriment pump implant PLUS" desc = "This implant will synthesize and pump into your bloodstream a small amount of nutriment when you are hungry." - icon_state = "chest_implant" - implant_color = "#006607" + icon_state = "adv_nutriment_implant" hunger_threshold = NUTRITION_LEVEL_HUNGRY poison_amount = 10 /obj/item/organ/internal/cyberimp/chest/reviver - name = "Reviver implant" + name = "reviver implant" desc = "This implant will attempt to revive and heal you if you lose consciousness. For the faint of heart!" - icon_state = "chest_implant" - implant_color = "#AD0000" + icon_state = "reviver_implant" slot = ORGAN_SLOT_HEART_AID var/revive_cost = 0 var/reviving = FALSE @@ -164,7 +159,6 @@ slot = ORGAN_SLOT_THRUSTERS icon_state = "imp_jetpack" base_icon_state = "imp_jetpack" - implant_overlay = null implant_color = null actions_types = list(/datum/action/item_action/organ_action/toggle) w_class = WEIGHT_CLASS_NORMAL @@ -265,7 +259,7 @@ name = "\improper Herculean gravitronic spinal implant" desc = "This gravitronic spinal interface is able to improve the athletics of a user, allowing them greater physical ability. \ Contains a slot which can be upgraded with a gravity anomaly core, improving its performance." - implant_color = "#15704c" + icon_state = "herculean_implant" slot = ORGAN_SLOT_SPINE /// How much faster does the spinal implant improve our lifting speed, workout ability, reducing falling damage and improving climbing and standing speed var/athletics_boost_multiplier = 0.8 @@ -315,5 +309,7 @@ name = "\improper Atlas gravitonic spinal implant" desc = "This gravitronic spinal interface is able to improve the athletics of a user, allowing them greater physical ability. \ This one has been improved through the installation of a gravity anomaly core, allowing for personal gravity manipulation." + icon_state = "herculean_implant_core" + update_appearance() qdel(tool) return ITEM_INTERACT_SUCCESS diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm b/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm index 401e951a8534a..d4c4b57d75f6b 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm @@ -2,7 +2,6 @@ name = "cybernetic eye implant" desc = "Implants for your eyes." icon_state = "eye_implant" - implant_overlay = "eye_implant_overlay" slot = ORGAN_SLOT_EYES zone = BODY_ZONE_PRECISE_EYES w_class = WEIGHT_CLASS_TINY @@ -41,21 +40,25 @@ toggled_on = FALSE /obj/item/organ/internal/cyberimp/eyes/hud/medical - name = "Medical HUD implant" + name = "medical HUD implant" desc = "These cybernetic eye implants will display a medical HUD over everything you see." + icon_state = "eye_implant_medical" HUD_traits = list(TRAIT_MEDICAL_HUD) /obj/item/organ/internal/cyberimp/eyes/hud/security - name = "Security HUD implant" + name = "security HUD implant" desc = "These cybernetic eye implants will display a security HUD over everything you see." + icon_state = "eye_implant_security" HUD_traits = list(TRAIT_SECURITY_HUD) /obj/item/organ/internal/cyberimp/eyes/hud/diagnostic - name = "Diagnostic HUD implant" + name = "diagnostic HUD implant" desc = "These cybernetic eye implants will display a diagnostic HUD over everything you see." + icon_state = "eye_implant_diagnostic" HUD_traits = list(TRAIT_DIAGNOSTIC_HUD, TRAIT_BOT_PATH_HUD) /obj/item/organ/internal/cyberimp/eyes/hud/security/syndicate - name = "Contraband Security HUD Implant" + name = "contraband security HUD implant" desc = "A Cybersun Industries brand Security HUD Implant. These illicit cybernetic eye implants will display a security HUD over everything you see." + icon_state = "eye_implant_syndicate" organ_flags = ORGAN_ROBOTIC | ORGAN_HIDDEN diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm index f36c6cfe838b8..f71e29631b384 100644 --- a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm +++ b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm @@ -22,8 +22,6 @@ /obj/item/organ/internal/cyberimp/brain name = "cybernetic brain implant" desc = "Injectors of extra sub-routines for the brain." - icon_state = "brain_implant" - implant_overlay = "brain_implant_overlay" zone = BODY_ZONE_HEAD w_class = WEIGHT_CLASS_TINY @@ -39,9 +37,9 @@ /obj/item/organ/internal/cyberimp/brain/anti_drop name = "anti-drop implant" desc = "This cybernetic brain implant will allow you to force your hand muscles to contract, preventing item dropping. Twitch ear to toggle." + icon_state = "brain_implant_antidrop" var/active = FALSE var/list/stored_items = list() - implant_color = "#DE7E00" slot = ORGAN_SLOT_BRAIN_ANTIDROP actions_types = list(/datum/action/item_action/organ_action/toggle) @@ -99,9 +97,9 @@ stored_items -= source /obj/item/organ/internal/cyberimp/brain/anti_stun - name = "CNS Rebooter implant" + name = "CNS rebooter implant" desc = "This implant will automatically give you back control over your central nervous system, reducing downtime when stunned." - implant_color = COLOR_YELLOW + icon_state = "brain_implant_rebooter" slot = ORGAN_SLOT_BRAIN_ANTISTUN var/static/list/signalCache = list( diff --git a/code/modules/uplink/uplink_items/nukeops.dm b/code/modules/uplink/uplink_items/nukeops.dm index 2a93976992216..80df9de47cfbc 100644 --- a/code/modules/uplink/uplink_items/nukeops.dm +++ b/code/modules/uplink/uplink_items/nukeops.dm @@ -373,16 +373,18 @@ //Meme weapons: Literally just weapons used as a joke, shouldn't be particularly expensive. /datum/uplink_item/weapon_kits/surplus_smg - name = "Surplus SMG Case (Flukie)" - desc = "A horribly outdated automatic weapon. Why would you want to use this? Comes with...rations." - item = /obj/item/gun/ballistic/automatic/plastikov + name = "Surplus Smart-SMG (Flukie)" + desc = "An outdated smart-SMG with limited stopping power, however it's bullets will gradually track towards whatever \ + the gun was shot at. This does require you to actually aim at the person you are shooting at before firing, but \ + surely a highly trained operative such as yourself can manage that." + item = /obj/item/gun/ballistic/automatic/smartgun cost = 2 purchasable_from = UPLINK_SERIOUS_OPS /datum/uplink_item/ammo_nuclear/surplus_smg - name = "Surplus SMG Magazine (Surplus)" - desc = "A cylindrical magazine designed for the PP-95 SMG." - item = /obj/item/ammo_box/magazine/plastikov9mm + name = "Surplus Smart-SMG Magazine (Smartgun)" + desc = "A large box magazine made for use in the Abielle smart-SMG." + item = /obj/item/ammo_box/magazine/smartgun cost = 1 purchasable_from = UPLINK_SERIOUS_OPS uplink_item_flags = SYNDIE_TRIPS_CONTRABAND diff --git a/code/modules/vehicles/cars/vim.dm b/code/modules/vehicles/cars/vim.dm index 221c9268febbb..dfcd604ef144d 100644 --- a/code/modules/vehicles/cars/vim.dm +++ b/code/modules/vehicles/cars/vim.dm @@ -18,6 +18,7 @@ light_power = 1.5 light_on = FALSE engine_sound = 'sound/effects/servostep.ogg' + interaction_flags_mouse_drop = NONE ///Maximum size of a mob trying to enter the mech var/maximum_mob_size = MOB_SIZE_SMALL COOLDOWN_DECLARE(sound_cooldown) diff --git a/code/modules/vehicles/mecha/equipment/mecha_equipment.dm b/code/modules/vehicles/mecha/equipment/mecha_equipment.dm index b32e26be94a4e..00d0a955d7b87 100644 --- a/code/modules/vehicles/mecha/equipment/mecha_equipment.dm +++ b/code/modules/vehicles/mecha/equipment/mecha_equipment.dm @@ -149,12 +149,12 @@ if(equipment_slot == MECHA_WEAPON) if(attach_right) // We need to check for length in case a mech doesn't support any arm attachments at all - if((mech.equip_by_category[MECHA_R_ARM] == mech.max_equip_by_category[MECHA_R_ARM]) && (!special_attaching_interaction(attach_right, mech, user, checkonly = TRUE))) - to_chat(user, span_warning("\The [mech]'s right arm is full![mech.equip_by_category[MECHA_L_ARM] ? "" : " Try left arm!"]")) + if((!isnull(mech.equip_by_category[MECHA_R_ARM]) || !mech.max_equip_by_category[MECHA_R_ARM]) && (!special_attaching_interaction(attach_right, mech, user, checkonly = TRUE))) + to_chat(user, span_warning("\The [mech]'s right arm is full![mech.equip_by_category[MECHA_L_ARM] || !mech.max_equip_by_category[MECHA_L_ARM] ? "" : " Try left arm!"]")) return FALSE else - if((mech.equip_by_category[MECHA_L_ARM] == mech.max_equip_by_category[MECHA_L_ARM]) && (!special_attaching_interaction(attach_right, mech, user, checkonly = TRUE))) - to_chat(user, span_warning("\The [mech]'s left arm is full![mech.equip_by_category[MECHA_R_ARM] ? "" : " Try right arm!"]")) + if((!isnull(mech.equip_by_category[MECHA_L_ARM]) || !mech.max_equip_by_category[MECHA_L_ARM]) && (!special_attaching_interaction(attach_right, mech, user, checkonly = TRUE))) + to_chat(user, span_warning("\The [mech]'s left arm is full![mech.equip_by_category[MECHA_R_ARM] || !mech.max_equip_by_category[MECHA_R_ARM] ? "" : " Try right arm!"]")) return FALSE return TRUE if(length(mech.equip_by_category[equipment_slot]) == mech.max_equip_by_category[equipment_slot]) diff --git a/code/modules/vehicles/mecha/equipment/tools/work_tools.dm b/code/modules/vehicles/mecha/equipment/tools/work_tools.dm index 35645bf56ba92..c8e77f2e888e4 100644 --- a/code/modules/vehicles/mecha/equipment/tools/work_tools.dm +++ b/code/modules/vehicles/mecha/equipment/tools/work_tools.dm @@ -98,7 +98,7 @@ span_notice("[chassis] pushes you aside.")) return ..() - if(iscarbon(victim))//meme clamp here + if(iscarbon(victim) && killer_clamp)//meme clamp here var/mob/living/carbon/carbon_victim = target var/torn_off = FALSE var/obj/item/bodypart/affected = carbon_victim.get_bodypart(BODY_ZONE_L_ARM) diff --git a/config/game_options.txt b/config/game_options.txt index 8fce99691ea56..14646c6920758 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -185,6 +185,10 @@ ALLOW_AI_MULTICAM ## 600 is one minute. GATEWAY_DELAY 18000 +## Overrides to gateway delay for specific away mission start points. +GATEWAY_DELAYS_BY_ID AWAYSTART_BEACH 6000 +GATEWAY_DELAYS_BY_ID AWAYSTART_MUSEUM 9000 + ## The probability of the gateway mission being a config one CONFIG_GATEWAY_CHANCE 0 diff --git a/dependencies.sh b/dependencies.sh index a15d39ef31f4c..d6600a5ff58b4 100644 --- a/dependencies.sh +++ b/dependencies.sh @@ -21,11 +21,11 @@ export SPACEMAN_DMM_VERSION=suite-1.8 # Python version for mapmerge and other tools export PYTHON_VERSION=3.9.0 -#auxlua repo -export AUXLUA_REPO=tgstation/auxlua +#dreamluau repo +export DREAMLUAU_REPO="tgstation/dreamluau" -#auxlua git tag -export AUXLUA_VERSION=1.4.4 +#dreamluau git tag +export DREAMLUAU_VERSION=0.1.0 #hypnagogic repo export CUTTER_REPO=spacestation13/hypnagogic diff --git a/dreamluau.dll b/dreamluau.dll new file mode 100644 index 0000000000000..7d49f0742d8f8 Binary files /dev/null and b/dreamluau.dll differ diff --git a/html/changelogs/AutoChangeLog-pr-85251.yml b/html/changelogs/AutoChangeLog-pr-85251.yml deleted file mode 100644 index b40f87748a9d1..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-85251.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - bugfix: "Recyclers no longer recycle contents of indestructible items" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-85302.yml b/html/changelogs/AutoChangeLog-pr-85302.yml deleted file mode 100644 index 3797ee383bcb6..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-85302.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - qol: "If you have auto fit viewport enabled, it will trigger upon entering or exiting fullscreen" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-85305.yml b/html/changelogs/AutoChangeLog-pr-85305.yml deleted file mode 100644 index 14da28c2ee27d..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-85305.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Time-Green" -delete-after: True -changes: - - bugfix: "Fixes void eater not refreshing" - - bugfix: "Fixes planetary gravity not killing voidwalkers and voideds" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-85368.yml b/html/changelogs/AutoChangeLog-pr-85368.yml new file mode 100644 index 0000000000000..6cb72d36e4bf1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-85368.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - image: "All implants have received a fresh coat of paint" \ No newline at end of file diff --git a/html/changelogs/archive/2024-07.yml b/html/changelogs/archive/2024-07.yml index 55c4a30a517d8..f4a64a129e247 100644 --- a/html/changelogs/archive/2024-07.yml +++ b/html/changelogs/archive/2024-07.yml @@ -1408,3 +1408,88 @@ vent - sound: only the scream emote can be heard through walls - sound: the sneeze projectile no longer makes a sound when making contact. +2024-07-29: + DaCoolBoss: + - spellcheck: military javelin's name is now fully uncapitalised + SmArtKar: + - rscadd: Mining headsets now allow you to make callouts via pointing. You can use + them to communicate with fellow miners or order your army of bots and raptors! + - rscadd: Mining headsets keep your voice loud and clear in low-pressure environments + (not vacuum!) + - qol: If you have auto fit viewport enabled, it will trigger upon entering or exiting + fullscreen + - bugfix: VIM no longer requires hands to enter + - bugfix: Recyclers no longer recycle contents of indestructible items + - bugfix: Grass sheath now holds parsnip sabres like its supposed to + - spellcheck: Fixed up parsnip sabre description grammar + StrangeWeirdKitten: + - rscadd: Cosmic Skull glows purple. + Time-Green: + - bugfix: Fixes void eater not refreshing + - bugfix: Fixes planetary gravity not killing voidwalkers and voideds + Y0SH1M4S73R: + - admin: Admin lua scripting uses a new library that (probably) will not break when + BYOND updates. +2024-07-30: + 13spacemen: + - balance: TTV bombs can be implanted into people's chest once again + AyIong: + - rscadd: Stat Panel now scales like a chat, depends on the font size. Defaults + from the chat font size, but you can separate it. + - refactor: Refactored Stat Panel styles and Byond skin theme applying. Stat Panel + now looks more like a TGUI + Bisar: + - bugfix: All mining mobs now properly listen to the signals sent by attackers and + will respond appropriately. + Gaxeer: + - refactor: replace some copypaste code for pod spawn to use `podspawn` proc instead + - code_imp: modify `podspawn` proc to accept amount of item type to spawn in `spawn` + specification + Ghommie: + - qol: Aquariums start unanchored and don't autoconnect to plumbing. Their reproduction + prevention is also disabled by default. + - balance: Made it a tad easier to control the bait during the minigame. Buffed + the fishing skill. No fishing duds at all when using ANY bait. + - balance: Chasm Chrabs take less time to grow into Lobstrosities but need food + a bit more frequently. + - balance: '"Profound fisher" mobs will have less RNG-dependant time fishing.' + - bugfix: You can now ACTUALLY interact with other things while fishing if the fishing + rod isn't in your active hand. + - config: Added a config for specific gateway delays so locations like the beach + and the museum don't have to take 30 minutes to become available like the rest. + Jackriip: + - refactor: moves the create_all_lighting_objects proc to the lighting subsystem + KazooBard: + - rscadd: The heretic's ritual of knowledge no longer requires binoculars + - rscadd: Clipboards are craftable using a wood plank, an iron rod and wirecutters + Paxilmaniac: + - balance: The nukeops surplus smg, the pp-95, has been reworked into the Abielle + Smart-SMG. It performs nearly identically to the pp-95, however it's projectiles + get a slight homing ability towards whatever you click on. + - sound: New firing sounds for the surplus smg, credit to the m41 sound effects + from tgmc + - image: New sprites for the surplus smg, made by me + SmArtKar: + - bugfix: Fixed mapload circuit floors not drawing power and deconstructing circuit + floors not reducing power load + - bugfix: Projectile dampener, recycler and ninja stealth MOD modules now work properly + - bugfix: You can no longer tear peoples arms off with non-killer clamps + - bugfix: Mechs can no longer equip infinite amount of weapons + - bugfix: Mining MODsuits now can store everything that explorer suits can + Vekter: + - bugfix: Fixes an exploit that allowed players to open a Bag of Holding rift in + the Heretic dimension. + deathrobotpunch: + - qol: drones now have soap in their internal storage! + grungussuss: + - sound: leather, skins and cardboard have their own sound now + - bugfix: smuggler satchels will no longer spawn in space + - sound: added sniff sounds + - sound: added sigh sounds + grungussuss and Kayozz: + - sound: more maintenance ambience has been added + rroqc: + - bugfix: plants no longer select reagent genes they already have while cross pollinating + - code_imp: 'improves reagent cross pollination code + + :cl:' diff --git a/html/statbrowser.css b/html/statbrowser.css index dc693f42f756b..cd1d63bf7c060 100644 --- a/html/statbrowser.css +++ b/html/statbrowser.css @@ -1,150 +1,125 @@ body { font-family: Verdana, Geneva, Tahoma, sans-serif; - font-size: 12px !important; + font-size: 12px; margin: 0 !important; padding: 0 !important; - overflow-x: hidden; - overflow-y: scroll; -} - -body.dark { - background-color: #131313; - color: #b2c4dd; - scrollbar-base-color: #1c1c1c; - scrollbar-face-color: #3b3b3b; - scrollbar-3dlight-color: #252525; - scrollbar-highlight-color: #252525; - scrollbar-track-color: #1c1c1c; - scrollbar-arrow-color: #929292; - scrollbar-shadow-color: #3b3b3b; -} - -#menu { - background-color: #F0F0F0; - position: fixed; - width: 100%; - z-index: 100; -} - -.dark #menu { - background-color: #202020; -} - -#statcontent { - padding: 7px 7px 7px 7px; + overflow: hidden; } a { - color: black; - text-decoration: none -} - -.dark a { - color: #b2c4dd; + color: #003399; + text-decoration: none; } -a:hover, -.dark a:hover { - text-decoration: underline; +a:hover { + color: #007fff; } -ul { - list-style-type: none; - margin: 0; - padding: 0; - background-color: #333; +h3 { + margin: 0 -0.5em 0.25em; + padding: 1em 0.66em 0.5em; + border-bottom: 0.1667em solid; } -li { - float: left; -} -li a { - display: block; - color: white; - text-align: center; - padding: 14px 16px; - text-decoration: none; +img { + -ms-interpolation-mode: nearest-neighbor; + image-rendering: pixelated; } -li a:hover:not(.active) { - background-color: #111; +.stat-container { + display: flex; + flex-direction: column; + height: 100vh; } -.button-container { - display: inline-flex; - flex-wrap: wrap-reverse; - flex-direction: row; - align-items: flex-start; - overflow-x: hidden; - white-space: pre-wrap; - padding: 0 4px; +#menu { + display: flex; + overflow-x: auto; + overflow-y: hidden; + padding: 0.25em 0.25em 0; + background-color: #ffffff; } .button { - background-color: #dfdfdf; - border: 1px solid #cecece; - border-bottom-width: 2px; - color: rgba(0, 0, 0, 0.7); - padding: 6px 4px 4px; - text-align: center; - text-decoration: none; - font-size: 12px; - margin: 0; + display: inline-table; cursor: pointer; - transition-duration: 100ms; - order: 3; - min-width: 40px; -} - -.dark button { - background-color: #222222; - border-color: #343434; - color: rgba(255, 255, 255, 0.5); + user-select: none; + -ms-user-select: none; /* Remove after Byond 516 */ + text-align: center; + font-size: 1em; + min-width: 2.9em; + padding: 0.5em 0.5em 0.4em; + background-color: transparent; + color: rgba(0, 0, 0, 0.5); + border: 0; + border-bottom: 0.1667em solid transparent; + border-radius: 0.25em 0.25em 0 0; } .button:hover { background-color: #ececec; - transition-duration: 0; -} - -.dark button:hover { - background-color: #2e2e2e; } -.button:active, .button.active { - background-color: #ffffff; + background-color: #dfdfdf; color: black; - border-top-color: #cecece; - border-left-color: #cecece; - border-right-color: #cecece; - border-bottom-color: #ffffff; + border-bottom-color: #000000; +} + +#under-menu { + height: 0.5em; + background-color: #eeeeee; } -.dark .button:active, -.dark .button.active { - background-color: #444444; - color: white; - border-top-color: #343434; - border-left-color: #343434; - border-right-color: #343434; - border-bottom-color: #ffffff; +#statcontent { + flex: 1; + padding: 0.75em 0.5em; + overflow-y: scroll; + overflow-x: hidden; } .grid-container { - margin: -2px; - margin-right: -15px; + margin: 0; } .grid-item { + display: inline-flex; position: relative; - display: inline-block; + user-select: none; + -ms-user-select: none; /* Remove after Byond 516 */ width: 100%; box-sizing: border-box; - overflow: visible; - padding: 3px 2px; text-decoration: none; + background-color: transparent; + color: black; +} + +.grid-item:hover { + color: #003399; + z-index: 1; +} + +.grid-item-text { + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + box-sizing: border-box; + white-space: nowrap; + width: 100%; + padding: 0.33em 0.5em; + border-radius: 0.25em; +} + +.grid-item-text:hover { + position: absolute; + top: -1.33em; + white-space: normal; + background-color: #ececec; +} + +.grid-item-text:active { + background-color: #dfdfdf; } @media only screen and (min-width: 300px) { @@ -171,57 +146,67 @@ li a:hover:not(.active) { } } -.grid-item:hover { - z-index: 1; +.status-info { + margin: 0 0.33em 0.25em; } -.grid-item:hover .grid-item-text { - width: auto; - text-decoration: underline; +.interview_panel_stats, +.interview_panel_controls { + margin-bottom: 1em; } -.grid-item-text { - display: inline-block; - width: 100%; - background-color: #ffffff; - margin: 0 -6px; - padding: 0 6px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - pointer-events: none; +/* Dark theme colors */ +body.dark { + background-color: #131313; + color: #b2c4dd; + scrollbar-base-color: #1c1c1c; + scrollbar-face-color: #3b3b3b; + scrollbar-3dlight-color: #252525; + scrollbar-highlight-color: #252525; + scrollbar-track-color: #1c1c1c; + scrollbar-arrow-color: #929292; + scrollbar-shadow-color: #3b3b3b; +} + +.dark a { + color: #6699ff; +} + +.dark a:hover, +.dark .grid-item:hover { + color: #80bfff; } -.dark .grid-item-text { +.dark #menu { background-color: #131313; } -.link { - display: inline; - background: none; - border: none; - padding: 7px 14px; - color: black; - text-decoration: none; - cursor: pointer; - font-size: 13px; - margin: 2px 2px; +.dark .button { + color: rgba(255, 255, 255, 0.5); } -.dark .link { - color: #abc6ec; +.dark .button:hover { + background-color: #252525; } -.link:hover { - text-decoration: underline; +.dark .button.active { + background-color: #313131; + color: #d4dfec; + border-bottom-color: #d4dfec; } -img { - -ms-interpolation-mode: nearest-neighbor; - image-rendering: pixelated; +.dark #under-menu { + background-color: #202020; +} + +.dark .grid-item{ + color: #b2c4dd; +} + +.dark .grid-item-text:hover { + background-color: #252525; } -.interview_panel_controls, -.interview_panel_stats { - margin-bottom: 10px; +.dark .grid-item-text:active { + background-color: #313131; } diff --git a/html/statbrowser.html b/html/statbrowser.html index 1aea8811d58a0..ffd7425bd2607 100644 --- a/html/statbrowser.html +++ b/html/statbrowser.html @@ -1,3 +1,5 @@ - -
-
+
+ +
+
+
diff --git a/html/statbrowser.js b/html/statbrowser.js index 0d89487af5b39..f6c188c6edd61 100644 --- a/html/statbrowser.js +++ b/html/statbrowser.js @@ -32,7 +32,6 @@ var turfname = ""; var imageRetryDelay = 500; var imageRetryLimit = 50; var menu = document.getElementById('menu'); -var under_menu = document.getElementById('under_menu'); var statcontentdiv = document.getElementById('statcontent'); var storedimages = []; var split_admin_tabs = false; @@ -58,23 +57,23 @@ function createStatusTab(name) { if (!verb_tabs.includes(name) && !permanent_tabs.includes(name)) { return; } - var B = document.createElement("BUTTON"); - B.onclick = function () { + var button = document.createElement("DIV"); + button.onclick = function () { tab_change(name); this.blur(); + statcontentdiv.focus(); }; - B.id = name; - B.textContent = name; - B.className = "button"; + button.id = name; + button.textContent = name; + button.className = "button"; //ORDERING ALPHABETICALLY - B.style.order = name.charCodeAt(0); + button.style.order = name.charCodeAt(0); if (name == "Status" || name == "MC") { - B.style.order = name == "Status" ? 1 : 2; + button.style.order = name == "Status" ? 1 : 2; } //END ORDERING - menu.appendChild(B); + menu.appendChild(button); SendTabToByond(name); - under_menu.style.height = menu.clientHeight + 'px'; } function removeStatusTab(name) { @@ -88,7 +87,6 @@ function removeStatusTab(name) { } menu.removeChild(document.getElementById(name)); TakeTabFromByond(name); - under_menu.style.height = menu.clientHeight + 'px'; } function sortVerbs() { @@ -104,10 +102,6 @@ function sortVerbs() { }) } -window.onresize = function () { - under_menu.style.height = menu.clientHeight + 'px'; -} - function addPermanentTab(name) { if (!permanent_tabs.includes(name)) { permanent_tabs.push(name); @@ -361,6 +355,7 @@ function draw_status() { } else { var div = document.createElement("div"); div.textContent = status_tab_parts[i]; + div.className = "status-info"; document.getElementById("statcontent").appendChild(div); } } @@ -717,6 +712,10 @@ function set_theme(which) { } } +function set_font_size(size) { + document.body.style.setProperty('font-size', size); +} + function set_style_sheet(sheet) { if (document.getElementById("goonStyle")) { var currentSheet = document.getElementById("goonStyle"); diff --git a/icons/effects/callouts.dmi b/icons/effects/callouts.dmi new file mode 100644 index 0000000000000..583f47d52b347 Binary files /dev/null and b/icons/effects/callouts.dmi differ diff --git a/icons/hud/fishing_hud.dmi b/icons/hud/fishing_hud.dmi index 58c478d071064..f18ed7c6cfca9 100644 Binary files a/icons/hud/fishing_hud.dmi and b/icons/hud/fishing_hud.dmi differ diff --git a/icons/hud/radial.dmi b/icons/hud/radial.dmi index e4a1693fb573e..f6e141ab6855a 100644 Binary files a/icons/hud/radial.dmi and b/icons/hud/radial.dmi differ diff --git a/icons/mob/inhands/weapons/guns_lefthand.dmi b/icons/mob/inhands/weapons/guns_lefthand.dmi index 9c8d8eec10781..90df2a892f984 100644 Binary files a/icons/mob/inhands/weapons/guns_lefthand.dmi and b/icons/mob/inhands/weapons/guns_lefthand.dmi differ diff --git a/icons/mob/inhands/weapons/guns_righthand.dmi b/icons/mob/inhands/weapons/guns_righthand.dmi index 1525f403312c9..eebed61656aa4 100644 Binary files a/icons/mob/inhands/weapons/guns_righthand.dmi and b/icons/mob/inhands/weapons/guns_righthand.dmi differ diff --git a/icons/obj/medical/organs/organs.dmi b/icons/obj/medical/organs/organs.dmi index fd1f5af2ddefc..a9366894d7bf3 100644 Binary files a/icons/obj/medical/organs/organs.dmi and b/icons/obj/medical/organs/organs.dmi differ diff --git a/icons/obj/weapons/guns/ammo.dmi b/icons/obj/weapons/guns/ammo.dmi index 889d66c18b7f5..4cd031af7ee8b 100644 Binary files a/icons/obj/weapons/guns/ammo.dmi and b/icons/obj/weapons/guns/ammo.dmi differ diff --git a/icons/obj/weapons/guns/ballistic.dmi b/icons/obj/weapons/guns/ballistic.dmi index 98d92ff716a0d..824d8b7c0a073 100644 Binary files a/icons/obj/weapons/guns/ballistic.dmi and b/icons/obj/weapons/guns/ballistic.dmi differ diff --git a/icons/obj/weapons/guns/projectiles.dmi b/icons/obj/weapons/guns/projectiles.dmi index 8c8356a0e51b8..a6cf89e9b910f 100644 Binary files a/icons/obj/weapons/guns/projectiles.dmi and b/icons/obj/weapons/guns/projectiles.dmi differ diff --git a/lua/SS13_base.lua b/lua/SS13_base.lua index ea04c8c6503dd..2b0645172ea1c 100644 --- a/lua/SS13_base.lua +++ b/lua/SS13_base.lua @@ -5,59 +5,38 @@ local SS13 = {} __SS13_signal_handlers = __SS13_signal_handlers or {} -SS13.SSlua = dm.global_vars.vars.SSlua +SS13.SSlua = dm.global_vars.SSlua SS13.global_proc = "some_magic_bullshit" SS13.state = state.state function SS13.get_runner_ckey() - return SS13.state:get_var("ckey_last_runner") + return SS13.state.ckey_last_runner end function SS13.get_runner_client() - return dm.global_vars:get_var("GLOB"):get_var("directory"):get(SS13.get_runner_ckey()) + return dm.global_vars.GLOB.directory[SS13.get_runner_ckey()] end -function SS13.istype(thing, type) - return dm.global_proc("_istype", thing, dm.global_proc("_text2path", type)) == 1 -end - -function SS13.start_tracking(datum) - local references = SS13.state.vars.references - references:add(datum) - SS13.state:call_proc("clear_on_delete", datum) -end +SS13.type = dm.global_procs._text2path -function SS13.new(type, ...) - local datum = SS13.new_untracked(type, ...) - if datum then - SS13.start_tracking(datum) - return datum - end +function SS13.istype(thing, type) + return dm.global_procs._istype(thing, SS13.type(type)) == 1 end -function SS13.type(string_type) - return dm.global_proc("_text2path", string_type) -end +SS13.new = dm.new function SS13.qdel(datum) if SS13.is_valid(datum) then - dm.global_proc("qdel", datum) + dm.global_procs.qdel(datum) return true end return false end -function SS13.new_untracked(type, ...) - return dm.global_proc("_new", type, { ... }) -end - function SS13.is_valid(datum) - if datum and not datum:is_null() and not datum:get_var("gc_destroyed") then - return true - end - return false + return dm.is_valid_ref(datum) and not datum.gc_destroyed end function SS13.await(thing_to_call, proc_to_call, ...) @@ -67,123 +46,107 @@ function SS13.await(thing_to_call, proc_to_call, ...) if thing_to_call == SS13.global_proc then proc_to_call = "/proc/" .. proc_to_call end - local promise = SS13.new("/datum/auxtools_promise", thing_to_call, proc_to_call, ...) - local promise_vars = promise.vars - while promise_vars.status == 0 do + local promise = SS13.new("/datum/promise", thing_to_call, proc_to_call, ...) + while promise.status == 0 do sleep() end - local return_value, runtime_message = promise_vars.return_value, promise_vars.runtime_message - SS13.stop_tracking(promise) - return return_value, runtime_message + return promise.return_value, promise.runtime_message end -function SS13.register_signal(datum, signal, func) - if not SS13.istype(datum, "/datum") then - return +local function signal_handler(data, ...) + local output = 0 + for func, _ in data.functions do + output = bit32.bor(output, func(...)) end - if not SS13.is_valid(datum) then - error("Tried to register a signal on a deleted datum!", 2) - return + return output +end + +local function create_qdeleting_callback(datum) + local callback = SS13.new("/datum/callback", SS13.state, "call_function_return_first") + callback:RegisterSignal(datum, "parent_qdeleting", "Invoke") + local path = { + "__SS13_signal_handlers", + dm.global_procs.WEAKREF(datum), + "parent_qdeleting", + "handler", + } + callback.arguments = { path } + local handler_data = { callback = callback, functions = {} } + handler_data.handler = function(source, ...) + local result = signal_handler(handler_data, source, ...) + for signal, signal_data in __SS13_signal_handlers[source] do + signal_data.callback:UnregisterSignal(source, signal) + end + __SS13_signal_handlers[source] = nil + return result end - local datumWeakRef = dm.global_proc("WEAKREF", datum) - if not __SS13_signal_handlers[datumWeakRef] then - __SS13_signal_handlers[datumWeakRef] = {} + __SS13_signal_handlers[datum]["parent_qdeleting"] = handler_data +end + +function SS13.register_signal(datum, signal, func) + if not type(func) == "function" then + return end - if signal == "_cleanup" then + if not SS13.istype(datum, "/datum") then return end - if not __SS13_signal_handlers[datumWeakRef][signal] then - __SS13_signal_handlers[datumWeakRef][signal] = {} + if not SS13.is_valid(datum) then + error("Tried to register a signal on a deleted datum", 2) end - local callback = SS13.new("/datum/callback", SS13.state, "call_function_return_first") - local callbackWeakRef = dm.global_proc("WEAKREF", callback) - callback:call_proc("RegisterSignal", datum, signal, "Invoke") - local path = { "__SS13_signal_handlers", datumWeakRef, signal, callbackWeakRef, "func" } - callback.vars.arguments = { path } - -- Turfs don't remove their signals on deletion. - if not __SS13_signal_handlers[datumWeakRef]._cleanup and not SS13.istype(datum, "/turf") then - local cleanupCallback = SS13.new("/datum/callback", SS13.state, "call_function_return_first") - local cleanupPath = { "__SS13_signal_handlers", datumWeakRef, "_cleanup"} - cleanupCallback.vars.arguments = { cleanupPath } - cleanupCallback:call_proc("RegisterSignal", datum, "parent_qdeleting", "Invoke") - __SS13_signal_handlers[datumWeakRef]._cleanup = function(datum) - SS13.start_tracking(datumWeakRef) - timer.set_timeout(0, function() - SS13.signal_handler_cleanup(datumWeakRef) - SS13.stop_tracking(cleanupCallback) - SS13.stop_tracking(datumWeakRef) - end) + if not __SS13_signal_handlers[datum] then + __SS13_signal_handlers[datum] = {} + -- Turfs don't remove their signals on deletion. + if not SS13.istype(datum, "/turf") then + create_qdeleting_callback(datum) end end - __SS13_signal_handlers[datumWeakRef][signal][callbackWeakRef] = { func = func, callback = callback } - return callback -end - -function SS13.stop_tracking(datum) - SS13.state:call_proc("let_soft_delete", datum) -end - -function SS13.unregister_signal(datum, signal, callback) - local function clear_handler(handler_info) - if not handler_info then - return - end - if not handler_info.callback then - return + local handler_data = __SS13_signal_handlers[datum][signal] + if not handler_data then + handler_data = { callback = nil, functions = {} } + local callback = SS13.new("/datum/callback", SS13.state, "call_function_return_first") + callback:RegisterSignal(datum, signal, "Invoke") + local path = { + "__SS13_signal_handlers", + dm.global_procs.WEAKREF(datum), + signal, + "handler", + } + callback.arguments = { path } + handler_data.callback = callback + handler_data.handler = function(...) + return signal_handler(handler_data, ...) end - local handler_callback = handler_info.callback - local callbackWeakRef = dm.global_proc("WEAKREF", handler_callback) - if not SS13.istype(datum, "/datum/weakref") then - handler_callback:call_proc("UnregisterSignal", datum, signal) - else - local actualDatum = datum:call_proc("hard_resolve") - if SS13.is_valid(actualDatum) then - handler_callback:call_proc("UnregisterSignal", actualDatum, signal) - end - end - SS13.stop_tracking(handler_callback) + __SS13_signal_handlers[datum][signal] = handler_data end + handler_data.functions[func] = true + return true +end - local datumWeakRef = datum - if not SS13.istype(datum, "/datum/weakref") then - datumWeakRef = dm.global_proc("WEAKREF", datum) - end - if not __SS13_signal_handlers[datumWeakRef] then +function SS13.unregister_signal(datum, signal, func) + if not (func == nil or type(func) == "function") then return end - - if signal == "_cleanup" then + if not __SS13_signal_handlers[datum] then return end - - if not __SS13_signal_handlers[datumWeakRef][signal] then + local handler_data = __SS13_signal_handlers[datum][signal] + if not handler_data then return end - - if not callback then - for handler_key, handler_info in __SS13_signal_handlers[datumWeakRef][signal] do - clear_handler(handler_info) + if func == nil then + if signal == "parent_qdeleting" then + handler_data.functions = {} + else + handler_data.callback:UnregisterSignal(datum, signal) + __SS13_signal_handlers[datum][signal] = nil end - __SS13_signal_handlers[datumWeakRef][signal] = nil else - if not SS13.istype(callback, "/datum/callback") then - return + handler_data.functions[func] = nil + if not (#handler_data.functions or (signal == "parent_qdeleting")) then + handler_data.callback:UnregisterSignal(datum, signal) + __SS13_signal_handlers[datum][signal] = nil end - local callbackWeakRef = dm.global_proc("WEAKREF", callback) - clear_handler(__SS13_signal_handlers[datumWeakRef][signal][callbackWeakRef]) - __SS13_signal_handlers[datumWeakRef][signal][callbackWeakRef] = nil - end -end - -function SS13.signal_handler_cleanup(datumWeakRef) - if not __SS13_signal_handlers[datumWeakRef] then - return - end - - for signal, _ in __SS13_signal_handlers[datumWeakRef] do - SS13.unregister_signal(datumWeakRef, signal) end - __SS13_signal_handlers[datumWeakRef] = nil end return SS13 diff --git a/lua/handler_group.lua b/lua/handler_group.lua index 0246d33c74488..050551b852969 100644 --- a/lua/handler_group.lua +++ b/lua/handler_group.lua @@ -1,29 +1,29 @@ -local SS13 = require('SS13') +local SS13 = require("SS13") local HandlerGroup = {} HandlerGroup.__index = HandlerGroup function HandlerGroup.new() return setmetatable({ - registered = {} + registered = {}, }, HandlerGroup) end -- Registers a signal on a datum for this handler group instance. function HandlerGroup:register_signal(datum, signal, func) - local callback = SS13.register_signal(datum, signal, func) - if not callback then + local registered_successfully = SS13.register_signal(datum, signal, func) + if not registered_successfully then return end - table.insert(self.registered, { datum = dm.global_proc("WEAKREF", datum), signal = signal, callback = callback }) + table.insert(self.registered, { datum = datum, signal = signal, func = func }) end -- Clears all the signals that have been registered on this HandlerGroup function HandlerGroup:clear() for _, data in self.registered do - if not data.callback or not data.datum then + if not data.func or not SS13.is_valid(data.datum) then continue end - SS13.unregister_signal(data.datum, data.signal, data.callback) + SS13.unregister_signal(data.datum, data.signal, data.func) end table.clear(self.registered) end @@ -45,5 +45,4 @@ function HandlerGroup.register_once(datum, signal, func) return callback end - return HandlerGroup diff --git a/lua/state.lua b/lua/state.lua index 080ee9f7eb32c..cba24d6435611 100644 --- a/lua/state.lua +++ b/lua/state.lua @@ -1,7 +1,7 @@ -local SSlua = dm.global_vars:get_var("SSlua") +local SSlua = dm.global_vars.SSlua -for _, state in SSlua:get_var("states") do - if state:get_var("internal_id") == dm.state_id then +for _, state in SSlua.states do + if state.internal_id == _state_id then return { state = state } end end diff --git a/lua/timer.lua b/lua/timer.lua index 8619bbb54a29e..4bcbf111b9be0 100644 --- a/lua/timer.lua +++ b/lua/timer.lua @@ -2,19 +2,19 @@ local state = require("state") local Timer = {} -local SSlua = dm.global_vars:get_var("SSlua") +local SSlua = dm.global_vars.SSlua __Timer_timers = __Timer_timers or {} __Timer_callbacks = __Timer_callbacks or {} function __add_internal_timer(func, time, loop) local timer = { loop = loop, - executeTime = time + dm.world:get_var("time") + executeTime = time + dm.world.time, } __Timer_callbacks[tostring(func)] = function() timer.executing = false if loop and timer.terminate ~= true then - timer.executeTime = dm.world:get_var("time") + time + timer.executeTime = dm.world.time + time else __stop_internal_timer(tostring(func)) end @@ -37,22 +37,21 @@ function __stop_internal_timer(func) end __Timer_timer_processing = __Timer_timer_processing or false -state.state:set_var("timer_enabled", 1) +state.state.timer_enabled = 1 __Timer_timer_process = function(seconds_per_tick) if __Timer_timer_processing then return 0 end __Timer_timer_processing = true - local time = dm.world:get_var("time") for func, timeData in __Timer_timers do if timeData.executing == true then continue end - if over_exec_usage(0.85) then + if _exec.time / (dm.world.tick_lag * 100) > 0.85 then sleep() end - if time >= timeData.executeTime then - state.state:get_var("functions_to_execute"):add(func) + if dm.world.time >= timeData.executeTime then + list.add(state.state.functions_to_execute, func) timeData.executing = true end end @@ -61,9 +60,8 @@ __Timer_timer_process = function(seconds_per_tick) end function Timer.wait(time) - local next_yield_index = __next_yield_index __add_internal_timer(function() - SSlua:call_proc("queue_resume", state.state, next_yield_index) + SSlua:queue_resume(state.state, _exec.next_yield_index) end, time * 10, false) coroutine.yield() end diff --git a/sound/ambience/ambimaint10.ogg b/sound/ambience/ambimaint10.ogg new file mode 100644 index 0000000000000..975aae32bff54 Binary files /dev/null and b/sound/ambience/ambimaint10.ogg differ diff --git a/sound/ambience/ambimaint11.ogg b/sound/ambience/ambimaint11.ogg new file mode 100644 index 0000000000000..2723c6008eb3b Binary files /dev/null and b/sound/ambience/ambimaint11.ogg differ diff --git a/sound/ambience/ambimaint12.ogg b/sound/ambience/ambimaint12.ogg new file mode 100644 index 0000000000000..2c873e0f5f996 Binary files /dev/null and b/sound/ambience/ambimaint12.ogg differ diff --git a/sound/ambience/ambimaint8.ogg b/sound/ambience/ambimaint8.ogg new file mode 100644 index 0000000000000..582ec800cbc45 Binary files /dev/null and b/sound/ambience/ambimaint8.ogg differ diff --git a/sound/ambience/ambimaint9.ogg b/sound/ambience/ambimaint9.ogg new file mode 100644 index 0000000000000..c990f954e5f6a Binary files /dev/null and b/sound/ambience/ambimaint9.ogg differ diff --git a/sound/ambience/antag/attribution.txt b/sound/ambience/antag/attribution.txt index 6390dd525c459..8db2b1b8ec2eb 100644 --- a/sound/ambience/antag/attribution.txt +++ b/sound/ambience/antag/attribution.txt @@ -1,3 +1,11 @@ sound/ambience/antag/abductee.ogg is from "Warp SFX" https://freesound.org/people/Breviceps/sounds/453391 (CC0) sound/ambience/antag/brainwash.ogg is from "nog.wav" https://freesound.org/people/_NOMINAL_/sounds/124602 (CC-BY 3.0) -sound/ambience/antag/hypnosis.ogg is from "Flashback.wav" https://freesound.org/people/Sclolex/sounds/342103 (CC0) \ No newline at end of file +sound/ambience/antag/hypnosis.ogg is from "Flashback.wav" https://freesound.org/people/Sclolex/sounds/342103 (CC0) + +{ +ambimaint8.ogg +ambimaint9.ogg +ambimaint10.ogg +ambimaint11.ogg +ambimaint12.ogg +} made by Kayozz , license: CC-by-SA \ No newline at end of file diff --git a/sound/items/attributions.txt b/sound/items/attributions.txt index 592b1855aaebc..a9e708c477c68 100644 --- a/sound/items/attributions.txt +++ b/sound/items/attributions.txt @@ -24,6 +24,12 @@ plastic_pick_up.ogg - https://freesound.org/people/Jessica190091/sounds/491304/ plastic_drop.ogg - https://freesound.org/people/martian/sounds/338854/ , License: CC0 } - edited by sadboysuss +{ +skin_drop.ogg - https://freesound.org/people/Crinkem/sounds/501015/ , License: CC4 +skin_pick_up.ogg - https://freesound.org/people/Crinkem/sounds/501015/ , License CC3 +cardboard_drop and cardboard_pick_up - https://freesound.org/people/newagesoup/sounds/364736/ , License CC0 +} + { medkit_open.ogg - https://freesound.org/people/Jandre160108/sounds/365866/ , License: CC BY-NC 4.0 medkit_drop.ogg - https://freesound.org/people/Jandre160108/sounds/365866/ , License: CC BY-NC 4.0 diff --git a/sound/items/cardboard_drop.ogg b/sound/items/cardboard_drop.ogg new file mode 100644 index 0000000000000..c1082f2e37d7d Binary files /dev/null and b/sound/items/cardboard_drop.ogg differ diff --git a/sound/items/cardboard_pick_up.ogg b/sound/items/cardboard_pick_up.ogg new file mode 100644 index 0000000000000..a363c587b375b Binary files /dev/null and b/sound/items/cardboard_pick_up.ogg differ diff --git a/sound/items/skin_drop.ogg b/sound/items/skin_drop.ogg new file mode 100644 index 0000000000000..effdd83392bee Binary files /dev/null and b/sound/items/skin_drop.ogg differ diff --git a/sound/items/skin_pick_up.ogg b/sound/items/skin_pick_up.ogg new file mode 100644 index 0000000000000..9edf326cd9ef3 Binary files /dev/null and b/sound/items/skin_pick_up.ogg differ diff --git a/sound/voice/credits.txt b/sound/voice/attribution.txt similarity index 61% rename from sound/voice/credits.txt rename to sound/voice/attribution.txt index b54e6ad531966..7bfe5c4a9ce14 100644 --- a/sound/voice/credits.txt +++ b/sound/voice/attribution.txt @@ -3,3 +3,10 @@ borg_deathsound.ogg is spliced from two clips, both of which are under the CC At all complianator sounds are licensed under CC-BY-SA by Michael Haugh (supermichael) The male sharp gasps in /sound/voice/human/ are from https://freesound.org/people/bacruz666/sounds/341908/ and https://freesound.org/people/nettoi/sounds/677540/, the female sharp gasps are from https://freesound.org/people/drotzruhn/sounds/405203/ + +{ +human/male_sniff.ogg - https://freesound.org/people/Fluffayfish/sounds/327799/ , License: CC BY-NC 3.0 +human/male_sigh.ogg - https://freesound.org/people/giddster/sounds/336540/ , License: CC0 +human/female_sniff.ogg - https://freesound.org/people/SpliceSound/sounds/218307/ , License: CC0 +human/female_sigh.ogg - https://freesound.org/people/biawinter/sounds/408090/ , License: CC BY-NC 4.0 +} modified by grungussuss \ No newline at end of file diff --git a/sound/voice/human/female_sigh.ogg b/sound/voice/human/female_sigh.ogg new file mode 100644 index 0000000000000..3c338a868baf5 Binary files /dev/null and b/sound/voice/human/female_sigh.ogg differ diff --git a/sound/voice/human/female_sniff.ogg b/sound/voice/human/female_sniff.ogg new file mode 100644 index 0000000000000..edc75248790de Binary files /dev/null and b/sound/voice/human/female_sniff.ogg differ diff --git a/sound/voice/human/male_sigh.ogg b/sound/voice/human/male_sigh.ogg new file mode 100644 index 0000000000000..ec61683d68e93 Binary files /dev/null and b/sound/voice/human/male_sigh.ogg differ diff --git a/sound/voice/human/male_sniff.ogg b/sound/voice/human/male_sniff.ogg new file mode 100644 index 0000000000000..b940505c97a8a Binary files /dev/null and b/sound/voice/human/male_sniff.ogg differ diff --git a/sound/weapons/gun/smartgun/smartgun_shoot_1.ogg b/sound/weapons/gun/smartgun/smartgun_shoot_1.ogg new file mode 100644 index 0000000000000..5d567b7fca6c0 Binary files /dev/null and b/sound/weapons/gun/smartgun/smartgun_shoot_1.ogg differ diff --git a/sound/weapons/gun/smartgun/smartgun_shoot_2.ogg b/sound/weapons/gun/smartgun/smartgun_shoot_2.ogg new file mode 100644 index 0000000000000..9891482865c66 Binary files /dev/null and b/sound/weapons/gun/smartgun/smartgun_shoot_2.ogg differ diff --git a/sound/weapons/gun/smartgun/smartgun_shoot_3.ogg b/sound/weapons/gun/smartgun/smartgun_shoot_3.ogg new file mode 100644 index 0000000000000..89eb9fcc989c4 Binary files /dev/null and b/sound/weapons/gun/smartgun/smartgun_shoot_3.ogg differ diff --git a/tgstation.dme b/tgstation.dme index d5ec19775013b..a64698fcfbb1d 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -396,6 +396,7 @@ #include "code\__DEFINES\traits\macros.dm" #include "code\__DEFINES\traits\sources.dm" #include "code\__HELPERS\_auxtools_api.dm" +#include "code\__HELPERS\_dreamluau.dm" #include "code\__HELPERS\_lists.dm" #include "code\__HELPERS\_planes.dm" #include "code\__HELPERS\_string_lists.dm" @@ -1050,6 +1051,7 @@ #include "code\datums\components\bumpattack.dm" #include "code\datums\components\burning.dm" #include "code\datums\components\butchering.dm" +#include "code\datums\components\callouts.dm" #include "code\datums\components\caltrop.dm" #include "code\datums\components\can_flash_from_behind.dm" #include "code\datums\components\chasm.dm" @@ -2954,7 +2956,6 @@ #include "code\modules\admin\verbs\shuttlepanel.dm" #include "code\modules\admin\verbs\spawnobjasmob.dm" #include "code\modules\admin\verbs\special_verbs.dm" -#include "code\modules\admin\verbs\lua\_hooks.dm" #include "code\modules\admin\verbs\lua\_wrappers.dm" #include "code\modules\admin\verbs\lua\helpers.dm" #include "code\modules\admin\verbs\lua\lua_editor.dm" @@ -4464,7 +4465,6 @@ #include "code\modules\lighting\lighting_atom.dm" #include "code\modules\lighting\lighting_corner.dm" #include "code\modules\lighting\lighting_object.dm" -#include "code\modules\lighting\lighting_setup.dm" #include "code\modules\lighting\lighting_source.dm" #include "code\modules\lighting\lighting_turf.dm" #include "code\modules\lighting\static_lighting_area.dm" diff --git a/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx b/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx index 4bd0383d7d759..ba577a1e5ef2f 100644 --- a/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx +++ b/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx @@ -30,7 +30,23 @@ export const ChatPageSettings = (props) => { return (
- + {!!!page.isMain && ( + + - ) : ( - '' - )} - - - - {!page.isMain ? ( - - Reorder Chat:  - - - - ) : ( - '' )} diff --git a/tgui/packages/tgui-panel/settings/SettingsGeneral.tsx b/tgui/packages/tgui-panel/settings/SettingsGeneral.tsx index 8203f0313951d..3599f24e70703 100644 --- a/tgui/packages/tgui-panel/settings/SettingsGeneral.tsx +++ b/tgui/packages/tgui-panel/settings/SettingsGeneral.tsx @@ -8,8 +8,8 @@ import { Divider, Input, LabeledList, - NumberInput, Section, + Slider, Stack, } from 'tgui/components'; @@ -20,10 +20,11 @@ import { FONTS } from './constants'; import { selectSettings } from './selectors'; export function SettingsGeneral(props) { - const { theme, fontFamily, fontSize, lineHeight } = + const { theme, fontFamily, fontSize, lineHeight, statLinked, statFontSize } = useSelector(selectSettings); const dispatch = useDispatch(); const [freeFont, setFreeFont] = useState(false); + const [statFont, setStatFont] = useState(false); return (
@@ -109,35 +110,66 @@ export function SettingsGeneral(props) { )} - - toFixed(value)} - onChange={(value) => - dispatch( - updateSettings({ - fontSize: value, - }), - ) - } - /> + + + + toFixed(value)} + onChange={(e, value) => + dispatch( + updateSettings({ + [statFont ? 'statFontSize' : 'fontSize']: value, + }), + ) + } + /> + + + + + {!!statFont && ( + + - ); - } else if (UnconvertibleLuaValueRegex.test(thing)) { - return {thing.charAt(0).toUpperCase() + thing.substring(1)}; - } else if (RefRegex.test(thing)) { - return ( - - ); - } else if (thing === null) { - return Nil; - } else { - return thing; - } - } else { - return {thing}; - } - }; - - const ListMapperInner = (element, i) => { - const { key, value } = element; - const basePath = path ? path : []; - let keyPath = [...basePath, { index: i + 1, type: 'key' }]; - let valuePath = [...basePath, { index: i + 1, type: 'value' }]; - let entryPath = [...basePath, { index: i + 1, type: 'entry' }]; - - if (key === null && skipNulls) { - return; - } - - /* - * Finding a function only accessible as a table's key is too awkward to - * deal with for now - */ - let keyNode = ThingNode(key, keyPath, { callType: null }); - - /* - * Likewise, since table, thread, and userdata equality is tested by - * reference rather than value, we can't find functions whose keys - * within the table are tables, threads, or userdata - */ - const uniquelyIndexable = - (typeof key === 'string' && - !(UnconvertibleLuaValueRegex.test(key) || RefRegex.test(key))) || - typeof key === 'number'; - let valueNode = ThingNode(value, valuePath, { - callType: uniquelyIndexable && callType, - }); - return ( - - - - ); - } - return ( - <> - {i > 0 && } - - {message} - - {repeats && ( - - x{repeats + 1} - - )} - - ); - }); -}; diff --git a/tgui/packages/tgui/interfaces/LuaEditor/Log.tsx b/tgui/packages/tgui/interfaces/LuaEditor/Log.tsx new file mode 100644 index 0000000000000..b4f2b82bb3e9f --- /dev/null +++ b/tgui/packages/tgui/interfaces/LuaEditor/Log.tsx @@ -0,0 +1,224 @@ +import { Dispatch, SetStateAction } from 'react'; + +import { useBackend } from '../../backend'; +import { + Box, + Button, + Collapsible, + Divider, + LabeledList, + Stack, +} from '../../components'; +import { logger } from '../../logging'; +import { ListMapper } from './ListMapper'; +import { LuaEditorData, LuaEditorModal } from './types'; + +const parsePanic = (name, panic_json) => { + const panic_info = JSON.parse(panic_json); + const { + message, + location: { file, line }, + backtrace, + } = panic_info; + return ( + <> + + {name} panicked at {file}:{line}: {message} + + + + {backtrace + ?.filter( + (frame) => frame.file !== undefined && frame.line !== undefined, + ) + ?.map(({ name, file, line }, i) => ( + <> + {i > 0 && } + + + {name} + + {file}:{line} + + + + + ))} + + + + ); +}; + +type LogProps = { + setViewedChunk: Dispatch>; + setModal: Dispatch>; +}; + +export const Log = (props: LogProps) => { + const { act, data } = useBackend(); + const { stateLog } = data; + const { setViewedChunk, setModal } = props; + return stateLog.map((element, i) => { + const { status, repeats } = element; + let output; + let messageColor; + switch (status) { + case 'sleep': { + const { chunk, name } = element; + if (chunk) { + messageColor = 'blue'; + output = ( + <> + {name} slept. + + ); + } + break; + } + case 'yield': { + const { name, return_values, variants } = element; + output = ( + <> + {name} yielded + {return_values.length + ? ` ${return_values.length} value${ + return_values.length > 1 ? 's' : '' + }` + : ''} + . + {return_values.length ? ( + + act('vvReturnValue', { + entryIndex: i + 1, + indices: path, + }) + } + /> + ) : ( +
+ )} + + ); + messageColor = 'yellow'; + break; + } + case 'finished': { + const { name, return_values, variants } = element; + output = ( + <> + {name} returned + {return_values.length + ? ` ${return_values.length} value${ + return_values.length > 1 ? 's' : '' + }` + : ''} + . + + {return_values.length ? ( + + act('vvReturnValue', { + entryIndex: i + 1, + tableIndices: path, + }) + } + /> + ) : ( +
+ )} +
+ + ); + messageColor = 'green'; + break; + } + case 'error': { + const { message } = element; + output = message; + messageColor = 'red'; + break; + } + case 'panic': { + const { name, message } = element; + output = parsePanic(name, message); + break; + } + case 'runtime': { + const { file, line, message, stack } = element; + output = ( + <> + Runtime at {file}:{line}: {message} + { + return { key: null, value: frame }; + })} + name="Stack Trace" + collapsible + /> + + ); + messageColor = 'red'; + break; + } + case 'print': { + const { message } = element; + output = message; + break; + } + default: + logger.warn(`unknown log status ${status}`); + } + if (output === undefined) { + return; + } + const { chunk } = element; + if (chunk) { + output = ( + <> + {output} + + + ); + } + return ( + <> + {i > 0 && } + + {output} + + {repeats && ( + + x{repeats + 1} + + )} + + ); + }); +}; diff --git a/tgui/packages/tgui/interfaces/LuaEditor/StateSelectModal.jsx b/tgui/packages/tgui/interfaces/LuaEditor/StateSelectModal.tsx similarity index 64% rename from tgui/packages/tgui/interfaces/LuaEditor/StateSelectModal.jsx rename to tgui/packages/tgui/interfaces/LuaEditor/StateSelectModal.tsx index 3ac6373611e1a..4ff3deeb09bfe 100644 --- a/tgui/packages/tgui/interfaces/LuaEditor/StateSelectModal.jsx +++ b/tgui/packages/tgui/interfaces/LuaEditor/StateSelectModal.tsx @@ -1,16 +1,20 @@ -import { useBackend, useLocalState } from '../../backend'; +import { useState } from 'react'; + +import { useBackend } from '../../backend'; import { Button, Input, Modal, Section, Stack } from '../../components'; +import { LuaEditorData, LuaEditorModal } from './types'; + +type StateSelectModalProps = { + setModal: (modal: LuaEditorModal) => void; +}; -export const StateSelectModal = (props) => { - const { act, data } = useBackend(); - const [, setModal] = useLocalState('modal', 'states'); - const [input, setInput] = useLocalState('newStateName', ''); +export const StateSelectModal = (props: StateSelectModalProps) => { + const { act, data } = useBackend(); + const { setModal } = props; + const [input, setInput] = useState(); const { states } = data; return ( - +
{ color="red" icon="window-close" onClick={() => { - setModal(null); + setModal(undefined); }} > Cancel @@ -30,7 +34,7 @@ export const StateSelectModal = (props) => { ))} - + { @@ -36,16 +35,16 @@ export const TaskManager = (props) => {
- +
- {yields.map((info, i) => ( - + {yields.map(({ index, name }, i) => ( + - {noStateYet ? ( - -

Please select or create a lua state to get started.

-
- ) : ( - - -
- - - - } - > -