(code bounty) The tram is now unstoppably powerful. it cannot be stopped, it cannot be slowed, it cannot be reasoned with. YOU HAVE NO IDEA HOW READY YOU ARE (#66657)

ever see the tram take 10 milliseconds per movement to move 2100 objects? now you have
https://user-images.githubusercontent.com/15794172/166198184-8bab93bd-f584-4269-9ed1-6aee746f8f3c.mp4
About The Pull Request

fixes #66887

done for the code bounty posted by @MMMiracles to optimize the tram so that it can be sped up. the tram is now twice as fast, firing every tick instead of every 2 ticks. and is now around 10x cheaper to move. also adds support for multiz trams, as in trams that span multiple z levels.

the tram on master takes around 10-15 milliseconds per movement with nothing on it other than its starting contents. why is this? because the tram is the canary in the coal mines when it comes to movement code, which is normally expensive as fuck. the tram does way more work than it needs to, and even finds new ways to slow the game down. I'll walk you through a few of the dumber things the tram currently does and how i fixed them.

    the tram, at absolute minimum, has to move 55 separate industrial_lift platforms once per movement. this means that the tram has to unregister its entered/exited signals 55 times when "the tram" as a singular object is only entering 5 new turfs and exiting 5 old turfs every movement, this means that each of the 55 platforms calculates their own destination turfs and checks their contents every movement. The biggest single optimization in this pr was that I made the tram into a single 5x11 multitile object and made it only do entering/exiting checks on the 5 new and 5 old turfs in each movement.
    way too many of the default tram contents are expensive to move for something that has to move a lot. fun fact, did you know that the walls on the tram have opacity? do you know what opacity does for movables? it makes them recalculate static lighting every time they move. did you know that the tram, this entire time, was taking JUST as much time spamming SSlighting updates as it was spending time in SStramprocess? well it is! now it doesnt do that, the walls are transparent. also, every window and every grille on the tram had the atmos_sensitive element applied to them which then added connect_loc to them, causing them to update signals every movement. that is also dumb and i got rid of that with snowflake overrides. Now we must take care to not add things that sneakily register to Moved() or the moved signal to the roundstart tram, because that is dumb, and the relative utility of simulating objects that should normally shatter due to heat and conduct heat from the atmosphere is far less than the cost of moving them, for this one object.
    all tram contents physically Entered() and Exited() their destination and old turfs every movement, even though because they are on a tram they literally do not interact with the turf, the tram does. also, any objects that use connect_loc or connect_loc behalf that are on the same point on the tram also interact with each other because of this. now all contents of the tram act as if theyre being abstract_move()'d to their destination so that (almost) nothing thats in the destination turf or the exit turf can react to the event of "something laying on the tram is moving over you". the rare things that DO need to know what is physically entering or exiting their turf regardless of whether theyre interacting with the ground can register to the abstract entered and exited signals which are now always sent.
    many of the things hooked into Moved(), whether it be overrides of Moved() itself, or handlers for the moved signal, add up to a LOT of processing time. especially for humans. now ive gotten rid of a lot of it, mostly for the tram but also for normal movement. i made footsteps (a significant portion of human movement cost) not do any work if the human themselves didnt do the movement. i optimized has_gravity() a fair amount, and then realized that since everything on the tram isnt changing momentum, i didnt actually need to check gravity for the purposes of drifting (newtonian_move() was taking a significant portion of the cost of movement at some points along the development process). so now it simply doesnt call newtonian_move() for movements that dont represent a change in momentum (by default all movements do).

also i put effort into 1. better organizing tram/lift code so that most of it is inside of a dedicated modules folder instead of scattered around 5 generic folders and 2. moved a lot of behavior from lift platforms themselves into their lift_master_datum since ideally the platforms would just handle moving themselves, while any behavior involving the entire lift such as "move to destination" and "blow up" would be handled by the lift_master_datum.

also
https://user-images.githubusercontent.com/15794172/166220129-ff2ea344-442f-4e3e-94f0-ec58ab438563.mp4
multiz tram (this just adds the capability to map it like this, no tram does this)
Actual Performance Differences

to benchmark this, i added a world.Profile(PROFILER_START) and world.Profile(PROFILER_START) to the tram moving, so that it generates a profiler output of all tram movement without any unrelated procs being recorded (except for world.Profile() overhead). this made it a lot easier to quantify what was slowing down both the tram and movement in general. and i did 3 types of tests on both master and my branch.

also i should note that i sped up the "master" tram test to move once per tick as well, simply because the normal movement speed seems unbearably slow now. so all recorded videos are done at twice the speed of the real tram on master. this doesnt affect the main thing i was trying to measure: cost for each movement.

the first test was the base tram, containing only my player mob and the movables starting on the tram roundstart. on master, this takes around 13 milliseconds or so on my computer (which is pretty close to what it takes on the servers), on this branch, it takes between 0.9-1.3 milliseconds.

ALSO in these benchmarks youll see that tram/proc/travel() will vary significantly between the master and optimized branches. this is 100% because there are 55 times more platforms moving on master compared to the master branch, and thus 55x more calls to this proc. every test was recorded with the exact same amount of distance moved

here are the master and optimized benchmark text files:
master
master base tram.txt
https://user-images.githubusercontent.com/15794172/166210149-f118683d-6f6d-4dfb-b9e4-14f17b26aad8.mp4
also this shows the increased SSlighting usage resulting from the tram on master spamming updates, which doesnt happen on the optimized branch

optimized
optimization base tram.txt
https://user-images.githubusercontent.com/15794172/166206280-cd849aaa-ed3b-4e2f-b741-b8a5726091a9.mp4

the second test is meant to benchmark the best case scaling cost of moving objects, where nothing extra is registered to movement besides the bare minimum stuff on the /atom/movable level. Each of the open tiles of the tram had 1 bluespace rped filled with parts dumped onto it, to the point that the tram in total was moving 2100 objects. the vast majority of these objects did nothing special in movement so they serve as a good base case. only slightly off due to the rped's registering to movement.

on master, this test takes over 100 milliseconds per movement
master 2000 obj's.txt
https://user-images.githubusercontent.com/15794172/166210560-f4de620d-7dc6-4dbd-8b61-4a48149af707.mp4

when optimized, about 10 milliseconds per movement
https://user-images.githubusercontent.com/15794172/166208654-bc10086b-bbfc-49fa-9987-d7558109cc1d.mp4
optimization 2000 obj's.txt

the third test is 300 humans spawned onto the tram, meant to test all the shit added on to movement cost for humans/carbons. in retrospect this test is actually way too biased in favor of my optimizations since the humans are all in only 3 tiles, so all 100 humans on a tile are reacting to the other 99 humans movements, which wouldnt be as bad if they were distributed across 20 tiles like in the second test. so dont read into this one too hard.

on master, this test takes 200 milliseconds
master 300 catgirls.txt

when optimized, this takes about 13-14 milliseconds.
optimization 300 catgirls on ram ranch.txt
Why It's Good For The Game

the tram is literally 10x cheaper to move. and the code is better organized.
currently on master the tram is as fast as running speed, meaning it has no real relative utility compared to just running the tracks (except for the added safety of not having to risk being ran over by the tram). now the tram of which we have an entire map based around can be used to its full potential.

also, has some fixes to things on the tram reacting to movement. for example on master if you are standing on a tram tile that contains a banana and the TRAM moves, you will slip if the banana was in that spot before you (not if you were there first however). this is because the banana has no concept of relative movement, you and it are in the same reference frame but the banana, which failed highschool physics, believes you to have moved onto it and thus subjected you to the humiliation of an unjust slipping. now since tram contents that dont register to abstract entered/exited cannot know about other tram contents on the same tile during a movement, this cannot happen.

also, you no longer make footstep sounds when the tram moves you over a floor
TODO

mainly opened it now so i can create a stopping point and attend to my other now staling prs, we're at a state of functionality far enough to start testmerging it anyways.

add a better way for admins to be notified of the tram overloading the server if someone purposefully stuffs it with as much shit as they can, and for admins to clear said shit.
automatically slow down the tram if SStramprocess takes over like, 10 milliseconds complete. the tram still cant really check tick and yield without introducing logic holes, so making sure it doesnt take half of the tick every tick is important
go over my code to catch dumb shit i forgot about, there always is for these kinds of refactors because im very messy
remove the area based forced_gravity optimization its not worth figuring out why it doesnt work
fix the inevitable merge conflict with master lol
create an icon for the tram_tunnel area type i made so that objects on the tram dont have to enter and exit areas twice in a cross-station traversal

    add an easy way to vv tram lethality for mobs/things being hit by it. its an easy target in another thing i already wanted to do: a reinforced concept of shared variables from any particular tram platform and the entire tram itself. admins should be able to slow down the tram by vv'ing one platform and have it apply to the entire tram for example.

Changelog

cl
balance: the tram is now twice as fast, pray it doesnt get any faster (it cant without raising world fps)
performance: the tram is now about 10 times cheaper to move for the server
add: mappers can now create trams with multiple z levels
code: industrial_lift's now have more of their behavior pertaining to "the entire lift" being handled by their lift_master_datum as opposed to belonging to a random platform on the lift.
/cl
This commit is contained in:
Kylerace
2022-06-23 18:42:09 -07:00
committed by GitHub
parent 186c9d8f7e
commit 8f0df7816b
75 changed files with 109194 additions and 108137 deletions

View File

@@ -10,14 +10,6 @@
},
/turf/open/floor/mineral/plastitanium,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"av" = (
/obj/effect/mapping_helpers/airlock/locked,
/obj/machinery/door/airlock/vault{
name = "Secured Door"
},
/obj/effect/mapping_helpers/airlock/access/all/away/generic3,
/turf/open/floor/mineral/plastitanium,
/area/ruin/space/has_grav/powered/hilbertresearchfacility/secretroom)
"az" = (
/turf/open/floor/carpet/black,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
@@ -238,6 +230,33 @@
/obj/structure/frame/computer,
/turf/open/floor/plating/airless,
/area/ruin/unpowered/no_grav)
"gm" = (
/obj/structure/fluff/tram_rail{
dir = 1
},
/obj/structure/window/reinforced/survival_pod{
dir = 4
},
/obj/structure/window/reinforced/survival_pod{
dir = 1
},
/obj/structure/shuttle/engine/propulsion/in_wall/tram{
dir = 4;
pixel_x = 32
},
/obj/structure/industrial_lift/tram{
icon_state = "titanium_purple"
},
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"gv" = (
/obj/structure/fluff/tram_rail,
/obj/machinery/door/window/survival_pod,
/obj/structure/industrial_lift/tram{
icon_state = "titanium_white"
},
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"gx" = (
/obj/machinery/mineral/processing_unit{
dir = 1
@@ -320,19 +339,6 @@
},
/turf/open/floor/grass/fairy,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"hN" = (
/obj/structure/fluff/tram_rail{
dir = 1
},
/obj/structure/industrial_lift/tram{
icon_state = "titanium_white";
initial_id = "middle_part_hilbert"
},
/obj/machinery/door/window/survival_pod{
dir = 1
},
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"hQ" = (
/obj/machinery/door/puzzle/light{
puzzle_id = "hilbert"
@@ -384,6 +390,22 @@
},
/turf/open/floor/mineral/titanium/tiled/yellow,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"iS" = (
/obj/effect/landmark/tram/middle_part/hilbert,
/obj/machinery/light/floor{
brightness = 2;
bulb_colour = "#deefff";
bulb_power = 0.6
},
/obj/machinery/computer/tram_controls{
dir = 8;
specific_lift_id = "tram_hilbert"
},
/obj/structure/industrial_lift/tram{
icon_state = "titanium_white"
},
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"iY" = (
/obj/machinery/mineral/processing_unit{
dir = 1
@@ -466,16 +488,6 @@
/obj/item/plant_analyzer,
/turf/open/floor/glass,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"kK" = (
/obj/structure/industrial_lift/tram{
icon_state = "titanium_white";
initial_id = "middle_part_hilbert"
},
/obj/structure/window/reinforced/survival_pod{
dir = 4
},
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"kO" = (
/obj/effect/turf_decal/siding/thinplating_new/light,
/turf/open/floor/mineral/titanium/tiled/white,
@@ -662,24 +674,6 @@
},
/turf/open/floor/mineral/titanium/tiled/yellow,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"pE" = (
/obj/effect/landmark/tram/middle_part/hilbert,
/obj/structure/industrial_lift/tram/central{
icon_state = "titanium_white";
initial_id = "middle_part_hilbert";
tram_id = "tram_hilbert"
},
/obj/machinery/computer/tram_controls{
dir = 8;
tram_id = "tram_hilbert"
},
/obj/machinery/light/floor{
brightness = 2;
bulb_colour = "#deefff";
bulb_power = 0.6
},
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"pI" = (
/obj/structure/chair/stool/bar/directional/south,
/obj/effect/decal/cleanable/dirt/dust,
@@ -940,6 +934,14 @@
},
/turf/open/floor/grass/fairy,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"vT" = (
/obj/effect/mapping_helpers/airlock/locked,
/obj/machinery/door/airlock/vault{
name = "Secured Door"
},
/obj/effect/mapping_helpers/airlock/access/all/away/generic3,
/turf/open/floor/mineral/plastitanium,
/area/ruin/space/has_grav/powered/hilbertresearchfacility/secretroom)
"vZ" = (
/obj/effect/turf_decal/siding/wood{
dir = 4
@@ -1008,6 +1010,21 @@
},
/turf/open/floor/mineral/titanium/tiled/yellow,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"xI" = (
/obj/structure/fluff/tram_rail,
/obj/structure/window/reinforced/survival_pod{
dir = 4
},
/obj/structure/window/reinforced/survival_pod,
/obj/structure/shuttle/engine/propulsion/in_wall/tram{
dir = 4;
pixel_x = 32
},
/obj/structure/industrial_lift/tram{
icon_state = "titanium_purple"
},
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"xO" = (
/obj/structure/shuttle/engine/propulsion/burst{
anchored = 0;
@@ -1524,6 +1541,16 @@
},
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"HN" = (
/obj/structure/window/reinforced/survival_pod{
dir = 4
},
/obj/structure/industrial_lift/tram{
icon_state = "titanium_white"
},
/obj/effect/landmark/lift_id/hilbert,
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"Ic" = (
/turf/open/floor/iron/stairs{
dir = 4
@@ -1604,6 +1631,21 @@
},
/turf/open/floor/mineral/titanium/tiled/white,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"Jd" = (
/obj/structure/fluff/tram_rail,
/obj/structure/window/reinforced/survival_pod{
dir = 8
},
/obj/structure/window/reinforced/survival_pod,
/obj/structure/shuttle/engine/propulsion/in_wall/tram{
dir = 8;
pixel_x = -32
},
/obj/structure/industrial_lift/tram{
icon_state = "titanium_purple"
},
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"Jh" = (
/obj/structure/fluff/tram_rail/anchor,
/turf/open/floor/engine,
@@ -1651,64 +1693,13 @@
/obj/structure/barricade/wooden,
/turf/open/floor/mineral/titanium/tiled/purple,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"Kv" = (
/obj/structure/industrial_lift/tram{
icon_state = "titanium_white";
initial_id = "middle_part_hilbert"
},
/obj/structure/window/reinforced/survival_pod{
dir = 8
},
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"KC" = (
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"KF" = (
/obj/structure/fluff/tram_rail,
/obj/structure/industrial_lift/tram{
icon_state = "titanium_purple";
initial_id = "middle_part_hilbert"
},
/obj/structure/shuttle/engine/propulsion/in_wall{
dir = 8;
pixel_x = -32
},
/obj/structure/window/reinforced/survival_pod{
dir = 8
},
/obj/structure/window/reinforced/survival_pod,
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"KI" = (
/obj/structure/fluff/tram_rail,
/obj/structure/industrial_lift/tram{
icon_state = "titanium_white";
initial_id = "middle_part_hilbert"
},
/obj/machinery/door/window/survival_pod,
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"KJ" = (
/obj/structure/fans/tiny,
/turf/open/floor/mineral/plastitanium,
/area/ruin/unpowered/no_grav)
"KK" = (
/obj/structure/fluff/tram_rail,
/obj/structure/industrial_lift/tram{
icon_state = "titanium_purple";
initial_id = "middle_part_hilbert"
},
/obj/structure/shuttle/engine/propulsion/in_wall{
dir = 4;
pixel_x = 32
},
/obj/structure/window/reinforced/survival_pod{
dir = 4
},
/obj/structure/window/reinforced/survival_pod,
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"KL" = (
/obj/structure/chair/stool/bar/directional/north,
/obj/effect/decal/cleanable/dirt/dust,
@@ -1783,26 +1774,6 @@
/obj/structure/chair/stool/bar/directional/west,
/turf/open/floor/wood,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"MR" = (
/obj/structure/fluff/tram_rail{
dir = 1
},
/obj/structure/industrial_lift/tram{
icon_state = "titanium_purple";
initial_id = "middle_part_hilbert"
},
/obj/structure/shuttle/engine/propulsion/in_wall{
dir = 8;
pixel_x = -32
},
/obj/structure/window/reinforced/survival_pod{
dir = 8
},
/obj/structure/window/reinforced/survival_pod{
dir = 1
},
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"MX" = (
/turf/closed/wall/mineral/titanium,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
@@ -1885,6 +1856,18 @@
},
/turf/open/floor/iron/grimy,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"Pw" = (
/obj/structure/fluff/tram_rail{
dir = 1
},
/obj/machinery/door/window/survival_pod{
dir = 1
},
/obj/structure/industrial_lift/tram{
icon_state = "titanium_white"
},
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"Px" = (
/obj/machinery/door/window/left/directional/south,
/turf/open/floor/iron/grimy,
@@ -1938,6 +1921,15 @@
},
/turf/open/floor/wood,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"QS" = (
/obj/structure/window/reinforced/survival_pod{
dir = 8
},
/obj/structure/industrial_lift/tram{
icon_state = "titanium_white"
},
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"QU" = (
/obj/structure/railing/corner{
dir = 1
@@ -1990,26 +1982,6 @@
},
/turf/open/floor/mineral/titanium/tiled/white,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"Rz" = (
/obj/structure/fluff/tram_rail{
dir = 1
},
/obj/structure/industrial_lift/tram{
icon_state = "titanium_purple";
initial_id = "middle_part_hilbert"
},
/obj/structure/shuttle/engine/propulsion/in_wall{
dir = 4;
pixel_x = 32
},
/obj/structure/window/reinforced/survival_pod{
dir = 4
},
/obj/structure/window/reinforced/survival_pod{
dir = 1
},
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"RE" = (
/obj/item/stack/cable_coil/cut,
/turf/open/floor/plating,
@@ -2357,6 +2329,25 @@
/obj/structure/table/reinforced/rglass,
/turf/open/floor/wood,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"Yt" = (
/obj/structure/fluff/tram_rail{
dir = 1
},
/obj/structure/window/reinforced/survival_pod{
dir = 8
},
/obj/structure/window/reinforced/survival_pod{
dir = 1
},
/obj/structure/shuttle/engine/propulsion/in_wall/tram{
dir = 8;
pixel_x = -32
},
/obj/structure/industrial_lift/tram{
icon_state = "titanium_purple"
},
/turf/open/floor/engine,
/area/ruin/space/has_grav/powered/hilbertresearchfacility)
"Yv" = (
/obj/item/kirbyplants/random,
/obj/effect/turf_decal/siding/wood{
@@ -3797,9 +3788,9 @@ wo
Bo
zu
Xi
MR
Kv
KF
Yt
QS
Jd
YG
Sa
Bo
@@ -3836,9 +3827,9 @@ YL
Fg
zC
Xi
hN
pE
KI
Pw
iS
gv
YG
WY
UF
@@ -3875,9 +3866,9 @@ La
Bo
Ax
Xi
Rz
kK
KK
gm
HN
xI
YG
Sp
Bo
@@ -5214,7 +5205,7 @@ La
La
ru
La
av
vT
sj
xc
sj

View File

@@ -3327,10 +3327,11 @@
/turf/open/floor/plating/icemoon,
/area/station/engineering/atmos)
"bed" = (
/obj/structure/industrial_lift{
id = "publicElevator"
},
/obj/machinery/light/floor,
/obj/structure/industrial_lift,
/obj/effect/landmark/lift_id{
specific_lift_id = "publicElevator"
},
/turf/open/openspace,
/area/station/commons/storage/mining)
"ben" = (
@@ -26836,8 +26837,8 @@
},
/obj/effect/turf_decal/tile/neutral/fourcorners,
/obj/effect/turf_decal/loading_area{
dir = 1;
color = "#439C1E"
color = "#439C1E";
dir = 1
},
/obj/effect/turf_decal/trimline/dark_red/warning{
dir = 1
@@ -33112,12 +33113,12 @@
"kpG" = (
/obj/structure/table/reinforced,
/obj/item/hfr_box/body/waste_output{
pixel_y = -1;
pixel_x = 5
pixel_x = 5;
pixel_y = -1
},
/obj/item/hfr_box/body/moderator_input{
pixel_y = 8;
pixel_x = -5
pixel_x = -5;
pixel_y = 8
},
/obj/item/radio/intercom/directional/west,
/turf/open/floor/iron/dark,
@@ -48815,8 +48816,8 @@
/obj/structure/table/reinforced,
/obj/item/hfr_box/body/fuel_input,
/obj/item/hfr_box/body/interface{
pixel_y = 9;
pixel_x = 6
pixel_x = 6;
pixel_y = 9
},
/turf/open/floor/iron/dark,
/area/station/engineering/atmos/hfr_room)
@@ -52505,8 +52506,8 @@
"qqh" = (
/obj/effect/turf_decal/tile/neutral/fourcorners,
/obj/machinery/atmospherics/components/unary/portables_connector/visible{
name = "Exfiltrate Port";
dir = 8
dir = 8;
name = "Exfiltrate Port"
},
/turf/open/floor/iron/dark,
/area/station/engineering/atmos/mix)
@@ -60802,9 +60803,9 @@
/area/station/maintenance/port/aft)
"sVE" = (
/obj/machinery/door/poddoor/shutters/preopen{
dir = 8;
id = "Atmospherics HFR Shutters";
name = "Atmospherics HFR Shutters";
dir = 8
name = "Atmospherics HFR Shutters"
},
/obj/effect/turf_decal/trimline/yellow/warning{
dir = 4
@@ -71820,8 +71821,8 @@
"wrE" = (
/obj/structure/table/reinforced,
/obj/item/stack/sheet/glass/fifty{
pixel_y = 0;
pixel_x = 2
pixel_x = 2;
pixel_y = 0
},
/obj/item/stack/sheet/iron/fifty,
/obj/item/stack/cable_coil{

File diff suppressed because it is too large Load Diff

View File

@@ -45,6 +45,8 @@
#define COMSIG_ATOM_SMOOTHED_ICON "atom_smoothed_icon"
///from base of atom/Entered(): (atom/movable/arrived, atom/old_loc, list/atom/old_locs)
#define COMSIG_ATOM_ENTERED "atom_entered"
///from base of atom/movable/Moved(): (atom/movable/arrived, atom/old_loc, list/atom/old_locs)
#define COMSIG_ATOM_ABSTRACT_ENTERED "atom_abstract_entered"
/// Sent from the atom that just Entered src. From base of atom/Entered(): (/atom/destination, atom/old_loc, list/atom/old_locs)
#define COMSIG_ATOM_ENTERING "atom_entering"
///from base of atom/Exit(): (/atom/movable/leaving, direction)
@@ -52,6 +54,8 @@
#define COMPONENT_ATOM_BLOCK_EXIT (1<<0)
///from base of atom/Exited(): (atom/movable/gone, direction)
#define COMSIG_ATOM_EXITED "atom_exited"
///from base of atom/movable/Moved(): (atom/movable/gone, direction)
#define COMSIG_ATOM_ABSTRACT_EXITED "atom_abstract_exited"
///from base of atom/Bumped(): (/atom/movable)
#define COMSIG_ATOM_BUMPED "atom_bumped"
///from base of atom/handle_atom_del(): (atom/deleted)

View File

@@ -59,6 +59,9 @@
/// from /obj/machinery/atmospherics/components/unary/cryo_cell/set_on(bool): (on)
#define COMSIG_CRYO_SET_ON "cryo_set_on"
/// from /obj/proc/make_unfrozen()
#define COMSIG_OBJ_UNFREEZE "obj_unfreeze"
// /obj/machinery/atmospherics/components/binary/valve signals
/// from /obj/machinery/atmospherics/components/binary/valve/toggle(): (on)

View File

@@ -5,3 +5,5 @@
///the client mobs channel of the important_recursive_contents list, everything in here will be a mob with an attached client
///this is given to both a clients mob, and a clients eye, both point to the clients mob
#define RECURSIVE_CONTENTS_CLIENT_MOBS "recursive_contents_client_mobs"
///the parent of storage components currently shown to some client mob get this. gets removed when nothing is viewing the parent
#define RECURSIVE_CONTENTS_ACTIVE_STORAGE "recursive_contents_active_storage"

View File

@@ -0,0 +1,20 @@
//Booleans in arguments are confusing, so I made them defines.
///the lift's controls are currently locked from user input
#define LIFT_PLATFORM_LOCKED 1
///the lift's controls are currently unlocked so user's can direct it
#define LIFT_PLATFORM_UNLOCKED 0
//lift_id's
///basic lift_id, goes up and down
#define BASIC_LIFT_ID "base"
///tram lift_id, goes left and right or north and south. maybe one day be able to turn and go up/down as well
#define TRAM_LIFT_ID "tram"
///debug lift_id
#define DEBUG_LIFT_ID "debug"
//specific_lift_id's
///the specific_lift_id of the main station tram landmark for tramstation that spawns roundstart.
#define MAIN_STATION_TRAM "main station tram"
///the specific_lift_id of the tram on the hilbert research station
#define HILBERT_TRAM "tram_hilbert"

View File

@@ -9,7 +9,6 @@
#define ON_BLUEPRINTS (1<<5) //Are we visible on the station blueprints at roundstart?
#define UNIQUE_RENAME (1<<6) // can you customize the description/name of the thing?
#define USES_TGUI (1<<7) //put on things that use tgui on ui_interact instead of custom/old UI.
#define FROZEN (1<<8)
#define BLOCK_Z_OUT_DOWN (1<<9) // Should this object block z falling from loc?
#define BLOCK_Z_OUT_UP (1<<10) // Should this object block z uprise from loc?
#define BLOCK_Z_IN_DOWN (1<<11) // Should this object block z falling from above?

View File

@@ -488,6 +488,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_AREA_SENSITIVE "area-sensitive"
///every hearing sensitive atom has this trait
#define TRAIT_HEARING_SENSITIVE "hearing_sensitive"
///every object that is currently the active storage of some client mob has this trait
#define TRAIT_ACTIVE_STORAGE "active_storage"
/// Climbable trait, given and taken by the climbable element when added or removed. Exists to be easily checked via HAS_TRAIT().
#define TRAIT_CLIMBABLE "trait_climbable"
@@ -895,5 +897,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
/// Ignores body_parts_covered during the add_fingerprint() proc. Works both on the person and the item in the glove slot.
#define TRAIT_FINGERPRINT_PASSTHROUGH "fingerprint_passthrough"
/// this object has been frozen
#define TRAIT_FROZEN "frozen"
/// Currently fishing
#define TRAIT_GONE_FISHING "fishing"

View File

@@ -1086,23 +1086,9 @@ GLOBAL_LIST_EMPTY(friendly_animal_types)
GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0,0,0)))
/obj/proc/make_frozen_visual()
// Used to make the frozen item visuals for Freon.
if(resistance_flags & FREEZE_PROOF)
return
if(!(obj_flags & FROZEN))
name = "frozen [name]"
add_atom_colour(GLOB.freon_color_matrix, TEMPORARY_COLOUR_PRIORITY)
alpha -= 25
obj_flags |= FROZEN
//Assumes already frozed
/obj/proc/make_unfrozen()
if(obj_flags & FROZEN)
name = replacetext(name, "frozen ", "")
remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, GLOB.freon_color_matrix)
alpha += 25
obj_flags &= ~FROZEN
SEND_SIGNAL(src, COMSIG_OBJ_UNFREEZE)
/// generates a filename for a given asset.
/// like generate_asset_name(), except returns the rsc reference and the rsc file hash as well as the asset name (sans extension)

View File

@@ -274,7 +274,6 @@ DEFINE_BITFIELD(obj_flags, list(
"CAN_BE_HIT" = CAN_BE_HIT,
"DANGEROUS_POSSESSION" = DANGEROUS_POSSESSION,
"EMAGGED" = EMAGGED,
"FROZEN" = FROZEN,
"IN_USE" = IN_USE,
"NO_BUILD" = NO_BUILD,
"ON_BLUEPRINTS" = ON_BLUEPRINTS,

View File

@@ -213,11 +213,13 @@
for(var/atom/target in checking) // will filter out nulls
if(closed[target] || isarea(target)) // avoid infinity situations
continue
closed[target] = TRUE
if(isturf(target) || isturf(target.loc) || (target in direct_access) || (ismovable(target) && target.flags_1 & IS_ONTOP_1)) //Directly accessible atoms
if(Adjacent(target) || (tool && CheckToolReach(src, target, tool.reach))) //Adjacent or reaching attacks
return TRUE
closed[target] = TRUE
if (!target.loc)
continue

View File

@@ -528,18 +528,18 @@ SUBSYSTEM_DEF(explosions)
var/base_shake_amount = sqrt(near_distance / (distance + 1))
if(distance <= round(near_distance + world.view - 2, 1)) // If you are close enough to see the effects of the explosion first-hand (ignoring walls)
listener.playsound_local(epicenter, null, 100, TRUE, frequency, S = near_sound)
listener.playsound_local(epicenter, null, 100, TRUE, frequency, sound_to_use = near_sound)
if(base_shake_amount > 0)
shake_camera(listener, NEAR_SHAKE_DURATION, clamp(base_shake_amount, 0, NEAR_SHAKE_CAP))
else if(distance < far_distance) // You can hear a far explosion if you are outside the blast radius. Small explosions shouldn't be heard throughout the station.
var/far_volume = clamp(far_distance / 2, FAR_LOWER, FAR_UPPER)
if(creaking)
listener.playsound_local(epicenter, null, far_volume, TRUE, frequency, S = creaking_sound, distance_multiplier = 0)
listener.playsound_local(epicenter, null, far_volume, TRUE, frequency, sound_to_use = creaking_sound, distance_multiplier = 0)
else if(prob(FAR_SOUND_PROB)) // Sound variety during meteor storm/tesloose/other bad event
listener.playsound_local(epicenter, null, far_volume, TRUE, frequency, S = far_sound, distance_multiplier = 0)
listener.playsound_local(epicenter, null, far_volume, TRUE, frequency, sound_to_use = far_sound, distance_multiplier = 0)
else
listener.playsound_local(epicenter, null, far_volume, TRUE, frequency, S = echo_sound, distance_multiplier = 0)
listener.playsound_local(epicenter, null, far_volume, TRUE, frequency, sound_to_use = echo_sound, distance_multiplier = 0)
if(base_shake_amount || quake_factor)
base_shake_amount = max(base_shake_amount, quake_factor * 3, 0) // Devastating explosions rock the station and ground
@@ -552,7 +552,7 @@ SUBSYSTEM_DEF(explosions)
shake_camera(listener, FAR_SHAKE_DURATION, clamp(quake_factor / 4, 0, FAR_SHAKE_CAP))
else
echo_volume = 40
listener.playsound_local(epicenter, null, echo_volume, TRUE, frequency, S = echo_sound, distance_multiplier = 0)
listener.playsound_local(epicenter, null, echo_volume, TRUE, frequency, sound_to_use = echo_sound, distance_multiplier = 0)
if(creaking) // 5 seconds after the bang, the station begins to creak
addtimer(CALLBACK(listener, /mob/proc/playsound_local, epicenter, null, rand(FREQ_LOWER, FREQ_UPPER), TRUE, frequency, null, null, FALSE, hull_creaking_sound, 0), CREAK_DELAY)

View File

@@ -40,14 +40,21 @@ SUBSYSTEM_DEF(mapping)
// Z-manager stuff
var/station_start // should only be used for maploading-related tasks
var/space_levels_so_far = 0
///list of all the z level datums created representing the z levels in the world
var/list/z_list
///list of all z level datums in the order of their z (z level 1 is at index 1, etc.)
var/list/datum/space_level/z_list
///list of all z level indices that form multiz connections and whether theyre linked up or down
///list of lists, inner lists are of the form: list("up or down link direction" = TRUE)
var/list/multiz_levels = list()
var/datum/space_level/transit
var/datum/space_level/empty_space
var/num_of_res_levels = 1
/// True when in the process of adding a new Z-level, global locking
var/adding_new_zlevel = FALSE
///shows the default gravity value for each z level. recalculated when gravity generators change.
///associative list of the form: list("[z level num]" = max generator gravity in that z level OR the gravity level trait)
var/list/gravity_by_z_level = list()
/datum/controller/subsystem/mapping/New()
..()
#ifdef FORCE_MAP
@@ -101,8 +108,48 @@ SUBSYSTEM_DEF(mapping)
generate_station_area_list()
initialize_reserved_level(transit.z_value)
SSticker.OnRoundstart(CALLBACK(src, .proc/spawn_maintenance_loot))
generate_z_level_linkages()
calculate_default_z_level_gravities()
return ..()
/datum/controller/subsystem/mapping/proc/calculate_default_z_level_gravities()
for(var/z_level in 1 to length(z_list))
calculate_z_level_gravity(z_level)
/datum/controller/subsystem/mapping/proc/generate_z_level_linkages()
for(var/z_level in 1 to length(z_list))
generate_linkages_for_z_level(z_level)
/datum/controller/subsystem/mapping/proc/generate_linkages_for_z_level(z_level)
if(!isnum(z_level) || z_level <= 0)
return FALSE
if(multiz_levels.len < z_level)
multiz_levels.len = z_level
var/linked_down = level_trait(z_level, ZTRAIT_DOWN)
var/linked_up = level_trait(z_level, ZTRAIT_UP)
multiz_levels[z_level] = list()
if(linked_down)
multiz_levels[z_level]["[DOWN]"] = TRUE
if(linked_up)
multiz_levels[z_level]["[UP]"] = TRUE
/datum/controller/subsystem/mapping/proc/calculate_z_level_gravity(z_level_number)
if(!isnum(z_level_number) || z_level_number < 1)
return FALSE
var/max_gravity = 0
for(var/obj/machinery/gravity_generator/main/grav_gen as anything in GLOB.gravity_generators["[z_level_number]"])
max_gravity = max(grav_gen.setting, max_gravity)
max_gravity = max_gravity || level_trait(z_level_number, ZTRAIT_GRAVITY) || 0//just to make sure no nulls
gravity_by_z_level["[z_level_number]"] = max_gravity
return max_gravity
/**
* ##setup_ruins
*
@@ -215,6 +262,7 @@ Used by the AI doomsday and the self-destruct nuke.
clearing_reserved_turfs = SSmapping.clearing_reserved_turfs
z_list = SSmapping.z_list
multiz_levels = SSmapping.multiz_levels
#define INIT_ANNOUNCE(X) to_chat(world, span_boldannounce("[X]")); log_world(X)
/datum/controller/subsystem/mapping/proc/LoadGroup(list/errorList, name, path, files, list/traits, list/default_traits, silent = FALSE)

View File

@@ -1,5 +1,15 @@
PROCESSING_SUBSYSTEM_DEF(tramprocess)
name = "Tram Process"
wait = 1
wait = 0.5
/// only used on maps with trams, so only enabled by such.
can_fire = FALSE
///how much time a tram can take per movement before we notify admins and slow down the tram. in milliseconds
var/max_time = 15
///how many times the tram can move costing over max_time milliseconds before it gets slowed down
var/max_exceeding_moves = 5
///how many times the tram can move costing less than half max_time milliseconds before we speed it back up again.
///is only used if the tram has been slowed down for exceeding max_time
var/max_cheap_moves = 5

View File

@@ -171,53 +171,6 @@ SUBSYSTEM_DEF(spatial_grid)
var/datum/spatial_grid_cell/cell = new(x, y, z_level.z_value)
new_cell_grid[y] += cell
///creates number_to_generate new oranges_ear's and adds them to the subsystems list of ears.
///i really fucking hope this never gets called after init :clueless:
/datum/controller/subsystem/spatial_grid/proc/pregenerate_more_oranges_ears(number_to_generate)
for(var/new_ear in 1 to number_to_generate)
pregenerated_oranges_ears += new/mob/oranges_ear(null)
number_of_oranges_ears = length(pregenerated_oranges_ears)
///allocate one [/mob/oranges_ear] mob per turf containing atoms_that_need_ears and give them a reference to every listed atom in their turf.
///if an oranges_ear is allocated to a turf that already has an oranges_ear then the second one fails to allocate (and gives the existing one the atom it was assigned to)
/datum/controller/subsystem/spatial_grid/proc/assign_oranges_ears(list/atoms_that_need_ears)
var/input_length = length(atoms_that_need_ears)
if(input_length > number_of_oranges_ears)
stack_trace("somehow, for some reason, more than the preset generated number of oranges ears was requested. thats fucking [number_of_oranges_ears]. this is not good that should literally never happen")
pregenerate_more_oranges_ears(input_length - number_of_oranges_ears)//im still gonna DO IT but ill complain about it
. = list()
///the next unallocated /mob/oranges_ear that we try to allocate to assigned_atom's turf
var/mob/oranges_ear/current_ear
///the next atom in atoms_that_need_ears an ear assigned to it
var/atom/assigned_atom
///the turf loc of the current assigned_atom. turfs are used to track oranges_ears already assigned to one location so we dont allocate more than one
///because allocating more than one oranges_ear to a given loc wastes view iterations
var/turf/turf_loc
for(var/current_ear_index in 1 to input_length)
assigned_atom = atoms_that_need_ears[current_ear_index]
turf_loc = get_turf(assigned_atom)
if(!turf_loc)
continue
current_ear = pregenerated_oranges_ears[current_ear_index]
if(turf_loc.assigned_oranges_ear)
turf_loc.assigned_oranges_ear.references += assigned_atom
continue //if theres already an oranges_ear mob at assigned_movable's turf we give assigned_movable to it instead and dont allocate ourselves
current_ear.references += assigned_atom
current_ear.loc = turf_loc //normally this is bad, but since this is meant to be as fast as possible we literally just need to exist there for view() to see us
turf_loc.assigned_oranges_ear = current_ear
. += current_ear
///adds cells to the grid for every z level when world.maxx or world.maxy is expanded after this subsystem is initialized. hopefully this is never needed.
///because i never tested this.
/datum/controller/subsystem/spatial_grid/proc/after_world_bounds_expanded(datum/controller/subsystem/processing/dcs/fucking_dcs, has_expanded_world_maxx, has_expanded_world_maxy)
@@ -278,10 +231,6 @@ SUBSYSTEM_DEF(spatial_grid)
. = list()
//cache for sanic speeds
var/cells_on_y_axis = src.cells_on_y_axis
var/cells_on_x_axis = src.cells_on_x_axis
//technically THIS list only contains lists, but inside those lists are grid cell datums and we can go without a SINGLE var init if we do this
var/list/datum/spatial_grid_cell/grid_level = grids_by_z_level[center_turf.z]
@@ -458,6 +407,8 @@ SUBSYSTEM_DEF(spatial_grid)
GRID_CELL_SET(intersecting_cell.atmos_contents, new_target)
SEND_SIGNAL(intersecting_cell, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_ATMOS), new_target)
return intersecting_cell
/**
* find the spatial map cell that target used to belong to, then remove the target (and sometimes it's important_recusive_contents) from it.
* make sure to provide the turf old_target used to be "in"
@@ -584,6 +535,53 @@ SUBSYSTEM_DEF(spatial_grid)
message_admins(cell_coords)
message_admins("[src] is supposed to only be contained in the cell at indexes ([real_cell.cell_x], [real_cell.cell_y], [real_cell.cell_z]). but is contained at the cells at [cell_coords]")
///creates number_to_generate new oranges_ear's and adds them to the subsystems list of ears.
///i really fucking hope this never gets called after init :clueless:
/datum/controller/subsystem/spatial_grid/proc/pregenerate_more_oranges_ears(number_to_generate)
for(var/new_ear in 1 to number_to_generate)
pregenerated_oranges_ears += new/mob/oranges_ear(null)
number_of_oranges_ears = length(pregenerated_oranges_ears)
///allocate one [/mob/oranges_ear] mob per turf containing atoms_that_need_ears and give them a reference to every listed atom in their turf.
///if an oranges_ear is allocated to a turf that already has an oranges_ear then the second one fails to allocate (and gives the existing one the atom it was assigned to)
/datum/controller/subsystem/spatial_grid/proc/assign_oranges_ears(list/atoms_that_need_ears)
var/input_length = length(atoms_that_need_ears)
if(input_length > number_of_oranges_ears)
stack_trace("somehow, for some reason, more than the preset generated number of oranges ears was requested. thats fucking [number_of_oranges_ears]. this is not good that should literally never happen")
pregenerate_more_oranges_ears(input_length - number_of_oranges_ears)//im still gonna DO IT but ill complain about it
. = list()
///the next unallocated /mob/oranges_ear that we try to allocate to assigned_atom's turf
var/mob/oranges_ear/current_ear
///the next atom in atoms_that_need_ears an ear assigned to it
var/atom/assigned_atom
///the turf loc of the current assigned_atom. turfs are used to track oranges_ears already assigned to one location so we dont allocate more than one
///because allocating more than one oranges_ear to a given loc wastes view iterations
var/turf/turf_loc
for(var/current_ear_index in 1 to input_length)
assigned_atom = atoms_that_need_ears[current_ear_index]
turf_loc = get_turf(assigned_atom)
if(!turf_loc)
continue
current_ear = pregenerated_oranges_ears[current_ear_index]
if(turf_loc.assigned_oranges_ear)
turf_loc.assigned_oranges_ear.references += assigned_atom
continue //if theres already an oranges_ear mob at assigned_movable's turf we give assigned_movable to it instead and dont allocate ourselves
current_ear.references += assigned_atom
current_ear.loc = turf_loc //normally this is bad, but since this is meant to be as fast as possible we literally just need to exist there for view() to see us
turf_loc.assigned_oranges_ear = current_ear
. += current_ear
///debug proc for finding how full the cells of src's z level are
/atom/proc/find_grid_statistics_for_z_level(insert_clients = 0)
var/raw_clients = 0

View File

@@ -53,19 +53,42 @@
var/obj/item/parent_item = parent
parent_item.update_slot_icon()
/datum/component/bloodysoles/proc/reset_bloody_shoes()
bloody_shoes = list(BLOOD_STATE_HUMAN = 0, BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0)
on_changed_bloody_shoes(BLOOD_STATE_NOT_BLOODY)
///lowers bloody_shoes[index] by adjust_by
/datum/component/bloodysoles/proc/adjust_bloody_shoes(index, adjust_by)
bloody_shoes[index] = max(bloody_shoes[index] - adjust_by, 0)
on_changed_bloody_shoes()
/datum/component/bloodysoles/proc/set_bloody_shoes(index, new_value)
bloody_shoes[index] = new_value
on_changed_bloody_shoes(index)
///called whenever the value of bloody_soles changes
/datum/component/bloodysoles/proc/on_changed_bloody_shoes(index)
if(index && index != last_blood_state)
last_blood_state = index
if(!wielder)
return
if(bloody_shoes[last_blood_state] <= BLOOD_FOOTPRINTS_MIN * 2)//need twice that amount to make footprints
UnregisterSignal(wielder, COMSIG_MOVABLE_MOVED)
else
RegisterSignal(wielder, COMSIG_MOVABLE_MOVED, .proc/on_moved, override = TRUE)
/**
* Run to equally share the blood between us and a decal
*/
/datum/component/bloodysoles/proc/share_blood(obj/effect/decal/cleanable/pool)
last_blood_state = pool.blood_state
// Share the blood between our boots and the blood pool
var/total_bloodiness = pool.bloodiness + bloody_shoes[last_blood_state]
var/total_bloodiness = pool.bloodiness + bloody_shoes[pool.blood_state]
// We can however be limited by how much blood we can hold
var/new_our_bloodiness = min(BLOOD_ITEM_MAX, total_bloodiness / 2)
bloody_shoes[last_blood_state] = new_our_bloodiness
set_bloody_shoes(pool.blood_state, new_our_bloodiness)
pool.bloodiness = total_bloodiness - new_our_bloodiness // Give the pool the remaining blood incase we were limited
if(HAS_TRAIT(parent_atom, TRAIT_LIGHT_STEP)) //the character is agile enough to don't mess their clothing and hands just from one blood splatter at floor
@@ -105,7 +128,8 @@
equipped_slot = slot
wielder = equipper
RegisterSignal(wielder, COMSIG_MOVABLE_MOVED, .proc/on_moved)
if(bloody_shoes[last_blood_state] > BLOOD_FOOTPRINTS_MIN * 2)
RegisterSignal(wielder, COMSIG_MOVABLE_MOVED, .proc/on_moved)
RegisterSignal(wielder, COMSIG_STEP_ON_BLOOD, .proc/on_step_blood)
/**
@@ -147,7 +171,7 @@
oldLocFP.update_appearance()
else if(find_pool_by_blood_state(oldLocTurf))
// No footprints in the tile we left, but there was some other blood pool there. Add exit footprints on it
bloody_shoes[last_blood_state] -= half_our_blood
adjust_bloody_shoes(last_blood_state, half_our_blood)
update_icon()
oldLocFP = new(oldLocTurf)
@@ -167,7 +191,7 @@
// Create new footprints
if(half_our_blood >= BLOOD_FOOTPRINTS_MIN)
bloody_shoes[last_blood_state] -= half_our_blood
adjust_bloody_shoes(last_blood_state, half_our_blood)
update_icon()
var/obj/effect/decal/cleanable/blood/footprints/FP = new(get_turf(parent_atom))
@@ -213,8 +237,7 @@
if(!(clean_types & CLEAN_TYPE_BLOOD) || last_blood_state == BLOOD_STATE_NOT_BLOODY)
return NONE
bloody_shoes = list(BLOOD_STATE_HUMAN = 0, BLOOD_STATE_XENO = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0)
last_blood_state = BLOOD_STATE_NOT_BLOODY
reset_bloody_shoes()
update_icon()
return COMPONENT_CLEANED
@@ -235,7 +258,6 @@
bloody_feet = mutable_appearance('icons/effects/blood.dmi', "shoeblood", SHOES_LAYER)
RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/on_clean)
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_moved)
RegisterSignal(parent, COMSIG_STEP_ON_BLOOD, .proc/on_step_blood)
RegisterSignal(parent, COMSIG_CARBON_UNEQUIP_SHOECOVER, .proc/unequip_shoecover)
RegisterSignal(parent, COMSIG_CARBON_EQUIP_SHOECOVER, .proc/equip_shoecover)

View File

@@ -114,7 +114,6 @@
return
var/atom/movable/movable_parent = parent
movable_parent.inertia_moving = FALSE
movable_parent.setDir(old_dir)
if(movable_parent.Process_Spacemove(drifting_loop.direction, continuous_move = TRUE))
glide_to_halt(visual_delay)
@@ -137,7 +136,7 @@
if(!isturf(movable_parent.loc))
qdel(src)
return
if(movable_parent.inertia_moving) //This'll be handled elsewhere
if(movable_parent.inertia_moving)
return
if(!movable_parent.Process_Spacemove(drifting_loop.direction, continuous_move = TRUE))
return

View File

@@ -111,7 +111,6 @@
. = ..()
if(directional)
RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, .proc/on_parent_dir_change)
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_parent_moved)
RegisterSignal(parent, COMSIG_ATOM_UPDATE_LIGHT_RANGE, .proc/set_range)
RegisterSignal(parent, COMSIG_ATOM_UPDATE_LIGHT_POWER, .proc/set_power)
RegisterSignal(parent, COMSIG_ATOM_UPDATE_LIGHT_COLOR, .proc/set_color)
@@ -119,6 +118,7 @@
RegisterSignal(parent, COMSIG_ATOM_UPDATE_LIGHT_FLAGS, .proc/on_light_flags_change)
RegisterSignal(parent, COMSIG_ATOM_USED_IN_CRAFT, .proc/on_parent_crafted)
RegisterSignal(parent, COMSIG_LIGHT_EATER_QUEUE, .proc/on_light_eater)
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_parent_moved)
var/atom/movable/movable_parent = parent
if(movable_parent.light_flags & LIGHT_ATTACHED)
overlay_lighting_flags |= LIGHTING_ATTACHED
@@ -245,8 +245,9 @@
return
if(new_holder != parent && new_holder != parent_attached_to)
RegisterSignal(new_holder, COMSIG_PARENT_QDELETING, .proc/on_holder_qdel)
RegisterSignal(new_holder, COMSIG_MOVABLE_MOVED, .proc/on_holder_moved)
RegisterSignal(new_holder, COMSIG_LIGHT_EATER_QUEUE, .proc/on_light_eater)
if(overlay_lighting_flags & LIGHTING_ON)
RegisterSignal(new_holder, COMSIG_MOVABLE_MOVED, .proc/on_holder_moved)
if(directional)
RegisterSignal(new_holder, COMSIG_ATOM_DIR_CHANGE, .proc/on_holder_dir_change)
set_direction(new_holder.dir)
@@ -423,6 +424,8 @@
cast_directional_light()
add_dynamic_lumi()
overlay_lighting_flags |= LIGHTING_ON
if(current_holder && current_holder != parent && current_holder != parent_attached_to)
RegisterSignal(current_holder, COMSIG_MOVABLE_MOVED, .proc/on_holder_moved)
get_new_turfs()
@@ -433,6 +436,8 @@
if(current_holder)
remove_dynamic_lumi()
overlay_lighting_flags &= ~LIGHTING_ON
if(current_holder)
UnregisterSignal(current_holder, COMSIG_MOVABLE_MOVED)
clean_old_turfs()

View File

@@ -4,45 +4,66 @@
/datum/component/storage
dupe_mode = COMPONENT_DUPE_UNIQUE
var/datum/component/storage/concrete/master //If not null, all actions act on master and this is just an access point.
///If not null, all actions act on master and this is just an access point.
var/datum/component/storage/concrete/master
var/list/can_hold //if this is set, only items, and their children, will fit
var/list/cant_hold //if this is set, items, and their children, won't fit
var/list/exception_hold //if set, these items will be the exception to the max size of object that can fit.
///if this is set, only items, and their children, will fit
var/list/can_hold
///if this is set, items, and their children, won't fit
var/list/cant_hold
///if set, these items will be the exception to the max size of object that can fit.
var/list/exception_hold
/// If set can only contain stuff with this single trait present.
var/list/can_hold_trait
var/can_hold_description
var/list/mob/is_using //lazy list of mobs looking at the contents of this storage.
///lazy list of mobs looking at the contents of this storage.
var/list/mob/is_using
var/locked = FALSE //when locked nothing can see inside or use it.
///when locked nothing can see inside or use it.
var/locked = FALSE
var/max_w_class = WEIGHT_CLASS_SMALL //max size of objects that will fit.
var/max_combined_w_class = 14 //max combined sizes of objects that will fit.
var/max_items = 7 //max number of objects that will fit.
///max size of objects that will fit.
var/max_w_class = WEIGHT_CLASS_SMALL
///max combined sizes of objects that will fit.
var/max_combined_w_class = 14
///max number of objects that will fit.
var/max_items = 7
var/emp_shielded = FALSE
var/silent = FALSE //whether this makes a message when things are put in.
var/click_gather = FALSE //whether this can be clicked on items to pick it up rather than the other way around.
var/rustle_sound = TRUE //play rustle sound on interact.
var/allow_quick_empty = FALSE //allow empty verb which allows dumping on the floor of everything inside quickly.
var/allow_quick_gather = FALSE //allow toggle mob verb which toggles collecting all items from a tile.
///whether this makes a message when things are put in.
var/silent = FALSE
///whether this can be clicked on items to pick it up rather than the other way around.
var/click_gather = FALSE
///play rustle sound on interact.
var/rustle_sound = TRUE
///allow empty verb which allows dumping on the floor of everything inside quickly.
var/allow_quick_empty = FALSE
///allow toggle mob verb which toggles collecting all items from a tile.
var/allow_quick_gather = FALSE
var/collection_mode = COLLECT_EVERYTHING
var/insert_preposition = "in" //you put things "in" a bag, but "on" a tray.
///you put things "in" a bag, but "on" a tray.
var/insert_preposition = "in"
var/display_numerical_stacking = FALSE //stack things of the same type and show as a single object with a number.
///stack things of the same type and show as a single object with a number.
var/display_numerical_stacking = FALSE
var/atom/movable/screen/storage/boxes //storage display object
var/atom/movable/screen/close/closer //close button object
///storage display object
var/atom/movable/screen/storage/boxes
///close button object
var/atom/movable/screen/close/closer
var/allow_big_nesting = FALSE //allow storage objects of the same or greater size.
///allow storage objects of the same or greater size.
var/allow_big_nesting = FALSE
var/attack_hand_interact = TRUE //interact on attack hand.
var/quickdraw = FALSE //altclick interact
///interact on attack hand.
var/attack_hand_interact = TRUE
///altclick interact
var/quickdraw = FALSE
var/datum/action/item_action/storage_gather_mode/modeswitch_action
@@ -420,6 +441,9 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches)
M.client.screen |= closer
M.client.screen |= real_location.contents
M.set_active_storage(src)
if(ismovable(real_location))
var/atom/movable/movable_loc = real_location
movable_loc.become_active_storage(src)
LAZYOR(is_using, M)
RegisterSignal(M, COMSIG_PARENT_QDELETING, .proc/mob_deleted)
return TRUE
@@ -437,6 +461,10 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches)
if(!M.client)
return TRUE
var/atom/real_location = real_location()
if(!length(is_using) && ismovable(real_location))
var/atom/movable/movable_loc = real_location
movable_loc.lose_active_storage(src)
M.client.screen -= boxes
M.client.screen -= closer
M.client.screen -= real_location.contents

View File

@@ -127,8 +127,7 @@
decrease = max(0, decrease)
if((is_wet() & TURF_WET_ICE) && t > T0C) //Ice melts into water!
for(var/obj/O in T.contents)
if(O.obj_flags & FROZEN)
O.make_unfrozen()
O.make_unfrozen()
add_wet(TURF_WET_WATER, max_time_left())
dry(null, TURF_WET_ICE)
dry(null, ALL, FALSE, decrease)

View File

@@ -32,9 +32,9 @@
/// Deactivates the functionality defines by the element on the given datum
/datum/element/proc/Detach(datum/source, ...)
SIGNAL_HANDLER
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(source, COMSIG_ELEMENT_DETACH, src)
SHOULD_CALL_PARENT(TRUE)
UnregisterSignal(source, COMSIG_PARENT_QDELETING)
/datum/element/Destroy(force)

View File

@@ -22,6 +22,7 @@
/datum/element/atmos_sensitive/Detach(datum/source)
var/atom/us = source
us.RemoveElement(/datum/element/connect_loc, pass_on)
UnregisterSignal(source, COMSIG_MOVABLE_MOVED)
if(us.flags_1 & ATMOS_IS_PROCESSING_1)
us.atmos_end()
SSair.atom_process -= us

View File

@@ -30,6 +30,7 @@
RegisterSignal(target, COMSIG_ITEM_EQUIPPED, .proc/on_equipped)
/datum/element/chewable/Detach(datum/source, force)
. = ..()
processing -= source
UnregisterSignal(source, list(COMSIG_ITEM_DROPPED, COMSIG_ITEM_EQUIPPED))

View File

@@ -87,7 +87,7 @@
if(steps % 2)
return
if(steps != 0 && !source.has_gravity(turf)) // don't need to step as often when you hop around
if(steps != 0 && !source.has_gravity()) // don't need to step as often when you hop around
return
return turf
@@ -117,10 +117,10 @@
return
playsound(source_loc, pick(footstep_sounds[turf_footstep][1]), footstep_sounds[turf_footstep][2] * volume, TRUE, footstep_sounds[turf_footstep][3] + e_range, falloff_distance = 1, vary = sound_vary)
/datum/element/footstep/proc/play_humanstep(mob/living/carbon/human/source, atom/oldloc, direction)
/datum/element/footstep/proc/play_humanstep(mob/living/carbon/human/source, atom/oldloc, direction, forced, list/old_locs, momentum_change)
SIGNAL_HANDLER
if (SHOULD_DISABLE_FOOTSTEPS(source))
if (SHOULD_DISABLE_FOOTSTEPS(source) || !momentum_change)
return
var/volume_multiplier = 1
@@ -134,21 +134,31 @@
if(!source_loc)
return
play_fov_effect(source, 5, "footstep", direction, ignore_self = TRUE)
//cache for sanic speed (lists are references anyways)
var/static/list/footstep_sounds = GLOB.footstep
///list returned by playsound() filled by client mobs who heard the footstep. given to play_fov_effect()
var/list/heard_clients
if ((source.wear_suit?.body_parts_covered | source.w_uniform?.body_parts_covered | source.shoes?.body_parts_covered) & FEET)
// we are wearing shoes
playsound(source_loc, pick(GLOB.footstep[source_loc.footstep][1]),
GLOB.footstep[source_loc.footstep][2] * volume * volume_multiplier,
heard_clients = playsound(source_loc, pick(footstep_sounds[source_loc.footstep][1]),
footstep_sounds[source_loc.footstep][2] * volume * volume_multiplier,
TRUE,
GLOB.footstep[source_loc.footstep][3] + e_range + range_adjustment, falloff_distance = 1, vary = sound_vary)
footstep_sounds[source_loc.footstep][3] + e_range + range_adjustment, falloff_distance = 1, vary = sound_vary)
else
if(source.dna.species.special_step_sounds)
playsound(source_loc, pick(source.dna.species.special_step_sounds), 50, TRUE, falloff_distance = 1, vary = sound_vary)
heard_clients = playsound(source_loc, pick(source.dna.species.special_step_sounds), 50, TRUE, falloff_distance = 1, vary = sound_vary)
else
playsound(source_loc, pick(GLOB.barefootstep[source_loc.barefootstep][1]),
GLOB.barefootstep[source_loc.barefootstep][2] * volume * volume_multiplier,
var/static/list/bare_footstep_sounds = GLOB.barefootstep
heard_clients = playsound(source_loc, pick(bare_footstep_sounds[source_loc.barefootstep][1]),
bare_footstep_sounds[source_loc.barefootstep][2] * volume * volume_multiplier,
TRUE,
GLOB.barefootstep[source_loc.barefootstep][3] + e_range + range_adjustment, falloff_distance = 1, vary = sound_vary)
bare_footstep_sounds[source_loc.barefootstep][3] + e_range + range_adjustment, falloff_distance = 1, vary = sound_vary)
if(heard_clients)
play_fov_effect(source, 5, "footstep", direction, ignore_self = TRUE, override_list = heard_clients)
///Prepares a footstep for machine walking

View File

@@ -1,16 +1,18 @@
/datum/element/forced_gravity
element_flags = ELEMENT_BESPOKE
id_arg_index = 2
///the level of gravity we force unto our target
var/gravity
var/ignore_space
///whether we will override the turf if it forces no gravity
var/ignore_turf_gravity
/datum/element/forced_gravity/Attach(datum/target, gravity=1, ignore_space=FALSE)
/datum/element/forced_gravity/Attach(datum/target, gravity=1, ignore_turf_gravity = FALSE)
. = ..()
if(!isatom(target))
return ELEMENT_INCOMPATIBLE
src.gravity = gravity
src.ignore_space = ignore_space
src.ignore_turf_gravity = ignore_turf_gravity
RegisterSignal(target, COMSIG_ATOM_HAS_GRAVITY, .proc/gravity_check)
if(isturf(target))
@@ -24,10 +26,12 @@
/datum/element/forced_gravity/proc/gravity_check(datum/source, turf/location, list/gravs)
SIGNAL_HANDLER
if(!ignore_space && isspaceturf(location))
return
if(!ignore_turf_gravity && location.force_no_gravity)
return FALSE
gravs += gravity
return TRUE
/datum/element/forced_gravity/proc/turf_gravity_check(datum/source, atom/checker, list/gravs)
SIGNAL_HANDLER

View File

@@ -0,0 +1,47 @@
///simple element to handle frozen obj's
/datum/element/frozen
element_flags = ELEMENT_DETACH
/datum/element/frozen/Attach(datum/target)
. = ..()
if(!isobj(target))
return ELEMENT_INCOMPATIBLE
var/obj/target_obj = target
if(target_obj.obj_flags & FREEZE_PROOF)
return ELEMENT_INCOMPATIBLE
target_obj.name = "frozen [target_obj.name]"
target_obj.add_atom_colour(GLOB.freon_color_matrix, TEMPORARY_COLOUR_PRIORITY)
target_obj.alpha -= 25
RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/on_moved)
RegisterSignal(target, COMSIG_MOVABLE_POST_THROW, .proc/shatter_on_throw)
RegisterSignal(target, COMSIG_OBJ_UNFREEZE, .proc/on_unfrozen)
/datum/element/frozen/Detach(datum/source, ...)
var/obj/obj_source = source
obj_source.name = replacetext(obj_source.name, "frozen ", "")
obj_source.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, GLOB.freon_color_matrix)
obj_source.alpha += 25
. = ..()
/datum/element/frozen/proc/on_unfrozen(datum/source)
SIGNAL_HANDLER
Detach(source)
/datum/element/frozen/proc/shatter_on_throw(datum/target)
SIGNAL_HANDLER
var/obj/obj_target = target
obj_target.visible_message(span_danger("[obj_target] shatters into a million pieces!"))
qdel(obj_target)
/datum/element/frozen/proc/on_moved(datum/target)
SIGNAL_HANDLER
var/obj/obj_target = target
if(!isopenturf(obj_target.loc))
return
var/turf/open/turf_loc = obj_target.loc
if(turf_loc.air?.temperature >= T0C)//unfreezes target
Detach(target)

View File

@@ -67,8 +67,7 @@
/datum/element/light_eater/proc/table_buffet(atom/commisary, datum/devourer)
. = list()
SEND_SIGNAL(commisary, COMSIG_LIGHT_EATER_QUEUE, ., devourer)
for(var/nom in commisary.light_sources)
var/datum/light_source/morsel = nom
for(var/datum/light_source/morsel as anything in commisary.light_sources)
.[morsel.source_atom] = TRUE
/**

View File

@@ -28,6 +28,7 @@
processing |= target
/datum/element/obj_regen/Detach(obj/target)
. = ..()
UnregisterSignal(target, COMSIG_ATOM_TAKE_DAMAGE)
processing -= target
if(!length(processing))

View File

@@ -403,7 +403,8 @@ GLOBAL_LIST_EMPTY(teleportlocs)
/area/Entered(atom/movable/arrived, area/old_area)
set waitfor = FALSE
SEND_SIGNAL(src, COMSIG_AREA_ENTERED, arrived, old_area)
if(!LAZYACCESS(arrived.important_recursive_contents, RECURSIVE_CONTENTS_AREA_SENSITIVE))
if(!arrived.important_recursive_contents?[RECURSIVE_CONTENTS_AREA_SENSITIVE])
return
for(var/atom/movable/recipient as anything in arrived.important_recursive_contents[RECURSIVE_CONTENTS_AREA_SENSITIVE])
SEND_SIGNAL(recipient, COMSIG_ENTER_AREA, src)
@@ -419,7 +420,18 @@ GLOBAL_LIST_EMPTY(teleportlocs)
if(L.client?.prefs.toggles & SOUND_SHIP_AMBIENCE)
SEND_SOUND(L, sound('sound/ambience/shipambience.ogg', repeat = 1, wait = 0, volume = 35, channel = CHANNEL_BUZZ))
/**
* Called when an atom exits an area
*
* Sends signals COMSIG_AREA_EXITED and COMSIG_EXIT_AREA (to a list of atoms)
*/
/area/Exited(atom/movable/gone, direction)
SEND_SIGNAL(src, COMSIG_AREA_EXITED, gone, direction)
if(!gone.important_recursive_contents?[RECURSIVE_CONTENTS_AREA_SENSITIVE])
return
for(var/atom/movable/recipient as anything in gone.important_recursive_contents[RECURSIVE_CONTENTS_AREA_SENSITIVE])
SEND_SIGNAL(recipient, COMSIG_EXIT_AREA, src)
///Divides total beauty in the room by roomsize to allow us to get an average beauty per tile.
/area/proc/update_beauty()
@@ -431,20 +443,6 @@ GLOBAL_LIST_EMPTY(teleportlocs)
return FALSE //Too big
beauty = totalbeauty / areasize
/**
* Called when an atom exits an area
*
* Sends signals COMSIG_AREA_EXITED and COMSIG_EXIT_AREA (to a list of atoms)
*/
/area/Exited(atom/movable/gone, direction)
SEND_SIGNAL(src, COMSIG_AREA_EXITED, gone, direction)
if(!LAZYACCESS(gone.important_recursive_contents, RECURSIVE_CONTENTS_AREA_SENSITIVE))
return
for(var/atom/movable/recipient as anything in gone.important_recursive_contents[RECURSIVE_CONTENTS_AREA_SENSITIVE])
SEND_SIGNAL(recipient, COMSIG_EXIT_AREA, src)
/**
* Setup an area (with the given name)
*

View File

@@ -1781,7 +1781,9 @@
* Returns true if this atom has gravity for the passed in turf
*
* Sends signals [COMSIG_ATOM_HAS_GRAVITY] and [COMSIG_TURF_HAS_GRAVITY], both can force gravity with
* the forced gravity var
* the forced gravity var.
*
* micro-optimized to hell because this proc is very hot, being called several times per movement every movement.
*
* Gravity situations:
* * No gravity if you're not in a turf
@@ -1792,39 +1794,28 @@
* * otherwise no gravity
*/
/atom/proc/has_gravity(turf/gravity_turf)
if(!gravity_turf || !isturf(gravity_turf))
if(!isturf(gravity_turf))
gravity_turf = get_turf(src)
if(!gravity_turf)
return 0
var/list/forced_gravity = list()
SEND_SIGNAL(src, COMSIG_ATOM_HAS_GRAVITY, gravity_turf, forced_gravity)
if(!forced_gravity.len)
SEND_SIGNAL(gravity_turf, COMSIG_TURF_HAS_GRAVITY, src, forced_gravity)
if(forced_gravity.len)
var/max_grav = forced_gravity[1]
for(var/i in forced_gravity)
max_grav = max(max_grav, i)
return max_grav
if(isspaceturf(gravity_turf)) // Turf never has gravity
return 0
if(istype(gravity_turf, /turf/open/openspace)) //openspace in a space area doesn't get gravity
if(istype(get_area(gravity_turf), /area/space))
if(!gravity_turf)//no gravity in nullspace
return 0
var/area/turf_area = get_area(gravity_turf)
if(turf_area.has_gravity) // Areas which always has gravity
return turf_area.has_gravity
else
// There's a gravity generator on our z level
if(GLOB.gravity_generators["[gravity_turf.z]"])
var/max_grav = 0
for(var/obj/machinery/gravity_generator/main/main_grav_gen as anything in GLOB.gravity_generators["[gravity_turf.z]"])
max_grav = max(main_grav_gen.setting,max_grav)
return max_grav
return SSmapping.level_trait(gravity_turf.z, ZTRAIT_GRAVITY)
//the list isnt created every time as this proc is very hot, its only accessed if anything is actually listening to the signal too
var/static/list/forced_gravity = list()
if(SEND_SIGNAL(src, COMSIG_ATOM_HAS_GRAVITY, gravity_turf, forced_gravity))
if(!length(forced_gravity))
SEND_SIGNAL(gravity_turf, COMSIG_TURF_HAS_GRAVITY, src, forced_gravity)
var/max_grav = 0
for(var/i in forced_gravity)//our gravity is the strongest return forced gravity we get
max_grav = max(max_grav, i)
forced_gravity.Cut()
//cut so we can reuse the list, this is ok since forced gravity movers are exceedingly rare compared to all other movement
return max_grav
var/area/turf_area = gravity_turf.loc
return !gravity_turf.force_no_gravity && (SSmapping.gravity_by_z_level["[gravity_turf.z]"] || turf_area.has_gravity)
/**
* Causes effects when the atom gets hit by a rust effect from heretics

View File

@@ -3,8 +3,6 @@
glide_size = 8
appearance_flags = TILE_BOUND|PIXEL_SCALE|LONG_GLIDE
///how many times a this movable had movement procs called on it since Moved() was last called
var/move_stacks = 0
var/last_move = null
var/anchored = FALSE
var/move_resist = MOVE_RESIST_DEFAULT
@@ -36,8 +34,10 @@
var/pass_flags = NONE
/// If false makes [CanPass][/atom/proc/CanPass] call [CanPassThrough][/atom/movable/proc/CanPassThrough] on this type instead of using default behaviour
var/generic_canpass = TRUE
var/moving_diagonally = 0 //0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move
var/atom/movable/moving_from_pull //attempt to resume grab after moving instead of before.
///0: not doing a diagonal move. 1 and 2: doing the first/second step of the diagonal move
var/moving_diagonally = 0
///attempt to resume grab after moving instead of before.
var/atom/movable/moving_from_pull
///Holds information about any movement loops currently running/waiting to run on the movable. Lazy, will be null if nothing's going on
var/datum/movement_packet/move_packet
var/datum/forced_movement/force_moving = null //handled soley by forced_movement.dm
@@ -441,8 +441,7 @@
SEND_SIGNAL(src, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, target)
glide_size = target
for(var/m in buckled_mobs)
var/mob/buckled_mob = m
for(var/mob/buckled_mob as anything in buckled_mobs)
buckled_mob.set_glide_size(target)
/**
@@ -452,9 +451,9 @@
*/
/atom/movable/proc/abstract_move(atom/new_loc)
var/atom/old_loc = loc
move_stacks++
var/direction = get_dir(old_loc, new_loc)
loc = new_loc
Moved(old_loc)
Moved(old_loc, direction)
////////////////////////////////////////
// Here's where we rewrite how byond handles movement except slightly different
@@ -468,7 +467,7 @@
if(!direction)
direction = get_dir(src, newloc)
if(set_dir_on_move)
if(set_dir_on_move && dir != direction)
setDir(direction)
var/is_multi_tile_object = bound_width > 32 || bound_height > 32
@@ -508,7 +507,6 @@
var/atom/oldloc = loc
var/area/oldarea = get_area(oldloc)
var/area/newarea = get_area(newloc)
move_stacks++
loc = newloc
@@ -543,7 +541,7 @@
return FALSE
var/atom/oldloc = loc
//Early override for some cases like diagonal movement
if(glide_size_override)
if(glide_size_override && glide_size != glide_size_override)
set_glide_size(glide_size_override)
if(loc != newloc)
@@ -597,10 +595,6 @@
if(moving_diagonally == SECOND_DIAG_STEP)
if(!. && set_dir_on_move)
setDir(first_step_dir)
else if (!inertia_moving)
newtonian_move(direct)
if(client_mobs_in_contents) // We're done moving, update our parallax now
update_parallax_contents()
moving_diagonally = 0
return
@@ -631,12 +625,12 @@
//glide_size strangely enough can change mid movement animation and update correctly while the animation is playing
//This means that if you don't override it late like this, it will just be set back by the movement update that's called when you move turfs.
if(glide_size_override)
if(glide_size_override && glide_size != glide_size_override)
set_glide_size(glide_size_override)
last_move = direct
if(set_dir_on_move)
if(set_dir_on_move && dir != direct)
setDir(direct)
if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct, glide_size_override)) //movement failed due to buckled mob(s)
. = FALSE
@@ -661,11 +655,12 @@
* * movement_dir is the direction the movement took place. Can be NONE if it was some sort of teleport.
* * The forced flag indicates whether this was a forced move, which skips many checks of regular movement.
* * The old_locs is an optional argument, in case the moved movable was present in multiple locations before the movement.
* * momentum_change represents whether this movement is due to a "new" force if TRUE or an already "existing" force if FALSE
**/
/atom/movable/proc/Moved(atom/old_loc, movement_dir, forced = FALSE, list/old_locs)
/atom/movable/proc/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
SHOULD_CALL_PARENT(TRUE)
if (!inertia_moving)
if (!inertia_moving && momentum_change)
newtonian_move(movement_dir)
// If we ain't moving diagonally right now, update our parallax
// We don't do this all the time because diag movements should trigger one call to this, not two
@@ -673,14 +668,12 @@
if (!moving_diagonally && client_mobs_in_contents)
update_parallax_contents()
move_stacks--
if(move_stacks > 0) //we want only the first Moved() call in the stack to send this signal, all the other ones have an incorrect old_loc
return
if(move_stacks < 0)
stack_trace("move_stacks is negative in Moved()!")
move_stacks = 0 //setting it to 0 so that we dont get every movable with negative move_stacks runtiming on every movement
SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, old_loc, movement_dir, forced, old_locs, momentum_change)
SEND_SIGNAL(src, COMSIG_MOVABLE_MOVED, old_loc, movement_dir, forced, old_locs)
if(old_loc)
SEND_SIGNAL(old_loc, COMSIG_ATOM_ABSTRACT_EXITED, src, movement_dir)
if(loc)
SEND_SIGNAL(loc, COMSIG_ATOM_ABSTRACT_ENTERED, src, old_loc, old_locs)
var/turf/old_turf = get_turf(old_loc)
var/turf/new_turf = get_turf(src)
@@ -843,7 +836,6 @@
///allows this movable to know when it has "entered" another area no matter how many movable atoms its stuffed into, uses important_recursive_contents
/atom/movable/proc/become_area_sensitive(trait_source = TRAIT_GENERIC)
if(!HAS_TRAIT(src, TRAIT_AREA_SENSITIVE))
//RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_AREA_SENSITIVE), .proc/on_area_sensitive_trait_loss)
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
LAZYADDASSOCLIST(location.important_recursive_contents, RECURSIVE_CONTENTS_AREA_SENSITIVE, src)
ADD_TRAIT(src, TRAIT_AREA_SENSITIVE, trait_source)
@@ -887,6 +879,24 @@
ASSOC_UNSETEMPTY(recursive_contents, RECURSIVE_CONTENTS_CLIENT_MOBS)
UNSETEMPTY(movable_loc.important_recursive_contents)
///called when this movable becomes the parent of a storage component that is currently being viewed by a player. uses important_recursive_contents
/atom/movable/proc/become_active_storage(datum/component/storage/component_source)
if(!HAS_TRAIT(src, TRAIT_ACTIVE_STORAGE))
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
LAZYADDASSOCLIST(location.important_recursive_contents, RECURSIVE_CONTENTS_ACTIVE_STORAGE, src)
ADD_TRAIT(src, TRAIT_ACTIVE_STORAGE, component_source)
///called when this movable's storage component is no longer viewed by any players, unsets important_recursive_contents
/atom/movable/proc/lose_active_storage(datum/component/storage/component_source)
if(!HAS_TRAIT(src, TRAIT_ACTIVE_STORAGE))
return
REMOVE_TRAIT(src, TRAIT_ACTIVE_STORAGE, component_source)
if(HAS_TRAIT(src, TRAIT_ACTIVE_STORAGE))
return
for(var/atom/movable/location as anything in get_nested_locs(src) + src)
LAZYREMOVEASSOC(location.important_recursive_contents, RECURSIVE_CONTENTS_ACTIVE_STORAGE, src)
///Sets the anchored var and returns if it was sucessfully changed or not.
/atom/movable/proc/set_anchored(anchorvalue)
SHOULD_CALL_PARENT(TRUE)
@@ -917,12 +927,13 @@
/atom/movable/proc/doMove(atom/destination)
. = FALSE
move_stacks++
var/atom/oldloc = loc
var/is_multi_tile = bound_width > world.icon_size || bound_height > world.icon_size
if(destination)
///zMove already handles whether a pull from another movable should be broken.
if(pulledby && !currently_z_moving)
pulledby.stop_pulling()
var/same_loc = oldloc == destination
var/area/old_area = get_area(oldloc)
var/area/destarea = get_area(destination)
@@ -933,23 +944,49 @@
loc = destination
if(!same_loc)
if(oldloc)
oldloc.Exited(src, movement_dir)
if(is_multi_tile && isturf(destination))
var/list/new_locs = block(
destination,
locate(
min(world.maxx, destination.x + ROUND_UP(bound_width / 32)),
min(world.maxy, destination.y + ROUND_UP(bound_height / 32)),
destination.z
)
)
if(old_area && old_area != destarea)
old_area.Exited(src, movement_dir)
destination.Entered(src, oldloc)
if(destarea && old_area != destarea)
destarea.Entered(src, old_area)
for(var/atom/left_loc as anything in locs - new_locs)
left_loc.Exited(src, movement_dir)
for(var/atom/entering_loc as anything in new_locs - locs)
entering_loc.Entered(src, movement_dir)
if(old_area && old_area != destarea)
destarea.Entered(src, movement_dir)
else
if(oldloc)
oldloc.Exited(src, movement_dir)
if(old_area && old_area != destarea)
old_area.Exited(src, movement_dir)
destination.Entered(src, oldloc)
if(destarea && old_area != destarea)
destarea.Entered(src, old_area)
. = TRUE
//If no destination, move the atom into nullspace (don't do this unless you know what you're doing)
else
. = TRUE
loc = null
if (oldloc)
loc = null
var/area/old_area = get_area(oldloc)
oldloc.Exited(src, NONE)
if(is_multi_tile && isturf(oldloc))
for(var/atom/old_loc as anything in locs)
old_loc.Exited(src, NONE)
else
oldloc.Exited(src, NONE)
if(old_area)
old_area.Exited(src, NONE)
@@ -975,7 +1012,7 @@
* Called whenever an object moves and by mobs when they attempt to move themselves through space
* And when an object or action applies a force on src, see [newtonian_move][/atom/movable/proc/newtonian_move]
*
* Return 0 to have src start/keep drifting in a no-grav area and 1 to stop/not start drifting
* Return FALSE to have src start/keep drifting in a no-grav area and TRUE to stop/not start drifting
*
* Mobs should return 1 if they should be able to move of their own volition, see [/client/proc/Move]
*
@@ -984,10 +1021,10 @@
* * continuous_move - If this check is coming from something in the context of already drifting
*/
/atom/movable/proc/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
if(SEND_SIGNAL(src, COMSIG_MOVABLE_SPACEMOVE, movement_dir, continuous_move) & COMSIG_MOVABLE_STOP_SPACEMOVE)
if(has_gravity())
return TRUE
if(has_gravity(src))
if(SEND_SIGNAL(src, COMSIG_MOVABLE_SPACEMOVE, movement_dir, continuous_move) & COMSIG_MOVABLE_STOP_SPACEMOVE)
return TRUE
if(pulledby && (pulledby.pulledby != src || moving_from_pull))
@@ -1393,7 +1430,6 @@
log_admin("[key_name(usr)] has added deadchat control to [src]")
message_admins(span_notice("[key_name(usr)] has added deadchat control to [src]"))
/**
* A wrapper for setDir that should only be able to fail by living mobs.
*

View File

@@ -458,7 +458,7 @@
continue
if(!(M in rangers))
rangers[M] = TRUE
M.playsound_local(get_turf(M), null, volume, channel = CHANNEL_JUKEBOX, S = song_played, use_reverb = FALSE)
M.playsound_local(get_turf(M), null, volume, channel = CHANNEL_JUKEBOX, sound_to_use = song_played, use_reverb = FALSE)
for(var/mob/L in rangers)
if(get_dist(src,L) > 10)
rangers -= L

View File

@@ -104,13 +104,6 @@
span_danger("You [damage_verb] [src] with [attacking_item][damage ? "." : ", without leaving a mark!"]"), null, COMBAT_MESSAGE_RANGE)
log_combat(user, src, "attacked", attacking_item)
/obj/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE)
. = ..()
if(obj_flags & FROZEN)
visible_message(span_danger("[src] shatters into a million pieces!"))
qdel(src)
/obj/assume_air(datum/gas_mixture/giver)
if(loc)
return loc.assume_air(giver)

View File

@@ -1,620 +0,0 @@
//Booleans in arguments are confusing, so I made them defines.
#define LOCKED 1
#define UNLOCKED 0
///Collect and command
/datum/lift_master
var/list/lift_platforms
/// Typepath list of what to ignore smashing through, controls all lifts
var/list/ignored_smashthroughs = list(
/obj/machinery/power/supermatter_crystal,
/obj/structure/holosign
)
/datum/lift_master/New(obj/structure/industrial_lift/lift_platform)
Rebuild_lift_plaform(lift_platform)
ignored_smashthroughs = typecacheof(ignored_smashthroughs)
/datum/lift_master/Destroy()
for(var/l in lift_platforms)
var/obj/structure/industrial_lift/lift_platform = l
lift_platform.lift_master_datum = null
lift_platforms = null
return ..()
/datum/lift_master/proc/add_lift_platforms(obj/structure/industrial_lift/new_lift_platform)
if(new_lift_platform in lift_platforms)
return
new_lift_platform.lift_master_datum = src
LAZYADD(lift_platforms, new_lift_platform)
RegisterSignal(new_lift_platform, COMSIG_PARENT_QDELETING, .proc/remove_lift_platforms)
/datum/lift_master/proc/remove_lift_platforms(obj/structure/industrial_lift/old_lift_platform)
SIGNAL_HANDLER
if(!(old_lift_platform in lift_platforms))
return
old_lift_platform.lift_master_datum = null
LAZYREMOVE(lift_platforms, old_lift_platform)
UnregisterSignal(old_lift_platform, COMSIG_PARENT_QDELETING)
///Collect all bordered platforms
/datum/lift_master/proc/Rebuild_lift_plaform(obj/structure/industrial_lift/base_lift_platform)
add_lift_platforms(base_lift_platform)
var/list/possible_expansions = list(base_lift_platform)
while(possible_expansions.len)
for(var/b in possible_expansions)
var/obj/structure/industrial_lift/borderline = b
var/list/result = borderline.lift_platform_expansion(src)
if(length(result))
for(var/p in result)
if(lift_platforms.Find(p))
continue
var/obj/structure/industrial_lift/lift_platform = p
add_lift_platforms(lift_platform)
possible_expansions |= lift_platform
possible_expansions -= borderline
/**
* Moves the lift UP or DOWN, this is what users invoke with their hand.
* This is a SAFE proc, ensuring every part of the lift moves SANELY.
* It also locks controls for the (miniscule) duration of the movement, so the elevator cannot be broken by spamming.
* Arguments:
* going - UP or DOWN directions, where the lift should go. Keep in mind by this point checks of whether it should go up or down have already been done.
* user - Whomever made the lift movement.
*/
/datum/lift_master/proc/MoveLift(going, mob/user)
set_controls(LOCKED)
for(var/p in lift_platforms)
var/obj/structure/industrial_lift/lift_platform = p
lift_platform.travel(going)
set_controls(UNLOCKED)
/**
* Moves the lift, this is what users invoke with their hand.
* This is a SAFE proc, ensuring every part of the lift moves SANELY.
* It also locks controls for the (miniscule) duration of the movement, so the elevator cannot be broken by spamming.
*/
/datum/lift_master/proc/MoveLiftHorizontal(going, z, gliding_amount = 8)
var/max_x = 1
var/max_y = 1
var/min_x = world.maxx
var/min_y = world.maxy
set_controls(LOCKED)
for(var/p in lift_platforms)
var/obj/structure/industrial_lift/lift_platform = p
max_x = max(max_x, lift_platform.x)
max_y = max(max_y, lift_platform.y)
min_x = min(min_x, lift_platform.x)
min_y = min(min_y, lift_platform.y)
//This must be safe way to border tile to tile move of bordered platforms, that excludes platform overlapping.
if( going & WEST )
//Go along the X axis from min to max, from left to right
for(var/x in min_x to max_x)
if( going & NORTH )
//Go along the Y axis from max to min, from up to down
for(var/y in max_y to min_y step -1)
var/obj/structure/industrial_lift/lift_platform = locate(/obj/structure/industrial_lift, locate(x, y, z))
lift_platform?.travel(going, gliding_amount)
else
//Go along the Y axis from min to max, from down to up
for(var/y in min_y to max_y)
var/obj/structure/industrial_lift/lift_platform = locate(/obj/structure/industrial_lift, locate(x, y, z))
lift_platform?.travel(going, gliding_amount)
else
//Go along the X axis from max to min, from right to left
for(var/x in max_x to min_x step -1)
if( going & NORTH )
//Go along the Y axis from max to min, from up to down
for(var/y in max_y to min_y step -1)
var/obj/structure/industrial_lift/lift_platform = locate(/obj/structure/industrial_lift, locate(x, y, z))
lift_platform?.travel(going, gliding_amount)
else
//Go along the Y axis from min to max, from down to up
for(var/y in min_y to max_y)
var/obj/structure/industrial_lift/lift_platform = locate(/obj/structure/industrial_lift, locate(x, y, z))
lift_platform?.travel(going, gliding_amount)
set_controls(UNLOCKED)
///Check destination turfs
/datum/lift_master/proc/Check_lift_move(check_dir)
for(var/l in lift_platforms)
var/obj/structure/industrial_lift/lift_platform = l
var/turf/T = get_step_multiz(lift_platform, check_dir)
if(!T)//the edges of multi-z maps
return FALSE
if(check_dir == UP && !istype(T, /turf/open/openspace)) // We don't want to go through the ceiling!
return FALSE
if(check_dir == DOWN && !istype(get_turf(lift_platform), /turf/open/openspace)) // No going through the floor!
return FALSE
return TRUE
/**
* Sets all lift parts's controls_locked variable. Used to prevent moving mid movement, or cooldowns.
*/
/datum/lift_master/proc/set_controls(state)
for(var/l in lift_platforms)
var/obj/structure/industrial_lift/lift_platform = l
lift_platform.controls_locked = state
GLOBAL_LIST_EMPTY(lifts)
/obj/structure/industrial_lift
name = "lift platform"
desc = "A lightweight lift platform. It moves up and down."
icon = 'icons/obj/smooth_structures/catwalk.dmi'
icon_state = "catwalk-0"
base_icon_state = "catwalk"
density = FALSE
anchored = TRUE
armor = list(MELEE = 50, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, FIRE = 80, ACID = 50)
max_integrity = 50
layer = LATTICE_LAYER //under pipes
plane = FLOOR_PLANE
smoothing_flags = SMOOTH_BITMASK
smoothing_groups = list(SMOOTH_GROUP_INDUSTRIAL_LIFT)
canSmoothWith = list(SMOOTH_GROUP_INDUSTRIAL_LIFT)
obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN
var/id = null //ONLY SET THIS TO ONE OF THE LIFT'S PARTS. THEY'RE CONNECTED! ONLY ONE NEEDS THE SIGNAL!
var/pass_through_floors = FALSE //if true, the elevator works through floors
var/controls_locked = FALSE //if true, the lift cannot be manually moved.
var/list/atom/movable/lift_load //things to move
var/datum/lift_master/lift_master_datum //control from
/obj/structure/industrial_lift/Initialize(mapload)
. = ..()
GLOB.lifts.Add(src)
var/static/list/loc_connections = list(
COMSIG_ATOM_EXITED =.proc/UncrossedRemoveItemFromLift,
COMSIG_ATOM_ENTERED = .proc/AddItemOnLift,
COMSIG_ATOM_INITIALIZED_ON = .proc/AddItemOnLift,
)
AddElement(/datum/element/connect_loc, loc_connections)
RegisterSignal(src, COMSIG_MOVABLE_BUMP, .proc/GracefullyBreak)
if(!lift_master_datum)
lift_master_datum = new(src)
/obj/structure/industrial_lift/proc/UncrossedRemoveItemFromLift(datum/source, atom/movable/gone, direction)
SIGNAL_HANDLER
RemoveItemFromLift(gone)
/obj/structure/industrial_lift/proc/RemoveItemFromLift(atom/movable/potential_rider)
SIGNAL_HANDLER
if(!(potential_rider in lift_load))
return
if(isliving(potential_rider) && HAS_TRAIT(potential_rider, TRAIT_CANNOT_BE_UNBUCKLED))
REMOVE_TRAIT(potential_rider, TRAIT_CANNOT_BE_UNBUCKLED, BUCKLED_TRAIT)
LAZYREMOVE(lift_load, potential_rider)
UnregisterSignal(potential_rider, COMSIG_PARENT_QDELETING)
/obj/structure/industrial_lift/proc/AddItemOnLift(datum/source, atom/movable/AM)
SIGNAL_HANDLER
if(istype(AM, /obj/structure/fluff/tram_rail) || AM.invisibility == INVISIBILITY_ABSTRACT) //prevents the tram from stealing things like landmarks
return
if(AM in lift_load)
return
if(isliving(AM) && !HAS_TRAIT(AM, TRAIT_CANNOT_BE_UNBUCKLED))
ADD_TRAIT(AM, TRAIT_CANNOT_BE_UNBUCKLED, BUCKLED_TRAIT)
LAZYADD(lift_load, AM)
RegisterSignal(AM, COMSIG_PARENT_QDELETING, .proc/RemoveItemFromLift)
/**
* Signal for when the tram runs into a field of which it cannot go through.
* Stops the train's travel fully, sends a message, and destroys the train.
* Arguments:
* bumped_atom - The atom this tram bumped into
*/
/obj/structure/industrial_lift/proc/GracefullyBreak(atom/bumped_atom)
SIGNAL_HANDLER
if(istype(bumped_atom, /obj/machinery/field))
return
bumped_atom.visible_message(span_userdanger("[src] crashes into the field violently!"))
for(var/obj/structure/industrial_lift/tram/tram_part as anything in lift_master_datum.lift_platforms)
tram_part.travel_distance = 0
tram_part.set_travelling(FALSE)
if(prob(15) || locate(/mob/living) in tram_part.lift_load) //always go boom on people on the track
explosion(tram_part, devastation_range = rand(0, 1), heavy_impact_range = 2, light_impact_range = 3) //50% chance of gib
qdel(tram_part)
/obj/structure/industrial_lift/proc/lift_platform_expansion(datum/lift_master/lift_master_datum)
. = list()
for(var/direction in GLOB.cardinals)
var/obj/structure/industrial_lift/neighbor = locate() in get_step(src, direction)
if(!neighbor)
continue
. += neighbor
/obj/structure/industrial_lift/proc/travel(going, gliding_amount = 8)
var/list/things_to_move = LAZYCOPY(lift_load)
var/turf/destination
if(!isturf(going))
destination = get_step_multiz(src, going)
else
destination = going
///handles any special interactions objects could have with the lift/tram, handled on the item itself
SEND_SIGNAL(destination, COMSIG_TURF_INDUSTRIAL_LIFT_ENTER, things_to_move)
if(istype(destination, /turf/closed/wall))
var/turf/closed/wall/C = destination
do_sparks(2, FALSE, C)
C.dismantle_wall(devastated = TRUE)
for(var/mob/M in urange(8, src))
shake_camera(M, 2, 3)
playsound(C, 'sound/effects/meteorimpact.ogg', 100, TRUE)
if(going == DOWN)
for(var/mob/living/crushed in destination.contents)
to_chat(crushed, span_userdanger("You are crushed by [src]!"))
crushed.gib(FALSE,FALSE,FALSE)//the nicest kind of gibbing, keeping everything intact.
else if(going != UP) //can't really crush something upwards
var/atom/throw_target = get_edge_target_turf(src, turn(going, pick(45, -45))) //finds a spot to throw the victim at for daring to be hit by a tram
for(var/obj/structure/victim_structure in destination.contents)
if(QDELETED(victim_structure))
continue
if(!is_type_in_typecache(victim_structure, lift_master_datum.ignored_smashthroughs) && victim_structure.layer >= LOW_OBJ_LAYER)
if(victim_structure.anchored && initial(victim_structure.anchored) == TRUE)
visible_message(span_danger("[src] smashes through [victim_structure]!"))
victim_structure.deconstruct(FALSE)
else
visible_message(span_danger("[src] violently rams [victim_structure] out of the way!"))
victim_structure.anchored = FALSE
victim_structure.take_damage(rand(20, 25))
victim_structure.throw_at(throw_target, 200, 4)
for(var/obj/machinery/victim_machine in destination.contents)
if(QDELETED(victim_machine))
continue
if(is_type_in_typecache(victim_machine, lift_master_datum.ignored_smashthroughs))
continue
if(istype(victim_machine, /obj/machinery/field)) //graceful break handles this scenario
continue
if(victim_machine.layer >= LOW_OBJ_LAYER) //avoids stuff that is probably flush with the ground
playsound(src, 'sound/effects/bang.ogg', 50, TRUE)
visible_message(span_danger("[src] smashes through [victim_machine]!"))
qdel(victim_machine)
for(var/mob/living/collided in destination.contents)
if(is_type_in_typecache(collided, lift_master_datum.ignored_smashthroughs))
continue
to_chat(collided, span_userdanger("[src] collides into you!"))
playsound(src, 'sound/effects/splat.ogg', 50, TRUE)
var/damage = rand(5, 10)
collided.apply_damage(2 * damage, BRUTE, BODY_ZONE_HEAD)
collided.apply_damage(2 * damage, BRUTE, BODY_ZONE_CHEST)
collided.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_L_LEG)
collided.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_R_LEG)
collided.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_L_ARM)
collided.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_R_ARM)
if(QDELETED(collided)) //in case it was a mob that dels on death
continue
var/turf/T = get_turf(src)
T.add_mob_blood(collided)
collided.throw_at()
//if going EAST, will turn to the NORTHEAST or SOUTHEAST and throw the ran over guy away
var/datum/callback/land_slam = new(collided, /mob/living/.proc/tram_slam_land)
collided.throw_at(throw_target, 200, 4, callback = land_slam)
set_glide_size(gliding_amount)
forceMove(destination)
for(var/atom/movable/thing as anything in things_to_move)
thing.set_glide_size(gliding_amount) //matches the glide size of the moving platform to stop them from jittering on it.
thing.forceMove(destination)
/obj/structure/industrial_lift/proc/use(mob/living/user)
if(!isliving(user) || !in_range(src, user) || user.combat_mode)
return
var/list/tool_list = list()
if(lift_master_datum.Check_lift_move(UP))
tool_list["Up"] = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH)
if(lift_master_datum.Check_lift_move(DOWN))
tool_list["Down"] = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH)
if(!length(tool_list))
to_chat(user, span_warning("[src] doesn't seem to able to move anywhere!"))
add_fingerprint(user)
return
if(controls_locked)
to_chat(user, span_warning("[src] has its controls locked! It must already be trying to do something!"))
add_fingerprint(user)
return
var/result = show_radial_menu(user, src, tool_list, custom_check = CALLBACK(src, .proc/check_menu, user, src.loc), require_near = TRUE, tooltips = TRUE)
if(!isliving(user) || !in_range(src, user) || user.combat_mode)
return //nice try
switch(result)
if("Up")
// We have to make sure that they don't do illegal actions by not having their radial menu refresh from someone else moving the lift.
if(!lift_master_datum.Check_lift_move(UP))
to_chat(user, span_warning("[src] doesn't seem to able to move up!"))
add_fingerprint(user)
return
lift_master_datum.MoveLift(UP, user)
show_fluff_message(TRUE, user)
use(user)
if("Down")
if(!lift_master_datum.Check_lift_move(DOWN))
to_chat(user, span_warning("[src] doesn't seem to able to move down!"))
add_fingerprint(user)
return
lift_master_datum.MoveLift(DOWN, user)
show_fluff_message(FALSE, user)
use(user)
if("Cancel")
return
add_fingerprint(user)
/**
* Proc to ensure that the radial menu closes when it should.
* Arguments:
* * user - The person that opened the menu.
* * starting_loc - The location of the lift when the menu was opened, used to prevent the menu from being interacted with after the lift was moved by someone else.
*
* Returns:
* * boolean, FALSE if the menu should be closed, TRUE if the menu is clear to stay opened.
*/
/obj/structure/industrial_lift/proc/check_menu(mob/user, starting_loc)
if(user.incapacitated() || !user.Adjacent(src) || starting_loc != src.loc)
return FALSE
return TRUE
/obj/structure/industrial_lift/attack_hand(mob/user, list/modifiers)
. = ..()
if(.)
return
use(user)
//ai probably shouldn't get to use lifts but they sure are great for admins to crush people with
/obj/structure/industrial_lift/attack_ghost(mob/user)
. = ..()
if(.)
return
if(isAdminGhostAI(user))
use(user)
/obj/structure/industrial_lift/attack_paw(mob/user, list/modifiers)
return use(user)
/obj/structure/industrial_lift/attackby(obj/item/W, mob/user, params)
return use(user)
/obj/structure/industrial_lift/attack_robot(mob/living/silicon/robot/R)
if(R.Adjacent(src))
return use(R)
/**
* Shows a message indicating that the lift has moved up or down.
* Arguments:
* * going_up - Boolean on whether or not we're going up, to adjust the message appropriately.
* * user - The mob that caused the lift to move, for the visible message.
*/
/obj/structure/industrial_lift/proc/show_fluff_message(going_up, mob/user)
if(going_up)
user.visible_message(span_notice("[user] moves the lift upwards."), span_notice("You move the lift upwards."))
else
user.visible_message(span_notice("[user] moves the lift downwards."), span_notice("You move the lift downwards."))
/obj/structure/industrial_lift/Destroy()
GLOB.lifts.Remove(src)
QDEL_NULL(lift_master_datum)
var/list/border_lift_platforms = lift_platform_expansion()
moveToNullspace()
for(var/border_lift in border_lift_platforms)
lift_master_datum = new(border_lift)
return ..()
/obj/structure/industrial_lift/debug
name = "transport platform"
desc = "A lightweight platform. It moves in any direction, except up and down."
color = "#5286b9ff"
/obj/structure/industrial_lift/debug/use(mob/user)
if (!in_range(src, user))
return
//NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST
var/static/list/tool_list = list(
"NORTH" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH),
"NORTHEAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH),
"EAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = EAST),
"SOUTHEAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = EAST),
"SOUTH" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH),
"SOUTHWEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH),
"WEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = WEST),
"NORTHWEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = WEST)
)
var/result = show_radial_menu(user, src, tool_list, custom_check = CALLBACK(src, .proc/check_menu, user, loc), require_near = TRUE, tooltips = FALSE)
if (!in_range(src, user))
return // nice try
switch(result)
if("NORTH")
lift_master_datum.MoveLiftHorizontal(NORTH, z)
use(user)
if("NORTHEAST")
lift_master_datum.MoveLiftHorizontal(NORTHEAST, z)
use(user)
if("EAST")
lift_master_datum.MoveLiftHorizontal(EAST, z)
use(user)
if("SOUTHEAST")
lift_master_datum.MoveLiftHorizontal(SOUTHEAST, z)
use(user)
if("SOUTH")
lift_master_datum.MoveLiftHorizontal(SOUTH, z)
use(user)
if("SOUTHWEST")
lift_master_datum.MoveLiftHorizontal(SOUTHWEST, z)
use(user)
if("WEST")
lift_master_datum.MoveLiftHorizontal(WEST, z)
use(user)
if("NORTHWEST")
lift_master_datum.MoveLiftHorizontal(NORTHWEST, z)
use(user)
if("Cancel")
return
add_fingerprint(user)
/obj/structure/industrial_lift/tram
name = "tram"
desc = "A tram for traversing the station."
icon = 'icons/turf/floors.dmi'
icon_state = "titanium_yellow"
base_icon_state = null
smoothing_flags = NONE
smoothing_groups = null
canSmoothWith = null
//kind of a centerpiece of the station, so pretty tough to destroy
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
/// Set by the tram control console in late initialize
var/travelling = FALSE
var/travel_distance = 0
/// For finding the landmark initially - should be the exact same as the landmark's destination id.
var/initial_id = "middle_part"
var/obj/effect/landmark/tram/from_where
var/travel_direction
GLOBAL_LIST_EMPTY_TYPED(central_trams, /obj/structure/industrial_lift/tram/central)
/obj/structure/industrial_lift/tram/Initialize(mapload)
. = ..()
return INITIALIZE_HINT_LATELOAD
/obj/structure/industrial_lift/tram/central
var/tram_id = "tram_station"
/obj/structure/industrial_lift/tram/central/Initialize(mapload)
. = ..()
if(!SStramprocess.can_fire)
SStramprocess.can_fire = TRUE
GLOB.central_trams += src
/obj/structure/industrial_lift/tram/central/Destroy()
GLOB.central_trams -= src
return ..()
/obj/structure/industrial_lift/tram/LateInitialize()
. = ..()
find_our_location()
/**
* Finds the location of the tram
*
* The initial_id is assumed to the be the landmark the tram is built on in the map
* and where the tram will set itself to be on roundstart.
* The central tram piece goes further into this by actually checking the contents of the turf its on
* for a tram landmark when it docks anywhere. This assures the tram actually knows where it is after docking,
* even in the worst cast scenario.
*/
/obj/structure/industrial_lift/tram/proc/find_our_location()
for(var/obj/effect/landmark/tram/our_location in GLOB.landmarks_list)
if(our_location.destination_id == initial_id)
from_where = our_location
break
/obj/structure/industrial_lift/tram/proc/set_travelling(travelling)
if (src.travelling == travelling)
return
src.travelling = travelling
SEND_SIGNAL(src, COMSIG_TRAM_SET_TRAVELLING, travelling)
/obj/structure/industrial_lift/tram/use(mob/user) //dont click the floor dingus we use computers now
return
/obj/structure/industrial_lift/tram/process(delta_time)
if(!travel_distance)
addtimer(CALLBACK(src, .proc/unlock_controls), 3 SECONDS)
return PROCESS_KILL
else
travel_distance--
lift_master_datum.MoveLiftHorizontal(travel_direction, z, DELAY_TO_GLIDE_SIZE(SStramprocess.wait))
/**
* Handles moving the tram
*
* Tells the individual tram parts where to actually go and has an extra safety check
* incase multiple inputs get through, preventing conflicting directions and the tram
* literally ripping itself apart. The proc handles the first move before the subsystem
* takes over to keep moving it in process()
*/
/obj/structure/industrial_lift/tram/proc/tram_travel(obj/effect/landmark/tram/to_where)
if(to_where == from_where)
return
visible_message(span_notice("[src] has been called to the [to_where]!"))
lift_master_datum.set_controls(LOCKED)
travel_direction = get_dir(from_where, to_where)
travel_distance = get_dist(from_where, to_where)
//first movement is immediate
for(var/obj/structure/industrial_lift/tram/other_tram_part as anything in lift_master_datum.lift_platforms) //only thing everyone needs to know is the new location.
if(other_tram_part.travelling) //wee woo wee woo there was a double action queued. damn multi tile structs
return //we don't care to undo locked controls, though, as that will resolve itself
SEND_SIGNAL(src, COMSIG_TRAM_TRAVEL, from_where, to_where)
other_tram_part.set_travelling(TRUE)
other_tram_part.from_where = to_where
lift_master_datum.MoveLiftHorizontal(travel_direction, z, DELAY_TO_GLIDE_SIZE(SStramprocess.wait))
travel_distance--
START_PROCESSING(SStramprocess, src)
/**
* Handles unlocking the tram controls for use after moving
*
* More safety checks to make sure the tram has actually docked properly
* at a location before users are allowed to interact with the tram console again.
* Tram finds its location at this point before fully unlocking controls to the user.
*/
/obj/structure/industrial_lift/tram/proc/unlock_controls()
visible_message(span_notice("[src]'s controls are now unlocked."))
for(var/obj/structure/industrial_lift/tram/tram_part as anything in lift_master_datum.lift_platforms) //only thing everyone needs to know is the new location.
tram_part.set_travelling(FALSE)
lift_master_datum.set_controls(UNLOCKED)
GLOBAL_LIST_EMPTY(tram_landmarks)
/obj/effect/landmark/tram
name = "tram destination" //the tram buttons will mention this.
icon_state = "tram"
/// The ID of that particular destination.
var/destination_id
/// The ID of the tram that can travel to use
var/tram_id = "tram_station"
/// Icons for the tgui console to list out for what is at this location
var/list/tgui_icons = list()
/obj/effect/landmark/tram/Initialize(mapload)
. = ..()
GLOB.tram_landmarks += src
/obj/effect/landmark/tram/Destroy()
GLOB.tram_landmarks -= src
return ..()
/obj/effect/landmark/tram/left_part
name = "West Wing"
destination_id = "left_part"
tgui_icons = list("Arrivals" = "plane-arrival", "Command" = "bullhorn", "Security" = "gavel")
/obj/effect/landmark/tram/middle_part
name = "Central Wing"
destination_id = "middle_part"
tgui_icons = list("Service" = "cocktail", "Medical" = "plus", "Engineering" = "wrench")
/obj/effect/landmark/tram/right_part
name = "East Wing"
destination_id = "right_part"
tgui_icons = list("Departures" = "plane-departure", "Cargo" = "box", "Science" = "flask")

View File

@@ -23,120 +23,101 @@
)
environment = SOUND_ENVIRONMENT_NONE //Default to none so sounds without overrides dont get reverb
/*! playsound
playsound is a proc used to play a 3D sound in a specific range. This uses SOUND_RANGE + extra_range to determine that.
source - Origin of sound
soundin - Either a file, or a string that can be used to get an SFX
vol - The volume of the sound, excluding falloff and pressure affection.
vary - bool that determines if the sound changes pitch every time it plays
extrarange - modifier for sound range. This gets added on top of SOUND_RANGE
falloff_exponent - Rate of falloff for the audio. Higher means quicker drop to low volume. Should generally be over 1 to indicate a quick dive to 0 rather than a slow dive.
frequency - playback speed of audio
channel - The channel the sound is played at
pressure_affected - Whether or not difference in pressure affects the sound (E.g. if you can hear in space)
ignore_walls - Whether or not the sound can pass through walls.
falloff_distance - Distance at which falloff begins. Sound is at peak volume (in regards to falloff) aslong as it is in this range.
*/
/**
* playsound is a proc used to play a 3D sound in a specific range. This uses SOUND_RANGE + extra_range to determine that.
*
* source - Origin of sound.
* soundin - Either a file, or a string that can be used to get an SFX.
* vol - The volume of the sound, excluding falloff and pressure affection.
* vary - bool that determines if the sound changes pitch every time it plays.
* extrarange - modifier for sound range. This gets added on top of SOUND_RANGE.
* falloff_exponent - Rate of falloff for the audio. Higher means quicker drop to low volume. Should generally be over 1 to indicate a quick dive to 0 rather than a slow dive.
* frequency - playback speed of audio.
* channel - The channel the sound is played at.
* pressure_affected - Whether or not difference in pressure affects the sound (E.g. if you can hear in space).
* ignore_walls - Whether or not the sound can pass through walls.
* falloff_distance - Distance at which falloff begins. Sound is at peak volume (in regards to falloff) aslong as it is in this range.
*/
/proc/playsound(atom/source, soundin, vol as num, vary, extrarange as num, falloff_exponent = SOUND_FALLOFF_EXPONENT, frequency = null, channel = 0, pressure_affected = TRUE, ignore_walls = TRUE, falloff_distance = SOUND_DEFAULT_FALLOFF_DISTANCE, use_reverb = TRUE)
if(isarea(source))
CRASH("playsound(): source is an area")
var/turf/turf_source = get_turf(source)
if (!turf_source)
if (!turf_source || !soundin || !vol)
return
//allocate a channel if necessary now so its the same for everyone
channel = channel || SSsounds.random_available_channel()
// Looping through the player list has the added bonus of working for mobs inside containers
var/sound/S = sound(get_sfx(soundin))
var/maxdistance = SOUND_RANGE + extrarange
var/source_z = turf_source.z
var/list/listeners = SSmobs.clients_by_zlevel[source_z].Copy()
. = list()//output everything that successfully heard the sound
var/turf/above_turf = SSmapping.get_turf_above(turf_source)
var/turf/below_turf = SSmapping.get_turf_below(turf_source)
if(!ignore_walls) //these sounds don't carry through walls
listeners = listeners & hearers(maxdistance,turf_source)
if(ignore_walls)
if(above_turf && istransparentturf(above_turf))
listeners += hearers(maxdistance,above_turf)
if(below_turf && istransparentturf(turf_source))
listeners += hearers(maxdistance,below_turf)
else
if(above_turf && istransparentturf(above_turf))
listeners += SSmobs.clients_by_zlevel[above_turf.z]
if(below_turf && istransparentturf(turf_source))
listeners += SSmobs.clients_by_zlevel[below_turf.z]
for(var/mob/listening_mob as anything in listeners)
if(get_dist(listening_mob, turf_source) <= maxdistance)
listening_mob.playsound_local(turf_source, soundin, vol, vary, frequency, falloff_exponent, channel, pressure_affected, S, maxdistance, falloff_distance, 1, use_reverb)
for(var/mob/listening_mob as anything in SSmobs.dead_players_by_zlevel[source_z])
if(get_dist(listening_mob, turf_source) <= maxdistance)
listening_mob.playsound_local(turf_source, soundin, vol, vary, frequency, falloff_exponent, channel, pressure_affected, S, maxdistance, falloff_distance, 1, use_reverb)
for(var/mob/listening_mob as anything in listeners)
if(get_dist(listening_mob, turf_source) <= maxdistance)
listening_mob.playsound_local(turf_source, soundin, vol, vary, frequency, falloff_exponent, channel, pressure_affected, S, maxdistance, falloff_distance, 1, use_reverb)
. += listening_mob
/*! playsound
else //these sounds don't carry through walls
listeners = get_hearers_in_view(maxdistance, turf_source)
playsound_local is a proc used to play a sound directly on a mob from a specific turf.
This is called by playsound to send sounds to players, in which case it also gets the max_distance of that sound.
if(above_turf && istransparentturf(above_turf))
listeners += get_hearers_in_view(maxdistance, above_turf)
turf_source - Origin of sound
soundin - Either a file, or a string that can be used to get an SFX
vol - The volume of the sound, excluding falloff
vary - bool that determines if the sound changes pitch every time it plays
frequency - playback speed of audio
falloff_exponent - Rate of falloff for the audio. Higher means quicker drop to low volume. Should generally be over 1 to indicate a quick dive to 0 rather than a slow dive.
channel - The channel the sound is played at
pressure_affected - Whether or not difference in pressure affects the sound (E.g. if you can hear in space)
max_distance - The peak distance of the sound, if this is a 3D sound
falloff_distance - Distance at which falloff begins, if this is a 3D sound
distance_multiplier - Can be used to multiply the distance at which the sound is heard
if(below_turf && istransparentturf(turf_source))
listeners += get_hearers_in_view(maxdistance, below_turf)
*/
for(var/mob/listening_mob in listeners | SSmobs.dead_players_by_zlevel[source_z])//observers always hear through walls
if(get_dist(listening_mob, turf_source) <= maxdistance)
listening_mob.playsound_local(turf_source, soundin, vol, vary, frequency, falloff_exponent, channel, pressure_affected, S, maxdistance, falloff_distance, 1, use_reverb)
. += listening_mob
/mob/proc/playsound_local(turf/turf_source, soundin, vol as num, vary, frequency, falloff_exponent = SOUND_FALLOFF_EXPONENT, channel = 0, pressure_affected = TRUE, sound/S, max_distance, falloff_distance = SOUND_DEFAULT_FALLOFF_DISTANCE, distance_multiplier = 1, use_reverb = TRUE)
/mob/proc/playsound_local(turf/turf_source, soundin, vol as num, vary, frequency, falloff_exponent = SOUND_FALLOFF_EXPONENT, channel = 0, pressure_affected = TRUE, sound/sound_to_use, max_distance, falloff_distance = SOUND_DEFAULT_FALLOFF_DISTANCE, distance_multiplier = 1, use_reverb = TRUE)
if(!client || !can_hear())
return
if(!S)
S = sound(get_sfx(soundin))
if(!sound_to_use)
sound_to_use = sound(get_sfx(soundin))
S.wait = 0 //No queue
S.channel = channel || SSsounds.random_available_channel()
S.volume = vol
sound_to_use.wait = 0 //No queue
sound_to_use.channel = channel || SSsounds.random_available_channel()
sound_to_use.volume = vol
if(vary)
if(frequency)
S.frequency = frequency
sound_to_use.frequency = frequency
else
S.frequency = get_rand_frequency()
sound_to_use.frequency = get_rand_frequency()
if(isturf(turf_source))
var/turf/T = get_turf(src)
var/turf/turf_loc = get_turf(src)
//sound volume falloff with distance
var/distance = get_dist(T, turf_source)
distance *= distance_multiplier
var/distance = get_dist(turf_loc, turf_source) * distance_multiplier
if(max_distance) //If theres no max_distance we're not a 3D sound, so no falloff.
S.volume -= (max(distance - falloff_distance, 0) ** (1 / falloff_exponent)) / ((max(max_distance, distance) - falloff_distance) ** (1 / falloff_exponent)) * S.volume
sound_to_use.volume -= (max(distance - falloff_distance, 0) ** (1 / falloff_exponent)) / ((max(max_distance, distance) - falloff_distance) ** (1 / falloff_exponent)) * sound_to_use.volume
//https://www.desmos.com/calculator/sqdfl8ipgf
if(pressure_affected)
//Atmosphere affects sound
var/pressure_factor = 1
var/datum/gas_mixture/hearer_env = T.return_air()
var/datum/gas_mixture/hearer_env = turf_loc.return_air()
var/datum/gas_mixture/source_env = turf_source.return_air()
if(hearer_env && source_env)
@@ -149,35 +130,35 @@ distance_multiplier - Can be used to multiply the distance at which the sound is
if(distance <= 1)
pressure_factor = max(pressure_factor, 0.15) //touching the source of the sound
S.volume *= pressure_factor
sound_to_use.volume *= pressure_factor
//End Atmosphere affecting sound
if(S.volume <= 0)
if(sound_to_use.volume <= 0)
return //No sound
var/dx = turf_source.x - T.x // Hearing from the right/left
S.x = dx * distance_multiplier
var/dz = turf_source.y - T.y // Hearing from infront/behind
S.z = dz * distance_multiplier
var/dy = (turf_source.z - T.z) * 5 * distance_multiplier // Hearing from above / below, multiplied by 5 because we assume height is further along coords.
S.y = dy
var/dx = turf_source.x - turf_loc.x // Hearing from the right/left
sound_to_use.x = dx * distance_multiplier
var/dz = turf_source.y - turf_loc.y // Hearing from infront/behind
sound_to_use.z = dz * distance_multiplier
var/dy = (turf_source.z - turf_loc.z) * 5 * distance_multiplier // Hearing from above / below, multiplied by 5 because we assume height is further along coords.
sound_to_use.y = dy
S.falloff = max_distance || 1 //use max_distance, else just use 1 as we are a direct sound so falloff isnt relevant.
sound_to_use.falloff = max_distance || 1 //use max_distance, else just use 1 as we are a direct sound so falloff isnt relevant.
// Sounds can't have their own environment. A sound's environment will be:
// 1. the mob's
// 2. the area's (defaults to SOUND_ENVRIONMENT_NONE)
if(sound_environment_override != SOUND_ENVIRONMENT_NONE)
S.environment = sound_environment_override
sound_to_use.environment = sound_environment_override
else
var/area/A = get_area(src)
S.environment = A.sound_environment
sound_to_use.environment = A.sound_environment
if(use_reverb && S.environment != SOUND_ENVIRONMENT_NONE) //We have reverb, reset our echo setting
S.echo[3] = 0 //Room setting, 0 means normal reverb
S.echo[4] = 0 //RoomHF setting, 0 means normal reverb.
if(use_reverb && sound_to_use.environment != SOUND_ENVIRONMENT_NONE) //We have reverb, reset our echo setting
sound_to_use.echo[3] = 0 //Room setting, 0 means normal reverb
sound_to_use.echo[4] = 0 //RoomHF setting, 0 means normal reverb.
SEND_SOUND(src, S)
SEND_SOUND(src, sound_to_use)
/proc/sound_to_playing_players(soundin, volume = 100, vary = FALSE, frequency = 0, channel = 0, pressure_affected = FALSE, sound/S)
if(!S)

View File

@@ -1,6 +1,7 @@
/turf/open
plane = FLOOR_PLANE
var/slowdown = 0 //negative for faster, positive for slower
///negative for faster, positive for slower
var/slowdown = 0
var/footstep = null
var/barefootstep = null
@@ -187,10 +188,9 @@
/turf/open/proc/freeze_turf()
for(var/obj/I in contents)
if(I.resistance_flags & FREEZE_PROOF)
continue
if(!(I.obj_flags & FROZEN))
I.make_frozen_visual()
if(!HAS_TRAIT(I, TRAIT_FROZEN) && !(I.obj_flags & FREEZE_PROOF))
I.AddElement(/datum/element/frozen)
for(var/mob/living/L in contents)
if(L.bodytemperature <= 50)
L.apply_status_effect(/datum/status_effect/freon)
@@ -204,8 +204,7 @@
M.apply_water()
wash(CLEAN_WASH)
for(var/am in src)
var/atom/movable/movable_content = am
for(var/atom/movable/movable_content as anything in src)
if(ismopable(movable_content)) // Will have already been washed by the wash call above at this point.
continue
movable_content.wash(CLEAN_WASH)

View File

@@ -33,6 +33,9 @@ GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdr
. = ..()
overlays += GLOB.openspace_backdrop_one_for_all //Special grey square for projecting backdrop darkness filter on it.
RegisterSignal(src, COMSIG_ATOM_INITIALIZED_ON, .proc/on_atom_created)
var/area/our_area = loc
if(istype(our_area, /area/space))
force_no_gravity = TRUE
return INITIALIZE_HINT_LATELOAD
/turf/open/openspace/LateInitialize()

View File

@@ -22,6 +22,8 @@
bullet_bounce_sound = null
vis_flags = VIS_INHERIT_ID //when this be added to vis_contents of something it be associated with something on clicking, important for visualisation of turf in openspace and interraction with openspace that show you turf.
force_no_gravity = TRUE
/turf/open/space/basic/New() //Do not convert to Initialize
//This is used to optimize the map loader
return

View File

@@ -74,6 +74,8 @@ GLOBAL_LIST_EMPTY(station_turfs)
/// but is now destroyed, this will preserve the value.
/// See __DEFINES/construction.dm for RCD_MEMORY_*.
var/rcd_memory
///whether or not this turf forces movables on it to have no gravity (unless they themselves have forced gravity)
var/force_no_gravity = FALSE
/// How pathing algorithm will check if this turf is passable by itself (not including content checks). By default it's just density check.
/// WARNING: Currently to use a density shortcircuiting this does not support dense turfs with special allow through function
@@ -363,8 +365,6 @@ GLOBAL_LIST_EMPTY(station_turfs)
var/canPassSelf = CanPass(mover, get_dir(src, mover))
if(canPassSelf || (mover.movement_type & PHASING))
for(var/atom/movable/thing as anything in contents)
if(QDELETED(mover))
return FALSE //We were deleted, do not attempt to proceed with movement.
if(thing == mover || thing == mover.loc) // Multi tile objects and moving out of other objects
continue
if(!thing.Cross(mover))
@@ -385,14 +385,6 @@ GLOBAL_LIST_EMPTY(station_turfs)
return (mover.movement_type & PHASING)
return TRUE
/turf/open/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
. = ..()
//melting
if(isobj(arrived) && air && air.temperature > T0C)
var/obj/O = arrived
if(O.obj_flags & FROZEN)
O.make_unfrozen()
// A proc in case it needs to be recreated or badmins want to change the baseturfs
/turf/proc/assemble_baseturfs(turf/fake_baseturf_type)
var/static/list/created_baseturf_lists = list()

View File

@@ -185,34 +185,46 @@
if(cooldown)
return
cooldown = TRUE
var/obj/structure/industrial_lift/lift
for(var/l in GLOB.lifts)
var/obj/structure/industrial_lift/possible_lift = l
if(possible_lift.id != id || possible_lift.z == z || possible_lift.controls_locked)
var/datum/lift_master/lift
for(var/datum/lift_master/possible_match as anything in GLOB.active_lifts_by_type[BASIC_LIFT_ID])
if(possible_match.specific_lift_id != id || !check_z(possible_match) || possible_match.controls_locked)
continue
lift = possible_lift
lift = possible_match
break
if(!lift)
addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 2 SECONDS)
return
lift.visible_message(span_notice("[src] clinks and whirrs into automated motion, locking controls."))
lift.lift_master_datum.set_controls(LOCKED)
var/obj/structure/industrial_lift/target = lift.lift_platforms[1]
var/target_z = target.z
lift.set_controls(LIFT_PLATFORM_LOCKED)
///The z level to which the elevator should travel
var/targetZ = (abs(loc.z)) //The target Z (where the elevator should move to) is not our z level (we are just some assembly in nullspace) but actually the Z level of whatever we are contained in (e.g. elevator button)
///The amount of z levels between the our and targetZ
var/difference = abs(targetZ - lift.z)
var/difference = abs(targetZ - target_z)
///Direction (up/down) needed to go to reach targetZ
var/direction = lift.z < targetZ ? UP : DOWN
var/direction = target_z < targetZ ? UP : DOWN
///How long it will/should take us to reach the target Z level
var/travel_duration = FLOOR_TRAVEL_TIME * difference //100 / 2 floors up = 50 seconds on every floor, will always reach destination in the same time
addtimer(VARSET_CALLBACK(src, cooldown, FALSE), travel_duration)
for(var/i in 1 to difference)
sleep(FLOOR_TRAVEL_TIME)//hey this should be alright... right?
if(QDELETED(lift) || QDELETED(src))//elevator control or button gone = don't go up anymore
return
lift.lift_master_datum.MoveLift(direction, null)
lift.visible_message(span_notice("[src] clicks, ready to be manually operated again."))
lift.lift_master_datum.set_controls(UNLOCKED)
lift.MoveLift(direction, null)
lift.set_controls(LIFT_PLATFORM_UNLOCKED)
///check if any of the lift platforms are already here
/obj/item/assembly/control/elevator/proc/check_z(datum/lift_master/lift)
for(var/obj/structure/industrial_lift/platform as anything in lift.lift_platforms)
if(platform.z == loc.z)
return FALSE
return TRUE
#undef FLOOR_TRAVEL_TIME
@@ -221,17 +233,19 @@
desc = "A small device used to bring trams to you."
///for finding the landmark initially - should be the exact same as the landmark's destination id.
var/initial_id
///ID to link to allow us to link to one specific tram in the world
var/specific_lift_id = MAIN_STATION_TRAM
///this is our destination's landmark, so we only have to find it the first time.
var/datum/weakref/to_where
/obj/item/assembly/control/tram/Initialize(mapload)
. = ..()
..()
return INITIALIZE_HINT_LATELOAD
/obj/item/assembly/control/tram/LateInitialize()
. = ..()
//find where the tram needs to go to (our destination). only needs to happen the first time
for(var/obj/effect/landmark/tram/our_destination as anything in GLOB.tram_landmarks)
for(var/obj/effect/landmark/tram/our_destination as anything in GLOB.tram_landmarks[specific_lift_id])
if(our_destination.destination_id == initial_id)
to_where = WEAKREF(our_destination)
break
@@ -245,29 +259,27 @@
return
cooldown = TRUE
addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 2 SECONDS)
var/obj/structure/industrial_lift/tram/tram_part
var/turf/current_turf = get_turf(src)
if(!current_turf)
return
for(var/atom/tram as anything in GLOB.central_trams)
if(tram.z != current_turf.z)
continue
tram_part = tram
break
if(!tram_part)
var/datum/lift_master/tram/tram
for(var/datum/lift_master/tram/possible_match as anything in GLOB.active_lifts_by_type[TRAM_LIFT_ID])
if(possible_match.specific_lift_id == specific_lift_id)
tram = possible_match
break
if(!tram)
say("The tram is not responding to call signals. Please send a technician to repair the internals of the tram.")
return
if(tram_part.travelling) //in use
say("The tram is already travelling to [tram_part.from_where].")
if(tram.travelling) //in use
say("The tram is already travelling to [tram.from_where].")
return
if(!to_where)
return
var/obj/effect/landmark/tram/current_location = to_where.resolve()
if(!current_location)
return
if(tram_part.from_where == current_location) //already here
if(tram.from_where == current_location) //already here
say("The tram is already here. Please board the tram and select a destination.")
return
say("The tram has been called to [current_location.name]. Please wait for its arrival.")
tram_part.tram_travel(current_location)
tram.tram_travel(current_location)

View File

@@ -107,6 +107,7 @@
air_update_turf(FALSE, FALSE)
var/static/list/loc_connections = list(
COMSIG_ATOM_ENTERED = .proc/on_entered,
COMSIG_ATOM_ABSTRACT_ENTERED = .proc/on_entered,
)
AddElement(/datum/element/connect_loc, loc_connections)

View File

@@ -111,8 +111,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
if(volume) // to prevent division by zero
var/cached_gases = gases
TOTAL_MOLES(cached_gases, .)
. *= R_IDEAL_GAS_EQUATION * temperature / volume
return
return . * R_IDEAL_GAS_EQUATION * temperature / volume
return 0
/// Calculate temperature in kelvins
@@ -661,7 +660,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache())
/datum/gas_mixture/proc/gas_pressure_quadratic(a, b, c, lower_limit, upper_limit)
var/solution
if(!IS_INF_OR_NAN(a) && !IS_INF_OR_NAN(b) && !IS_INF_OR_NAN(c))
solution = max(SolveQuadratic(a, b, c))
solution = max(SolveQuadratic(a, b, c))
if(solution > lower_limit && solution < upper_limit) //SolveQuadratic can return empty lists so be careful here
return solution
stack_trace("Failed to solve pressure quadratic equation. A: [a]. B: [b]. C:[c]. Current value = [solution]. Expected lower limit: [lower_limit]. Expected upper limit: [upper_limit].")

View File

@@ -26,7 +26,7 @@
/// green, amber, or red.
var/signal_state = XING_STATE_GREEN
/// The ID of the tram we control
var/tram_id = "tram_station"
var/tram_id = MAIN_STATION_TRAM
/// Weakref to the tram piece we control
var/datum/weakref/tram_ref
/// Proximity threshold for amber warning (slow people may be in danger)
@@ -42,14 +42,14 @@
. = ..()
find_tram()
var/obj/structure/industrial_lift/tram/central/tram_part = tram_ref?.resolve()
var/datum/lift_master/tram/tram_part = tram_ref?.resolve()
if(tram_part)
RegisterSignal(tram_part, COMSIG_TRAM_SET_TRAVELLING, .proc/on_tram_travelling)
/obj/machinery/crossing_signal/Destroy()
. = ..()
var/obj/structure/industrial_lift/tram/central/tram_part = tram_ref?.resolve()
var/datum/lift_master/tram/tram_part = tram_ref?.resolve()
if(tram_part)
UnregisterSignal(tram_part, COMSIG_TRAM_SET_TRAVELLING)
@@ -67,8 +67,8 @@
* Locates tram parts in the lift global list after everything is done.
*/
/obj/machinery/crossing_signal/proc/find_tram()
for(var/obj/structure/industrial_lift/tram/central/tram as anything in GLOB.central_trams)
if(tram.tram_id != tram_id)
for(var/datum/lift_master/tram/tram as anything in GLOB.active_lifts_by_type[TRAM_LIFT_ID])
if(tram.specific_lift_id != tram_id)
continue
tram_ref = WEAKREF(tram)
break
@@ -103,10 +103,10 @@
end_processing()
/obj/machinery/crossing_signal/process()
var/obj/structure/industrial_lift/tram/central/tram_part = tram_ref?.resolve()
var/datum/lift_master/tram/tram = tram_ref?.resolve()
// Check for stopped states.
if(!tram_part || !is_operational)
if(!tram || !is_operational)
// Tram missing, or we lost power.
// Tram missing is always safe (green)
set_signal_state(XING_STATE_GREEN, force = !is_operational)
@@ -114,26 +114,32 @@
use_power(active_power_usage)
var/obj/structure/industrial_lift/tram/tram_part = tram.return_closest_platform_to(src)
if(QDELETED(tram_part))
set_signal_state(XING_STATE_GREEN, force = !is_operational)
return PROCESS_KILL
// Everything will be based on position and travel direction
var/signal_pos
var/tram_pos
var/tram_velocity_sign // 1 for positive axis movement, -1 for negative
// Try to be agnostic about N-S vs E-W movement
if(tram_part.travel_direction & (NORTH|SOUTH))
if(tram.travel_direction & (NORTH|SOUTH))
signal_pos = y
tram_pos = tram_part.y
tram_velocity_sign = tram_part.travel_direction & NORTH ? 1 : -1
tram_velocity_sign = tram.travel_direction & NORTH ? 1 : -1
else
signal_pos = x
tram_pos = tram_part.x
tram_velocity_sign = tram_part.travel_direction & EAST ? 1 : -1
tram_velocity_sign = tram.travel_direction & EAST ? 1 : -1
// How far away are we? negative if already passed.
var/approach_distance = tram_velocity_sign * (signal_pos - tram_pos)
// Check for stopped state.
// Will kill the process since tram starting up will restart process.
if(!tram_part.travelling)
if(!tram.travelling)
// if super close, show red anyway since tram could suddenly start moving
if(abs(approach_distance) < red_distance_threshold)
set_signal_state(XING_STATE_RED)

View File

@@ -0,0 +1,752 @@
GLOBAL_LIST_EMPTY(lifts)
/obj/structure/industrial_lift
name = "lift platform"
desc = "A lightweight lift platform. It moves up and down."
icon = 'icons/obj/smooth_structures/catwalk.dmi'
icon_state = "catwalk-0"
base_icon_state = "catwalk"
density = FALSE
anchored = TRUE
armor = list(MELEE = 50, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, FIRE = 80, ACID = 50)
max_integrity = 50
layer = LATTICE_LAYER //under pipes
plane = FLOOR_PLANE
smoothing_flags = SMOOTH_BITMASK
smoothing_groups = list(SMOOTH_GROUP_INDUSTRIAL_LIFT)
canSmoothWith = list(SMOOTH_GROUP_INDUSTRIAL_LIFT)
obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN
appearance_flags = PIXEL_SCALE|KEEP_TOGETHER //no TILE_BOUND since we're potentially multitile
///ID used to determine what lift types we can merge with
var/lift_id = BASIC_LIFT_ID
///if true, the elevator works through floors
var/pass_through_floors = FALSE
///what movables on our platform that we are moving
var/list/atom/movable/lift_load
///lazylist of weakrefs to the contents we have when we're first created. stored so that admins can clear the tram to its initial state
///if someone put a bunch of stuff onto it.
var/list/datum/weakref/initial_contents
///what glide_size we set our moving contents to.
var/glide_size_override = 8
///lazy list of movables inside lift_load who had their glide_size changed since our last movement.
///used so that we dont have to change the glide_size of every object every movement, which scales to cost more than you'd think
var/list/atom/movable/changed_gliders
///master datum that controls our movement. in general /industrial_lift subtypes control moving themselves, and
/// /datum/lift_master instances control moving the entire tram and any behavior associated with that.
var/datum/lift_master/lift_master_datum
///what subtype of /datum/lift_master to create for itself if no other platform on this tram has created one yet.
///very important for some behaviors since
var/lift_master_type = /datum/lift_master
///how many tiles this platform extends on the x axis
var/width = 1
///how many tiles this platform extends on the y axis (north-south not up-down, that would be the z axis)
var/height = 1
///if TRUE, this platform will late initialize and then expand to become a multitile object across all other linked platforms on this z level
var/create_multitile_platform = FALSE
/obj/structure/industrial_lift/Initialize(mapload)
. = ..()
GLOB.lifts.Add(src)
set_movement_registrations()
//since lift_master datums find all connected platforms when an industrial lift first creates it and then
//sets those platforms' lift_master_datum to itself, this check will only evaluate to true once per tram platform
if(!lift_master_datum && lift_master_type)
lift_master_datum = new lift_master_type(src)
return INITIALIZE_HINT_LATELOAD
/obj/structure/industrial_lift/LateInitialize()
//after everything is initialized the lift master can order everything
lift_master_datum.order_platforms_by_z_level()
/obj/structure/industrial_lift/Destroy()
GLOB.lifts.Remove(src)
lift_master_datum = null
return ..()
///set the movement registrations to our current turf(s) so contents moving out of our tile(s) are removed from our movement lists
/obj/structure/industrial_lift/proc/set_movement_registrations(list/turfs_to_set)
for(var/turf/turf_loc as anything in turfs_to_set || locs)
RegisterSignal(turf_loc, COMSIG_ATOM_EXITED, .proc/UncrossedRemoveItemFromLift)
RegisterSignal(turf_loc, list(COMSIG_ATOM_ENTERED,COMSIG_ATOM_INITIALIZED_ON), .proc/AddItemOnLift)
///unset our movement registrations from turfs that no longer contain us (or every loc if turfs_to_unset is unspecified)
/obj/structure/industrial_lift/proc/unset_movement_registrations(list/turfs_to_unset)
var/static/list/registrations = list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_EXITED, COMSIG_ATOM_INITIALIZED_ON)
for(var/turf/turf_loc as anything in turfs_to_unset || locs)
UnregisterSignal(turf_loc, registrations)
/obj/structure/industrial_lift/proc/UncrossedRemoveItemFromLift(datum/source, atom/movable/gone, direction)
SIGNAL_HANDLER
if(!(gone.loc in locs))
RemoveItemFromLift(gone)
/obj/structure/industrial_lift/proc/RemoveItemFromLift(atom/movable/potential_rider)
SIGNAL_HANDLER
if(!(potential_rider in lift_load))
return
if(isliving(potential_rider) && HAS_TRAIT(potential_rider, TRAIT_CANNOT_BE_UNBUCKLED))
REMOVE_TRAIT(potential_rider, TRAIT_CANNOT_BE_UNBUCKLED, BUCKLED_TRAIT)
LAZYREMOVE(lift_load, potential_rider)
LAZYREMOVE(changed_gliders, potential_rider)
UnregisterSignal(potential_rider, list(COMSIG_PARENT_QDELETING, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE))
/obj/structure/industrial_lift/proc/AddItemOnLift(datum/source, atom/movable/new_lift_contents)
SIGNAL_HANDLER
var/static/list/blacklisted_types = typecacheof(list(/obj/structure/fluff/tram_rail, /obj/effect/decal/cleanable, /obj/structure/industrial_lift, /mob/camera))
if(is_type_in_typecache(new_lift_contents, blacklisted_types) || new_lift_contents.invisibility == INVISIBILITY_ABSTRACT) //prevents the tram from stealing things like landmarks
return FALSE
if(new_lift_contents in lift_load)
return FALSE
if(isliving(new_lift_contents) && !HAS_TRAIT(new_lift_contents, TRAIT_CANNOT_BE_UNBUCKLED))
ADD_TRAIT(new_lift_contents, TRAIT_CANNOT_BE_UNBUCKLED, BUCKLED_TRAIT)
LAZYADD(lift_load, new_lift_contents)
RegisterSignal(new_lift_contents, COMSIG_PARENT_QDELETING, .proc/RemoveItemFromLift)
return TRUE
///adds everything on our tile that can be added to our lift_load and initial_contents lists when we're created
/obj/structure/industrial_lift/proc/add_initial_contents()
for(var/turf/turf_loc in locs)
for(var/atom/movable/movable_contents as anything in turf_loc)
if(movable_contents == src)
continue
if(AddItemOnLift(src, movable_contents))
var/datum/weakref/new_initial_contents = WEAKREF(movable_contents)
if(!new_initial_contents)
continue
LAZYADD(initial_contents, new_initial_contents)
///signal handler for COMSIG_MOVABLE_UPDATE_GLIDE_SIZE: when a movable in lift_load changes its glide_size independently.
///adds that movable to a lazy list, movables in that list have their glide_size updated when the tram next moves
/obj/structure/industrial_lift/proc/on_changed_glide_size(atom/movable/moving_contents, new_glide_size)
SIGNAL_HANDLER
if(new_glide_size != glide_size_override)
LAZYADD(changed_gliders, moving_contents)
///make this tram platform multitile, expanding to cover all the tram platforms adjacent to us and deleting them. makes movement more efficient.
///the platform becoming multitile should be in the bottom left corner since thats assumed to be the loc of multitile objects
/obj/structure/industrial_lift/proc/create_multitile_platform(min_x, min_y, max_x, max_y, z)
if(!(min_x && min_y && max_x && max_y && z))
for(var/obj/structure/industrial_lift/other_lift as anything in lift_master_datum.lift_platforms)
if(other_lift.z != z)
continue
min_x = min(min_x, other_lift.x)
max_x = max(max_x, other_lift.x)
min_y = min(min_y, other_lift.y)
max_y = max(max_y, other_lift.y)
var/turf/bottom_left_loc = locate(min_x, min_y, z)
var/obj/structure/industrial_lift/loc_corner_lift = locate() in bottom_left_loc
if(!loc_corner_lift)
stack_trace("no lift in the bottom left corner of a lift level!")
return FALSE
if(loc_corner_lift != src)
//the loc of a multitile object must always be the lower left corner
return loc_corner_lift.create_multitile_platform()
width = (max_x - min_x) + 1
height = (max_y - min_y) + 1
///list of turfs we dont go over. if for whatever reason we encounter an already multitile lift platform
///we add all of its locs to this list so we dont add that lift platform multiple times as we iterate through its locs
var/list/locs_to_skip = locs.Copy()
bound_width = bound_width * width
bound_height = bound_height * height
//multitile movement code assumes our loc is on the lower left corner of our bounding box
var/first_x = 0
var/first_y = 0
var/last_x = max(max_x - min_x, 0)
var/last_y = max(max_y - min_y, 0)
for(var/y in first_y to last_y)
var/y_pixel_offset = world.icon_size * y
for(var/x in first_x to last_x)
var/x_pixel_offset = world.icon_size * x
var/turf/lift_turf = locate(x + min_x, y + min_y, z)
if(!lift_turf)
continue
if(lift_turf in locs_to_skip)
continue
var/obj/structure/industrial_lift/other_lift = locate() in lift_turf
if(!other_lift)
continue
locs_to_skip += other_lift.locs.Copy()//make sure we never go over multitile platforms multiple times
other_lift.pixel_x = x_pixel_offset
other_lift.pixel_y = y_pixel_offset
overlays += other_lift
//now we vore all the other lifts connected to us on our z level
for(var/obj/structure/industrial_lift/other_lift in lift_master_datum.lift_platforms)
if(other_lift == src || other_lift.z != z)
continue
lift_master_datum.lift_platforms -= other_lift
if(other_lift.lift_load)
LAZYOR(lift_load, other_lift.lift_load)
if(other_lift.initial_contents)
LAZYOR(initial_contents, other_lift.initial_contents)
qdel(other_lift)
lift_master_datum.multitile_platform = TRUE
var/turf/old_loc = loc
forceMove(locate(min_x, min_y, z))//move to the lower left corner
set_movement_registrations(locs - old_loc)
return TRUE
///returns an unordered list of all lift platforms adjacent to us. used so our lift_master_datum can control all connected platforms.
///includes platforms directly above or below us as well. only includes platforms with an identical lift_id to our own.
/obj/structure/industrial_lift/proc/lift_platform_expansion(datum/lift_master/lift_master_datum)
. = list()
for(var/direction in GLOB.cardinals_multiz)
var/obj/structure/industrial_lift/neighbor = locate() in get_step_multiz(src, direction)
if(!neighbor || neighbor.lift_id != lift_id)
continue
. += neighbor
///main proc for moving the lift in the direction [going]. handles horizontal and/or vertical movement for multi platformed lifts and multitile lifts.
/obj/structure/industrial_lift/proc/travel(going)
var/list/things_to_move = lift_load
var/turf/destination
if(!isturf(going))
destination = get_step_multiz(src, going)
else
destination = going
going = get_dir_multiz(loc, going)
var/x_offset = ROUND_UP(bound_width / 32) - 1 //how many tiles our horizontally farthest edge is from us
var/y_offset = ROUND_UP(bound_height / 32) - 1 //how many tiles our vertically farthest edge is from us
//the x coordinate of the edge furthest from our future destination, which would be our right hand side
var/back_edge_x = destination.x + x_offset//if we arent multitile this should just be destination.x
var/top_edge_y = destination.y + y_offset
var/turf/top_right_corner = locate(min(world.maxx, back_edge_x), min(world.maxy, top_edge_y), destination.z)
var/list/dest_locs = block(
destination,
top_right_corner
)
var/list/entering_locs = dest_locs - locs
var/list/exited_locs = locs - dest_locs
if(going == DOWN)
for(var/turf/dest_turf as anything in entering_locs)
SEND_SIGNAL(dest_turf, COMSIG_TURF_INDUSTRIAL_LIFT_ENTER, things_to_move)
if(istype(dest_turf, /turf/closed/wall))
var/turf/closed/wall/C = dest_turf
do_sparks(2, FALSE, C)
C.dismantle_wall(devastated = TRUE)
for(var/mob/M in urange(8, src))
shake_camera(M, 2, 3)
playsound(C, 'sound/effects/meteorimpact.ogg', 100, TRUE)
for(var/mob/living/crushed in dest_turf.contents)
to_chat(crushed, span_userdanger("You are crushed by [src]!"))
crushed.gib(FALSE,FALSE,FALSE)//the nicest kind of gibbing, keeping everything intact.
else if(going == UP)
for(var/turf/dest_turf as anything in entering_locs)
///handles any special interactions objects could have with the lift/tram, handled on the item itself
SEND_SIGNAL(dest_turf, COMSIG_TURF_INDUSTRIAL_LIFT_ENTER, things_to_move)
if(istype(dest_turf, /turf/closed/wall))
var/turf/closed/wall/C = dest_turf
do_sparks(2, FALSE, C)
C.dismantle_wall(devastated = TRUE)
for(var/mob/client_mob in SSspatial_grid.orthogonal_range_search(src, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS, 8))
shake_camera(client_mob, 2, 3)
playsound(C, 'sound/effects/meteorimpact.ogg', 100, TRUE)
else
///potentially finds a spot to throw the victim at for daring to be hit by a tram. is null if we havent found anything to throw
var/atom/throw_target
var/datum/lift_master/tram/our_lift = lift_master_datum
var/collision_lethality = our_lift.collision_lethality
for(var/turf/dest_turf as anything in entering_locs)
///handles any special interactions objects could have with the lift/tram, handled on the item itself
SEND_SIGNAL(dest_turf, COMSIG_TURF_INDUSTRIAL_LIFT_ENTER, things_to_move)
if(istype(dest_turf, /turf/closed/wall))
var/turf/closed/wall/collided_wall = dest_turf
do_sparks(2, FALSE, collided_wall)
collided_wall.dismantle_wall(devastated = TRUE)
for(var/mob/client_mob in SSspatial_grid.orthogonal_range_search(collided_wall, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS, 8))
if(get_dist(dest_turf, client_mob) <= 8)
shake_camera(client_mob, 2, 3)
playsound(collided_wall, 'sound/effects/meteorimpact.ogg', 100, TRUE)
for(var/obj/structure/victim_structure in dest_turf.contents)
if(QDELING(victim_structure))
continue
if(!is_type_in_typecache(victim_structure, lift_master_datum.ignored_smashthroughs) && victim_structure.layer >= LOW_OBJ_LAYER)
if(victim_structure.anchored && initial(victim_structure.anchored) == TRUE)
visible_message(span_danger("[src] smashes through [victim_structure]!"))
victim_structure.deconstruct(FALSE)
else
if(!throw_target)
throw_target = get_edge_target_turf(src, turn(going, pick(45, -45)))
visible_message(span_danger("[src] violently rams [victim_structure] out of the way!"))
victim_structure.anchored = FALSE
victim_structure.take_damage(rand(20, 25) * collision_lethality)
victim_structure.throw_at(throw_target, 200 * collision_lethality, 4 * collision_lethality)
for(var/obj/machinery/victim_machine in dest_turf.contents)
if(QDELING(victim_machine))
continue
if(is_type_in_typecache(victim_machine, lift_master_datum.ignored_smashthroughs))
continue
if(istype(victim_machine, /obj/machinery/field)) //graceful break handles this scenario
continue
if(victim_machine.layer >= LOW_OBJ_LAYER) //avoids stuff that is probably flush with the ground
playsound(src, 'sound/effects/bang.ogg', 50, TRUE)
visible_message(span_danger("[src] smashes through [victim_machine]!"))
qdel(victim_machine)
for(var/mob/living/collided in dest_turf.contents)
if(lift_master_datum.ignored_smashthroughs[collided.type])
continue
to_chat(collided, span_userdanger("[src] collides into you!"))
playsound(src, 'sound/effects/splat.ogg', 50, TRUE)
var/damage = rand(5, 10) * collision_lethality
collided.apply_damage(2 * damage, BRUTE, BODY_ZONE_HEAD)
collided.apply_damage(2 * damage, BRUTE, BODY_ZONE_CHEST)
collided.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_L_LEG)
collided.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_R_LEG)
collided.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_L_ARM)
collided.apply_damage(0.5 * damage, BRUTE, BODY_ZONE_R_ARM)
if(QDELETED(collided)) //in case it was a mob that dels on death
continue
if(!throw_target)
throw_target = get_edge_target_turf(src, turn(going, pick(45, -45)))
var/turf/T = get_turf(collided)
T.add_mob_blood(collided)
collided.throw_at()
//if going EAST, will turn to the NORTHEAST or SOUTHEAST and throw the ran over guy away
var/datum/callback/land_slam = new(collided, /mob/living/.proc/tram_slam_land)
collided.throw_at(throw_target, 200 * collision_lethality, 4 * collision_lethality, callback = land_slam)
unset_movement_registrations(exited_locs)
group_move(things_to_move, going)
set_movement_registrations(entering_locs)
///move the movers list of movables on our tile to destination if we successfully move there first.
///this is like calling forceMove() on everything in movers and ourselves, except nothing in movers
///has destination.Entered() and origin.Exited() called on them, as only our movement can be perceived.
///none of the movers are able to react to the movement of any other mover, saving a lot of needless processing cost
///and is more sensible. without this, if you and a banana are on the same platform, when that platform moves you will slip
///on the banana even if youre not moving relative to it.
/obj/structure/industrial_lift/proc/group_move(list/atom/movable/movers, movement_direction)
if(movement_direction == NONE)
stack_trace("an industrial lift was told to move to somewhere it already is!")
return FALSE
var/turf/our_dest = get_step(src, movement_direction)
var/area/our_area = get_area(src)
var/area/their_area = get_area(our_dest)
var/different_areas = our_area != their_area
var/turf/mover_old_loc
if(glide_size != glide_size_override)
set_glide_size(glide_size_override)
forceMove(our_dest)
if(loc != our_dest || QDELETED(src))//check if our movement succeeded, if it didnt then the movers cant be moved
return FALSE
for(var/atom/movable/mover as anything in changed_gliders)
if(mover.glide_size != glide_size_override)
mover.set_glide_size(glide_size_override)
LAZYREMOVE(changed_gliders, mover)
if(different_areas)
for(var/atom/movable/mover as anything in movers)
if(QDELETED(mover))
movers -= mover
continue
//we dont need to call Entered() and Exited() for origin and destination here for each mover because
//all of them are considered to be on top of us, so the turfs and anything on them can only perceive us,
//which is why the platform itself uses forceMove()
mover_old_loc = mover.loc
our_area.Exited(mover, movement_direction)
mover.loc = get_step(mover, movement_direction)
their_area.Entered(mover, movement_direction)
mover.Moved(mover_old_loc, movement_direction, TRUE, null, FALSE)
else
for(var/atom/movable/mover as anything in movers)
if(QDELETED(mover))
movers -= mover
continue
mover_old_loc = mover.loc
mover.loc = get_step(mover, movement_direction)
mover.Moved(mover_old_loc, movement_direction, TRUE, null, FALSE)
return TRUE
/**
* reset the contents of this lift platform to its original state in case someone put too much shit on it.
* used by an admin via calling reset_lift_contents() on our lift_master_datum.
*
* Arguments:
* * consider_anything_past - number. if > 0 this platform will only handle foreign contents that exceed this number on each of our locs
* * foreign_objects - bool. if true this platform will consider /atom/movable's that arent mobs as part of foreign contents
* * foreign_non_player_mobs - bool. if true we consider mobs that dont have a mind to be foreign
* * consider_player_mobs - bool. if true we consider player mobs to be foreign. only works if foreign_non_player_mobs is true as well
*/
/obj/structure/industrial_lift/proc/reset_contents(consider_anything_past = 0, foreign_objects = TRUE, foreign_non_player_mobs = TRUE, consider_player_mobs = FALSE)
if(!foreign_objects && !foreign_non_player_mobs && !consider_player_mobs)
return FALSE
consider_anything_past = isnum(consider_anything_past) ? max(consider_anything_past, 0) : 0
//just in case someone fucks up the arguments
if(consider_anything_past && length(lift_load) <= consider_anything_past)
return FALSE
///list of resolve()'d initial_contents that are still in lift_load
var/list/atom/movable/original_contents = list(src)
///list of objects we consider foreign according to the given arguments
var/list/atom/movable/foreign_contents = list()
for(var/datum/weakref/initial_contents_ref as anything in initial_contents)
if(!initial_contents_ref)
continue
var/atom/movable/resolved_contents = initial_contents_ref.resolve()
if(!resolved_contents)
continue
if(!(resolved_contents in lift_load))
continue
original_contents += resolved_contents
for(var/turf/turf_loc as anything in locs)
var/list/atom/movable/foreign_contents_in_loc = list()
for(var/atom/movable/foreign_movable as anything in (turf_loc.contents - original_contents))
if(foreign_objects && ismovable(foreign_movable) && !ismob(foreign_movable))
foreign_contents_in_loc += foreign_movable
continue
if(foreign_non_player_mobs && ismob(foreign_movable))
var/mob/foreign_mob = foreign_movable
if(consider_player_mobs || !foreign_mob.mind)
foreign_contents_in_loc += foreign_mob
continue
if(consider_anything_past)
foreign_contents_in_loc.len -= consider_anything_past
//hey cool this works, neat. this takes from the opposite side of the list that youd expect but its easy so idc
//also this means that if you use consider_anything_past then foreign mobs are less likely to be deleted than foreign objects
//because internally the contents list is 2 linked lists of obj contents - mob contents, thus mobs are always last in the order
//when you iterate it.
foreign_contents += foreign_contents_in_loc
for(var/atom/movable/contents_to_delete as anything in foreign_contents)
qdel(contents_to_delete)
return TRUE
/obj/structure/industrial_lift/proc/use(mob/living/user)
if(!isliving(user) || !in_range(src, user) || user.combat_mode)
return
var/list/tool_list = list()
if(lift_master_datum.Check_lift_move(UP))
tool_list["Up"] = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH)
if(lift_master_datum.Check_lift_move(DOWN))
tool_list["Down"] = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH)
if(!length(tool_list))
to_chat(user, span_warning("[src] doesn't seem to able to move anywhere!"))
add_fingerprint(user)
return
if(lift_master_datum.controls_locked)
to_chat(user, span_warning("[src] has its controls locked! It must already be trying to do something!"))
add_fingerprint(user)
return
var/result = show_radial_menu(user, src, tool_list, custom_check = CALLBACK(src, .proc/check_menu, user, src.loc), require_near = TRUE, tooltips = TRUE)
if(!isliving(user) || !in_range(src, user) || user.combat_mode)
return //nice try
switch(result)
if("Up")
// We have to make sure that they don't do illegal actions by not having their radial menu refresh from someone else moving the lift.
if(!lift_master_datum.Check_lift_move(UP))
to_chat(user, span_warning("[src] doesn't seem to able to move up!"))
add_fingerprint(user)
return
lift_master_datum.MoveLift(UP, user)
show_fluff_message(TRUE, user)
use(user)
if("Down")
if(!lift_master_datum.Check_lift_move(DOWN))
to_chat(user, span_warning("[src] doesn't seem to able to move down!"))
add_fingerprint(user)
return
lift_master_datum.MoveLift(DOWN, user)
show_fluff_message(FALSE, user)
use(user)
if("Cancel")
return
add_fingerprint(user)
/**
* Proc to ensure that the radial menu closes when it should.
* Arguments:
* * user - The person that opened the menu.
* * starting_loc - The location of the lift when the menu was opened, used to prevent the menu from being interacted with after the lift was moved by someone else.
*
* Returns:
* * boolean, FALSE if the menu should be closed, TRUE if the menu is clear to stay opened.
*/
/obj/structure/industrial_lift/proc/check_menu(mob/user, starting_loc)
if(user.incapacitated() || !user.Adjacent(src) || starting_loc != src.loc)
return FALSE
return TRUE
/obj/structure/industrial_lift/attack_hand(mob/user, list/modifiers)
. = ..()
if(.)
return
use(user)
//ai probably shouldn't get to use lifts but they sure are great for admins to crush people with
/obj/structure/industrial_lift/attack_ghost(mob/user)
. = ..()
if(.)
return
if(isAdminGhostAI(user))
use(user)
/obj/structure/industrial_lift/attack_paw(mob/user, list/modifiers)
return use(user)
/obj/structure/industrial_lift/attackby(obj/item/W, mob/user, params)
return use(user)
/obj/structure/industrial_lift/attack_robot(mob/living/silicon/robot/R)
if(R.Adjacent(src))
return use(R)
/**
* Shows a message indicating that the lift has moved up or down.
* Arguments:
* * going_up - Boolean on whether or not we're going up, to adjust the message appropriately.
* * user - The mob that caused the lift to move, for the visible message.
*/
/obj/structure/industrial_lift/proc/show_fluff_message(going_up, mob/user)
if(going_up)
user.visible_message(span_notice("[user] moves the lift upwards."), span_notice("You move the lift upwards."))
else
user.visible_message(span_notice("[user] moves the lift downwards."), span_notice("You move the lift downwards."))
/obj/structure/industrial_lift/debug
name = "transport platform"
desc = "A lightweight platform. It moves in any direction, except up and down."
color = "#5286b9ff"
lift_id = DEBUG_LIFT_ID
/obj/structure/industrial_lift/debug/use(mob/user)
if (!in_range(src, user))
return
//NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST
var/static/list/tool_list = list(
"NORTH" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH),
"NORTHEAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH),
"EAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = EAST),
"SOUTHEAST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = EAST),
"SOUTH" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH),
"SOUTHWEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH),
"WEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = WEST),
"NORTHWEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = WEST)
)
var/result = show_radial_menu(user, src, tool_list, custom_check = CALLBACK(src, .proc/check_menu, user), require_near = TRUE, tooltips = FALSE)
if (!in_range(src, user))
return // nice try
switch(result)
if("NORTH")
lift_master_datum.MoveLiftHorizontal(NORTH, z)
use(user)
if("NORTHEAST")
lift_master_datum.MoveLiftHorizontal(NORTHEAST, z)
use(user)
if("EAST")
lift_master_datum.MoveLiftHorizontal(EAST, z)
use(user)
if("SOUTHEAST")
lift_master_datum.MoveLiftHorizontal(SOUTHEAST, z)
use(user)
if("SOUTH")
lift_master_datum.MoveLiftHorizontal(SOUTH, z)
use(user)
if("SOUTHWEST")
lift_master_datum.MoveLiftHorizontal(SOUTHWEST, z)
use(user)
if("WEST")
lift_master_datum.MoveLiftHorizontal(WEST, z)
use(user)
if("NORTHWEST")
lift_master_datum.MoveLiftHorizontal(NORTHWEST, z)
use(user)
if("Cancel")
return
add_fingerprint(user)
/obj/structure/industrial_lift/tram
name = "tram"
desc = "A tram for tramversing the station."
icon = 'icons/turf/floors.dmi'
icon_state = "titanium_yellow"
base_icon_state = null
smoothing_flags = NONE
smoothing_groups = null
canSmoothWith = null
//kind of a centerpiece of the station, so pretty tough to destroy
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
lift_id = TRAM_LIFT_ID
lift_master_type = /datum/lift_master/tram
/// Set by the tram control console in late initialize
var/travelling = FALSE
//the following are only used to give to the lift_master datum when it's first created
///decisecond delay between horizontal movements. cannot make the tram move faster than 1 movement per world.tick_lag. only used to give to the lift_master
var/horizontal_speed = 0.5
create_multitile_platform = TRUE
/obj/structure/industrial_lift/tram/AddItemOnLift(datum/source, atom/movable/AM)
. = ..()
if(travelling)
on_changed_glide_size(AM, AM.glide_size)
/obj/structure/industrial_lift/tram/proc/set_travelling(travelling)
if (src.travelling == travelling)
return
for(var/atom/movable/glider as anything in lift_load)
if(travelling)
glider.set_glide_size(glide_size_override)
RegisterSignal(glider, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, .proc/on_changed_glide_size)
else
LAZYREMOVE(changed_gliders, glider)
UnregisterSignal(glider, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE)
src.travelling = travelling
SEND_SIGNAL(src, COMSIG_TRAM_SET_TRAVELLING, travelling)
/obj/structure/industrial_lift/tram/use(mob/user) //dont click the floor dingus we use computers now
return
/obj/structure/industrial_lift/tram/set_currently_z_moving()
return FALSE //trams can never z fall and shouldnt waste any processing time trying to do so
/**
* Handles unlocking the tram controls for use after moving
*
* More safety checks to make sure the tram has actually docked properly
* at a location before users are allowed to interact with the tram console again.
* Tram finds its location at this point before fully unlocking controls to the user.
*/
/obj/structure/industrial_lift/tram/proc/unlock_controls()
visible_message(span_notice("[src]'s controls are now unlocked."))
for(var/obj/structure/industrial_lift/tram/tram_part as anything in lift_master_datum.lift_platforms) //only thing everyone needs to know is the new location.
tram_part.set_travelling(FALSE)
lift_master_datum.set_controls(LIFT_PLATFORM_UNLOCKED)
///debug proc to highlight the locs of the tram platform
/obj/structure/industrial_lift/tram/proc/find_dimensions(iterations = 100)
message_admins("num turfs: [length(locs)]")
var/overlay = /obj/effect/overlay/ai_detect_hud
var/list/turfs = list()
for(var/turf/our_turf as anything in locs)
new overlay(our_turf)
turfs += our_turf
addtimer(CALLBACK(src, .proc/clear_turfs, turfs, iterations), 1)
/obj/structure/industrial_lift/tram/proc/clear_turfs(list/turfs_to_clear, iterations)
for(var/turf/our_old_turf as anything in turfs_to_clear)
var/obj/effect/overlay/ai_detect_hud/hud = locate() in our_old_turf
if(hud)
qdel(hud)
var/overlay = /obj/effect/overlay/ai_detect_hud
for(var/turf/our_turf as anything in locs)
new overlay(our_turf)
iterations--
var/list/turfs = list()
for(var/turf/our_turf as anything in locs)
turfs += our_turf
if(iterations)
addtimer(CALLBACK(src, .proc/clear_turfs, turfs, iterations), 1)

View File

@@ -0,0 +1,397 @@
///associative list of the form: list(lift_id = list(all lift_master datums attached to lifts of that type))
GLOBAL_LIST_EMPTY(active_lifts_by_type)
///coordinate and control movement across linked industrial_lift's. allows moving large single multitile platforms and many 1 tile platforms.
///also is capable of linking platforms across linked z levels
/datum/lift_master
///the lift platforms we consider as part of this lift. ordered in order of lowest z level to highest z level after init.
///(the sorting algorithm sucks btw)
var/list/obj/structure/industrial_lift/lift_platforms
/// Typepath list of what to ignore smashing through, controls all lifts
var/static/list/ignored_smashthroughs = list(
/obj/machinery/power/supermatter_crystal,
/obj/structure/holosign,
/obj/machinery/field,
)
///whether the lift handled by this lift_master datum is multitile as opposed to nxm platforms per z level
var/multitile_platform = FALSE
///taken from our lift platforms. if true we go through each z level of platforms and attempt to make the lowest left corner platform
///into one giant multitile object the size of all other platforms on that z level.
var/create_multitile_platform = FALSE
///lift platforms have already been sorted in order of z level.
var/z_sorted = FALSE
///lift_id taken from our base lift platform, used to put us into GLOB.active_lifts_by_type
var/lift_id = BASIC_LIFT_ID
///overridable ID string to link control units to this specific lift_master datum. created by placing a lift id landmark object
///somewhere on the tram, if its anywhere on the tram we'll find it in init and set this to whatever it specifies
var/specific_lift_id
///what directions we're allowed to move
var/allowed_travel_directions = ALL
///if true, the lift cannot be manually moved.
var/controls_locked = FALSE
/datum/lift_master/New(obj/structure/industrial_lift/lift_platform)
lift_id = lift_platform.lift_id
create_multitile_platform = lift_platform.create_multitile_platform
Rebuild_lift_plaform(lift_platform)
ignored_smashthroughs = typecacheof(ignored_smashthroughs)
LAZYADDASSOCLIST(GLOB.active_lifts_by_type, lift_id, src)
for(var/obj/structure/industrial_lift/lift as anything in lift_platforms)
lift.add_initial_contents()
/datum/lift_master/Destroy()
for(var/obj/structure/industrial_lift/lift_platform as anything in lift_platforms)
lift_platform.lift_master_datum = null
lift_platforms = null
LAZYREMOVEASSOC(GLOB.active_lifts_by_type, lift_id, src)
if(isnull(GLOB.active_lifts_by_type))
GLOB.active_lifts_by_type = list()//im lazy
return ..()
/datum/lift_master/proc/add_lift_platforms(obj/structure/industrial_lift/new_lift_platform)
if(new_lift_platform in lift_platforms)
return
for(var/obj/structure/industrial_lift/other_platform in new_lift_platform.loc)
if(other_platform != new_lift_platform)
stack_trace("there is more than one lift platform on a tile when a lift_master adds it. this causes problems")
qdel(other_platform)
new_lift_platform.lift_master_datum = src
LAZYADD(lift_platforms, new_lift_platform)
RegisterSignal(new_lift_platform, COMSIG_PARENT_QDELETING, .proc/remove_lift_platforms)
check_for_landmarks(new_lift_platform)
if(z_sorted)//make sure we dont lose z ordering if we get additional platforms after init
order_platforms_by_z_level()
/datum/lift_master/proc/remove_lift_platforms(obj/structure/industrial_lift/old_lift_platform)
SIGNAL_HANDLER
if(!(old_lift_platform in lift_platforms))
return
old_lift_platform.lift_master_datum = null
LAZYREMOVE(lift_platforms, old_lift_platform)
UnregisterSignal(old_lift_platform, COMSIG_PARENT_QDELETING)
if(!length(lift_platforms))
qdel(src)
///Collect all bordered platforms via a simple floodfill algorithm. allows multiz trams because its funny
/datum/lift_master/proc/Rebuild_lift_plaform(obj/structure/industrial_lift/base_lift_platform)
add_lift_platforms(base_lift_platform)
var/list/possible_expansions = list(base_lift_platform)
while(possible_expansions.len)
for(var/obj/structure/industrial_lift/borderline as anything in possible_expansions)
var/list/result = borderline.lift_platform_expansion(src)
if(length(result))
for(var/obj/structure/industrial_lift/lift_platform as anything in result)
if(lift_platforms.Find(lift_platform))
continue
add_lift_platforms(lift_platform)
possible_expansions |= lift_platform
possible_expansions -= borderline
///check for any landmarks placed inside the locs of the given lift_platform
/datum/lift_master/proc/check_for_landmarks(obj/structure/industrial_lift/new_lift_platform)
SHOULD_CALL_PARENT(TRUE)
for(var/turf/platform_loc as anything in new_lift_platform.locs)
var/obj/effect/landmark/lift_id/id_giver = locate() in platform_loc
if(id_giver)
set_info_from_id_landmark(id_giver)
///set vars and such given an overriding lift_id landmark
/datum/lift_master/proc/set_info_from_id_landmark(obj/effect/landmark/lift_id/landmark)
SHOULD_CALL_PARENT(TRUE)
if(!istype(landmark, /obj/effect/landmark/lift_id))//lift_master subtypes can want differnet id's than the base type wants
return
if(landmark.specific_lift_id)
specific_lift_id = landmark.specific_lift_id
qdel(landmark)
///orders the lift platforms in order of lowest z level to highest z level.
/datum/lift_master/proc/order_platforms_by_z_level()
//contains nested lists for every z level in the world. why? because its really easy to sort
var/list/platforms_by_z = list()
platforms_by_z.len = world.maxz
for(var/z in 1 to world.maxz)
platforms_by_z[z] = list()
for(var/obj/structure/industrial_lift/lift_platform as anything in lift_platforms)
if(QDELETED(lift_platform) || !lift_platform.z)
lift_platforms -= lift_platform
continue
platforms_by_z[lift_platform.z] += lift_platform
if(create_multitile_platform)
for(var/list/z_list as anything in platforms_by_z)
if(!length(z_list))
continue
create_multitile_platform_for_z_level(z_list)//this will subtract all but one platform from the list
var/list/output = list()
for(var/list/z_list as anything in platforms_by_z)
output += z_list
lift_platforms = output
z_sorted = TRUE
///goes through all platforms in the given list and finds the one in the lower left corner
/datum/lift_master/proc/create_multitile_platform_for_z_level(list/obj/structure/industrial_lift/platforms_in_z)
var/min_x = INFINITY
var/max_x = 0
var/min_y = INFINITY
var/max_y = 0
var/z = 0
for(var/obj/structure/industrial_lift/lift_to_sort as anything in platforms_in_z)
if(!z)
if(!lift_to_sort.z)
stack_trace("create_multitile_platform_for_z_level() was given a platform in nullspace or not on a turf!")
platforms_in_z -= lift_to_sort
continue
z = lift_to_sort.z
if(z != lift_to_sort.z)
stack_trace("create_multitile_platform_for_z_level() was given lifts on different z levels!")
platforms_in_z -= lift_to_sort
continue
min_x = min(min_x, lift_to_sort.x)
max_x = max(max_x, lift_to_sort.x)
min_y = min(min_y, lift_to_sort.y)
max_y = max(max_y, lift_to_sort.y)
var/turf/lower_left_corner_loc = locate(min_x, min_y, z)
if(!lower_left_corner_loc)
CRASH("was unable to find a turf at the lower left corner of this z")
var/obj/structure/industrial_lift/lower_left_corner_lift = locate() in lower_left_corner_loc
if(!lower_left_corner_lift)
CRASH("there was no lift in the lower left corner of the given lifts")
platforms_in_z.Cut()
platforms_in_z += lower_left_corner_lift//we want to change the list given to us not create a new one. so we do this
lower_left_corner_lift.create_multitile_platform(min_x, min_y, max_x, max_y, z)
///returns the closest lift to the specified atom, prioritizing lifts on the same z level. used for comparing distance
/datum/lift_master/proc/return_closest_platform_to(atom/comparison, allow_multiple_answers = FALSE)
if(!istype(comparison) || !comparison.z)
return FALSE
var/list/obj/structure/industrial_lift/candidate_platforms = list()
for(var/obj/structure/industrial_lift/platform as anything in lift_platforms)
if(platform.z == comparison.z)
candidate_platforms += platform
var/obj/structure/industrial_lift/winner = candidate_platforms[1]
var/winner_distance = get_dist(comparison, winner)
var/list/tied_winners = list(winner)
for(var/obj/structure/industrial_lift/platform_to_sort as anything in candidate_platforms)
var/platform_distance = get_dist(comparison, platform_to_sort)
if(platform_distance < winner_distance)
winner = platform_to_sort
winner_distance = platform_distance
if(allow_multiple_answers)
tied_winners = list(winner)
else if(platform_distance == winner_distance && allow_multiple_answers)
tied_winners += platform_to_sort
if(allow_multiple_answers)
return tied_winners
return winner
///returns all industrial_lifts associated with this tram on the given z level or given atoms z level
/datum/lift_master/proc/get_platforms_on_level(atom/atom_reference_OR_z_level_number)
var/z = atom_reference_OR_z_level_number
if(isatom(atom_reference_OR_z_level_number))
z = atom_reference_OR_z_level_number.z
if(!isnum(z) || z < 0 || z > world.maxz)
return null
var/list/platforms_in_z = list()
for(var/obj/structure/industrial_lift/lift_to_check as anything in lift_platforms)
if(lift_to_check.z)
platforms_in_z += lift_to_check
return platforms_in_z
/**
* Moves the lift UP or DOWN, this is what users invoke with their hand.
* This is a SAFE proc, ensuring every part of the lift moves SANELY.
* It also locks controls for the (miniscule) duration of the movement, so the elevator cannot be broken by spamming.
* Arguments:
* going - UP or DOWN directions, where the lift should go. Keep in mind by this point checks of whether it should go up or down have already been done.
* user - Whomever made the lift movement.
*/
/datum/lift_master/proc/MoveLift(going, mob/user)
set_controls(LIFT_PLATFORM_LOCKED)
//lift_platforms are sorted in order of lowest z to highest z, so going upwards we need to move them in reverse order to not collide
if(going == UP)
var/obj/structure/industrial_lift/platform_to_move
var/current_index = length(lift_platforms)
while(current_index > 0)
platform_to_move = lift_platforms[current_index]
current_index--
platform_to_move.travel(going)
else if(going == DOWN)
for(var/obj/structure/industrial_lift/lift_platform as anything in lift_platforms)
lift_platform.travel(going)
set_controls(LIFT_PLATFORM_UNLOCKED)
/**
* Moves the lift, this is what users invoke with their hand.
* This is a SAFE proc, ensuring every part of the lift moves SANELY.
* It also locks controls for the (miniscule) duration of the movement, so the elevator cannot be broken by spamming.
*/
/datum/lift_master/proc/MoveLiftHorizontal(going)
set_controls(LIFT_PLATFORM_LOCKED)
if(multitile_platform)
for(var/obj/structure/industrial_lift/platform_to_move as anything in lift_platforms)
platform_to_move.travel(going)
set_controls(LIFT_PLATFORM_UNLOCKED)
return
var/max_x = 0
var/max_y = 0
var/max_z = 0
var/min_x = world.maxx
var/min_y = world.maxy
var/min_z = world.maxz
for(var/obj/structure/industrial_lift/lift_platform as anything in lift_platforms)
max_z = max(max_z, lift_platform.z)
min_z = min(min_z, lift_platform.z)
min_x = min(min_x, lift_platform.x)
max_x = max(max_x, lift_platform.x)
//this assumes that all z levels have identical horizontal bounding boxes
//but if youre still using a non multitile tram platform at this point
//then its your own problem. it wont runtime it will jsut be slower than it needs to be if this assumption isnt
//the case
min_y = min(min_y, lift_platform.y)
max_y = max(max_y, lift_platform.y)
for(var/z in min_z to max_z)
//This must be safe way to border tile to tile move of bordered platforms, that excludes platform overlapping.
if(going & WEST)
//Go along the X axis from min to max, from left to right
for(var/x in min_x to max_x)
if(going & NORTH)
//Go along the Y axis from max to min, from up to down
for(var/y in max_y to min_y step -1)
var/obj/structure/industrial_lift/lift_platform = locate(/obj/structure/industrial_lift, locate(x, y, z))
lift_platform?.travel(going)
else if(going & SOUTH)
//Go along the Y axis from min to max, from down to up
for(var/y in min_y to max_y)
var/obj/structure/industrial_lift/lift_platform = locate(/obj/structure/industrial_lift, locate(x, y, z))
lift_platform?.travel(going)
else
for(var/y in min_y to max_y)
var/obj/structure/industrial_lift/lift_platform = locate(/obj/structure/industrial_lift, locate(x, y, z))
lift_platform?.travel(going)
else
//Go along the X axis from max to min, from right to left
for(var/x in max_x to min_x step -1)
if(going & NORTH)
//Go along the Y axis from max to min, from up to down
for(var/y in max_y to min_y step -1)
var/obj/structure/industrial_lift/lift_platform = locate(/obj/structure/industrial_lift, locate(x, y, z))
lift_platform?.travel(going)
else if (going & SOUTH)
for(var/y in min_y to max_y)
var/obj/structure/industrial_lift/lift_platform = locate(/obj/structure/industrial_lift, locate(x, y, z))
lift_platform?.travel(going)
else
//Go along the Y axis from min to max, from down to up
for(var/y in min_y to max_y)
var/obj/structure/industrial_lift/lift_platform = locate(/obj/structure/industrial_lift, locate(x, y, z))
lift_platform?.travel(going)
set_controls(LIFT_PLATFORM_UNLOCKED)
///Check destination turfs
/datum/lift_master/proc/Check_lift_move(check_dir)
for(var/obj/structure/industrial_lift/lift_platform as anything in lift_platforms)
for(var/turf/bound_turf in lift_platform.locs)
var/turf/T = get_step_multiz(lift_platform, check_dir)
if(!T)//the edges of multi-z maps
return FALSE
if(check_dir == UP && !istype(T, /turf/open/openspace)) // We don't want to go through the ceiling!
return FALSE
if(check_dir == DOWN && !istype(get_turf(lift_platform), /turf/open/openspace)) // No going through the floor!
return FALSE
return TRUE
/**
* Sets all lift parts's controls_locked variable. Used to prevent moving mid movement, or cooldowns.
*/
/datum/lift_master/proc/set_controls(state)
controls_locked = state
/**
* resets the contents of all platforms to their original state in case someone put a bunch of shit onto the tram.
* intended to be called by admins. passes all arguments to reset_contents() for each of our platforms.
*
* Arguments:
* * consider_anything_past - number. if > 0 our platforms will only handle foreign contents that exceed this number in each of their locs
* * foreign_objects - bool. if true our platforms will consider /atom/movable's that arent mobs as part of foreign contents
* * foreign_non_player_mobs - bool. if true our platforms consider mobs that dont have a mind to be foreign
* * consider_player_mobs - bool. if true our platforms consider player mobs to be foreign. only works if foreign_non_player_mobs is true as well
*/
/datum/lift_master/proc/reset_lift_contents(consider_anything_past = 0, foreign_objects = TRUE, foreign_non_player_mobs = TRUE, consider_player_mobs = FALSE)
for(var/obj/structure/industrial_lift/lift_to_reset in lift_platforms)
lift_to_reset.reset_contents(consider_anything_past, foreign_objects, foreign_non_player_mobs, consider_player_mobs)
return TRUE

View File

@@ -6,12 +6,14 @@
circuit = /obj/item/circuitboard/computer/tram_controls
flags_1 = NODECONSTRUCT_1 | SUPERMATTER_IGNORES_1
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
light_color = LIGHT_COLOR_GREEN
///The ID of the tram we control
var/tram_id = "tram_station"
light_range = 0 //we dont want to spam SSlighting with source updates every movement
///Weakref to the tram piece we control
var/datum/weakref/tram_ref
var/specific_lift_id = MAIN_STATION_TRAM
/obj/machinery/computer/tram_controls/Initialize(mapload, obj/item/circuitboard/C)
. = ..()
AddComponent(/datum/component/usb_port, list(/obj/item/circuit_component/tram_controls))
@@ -19,7 +21,6 @@
/obj/machinery/computer/tram_controls/LateInitialize()
. = ..()
//find the tram, late so the tram is all... set up so when this is called? i'm seriously stupid and 90% of what i do consists of barely educated guessing :)
find_tram()
/**
@@ -28,19 +29,17 @@
* Locates tram parts in the lift global list after everything is done.
*/
/obj/machinery/computer/tram_controls/proc/find_tram()
for(var/obj/structure/industrial_lift/tram/central/tram as anything in GLOB.central_trams)
if(tram.tram_id != tram_id)
continue
tram_ref = WEAKREF(tram)
break
for(var/datum/lift_master/lift as anything in GLOB.active_lifts_by_type[TRAM_LIFT_ID])
if(lift.specific_lift_id == specific_lift_id)
tram_ref = WEAKREF(lift)
/obj/machinery/computer/tram_controls/ui_state(mob/user)
return GLOB.not_incapacitated_state
/obj/machinery/computer/tram_controls/ui_status(mob/user,/datum/tgui/ui)
var/obj/structure/industrial_lift/tram/central/tram_part = tram_ref?.resolve()
var/datum/lift_master/tram/tram = tram_ref?.resolve()
if(tram_part?.travelling)
if(tram?.travelling)
return UI_CLOSE
if(!in_range(user, src) && !isobserver(user))
return UI_CLOSE
@@ -54,11 +53,11 @@
ui.open()
/obj/machinery/computer/tram_controls/ui_data(mob/user)
var/obj/structure/industrial_lift/tram/central/tram_part = tram_ref?.resolve()
var/datum/lift_master/tram/tram_lift = tram_ref?.resolve()
var/list/data = list()
data["moving"] = tram_part?.travelling
data["broken"] = tram_part ? FALSE : TRUE
var/obj/effect/landmark/tram/current_loc = tram_part?.from_where
data["moving"] = tram_lift?.travelling
data["broken"] = tram_lift ? FALSE : TRUE
var/obj/effect/landmark/tram/current_loc = tram_lift?.from_where
if(current_loc)
data["tram_location"] = current_loc.name
return data
@@ -77,9 +76,7 @@
*/
/obj/machinery/computer/tram_controls/proc/get_destinations()
. = list()
for(var/obj/effect/landmark/tram/destination as anything in GLOB.tram_landmarks)
if(destination.tram_id != tram_id)
continue
for(var/obj/effect/landmark/tram/destination as anything in GLOB.tram_landmarks[specific_lift_id])
var/list/this_destination = list()
this_destination["name"] = destination.name
this_destination["dest_icons"] = destination.tgui_icons
@@ -94,9 +91,7 @@
switch (action)
if ("send")
var/obj/effect/landmark/tram/to_where
for (var/obj/effect/landmark/tram/destination as anything in GLOB.tram_landmarks)
if(destination.tram_id != tram_id)
continue
for (var/obj/effect/landmark/tram/destination as anything in GLOB.tram_landmarks[specific_lift_id])
if(destination.destination_id == params["destination"])
to_where = destination
break
@@ -108,14 +103,13 @@
/// Attempts to sends the tram to the given destination
/obj/machinery/computer/tram_controls/proc/try_send_tram(obj/effect/landmark/tram/to_where)
var/obj/structure/industrial_lift/tram/central/tram_part = tram_ref?.resolve()
var/datum/lift_master/tram/tram_part = tram_ref?.resolve()
if(!tram_part)
return FALSE
if(tram_part.travelling)
return FALSE
if(tram_part.controls_locked) // someone else started
if(tram_part.controls_locked) // someone else started already
return FALSE
tram_part.tram_travel(to_where)
visible_message("The tram has been called to [to_where.name]")
return TRUE
/obj/item/circuit_component/tram_controls
@@ -147,12 +141,12 @@
. = ..()
if (istype(shell, /obj/machinery/computer/tram_controls))
computer = shell
var/obj/structure/industrial_lift/tram/central/tram_part = computer.tram_ref?.resolve()
var/datum/lift_master/tram/tram_part = computer.tram_ref?.resolve()
RegisterSignal(tram_part, COMSIG_TRAM_SET_TRAVELLING, .proc/on_tram_set_travelling)
RegisterSignal(tram_part, COMSIG_TRAM_TRAVEL, .proc/on_tram_travel)
/obj/item/circuit_component/tram_controls/unregister_usb_parent(atom/movable/shell)
var/obj/structure/industrial_lift/tram/central/tram_part = computer.tram_ref?.resolve()
var/datum/lift_master/tram/tram_part = computer.tram_ref?.resolve()
computer = null
UnregisterSignal(tram_part, list(COMSIG_TRAM_SET_TRAVELLING, COMSIG_TRAM_TRAVEL))
return ..()
@@ -168,9 +162,7 @@
return
var/destination
for(var/obj/effect/landmark/tram/possible_destination as anything in GLOB.tram_landmarks)
if(possible_destination.tram_id != computer.tram_id)
continue
for(var/obj/effect/landmark/tram/possible_destination as anything in GLOB.tram_landmarks[computer.specific_lift_id])
if(possible_destination.name == new_destination.value)
destination = possible_destination
break

View File

@@ -0,0 +1,47 @@
GLOBAL_LIST_EMPTY(tram_landmarks)
/obj/effect/landmark/tram
name = "tram destination" //the tram buttons will mention this.
icon_state = "tram"
///the id of the tram we're linked to.
var/specific_lift_id = MAIN_STATION_TRAM
/// The ID of that particular destination.
var/destination_id
/// Icons for the tgui console to list out for what is at this location
var/list/tgui_icons = list()
/obj/effect/landmark/tram/Initialize(mapload)
. = ..()
LAZYADDASSOCLIST(GLOB.tram_landmarks, specific_lift_id, src)
/obj/effect/landmark/tram/Destroy()
LAZYREMOVEASSOC(GLOB.tram_landmarks, specific_lift_id, src)
return ..()
/obj/effect/landmark/tram/left_part
name = "West Wing"
destination_id = "left_part"
tgui_icons = list("Arrivals" = "plane-arrival", "Command" = "bullhorn", "Security" = "gavel")
/obj/effect/landmark/tram/middle_part
name = "Central Wing"
destination_id = "middle_part"
tgui_icons = list("Service" = "cocktail", "Medical" = "plus", "Engineering" = "wrench")
/obj/effect/landmark/tram/right_part
name = "East Wing"
destination_id = "right_part"
tgui_icons = list("Departures" = "plane-departure", "Cargo" = "box", "Science" = "flask")
/**
* lift_id landmarks. used to map in specific_lift_id to trams. when the trams lift_master encounters one on a trams tile
* it sets its specific_lift_id to that landmark. allows you to have multiple trams and multiple controls linking to their specific tram
*/
/obj/effect/landmark/lift_id
name = "lift id setter"
icon_state = "lift_id"
///what specific id we give to the tram we're placed on, should explicitely set this if its a subtype, or weird things might happen
var/specific_lift_id = MAIN_STATION_TRAM

View File

@@ -0,0 +1,174 @@
/datum/lift_master/tram
///whether this tram is traveling across vertical and/or horizontal axis for some distance. not all lifts use this
var/travelling = FALSE
///if we're travelling, what direction are we going
var/travel_direction = NONE
///if we're travelling, how far do we have to go
var/travel_distance = 0
///multiplier on how much damage/force the tram imparts on things it hits
var/collision_lethality = 1
/// reference to the destination landmark we consider ourselves "at". since we potentially span multiple z levels we dont actually
/// know where on us this platform is. as long as we know THAT its on us we can just move the distance and direction between this
/// and the destination landmark.
var/obj/effect/landmark/tram/from_where
///decisecond delay between horizontal movement. cannot make the tram move faster than 1 movement per world.tick_lag.
///this var is poorly named its actually horizontal movement delay but whatever.
var/horizontal_speed = 0.5
///version of horizontal_speed that gets set in init and is considered our base speed if our lift gets slowed down
var/base_horizontal_speed = 0.5
///the world.time we should next move at. in case our speed is set to less than 1 movement per tick
var/next_move = INFINITY
///whether we have been slowed down automatically
var/slowed_down = FALSE
///how many times we moved while costing more than SStramprocess.max_time milliseconds per movement.
///if this exceeds SStramprocess.max_exceeding_moves
var/times_exceeded = 0
///how many times we moved while costing less than 0.5 * SStramprocess.max_time milliseconds per movement
var/times_below = 0
/datum/lift_master/tram/New(obj/structure/industrial_lift/tram/lift_platform)
. = ..()
horizontal_speed = lift_platform.horizontal_speed
base_horizontal_speed = lift_platform.horizontal_speed
check_starting_landmark()
/datum/lift_master/tram/vv_edit_var(var_name, var_value)
. = ..()
if(var_name == "base_horizontal_speed")
horizontal_speed = max(horizontal_speed, base_horizontal_speed)
/datum/lift_master/tram/add_lift_platforms(obj/structure/industrial_lift/new_lift_platform)
. = ..()
RegisterSignal(new_lift_platform, COMSIG_MOVABLE_BUMP, .proc/gracefully_break)
/datum/lift_master/tram/check_for_landmarks(obj/structure/industrial_lift/tram/new_lift_platform)
. = ..()
for(var/turf/platform_loc as anything in new_lift_platform.locs)
var/obj/effect/landmark/tram/initial_destination = locate() in platform_loc
if(initial_destination)
from_where = initial_destination
/datum/lift_master/tram/proc/check_starting_landmark()
if(!from_where)
CRASH("a tram lift_master was initialized without any tram landmark to give it direction!")
SStramprocess.can_fire = TRUE
return TRUE
/**
* Signal for when the tram runs into a field of which it cannot go through.
* Stops the train's travel fully, sends a message, and destroys the train.
* Arguments:
* bumped_atom - The atom this tram bumped into
*/
/datum/lift_master/tram/proc/gracefully_break(atom/bumped_atom)
SIGNAL_HANDLER
if(istype(bumped_atom, /obj/machinery/field))
return
travel_distance = 0
bumped_atom.visible_message(span_userdanger("[src] crashes into the field violently!"))
for(var/obj/structure/industrial_lift/tram/tram_part as anything in lift_platforms)
tram_part.set_travelling(FALSE)
if(prob(15) || locate(/mob/living) in tram_part.lift_load) //always go boom on people on the track
explosion(tram_part, devastation_range = rand(0, 1), heavy_impact_range = 2, light_impact_range = 3) //50% chance of gib
qdel(tram_part)
/**
* Handles moving the tram
*
* Tells the individual tram parts where to actually go and has an extra safety check
* incase multiple inputs get through, preventing conflicting directions and the tram
* literally ripping itself apart. all of the actual movement is handled by SStramprocess
*/
/datum/lift_master/tram/proc/tram_travel(obj/effect/landmark/tram/to_where)
if(to_where == from_where)
return
travel_direction = get_dir(from_where, to_where)
travel_distance = get_dist(from_where, to_where)
from_where = to_where
set_travelling(TRUE)
set_controls(LIFT_PLATFORM_LOCKED)
SEND_SIGNAL(src, COMSIG_TRAM_TRAVEL, from_where, to_where)
for(var/obj/structure/industrial_lift/tram/tram_part as anything in lift_platforms) //only thing everyone needs to know is the new location.
if(tram_part.travelling) //wee woo wee woo there was a double action queued. damn multi tile structs
return //we don't care to undo locked controls, though, as that will resolve itself
tram_part.glide_size_override = DELAY_TO_GLIDE_SIZE(horizontal_speed)
tram_part.set_travelling(TRUE)
next_move = world.time + horizontal_speed
START_PROCESSING(SStramprocess, src)
/datum/lift_master/tram/process(delta_time)
if(!travel_distance)
addtimer(CALLBACK(src, .proc/unlock_controls), 3 SECONDS)
return PROCESS_KILL
else if(world.time >= next_move)
var/start_time = TICK_USAGE
travel_distance--
MoveLiftHorizontal(travel_direction)
var/duration = TICK_USAGE_TO_MS(start_time)
if(slowed_down)
if(duration <= (SStramprocess.max_time / 2))
times_below++
else
times_below = 0
if(times_below >= SStramprocess.max_cheap_moves)
horizontal_speed = base_horizontal_speed
slowed_down = FALSE
times_below = 0
else if(duration > SStramprocess.max_time)
times_exceeded++
if(times_exceeded >= SStramprocess.max_exceeding_moves)
message_admins("The tram at [ADMIN_JMP(lift_platforms[1])] is taking more than [SStramprocess.max_time] milliseconds per movement, halving its movement speed. if this continues to be a problem you can call reset_lift_contents() on the trams lift_master_datum to reset it to its original state and clear added objects")
horizontal_speed = base_horizontal_speed * 2 //halves its speed
slowed_down = TRUE
times_exceeded = 0
else
times_exceeded = max(times_exceeded - 1, 0)
next_move = world.time + horizontal_speed
/**
* Handles unlocking the tram controls for use after moving
*
* More safety checks to make sure the tram has actually docked properly
* at a location before users are allowed to interact with the tram console again.
* Tram finds its location at this point before fully unlocking controls to the user.
*/
/datum/lift_master/tram/proc/unlock_controls()
set_travelling(FALSE)
set_controls(LIFT_PLATFORM_UNLOCKED)
for(var/obj/structure/industrial_lift/tram/tram_part as anything in lift_platforms) //only thing everyone needs to know is the new location.
tram_part.set_travelling(FALSE)
/datum/lift_master/tram/proc/set_travelling(new_travelling)
if(travelling == new_travelling)
return
travelling = new_travelling
SEND_SIGNAL(src, COMSIG_TRAM_SET_TRAVELLING, travelling)

View File

@@ -0,0 +1,39 @@
/**
* the tram has a few objects mapped onto it at roundstart, by default many of those objects have unwanted properties
* for example grilles and windows have the atmos_sensitive element applied to them, which makes them register to
* themselves moving to re register signals onto the turf via connect_loc. this is bad and dumb since it makes the tram
* more expensive to move.
*
* if you map something on to the tram, make SURE if possible that it doesnt have anythign reacting to its own movement
* it will make the tram more expensive to move and we dont want that because we dont want to return to the days where
* the tram took a third of the tick per movement when its just carrying its default mapped in objects
*/
/obj/structure/grille/tram/Initialize(mapload)
. = ..()
RemoveElement(/datum/element/atmos_sensitive, mapload)
//atmos_sensitive applies connect_loc which 1. reacts to movement in order to 2. unregister and register signals to
//the old and new locs. we dont want that, pretend these grilles and windows are plastic or something idk
/obj/structure/window/reinforced/shuttle/tram/Initialize(mapload, direct)
. = ..()
RemoveElement(/datum/element/atmos_sensitive, mapload)
/obj/structure/shuttle/engine/propulsion/in_wall/tram
//if this has opacity, then every movement of the tram causes lighting updates
//DO NOT put something on the tram roundstart that has opacity, it WILL overload SSlighting
opacity = FALSE
/obj/machinery/door/window/left/tram
/obj/machinery/door/window/right/tram
/obj/machinery/door/window/left/tram/Initialize(mapload, set_dir, unres_sides)
. = ..()
RemoveElement(/datum/element/atmos_sensitive, mapload)
/obj/machinery/door/window/right/tram/Initialize(mapload, set_dir, unres_sides)
. = ..()
RemoveElement(/datum/element/atmos_sensitive, mapload)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/door/window/left/tram, 0)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/door/window/right/tram, 0)

View File

@@ -10,7 +10,7 @@
base_icon_state = "wall"
layer = LOW_OBJ_LAYER
density = TRUE
opacity = TRUE
opacity = FALSE
max_integrity = 100
smoothing_flags = SMOOTH_BITMASK
smoothing_groups = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_WALLS)

View File

@@ -87,5 +87,5 @@
L.apply_status_effect(/datum/status_effect/good_music)
if(!(M?.client?.prefs?.toggles & SOUND_INSTRUMENTS))
continue
M.playsound_local(source, null, volume * using_instrument.volume_multiplier, S = music_played)
M.playsound_local(source, null, volume * using_instrument.volume_multiplier, sound_to_use = music_played)
// Could do environment and echo later but not for now

View File

@@ -25,8 +25,8 @@
#undef NONSENSICAL_VALUE
// Will update the light (duh).
// Creates or destroys it if needed, makes it update values, makes sure it's got the correct source turf...
/// Will update the light (duh).
/// Creates or destroys it if needed, makes it update values, makes sure it's got the correct source turf...
/atom/proc/update_light()
set waitfor = FALSE
if (QDELETED(src))
@@ -80,13 +80,6 @@
return
recalculate_directional_opacity()
/atom/movable/Moved(atom/OldLoc, Dir)
. = ..()
for (var/datum/light_source/light as anything in light_sources) // Cycle through the light sources on this atom and tell them to update.
light.source_atom.update_light()
/atom/proc/flash_lighting_fx(_range = FLASH_LIGHT_RANGE, _power = FLASH_LIGHT_POWER, _color = COLOR_WHITE, _duration = FLASH_LIGHT_DURATION)
return

View File

@@ -40,10 +40,10 @@
/datum/light_source/New(atom/owner, atom/top)
source_atom = owner // Set our new owner.
LAZYADD(source_atom.light_sources, src)
add_to_light_sources(source_atom.light_sources)
top_atom = top
if (top_atom != source_atom)
LAZYADD(top_atom.light_sources, src)
add_to_light_sources(top_atom.light_sources)
source_turf = top_atom
pixel_turf = get_turf_pixel(top_atom) || source_turf
@@ -59,10 +59,10 @@
/datum/light_source/Destroy(force)
remove_lum()
if (source_atom)
LAZYREMOVE(source_atom.light_sources, src)
remove_from_light_sources(source_atom.light_sources)
if (top_atom)
LAZYREMOVE(top_atom.light_sources, src)
remove_from_light_sources(top_atom.light_sources)
if (needs_update)
SSlighting.sources_queue -= src
@@ -74,6 +74,35 @@
return ..()
///add this light source to new_atom_host's light_sources list. updating movement registrations as needed
/datum/light_source/proc/add_to_light_sources(atom/new_atom_host)
if(QDELETED(new_atom_host))
return FALSE
LAZYADD(new_atom_host.light_sources, src)
if(ismovable(new_atom_host) && new_atom_host == source_atom)
RegisterSignal(new_atom_host, COMSIG_MOVABLE_MOVED, .proc/update_host_lights)
return TRUE
///remove this light source from old_atom_host's light_sources list, unsetting movement registrations
/datum/light_source/proc/remove_from_light_sources(atom/old_atom_host)
if(QDELETED(old_atom_host))
return FALSE
LAZYREMOVE(old_atom_host.light_sources, src)
if(ismovable(old_atom_host) && old_atom_host == source_atom)
UnregisterSignal(old_atom_host, COMSIG_MOVABLE_MOVED)
return TRUE
///signal handler for when our host atom moves and we need to update our effects
/datum/light_source/proc/update_host_lights(atom/movable/host)
SIGNAL_HANDLER
if(QDELETED(host))
return
host.update_light()
// Yes this doesn't align correctly on anything other than 4 width tabs.
// If you want it to go switch everybody to elastic tab stops.
// Actually that'd be great if you could!
@@ -84,17 +113,17 @@
needs_update = level; \
// This proc will cause the light source to update the top atom, and add itself to the update queue.
/// This proc will cause the light source to update the top atom, and add itself to the update queue.
/datum/light_source/proc/update(atom/new_top_atom)
// This top atom is different.
if (new_top_atom && new_top_atom != top_atom)
if(top_atom != source_atom && top_atom.light_sources) // Remove ourselves from the light sources of that top atom.
LAZYREMOVE(top_atom.light_sources, src)
remove_from_light_sources(top_atom.light_sources)
top_atom = new_top_atom
if (top_atom != source_atom)
LAZYADD(top_atom.light_sources, src) // Add ourselves to the light sources of our new top atom.
add_to_light_sources(top_atom.light_sources)
EFFECT_UPDATE(LIGHTING_CHECK_UPDATE)

View File

@@ -41,8 +41,9 @@
if(hit_object.resistance_flags & FREEZE_PROOF)
hit_object.visible_message(span_warning("[hit_object] is freeze-proof!"))
return
if(!(hit_object.obj_flags & FROZEN))
hit_object.make_frozen_visual()
if(HAS_TRAIT(hit_object, TRAIT_FROZEN))
return
hit_object.AddElement(/datum/element/frozen)
else if(isliving(hit_atom))
var/mob/living/hit_mob = hit_atom
SSmove_manager.stop_looping(hit_mob) //stops them mid pathing even if they're stunimmune

View File

@@ -187,7 +187,7 @@ GLOBAL_DATUM(necropolis_gate, /obj/structure/necropolis_gate/legion_gate)
for(var/mob/M in GLOB.player_list)
if(M.z == z)
to_chat(M, span_userdanger("Discordant whispers flood your mind in a thousand voices. Each one speaks your name, over and over. Something horrible has been released."))
M.playsound_local(T, null, 100, FALSE, 0, FALSE, pressure_affected = FALSE, S = legion_sound)
M.playsound_local(T, null, 100, FALSE, 0, FALSE, pressure_affected = FALSE, sound_to_use = legion_sound)
flash_color(M, flash_color = "#FF0000", flash_time = 50)
var/mutable_appearance/release_overlay = mutable_appearance('icons/effects/effects.dmi', "legiondoor")
notify_ghosts("Legion has been released in the [get_area(src)]!", source = src, alert_overlay = release_overlay, action = NOTIFY_JUMP, flashwindow = FALSE)

View File

@@ -494,19 +494,22 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999))
else
to_chat(user, "No vacated rooms.")
/obj/effect/landmark/lift_id/hilbert
specific_lift_id = HILBERT_TRAM
/obj/effect/landmark/tram/left_part/hilbert
specific_lift_id = HILBERT_TRAM
destination_id = "left_part_hilbert"
tram_id = "tram_hilbert"
tgui_icons = list("Reception" = "briefcase", "Botany" = "leaf", "Chemistry" = "flask")
/obj/effect/landmark/tram/middle_part/hilbert
specific_lift_id = HILBERT_TRAM
destination_id = "middle_part_hilbert"
tram_id = "tram_hilbert"
tgui_icons = list("Processing" = "cogs", "Xenobiology" = "paw")
/obj/effect/landmark/tram/right_part/hilbert
specific_lift_id = HILBERT_TRAM
destination_id = "right_part_hilbert"
tram_id = "tram_hilbert"
tgui_icons = list("Ordnance" = "bullseye", "Office" = "user", "Dormitories" = "bed")
/obj/item/keycard/hilbert

View File

@@ -57,18 +57,19 @@
/// Attempt to get the turf below the provided one according to Z traits
/datum/controller/subsystem/mapping/proc/get_turf_below(turf/T)
if (!T)
if (!T || !initialized)
return
var/offset = level_trait(T.z, ZTRAIT_DOWN)
var/offset = multiz_levels[T.z]["[DOWN]"]
if (!offset)
return
return locate(T.x, T.y, T.z + offset)
return locate(T.x, T.y, T.z - offset)
/// Attempt to get the turf above the provided one according to Z traits
/datum/controller/subsystem/mapping/proc/get_turf_above(turf/T)
if (!T)
if (!T || !initialized)
return
var/offset = level_trait(T.z, ZTRAIT_UP)
var/offset = multiz_levels[T.z]["[UP]"]
if (!offset)
return
return locate(T.x, T.y, T.z + offset)

View File

@@ -26,6 +26,8 @@
// TODO: sleep here if the Z level needs to be cleared
var/datum/space_level/S = new z_type(new_z, name, traits)
z_list += S
generate_linkages_for_z_level(new_z)
calculate_z_level_gravity(new_z)
adding_new_zlevel = FALSE
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NEW_Z, S)
return S

View File

@@ -221,7 +221,7 @@ GLOBAL_LIST_INIT(meteorsC, list(/obj/effect/meteor/dust=1)) //for space dust eve
continue
var/dist = get_dist(M.loc, src.loc)
shake_camera(M, dist > 20 ? 2 : 4, dist > 20 ? 1 : 3)
M.playsound_local(src.loc, null, 50, 1, random_frequency, 10, S = meteor_sound)
M.playsound_local(src.loc, null, 50, 1, random_frequency, 10, sound_to_use = meteor_sound)
///////////////////////
//Meteor types

View File

@@ -25,11 +25,11 @@
AddComponent(/datum/component/bloodysoles/feet)
AddElement(/datum/element/ridable, /datum/component/riding/creature/human)
AddElement(/datum/element/strippable, GLOB.strippable_human_items, /mob/living/carbon/human/.proc/should_strip)
GLOB.human_list += src
var/static/list/loc_connections = list(
COMSIG_ATOM_ENTERED = .proc/on_entered,
)
AddElement(/datum/element/connect_loc, loc_connections)
GLOB.human_list += src
/mob/living/carbon/human/proc/setup_human_dna()
//initialize dna. for spawned humans; overwritten by other code
@@ -95,11 +95,6 @@
. += "Chemical Storage: [changeling.chem_charges]/[changeling.total_chem_storage]"
. += "Absorbed DNA: [changeling.absorbed_count]"
// called when something steps onto a human
/mob/living/carbon/human/proc/on_entered(datum/source, atom/movable/AM)
SIGNAL_HANDLER
spreadFire(AM)
/mob/living/carbon/human/reset_perspective(atom/new_eye, force_reset = FALSE)
if(dna?.species?.prevent_perspective_change && !force_reset) // This is in case a species needs to prevent perspective changes in certain cases, like Dullahans preventing perspective changes when they're looking through their head.
update_fullscreen()
@@ -371,6 +366,10 @@
..() //end of this massive fucking chain. TODO: make the hud chain not spooky. - Yeah, great job doing that.
//called when something steps onto a human
/mob/living/carbon/human/proc/on_entered(datum/source, atom/movable/AM)
SIGNAL_HANDLER
spreadFire(AM)
/mob/living/carbon/human/proc/canUseHUD()
return (mobility_flags & MOBILITY_USE)

View File

@@ -879,7 +879,7 @@
var/mob/living/L = pulledby
L.set_pull_offsets(src, pulledby.grab_state)
if(active_storage && !(CanReach(active_storage.parent,view_only = TRUE)))
if(active_storage && !((active_storage.parent in important_recursive_contents?[RECURSIVE_CONTENTS_ACTIVE_STORAGE]) || CanReach(active_storage.parent,view_only = TRUE)))
active_storage.close(src)
if(body_position == LYING_DOWN && !buckled && prob(getBruteLoss()*200/maxHealth))
@@ -1404,6 +1404,7 @@ GLOBAL_LIST_EMPTY(fire_appearances)
var/datum/status_effect/fire_handler/fire_stacks/fire_status = has_status_effect(/datum/status_effect/fire_handler/fire_stacks)
if(!fire_status || !fire_status.on_fire)
return
remove_status_effect(/datum/status_effect/fire_handler/fire_stacks)
/**

View File

@@ -209,3 +209,5 @@
var/native_fov = FOV_90_DEGREES
/// Lazy list of FOV traits that will apply a FOV view when handled.
var/list/fov_traits
///what multiplicative slowdown we get from turfs currently.
var/current_turf_slowdown = 0

View File

@@ -88,11 +88,20 @@
UNSETEMPTY(fov_traits)
update_fov()
//did you know you can subtype /image and /mutable_appearance?
/image/fov_image
icon = 'icons/effects/fov/fov_effects.dmi'
layer = FOV_EFFECTS_LAYER
appearance_flags = RESET_COLOR | RESET_TRANSFORM
plane = FULLSCREEN_PLANE
/// Plays a visual effect representing a sound cue for people with vision obstructed by FOV or blindness
/proc/play_fov_effect(atom/center, range, icon_state, dir = SOUTH, ignore_self = FALSE, angle = 0)
/proc/play_fov_effect(atom/center, range, icon_state, dir = SOUTH, ignore_self = FALSE, angle = 0, list/override_list)
var/turf/anchor_point = get_turf(center)
var/image/fov_image
for(var/mob/living/living_mob in get_hearers_in_view(range, center))
var/image/fov_image/fov_image
var/list/clients_shown
for(var/mob/living/living_mob in override_list || get_hearers_in_view(range, center))
var/client/mob_client = living_mob.client
if(!mob_client)
continue
@@ -101,18 +110,22 @@
if(living_mob.in_fov(center, ignore_self))
continue
if(!fov_image) //Make the image once we found one recipient to receive it
fov_image = image(icon = 'icons/effects/fov/fov_effects.dmi', icon_state = icon_state, loc = anchor_point)
fov_image.plane = FULLSCREEN_PLANE
fov_image.layer = FOV_EFFECTS_LAYER
fov_image = new()
fov_image.loc = anchor_point
fov_image.icon_state = icon_state
fov_image.dir = dir
fov_image.appearance_flags = RESET_COLOR | RESET_TRANSFORM
if(angle)
var/matrix/matrix = new
matrix.Turn(angle)
fov_image.transform = matrix
fov_image.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
LAZYADD(clients_shown, mob_client)
mob_client.images += fov_image
addtimer(CALLBACK(GLOBAL_PROC, .proc/remove_image_from_client, fov_image, mob_client), 30)
//when added as an image mutable_appearances act identically. we just make it an MA becuase theyre faster to change appearance
if(clients_shown)
addtimer(CALLBACK(GLOBAL_PROC, .proc/remove_images_from_clients, fov_image, clients_shown), 30)
/atom/movable/screen/fov_blocker
icon = 'icons/effects/fov/field_of_view.dmi'

View File

@@ -28,9 +28,12 @@
/mob/living/proc/update_turf_movespeed(turf/open/T)
if(isopenturf(T))
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/turf_slowdown, multiplicative_slowdown = T.slowdown)
else
if(T.slowdown != current_turf_slowdown)
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/turf_slowdown, multiplicative_slowdown = T.slowdown)
current_turf_slowdown = T.slowdown
else if(current_turf_slowdown)
remove_movespeed_modifier(/datum/movespeed_modifier/turf_slowdown)
current_turf_slowdown = 0
/mob/living/proc/update_pull_movespeed()

View File

@@ -38,6 +38,7 @@
if(observers?.len)
for(var/mob/dead/observe as anything in observers)
observe.reset_perspective(null)
qdel(hud_used)
QDEL_LIST(client_colours)
ghostize() //False, since we're deleting it currently

View File

@@ -241,3 +241,6 @@
var/thinking_indicator = FALSE
/// User is thinking in character. Used to revert to thinking state after stop_typing
var/thinking_IC = FALSE
///how much gravity is slowing us down
var/gravity_slowdown = 0

View File

@@ -70,9 +70,9 @@
next_move_dir_sub = 0
var/old_move_delay = move_delay
move_delay = world.time + world.tick_lag //this is here because Move() can now be called mutiple times per tick
if(!mob || !mob.loc)
if(!direct || !new_loc)
return FALSE
if(!new_loc || !direct)
if(!mob?.loc)
return FALSE
if(mob.notransform)
return FALSE //This is sota the goto stop mobs from moving var
@@ -118,7 +118,9 @@
//We are now going to move
var/add_delay = mob.cached_multiplicative_slowdown
mob.set_glide_size(DELAY_TO_GLIDE_SIZE(add_delay * ( (NSCOMPONENT(direct) && EWCOMPONENT(direct)) ? SQRT_2 : 1 ) )) // set it now in case of pulled objects
var/new_glide_size = DELAY_TO_GLIDE_SIZE(add_delay * ( (NSCOMPONENT(direct) && EWCOMPONENT(direct)) ? SQRT_2 : 1 ) )
if(mob.glide_size != new_glide_size)
mob.set_glide_size(new_glide_size) // set it now in case of pulled objects
//If the move was recent, count using old_move_delay
//We want fractional behavior and all
if(old_move_delay + world.tick_lag > world.time)
@@ -137,10 +139,15 @@
if((direct & (direct - 1)) && mob.loc == new_loc) //moved diagonally successfully
add_delay *= SQRT_2
var/after_glide = 0
if(visual_delay)
mob.set_glide_size(visual_delay)
after_glide = visual_delay
else
mob.set_glide_size(DELAY_TO_GLIDE_SIZE(add_delay))
after_glide = DELAY_TO_GLIDE_SIZE(add_delay)
if(after_glide != mob.glide_size)
mob.set_glide_size(after_glide)
move_delay += add_delay
if(.) // If mob is null here, we deserve the runtime
if(mob.throwing)
@@ -358,10 +365,12 @@
/// Update the gravity status of this mob
/mob/proc/update_gravity(has_gravity, override=FALSE)
var/speed_change = max(0, has_gravity - STANDARD_GRAVITY)
if(!speed_change)
if(!speed_change && gravity_slowdown)
remove_movespeed_modifier(/datum/movespeed_modifier/gravity)
else
gravity_slowdown = 0
else if(gravity_slowdown != speed_change)
add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/gravity, multiplicative_slowdown=speed_change)
gravity_slowdown = speed_change
//bodypart selection verbs - Cyberboss
//8: repeated presses toggles through head - eyes - mouth

View File

@@ -105,7 +105,7 @@
rave_screen = mod.wearer.add_client_colour(/datum/client_colour/rave)
rave_screen.update_colour(rainbow_order[rave_number])
if(selection)
SEND_SOUND(mod.wearer, sound(selection.song_path, volume = 50, channel = CHANNEL_JUKEBOX))
mod.wearer.playsound_local(get_turf(src), null, 50, channel = CHANNEL_JUKEBOX, sound_to_use = sound(selection.song_path), use_reverb = FALSE)
/obj/item/mod/module/visor/rave/on_deactivation(display_message = TRUE, deleting = FALSE)
. = ..()

View File

@@ -3,7 +3,8 @@
// Gravity Generator
//
GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding new gravity generators to the list, and keying it with the z level.
/// We will keep track of this by adding new gravity generators to the list, and keying it with the z level.
GLOBAL_LIST_EMPTY(gravity_generators)
#define POWER_IDLE 0
#define POWER_UP 1
@@ -146,7 +147,8 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
var/charge_count = 100
var/current_overlay = null
var/broken_state = 0
var/setting = 1 //Gravity value when on
///Gravity value when on
var/setting = 1
///Station generator that spawns with gravity turned off.
/obj/machinery/gravity_generator/main/station/off
@@ -407,7 +409,7 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
M.update_gravity(M.has_gravity())
if(M.client)
shake_camera(M, 15, 1)
M.playsound_local(T, null, 100, 1, 0.5, S = alert_sound)
M.playsound_local(T, null, 100, 1, 0.5, sound_to_use = alert_sound)
/obj/machinery/gravity_generator/main/proc/gravity_in_level()
var/turf/T = get_turf(src)
@@ -434,6 +436,7 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne
GLOB.gravity_generators["[z]"] |= src
else
GLOB.gravity_generators["[z]"] -= src
SSmapping.calculate_z_level_gravity(z)
/obj/machinery/gravity_generator/main/proc/change_setting(value)
if(value != setting)

View File

@@ -99,7 +99,7 @@ All ShuttleMove procs go here
/atom/movable/proc/beforeShuttleMove(turf/newT, rotation, move_mode, obj/docking_port/mobile/moving_dock)
return move_mode
// Called on atoms to move the atom to the new location
/// Called on atoms to move the atom to the new location
/atom/movable/proc/onShuttleMove(turf/newT, turf/oldT, list/movement_force, move_dir, obj/docking_port/stationary/old_dock, obj/docking_port/mobile/moving_dock)
if(newT == oldT) // In case of in place shuttle rotation shenanigans.
return

View File

@@ -39,7 +39,7 @@
///are we registered in SSshuttles?
var/registered = FALSE
///register to SSshuttles
///register to SSshuttles
/obj/docking_port/proc/register()
if(registered)
WARNING("docking_port registered multiple times")
@@ -47,7 +47,7 @@
registered = TRUE
return
///unregister from SSshuttles
///unregister from SSshuttles
/obj/docking_port/proc/unregister()
if(!registered)
WARNING("docking_port unregistered multiple times")
@@ -57,7 +57,7 @@
/obj/docking_port/proc/Check_id()
return
//these objects are indestructible
//these objects are indestructible
/obj/docking_port/Destroy(force)
// unless you assert that you know what you're doing. Horrible things
// may result.
@@ -68,7 +68,7 @@
return QDEL_HINT_LETMELIVE
/obj/docking_port/has_gravity(turf/T)
return FALSE
return TRUE
/obj/docking_port/take_damage()
return

View File

@@ -90,6 +90,8 @@
ignore += typesof(/obj/item/toy/cards/cardhand)
//Needs a holodeck area linked to it which is not guarenteed to exist and technically is supposed to have a 1:1 relationship with computer anyway.
ignore += typesof(/obj/machinery/computer/holodeck)
//runtimes if not paired with a landmark
ignore += typesof(/obj/structure/industrial_lift)
var/list/cached_contents = spawn_at.contents.Copy()
var/baseturf_count = length(spawn_at.baseturfs)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -90,6 +90,7 @@
#include "code\__DEFINES\icon_smoothing.dm"
#include "code\__DEFINES\id_cards.dm"
#include "code\__DEFINES\important_recursive_contents.dm"
#include "code\__DEFINES\industrial_lift.dm"
#include "code\__DEFINES\injection.dm"
#include "code\__DEFINES\instruments.dm"
#include "code\__DEFINES\interaction_flags.dm"
@@ -984,6 +985,7 @@
#include "code\datums\elements\firestacker.dm"
#include "code\datums\elements\footstep.dm"
#include "code\datums\elements\forced_gravity.dm"
#include "code\datums\elements\frozen.dm"
#include "code\datums\elements\haunted.dm"
#include "code\datums\elements\honkspam.dm"
#include "code\datums\elements\item_fov.dm"
@@ -1263,7 +1265,6 @@
#include "code\game\machinery\cell_charger.dm"
#include "code\game\machinery\civilian_bounties.dm"
#include "code\game\machinery\constructable_frame.dm"
#include "code\game\machinery\crossing_signal.dm"
#include "code\game\machinery\dance_machine.dm"
#include "code\game\machinery\defibrillator_mount.dm"
#include "code\game\machinery\deployable.dm"
@@ -1343,7 +1344,6 @@
#include "code\game\machinery\computer\station_alert.dm"
#include "code\game\machinery\computer\teleporter.dm"
#include "code\game\machinery\computer\terminal.dm"
#include "code\game\machinery\computer\tram_controls.dm"
#include "code\game\machinery\computer\warrant.dm"
#include "code\game\machinery\computer\arcade\arcade.dm"
#include "code\game\machinery\computer\arcade\orion.dm"
@@ -1782,7 +1782,6 @@
#include "code\game\objects\structures\headpike.dm"
#include "code\game\objects\structures\hivebot.dm"
#include "code\game\objects\structures\holosign.dm"
#include "code\game\objects\structures\industrial_lift.dm"
#include "code\game\objects\structures\janicart.dm"
#include "code\game\objects\structures\kitchen_spike.dm"
#include "code\game\objects\structures\ladders.dm"
@@ -1812,7 +1811,6 @@
#include "code\game\objects\structures\tank_dispenser.dm"
#include "code\game\objects\structures\tank_holder.dm"
#include "code\game\objects\structures\training_machine.dm"
#include "code\game\objects\structures\tram_walls.dm"
#include "code\game\objects\structures\traps.dm"
#include "code\game\objects\structures\votingbox.dm"
#include "code\game\objects\structures\watercloset.dm"
@@ -3023,6 +3021,14 @@
#include "code\modules\hydroponics\grown\weeds\kudzu.dm"
#include "code\modules\hydroponics\grown\weeds\nettle.dm"
#include "code\modules\hydroponics\grown\weeds\starthistle.dm"
#include "code\modules\industrial_lift\crossing_signal.dm"
#include "code\modules\industrial_lift\industrial_lift.dm"
#include "code\modules\industrial_lift\lift_master.dm"
#include "code\modules\industrial_lift\tram_controls.dm"
#include "code\modules\industrial_lift\tram_landmark.dm"
#include "code\modules\industrial_lift\tram_lift_master.dm"
#include "code\modules\industrial_lift\tram_override_objects.dm"
#include "code\modules\industrial_lift\tram_walls.dm"
#include "code\modules\instruments\items.dm"
#include "code\modules\instruments\piano_synth.dm"
#include "code\modules\instruments\stationary.dm"