Files
Bubberstation/code/game/atom/_atom.dm
LemonInTheDark 4b4e9dff1d Wallening [IDB IGNORE] [MDB IGNORE] (#85491)
## What's going on here

Kept you waitin huh!

This pr resprites most all walls, windows and other "wall adjacent"
things to a 3/4th perspective, technical term is "tall" walls (we are
very smart).

If you're trying to understand the technical details here, much of the
"rendering tech" is built off the idea of split-vis. Basically, split a
sprite up and render it on adjacent turfs, to prevent seeing "through"
walls/doors, and to support seeing "edges" without actually seeing the
atom itself.

Most of the rest of it is pipelining done to accommodate how icons are
cut.

## Path To Merge

Almost* all sprites and code is done at this point. 
There are some things missing both on and off the bounty list, but that
will be the case forever unless we force upstream (you guys) to stop
adding new shit that doesn't fit the style.
I plan on accepting and integrating prs to the current working repo
<https://github.com/wall-nerds/wallening> up until a merge, to make
contribution simpler and allow things like bounties to close out more
easily

This pr is quite bulky, even stripping away map changes it's maybe 7000
LOC (We have a few maps that were modified with UpdatePaths, I am also
tentatively pring our test map, for future use.)
This may inhibit proper review, although that is part of why I am
willing to make it despite my perfectionism. Apologies in advance.

Due to the perspective shift, a lot of mapping work is going to need to
be done at some point. This comes in varying levels of priority. Many
wallmounts are offset by hand, some are stuck in the wall/basically
cannot be placed on the east/west/north edges of walls (posters), some
just don't look great good in their current position.

Tests are currently a minor bit yorked, I thought it was more important
to get this up then to clean them fully.

## What does it look like?


![dreamseeker_hBsU6wCu91](https://github.com/user-attachments/assets/5392fa3b-60f6-40ea-876f-e686f25f996a)

![dreamseeker_CTiK0Je5iR](https://github.com/user-attachments/assets/1aee23bd-a5ec-4679-b094-d044401b7222)

![dreamseeker_HYkS1Q9GRq](https://github.com/user-attachments/assets/bad8844b-3179-4856-8684-f912e14e844a)

![dreamseeker_Pa18tgyKYp](https://github.com/user-attachments/assets/c2e1d222-9e5c-4500-8829-dd065428644a)

![dreamseeker_BfOBwS2mjH](https://github.com/user-attachments/assets/7dc51153-111d-4b17-93c3-8389daa6b60b)

![dreamseeker_iJazOumiMQ](https://github.com/user-attachments/assets/5837e203-3865-4f60-854e-62b4875c6b99)

## Credits

<details>
<summary>Historical Mumbojumbo</summary>

I am gonna do my best to document how this project came to be. I am
operating off third party info and half remembered details, so if I'm
wrong please yell at me.

This project started sometime in late 2020, as a product of Rohesie
trying to integrate and make easier work from Mojave Sun (A recently
defunct fallout server) with /tg/.

Mojave Sun (Apparently this was LITERALLY JUST infrared baron, that man
is insane) was working with tall walls, IE walls that are 48px tall
instead of the normal 32. This was I THINK done based off a technical
prototype from aao7 proving A it was possible and B it didn't look like
dogwater.

This alongside oranges begging the art team for 3/4th walls (he meant
TGMC style) lead to Rohesie bringing on contributors from general /tg/,
including actionninja who would eventually take over as technical lead
and Kryson, who would define /tg/'s version of the artstyle. Much of the
formative aspects of this project are their work.

The project was coming along pretty well for a few months, but ran into
serious technical issues with `SIDE_MAP`, a byond map_format that allows
for simpler 3/4th rendering.
Due to BULLSHIT I will not detail here, the map format caused issues
both at random with flickering and heavily with multiz.

Concurrent with this, action stepped down after hacking out the
rendering tech and starting work on an icon cutter that would allow for
simpler icon generation, leaving ninjanomnom to manage the project.

Some time passed, and the project stalled out due to the technical
issues. Eventually I built a test case for the issues we had with
`SIDE_MAP` and convinced lummox jr (byond's developer) to explain how
the fuckin thing actually worked. This understanding made the project
theoretically possible, but did not resolve the problems with multi-z.
Resolving those required a full rework of how rendering like, worked. I
(alongside tattle) took over project development from ninjanomnom at
this time, and started work on Plane Cube (#69115), which when finished
would finally make the project technically feasible.

The time between then and now has been slow, progressive work. Many many
artists and technical folks have dumped their time into this (as you can
see from the credits). I will get into this more below but I would like
to explicitly thank (in no particular order) tattle, draco, arcanemusic,
actionninja, imaginos, viro and kylerace for keeping the project alive
in this time period. I would have curled up into a ball and died if I
had to do this all myself, your help has been indispensable.

</details>

<details>
<summary>Detailed Credits</summary>

Deep apologies if I have forgotten someone (I am sure I have, if someone
is you please contact me). I've done my best to collate from the git
log/my memory.

Thanks to (In no particular order):
Raccoff: Being funny to bully, creating threshold decals for airlocks
aa07: (I think) inspiring the project
ActionNinja: Laying the technical rock we build off, supporting me
despite byond trying to kill him, building the icon cutter that makes
this possible
ArcaneMusic: Artistic and technical work spanning from the project's
start to literally today, being a constant of motivation and positivity.
I can't list all the stuff he's done
Armhulen: Key rendering work (he's the reason thindows render right), an
upbeat personality and a kick in the ass. Love you arm
Azlan: Damn cool sprites, consistently
Ben10Omintrix: You know ben showed up just to make basic mobs work, he's
just fuckin like that man
BigBimmer: A large amount of bounty work, alongside just like, throwing
shit around. An absolute joy to work with
Capsandi: Plaques, blastdoors, artistic work early on
CapybaraExtravagante: Rendering work on wall frames
Draco: SO MUCH STUFF. Much of the spritework done over the past two
years is his, constantly engaged and will take on anything. I would have
given up if not for you
Floyd: Early rendering work, so early I don't even know the details.
Enjoy freedom brother
Imaginos16: A guiding hand through the middle years, handled much of the
sprite review and contribution for a good bit there
Iamgoofball: A dedication to detail and aesthetic goals, spends a lot of
effort dissecting feedback with a focus on making things as good as they
can be at the jump
Infrared: Part of the impetus for the project, made all the xenomorph
stuff in the MS style
Jacquerel: A bunch of little upkeep/technical things, has done so much
sprite gruntwork (WHY ARE THERE SO MANY PAINTING TYPES)
Justice12354: Solved a bunch of error sprites (and worked out how to
actually make prs to the project) Thanks bro!
Kryson: Built the artstyle of the project, carrying on for years even
when it was technically dying, only stopping to casually beat cancer. So
much of our style and art is Kryson
KylerAce: Handled annoying technical stuff for me, built window frame
logic and fully got rid of grilles.
LemonInTheDark: Rendering dirtywork, project management and just so much
fucking time in dreammaker editing sprites
Meyhazah: Table buttons, brass windows and alll the old style doors
Mothblocks: Has provided constant support, gave me a deadline and
motivation, erased worries about "it not being done", gave just SO much
money to fill in the critical holes in sprites. Thanks moth
MTandi: Contributed art despite his own blackjack and hookers club
opening right down the road, I'm sorry I rolled over some of your
sprites man I wish we had finished earlier
Ninjanomnomnom: Consulted on gags issues, kept things alive through some
truly shit times
oranges: This is his fault
Rohesie: Organized the effort, did much of the initial like, proof of
concept stuff. I hope you're doin well whatever you're up to.
san7890: Consulting on mapper UX/design problems, being my pet mapper
Senefi: Offsetting items with a focus on detail/the more unused
canidates
SimplyLogan: Detailed map work and mapper feedback, personally very kind
even if we end up talking past each other sometimes. Thank you!
SpaceSmithers: Just like, random mapping support out of nowhere, and
bein a straight up cool dude
Tattle: A bunch of misc project management stuff, organizing the
discord, managing the test server, dealing with all the mapping bullshit
for me, being my backup in case of bus. I know you think you didn't do
much but your presence and work have been a great help
Thunder12345: Came out of nowhere and just so much of the random
bounties, I'm kind of upset about how much we paid him
Time-Green: I hooked him in by fucking with stuff he made and now he's
just doin shit, thanks for helping out man!
Twaticus: Provided artistic feedback and authority for my poor feeble
coder brain, believed in the project for YEARS, was a constant source of
❤️ and affirmation
unit0016: I have no god damn idea who she is, popped out of nowhere on
the github one day and dealt with a bunch of annoying
rendering/refactoring. Godspeed random furry thank you for all your
effort and issue reports
Viro: A bunch of detailed spriting moving towards 3/4ths, both on and
off the wallening fork. If anyone believed this project would be done,
it was viro
Wallem: Artistic review and consultation, was my go-to guy for a long
time when the other two spritetainers were inactive
Waltermeldon: Cracked out a bunch of rendering work, he's the reason
windows look like not dogwater. Alongside floyd and action spent a TON
of time speaking to lummox/unearthing how byond rendering worked trying
to make this thing happen
ZephyrTFA: Added directional airlock helpers, dealt with a big fuckin
bugaboo that was living in my brain like it was nothing. Love you
brother

And finally:
The Mojave Sun development team. They provided a testbed for the idea,
committed hundreds and hundreds of hours to the artstyle, and were a
large reason we caught issues early enough to meaningfully deal with
them. Your work is a testament to what longterm effort and deep detailed
care produce. I hope you're doing well whatever you're up to. Go out
with a bang!
</details>

## Changelog
🆑 Raccoff, aa07, ActionNinja, ArcaneMusic, Armhulen, Azlan,
Ben10Omintrix, BigBimmer, Capsandi, CapybaraExtravagante, Draco, Floyd,
Iamgoofball, Imaginos16, Infrared, Jacquerel, Justice12354, Kryson,
KylerAce, LemonInTheDark, Meyhazah, Mothblocks, MTandi, Ninjanomnom,
oranges, Rohesie, Runi-c, san7890, Senefi, SimplyLogan, SomeAngryMiner,
SpaceSmithers, Tattle, Thunder12345, Time-Green, Twaticus, unit0016,
Viro, Waltermeldon, ZephyrTFA with thanks to the Mojave Sun team!
add: Resprites or offsets almost all "tall" objects in the game to match
a 3/4ths perspective
add: Bunch of rendering mumbo jumbo to make said 3/4ths perspective work
/🆑

---------

Co-authored-by: Jacquerel <hnevard@gmail.com>
Co-authored-by: san7890 <the@san7890.com>
Co-authored-by: = <stewartareid@outlook.com>
Co-authored-by: Capsandi <dansullycc@gmail.com>
Co-authored-by: ArcaneMusic <hero12290@aol.com>
Co-authored-by: tattle <66640614+dragomagol@users.noreply.github.com>
Co-authored-by: SomeAngryMiner <53237389+SomeAngryMiner@users.noreply.github.com>
Co-authored-by: KylerAce <kylerlumpkin1@gmail.com>
Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com>
Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com>
Co-authored-by: lessthanthree <83487515+lessthnthree@users.noreply.github.com>
Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com>
Co-authored-by: Runi-c <5150427+Runi-c@users.noreply.github.com>
Co-authored-by: Roryl-c <5150427+Roryl-c@users.noreply.github.com>
Co-authored-by: tattle <article.disaster@gmail.com>
Co-authored-by: Senefi <20830349+Peliex@users.noreply.github.com>
Co-authored-by: Justice <42555530+Justice12354@users.noreply.github.com>
Co-authored-by: BluBerry016 <50649185+unit0016@users.noreply.github.com>
Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: SimplyLogan <47579821+loganuk@users.noreply.github.com>
Co-authored-by: Emmett Gaines <ninjanomnom@gmail.com>
Co-authored-by: Rob Bailey <github@criticalaction.net>
Co-authored-by: MMMiracles <lolaccount1@hotmail.com>
2024-08-14 09:07:45 +00:00

1053 lines
39 KiB
Plaintext

/**
* The base type for nearly all physical objects in SS13
* Lots and lots of functionality lives here, although in general we are striving to move
* as much as possible to the components/elements system
*/
/atom
layer = ABOVE_NORMAL_TURF_LAYER
plane = GAME_PLANE
appearance_flags = TILE_BOUND|LONG_GLIDE
/// pass_flags that we are. If any of this matches a pass_flag on a moving thing, by default, we let them through.
var/pass_flags_self = NONE
///First atom flags var
var/flags_1 = NONE
///Intearaction flags
var/interaction_flags_atom = NONE
var/flags_ricochet = NONE
///When a projectile tries to ricochet off this atom, the projectile ricochet chance is multiplied by this
var/receive_ricochet_chance_mod = 1
///When a projectile ricochets off this atom, it deals the normal damage * this modifier to this atom
var/receive_ricochet_damage_coeff = 0.33
///Reagents holder
var/datum/reagents/reagents = null
///all of this atom's HUD (med/sec, etc) images. Associative list of the form: list(hud category = hud image or images for that category).
///most of the time hud category is associated with a single image, sometimes its associated with a list of images.
///not every hud in this list is actually used. for ones available for others to see, look at active_hud_list.
var/list/image/hud_list = null
///all of this atom's HUD images which can actually be seen by players with that hud
var/list/image/active_hud_list = null
///HUD images that this atom can provide.
var/list/hud_possible
///How much this atom resists explosions by, in the end
var/explosive_resistance = 0
///vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays.
var/list/managed_vis_overlays
/// Lazylist of all images (or atoms, I'm sorry) (hopefully attached to us) to update when we change z levels
/// You will need to manage adding/removing from this yourself, but I'll do the updating for you
var/list/image/update_on_z
/// Lazylist of all overlays attached to us to update when we change z levels
/// You will need to manage adding/removing from this yourself, but I'll do the updating for you
/// Oh and note, if order of addition is important this WILL break that. so mind yourself
var/list/image/update_overlays_on_z
///Cooldown tick timer for buckle messages
var/buckle_message_cooldown = 0
///Last fingerprints to touch this atom
var/fingerprintslast
/// Radiation insulation types
var/rad_insulation = RAD_NO_INSULATION
/// The icon state intended to be used for the acid component. Used to override the default acid overlay icon state.
var/custom_acid_overlay = null
var/datum/wires/wires = null
///Light systems, both shouldn't be active at the same time.
var/light_system = COMPLEX_LIGHT
///Range of the light in tiles. Zero means no light.
var/light_range = 0
///Intensity of the light. The stronger, the less shadows you will see on the lit area.
var/light_power = 1
///Hexadecimal RGB string representing the colour of the light. White by default.
var/light_color = COLOR_WHITE
/// Angle of light to show in light_dir
/// 360 is a circle, 90 is a cone, etc.
var/light_angle = 360
/// What angle to project light in
var/light_dir = NORTH
///Boolean variable for toggleable lights. Has no effect without the proper light_system, light_range and light_power values.
var/light_on = TRUE
/// How many tiles "up" this light is. 1 is typical, should only really change this if it's a floor light
var/light_height = LIGHTING_HEIGHT
///Bitflags to determine lighting-related atom properties.
var/light_flags = NONE
///Our light source. Don't fuck with this directly unless you have a good reason!
var/tmp/datum/light_source/light
///Any light sources that are "inside" of us, for example, if src here was a mob that's carrying a flashlight, that flashlight's light source would be part of this list.
var/tmp/list/light_sources
/// Last name used to calculate a color for the chatmessage overlays
var/chat_color_name
/// Last color calculated for the the chatmessage overlays
var/chat_color
/// A luminescence-shifted value of the last color calculated for chatmessage overlays
var/chat_color_darkened
// Use SET_BASE_PIXEL(x, y) to set these in typepath definitions, it'll handle pixel_x and y for you
///Default pixel x shifting for the atom's icon.
var/base_pixel_x = 0
///Default pixel y shifting for the atom's icon.
var/base_pixel_y = 0
// Use SET_BASE_VISUAL_PIXEL(x, y) to set these in typepath definitions, it'll handle pixel_w and z for you
///Default pixel w shifting for the atom's icon.
var/base_pixel_w = 0
///Default pixel z shifting for the atom's icon.
var/base_pixel_z = 0
///Used for changing icon states for different base sprites.
var/base_icon_state
///Icon-smoothing behavior.
var/smoothing_flags = NONE
///What directions this is currently smoothing with. IMPORTANT: This uses the smoothing direction flags as defined in icon_smoothing.dm, instead of the BYOND flags.
var/smoothing_junction = null //This starts as null for us to know when it's first set, but after that it will hold a 8-bit mask ranging from 0 to 255.
///Smoothing variable
var/top_left_corner
///Smoothing variable
var/top_right_corner
///Smoothing variable
var/bottom_left_corner
///Smoothing variable
var/bottom_right_corner
///What smoothing groups does this atom belongs to, to match canSmoothWith. If null, nobody can smooth with it. Must be sorted.
var/list/smoothing_groups = null
///List of smoothing groups this atom can smooth with. If this is null and atom is smooth, it smooths only with itself. Must be sorted.
var/list/canSmoothWith = null
///AI controller that controls this atom. type on init, then turned into an instance during runtime
var/datum/ai_controller/ai_controller
///Should we ignore any attempts to auto align? Mappers should edit this
var/manual_align = FALSE
/// forensics datum, contains fingerprints, fibres, blood_dna and hiddenprints on this atom
var/datum/forensics/forensics
/// How this atom should react to having its astar blocking checked
var/can_astar_pass = CANASTARPASS_DENSITY
///whether ghosts can see screentips on it
var/ghost_screentips = FALSE
/// Flags to check for in can_perform_action. Used in alt-click & ctrl-click checks
var/interaction_flags_click = NONE
/// Flags to check for in can_perform_action for mouse drag & drop checks. To bypass checks see interaction_flags_atom mouse drop flags
var/interaction_flags_mouse_drop = NONE
/// if truthy, rcd spritesheets will use this as the key to this atom's cached icon
/// instead of its name.
var/rcd_spritesheet_override = ""
/**
* Top level of the destroy chain for most atoms
*
* Cleans up the following:
* * Removes alternate apperances from huds that see them
* * qdels the reagent holder from atoms if it exists
* * clears the orbiters list
* * clears overlays and priority overlays
* * clears the light object
*/
/atom/Destroy(force)
if(alternate_appearances)
for(var/current_alternate_appearance in alternate_appearances)
var/datum/atom_hud/alternate_appearance/selected_alternate_appearance = alternate_appearances[current_alternate_appearance]
selected_alternate_appearance.remove_atom_from_hud(src)
if(reagents)
QDEL_NULL(reagents)
if(forensics)
QDEL_NULL(forensics)
if(atom_storage)
QDEL_NULL(atom_storage)
if(wires)
QDEL_NULL(wires)
orbiters = null // The component is attached to us normaly and will be deleted elsewhere
// Checking length(overlays) before cutting has significant speed benefits
if (length(overlays))
overlays.Cut()
LAZYNULL(managed_overlays)
if(ai_controller)
QDEL_NULL(ai_controller)
if(light)
QDEL_NULL(light)
if (length(light_sources))
light_sources.Cut()
if(smoothing_flags & SMOOTH_QUEUED)
SSicon_smooth.remove_from_queues(src)
// These lists cease existing when src does, so we need to clear any lua refs to them that exist.
if(!(datum_flags & DF_STATIC_OBJECT))
DREAMLUAU_CLEAR_REF_USERDATA(contents)
DREAMLUAU_CLEAR_REF_USERDATA(filters)
DREAMLUAU_CLEAR_REF_USERDATA(overlays)
DREAMLUAU_CLEAR_REF_USERDATA(underlays)
return ..()
/atom/proc/handle_ricochet(obj/projectile/ricocheting_projectile)
var/turf/p_turf = get_turf(ricocheting_projectile)
var/face_direction = get_dir(src, p_turf) || get_dir(src, ricocheting_projectile)
var/face_angle = dir2angle(face_direction)
var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (ricocheting_projectile.Angle + 180))
var/a_incidence_s = abs(incidence_s)
if(a_incidence_s > 90 && a_incidence_s < 270)
return FALSE
if((ricocheting_projectile.armor_flag in list(BULLET, BOMB)) && ricocheting_projectile.ricochet_incidence_leeway)
if((a_incidence_s < 90 && a_incidence_s < 90 - ricocheting_projectile.ricochet_incidence_leeway) || (a_incidence_s > 270 && a_incidence_s -270 > ricocheting_projectile.ricochet_incidence_leeway))
return FALSE
var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s)
ricocheting_projectile.set_angle(new_angle_s)
return TRUE
/// Whether the mover object can avoid being blocked by this atom, while arriving from (or leaving through) the border_dir.
/atom/proc/CanPass(atom/movable/mover, border_dir)
SHOULD_CALL_PARENT(TRUE)
SHOULD_BE_PURE(TRUE)
if(SEND_SIGNAL(src, COMSIG_ATOM_TRIED_PASS, mover, border_dir) & COMSIG_COMPONENT_PERMIT_PASSAGE)
return TRUE
if(mover.movement_type & PHASING)
return TRUE
. = CanAllowThrough(mover, border_dir)
// This is cheaper than calling the proc every time since most things dont override CanPassThrough
if(!mover.generic_canpass)
return mover.CanPassThrough(src, REVERSE_DIR(border_dir), .)
/// Returns true or false to allow the mover to move through src
/atom/proc/CanAllowThrough(atom/movable/mover, border_dir)
SHOULD_CALL_PARENT(TRUE)
//SHOULD_BE_PURE(TRUE)
if(mover.pass_flags & pass_flags_self)
return TRUE
if(mover.throwing && (pass_flags_self & LETPASSTHROW))
return TRUE
if(SEND_SIGNAL(src, COMSIG_ATOM_CAN_ALLOW_THROUGH, mover, border_dir) & COMSIG_FORCE_ALLOW_THROUGH)
return TRUE
return !density
/**
* Is this atom currently located on centcom (or riding off into the sunset on a shuttle)
*
* Specifically, is it on the z level and within the centcom areas.
* You can also be in a shuttle during endgame transit.
*
* Used in gamemode to identify mobs who have escaped and for some other areas of the code
* who don't want atoms where they shouldn't be
*
* Returns TRUE if this atom is on centcom or an escape shuttle, or FALSE if not
*/
/atom/proc/onCentCom()
var/turf/current_turf = get_turf(src)
if(!current_turf)
return FALSE
// This doesn't necessarily check that we're at central command,
// but it checks for any shuttles which have finished are still in hyperspace
// (IE, stuff like the whiteship which fly off into the sunset and "escape")
if(is_reserved_level(current_turf.z))
return on_escaped_shuttle(ENDGAME_TRANSIT)
// From here on we only concern ourselves with people actually on the centcom Z
if(!is_centcom_level(current_turf.z))
return FALSE
if(istype(current_turf.loc, /area/centcom))
return TRUE
// Finally, check if we're on an escaped shuttle
return on_escaped_shuttle()
/**
* Is the atom in any of the syndicate areas
*
* Either in the syndie base, or any of their shuttles
*
* Also used in gamemode code for win conditions
*
* Returns TRUE if this atom is on the syndicate recon base, any of its shuttles, or an escape shuttle, or FALSE if not
*/
/atom/proc/onSyndieBase()
var/turf/current_turf = get_turf(src)
if(!current_turf)
return FALSE
// Syndicate base is loaded in a reserved level. If not reserved, we don't care.
if(!is_reserved_level(current_turf.z))
return FALSE
var/static/list/syndie_typecache = typecacheof(list(
/area/centcom/syndicate_mothership, // syndicate base itself
/area/shuttle/assault_pod, // steel rain
/area/shuttle/syndicate, // infiltrator
))
if(is_type_in_typecache(current_turf.loc, syndie_typecache))
return TRUE
// Finally, check if we're on an escaped shuttle
return on_escaped_shuttle()
/**
* Checks that we're on a shuttle that's escaped
*
* * check_for_launch_status - What launch status do we check for? Generally the two you want to check for are ENDGAME_LAUNCHED or ENDGAME_TRANSIT
*
* Returns TRUE if this atom is on a shuttle which is escaping or has escaped, or FALSE otherwise
*/
/atom/proc/on_escaped_shuttle(check_for_launch_status = ENDGAME_LAUNCHED)
var/turf/current_turf = get_turf(src)
if(!current_turf)
return FALSE
for(var/obj/docking_port/mobile/mobile_docking_port as anything in SSshuttle.mobile_docking_ports)
if(mobile_docking_port.launch_status != check_for_launch_status)
continue
for(var/area/shuttle/shuttle_area as anything in mobile_docking_port.shuttle_areas)
if(shuttle_area == current_turf.loc)
return TRUE
return FALSE
/**
* Is the atom in an away mission
*
* Must be in the away mission z-level to return TRUE
*
* Also used in gamemode code for win conditions
*/
/atom/proc/onAwayMission()
var/turf/current_turf = get_turf(src)
if(!current_turf)
return FALSE
if(is_away_level(current_turf.z))
return TRUE
return FALSE
/**
* Ensure a list of atoms/reagents exists inside this atom
*
* Goes throught he list of passed in parts, if they're reagents, adds them to our reagent holder
* creating the reagent holder if it exists.
*
* If the part is a moveable atom and the previous location of the item was a mob/living,
* it calls the inventory handler transferItemToLoc for that mob/living and transfers the part
* to this atom
*
* Otherwise it simply forceMoves the atom into this atom
*/
/atom/proc/CheckParts(list/parts_list, datum/crafting_recipe/current_recipe)
SEND_SIGNAL(src, COMSIG_ATOM_CHECKPARTS, parts_list, current_recipe)
if(!parts_list)
return
for(var/part in parts_list)
if(istype(part, /datum/reagent))
if(!reagents)
reagents = new()
reagents.reagent_list.Add(part)
else if(ismovable(part))
var/atom/movable/object = part
if(isliving(object.loc))
var/mob/living/living = object.loc
living.transferItemToLoc(object, src)
else
object.forceMove(src)
SEND_SIGNAL(object, COMSIG_ATOM_USED_IN_CRAFT, src)
parts_list.Cut()
///Take air from the passed in gas mixture datum
/atom/proc/assume_air(datum/gas_mixture/giver)
return null
///Remove air from this atom
/atom/proc/remove_air(amount)
return null
///Return the current air environment in this atom
/atom/proc/return_air()
if(loc)
return loc.return_air()
else
return null
///Return the air if we can analyze it
/atom/proc/return_analyzable_air()
return null
/atom/proc/Bumped(atom/movable/bumped_atom)
set waitfor = FALSE
SEND_SIGNAL(src, COMSIG_ATOM_BUMPED, bumped_atom)
/// Convenience proc to see if a container is open for chemistry handling
/atom/proc/is_open_container()
return is_refillable() && is_drainable()
/// Is this atom injectable into other atoms
/atom/proc/is_injectable(mob/user, allowmobs = TRUE)
return reagents && (reagents.flags & (INJECTABLE | REFILLABLE))
/// Can we draw from this atom with an injectable atom
/atom/proc/is_drawable(mob/user, allowmobs = TRUE)
return reagents && (reagents.flags & (DRAWABLE | DRAINABLE))
/// Can this atoms reagents be refilled
/atom/proc/is_refillable()
return reagents && (reagents.flags & REFILLABLE)
/// Is this atom drainable of reagents
/atom/proc/is_drainable()
return reagents && (reagents.flags & DRAINABLE)
/** Handles exposing this atom to a list of reagents.
*
* Sends COMSIG_ATOM_EXPOSE_REAGENTS
* Calls expose_atom() for every reagent in the reagent list.
*
* Arguments:
* - [reagents][/list]: The list of reagents the atom is being exposed to.
* - [source][/datum/reagents]: The reagent holder the reagents are being sourced from.
* - methods: How the atom is being exposed to the reagents. Bitflags.
* - volume_modifier: Volume multiplier.
* - show_message: Whether to display anything to mobs when they are exposed.
*/
/atom/proc/expose_reagents(list/reagents, datum/reagents/source, methods=TOUCH, volume_modifier=1, show_message=TRUE)
. = SEND_SIGNAL(src, COMSIG_ATOM_EXPOSE_REAGENTS, reagents, source, methods, volume_modifier, show_message)
if(. & COMPONENT_NO_EXPOSE_REAGENTS)
return
SEND_SIGNAL(source, COMSIG_REAGENTS_EXPOSE_ATOM, src, reagents, methods, volume_modifier, show_message)
for(var/datum/reagent/current_reagent as anything in reagents)
. |= current_reagent.expose_atom(src, reagents[current_reagent])
SEND_SIGNAL(src, COMSIG_ATOM_AFTER_EXPOSE_REAGENTS, reagents, source, methods, volume_modifier, show_message)
/// Are you allowed to drop this atom
/atom/proc/AllowDrop()
return FALSE
///Is this atom within 1 tile of another atom
/atom/proc/HasProximity(atom/movable/proximity_check_mob as mob|obj)
return
/// Sets the wire datum of an atom
/atom/proc/set_wires(datum/wires/new_wires)
wires = new_wires
///Return true if we're inside the passed in atom
/atom/proc/in_contents_of(container)//can take class or object instance as argument
if(ispath(container))
if(istype(src.loc, container))
return TRUE
else if(src in container)
return TRUE
return FALSE
/**
* Checks the atom's loc and calls update_held_items on it if it is a mob.
*
* This should only be used in situations when you are unable to use /datum/element/update_icon_updates_onmob for whatever reason.
* Check code/datums/elements/update_icon_updates_onmob.dm before using this. Adding that to the atom and calling update_appearance will work for most cases.
*
* Arguments:
* * mob/target - The mob to update the icons of. Optional argument, use if the atom's loc is not the mob you want to update.
*/
/atom/proc/update_inhand_icon(mob/target = loc)
SHOULD_CALL_PARENT(TRUE)
if(!istype(target))
return
target.update_held_items()
SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_INHAND_ICON, target)
/**
* An atom we are buckled or is contained within us has tried to move
*
* Default behaviour is to send a warning that the user can't move while buckled as long
* as the [buckle_message_cooldown][/atom/var/buckle_message_cooldown] has expired (50 ticks)
*/
/atom/proc/relaymove(mob/living/user, direction)
if(SEND_SIGNAL(src, COMSIG_ATOM_RELAYMOVE, user, direction) & COMSIG_BLOCK_RELAYMOVE)
return
if(buckle_message_cooldown <= world.time)
buckle_message_cooldown = world.time + 25
balloon_alert(user, "can't move while buckled!")
return
/**
* A special case of relaymove() in which the person relaying the move may be "driving" this atom
*
* This is a special case for vehicles and ridden animals where the relayed movement may be handled
* by the riding component attached to this atom. Returns TRUE as long as there's nothing blocking
* the movement, or FALSE if the signal gets a reply that specifically blocks the movement
*/
/atom/proc/relaydrive(mob/living/user, direction)
return !(SEND_SIGNAL(src, COMSIG_RIDDEN_DRIVER_MOVE, user, direction) & COMPONENT_DRIVER_BLOCK_MOVE)
///returns the mob's dna info as a list, to be inserted in an object's blood_DNA list
/mob/living/proc/get_blood_dna_list()
if(get_blood_id() != /datum/reagent/blood)
return
return list("ANIMAL DNA" = "Y-")
///Get the mobs dna list
/mob/living/carbon/get_blood_dna_list()
if(get_blood_id() != /datum/reagent/blood)
return
var/list/blood_dna = list()
if(dna)
blood_dna[dna.unique_enzymes] = dna.blood_type
else
blood_dna["UNKNOWN DNA"] = "X*"
return blood_dna
/mob/living/carbon/alien/get_blood_dna_list()
return list("UNKNOWN DNA" = "X*")
/mob/living/silicon/get_blood_dna_list()
return
///to add a mob's dna info into an object's blood_dna list.
/atom/proc/transfer_mob_blood_dna(mob/living/injected_mob)
// Returns 0 if we have that blood already
var/new_blood_dna = injected_mob.get_blood_dna_list()
if(!new_blood_dna)
return FALSE
var/old_length = GET_ATOM_BLOOD_DNA_LENGTH(src)
add_blood_DNA(new_blood_dna)
if(GET_ATOM_BLOOD_DNA_LENGTH(src) == old_length)
return FALSE
return TRUE
///to add blood from a mob onto something, and transfer their dna info
/atom/proc/add_mob_blood(mob/living/injected_mob)
var/list/blood_dna = injected_mob.get_blood_dna_list()
if(!blood_dna)
return FALSE
return add_blood_DNA(blood_dna)
///Is this atom in space
/atom/proc/isinspace()
if(isspaceturf(get_turf(src)))
return TRUE
else
return FALSE
/**
* If someone's trying to dump items onto our atom, where should they be dumped to?
*
* Return a loc to place objects, or null to stop dumping.
*/
/atom/proc/get_dumping_location()
return null
/**
* the vision impairment to give to the mob whose perspective is set to that atom
*
* (e.g. an unfocused camera giving you an impaired vision when looking through it)
*/
/atom/proc/get_remote_view_fullscreens(mob/user)
return
/**
* the sight changes to give to the mob whose perspective is set to that atom
*
* (e.g. A mob with nightvision loses its nightvision while looking through a normal camera)
*/
/atom/proc/update_remote_sight(mob/living/user)
return
/**
* Hook for running code when a dir change occurs
*
* Not recommended to use, listen for the [COMSIG_ATOM_DIR_CHANGE] signal instead (sent by this proc)
*/
/atom/proc/setDir(newdir)
SHOULD_CALL_PARENT(TRUE)
if (SEND_SIGNAL(src, COMSIG_ATOM_PRE_DIR_CHANGE, dir, newdir) & COMPONENT_ATOM_BLOCK_DIR_CHANGE)
newdir = dir
return
SEND_SIGNAL(src, COMSIG_ATOM_DIR_CHANGE, dir, newdir)
var/oldDir = dir
dir = newdir
SEND_SIGNAL(src, COMSIG_ATOM_POST_DIR_CHANGE, oldDir, newdir)
if(smoothing_flags & SMOOTH_BORDER_OBJECT)
QUEUE_SMOOTH_NEIGHBORS(src)
/**
* Wash this atom
*
* This will clean it off any temporary stuff like blood. Override this in your item to add custom cleaning behavior.
* Returns true if any washing was necessary and thus performed
* Arguments:
* * clean_types: any of the CLEAN_ constants
*/
/atom/proc/wash(clean_types)
SHOULD_CALL_PARENT(TRUE)
if(SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, clean_types) & COMPONENT_CLEANED)
return TRUE
// Basically "if has washable coloration"
if(length(atom_colours) >= WASHABLE_COLOUR_PRIORITY && atom_colours[WASHABLE_COLOUR_PRIORITY])
remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
return TRUE
return FALSE
///Where atoms should drop if taken from this atom
/atom/proc/drop_location()
var/atom/location = loc
if(!location)
return null
return location.AllowDrop() ? location : location.drop_location()
/**
* An atom has entered this atom's contents
*
* Default behaviour is to send the [COMSIG_ATOM_ENTERED]
*/
/atom/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, arrived, old_loc, old_locs)
SEND_SIGNAL(arrived, COMSIG_ATOM_ENTERING, src, old_loc, old_locs)
/**
* An atom is attempting to exit this atom's contents
*
* Default behaviour is to send the [COMSIG_ATOM_EXIT]
*/
/atom/Exit(atom/movable/leaving, direction)
// Don't call `..()` here, otherwise `Uncross()` gets called.
// See the doc comment on `Uncross()` to learn why this is bad.
if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, leaving, direction) & COMPONENT_ATOM_BLOCK_EXIT)
return FALSE
return TRUE
/**
* An atom has exited this atom's contents
*
* Default behaviour is to send the [COMSIG_ATOM_EXITED]
*/
/atom/Exited(atom/movable/gone, direction)
SEND_SIGNAL(src, COMSIG_ATOM_EXITED, gone, direction)
///Return atom temperature
/atom/proc/return_temperature()
return
/atom/proc/process_recipes(mob/living/user, obj/item/processed_object, list/processing_recipes)
//Only one recipe? use the first
if(processing_recipes.len == 1)
StartProcessingAtom(user, processed_object, processing_recipes[1])
return
//Otherwise, select one with a radial
ShowProcessingGui(user, processed_object, processing_recipes)
///Creates the radial and processes the selected option
/atom/proc/ShowProcessingGui(mob/living/user, obj/item/processed_object, list/possible_options)
var/list/choices_to_options = list() //Dict of object name | dict of object processing settings
var/list/choices = list()
for(var/list/current_option as anything in possible_options)
var/atom/current_option_type = current_option[TOOL_PROCESSING_RESULT]
choices_to_options[initial(current_option_type.name)] = current_option
var/image/option_image = image(icon = initial(current_option_type.icon), icon_state = initial(current_option_type.icon_state))
choices += list("[initial(current_option_type.name)]" = option_image)
var/pick = show_radial_menu(user, src, choices, radius = 36, require_near = TRUE)
if(!pick)
return
StartProcessingAtom(user, processed_object, choices_to_options[pick])
/atom/proc/StartProcessingAtom(mob/living/user, obj/item/process_item, list/chosen_option)
var/processing_time = chosen_option[TOOL_PROCESSING_TIME]
to_chat(user, span_notice("You start working on [src]."))
if(process_item.use_tool(src, user, processing_time, volume=50))
var/atom/atom_to_create = chosen_option[TOOL_PROCESSING_RESULT]
var/list/atom/created_atoms = list()
var/amount_to_create = chosen_option[TOOL_PROCESSING_AMOUNT]
for(var/i = 1 to amount_to_create)
var/atom/created_atom = new atom_to_create(drop_location())
if(custom_materials)
created_atom.set_custom_materials(custom_materials, 1 / amount_to_create)
created_atom.pixel_x = pixel_x
created_atom.pixel_y = pixel_y
if(i > 1)
created_atom.pixel_x += rand(-8,8)
created_atom.pixel_y += rand(-8,8)
created_atom.OnCreatedFromProcessing(user, process_item, chosen_option, src)
created_atoms.Add(created_atom)
to_chat(user, span_notice("You manage to create [amount_to_create] [initial(atom_to_create.gender) == PLURAL ? "[initial(atom_to_create.name)]" : "[initial(atom_to_create.name)][plural_s(initial(atom_to_create.name))]"] from [src]."))
SEND_SIGNAL(src, COMSIG_ATOM_PROCESSED, user, process_item, created_atoms)
UsedforProcessing(user, process_item, chosen_option)
return
/atom/proc/UsedforProcessing(mob/living/user, obj/item/used_item, list/chosen_option)
qdel(src)
return
/atom/proc/OnCreatedFromProcessing(mob/living/user, obj/item/work_tool, list/chosen_option, atom/original_atom)
SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_ATOM_CREATEDBY_PROCESSING, original_atom, chosen_option)
if(user.mind)
ADD_TRAIT(src, TRAIT_FOOD_CHEF_MADE, REF(user.mind))
///Connect this atom to a shuttle
/atom/proc/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
return
/atom/proc/intercept_zImpact(list/falling_movables, levels = 1)
SHOULD_CALL_PARENT(TRUE)
. |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, falling_movables, levels)
///Setter for the `density` variable to append behavior related to its changing.
/atom/proc/set_density(new_value)
SHOULD_CALL_PARENT(TRUE)
if(density == new_value)
return
. = density
density = new_value
///Setter for the `base_pixel_x` variable to append behavior related to its changing.
/atom/proc/set_base_pixel_x(new_value)
if(base_pixel_x == new_value)
return
. = base_pixel_x
base_pixel_x = new_value
pixel_x = pixel_x + base_pixel_x - .
///Setter for the `base_pixel_y` variable to append behavior related to its changing.
/atom/proc/set_base_pixel_y(new_value)
if(base_pixel_y == new_value)
return
. = base_pixel_y
base_pixel_y = new_value
pixel_y = pixel_y + base_pixel_y - .
// Not a valid operation, turfs and movables handle block differently
/atom/proc/set_explosion_block(explosion_block)
return
/**
* 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.
*
* micro-optimized to hell because this proc is very hot, being called several times per movement every movement.
*
* HEY JACKASS, LISTEN
* IF YOU ADD SOMETHING TO THIS PROC, MAKE SURE /mob/living ACCOUNTS FOR IT
* Living mobs treat gravity in an event based manner. We've decomposed this proc into different checks
* for them to use. If you add more to it, make sure you do that, or things will behave strangely
*
* Gravity situations:
* * No gravity if you're not in a turf
* * No gravity if this atom is in is a space turf
* * Gravity if the area it's in always has gravity
* * Gravity if there's a gravity generator on the z level
* * Gravity if the Z level has an SSMappingTrait for ZTRAIT_GRAVITY
* * otherwise no gravity
*/
/atom/proc/has_gravity(turf/gravity_turf)
if(!isturf(gravity_turf))
gravity_turf = get_turf(src)
if(!gravity_turf)//no gravity in nullspace
return FALSE
var/list/forced_gravity = list()
SEND_SIGNAL(src, COMSIG_ATOM_HAS_GRAVITY, gravity_turf, forced_gravity)
SEND_SIGNAL(gravity_turf, COMSIG_TURF_HAS_GRAVITY, src, forced_gravity)
if(length(forced_gravity))
var/positive_grav = max(forced_gravity)
var/negative_grav = min(min(forced_gravity), 0) //negative grav needs to be below or equal to 0
//our gravity is sum of the most massive positive and negative numbers returned by the signal
//so that adding two forced_gravity elements with an effect size of 1 each doesnt add to 2 gravity
//but negative force gravity effects can cancel out positive ones
return (positive_grav + negative_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)
/**
* Used to set something as 'open' if it's being used as a supplypod
*
* Override this if you want an atom to be usable as a supplypod.
*/
/atom/proc/setOpened()
return
/**
* Used to set something as 'closed' if it's being used as a supplypod
*
* Override this if you want an atom to be usable as a supplypod.
*/
/atom/proc/setClosed()
return
///Called after the atom is 'tamed' for type-specific operations, Usually called by the tameable component but also other things.
/atom/proc/tamed(mob/living/tamer, obj/item/food)
return
/**
* Used to attempt to charge an object with a payment component.
*
* Use this if an atom needs to attempt to charge another atom.
*/
/atom/proc/attempt_charge(atom/sender, atom/target, extra_fees = 0)
return SEND_SIGNAL(sender, COMSIG_OBJ_ATTEMPT_CHARGE, target, extra_fees)
///Passes Stat Browser Panel clicks to the game and calls client click on an atom
/atom/Topic(href, list/href_list)
. = ..()
if(!usr?.client)
return
var/client/usr_client = usr.client
var/list/paramslist = list()
if(href_list["statpanel_item_click"])
switch(href_list["statpanel_item_click"])
if("left")
paramslist[LEFT_CLICK] = "1"
if("right")
paramslist[RIGHT_CLICK] = "1"
if("middle")
paramslist[MIDDLE_CLICK] = "1"
else
return
if(href_list["statpanel_item_shiftclick"])
paramslist[SHIFT_CLICK] = "1"
if(href_list["statpanel_item_ctrlclick"])
paramslist[CTRL_CLICK] = "1"
if(href_list["statpanel_item_altclick"])
paramslist[ALT_CLICK] = "1"
var/mouseparams = list2params(paramslist)
usr_client.Click(src, loc, null, mouseparams)
return TRUE
/atom/MouseEntered(location, control, params)
SSmouse_entered.hovers[usr.client] = src
/// Fired whenever this atom is the most recent to be hovered over in the tick.
/// Preferred over MouseEntered if you do not need information such as the position of the mouse.
/// Especially because this is deferred over a tick, do not trust that `client` is not null.
/atom/proc/on_mouse_enter(client/client)
SHOULD_NOT_SLEEP(TRUE)
var/mob/user = client?.mob
if (isnull(user))
return
// Screentips
var/datum/hud/active_hud = user.hud_used
if(!active_hud)
return
var/screentips_enabled = active_hud.screentips_enabled
if(screentips_enabled == SCREENTIP_PREFERENCE_DISABLED || flags_1 & NO_SCREENTIPS_1)
active_hud.screentip_text.maptext = ""
return
var/lmb_rmb_line = ""
var/ctrl_lmb_ctrl_rmb_line = ""
var/alt_lmb_alt_rmb_line = ""
var/shift_lmb_ctrl_shift_lmb_line = ""
var/extra_lines = 0
var/extra_context = ""
if(isliving(user) || isovermind(user) || isaicamera(user) || (ghost_screentips && isobserver(user)))
var/obj/item/held_item = user.get_active_held_item()
if (flags_1 & HAS_CONTEXTUAL_SCREENTIPS_1 || held_item?.item_flags & ITEM_HAS_CONTEXTUAL_SCREENTIPS)
var/list/context = list()
var/contextual_screentip_returns = \
SEND_SIGNAL(src, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, context, held_item, user) \
| (held_item && SEND_SIGNAL(held_item, COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET, context, src, user))
if (contextual_screentip_returns & CONTEXTUAL_SCREENTIP_SET)
var/screentip_images = active_hud.screentip_images
// LMB and RMB on one line...
var/lmb_text = build_context(context, SCREENTIP_CONTEXT_LMB, screentip_images)
var/rmb_text = build_context(context, SCREENTIP_CONTEXT_RMB, screentip_images)
if (lmb_text != "")
lmb_rmb_line = lmb_text
if (rmb_text != "")
lmb_rmb_line += " | [rmb_text]"
else if (rmb_text != "")
lmb_rmb_line = rmb_text
// Ctrl-LMB, Ctrl-RMB on one line...
if (lmb_rmb_line != "")
lmb_rmb_line += "<br>"
extra_lines++
if (SCREENTIP_CONTEXT_CTRL_LMB in context)
ctrl_lmb_ctrl_rmb_line += build_context(context, SCREENTIP_CONTEXT_CTRL_LMB, screentip_images)
if (SCREENTIP_CONTEXT_CTRL_RMB in context)
if (ctrl_lmb_ctrl_rmb_line != "")
ctrl_lmb_ctrl_rmb_line += " | "
ctrl_lmb_ctrl_rmb_line += build_context(context, SCREENTIP_CONTEXT_CTRL_RMB, screentip_images)
// Alt-LMB, Alt-RMB on one line...
if (ctrl_lmb_ctrl_rmb_line != "")
ctrl_lmb_ctrl_rmb_line += "<br>"
extra_lines++
if (SCREENTIP_CONTEXT_ALT_LMB in context)
alt_lmb_alt_rmb_line += build_context(context, SCREENTIP_CONTEXT_ALT_LMB, screentip_images)
if (SCREENTIP_CONTEXT_ALT_RMB in context)
if (alt_lmb_alt_rmb_line != "")
alt_lmb_alt_rmb_line += " | "
alt_lmb_alt_rmb_line += build_context(context, SCREENTIP_CONTEXT_ALT_RMB, screentip_images)
// Shift-LMB, Ctrl-Shift-LMB on one line...
if (alt_lmb_alt_rmb_line != "")
alt_lmb_alt_rmb_line += "<br>"
extra_lines++
if (SCREENTIP_CONTEXT_SHIFT_LMB in context)
shift_lmb_ctrl_shift_lmb_line += build_context(context, SCREENTIP_CONTEXT_SHIFT_LMB, screentip_images)
if (SCREENTIP_CONTEXT_CTRL_SHIFT_LMB in context)
if (shift_lmb_ctrl_shift_lmb_line != "")
shift_lmb_ctrl_shift_lmb_line += " | "
shift_lmb_ctrl_shift_lmb_line += build_context(context, SCREENTIP_CONTEXT_CTRL_SHIFT_LMB, screentip_images)
if (shift_lmb_ctrl_shift_lmb_line != "")
extra_lines++
if(extra_lines)
extra_context = "<br><span class='subcontext'>[lmb_rmb_line][ctrl_lmb_ctrl_rmb_line][alt_lmb_alt_rmb_line][shift_lmb_ctrl_shift_lmb_line]</span>"
var/new_maptext
if (screentips_enabled == SCREENTIP_PREFERENCE_CONTEXT_ONLY && extra_context == "")
new_maptext = ""
else
//We inline a MAPTEXT() here, because there's no good way to statically add to a string like this
new_maptext = "<span class='context' style='text-align: center; color: [active_hud.screentip_color]'>[name][extra_context]</span>"
if (length(name) * 10 > active_hud.screentip_text.maptext_width)
INVOKE_ASYNC(src, PROC_REF(set_hover_maptext), client, active_hud, new_maptext)
return
active_hud.screentip_text.maptext = new_maptext
active_hud.screentip_text.maptext_y = 10 - (extra_lines > 0 ? 11 + 9 * (extra_lines - 1): 0)
/atom/proc/set_hover_maptext(client/client, datum/hud/active_hud, new_maptext)
var/map_height
WXH_TO_HEIGHT(client.MeasureText(new_maptext, null, active_hud.screentip_text.maptext_width), map_height)
active_hud.screentip_text.maptext = new_maptext
active_hud.screentip_text.maptext_y = 26 - map_height
/**
* This proc is used for telling whether something can pass by this atom in a given direction, for use by the pathfinding system.
*
* Trying to generate one long path across the station will call this proc on every single object on every single tile that we're seeing if we can move through, likely
* multiple times per tile since we're likely checking if we can access said tile from multiple directions, so keep these as lightweight as possible.
*
* For turfs this will only be used if pathing_pass_method is TURF_PATHING_PASS_PROC
*
* Arguments:
* * to_dir - What direction we're trying to move in, relevant for things like directional windows that only block movement in certain directions
* * pass_info - Datum that stores info about the thing that's trying to pass us
*
* IMPORTANT NOTE: /turf/proc/LinkBlockedWithAccess assumes that overrides of CanAStarPass will always return true if density is FALSE
* If this is NOT you, ensure you edit your can_astar_pass variable. Check __DEFINES/path.dm
**/
/atom/proc/CanAStarPass(to_dir, datum/can_pass_info/pass_info)
if(pass_info.pass_flags & pass_flags_self)
return TRUE
. = !density
/*
Some details about how to use these lists
We're essentially trying to predict how doors/doorlike things will be placed/surounded, and use that to set their direction
It's a little finiky, and you may need to override the lists or worst case senario manually edit something's dir
But it should behave like you expect
*/
///What to connect with by default. Used by /atom/proc/auto_align(). This can be overriden
GLOBAL_LIST_INIT(default_connectables, typecacheof(list(
/obj/machinery/door/airlock,
/obj/machinery/door/poddoor,
/obj/machinery/smartfridge,
/obj/structure/girder/reinforced,
/obj/structure/plasticflaps,
/obj/machinery/power/shieldwallgen,
/obj/structure/door_assembly,
)))
///What to connect with at a lower priority by default. Used for stuff that we want to consider, but only if we don't find anything else
GLOBAL_LIST_INIT(lower_priority_connectables, typecacheof(list(
/obj/machinery/door/firedoor,
/obj/machinery/door/window,
/obj/structure/table,
/obj/structure/window,
/obj/structure/girder,
)))
/// Ok so this whole proc is about finding tiles that we could in theory be connected to, and blocking off that direction right?
/// It's not perfect, and it can make mistakes, but it does a pretty good job predicting a mapper's intentions
/// Maybe someday every door will have its dir set properly, but we'll keep this until then
/atom/proc/auto_align(connectables_typecache, lower_priority_typecache)
if(manual_align)
return
if(!connectables_typecache)
connectables_typecache = GLOB.default_connectables
if(!lower_priority_typecache)
lower_priority_typecache = GLOB.lower_priority_connectables
var/list/dirs_usable = GLOB.cardinals.Copy()
var/list/dirs_secondary_priority = GLOB.cardinals.Copy()
for(var/dir_to_check in GLOB.cardinals)
var/turf/turf_to_check = get_step(src, dir_to_check)
if(turf_to_check.density) //Dense turfs are connectable
dirs_usable -= dir_to_check
continue
for(var/atom/movable/thing_to_check as anything in turf_to_check)
if(is_type_in_typecache(thing_to_check, connectables_typecache))
dirs_usable -= dir_to_check //So are things in the default typecache
break
if(is_type_in_typecache(thing_to_check, lower_priority_typecache))
dirs_secondary_priority -= dir_to_check //Assuming we find nothing else, note down the secondary priority stuff
var/dirs_avalible = length(dirs_usable)
//Only continue if we've got ourself either a corner or a side piece. Only side pieces really work well here, since corners aren't really something we can fudge handling for
if(dirs_avalible && dirs_avalible <= 2)
setDir(dirs_usable[1]) //Just take the first dir avalible
return
dirs_usable &= dirs_secondary_priority //Only consider dirs we both share
dirs_avalible = length(dirs_usable)
if(dirs_avalible && dirs_avalible <= 2)
setDir(dirs_usable[1])
return