Fixes#56292
Why It's Good For The Game
Increases chances of smooth experience
Changelog
cl Semoro
fix: timers not removing from second queue on init
/cl
Previously it was possible for events to enter the short queue when the timer is offset by more than BUCKET_LEN
Now it is forced to schedule events into the second queue if the timer is processing slower then world time goes allowing the timer to keep up
This PR provides a better definition of TIMER_MAX to avoid scheduling timed events that are more than one window of buckets away in terms of timeToRun into buckets queue and properly passing them into the second queue.
Ports ss220-space/Paradise#578
Should be merged with/after #64138
Detailed explanation
The timer subsystem mainly uses two concepts, buckets, and second queue
Buckets is a fixed-length list of linked lists, where each "bucket" contains timers scheduled to run on the same tick
The second queue is a simple list containing sorted timers that scheduled too far in future
To process buckets, the timer uses two variables named head_offset and practical_offset
head_offset determines the offset of the first bucket in time
while practical_offset determines offset from bucket list beginning
There are two equations responsible for determining where timed event would end up scheduled
TIMER_MAX and BUCKET_POS
TIMER_MAX determines the maximum value of timeToRun for timed event to schedule into buckets and not the second queue
While BUCKET_POS determines where to put timed event relative to current head_offset
Let's look at BUCKET_POS first
BUCKET_POS(timer) = (((round((timer.timeToRun - SStimer.head_offset) / world.tick_lag)+1) % BUCKET_LEN)||BUCKET_LEN)
Let's imagine we have our tick_lag set to 0.5, due to that we will have BUCKET_LEN = (10 / 0.5) * 60 = 1200
And head_offset of 100, that would make any timed event with timeToRun = 100 + 600N to get bucket_pos of 1
Now let's look at the current implementation of TIMER_MAX
TIMER_MAX = (world.time + TICKS2DS(min(BUCKET_LEN-(SStimer.practical_offset-DS2TICKS(world.time - SStimer.head_offset))-1, BUCKET_LEN-1)))
Let's say our world.time = 100 and practical_offset = 1 for now
So TIMER_MAX = 100 + min(1200 - (1 - (100 - 100)/0.5) - 1, 1200 - 1) * 0.5 = 100 + 1198 * 0.5 = 699
As you might see, in that example we're fine and no events can be scheduled in buckets past boundary
But let's now imagine a situation: some high priority subsystem lagged and caused the timer not to fire for a bit
Now our world.time = 200 and practical_offset = 1 still
So now our TIMER_MAX would be calculated as follow
TIMER_MAX = 200 + min(Q, 1199) * 0.5
Where Q = 1200 - 1 - (1 - (200 - 100) / 0.5) = 1200 - 1 - 1 + (200 - 100) / 0.5 = 1398
Which is bigger then 1199, so we will choose 1199 instead
TIMER_MAX = 200 + 599.5 = 799.5
Let's now schedule repetitive timed event with timeToRun = world.time + 500
It will be scheduled into buckets since, 700 < TIMER_MAX
BUCKET_POS will be ((700 - 100) / 0.5 + 1) % 1200 = 1
Let's run the timer subsystem
During the execution of that timer, we will try to reschedule it for the next fire at timeToRun = world.time + 500
Which would end up adding it in the same bucket we are currently processing, locking subsystem in a loop till suspending
On next tick we will try to continue and will reschedule at timeToRun = world.time + 0.5 + 500
Which would end up in bucket 2, constantly blocking the timer from processing normally
Why It's Good For The Game
Increases chances of smooth experience
Changelog
cl Semoro
fix: Avoid timer scheduling too far events into short queue
/cl
Moves hot/cold checks to the fire locks
Makes detection due to above only close the affected Fire Lock and its neighbors (via merge code stolen from tanks)
Fire Alarms still can lock an area down if clicked by a user, right clicking is now the reset function. Resetting also resets all Fire Locks closed by their own detection.
Gives Fire Locks an overlay of lights that display the reason it closed (Orange for Hot, Cyan for Cold, White for unknown/Fire Alarm Pulled)
Emagging a Fire Alarm now emags all Fire Locks in the area, which accomplishes the same effect as before (disables atmos monitoring). Pulling a Fire Alarm will make emagged Fire Locks in the area display a corrupted warning light pattern, making finding and replacing emagged Fire Locks somewhat easier.
Silicons can disable atmos sensors in an area by CTRL-Clicking a Fire Alarm, much like before. but now the Fire Alarms in the area will change their sprite to hint at this (the normally-green light will turn off).
Makes a breach or fire not cause Fire Lock Hell anymore. Only the Fire Locks facing an issue will tend to close, leaving the rest of the area mostly unaffected.
This needs some work done to make it detect based on edges, but it's a decent enough start to live with if we're all killed by meteors (Lemon)
Standardizes the two functions of hacking the communications console between ninjas and traitors. The code was copy pasted for some reason.
Adds in 3 new options for hack results, resulting in 4 total:
If there are >4 ghosts and the map is in space, it can summon Pirates
If there are >6 ghosts and the map is in space, it can summon Fugitives
If less than 20% of the players are ghosts, it can inject 1-3 Syndicate Sleeper Agents into the crew
Lastly, it can flat inject 15 threat into the round. (the "default" option)
Apparently, the current sound we know as goon/sounds/effects/spring.ogg is actually this sound, sourced from freesound.org. If you take a look at the licensure screen, you'll see that it's public domain. Creative Commons 0. Wow!
When I made my move loop changes (815bb8a) 62567, I converted a few walk() procs to
the new system
What I didn't know when I did that conversion is that walk() operates on ticks, when move loops operate on
deciseconds
So when I converted say, mob movement over, I accidentially halved the attack movespeed of all of our mobs
This resolves that, alongside a few other misteps
Of note: There are old comments implying that walk()'s delay is not actually linear, or simply as the reference says "in ticks"
I don't have a good idea of how fast things actually should be though, which makes this tricky
In light of this, I've decreased the move speed of legion slightly, in hopes that it will feel more "normal"
I've also fixed a bug with move_to and move_away, they were treating their distance parameters as move to this and one more, rather then move to this. This lead to mobs attempting to overlap with your sprite. s cringe, and also fixed
This had some weird list manipulation, and generally just a weird
structure. I changed it to hold every law it's announcing to be
contained within a list, rather than just have a list of Yeses and Nos.
The old functionality also increased the length of this list each time
laws were checked, meaning theoretically this could cause high memory
usage issues. Anyways... fixed all that.
remove_ratio() returns null if the parameter is 0 for some reason (in this case, transfer_rate being 0), which will shortly cause a runtime since something called removed.total_moles() when removed is null. This changes remove_ratio() to return a new gasmixture instead of null when parameter is below or equal to 0.
The current thought is it is reasonable to expect callers of remove_ratio to check for the amount removed if that's something they care about. I assume if you're reading this that turned out to be false.
-Lemon
Tritium fire will need a certain amount of energy released from the reaction in order to release radiation. The larger the volume of the gasmix, the more energy (so more tritium needed) needs to get released to pass the threshold. It's not a very high threshold, but it should stop the small amounts of tritium burning from plasmafires in waste from randomly releasing radiation around the station. The chance of irradiation depends on how much energy gets released, receiving about a 50% chance to irradiate you for every 1.68GJ (about 60 moles of tritium burned in high energy reaction, which needs 600 moles of tritium) released. The range of the radiation pulse is increased by 6 tiles, but will need more energy released to increase it, which is intended for the incinerator.
Plasmafires can produce tritium if there's a lot more oxygen than plasma, and that tritium usually gets burned. This is a pretty common reaction that can occur in waste, especially in DeltaStation where engine waste goes to waste by default. This can cause random items and even people to get randomly irradiated if they stand too close to waste, and they won't even know why, which sucks. This PR aims to reduce that significantly by adding higher requirements in order to irradiate, especially for large pipenets, and reduces the chance of low amounts of tritium irradiating significantly.
Borgs have a lightbulb overlay. The plane of the overlay depends on the status of the lights, so as to glow when on. The plane being used for disabled lights no longer shows above mobs for reasons I do not know. But this plane (-2) works.
Fixes misc moveloop runtimes souced by mid move deletes and disposal loop traps, this was caused by a misunderstood bit of logic. current_pipe needs to be set to the actual return value of transfer() rather then our current location
When I added a drop animation, I forgot that things other then item animations can modify transform
In the spirit of this, I've ensured that other animations maintain the proper scale
if you couldnt equip a device, it would drop it on the ground and fuck up being a nodrop item, fixes that
fixes the modsuit outfits not having internal slots set
adds feedback to not being able to turn on a module due to suit not being active
Fully refactors boomerang behavior to work as a component, so that a thrown boomerang will return back to it's thrower if within range. More than anything this modularizes the behavior for the backend,
Space drifting "listened" for moves outside of its expected range, and if it saw them it would self delete
The problem is it registered for this behavior in drifting_start(), which was intended to be called by the MOVELOOP_START signal
But because that signal was fired as a consequence of move() being called, we never registered the signal
So if you took an item out of your pack, when it hit the ground it would get the drifting component
Next tick it would be drift moved to its intended location, the move would "fail", and then it would stop
This lead to items being visually in your inventory, but not functionally
Which leads to a lot of really weird behavior
Oh and I added a var to moveloops that's just "are we running" to make solving this class of issue easier
It can sometimes be hard to tell which area a fire alarm is for. This makes it clear which fire alarms you need to return to a normal temperature to raise the firelocks in a given area.
* Adds logging to the tippable component
Headmin wanted it. Logs the tippable component if the tippable mob has
an active client (we don't want to log medbot flipping).
* better logging
Syndicate, ninja, fugitive hunter masks and some costume masks (cyborg visor, plague doctor, carp, owl, monkey) no longer impair vision with field of vision cones.
This PR fixes the problem with missing texture when you put headset, spray can, card, crayon, seclite in suit storage.
I also fixes the cigpack suit storage east direction sprite because whoever ported it from belt forgot that sprite has different direction. (me)
My PR to fix pixel aiming broke aiming when you were aiming at obscured turfs.
The click catcher was properly modifying the click target to be the turf under the click location,
but it was passing the `ICON_X` and `ICON_Y` variables on unmodified.
This means that instead of being measured from the bottom left corner of the turf you clicked on they were being measured from
roughly the bottom left corner of your screen.
This makes the click relevant click parsing proc also update the ICON_X and ICON_Y values of the click modifiers.
## About The Pull Request
Moves the modmap root object's load_map() call to New(). This changes something about where it falls in init order that stops it from causing a flood of lighting runtimes. Don't ask my why, it just does.
## Why It's Good For The Game
Fixes#64192
fixes#64029 (Springlock MOD module kills you even when you have Memento Mori necklace)
fixes#64136 (modsuits cant run out of charge)
fixes#64158 (trying to install a battery into a modsuit with free storage space places the battery into storage)
fixes#64186 (ModSuits : taking out cell doesn't give it back)
fixes#64161 (Modsuit cores disappear in construction)
makes mod jetpacks show particles indoors
gives the prototype suit back their upgraded cell (they literally lose 1% of power per 3 seconds with their current one)
Fully reworks the kinesis module to not be TK but lamer, it is now a gravitational anomaly locked module.
Unique stuff that can be used in fun scenarios
Increases drone health from 30 to 45, making it harder to instantly die from some things
Lowers the range that drones can't do interactions from 4 tiles to 3 tiles
Lowers stun time from 10 seconds to 7 seconds when EMP'd
Wait time for when a mob has recently interacted with something has been decreased from 5 minutes to 1 minute
Drones can now build near dead mobs, but not interact when right next to them (to prevent pulling)
Also adds a new argument for the shy component that allows interacting near dead bodies, but not right on top of them
Why It's Good For The Game
This is mainly for the QOL of Drones. I know "maint" is implied in the name of drones, but it sucks that you have to hide in maint for half the shift while all the airlocks are being opened by everyone, being unable to fix the station from explosions. Also even though the range was 4 tiles for interacting next to living mobs, it seemed a bit excessive when you tried to do stuff.
Changelog
cl
balance: Maintenance Drone health changed from 30 to 45
balance: Maintenance Drone stun times from getting EMP'd reduced from 10 seconds to 7 seconds
qol: Maintenance Drones can now interact near dead bodies
qol: Maintenance Drones don't have to wait 5 minutes to open an airlock when someone recently did it. Now they have to wait 1 minute.
qol: Changed distance Maintenance Drones aren't allowed to interact with anything from a nearby mob from 4 tiles to 3 tiles
/cl
Ever pump hot nob into a tank of oxygen, see the pressure rise to 8000 kpa for a few seconds and have that deafening explosion sound play right after? This attempts to eliminate those by making the pressure transfer proc thingy in gas_mixture account for resulting temperature. The old one is preserved if the temperature difference isn't too big because it's most definitely more performant. The minimum temperature difference of 5 is completely arbitrary and I won't mind changing if asked. Math is a bit messy by necessity but I hope the comment is adequate.
While I'm at it i also changed the return value of those procs to return a removed gasmix instead of directly merging. I sort of needed this for a clean implementation of assume_air in #62284. Will need more testing though. Found a better solution.
Writes a small unit test to check for this too.
Cuts down on one unintended cause of explosions.