From 90dff0ae632ad98b21bade60ae9871eec52d4b11 Mon Sep 17 00:00:00 2001 From: Leshana Date: Tue, 30 May 2017 21:00:37 -0400 Subject: [PATCH 01/22] Ports /tg's StonedMC Subsystem from Baystation12 * Partial port of @PsiOmegaDelta's https://github.com/Baystation12/Baystation12/pull/16820 * Only ports the StonedMC changes, not the garbage collector (forthcoming in future) --- code/__defines/MC.dm | 53 ++ code/__defines/math.dm | 10 + code/__defines/misc.dm | 2 + code/__defines/tick.dm | 7 + code/_helpers/lists.dm | 63 ++ code/_helpers/sorts/TimSort.dm | 17 + code/_helpers/sorts/__main.dm | 656 ++++++++++++++++++ code/_helpers/sorts/comparators.dm | 20 + code/_helpers/time.dm | 22 +- code/_macros.dm | 11 + .../ProcessScheduler/core/process.dm | 1 - code/controllers/Processes/ticker.dm | 3 + code/controllers/configuration.dm | 10 +- code/controllers/controller.dm | 19 + code/controllers/failsafe.dm | 102 +++ code/controllers/master.dm | 519 ++++++++++++++ code/controllers/master_controller.dm | 6 +- code/controllers/subsystem.dm | 192 +++++ code/controllers/subsystems.dm | 46 -- code/controllers/verbs.dm | 54 +- polaris.dme | 11 +- 21 files changed, 1763 insertions(+), 61 deletions(-) create mode 100644 code/__defines/MC.dm create mode 100644 code/__defines/math.dm create mode 100644 code/__defines/tick.dm create mode 100644 code/_helpers/sorts/TimSort.dm create mode 100644 code/_helpers/sorts/__main.dm create mode 100644 code/_helpers/sorts/comparators.dm create mode 100644 code/controllers/controller.dm create mode 100644 code/controllers/failsafe.dm create mode 100644 code/controllers/master.dm create mode 100644 code/controllers/subsystem.dm delete mode 100644 code/controllers/subsystems.dm diff --git a/code/__defines/MC.dm b/code/__defines/MC.dm new file mode 100644 index 0000000000..90724a2f98 --- /dev/null +++ b/code/__defines/MC.dm @@ -0,0 +1,53 @@ +#define MC_TICK_CHECK ( ( world.tick_usage > CURRENT_TICKLIMIT || src.state != SS_RUNNING ) ? pause() : 0 ) +// Used to smooth out costs to try and avoid oscillation. +#define MC_AVERAGE_FAST(average, current) (0.7 * (average) + 0.3 * (current)) +#define MC_AVERAGE(average, current) (0.8 * (average) + 0.2 * (current)) +#define MC_AVERAGE_SLOW(average, current) (0.9 * (average) + 0.1 * (current)) +#define NEW_SS_GLOBAL(varname) if(varname != src){if(istype(varname)){Recover();qdel(varname);}varname = src;} + +#define START_PROCESSING(Processor, Datum) if (!Datum.isprocessing) {Datum.isprocessing = 1;Processor.processing += Datum} +#define STOP_PROCESSING(Processor, Datum) Datum.isprocessing = 0;Processor.processing -= Datum + +//SubSystem flags (Please design any new flags so that the default is off, to make adding flags to subsystems easier) + +//subsystem should fire during pre-game lobby. +#define SS_FIRE_IN_LOBBY 1 + +//subsystem does not initialize. +#define SS_NO_INIT 2 + +//subsystem does not fire. +// (like can_fire = 0, but keeps it from getting added to the processing subsystems list) +// (Requires a MC restart to change) +#define SS_NO_FIRE 4 + +//subsystem only runs on spare cpu (after all non-background subsystems have ran that tick) +// SS_BACKGROUND has its own priority bracket +#define SS_BACKGROUND 8 + +//subsystem does not tick check, and should not run unless there is enough time (or its running behind (unless background)) +#define SS_NO_TICK_CHECK 16 + +//Treat wait as a tick count, not DS, run every wait ticks. +// (also forces it to run first in the tick, above even SS_NO_TICK_CHECK subsystems) +// (implies SS_FIRE_IN_LOBBY because of how it works) +// (overrides SS_BACKGROUND) +// This is designed for basically anything that works as a mini-mc (like SStimer) +#define SS_TICKER 32 + +//keep the subsystem's timing on point by firing early if it fired late last fire because of lag +// ie: if a 20ds subsystem fires say 5 ds late due to lag or what not, its next fire would be in 15ds, not 20ds. +#define SS_KEEP_TIMING 64 + +//Calculate its next fire after its fired. +// (IE: if a 5ds wait SS takes 2ds to run, its next fire should be 5ds away, not 3ds like it normally would be) +// This flag overrides SS_KEEP_TIMING +#define SS_POST_FIRE_TIMING 128 + +//SUBSYSTEM STATES +#define SS_IDLE 0 //aint doing shit. +#define SS_QUEUED 1 //queued to run +#define SS_RUNNING 2 //actively running +#define SS_PAUSED 3 //paused by mc_tick_check +#define SS_SLEEPING 4 //fire() slept. +#define SS_PAUSING 5 //in the middle of pausing diff --git a/code/__defines/math.dm b/code/__defines/math.dm new file mode 100644 index 0000000000..0729a4526d --- /dev/null +++ b/code/__defines/math.dm @@ -0,0 +1,10 @@ +//"fancy" math for calculating time in ms from tick_usage percentage and the length of ticks +//percent_of_tick_used * (ticklag * 100(to convert to ms)) / 100(percent ratio) +//collapsed to percent_of_tick_used * tick_lag +#define TICK_DELTA_TO_MS(percent_of_tick_used) ((percent_of_tick_used) * world.tick_lag) +#define TICK_USAGE_TO_MS(starting_tickusage) (TICK_DELTA_TO_MS(world.tick_usage-starting_tickusage)) + +//time of day but automatically adjusts to the server going into the next day within the same round. +//for when you need a reliable time number that doesn't depend on byond time. +#define REALTIMEOFDAY (world.timeofday + (MIDNIGHT_ROLLOVER * MIDNIGHT_ROLLOVER_CHECK)) +#define MIDNIGHT_ROLLOVER_CHECK ( rollovercheck_last_timeofday != world.timeofday ? update_midnight_rollover() : midnight_rollovers ) diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index f85ac5e7d4..12deebbfc1 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -196,3 +196,5 @@ #define TSC_MORPH "Morpheus" #define TSC_XION "Xion" // Not really needed but consistancy I guess. #define TSC_GIL "Gilthari" + +#define MIDNIGHT_ROLLOVER 864000 //number of deciseconds in a day diff --git a/code/__defines/tick.dm b/code/__defines/tick.dm new file mode 100644 index 0000000000..3fa6e21bf3 --- /dev/null +++ b/code/__defines/tick.dm @@ -0,0 +1,7 @@ +#define TICK_LIMIT_RUNNING 80 +#define TICK_LIMIT_TO_RUN 78 +#define TICK_LIMIT_MC 70 +#define TICK_LIMIT_MC_INIT_DEFAULT 98 + +#define TICK_CHECK ( world.tick_usage > CURRENT_TICKLIMIT ) +#define CHECK_TICK if TICK_CHECK stoplag() diff --git a/code/_helpers/lists.dm b/code/_helpers/lists.dm index 30df4693db..e647cd1df7 100644 --- a/code/_helpers/lists.dm +++ b/code/_helpers/lists.dm @@ -599,3 +599,66 @@ proc/dd_sortedTextList(list/incoming) for(var/path in subtypesof(prototype)) L += new path() return L + +//creates every subtype of prototype (excluding prototype) and adds it to list L as a type/instance pair. +//if no list/L is provided, one is created. +/proc/init_subtypes_assoc(prototype, list/L) + if(!istype(L)) L = list() + for(var/path in subtypesof(prototype)) + L[path] = new path() + return L + +//Move a single element from position fromIndex within a list, to position toIndex +//All elements in the range [1,toIndex) before the move will be before the pivot afterwards +//All elements in the range [toIndex, L.len+1) before the move will be after the pivot afterwards +//In other words, it's as if the range [fromIndex,toIndex) have been rotated using a <<< operation common to other languages. +//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 + return + if(fromIndex > toIndex) + ++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) + L.Cut(fromIndex, fromIndex+1) + +//Move elements [fromIndex,fromIndex+len) to [toIndex-len, toIndex) +//Same as moveElement but for ranges of elements +//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(fromIndex <= toIndex) + return //no need to move + fromIndex += len //we want to shift left instead of right + + for(var/i=0, i toIndex) + fromIndex += len + + for(var/i=0, i= 2) + fromIndex = fromIndex % L.len + toIndex = toIndex % (L.len+1) + if(fromIndex <= 0) + fromIndex += L.len + if(toIndex <= 0) + toIndex += L.len + 1 + + sortInstance.L = L + sortInstance.cmp = cmp + sortInstance.associative = associative + + sortInstance.timSort(fromIndex, toIndex) + + return L diff --git a/code/_helpers/sorts/__main.dm b/code/_helpers/sorts/__main.dm new file mode 100644 index 0000000000..622d88f147 --- /dev/null +++ b/code/_helpers/sorts/__main.dm @@ -0,0 +1,656 @@ + //These are macros used to reduce on proc calls +#define fetchElement(L, i) (associative) ? L[L[i]] : L[i] + + //Minimum sized sequence that will be merged. Anything smaller than this will use binary-insertion sort. + //Should be a power of 2 +#define MIN_MERGE 32 + + //When we get into galloping mode, we stay there until both runs win less often than MIN_GALLOP consecutive times. +#define MIN_GALLOP 7 + + //This is a global instance to allow much of this code to be reused. The interfaces are kept separately +var/datum/sortInstance/sortInstance = new() +/datum/sortInstance + //The array being sorted. + var/list/L + + //The comparator proc-reference + var/cmp = /proc/cmp_numeric_asc + + //whether we are sorting list keys (0: L[i]) or associated values (1: L[L[i]]) + var/associative = 0 + + //This controls when we get *into* galloping mode. It is initialized to MIN_GALLOP. + //The mergeLo and mergeHi methods nudge it higher for random data, and lower for highly structured data. + var/minGallop = MIN_GALLOP + + //Stores information regarding runs yet to be merged. + //Run i starts at runBase[i] and extends for runLen[i] elements. + //runBase[i] + runLen[i] == runBase[i+1] + //var/stackSize + var/list/runBases = list() + var/list/runLens = list() + + +/datum/sortInstance/proc/timSort(start, end) + runBases.Cut() + runLens.Cut() + + var/remaining = end - start + + //If array is small, do a 'mini-TimSort' with no merges + if(remaining < MIN_MERGE) + var/initRunLen = countRunAndMakeAscending(start, end) + binarySort(start, end, start+initRunLen) + return + + //March over the array finding natural runs + //Extend any short natural runs to runs of length minRun + var/minRun = minRunLength(remaining) + + do + //identify next run + var/runLen = countRunAndMakeAscending(start, end) + + //if run is short, extend to min(minRun, remaining) + if(runLen < minRun) + var/force = (remaining <= minRun) ? remaining : minRun + + binarySort(start, start+force, start+runLen) + runLen = force + + //add data about run to queue + runBases.Add(start) + runLens.Add(runLen) + + //maybe merge + mergeCollapse() + + //Advance to find next run + start += runLen + remaining -= runLen + + while(remaining > 0) + + + //Merge all remaining runs to complete sort + //ASSERT(start == end) + mergeForceCollapse(); + //ASSERT(runBases.len == 1) + + //reset minGallop, for successive calls + minGallop = MIN_GALLOP + + return L + +/* +Sorts the specified portion of the specified array using a binary +insertion sort. This is the best method for sorting small numbers +of elements. It requires O(n log n) compares, but O(n^2) data +movement (worst case). + +If the initial part of the specified range is already sorted, +this method can take advantage of it: the method assumes that the +elements in range [lo,start) are already sorted + +lo the index of the first element in the range to be sorted +hi the index after the last element in the range to be sorted +start the index of the first element in the range that is not already known to be sorted +*/ +/datum/sortInstance/proc/binarySort(lo, hi, start) + //ASSERT(lo <= start && start <= hi) + if(start <= lo) + start = lo + 1 + + for(,start < hi, ++start) + var/pivot = fetchElement(L,start) + + //set left and right to the index where pivot belongs + var/left = lo + var/right = start + //ASSERT(left <= right) + + //[lo, left) elements <= pivot < [right, start) elements + //in other words, find where the pivot element should go using bisection search + while(left < right) + var/mid = (left + right) >> 1 //round((left+right)/2) + if(call(cmp)(fetchElement(L,mid), pivot) > 0) + right = mid + else + left = mid+1 + + //ASSERT(left == right) + moveElement(L, start, left) //move pivot element to correct location in the sorted range + +/* +Returns the length of the run beginning at the specified position and reverses the run if it is back-to-front + +A run is the longest ascending sequence with: + a[lo] <= a[lo + 1] <= a[lo + 2] <= ... +or the longest descending sequence with: + a[lo] > a[lo + 1] > a[lo + 2] > ... + +For its intended use in a stable mergesort, the strictness of the +definition of "descending" is needed so that the call can safely +reverse a descending sequence without violating stability. +*/ +/datum/sortInstance/proc/countRunAndMakeAscending(lo, hi) + //ASSERT(lo < hi) + + var/runHi = lo + 1 + if(runHi >= hi) + return 1 + + var/last = fetchElement(L,lo) + var/current = fetchElement(L,runHi++) + + if(call(cmp)(current, last) < 0) + while(runHi < hi) + last = current + current = fetchElement(L,runHi) + if(call(cmp)(current, last) >= 0) + break + ++runHi + reverseRange(L, lo, runHi) + else + while(runHi < hi) + last = current + current = fetchElement(L,runHi) + if(call(cmp)(current, last) < 0) + break + ++runHi + + return runHi - lo + +//Returns the minimum acceptable run length for an array of the specified length. +//Natural runs shorter than this will be extended with binarySort +/datum/sortInstance/proc/minRunLength(n) + //ASSERT(n >= 0) + var/r = 0 //becomes 1 if any bits are shifted off + while(n >= MIN_MERGE) + r |= (n & 1) + n >>= 1 + return n + r + +//Examines the stack of runs waiting to be merged and merges adjacent runs until the stack invariants are reestablished: +// runLen[i-3] > runLen[i-2] + runLen[i-1] +// runLen[i-2] > runLen[i-1] +//This method is called each time a new run is pushed onto the stack. +//So the invariants are guaranteed to hold for i= 2) + var/n = runBases.len - 1 + if(n > 1 && runLens[n-1] <= runLens[n] + runLens[n+1]) + if(runLens[n-1] < runLens[n+1]) + --n + mergeAt(n) + else if(runLens[n] <= runLens[n+1]) + mergeAt(n) + else + break //Invariant is established + + +//Merges all runs on the stack until only one remains. +//Called only once, to finalise the sort +/datum/sortInstance/proc/mergeForceCollapse() + while(runBases.len >= 2) + var/n = runBases.len - 1 + if(n > 1 && runLens[n-1] < runLens[n+1]) + --n + mergeAt(n) + + +//Merges the two consecutive runs at stack indices i and i+1 +//Run i must be the penultimate or antepenultimate run on the stack +//In other words, i must be equal to stackSize-2 or stackSize-3 +/datum/sortInstance/proc/mergeAt(i) + //ASSERT(runBases.len >= 2) + //ASSERT(i >= 1) + //ASSERT(i == runBases.len - 1 || i == runBases.len - 2) + + var/base1 = runBases[i] + var/base2 = runBases[i+1] + var/len1 = runLens[i] + var/len2 = runLens[i+1] + + //ASSERT(len1 > 0 && len2 > 0) + //ASSERT(base1 + len1 == base2) + + //Record the legth of the combined runs. If i is the 3rd last run now, also slide over the last run + //(which isn't involved in this merge). The current run (i+1) goes away in any case. + runLens[i] += runLens[i+1] + runLens.Cut(i+1, i+2) + runBases.Cut(i+1, i+2) + + + //Find where the first element of run2 goes in run1. + //Prior elements in run1 can be ignored (because they're already in place) + var/k = gallopRight(fetchElement(L,base2), base1, len1, 0) + //ASSERT(k >= 0) + base1 += k + len1 -= k + if(len1 == 0) + return + + //Find where the last element of run1 goes in run2. + //Subsequent elements in run2 can be ignored (because they're already in place) + len2 = gallopLeft(fetchElement(L,base1 + len1 - 1), base2, len2, len2-1) + //ASSERT(len2 >= 0) + if(len2 == 0) + return + + //Merge remaining runs, using tmp array with min(len1, len2) elements + if(len1 <= len2) + mergeLo(base1, len1, base2, len2) + else + mergeHi(base1, len1, base2, len2) + + +/* + Locates the position to insert key within the specified sorted range + If the range contains elements equal to key, this will return the index of the LEFTMOST of those elements + + key the element to be inserted into the sorted range + base the index of the first element of the sorted range + len the length of the sorted range, must be greater than 0 + hint the offset from base at which to begin the search, such that 0 <= hint < len; i.e. base <= hint < base+hint + + Returns the index at which to insert element 'key' +*/ +/datum/sortInstance/proc/gallopLeft(key, base, len, hint) + //ASSERT(len > 0 && hint >= 0 && hint < len) + + var/lastOffset = 0 + var/offset = 1 + if(call(cmp)(key, fetchElement(L,base+hint)) > 0) + var/maxOffset = len - hint + while(offset < maxOffset && call(cmp)(key, fetchElement(L,base+hint+offset)) > 0) + lastOffset = offset + offset = (offset << 1) + 1 + + if(offset > maxOffset) + offset = maxOffset + + lastOffset += hint + offset += hint + + else + var/maxOffset = hint + 1 + while(offset < maxOffset && call(cmp)(key, fetchElement(L,base+hint-offset)) <= 0) + lastOffset = offset + offset = (offset << 1) + 1 + + if(offset > maxOffset) + offset = maxOffset + + var/temp = lastOffset + lastOffset = hint - offset + offset = hint - temp + + //ASSERT(-1 <= lastOffset && lastOffset < offset && offset <= len) + + //Now L[base+lastOffset] < key <= L[base+offset], so key belongs somewhere to the right of lastOffset but no farther than + //offset. Do a binary search with invariant L[base+lastOffset-1] < key <= L[base+offset] + ++lastOffset + while(lastOffset < offset) + var/m = lastOffset + ((offset - lastOffset) >> 1) + + if(call(cmp)(key, fetchElement(L,base+m)) > 0) + lastOffset = m + 1 + else + offset = m + + //ASSERT(lastOffset == offset) + return offset + +/** + * Like gallopLeft, except that if the range contains an element equal to + * key, gallopRight returns the index after the rightmost equal element. + * + * @param key the key whose insertion point to search for + * @param a the array in which to search + * @param base the index of the first element in the range + * @param len the length of the range; must be > 0 + * @param hint the index at which to begin the search, 0 <= hint < n. + * The closer hint is to the result, the faster this method will run. + * @param c the comparator used to order the range, and to search + * @return the int k, 0 <= k <= n such that a[b + k - 1] <= key < a[b + k] + */ +/datum/sortInstance/proc/gallopRight(key, base, len, hint) + //ASSERT(len > 0 && hint >= 0 && hint < len) + + var/offset = 1 + var/lastOffset = 0 + if(call(cmp)(key, fetchElement(L,base+hint)) < 0) //key <= L[base+hint] + var/maxOffset = hint + 1 //therefore we want to insert somewhere in the range [base,base+hint] = [base+,base+(hint+1)) + while(offset < maxOffset && call(cmp)(key, fetchElement(L,base+hint-offset)) < 0) //we are iterating backwards + lastOffset = offset + offset = (offset << 1) + 1 //1 3 7 15 + //if(offset <= 0) //int overflow, not an issue here since we are using floats + // offset = maxOffset + + if(offset > maxOffset) + offset = maxOffset + + var/temp = lastOffset + lastOffset = hint - offset + offset = hint - temp + + else //key > L[base+hint] + var/maxOffset = len - hint //therefore we want to insert somewhere in the range (base+hint,base+len) = [base+hint+1, base+hint+(len-hint)) + while(offset < maxOffset && call(cmp)(key, fetchElement(L,base+hint+offset)) >= 0) + lastOffset = offset + offset = (offset << 1) + 1 + //if(offset <= 0) //int overflow, not an issue here since we are using floats + // offset = maxOffset + + if(offset > maxOffset) + offset = maxOffset + + lastOffset += hint + offset += hint + + //ASSERT(-1 <= lastOffset && lastOffset < offset && offset <= len) + + ++lastOffset + while(lastOffset < offset) + var/m = lastOffset + ((offset - lastOffset) >> 1) + + if(call(cmp)(key, fetchElement(L,base+m)) < 0) //key <= L[base+m] + offset = m + else //key > L[base+m] + lastOffset = m + 1 + + //ASSERT(lastOffset == offset) + + return offset + + +//Merges two adjacent runs in-place in a stable fashion. +//For performance this method should only be called when len1 <= len2! +/datum/sortInstance/proc/mergeLo(base1, len1, base2, len2) + //ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2) + + var/cursor1 = base1 + var/cursor2 = base2 + + //degenerate cases + if(len2 == 1) + moveElement(L, cursor2, cursor1) + return + + if(len1 == 1) + moveElement(L, cursor1, cursor2+len2) + return + + + //Move first element of second run + moveElement(L, cursor2++, cursor1++) + --len2 + + outer: + while(1) + var/count1 = 0 //# of times in a row that first run won + var/count2 = 0 // " " " " " " second run won + + //do the straightfoward thin until one run starts winning consistently + + do + //ASSERT(len1 > 1 && len2 > 0) + if(call(cmp)(fetchElement(L,cursor2), fetchElement(L,cursor1)) < 0) + moveElement(L, cursor2++, cursor1++) + --len2 + + ++count2 + count1 = 0 + + if(len2 == 0) + break outer + else + ++cursor1 + + ++count1 + count2 = 0 + + if(--len1 == 1) + break outer + + while((count1 | count2) < minGallop) + + + //one run is winning consistently so galloping may provide huge benifits + //so try galloping, until such time as the run is no longer consistently winning + do + //ASSERT(len1 > 1 && len2 > 0) + + count1 = gallopRight(fetchElement(L,cursor2), cursor1, len1, 0) + if(count1) + cursor1 += count1 + len1 -= count1 + + if(len1 <= 1) + break outer + + moveElement(L, cursor2, cursor1) + ++cursor2 + ++cursor1 + if(--len2 == 0) + break outer + + count2 = gallopLeft(fetchElement(L,cursor1), cursor2, len2, 0) + if(count2) + moveRange(L, cursor2, cursor1, count2) + + cursor2 += count2 + cursor1 += count2 + len2 -= count2 + + if(len2 == 0) + break outer + + ++cursor1 + if(--len1 == 1) + break outer + + --minGallop + + while((count1|count2) > MIN_GALLOP) + + if(minGallop < 0) + minGallop = 0 + minGallop += 2; // Penalize for leaving gallop mode + + + if(len1 == 1) + //ASSERT(len2 > 0) + moveElement(L, cursor1, cursor2+len2) + + //else + //ASSERT(len2 == 0) + //ASSERT(len1 > 1) + + +/datum/sortInstance/proc/mergeHi(base1, len1, base2, len2) + //ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2) + + var/cursor1 = base1 + len1 - 1 //start at end of sublists + var/cursor2 = base2 + len2 - 1 + + //degenerate cases + if(len2 == 1) + moveElement(L, base2, base1) + return + + if(len1 == 1) + moveElement(L, base1, cursor2+1) + return + + moveElement(L, cursor1--, cursor2-- + 1) + --len1 + + outer: + while(1) + var/count1 = 0 //# of times in a row that first run won + var/count2 = 0 // " " " " " " second run won + + //do the straightfoward thing until one run starts winning consistently + do + //ASSERT(len1 > 0 && len2 > 1) + if(call(cmp)(fetchElement(L,cursor2), fetchElement(L,cursor1)) < 0) + moveElement(L, cursor1--, cursor2-- + 1) + --len1 + + ++count1 + count2 = 0 + + if(len1 == 0) + break outer + else + --cursor2 + --len2 + + ++count2 + count1 = 0 + + if(len2 == 1) + break outer + while((count1 | count2) < minGallop) + + //one run is winning consistently so galloping may provide huge benifits + //so try galloping, until such time as the run is no longer consistently winning + do + //ASSERT(len1 > 0 && len2 > 1) + + count1 = len1 - gallopRight(fetchElement(L,cursor2), base1, len1, len1-1) //should cursor1 be base1? + if(count1) + cursor1 -= count1 + + moveRange(L, cursor1+1, cursor2+1, count1) //cursor1+1 == cursor2 by definition + + cursor2 -= count1 + len1 -= count1 + + if(len1 == 0) + break outer + + --cursor2 + + if(--len2 == 1) + break outer + + count2 = len2 - gallopLeft(fetchElement(L,cursor1), cursor1+1, len2, len2-1) + if(count2) + cursor2 -= count2 + len2 -= count2 + + if(len2 <= 1) + break outer + + moveElement(L, cursor1--, cursor2-- + 1) + --len1 + + if(len1 == 0) + break outer + + --minGallop + while((count1|count2) > MIN_GALLOP) + + if(minGallop < 0) + minGallop = 0 + minGallop += 2 // Penalize for leaving gallop mode + + if(len2 == 1) + //ASSERT(len1 > 0) + + cursor1 -= len1 + moveRange(L, cursor1+1, cursor2+1, len1) + + //else + //ASSERT(len1 == 0) + //ASSERT(len2 > 0) + + +/datum/sortInstance/proc/mergeSort(start, end) + var/remaining = end - start + + //If array is small, do an insertion sort + if(remaining < MIN_MERGE) + //var/initRunLen = countRunAndMakeAscending(start, end) + binarySort(start, end, start/*+initRunLen*/) + return + + var/minRun = minRunLength(remaining) + + do + var/runLen = (remaining <= minRun) ? remaining : minRun + + binarySort(start, start+runLen, start) + + //add data about run to queue + runBases.Add(start) + runLens.Add(runLen) + + //Advance to find next run + start += runLen + remaining -= runLen + + while(remaining > 0) + + while(runBases.len >= 2) + var/n = runBases.len - 1 + if(n > 1 && runLens[n-1] <= runLens[n] + runLens[n+1]) + if(runLens[n-1] < runLens[n+1]) + --n + mergeAt2(n) + else if(runLens[n] <= runLens[n+1]) + mergeAt2(n) + else + break //Invariant is established + + while(runBases.len >= 2) + var/n = runBases.len - 1 + if(n > 1 && runLens[n-1] < runLens[n+1]) + --n + mergeAt2(n) + + return L + +/datum/sortInstance/proc/mergeAt2(i) + var/cursor1 = runBases[i] + var/cursor2 = runBases[i+1] + + var/end1 = cursor1+runLens[i] + var/end2 = cursor2+runLens[i+1] + + var/val1 = fetchElement(L,cursor1) + var/val2 = fetchElement(L,cursor2) + + while(1) + if(call(cmp)(val1,val2) < 0) + if(++cursor1 >= end1) + break + val1 = fetchElement(L,cursor1) + else + moveElement(L,cursor2,cursor1) + + ++cursor2 + if(++cursor2 >= end2) + break + ++end1 + ++cursor1 + //if(++cursor1 >= end1) + // break + + val2 = fetchElement(L,cursor2) + + + //Record the legth of the combined runs. If i is the 3rd last run now, also slide over the last run + //(which isn't involved in this merge). The current run (i+1) goes away in any case. + runLens[i] += runLens[i+1] + runLens.Cut(i+1, i+2) + runBases.Cut(i+1, i+2) + +#undef MIN_GALLOP +#undef MIN_MERGE + +#undef fetchElement diff --git a/code/_helpers/sorts/comparators.dm b/code/_helpers/sorts/comparators.dm new file mode 100644 index 0000000000..8c1f954a00 --- /dev/null +++ b/code/_helpers/sorts/comparators.dm @@ -0,0 +1,20 @@ +// +// Comparators for use with /datum/sortInstance (or wherever you want) +// They should return negative, zero, or positive numbers for a < b, a == b, and a > b respectively. +// + +// Sorts numeric ascending +/proc/cmp_numeric_asc(a,b) + return a - b + +// Sorts subsystems alphabetically +/proc/cmp_subsystem_display(datum/controller/subsystem/a, datum/controller/subsystem/b) + return sorttext(b.name, a.name) + +// Sorts subsystems by init_order +/proc/cmp_subsystem_init(datum/controller/subsystem/a, datum/controller/subsystem/b) + return b.init_order - a.init_order + +// Sorts subsystems by priority +/proc/cmp_subsystem_priority(datum/controller/subsystem/a, datum/controller/subsystem/b) + return a.priority - b.priority diff --git a/code/_helpers/time.dm b/code/_helpers/time.dm index 991d64e20b..b3989a32a4 100644 --- a/code/_helpers/time.dm +++ b/code/_helpers/time.dm @@ -100,4 +100,24 @@ var/round_start_time = 0 /hook/startup/proc/set_roundstart_hour() roundstart_hour = pick(2,7,12,17) - return 1 \ No newline at end of file + return 1 + +/var/midnight_rollovers = 0 +/var/rollovercheck_last_timeofday = 0 +/proc/update_midnight_rollover() + if (world.timeofday < rollovercheck_last_timeofday) //TIME IS GOING BACKWARDS! + return midnight_rollovers++ + return midnight_rollovers + +//Increases delay as the server gets more overloaded, +//as sleeps aren't cheap and sleeping only to wake up and sleep again is wasteful +#define DELTA_CALC max(((max(world.tick_usage, world.cpu) / 100) * max(Master.sleep_delta,1)), 1) + +/proc/stoplag() + . = 0 + var/i = 1 + do + . += round(i*DELTA_CALC) + sleep(i*world.tick_lag*DELTA_CALC) + i *= 2 + while (world.tick_usage > min(TICK_LIMIT_TO_RUN, CURRENT_TICKLIMIT)) diff --git a/code/_macros.dm b/code/_macros.dm index 463a7da26d..befbc99a54 100644 --- a/code/_macros.dm +++ b/code/_macros.dm @@ -49,5 +49,16 @@ #define RANDOM_BLOOD_TYPE pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+") #define to_chat(target, message) target << message +#define to_world(message) world << message +#define to_world_log(message) world.log << message +// TODO - Baystation has this log to crazy places. For now lets just world.log, but maybe look into it later. +#define log_world(message) world.log << message + #define CanInteract(user, state) (CanUseTopic(user, state) == STATUS_INTERACTIVE) + +#define qdel_null_list(x) if(x) { for(var/y in x) { qdel(y) } ; x = null } + +#define qdel_null(x) if(x) { qdel(x) ; x = null } + +#define ARGS_DEBUG log_debug("[__FILE__] - [__LINE__]") ; for(var/arg in args) { log_debug("\t[log_info_line(arg)]") } diff --git a/code/controllers/ProcessScheduler/core/process.dm b/code/controllers/ProcessScheduler/core/process.dm index 323c5f7663..edcb8d703c 100644 --- a/code/controllers/ProcessScheduler/core/process.dm +++ b/code/controllers/ProcessScheduler/core/process.dm @@ -35,7 +35,6 @@ * Config vars */ // Process name - var/name // Process schedule interval // This controls how often the process would run under ideal conditions. diff --git a/code/controllers/Processes/ticker.dm b/code/controllers/Processes/ticker.dm index b7c45ba91a..6fa6d43237 100644 --- a/code/controllers/Processes/ticker.dm +++ b/code/controllers/Processes/ticker.dm @@ -33,3 +33,6 @@ var/global/datum/controller/process/ticker/tickerProcess /datum/controller/process/ticker/proc/getLastTickerTimeDuration() return lastTickerTimeDuration + +/world/proc/has_round_started() + return (ticker && ticker.current_state >= GAME_STATE_PLAYING) diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 5021453462..64ae30116a 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -46,7 +46,8 @@ var/list/gamemode_cache = list() var/continous_rounds = 0 // Gamemodes which end instantly will instead keep on going until the round ends by escape shuttle or nuke. var/allow_Metadata = 0 // Metadata is supported. var/popup_admin_pm = 0 //adminPMs to non-admins show in a pop-up 'reply' window when set to 1. - var/Ticklag = 0.9 + var/fps = 20 + var/tick_limit_mc_init = TICK_LIMIT_MC_INIT_DEFAULT //SSinitialization throttling var/Tickcomp = 0 var/socket_talk = 0 // use socket_talk to communicate with other processes var/list/resource_urls = null @@ -564,7 +565,12 @@ var/list/gamemode_cache = list() irc_bot_export = 1 if("ticklag") - Ticklag = text2num(value) + var/ticklag = text2num(value) + if(ticklag > 0) + fps = 10 / ticklag + + if("tick_limit_mc_init") + tick_limit_mc_init = text2num(value) if("allow_antag_hud") config.antag_hud_allowed = 1 diff --git a/code/controllers/controller.dm b/code/controllers/controller.dm new file mode 100644 index 0000000000..c9d5f1e565 --- /dev/null +++ b/code/controllers/controller.dm @@ -0,0 +1,19 @@ +/datum/controller + var/name + // The object used for the clickable stat() button. + var/obj/effect/statclick/statclick + +/datum/controller/proc/Initialize() + +//cleanup actions +/datum/controller/proc/Shutdown() + +//when we enter dmm_suite.load_map +/datum/controller/proc/StartLoadingMap() + +//when we exit dmm_suite.load_map +/datum/controller/proc/StopLoadingMap() + +/datum/controller/proc/Recover() + +/datum/controller/proc/stat_entry() diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm new file mode 100644 index 0000000000..98c6a5d89d --- /dev/null +++ b/code/controllers/failsafe.dm @@ -0,0 +1,102 @@ + /** + * Failsafe + * + * Pretty much pokes the MC to make sure it's still alive. + **/ + +var/datum/controller/failsafe/Failsafe + +/datum/controller/failsafe // This thing pretty much just keeps poking the master controller + name = "Failsafe" + + // The length of time to check on the MC (in deciseconds). + // Set to 0 to disable. + var/processing_interval = 20 + // The alert level. For every failed poke, we drop a DEFCON level. Once we hit DEFCON 1, restart the MC. + var/defcon = 5 + //the world.time of the last check, so the mc can restart US if we hang. + // (Real friends look out for *eachother*) + var/lasttick = 0 + + // Track the MC iteration to make sure its still on track. + var/master_iteration = 0 + var/running = TRUE + +/datum/controller/failsafe/New() + // Highlander-style: there can only be one! Kill off the old and replace it with the new. + if(Failsafe != src) + if(istype(Failsafe)) + qdel(Failsafe) + Failsafe = src + Initialize() + +/datum/controller/failsafe/Initialize() + set waitfor = 0 + Failsafe.Loop() + if(!deleted(src)) + qdel(src) //when Loop() returns, we delete ourselves and let the mc recreate us + +/datum/controller/failsafe/Destroy() + running = FALSE + ..() + // return QDEL_HINT_HARDDEL_NOW // TODO - Once we port garbage.dm + +/datum/controller/failsafe/proc/Loop() + while(running) + lasttick = world.time + if(!Master) + // Replace the missing Master! This should never, ever happen. + new /datum/controller/master() + // Only poke it if overrides are not in effect. + if(processing_interval > 0) + if(Master.processing && Master.iteration) + // Check if processing is done yet. + if(Master.iteration == master_iteration) + switch(defcon) + if(4,5) + --defcon + if(3) + to_chat(admins, "Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks.") + --defcon + if(2) + to_chat(admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.") + --defcon + if(1) + + to_chat(admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...") + --defcon + var/rtn = Recreate_MC() + if(rtn > 0) + defcon = 4 + master_iteration = 0 + to_chat(admins, "MC restarted successfully") + else if(rtn < 0) + log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0") + to_chat(admins, "ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying.") + //if the return number was 0, it just means the mc was restarted too recently, and it just needs some time before we try again + //no need to handle that specially when defcon 0 can handle it + if(0) //DEFCON 0! (mc failed to restart) + var/rtn = Recreate_MC() + if(rtn > 0) + defcon = 4 + master_iteration = 0 + to_chat(admins, "MC restarted successfully") + else + defcon = min(defcon + 1,5) + master_iteration = Master.iteration + if (defcon <= 1) + sleep(processing_interval*2) + else + sleep(processing_interval) + else + defcon = 5 + sleep(initial(processing_interval)) + +/datum/controller/failsafe/proc/defcon_pretty() + return defcon + +/datum/controller/failsafe/stat_entry() + if(!statclick) + statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) + + stat("Failsafe Controller:", statclick.update("Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])")) diff --git a/code/controllers/master.dm b/code/controllers/master.dm new file mode 100644 index 0000000000..4dcf58d45e --- /dev/null +++ b/code/controllers/master.dm @@ -0,0 +1,519 @@ + /** + * StonedMC + * + * Designed to properly split up a given tick among subsystems + * Note: if you read parts of this code and think "why is it doing it that way" + * Odds are, there is a reason + * + **/ +var/datum/controller/master/Master = new() +var/MC_restart_clear = 0 +var/MC_restart_timeout = 0 +var/MC_restart_count = 0 + + +//current tick limit, assigned by the queue controller before running a subsystem. +//used by check_tick as well so that the procs subsystems call can obey that SS's tick limits +var/CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING + +/datum/controller/master + name = "Master" + + // Are we processing (higher values increase the processing delay by n ticks) + var/processing = 1 + // How many times have we ran + var/iteration = 0 + + // world.time of last fire, for tracking lag outside of the mc + var/last_run + + // List of subsystems to process(). + var/list/subsystems + + // Vars for keeping track of tick drift. + var/init_timeofday + var/init_time + var/tickdrift = 0 + + var/sleep_delta + + var/make_runtime = 0 + + var/initializations_finished_with_no_players_logged_in //I wonder what this could be? + // Has round started? (So we know what subsystems to run) + var/round_started = 0 + + // The type of the last subsystem to be process()'d. + var/last_type_processed + + var/datum/controller/subsystem/queue_head //Start of queue linked list + var/datum/controller/subsystem/queue_tail //End of queue linked list (used for appending to the list) + var/queue_priority_count = 0 //Running total so that we don't have to loop thru the queue each run to split up the tick + var/queue_priority_count_bg = 0 //Same, but for background subsystems + var/map_loading = FALSE //Are we loading in a new map? + +/datum/controller/master/New() + // Highlander-style: there can only be one! Kill off the old and replace it with the new. + subsystems = list() + if (Master != src) + if (istype(Master)) + Recover() + qdel(Master) + else + init_subtypes(/datum/controller/subsystem, subsystems) + Master = src + +/datum/controller/master/Destroy() + ..() + // Tell qdel() to Del() this object. + // return QDEL_HINT_HARDDEL_NOW // TODO - Once we port garbage.dm + +/datum/controller/master/Shutdown() + processing = FALSE + for(var/datum/controller/subsystem/ss in subsystems) + ss.Shutdown() + +// Returns 1 if we created a new mc, 0 if we couldn't due to a recent restart, +// -1 if we encountered a runtime trying to recreate it +/proc/Recreate_MC() + . = -1 //so if we runtime, things know we failed + if (world.time < MC_restart_timeout) + return 0 + if (world.time < MC_restart_clear) + MC_restart_count *= 0.5 + + var/delay = 50 * ++MC_restart_count + MC_restart_timeout = world.time + delay + MC_restart_clear = world.time + (delay * 2) + Master.processing = 0 //stop ticking this one + try + new/datum/controller/master() + catch + return -1 + return 1 + + +/datum/controller/master/Recover() + var/msg = "## DEBUG: [time2text(world.timeofday)] MC restarted. Reports:\n" + for (var/varname in Master.vars) + switch (varname) + if("name", "tag", "bestF", "type", "parent_type", "vars", "statclick") // Built-in junk. + continue + else + var/varval = Master.vars[varname] + if (istype(varval, /datum)) // Check if it has a type var. + var/datum/D = varval + msg += "\t [varname] = [D]([D.type])\n" + else + msg += "\t [varname] = [varval]\n" + log_world(msg) + if (istype(Master.subsystems)) + subsystems = Master.subsystems + StartProcessing(10) + else + to_chat(world, "The Master Controller is having some issues, we will need to re-initialize EVERYTHING") + Initialize(20, TRUE) + + +// Please don't stuff random bullshit here, +// Make a subsystem, give it the SS_NO_FIRE flag, and do your work in it's Initialize() +/datum/controller/master/Initialize(delay, init_sss) + set waitfor = 0 + + if(delay) + sleep(delay) + + if(init_sss) + init_subtypes(/datum/controller/subsystem, subsystems) + + to_chat(world, "Initializing subsystems...") + + // Sort subsystems by init_order, so they initialize in the correct order. + sortTim(subsystems, /proc/cmp_subsystem_init) + + var/start_timeofday = REALTIMEOFDAY + // Initialize subsystems. + CURRENT_TICKLIMIT = config.tick_limit_mc_init + for (var/datum/controller/subsystem/SS in subsystems) + if (SS.flags & SS_NO_INIT) + continue + SS.Initialize(REALTIMEOFDAY) + CHECK_TICK + CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING + var/time = (REALTIMEOFDAY - start_timeofday) / 10 + + var/msg = "Initializations complete within [time] second[time == 1 ? "" : "s"]!" + to_chat(world, "[msg]") + log_world(msg) + + // Sort subsystems by display setting for easy access. + sortTim(subsystems, /proc/cmp_subsystem_display) + // Set world options. + world.sleep_offline = 1 + world.fps = config.fps + var/initialized_tod = REALTIMEOFDAY + sleep(1) + initializations_finished_with_no_players_logged_in = initialized_tod < REALTIMEOFDAY - 10 + // Loop. + Master.StartProcessing(0) + +// Notify the MC that the round has started. +/datum/controller/master/proc/RoundStart() + round_started = 1 + var/timer = world.time + for (var/datum/controller/subsystem/SS in subsystems) + if (SS.flags & SS_FIRE_IN_LOBBY || SS.flags & SS_TICKER) + continue //already firing + // Stagger subsystems. + timer += world.tick_lag * rand(1, 5) + SS.next_fire = timer + +// Starts the mc, and sticks around to restart it if the loop ever ends. +/datum/controller/master/proc/StartProcessing(delay) + set waitfor = 0 + if(delay) + sleep(delay) + var/rtn = Loop() + if (rtn > 0 || processing < 0) + return //this was suppose to happen. + //loop ended, restart the mc + log_game("MC crashed or runtimed, restarting") + message_admins("MC crashed or runtimed, restarting") + var/rtn2 = Recreate_MC() + if (rtn2 <= 0) + log_game("Failed to recreate MC (Error code: [rtn2]), it's up to the failsafe now") + message_admins("Failed to recreate MC (Error code: [rtn2]), it's up to the failsafe now") + Failsafe.defcon = 2 + +// Main loop. +/datum/controller/master/proc/Loop() + . = -1 + //Prep the loop (most of this is because we want MC restarts to reset as much state as we can, and because + // local vars rock + + // Schedule the first run of the Subsystems. + round_started = world.has_round_started() + //all this shit is here so that flag edits can be refreshed by restarting the MC. (and for speed) + var/list/tickersubsystems = list() + var/list/normalsubsystems = list() + var/list/lobbysubsystems = list() + var/timer = world.time + for (var/thing in subsystems) + var/datum/controller/subsystem/SS = thing + if (SS.flags & SS_NO_FIRE) + continue + SS.queued_time = 0 + SS.queue_next = null + SS.queue_prev = null + SS.state = SS_IDLE + if (SS.flags & SS_TICKER) + tickersubsystems += SS + timer += world.tick_lag * rand(1, 5) + SS.next_fire = timer + continue + if (SS.flags & SS_FIRE_IN_LOBBY) + lobbysubsystems += SS + timer += world.tick_lag * rand(1, 5) + SS.next_fire = timer + else if (round_started) + timer += world.tick_lag * rand(1, 5) + SS.next_fire = timer + normalsubsystems += SS + + queue_head = null + queue_tail = null + //these sort by lower priorities first to reduce the number of loops needed to add subsequent SS's to the queue + //(higher subsystems will be sooner in the queue, adding them later in the loop means we don't have to loop thru them next queue add) + sortTim(tickersubsystems, /proc/cmp_subsystem_priority) + sortTim(normalsubsystems, /proc/cmp_subsystem_priority) + sortTim(lobbysubsystems, /proc/cmp_subsystem_priority) + + normalsubsystems += tickersubsystems + lobbysubsystems += tickersubsystems + + init_timeofday = REALTIMEOFDAY + init_time = world.time + + iteration = 1 + var/error_level = 0 + var/sleep_delta = 0 + var/list/subsystems_to_check + //the actual loop. + while (1) + tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, (((REALTIMEOFDAY - init_timeofday) - (world.time - init_time)) / world.tick_lag))) + if (processing <= 0) + CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING + sleep(10) + continue + + //if there are mutiple sleeping procs running before us hogging the cpu, we have to run later + // because sleeps are processed in the order received, so longer sleeps are more likely to run first + if (world.tick_usage > TICK_LIMIT_MC) + sleep_delta += 2 + CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING - (TICK_LIMIT_RUNNING * 0.5) + sleep(world.tick_lag * (processing + sleep_delta)) + continue + + sleep_delta = MC_AVERAGE_FAST(sleep_delta, 0) + if (last_run + (world.tick_lag * processing) > world.time) + sleep_delta += 1 + if (world.tick_usage > (TICK_LIMIT_MC*0.5)) + sleep_delta += 1 + + if (make_runtime) + var/datum/controller/subsystem/SS + SS.can_fire = 0 + if (!Failsafe || (Failsafe.processing_interval > 0 && (Failsafe.lasttick+(Failsafe.processing_interval*5)) < world.time)) + new/datum/controller/failsafe() // (re)Start the failsafe. + if (!queue_head || !(iteration % 3)) + if (round_started) + subsystems_to_check = normalsubsystems + else + subsystems_to_check = lobbysubsystems + else + subsystems_to_check = tickersubsystems + if (CheckQueue(subsystems_to_check) <= 0) + if (!SoftReset(tickersubsystems, normalsubsystems, lobbysubsystems)) + log_world("MC: SoftReset() failed, crashing") + return + if (!error_level) + iteration++ + error_level++ + CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING + sleep(10) + continue + + if (queue_head) + if (RunQueue() <= 0) + if (!SoftReset(tickersubsystems, normalsubsystems, lobbysubsystems)) + log_world("MC: SoftReset() failed, crashing") + return + if (!error_level) + iteration++ + error_level++ + CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING + sleep(10) + continue + error_level-- + if (!queue_head) //reset the counts if the queue is empty, in the off chance they get out of sync + queue_priority_count = 0 + queue_priority_count_bg = 0 + + iteration++ + last_run = world.time + src.sleep_delta = MC_AVERAGE_FAST(src.sleep_delta, sleep_delta) + CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING - (TICK_LIMIT_RUNNING * 0.25) //reserve the tail 1/4 of the next tick for the mc. + sleep(world.tick_lag * (processing + sleep_delta)) + + + + +// This is what decides if something should run. +/datum/controller/master/proc/CheckQueue(list/subsystemstocheck) + . = 0 //so the mc knows if we runtimed + + //we create our variables outside of the loops to save on overhead + var/datum/controller/subsystem/SS + var/SS_flags + + for (var/thing in subsystemstocheck) + if (!thing) + subsystemstocheck -= thing + SS = thing + if (SS.state != SS_IDLE) + continue + if (SS.can_fire <= 0) + continue + if (SS.next_fire > world.time) + continue + SS_flags = SS.flags + if (SS_flags & SS_NO_FIRE) + subsystemstocheck -= SS + continue + if (!(SS_flags & SS_TICKER) && (SS_flags & SS_KEEP_TIMING) && SS.last_fire + (SS.wait * 0.75) > world.time) + continue + SS.enqueue() + . = 1 + + +// Run thru the queue of subsystems to run, running them while balancing out their allocated tick precentage +/datum/controller/master/proc/RunQueue() + . = 0 + var/datum/controller/subsystem/queue_node + var/queue_node_flags + var/queue_node_priority + var/queue_node_paused + + var/current_tick_budget + var/tick_precentage + var/tick_remaining + var/ran = TRUE //this is right + var/ran_non_ticker = FALSE + var/bg_calc //have we swtiched current_tick_budget to background mode yet? + var/tick_usage + + //keep running while we have stuff to run and we haven't gone over a tick + // this is so subsystems paused eariler can use tick time that later subsystems never used + while (ran && queue_head && world.tick_usage < TICK_LIMIT_MC) + ran = FALSE + bg_calc = FALSE + current_tick_budget = queue_priority_count + queue_node = queue_head + while (queue_node) + if (ran && world.tick_usage > TICK_LIMIT_RUNNING) + break + + queue_node_flags = queue_node.flags + queue_node_priority = queue_node.queued_priority + + //super special case, subsystems where we can't make them pause mid way through + //if we can't run them this tick (without going over a tick) + //we bump up their priority and attempt to run them next tick + //(unless we haven't even ran anything this tick, since its unlikely they will ever be able run + // in those cases, so we just let them run) + if (queue_node_flags & SS_NO_TICK_CHECK) + if (queue_node.tick_usage > TICK_LIMIT_RUNNING - world.tick_usage && ran_non_ticker) + queue_node.queued_priority += queue_priority_count * 0.10 + queue_priority_count -= queue_node_priority + queue_priority_count += queue_node.queued_priority + current_tick_budget -= queue_node_priority + queue_node = queue_node.queue_next + continue + + if ((queue_node_flags & SS_BACKGROUND) && !bg_calc) + current_tick_budget = queue_priority_count_bg + bg_calc = TRUE + + tick_remaining = TICK_LIMIT_RUNNING - world.tick_usage + + if (current_tick_budget > 0 && queue_node_priority > 0) + tick_precentage = tick_remaining / (current_tick_budget / queue_node_priority) + else + tick_precentage = tick_remaining + + CURRENT_TICKLIMIT = world.tick_usage + tick_precentage + + if (!(queue_node_flags & SS_TICKER)) + ran_non_ticker = TRUE + ran = TRUE + tick_usage = world.tick_usage + queue_node_paused = (queue_node.state == SS_PAUSED || queue_node.state == SS_PAUSING) + last_type_processed = queue_node + + queue_node.state = SS_RUNNING + + var/state = queue_node.ignite(queue_node_paused) + if (state == SS_RUNNING) + state = SS_IDLE + current_tick_budget -= queue_node_priority + tick_usage = world.tick_usage - tick_usage + + if (tick_usage < 0) + tick_usage = 0 + + queue_node.state = state + + if (state == SS_PAUSED) + queue_node.paused_ticks++ + queue_node.paused_tick_usage += tick_usage + queue_node = queue_node.queue_next + continue + + queue_node.ticks = MC_AVERAGE(queue_node.ticks, queue_node.paused_ticks) + tick_usage += queue_node.paused_tick_usage + + queue_node.tick_usage = MC_AVERAGE_FAST(queue_node.tick_usage, tick_usage) + + queue_node.cost = MC_AVERAGE_FAST(queue_node.cost, TICK_DELTA_TO_MS(tick_usage)) + queue_node.paused_ticks = 0 + queue_node.paused_tick_usage = 0 + + if (queue_node_flags & SS_BACKGROUND) //update our running total + queue_priority_count_bg -= queue_node_priority + else + queue_priority_count -= queue_node_priority + + queue_node.last_fire = world.time + queue_node.times_fired++ + + if (queue_node_flags & SS_TICKER) + queue_node.next_fire = world.time + (world.tick_lag * queue_node.wait) + else if (queue_node_flags & SS_POST_FIRE_TIMING) + queue_node.next_fire = world.time + queue_node.wait + else if (queue_node_flags & SS_KEEP_TIMING) + queue_node.next_fire += queue_node.wait + else + queue_node.next_fire = queue_node.queued_time + queue_node.wait + + queue_node.queued_time = 0 + + //remove from queue + queue_node.dequeue() + + queue_node = queue_node.queue_next + + . = 1 + +//resets the queue, and all subsystems, while filtering out the subsystem lists +// called if any mc's queue procs runtime or exit improperly. +/datum/controller/master/proc/SoftReset(list/ticker_SS, list/normal_SS, list/lobby_SS) + . = 0 + log_world("MC: SoftReset called, resetting MC queue state.") + if (!istype(subsystems) || !istype(ticker_SS) || !istype(normal_SS) || !istype(lobby_SS)) + log_world("MC: SoftReset: Bad list contents: '[subsystems]' '[ticker_SS]' '[normal_SS]' '[lobby_SS]' Crashing!") + return + var/subsystemstocheck = subsystems + ticker_SS + normal_SS + lobby_SS + + for (var/thing in subsystemstocheck) + var/datum/controller/subsystem/SS = thing + if (!SS || !istype(SS)) + //list(SS) is so if a list makes it in the subsystem list, we remove the list, not the contents + subsystems -= list(SS) + ticker_SS -= list(SS) + normal_SS -= list(SS) + lobby_SS -= list(SS) + log_world("MC: SoftReset: Found bad entry in subsystem list, '[SS]'") + continue + if (SS.queue_next && !istype(SS.queue_next)) + log_world("MC: SoftReset: Found bad data in subsystem queue, queue_next = '[SS.queue_next]'") + SS.queue_next = null + if (SS.queue_prev && !istype(SS.queue_prev)) + log_world("MC: SoftReset: Found bad data in subsystem queue, queue_prev = '[SS.queue_prev]'") + SS.queue_prev = null + SS.queued_priority = 0 + SS.queued_time = 0 + SS.state = SS_IDLE + if (queue_head && !istype(queue_head)) + log_world("MC: SoftReset: Found bad data in subsystem queue, queue_head = '[queue_head]'") + queue_head = null + if (queue_tail && !istype(queue_tail)) + log_world("MC: SoftReset: Found bad data in subsystem queue, queue_tail = '[queue_tail]'") + queue_tail = null + queue_priority_count = 0 + queue_priority_count_bg = 0 + log_world("MC: SoftReset: Finished.") + . = 1 + + + +/datum/controller/master/stat_entry() + if(!statclick) + statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) + + stat("Byond:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%))") + stat("Master Controller:", statclick.update("(TickRate:[Master.processing]) (Iteration:[Master.iteration])")) + +/datum/controller/master/StartLoadingMap() + //disallow more than one map to load at once, multithreading it will just cause race conditions + while(map_loading) + stoplag() + for(var/S in subsystems) + var/datum/controller/subsystem/SS = S + SS.StartLoadingMap() + map_loading = TRUE + +/datum/controller/master/StopLoadingMap(bounds = null) + map_loading = FALSE + for(var/S in subsystems) + var/datum/controller/subsystem/SS = S + SS.StopLoadingMap() diff --git a/code/controllers/master_controller.dm b/code/controllers/master_controller.dm index 62209a1014..3075682438 100644 --- a/code/controllers/master_controller.dm +++ b/code/controllers/master_controller.dm @@ -2,6 +2,10 @@ //It ensures master_controller.process() is never doubled up by killing the MC (hence terminating any of its sleeping procs) //WIP, needs lots of work still +// +// TODO - This will be completely replaced by master.dm in time. +// + var/global/datum/controller/game_controller/master_controller //Set in world.New() var/global/controller_iteration = 0 @@ -31,8 +35,6 @@ datum/controller/game_controller/New() if(!syndicate_code_response) syndicate_code_response = generate_code_phrase() datum/controller/game_controller/proc/setup() - world.tick_lag = config.Ticklag - spawn(20) createRandomZlevel() diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm new file mode 100644 index 0000000000..ce9558ca4f --- /dev/null +++ b/code/controllers/subsystem.dm @@ -0,0 +1,192 @@ +/datum/controller/subsystem + // Metadata; you should define these. + name = "fire coderbus" //name of the subsystem + var/init_order = 0 //order of initialization. Higher numbers are initialized first, lower numbers later. Can be decimal and negative values. + var/wait = 20 //time to wait (in deciseconds) between each call to fire(). Must be a positive integer. + var/priority = 50 //When mutiple subsystems need to run in the same tick, higher priority subsystems will run first and be given a higher share of the tick before MC_TICK_CHECK triggers a sleep + + var/flags = 0 //see MC.dm in __DEFINES Most flags must be set on world start to take full effect. (You can also restart the mc to force them to process again) + + //set to 0 to prevent fire() calls, mostly for admin use or subsystems that may be resumed later + // use the SS_NO_FIRE flag instead for systems that never fire to keep it from even being added to the list + var/can_fire = TRUE + + // Bookkeeping variables; probably shouldn't mess with these. + var/last_fire = 0 //last world.time we called fire() + var/next_fire = 0 //scheduled world.time for next fire() + var/cost = 0 //average time to execute + var/tick_usage = 0 //average tick usage + var/state = SS_IDLE //tracks the current state of the ss, running, paused, etc. + var/paused_ticks = 0 //ticks this ss is taking to run right now. + var/paused_tick_usage //total tick_usage of all of our runs while pausing this run + var/ticks = 1 //how many ticks does this ss take to run on avg. + var/times_fired = 0 //number of times we have called fire() + var/queued_time = 0 //time we entered the queue, (for timing and priority reasons) + var/queued_priority //we keep a running total to make the math easier, if priority changes mid-fire that would break our running total, so we store it here + //linked list stuff for the queue + var/datum/controller/subsystem/queue_next + var/datum/controller/subsystem/queue_prev + +// Used to initialize the subsystem BEFORE the map has loaded +/datum/controller/subsystem/New() + +//This is used so the mc knows when the subsystem sleeps. do not override. +/datum/controller/subsystem/proc/ignite(resumed = 0) + set waitfor = 0 + . = SS_SLEEPING + fire(resumed) + . = state + if (state == SS_SLEEPING) + state = SS_IDLE + if (state == SS_PAUSING) + var/QT = queued_time + enqueue() + state = SS_PAUSED + queued_time = QT + +//previously, this would have been named 'process()' but that name is used everywhere for different things! +//fire() seems more suitable. This is the procedure that gets called every 'wait' deciseconds. +//Sleeping in here prevents future fires until returned. +/datum/controller/subsystem/proc/fire(resumed = 0) + flags |= SS_NO_FIRE + throw EXCEPTION("Subsystem [src]([type]) does not fire() but did not set the SS_NO_FIRE flag. Please add the SS_NO_FIRE flag to any subsystem that doesn't fire so it doesn't get added to the processing list and waste cpu.") + +/datum/controller/subsystem/Destroy() + dequeue() + can_fire = 0 + flags |= SS_NO_FIRE + Master.subsystems -= src + + +//Queue it to run. +// (we loop thru a linked list until we get to the end or find the right point) +// (this lets us sort our run order correctly without having to re-sort the entire already sorted list) +/datum/controller/subsystem/proc/enqueue() + var/SS_priority = priority + var/SS_flags = flags + var/datum/controller/subsystem/queue_node + var/queue_node_priority + var/queue_node_flags + + for (queue_node = Master.queue_head; queue_node; queue_node = queue_node.queue_next) + queue_node_priority = queue_node.queued_priority + queue_node_flags = queue_node.flags + + if (queue_node_flags & SS_TICKER) + if (!(SS_flags & SS_TICKER)) + continue + if (queue_node_priority < SS_priority) + break + + else if (queue_node_flags & SS_BACKGROUND) + if (!(SS_flags & SS_BACKGROUND)) + break + if (queue_node_priority < SS_priority) + break + + else + if (SS_flags & SS_BACKGROUND) + continue + if (SS_flags & SS_TICKER) + break + if (queue_node_priority < SS_priority) + break + + queued_time = world.time + queued_priority = SS_priority + state = SS_QUEUED + if (SS_flags & SS_BACKGROUND) //update our running total + Master.queue_priority_count_bg += SS_priority + else + Master.queue_priority_count += SS_priority + + queue_next = queue_node + if (!queue_node)//we stopped at the end, add to tail + queue_prev = Master.queue_tail + if (Master.queue_tail) + Master.queue_tail.queue_next = src + else //empty queue, we also need to set the head + Master.queue_head = src + Master.queue_tail = src + + else if (queue_node == Master.queue_head)//insert at start of list + Master.queue_head.queue_prev = src + Master.queue_head = src + queue_prev = null + else + queue_node.queue_prev.queue_next = src + queue_prev = queue_node.queue_prev + queue_node.queue_prev = src + + +/datum/controller/subsystem/proc/dequeue() + if (queue_next) + queue_next.queue_prev = queue_prev + if (queue_prev) + queue_prev.queue_next = queue_next + if (src == Master.queue_tail) + Master.queue_tail = queue_prev + if (src == Master.queue_head) + Master.queue_head = queue_next + queued_time = 0 + if (state == SS_QUEUED) + state = SS_IDLE + + +/datum/controller/subsystem/proc/pause() + . = 1 + switch(state) + if(SS_RUNNING) + state = SS_PAUSED + if(SS_SLEEPING) + state = SS_PAUSING + + +//used to initialize the subsystem AFTER the map has loaded +/datum/controller/subsystem/Initialize(start_timeofday) + var/time = (REALTIMEOFDAY - start_timeofday) / 10 + var/msg = "Initialized [name] subsystem within [time] second[time == 1 ? "" : "s"]!" + to_chat(world, "[msg]") + log_world(msg) + return time + +//hook for printing stats to the "MC" statuspanel for admins to see performance and related stats etc. +/datum/controller/subsystem/stat_entry(msg) + if(!statclick) + statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) + + + + if(can_fire && !(SS_NO_FIRE in flags)) + msg = "[round(cost,1)]ms|[round(tick_usage,1)]%|[round(ticks,0.1)]\t[msg]" + else + msg = "OFFLINE\t[msg]" + + var/title = name + if (can_fire) + title = "\[[state_letter()]][title]" + + stat(title, statclick.update(msg)) + +/datum/controller/subsystem/proc/state_letter() + switch (state) + if (SS_RUNNING) + . = "R" + if (SS_QUEUED) + . = "Q" + if (SS_PAUSED, SS_PAUSING) + . = "P" + if (SS_SLEEPING) + . = "S" + if (SS_IDLE) + . = " " + +//could be used to postpone a costly subsystem for (default one) var/cycles, cycles +//for instance, during cpu intensive operations like explosions +/datum/controller/subsystem/proc/postpone(cycles = 1) + if(next_fire - world.time < wait) + next_fire += (wait*cycles) + +//usually called via datum/controller/subsystem/New() when replacing a subsystem (i.e. due to a recurring crash) +//should attempt to salvage what it can from the old instance of subsystem +/datum/controller/subsystem/Recover() diff --git a/code/controllers/subsystems.dm b/code/controllers/subsystems.dm deleted file mode 100644 index 11025d8d53..0000000000 --- a/code/controllers/subsystems.dm +++ /dev/null @@ -1,46 +0,0 @@ -#define NEW_SS_GLOBAL(varname) if(varname != src){if(istype(varname)){Recover();qdel(varname);}varname = src;} - -/datum/subsystem - //things you will want to define - var/name //name of the subsystem - var/priority = 0 //priority affects order of initialization. Higher priorities are initialized first, lower priorities later. Can be decimal and negative values. - var/wait = 20 //time to wait (in deciseconds) between each call to fire(). Must be a positive integer. - - //things you will probably want to leave alone - var/can_fire = 0 //prevent fire() calls - var/last_fire = 0 //last world.time we called fire() - var/next_fire = 0 //scheduled world.time for next fire() - var/cpu = 0 //cpu-usage stats (somewhat vague) - var/cost = 0 //average time to execute - var/times_fired = 0 //number of times we have called fire() - -//used to initialize the subsystem BEFORE the map has loaded -/datum/subsystem/New() - -//previously, this would have been named 'process()' but that name is used everywhere for different things! -//fire() seems more suitable. This is the procedure that gets called every 'wait' deciseconds. -//fire(), and the procs it calls, SHOULD NOT HAVE ANY SLEEP OPERATIONS in them! -//YE BE WARNED! -/datum/subsystem/proc/fire() - can_fire = 0 - -//used to initialize the subsystem AFTER the map has loaded -/datum/subsystem/proc/Initialize(start_timeofday) - var/time = (world.timeofday - start_timeofday) / 10 - var/msg = "Initialized [name] SubSystem within [time] seconds" - world << "[msg]" - world.log << msg - -//hook for printing stats to the "MC" statuspanel for admins to see performance and related stats etc. -/datum/subsystem/proc/stat_entry() - stat(name, "[round(cost,0.001)]ds\t(CPU:[round(cpu,1)]%)") - -//could be used to postpone a costly subsystem for one cycle -//for instance, during cpu intensive operations like explosions -/datum/subsystem/proc/postpone() - if(next_fire - world.time < wait) - next_fire += wait - -//usually called via datum/subsystem/New() when replacing a subsystem (i.e. due to a recurring crash) -//should attempt to salvage what it can from the old instance of subsystem -/datum/subsystem/proc/Recover() diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm index f0d3cc3341..fcf239c88f 100644 --- a/code/controllers/verbs.dm +++ b/code/controllers/verbs.dm @@ -1,19 +1,57 @@ //TODO: rewrite and standardise all controller datums to the datum/controller type //TODO: allow all controllers to be deleted for clean restarts (see WIP master controller stuff) - MC done - lighting done -/client/proc/restart_controller(controller in list("Supply")) + +// Clickable stat() button. +/obj/effect/statclick + name = "Initializing..." + var/target + +/obj/effect/statclick/New(loc, text, target) //Don't port this to Initialize it's too critical + ..() + name = text + src.target = target + +/obj/effect/statclick/proc/update(text) + name = text + return src + +/obj/effect/statclick/debug + var/class + +/obj/effect/statclick/debug/Click() + if(!usr.client.holder || !target) + return + if(!class) + if(istype(target, /datum/controller/subsystem)) + class = "subsystem" + else if(istype(target, /datum/controller)) + class = "controller" + else if(istype(target, /datum)) + class = "datum" + else + class = "unknown" + + usr.client.debug_variables(target) + message_admins("Admin [key_name_admin(usr)] is debugging the [target] [class].") + + +// Debug verbs. +/client/proc/restart_controller(controller in list("Master", "Failsafe")) set category = "Debug" set name = "Restart Controller" set desc = "Restart one of the various periodic loop controllers for the game (be careful!)" - if(!holder) return - usr = null - src = null + if(!holder) + return switch(controller) - if("Supply") - supply_controller.process() - feedback_add_details("admin_verb","RSupply") + if("Master") + Recreate_MC() + feedback_add_details("admin_verb","RMC") + if("Failsafe") + new /datum/controller/failsafe() + feedback_add_details("admin_verb","RFailsafe") + message_admins("Admin [key_name_admin(usr)] has restarted the [controller] controller.") - return /client/proc/debug_antagonist_template(antag_type in all_antag_types) set category = "Debug" diff --git a/polaris.dme b/polaris.dme index 9f075b1d76..a60f9ef6e8 100644 --- a/polaris.dme +++ b/polaris.dme @@ -30,7 +30,9 @@ #include "code\__defines\lighting.dm" #include "code\__defines\machinery.dm" #include "code\__defines\map.dm" +#include "code\__defines\math.dm" #include "code\__defines\math_physics.dm" +#include "code\__defines\MC.dm" #include "code\__defines\misc.dm" #include "code\__defines\mobs.dm" #include "code\__defines\planets.dm" @@ -38,6 +40,7 @@ #include "code\__defines\research.dm" #include "code\__defines\species_languages.dm" #include "code\__defines\targeting.dm" +#include "code\__defines\tick.dm" #include "code\__defines\turfs.dm" #include "code\__defines\unit_tests.dm" #include "code\__defines\xenoarcheaology.dm" @@ -66,6 +69,9 @@ #include "code\_helpers\type2type.dm" #include "code\_helpers\unsorted.dm" #include "code\_helpers\vector.dm" +#include "code\_helpers\sorts\__main.dm" +#include "code\_helpers\sorts\comparators.dm" +#include "code\_helpers\sorts\TimSort.dm" #include "code\_onclick\_defines.dm" #include "code\_onclick\adjacent.dm" #include "code\_onclick\ai.dm" @@ -128,12 +134,15 @@ #include "code\controllers\autotransfer.dm" #include "code\controllers\communications.dm" #include "code\controllers\configuration.dm" +#include "code\controllers\controller.dm" #include "code\controllers\emergency_shuttle_controller.dm" +#include "code\controllers\failsafe.dm" #include "code\controllers\hooks-defs.dm" #include "code\controllers\hooks.dm" +#include "code\controllers\master.dm" #include "code\controllers\master_controller.dm" #include "code\controllers\shuttle_controller.dm" -#include "code\controllers\subsystems.dm" +#include "code\controllers\subsystem.dm" #include "code\controllers\verbs.dm" #include "code\controllers\voting.dm" #include "code\controllers\observer_listener\atom\observer.dm" From 2bdfe8bfa3970313974c84131474f332f85a8cc0 Mon Sep 17 00:00:00 2001 From: LorenLuke Date: Wed, 31 May 2017 18:44:43 -0700 Subject: [PATCH 02/22] Prevents people from seeing names/examining cloaked changelings at range --- .../mob/living/carbon/human/examine.dm | 29 +++++++++++++++++-- code/modules/mob/living/carbon/human/human.dm | 2 ++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 2953591a5a..cb8c2ac767 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -1,4 +1,5 @@ /mob/living/carbon/human/examine(mob/user) + var/skipgloves = 0 var/skipsuitstorage = 0 var/skipjumpsuit = 0 @@ -17,6 +18,18 @@ var/skiparms = 0 var/skipfeet = 0 + + var/cloaked = 0 // 0 for normal, 1 for cloaked close + + if(mind.changeling && mind.changeling.cloaked && !istype(user, /mob/observer)) + var/distance = get_dist(user, src) + if(distance > 2) + src.loc.examine(user) + return + else + cloaked = 1 + + var/looks_synth = looksSynthetic() //exosuits and helmets obscure our view and stuff. @@ -79,9 +92,13 @@ var/list/msg = list("*---------*\nThis is ") + var/datum/gender/T = gender_datums[get_gender()] + if(skipjumpsuit && skipface) //big suits/masks/helmets make it hard to tell their gender T = gender_datums[PLURAL] + if(cloaked) + T = gender_datums[NEUTER] else if(species && species.ambiguous_genders) var/can_detect_gender = FALSE @@ -441,17 +458,25 @@ msg += "Medical records: \[View\] \[Add comment\]\n" - if(print_flavor_text()) msg += "[print_flavor_text()]\n" + if(print_flavor_text() && !cloaked) + msg += "[print_flavor_text()]\n" msg += "*---------*
" msg += applying_pressure - if (pose) + if (pose && !cloaked) if( findtext(pose,".",lentext(pose)) == 0 && findtext(pose,"!",lentext(pose)) == 0 && findtext(pose,"?",lentext(pose)) == 0 ) pose = addtext(pose,".") //Makes sure all emotes end with a period. msg += "[T.He] [pose]" user << jointext(msg, null) + +/mob/living/carbon/human/get_description_fluff() + if(mind.changeling && mind.changeling.cloaked) + return "" + else + return ..() + //Helper procedure. Called by /mob/living/carbon/human/examine() and /mob/living/carbon/human/Topic() to determine HUD access to security and medical records. /proc/hasHUD(mob/M as mob, hudtype) if(istype(M, /mob/living/carbon/human)) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 66abc50cab..856cf3aa04 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -305,6 +305,8 @@ //repurposed proc. Now it combines get_id_name() and get_face_name() to determine a mob's name variable. Made into a seperate proc as it'll be useful elsewhere /mob/living/carbon/human/proc/get_visible_name() + if( mind.changeling && mind.changeling.cloaked) + return "Unknown" if( wear_mask && (wear_mask.flags_inv&HIDEFACE) ) //Wearing a mask which hides our face, use id-name if possible return get_id_name("Unknown") if( head && (head.flags_inv&HIDEFACE) ) From 8b3403d54f5674574a386ae5b121e57f9501f9b3 Mon Sep 17 00:00:00 2001 From: Leshana Date: Thu, 1 Jun 2017 15:52:38 -0400 Subject: [PATCH 03/22] Prevent returning null from get_authentification_rank() Make sure we return the no job value if they have an id without a rank that is in a PDA. Should fix https://github.com/PolarisSS13/Polaris/issues/3486 --- code/modules/mob/living/carbon/human/human.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 66abc50cab..83ce56e29e 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -261,7 +261,7 @@ var/obj/item/device/pda/pda = wear_id if (istype(pda)) if (pda.id) - return pda.id.rank + return pda.id.rank ? pda.id.rank : if_no_job else return pda.ownrank else From b17fb867214eb740c78671862d3a1c4d2ad1fad9 Mon Sep 17 00:00:00 2001 From: Leshana Date: Tue, 30 May 2017 23:54:59 -0400 Subject: [PATCH 04/22] Tweaks Statpanel and System Initialization --- code/controllers/master.dm | 4 ++++ code/modules/admin/verbs/modifyvariables.dm | 2 +- code/modules/client/client procs.dm | 12 ++++++++++++ code/modules/examine/examine.dm | 5 +++-- code/modules/mob/mob.dm | 17 +++++++++++++++++ code/world.dm | 3 +-- 6 files changed, 38 insertions(+), 5 deletions(-) diff --git a/code/controllers/master.dm b/code/controllers/master.dm index 4dcf58d45e..981cf6d2bb 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -149,7 +149,11 @@ var/CURRENT_TICKLIMIT = TICK_LIMIT_RUNNING // Sort subsystems by display setting for easy access. sortTim(subsystems, /proc/cmp_subsystem_display) // Set world options. + #ifdef UNIT_TEST + world.sleep_offline = 0 + #else world.sleep_offline = 1 + #endif world.fps = config.fps var/initialized_tod = REALTIMEOFDAY sleep(1) diff --git a/code/modules/admin/verbs/modifyvariables.dm b/code/modules/admin/verbs/modifyvariables.dm index 51f1fbceb3..ecd26e9b56 100644 --- a/code/modules/admin/verbs/modifyvariables.dm +++ b/code/modules/admin/verbs/modifyvariables.dm @@ -4,7 +4,7 @@ var/list/forbidden_varedit_object_types = list( /datum/feedback_variable //Prevents people messing with feedback gathering, ) -var/list/VVlocked = list("vars", "client", "virus", "viruses", "cuffed", "last_eaten", "unlock_content", "bound_x", "bound_y", "step_x", "step_y", "force_ending") +var/list/VVlocked = list("vars", "client", "virus", "viruses", "cuffed", "last_eaten", "unlock_content", "bound_x", "bound_y", "step_x", "step_y", "force_ending", "queued_priority") var/list/VVicon_edit_lock = list("icon", "icon_state", "overlays", "underlays") var/list/VVckey_edit = list("key", "ckey") diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 212e7a49e8..d908df1b3d 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -284,6 +284,18 @@ if(inactivity > duration) return inactivity return 0 +// Byond seemingly calls stat, each tick. +// Calling things each tick can get expensive real quick. +// So we slow this down a little. +// See: http://www.byond.com/docs/ref/info.html#/client/proc/Stat +/client/Stat() + . = ..() + if (holder) + sleep(1) + else + sleep(5) + stoplag() + /client/proc/last_activity_seconds() return inactivity / 10 diff --git a/code/modules/examine/examine.dm b/code/modules/examine/examine.dm index f09fce6cf3..ec64ffbe41 100644 --- a/code/modules/examine/examine.dm +++ b/code/modules/examine/examine.dm @@ -49,9 +49,10 @@ description_holders["icon"] = "\icon[A]" description_holders["desc"] = A.desc -/client/Stat() +/mob/Stat() . = ..() - if(usr && statpanel("Examine")) + if(client && statpanel("Examine")) + var/description_holders = client.description_holders stat(null,"[description_holders["icon"]] [description_holders["name"]]") //The name, written in big letters. stat(null,"[description_holders["desc"]]") //the default examine text. if(description_holders["info"]) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index f16af92a74..7574bb4c77 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -672,6 +672,23 @@ if(processScheduler) processScheduler.statProcesses() + if(statpanel("MC")) + stat("CPU:","[world.cpu]") + stat("Instances:","[world.contents.len]") + stat(null) + if(Master) + Master.stat_entry() + else + stat("Master Controller:", "ERROR") + if(Failsafe) + Failsafe.stat_entry() + else + stat("Failsafe Controller:", "ERROR") + if(Master) + stat(null) + for(var/datum/controller/subsystem/SS in Master.subsystems) + SS.stat_entry() + if(listed_turf && client) if(!TurfAdjacent(listed_turf)) listed_turf = null diff --git a/code/world.dm b/code/world.dm index 02ec2669a9..8d51f84b47 100644 --- a/code/world.dm +++ b/code/world.dm @@ -70,8 +70,6 @@ var/global/datum/global_init/init = new () #if UNIT_TEST log_unit_test("Unit Tests Enabled. This will destroy the world when testing is complete.") log_unit_test("If you did not intend to enable this please check code/__defines/unit_testing.dm") -#else - sleep_offline = 1 #endif // Set up roundstart seed list. @@ -116,6 +114,7 @@ var/global/datum/global_init/init = new () processScheduler = new master_controller = new /datum/controller/game_controller() + Master.Initialize(10, FALSE) spawn(1) processScheduler.deferSetupFor(/datum/controller/process/ticker) processScheduler.setup() From 8315abb352a612b269b0b3458493f94eaee1bc77 Mon Sep 17 00:00:00 2001 From: Leshana Date: Fri, 2 Jun 2017 07:56:09 -0400 Subject: [PATCH 05/22] Fix issues with radiation controller on multi-z maps. Radiation sources are by design z-level specific, the ray trace check needs to respect this. --- code/datums/repositories/radiation.dm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/datums/repositories/radiation.dm b/code/datums/repositories/radiation.dm index 5471282026..0245bb801c 100644 --- a/code/datums/repositories/radiation.dm +++ b/code/datums/repositories/radiation.dm @@ -46,6 +46,8 @@ var/global/repository/radiation/radiation_repository = new() var/datum/radiation_source/source = value if(source.rad_power < .) continue // Already being affected by a stronger source + if(source.source_turf.z != T.z) + continue // Radiation is not multi-z var/dist = get_dist(source.source_turf, T) if(dist > source.range) continue // Too far to possibly affect From 72c8b745ddcf2a640f397685e2ec3c597ae93402 Mon Sep 17 00:00:00 2001 From: Yoshax Date: Fri, 2 Jun 2017 19:18:29 +0100 Subject: [PATCH 06/22] Fixes an advanced medical scanner bug --- code/game/machinery/adv_med.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/game/machinery/adv_med.dm b/code/game/machinery/adv_med.dm index b1cf9bac08..ed3ea420b3 100644 --- a/code/game/machinery/adv_med.dm +++ b/code/game/machinery/adv_med.dm @@ -287,8 +287,9 @@ var/bloodData[0] if(H.vessel) var/blood_volume = round(H.vessel.get_reagent_amount("blood")) + var/blood_max = H.species.blood_volume bloodData["volume"] = blood_volume - bloodData["percent"] = round(((blood_volume / 560)*100)) + bloodData["percent"] = round(((blood_volume / blood_max)*100)) occupantData["blood"] = bloodData From 4e6a5ab55b1e51daa4172537463e08d2cfa9377f Mon Sep 17 00:00:00 2001 From: BuildTools Date: Sat, 3 Jun 2017 01:15:27 -0400 Subject: [PATCH 07/22] Adds "treadwell" engiborg sprite. --- .../mob/living/silicon/robot/robot_modules.dm | 3 ++- icons/mob/robots.dmi | Bin 362727 -> 363965 bytes 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm index 4b2c7f6f79..1dc391f478 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules.dm @@ -334,7 +334,8 @@ var/global/list/robot_modules = list( "Antique" = "engineerrobot", "Landmate" = "landmate", "Landmate - Treaded" = "engiborg+tread", - "Drone" = "drone-engineer" + "Drone" = "drone-engineer", + "Treadwell" = "treadwell" ) /obj/item/weapon/robot_module/robot/engineering/construction diff --git a/icons/mob/robots.dmi b/icons/mob/robots.dmi index 2b3dfe51d82b11cd8275bbcc5f1b0a06d6a8fabc..e742bdc8040bcca702513ab219e717d6cecc0d53 100644 GIT binary patch delta 17860 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 CEY*1c delta 16613 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 IH%xs04?MVJ6aWAK From 5ce5f87d4efa42c007a3a0acccf3381ff9957978 Mon Sep 17 00:00:00 2001 From: Yoshax Date: Fri, 2 Jun 2017 19:28:20 +0100 Subject: [PATCH 08/22] Fixes the printout too --- code/game/machinery/adv_med.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/game/machinery/adv_med.dm b/code/game/machinery/adv_med.dm index ed3ea420b3..f980cfd0b1 100644 --- a/code/game/machinery/adv_med.dm +++ b/code/game/machinery/adv_med.dm @@ -460,7 +460,8 @@ if(occupant.vessel) var/blood_volume = round(occupant.vessel.get_reagent_amount("blood")) - var/blood_percent = blood_volume / 560 + var/blood_max = occupant.species.blood_volume + var/blood_percent = blood_volume / blood_max blood_percent *= 100 extra_font = " 448 ? "blue" : "red"]>" From 3a751fd403cf9befe59d6e579c3e9f2c4b6609cc Mon Sep 17 00:00:00 2001 From: Anewbe Date: Sat, 3 Jun 2017 18:30:16 -0500 Subject: [PATCH 09/22] Teshari can now be cloned properly --- code/game/machinery/cloning.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm index 1c3d957b98..1a1bd9a463 100644 --- a/code/game/machinery/cloning.dm +++ b/code/game/machinery/cloning.dm @@ -181,7 +181,7 @@ use_power(7500) //This might need tweaking. return - else if((occupant.health >= heal_level) && (!eject_wait)) + else if((occupant.health >= heal_level || occupant.health == occupant.maxHealth) && (!eject_wait)) playsound(src.loc, 'sound/machines/ding.ogg', 50, 1) audible_message("\The [src] signals that the cloning process is complete.") connected_message("Cloning Process Complete.") From e2860b30b3465e47b00d7fa5ae1083a18be8a5df Mon Sep 17 00:00:00 2001 From: Anewbe Date: Sat, 3 Jun 2017 19:42:17 -0500 Subject: [PATCH 10/22] Fixes a runtime --- code/modules/mob/living/carbon/human/human.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 6edc754447..ba7aed0250 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -305,7 +305,7 @@ //repurposed proc. Now it combines get_id_name() and get_face_name() to determine a mob's name variable. Made into a seperate proc as it'll be useful elsewhere /mob/living/carbon/human/proc/get_visible_name() - if( mind.changeling && mind.changeling.cloaked) + if( mind && mind.changeling && mind.changeling.cloaked) return "Unknown" if( wear_mask && (wear_mask.flags_inv&HIDEFACE) ) //Wearing a mask which hides our face, use id-name if possible return get_id_name("Unknown") From 5daea1a43d7ae19278fa8c64db7dae14c657d773 Mon Sep 17 00:00:00 2001 From: Yoshax Date: Sun, 4 Jun 2017 02:40:58 +0100 Subject: [PATCH 11/22] Lazyports artifact containers from Bay --- code/_onclick/drag_drop.dm | 10 +++++ .../xenoarcheaology/anomaly_container.dm | 43 +++++++++++++++++++ polaris.dme | 1 + 3 files changed, 54 insertions(+) create mode 100644 code/modules/xenoarcheaology/anomaly_container.dm diff --git a/code/_onclick/drag_drop.dm b/code/_onclick/drag_drop.dm index 42a0574f4d..65974fe009 100644 --- a/code/_onclick/drag_drop.dm +++ b/code/_onclick/drag_drop.dm @@ -5,6 +5,16 @@ recieving object instead, so that's the default action. This allows you to drag almost anything into a trash can. */ + +/atom/proc/CanMouseDrop(atom/over, var/mob/user = usr) + if(!user || !over) + return FALSE + if(user.incapacitated()) + return FALSE + if(!src.Adjacent(user) || !over.Adjacent(user)) + return FALSE // should stop you from dragging through windows + return TRUE + /atom/MouseDrop(atom/over) if(!usr || !over) return if(!Adjacent(usr) || !over.Adjacent(usr)) return // should stop you from dragging through windows diff --git a/code/modules/xenoarcheaology/anomaly_container.dm b/code/modules/xenoarcheaology/anomaly_container.dm new file mode 100644 index 0000000000..616b04b10d --- /dev/null +++ b/code/modules/xenoarcheaology/anomaly_container.dm @@ -0,0 +1,43 @@ +/obj/structure/anomaly_container + name = "anomaly container" + desc = "Used to safely contain and move anomalies." + icon = 'icons/obj/xenoarchaeology.dmi' + icon_state = "anomaly_container" + density = 1 + + var/obj/machinery/artifact/contained + +/obj/structure/anomaly_container/initialize() + ..() + + var/obj/machinery/artifact/A = locate() in loc + if(A) + contain(A) + +/obj/structure/anomaly_container/attack_hand(var/mob/user) + release() + +/obj/structure/anomaly_container/attack_robot(var/mob/user) + if(Adjacent(user)) + release() + +/obj/structure/anomaly_container/proc/contain(var/obj/machinery/artifact/artifact) + if(contained) + return + contained = artifact + artifact.forceMove(src) + underlays += image(artifact) + desc = "Used to safely contain and move anomalies. \The [contained] is kept inside." + +/obj/structure/anomaly_container/proc/release() + if(!contained) + return + contained.dropInto(src) + contained = null + underlays.Cut() + desc = initial(desc) + +/obj/machinery/artifact/MouseDrop(var/obj/structure/anomaly_container/over_object) + if(istype(over_object) && Adjacent(over_object) && CanMouseDrop(over_object, usr)) + Bumped(usr) + over_object.contain(src) \ No newline at end of file diff --git a/polaris.dme b/polaris.dme index 3d82655c4e..59cf36b9e8 100644 --- a/polaris.dme +++ b/polaris.dme @@ -2120,6 +2120,7 @@ #include "code\modules\virus2\helpers.dm" #include "code\modules\virus2\isolator.dm" #include "code\modules\virus2\items_devices.dm" +#include "code\modules\xenoarcheaology\anomaly_container.dm" #include "code\modules\xenoarcheaology\boulder.dm" #include "code\modules\xenoarcheaology\effect.dm" #include "code\modules\xenoarcheaology\manuals.dm" From 8d04339b1ca68b8a0f311bcddec57683e2e102c5 Mon Sep 17 00:00:00 2001 From: Yoshax Date: Sun, 4 Jun 2017 22:49:28 +0100 Subject: [PATCH 12/22] Commits missed file --- icons/obj/xenoarchaeology.dmi | Bin 63847 -> 64398 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/obj/xenoarchaeology.dmi b/icons/obj/xenoarchaeology.dmi index a074f17c3f9cf368da35947f86207a09870dad38..24ebc6ef6cbea5362bf1c262be31a93dbadf2991 100644 GIT binary patch delta 23734 zcmZ^~1z1$w+c!EiC@I~kh#;VJ4oE6WOGqQ#4N`kZrAxX&1f)Z{Q9$W#X@(fO83w-X z^SuA}yzh6;xtNQoJ!|c~*1GTYi+jV5F`_RpV%gB{;vPbQm$o6VL-pyF<6>gISpBX? zz}Lo4c+L-x#TQ{Gb2N3vKV$4fXLm$v?K)jVGHY|v1rfKGIp~#G&1yXsvx9Exw(!}& zu!imEA{K)SNc)+DVSZzt5B{cE5s(*w+_048{R#P*L)t@P^%jF)+F)4P2y zF|W=rvBGiyMdASkJb9~E58kBakgRrS_-|fy2`IS~{1)y$Nj?dr6o+H+D8D{X?Am{T zxnZ6Ei|5@w&y-7c&Xee3m#mW;Kwpu&alP=PxALo>86zds$@&Otb?JL^_$0;5#~&Vi zAzjoHtHerAG&l5rp2z-V&%EFTIE)m9>2Ja)fz zC3=Y5lC=6noEy58dA;ys(V!YEa+YA2XMmt&LjyLclj%5hvw#=%xl{|W50hcI1Thab ze;hFrKM+Su55Az`kE5dW@5&9c2+dP`@bcbE^q1FK=S1NjTp*!m&JsBzOJ&~m!GiTw z7o9X61zKg#3550B8>S5%J61UUtT^q^6ROk_DW`_=HsmvQc-hB9Xh-t6v)ei4!Ec4aH$nFRcgVrAol$LJX&j-)XJO>OT=fe5w6y`S~?{i;rjbcf3KpZ#{ z@bV9egUUs_&~r+n4{xX6DtMuGzjfm*<&+RIBET6 zrq!L5-eR@B#LsoTsF*gqstYU5&CERY>A^~VNZGO;*dKZScH1=JI!CJQ;Qrjli0?Z~ zNr?i|+k7vZ%wyUaX1e=NPI(4!DJ8eRzbL3-#*oSe92ptqKJ4EC*a5F&KfZb4HVb)2~BH+^&|_^9E11kr%$0d%apd9b*DwRihedK_sWbm$Yg6@ z2q|D#x7#Vc<#ke3XEtGGv-47XgqgC$SaY$SHJiHl_wS8Mt5Jp7W>#kE@Pg~$fID=_ z#j+5n$(J;@v|Ov|Wr!%=6-s$0_~-j0m)Cg>&VKyFIMT$j*ee%E^TNzeajd-gH8rHu zMu3Eit7}|x^2e4Iad*Fw9O4mRIkTSjV%lgE~_#0)K`rLCJ+b z_)+h{gl_iTsUe%bYlPplnQ&QHy@(nc;vo=#3Gz}_TGMw^J1=84M(A!f&Qa6ax_GnO zP)#jP^O?ijw}~BM=$y(=#Kh*4x@%u}cz7)Mp$EX0FOVC?yr0I##)^D84|c(G%pU$& z^}aVRKOdtvnqEd%H}zY$%Mlm6jEHu-SIlB(qIe5|kaczClaHdoq9`vaiY@u}Ew3CP z`sC4e?PXSrferf_vql@ugxh7GwB(nM57CY$bDAxn!|$grzV#?RYq;r3PPyGmFIz>o4b2%UYBp*yZp7m z%GF?$f4jbxR_Ja;JA=EsJ1%z6a<`NU!2O5IPPXN}4i)4aF`I8wZM#}Q^y$DiK$!4y z%4f6R@n(AN^QNR@tEInY_`#HqU_XR-_uZxKn_hQP%uykeXwLH`3MRsNrQbr23>xXJ&3qo0uAs z`shB=x2f<#sVL6fPPA8xcpTyU-Mbk)iM3uDJ!JIUI;+zyp|F6xcN+Dcs~C2 zIE%;=?UP4Oy{~)^pVHN z@4^pI)3(PL7Yo$WPQQeA8}HiQxwlON{#-^n~t<|NKnU+0! zJAZ|aO?yW@8+>EUVEiB4lKxxBk_{e19~_ABM54&y2iLa>{ap~^Qs@_kwMJhye||V# zBxe(LvVSubD*)X1AksXcJ>+ih*JFU7Yi$>&?y32HU-gRC;>PB8&q;7q?g3QvxdDmUvJ9F&5@@?|FFZr}FEURAf zMZbwuaHJCh@8xSwLoXK&<@)b=yS1=7Zff=PRi_WV0H?H+KMAqq(|ly@nL>bwiGO

