Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AFK players now show up as SSD on examine (+ soft ping port) #9705

Merged
merged 13 commits into from
Oct 19, 2023
Merged
1 change: 1 addition & 0 deletions beestation.dme
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@
#include "code\controllers\subsystem\parallax.dm"
#include "code\controllers\subsystem\pathfinder.dm"
#include "code\controllers\subsystem\persistence.dm"
#include "code\controllers\subsystem\ping.dm"
#include "code\controllers\subsystem\preferences.dm"
#include "code\controllers\subsystem\profiler.dm"
#include "code\controllers\subsystem\radiation.dm"
Expand Down
1 change: 1 addition & 0 deletions code/__DEFINES/subsystems.dm
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@
// Subsystem fire priority, from lowest to highest priority
// If the subsystem isn't listed here it's either DEFAULT or PROCESS (if it's a processing subsystem child)

#define FIRE_PRIORITY_PING 10
#define FIRE_PRIORITY_STAT 10
#define FIRE_PRIORITY_AMBIENCE 10
#define FIRE_PRIORITY_IDLE_NPC 10
Expand Down
38 changes: 38 additions & 0 deletions code/controllers/subsystem/ping.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*!
* Copyright (c) 2022 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/

SUBSYSTEM_DEF(ping)
name = "Ping"
priority = FIRE_PRIORITY_PING
wait = 4 SECONDS
flags = SS_NO_INIT
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
var/list/currentrun = list()

/datum/controller/subsystem/ping/stat_entry()
..("P:[length(GLOB.clients)]")
Absolucy marked this conversation as resolved.
Show resolved Hide resolved

/datum/controller/subsystem/ping/fire(resumed = FALSE)
// Prepare the new batch of clients
if (!resumed)
src.currentrun = GLOB.clients.Copy()

// De-reference the list for sanic speeds
var/list/currentrun = src.currentrun

while (currentrun.len)
var/client/client = currentrun[currentrun.len]
currentrun.len--

if (client?.tgui_panel?.is_ready())
// Send a soft ping
client.tgui_panel.window.send_message("ping/soft", list(
// Slightly less than the subsystem timer (somewhat arbitrary)
// to prevent incoming pings from resetting the afk state
"afk" = client.is_afk(3.5 SECONDS),
))

if (MC_TICK_CHECK)
return
4 changes: 2 additions & 2 deletions code/game/data_huds.dm
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,12 @@
if(tod)
var/tdelta = round(world.time - timeofdeath)
if(tdelta < (DEFIB_TIME_LIMIT * 10))
if(!client && key)
if((!client && key) || client?.is_afk())
holder.icon_state = "huddefib-ssd"
return
holder.icon_state = "huddefib"
return
if(!client && key)
if((!client && key) || client?.is_afk())
holder.icon_state = "huddead-ssd"
return
holder.icon_state = "huddead"
Expand Down
1 change: 1 addition & 0 deletions code/modules/client/client_procs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
GLOB.clients -= src
GLOB.mentors -= src
SSambience.remove_ambience_client(src)
SSping.currentrun -= src
Master.UpdateTickRate()
return ..()

Expand Down
2 changes: 1 addition & 1 deletion code/modules/mob/living/carbon/human/examine.dm
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@
msg += "<span class='deadsay'>[t_He] do[t_es]n't appear to be [t_him]self.</span>\n"
if(!key)
msg += "<span class='deadsay'>[t_He] [t_is] totally catatonic. The stresses of life in deep-space must have been too much for [t_him]. Any recovery is unlikely.</span>\n"
else if(!client)
else if(!client || client.is_afk())
msg += "[t_He] [t_has] a blank, absent-minded stare and appears completely unresponsive to anything. [t_He] may snap out of it soon.\n"

//handcuffed?
Expand Down
2 changes: 1 addition & 1 deletion code/modules/tgui/tgui.dm
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@
if(initialized)
send_full_update()
initialized = TRUE
if("pingReply")
if("ping/reply")
initialized = TRUE
if("suspend")
close(can_be_suspended = TRUE)
Expand Down
2 changes: 1 addition & 1 deletion code/modules/tgui/tgui_window.dm
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@
// If not locked, handle these message types
switch(type)
if("ping")
send_message("pingReply", payload)
send_message("ping/reply", payload)
if("suspend")
close(can_be_suspended = TRUE)
if("close")
Expand Down
2 changes: 1 addition & 1 deletion tgui/packages/tgui-panel/game/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
* @license MIT
*/

export const CONNECTION_LOST_AFTER = 15000;
export const CONNECTION_LOST_AFTER = 20000;
13 changes: 9 additions & 4 deletions tgui/packages/tgui-panel/game/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* @license MIT
*/

