This commit is contained in:
Putnam3145
2022-03-30 12:35:12 -07:00
1281 changed files with 75215 additions and 68744 deletions
-209
View File
@@ -1,209 +0,0 @@
/*
A Star pathfinding algorithm
Returns a list of tiles forming a path from A to B, taking dense objects as well as walls, and the orientation of
windows along the route into account.
Use:
your_list = AStar(start location, end location, moving atom, distance proc, max nodes, maximum node depth, minimum distance to target, adjacent proc, atom id, turfs to exclude, check only simulated)
Optional extras to add on (in order):
Distance proc : the distance used in every A* calculation (length of path and heuristic)
MaxNodes: The maximum number of nodes the returned path can be (0 = infinite)
Maxnodedepth: The maximum number of nodes to search (default: 30, 0 = infinite)
Mintargetdist: Minimum distance to the target before path returns, could be used to get
near a target, but not right to it - for an AI mob with a gun, for example.
Adjacent proc : returns the turfs to consider around the actually processed node
Simulated only : whether to consider unsimulated turfs or not (used by some Adjacent proc)
Also added 'exclude' turf to avoid travelling over; defaults to null
Actual Adjacent procs :
/turf/proc/reachableAdjacentTurfs : returns reachable turfs in cardinal directions (uses simulated_only)
/turf/proc/reachableAdjacentAtmosTurfs : returns turfs in cardinal directions reachable via atmos
*/
#define PF_TIEBREAKER 0.005
//tiebreker weight.To help to choose between equal paths
//////////////////////
//datum/PathNode object
//////////////////////
#define MASK_ODD 85
#define MASK_EVEN 170
//A* nodes variables
/datum/PathNode
var/turf/source //turf associated with the PathNode
var/datum/PathNode/prevNode //link to the parent PathNode
var/f //A* Node weight (f = g + h)
var/g //A* movement cost variable
var/h //A* heuristic variable
var/nt //count the number of Nodes traversed
var/bf //bitflag for dir to expand.Some sufficiently advanced motherfuckery
/datum/PathNode/New(s,p,pg,ph,pnt,_bf)
source = s
prevNode = p
g = pg
h = ph
f = g + h*(1+ PF_TIEBREAKER)
nt = pnt
bf = _bf
/datum/PathNode/proc/setp(p,pg,ph,pnt)
prevNode = p
g = pg
h = ph
f = g + h*(1+ PF_TIEBREAKER)
nt = pnt
/datum/PathNode/proc/calc_f()
f = g + h
//////////////////////
//A* procs
//////////////////////
//the weighting function, used in the A* algorithm
/proc/PathWeightCompare(datum/PathNode/a, datum/PathNode/b)
return a.f - b.f
//reversed so that the Heap is a MinHeap rather than a MaxHeap
/proc/HeapPathWeightCompare(datum/PathNode/a, datum/PathNode/b)
return b.f - a.f
//wrapper that returns an empty list if A* failed to find a path
/proc/get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = 1)
var/l = SSpathfinder.mobs.getfree(caller)
while(!l)
stoplag(3)
l = SSpathfinder.mobs.getfree(caller)
var/list/path = AStar(caller, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only)
SSpathfinder.mobs.found(l)
if(!path)
path = list()
return path
/proc/cir_get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = 1)
var/l = SSpathfinder.circuits.getfree(caller)
while(!l)
stoplag(3)
l = SSpathfinder.circuits.getfree(caller)
var/list/path = AStar(caller, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only)
SSpathfinder.circuits.found(l)
if(!path)
path = list()
return path
/proc/AStar(caller, _end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = 1)
//sanitation
var/turf/end = get_turf(_end)
var/turf/start = get_turf(caller)
if(!start || !end)
stack_trace("Invalid A* start or destination")
return 0
if( start.z != end.z || start == end ) //no pathfinding between z levels
return 0
if(maxnodes)
//if start turf is farther than maxnodes from end turf, no need to do anything
if(call(start, dist)(end) > maxnodes)
return 0
maxnodedepth = maxnodes //no need to consider path longer than maxnodes
var/datum/Heap/open = new /datum/Heap(/proc/HeapPathWeightCompare) //the open list
var/list/openc = new() //open list for node check
var/list/path = null //the returned path, if any
//initialization
var/datum/PathNode/cur = new /datum/PathNode(start,null,0,call(start,dist)(end),0,15,1)//current processed turf
open.Insert(cur)
openc[start] = cur
//then run the main loop
while(!open.IsEmpty() && !path)
cur = open.Pop() //get the lower f turf in the open list
//get the lower f node on the open list
//if we only want to get near the target, check if we're close enough
var/closeenough
if(mintargetdist)
closeenough = call(cur.source,dist)(end) <= mintargetdist
//found the target turf (or close enough), let's create the path to it
if(cur.source == end || closeenough)
path = new()
path.Add(cur.source)
while(cur.prevNode)
cur = cur.prevNode
path.Add(cur.source)
break
//get adjacents turfs using the adjacent proc, checking for access with id
if((!maxnodedepth)||(cur.nt <= maxnodedepth))//if too many steps, don't process that path
for(var/i = 0 to 3)
var/f= 1<<i //get cardinal directions.1,2,4,8
if(cur.bf & f)
var/T = get_step(cur.source,f)
if(T != exclude)
var/datum/PathNode/CN = openc[T] //current checking turf
var/r=((f & MASK_ODD)<<1)|((f & MASK_EVEN)>>1) //getting reverse direction throught swapping even and odd bits.((f & 01010101)<<1)|((f & 10101010)>>1)
var/newg = cur.g + call(cur.source,dist)(T)
if(CN)
//is already in open list, check if it's a better way from the current turf
CN.bf &= 15^r //we have no closed, so just cut off exceed dir.00001111 ^ reverse_dir.We don't need to expand to checked turf.
if((newg < CN.g) )
if(call(cur.source,adjacent)(caller, T, id, simulated_only))
CN.setp(cur,newg,CN.h,cur.nt+1)
open.ReSort(CN)//reorder the changed element in the list
else
//is not already in open list, so add it
if(call(cur.source,adjacent)(caller, T, id, simulated_only))
CN = new(T,cur,newg,call(T,dist)(end),cur.nt+1,15^r)
open.Insert(CN)
openc[T] = CN
cur.bf = 0
CHECK_TICK
//reverse the path to get it from start to finish
if(path)
for(var/i = 1 to round(0.5*path.len))
path.Swap(i,path.len-i+1)
openc = null
//cleaning after us
return path
//Returns adjacent turfs in cardinal directions that are reachable
//simulated_only controls whether only simulated turfs are considered or not
/turf/proc/reachableAdjacentTurfs(caller, ID, simulated_only)
var/list/L = new()
var/turf/T
var/static/space_type_cache = typecacheof(/turf/open/space)
for(var/k in 1 to GLOB.cardinals.len)
T = get_step(src,GLOB.cardinals[k])
if(!T || (simulated_only && space_type_cache[T.type]))
continue
if(!T.density && !LinkBlockedWithAccess(T,caller, ID))
L.Add(T)
return L
/turf/proc/reachableTurftest(caller, var/turf/T, ID, simulated_only)
if(T && !T.density && !(simulated_only && SSpathfinder.space_type_cache[T.type]) && !LinkBlockedWithAccess(T,caller, ID))
return TRUE
//Returns adjacent turfs in cardinal directions that are reachable via atmos
/turf/proc/reachableAdjacentAtmosTurfs()
return atmos_adjacent_turfs
/turf/proc/LinkBlockedWithAccess(turf/T, caller, ID)
var/adir = get_dir(src, T)
var/rdir = ((adir & MASK_ODD)<<1)|((adir & MASK_EVEN)>>1)
for(var/obj/structure/window/W in src)
if(!W.CanAStarPass(ID, adir))
return 1
for(var/obj/machinery/door/window/W in src)
if(!W.CanAStarPass(ID, adir))
return 1
for(var/obj/O in T)
if(!O.CanAStarPass(ID, rdir, caller))
return 1
return 0
+103 -50
View File
@@ -1,32 +1,50 @@
/*
* Holds procs to help with list operations
* Contains groups:
* Misc
* Sorting
* Misc
* Sorting
*/
/*
* Misc
*/
#define LAZYINITLIST(L) if (!L) L = list()
#define LAZYINITLIST(L) if (!L) { L = list(); }
#define UNSETEMPTY(L) if (L && !length(L)) L = null
#define LAZYCOPY(L) (L ? L.Copy() : list() )
///Like LAZYCOPY - copies an input list if the list has entries, If it doesn't the assigned list is nulled
#define LAZYLISTDUPLICATE(L) (L ? L.Copy() : null )
#define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } }
//ambition start
#define LAZYCUT(L, S, E) if((length(L) >= S) && (E == 0 || length(L) >= (E - 1))) { L.Cut(S, E); if(!length(L)) { L = null; } }
//ambition end
#define LAZYADD(L, I) if(!L) { L = list(); } L += I;
#define LAZYOR(L, I) if(!L) { L = list(); } L |= I;
#define LAZYFIND(L, V) (L ? L.Find(V) : 0)
///returns L[I] if L exists and I is a valid index of L, runtimes if L is not a list
#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null)
#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V;
#define LAZYLEN(L) length(L)
///This is used to add onto lazy assoc list when the value you're adding is a /list/. This one has extra safety over lazyaddassoc because the value could be null (and thus cant be used to += objects)
#define LAZYADDASSOCLIST(L, K, V) if(!L) { L = list(); } L[K] += list(V);
//Sets a list to null
#define LAZYNULL(L) L = null
#define LAZYCLEARLIST(L) if(L) L.Cut()
#define SANITIZE_LIST(L) ( islist(L) ? L : list() )
#define reverseList(L) reverseRange(L.Copy())
#define LAZYADDASSOC_TG(L, K, V) if(!L) { L = list(); } L[K] += V;
///This is used to add onto lazy assoc list when the value you're adding is a /list/. This one has extra safety over lazyaddassoc because the value could be null (and thus cant be used to += objects)
#define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += list(V);
#define LAZYREMOVEASSOC(L, K, V) if(L) { if(L[K]) { L[K] -= V; if(!length(L[K])) L -= K; } if(!length(L)) L = null; }
#define LAZYACCESSASSOC(L, I, K) L ? L[I] ? L[I][K] ? L[I][K] : null : null : null
#define QDEL_LAZYLIST(L) for(var/I in L) qdel(I); L = null;
//These methods don't null the list
#define LAZYCOPY(L) (L ? L.Copy() : list() ) //Use LAZYLISTDUPLICATE instead if you want it to null with no entries
#define LAZYCLEARLIST(L) if(L) L.Cut() // Consider LAZYNULL instead
#define SANITIZE_LIST(L) ( islist(L) ? L : list() )
#define reverseList(L) reverseRange(L.Copy())
/// Performs an insertion on the given lazy list with the given key and value. If the value already exists, a new one will not be made.
#define LAZYORASSOCLIST(lazy_list, key, value) \
LAZYINITLIST(lazy_list); \
LAZYINITLIST(lazy_list[key]); \
lazy_list[key] |= value;
/// Passed into BINARY_INSERT to compare keys
#define COMPARE_KEY __BIN_LIST[__BIN_MID]
@@ -69,7 +87,7 @@
} while(FALSE)
//Returns a list in plain english as a string
/proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "")
/proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" )
var/total = length(input)
switch(total)
if (0)
@@ -92,6 +110,7 @@
/**
* English_list but associative supporting. Higher overhead.
* @depricated
*/
/proc/english_list_assoc(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "")
var/total = length(input)
@@ -119,6 +138,7 @@
return "[output][and_text][input[index]]"
//Returns list element or null. Should prevent "index out of bounds" error.
/// @depricated
/proc/listgetindex(list/L, index)
if(LAZYLEN(L))
if(isnum(index) && ISINTEGER(index))
@@ -129,17 +149,19 @@
return
//Return either pick(list) or null if list is not of type /list or is empty
/// @depricated
/proc/safepick(list/L)
if(LAZYLEN(L))
return pick(L)
//Checks if the list is empty
/// @depricated
/proc/isemptylist(list/L)
if(!L.len)
return TRUE
return FALSE
//Checks for specific types in a list
//Checks for specific types in a listc
/proc/is_type_in_list(atom/A, list/L)
if(!LAZYLEN(L) || !A)
return FALSE
@@ -180,43 +202,45 @@
/proc/typecache_filter_list_reverse(list/atoms, list/typecache)
RETURN_TYPE(/list)
. = list()
for(var/atom/A as anything in atoms)
if(!typecache[A.type])
. += A
for(var/atom/atom as anything in atoms)
if(!typecache[atom.type])
. += atom
/proc/typecache_filter_multi_list_exclusion(list/atoms, list/typecache_include, list/typecache_exclude)
. = list()
for(var/atom/A as anything in atoms)
if(typecache_include[A.type] && !typecache_exclude[A.type])
. += A
for(var/atom/atom as anything in atoms)
if(typecache_include[atom.type] && !typecache_exclude[atom.type])
. += atom
//Like typesof() or subtypesof(), but returns a typecache instead of a list
///Like typesof() or subtypesof(), but returns a typecache instead of a list
/proc/typecacheof(path, ignore_root_path, only_root_path = FALSE)
if(ispath(path))
var/list/types = list()
var/list/types
var/list/output = list()
if(only_root_path)
types = list(path)
output[path] = TRUE
else
types = ignore_root_path ? subtypesof(path) : typesof(path)
var/list/L = list()
for(var/T in types)
L[T] = TRUE
return L
for(var/T in types)
output[T] = TRUE
return output
else if(islist(path))
var/list/pathlist = path
var/list/L = list()
var/list/output = list()
if(ignore_root_path)
for(var/P in pathlist)
for(var/T in subtypesof(P))
L[T] = TRUE
for(var/current_path in pathlist)
for(var/subtype in subtypesof(current_path))
output[subtype] = TRUE
return output
if(only_root_path)
for(var/current_path in pathlist)
output[current_path] = TRUE
else
for(var/P in pathlist)
if(only_root_path)
L[P] = TRUE
else
for(var/T in typesof(P))
L[T] = TRUE
return L
for(var/current_path in pathlist)
for(var/subpath in typesof(current_path))
output[subpath] = TRUE
return output
/proc/typecacheof_assoc_list(list/pathlist, ignore_root_path = FALSE)
. = list()
@@ -275,9 +299,10 @@
//Picks a random element from a list based on a weighting system:
//1. Adds up the total of weights for each element
//2. Gets the total from 0% to 100% of previous total value.
//2. Gets a number between 1 and that total
//3. For each element in the list, subtracts its weighting from that number
//4. If that makes the number 0 or less, return that element.
//Will output null sometimes if you use decimals (e.g. 0.1 instead of 10) as rand() uses integers, not floats
/proc/pickweight(list/L, base_weight = 1)
var/total = 0
var/item
@@ -286,12 +311,14 @@
L[item] = base_weight
total += L[item]
total = rand() * total
total = rand(base_weight, total)
for (item in L)
total -= L[item]
total -=L [item]
if (total <= 0)
return item
return null
/proc/pickweightAllowZero(list/L) //The original pickweight proc will sometimes pick entries with zero weight. I'm not sure if changing the original will break anything, so I left it be.
var/total = 0
var/item
@@ -363,13 +390,13 @@
//Results will have a length of quality
return results
//Pick a random element from the list and remove it from the list.
/// Pick a random element from the list and remove it from the list.
/proc/pick_n_take(list/L)
RETURN_TYPE(L[_].type)
if(L.len)
var/picked = rand(1,L.len)
. = L[picked]
L.Cut(picked,picked+1) //Cut is far more efficient that Remove()
L.Cut(picked,picked+1) //Cut is far more efficient that Remove()
//Pick a random element from the list by weight and remove it from the list.
//Result is returned as a list in the format list(key, value)
@@ -465,7 +492,7 @@
//uses sortList() but uses the var's name specifically. This should probably be using mergeAtom() instead
/proc/sortNames(list/L, order=1)
return sortTim(L, order >= 0 ? /proc/cmp_name_asc : /proc/cmp_name_dsc)
return sortTim(L.Copy(), order >= 0 ? /proc/cmp_name_asc : /proc/cmp_name_dsc)
//Converts a bitfield to a list of numbers (or words if a wordlist is provided)
@@ -501,10 +528,12 @@
if (L[i] == val)
.++
/// Returns datum/data/record
/proc/find_record(field, value, list/L)
for(var/datum/data/record/R in L)
if(R.fields[field] == value)
return R
return null
//Move a single element from position fromIndex within a list, to position toIndex
@@ -514,10 +543,10 @@
//fromIndex and toIndex must be in the range [1,L.len+1]
//This will preserve associations ~Carnie
/proc/moveElement(list/L, fromIndex, toIndex)
if(fromIndex == toIndex || fromIndex+1 == toIndex) //no need to move
if(fromIndex == toIndex || fromIndex+1 == toIndex) //no need to move
return
if(fromIndex > toIndex)
++fromIndex //since a null will be inserted before fromIndex, the index needs to be nudged right by one
++fromIndex //since a null will be inserted before fromIndex, the index needs to be nudged right by one
L.Insert(toIndex, null)
L.Swap(fromIndex, toIndex)
@@ -529,10 +558,10 @@
//This will preserve associations ~Carnie
/proc/moveRange(list/L, fromIndex, toIndex, len=1)
var/distance = abs(toIndex - fromIndex)
if(len >= distance) //there are more elements to be moved than the distance to be moved. Therefore the same result can be achieved (with fewer operations) by moving elements between where we are and where we are going. The result being, our range we are moving is shifted left or right by dist elements
if(len >= distance) //there are more elements to be moved than the distance to be moved. Therefore the same result can be achieved (with fewer operations) by moving elements between where we are and where we are going. The result being, our range we are moving is shifted left or right by dist elements
if(fromIndex <= toIndex)
return //no need to move
fromIndex += len //we want to shift left instead of right
return //no need to move
fromIndex += len //we want to shift left instead of right
for(var/i=0, i<distance, ++i)
L.Insert(fromIndex, null)
@@ -552,7 +581,7 @@
//Note: if the two ranges overlap, only the destination order will be preserved fully, since some elements will be within both ranges ~Carnie
/proc/swapRange(list/L, fromIndex, toIndex, len=1)
var/distance = abs(toIndex - fromIndex)
if(len > distance) //there is an overlap, therefore swapping each element will require more swaps than inserting new elements
if(len > distance) //there is an overlap, therefore swapping each element will require more swaps than inserting new elements
if(fromIndex < toIndex)
toIndex += len
else
@@ -598,6 +627,12 @@
if(D.vars[varname] == value)
return D
//remove all nulls from a list
/proc/removeNullsFromList(list/L)
while(L.Remove(null))
continue
return L
//Copies a list, and all lists inside it recusively
//Does not copy any other reference type
/proc/deepCopyList(list/l)
@@ -630,11 +665,6 @@
used_key_list[input_key] = 1
return input_key
#if DM_VERSION > 514
#error Remie said that lummox was adding a way to get a lists
#error contents via list.values, if that is true remove this
#error otherwise, update the version and bug lummox
#endif
//Flattens a keyed list into a list of it's contents
/proc/flatten_list(list/key_list)
if(!islist(key_list))
@@ -711,6 +741,29 @@
ret += key
return ret
/proc/compare_list(list/l,list/d)
if(!islist(l) || !islist(d))
return FALSE
if(l.len != d.len)
return FALSE
for(var/i in 1 to l.len)
if(l[i] != d[i])
return FALSE
return TRUE
#define LAZY_LISTS_OR(left_list, right_list)\
( length(left_list)\
? length(right_list)\
? (left_list | right_list)\
: left_list.Copy()\
: length(right_list)\
? right_list.Copy()\
: null\
)
/proc/is_type_in_ref_list(path, list/L)
if(!ispath(path))//not a path
return
+17
View File
@@ -88,6 +88,19 @@
if (CONFIG_GET(flag/log_access))
WRITE_LOG(GLOB.world_game_log, "ACCESS: [text]")
/**
* Writes to a special log file if the log_suspicious_login config flag is set,
* which is intended to contain all logins that failed under suspicious circumstances.
*
* Mirrors this log entry to log_access when access_log_mirror is TRUE, so this proc
* doesn't need to be used alongside log_access and can replace it where appropriate.
*/
/proc/log_suspicious_login(text, access_log_mirror = TRUE)
if (CONFIG_GET(flag/log_suspicious_login))
WRITE_LOG(GLOB.world_suspicious_login_log, "SUSPICIOUS_ACCESS: [text]")
if(access_log_mirror)
log_access(text)
/proc/log_law(text)
if (CONFIG_GET(flag/log_law))
WRITE_LOG(GLOB.world_game_log, "LAW: [text]")
@@ -96,6 +109,10 @@
if (CONFIG_GET(flag/log_attack))
WRITE_LOG(GLOB.world_attack_log, "ATTACK: [text]")
/proc/log_victim(text)
if (CONFIG_GET(flag/log_victim))
WRITE_LOG(GLOB.world_victim_log, "VICTIM: [text]")
/proc/log_manifest(ckey, datum/mind/mind,mob/body, latejoin = FALSE)
if (CONFIG_GET(flag/log_manifest))
WRITE_LOG(GLOB.world_manifest_log, "[ckey] \\ [body.real_name] \\ [mind.assigned_role] \\ [mind.special_role ? mind.special_role : "NONE"] \\ [latejoin ? "LATEJOIN":"ROUNDSTART"]")
+3
View File
@@ -140,3 +140,6 @@ GLOBAL_VAR_INIT(cmp_field, "name")
/proc/cmp_typepaths_asc(A, B)
return sorttext("[B]","[A]")
/proc/cmp_playtime(list/A, list/B)
return A["playtime"] - B["playtime"]
+1 -1
View File
@@ -18,7 +18,7 @@
if(length(inhand_equipment))
for(var/path in inhand_equipment)
var/obj/item/I = new path
mannequin.equip_to_slot_if_possible(I, SLOT_HANDS, TRUE, TRUE, TRUE, TRUE)
mannequin.equip_to_slot_if_possible(I, ITEM_SLOT_HANDS, TRUE, TRUE, TRUE, TRUE)
var/icon/combined = new
+5 -1
View File
@@ -399,12 +399,16 @@
/proc/ScreenText(obj/O, maptext="", screen_loc="CENTER-7,CENTER-7", maptext_height=480, maptext_width=480)
if(!isobj(O))
O = new /atom/movable/screen/text()
O.maptext = maptext
O.maptext = MAPTEXT(maptext)
O.maptext_height = maptext_height
O.maptext_width = maptext_width
O.screen_loc = screen_loc
return O
/// Removes an image from a client's `.images`. Useful as a callback.
/proc/remove_image_from_client(image/image, client/remove_from)
remove_from?.images -= image
/proc/remove_images_from_clients(image/I, list/show_to)
for(var/client/C in show_to)
C.images -= I
-10
View File
@@ -86,16 +86,6 @@
// Keybindings
init_keybindings()
//Uplink Items
for(var/path in subtypesof(/datum/uplink_item))
var/datum/uplink_item/I = path
if(!initial(I.item)) //We add categories to a separate list.
GLOB.uplink_categories |= initial(I.category)
continue
GLOB.uplink_items += path
//(sub)typesof entries are listed by the order they are loaded in the code, so we'll have to rearrange them here.
GLOB.uplink_items = sortList(GLOB.uplink_items, /proc/cmp_uplink_items_dsc)
init_subtypes(/datum/crafting_recipe, GLOB.crafting_recipes)
INVOKE_ASYNC(GLOBAL_PROC, /proc/init_ref_coin_values) //so the current procedure doesn't sleep because of UNTIL()
+32 -27
View File
@@ -1,39 +1,45 @@
//////////////////////
//datum/Heap object
//datum/heap object
//////////////////////
/datum/Heap
/datum/heap
var/list/L
var/cmp
/datum/Heap/New(compare)
/datum/heap/New(compare)
L = new()
cmp = compare
/datum/Heap/proc/IsEmpty()
return !L.len
/datum/heap/Destroy(force, ...)
for(var/i in L) // because this is before the list helpers are loaded
qdel(i)
L = null
return ..()
//Insert and place at its position a new node in the heap
/datum/Heap/proc/Insert(atom/A)
/datum/heap/proc/is_empty()
return !length(L)
//insert and place at its position a new node in the heap
/datum/heap/proc/insert(atom/A)
L.Add(A)
Swim(L.len)
swim(length(L))
//removes and returns the first element of the heap
//(i.e the max or the min dependant on the comparison function)
/datum/Heap/proc/Pop()
if(!L.len)
/datum/heap/proc/pop()
if(!length(L))
return 0
. = L[1]
L[1] = L[L.len]
L.Cut(L.len)
if(L.len)
Sink(1)
L[1] = L[length(L)]
L.Cut(length(L))
if(length(L))
sink(1)
//Get a node up to its right position in the heap
/datum/Heap/proc/Swim(var/index)
/datum/heap/proc/swim(index)
var/parent = round(index * 0.5)
while(parent > 0 && (call(cmp)(L[index],L[parent]) > 0))
@@ -42,21 +48,21 @@
parent = round(index * 0.5)
//Get a node down to its right position in the heap
/datum/Heap/proc/Sink(var/index)
var/g_child = GetGreaterChild(index)
/datum/heap/proc/sink(index)
var/g_child = get_greater_child(index)
while(g_child > 0 && (call(cmp)(L[index],L[g_child]) < 0))
L.Swap(index,g_child)
index = g_child
g_child = GetGreaterChild(index)
g_child = get_greater_child(index)
//Returns the greater (relative to the comparison proc) of a node children
//or 0 if there's no child
/datum/Heap/proc/GetGreaterChild(var/index)
if(index * 2 > L.len)
/datum/heap/proc/get_greater_child(index)
if(index * 2 > length(L))
return 0
if(index * 2 + 1 > L.len)
if(index * 2 + 1 > length(L))
return index * 2
if(call(cmp)(L[index * 2],L[index * 2 + 1]) < 0)
@@ -65,12 +71,11 @@
return index * 2
//Replaces a given node so it verify the heap condition
/datum/Heap/proc/ReSort(atom/A)
/datum/heap/proc/resort(atom/A)
var/index = L.Find(A)
Swim(index)
Sink(index)
swim(index)
sink(index)
/datum/Heap/proc/List()
/datum/heap/proc/List()
. = L.Copy()
+1 -1
View File
@@ -992,7 +992,7 @@ world
letter = lowertext(letter)
var/image/text_image = new(loc = A)
text_image.maptext = "<font size = 4>[letter]</font>"
text_image.maptext = MAPTEXT("<font size = 4>[letter]</font>")
text_image.pixel_x = 7
text_image.pixel_y = 5
qdel(atom_icon)
@@ -1,85 +1,3 @@
/matrix/proc/TurnTo(old_angle, new_angle)
. = new_angle - old_angle
Turn(.) //BYOND handles cases such as -270, 360, 540 etc. DOES NOT HANDLE 180 TURNS WELL, THEY TWEEN AND LOOK LIKE SHIT
/atom/proc/SpinAnimation(speed = 10, loops = -1, clockwise = 1, segments = 3, parallel = TRUE)
if(!segments)
return
var/segment = 360/segments
if(!clockwise)
segment = -segment
var/list/matrices = list()
for(var/i in 1 to segments-1)
var/matrix/M = matrix(transform)
M.Turn(segment*i)
matrices += M
var/matrix/last = matrix(transform)
matrices += last
speed /= segments
if(parallel)
animate(src, transform = matrices[1], time = speed, loops , flags = ANIMATION_PARALLEL)
else
animate(src, transform = matrices[1], time = speed, loops)
for(var/i in 2 to segments) //2 because 1 is covered above
animate(transform = matrices[i], time = speed)
//doesn't have an object argument because this is "Stacking" with the animate call above
//3 billion% intentional
//Dumps the matrix data in format a-f
/matrix/proc/tolist()
. = list()
. += a
. += b
. += c
. += d
. += e
. += f
//Dumps the matrix data in a matrix-grid format
/*
a d 0
b e 0
c f 1
*/
/matrix/proc/togrid()
. = list()
. += a
. += d
. += 0
. += b
. += e
. += 0
. += c
. += f
. += 1
//The X pixel offset of this matrix
/matrix/proc/get_x_shift()
. = c
//The Y pixel offset of this matrix
/matrix/proc/get_y_shift()
. = f
/matrix/proc/get_x_skew()
. = b
/matrix/proc/get_y_skew()
. = d
//Skews a matrix in a particular direction
//Missing arguments are treated as no skew in that direction
//As Rotation is defined as a scale+skew, these procs will break any existing rotation
//Unless the result is multiplied against the current matrix
/matrix/proc/set_skew(x = 0, y = 0)
b = x
d = y
/////////////////////
// COLOUR MATRICES //
/////////////////////
@@ -114,12 +32,64 @@ list(0.393,0.349,0.272,0, 0.769,0.686,0.534,0, 0.189,0.168,0.131,0, 0,0,0,1, 0,0
return list(R + value,R,R,0, G,G + value,G,0, B,B,B + value,0, 0,0,0,1, 0,0,0,0)
/**
* Exxagerates or removes colors
*/
/proc/color_matrix_saturation_percent(percent)
if(percent == 0)
return color_matrix_identity()
percent = clamp(percent, -100, 100)
if(percent > 0)
percent *= 3
var/x = 1 + percent / 100
var/inv = 1 - x
var/R = LUMA_R * inv
var/G = LUMA_G * inv
var/B = LUMA_B * inv
return list(R + x,R,R, G,G + x,G, B,B,B + x)
//Changes distance colors have from rgb(127,127,127) grey
//1 is identity. 0 makes everything grey >1 blows out colors and greys
/proc/color_matrix_contrast(value)
var/add = (1 - value) / 2
return list(value,0,0,0, 0,value,0,0, 0,0,value,0, 0,0,0,1, add,add,add,0)
/**
* Exxagerates or removes brightness
*/
/proc/color_matrix_contrast_percent(percent)
var/static/list/delta_index = list(
0, 0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.11,
0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24,
0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42,
0.44, 0.46, 0.48, 0.5, 0.53, 0.56, 0.59, 0.62, 0.65, 0.68,
0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98,
1.0, 1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54,
1.60, 1.66, 1.72, 1.78, 1.84, 1.90, 1.96, 2.0, 2.12, 2.25,
2.37, 2.50, 2.62, 2.75, 2.87, 3.0, 3.2, 3.4, 3.6, 3.8,
4.0, 4.3, 4.7, 4.9, 5.0, 5.5, 6.0, 6.5, 6.8, 7.0,
7.3, 7.5, 7.8, 8.0, 8.4, 8.7, 9.0, 9.4, 9.6, 9.8,
10.0)
percent = clamp(percent, -100, 100)
if(percent == 0)
return color_matrix_identity()
var/x = 0
if (percent < 0)
x = 127 + percent / 100 * 127;
else
x = percent % 1
if(x == 0)
x = delta_index[percent]
else
x = delta_index[percent] * (1-x) + delta_index[percent+1] * x//use linear interpolation for more granularity.
x = x * 127 + 127
var/mult = x / 127
var/add = 0.5 * (127-x) / 255
return list(mult,0,0, 0,mult,0, 0,0,mult, add,add,add)
//Moves all colors angle degrees around the color wheel while maintaining intensity of the color and not affecting greys
//0 is identity, 120 moves reds to greens, 240 moves reds to blues
/proc/color_matrix_rotate_hue(angle)
@@ -134,6 +104,26 @@ round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), ro
0,0,0,1,
0,0,0,0)
/**
* Moves all colors angle degrees around the color wheel while maintaining intensity of the color and not affecting whites
* TODO: Need a version that only affects one color (ie shift red to blue but leave greens and blues alone)
*/
/proc/color_matrix_rotation(angle)
if(angle == 0)
return color_matrix_identity()
angle = clamp(angle, -180, 180)
var/cos = cos(angle)
var/sin = sin(angle)
var/constA = 0.143
var/constB = 0.140
var/constC = -0.283
return list(
LUMA_R + cos * (1-LUMA_R) + sin * -LUMA_R, LUMA_R + cos * -LUMA_R + sin * constA, LUMA_R + cos * -LUMA_R + sin * -(1-LUMA_R),
LUMA_G + cos * -LUMA_G + sin * -LUMA_G, LUMA_G + cos * (1-LUMA_G) + sin * constB, LUMA_G + cos * -LUMA_G + sin * LUMA_G,
LUMA_B + cos * -LUMA_B + sin * (1-LUMA_B), LUMA_B + cos * -LUMA_B + sin * constC, LUMA_B + cos * (1-LUMA_B) + sin * LUMA_B
)
//These next three rotate values about one axis only
//x is the red axis, y is the green axis, z is the blue axis.
/proc/color_matrix_rotate_x(angle)
@@ -183,3 +173,9 @@ round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), ro
*/
/proc/rgb_construct_color_matrix(rr = 1, rg, rb, gr, gg = 1, gb, br, bg, bb = 1, cr, cg, cb)
return list(rr, rg, rb, gr, gg, gb, br, bg, bb, cr, cg, cb)
/**
* Assembles a color matrix, defaulting to identity
*/
/proc/rgba_construct_color_matrix(rr = 1, rg, rb, ra, gr, gg = 1, gb, ga, br, bg, bb = 1, ba, ar, ag, ab, aa = 1, cr, cg, cb, ca)
return list(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba, ar, ag, ab, aa, cr, cg, cb, ca)
@@ -0,0 +1,86 @@
/matrix/proc/TurnTo(old_angle, new_angle)
. = new_angle - old_angle
Turn(.) //BYOND handles cases such as -270, 360, 540 etc. DOES NOT HANDLE 180 TURNS WELL, THEY TWEEN AND LOOK LIKE SHIT
/atom/proc/SpinAnimation(speed = 10, loops = -1, clockwise = 1, segments = 3, parallel = TRUE)
if(!segments)
return
var/segment = 360/segments
if(!clockwise)
segment = -segment
var/list/matrices = list()
for(var/i in 1 to segments-1)
var/matrix/M = matrix(transform)
M.Turn(segment*i)
matrices += M
var/matrix/last = matrix(transform)
matrices += last
speed /= segments
if(parallel)
animate(src, transform = matrices[1], time = speed, loops , flags = ANIMATION_PARALLEL)
else
animate(src, transform = matrices[1], time = speed, loops)
for(var/i in 2 to segments) //2 because 1 is covered above
animate(transform = matrices[i], time = speed)
//doesn't have an object argument because this is "Stacking" with the animate call above
//3 billion% intentional
//Dumps the matrix data in format a-f
/matrix/proc/tolist()
. = list()
. += a
. += b
. += c
. += d
. += e
. += f
//Dumps the matrix data in a matrix-grid format
/*
a d 0
b e 0
c f 1
*/
/matrix/proc/togrid()
. = list()
. += a
. += d
. += 0
. += b
. += e
. += 0
. += c
. += f
. += 1
//The X pixel offset of this matrix
/matrix/proc/get_x_shift()
. = c
//The Y pixel offset of this matrix
/matrix/proc/get_y_shift()
. = f
/matrix/proc/get_x_skew()
. = b
/matrix/proc/get_y_skew()
. = d
//Skews a matrix in a particular direction
//Missing arguments are treated as no skew in that direction
//As Rotation is defined as a scale+skew, these procs will break any existing rotation
//Unless the result is multiplied against the current matrix
/matrix/proc/set_skew(x = 0, y = 0)
b = x
d = y
/**
* constructs a transform matrix, defaulting to identity
*/
/proc/transform_matrix_construct(a = 1, b, c, d = 1, e, f)
return matrix(a, b, c, d, e, f)
+15
View File
@@ -458,6 +458,21 @@ GLOBAL_LIST_EMPTY(species_datums)
if(!HAS_TRAIT(L, TRAIT_PASSTABLE))
L.pass_flags &= ~PASSTABLE
/proc/dance_rotate(atom/movable/AM, datum/callback/callperrotate, set_original_dir=FALSE)
set waitfor = FALSE
var/originaldir = AM.dir
for(var/i in list(NORTH,SOUTH,EAST,WEST,EAST,SOUTH,NORTH,SOUTH,EAST,WEST,EAST,SOUTH))
if(!AM)
return
AM.setDir(i)
callperrotate?.Invoke()
sleep(1)
if(set_original_dir)
AM.setDir(originaldir)
/// Gets the client of the mob, allowing for mocking of the client.
/// You only need to use this if you know you're going to be mocking clients somewhere else.
#define GET_CLIENT(mob) (##mob.client || ##mob.mock_client)
//check if the person is dead, not sure where to put this
#define IS_DEAD_OR_INCAP(source) (source.incapacitated() || source.stat)
+359
View File
@@ -0,0 +1,359 @@
/**
* This file contains the stuff you need for using JPS (Jump Point Search) pathing, an alternative to A* that skips
* over large numbers of uninteresting tiles resulting in much quicker pathfinding solutions. Mind that diagonals
* cost the same as cardinal moves currently, so paths may look a bit strange, but should still be optimal.
*/
/**
* This is the proc you use whenever you want to have pathfinding more complex than "try stepping towards the thing".
* If no path was found, returns an empty list, which is important for bots like medibots who expect an empty list rather than nothing.
*
* Arguments:
* * caller: The movable atom that's trying to find the path
* * end: What we're trying to path to. It doesn't matter if this is a turf or some other atom, we're gonna just path to the turf it's on anyway
* * max_distance: The maximum number of steps we can take in a given path to search (default: 30, 0 = infinite)
* * mintargetdistance: Minimum distance to the target before path returns, could be used to get near a target, but not right to it - for an AI mob with a gun, for example.
* * id: An ID card representing what access we have and what doors we can open. Its location relative to the pathing atom is irrelevant
* * simulated_only: Whether we consider turfs without atmos simulation (AKA do we want to ignore space)
* * exclude: If we want to avoid a specific turf, like if we're a mulebot who already got blocked by some turf
* * skip_first: Whether or not to delete the first item in the path. This would be done because the first item is the starting tile, which can break movement for some creatures.
*/
/proc/get_path_to(caller, end, max_distance = 30, mintargetdist, id=null, simulated_only = TRUE, turf/exclude, skip_first=TRUE)
if(!caller || !get_turf(end))
return
var/l = SSpathfinder.mobs.getfree(caller)
while(!l)
stoplag(3)
l = SSpathfinder.mobs.getfree(caller)
var/list/path
var/datum/pathfind/pathfind_datum = new(caller, end, id, max_distance, mintargetdist, simulated_only, exclude)
path = pathfind_datum.search()
qdel(pathfind_datum)
SSpathfinder.mobs.found(l)
if(!path)
path = list()
if(length(path) > 0 && skip_first)
path.Cut(1,2)
return path
/**
* A helper macro to see if it's possible to step from the first turf into the second one, minding things like door access and directional windows.
* Note that this can only be used inside the [datum/pathfind][pathfind datum] since it uses variables from said datum.
* If you really want to optimize things, optimize this, cuz this gets called a lot.
*/
#define CAN_STEP(cur_turf, next) (next && !next.density && cur_turf.Adjacent(next) && !(simulated_only && SSpathfinder.space_type_cache[next.type]) && !cur_turf.LinkBlockedWithAccess(next,caller, id) && (next != avoid))
/// Another helper macro for JPS, for telling when a node has forced neighbors that need expanding
#define STEP_NOT_HERE_BUT_THERE(cur_turf, dirA, dirB) ((!CAN_STEP(cur_turf, get_step(cur_turf, dirA)) && CAN_STEP(cur_turf, get_step(cur_turf, dirB))))
/// The JPS Node datum represents a turf that we find interesting enough to add to the open list and possibly search for new tiles from
/datum/jps_node
/// The turf associated with this node
var/turf/tile
/// The node we just came from
var/datum/jps_node/previous_node
/// The A* node weight (f_value = number_of_tiles + heuristic)
var/f_value
/// The A* node heuristic (a rough estimate of how far we are from the goal)
var/heuristic
/// How many steps it's taken to get here from the start (currently pulling double duty as steps taken & cost to get here, since all moves incl diagonals cost 1 rn)
var/number_tiles
/// How many steps it took to get here from the last node
var/jumps
/// Nodes store the endgoal so they can process their heuristic without a reference to the pathfind datum
var/turf/node_goal
/datum/jps_node/New(turf/our_tile, datum/jps_node/incoming_previous_node, jumps_taken, turf/incoming_goal)
tile = our_tile
jumps = jumps_taken
if(incoming_goal) // if we have the goal argument, this must be the first/starting node
node_goal = incoming_goal
else if(incoming_previous_node) // if we have the parent, this is from a direct lateral/diagonal scan, we can fill it all out now
previous_node = incoming_previous_node
number_tiles = previous_node.number_tiles + jumps
node_goal = previous_node.node_goal
heuristic = get_dist(tile, node_goal)
f_value = number_tiles + heuristic
// otherwise, no parent node means this is from a subscan lateral scan, so we just need the tile for now until we call [datum/jps/proc/update_parent] on it
/datum/jps_node/Destroy(force, ...)
previous_node = null
return ..()
/datum/jps_node/proc/update_parent(datum/jps_node/new_parent)
previous_node = new_parent
node_goal = previous_node.node_goal
jumps = get_dist(tile, previous_node.tile)
number_tiles = previous_node.number_tiles + jumps
heuristic = get_dist(tile, node_goal)
f_value = number_tiles + heuristic
/// TODO: Macro this to reduce proc overhead
/proc/HeapPathWeightCompare(datum/jps_node/a, datum/jps_node/b)
return b.f_value - a.f_value
/// The datum used to handle the JPS pathfinding, completely self-contained
/datum/pathfind
/// The thing that we're actually trying to path for
var/atom/movable/caller
/// The turf where we started at
var/turf/start
/// The turf we're trying to path to (note that this won't track a moving target)
var/turf/end
/// The open list/stack we pop nodes out from (TODO: make this a normal list and macro-ize the heap operations to reduce proc overhead)
var/datum/heap/open
///An assoc list that serves as the closed list & tracks what turfs came from where. Key is the turf, and the value is what turf it came from
var/list/sources
/// The list we compile at the end if successful to pass back
var/list/path
// general pathfinding vars/args
/// An ID card representing what access we have and what doors we can open. Its location relative to the pathing atom is irrelevant
var/obj/item/card/id/id
/// How far away we have to get to the end target before we can call it quits
var/mintargetdist = 0
/// I don't know what this does vs , but they limit how far we can search before giving up on a path
var/max_distance = 30
/// Space is big and empty, if this is TRUE then we ignore pathing through unsimulated tiles
var/simulated_only
/// A specific turf we're avoiding, like if a mulebot is being blocked by someone t-posing in a doorway we're trying to get through
var/turf/avoid
/datum/pathfind/New(atom/movable/caller, atom/goal, id, max_distance, mintargetdist, simulated_only, avoid)
src.caller = caller
end = get_turf(goal)
open = new /datum/heap(/proc/HeapPathWeightCompare)
sources = new()
src.id = id
src.max_distance = max_distance
src.mintargetdist = mintargetdist
src.simulated_only = simulated_only
src.avoid = avoid
/**
* search() is the proc you call to kick off and handle the actual pathfinding, and kills the pathfind datum instance when it's done.
*
* If a valid path was found, it's returned as a list. If invalid or cross-z-level params are entered, or if there's no valid path found, we
* return null, which [/proc/get_path_to] translates to an empty list (notable for simple bots, who need empty lists)
*/
/datum/pathfind/proc/search()
start = get_turf(caller)
if(!start || !end)
stack_trace("Invalid A* start or destination")
return
if(start.z != end.z || start == end ) //no pathfinding between z levels
return
if(max_distance && (max_distance < get_dist(start, end))) //if start turf is farther than max_distance from end turf, no need to do anything
return
//initialization
var/datum/jps_node/current_processed_node = new (start, -1, 0, end)
open.insert(current_processed_node)
sources[start] = start // i'm sure this is fine
//then run the main loop
while(!open.is_empty() && !path)
if(!caller)
return
current_processed_node = open.pop() //get the lower f_value turf in the open list
if(max_distance && (current_processed_node.number_tiles > max_distance))//if too many steps, don't process that path
continue
var/turf/current_turf = current_processed_node.tile
for(var/scan_direction in list(EAST, WEST, NORTH, SOUTH))
lateral_scan_spec(current_turf, scan_direction, current_processed_node)
for(var/scan_direction in list(NORTHEAST, SOUTHEAST, NORTHWEST, SOUTHWEST))
diag_scan_spec(current_turf, scan_direction, current_processed_node)
CHECK_TICK
//we're done! reverse the path to get it from start to finish
if(path)
for(var/i = 1 to round(0.5 * length(path)))
path.Swap(i, length(path) - i + 1)
sources = null
qdel(open)
return path
/// Called when we've hit the goal with the node that represents the last tile, then sets the path var to that path so it can be returned by [datum/pathfind/proc/search]
/datum/pathfind/proc/unwind_path(datum/jps_node/unwind_node)
path = new()
var/turf/iter_turf = unwind_node.tile
path.Add(iter_turf)
while(unwind_node.previous_node)
var/dir_goal = get_dir(iter_turf, unwind_node.previous_node.tile)
for(var/i = 1 to unwind_node.jumps)
iter_turf = get_step(iter_turf,dir_goal)
path.Add(iter_turf)
unwind_node = unwind_node.previous_node
/**
* For performing lateral scans from a given starting turf.
*
* These scans are called from both the main search loop, as well as subscans for diagonal scans, and they treat finding interesting turfs slightly differently.
* If we're doing a normal lateral scan, we already have a parent node supplied, so we just create the new node and immediately insert it into the heap, ezpz.
* If we're part of a subscan, we still need for the diagonal scan to generate a parent node, so we return a node datum with just the turf and let the diag scan
* proc handle transferring the values and inserting them into the heap.
*
* Arguments:
* * original_turf: What turf did we start this scan at?
* * heading: What direction are we going in? Obviously, should be cardinal
* * parent_node: Only given for normal lateral scans, if we don't have one, we're a diagonal subscan.
*/
/datum/pathfind/proc/lateral_scan_spec(turf/original_turf, heading, datum/jps_node/parent_node)
var/steps_taken = 0
var/turf/current_turf = original_turf
var/turf/lag_turf = original_turf
while(TRUE)
if(path)
return
lag_turf = current_turf
current_turf = get_step(current_turf, heading)
steps_taken++
if(!CAN_STEP(lag_turf, current_turf))
return
if(current_turf == end || (mintargetdist && (get_dist(current_turf, end) <= mintargetdist)))
var/datum/jps_node/final_node = new(current_turf, parent_node, steps_taken)
sources[current_turf] = original_turf
if(parent_node) // if this is a direct lateral scan we can wrap up, if it's a subscan from a diag, we need to let the diag make their node first, then finish
unwind_path(final_node)
return final_node
else if(sources[current_turf]) // already visited, essentially in the closed list
return
else
sources[current_turf] = original_turf
if(parent_node && parent_node.number_tiles + steps_taken > max_distance)
return
var/interesting = FALSE // have we found a forced neighbor that would make us add this turf to the open list?
switch(heading)
if(NORTH)
if(STEP_NOT_HERE_BUT_THERE(current_turf, WEST, NORTHWEST) || STEP_NOT_HERE_BUT_THERE(current_turf, EAST, NORTHEAST))
interesting = TRUE
if(SOUTH)
if(STEP_NOT_HERE_BUT_THERE(current_turf, WEST, SOUTHWEST) || STEP_NOT_HERE_BUT_THERE(current_turf, EAST, SOUTHEAST))
interesting = TRUE
if(EAST)
if(STEP_NOT_HERE_BUT_THERE(current_turf, NORTH, NORTHEAST) || STEP_NOT_HERE_BUT_THERE(current_turf, SOUTH, SOUTHEAST))
interesting = TRUE
if(WEST)
if(STEP_NOT_HERE_BUT_THERE(current_turf, NORTH, NORTHWEST) || STEP_NOT_HERE_BUT_THERE(current_turf, SOUTH, SOUTHWEST))
interesting = TRUE
if(interesting)
var/datum/jps_node/newnode = new(current_turf, parent_node, steps_taken)
if(parent_node) // if we're a diagonal subscan, we'll handle adding ourselves to the heap in the diag
open.insert(newnode)
return newnode
/**
* For performing diagonal scans from a given starting turf.
*
* Unlike lateral scans, these only are called from the main search loop, so we don't need to worry about returning anything,
* though we do need to handle the return values of our lateral subscans of course.
*
* Arguments:
* * original_turf: What turf did we start this scan at?
* * heading: What direction are we going in? Obviously, should be diagonal
* * parent_node: We should always have a parent node for diagonals
*/
/datum/pathfind/proc/diag_scan_spec(turf/original_turf, heading, datum/jps_node/parent_node)
var/steps_taken = 0
var/turf/current_turf = original_turf
var/turf/lag_turf = original_turf
while(TRUE)
if(path)
return
lag_turf = current_turf
current_turf = get_step(current_turf, heading)
steps_taken++
if(!CAN_STEP(lag_turf, current_turf))
return
if(current_turf == end || (mintargetdist && (get_dist(current_turf, end) <= mintargetdist)))
var/datum/jps_node/final_node = new(current_turf, parent_node, steps_taken)
sources[current_turf] = original_turf
unwind_path(final_node)
return
else if(sources[current_turf]) // already visited, essentially in the closed list
return
else
sources[current_turf] = original_turf
if(parent_node.number_tiles + steps_taken > max_distance)
return
var/interesting = FALSE // have we found a forced neighbor that would make us add this turf to the open list?
var/datum/jps_node/possible_child_node // otherwise, did one of our lateral subscans turn up something?
switch(heading)
if(NORTHWEST)
if(STEP_NOT_HERE_BUT_THERE(current_turf, EAST, NORTHEAST) || STEP_NOT_HERE_BUT_THERE(current_turf, SOUTH, SOUTHWEST))
interesting = TRUE
else
possible_child_node = (lateral_scan_spec(current_turf, WEST) || lateral_scan_spec(current_turf, NORTH))
if(NORTHEAST)
if(STEP_NOT_HERE_BUT_THERE(current_turf, WEST, NORTHWEST) || STEP_NOT_HERE_BUT_THERE(current_turf, SOUTH, SOUTHEAST))
interesting = TRUE
else
possible_child_node = (lateral_scan_spec(current_turf, EAST) || lateral_scan_spec(current_turf, NORTH))
if(SOUTHWEST)
if(STEP_NOT_HERE_BUT_THERE(current_turf, EAST, SOUTHEAST) || STEP_NOT_HERE_BUT_THERE(current_turf, NORTH, NORTHWEST))
interesting = TRUE
else
possible_child_node = (lateral_scan_spec(current_turf, SOUTH) || lateral_scan_spec(current_turf, WEST))
if(SOUTHEAST)
if(STEP_NOT_HERE_BUT_THERE(current_turf, WEST, SOUTHWEST) || STEP_NOT_HERE_BUT_THERE(current_turf, NORTH, NORTHEAST))
interesting = TRUE
else
possible_child_node = (lateral_scan_spec(current_turf, SOUTH) || lateral_scan_spec(current_turf, EAST))
if(interesting || possible_child_node)
var/datum/jps_node/newnode = new(current_turf, parent_node, steps_taken)
open.insert(newnode)
if(possible_child_node)
possible_child_node.update_parent(newnode)
open.insert(possible_child_node)
if(possible_child_node.tile == end || (mintargetdist && (get_dist(possible_child_node.tile, end) <= mintargetdist)))
unwind_path(possible_child_node)
return
/**
* For seeing if we can actually move between 2 given turfs while accounting for our access and the caller's pass_flags
*
* Arguments:
* * caller: The movable, if one exists, being used for mobility checks to see what tiles it can reach
* * ID: An ID card that decides if we can gain access to doors that would otherwise block a turf
* * simulated_only: Do we only worry about turfs with simulated atmos, most notably things that aren't space?
*/
/turf/proc/LinkBlockedWithAccess(turf/destination_turf, caller, ID)
var/actual_dir = get_dir(src, destination_turf)
for(var/obj/structure/window/iter_window in src)
if(!iter_window.CanAStarPass(ID, actual_dir))
return TRUE
for(var/obj/machinery/door/window/iter_windoor in src)
if(!iter_windoor.CanAStarPass(ID, actual_dir))
return TRUE
var/reverse_dir = get_dir(destination_turf, src)
for(var/obj/iter_object in destination_turf)
if(!iter_object.CanAStarPass(ID, reverse_dir, caller))
return TRUE
return FALSE
#undef CAN_STEP
#undef STEP_NOT_HERE_BUT_THERE
+6 -2
View File
@@ -57,13 +57,17 @@
SScommunications.send_message(M)
/proc/minor_announce(message, title = "Attention:", alert)
/proc/minor_announce(message, title = "Attention:", alert, html_encode = TRUE)
if(!message)
return
if (html_encode)
title = html_encode(title)
message = html_encode(message)
for(var/mob/M in GLOB.player_list)
if(!isnewplayer(M) && M.can_hear())
to_chat(M, "<span class='big bold'><font color = red>[html_encode(title)]</font color><BR>[html_encode(message)]</span><BR>")
to_chat(M, "[span_minorannounce("<font color = red>[title]</font color><BR>[message]")]<BR>")
if(M.client.prefs.toggles & SOUND_ANNOUNCEMENTS)
if(alert)
SEND_SOUND(M, sound('sound/misc/notice1.ogg'))
+9 -9
View File
@@ -203,62 +203,62 @@
/mob/living/carbon/human/p_they(capitalized, temp_gender)
var/list/obscured = check_obscured_slots()
var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
if((SLOT_W_UNIFORM in obscured) && skipface)
if((ITEM_SLOT_ICLOTHING in obscured) && skipface)
temp_gender = PLURAL
return ..()
/mob/living/carbon/human/p_their(capitalized, temp_gender)
var/list/obscured = check_obscured_slots()
var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
if((SLOT_W_UNIFORM in obscured) && skipface)
if((ITEM_SLOT_ICLOTHING in obscured) && skipface)
temp_gender = PLURAL
return ..()
/mob/living/carbon/human/p_them(capitalized, temp_gender)
var/list/obscured = check_obscured_slots()
var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
if((SLOT_W_UNIFORM in obscured) && skipface)
if((ITEM_SLOT_ICLOTHING in obscured) && skipface)
temp_gender = PLURAL
return ..()
/mob/living/carbon/human/p_have(temp_gender)
var/list/obscured = check_obscured_slots()
var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
if((SLOT_W_UNIFORM in obscured) && skipface)
if((ITEM_SLOT_ICLOTHING in obscured) && skipface)
temp_gender = PLURAL
return ..()
/mob/living/carbon/human/p_are(temp_gender)
var/list/obscured = check_obscured_slots()
var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
if((SLOT_W_UNIFORM in obscured) && skipface)
if((ITEM_SLOT_ICLOTHING in obscured) && skipface)
temp_gender = PLURAL
return ..()
/mob/living/carbon/human/p_were(temp_gender)
var/list/obscured = check_obscured_slots()
var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
if((SLOT_W_UNIFORM in obscured) && skipface)
if((ITEM_SLOT_ICLOTHING in obscured) && skipface)
temp_gender = PLURAL
return ..()
/mob/living/carbon/human/p_do(temp_gender)
var/list/obscured = check_obscured_slots()
var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
if((SLOT_W_UNIFORM in obscured) && skipface)
if((ITEM_SLOT_ICLOTHING in obscured) && skipface)
temp_gender = PLURAL
return ..()
/mob/living/carbon/human/p_s(temp_gender)
var/list/obscured = check_obscured_slots()
var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
if((SLOT_W_UNIFORM in obscured) && skipface)
if((ITEM_SLOT_ICLOTHING in obscured) && skipface)
temp_gender = PLURAL
return ..()
/mob/living/carbon/human/p_es(temp_gender)
var/list/obscured = check_obscured_slots()
var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))
if((SLOT_W_UNIFORM in obscured) && skipface)
if((ITEM_SLOT_ICLOTHING in obscured) && skipface)
temp_gender = PLURAL
return ..()
+17 -3
View File
@@ -1,4 +1,4 @@
// Ensure the frequency is within bounds of what it should be sending/receiving at
/// Ensure the frequency is within bounds of what it should be sending/receiving at
/proc/sanitize_frequency(frequency, free = FALSE)
frequency = round(frequency)
if(free)
@@ -8,12 +8,26 @@
if(!(. % 2)) // Ensure the last digit is an odd number
. += 1
// Format frequency by moving the decimal.
/// Format frequency by moving the decimal.
/proc/format_frequency(frequency)
frequency = text2num(frequency)
return "[round(frequency / 10)].[frequency % 10]"
//Opposite of format, returns as a number
///Opposite of format, returns as a number
/proc/unformat_frequency(frequency)
frequency = text2num(frequency)
return frequency * 10
///returns a random unused frequency between MIN_FREE_FREQ & MAX_FREE_FREQ if free = TRUE, and MIN_FREQ & MAX_FREQ if FALSE
/proc/return_unused_frequency(free = FALSE)
var/start = free ? MIN_FREE_FREQ : MIN_FREQ
var/end = free ? MAX_FREE_FREQ : MAX_FREQ
var/freq_to_check = 0
do
freq_to_check = rand(start, end)
if(!(freq_to_check % 2)) // Ensure the last digit is an odd number
freq_to_check++
while((freq_to_check == 0) || ("[freq_to_check]" in GLOB.reverseradiochannels))
return freq_to_check
+6 -2
View File
@@ -590,8 +590,8 @@
var/list/all_teams = list()
var/list/all_antagonists = list()
// for(var/datum/team/A in GLOB.antagonist_teams)
// all_teams |= A
for(var/datum/team/A in GLOB.antagonist_teams)
all_teams |= A
for(var/datum/antagonist/A in GLOB.antagonists)
if(!A.owner)
@@ -624,6 +624,10 @@
currrent_category = A.roundend_category
previous_category = A
result += A.roundend_report()
//ambition start
for(var/count in 1 to LAZYLEN(A.owner.ambitions))
result += "<br><B>Ambition #[count]</B>: [A.owner.ambitions[count]]"
//ambition end
result += "<br><br>"
CHECK_TICK
+22 -22
View File
@@ -412,25 +412,25 @@
/proc/slot2body_zone(slot)
switch(slot)
if(SLOT_BACK, SLOT_WEAR_SUIT, SLOT_W_UNIFORM, SLOT_BELT, SLOT_WEAR_ID)
if(ITEM_SLOT_BACK, ITEM_SLOT_OCLOTHING, ITEM_SLOT_ICLOTHING, ITEM_SLOT_BELT, ITEM_SLOT_ID)
return BODY_ZONE_CHEST
if(SLOT_GLOVES, SLOT_HANDS, SLOT_HANDCUFFED)
if(ITEM_SLOT_GLOVES, ITEM_SLOT_HANDS, ITEM_SLOT_HANDCUFFED)
return pick(BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND)
if(SLOT_HEAD, SLOT_NECK, SLOT_NECK, SLOT_EARS)
if(ITEM_SLOT_HEAD, ITEM_SLOT_NECK, ITEM_SLOT_NECK, ITEM_SLOT_EARS)
return BODY_ZONE_HEAD
if(SLOT_WEAR_MASK)
if(ITEM_SLOT_MASK)
return BODY_ZONE_PRECISE_MOUTH
if(SLOT_GLASSES)
if(ITEM_SLOT_EYES)
return BODY_ZONE_PRECISE_EYES
if(SLOT_SHOES)
if(ITEM_SLOT_FEET)
return pick(BODY_ZONE_PRECISE_R_FOOT, BODY_ZONE_PRECISE_L_FOOT)
if(SLOT_LEGCUFFED)
if(ITEM_SLOT_LEGCUFFED)
return pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
//adapted from http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
@@ -591,9 +591,9 @@
switch(child)
if(/datum)
return null
if(/obj || /mob)
if(/obj, /mob)
return /atom/movable
if(/area || /turf)
if(/area, /turf)
return /atom
else
return /datum
@@ -652,31 +652,31 @@
/proc/slot_to_string(slot)
switch(slot)
if(SLOT_BACK)
if(ITEM_SLOT_BACK)
return "Backpack"
if(SLOT_WEAR_MASK)
if(ITEM_SLOT_MASK)
return "Mask"
if(SLOT_HANDS)
if(ITEM_SLOT_HANDS)
return "Hands"
if(SLOT_BELT)
if(ITEM_SLOT_BELT)
return "Belt"
if(SLOT_EARS)
if(ITEM_SLOT_EARS)
return "Ears"
if(SLOT_GLASSES)
if(ITEM_SLOT_EYES)
return "Glasses"
if(SLOT_GLOVES)
if(ITEM_SLOT_GLOVES)
return "Gloves"
if(SLOT_NECK)
if(ITEM_SLOT_NECK)
return "Neck"
if(SLOT_HEAD)
if(ITEM_SLOT_HEAD)
return "Head"
if(SLOT_SHOES)
if(ITEM_SLOT_FEET)
return "Shoes"
if(SLOT_WEAR_SUIT)
if(ITEM_SLOT_OCLOTHING)
return "Suit"
if(SLOT_W_UNIFORM)
if(ITEM_SLOT_ICLOTHING)
return "Uniform"
if(SLOT_IN_BACKPACK)
if(ITEM_SLOT_BACKPACK)
return "In backpack"
/proc/tg_ui_icon_to_cit_ui(ui_style)
+24 -13
View File
@@ -357,16 +357,27 @@ Turf and target are separate in case you want to teleport some distance from a t
if(M.ckey == key)
return M
//Returns the atom sitting on the turf.
//For example, using this on a disk, which is in a bag, on a mob, will return the mob because it's on the turf.
//Optional arg 'type' to stop once it reaches a specific type instead of a turf.
/proc/get_atom_on_turf(atom/movable/M, stop_type)
var/atom/loc = M
while(loc && loc.loc && !isturf(loc.loc))
loc = loc.loc
if(stop_type && istype(loc, stop_type))
/**
* Returns the top-most atom sitting on the turf.
* For example, using this on a disk, which is in a bag, on a mob,
* will return the mob because it's on the turf.
*
* Arguments
* * something_in_turf - a movable within the turf, somewhere.
* * stop_type - optional - stops looking if stop_type is found in the turf, returning that type (if found).
**/
/proc/get_atom_on_turf(atom/movable/something_in_turf, stop_type)
if(!istype(something_in_turf))
CRASH("get_atom_on_turf was not passed an /atom/movable! Got [isnull(something_in_turf) ? "null":"type: [something_in_turf.type]"]")
var/atom/movable/topmost_thing = something_in_turf
while(topmost_thing?.loc && !isturf(topmost_thing.loc))
topmost_thing = topmost_thing.loc
if(stop_type && istype(topmost_thing, stop_type))
break
return loc
return topmost_thing
//Returns a list of all locations the target is within.
/proc/get_nested_locs(atom/movable/M, include_turf = FALSE)
@@ -663,10 +674,10 @@ Turf and target are separate in case you want to teleport some distance from a t
return locate(final_x, final_y, T.z)
//Finds the distance between two atoms, in pixels
//centered = 0 counts from turf edge to edge
//centered = 1 counts from turf center to turf center
//centered = FALSE counts from turf edge to edge
//centered = TRUE counts from turf center to turf center
//of course mathematically this is just adding world.icon_size on again
/proc/getPixelDistance(atom/A, atom/B, centered = 1)
/proc/getPixelDistance(atom/A, atom/B, centered = TRUE)
if(!istype(A)||!istype(B))
return 0
. = bounds_dist(A, B) + sqrt((((A.pixel_x+B.pixel_x)**2) + ((A.pixel_y+B.pixel_y)**2)))
@@ -1600,7 +1611,7 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
if(!iscarbon(target))
return TRUE
if(check_neck)
if(istype(target.get_item_by_slot(SLOT_NECK), /obj/item/clothing/neck/garlic_necklace))
if(istype(target.get_item_by_slot(ITEM_SLOT_NECK), /obj/item/clothing/neck/garlic_necklace))
return FALSE
if(check_blood)
if(target.reagents.has_reagent(/datum/reagent/consumable/garlic))