Files
Charlie Nolan d3584b1d1c LINDA -> MILLA (#25328)
* LINDA -> MILLA

* Tiles now change their atmos mode properly.

* Fixed a bug that made walls generate supercold air when deconstructed/destroyed.

* Apply suggestions from code review

Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
Co-authored-by: GDN <96800819+GDNgit@users.noreply.github.com>
Signed-off-by: Charlie Nolan <funnyman3595@gmail.com>

* Begone, var!

* MILLA -> milla

* Lint.

* More lint.

* Prevent negative/NaN atmos from spreading.

* Made fires start more reliably.

* Undef what the rest should not see.

* Enums are nice.

* Match LINDA better.

* Debugging verbs.

* Debug verb fixes.

* Labelled more lavaland stuff with ATMOS_MODE_LAVALAND

* Block superconductivity on a couple more things for Lavaland.

* Updated milla.dll

* Lavaland map fixes.

* Missed a spot.

* Begone, var! (again)

* Add libmilla_ci.so for continuous integration tests.

* Install/contributing instructions for MILLA.

* Directional plasmaglass windows on shuttles shouldn't stop protecting from heat after moving.

* Banish bad naming.

* Better naming and debug stats for SSair.

* Apply suggestions from code review

Co-authored-by: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Signed-off-by: Charlie Nolan <funnyman3595@gmail.com>

* Magic numbers and formatting.

* Don't screw up the temperature during reactions.

* Efficiency and readability.

* Fix hotspots.

* Removed redundant safety (MILLA will already output an error)

* Optimizations.

* oops

* TGS build script update.

* Change API of gas_mixture and how you get one from a turf.

* Get in there.

* so many vars

* MILLA binary updates.

* Lint

* Made MT cost more honest.

* Gave MILLA a read-only mode.

* More read-only vs synchronized mode work.

* Even more.

* Synchronize disposals.

* Updated a bunch of stuff to use SSair.synchronize or GM.synchronize.

* MILLA <3 Tracy

* lint

* cargo fmt

* more lint

* Test needed MILLA sync.

* Silence atmos machinery that's behaving as intended, but continue objecting to other potential failures to synchronize.

* Push new MILLA DLL

* Build MILLA

* Update PR instructions for MILLA.

* Add some fancy plasmaglass windows to the Lifebringers' ship.

* The golems remembered that they should insulate *all* their engines.

* First round of TM bugfixes.

* Build MILLA

* fix two lavaland ruin turf types

* bunch more maps

* fixes milla ci and makes the interesting turf verb better

* Solid turfs can no longer conduct heat (for now...)

* Made indoor tiles in beach and winter biodomes not lavaland air.

* Re-added random IT teleport, adjusted warning message

* Fixed a tinted window on Lavaland to be a plasglass window instead.

* Made firelocks and holofans heatproof

* Made RPD auto-wrench pipes if you have a wrench in your offhand.

* Runtime fixes.

* Made visuals update more reliably.

* Lint

* Update .github/CONTRIBUTING.md

Co-authored-by: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Signed-off-by: Charlie Nolan <funnyman3595@gmail.com>

* Build MILLA

* The Syndicate have installed tiny fans on their research base.

* The syndicate have figured out how to make plastitanium windows heatproof.

* This is getting out of hand. Now there are two of them.

* Un-break the auto-wrenching that I broke before committing.

* Build MILLA

* Build MILLA

* Massive MILLA refactor, assorted bugfixes.

* Build MILLA

* Explicit typing.

* Less sleeping/blocking.

* Build MILLA

* Yes, yes, this part can sleep.

* Build MILLA

* SSair reporting improvements.

* Build MILLA

* Please do not dock twice and splat yourself.

* Build MILLA

* Pls to not make SM go nuts.

* Build MILLA

* Build MILLA

* Added stricter lower bounds on plasmafire, fixing perpetually-burning SM.

* Build MILLA

* Lint

* Temporary logging, begone.

* Build MILLA

* Prevent hotspot strobing.

* Add in radiate_to_spess behavior.

* Build MILLA

* progress

* Compiling is good.

* bugfixes

* Progress.

* Ruins update.

* This one exists.

* Fixup temporary hotspots to last longer than one tick.

* Add/fix comments.

* Cut the slash.

* Fix test.

* No sleep for you.

* Build MILLA

* Prevent nested MILLA safety from runtiming.

* Build MILLA

* Runtime fix.

* Build MILLA

* Fix canisters not updating icon when gas contents change.

* Build MILLA

* Build MILLA

* Fixed an issue that made mechs very cold on environment air.

* Build MILLA

* oops

* Build MILLA

* Walls can make loud noises again.

* Build MILLA

* Fixes a rare runtime.

* Build MILLA

* Update .github/DOWNLOADING.md

Co-authored-by: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Signed-off-by: Charlie Nolan <funnyman3595@gmail.com>

* Review suggestions

* Lint

* Build MILLA

* bound_mixtures now only contains mixtures that can be written

* Build MILLA

* Apply suggestions from code review

Co-authored-by: Matt <116982774+Burzah@users.noreply.github.com>
Signed-off-by: Charlie Nolan <funnyman3595@gmail.com>

* Build MILLA

* Restore the original async behavior of pyro anomalies.

* Build MILLA

* Build MILLA

* Make the safety assertion in /datum/milla_safe safer.

* Build MILLA

---------

Signed-off-by: Charlie Nolan <funnyman3595@gmail.com>
Co-authored-by: FunnyMan3595 (Charlie Nolan) <funnyman@google.com>
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
Co-authored-by: GDN <96800819+GDNgit@users.noreply.github.com>
Co-authored-by: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com>
Co-authored-by: paradisess13[bot] <165046124+paradisess13[bot]@users.noreply.github.com>
Co-authored-by: warriorstar-orion <orion@snowfrost.garden>
Co-authored-by: GDN <Roanrichards1@Gmail.com>
2024-06-09 07:18:11 +00:00

499 lines
15 KiB
Plaintext

/proc/get_area_name(atom/X, format_text = FALSE)
var/area/A = isarea(X) ? X : get_area(X)
if(!A)
return null
return format_text ? format_text(A.name) : A.name
/proc/get_location_name(atom/X, format_text = FALSE)
var/area/A = isarea(X) ? X : get_area(X)
if(!A)
return null
return format_text ? format_text(A.name) : A.name
/proc/get_areas_in_range(dist=0, atom/center=usr)
if(!dist)
var/turf/T = get_turf(center)
return T ? list(T.loc) : list()
if(!center)
return list()
var/list/turfs = RANGE_TURFS(dist, center)
var/list/areas = list()
for(var/V in turfs)
var/turf/T = V
areas |= T.loc
return areas
/proc/get_open_turf_in_dir(atom/center, dir)
var/turf/T = get_ranged_target_turf(center, dir, 1)
if(T && !T.density)
return T
/proc/get_adjacent_open_turfs(atom/center)
. = list(get_open_turf_in_dir(center, NORTH),
get_open_turf_in_dir(center, SOUTH),
get_open_turf_in_dir(center, EAST),
get_open_turf_in_dir(center, WEST))
listclearnulls(.)
/proc/get_adjacent_open_areas(atom/center)
. = list()
var/list/adjacent_turfs = get_adjacent_open_turfs(center)
for(var/I in adjacent_turfs)
. |= get_area(I)
// Like view but bypasses luminosity check
/proc/hear(range, atom/source)
var/lum = source.luminosity
source.luminosity = 6
var/list/heard = view(range, source)
source.luminosity = lum
return heard
/proc/circlerange(center=usr,radius=3)
var/turf/centerturf = get_turf(center)
var/list/turfs = list()
var/rsq = radius * (radius+0.5)
for(var/atom/T in range(radius, centerturf))
var/dx = T.x - centerturf.x
var/dy = T.y - centerturf.y
if(dx*dx + dy*dy <= rsq)
turfs += T
//turfs += centerturf
return turfs
/proc/circleview(center=usr,radius=3)
var/turf/centerturf = get_turf(center)
var/list/atoms = list()
var/rsq = radius * (radius+0.5)
for(var/atom/A in view(radius, centerturf))
var/dx = A.x - centerturf.x
var/dy = A.y - centerturf.y
if(dx*dx + dy*dy <= rsq)
atoms += A
//turfs += centerturf
return atoms
/proc/ff_cansee(atom/A, atom/B)
var/AT = get_turf(A)
var/BT = get_turf(B)
if(AT == BT)
return 1
var/list/line = get_line(A, B)
for(var/turf/T in line)
if(T == AT || T == BT)
break
if(T.density)
return FALSE
return TRUE
/proc/get_dist_euclidian(atom/Loc1 as turf|mob|obj,atom/Loc2 as turf|mob|obj)
var/dx = Loc1.x - Loc2.x
var/dy = Loc1.y - Loc2.y
var/dist = sqrt(dx**2 + dy**2)
return dist
/proc/circle_edge_turfs(center = usr, radius = 3) // Get the turfs on the edge of a circle. Currently only works for radius 3
var/turf/centerturf = get_turf(center)
var/list/turfs = list()
var/rsq = radius * (radius+0.5)
for(var/turf/T in range(radius, centerturf))
var/dx = T.x - centerturf.x
var/dy = T.y - centerturf.y
if(dx * dx + dy * dy <= (rsq - radius))
continue
if(dx * dx + dy * dy <= rsq)
turfs += T
return turfs
/proc/circleviewturfs(center = usr, radius = 3) // All the turfs in a circle of the radius
var/turf/centerturf = get_turf(center)
var/list/turfs = list()
var/rsq = radius * (radius+0.5)
for(var/turf/T in view(radius, centerturf))
var/dx = T.x - centerturf.x
var/dy = T.y - centerturf.y
if(dx * dx + dy * dy <= rsq)
turfs += T
return turfs
/proc/circlerangeturfs(center = usr, radius = 3)
var/turf/centerturf = get_turf(center)
var/list/turfs = list()
var/rsq = radius * (radius + 0.5)
for(var/turf/T in range(radius, centerturf))
var/dx = T.x - centerturf.x
var/dy = T.y - centerturf.y
if(dx * dx + dy * dy <= rsq)
turfs += T
return turfs
//GLOBAL_VAR_INIT(debug_mob, 0)
// Will recursively loop through an atom's contents and check for mobs, then it will loop through every atom in that atom's contents.
// It will keep doing this until it checks every content possible. This will fix any problems with mobs, that are inside objects,
// being unable to hear people due to being in a box within a bag.
/proc/recursive_mob_check(atom/O, list/L = list(), recursion_limit = 3, client_check = TRUE, sight_check = TRUE)
if(!recursion_limit)
return L
for(var/atom/A in O.contents)
if(ismob(A))
var/mob/M = A
if(client_check && !M.client)
L |= recursive_mob_check(A, L, recursion_limit - 1, client_check, sight_check)
continue
if(sight_check && !isInSight(A, O))
continue
L |= M
for(var/mob/dead/observer/ghost in M.observers)
L |= ghost
//log_world("[recursion_limit] = [M] - [get_turf(M)] - ([M.x], [M.y], [M.z])")
if(isobj(A) || ismob(A))
L |= recursive_mob_check(A, L, recursion_limit - 1, client_check, sight_check)
return L
// The old system would loop through lists for a total of 5000 per function call, in an empty server.
// This new system will loop at around 1000 in an empty server.
/proc/get_mobs_in_view(R, atom/source, include_clientless = FALSE)
// Returns a list of mobs in range of R from source. Used in radio and say code.
var/turf/T = get_turf(source)
var/list/hear = list()
if(!T)
return hear
for(var/atom/A in hear(R, T))
if(ismob(A))
var/mob/M = A
if(M.client || include_clientless)
hear += M
if(isobj(A) || ismob(A))
hear |= recursive_mob_check(A, hear, 3, TRUE, FALSE)
return hear
/proc/is_same_root_atom(atom/one, atom/two)
return get_atom_on_turf(one) == get_atom_on_turf(two)
/proc/get_mobs_in_radio_ranges(list/obj/item/radio/radios)
. = list()
// Returns a list of mobs who can hear any of the radios given in @radios
var/list/speaker_coverage = list()
for(var/obj/item/radio/R in radios)
if(R)
//Cyborg checks. Receiving message uses a bit of cyborg's charge.
var/obj/item/radio/borg/BR = R
if(istype(BR) && BR.myborg)
var/mob/living/silicon/robot/borg = BR.myborg
var/datum/robot_component/CO = borg.get_component("radio")
if(!CO)
continue //No radio component (Shouldn't happen)
if(!borg.is_component_functioning("radio"))
continue //No power.
var/turf/speaker = get_turf(R)
if(speaker)
for(var/turf/T in hear(R.canhear_range,speaker))
var/obj/item/radio/oldR = speaker_coverage[T]
if(!istype(oldR))
speaker_coverage[T] = R
continue
if(oldR.canhear_range < R.canhear_range)
speaker_coverage[T] = R
// Try to find all the players who can hear the message
for(var/A in GLOB.player_list + GLOB.hear_radio_list)
var/mob/M = A
if(!M)
continue
var/turf/ear = get_turf(M)
if(!ear)
continue
// Ghostship is magic: Ghosts can hear radio chatter from anywhere
if(isobserver(M) && M.get_preference(PREFTOGGLE_CHAT_GHOSTRADIO))
. |= M
continue
if(!speaker_coverage[ear])
continue
var/obj/item/radio/R = speaker_coverage[ear]
if(!istype(R) || R.canhear_range > 0)
. |= M
continue
if(is_same_root_atom(M, speaker_coverage[ear]))
. |= M
/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 1 //Light cannot be blocked on same tile
else
var/s = SIMPLE_SIGN(Y2-Y1)
Y1+=s
while(Y1!=Y2)
T=locate(X1,Y1,Z)
if(T.opacity)
return 0
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 = SIMPLE_SIGN(X2-X1)
var/signY = SIMPLE_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(T.opacity)
return 0
return 1
/proc/isInSight(atom/A, atom/B)
var/turf/Aturf = get_turf(A)
var/turf/Bturf = get_turf(B)
if(!Aturf || !Bturf)
return 0
return inLineOfSight(Aturf.x, Aturf.y, Bturf.x, Bturf.y, Aturf.z)
/proc/get_cardinal_step_away(atom/start, atom/finish) //returns the position of a step from start away from finish, in one of the cardinal directions
//returns only NORTH, SOUTH, EAST, or WEST
var/dx = finish.x - start.x
var/dy = finish.y - start.y
if(abs(dy) > abs (dx)) //slope is above 1:1 (move horizontally in a tie)
if(dy > 0)
return get_step(start, SOUTH)
else
return get_step(start, NORTH)
else
if(dx > 0)
return get_step(start, WEST)
else
return get_step(start, EAST)
/proc/try_move_adjacent(atom/movable/AM)
var/turf/T = get_turf(AM)
for(var/direction in GLOB.cardinal)
if(AM.Move(get_step(T, direction)))
break
/proc/get_mob_by_key(key)
for(var/mob/M in GLOB.mob_list)
if(M.ckey == lowertext(key))
return M
return null
/proc/get_candidates(be_special_type, afk_bracket=3000, override_age=0, override_jobban=0)
var/roletext = get_roletext(be_special_type)
var/list/candidates = list()
// Keep looping until we find a non-afk candidate within the time bracket (we limit the bracket to 10 minutes (6000))
while(!length(candidates) && afk_bracket < 6000)
for(var/mob/dead/observer/G in GLOB.player_list)
if(G.client != null)
if(!(G.mind && G.mind.current && G.mind.current.stat != DEAD))
if(!G.client.is_afk(afk_bracket) && (be_special_type in G.client.prefs.be_special))
if(!override_jobban || (!jobban_isbanned(G, roletext) && !jobban_isbanned(G, ROLE_SYNDICATE)))
if(override_age || player_old_enough_antag(G.client,be_special_type))
candidates += G.client
afk_bracket += 600 // Add a minute to the bracket, for every attempt
return candidates
/proc/get_candidate_ghosts(be_special_type, afk_bracket=3000, override_age=0, override_jobban=0)
var/roletext = get_roletext(be_special_type)
var/list/candidates = list()
// Keep looping until we find a non-afk candidate within the time bracket (we limit the bracket to 10 minutes (6000))
while(!length(candidates) && afk_bracket < 6000)
for(var/mob/dead/observer/G in GLOB.player_list)
if(G.client != null)
if(!(G.mind && G.mind.current && G.mind.current.stat != DEAD))
if(!G.client.is_afk(afk_bracket) && (be_special_type in G.client.prefs.be_special))
if(!override_jobban || (!jobban_isbanned(G, roletext) && !jobban_isbanned(G, ROLE_SYNDICATE)))
if(override_age || player_old_enough_antag(G.client,be_special_type))
candidates += G
afk_bracket += 600 // Add a minute to the bracket, for every attempt
return candidates
/proc/remove_images_from_clients(image/I, list/show_to)
for(var/client/C in show_to)
C.images -= I
/proc/flick_overlay(image/I, list/show_to, duration)
for(var/client/C in show_to)
C.images += I
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(remove_images_from_clients), I, show_to), duration, TIMER_CLIENT_TIME)
/proc/flick_overlay_view(image/I, atom/target, duration) //wrapper for the above, flicks to everyone who can see the target atom
var/list/viewing = list()
for(var/m in viewers(target))
var/mob/M = m
if(M.client)
viewing += M.client
flick_overlay(I, viewing, duration)
/proc/get_active_player_count()
// Get active players who are playing in the round
var/active_players = 0
for(var/i = 1; i <= length(GLOB.player_list); i++)
var/mob/M = GLOB.player_list[i]
if(M && M.client)
if(isnewplayer(M)) // exclude people in the lobby
continue
else if(isobserver(M)) // Ghosts are fine if they were playing once (didn't start as observers)
var/mob/dead/observer/O = M
if(O.started_as_observer) // Exclude people who started as observers
continue
active_players++
return active_players
/proc/mobs_in_area(area/the_area, client_needed=0, moblist=GLOB.mob_list)
var/list/mobs_found[0]
for(var/mob/M in moblist)
if(client_needed && !M.client)
continue
if(the_area != get_area(M))
continue
mobs_found += M
return mobs_found
/proc/alone_in_area(area/the_area, mob/must_be_alone, check_type = /mob/living/carbon)
for(var/C in GLOB.alive_mob_list)
if(!istype(C, check_type))
continue
if(C == must_be_alone)
continue
if(the_area == get_area(C))
return 0
return 1
/proc/lavaland_equipment_pressure_check(turf/T)
. = FALSE
if(!istype(T))
return
var/datum/gas_mixture/environment = T.get_readonly_air()
if(!istype(environment))
return
var/pressure = environment.return_pressure()
if(pressure <= LAVALAND_EQUIPMENT_EFFECT_PRESSURE)
. = TRUE
/proc/pollCandidatesWithVeto(adminclient, adminusr, max_slots, Question, be_special_type, antag_age_check = FALSE, poll_time = 300, ignore_respawnability = FALSE, min_hours = FALSE, flashwindow = TRUE, check_antaghud = TRUE, source, role_cleanname)
var/list/willing_ghosts = SSghost_spawns.poll_candidates(Question, be_special_type, antag_age_check, poll_time, ignore_respawnability, min_hours, flashwindow, check_antaghud, source, role_cleanname)
var/list/selected_ghosts = list()
if(!length(willing_ghosts))
return selected_ghosts
var/list/candidate_ghosts = willing_ghosts.Copy()
to_chat(adminclient, "Candidate Ghosts:");
for(var/mob/dead/observer/G in candidate_ghosts)
if(G.key && G.client)
to_chat(adminclient, "- [G] ([G.key])");
else
candidate_ghosts -= G
for(var/i = max_slots, (i > 0 && length(candidate_ghosts)), i--)
var/this_ghost = input("Pick players. This will go on until there either no more ghosts to pick from or the [i] remaining slot(s) are full.", "Candidates") as null|anything in candidate_ghosts
candidate_ghosts -= this_ghost
selected_ghosts += this_ghost
return selected_ghosts
/proc/window_flash(client/C)
if(ismob(C))
var/mob/M = C
if(M.client)
C = M.client
if(!C || !(C.prefs.toggles2 & PREFTOGGLE_2_WINDOWFLASHING))
return
winset(C, "mainwindow", "flash=5")
/**
* Returns a list of vents that can be used as a potential spawn if they meet the criteria set by the arguments
*
* Will not include parent-less vents to the returned list.
* Arguments:
* * unwelded_only - Whether the list should only include vents that are unwelded
* * exclude_mobs_nearby - Whether to exclude vents that are near living mobs
* * min_network_size - The minimum length (non-inclusive) of the vent's parent network. A smaller number means vents in small networks (Security, Virology) will appear in the list
*/
/proc/get_valid_vent_spawns(unwelded_only = TRUE, exclude_mobs_nearby = FALSE, min_network_size = 50)
ASSERT(min_network_size >= 0)
. = list()
for(var/object in GLOB.all_vent_pumps) // This only contains vent_pumps so don't bother with type checking
var/obj/machinery/atmospherics/unary/vent_pump/vent = object
var/vent_z = vent.z
if(!is_station_level(vent_z))
continue
if(unwelded_only && vent.welded)
continue
if(exclude_mobs_nearby)
var/turf/T = get_turf(vent)
var/mobs_nearby = FALSE
for(var/mob/living/M in orange(7, T))
if(M.stat == DEAD) //we don't care about dead mobs
continue
if(!M.client && !istype(get_area(T), /area/station/science/xenobiology)) //we add an exception here for clientless mobs (apart from ones near xenobiology vents because it's usually filled with gold slime mobs who attack hostile mobs)
continue
mobs_nearby = TRUE
break
if(mobs_nearby)
continue
if(!vent.parent) // This seems to have been an issue in the past, so this is here until it's definitely fixed
// Can cause heavy message spam in some situations (e.g. pipenets breaking)
// log_debug("get_valid_vent_spawns(), vent has no parent: [vent], qdeled: [QDELETED(vent)], loc: [vent.loc]")
continue
if(length(vent.parent.other_atmosmch) <= min_network_size)
continue
. += vent
/**
* 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))