refactor some game interface plugin approach
This commit is contained in:
parent
acf66da4ca
commit
9f4d06c265
@ -276,6 +276,8 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
{
|
{
|
||||||
_logger.LogDebug("OnLoad executing for {Name}", Name);
|
_logger.LogDebug("OnLoad executing for {Name}", Name);
|
||||||
_scriptEngine.SetValue("_manager", manager);
|
_scriptEngine.SetValue("_manager", manager);
|
||||||
|
_scriptEngine.SetValue("getDvar", GetDvarAsync);
|
||||||
|
_scriptEngine.SetValue("setDvar", SetDvarAsync);
|
||||||
_scriptEngine.Evaluate("plugin.onLoadAsync(_manager)");
|
_scriptEngine.Evaluate("plugin.onLoadAsync(_manager)");
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@ -451,6 +453,85 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
return commandList;
|
return commandList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void GetDvarAsync(Server server, string dvarName, Delegate onCompleted)
|
||||||
|
{
|
||||||
|
Task.Run<Task>(async () =>
|
||||||
|
{
|
||||||
|
var tokenSource = new CancellationTokenSource();
|
||||||
|
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
||||||
|
string result = null;
|
||||||
|
var success = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = (await server.GetDvarAsync<string>(dvarName, token: tokenSource.Token)).Value;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _onProcessing.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
onCompleted.DynamicInvoke(JsValue.Undefined,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
JsValue.FromObject(_scriptEngine, server),
|
||||||
|
JsValue.FromObject(_scriptEngine, dvarName),
|
||||||
|
JsValue.FromObject(_scriptEngine, result),
|
||||||
|
JsValue.FromObject(_scriptEngine, success),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_onProcessing.CurrentCount == 0)
|
||||||
|
{
|
||||||
|
_onProcessing.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private void SetDvarAsync(Server server, string dvarName, string dvarValue, Delegate onCompleted)
|
||||||
|
{
|
||||||
|
Task.Run<Task>(async () =>
|
||||||
|
{
|
||||||
|
var tokenSource = new CancellationTokenSource();
|
||||||
|
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
||||||
|
var success = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await server.SetDvarAsync(dvarName, dvarValue, tokenSource.Token);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _onProcessing.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
onCompleted.DynamicInvoke(JsValue.Undefined,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
JsValue.FromObject(_scriptEngine, server),
|
||||||
|
JsValue.FromObject(_scriptEngine, dvarName),
|
||||||
|
JsValue.FromObject(_scriptEngine, dvarValue),
|
||||||
|
JsValue.FromObject(_scriptEngine, success)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_onProcessing.CurrentCount == 0)
|
||||||
|
{
|
||||||
|
_onProcessing.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PermissionLevelToStringConverter : IObjectConverter
|
public class PermissionLevelToStringConverter : IObjectConverter
|
||||||
|
@ -603,6 +603,7 @@ HideImpl()
|
|||||||
if ( !IsDefined( self.savedHealth ) || self.health < 1000 )
|
if ( !IsDefined( self.savedHealth ) || self.health < 1000 )
|
||||||
{
|
{
|
||||||
self.savedHealth = self.health;
|
self.savedHealth = self.health;
|
||||||
|
self.savedMaxHealth = self.maxhealth;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.maxhealth = 99999;
|
self.maxhealth = 99999;
|
||||||
@ -622,11 +623,18 @@ UnhideImpl()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( IsDefined( self.isHidden ) && !self.isHidden )
|
||||||
|
{
|
||||||
|
self IPrintLnBold( "You are not hidden" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self SetClientDvar( "sv_cheats", 1 );
|
self SetClientDvar( "sv_cheats", 1 );
|
||||||
self SetClientDvar( "cg_thirdperson", 0 );
|
self SetClientDvar( "cg_thirdperson", 0 );
|
||||||
self SetClientDvar( "sv_cheats", 0 );
|
self SetClientDvar( "sv_cheats", 0 );
|
||||||
|
|
||||||
self.health = self.savedHealth;
|
self.health = self.savedHealth;
|
||||||
|
self.maxhealth = self.savedMaxHealth;
|
||||||
self.isHidden = false;
|
self.isHidden = false;
|
||||||
|
|
||||||
self Show();
|
self Show();
|
||||||
|
@ -1,19 +1,14 @@
|
|||||||
const eventTypes = {
|
const servers = {};
|
||||||
1: 'start', // a server started being monitored
|
|
||||||
6: 'disconnect', // a client detected a leaving the game
|
|
||||||
9: 'preconnect', // client detected as joining via log or status
|
|
||||||
101: 'warn' // client was warned
|
|
||||||
};
|
|
||||||
|
|
||||||
const servers = {};
|
|
||||||
const inDvar = 'sv_iw4madmin_in';
|
const inDvar = 'sv_iw4madmin_in';
|
||||||
const outDvar = 'sv_iw4madmin_out';
|
const outDvar = 'sv_iw4madmin_out';
|
||||||
const pollRate = 750;
|
const pollRate = 900;
|
||||||
|
const enableCheckTimeout = 10000;
|
||||||
let logger = {};
|
let logger = {};
|
||||||
|
const maxQueuedMessages = 25;
|
||||||
|
|
||||||
let plugin = {
|
let plugin = {
|
||||||
author: 'RaidMax',
|
author: 'RaidMax',
|
||||||
version: 1.0,
|
version: 1.1,
|
||||||
name: 'Game Interface',
|
name: 'Game Interface',
|
||||||
|
|
||||||
onEventAsync: (gameEvent, server) => {
|
onEventAsync: (gameEvent, server) => {
|
||||||
@ -21,7 +16,7 @@ let plugin = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventType = eventTypes[gameEvent.Type];
|
const eventType = String(gameEvent.TypeName).toLowerCase();
|
||||||
|
|
||||||
if (eventType === undefined) {
|
if (eventType === undefined) {
|
||||||
return;
|
return;
|
||||||
@ -301,67 +296,24 @@ const sendScriptCommand = (server, command, origin, target, data) => {
|
|||||||
|
|
||||||
const sendEvent = (server, responseExpected, event, subtype, origin, target, data) => {
|
const sendEvent = (server, responseExpected, event, subtype, origin, target, data) => {
|
||||||
const logger = _serviceResolver.ResolveService('ILogger');
|
const logger = _serviceResolver.ResolveService('ILogger');
|
||||||
|
const state = servers[server.EndPoint];
|
||||||
|
|
||||||
let pendingOut = true;
|
if (state.queuedMessages.length >= maxQueuedMessages) {
|
||||||
let pendingCheckCount = 0;
|
logger.WriteWarning('Too many queued messages so we are skipping');
|
||||||
const start = new Date();
|
|
||||||
|
|
||||||
while (pendingOut && pendingCheckCount <= 10) {
|
|
||||||
if (server.Throttled) {
|
|
||||||
logger.WriteWarning('Server is throttled, so we are not attempting to send data');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const out = server.GetServerDvar(outDvar);
|
|
||||||
pendingOut = !(out == null || out === '' || out === 'null');
|
|
||||||
} catch (error) {
|
|
||||||
logger.WriteError(`Could not check server output dvar for IO status ${error}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pendingOut) {
|
|
||||||
logger.WriteDebug('Waiting for event bus to be cleared');
|
|
||||||
System.Threading.Tasks.Task.Delay(1000).Wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingCheckCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pendingOut) {
|
|
||||||
logger.WriteWarning(`Reached maximum attempts waiting for output to be available for ${server.EndPoint}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
let targetClientNumber = -1;
|
let targetClientNumber = -1;
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
targetClientNumber = target.ClientNumber;
|
targetClientNumber = target.ClientNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
const output = `${responseExpected ? '1' : '0'};${event};${subtype};${origin.ClientNumber};${targetClientNumber};${buildDataString(data)}`;
|
const output = `${responseExpected ? '1' : '0'};${event};${subtype};${origin.ClientNumber};${targetClientNumber};${buildDataString(data)}`;
|
||||||
logger.WriteDebug(`Sending output to server ${output}`);
|
logger.WriteDebug(`Queuing output for server ${output}`);
|
||||||
|
|
||||||
try {
|
state.queuedMessages.push(output);
|
||||||
server.SetServerDvar(outDvar, output);
|
|
||||||
logger.WriteDebug(`SendEvent took ${(new Date() - start) / 1000}ms`);
|
|
||||||
} catch (error) {
|
|
||||||
logger.WriteError(`Could not set server output dvar ${error}`);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseEvent = (input) => {
|
|
||||||
if (input === undefined) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventInfo = input.split(';');
|
|
||||||
|
|
||||||
return {
|
|
||||||
eventType: eventInfo[1],
|
|
||||||
subType: eventInfo[2],
|
|
||||||
clientNumber: eventInfo[3],
|
|
||||||
data: eventInfo.length > 4 ? parseDataString(eventInfo[4]) : undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialize = (server) => {
|
const initialize = (server) => {
|
||||||
const logger = _serviceResolver.ResolveService('ILogger');
|
const logger = _serviceResolver.ResolveService('ILogger');
|
||||||
|
|
||||||
@ -371,7 +323,7 @@ const initialize = (server) => {
|
|||||||
|
|
||||||
let enabled = false;
|
let enabled = false;
|
||||||
try {
|
try {
|
||||||
enabled = server.GetServerDvar('sv_iw4madmin_integration_enabled') === '1';
|
enabled = server.GetServerDvar('sv_iw4madmin_integration_enabled', enableCheckTimeout) === '1';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.WriteError(`Could not get integration status of ${server.EndPoint} - ${error}`);
|
logger.WriteError(`Could not get integration status of ${server.EndPoint} - ${error}`);
|
||||||
}
|
}
|
||||||
@ -391,33 +343,36 @@ const initialize = (server) => {
|
|||||||
|
|
||||||
servers[server.EndPoint].timer = timer;
|
servers[server.EndPoint].timer = timer;
|
||||||
servers[server.EndPoint].enabled = true;
|
servers[server.EndPoint].enabled = true;
|
||||||
|
servers[server.EndPoint].waitingOnInput = false;
|
||||||
|
servers[server.EndPoint].waitingOnOutput = false;
|
||||||
|
servers[server.EndPoint].queuedMessages = [];
|
||||||
|
|
||||||
try {
|
setDvar(server, inDvar, '', onSetDvar);
|
||||||
server.SetServerDvar(inDvar, '');
|
setDvar(server, outDvar, '', onSetDvar);
|
||||||
server.SetServerDvar(outDvar, '');
|
|
||||||
} catch (error) {
|
|
||||||
logger.WriteError(`Could set default values bus dvars for ${server.EndPoint} - ${error}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
|
||||||
|
|
||||||
const pollForEvents = server => {
|
|
||||||
if (server.Throttled) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onReceivedDvar(server, dvarName, dvarValue, success) {
|
||||||
const logger = _serviceResolver.ResolveService('ILogger');
|
const logger = _serviceResolver.ResolveService('ILogger');
|
||||||
|
logger.WriteDebug(`Received ${dvarName}=${dvarValue} success=${success}`);
|
||||||
|
|
||||||
let input;
|
let input = dvarValue;
|
||||||
try {
|
const state = servers[server.EndPoint];
|
||||||
input = server.GetServerDvar(inDvar);
|
|
||||||
} catch (error) {
|
if (state.waitingOnOutput && dvarName === outDvar && isEmpty(dvarValue)) {
|
||||||
logger.WriteError(`Could not get input bus value for ${server.EndPoint} - ${error}`);
|
logger.WriteDebug('Setting out bus to read to send');
|
||||||
return;
|
// reset our flag letting use the out bus is open
|
||||||
|
state.waitingOnOutput = !success;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input === undefined || input === null || input === 'null') {
|
if (state.waitingOnInput && dvarName === inDvar) {
|
||||||
|
logger.WriteDebug('Setting in bus to ready to receive');
|
||||||
|
// we've received the data so now we can mark it as ready for more
|
||||||
|
state.waitingOnInput = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEmpty(input)) {
|
||||||
input = '';
|
input = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -489,16 +444,72 @@ const pollForEvents = server => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
setDvar(server, inDvar, '', onSetDvar);
|
||||||
server.SetServerDvar(inDvar, '');
|
|
||||||
} catch (error) {
|
|
||||||
logger.WriteError(`Could not reset in bus value for ${server.EndPoint} - ${error}`);
|
|
||||||
}
|
|
||||||
} else if (server.ClientNum === 0) {
|
} else if (server.ClientNum === 0) {
|
||||||
servers[server.EndPoint].timer.Stop();
|
servers[server.EndPoint].timer.Stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSetDvar(server, dvarName, dvarValue, success) {
|
||||||
|
const logger = _serviceResolver.ResolveService('ILogger');
|
||||||
|
logger.WriteDebug(`Completed set of dvar ${dvarName}=${dvarValue}, success=${success}`);
|
||||||
|
|
||||||
|
const state = servers[server.EndPoint];
|
||||||
|
|
||||||
|
if (dvarName === inDvar && success && isEmpty(dvarValue)) {
|
||||||
|
logger.WriteDebug('In bus is ready for new data');
|
||||||
|
// reset our flag letting use the in bus is ready for more data
|
||||||
|
state.waitingOnInput = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pollForEvents = server => {
|
||||||
|
const state = servers[server.EndPoint];
|
||||||
|
|
||||||
|
if (state === null || !state.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (server.Throttled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.waitingOnInput) {
|
||||||
|
state.waitingOnInput = true;
|
||||||
|
getDvar(server, inDvar, onReceivedDvar);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.waitingOnOutput) {
|
||||||
|
if (state.queuedMessages.length === 0) {
|
||||||
|
logger.WriteDebug('No messages in queue');
|
||||||
|
return;``
|
||||||
|
}
|
||||||
|
|
||||||
|
state.waitingOnOutput = true;
|
||||||
|
const nextMessage = state.queuedMessages.splice(0, 1);
|
||||||
|
setDvar(server, outDvar, nextMessage, onSetDvar);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.waitingOnOutput) {
|
||||||
|
getDvar(server, outDvar, onReceivedDvar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseEvent = (input) => {
|
||||||
|
if (input === undefined) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventInfo = input.split(';');
|
||||||
|
|
||||||
|
return {
|
||||||
|
eventType: eventInfo[1],
|
||||||
|
subType: eventInfo[2],
|
||||||
|
clientNumber: eventInfo[3],
|
||||||
|
data: eventInfo.length > 4 ? parseDataString(eventInfo[4]) : undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const buildDataString = data => {
|
const buildDataString = data => {
|
||||||
if (data === undefined) {
|
if (data === undefined) {
|
||||||
return '';
|
return '';
|
||||||
@ -534,7 +545,11 @@ const parseDataString = data => {
|
|||||||
const validateEnabled = (server, origin) => {
|
const validateEnabled = (server, origin) => {
|
||||||
const enabled = servers[server.EndPoint] != null && servers[server.EndPoint].enabled;
|
const enabled = servers[server.EndPoint] != null && servers[server.EndPoint].enabled;
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
origin.Tell("Game interface is not enabled on this server");
|
origin.Tell('Game interface is not enabled on this server');
|
||||||
}
|
}
|
||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isEmpty(value) {
|
||||||
|
return value == null || false || value === '' || value === 'null';
|
||||||
|
}
|
||||||
|
@ -257,6 +257,7 @@ namespace SharedLibraryCore
|
|||||||
public EFClient Target;
|
public EFClient Target;
|
||||||
|
|
||||||
public EventType Type;
|
public EventType Type;
|
||||||
|
public string TypeName => Type.ToString();
|
||||||
|
|
||||||
public GameEvent()
|
public GameEvent()
|
||||||
{
|
{
|
||||||
|
@ -388,10 +388,10 @@ namespace SharedLibraryCore
|
|||||||
|
|
||||||
public abstract Task<long> GetIdForServer(Server server = null);
|
public abstract Task<long> GetIdForServer(Server server = null);
|
||||||
|
|
||||||
public string[] ExecuteServerCommand(string command)
|
public string[] ExecuteServerCommand(string command, int timeoutMs = 1000)
|
||||||
{
|
{
|
||||||
var tokenSource = new CancellationTokenSource();
|
var tokenSource = new CancellationTokenSource();
|
||||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(0.5));
|
tokenSource.CancelAfter(TimeSpan.FromSeconds(timeoutMs));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -403,10 +403,10 @@ namespace SharedLibraryCore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetServerDvar(string dvarName)
|
public string GetServerDvar(string dvarName, int timeoutMs = 1000)
|
||||||
{
|
{
|
||||||
var tokenSource = new CancellationTokenSource();
|
var tokenSource = new CancellationTokenSource();
|
||||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(0.5));
|
tokenSource.CancelAfter(TimeSpan.FromSeconds(timeoutMs));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return this.GetDvarAsync<string>(dvarName, token: tokenSource.Token).GetAwaiter().GetResult().Value;
|
return this.GetDvarAsync<string>(dvarName, token: tokenSource.Token).GetAwaiter().GetResult().Value;
|
||||||
@ -417,10 +417,10 @@ namespace SharedLibraryCore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SetServerDvar(string dvarName, string dvarValue)
|
public bool SetServerDvar(string dvarName, string dvarValue, int timeoutMs = 1000)
|
||||||
{
|
{
|
||||||
var tokenSource = new CancellationTokenSource();
|
var tokenSource = new CancellationTokenSource();
|
||||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(0.5));
|
tokenSource.CancelAfter(TimeSpan.FromSeconds(timeoutMs));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.SetDvarAsync(dvarName, dvarValue, tokenSource.Token).GetAwaiter().GetResult();
|
this.SetDvarAsync(dvarName, dvarValue, tokenSource.Token).GetAwaiter().GetResult();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user