Files
Bubberstation/code/__HELPERS/spatial_info.dm
SkyratBot 5543a44882 [MIRROR] ArcMining Pr Beta: Version 1.2 (#26205)
ArcMining Pr Beta: Version 1.2 (#78524)

This one's not like the last one, so much so that I'm not even going to
outsource the PR description to a robot this time!
Basically, **You should read the PR body before assuming that everything
is the same as last time. It's not.**

Click the link below to see a video summary of the main features of this
pull request.
https://youtu.be/Aho2omR0mjY?feature=shared

This pull request serves as a large rework of minerals produced by
mining, and by extension mining itself. I'll try and list each change
and it's associated nuance here.

The biggest addition to the game with ArcMining is **Ore Vents**. Ore
vents spawn as a ruin on the map, placing a randomized ore vent onto map
generation. Ore vents spawn in 3 different sizes, **Small, Medium, and
Large**. These vents will pick from a pool of materials they can
generate, and will hang out across the map. A player can use a mining
scanner to discover an ore vent, granting a small quantity of **mining
points** to begin with. Once scanned, ore vents will show what minerals
that ore vent will generate after they're fully tapped.

Scanning the vent again will trigger the extraction process. A small
drone will fly down, called the NODE drone, and buckle onto the vent.
Your job during wave defense is to protect the drone and to defeat waves
of randomly spawning mobs (dependent on if you're on lavaland or on
icebox). The quantity, duration, and time between waves is scaled to the
size of the vent you're protecting. Starting by scanning and protecting
lower tier vents earlier in the shift is a safer bet than doing a large
vent in the first few minutes. The drone has 500 health, and can take a
good few hits, but leaving it alone will cause it to meet an unfortunate
end quite quickly.

Cooperation can be your best asset, as mining with allies can greatly
help with wave defense, and mineral points are granted to anyone who
helps with defending the ore vent equally (So 500 * size tier,
regardless of how much help you receive). Once complete, the ore vent
will have a mining machine constructed on top of it, and will start to
dredge up **Boulders** from the earth automatically. More on boulders
later.

Ore vents can be located based on your mining scanner, and will provide
an appropriate audio cue based on if the ore vent has been discovered or
not, and once processed will no longer alert you to it's presence.

**Each station comes with a free vent that produces exclusively iron and
glass, free of charge.** This is to help with shifts where the station
may not have shaft miners to produce minerals, and to provide the
station with a baseline amount of minerals where none may exist
otherwise.

Mineral generation has been completely reworked. Previously, Mineral
Generation had a flat 13% spawn rate in-game. Once minerals spawned,
they would also have a chance to propagate their minerals to nearby
tiles, resulting in a rather massive pool of minerals that could spawn
throughout lavaland on the whole.

This tweaks that, by making minerals in walls spawn based on their
proximity to ore vents on maps that use cave generation. Both the
probability, and quantity of ores spawning in walls is scaled based on
distance, with ore vents looking like large caches of ores found in
walls. This makes following ores found in walls and checking their
quantity of minerals spawned a good indicator of how close you are to a
nearby vent in-round.

This means you can collect some points form both discovering ore vents
first, as well as collecting their surrounding ores, turn those in for
mining points, and then trading them in for gear upgrades to more
effectively take on ore vents. As a result of tweaking the balance of
this, the total amount of ores spawned in walls overall has been
decreased. However, by making more of the process time based, we still
result in a mostly balanced finished product.

On station, there are now three new machines. These are the BRM, the
Refinery, and the Smelter.

- The BRM acts as a teleporter. Instead of needing to carry boulders
back to the station, you can activate the BRM, and it will automatically
pick boulders to teleport back to itself. You can use this to teleport
boulders dredged up from lavaland onto the station for processing. **The
BRM will only lock on to boulders that are resting on an ore vent.**
Moving boulders back by hand will mean you'll have to haul it back by
hand.
- The refinery processes the non-metallic materials out of boulders.
This process sends the materials straight to the ORM, and collects
mining points from the ores smelted in the machine. Swiping with an ID
card lets you withdraw those points for your own personal account, but
remember that these points are for your whole team to share from. The
**Mining points obtained from this process is only 75% of the amount an
equivalent amount of ores would provide.**
- The smelter works nearly identically, however the smelter produces
metallic materials out of boulders instead.
- Once a boulder has had all of it's materials extracted, it's broken
down and deleted from the line. Otherwise, the boulder is spat out for
the next machine to process it (either the refinery or smelter).
- Once there's no minerals left in a boulder of any type, the refinery
or smelter will break the boulder down.
- Boulders **do not stack onto tiles with each other**, so they'll block
each other when pulled or when moving on a conveyor belt.

Boulders can also be processed by hand. Using a mining tool on a boulder
with right click will allow you to break down a boulder into it's
composite ores, but limits you to a maximum of 10 ore per boulder, where
the full amount can be extracted using the proper processing machines.
Also, processing by hand does deal small amounts of stamina damage over
time, do breaking a full large boulder can be particularly taxing.

Additional Boulder Processing Machines can be built, with the BRM board
being obtained from the Protolathe, while the Smelter and Refinery
boards being obtainable from the Autolathe instead. A _boulder
processing beacon_ can also be obtained from the mining points vendor as
a reward to assist with boulder processing. Boulder processing beacons
can be used to spawn in a new BRM, refinery, and smelter on the tile the
user is standing on, however **you'll still need to link them to the
ORM**!

All three machines can be upgraded with Stock Parts, allowing for **more
boulders to be processed at a time**. It does not, however, increase the
amount of minerals received from boulders, or points earned.

Mining borgs have been given some minor adjustments to compensate for
the changes to mining. Their mineral scanner, which now has an active
component to gameplay, is now a module as opposed to built into the mob.
This module allows for the same ability to discover and start waves of
monsters to fight.

Mining modules will find that their PKA now has a total of 90% mod
capacity as compared to the 80% they had before, to allow for more
robust defense of ore vents.

In addition, all borgs and AIs can interact with the BRM for boulder
collection.

Mining Mechs have had their utility tweaked as a result of these changes
as well. Mineral scanners to be used on mining mechs now have a larger
radius by comparison to their handheld cousins. Similarly, it now has an
active scanning button, which will actively discovery nearby ore vents.
To begin wave defense, you will need to hop out and scan a second time
however, so that you can properly accept the risks of drawing a horde of
bloodthirsty wildlife towards you and your companions.

Mechs can also manually process boulders, similar to mining tools using
their drill.

Golems, being more gentle and less aggressive than humans, while being
made out of LITERAL ROCKS, have a greater need to secure access to ores
and minerals to eat. As such, they have adapted to be able to do two new
things:

- Golems may now right click ore vents to be able to manually haul a
boulder out of the vent. This costs a hefty amount of stamina, but it
allows for golems to avoid combat during regular gameplay.
- Golems may now left click a boulder with an open hand in order to
manually process a boulder like a pickaxe. While not faster, it is
consistent and prevents golems from starving if they have access to a
vent, but no ores, somehow.

The labor camp, being a camp for rehabilitation and ~~excessive manual
labor~~ has been tweaked. Boulders now replace the random minerals
located on their island, and to acquire their prizes inside, much be
excavated and then broken out of the rock. Now YOU TOO can excavate
minerals and become a true mineral hero by working your way to freedom.

As a result of fewer mining points being available across the map due to
the new ore spawning mechanics, and the shift in how and when ores will
be coming in, almost every progress based mining point cost has been
reduced by around 10-20%. Many numbers are still subject to change at
present, but the idea is that core progress unlocks should be made a bit
more available earlier in the round before players can start to solo or
duo larger or more difficult ore vents, after which they'll be rolling
in ores.

Every once in awhile, an unusual boulder will get hauled up from the
mineral rich depths of lavaland. These **Artifact boulders** can
occasionally produce rare items, but for now they've mostly just been
pulling up **Strange objects** for science. Nanotrasen Natural Sciences
department will reward you extra points to be collected by boulder
processing machines for successfully extracting one. In the future, this
opens up a passive reward space that mining can reward to the station,
like providing cytology DNA samples, ancient seeds, or other artifacts.

- Boulders can be stored in all varieties of ore boxes (ground, mech)
should you choose, however as mentioned it's best to leave them where
they spawn and teleport them to the station for convenience.
- Maps that are not subject to cave generation will find that they are
largely untouched in terms of mineral balance.
- Future or existing ruins can now be tweaked to have a mineral balance
cost, as the ore vent ruin does. This will allow us to spawn in more
interesting ruins for pre-made combat challenges.
- There are unique ore vents that spawn across the map, that will summon
a boss mob relevant to that map. If the boss mob is defeated, that vent
will spawn large boulders pulling from every possible ore type that can
spawn. Not for the faint of heart!
- Similarly, the number of ore vents and mineral budget is now
adjustable in the cave generation procs, so maps may spawn with more or
less ore vents as desired for balance.
- Artifact boulders opens up a LOT of room for possible future content
like archaeology, xenoarch, artisci, and other design spaces!
- Megafauna STILL SPAWN ON THE MAP. They just happen to spawn in
addition to boss ore vents.
- **I'll add more to this as I get asked questions and remember things,
this is a huge PR and I'm confident I've missed at least something**

I outlined a lot of this in #78040, so I'll try and keep this relatively
snappy this time, while noting that I've made some concessions to make
the whole system a lot more playable while not trying to break out
design decisions that are at the end of the day, better for the game and
the overall resource balance in round.

Minerals are a very poorly balanced system, and have been since their
inception many years ago. We heavily rely on mineral balance in round,
and yet we've really only balanced it by introducing so much supply that
there's no equivalent exchange for materials that doesn't just heavily
flood the exchanged material. For example, items printed from materials
that are otherwise considered "rare" on master exist in such quantities
and they'll never practically run out in our allotted 90 minute time
slot design. This PR adjusts how ores spawn to a point where we can
minimize the amount of ores that need to exist on the map for mining to
be able to progress, while still providing enough resources for the
station that it covers the needs of the station adequately.

Miners will need to be more strategic about what resources they've
collected, and be able to make decisions about which vents are worth the
risk of attempting to fight, how to prepare for a wave defense, and when
to head back up for upgrades, while finally giving them at least some
kind of incentive to work together and use different equipment.
Resonators make cleaning up the caves around vent easy, sandbags set up
easy defenses for your vent, mechs can serve as a wider range radar
while mining, all while still providing a new gameplay loop to mining.

By limiting the amount of ores that can enter the round from the
massive, massive amounts that were coming into the round beforehand (see

gameplay to the processing of minerals. I have some plans for that,
however this PR already got bloated really REALLY badly due to scope
creep and the number of intersecting systems that rammed into each other
to make this PR possible. So that'll be next. Plus, as I've mentioned,
we open up places for ore processing to find fossils, relics, and other
things that can implemented down the line.

Overall, I don't expect this PR to save or kill ore balance, but we gain
a LOT more control over it through the use of our mining defines
attached to this PR, and at the end of the day, that's a great place to
start off of.

🆑
add: Added ore vents. Scanning them with mining scanners shows what
minerals they contain. Scan again to fight off a horde of beasts as your
drone assistant excavates the vent, so the ore vent will produce mineral
boulders!
bal: Ores that spawn in walls now spawn based on their proximity to ore
vents, with their chance to spawn and their minerals contained scaling
from low to high.
add: Added the BRM, Refinery, and Smelter. These pieces of equipment are
used to process ore boulders into minerals for the station. Stock Part
upgrades allow more boulders to be processed at one time. They collect
mining points as well, to be redeemed with an ID card swipe.
add: Boulders are teleported to the station via the BRM if left
untouched. Boulders can also be cracked open for a reduced amount of ore
using pickaxes or golems hands.
add: All stations come equipt with a pre-excavated ore vent, which
produces a basic supply of iron and glass only. Scan other vents for
your critical resources!
add: Look there's a shit ton of changes on mining, for more detail check
out the Pull Request: https://github.com/tgstation/tgstation/pull/78524.
sound: New sounds and noises for your high octane factorio-like
gameplay!
image: All new boulder sprites for the new minerals and rocks added to
the mining gameplay loop, as well as mining machines!
image: Overlays appear over vents when scanned to let you know their
contents at a glance when actively scanned with any mining scanners.
/🆑

---------

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: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
Co-authored-by: Jacquerel <hnevard@gmail.com>
Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
2024-01-22 01:33:55 +01:00

473 lines
18 KiB
Plaintext

/** # Oranges Ear
*
* turns out view() spends a significant portion of its processing time generating lists of contents of viewable turfs which includes EVERYTHING on it visible
* and the turf itself. there is an optimization to view() which makes it only iterate through either /obj or /mob contents, as well as normal list typechecking filters
*
* a fuckton of these are generated as part of its SS's init and stored in a list, when requested for a list of movables returned by the spatial grid or by some
* superset of the final output that must be narrowed down by view(), one of these gets put on every turf that contains the movables that need filtering
* and each is given references to the movables they represent. that way you can do for(var/mob/oranges_ear/ear in view(...)) and check what they reference
* as opposed to for(var/atom/movable/target in view(...)) and checking if they have the properties you want which leads to much larger lists generated by view()
* and also leads to iterating through more movables to filter them.
*
* TLDR: iterating through just mobs is much faster than all movables when iterating through view() on average, this system leverages that to boost speed
* enough to offset the cost of allocating the mobs
*
* named because the idea was first made by oranges and i didnt know what else to call it (note that this system was originally made for get_hearers_in_view())
*/
/mob/oranges_ear
icon_state = null
density = FALSE
move_resist = INFINITY
invisibility = INVISIBILITY_NONE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
logging = null
held_items = null //all of these are list objects that should not exist for something like us
faction = null
alerts = null
screens = null
client_colours = null
hud_possible = null
/// references to everything "on" the turf we are assigned to, that we care about. populated in assign() and cleared in unassign().
/// movables iside of other movables count as being "on" if they have get_turf(them) == our turf. intentionally not a lazylist
var/list/references = list()
/mob/oranges_ear/Initialize(mapload)
SHOULD_CALL_PARENT(FALSE)
if(flags_1 & INITIALIZED_1)
stack_trace("Warning: [src]([type]) initialized multiple times!")
flags_1 |= INITIALIZED_1
return INITIALIZE_HINT_NORMAL
/mob/oranges_ear/Destroy(force)
var/old_length = length(SSspatial_grid.pregenerated_oranges_ears)
SSspatial_grid.pregenerated_oranges_ears -= src
if(length(SSspatial_grid.pregenerated_oranges_ears) < old_length)
SSspatial_grid.number_of_oranges_ears -= 1
var/turf/our_loc = get_turf(src)
if(our_loc && our_loc.assigned_oranges_ear == src)
our_loc.assigned_oranges_ear = null
. = ..()
/mob/oranges_ear/Move()
SHOULD_CALL_PARENT(FALSE)
stack_trace("SOMEHOW A /mob/oranges_ear MOVED")
return FALSE
/mob/oranges_ear/abstract_move(atom/destination)
SHOULD_CALL_PARENT(FALSE)
stack_trace("SOMEHOW A /mob/oranges_ear MOVED")
return FALSE
/mob/oranges_ear/Bump()
SHOULD_CALL_PARENT(FALSE)
return FALSE
///clean this oranges_ear up for future use
/mob/oranges_ear/proc/unassign()
var/turf/turf_loc = loc
turf_loc.assigned_oranges_ear = null//trollface. our loc should ALWAYS be a turf, no exceptions. if it isnt then this doubles as an error message ;)
loc = null
references.Cut()
/**
* returns every hearaing movable in view to the turf of source not taking into account lighting
* useful when you need to maintain always being able to hear something if a sound is emitted from it and you can see it (and youre in range).
* otherwise this is just a more expensive version of get_hearers_in_LOS().
*
* * view_radius - what radius search circle we are using, worse performance as this increases
* * source - object at the center of our search area. everything in get_turf(source) is guaranteed to be part of the search area
*/
/proc/get_hearers_in_view(view_radius, atom/source)
var/turf/center_turf = get_turf(source)
if(!center_turf)
return
. = list()
if(view_radius <= 0)//special case for if only source cares
for(var/atom/movable/target as anything in center_turf)
var/list/recursive_contents = target.important_recursive_contents?[RECURSIVE_CONTENTS_HEARING_SENSITIVE]
if(recursive_contents)
. += recursive_contents
return .
var/list/hearables_from_grid = SSspatial_grid.orthogonal_range_search(source, RECURSIVE_CONTENTS_HEARING_SENSITIVE, view_radius)
if(!length(hearables_from_grid))//we know that something is returned by the grid, but we dont know if we need to actually filter down the output
return .
var/list/assigned_oranges_ears = SSspatial_grid.assign_oranges_ears(hearables_from_grid)
//this is the ENTIRE reason all this shit is worth it due to how view()-like procs and the contents list works and can be optimized
//internally, the contents list is secretly two linked lists, one for /obj's and one for /mob's (/atom/movable counts as /obj here)
//by default, for(var/atom/name in view()) iterates through both the /obj linked list then the /mob linked list of each turf
//but because what we want are only a tiny proportion of all movables, most of the things in the /obj contents list are not what we're looking for
//while every mob can hear. for this case view() and similar procs have an optimization to only look through 1 of these lists if it can (eg youre only looking for mobs)
//so by representing every hearing contents on a turf with a single /mob/oranges_ear containing references to all of them, we are:
//1. making view() only go through the smallest of the two linked lists per turf, which contains the type we're looking for at the end
//2. typechecking all mobs in the output to only actually return mobs of type /mob/oranges_ear
//on a whole this can outperform iterating through all movables in view() by ~2x especially when hearables are a tiny percentage of movables in view
//using hearers is a further optimization of that because for our purposes its the same as view except we dont have to set center's luminosity to 6 and then unset it
for(var/mob/oranges_ear/ear in hearers(view_radius, center_turf))
. += ear.references
for(var/mob/oranges_ear/remaining_ear as anything in assigned_oranges_ears)//we need to clean up our mess
remaining_ear.unassign()
return .
/**
* The exact same as get_hearers_in_view, but not limited by visibility. Does no filtering for traits, line of sight, or any other such criteria.
* Filtering is intended to be done by whatever calls this function.
*
* This function exists to allow for mobs to hear speech without line of sight, if such a thing is needed.
*
* * radius - what radius search circle we are using, worse performance as this increases
* * source - object at the center of our search area. everything in get_turf(source) is guaranteed to be part of the search area
*/
/proc/get_hearers_in_range(range, atom/source)
var/turf/center_turf = get_turf(source)
if(!center_turf)
return
. = list()
if(range <= 0)//special case for if only source cares
for(var/atom/movable/target as anything in center_turf)
var/list/recursive_contents = target.important_recursive_contents?[RECURSIVE_CONTENTS_HEARING_SENSITIVE]
if(recursive_contents)
. += recursive_contents
return .
var/list/hearables_from_grid = SSspatial_grid.orthogonal_range_search(source, RECURSIVE_CONTENTS_HEARING_SENSITIVE, range)
if(!length(hearables_from_grid))//we know that something is returned by the grid, but we dont know if we need to actually filter down the output
return .
for(var/atom/movable/hearable as anything in hearables_from_grid)
if (get_dist(center_turf, hearable) <= range)
. += hearable
return .
/**
* Returns a list of movable atoms that are hearing sensitive in view_radius and line of sight to source
* the majority of the work is passed off to the spatial grid if view_radius > 0
* because view() isnt a raycasting algorithm, this does not hold symmetry to it. something in view might not be hearable with this.
* if you want that use get_hearers_in_view() - however thats significantly more expensive
*
* * view_radius - what radius search circle we are using, worse performance as this increases but not as much as it used to
* * source - object at the center of our search area. everything in get_turf(source) is guaranteed to be part of the search area
*/
/proc/get_hearers_in_LOS(view_radius, atom/source)
var/turf/center_turf = get_turf(source)
if(!center_turf)
return
if(view_radius <= 0)//special case for if only source cares
. = list()
for(var/atom/movable/target as anything in center_turf)
var/list/hearing_contents = target.important_recursive_contents?[RECURSIVE_CONTENTS_HEARING_SENSITIVE]
if(hearing_contents)
. += hearing_contents
return
. = SSspatial_grid.orthogonal_range_search(source, SPATIAL_GRID_CONTENTS_TYPE_HEARING, view_radius)
for(var/atom/movable/target as anything in .)
var/turf/target_turf = get_turf(target)
var/distance = get_dist(center_turf, target_turf)
if(distance > view_radius)
. -= target
continue
else if(distance < 2) //we should always be able to see something 0 or 1 tiles away
continue
//this turf search algorithm is the worst scaling part of this proc, scaling worse than view() for small-moderate ranges and > 50 length contents_to_return
//luckily its significantly faster than view for large ranges in large spaces and/or relatively few contents_to_return
//i can do things that would scale better, but they would be slower for low volume searches which is the vast majority of the current workload
//maybe in the future a high volume algorithm would be worth it
var/turf/inbetween_turf = center_turf
//this is the lowest overhead way of doing a loop in dm other than a goto. distance is guaranteed to be >= steps taken to target by this algorithm
for(var/step_counter in 1 to distance)
inbetween_turf = get_step_towards(inbetween_turf, target_turf)
if(inbetween_turf == target_turf)//we've gotten to target's turf without returning due to turf opacity, so we must be able to see target
break
if(IS_OPAQUE_TURF(inbetween_turf))//this turf or something on it is opaque so we cant see through it
. -= target
break
/proc/get_hearers_in_radio_ranges(list/obj/item/radio/radios)
. = list()
// Returns a list of mobs who can hear any of the radios given in @radios
for(var/obj/item/radio/radio as anything in radios)
. |= get_hearers_in_LOS(radio.canhear_range, radio, FALSE)
///Calculate if two atoms are in sight, returns TRUE or FALSE
/proc/inLineOfSight(X1,Y1,X2,Y2,Z=1,PX1=16.5,PY1=16.5,PX2=16.5,PY2=16.5)
var/turf/T
if(X1 == X2)
if(Y1 == Y2)
return TRUE //Light cannot be blocked on same tile
else
var/s = SIGN(Y2-Y1)
Y1+=s
while(Y1 != Y2)
T=locate(X1,Y1,Z)
if(IS_OPAQUE_TURF(T))
return FALSE
Y1+=s
else
var/m=(32*(Y2-Y1)+(PY2-PY1))/(32*(X2-X1)+(PX2-PX1))
var/b=(Y1+PY1/32-0.015625)-m*(X1+PX1/32-0.015625) //In tiles
var/signX = SIGN(X2-X1)
var/signY = SIGN(Y2-Y1)
if(X1<X2)
b+=m
while(X1 != X2 || Y1 != Y2)
if(round(m*X1+b-Y1))
Y1+=signY //Line exits tile vertically
else
X1+=signX //Line exits tile horizontally
T=locate(X1,Y1,Z)
if(IS_OPAQUE_TURF(T))
return FALSE
return TRUE
/proc/is_in_sight(atom/first_atom, atom/second_atom)
var/turf/first_turf = get_turf(first_atom)
var/turf/second_turf = get_turf(second_atom)
if(!first_turf || !second_turf)
return FALSE
return inLineOfSight(first_turf.x, first_turf.y, second_turf.x, second_turf.y, first_turf.z)
///Returns all atoms present in a circle around the center
/proc/circle_range(center = usr,radius = 3)
var/turf/center_turf = get_turf(center)
var/list/atoms = new/list()
var/rsq = radius * (radius + 0.5)
for(var/atom/checked_atom as anything in range(radius, center_turf))
var/dx = checked_atom.x - center_turf.x
var/dy = checked_atom.y - center_turf.y
if(dx * dx + dy * dy <= rsq)
atoms += checked_atom
return atoms
///Returns all atoms present in a circle around the center but uses view() instead of range() (Currently not used)
/proc/circle_view(center=usr,radius=3)
var/turf/center_turf = get_turf(center)
var/list/atoms = new/list()
var/rsq = radius * (radius + 0.5)
for(var/atom/checked_atom as anything in view(radius, center_turf))
var/dx = checked_atom.x - center_turf.x
var/dy = checked_atom.y - center_turf.y
if(dx * dx + dy * dy <= rsq)
atoms += checked_atom
return atoms
///Returns the distance between two atoms
/proc/get_dist_euclidian(atom/first_location as turf|mob|obj, atom/second_location as turf|mob|obj)
var/dx = first_location.x - second_location.x
var/dy = first_location.y - second_location.y
var/dist = sqrt(dx ** 2 + dy ** 2)
return dist
///Returns a list of turfs around a center based on RANGE_TURFS()
/proc/circle_range_turfs(center = usr, radius = 3)
var/turf/center_turf = get_turf(center)
var/list/turfs = new/list()
var/rsq = radius * (radius + 0.5)
for(var/turf/checked_turf as anything in RANGE_TURFS(radius, center_turf))
var/dx = checked_turf.x - center_turf.x
var/dy = checked_turf.y - center_turf.y
if(dx * dx + dy * dy <= rsq)
turfs += checked_turf
return turfs
///Returns a list of turfs around a center based on view()
/proc/circle_view_turfs(center=usr,radius=3) //Is there even a diffrence between this proc and circle_range_turfs()? // Yes
var/turf/center_turf = get_turf(center)
var/list/turfs = new/list()
var/rsq = radius * (radius + 0.5)
for(var/turf/checked_turf in view(radius, center_turf))
var/dx = checked_turf.x - center_turf.x
var/dy = checked_turf.y - center_turf.y
if(dx * dx + dy * dy <= rsq)
turfs += checked_turf
return turfs
///Returns the list of turfs around the outside of a center based on RANGE_TURFS()
/proc/border_diamond_range_turfs(atom/center = usr, radius = 3)
var/turf/center_turf = get_turf(center)
var/list/turfs = list()
for(var/turf/checked_turf as anything in RANGE_TURFS(radius, center_turf))
var/dx = checked_turf.x - center_turf.x
var/dy = checked_turf.y - center_turf.y
var/abs_sum = abs(dx) + abs(dy)
if(abs_sum == radius)
turfs += checked_turf
return turfs
///Returns a slice of a list of turfs, defined by the ones that are inside the inner/outer angle's bounds
/proc/slice_off_turfs(atom/center, list/turf/turfs, inner_angle, outer_angle)
var/turf/center_turf = get_turf(center)
var/list/sliced_turfs = list()
for(var/turf/checked_turf as anything in turfs)
var/angle_to = get_angle(center_turf, checked_turf)
if(angle_to < inner_angle || angle_to > outer_angle)
continue
sliced_turfs += checked_turf
return sliced_turfs
/**
* Get a bounding box of a list of atoms.
*
* Arguments:
* - atoms - List of atoms. Can accept output of view() and range() procs.
*
* Returns: list(x1, y1, x2, y2)
*/
/proc/get_bbox_of_atoms(list/atoms)
var/list/list_x = list()
var/list/list_y = list()
for(var/_a in atoms)
var/atom/a = _a
list_x += a.x
list_y += a.y
return list(
min(list_x),
min(list_y),
max(list_x),
max(list_y))
/// Like view but bypasses luminosity check
/proc/get_hear(range, atom/source)
var/lum = source.luminosity
source.luminosity = 6
. = view(range, source)
source.luminosity = lum
///Returns the open turf next to the center in a specific direction
/proc/get_open_turf_in_dir(atom/center, dir)
var/turf/open/get_turf = get_step(center, dir)
if(istype(get_turf))
return get_turf
///Returns a list with all the adjacent open turfs. Clears the list of nulls in the end.
/proc/get_adjacent_open_turfs(atom/center)
var/list/hand_back = list()
// Inlined get_open_turf_in_dir, just to be fast
var/turf/open/new_turf = get_step(center, NORTH)
if(istype(new_turf))
hand_back += new_turf
new_turf = get_step(center, SOUTH)
if(istype(new_turf))
hand_back += new_turf
new_turf = get_step(center, EAST)
if(istype(new_turf))
hand_back += new_turf
new_turf = get_step(center, WEST)
if(istype(new_turf))
hand_back += new_turf
return hand_back
///Returns a list with all the adjacent areas by getting the adjacent open turfs
/proc/get_adjacent_open_areas(atom/center)
. = list()
var/list/adjacent_turfs = get_adjacent_open_turfs(center)
for(var/near_turf in adjacent_turfs)
. |= get_area(near_turf)
/**
* Returns a list with the names of the areas around a center at a certain distance
* Returns the local area if no distance is indicated
* Returns an empty list if the center is null
**/
/proc/get_areas_in_range(distance = 0, atom/center = usr)
if(!distance)
var/turf/center_turf = get_turf(center)
return center_turf ? list(center_turf.loc) : list()
if(!center)
return list()
var/list/turfs = RANGE_TURFS(distance, center)
var/list/areas = list()
for(var/turf/checked_turf as anything in turfs)
areas |= checked_turf.loc
return areas
///Returns a list of all areas that are adjacent to the center atom's area, clear the list of nulls at the end.
/proc/get_adjacent_areas(atom/center)
. = list(
get_area(get_ranged_target_turf(center, NORTH, 1)),
get_area(get_ranged_target_turf(center, SOUTH, 1)),
get_area(get_ranged_target_turf(center, EAST, 1)),
get_area(get_ranged_target_turf(center, WEST, 1))
)
list_clear_nulls(.)
///Checks if the mob provided (must_be_alone) is alone in an area
/proc/alone_in_area(area/the_area, mob/must_be_alone, check_type = /mob/living/carbon)
var/area/our_area = get_area(the_area)
for(var/carbon in GLOB.alive_mob_list)
if(!istype(carbon, check_type))
continue
if(carbon == must_be_alone)
continue
if(our_area == get_area(carbon))
return FALSE
return TRUE
/**
* Behaves like the orange() proc, but only looks in the outer range of the function (The "peel" of the orange).
* This is useful for things like checking if a mob is in a certain range, but not within a smaller range.
*
* @params outer_range - The outer range of the cicle to pull from.
* @params inner_range - The inner range of the circle to NOT pull from.
* @params center - The center of the circle to pull from, can be an atom (we'll apply get_turf() to it within circle_x_turfs procs.)
* @params view_based - If TRUE, we'll use circle_view_turfs instead of circle_range_turfs procs.
*/
/proc/turf_peel(outer_range, inner_range, center, view_based = FALSE)
if(inner_range > outer_range) // If the inner range is larger than the outer range, you're using this wrong.
CRASH("Turf peel inner range is larger than outer range!")
var/list/peel = list()
var/list/outer
var/list/inner
if(view_based)
outer = circle_view_turfs(center, outer_range)
inner = circle_view_turfs(center, inner_range)
else
outer = circle_range_turfs(center, outer_range)
inner = circle_range_turfs(center, inner_range)
for(var/turf/possible_spawn as anything in outer)
if(possible_spawn in inner)
continue
peel += possible_spawn
return peel