jv-I`zlel;9-pIs6a9|+% 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 KwM#S|qy7gYhL-~X From c0bce69b7ae95489f50cc21ff800b3ae8dce37d5 Mon Sep 17 00:00:00 2001 From: Leshana Date: Thu, 25 May 2017 15:43:59 -0400 Subject: [PATCH 13/22] Fixes a typo in Guest Pass UI template. Fixes https://github.com/VOREStation/VOREStation/issues/1441 --- nano/templates/guest_pass.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nano/templates/guest_pass.tmpl b/nano/templates/guest_pass.tmpl index 80525f5331..821ba0a474 100644 --- a/nano/templates/guest_pass.tmpl +++ b/nano/templates/guest_pass.tmpl @@ -19,7 +19,7 @@ {{else}}

Guest pass terminal #{{:data.uid}}

- {{:helper.link('View activity log', 'list', {'mode' : 1})}} {{:helper.link('Issure Pass', 'eject', {'action' : 'issue'})}} + {{:helper.link('View activity log', 'list', {'mode' : 1})}} {{:helper.link('Issue Pass', 'eject', {'action' : 'issue'})}}

From 746571f53f1135a0878e2e0f0c5d371bfd3fcfee Mon Sep 17 00:00:00 2001 From: Leshana Date: Thu, 25 May 2017 15:44:47 -0400 Subject: [PATCH 14/22] Adds bedsheets to the list of washable items. Fixes https://github.com/VOREStation/VOREStation/issues/1456 --- code/game/machinery/washing_machine.dm | 2 +- html/changelogs/Leshana-smol-fixes.yml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 html/changelogs/Leshana-smol-fixes.yml diff --git a/code/game/machinery/washing_machine.dm b/code/game/machinery/washing_machine.dm index cf96260358..f245682861 100644 --- a/code/game/machinery/washing_machine.dm +++ b/code/game/machinery/washing_machine.dm @@ -115,7 +115,7 @@ user << "You can't fit \the [W] inside." return - else if(istype(W, /obj/item/clothing)) + else if(istype(W, /obj/item/clothing) || istype(W, /obj/item/weapon/bedsheet)) if(washing.len < 5) if(state in list(1, 3)) user.drop_item() diff --git a/html/changelogs/Leshana-smol-fixes.yml b/html/changelogs/Leshana-smol-fixes.yml new file mode 100644 index 0000000000..9d972dec1a --- /dev/null +++ b/html/changelogs/Leshana-smol-fixes.yml @@ -0,0 +1,4 @@ +author: Leshana +delete-after: True +changes: + - bugfix: "Bedsheets can be put into washing machines again." From d438cba4062880b242ba6aaafbafc1139a80fb00 Mon Sep 17 00:00:00 2001 From: Leshana Date: Mon, 5 Jun 2017 15:04:29 -0400 Subject: [PATCH 15/22] Fix do_after exploits in Water-Coolers * Fixes https://github.com/VOREStation/VOREStation/issues/561 * Prevents multi-clicking from doing the construction steps multiple times (spawning multiple jugs, or emptying contents of jug multiplie times). * Prevents after-attack from transferring reagent from jug to cooler twice when putting a jug on. --- code/modules/reagents/reagent_dispenser.dm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm index 49d251d311..e84a963446 100644 --- a/code/modules/reagents/reagent_dispenser.dm +++ b/code/modules/reagents/reagent_dispenser.dm @@ -227,7 +227,7 @@ src.add_fingerprint(user) if(bottle) playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1) - if(do_after(user, 20)) + if(do_after(user, 20) && bottle) user << "You unfasten the jug." var/obj/item/weapon/reagent_containers/glass/cooler_bottle/G = new /obj/item/weapon/reagent_containers/glass/cooler_bottle( src.loc ) for(var/datum/reagent/R in reagents.reagent_list) @@ -262,7 +262,7 @@ if(!bottle && !cupholder) playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1) user << "You start taking the water-cooler apart." - if(do_after(user, 20)) + if(do_after(user, 20) && !bottle && !cupholder) user << "You take the water-cooler apart." new /obj/item/stack/material/plastic( src.loc, 4 ) qdel(src) @@ -274,7 +274,7 @@ if(anchored) var/obj/item/weapon/reagent_containers/glass/cooler_bottle/G = I user << "You start to screw the bottle onto the water-cooler." - if(do_after(user, 20)) + if(do_after(user, 20) && !bottle && anchored) bottle = 1 update_icon() user << "You screw the bottle onto the water-cooler!" @@ -286,7 +286,7 @@ user << "You need to wrench down the cooler first." else user << "There is already a bottle there!" - return + return 1 if(istype(I, /obj/item/stack/material/plastic)) if(!cupholder) @@ -295,7 +295,7 @@ src.add_fingerprint(user) user << "You start to attach a cup dispenser onto the water-cooler." playsound(src.loc, 'sound/items/Deconstruct.ogg', 50, 1) - if(do_after(user, 20)) + if(do_after(user, 20) && !cupholder && anchored) if (P.use(1)) user << "You attach a cup dispenser onto the water-cooler." cupholder = 1 From ef7b19926aec4952f74a090af058ac987dabe43f Mon Sep 17 00:00:00 2001 From: Anewbe Date: Mon, 5 Jun 2017 15:08:28 -0500 Subject: [PATCH 16/22] Fixes an examine runtime --- code/modules/mob/living/carbon/human/examine.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index cb8c2ac767..615a3ecf53 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -21,7 +21,7 @@ var/cloaked = 0 // 0 for normal, 1 for cloaked close - if(mind.changeling && mind.changeling.cloaked && !istype(user, /mob/observer)) + if(mind && mind.changeling && mind.changeling.cloaked && !istype(user, /mob/observer)) var/distance = get_dist(user, src) if(distance > 2) src.loc.examine(user) @@ -472,7 +472,7 @@ /mob/living/carbon/human/get_description_fluff() - if(mind.changeling && mind.changeling.cloaked) + if(mind && mind.changeling && mind.changeling.cloaked) return "" else return ..() From 914a291c1bc6c159d95a47938a6681c2bad681d3 Mon Sep 17 00:00:00 2001 From: Woodratt Date: Mon, 5 Jun 2017 17:03:58 -0700 Subject: [PATCH 17/22] WR Lighting changes and other tweaks - Adjusts the radius of lights - Roofs to shuttles - Allows placement of full windows in DM --- code/game/objects/structures/window.dm | 5 +++++ code/game/turfs/unsimulated/floor.dm | 3 +++ code/modules/power/lighting.dm | 6 +++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 809dd008a4..4c36a7ac15 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -472,6 +472,11 @@ if (constructed) state = 0 +/obj/structure/window/reinforced/full + dir = 5 + icon_state = "fwindow" + maxhealth = 60 + /obj/structure/window/reinforced/tinted name = "tinted window" desc = "It looks rather strong and opaque. Might take a few good hits to shatter it." diff --git a/code/game/turfs/unsimulated/floor.dm b/code/game/turfs/unsimulated/floor.dm index b2e7836334..bd4fdca814 100644 --- a/code/game/turfs/unsimulated/floor.dm +++ b/code/game/turfs/unsimulated/floor.dm @@ -7,3 +7,6 @@ name = "mask" icon = 'icons/turf/walls.dmi' icon_state = "rockvault" + +/turf/unsimulated/floor/shuttle_ceiling + icon_state = "reinforced" \ No newline at end of file diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm index 09921cf58b..1f4f8ec756 100644 --- a/code/modules/power/lighting.dm +++ b/code/modules/power/lighting.dm @@ -162,7 +162,7 @@ active_power_usage = 20 power_channel = LIGHT //Lights are calc'd via area so they dont need to be in the machine list var/on = 0 // 1 if on, 0 if off - var/brightness_range = 10 // luminosity when on, also used in power calculation + var/brightness_range = 6 // luminosity when on, also used in power calculation var/brightness_power = 3 var/brightness_color = null var/status = LIGHT_OK // LIGHT_OK, _EMPTY, _BURNED or _BROKEN @@ -180,7 +180,7 @@ icon_state = "bulb1" base_state = "bulb" fitting = "bulb" - brightness_range = 6 + brightness_range = 4 brightness_power = 2 brightness_color = "#FFF4E5" desc = "A small lighting fixture." @@ -199,7 +199,7 @@ var/lamp_shade = 1 /obj/machinery/light/small/emergency - brightness_range = 6 + brightness_range = 4 brightness_power = 2 brightness_color = "#da0205" From ade7f6c1cd04c3deda193df8b86d5f974d3fc8ed Mon Sep 17 00:00:00 2001 From: Arokha Sieyes Date: Sun, 4 Jun 2017 18:00:16 -0400 Subject: [PATCH 18/22] Communicator QOL Tweak Call/Msg buttons on contacts list --- code/game/objects/items/devices/communicator/communicator.dm | 4 ++-- nano/templates/communicator.tmpl | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/code/game/objects/items/devices/communicator/communicator.dm b/code/game/objects/items/devices/communicator/communicator.dm index 018be58705..5df911cba8 100644 --- a/code/game/objects/items/devices/communicator/communicator.dm +++ b/code/game/objects/items/devices/communicator/communicator.dm @@ -261,7 +261,7 @@ var/global/list/obj/item/device/communicator/all_communicators = list() // Proc: ui_interact() // Parameters: 4 (standard NanoUI arguments) // Description: Uses a bunch of for loops to turn lists into lists of lists, so they can be displayed in nanoUI, then displays various buttons to the user. -/obj/item/device/communicator/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) +/obj/item/device/communicator/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/key_state = null) // this is the data which will be sent to the ui var/data[0] //General nanoUI information var/communicators[0] //List of communicators @@ -356,7 +356,7 @@ var/global/list/obj/item/device/communicator/all_communicators = list() if(!ui) // the ui does not exist, so we'll create a new() one // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm - ui = new(user, src, ui_key, "communicator.tmpl", "Communicator", 475, 700) + ui = new(user, src, ui_key, "communicator.tmpl", "Communicator", 475, 700, state = key_state) // when the ui is first opened this is the data it will use ui.set_initial_data(data) // open the new ui window diff --git a/nano/templates/communicator.tmpl b/nano/templates/communicator.tmpl index 58b86a30d7..6157f45d61 100644 --- a/nano/templates/communicator.tmpl +++ b/nano/templates/communicator.tmpl @@ -152,7 +152,10 @@ Used In File(s): code\game\objects\items\devices\communicator\communicator.dm {{:value.name}}
-
{{:value.address}}
{{:helper.link('Copy', 'pencil', {'copy' : value.address, 'switch_tab' : 2})}} +
{{:value.address}}
+ {{:helper.link('Copy', 'pencil', {'copy' : value.address, 'switch_tab' : 2})}} + {{:helper.link('Call', 'phone', {'dial' : value.address, 'copy' : value.address, 'switch_tab' : 2})}} + {{:helper.link('Msg', 'mail-closed', {'copy' : value.address, 'copy_name' : value.name, 'switch_tab' : 40})}}
{{/for}} From c829a195e1c24b9e0d15c5d3d0c03ceb6667d4f2 Mon Sep 17 00:00:00 2001 From: Arokha Sieyes Date: Sat, 3 Jun 2017 00:36:03 -0400 Subject: [PATCH 19/22] Polaris-able surgery fix --- code/modules/surgery/implant.dm | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/code/modules/surgery/implant.dm b/code/modules/surgery/implant.dm index 3e49482c75..48aca377ee 100644 --- a/code/modules/surgery/implant.dm +++ b/code/modules/surgery/implant.dm @@ -13,22 +13,22 @@ return affected && affected.open == (affected.encased ? 3 : 2) && !(affected.status & ORGAN_BLEEDING) proc/get_max_wclass(var/obj/item/organ/external/affected) - switch (affected.name) - if ("head") - return 1 - if ("upper body") - return 3 - if ("lower body") - return 2 + switch (affected.organ_tag) + if (BP_HEAD) + return ITEMSIZE_TINY + if (BP_TORSO) + return ITEMSIZE_NORMAL + if (BP_GROIN) + return ITEMSIZE_SMALL return 0 proc/get_cavity(var/obj/item/organ/external/affected) - switch (affected.name) - if ("head") + switch (affected.organ_tag) + if (BP_HEAD) return "cranial" - if ("upper body") + if (BP_TORSO) return "thoracic" - if ("lower body") + if (BP_GROIN) return "abdominal" return "" From 96a9527d0bcf6aa1980e2bd0ebf75423a98ec662 Mon Sep 17 00:00:00 2001 From: Leshana Date: Thu, 8 Jun 2017 17:13:54 -0400 Subject: [PATCH 20/22] Adds macros to assist with lazily instantiating lists. * For various reasons its best to not instantiate lists until they are actually going to be used, especially if there is a good chance that a given list variable might *never* be used during the lifetime of an object. * These macros make it simple and concise to add remove and access entries in lazily created lists. --- code/_macros.dm | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/code/_macros.dm b/code/_macros.dm index befbc99a54..a3e4065544 100644 --- a/code/_macros.dm +++ b/code/_macros.dm @@ -62,3 +62,25 @@ #define qdel_null(x) if(x) { qdel(x) ; x = null } #define ARGS_DEBUG log_debug("[__FILE__] - [__LINE__]") ; for(var/arg in args) { log_debug("\t[log_info_line(arg)]") } + +// Helper macros to aid in optimizing lazy instantiation of lists. +// All of these are null-safe, you can use them without knowing if the list var is initialized yet + +//Picks from the list, with some safeties, and returns the "default" arg if it fails +#define DEFAULTPICK(L, default) ((istype(L, /list) && L:len) ? pick(L) : default) +// Ensures L is initailized after this point +#define LAZYINITLIST(L) if (!L) L = list() +// Sets a L back to null iff it is empty +#define UNSETEMPTY(L) if (L && !L.len) L = null +// Removes I from list L, and sets I to null if it is now empty +#define LAZYREMOVE(L, I) if(L) { L -= I; if(!L.len) { L = null; } } +// Adds I to L, initalizing I if necessary +#define LAZYADD(L, I) if(!L) { L = list(); } L += I; +// Reads I from L safely - Works with both associative and traditional lists. +#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= L.len ? L[I] : null) : L[I]) : null) +// Reads the length of L, returning 0 if null +#define LAZYLEN(L) length(L) +// Null-safe L.Cut() +#define LAZYCLEARLIST(L) if(L) L.Cut() +// Reads L or an empty list if L is not a list. Note: Does NOT assign, L may be an expression. +#define SANITIZE_LIST(L) ( islist(L) ? L : list() ) From 3e84cbbbfd6bc27bb8ac37fa3ff9fcecc0f90295 Mon Sep 17 00:00:00 2001 From: Leshana Date: Thu, 8 Jun 2017 16:54:42 -0400 Subject: [PATCH 21/22] Converts area.all_doors list to be lazily instantiated as a simple example of using the lazy list macros. --- code/game/area/Space Station 13 areas.dm | 2 +- code/game/area/areas.dm | 4 ++++ code/game/machinery/doors/firedoor.dm | 6 +++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm index b98b2370ce..610e5577f6 100755 --- a/code/game/area/Space Station 13 areas.dm +++ b/code/game/area/Space Station 13 areas.dm @@ -48,7 +48,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station var/obj/machinery/power/apc/apc = null var/no_air = null // var/list/lights // list of all lights on this area - var/list/all_doors = list() //Added by Strumpetplaya - Alarm Change - Contains a list of doors adjacent to this area + var/list/all_doors = null //Added by Strumpetplaya - Alarm Change - Contains a list of doors adjacent to this area var/firedoors_closed = 0 var/list/ambience = list('sound/ambience/ambigen1.ogg','sound/ambience/ambigen3.ogg','sound/ambience/ambigen4.ogg','sound/ambience/ambigen5.ogg','sound/ambience/ambigen6.ogg','sound/ambience/ambigen7.ogg','sound/ambience/ambigen8.ogg','sound/ambience/ambigen9.ogg','sound/ambience/ambigen10.ogg','sound/ambience/ambigen11.ogg','sound/ambience/ambigen12.ogg','sound/ambience/ambigen14.ogg') var/list/forced_ambience = null diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index 4a77ec2c84..6ce2de18d3 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -75,6 +75,8 @@ /area/proc/firedoors_close() if(!firedoors_closed) firedoors_closed = TRUE + if(!all_doors) + return for(var/obj/machinery/door/firedoor/E in all_doors) if(!E.blocked) if(E.operating) @@ -87,6 +89,8 @@ /area/proc/firedoors_open() if(firedoors_closed) firedoors_closed = FALSE + if(!all_doors) + return for(var/obj/machinery/door/firedoor/E in all_doors) if(!E.blocked) if(E.operating) diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm index 1162264dd7..6ccbb46ac1 100644 --- a/code/game/machinery/doors/firedoor.dm +++ b/code/game/machinery/doors/firedoor.dm @@ -60,18 +60,18 @@ var/area/A = get_area(src) ASSERT(istype(A)) - A.all_doors.Add(src) + LAZYADD(A.all_doors, src) areas_added = list(A) for(var/direction in cardinal) A = get_area(get_step(src,direction)) if(istype(A) && !(A in areas_added)) - A.all_doors.Add(src) + LAZYADD(A.all_doors, src) areas_added += A /obj/machinery/door/firedoor/Destroy() for(var/area/A in areas_added) - A.all_doors.Remove(src) + LAZYREMOVE(A.all_doors, src) . = ..() /obj/machinery/door/firedoor/get_material() From 6d58df9f605911efd41304bd625dd4e4ba613dd8 Mon Sep 17 00:00:00 2001 From: Arokha Sieyes Date: Fri, 9 Jun 2017 02:10:09 -0400 Subject: [PATCH 22/22] Surgery and syringe fixes Fixes a line printed about staying close to your patient printed in error, and makes syringes work on laying patients again. The do_surgery overrides are from a bygone era. Tested == yes --- code/modules/mob/living/silicon/robot/analyzer.dm | 8 -------- code/modules/reagents/reagent_containers/hypospray.dm | 6 ------ code/modules/reagents/reagent_containers/pill.dm | 7 ------- code/modules/reagents/reagent_containers/syringes.dm | 7 ------- code/modules/surgery/surgery.dm | 1 - 5 files changed, 29 deletions(-) diff --git a/code/modules/mob/living/silicon/robot/analyzer.dm b/code/modules/mob/living/silicon/robot/analyzer.dm index 671f61e29c..3e1564b0cf 100644 --- a/code/modules/mob/living/silicon/robot/analyzer.dm +++ b/code/modules/mob/living/silicon/robot/analyzer.dm @@ -16,14 +16,6 @@ matter = list(DEFAULT_WALL_MATERIAL = 500, "glass" = 200) var/mode = 1; - -/obj/item/device/robotanalyzer/do_surgery(mob/living/M, mob/living/user) - if(user.a_intent != I_HELP) //in case it is ever used as a surgery tool - return ..() - do_scan(M, user) //default surgery behaviour is just to scan as usual - return 1 - - /obj/item/device/robotanalyzer/attack(mob/living/M as mob, mob/living/user as mob) do_scan(M, user) diff --git a/code/modules/reagents/reagent_containers/hypospray.dm b/code/modules/reagents/reagent_containers/hypospray.dm index 427d08d141..1c3151fee9 100644 --- a/code/modules/reagents/reagent_containers/hypospray.dm +++ b/code/modules/reagents/reagent_containers/hypospray.dm @@ -28,12 +28,6 @@ update_icon() return -/obj/item/weapon/reagent_containers/hypospray/do_surgery(mob/living/carbon/M, mob/living/user) - if(user.a_intent != I_HELP) //in case it is ever used as a surgery tool - return ..() - attack(M, user) - return 1 - /obj/item/weapon/reagent_containers/hypospray/attack(mob/living/M as mob, mob/user as mob) if(!reagents.total_volume) user << "[src] is empty." diff --git a/code/modules/reagents/reagent_containers/pill.dm b/code/modules/reagents/reagent_containers/pill.dm index 464e066264..e21baa48e2 100644 --- a/code/modules/reagents/reagent_containers/pill.dm +++ b/code/modules/reagents/reagent_containers/pill.dm @@ -19,13 +19,6 @@ if(!icon_state) icon_state = "pill[rand(1, 20)]" - -/obj/item/weapon/reagent_containers/pill/do_surgery(mob/M, mob/user) - if(user.a_intent != I_HELP) //in case it is ever used as a surgery tool - return ..() - attack(M, user) //default surgery behaviour is just to scan as usual - return 1 - /obj/item/weapon/reagent_containers/pill/attack(mob/M as mob, mob/user as mob) if(M == user) if(istype(M, /mob/living/carbon/human)) diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm index 058e09c6f7..dfe5dd0349 100644 --- a/code/modules/reagents/reagent_containers/syringes.dm +++ b/code/modules/reagents/reagent_containers/syringes.dm @@ -53,13 +53,6 @@ /obj/item/weapon/reagent_containers/syringe/attackby(obj/item/I as obj, mob/user as mob) return -/obj/item/weapon/reagent_containers/syringe/do_surgery(mob/living/carbon/M, mob/living/user) - if(user.a_intent == I_HURT) - return 0 - if(user.a_intent != I_HELP) //in case it is ever used as a surgery tool - return ..() - return 1 - /obj/item/weapon/reagent_containers/syringe/afterattack(obj/target, mob/user, proximity) if(!proximity || !target.reagents) return diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm index b9fe362031..19007addae 100644 --- a/code/modules/surgery/surgery.dm +++ b/code/modules/surgery/surgery.dm @@ -129,7 +129,6 @@ if(success) if(!do_mob(user, M, rand(S.min_duration, S.max_duration))) success = FALSE - else to_chat(user, "You must remain close to your patient to conduct surgery.") if(success)