Merge branch 'develop' into release/pre
This commit is contained in:
commit
28fd712a63
@ -24,7 +24,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Jint" Version="3.0.0-beta-2047" />
|
||||
<PackageReference Include="Jint" Version="3.0.0-beta-2049" />
|
||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
@ -377,7 +377,6 @@ namespace IW4MAdmin
|
||||
if (E.Origin.State != ClientState.Connected)
|
||||
{
|
||||
E.Origin.State = ClientState.Connected;
|
||||
E.Origin.LastConnection = DateTime.UtcNow;
|
||||
E.Origin.Connections += 1;
|
||||
|
||||
ChatHistory.Add(new ChatInfo()
|
||||
|
@ -156,8 +156,8 @@ namespace IW4MAdmin.Application.Plugin
|
||||
}
|
||||
|
||||
_logger.LogDebug("Discovered {Count} plugin implementations", pluginTypes.Count);
|
||||
_logger.LogDebug("Discovered {Count} plugin commands", pluginTypes.Count);
|
||||
_logger.LogDebug("Discovered {Count} configuration implementations", pluginTypes.Count);
|
||||
_logger.LogDebug("Discovered {Count} plugin command implementations", commandTypes.Count);
|
||||
_logger.LogDebug("Discovered {Count} plugin configuration implementations", configurationTypes.Count);
|
||||
|
||||
return (pluginTypes, commandTypes, configurationTypes);
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ public class ScriptPluginHelper
|
||||
try
|
||||
{
|
||||
await Task.Delay(delayMs, _manager.CancellationToken);
|
||||
_scriptPlugin.ExecuteWithErrorHandling(_ => callback.DynamicInvoke(JsValue.Undefined));
|
||||
_scriptPlugin.ExecuteWithErrorHandling(_ => callback.DynamicInvoke(JsValue.Undefined, new[] { JsValue.Undefined }));
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -76,6 +76,11 @@ public class ScriptPluginHelper
|
||||
});
|
||||
}
|
||||
|
||||
public void RegisterDynamicCommand(JsValue command)
|
||||
{
|
||||
_scriptPlugin.RegisterDynamicCommand(command.ToObject());
|
||||
}
|
||||
|
||||
private object RequestInternal(ScriptPluginWebRequest request)
|
||||
{
|
||||
var entered = false;
|
||||
|
@ -47,6 +47,7 @@ public class ScriptPluginV2 : IPluginV2
|
||||
private readonly List<string> _registeredCommandNames = new();
|
||||
private readonly List<string> _registeredInteractions = new();
|
||||
private readonly Dictionary<MethodInfo, List<object>> _registeredEvents = new();
|
||||
private IManager _manager;
|
||||
private bool _firstInitialization = true;
|
||||
|
||||
private record ScriptPluginDetails(string Name, string Author, string Version,
|
||||
@ -112,8 +113,15 @@ public class ScriptPluginV2 : IPluginV2
|
||||
}, _logger, _fileName, _onProcessingScript);
|
||||
}
|
||||
|
||||
public void RegisterDynamicCommand(object command)
|
||||
{
|
||||
var parsedCommand = ParseScriptCommandDetails(command);
|
||||
RegisterCommand(_manager, parsedCommand.First());
|
||||
}
|
||||
|
||||
private async Task OnLoad(IManager manager, CancellationToken token)
|
||||
{
|
||||
_manager = manager;
|
||||
var entered = false;
|
||||
try
|
||||
{
|
||||
@ -253,8 +261,12 @@ public class ScriptPluginV2 : IPluginV2
|
||||
command.Permission, command.TargetRequired,
|
||||
command.Arguments, Execute, command.SupportedGames);
|
||||
|
||||
manager.RemoveCommandByName(scriptCommand.Name);
|
||||
manager.AddAdditionalCommand(scriptCommand);
|
||||
_registeredCommandNames.Add(scriptCommand.Name);
|
||||
if (!_registeredCommandNames.Contains(scriptCommand.Name))
|
||||
{
|
||||
_registeredCommandNames.Add(scriptCommand.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetEngineState()
|
||||
@ -480,6 +492,33 @@ public class ScriptPluginV2 : IPluginV2
|
||||
}
|
||||
|
||||
private static ScriptPluginDetails AsScriptPluginInstance(dynamic source)
|
||||
{
|
||||
var commandDetails = ParseScriptCommandDetails(source);
|
||||
|
||||
var interactionDetails = Array.Empty<ScriptPluginInteractionDetails>();
|
||||
if (HasProperty(source, "interactions") && source.interactions is dynamic[])
|
||||
{
|
||||
interactionDetails = ((dynamic[])source.interactions).Select(interaction =>
|
||||
{
|
||||
var name = HasProperty(interaction, "name") && interaction.name is string
|
||||
? (string)interaction.name
|
||||
: string.Empty;
|
||||
var action = HasProperty(interaction, "action") && interaction.action is Delegate
|
||||
? (Delegate)interaction.action
|
||||
: null;
|
||||
|
||||
return new ScriptPluginInteractionDetails(name, action);
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
var name = HasProperty(source, "name") && source.name is string ? (string)source.name : string.Empty;
|
||||
var author = HasProperty(source, "author") && source.author is string ? (string)source.author : string.Empty;
|
||||
var version = HasProperty(source, "version") && source.version is string ? (string)source.author : string.Empty;
|
||||
|
||||
return new ScriptPluginDetails(name, author, version, commandDetails, interactionDetails);
|
||||
}
|
||||
|
||||
private static ScriptPluginCommandDetails[] ParseScriptCommandDetails(dynamic source)
|
||||
{
|
||||
var commandDetails = Array.Empty<ScriptPluginCommandDetails>();
|
||||
if (HasProperty(source, "commands") && source.commands is dynamic[])
|
||||
@ -513,7 +552,7 @@ public class ScriptPluginV2 : IPluginV2
|
||||
(bool)command.targetRequired;
|
||||
var supportedGames =
|
||||
HasProperty(command, "supportedGames") && command.supportedGames is IEnumerable<object>
|
||||
? ((IEnumerable<object>)command.supportedGames).Where(game => game?.ToString() is not null)
|
||||
? ((IEnumerable<object>)command.supportedGames).Where(game => !string.IsNullOrEmpty(game?.ToString()))
|
||||
.Select(game =>
|
||||
Enum.Parse<Reference.Game>(game.ToString()!))
|
||||
: Array.Empty<Reference.Game>();
|
||||
@ -523,31 +562,10 @@ public class ScriptPluginV2 : IPluginV2
|
||||
|
||||
return new ScriptPluginCommandDetails(name, description, alias, permission, isTargetRequired,
|
||||
commandArgs, supportedGames, execute);
|
||||
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
var interactionDetails = Array.Empty<ScriptPluginInteractionDetails>();
|
||||
if (HasProperty(source, "interactions") && source.interactions is dynamic[])
|
||||
{
|
||||
interactionDetails = ((dynamic[])source.interactions).Select(interaction =>
|
||||
{
|
||||
var name = HasProperty(interaction, "name") && interaction.name is string
|
||||
? (string)interaction.name
|
||||
: string.Empty;
|
||||
var action = HasProperty(interaction, "action") && interaction.action is Delegate
|
||||
? (Delegate)interaction.action
|
||||
: null;
|
||||
|
||||
return new ScriptPluginInteractionDetails(name, action);
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
var name = HasProperty(source, "name") && source.name is string ? (string)source.name : string.Empty;
|
||||
var author = HasProperty(source, "author") && source.author is string ? (string)source.author : string.Empty;
|
||||
var version = HasProperty(source, "version") && source.version is string ? (string)source.author : string.Empty;
|
||||
|
||||
return new ScriptPluginDetails(name, author, version, commandDetails, interactionDetails);
|
||||
return commandDetails;
|
||||
}
|
||||
|
||||
private static bool HasProperty(dynamic source, string name)
|
||||
|
@ -6,6 +6,7 @@ trigger:
|
||||
include:
|
||||
- release/pre
|
||||
- master
|
||||
- develop
|
||||
|
||||
pr: none
|
||||
|
||||
@ -20,227 +21,233 @@ variables:
|
||||
buildConfiguration: Stable
|
||||
isPreRelease: false
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .NET Core 6 SDK'
|
||||
inputs:
|
||||
packageType: 'sdk'
|
||||
version: '6.0.x'
|
||||
includePreviewVersions: true
|
||||
|
||||
- task: NuGetToolInstaller@1
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Setup Pre-Release configuration'
|
||||
condition: eq(variables['Build.SourceBranch'], 'refs/heads/release/pre')
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
echo '##vso[task.setvariable variable=releaseType]prerelease'
|
||||
echo '##vso[task.setvariable variable=buildConfiguration]Prerelease'
|
||||
echo '##vso[task.setvariable variable=isPreRelease]true'
|
||||
failOnStderr: true
|
||||
|
||||
- task: NuGetCommand@2
|
||||
displayName: 'Restore nuget packages'
|
||||
inputs:
|
||||
restoreSolution: '$(solution)'
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Preload external resources'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
Write-Host 'Build Configuration is $(buildConfiguration), Release Type is $(releaseType)'
|
||||
md -Force lib\open-iconic\font\css
|
||||
wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o lib\open-iconic\font\css\open-iconic-bootstrap-override.scss
|
||||
cd lib\open-iconic\font\css
|
||||
(Get-Content open-iconic-bootstrap-override.scss).replace('../fonts/', '/font/') | Set-Content open-iconic-bootstrap-override.scss
|
||||
failOnStderr: true
|
||||
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore\wwwroot'
|
||||
|
||||
- task: VSBuild@1
|
||||
displayName: 'Build projects'
|
||||
inputs:
|
||||
solution: '$(solution)'
|
||||
msbuildArgs: '/p:DeployOnBuild=false /p:PackageAsSingleFile=false /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)" /p:Version=$(Build.BuildNumber)'
|
||||
platform: '$(buildPlatform)'
|
||||
configuration: '$(buildConfiguration)'
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Bundle JS Files'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
Write-Host 'Getting dotnet bundle'
|
||||
wget http://raidmax.org/IW4MAdmin/res/dotnet-bundle.zip -o $(Build.Repository.LocalPath)\dotnet-bundle.zip
|
||||
Write-Host 'Unzipping download'
|
||||
Expand-Archive -LiteralPath $(Build.Repository.LocalPath)\dotnet-bundle.zip -DestinationPath $(Build.Repository.LocalPath)
|
||||
Write-Host 'Executing dotnet-bundle'
|
||||
$(Build.Repository.LocalPath)\dotnet-bundle.exe clean $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json
|
||||
$(Build.Repository.LocalPath)\dotnet-bundle.exe $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json
|
||||
failOnStderr: true
|
||||
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore'
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Publish projects'
|
||||
inputs:
|
||||
command: 'publish'
|
||||
publishWebProjects: false
|
||||
projects: |
|
||||
**/WebfrontCore.csproj
|
||||
**/Application.csproj
|
||||
arguments: '-c $(buildConfiguration) -o $(outputFolder) /p:Version=$(Build.BuildNumber)'
|
||||
zipAfterPublish: false
|
||||
modifyOutputPath: false
|
||||
jobs:
|
||||
- job: Build_Deploy
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Install .NET Core 6 SDK'
|
||||
inputs:
|
||||
packageType: 'sdk'
|
||||
version: '6.0.x'
|
||||
includePreviewVersions: true
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Run publish script 1'
|
||||
inputs:
|
||||
filePath: 'DeploymentFiles/PostPublish.ps1'
|
||||
arguments: '$(outputFolder)'
|
||||
failOnStderr: true
|
||||
workingDirectory: '$(Build.Repository.LocalPath)'
|
||||
|
||||
- task: BatchScript@1
|
||||
displayName: 'Run publish script 2'
|
||||
inputs:
|
||||
filename: 'Application\BuildScripts\PostPublish.bat'
|
||||
workingFolder: '$(Build.Repository.LocalPath)'
|
||||
arguments: '$(outputFolder) $(Build.Repository.LocalPath)'
|
||||
failOnStandardError: true
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Download dos2unix for line endings'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: 'wget https://raidmax.org/downloads/dos2unix.exe'
|
||||
failOnStderr: true
|
||||
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: 'Convert Linux start script line endings'
|
||||
inputs:
|
||||
script: |
|
||||
echo changing to encoding for linux start script
|
||||
dos2unix $(outputFolder)\StartIW4MAdmin.sh
|
||||
dos2unix $(outputFolder)\UpdateIW4MAdmin.sh
|
||||
echo creating website version filename
|
||||
@echo IW4MAdmin-$(Build.BuildNumber) > $(Build.ArtifactStagingDirectory)\version_$(releaseType).txt
|
||||
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Move script plugins into publish directory'
|
||||
inputs:
|
||||
SourceFolder: '$(Build.Repository.LocalPath)\Plugins\ScriptPlugins'
|
||||
Contents: '*.js'
|
||||
TargetFolder: '$(outputFolder)\Plugins'
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Move binary plugins into publish directory'
|
||||
inputs:
|
||||
SourceFolder: '$(Build.Repository.LocalPath)\BUILD\Plugins\'
|
||||
Contents: '*.dll'
|
||||
TargetFolder: '$(outputFolder)\Plugins'
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: 'Move webfront resources into publish directory'
|
||||
inputs:
|
||||
script: 'xcopy /s /y /f wwwroot $(outputFolder)\wwwroot'
|
||||
workingDirectory: '$(Build.Repository.LocalPath)\BUILD\Plugins'
|
||||
failOnStderr: true
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: 'Move gamescript files into publish directory'
|
||||
inputs:
|
||||
script: 'echo d | xcopy /s /y /f GameFiles $(outputFolder)\GameFiles'
|
||||
workingDirectory: '$(Build.Repository.LocalPath)'
|
||||
failOnStderr: true
|
||||
|
||||
- task: ArchiveFiles@2
|
||||
displayName: 'Generate final zip file'
|
||||
inputs:
|
||||
rootFolderOrFile: '$(outputFolder)'
|
||||
includeRootFolder: false
|
||||
archiveType: 'zip'
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Build.BuildNumber).zip'
|
||||
replaceExistingArchive: true
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
inputs:
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Build.BuildNumber).zip'
|
||||
artifact: 'IW4MAdmin-$(Build.BuildNumber).zip'
|
||||
|
||||
- task: FtpUpload@2
|
||||
displayName: 'Upload zip file to website'
|
||||
inputs:
|
||||
credentialsOption: 'inputs'
|
||||
serverUrl: '$(FTPUrl)'
|
||||
username: '$(FTPUsername)'
|
||||
password: '$(FTPPassword)'
|
||||
rootDirectory: '$(Build.ArtifactStagingDirectory)'
|
||||
filePatterns: '*.zip'
|
||||
remoteDirectory: 'IW4MAdmin/Download'
|
||||
clean: false
|
||||
cleanContents: false
|
||||
preservePaths: false
|
||||
trustSSL: false
|
||||
|
||||
- task: FtpUpload@2
|
||||
displayName: 'Upload version info to website'
|
||||
inputs:
|
||||
credentialsOption: 'inputs'
|
||||
serverUrl: '$(FTPUrl)'
|
||||
username: '$(FTPUsername)'
|
||||
password: '$(FTPPassword)'
|
||||
rootDirectory: '$(Build.ArtifactStagingDirectory)'
|
||||
filePatterns: 'version_$(releaseType).txt'
|
||||
remoteDirectory: 'IW4MAdmin'
|
||||
clean: false
|
||||
cleanContents: false
|
||||
preservePaths: false
|
||||
trustSSL: false
|
||||
|
||||
- task: GitHubRelease@1
|
||||
displayName: 'Make GitHub release'
|
||||
inputs:
|
||||
gitHubConnection: 'github.com_RaidMax'
|
||||
repositoryName: 'RaidMax/IW4M-Admin'
|
||||
action: 'create'
|
||||
target: '$(Build.SourceVersion)'
|
||||
tagSource: 'userSpecifiedTag'
|
||||
tag: '$(Build.BuildNumber)-$(releaseType)'
|
||||
title: 'IW4MAdmin $(Build.BuildNumber) ($(releaseType))'
|
||||
assets: '$(Build.ArtifactStagingDirectory)/*.zip'
|
||||
isPreRelease: $(isPreRelease)
|
||||
releaseNotesSource: 'inline'
|
||||
releaseNotesInline: 'todo'
|
||||
changeLogCompareToRelease: 'lastNonDraftRelease'
|
||||
changeLogType: 'commitBased'
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Update master version'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
$payload = @{
|
||||
'current-version-$(releaseType)' = '$(Build.BuildNumber)'
|
||||
'jwt-secret' = '$(JWTSecret)'
|
||||
} | ConvertTo-Json
|
||||
|
||||
- task: NuGetToolInstaller@1
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Setup Pre-Release configuration'
|
||||
condition: or(eq(variables['Build.SourceBranch'], 'refs/heads/release/pre'), eq(variables['Build.SourceBranch'], 'refs/heads/develop'))
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
echo '##vso[task.setvariable variable=releaseType]prerelease'
|
||||
echo '##vso[task.setvariable variable=buildConfiguration]Prerelease'
|
||||
echo '##vso[task.setvariable variable=isPreRelease]true'
|
||||
failOnStderr: true
|
||||
|
||||
$params = @{
|
||||
Uri = 'http://api.raidmax.org:5000/version'
|
||||
Method = 'POST'
|
||||
Body = $payload
|
||||
ContentType = 'application/json'
|
||||
}
|
||||
|
||||
Invoke-RestMethod @params
|
||||
- task: NuGetCommand@2
|
||||
displayName: 'Restore nuget packages'
|
||||
inputs:
|
||||
restoreSolution: '$(solution)'
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Preload external resources'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
Write-Host 'Build Configuration is $(buildConfiguration), Release Type is $(releaseType)'
|
||||
md -Force lib\open-iconic\font\css
|
||||
wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o lib\open-iconic\font\css\open-iconic-bootstrap-override.scss
|
||||
cd lib\open-iconic\font\css
|
||||
(Get-Content open-iconic-bootstrap-override.scss).replace('../fonts/', '/font/') | Set-Content open-iconic-bootstrap-override.scss
|
||||
failOnStderr: true
|
||||
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore\wwwroot'
|
||||
|
||||
- task: VSBuild@1
|
||||
displayName: 'Build projects'
|
||||
inputs:
|
||||
solution: '$(solution)'
|
||||
msbuildArgs: '/p:DeployOnBuild=false /p:PackageAsSingleFile=false /p:SkipInvalidConfigurations=true /p:PackageLocation="$(build.artifactStagingDirectory)" /p:Version=$(Build.BuildNumber)'
|
||||
platform: '$(buildPlatform)'
|
||||
configuration: '$(buildConfiguration)'
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Bundle JS Files'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
Write-Host 'Getting dotnet bundle'
|
||||
wget http://raidmax.org/IW4MAdmin/res/dotnet-bundle.zip -o $(Build.Repository.LocalPath)\dotnet-bundle.zip
|
||||
Write-Host 'Unzipping download'
|
||||
Expand-Archive -LiteralPath $(Build.Repository.LocalPath)\dotnet-bundle.zip -DestinationPath $(Build.Repository.LocalPath)
|
||||
Write-Host 'Executing dotnet-bundle'
|
||||
$(Build.Repository.LocalPath)\dotnet-bundle.exe clean $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json
|
||||
$(Build.Repository.LocalPath)\dotnet-bundle.exe $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json
|
||||
failOnStderr: true
|
||||
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore'
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Publish projects'
|
||||
inputs:
|
||||
command: 'publish'
|
||||
publishWebProjects: false
|
||||
projects: |
|
||||
**/WebfrontCore.csproj
|
||||
**/Application.csproj
|
||||
arguments: '-c $(buildConfiguration) -o $(outputFolder) /p:Version=$(Build.BuildNumber)'
|
||||
zipAfterPublish: false
|
||||
modifyOutputPath: false
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Run publish script 1'
|
||||
inputs:
|
||||
filePath: 'DeploymentFiles/PostPublish.ps1'
|
||||
arguments: '$(outputFolder)'
|
||||
failOnStderr: true
|
||||
workingDirectory: '$(Build.Repository.LocalPath)'
|
||||
|
||||
- task: BatchScript@1
|
||||
displayName: 'Run publish script 2'
|
||||
inputs:
|
||||
filename: 'Application\BuildScripts\PostPublish.bat'
|
||||
workingFolder: '$(Build.Repository.LocalPath)'
|
||||
arguments: '$(outputFolder) $(Build.Repository.LocalPath)'
|
||||
failOnStandardError: true
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Download dos2unix for line endings'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: 'wget https://raidmax.org/downloads/dos2unix.exe'
|
||||
failOnStderr: true
|
||||
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: 'Convert Linux start script line endings'
|
||||
inputs:
|
||||
script: |
|
||||
echo changing to encoding for linux start script
|
||||
dos2unix $(outputFolder)\StartIW4MAdmin.sh
|
||||
dos2unix $(outputFolder)\UpdateIW4MAdmin.sh
|
||||
echo creating website version filename
|
||||
@echo IW4MAdmin-$(Build.BuildNumber) > $(Build.ArtifactStagingDirectory)\version_$(releaseType).txt
|
||||
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Move script plugins into publish directory'
|
||||
inputs:
|
||||
SourceFolder: '$(Build.Repository.LocalPath)\Plugins\ScriptPlugins'
|
||||
Contents: '*.js'
|
||||
TargetFolder: '$(outputFolder)\Plugins'
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Move binary plugins into publish directory'
|
||||
inputs:
|
||||
SourceFolder: '$(Build.Repository.LocalPath)\BUILD\Plugins\'
|
||||
Contents: '*.dll'
|
||||
TargetFolder: '$(outputFolder)\Plugins'
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: 'Move webfront resources into publish directory'
|
||||
inputs:
|
||||
script: 'xcopy /s /y /f wwwroot $(outputFolder)\wwwroot'
|
||||
workingDirectory: '$(Build.Repository.LocalPath)\BUILD\Plugins'
|
||||
failOnStderr: true
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: 'Move gamescript files into publish directory'
|
||||
inputs:
|
||||
script: 'echo d | xcopy /s /y /f GameFiles $(outputFolder)\GameFiles'
|
||||
workingDirectory: '$(Build.Repository.LocalPath)'
|
||||
failOnStderr: true
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Publish artifact for analysis'
|
||||
inputs:
|
||||
targetPath: '$(outputFolder)'
|
||||
artifact: 'IW4MAdmin.$(buildConfiguration)'
|
||||
publishLocation: 'pipeline'
|
||||
- task: ArchiveFiles@2
|
||||
displayName: 'Generate final zip file'
|
||||
inputs:
|
||||
rootFolderOrFile: '$(outputFolder)'
|
||||
includeRootFolder: false
|
||||
archiveType: 'zip'
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Build.BuildNumber).zip'
|
||||
replaceExistingArchive: true
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
inputs:
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Build.BuildNumber).zip'
|
||||
artifact: 'IW4MAdmin-$(Build.BuildNumber).zip'
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Publish artifact for analysis'
|
||||
inputs:
|
||||
targetPath: '$(outputFolder)'
|
||||
artifact: 'IW4MAdmin.$(buildConfiguration)'
|
||||
publishLocation: 'pipeline'
|
||||
|
||||
- task: FtpUpload@2
|
||||
condition: ne(variables['Build.SourceBranch'], 'refs/heads/develop')
|
||||
displayName: 'Upload zip file to website'
|
||||
inputs:
|
||||
credentialsOption: 'inputs'
|
||||
serverUrl: '$(FTPUrl)'
|
||||
username: '$(FTPUsername)'
|
||||
password: '$(FTPPassword)'
|
||||
rootDirectory: '$(Build.ArtifactStagingDirectory)'
|
||||
filePatterns: '*.zip'
|
||||
remoteDirectory: 'IW4MAdmin/Download'
|
||||
clean: false
|
||||
cleanContents: false
|
||||
preservePaths: false
|
||||
trustSSL: false
|
||||
|
||||
- task: FtpUpload@2
|
||||
condition: ne(variables['Build.SourceBranch'], 'refs/heads/develop')
|
||||
displayName: 'Upload version info to website'
|
||||
inputs:
|
||||
credentialsOption: 'inputs'
|
||||
serverUrl: '$(FTPUrl)'
|
||||
username: '$(FTPUsername)'
|
||||
password: '$(FTPPassword)'
|
||||
rootDirectory: '$(Build.ArtifactStagingDirectory)'
|
||||
filePatterns: 'version_$(releaseType).txt'
|
||||
remoteDirectory: 'IW4MAdmin'
|
||||
clean: false
|
||||
cleanContents: false
|
||||
preservePaths: false
|
||||
trustSSL: false
|
||||
|
||||
- task: GitHubRelease@1
|
||||
condition: ne(variables['Build.SourceBranch'], 'refs/heads/develop')
|
||||
displayName: 'Make GitHub release'
|
||||
inputs:
|
||||
gitHubConnection: 'github.com_RaidMax'
|
||||
repositoryName: 'RaidMax/IW4M-Admin'
|
||||
action: 'create'
|
||||
target: '$(Build.SourceVersion)'
|
||||
tagSource: 'userSpecifiedTag'
|
||||
tag: '$(Build.BuildNumber)-$(releaseType)'
|
||||
title: 'IW4MAdmin $(Build.BuildNumber) ($(releaseType))'
|
||||
assets: '$(Build.ArtifactStagingDirectory)/*.zip'
|
||||
isPreRelease: $(isPreRelease)
|
||||
releaseNotesSource: 'inline'
|
||||
releaseNotesInline: 'Automated rolling release - changelog below. [Updating Instructions](https://github.com/RaidMax/IW4M-Admin/wiki/Getting-Started#updating)'
|
||||
changeLogCompareToRelease: 'lastNonDraftRelease'
|
||||
changeLogType: 'commitBased'
|
||||
|
||||
- task: PowerShell@2
|
||||
condition: ne(variables['Build.SourceBranch'], 'refs/heads/develop')
|
||||
displayName: 'Update master version'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
$payload = @{
|
||||
'current-version-$(releaseType)' = '$(Build.BuildNumber)'
|
||||
'jwt-secret' = '$(JWTSecret)'
|
||||
} | ConvertTo-Json
|
||||
|
||||
|
||||
$params = @{
|
||||
Uri = 'http://api.raidmax.org:5000/version'
|
||||
Method = 'POST'
|
||||
Body = $payload
|
||||
ContentType = 'application/json'
|
||||
}
|
||||
|
||||
Invoke-RestMethod @params
|
||||
|
@ -19,11 +19,33 @@ Setup()
|
||||
|
||||
level.commonFunctions = spawnstruct();
|
||||
level.commonFunctions.setDvar = "SetDvarIfUninitialized";
|
||||
level.commonFunctions.isBot = "IsBot";
|
||||
level.commonFunctions.getXuid = "GetXuid";
|
||||
level.commonFunctions.getPlayerFromClientNum = "GetPlayerFromClientNum";
|
||||
level.commonFunctions.waittillNotifyOrTimeout = "WaittillNotifyOrTimeout";
|
||||
level.commonFunctions.getInboundData = "GetInboundData";
|
||||
level.commonFunctions.getOutboundData = "GetOutboundData";
|
||||
level.commonFunctions.setInboundData = "SetInboundData";
|
||||
level.commonFunctions.setOutboundData = "SetOutboundData";
|
||||
|
||||
level.overrideMethods = [];
|
||||
level.overrideMethods[level.commonFunctions.setDvar] = scripts\_integration_base::NotImplementedFunction;
|
||||
level.overrideMethods[level.commonFunctions.getPlayerFromClientNum] = ::_GetPlayerFromClientNum;
|
||||
level.overrideMethods[level.commonFunctions.getInboundData] = ::_GetInboundData;
|
||||
level.overrideMethods[level.commonFunctions.getOutboundData] = ::_GetOutboundData;
|
||||
level.overrideMethods[level.commonFunctions.setInboundData] = ::_SetInboundData;
|
||||
level.overrideMethods[level.commonFunctions.setOutboundData] = ::_SetOutboundData;
|
||||
|
||||
level.busMethods = [];
|
||||
level.busMethods[level.commonFunctions.getInboundData] = ::_GetInboundData;
|
||||
level.busMethods[level.commonFunctions.getOutboundData] = ::_GetOutboundData;
|
||||
level.busMethods[level.commonFunctions.setInboundData] = ::_SetInboundData;
|
||||
level.busMethods[level.commonFunctions.setOutboundData] = ::_SetOutboundData;
|
||||
|
||||
level.commonKeys = spawnstruct();
|
||||
level.commonKeys.enabled = "sv_iw4madmin_integration_enabled";
|
||||
level.commonKeys.busMode = "sv_iw4madmin_integration_busmode";
|
||||
level.commonKeys.busDir = "sv_iw4madmin_integration_busdir";
|
||||
level.eventBus.inLocation = "";
|
||||
level.eventBus.outLocation = "";
|
||||
|
||||
level.notifyTypes = spawnstruct();
|
||||
level.notifyTypes.gameFunctionsInitialized = "GameFunctionsInitialized";
|
||||
@ -33,7 +55,7 @@ Setup()
|
||||
level.clientDataKey = "clientData";
|
||||
|
||||
level.eventTypes = spawnstruct();
|
||||
level.eventTypes.localClientEvent = "client_event";
|
||||
level.eventTypes.eventAvailable = "EventAvailable";
|
||||
level.eventTypes.clientDataReceived = "ClientDataReceived";
|
||||
level.eventTypes.clientDataRequested = "ClientDataRequested";
|
||||
level.eventTypes.setClientDataRequested = "SetClientDataRequested";
|
||||
@ -51,12 +73,11 @@ Setup()
|
||||
level.clientCommandCallbacks = [];
|
||||
level.clientCommandRusAsTarget = [];
|
||||
level.logger = spawnstruct();
|
||||
level.overrideMethods = [];
|
||||
|
||||
level.iw4madminIntegrationDebug = GetDvarInt( "sv_iw4madmin_integration_debug" );
|
||||
InitializeLogger();
|
||||
|
||||
wait ( 0.05 ); // needed to give script engine time to propagate notifies
|
||||
wait ( 0.05 * 2 ); // needed to give script engine time to propagate notifies
|
||||
|
||||
level notify( level.notifyTypes.integrationBootstrapInitialized );
|
||||
level waittill( level.notifyTypes.gameFunctionsInitialized );
|
||||
@ -65,122 +86,58 @@ Setup()
|
||||
|
||||
_SetDvarIfUninitialized( level.eventBus.inVar, "" );
|
||||
_SetDvarIfUninitialized( level.eventBus.outVar, "" );
|
||||
_SetDvarIfUninitialized( "sv_iw4madmin_integration_enabled", 1 );
|
||||
_SetDvarIfUninitialized( level.commonKeys.enabled, 1 );
|
||||
_SetDvarIfUninitialized( level.commonKeys.busMode, "rcon" );
|
||||
_SetDvarIfUninitialized( level.commonKeys.busdir, "" );
|
||||
_SetDvarIfUninitialized( "sv_iw4madmin_integration_debug", 0 );
|
||||
_SetDvarIfUninitialized( "GroupSeparatorChar", "" );
|
||||
_SetDvarIfUninitialized( "RecordSeparatorChar", "" );
|
||||
_SetDvarIfUninitialized( "UnitSeparatorChar", "" );
|
||||
|
||||
if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 )
|
||||
if ( GetDvarInt( level.commonKeys.enabled ) != 1 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// start long running tasks
|
||||
level thread MonitorClientEvents();
|
||||
level thread MonitorBus();
|
||||
level thread OnPlayerConnect();
|
||||
thread MonitorEvents();
|
||||
thread MonitorBus();
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// Client Methods
|
||||
//////////////////////////////////
|
||||
|
||||
OnPlayerConnect()
|
||||
MonitorEvents()
|
||||
{
|
||||
level endon ( "game_ended" );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
level waittill( "connected", player );
|
||||
|
||||
if ( _IsBot( player ) )
|
||||
{
|
||||
// we don't want to track bots
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( !IsDefined( player.pers[level.clientDataKey] ) )
|
||||
{
|
||||
player.pers[level.clientDataKey] = spawnstruct();
|
||||
}
|
||||
|
||||
player thread OnPlayerSpawned();
|
||||
}
|
||||
}
|
||||
|
||||
OnPlayerSpawned()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
self waittill( "spawned_player" );
|
||||
self PlayerSpawnEvents();
|
||||
}
|
||||
}
|
||||
|
||||
OnGameEnded()
|
||||
{
|
||||
for ( ;; )
|
||||
{
|
||||
level waittill( "game_ended" );
|
||||
// note: you can run data code here but it's possible for
|
||||
// data to get truncated, so we will try a timer based approach for now
|
||||
}
|
||||
}
|
||||
|
||||
DisplayWelcomeData()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
clientData = self.pers[level.clientDataKey];
|
||||
|
||||
if ( clientData.permissionLevel == "User" || clientData.permissionLevel == "Flagged" )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self IPrintLnBold( "Welcome, your level is ^5" + clientData.permissionLevel );
|
||||
wait( 2.0 );
|
||||
self IPrintLnBold( "You were last seen ^5" + clientData.lastConnection );
|
||||
}
|
||||
|
||||
PlayerSpawnEvents()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
clientData = self.pers[level.clientDataKey];
|
||||
|
||||
// this gives IW4MAdmin some time to register the player before making the request;
|
||||
// although probably not necessary some users might have a slow database or poll rate
|
||||
wait ( 2 );
|
||||
|
||||
if ( IsDefined( clientData.state ) && clientData.state == "complete" )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self RequestClientBasicData();
|
||||
}
|
||||
|
||||
MonitorClientEvents()
|
||||
{
|
||||
level endon( "game_ended" );
|
||||
level endon( level.eventTypes.gameEnd );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
level waittill( level.eventTypes.localClientEvent, client );
|
||||
level waittill( level.eventTypes.eventAvailable, event );
|
||||
|
||||
LogDebug( "Processing Event " + client.event.type + "-" + client.event.subtype );
|
||||
LogDebug( "Processing Event " + event.type + "-" + event.subtype );
|
||||
|
||||
eventHandler = level.eventCallbacks[client.event.type];
|
||||
eventHandler = level.eventCallbacks[event.type];
|
||||
|
||||
if ( IsDefined( eventHandler ) )
|
||||
{
|
||||
client [[eventHandler]]( client.event );
|
||||
LogDebug( "notify client for " + client.event.type );
|
||||
client notify( level.eventTypes.localClientEvent, client.event );
|
||||
if ( IsDefined( event.entity ) )
|
||||
{
|
||||
event.entity [[eventHandler]]( event );
|
||||
}
|
||||
else
|
||||
{
|
||||
[[eventHandler]]( event );
|
||||
}
|
||||
}
|
||||
|
||||
if ( IsDefined( event.entity ) )
|
||||
{
|
||||
LogDebug( "Notify client for " + event.type );
|
||||
event.entity notify( event.type, event );
|
||||
}
|
||||
else
|
||||
{
|
||||
LogDebug( "Notify level for " + event.type );
|
||||
level notify( event.type, event );
|
||||
}
|
||||
|
||||
client.eventData = [];
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,11 +145,13 @@ MonitorClientEvents()
|
||||
// Helper Methods
|
||||
//////////////////////////////////
|
||||
|
||||
_IsBot( entity )
|
||||
NotImplementedFunction( a, b, c, d, e, f )
|
||||
{
|
||||
// there already is a cgame function exists as "IsBot", for IW4, but unsure what all titles have it defined,
|
||||
// so we are defining it here
|
||||
return IsDefined( entity.pers["isBot"] ) && entity.pers["isBot"];
|
||||
LogWarning( "Function not implemented" );
|
||||
if ( IsDefined ( a ) )
|
||||
{
|
||||
LogWarning( a );
|
||||
}
|
||||
}
|
||||
|
||||
_SetDvarIfUninitialized( dvarName, dvarValue )
|
||||
@ -200,9 +159,42 @@ _SetDvarIfUninitialized( dvarName, dvarValue )
|
||||
[[level.overrideMethods[level.commonFunctions.setDvar]]]( dvarName, dvarValue );
|
||||
}
|
||||
|
||||
NotImplementedFunction( a, b, c, d, e, f )
|
||||
_GetPlayerFromClientNum( clientNum )
|
||||
{
|
||||
LogWarning( "Function not implemented" );
|
||||
if ( clientNum < 0 )
|
||||
{
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for ( i = 0; i < level.players.size; i++ )
|
||||
{
|
||||
if ( level.players[i] getEntityNumber() == clientNum )
|
||||
{
|
||||
return level.players[i];
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
_GetInboundData( location )
|
||||
{
|
||||
return GetDvar( level.eventBus.inVar );
|
||||
}
|
||||
|
||||
_GetOutboundData( location )
|
||||
{
|
||||
return GetDvar( level.eventBus.outVar );
|
||||
}
|
||||
|
||||
_SetInboundData( location, data )
|
||||
{
|
||||
return SetDvar( level.eventBus.inVar, data );
|
||||
}
|
||||
|
||||
_SetOutboundData( location, data )
|
||||
{
|
||||
return SetDvar( level.eventBus.outVar, data );
|
||||
}
|
||||
|
||||
// Not every game can output to console or even game log.
|
||||
@ -223,7 +215,7 @@ _Log( LogLevel, message )
|
||||
{
|
||||
for( i = 0; i < level.logger._logger.size; i++ )
|
||||
{
|
||||
[[level.logger._logger[i]]]( LogLevel, message );
|
||||
[[level.logger._logger[i]]]( LogLevel, GetSubStr( message, 0, 1000 ) );
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,13 +277,13 @@ RegisterLogger( logger )
|
||||
RequestClientMeta( metaKey )
|
||||
{
|
||||
getClientMetaEvent = BuildEventRequest( true, level.eventTypes.clientDataRequested, "Meta", self, metaKey );
|
||||
level thread QueueEvent( getClientMetaEvent, level.eventTypes.clientDataRequested, self );
|
||||
thread QueueEvent( getClientMetaEvent, level.eventTypes.clientDataRequested, self );
|
||||
}
|
||||
|
||||
RequestClientBasicData()
|
||||
{
|
||||
getClientDataEvent = BuildEventRequest( true, level.eventTypes.clientDataRequested, "None", self, "" );
|
||||
level thread QueueEvent( getClientDataEvent, level.eventTypes.clientDataRequested, self );
|
||||
thread QueueEvent( getClientDataEvent, level.eventTypes.clientDataRequested, self );
|
||||
}
|
||||
|
||||
IncrementClientMeta( metaKey, incrementValue, clientId )
|
||||
@ -306,18 +298,20 @@ DecrementClientMeta( metaKey, decrementValue, clientId )
|
||||
|
||||
SetClientMeta( metaKey, metaValue, clientId, direction )
|
||||
{
|
||||
data = "key=" + metaKey + "|value=" + metaValue;
|
||||
data = [];
|
||||
data["key"] = metaKey;
|
||||
data["value"] = metaValue;
|
||||
clientNumber = -1;
|
||||
|
||||
if ( IsDefined ( clientId ) )
|
||||
{
|
||||
data = data + "|clientId=" + clientId;
|
||||
data["clientId"] = clientId;
|
||||
clientNumber = -1;
|
||||
}
|
||||
|
||||
if ( IsDefined( direction ) )
|
||||
{
|
||||
data = data + "|direction=" + direction;
|
||||
data["direction"] = direction;
|
||||
}
|
||||
|
||||
if ( IsPlayer( self ) )
|
||||
@ -326,7 +320,7 @@ SetClientMeta( metaKey, metaValue, clientId, direction )
|
||||
}
|
||||
|
||||
setClientMetaEvent = BuildEventRequest( true, level.eventTypes.setClientDataRequested, "Meta", clientNumber, data );
|
||||
level thread QueueEvent( setClientMetaEvent, level.eventTypes.setClientDataRequested, self );
|
||||
thread QueueEvent( setClientMetaEvent, level.eventTypes.setClientDataRequested, self );
|
||||
}
|
||||
|
||||
BuildEventRequest( responseExpected, eventType, eventSubtype, entOrId, data )
|
||||
@ -341,6 +335,11 @@ BuildEventRequest( responseExpected, eventType, eventSubtype, entOrId, data )
|
||||
eventSubtype = "None";
|
||||
}
|
||||
|
||||
if ( !IsDefined( entOrId ) )
|
||||
{
|
||||
entOrId = "-1";
|
||||
}
|
||||
|
||||
if ( IsPlayer( entOrId ) )
|
||||
{
|
||||
entOrId = entOrId getEntityNumber();
|
||||
@ -352,52 +351,65 @@ BuildEventRequest( responseExpected, eventType, eventSubtype, entOrId, data )
|
||||
{
|
||||
request = "1";
|
||||
}
|
||||
|
||||
request = request + ";" + eventType + ";" + eventSubtype + ";" + entOrId + ";" + data;
|
||||
|
||||
data = BuildDataString( data );
|
||||
groupSeparator = GetSubStr( GetDvar( "GroupSeparatorChar" ), 0, 1 );
|
||||
request = request + groupSeparator + eventType + groupSeparator + eventSubtype + groupSeparator + entOrId + groupSeparator + data;
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
MonitorBus()
|
||||
{
|
||||
level endon( "game_ended" );
|
||||
level endon( level.eventTypes.gameEnd );
|
||||
|
||||
level.eventBus.inLocation = level.eventBus.inVar + "_" + GetDvar( "net_port" );
|
||||
level.eventBus.outLocation = level.eventBus.outVar + "_" + GetDvar( "net_port" );
|
||||
|
||||
[[level.overrideMethods[level.commonFunctions.SetInboundData]]]( level.eventBus.inLocation, "" );
|
||||
[[level.overrideMethods[level.commonFunctions.SetOutboundData]]]( level.eventBus.outLocation, "" );
|
||||
|
||||
for( ;; )
|
||||
{
|
||||
wait ( 0.1 );
|
||||
|
||||
// check to see if IW4MAdmin is ready to receive more data
|
||||
if ( getDvar( level.eventBus.inVar ) == "" )
|
||||
inVal = [[level.busMethods[level.commonFunctions.getInboundData]]]( level.eventBus.inLocation );
|
||||
|
||||
if ( !IsDefined( inVal ) || inVal == "" )
|
||||
{
|
||||
level notify( "bus_ready" );
|
||||
}
|
||||
|
||||
eventString = getDvar( level.eventBus.outVar );
|
||||
eventString = [[level.busMethods[level.commonFunctions.getOutboundData]]]( level.eventBus.outLocation );
|
||||
|
||||
if ( eventString == "" )
|
||||
if ( !IsDefined( eventString ) || eventString == "" )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
LogDebug( "-> " + eventString );
|
||||
|
||||
NotifyClientEvent( strtok( eventString, ";" ) );
|
||||
groupSeparator = GetSubStr( GetDvar( "GroupSeparatorChar" ), 0, 1 );
|
||||
NotifyEvent( strtok( eventString, groupSeparator ) );
|
||||
|
||||
SetDvar( level.eventBus.outVar, "" );
|
||||
[[level.busMethods[level.commonFunctions.SetOutboundData]]]( level.eventBus.outLocation, "" );
|
||||
}
|
||||
}
|
||||
|
||||
QueueEvent( request, eventType, notifyEntity )
|
||||
{
|
||||
level endon( "game_ended" );
|
||||
level endon( level.eventTypes.gameEnd );
|
||||
|
||||
start = GetTime();
|
||||
maxWait = level.eventBus.timeout * 1000; // 30 seconds
|
||||
timedOut = "";
|
||||
|
||||
while ( GetDvar( level.eventBus.inVar ) != "" && ( GetTime() - start ) < maxWait )
|
||||
while ( [[level.busMethods[level.commonFunctions.getInboundData]]]( level.eventBus.inLocation ) != "" && ( GetTime() - start ) < maxWait )
|
||||
{
|
||||
level [[level.overrideMethods["waittill_notify_or_timeout"]]]( "bus_ready", 1 );
|
||||
level [[level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout]]]( "bus_ready", 1 );
|
||||
|
||||
if ( GetDvar( level.eventBus.inVar ) != "" )
|
||||
if ( [[level.busMethods[level.commonFunctions.getInboundData]]]( level.eventBus.inLocation ) != "" )
|
||||
{
|
||||
LogDebug( "A request is already in progress..." );
|
||||
timedOut = "set";
|
||||
@ -407,7 +419,7 @@ QueueEvent( request, eventType, notifyEntity )
|
||||
timedOut = "unset";
|
||||
}
|
||||
|
||||
if ( timedOut == "set")
|
||||
if ( timedOut == "set" )
|
||||
{
|
||||
LogDebug( "Timed out waiting for response..." );
|
||||
|
||||
@ -416,14 +428,14 @@ QueueEvent( request, eventType, notifyEntity )
|
||||
notifyEntity NotifyClientEventTimeout( eventType );
|
||||
}
|
||||
|
||||
SetDvar( level.eventBus.inVar, "" );
|
||||
[[level.busMethods[level.commonFunctions.SetInboundData]]]( level.eventBus.inLocation, "" );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LogDebug("<- " + request );
|
||||
LogDebug( "<- " + request );
|
||||
|
||||
SetDvar( level.eventBus.inVar, request );
|
||||
[[level.busMethods[level.commonFunctions.setInboundData]]]( level.eventBus.inLocation, request );
|
||||
}
|
||||
|
||||
ParseDataString( data )
|
||||
@ -434,13 +446,13 @@ ParseDataString( data )
|
||||
return [];
|
||||
}
|
||||
|
||||
dataParts = strtok( data, "|" );
|
||||
dataParts = strtok( data, GetSubStr( GetDvar( "RecordSeparatorChar" ), 0, 1 ) );
|
||||
dict = [];
|
||||
|
||||
for ( i = 0; i < dataParts.size; i++ )
|
||||
{
|
||||
part = dataParts[i];
|
||||
splitPart = strtok( part, "=" );
|
||||
splitPart = strtok( part, GetSubStr( GetDvar( "UnitSeparatorChar" ), 0, 1 ) );
|
||||
key = splitPart[0];
|
||||
value = splitPart[1];
|
||||
dict[key] = value;
|
||||
@ -450,6 +462,26 @@ ParseDataString( data )
|
||||
return dict;
|
||||
}
|
||||
|
||||
BuildDataString( data )
|
||||
{
|
||||
if ( IsString( data ) )
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
dataString = "";
|
||||
keys = GetArrayKeys( data );
|
||||
unitSeparator = GetSubStr( GetDvar( "UnitSeparatorChar" ), 0, 1 );
|
||||
recordSeparator = GetSubStr( GetDvar( "RecordSeparatorChar" ), 0, 1 );
|
||||
|
||||
for ( i = 0; i < keys.size; i++ )
|
||||
{
|
||||
dataString = dataString + keys[i] + unitSeparator + data[keys[i]] + recordSeparator;
|
||||
}
|
||||
|
||||
return dataString;
|
||||
}
|
||||
|
||||
NotifyClientEventTimeout( eventType )
|
||||
{
|
||||
// todo: make this actual eventing
|
||||
@ -459,7 +491,7 @@ NotifyClientEventTimeout( eventType )
|
||||
}
|
||||
}
|
||||
|
||||
NotifyClientEvent( eventInfo )
|
||||
NotifyEvent( eventInfo )
|
||||
{
|
||||
origin = [[level.overrideMethods[level.commonFunctions.getPlayerFromClientNum]]]( int( eventInfo[3] ) );
|
||||
target = [[level.overrideMethods[level.commonFunctions.getPlayerFromClientNum]]]( int( eventInfo[4] ) );
|
||||
@ -467,15 +499,10 @@ NotifyClientEvent( eventInfo )
|
||||
event = spawnstruct();
|
||||
event.type = eventInfo[1];
|
||||
event.subtype = eventInfo[2];
|
||||
event.data = eventInfo[5];
|
||||
event.data = ParseDataString( eventInfo[5] );
|
||||
event.origin = origin;
|
||||
event.target = target;
|
||||
|
||||
if ( IsDefined( event.data ) )
|
||||
{
|
||||
LogDebug( "NotifyClientEvent->" + event.data );
|
||||
}
|
||||
|
||||
if ( int( eventInfo[3] ) != -1 && !IsDefined( origin ) )
|
||||
{
|
||||
LogDebug( "origin is null but the slot id is " + int( eventInfo[3] ) );
|
||||
@ -485,23 +512,15 @@ NotifyClientEvent( eventInfo )
|
||||
LogDebug( "target is null but the slot id is " + int( eventInfo[4] ) );
|
||||
}
|
||||
|
||||
if ( IsDefined( target ) )
|
||||
client = event.origin;
|
||||
|
||||
if ( !IsDefined( client ) )
|
||||
{
|
||||
client = event.target;
|
||||
}
|
||||
else if ( IsDefined( origin ) )
|
||||
{
|
||||
client = event.origin;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogDebug( "Neither origin or target are set but we are a Client Event, aborting" );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
client.event = event;
|
||||
level notify( level.eventTypes.localClientEvent, client );
|
||||
|
||||
event.entity = client;
|
||||
level notify( level.eventTypes.eventAvailable, event );
|
||||
}
|
||||
|
||||
AddClientCommand( commandName, shouldRunAsTarget, callback, shouldOverwrite )
|
||||
@ -521,7 +540,6 @@ AddClientCommand( commandName, shouldRunAsTarget, callback, shouldOverwrite )
|
||||
|
||||
OnClientDataReceived( event )
|
||||
{
|
||||
event.data = ParseDataString( event.data );
|
||||
clientData = self.pers[level.clientDataKey];
|
||||
|
||||
if ( event.subtype == "Fail" )
|
||||
@ -541,7 +559,7 @@ OnClientDataReceived( event )
|
||||
metaKey = event.data[0];
|
||||
clientData.meta[metaKey] = event.data[metaKey];
|
||||
|
||||
LogDebug( "Meta Key=" + metaKey + ", Meta Value=" + event.data[metaKey] );
|
||||
LogDebug( "Meta Key=" + CoerceUndefined( metaKey ) + ", Meta Value=" + CoerceUndefined( event.data[metaKey] ) );
|
||||
|
||||
return;
|
||||
}
|
||||
@ -553,13 +571,11 @@ OnClientDataReceived( event )
|
||||
clientData.performance = event.data["performance"];
|
||||
clientData.state = "complete";
|
||||
self.persistentClientId = event.data["clientId"];
|
||||
|
||||
self thread DisplayWelcomeData();
|
||||
}
|
||||
|
||||
OnExecuteCommand( event )
|
||||
{
|
||||
data = ParseDataString( event.data );
|
||||
data = event.data;
|
||||
response = "";
|
||||
|
||||
command = level.clientCommandCallbacks[event.subtype];
|
||||
@ -573,7 +589,14 @@ OnExecuteCommand( event )
|
||||
|
||||
if ( IsDefined( command ) )
|
||||
{
|
||||
response = executionContextEntity [[command]]( event, data );
|
||||
if ( IsDefined( executionContextEntity ) )
|
||||
{
|
||||
response = executionContextEntity thread [[command]]( event, data );
|
||||
}
|
||||
else
|
||||
{
|
||||
thread [[command]]( event );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -589,6 +612,15 @@ OnExecuteCommand( event )
|
||||
|
||||
OnSetClientDataCompleted( event )
|
||||
{
|
||||
// IW4MAdmin let us know it persisted (success or fail)
|
||||
LogDebug( "Set Client Data -> subtype = " + event.subType + " status = " + event.data["status"] );
|
||||
LogDebug( "Set Client Data -> subtype = " + CoerceUndefined( event.subType ) + ", status = " + CoerceUndefined( event.data["status"] ) );
|
||||
}
|
||||
|
||||
CoerceUndefined( object )
|
||||
{
|
||||
if ( !IsDefined( object ) )
|
||||
{
|
||||
return "undefined";
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
@ -8,18 +8,17 @@ Init()
|
||||
Setup()
|
||||
{
|
||||
level endon( "game_ended" );
|
||||
waittillframeend;
|
||||
|
||||
// it's possible that the notify type has not been defined yet so we have to hard code it
|
||||
level waittill( "SharedFunctionsInitialized" );
|
||||
level waittill( level.notifyTypes.sharedFunctionsInitialized );
|
||||
level.eventBus.gamename = "IW4";
|
||||
|
||||
scripts\_integration_base::RegisterLogger( ::Log2Console );
|
||||
|
||||
level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired;
|
||||
level.overrideMethods[level.commonFunctions.setDvar] = ::_SetDvarIfUninitialized;
|
||||
level.overrideMethods[level.commonFunctions.isBot] = ::IsTestClient;
|
||||
level.overrideMethods[level.commonFunctions.getXuid] = ::_GetXUID;
|
||||
level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout;
|
||||
level.overrideMethods[level.commonFunctions.getTotalShotsFired] = ::GetTotalShotsFired;
|
||||
level.overrideMethods[level.commonFunctions.setDvar] = ::SetDvarIfUninitializedWrapper;
|
||||
level.overrideMethods[level.commonFunctions.isBot] = ::IsBotWrapper;
|
||||
level.overrideMethods[level.commonFunctions.getXuid] = ::GetXuidWrapper;
|
||||
level.overrideMethods[level.commonFunctions.changeTeam] = ::ChangeTeam;
|
||||
level.overrideMethods[level.commonFunctions.getTeamCounts] = ::CountPlayers;
|
||||
level.overrideMethods[level.commonFunctions.getMaxClients] = ::GetMaxClients;
|
||||
@ -28,19 +27,23 @@ Setup()
|
||||
level.overrideMethods[level.commonFunctions.getClientKillStreak] = ::GetClientKillStreak;
|
||||
level.overrideMethods[level.commonFunctions.backupRestoreClientKillStreakData] = ::BackupRestoreClientKillStreakData;
|
||||
level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = ::WaitTillAnyTimeout;
|
||||
|
||||
level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout] = ::WaitillNotifyOrTimeoutWrapper;
|
||||
|
||||
level.overrideMethods[level.commonFunctions.getInboundData] = ::GetInboundData;
|
||||
level.overrideMethods[level.commonFunctions.getOutboundData] = ::GetOutboundData;
|
||||
level.overrideMethods[level.commonFunctions.setInboundData] = ::SetInboundData;
|
||||
level.overrideMethods[level.commonFunctions.setOutboundData] = ::SetOutboundData;
|
||||
|
||||
RegisterClientCommands();
|
||||
|
||||
_SetDvarIfUninitialized( "sv_iw4madmin_autobalance", 0 );
|
||||
|
||||
level notify( level.notifyTypes.gameFunctionsInitialized );
|
||||
|
||||
if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 )
|
||||
if ( GetDvarInt( level.commonKeys.enabled ) != 1 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
level thread OnPlayerConnect();
|
||||
thread OnPlayerConnect();
|
||||
}
|
||||
|
||||
OnPlayerConnect()
|
||||
@ -51,12 +54,12 @@ OnPlayerConnect()
|
||||
{
|
||||
level waittill( "connected", player );
|
||||
|
||||
if ( player call [[ level.overrideMethods[ level.commonFunctions.isBot ] ]]() )
|
||||
if ( player IsTestClient() )
|
||||
{
|
||||
// we don't want to track bots
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
player thread SetPersistentData();
|
||||
player thread WaitForClientEvents();
|
||||
}
|
||||
@ -87,7 +90,7 @@ WaitForClientEvents()
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
self waittill( level.eventTypes.localClientEvent, event );
|
||||
self waittill( level.eventTypes.eventAvailable, event );
|
||||
|
||||
scripts\_integration_base::LogDebug( "Received client event " + event.type );
|
||||
|
||||
@ -99,6 +102,26 @@ WaitForClientEvents()
|
||||
}
|
||||
}
|
||||
|
||||
GetInboundData( location )
|
||||
{
|
||||
return FileRead( location );
|
||||
}
|
||||
|
||||
GetOutboundData( location )
|
||||
{
|
||||
return FileRead( location );
|
||||
}
|
||||
|
||||
SetInboundData( location, data )
|
||||
{
|
||||
FileWrite( location, data, "write" );
|
||||
}
|
||||
|
||||
SetOutboundData( location, data )
|
||||
{
|
||||
FileWrite( location, data, "write" );
|
||||
}
|
||||
|
||||
GetMaxClients()
|
||||
{
|
||||
return level.maxClients;
|
||||
@ -186,12 +209,7 @@ GetTotalShotsFired()
|
||||
return maps\mp\_utility::getPlayerStat( "mostshotsfired" );
|
||||
}
|
||||
|
||||
_SetDvarIfUninitialized( dvar, value )
|
||||
{
|
||||
SetDvarIfUninitialized( dvar, value );
|
||||
}
|
||||
|
||||
_waittill_notify_or_timeout( _notify, timeout )
|
||||
WaitillNotifyOrTimeoutWrapper( _notify, timeout )
|
||||
{
|
||||
common_scripts\utility::waittill_notify_or_timeout( _notify, timeout );
|
||||
}
|
||||
@ -201,11 +219,21 @@ Log2Console( logLevel, message )
|
||||
PrintConsole( "[" + logLevel + "] " + message + "\n" );
|
||||
}
|
||||
|
||||
_GetXUID()
|
||||
SetDvarIfUninitializedWrapper( dvar, value )
|
||||
{
|
||||
SetDvarIfUninitialized( dvar, value );
|
||||
}
|
||||
|
||||
GetXuidWrapper()
|
||||
{
|
||||
return self GetXUID();
|
||||
}
|
||||
|
||||
IsBotWrapper( client )
|
||||
{
|
||||
return client IsTestClient();
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// GUID helpers
|
||||
/////////////////////////////////
|
||||
@ -519,11 +547,7 @@ HideImpl()
|
||||
|
||||
AlertImpl( event, data )
|
||||
{
|
||||
if ( level.eventBus.gamename == "IW4" )
|
||||
{
|
||||
self thread maps\mp\gametypes\_hud_message::oldNotifyMessage( data["alertType"], data["message"], "compass_waypoint_target", ( 1, 0, 0 ), "ui_mp_nukebomb_timer", 7.5 );
|
||||
}
|
||||
|
||||
self thread maps\mp\gametypes\_hud_message::oldNotifyMessage( data["alertType"], data["message"], "compass_waypoint_target", ( 1, 0, 0 ), "ui_mp_nukebomb_timer", 7.5 );
|
||||
return "Sent alert to " + self.name;
|
||||
}
|
||||
|
||||
|
@ -8,50 +8,22 @@ Init()
|
||||
Setup()
|
||||
{
|
||||
level endon( "game_ended" );
|
||||
waittillframeend;
|
||||
|
||||
// it's possible that the notify type has not been defined yet so we have to hard code it
|
||||
level waittill( "SharedFunctionsInitialized" );
|
||||
level waittill( level.notifyTypes.sharedFunctionsInitialized );
|
||||
level.eventBus.gamename = "IW5";
|
||||
|
||||
scripts\_integration_base::RegisterLogger( ::Log2Console );
|
||||
|
||||
level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired;
|
||||
level.overrideMethods["SetDvarIfUninitialized"] = ::_SetDvarIfUninitialized;
|
||||
level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout;
|
||||
level.overrideMethods[level.commonFunctions.isBot] = ::IsTestClient;
|
||||
level.overrideMethods[level.commonFunctions.getXuid] = ::_GetXUID;
|
||||
|
||||
level.overrideMethods[level.commonFunctions.getTotalShotsFired] = ::GetTotalShotsFired;
|
||||
level.overrideMethods[level.commonFunctions.setDvar] = ::SetDvarIfUninitializedWrapper;
|
||||
level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout] = ::WaitillNotifyOrTimeoutWrapper;
|
||||
level.overrideMethods[level.commonFunctions.isBot] = ::IsBotWrapper;
|
||||
level.overrideMethods[level.commonFunctions.getXuid] = ::GetXuidWrapper;
|
||||
level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = ::WaitTillAnyTimeout;
|
||||
RegisterClientCommands();
|
||||
|
||||
_SetDvarIfUninitialized( "sv_iw4madmin_autobalance", 0 );
|
||||
|
||||
level notify( level.notifyTypes.gameFunctionsInitialized );
|
||||
|
||||
if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
level thread OnPlayerConnect();
|
||||
}
|
||||
|
||||
OnPlayerConnect()
|
||||
{
|
||||
level endon ( "game_ended" );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
level waittill( "connected", player );
|
||||
|
||||
if ( player call [[ level.overrideMethods[ level.commonFunctions.isBot ] ]]() )
|
||||
{
|
||||
// we don't want to track bots
|
||||
continue;
|
||||
}
|
||||
|
||||
player thread SetPersistentData();
|
||||
player thread WaitForClientEvents();
|
||||
}
|
||||
}
|
||||
|
||||
RegisterClientCommands()
|
||||
@ -69,39 +41,17 @@ RegisterClientCommands()
|
||||
scripts\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl );
|
||||
}
|
||||
|
||||
WaitForClientEvents()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
// example of requesting a meta value
|
||||
lastServerMetaKey = "LastServerPlayed";
|
||||
// self scripts\_integration_base::RequestClientMeta( lastServerMetaKey );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
self waittill( level.eventTypes.localClientEvent, event );
|
||||
|
||||
scripts\_integration_base::LogDebug( "Received client event " + event.type );
|
||||
|
||||
if ( event.type == level.eventTypes.clientDataReceived && event.data[0] == lastServerMetaKey )
|
||||
{
|
||||
clientData = self.pers[level.clientDataKey];
|
||||
lastServerPlayed = clientData.meta[lastServerMetaKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GetTotalShotsFired()
|
||||
{
|
||||
return maps\mp\_utility::getPlayerStat( "mostshotsfired" );
|
||||
}
|
||||
|
||||
_SetDvarIfUninitialized( dvar, value )
|
||||
SetDvarIfUninitializedWrapper( dvar, value )
|
||||
{
|
||||
SetDvarIfUninitialized( dvar, value );
|
||||
}
|
||||
|
||||
_waittill_notify_or_timeout( _notify, timeout )
|
||||
WaitillNotifyOrTimeoutWrapper( _notify, timeout )
|
||||
{
|
||||
common_scripts\utility::waittill_notify_or_timeout( _notify, timeout );
|
||||
}
|
||||
@ -111,140 +61,19 @@ Log2Console( logLevel, message )
|
||||
Print( "[" + logLevel + "] " + message + "\n" );
|
||||
}
|
||||
|
||||
_GetXUID()
|
||||
IsBotWrapper( client )
|
||||
{
|
||||
return client IsTestClient();
|
||||
}
|
||||
|
||||
GetXuidWrapper()
|
||||
{
|
||||
return self GetXUID();
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// GUID helpers
|
||||
/////////////////////////////////
|
||||
|
||||
SetPersistentData()
|
||||
WaitTillAnyTimeout( timeOut, string1, string2, string3, string4, string5 )
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
guidHigh = self GetPlayerData( "bests", "none" );
|
||||
guidLow = self GetPlayerData( "awards", "none" );
|
||||
persistentGuid = guidHigh + "," + guidLow;
|
||||
guidIsStored = guidHigh != 0 && guidLow != 0;
|
||||
|
||||
if ( guidIsStored )
|
||||
{
|
||||
// give IW4MAdmin time to collect IP
|
||||
wait( 15 );
|
||||
scripts\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid );
|
||||
scripts\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid );
|
||||
return;
|
||||
}
|
||||
|
||||
guid = self SplitGuid();
|
||||
|
||||
scripts\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow );
|
||||
|
||||
self SetPlayerData( "bests", "none", guid["high"] );
|
||||
self SetPlayerData( "awards", "none", guid["low"] );
|
||||
}
|
||||
|
||||
SplitGuid()
|
||||
{
|
||||
guid = self GetGuid();
|
||||
|
||||
if ( isDefined( self.guid ) )
|
||||
{
|
||||
guid = self.guid;
|
||||
}
|
||||
|
||||
firstPart = 0;
|
||||
secondPart = 0;
|
||||
stringLength = 17;
|
||||
firstPartExp = 0;
|
||||
secondPartExp = 0;
|
||||
|
||||
for ( i = stringLength - 1; i > 0; i-- )
|
||||
{
|
||||
char = GetSubStr( guid, i - 1, i );
|
||||
if ( char == "" )
|
||||
{
|
||||
char = "0";
|
||||
}
|
||||
|
||||
if ( i > stringLength / 2 )
|
||||
{
|
||||
value = GetIntForHexChar( char );
|
||||
power = Pow( 16, secondPartExp );
|
||||
secondPart = secondPart + ( value * power );
|
||||
secondPartExp++;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = GetIntForHexChar( char );
|
||||
power = Pow( 16, firstPartExp );
|
||||
firstPart = firstPart + ( value * power );
|
||||
firstPartExp++;
|
||||
}
|
||||
}
|
||||
|
||||
split = [];
|
||||
split["low"] = int( secondPart );
|
||||
split["high"] = int( firstPart );
|
||||
|
||||
return split;
|
||||
}
|
||||
|
||||
Pow( num, exponent )
|
||||
{
|
||||
result = 1;
|
||||
while( exponent != 0 )
|
||||
{
|
||||
result = result * num;
|
||||
exponent--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GetIntForHexChar( char )
|
||||
{
|
||||
char = ToLower( char );
|
||||
// generated by co-pilot because I can't be bothered to make it more "elegant"
|
||||
switch( char )
|
||||
{
|
||||
case "0":
|
||||
return 0;
|
||||
case "1":
|
||||
return 1;
|
||||
case "2":
|
||||
return 2;
|
||||
case "3":
|
||||
return 3;
|
||||
case "4":
|
||||
return 4;
|
||||
case "5":
|
||||
return 5;
|
||||
case "6":
|
||||
return 6;
|
||||
case "7":
|
||||
return 7;
|
||||
case "8":
|
||||
return 8;
|
||||
case "9":
|
||||
return 9;
|
||||
case "a":
|
||||
return 10;
|
||||
case "b":
|
||||
return 11;
|
||||
case "c":
|
||||
return 12;
|
||||
case "d":
|
||||
return 13;
|
||||
case "e":
|
||||
return 14;
|
||||
case "f":
|
||||
return 15;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
return common_scripts\utility::waittill_any_timeout( timeOut, string1, string2, string3, string4, string5 );
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
@ -427,10 +256,7 @@ HideImpl()
|
||||
|
||||
AlertImpl( event, data )
|
||||
{
|
||||
if ( level.eventBus.gamename == "IW5" ) {
|
||||
self thread maps\mp\gametypes\_hud_message::oldNotifyMessage( data["alertType"], data["message"], undefined, ( 1, 0, 0 ), "ui_mp_nukebomb_timer", 7.5 );
|
||||
}
|
||||
|
||||
self thread maps\mp\gametypes\_hud_message::oldNotifyMessage( data["alertType"], data["message"], undefined, ( 1, 0, 0 ), "ui_mp_nukebomb_timer", 7.5 );
|
||||
return "Sent alert to " + self.name;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
Init()
|
||||
{
|
||||
thread Setup();
|
||||
@ -6,10 +5,10 @@ Init()
|
||||
|
||||
Setup()
|
||||
{
|
||||
wait ( 0.05 );
|
||||
level endon( "game_ended" );
|
||||
|
||||
// it's possible that the notify type has not been defined yet so we have to hard code it
|
||||
level waittill( "IntegrationBootstrapInitialized" );
|
||||
level waittill( level.notifyTypes.integrationBootstrapInitialized );
|
||||
|
||||
level.commonFunctions.changeTeam = "ChangeTeam";
|
||||
level.commonFunctions.getTeamCounts = "GetTeamCounts";
|
||||
@ -18,7 +17,10 @@ Setup()
|
||||
level.commonFunctions.getClientTeam = "GetClientTeam";
|
||||
level.commonFunctions.getClientKillStreak = "GetClientKillStreak";
|
||||
level.commonFunctions.backupRestoreClientKillStreakData = "BackupRestoreClientKillStreakData";
|
||||
level.commonFunctions.getTotalShotsFired = "GetTotalShotsFired";
|
||||
level.commonFunctions.waitTillAnyTimeout = "WaitTillAnyTimeout";
|
||||
level.commonFunctions.isBot = "IsBot";
|
||||
level.commonFunctions.getXuid = "GetXuid";
|
||||
|
||||
level.overrideMethods[level.commonFunctions.changeTeam] = scripts\_integration_base::NotImplementedFunction;
|
||||
level.overrideMethods[level.commonFunctions.getTeamCounts] = scripts\_integration_base::NotImplementedFunction;
|
||||
@ -28,30 +30,52 @@ Setup()
|
||||
level.overrideMethods[level.commonFunctions.getClientKillStreak] = scripts\_integration_base::NotImplementedFunction;
|
||||
level.overrideMethods[level.commonFunctions.backupRestoreClientKillStreakData] = scripts\_integration_base::NotImplementedFunction;
|
||||
level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = scripts\_integration_base::NotImplementedFunction;
|
||||
level.overrideMethods["GetPlayerFromClientNum"] = ::GetPlayerFromClientNum;
|
||||
level.overrideMethods[level.commonFunctions.getXuid] = scripts\_integration_base::NotImplementedFunction;
|
||||
level.overrideMethods[level.commonFunctions.isBot] = scripts\_integration_base::NotImplementedFunction;
|
||||
|
||||
// these can be overridden per game if needed
|
||||
level.commonKeys.team1 = "allies";
|
||||
level.commonKeys.team2 = "axis";
|
||||
level.commonKeys.teamSpectator = "spectator";
|
||||
level.commonKeys.autoBalance = "sv_iw4madmin_autobalance";
|
||||
|
||||
level.eventTypes.connect = "connected";
|
||||
level.eventTypes.disconnect = "disconnect";
|
||||
level.eventTypes.joinTeam = "joined_team";
|
||||
level.eventTypes.joinSpec = "joined_spectators";
|
||||
level.eventTypes.spawned = "spawned_player";
|
||||
level.eventTypes.gameEnd = "game_ended";
|
||||
|
||||
level.eventTypes.urlRequested = "UrlRequested";
|
||||
level.eventTypes.urlRequestCompleted = "UrlRequestCompleted";
|
||||
level.eventTypes.registerCommandRequested = "RegisterCommandRequested";
|
||||
level.eventTypes.getCommandsRequested = "GetCommandsRequested";
|
||||
level.eventTypes.getBusModeRequested = "GetBusModeRequested";
|
||||
|
||||
level.eventCallbacks[level.eventTypes.urlRequestCompleted] = ::OnUrlRequestCompletedCallback;
|
||||
level.eventCallbacks[level.eventTypes.getCommandsRequested] = ::OnCommandsRequestedCallback;
|
||||
level.eventCallbacks[level.eventTypes.getBusModeRequested] = ::OnBusModeRequestedCallback;
|
||||
|
||||
level.iw4madminIntegrationDefaultPerformance = 200;
|
||||
level.notifyEntities = [];
|
||||
level.customCommands = [];
|
||||
|
||||
level notify( level.notifyTypes.sharedFunctionsInitialized );
|
||||
level waittill( level.notifyTypes.gameFunctionsInitialized );
|
||||
|
||||
if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 )
|
||||
|
||||
scripts\_integration_base::_SetDvarIfUninitialized( level.commonKeys.autoBalance, 0 );
|
||||
|
||||
if ( GetDvarInt( level.commonKeys.enabled ) != 1 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
level thread OnPlayerConnect();
|
||||
thread OnPlayerConnect();
|
||||
}
|
||||
|
||||
_IsBot( player )
|
||||
{
|
||||
return [[level.overrideMethods[level.commonFunctions.isBot]]]( player );
|
||||
}
|
||||
|
||||
OnPlayerConnect()
|
||||
@ -62,17 +86,23 @@ OnPlayerConnect()
|
||||
{
|
||||
level waittill( level.eventTypes.connect, player );
|
||||
|
||||
if ( scripts\_integration_base::_IsBot( player ) )
|
||||
if ( _IsBot( player ) )
|
||||
{
|
||||
// we don't want to track bots
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( !IsDefined( player.pers[level.clientDataKey] ) )
|
||||
{
|
||||
player.pers[level.clientDataKey] = spawnstruct();
|
||||
}
|
||||
|
||||
player thread OnPlayerSpawned();
|
||||
player thread OnPlayerJoinedTeam();
|
||||
player thread OnPlayerJoinedSpectators();
|
||||
player thread PlayerTrackingOnInterval();
|
||||
|
||||
if ( GetDvarInt( "sv_iw4madmin_autobalance" ) != 1 || !IsDefined( [[level.overrideMethods[level.commonFunctions.getTeamBased]]]() ) )
|
||||
if ( GetDvarInt( level.commonKeys.autoBalance ) != 1 || !IsDefined( [[level.overrideMethods[level.commonFunctions.getTeamBased]]]() ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -85,13 +115,341 @@ OnPlayerConnect()
|
||||
teamToJoin = player GetTeamToJoin();
|
||||
player [[level.overrideMethods[level.commonFunctions.changeTeam]]]( teamToJoin );
|
||||
|
||||
player thread OnClientFirstSpawn();
|
||||
player thread OnClientJoinedTeam();
|
||||
player thread OnClientDisconnect();
|
||||
player thread OnPlayerFirstSpawn();
|
||||
player thread OnPlayerDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
OnClientDisconnect()
|
||||
PlayerSpawnEvents()
|
||||
{
|
||||
self endon( level.eventTypes.disconnect );
|
||||
|
||||
clientData = self.pers[level.clientDataKey];
|
||||
|
||||
// this gives IW4MAdmin some time to register the player before making the request;
|
||||
// although probably not necessary some users might have a slow database or poll rate
|
||||
wait ( 2 );
|
||||
|
||||
if ( IsDefined( clientData.state ) && clientData.state == "complete" )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self scripts\_integration_base::RequestClientBasicData();
|
||||
|
||||
self waittill( level.eventTypes.clientDataReceived, clientEvent );
|
||||
|
||||
if ( clientData.permissionLevel == "User" || clientData.permissionLevel == "Flagged" )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self IPrintLnBold( "Welcome, your level is ^5" + clientData.permissionLevel );
|
||||
wait( 2.0 );
|
||||
self IPrintLnBold( "You were last seen ^5" + clientData.lastConnection + " ago" );
|
||||
}
|
||||
|
||||
|
||||
PlayerTrackingOnInterval()
|
||||
{
|
||||
self endon( level.eventTypes.disconnect );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
wait ( 120 );
|
||||
if ( IsAlive( self ) )
|
||||
{
|
||||
self SaveTrackingMetrics();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SaveTrackingMetrics()
|
||||
{
|
||||
if ( !IsDefined( self.persistentClientId ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scripts\_integration_base::LogDebug( "Saving tracking metrics for " + self.persistentClientId );
|
||||
|
||||
if ( !IsDefined( self.lastShotCount ) )
|
||||
{
|
||||
self.lastShotCount = 0;
|
||||
}
|
||||
|
||||
currentShotCount = self [[level.overrideMethods["GetTotalShotsFired"]]]();
|
||||
change = currentShotCount - self.lastShotCount;
|
||||
self.lastShotCount = currentShotCount;
|
||||
|
||||
scripts\_integration_base::LogDebug( "Total Shots Fired increased by " + change );
|
||||
|
||||
if ( !IsDefined( change ) )
|
||||
{
|
||||
change = 0;
|
||||
}
|
||||
|
||||
if ( change == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scripts\_integration_base::IncrementClientMeta( "TotalShotsFired", change, self.persistentClientId );
|
||||
}
|
||||
|
||||
OnBusModeRequestedCallback( event )
|
||||
{
|
||||
data = [];
|
||||
data["mode"] = GetDvar( level.commonKeys.busMode );
|
||||
data["directory"] = GetDvar( level.commonKeys.busDir );
|
||||
data["inLocation"] = level.eventBus.inLocation;
|
||||
data["outLocation"] = level.eventBus.outLocation;
|
||||
|
||||
scripts\_integration_base::LogDebug( "Bus mode requested" );
|
||||
|
||||
busModeRequest = scripts\_integration_base::BuildEventRequest( false, level.eventTypes.getBusModeRequested, "", undefined, data );
|
||||
scripts\_integration_base::QueueEvent( busModeRequest, level.eventTypes.getBusModeRequested, undefined );
|
||||
|
||||
scripts\_integration_base::LogDebug( "Bus mode updated" );
|
||||
|
||||
if ( GetDvar( level.commonKeys.busMode ) == "file" || GetDvar( level.commonKeys.busDir ) != "" )
|
||||
{
|
||||
level.busMethods[level.commonFunctions.getInboundData] = level.overrideMethods[level.commonFunctions.getInboundData];
|
||||
level.busMethods[level.commonFunctions.getOutboundData] = level.overrideMethods[level.commonFunctions.getOutboundData];
|
||||
level.busMethods[level.commonFunctions.setInboundData] = level.overrideMethods[level.commonFunctions.setInboundData];
|
||||
level.busMethods[level.commonFunctions.setOutboundData] = level.overrideMethods[level.commonFunctions.setOutboundData];
|
||||
}
|
||||
}
|
||||
|
||||
// #region register script command
|
||||
|
||||
OnCommandsRequestedCallback( event )
|
||||
{
|
||||
scripts\_integration_base::LogDebug( "Get commands requested" );
|
||||
thread SendCommands( event.data["name"] );
|
||||
}
|
||||
|
||||
SendCommands( commandName )
|
||||
{
|
||||
level endon( level.eventTypes.gameEnd );
|
||||
|
||||
for ( i = 0; i < level.customCommands.size; i++ )
|
||||
{
|
||||
data = level.customCommands[i];
|
||||
|
||||
if ( IsDefined( commandName ) && commandName != data["name"] )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
scripts\_integration_base::LogDebug( "Sending custom command " + ( i + 1 ) + "/" + level.customCommands.size + ": " + data["name"] );
|
||||
commandRegisterRequest = scripts\_integration_base::BuildEventRequest( false, level.eventTypes.registerCommandRequested, "", undefined, data );
|
||||
// not threading here as there might be a lot of commands to register
|
||||
scripts\_integration_base::QueueEvent( commandRegisterRequest, level.eventTypes.registerCommandRequested, undefined );
|
||||
}
|
||||
}
|
||||
|
||||
RegisterScriptCommandObject( command )
|
||||
{
|
||||
RegisterScriptCommand( command.eventKey, command.name, command.alias, command.description, command.minPermission, command.supportedGames, command.requiresTarget, command.handler );
|
||||
}
|
||||
|
||||
RegisterScriptCommand( eventKey, name, alias, description, minPermission, supportedGames, requiresTarget, handler )
|
||||
{
|
||||
if ( !IsDefined( eventKey ) )
|
||||
{
|
||||
scripts\_integration_base::LogError( "eventKey must be provided for script command" );
|
||||
return;
|
||||
}
|
||||
|
||||
data = [];
|
||||
|
||||
data["eventKey"] = eventKey;
|
||||
|
||||
if ( IsDefined( name ) )
|
||||
{
|
||||
data["name"] = name;
|
||||
}
|
||||
else
|
||||
{
|
||||
scripts\_integration_base::LogError( "name must be provided for script command" );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( IsDefined( alias ) )
|
||||
{
|
||||
data["alias"] = alias;
|
||||
}
|
||||
|
||||
if ( IsDefined( description ) )
|
||||
{
|
||||
data["description"] = description;
|
||||
}
|
||||
|
||||
if ( IsDefined( minPermission ) )
|
||||
{
|
||||
data["minPermission"] = minPermission;
|
||||
}
|
||||
|
||||
if ( IsDefined( supportedGames ) )
|
||||
{
|
||||
data["supportedGames"] = supportedGames;
|
||||
}
|
||||
|
||||
data["requiresTarget"] = false;
|
||||
|
||||
if ( IsDefined( requiresTarget ) )
|
||||
{
|
||||
data["requiresTarget"] = requiresTarget;
|
||||
}
|
||||
|
||||
if ( IsDefined( handler ) )
|
||||
{
|
||||
level.clientCommandCallbacks[eventKey + "Execute"] = handler;
|
||||
level.clientCommandRusAsTarget[eventKey + "Execute"] = data["requiresTarget"];
|
||||
}
|
||||
else
|
||||
{
|
||||
scripts\_integration_base::LogWarning( "handler not defined for script command " + name );
|
||||
}
|
||||
|
||||
level.customCommands[level.customCommands.size] = data;
|
||||
}
|
||||
|
||||
// #end region
|
||||
|
||||
// #region web requests
|
||||
|
||||
RequestUrlObject( request )
|
||||
{
|
||||
return RequestUrl( request.url, request.method, request.body, request.headers, request );
|
||||
}
|
||||
|
||||
RequestUrl( url, method, body, headers, webNotify )
|
||||
{
|
||||
if ( !IsDefined( webNotify ) )
|
||||
{
|
||||
webNotify = SpawnStruct();
|
||||
webNotify.url = url;
|
||||
webNotify.method = method;
|
||||
webNotify.body = body;
|
||||
webNotify.headers = headers;
|
||||
}
|
||||
|
||||
webNotify.index = GetNextNotifyEntity();
|
||||
|
||||
scripts\_integration_base::LogDebug( "next notify index is " + webNotify.index );
|
||||
level.notifyEntities[webNotify.index] = webNotify;
|
||||
|
||||
data = [];
|
||||
data["url"] = webNotify.url;
|
||||
data["entity"] = webNotify.index;
|
||||
|
||||
if ( IsDefined( method ) )
|
||||
{
|
||||
data["method"] = method;
|
||||
}
|
||||
|
||||
if ( IsDefined( body ) )
|
||||
{
|
||||
data["body"] = body;
|
||||
}
|
||||
|
||||
if ( IsDefined( headers ) )
|
||||
{
|
||||
headerString = "";
|
||||
|
||||
keys = GetArrayKeys( headers );
|
||||
for ( i = 0; i < keys.size; i++ )
|
||||
{
|
||||
headerString = headerString + keys[i] + ":" + headers[keys[i]] + ",";
|
||||
}
|
||||
|
||||
data["headers"] = headerString;
|
||||
}
|
||||
|
||||
webNotifyEvent = scripts\_integration_base::BuildEventRequest( true, level.eventTypes.urlRequested, "", webNotify.index, data );
|
||||
thread scripts\_integration_base::QueueEvent( webNotifyEvent, level.eventTypes.urlRequested, webNotify );
|
||||
webNotify thread WaitForUrlRequestComplete();
|
||||
|
||||
return webNotify;
|
||||
}
|
||||
|
||||
WaitForUrlRequestComplete()
|
||||
{
|
||||
level endon( level.eventTypes.gameEnd );
|
||||
|
||||
timeoutResult = self [[level.overrideMethods[level.commonFunctions.waitTillAnyTimeout]]]( 30, level.eventTypes.urlRequestCompleted );
|
||||
|
||||
if ( timeoutResult == level.eventBus.timeoutKey )
|
||||
{
|
||||
scripts\_integration_base::LogWarning( "Request to " + self.url + " timed out" );
|
||||
self notify ( level.eventTypes.urlRequestCompleted, "error" );
|
||||
}
|
||||
|
||||
scripts\_integration_base::LogDebug( "Request to " + self.url + " completed" );
|
||||
|
||||
level.notifyEntities[self.index] = undefined;
|
||||
}
|
||||
|
||||
OnUrlRequestCompletedCallback( event )
|
||||
{
|
||||
if ( !IsDefined( event ) || !IsDefined( event.data ) )
|
||||
{
|
||||
scripts\_integration_base::LogWarning( "Incomplete data for url request callback. [1]" );
|
||||
return;
|
||||
}
|
||||
|
||||
notifyEnt = event.data["entity"];
|
||||
response = event.data["response"];
|
||||
|
||||
if ( !IsDefined( notifyEnt ) || !IsDefined( response ) )
|
||||
{
|
||||
scripts\_integration_base::LogWarning( "Incomplete data for url request callback. [2] " + scripts\_integration_base::CoerceUndefined( notifyEnt ) + " , " + scripts\_integration_base::CoerceUndefined( response ) );
|
||||
return;
|
||||
}
|
||||
|
||||
webNotify = level.notifyEntities[int( notifyEnt )];
|
||||
|
||||
if ( !IsDefined( webNotify.response ) )
|
||||
{
|
||||
webNotify.response = response;
|
||||
}
|
||||
else
|
||||
{
|
||||
webNotify.response = webNotify.response + response;
|
||||
}
|
||||
|
||||
if ( int( event.data["remaining"] ) != 0 )
|
||||
{
|
||||
scripts\_integration_base::LogDebug( "Additional data available for url request " + notifyEnt + " (" + event.data["remaining"] + " chunks remaining)" );
|
||||
return;
|
||||
}
|
||||
|
||||
scripts\_integration_base::LogDebug( "Notifying " + notifyEnt + " that url request completed" );
|
||||
webNotify notify( level.eventTypes.urlRequestCompleted, webNotify.response );
|
||||
}
|
||||
|
||||
GetNextNotifyEntity()
|
||||
{
|
||||
max = level.notifyEntities.size + 1;
|
||||
|
||||
for ( i = 0; i < max; i++ )
|
||||
{
|
||||
if ( !IsDefined( level.notifyEntities[i] ) )
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
// #end region
|
||||
|
||||
// #region team balance
|
||||
|
||||
OnPlayerDisconnect()
|
||||
{
|
||||
level endon( level.eventTypes.gameEnd );
|
||||
self endon( "disconnect_logic_end" );
|
||||
@ -106,7 +464,7 @@ OnClientDisconnect()
|
||||
}
|
||||
}
|
||||
|
||||
OnClientJoinedTeam()
|
||||
OnPlayerJoinedTeam()
|
||||
{
|
||||
self endon( level.eventTypes.disconnect );
|
||||
|
||||
@ -114,6 +472,14 @@ OnClientJoinedTeam()
|
||||
{
|
||||
self waittill( level.eventTypes.joinTeam );
|
||||
|
||||
wait( 0.25 );
|
||||
LogPrint( GenerateJoinTeamString( false ) );
|
||||
|
||||
if ( GetDvarInt( level.commonKeys.autoBalance ) != 1 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( IsDefined( self.wasAutoBalanced ) && self.wasAutoBalanced )
|
||||
{
|
||||
self.wasAutoBalanced = false;
|
||||
@ -126,7 +492,7 @@ OnClientJoinedTeam()
|
||||
if ( newTeam != level.commonKeys.team1 && newTeam != level.commonKeys.team2 )
|
||||
{
|
||||
OnTeamSizeChanged();
|
||||
scripts\_integration_base::LogDebug( "not force balancing " + self.name + " because they switched to spec" );
|
||||
scripts\_integration_base::LogDebug( "not force balancing " + self.name + " because they switched to spec" );
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -141,12 +507,34 @@ OnClientJoinedTeam()
|
||||
}
|
||||
}
|
||||
|
||||
OnClientFirstSpawn()
|
||||
OnPlayerSpawned()
|
||||
{
|
||||
self endon( level.eventTypes.disconnect );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
self waittill( level.eventTypes.spawned );
|
||||
self thread PlayerSpawnEvents();
|
||||
}
|
||||
}
|
||||
|
||||
OnPlayerJoinedSpectators()
|
||||
{
|
||||
self endon( level.eventTypes.disconnect );
|
||||
|
||||
for( ;; )
|
||||
{
|
||||
self waittill( level.eventTypes.joinSpec );
|
||||
LogPrint( GenerateJoinTeamString( true ) );
|
||||
}
|
||||
}
|
||||
|
||||
OnPlayerFirstSpawn()
|
||||
{
|
||||
self endon( level.eventTypes.disconnect );
|
||||
timeoutResult = self [[level.overrideMethods[level.commonFunctions.waitTillAnyTimeout]]]( 30, level.eventTypes.spawned );
|
||||
|
||||
if ( timeoutResult != "timeout" )
|
||||
if ( timeoutResult != level.eventBus.timeoutKey )
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -341,7 +729,7 @@ GetClosestPerformanceClientForTeam( sourceTeam, excluded )
|
||||
|
||||
else if ( candidateValue < closest )
|
||||
{
|
||||
scripts\_integration_base::LogDebug( candidateValue + " is the new best value ");
|
||||
scripts\_integration_base::LogDebug( candidateValue + " is the new best value " );
|
||||
choice = players[i];
|
||||
closest = candidateValue;
|
||||
}
|
||||
@ -467,48 +855,6 @@ GetClientPerformanceOrDefault()
|
||||
return performance;
|
||||
}
|
||||
|
||||
GetPlayerFromClientNum( clientNum )
|
||||
{
|
||||
if ( clientNum < 0 )
|
||||
{
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for ( i = 0; i < level.players.size; i++ )
|
||||
{
|
||||
if ( level.players[i] getEntityNumber() == clientNum )
|
||||
{
|
||||
return level.players[i];
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
OnPlayerJoinedTeam()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
for( ;; )
|
||||
{
|
||||
self waittill( "joined_team" );
|
||||
// join spec and join team occur at the same moment - out of order logging would be problematic
|
||||
wait( 0.25 );
|
||||
LogPrint( GenerateJoinTeamString( false ) );
|
||||
}
|
||||
}
|
||||
|
||||
OnPlayerJoinedSpectators()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
for( ;; )
|
||||
{
|
||||
self waittill( "joined_spectators" );
|
||||
LogPrint( GenerateJoinTeamString( true ) );
|
||||
}
|
||||
}
|
||||
|
||||
GenerateJoinTeamString( isSpectator )
|
||||
{
|
||||
team = self.team;
|
||||
@ -540,49 +886,4 @@ GenerateJoinTeamString( isSpectator )
|
||||
return "JT;" + guid + ";" + self getEntityNumber() + ";" + team + ";" + self.name + "\n";
|
||||
}
|
||||
|
||||
PlayerTrackingOnInterval()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
wait ( 120 );
|
||||
if ( IsAlive( self ) )
|
||||
{
|
||||
self SaveTrackingMetrics();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SaveTrackingMetrics()
|
||||
{
|
||||
if ( !IsDefined( self.persistentClientId ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scripts\_integration_base::LogDebug( "Saving tracking metrics for " + self.persistentClientId );
|
||||
|
||||
if ( !IsDefined( self.lastShotCount ) )
|
||||
{
|
||||
self.lastShotCount = 0;
|
||||
}
|
||||
|
||||
currentShotCount = self [[level.overrideMethods["GetTotalShotsFired"]]]();
|
||||
change = currentShotCount - self.lastShotCount;
|
||||
self.lastShotCount = currentShotCount;
|
||||
|
||||
scripts\_integration_base::LogDebug( "Total Shots Fired increased by " + change );
|
||||
|
||||
if ( !IsDefined( change ) )
|
||||
{
|
||||
change = 0;
|
||||
}
|
||||
|
||||
if ( change == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scripts\_integration_base::IncrementClientMeta( "TotalShotsFired", change, self.persistentClientId );
|
||||
}
|
||||
// #end region
|
||||
|
@ -8,49 +8,22 @@ Init()
|
||||
Setup()
|
||||
{
|
||||
level endon( "game_ended" );
|
||||
waittillframeend;
|
||||
|
||||
// it's possible that the notify type has not been defined yet so we have to hard code it
|
||||
level waittill( "SharedFunctionsInitialized" );
|
||||
level waittill( level.notifyTypes.sharedFunctionsInitialized );
|
||||
level.eventBus.gamename = "T5";
|
||||
|
||||
scripts\_integration_base::RegisterLogger( ::Log2Console );
|
||||
|
||||
level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired;
|
||||
level.overrideMethods["SetDvarIfUninitialized"] = ::_SetDvarIfUninitialized;
|
||||
level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout;
|
||||
level.overrideMethods[level.commonFunctions.getXuid] = ::_GetXUID;
|
||||
level.overrideMethods[level.commonFunctions.getTotalShotsFired] = ::GetTotalShotsFired;
|
||||
level.overrideMethods[level.commonFunctions.setDvar] = ::SetDvarIfUninitializedWrapper;
|
||||
level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout] = ::WaitillNotifyOrTimeoutWrapper;
|
||||
level.overrideMethods[level.commonFunctions.isBot] = ::IsBotWrapper;
|
||||
level.overrideMethods[level.commonFunctions.getXuid] = ::GetXuidWrapper;
|
||||
|
||||
RegisterClientCommands();
|
||||
|
||||
_SetDvarIfUninitialized( "sv_iw4madmin_autobalance", 0 );
|
||||
|
||||
level notify( level.notifyTypes.gameFunctionsInitialized );
|
||||
|
||||
if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
level thread OnPlayerConnect();
|
||||
}
|
||||
|
||||
OnPlayerConnect()
|
||||
{
|
||||
level endon ( "game_ended" );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
level waittill( "connected", player );
|
||||
|
||||
if ( scripts\_integration_base::_IsBot( player ) )
|
||||
{
|
||||
// we don't want to track bots
|
||||
continue;
|
||||
}
|
||||
|
||||
//player thread SetPersistentData();
|
||||
player thread WaitForClientEvents();
|
||||
}
|
||||
}
|
||||
|
||||
RegisterClientCommands()
|
||||
@ -68,39 +41,17 @@ RegisterClientCommands()
|
||||
scripts\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl );
|
||||
}
|
||||
|
||||
WaitForClientEvents()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
// example of requesting a meta value
|
||||
lastServerMetaKey = "LastServerPlayed";
|
||||
// self scripts\_integration_base::RequestClientMeta( lastServerMetaKey );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
self waittill( level.eventTypes.localClientEvent, event );
|
||||
|
||||
scripts\_integration_base::LogDebug( "Received client event " + event.type );
|
||||
|
||||
if ( event.type == level.eventTypes.clientDataReceived && event.data[0] == lastServerMetaKey )
|
||||
{
|
||||
clientData = self.pers[level.clientDataKey];
|
||||
lastServerPlayed = clientData.meta[lastServerMetaKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GetTotalShotsFired()
|
||||
{
|
||||
return maps\mp\gametypes\_persistence::statGet( "total_shots" );
|
||||
}
|
||||
|
||||
_SetDvarIfUninitialized(dvar, value)
|
||||
SetDvarIfUninitializedWrapper( dvar, value )
|
||||
{
|
||||
maps\mp\_utility::set_dvar_if_unset(dvar, value);
|
||||
maps\mp\_utility::set_dvar_if_unset( dvar, value );
|
||||
}
|
||||
|
||||
_waittill_notify_or_timeout( msg, timer )
|
||||
WaitillNotifyOrTimeoutWrapper( msg, timer )
|
||||
{
|
||||
self endon( msg );
|
||||
wait( timer );
|
||||
@ -113,7 +64,6 @@ Log2Console( logLevel, message )
|
||||
|
||||
God()
|
||||
{
|
||||
|
||||
if ( !IsDefined( self.godmode ) )
|
||||
{
|
||||
self.godmode = false;
|
||||
@ -131,142 +81,16 @@ God()
|
||||
}
|
||||
}
|
||||
|
||||
_GetXUID()
|
||||
IsBotWrapper( client )
|
||||
{
|
||||
return client maps\mp\_utility::is_bot();
|
||||
}
|
||||
|
||||
GetXuidWrapper()
|
||||
{
|
||||
return self GetXUID();
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// GUID helpers
|
||||
/////////////////////////////////
|
||||
|
||||
/*SetPersistentData()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
guidHigh = self GetPlayerData( "bests", "none" );
|
||||
guidLow = self GetPlayerData( "awards", "none" );
|
||||
persistentGuid = guidHigh + "," + guidLow;
|
||||
guidIsStored = guidHigh != 0 && guidLow != 0;
|
||||
|
||||
if ( guidIsStored )
|
||||
{
|
||||
// give IW4MAdmin time to collect IP
|
||||
wait( 15 );
|
||||
scripts\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid );
|
||||
scripts\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid );
|
||||
return;
|
||||
}
|
||||
|
||||
guid = self SplitGuid();
|
||||
|
||||
scripts\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow );
|
||||
|
||||
self SetPlayerData( "bests", "none", guid["high"] );
|
||||
self SetPlayerData( "awards", "none", guid["low"] );
|
||||
}
|
||||
|
||||
SplitGuid()
|
||||
{
|
||||
guid = self GetGuid();
|
||||
|
||||
if ( isDefined( self.guid ) )
|
||||
{
|
||||
guid = self.guid;
|
||||
}
|
||||
|
||||
firstPart = 0;
|
||||
secondPart = 0;
|
||||
stringLength = 17;
|
||||
firstPartExp = 0;
|
||||
secondPartExp = 0;
|
||||
|
||||
for ( i = stringLength - 1; i > 0; i-- )
|
||||
{
|
||||
char = GetSubStr( guid, i - 1, i );
|
||||
if ( char == "" )
|
||||
{
|
||||
char = "0";
|
||||
}
|
||||
|
||||
if ( i > stringLength / 2 )
|
||||
{
|
||||
value = GetIntForHexChar( char );
|
||||
power = Pow( 16, secondPartExp );
|
||||
secondPart = secondPart + ( value * power );
|
||||
secondPartExp++;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = GetIntForHexChar( char );
|
||||
power = Pow( 16, firstPartExp );
|
||||
firstPart = firstPart + ( value * power );
|
||||
firstPartExp++;
|
||||
}
|
||||
}
|
||||
|
||||
split = [];
|
||||
split["low"] = int( secondPart );
|
||||
split["high"] = int( firstPart );
|
||||
|
||||
return split;
|
||||
}
|
||||
|
||||
Pow( num, exponent )
|
||||
{
|
||||
result = 1;
|
||||
while( exponent != 0 )
|
||||
{
|
||||
result = result * num;
|
||||
exponent--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GetIntForHexChar( char )
|
||||
{
|
||||
char = ToLower( char );
|
||||
// generated by co-pilot because I can't be bothered to make it more "elegant"
|
||||
switch( char )
|
||||
{
|
||||
case "0":
|
||||
return 0;
|
||||
case "1":
|
||||
return 1;
|
||||
case "2":
|
||||
return 2;
|
||||
case "3":
|
||||
return 3;
|
||||
case "4":
|
||||
return 4;
|
||||
case "5":
|
||||
return 5;
|
||||
case "6":
|
||||
return 6;
|
||||
case "7":
|
||||
return 7;
|
||||
case "8":
|
||||
return 8;
|
||||
case "9":
|
||||
return 9;
|
||||
case "a":
|
||||
return 10;
|
||||
case "b":
|
||||
return 11;
|
||||
case "c":
|
||||
return 12;
|
||||
case "d":
|
||||
return 13;
|
||||
case "e":
|
||||
return 14;
|
||||
case "f":
|
||||
return 15;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}*/
|
||||
|
||||
//////////////////////////////////
|
||||
// Command Implementations
|
||||
/////////////////////////////////
|
||||
|
@ -7,50 +7,25 @@ Init()
|
||||
|
||||
Setup()
|
||||
{
|
||||
level endon( "game_ended" );
|
||||
level endon( "end_game" );
|
||||
waittillframeend;
|
||||
|
||||
// it's possible that the notify type has not been defined yet so we have to hard code it
|
||||
level waittill( "SharedFunctionsInitialized" );
|
||||
level waittill( level.notifyTypes.sharedFunctionsInitialized );
|
||||
level.eventBus.gamename = "T5";
|
||||
level.eventTypes.gameEnd = "end_game";
|
||||
|
||||
scripts\_integration_base::RegisterLogger( ::Log2Console );
|
||||
|
||||
level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired;
|
||||
level.overrideMethods["SetDvarIfUninitialized"] = ::_SetDvarIfUninitialized;
|
||||
level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout;
|
||||
level.overrideMethods["GetPlayerFromClientNum"] = ::_GetPlayerFromClientNum;
|
||||
level.overrideMethods[level.commonFunctions.getTotalShotsFired] = ::GetTotalShotsFired;
|
||||
level.overrideMethods[level.commonFunctions.setDvar] = ::SetDvarIfUninitializedWrapper;
|
||||
level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout] = ::WaitillNotifyOrTimeoutWrapper;
|
||||
level.overrideMethods[level.commonFunctions.isBot] = ::IsBotWrapper;
|
||||
level.overrideMethods[level.commonFunctions.getXuid] = ::GetXuidWrapper;
|
||||
level.overrideMethods[level.commonFunction.getPlayerFromClientNum] = ::_GetPlayerFromClientNum;
|
||||
|
||||
RegisterClientCommands();
|
||||
|
||||
_SetDvarIfUninitialized( "sv_iw4madmin_autobalance", 0 );
|
||||
|
||||
level notify( level.notifyTypes.gameFunctionsInitialized );
|
||||
|
||||
if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
level thread OnPlayerConnect();
|
||||
}
|
||||
|
||||
OnPlayerConnect()
|
||||
{
|
||||
level endon ( "game_ended" );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
level waittill( "connected", player );
|
||||
|
||||
if ( scripts\_integration_base::_IsBot( player ) )
|
||||
{
|
||||
// we don't want to track bots
|
||||
continue;
|
||||
}
|
||||
|
||||
//player thread SetPersistentData();
|
||||
player thread WaitForClientEvents();
|
||||
}
|
||||
}
|
||||
|
||||
RegisterClientCommands()
|
||||
@ -68,45 +43,23 @@ RegisterClientCommands()
|
||||
scripts\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl );
|
||||
}
|
||||
|
||||
WaitForClientEvents()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
// example of requesting a meta value
|
||||
lastServerMetaKey = "LastServerPlayed";
|
||||
// self scripts\_integration_base::RequestClientMeta( lastServerMetaKey );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
self waittill( level.eventTypes.localClientEvent, event );
|
||||
|
||||
scripts\_integration_base::LogDebug( "Received client event " + event.type );
|
||||
|
||||
if ( event.type == level.eventTypes.clientDataReceived && event.data[0] == lastServerMetaKey )
|
||||
{
|
||||
clientData = self.pers[level.clientDataKey];
|
||||
lastServerPlayed = clientData.meta[lastServerMetaKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GetTotalShotsFired()
|
||||
{
|
||||
return 0; //ZM has no shot tracking. TODO: add tracking function for event weapon_fired
|
||||
}
|
||||
|
||||
_SetDvarIfUninitialized(dvar, value)
|
||||
SetDvarIfUninitializedWrapper( dvar, value )
|
||||
{
|
||||
if (GetDvar(dvar)=="" )
|
||||
if ( GetDvar( dvar ) == "" )
|
||||
{
|
||||
SetDvar(dvar, value);
|
||||
SetDvar( dvar, value );
|
||||
return value;
|
||||
}
|
||||
|
||||
return GetDvar(dvar);
|
||||
return GetDvar( dvar );
|
||||
}
|
||||
|
||||
_waittill_notify_or_timeout( msg, timer )
|
||||
WaitillNotifyOrTimeoutWrapper( msg, timer )
|
||||
{
|
||||
self endon( msg );
|
||||
wait( timer );
|
||||
@ -119,7 +72,6 @@ Log2Console( logLevel, message )
|
||||
|
||||
God()
|
||||
{
|
||||
|
||||
if ( !IsDefined( self.godmode ) )
|
||||
{
|
||||
self.godmode = false;
|
||||
@ -137,6 +89,16 @@ God()
|
||||
}
|
||||
}
|
||||
|
||||
IsBotWrapper( client )
|
||||
{
|
||||
return ( IsDefined ( client.pers["isBot"] ) && client.pers["isBot"] != 0 );
|
||||
}
|
||||
|
||||
GetXuidWrapper()
|
||||
{
|
||||
return self GetXUID();
|
||||
}
|
||||
|
||||
_GetPlayerFromClientNum( clientNum )
|
||||
{
|
||||
if ( clientNum < 0 )
|
||||
@ -144,11 +106,12 @@ _GetPlayerFromClientNum( clientNum )
|
||||
return undefined;
|
||||
}
|
||||
|
||||
players = GetPlayers("all");
|
||||
players = GetPlayers( "all" );
|
||||
|
||||
for ( i = 0; i < players.size; i++ )
|
||||
{
|
||||
scripts\_integration_base::LogDebug(i+"/"+players.size+ "=" + players[i].name);
|
||||
scripts\_integration_base::LogDebug( i+"/"+players.size+ "=" + players[i].name );
|
||||
|
||||
if ( players[i] getEntityNumber() == clientNum )
|
||||
{
|
||||
return players[i];
|
||||
@ -158,137 +121,6 @@ _GetPlayerFromClientNum( clientNum )
|
||||
return undefined;
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// GUID helpers
|
||||
/////////////////////////////////
|
||||
|
||||
/*SetPersistentData()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
guidHigh = self GetPlayerData( "bests", "none" );
|
||||
guidLow = self GetPlayerData( "awards", "none" );
|
||||
persistentGuid = guidHigh + "," + guidLow;
|
||||
guidIsStored = guidHigh != 0 && guidLow != 0;
|
||||
|
||||
if ( guidIsStored )
|
||||
{
|
||||
// give IW4MAdmin time to collect IP
|
||||
wait( 15 );
|
||||
scripts\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid );
|
||||
scripts\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid );
|
||||
return;
|
||||
}
|
||||
|
||||
guid = self SplitGuid();
|
||||
|
||||
scripts\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow );
|
||||
|
||||
self SetPlayerData( "bests", "none", guid["high"] );
|
||||
self SetPlayerData( "awards", "none", guid["low"] );
|
||||
}
|
||||
|
||||
SplitGuid()
|
||||
{
|
||||
guid = self GetGuid();
|
||||
|
||||
if ( isDefined( self.guid ) )
|
||||
{
|
||||
guid = self.guid;
|
||||
}
|
||||
|
||||
firstPart = 0;
|
||||
secondPart = 0;
|
||||
stringLength = 17;
|
||||
firstPartExp = 0;
|
||||
secondPartExp = 0;
|
||||
|
||||
for ( i = stringLength - 1; i > 0; i-- )
|
||||
{
|
||||
char = GetSubStr( guid, i - 1, i );
|
||||
if ( char == "" )
|
||||
{
|
||||
char = "0";
|
||||
}
|
||||
|
||||
if ( i > stringLength / 2 )
|
||||
{
|
||||
value = GetIntForHexChar( char );
|
||||
power = Pow( 16, secondPartExp );
|
||||
secondPart = secondPart + ( value * power );
|
||||
secondPartExp++;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = GetIntForHexChar( char );
|
||||
power = Pow( 16, firstPartExp );
|
||||
firstPart = firstPart + ( value * power );
|
||||
firstPartExp++;
|
||||
}
|
||||
}
|
||||
|
||||
split = [];
|
||||
split["low"] = int( secondPart );
|
||||
split["high"] = int( firstPart );
|
||||
|
||||
return split;
|
||||
}
|
||||
|
||||
Pow( num, exponent )
|
||||
{
|
||||
result = 1;
|
||||
while( exponent != 0 )
|
||||
{
|
||||
result = result * num;
|
||||
exponent--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GetIntForHexChar( char )
|
||||
{
|
||||
char = ToLower( char );
|
||||
// generated by co-pilot because I can't be bothered to make it more "elegant"
|
||||
switch( char )
|
||||
{
|
||||
case "0":
|
||||
return 0;
|
||||
case "1":
|
||||
return 1;
|
||||
case "2":
|
||||
return 2;
|
||||
case "3":
|
||||
return 3;
|
||||
case "4":
|
||||
return 4;
|
||||
case "5":
|
||||
return 5;
|
||||
case "6":
|
||||
return 6;
|
||||
case "7":
|
||||
return 7;
|
||||
case "8":
|
||||
return 8;
|
||||
case "9":
|
||||
return 9;
|
||||
case "a":
|
||||
return 10;
|
||||
case "b":
|
||||
return 11;
|
||||
case "c":
|
||||
return 12;
|
||||
case "d":
|
||||
return 13;
|
||||
case "e":
|
||||
return 14;
|
||||
case "f":
|
||||
return 15;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}*/
|
||||
|
||||
//////////////////////////////////
|
||||
// Command Implementations
|
||||
/////////////////////////////////
|
||||
@ -472,7 +304,7 @@ HideImpl( event, data )
|
||||
AlertImpl( event, data )
|
||||
{
|
||||
//self thread maps\mp\gametypes\_hud_message::oldNotifyMessage( data["alertType"], data["message"], undefined, ( 1, 0, 0 ), "mpl_sab_ui_suitcasebomb_timer", 7.5 );
|
||||
self IPrintLnBold(data["message"]);
|
||||
self IPrintLnBold( data["message"] );
|
||||
|
||||
return "Sent alert to " + self.name;
|
||||
}
|
||||
|
@ -9,49 +9,29 @@ Init()
|
||||
Setup()
|
||||
{
|
||||
level endon( "game_ended" );
|
||||
level endon( "end_game" );
|
||||
waittillframeend;
|
||||
|
||||
// it's possible that the notify type has not been defined yet so we have to hard code it
|
||||
level waittill( "SharedFunctionsInitialized" );
|
||||
level waittill( level.notifyTypes.sharedFunctionsInitialized );
|
||||
level.eventBus.gamename = "T6";
|
||||
|
||||
if ( sessionmodeiszombiesgame() )
|
||||
{
|
||||
level.eventTypes.gameEnd = "end_game";
|
||||
}
|
||||
|
||||
scripts\_integration_base::RegisterLogger( ::Log2Console );
|
||||
|
||||
level.overrideMethods["GetTotalShotsFired"] = ::GetTotalShotsFired;
|
||||
level.overrideMethods["SetDvarIfUninitialized"] = ::_SetDvarIfUninitialized;
|
||||
level.overrideMethods["waittill_notify_or_timeout"] = ::_waittill_notify_or_timeout;
|
||||
level.overrideMethods[level.commonFunctions.getXuid] = ::_GetXUID;
|
||||
level.overrideMethods[level.commonFunctions.getTotalShotsFired] = ::GetTotalShotsFired;
|
||||
level.overrideMethods[level.commonFunctions.setDvar] = ::SetDvarIfUninitializedWrapper;
|
||||
level.overrideMethods[level.commonFunctions.waittillNotifyOrTimeout] = ::WaitillNotifyOrTimeoutWrapper;
|
||||
level.overrideMethods[level.commonFunctions.isBot] = ::IsBotWrapper;
|
||||
level.overrideMethods[level.commonFunctions.getXuid] = ::GetXuidWrapper;
|
||||
level.overrideMethods[level.commonFunctions.waitTillAnyTimeout] = ::WaitTillAnyTimeout;
|
||||
|
||||
RegisterClientCommands();
|
||||
|
||||
_SetDvarIfUninitialized( "sv_iw4madmin_autobalance", 0 );
|
||||
|
||||
|
||||
level notify( level.notifyTypes.gameFunctionsInitialized );
|
||||
|
||||
if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
level thread OnPlayerConnect();
|
||||
}
|
||||
|
||||
OnPlayerConnect()
|
||||
{
|
||||
level endon ( "game_ended" );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
level waittill( "connected", player );
|
||||
|
||||
if ( scripts\_integration_base::_IsBot( player ) )
|
||||
{
|
||||
// we don't want to track bots
|
||||
continue;
|
||||
}
|
||||
|
||||
//player thread SetPersistentData();
|
||||
player thread WaitForClientEvents();
|
||||
}
|
||||
}
|
||||
|
||||
RegisterClientCommands()
|
||||
@ -69,39 +49,17 @@ RegisterClientCommands()
|
||||
scripts\_integration_base::AddClientCommand( "NoClip", false, ::NoClipImpl );
|
||||
}
|
||||
|
||||
WaitForClientEvents()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
// example of requesting a meta value
|
||||
lastServerMetaKey = "LastServerPlayed";
|
||||
// self scripts\_integration_base::RequestClientMeta( lastServerMetaKey );
|
||||
|
||||
for ( ;; )
|
||||
{
|
||||
self waittill( level.eventTypes.localClientEvent, event );
|
||||
|
||||
scripts\_integration_base::LogDebug( "Received client event " + event.type );
|
||||
|
||||
if ( event.type == level.eventTypes.clientDataReceived && event.data[0] == lastServerMetaKey )
|
||||
{
|
||||
clientData = self.pers[level.clientDataKey];
|
||||
lastServerPlayed = clientData.meta[lastServerMetaKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GetTotalShotsFired()
|
||||
{
|
||||
return self.pers[ "total_shots" ];
|
||||
return self.pers["total_shots"];
|
||||
}
|
||||
|
||||
_SetDvarIfUninitialized(dvar, value)
|
||||
SetDvarIfUninitializedWrapper( dvar, value )
|
||||
{
|
||||
maps\mp\_utility::set_dvar_if_unset(dvar, value);
|
||||
maps\mp\_utility::set_dvar_if_unset( dvar, value );
|
||||
}
|
||||
|
||||
_waittill_notify_or_timeout( msg, timer )
|
||||
WaitillNotifyOrTimeoutWrapper( msg, timer )
|
||||
{
|
||||
self endon( msg );
|
||||
wait( timer );
|
||||
@ -114,7 +72,6 @@ Log2Console( logLevel, message )
|
||||
|
||||
God()
|
||||
{
|
||||
|
||||
if ( !IsDefined( self.godmode ) )
|
||||
{
|
||||
self.godmode = false;
|
||||
@ -132,142 +89,21 @@ God()
|
||||
}
|
||||
}
|
||||
|
||||
_GetXUID()
|
||||
IsBotWrapper( client )
|
||||
{
|
||||
return client maps\mp\_utility::is_bot();
|
||||
}
|
||||
|
||||
GetXuidWrapper()
|
||||
{
|
||||
return self GetXUID();
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// GUID helpers
|
||||
/////////////////////////////////
|
||||
|
||||
/*SetPersistentData()
|
||||
WaitTillAnyTimeout( timeOut, string1, string2, string3, string4, string5 )
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
|
||||
guidHigh = self GetPlayerData( "bests", "none" );
|
||||
guidLow = self GetPlayerData( "awards", "none" );
|
||||
persistentGuid = guidHigh + "," + guidLow;
|
||||
guidIsStored = guidHigh != 0 && guidLow != 0;
|
||||
|
||||
if ( guidIsStored )
|
||||
{
|
||||
// give IW4MAdmin time to collect IP
|
||||
wait( 15 );
|
||||
scripts\_integration_base::LogDebug( "Uploading persistent guid " + persistentGuid );
|
||||
scripts\_integration_base::SetClientMeta( "PersistentClientGuid", persistentGuid );
|
||||
return;
|
||||
}
|
||||
|
||||
guid = self SplitGuid();
|
||||
|
||||
scripts\_integration_base::LogDebug( "Persisting client guid " + guidHigh + "," + guidLow );
|
||||
|
||||
self SetPlayerData( "bests", "none", guid["high"] );
|
||||
self SetPlayerData( "awards", "none", guid["low"] );
|
||||
return common_scripts\utility::waittill_any_timeout( timeOut, string1, string2, string3, string4, string5 );
|
||||
}
|
||||
|
||||
SplitGuid()
|
||||
{
|
||||
guid = self GetGuid();
|
||||
|
||||
if ( isDefined( self.guid ) )
|
||||
{
|
||||
guid = self.guid;
|
||||
}
|
||||
|
||||
firstPart = 0;
|
||||
secondPart = 0;
|
||||
stringLength = 17;
|
||||
firstPartExp = 0;
|
||||
secondPartExp = 0;
|
||||
|
||||
for ( i = stringLength - 1; i > 0; i-- )
|
||||
{
|
||||
char = GetSubStr( guid, i - 1, i );
|
||||
if ( char == "" )
|
||||
{
|
||||
char = "0";
|
||||
}
|
||||
|
||||
if ( i > stringLength / 2 )
|
||||
{
|
||||
value = GetIntForHexChar( char );
|
||||
power = Pow( 16, secondPartExp );
|
||||
secondPart = secondPart + ( value * power );
|
||||
secondPartExp++;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = GetIntForHexChar( char );
|
||||
power = Pow( 16, firstPartExp );
|
||||
firstPart = firstPart + ( value * power );
|
||||
firstPartExp++;
|
||||
}
|
||||
}
|
||||
|
||||
split = [];
|
||||
split["low"] = int( secondPart );
|
||||
split["high"] = int( firstPart );
|
||||
|
||||
return split;
|
||||
}
|
||||
|
||||
Pow( num, exponent )
|
||||
{
|
||||
result = 1;
|
||||
while( exponent != 0 )
|
||||
{
|
||||
result = result * num;
|
||||
exponent--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GetIntForHexChar( char )
|
||||
{
|
||||
char = ToLower( char );
|
||||
// generated by co-pilot because I can't be bothered to make it more "elegant"
|
||||
switch( char )
|
||||
{
|
||||
case "0":
|
||||
return 0;
|
||||
case "1":
|
||||
return 1;
|
||||
case "2":
|
||||
return 2;
|
||||
case "3":
|
||||
return 3;
|
||||
case "4":
|
||||
return 4;
|
||||
case "5":
|
||||
return 5;
|
||||
case "6":
|
||||
return 6;
|
||||
case "7":
|
||||
return 7;
|
||||
case "8":
|
||||
return 8;
|
||||
case "9":
|
||||
return 9;
|
||||
case "a":
|
||||
return 10;
|
||||
case "b":
|
||||
return 11;
|
||||
case "c":
|
||||
return 12;
|
||||
case "d":
|
||||
return 13;
|
||||
case "e":
|
||||
return 14;
|
||||
case "f":
|
||||
return 15;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}*/
|
||||
|
||||
//////////////////////////////////
|
||||
// Command Implementations
|
||||
/////////////////////////////////
|
||||
@ -456,17 +292,7 @@ HideImpl( event, data )
|
||||
|
||||
AlertImpl( event, data )
|
||||
{
|
||||
/*if ( !sessionmodeiszombiesgame() )
|
||||
{*/
|
||||
self thread oldNotifyMessage( data["alertType"], data["message"], undefined, ( 1, 0, 0 ), "mpl_sab_ui_suitcasebomb_timer", 7.5 );
|
||||
/*}
|
||||
else
|
||||
{
|
||||
self IPrintLnBold( data["alertType"] );
|
||||
self IPrintLnBold( data["message"] );
|
||||
}*/
|
||||
|
||||
|
||||
self thread oldNotifyMessage( data["alertType"], data["message"], undefined, ( 1, 0, 0 ), "mpl_sab_ui_suitcasebomb_timer", 7.5 );
|
||||
return "Sent alert to " + self.name;
|
||||
}
|
||||
|
||||
@ -482,7 +308,7 @@ GotoImpl( event, data )
|
||||
}
|
||||
}
|
||||
|
||||
GotoCoordImpl( event, data )
|
||||
GotoCoordImpl( data )
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
{
|
||||
@ -550,7 +376,7 @@ SetSpectatorImpl( event, data )
|
||||
/////////////////////////////////
|
||||
|
||||
/*
|
||||
1:1 the same on MP and ZM but in different includes. Since we probably want to be able to send Alerts on non teambased wagermatechs use our own copy.
|
||||
1:1 the same on MP and ZM but in different includes. Since we probably want to be able to send Alerts on non teambased wagermatches use our own copy.
|
||||
*/
|
||||
oldnotifymessage( titletext, notifytext, iconname, glowcolor, sound, duration )
|
||||
{
|
||||
@ -567,5 +393,3 @@ oldnotifymessage( titletext, notifytext, iconname, glowcolor, sound, duration )
|
||||
self.startmessagenotifyqueue[ self.startmessagenotifyqueue.size ] = notifydata;
|
||||
self notify( "received award" );
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,45 +1,48 @@
|
||||
init()
|
||||
Init()
|
||||
{
|
||||
|
||||
level.startmessagedefaultduration = 2;
|
||||
level.regulargamemessages = spawnstruct();
|
||||
level.regulargamemessages.waittime = 6;
|
||||
|
||||
|
||||
level thread onplayerconnect();
|
||||
thread OnPlayerConnect();
|
||||
}
|
||||
|
||||
onplayerconnect()
|
||||
OnPlayerConnect()
|
||||
{
|
||||
for ( ;; )
|
||||
{
|
||||
level waittill( "connecting", player );
|
||||
player thread displaypopupswaiter();
|
||||
player thread DisplayPopupsWaiter();
|
||||
}
|
||||
}
|
||||
|
||||
displaypopupswaiter()
|
||||
DisplayPopupsWaiter()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
self.ranknotifyqueue = [];
|
||||
if ( !isDefined( self.pers[ "challengeNotifyQueue" ] ) )
|
||||
|
||||
if ( !IsDefined( self.pers[ "challengeNotifyQueue" ] ) )
|
||||
{
|
||||
self.pers[ "challengeNotifyQueue" ] = [];
|
||||
}
|
||||
if ( !isDefined( self.pers[ "contractNotifyQueue" ] ) )
|
||||
if ( !IsDefined( self.pers[ "contractNotifyQueue" ] ) )
|
||||
{
|
||||
self.pers[ "contractNotifyQueue" ] = [];
|
||||
}
|
||||
|
||||
self.messagenotifyqueue = [];
|
||||
self.startmessagenotifyqueue = [];
|
||||
self.wagernotifyqueue = [];
|
||||
|
||||
while ( !level.gameended )
|
||||
{
|
||||
if ( self.startmessagenotifyqueue.size == 0 && self.messagenotifyqueue.size == 0 )
|
||||
{
|
||||
self waittill( "received award" );
|
||||
}
|
||||
|
||||
waittillframeend;
|
||||
|
||||
if ( level.gameended )
|
||||
{
|
||||
return;
|
||||
@ -50,7 +53,7 @@ displaypopupswaiter()
|
||||
{
|
||||
nextnotifydata = self.startmessagenotifyqueue[ 0 ];
|
||||
arrayremoveindex( self.startmessagenotifyqueue, 0, 0 );
|
||||
if ( isDefined( nextnotifydata.duration ) )
|
||||
if ( IsDefined( nextnotifydata.duration ) )
|
||||
{
|
||||
duration = nextnotifydata.duration;
|
||||
}
|
||||
@ -58,15 +61,18 @@ displaypopupswaiter()
|
||||
{
|
||||
duration = level.startmessagedefaultduration;
|
||||
}
|
||||
|
||||
self maps\mp\gametypes_zm\_hud_message::shownotifymessage( nextnotifydata, duration );
|
||||
wait duration;
|
||||
wait ( duration );
|
||||
|
||||
continue;
|
||||
}
|
||||
else if ( self.messagenotifyqueue.size > 0 )
|
||||
{
|
||||
nextnotifydata = self.messagenotifyqueue[ 0 ];
|
||||
arrayremoveindex( self.messagenotifyqueue, 0, 0 );
|
||||
if ( isDefined( nextnotifydata.duration ) )
|
||||
|
||||
if ( IsDefined( nextnotifydata.duration ) )
|
||||
{
|
||||
duration = nextnotifydata.duration;
|
||||
}
|
||||
@ -74,13 +80,14 @@ displaypopupswaiter()
|
||||
{
|
||||
duration = level.regulargamemessages.waittime;
|
||||
}
|
||||
|
||||
self maps\mp\gametypes_zm\_hud_message::shownotifymessage( nextnotifydata, duration );
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
wait 1;
|
||||
wait ( 1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
88
GameFiles/GameInterface/example_module.gsc
Normal file
88
GameFiles/GameInterface/example_module.gsc
Normal file
@ -0,0 +1,88 @@
|
||||
Init()
|
||||
{
|
||||
// this gives the game interface time to setup
|
||||
waittillframeend;
|
||||
thread ModuleSetup();
|
||||
}
|
||||
|
||||
ModuleSetup()
|
||||
{
|
||||
// waiting until the game specific functions are ready
|
||||
level waittill( level.notifyTypes.gameFunctionsInitialized );
|
||||
|
||||
RegisterCustomCommands();
|
||||
}
|
||||
|
||||
RegisterCustomCommands()
|
||||
{
|
||||
command = SpawnStruct();
|
||||
|
||||
// unique key for each command (how iw4madmin identifies the command)
|
||||
command.eventKey = "PrintLineCommand";
|
||||
|
||||
// name of the command (cannot conflict with existing command names)
|
||||
command.name = "println";
|
||||
|
||||
// short version of the command (cannot conflcit with existing command aliases)
|
||||
command.alias = "pl";
|
||||
|
||||
// description of what the command does
|
||||
command.description = "prints line to game";
|
||||
|
||||
// minimum permision required to execute
|
||||
// valid values: User, Trusted, Moderator, Administrator, SeniorAdmin, Owner
|
||||
command.minPermission = "Trusted";
|
||||
|
||||
// games the command is supported on
|
||||
// separate with comma or don't define for all
|
||||
// valid values: IW3, IW4, IW5, IW6, T4, T5, T6, T7, SHG1, CSGO, H1
|
||||
command.supportedGames = "IW4,IW5,T5,T6";
|
||||
|
||||
// indicates if a target player must be provided to execvute on
|
||||
command.requiresTarget = false;
|
||||
|
||||
// code to run when the command is executed
|
||||
command.handler = ::PrintLnCommandCallback;
|
||||
|
||||
// register the command with integration to be send to iw4madmin
|
||||
scripts\_integration_shared::RegisterScriptCommandObject( command );
|
||||
|
||||
// you can also register via parameters
|
||||
scripts\_integration_shared::RegisterScriptCommand( "AffirmationCommand", "affirm", "af", "provide affirmations", "User", undefined, false, ::AffirmationCommandCallback );
|
||||
}
|
||||
|
||||
PrintLnCommandCallback( event )
|
||||
{
|
||||
if ( IsDefined( event.data["args"] ) )
|
||||
{
|
||||
IPrintLnBold( event.data["args"] );
|
||||
return;
|
||||
}
|
||||
|
||||
scripts\_integration_base::LogDebug( "No data was provided for PrintLnCallback" );
|
||||
}
|
||||
|
||||
AffirmationCommandCallback( event, _ )
|
||||
{
|
||||
level endon( level.eventTypes.gameEnd );
|
||||
|
||||
request = SpawnStruct();
|
||||
request.url = "https://www.affirmations.dev";
|
||||
request.method = "GET";
|
||||
|
||||
// If making a post request you can also provide more data
|
||||
// request.body = "Body of the post message";
|
||||
// request.headers = [];
|
||||
// request.headers["Authorization"] = "api-key";
|
||||
|
||||
scripts\_integration_shared::RequestUrlObject( request );
|
||||
request waittill( level.eventTypes.urlRequestCompleted, response );
|
||||
|
||||
// horrible json parsing.. but it's just an example
|
||||
parsedResponse = strtok( response, "\"" );
|
||||
|
||||
if ( IsPlayer( self ) )
|
||||
{
|
||||
self IPrintLnBold ( "^5" + parsedResponse[parsedResponse.size - 2] );
|
||||
}
|
||||
}
|
@ -72,6 +72,9 @@ EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mute", "Plugins\Mute\Mute.csproj", "{259824F3-D860-4233-91D6-FF73D4DD8B18}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GameFiles", "GameFiles", "{6CBF412C-EFEE-45F7-80FD-AC402C22CDB9}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
GameFiles\deploy.bat = GameFiles\deploy.bat
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GameInterface", "GameInterface", "{5C2BE2A8-EA1D-424F-88E1-7FC33EEC2E55}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
@ -80,6 +83,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GameInterface", "GameInterf
|
||||
GameFiles\GameInterface\_integration_iw5.gsc = GameFiles\GameInterface\_integration_iw5.gsc
|
||||
GameFiles\GameInterface\_integration_shared.gsc = GameFiles\GameInterface\_integration_shared.gsc
|
||||
GameFiles\GameInterface\_integration_t5.gsc = GameFiles\GameInterface\_integration_t5.gsc
|
||||
GameFiles\GameInterface\_integration_t5zm.gsc = GameFiles\GameInterface\_integration_t5zm.gsc
|
||||
GameFiles\GameInterface\_integration_t6.gsc = GameFiles\GameInterface\_integration_t6.gsc
|
||||
GameFiles\GameInterface\_integration_t6zm_helper.gsc = GameFiles\GameInterface\_integration_t6zm_helper.gsc
|
||||
GameFiles\GameInterface\example_module.gsc = GameFiles\GameInterface\example_module.gsc
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AntiCheat", "AntiCheat", "{AB83BAC0-C539-424A-BF00-78487C10753C}"
|
||||
|
@ -147,6 +147,18 @@ namespace Integrations.Cod
|
||||
{
|
||||
var convertedRConPassword = ConvertEncoding(RConPassword);
|
||||
var convertedParameters = ConvertEncoding(parameters);
|
||||
byte SafeConversion(char c)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Convert.ToByte(c);
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
return (byte)'.';
|
||||
}
|
||||
};
|
||||
|
||||
switch (type)
|
||||
{
|
||||
@ -154,30 +166,30 @@ namespace Integrations.Cod
|
||||
waitForResponse = true;
|
||||
payload = string
|
||||
.Format(_config.CommandPrefixes.RConGetDvar, convertedRConPassword,
|
||||
convertedParameters + '\0').Select(Convert.ToByte).ToArray();
|
||||
convertedParameters + '\0').Select(SafeConversion).ToArray();
|
||||
break;
|
||||
case StaticHelpers.QueryType.SET_DVAR:
|
||||
payload = string
|
||||
.Format(_config.CommandPrefixes.RConSetDvar, convertedRConPassword,
|
||||
convertedParameters + '\0').Select(Convert.ToByte).ToArray();
|
||||
convertedParameters + '\0').Select(SafeConversion).ToArray();
|
||||
break;
|
||||
case StaticHelpers.QueryType.COMMAND:
|
||||
payload = string
|
||||
.Format(_config.CommandPrefixes.RConCommand, convertedRConPassword,
|
||||
convertedParameters + '\0').Select(Convert.ToByte).ToArray();
|
||||
convertedParameters + '\0').Select(SafeConversion).ToArray();
|
||||
break;
|
||||
case StaticHelpers.QueryType.GET_STATUS:
|
||||
waitForResponse = true;
|
||||
payload = (_config.CommandPrefixes.RConGetStatus + '\0').Select(Convert.ToByte).ToArray();
|
||||
payload = (_config.CommandPrefixes.RConGetStatus + '\0').Select(SafeConversion).ToArray();
|
||||
break;
|
||||
case StaticHelpers.QueryType.GET_INFO:
|
||||
waitForResponse = true;
|
||||
payload = (_config.CommandPrefixes.RConGetInfo + '\0').Select(Convert.ToByte).ToArray();
|
||||
payload = (_config.CommandPrefixes.RConGetInfo + '\0').Select(SafeConversion).ToArray();
|
||||
break;
|
||||
case StaticHelpers.QueryType.COMMAND_STATUS:
|
||||
waitForResponse = true;
|
||||
payload = string.Format(_config.CommandPrefixes.RConCommand, convertedRConPassword, "status\0")
|
||||
.Select(Convert.ToByte).ToArray();
|
||||
.Select(SafeConversion).ToArray();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -2,34 +2,61 @@
|
||||
const inDvar = 'sv_iw4madmin_in';
|
||||
const outDvar = 'sv_iw4madmin_out';
|
||||
const integrationEnabledDvar = 'sv_iw4madmin_integration_enabled';
|
||||
const pollingRate = 300;
|
||||
const groupSeparatorChar = '\x1d';
|
||||
const recordSeparatorChar = '\x1e';
|
||||
const unitSeparatorChar = '\x1f';
|
||||
|
||||
const init = (registerNotify, serviceResolver, config) => {
|
||||
let busFileIn = '';
|
||||
let busFileOut = '';
|
||||
let busMode = 'rcon';
|
||||
let busDir = '';
|
||||
|
||||
const init = (registerNotify, serviceResolver, config, scriptHelper) => {
|
||||
registerNotify('IManagementEventSubscriptions.ClientStateInitialized', (clientEvent, _) => plugin.onClientEnteredMatch(clientEvent));
|
||||
registerNotify('IGameServerEventSubscriptions.ServerValueReceived', (serverValueEvent, _) => plugin.onServerValueReceived(serverValueEvent));
|
||||
registerNotify('IGameServerEventSubscriptions.ServerValueSetCompleted', (serverValueEvent, _) => plugin.onServerValueSetCompleted(serverValueEvent));
|
||||
registerNotify('IGameServerEventSubscriptions.MonitoringStarted', (monitorStartEvent, _) => plugin.onServerMonitoringStart(monitorStartEvent));
|
||||
registerNotify('IGameEventSubscriptions.MatchStarted', (matchStartEvent, _) => plugin.onMatchStart(matchStartEvent));
|
||||
registerNotify('IManagementEventSubscriptions.ClientPenaltyAdministered', (penaltyEvent, _) => plugin.onPenalty(penaltyEvent));
|
||||
|
||||
plugin.onLoad(serviceResolver, config);
|
||||
plugin.onLoad(serviceResolver, config, scriptHelper);
|
||||
return plugin;
|
||||
};
|
||||
|
||||
const plugin = {
|
||||
author: 'RaidMax',
|
||||
version: '2.0',
|
||||
version: '2.1',
|
||||
name: 'Game Interface',
|
||||
serviceResolver: null,
|
||||
eventManager: null,
|
||||
logger: null,
|
||||
commands: null,
|
||||
scriptHelper: null,
|
||||
configWrapper: null,
|
||||
config: {
|
||||
pollingRate: 300
|
||||
},
|
||||
|
||||
onLoad: function (serviceResolver, config) {
|
||||
onLoad: function (serviceResolver, configWrapper, scriptHelper) {
|
||||
this.serviceResolver = serviceResolver;
|
||||
this.eventManager = serviceResolver.resolveService('IManager');
|
||||
this.logger = serviceResolver.resolveService('ILogger', ['ScriptPluginV2']);
|
||||
this.commands = commands;
|
||||
this.config = config;
|
||||
this.configWrapper = configWrapper;
|
||||
this.scriptHelper = scriptHelper;
|
||||
|
||||
const storedConfig = this.configWrapper.getValue('config', newConfig => {
|
||||
if (newConfig) {
|
||||
plugin.logger.logInformation('{Name} config reloaded.', plugin.name);
|
||||
plugin.config = newConfig;
|
||||
}
|
||||
});
|
||||
|
||||
if (storedConfig != null) {
|
||||
this.config = storedConfig
|
||||
} else {
|
||||
this.configWrapper.setValue('config', this.config);
|
||||
}
|
||||
},
|
||||
|
||||
onClientEnteredMatch: function (clientEvent) {
|
||||
@ -65,6 +92,9 @@ const plugin = {
|
||||
},
|
||||
|
||||
onServerValueSetCompleted: async function (serverValueEvent) {
|
||||
this.logger.logDebug('Set {dvarName}={dvarValue} success={success} from {server}', serverValueEvent.valueName,
|
||||
serverValueEvent.value, serverValueEvent.success, serverValueEvent.server.id);
|
||||
|
||||
if (serverValueEvent.valueName !== inDvar && serverValueEvent.valueName !== outDvar) {
|
||||
this.logger.logDebug('Ignoring set complete of {name}', serverValueEvent.valueName);
|
||||
return;
|
||||
@ -87,15 +117,22 @@ const plugin = {
|
||||
const input = serverState.inQueue.shift();
|
||||
|
||||
// if we queued an event then the next loop will be at the value set complete
|
||||
if (await this.processEventMessage(input, serverValueEvent.server)) {
|
||||
// return;
|
||||
}
|
||||
await this.processEventMessage(input, serverValueEvent.server);
|
||||
}
|
||||
|
||||
this.logger.logDebug('loop complete');
|
||||
// loop restarts
|
||||
this.requestGetDvar(inDvar, serverValueEvent.server);
|
||||
},
|
||||
|
||||
onServerMonitoringStart: function (monitorStartEvent) {
|
||||
this.initializeServer(monitorStartEvent.server);
|
||||
},
|
||||
|
||||
onMatchStart: function (matchStartEvent) {
|
||||
busMode = 'rcon';
|
||||
this.sendEventMessage(matchStartEvent.server, true, 'GetBusModeRequested', null, null, null, {});
|
||||
},
|
||||
|
||||
initializeServer: function (server) {
|
||||
servers[server.id] = {
|
||||
@ -125,7 +162,12 @@ const plugin = {
|
||||
serverState.enabled = true;
|
||||
serverState.running = true;
|
||||
serverState.initializationInProgress = false;
|
||||
|
||||
// todo: this might not work for all games
|
||||
responseEvent.server.rconParser.configuration.floodProtectInterval = 150;
|
||||
|
||||
this.sendEventMessage(responseEvent.server, true, 'GetBusModeRequested', null, null, null, {});
|
||||
this.sendEventMessage(responseEvent.server, true, 'GetCommandsRequested', null, null, null, {});
|
||||
this.requestGetDvar(inDvar, responseEvent.server);
|
||||
},
|
||||
|
||||
@ -136,7 +178,9 @@ const plugin = {
|
||||
const serverState = servers[responseEvent.server.id];
|
||||
serverState.outQueue.shift();
|
||||
|
||||
if (responseEvent.server.connectedClients.count === 0) {
|
||||
const utilities = importNamespace('SharedLibraryCore.Utilities');
|
||||
|
||||
if (responseEvent.server.connectedClients.count === 0 && !utilities.isDevelopment) {
|
||||
// no clients connected so we don't need to query
|
||||
serverState.running = false;
|
||||
return;
|
||||
@ -179,8 +223,8 @@ const plugin = {
|
||||
let messageQueued = false;
|
||||
const event = parseEvent(input);
|
||||
|
||||
this.logger.logDebug('Processing input... {eventType} {subType} {data} {clientNumber}', event.eventType,
|
||||
event.subType, event.data.toString(), event.clientNumber);
|
||||
this.logger.logDebug('Processing input... {eventType} {subType} {@data} {clientNumber}', event.eventType,
|
||||
event.subType, event.data, event.clientNumber);
|
||||
|
||||
const metaService = this.serviceResolver.ResolveService('IMetaServiceV2');
|
||||
const threading = importNamespace('System.Threading');
|
||||
@ -208,7 +252,7 @@ const plugin = {
|
||||
data = {
|
||||
level: client.level,
|
||||
clientId: client.clientId,
|
||||
lastConnection: client.lastConnection,
|
||||
lastConnection: client.timeSinceLastConnectionString,
|
||||
tag: tagMeta?.value ?? '',
|
||||
performance: clientStats?.performance ?? 200.0
|
||||
};
|
||||
@ -287,17 +331,74 @@ const plugin = {
|
||||
}
|
||||
}
|
||||
|
||||
if (event.eventType === 'UrlRequested') {
|
||||
const urlRequest = this.parseUrlRequest(event);
|
||||
|
||||
this.logger.logDebug('Making gamescript web request {@Request}', urlRequest);
|
||||
|
||||
this.scriptHelper.requestUrl(urlRequest, response => {
|
||||
this.logger.logDebug('Got response for gamescript web request - {Response}', response);
|
||||
|
||||
if (typeof response !== 'string' && !(response instanceof String)) {
|
||||
response = JSON.stringify(response);
|
||||
}
|
||||
|
||||
const max = 10;
|
||||
this.logger.logDebug(`response length ${response.length}`);
|
||||
|
||||
let quoteReplace = '\\"';
|
||||
// todo: may be more than just T6
|
||||
if (server.gameCode === 'T6') {
|
||||
quoteReplace = '\\\\"';
|
||||
}
|
||||
|
||||
let chunks = chunkString(response.replace(/"/gm, quoteReplace).replace(/[\n|\t]/gm, ''), 800);
|
||||
if (chunks.length > max) {
|
||||
this.logger.logWarning(`Response chunks greater than max (${max}). Data truncated!`);
|
||||
chunks = chunks.slice(0, max);
|
||||
}
|
||||
this.logger.logDebug(`chunk size ${chunks.length}`);
|
||||
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
this.sendEventMessage(server, false, 'UrlRequestCompleted', null, null,
|
||||
null, { entity: event.data.entity, remaining: chunks.length - (i + 1), response: chunks[i]});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (event.eventType === 'RegisterCommandRequested') {
|
||||
this.registerDynamicCommand(event);
|
||||
}
|
||||
|
||||
if (event.eventType === 'GetBusModeRequested') {
|
||||
if (event.data?.directory && event.data?.mode) {
|
||||
busMode = event.data.mode;
|
||||
busDir = event.data.directory.replace('\'', '').replace('"', '');
|
||||
if (event.data?.inLocation && event.data?.outLocation) {
|
||||
busFileIn = event.data?.inLocation;
|
||||
busFileOut = event.data?.outLocation;
|
||||
}
|
||||
this.logger.logDebug('Setting bus mode to {mode} {dir}', busMode, busDir);
|
||||
}
|
||||
}
|
||||
|
||||
tokenSource.dispose();
|
||||
return messageQueued;
|
||||
},
|
||||
|
||||
sendEventMessage: function (server, responseExpected, event, subtype, origin, target, data) {
|
||||
let targetClientNumber = -1;
|
||||
let originClientNumber = -1;
|
||||
|
||||
if (target != null) {
|
||||
targetClientNumber = target.ClientNumber;
|
||||
targetClientNumber = target.clientNumber;
|
||||
}
|
||||
|
||||
const output = `${responseExpected ? '1' : '0'};${event};${subtype};${origin.ClientNumber};${targetClientNumber};${buildDataString(data)}`;
|
||||
if (origin != null) {
|
||||
originClientNumber = origin.clientNumber
|
||||
}
|
||||
|
||||
const output = `${responseExpected ? '1' : '0'}${groupSeparatorChar}${event}${groupSeparatorChar}${subtype}${groupSeparatorChar}${originClientNumber}${groupSeparatorChar}${targetClientNumber}${groupSeparatorChar}${buildDataString(data)}`;
|
||||
this.logger.logDebug('Queuing output for server {output}', output);
|
||||
|
||||
servers[server.id].commandQueue.push(output);
|
||||
@ -305,9 +406,40 @@ const plugin = {
|
||||
|
||||
requestGetDvar: function (dvarName, server) {
|
||||
const serverState = servers[server.id];
|
||||
|
||||
if (dvarName !== integrationEnabledDvar && busMode === 'file') {
|
||||
this.scriptHelper.requestNotifyAfterDelay(250, () => {
|
||||
const io = importNamespace('System.IO');
|
||||
serverState.outQueue.push({});
|
||||
try {
|
||||
const content = io.File.ReadAllText(`${busDir}/${fileForDvar(dvarName)}`);
|
||||
plugin.onServerValueReceived({
|
||||
server: server,
|
||||
source: server,
|
||||
success: true,
|
||||
response: {
|
||||
name: dvarName,
|
||||
value: content
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
plugin.logger.logError('Could not get bus data {exception}', e.toString());
|
||||
plugin.onServerValueReceived({
|
||||
server: server,
|
||||
success: false,
|
||||
response: {
|
||||
name: dvarName
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const serverEvents = importNamespace('SharedLibraryCore.Events.Server');
|
||||
const requestEvent = new serverEvents.ServerValueRequestEvent(dvarName, server);
|
||||
requestEvent.delayMs = pollingRate;
|
||||
requestEvent.delayMs = this.config.pollingRate;
|
||||
requestEvent.timeoutMs = 2000;
|
||||
requestEvent.source = this.name;
|
||||
|
||||
@ -317,7 +449,7 @@ const plugin = {
|
||||
const diff = new Date().getTime() - end.getTime();
|
||||
|
||||
if (diff < extraDelay) {
|
||||
requestEvent.delayMs = (extraDelay - diff) + pollingRate;
|
||||
requestEvent.delayMs = (extraDelay - diff) + this.config.pollingRate;
|
||||
this.logger.logDebug('Increasing delay time to {Delay}ms due to recent map change', requestEvent.delayMs);
|
||||
}
|
||||
}
|
||||
@ -335,10 +467,39 @@ const plugin = {
|
||||
|
||||
requestSetDvar: function (dvarName, dvarValue, server) {
|
||||
const serverState = servers[server.id];
|
||||
|
||||
if ( busMode === 'file' ) {
|
||||
this.scriptHelper.requestNotifyAfterDelay(250, async () => {
|
||||
const io = importNamespace('System.IO');
|
||||
try {
|
||||
const path = `${busDir}/${fileForDvar(dvarName)}`;
|
||||
plugin.logger.logDebug('writing {value} to {file}', dvarValue, path);
|
||||
io.File.WriteAllText(path, dvarValue);
|
||||
serverState.outQueue.push({});
|
||||
await plugin.onServerValueSetCompleted({
|
||||
server: server,
|
||||
source: server,
|
||||
success: true,
|
||||
value: dvarValue,
|
||||
valueName: dvarName,
|
||||
});
|
||||
} catch (e) {
|
||||
plugin.logger.logError('Could not set bus data {exception}', e.toString());
|
||||
await plugin.onServerValueSetCompleted({
|
||||
server: server,
|
||||
success: false,
|
||||
valueName: dvarName,
|
||||
value: dvarValue
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const serverEvents = importNamespace('SharedLibraryCore.Events.Server');
|
||||
const requestEvent = new serverEvents.ServerValueSetRequestEvent(dvarName, dvarValue, server);
|
||||
requestEvent.delayMs = pollingRate;
|
||||
requestEvent.delayMs = this.config.pollingRate;
|
||||
requestEvent.timeoutMs = 2000;
|
||||
requestEvent.source = this.name;
|
||||
|
||||
@ -348,7 +509,7 @@ const plugin = {
|
||||
const diff = new Date().getTime() - end.getTime();
|
||||
|
||||
if (diff < extraDelay) {
|
||||
requestEvent.delayMs = (extraDelay - diff) + pollingRate;
|
||||
requestEvent.delayMs = (extraDelay - diff) + this.config.pollingRate;
|
||||
this.logger.logDebug('Increasing delay time to {Delay}ms due to recent map change', requestEvent.delayMs);
|
||||
}
|
||||
}
|
||||
@ -365,8 +526,64 @@ const plugin = {
|
||||
}
|
||||
},
|
||||
|
||||
onServerMonitoringStart: function (monitorStartEvent) {
|
||||
this.initializeServer(monitorStartEvent.server);
|
||||
parseUrlRequest: function(event) {
|
||||
const url = event.data?.url;
|
||||
|
||||
if (url === undefined) {
|
||||
this.logger.logWarning('No url provided for gamescript web request - {Event}', event);
|
||||
return;
|
||||
}
|
||||
|
||||
const body = event.data?.body;
|
||||
const method = event.data?.method || 'GET';
|
||||
const contentType = event.data?.contentType || 'text/plain';
|
||||
const headers = event.data?.headers;
|
||||
|
||||
const dictionary = System.Collections.Generic.Dictionary(System.String, System.String);
|
||||
const headerDict = new dictionary();
|
||||
|
||||
if (headers) {
|
||||
const eachHeader = headers.split(',');
|
||||
|
||||
for (let eachKeyValue of eachHeader) {
|
||||
const keyValueSplit = eachKeyValue.split(':');
|
||||
if (keyValueSplit.length === 2) {
|
||||
headerDict.add(keyValueSplit[0], keyValueSplit[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const script = importNamespace('IW4MAdmin.Application.Plugin.Script');
|
||||
return new script.ScriptPluginWebRequest(url, body, method, contentType, headerDict);
|
||||
},
|
||||
|
||||
registerDynamicCommand: function(event) {
|
||||
const commandWrapper = {
|
||||
commands: [{
|
||||
name: event.data['name'] || 'DEFAULT',
|
||||
description: event.data['description'] || 'DEFAULT',
|
||||
alias: event.data['alias'] || 'DEFAULT',
|
||||
permission: event.data['minPermission'] || 'DEFAULT',
|
||||
targetRequired: (event.data['targetRequired'] || '0') === '1',
|
||||
supportedGames: (event.data['supportedGames'] || '').split(','),
|
||||
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.owner, gameEvent.origin)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gameEvent.data === '--reload' && gameEvent.origin.level === 'Owner') {
|
||||
this.sendEventMessage(gameEvent.owner, true, 'GetCommandsRequested', null, null, null, { name: gameEvent.extra.name });
|
||||
} else {
|
||||
sendScriptCommand(gameEvent.owner, `${event.data['eventKey']}Execute`, gameEvent.origin, gameEvent.target, {
|
||||
args: gameEvent.data
|
||||
});
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
this.scriptHelper.registerDynamicCommand(commandWrapper);
|
||||
}
|
||||
};
|
||||
|
||||
@ -634,7 +851,7 @@ const parseEvent = (input) => {
|
||||
return {};
|
||||
}
|
||||
|
||||
const eventInfo = input.split(';');
|
||||
const eventInfo = input.split(groupSeparatorChar);
|
||||
|
||||
return {
|
||||
eventType: eventInfo[1],
|
||||
@ -652,7 +869,7 @@ const buildDataString = data => {
|
||||
let formattedData = '';
|
||||
|
||||
for (let [key, value] of Object.entries(data)) {
|
||||
formattedData += `${key}=${value}|`;
|
||||
formattedData += `${key}${unitSeparatorChar}${value}${recordSeparatorChar}`;
|
||||
}
|
||||
|
||||
return formattedData.slice(0, -1);
|
||||
@ -664,11 +881,11 @@ const parseDataString = data => {
|
||||
}
|
||||
|
||||
const dict = {};
|
||||
const split = data.split('|');
|
||||
const split = data.split(recordSeparatorChar);
|
||||
|
||||
for (let i = 0; i < split.length; i++) {
|
||||
const segment = split[i];
|
||||
const keyValue = segment.split('=');
|
||||
const keyValue = segment.split(unitSeparatorChar);
|
||||
if (keyValue.length !== 2) {
|
||||
continue;
|
||||
}
|
||||
@ -689,3 +906,20 @@ const validateEnabled = (server, origin) => {
|
||||
const isEmpty = (value) => {
|
||||
return value == null || false || value === '' || value === 'null';
|
||||
};
|
||||
|
||||
const chunkString = (str, chunkSize) => {
|
||||
const result = [];
|
||||
for (let i = 0; i < str.length; i += chunkSize) {
|
||||
result.push(str.slice(i, i + chunkSize));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const fileForDvar = (dvar) => {
|
||||
if (dvar === inDvar) {
|
||||
return busFileIn;
|
||||
}
|
||||
|
||||
return busFileOut;
|
||||
}
|
||||
|
@ -117,6 +117,9 @@ namespace SharedLibraryCore.Database.Models
|
||||
[NotMapped] public TeamType Team { get; set; }
|
||||
[NotMapped] public string TeamName { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public string TimeSinceLastConnectionString => (DateTime.UtcNow - LastConnection).HumanizeForCurrentCulture();
|
||||
|
||||
[NotMapped]
|
||||
// this is kinda dirty, but I need localizable level names
|
||||
public ClientPermission ClientPermission => new ClientPermission
|
||||
|
Loading…
Reference in New Issue
Block a user