import { pingSuccess } from '../ping/actions';
import { pingSoft, pingSuccess } from '../ping/actions';
import { connectionLost, connectionRestored, roundRestarted } from './actions';
import { selectGame } from './selectors';
import { CONNECTION_LOST_AFTER } from './constants';
Expand All @@ -19,6 +19,7 @@ const withTimestamp = (action) => ({

export const gameMiddleware = (store) => {
let lastPingedAt;

setInterval(() => {
const state = store.getState();
if (!state) {
Expand All @@ -33,15 +34,19 @@ export const gameMiddleware = (store) => {
store.dispatch(withTimestamp(connectionRestored()));
}
}, 1000);

return (next) => (action) => {
const { type, payload, meta } = action;
if (type === pingSuccess.type) {
lastPingedAt = meta.now;
const { type } = action;

if (type === pingSuccess.type || type === pingSoft.type) {
lastPingedAt = Date.now();
return next(action);
}

if (type === roundRestarted.type) {
return next(withTimestamp(action));
}

return next(action);
};
};
27 changes: 15 additions & 12 deletions tgui/packages/tgui-panel/ping/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@

import { createAction } from 'common/redux';

export const pingSuccess = createAction('ping/success', (ping) => {
const now = Date.now();
const roundtrip = (now - ping.sentAt) * 0.5;
return {
payload: {
lastId: ping.id,
roundtrip,
},
meta: { now },
};
});
export const pingReply = createAction('ping/reply');

/**
* Soft ping from the server.
* It's intended to send periodic server-side metadata about the client,
* e.g. its AFK status.
*/
export const pingSoft = createAction('ping/soft');

export const pingSuccess = createAction('ping/success', (ping) => ({
payload: {
lastId: ping.id,
roundtrip: (Date.now() - ping.sentAt) * 0.5,
},
}));

export const pingFail = createAction('ping/fail');
export const pingReply = createAction('ping/reply');
1 change: 0 additions & 1 deletion tgui/packages/tgui-panel/ping/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
* @license MIT
*/

export const PING_INTERVAL = 2500;
export const PING_TIMEOUT = 2000;
export const PING_MAX_FAILS = 3;
export const PING_QUEUE_SIZE = 8;
Expand Down
22 changes: 18 additions & 4 deletions tgui/packages/tgui-panel/ping/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
* @license MIT
*/

import { pingFail, pingSuccess } from './actions';
import { PING_INTERVAL, PING_QUEUE_SIZE, PING_TIMEOUT } from './constants';
import { pingFail, pingReply, pingSoft, pingSuccess } from './actions';
import { PING_QUEUE_SIZE, PING_TIMEOUT } from './constants';

export const pingMiddleware = (store) => {
let initialized = false;
let index = 0;
const pings = [];

const sendPing = () => {
for (let i = 0; i < PING_QUEUE_SIZE; i++) {
const ping = pings[i];
Expand All @@ -24,14 +25,26 @@ export const pingMiddleware = (store) => {
Byond.sendMessage('ping', { index });
index = (index + 1) % PING_QUEUE_SIZE;
};

return (next) => (action) => {
const { type, payload } = action;

if (!initialized) {
initialized = true;
setInterval(sendPing, PING_INTERVAL);
sendPing();
}
if (type === 'pingReply') {

if (type === pingSoft.type) {
const { afk } = payload;
// On each soft ping where client is not flagged as afk,
// initiate a new ping.
if (!afk) {
sendPing();
}
return next(action);
}

if (type === pingReply.type) {
const { index } = payload;
const ping = pings[index];
// Received a timed out ping
Expand All @@ -41,6 +54,7 @@ export const pingMiddleware = (store) => {
pings[index] = null;
return next(pingSuccess(ping));
}

return next(action);
};
};
3 changes: 3 additions & 0 deletions tgui/packages/tgui-panel/ping/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { PING_MAX_FAILS, PING_ROUNDTRIP_BEST, PING_ROUNDTRIP_WORST } from './con

export const pingReducer = (state = {}, action) => {
const { type, payload } = action;

if (type === pingSuccess.type) {
const { roundtrip } = payload;
const prevRoundtrip = state.roundtripAvg || roundtrip;
Expand All @@ -22,6 +23,7 @@ export const pingReducer = (state = {}, action) => {
networkQuality,
};
}

if (type === pingFail.type) {
const { failCount = 0 } = state;
const networkQuality = clamp01(state.networkQuality - failCount / PING_MAX_FAILS);
Expand All @@ -36,5 +38,6 @@ export const pingReducer = (state = {}, action) => {
}
return nextState;
}

return state;
};
2 changes: 1 addition & 1 deletion tgui/packages/tgui/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export const backendMiddleware = (store) => {
}

if (type === 'ping') {
Byond.sendMessage('pingReply');
Byond.sendMessage('ping/reply');
return;
}

Expand Down