From e7a7a74952ca6a9942e11f97fae4c35f44a172e7 Mon Sep 17 00:00:00 2001 From: Social-Moth <117739059+Social-Moth@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:26:35 -0600 Subject: [PATCH 01/55] Buff spacer by reducing it's cost (#88055) ## About The Pull Request Reduced cost of "Spacer" quirk by 2 points ## Why It's Good For The Game Spacer is a quirk with many downsides built in, but it's cost is on par with strong perks that have no negatives. 20% less damage from space and faster movement is debatably worth built in depression while on Icebox or playing Shaft Miner, but it's cost makes using it really restrictive for the flavor and gameplay benefits it offers ## Changelog :cl: tweak: Reduce Spacers cost from 7 to 5 /:cl: --- code/datums/quirks/positive_quirks/spacer.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/datums/quirks/positive_quirks/spacer.dm b/code/datums/quirks/positive_quirks/spacer.dm index 344462703e906..8d1503ccf2f6e 100644 --- a/code/datums/quirks/positive_quirks/spacer.dm +++ b/code/datums/quirks/positive_quirks/spacer.dm @@ -9,7 +9,7 @@ gain_text = span_notice("You feel at home in space.") lose_text = span_danger("You feel homesick.") icon = FA_ICON_USER_ASTRONAUT - value = 7 + value = 5 quirk_flags = QUIRK_HUMAN_ONLY|QUIRK_CHANGES_APPEARANCE medical_record_text = "Patient is well-adapted to non-terrestrial environments." mail_goodies = list( From b10a9d26fcaaeac49e541b9574bf7058072cefc5 Mon Sep 17 00:00:00 2001 From: jimmyl <70376633+mc-oofert@users.noreply.github.com> Date: Thu, 21 Nov 2024 23:29:59 +0100 Subject: [PATCH 02/55] removes quiet succumb as it actually works now (#88060) ## About The Pull Request removes RMB quiet succumb as it actually works now https://github.com/user-attachments/assets/d59351e5-3d1c-4743-95d9-99b52629e133 ## Why It's Good For The Game the trimtext bug was patched out both in our code and additionally byond 515.1647 ## Changelog :cl: del: removed quiet succumb which is pointless now that the bug got fixed /:cl: --- code/_onclick/hud/alert.dm | 27 --------------------------- code/modules/mob/living/living.dm | 2 +- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index c3fb19352ab05..73916264a8b3c 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -498,19 +498,6 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." "All Good Things Must End" ) -/atom/movable/screen/alert/succumb/Initialize(mapload, datum/hud/hud_owner) - . = ..() - register_context() - -/atom/movable/screen/alert/succumb/add_context(atom/source, list/context, obj/item/held_item, mob/user) - context[SCREENTIP_CONTEXT_LMB] = "Succumb With Last Words" - context[SCREENTIP_CONTEXT_RMB] = "Succumb Silently" - return CONTEXTUAL_SCREENTIP_SET - -#define FASTSUCCUMB_YES "Yes" -#define FASTSUCCUMB_WAIT "Wait, I have last words!" -#define FASTSUCCUMB_NO "No" - /atom/movable/screen/alert/succumb/Click(location, control, params) . = ..() if(!.) @@ -521,17 +508,6 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." var/title = pick(death_titles) - if(LAZYACCESS(params2list(params), RIGHT_CLICK)) - //Succumbing without a message - var/choice = tgui_alert(living_owner, "Are you sure you want to succumb?", title, list(FASTSUCCUMB_YES, FASTSUCCUMB_WAIT, FASTSUCCUMB_NO)) - switch(choice) - if(FASTSUCCUMB_NO, null) - return - if(FASTSUCCUMB_YES) - living_owner.succumb() - return - //if(FASTSUCCUMB_WAIT), we continue to last words - //Succumbing with a message var/last_whisper = tgui_input_text(usr, "Do you have any last words?", title, max_length = CHAT_MESSAGE_MAX_LENGTH, encode = FALSE) // saycode already handles sanitization if(isnull(last_whisper)) @@ -540,9 +516,6 @@ or shoot a gun to move around via Newton's 3rd Law of Motion." living_owner.say("#[last_whisper]") living_owner.succumb(whispered = length(last_whisper) > 0) -#undef FASTSUCCUMB_NO -#undef FASTSUCCUMB_WAIT -#undef FASTSUCCUMB_YES //ALIENS /atom/movable/screen/alert/alien_plas diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index c77c576ffad21..b64a009ac1a40 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -534,7 +534,7 @@ if (!CAN_SUCCUMB(src)) if(HAS_TRAIT(src, TRAIT_SUCCUMB_OVERRIDE)) if(whispered) - to_chat(src, span_notice("Your immortal body is keeping you alive. If you want to accept death, you must do so [span_bold("quietly")]."), type=MESSAGE_TYPE_INFO) + to_chat(src, span_notice("Your immortal body is keeping you alive! Unless you just press the UI button."), type=MESSAGE_TYPE_INFO) return else to_chat(src, span_warning("You are unable to succumb to death! This life continues."), type=MESSAGE_TYPE_INFO) From c3fe91392c58c9d181a50ee0b9d9c4f700d770c7 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Thu, 21 Nov 2024 22:30:29 +0000 Subject: [PATCH 03/55] Automatic changelog for PR #88060 [ci skip] --- html/changelogs/AutoChangeLog-pr-88060.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88060.yml diff --git a/html/changelogs/AutoChangeLog-pr-88060.yml b/html/changelogs/AutoChangeLog-pr-88060.yml new file mode 100644 index 0000000000000..225734e20dfba --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88060.yml @@ -0,0 +1,4 @@ +author: "mc-oofert" +delete-after: True +changes: + - rscdel: "removed quiet succumb which is pointless now that the bug got fixed" \ No newline at end of file From 5c2fc8a47f812bf0f4884edbefbcf307ea20a517 Mon Sep 17 00:00:00 2001 From: Acantharctia <96220088+Acantharctia@users.noreply.github.com> Date: Thu, 21 Nov 2024 19:32:46 -0300 Subject: [PATCH 04/55] Enhances the Numb quirk by expanding its effects (#88029) ## About The Pull Request Works on details overlooked on the original PR. - Removes screams from damage. - Removes screen overlay from damage. ## Why It's Good For The Game Enhances the quirk by adding on to the bare-bones structure it had while also making it more mechanically unique. ## Changelog :cl: balance: Numb quirk now stops you from screaming and seeing the damage overlay when damaged. Better keep a close eye on your health. /:cl: --- code/datums/quirks/negative_quirks/numb.dm | 2 ++ code/modules/mob/living/carbon/carbon.dm | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/code/datums/quirks/negative_quirks/numb.dm b/code/datums/quirks/negative_quirks/numb.dm index cd4f28cb30228..ee8b86d342679 100644 --- a/code/datums/quirks/negative_quirks/numb.dm +++ b/code/datums/quirks/negative_quirks/numb.dm @@ -10,6 +10,8 @@ /datum/quirk/numb/add(client/client_source) quirk_holder.apply_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + quirk_holder.add_traits(list(TRAIT_ANALGESIA, QUIRK_TRAIT)) /datum/quirk/numb/remove(client/client_source) quirk_holder.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type) + quirk_holder.remove_traits(list(TRAIT_ANALGESIA, QUIRK_TRAIT)) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 288623af8655d..63fd4aad17b47 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -749,7 +749,7 @@ //Fire and Brute damage overlay (BSSR) var/hurtdamage = getBruteLoss() + getFireLoss() + damageoverlaytemp - if(hurtdamage) + if(hurtdamage && !HAS_TRAIT(src, TRAIT_ANALGESIA)) var/severity = 0 switch(hurtdamage) if(5 to 15) From 7e1979bb3d73a9ba3cfd16b0723404bcd47ef617 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Thu, 21 Nov 2024 22:33:10 +0000 Subject: [PATCH 05/55] Automatic changelog for PR #88029 [ci skip] --- html/changelogs/AutoChangeLog-pr-88029.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88029.yml diff --git a/html/changelogs/AutoChangeLog-pr-88029.yml b/html/changelogs/AutoChangeLog-pr-88029.yml new file mode 100644 index 0000000000000..61c7f8702aef4 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88029.yml @@ -0,0 +1,4 @@ +author: "Acantharctia" +delete-after: True +changes: + - balance: "Numb quirk now stops you from screaming and seeing the damage overlay when damaged. Better keep a close eye on your health." \ No newline at end of file From abe868c9215456737b994ea310272f585c40d2bf Mon Sep 17 00:00:00 2001 From: Kiko Wen <143291881+KikoWen0@users.noreply.github.com> Date: Fri, 22 Nov 2024 00:33:37 +0200 Subject: [PATCH 06/55] Changes the stairs near arrivals on Nebula and more (#88020) ## About The Pull Request Makes stairs near arrivals more convenient to use and fixes some long ago mentioned problems ![image](https://github.com/user-attachments/assets/1c0d711b-7eaf-43bb-a66d-d7cb94dbbf3d) ![image](https://github.com/user-attachments/assets/c52e9330-6806-492f-85c3-e04b21a1d680) ## Why It's Good For The Game Better version of https://github.com/tgstation/tgstation/pull/87963 ## Changelog :cl: map: Nebula Arrivals stairs are now easier to navigate map: Fixed unconnected wire in dormitories on Nebula map: Fixed engineers having free pass to cargo department on Nebula /:cl: --- .../map_files/NebulaStation/NebulaStation.dmm | 207 +++++++++++------- 1 file changed, 130 insertions(+), 77 deletions(-) diff --git a/_maps/map_files/NebulaStation/NebulaStation.dmm b/_maps/map_files/NebulaStation/NebulaStation.dmm index fc9b1b99823b4..f4e7bbd43cc51 100644 --- a/_maps/map_files/NebulaStation/NebulaStation.dmm +++ b/_maps/map_files/NebulaStation/NebulaStation.dmm @@ -770,6 +770,7 @@ /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, +/obj/structure/railing/corner/end/flip, /turf/open/floor/iron/dark/small, /area/station/maintenance/central) "afN" = ( @@ -11757,8 +11758,6 @@ /obj/effect/turf_decal/stripes/line{ dir = 1 }, -/obj/effect/turf_decal/stripes/line, -/obj/effect/turf_decal/trimline/dark/warning, /obj/effect/turf_decal/trimline/dark/warning{ dir = 1 }, @@ -11769,6 +11768,9 @@ /obj/machinery/power/apc/auto_name/directional/west, /obj/structure/cable, /obj/machinery/camera/autoname/directional/west, +/obj/machinery/door/firedoor/border_only{ + dir = 1 + }, /turf/open/floor/iron, /area/station/hallway/secondary/entry) "bMP" = ( @@ -12152,6 +12154,19 @@ }, /turf/open/floor/iron/dark, /area/station/science/xenobiology) +"bPJ" = ( +/obj/structure/table/glass, +/obj/item/reagent_containers/cup/glass/mug/britcup{ + pixel_x = -7; + pixel_y = 9 + }, +/obj/item/reagent_containers/cup/glass/coffee{ + pixel_y = 5; + pixel_x = 7 + }, +/obj/machinery/digital_clock/directional/south, +/turf/open/floor/iron/dark/small, +/area/station/hallway/secondary/entry) "bPN" = ( /obj/effect/turf_decal/siding/dark{ dir = 8 @@ -14491,6 +14506,7 @@ name = "Auxillary Base Shutters"; dir = 8 }, +/obj/effect/turf_decal/box, /turf/open/floor/iron/dark/textured_half{ dir = 1 }, @@ -27279,13 +27295,6 @@ /turf/open/floor/engine/hull/reinforced, /area/space/nearstation) "eaQ" = ( -/obj/effect/turf_decal/stripes/corner{ - dir = 1 - }, -/obj/structure/sign/warning/secure_area/directional/west, -/obj/effect/turf_decal/trimline/dark/corner{ - dir = 1 - }, /obj/effect/turf_decal/siding/thinplating_new/corner{ dir = 1 }, @@ -42490,6 +42499,12 @@ }, /turf/open/floor/iron/dark, /area/station/command/gateway) +"gnG" = ( +/obj/effect/turf_decal/siding/thinplating_new{ + dir = 4 + }, +/turf/open/floor/iron, +/area/station/hallway/primary/central) "gnK" = ( /obj/machinery/airalarm/directional/south, /obj/structure/railing{ @@ -56439,6 +56454,9 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/structure/disposalpipe/segment, +/obj/structure/railing/corner/end/flip{ + dir = 1 + }, /turf/open/floor/iron/dark/small, /area/station/maintenance/central) "isi" = ( @@ -63090,18 +63108,15 @@ /area/station/engineering/supermatter/room/upper) "jsw" = ( /obj/effect/decal/cleanable/dirt, -/obj/structure/railing{ - dir = 4 - }, -/obj/effect/turf_decal/trimline/dark/warning{ - dir = 4 - }, /obj/effect/turf_decal/siding/thinplating_new/dark{ dir = 4 }, /obj/structure/cable, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, +/obj/effect/turf_decal/trimline/dark/line{ + dir = 4 + }, /turf/open/floor/iron/dark/small, /area/station/maintenance/central) "jsz" = ( @@ -68958,9 +68973,6 @@ /turf/open/floor/plating/airless, /area/station/science/ordnance/bomb) "kki" = ( -/obj/structure/railing{ - dir = 4 - }, /obj/effect/turf_decal/siding/thinplating_new/dark{ dir = 4 }, @@ -72845,6 +72857,18 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /turf/open/floor/iron/dark, /area/station/security/brig) +"kJJ" = ( +/obj/structure/rack, +/obj/effect/decal/cleanable/oil, +/obj/effect/turf_decal/bot_white, +/obj/item/clothing/suit/hazardvest, +/obj/item/clothing/suit/hazardvest, +/obj/item/tank/internals/emergency_oxygen, +/obj/item/tank/internals/emergency_oxygen, +/obj/item/clothing/mask/breath, +/obj/item/clothing/mask/breath, +/turf/open/floor/iron/dark/textured_large, +/area/station/maintenance/central) "kJO" = ( /obj/effect/turf_decal/tile/red/anticorner/contrasted{ dir = 8 @@ -75522,9 +75546,6 @@ /obj/structure/railing{ dir = 8 }, -/obj/effect/turf_decal/trimline/dark/warning{ - dir = 8 - }, /obj/effect/turf_decal/siding/thinplating_new/dark{ dir = 8 }, @@ -75532,6 +75553,9 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/structure/disposalpipe/segment, +/obj/effect/turf_decal/trimline/dark/line{ + dir = 8 + }, /turf/open/floor/iron/dark/small, /area/station/maintenance/central) "lhV" = ( @@ -78619,6 +78643,12 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /turf/open/floor/iron/dark, /area/station/medical/virology) +"lFB" = ( +/obj/structure/chair/sofa/bench/solo{ + dir = 4 + }, +/turf/open/floor/iron/dark/small, +/area/station/hallway/secondary/entry) "lFM" = ( /obj/effect/turf_decal/tile/purple, /obj/structure/railing/corner/end/flip, @@ -78763,6 +78793,11 @@ /obj/structure/cable, /turf/open/floor/iron/dark, /area/station/science/xenobiology) +"lGG" = ( +/obj/effect/turf_decal/siding/thinplating_new/dark, +/obj/effect/turf_decal/trimline/dark/line, +/turf/open/floor/iron/dark/small, +/area/station/maintenance/central) "lGR" = ( /obj/effect/turf_decal/siding/dark{ dir = 9 @@ -83903,28 +83938,22 @@ /turf/open/floor/iron/dark, /area/station/engineering/atmos) "mxU" = ( -/obj/effect/turf_decal/stripes/corner{ - dir = 8 - }, /obj/effect/turf_decal/stripes/corner{ dir = 1 }, /obj/effect/turf_decal/trimline/dark/corner{ dir = 1 }, -/obj/effect/turf_decal/trimline/dark/corner{ - dir = 8 - }, /obj/effect/turf_decal/siding/thinplating_new/corner{ dir = 8 }, /obj/effect/turf_decal/siding/thinplating_new/corner{ dir = 1 }, -/obj/machinery/door/firedoor/border_only{ - dir = 8 - }, /obj/structure/cable, +/obj/structure/railing/corner/end/flip{ + dir = 1 + }, /turf/open/floor/iron, /area/station/hallway/primary/central) "mxV" = ( @@ -84189,6 +84218,12 @@ }, /turf/open/floor/wood/large, /area/station/service/theater) +"mAd" = ( +/obj/effect/turf_decal/siding/thinplating_new{ + dir = 8 + }, +/turf/open/floor/iron, +/area/station/hallway/primary/central) "mAi" = ( /obj/effect/turf_decal/siding/wood{ dir = 8 @@ -85767,6 +85802,15 @@ /obj/machinery/light_switch/directional/south, /turf/open/floor/iron/dark, /area/station/engineering/lobby) +"mMv" = ( +/obj/structure/table/glass, +/obj/item/newspaper{ + pixel_x = 3; + pixel_y = 4 + }, +/obj/machinery/digital_clock/directional/north, +/turf/open/floor/iron/dark/small, +/area/station/hallway/secondary/entry) "mMD" = ( /obj/effect/turf_decal/trimline/blue/line{ dir = 6 @@ -93247,20 +93291,12 @@ /area/station/service/chapel/office) "nTb" = ( /obj/effect/turf_decal/stripes/corner, -/obj/effect/turf_decal/stripes/corner{ - dir = 4 - }, /obj/effect/turf_decal/trimline/dark/corner, -/obj/effect/turf_decal/trimline/dark/corner{ - dir = 4 - }, /obj/effect/turf_decal/siding/thinplating_new/corner, /obj/effect/turf_decal/siding/thinplating_new/corner{ dir = 4 }, -/obj/machinery/door/firedoor/border_only{ - dir = 4 - }, +/obj/structure/railing/corner/end/flip, /turf/open/floor/iron, /area/station/hallway/primary/central) "nTe" = ( @@ -93392,12 +93428,6 @@ "nUi" = ( /obj/machinery/light/directional/east, /obj/effect/turf_decal/stripes/line, -/obj/effect/turf_decal/stripes/line{ - dir = 1 - }, -/obj/effect/turf_decal/trimline/dark/warning{ - dir = 1 - }, /obj/effect/turf_decal/trimline/dark/warning, /obj/effect/turf_decal/siding/thinplating_new, /obj/effect/turf_decal/siding/thinplating_new{ @@ -93405,6 +93435,7 @@ }, /obj/structure/sign/poster/official/random/directional/east, /obj/machinery/camera/autoname/directional/east, +/obj/machinery/door/firedoor/border_only, /turf/open/floor/iron, /area/station/hallway/secondary/entry) "nUp" = ( @@ -102226,6 +102257,21 @@ }, /turf/open/floor/iron/white/herringbone, /area/station/commons/toilet/restrooms) +"pfI" = ( +/obj/effect/turf_decal/stripes/line{ + dir = 1 + }, +/obj/effect/turf_decal/siding/thinplating_new/dark{ + dir = 1 + }, +/obj/effect/turf_decal/trimline/dark/warning{ + dir = 1 + }, +/obj/structure/sign/directions/arrival/directional/west{ + dir = 10 + }, +/turf/open/floor/iron/dark/small, +/area/station/maintenance/central) "pfY" = ( /obj/effect/turf_decal/siding/thinplating_new/dark{ dir = 10 @@ -121577,9 +121623,6 @@ /turf/open/floor/wood/large, /area/station/service/chapel/office) "rZU" = ( -/obj/structure/railing{ - dir = 8 - }, /obj/effect/turf_decal/siding/thinplating_new/dark{ dir = 8 }, @@ -121592,6 +121635,9 @@ /obj/structure/disposalpipe/segment, /obj/machinery/power/apc/auto_name/directional/west, /obj/machinery/camera/autoname/directional/west, +/obj/structure/railing/corner{ + dir = 8 + }, /turf/open/floor/iron/dark/small, /area/station/maintenance/central) "rZV" = ( @@ -122546,7 +122592,7 @@ /obj/structure/disposalpipe/segment{ dir = 4 }, -/obj/effect/mapping_helpers/airlock/access/all/engineering/aux_base, +/obj/effect/mapping_helpers/airlock/access/all/supply/general, /turf/open/floor/iron/dark, /area/station/construction/mining/aux_base) "shs" = ( @@ -123039,6 +123085,9 @@ /obj/effect/turf_decal/stripes/line, /obj/effect/turf_decal/siding/thinplating_new/dark, /obj/effect/turf_decal/trimline/dark/warning, +/obj/structure/sign/directions/arrival/directional/east{ + dir = 5 + }, /turf/open/floor/iron/dark/small, /area/station/maintenance/central) "sms" = ( @@ -123389,6 +123438,7 @@ }, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, +/obj/structure/cable, /turf/open/floor/iron/dark/corner, /area/station/commons/dorms) "spf" = ( @@ -129792,6 +129842,12 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /turf/open/floor/iron/dark/textured, /area/station/commons/fitness/recreation/lasertag) +"toI" = ( +/obj/structure/chair/sofa/bench/solo{ + dir = 8 + }, +/turf/open/floor/iron/dark/small, +/area/station/hallway/secondary/entry) "toJ" = ( /obj/effect/turf_decal/siding/thinplating_new/dark{ dir = 9 @@ -146917,12 +146973,6 @@ /turf/open/floor/plating/airless, /area/station/solars/starboard/fore) "vQl" = ( -/obj/structure/railing{ - dir = 8 - }, -/obj/effect/turf_decal/trimline/dark/warning{ - dir = 8 - }, /obj/effect/turf_decal/siding/thinplating_new/dark{ dir = 8 }, @@ -146930,6 +146980,9 @@ /obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, /obj/structure/disposalpipe/segment, +/obj/effect/turf_decal/trimline/dark/line{ + dir = 8 + }, /turf/open/floor/iron/dark/small, /area/station/maintenance/central) "vQw" = ( @@ -146942,7 +146995,7 @@ /obj/machinery/button/door/directional/north{ id = "aux_base_shutters"; name = "Public Shutters Control"; - req_access = list("aux_base") + req_access = list("cargo") }, /obj/machinery/light/small/directional/north, /obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4, @@ -149558,13 +149611,10 @@ /turf/open/floor/grass, /area/station/hallway/secondary/entry) "wkR" = ( -/obj/effect/turf_decal/stripes/line{ - dir = 1 - }, /obj/effect/turf_decal/siding/thinplating_new/dark{ dir = 1 }, -/obj/effect/turf_decal/trimline/dark/warning{ +/obj/effect/turf_decal/trimline/dark/line{ dir = 1 }, /turf/open/floor/iron/dark/small, @@ -149741,6 +149791,14 @@ }, /turf/open/floor/iron/white, /area/station/science/lab) +"wmi" = ( +/obj/structure/rack, +/obj/item/storage/toolbox/emergency, +/obj/effect/decal/cleanable/oil, +/obj/item/screwdriver, +/obj/effect/turf_decal/bot_white, +/turf/open/floor/iron/dark/textured_large, +/area/station/maintenance/central) "wmj" = ( /obj/effect/turf_decal/siding/wideplating_new/dark/corner{ dir = 1 @@ -150837,10 +150895,6 @@ /turf/open/floor/iron/dark, /area/station/medical/virology) "wvW" = ( -/obj/effect/turf_decal/stripes/corner, -/obj/structure/sign/warning/secure_area/directional/east, -/obj/structure/railing/corner, -/obj/effect/turf_decal/trimline/dark/corner, /obj/effect/turf_decal/siding/thinplating_new/corner, /turf/open/floor/iron, /area/station/hallway/primary/central) @@ -155497,7 +155551,6 @@ }, /area/station/maintenance/department/science) "xiv" = ( -/obj/structure/cable, /obj/machinery/atmospherics/components/unary/vent_pump/on/layer4{ dir = 4 }, @@ -193502,11 +193555,11 @@ pmV qFS tUf dOv -smp -fCR +lGG +wmi pmV qwd -wkR +pfI ntL lND yhH @@ -199159,7 +199212,7 @@ rLH smp fCR pmV -qwd +kJJ wkR aBc ooG @@ -258781,8 +258834,8 @@ bna ijB czt wvW -jBA -jBA +gnG +gnG nTb jBA jBA @@ -259038,8 +259091,8 @@ fub ijB eev kJl -cIJ -cIJ +mMv +toI nUi cIJ cIJ @@ -264695,8 +264748,8 @@ kJl cIJ cIJ bMx -cIJ -cIJ +lFB +bPJ kJl eev ijB @@ -264952,8 +265005,8 @@ psN xCp xCp mxU -xCp -xCp +mAd +mAd eaQ ijB ijB From 5d223fb9067d50057d22c3744ce35c390826635a Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Thu, 21 Nov 2024 22:34:00 +0000 Subject: [PATCH 07/55] Automatic changelog for PR #88020 [ci skip] --- html/changelogs/AutoChangeLog-pr-88020.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88020.yml diff --git a/html/changelogs/AutoChangeLog-pr-88020.yml b/html/changelogs/AutoChangeLog-pr-88020.yml new file mode 100644 index 0000000000000..0951c50b247fe --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88020.yml @@ -0,0 +1,6 @@ +author: "KikoWen0" +delete-after: True +changes: + - map: "Nebula Arrivals stairs are now easier to navigate" + - map: "Fixed unconnected wire in dormitories on Nebula" + - map: "Fixed engineers having free pass to cargo department on Nebula" \ No newline at end of file From 987aedde4caba2d051505f20be29e9e6df769b80 Mon Sep 17 00:00:00 2001 From: carlarctg <53100513+carlarctg@users.noreply.github.com> Date: Thu, 21 Nov 2024 20:43:51 -0300 Subject: [PATCH 08/55] [IDED] Replaces UZI domain reward with ballistic chameleon gun (#87600) ## About The Pull Request Replaces the mini-UZI beach battle domain reward with a ballistic chameleon gun. Also added a separate possible reward for an uzi bitrunning disk. --- code/modules/bitrunning/objects/disks.dm | 6 +++ .../virtual_domain/domains/island_brawl.dm | 4 +- .../chameleon/chameleon_action_subtypes.dm | 4 ++ .../clothing/chameleon/chameleon_gun.dm | 45 +++++++++++-------- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/code/modules/bitrunning/objects/disks.dm b/code/modules/bitrunning/objects/disks.dm index 17b768c54d08f..dd81a0f463ed0 100644 --- a/code/modules/bitrunning/objects/disks.dm +++ b/code/modules/bitrunning/objects/disks.dm @@ -185,3 +185,9 @@ /obj/item/crusher_trophy/vortex_talisman, /obj/item/crusher_trophy/ice_demon_cube, ) + +/obj/item/bitrunning_disk/item/mini_uzi + name = "bitrunning gear: mini-uzi" + selectable_items = list( + /obj/item/gun/ballistic/automatic/mini_uzi, + ) diff --git a/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm b/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm index 84eb53e026a29..3969907205750 100644 --- a/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm +++ b/code/modules/bitrunning/virtual_domain/domains/island_brawl.dm @@ -13,10 +13,10 @@ /obj/item/toy/beach_ball = 2, /obj/item/clothing/shoes/sandal = 1, /obj/item/clothing/glasses/sunglasses = 1, - /obj/item/gun/ballistic/automatic/mini_uzi = 1, + /obj/item/gun/energy/laser/chameleon/ballistic_only = 1, + /obj/item/bitrunning_disk/item/mini_uzi = 1, ) - /datum/lazy_template/virtual_domain/island_brawl/setup_domain(list/created_atoms) for(var/obj/effect/mob_spawn/ghost_role/human/virtual_domain/islander/spawner in created_atoms) custom_spawns += spawner diff --git a/code/modules/clothing/chameleon/chameleon_action_subtypes.dm b/code/modules/clothing/chameleon/chameleon_action_subtypes.dm index bd15bb908f227..1c433f0c0d953 100644 --- a/code/modules/clothing/chameleon/chameleon_action_subtypes.dm +++ b/code/modules/clothing/chameleon/chameleon_action_subtypes.dm @@ -301,3 +301,7 @@ . = ..() for(var/other_type in other_cham_types) add_chameleon_items(other_type) + +/datum/action/item_action/chameleon/change/gun/ballistic + chameleon_type = /obj/item/gun/ballistic + chameleon_name = "Gun" diff --git a/code/modules/clothing/chameleon/chameleon_gun.dm b/code/modules/clothing/chameleon/chameleon_gun.dm index dafd0ba5f624a..4a282a3e75aee 100644 --- a/code/modules/clothing/chameleon/chameleon_gun.dm +++ b/code/modules/clothing/chameleon/chameleon_gun.dm @@ -11,14 +11,15 @@ /// The badmin mode. Makes your projectiles act like the real deal. var/real_hits = FALSE + /// how it looks by default. + var/default_look = /obj/item/gun/energy/laser /obj/item/gun/energy/laser/chameleon/Initialize(mapload) . = ..() - recharge_newshot() AddElement(/datum/element/empprotection, EMP_PROTECT_SELF|EMP_PROTECT_CONTENTS) // Init order shenanigans dictate we have to do this last so we can't just use `active_type` var/datum/action/item_action/chameleon/change/gun/gun_action = locate() in actions - gun_action?.update_look(/obj/item/gun/energy/laser) + gun_action?.update_look(default_look) /** * Description: Resets the currently loaded chameleon variables, essentially resetting it to brand new. @@ -54,36 +55,34 @@ inhand_x_dimension = gun_to_set.inhand_x_dimension inhand_y_dimension = gun_to_set.inhand_y_dimension + // We dupe this casing and then delete it at the end, to grab the projectile. + var/obj/item/ammo_casing/casing_to_dupe + if(istype(gun_to_set, /obj/item/gun/ballistic)) var/obj/item/gun/ballistic/ball_gun = gun_to_set - var/obj/item/ammo_box/ball_ammo = new ball_gun.spawn_magazine_type(gun_to_set) - qdel(ball_gun) - - if(!istype(ball_ammo) || !ball_ammo.ammo_type) - qdel(ball_ammo) - return FALSE - - var/obj/item/ammo_casing/ball_cartridge = new ball_ammo.ammo_type(gun_to_set) - set_chameleon_ammo(ball_cartridge) + // We also need to copy the starting magazine for ballistics. + casing_to_dupe = initial(ball_gun.spawn_magazine_type.ammo_type) + casing_to_dupe = new casing_to_dupe(src) else if(istype(gun_to_set, /obj/item/gun/magic)) var/obj/item/gun/magic/magic_gun = gun_to_set - var/obj/item/ammo_casing/magic_cartridge = new magic_gun.ammo_type(gun_to_set) - set_chameleon_ammo(magic_cartridge) + casing_to_dupe = new magic_gun.ammo_type(src) else if(istype(gun_to_set, /obj/item/gun/energy)) var/obj/item/gun/energy/energy_gun = gun_to_set + // Even if the energy gun has multiple ammo types, we copy the first. Energy guns always (should) have a list in ammo_type. if(islist(energy_gun.ammo_type) && energy_gun.ammo_type.len) - var/obj/item/ammo_casing/energy_cartridge = energy_gun.ammo_type[1] - set_chameleon_ammo(energy_cartridge) + var/obj/item/first_casing = energy_gun.ammo_type[1] + casing_to_dupe = new first_casing.type(src) else if(istype(gun_to_set, /obj/item/gun/syringe)) - var/obj/item/ammo_casing/syringe_cartridge = new /obj/item/ammo_casing/syringegun(src) - set_chameleon_ammo(syringe_cartridge) + casing_to_dupe = new /obj/item/ammo_casing/syringegun(src) else - var/obj/item/ammo_casing/default_cartridge = new /obj/item/ammo_casing(src) - set_chameleon_ammo(default_cartridge) + casing_to_dupe = new /obj/item/ammo_casing(src) + + set_chameleon_ammo(casing_to_dupe) + qdel(casing_to_dupe) /** * Description: Sets the ammo type our gun should have. @@ -155,3 +154,11 @@ var/obj/item/gun/new_gun = new guntype(src) set_chameleon_gun(new_gun) qdel(new_gun) + +/obj/item/gun/energy/laser/chameleon/ballistic_only + actions_types = list(/datum/action/item_action/chameleon/change/gun/ballistic) + default_look = /obj/item/gun/ballistic/automatic/mini_uzi + +/obj/item/gun/energy/laser/chameleon/ballistic_only/Initialize(mapload) + . = ..() + set_chameleon_disguise(default_look) From d5ea6bf5ce3506e8ac3705f07a5cc5d06eed12af Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Thu, 21 Nov 2024 23:44:11 +0000 Subject: [PATCH 09/55] Automatic changelog for PR #87600 [ci skip] --- html/changelogs/AutoChangeLog-pr-87600.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-87600.yml diff --git a/html/changelogs/AutoChangeLog-pr-87600.yml b/html/changelogs/AutoChangeLog-pr-87600.yml new file mode 100644 index 0000000000000..8f0d87fd415b8 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-87600.yml @@ -0,0 +1,5 @@ +author: "carlarctg" +delete-after: True +changes: + - rscdel: "Replaces UZI domain reward with ballistic chameleon gun" + - rscadd: "Also added a separate possible reward for an uzi bitrunning disk." \ No newline at end of file From ce6bad9559b9910518996fb97b8043f7980eac0e Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 22 Nov 2024 00:33:52 +0000 Subject: [PATCH 10/55] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-87600.yml | 5 -- html/changelogs/AutoChangeLog-pr-87805.yml | 12 ----- html/changelogs/AutoChangeLog-pr-88012.yml | 4 -- html/changelogs/AutoChangeLog-pr-88015.yml | 4 -- html/changelogs/AutoChangeLog-pr-88017.yml | 4 -- html/changelogs/AutoChangeLog-pr-88020.yml | 6 --- html/changelogs/AutoChangeLog-pr-88026.yml | 4 -- html/changelogs/AutoChangeLog-pr-88029.yml | 4 -- html/changelogs/AutoChangeLog-pr-88040.yml | 4 -- html/changelogs/AutoChangeLog-pr-88041.yml | 4 -- html/changelogs/AutoChangeLog-pr-88042.yml | 5 -- html/changelogs/AutoChangeLog-pr-88043.yml | 4 -- html/changelogs/AutoChangeLog-pr-88044.yml | 4 -- html/changelogs/AutoChangeLog-pr-88045.yml | 4 -- html/changelogs/AutoChangeLog-pr-88048.yml | 4 -- html/changelogs/AutoChangeLog-pr-88049.yml | 4 -- html/changelogs/AutoChangeLog-pr-88052.yml | 4 -- html/changelogs/AutoChangeLog-pr-88054.yml | 4 -- html/changelogs/AutoChangeLog-pr-88056.yml | 4 -- html/changelogs/AutoChangeLog-pr-88059.yml | 4 -- html/changelogs/AutoChangeLog-pr-88060.yml | 4 -- html/changelogs/AutoChangeLog-pr-88061.yml | 4 -- html/changelogs/AutoChangeLog-pr-88069.yml | 4 -- html/changelogs/archive/2024-11.yml | 60 ++++++++++++++++++++++ 24 files changed, 60 insertions(+), 104 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-87600.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-87805.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88012.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88015.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88017.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88020.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88026.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88029.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88040.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88041.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88042.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88043.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88044.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88045.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88048.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88049.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88052.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88054.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88056.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88059.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88060.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88061.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88069.yml diff --git a/html/changelogs/AutoChangeLog-pr-87600.yml b/html/changelogs/AutoChangeLog-pr-87600.yml deleted file mode 100644 index 8f0d87fd415b8..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-87600.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "carlarctg" -delete-after: True -changes: - - rscdel: "Replaces UZI domain reward with ballistic chameleon gun" - - rscadd: "Also added a separate possible reward for an uzi bitrunning disk." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-87805.yml b/html/changelogs/AutoChangeLog-pr-87805.yml deleted file mode 100644 index 6ec1d65a16578..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-87805.yml +++ /dev/null @@ -1,12 +0,0 @@ -author: "tontyGH" -delete-after: True -changes: - - code_imp: "made /proc/getviewsize() pure" - - refactor: "mob/eye/ai_eye has been restructured, now inheriting from a generic mob/eye/camera type" - - refactor: "advanced cameras and their subtypes are now mob/eye/camera/remote subtypes" - - code_imp: "the cameranet no longer expects the user to be an AI eye" - - code_imp: "remote camera eyes have had their initialization streamlined" - - code_imp: "remote cameras handle assigning and unassigning users by themselves now" - - code_imp: "remote cameras now use weakrefs instead of hard referencing owners and origins" - - code_imp: "also the sentient disease is_define was removed (we don't have those anymore)" - - bugfix: "AI eyes no longer assign real names to themselves, fixing their orbit name" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88012.yml b/html/changelogs/AutoChangeLog-pr-88012.yml deleted file mode 100644 index 58037f5e808cd..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88012.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Ben10Omintrix" -delete-after: True -changes: - - bugfix: "fixes monkey AI not being able to attack" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88015.yml b/html/changelogs/AutoChangeLog-pr-88015.yml deleted file mode 100644 index ded49e2910ed7..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88015.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - bugfix: "Reconnected Wawa's sci to atmos distro" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88017.yml b/html/changelogs/AutoChangeLog-pr-88017.yml deleted file mode 100644 index 1d5fd6c85019e..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88017.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "grungussuss" -delete-after: True -changes: - - admin: "admins can now control the volume that some admin triggered sounds are played at" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88020.yml b/html/changelogs/AutoChangeLog-pr-88020.yml deleted file mode 100644 index 0951c50b247fe..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88020.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "KikoWen0" -delete-after: True -changes: - - map: "Nebula Arrivals stairs are now easier to navigate" - - map: "Fixed unconnected wire in dormitories on Nebula" - - map: "Fixed engineers having free pass to cargo department on Nebula" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88026.yml b/html/changelogs/AutoChangeLog-pr-88026.yml deleted file mode 100644 index a272897410c7a..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88026.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - bugfix: "Fixed all hoverboards having holyboard space slowdown effect" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88029.yml b/html/changelogs/AutoChangeLog-pr-88029.yml deleted file mode 100644 index 61c7f8702aef4..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88029.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Acantharctia" -delete-after: True -changes: - - balance: "Numb quirk now stops you from screaming and seeing the damage overlay when damaged. Better keep a close eye on your health." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88040.yml b/html/changelogs/AutoChangeLog-pr-88040.yml deleted file mode 100644 index d8246accb9d10..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88040.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - bugfix: "Fixes some atmos and record related log messages" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88041.yml b/html/changelogs/AutoChangeLog-pr-88041.yml deleted file mode 100644 index d5618c609759b..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88041.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - bugfix: "Monkeys no longer ignore basic rules such as \"you can't escape a passive grab if you're cuffed or in crit\"" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88042.yml b/html/changelogs/AutoChangeLog-pr-88042.yml deleted file mode 100644 index eae1321dcfba3..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88042.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "necromanceranne" -delete-after: True -changes: - - code_imp: "The cost for Core Equipment for nuclear operatives will always be equal to the cost of the contained items if purchased directly from the uplink." - - bugfix: "Improves the descriptions for some uplink entries." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88043.yml b/html/changelogs/AutoChangeLog-pr-88043.yml deleted file mode 100644 index 02c43ee4e727c..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88043.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "necromanceranne" -delete-after: True -changes: - - image: "Cleans up some redundant pixels on the bowman sprites." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88044.yml b/html/changelogs/AutoChangeLog-pr-88044.yml deleted file mode 100644 index f12d837c1a314..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88044.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "necromanceranne" -delete-after: True -changes: - - bugfix: "Correctly applies resistance and EMP proofing to the broadcasting camera so that it doesn't cause weird things to happen." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88045.yml b/html/changelogs/AutoChangeLog-pr-88045.yml deleted file mode 100644 index 2546931cc522e..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88045.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "necromanceranne" -delete-after: True -changes: - - image: "Kitchen and eating utensils now have suit storage sprites." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88048.yml b/html/changelogs/AutoChangeLog-pr-88048.yml deleted file mode 100644 index 66cbd6a3294cc..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88048.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - refactor: "Converted most common particle sources to use our new pooling system." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88049.yml b/html/changelogs/AutoChangeLog-pr-88049.yml deleted file mode 100644 index c4af8e0e6703c..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88049.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Dawnseer" -delete-after: True -changes: - - bugfix: "Turns Cap (White) into a white cap in loadout" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88052.yml b/html/changelogs/AutoChangeLog-pr-88052.yml deleted file mode 100644 index 3bee182df0481..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88052.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "grungussuss" -delete-after: True -changes: - - sound: "the \"curse you\" sfx has been replaced" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88054.yml b/html/changelogs/AutoChangeLog-pr-88054.yml deleted file mode 100644 index 1c0b39bbc4718..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88054.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SyncIt21" -delete-after: True -changes: - - bugfix: "Labour stacker machine gives points for sheets again" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88056.yml b/html/changelogs/AutoChangeLog-pr-88056.yml deleted file mode 100644 index 8aac561076b57..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88056.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - bugfix: "Fixed incorrect coverage descriptions" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88059.yml b/html/changelogs/AutoChangeLog-pr-88059.yml deleted file mode 100644 index 9da40b5eba146..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88059.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "wallem" -delete-after: True -changes: - - image: "Updates slot machine sprites" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88060.yml b/html/changelogs/AutoChangeLog-pr-88060.yml deleted file mode 100644 index 225734e20dfba..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88060.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "mc-oofert" -delete-after: True -changes: - - rscdel: "removed quiet succumb which is pointless now that the bug got fixed" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88061.yml b/html/changelogs/AutoChangeLog-pr-88061.yml deleted file mode 100644 index 3322c84351c64..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88061.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - bugfix: "Fixed limping status effect harddels" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88069.yml b/html/changelogs/AutoChangeLog-pr-88069.yml deleted file mode 100644 index b24f17ddf23e6..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88069.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "jlsnow301" -delete-after: True -changes: - - bugfix: "TGUI panel light mode should no longer be gross gray" \ No newline at end of file diff --git a/html/changelogs/archive/2024-11.yml b/html/changelogs/archive/2024-11.yml index 0f1dbefbf9c32..d88f4911d801b 100644 --- a/html/changelogs/archive/2024-11.yml +++ b/html/changelogs/archive/2024-11.yml @@ -764,3 +764,63 @@ to 80. timothymtorres, MrMelbert: - bugfix: Fix lustrous brain trauma not removing during polymorph +2024-11-22: + Acantharctia: + - balance: Numb quirk now stops you from screaming and seeing the damage overlay + when damaged. Better keep a close eye on your health. + Ben10Omintrix: + - bugfix: fixes monkey AI not being able to attack + Dawnseer: + - bugfix: Turns Cap (White) into a white cap in loadout + KikoWen0: + - map: Nebula Arrivals stairs are now easier to navigate + - map: Fixed unconnected wire in dormitories on Nebula + - map: Fixed engineers having free pass to cargo department on Nebula + Melbert: + - bugfix: Monkeys no longer ignore basic rules such as "you can't escape a passive + grab if you're cuffed or in crit" + - bugfix: Fixes some atmos and record related log messages + SmArtKar: + - bugfix: Reconnected Wawa's sci to atmos distro + - bugfix: Fixed incorrect coverage descriptions + - refactor: Converted most common particle sources to use our new pooling system. + - bugfix: Fixed all hoverboards having holyboard space slowdown effect + - bugfix: Fixed limping status effect harddels + SyncIt21: + - bugfix: Labour stacker machine gives points for sheets again + carlarctg: + - rscdel: Replaces UZI domain reward with ballistic chameleon gun + - rscadd: Also added a separate possible reward for an uzi bitrunning disk. + grungussuss: + - sound: the "curse you" sfx has been replaced + - admin: admins can now control the volume that some admin triggered sounds are + played at + jlsnow301: + - bugfix: TGUI panel light mode should no longer be gross gray + mc-oofert: + - rscdel: removed quiet succumb which is pointless now that the bug got fixed + necromanceranne: + - image: Cleans up some redundant pixels on the bowman sprites. + - image: Kitchen and eating utensils now have suit storage sprites. + - code_imp: The cost for Core Equipment for nuclear operatives will always be equal + to the cost of the contained items if purchased directly from the uplink. + - bugfix: Improves the descriptions for some uplink entries. + - bugfix: Correctly applies resistance and EMP proofing to the broadcasting camera + so that it doesn't cause weird things to happen. + tontyGH: + - code_imp: made /proc/getviewsize() pure + - refactor: mob/eye/ai_eye has been restructured, now inheriting from a generic + mob/eye/camera type + - refactor: advanced cameras and their subtypes are now mob/eye/camera/remote subtypes + - code_imp: the cameranet no longer expects the user to be an AI eye + - code_imp: remote camera eyes have had their initialization streamlined + - code_imp: remote cameras handle assigning and unassigning users by themselves + now + - code_imp: remote cameras now use weakrefs instead of hard referencing owners and + origins + - code_imp: also the sentient disease is_define was removed (we don't have those + anymore) + - bugfix: AI eyes no longer assign real names to themselves, fixing their orbit + name + wallem: + - image: Updates slot machine sprites From 0b0114cb39ccd59264ab9103ab69e1de5cdb2a29 Mon Sep 17 00:00:00 2001 From: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Date: Fri, 22 Nov 2024 09:45:55 +0200 Subject: [PATCH 11/55] gives some more very common behaviors a slight cooldown (#88066) --- .../mob/living/basic/farm_animals/bee/bee_ai_behavior.dm | 2 ++ .../mob/living/basic/farm_animals/bee/bee_ai_subtree.dm | 8 +++++++- .../living/basic/lavaland/raptor/raptor_ai_behavior.dm | 3 +++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm index 77fa9ce8ca088..1081c9b7b63b8 100644 --- a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm +++ b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm @@ -8,6 +8,7 @@ callback.Invoke() /datum/ai_behavior/find_hunt_target/pollinate + action_cooldown = 10 SECONDS /datum/ai_behavior/find_hunt_target/pollinate/valid_dinner(mob/living/source, obj/machinery/hydroponics/dinner, radius) if(!dinner.can_bee_pollinate()) @@ -16,6 +17,7 @@ /datum/ai_behavior/enter_exit_hive behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH + action_cooldown = 10 SECONDS /datum/ai_behavior/enter_exit_hive/setup(datum/ai_controller/controller, target_key, attack_key) . = ..() diff --git a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_subtree.dm b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_subtree.dm index 3d56dd990dcbd..76abb281b99b7 100644 --- a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_subtree.dm +++ b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_subtree.dm @@ -71,7 +71,7 @@ return var/mob/living/bee_pawn = controller.pawn - var/action_prob = (bee_pawn in current_home.contents) ? exit_chance : flyback_chance + var/action_prob = (bee_pawn.loc == current_home) ? exit_chance : flyback_chance if(!SPT_PROB(action_prob, seconds_per_tick)) return @@ -91,3 +91,9 @@ hunt_targets = list(/obj/machinery/hydroponics) hunt_range = 10 hunt_chance = 85 + +/datum/ai_planning_subtree/find_and_hunt_target/pollinate/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/atom/atom_pawn = controller.pawn + if(!isturf(atom_pawn.loc)) + return + return ..() diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_behavior.dm b/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_behavior.dm index 7e3022f95716d..a826fb4a4363a 100644 --- a/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_behavior.dm +++ b/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_behavior.dm @@ -2,11 +2,13 @@ always_reset_target = TRUE /datum/ai_behavior/find_hunt_target/injured_raptor + action_cooldown = 7.5 SECONDS /datum/ai_behavior/find_hunt_target/injured_raptor/valid_dinner(mob/living/source, mob/living/target, radius) return (source != target && target.health < target.maxHealth) /datum/ai_behavior/find_hunt_target/raptor_victim + action_cooldown = 30 SECONDS /datum/ai_behavior/find_hunt_target/raptor_victim/valid_dinner(mob/living/source, mob/living/target, radius) if(target.ai_controller?.blackboard[BB_RAPTOR_TROUBLE_MAKER]) @@ -30,6 +32,7 @@ return ..() /datum/ai_behavior/find_hunt_target/raptor_trough + action_cooldown = 7.5 SECONDS /datum/ai_behavior/find_hunt_target/raptor_trough/valid_dinner(mob/living/source, atom/movable/trough, radius) return !!(locate(/obj/item/stack/ore) in trough.contents) From 56a9de5a692bd414e13287b9811af011748c06d0 Mon Sep 17 00:00:00 2001 From: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:04:04 +0530 Subject: [PATCH 12/55] [NO GBP] Inducers can be inserted into storage objects again (#88071) ## About The Pull Request - Fixes #88063 ## Changelog :cl: fix: inducers can be inserted into storage objects again /:cl: --- code/game/objects/items/inducer.dm | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/code/game/objects/items/inducer.dm b/code/game/objects/items/inducer.dm index ea942c4f663b6..0e430589981e4 100644 --- a/code/game/objects/items/inducer.dm +++ b/code/game/objects/items/inducer.dm @@ -100,14 +100,15 @@ /obj/item/inducer/item_interaction(mob/living/user, obj/item/tool, list/modifiers) . = NONE + if(user.combat_mode || !istype(tool) || tool.flags_1 & HOLOGRAM_1 || tool.item_flags & ABSTRACT) return ITEM_INTERACT_SKIP_TO_ATTACK - if(!opened) - balloon_alert(user, "open first!") - return ITEM_INTERACT_FAILURE - if(istype(tool, /obj/item/stock_parts/power_store)) + if(!opened) + balloon_alert(user, "open first!") + return ITEM_INTERACT_FAILURE + if(!QDELETED(powerdevice)) balloon_alert(user, "cell already installed!") return ITEM_INTERACT_FAILURE @@ -119,7 +120,7 @@ powerdevice = tool return ITEM_INTERACT_SUCCESS - if(istype(tool, /obj/item/stack/sheet/mineral/plasma) && !QDELETED(powerdevice)) + else if(istype(tool, /obj/item/stack/sheet/mineral/plasma) && !QDELETED(powerdevice)) if(!powerdevice.used_charge()) balloon_alert(user, "fully charged!") return ITEM_INTERACT_FAILURE @@ -132,6 +133,10 @@ /obj/item/inducer/interact_with_atom(atom/movable/interacting_with, mob/living/user, list/modifiers) . = NONE + + if(HAS_TRAIT(interacting_with, TRAIT_COMBAT_MODE_SKIP_INTERACTION)) + return + if(user.combat_mode || !istype(interacting_with) || interacting_with.flags_1 & HOLOGRAM_1) return ITEM_INTERACT_SKIP_TO_ATTACK @@ -176,7 +181,7 @@ break //transfer of charge - var/transferred = min(our_cell.charge, target_cell.used_charge(), (target_cell.rating_base * target_cell.rating * power_transfer_multiplier)) + var/transferred = min(our_cell.charge, target_cell.used_charge(), target_cell.rating_base * target_cell.rating * power_transfer_multiplier) if(!transferred) break our_cell.use(target_cell.give(transferred)) From 7e27663517731fe8f3d955477b1a97ace5a6ff83 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:34:27 +0000 Subject: [PATCH 13/55] Automatic changelog for PR #88071 [ci skip] --- html/changelogs/AutoChangeLog-pr-88071.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88071.yml diff --git a/html/changelogs/AutoChangeLog-pr-88071.yml b/html/changelogs/AutoChangeLog-pr-88071.yml new file mode 100644 index 0000000000000..a6556ae654e60 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88071.yml @@ -0,0 +1,4 @@ +author: "SyncIt21" +delete-after: True +changes: + - bugfix: "inducers can be inserted into storage objects again" \ No newline at end of file From 1fe4f77dc534a17293485224e10c0870b25a6621 Mon Sep 17 00:00:00 2001 From: EOBGames <58124831+EOBGames@users.noreply.github.com> Date: Fri, 22 Nov 2024 19:48:29 +0000 Subject: [PATCH 14/55] Shipping and Receiving: More Containers, Revamped Containers (#87995) --- .../effects/spawners/random/structure.dm | 111 +++++++++++- code/game/objects/structures/containers.dm | 165 ++++++++++++++++-- icons/obj/fluff/containers.dmi | Bin 7751 -> 55866 bytes 3 files changed, 257 insertions(+), 19 deletions(-) diff --git a/code/game/objects/effects/spawners/random/structure.dm b/code/game/objects/effects/spawners/random/structure.dm index 289a2aba27600..a83b88c3a9863 100644 --- a/code/game/objects/effects/spawners/random/structure.dm +++ b/code/game/objects/effects/spawners/random/structure.dm @@ -260,22 +260,117 @@ ) /obj/effect/spawner/random/structure/shipping_container - name = "shipping container spawner" + name = "random shipping container spawner" icon = 'icons/obj/fluff/containers.dmi' icon_state = "random_container" loot = list( + /obj/structure/shipping_container = 3, + /obj/structure/shipping_container/amsco = 3, + /obj/structure/shipping_container/blue = 3, /obj/structure/shipping_container/conarex = 3, /obj/structure/shipping_container/deforest = 3, - /obj/structure/shipping_container/kahraman = 3, - /obj/structure/shipping_container/kahraman/alt = 3, + /obj/structure/shipping_container/defaced = 3, + /obj/structure/shipping_container/great_northern = 3, + /obj/structure/shipping_container/green = 3, + /obj/structure/shipping_container/kahraman = 2, + /obj/structure/shipping_container/kahraman/alt = 1, /obj/structure/shipping_container/kosmologistika = 3, - /obj/structure/shipping_container/interdyne = 3, + /obj/structure/shipping_container/magenta = 3, /obj/structure/shipping_container/nakamura = 3, /obj/structure/shipping_container/nanotrasen = 3, - /obj/structure/shipping_container/nthi = 3, - /obj/structure/shipping_container/vitezstvi = 3, + /obj/structure/shipping_container/ntfid = 2, + /obj/structure/shipping_container/ntfid/defaced = 1, + /obj/structure/shipping_container/nthi = 1, + /obj/structure/shipping_container/nthi/minor = 1, + /obj/structure/shipping_container/nthi/precious = 1, + /obj/structure/shipping_container/orange = 3, + /obj/structure/shipping_container/purple = 3, + /obj/structure/shipping_container/red = 3, + /obj/structure/shipping_container/sunda = 3, + /obj/structure/shipping_container/vitezstvi = 2, + /obj/structure/shipping_container/vitezstvi/flags = 1, + /obj/structure/shipping_container/yellow = 3, + /obj/structure/shipping_container/biosustain = 3, /obj/structure/shipping_container/cybersun = 2, - /obj/structure/shipping_container/donk_co = 2, - /obj/structure/shipping_container/gorlex = 1, + /obj/structure/shipping_container/cybersun/defaced = 1, + /obj/structure/shipping_container/donk_co = 3, + /obj/structure/shipping_container/exagon = 1, + /obj/structure/shipping_container/exagon/minor = 1, + /obj/structure/shipping_container/exagon/precious = 1, + /obj/structure/shipping_container/gorlex = 2, + /obj/structure/shipping_container/gorlex/red = 1, + /obj/structure/shipping_container/interdyne = 3, + /obj/structure/shipping_container/oms = 3, + /obj/structure/shipping_container/tiger_coop = 2, + /obj/structure/shipping_container/tiger_coop = 1, + ) + +/obj/effect/spawner/random/structure/shipping_container/blank + name = "random blank shipping container spawner" + loot = list( + /obj/structure/shipping_container = 3, + /obj/structure/shipping_container/blue = 3, + /obj/structure/shipping_container/green = 3, + /obj/structure/shipping_container/magenta = 3, + /obj/structure/shipping_container/orange = 3, + /obj/structure/shipping_container/purple = 3, + /obj/structure/shipping_container/red = 3, + /obj/structure/shipping_container/yellow = 3, + ) + +/obj/effect/spawner/random/structure/syndicate //syndicate containers only + name = "random syndicate shipping container spawner" + loot = list( + /obj/structure/shipping_container/biosustain = 3, + /obj/structure/shipping_container/cybersun = 2, + /obj/structure/shipping_container/cybersun/defaced = 1, + /obj/structure/shipping_container/donk_co = 3, + /obj/structure/shipping_container/exagon = 1, + /obj/structure/shipping_container/exagon/minor = 1, + /obj/structure/shipping_container/exagon/precious = 1, + /obj/structure/shipping_container/gorlex = 2, /obj/structure/shipping_container/gorlex/red = 1, + /obj/structure/shipping_container/interdyne = 3, + /obj/structure/shipping_container/oms = 3, + /obj/structure/shipping_container/tiger_coop = 2, + /obj/structure/shipping_container/tiger_coop = 1, + ) + +/obj/effect/spawner/random/structure/shipping_container/station_appropriate //places extra emphasis on NT containers, excludes syndicate companies (except Donk. Co.) entirely + name = "station-appropriate shipping container spawner" + loot = list( + /obj/structure/shipping_container/nanotrasen = 5, + /obj/structure/shipping_container/nthi = 1, + /obj/structure/shipping_container/nthi/minor = 1, + /obj/structure/shipping_container/nthi/precious = 1, + /obj/structure/shipping_container/ntfid = 3, + /obj/structure/shipping_container/nakamura = 2, + /obj/structure/shipping_container/deforest = 2, + /obj/structure/shipping_container/kosmologistika = 2, + /obj/structure/shipping_container/donk_co = 2, + /obj/structure/shipping_container/amsco = 1, + /obj/structure/shipping_container/conarex = 1, + /obj/structure/shipping_container/kahraman = 1, + /obj/structure/shipping_container/kahraman/alt = 1, + /obj/structure/shipping_container/sunda = 1, + /obj/structure/shipping_container/vitezstvi = 1, + ) + +/obj/effect/spawner/random/structure/shipping_container/reefer //reefers only + name = "random reefer container spawner" + loot = list( + /obj/structure/shipping_container/reefer = 3, + /obj/structure/shipping_container/reefer/biosustain = 3, + /obj/structure/shipping_container/reefer/deforest = 3, + /obj/structure/shipping_container/reefer/interdyne = 3, + ) + +/obj/effect/spawner/random/structure/shipping_container/gas //gas cisterns only + name = "random gas cistern spawner" + loot = list( + /obj/structure/shipping_container/gas = 3, + /obj/structure/shipping_container/gas/apda = 3, + /obj/structure/shipping_container/gas/apda/hydrogen = 3, + /obj/structure/shipping_container/gas/exagon = 3, + /obj/structure/shipping_container/gas/nthi = 3, ) diff --git a/code/game/objects/structures/containers.dm b/code/game/objects/structures/containers.dm index bf56f7850f82d..965d74ae94d09 100644 --- a/code/game/objects/structures/containers.dm +++ b/code/game/objects/structures/containers.dm @@ -2,7 +2,7 @@ name = "shipping container" desc = "A standard-measure shipping container for bulk transport of goods. This one is blank, offering no clue as to its contents." icon = 'icons/obj/fluff/containers.dmi' - icon_state = "container_blank" + icon_state = "blank" max_integrity = 1000 bound_width = 96 bound_height = 32 @@ -16,19 +16,40 @@ AddComponent(/datum/component/seethrough, SEE_THROUGH_MAP_SHIPPING_CONTAINER) +/obj/structure/shipping_container/amsco + name = "\improper AMSCO shipping container" + desc = "A standard-measure shipping container for bulk transport of goods. This one is from Amundsen-Scott, and so is probably carrying prospecting gear." + icon_state = "amsco" + +/obj/structure/shipping_container/blue + icon_state = "blue" + /obj/structure/shipping_container/conarex name = "\improper Conarex Aeronautics shipping container" desc = "A standard-measure shipping container for bulk transport of goods. This one is from Conarex Aeronautics, and is probably carrying spacecraft parts (or a bribery scandal) as a result." icon_state = "conarex" +/obj/structure/shipping_container/defaced + name = "defaced shipping container" + desc = "A standard-measure shipping container for bulk transport of goods. This one's covered in tasteful graffiti." + icon_state = "defaced" + /obj/structure/shipping_container/deforest - name = "\improper DeForest Medical Corp. shipping container" - desc = "A standard-measure shipping container for bulk transport of goods. This one is from DeForest, and so is probably carrying medical supplies." + name = "\improper Nanotrasen-DeForest shipping container" + desc = "A standard-measure shipping container for bulk transport of goods. This one is from Nanotrasen-DeForest, and so is probably carrying medical supplies." icon_state = "deforest" +/obj/structure/shipping_container/great_northern + name = "\improper Great Northern shipping container" + desc = "A standard-measure shipping container for bulk transport of goods. This one is from Great Northern, and is probably carrying farming equipment." + icon_state = "great_northern" + +/obj/structure/shipping_container/green + icon_state = "green" + /obj/structure/shipping_container/kahraman name = "\improper Kahraman Heavy Industry shipping container" - desc = "A standard-measure shipping container for bulk transport of goods. This one is from Kahraman, and is reinforced for carrying ore." + desc = "A standard-measure shipping container for bulk transport of goods. This one is from Kahraman, and is reinforced for carrying mining equipment." icon_state = "kahraman" /obj/structure/shipping_container/kahraman/alt @@ -36,13 +57,11 @@ /obj/structure/shipping_container/kosmologistika name = "\improper Kosmologistika shipping container" - desc = "A standard-measure shipping container for bulk transport of goods. This one is from Kosmologistika, the logistics company owned and operated by the SSC." + desc = "A standard-measure shipping container for bulk transport of goods. This one is from Kosmologistika, the state logistics company owned and operated by the SSC." icon_state = "kosmologistika" -/obj/structure/shipping_container/interdyne - name = "\improper Interdyne shipping container" - desc = "A standard-measure shipping container for bulk transport of goods. This one is from Interdyne, a private pharmaceutical company. Probably carrying medical or research supplies, probably." - icon_state = "interdyne" +/obj/structure/shipping_container/magenta + icon_state = "magenta" /obj/structure/shipping_container/nakamura name = "\improper Nakamura Engineering shipping container" @@ -54,27 +73,86 @@ desc = "A standard-measure shipping container for bulk transport of goods. This one prominently features Nanotrasen's logo, and so presumably could be carrying anything." icon_state = "nanotrasen" +/obj/structure/shipping_container/ntfid + name = "\improper Nanotrasen Futures and Innovation shipping container" + desc = "A standard-measure shipping container for bulk transport of goods. This one is from NTFID: Nanotrasen's research and development subdivision." + icon_state = "ntfid" + +/obj/structure/shipping_container/ntfid/defaced + desc = "A standard-measure shipping container for bulk transport of goods. Someone clearly has a bone to pick with NTFID." + icon_state = "ntfid_defaced" + /obj/structure/shipping_container/nthi name = "\improper Nanotrasen Heavy Industries shipping container" - desc = "A standard-measure shipping container for bulk transport of goods. This one is from NTHI: Nanotrasen's mining and refining subdivision." + desc = "A standard-measure shipping container for bulk transport of common metals and minerals. This one is from NTHI: Nanotrasen's mining and refining subdivision." icon_state = "nthi" +/obj/structure/shipping_container/nthi/minor + desc = "A standard-measure shipping container for bulk transport of rare metals and minerals. This one is from NTHI: Nanotrasen's mining and refining subdivision." + icon_state = "nthi_minor" + +/obj/structure/shipping_container/nthi/precious + desc = "A standard-measure shipping container for bulk transport of precious metals and minerals. This one is from NTHI: Nanotrasen's mining and refining subdivision." + icon_state = "nthi_precious" + +/obj/structure/shipping_container/orange + icon_state = "orange" + +/obj/structure/shipping_container/purple + icon_state = "purple" + +/obj/structure/shipping_container/red + icon_state = "red" + +/obj/structure/shipping_container/sunda + name = "\improper Sunda Galaksi shipping container" + desc = "A standard-measure shipping container for bulk transport of goods. This one is from Sunda Galaksi, and could be carrying just about anything." + icon_state = "sunda" + /obj/structure/shipping_container/vitezstvi name = "\improper Vítězství Arms shipping container" desc = "A standard-measure shipping container for bulk transport of goods. This one is from Vítězství Arms, proudly proclaiming that Vítězství weapons mean victory." icon_state = "vitezstvi" +/obj/structure/shipping_container/vitezstvi/flags + icon_state = "vitezstvi_flags" + +/obj/structure/shipping_container/yellow + icon_state = "yellow" + //Syndies +/obj/structure/shipping_container/biosustain + name = "\improper Biosustain shipping container" + desc = "A standard-measure shipping container for bulk transport of goods. This one is from Biosustain, and so it's probably carrying seeds or farming equipment." + icon_state = "biosustain" + /obj/structure/shipping_container/cybersun name = "\improper Cybersun Industries shipping container" desc = "A standard-measure shipping container for bulk transport of goods. This one prominently features Cybersun's logo, and so presumably could be carrying almost anything." icon_state = "cybersun" +/obj/structure/shipping_container/cybersun/defaced + desc = "A standard-measure shipping container for bulk transport of goods. This one originally featured Cybersun's logo, before it was painted over by an enterprising artist." + icon_state = "cybersun_defaced" + /obj/structure/shipping_container/donk_co name = "\improper Donk Co. shipping container" - desc = "A standard-measure shipping container for bulk transport of goods. This one is from Donk Co. and so could be carrying just about anything- although it's probably Donk Pockets." + desc = "A standard-measure shipping container for bulk transport of goods. This one is from Donk Co. and so could be carrying just about anything- although it's probably just Donk Pockets." icon_state = "donk_co" +/obj/structure/shipping_container/exagon + name = "\improper Exagon-Ichikawa shipping container" + desc = "A standard-measure shipping container for bulk transport of common metals and minerals. This one is from Exagon-Ichikawa, Cybersun Industries' mining and refining subdivision." + icon_state = "exagon" + +/obj/structure/shipping_container/exagon/minor + desc = "A standard-measure shipping container for bulk transport of rare metals and minerals. This one is from Exagon-Ichikawa, Cybersun Industries' mining and refining subdivision." + icon_state = "exagon_minor" + +/obj/structure/shipping_container/exagon/precious + desc = "A standard-measure shipping container for bulk transport of precious metals and minerals. This one is from Exagon-Ichikawa, Cybersun Industries' mining and refining subdivision." + icon_state = "exagon_precious" + /obj/structure/shipping_container/gorlex name = "\improper Gorlex Securities shipping container" desc = "A standard-measure shipping container for bulk transport of goods. This one is from Gorlex Securities, and is probably carrying their primary export: war crimes." @@ -82,3 +160,68 @@ /obj/structure/shipping_container/gorlex/red icon_state = "gorlex_red" + +/obj/structure/shipping_container/interdyne + name = "\improper Interdyne shipping container" + desc = "A standard-measure shipping container for bulk transport of goods. This one is from Interdyne, a private pharmaceutical company. Probably carrying medical or research supplies, probably." + icon_state = "interdyne" + +/obj/structure/shipping_container/oms + name = "\improper OMS shipping container" + desc = "A standard-measure shipping container for bulk transport of goods. This one is from Cybersun's medical subdivision OMS (Osaka Medical Systems), and is probably carrying medical cybernetics or somesuch." + icon_state = "oms" + +/obj/structure/shipping_container/tiger_coop + name = "suspicious shipping container" + desc = "A standard-measure shipping container for bulk transport of goods. This previously blank container has been spray-painted with the insignia of the Tiger Cooperative, meaning whatever's inside is probably dangerous." + icon_state = "tiger_coop" + +/obj/structure/shipping_container/tiger_coop/text + icon_state = "tiger_coop_text" + +// REEFER CONTAINERS (REFRIGERATED) +/obj/structure/shipping_container/reefer + name = "reefer shipping container" + desc = "A standard-measure reefer shipping container for bulk transport of refrigerated goods. This one is blank, offering no clue as to its contents." + icon_state = "blank_reefer" + +/obj/structure/shipping_container/reefer/deforest + name = "\improper Nanotrasen-DeForest reefer shipping container" + desc = "A standard-measure reefer shipping container for bulk transport of refrigerated goods. This one is from Nanotrasen-DeForest, and is probably carrying temperature sensitive biological material." + icon_state = "deforest_reefer" + +/obj/structure/shipping_container/reefer/biosustain + name = "\improper Biosustain reefer shipping container" + desc = "A standard-measure reefer shipping container for bulk transport of refrigerated goods. This one is from Biosustain, and so it's probably carrying GMOs or agrichemicals." + icon_state = "biosustain_reefer" + +/obj/structure/shipping_container/reefer/interdyne + name = "\improper Interdyne reefer shipping container" + desc = "A standard-measure reefer shipping container for bulk transport of refrigerated goods. This one is from Interdyne, a private pharmaceutical company, and is probably carrying organs or blood, maybe both." + icon_state = "interdyne_reefer" + +// GAS TANK +/obj/structure/shipping_container/gas + name = "bulk gas tank" + desc = "A standard-measure gas tank for bulk transport of gases. This one is rather irresponsibly blank, offering no clue as to its contents." + icon_state = "blank_gas" + +/obj/structure/shipping_container/gas/apda + name = "\improper APdA S.p.A. bulk helium tank" + desc = "A standard-measure gas tank for bulk transport of gases. This one is from Associato Petrochimico dell'Adriatico, containing their second most important export: helium-3 for fuel use." + icon_state = "apda_gas_helium" + +/obj/structure/shipping_container/gas/apda/hydrogen + name = "\improper APdA S.p.A. bulk hydrogen tank" + desc = "A standard-measure gas tank for bulk transport of gases. This one is from Associato Petrochimico dell'Adriatico, containing their most important export: hydrogen for fuel use." + icon_state = "apda_gas_hydrogen" + +/obj/structure/shipping_container/gas/nthi + name = "\improper NTHI bulk plasma tank" + desc = "A standard-measure gas tank for bulk transport of gases. This one is from NTHI, Nanotrasen's mining and refining subdivision, and contains high-grade gaseous plasma from the Spinward Sector." + icon_state = "nthi_gas_plasma" + +/obj/structure/shipping_container/gas/exagon + name = "\improper Exagon-Ichikawa bulk plasma tank" + desc = "A standard-measure gas tank for bulk transport of gases. This one is from Exagon-Ichikawa, Cybersun Industries' mining and refining subdivision, and contains gaseous plasma most likely sourced from Mars." + icon_state = "exagon_gas_plasma" diff --git a/icons/obj/fluff/containers.dmi b/icons/obj/fluff/containers.dmi index 8aed1ffbb9a99bb109e4ddbcc7c64cbdb4f5a015..0e966c9f794fb1ca14b4c0d66618ef71114db6e5 100644 GIT binary patch literal 55866 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 literal 7751 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 Date: Fri, 22 Nov 2024 20:48:39 +0100 Subject: [PATCH 15/55] Fixes atmos goggles not showing the temperature of turfs (#88075) Co-authored-by: Xander3359 <66163761+Xander3359@users.noreply.github.com> --- .../modules/clothing/glasses/engine_goggles.dm | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/code/modules/clothing/glasses/engine_goggles.dm b/code/modules/clothing/glasses/engine_goggles.dm index 064a24cf5c779..4d7951a62f62a 100644 --- a/code/modules/clothing/glasses/engine_goggles.dm +++ b/code/modules/clothing/glasses/engine_goggles.dm @@ -183,26 +183,26 @@ continue var/datum/gas_mixture/environment = open.return_air() var/temp = round(environment.return_temperature()) - var/image/pic = image('icons/turf/overlays.dmi', open, "greyOverlay", ABOVE_ALL_MOB_LAYER) + var/image/turf_overlay = image('icons/turf/overlays.dmi', open, "greyOverlay", ABOVE_OPEN_TURF_LAYER) // Lower than TEMP_SHADE_CYAN should be deep blue switch(temp) if(-INFINITY to TEMP_SHADE_CYAN) - pic.color = COLOR_STRONG_BLUE + turf_overlay.color = COLOR_STRONG_BLUE // Between TEMP_SHADE_CYAN and TEMP_SHADE_GREEN if(TEMP_SHADE_CYAN to TEMP_SHADE_GREEN) - pic.color = BlendRGB(COLOR_DARK_CYAN, COLOR_LIME, max(round((temp - TEMP_SHADE_CYAN)/(TEMP_SHADE_GREEN - TEMP_SHADE_CYAN), 0.01), 0)) + turf_overlay.color = BlendRGB(COLOR_DARK_CYAN, COLOR_LIME, max(round((temp - TEMP_SHADE_CYAN)/(TEMP_SHADE_GREEN - TEMP_SHADE_CYAN), 0.01), 0)) // Between TEMP_SHADE_GREEN and TEMP_SHADE_YELLOW if(TEMP_SHADE_GREEN to TEMP_SHADE_YELLOW) - pic.color = BlendRGB(COLOR_LIME, COLOR_YELLOW, clamp(round((temp-TEMP_SHADE_GREEN)/(TEMP_SHADE_YELLOW - TEMP_SHADE_GREEN), 0.01), 0, 1)) + turf_overlay.color = BlendRGB(COLOR_LIME, COLOR_YELLOW, clamp(round((temp-TEMP_SHADE_GREEN)/(TEMP_SHADE_YELLOW - TEMP_SHADE_GREEN), 0.01), 0, 1)) // Between TEMP_SHADE_YELLOW and TEMP_SHADE_RED if(TEMP_SHADE_YELLOW to TEMP_SHADE_RED) - pic.color = BlendRGB(COLOR_YELLOW, COLOR_RED, clamp(round((temp-TEMP_SHADE_YELLOW)/(TEMP_SHADE_RED - TEMP_SHADE_YELLOW), 0.01), 0, 1)) + turf_overlay.color = BlendRGB(COLOR_YELLOW, COLOR_RED, clamp(round((temp-TEMP_SHADE_YELLOW)/(TEMP_SHADE_RED - TEMP_SHADE_YELLOW), 0.01), 0, 1)) // Over TEMP_SHADE_RED should be red if(TEMP_SHADE_RED to INFINITY) - pic.color = COLOR_RED - pic.mouse_opacity = MOUSE_OPACITY_TRANSPARENT - pic.alpha = 200 - flick_overlay_global(pic, list(viewer.client), duration) + turf_overlay.color = COLOR_RED + turf_overlay.mouse_opacity = MOUSE_OPACITY_TRANSPARENT + turf_overlay.alpha = 200 + flick_overlay_global(turf_overlay, list(viewer.client), duration) #undef MODE_NONE From 6f76ef5381d0568b53684cf49e7a20b322860a78 Mon Sep 17 00:00:00 2001 From: Thunder12345 Date: Fri, 22 Nov 2024 19:48:49 +0000 Subject: [PATCH 16/55] Removes some duplicate crossbow defines (#88076) --- code/__DEFINES/projectiles.dm | 4 ---- code/modules/projectiles/boxes_magazines/internal/rifle.dm | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/code/__DEFINES/projectiles.dm b/code/__DEFINES/projectiles.dm index 28b7b6f3d1be5..f93faab62de90 100644 --- a/code/__DEFINES/projectiles.dm +++ b/code/__DEFINES/projectiles.dm @@ -56,10 +56,6 @@ #define CALIBER_HARPOON "harpoon" /// The caliber used by the rebar crossbow. #define CALIBER_REBAR "sharpened rod" -/// The caliber used by the rebar crossbow when forced to hold 2 rods. -#define CALIBER_REBAR_FORCED "sharpened rod" -/// The caliber used by the syndicate rebar crossbow. -#define CALIBER_REBAR_SYNDIE "sharpened rod" /// The caliber used by the meat hook. #define CALIBER_HOOK "hook" /// The caliber used by the changeling tentacle mutation. diff --git a/code/modules/projectiles/boxes_magazines/internal/rifle.dm b/code/modules/projectiles/boxes_magazines/internal/rifle.dm index b1f761831ee62..fb44ee41d9d15 100644 --- a/code/modules/projectiles/boxes_magazines/internal/rifle.dm +++ b/code/modules/projectiles/boxes_magazines/internal/rifle.dm @@ -54,11 +54,11 @@ /obj/item/ammo_box/magazine/internal/boltaction/rebarxbow/force name = "two round magazine" max_ammo = 2 - caliber = CALIBER_REBAR_FORCED + caliber = CALIBER_REBAR ammo_type = /obj/item/ammo_casing/rebar /obj/item/ammo_box/magazine/internal/boltaction/rebarxbow/syndie max_ammo = 3 - caliber = CALIBER_REBAR_SYNDIE + caliber = CALIBER_REBAR ammo_type = /obj/item/ammo_casing/rebar/syndie From aea1e34b1908230065a6c7f90b7dda7dbdd92abe Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 22 Nov 2024 19:48:51 +0000 Subject: [PATCH 17/55] Automatic changelog for PR #87995 [ci skip] --- html/changelogs/AutoChangeLog-pr-87995.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-87995.yml diff --git a/html/changelogs/AutoChangeLog-pr-87995.yml b/html/changelogs/AutoChangeLog-pr-87995.yml new file mode 100644 index 0000000000000..5b009dd4af0ba --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-87995.yml @@ -0,0 +1,4 @@ +author: "EOBGames" +delete-after: True +changes: + - rscadd: "A new series of shipping containers have been added: keep an eye out for them at your local cargo terminus." \ No newline at end of file From 5b330349ac960164f866959071f3eb900629eb09 Mon Sep 17 00:00:00 2001 From: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> Date: Sat, 23 Nov 2024 01:19:29 +0530 Subject: [PATCH 18/55] [NO GBP] Fixes grown foods getting deleted sometimes (#88077) --- code/modules/hydroponics/seeds.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm index e0e88f7c402d9..22434788e999f 100644 --- a/code/modules/hydroponics/seeds.dm +++ b/code/modules/hydroponics/seeds.dm @@ -280,7 +280,7 @@ //Handles the juicing trait, swaps nutriment and vitamins for that species various juices if they exist. Mutually exclusive with distilling. if(get_gene(/datum/plant_gene/trait/juicing) && grown_edible.juice_typepath) - grown_edible.juice() + grown_edible.juice(juicer = FALSE) //we pass FALSE & not null because Byond default args will subtitute it with the default value else if(get_gene(/datum/plant_gene/trait/brewing)) grown_edible.ferment() From 82a4c47b9cda1137436e30ab79afd1dedd09fa9e Mon Sep 17 00:00:00 2001 From: Ghom <42542238+Ghommie@users.noreply.github.com> Date: Fri, 22 Nov 2024 20:49:38 +0100 Subject: [PATCH 19/55] Fixes ink spit not respecting its cooldown. (#88079) --- code/game/machinery/dna_infuser/infuser_actions.dm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/game/machinery/dna_infuser/infuser_actions.dm b/code/game/machinery/dna_infuser/infuser_actions.dm index 1b55059bb9899..48f92b9ae6850 100644 --- a/code/game/machinery/dna_infuser/infuser_actions.dm +++ b/code/game/machinery/dna_infuser/infuser_actions.dm @@ -41,6 +41,8 @@ if(!LAZYACCESS(params2list(params), RIGHT_CLICK)) return . = ..() + if(!.) + return var/modifiers = params2list(params) caller.visible_message( From b2ce17486eee85d5a4ae0732503d66bb3175cd9f Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 22 Nov 2024 19:49:47 +0000 Subject: [PATCH 20/55] Automatic changelog for PR #88075 [ci skip] --- html/changelogs/AutoChangeLog-pr-88075.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88075.yml diff --git a/html/changelogs/AutoChangeLog-pr-88075.yml b/html/changelogs/AutoChangeLog-pr-88075.yml new file mode 100644 index 0000000000000..a24731067e388 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88075.yml @@ -0,0 +1,4 @@ +author: "EnterTheJake" +delete-after: True +changes: + - bugfix: "fixes atmos goggles not showing the temperature of turfs." \ No newline at end of file From afd98acc8f5998c387b13ffd679465bddd2a10a1 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 22 Nov 2024 19:50:34 +0000 Subject: [PATCH 21/55] Automatic changelog for PR #88077 [ci skip] --- html/changelogs/AutoChangeLog-pr-88077.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88077.yml diff --git a/html/changelogs/AutoChangeLog-pr-88077.yml b/html/changelogs/AutoChangeLog-pr-88077.yml new file mode 100644 index 0000000000000..794fa0dcdc423 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88077.yml @@ -0,0 +1,4 @@ +author: "SyncIt21" +delete-after: True +changes: + - bugfix: "fixes grown foods(like pineapples) getting deleted sometimes inside crates and stuff" \ No newline at end of file From 6dee189bc1475804392151e616792d4a92b2664d Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 22 Nov 2024 19:50:46 +0000 Subject: [PATCH 22/55] Automatic changelog for PR #88079 [ci skip] --- html/changelogs/AutoChangeLog-pr-88079.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88079.yml diff --git a/html/changelogs/AutoChangeLog-pr-88079.yml b/html/changelogs/AutoChangeLog-pr-88079.yml new file mode 100644 index 0000000000000..b4b00b3cda342 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88079.yml @@ -0,0 +1,4 @@ +author: "Ghommie" +delete-after: True +changes: + - bugfix: "Fixes ink spit not respecting its 21 seconds cooldown." \ No newline at end of file From 67325ff3e0ecef6b21fd80d470929a0ea3a009a0 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Fri, 22 Nov 2024 23:37:18 +0300 Subject: [PATCH 23/55] [NO GBP] Armor now decreases the chances of getting your eye blown out by a rogue bullet (#88082) ## About The Pull Request Chance of getting an eye wound from being hit by a projectile now scales inversely with your head armor. Meant to do this originally but forgot, so here we go. ## Why It's Good For The Game You'd expect that a bulletproof helmet will prevent your eyes from being popped like balloons, especially if you don't take any damage from the hit. ## Changelog :cl: balance: Armor now decreases the chances of getting your eye blown out by a rogue bullet /:cl: --- code/modules/surgery/organs/internal/eyes/_eyes.dm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/code/modules/surgery/organs/internal/eyes/_eyes.dm b/code/modules/surgery/organs/internal/eyes/_eyes.dm index 34ce46bc88b87..fd6beabcb0d1f 100644 --- a/code/modules/surgery/organs/internal/eyes/_eyes.dm +++ b/code/modules/surgery/organs/internal/eyes/_eyes.dm @@ -107,13 +107,18 @@ organ_owner.update_sight() UnregisterSignal(organ_owner, COMSIG_ATOM_BULLET_ACT) -/obj/item/organ/eyes/proc/on_bullet_act(datum/source, obj/projectile/proj, def_zone) +/obj/item/organ/eyes/proc/on_bullet_act(mob/living/carbon/source, obj/projectile/proj, def_zone) SIGNAL_HANDLER // Once-a-dozen-rounds level of rare if (def_zone != BODY_ZONE_HEAD || !prob(proj.damage * 0.1) || !(proj.damage_type == BRUTE || proj.damage_type == BURN)) return + var/blocked = source.check_projectile_armor(def_zone, proj, is_silent = TRUE) + if (blocked && source.is_eyes_covered()) + if (!proj.armour_penetration || prob(blocked - proj.armour_penetration)) + return + var/valid_sides = list() if (!(scarring & RIGHT_EYE_SCAR)) valid_sides += RIGHT_EYE_SCAR From f4549cc666c0e36f06c110c77d40b18d41971c99 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 22 Nov 2024 20:45:16 +0000 Subject: [PATCH 24/55] Automatic changelog for PR #88082 [ci skip] --- html/changelogs/AutoChangeLog-pr-88082.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88082.yml diff --git a/html/changelogs/AutoChangeLog-pr-88082.yml b/html/changelogs/AutoChangeLog-pr-88082.yml new file mode 100644 index 0000000000000..df86d8c6e35c3 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88082.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - balance: "Armor now decreases the chances of getting your eye blown out by a rogue bullet" \ No newline at end of file From 185f96efb02ccb66db8c8d7b41cd082b315060dc Mon Sep 17 00:00:00 2001 From: Fikou <23585223+Fikou@users.noreply.github.com> Date: Fri, 22 Nov 2024 21:56:25 +0100 Subject: [PATCH 25/55] changes default prefs to deadmin you as antagonist/head/sec/silicon, stops auto deadmin on localhost (#88028) --- code/__DEFINES/preferences.dm | 3 ++- code/modules/admin/permissionedit.dm | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/code/__DEFINES/preferences.dm b/code/__DEFINES/preferences.dm index 5fb9d9447bbc0..28587a77e6a95 100644 --- a/code/__DEFINES/preferences.dm +++ b/code/__DEFINES/preferences.dm @@ -17,7 +17,8 @@ #define ADMIN_IGNORE_CULT_GHOST (1<<21) #define SPLIT_ADMIN_TABS (1<<23) -#define TOGGLES_DEFAULT (SOUND_ADMINHELP|MEMBER_PUBLIC|SOUND_PRAYERS) +#define TOGGLES_DEADMIN_DEFAULT (DEADMIN_ANTAGONIST|DEADMIN_POSITION_HEAD|DEADMIN_POSITION_SECURITY|DEADMIN_POSITION_SILICON) +#define TOGGLES_DEFAULT (SOUND_ADMINHELP|MEMBER_PUBLIC|SOUND_PRAYERS|TOGGLES_DEADMIN_DEFAULT) // Legacy chat toggles. // !!! DO NOT ADD ANY NEW ONES HERE !!! diff --git a/code/modules/admin/permissionedit.dm b/code/modules/admin/permissionedit.dm index 296b6fd2dd86f..73987622202e6 100644 --- a/code/modules/admin/permissionedit.dm +++ b/code/modules/admin/permissionedit.dm @@ -300,7 +300,9 @@ ADMIN_VERB(edit_admin_permissions, R_PERMISSIONS, "Permissions Panel", "Edit adm D.deactivate() //after logs so the deadmined admin can see the message. /datum/admins/proc/auto_deadmin() - if (owner.prefs.read_preference(/datum/preference/toggle/bypass_deadmin_in_centcom) && is_centcom_level(owner.mob.z)) + if(owner.is_localhost()) + return FALSE + if(owner.prefs.read_preference(/datum/preference/toggle/bypass_deadmin_in_centcom) && is_centcom_level(owner.mob.z)) return FALSE to_chat(owner, span_interface("You are now a normal player."), confidential = TRUE) From 2d438d6d3ff743e33bdbe16734eaa2370876ac5d Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sat, 23 Nov 2024 00:14:58 +0300 Subject: [PATCH 26/55] Implements final type filtering for spawn commands (#88070) ## About The Pull Request Putting ``*`` at the end of a spawn type string will tell the command to not offer subtypes of that string. For example, running ``laser/cap`` offers you two types: ``GUN_LASER/captain`` and ``GUN_LASER/captain/scattershot``. Adding a start to the end like ``laser/cap*`` would only offer ``GUN_LASER/captain`` without the scattershot subtype, and instantly spawn it without an input window because its the only fitting type. ## Why It's Good For The Game Easier to spawn certain types without having subtypes offered to you --- code/__HELPERS/type_processing.dm | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/code/__HELPERS/type_processing.dm b/code/__HELPERS/type_processing.dm index dfd74f7e3c6bc..e005088c2813f 100644 --- a/code/__HELPERS/type_processing.dm +++ b/code/__HELPERS/type_processing.dm @@ -128,11 +128,29 @@ if(endcheck.len > 1) filter = endcheck[1] end_len = length_char(filter) + var/endtype = (filter[length(filter)] == "*") + if (endtype) + filter = splittext(filter, "*")[1] for(var/key in L) var/value = L[key] - if(findtext("[key]", filter, -end_len) || findtext("[value]", filter, -end_len)) + if (findtext("[key]", filter, -end_len)) + if (endtype) + var/list/split_filter = splittext("[key]", filter) + if (!findtext(split_filter[length(split_filter)], "/")) + matches[key] = value + continue + else + matches[key] = value + continue + + if (findtext("[value]", filter, -end_len)) + if (endtype) + var/list/split_filter = splittext("[value]", filter) + if (findtext(split_filter[length(split_filter)], "/")) + continue matches[key] = value + return matches /proc/return_typenames(type) From 8fde1ee2aa58cac94dcf548173b6d90cbd4750ef Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 22 Nov 2024 21:20:52 +0000 Subject: [PATCH 27/55] Automatic changelog for PR #88028 [ci skip] --- html/changelogs/AutoChangeLog-pr-88028.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88028.yml diff --git a/html/changelogs/AutoChangeLog-pr-88028.yml b/html/changelogs/AutoChangeLog-pr-88028.yml new file mode 100644 index 0000000000000..bd4a9fce50dd4 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88028.yml @@ -0,0 +1,5 @@ +author: "Fikou" +delete-after: True +changes: + - admin: "you no longer get auto deadminned on localhost" + - admin: "default prefs are now to be deadminned as antag/head/sec/silicon" \ No newline at end of file From 775b818fd694c35ca2f0000d8056b9d2b10f8b7d Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Fri, 22 Nov 2024 21:21:41 +0000 Subject: [PATCH 28/55] Automatic changelog for PR #88070 [ci skip] --- html/changelogs/AutoChangeLog-pr-88070.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88070.yml diff --git a/html/changelogs/AutoChangeLog-pr-88070.yml b/html/changelogs/AutoChangeLog-pr-88070.yml new file mode 100644 index 0000000000000..074447b63e9c4 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88070.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - admin: "Implemented final type filtering for spawn commands - putting * at the end of a spawn string will signify that there should be no subtypes offered!" \ No newline at end of file From bb697ae67fb1f41bd342ea4eef27c2a1ffdd3a9a Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 00:26:30 +0000 Subject: [PATCH 29/55] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-87995.yml | 4 ---- html/changelogs/AutoChangeLog-pr-88028.yml | 5 ----- html/changelogs/AutoChangeLog-pr-88070.yml | 4 ---- html/changelogs/AutoChangeLog-pr-88071.yml | 4 ---- html/changelogs/AutoChangeLog-pr-88075.yml | 4 ---- html/changelogs/AutoChangeLog-pr-88077.yml | 4 ---- html/changelogs/AutoChangeLog-pr-88079.yml | 4 ---- html/changelogs/AutoChangeLog-pr-88082.yml | 4 ---- html/changelogs/archive/2024-11.yml | 20 ++++++++++++++++++++ 9 files changed, 20 insertions(+), 33 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-87995.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88028.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88070.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88071.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88075.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88077.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88079.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-88082.yml diff --git a/html/changelogs/AutoChangeLog-pr-87995.yml b/html/changelogs/AutoChangeLog-pr-87995.yml deleted file mode 100644 index 5b009dd4af0ba..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-87995.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "EOBGames" -delete-after: True -changes: - - rscadd: "A new series of shipping containers have been added: keep an eye out for them at your local cargo terminus." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88028.yml b/html/changelogs/AutoChangeLog-pr-88028.yml deleted file mode 100644 index bd4a9fce50dd4..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88028.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Fikou" -delete-after: True -changes: - - admin: "you no longer get auto deadminned on localhost" - - admin: "default prefs are now to be deadminned as antag/head/sec/silicon" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88070.yml b/html/changelogs/AutoChangeLog-pr-88070.yml deleted file mode 100644 index 074447b63e9c4..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88070.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - admin: "Implemented final type filtering for spawn commands - putting * at the end of a spawn string will signify that there should be no subtypes offered!" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88071.yml b/html/changelogs/AutoChangeLog-pr-88071.yml deleted file mode 100644 index a6556ae654e60..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88071.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SyncIt21" -delete-after: True -changes: - - bugfix: "inducers can be inserted into storage objects again" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88075.yml b/html/changelogs/AutoChangeLog-pr-88075.yml deleted file mode 100644 index a24731067e388..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88075.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "EnterTheJake" -delete-after: True -changes: - - bugfix: "fixes atmos goggles not showing the temperature of turfs." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88077.yml b/html/changelogs/AutoChangeLog-pr-88077.yml deleted file mode 100644 index 794fa0dcdc423..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88077.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SyncIt21" -delete-after: True -changes: - - bugfix: "fixes grown foods(like pineapples) getting deleted sometimes inside crates and stuff" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88079.yml b/html/changelogs/AutoChangeLog-pr-88079.yml deleted file mode 100644 index b4b00b3cda342..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88079.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Ghommie" -delete-after: True -changes: - - bugfix: "Fixes ink spit not respecting its 21 seconds cooldown." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-88082.yml b/html/changelogs/AutoChangeLog-pr-88082.yml deleted file mode 100644 index df86d8c6e35c3..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-88082.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SmArtKar" -delete-after: True -changes: - - balance: "Armor now decreases the chances of getting your eye blown out by a rogue bullet" \ No newline at end of file diff --git a/html/changelogs/archive/2024-11.yml b/html/changelogs/archive/2024-11.yml index d88f4911d801b..671fbfc440c68 100644 --- a/html/changelogs/archive/2024-11.yml +++ b/html/changelogs/archive/2024-11.yml @@ -824,3 +824,23 @@ name wallem: - image: Updates slot machine sprites +2024-11-23: + EOBGames: + - rscadd: 'A new series of shipping containers have been added: keep an eye out + for them at your local cargo terminus.' + EnterTheJake: + - bugfix: fixes atmos goggles not showing the temperature of turfs. + Fikou: + - admin: you no longer get auto deadminned on localhost + - admin: default prefs are now to be deadminned as antag/head/sec/silicon + Ghommie: + - bugfix: Fixes ink spit not respecting its 21 seconds cooldown. + SmArtKar: + - balance: Armor now decreases the chances of getting your eye blown out by a rogue + bullet + - admin: Implemented final type filtering for spawn commands - putting * at the + end of a spawn string will signify that there should be no subtypes offered! + SyncIt21: + - bugfix: inducers can be inserted into storage objects again + - bugfix: fixes grown foods(like pineapples) getting deleted sometimes inside crates + and stuff From 53cfcc8f3e963183bbcfe5925472e3d1f6f13adc Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:21:39 +0300 Subject: [PATCH 30/55] Fixes parsnip sabre not receiving a jousting component (#88104) ## About The Pull Request Copypaste oopsie ## Changelog :cl: fix: Fixed parsnip sabre not receiving a jousting component /:cl: --- code/game/objects/items/melee/misc.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm index 7ea8a663caf56..6ef4624f50e2a 100644 --- a/code/game/objects/items/melee/misc.dm +++ b/code/game/objects/items/melee/misc.dm @@ -188,7 +188,7 @@ wound_bonus = 5 bare_wound_bonus = 15 -/obj/item/melee/sabre/Initialize(mapload) +/obj/item/melee/parsnip_sabre/Initialize(mapload) . = ..() AddComponent(/datum/component/jousting) From c853d4d0e1a038229767422404170dc5b3d04dc2 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:22:00 +0300 Subject: [PATCH 31/55] Organ manipulation can be completed/GCd once again (#88099) ## About The Pull Request This causes a ton of runtimes, attack chain issues and uncompleteable surgeries. Also yeah please don't override BYOND procs with var names, OD pragmas where were you when we re-used step... ## Changelog :cl: fix: Organ manipulation can be completed once again and doesn't cause you to be un-melee-able in some cases. /:cl: --- code/modules/surgery/organ_manipulation.dm | 13 +++++++++++-- code/modules/surgery/surgery.dm | 6 +++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/code/modules/surgery/organ_manipulation.dm b/code/modules/surgery/organ_manipulation.dm index 5e6b4bb4b512d..2b5cb38b92f58 100644 --- a/code/modules/surgery/organ_manipulation.dm +++ b/code/modules/surgery/organ_manipulation.dm @@ -17,6 +17,11 @@ . = ..() if(!.) return + // We've completed the surgery + if(status > length(steps)) + remove_fishing_spot() + return + if(!ispath(steps[status], /datum/surgery_step/manipulate_organs)) //The manipulate_organs step either hasn't been reached yet or we're already past it. if(!HAS_TRAIT(target, TRAIT_FISHING_SPOT)) @@ -26,10 +31,14 @@ if(HAS_TRAIT(target, TRAIT_FISHING_SPOT)) return + target.AddComponent(/datum/component/fishing_spot, /datum/fish_source/surgery) /datum/surgery/organ_manipulation/Destroy() - if(HAS_TRAIT(target, TRAIT_FISHING_SPOT) && ispath(steps[status], /datum/surgery_step/manipulate_organs)) + if(QDELETED(target) || !HAS_TRAIT(target, TRAIT_FISHING_SPOT)) + return + // The surgery is not finished yet and we're currently on manipulate organs step + if(status <= length(steps) && ispath(steps[status], /datum/surgery_step/manipulate_organs)) remove_fishing_spot() return ..() @@ -40,7 +49,7 @@ */ /datum/surgery/organ_manipulation/proc/remove_fishing_spot() for(var/datum/surgery/organ_manipulation/manipulation in target.surgeries) - if(ispath(manipulation.steps[manipulation.status], /datum/surgery_step/manipulate_organs)) + if(manipulation != src && ispath(manipulation.steps[manipulation.status], /datum/surgery_step/manipulate_organs)) return qdel(target.GetComponent(/datum/component/fishing_spot)) diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm index 8b869d3e20b80..c8ca5ba8fb517 100644 --- a/code/modules/surgery/surgery.dm +++ b/code/modules/surgery/surgery.dm @@ -119,13 +119,13 @@ if(LAZYACCESS(modifiers, RIGHT_CLICK)) try_to_fail = TRUE - var/datum/surgery_step/step = GLOB.surgery_steps[steps[status]] - if(isnull(step)) + var/datum/surgery_step/surgery_step = GLOB.surgery_steps[steps[status]] + if(isnull(surgery_step)) return FALSE var/obj/item/tool = user.get_active_held_item() if(tool) tool = tool.get_proxy_attacker_for(target, user) - if(step.try_op(user, target, user.zone_selected, tool, src, try_to_fail)) + if(surgery_step.try_op(user, target, user.zone_selected, tool, src, try_to_fail)) return TRUE if(!tool) return FALSE From 6eab389cabfeb6191277b879173f1582f2449888 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:27:11 +0300 Subject: [PATCH 32/55] Fixes full augmentation scan experiment being uncompletable (#88100) ## About The Pull Request someone cooked too hard ## Changelog :cl: fix: Fixed full augmentation scan experiment being uncompletable /:cl: --- code/modules/experisci/experiment/experiments.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/experisci/experiment/experiments.dm b/code/modules/experisci/experiment/experiments.dm index 4b4871e33f67d..b98ae843f7102 100644 --- a/code/modules/experisci/experiment/experiments.dm +++ b/code/modules/experisci/experiment/experiments.dm @@ -452,7 +452,7 @@ return if (isandroid(check)) return TRUE - if (check.organs < 6 || check.bodyparts < 6) + if (length(check.organs) < 6 || length(check.bodyparts) < 6) return FALSE var/static/list/augmented_organ_slots = list( From 46d27998de75be0d532d56dd1e8aab0b10d1d436 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 10:27:50 +0000 Subject: [PATCH 33/55] Automatic changelog for PR #88104 [ci skip] --- html/changelogs/AutoChangeLog-pr-88104.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88104.yml diff --git a/html/changelogs/AutoChangeLog-pr-88104.yml b/html/changelogs/AutoChangeLog-pr-88104.yml new file mode 100644 index 0000000000000..1d2da746e338e --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88104.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - bugfix: "Fixed parsnip sabre not receiving a jousting component" \ No newline at end of file From 63cd4b2a7f285b7455e7bd5eda07c04af34d22ed Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 10:27:59 +0000 Subject: [PATCH 34/55] Automatic changelog for PR #88099 [ci skip] --- html/changelogs/AutoChangeLog-pr-88099.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88099.yml diff --git a/html/changelogs/AutoChangeLog-pr-88099.yml b/html/changelogs/AutoChangeLog-pr-88099.yml new file mode 100644 index 0000000000000..95c5f4edc254d --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88099.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - bugfix: "Organ manipulation can be completed once again and doesn't cause you to be un-melee-able in some cases." \ No newline at end of file From b4a9af772d60fba2db7c14e841978726935c7f6c Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 10:28:06 +0000 Subject: [PATCH 35/55] Automatic changelog for PR #88100 [ci skip] --- html/changelogs/AutoChangeLog-pr-88100.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88100.yml diff --git a/html/changelogs/AutoChangeLog-pr-88100.yml b/html/changelogs/AutoChangeLog-pr-88100.yml new file mode 100644 index 0000000000000..cfc2f7649d8ed --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88100.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - bugfix: "Fixed full augmentation scan experiment being uncompletable" \ No newline at end of file From f2c35f1b381eddde05d847fff31abd00859e5523 Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 23 Nov 2024 04:33:33 -0600 Subject: [PATCH 36/55] Fix chewable bubblegum not metabolizing due to microdose (#88102) ## About The Pull Request - Fixes #87873 Bubblegum with the chewable element was not triggering due to a microdose that would get deleted as soon as it was inserted into the mob. Also added a crash message so if anyone in the future tries to microdose the chewable element it gets caught by our CI/CD checks. ![chrome_MUuaIxcpTI](https://github.com/user-attachments/assets/5d473499-0c42-4677-9a13-598078e512f1) Special thanks to @SmArtKar for digging around in the code and finding the problem. ## Why It's Good For The Game Bubblegum now makes you happy. ## Changelog :cl: timothymtorres, SmArtKar fix: Fix chewable bubblegum not metabolizing due to microdose /:cl: --- code/datums/elements/chewable.dm | 4 ++++ code/game/objects/items/food/sweets.dm | 6 +----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/code/datums/elements/chewable.dm b/code/datums/elements/chewable.dm index d520597f4310b..fb1d1e995b2b6 100644 --- a/code/datums/elements/chewable.dm +++ b/code/datums/elements/chewable.dm @@ -22,6 +22,10 @@ var/obj/item/target_item = target if (metabolization_amount) + // this prevents microdosing which causes the reagent to enter and then delete itself before it can be processed + if(metabolization_amount < REAGENTS_METABOLISM) + CRASH("Attatching /datum/element/chewable to [target] requires metabolization_amount to be higher than [REAGENTS_METABOLISM]u. The amount used was [metabolization_amount]u!") + src.metabolization_amount = metabolization_amount src.slots_to_check = slots_to_check || target_item.slot_flags diff --git a/code/game/objects/items/food/sweets.dm b/code/game/objects/items/food/sweets.dm index d757261ac0154..62c10675f0458 100644 --- a/code/game/objects/items/food/sweets.dm +++ b/code/game/objects/items/food/sweets.dm @@ -204,9 +204,6 @@ slot_flags = ITEM_SLOT_MASK w_class = WEIGHT_CLASS_TINY - /// The amount to metabolize per second - var/metabolization_amount = REAGENTS_METABOLISM / 2 - /obj/item/food/bubblegum/suicide_act(mob/living/user) user.visible_message(span_suicide("[user] swallows [src]! It looks like [user.p_theyre()] trying to commit suicide!")) qdel(src) @@ -214,7 +211,7 @@ /obj/item/food/bubblegum/Initialize(mapload) . = ..() - AddElement(/datum/element/chewable, metabolization_amount = metabolization_amount) + AddElement(/datum/element/chewable) /obj/item/food/bubblegum/nicotine name = "nicotine gum" @@ -238,7 +235,6 @@ color = "#913D3D" food_reagents = list(/datum/reagent/blood = 15) tastes = list("hell" = 1, "people" = 1) - metabolization_amount = REAGENTS_METABOLISM /obj/item/food/bubblegum/bubblegum/process() if(iscarbon(loc)) From 1c4d30ec55765fda9bf526891fffc20d11c29cf6 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:34:16 +0300 Subject: [PATCH 37/55] [NO GBP] Fixes a rare runtime in jetpack component code (#88097) ## About The Pull Request shrug ## Changelog :cl: fix: Fixed a rare runtime in jetpack component code /:cl: --- code/datums/components/jetpack.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/datums/components/jetpack.dm b/code/datums/components/jetpack.dm index fd49aa2b67b6a..0a52d2250be93 100644 --- a/code/datums/components/jetpack.dm +++ b/code/datums/components/jetpack.dm @@ -167,7 +167,7 @@ if (get_dir(source, backup) == movement_dir || source.loc == backup.loc) return - if (!source.client.intended_direction || (source.client.intended_direction & get_dir(source, backup))) + if (!source.client?.intended_direction || (source.client.intended_direction & get_dir(source, backup))) return if (!should_trigger(source) || !check_on_move.Invoke(FALSE)) From 8b0225a3761db1eb3fe50134b534b50af8e473f1 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 10:39:22 +0000 Subject: [PATCH 38/55] Automatic changelog for PR #88097 [ci skip] --- html/changelogs/AutoChangeLog-pr-88097.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88097.yml diff --git a/html/changelogs/AutoChangeLog-pr-88097.yml b/html/changelogs/AutoChangeLog-pr-88097.yml new file mode 100644 index 0000000000000..61304e825ffb0 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88097.yml @@ -0,0 +1,4 @@ +author: "SmArtKar" +delete-after: True +changes: + - bugfix: "Fixed a rare runtime in jetpack component code" \ No newline at end of file From ccc63bf90f9df9d5bfb403a649daa94a7ae90dee Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 10:39:54 +0000 Subject: [PATCH 39/55] Automatic changelog for PR #88102 [ci skip] --- html/changelogs/AutoChangeLog-pr-88102.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88102.yml diff --git a/html/changelogs/AutoChangeLog-pr-88102.yml b/html/changelogs/AutoChangeLog-pr-88102.yml new file mode 100644 index 0000000000000..c04d08c991874 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88102.yml @@ -0,0 +1,4 @@ +author: "timothymtorres, SmArtKar" +delete-after: True +changes: + - bugfix: "Fix chewable bubblegum not metabolizing due to microdose" \ No newline at end of file From 87887757e0f887bf5ccb059c7fb22e98827d633e Mon Sep 17 00:00:00 2001 From: lovegreenstuff <59631103+lovegreenstuff@users.noreply.github.com> Date: Sat, 23 Nov 2024 02:41:09 -0800 Subject: [PATCH 40/55] fixes synthmeat having 0% purity on reaction (#88090) ## About The Pull Request read the title doofus ## Why It's Good For The Game theoretical meat is bad for the economy ok so basically the guy who did this whole thing only had it check for reagent/consumable in the entire beaker, which is goofy as hell for a lot of reasons, but it also has the side effect of not including non/consumable reagents like blood, cryoxadone, and carpotoxin. there's probably a bug in there where adding pure salt or other reagent to the beaker can make highly pure food items with highly impure ingredients, but if youre bothering to do that you couldve made pure ingredients to begin with, and that wouldve existed before i touched it and i dont wanna bother ## Changelog :cl: fix: synthmeat is no longer an nft /:cl: --------- Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> --- code/modules/food_and_drinks/recipes/food_mixtures.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/food_and_drinks/recipes/food_mixtures.dm b/code/modules/food_and_drinks/recipes/food_mixtures.dm index e4ef45bda7fde..be04767b55ded 100644 --- a/code/modules/food_and_drinks/recipes/food_mixtures.dm +++ b/code/modules/food_and_drinks/recipes/food_mixtures.dm @@ -40,7 +40,7 @@ var/resulting_reagent_purity /datum/chemical_reaction/food/pre_reaction_other_checks(datum/reagents/holder) - resulting_reagent_purity = holder.get_average_purity(/datum/reagent/consumable) + resulting_reagent_purity = holder.get_average_purity() return TRUE /datum/chemical_reaction/food/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) From aacaf527f34e40a2eb4672e180fb43b17b7100c4 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 10:41:42 +0000 Subject: [PATCH 41/55] Automatic changelog for PR #88090 [ci skip] --- html/changelogs/AutoChangeLog-pr-88090.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88090.yml diff --git a/html/changelogs/AutoChangeLog-pr-88090.yml b/html/changelogs/AutoChangeLog-pr-88090.yml new file mode 100644 index 0000000000000..6ea4dd864a7fa --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88090.yml @@ -0,0 +1,4 @@ +author: "lovegreenstuff" +delete-after: True +changes: + - bugfix: "synthmeat is no longer an nft" \ No newline at end of file From 13705da08d43c332f766b5f3c8177375d3201b4d Mon Sep 17 00:00:00 2001 From: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:27:02 +0200 Subject: [PATCH 42/55] fixes a few issues with the chipped quirk (#87764) ## About The Pull Request the text displayed when this quirk's gained would include the skillchip's typepath rather than its name. this fixes that. also fixes an issue with how the callback timer was being handled, which would lead to a runtime whenever the quirk was removed (since addtimer returns the id and not the callback itself). also fixes the scratch effect never actually working because an organ slot was being provided to `get_organ_by_type` rather than the type. also fixes the itchy effect not going away after the skillchip is removed (it gets added again if the skillchip is reimplanted). ## Why It's Good For The Game fixes a few issues with the chipped quirk ## Changelog :cl: fix: fixes the chipped quirk displaying the skill chip's typepath rather than its name fix: fixes chipped quirk's itchy effect not working fix: the chipped quirk's itchy effect now goes away when the skillchip is removed /:cl: --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> --- code/__DEFINES/dcs/signals/signals_object.dm | 6 ++ code/datums/quirks/positive_quirks/chipped.dm | 70 +++++++++++++------ code/datums/status_effects/_status_effect.dm | 6 +- .../library/skill_learning/skillchip.dm | 4 +- 4 files changed, 62 insertions(+), 24 deletions(-) diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 2b6c1b3220679..8b5778602e02c 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -539,3 +539,9 @@ #define COMSIG_ITEM_WEIGHT_CLASS_CHANGED "item_weight_class_changed" /// Sent from /obj/item/update_weight_class(), to its loc. (obj/item/changed_item, old_w_class, new_w_class) #define COMSIG_ATOM_CONTENTS_WEIGHT_CLASS_CHANGED "atom_contents_weight_class_changed" + +///Sent from /obj/item/skillchip/on_implant() +#define COMSIG_SKILLCHIP_IMPLANTED "skillchip_implanted" + +///Sent from /obj/item/skillchip/on_remove() +#define COMSIG_SKILLCHIP_REMOVED "skillchip_removed" diff --git a/code/datums/quirks/positive_quirks/chipped.dm b/code/datums/quirks/positive_quirks/chipped.dm index 7f43fc07b5bbc..474e8a4adcdc4 100644 --- a/code/datums/quirks/positive_quirks/chipped.dm +++ b/code/datums/quirks/positive_quirks/chipped.dm @@ -5,7 +5,7 @@ value = 2 gain_text = span_notice("The chip in your head itches a bit.") lose_text = span_danger("You don't feel so chipped anymore..") - medical_record_text = "Patient explained how they got caught up in 'the skillchip chase' recently, and now the chip in they head itches every so often. Dumbass." + medical_record_text = "Patient explained how they got caught up in 'the skillchip chase' recently, and now the chip in their head itches every so often. Dumbass." mail_goodies = list( /obj/item/skillchip/matrix_taunt, /obj/item/skillchip/big_pointer, @@ -14,44 +14,70 @@ ) /// Variable that holds the chip, used on removal. var/obj/item/skillchip/installed_chip - var/datum/callback/itchy_timer + ///itchy status effect we give our owner + var/datum/itchy_effect /datum/quirk_constant_data/chipped associated_typepath = /datum/quirk/chipped customization_options = list(/datum/preference/choiced/chipped) /datum/quirk/chipped/add_to_holder(mob/living/new_holder, quirk_transfer, client/client_source) - var/obj/item/skillchip/chip_pref = GLOB.quirk_chipped_choice[client_source?.prefs?.read_preference(/datum/preference/choiced/chipped)] + var/chip_pref = client_source?.prefs?.read_preference(/datum/preference/choiced/chipped) - if(!chip_pref) + if(isnull(chip_pref)) return ..() - - gain_text = span_notice("The [chip_pref] in your head itches a bit.") + installed_chip = GLOB.quirk_chipped_choice[chip_pref] || GLOB.quirk_chipped_choice[pick(GLOB.quirk_chipped_choice)] + gain_text = span_notice("The [installed_chip::name] in your head itches a bit.") lose_text = span_notice("Your head stops itching so much.") return ..() /datum/quirk/chipped/add_unique(client/client_source) - - var/preferred_chip = GLOB.quirk_chipped_choice[client_source?.prefs?.read_preference(/datum/preference/choiced/chipped)] - if(isnull(preferred_chip)) //Client is gone or they chose a random chip - preferred_chip = GLOB.quirk_chipped_choice[pick(GLOB.quirk_chipped_choice)] + if(!iscarbon(quirk_holder)) + return var/mob/living/carbon/quirk_holder_carbon = quirk_holder - if(iscarbon(quirk_holder)) - installed_chip = new preferred_chip() - quirk_holder_carbon.implant_skillchip(installed_chip, force = TRUE) + installed_chip = new installed_chip() + + RegisterSignals(installed_chip, list(COMSIG_QDELETING, COMSIG_SKILLCHIP_REMOVED), PROC_REF(remove_effect)) + RegisterSignal(installed_chip, COMSIG_SKILLCHIP_IMPLANTED, PROC_REF(apply_effect)) + + quirk_holder_carbon.implant_skillchip(installed_chip, force = TRUE) installed_chip.try_activate_skillchip(silent = FALSE, force = TRUE) - var/obj/item/organ/brain/itchy_brain = quirk_holder.get_organ_by_type(ORGAN_SLOT_BRAIN) - itchy_timer = addtimer(CALLBACK(src, PROC_REF(cause_itchy), itchy_brain), rand(5 SECONDS, 10 MINUTES)) // they get The Itch from a poor quality install every so often +/datum/quirk/chipped/proc/apply_effect(datum/source, obj/item/brain_applied) + SIGNAL_HANDLER + var/mob/living/carbon/quirk_holder_carbon = quirk_holder + if(brain_applied == quirk_holder_carbon.get_organ_slot(ORGAN_SLOT_BRAIN)) + itchy_effect = quirk_holder.apply_status_effect(/datum/status_effect/itchy_skillchip_quirk) + +/datum/quirk/chipped/proc/remove_effect(datum/source, obj/item/brain_removed) + SIGNAL_HANDLER + var/mob/living/carbon/quirk_holder_carbon = quirk_holder + if(QDELING(source) || brain_removed == quirk_holder_carbon.get_organ_slot(ORGAN_SLOT_BRAIN)) + quirk_holder.remove_status_effect(itchy_effect) + itchy_effect = null /datum/quirk/chipped/remove() - qdel(installed_chip) - deltimer(itchy_timer) - . = ..() + QDEL_NULL(installed_chip) + if(itchy_effect) + quirk_holder.remove_status_effect(itchy_effect) + itchy_effect = null + return ..() -/datum/quirk/chipped/proc/cause_itchy(obj/item/organ/brain/itchy_brain) +/datum/status_effect/itchy_skillchip_quirk + id = "itchy skillchip" + tick_interval_lowerbound = 5 SECONDS + tick_interval_upperbound = 10 MINUTES + ///lower damage we apply to our itchy owner + var/minimum_damage = 1 + ///upper damage we apply to our itchy owner + var/maximum_damage = 5 - itchy_brain.apply_organ_damage(rand(1, 5), maximum = itchy_brain.maxHealth * 0.3) - to_chat(itchy_brain.owner, span_warning("Your [itchy_brain] itches.")) - itchy_timer = addtimer(CALLBACK(itchy_brain, PROC_REF(cause_itchy)), rand(5 SECONDS, 10 MINUTES)) // it will never end +/datum/status_effect/itchy_skillchip_quirk/tick(seconds_between_ticks) + var/mob/living/carbon/carbon_owner = owner + var/obj/item/organ/brain/itchy_brain = carbon_owner.get_organ_slot(ORGAN_SLOT_BRAIN) + if(isnull(itchy_brain)) + return + itchy_brain.apply_organ_damage(rand(minimum_damage, maximum_damage), maximum = itchy_brain.maxHealth * 0.3) + if(owner.stat == CONSCIOUS && !owner.incapacitated && owner.get_empty_held_indexes()) + to_chat(owner, span_warning("You scratch the itch in your head.")) diff --git a/code/datums/status_effects/_status_effect.dm b/code/datums/status_effects/_status_effect.dm index df525fa8c81b1..9f3e3a549046b 100644 --- a/code/datums/status_effects/_status_effect.dm +++ b/code/datums/status_effects/_status_effect.dm @@ -12,6 +12,10 @@ /// While processing, this becomes the world.time when the next tick will occur. /// -1 = will prevent ticks, and if duration is also unlimited (-1), stop processing wholesale. var/tick_interval = 1 SECONDS + ///If our tick intervals are set to be a dynamic value within a range, the lowerbound of said range + var/tick_interval_lowerbound + ///If our tick intervals are set to be a dynamic value within a range, the upperbound of said range + var/tick_interval_upperbound /// The mob affected by the status effect. VAR_FINAL/mob/living/owner /// How many of the effect can be on one mob, and/or what happens when you try to add a duplicate. @@ -112,7 +116,7 @@ return if(tick_interval != STATUS_EFFECT_NO_TICK && tick_interval < world.time) - var/tick_length = initial(tick_interval) + var/tick_length = (tick_interval_upperbound && tick_interval_lowerbound) ? rand(tick_interval_lowerbound, tick_interval_upperbound) : initial(tick_interval) tick(tick_length / (1 SECONDS)) tick_interval = world.time + tick_length if(QDELING(src)) diff --git a/code/modules/library/skill_learning/skillchip.dm b/code/modules/library/skill_learning/skillchip.dm index 5ca3f784ecbec..f534e391bcb27 100644 --- a/code/modules/library/skill_learning/skillchip.dm +++ b/code/modules/library/skill_learning/skillchip.dm @@ -132,10 +132,12 @@ * * owner_brain - The brain that this skillchip was implanted in to. */ /obj/item/skillchip/proc/on_implant(obj/item/organ/brain/owner_brain) + SHOULD_CALL_PARENT(TRUE) if(holding_brain) CRASH("Skillchip is trying to be implanted into [owner_brain], but it's already implanted in [holding_brain]") holding_brain = owner_brain + SEND_SIGNAL(src, COMSIG_SKILLCHIP_IMPLANTED, holding_brain) /** * Called when a skillchip is activated. @@ -172,7 +174,7 @@ try_deactivate_skillchip(silent, TRUE) COOLDOWN_RESET(src, chip_cooldown) - + SEND_SIGNAL(src, COMSIG_SKILLCHIP_REMOVED, holding_brain) holding_brain = null /** From 2e9932a889cab69c45b7acb4cd2535bd8d05320f Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 11:27:25 +0000 Subject: [PATCH 43/55] Automatic changelog for PR #87764 [ci skip] --- html/changelogs/AutoChangeLog-pr-87764.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-87764.yml diff --git a/html/changelogs/AutoChangeLog-pr-87764.yml b/html/changelogs/AutoChangeLog-pr-87764.yml new file mode 100644 index 0000000000000..acbb17758af6c --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-87764.yml @@ -0,0 +1,6 @@ +author: "Ben10Omintrix" +delete-after: True +changes: + - bugfix: "fixes the chipped quirk displaying the skill chip's typepath rather than its name" + - bugfix: "fixes chipped quirk's itchy effect not working" + - bugfix: "the chipped quirk's itchy effect now goes away when the skillchip is removed" \ No newline at end of file From bbb7a4174355b42bc1cebbc4aec01054341a19d2 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:02:35 +0300 Subject: [PATCH 44/55] Guncode Agony 4: The Great Projectile Purge (#87740) ## About The Pull Request ~~Kept you waitin huh!~~ The projectile refactor is finally here, 4 years later. This PR (almost) completely rewrites projectile logic to be more maintainable and performant. ### Key changes: * Instead of moving by a fixed amount of pixels, potentially skipping tile corners and being performance-heavy, projectiles now use raymarching in order to teleport through tiles and only visually animate themselves. This allows us to do custom per-projectile animations and makes the code much more reliable, sane and maintainable. You (did not) serve us well, pixel_move. * Speed variable now measures how many tiles (if SSprojectiles has default values) a projectile passes in a tick instead of being a magical Kevinz Unit:tm: coefficient. pixel_speed_multiplier has been retired because it never had a right to exist in the first place. __This means that downstreams will need to set all of their custom projectiles' speed values to ``pixel_speed_multiplier / speed``__ in order to prevent projectiles from inverting their speed. * Hitscans no longer operate with spartial vectors and instead only store key points in which the projectile impacted something or changed its angle. This should similarly make the code much easier to work with, as well as fixing some visual jank due to incorrect calculations. * Projectiles only delete themselves the ***next*** tick after impacting something or reaching their maximum range. Doing so allows them to finish their impact animation and hide themselves between ticks via animation chains. This means that projectiles no longer disappear ~a tile before hitting their target, and that we can finally make impact markers be consistent with where the projectile actually landed instead of being entirely random.
Here is an example of how this affects our slowest-moving projectile: Magic Missiles. Before: https://github.com/user-attachments/assets/06b3a980-4701-4aeb-aa3e-e21cd056020e After: https://github.com/user-attachments/assets/abe8ed5c-4b81-4120-8d2f-cf16ff5be915
And here is a much faster, and currently jankier, disabler SMG. Before: https://github.com/user-attachments/assets/2d84aef1-0c83-44ef-a698-8ec716587348 After: https://github.com/user-attachments/assets/2e7c1336-f611-404f-b3ff-87433398d238
### But how will this affect the ~~trout population~~ gameplay? Beyond improved visuals, smoother movement and a few minor bugfixes, this should not have a major gameplay impact. If something changed its behavior in an unexpected way or started looking odd, please make an issue report. Projectile impacts should now be consistent with their visual position, so hitting and dodging shots should be slightly easier and more intuitive. This PR should be testmerged extensively due to the amount of changes it brings and considerable difficulty in reviewing them. Please contact me to ensure its good to merge. Closes #71822 Closes #78547 Closes #78871 Closes #83901 Closes #87802 Closes #88073 ## Why It's Good For The Game Our core projectile code is an ungodly abomination that nobody except me, Kapu and Potato dared to poke in the past months (potentially longer). It is laggy, overcomplicated and absolutely unmaintaineable - while a lot of decisions made sense 4 years ago when we were attempting to introduce pixel movement, nowadays they are only acting as major roadblocks for any contributor who is attempting to make projectile behavior that differs from normal in any way. Huge thanks to Kapu and Potato (Lemon) on the discord for providing insights, ideas and advice throughout the past months regarding potential improvements to projectile code, almost all of which made it in. ## Changelog :cl: qol: Projectiles now visually impact their targets instead of disappearing about a tile short of it. fix: Fixed multiple minor issues with projectile behavior refactor: Completely rewrote almost all of our projectile code - if anything broke or started looking/behaving oddly, make an issue report! /:cl: --- .github/CODEOWNERS | 4 + code/__DEFINES/combat.dm | 4 - code/__DEFINES/dcs/signals/signals_object.dm | 10 +- code/__DEFINES/maths.dm | 3 + code/__DEFINES/projectiles.dm | 21 +- code/__HELPERS/maths.dm | 2 +- .../subsystem/processing/projectiles.dm | 33 +- .../actions/mobs/create_legion_turrets.dm | 8 +- code/datums/actions/mobs/projectileattack.dm | 6 +- code/datums/components/dart_insert.dm | 2 - code/datums/components/mirv.dm | 14 +- code/datums/components/parry.dm | 11 +- code/datums/components/pellet_cloud.dm | 44 +- code/datums/components/splat.dm | 4 +- code/datums/elements/embed.dm | 6 +- code/datums/elements/projectile_drop.dm | 4 +- code/datums/elements/selfknockback.dm | 10 +- code/datums/mutations/sight.dm | 2 +- code/datums/position_point_vector.dm | 171 +-- .../proximity_monitor/fields/timestop.dm | 8 +- code/datums/status_effects/debuffs/debuffs.dm | 2 +- .../machinery/dna_infuser/infuser_actions.dm | 2 +- .../machinery/porta_turret/portable_turret.dm | 2 +- code/game/objects/effects/portals.dm | 5 +- .../projectiles/projectile_effects.dm | 21 +- .../temporary_visuals/projectiles/tracer.dm | 22 - .../objects/items/devices/traitordevices.dm | 2 +- code/game/objects/items/hand_items.dm | 6 +- code/game/objects/items/robot/items/food.dm | 4 +- code/game/objects/items/robot/items/tools.dm | 8 +- code/game/objects/items/shooting_range.dm | 2 +- code/game/objects/objs.dm | 4 +- .../objects/structures/deployable_turret.dm | 2 +- code/game/objects/structures/mirror.dm | 6 +- code/game/objects/structures/reflector.dm | 13 +- code/modules/admin/verbs/adminfun.dm | 2 +- code/modules/antagonists/cult/cult_items.dm | 2 +- .../sacrifice_knowledge/sacrifice_buff.dm | 2 +- .../heretic/magic/furious_steel.dm | 2 +- .../antagonists/heretic/magic/moon_parade.dm | 3 +- .../antagonists/heretic/magic/rust_wave.dm | 2 +- .../antagonists/heretic/magic/star_blast.dm | 3 +- code/modules/clothing/shoes/gunboots.dm | 2 +- .../experiment/physical_experiments.dm | 4 +- code/modules/fishing/fishing_rod.dm | 4 +- code/modules/hallucination/stray_bullet.dm | 7 +- .../ruins/lavalandruin_code/watcher_grave.dm | 3 +- code/modules/mining/equipment/grapple_gun.dm | 2 +- .../mining/equipment/kinetic_crusher.dm | 4 +- .../modules/mining/lavaland/megafauna_loot.dm | 2 +- .../mining/lavaland/necropolis_chests.dm | 4 +- code/modules/mining/ores_coins.dm | 4 +- .../icemoon/ice_demon/ice_demon_abilities.dm | 3 +- .../jungle/seedling/seedling_projectiles.dm | 2 +- .../lavaland/bileworm/bileworm_actions.dm | 2 +- .../living/carbon/alien/adult/alien_powers.dm | 2 +- code/modules/mob/living/carbon/carbon.dm | 6 + .../mob/living/carbon/carbon_defense.dm | 10 +- .../mob/living/carbon/human/human_defense.dm | 8 +- code/modules/mob/living/living.dm | 5 + code/modules/mob/living/living_defense.dm | 9 +- .../mob/living/simple_animal/bot/ed209bot.dm | 2 +- .../mob/living/simple_animal/bot/mulebot.dm | 4 +- .../mob/living/simple_animal/bot/secbot.dm | 10 +- .../living/simple_animal/hostile/hostile.dm | 8 +- .../hostile/megafauna/blood_drunk_miner.dm | 2 +- .../hostile/megafauna/bubblegum.dm | 4 +- .../hostile/megafauna/colossus.dm | 16 +- .../hostile/megafauna/demonic_frost_miner.dm | 11 +- .../hostile/megafauna/hierophant.dm | 4 +- .../hostile/megafauna/wendigo.dm | 22 +- .../hostile/mining_mobs/curse_blob.dm | 8 +- .../hostile/mining_mobs/elites/herald.dm | 6 +- .../mob/living/simple_animal/hostile/ooze.dm | 4 +- code/modules/mob/living/sneeze.dm | 2 +- code/modules/mod/modules/modules_antag.dm | 2 +- .../mod/modules/modules_engineering.dm | 12 +- code/modules/mod/modules/modules_medical.dm | 2 +- code/modules/mod/modules/modules_ninja.dm | 2 +- code/modules/mod/modules/modules_security.dm | 2 +- code/modules/mod/modules/modules_supply.dm | 2 +- code/modules/mod/modules/modules_timeline.dm | 2 +- code/modules/power/rtg.dm | 6 +- .../modules/projectiles/ammunition/_firing.dm | 2 +- .../guns/ballistic/bows/bow_arrows.dm | 2 +- code/modules/projectiles/guns/energy/laser.dm | 4 +- .../projectiles/guns/energy/special.dm | 2 +- .../projectiles/guns/special/blastcannon.dm | 4 +- code/modules/projectiles/projectile.dm | 1065 ++++++++++------- code/modules/projectiles/projectile/beams.dm | 6 +- .../projectile/bullets/_incendiary.dm | 2 +- .../projectiles/projectile/bullets/pistol.dm | 2 +- .../projectiles/projectile/bullets/rifle.dm | 9 +- .../projectiles/projectile/bullets/shotgun.dm | 6 +- .../projectiles/projectile/bullets/sniper.dm | 4 +- .../projectiles/projectile/bullets/special.dm | 7 +- .../projectiles/projectile/energy/_energy.dm | 2 +- .../projectile/energy/nuclear_particle.dm | 2 +- .../projectiles/projectile/energy/photon.dm | 9 +- .../projectiles/projectile/energy/tesla.dm | 2 +- code/modules/projectiles/projectile/magic.dm | 48 +- .../projectiles/projectile/special/curse.dm | 2 +- .../reagent_containers/cups/drinks.dm | 4 +- .../reagents/reagent_containers/cups/soda.dm | 16 +- code/modules/research/experimentor.dm | 2 +- .../spell_types/aoe_spell/magic_missile.dm | 2 +- .../spells/spell_types/pointed/_pointed.dm | 2 +- .../spells/spell_types/pointed/spell_cards.dm | 2 +- .../projectile/_basic_projectile.dm | 2 +- code/modules/unit_tests/embedding.dm | 2 +- code/modules/vehicles/atv.dm | 6 +- .../mecha/equipment/weapons/weapons.dm | 2 +- code/modules/vehicles/secway.dm | 4 +- 113 files changed, 1016 insertions(+), 908 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dfd2d3291901d..c2d3fa09709c7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -94,6 +94,10 @@ /code/game/area/ @san7890 /icons/area/ @san7890 +# SmArtKar + +/code/modules/projectiles/projectile.dm @SmArtKar + # stylemistake /code/__DEFINES/chat.dm @stylemistake diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index aabd281bd7dab..2c20765d3519b 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -224,10 +224,6 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( #define SUPPRESSED_QUIET 1 ///standard suppressed #define SUPPRESSED_VERY 2 /// no message -//Projectile Reflect -#define REFLECT_NORMAL (1<<0) -#define REFLECT_FAKEPROJECTILE (1<<1) - //His Grace. #define HIS_GRACE_SATIATED 0 //He hungers not. If bloodthirst is set to this, His Grace is asleep. #define HIS_GRACE_PECKISH 20 //Slightly hungry. diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 8b5778602e02c..69ccaa5fa641b 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -380,9 +380,9 @@ // /obj/projectile signals (sent to the firer) -///from base of /obj/projectile/proc/on_hit(), like COMSIG_PROJECTILE_ON_HIT but on the projectile itself and with the hit limb (if any): (atom/movable/firer, atom/target, angle, hit_limb, blocked) +///from base of /obj/projectile/proc/on_hit(), like COMSIG_PROJECTILE_ON_HIT but on the projectile itself and with the hit limb (if any): (atom/movable/firer, atom/target, angle, hit_limb, blocked, pierce_hit) #define COMSIG_PROJECTILE_SELF_ON_HIT "projectile_self_on_hit" -///from base of /obj/projectile/proc/on_hit(): (atom/movable/firer, atom/target, angle, hit_limb, blocked) +///from base of /obj/projectile/proc/on_hit(): (atom/movable/firer, atom/target, angle, hit_limb, blocked, pierce_hit) #define COMSIG_PROJECTILE_ON_HIT "projectile_on_hit" ///from base of /obj/projectile/proc/fire(): (obj/projectile, atom/original_target) #define COMSIG_PROJECTILE_BEFORE_FIRE "projectile_before_fire" @@ -393,11 +393,11 @@ ///sent to targets during the process_hit proc of projectiles #define COMSIG_PROJECTILE_PREHIT "com_proj_prehit" #define PROJECTILE_INTERRUPT_HIT (1<<0) -///from /obj/projectile/pixel_move(): () -#define COMSIG_PROJECTILE_PIXEL_STEP "projectile_pixel_step" +///from /obj/projectile/process_movement(): () +#define COMSIG_PROJECTILE_MOVE_PROCESS_STEP "projectile_move_process_step" ///sent to self during the process_hit proc of projectiles #define COMSIG_PROJECTILE_SELF_PREHIT "com_proj_prehit" -///from the base of /obj/projectile/Range(): () +///from the base of /obj/projectile/reduce_range(): () #define COMSIG_PROJECTILE_RANGE "projectile_range" ///from the base of /obj/projectile/on_range(): () #define COMSIG_PROJECTILE_RANGE_OUT "projectile_range_out" diff --git a/code/__DEFINES/maths.dm b/code/__DEFINES/maths.dm index aa13298e339a3..35c8d3edaf632 100644 --- a/code/__DEFINES/maths.dm +++ b/code/__DEFINES/maths.dm @@ -29,6 +29,9 @@ /// Gets the sign of x, returns -1 if negative, 0 if 0, 1 if positive #define SIGN(x) ( ((x) > 0) - ((x) < 0) ) +/// Returns the integer closest to 0 from a division +#define SIGNED_FLOOR_DIVISION(x, y) (SIGN(x) * FLOOR(abs(x) / y, 1)) + #define CEILING(x, y) ( -round(-(x) / (y)) * (y) ) #define ROUND_UP(x) ( -round(-(x))) diff --git a/code/__DEFINES/projectiles.dm b/code/__DEFINES/projectiles.dm index f93faab62de90..c3e861b56c99f 100644 --- a/code/__DEFINES/projectiles.dm +++ b/code/__DEFINES/projectiles.dm @@ -77,12 +77,27 @@ #define RETURN_PRECISE_POSITION(A) new /datum/position(A) #define RETURN_PRECISE_POINT(A) new /datum/point(A) -#define RETURN_POINT_VECTOR(ATOM, ANGLE, SPEED) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED)) -#define RETURN_POINT_VECTOR_INCREMENT(ATOM, ANGLE, SPEED, AMT) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED, AMT)) - ///The self charging rate of energy guns that magically recharge themselves, in watts. #define STANDARD_ENERGY_GUN_SELF_CHARGE_RATE (0.05 * STANDARD_CELL_CHARGE) /// Macro to turn a number of laser shots into an energy cost, based on the above define /// e.g. LASER_SHOTS(12, STANDARD_CELL_CHARGE) means 12 shots #define LASER_SHOTS(X, MAX_CHARGE) (((100 * MAX_CHARGE) - ((100 * MAX_CHARGE) % X)) / (100 * X)) // I wish I could just use round, but it can't be used in datum members + +/// How far do the projectile hits the prone mob +#define MAX_RANGE_HIT_PRONE_TARGETS 10 + +/// Queued for impact deletion (simple qdel) +#define PROJECTILE_IMPACT_DELETE "impact_delete" +/// Queued for range deletion (on_range call) +#define PROJECTILE_RANGE_DELETE "range_delete" + +/// Projectile either hasn't impacted anything, or pierced through the target +#define PROJECTILE_IMPACT_PASSED "impact_passed" +/// Projectile has been "deleted" before bullet_act call has occured +#define PROJECTILE_IMPACT_INTERRUPTED "impact_interrupted" +/// Projectile has successfully impacted something and is scheduled for deletion +#define PROJECTILE_IMPACT_SUCCESSFUL "impact_successful" + +/// For how long projectile tracers linger +#define PROJECTILE_TRACER_DURATION 0.3 SECONDS diff --git a/code/__HELPERS/maths.dm b/code/__HELPERS/maths.dm index 040e9694429dc..bb2df78ab34ab 100644 --- a/code/__HELPERS/maths.dm +++ b/code/__HELPERS/maths.dm @@ -238,7 +238,7 @@ return min(new_value, threshold * -1) /// Takes two values x and y, and returns 1/((1/x) + y) -/// Useful for providing an additive modifier to a value that is used as a divisor, such as `/obj/projectile/var/speed` +/// Useful for providing an additive modifier to a value that is used as a divisor /proc/reciprocal_add(x, y) return 1/((1/x)+y) diff --git a/code/controllers/subsystem/processing/projectiles.dm b/code/controllers/subsystem/processing/projectiles.dm index 48d465dd0fdaf..0124296a3a23b 100644 --- a/code/controllers/subsystem/processing/projectiles.dm +++ b/code/controllers/subsystem/processing/projectiles.dm @@ -3,21 +3,18 @@ PROCESSING_SUBSYSTEM_DEF(projectiles) wait = 1 stat_tag = "PP" flags = SS_NO_INIT|SS_TICKER - var/global_max_tick_moves = 10 - var/global_pixel_speed = 2 - var/global_iterations_per_move = 16 - -/datum/controller/subsystem/processing/projectiles/proc/set_pixel_speed(new_speed) - global_pixel_speed = new_speed - for(var/i in processing) - var/obj/projectile/P = i - if(istype(P)) //there's non projectiles on this too. - P.set_pixel_speed(new_speed) - -/datum/controller/subsystem/processing/projectiles/vv_edit_var(var_name, var_value) - switch(var_name) - if(NAMEOF(src, global_pixel_speed)) - set_pixel_speed(var_value) - return TRUE - else - return ..() + /* + * Maximum amount of pixels a projectile can pass per tick *unless* its a hitscan projectile. + * This prevents projectiles from turning into essentially hitscans if SSprojectiles starts chugging + * and projectiles accumulate a bunch of overtime they try to process next tick to fly through half the map. + * Shouldn't really be increased past 5 tiles per tick because this maxes out at 100 FPS (recommended as of now) + * and making a projectile faster than that will make it look jumpy because it'll be passing inconsistent + * amounts of pixels per tick. + */ + var/max_pixels_per_tick = ICON_SIZE_ALL * 5 + /* + * How many pixels a projectile with a speed value of 1 passes in a tick. Currently all speed values + * assume that 1 speed = 1 tile per decisecond, but this is a variable so that admins/debuggers can edit + * in order to debug projectile behavior by evenly slowing or speeding all of them up. + */ + var/pixels_per_decisecond = ICON_SIZE_ALL diff --git a/code/datums/actions/mobs/create_legion_turrets.dm b/code/datums/actions/mobs/create_legion_turrets.dm index 71427893f43da..34a0c6852d6c7 100644 --- a/code/datums/actions/mobs/create_legion_turrets.dm +++ b/code/datums/actions/mobs/create_legion_turrets.dm @@ -78,8 +78,7 @@ return //Now we generate the tracer. var/angle = get_angle(our_turf, target_turf) - var/datum/point/vector/V = new(our_turf.x, our_turf.y, our_turf.z, 0, 0, angle) - generate_tracer_between_points(V, V.return_vector_after_increments(6), /obj/effect/projectile/tracer/legion/tracer, 0, shot_delay, 0, 0, 0, null) + our_turf.Beam(target_turf, 'icons/effects/beam.dmi', "blood_light", time = shot_delay) playsound(src, 'sound/machines/airlock/airlockopen.ogg', 100, TRUE) addtimer(CALLBACK(src, PROC_REF(fire_beam), angle), shot_delay) @@ -105,11 +104,6 @@ hitscan = TRUE projectile_piercing = ALL -/// Used for the legion turret tracer. -/obj/effect/projectile/tracer/legion/tracer - icon = 'icons/effects/beam.dmi' - icon_state = "blood_light" - /// Used for the legion turret beam. /obj/effect/projectile/tracer/legion icon = 'icons/effects/beam.dmi' diff --git a/code/datums/actions/mobs/projectileattack.dm b/code/datums/actions/mobs/projectileattack.dm index 933f94d0025f3..f833e05dde2e5 100644 --- a/code/datums/actions/mobs/projectileattack.dm +++ b/code/datums/actions/mobs/projectileattack.dm @@ -49,7 +49,7 @@ if(!isnum(speed_multiplier)) speed_multiplier = projectile_speed_multiplier our_projectile.speed *= speed_multiplier - our_projectile.preparePixelProjectile(endloc, startloc, null, projectile_spread) + our_projectile.aim_projectile(endloc, startloc, null, projectile_spread) our_projectile.firer = firer if(target) our_projectile.original = target @@ -224,7 +224,7 @@ cooldown_time = 10 SECONDS projectile_type = /obj/projectile/colossus/wendigo_shockwave shot_angles = list(-20, -10, 0, 10, 20) - projectile_speed_multiplier = 4 + projectile_speed_multiplier = 0.25 /datum/action/cooldown/mob_cooldown/projectile_attack/shotgun_blast/colossus @@ -378,7 +378,7 @@ if(enraged) projectile_speed_multiplier = 1 else - projectile_speed_multiplier = 1.5 + projectile_speed_multiplier = 0.66 var/shots_per = 24 for(var/shoot_times in 1 to 8) var/offset = shoot_times % 2 diff --git a/code/datums/components/dart_insert.dm b/code/datums/components/dart_insert.dm index ad869903051d2..459da9d217cbe 100644 --- a/code/datums/components/dart_insert.dm +++ b/code/datums/components/dart_insert.dm @@ -135,8 +135,6 @@ /datum/component/dart_insert/proc/apply_var_modifiers(obj/projectile/projectile) var_modifiers = istype(modifier_getter) ? modifier_getter.Invoke() : list() projectile.damage += var_modifiers["damage"] - if(var_modifiers["speed"]) - var_modifiers["speed"] = reciprocal_add(projectile.speed, var_modifiers["speed"]) - projectile.speed projectile.speed += var_modifiers["speed"] projectile.armour_penetration += var_modifiers["armour_penetration"] projectile.wound_bonus += var_modifiers["wound_bonus"] diff --git a/code/datums/components/mirv.dm b/code/datums/components/mirv.dm index 52b4053babb5a..ead33d476d459 100644 --- a/code/datums/components/mirv.dm +++ b/code/datums/components/mirv.dm @@ -32,12 +32,12 @@ var/turf/target_turf = get_turf(target) for(var/turf/shootat_turf in RANGE_TURFS(radius, target) - RANGE_TURFS(radius-1, target)) - var/obj/projectile/P = new projectile_type(target_turf) + var/obj/projectile/proj = new projectile_type(target_turf) //Shooting Code: - P.range = radius+1 + proj.range = radius+1 if(override_projectile_range) - P.range = override_projectile_range - P.preparePixelProjectile(shootat_turf, target) - P.firer = firer // don't hit ourself that would be really annoying - P.impacted = list(WEAKREF(target) = TRUE) // don't hit the target we hit already with the flak - P.fire() + proj.range = override_projectile_range + proj.aim_projectile(shootat_turf, target) + proj.firer = firer // don't hit ourself that would be really annoying + proj.impacted = list(WEAKREF(target) = TRUE) // don't hit the target we hit already with the flak + proj.fire() diff --git a/code/datums/components/parry.dm b/code/datums/components/parry.dm index 2486796d378bb..e24bb4d2480df 100644 --- a/code/datums/components/parry.dm +++ b/code/datums/components/parry.dm @@ -23,7 +23,7 @@ /// Callback for special effects upon parrying var/datum/callback/parry_callback -/datum/component/parriable_projectile/Initialize(parry_speed_mult = 0.8, parry_damage_mult = 1.15, boost_speed_mult = 0.6, boost_damage_mult = 1.5, parry_trait = TRAIT_MINING_PARRYING, grace_period = 0.25 SECONDS, datum/callback/parry_callback = null) +/datum/component/parriable_projectile/Initialize(parry_speed_mult = 1.25, parry_damage_mult = 1.15, boost_speed_mult = 1.6, boost_damage_mult = 1.5, parry_trait = TRAIT_MINING_PARRYING, grace_period = 0.25 SECONDS, datum/callback/parry_callback = null) if(!isprojectile(parent)) return COMPONENT_INCOMPATIBLE src.parry_speed_mult = parry_speed_mult @@ -41,13 +41,13 @@ . = ..() /datum/component/parriable_projectile/RegisterWithParent() - RegisterSignal(parent, COMSIG_PROJECTILE_PIXEL_STEP, PROC_REF(on_moved)) + RegisterSignal(parent, COMSIG_PROJECTILE_MOVE_PROCESS_STEP, PROC_REF(on_moved)) RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(before_move)) RegisterSignal(parent, COMSIG_PROJECTILE_BEFORE_MOVE, PROC_REF(before_move)) RegisterSignal(parent, COMSIG_PROJECTILE_SELF_PREHIT, PROC_REF(before_hit)) /datum/component/parriable_projectile/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_PROJECTILE_PIXEL_STEP, COMSIG_MOVABLE_MOVED, COMSIG_PROJECTILE_BEFORE_MOVE, COMSIG_PROJECTILE_SELF_PREHIT)) + UnregisterSignal(parent, list(COMSIG_PROJECTILE_MOVE_PROCESS_STEP, COMSIG_MOVABLE_MOVED, COMSIG_PROJECTILE_BEFORE_MOVE, COMSIG_PROJECTILE_SELF_PREHIT)) /datum/component/parriable_projectile/proc/before_move(obj/projectile/source) SIGNAL_HANDLER @@ -71,7 +71,7 @@ /datum/component/parriable_projectile/proc/on_moved(obj/projectile/source) SIGNAL_HANDLER - if (!isturf(source.loc)) + if (!isturf(source.loc) || parry_turfs[source.loc]) return parry_turfs[source.loc] = world.time + grace_period RegisterSignal(source.loc, COMSIG_CLICK, PROC_REF(on_turf_click)) @@ -95,6 +95,9 @@ attempt_parry(source, user) /datum/component/parriable_projectile/proc/attempt_parry(obj/projectile/source, mob/user) + if (QDELETED(source) || source.deletion_queued) + return + if (SEND_SIGNAL(user, COMSIG_LIVING_PROJECTILE_PARRIED, source) & INTERCEPT_PARRY_EFFECTS) return diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm index e9b7fad34fc20..41f18a6cb1809 100644 --- a/code/datums/components/pellet_cloud.dm +++ b/code/datums/components/pellet_cloud.dm @@ -224,10 +224,10 @@ break ///One of our pellets hit something, record what it was and check if we're done (terminated == num_pellets) -/datum/component/pellet_cloud/proc/pellet_hit(obj/projectile/P, atom/movable/firer, atom/target, Angle, hit_zone) +/datum/component/pellet_cloud/proc/pellet_hit(obj/projectile/proj, atom/movable/firer, atom/target, Angle, hit_zone) SIGNAL_HANDLER - pellets -= P + pellets -= proj terminated++ hits++ var/obj/item/bodypart/hit_part @@ -237,34 +237,34 @@ hit_part = hit_carbon.get_bodypart(hit_zone) if(hit_part) target = hit_part - if(P.wound_bonus != CANT_WOUND) // handle wounding + if(proj.wound_bonus != CANT_WOUND) // handle wounding // unfortunately, due to how pellet clouds handle finalizing only after every pellet is accounted for, that also means there might be a short delay in dealing wounds if one pellet goes wide // while buckshot may reach a target or miss it all in one tick, we also have to account for possible ricochets that may take a bit longer to hit the target if(isnull(wound_info_by_part[hit_part])) wound_info_by_part[hit_part] = list(0, 0, 0) - wound_info_by_part[hit_part][CLOUD_POSITION_DAMAGE] += P.damage // these account for decay - wound_info_by_part[hit_part][CLOUD_POSITION_W_BONUS] += P.wound_bonus - wound_info_by_part[hit_part][CLOUD_POSITION_BW_BONUS] += P.bare_wound_bonus - P.wound_bonus = CANT_WOUND // actual wounding will be handled aggregate + wound_info_by_part[hit_part][CLOUD_POSITION_DAMAGE] += proj.damage // these account for decay + wound_info_by_part[hit_part][CLOUD_POSITION_W_BONUS] += proj.wound_bonus + wound_info_by_part[hit_part][CLOUD_POSITION_BW_BONUS] += proj.bare_wound_bonus + proj.wound_bonus = CANT_WOUND // actual wounding will be handled aggregate else if(isobj(target)) var/obj/hit_object = target - if(hit_object.damage_deflection > P.damage || !P.damage) + if(hit_object.damage_deflection > proj.damage || !proj.damage) damage = FALSE LAZYADDASSOC(targets_hit[target], "hits", 1) LAZYSET(targets_hit[target], "damage", damage) if(targets_hit[target]["hits"] == 1) - RegisterSignal(target, COMSIG_QDELETING, PROC_REF(on_target_qdel), override=TRUE) - UnregisterSignal(P, list(COMSIG_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT)) + RegisterSignal(target, COMSIG_QDELETING, PROC_REF(on_target_qdel), override = TRUE) + UnregisterSignal(proj, list(COMSIG_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT)) if(terminated == num_pellets) finalize() ///One of our pellets disappeared due to hitting their max range (or just somehow got qdel'd), remove it from our list and check if we're done (terminated == num_pellets) -/datum/component/pellet_cloud/proc/pellet_range(obj/projectile/P) +/datum/component/pellet_cloud/proc/pellet_range(obj/projectile/proj) SIGNAL_HANDLER - pellets -= P + pellets -= proj terminated++ - UnregisterSignal(P, list(COMSIG_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT)) + UnregisterSignal(proj, list(COMSIG_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT)) if(terminated == num_pellets) finalize() @@ -279,18 +279,18 @@ pellet.firer = parent // don't hit ourself that would be really annoying pellet.impacted = list(WEAKREF(parent) = TRUE) // don't hit the target we hit already with the flak pellet.suppressed = SUPPRESSED_VERY // set the projectiles to make no message so we can do our own aggregate message - pellet.preparePixelProjectile(target, parent) + pellet.aim_projectile(target, parent) RegisterSignal(pellet, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(pellet_hit)) RegisterSignals(pellet, list(COMSIG_PROJECTILE_RANGE_OUT, COMSIG_QDELETING), PROC_REF(pellet_range)) pellets += pellet pellet.fire() if(landmine_victim) - pellet.process_hit_loop(target) + pellet.impact(target) ///All of our pellets are accounted for, time to go target by target and tell them how many things they got hit by. /datum/component/pellet_cloud/proc/finalize() - var/obj/projectile/P = projectile_type - var/proj_name = initial(P.name) + var/obj/projectile/proj_type = projectile_type + var/proj_name = initial(proj_type.name) for(var/atom/target in targets_hit) var/num_hits = targets_hit[target]["hits"] @@ -303,24 +303,24 @@ hit_part = null //so the visible_message later on doesn't generate extra text. else target = hit_part.owner - if(wound_info_by_part[hit_part] && (initial(P.damage_type) == BRUTE || initial(P.damage_type) == BURN)) // so a cloud of disablers that deal stamina don't inadvertently end up causing burn wounds) + if(wound_info_by_part[hit_part] && (initial(proj_type.damage_type) == BRUTE || initial(proj_type.damage_type) == BURN)) // so a cloud of disablers that deal stamina don't inadvertently end up causing burn wounds) var/damage_dealt = wound_info_by_part[hit_part][CLOUD_POSITION_DAMAGE] var/w_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_W_BONUS] var/bw_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_BW_BONUS] - var/wounding_type = (initial(P.damage_type) == BRUTE) ? WOUND_BLUNT : WOUND_BURN // sharpness is handled in the wound rolling + var/wounding_type = (initial(proj_type.damage_type) == BRUTE) ? WOUND_BLUNT : WOUND_BURN // sharpness is handled in the wound rolling wound_info_by_part -= hit_part // technically this only checks armor worn the moment that all the pellets resolve rather than as each one hits you, // but this isn't important enough to warrant all the extra loops of mostly redundant armor checks var/mob/living/carbon/hit_carbon = target - var/armor_factor = hit_carbon.getarmor(hit_part, initial(P.armor_flag)) + var/armor_factor = hit_carbon.getarmor(hit_part, initial(proj_type.armor_flag)) armor_factor = min(ARMOR_MAX_BLOCK, armor_factor) //cap damage reduction at 90% if(armor_factor > 0) - if(initial(P.weak_against_armour) && armor_factor >= 0) + if(initial(proj_type.weak_against_armour) && armor_factor >= 0) armor_factor *= ARMOR_WEAKENED_MULTIPLIER damage_dealt *= max(0, 1 - armor_factor*0.01) - hit_part.painless_wound_roll(wounding_type, damage_dealt, w_bonus, bw_bonus, initial(P.sharpness)) + hit_part.painless_wound_roll(wounding_type, damage_dealt, w_bonus, bw_bonus, initial(proj_type.sharpness)) var/limb_hit_text = "" if(hit_part) diff --git a/code/datums/components/splat.dm b/code/datums/components/splat.dm index d22613204bbbd..0ae407166abff 100644 --- a/code/datums/components/splat.dm +++ b/code/datums/components/splat.dm @@ -48,9 +48,9 @@ /datum/component/splat/UnregisterFromParent() UnregisterSignal(parent, list(COMSIG_MOVABLE_IMPACT, COMSIG_PROJECTILE_SELF_ON_HIT)) -/datum/component/splat/proc/projectile_splat(obj/projectile/source, atom/firer, atom/target, angle, hit_limb_zone, blocked) +/datum/component/splat/proc/projectile_splat(obj/projectile/source, atom/firer, atom/target, angle, hit_limb_zone, blocked, pierce_hit) SIGNAL_HANDLER - if(blocked != 100) + if(blocked != 100 && !pierce_hit) splat(source, target) /datum/component/splat/proc/throw_splat(atom/movable/source, atom/hit_atom, datum/thrownthing/throwing_datum, caught) diff --git a/code/datums/elements/embed.dm b/code/datums/elements/embed.dm index fbaf638bdd520..90787f8581798 100644 --- a/code/datums/elements/embed.dm +++ b/code/datums/elements/embed.dm @@ -98,12 +98,16 @@ * That's awful, and it'll limit us to drop-deletable shrapnels in the worry of stuff like * arrows and harpoons being embeddable even when not let loose by their weapons. */ -/datum/element/embed/proc/check_embed_projectile(obj/projectile/source, atom/movable/firer, atom/hit, angle, hit_zone, blocked) +/datum/element/embed/proc/check_embed_projectile(obj/projectile/source, atom/movable/firer, atom/hit, angle, hit_zone, blocked, pierce_hit) SIGNAL_HANDLER + if (pierce_hit) + return + if(!source.can_embed_into(hit) || blocked) Detach(source) return // we don't care + var/payload_type = source.shrapnel_type var/obj/item/payload = new payload_type(get_turf(hit)) payload.set_embed(source.get_embed()) diff --git a/code/datums/elements/projectile_drop.dm b/code/datums/elements/projectile_drop.dm index 36e189d6c3aeb..0b1992ef56097 100644 --- a/code/datums/elements/projectile_drop.dm +++ b/code/datums/elements/projectile_drop.dm @@ -28,8 +28,10 @@ //Just to be safe, knowing it won't be spawned multiple times. Detach(source) -/datum/element/projectile_drop/proc/spawn_drop_if_not_embeddable(obj/projectile/source, atom/movable/firer, atom/hit, angle, hit_zone) +/datum/element/projectile_drop/proc/spawn_drop_if_not_embeddable(obj/projectile/source, atom/movable/firer, atom/hit, angle, hit_zone, blocked, pierce_hit) SIGNAL_HANDLER + if (pierce_hit) + return if(source.can_embed_into(hit)) Detach(source) return diff --git a/code/datums/elements/selfknockback.dm b/code/datums/elements/selfknockback.dm index d330b30debc1a..416240b7cfd33 100644 --- a/code/datums/elements/selfknockback.dm +++ b/code/datums/elements/selfknockback.dm @@ -47,15 +47,15 @@ clamping the Knockback_Force value below. */ usertarget.throw_at(move_target, knockback_force, knockback_speed) usertarget.visible_message(span_warning("[usertarget] gets thrown back by the force of \the [I] impacting \the [attacktarget]!"), span_warning("The force of \the [I] impacting \the [attacktarget] sends you flying!")) -/datum/element/selfknockback/proc/Projectile_SelfKnockback(obj/projectile/P) +/datum/element/selfknockback/proc/Projectile_SelfKnockback(obj/projectile/proj) SIGNAL_HANDLER - if(!P.firer) + if(!proj.firer) return - var/knockback_force = Get_Knockback_Force(clamp(CEILING((P.damage / 10), 1), 1, 5)) + var/knockback_force = Get_Knockback_Force(clamp(CEILING((proj.damage / 10), 1), 1, 5)) var/knockback_speed = Get_Knockback_Speed(clamp(knockback_force, 1, 5)) - var/atom/movable/knockback_target = P.firer - var/move_target = get_edge_target_turf(knockback_target, angle2dir(P.original_angle+180)) + var/atom/movable/knockback_target = proj.firer + var/move_target = get_edge_target_turf(knockback_target, angle2dir(proj.original_angle+180)) knockback_target.throw_at(move_target, knockback_force, knockback_speed) diff --git a/code/datums/mutations/sight.dm b/code/datums/mutations/sight.dm index d3627167cb507..d32c35a6239c5 100644 --- a/code/datums/mutations/sight.dm +++ b/code/datums/mutations/sight.dm @@ -171,7 +171,7 @@ var/obj/projectile/beam/laser/laser_eyes/LE = new(source.loc) LE.firer = source LE.def_zone = ran_zone(source.zone_selected) - LE.preparePixelProjectile(target, source, modifiers) + LE.aim_projectile(target, source, modifiers) INVOKE_ASYNC(LE, TYPE_PROC_REF(/obj/projectile, fire)) playsound(source, 'sound/items/weapons/taser2.ogg', 75, TRUE) diff --git a/code/datums/position_point_vector.dm b/code/datums/position_point_vector.dm index b8b697c053bca..02281e2437a08 100644 --- a/code/datums/position_point_vector.dm +++ b/code/datums/position_point_vector.dm @@ -1,15 +1,11 @@ /proc/point_midpoint_points(datum/point/a, datum/point/b) //Obviously will not support multiZ calculations! Same for the two below. - var/datum/point/P = new - P.x = a.x + (b.x - a.x) * 0.5 - P.y = a.y + (b.y - a.y) * 0.5 - P.z = a.z - return P + return new /datum/point(_z = a.z, _pixel_x = (a.return_px() + b.return_px()) * 0.5, _pixel_y = (a.return_py() + b.return_py()) * 0.5) /proc/pixel_length_between_points(datum/point/a, datum/point/b) - return sqrt(((b.x - a.x) ** 2) + ((b.y - a.y) ** 2)) + return sqrt(((b.return_px() - a.return_px()) ** 2) + ((b.return_py() - a.return_py()) ** 2)) /proc/angle_between_points(datum/point/a, datum/point/b) - return ATAN2((b.y - a.y), (b.x - a.x)) + return ATAN2(b.return_py() - a.return_py(), b.return_px() - a.return_px()) /// For positions with map x/y/z and pixel x/y so you don't have to return lists. Could use addition/subtraction in the future I guess. /datum/position @@ -29,8 +25,8 @@ _x = T.x _y = T.y _z = T.z - _pixel_x = P.return_px() - _pixel_y = P.return_py() + _pixel_x = P.pixel_x + _pixel_y = P.pixel_y else if(isatom(_x)) var/atom/A = _x _x = A.x @@ -61,6 +57,8 @@ var/x = 0 var/y = 0 var/z = 0 + var/pixel_x = 0 + var/pixel_y = 0 /datum/point/proc/valid() return x && y && z @@ -89,143 +87,88 @@ _pixel_y = A.pixel_y initialize_location(_x, _y, _z, _pixel_x, _pixel_y) -/datum/point/proc/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0) +/datum/point/proc/initialize_location(tile_x, tile_y, tile_z, p_x, p_y) if(!isnull(tile_x)) - x = ((tile_x - 1) * ICON_SIZE_X) + ICON_SIZE_X * 0.5 + p_x + 1 + x = tile_x if(!isnull(tile_y)) - y = ((tile_y - 1) * ICON_SIZE_Y) + ICON_SIZE_Y * 0.5 + p_y + 1 + y = tile_y if(!isnull(tile_z)) z = tile_z + if(!isnull(p_x)) + var/x_offset = SIGNED_FLOOR_DIVISION(p_x, ICON_SIZE_X) + x += x_offset + pixel_x = p_x - x_offset * ICON_SIZE_X + if(!isnull(p_y)) + var/y_offset = SIGNED_FLOOR_DIVISION(p_y, ICON_SIZE_Y) + y += y_offset + pixel_y = p_y - y_offset * ICON_SIZE_Y + +/datum/point/proc/increment(p_x, p_y) + var/x_offset = SIGNED_FLOOR_DIVISION(p_x, ICON_SIZE_X) + x += x_offset + pixel_x += p_x - x_offset * ICON_SIZE_X + var/y_offset = SIGNED_FLOOR_DIVISION(p_y, ICON_SIZE_Y) + y += y_offset + pixel_y += p_y - y_offset * ICON_SIZE_Y /datum/point/proc/debug_out() var/turf/T = return_turf() - return "[text_ref(src)] aX [x] aY [y] aZ [z] pX [return_px()] pY [return_py()] mX [T.x] mY [T.y] mZ [T.z]" + return "[text_ref(src)] aX [x] aY [y] aZ [z] pX [pixel_x] pY [pixel_y] mX [T.x] mY [T.y] mZ [T.z]" /datum/point/proc/move_atom_to_src(atom/movable/AM) AM.forceMove(return_turf()) - AM.pixel_x = return_px() - AM.pixel_y = return_py() + AM.pixel_x = pixel_x + AM.pixel_y = pixel_y /datum/point/proc/return_turf() - return locate(CEILING(x / ICON_SIZE_X, 1), CEILING(y / ICON_SIZE_Y, 1), z) + return locate(x + SIGNED_FLOOR_DIVISION(pixel_x, ICON_SIZE_X), y + SIGNED_FLOOR_DIVISION(pixel_y, ICON_SIZE_Y), z) /datum/point/proc/return_coordinates() //[turf_x, turf_y, z] - return list(CEILING(x / ICON_SIZE_X, 1), CEILING(y / ICON_SIZE_Y, 1), z) + return list(x + SIGNED_FLOOR_DIVISION(pixel_x, ICON_SIZE_X), y + SIGNED_FLOOR_DIVISION(pixel_y, ICON_SIZE_Y), z) /datum/point/proc/return_position() return new /datum/position(src) /datum/point/proc/return_px() - return MODULUS(x, ICON_SIZE_X) - (ICON_SIZE_X/2) - 1 + return x * ICON_SIZE_X + pixel_x /datum/point/proc/return_py() - return MODULUS(y, ICON_SIZE_Y) - (ICON_SIZE_Y/2) - 1 + return y * ICON_SIZE_Y + pixel_y -/datum/point/vector - /// Pixels per iteration - var/speed = ICON_SIZE_ALL - var/iteration = 0 +/datum/vector + var/magnitude = 1 var/angle = 0 - /// Calculated x movement amounts to prevent having to do trig every step. - var/mpx = 0 - /// Calculated y movement amounts to prevent having to do trig every step. - var/mpy = 0 - var/starting_x = 0 //just like before, pixels from EDGE of map! This is set in initialize_location(). - var/starting_y = 0 - var/starting_z = 0 - -/datum/point/vector/New(_x, _y, _z, _pixel_x = 0, _pixel_y = 0, _angle, _speed, initial_increment = 0) - ..() - initialize_trajectory(_speed, _angle) - if(initial_increment) - increment(initial_increment) - -/datum/point/vector/initialize_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0) - . = ..() - starting_x = x - starting_y = y - starting_z = z + // Calculated coordinate amounts to prevent having to do trig every step. + var/pixel_x = 0 + var/pixel_y = 0 + var/total_x = 0 + var/total_y = 0 -/// Same effect as initiliaze_location, but without setting the starting_x/y/z -/datum/point/vector/proc/set_location(tile_x, tile_y, tile_z, p_x = 0, p_y = 0) - if(!isnull(tile_x)) - x = ((tile_x - 1) * ICON_SIZE_X) + ICON_SIZE_X * 0.5 + p_x + 1 - if(!isnull(tile_y)) - y = ((tile_y - 1) * ICON_SIZE_Y) + ICON_SIZE_Y * 0.5 + p_y + 1 - if(!isnull(tile_z)) - z = tile_z +/datum/vector/New(new_magnitude, new_angle) + . = ..() + initialize_trajectory(new_magnitude, new_angle) -/datum/point/vector/copy_to(datum/point/vector/v = new) - ..(v) - v.speed = speed - v.iteration = iteration - v.angle = angle - v.mpx = mpx - v.mpy = mpy - v.starting_x = starting_x - v.starting_y = starting_y - v.starting_z = starting_z - return v - -/datum/point/vector/proc/initialize_trajectory(pixel_speed, new_angle) - if(!isnull(pixel_speed)) - speed = pixel_speed +/datum/vector/proc/initialize_trajectory(new_magnitude, new_angle) + if(!isnull(new_magnitude)) + magnitude = new_magnitude set_angle(new_angle) /// Calculations use "byond angle" where north is 0 instead of 90, and south is 180 instead of 270. -/datum/point/vector/proc/set_angle(new_angle) +/datum/vector/proc/set_angle(new_angle) if(isnull(angle)) return angle = new_angle update_offsets() -/datum/point/vector/proc/update_offsets() - mpx = sin(angle) * speed - mpy = cos(angle) * speed - -/datum/point/vector/proc/set_speed(new_speed) - if(isnull(new_speed) || speed == new_speed) - return - speed = new_speed - update_offsets() +/datum/vector/proc/update_offsets() + pixel_x = sin(angle) + pixel_y = cos(angle) + total_x = pixel_x * magnitude + total_y = pixel_y * magnitude -/datum/point/vector/proc/increment(multiplier = 1) - iteration++ - x += mpx * (multiplier) - y += mpy * (multiplier) - -/datum/point/vector/proc/return_vector_after_increments(amount = 7, multiplier = 1, force_simulate = FALSE) - var/datum/point/vector/v = copy_to() - if(force_simulate) - for(var/i in 1 to amount) - v.increment(multiplier) - else - v.increment(multiplier * amount) - return v - -/datum/point/vector/proc/on_z_change() - return - -/datum/point/vector/processed //pixel_speed is per decisecond. - var/last_process = 0 - var/last_move = 0 - var/paused = FALSE - -/datum/point/vector/processed/Destroy() - STOP_PROCESSING(SSprojectiles, src) - return ..() - -/datum/point/vector/processed/proc/start() - last_process = world.time - last_move = world.time - START_PROCESSING(SSprojectiles, src) - -/datum/point/vector/processed/process() - if(paused) - last_move += world.time - last_process - last_process = world.time +/datum/vector/proc/set_speed(new_magnitude) + if(isnull(new_magnitude) || magnitude == new_magnitude) return - var/needed_time = world.time - last_move - last_process = world.time - last_move = world.time - increment(needed_time / SSprojectiles.wait) + magnitude = new_magnitude + total_x = pixel_x * magnitude + total_y = pixel_y * magnitude diff --git a/code/datums/proximity_monitor/fields/timestop.dm b/code/datums/proximity_monitor/fields/timestop.dm index 3b8001426a03c..8411e2c3bb3f1 100644 --- a/code/datums/proximity_monitor/fields/timestop.dm +++ b/code/datums/proximity_monitor/fields/timestop.dm @@ -204,11 +204,11 @@ freeze_atom(i) freeze_turf(target) -/datum/proximity_monitor/advanced/timestop/proc/freeze_projectile(obj/projectile/P) - P.paused = TRUE +/datum/proximity_monitor/advanced/timestop/proc/freeze_projectile(obj/projectile/proj) + proj.paused = TRUE -/datum/proximity_monitor/advanced/timestop/proc/unfreeze_projectile(obj/projectile/P) - P.paused = FALSE +/datum/proximity_monitor/advanced/timestop/proc/unfreeze_projectile(obj/projectile/proj) + proj.paused = FALSE /datum/proximity_monitor/advanced/timestop/proc/freeze_mob(mob/living/victim) frozen_mobs += victim diff --git a/code/datums/status_effects/debuffs/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm index 25c133e46d14a..02fe573ef36e9 100644 --- a/code/datums/status_effects/debuffs/debuffs.dm +++ b/code/datums/status_effects/debuffs/debuffs.dm @@ -512,7 +512,7 @@ new/obj/effect/temp_visual/dir_setting/curse/grasp_portal(spawn_turf, owner.dir) playsound(spawn_turf, 'sound/effects/curse/curse2.ogg', 80, TRUE, -1) var/obj/projectile/curse_hand/C = new (spawn_turf) - C.preparePixelProjectile(owner, spawn_turf) + C.aim_projectile(owner, spawn_turf) C.fire() /obj/effect/temp_visual/curse diff --git a/code/game/machinery/dna_infuser/infuser_actions.dm b/code/game/machinery/dna_infuser/infuser_actions.dm index 48f92b9ae6850..466bed9e17efe 100644 --- a/code/game/machinery/dna_infuser/infuser_actions.dm +++ b/code/game/machinery/dna_infuser/infuser_actions.dm @@ -50,7 +50,7 @@ span_bold("You spit ink."), ) var/obj/projectile/ink_spit/ink = new /obj/projectile/ink_spit(caller.loc) - ink.preparePixelProjectile(target, caller, modifiers) + ink.aim_projectile(target, caller, modifiers) ink.firer = caller ink.fire() playsound(caller, 'sound/items/weapons/pierce.ogg', 20, TRUE, -1) diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm index e64e01bbcf246..32e29a2e5dd0a 100644 --- a/code/game/machinery/porta_turret/portable_turret.dm +++ b/code/game/machinery/porta_turret/portable_turret.dm @@ -655,7 +655,7 @@ DEFINE_BITFIELD(turret_flags, list( //Shooting Code: - A.preparePixelProjectile(target, T) + A.aim_projectile(target, T) A.firer = src A.fired_from = src if(ignore_faction) diff --git a/code/game/objects/effects/portals.dm b/code/game/objects/effects/portals.dm index 21a51ba9bcb42..dbb5ad3d8bdba 100644 --- a/code/game/objects/effects/portals.dm +++ b/code/game/objects/effects/portals.dm @@ -146,6 +146,7 @@ var/turf/real_target = get_link_target_turf() if(!istype(real_target)) return FALSE + if(!force && (!ismecha(moving) && !isprojectile(moving) && moving.anchored && !allow_anchored)) return var/no_effect = FALSE @@ -156,8 +157,8 @@ var/turf/start_turf = get_turf(moving) if(do_teleport(moving, real_target, innate_accuracy_penalty, no_effects = no_effect, channel = teleport_channel, forced = force_teleport)) if(isprojectile(moving)) - var/obj/projectile/P = moving - P.ignore_source_check = TRUE + var/obj/projectile/proj = moving + proj.ignore_source_check = TRUE new /obj/effect/temp_visual/portal_animation(start_turf, src, moving) playsound(start_turf, SFX_PORTAL_ENTER, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) playsound(real_target, SFX_PORTAL_ENTER, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) diff --git a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm index 2d112aa205909..e9e043a598173 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/projectile_effects.dm @@ -36,26 +36,19 @@ apply_vars(angle_override, p_x, p_y, color_override, scaling) return ..() -/obj/effect/projectile/proc/apply_vars(angle_override, p_x = 0, p_y = 0, color_override, scaling = 1, atom/new_loc, increment = 0) - var/mutable_appearance/look = new(src) - SET_PLANE_EXPLICIT(look, plane, new_loc || src) - look.pixel_x = p_x - look.pixel_y = p_y +/obj/effect/projectile/proc/apply_vars(angle_override, p_x = 0, p_y = 0, color_override, scaling = 1, increment = 0) + pixel_x = p_x + pixel_y = p_y if(color_override) - look.color = color_override - appearance = look - scale_to(1,scaling, FALSE) + color = color_override + scale_to(1, scaling, FALSE) turn_to(angle_override, FALSE) - if(!isnull(new_loc)) //If you want to null it just delete it... - forceMove(new_loc) for(var/i in 1 to increment) pixel_x += round((sin(angle_override)+16*sin(angle_override)*2), 1) pixel_y += round((cos(angle_override)+16*cos(angle_override)*2), 1) -/obj/effect/projectile_lighting - var/owner +/obj/effect/abstract/projectile_lighting -/obj/effect/projectile_lighting/Initialize(mapload, color, range, intensity, owner_key) +/obj/effect/abstract/projectile_lighting/Initialize(mapload, color, range, intensity) . = ..() set_light(range, intensity, color) - owner = owner_key diff --git a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm index 8c4ea163232e1..e2323ce4413f9 100644 --- a/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm +++ b/code/game/objects/effects/temporary_visuals/projectiles/tracer.dm @@ -1,25 +1,3 @@ -/proc/generate_tracer_between_points(datum/point/starting, datum/point/ending, beam_type, color, qdel_in = 5, light_range = 2, light_color_override, light_intensity = 1, instance_key) //Do not pass z-crossing points as that will not be properly (and likely will never be properly until it's absolutely needed) supported! - if(!istype(starting) || !istype(ending) || !ispath(beam_type)) - return - var/datum/point/midpoint = point_midpoint_points(starting, ending) - var/obj/effect/projectile/tracer/PB = new beam_type - if(isnull(light_color_override)) - light_color_override = color - PB.apply_vars(angle_between_points(starting, ending), midpoint.return_px(), midpoint.return_py(), color, pixel_length_between_points(starting, ending) / ICON_SIZE_ALL, midpoint.return_turf(), 0) - . = PB - if(light_range > 0 && light_intensity > 0) - var/list/turf/line = get_line(starting.return_turf(), ending.return_turf()) - tracing_line: - for(var/i in line) - var/turf/T = i - for(var/obj/effect/projectile_lighting/PL in T) - if(PL.owner == instance_key) - continue tracing_line - QDEL_IN(new /obj/effect/projectile_lighting(T, light_color_override, light_range, light_intensity, instance_key), qdel_in > 0? qdel_in : 5) - line = null - if(qdel_in) - QDEL_IN(PB, qdel_in) - /obj/effect/projectile/tracer name = "beam" icon = 'icons/obj/weapons/guns/projectiles_tracer.dmi' diff --git a/code/game/objects/items/devices/traitordevices.dm b/code/game/objects/items/devices/traitordevices.dm index c1ed690a3143c..d8b7c1999aea4 100644 --- a/code/game/objects/items/devices/traitordevices.dm +++ b/code/game/objects/items/devices/traitordevices.dm @@ -530,4 +530,4 @@ effective or pretty fucking useless. /obj/projectile/bullet/toolbox_turret damage = 10 - speed = 0.6 + speed = 1.6 diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm index 005bcff76771a..880c73ceced39 100644 --- a/code/game/objects/items/hand_items.dm +++ b/code/game/objects/items/hand_items.dm @@ -493,7 +493,7 @@ blown_kiss.fired_from = user blown_kiss.firer = user // don't hit ourself that would be really annoying blown_kiss.impacted = list(WEAKREF(user) = TRUE) // just to make sure we don't hit the wearer - blown_kiss.preparePixelProjectile(target, user) + blown_kiss.aim_projectile(target, user) blown_kiss.fire() qdel(src) return ITEM_INTERACT_SUCCESS @@ -520,7 +520,7 @@ blown_kiss.fired_from = offerer blown_kiss.firer = offerer // don't hit ourself that would be really annoying blown_kiss.impacted = list(WEAKREF(offerer) = TRUE) // just to make sure we don't hit the wearer - blown_kiss.preparePixelProjectile(taker, offerer) + blown_kiss.aim_projectile(taker, offerer) blown_kiss.suppressed = SUPPRESSED_VERY // this also means it's a direct offer blown_kiss.fire() qdel(src) @@ -545,7 +545,7 @@ hitsound = 'sound/effects/emotes/kiss.ogg' hitsound_wall = 'sound/effects/emotes/kiss.ogg' pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE - speed = 1.6 + speed = 0.66 damage_type = BRUTE damage = 0 // love can't actually hurt you armour_penetration = 100 // but if it could, it would cut through even the thickest plate diff --git a/code/game/objects/items/robot/items/food.dm b/code/game/objects/items/robot/items/food.dm index 3dd15b508cc97..6eba8e8fa760b 100644 --- a/code/game/objects/items/robot/items/food.dm +++ b/code/game/objects/items/robot/items/food.dm @@ -199,7 +199,7 @@ desc = "Oh noes! A fast-moving gumball!" icon_state = "gumball" damage = 0 - speed = 0.5 + speed = 2 embed_type = null /obj/projectile/bullet/gumball/Initialize(mapload) @@ -232,7 +232,7 @@ desc = "Oh noes! A fast-moving lollipop!" icon_state = "lollipop_1" damage = 0 - speed = 0.5 + speed = 2 embed_type = null var/head_color diff --git a/code/game/objects/items/robot/items/tools.dm b/code/game/objects/items/robot/items/tools.dm index 708563cb751d5..6e16388b4b149 100644 --- a/code/game/objects/items/robot/items/tools.dm +++ b/code/game/objects/items/robot/items/tools.dm @@ -36,9 +36,9 @@ var/projectile_damage_tick_ecost_coefficient = 10 /** * Speed coefficient - * Higher the coefficient slower the projectile. + * Higher the coefficient faster the projectile. */ - var/projectile_speed_coefficient = 1.5 + var/projectile_speed_coefficient = 0.66 /// Energy cost per tracked projectile per second var/projectile_tick_speed_ecost = 75 /// Projectile sent out by the dampener @@ -170,8 +170,8 @@ SIGNAL_HANDLER tracked -= projectile - projectile.damage *= (1 / projectile_damage_coefficient) - projectile.speed *= (1 / projectile_speed_coefficient) + projectile.damage /= projectile_damage_coefficient + projectile.speed /= projectile_speed_coefficient projectile.cut_overlay(projectile_effect) //bare minimum omni-toolset for modularity diff --git a/code/game/objects/items/shooting_range.dm b/code/game/objects/items/shooting_range.dm index ff08886e7d3ed..0930de71c0e0c 100644 --- a/code/game/objects/items/shooting_range.dm +++ b/code/game/objects/items/shooting_range.dm @@ -82,6 +82,6 @@ desc = "A shooting target that looks like a useless clown." max_integrity = 2000 -/obj/item/target/clown/bullet_act(obj/projectile/P) +/obj/item/target/clown/bullet_act(obj/projectile/proj) . = ..() playsound(src, 'sound/items/bikehorn.ogg', 50, TRUE) diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 7c8173fae3653..40713320eeb10 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -206,10 +206,10 @@ GLOBAL_LIST_EMPTY(objects_by_id_tag) SHOULD_CALL_PARENT(FALSE) CRASH("Unimplemented.") -/obj/handle_ricochet(obj/projectile/P) +/obj/handle_ricochet(obj/projectile/proj) . = ..() if(. && receive_ricochet_damage_coeff) - take_damage(P.damage * receive_ricochet_damage_coeff, P.damage_type, P.armor_flag, 0, REVERSE_DIR(P.dir), P.armour_penetration) // pass along receive_ricochet_damage_coeff damage to the structure for the ricochet + take_damage(proj.damage * receive_ricochet_damage_coeff, proj.damage_type, proj.armor_flag, 0, REVERSE_DIR(proj.dir), proj.armour_penetration) // pass along receive_ricochet_damage_coeff damage to the structure for the ricochet /// Handles exposing an object to reagents. /obj/expose_reagents(list/reagents, datum/reagents/source, methods=TOUCH, volume_modifier=1, show_message=TRUE) diff --git a/code/game/objects/structures/deployable_turret.dm b/code/game/objects/structures/deployable_turret.dm index e9162294c8f42..a02481d8ef73d 100644 --- a/code/game/objects/structures/deployable_turret.dm +++ b/code/game/objects/structures/deployable_turret.dm @@ -195,7 +195,7 @@ target = target_turf var/obj/projectile/projectile_to_fire = new projectile_type(targets_from) playsound(src, firesound, 75, TRUE) - projectile_to_fire.preparePixelProjectile(target, targets_from) + projectile_to_fire.aim_projectile(target, targets_from) projectile_to_fire.firer = user projectile_to_fire.fired_from = src projectile_to_fire.fire() diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index 5fd3b2b435a4a..b785c48ceb37f 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -248,13 +248,13 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) to_chat(user, span_warning("A chill runs down your spine as [src] shatters...")) user.AddComponent(/datum/component/omen, incidents_left = 7) -/obj/structure/mirror/bullet_act(obj/projectile/P) - if(broken || !isliving(P.firer) || !P.damage) +/obj/structure/mirror/bullet_act(obj/projectile/proj) + if(broken || !isliving(proj.firer) || !proj.damage) return ..() . = ..() if(broken) // breaking a mirror truly gets you bad luck! - var/mob/living/unlucky_dude = P.firer + var/mob/living/unlucky_dude = proj.firer to_chat(unlucky_dude, span_warning("A chill runs down your spine as [src] shatters...")) unlucky_dude.AddComponent(/datum/component/omen, incidents_left = 7) diff --git a/code/game/objects/structures/reflector.dm b/code/game/objects/structures/reflector.dm index 532ecebde0e5e..d470008bb9cad 100644 --- a/code/game/objects/structures/reflector.dm +++ b/code/game/objects/structures/reflector.dm @@ -75,8 +75,8 @@ /obj/structure/reflector/proc/auto_reflect(obj/projectile/proj, pdir, turf/ploc, pangle) proj.ignore_source_check = TRUE - proj.range = proj.decayedRange - proj.decayedRange = max(proj.decayedRange--, 0) + proj.range = proj.maximum_range + proj.maximum_range = max(proj.maximum_range--, 0) return BULLET_ACT_FORCE_PIERCE /obj/structure/reflector/tool_act(mob/living/user, obj/item/tool, list/modifiers) @@ -196,7 +196,7 @@ if(abs(incidence) > 90 && abs(incidence) < 270) return FALSE var/new_angle = SIMPLIFY_DEGREES(rotation_angle + incidence) - proj.set_angle_centered(new_angle) + proj.set_angle_centered(loc, new_angle) return ..() //DOUBLE @@ -220,7 +220,8 @@ /obj/structure/reflector/double/auto_reflect(obj/projectile/proj, pdir, turf/ploc, pangle) var/incidence = GET_ANGLE_OF_INCIDENCE(rotation_angle, (proj.angle + 180)) var/new_angle = SIMPLIFY_DEGREES(rotation_angle + incidence) - proj.set_angle_centered(new_angle) + proj.forceMove(loc) + proj.set_angle_centered(loc, new_angle) return ..() //BOX @@ -241,8 +242,8 @@ admin = TRUE anchored = TRUE -/obj/structure/reflector/box/auto_reflect(obj/projectile/P) - P.set_angle_centered(rotation_angle) +/obj/structure/reflector/box/auto_reflect(obj/projectile/proj) + proj.set_angle_centered(loc, rotation_angle) return ..() /obj/structure/reflector/ex_act() diff --git a/code/modules/admin/verbs/adminfun.dm b/code/modules/admin/verbs/adminfun.dm index 93b398397c284..984ced4c0bf7e 100644 --- a/code/modules/admin/verbs/adminfun.dm +++ b/code/modules/admin/verbs/adminfun.dm @@ -194,7 +194,7 @@ ADMIN_VERB_AND_CONTEXT_MENU(admin_smite, R_ADMIN|R_FUN, "Smite", "Smite a player divine_wrath.original = target divine_wrath.def_zone = body_zone divine_wrath.spread = 0 - divine_wrath.preparePixelProjectile(target, source_turf) + divine_wrath.aim_projectile(target, source_turf) divine_wrath.fire() /client/proc/punish_log(whom, punishment) diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index 150c61af29af6..c6bfa9cca425a 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -1426,7 +1426,7 @@ Striking a noncultist, however, will tear their flesh."} qdel(src) return FALSE var/obj/projectile/projectile = hitby - if(projectile.reflectable & REFLECT_NORMAL) + if(projectile.reflectable) return FALSE //To avoid reflection chance double-dipping with block chance . = ..() if(.) diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm index 9c29d15ba67c6..6fec632e5102f 100644 --- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm +++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_buff.dm @@ -117,7 +117,7 @@ new /obj/effect/temp_visual/dir_setting/curse/grasp_portal(spawn_turf, victim.dir) playsound(spawn_turf, 'sound/effects/curse/curse2.ogg', 80, TRUE, -1) var/obj/projectile/curse_hand/hel/hand = new (spawn_turf) - hand.preparePixelProjectile(victim, spawn_turf) + hand.aim_projectile(victim, spawn_turf) if (QDELETED(hand)) // safety check if above fails - above has a stack trace if it does fail return hand.fire() diff --git a/code/modules/antagonists/heretic/magic/furious_steel.dm b/code/modules/antagonists/heretic/magic/furious_steel.dm index dceb9023bdbcc..d72c7fc7c04e9 100644 --- a/code/modules/antagonists/heretic/magic/furious_steel.dm +++ b/code/modules/antagonists/heretic/magic/furious_steel.dm @@ -102,7 +102,7 @@ name = "blade" icon = 'icons/effects/eldritch.dmi' icon_state = "dio_knife" - speed = 2 + speed = 0.5 damage = 25 armour_penetration = 100 sharpness = SHARP_EDGED diff --git a/code/modules/antagonists/heretic/magic/moon_parade.dm b/code/modules/antagonists/heretic/magic/moon_parade.dm index 6d18c2113f470..49f8c42808219 100644 --- a/code/modules/antagonists/heretic/magic/moon_parade.dm +++ b/code/modules/antagonists/heretic/magic/moon_parade.dm @@ -26,12 +26,11 @@ icon_state = "lunar_parade" damage = 0 damage_type = BURN - speed = 1 + speed = 0.2 range = 75 ricochets_max = 40 ricochet_chance = 500 ricochet_incidence_leeway = 0 - pixel_speed_multiplier = 0.2 projectile_piercing = PASSMOB|PASSVEHICLE ///looping sound datum for our projectile. var/datum/looping_sound/moon_parade/soundloop diff --git a/code/modules/antagonists/heretic/magic/rust_wave.dm b/code/modules/antagonists/heretic/magic/rust_wave.dm index b109a068042b9..1464829aa4c6a 100644 --- a/code/modules/antagonists/heretic/magic/rust_wave.dm +++ b/code/modules/antagonists/heretic/magic/rust_wave.dm @@ -119,4 +119,4 @@ /obj/projectile/magic/aoe/rust_wave/short range = 7 - speed = 2 + speed = 0.5 diff --git a/code/modules/antagonists/heretic/magic/star_blast.dm b/code/modules/antagonists/heretic/magic/star_blast.dm index e6f7a96811e40..ad36cf9186ace 100644 --- a/code/modules/antagonists/heretic/magic/star_blast.dm +++ b/code/modules/antagonists/heretic/magic/star_blast.dm @@ -24,10 +24,9 @@ icon_state = "star_ball" damage = 20 damage_type = BURN - speed = 1 + speed = 0.2 range = 100 knockdown = 4 SECONDS - pixel_speed_multiplier = 0.2 /// Effect for when the ball hits something var/obj/effect/explosion_effect = /obj/effect/temp_visual/cosmic_explosion /// The range at which people will get marked with a star mark. diff --git a/code/modules/clothing/shoes/gunboots.dm b/code/modules/clothing/shoes/gunboots.dm index de74703d449ed..6c9da817146c7 100644 --- a/code/modules/clothing/shoes/gunboots.dm +++ b/code/modules/clothing/shoes/gunboots.dm @@ -61,7 +61,7 @@ shot.firer = wearer // don't hit ourself that would be really annoying shot.impacted = list(WEAKREF(wearer) = TRUE) shot.def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) // they're fired from boots after all - shot.preparePixelProjectile(target, wearer) + shot.aim_projectile(target, wearer) if(!shot.suppressed) wearer.visible_message(span_danger("[wearer]'s [name] fires \a [shot]!"), "", blind_message = span_hear("You hear a gunshot!"), vision_distance=COMBAT_MESSAGE_RANGE) shot.fire() diff --git a/code/modules/experisci/experiment/physical_experiments.dm b/code/modules/experisci/experiment/physical_experiments.dm index 6b88e19e4a3b6..60303814e44ef 100644 --- a/code/modules/experisci/experiment/physical_experiments.dm +++ b/code/modules/experisci/experiment/physical_experiments.dm @@ -21,9 +21,9 @@ /datum/experiment/physical/meat_wall_explosion/check_progress() . += EXPERIMENT_PROG_BOOL("Fire an emitter at a tracked meat wall", is_complete()) -/datum/experiment/physical/meat_wall_explosion/proc/check_experiment(datum/source, obj/projectile/Proj) +/datum/experiment/physical/meat_wall_explosion/proc/check_experiment(datum/source, obj/projectile/proj) SIGNAL_HANDLER - if(istype(Proj, /obj/projectile/beam/emitter)) + if(istype(proj, /obj/projectile/beam/emitter)) finish_experiment(linked_experiment_handler) /datum/experiment/physical/meat_wall_explosion/finish_experiment(datum/component/experiment_handler/experiment_handler) diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm index f554a475ad491..08482ac6be59b 100644 --- a/code/modules/fishing/fishing_rod.dm +++ b/code/modules/fishing/fishing_rod.dm @@ -425,13 +425,13 @@ casting = TRUE var/obj/projectile/fishing_cast/cast_projectile = new(get_turf(src)) cast_projectile.range = get_cast_range(user) - cast_projectile.decayedRange = get_cast_range(user) + cast_projectile.maximum_range = get_cast_range(user) cast_projectile.owner = src cast_projectile.original = target cast_projectile.fired_from = src cast_projectile.firer = user cast_projectile.impacted = list(WEAKREF(user) = TRUE) - cast_projectile.preparePixelProjectile(target, user) + cast_projectile.aim_projectile(target, user) cast_projectile.fire() COOLDOWN_START(src, casting_cd, 1 SECONDS) diff --git a/code/modules/hallucination/stray_bullet.dm b/code/modules/hallucination/stray_bullet.dm index 11f88c8c964ed..b670cd869e9b2 100644 --- a/code/modules/hallucination/stray_bullet.dm +++ b/code/modules/hallucination/stray_bullet.dm @@ -16,7 +16,7 @@ var/obj/projectile/hallucination/fake_projectile = new fake_type(start, src) - fake_projectile.preparePixelProjectile(hallucinator, start) + fake_projectile.aim_projectile(hallucinator, start) fake_projectile.fire() QDEL_IN(src, 10 SECONDS) // Should clean up the projectile if it somehow gets stuck. @@ -32,7 +32,6 @@ ricochets_max = 0 ricochet_chance = 0 damage = 0 - projectile_type = /obj/projectile/hallucination log_override = TRUE do_not_log = TRUE /// Our parent hallucination that's created us @@ -213,7 +212,7 @@ ricochets_max = 50 ricochet_chance = 80 - reflectable = REFLECT_NORMAL // No idea if this works + reflectable = TRUE /obj/projectile/hallucination/laser/apply_effect_to_hallucinator(mob/living/afflicted) afflicted.adjustStaminaLoss(20) @@ -259,7 +258,7 @@ ricochets_max = 50 ricochet_chance = 80 - reflectable = REFLECT_NORMAL // No idea if this works + reflectable = TRUE /obj/projectile/hallucination/disabler/apply_effect_to_hallucinator(mob/living/afflicted) afflicted.adjustStaminaLoss(30) diff --git a/code/modules/mapfluff/ruins/lavalandruin_code/watcher_grave.dm b/code/modules/mapfluff/ruins/lavalandruin_code/watcher_grave.dm index 6d3ef03c3f028..d25581bd3f3a1 100644 --- a/code/modules/mapfluff/ruins/lavalandruin_code/watcher_grave.dm +++ b/code/modules/mapfluff/ruins/lavalandruin_code/watcher_grave.dm @@ -254,8 +254,7 @@ icon_state = "ice_2" damage = 10 damage_type = BRUTE // Mining mobs don't take a lot of burn damage so we'll pretend - speed = 1 - pixel_speed_multiplier = 0.5 + speed = 0.5 /obj/projectile/baby_watcher_blast/Initialize(mapload) . = ..() diff --git a/code/modules/mining/equipment/grapple_gun.dm b/code/modules/mining/equipment/grapple_gun.dm index fa7f0b0b73570..99144d84867ce 100644 --- a/code/modules/mining/equipment/grapple_gun.dm +++ b/code/modules/mining/equipment/grapple_gun.dm @@ -179,7 +179,7 @@ icon_state = "grapple_hook" damage = 0 range = 9 - speed = 0.1 + speed = 10 can_hit_turfs = TRUE hitsound = 'sound/items/weapons/zipline_hit.ogg' diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm index 1a83ad0f30330..c5bdfa9daa239 100644 --- a/code/modules/mining/equipment/kinetic_crusher.dm +++ b/code/modules/mining/equipment/kinetic_crusher.dm @@ -157,7 +157,7 @@ destabilizer.icon_state = "[projectile_icon]" for(var/obj/item/crusher_trophy/attached_trophy as anything in trophies) attached_trophy.on_projectile_fire(destabilizer, user) - destabilizer.preparePixelProjectile(target, user, modifiers) + destabilizer.aim_projectile(target, user, modifiers) destabilizer.firer = user playsound(user, 'sound/items/weapons/plasma_cutter.ogg', 100, TRUE) destabilizer.fire() @@ -423,7 +423,7 @@ marker.name = "deadly [marker.name]" marker.icon_state = "chronobolt" marker.damage = bonus_value - marker.speed = 2 + marker.speed = 0.5 deadly_shot = FALSE /obj/item/crusher_trophy/blaster_tubes/on_mark_detonation(mob/living/target, mob/living/user) diff --git a/code/modules/mining/lavaland/megafauna_loot.dm b/code/modules/mining/lavaland/megafauna_loot.dm index d355014c6ad70..3b33119aa24e7 100644 --- a/code/modules/mining/lavaland/megafauna_loot.dm +++ b/code/modules/mining/lavaland/megafauna_loot.dm @@ -553,7 +553,7 @@ return COOLDOWN_START(src, attack_cooldown, 3 SECONDS) var/obj/projectile/projectile = new /obj/projectile/soulscythe(get_turf(src)) - projectile.preparePixelProjectile(attacked_atom, src) + projectile.aim_projectile(attacked_atom, src) projectile.firer = src projectile.fire(null, attacked_atom) visible_message(span_danger("[src] fires at [attacked_atom]!"), span_notice("You fire at [attacked_atom]!")) diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm index 58b7b437d7c54..bb6215705160d 100644 --- a/code/modules/mining/lavaland/necropolis_chests.dm +++ b/code/modules/mining/lavaland/necropolis_chests.dm @@ -145,8 +145,8 @@ /obj/structure/closet/crate/necropolis/colossus name = "colossus chest" -/obj/structure/closet/crate/necropolis/colossus/bullet_act(obj/projectile/P) - if(istype(P, /obj/projectile/colossus)) +/obj/structure/closet/crate/necropolis/colossus/bullet_act(obj/projectile/proj) + if(istype(proj, /obj/projectile/colossus)) return BULLET_ACT_FORCE_PIERCE return ..() diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm index 0b051248a3966..55a1b9557c26d 100644 --- a/code/modules/mining/ores_coins.dm +++ b/code/modules/mining/ores_coins.dm @@ -341,8 +341,8 @@ GLOBAL_LIST_INIT(sand_recipes, list(\ else return ..() -/obj/item/gibtonite/bullet_act(obj/projectile/P) - GibtoniteReaction(P.firer, "A projectile has primed for detonation a") +/obj/item/gibtonite/bullet_act(obj/projectile/proj) + GibtoniteReaction(proj.firer, "A projectile has primed for detonation a") return ..() /obj/item/gibtonite/ex_act() diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm index 350106d707fdb..f85b50c5eb123 100644 --- a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_abilities.dm @@ -4,8 +4,7 @@ damage = 5 damage_type = BURN armor_flag = ENERGY - speed = 1 - pixel_speed_multiplier = 0.25 + speed = 0.25 temperature = -75 /datum/action/cooldown/mob_cooldown/ice_demon_teleport diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm b/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm index 4e9e045958732..a5b47c7d22f09 100644 --- a/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm +++ b/code/modules/mob/living/basic/jungle/seedling/seedling_projectiles.dm @@ -6,7 +6,7 @@ light_range = 2 armor_flag = ENERGY light_color = LIGHT_COLOR_DIM_YELLOW - speed = 1.6 + speed = 0.66 hitsound = 'sound/items/weapons/sear.ogg' hitsound_wall = 'sound/items/weapons/effects/searwall.ogg' nondirectional_sprite = TRUE diff --git a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm index 8e43e43e72c31..bd480cc6972ea 100644 --- a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm +++ b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm @@ -72,7 +72,7 @@ icon_state = "neurotoxin" hitsound = 'sound/items/weapons/sear.ogg' damage = 20 - speed = 2 + speed = 0.5 range = 20 jitter = 3 SECONDS stutter = 3 SECONDS diff --git a/code/modules/mob/living/carbon/alien/adult/alien_powers.dm b/code/modules/mob/living/carbon/alien/adult/alien_powers.dm index bfdcd1a5fd2ea..b9cdd8581e4cd 100644 --- a/code/modules/mob/living/carbon/alien/adult/alien_powers.dm +++ b/code/modules/mob/living/carbon/alien/adult/alien_powers.dm @@ -295,7 +295,7 @@ Doesn't work on other aliens/AI.*/ span_alertalien("You spit neurotoxin."), ) var/obj/projectile/neurotoxin/neurotoxin = new /obj/projectile/neurotoxin(caller.loc) - neurotoxin.preparePixelProjectile(target, caller, modifiers) + neurotoxin.aim_projectile(target, caller, modifiers) neurotoxin.firer = caller neurotoxin.fire() caller.newtonian_move(get_angle(target, caller)) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 63fd4aad17b47..7ff695e4a744f 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -1494,3 +1494,9 @@ return head.adjustBleedStacks(5) visible_message(span_notice("[src] gets a nosebleed."), span_warning("You get a nosebleed.")) + +/mob/living/carbon/check_hit_limb_zone_name(hit_zone) + if(get_bodypart(hit_zone)) + return hit_zone + // When a limb is missing the damage is actually passed to the chest + return BODY_ZONE_CHEST diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index fd76fa07ddd59..2c5d2df051758 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -62,12 +62,12 @@ return null -/mob/living/carbon/check_projectile_dismemberment(obj/projectile/P, def_zone) +/mob/living/carbon/check_projectile_dismemberment(obj/projectile/proj, def_zone) var/obj/item/bodypart/affecting = get_bodypart(def_zone) - if(affecting && affecting.can_dismember() && !(affecting.bodypart_flags & BODYPART_UNREMOVABLE) && affecting.get_damage() >= (affecting.max_damage - P.dismemberment)) - affecting.dismember(P.damtype) - if(P.catastropic_dismemberment) - apply_damage(P.damage, P.damtype, BODY_ZONE_CHEST, wound_bonus = P.wound_bonus) //stops a projectile blowing off a limb effectively doing no damage. Mostly relevant for sniper rifles. + if(affecting && affecting.can_dismember() && !(affecting.bodypart_flags & BODYPART_UNREMOVABLE) && affecting.get_damage() >= (affecting.max_damage - proj.dismemberment)) + affecting.dismember(proj.damtype) + if(proj.catastropic_dismemberment) + apply_damage(proj.damage, proj.damtype, BODY_ZONE_CHEST, wound_bonus = proj.wound_bonus) //stops a projectile blowing off a limb effectively doing no damage. Mostly relevant for sniper rifles. /mob/living/carbon/try_catch_item(obj/item/item, skip_throw_mode_check = FALSE, try_offhand = FALSE) . = ..() diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 8df7c758e2b1e..36c9182d32b06 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -45,11 +45,10 @@ return covering_part /mob/living/carbon/human/bullet_act(obj/projectile/bullet, def_zone, piercing_hit = FALSE) - if(bullet.firer == src && bullet.original == src) //can't block or reflect when shooting yourself return ..() - if(bullet.reflectable & REFLECT_NORMAL) + if(bullet.reflectable) if(check_reflect(def_zone)) // Checks if you've passed a reflection% check visible_message( span_danger("The [bullet.name] gets reflected by [src]!"), @@ -61,11 +60,8 @@ playsound(src, held_item.block_sound, BLOCK_SOUND_VOLUME, TRUE) // Find a turf near or on the original location to bounce to if(!isturf(loc)) //Open canopy mech (ripley) check. if we're inside something and still got hit - bullet.force_hit = TRUE //The thing we're in passed the bullet to us. Pass it back, and tell it to take the damage. - loc.bullet_act(bullet, def_zone, piercing_hit) - return BULLET_ACT_HIT + return loc.bullet_act(bullet, def_zone, piercing_hit) bullet.reflect(src) - return BULLET_ACT_FORCE_PIERCE // complete projectile permutation if(check_block(bullet, bullet.damage, "the [bullet.name]", PROJECTILE_ATTACK, bullet.armour_penetration, bullet.damage_type)) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index b64a009ac1a40..029a4376437fb 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -3004,3 +3004,8 @@ GLOBAL_LIST_EMPTY(fire_appearances) REMOVE_TRAIT(src, TRAIT_BLOCKING_PROJECTILES, BLOCKING_TRAIT) cut_overlay(selected_overlay) update_transform(0.8) + +/// Returns the string form of the def_zone we have hit. +/mob/living/proc/check_hit_limb_zone_name(hit_zone) + if(has_limbs) + return hit_zone diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 5eb7815437336..71dc34b986f38 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -174,16 +174,15 @@ /mob/living/check_projectile_armor(def_zone, obj/projectile/impacting_projectile, is_silent) return run_armor_check(def_zone, impacting_projectile.armor_flag, "","",impacting_projectile.armour_penetration, "", is_silent, impacting_projectile.weak_against_armour) -/mob/living/proc/check_projectile_dismemberment(obj/projectile/P, def_zone) - return 0 +/mob/living/proc/check_projectile_dismemberment(obj/projectile/proj, def_zone) + return /obj/item/proc/get_volume_by_throwforce_and_or_w_class() if(throwforce && w_class) return clamp((throwforce + w_class) * 5, 30, 100)// Add the item's throwforce to its weight class and multiply by 5, then clamp the value between 30 and 100 - else if(w_class) + if(w_class) return clamp(w_class * 8, 20, 100) // Multiply the item's weight class by 8, then clamp the value between 20 and 100 - else - return 0 + return 0 /mob/living/proc/set_combat_mode(new_mode, silent = TRUE) if(combat_mode == new_mode) diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm index df9db2defb173..81fad948ae12d 100644 --- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm +++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm @@ -86,7 +86,7 @@ var/obj/projectile/fired_bullet = new projectile(loc) playsound(src, shoot_sound, 50, TRUE) - fired_bullet.preparePixelProjectile(target, src) + fired_bullet.aim_projectile(target, src) fired_bullet.fire() /mob/living/simple_animal/bot/secbot/ed209/emp_act(severity) diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm index 5e9018384bef6..e1117e3200f77 100644 --- a/code/modules/mob/living/simple_animal/bot/mulebot.dm +++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm @@ -243,14 +243,14 @@ return TRUE -/mob/living/simple_animal/bot/mulebot/bullet_act(obj/projectile/Proj) +/mob/living/simple_animal/bot/mulebot/bullet_act(obj/projectile/proj) . = ..() if(. && !QDELETED(src)) //Got hit and not blown up yet. if(prob(50) && !isnull(load)) unload(0) if(prob(25)) visible_message(span_danger("Something shorts out inside [src]!")) - wires.cut_random(source = Proj.firer) + wires.cut_random(source = proj.firer) /mob/living/simple_animal/bot/mulebot/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm index 0f60aac10fa52..d627053179bef 100644 --- a/code/modules/mob/living/simple_animal/bot/secbot.dm +++ b/code/modules/mob/living/simple_animal/bot/secbot.dm @@ -297,15 +297,15 @@ update_appearance() return TRUE -/mob/living/simple_animal/bot/secbot/bullet_act(obj/projectile/Proj) +/mob/living/simple_animal/bot/secbot/bullet_act(obj/projectile/proj) . = ..() if(. != BULLET_ACT_HIT) return - if(istype(Proj, /obj/projectile/beam) || istype(Proj, /obj/projectile/bullet)) - if((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE)) - if(Proj.is_hostile_projectile() && Proj.damage < src.health && ishuman(Proj.firer)) - retaliate(Proj.firer) + if(istype(proj, /obj/projectile/beam) || istype(proj, /obj/projectile/bullet)) + if((proj.damage_type == BURN) || (proj.damage_type == BRUTE)) + if(proj.is_hostile_projectile() && proj.damage < src.health && ishuman(proj.firer)) + retaliate(proj.firer) /mob/living/simple_animal/bot/secbot/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) if(!(bot_mode_flags & BOT_MODE_ON)) diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index 201c83497dfd5..583f2ba1d6c9e 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -165,11 +165,11 @@ FindTarget(list(source)) return ..() -/mob/living/simple_animal/hostile/bullet_act(obj/projectile/P) +/mob/living/simple_animal/hostile/bullet_act(obj/projectile/proj) if(stat == CONSCIOUS && !target && AIStatus != AI_OFF && !client) - if(P.firer && get_dist(src, P.firer) <= aggro_vision_range) - FindTarget(list(P.firer)) - Goto(P.starting, move_to_delay, 3) + if(proj.firer && get_dist(src, proj.firer) <= aggro_vision_range) + FindTarget(list(proj.firer)) + Goto(proj.starting, move_to_delay, 3) return ..() //////////////HOSTILE MOB TARGETING AND AGGRESSION//////////// diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm index ae3b10b11990a..1f544c0d322eb 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/blood_drunk_miner.dm @@ -106,7 +106,7 @@ Difficulty: Medium /obj/projectile/kinetic/miner damage = 20 - speed = 0.9 + speed = 1.1 icon_state = "ka_tracer" range = 4 diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm index f1e3461271359..248b73c6677cc 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm @@ -297,9 +297,9 @@ Difficulty: Hard if(.) recovery_time = world.time + 20 // can only attack melee once every 2 seconds but rapid_melee gives higher priority -/mob/living/simple_animal/hostile/megafauna/bubblegum/bullet_act(obj/projectile/P) +/mob/living/simple_animal/hostile/megafauna/bubblegum/bullet_act(obj/projectile/proj) if(BUBBLEGUM_IS_ENRAGED) - visible_message(span_danger("[src] deflects the projectile; [p_they()] can't be hit with ranged weapons while enraged!"), span_userdanger("You deflect the projectile!")) + visible_message(span_danger("[src] deflects the [proj]! [p_They()] can't be hit with ranged weapons while enraged!"), span_userdanger("You deflect the projectile!")) playsound(src, SFX_BULLET_MISS, 300, TRUE) return BULLET_ACT_BLOCK return ..() diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index 3af739f736c0a..08a90c640d84d 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -182,7 +182,7 @@ icon_state = "chronobolt" damage = 25 armour_penetration = 100 - speed = 2 + speed = 0.5 damage_type = BRUTE pass_flags = PASSTABLE plane = GAME_PLANE @@ -278,12 +278,12 @@ ActivationReaction(user, ACTIVATE_WEAPON) ..() -/obj/machinery/anomalous_crystal/bullet_act(obj/projectile/P, def_zone) +/obj/machinery/anomalous_crystal/bullet_act(obj/projectile/proj, def_zone) . = ..() - if(istype(P, /obj/projectile/magic)) - ActivationReaction(P.firer, ACTIVATE_MAGIC, P.damage_type) + if(istype(proj, /obj/projectile/magic)) + ActivationReaction(proj.firer, ACTIVATE_MAGIC, proj.damage_type) return - ActivationReaction(P.firer, P.armor_flag, P.damage_type) + ActivationReaction(proj.firer, proj.armor_flag, proj.damage_type) /obj/machinery/anomalous_crystal/proc/ActivationReaction(mob/user, method, damtype) if(!COOLDOWN_FINISHED(src, cooldown_timer)) @@ -405,9 +405,9 @@ /obj/machinery/anomalous_crystal/emitter/ActivationReaction(mob/user, method) if(..()) - var/obj/projectile/P = new generated_projectile(get_turf(src)) - P.firer = src - P.fire(dir2angle(dir)) + var/obj/projectile/proj = new generated_projectile(get_turf(src)) + proj.firer = src + proj.fire(dir2angle(dir)) /obj/machinery/anomalous_crystal/dark_reprise //Revives anyone nearby, but turns them into shadowpeople and renders them uncloneable, so the crystal is your only hope of getting up again if you go down. observer_desc = "When activated, this crystal revives anyone nearby, but turns them into Shadowpeople and makes them unclonable, making the crystal their only hope of getting up again." diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm index 8c32b2a3c47d6..8cdf9141ab845 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm @@ -126,7 +126,7 @@ Difficulty: Extremely Hard if(FROST_MINER_SHOULD_ENRAGE) INVOKE_ASYNC(src, PROC_REF(check_enraged)) return COMPONENT_BLOCK_ABILITY_START - var/projectile_speed_multiplier = 1 - enraged * 0.5 + var/projectile_speed_multiplier = 1 + enraged frost_orbs.projectile_speed_multiplier = projectile_speed_multiplier snowball_machine_gun.projectile_speed_multiplier = projectile_speed_multiplier ice_shotgun.projectile_speed_multiplier = projectile_speed_multiplier @@ -198,8 +198,7 @@ Difficulty: Extremely Hard icon_state = "ice_1" damage = 20 armour_penetration = 100 - speed = 1 - pixel_speed_multiplier = 0.1 + speed = 0.1 range = 500 homing_turn_speed = 3 damage_type = BURN @@ -214,8 +213,7 @@ Difficulty: Extremely Hard icon_state = "nuclear_particle" damage = 5 armour_penetration = 100 - speed = 1 - pixel_speed_multiplier = 0.333 + speed = 0.33 range = 150 damage_type = BRUTE explode_hit_objects = FALSE @@ -225,8 +223,7 @@ Difficulty: Extremely Hard icon_state = "ice_2" damage = 15 armour_penetration = 100 - speed = 1 - pixel_speed_multiplier = 0.333 + speed = 0.33 range = 150 damage_type = BRUTE diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm index 9383718e7bd30..509ca49ab34ea 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm @@ -573,8 +573,8 @@ Difficulty: Hard if(mover == caster.pulledby) return if(isprojectile(mover)) - var/obj/projectile/P = mover - if(P.firer == caster) + var/obj/projectile/proj = mover + if(proj.firer == caster) return if(mover != caster) return FALSE diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm index a7db852492442..aad198801adf0 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm @@ -198,11 +198,9 @@ Difficulty: Hard /obj/projectile/colossus/wendigo_shockwave name = "wendigo shockwave" - speed = 2 - /// If wave movement is enabled - var/wave_movement = FALSE + speed = 0.5 /// Amount the angle changes every pixel move - var/wave_speed = 15 + var/wave_speed = 0.5 /// Amount of movements this projectile has made var/pixel_moves = 0 @@ -210,18 +208,16 @@ Difficulty: Hard damage = 15 /obj/projectile/colossus/wendigo_shockwave/wave - speed = 8 - wave_movement = TRUE - wave_speed = 10 + speed = 0.125 + homing = TRUE + wave_speed = 0.3 /obj/projectile/colossus/wendigo_shockwave/wave/alternate - wave_speed = -10 + wave_speed = -0.3 -/obj/projectile/colossus/wendigo_shockwave/pixel_move(trajectory_multiplier, hitscanning = FALSE) - . = ..() - if(wave_movement) - pixel_moves++ - set_angle(original_angle + pixel_moves * wave_speed) +/obj/projectile/colossus/wendigo_shockwave/process_homing() + pixel_moves++ + set_angle(original_angle + pixel_moves * wave_speed) /obj/item/wendigo_blood name = "bottle of wendigo blood" diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm index f57ea3e058203..5fbd6cda5cbfb 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/curse_blob.dm @@ -101,8 +101,8 @@ if(mover == set_target) return FALSE if(isprojectile(mover)) - var/obj/projectile/P = mover - if(P.firer == set_target) + var/obj/projectile/proj = mover + if(proj.firer == set_target) return FALSE #define IGNORE_PROC_IF_NOT_TARGET(X) /mob/living/simple_animal/hostile/asteroid/curseblob/##X(AM) { if (AM == set_target) return ..(); } @@ -119,8 +119,8 @@ IGNORE_PROC_IF_NOT_TARGET(attack_larva) IGNORE_PROC_IF_NOT_TARGET(attack_animal) -/mob/living/simple_animal/hostile/asteroid/curseblob/bullet_act(obj/projectile/Proj) - if(Proj.firer != set_target) +/mob/living/simple_animal/hostile/asteroid/curseblob/bullet_act(obj/projectile/proj) + if(proj.firer != set_target) return BULLET_ACT_BLOCK return ..() diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm index b12be32587e3d..48316d25020bd 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm @@ -135,7 +135,7 @@ H = new /obj/projectile/herald(startloc) else H = new /obj/projectile/herald/teleshot(startloc) - H.preparePixelProjectile(marker, startloc) + H.aim_projectile(marker, startloc) H.firer = src if(target) H.original = target @@ -228,7 +228,7 @@ icon_state= "chronobolt" damage = 20 armour_penetration = 60 - speed = 2 + speed = 0.5 damage_type = BRUTE pass_flags = PASSTABLE @@ -276,7 +276,7 @@ var/turf/startloc = get_turf(owner) var/obj/projectile/herald/H = null H = new /obj/projectile/herald(startloc) - H.preparePixelProjectile(marker, startloc) + H.aim_projectile(marker, startloc) H.firer = owner H.fire(set_angle) diff --git a/code/modules/mob/living/simple_animal/hostile/ooze.dm b/code/modules/mob/living/simple_animal/hostile/ooze.dm index a44cae6f139f5..7f6a5c5cd36e3 100644 --- a/code/modules/mob/living/simple_animal/hostile/ooze.dm +++ b/code/modules/mob/living/simple_animal/hostile/ooze.dm @@ -359,7 +359,7 @@ // Why is this in InterceptClickOn() and not Activate()? // Well, we need to use the params of the click intercept - // for passing into preparePixelProjectile, so we'll handle it here instead. + // for passing into aim_projectile, so we'll handle it here instead. // We just need to make sure Pre-activate and Activate return TRUE so we make it this far caller.visible_message( span_nicegreen("[caller] launches a mending globule!"), @@ -372,7 +372,7 @@ var/modifiers = params2list(params) var/obj/projectile/globule/globule = new(caller.loc) - globule.preparePixelProjectile(target, caller, modifiers) + globule.aim_projectile(target, caller, modifiers) globule.def_zone = caller.zone_selected globule.fire() diff --git a/code/modules/mob/living/sneeze.dm b/code/modules/mob/living/sneeze.dm index ebf6162083482..af638d7c71a77 100644 --- a/code/modules/mob/living/sneeze.dm +++ b/code/modules/mob/living/sneeze.dm @@ -53,7 +53,7 @@ suppressed = SUPPRESSED_VERY range = 4 - speed = 4 + speed = 0.25 spread = 40 damage_type = BRUTE damage = 0 diff --git a/code/modules/mod/modules/modules_antag.dm b/code/modules/mod/modules/modules_antag.dm index ec550f3cfde3a..d09781451f42d 100644 --- a/code/modules/mod/modules/modules_antag.dm +++ b/code/modules/mod/modules/modules_antag.dm @@ -313,7 +313,7 @@ if(!.) return var/obj/projectile/flame = new /obj/projectile/bullet/incendiary/fire(mod.wearer.loc) - flame.preparePixelProjectile(target, mod.wearer) + flame.aim_projectile(target, mod.wearer) flame.firer = mod.wearer playsound(src, 'sound/items/modsuit/flamethrower.ogg', 75, TRUE) INVOKE_ASYNC(flame, TYPE_PROC_REF(/obj/projectile, fire)) diff --git a/code/modules/mod/modules/modules_engineering.dm b/code/modules/mod/modules/modules_engineering.dm index 9c96d9edd82b9..abe09594500c1 100644 --- a/code/modules/mod/modules/modules_engineering.dm +++ b/code/modules/mod/modules/modules_engineering.dm @@ -96,7 +96,7 @@ if(!.) return var/obj/projectile/tether = new /obj/projectile/tether(mod.wearer.loc, src) - tether.preparePixelProjectile(target, mod.wearer) + tether.aim_projectile(target, mod.wearer) tether.firer = mod.wearer playsound(src, 'sound/items/weapons/batonextend.ogg', 25, TRUE) INVOKE_ASYNC(tether, TYPE_PROC_REF(/obj/projectile, fire)) @@ -164,14 +164,8 @@ firer.AddComponent(/datum/component/tether, target, 7, "MODtether", parent_module = parent_module) return - var/hitx - var/hity - if(target == original) - hitx = target.pixel_x + p_x - 16 - hity = target.pixel_y + p_y - 16 - else - hitx = target.pixel_x + rand(-8, 8) - hity = target.pixel_y + rand(-8, 8) + var/hitx = impact_x + var/hity = impact_y if (!isnull(last_turf) && last_turf != target && last_turf != target.loc) var/turf_dir = get_dir(last_turf, get_turf(target)) diff --git a/code/modules/mod/modules/modules_medical.dm b/code/modules/mod/modules/modules_medical.dm index 7c4650ff81a2f..f71814fc7db53 100644 --- a/code/modules/mod/modules/modules_medical.dm +++ b/code/modules/mod/modules/modules_medical.dm @@ -165,7 +165,7 @@ return var/atom/movable/fired_organ = pop(organ_list) var/obj/projectile/organ/projectile = new /obj/projectile/organ(mod.wearer.loc, fired_organ) - projectile.preparePixelProjectile(target, mod.wearer) + projectile.aim_projectile(target, mod.wearer) projectile.firer = mod.wearer playsound(src, 'sound/vehicles/mecha/hydraulic.ogg', 25, TRUE) INVOKE_ASYNC(projectile, TYPE_PROC_REF(/obj/projectile, fire)) diff --git a/code/modules/mod/modules/modules_ninja.dm b/code/modules/mod/modules/modules_ninja.dm index a6ade06a9098c..5f7abdd712b87 100644 --- a/code/modules/mod/modules/modules_ninja.dm +++ b/code/modules/mod/modules/modules_ninja.dm @@ -323,7 +323,7 @@ if(IS_SPACE_NINJA(mod.wearer) && isliving(target)) mod.wearer.say("Get over here!", forced = type) var/obj/projectile/net = new /obj/projectile/energy_net(mod.wearer.loc, src) - net.preparePixelProjectile(target, mod.wearer) + net.aim_projectile(target, mod.wearer) net.firer = mod.wearer playsound(src, 'sound/items/weapons/punchmiss.ogg', 25, TRUE) INVOKE_ASYNC(net, TYPE_PROC_REF(/obj/projectile, fire)) diff --git a/code/modules/mod/modules/modules_security.dm b/code/modules/mod/modules/modules_security.dm index 0903231830486..dff5a000b928f 100644 --- a/code/modules/mod/modules/modules_security.dm +++ b/code/modules/mod/modules/modules_security.dm @@ -310,7 +310,7 @@ /// Debuff multiplier on projectiles. var/debuff_multiplier = 0.66 /// Speed multiplier on projectiles, higher means slower. - var/speed_multiplier = 2.5 + var/speed_multiplier = 0.4 /// List of all tracked projectiles. var/list/tracked_projectiles = list() /// Effect image on projectiles. diff --git a/code/modules/mod/modules/modules_supply.dm b/code/modules/mod/modules/modules_supply.dm index bc34cf8781ec4..2c4a5955aed2b 100644 --- a/code/modules/mod/modules/modules_supply.dm +++ b/code/modules/mod/modules/modules_supply.dm @@ -528,7 +528,7 @@ if(!.) return var/obj/projectile/bomb = new /obj/projectile/bullet/mining_bomb(mod.wearer.loc) - bomb.preparePixelProjectile(target, mod.wearer) + bomb.aim_projectile(target, mod.wearer) bomb.firer = mod.wearer playsound(src, 'sound/items/weapons/gun/general/grenade_launch.ogg', 75, TRUE) INVOKE_ASYNC(bomb, TYPE_PROC_REF(/obj/projectile, fire)) diff --git a/code/modules/mod/modules/modules_timeline.dm b/code/modules/mod/modules/modules_timeline.dm index bfb22da0c20da..da36c2153d00f 100644 --- a/code/modules/mod/modules/modules_timeline.dm +++ b/code/modules/mod/modules/modules_timeline.dm @@ -222,7 +222,7 @@ //fire projectile var/obj/projectile/energy/chrono_beam/chrono_beam = new /obj/projectile/energy/chrono_beam(get_turf(src)) chrono_beam.tem_weakref = WEAKREF(src) - chrono_beam.preparePixelProjectile(target, mod.wearer) + chrono_beam.aim_projectile(target, mod.wearer) chrono_beam.firer = mod.wearer playsound(src, 'sound/items/modsuit/time_anchor_set.ogg', 50, TRUE) INVOKE_ASYNC(chrono_beam, TYPE_PROC_REF(/obj/projectile, fire)) diff --git a/code/modules/power/rtg.dm b/code/modules/power/rtg.dm index dff4a732b9312..69693af3a4a92 100644 --- a/code/modules/power/rtg.dm +++ b/code/modules/power/rtg.dm @@ -73,10 +73,10 @@ tesla_zap(source = src, zap_range = 5, power = power_gen * 20) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(explosion), src, 2, 3, 4, null, 8), 10 SECONDS) // Not a normal explosion. -/obj/machinery/power/rtg/abductor/bullet_act(obj/projectile/Proj) +/obj/machinery/power/rtg/abductor/bullet_act(obj/projectile/proj) . = ..() - if(!going_kaboom && istype(Proj) && Proj.damage > 0 && ((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE))) - log_bomber(Proj.firer, "triggered a", src, "explosion via projectile") + if(!going_kaboom && istype(proj) && proj.damage > 0 && ((proj.damage_type == BURN) || (proj.damage_type == BRUTE))) + log_bomber(proj.firer, "triggered a", src, "explosion via projectile") overload() /obj/machinery/power/rtg/abductor/blob_act(obj/structure/blob/B) diff --git a/code/modules/projectiles/ammunition/_firing.dm b/code/modules/projectiles/ammunition/_firing.dm index b15cd334a8e6d..5ea2da3513d75 100644 --- a/code/modules/projectiles/ammunition/_firing.dm +++ b/code/modules/projectiles/ammunition/_firing.dm @@ -88,7 +88,7 @@ var/direct_target if(target && curloc.Adjacent(targloc, target=targloc, mover=src)) //if the target is right on our location or adjacent (including diagonally if reachable) we'll skip the travelling code in the proj's fire() direct_target = target - loaded_projectile.preparePixelProjectile(target, fired_from, params2list(params), spread) + loaded_projectile.aim_projectile(target, fired_from, params2list(params), spread) var/obj/projectile/loaded_projectile_cache = loaded_projectile loaded_projectile = null loaded_projectile_cache.fire(null, direct_target) diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm index d7a24c9deba47..41152b170ad3b 100644 --- a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm +++ b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm @@ -60,7 +60,7 @@ desc = "Quite the sticky situation..." icon_state = "sticky_arrow_projectile" damage = 30 - speed = 0.75 + speed = 1.3 range = 20 embed_type = /datum/embed_data/arrow/sticky diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm index 08cce7d73151c..b1a34c30eb936 100644 --- a/code/modules/projectiles/guns/energy/laser.dm +++ b/code/modules/projectiles/guns/energy/laser.dm @@ -151,11 +151,11 @@ var/size_per_tile = 0.1 var/max_scale = 4 -/obj/projectile/beam/laser/accelerator/Range() +/obj/projectile/beam/laser/accelerator/reduce_range() ..() damage += 7 transform = matrix() - transform *= min(1 + (decayedRange - range) * size_per_tile, max_scale) + transform *= min(1 + (maximum_range - range) * size_per_tile, max_scale) ///X-ray gun diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm index 8072f62e46458..0cd4148e70996 100644 --- a/code/modules/projectiles/guns/energy/special.dm +++ b/code/modules/projectiles/guns/energy/special.dm @@ -431,7 +431,7 @@ playsound(user.loc, 'sound/effects/coin2.ogg', 50, TRUE) user.visible_message(span_warning("[user] flips a coin towards [target]!"), span_danger("You flip a coin towards [target]!")) var/obj/projectile/bullet/coin/new_coin = new(get_turf(user), target_turf, user) - new_coin.preparePixelProjectile(target_turf, user) + new_coin.aim_projectile(target_turf, user) new_coin.fire() return ITEM_INTERACT_SUCCESS diff --git a/code/modules/projectiles/guns/special/blastcannon.dm b/code/modules/projectiles/guns/special/blastcannon.dm index a7c35c9122ec2..befb622251a48 100644 --- a/code/modules/projectiles/guns/special/blastcannon.dm +++ b/code/modules/projectiles/guns/special/blastcannon.dm @@ -193,7 +193,7 @@ SSexplosions.shake_the_room(start_turf, max(heavy, medium, light, 0), (capped_heavy * 15) + (capped_medium * 20), capped_heavy, capped_medium) var/obj/projectile/blastwave/blastwave = new(loc, heavy, medium, light) - blastwave.preparePixelProjectile(target, start_turf, params2list(modifiers), spread) + blastwave.aim_projectile(target, start_turf, params2list(modifiers), spread) blastwave.fire() cached_firer = null cached_target = null @@ -314,7 +314,7 @@ /obj/projectile/blastwave/is_hostile_projectile() return TRUE -/obj/projectile/blastwave/Range() +/obj/projectile/blastwave/reduce_range() . = ..() if(QDELETED(src)) return diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 3d2834edc62db..cbe396e07735f 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -1,7 +1,8 @@ -#define MOVES_HITSCAN -1 //Not actually hitscan but close as we get without actual hitscan. -#define MUZZLE_EFFECT_PIXEL_INCREMENT 17 //How many pixels to move the muzzle flash up so your character doesn't look like they're shitting out lasers. -#define MAX_RANGE_HIT_PRONE_TARGETS 10 //How far do the projectile hits the prone mob +/// Not actually hitscan but close as we get without actual hitscan. +#define MOVES_HITSCAN -1 +/// How many pixels to move the muzzle flash up so your character doesn't look like they're shitting out lasers. +#define MUZZLE_EFFECT_PIXEL_INCREMENT 17 /obj/projectile name = "projectile" @@ -15,30 +16,56 @@ generic_canpass = FALSE blocks_emissive = EMISSIVE_BLOCK_GENERIC layer = MOB_LAYER - //The sound this plays on impact. + /// The sound this plays on impact. var/hitsound = 'sound/items/weapons/pierce.ogg' - var/hitsound_wall = "" + /// Sound played when the projectile hits a wall + var/hitsound_wall resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - var/def_zone = "" //Aiming at - var/atom/movable/firer = null//Who shot it - var/datum/fired_from = null // the thing that the projectile was fired from (gun, turret, spell) - var/suppressed = FALSE //Attack message - var/yo = null - var/xo = null - var/atom/original = null // the original target clicked - var/turf/starting = null // the projectile's starting turf + /// Zone at which the projectile is aimed at + var/def_zone = "" + /// Atom who shot the projectile (Not the gun, the guy who shot the gun) + var/atom/movable/firer = null + /// The thing that the projectile was fired from (gun, turret, spell) + var/datum/fired_from = null + /// One of three suppression states: NONE displays the hit message and produces a loud sound, + /// QUIET makes a quiet sound and only lets the victim know they've been shot, and VERY only makes a very quiet sound with no messages + var/suppressed = SUPPRESSED_NONE + /// Original clicked target + var/atom/original = null + /// Initial target x coordinate offset of the projectile + VAR_FINAL/xo = null + /// Initial target y coordinate offset of the projectile + VAR_FINAL/yo = null + /// Projectile's starting turf + var/turf/starting = null + /// pixel_x where the player clicked. Default is the center. var/p_x = 16 - var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center - - //Fired processing vars - var/fired = FALSE //Have we been fired yet - var/paused = FALSE //for suspending the projectile midair - var/last_projectile_move = 0 - var/last_process = 0 - var/time_offset = 0 - var/datum/point/vector/trajectory - var/trajectory_ignore_forcemove = FALSE //instructs forceMove to NOT reset our trajectory to the new location! + /// pixel_y where the player clicked. Default is the center + var/p_y = 16 + /// X coordinate at which the projectile entered a new turf + var/entry_x + /// Y coordinate at which the projectile entered a new turf + var/entry_y + /// X coordinate at which the projectile visually impacted the target + var/impact_x + /// Y coordinate at which the projectile visually impacted the target + var/impact_y + /// Turf of the last atom we've impacted + VAR_FINAL/turf/last_impact_turf = null + + /// If the projectile was fired already + var/fired = FALSE + /// If the projectile is suspended mid-air + var/paused = FALSE + /// Last time the projectile moved, used for lag compensation if SSprojectiles starts chugging + VAR_PRIVATE/last_projectile_move = 0 + /// Last time the projectile was processed, also used for lag compensation + VAR_PRIVATE/last_process = 0 + /// How many pixels we missed last tick due to lag or speed cap + VAR_PRIVATE/overrun = 0 + /// Projectile's movement vector - this caches sine/cosine of our angle to cut down on trig calculations + var/datum/vector/movement_vector /// We already impacted these things, do not impact them again. Used to make sure we can pierce things we want to pierce. Lazylist, typecache style (object = TRUE) for performance. var/list/impacted = list() /// If TRUE, we can hit our firer. @@ -66,38 +93,34 @@ var/projectile_phasing = NONE /// Bitflag for things the projectile should hit, but pierce through without deleting itself. Defers to projectile_phasing. Uses pass_flags flags. var/projectile_piercing = NONE - /// number of times we've pierced something. Incremented BEFORE bullet_act and on_hit proc! + /// Number of times we've pierced something. Incremented BEFORE bullet_act and on_hit proc! var/pierces = 0 - /// how many times this projectile can pierce something before deleting + /// How many times this projectile can pierce something before deleting var/max_pierces = 0 /// If objects are below this layer, we pass through them var/hit_threshhold = PROJECTILE_HIT_THRESHHOLD_LAYER - /// During each fire of SSprojectiles, the number of deciseconds since the last fire of SSprojectiles - /// is divided by this var, and the result truncated to the next lowest integer is - /// the number of times the projectile's `pixel_move` proc will be called. - var/speed = 0.8 - - /// This var is multiplied by SSprojectiles.global_pixel_speed to get how many pixels - /// the projectile moves during each iteration of the movement loop - /// - /// If you want to make a fast-moving projectile, you should keep this equal to 1 and - /// reduce the value of `speed`. If you want to make a slow-moving projectile, make - /// `speed` a modest value like 1 and set this to a low value like 0.2. - var/pixel_speed_multiplier = 1 + /// How many tiles we pass in a single SSprojectiles tick + var/speed = 1.25 /// The current angle of the projectile. Initially null, so if the arg is missing from [/fire()], we can calculate it from firer and target as fallback. var/angle - var/original_angle = 0 //Angle at firing - var/nondirectional_sprite = FALSE //Set TRUE to prevent projectiles from having their sprites rotated based on firing angle - var/spread = 0 //amount (in degrees) of projectile spread - animate_movement = NO_STEPS //Use SLIDE_STEPS in conjunction with legacy - /// how many times we've ricochet'd so far (instance variable, not a stat) + /// Angle at the moment of firing + var/original_angle = 0 + /// Set TRUE to prevent projectiles from having their sprites rotated based on firing angle + var/nondirectional_sprite = FALSE + /// Random spread done projectile-side for convinience + var/spread = 0 + /// Gliding does not enjoy something getting moved multiple turfs in a tick, which is why we animate it manually + animate_movement = NO_STEPS + + // Ricochet logic + /// How many times we've ricochet'd so far (instance variable, not a stat) var/ricochets = 0 - /// how many times we can ricochet max + /// How many times we can ricochet max var/ricochets_max = 0 - /// how many times we have to ricochet min (unless we hit an atom we can ricochet off) + /// How many times we have to ricochet min (unless we hit an atom we can ricochet off) var/min_ricochets = 0 /// 0-100 (or more, I guess), the base chance of ricocheting, before being modified by the atom we shoot and our chance decay var/ricochet_chance = 0 @@ -114,18 +137,22 @@ /// Can our ricochet autoaim hit our firer? var/ricochet_shoots_firer = TRUE - ///If the object being hit can pass the damage on to something else, it should not do it for this bullet - var/force_hit = FALSE - - //Hitscan - var/hitscan = FALSE //Whether this is hitscan. If it is, speed is basically ignored. - var/list/beam_segments //assoc list of datum/point or datum/point/vector, start = end. Used for hitscan effect generation. - /// Last turf an angle was changed in for hitscan projectiles. - var/turf/last_angle_set_hitscan_store - var/datum/point/beam_index - var/turf/hitscan_last //last turf touched during hitscanning. + // Hitscan logic + /// Wherever this projectile is hitscan. Hitscan projectiles are processed until the end of their path instantly upon being fired and leave a tracer in their path + var/hitscan = FALSE + /// Associated list of coordinate points in which we changed trajectories in order to calculate hitscan tracers + /// Value points to the next point in the beam + var/list/datum/point/beam_points + /// Last point in the beam + var/datum/point/last_point + /// Next forceMove will not create tracer end/start effects + var/free_hitscan_forceMove = FALSE + + /// Hitscan tracer effect left behind the projectile var/tracer_type + /// Hitscan muzzle effect spawned on the firer var/muzzle_type + /// Hitscan impact effect spawned on the target var/impact_type //Fancy hitscan lighting effects! @@ -139,11 +166,16 @@ var/impact_light_range = 2 var/impact_light_color_override - //Homing + // Homing + /// If the projectile is homing. Warning - this changes projectile's processing logic, reverting it to segmented processing instead of new raymarching logic var/homing = FALSE + /// Target the projectile is homing on var/atom/homing_target - var/homing_turn_speed = 10 //Angle per tick. - var/homing_inaccuracy_min = 0 //in pixels for these. offsets are set once when setting target. + /// Angles per move segment, distance is based on SSprojectiles.pixels_per_decisecond + /// With pixels_per_decisecond set to 16 and homing_turn_speed, the projectile can turn up to 20 pixels per turf passed + var/homing_turn_speed = 10 + // Allowed leeway in pixels + var/homing_inaccuracy_min = 0 var/homing_inaccuracy_max = 0 var/homing_offset_x = 0 var/homing_offset_y = 0 @@ -151,17 +183,20 @@ var/damage = 10 var/damage_type = BRUTE //BRUTE, BURN, TOX, OXY are the only things that should be in here - ///Defines what armor to use when it hits things. Must be set to bullet, laser, energy, or bomb + /// Defines what armor to use when it hits things. Must be set to bullet, laser, energy, or bomb var/armor_flag = BULLET - ///How much armor this projectile pierces. + /// How much armor this projectile pierces. var/armour_penetration = 0 - ///Whether or not our projectile doubles the value of affecting armour + /// Whether or not our projectile doubles the value of affecting armour var/weak_against_armour = FALSE - var/projectile_type = /obj/projectile - var/range = 50 //This will de-increment every step. When 0, it will deletze the projectile. - var/decayedRange //stores original range - var/reflect_range_decrease = 5 //amount of original range that falls off when reflecting, so it doesn't go forever - var/reflectable = NONE // Can it be reflected or not? + /// This will de-increment every step. When 0, it will delete the projectile. + var/range = 50 + /// Original range upon being fired/reflected + var/maximum_range + /// Amount of original range that falls off when reflecting, so it doesn't go forever + var/reflect_range_decrease = 5 + /// If this projectile can be reflected + var/reflectable = FALSE // Status effects applied on hit var/stun = 0 SECONDS @@ -182,59 +217,86 @@ /// Slurring applied on projectile hit var/slur = 0 SECONDS - var/dismemberment = 0 //The higher the number, the greater the bonus to dismembering. 0 will not dismember at all. - var/catastropic_dismemberment = FALSE //If TRUE, this projectile deals its damage to the chest if it dismembers a limb. - var/impact_effect_type //what type of impact effect to show when hitting something - var/log_override = FALSE //is this type spammed enough to not log? (KAs) - /// If true, the projectile won't cause any logging. Used for hallucinations and shit. + /// Damage the limb must have for it to be dismembered upon getting hit. 0 will prevent dismembering altogether + var/dismemberment = 0 + /// If TRUE, this projectile deals its damage to the chest if it dismembers a limb. + var/catastropic_dismemberment = FALSE + /// Impact VFX created upon hitting something + var/impact_effect_type + /// If the act of firing this projectile does not create logs + var/log_override = FALSE + /// If true, the projectile won't cause any logging whatsoever. Used for hallucinations and shit. var/do_not_log = FALSE /// We ignore mobs with these factions. var/list/ignored_factions - - ///If defined, on hit we create an item of this type then call hitby() on the hit target with this, mainly used for embedding items (bullets) in targets + /// Turf that we have registered connect_loc signal - this is done for performance, as we're moving ~a dozen turfs per tick + /// and registering and unregistering signal for every single one of them is stupid. Unregistering the signal from the correct turf in case we get moved by smth else is important + var/turf/last_tick_turf + /// Remaining pixel movement last tick - used for precise range calculations + var/pixels_moved_last_tile = 0 + /// In order to preserve animations, projectiles are only deleted the tick *after* they impact something. + /// Same is applied to reaching the range limit + var/deletion_queued = NONE + /// How many ticks should we wait in queued deletion mode before qdeleting? Sometimes increased in animations + var/ticks_to_deletion = 1 + + /// If defined, on hit we create an item of this type then call hitby() on the hit target with this, mainly used for embedding items (bullets) in targets var/shrapnel_type - ///If we have a shrapnel_type defined, these embedding stats will be passed to the spawned shrapnel type, which will roll for embedding on the target + /// If we have a shrapnel_type defined, these embedding stats will be passed to the spawned shrapnel type, which will roll for embedding on the target var/embed_type - ///Saves embedding data + /// Saves embedding data var/datum/embed_data/embed_data - ///If TRUE, hit mobs, even if they are lying on the floor and are not our target within MAX_RANGE_HIT_PRONE_TARGETS tiles + /// If TRUE, hit mobs, even if they are lying on the floor and are not our target within MAX_RANGE_HIT_PRONE_TARGETS tiles var/hit_prone_targets = FALSE - ///if TRUE, ignores the range of MAX_RANGE_HIT_PRONE_TARGETS tiles of hit_prone_targets + /// If TRUE, ignores the range of MAX_RANGE_HIT_PRONE_TARGETS tiles of hit_prone_targets var/ignore_range_hit_prone_targets = FALSE - ///For what kind of brute wounds we're rolling for, if we're doing such a thing. Lasers obviously don't care since they do burn instead. + /// For what kind of brute wounds we're rolling for, if we're doing such a thing. Lasers obviously don't care since they do burn instead. var/sharpness = NONE - ///How much we want to drop damage per tile as it travels through the air + /// How much we want to drop damage per tile as it travels through the air var/damage_falloff_tile - ///How much we want to drop stamina damage (defined by the stamina variable) per tile as it travels through the air + /// How much we want to drop stamina damage (defined by the stamina variable) per tile as it travels through the air var/stamina_falloff_tile - ///How much we want to drop both wound_bonus and bare_wound_bonus (to a minimum of 0 for the latter) per tile, for falloff purposes + /// How much we want to drop both wound_bonus and bare_wound_bonus (to a minimum of 0 for the latter) per tile, for falloff purposes var/wound_falloff_tile - ///How much we want to drop the embed_chance value, if we can embed, per tile, for falloff purposes + /// How much we want to drop the embed_chance value, if we can embed, per tile, for falloff purposes var/embed_falloff_tile - ///How much accuracy is lost for each tile travelled + /// How much accuracy is lost for each tile travelled var/accuracy_falloff = 7 - ///How much accuracy before falloff starts to matter. Formula is range - falloff * tiles travelled + /// How much accuracy before falloff starts to matter. Formula is range - falloff * tiles travelled var/accurate_range = 100 - var/static/list/projectile_connections = list(COMSIG_ATOM_ENTERED = PROC_REF(on_entered)) /// If true directly targeted turfs can be hit var/can_hit_turfs = FALSE /obj/projectile/Initialize(mapload) . = ..() - decayedRange = range - if(get_embed()) + maximum_range = range + if (get_embed()) AddElement(/datum/element/embed) - AddElement(/datum/element/connect_loc, projectile_connections) - add_traits(list(TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_FREE_HYPERSPACE_SOFTCORDON_MOVEMENT), INNATE_TRAIT) -/obj/projectile/proc/Range() +/obj/projectile/Destroy() + if (hitscan) + generate_hitscan_tracers() + STOP_PROCESSING(SSprojectiles, src) + firer = null + original = null + if (movement_vector) + QDEL_NULL(movement_vector) + if (beam_points) + QDEL_LIST(beam_points) + if (last_point) + QDEL_NULL(last_point) + return ..() + +/// Called every time a projectile passes one tile worth of movement +/obj/projectile/proc/reduce_range() range-- - if(wound_bonus != CANT_WOUND) + pixels_moved_last_tile -= ICON_SIZE_ALL + if(wound_falloff_tile && wound_bonus != CANT_WOUND) wound_bonus += wound_falloff_tile bare_wound_bonus = max(0, bare_wound_bonus + wound_falloff_tile) - if(get_embed()) - set_embed(embed_data.generate_with_values(embed_data.embed_chance + embed_falloff_tile)) // Should be rewritten in projecitle refactor + if(embed_falloff_tile && get_embed()) + set_embed(embed_data.generate_with_values(embed_data.embed_chance + embed_falloff_tile)) if(damage_falloff_tile && damage >= 0) damage += damage_falloff_tile if(stamina_falloff_tile && stamina >= 0) @@ -242,26 +304,22 @@ SEND_SIGNAL(src, COMSIG_PROJECTILE_RANGE) if(range <= 0 && loc) - on_range() + if (hitscan) + qdel(src) + return + deletion_queued = PROJECTILE_RANGE_DELETE if(damage_falloff_tile && damage <= 0 || stamina_falloff_tile && stamina <= 0) - on_range() + if (hitscan) + qdel(src) + return + deletion_queued = PROJECTILE_RANGE_DELETE -/obj/projectile/proc/on_range() //if we want there to be effects when they reach the end of their range +/// Called next tick after the projectile reaches its maximum range so the animation has time to fully play out +/obj/projectile/proc/on_range() SEND_SIGNAL(src, COMSIG_PROJECTILE_RANGE_OUT) qdel(src) -/// Returns the string form of the def_zone we have hit. -/mob/living/proc/check_hit_limb_zone_name(hit_zone) - if(has_limbs) - return hit_zone - -/mob/living/carbon/check_hit_limb_zone_name(hit_zone) - if(get_bodypart(hit_zone)) - return hit_zone - else //when a limb is missing the damage is actually passed to the chest - return BODY_ZONE_CHEST - /** * Called when the projectile hits something * @@ -289,21 +347,19 @@ hit_limb_zone = victim.check_hit_limb_zone_name(def_zone) if(fired_from) - SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, angle, hit_limb_zone, blocked) - SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, angle, hit_limb_zone, blocked) + SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, angle, hit_limb_zone, blocked, pierce_hit) + SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, angle, hit_limb_zone, blocked, pierce_hit) - if(QDELETED(src)) // in case one of the above signals deleted the projectile for whatever reason + if(QDELETED(src) || deletion_queued) // in case one of the above signals deleted the projectile for whatever reason return BULLET_ACT_BLOCK var/turf/target_turf = get_turf(target) - var/hitx - var/hity if(target == original) - hitx = target.pixel_x + p_x - 16 - hity = target.pixel_y + p_y - 16 + impact_x = target.pixel_x + p_x - ICON_SIZE_X / 2 + impact_y = target.pixel_y + p_y - ICON_SIZE_Y / 2 else - hitx = target.pixel_x + rand(-8, 8) - hity = target.pixel_y + rand(-8, 8) + impact_x = entry_x + movement_vector?.pixel_x * rand(0, ICON_SIZE_X / 2) + impact_y = entry_y + movement_vector?.pixel_y * rand(0, ICON_SIZE_Y / 2) if(isturf(target) && hitsound_wall) playsound(src, hitsound_wall, clamp(vol_by_damage() + (suppressed ? 0 : 20), 0, 100), TRUE, -1) @@ -311,9 +367,9 @@ if(damage > 0 && (damage_type == BRUTE || damage_type == BURN) && iswallturf(target_turf) && prob(75)) var/turf/closed/wall/target_wall = target_turf if(impact_effect_type && !hitscan) - new impact_effect_type(target_wall, hitx, hity) + new impact_effect_type(target_wall, impact_x, impact_y) - target_wall.add_dent(WALL_DENT_SHOT, hitx, hity) + target_wall.add_dent(WALL_DENT_SHOT, impact_x, impact_y) return BULLET_ACT_HIT if (hitsound) @@ -321,11 +377,11 @@ if (!isliving(target)) if(impact_effect_type && !hitscan) - new impact_effect_type(target_turf, hitx, hity) + new impact_effect_type(target_turf, impact_x, impact_y) return BULLET_ACT_HIT if((blocked >= 100 || (damage && damage_type != BRUTE)) && impact_effect_type && !hitscan) - new impact_effect_type(target_turf, hitx, hity) + new impact_effect_type(target_turf, impact_x, impact_y) var/mob/living/living_target = target var/reagent_note @@ -353,9 +409,20 @@ /obj/projectile/proc/vol_by_damage() if (suppressed) return 5 - if(!damage) + if (!damage) return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume - return clamp(damage * 0.67, 30, 100)// Multiply projectile damage by 0.67, then CLAMP the value between 30 and 1 + return clamp(damage * 0.67, 30, 100) // Multiply projectile damage by 0.67, then CLAMP the value between 30 and 1 + +/obj/projectile/proc/firer_deleted(datum/source) + SIGNAL_HANDLER + // Shooting yourself point-blank + if (firer == original) + original = null + firer = null + +/obj/projectile/proc/original_deleted(datum/source) + SIGNAL_HANDLER + original = null /obj/projectile/proc/on_ricochet(atom/target) ricochets++ @@ -378,16 +445,10 @@ set_angle(get_angle(src, unlucky_sob.loc)) original = unlucky_sob -/obj/projectile/proc/store_hitscan_collision(datum/point/point_cache) - beam_segments[beam_index] = point_cache - beam_index = point_cache - beam_segments[beam_index] = null - /obj/projectile/Bump(atom/bumped_atom) SEND_SIGNAL(src, COMSIG_MOVABLE_BUMP, bumped_atom) - if(!can_hit_target(bumped_atom, bumped_atom == original, TRUE, TRUE)) - return - impact(bumped_atom) + if (can_hit_target(bumped_atom, bumped_atom == original, TRUE, TRUE)) + impact(bumped_atom) /** * Called when the projectile hits something @@ -401,44 +462,58 @@ * Also, we select_target to find what to process_hit first. */ /obj/projectile/proc/impact(atom/target) - if(impacted[target.weak_reference]) // never doublehit, otherwise someone may end up running into a projectile from the back + // Don't impact anything if we've been queued for deletion + if (deletion_queued) + return + + // never doublehit, otherwise someone may end up running into a projectile from the back + if(impacted[target.weak_reference]) return if(ricochets < ricochets_max && check_ricochet_flag(target) && check_ricochet(target) && target.handle_ricochet(src)) on_ricochet(target) impacted = list() // Shoot a x-ray laser at a pair of mirrors I dare you ignore_source_check = TRUE // Firer is no longer immune - decayedRange = max(0, decayedRange - reflect_range_decrease) + maximum_range = max(0, maximum_range - reflect_range_decrease) ricochet_chance *= ricochet_decay_chance damage *= ricochet_decay_damage stamina *= ricochet_decay_damage - range = decayedRange - if(hitscan && trajectory) - store_hitscan_collision(trajectory.copy_to()) + range = maximum_range return - var/turf/target_turf = get_turf(target) + last_impact_turf = get_turf(target) // Lower accurancy/longer range tradeoff. 7 is a balanced number to use. - def_zone = ran_zone(def_zone, clamp(accurate_range - (accuracy_falloff * get_dist(target_turf, starting)), 5, 100)) - process_hit_loop(select_target(target_turf, target)) + def_zone = ran_zone(def_zone, clamp(accurate_range - (accuracy_falloff * get_dist(last_impact_turf, starting)), 5, 100)) + var/impact_result = process_hit_loop(select_target(last_impact_turf, target)) + if (impact_result == PROJECTILE_IMPACT_PASSED) + return + if (hitscan) + qdel(src) + return + deletion_queued = PROJECTILE_IMPACT_DELETE /* * Main projectile hit loop code * As long as there are valid targets on the hit target's tile, we will loop through all the ones that we have not hit * (and thus invalidated) and try to hit them until either no targets remain or we've been deleted. + * Should *never* be called directly, as impact() is the proc queueing projectiles for deletion + * If you need to call this directly, you should reconsider the choices that led you to this point */ /obj/projectile/proc/process_hit_loop(atom/target) SHOULD_NOT_SLEEP(TRUE) - SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + + // Don't impact anything if we've been queued for deletion + if (deletion_queued) + return PROJECTILE_IMPACT_PASSED var/turf/target_turf = get_turf(target) - while (target && !QDELETED(src)) + while (target && !QDELETED(src) && !deletion_queued) // Doublehitting can be an issue with slow projectiles or when the server is chugging impacted[WEAKREF(target)] = TRUE var/mode = prehit_pierce(target) if(mode == PROJECTILE_DELETE_WITHOUT_HITTING) - qdel(src) - return + return PROJECTILE_IMPACT_INTERRUPTED // If we've phasing through a target, first set ourselves as phasing and then try to locate a new one if(mode == PROJECTILE_PIERCE_PHASE) @@ -449,12 +524,10 @@ continue if (SEND_SIGNAL(target, COMSIG_PROJECTILE_PREHIT, src) & PROJECTILE_INTERRUPT_HIT) - qdel(src) - return + return PROJECTILE_IMPACT_INTERRUPTED if (SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_PREHIT, target) & PROJECTILE_INTERRUPT_HIT) - qdel(src) - return + return PROJECTILE_IMPACT_INTERRUPTED if(mode == PROJECTILE_PIERCE_HIT) pierces += 1 @@ -462,13 +535,11 @@ // Targets should handle their impact logic on our own and if they decide that we hit them, they call our on_hit var/result = target.bullet_act(src, def_zone, mode == PROJECTILE_PIERCE_HIT) if (result != BULLET_ACT_FORCE_PIERCE && max_pierces && pierces >= max_pierces) - qdel(src) - return + return PROJECTILE_IMPACT_SUCCESSFUL // If we're not piercing or phasing, delete ourselves if (result != BULLET_ACT_FORCE_PIERCE && mode != PROJECTILE_PIERCE_HIT && mode != PROJECTILE_PIERCE_PHASE) - qdel(src) - return + return PROJECTILE_IMPACT_SUCCESSFUL // We've piercing though this one, go look for a new target if(!(movement_type & PHASING)) @@ -477,13 +548,14 @@ target = select_target(target_turf, target) + return PROJECTILE_IMPACT_PASSED + /** * Selects a target to hit from a turf * * @params - * T - The turf - * target - The "preferred" atom to hit, usually what we Bumped() first. - * bumped - used to track if something is the reason we impacted in the first place. + * our_turf - Turf on which we hit the target + * bumped - What we've impacted and why this selection was called in the first place. * If set, this atom is always treated as dense by can_hit_target. * * Priority: @@ -523,8 +595,8 @@ // 6. nothing // (returns null) -//Returns true if the target atom is on our current turf and above the right layer -//If direct target is true it's the originally clicked target. +/// Returns true if the target atom is on our current turf and above the right layer +/// If direct target is true it's the originally clicked target. /obj/projectile/proc/can_hit_target(atom/target, direct_target = FALSE, ignore_loc = FALSE, cross_failed = FALSE) if(QDELETED(target) || impacted[target.weak_reference]) return FALSE @@ -568,7 +640,7 @@ return FALSE if(hit_prone_targets) var/mob/living/buckled_to = living_target.lowest_buckled_mob() - if((decayedRange - range) <= MAX_RANGE_HIT_PRONE_TARGETS) // after MAX_RANGE_HIT_PRONE_TARGETS tiles, auto-aim hit for mobs on the floor turns off + if((maximum_range - range) <= MAX_RANGE_HIT_PRONE_TARGETS) // after MAX_RANGE_HIT_PRONE_TARGETS tiles, auto-aim hit for mobs on the floor turns off return TRUE if(ignore_range_hit_prone_targets) // doesn't apply to projectiles that must hit the target in combat mode or something else, no matter what return TRUE @@ -579,19 +651,6 @@ return FALSE return TRUE -/** - * Scan if we should hit something and hit it if we need to - * The difference between this and handling in impact is - * In this we strictly check if we need to impact() something in specific - * If we do, we do - * We don't even check if it got hit already - impact() does that - * In impact there's more code for selecting WHAT to hit - * So this proc is more of checking if we should hit something at all BY having an atom cross us. - */ -/obj/projectile/proc/scan_crossed_hit(atom/movable/crossed_atom) - if(can_hit_target(crossed_atom, direct_target = (crossed_atom == original))) - impact(crossed_atom) - /** * Scans if we should hit something on the turf we just moved to if we haven't already * @@ -619,14 +678,15 @@ */ /obj/projectile/proc/on_entered(datum/source, atom/movable/entered_atom) SIGNAL_HANDLER - scan_crossed_hit(entered_atom) + if(can_hit_target(entered_atom, direct_target = (entered_atom == original))) + impact(entered_atom) /** * Projectile can pass through * Used to not even attempt to Bump() or fail to Cross() anything we already hit. */ /obj/projectile/CanPassThrough(atom/blocker, movement_dir, blocker_opinion) - return ..() || impacted[blocker.weak_reference] + return impacted[blocker.weak_reference] || ..() /** * Projectile moved: @@ -644,7 +704,8 @@ if(temporary_unstoppable_movement) temporary_unstoppable_movement = FALSE movement_type &= ~PHASING - scan_moved_turf() //mostly used for making sure we can hit a non-dense object the user directly clicked on, and for penetrating projectiles that don't bump + // Mostly used for making sure we can hit a non-dense object the user directly clicked on, and for penetrating projectiles that don't bump + scan_moved_turf() /** * Checks if we should pierce something. @@ -674,268 +735,438 @@ /obj/projectile/proc/check_ricochet_flag(atom/target) if((armor_flag in list(ENERGY, LASER)) && (target.flags_ricochet & RICOCHET_SHINY)) return TRUE - if((armor_flag in list(BOMB, BULLET)) && (target.flags_ricochet & RICOCHET_HARD)) return TRUE - return FALSE -/obj/projectile/proc/return_predicted_turf_after_moves(moves, forced_angle) //I say predicted because there's no telling that the projectile won't change direction/location in flight. - if(!trajectory && isnull(forced_angle) && isnull(angle)) - return FALSE - var/datum/point/vector/current = trajectory - if(!current) - var/turf/T = get_turf(src) - current = new(T.x, T.y, T.z, pixel_x, pixel_y, isnull(forced_angle)? angle : forced_angle, SSprojectiles.global_pixel_speed) - var/datum/point/vector/v = current.return_vector_after_increments(moves * SSprojectiles.global_iterations_per_move) - return v.return_turf() - -/obj/projectile/proc/return_pathing_turfs_in_moves(moves, forced_angle) - var/turf/current = get_turf(src) - var/turf/ending = return_predicted_turf_after_moves(moves, forced_angle) - return get_line(current, ending) - /obj/projectile/Process_Spacemove(movement_dir = 0, continuous_move = FALSE) return TRUE //Bullets don't drift in space -/obj/projectile/process() - last_process = world.time - if(!loc || !fired || !trajectory) - fired = FALSE - return PROCESS_KILL - if(paused || !isturf(loc)) - last_projectile_move += world.time - last_process //Compensates for pausing, so it doesn't become a hitscan projectile when unpaused from charged up ticks. - return - var/elapsed_time_deciseconds = (world.time - last_projectile_move) + time_offset - time_offset = 0 - var/required_moves = speed > 0? FLOOR(elapsed_time_deciseconds / speed, 1) : MOVES_HITSCAN //Would be better if a 0 speed made hitscan but everyone hates those so I can't make it a universal system :< - if(required_moves == MOVES_HITSCAN) - required_moves = SSprojectiles.global_max_tick_moves - else - if(required_moves > SSprojectiles.global_max_tick_moves) - var/overrun = required_moves - SSprojectiles.global_max_tick_moves - required_moves = SSprojectiles.global_max_tick_moves - time_offset += overrun * speed - time_offset += MODULUS(elapsed_time_deciseconds, speed) - SEND_SIGNAL(src, COMSIG_PROJECTILE_BEFORE_MOVE) - for(var/i in 1 to required_moves) - pixel_move(pixel_speed_multiplier, FALSE) - /obj/projectile/proc/fire(fire_angle, atom/direct_target) LAZYINITLIST(impacted) - if(fired_from) + if (fired_from) SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original) - if(firer) + if (firer) RegisterSignal(firer, COMSIG_QDELETING, PROC_REF(firer_deleted)) SEND_SIGNAL(firer, COMSIG_PROJECTILE_FIRER_BEFORE_FIRE, src, fired_from, original) if (original) if (firer != original) RegisterSignal(original, COMSIG_QDELETING, PROC_REF(original_deleted)) - if(!log_override && firer && original && !do_not_log) + if (!log_override && firer && original && !do_not_log) log_combat(firer, original, "fired at", src, "from [get_area_name(src, TRUE)]") //note: mecha projectile logging is handled in /obj/item/mecha_parts/mecha_equipment/weapon/action(). try to keep these messages roughly the sameish just for consistency's sake. - if(direct_target && (get_dist(direct_target, get_turf(src)) <= 1)) // point blank shots - process_hit_loop(direct_target) - if(QDELETED(src)) + if (direct_target && (get_dist(direct_target, get_turf(src)) <= 1)) // point blank shots + impact(direct_target) + if (QDELETED(src)) return var/turf/starting = get_turf(src) - if(isnum(fire_angle)) + if (isnum(fire_angle)) set_angle(fire_angle) - else if(isnull(angle)) //Try to resolve through offsets if there's no angle set. - if(isnull(xo) || isnull(yo)) + else if (isnull(angle)) //Try to resolve through offsets if there's no angle set. + if (isnull(xo) || isnull(yo)) stack_trace("WARNING: Projectile [type] deleted due to being unable to resolve a target after angle was null!") qdel(src) return var/turf/target = locate(clamp(starting + xo, 1, world.maxx), clamp(starting + yo, 1, world.maxy), starting.z) set_angle(get_angle(src, target)) - if(spread) + if (spread) set_angle(angle + (rand() - 0.5) * spread) original_angle = angle - trajectory_ignore_forcemove = TRUE + movement_vector = new(speed, angle) + if (hitscan) + beam_points = list() + free_hitscan_forceMove = TRUE forceMove(starting) - trajectory_ignore_forcemove = FALSE - trajectory = new(starting.x, starting.y, starting.z, pixel_x, pixel_y, angle, SSprojectiles.global_pixel_speed) last_projectile_move = world.time fired = TRUE play_fov_effect(starting, 6, "gunfire", dir = NORTH, angle = angle) SEND_SIGNAL(src, COMSIG_PROJECTILE_FIRE) - if(hitscan) + if (hitscan && !deletion_queued) + record_hitscan_start() process_hitscan() - if(QDELETED(src)) + if (QDELETED(src)) return - if(!(datum_flags & DF_ISPROCESSING)) + if (!(datum_flags & DF_ISPROCESSING)) START_PROCESSING(SSprojectiles, src) - pixel_move(pixel_speed_multiplier, FALSE) //move it now! + // move it now to avoid potentially hitting yourself with firer-hitting projectiles + if (!deletion_queued && !hitscan) + process_movement(max(FLOOR(speed, 1), 1), tile_limit = TRUE) -/obj/projectile/proc/set_angle(new_angle) //wrapper for overrides. +/// Makes projectile home onto the passed target with minor inaccuracy +/obj/projectile/proc/set_homing_target(atom/target) + if(!target || (!isturf(target) && !isturf(target.loc))) + return FALSE + homing = TRUE + homing_target = target + homing_offset_x = rand(homing_inaccuracy_min, homing_inaccuracy_max) + homing_offset_y = rand(homing_inaccuracy_min, homing_inaccuracy_max) + if(prob(50)) + homing_offset_x = -homing_offset_x + if(prob(50)) + homing_offset_y = -homing_offset_y + +/obj/projectile/proc/set_angle(new_angle) + if (angle == new_angle) + return if(!nondirectional_sprite) transform = transform.TurnTo(angle, new_angle) angle = new_angle - if(trajectory) - trajectory.set_angle(new_angle) - if(fired && hitscan && isloc(loc) && (loc != last_angle_set_hitscan_store)) - last_angle_set_hitscan_store = loc - var/datum/point/point_cache = new (src) - point_cache = trajectory.copy_to() - store_hitscan_collision(point_cache) - return TRUE - -/obj/projectile/proc/firer_deleted(datum/source) - SIGNAL_HANDLER - // Shooting yourself point-blank - if (firer == original) - original = null - firer = null - -/obj/projectile/proc/original_deleted(datum/source) - SIGNAL_HANDLER - original = null + if(movement_vector) + movement_vector.set_angle(new_angle) + if(fired && hitscan && isturf(loc)) + create_hitscan_point() /// Same as set_angle, but the reflection continues from the center of the object that reflects it instead of the side -/obj/projectile/proc/set_angle_centered(new_angle) +/obj/projectile/proc/set_angle_centered(center_turf, new_angle) + if (angle == new_angle) + return if(!nondirectional_sprite) transform = transform.TurnTo(angle, new_angle) + free_hitscan_forceMove = TRUE + forceMove(center_turf) + entry_x = 0 + entry_y = 0 angle = new_angle - if(trajectory) - trajectory.set_angle(new_angle) + if(movement_vector) + movement_vector.set_angle(new_angle) + if(fired && hitscan && isturf(loc)) + create_hitscan_point(tile_center = TRUE) - var/list/coordinates = trajectory.return_coordinates() - trajectory.set_location(coordinates[1], coordinates[2], coordinates[3]) // Sets the trajectory to the center of the tile it bounced at +/obj/projectile/vv_edit_var(var_name, var_value) + if(var_name == NAMEOF(src, angle)) + set_angle(var_value) + return TRUE + return ..() - if(fired && hitscan && isloc(loc) && (loc != last_angle_set_hitscan_store)) // Handles hitscan projectiles - last_angle_set_hitscan_store = loc - var/datum/point/point_cache = new (src) - point_cache.initialize_location(coordinates[1], coordinates[2], coordinates[3]) // Take the center of the hitscan collision tile - store_hitscan_collision(point_cache) - return TRUE +/* + * Projectile's process calculates the amount of pixels that it needs to move per tick and calls moveloop processing + * There is a strict cap on how many pixels it can move in a tick to prevent them from turning into hitscans during lag + * Path that the projectile could not finish would be stored in the overrun variable to be processed next tick + */ -/obj/projectile/forceMove(atom/target) - if(!isloc(target) || !isloc(loc) || !z) - return ..() - var/zc = target.z != z - var/old = loc - if(zc) - before_z_change(old, target) - . = ..() - if(QDELETED(src)) // we coulda bumped something +/obj/projectile/process() + last_process = world.time + if(!loc || !fired || !movement_vector) + fired = FALSE + return PROCESS_KILL + + // If last tick the projectile impacted something or reached its range, don't process it + if (deletion_queued == PROJECTILE_IMPACT_DELETE) + ticks_to_deletion -= 1 + if (!ticks_to_deletion) + qdel(src) return - if(trajectory && !trajectory_ignore_forcemove && isturf(target)) - if(hitscan) - finalize_hitscan_and_generate_tracers(FALSE) - trajectory.initialize_location(target.x, target.y, target.z, 0, 0) - if(hitscan) - record_hitscan_start(RETURN_PRECISE_POINT(src)) - if(zc) - after_z_change(old, target) - -/obj/projectile/proc/after_z_change(atom/olcloc, atom/newloc) - return -/obj/projectile/proc/before_z_change(atom/oldloc, atom/newloc) - return + if (deletion_queued == PROJECTILE_RANGE_DELETE) + on_range() + return -/obj/projectile/vv_edit_var(var_name, var_value) - switch(var_name) - if(NAMEOF(src, angle)) - set_angle(var_value) - return TRUE - else - return ..() + if(paused || !isturf(loc)) + // Compensates for pausing, so it doesn't become a hitscan projectile when unpaused from charged up ticks. + last_projectile_move = last_process + return -/obj/projectile/proc/set_pixel_speed(new_speed) - if(trajectory) - trajectory.set_speed(new_speed) - return TRUE - return FALSE + if (hitscan) + process_hitscan() + return -/obj/projectile/proc/record_hitscan_start(datum/point/point_cache) - if(point_cache) - beam_segments = list() - beam_index = point_cache - beam_segments[beam_index] = null //record start. + // Calculates how many pixels should be moved this tick, including overrun debt from the previous tick + var/elapsed_time = world.time - last_projectile_move + var/pixels_to_move = elapsed_time * SSprojectiles.pixels_per_decisecond * speed + overrun + overrun = 0 -/obj/projectile/proc/process_hitscan() - var/safety = range * 10 - record_hitscan_start(RETURN_POINT_VECTOR_INCREMENT(src, angle, MUZZLE_EFFECT_PIXEL_INCREMENT, 1)) - while(loc && !QDELETED(src)) - if(paused) - stoplag(1) - continue - if(safety-- <= 0) - if(loc) - Bump(loc) - if(!QDELETED(src)) - qdel(src) - return //Kill! - pixel_move(1, TRUE) - // No kevinz I do not care that this is a hitscan weapon, it is not allowed to travel 100 turfs in a tick - if(CHECK_TICK && QDELETED(src)) - return + if (pixels_to_move > SSprojectiles.max_pixels_per_tick) + overrun = pixels_to_move - SSprojectiles.max_pixels_per_tick + pixels_to_move = SSprojectiles.max_pixels_per_tick + + overrun += MODULUS(pixels_to_move, 1) + pixels_to_move = FLOOR(pixels_to_move, 1) + SEND_SIGNAL(src, COMSIG_PROJECTILE_BEFORE_MOVE) + + // Registering turf entries is done here instead of a connect_loc because else it could be called multiple times per tick and waste performance + if (last_tick_turf) + UnregisterSignal(last_tick_turf, COMSIG_ATOM_ENTERED) -/obj/projectile/proc/pixel_move(trajectory_multiplier, hitscanning = FALSE) - if(!loc || !trajectory) + process_movement(pixels_to_move) + + if (!QDELETED(src) && !deletion_queued && isturf(loc)) + RegisterSignal(loc, COMSIG_ATOM_ENTERED, PROC_REF(on_entered)) + last_tick_turf = loc + +/* + * Main projectile movement cycle. + * Normal behavior moves projectiles in a straight line through tiles, but it gets trickier with homing. + * Every pixels_per_decisecond we will stop and call process_homing(), which while a bit rough, does not have a significant performance impact + * This proc needs to be very performant, so do not add overridable logic that can be handled in homing or animations here. + * + * pixels_to_move determines how many pixels the projectile should move + * hitscan prevents animation logic from running + * tile_limit prevents any movements past the first tile change + */ +/obj/projectile/proc/process_movement(pixels_to_move, hitscan = FALSE, tile_limit = FALSE) + if (!isturf(loc) || !movement_vector) return + var/total_move_distance = pixels_to_move last_projectile_move = world.time - if(homing) - process_homing() - var/forcemoved = FALSE - for(var/i in 1 to SSprojectiles.global_iterations_per_move) - if(QDELETED(src)) - return - trajectory.increment(trajectory_multiplier) - var/turf/cur_turf = trajectory.return_turf() - if(!istype(cur_turf)) - // step back to the last valid turf before we Destroy - trajectory.increment(-trajectory_multiplier) + while (pixels_to_move > 0 && isturf(loc) && !QDELETED(src) && !deletion_queued) + // Because pixel_x/y represents offset and not actual visual position of the projectile, we add 16 pixels to each and cut the excess because projectiles are not meant to be highly offset by default + var/pixel_x_actual = pixel_x + ICON_SIZE_X / 2 + if(pixel_x_actual > ICON_SIZE_X) + pixel_x_actual = pixel_x_actual % ICON_SIZE_X + + var/pixel_y_actual = pixel_y + ICON_SIZE_Y / 2 + if(pixel_y_actual > ICON_SIZE_Y) + pixel_y_actual = pixel_y_actual % ICON_SIZE_Y + + var/distance_to_border = INFINITY + // What distances do we need to move to hit the horizontal/vertical turf border + var/x_to_border = INFINITY + var/y_to_border = INFINITY + // If we're moving strictly up/down/left/right then one of these can be 0 and produce div by zero + if (movement_vector.pixel_x) + var/x_border_dist = -pixel_x_actual + if (movement_vector.pixel_x > 0) + x_border_dist = ICON_SIZE_X - pixel_x_actual + x_to_border = x_border_dist / movement_vector.pixel_x + distance_to_border = x_to_border + + if (movement_vector.pixel_y) + var/y_border_dist = -pixel_y_actual + if (movement_vector.pixel_y > 0) + y_border_dist = ICON_SIZE_Y - pixel_y_actual + y_to_border = y_border_dist / movement_vector.pixel_y + distance_to_border = min(distance_to_border, y_to_border) + + // Something went extremely wrong + if (distance_to_border == INFINITY) + stack_trace("WARNING: Projectile had an empty movement vector and tried to process") qdel(src) return - if (cur_turf == loc) - continue - if (cur_turf.z == loc.z) - step_towards(src, cur_turf) - hitscan_last = loc - SEND_SIGNAL(src, COMSIG_PROJECTILE_PIXEL_STEP) - continue - var/old = loc - before_z_change(loc, cur_turf) - trajectory_ignore_forcemove = TRUE - forceMove(cur_turf) - trajectory_ignore_forcemove = FALSE - after_z_change(old, loc) - if(!hitscanning) - pixel_x = trajectory.return_px() - pixel_y = trajectory.return_py() - forcemoved = TRUE - hitscan_last = loc - SEND_SIGNAL(src, COMSIG_PROJECTILE_PIXEL_STEP) - if(QDELETED(src)) //deleted on last move - return - if(!hitscanning && !forcemoved) - pixel_x = trajectory.return_px() - trajectory.mpx * trajectory_multiplier * SSprojectiles.global_iterations_per_move - pixel_y = trajectory.return_py() - trajectory.mpy * trajectory_multiplier * SSprojectiles.global_iterations_per_move - animate(src, pixel_x = trajectory.return_px(), pixel_y = trajectory.return_py(), time = 1, flags = ANIMATION_END_NOW) - Range() -/obj/projectile/proc/process_homing() //may need speeding up in the future performance wise. + var/distance_to_move = min(distance_to_border, pixels_to_move) + // For homing we cap the maximum distance to move every loop + if (homing && distance_to_move > SSprojectiles.pixels_per_decisecond) + distance_to_move = SSprojectiles.pixels_per_decisecond + + // Figure out if we move to the next turf and if so, what its positioning relatively to us is + var/x_shift = distance_to_move >= x_to_border ? SIGN(movement_vector.pixel_x) : 0 + var/y_shift = distance_to_move >= y_to_border ? SIGN(movement_vector.pixel_y) : 0 + var/moving_turfs = x_shift || y_shift + // Calculate where in the turf we will be when we cross the edge. + // This is a projectile variable because its also used in hit VFX + entry_x = pixel_x + movement_vector.pixel_x * distance_to_move - x_shift * ICON_SIZE_X + entry_y = pixel_y + movement_vector.pixel_y * distance_to_move - y_shift * ICON_SIZE_Y + var/delete_distance = 0 + + if (moving_turfs) + var/turf/new_turf = locate(x + x_shift, y + y_shift, z) + // We've hit an invalid turf, end of a z level or smth went wrong + if (!istype(new_turf)) + qdel(src) + return + + // Move to the next tile + step_towards(src, new_turf) + SEND_SIGNAL(src, COMSIG_PROJECTILE_MOVE_PROCESS_STEP) + // We hit something and got deleted, stop the loop + if (QDELETED(src)) + return + if (loc != new_turf) + moving_turfs = FALSE + // If we've impacted something, we need to animate our movement until the actual hit + // Otherwise the projectile visually disappears slightly before the actual impact + if (deletion_queued) + // distance_to_move is how much we have to step to get to the next turf, hypotenuse is how much we need + // to move in the next turf to get from entry to impact position + delete_distance = distance_to_move + sqrt((impact_x - entry_x) ** 2 + (impact_y - entry_y) ** 2) + + // We cannot move more than one turf worth of distance per loop, so this is a safe solution + pixels_moved_last_tile += distance_to_move + if (!deletion_queued && pixels_moved_last_tile >= ICON_SIZE_ALL) + reduce_range() + if (QDELETED(src)) + return + // Similarly with range out deletion, need to calculate how many pixels we can actually move before deleting + if (deletion_queued) + delete_distance = distance_to_move - (ICON_SIZE_ALL - pixels_moved_last_tile) + + if (deletion_queued) + // We moved to the next turf first, then impacted something + // This means that we need to offset our visual position back to the previous turf, then figure out + // how much we moved on the next turf (or we didn't move at all in which case we both shifts are 0 anyways) + if (moving_turfs) + pixel_x -= x_shift * ICON_SIZE_X + pixel_y -= y_shift * ICON_SIZE_Y + + // Similarly to normal animate code, but use lowered deletion distance instead. + var/delete_x = pixel_x + movement_vector.pixel_x * delete_distance + var/delete_y = pixel_y + movement_vector.pixel_y * delete_distance + // In order to keep a consistent speed, calculate at what point between ticks we get deleted + var/animate_time = world.tick_lag * delete_distance / total_move_distance + // Sometimes we need to move *just a bit* more than we can afford this tick - in this case, delete a tick after + // so we don't disappear before impact. This shouldn't be more than 1, ever. + if (delete_distance > pixels_to_move) + ticks_to_deletion += 1 + // We can use animation chains to visually disappear between ticks. + if (!move_animate(delete_x, delete_y, animate_time, deleting = TRUE)) + animate(src, pixel_x = delete_x, pixel_y = delete_y, time = animate_time, flags = ANIMATION_PARALLEL | ANIMATION_CONTINUE) + animate(alpha = 0, time = 0, flags = ANIMATION_CONTINUE) + return + + pixels_to_move -= distance_to_move + // animate() instantly changes pixel_x/y values and just interpolates them client-side so next loop processes properly + if (hitscan) + pixel_x = entry_x + pixel_y = entry_y + else + // We need to shift back to the tile we were on before moving + pixel_x -= x_shift * ICON_SIZE_X + pixel_y -= y_shift * ICON_SIZE_Y + if (!move_animate(entry_x, entry_y)) + animate(src, pixel_x = entry_x, pixel_y = entry_y, time = world.tick_lag * distance_to_move / total_move_distance, flags = ANIMATION_PARALLEL | ANIMATION_CONTINUE) + + // Homing caps our movement speed per loop while leaving per tick speed intact, so we can just call process_homing every loop here + if (homing) + process_homing() + + // We've hit a timestop field, abort any remaining movement + if (paused) + return + + // Prevents long-range high-speed projectiles from ruining the server performance by moving 100 tiles per tick when subsystem is set to a high cap + if (TICK_CHECK) + // If we ran out of time, add whatever distance we're yet to pass to overrun debt to be processed next tick and break the loop + overrun += pixels_to_move + return + + if (tile_limit && moving_turfs) + return + +/// Called every time projectile animates its movement, in case child wants to have custom animations. +/// Returning TRUE cancels normal animation +/obj/projectile/proc/move_animate(animate_x, animate_y, animate_time = world.tick_lag, deleting = FALSE) + return FALSE + +/// Called every projectile loop for homing or alternatively, custom trajectory changes. +/obj/projectile/proc/process_homing() if(!homing_target) - return FALSE + return var/datum/point/new_point = RETURN_PRECISE_POINT(homing_target) new_point.x += clamp(homing_offset_x, 1, world.maxx) new_point.y += clamp(homing_offset_y, 1, world.maxy) var/new_angle = closer_angle_difference(angle, angle_between_points(RETURN_PRECISE_POINT(src), new_point)) set_angle(angle + clamp(new_angle, -homing_turn_speed, homing_turn_speed)) -/obj/projectile/proc/set_homing_target(atom/target) - if(!target || (!isturf(target) && !isturf(target.loc))) - return FALSE - homing = TRUE - homing_target = target - homing_offset_x = rand(homing_inaccuracy_min, homing_inaccuracy_max) - homing_offset_y = rand(homing_inaccuracy_min, homing_inaccuracy_max) - if(prob(50)) - homing_offset_x = -homing_offset_x - if(prob(50)) - homing_offset_y = -homing_offset_y +/// Attempts to force the projectile to move until the subsystem runs out of processing time, the projectile impacts something or gets frozen by timestop +/obj/projectile/proc/process_hitscan() + if (isnull(movement_vector)) + qdel(src) + return + + while (isturf(loc) && !QDELETED(src)) + process_movement(ICON_SIZE_ALL, hitscan = TRUE) + if (TICK_CHECK || paused || QDELETED(src)) + return + +/// Creates (or wipes clean) list of tracer keypoints and creates a first point. +/obj/projectile/proc/record_hitscan_start(offset = TRUE) + if (isnull(beam_points)) + beam_points = list() + else + QDEL_LIST_ASSOC(beam_points) + QDEL_NULL(last_point) + last_point = RETURN_PRECISE_POINT(src) + // If moving, increment its position a bit to prevent it from looking like its coming from firer's ass + if (offset && !isnull(movement_vector)) + last_point.increment(movement_vector.pixel_x * MUZZLE_EFFECT_PIXEL_INCREMENT, movement_vector.pixel_y * MUZZLE_EFFECT_PIXEL_INCREMENT) + beam_points[last_point] = null + +/// Creates a new keypoint in which the tracer will split +/obj/projectile/proc/create_hitscan_point(impact = FALSE, tile_center = FALSE, broken_segment = FALSE) + var/atom/handle_atom = last_impact_turf || src + var/atom/used_point = tile_center ? loc : src + var/datum/point/new_point = impact ? new /datum/point(handle_atom.x, handle_atom.y, handle_atom.z, impact_x, impact_y) : RETURN_PRECISE_POINT(used_point) + if (!broken_segment) + beam_points[last_point] = new_point + beam_points[new_point] = null + last_point = new_point + +/obj/projectile/forceMove(atom/target) + if (!hitscan || isnull(beam_points)) + return ..() + create_hitscan_point() + . = ..() + if(!isturf(loc) || !isturf(target) || !z || QDELETED(src) || deletion_queued) + return + if (isnull(movement_vector) || free_hitscan_forceMove) + return + // Create firing VFX and start a new chain because we most likely got teleported + generate_hitscan_tracers(impact = FALSE) + original_angle = angle + record_hitscan_start(offset = FALSE) + +/obj/projectile/proc/generate_hitscan_tracers(impact = TRUE) + if (!length(beam_points)) + return + + if (impact) + create_hitscan_point(impact = TRUE) + + if (tracer_type) + // Stores all turfs we've created light effects on, in order to not dupe them if we enter a reflector loop + // Uses an assoc list for performance reasons + var/list/passed_turfs = list() + for (var/beam_point in beam_points) + generate_tracer(beam_point, passed_turfs) + + if (muzzle_type) + var/datum/point/start_point = beam_points[1] + var/atom/movable/muzzle_effect = new muzzle_type(loc) + start_point.move_atom_to_src(muzzle_effect) + var/matrix/matrix = new + matrix.Turn(original_angle) + muzzle_effect.transform = matrix + muzzle_effect.color = color + muzzle_effect.set_light(muzzle_flash_range, muzzle_flash_intensity, muzzle_flash_color_override || color) + QDEL_IN(muzzle_effect, PROJECTILE_TRACER_DURATION) + + if (impact_type) + var/atom/movable/impact_effect = new impact_type(loc) + last_point.move_atom_to_src(impact_effect) + var/matrix/matrix = new + matrix.Turn(angle) + impact_effect.transform = matrix + impact_effect.color = color + impact_effect.set_light(impact_light_range, impact_light_intensity, impact_light_color_override || color) + QDEL_IN(impact_effect, PROJECTILE_TRACER_DURATION) + +/obj/projectile/proc/generate_tracer(datum/point/start_point, list/passed_turfs) + if (isnull(beam_points[start_point])) + return + + var/datum/point/end_point = beam_points[start_point] + var/datum/point/midpoint = point_midpoint_points(start_point, end_point) + var/obj/effect/projectile/tracer/tracer_effect = new tracer_type(midpoint.return_turf()) + tracer_effect.apply_vars( + angle_override = angle_between_points(start_point, end_point), + p_x = midpoint.pixel_x, + p_y = midpoint.pixel_y, + color_override = color, + scaling = pixel_length_between_points(start_point, end_point) / ICON_SIZE_ALL + ) + SET_PLANE_EXPLICIT(tracer_effect, GAME_PLANE, src) + + QDEL_IN(tracer_effect, PROJECTILE_TRACER_DURATION) + + if (!hitscan_light_range || !hitscan_light_intensity) + return + + var/list/turf/light_line = get_line(start_point.return_turf(), end_point.return_turf()) + for (var/turf/light_turf as anything in light_line) + if (passed_turfs[light_turf]) + continue + passed_turfs[light_turf] = TRUE + QDEL_IN(new /obj/effect/abstract/projectile_lighting(light_turf, hitscan_light_color_override || color, hitscan_light_range, hitscan_light_intensity), PROJECTILE_TRACER_DURATION) /** * Aims the projectile at a target. @@ -950,29 +1181,50 @@ * - deviation: (Optional) How the trajectory should deviate from the target in degrees. * - //Spread is FORCED! */ -/obj/projectile/proc/preparePixelProjectile(atom/target, atom/source, list/modifiers = null, deviation = 0) +/obj/projectile/proc/aim_projectile(atom/target, atom/source, list/modifiers = null, deviation = 0) if(!(isnull(modifiers) || islist(modifiers))) - stack_trace("WARNING: Projectile [type] fired with non-list modifiers, likely was passed click params.") + stack_trace("WARNING: Projectile [type] fired with non-list modifiers, likely was passed click params. Modifiers were the following: [modifiers]") modifiers = null var/turf/source_loc = get_turf(source) var/turf/target_loc = get_turf(target) + if(isnull(source_loc)) stack_trace("WARNING: Projectile [type] fired from nullspace.") qdel(src) return FALSE - trajectory_ignore_forcemove = TRUE - forceMove(source_loc) - trajectory_ignore_forcemove = FALSE + if(fired) + stack_trace("WARNING: Projectile [type] was aimed after already being fired.") + qdel(src) + return FALSE + free_hitscan_forceMove = TRUE + forceMove(source_loc) starting = source_loc pixel_x = source.pixel_x pixel_y = source.pixel_y original = target + + // Trim off excess pixel_x/y by converting them into turf offset + if (abs(pixel_x) > ICON_SIZE_X / 2) + for (var/i in 1 to floor(abs(pixel_x) + ICON_SIZE_X / 2) / ICON_SIZE_X) + var/turf/new_loc = get_step(source_loc, pixel_x > 0 ? EAST : WEST) + if (!istype(new_loc)) + break + source_loc = new_loc + pixel_x = pixel_x % (ICON_SIZE_X / 2) + + if (abs(pixel_y) > ICON_SIZE_Y / 2) + for (var/i in 1 to floor(abs(pixel_y) + ICON_SIZE_Y / 2) / ICON_SIZE_Y) + var/turf/new_loc = get_step(source_loc, pixel_y > 0 ? NORTH : SOUTH) + if (!istype(new_loc)) + break + source_loc = new_loc + pixel_y = pixel_y % (ICON_SIZE_X / 2) + if(length(modifiers)) var/list/calculated = calculate_projectile_angle_and_pixel_offsets(source, target_loc && target, modifiers) - p_x = calculated[2] p_y = calculated[3] set_angle(calculated[1] + deviation) @@ -1031,63 +1283,11 @@ //Calculate the "resolution" of screen based on client's view and world's icon size. This will work if the user can view more tiles than average. var/list/screenview = view_to_pixels(user.client.view) - var/ox = round(screenview[1] / 2) - user.client.pixel_x //"origin" x - var/oy = round(screenview[2] / 2) - user.client.pixel_y //"origin" y + var/ox = round(screenview[1] * 0.5) - user.client.pixel_x //"origin" x + var/oy = round(screenview[2] * 0.5) - user.client.pixel_y //"origin" y angle = ATAN2(tx - oy, ty - ox) return list(angle, p_x, p_y) -/obj/projectile/Destroy() - if(hitscan) - finalize_hitscan_and_generate_tracers() - STOP_PROCESSING(SSprojectiles, src) - cleanup_beam_segments() - firer = null - original = null - if(trajectory) - QDEL_NULL(trajectory) - return ..() - -/obj/projectile/proc/cleanup_beam_segments() - QDEL_LIST_ASSOC(beam_segments) - beam_segments = list() - QDEL_NULL(beam_index) - -/obj/projectile/proc/finalize_hitscan_and_generate_tracers(impacting = TRUE) - if(trajectory && beam_index) - var/datum/point/point_cache = trajectory.copy_to() - beam_segments[beam_index] = point_cache - generate_hitscan_tracers(null, null, impacting) - -/obj/projectile/proc/generate_hitscan_tracers(cleanup = TRUE, duration = 3, impacting = TRUE) - if(!length(beam_segments)) - return - if(tracer_type) - var/tempref = REF(src) - for(var/datum/point/beam_point in beam_segments) - generate_tracer_between_points(beam_point, beam_segments[beam_point], tracer_type, color, duration, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity, tempref) - if(muzzle_type && duration > 0) - var/datum/point/beam_point = beam_segments[1] - var/atom/movable/thing = new muzzle_type - beam_point.move_atom_to_src(thing) - var/matrix/matrix = new - matrix.Turn(original_angle) - thing.transform = matrix - thing.color = color - thing.set_light(muzzle_flash_range, muzzle_flash_intensity, muzzle_flash_color_override? muzzle_flash_color_override : color) - QDEL_IN(thing, duration) - if(impacting && impact_type && duration > 0) - var/datum/point/beam_point = beam_segments[beam_segments[beam_segments.len]] - var/atom/movable/thing = new impact_type - beam_point.move_atom_to_src(thing) - var/matrix/matrix = new - matrix.Turn(angle) - thing.transform = matrix - thing.color = color - thing.set_light(impact_light_range, impact_light_intensity, impact_light_color_override? impact_light_color_override : color) - QDEL_IN(thing, duration) - if(cleanup) - cleanup_beam_segments() - /obj/projectile/experience_pressure_difference() return @@ -1126,15 +1326,11 @@ firer = hit_atom yo = new_y - current_tile.y xo = new_x - current_tile.x - var/new_angle_s = angle + rand(120,240) + var/new_angle_s = angle + rand(120, 240) while(new_angle_s > 180) // Translate to regular projectile degrees new_angle_s -= 360 set_angle(new_angle_s) -#undef MOVES_HITSCAN -#undef MUZZLE_EFFECT_PIXEL_INCREMENT -#undef MAX_RANGE_HIT_PRONE_TARGETS - /// Fire a projectile from this atom at another atom /atom/proc/fire_projectile(projectile_type, atom/target, sound, firer, list/ignore_targets = list()) if (!isnull(sound)) @@ -1150,7 +1346,7 @@ bullet.yo = target.y - startloc.y bullet.xo = target.x - startloc.x bullet.original = target - bullet.preparePixelProjectile(target, src) + bullet.aim_projectile(target, src) bullet.fire() return bullet @@ -1167,3 +1363,6 @@ if(!isnull(embed_data) && !GLOB.embed_by_type[embed_data.type]) qdel(embed_data) embed_data = ispath(embed) ? get_embed_by_type(armor) : embed + +#undef MOVES_HITSCAN +#undef MUZZLE_EFFECT_PIXEL_INCREMENT diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index 355221eab8620..edbb0535ac640 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -15,7 +15,7 @@ light_color = COLOR_SOFT_RED ricochets_max = 50 //Honk! ricochet_chance = 80 - reflectable = REFLECT_NORMAL + reflectable = TRUE wound_bonus = -20 bare_wound_bonus = 10 @@ -45,7 +45,7 @@ impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser damage = 9 wound_bonus = -40 - speed = 1.1 + speed = 0.9 //overclocked laser, does a bit more damage but has much higher wound power (-0 vs -20) /obj/projectile/beam/laser/hellfire @@ -53,7 +53,7 @@ icon_state = "hellfire" wound_bonus = 0 damage = 30 - speed = 0.6 // higher power = faster, that's how light works right + speed = 1.6 light_color = "#FF969D" /obj/projectile/beam/laser/heavylaser diff --git a/code/modules/projectiles/projectile/bullets/_incendiary.dm b/code/modules/projectiles/projectile/bullets/_incendiary.dm index 2e526adb05311..d5c4dc93c5bfa 100644 --- a/code/modules/projectiles/projectile/bullets/_incendiary.dm +++ b/code/modules/projectiles/projectile/bullets/_incendiary.dm @@ -35,7 +35,7 @@ suppressed = SUPPRESSED_VERY damage_type = BURN armor_flag = BOMB - speed = 1.2 + speed = 0.8 wound_bonus = 30 bare_wound_bonus = 30 wound_falloff_tile = -4 diff --git a/code/modules/projectiles/projectile/bullets/pistol.dm b/code/modules/projectiles/projectile/bullets/pistol.dm index 63e491e2f290d..bc64363a2d33f 100644 --- a/code/modules/projectiles/projectile/bullets/pistol.dm +++ b/code/modules/projectiles/projectile/bullets/pistol.dm @@ -79,7 +79,7 @@ icon_state = "smartgun" damage = 10 embed_type = /datum/embed_data/bullet_c160smart - speed = 2 + speed = 0.5 homing_turn_speed = 5 homing_inaccuracy_min = 4 homing_inaccuracy_max = 10 diff --git a/code/modules/projectiles/projectile/bullets/rifle.dm b/code/modules/projectiles/projectile/bullets/rifle.dm index 4caec924625e1..1302aea9315a4 100644 --- a/code/modules/projectiles/projectile/bullets/rifle.dm +++ b/code/modules/projectiles/projectile/bullets/rifle.dm @@ -67,7 +67,7 @@ name = "rebar" icon_state = "rebar" damage = 30 - speed = 0.4 + speed = 2.5 dismemberment = 1 //because a 1 in 100 chance to just blow someones arm off is enough to be cool but also not enough to be reliable armour_penetration = 10 wound_bonus = -20 @@ -93,7 +93,6 @@ name = "rebar" icon_state = "rebar" damage = 45 - speed = 0.4 dismemberment = 2 //It's a budget sniper rifle. armour_penetration = 20 //A bit better versus armor. Gets past anti laser armor or a vest, but doesnt wound proc on sec armor. wound_bonus = 10 @@ -116,7 +115,7 @@ name = "zaukerite shard" icon_state = "rebar_zaukerite" damage = 60 - speed = 0.6 + speed = 1.6 dismemberment = 10 damage_type = TOX eyeblur = 5 @@ -141,7 +140,7 @@ name = "metallic hydrogen bolt" icon_state = "rebar_hydrogen" damage = 35 - speed = 0.6 + speed = 1.6 projectile_piercing = PASSMOB|PASSVEHICLE projectile_phasing = ~(PASSMOB|PASSVEHICLE) max_pierces = 3 @@ -172,7 +171,6 @@ name = "healium bolt" icon_state = "rebar_healium" damage = 0 - speed = 0.4 dismemberment = 0 damage_type = BRUTE armour_penetration = 100 @@ -199,7 +197,6 @@ name = "supermatter bolt" icon_state = "rebar_supermatter" damage = 0 - speed = 0.4 dismemberment = 0 damage_type = TOX embed_type = null diff --git a/code/modules/projectiles/projectile/bullets/shotgun.dm b/code/modules/projectiles/projectile/bullets/shotgun.dm index a2c11f76d4789..3576c9e5a12d1 100644 --- a/code/modules/projectiles/projectile/bullets/shotgun.dm +++ b/code/modules/projectiles/projectile/bullets/shotgun.dm @@ -94,7 +94,7 @@ stamina = 11 sharpness = NONE embed_type = null - speed = 1.2 + speed = 0.8 stamina_falloff_tile = -0.25 ricochets_max = 4 ricochet_chance = 120 @@ -106,7 +106,7 @@ /// Subtracted from the ricochet chance for each tile traveled var/tile_dropoff_ricochet = 4 -/obj/projectile/bullet/pellet/shotgun_rubbershot/Range() +/obj/projectile/bullet/pellet/shotgun_rubbershot/reduce_range() if(ricochet_chance > 0) ricochet_chance -= tile_dropoff_ricochet . = ..() @@ -124,7 +124,7 @@ wound_bonus = -25 bare_wound_bonus = 50 wound_falloff_tile = -10 - speed = 0.8 + speed = 1.2 ricochet_decay_chance = 0.6 ricochet_decay_damage = 0.3 demolition_mod = 10 diff --git a/code/modules/projectiles/projectile/bullets/sniper.dm b/code/modules/projectiles/projectile/bullets/sniper.dm index 6118d90644d9e..44caa2ebe2162 100644 --- a/code/modules/projectiles/projectile/bullets/sniper.dm +++ b/code/modules/projectiles/projectile/bullets/sniper.dm @@ -2,7 +2,7 @@ /obj/projectile/bullet/p50 name =".50 BMG bullet" - speed = 0.4 + speed = 2.5 range = 400 // Enough to travel from one corner of the Z to the opposite corner and then some. damage = 70 paralyze = 100 @@ -87,7 +87,7 @@ name = ".50 BMG aggression dissuasion round" icon_state = "gaussstrong" damage = 25 - speed = 0.3 + speed = 3 range = 16 /obj/projectile/bullet/p50/marksman diff --git a/code/modules/projectiles/projectile/bullets/special.dm b/code/modules/projectiles/projectile/bullets/special.dm index c1299b3bed0f0..cd1439cbb01ae 100644 --- a/code/modules/projectiles/projectile/bullets/special.dm +++ b/code/modules/projectiles/projectile/bullets/special.dm @@ -79,8 +79,7 @@ /obj/projectile/bullet/coin name = "marksman coin" icon_state = "coinshot" - pixel_speed_multiplier = 0.333 - speed = 1 + speed = 0.33 damage = 5 color = "#dbdd4c" @@ -100,7 +99,7 @@ /obj/projectile/bullet/coin/Initialize(mapload, turf/the_target, mob/original_firer) src.original_firer = original_firer target_turf = the_target - range = (get_dist(original_firer, target_turf) + 3) * 3 // 3 tiles past the origin (the *3 is because Range() ticks 3 times a tile because of the slower speed) + range = (get_dist(original_firer, target_turf) + 3) * 3 // 3 tiles past the origin (the *3 is because reduce_range() ticks 3 times a tile because of the slower speed) . = ..() @@ -167,7 +166,7 @@ if(Adjacent(current_turf, target_turf)) new_splitshot.fire(get_angle(current_turf, target_turf), direct_target = next_target) else - new_splitshot.preparePixelProjectile(next_target, get_turf(src)) + new_splitshot.aim_projectile(next_target, get_turf(src)) new_splitshot.fire() if(istype(next_target, /obj/projectile/bullet/coin)) // handle further splitshot checks diff --git a/code/modules/projectiles/projectile/energy/_energy.dm b/code/modules/projectiles/projectile/energy/_energy.dm index 8527041e86006..0985691a5fb72 100644 --- a/code/modules/projectiles/projectile/energy/_energy.dm +++ b/code/modules/projectiles/projectile/energy/_energy.dm @@ -4,5 +4,5 @@ damage = 0 damage_type = BURN armor_flag = ENERGY - reflectable = REFLECT_NORMAL + reflectable = TRUE impact_effect_type = /obj/effect/temp_visual/impact_effect/energy diff --git a/code/modules/projectiles/projectile/energy/nuclear_particle.dm b/code/modules/projectiles/projectile/energy/nuclear_particle.dm index 6626cb9b5ed75..08c3569028664 100644 --- a/code/modules/projectiles/projectile/energy/nuclear_particle.dm +++ b/code/modules/projectiles/projectile/energy/nuclear_particle.dm @@ -6,7 +6,7 @@ armor_flag = ENERGY damage_type = TOX damage = 10 - speed = 0.4 + speed = 2.5 hitsound = 'sound/items/weapons/emitter2.ogg' impact_type = /obj/effect/projectile/impact/xray var/static/list/particle_colors = list( diff --git a/code/modules/projectiles/projectile/energy/photon.dm b/code/modules/projectiles/projectile/energy/photon.dm index 5210737a19801..00ae9281af7b6 100644 --- a/code/modules/projectiles/projectile/energy/photon.dm +++ b/code/modules/projectiles/projectile/energy/photon.dm @@ -1,4 +1,4 @@ -#define MULTIPLY_PIXELSPEED 0.8 +#define MULTIPLY_SPEED 0.8 /obj/projectile/energy/photon name = "photon bolt" @@ -9,7 +9,6 @@ damage = 5 //It's literally a weaker tesla bolt, which is already weak. Don't worry, we'll fix that. range = 20 speed = 1 - pixel_speed_multiplier = 1 projectile_piercing = PASSMOB light_color = LIGHT_COLOR_DEFAULT light_system = OVERLAY_LIGHT @@ -44,9 +43,9 @@ if(prob(40)) new /obj/effect/hotspot(arrived) -/obj/projectile/energy/photon/Range() +/obj/projectile/energy/photon/reduce_range() . = ..() - pixel_speed_multiplier *= MULTIPLY_PIXELSPEED + speed *= MULTIPLY_SPEED /obj/projectile/energy/photon/on_range() do_sparks(rand(4, 9), FALSE, src) @@ -55,4 +54,4 @@ flashed_mob.flash_act() return ..() -#undef MULTIPLY_PIXELSPEED +#undef MULTIPLY_SPEED diff --git a/code/modules/projectiles/projectile/energy/tesla.dm b/code/modules/projectiles/projectile/energy/tesla.dm index 9dfe043a01565..91677dab2fc8d 100644 --- a/code/modules/projectiles/projectile/energy/tesla.dm +++ b/code/modules/projectiles/projectile/energy/tesla.dm @@ -29,7 +29,7 @@ name = "tesla orb" icon_state = "ice_1" damage = 0 - speed = 1.5 + speed = 0.66 var/shock_damage = 5 /obj/projectile/energy/tesla_cannon/on_hit(atom/target, blocked = 0, pierce_hit) diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm index 5c050bdebb6c2..1755d43de1f6b 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -238,7 +238,8 @@ /obj/projectile/magic/locker/Destroy() locker_suck = FALSE - RemoveElement(/datum/element/connect_loc, projectile_connections) //We do this manually so the forcemoves don't "hit" us. This behavior is kinda dumb, someone refactor this + if (last_tick_turf) + UnregisterSignal(last_tick_turf, COMSIG_ATOM_ENTERED) for(var/atom/movable/AM in contents) AM.forceMove(get_turf(src)) . = ..() @@ -400,8 +401,10 @@ var/trail_icon = 'icons/effects/magic.dmi' /// The icon state the trail uses. var/trail_icon_state = "arrow" + /// Can we spawn a trail effect again? + COOLDOWN_DECLARE(trail_cooldown) -/obj/projectile/magic/aoe/Range() +/obj/projectile/magic/aoe/reduce_range() if(trigger_range >= 1) for(var/mob/living/nearby_guy in range(trigger_range, get_turf(src))) if(nearby_guy.stat == DEAD) @@ -413,38 +416,31 @@ return ..() -/obj/projectile/magic/aoe/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE) +/obj/projectile/magic/aoe/prehit_pierce(atom/target) if(can_only_hit_target && target != original) - return FALSE + return PROJECTILE_PIERCE_PHASE return ..() -/obj/projectile/magic/aoe/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) - . = ..() - if(trail) - create_trail() - -/// Creates and handles the trail that follows the projectile. -/obj/projectile/magic/aoe/proc/create_trail() - if(!trajectory) +/obj/projectile/magic/aoe/move_animate(animate_x, animate_y, animate_time = world.tick_lag, deleting = FALSE) + if(!trail || !movement_vector || deleting || !COOLDOWN_FINISHED(src, trail_cooldown)) return - var/datum/point/vector/previous = trajectory.return_vector_after_increments(1, -1) - var/obj/effect/overlay/trail = new /obj/effect/overlay(previous.return_turf()) - trail.pixel_x = previous.return_px() - trail.pixel_y = previous.return_py() - trail.icon = trail_icon - trail.icon_state = trail_icon_state - //might be changed to temp overlay - trail.set_density(FALSE) - trail.mouse_opacity = MOUSE_OPACITY_TRANSPARENT - QDEL_IN(trail, trail_lifespan) + var/obj/effect/overlay/trail_effect = new /obj/effect/overlay(loc) + trail_effect.pixel_x = pixel_x + trail_effect.pixel_y = pixel_y + trail_effect.icon = trail_icon + trail_effect.icon_state = trail_icon_state + trail_effect.set_density(FALSE) + trail_effect.mouse_opacity = MOUSE_OPACITY_TRANSPARENT + QDEL_IN(trail_effect, trail_lifespan) + COOLDOWN_START(src, trail_cooldown, trail_lifespan) /obj/projectile/magic/aoe/lightning name = "lightning bolt" icon_state = "tesla_projectile" //Better sprites are REALLY needed and appreciated!~ damage = 15 damage_type = BURN - speed = 0.3 + speed = 3.5 /// The power of the zap itself when it electrocutes someone var/zap_power = 2e4 @@ -513,8 +509,7 @@ name = "magic missile" icon_state = "magicm" range = 100 - speed = 1 - pixel_speed_multiplier = 0.2 + speed = 0.2 trigger_range = 0 can_only_hit_target = TRUE paralyze = 6 SECONDS @@ -540,8 +535,7 @@ antimagic_flags = MAGIC_RESISTANCE_HOLY ignored_factions = list(FACTION_CULT) range = 105 - speed = 1 - pixel_speed_multiplier = 1/7 + speed = 0.15 /obj/projectile/magic/aoe/juggernaut/on_hit(atom/target, blocked = 0, pierce_hit) . = ..() diff --git a/code/modules/projectiles/projectile/special/curse.dm b/code/modules/projectiles/projectile/special/curse.dm index 5f1f1017e836e..8d1030c8c93ef 100644 --- a/code/modules/projectiles/projectile/special/curse.dm +++ b/code/modules/projectiles/projectile/special/curse.dm @@ -10,7 +10,7 @@ damage_type = BURN damage = 10 paralyze = 20 - speed = 2 + speed = 0.5 range = 16 var/datum/beam/arm var/handedness = 0 diff --git a/code/modules/reagents/reagent_containers/cups/drinks.dm b/code/modules/reagents/reagent_containers/cups/drinks.dm index 717e2248ddb01..41d3161bd6879 100644 --- a/code/modules/reagents/reagent_containers/cups/drinks.dm +++ b/code/modules/reagents/reagent_containers/cups/drinks.dm @@ -31,11 +31,11 @@ qdel(src) target.Bumped(B) -/obj/item/reagent_containers/cup/glass/bullet_act(obj/projectile/P) +/obj/item/reagent_containers/cup/glass/bullet_act(obj/projectile/proj) . = ..() if(QDELETED(src)) return - if(P.damage > 0 && P.damage_type == BRUTE) + if(proj.damage > 0 && proj.damage_type == BRUTE) var/atom/T = get_turf(src) smash(T) diff --git a/code/modules/reagents/reagent_containers/cups/soda.dm b/code/modules/reagents/reagent_containers/cups/soda.dm index a23e9c323545b..c9380d2b9b9c1 100644 --- a/code/modules/reagents/reagent_containers/cups/soda.dm +++ b/code/modules/reagents/reagent_containers/cups/soda.dm @@ -76,17 +76,17 @@ return TRUE return ..() -/obj/item/reagent_containers/cup/soda_cans/bullet_act(obj/projectile/P) +/obj/item/reagent_containers/cup/soda_cans/bullet_act(obj/projectile/proj) . = ..() if(QDELETED(src)) return - if(P.damage > 0 && P.damage_type == BRUTE) - var/obj/item/trash/can/crushed_can = new /obj/item/trash/can(src.loc) - crushed_can.icon_state = icon_state - var/atom/throw_target = get_edge_target_turf(crushed_can, pick(GLOB.alldirs)) - crushed_can.throw_at(throw_target, rand(1,2), 7) - qdel(src) + if(!proj.damage || proj.damage_type != BRUTE) return + var/obj/item/trash/can/crushed_can = new /obj/item/trash/can(loc) + crushed_can.icon_state = icon_state + var/atom/throw_target = get_edge_target_turf(crushed_can, pick(GLOB.alldirs)) + crushed_can.throw_at(throw_target, rand(1,2), 7) + qdel(src) /obj/item/reagent_containers/cup/soda_cans/proc/open_soda(mob/user) if(prob(fizziness)) @@ -138,7 +138,7 @@ burst_soda(hit_atom, hide_message = TRUE) visible_message(span_danger("[src]'s impact with [hit_atom] causes it to rupture, spilling everywhere!")) - var/obj/item/trash/can/crushed_can = new /obj/item/trash/can(src.loc) + var/obj/item/trash/can/crushed_can = new /obj/item/trash/can(loc) crushed_can.icon_state = icon_state moveToNullspace() QDEL_IN(src, 1 SECONDS) // give it a second so it can still be logged for the throw impact diff --git a/code/modules/research/experimentor.dm b/code/modules/research/experimentor.dm index 998995436ad52..c78ca6601d414 100644 --- a/code/modules/research/experimentor.dm +++ b/code/modules/research/experimentor.dm @@ -363,7 +363,7 @@ visible_message(span_danger("[src] dangerously overheats, launching a flaming fuel orb!")) investigate_log("Experimentor has launched a fireball at [M]!", INVESTIGATE_EXPERIMENTOR) var/obj/projectile/magic/fireball/FB = new /obj/projectile/magic/fireball(start) - FB.preparePixelProjectile(MT, start) + FB.aim_projectile(MT, start) FB.fire() else if(prob(EFFECT_PROB_LOW * (100 - malfunction_probability_coeff) * 0.01)) visible_message(span_danger("[src] malfunctions, melting [exp_on] and releasing a burst of flame!")) diff --git a/code/modules/spells/spell_types/aoe_spell/magic_missile.dm b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm index 02f19f45323c2..743ea8b23d880 100644 --- a/code/modules/spells/spell_types/aoe_spell/magic_missile.dm +++ b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm @@ -31,7 +31,7 @@ /datum/action/cooldown/spell/aoe/magic_missile/proc/fire_projectile(atom/victim, mob/caster) var/obj/projectile/to_fire = new projectile_type() - to_fire.preparePixelProjectile(victim, caster) + to_fire.aim_projectile(victim, caster) SEND_SIGNAL(caster, COMSIG_MOB_SPELL_PROJECTILE, src, victim, to_fire) to_fire.fire() diff --git a/code/modules/spells/spell_types/pointed/_pointed.dm b/code/modules/spells/spell_types/pointed/_pointed.dm index a96e2fe9fd291..39d6fb9d6736f 100644 --- a/code/modules/spells/spell_types/pointed/_pointed.dm +++ b/code/modules/spells/spell_types/pointed/_pointed.dm @@ -168,7 +168,7 @@ /datum/action/cooldown/spell/pointed/projectile/proc/ready_projectile(obj/projectile/to_fire, atom/target, mob/user, iteration) to_fire.firer = owner to_fire.fired_from = src - to_fire.preparePixelProjectile(target, owner) + to_fire.aim_projectile(target, owner) RegisterSignal(to_fire, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(on_cast_hit)) if(istype(to_fire, /obj/projectile/magic)) diff --git a/code/modules/spells/spell_types/pointed/spell_cards.dm b/code/modules/spells/spell_types/pointed/spell_cards.dm index 65f9561658b7b..bbaed5ad66d64 100644 --- a/code/modules/spells/spell_types/pointed/spell_cards.dm +++ b/code/modules/spells/spell_types/pointed/spell_cards.dm @@ -81,4 +81,4 @@ to_fire.pixel_x = rand(-projectile_location_spread_amount, projectile_location_spread_amount) to_fire.pixel_y = rand(-projectile_location_spread_amount, projectile_location_spread_amount) - to_fire.preparePixelProjectile(target, user, null, current_angle) + to_fire.aim_projectile(target, user, null, current_angle) diff --git a/code/modules/spells/spell_types/projectile/_basic_projectile.dm b/code/modules/spells/spell_types/projectile/_basic_projectile.dm index 343de438cd669..bcab5633eccb7 100644 --- a/code/modules/spells/spell_types/projectile/_basic_projectile.dm +++ b/code/modules/spells/spell_types/projectile/_basic_projectile.dm @@ -25,6 +25,6 @@ /datum/action/cooldown/spell/basic_projectile/proc/fire_projectile(atom/target, atom/caster) var/obj/projectile/to_fire = new projectile_type() - to_fire.preparePixelProjectile(target, caster) + to_fire.aim_projectile(target, caster) SEND_SIGNAL(caster, COMSIG_MOB_SPELL_PROJECTILE, src, target, to_fire) to_fire.fire() diff --git a/code/modules/unit_tests/embedding.dm b/code/modules/unit_tests/embedding.dm index 05e8cc8b8aa27..5e6a8a90647ef 100644 --- a/code/modules/unit_tests/embedding.dm +++ b/code/modules/unit_tests/embedding.dm @@ -6,7 +6,7 @@ var/obj/projectile/bullet/c38/bullet = new(get_turf(firer)) bullet.set_embed(bullet.get_embed().generate_with_values(embed_chance = 100)) TEST_ASSERT_EQUAL(bullet.get_embed().embed_chance, 100, "embed_chance failed to modify") - bullet.preparePixelProjectile(victim, firer) + bullet.aim_projectile(victim, firer) bullet.fire(get_angle(firer, victim), victim) var/list/components = victim.GetComponents(/datum/component/embedded) TEST_ASSERT_EQUAL(components.len, 1, "Projectile with 100% embed chance didn't embed, or embedded multiple times") diff --git a/code/modules/vehicles/atv.dm b/code/modules/vehicles/atv.dm index 4aa28d6fe5a47..6a472ccc31c4d 100644 --- a/code/modules/vehicles/atv.dm +++ b/code/modules/vehicles/atv.dm @@ -112,12 +112,12 @@ smoke.set_up(0, holder = src, location = src) smoke.start() -/obj/vehicle/ridden/atv/bullet_act(obj/projectile/P) +/obj/vehicle/ridden/atv/bullet_act(obj/projectile/proj) if(prob(50) || !LAZYLEN(buckled_mobs)) return ..() for(var/mob/buckled_mob as anything in buckled_mobs) - buckled_mob.bullet_act(P) - return BULLET_ACT_HIT + return buckled_mob.bullet_act(proj) + return ..() /obj/vehicle/ridden/atv/atom_destruction() explosion(src, devastation_range = -1, light_impact_range = 2, flame_range = 3, flash_range = 4) diff --git a/code/modules/vehicles/mecha/equipment/weapons/weapons.dm b/code/modules/vehicles/mecha/equipment/weapons/weapons.dm index b29c5501a7959..cf042033cba10 100644 --- a/code/modules/vehicles/mecha/equipment/weapons/weapons.dm +++ b/code/modules/vehicles/mecha/equipment/weapons/weapons.dm @@ -68,7 +68,7 @@ var/obj/projectile/projectile_obj = new projectile(get_turf(src)) projectile_obj.log_override = TRUE //we log being fired ourselves a little further down. projectile_obj.firer = chassis - projectile_obj.preparePixelProjectile(target, source, modifiers, spread) + projectile_obj.aim_projectile(target, source, modifiers, spread) if(isliving(source) && source.client) //dont want it to happen from syndie mecha npc mobs, they do direct fire anyways var/mob/living/shooter = source projectile_obj.hit_prone_targets = shooter.combat_mode diff --git a/code/modules/vehicles/secway.dm b/code/modules/vehicles/secway.dm index ab366da4b283e..341167630af14 100644 --- a/code/modules/vehicles/secway.dm +++ b/code/modules/vehicles/secway.dm @@ -97,9 +97,9 @@ return ..() //bullets will have a 60% chance to hit any riders -/obj/vehicle/ridden/secway/bullet_act(obj/projectile/P) +/obj/vehicle/ridden/secway/bullet_act(obj/projectile/proj) if(!buckled_mobs || prob(40)) return ..() for(var/mob/rider as anything in buckled_mobs) - return rider.bullet_act(P) + return rider.bullet_act(proj) return ..() From d136397c0ef100d2259b4f275b98706a101f4de5 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 12:03:44 +0000 Subject: [PATCH 45/55] Automatic changelog for PR #87740 [ci skip] --- html/changelogs/AutoChangeLog-pr-87740.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-87740.yml diff --git a/html/changelogs/AutoChangeLog-pr-87740.yml b/html/changelogs/AutoChangeLog-pr-87740.yml new file mode 100644 index 0000000000000..1221facbb1341 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-87740.yml @@ -0,0 +1,6 @@ +author: "SmArtKar" +delete-after: True +changes: + - qol: "Projectiles now visually impact their targets instead of disappearing about a tile short of it." + - bugfix: "Fixed multiple minor issues with projectile behavior" + - refactor: "Completely rewrote almost all of our projectile code - if anything broke or started looking/behaving oddly, make an issue report!" \ No newline at end of file From 0d1ccc22730c275eeb31b269a0a5105fed3eee74 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:33:47 +0300 Subject: [PATCH 46/55] Fixes a potential harddel in Ripley code (#88101) ## About The Pull Request Ejector didn't clean its ref in parent ripley before deleting itself which could cause harddels if it didn't delete fast enough (as seen during round 243009) ## Changelog :cl: fix: Fixed a potential harddel in Ripley code /:cl: --- code/modules/vehicles/mecha/working/ripley.dm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/code/modules/vehicles/mecha/working/ripley.dm b/code/modules/vehicles/mecha/working/ripley.dm index a2aaa53784f45..7d8452893ac21 100644 --- a/code/modules/vehicles/mecha/working/ripley.dm +++ b/code/modules/vehicles/mecha/working/ripley.dm @@ -301,6 +301,10 @@ GLOBAL_DATUM(cargo_ripley, /obj/vehicle/sealed/mecha/ripley/cargo) var/obj/vehicle/sealed/mecha/ripley/workmech = chassis workmech.cargo_hold = src +/obj/item/mecha_parts/mecha_equipment/ejector/detach() + var/obj/vehicle/sealed/mecha/ripley/workmech = chassis + workmech.cargo_hold = null + return ..() /obj/item/mecha_parts/mecha_equipment/ejector/Destroy() for(var/atom/stored in contents) From c5de7e72d5265802dc659c9f80840070d2112bb5 Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 23 Nov 2024 06:34:28 -0600 Subject: [PATCH 47/55] Fix coffee maker smoke particles not deleting (#88111) ## About The Pull Request Caused by: - #88048 The coffee maker smoke particles stayed on forever after brewing something. This is now fixed. ## Why It's Good For The Game Coffee brewing is no longer a fire hazard. ## Changelog :cl: fix: Fix coffee maker smoke particles not deleting /:cl: --- code/modules/food_and_drinks/machinery/coffeemaker.dm | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/code/modules/food_and_drinks/machinery/coffeemaker.dm b/code/modules/food_and_drinks/machinery/coffeemaker.dm index 3a9e7a1af9f2d..c6f8ba92f2af6 100644 --- a/code/modules/food_and_drinks/machinery/coffeemaker.dm +++ b/code/modules/food_and_drinks/machinery/coffeemaker.dm @@ -392,6 +392,10 @@ ///Updates the smoke state to something else, setting particles if relevant /obj/machinery/coffeemaker/proc/toggle_steam() + if(!brewing) + remove_shared_particles("smoke_coffeemaker") + return + var/obj/effect/abstract/shared_particle_holder/smoke_particles = add_shared_particles(/particles/smoke/steam/mild, "smoke_coffeemaker") smoke_particles.particles.position = list(-6, 0, 0) @@ -704,6 +708,10 @@ update_appearance(UPDATE_OVERLAYS) /obj/machinery/coffeemaker/impressa/toggle_steam() + if(!brewing) + remove_shared_particles("smoke_impressa") + return + var/obj/effect/abstract/shared_particle_holder/smoke_particles = add_shared_particles(/particles/smoke/steam/mild, "smoke_impressa") smoke_particles.particles.position = list(-2, 1, 0) From f781e0dce6cc73fdbe4bd2e90c0b220e06d9c4fc Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 23 Nov 2024 06:34:55 -0600 Subject: [PATCH 48/55] Fix fireplace particles runtimes (#88114) ## About The Pull Request Caused by: - https://github.com/tgstation/tgstation/pull/88048 Trying to use the fireplace would result in runtimes and the smoke particles not triggering. Even though the runtime is fixed, the new particle changes in #88048 broke the pixel offsets. While I was testing, anytime I tried switching a pixel offset it would update all fireplaces. I tried to limit it to add the shared particle id to `"fireplace_[dir]"` so that it would only apply to the objects in that direction but I couldn't get it to work. I would guess this also affects a lot of other objects that have particle pixel offsets. Runtime is fixed. Particle offsets are still broken. ## Why It's Good For The Game Fireplaces no more runtime. ## Changelog :cl: fix: Fix fireplace particles runtimes. /:cl: --- code/game/objects/structures/fireplace.dm | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/code/game/objects/structures/fireplace.dm b/code/game/objects/structures/fireplace.dm index 7e94805045f75..f1505cd1b50a0 100644 --- a/code/game/objects/structures/fireplace.dm +++ b/code/game/objects/structures/fireplace.dm @@ -30,6 +30,7 @@ /obj/structure/fireplace/Destroy() STOP_PROCESSING(SSobj, src) QDEL_NULL(burning_loop) + remove_shared_particles(/particles/smoke/burning) . = ..() /obj/structure/fireplace/setDir(newdir) @@ -166,17 +167,17 @@ fuel_added = 0 update_appearance() adjust_light() - add_shared_particles(/particles/smoke/burning) + var/obj/effect/abstract/shared_particle_holder/smoke_particles = add_shared_particles(/particles/smoke/burning) switch(dir) if(SOUTH) - particles.position = list(0, 29, 0) + smoke_particles.particles.position = list(0, 29, 0) if(EAST) - particles.position = list(-20, 9, 0) + smoke_particles.particles.position = list(-20, 9, 0) if(WEST) - particles.position = list(20, 9, 0) + smoke_particles.particles.position = list(20, 9, 0) if(NORTH) // there is no icon state for SOUTH - QDEL_NULL(particles) + remove_shared_particles(/particles/smoke/burning) /obj/structure/fireplace/proc/put_out() STOP_PROCESSING(SSobj, src) From f7dc1bd6065a448b7d4d33c01bdbf92f51d57b34 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 12:35:15 +0000 Subject: [PATCH 49/55] Automatic changelog for PR #88114 [ci skip] --- html/changelogs/AutoChangeLog-pr-88114.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88114.yml diff --git a/html/changelogs/AutoChangeLog-pr-88114.yml b/html/changelogs/AutoChangeLog-pr-88114.yml new file mode 100644 index 0000000000000..8c6ca0cf3a0cd --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88114.yml @@ -0,0 +1,4 @@ +author: "timothymtorres" +delete-after: True +changes: + - bugfix: "Fix fireplace particles runtimes." \ No newline at end of file From d01e8a33a89b50a23704b8028031d6698f882829 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 12:35:40 +0000 Subject: [PATCH 50/55] Automatic changelog for PR #88111 [ci skip] --- html/changelogs/AutoChangeLog-pr-88111.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88111.yml diff --git a/html/changelogs/AutoChangeLog-pr-88111.yml b/html/changelogs/AutoChangeLog-pr-88111.yml new file mode 100644 index 0000000000000..51d16028801e1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88111.yml @@ -0,0 +1,4 @@ +author: "timothymtorres" +delete-after: True +changes: + - bugfix: "Fix coffee maker smoke particles not deleting" \ No newline at end of file From 4fde078f23474cdd2b927d88e219748a9ca34231 Mon Sep 17 00:00:00 2001 From: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:50:58 +0200 Subject: [PATCH 51/55] Refactors projectile dampeners and adds some new visuals (#87913) Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> --- code/__DEFINES/traits/declarations.dm | 3 + code/__HELPERS/filters.dm | 10 +- code/_globalvars/traits/_traits.dm | 1 + .../fields/projectile_dampener.dm | 117 --------- .../projectile_dampener.dm | 238 ++++++++++++++++++ .../projectile_dampener_effects.dm | 38 +++ code/game/objects/items/robot/items/tools.dm | 37 +-- .../mob/living/basic/bots/medbot/medbot.dm | 1 - .../modules/mob/living/silicon/robot/robot.dm | 19 ++ code/modules/mod/modules/modules_security.dm | 22 -- tgstation.dme | 3 +- 11 files changed, 318 insertions(+), 171 deletions(-) delete mode 100644 code/datums/proximity_monitor/fields/projectile_dampener.dm create mode 100644 code/datums/proximity_monitor/fields/projectile_dampener/projectile_dampener.dm create mode 100644 code/datums/proximity_monitor/fields/projectile_dampener/projectile_dampener_effects.dm diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 94064352b846a..eb623ed708ec9 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1391,4 +1391,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Gives a little examine to their body that they can be revived with a soul #define TRAIT_GHOSTROLE_ON_REVIVE "ghostrole_on_revive" +///Trait given to atoms currently affected by projectile dampeners +#define TRAIT_GOT_DAMPENED "got_dampened" + // END TRAIT DEFINES diff --git a/code/__HELPERS/filters.dm b/code/__HELPERS/filters.dm index 14233a2807636..930c889b09cab 100644 --- a/code/__HELPERS/filters.dm +++ b/code/__HELPERS/filters.dm @@ -312,9 +312,13 @@ GLOBAL_LIST_INIT(master_filter_info, list( animate(filter, offset = random_roll, time = 0, loop = -1, flags = ANIMATION_PARALLEL) animate(offset = random_roll - 1, time = rand() * 20 + 10) -/proc/remove_wibbly_filters(atom/in_atom) +/proc/remove_wibbly_filters(atom/in_atom, remove_duration = 0) var/filter for(var/i in 1 to 7) filter = in_atom.get_filter("wibbly-[i]") - animate(filter) - in_atom.remove_filter("wibbly-[i]") + if(remove_duration == 0) + animate(filter) + in_atom.remove_filter("wibbly-[i]") + continue + animate(filter, x = 0, y = 0, size = 0, offset = 0, time = remove_duration) + addtimer(CALLBACK(in_atom, TYPE_PROC_REF(/datum, remove_filter), "wibbly-[i]"), remove_duration) diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index cd68ebac4acab..c54d78421a304 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -22,6 +22,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_FISHING_SPOT" = TRAIT_FISHING_SPOT, "TRAIT_FOOD_CHEF_MADE" = TRAIT_FOOD_CHEF_MADE, "TRAIT_FOOD_FRIED" = TRAIT_FOOD_FRIED, + "TRAIT_GOT_DAMPENED" = TRAIT_GOT_DAMPENED, "TRAIT_QUALITY_FOOD_INGREDIENT" = TRAIT_QUALITY_FOOD_INGREDIENT, "TRAIT_FOOD_SILVER" = TRAIT_FOOD_SILVER, "TRAIT_KEEP_TOGETHER" = TRAIT_KEEP_TOGETHER, diff --git a/code/datums/proximity_monitor/fields/projectile_dampener.dm b/code/datums/proximity_monitor/fields/projectile_dampener.dm deleted file mode 100644 index fe23fe0be33e5..0000000000000 --- a/code/datums/proximity_monitor/fields/projectile_dampener.dm +++ /dev/null @@ -1,117 +0,0 @@ - -//Projectile dampening field that slows projectiles and lowers their damage for an energy cost deducted every 1/5 second. -//Only use square radius for this! -/datum/proximity_monitor/advanced/projectile_dampener - var/static/image/edgeturf_south = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_south") - var/static/image/edgeturf_north = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_north") - var/static/image/edgeturf_west = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_west") - var/static/image/edgeturf_east = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_east") - var/static/image/northwest_corner = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_northwest") - var/static/image/southwest_corner = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_southwest") - var/static/image/northeast_corner = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_northeast") - var/static/image/southeast_corner = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_southeast") - var/static/image/generic_edge = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_generic") - var/list/obj/projectile/tracked = list() - var/list/obj/projectile/staging = list() - // lazylist that keeps track of the overlays added to the edge of the field - var/list/edgeturf_effects - -/datum/proximity_monitor/advanced/projectile_dampener/New(atom/_host, range, _ignore_if_not_on_turf = TRUE, atom/projector) - ..() - RegisterSignal(projector, COMSIG_QDELETING, PROC_REF(on_projector_del)) - recalculate_field(full_recalc = TRUE) - START_PROCESSING(SSfastprocess, src) - -/datum/proximity_monitor/advanced/projectile_dampener/Destroy() - STOP_PROCESSING(SSfastprocess, src) - for(var/obj/projectile/projectile in tracked) - release_projectile(projectile) - return ..() - -/datum/proximity_monitor/advanced/projectile_dampener/recalculate_field(full_recalc) - full_recalc = TRUE // We always perform a full recalc because we need to update ALL the sprites - return ..() - -/datum/proximity_monitor/advanced/projectile_dampener/process() - var/list/ranged = list() - for(var/obj/projectile/projectile in range(current_range, get_turf(host))) - ranged += projectile - for(var/obj/projectile/projectile in tracked) - if(!(projectile in ranged) || !projectile.loc) - release_projectile(projectile) - -/datum/proximity_monitor/advanced/projectile_dampener/setup_edge_turf(turf/target) - . = ..() - var/image/overlay = get_edgeturf_overlay(get_edgeturf_direction(target)) - var/obj/effect/abstract/effect = new(target) // Makes the field visible to players. - effect.icon = overlay.icon - effect.icon_state = overlay.icon_state - effect.mouse_opacity = MOUSE_OPACITY_TRANSPARENT - effect.layer = ABOVE_ALL_MOB_LAYER - SET_PLANE(effect, ABOVE_GAME_PLANE, target) - LAZYSET(edgeturf_effects, target, effect) - -/datum/proximity_monitor/advanced/projectile_dampener/on_z_change(datum/source) - recalculate_field(full_recalc = TRUE) - -/datum/proximity_monitor/advanced/projectile_dampener/cleanup_edge_turf(turf/target) - . = ..() - var/obj/effect/abstract/effect = LAZYACCESS(edgeturf_effects, target) - LAZYREMOVE(edgeturf_effects, target) - if(effect) - qdel(effect) - -/datum/proximity_monitor/advanced/projectile_dampener/proc/get_edgeturf_overlay(direction) - switch(direction) - if(NORTH) - return edgeturf_north - if(SOUTH) - return edgeturf_south - if(EAST) - return edgeturf_east - if(WEST) - return edgeturf_west - if(NORTHEAST) - return northeast_corner - if(NORTHWEST) - return northwest_corner - if(SOUTHEAST) - return southeast_corner - if(SOUTHWEST) - return southwest_corner - else - return generic_edge - -/datum/proximity_monitor/advanced/projectile_dampener/proc/capture_projectile(obj/projectile/projectile) - if(projectile in tracked) - return - SEND_SIGNAL(src, COMSIG_DAMPENER_CAPTURE, projectile) - tracked += projectile - -/datum/proximity_monitor/advanced/projectile_dampener/proc/release_projectile(obj/projectile/projectile) - SEND_SIGNAL(src, COMSIG_DAMPENER_RELEASE, projectile) - tracked -= projectile - -/datum/proximity_monitor/advanced/projectile_dampener/proc/on_projector_del(datum/source) - SIGNAL_HANDLER - qdel(src) - -/datum/proximity_monitor/advanced/projectile_dampener/field_edge_uncrossed(atom/movable/movable, turf/old_location, turf/new_location) - if(isprojectile(movable) && get_dist(movable, host) > current_range) - if(movable in tracked) - release_projectile(movable) - -/datum/proximity_monitor/advanced/projectile_dampener/field_edge_crossed(atom/movable/movable, turf/location, turf/old_location) - if(isprojectile(movable)) - capture_projectile(movable) - -/datum/proximity_monitor/advanced/projectile_dampener/peaceborg/process(seconds_per_tick) - for(var/mob/living/silicon/robot/borg in range(current_range, get_turf(host))) - if(!borg.has_buckled_mobs()) - continue - for(var/mob/living/buckled_mob in borg.buckled_mobs) - buckled_mob.visible_message(span_warning("[buckled_mob] is knocked off of [borg] by the charge in [borg]'s chassis induced by the hyperkinetic dampener field!")) //I know it's bad. - buckled_mob.Paralyze(1 SECONDS) - borg.unbuckle_mob(buckled_mob) - do_sparks(5, 0, buckled_mob) - ..() diff --git a/code/datums/proximity_monitor/fields/projectile_dampener/projectile_dampener.dm b/code/datums/proximity_monitor/fields/projectile_dampener/projectile_dampener.dm new file mode 100644 index 0000000000000..3185872efad4a --- /dev/null +++ b/code/datums/proximity_monitor/fields/projectile_dampener/projectile_dampener.dm @@ -0,0 +1,238 @@ +#define CHANGING_OFFSET "changing_offset" +#define OVERLAY_DATA "overlay_data" +#define STARTING_POSITION "starting_position" +#define ANIMATE_DAMPENER_TIME 1.5 SECONDS + +//Projectile dampening field that slows projectiles and lowers their damage for an energy cost deducted every 1/5 second. +//Only use square radius for this! +/datum/proximity_monitor/advanced/projectile_dampener + edge_is_a_field = TRUE + var/static/list/effect_direction_images = list( + "[SOUTH]" = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_south"), + "[NORTH]" = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_north"), + "[WEST]" = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_west"), + "[EAST]" = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_east"), + "[NORTHWEST]" = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_northwest"), + "[SOUTHWEST]" = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_southwest"), + "[NORTHEAST]" = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_northeast"), + "[SOUTHEAST]" = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_southeast"), + ) + var/static/image/generic_edge = image('icons/effects/fields.dmi', icon_state = "projectile_dampen_generic") + ///overlay we apply to caught bullets + var/static/image/new_bullet_overlay= image('icons/effects/fields.dmi', "projectile_dampen_effect") + ///list of all the visual effects we keep track of + var/list/edgeturf_effects = list() + ///atom that contains all the fields in its vis_contents + var/atom/movable/field_effect_holder/my_movable + /// datum that holds the effects we apply on caught bullets + var/datum/dampener_projectile_effects/bullet_effects + +/datum/proximity_monitor/advanced/projectile_dampener/New(atom/_host, range, _ignore_if_not_on_turf = TRUE, atom/projector, datum/dampener_projectile_effects/effects_typepath) + ..() + RegisterSignal(projector, COMSIG_QDELETING, PROC_REF(on_projector_del)) + var/atom/movable/movable_host = _host + my_movable = new(get_turf(_host)) + my_movable.transform = my_movable.transform.Scale(current_range, current_range) + my_movable.set_glide_size(movable_host.glide_size) + bullet_effects = effects_typepath ? new effects_typepath() : new + draw_effect() + +/datum/proximity_monitor/advanced/projectile_dampener/on_moved(atom/movable/source, atom/old_loc) + . = ..() + my_movable.Move(source.loc, get_dir(my_movable.loc, source.loc), source.glide_size) + +/datum/proximity_monitor/advanced/projectile_dampener/on_z_change(datum/source) + recalculate_field(full_recalc = TRUE) + +/datum/proximity_monitor/advanced/projectile_dampener/field_edge_crossed(atom/movable/movable, turf/location, turf/old_location) + . = ..() + if(!isprojectile(movable)) + return + determine_wobble(location) + +/datum/proximity_monitor/advanced/projectile_dampener/field_edge_uncrossed(atom/movable/movable, turf/old_location, turf/new_location) + . = ..() + if(!isprojectile(movable)) + return + determine_wobble(old_location) + +/datum/proximity_monitor/advanced/projectile_dampener/field_turf_crossed(atom/movable/movable, turf/old_location, turf/new_location) + if(!isprojectile(movable) || HAS_TRAIT_FROM(movable, TRAIT_GOT_DAMPENED, REF(src))) + return + catch_bullet_effect(movable) + +/datum/proximity_monitor/advanced/projectile_dampener/field_turf_uncrossed(atom/movable/movable, turf/old_location, turf/new_location) + if(!isprojectile(movable) || get_dist(new_location, host) <= current_range) + return + release_bullet_effect(movable) + +/datum/proximity_monitor/advanced/projectile_dampener/setup_field_turf(turf/target) + for(var/atom/possible_projectile in target) + if(isprojectile(possible_projectile)) + catch_bullet_effect(possible_projectile) + +/datum/proximity_monitor/advanced/projectile_dampener/cleanup_field_turf(turf/target) + for(var/atom/possible_projectile in target) + if(isprojectile(possible_projectile) && HAS_TRAIT_FROM(possible_projectile, TRAIT_GOT_DAMPENED, REF(src))) + release_bullet_effect(possible_projectile) + +///proc that applies the wobbly effect on point of bullet entry +/datum/proximity_monitor/advanced/projectile_dampener/proc/determine_wobble(turf/location) + var/coord_x = location.x - host.x + var/coord_y = location.y - host.y + var/obj/effect/overlay/vis/field/my_field = edgeturf_effects["[coord_x],[coord_y]"] + my_field?.set_wobbly(0.15 SECONDS) + +/datum/proximity_monitor/advanced/projectile_dampener/proc/projectile_overlay_updated(atom/source, list/overlays) + SIGNAL_HANDLER + + if(!isnull(new_bullet_overlay) && HAS_TRAIT_FROM(source, TRAIT_GOT_DAMPENED, REF(src))) + overlays += new_bullet_overlay + +///a bullet has entered our field, apply the dampening effects to it +/datum/proximity_monitor/advanced/projectile_dampener/proc/catch_bullet_effect(obj/projectile/bullet) + ADD_TRAIT(bullet,TRAIT_GOT_DAMPENED, REF(src)) + RegisterSignal(bullet, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(projectile_overlay_updated)) + SEND_SIGNAL(src, COMSIG_DAMPENER_CAPTURE, bullet) + bullet_effects.apply_effects(bullet) + bullet.update_appearance() + +///removing the effects after it has exited our field +/datum/proximity_monitor/advanced/projectile_dampener/proc/release_bullet_effect(obj/projectile/bullet) + REMOVE_TRAIT(bullet, TRAIT_GOT_DAMPENED, REF(src)) + SEND_SIGNAL(src, COMSIG_DAMPENER_RELEASE, bullet) + bullet_effects.remove_effects(bullet) + bullet.update_appearance() + UnregisterSignal(bullet, COMSIG_ATOM_UPDATE_OVERLAYS) + +///rendering all the field visuals. first we render the corners, then we connect them +/datum/proximity_monitor/advanced/projectile_dampener/proc/draw_effect() + var/max_pixel_offset = current_range * ICON_SIZE_ALL + var/top_right_corner = list(effect_direction_images["[NORTHEAST]"], max_pixel_offset, max_pixel_offset) + var/top_left_corner = list(effect_direction_images["[NORTHWEST]"], -max_pixel_offset, max_pixel_offset) + var/bottom_left_corner = list(effect_direction_images["[SOUTHWEST]"], -max_pixel_offset, -max_pixel_offset) + var/bottom_right_corner = list(effect_direction_images["[SOUTHEAST]"], max_pixel_offset, -max_pixel_offset) + + var/list/corners = list(top_right_corner, top_left_corner, bottom_left_corner, bottom_right_corner) + for(var/corner in corners) + draw_corner(corner) + + var/list/corners_to_connect = list( + list(OVERLAY_DATA = effect_direction_images["[NORTH]"], CHANGING_OFFSET = "x_offset", STARTING_POSITION = max_pixel_offset), + list(OVERLAY_DATA = effect_direction_images["[SOUTH]"], CHANGING_OFFSET = "x_offset", STARTING_POSITION = -max_pixel_offset), + list(OVERLAY_DATA = effect_direction_images["[WEST]"], CHANGING_OFFSET = "y_offset", STARTING_POSITION = -max_pixel_offset), + list(OVERLAY_DATA = effect_direction_images["[EAST]"], CHANGING_OFFSET = "y_offset", STARTING_POSITION = max_pixel_offset), + ) + for(var/direction in corners_to_connect) + draw_edge(direction, max_pixel_offset) + +///rendering the corners +/datum/proximity_monitor/advanced/projectile_dampener/proc/draw_corner(list/corner_data) + var/obj/effect/overlay/vis/field/corner_effect = new() + var/image/image_overlay = corner_data[1] + corner_effect.icon = image_overlay.icon + corner_effect.icon_state = image_overlay.icon_state + corner_effect.alpha = 0 + corner_effect.pixel_x = corner_data[2] + corner_effect.pixel_y = corner_data[3] + add_effect_to_host(corner_effect) + +///connecting the corners to one another +/datum/proximity_monitor/advanced/projectile_dampener/proc/draw_edge(list/edge_data, target_offset) + var/starting_offset = edge_data[STARTING_POSITION] + var/current_offset = (-1 * target_offset) + ICON_SIZE_ALL + var/image/overlay = edge_data[OVERLAY_DATA] + while(current_offset != target_offset) + var/obj/effect/overlay/vis/field/edge_effect = new() + edge_effect.alpha = 0 + edge_effect.icon = overlay.icon + edge_effect.icon_state = overlay.icon_state + + if(edge_data[CHANGING_OFFSET] == "x_offset") + edge_effect.pixel_y = starting_offset + edge_effect.pixel_x = current_offset + else + edge_effect.pixel_x = starting_offset + edge_effect.pixel_y = current_offset + add_effect_to_host(edge_effect) + current_offset += ICON_SIZE_ALL + +///handles adding the visual effect's data +/datum/proximity_monitor/advanced/projectile_dampener/proc/add_effect_to_host(obj/effect/overlay/vis/field/effect_to_add) + my_movable.vis_contents += effect_to_add + var/coordinate_x = effect_to_add.pixel_x / ICON_SIZE_ALL + var/coordinate_y = effect_to_add.pixel_y / ICON_SIZE_ALL + effect_to_add.transform = effect_to_add.transform.Scale(1 / current_range, 1 / current_range) + edgeturf_effects["[coordinate_x],[coordinate_y]"] = effect_to_add + effect_to_add.set_wobbly(wobble_duration = ANIMATE_DAMPENER_TIME) + animate(effect_to_add, alpha = 255, time = ANIMATE_DAMPENER_TIME, flags = ANIMATION_PARALLEL) + +/datum/proximity_monitor/advanced/projectile_dampener/proc/on_projector_del(datum/source) + SIGNAL_HANDLER + qdel(src) + +/datum/proximity_monitor/advanced/projectile_dampener/Destroy() + for(var/coordinates in edgeturf_effects) + var/obj/effect/overlay/vis/field/effect_to_remove = edgeturf_effects[coordinates] + edgeturf_effects -= coordinates + effect_to_remove.set_wobbly(wobble_duration = ANIMATE_DAMPENER_TIME) + animate(effect_to_remove, alpha = 0, time = ANIMATE_DAMPENER_TIME, flags = ANIMATION_PARALLEL) + QDEL_IN(my_movable, ANIMATE_DAMPENER_TIME) + my_movable = null + bullet_effects = null + return ..() + +/datum/proximity_monitor/advanced/projectile_dampener/peaceborg + +/datum/proximity_monitor/advanced/projectile_dampener/peaceborg/field_turf_crossed(atom/movable/movable, turf/old_location, turf/new_location) + . = ..() + if(!iscyborg(movable) || !HAS_TRAIT_FROM(movable, TRAIT_GOT_DAMPENED, REF(src))) + ADD_TRAIT(movable, TRAIT_GOT_DAMPENED, REF(src)) + +/datum/proximity_monitor/advanced/projectile_dampener/peaceborg/field_turf_uncrossed(atom/movable/movable, turf/old_location, turf/new_location) + if(!iscyborg(movable) || get_dist(new_location, host) <= current_range) + return + REMOVE_TRAIT(movable, TRAIT_GOT_DAMPENED, REF(src)) + +/datum/proximity_monitor/advanced/projectile_dampener/peaceborg/setup_field_turf(turf/target) + for(var/atom/interesting_atom as anything in target) + if(iscyborg(interesting_atom)) + ADD_TRAIT(interesting_atom, TRAIT_GOT_DAMPENED, REF(src)) + if(isprojectile(interesting_atom)) + catch_bullet_effect(interesting_atom) + +/datum/proximity_monitor/advanced/projectile_dampener/peaceborg/cleanup_field_turf(turf/target) + for(var/atom/interesting_atom as anything in target) + if(iscyborg(interesting_atom)) + REMOVE_TRAIT(interesting_atom, TRAIT_GOT_DAMPENED, REF(src)) + if(isprojectile(interesting_atom)) + release_bullet_effect(interesting_atom) + +/obj/effect/overlay/vis/field + appearance_flags = PIXEL_SCALE|LONG_GLIDE + vis_flags = parent_type::vis_flags | VIS_INHERIT_PLANE + ///are we currently WOBBLING + var/wobbling_effect = FALSE + +/obj/effect/overlay/vis/field/proc/set_wobbly(wobble_duration) + if(wobbling_effect) + return + wobbling_effect = TRUE + apply_wibbly_filters(src) + addtimer(CALLBACK(src, PROC_REF(remove_wobbly)), wobble_duration) + +/obj/effect/overlay/vis/field/proc/remove_wobbly() + if(QDELETED(src)) + return + remove_wibbly_filters(src, remove_duration = 0.25 SECONDS) + addtimer(VARSET_CALLBACK(src, wobbling_effect, FALSE), 0.25 SECONDS) + +/atom/movable/field_effect_holder + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + appearance_flags = PIXEL_SCALE|LONG_GLIDE + plane = ABOVE_GAME_PLANE + +#undef CHANGING_OFFSET +#undef OVERLAY_DATA +#undef STARTING_POSITION +#undef ANIMATE_DAMPENER_TIME diff --git a/code/datums/proximity_monitor/fields/projectile_dampener/projectile_dampener_effects.dm b/code/datums/proximity_monitor/fields/projectile_dampener/projectile_dampener_effects.dm new file mode 100644 index 0000000000000..bd07d7836fc77 --- /dev/null +++ b/code/datums/proximity_monitor/fields/projectile_dampener/projectile_dampener_effects.dm @@ -0,0 +1,38 @@ +///datum that holds the effects we have on bullet catching +/datum/dampener_projectile_effects + ///new projectiles speeds + var/projectile_speed_multiplier = 0.4 + /// new projectiles damage + var/projectile_damage_multiplier = 0.75 + /// new projectiles knockdown + var/projectile_knockdown_multiplier = 0.66 + /// new projectiles stun + var/projectile_stun_multiplier = 0.66 + /// new projectiles stamina damage + var/projectile_stamina_multiplier = 0.66 + +/datum/dampener_projectile_effects/proc/apply_effects(obj/projectile/bullet) + if(projectile_speed_multiplier) + bullet.speed *= projectile_speed_multiplier + if(projectile_damage_multiplier) + bullet.damage *= projectile_damage_multiplier + if(projectile_knockdown_multiplier) + bullet.knockdown *= projectile_knockdown_multiplier + if(projectile_stamina_multiplier) + bullet.stamina *= projectile_stamina_multiplier + if(projectile_stun_multiplier) + bullet.stun *= projectile_stun_multiplier + +/datum/dampener_projectile_effects/proc/remove_effects(obj/projectile/bullet) + bullet.speed /= projectile_speed_multiplier + bullet.damage /= projectile_damage_multiplier + bullet.knockdown /= projectile_knockdown_multiplier + bullet.stamina /= projectile_stamina_multiplier + bullet.stun /= projectile_stun_multiplier + +/datum/dampener_projectile_effects/peacekeeper + projectile_speed_multiplier = 0.66 + projectile_damage_multiplier = 0.5 + projectile_knockdown_multiplier = 1 + projectile_stun_multiplier = 1 + projectile_stamina_multiplier = 1 diff --git a/code/game/objects/items/robot/items/tools.dm b/code/game/objects/items/robot/items/tools.dm index 6e16388b4b149..dfea34fba9bcd 100644 --- a/code/game/objects/items/robot/items/tools.dm +++ b/code/game/objects/items/robot/items/tools.dm @@ -31,19 +31,13 @@ var/mob/living/silicon/robot/host = null /// The field var/datum/proximity_monitor/advanced/projectile_dampener/peaceborg/dampening_field - var/projectile_damage_coefficient = 0.5 /// Energy cost per tracked projectile damage amount per second var/projectile_damage_tick_ecost_coefficient = 10 - /** - * Speed coefficient - * Higher the coefficient faster the projectile. - */ - var/projectile_speed_coefficient = 0.66 /// Energy cost per tracked projectile per second var/projectile_tick_speed_ecost = 75 - /// Projectile sent out by the dampener - var/list/obj/projectile/tracked - var/image/projectile_effect + /// Projectiles dampened by our dampener + var/list/tracked_bullet_cost = list() + /// the radius of our field var/field_radius = 3 var/active = FALSE /// activation cooldown @@ -55,8 +49,6 @@ energy_recharge = 5000 /obj/item/borg/projectile_dampen/Initialize(mapload) - projectile_effect = image('icons/effects/fields.dmi', "projectile_dampen_effect") - tracked = list() START_PROCESSING(SSfastprocess, src) host = loc RegisterSignal(host, COMSIG_LIVING_DEATH, PROC_REF(on_death)) @@ -94,7 +86,7 @@ if(istype(dampening_field)) QDEL_NULL(dampening_field) var/mob/living/silicon/robot/owner = get_host() - dampening_field = new(owner, field_radius, TRUE, src) + dampening_field = new(owner, field_radius, TRUE, src, /datum/dampener_projectile_effects/peacekeeper) RegisterSignal(dampening_field, COMSIG_DAMPENER_CAPTURE, PROC_REF(dampen_projectile)) RegisterSignal(dampening_field, COMSIG_DAMPENER_RELEASE, PROC_REF(restore_projectile)) owner?.model.allow_riding = FALSE @@ -103,8 +95,7 @@ /obj/item/borg/projectile_dampen/proc/deactivate_field() QDEL_NULL(dampening_field) visible_message(span_warning("\The [src] shuts off!")) - for(var/projectile in tracked) - restore_projectile(projectile = projectile) + tracked_bullet_cost.Cut() active = FALSE var/mob/living/silicon/robot/owner = get_host() @@ -137,11 +128,9 @@ /obj/item/borg/projectile_dampen/proc/process_usage(seconds_per_tick) var/usage = 0 - for(var/obj/projectile/inner_projectile as anything in tracked) - if(!inner_projectile.is_hostile_projectile()) - continue + for(var/projectile as anything in tracked_bullet_cost) usage += projectile_tick_speed_ecost * seconds_per_tick - usage += tracked[inner_projectile] * projectile_damage_tick_ecost_coefficient * seconds_per_tick + usage += tracked_bullet_cost[projectile] * projectile_damage_tick_ecost_coefficient * seconds_per_tick energy = clamp(energy - usage, 0, maxenergy) if(energy <= 0) deactivate_field() @@ -161,18 +150,12 @@ /obj/item/borg/projectile_dampen/proc/dampen_projectile(datum/source, obj/projectile/projectile) SIGNAL_HANDLER - tracked[projectile] = projectile.damage - projectile.damage *= projectile_damage_coefficient - projectile.speed *= projectile_speed_coefficient - projectile.add_overlay(projectile_effect) + if(projectile.is_hostile_projectile()) + tracked_bullet_cost[REF(projectile)] = projectile.damage /obj/item/borg/projectile_dampen/proc/restore_projectile(datum/source, obj/projectile/projectile) SIGNAL_HANDLER - - tracked -= projectile - projectile.damage /= projectile_damage_coefficient - projectile.speed /= projectile_speed_coefficient - projectile.cut_overlay(projectile_effect) + tracked_bullet_cost -= REF(projectile) //bare minimum omni-toolset for modularity /obj/item/borg/cyborg_omnitool diff --git a/code/modules/mob/living/basic/bots/medbot/medbot.dm b/code/modules/mob/living/basic/bots/medbot/medbot.dm index 2466ab931cd19..aeb03354db0a5 100644 --- a/code/modules/mob/living/basic/bots/medbot/medbot.dm +++ b/code/modules/mob/living/basic/bots/medbot/medbot.dm @@ -145,7 +145,6 @@ remove_hat_signals = remove_hat,\ traits_prevent_checks = prevent_checks,\ ) - RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) if(!HAS_TRAIT(SSstation, STATION_TRAIT_MEDBOT_MANIA) || !mapload || !is_station_level(z)) diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 67a61c4b8322c..a592a605e5ad1 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -19,6 +19,7 @@ AddElement(/datum/element/ridable, /datum/component/riding/creature/cyborg) RegisterSignal(src, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, PROC_REF(charge)) RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater)) + RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_GOT_DAMPENED), PROC_REF(on_dampen)) robot_modules_background = new() robot_modules_background.icon_state = "block" @@ -963,6 +964,10 @@ buckle_mob_flags= RIDER_NEEDS_ARM // just in case return ..() +/mob/living/silicon/robot/post_buckle_mob(mob/living/victim_to_boot) + if(HAS_TRAIT(src, TRAIT_GOT_DAMPENED)) + eject_riders() + /mob/living/silicon/robot/can_resist() if(lockcharge) balloon_alert(src, "locked down!") @@ -1059,3 +1064,17 @@ . = ..() set_stat(CONSCIOUS) //This is a horrible hack, but silicon code forced my hand update_stat() + +/mob/living/silicon/robot/proc/on_dampen() + SIGNAL_HANDLER + eject_riders() + +/mob/living/silicon/robot/proc/eject_riders() + if(!length(buckled_mobs)) + return + for(var/mob/living/buckled_mob as anything in buckled_mobs) + buckled_mob.visible_message(span_warning("[buckled_mob] is knocked off of [src] by the charge in [src]'s chassis induced by the hyperkinetic dampener field!")) + buckled_mob.Paralyze(1 SECONDS) + unbuckle_mob(buckled_mob) + do_sparks(5, 0, src) + diff --git a/code/modules/mod/modules/modules_security.dm b/code/modules/mod/modules/modules_security.dm index dff5a000b928f..b705de0f4beaa 100644 --- a/code/modules/mod/modules/modules_security.dm +++ b/code/modules/mod/modules/modules_security.dm @@ -326,32 +326,10 @@ if(istype(dampening_field)) QDEL_NULL(dampening_field) dampening_field = new(mod.wearer, field_radius, TRUE, src) - RegisterSignal(dampening_field, COMSIG_DAMPENER_CAPTURE, PROC_REF(dampen_projectile)) - RegisterSignal(dampening_field, COMSIG_DAMPENER_RELEASE, PROC_REF(release_projectile)) /obj/item/mod/module/projectile_dampener/on_deactivation(display_message, deleting = FALSE) QDEL_NULL(dampening_field) -/obj/item/mod/module/projectile_dampener/proc/dampen_projectile(datum/source, obj/projectile/projectile) - SIGNAL_HANDLER - - projectile.damage *= damage_multiplier - projectile.stamina *= damage_multiplier - projectile.stun *= debuff_multiplier - projectile.knockdown *= debuff_multiplier - projectile.speed *= speed_multiplier - projectile.add_overlay(projectile_effect) - -/obj/item/mod/module/projectile_dampener/proc/release_projectile(datum/source, obj/projectile/projectile) - SIGNAL_HANDLER - - projectile.damage /= damage_multiplier - projectile.speed /= speed_multiplier - projectile.stamina /= damage_multiplier - projectile.stun /= debuff_multiplier - projectile.knockdown /= debuff_multiplier - projectile.cut_overlay(projectile_effect) - ///Active Sonar - Displays a hud circle on the turf of any living creatures in the given radius /obj/item/mod/module/active_sonar name = "MOD active sonar" diff --git a/tgstation.dme b/tgstation.dme index c83d7d87c42d7..16d883964c4fc 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1738,9 +1738,10 @@ #include "code\datums\proximity_monitor\proximity_monitor.dm" #include "code\datums\proximity_monitor\fields\ai_target_tracking.dm" #include "code\datums\proximity_monitor\fields\gravity.dm" -#include "code\datums\proximity_monitor\fields\projectile_dampener.dm" #include "code\datums\proximity_monitor\fields\timestop.dm" #include "code\datums\proximity_monitor\fields\void_storm.dm" +#include "code\datums\proximity_monitor\fields\projectile_dampener\projectile_dampener.dm" +#include "code\datums\proximity_monitor\fields\projectile_dampener\projectile_dampener_effects.dm" #include "code\datums\quirks\_quirk.dm" #include "code\datums\quirks\_quirk_constant_data.dm" #include "code\datums\quirks\negative_quirks\addict.dm" From 14f23f6666c5e599851ee8e7fdd4f7447ed77ad2 Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 13:51:37 +0000 Subject: [PATCH 52/55] Automatic changelog for PR #87913 [ci skip] --- html/changelogs/AutoChangeLog-pr-87913.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-87913.yml diff --git a/html/changelogs/AutoChangeLog-pr-87913.yml b/html/changelogs/AutoChangeLog-pr-87913.yml new file mode 100644 index 0000000000000..a3728e89bc96d --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-87913.yml @@ -0,0 +1,4 @@ +author: "Ben10Omintrix" +delete-after: True +changes: + - refactor: "projectile dampeners have been refactored. please report any bugs" \ No newline at end of file From 8804f5595cac1266c9171070f4f25c6db635fcff Mon Sep 17 00:00:00 2001 From: Ghom <42542238+Ghommie@users.noreply.github.com> Date: Sat, 23 Nov 2024 14:53:30 +0100 Subject: [PATCH 53/55] turfs now handle fishing more internally to save time on world initialization (#88092) --- code/datums/components/chasm.dm | 2 +- code/datums/components/fishing_spot.dm | 7 -- code/datums/elements/lazy_fishing_spot.dm | 85 ------------------- .../structures/water_structures/toilet.dm | 2 +- code/game/turfs/open/lava.dm | 4 +- code/game/turfs/open/sand.dm | 4 +- code/game/turfs/open/water.dm | 2 +- code/game/turfs/turf.dm | 67 +++++++++++++++ code/modules/fishing/sources/_fish_source.dm | 2 +- .../food_and_drinks/machinery/deep_fryer.dm | 2 +- code/modules/unit_tests/fish_unit_tests.dm | 7 +- tgstation.dme | 1 - 12 files changed, 82 insertions(+), 103 deletions(-) delete mode 100644 code/datums/elements/lazy_fishing_spot.dm diff --git a/code/datums/components/chasm.dm b/code/datums/components/chasm.dm index 0d65d2840f3c8..c281addacc231 100644 --- a/code/datums/components/chasm.dm +++ b/code/datums/components/chasm.dm @@ -49,7 +49,7 @@ //otherwise don't do anything because turfs and areas are initialized before movables. if(!mapload) addtimer(CALLBACK(src, PROC_REF(drop_stuff)), 0) - parent.AddElement(/datum/element/lazy_fishing_spot, /datum/fish_source/chasm) + parent.AddComponent(/datum/component/fishing_spot, GLOB.preset_fish_sources[/datum/fish_source/chasm]) /datum/component/chasm/UnregisterFromParent() storage = null diff --git a/code/datums/components/fishing_spot.dm b/code/datums/components/fishing_spot.dm index 982b0da2df71a..3dc99cd8067de 100644 --- a/code/datums/components/fishing_spot.dm +++ b/code/datums/components/fishing_spot.dm @@ -13,7 +13,6 @@ else return COMPONENT_INCOMPATIBLE fish_source.on_fishing_spot_init(src) - RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(handle_attackby)) RegisterSignal(parent, COMSIG_FISHING_ROD_CAST, PROC_REF(handle_cast)) RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined)) RegisterSignal(parent, COMSIG_ATOM_EXAMINE_MORE, PROC_REF(on_examined_more)) @@ -36,12 +35,6 @@ return FISHING_ROD_CAST_HANDLED return NONE -/datum/component/fishing_spot/proc/handle_attackby(datum/source, obj/item/item, mob/user, params) - SIGNAL_HANDLER - if(try_start_fishing(item,user)) - return COMPONENT_NO_AFTERATTACK - return NONE - ///If the fish source has fishes that are shown in the /datum/component/fishing_spot/proc/on_examined(datum/source, mob/user, list/examine_text) SIGNAL_HANDLER diff --git a/code/datums/elements/lazy_fishing_spot.dm b/code/datums/elements/lazy_fishing_spot.dm deleted file mode 100644 index 67edcea2e88ed..0000000000000 --- a/code/datums/elements/lazy_fishing_spot.dm +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Lazy fishing spot element so fisheable turfs do not have a component each since - * they're usually pretty common on their respective maps (lava/water/etc) - */ -/datum/element/lazy_fishing_spot - element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH_ON_HOST_DESTROY // Detach for turfs - argument_hash_start_idx = 2 - var/configuration - -/datum/element/lazy_fishing_spot/Attach(datum/target, configuration) - . = ..() - if(!isatom(target)) - return ELEMENT_INCOMPATIBLE - if(!ispath(configuration, /datum/fish_source) || configuration == /datum/fish_source) - CRASH("Lazy fishing spot has incorrect configuration passed in: [configuration].") - src.configuration = configuration - ADD_TRAIT(target, TRAIT_FISHING_SPOT, REF(src)) - RegisterSignal(target, COMSIG_PRE_FISHING, PROC_REF(create_fishing_spot)) - RegisterSignal(target, COMSIG_NPC_FISHING, PROC_REF(return_glob_fishing_spot)) - RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined)) - RegisterSignal(target, COMSIG_ATOM_EXAMINE_MORE, PROC_REF(on_examined_more)) - RegisterSignal(target, COMSIG_ATOM_EX_ACT, PROC_REF(explosive_fishing)) - RegisterSignal(target, COMSIG_FISH_RELEASED_INTO, PROC_REF(fish_released)) - RegisterSignal(target, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), PROC_REF(link_to_fish_porter)) - -/datum/element/lazy_fishing_spot/Detach(datum/target) - UnregisterSignal(target, list( - COMSIG_FISH_RELEASED_INTO, - COMSIG_PRE_FISHING, - COMSIG_NPC_FISHING, - COMSIG_ATOM_EXAMINE, - COMSIG_ATOM_EXAMINE_MORE, - COMSIG_ATOM_EX_ACT, - COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), - )) - REMOVE_TRAIT(target, TRAIT_FISHING_SPOT, REF(src)) - return ..() - -/datum/element/lazy_fishing_spot/proc/create_fishing_spot(datum/source) - SIGNAL_HANDLER - - source.AddComponent(/datum/component/fishing_spot, GLOB.preset_fish_sources[configuration]) - Detach(source) - -///If the fish source has fishes that are shown in the -/datum/element/lazy_fishing_spot/proc/on_examined(datum/source, mob/user, list/examine_text) - SIGNAL_HANDLER - if(!HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISHING_SPOT)) - return - - var/datum/fish_source/fish_source = GLOB.preset_fish_sources[configuration] - - if(!fish_source.has_known_fishes()) - return - - examine_text += span_tinynoticeital("This is a fishing spot. You can look again to list its fishes...") - -/datum/element/lazy_fishing_spot/proc/on_examined_more(datum/source, mob/user, list/examine_text) - SIGNAL_HANDLER - if(!HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISHING_SPOT)) - return - - var/datum/fish_source/fish_source = GLOB.preset_fish_sources[configuration] - fish_source.get_catchable_fish_names(user, source, examine_text) - -/datum/element/lazy_fishing_spot/proc/explosive_fishing(atom/location, severity) - SIGNAL_HANDLER - var/datum/fish_source/fish_source = GLOB.preset_fish_sources[configuration] - fish_source.spawn_reward_from_explosion(location, severity) - -/datum/element/lazy_fishing_spot/proc/return_glob_fishing_spot(datum/source, list/fish_spot_container) - fish_spot_container[NPC_FISHING_SPOT] = GLOB.preset_fish_sources[configuration] - -/datum/element/lazy_fishing_spot/proc/link_to_fish_porter(atom/source, mob/user, obj/item/multitool/tool) - SIGNAL_HANDLER - if(!istype(tool.buffer, /obj/machinery/fishing_portal_generator)) - return - var/datum/fish_source/fish_source = GLOB.preset_fish_sources[configuration] - var/obj/machinery/fishing_portal_generator/portal = tool.buffer - return portal.link_fishing_spot(fish_source, source, user) - -/datum/element/lazy_fishing_spot/proc/fish_released(datum/source, obj/item/fish/fish, mob/living/releaser) - SIGNAL_HANDLER - var/datum/fish_source/fish_source = GLOB.preset_fish_sources[configuration] - fish_source.readd_fish(fish, releaser) diff --git a/code/game/objects/structures/water_structures/toilet.dm b/code/game/objects/structures/water_structures/toilet.dm index 986d7eae4ea05..435af610996ae 100644 --- a/code/game/objects/structures/water_structures/toilet.dm +++ b/code/game/objects/structures/water_structures/toilet.dm @@ -31,7 +31,7 @@ cover_open = round(rand(0, 1)) update_appearance(UPDATE_ICON) if(mapload && SSmapping.level_trait(z, ZTRAIT_STATION)) - AddElement(/datum/element/lazy_fishing_spot, /datum/fish_source/toilet) + AddComponent(/datum/component/fishing_spot, GLOB.preset_fish_sources[/datum/fish_source/toilet]) AddElement(/datum/element/fish_safe_storage) register_context() diff --git a/code/game/turfs/open/lava.dm b/code/game/turfs/open/lava.dm index 06fb2d88bdea0..283ee3c31901a 100644 --- a/code/game/turfs/open/lava.dm +++ b/code/game/turfs/open/lava.dm @@ -49,7 +49,7 @@ /turf/open/lava/Initialize(mapload) . = ..() if(fish_source_type) - AddElement(/datum/element/lazy_fishing_spot, fish_source_type) + add_lazy_fishing(fish_source_type) // You can release chrabs and lavaloops and likes in lava, or be an absolute scumbag and drop other fish there too. ADD_TRAIT(src, TRAIT_CATCH_AND_RELEASE, INNATE_TRAIT) refresh_light() @@ -150,6 +150,8 @@ update_appearance(~UPDATE_SMOOTHING) /turf/open/lava/ex_act(severity, target) + if(fish_source) + GLOB.preset_fish_sources[fish_source].spawn_reward_from_explosion(src, severity) return FALSE /turf/open/lava/MakeSlippery(wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent) diff --git a/code/game/turfs/open/sand.dm b/code/game/turfs/open/sand.dm index 254c595fcbdd6..af583de777910 100644 --- a/code/game/turfs/open/sand.dm +++ b/code/game/turfs/open/sand.dm @@ -12,9 +12,11 @@ /turf/open/misc/beach/Initialize(mapload) . = ..() - AddElement(/datum/element/lazy_fishing_spot, /datum/fish_source/sand) + add_lazy_fishing(/datum/fish_source/sand) /turf/open/misc/beach/ex_act(severity, target) + if(fish_source) + GLOB.preset_fish_sources[fish_source].spawn_reward_from_explosion(src, severity) return FALSE /turf/open/misc/beach/sand diff --git a/code/game/turfs/open/water.dm b/code/game/turfs/open/water.dm index b70d780163556..67395193d4bdf 100644 --- a/code/game/turfs/open/water.dm +++ b/code/game/turfs/open/water.dm @@ -35,7 +35,7 @@ RegisterSignal(src, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, PROC_REF(on_atom_inited)) AddElement(/datum/element/watery_tile) if(!isnull(fishing_datum)) - AddElement(/datum/element/lazy_fishing_spot, fishing_datum) + add_lazy_fishing(fishing_datum) ADD_TRAIT(src, TRAIT_CATCH_AND_RELEASE, INNATE_TRAIT) ///We lazily add the immerse element when something is spawned or crosses this turf and not before. diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index fdbc1e04b8eb6..9fffce4b28f51 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -109,6 +109,9 @@ GLOBAL_LIST_EMPTY(station_turfs) /// Never directly access this, use get_explosive_block() instead var/inherent_explosive_resistance = -1 + ///The typepath we use for lazy fishing on turfs, to save on world init time. + var/fish_source + /turf/vv_edit_var(var_name, new_value) var/static/list/banned_edits = list(NAMEOF_STATIC(src, x), NAMEOF_STATIC(src, y), NAMEOF_STATIC(src, z)) @@ -790,3 +793,67 @@ GLOBAL_LIST_EMPTY(station_turfs) /// Returns whether it is safe for an atom to move across this turf /turf/proc/can_cross_safely(atom/movable/crossing) return TRUE + +/** + * the following are some hacky fishing-related optimizations to shave off + * time we spend implementing the fishing as possible, even if that means + * doing hackier code, because we've hundreds of turfs like lava, water etc every round, + */ +/turf/proc/add_lazy_fishing(fish_source_path) + RegisterSignal(src, COMSIG_PRE_FISHING, PROC_REF(add_fishing_spot_comp)) + RegisterSignal(src, COMSIG_NPC_FISHING, PROC_REF(on_npc_fishing)) + RegisterSignal(src, COMSIG_FISH_RELEASED_INTO, PROC_REF(on_fish_release_into)) + RegisterSignal(src, COMSIG_TURF_CHANGE, PROC_REF(remove_lazy_fishing)) + ADD_TRAIT(src, TRAIT_FISHING_SPOT, INNATE_TRAIT) + fish_source = fish_source_path + +/turf/proc/remove_lazy_fishing() + SIGNAL_HANDLER + UnregisterSignal(src, list( + COMSIG_PRE_FISHING, + COMSIG_NPC_FISHING, + COMSIG_FISH_RELEASED_INTO, + COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), + COMSIG_TURF_CHANGE, + )) + REMOVE_TRAIT(src, TRAIT_FISHING_SPOT, INNATE_TRAIT) + fish_source = null + +/turf/proc/add_fishing_spot_comp(datum/source) + SIGNAL_HANDLER + remove_lazy_fishing() + source.AddComponent(/datum/component/fishing_spot, fish_source) + +/turf/proc/on_npc_fishing(datum/source, list/fish_spot_container) + SIGNAL_HANDLER + fish_spot_container[NPC_FISHING_SPOT] = GLOB.preset_fish_sources[fish_source] + +/turf/proc/on_fish_release_into(datum/source, obj/item/fish/fish, mob/living/releaser) + SIGNAL_HANDLER + GLOB.preset_fish_sources[fish_source].readd_fish(fish, releaser) + +/turf/examine(mob/user) + . = ..() + if(!fish_source || !HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISHING_SPOT)) + return + if(!GLOB.preset_fish_sources[fish_source].has_known_fishes()) + return + . += span_tinynoticeital("This is a fishing spot. You can look again to list its fishes...") + +/turf/examine_more(mob/user) + . = ..() + if(!HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISHING_SPOT) || !fish_source) + return + GLOB.preset_fish_sources[fish_source].get_catchable_fish_names(user, src, .) + +/turf/ex_act(severity, target) + . = ..() + if(!fish_source) + return + GLOB.preset_fish_sources[fish_source].spawn_reward_from_explosion(src, severity) + +/turf/multitool_act(mob/living/user, obj/item/multitool/tool) + if(!fish_source || !istype(tool.buffer, /obj/machinery/fishing_portal_generator)) + return ..() + var/obj/machinery/fishing_portal_generator/portal = tool.buffer + return portal.link_fishing_spot(GLOB.preset_fish_sources[fish_source], src, user) diff --git a/code/modules/fishing/sources/_fish_source.dm b/code/modules/fishing/sources/_fish_source.dm index 9cde6df476af5..64c9977fbb924 100644 --- a/code/modules/fishing/sources/_fish_source.dm +++ b/code/modules/fishing/sources/_fish_source.dm @@ -1,4 +1,4 @@ -GLOBAL_LIST_INIT(preset_fish_sources, init_subtypes_w_path_keys(/datum/fish_source, list())) +GLOBAL_LIST_INIT_TYPED(preset_fish_sources, /datum/fish_source, init_subtypes_w_path_keys(/datum/fish_source, list())) /** * When adding new fishable rewards to a table/counts, you can specify an icon to show in place of the diff --git a/code/modules/food_and_drinks/machinery/deep_fryer.dm b/code/modules/food_and_drinks/machinery/deep_fryer.dm index 313bc29d199dc..4dfab9fb9474e 100644 --- a/code/modules/food_and_drinks/machinery/deep_fryer.dm +++ b/code/modules/food_and_drinks/machinery/deep_fryer.dm @@ -63,7 +63,7 @@ GLOBAL_LIST_INIT(oilfry_blacklisted_items, typecacheof(list( reagents.add_reagent(/datum/reagent/consumable/nutriment/fat/oil, 25) fry_loop = new(src, FALSE) RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(on_cleaned)) - AddElement(/datum/element/lazy_fishing_spot, /datum/fish_source/deepfryer) + AddComponent(/datum/component/fishing_spot, GLOB.preset_fish_sources[/datum/fish_source/deepfryer]) AddElement(/datum/element/fish_safe_storage) //Prevents fryish and fritterish from dying inside the deepfryer. /obj/machinery/deepfryer/Destroy() diff --git a/code/modules/unit_tests/fish_unit_tests.dm b/code/modules/unit_tests/fish_unit_tests.dm index 43f06ad11e58a..b399dcb94e12e 100644 --- a/code/modules/unit_tests/fish_unit_tests.dm +++ b/code/modules/unit_tests/fish_unit_tests.dm @@ -242,7 +242,7 @@ /obj/structure/toilet/unit_test/Initialize(mapload) . = ..() if(!HAS_TRAIT(src, TRAIT_FISHING_SPOT)) //Ensure this toilet has a fishing spot because only maploaded ones have it. - AddElement(/datum/element/lazy_fishing_spot, /datum/fish_source/toilet) + AddComponent(/datum/component/fishing_spot, GLOB.preset_fish_sources[/datum/fish_source/toilet]) // we want no default spawns in this unit test /datum/chasm_detritus/restricted/bodies/no_defaults @@ -379,7 +379,8 @@ ///From here, we check that the profound_fisher as well as fish source procs for rolling rewards don't fail. source = GLOB.preset_fish_sources[/datum/fish_source/unit_test_profound_fisher] - run_loc_floor_bottom_left.AddElement(/datum/element/lazy_fishing_spot, /datum/fish_source/unit_test_profound_fisher) + + run_loc_floor_bottom_left.AddComponent(/datum/component/fishing_spot, source) var/mob/living/basic/fisher = allocate(/mob/living/basic) fisher.AddComponent(/datum/component/profound_fisher) fisher.set_combat_mode(FALSE) @@ -388,7 +389,7 @@ TEST_FAIL("The unit test profound fisher didn't catch the test fish on a lazy fishing spot (element)") ///For good measure, let's try it again, but with the component this time, and a human mob and gloves - run_loc_floor_bottom_left.RemoveElement(/datum/element/lazy_fishing_spot, /datum/fish_source/unit_test_profound_fisher) + qdel(run_loc_floor_bottom_left.GetComponent(/datum/component/fishing_spot)) var/datum/component/comp = run_loc_floor_bottom_left.AddComponent(/datum/component/fishing_spot, source) var/mob/living/carbon/human/consistent/angler = allocate(/mob/living/carbon/human/consistent) var/obj/item/clothing/gloves/noodling = allocate(/obj/item/clothing/gloves) diff --git a/tgstation.dme b/tgstation.dme index 16d883964c4fc..ae33fa251309a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1513,7 +1513,6 @@ #include "code\datums\elements\kneecapping.dm" #include "code\datums\elements\kneejerk.dm" #include "code\datums\elements\knockback.dm" -#include "code\datums\elements\lazy_fishing_spot.dm" #include "code\datums\elements\leeching_walk.dm" #include "code\datums\elements\lifesteal.dm" #include "code\datums\elements\light_blocking.dm" From ea86a6646b00c75f3988fe88f80c1885ebf1a963 Mon Sep 17 00:00:00 2001 From: Ghom <42542238+Ghommie@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:25:35 +0100 Subject: [PATCH 54/55] [NO GBP] Fixes watery turfs not keeping you wet over time (#88086) --- code/datums/elements/watery_tile.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/datums/elements/watery_tile.dm b/code/datums/elements/watery_tile.dm index 36e893fe0fc3c..878253a89692f 100644 --- a/code/datums/elements/watery_tile.dm +++ b/code/datums/elements/watery_tile.dm @@ -54,6 +54,6 @@ duration = STATUS_EFFECT_PERMANENT status_type = STATUS_EFFECT_UNIQUE -/datum/status_effect/washing_regen/tick(seconds_between_ticks) +/datum/status_effect/watery_tile_wetness/tick(seconds_between_ticks) . = ..() owner.adjust_wet_stacks(1) From ef2ca85e8eadbfd6944fbe1aeb997a58d458a88b Mon Sep 17 00:00:00 2001 From: "tgstation-ci[bot]" <179393467+tgstation-ci[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 14:25:54 +0000 Subject: [PATCH 55/55] Automatic changelog for PR #88086 [ci skip] --- html/changelogs/AutoChangeLog-pr-88086.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-88086.yml diff --git a/html/changelogs/AutoChangeLog-pr-88086.yml b/html/changelogs/AutoChangeLog-pr-88086.yml new file mode 100644 index 0000000000000..ed78288b3be77 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-88086.yml @@ -0,0 +1,4 @@ +author: "thimothymtorres" +delete-after: True +changes: + - bugfix: "Fix wrong status effect for watery tile" \ No newline at end of file