Merge branch 'master' of https://github.com/Citadel-Station-13/Citadel-Station-13 into station_traits
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
@@ -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"]")
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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'))
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user